The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

BirdFontFile.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

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