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