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