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