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