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