The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

BirdFontFile.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

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

Revisions

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