Clone in a terminal:
Commit d2940ce
Johan Mattsson - 2014-07-02T15:40:39+00:00
d2940ce33570c078402f8a31dbad4e300bdaaff3johan.mattsson.m@gmail.com

Parse SVG arc instruction This commit makes it possible to import round corners from SVG editors.

Changed files (3)

Diff

diff --git a/libbirdfont/BezierPoints.vala b/libbirdfont/BezierPoints.valanew file mode 100644index 0000000..aa72d56--- /dev/null+++ b/libbirdfont/BezierPoints.vala@@ -0,0 +1,33 @@+/*+    Copyright (C) 2014 Johan Mattsson++    This library is free software; you can redistribute it and/or modify +    it under the terms of the GNU Lesser General Public License as +    published by the Free Software Foundation; either version 3 of the +    License, or (at your option) any later version.++    This library 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 +    Lesser General Public License for more details.+*/++namespace BirdFont {++/** Bezier point container for the SVG parser. */+public class BezierPoints {+	public unichar type = '\0';+	public unichar svg_type = '\0';+	public double x0  = 0;+	public double y0 = 0;+	public double x1 = 0;+	public double y1 = 0;+	public double x2 = 0;+	public double y2 = 0;+	+	public string to_string () {+		return @"$((!)type.to_string ()) $x0,$y0 $x1,$y1 $x2,$y2 SVG:$((!)svg_type.to_string ())";+	}+}++}diff --git a/libbirdfont/SvgArc.vala b/libbirdfont/SvgArc.valanew file mode 100644index 0000000..b9ec170--- /dev/null+++ b/libbirdfont/SvgArc.vala@@ -0,0 +1,177 @@+/*+ * BirdFont code from SVG Salamander+ * + * Copyright (c) 2004, Mark McKay+ * Copyright (c) 2014, Johan Mattsson+ * All rights reserved.+ *+ * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following+ * conditions are met:+ *+ *   - Redistributions of source code must retain the above + *     copyright notice, this list of conditions and the following+ *     disclaimer.+ *   - Redistributions in binary form must reproduce the above+ *     copyright notice, this list of conditions and the following+ *     disclaimer in the documentation and/or other materials + *     provided with the distribution.+ *+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE+ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED+ * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Mark McKay can be contacted at mark@kitfox.com.  Salamander and other+ * projects can be found at http://www.kitfox.com+ *+ * Created on January 26, 2004, 8:40 PM+ * Adapded to BirdFont on Juli 2, 2014, 5:01 PM+ */++using Math;++namespace BirdFont {++/** Convert an SVG arc instruction to a Beziér path. */+static void add_arc_points (BezierPoints[] bezier_points, ref int bi, double x0, double y0, double rx, double ry, double angle, bool largeArcFlag, bool sweepFlag, double x, double y) {++	//+	// Elliptical arc implementation based on the SVG specification notes+	//++	double dx2, dy2, cosAngle, sinAngle;+	double x1, y1, Prx, Pry, Px1, Py1, radiiCheck;+	double sign, sq, coef, cx1, cy1;+	double sx2, sy2, cx, cy;+	double ux, uy, vx, vy, p, n;+	double angleStart, angleExtent;+	double s, step, theta;+	+	// Compute the half distance between the current and the final point+	dx2 = (x0 - x) / 2.0;+	dy2 = (y0 - y) / 2.0;+	+	// Convert angle from degrees to radians+	angle = 2 * PI * ((angle % 360.0) / 360.0);+	+	cosAngle = cos (angle);+	sinAngle = sin (angle);++	//+	// Step 1 : Compute (x1, y1)+	//+	x1 = cosAngle * dx2 + sinAngle * dy2;+	y1 = -sinAngle * dx2 + cosAngle * dy2;+	+	// Ensure radii are large enough+	rx = fabs(rx);+	ry = fabs(ry);+	Prx = rx * rx;+	Pry = ry * ry;+	Px1 = x1 * x1;+	Py1 = y1 * y1;+	+	// Check that radii are large enough+	radiiCheck = Px1 / Prx + Py1 / Pry;+	if (radiiCheck > 1) {+		rx = sqrt (radiiCheck) * rx;+		ry = sqrt (radiiCheck) * ry;+		Prx = rx * rx;+		Pry = ry * ry;+	}++	//+	// Step 2 : Compute (cx1, cy1)+	//+	sign = (largeArcFlag == sweepFlag) ? -1 : 1;+	sq = ((Prx*Pry)-(Prx*Py1)-(Pry*Px1)) / ((Prx*Py1)+(Pry*Px1));+	sq = (sq < 0) ? 0 : sq;+	coef = (sign * Math.sqrt(sq));+	cx1 = coef * ((rx * y1) / ry);+	cy1 = coef * -((ry * x1) / rx);++	//+	// Step 3 : Compute (cx, cy) from (cx1, cy1)+	//+	+	sx2 = (x0 + x) / 2.0;+	sy2 = (y0 + y) / 2.0;+	cx = sx2 - (cosAngle * cx1 - sinAngle * cy1);+	cy = sy2 - (sinAngle * cx1 + cosAngle * cy1);++	//+	// Step 4 : Compute the angleStart (angle1) and the angleExtent (dangle)+	//+	+	ux = (x1 - cx1) / rx;+	uy = (y1 - cy1) / ry;+	vx = (-x1 - cx1) / rx;+	vy = (-y1 - cy1) / ry;++	// Compute the angle start+	n = sqrt((ux * ux) + (uy * uy));+	p = ux; // (1 * ux) + (0 * uy)+	sign = (uy < 0) ? -1d : 1d;+	angleStart = sign * acos(p / n);++	// Compute the angle extent+	n = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));+	p = ux * vx + uy * vy;+	sign = (ux * vy - uy * vx < 0) ? -1d : 1d;+	angleExtent = sign * Math.acos(p / n);+	+	if(!sweepFlag && angleExtent > 0) {+		angleExtent -= 2 *PI;+	} else if (sweepFlag && angleExtent < 0) {+		angleExtent += 2 *PI;+	}+	angleExtent %= 2 * PI;+	angleStart %= 2 * PI;++	angleExtent *= -1;+	angleStart *= -1;+	+	// Approximate the path with Beziér points+	s = (angleExtent > 0) ? 1 : -1;+	step = fabs (angleExtent) / (3 * fabs (angleExtent));++	theta = PI - angleStart - angleExtent;+	+	bezier_points[bi].type = 'L';+	bezier_points[bi].svg_type = 'a';++	bezier_points[bi].x0 = cx + rx * cos (theta);+	bezier_points[bi].y0 = cy + ry * sin (theta);+									+	bi++;+					+	for (double a = 0; a < fabs (angleExtent); a += step) {+		theta = PI - angleStart - angleExtent + s * a;++		bezier_points[bi].type = 'S';+		bezier_points[bi].svg_type = 'a';++		bezier_points[bi].x0 = cx + rx * cos (theta);+		bezier_points[bi].y0 = cy + ry * sin (theta);++		bezier_points[bi].x1 = cx + rx * cos (theta + 1 * step / 3);+		bezier_points[bi].y1 = cy + ry * sin (theta + 1 * step / 3);+		+		bezier_points[bi].x2 = cx + rx * cos (theta + 2 * step / 3);+		bezier_points[bi].y2 = cy + ry * sin (theta + 2 * step / 3);+						+		bi++;+	}+}++}+    diff --git a/libbirdfont/SvgParser.vala b/libbirdfont/SvgParser.valaindex 175b72d..a96da11 100644--- a/libbirdfont/SvgParser.vala+++ b/libbirdfont/SvgParser.vala@@ -207,6 +207,8 @@ public class SvgParser { 		string data = d; 		 		data = data.replace (",", " ");+		data = data.replace ("a", " a ");+		data = data.replace ("A", " A "); 		data = data.replace ("m", " m "); 		data = data.replace ("M", " M "); 		data = data.replace ("h", " h ");@@ -267,6 +269,11 @@ public class SvgParser { 		PathList path_list = new PathList (); 		BezierPoints[] bezier_points; 		string[] c;+		double arc_rx, arc_ry;+		double arc_rotation;+		int large_arc;+		int arc_sweep;+		double arc_dest_x, arc_dest_y; 		 		if (d.index_of ("z") == -1) { // ignore all open paths 			return path_list;@@ -276,7 +283,7 @@ public class SvgParser { 		 		data = add_separators (d); 		c = data.split (" ");-		bezier_points = new BezierPoints[2 * c.length + 1];+		bezier_points = new BezierPoints[8 * c.length + 1]; // the arc instruction can use up to eight points 		 		for (int i = 0; i < 2 * c.length + 1; i++) { 			bezier_points[i] = new BezierPoints ();@@ -351,7 +358,7 @@ public class SvgParser { 					if (svg_glyph) { 						py = py + parse_double (c[++i]); 					} else {-						py = py + -parse_double (c[++i]);+						py = py - parse_double (c[++i]); 					} 					 					bezier_points[bi].x0 = px;@@ -383,7 +390,7 @@ public class SvgParser { 					if (svg_glyph) { 						cy = py + parse_double (c[++i]); 					} else {-						cy = py + -parse_double (c[++i]);+						cy = py - parse_double (c[++i]); 					} 					 					px = cx;@@ -423,7 +430,7 @@ public class SvgParser { 					if (svg_glyph) { 						cy = py + parse_double (c[++i]); 					} else {-						cy = py + -parse_double (c[++i]);+						cy = py - parse_double (c[++i]); 					} 					 					bezier_points[bi].x0 = cx;@@ -434,7 +441,7 @@ public class SvgParser { 					if (svg_glyph) { 						cy = py + parse_double (c[++i]); 					} else {-						cy = py + -parse_double (c[++i]);+						cy = py - parse_double (c[++i]); 					} 					 					px2 = cx;@@ -515,7 +522,7 @@ public class SvgParser { 					if (svg_glyph) { 						cy = py + parse_double (c[++i]); 					} else {-						cy = py + -parse_double (c[++i]);+						cy = py - parse_double (c[++i]); 					} 					 					bezier_points[bi].x0 = cx;@@ -529,7 +536,7 @@ public class SvgParser { 					if (svg_glyph) { 						cy = py + parse_double (c[++i]); 					} else {-						cy = py + -parse_double (c[++i]);+						cy = py - parse_double (c[++i]); 					} 					 					bezier_points[bi].x1 = cx;@@ -596,7 +603,7 @@ public class SvgParser { 					if (svg_glyph) { 						cy = py + parse_double (c[++i]); 					} else {-						cy = py + -parse_double (c[++i]);+						cy = py - parse_double (c[++i]); 					} 					 					px = cx;@@ -655,7 +662,7 @@ public class SvgParser { 					if (svg_glyph) { 						cy = py + parse_double (c[++i]); 					} else {-						cy = py + -parse_double (c[++i]);+						cy = py - parse_double (c[++i]); 					} 					 					px2 = cx;@@ -669,7 +676,7 @@ public class SvgParser { 					if (svg_glyph) { 						cy = py + parse_double (c[++i]); 					} else {-						cy = py + -parse_double (c[++i]);+						cy = py - parse_double (c[++i]); 					} 					 					bezier_points[bi].x2 = cx;@@ -723,6 +730,60 @@ public class SvgParser { 					 					bi++;				 				}+			} else if (c[i] == "a") {+				while (is_point (c[i + 1])) {					+					arc_rx = parse_double (c[++i]);+					arc_ry = parse_double (c[++i]);+					+					arc_rotation = parse_double (c[++i]);+					large_arc = parse_int (c[++i]);+					arc_sweep = parse_int (c[++i]);+							+					cx = px + parse_double (c[++i]);+					+					if (svg_glyph) {+						cy = py + parse_double (c[++i]);+					} else {+						cy = py - parse_double (c[++i]);+					}+					+					arc_dest_x = cx;+					arc_dest_y = cy;+					+					add_arc_points (bezier_points, ref bi, px, py, arc_rx, arc_ry, arc_rotation, large_arc == 1, arc_sweep == 1, cx, cy);+					+					px = cx;+					py = cy;+					+					+				}+			} else if (c[i] == "A") {+				while (is_point (c[i + 1])) {					+					arc_rx = parse_double (c[++i]);+					arc_ry = parse_double (c[++i]);+					+					arc_rotation = parse_double (c[++i]);+					large_arc = parse_int (c[++i]);+					arc_sweep = parse_int (c[++i]);+							+					cx = parse_double (c[++i]);+					+					if (svg_glyph) {+						cy = parse_double (c[++i]);+					} else {+						cy = -parse_double (c[++i]);+					}+					+					arc_dest_x = cx;+					arc_dest_y = cy;+					+					add_arc_points (bezier_points, ref bi, px, py, arc_rx, arc_ry, arc_rotation, large_arc == 1, arc_sweep == 1, cx, cy);++					px = cx;+					py = cy;+					+					+				} 			} else if (c[i] == "z") { 				bezier_points[bi].type = 'z'; 				bezier_points[bi].svg_type = 'z';@@ -811,7 +872,12 @@ public class SvgParser { 						left_x = b[i - 1].x1; 						left_y = b[i - 1].y1; 						last_type = PointType.CUBIC;-					} else if (b[i - 1].type == 'L' || last.type == 'M') {+					} else if (b[i - 1].type == 'S') {+						return_if_fail (i >= 1);+						left_x = b[i - 1].x1;+						left_y = b[i - 1].y1;+						last_type = PointType.CUBIC;+					}else if (b[i - 1].type == 'L' || last.type == 'M') { 						return_if_fail (i >= 2); // FIXME: -2 can be C or L 						left_x = b[i - 2].x0 + (b[i - 1].x0 - b[i - 2].x0) / 3.0; 						left_y = b[i - 2].y0 + (b[i - 1].y0 - b[i - 2].y0) / 3.0;@@ -837,7 +903,9 @@ public class SvgParser { 		Path path; 		PathList path_list = new PathList (); 		EditPoint ep = new EditPoint ();-		+		Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> ();+		EditPoint next, prev;+				 		path = new Path (); 		 		if (num_b == 0) {@@ -884,12 +952,12 @@ public class SvgParser { 					ep.get_left_handle ().set_point_type (last_type); 					ep.get_left_handle ().move_to_coordinate (last_x, last_y); 				} else {-					if (b[i - 1].type == 'C') {+					if (b[i - 1].type == 'C' || b[i - 1].type == 'S') { 						ep.get_left_handle ().set_point_type (PointType.CUBIC); 						ep.get_left_handle ().move_to_coordinate (b[i + 1].x1, b[i + 1].y1); 					}  					-					if (b[i + 1].type == 'C') {+					if (b[i + 1].type == 'C' || b[i - 1].type == 'S') { 						ep.get_right_handle ().set_point_type (PointType.CUBIC); 						ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0); 					} else if (b[i + 1].type == 'L' || b[i + 1].type == 'M') {@@ -931,7 +999,7 @@ public class SvgParser { 				} 			} 	-			if (b[i].type == 'C') {+			if (b[i].type == 'C' || b[i].type == 'S') { 				return_val_if_fail (i != 0, path_list);  				ep.set_point_type (PointType.CUBIC);@@ -939,6 +1007,10 @@ public class SvgParser { 				ep.get_right_handle ().set_point_type (PointType.CUBIC); 				ep.get_right_handle ().move_to_coordinate (b[i].x0, b[i].y0); 				+				if (b[i].type == 'S') {+					smooth_points.add (ep);+				}+				 				if (b[i + 1].type != 'z') { 					ep = path.add (b[i].x2, b[i].y2); @@ -947,6 +1019,25 @@ public class SvgParser { 				} 			} 		}++		foreach (EditPoint e in smooth_points) {+			e.set_tie_handle (true);+			e.process_tied_handle ();+			+			e.set_point_type (PointType.CUBIC);+			e.get_right_handle ().set_point_type (PointType.CUBIC);+			e.get_left_handle ().set_point_type (PointType.CUBIC);+			+			if (e.next != null) {+				next = e.get_next ();+				next.get_left_handle ().angle = next.get_right_handle ().angle - PI;+			}++			if (e.prev != null) {+				prev = e.get_prev ();+				prev.get_right_handle ().angle = prev.get_left_handle ().angle - PI;+			}+		} 		 		if (path.points.size > 0) { 			path_list.add (path);@@ -961,7 +1052,9 @@ public class SvgParser { 		EditPoint ep; 		bool first_point = true; 		double first_left_x, first_left_y;-		+		Gee.ArrayList<EditPoint> smooth_points = new Gee.ArrayList<EditPoint> ();+		EditPoint next, prev;+				 		path = new Path (); 				 		if (num_b == 0) {@@ -1004,7 +1097,7 @@ public class SvgParser { 					ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC); 				} 				-				if (b[i + 1].type == 'C') {+				if (b[i + 1].type == 'C' || b[i + 1].type == 'S') { 					return_val_if_fail (i + 1 < num_b, path_list); 					ep.get_right_handle ().set_point_type (PointType.CUBIC); 					ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0);@@ -1014,7 +1107,7 @@ public class SvgParser { 			} else if (b[i].type == 'Q') { 				warning ("Illustrator does not support quadratic control points."); 				print (@"$(b[i])\n");-			} else if (b[i].type == 'C') {+			} else if (b[i].type == 'C' || b[i].type == 'S') { 				 				if (first_point) { 					first_left_x = b[i].x0;@@ -1028,6 +1121,10 @@ public class SvgParser { 				ep.get_left_handle ().set_point_type (PointType.CUBIC); 				 				ep.get_left_handle ().move_to_coordinate (b[i].x1, b[i].y1);++				if (b[i].type == 'S') {+					smooth_points.add (ep);+				}				 				 				if (b[i + 1].type != 'z') { 					ep.get_right_handle ().move_to_coordinate (b[i + 1].x0, b[i + 1].y0);@@ -1042,6 +1139,25 @@ public class SvgParser { 			} 		} 		+		foreach (EditPoint e in smooth_points) {+			e.set_tie_handle (true);+			e.process_tied_handle ();++			e.set_point_type (PointType.CUBIC);+			e.get_right_handle ().set_point_type (PointType.CUBIC);+			e.get_left_handle ().set_point_type (PointType.CUBIC);++			if (e.next != null) {+				next = e.get_next ();+				next.get_left_handle ().angle = next.get_right_handle ().angle - PI;+			}+			+			if (e.prev != null) {+				prev = e.get_prev ();+				prev.get_right_handle ().angle = prev.get_left_handle ().angle - PI;+			}+		}+				 		if (path.points.size > 0) { 			warning ("Open path."); 			path_list.add (path);@@ -1051,6 +1167,20 @@ public class SvgParser { 	} 	 	// TODO: implement a default svg parser++	static int parse_int (string? s) {+		if (is_null (s)) {+			warning ("null instead of string");+			return 0;+		}+		+		if (!is_point ((!) s)) {+			warning (@"Expecting an integer got: $((!) s)");+			return 0;+		}+		+		return int.parse ((!) s);+	} 	 	static double parse_double (string? s) { 		if (is_null (s)) {@@ -1096,21 +1226,6 @@ public class SvgParser { 		 		return path; 	}--	private class BezierPoints {-		public unichar type = '\0';-		public unichar svg_type = '\0';-		public double x0  = 0;-		public double y0 = 0;-		public double x1 = 0;-		public double y1 = 0;-		public double x2 = 0;-		public double y2 = 0;-		-		public string to_string () {-			return @"$((!)type.to_string ()) $x0,$y0 $x1,$y1 $x2,$y2 SVG:$((!)svg_type.to_string ())";-		}-	} }  }

Contact

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