Commit d051bd2
Parse arc instructions in SVG paths
Changed files (14)
modifiedlibbirdfont/EmbeddedSvg.valamodifiedlibbirdfont/MoveTool.valamodifiedlibbirdfont/OverViewItem.valamodifiedlibbirdfont/SvgParser.valamodifiedlibsvgbird/BezierPoints.valamodifiedlibsvgbird/Doubles.valamodifiedlibsvgbird/Ellipse.valamodifiedlibsvgbird/Points.valamodifiedlibsvgbird/Polygon.valamodifiedlibsvgbird/Polyline.valamodifiedlibsvgbird/SvgArc.valamodifiedlibsvgbird/SvgFile.valamodifiedlibsvgbird/SvgPath.valamodifiedlibsvgbird/SvgStyle.vala
Diff
@@ -71,11 +71,7 @@ public class EmbeddedSvg : SvgBird.Object { drawing.update_region_boundaries (); } - // FIXME: handle this in SVG library instead public override bool is_over (double x, double y) {- print (@" $(this.x) <= $(x) <= $(this.x) + $(drawing.width)");- print (@" $(this.y) <= $(y) <= $(this.y) + $(drawing.height)");- return (this.x <= x <= this.x + drawing.width) && (this.y - drawing.height <= y <= this.y); }@@ -493,8 +493,6 @@ public class MoveTool : Tool { double w = Math.fabs (selection_x - last_x); double h = Math.fabs (selection_y - last_y); - Glyph glyph = MainWindow.get_current_glyph ();- cr.save (); Theme.color (cr, "Foreground 1"); cr.set_line_width (2);@@ -99,7 +99,6 @@ public class OverViewItem : GLib.Object { } Glyph g;- Font font; double gx, gy; double x1, x2, y1, y2; double scale_box;@@ -1016,6 +1016,36 @@ public class SvgParser { } } + public void get_bezier_points (string svg_data, out BezierPoints[] instructions, out int points, bool svg_glyph) {+ SvgFile.get_bezier_points (svg_data, out instructions, out points, svg_glyph);+ Gee.ArrayList<BezierPoints> bezier_points = new Gee.ArrayList<BezierPoints> ();+ BezierPoints[] arc_data = new BezierPoints[8];+ + for (int i = 0; i < points; i++) {+ if (instructions[i].type == 'A') {+ int arc_index = 0;+ + add_arc_points (arc_data, ref arc_index, + instructions[i].x0, instructions[i].y0,+ instructions[i].rx, instructions[i].ry,+ instructions[i].angle,+ instructions[i].large_arc,+ instructions[i].sweep,+ instructions[i].x1, instructions[i].y1);+ + for (int j = 0; j < arc_index; j++) {+ bezier_points.add (instructions[j]);+ }+ }+ + bezier_points.add (instructions[i]);+ }+ + instructions = new BezierPoints[bezier_points.size];+ for (int i = 0; i < bezier_points.size; i++) {+ instructions[i] = bezier_points.get (i);+ }+ } /** * @param d svg data * @param glyph use lines from this glyph but don't add the generated paths@@ -1517,7 +1547,6 @@ public class SvgParser { public static EmbeddedSvg parse_embedded_svg_file (string path) { string xml_data;- SvgFile svg_file = new SvgFile (); try { FileUtils.get_contents (path, out xml_data);@@ -1532,7 +1561,6 @@ public class SvgParser { public static EmbeddedSvg parse_embedded_svg_data (string xml_data) { XmlTree tree = new XmlTree (xml_data);- XmlElement tag = tree.get_root (); SvgDrawing drawing = new SvgDrawing (); SvgFile svg_file = new SvgFile (); @@ -1543,6 +1571,55 @@ public class SvgParser { return svg; } + /** Convert an SVG arc instruction to a Beziér path. */+ public 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) {+ + double angleStart, angleExtent;+ double s, step, theta;+ double cx, cy;+ + cx = 0;+ cy = 0;+ + // Approximate the path with Beziér points+ SvgBird.get_arc_arguments (x0, y0, rx, ry, angle, largeArcFlag, sweepFlag, x, y,+ out angleStart, out angleExtent, out cx, out cx);+ + s = (angleExtent > 0) ? 1 : -1;+ step = fabs (angleExtent) / (2 * fabs (angleExtent));++ theta = PI - angleStart - angleExtent;+ + bezier_points[bi].type = 'C';+ 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;++ return_if_fail (0 <= bi < bezier_points.length);++ 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 / 4);+ bezier_points[bi].y1 = cy + ry * sin (theta + 1 * step / 4);+ + bezier_points[bi].x2 = cx + rx * cos (theta + 2 * step / 4);+ bezier_points[bi].y2 = cy + ry * sin (theta + 2 * step / 4);+ + bi++;+ }+ } } }@@ -1,5 +1,5 @@ /*- Copyright (C) 2014 Johan Mattsson+ Copyright (C) 2014 2016 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 @@ -24,8 +24,20 @@ public class BezierPoints { public double y1 = 0; public double x2 = 0; public double y2 = 0;- ++ // arc arguments+ public double rx = 0; + public double ry = 0;+ public double angle = 0;+ public bool large_arc = false;+ public bool sweep = false;+ // the arc instructions begins at x0, y0 and ends at x1, x1+ public string to_string () {+ if (svg_type == 'A' || svg_type == 'a') {+ return @"SVG type:$((!) svg_type.to_string ()) $x0,$y0 $x1,$y1 rx=$rx, ry=$ry, angle=$angle, large_arc=$large_arc, sweep=$sweep)";+ }+ return @"$((!)type.to_string ()) $x0,$y0 $x1,$y1 $x2,$y2 SVG:$((!)svg_type.to_string ())"; } }@@ -17,12 +17,12 @@ using Cairo; namespace SvgBird { public class Doubles : GLib.Object {- public double* data;+ public PointValue* data; public int size = 0; int capacity = 10; public Doubles () {- data = new double[capacity];+ data = new PointValue[capacity]; } ~Doubles () {@@ -31,21 +31,34 @@ public class Doubles : GLib.Object { } public Doubles.for_capacity (int capacity) {- data = new double[capacity];+ data = new PointValue[capacity]; this.capacity = capacity; }- - public void add(double d) {+ + void increase_capacity () {+ int new_capacity = 2 * capacity;+ PointValue* new_data = new PointValue[new_capacity];+ Posix.memcpy (new_data, data, sizeof (PointValue) * size);+ delete data;+ data = new_data;+ capacity = new_capacity; + }++ public void add_type (uchar type) {+ if (size >= capacity) {+ increase_capacity ();+ }++ data[size].type = type;+ size++;+ }++ public void add (double d) { if (size >= capacity) {- int new_capacity = 2 * capacity;- double* new_data = new double[new_capacity];- Posix.memcpy (new_data, data, sizeof (double) * size);- delete data;- data = new_data;- capacity = new_capacity;+ increase_capacity (); } - data[size] = d;+ data[size].value = d; size++; } @@ -55,7 +68,7 @@ public class Doubles : GLib.Object { d.data = new double[capacity]; d.capacity = capacity; d.size = size;- Posix.memcpy (d.data, data, sizeof (double) * size);+ Posix.memcpy (d.data, data, sizeof (PointValue) * size); return d; } @@ -70,7 +83,7 @@ public class Doubles : GLib.Object { return 0; } - return data[index];+ return data[index].value; } } @@ -13,6 +13,7 @@ */ using Cairo;+using Math; namespace SvgBird { @@ -40,10 +41,10 @@ public class Ellipse : Object { public override void draw (Context cr) { cr.save ();- cr.translate (cx + rx, cy + ry);+ cr.translate (cx, cy); cr.scale (rx, ry);- cr.arc (0, 0, 1, 0, 2 * Math.PI);- cr.restore ();+ cr.arc (0, 0, 1, 0, 2 * PI);+ cr.restore (); cr.save (); apply_transform (cr); @@ -25,6 +25,10 @@ public class Points : GLib.Object { public void add (double p) { point_data.add (p); }+ + public void add_type (uchar type) {+ point_data.add_type (type);+ } } }@@ -33,10 +33,10 @@ public class Polygon : Object { apply_transform (cr); if (points.size > 2) {- cr.move_to (points.data[0], points.data[1]);+ cr.move_to (points.data[0].value, points.data[1].value); for (int i = 2; i < points.size - 1; i += 2) {- cr.line_to (points.data[i], points.data[i + 1]);+ cr.line_to (points.data[i].value, points.data[i + 1].value); } cr.close_path ();@@ -33,10 +33,10 @@ public class Polyline : Object { apply_transform (cr); if (points.size > 2) {- cr.move_to (points.data[0], points.data[1]);+ cr.move_to (points.data[0].value, points.data[1].value); for (int i = 2; i < points.size - 1; i += 2) {- cr.line_to (points.data[i], points.data[i + 1]);+ cr.line_to (points.data[i].value, points.data[i + 1].value); } } @@ -41,27 +41,30 @@ using Math; namespace SvgBird { -/** 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) {+public static void get_arc_arguments (double x0, double y0,+ double rx, double ry, double angle, bool largeArcFlag,+ bool sweepFlag, double x, double y,+ out double angle_start, out double angle_extent,+ out double center_x, out double center_y) { // // Elliptical arc implementation based on the SVG specification notes // + double cx, cy; 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 sx2, sy2; 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);+ angle = angle % (2 * Math.PI); cosAngle = cos (angle); sinAngle = sin (angle);@@ -71,7 +74,7 @@ static void add_arc_points (BezierPoints[] bezier_points, ref int bi, double x0, // x1 = cosAngle * dx2 + sinAngle * dy2; y1 = -sinAngle * dx2 + cosAngle * dy2;- + // Ensure radii are large enough rx = fabs(rx); ry = fabs(ry);@@ -80,7 +83,6 @@ static void add_arc_points (BezierPoints[] bezier_points, ref int bi, double x0, Px1 = x1 * x1; Py1 = y1 * y1; - // Check that radii are large enough radiiCheck = Px1 / Prx + Py1 / Pry; @@ -107,13 +109,13 @@ static void add_arc_points (BezierPoints[] bezier_points, ref int bi, double x0, sx2 = (x0 + x) / 2.0; sy2 = (y0 + y) / 2.0;- cx = sx2 - (cosAngle * cx1 - sinAngle * cy1);- cy = sy2 - (sinAngle * cx1 + cosAngle * cy1);+ cx = sx2 + (cosAngle * cx1 - sinAngle * cy1); // FIXME: + delete this+ 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;@@ -132,50 +134,26 @@ static void add_arc_points (BezierPoints[] bezier_points, ref int bi, double x0, angleExtent = sign * Math.acos(p / n); if(!sweepFlag && angleExtent > 0) {- angleExtent -= 2 *PI;+ angleExtent -= 2 * PI; } else if (sweepFlag && angleExtent < 0) {- angleExtent += 2 *PI;+ 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) / (2 * fabs (angleExtent));-- theta = PI - angleStart - angleExtent;- - bezier_points[bi].type = 'L';- bezier_points[bi].svg_type = 'a';+ + //center_x = cx - rx;+ //center_y = cy - ry; - 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;+ center_x = cx;+ center_y = cy;+ + angleExtent = -angleExtent;+ angleStart = -angleStart; - return_if_fail (0 <= bi < bezier_points.length);+ angle_start = angleStart;+ angle_extent = angleExtent; - bezier_points[bi].type = 'S';- bezier_points[bi].svg_type = 'a';+ print (@"angleStart: $angle_start angleExtent $angle_extent\n");+} - 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 / 4);- bezier_points[bi].y1 = cy + ry * sin (theta + 1 * step / 4);- - bezier_points[bi].x2 = cx + rx * cos (theta + 2 * step / 4);- bezier_points[bi].y2 = cy + ry * sin (theta + 2 * step / 4);- - bi++;- } } -}- @@ -14,6 +14,7 @@ using B; using Cairo;+using Math; namespace SvgBird { @@ -636,25 +637,53 @@ public class SvgFile : GLib.Object { get_bezier_points (data, out bezier_points, out points_size, true); + // instructions are padded+ for (int i = 0; i < points_size; i++) { // FIXME: add more types if (bezier_points[i].type == 'M') { points.x = bezier_points[i].x0; points.y = bezier_points[i].y0; } else if (bezier_points[i].type == 'C') {+ points.add_type (CUBIC); points.add (bezier_points[i].x0); points.add (bezier_points[i].y0); points.add (bezier_points[i].x1); points.add (bezier_points[i].y1); points.add (bezier_points[i].x2); points.add (bezier_points[i].y2);+ points.add (0); } else if (bezier_points[i].type == 'L') {+ points.add_type (CUBIC); // FIXME: use cairo line points.add (bezier_points[i].x0); points.add (bezier_points[i].y0); points.add (bezier_points[i].x0); points.add (bezier_points[i].y0); points.add (bezier_points[i].x0); points.add (bezier_points[i].y0);+ points.add (0);+ } else if (bezier_points[i].type == 'A') {+ BezierPoints b = bezier_points[i];+ double angle_start;+ double angle_extent;+ double center_x;+ double center_y;+ //double rotation = Math.PI * (b.angle / 180.0);+ double rotation = b.angle;+ + get_arc_arguments (b.x0, b.y0, b.rx, b.ry,+ b.angle, b.large_arc, b.sweep, b.x1, b.y1,+ out angle_start, out angle_extent,+ out center_x, out center_y);+ + points.add_type (ARC);+ points.add (center_x);+ points.add (center_y);+ points.add (b.rx);+ points.add (b.ry);+ points.add (angle_start);+ points.add (angle_extent); + points.add (rotation); } else if (bezier_points[i].type == 'z') { points.closed = true; path_data.add (points);@@ -686,7 +715,7 @@ public class SvgFile : GLib.Object { return double.parse ((!) s); } -+ // FIXME: rename to instructions public static void get_bezier_points (string point_data, out BezierPoints[] bezier_points, out int points, bool svg_glyph) { double px = 0; double py = 0;@@ -1157,7 +1186,7 @@ public class SvgFile : GLib.Object { arc_rx = parse_double (c[++i]); arc_ry = parse_double (c[++i]); - arc_rotation = parse_double (c[++i]);+ arc_rotation = PI * (parse_double (c[++i]) / 180.0); large_arc = parse_int (c[++i]); arc_sweep = parse_int (c[++i]); @@ -1172,7 +1201,20 @@ public class SvgFile : GLib.Object { 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);+ bezier_points[bi].type = 'A';+ bezier_points[bi].svg_type = 'a';+ bezier_points[bi].x0 = px;+ bezier_points[bi].y0 = py;+ bezier_points[bi].x1 = cx;+ bezier_points[bi].y1 = cy;+ bezier_points[bi].rx = arc_rx;+ bezier_points[bi].ry = arc_ry;+ bezier_points[bi].angle = arc_rotation;+ bezier_points[bi].large_arc = large_arc == 1;+ bezier_points[bi].sweep = arc_sweep == 1;+ bi++;+ + // FIXME: Delete 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;@@ -1182,7 +1224,7 @@ public class SvgFile : GLib.Object { arc_rx = parse_double (c[++i]); arc_ry = parse_double (c[++i]); - arc_rotation = parse_double (c[++i]);+ arc_rotation = PI * (parse_double (c[++i]) / 180.0); large_arc = parse_int (c[++i]); arc_sweep = parse_int (c[++i]); @@ -1197,12 +1239,21 @@ public class SvgFile : GLib.Object { 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);-+ bezier_points[bi].type = 'A';+ bezier_points[bi].svg_type = 'A';+ bezier_points[bi].x0 = px;+ bezier_points[bi].y0 = py;+ bezier_points[bi].x1 = cx;+ bezier_points[bi].y1 = cy;+ bezier_points[bi].rx = arc_rx;+ bezier_points[bi].ry = arc_ry;+ bezier_points[bi].angle = arc_rotation;+ bezier_points[bi].large_arc = large_arc == 1;+ bezier_points[bi].sweep = arc_sweep == 1;+ bi++;+ px = cx; py = cy;- - } } else if (c[i] == "z") { bezier_points[bi].type = 'z';@@ -48,18 +48,49 @@ public class SvgPath : Object { cr.restore (); } - public void draw_points (Context cr, Points points) {- Doubles p = points.point_data;+ public void draw_points (Context cr, Points path) {+ PointValue* points = path.point_data.data;+ int size = path.point_data.size; - return_if_fail (p.size % 6 == 0);+ return_if_fail (size % 8 == 0); - for (int i = 0; i < p.size; i += 6) {- cr.curve_to (p.data[i], p.data[i + 1], - p.data[i + 2], p.data[i + 3],- p.data[i + 4], p.data[i + 5]);+ for (int i = 0; i < size; i += 8) {+ switch (points[i].type) {+ case ARC: + draw_arc (cr, points[i + 1].value, points[i + 2].value,+ points[i + 3].value, points[i + 4].value,+ points[i + 5].value, points[i + 6].value,+ points[i + 7].value);+ break;+ case CUBIC:+ cr.curve_to (points[i + 1].value, points[i + 2].value, + points[i + 3].value, points[i + 4].value,+ points[i + 5].value, points[i + 6].value);+ break;+ } } } + static void draw_arc (Context cr,+ double x, double y,+ double rx, double ry,+ double angle_start, double angle_extent,+ double rotation) {+ + cr.save ();+ cr.translate (x, y);+ cr.rotate (rotation);+ cr.scale (rx, ry);++ if (angle_extent > 0) {+ cr.arc_negative (0, 0, 1, -angle_start, -angle_start - angle_extent);+ } else {+ cr.arc (0, 0, 1, -angle_start, -angle_start - angle_extent);+ }+ + cr.restore ();+ }+ public override void move (double dx, double dy) { } @@ -1,5 +1,5 @@ /*- Copyright (C) 2015 Johan Mattsson+ Copyright (C) 2015 2016 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 @@ -70,7 +70,6 @@ public class SvgStyle : GLib.Object { public void inherit (SvgStyle inherited) { foreach (string key in inherited.style.keys) {- print (@"inherit $key: " + inherited.style.get (key) + "\n"); style.set (key, inherited.style.get (key)); } }