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