diff options
author | Tor Andersson <tor.andersson@artifex.com> | 2013-06-19 15:29:44 +0200 |
---|---|---|
committer | Tor Andersson <tor.andersson@artifex.com> | 2013-06-20 16:45:35 +0200 |
commit | 0a927854a10e1e6b9770a81e2e1d9f3093631757 (patch) | |
tree | 3d65d820d9fdba2d0d394d99c36290c851b78ca0 /source/xps/xps-path.c | |
parent | 1ae8f19179c5f0f8c6352b3c7855465325d5449a (diff) | |
download | mupdf-0a927854a10e1e6b9770a81e2e1d9f3093631757.tar.xz |
Rearrange source files.
Diffstat (limited to 'source/xps/xps-path.c')
-rw-r--r-- | source/xps/xps-path.c | 1053 |
1 files changed, 1053 insertions, 0 deletions
diff --git a/source/xps/xps-path.c b/source/xps/xps-path.c new file mode 100644 index 00000000..371f52ab --- /dev/null +++ b/source/xps/xps-path.c @@ -0,0 +1,1053 @@ +#include "mupdf/xps.h" + +static char * +xps_parse_float_array(char *s, int num, float *x) +{ + int k = 0; + + if (s == NULL || *s == 0) + return NULL; + + while (*s) + { + while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a) + s++; + x[k] = (float)strtod(s, &s); + while (*s == 0x0d || *s == '\t' || *s == ' ' || *s == 0x0a) + s++; + if (*s == ',') + s++; + if (++k == num) + break; + } + return s; +} + +char * +xps_parse_point(char *s_in, float *x, float *y) +{ + char *s_out = s_in; + float xy[2]; + + s_out = xps_parse_float_array(s_out, 2, &xy[0]); + *x = xy[0]; + *y = xy[1]; + return s_out; +} + +/* Draw an arc segment transformed by the matrix, we approximate with straight + * line segments. We cannot use the fz_arc function because they only draw + * circular arcs, we need to transform the line to make them elliptical but + * without transforming the line width. + * + * We are guaranteed that on entry the point is at the point that would be + * calculated by th0, and on exit, a point is generated for us at th0. + */ +static void +xps_draw_arc_segment(fz_context *doc, 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) + { + p.x = cosf(t); + p.y = sinf(t); + fz_transform_point(&p, mtx); + fz_lineto(doc, path, p.x, p.y); + } + } + else + { + th0 += (float)M_PI * 2; + for (t = th0 - d; t > th1 + d/2; t -= d) + { + p.x = cosf(t); + p.y = sinf(t); + fz_transform_point(&p, mtx); + fz_lineto(doc, path, p.x, p.y); + } + } +} + +/* Given two vectors find the angle between them. */ +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); +} + +/* + Some explaination of the parameters here is warranted. See: + + http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes + + Add an arc segment to path, that describes a section of an elliptical + arc from the current point of path to (point_x,point_y), such that: + + The arc segment is taken from an elliptical arc of semi major radius + size_x, semi minor radius size_y, where the semi major axis of the + ellipse is rotated by rotation_angle. + + If is_large_arc, then the arc segment is selected to be > 180 degrees. + + If is_clockwise, then the arc sweeps clockwise. +*/ +static void +xps_draw_arc(fz_context *doc, 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(doc, 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(doc, 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); + xps_draw_arc_segment(doc, path, &mtx, th1, th1 + dth, is_clockwise); + + fz_lineto(doc, path, point_x, point_y); +} + +/* + * Parse an abbreviated geometry string, and call + * ghostscript moveto/lineto/curveto functions to + * build up a path. + */ + +static fz_path * +xps_parse_abbreviated_geometry(xps_document *doc, char *geom, int *fill_rule) +{ + fz_path *path; + char **args; + char **pargs; + char *s = geom; + fz_point pt; + int i, n; + int cmd, old; + float x1, y1, x2, y2, x3, y3; + float smooth_x, smooth_y; /* saved cubic bezier control point for smooth curves */ + int reset_smooth; + + path = fz_new_path(doc->ctx); + + args = fz_malloc_array(doc->ctx, strlen(geom) + 1, sizeof(char*)); + pargs = args; + + while (*s) + { + if ((*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z')) + { + *pargs++ = s++; + } + else if ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E') + { + *pargs++ = s; + while ((*s >= '0' && *s <= '9') || *s == '.' || *s == '+' || *s == '-' || *s == 'e' || *s == 'E') + s ++; + } + else + { + s++; + } + } + + *pargs = s; + + n = pargs - args; + i = 0; + + old = 0; + + reset_smooth = 1; + smooth_x = 0; + smooth_y = 0; + + while (i < n) + { + cmd = args[i][0]; + if (cmd == '+' || cmd == '.' || cmd == '-' || (cmd >= '0' && cmd <= '9')) + cmd = old; /* it's a number, repeat old command */ + else + i ++; + + if (reset_smooth) + { + smooth_x = 0; + smooth_y = 0; + } + + reset_smooth = 1; + + switch (cmd) + { + case 'F': + if (i >= n) break; + *fill_rule = atoi(args[i]); + i ++; + break; + + case 'M': + if (i + 1 >= n) break; + fz_moveto(doc->ctx, path, fz_atof(args[i]), fz_atof(args[i+1])); + i += 2; + break; + case 'm': + if (i + 1 >= n) break; + pt = fz_currentpoint(doc->ctx, path); + fz_moveto(doc->ctx, path, pt.x + fz_atof(args[i]), pt.y + fz_atof(args[i+1])); + i += 2; + break; + + case 'L': + if (i + 1 >= n) break; + fz_lineto(doc->ctx, path, fz_atof(args[i]), fz_atof(args[i+1])); + i += 2; + break; + case 'l': + if (i + 1 >= n) break; + pt = fz_currentpoint(doc->ctx, path); + fz_lineto(doc->ctx, path, pt.x + fz_atof(args[i]), pt.y + fz_atof(args[i+1])); + i += 2; + break; + + case 'H': + if (i >= n) break; + pt = fz_currentpoint(doc->ctx, path); + fz_lineto(doc->ctx, path, fz_atof(args[i]), pt.y); + i += 1; + break; + case 'h': + if (i >= n) break; + pt = fz_currentpoint(doc->ctx, path); + fz_lineto(doc->ctx, path, pt.x + fz_atof(args[i]), pt.y); + i += 1; + break; + + case 'V': + if (i >= n) break; + pt = fz_currentpoint(doc->ctx, path); + fz_lineto(doc->ctx, path, pt.x, fz_atof(args[i])); + i += 1; + break; + case 'v': + if (i >= n) break; + pt = fz_currentpoint(doc->ctx, path); + fz_lineto(doc->ctx, path, pt.x, pt.y + fz_atof(args[i])); + i += 1; + break; + + case 'C': + if (i + 5 >= n) break; + x1 = fz_atof(args[i+0]); + y1 = fz_atof(args[i+1]); + x2 = fz_atof(args[i+2]); + y2 = fz_atof(args[i+3]); + x3 = fz_atof(args[i+4]); + y3 = fz_atof(args[i+5]); + fz_curveto(doc->ctx, path, x1, y1, x2, y2, x3, y3); + i += 6; + reset_smooth = 0; + smooth_x = x3 - x2; + smooth_y = y3 - y2; + break; + + case 'c': + if (i + 5 >= n) break; + pt = fz_currentpoint(doc->ctx, path); + x1 = fz_atof(args[i+0]) + pt.x; + y1 = fz_atof(args[i+1]) + pt.y; + x2 = fz_atof(args[i+2]) + pt.x; + y2 = fz_atof(args[i+3]) + pt.y; + x3 = fz_atof(args[i+4]) + pt.x; + y3 = fz_atof(args[i+5]) + pt.y; + fz_curveto(doc->ctx, path, x1, y1, x2, y2, x3, y3); + i += 6; + reset_smooth = 0; + smooth_x = x3 - x2; + smooth_y = y3 - y2; + break; + + case 'S': + if (i + 3 >= n) break; + pt = fz_currentpoint(doc->ctx, path); + x1 = fz_atof(args[i+0]); + y1 = fz_atof(args[i+1]); + x2 = fz_atof(args[i+2]); + y2 = fz_atof(args[i+3]); + fz_curveto(doc->ctx, path, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2); + i += 4; + reset_smooth = 0; + smooth_x = x2 - x1; + smooth_y = y2 - y1; + break; + + case 's': + if (i + 3 >= n) break; + pt = fz_currentpoint(doc->ctx, path); + x1 = fz_atof(args[i+0]) + pt.x; + y1 = fz_atof(args[i+1]) + pt.y; + x2 = fz_atof(args[i+2]) + pt.x; + y2 = fz_atof(args[i+3]) + pt.y; + fz_curveto(doc->ctx, path, pt.x + smooth_x, pt.y + smooth_y, x1, y1, x2, y2); + i += 4; + reset_smooth = 0; + smooth_x = x2 - x1; + smooth_y = y2 - y1; + break; + + case 'Q': + if (i + 3 >= n) break; + pt = fz_currentpoint(doc->ctx, path); + x1 = fz_atof(args[i+0]); + y1 = fz_atof(args[i+1]); + x2 = fz_atof(args[i+2]); + y2 = fz_atof(args[i+3]); + fz_curveto(doc->ctx, path, + (pt.x + 2 * x1) / 3, (pt.y + 2 * y1) / 3, + (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3, + x2, y2); + i += 4; + break; + case 'q': + if (i + 3 >= n) break; + pt = fz_currentpoint(doc->ctx, path); + x1 = fz_atof(args[i+0]) + pt.x; + y1 = fz_atof(args[i+1]) + pt.y; + x2 = fz_atof(args[i+2]) + pt.x; + y2 = fz_atof(args[i+3]) + pt.y; + fz_curveto(doc->ctx, path, + (pt.x + 2 * x1) / 3, (pt.y + 2 * y1) / 3, + (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3, + x2, y2); + i += 4; + break; + + case 'A': + if (i + 6 >= n) break; + xps_draw_arc(doc->ctx, path, + fz_atof(args[i+0]), fz_atof(args[i+1]), fz_atof(args[i+2]), + atoi(args[i+3]), atoi(args[i+4]), + fz_atof(args[i+5]), fz_atof(args[i+6])); + i += 7; + break; + case 'a': + if (i + 6 >= n) break; + pt = fz_currentpoint(doc->ctx, path); + xps_draw_arc(doc->ctx, path, + fz_atof(args[i+0]), fz_atof(args[i+1]), fz_atof(args[i+2]), + atoi(args[i+3]), atoi(args[i+4]), + fz_atof(args[i+5]) + pt.x, fz_atof(args[i+6]) + pt.y); + i += 7; + break; + + case 'Z': + case 'z': + fz_closepath(doc->ctx, path); + break; + + default: + /* eek */ + fz_warn(doc->ctx, "ignoring invalid command '%c'", cmd); + /* Skip any trailing numbers to avoid an infinite loop */ + while (i < n && (args[i][0] == '+' || args[i][0] == '.' || args[i][0] == '-' || (args[i][0] >= '0' && args[i][0] <= '9'))) + i ++; + break; + } + + old = cmd; + } + + fz_free(doc->ctx, args); + return path; +} + +static void +xps_parse_arc_segment(fz_context *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke) +{ + /* ArcSegment pretty much follows the SVG algorithm for converting an + * arc in endpoint representation to an arc in centerpoint + * representation. Once in centerpoint it can be given to the + * graphics library in the form of a postscript arc. */ + + float rotation_angle; + int is_large_arc, is_clockwise; + float point_x, point_y; + float size_x, size_y; + int is_stroked; + + char *point_att = fz_xml_att(root, "Point"); + char *size_att = fz_xml_att(root, "Size"); + char *rotation_angle_att = fz_xml_att(root, "RotationAngle"); + char *is_large_arc_att = fz_xml_att(root, "IsLargeArc"); + char *sweep_direction_att = fz_xml_att(root, "SweepDirection"); + char *is_stroked_att = fz_xml_att(root, "IsStroked"); + + if (!point_att || !size_att || !rotation_angle_att || !is_large_arc_att || !sweep_direction_att) + { + fz_warn(doc, "ArcSegment element is missing attributes"); + return; + } + + is_stroked = 1; + if (is_stroked_att && !strcmp(is_stroked_att, "false")) + is_stroked = 0; + if (!is_stroked) + *skipped_stroke = 1; + + point_x = point_y = 0; + size_x = size_y = 0; + + xps_parse_point(point_att, &point_x, &point_y); + xps_parse_point(size_att, &size_x, &size_y); + rotation_angle = fz_atof(rotation_angle_att); + is_large_arc = !strcmp(is_large_arc_att, "true"); + is_clockwise = !strcmp(sweep_direction_att, "Clockwise"); + + if (stroking && !is_stroked) + { + fz_moveto(doc, path, point_x, point_y); + return; + } + + xps_draw_arc(doc, path, size_x, size_y, rotation_angle, is_large_arc, is_clockwise, point_x, point_y); +} + +static void +xps_parse_poly_quadratic_bezier_segment(fz_context *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke) +{ + char *points_att = fz_xml_att(root, "Points"); + char *is_stroked_att = fz_xml_att(root, "IsStroked"); + float x[2], y[2]; + int is_stroked; + fz_point pt; + char *s; + int n; + + if (!points_att) + { + fz_warn(doc, "PolyQuadraticBezierSegment element has no points"); + return; + } + + is_stroked = 1; + if (is_stroked_att && !strcmp(is_stroked_att, "false")) + is_stroked = 0; + if (!is_stroked) + *skipped_stroke = 1; + + s = points_att; + n = 0; + while (*s != 0) + { + while (*s == ' ') s++; + s = xps_parse_point(s, &x[n], &y[n]); + n ++; + if (n == 2) + { + if (stroking && !is_stroked) + { + fz_moveto(doc, path, x[1], y[1]); + } + else + { + pt = fz_currentpoint(doc, path); + fz_curveto(doc, path, + (pt.x + 2 * x[0]) / 3, (pt.y + 2 * y[0]) / 3, + (x[1] + 2 * x[0]) / 3, (y[1] + 2 * y[0]) / 3, + x[1], y[1]); + } + n = 0; + } + } +} + +static void +xps_parse_poly_bezier_segment(fz_context *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke) +{ + char *points_att = fz_xml_att(root, "Points"); + char *is_stroked_att = fz_xml_att(root, "IsStroked"); + float x[3], y[3]; + int is_stroked; + char *s; + int n; + + if (!points_att) + { + fz_warn(doc, "PolyBezierSegment element has no points"); + return; + } + + is_stroked = 1; + if (is_stroked_att && !strcmp(is_stroked_att, "false")) + is_stroked = 0; + if (!is_stroked) + *skipped_stroke = 1; + + s = points_att; + n = 0; + while (*s != 0) + { + while (*s == ' ') s++; + s = xps_parse_point(s, &x[n], &y[n]); + n ++; + if (n == 3) + { + if (stroking && !is_stroked) + fz_moveto(doc, path, x[2], y[2]); + else + fz_curveto(doc, path, x[0], y[0], x[1], y[1], x[2], y[2]); + n = 0; + } + } +} + +static void +xps_parse_poly_line_segment(fz_context *doc, fz_path *path, fz_xml *root, int stroking, int *skipped_stroke) +{ + char *points_att = fz_xml_att(root, "Points"); + char *is_stroked_att = fz_xml_att(root, "IsStroked"); + int is_stroked; + float x, y; + char *s; + + if (!points_att) + { + fz_warn(doc, "PolyLineSegment element has no points"); + return; + } + + is_stroked = 1; + if (is_stroked_att && !strcmp(is_stroked_att, "false")) + is_stroked = 0; + if (!is_stroked) + *skipped_stroke = 1; + + s = points_att; + while (*s != 0) + { + while (*s == ' ') s++; + s = xps_parse_point(s, &x, &y); + if (stroking && !is_stroked) + fz_moveto(doc, path, x, y); + else + fz_lineto(doc, path, x, y); + } +} + +static void +xps_parse_path_figure(fz_context *doc, fz_path *path, fz_xml *root, int stroking) +{ + fz_xml *node; + + char *is_closed_att; + char *start_point_att; + char *is_filled_att; + + int is_closed = 0; + int is_filled = 1; + float start_x = 0; + float start_y = 0; + + int skipped_stroke = 0; + + is_closed_att = fz_xml_att(root, "IsClosed"); + start_point_att = fz_xml_att(root, "StartPoint"); + is_filled_att = fz_xml_att(root, "IsFilled"); + + if (is_closed_att) + is_closed = !strcmp(is_closed_att, "true"); + if (is_filled_att) + is_filled = !strcmp(is_filled_att, "true"); + if (start_point_att) + xps_parse_point(start_point_att, &start_x, &start_y); + + if (!stroking && !is_filled) /* not filled, when filling */ + return; + + fz_moveto(doc, path, start_x, start_y); + + for (node = fz_xml_down(root); node; node = fz_xml_next(node)) + { + if (!strcmp(fz_xml_tag(node), "ArcSegment")) + xps_parse_arc_segment(doc, path, node, stroking, &skipped_stroke); + if (!strcmp(fz_xml_tag(node), "PolyBezierSegment")) + xps_parse_poly_bezier_segment(doc, path, node, stroking, &skipped_stroke); + if (!strcmp(fz_xml_tag(node), "PolyLineSegment")) + xps_parse_poly_line_segment(doc, path, node, stroking, &skipped_stroke); + if (!strcmp(fz_xml_tag(node), "PolyQuadraticBezierSegment")) + xps_parse_poly_quadratic_bezier_segment(doc, path, node, stroking, &skipped_stroke); + } + + if (is_closed) + { + if (stroking && skipped_stroke) + fz_lineto(doc, path, start_x, start_y); /* we've skipped using fz_moveto... */ + else + fz_closepath(doc, path); /* no skipped segments, safe to closepath properly */ + } +} + +fz_path * +xps_parse_path_geometry(xps_document *doc, xps_resource *dict, fz_xml *root, int stroking, int *fill_rule) +{ + fz_xml *node; + + char *figures_att; + char *fill_rule_att; + char *transform_att; + + fz_xml *transform_tag = NULL; + fz_xml *figures_tag = NULL; /* only used by resource */ + + fz_matrix transform; + fz_path *path; + + figures_att = fz_xml_att(root, "Figures"); + fill_rule_att = fz_xml_att(root, "FillRule"); + transform_att = fz_xml_att(root, "Transform"); + + for (node = fz_xml_down(root); node; node = fz_xml_next(node)) + { + if (!strcmp(fz_xml_tag(node), "PathGeometry.Transform")) + transform_tag = fz_xml_down(node); + } + + xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL); + xps_resolve_resource_reference(doc, dict, &figures_att, &figures_tag, NULL); + + if (fill_rule_att) + { + if (!strcmp(fill_rule_att, "NonZero")) + *fill_rule = 1; + if (!strcmp(fill_rule_att, "EvenOdd")) + *fill_rule = 0; + } + + transform = fz_identity; + if (transform_att) + xps_parse_render_transform(doc, transform_att, &transform); + if (transform_tag) + xps_parse_matrix_transform(doc, transform_tag, &transform); + + if (figures_att) + path = xps_parse_abbreviated_geometry(doc, figures_att, fill_rule); + else + path = fz_new_path(doc->ctx); + + if (figures_tag) + xps_parse_path_figure(doc->ctx, path, figures_tag, stroking); + + for (node = fz_xml_down(root); node; node = fz_xml_next(node)) + { + if (!strcmp(fz_xml_tag(node), "PathFigure")) + xps_parse_path_figure(doc->ctx, path, node, stroking); + } + + if (transform_att || transform_tag) + fz_transform_path(doc->ctx, path, &transform); + + return path; +} + +static int +xps_parse_line_cap(char *attr) +{ + if (attr) + { + if (!strcmp(attr, "Flat")) return 0; + if (!strcmp(attr, "Round")) return 1; + if (!strcmp(attr, "Square")) return 2; + if (!strcmp(attr, "Triangle")) return 3; + } + return 0; +} + +void +xps_clip(xps_document *doc, const fz_matrix *ctm, xps_resource *dict, char *clip_att, fz_xml *clip_tag) +{ + fz_path *path; + int fill_rule = 0; + + if (clip_att) + path = xps_parse_abbreviated_geometry(doc, clip_att, &fill_rule); + else if (clip_tag) + path = xps_parse_path_geometry(doc, dict, clip_tag, 0, &fill_rule); + else + path = fz_new_path(doc->ctx); + fz_clip_path(doc->dev, path, NULL, fill_rule == 0, ctm); + fz_free_path(doc->ctx, path); +} + +/* + * Parse an XPS <Path> element, and call relevant ghostscript + * functions for drawing and/or clipping the child elements. + */ + +void +xps_parse_path(xps_document *doc, const fz_matrix *ctm, char *base_uri, xps_resource *dict, fz_xml *root) +{ + fz_xml *node; + + char *fill_uri; + char *stroke_uri; + char *opacity_mask_uri; + + char *transform_att; + char *clip_att; + char *data_att; + char *fill_att; + char *stroke_att; + char *opacity_att; + char *opacity_mask_att; + + fz_xml *transform_tag = NULL; + fz_xml *clip_tag = NULL; + fz_xml *data_tag = NULL; + fz_xml *fill_tag = NULL; + fz_xml *stroke_tag = NULL; + fz_xml *opacity_mask_tag = NULL; + + char *fill_opacity_att = NULL; + char *stroke_opacity_att = NULL; + + char *stroke_dash_array_att; + char *stroke_dash_cap_att; + char *stroke_dash_offset_att; + char *stroke_end_line_cap_att; + char *stroke_start_line_cap_att; + char *stroke_line_join_att; + char *stroke_miter_limit_att; + char *stroke_thickness_att; + char *navigate_uri_att; + + fz_stroke_state *stroke = NULL; + fz_matrix transform; + float samples[32]; + fz_colorspace *colorspace; + fz_path *path = NULL; + fz_path *stroke_path = NULL; + fz_rect area; + int fill_rule; + int dash_len = 0; + fz_matrix local_ctm; + + /* + * Extract attributes and extended attributes. + */ + + transform_att = fz_xml_att(root, "RenderTransform"); + clip_att = fz_xml_att(root, "Clip"); + data_att = fz_xml_att(root, "Data"); + fill_att = fz_xml_att(root, "Fill"); + stroke_att = fz_xml_att(root, "Stroke"); + opacity_att = fz_xml_att(root, "Opacity"); + opacity_mask_att = fz_xml_att(root, "OpacityMask"); + + stroke_dash_array_att = fz_xml_att(root, "StrokeDashArray"); + stroke_dash_cap_att = fz_xml_att(root, "StrokeDashCap"); + stroke_dash_offset_att = fz_xml_att(root, "StrokeDashOffset"); + stroke_end_line_cap_att = fz_xml_att(root, "StrokeEndLineCap"); + stroke_start_line_cap_att = fz_xml_att(root, "StrokeStartLineCap"); + stroke_line_join_att = fz_xml_att(root, "StrokeLineJoin"); + stroke_miter_limit_att = fz_xml_att(root, "StrokeMiterLimit"); + stroke_thickness_att = fz_xml_att(root, "StrokeThickness"); + navigate_uri_att = fz_xml_att(root, "FixedPage.NavigateUri"); + + for (node = fz_xml_down(root); node; node = fz_xml_next(node)) + { + if (!strcmp(fz_xml_tag(node), "Path.RenderTransform")) + transform_tag = fz_xml_down(node); + if (!strcmp(fz_xml_tag(node), "Path.OpacityMask")) + opacity_mask_tag = fz_xml_down(node); + if (!strcmp(fz_xml_tag(node), "Path.Clip")) + clip_tag = fz_xml_down(node); + if (!strcmp(fz_xml_tag(node), "Path.Fill")) + fill_tag = fz_xml_down(node); + if (!strcmp(fz_xml_tag(node), "Path.Stroke")) + stroke_tag = fz_xml_down(node); + if (!strcmp(fz_xml_tag(node), "Path.Data")) + data_tag = fz_xml_down(node); + } + + fill_uri = base_uri; + stroke_uri = base_uri; + opacity_mask_uri = base_uri; + + xps_resolve_resource_reference(doc, dict, &data_att, &data_tag, NULL); + xps_resolve_resource_reference(doc, dict, &clip_att, &clip_tag, NULL); + xps_resolve_resource_reference(doc, dict, &transform_att, &transform_tag, NULL); + xps_resolve_resource_reference(doc, dict, &fill_att, &fill_tag, &fill_uri); + xps_resolve_resource_reference(doc, dict, &stroke_att, &stroke_tag, &stroke_uri); + xps_resolve_resource_reference(doc, dict, &opacity_mask_att, &opacity_mask_tag, &opacity_mask_uri); + + /* + * Act on the information we have gathered: + */ + + if (!data_att && !data_tag) + return; + + if (fill_tag && !strcmp(fz_xml_tag(fill_tag), "SolidColorBrush")) + { + fill_opacity_att = fz_xml_att(fill_tag, "Opacity"); + fill_att = fz_xml_att(fill_tag, "Color"); + fill_tag = NULL; + } + + if (stroke_tag && !strcmp(fz_xml_tag(stroke_tag), "SolidColorBrush")) + { + stroke_opacity_att = fz_xml_att(stroke_tag, "Opacity"); + stroke_att = fz_xml_att(stroke_tag, "Color"); + stroke_tag = NULL; + } + + if (stroke_att || stroke_tag) + { + if (stroke_dash_array_att) + { + char *s = stroke_dash_array_att; + + while (*s) + { + while (*s == ' ') + s++; + if (*s) /* needed in case of a space before the last quote */ + dash_len++; + + while (*s && *s != ' ') + s++; + } + } + stroke = fz_new_stroke_state_with_len(doc->ctx, dash_len); + stroke->start_cap = xps_parse_line_cap(stroke_start_line_cap_att); + stroke->dash_cap = xps_parse_line_cap(stroke_dash_cap_att); + stroke->end_cap = xps_parse_line_cap(stroke_end_line_cap_att); + + stroke->linejoin = FZ_LINEJOIN_MITER_XPS; + if (stroke_line_join_att) + { + if (!strcmp(stroke_line_join_att, "Miter")) stroke->linejoin = FZ_LINEJOIN_MITER_XPS; + if (!strcmp(stroke_line_join_att, "Round")) stroke->linejoin = FZ_LINEJOIN_ROUND; + if (!strcmp(stroke_line_join_att, "Bevel")) stroke->linejoin = FZ_LINEJOIN_BEVEL; + } + + stroke->miterlimit = 10; + if (stroke_miter_limit_att) + stroke->miterlimit = fz_atof(stroke_miter_limit_att); + + stroke->linewidth = 1; + if (stroke_thickness_att) + stroke->linewidth = fz_atof(stroke_thickness_att); + + stroke->dash_phase = 0; + stroke->dash_len = 0; + if (stroke_dash_array_att) + { + char *s = stroke_dash_array_att; + + if (stroke_dash_offset_att) + stroke->dash_phase = fz_atof(stroke_dash_offset_att) * stroke->linewidth; + + while (*s) + { + while (*s == ' ') + s++; + if (*s) /* needed in case of a space before the last quote */ + stroke->dash_list[stroke->dash_len++] = fz_atof(s) * stroke->linewidth; + while (*s && *s != ' ') + s++; + } + stroke->dash_len = dash_len; + } + } + + transform = fz_identity; + if (transform_att) + xps_parse_render_transform(doc, transform_att, &transform); + if (transform_tag) + xps_parse_matrix_transform(doc, transform_tag, &transform); + fz_concat(&local_ctm, &transform, ctm); + + if (clip_att || clip_tag) + xps_clip(doc, &local_ctm, dict, clip_att, clip_tag); + + fill_rule = 0; + if (data_att) + path = xps_parse_abbreviated_geometry(doc, data_att, &fill_rule); + else if (data_tag) + { + path = xps_parse_path_geometry(doc, dict, data_tag, 0, &fill_rule); + if (stroke_att || stroke_tag) + stroke_path = xps_parse_path_geometry(doc, dict, data_tag, 1, &fill_rule); + } + if (!stroke_path) + stroke_path = path; + + if (stroke_att || stroke_tag) + { + fz_bound_path(doc->ctx, stroke_path, stroke, &local_ctm, &area); + if (stroke_path != path && (fill_att || fill_tag)) { + fz_rect bounds; + fz_bound_path(doc->ctx, path, NULL, &local_ctm, &bounds); + fz_union_rect(&area, &bounds); + } + } + else + fz_bound_path(doc->ctx, path, NULL, &local_ctm, &area); + + if (navigate_uri_att) + xps_add_link(doc, &area, base_uri, navigate_uri_att); + + xps_begin_opacity(doc, &local_ctm, &area, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); + + if (fill_att) + { + xps_parse_color(doc, base_uri, fill_att, &colorspace, samples); + if (fill_opacity_att) + samples[0] *= fz_atof(fill_opacity_att); + xps_set_color(doc, colorspace, samples); + + fz_fill_path(doc->dev, path, fill_rule == 0, &local_ctm, + doc->colorspace, doc->color, doc->alpha); + } + + if (fill_tag) + { + fz_clip_path(doc->dev, path, NULL, fill_rule == 0, &local_ctm); + xps_parse_brush(doc, &local_ctm, &area, fill_uri, dict, fill_tag); + fz_pop_clip(doc->dev); + } + + if (stroke_att) + { + xps_parse_color(doc, base_uri, stroke_att, &colorspace, samples); + if (stroke_opacity_att) + samples[0] *= fz_atof(stroke_opacity_att); + xps_set_color(doc, colorspace, samples); + + fz_stroke_path(doc->dev, stroke_path, stroke, &local_ctm, + doc->colorspace, doc->color, doc->alpha); + } + + if (stroke_tag) + { + fz_clip_stroke_path(doc->dev, stroke_path, NULL, stroke, &local_ctm); + xps_parse_brush(doc, &local_ctm, &area, stroke_uri, dict, stroke_tag); + fz_pop_clip(doc->dev); + } + + xps_end_opacity(doc, opacity_mask_uri, dict, opacity_att, opacity_mask_tag); + + if (stroke_path != path) + fz_free_path(doc->ctx, stroke_path); + fz_free_path(doc->ctx, path); + path = NULL; + fz_drop_stroke_state(doc->ctx, stroke); + + if (clip_att || clip_tag) + fz_pop_clip(doc->dev); +} |