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