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