Clone in a terminal:
Commit 2960bf3
Johan Mattsson - 2012-08-15T21:53:37+00:00
2960bf3d6864f3a4c5f41447e3eb5698619a4032johan.mattsson.m@gmail.com

export quadratic bezier paths

Changed files (5)

Diff

diff --git a/src/Font.vala b/src/Font.valaindex 0f4ff31..aefd26d 100644--- a/src/Font.vala+++ b/src/Font.vala@@ -284,11 +284,11 @@ class Font : GLib.Object { 		Path p; 		Path i; 		-		if (has_glyph ("notdef")) {-			return (!) get_glyph ("notdef");+		if (has_glyph (".notdef")) {+			return (!) get_glyph (".notdef"); 		} 		-		g = new Glyph ("notdef", 0);+		g = new Glyph (".notdef", 0); 		p = new Path (); 		i = new Path (); 		diff --git a/src/OpenFontFormatWriter.vala b/src/OpenFontFormatWriter.valaindex 3d51343..ee47234 100644--- a/src/OpenFontFormatWriter.vala+++ b/src/OpenFontFormatWriter.vala@@ -19,6 +19,8 @@ using Math; 
 namespace Supplement {
 
+const double UNITS = 10;
+
 class OpenFontFormatWriter : Object  {
 
 	DataOutputStream os;
@@ -422,6 +424,7 @@ class FontData : Object { class Coordinate {
 	/** TTF coordinate flags. */
 
+	public static const uint8 NONE           = 0;
 	public static const uint8 ON_PATH        = 1 << 0;
 	public static const uint8 X_SHORT_VECTOR = 1 << 1;
 	public static const uint8 Y_SHORT_VECTOR = 1 << 2;
@@ -1330,8 +1333,13 @@ class GlyfTable : Table { 		end_point = 0;
 		last_end_point = 0;
 		foreach (Path p in g.path_list) {
+			p = p.get_quadratic_points ();
 			foreach (EditPoint e in p.points) {
 				end_point++;
+				
+				if (e.get_right_handle ().type == PointType.CURVE) {
+					end_point++;
+				}
 			}
 			fd.add_u16 (end_point - 1);
 			
@@ -1359,9 +1367,15 @@ class GlyfTable : Table { 		// flags
 		nflags = 0;
 		foreach (Path p in g.path_list) {
+			p = p.get_quadratic_points ();
 			foreach (EditPoint e in p.points) {
 				fd.add_byte (Coordinate.ON_PATH);
 				nflags++;
+				
+				if (e.get_right_handle ().type == PointType.CURVE) {
+					fd.add_byte (Coordinate.NONE);
+					nflags++;
+				}
 			}
 		}
 		
@@ -1373,30 +1387,53 @@ class GlyfTable : Table { 		// x coordinates
 		double prev = 0;
 		foreach (Path p in g.path_list) {
+			p = p.get_quadratic_points ();
 			foreach (EditPoint e in p.points) {
-				x = e.x - prev - g.left_limit;
+				x = e.x * UNITS - prev + g.left_limit * UNITS;
 				
 				fd.add_16 ((int16) x);
 				
 				if (x + prev < txmin) txmin = (int16) (x + prev);
 				if (x + prev > txmax) txmax = (int16) (x + prev);
 				
-				prev = e.x - g.left_limit;
+				prev = e.x * UNITS + g.left_limit * UNITS;
+				
+				if (e.get_right_handle ().type == PointType.CURVE) {
+					x = e.get_right_handle ().x () * UNITS - prev + g.left_limit  * UNITS;
+
+					fd.add_16 ((int16) x);
+					
+					if (x + prev < txmin) txmin = (int16) (x + prev);
+					if (x + prev > txmax) txmax = (int16) (x + prev);
+					
+					prev = e.get_right_handle ().x () * UNITS + g.left_limit  * UNITS;
+				}
 			}
 		}
 
-		// x coordinates
+		// y coordinates
 		prev = 0;
 		foreach (Path p in g.path_list) {
+			p = p.get_quadratic_points ();
 			foreach (EditPoint e in p.points) {
-				y =  e.y - prev + font.base_line;
-				
+				y = e.y * UNITS - prev + font.base_line  * UNITS;
 				fd.add_16 ((int16) y);
 
 				if (y + prev < tymin) tymin = (int16) (y + prev);
 				if (y + prev > tymax) tymax = (int16) (y + prev);
 
-				prev = e.y + font.base_line;
+				prev = e.y * UNITS + font.base_line * UNITS;
+				
+				if (e.get_right_handle ().type == PointType.CURVE) {
+					y = e.get_right_handle ().y () * UNITS - prev + font.base_line * UNITS;
+					
+					fd.add_16 ((int16) y);
+					
+					if (y + prev < tymin) tymin = (int16) (y + prev);
+					if (y + prev > tymax) tymax = (int16) (y + prev);
+					
+					prev = e.get_right_handle ().y () * UNITS + font.base_line  * UNITS;
+				}
 			}
 		}
 		
@@ -1442,17 +1479,27 @@ class GlyfTable : Table { 		//glyphs.append (font.get_null_character ());
 		//glyphs.append (font.get_nonmarking_return ());
 		//glyphs.append (font.get_space ());
-			
+		
+		List<Glyph> unassigned_glyphs = new List<Glyph> ();
+		
 		// add glyphs, first all assigned then the unassigned ones
 		for (indice = 0; (gl = font.get_glyph_indice (indice)) != null; indice++) {		
 			g = (!) gl;
 			
-			if (g.name == "notdef" || g.name == "null"  || g.name == "nonmarkingreturn" || g.name == "space" || g.name == " ") {
+			if (g.name == ".notdef" || g.unichar_code == 0) {
 				continue;
 			}
 			
-			glyphs.append (g);
-		}	
+			if (!g.is_unassigned ()) {
+				glyphs.append (g);
+			} else {
+				unassigned_glyphs.append (g);
+			}
+		}
+		
+		foreach (Glyph ug in unassigned_glyphs) {
+			glyphs.append (ug);
+		}
 		
 	}
 
@@ -1696,17 +1743,8 @@ class CmapSubtableWindowsUnicode : CmapSubtable { 		uint16 gid_length = 0;
 		
 		uint32 indice;
-		uint32 first_assigned = 0;
-
-		foreach (Glyph g in glyf_table.glyphs) {
-			if (!g.is_unassigned ()) {
-				first_assigned++;
-			} else {
-				break;
-			}
-		}
-		printd (@"first_assigned $first_assigned\n");
-		first_assigned = 1;
+		uint32 first_assigned = 1;
+		
 		foreach (Glyph g in glyf_table.glyphs) {
 			if (!g.is_unassigned ()) {
 				glyph_range.add_single (g.unichar_code);
@@ -2082,7 +2120,7 @@ class HeadTable : Table { 		// font_data.add_u16 (BASELINE_AT_ZERO | LSB_AT_ZERO);
 		font_data.add_u16 (0); // flags
 		
-		font_data.add_u16 (100); // units per em (should be a power of two for ttf fonts)
+		font_data.add_u16 (1000); // units per em (should be a power of two for ttf fonts)
 		
 		font_data.add_64 (0); // creation time since 1904-01-01
 		font_data.add_64 (0); // modified time since 1904-01-01
@@ -2183,10 +2221,9 @@ class HheaTable : Table { 		Fixed version = 1 << 16;
 		
 		fd.add_fixed (version); // table version
-				
-		// TODO: units per em
-		fd.add_16 ((int16) (-1 * (font.top_position - font.base_line))); // Ascender
-		fd.add_16 ((int16) (-1 * font.bottom_position)); // Descender
+		
+		fd.add_16 ((int16) (-1 * (font.top_position - font.base_line) * UNITS)); // Ascender
+		fd.add_16 ((int16) (-1 * font.bottom_position * UNITS)); // Descender
 		fd.add_16 (0); // LineGap
 				
 		fd.add_u16 (hmtx_table.max_advance); // maximum advance width value in 'hmtx' table.
@@ -2309,9 +2346,9 @@ class HmtxTable : Table { 		foreach (Glyph g in glyf_table.glyphs) {
 			g.boundries (out xmin, out ymin, out xmax, out ymax);
 
-			lsb = (int16) (xmin - g.left_limit + 0.5);
-			advance = (int16) (g.right_limit - g.left_limit + 0.5);
-			extent = (int16) (lsb + (xmax - xmin) + 0.5);
+			lsb = (int16) ((xmin - g.left_limit) * UNITS + 0.5);
+			advance = (int16) (g.right_limit * UNITS  - g.left_limit * UNITS  + 0.5);
+			extent = (int16) (lsb + (xmax * UNITS  - xmin * UNITS ) + 0.5);
 			rsb = (int16) (advance - extent);
 
 			printd (@"$(g.name) advance: $advance  = $((int16) (g.right_limit))  -  $((int16) (g.left_limit))\n");
diff --git a/src/Path.vala b/src/Path.valaindex 99ddbc2..d3f9066 100644--- a/src/Path.vala+++ b/src/Path.vala@@ -62,6 +62,8 @@ class Path { 	private static ImageSurface? edit_point_handle_image = null; 	private static ImageSurface? active_edit_point_handle_image = null; 	+	Path quadratic_path; // quadratic points for ttf export+	 	public Path () { 		if (edit_point_image == null) { 			edit_point_image = Icons.get_icon ("edit_point.png");@@ -1029,13 +1031,163 @@ class Path { 		return true; 	} +	/** Convert quadratic bezier points to cubic representation of the glyph+	 * suitable for ttf-export.+	 */ +	public Path get_quadratic_points () {+		EditPoint middle = new EditPoint ();+		unowned List<EditPoint> e;+		int i = 0;+		int j = 0;+		+		quadratic_path = copy ();+		+		if (quadratic_path.points.length () == 0) {+			return quadratic_path;+		}+	+		// split all curves in as many regions as we need+		while (split_all_cubic_in_half (quadratic_path)) {+			j++;+			+			if (j > 5) {+				warning ("too many iterations in split path");+				break;+			}+		}+		+		// estimate quatdratic form+		middle.prev = quadratic_path.points.last ();+		middle.next = quadratic_path.points.first ();++		if (((!)middle.prev).data.get_left_handle ().type == PointType.CURVE) {+			convert_to_quadratic (quadratic_path, middle);	+		}	+		+		e = (!) quadratic_path.points.first ();+		while (e != quadratic_path.points.last ()) {+			middle.prev = e;+			middle.next = e.next;+							+			if (e.data.get_left_handle ().type == PointType.CURVE) {+				convert_to_quadratic (quadratic_path, middle);	+			}+			+			e = (!) e.next;+		}+		+		return quadratic_path;+	}++	void convert_to_quadratic (Path cubic_path, EditPoint middle) {+		double curve_x, curve_y, distance;	+		EditPointHandle eh;+		+		EditPoint start = middle.get_prev ().data;+		EditPoint stop = middle.get_next ().data;++		estimate_quadratic (start, stop, out curve_x, out curve_y);+		+		eh = start.get_left_handle ();+		eh.set_point_type (PointType.NONE);+		eh.length = 0;+		+		eh = start.get_right_handle ();+		eh.set_point_type (PointType.CURVE);+		eh.move_to_coordinate (curve_x, curve_y);+	}++	public bool split_all_cubic_in_half (Path cubic_path) {+		EditPoint middle = new EditPoint ();+		unowned List<EditPoint> e;+		bool need_split = false;+		uint len = cubic_path.points.length ();++		middle.prev = cubic_path.points.last ();+		middle.next = cubic_path.points.first ();+		+		if (split_cubic_in_half (cubic_path, middle)) {+			need_split = true;+		}+				+		e = (!) cubic_path.points.first ();+		for (uint i = 0; i < len - 1; i++) {+			middle.prev = e;+			middle.next = e.next;+			+			if (e.data.get_left_handle ().type == PointType.CURVE +			    && split_cubic_in_half (cubic_path, middle)) {+				e = (!) e.next.next;+				need_split = true;+			} else {+				e = (!) e.next;+			}+		}+				+		return need_split;	+	}++	public void estimate_quadratic (EditPoint start, EditPoint stop, out double curve_x, out double curve_y) {+		curve_x = -0.25 * start.x + 0.75 *  stop.get_left_handle ().x () + 0.75 * start.get_right_handle ().x () - 0.25 * stop.x;+		curve_y = -0.25 * start.y + 0.75 *  stop.get_left_handle ().y () + 0.75 * start.get_right_handle ().y () - 0.25 * stop.y;		+	}++	bool split_cubic_in_half (Path cubic_path, EditPoint middle) {+		double curve_x, curve_y, cx, cy, qx, qy, nx, ny, distance;	+		+		EditPoint start = middle.get_prev ().data;+		EditPoint stop = middle.get_next ().data;+		+		estimate_quadratic (start, stop, out curve_x, out curve_y);+	+		qx = quadratic_bezier_path (0.75, start.x, curve_x, stop.x);+		qy = quadratic_bezier_path (0.75, start.y, curve_y, stop.y);++		cx = bezier_path (0.75, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);+		cy = bezier_path (0.75, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);++		distance = Math.sqrt (Math.pow (cx - qx, 2) + Math.pow (cy - qy, 2));++		nx = bezier_path (0.5, start.x, start.get_right_handle ().x (), stop.get_left_handle ().x (), stop.x);+		ny = bezier_path (0.5, start.y, start.get_right_handle ().y (), stop.get_left_handle ().y (), stop.y);++		if (Math.fabs (distance) > 0.1) {+			EditPoint new_edit_point = new EditPoint (nx, ny, PointType.CURVE);++			new_edit_point.next = middle.get_next ();+			new_edit_point.prev = middle.get_prev ();+			cubic_path.insert_new_point_on_path (new_edit_point);+			cubic_path.create_list ();++			return true;	+		}+		+		return false;+	}++	double distance_to_path (EditPoint start, EditPoint stop, double x, double y) {+		double min = double.MAX;+		+		all_of (start, stop, (cx, cy, t) => {+			double n = pow (x - cx, 2) + pow (y - cy, 2);+			+			if (n < min) {+				min = n;+			}+			+			return true;+		});+		+		return min;+	}+ 	public void insert_new_point_on_path (EditPoint ep) { 		EditPoint start, stop; 		double x0, x1, y0, y1; 		double px, py; 		 		double position, t, d, min;-		double steps = 100;+		double steps = 200;  		start = ep.get_prev ().data; 		stop = ep.get_next ().data;@@ -1123,24 +1275,24 @@ class Path { 			} 			 			all_of (prev.data, i.data, (cx, cy, t) => {-					n = pow (x - cx, 2) + pow (y - cy, 2);+				n = pow (x - cx, 2) + pow (y - cy, 2);+				+				if (n < min) {+					min = n; 					-					if (n < min) {-						min = n;-						-						ox = cx;-						oy = cy;+					ox = cx;+					oy = cy;+				+					previous_point = prev;+					next_point = i; 					-						previous_point = prev;-						next_point = i;-						-						step = t;-						-						g = true;-					}+					step = t; 					-					return true;-				});+					g = true;+				}+				+				return true;+			}); 		}  		if (previous_point == null) {@@ -1245,7 +1397,14 @@ class Path { 		a0 = step * (q1 - q0) + q0; 		a1 = step * (q2 - q1) + q1; 	}-	++	public static double quadratic_bezier_path (double step, double p0, double p1, double p2) {+		double q0 = step * (p1 - p0) + p0;+		double q1 = step * (p2 - p1) + p1;+		+		return step * (q1 - q0) + q0;+	}+			 	public void plot (Context cr, Allocation allocation, double view_zoom) { 			double px = 0, py = 0; 			double xc = allocation.width / 2.0;diff --git a/src/TestCases.vala b/src/TestCases.valaindex de4f455..e9420ab 100644--- a/src/TestCases.vala+++ b/src/TestCases.vala@@ -37,6 +37,48 @@ class TestCases { 		add (test_delete_points, "Delete edit points"); 		add (test_view_result, "View result in web browser"); 		add (test_save_backup, "Save backup");+		add (test_convert_to_quadratic_bezier_path, "Convert to quadratic path");+	}++	public static void test_convert_to_quadratic_bezier_path () {+		Tool.test_open_next_glyph ();+		Glyph g = MainWindow.get_current_glyph ();+		Path p = new Path ();+		Path p1 = new Path ();+		+		EditPoint e0, e1, e2, e3;+		+		p.add (-10, 10);+		p.add (10, 10);+		p.add (10, -10);+		p.add (-10, -10);+		p.close ();+		g.add_path (p);+		g.add_path (p1.get_quadratic_points ());++		e0 = new EditPoint (20, 40);+		e1 = new EditPoint (40, 40);+		e2 = new EditPoint (40, 20);+		e3 = new EditPoint (20, 20);++		p1.add_point (e0);+		p1.add_point (e1);+		p1.add_point (e2);+		p1.add_point (e3);+		p1.close ();++		e0.set_tie_handle (true);+		e1.set_tie_handle (true);+		e2.set_tie_handle (true);+		e3.set_tie_handle (true);++		e0.process_tied_handle ();+		e1.process_tied_handle ();+		e2.process_tied_handle ();+		e3.process_tied_handle ();++		g.add_path (p1);+		g.add_path (p1.get_quadratic_points ()); 	}  	public static void test_overview () {diff --git a/src/Tool.vala b/src/Tool.valaindex 5dcc07a..a2d1291 100644--- a/src/Tool.vala+++ b/src/Tool.vala@@ -235,49 +235,49 @@ class Tool : GLib.Object { 	 	/** Help function to test button press actions. */ 	public void test_click_action (int b, int x, int y) {-		this.yield ();+		Tool.yield (); 		press_action (this, b, x, y); 		-		this.yield ();+		Tool.yield (); 		release_action (this, b, x, y); 	}  	/** Help function to test select action for this tool. */ 	public void test_select_action () {-		this.yield ();+		Tool.yield (); 		MainWindow.get_toolbox ().select_tool (this); 	}  	public void test_move_action (int x, int y) {-		this.yield ();+		Tool.yield (); 		move_action (this, x, y); 	}  	public void test_press_action (int b, int x, int y) {-		this.yield ();+		Tool.yield (); 		press_action (this, b, x, y); 	}  	public void test_release_action (int b, int x, int y) {-		this.yield ();+		Tool.yield (); 		release_action (this, b, x, y); 	} -	public void test_open_next_glyph () {-		this.yield ();+	public static void test_open_next_glyph () {+		Tool.yield (); 		OverView o = MainWindow.get_overview (); 		 		MainWindow.get_tab_bar ().select_overview (); 		 		o.select_next_glyph ();-		this.yield ();+		Tool.yield (); 		 		o.open_current_glyph ();-		this.yield ();+		Tool.yield (); 	} 	 	/** Run pending events in main loop before continue. */-	public void @yield () {+	public static void @yield () { 		int t = 0; 		var time = new TimeoutSource(500); 		bool timeout;@@ -313,7 +313,6 @@ class Tool : GLib.Object { 			} 						 			if (unlikely (t > 100000)) {-				warning (@"\nToo many interations in test suite main loop for $(get_name ())\n"); 				assert (false); 				break; 			}

Contact

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