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