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