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