The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

Font.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdfont/Font.vala.
Thread safety in text rendering
1 /* 2 Copyright (C) 2012, 2013, 2014 Johan Mattsson 3 4 This library is free software; you can redistribute it and/or modify 5 it under the terms of the GNU Lesser General Public License as 6 published by the Free Software Foundation; either version 3 of the 7 License, or (at your option) any later version. 8 9 This library is distributed in the hope that it will be useful, but 10 WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 Lesser General Public License for more details. 13 */ 14 15 using Cairo; 16 17 namespace BirdFont { 18 19 public enum FontFormat { 20 BIRDFONT, 21 BIRDFONT_PART, 22 FFI, 23 SVG, 24 FREETYPE 25 } 26 27 public class Font : GLib.Object { 28 29 /** Table with glyphs sorted by their unicode value. */ 30 public GlyphTable glyph_cache; 31 32 /** Table with glyphs sorted by their name. */ 33 public GlyphTable glyph_name; 34 35 /** Table with ligatures. */ 36 public GlyphTable ligature; 37 38 public Gee.ArrayList<BackgroundImage> background_images; 39 public string background_scale = "1"; 40 41 /** Top margin */ 42 public double top_limit; 43 44 /** Height of upper case letters. */ 45 public double top_position; 46 47 /** x-height upper bearing from origo. */ 48 public double xheight_position; 49 50 /** Base line coordinate from origo. */ 51 public double base_line; 52 53 /** Descender position */ 54 public double bottom_position; 55 56 /** Bottom margin */ 57 public double bottom_limit; 58 59 /** Custom guides. */ 60 public Gee.ArrayList<Line> custom_guides = new Gee.ArrayList<Line> (); 61 62 public string? font_file = null; 63 64 bool modified = false; 65 66 // name table descriptions 67 public string postscript_name; 68 public string name; 69 public string subfamily; 70 public string full_name; 71 public string unique_identifier; 72 public string version; 73 public string description; 74 public string copyright; 75 76 public bool bold = false; 77 public bool italic = false; 78 public int weight = 400; 79 80 public bool initialised = true; 81 82 OpenFontFormatReader otf; 83 bool otf_font = false; 84 85 public Gee.ArrayList<string> grid_width; 86 87 /** File format. */ 88 public FontFormat format = FontFormat.BIRDFONT; 89 90 SpacingData spacing; 91 92 bool read_only = false; 93 94 /** Save font as many .bfp files instead of one big .bf */ 95 bool bfp = false; 96 BirdFontPart bfp_file; 97 98 public Gee.ArrayList<Glyph> deleted_glyphs; 99 100 Ligatures ligatures_substitution; 101 102 public static string? default_license = null; 103 104 public Font () { 105 KerningClasses kerning_classes; 106 107 postscript_name = "Typeface"; 108 name = "Typeface"; 109 subfamily = "Regular"; 110 full_name = "Typeface"; 111 unique_identifier = "Typeface"; 112 version = "Version 1.0"; 113 description = ""; 114 copyright = default_license != null ? ((!) default_license).dup () : ""; 115 116 glyph_cache = new GlyphTable (); 117 glyph_name = new GlyphTable (); 118 ligature = new GlyphTable (); 119 120 grid_width = new Gee.ArrayList<string> (); 121 122 kerning_classes = new KerningClasses (this); 123 spacing = new SpacingData (kerning_classes); 124 125 top_limit = 84 ; 126 top_position = 72; 127 xheight_position = 56; 128 base_line = 0; 129 bottom_position = -20; 130 bottom_limit = -27; 131 132 bfp_file = new BirdFontPart (this); 133 134 deleted_glyphs = new Gee.ArrayList<Glyph> (); 135 ligatures_substitution = new Ligatures (this); 136 137 background_images = new Gee.ArrayList<BackgroundImage> (); 138 } 139 140 public static void set_default_license (string license) { 141 default_license = license; 142 } 143 144 public Ligatures get_ligatures () { 145 return ligatures_substitution; 146 } 147 148 public void set_weight (string w) { 149 int wi = int.parse (w); 150 151 if (wi > 0) { 152 weight = wi; 153 } 154 } 155 156 public string get_weight () { 157 return @"$weight"; 158 } 159 160 public void touch () { 161 modified = true; 162 } 163 164 public KerningClasses get_kerning_classes () { 165 return spacing.get_kerning_classes (); 166 } 167 168 public SpacingData get_spacing () { 169 return spacing; 170 } 171 172 public File get_backgrounds_folder () { 173 string fn = @"$(get_name ()) backgrounds"; 174 File f = get_child (BirdFont.get_settings_directory (), fn); 175 return f; 176 } 177 178 /** Retuns true if the current font has be modified */ 179 public bool is_modified () { 180 return modified; 181 } 182 183 /** Full path to this font file. */ 184 public string get_path () { 185 int i = 0; 186 string fn; 187 File f; 188 File file; 189 190 if (font_file != null) { 191 fn = (!) font_file; 192 193 // assume only absolute paths are used on windows 194 if (BirdFont.win32) { 195 return fn; 196 } else { 197 file = File.new_for_path (fn); 198 return (!) file.resolve_relative_path ("").get_path (); 199 } 200 } 201 202 StringBuilder sb = new StringBuilder (); 203 sb.append (Environment.get_home_dir ()); 204 sb.append (@"/$(get_name ()).bf"); 205 206 f = File.new_for_path (sb.str); 207 208 while (f.query_exists ()) { 209 sb.erase (); 210 sb.append (Environment.get_home_dir ()); 211 sb.append (@"/$(get_name ())$(++i).bf"); 212 f = File.new_for_path (sb.str); 213 } 214 215 return sb.str; 216 } 217 218 public string get_file_name () { 219 string p = get_path (); 220 int i = p.last_index_of ("/"); 221 222 if (i == -1) { 223 i = p.last_index_of ("\\"); 224 } 225 226 p = p.substring (i + 1); 227 228 return p; 229 } 230 231 /** @return an absolute path to the font folder. */ 232 public File get_folder () { 233 string p = get_folder_path (); 234 File fp = File.new_for_path (p); 235 236 if (BirdFont.win32) { 237 if (p.index_of (":\\") == -1) { 238 p = (!) fp.resolve_relative_path ("").get_path (); 239 } 240 } else { 241 if (!p.has_prefix ("/")) { 242 p = (!) fp.resolve_relative_path ("").get_path (); 243 } 244 } 245 246 return File.new_for_path (p); 247 } 248 249 /** @return a path to the font folder, it can be relative. */ 250 public string get_folder_path () { 251 string p = get_path (); 252 int i = p.last_index_of ("/"); 253 254 if (i == -1) { 255 i = p.last_index_of ("\\"); 256 } 257 258 if (i == -1) { 259 warning (@"Can not find folder in $p."); 260 p = "."; 261 } else { 262 p = p.substring (0, i); 263 } 264 265 if (p.index_of (":") != -1 && p.char_count () == 2) { 266 p += "\\"; 267 } 268 269 return p; 270 } 271 272 public double get_height () { 273 double r = top_position - base_line; 274 return (r > 0) ? r : -r; 275 } 276 277 public void set_name (string name) { 278 string n = name; 279 this.name = n; 280 } 281 282 public string get_full_name () { 283 return full_name; 284 } 285 286 public string get_name () { 287 return name; 288 } 289 290 public void print_all () { 291 stdout.printf ("Unicode:\n"); 292 glyph_cache.for_each((g) => { 293 stdout.printf (@"$(g.get_unicode ())\n"); 294 }); 295 296 stdout.printf ("Names:\n"); 297 glyph_name.for_each((g) => { 298 stdout.printf (@"$(g.get_name ())\n"); 299 }); 300 } 301 302 public bool has_glyph (string n) { 303 return get_glyph (n) != null; 304 } 305 306 public GlyphCollection get_nonmarking_return () { 307 Glyph g; 308 GlyphCollection gc; 309 310 if (has_glyph ("nonmarkingreturn")) { 311 return (!) get_glyph_collection ("nonmarkingreturn"); 312 } 313 314 gc = new GlyphCollection ('\r', "nonmarkingreturn"); 315 316 g = new Glyph ("nonmarkingreturn", '\r'); 317 g.left_limit = 0; 318 g.right_limit = 0; 319 g.remove_empty_paths (); 320 321 gc.set_unassigned (false); 322 gc.add_glyph (g); 323 324 return gc; 325 } 326 327 public GlyphCollection get_null_character () { 328 Glyph n; 329 GlyphCollection gc; 330 331 if (has_glyph ("null")) { 332 return (!) get_glyph_collection ("null"); 333 } 334 335 gc = new GlyphCollection ('\0', "null"); 336 n = new Glyph ("null", '\0'); 337 338 gc.add_glyph (n); 339 gc.set_unassigned (false); 340 341 n.left_limit = 0; 342 n.right_limit = 0; 343 n.remove_empty_paths (); 344 345 assert (n.path_list.size == 0); 346 347 return gc; 348 } 349 350 public GlyphCollection get_space () { 351 Glyph n; 352 GlyphCollection gc; 353 354 if (has_glyph (" ")) { 355 return (!) get_glyph_collection (" "); 356 } 357 358 if (has_glyph ("space")) { 359 return (!) get_glyph_collection ("space"); 360 } 361 362 gc = new GlyphCollection (' ', "space"); 363 364 n = new Glyph ("space", ' '); 365 n.left_limit = 0; 366 n.right_limit = 27; 367 n.remove_empty_paths (); 368 369 gc.add_glyph (n); 370 gc.set_unassigned (false); 371 372 return gc; 373 } 374 375 public GlyphCollection get_not_def_character () { 376 Glyph g; 377 GlyphCollection gc; 378 379 Path p; 380 Path i; 381 382 if (has_glyph (".notdef")) { 383 return (!) get_glyph_collection (".notdef"); 384 } 385 386 gc = new GlyphCollection ('\0', ".notdef"); 387 g = new Glyph (".notdef", 0); 388 p = new Path (); 389 i = new Path (); 390 391 gc.set_unassigned (true); 392 gc.add_glyph (g); 393 394 g.left_limit = -20; 395 g.right_limit = 33; 396 397 g.add_help_lines (); 398 399 p.add (-20, top_position - 5); 400 p.add (20, top_position - 5); 401 p.add (20, base_line + 5); 402 p.add (-20, base_line + 5); 403 p.close (); 404 405 i.add (-15, top_position - 10); 406 i.add (15, top_position - 10); 407 i.add (15, base_line + 10); 408 i.add (-15, base_line + 10); 409 i.reverse (); 410 i.close (); 411 412 g.add_path (i); 413 g.add_path (p); 414 415 i.recalculate_linear_handles (); 416 p.recalculate_linear_handles (); 417 418 return gc; 419 } 420 421 public void add_glyph_collection (GlyphCollection glyph_collection) { 422 GlyphCollection? gc; 423 424 if (unlikely (glyph_collection.get_name () == "")) { 425 warning ("Refusing to add glyph with name \"\", null character should be named null."); 426 return; 427 } 428 429 gc = glyph_name.get (glyph_collection.get_name ()); 430 if (unlikely (gc != null)) { 431 warning ("glyph has already been added"); 432 return; 433 } 434 435 glyph_name.insert (glyph_collection.get_name (), glyph_collection); 436 437 if (glyph_collection.get_unicode () != "") { 438 glyph_cache.insert ((!) glyph_collection.get_unicode (), glyph_collection); 439 } else { 440 glyph_cache.insert ((!) glyph_collection.get_name (), glyph_collection); 441 } 442 443 if (glyph_collection.is_unassigned ()) { 444 ligature.insert (glyph_collection.get_name (), glyph_collection); 445 } 446 } 447 448 public string get_name_for_character (unichar c) { 449 StringBuilder sb; 450 451 if (c == 0) { 452 return ".null".dup (); 453 } 454 455 sb = new StringBuilder (); 456 sb.append_unichar (c); 457 return sb.str; 458 } 459 460 public bool has_name (string name) { 461 return glyph_name.has_key (name); 462 } 463 464 public void delete_glyph (GlyphCollection glyph) { 465 glyph_cache.remove (glyph.get_unicode ()); 466 glyph_cache.remove (glyph.get_name ()); 467 glyph_name.remove (glyph.get_name ()); 468 ligature.remove (glyph.get_current ().get_name ()); 469 470 foreach (Glyph g in glyph.get_version_list ().glyphs) { 471 deleted_glyphs.add (g); 472 } 473 } 474 475 // FIXME: the order of ligature substitutions 476 public GlyphCollection? get_ligature (uint indice) { 477 return ligature.nth (indice); 478 } 479 480 /** Obtain all versions and alterntes for this glyph. */ 481 public GlyphCollection? get_glyph_collection (string glyph) { 482 GlyphCollection? gc = get_cached_glyph_collection (glyph); 483 Glyph? g; 484 485 if (gc == null && otf_font) { 486 // load it from otf file if we need to 487 g = otf.read_glyph (glyph); 488 489 if (g != null) { 490 return get_cached_glyph_collection (glyph); 491 } 492 } 493 494 return gc; 495 } 496 497 /** Get glyph collection by unichar code. */ 498 public GlyphCollection? get_cached_glyph_collection (string unichar_code) { 499 GlyphCollection? gc = null; 500 gc = glyph_cache.get (unichar_code); 501 return gc; 502 } 503 504 /** Get glyph collection by name. */ 505 public GlyphCollection? get_glyph_collection_by_name (string? glyph) { 506 // TODO: load from disk here if needed. 507 GlyphCollection? gc = null; 508 509 if (glyph != null) { 510 gc = glyph_name.get ((!) glyph); 511 } 512 513 return gc; 514 } 515 516 /** Get glyph by name. */ 517 public Glyph? get_glyph_by_name (string glyph) { 518 GlyphCollection? gc = get_glyph_collection_by_name (glyph); 519 520 if (gc == null) { 521 return null; 522 } 523 524 return ((!)gc).get_current (); 525 } 526 527 public Glyph? get_glyph (string name) { 528 GlyphCollection? gc = null; 529 gc = glyph_name.get (name); 530 531 if (gc == null || ((!)gc).length () == 0) { 532 return null; 533 } 534 535 return ((!)gc).get_current (); 536 } 537 538 public GlyphCollection? get_glyph_collection_indice (unichar glyph_indice) { 539 if (!(0 <= glyph_indice < glyph_name.length ())) { 540 return null; 541 } 542 543 return glyph_name.nth (glyph_indice); 544 } 545 546 public Glyph? get_glyph_indice (unichar glyph_indice) { 547 GlyphCollection? gc; 548 549 gc = get_glyph_collection_indice (glyph_indice); 550 551 if (gc != null) { 552 return ((!) gc).get_current (); 553 } 554 555 return null; 556 } 557 558 public void add_background_image (BackgroundImage image) { 559 background_images.add (image); 560 } 561 562 /** Delete temporary rescue files. */ 563 public void delete_backup () { 564 File dir = BirdFont.get_backup_directory (); 565 File? new_file = null; 566 File file; 567 string backup_file; 568 569 new_file = get_child (dir, @"$(name).bf"); 570 backup_file = (!) ((!) new_file).get_path (); 571 572 try { 573 file = File.new_for_path (backup_file); 574 if (file.query_exists ()) { 575 file.delete (); 576 } 577 } catch (GLib.Error e) { 578 stderr.printf (@"Failed to delete backup\n"); 579 warning (@"$(e.message) \n"); 580 } 581 } 582 583 /** Returns path to backup file. */ 584 public string save_backup () { 585 File dir = BirdFont.get_backup_directory (); 586 File? temp_file = null; 587 string backup_file; 588 BirdFontFile birdfont_file = new BirdFontFile (this); 589 590 temp_file = get_child (dir, @"$(name).bf"); 591 backup_file = (!) ((!) temp_file).get_path (); 592 backup_file = backup_file.replace (" ", "_"); 593 594 if (get_path () == backup_file) { 595 warning ("Refusing to write backup of a backup."); 596 return backup_file; 597 } 598 599 birdfont_file.write_font_file (backup_file, true); 600 return backup_file; 601 } 602 603 public void init_bfp (string directory) { 604 try { 605 bfp_file = new BirdFontPart (this); 606 bfp_file.create_directory (directory); 607 bfp_file.save (); 608 this.bfp = true; 609 } catch (GLib.Error e) { 610 warning (e.message); 611 // FIXME: notify user 612 } 613 } 614 615 public void set_bfp (bool bfp) { 616 this.bfp = bfp; 617 } 618 619 public bool is_bfp () { 620 return bfp; 621 } 622 623 public void save () { 624 if (is_bfp ()) { 625 save_bfp (); 626 } else { 627 save_bf (); 628 } 629 } 630 631 public bool save_bfp () { 632 return bfp_file.save (); 633 } 634 635 public void save_bf () { 636 Font font; 637 BirdFontFile birdfont_file = new BirdFontFile (this); 638 string path; 639 bool file_written; 640 641 if (font_file == null) { 642 warning ("File name not set."); 643 return; 644 } 645 646 path = (!) font_file; 647 file_written = birdfont_file.write_font_file (path); 648 649 if (read_only) { 650 warning (@"$path is write protected."); 651 TooltipArea.show_text (t_("The file is write protected.")); 652 return; 653 } 654 655 if (!path.has_suffix (".bf")) { 656 warning ("Expecting .bf format."); 657 return; 658 } 659 660 if (file_written) { 661 // delete the backup when the font is saved 662 font = BirdFont.get_current_font (); 663 font.delete_backup (); 664 } 665 666 modified = false; 667 } 668 669 public void set_font_file (string path) { 670 font_file = path; 671 modified = false; 672 } 673 674 public uint length () { 675 return glyph_name.length (); 676 } 677 678 public bool is_empty () { 679 return (glyph_name.length () == 0); 680 } 681 682 public void set_file (string path) { 683 font_file = path; 684 } 685 686 public bool load () { 687 string path; 688 bool loaded = false; 689 690 initialised = true; 691 otf_font = false; 692 693 if (font_file == null) { 694 warning ("No file name."); 695 return false; 696 } 697 698 path = (!) font_file; 699 700 grid_width.clear (); 701 702 // empty cache and fill it with new glyphs from disk 703 glyph_cache.remove_all (); 704 glyph_name.remove_all (); 705 ligature.remove_all (); 706 707 if (path.has_suffix (".svg")) { 708 Toolbox.select_tool_by_name ("cubic_points"); 709 loaded = parse_svg_file (path); 710 711 if (!loaded) { 712 warning ("Failed to load SVG font."); 713 } 714 715 format = FontFormat.SVG; 716 } 717 718 if (path.has_suffix (".ffi")) { 719 loaded = parse_bf_file (path); 720 format = FontFormat.FFI; 721 } 722 723 if (path.has_suffix (".bf")) { 724 loaded = parse_bf_file (path); 725 format = FontFormat.BIRDFONT; 726 } 727 728 if (path.has_suffix (".bfp")) { 729 loaded = parse_bfp_file (path); 730 format = FontFormat.BIRDFONT_PART; 731 } 732 733 if (path.has_suffix (".ttf")) { 734 loaded = parse_freetype_file (path); 735 736 if (!loaded) { 737 warning ("Failed to load TTF font."); 738 } 739 740 format = FontFormat.FREETYPE; 741 742 // run the old parser for debugging puposes 743 if (BirdFont.has_argument ("--test")) { 744 try { 745 OpenFontFormatReader or = new OpenFontFormatReader (); 746 or.parse_index (path); 747 } catch (GLib.Error e) { 748 warning (e.message); 749 } 750 } 751 752 font_file = null; // make sure BirdFont asks where to save the file 753 } 754 755 if (path.has_suffix (".otf")) { 756 loaded = parse_freetype_file (path); 757 758 if (!loaded) { 759 warning ("Failed to load OTF font."); 760 } 761 762 format = FontFormat.FREETYPE; 763 764 font_file = null; 765 } 766 767 return loaded; 768 } 769 770 private bool parse_bfp_file (string path) { 771 return bfp_file.load (path); 772 } 773 774 private bool parse_bf_file (string path) { 775 BirdFontFile font = new BirdFontFile (this); 776 return font.load (path); 777 } 778 779 private bool parse_freetype_file (string path) { 780 string font_data; 781 StringBuilder? data; 782 int error; 783 bool parsed; 784 BirdFontFile bf_font = new BirdFontFile (this); 785 786 data = load_freetype_font (path, out error); 787 788 if (error != 0) { 789 warning ("Failed to load freetype font."); 790 return false; 791 } 792 793 if (data == null) { 794 warning ("No svg data."); 795 return false; 796 } 797 798 font_data = ((!) data).str; 799 parsed = bf_font.load_data (font_data); 800 801 if (!parsed) { 802 warning ("Failed to parse loaded freetype font."); 803 } 804 805 return parsed; 806 } 807 808 private bool parse_svg_file (string path) { 809 SvgFont svg_font = new SvgFont (this); 810 svg_font.load (path); 811 return true; 812 } 813 814 public bool parse_otf_file (string path) throws GLib.Error { 815 otf = new OpenFontFormatReader (); 816 otf_font = true; 817 otf.parse_index (path); 818 return true; 819 } 820 821 public void set_read_only (bool r) { 822 read_only = r; 823 } 824 825 public static unichar to_unichar (string unicode) { 826 int index = 2; 827 int i = 0; 828 unichar c; 829 unichar rc = 0; 830 bool r; 831 832 if (unlikely (!unicode.has_prefix ("U+") && !unicode.has_prefix ("u+"))) { 833 warning (@"All unicode values must begin with U+ ($unicode)"); 834 return '\0'; 835 } 836 837 try { 838 while (r = unicode.get_next_char (ref index, out c)) { 839 rc <<= 4; 840 rc += hex_to_oct (c); 841 842 if (++i > 6) { 843 throw new ConvertError.FAILED ("i > 6"); 844 } 845 } 846 } catch (ConvertError e) { 847 warning (@"unicode: $unicode\n"); 848 warning (e.message); 849 rc = '\0'; 850 } 851 852 return rc; 853 } 854 855 private static string oct_to_hex (uint8 o) { 856 switch (o) { 857 case 10: return "a"; 858 case 11: return "b"; 859 case 12: return "c"; 860 case 13: return "d"; 861 case 14: return "e"; 862 case 15: return "f"; 863 } 864 865 return_val_if_fail (0 <= o <= 9, "-".dup ()); 866 867 return o.to_string (); 868 } 869 870 private static uint8 hex_to_oct (unichar o) 871 throws ConvertError { 872 StringBuilder s = new StringBuilder (); 873 s.append_unichar (o); 874 875 switch (o) { 876 case 'a': return 10; 877 case 'b': return 11; 878 case 'c': return 12; 879 case 'd': return 13; 880 case 'e': return 14; 881 case 'f': return 15; 882 case 'A': return 10; 883 case 'B': return 11; 884 case 'C': return 12; 885 case 'D': return 13; 886 case 'E': return 14; 887 case 'F': return 15; 888 } 889 890 if (!('0' <= o <= '9')) { 891 throw new ConvertError.FAILED (@"Expecting a number ($(s.str))."); 892 } 893 894 return (uint8) (o - '0'); 895 } 896 897 public static string to_hex (unichar ch) { 898 StringBuilder s = new StringBuilder (); 899 s.append ("U+"); 900 s.append (to_hex_code (ch)); 901 return s.str; 902 } 903 904 public static string to_hex_code (unichar ch) { 905 StringBuilder s = new StringBuilder (); 906 907 uint8 a = (uint8)(ch & 0x00000F); 908 uint8 b = (uint8)((ch & 0x0000F0) >> 4 * 1); 909 uint8 c = (uint8)((ch & 0x000F00) >> 4 * 2); 910 uint8 d = (uint8)((ch & 0x00F000) >> 4 * 3); 911 uint8 e = (uint8)((ch & 0x0F0000) >> 4 * 4); 912 uint8 f = (uint8)((ch & 0xF00000) >> 4 * 5); 913 914 if (e != 0 || f != 0) { 915 s.append (oct_to_hex (f)); 916 s.append (oct_to_hex (e)); 917 } 918 919 if (c != 0 || d != 0) { 920 s.append (oct_to_hex (d)); 921 s.append (oct_to_hex (c)); 922 } 923 924 s.append (oct_to_hex (b)); 925 s.append (oct_to_hex (a)); 926 927 return s.str; 928 } 929 } 930 931 } 932