The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

BirdFontFile.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/BirdFontFile.vala.
Add contextual ligatures to the GUI and fix directory privileges
1 /* 2 Copyright (C) 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 using Bird; 15 16 namespace BirdFont { 17 18 /** 19 * BirdFont file format. This class can parse both the old ffi format 20 * and the new bf format. 21 */ 22 class BirdFontFile : GLib.Object { 23 24 Font font; 25 26 public BirdFontFile (Font f) { 27 font = f; 28 } 29 30 /** Load a new .bf file. 31 * @param path path to a valid .bf file 32 */ 33 public bool load (string path) { 34 string xml_data; 35 XmlParser parser; 36 bool ok = false; 37 38 try { 39 FileUtils.get_contents(path, out xml_data); 40 41 font.background_images.clear (); 42 font.font_file = path; 43 44 parser = new XmlParser (xml_data); 45 ok = load_xml (parser); 46 } catch (GLib.FileError e) { 47 warning (e.message); 48 } 49 50 return ok; 51 } 52 53 public bool load_part (string bfp_file) { 54 string xml_data; 55 XmlParser parser; 56 bool ok = false; 57 58 try { 59 FileUtils.get_contents(bfp_file, out xml_data); 60 parser = new XmlParser (xml_data); 61 ok = load_xml (parser); 62 } catch (GLib.FileError e) { 63 warning (e.message); 64 } 65 66 return ok; 67 } 68 69 /** Load a new .bf file. 70 * @param xml_data data for a valid .bf file 71 */ 72 public bool load_data (string xml_data) { 73 bool ok; 74 XmlParser parser; 75 76 font.font_file = "typeface.bf"; 77 parser = new XmlParser (xml_data); 78 ok = load_xml (parser); 79 80 return ok; 81 } 82 83 private bool load_xml (XmlParser parser) { 84 bool ok = true; 85 86 create_background_files (parser.get_root_tag ()); 87 ok = parse_file (parser.get_root_tag ()); 88 89 return ok; 90 } 91 92 public bool write_font_file (string path, bool backup = false) { 93 try { 94 DataOutputStream os; 95 File file; 96 97 file = File.new_for_path (path); 98 99 if (file.query_file_type (0) == FileType.DIRECTORY) { 100 warning (@"Can't save font. $path is a directory."); 101 return false; 102 } 103 104 if (file.query_exists ()) { 105 file.delete (); 106 } 107 108 os = new DataOutputStream (file.create (FileCreateFlags.REPLACE_DESTINATION)); 109 write_root_tag (os); 110 111 // this a backup of another font 112 if (backup) { 113 os.put_string ("\n"); 114 os.put_string (@"<!-- This is a backup of the following font: -->\n"); 115 os.put_string (@"<backup>$((!) font.get_path ())</backup>\n"); 116 } 117 118 os.put_string ("\n"); 119 write_description (os); 120 121 os.put_string ("\n"); 122 write_lines (os); 123 124 os.put_string ("\n"); 125 write_settings (os); 126 127 os.put_string ("\n"); 128 // FIXME: add this to a font specific settings file 129 if (font.background_images.size > 0) { 130 os.put_string (@"<images>\n"); 131 132 foreach (string f in font.background_images) { 133 os.put_string (@"\t<img src=\"$f\"/>\n"); 134 } 135 136 os.put_string (@"</images>\n"); 137 os.put_string ("\n"); 138 } 139 140 font.glyph_cache.for_each ((gc) => { 141 try { 142 write_glyph_collection (gc, os); 143 } catch (GLib.Error e) { 144 warning (e.message); 145 } 146 147 TooltipArea.show_text (t_("Saving")); 148 }); 149 150 os.put_string ("\n"); 151 write_ligatures (os); 152 153 font.glyph_cache.for_each ((gc) => { 154 BackgroundImage bg; 155 156 try { 157 string data; 158 foreach (Glyph g in gc.get_version_list ().glyphs) { 159 if (g.get_background_image () != null) { 160 bg = (!) g.get_background_image (); 161 data = bg.get_png_base64 (); 162 163 if (!bg.is_valid ()) { 164 continue; 165 } 166 167 os.put_string (@"<background-image sha1=\""); 168 os.put_string (bg.get_sha1 ()); 169 os.put_string ("\" "); 170 os.put_string (" data=\""); 171 os.put_string (data); 172 os.put_string ("\" />\n"); 173 } 174 } 175 } catch (GLib.Error ef) { 176 warning (@"Failed to save $path \n"); 177 warning (@"$(ef.message) \n"); 178 } 179 180 TooltipArea.show_text (t_("Saving")); 181 }); 182 183 os.put_string ("\n"); 184 write_spacing_classes (os); 185 write_kerning (os); 186 write_closing_root_tag (os); 187 188 os.close (); 189 } catch (GLib.Error e) { 190 warning (@"Failed to save $path \n"); 191 warning (@"$(e.message) \n"); 192 return false; 193 } 194 195 return true; 196 } 197 198 public void write_root_tag (DataOutputStream os) throws GLib.Error { 199 os.put_string ("""<?xml version="1.0" encoding="utf-8" standalone="yes"?>"""); 200 os.put_string ("\n"); 201 os.put_string ("<font>\n"); 202 os.put_string ("<format>1.0</format>\n"); 203 } 204 205 public void write_closing_root_tag (DataOutputStream os) throws GLib.Error { 206 os.put_string ("</font>\n"); 207 } 208 209 public void write_spacing_classes (DataOutputStream os) throws GLib.Error { 210 SpacingClassTab s = MainWindow.get_spacing_class_tab (); 211 212 foreach (SpacingClass sc in s.classes) { 213 os.put_string ("<spacing "); 214 os.put_string ("first=\""); 215 os.put_string (Font.to_hex (sc.first.get_char ())); 216 os.put_string ("\" "); 217 218 os.put_string ("next=\""); 219 os.put_string (Font.to_hex (sc.next.get_char ())); 220 os.put_string ("\" "); 221 222 os.put_string (" />\n"); 223 } 224 } 225 226 public void write_kerning (DataOutputStream os) throws GLib.Error { 227 uint num_kerning_pairs; 228 string range; 229 230 num_kerning_pairs = KerningClasses.get_instance ().classes_first.size; 231 232 for (int i = 0; i < num_kerning_pairs; i++) { 233 range = KerningClasses.get_instance ().classes_first.get (i).get_all_ranges (); 234 235 os.put_string ("<kerning "); 236 os.put_string ("left=\""); 237 os.put_string (range); 238 os.put_string ("\" "); 239 240 range = KerningClasses.get_instance ().classes_last.get (i).get_all_ranges (); 241 242 os.put_string ("right=\""); 243 os.put_string (range); 244 os.put_string ("\" "); 245 246 os.put_string ("hadjustment=\""); 247 os.put_string (round (KerningClasses.get_instance ().classes_kerning.get (i).val)); 248 os.put_string ("\" />\n"); 249 } 250 251 KerningClasses.get_instance ().get_single_position_pairs ((l, r, k) => { 252 try { 253 os.put_string ("<kerning "); 254 os.put_string ("left=\""); 255 os.put_string (l); 256 os.put_string ("\" "); 257 258 os.put_string ("right=\""); 259 os.put_string (r); 260 os.put_string ("\" "); 261 262 os.put_string ("hadjustment=\""); 263 os.put_string (round (k)); 264 os.put_string ("\" />\n"); 265 } catch (GLib.Error e) { 266 warning (@"$(e.message) \n"); 267 } 268 269 TooltipArea.show_text (t_("Saving")); 270 }); 271 } 272 273 public void write_settings (DataOutputStream os) throws GLib.Error { 274 foreach (string gv in font.grid_width) { 275 os.put_string (@"<grid width=\"$(gv)\"/>\n"); 276 } 277 278 if (GridTool.sizes.size > 0) { 279 os.put_string ("\n"); 280 } 281 282 os.put_string (@"<background scale=\"$(font.background_scale)\" />\n"); 283 } 284 285 public void write_description (DataOutputStream os) throws GLib.Error { 286 os.put_string (@"<postscript_name>$(Markup.escape_text (font.postscript_name))</postscript_name>\n"); 287 os.put_string (@"<name>$(Markup.escape_text (font.name))</name>\n"); 288 os.put_string (@"<subfamily>$(Markup.escape_text (font.subfamily))</subfamily>\n"); 289 os.put_string (@"<bold>$(font.bold)</bold>\n"); 290 os.put_string (@"<italic>$(font.italic)</italic>\n"); 291 os.put_string (@"<full_name>$(Markup.escape_text (font.full_name))</full_name>\n"); 292 os.put_string (@"<unique_identifier>$(Markup.escape_text (font.unique_identifier))</unique_identifier>\n"); 293 os.put_string (@"<version>$(Markup.escape_text (font.version))</version>\n"); 294 os.put_string (@"<description>$(Markup.escape_text (font.description))</description>\n"); 295 os.put_string (@"<copyright>$(Markup.escape_text (font.copyright))</copyright>\n"); 296 } 297 298 public void write_lines (DataOutputStream os) throws GLib.Error { 299 os.put_string ("<horizontal>\n"); 300 os.put_string (@"\t<top_limit>$(round (font.top_limit))</top_limit>\n"); 301 os.put_string (@"\t<top_position>$(round (font.top_position))</top_position>\n"); 302 os.put_string (@"\t<x-height>$(round (font.xheight_position))</x-height>\n"); 303 os.put_string (@"\t<base_line>$(round (font.base_line))</base_line>\n"); 304 os.put_string (@"\t<bottom_position>$(round (font.bottom_position))</bottom_position>\n"); 305 os.put_string (@"\t<bottom_limit>$(round (font.bottom_limit))</bottom_limit>\n"); 306 os.put_string ("</horizontal>\n"); 307 } 308 309 public void write_glyph_collection_start (GlyphCollection gc, DataOutputStream os) throws GLib.Error { 310 os.put_string ("<collection "); 311 312 if (gc.is_unassigned ()) { 313 os.put_string (@"name=\"$(gc.get_current ().get_name ())\""); 314 } else { 315 os.put_string (@"unicode=\"$(Font.to_hex (gc.get_current ().unichar_code))\""); 316 } 317 318 os.put_string (">\n"); 319 } 320 321 public void write_glyph_collection_end (DataOutputStream os) throws GLib.Error { 322 os.put_string ("</collection>\n"); 323 } 324 325 public void write_selected (GlyphCollection gc, DataOutputStream os) throws GLib.Error { 326 os.put_string (@"\t<selected id=\"$(gc.get_selected_id ())\"/>\n"); 327 } 328 329 public void write_glyph_collection (GlyphCollection gc, DataOutputStream os) throws GLib.Error { 330 write_glyph_collection_start (gc, os); 331 write_selected (gc, os); 332 foreach (Glyph g in gc.get_version_list ().glyphs) { 333 write_glyph (g, gc, os); 334 } 335 write_glyph_collection_end (os); 336 } 337 338 public void write_glyph (Glyph g, GlyphCollection gc, DataOutputStream os) throws GLib.Error { 339 string data; 340 341 os.put_string (@"\t<glyph id=\"$(g.version_id)\" left=\"$(double_to_string (g.left_limit))\" right=\"$(double_to_string (g.right_limit))\">\n"); 342 foreach (Path p in g.path_list) { 343 data = get_point_data (p); 344 if (data != "") { 345 os.put_string (@"\t\t<path stroke=\"$(double_to_string (p.stroke))\" skew=\"$(double_to_string (p.skew))\" data=\"$(data)\" />\n"); 346 } 347 } 348 write_glyph_background (g, os); 349 os.put_string ("\t</glyph>\n"); 350 } 351 352 public static string double_to_string (double n) { 353 string d = @"$n"; 354 return d.replace (",", "."); 355 } 356 357 /** Get control points in BirdFont format. This function is uses a 358 * cartesian coordinate system with origo in the middle. 359 * 360 * Instructions: 361 * S - Start point for a quadratic path 362 * B - Start point for a cubic path 363 * L - Line with quadratic control points 364 * M - Line with cubic control points 365 * Q - Quadratic Bézier path 366 * D - Two quadratic off curve points 367 * C - Cubic Bézier path 368 * 369 * T - Tie handles for previous curve 370 * 371 * O - Keep open (do not close path) 372 */ 373 public static string get_point_data (Path pl) { 374 StringBuilder data = new StringBuilder (); 375 EditPoint? n = null; 376 EditPoint m; 377 int i = 0; 378 379 if (pl.points.size == 0) { 380 return data.str; 381 } 382 383 if (pl.points.size == 1) { 384 add_start_point (pl.points.get (0), data); 385 data.append (" "); 386 add_next_point (pl.points.get (0), pl.points.get (0), data); 387 388 if (pl.is_open ()) { 389 data.append (" O"); 390 } 391 392 return data.str; 393 } 394 395 if (pl.points.size == 2) { 396 add_start_point (pl.points.get (0), data); 397 data.append (" "); 398 add_next_point (pl.points.get (0), pl.points.get (pl.points.size - 1), data); 399 data.append (" "); 400 add_next_point (pl.points.get (pl.points.size - 1), pl.points.get (0), data); 401 402 if (pl.is_open ()) { 403 data.append (" O"); 404 } 405 406 return data.str; 407 } 408 409 pl.create_list (); 410 411 foreach (EditPoint e in pl.points) { 412 if (i == 0) { 413 add_start_point (e, data); 414 i++; 415 n = e; 416 continue; 417 } 418 419 m = (!) n; 420 data.append (" "); 421 add_next_point (m, e, data); 422 423 n = e; 424 i++; 425 } 426 427 data.append (" "); 428 m = pl.points.get (0); 429 add_next_point ((!) n, m, data); 430 431 if (pl.is_open ()) { 432 data.append (" O"); 433 } 434 435 return data.str; 436 } 437 438 private static void add_start_point (EditPoint e, StringBuilder data) { 439 if (e.type == PointType.CUBIC || e.type == PointType.LINE_CUBIC) { 440 add_cubic_start (e, data); 441 } else { 442 add_quadratic_start (e, data); 443 } 444 } 445 446 private static string round (double d) { 447 char[] b = new char [22]; 448 unowned string s = d.format (b, "%.10f"); 449 string n = s.dup (); 450 451 n = n.replace (",", "."); 452 453 if (n == "-0.0000000000") { 454 n = "0.0000000000"; 455 } 456 457 return n; 458 } 459 460 private static void add_quadratic_start (EditPoint p, StringBuilder data) { 461 string x, y; 462 463 x = round (p.x); 464 y = round (p.y); 465 466 data.append (@"S $(x),$(y)"); 467 } 468 469 private static void add_cubic_start (EditPoint p, StringBuilder data) { 470 string x, y; 471 472 x = round (p.x); 473 y = round (p.y); 474 475 data.append (@"B $(x),$(y)"); 476 } 477 478 private static void add_line_to (EditPoint p, StringBuilder data) { 479 string x, y; 480 481 x = round (p.x); 482 y = round (p.y); 483 484 data.append (@"L $(x),$(y)"); 485 } 486 487 private static void add_cubic_line_to (EditPoint p, StringBuilder data) { 488 string x, y; 489 490 x = round (p.x); 491 y = round (p.y); 492 493 data.append (@"M $(x),$(y)"); 494 } 495 496 private static void add_quadratic (EditPoint start, EditPoint end, StringBuilder data) { 497 EditPointHandle h = start.get_right_handle (); 498 string x0, y0, x1, y1; 499 500 x0 = round (h.x); 501 y0 = round (h.y); 502 x1 = round (end.x); 503 y1 = round (end.y); 504 505 data.append (@"Q $(x0),$(y0) $(x1),$(y1)"); 506 } 507 508 private static void add_double (EditPoint start, EditPoint end, StringBuilder data) { 509 EditPointHandle h1 = start.get_right_handle (); 510 EditPointHandle h2 = end.get_left_handle (); 511 string x0, y0, x1, y1, x2, y2; 512 513 x0 = round (h1.x); 514 y0 = round (h1.y); 515 x1 = round (h2.x); 516 y1 = round (h2.y); 517 x2 = round (end.x); 518 y2 = round (end.y); 519 520 data.append (@"D $(x0),$(y0) $(x1),$(y1) $(x2),$(y2)"); 521 } 522 523 private static void add_cubic (EditPoint start, EditPoint end, StringBuilder data) { 524 EditPointHandle h1 = start.get_right_handle (); 525 EditPointHandle h2 = end.get_left_handle (); 526 string x0, y0, x1, y1, x2, y2; 527 528 x0 = round (h1.x); 529 y0 = round (h1.y); 530 x1 = round (h2.x); 531 y1 = round (h2.y); 532 x2 = round (end.x); 533 y2 = round (end.y); 534 535 data.append (@"C $(x0),$(y0) $(x1),$(y1) $(x2),$(y2)"); 536 } 537 538 private static void add_next_point (EditPoint start, EditPoint end, StringBuilder data) { 539 if (start.right_handle.type == PointType.LINE_QUADRATIC && end.left_handle.type == PointType.LINE_QUADRATIC) { 540 add_line_to (end, data); 541 } else if (start.right_handle.type == PointType.LINE_DOUBLE_CURVE && end.left_handle.type == PointType.LINE_DOUBLE_CURVE) { 542 add_line_to (end, data); 543 } else if (start.right_handle.type == PointType.LINE_CUBIC && end.left_handle.type == PointType.LINE_CUBIC) { 544 add_cubic_line_to (end, data); 545 } else if (end.left_handle.type == PointType.DOUBLE_CURVE || start.right_handle.type == PointType.DOUBLE_CURVE) { 546 add_double (start, end, data); 547 } else if (end.left_handle.type == PointType.QUADRATIC || start.right_handle.type == PointType.QUADRATIC) { 548 add_quadratic (start, end, data); 549 } else if (end.left_handle.type == PointType.CUBIC || start.right_handle.type == PointType.CUBIC) { 550 add_cubic (start, end, data); 551 } else if (start.right_handle.type == PointType.LINE_CUBIC && end.left_handle.type == PointType.LINE_DOUBLE_CURVE) { 552 add_line_to (end, data); 553 } else if (start.right_handle.type == PointType.LINE_DOUBLE_CURVE && end.left_handle.type == PointType.LINE_CUBIC) { 554 add_line_to (end, data); 555 } else { 556 warning (@"Unknown point type. \nStart handle: $(start.right_handle.type) \nStop handle: $(end.left_handle.type)"); 557 add_cubic (start, end, data); 558 } 559 560 if (end.tie_handles) { 561 data.append (" "); 562 data.append (@"T"); 563 } 564 } 565 566 private void write_glyph_background (Glyph g, DataOutputStream os) throws GLib.Error { 567 BackgroundImage? bg; 568 BackgroundImage background_image; 569 double pos_x, pos_y, scale_x, scale_y, rotation; 570 571 bg = g.get_background_image (); 572 573 // FIXME: use the coordinate system 574 if (bg != null) { 575 background_image = (!) bg; 576 577 pos_x = background_image.img_x; 578 pos_y = background_image.img_y; 579 580 scale_x = background_image.img_scale_x; 581 scale_y = background_image.img_scale_y; 582 583 rotation = background_image.img_rotation; 584 585 if (background_image.is_valid ()) { 586 os.put_string (@"\t\t<background sha1=\"$(background_image.get_sha1 ())\" x=\"$pos_x\" y=\"$pos_y\" scale_x=\"$scale_x\" scale_y=\"$scale_y\" rotation=\"$rotation\"/>\n"); 587 } 588 } 589 } 590 591 private bool parse_file (Tag tag) { 592 foreach (Tag t in tag) { 593 // this is a backup file, but path pointing to the original file 594 if (t.get_name () == "backup") { 595 font.font_file = t.get_content (); 596 } 597 598 // glyph format 599 if (t.get_name () == "collection") { 600 parse_glyph_collection (t); 601 } 602 603 // horizontal lines in the new format 604 if (t.get_name () == "horizontal") { 605 parse_horizontal_lines (t); 606 } 607 608 // grid buttons 609 if (t.get_name () == "grid") { 610 parse_grid (t); 611 } 612 613 if (t.get_name () == "background") { 614 parse_background (t); 615 } 616 617 if (t.get_name () == "postscript_name") { 618 font.postscript_name = t.get_content (); 619 } 620 621 if (t.get_name () == "name") { 622 font.name = t.get_content (); 623 } 624 625 if (t.get_name () == "subfamily") { 626 font.subfamily = t.get_content (); 627 } 628 629 if (t.get_name () == "bold") { 630 font.bold = bool.parse (t.get_content ()); 631 } 632 633 if (t.get_name () == "italic") { 634 font.italic = bool.parse (t.get_content ()); 635 } 636 637 if (t.get_name () == "full_name") { 638 font.full_name = t.get_content (); 639 } 640 641 if (t.get_name () == "unique_identifier") { 642 font.unique_identifier = t.get_content (); 643 } 644 645 if (t.get_name () == "version") { 646 font.version = t.get_content (); 647 } 648 649 if (t.get_name () == "description") { 650 font.description = t.get_content (); 651 } 652 653 if (t.get_name () == "copyright") { 654 font.copyright = t.get_content (); 655 } 656 657 if (t.get_name () == "kerning") { 658 parse_kerning (t); 659 } 660 661 if (t.get_name () == "spacing") { 662 parse_spacing_class (t); 663 } 664 665 if (t.get_name () == "ligature") { 666 parse_ligature (t); 667 } 668 669 TooltipArea.show_text (t_("Loading XML data.")); 670 } 671 672 TooltipArea.show_text (""); 673 return true; 674 } 675 676 private void create_background_files (Tag root) { 677 foreach (Tag child in root) { 678 if (child.get_name () == "name") { 679 font.set_name (child.get_content ()); 680 } 681 682 if (child.get_name () == "background-image") { 683 parse_background_image (child); 684 } 685 } 686 } 687 688 public static string serialize_attribute (string s) { 689 string n = s.replace ("\"", "quote"); 690 n = n.replace ("&", "ampersand"); 691 return n; 692 } 693 694 public static string unserialize (string s) { 695 StringBuilder b; 696 string r; 697 r = s.replace ("quote", "\""); 698 r = r.replace ("ampersand", "&"); 699 700 if (s.has_prefix ("U+")) { 701 b = new StringBuilder (); 702 b.append_unichar (Font.to_unichar (s)); 703 r = @"$(b.str)"; 704 } 705 706 return r; 707 } 708 709 public static string serialize_unichar (unichar c) { 710 return GlyphRange.get_serialized_char (c); 711 } 712 713 private void parse_spacing_class (Tag tag) { 714 string first, next; 715 SpacingClassTab spacing_class_tab = MainWindow.get_spacing_class_tab (); 716 717 first = ""; 718 next = ""; 719 720 foreach (Attribute attr in tag.get_attributes ()) { 721 if (attr.get_name () == "first") { 722 first = (!) Font.to_unichar (attr.get_content ()).to_string (); 723 } 724 725 if (attr.get_name () == "next") { 726 next = (!) Font.to_unichar (attr.get_content ()).to_string (); 727 } 728 } 729 730 spacing_class_tab.add_class (first, next); 731 } 732 733 private void parse_kerning (Tag tag) { 734 GlyphRange range_left, range_right; 735 double hadjustment = 0; 736 KerningRange kerning_range; 737 738 try { 739 range_left = new GlyphRange (); 740 range_right = new GlyphRange (); 741 742 foreach (Attribute attr in tag.get_attributes ()) { 743 if (attr.get_name () == "left") { 744 range_left.parse_ranges (unserialize (attr.get_content ())); 745 } 746 747 if (attr.get_name () == "right") { 748 range_right.parse_ranges (unserialize (attr.get_content ())); 749 } 750 751 if (attr.get_name () == "hadjustment") { 752 hadjustment = double.parse (attr.get_content ()); 753 } 754 } 755 756 if (range_left.get_length () > 1) { 757 kerning_range = new KerningRange (); 758 kerning_range.set_ranges (range_left.get_all_ranges ()); 759 KerningTools.add_unique_class (kerning_range); 760 } 761 762 if (range_right.get_length () > 1) { 763 kerning_range = new KerningRange (); 764 kerning_range.set_ranges (range_right.get_all_ranges ()); 765 KerningTools.add_unique_class (kerning_range); 766 } 767 768 KerningClasses.get_instance ().set_kerning (range_left, range_right, hadjustment); 769 770 } catch (MarkupError e) { 771 warning (e.message); 772 } 773 } 774 775 private void parse_background_image (Tag tag) { 776 string file = ""; 777 string data = ""; 778 779 File img_dir; 780 File img_file; 781 FileOutputStream file_stream; 782 DataOutputStream png_stream; 783 784 tag.reparse (); 785 foreach (Attribute attr in tag.get_attributes ()) { 786 if (attr.get_name () == "sha1") { 787 file = attr.get_content (); 788 } 789 790 if (attr.get_name () == "data") { 791 data = attr.get_content (); 792 } 793 } 794 795 if (!font.get_backgrounds_folder ().query_exists ()) { 796 DirUtils.create ((!) font.get_backgrounds_folder ().get_path (), 0755); 797 } 798 799 img_dir = font.get_backgrounds_folder ().get_child ("parts"); 800 801 if (!img_dir.query_exists ()) { 802 DirUtils.create ((!) img_dir.get_path (), 0755); 803 } 804 805 img_file = img_dir.get_child (@"$(file).png"); 806 807 if (img_file.query_exists ()) { 808 return; 809 } 810 811 try { 812 file_stream = img_file.create (FileCreateFlags.REPLACE_DESTINATION); 813 png_stream = new DataOutputStream (file_stream); 814 815 png_stream.write (Base64.decode (data)); 816 png_stream.close (); 817 } catch (GLib.Error e) { 818 warning (e.message); 819 } 820 } 821 822 private void parse_background (Tag tag) { 823 foreach (Attribute attr in tag.get_attributes ()) { 824 if (attr.get_name () == "scale") { 825 font.background_scale = attr.get_content (); 826 } 827 } 828 } 829 830 private void parse_grid (Tag tag) { 831 foreach (Attribute attr in tag.get_attributes ()) { 832 if (attr.get_name () == "width") { 833 font.grid_width.add (attr.get_content ()); 834 } 835 } 836 } 837 838 private void parse_horizontal_lines (Tag tag) { 839 foreach (Tag t in tag) { 840 if (t.get_name () == "top_limit" && t.get_content () != "") { 841 font.top_limit = parse_double_from_node (t); 842 } 843 844 if (t.get_name () == "top_position" && t.get_content () != "") { 845 font.top_position = parse_double_from_node (t); 846 } 847 848 if (t.get_name () == "x-height" && t.get_content () != "") { 849 font.xheight_position = parse_double_from_node (t); 850 } 851 852 if (t.get_name () == "base_line" && t.get_content () != "") { 853 font.base_line = parse_double_from_node (t); 854 } 855 856 if (t.get_name () == "bottom_position" && t.get_content () != "") { 857 font.bottom_position = parse_double_from_node (t); 858 } 859 860 if (t.get_name () == "bottom_limit" && t.get_content () != "") { 861 font.bottom_limit = parse_double_from_node (t); 862 } 863 } 864 } 865 866 private double parse_double_from_node (Tag tag) { 867 double d; 868 bool r = double.try_parse (tag.get_content (), out d); 869 string s; 870 871 if (unlikely (!r)) { 872 s = tag.get_content (); 873 if (s == "") { 874 warning (@"No content for node\n"); 875 } else { 876 warning (@"Failed to parse double for \"$(tag.get_content ())\"\n"); 877 } 878 } 879 880 return (r) ? d : 0.0; 881 } 882 883 /** Parse the new glyph format */ 884 private void parse_glyph_collection (Tag tag) { 885 unichar unicode = 0; 886 GlyphCollection gc; 887 GlyphCollection? current_gc; 888 bool new_glyph_collection; 889 StringBuilder b; 890 string name = ""; 891 int selected_id = -1; 892 bool unassigned = false; 893 894 foreach (Attribute attribute in tag.get_attributes ()) { 895 if (attribute.get_name () == "unicode") { 896 unicode = Font.to_unichar (attribute.get_content ()); 897 b = new StringBuilder (); 898 b.append_unichar (unicode); 899 name = b.str; 900 901 if (name == "") { 902 name = ".null"; 903 } 904 905 unassigned = false; 906 } 907 908 if (attribute.get_name () == "name") { 909 unicode = '\0'; 910 name = attribute.get_content (); 911 unassigned = true; 912 } 913 } 914 915 current_gc = font.get_glyph_collection_by_name (name); 916 new_glyph_collection = (current_gc == null); 917 gc = (!new_glyph_collection) ? (!) current_gc : new GlyphCollection (unicode, name); 918 919 foreach (Tag t in tag) { 920 if (t.get_name () == "selected") { 921 selected_id = parse_selected (t); 922 gc.set_selected_version (selected_id); 923 } 924 } 925 926 foreach (Tag t in tag) { 927 if (t.get_name () == "glyph") { 928 parse_glyph (t, gc, name, unicode, selected_id, unassigned); 929 } 930 } 931 932 if (new_glyph_collection) { 933 font.add_glyph_collection (gc); 934 } 935 } 936 937 private int parse_selected (Tag tag) { 938 int id = 1; 939 bool has_selected_tag = false; 940 941 foreach (Attribute attribute in tag.get_attributes ()) { 942 if (attribute.get_name () == "id") { 943 id = int.parse (attribute.get_content ()); 944 has_selected_tag = true; 945 break; 946 } 947 } 948 949 if (unlikely (!has_selected_tag)) { 950 warning ("No selected tag."); 951 } 952 953 return id; 954 } 955 956 private void parse_glyph (Tag tag, GlyphCollection gc, string name, 957 unichar unicode, int selected_id, bool unassigned) { 958 Glyph glyph = new Glyph (name, unicode); 959 Path path; 960 bool selected = false; 961 bool has_id = false; 962 int id = 1; 963 964 foreach (Attribute attr in tag.get_attributes ()) { 965 if (attr.get_name () == "left") { 966 glyph.left_limit = double.parse (attr.get_content ()); 967 } 968 969 if (attr.get_name () == "right") { 970 glyph.right_limit = double.parse (attr.get_content ()); 971 } 972 973 // id is unique within the glyph collection 974 if (attr.get_name () == "id") { 975 id = int.parse (attr.get_content ()); 976 has_id = true; 977 } 978 979 // old way of selecting a glyph in the version list 980 if (attr.get_name () == "selected") { 981 selected = bool.parse (attr.get_content ()); 982 } 983 } 984 985 foreach (Tag t in tag) { 986 if (t.get_name () == "path") { 987 path = parse_path (t); 988 glyph.add_path (path); 989 } 990 991 if (t.get_name () == "background") { 992 parse_background_scale (glyph, t); 993 } 994 } 995 996 glyph.version_id = (has_id) ? id : (int) gc.length () + 1; 997 gc.set_unassigned (unassigned); 998 gc.insert_glyph (glyph, selected || selected_id == id); 999 } 1000 1001 private Path parse_path (Tag tag) { 1002 Path path = new Path (); 1003 1004 foreach (Attribute attr in tag.get_attributes ()) { 1005 if (attr.get_name () == "data") { 1006 path = parse_path_data (attr.get_content ()); 1007 } 1008 } 1009 1010 foreach (Attribute attr in tag.get_attributes ()) { 1011 if (attr.get_name () == "stroke") { 1012 path.set_stroke (double.parse (attr.get_content ())); 1013 } 1014 1015 if (attr.get_name () == "skew") { 1016 path.skew = double.parse (attr.get_content ()); 1017 } 1018 } 1019 1020 if (path.points.size == 0) { 1021 warning ("Empty path"); 1022 } 1023 1024 return path; 1025 } 1026 1027 private static void line (Path path, string px, string py) { 1028 EditPoint ep; 1029 1030 path.add (parse_double (px), parse_double (py)); 1031 ep = path.points.get (path.points.size - 1); 1032 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE; 1033 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE; 1034 ep.type = PointType.LINE_DOUBLE_CURVE; 1035 ep.recalculate_linear_handles (); 1036 } 1037 1038 private static void cubic_line (Path path, string px, string py) { 1039 EditPoint ep; 1040 1041 path.add (parse_double (px), parse_double (py)); 1042 ep = path.points.get (path.points.size - 1); 1043 ep.get_right_handle ().type = PointType.LINE_CUBIC; 1044 ep.type = PointType.LINE_CUBIC; 1045 ep.recalculate_linear_handles (); 1046 } 1047 1048 private static void quadratic (Path path, string px0, string py0, string px1, string py1) { 1049 EditPoint ep1, ep2; 1050 1051 double x0 = parse_double (px0); 1052 double y0 = parse_double (py0); 1053 double x1 = parse_double (px1); 1054 double y1 = parse_double (py1); 1055 1056 if (path.points.size == 0) { 1057 warning ("No point."); 1058 return; 1059 } 1060 1061 ep1 = path.points.get (path.points.size - 1); 1062 ep1.recalculate_linear_handles (); 1063 ep1.get_right_handle ().type = PointType.QUADRATIC; 1064 ep1.get_right_handle ().move_to_coordinate (x0, y0); 1065 ep1.type = PointType.QUADRATIC; 1066 1067 path.add (x1, y1); 1068 1069 ep2 = path.points.get (path.points.size - 1); 1070 ep2.recalculate_linear_handles (); 1071 ep2.get_left_handle ().type = PointType.QUADRATIC; 1072 ep2.get_left_handle ().move_to_coordinate (x0, y0); 1073 ep2.type = PointType.QUADRATIC; 1074 } 1075 1076 private static void cubic (Path path, string px0, string py0, string px1, string py1, string px2, string py2) { 1077 EditPoint ep1, ep2; 1078 1079 double x0 = parse_double (px0); 1080 double y0 = parse_double (py0); 1081 double x1 = parse_double (px1); 1082 double y1 = parse_double (py1); 1083 double x2 = parse_double (px2); 1084 double y2 = parse_double (py2); 1085 1086 double lx, ly; 1087 1088 if (path.points.size == 0) { 1089 warning ("No point"); 1090 return; 1091 } 1092 1093 // start with line handles 1094 ep1 = path.points.get (path.points.size - 1); 1095 ep1.get_right_handle ().type = PointType.LINE_CUBIC; 1096 1097 lx = ep1.x + ((x2 - ep1.x) / 3); 1098 ly = ep1.y + ((y2 - ep1.y) / 3); 1099 1100 ep1.get_right_handle ().move_to_coordinate (lx, ly); 1101 ep1.recalculate_linear_handles (); 1102 1103 // set curve handles 1104 ep1 = path.points.get (path.points.size - 1); 1105 ep1.recalculate_linear_handles (); 1106 ep1.get_right_handle ().type = PointType.CUBIC; 1107 ep1.get_right_handle ().move_to_coordinate (x0, y0); 1108 ep1.type = PointType.CUBIC; 1109 1110 path.add (x2, y2); 1111 1112 ep2 = path.points.get (path.points.size - 1); 1113 ep2.recalculate_linear_handles (); 1114 ep2.get_left_handle ().type = PointType.CUBIC; 1115 ep2.get_left_handle ().move_to_coordinate (x1, y1); 1116 ep2.type = PointType.CUBIC; 1117 1118 ep1.recalculate_linear_handles (); 1119 } 1120 1121 /** Two quadratic off curve points. */ 1122 private static void double_curve (Path path, string px0, string py0, string px1, string py1, string px2, string py2) { 1123 EditPoint ep1, ep2; 1124 1125 double x0 = parse_double (px0); 1126 double y0 = parse_double (py0); 1127 double x1 = parse_double (px1); 1128 double y1 = parse_double (py1); 1129 double x2 = parse_double (px2); 1130 double y2 = parse_double (py2); 1131 1132 double lx, ly; 1133 1134 if (path.points.size == 0) { 1135 warning ("No point"); 1136 return; 1137 } 1138 1139 // start with line handles 1140 ep1 = path.points.get (path.points.size - 1); 1141 ep1.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE; 1142 1143 lx = ep1.x + ((x2 - ep1.x) / 4); 1144 ly = ep1.y + ((y2 - ep1.y) / 4); 1145 1146 ep1.get_right_handle ().move_to_coordinate (lx, ly); 1147 ep1.recalculate_linear_handles (); 1148 1149 // set curve handles 1150 ep1 = path.points.get (path.points.size - 1); 1151 ep1.recalculate_linear_handles (); 1152 ep1.get_right_handle ().type = PointType.DOUBLE_CURVE; 1153 ep1.get_right_handle ().move_to_coordinate (x0, y0); 1154 ep1.type = PointType.DOUBLE_CURVE; 1155 1156 path.add (x2, y2); 1157 1158 ep2 = path.points.get (path.points.size - 1); 1159 ep2.recalculate_linear_handles (); 1160 ep2.get_left_handle ().type = PointType.DOUBLE_CURVE; 1161 ep2.get_left_handle ().move_to_coordinate (x1, y1); 1162 ep2.type = PointType.DOUBLE_CURVE; 1163 1164 ep1.recalculate_linear_handles (); 1165 } 1166 1167 public static void close (Path path) { 1168 EditPoint ep1, ep2; 1169 1170 if (path.points.size < 2) { 1171 warning ("Less than two points in path."); 1172 return; 1173 } 1174 1175 // last point is first 1176 ep1 = path.points.get (path.points.size - 1); 1177 ep2 = path.points.get (0); 1178 1179 path.points.remove_at (path.points.size - 1); 1180 1181 ep2.tie_handles = ep1.tie_handles; 1182 ep2.left_handle.angle = ep1.left_handle.angle; 1183 ep2.left_handle.length = ep1.left_handle.length; 1184 ep2.left_handle.type = ep1.left_handle.type; 1185 1186 path.close (); 1187 } 1188 1189 public static Path parse_path_data (string data) { 1190 string[] d = data.split (" "); 1191 string[] p, p1, p2; 1192 int i = 0; 1193 Path path = new Path (); 1194 string instruction = ""; 1195 bool open = false; 1196 1197 if (data == "") { 1198 return path; 1199 } 1200 1201 return_val_if_fail (d.length > 1, path); 1202 1203 if (!(d[0] == "S" || d[0] == "B")) { 1204 warning ("No start point."); 1205 return path; 1206 } 1207 1208 instruction = d[i++]; 1209 1210 if (instruction == "S") { 1211 p = d[i++].split (","); 1212 return_val_if_fail (p.length == 2, path); 1213 line (path, p[0], p[1]); 1214 } 1215 1216 if (instruction == "B") { 1217 p = d[i++].split (","); 1218 return_val_if_fail (p.length == 2, path); 1219 cubic_line (path, p[0], p[1]); 1220 } 1221 1222 while (i < d.length) { 1223 instruction = d[i++]; 1224 1225 if (instruction == "") { 1226 warning (@"No instruction at index $i."); 1227 return path; 1228 } 1229 1230 if (instruction == "L") { 1231 return_val_if_fail (i < d.length, path); 1232 p = d[i++].split (","); 1233 return_val_if_fail (p.length == 2, path); 1234 line (path, p[0], p[1]); 1235 }else if (instruction == "M") { 1236 return_val_if_fail (i < d.length, path); 1237 p = d[i++].split (","); 1238 return_val_if_fail (p.length == 2, path); 1239 cubic_line (path, p[0], p[1]); 1240 } else if (instruction == "Q") { 1241 return_val_if_fail (i + 1 < d.length, path); 1242 1243 p = d[i++].split (","); 1244 p1 = d[i++].split (","); 1245 1246 return_val_if_fail (p.length == 2, path); 1247 return_val_if_fail (p1.length == 2, path); 1248 1249 quadratic (path, p[0], p[1], p1[0], p1[1]); 1250 } else if (instruction == "D") { 1251 return_val_if_fail (i + 2 < d.length, path); 1252 1253 p = d[i++].split (","); 1254 p1 = d[i++].split (","); 1255 p2 = d[i++].split (","); 1256 1257 return_val_if_fail (p.length == 2, path); 1258 return_val_if_fail (p1.length == 2, path); 1259 return_val_if_fail (p2.length == 2, path); 1260 1261 double_curve (path, p[0], p[1], p1[0], p1[1], p2[0], p2[1]); 1262 } else if (instruction == "C") { 1263 return_val_if_fail (i + 2 < d.length, path); 1264 1265 p = d[i++].split (","); 1266 p1 = d[i++].split (","); 1267 p2 = d[i++].split (","); 1268 1269 return_val_if_fail (p.length == 2, path); 1270 return_val_if_fail (p1.length == 2, path); 1271 return_val_if_fail (p2.length == 2, path); 1272 1273 cubic (path, p[0], p[1], p1[0], p1[1], p2[0], p2[1]); 1274 } else if (instruction == "T") { 1275 path.points.get (path.points.size - 1).tie_handles = true; 1276 } else if (instruction == "O") { 1277 open = true; 1278 } else { 1279 warning (@"invalid instruction $instruction"); 1280 return path; 1281 } 1282 } 1283 1284 if (!open) { 1285 close (path); 1286 } else { 1287 path.points.remove_at (path.points.size - 1); 1288 1289 if (!path.is_open ()) { 1290 warning ("Closed path."); 1291 } 1292 } 1293 1294 path.update_region_boundaries (); 1295 1296 return path; 1297 } 1298 1299 private static double parse_double (string p) { 1300 double d; 1301 if (double.try_parse (p, out d)) { 1302 return d; 1303 } 1304 1305 warning (@"failed to parse $p"); 1306 return 0; 1307 } 1308 1309 private void parse_background_scale (Glyph g, Tag tag) { 1310 BackgroundImage img; 1311 BackgroundImage? new_img = null; 1312 1313 File img_file = font.get_backgrounds_folder ().get_child ("parts"); 1314 1315 foreach (Attribute attr in tag.get_attributes ()) { 1316 if (attr.get_name () == "sha1") { 1317 img_file = img_file.get_child (attr.get_content () + ".png"); 1318 1319 if (!img_file.query_exists ()) { 1320 warning (@"Background file has not been created yet. $((!) img_file.get_path ())"); 1321 } 1322 1323 new_img = new BackgroundImage ((!) img_file.get_path ()); 1324 g.set_background_image ((!) new_img); 1325 } 1326 } 1327 1328 if (unlikely (new_img == null)) { 1329 warning ("No source for image found."); 1330 return; 1331 } 1332 1333 img = (!) new_img; 1334 1335 foreach (Attribute attr in tag.get_attributes ()) { 1336 if (attr.get_name () == "x") { 1337 img.img_x = double.parse (attr.get_content ()); 1338 } 1339 1340 if (attr.get_name () == "y") { 1341 img.img_y = double.parse (attr.get_content ()); 1342 } 1343 1344 if (attr.get_name () == "scale_x") { 1345 img.img_scale_x = double.parse (attr.get_content ()); 1346 } 1347 1348 if (attr.get_name () == "scale_y") { 1349 img.img_scale_y = double.parse (attr.get_content ()); 1350 } 1351 1352 if (attr.get_name () == "rotation") { 1353 img.img_rotation = double.parse (attr.get_content ()); 1354 } 1355 } 1356 1357 img.set_position (img.img_x, img.img_y); 1358 } 1359 1360 public void write_ligatures (DataOutputStream os) { 1361 Ligatures ligatures = font.get_ligatures (); 1362 1363 ligatures.get_ligatures ((subst, liga) => { 1364 try { 1365 string lig = serialize_attribute (liga); 1366 string sequence = serialize_attribute (subst); 1367 os.put_string (@"<ligature sequence=\"$(sequence)\" replacement=\"$(lig)\"/>\n"); 1368 } catch (GLib.IOError e) { 1369 warning (e.message); 1370 } 1371 }); 1372 } 1373 1374 public void parse_ligature (Tag t) { 1375 string sequence = ""; 1376 string ligature = ""; 1377 Ligatures ligatures; 1378 1379 foreach (Attribute a in t.get_attributes ()) { 1380 if (a.get_name () == "sequence") { 1381 sequence = a.get_content (); 1382 } 1383 1384 if (a.get_name () == "replacement") { 1385 ligature = a.get_content (); 1386 } 1387 } 1388 1389 ligatures = font.get_ligatures (); 1390 ligatures.add_ligature (sequence, ligature); 1391 } 1392 } 1393 1394 } 1395