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