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