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