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