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