summaryrefslogtreecommitdiff
path: root/source/svg/svg-run.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/svg/svg-run.c')
-rw-r--r--source/svg/svg-run.c990
1 files changed, 990 insertions, 0 deletions
diff --git a/source/svg/svg-run.c b/source/svg/svg-run.c
new file mode 100644
index 00000000..85f6d623
--- /dev/null
+++ b/source/svg/svg-run.c
@@ -0,0 +1,990 @@
+#include "mupdf/svg.h"
+
+/* default page size */
+#define DEF_WIDTH 12
+#define DEF_HEIGHT 792
+#define DEF_FONTSIZE 12
+
+typedef struct svg_state_s svg_state;
+
+struct svg_state_s
+{
+ fz_matrix transform;
+ fz_stroke_state stroke;
+
+ float viewport_w, viewport_h;
+ float viewbox_w, viewbox_h, viewbox_size;
+ float fontsize;
+
+ float opacity;
+
+ int fill_rule;
+ int fill_is_set;
+ float fill_color[3];
+ float fill_opacity;
+
+ int stroke_is_set;
+ float stroke_color[3];
+ float stroke_opacity;
+};
+
+static void svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state);
+static void svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state);
+
+static void svg_fill(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
+{
+ float opacity = state->opacity * state->fill_opacity;
+ fz_fill_path(ctx, dev, path, state->fill_rule, &state->transform, fz_device_rgb(ctx), state->fill_color, opacity);
+}
+
+static void svg_stroke(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
+{
+ float opacity = state->opacity * state->stroke_opacity;
+ fz_stroke_path(ctx, dev, path, &state->stroke, &state->transform, fz_device_rgb(ctx), state->stroke_color, opacity);
+}
+
+static void svg_draw_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_path *path, svg_state *state)
+{
+ if (state->fill_is_set)
+ svg_fill(ctx, dev, doc, path, state);
+ if (state->stroke_is_set)
+ svg_stroke(ctx, dev, doc, path, state);
+}
+
+static void
+svg_run_rect(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
+{
+ svg_state local_state = *inherit_state;
+
+ char *x_att = fz_xml_att(node, "x");
+ char *y_att = fz_xml_att(node, "y");
+ char *w_att = fz_xml_att(node, "width");
+ char *h_att = fz_xml_att(node, "height");
+ char *rx_att = fz_xml_att(node, "rx");
+ char *ry_att = fz_xml_att(node, "ry");
+
+ float x = 0;
+ float y = 0;
+ float w = 0;
+ float h = 0;
+ float rx = 0;
+ float ry = 0;
+
+ fz_path *path;
+
+ svg_parse_common(ctx, doc, node, &local_state);
+
+ if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
+ if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
+ if (w_att) w = svg_parse_length(w_att, local_state.viewbox_w, local_state.fontsize);
+ if (h_att) h = svg_parse_length(h_att, local_state.viewbox_h, local_state.fontsize);
+ if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize);
+ if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize);
+
+ if (rx_att && !ry_att)
+ ry = rx;
+ if (ry_att && !rx_att)
+ rx = ry;
+ if (rx > w * 0.5)
+ rx = w * 0.5;
+ if (ry > h * 0.5)
+ ry = h * 0.5;
+
+ if (w <= 0 || h <= 0)
+ return;
+
+ /* TODO: we need elliptical arcs to draw rounded corners */
+
+ path = fz_new_path(ctx);
+ fz_moveto(ctx, path, x, y);
+ fz_lineto(ctx, path, x + w, y);
+ fz_lineto(ctx, path, x + w, y + h);
+ fz_lineto(ctx, path, x, y + h);
+ fz_closepath(ctx, path);
+
+ svg_draw_path(ctx, dev, doc, path, &local_state);
+
+ fz_drop_path(ctx, path);
+}
+
+static void
+svg_run_circle(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
+{
+ svg_state local_state = *inherit_state;
+
+ char *cx_att = fz_xml_att(node, "cx");
+ char *cy_att = fz_xml_att(node, "cy");
+ char *r_att = fz_xml_att(node, "r");
+
+ float cx = 0;
+ float cy = 0;
+ float r = 0;
+ fz_path *path;
+
+ svg_parse_common(ctx, doc, node, &local_state);
+
+ if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize);
+ if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize);
+ if (r_att) r = svg_parse_length(r_att, local_state.viewbox_size, 12);
+
+ if (r <= 0)
+ return;
+
+ path = fz_new_path(ctx);
+ // FIXME!
+ //fz_moveto(ctx, path, cx + r, cy);
+ //fz_arcn(ctx, path, cx, cy, r, 0, 90);
+ //fz_arcn(ctx, path, cx, cy, r, 90, 180);
+ //fz_arcn(ctx, path, cx, cy, r, 180, 270);
+ //fz_arcn(ctx, path, cx, cy, r, 270, 360);
+ //fz_closepath(ctx, path);
+ svg_draw_path(ctx, dev, doc, path, &local_state);
+ fz_drop_path(ctx, path);
+}
+
+static void
+svg_run_ellipse(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
+{
+ svg_state local_state = *inherit_state;
+
+ char *cx_att = fz_xml_att(node, "cx");
+ char *cy_att = fz_xml_att(node, "cy");
+ char *rx_att = fz_xml_att(node, "rx");
+ char *ry_att = fz_xml_att(node, "ry");
+
+ float cx = 0;
+ float cy = 0;
+ float rx = 0;
+ float ry = 0;
+
+ fz_path *path;
+
+ svg_parse_common(ctx, doc, node, &local_state);
+
+ if (cx_att) cx = svg_parse_length(cx_att, local_state.viewbox_w, local_state.fontsize);
+ if (cy_att) cy = svg_parse_length(cy_att, local_state.viewbox_h, local_state.fontsize);
+ if (rx_att) rx = svg_parse_length(rx_att, local_state.viewbox_w, local_state.fontsize);
+ if (ry_att) ry = svg_parse_length(ry_att, local_state.viewbox_h, local_state.fontsize);
+
+ if (rx <= 0 || ry <= 0)
+ return;
+
+ path = fz_new_path(ctx);
+ /* TODO: we need elliptic arcs */
+ // TODO: arc...
+ svg_draw_path(ctx, dev, doc, path, &local_state);
+ fz_drop_path(ctx, path);
+}
+
+static void
+svg_run_line(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
+{
+ svg_state local_state = *inherit_state;
+
+ char *x1_att = fz_xml_att(node, "x1");
+ char *y1_att = fz_xml_att(node, "y1");
+ char *x2_att = fz_xml_att(node, "x2");
+ char *y2_att = fz_xml_att(node, "y2");
+
+ float x1 = 0;
+ float y1 = 0;
+ float x2 = 0;
+ float y2 = 0;
+
+ svg_parse_common(ctx, doc, node, &local_state);
+
+ if (x1_att) x1 = svg_parse_length(x1_att, local_state.viewbox_w, local_state.fontsize);
+ if (y1_att) y1 = svg_parse_length(y1_att, local_state.viewbox_h, local_state.fontsize);
+ if (x2_att) x2 = svg_parse_length(x2_att, local_state.viewbox_w, local_state.fontsize);
+ if (y2_att) y2 = svg_parse_length(y2_att, local_state.viewbox_h, local_state.fontsize);
+
+ if (local_state.stroke_is_set)
+ {
+ fz_path *path = fz_new_path(ctx);
+ fz_moveto(ctx, path, x1, y1);
+ fz_lineto(ctx, path, x2, y2);
+ svg_stroke(ctx, dev, doc, path, &local_state);
+ fz_drop_path(ctx, path);
+ }
+}
+
+static fz_path *
+svg_parse_polygon_imp(fz_context *ctx, svg_document *doc, fz_xml *node, int doclose)
+{
+ fz_path *path;
+
+ const char *str = fz_xml_att(node, "points");
+ float number;
+ float args[2];
+ int nargs;
+ int isfirst;
+
+ if (!str)
+ return NULL;
+
+ isfirst = 1;
+ nargs = 0;
+
+ path = fz_new_path(ctx);
+
+ while (*str)
+ {
+ while (svg_is_whitespace_or_comma(*str))
+ str ++;
+
+ if (svg_is_digit(*str))
+ {
+ str = svg_lex_number(&number, str);
+ args[nargs++] = number;
+ }
+
+ if (nargs == 2)
+ {
+ if (isfirst)
+ {
+ fz_moveto(ctx, path, args[0], args[1]);
+ isfirst = 0;
+ }
+ else
+ {
+ fz_lineto(ctx, path, args[0], args[1]);
+ }
+ nargs = 0;
+ }
+ }
+
+ return path;
+}
+
+static void
+svg_run_polyline(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
+{
+ svg_state local_state = *inherit_state;
+
+ svg_parse_common(ctx, doc, node, &local_state);
+
+ if (local_state.stroke_is_set)
+ {
+ fz_path *path = svg_parse_polygon_imp(ctx, doc, node, 0);
+ svg_stroke(ctx, dev, doc, path, &local_state);
+ fz_drop_path(ctx, path);
+ }
+}
+
+static void
+svg_run_polygon(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
+{
+ svg_state local_state = *inherit_state;
+ fz_path *path;
+
+ svg_parse_common(ctx, doc, node, &local_state);
+
+ path = svg_parse_polygon_imp(ctx, doc, node, 1);
+ svg_draw_path(ctx, dev, doc, path, &local_state);
+ fz_drop_path(ctx, path);
+}
+
+static fz_path *
+svg_parse_path_data(fz_context *ctx, svg_document *doc, const char *str)
+{
+ fz_path *path = fz_new_path(ctx);
+
+ fz_point p;
+ float x1, y1, x2, y2;
+
+ int cmd;
+ float number;
+ float args[6];
+ int nargs;
+
+ /* saved control point for smooth curves */
+ int reset_smooth = 1;
+ float smooth_x = 0.0;
+ float smooth_y = 0.0;
+
+ cmd = 0;
+ nargs = 0;
+
+ fz_try(ctx)
+ {
+ fz_moveto(ctx, path, 0.0, 0.0); /* for the case of opening 'm' */
+
+ while (*str)
+ {
+ while (svg_is_whitespace_or_comma(*str))
+ str ++;
+
+ if (svg_is_digit(*str))
+ {
+ str = svg_lex_number(&number, str);
+ if (nargs == 6)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "stack overflow in path data");
+ args[nargs++] = number;
+ }
+ else if (svg_is_alpha(*str))
+ {
+ if (nargs != 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in path data (wrong number of parameters to '%c')", cmd);
+ cmd = *str++;
+ }
+ else if (*str == 0)
+ {
+ break;
+ }
+ else
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in path data: '%c'", *str);
+ }
+
+ if (reset_smooth)
+ {
+ smooth_x = 0.0;
+ smooth_y = 0.0;
+ }
+
+ reset_smooth = 1;
+
+ switch (cmd)
+ {
+ case 'M':
+ if (nargs == 2)
+ {
+ fz_moveto(ctx, path, args[0], args[1]);
+ nargs = 0;
+ cmd = 'L'; /* implicit lineto after */
+ }
+ break;
+
+ case 'm':
+ if (nargs == 2)
+ {
+ p = fz_currentpoint(ctx, path);
+ fz_moveto(ctx, path, p.x + args[0], p.y + args[1]);
+ nargs = 0;
+ cmd = 'l'; /* implicit lineto after */
+ }
+ break;
+
+ case 'Z':
+ case 'z':
+ if (nargs == 0)
+ {
+ fz_closepath(ctx, path);
+ }
+ break;
+
+ case 'L':
+ if (nargs == 2)
+ {
+ fz_lineto(ctx, path, args[0], args[1]);
+ nargs = 0;
+ }
+ break;
+
+ case 'l':
+ if (nargs == 2)
+ {
+ p = fz_currentpoint(ctx, path);
+ fz_lineto(ctx, path, p.x + args[0], p.y + args[1]);
+ nargs = 0;
+ }
+ break;
+
+ case 'H':
+ if (nargs == 1)
+ {
+ p = fz_currentpoint(ctx, path);
+ fz_lineto(ctx, path, args[0], p.y);
+ nargs = 0;
+ }
+ break;
+
+ case 'h':
+ if (nargs == 1)
+ {
+ p = fz_currentpoint(ctx, path);
+ fz_lineto(ctx, path, p.x + args[0], p.y);
+ nargs = 0;
+ }
+ break;
+
+ case 'V':
+ if (nargs == 1)
+ {
+ p = fz_currentpoint(ctx, path);
+ fz_lineto(ctx, path, p.x, args[0]);
+ nargs = 0;
+ }
+ break;
+
+ case 'v':
+ if (nargs == 1)
+ {
+ p = fz_currentpoint(ctx, path);
+ fz_lineto(ctx, path, p.x, p.y + args[0]);
+ nargs = 0;
+ }
+ break;
+
+ case 'C':
+ reset_smooth = 0;
+ if (nargs == 6)
+ {
+ fz_curveto(ctx, path, args[0], args[1], args[2], args[3], args[4], args[5]);
+ smooth_x = args[4] - args[2];
+ smooth_y = args[5] - args[3];
+ nargs = 0;
+ }
+ break;
+
+ case 'c':
+ reset_smooth = 0;
+ if (nargs == 6)
+ {
+ p = fz_currentpoint(ctx, path);
+ fz_curveto(ctx, path,
+ p.x + args[0], p.y + args[1],
+ p.x + args[2], p.y + args[3],
+ p.x + args[4], p.y + args[5]);
+ smooth_x = args[4] - args[2];
+ smooth_y = args[5] - args[3];
+ nargs = 0;
+ }
+ break;
+
+ case 'S':
+ reset_smooth = 0;
+ if (nargs == 4)
+ {
+ p = fz_currentpoint(ctx, path);
+ fz_curveto(ctx, path,
+ p.x + smooth_x, p.y + smooth_y,
+ args[0], args[1],
+ args[2], args[3]);
+ smooth_x = args[2] - args[0];
+ smooth_y = args[3] - args[1];
+ nargs = 0;
+ }
+ break;
+
+ case 's':
+ reset_smooth = 0;
+ if (nargs == 4)
+ {
+ p = fz_currentpoint(ctx, path);
+ fz_curveto(ctx, path,
+ p.x + smooth_x, p.y + smooth_y,
+ p.x + args[0], p.y + args[1],
+ p.x + args[2], p.y + args[3]);
+ smooth_x = args[2] - args[0];
+ smooth_y = args[3] - args[1];
+ nargs = 0;
+ }
+ break;
+
+ case 'Q':
+ reset_smooth = 0;
+ if (nargs == 4)
+ {
+ p = fz_currentpoint(ctx, path);
+ x1 = args[0];
+ y1 = args[1];
+ x2 = args[2];
+ y2 = args[3];
+ fz_curveto(ctx, path,
+ (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
+ (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
+ x2, y2);
+ smooth_x = x2 - x1;
+ smooth_y = y2 - y1;
+ nargs = 0;
+ }
+ break;
+
+ case 'q':
+ reset_smooth = 0;
+ if (nargs == 4)
+ {
+ p = fz_currentpoint(ctx, path);
+ x1 = args[0] + p.x;
+ y1 = args[1] + p.y;
+ x2 = args[2] + p.x;
+ y2 = args[3] + p.y;
+ fz_curveto(ctx, path,
+ (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
+ (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
+ x2, y2);
+ smooth_x = x2 - x1;
+ smooth_y = y2 - y1;
+ nargs = 0;
+ }
+ break;
+
+ case 'T':
+ reset_smooth = 0;
+ if (nargs == 4)
+ {
+ p = fz_currentpoint(ctx, path);
+ x1 = p.x + smooth_x;
+ y1 = p.y + smooth_y;
+ x2 = args[0];
+ y2 = args[1];
+ fz_curveto(ctx, path,
+ (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
+ (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
+ x2, y2);
+ smooth_x = x2 - x1;
+ smooth_y = y2 - y1;
+ nargs = 0;
+ }
+ break;
+
+ case 't':
+ reset_smooth = 0;
+ if (nargs == 4)
+ {
+ p = fz_currentpoint(ctx, path);
+ x1 = p.x + smooth_x;
+ y1 = p.y + smooth_y;
+ x2 = args[0] + p.x;
+ y2 = args[1] + p.y;
+ fz_curveto(ctx, path,
+ (p.x + 2 * x1) / 3, (p.y + 2 * y1) / 3,
+ (x2 + 2 * x1) / 3, (y2 + 2 * y1) / 3,
+ x2, y2);
+ smooth_x = x2 - x1;
+ smooth_y = y2 - y1;
+ nargs = 0;
+ }
+ break;
+
+ case 0:
+ if (nargs != 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "path data must begin with a command");
+ break;
+
+ default:
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unrecognized command in path data: '%c'", cmd);
+ }
+ }
+ }
+ fz_catch(ctx)
+ {
+ fz_drop_path(ctx, path);
+ fz_rethrow(ctx);
+ }
+
+ return path;
+}
+
+static void
+svg_run_path(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *node, const svg_state *inherit_state)
+{
+ svg_state local_state = *inherit_state;
+
+ const char *d_att = fz_xml_att(node, "d");
+ /* unused: char *path_length_att = fz_xml_att(node, "pathLength"); */
+
+ svg_parse_common(ctx, doc, node, &local_state);
+
+ if (d_att)
+ {
+ fz_path *path = svg_parse_path_data(ctx, doc, d_att);
+ svg_draw_path(ctx, dev, doc, path, &local_state);
+ fz_drop_path(ctx, path);
+ }
+}
+
+/* svg, symbol, image, foreignObject establish new viewports */
+void
+svg_parse_viewport(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
+{
+ //fz_matrix *transform = &state->transform;
+
+ char *x_att = fz_xml_att(node, "x");
+ char *y_att = fz_xml_att(node, "y");
+ char *w_att = fz_xml_att(node, "width");
+ char *h_att = fz_xml_att(node, "height");
+
+ float x = 0;
+ float y = 0;
+ float w = state->viewport_w;
+ float h = state->viewport_h;
+
+ if (x_att) x = svg_parse_length(x_att, state->viewbox_w, state->fontsize);
+ if (y_att) y = svg_parse_length(y_att, state->viewbox_h, state->fontsize);
+ if (w_att) w = svg_parse_length(w_att, state->viewbox_w, state->fontsize);
+ if (h_att) h = svg_parse_length(h_att, state->viewbox_h, state->fontsize);
+
+ /* TODO: new transform */
+ printf("push viewport: %g %g %g %g\n", x, y, w, h);
+
+ state->viewport_w = w;
+ state->viewport_h = h;
+}
+
+/* svg, symbol, image, foreignObject plus marker, pattern, view can use viewBox to set the transform */
+void
+svg_parse_viewbox(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
+{
+ //fz_matrix *transform = &state->transform;
+ //float port_w = state->viewport_w;
+ //float port_h = state->viewport_h;
+ float min_x, min_y, box_w, box_h;
+
+ char *viewbox_att = fz_xml_att(node, "viewBox");
+ if (viewbox_att)
+ {
+ sscanf(viewbox_att, "%g %g %g %g", &min_x, &min_y, &box_w, &box_h);
+
+ /* scale and translate to fit [x y w h] to [0 0 viewport.w viewport.h] */
+ printf("push viewbox: %g %g %g %g\n", min_x, min_y, box_w, box_h);
+ }
+}
+
+/* parse transform and presentation attributes */
+void
+svg_parse_common(fz_context *ctx, svg_document *doc, fz_xml *node, svg_state *state)
+{
+ fz_stroke_state *stroke = &state->stroke;
+ fz_matrix *transform = &state->transform;
+
+ char *transform_att = fz_xml_att(node, "transform");
+
+ char *font_size_att = fz_xml_att(node, "font-size");
+ // TODO: all font stuff
+
+ // TODO: clip, clip-path, clip-rule
+
+ char *opacity_att = fz_xml_att(node, "opacity");
+
+ char *fill_att = fz_xml_att(node, "fill");
+ char *fill_rule_att = fz_xml_att(node, "fill-rule");
+ char *fill_opacity_att = fz_xml_att(node, "fill-opacity");
+
+ char *stroke_att = fz_xml_att(node, "stroke");
+ char *stroke_opacity_att = fz_xml_att(node, "stroke-opacity");
+ char *stroke_width_att = fz_xml_att(node, "stroke-width");
+ char *stroke_linecap_att = fz_xml_att(node, "stroke-linecap");
+ char *stroke_linejoin_att = fz_xml_att(node, "stroke-linejoin");
+ char *stroke_miterlimit_att = fz_xml_att(node, "stroke-miterlimit");
+ // TODO: stroke-dasharray, stroke-dashoffset
+
+ // TODO: marker, marker-start, marker-mid, marker-end
+
+ // TODO: overflow
+ // TODO: mask
+
+ if (transform_att)
+ {
+ svg_parse_transform(ctx, doc, transform_att, transform);
+ }
+
+ if (font_size_att)
+ {
+ state->fontsize = svg_parse_length(font_size_att, state->fontsize, state->fontsize);
+ }
+
+ if (opacity_att)
+ {
+ state->opacity = svg_parse_number(fill_opacity_att, 0, 1, state->opacity);
+ }
+
+ if (fill_att)
+ {
+ if (!strcmp(fill_att, "none"))
+ {
+ state->fill_is_set = 0;
+ }
+ else
+ {
+ state->fill_is_set = 1;
+ svg_parse_color(ctx, doc, fill_att, state->fill_color);
+ }
+ }
+
+ if (fill_opacity_att)
+ state->fill_opacity = svg_parse_number(fill_opacity_att, 0, 1, state->fill_opacity);
+
+ if (fill_rule_att)
+ {
+ if (!strcmp(fill_rule_att, "nonzero"))
+ state->fill_rule = 1;
+ if (!strcmp(fill_rule_att, "evenodd"))
+ state->fill_rule = 0;
+ }
+
+ if (stroke_att)
+ {
+ if (!strcmp(stroke_att, "none"))
+ {
+ state->stroke_is_set = 0;
+ }
+ else
+ {
+ state->stroke_is_set = 1;
+ svg_parse_color(ctx, doc, stroke_att, state->stroke_color);
+ }
+ }
+
+ if (stroke_opacity_att)
+ state->stroke_opacity = svg_parse_number(stroke_opacity_att, 0, 1, state->stroke_opacity);
+
+ if (stroke_width_att)
+ {
+ if (!strcmp(stroke_width_att, "inherit"))
+ ;
+ else
+ stroke->linewidth = svg_parse_length(stroke_width_att, state->viewbox_size, state->fontsize);
+ }
+ else
+ {
+ stroke->linewidth = 1;
+ }
+
+ if (stroke_linecap_att)
+ {
+ if (!strcmp(stroke_linecap_att, "butt"))
+ stroke->start_cap = FZ_LINECAP_BUTT;
+ if (!strcmp(stroke_linecap_att, "round"))
+ stroke->start_cap = FZ_LINECAP_ROUND;
+ if (!strcmp(stroke_linecap_att, "square"))
+ stroke->start_cap = FZ_LINECAP_SQUARE;
+ }
+ else
+ {
+ stroke->start_cap = FZ_LINECAP_BUTT;
+ }
+
+ stroke->dash_cap = stroke->start_cap;
+ stroke->end_cap = stroke->start_cap;
+
+ if (stroke_linejoin_att)
+ {
+ if (!strcmp(stroke_linejoin_att, "miter"))
+ stroke->linejoin = FZ_LINEJOIN_MITER;
+ if (!strcmp(stroke_linejoin_att, "round"))
+ stroke->linejoin = FZ_LINEJOIN_ROUND;
+ if (!strcmp(stroke_linejoin_att, "bevel"))
+ stroke->linejoin = FZ_LINEJOIN_BEVEL;
+ }
+ else
+ {
+ stroke->linejoin = FZ_LINEJOIN_MITER;
+ }
+
+ if (stroke_miterlimit_att)
+ {
+ if (!strcmp(stroke_miterlimit_att, "inherit"))
+ ;
+ else
+ stroke->miterlimit = svg_parse_length(stroke_miterlimit_att, state->viewbox_size, state->fontsize);
+ }
+ else
+ {
+ stroke->miterlimit = 4.0;
+ }
+}
+
+static void
+svg_run_svg(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
+{
+ svg_state local_state = *inherit_state;
+ fz_xml *node;
+
+ svg_parse_viewport(ctx, doc, root, &local_state);
+ svg_parse_viewbox(ctx, doc, root, &local_state);
+ svg_parse_common(ctx, doc, root, &local_state);
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ svg_run_element(ctx, dev, doc, node, &local_state);
+}
+
+static void
+svg_run_g(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
+{
+ svg_state local_state = *inherit_state;
+ fz_xml *node;
+
+ svg_parse_common(ctx, doc, root, &local_state);
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ svg_run_element(ctx, dev, doc, node, &local_state);
+}
+
+static void
+svg_run_use_symbol(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *use, fz_xml *symbol, const svg_state *inherit_state)
+{
+ svg_state local_state = *inherit_state;
+ fz_xml *node;
+
+ svg_parse_viewport(ctx, doc, use, &local_state);
+ svg_parse_viewbox(ctx, doc, use, &local_state);
+ svg_parse_common(ctx, doc, use, &local_state);
+
+ for (node = fz_xml_down(symbol); node; node = fz_xml_next(node))
+ svg_run_element(ctx, dev, doc, node, &local_state);
+}
+
+static void
+svg_run_use(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *inherit_state)
+{
+ svg_state local_state = *inherit_state;
+
+ char *xlink_href_att = fz_xml_att(root, "xlink:href");
+ char *x_att = fz_xml_att(root, "x");
+ char *y_att = fz_xml_att(root, "y");
+
+ float x = 0;
+ float y = 0;
+
+ svg_parse_common(ctx, doc, root, &local_state);
+ if (x_att) x = svg_parse_length(x_att, local_state.viewbox_w, local_state.fontsize);
+ if (y_att) y = svg_parse_length(y_att, local_state.viewbox_h, local_state.fontsize);
+
+ fz_pre_translate(&local_state.transform, x, y);
+
+ if (xlink_href_att && xlink_href_att[0] == '#')
+ {
+ fz_xml *linked = fz_tree_lookup(ctx, doc->idmap, xlink_href_att + 1);
+ if (linked)
+ {
+ if (!strcmp(fz_xml_tag(linked), "symbol"))
+ svg_run_use_symbol(ctx, dev, doc, root, linked, &local_state);
+ else
+ svg_run_element(ctx, dev, doc, linked, &local_state);
+ return;
+ }
+ }
+
+ fz_warn(ctx, "svg: cannot find linked symbol");
+}
+
+static void
+svg_run_element(fz_context *ctx, fz_device *dev, svg_document *doc, fz_xml *root, const svg_state *state)
+{
+ char *tag = fz_xml_tag(root);
+
+ if (!strcmp(tag, "svg"))
+ svg_run_svg(ctx, dev, doc, root, state);
+
+ else if (!strcmp(tag, "g"))
+ svg_run_g(ctx, dev, doc, root, state);
+
+ else if (!strcmp(tag, "title"))
+ ;
+ else if (!strcmp(tag, "desc"))
+ ;
+
+ else if (!strcmp(tag, "defs"))
+ ;
+ else if (!strcmp(tag, "symbol"))
+ ;
+
+ else if (!strcmp(tag, "use"))
+ svg_run_use(ctx, dev, doc, root, state);
+
+ else if (!strcmp(tag, "path"))
+ svg_run_path(ctx, dev, doc, root, state);
+ else if (!strcmp(tag, "rect"))
+ svg_run_rect(ctx, dev, doc, root, state);
+ else if (!strcmp(tag, "circle"))
+ svg_run_circle(ctx, dev, doc, root, state);
+ else if (!strcmp(tag, "ellipse"))
+ svg_run_ellipse(ctx, dev, doc, root, state);
+ else if (!strcmp(tag, "line"))
+ svg_run_line(ctx, dev, doc, root, state);
+ else if (!strcmp(tag, "polyline"))
+ svg_run_polyline(ctx, dev, doc, root, state);
+ else if (!strcmp(tag, "polygon"))
+ svg_run_polygon(ctx, dev, doc, root, state);
+
+#if 0
+ else if (!strcmp(tag, "image"))
+ svg_parse_image(ctx, doc, root);
+ else if (!strcmp(tag, "text"))
+ svg_run_text(ctx, dev, doc, root);
+ else if (!strcmp(tag, "tspan"))
+ svg_run_text_span(ctx, dev, doc, root);
+ else if (!strcmp(tag, "tref"))
+ svg_run_text_ref(ctx, dev, doc, root);
+ else if (!strcmp(tag, "textPath"))
+ svg_run_text_path(ctx, dev, doc, root);
+#endif
+
+ else
+ {
+ /* debug print unrecognized tags */
+ fz_debug_xml(root, 0);
+ }
+}
+
+void
+svg_parse_document_bounds(fz_context *ctx, svg_document *doc, fz_xml *root)
+{
+ char *version_att;
+ char *w_att;
+ char *h_att;
+ int version;
+
+ if (!fz_xml_is_tag(root, "svg"))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "expected svg element (found %s)", fz_xml_tag(root));
+
+ version_att = fz_xml_att(root, "version");
+ w_att = fz_xml_att(root, "width");
+ h_att = fz_xml_att(root, "height");
+
+ version = 10;
+ if (version_att)
+ version = atof(version_att) * 10;
+
+ if (version > 12)
+ fz_warn(ctx, "svg document version is newer than we support");
+
+ doc->width = DEF_WIDTH;
+ if (w_att)
+ doc->width = svg_parse_length(w_att, doc->width, DEF_FONTSIZE);
+
+ doc->height = DEF_HEIGHT;
+ if (h_att)
+ doc->height = svg_parse_length(h_att, doc->height, DEF_FONTSIZE);
+}
+
+void
+svg_run_document(fz_context *ctx, svg_document *doc, fz_xml *root, fz_device *dev, const fz_matrix *ctm)
+{
+ svg_state state;
+
+ svg_parse_document_bounds(ctx, doc, root);
+
+ /* Initial graphics state */
+ state.transform = *ctm;
+ state.stroke = fz_default_stroke_state;
+
+ state.viewport_w = DEF_WIDTH;
+ state.viewport_h = DEF_HEIGHT;
+
+ state.viewbox_w = DEF_WIDTH;
+ state.viewbox_h = DEF_HEIGHT;
+ state.viewbox_size = sqrtf(DEF_WIDTH*DEF_WIDTH + DEF_HEIGHT*DEF_HEIGHT) / sqrtf(2);
+
+ state.fontsize = 12;
+
+ state.opacity = 1;
+
+ state.fill_rule = 0;
+
+ state.fill_is_set = 1;
+ state.fill_color[0] = 0;
+ state.fill_color[1] = 0;
+ state.fill_color[2] = 0;
+ state.fill_opacity = 1;
+
+ state.stroke_is_set = 0;
+ state.stroke_color[0] = 0;
+ state.stroke_color[1] = 0;
+ state.stroke_color[2] = 0;
+ state.stroke_opacity = 1;
+
+ svg_run_svg(ctx, dev, doc, root, &state);
+}