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