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