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