.
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