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.
Fix compile time warnings
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 bool has_grid (string v) { 1201 foreach (string g in font.grid_width) { 1202 if (g == v) { 1203 return true; 1204 } 1205 } 1206 1207 return false; 1208 } 1209 1210 private void parse_grid (Tag tag) { 1211 foreach (Attribute attr in tag.get_attributes ()) { 1212 string v = attr.get_content (); 1213 1214 if (attr.get_name () == "width" && !has_grid (v)) { 1215 font.grid_width.add (v); 1216 } 1217 } 1218 } 1219 1220 private void parse_horizontal_lines (Tag tag) { 1221 Line line; 1222 string label; 1223 double position; 1224 1225 foreach (Tag t in tag) { 1226 if (t.get_name () == "top_limit" && t.get_content () != "") { 1227 font.top_limit = parse_double_from_node (t); 1228 } 1229 1230 if (t.get_name () == "top_position" && t.get_content () != "") { 1231 font.top_position = parse_double_from_node (t); 1232 } 1233 1234 if (t.get_name () == "x-height" && t.get_content () != "") { 1235 font.xheight_position = parse_double_from_node (t); 1236 } 1237 1238 if (t.get_name () == "base_line" && t.get_content () != "") { 1239 font.base_line = parse_double_from_node (t); 1240 } 1241 1242 if (t.get_name () == "bottom_position" && t.get_content () != "") { 1243 font.bottom_position = parse_double_from_node (t); 1244 } 1245 1246 if (t.get_name () == "bottom_limit" && t.get_content () != "") { 1247 font.bottom_limit = parse_double_from_node (t); 1248 } 1249 1250 if (t.get_name () == "custom_guide" && t.get_content () != "") { 1251 position = parse_double_from_node (t); 1252 1253 label = ""; 1254 foreach (Attribute attr in t.get_attributes ()) { 1255 if (attr.get_name () == "label") { 1256 label = decode (attr.get_content ()); 1257 } 1258 } 1259 1260 line = new Line (label, label, position); 1261 1262 font.custom_guides.add (line); 1263 } 1264 } 1265 } 1266 1267 private double parse_double_from_node (Tag tag) { 1268 double d; 1269 bool r = double.try_parse (tag.get_content (), out d); 1270 string s; 1271 1272 if (unlikely (!r)) { 1273 s = tag.get_content (); 1274 if (s == "") { 1275 warning (@"No content for node\n"); 1276 } else { 1277 warning (@"Failed to parse double for \"$(tag.get_content ())\"\n"); 1278 } 1279 } 1280 1281 return (r) ? d : 0.0; 1282 } 1283 1284 /** Parse the new glyph format */ 1285 private void parse_glyph_collection (Tag tag) { 1286 unichar unicode = 0; 1287 GlyphCollection gc; 1288 GlyphCollection? current_gc; 1289 bool new_glyph_collection; 1290 StringBuilder b; 1291 string name = ""; 1292 int selected_id = -1; 1293 bool unassigned = false; 1294 string master_id = ""; 1295 GlyphMaster master; 1296 1297 foreach (Attribute attribute in tag.get_attributes ()) { 1298 if (attribute.get_name () == "unicode") { 1299 unicode = Font.to_unichar (attribute.get_content ()); 1300 b = new StringBuilder (); 1301 b.append_unichar (unicode); 1302 name = b.str; 1303 1304 if (name == "") { 1305 name = ".null"; 1306 } 1307 1308 unassigned = false; 1309 } 1310 1311 // set name because either name or unicode is set in the bf file 1312 if (attribute.get_name () == "name") { 1313 unicode = '\0'; 1314 name = decode (attribute.get_content ()); 1315 unassigned = true; 1316 } 1317 1318 if (attribute.get_name () == "master") { 1319 master_id = attribute.get_content (); 1320 } 1321 } 1322 1323 current_gc = font.get_glyph_collection_by_name (name); 1324 new_glyph_collection = (current_gc == null); 1325 1326 if (!new_glyph_collection) { 1327 gc = (!) current_gc; 1328 } else { 1329 gc = new GlyphCollection (unicode, name); 1330 } 1331 1332 if (gc.has_master (master_id)) { 1333 master = gc.get_master (master_id); 1334 } else { 1335 master = new GlyphMaster.for_id (master_id); 1336 gc.add_master (master); 1337 } 1338 1339 foreach (Tag t in tag) { 1340 if (t.get_name () == "selected") { 1341 selected_id = parse_selected (t); 1342 master.set_selected_version (selected_id); 1343 } 1344 } 1345 1346 foreach (Tag t in tag) { 1347 if (t.get_name () == "glyph") { 1348 parse_glyph (t, gc, master, name, unicode, selected_id, unassigned); 1349 } 1350 } 1351 1352 if (new_glyph_collection) { 1353 font.add_glyph_collection (gc); 1354 } 1355 } 1356 1357 private int parse_selected (Tag tag) { 1358 int id = 1; 1359 bool has_selected_tag = false; 1360 1361 foreach (Attribute attribute in tag.get_attributes ()) { 1362 if (attribute.get_name () == "id") { 1363 id = int.parse (attribute.get_content ()); 1364 has_selected_tag = true; 1365 break; 1366 } 1367 } 1368 1369 if (unlikely (!has_selected_tag)) { 1370 warning ("No selected tag."); 1371 } 1372 1373 return id; 1374 } 1375 1376 public void parse_glyph (Tag tag, GlyphCollection gc, GlyphMaster master, 1377 string name, unichar unicode, int selected_id, bool unassigned) { 1378 Glyph glyph = new Glyph (name, unicode); 1379 Path path; 1380 bool selected = false; 1381 bool has_id = false; 1382 int id = 1; 1383 Layer layer; 1384 1385 foreach (Attribute attr in tag.get_attributes ()) { 1386 if (attr.get_name () == "left") { 1387 glyph.left_limit = double.parse (attr.get_content ()); 1388 } 1389 1390 if (attr.get_name () == "right") { 1391 glyph.right_limit = double.parse (attr.get_content ()); 1392 } 1393 1394 // id is unique within the glyph collection 1395 if (attr.get_name () == "id") { 1396 id = int.parse (attr.get_content ()); 1397 has_id = true; 1398 } 1399 1400 // old way of selecting a glyph in the version list 1401 if (attr.get_name () == "selected") { 1402 selected = bool.parse (attr.get_content ()); 1403 } 1404 } 1405 1406 foreach (Tag t in tag) { 1407 if (t.get_name () == "layer") { 1408 layer = parse_layer (t); 1409 glyph.layers.add_layer (layer); 1410 } 1411 } 1412 1413 // parse paths without layers in old versions of the format 1414 foreach (Tag t in tag) { 1415 if (t.get_name () == "path") { 1416 path = parse_path (t); 1417 glyph.add_path (path); 1418 } 1419 } 1420 1421 foreach (Tag t in tag) { 1422 if (t.get_name () == "background") { 1423 parse_background_scale (glyph, t); 1424 } 1425 } 1426 1427 foreach (Path p in glyph.get_all_paths ()) { 1428 p.reset_stroke (); 1429 } 1430 1431 glyph.version_id = (has_id) ? id : (int) gc.length () + 1; 1432 gc.set_unassigned (unassigned); 1433 1434 master.insert_glyph (glyph, selected || selected_id == id); 1435 } 1436 1437 Layer parse_layer (Tag tag) { 1438 Layer layer = new Layer (); 1439 Path path; 1440 1441 foreach (Attribute a in tag.get_attributes ()) { 1442 if (a.get_name () == "visible") { 1443 layer.visible = bool.parse (a.get_content ()); 1444 } 1445 1446 if (a.get_name () == "name") { 1447 layer.name = a.get_content (); 1448 } 1449 } 1450 1451 foreach (Tag t in tag) { 1452 if (t.get_name () == "path") { 1453 path = parse_path (t); 1454 layer.add_path (path); 1455 } 1456 1457 if (t.get_name () == "embedded") { 1458 font.has_svg = true; 1459 } 1460 } 1461 1462 return layer; 1463 } 1464 1465 private Path parse_path (Tag tag) { 1466 Path path = new Path (); 1467 1468 foreach (Attribute attr in tag.get_attributes ()) { 1469 if (attr.get_name () == "data") { 1470 path.point_data = attr.get_content (); 1471 path.control_points = null; 1472 } 1473 } 1474 1475 foreach (Attribute attr in tag.get_attributes ()) { 1476 if (attr.get_name () == "stroke") { 1477 path.stroke = double.parse (attr.get_content ()); 1478 } 1479 1480 if (attr.get_name () == "skew") { 1481 path.skew = double.parse (attr.get_content ()); 1482 } 1483 1484 if (attr.get_name () == "cap") { 1485 if (attr.get_content () == "round") { 1486 path.line_cap = LineCap.ROUND; 1487 } else if (attr.get_content () == "square") { 1488 path.line_cap = LineCap.SQUARE; 1489 } 1490 } 1491 } 1492 1493 return path; 1494 } 1495 1496 private static void line (Path path, string px, string py) { 1497 EditPoint ep; 1498 1499 path.add (parse_double (px), parse_double (py)); 1500 ep = path.get_last_point (); 1501 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE; 1502 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE; 1503 ep.type = PointType.LINE_DOUBLE_CURVE; 1504 path.recalculate_linear_handles_for_point (ep); 1505 } 1506 1507 private static void cubic_line (Path path, string px, string py) { 1508 EditPoint ep; 1509 1510 path.add (parse_double (px), parse_double (py)); 1511 ep = path.points.get (path.points.size - 1); 1512 ep.get_right_handle ().type = PointType.LINE_CUBIC; 1513 ep.type = PointType.LINE_CUBIC; 1514 path.recalculate_linear_handles_for_point (ep); 1515 } 1516 1517 private static void quadratic (Path path, string px0, string py0, string px1, string py1) { 1518 EditPoint ep1, ep2; 1519 1520 double x0 = parse_double (px0); 1521 double y0 = parse_double (py0); 1522 double x1 = parse_double (px1); 1523 double y1 = parse_double (py1); 1524 1525 if (path.points.size == 0) { 1526 warning ("No point."); 1527 return; 1528 } 1529 1530 ep1 = path.points.get (path.points.size - 1); 1531 path.recalculate_linear_handles_for_point (ep1); 1532 ep1.get_right_handle ().type = PointType.QUADRATIC; 1533 ep1.get_right_handle ().move_to_coordinate (x0, y0); 1534 ep1.type = PointType.QUADRATIC; 1535 1536 path.add (x1, y1); 1537 1538 ep2 = path.points.get (path.points.size - 1); 1539 path.recalculate_linear_handles_for_point (ep2); 1540 ep2.get_left_handle ().type = PointType.QUADRATIC; 1541 ep2.get_left_handle ().move_to_coordinate (x0, y0); 1542 ep2.type = PointType.QUADRATIC; 1543 } 1544 1545 private static void cubic (Path path, string px0, string py0, string px1, string py1, string px2, string py2) { 1546 EditPoint ep1, ep2; 1547 1548 double x0 = parse_double (px0); 1549 double y0 = parse_double (py0); 1550 double x1 = parse_double (px1); 1551 double y1 = parse_double (py1); 1552 double x2 = parse_double (px2); 1553 double y2 = parse_double (py2); 1554 1555 double lx, ly; 1556 1557 if (path.points.size == 0) { 1558 warning ("No point"); 1559 return; 1560 } 1561 1562 // start with line handles 1563 ep1 = path.points.get (path.points.size - 1); 1564 ep1.get_right_handle ().type = PointType.LINE_CUBIC; 1565 1566 lx = ep1.x + ((x2 - ep1.x) / 3); 1567 ly = ep1.y + ((y2 - ep1.y) / 3); 1568 1569 ep1.get_right_handle ().move_to_coordinate (lx, ly); 1570 path.recalculate_linear_handles_for_point (ep1); 1571 1572 // set curve handles 1573 ep1 = path.points.get (path.points.size - 1); 1574 path.recalculate_linear_handles_for_point (ep1); 1575 ep1.get_right_handle ().type = PointType.CUBIC; 1576 ep1.get_right_handle ().move_to_coordinate (x0, y0); 1577 ep1.type = PointType.CUBIC; 1578 1579 path.add (x2, y2); 1580 1581 ep2 = path.points.get (path.points.size - 1); 1582 path.recalculate_linear_handles_for_point (ep2); 1583 ep2.get_left_handle ().type = PointType.CUBIC; 1584 ep2.get_left_handle ().move_to_coordinate (x1, y1); 1585 ep2.type = PointType.CUBIC; 1586 1587 path.recalculate_linear_handles_for_point (ep2); 1588 } 1589 1590 /** Two quadratic off curve points. */ 1591 private static void double_curve (Path path, string px0, string py0, string px1, string py1, string px2, string py2) { 1592 EditPoint ep1, ep2; 1593 1594 double x0 = parse_double (px0); 1595 double y0 = parse_double (py0); 1596 double x1 = parse_double (px1); 1597 double y1 = parse_double (py1); 1598 double x2 = parse_double (px2); 1599 double y2 = parse_double (py2); 1600 1601 double lx, ly; 1602 1603 if (path.points.size == 0) { 1604 warning ("No point"); 1605 return; 1606 } 1607 1608 // start with line handles 1609 ep1 = path.points.get (path.points.size - 1); 1610 ep1.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE; 1611 1612 lx = ep1.x + ((x2 - ep1.x) / 4); 1613 ly = ep1.y + ((y2 - ep1.y) / 4); 1614 1615 ep1.get_right_handle ().move_to_coordinate (lx, ly); 1616 path.recalculate_linear_handles_for_point (ep1); 1617 1618 // set curve handles 1619 ep1 = path.points.get (path.points.size - 1); 1620 path.recalculate_linear_handles_for_point (ep1); 1621 ep1.get_right_handle ().type = PointType.DOUBLE_CURVE; 1622 ep1.get_right_handle ().move_to_coordinate (x0, y0); 1623 ep1.type = PointType.DOUBLE_CURVE; 1624 1625 ep2 = path.add (x2, y2); 1626 path.recalculate_linear_handles_for_point (ep2); 1627 ep2.get_left_handle ().type = PointType.DOUBLE_CURVE; 1628 ep2.get_left_handle ().move_to_coordinate (x1, y1); 1629 ep2.type = PointType.DOUBLE_CURVE; 1630 1631 path.recalculate_linear_handles_for_point (ep1); 1632 } 1633 1634 public static void close (Path path) { 1635 EditPoint ep1, ep2; 1636 1637 if (path.points.size < 2) { 1638 warning ("Less than two points in path."); 1639 return; 1640 } 1641 1642 // last point is first 1643 ep1 = path.points.get (path.points.size - 1); 1644 ep2 = path.points.get (0); 1645 1646 path.points.remove_at (path.points.size - 1); 1647 1648 if (ep1.type != PointType.QUADRATIC || ep2.type != PointType.QUADRATIC) { 1649 ep2.tie_handles = ep1.tie_handles; 1650 ep2.left_handle.angle = ep1.left_handle.angle; 1651 ep2.left_handle.length = ep1.left_handle.length; 1652 ep2.left_handle.type = ep1.left_handle.type; 1653 } 1654 1655 path.close (); 1656 } 1657 1658 public static void parse_path_data (string data, Path path) { 1659 string[] d = data.split (" "); 1660 string[] p, p1, p2; 1661 int i = 0; 1662 string instruction = ""; 1663 bool open = false; 1664 1665 if (data == "") { 1666 return; 1667 } 1668 1669 return_if_fail (d.length > 1); 1670 1671 if (!(d[0] == "S" || d[0] == "B")) { 1672 warning ("No start point."); 1673 return; 1674 } 1675 1676 instruction = d[i++]; 1677 1678 if (instruction == "S") { 1679 p = d[i++].split (","); 1680 return_if_fail (p.length == 2); 1681 line (path, p[0], p[1]); 1682 } 1683 1684 if (instruction == "B") { 1685 p = d[i++].split (","); 1686 return_if_fail (p.length == 2); 1687 cubic_line (path, p[0], p[1]); 1688 } 1689 1690 while (i < d.length) { 1691 instruction = d[i++]; 1692 1693 if (instruction == "") { 1694 warning (@"No instruction at index $i."); 1695 return; 1696 } 1697 1698 if (instruction == "L") { 1699 return_if_fail (i < d.length); 1700 p = d[i++].split (","); 1701 return_if_fail (p.length == 2); 1702 line (path, p[0], p[1]); 1703 }else if (instruction == "M") { 1704 return_if_fail (i < d.length); 1705 p = d[i++].split (","); 1706 return_if_fail (p.length == 2); 1707 cubic_line (path, p[0], p[1]); 1708 } else if (instruction == "Q") { 1709 return_if_fail (i + 1 < d.length); 1710 1711 p = d[i++].split (","); 1712 p1 = d[i++].split (","); 1713 1714 return_if_fail (p.length == 2); 1715 return_if_fail (p1.length == 2); 1716 1717 quadratic (path, p[0], p[1], p1[0], p1[1]); 1718 } else if (instruction == "D") { 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 double_curve (path, p[0], p[1], p1[0], p1[1], p2[0], p2[1]); 1730 } else if (instruction == "C") { 1731 return_if_fail (i + 2 < d.length); 1732 1733 p = d[i++].split (","); 1734 p1 = d[i++].split (","); 1735 p2 = d[i++].split (","); 1736 1737 return_if_fail (p.length == 2); 1738 return_if_fail (p1.length == 2); 1739 return_if_fail (p2.length == 2); 1740 1741 cubic (path, p[0], p[1], p1[0], p1[1], p2[0], p2[1]); 1742 } else if (instruction == "T") { 1743 path.points.get (path.points.size - 1).tie_handles = true; 1744 } else if (instruction == "O") { 1745 open = true; 1746 } else { 1747 warning (@"invalid instruction $instruction"); 1748 return; 1749 } 1750 } 1751 1752 if (!open) { 1753 close (path); 1754 } else { 1755 path.points.remove_at (path.points.size - 1); 1756 1757 if (!path.is_open ()) { 1758 warning ("Closed path."); 1759 } 1760 } 1761 1762 path.update_region_boundaries (); 1763 } 1764 1765 private static double parse_double (string p) { 1766 double d; 1767 if (double.try_parse (p, out d)) { 1768 return d; 1769 } 1770 1771 warning (@"failed to parse $p"); 1772 return 0; 1773 } 1774 1775 private void parse_background_scale (Glyph g, Tag tag) { 1776 BackgroundImage img; 1777 BackgroundImage? new_img = null; 1778 1779 File img_file = get_child (font.get_backgrounds_folder (), "parts"); 1780 1781 foreach (Attribute attr in tag.get_attributes ()) { 1782 if (attr.get_name () == "sha1") { 1783 img_file = get_child (img_file, attr.get_content () + ".png"); 1784 1785 if (!img_file.query_exists ()) { 1786 warning (@"Background file has not been created yet. $((!) img_file.get_path ())"); 1787 } 1788 1789 new_img = new BackgroundImage ((!) img_file.get_path ()); 1790 g.set_background_image ((!) new_img); 1791 } 1792 } 1793 1794 if (unlikely (new_img == null)) { 1795 warning ("No source for image found."); 1796 return; 1797 } 1798 1799 img = (!) new_img; 1800 1801 foreach (Attribute attr in tag.get_attributes ()) { 1802 if (attr.get_name () == "x") { 1803 img.img_x = double.parse (attr.get_content ()); 1804 } 1805 1806 if (attr.get_name () == "y") { 1807 img.img_y = double.parse (attr.get_content ()); 1808 } 1809 1810 if (attr.get_name () == "scale_x") { 1811 img.img_scale_x = double.parse (attr.get_content ()); 1812 } 1813 1814 if (attr.get_name () == "scale_y") { 1815 img.img_scale_y = double.parse (attr.get_content ()); 1816 } 1817 1818 if (attr.get_name () == "rotation") { 1819 img.img_rotation = double.parse (attr.get_content ()); 1820 } 1821 } 1822 1823 img.set_position (img.img_x, img.img_y); 1824 } 1825 1826 public void write_ligatures (DataOutputStream os) { 1827 Ligatures ligatures = font.get_ligatures (); 1828 1829 ligatures.get_ligatures ((subst, liga) => { 1830 try { 1831 string lig = serialize_attribute (liga); 1832 string sequence = serialize_attribute (encode (subst)); 1833 os.put_string (@"<ligature sequence=\"$(encode (sequence))\" replacement=\"$(encode (lig))\"/>\n"); 1834 } catch (GLib.IOError e) { 1835 warning (e.message); 1836 } 1837 }); 1838 1839 try { 1840 foreach (ContextualLigature c in ligatures.contextual_ligatures) { 1841 os.put_string (@"<contextual " 1842 + @"ligature=\"$(c.ligatures)\" " 1843 + @"backtrack=\"$(c.backtrack)\" " 1844 + @"input=\"$(c.input)\" " 1845 + @"lookahead=\"$(c.lookahead)\" />\n"); 1846 } 1847 } catch (GLib.Error e) { 1848 warning (e.message); 1849 } 1850 } 1851 1852 public void parse_contectual_ligature (Tag t) { 1853 string ligature = ""; 1854 string backtrack = ""; 1855 string input = ""; 1856 string lookahead = ""; 1857 Ligatures ligatures; 1858 1859 foreach (Attribute a in t.get_attributes ()) { 1860 if (a.get_name () == "ligature") { 1861 ligature = decode (a.get_content ()); 1862 } 1863 1864 if (a.get_name () == "backtrack") { 1865 backtrack = decode (a.get_content ()); 1866 } 1867 1868 if (a.get_name () == "input") { 1869 input = decode (a.get_content ()); 1870 } 1871 1872 if (a.get_name () == "lookahead") { 1873 lookahead = decode (a.get_content ()); 1874 } 1875 } 1876 1877 ligatures = font.get_ligatures (); 1878 ligatures.add_contextual_ligature (ligature, backtrack, input, lookahead); 1879 } 1880 1881 public void parse_ligature (Tag t) { 1882 string sequence = ""; 1883 string ligature = ""; 1884 Ligatures ligatures; 1885 1886 foreach (Attribute a in t.get_attributes ()) { 1887 if (a.get_name () == "sequence") { 1888 sequence = decode (a.get_content ()); 1889 } 1890 1891 if (a.get_name () == "replacement") { 1892 ligature = decode (a.get_content ()); 1893 } 1894 } 1895 1896 ligatures = font.get_ligatures (); 1897 ligatures.add_ligature (sequence, ligature); 1898 } 1899 1900 public static string decode (string s) { 1901 string t; 1902 t = s.replace ("&quot;", "\""); 1903 t = t.replace ("&apos;", "'"); 1904 t = t.replace ("&lt;", "<"); 1905 t = t.replace ("&gt;", ">"); 1906 t = t.replace ("&amp;", "&"); 1907 return t; 1908 } 1909 1910 /** 1911 * Replace ", ' < > and & with encoded characters. 1912 */ 1913 public static string encode (string s) { 1914 string t; 1915 t = s.replace ("&", "&amp;"); 1916 t = t.replace ("\"", "&quot;"); 1917 t = t.replace ("'", "&apos;"); 1918 t = t.replace ("<", "&lt;"); 1919 t = t.replace (">", "&gt;"); 1920 return t; 1921 } 1922 } 1923 1924 } 1925