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