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