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