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