Clone in a terminal:
Commit 80251fa
Johan Mattsson - 2013-03-02T23:54:13+00:00
80251fa76d44f216664d0c4ab31a171270ca4a06johan.mattsson.m@gmail.com

new file format (.bf replaces .ffi)

Changed files (6)

Diff

diff --git a/libbirdfont/BirdFontFile.vala b/libbirdfont/BirdFontFile.valanew file mode 100644index 0000000..90d6405--- /dev/null+++ b/libbirdfont/BirdFontFile.vala@@ -0,0 +1,1049 @@+/*+    Copyright (C) 2013 Johan Mattsson++    This program is free software: you can redistribute it and/or modify+    it under the terms of the GNU General Public License as published by+    the Free Software Foundation, either version 3 of the License, or+    (at your option) any later version.++    This program is distributed in the hope that it will be useful,+    but WITHOUT ANY WARRANTY; without even the implied warranty of+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the+    GNU General Public License for more details.++    You should have received a copy of the GNU General Public License+    along with this program.  If not, see <http://www.gnu.org/licenses/>.+*/+using Xml;++namespace BirdFont {++/** + * BirdFont file format. This class can parse both the old ffi format + * and the new bf format.+ */+class BirdFontFile {+	+	Font font;+	+	public BirdFontFile (Font f) {+		font = f;+	}++	public bool write_font_file (string path, bool backup = false) {+		try {+			File file = File.new_for_path (path);++			if (file.query_file_type (0) == FileType.DIRECTORY) {+				stderr.printf (@"Can not save font. $path is a directory.");+				return false;+			}+			+			if (file.query_exists ()) {+				file.delete ();+			}+			+			DataOutputStream os = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION));+			+			os.put_string ("""<?xml version="1.0" encoding="utf-8" standalone="yes"?>""");+			os.put_string ("\n");+				+			os.put_string ("<font>\n");+			+			// this a backup of another font+			if (backup) {+				if (unlikely (font.font_file == null)) {+					warning ("No file name is set, write backup file name to font file.");+				} else {+					os.put_string ("\n");+					os.put_string (@"<!-- This is a backup of the following font: -->\n");	+					os.put_string (@"<backup>$((!) font.font_file)</backup>\n");	+				}+			}+			+			os.put_string ("\n");+			os.put_string (@"<name>$(font.get_name ())</name>\n");+			+			os.put_string ("\n");+			os.put_string ("<lines>\n");+			+			os.put_string (@"\t<top_limit>$(font.top_limit)</top_limit>\n");+			os.put_string (@"\t<top_position>$(font.top_position)</top_position>\n");+			os.put_string (@"\t<x-height>$(font.xheight_position)</x-height>\n");+			os.put_string (@"\t<base_line>$(font.base_line)</base_line>\n");+			os.put_string (@"\t<bottom_position>$(font.bottom_position)</bottom_position>\n");+			os.put_string (@"\t<bottom_limit>$(font.bottom_limit)</bottom_limit>\n");+			+			os.put_string ("</lines>\n\n");++			foreach (string gv in font.grid_width) {+				os.put_string (@"<grid width=\"$(gv)\"/>\n");+			}+			+			if (GridTool.sizes.length () > 0) {+				os.put_string ("\n");+			}+			+			os.put_string (@"<background scale=\"$(font.background_scale)\" />\n");+			os.put_string ("\n");+			+			if (font.background_images.length () > 0) {+				os.put_string (@"<images>\n");+				+				foreach (string f in font.background_images) {+					os.put_string (@"\t<img src=\"$f\"/>\n");+				}+			+				os.put_string (@"</images>\n");+				os.put_string ("\n");+			}+			+			font.glyph_cache.for_each ((gc) => {+				if (is_null (gc)) {+					warning ("No glyph collection");+				}+				+				try {+					write_glyph_collection (gc, os);+				} catch (GLib.Error e) {+					warning (e.message);+				}+			});+		+			font.glyph_cache.for_each ((gc) => {+				Glyph glyph;+				+				try {+					glyph = gc.get_current ();+					+					foreach (Kerning k in glyph.kerning) {+						string l, r;+						Glyph? gr = font.get_glyph (k.glyph_right);+						Glyph glyph_right;++						if (gr == null) {+							warning ("kerning a glyph that does not exist. (" + glyph.name + " -> " + k.glyph_right + ")");+							continue;+						}+						+						glyph_right = (!) gr;+						+						l = Font.to_hex_code (glyph.unichar_code);+						r = Font.to_hex_code (glyph_right.unichar_code);+										+						os.put_string (@"<hkern left=\"U+$l\" right=\"U+$r\" kerning=\"$(k.val)\"/>\n");+					}+				} catch (GLib.Error e) {+					warning (e.message);+				}+			});++			font.glyph_cache.for_each ((gc) => {+				GlyphBackgroundImage bg;+				+				try {+					string data;+					+					foreach (Glyph g in gc.get_version_list ().glyphs) {++						if (g.get_background_image () != null) {+							bg = (!) g.get_background_image ();+							data = bg.get_png_base64 ();+							+							if (!bg.is_valid ()) {+								continue;+							}+							+							os.put_string (@"<background-image sha1=\"");+							os.put_string (bg.get_sha1 ());+							os.put_string ("\" ");+							os.put_string (" data=\"");+							os.put_string (data);+							os.put_string ("");+							os.put_string ("\" />\n");	+						}+					}+				} catch (GLib.Error ef) {+					stderr.printf (@"Failed to save $path \n");+					stderr.printf (@"$(ef.message) \n");+				}+			});+						+			os.put_string ("</font>");+			+		} catch (GLib.Error e) {+			stderr.printf (@"Failed to save $path \n");+			stderr.printf (@"$(e.message) \n");+			return false;+		}+		+		return true;+	}++	/** Get control points in BirdFont format. This function is uses a+	 * cartesian coordinate system with origo in the middle.+	 * +	 * Instructions:+	 * S - Start point for path +	 * L - Line+	 * Q - Quadratic Bézier path+	 * C - Cubic Bézier path+	 */+	private string get_point_data (Path pl) {+		StringBuilder data = new StringBuilder ();+		EditPoint? n = null;+		EditPoint m;+		int i = 0;+		+		if (pl.points.length () < 2) {+			return "";+		}+		+		pl.create_list ();+			+		foreach (EditPoint e in pl.points) {+			if (i == 0) {+				add_start (e, data);+				i++;+				n = e;+				continue;+			}+			+			m = (!) n;+			data.append (" ");+			add_next_point (m, e, data);+			n = e;+			i++;+		}++		data.append (" ");+		m = pl.points.first ().data;	+		add_next_point ((!) n, m, data);+		+		return data.str;+	}+	+	private void add_start (EditPoint p, StringBuilder data) {+		data.append (@"S $(p.x),$(p.y)");+	}++	private void add_line_to (EditPoint p, StringBuilder data) {+		data.append (@"L $(p.x),$(p.y)");+	}++	private void add_quadratic (EditPoint start, EditPoint end, StringBuilder data) {+		EditPointHandle h = start.get_right_handle ();+		data.append (@"Q $(h.x ()),$(h.y ()) $(end.x),$(end.y)");+	}++	private void add_cubic (EditPoint start, EditPoint end, StringBuilder data) {+		EditPointHandle h1 = start.get_right_handle ();+		EditPointHandle h2 = end.get_left_handle ();+		data.append (@"C $(h1.x ()),$(h1.y ()) $(h2.x ()),$(h2.y ()) $(end.x),$(end.y)");+	}++	private void add_next_point (EditPoint start, EditPoint end, StringBuilder data) {+		if (start.right_handle.type == PointType.LINE_QUADRATIC) {+			add_line_to (end, data);+		} else if (start.right_handle.type == PointType.LINE_CUBIC && end.left_handle.type == PointType.LINE_CUBIC) {+			add_line_to (end, data);+		} else if (end.left_handle.type == PointType.QUADRATIC || start.right_handle.type == PointType.QUADRATIC) {+			add_quadratic (start, end, data);+		} else {+			add_cubic (start, end, data);+		}		+	}++	private void write_glyph_collection (GlyphCollection gc, DataOutputStream os)  throws GLib.Error {+		os.put_string (@"<collection unicode=\"$(Font.to_hex (gc.get_current ().unichar_code))\">\n");+		foreach (Glyph g in gc.get_version_list ().glyphs) {+			write_glyph (g, gc, os);+		}+		os.put_string ("</collection>\n");+	} ++	private void write_glyph (Glyph g, GlyphCollection gc, DataOutputStream os) throws GLib.Error {+		bool selected = (gc.get_current () == g);++		os.put_string (@"\t<glyph left=\"$(g.left_limit)\" right=\"$(g.right_limit)\" selected=\"$selected\">\n");+		+		foreach (Path p in g.path_list) {+			os.put_string (@"\t\t<path data=\"$(get_point_data (p))\" />\n");+		}+		+		write_glyph_background (g, os);+		+		os.put_string ("\t</glyph>\n");+	}++	private void write_glyph_background (Glyph g, DataOutputStream os) {+		GlyphBackgroundImage? bg;+		GlyphBackgroundImage background_image;+		double pos_x, pos_y, scale_x, scale_y, rotation;+		+		bg = g.get_background_image ();+		+		if (bg != null) {+			background_image = (!) bg;++			pos_x = background_image.img_x;+			pos_y = background_image.img_y;+			+			scale_x = background_image.img_scale_x;+			scale_y = background_image.img_scale_y;+			+			rotation = background_image.img_rotation;+			+			if (background_image.is_valid ()) {+				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");+			}+		}			+	}++	public bool load (string path) {+		try {+			return parse_file (path);+		} catch (GLib.Error e) {+			warning (e.message);+		}+		return false;+	}++	private bool parse_file (string path) throws GLib.Error {+		Parser.init ();+		+		Xml.Doc* doc;+		Xml.Node* root;+		Xml.Node* node;++		// set this path as file for this font, it will be updated if this is a backup+		font.font_file = path;++		while (font.background_images.length () > 0) {+			font.background_images.remove_link (font.background_images.first ());+		}++		create_background_files (path);++		doc = Parser.parse_file (path);+		root = doc->get_root_element ();+		+		if (root == null) {+			warning ("no root element");+			delete doc;+			return false;+		}++		node = root;+		+		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {++			// this is a backup file set path to the original +			if (iter->name == "backup") {+				font.font_file = iter->children->content;+			}+				+			// old glyph format+			if (iter->name == "glyph") {+				parse_ffi_glyph (iter);+			}+			+			// new glyph format+			if (iter->name == "collection") {+				parse_glyph_collection (iter);+			}++			if (iter->name == "lines") {+				parse_font_boundries (iter);+			}++			if (iter->name == "grid") {+				parse_grid (iter);+			}++			if (iter->name == "background") {+				parse_background (iter);+			}+			+			if (iter->name == "images") {+				parse_background_selection (iter);+			}++			if (iter->name == "name" && iter->children != null) {+				font.set_name (iter->children->content);+			}++			if (iter->name == "hkern") {+				parse_kerning (iter);+			}+		}+    +		delete doc;+		Parser.cleanup ();++		return true;+	}+	+	private void create_background_files (string path) {+		Xml.Doc* doc = Parser.parse_file (path);+		Xml.Node* root;+		Xml.Node* node;+		+		root = doc->get_root_element ();+		+		if (root == null) {+			delete doc;+			return;+		}++		node = root;+		+		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {+			+			if (iter->name == "name" && iter->children != null) {+				font.set_name (iter->children->content);+			}+			+			if (iter->name == "background-image") {+				parse_background_image (iter);+			}+		}+    +		delete doc;+		Parser.cleanup ();+	}+	+	private void parse_kerning (Xml.Node* node) {+		string attr_name;+		string attr_content;+		string left = "";+		string right = "";+		string kern = "";++		StringBuilder b;+		+		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+			attr_name = prop->name;+			attr_content = prop->children->content;+			+			if (attr_name == "left") {+				b = new StringBuilder ();+				b.append_unichar (Font.to_unichar (attr_content));+				left = @"$(b.str)";+			}++			if (attr_name == "right") {+				b = new StringBuilder ();+				b.append_unichar (Font.to_unichar (attr_content));+				right = @"$(b.str)";+			}+			+			if (attr_name == "kerning") {+				kern = attr_content;+			}+		}+		+		font.set_kerning (left, right, double.parse (kern));+	}+	+	private void parse_background_image (Xml.Node* node) +		requires (node != null)+	{+		string attr_name;+		string attr_content;+		+		string file = "";+		string data = "";+		+		File img_dir;+		File img_file;+		FileOutputStream file_stream;+		DataOutputStream png_stream;+		+		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+			return_if_fail (!is_null (prop->name));+			return_if_fail (!is_null (prop->children));+			return_if_fail (!is_null (prop->children->content));+			+			attr_name = prop->name;+			attr_content = prop->children->content;+			+			if (attr_name == "sha1") {+				file = attr_content;+			}+			+			if (attr_name == "data") {+				data = attr_content;+			}+		}+		+		img_dir = font.get_backgrounds_folder ().get_child ("parts");++		if (!img_dir.query_exists ()) {+			DirUtils.create ((!) img_dir.get_path (), 0xFFFFFF);+		}+	+		img_file = img_dir.get_child (@"$(file).png");+		+		if (img_file.query_exists ()) {+			return;+		}+		+		try {+			file_stream = img_file.create (FileCreateFlags.REPLACE_DESTINATION);+			png_stream = new DataOutputStream (file_stream);++			png_stream.write (Base64.decode (data));+			png_stream.close ();	+		} catch (GLib.Error e) {+			warning (e.message);+		}+	}+	+	private void parse_background (Xml.Node* node) +		requires (node != null)+	{+		string attr_name;+		string attr_content;+				+		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+			+			return_if_fail (!is_null (prop->name));+			return_if_fail (!is_null (prop->children));+			return_if_fail (!is_null (prop->children->content));+			+			attr_name = prop->name;+			attr_content = prop->children->content;+			+			if (attr_name == "scale") {+				font.background_scale = attr_content;+			}+		}+	}+	+	private void parse_background_selection (Xml.Node* node) {+		string attr_name = "";+		string attr_content;+		+		return_if_fail (node != null);+				+		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {+			if (iter->name == "img") {+				for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {+					attr_name = prop->name;+					attr_content = prop->children->content;+					+					if (attr_name == "src") {+						font.background_images.append (attr_content);+					}+				}+			}+		}+	}+	+	private void parse_grid (Xml.Node* node) {+		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+			string attr_name = prop->name;+			string attr_content = prop->children->content;+			+			if (attr_name == "width") {+				font.grid_width.append (attr_content);+			}+		}		+	}+	+	private void parse_font_boundries (Xml.Node* node) {+		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {+			if (iter->name == "top_limit") font.top_limit = parse_double_from_node (iter);+			if (iter->name == "top_position") font.top_position = parse_double_from_node (iter);+			if (iter->name == "x-height") font.xheight_position = parse_double_from_node (iter);+			if (iter->name == "base_line") font.base_line = parse_double_from_node (iter);+			if (iter->name == "bottom_position") font.bottom_position = parse_double_from_node (iter);+			if (iter->name == "bottom_limit") font.bottom_limit = parse_double_from_node (iter);+		}			+	}+	+	private double parse_double_from_node (Xml.Node* iter) {+		double d;+		bool r = double.try_parse (iter->children->content, out d);+		+		if (unlikely (!r)) {+			string? s = iter->content;+			if (s == null) warning (@"Content is null for node $(iter->name)\n");+			else warning (@"Failed to parse double for \"$(iter->content)\"\n");+		}+		+		return (r) ? d : 0;+	}++	/** Parse the new glyph format */+	private void parse_glyph_collection (Xml.Node* node) {+		unichar unicode = 0;+		GlyphCollection gc = new GlyphCollection ();+		string attr_name;+		string attr_content;+		StringBuilder b;+		string name = "";+				+		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+			attr_name = prop->name;+			attr_content = prop->children->content;+			+			if (attr_name == "unicode") {+				unicode = Font.to_unichar (attr_content);+				b = new StringBuilder ();+				b.append_unichar (unicode);+				name = b.str;+			}+		}+		+		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {+			parse_glyph (iter, gc, name, unicode);+		}+	}++	private void parse_glyph (Xml.Node* node, GlyphCollection gc, string name, unichar unicode) {	+		string attr_name;+		string attr_content;+		Glyph glyph = new Glyph (name, unicode);+		Path path;+		bool selected = false;+		+		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+			attr_name = prop->name;+			attr_content = prop->children->content;+			+			if (attr_name == "left") {+				glyph.left_limit = double.parse (attr_content);+			}+			+			if (attr_name == "right") {+				glyph.right_limit = double.parse (attr_content);+			}+			+			if (attr_name == "selected") {+				selected = bool.parse (attr_content);+			}+		}+		+		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {+			if (iter->name == "path") {+				path = parse_path (iter);+				glyph.add_path (path);+			}+			+			if (iter->name == "background") {+				parse_background_scale (glyph, iter);+			}+		}+		+		gc.insert_glyph (glyph, selected);+		font.add_glyph_collection (gc);+	}++	private Path parse_path (Xml.Node* node) {	+		string attr_name;+		string attr_content;+		+		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+			attr_name = prop->name;+			attr_content = prop->children->content;+			+			if (attr_name == "data") {+				return parse_path_data (attr_content);+			}+		}+		+		warning ("No path");+		return new Path ();	+	}+	+	private void line (Path path, string px, string py) {+		path.add (parse_double (px), parse_double (py));+	}++	private void quadratic (Path path, string px0, string py0, string px1, string py1) {+		EditPoint ep1, ep2;+		+		double x0 = parse_double (px0);+		double y0 = parse_double (py0);+		double x1 = parse_double (px1);+		double y1 = parse_double (py1);++		if (is_null (path.points.last ().data)) {+			warning ("No point.");+			return;+		}+		+		ep1 = path.points.last ().data;+		ep1.recalculate_linear_handles ();+		ep1.get_right_handle ().type = PointType.QUADRATIC;+		ep1.get_right_handle ().move_to_coordinate (x0, y0);	++		path.add (x1, y1);++		ep2 = path.points.last ().data;+		ep2.recalculate_linear_handles ();+		ep2.get_left_handle ().type = PointType.QUADRATIC;+		ep2.get_left_handle ().move_to_coordinate (x0, y0);		+	}++	private void cubic (Path path, string px0, string py0, string px1, string py1, string px2, string py2) {+		EditPoint ep1, ep2;+		+		double x0 = parse_double (px0);+		double y0 = parse_double (py0);+		double x1 = parse_double (px1);+		double y1 = parse_double (py1);+		double x2 = parse_double (px2);+		double y2 = parse_double (py2);+		+		double lx, ly;+				+		if (is_null (path.points.last ().data)) {+			warning ("No point");+			return;+		}++		// start with line handles+		ep1 = path.points.last ().data;+		ep1.get_right_handle ().type = PointType.LINE_CUBIC;+		+		lx = ep1.x + ((x2 - ep1.x) / 3);+		ly = ep1.y + ((y2 - ep1.y) / 3);+						+		ep1.get_right_handle ().move_to_coordinate (lx, ly);+		ep1.recalculate_linear_handles ();+		+		// set curve handles+		ep1 = path.points.last ().data;+		ep1.recalculate_linear_handles ();+		ep1.get_right_handle ().type = PointType.CUBIC;+		ep1.get_right_handle ().move_to_coordinate (x0, y0);				++		path.add (x2, y2);+						+		ep2 = path.points.last ().data;+		ep2.recalculate_linear_handles ();+		ep2.get_left_handle ().type = PointType.CUBIC;+		ep2.get_left_handle ().move_to_coordinate (x1, y1);+	+		ep1.recalculate_linear_handles ();+	}+	+	private void close (Path path) {+		EditPoint ep1, ep2;+		+		// last point is first+		ep1 = path.points.last ().data;+		ep2 = path.points.first ().data;+		+		path.points.remove_link (path.points.last ());+		ep2.left_handle.angle = ep1.left_handle.angle;+		ep2.left_handle.length = ep1.left_handle.length;+		ep2.left_handle.type = ep1.left_handle.type;+	}+	+	private Path parse_path_data (string data) {+		string[] d = data.split (" ");+		string[] p, p1, p2;+		int i = 0;+		Path path = new Path ();+		string instruction = "";+		+		return_val_if_fail (d.length > 1, path);+		+		if (d[0] != "S") {+			warning ("No start point.");+		}+		+		if (d[i++] == "S") {+			p = d[i++].split (",");+			return_val_if_fail (p.length == 2, path);+			line (path, p[0], p[1]);+		}+		+		while (i < d.length) {+			instruction = d[i++];+			+			if (instruction == "") {+				warning (@"No instruction at index $i.");+				return path;+			}+			+			if (instruction == "L") {+				return_val_if_fail (i < d.length, path);+				p = d[i++].split (",");+				return_val_if_fail (p.length == 2, path);+				line (path, p[0], p[1]);+			} else if (instruction == "Q") {+				return_val_if_fail (i + 1 < d.length, path);+				+				p = d[i++].split (",");+				p1 = d[i++].split (",");+				+				return_val_if_fail (p.length == 2, path);+				return_val_if_fail (p1.length == 2, path);+				+				quadratic (path, p[0], p[1], p1[0], p1[1]);+			} else if (instruction == "C") {+				return_val_if_fail (i + 2 < d.length, path);+				+				p = d[i++].split (",");+				p1 = d[i++].split (",");+				p2 = d[i++].split (",");++				return_val_if_fail (p.length == 2, path);+				return_val_if_fail (p1.length == 2, path);+				return_val_if_fail (p2.length == 2, path);+				+				cubic (path, p[0], p[1], p1[0], p1[1], p2[0], p2[1]);+			} else {+				warning (@"invalid instruction $instruction");+				return path;+			}+		}++		close (path);+		+		return path;	+	}+	+	private double parse_double (string p) {+		double d;+		if (double.try_parse (p, out d)) {+			return d;+		}+		+		warning (@"failed to parse $p");+		return 0;+	}+	+	/** Parse one glyph in the old ffi format. */+	private void parse_ffi_glyph (Xml.Node* node) {+		string name = "";+		int left = 0;+		int right = 0;+		unichar uni = 0;+		int version = 0;+		bool selected = false;+		Glyph g;+		GlyphCollection? gc;+		+		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+			string attr_name = prop->name;+			string attr_content = prop->children->content;+			StringBuilder b;+			+			if (attr_name == "unicode") {+				uni = Font.to_unichar (attr_content);+				b = new StringBuilder ();+				b.append_unichar (uni);+				name = b.str;+			}++			if (attr_name == "left") {+				left = int.parse (attr_content);+			}+			+			if (attr_name == "right") {+				right = int.parse (attr_content);+			}+			+			if (attr_name == "version") {+				version = int.parse (attr_content);+			}+			+			if (attr_name == "selected") {+				selected = bool.parse (attr_content);+			}+		}++		g = new Glyph (name, uni);+		+		g.left_limit = left;+		g.right_limit = right;++		parse_content (g, node);+		+		gc = font.get_glyph_collection (g.get_name ());+		+		if (g.get_name () == "") {+			warning ("No name set for glyph.");+		}+				+		if (gc == null) {+			gc = new GlyphCollection (g);+			font.add_glyph_collection ((!) gc);+		} else {+			((!)gc).insert_glyph (g, selected);+		}+	}+	+	/** Parse visual objects and paths */+	private void parse_content (Glyph g, Xml.Node* node) {+		Xml.Node* i;+		return_if_fail (node != null);+		+		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {+			if (iter->name == "object") {+				Path p = new Path ();+				+				for (i = iter->children; i != null; i = i->next) {+					if (i->name == "point") {+						parse_point (p, i);+					}					+				}++				p.close ();+				g.add_path (p);+			}+			+			if (iter->name == "background") {+				parse_background_scale (g, iter);+			}+		}+	}+	+	private void parse_background_scale (Glyph g, Xml.Node* node) {+		GlyphBackgroundImage img;+		GlyphBackgroundImage? new_img = null;+		+		string attr_name = "";+		string attr_content;+		+		File img_file = font.get_backgrounds_folder ().get_child ("parts");+		+		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+			attr_name = prop->name;+			attr_content = prop->children->content;+			+			if (attr_name == "sha1") {+				img_file = img_file.get_child (attr_content + ".png");++				if (!img_file.query_exists ()) {+					warning (@"Background file has not been created yet. $((!) img_file.get_path ())");+				}+				+				new_img = new GlyphBackgroundImage ((!) img_file.get_path ());+				g.set_background_image ((!) new_img);+			}+		}+		+		if (unlikely (new_img == null)) {+			warning (@"No source for image found for $attr_name in $(g.name)");+			return;+		}+	+		img = (!) new_img;+	+		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+			attr_name = prop->name;+			attr_content = prop->children->content;+							+			if (attr_name == "x") {+				img.img_x = double.parse (attr_content);+			}+			+			if (attr_name == "y") {+				img.img_y = double.parse (attr_content);+			}	++			if (attr_name == "scale_x") {+				img.img_scale_x = double.parse (attr_content);+			}++			if (attr_name == "scale_y") {+				img.img_scale_y = double.parse (attr_content);+			}+						+			if (attr_name == "rotation") {+				img.img_rotation = double.parse (attr_content);+			}+		}+		+		img.set_position(img.img_x, img.img_y);	+	}+	+	private void parse_point (Path p, Xml.Node* iter) {+		double x = 0;+		double y = 0;+		+		double angle_right = 0;+		double angle_left = 0;+		+		double length_right = 0;+		double length_left = 0;+		+		PointType type_right = PointType.LINE_CUBIC;+		PointType type_left = PointType.LINE_CUBIC;+		+		bool tie_handles = false;+		+		EditPoint ep;+		+		for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {+			string attr_name = prop->name;+			string attr_content = prop->children->content;+						+			if (attr_name == "x") x = double.parse (attr_content);+			if (attr_name == "y") y = double.parse (attr_content);++			if (attr_name == "right_type" && attr_content == "linear") {+				type_right = PointType.LINE_CUBIC;+			}	++			if (attr_name == "left_type" && attr_content == "linear") {+				type_left = PointType.LINE_CUBIC;+			}	++			if (attr_name == "right_type" && attr_content == "quadratic") {+				type_right = PointType.QUADRATIC;+			}	++			if (attr_name == "left_type" && attr_content == "quadratic") {+				type_left = PointType.QUADRATIC;+			}++			if (attr_name == "right_type" && attr_content == "cubic") {+				type_right = PointType.CUBIC;+			}	++			if (attr_name == "left_type" && attr_content == "cubic") {+				type_left = PointType.CUBIC;+			}+			+			if (attr_name == "right_angle") angle_right = double.parse (attr_content);+			if (attr_name == "right_length") length_right = double.parse (attr_content);+			if (attr_name == "left_angle") angle_left = double.parse (attr_content);+			if (attr_name == "left_length") length_left = double.parse (attr_content);+			+			if (attr_name == "tie_handles") tie_handles = bool.parse (attr_content);+		}+	+		// backward compabtility+		if (type_right == PointType.LINE_CUBIC && length_right != 0) {+			type_right = PointType.CUBIC;+		}++		if (type_left == PointType.LINE_CUBIC && length_left != 0) {+			type_left = PointType.CUBIC;+		}+		+		ep = new EditPoint (x, y);+		+		ep.right_handle.angle = angle_right;+		ep.right_handle.length = length_right;+		ep.right_handle.type = type_right;+		+		ep.left_handle.angle = angle_left;+		ep.left_handle.length = length_left;+		ep.left_handle.type = type_left;+		+		ep.tie_handles = tie_handles;+		+		p.add_point (ep);+	}+}++}diff --git a/libbirdfont/ExportTool.vala b/libbirdfont/ExportTool.valaindex 6b5ca92..c69c122 100644--- a/libbirdfont/ExportTool.vala+++ b/libbirdfont/ExportTool.vala@@ -19,7 +19,6 @@ namespace BirdFont {  public class ExportTool : GLib.Object { 	private static ExportThread export_thread;-	private static bool stop_export_thread = false;  	public ExportTool (string n) { 	}@@ -159,30 +158,18 @@ public class ExportTool : GLib.Object { 	} 	 -	public static bool export_all () {-		Font font = BirdFont.get_current_font ();+	public static void export_all () { 		bool f;-		bool r = true;-		-		if (font.get_ttf_export ()) {-			f = export_ttf_font ();-			-			if (!f) {-				warning ("Failed to export font");-				r = false;-			}++		f = export_ttf_font ();+		if (!f) {+			warning ("Failed to export ttf font"); 		}-		-		if (font.get_svg_export ()) {-			f = export_svg_font (); -			if (!f) {-				warning ("Failed to export font");-				r = false;-			}+		f = export_svg_font ();+		if (!f) {+			warning ("Failed to export svg font"); 		}-		-		return r; 	}  	public static void generate_html_document (string html_file, Font font) {@@ -374,21 +361,6 @@ os.put_string ( 		return export_ttf_font_path (file); 	} 	-	/** Stop a running export thread.*/-	public static void stop_export () {-		lock (stop_export_thread) {-			stop_export_thread = true;-		}-	}-	-	public static bool should_stop () {-		bool r;-		lock (stop_export_thread) {-			r = stop_export_thread;-		}-		return r;-	} -	 	private static string[] get_birdfont_export (File folder, string temp_file) { 		File f; 		string dest = @"\"$((!) folder.get_path ())\" \"$temp_file\"";@@ -567,13 +539,8 @@ os.put_string ( 			assert (!is_null (ttf)); 			assert (!is_null (eot)); 			-			if (!should_stop ()) { -				write_ttf ();-			}-			-			if (!should_stop ()) { -				write_eof ();-			}+			write_ttf ();+			write_eof ();  			return null; 		}diff --git a/libbirdfont/Font.vala b/libbirdfont/Font.valaindex 5cebcf1..25a5895 100644--- a/libbirdfont/Font.vala+++ b/libbirdfont/Font.vala@@ -16,11 +16,11 @@ */  using Cairo;-using Xml;  namespace BirdFont {  public enum FontFormat {+	BIRDFONT, 	FFI, 	SVG, 	FREETYPE@@ -29,13 +29,13 @@ public enum FontFormat { public class Font : GLib.Object { 	 	/** Table with glyphs sorted by their unicode value. */-	GlyphTable glyph_cache = new GlyphTable ();+	public GlyphTable glyph_cache = new GlyphTable (); 	 	/** Table with glyphs sorted by their name. */-	GlyphTable glyph_name = new GlyphTable ();+	public GlyphTable glyph_name = new GlyphTable ();  	/** Table with ligatures. */-	GlyphTable ligature = new GlyphTable ();+	public GlyphTable ligature = new GlyphTable (); 	 	public List <string> background_images = new List <string> (); 	public string background_scale = "1";@@ -64,9 +64,6 @@ public class Font : GLib.Object { 	 	private string name = "typeface"; 	public bool initialised = true;-	-	bool ttf_export = true;-	bool svg_export = true;  	OpenFontFormatReader otf; 	bool otf_font = false;@@ -74,7 +71,7 @@ public class Font : GLib.Object { 	public List<string> grid_width = new List<string> (); 	 	/** File format. */-	public FontFormat format = FontFormat.FFI;+	public FontFormat format = FontFormat.BIRDFONT; 	 	public Font () { 		// positions in pixels at first zoom level@@ -91,14 +88,6 @@ public class Font : GLib.Object { 		modified = true; 	} -	public bool get_ttf_export () {-		return ttf_export;-	}--	public bool get_svg_export () {-		return svg_export;-	}- 	public File get_backgrounds_folder () { 		string fn = @"$(get_name ()) backgrounds"; 		File f = BirdFont.get_settings_directory ().get_child (fn);@@ -120,14 +109,14 @@ public class Font : GLib.Object { 		 		StringBuilder sb = new StringBuilder (); 		sb.append (Environment.get_home_dir ());-		sb.append (@"/$(get_name ()).ffi");+		sb.append (@"/$(get_name ()).bf"); 		 		File f = File.new_for_path (sb.str);  		while (f.query_exists ()) { 			sb.erase (); 			sb.append (Environment.get_home_dir ());-			sb.append (@"/$(get_name ())$(++i).ffi");+			sb.append (@"/$(get_name ())$(++i).bf"); 			f = File.new_for_path (sb.str); 		} 		@@ -505,7 +494,7 @@ public class Font : GLib.Object { 		File file; 		string backup_file; 		-		new_file = dir.get_child (@"$(name).ffi");+		new_file = dir.get_child (@"$(name).bf"); 		backup_file = (!) ((!) new_file).get_path (); 		 		try {@@ -524,19 +513,25 @@ public class Font : GLib.Object { 		File dir = BirdFont.get_backup_directory (); 		File? temp_file = null; 		string backup_file;+		BirdFontFile birdfont_file = new BirdFontFile (this); -		temp_file = dir.get_child (@"$(name).ffi");+		temp_file = dir.get_child (@"$(name).bf"); 		backup_file = (!) ((!) temp_file).get_path (); 		backup_file = backup_file.replace (" ", "_");-		-		write_font_file (backup_file, true);+		birdfont_file.write_font_file (backup_file, true); 		 		return backup_file; 	} 	 	public bool save (string path) { 		Font font;-		bool file_written = write_font_file (path);+		BirdFontFile birdfont_file = new BirdFontFile (this);+		bool file_written = birdfont_file.write_font_file (path);+		+		if (!path.has_suffix (".bf")) {+			warning ("Expecting .bf format.");+			return false;+		} 		 		if (file_written) { 			font_file = path;@@ -553,248 +548,6 @@ public class Font : GLib.Object { 		return file_written; 	} -	public bool write_font_file (string path, bool backup = false) {-		try {-			File file = File.new_for_path (path);--			if (file.query_file_type (0) == FileType.DIRECTORY) {-				stderr.printf (@"Can not save font. $path is a directory.");-				return false;-			}-			-			if (file.query_exists ()) {-				file.delete ();-			}-			-			DataOutputStream os = new DataOutputStream(file.create(FileCreateFlags.REPLACE_DESTINATION));-			-			os.put_string ("""<?xml version="1.0" encoding="utf-8" standalone="yes"?>""");-			os.put_string ("\n");-				-			os.put_string ("<font>\n");-			-			// this a backup of another font-			if (backup) {-				if (unlikely (font_file == null)) {-					warning ("No file name is set, write backup file name to font file.");-				} else {-					os.put_string ("\n");-					os.put_string (@"<!-- This is a backup of the following font: -->\n");	-					os.put_string (@"<backup>$((!) font_file)</backup>\n");	-				}-			}-			-			os.put_string ("\n");-			os.put_string (@"<name>$(get_name ())</name>\n");-			-			os.put_string ("\n");-			os.put_string (@"<ttf-export>$(ttf_export)</ttf-export>\n");--			os.put_string ("\n");-			os.put_string (@"<svg-export>$(svg_export)</svg-export>\n");-			-			os.put_string ("\n");-			os.put_string ("<lines>\n");-			-			os.put_string (@"\t<top_limit>$top_limit</top_limit>\n");-			os.put_string (@"\t<top_position>$top_position</top_position>\n");-			os.put_string (@"\t<x-height>$xheight_position</x-height>\n");-			os.put_string (@"\t<base_line>$base_line</base_line>\n");-			os.put_string (@"\t<bottom_position>$bottom_position</bottom_position>\n");-			os.put_string (@"\t<bottom_limit>$bottom_limit</bottom_limit>\n");-			-			os.put_string ("</lines>\n\n");--			foreach (string gv in grid_width) {-				os.put_string (@"<grid width=\"$(gv)\"/>\n");-			}-			-			if (GridTool.sizes.length () > 0) {-				os.put_string ("\n");-			}-			-			os.put_string (@"<background scale=\"$(background_scale)\" />\n");-			os.put_string ("\n");-			-			if (background_images.length () > 0) {-				os.put_string (@"<images>\n");-				-				foreach (string f in background_images) {-					os.put_string (@"\t<img src=\"$f\"/>\n");-				}-			-				os.put_string (@"</images>\n");-				os.put_string ("\n");-			}-			-			glyph_cache.for_each ((gc) => {-				bool selected;-				-				if (is_null (gc)) {-					warning ("No glyph collection");-				}-				-				try {-					foreach (Glyph g in gc.get_version_list ().glyphs) {-						selected = (g == gc.get_current ());-						write_glyph (g, selected, os);-					}-				} catch (GLib.Error ef) {-					stderr.printf (@"Failed to save $path \n");-					stderr.printf (@"$(ef.message) \n");-				}-			});-		-			glyph_cache.for_each ((gc) => {-				Glyph glyph;-				-				try {-					glyph = gc.get_current ();-					-					foreach (Kerning k in glyph.kerning) {-						string l, r;-						Glyph? gr = get_glyph (k.glyph_right);-						Glyph glyph_right;--						if (gr == null) {-							warning ("kerning a glyph that does not exist. (" + glyph.name + " -> " + k.glyph_right + ")");-							continue;-						}-						-						glyph_right = (!) gr;-						-						l = Font.to_hex_code (glyph.unichar_code);-						r = Font.to_hex_code (glyph_right.unichar_code);-										-						os.put_string (@"<hkern left=\"U+$l\" right=\"U+$r\" kerning=\"$(k.val)\"/>\n");-					}-				} catch (GLib.Error e) {-					warning (e.message);-				}-			});--			glyph_cache.for_each ((gc) => {-				GlyphBackgroundImage bg;-				-				try {-					string data;-					-					foreach (Glyph g in gc.get_version_list ().glyphs) {--						if (g.get_background_image () != null) {-							bg = (!) g.get_background_image ();-							data = bg.get_png_base64 ();-							-							if (!bg.is_valid ()) {-								continue;-							}-							-							os.put_string (@"<background-image sha1=\"");-							os.put_string (bg.get_sha1 ());-							os.put_string ("\" ");-							os.put_string (" data=\"");-							os.put_string (data);-							os.put_string ("");-							os.put_string ("\" />\n");	-						}-					}-				} catch (GLib.Error ef) {-					stderr.printf (@"Failed to save $path \n");-					stderr.printf (@"$(ef.message) \n");-				}-			});-						-			os.put_string ("</font>");-			-		} catch (GLib.Error e) {-			stderr.printf (@"Failed to save $path \n");-			stderr.printf (@"$(e.message) \n");-			return false;-		}-		-		return true;-	}--	private void write_glyph (Glyph g, bool selected, DataOutputStream os) throws GLib.Error {-		os.put_string (@"<glyph unicode=\"$(to_hex (g.unichar_code))\" selected=\"$selected\" left=\"$(g.left_limit)\" right=\"$(g.right_limit)\">\n");--		foreach (var p in g.path_list) {-			if (p.points.length () == 0) {-				continue;-			}-			-			os.put_string ("\t<object>");--			foreach (var ep in p.points) {-				os.put_string (@"<point x=\"$(ep.x)\" y=\"$(ep.y)\" ");-				-				if (ep.right_handle.type == PointType.CUBIC) {-					os.put_string (@"right_type=\"cubic\" ");-				}--				if (ep.left_handle.type == PointType.CUBIC) {-					os.put_string (@"left_type=\"cubic\" ");-				}--				if (ep.right_handle.type == PointType.QUADRATIC) {-					os.put_string (@"right_type=\"quadratic\" ");-				}--				if (ep.left_handle.type == PointType.QUADRATIC) {-					os.put_string (@"left_type=\"quadratic\" ");-				}--				if (ep.right_handle.type == PointType.LINE_CUBIC) {-					os.put_string (@"right_type=\"linear\" ");-				}--				if (ep.left_handle.type == PointType.LINE_CUBIC) {-					os.put_string (@"left_type=\"linear\" ");-				}-								-				if (ep.right_handle.type == PointType.CUBIC || ep.right_handle.type == PointType.QUADRATIC) {-					os.put_string (@"right_angle=\"$(ep.right_handle.angle)\" ");-					os.put_string (@"right_length=\"$(ep.right_handle.length)\" ");-				}-				-				if (ep.left_handle.type == PointType.CUBIC) {-					os.put_string (@"left_angle=\"$(ep.left_handle.angle)\" ");-					os.put_string (@"left_length=\"$(ep.left_handle.length)\" ");						-				}-				-				if (ep.right_handle.type == PointType.CUBIC || ep.left_handle.type == PointType.CUBIC) {-					os.put_string (@"tie_handles=\"$(ep.tie_handles)\" ");-				}-				-				os.put_string ("/>");-				-			}-			-			os.put_string ("</object>\n");-		}-		-		GlyphBackgroundImage? bg = g.get_background_image ();-		-		if (bg != null) {-			GlyphBackgroundImage background_image = (!) bg;--			double pos_x = background_image.img_x;-			double pos_y = background_image.img_y;-			-			double scale_x = background_image.img_scale_x;-			double scale_y = background_image.img_scale_y;-			-			double rotation = background_image.img_rotation;-			-			if (background_image.is_valid ()) {-				os.put_string (@"\t<background sha1=\"$(background_image.get_sha1 ())\" x=\"$pos_x\" y=\"$pos_y\" scale_x=\"$scale_x\" scale_y=\"$scale_y\" rotation=\"$rotation\"/>\n");-			}-		}-		-		os.put_string ("</glyph>\n\n"); --	}- 	public void set_font_file (string path) { 		font_file = path; 		modified = false;@@ -811,65 +564,72 @@ public class Font : GLib.Object { 	public bool load (string path, bool recent = true) { 		bool loaded = false; 		initialised = true;+		otf_font = false;++		while (grid_width.length () > 0) {+			grid_width.remove_link (grid_width.first ());+		}+	+		// empty cache and fill it with new glyphs from disk+		glyph_cache.remove_all ();+		glyph_name.remove_all ();+		ligature.remove_all (); 		-		try {-			otf_font = false;+		if (path.has_suffix (".svg")) {+			Toolbox.select_tool_by_name ("cubic_points");+			font_file = path;+			loaded = parse_svg_file (path);+			format = FontFormat.SVG;+		}+		+		if (path.has_suffix (".ffi")) {+			Toolbox.select_tool_by_name ("cubic_points");+			font_file = path;+			loaded = parse_bf_file (path);+			format = FontFormat.FFI;+		} -			while (grid_width.length () > 0) {-				grid_width.remove_link (grid_width.first ());-			}+		if (path.has_suffix (".bf")) {+			Toolbox.select_tool_by_name ("cubic_points");+			font_file = path;+			loaded = parse_bf_file (path);+			format = FontFormat.BIRDFONT;+		}+		+		if (path.has_suffix (".ttf")) {+			Toolbox.select_tool_by_name ("quadratic_points");+			font_file = path;+			loaded = parse_freetype_file (path);+			format = FontFormat.FREETYPE;+		}			 -			glyph_cache.remove_all ();-			glyph_name.remove_all ();-			ligature.remove_all ();-			-			if (path.has_suffix (".svg")) {-				Toolbox.select_tool_by_name ("cubic_points");-				font_file = path;-				loaded = parse_svg_file (path);-				format = FontFormat.SVG;-			}-			-			if (path.has_suffix (".ffi")) {-				Toolbox.select_tool_by_name ("cubic_points");-				font_file = path;-				loaded = parse_file (path);-				format = FontFormat.FFI;-			}-			+		if (path.has_suffix (".otf")) {+			Toolbox.select_tool_by_name ("cubic_points");+			font_file = path;+			loaded = parse_freetype_file (path);+			format = FontFormat.FREETYPE;+		}	+					+		/* // TODO: Remove the old way of loading ttfs when testing of the OTF writer is complete.+		if (BirdFont.experimental) { 			if (path.has_suffix (".ttf")) {-				Toolbox.select_tool_by_name ("quadratic_points");-				font_file = path;-				loaded = parse_freetype_file (path);-				format = FontFormat.FREETYPE;-			}			--			if (path.has_suffix (".otf")) {-				Toolbox.select_tool_by_name ("cubic_points"); 				font_file = path;-				loaded = parse_freetype_file (path);-				format = FontFormat.FREETYPE;-			}	-						-			/* // TODO: Remove the old way of loading ttfs when testing of the OTF writer is complete.-			if (BirdFont.experimental) {-				if (path.has_suffix (".ttf")) {-					font_file = path;-					loaded = parse_otf_file (path);-				}-			}*/-			-			if (recent) {-				add_thumbnail ();-				Preferences.add_recent_files (get_path ());+				loaded = parse_otf_file (path); 			}-		} catch (GLib.Error e) {-			warning (e.message);-			return false;-		}+		}*/ 		+		if (recent) {+			add_thumbnail ();+			Preferences.add_recent_files (get_path ());+		}+					 		return loaded; 	}+	+	private bool parse_bf_file (string path) {+		BirdFontFile font = new BirdFontFile (this);+		return font.load (path);+	}  	private bool parse_freetype_file (string path) { 		string svg;@@ -978,540 +738,6 @@ public class Font : GLib.Object { 		otf.parse_index (path); 		return true; 	}-	-	public bool parse_file (string path) throws GLib.Error {-		Parser.init ();-		-		Xml.Doc* doc;-		Xml.Node* root;-		Xml.Node* node;--		// set this path as file for this font, it will be updated if this is a backup-		font_file = path;-		-		// empty cache and fill it with new glyphs from disk-		glyph_cache.remove_all ();-		glyph_name.remove_all ();--		while (background_images.length () > 0) {-			background_images.remove_link (background_images.first ());-		}--		create_background_files (path);--		doc = Parser.parse_file (path);-		root = doc->get_root_element ();-		-		if (root == null) {-			delete doc;-			return false;-		}--		node = root;-		-		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {--			// this is a backup file set path to the original -			if (iter->name == "backup") {-				font_file = iter->children->content;-			}-	-			if (iter->name == "glyph") {-				parse_glyph (iter);-			}--			if (iter->name == "lines") {-				parse_font_boundries (iter);-			}--			if (iter->name == "grid") {-				parse_grid (iter);-			}--			if (iter->name == "background") {-				parse_background (iter);-			}-			-			if (iter->name == "images") {-				parse_background_selection (iter);-			}--			if (iter->name == "name" && iter->children != null) {-				set_name (iter->children->content);-			}--			if (iter->name == "hkern") {-				parse_kerning (iter);-			}--			if (iter->name == "ttf-export") {-				ttf_export = bool.parse (iter->children->content);-			}			--			if (iter->name == "svg-export") {-				svg_export = bool.parse (iter->children->content);-			}-			-		}-    -		delete doc;-		Parser.cleanup ();--		return true;-	}-	-	private void create_background_files (string path) {-		Xml.Doc* doc = Parser.parse_file (path);-		Xml.Node* root;-		Xml.Node* node;-		-		root = doc->get_root_element ();-		-		if (root == null) {-			delete doc;-			return;-		}--		node = root;-		-		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {-			-			if (iter->name == "name" && iter->children != null) {-				set_name (iter->children->content);-			}-			-			if (iter->name == "background-image") {-				parse_background_image (iter);-			}-		}-    -		delete doc;-		Parser.cleanup ();-	}-	-	private void parse_kerning (Xml.Node* node) {-		string attr_name;-		string attr_content;-		string left = "";-		string right = "";-		string kern = "";--		StringBuilder b;-		-		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {-			attr_name = prop->name;-			attr_content = prop->children->content;-			-			if (attr_name == "left") {-				b = new StringBuilder ();-				b.append_unichar (to_unichar (attr_content));-				left = @"$(b.str)";-			}--			if (attr_name == "right") {-				b = new StringBuilder ();-				b.append_unichar (to_unichar (attr_content));-				right = @"$(b.str)";-			}-			-			if (attr_name == "kerning") {-				kern = attr_content;-			}-		}-		-		set_kerning (left, right, double.parse (kern));-	}-	-	private void parse_background_image (Xml.Node* node) -		requires (node != null)-	{-		string attr_name;-		string attr_content;-		-		string file = "";-		string data = "";-		-		File img_dir;-		File img_file;-		FileOutputStream file_stream;-		DataOutputStream png_stream;-		-		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {-			return_if_fail (!is_null (prop->name));-			return_if_fail (!is_null (prop->children));-			return_if_fail (!is_null (prop->children->content));-			-			attr_name = prop->name;-			attr_content = prop->children->content;-			-			if (attr_name == "sha1") {-				file = attr_content;-			}-			-			if (attr_name == "data") {-				data = attr_content;-			}-		}-		-		img_dir = get_backgrounds_folder ().get_child ("parts");--		if (!img_dir.query_exists ()) {-			DirUtils.create ((!) img_dir.get_path (), 0xFFFFFF);-		}-	-		img_file = img_dir.get_child (@"$(file).png");-		-		if (img_file.query_exists ()) {-			return;-		}-		-		try {-			file_stream = img_file.create (FileCreateFlags.REPLACE_DESTINATION);-			png_stream = new DataOutputStream (file_stream);--			png_stream.write (Base64.decode (data));-			png_stream.close ();	-		} catch (GLib.Error e) {-			warning (e.message);-		}-	}-	-	private void parse_background (Xml.Node* node) -		requires (node != null)-	{-		string attr_name;-		string attr_content;-				-		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {-			-			return_if_fail (!is_null (prop->name));-			return_if_fail (!is_null (prop->children));-			return_if_fail (!is_null (prop->children->content));-			-			attr_name = prop->name;-			attr_content = prop->children->content;-			-			if (attr_name == "scale") {-				background_scale = attr_content;-			}-		}-	}-	-	private void parse_background_selection (Xml.Node* node) {-		string attr_name = "";-		string attr_content;-		-		return_if_fail (node != null);-				-		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {-			if (iter->name == "img") {-				for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {-					attr_name = prop->name;-					attr_content = prop->children->content;-					-					if (attr_name == "src") {-						background_images.append (attr_content);-					}-				}-			}-		}-	}-	-	private void parse_grid (Xml.Node* node) {-		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {-			string attr_name = prop->name;-			string attr_content = prop->children->content;-			-			if (attr_name == "width") {-				grid_width.append (attr_content);-			}-		}		-	}-	-	private void parse_font_boundries (Xml.Node* node) {-		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {-			if (iter->name == "top_limit") top_limit = parse_double_from_node (iter);-			if (iter->name == "top_position") top_position = parse_double_from_node (iter);-			if (iter->name == "x-height") xheight_position = parse_double_from_node (iter);-			if (iter->name == "base_line") base_line = parse_double_from_node (iter);-			if (iter->name == "bottom_position") bottom_position = parse_double_from_node (iter);-			if (iter->name == "bottom_limit") bottom_limit = parse_double_from_node (iter);-		}			-	}-	-	private double parse_double_from_node (Xml.Node* iter) {-		double d;-		bool r = double.try_parse (iter->children->content, out d);-		-		if (unlikely (!r)) {-			string? s = iter->content;-			if (s == null) warning (@"Content is null for node $(iter->name)\n");-			else warning (@"Failed to parse double for \"$(iter->content)\"\n");-		}-		-		return (r) ? d : 0;-	}-	-	/** Parse one glyph. */-	public void parse_glyph (Xml.Node* node) {-		string name = "";-		int left = 0;-		int right = 0;-		unichar uni = 0;-		int version = 0;-		bool selected = false;-		Glyph g;-		GlyphCollection? gc;-		-		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {-			string attr_name = prop->name;-			string attr_content = prop->children->content;-			StringBuilder b;-			-			if (attr_name == "unicode") {-				uni = to_unichar (attr_content);-				b = new StringBuilder ();-				b.append_unichar (uni);-				name = b.str;-			}--			if (attr_name == "left") {-				left = int.parse (attr_content);-			}-			-			if (attr_name == "right") {-				right = int.parse (attr_content);-			}-			-			if (attr_name == "version") {-				version = int.parse (attr_content);-			}-			-			if (attr_name == "selected") {-				selected = bool.parse (attr_content);-			}-		}--		g = new Glyph (name, uni);-		-		g.left_limit = left;-		g.right_limit = right;--		parse_content (g, node);-		-		// todo: use disk thread and idle add this:-		-		gc = get_glyph_collection (g.get_name ());-		-		if (g.get_name () == "") {-			warning ("No name set for glyph.");-		}-				-		if (gc == null) {-			gc = new GlyphCollection (g);-			add_glyph_collection ((!) gc);-		} else {-			((!)gc).insert_glyph (g, selected);-		}-	}-	-	/** Parse visual objects and paths */-	private void parse_content (Glyph g, Xml.Node* node) {-		Xml.Node* i;-		return_if_fail (node != null);-		-		for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {-			if (iter->name == "object") {-				Path p = new Path ();-				-				for (i = iter->children; i != null; i = i->next) {-					if (i->name == "point") {-						parse_point (p, i);-					}					-				}--				p.close ();-				g.add_path (p);-			}-			-			if (iter->name == "background") {-				parse_background_scale (g, iter);-			}-		}-	}-	-	private void parse_background_scale (Glyph g, Xml.Node* node) {-		GlyphBackgroundImage img;-		GlyphBackgroundImage? new_img = null;-		-		string attr_name = "";-		string attr_content;-		-		File img_file = get_backgrounds_folder ().get_child ("parts");-		-		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {-			attr_name = prop->name;-			attr_content = prop->children->content;-			-			if (attr_name == "sha1") {-				img_file = img_file.get_child (attr_content + ".png");--				if (!img_file.query_exists ()) {-					warning (@"Background file has not been created yet. $((!) img_file.get_path ())");-				}-				-				new_img = new GlyphBackgroundImage ((!) img_file.get_path ());-				g.set_background_image ((!) new_img);-			}-		}-		-		if (unlikely (new_img == null)) {-			warning (@"No source for image found for $attr_name in $(g.name)");-			return;-		}-	-		img = (!) new_img;-	-		for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {-			attr_name = prop->name;-			attr_content = prop->children->content;-							-			if (attr_name == "x") {-				img.img_x = double.parse (attr_content);-			}-			-			if (attr_name == "y") {-				img.img_y = double.parse (attr_content);-			}	--			if (attr_name == "scale_x") {-				img.img_scale_x = double.parse (attr_content);-			}--			if (attr_name == "scale_y") {-				img.img_scale_y = double.parse (attr_content);-			}-						-			if (attr_name == "rotation") {-				img.img_rotation = double.parse (attr_content);-			}-		}-		-		img.set_position(img.img_x, img.img_y);	-	}-	-	private void parse_point (Path p, Xml.Node* iter) {-		double x = 0;-		double y = 0;-		-		double angle_right = 0;-		double angle_left = 0;-		-		double length_right = 0;-		double length_left = 0;-		-		PointType type_right = PointType.LINE_CUBIC;-		PointType type_left = PointType.LINE_CUBIC;-		-		bool tie_handles = false;-		-		EditPoint ep;-		-		for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {-			string attr_name = prop->name;-			string attr_content = prop->children->content;-						-			if (attr_name == "x") x = double.parse (attr_content);-			if (attr_name == "y") y = double.parse (attr_content);--			if (attr_name == "right_type" && attr_content == "linear") {-				type_right = PointType.LINE_CUBIC;-			}	--			if (attr_name == "left_type" && attr_content == "linear") {-				type_left = PointType.LINE_CUBIC;-			}	--			if (attr_name == "right_type" && attr_content == "quadratic") {-				type_right = PointType.QUADRATIC;-			}	--			if (attr_name == "left_type" && attr_content == "quadratic") {-				type_left = PointType.QUADRATIC;-			}--			if (attr_name == "right_type" && attr_content == "cubic") {-				type_right = PointType.CUBIC;-			}	--			if (attr_name == "left_type" && attr_content == "cubic") {-				type_left = PointType.CUBIC;-			}-			-			if (attr_name == "right_angle") angle_right = double.parse (attr_content);-			if (attr_name == "right_length") length_right = double.parse (attr_content);-			if (attr_name == "left_angle") angle_left = double.parse (attr_content);-			if (attr_name == "left_length") length_left = double.parse (attr_content);-			-			if (attr_name == "tie_handles") tie_handles = bool.parse (attr_content);-		}-	-		// backward compabtility-		if (type_right == PointType.LINE_CUBIC && length_right != 0) {-			type_right = PointType.CUBIC;-		}--		if (type_left == PointType.LINE_CUBIC && length_left != 0) {-			type_left = PointType.CUBIC;-		}-		-		ep = new EditPoint (x, y);-		-		ep.right_handle.angle = angle_right;-		ep.right_handle.length = length_right;-		ep.right_handle.type = type_right;-		-		ep.left_handle.angle = angle_left;-		ep.left_handle.length = length_left;-		ep.left_handle.type = type_left;-		-		ep.tie_handles = tie_handles;-		-		p.add_point (ep);-	}-		-	public static string to_hex (unichar ch) {-		StringBuilder s = new StringBuilder ();-		s.append ("U+");-		s.append (to_hex_code (ch));-		return s.str;-	}--	public static string to_hex_code (unichar ch) {-		StringBuilder s = new StringBuilder ();-		-		uint8 a = (uint8)(ch & 0x00000F);-		uint8 b = (uint8)((ch & 0x0000F0) >> 4 * 1);-		uint8 c = (uint8)((ch & 0x000F00) >> 4 * 2);-		uint8 d = (uint8)((ch & 0x00F000) >> 4 * 3);-		uint8 e = (uint8)((ch & 0x0F0000) >> 4 * 4);-		uint8 f = (uint8)((ch & 0xF00000) >> 4 * 5);-		-		if (e != 0 || f != 0) {-			s.append (oct_to_hex (f));-			s.append (oct_to_hex (e));-		}-		-		if (c != 0 || d != 0) {-			s.append (oct_to_hex (d));-			s.append (oct_to_hex (c));-		}-				-		s.append (oct_to_hex (b));-		s.append (oct_to_hex (a));-		-		return s.str;-	}  	public static unichar to_unichar (string unicode) { 		int index = 2;@@ -1520,7 +746,7 @@ public class Font : GLib.Object { 		unichar rc = 0; 		bool r; -		if (unlikely (unicode.index_of ("U+") != 0)) {+		if (unlikely (!unicode.has_prefix ("U+"))) { 			warning (@"All unicode values must begin with U+ ($unicode)"); 			return '\0'; 		}@@ -1568,6 +794,38 @@ public class Font : GLib.Object { 		return (uint8) (o - '0'); 	} +	public static string to_hex (unichar ch) {+		StringBuilder s = new StringBuilder ();+		s.append ("U+");+		s.append (to_hex_code (ch));+		return s.str;+	}++	public static string to_hex_code (unichar ch) {+		StringBuilder s = new StringBuilder ();+		+		uint8 a = (uint8)(ch & 0x00000F);+		uint8 b = (uint8)((ch & 0x0000F0) >> 4 * 1);+		uint8 c = (uint8)((ch & 0x000F00) >> 4 * 2);+		uint8 d = (uint8)((ch & 0x00F000) >> 4 * 3);+		uint8 e = (uint8)((ch & 0x0F0000) >> 4 * 4);+		uint8 f = (uint8)((ch & 0xF00000) >> 4 * 5);+		+		if (e != 0 || f != 0) {+			s.append (oct_to_hex (f));+			s.append (oct_to_hex (e));+		}+		+		if (c != 0 || d != 0) {+			s.append (oct_to_hex (d));+			s.append (oct_to_hex (c));+		}+				+		s.append (oct_to_hex (b));+		s.append (oct_to_hex (a));+		+		return s.str;+	} }  }diff --git a/libbirdfont/GlyphTable.vala b/libbirdfont/GlyphTable.valaindex eb970e5..5bf150e 100644--- a/libbirdfont/GlyphTable.vala+++ b/libbirdfont/GlyphTable.vala@@ -18,7 +18,7 @@ namespace BirdFont {  /** A sorted table of glyphs with search index. */-class GlyphTable : GLib.Object {+public class GlyphTable : GLib.Object { 	List<Item> data = new List<Item> (); 	 	List<RowItem> row1 = new List<RowItem> ();diff --git a/libbirdfont/MenuTab.vala b/libbirdfont/MenuTab.valaindex 429870c..2540636 100644--- a/libbirdfont/MenuTab.vala+++ b/libbirdfont/MenuTab.vala@@ -263,7 +263,7 @@ c.append (""" 				} 				 				// ignore backup of the current font-				if (file_name == @"$(font.get_name ()).ffi") {+				if (file_name == @"$(font.get_name ()).bf") { 					continue; 				} 				@@ -383,8 +383,8 @@ c.append (""" 		if (fn != null) { 			f = (!) fn; 			-			if (!f.has_suffix (".ffi")) {-				f += ".ffi";+			if (!f.has_suffix (".bf")) {+				f += ".bf"; 			} 			 			font.font_file = f;@@ -408,7 +408,7 @@ c.append (""" 		 		fn = (!) f.font_file; 		-		if (f.font_file != null && fn.has_suffix (".ffi")) {+		if (f.font_file != null && fn.has_suffix (".bf")) { 			f.background_scale = MainWindow.get_toolbox ().background_scale.get_display_value (); 			 			while (f.grid_width.length () > 0) {diff --git a/libbirdfont/PenTool.vala b/libbirdfont/PenTool.valaindex c764dba..b1909ed 100644--- a/libbirdfont/PenTool.vala+++ b/libbirdfont/PenTool.vala@@ -1159,7 +1159,6 @@ public class PenTool : Tool { 		return true; 	} 	-	 	class Point { 		 		public int x;

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112