summaryrefslogtreecommitdiff
path: root/source/svg
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2017-01-31 16:20:47 +0100
committerTor Andersson <tor.andersson@artifex.com>2017-01-31 19:46:00 +0100
commitc6531e339d7a919f2e58b65d08d859e5ac335688 (patch)
treed544f1af5506e7ad8c1d21845a52701ba4e4d409 /source/svg
parent22b73b418a25a37c95d68283514f8e50bb036017 (diff)
downloadmupdf-c6531e339d7a919f2e58b65d08d859e5ac335688.tar.xz
svg: Add arc support.
Copied from XPS implementation.
Diffstat (limited to 'source/svg')
-rw-r--r--source/svg/svg-run.c168
1 files changed, 166 insertions, 2 deletions
diff --git a/source/svg/svg-run.c b/source/svg/svg-run.c
index f3860d76..1f3c44e7 100644
--- a/source/svg/svg-run.c
+++ b/source/svg/svg-run.c
@@ -311,6 +311,154 @@ svg_run_polygon(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node
fz_drop_path(ctx, path);
}
+static void
+svg_add_arc_segment(fz_context *ctx, fz_path *path, const fz_matrix *mtx, float th0, float th1, int iscw)
+{
+ float t, d;
+ fz_point p;
+
+ while (th1 < th0)
+ th1 += (float)M_PI * 2;
+
+ d = (float)M_PI / 180; /* 1-degree precision */
+
+ if (iscw)
+ {
+ for (t = th0 + d; t < th1 - d/2; t += d)
+ {
+ fz_transform_point_xy(&p, mtx, cosf(t), sinf(t));
+ fz_lineto(ctx, path, p.x, p.y);
+ }
+ }
+ else
+ {
+ th0 += (float)M_PI * 2;
+ for (t = th0 - d; t > th1 + d/2; t -= d)
+ {
+ fz_transform_point_xy(&p, mtx, cosf(t), sinf(t));
+ fz_lineto(ctx, path, p.x, p.y);
+ }
+ }
+}
+
+static float
+angle_between(const fz_point u, const fz_point v)
+{
+ float det = u.x * v.y - u.y * v.x;
+ float sign = (det < 0 ? -1 : 1);
+ float magu = u.x * u.x + u.y * u.y;
+ float magv = v.x * v.x + v.y * v.y;
+ float udotv = u.x * v.x + u.y * v.y;
+ float t = udotv / (magu * magv);
+ /* guard against rounding errors when near |1| (where acos will return NaN) */
+ if (t < -1) t = -1;
+ if (t > 1) t = 1;
+ return sign * acosf(t);
+}
+
+static void
+svg_add_arc(fz_context *ctx, fz_path *path,
+ float size_x, float size_y, float rotation_angle,
+ int is_large_arc, int is_clockwise,
+ float point_x, float point_y)
+{
+ fz_matrix rotmat, revmat;
+ fz_matrix mtx;
+ fz_point pt;
+ float rx, ry;
+ float x1, y1, x2, y2;
+ float x1t, y1t;
+ float cxt, cyt, cx, cy;
+ float t1, t2, t3;
+ float sign;
+ float th1, dth;
+
+ pt = fz_currentpoint(ctx, path);
+ x1 = pt.x;
+ y1 = pt.y;
+ x2 = point_x;
+ y2 = point_y;
+ rx = size_x;
+ ry = size_y;
+
+ if (is_clockwise != is_large_arc)
+ sign = 1;
+ else
+ sign = -1;
+
+ fz_rotate(&rotmat, rotation_angle);
+ fz_rotate(&revmat, -rotation_angle);
+
+ /* http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes */
+ /* Conversion from endpoint to center parameterization */
+
+ /* F.6.6.1 -- ensure radii are positive and non-zero */
+ rx = fabsf(rx);
+ ry = fabsf(ry);
+ if (rx < 0.001f || ry < 0.001f || (x1 == x2 && y1 == y2))
+ {
+ fz_lineto(ctx, path, x2, y2);
+ return;
+ }
+
+ /* F.6.5.1 */
+ pt.x = (x1 - x2) / 2;
+ pt.y = (y1 - y2) / 2;
+ fz_transform_vector(&pt, &revmat);
+ x1t = pt.x;
+ y1t = pt.y;
+
+ /* F.6.6.2 -- ensure radii are large enough */
+ t1 = (x1t * x1t) / (rx * rx) + (y1t * y1t) / (ry * ry);
+ if (t1 > 1)
+ {
+ rx = rx * sqrtf(t1);
+ ry = ry * sqrtf(t1);
+ }
+
+ /* F.6.5.2 */
+ t1 = (rx * rx * ry * ry) - (rx * rx * y1t * y1t) - (ry * ry * x1t * x1t);
+ t2 = (rx * rx * y1t * y1t) + (ry * ry * x1t * x1t);
+ t3 = t1 / t2;
+ /* guard against rounding errors; sqrt of negative numbers is bad for your health */
+ if (t3 < 0) t3 = 0;
+ t3 = sqrtf(t3);
+
+ cxt = sign * t3 * (rx * y1t) / ry;
+ cyt = sign * t3 * -(ry * x1t) / rx;
+
+ /* F.6.5.3 */
+ pt.x = cxt;
+ pt.y = cyt;
+ fz_transform_vector(&pt, &rotmat);
+ cx = pt.x + (x1 + x2) / 2;
+ cy = pt.y + (y1 + y2) / 2;
+
+ /* F.6.5.4 */
+ {
+ fz_point coord1, coord2, coord3, coord4;
+ coord1.x = 1;
+ coord1.y = 0;
+ coord2.x = (x1t - cxt) / rx;
+ coord2.y = (y1t - cyt) / ry;
+ coord3.x = (x1t - cxt) / rx;
+ coord3.y = (y1t - cyt) / ry;
+ coord4.x = (-x1t - cxt) / rx;
+ coord4.y = (-y1t - cyt) / ry;
+ th1 = angle_between(coord1, coord2);
+ dth = angle_between(coord3, coord4);
+ if (dth < 0 && !is_clockwise)
+ dth += (((float)M_PI / 180) * 360);
+ if (dth > 0 && is_clockwise)
+ dth -= (((float)M_PI / 180) * 360);
+ }
+
+ fz_pre_scale(fz_pre_rotate(fz_translate(&mtx, cx, cy), rotation_angle), rx, ry);
+ svg_add_arc_segment(ctx, path, &mtx, th1, th1 + dth, is_clockwise);
+
+ fz_lineto(ctx, path, point_x, point_y);
+}
+
static fz_path *
svg_parse_path_data(fz_context *ctx, svg_document *doc, const char *str)
{
@@ -321,7 +469,7 @@ svg_parse_path_data(fz_context *ctx, svg_document *doc, const char *str)
int cmd;
float number;
- float args[6];
+ float args[7];
int nargs;
/* saved control point for smooth curves */
@@ -344,7 +492,7 @@ svg_parse_path_data(fz_context *ctx, svg_document *doc, const char *str)
if (svg_is_digit(*str))
{
str = svg_lex_number(&number, str);
- if (nargs == 6)
+ if (nargs == nelem(args))
fz_throw(ctx, FZ_ERROR_GENERIC, "stack overflow in path data");
args[nargs++] = number;
}
@@ -585,6 +733,22 @@ svg_parse_path_data(fz_context *ctx, svg_document *doc, const char *str)
}
break;
+ case 'A':
+ if (nargs == 7)
+ {
+ svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+ nargs = 0;
+ }
+ break;
+ case 'a':
+ if (nargs == 7)
+ {
+ p = fz_currentpoint(ctx, path);
+ svg_add_arc(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5] + p.x, args[6] + p.y);
+ nargs = 0;
+ }
+ break;
+
case 0:
if (nargs != 0)
fz_throw(ctx, FZ_ERROR_GENERIC, "path data must begin with a command");