summaryrefslogtreecommitdiff
path: root/source/svg
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2016-04-07 13:36:29 +0200
committerTor Andersson <tor.andersson@artifex.com>2016-04-26 15:12:57 +0200
commitd8ebcc7b59fc20b1dd3ea7887c6d3ad2859b3698 (patch)
tree74dc67e9c984bde63c4da7389d89b00e30b21182 /source/svg
parent1b16995f277aefda88957e31c00d963fc9a59d7a (diff)
downloadmupdf-d8ebcc7b59fc20b1dd3ea7887c6d3ad2859b3698.tar.xz
svg: Add SVG parser.
svg: Implement graphics state stack. svg: Use idmap for symbol and use elements. svg: Put viewport and viewBox in state stack. svg: Rebase to version 1.9 master.
Diffstat (limited to 'source/svg')
-rw-r--r--source/svg/svg-color.c273
-rw-r--r--source/svg/svg-doc.c143
-rw-r--r--source/svg/svg-parse.c276
-rw-r--r--source/svg/svg-run.c990
4 files changed, 1682 insertions, 0 deletions
diff --git a/source/svg/svg-color.c b/source/svg/svg-color.c
new file mode 100644
index 00000000..3fef124e
--- /dev/null
+++ b/source/svg/svg-color.c
@@ -0,0 +1,273 @@
+#include "mupdf/svg.h"
+
+/* Color keywords (white, blue, fuchsia)
+ * System color keywords (ActiveBorder, ButtonFace -- need to find reasonable defaults)
+ * #fb0 (expand to #ffbb00)
+ * #ffbb00
+ * rgb(255,255,255)
+ * rgb(100%,100%,100%)
+ *
+ * "red icc-color(profileName,255,0,0)" (not going to support for now)
+ */
+
+struct
+{
+ const char *name;
+ float red, green, blue;
+}
+svg_predefined_colors[] =
+{
+ { "aliceblue", 240, 248, 255 },
+ { "antiquewhite", 250, 235, 215 },
+ { "aqua", 0, 255, 255 },
+ { "aquamarine", 127, 255, 212 },
+ { "azure", 240, 255, 255 },
+ { "beige", 245, 245, 220 },
+ { "bisque", 255, 228, 196 },
+ { "black", 0, 0, 0 },
+ { "blanchedalmond", 255, 235, 205 },
+ { "blue", 0, 0, 255 },
+ { "blueviolet", 138, 43, 226 },
+ { "brown", 165, 42, 42 },
+ { "burlywood", 222, 184, 135 },
+ { "cadetblue", 95, 158, 160 },
+ { "chartreuse", 127, 255, 0 },
+ { "chocolate", 210, 105, 30 },
+ { "coral", 255, 127, 80 },
+ { "cornflowerblue", 100, 149, 237 },
+ { "cornsilk", 255, 248, 220 },
+ { "crimson", 220, 20, 60 },
+ { "cyan", 0, 255, 255 },
+ { "darkblue", 0, 0, 139 },
+ { "darkcyan", 0, 139, 139 },
+ { "darkgoldenrod", 184, 134, 11 },
+ { "darkgray", 169, 169, 169 },
+ { "darkgreen", 0, 100, 0 },
+ { "darkgrey", 169, 169, 169 },
+ { "darkkhaki", 189, 183, 107 },
+ { "darkmagenta", 139, 0, 139 },
+ { "darkolivegreen", 85, 107, 47 },
+ { "darkorange", 255, 140, 0 },
+ { "darkorchid", 153, 50, 204 },
+ { "darkred", 139, 0, 0 },
+ { "darksalmon", 233, 150, 122 },
+ { "darkseagreen", 143, 188, 143 },
+ { "darkslateblue", 72, 61, 139 },
+ { "darkslategray", 47, 79, 79 },
+ { "darkslategrey", 47, 79, 79 },
+ { "darkturquoise", 0, 206, 209 },
+ { "darkviolet", 148, 0, 211 },
+ { "deeppink", 255, 20, 147 },
+ { "deepskyblue", 0, 191, 255 },
+ { "dimgray", 105, 105, 105 },
+ { "dimgrey", 105, 105, 105 },
+ { "dodgerblue", 30, 144, 255 },
+ { "firebrick", 178, 34, 34 },
+ { "floralwhite", 255, 250, 240 },
+ { "forestgreen", 34, 139, 34 },
+ { "fuchsia", 255, 0, 255 },
+ { "gainsboro", 220, 220, 220 },
+ { "ghostwhite", 248, 248, 255 },
+ { "gold", 255, 215, 0 },
+ { "goldenrod", 218, 165, 32 },
+ { "gray", 128, 128, 128 },
+ { "green", 0, 128, 0 },
+ { "greenyellow", 173, 255, 47 },
+ { "grey", 128, 128, 128 },
+ { "honeydew", 240, 255, 240 },
+ { "hotpink", 255, 105, 180 },
+ { "indianred", 205, 92, 92 },
+ { "indigo", 75, 0, 130 },
+ { "ivory", 255, 255, 240 },
+ { "khaki", 240, 230, 140 },
+ { "lavender", 230, 230, 250 },
+ { "lavenderblush", 255, 240, 245 },
+ { "lawngreen", 124, 252, 0 },
+ { "lemonchiffon", 255, 250, 205 },
+ { "lightblue", 173, 216, 230 },
+ { "lightcoral", 240, 128, 128 },
+ { "lightcyan", 224, 255, 255 },
+ { "lightgoldenrodyellow", 250, 250, 210 },
+ { "lightgray", 211, 211, 211 },
+ { "lightgreen", 144, 238, 144 },
+ { "lightgrey", 211, 211, 211 },
+ { "lightpink", 255, 182, 193 },
+ { "lightsalmon", 255, 160, 122 },
+ { "lightseagreen", 32, 178, 170 },
+ { "lightskyblue", 135, 206, 250 },
+ { "lightslategray", 119, 136, 153 },
+ { "lightslategrey", 119, 136, 153 },
+ { "lightsteelblue", 176, 196, 222 },
+ { "lightyellow", 255, 255, 224 },
+ { "lime", 0, 255, 0 },
+ { "limegreen", 50, 205, 50 },
+ { "linen", 250, 240, 230 },
+ { "magenta", 255, 0, 255 },
+ { "maroon", 128, 0, 0 },
+ { "mediumaquamarine", 102, 205, 170 },
+ { "mediumblue", 0, 0, 205 },
+ { "mediumorchid", 186, 85, 211 },
+ { "mediumpurple", 147, 112, 219 },
+ { "mediumseagreen", 60, 179, 113 },
+ { "mediumslateblue", 123, 104, 238 },
+ { "mediumspringgreen", 0, 250, 154 },
+ { "mediumturquoise", 72, 209, 204 },
+ { "mediumvioletred", 199, 21, 133 },
+ { "midnightblue", 25, 25, 112 },
+ { "mintcream", 245, 255, 250 },
+ { "mistyrose", 255, 228, 225 },
+ { "moccasin", 255, 228, 181 },
+ { "navajowhite", 255, 222, 173 },
+ { "navy", 0, 0, 128 },
+ { "oldlace", 253, 245, 230 },
+ { "olive", 128, 128, 0 },
+ { "olivedrab", 107, 142, 35 },
+ { "orange", 255, 165, 0 },
+ { "orangered", 255, 69, 0 },
+ { "orchid", 218, 112, 214 },
+ { "palegoldenrod", 238, 232, 170 },
+ { "palegreen", 152, 251, 152 },
+ { "paleturquoise", 175, 238, 238 },
+ { "palevioletred", 219, 112, 147 },
+ { "papayawhip", 255, 239, 213 },
+ { "peachpuff", 255, 218, 185 },
+ { "peru", 205, 133, 63 },
+ { "pink", 255, 192, 203 },
+ { "plum", 221, 160, 221 },
+ { "powderblue", 176, 224, 230 },
+ { "purple", 128, 0, 128 },
+ { "red", 255, 0, 0 },
+ { "rosybrown", 188, 143, 143 },
+ { "royalblue", 65, 105, 225 },
+ { "saddlebrown", 139, 69, 19 },
+ { "salmon", 250, 128, 114 },
+ { "sandybrown", 244, 164, 96 },
+ { "seagreen", 46, 139, 87 },
+ { "seashell", 255, 245, 238 },
+ { "sienna", 160, 82, 45 },
+ { "silver", 192, 192, 192 },
+ { "skyblue", 135, 206, 235 },
+ { "slateblue", 106, 90, 205 },
+ { "slategray", 112, 128, 144 },
+ { "slategrey", 112, 128, 144 },
+ { "snow", 255, 250, 250 },
+ { "springgreen", 0, 255, 127 },
+ { "steelblue", 70, 130, 180 },
+ { "tan", 210, 180, 140 },
+ { "teal", 0, 128, 128 },
+ { "thistle", 216, 191, 216 },
+ { "tomato", 255, 99, 71 },
+ { "turquoise", 64, 224, 208 },
+ { "violet", 238, 130, 238 },
+ { "wheat", 245, 222, 179 },
+ { "white", 255, 255, 255 },
+ { "whitesmoke", 245, 245, 245 },
+ { "yellow", 255, 255, 0 },
+ { "yellowgreen", 154, 205, 50 },
+};
+
+static int unhex(int chr)
+{
+ const char *hextable = "0123456789abcdef";
+ return strchr(hextable, (chr|32)) - hextable;
+}
+
+void
+svg_parse_color(fz_context *ctx, svg_document *doc, char *str, float *rgb)
+{
+ int i, l, m, r, cmp;
+
+ rgb[0] = 0.0;
+ rgb[1] = 0.0;
+ rgb[2] = 0.0;
+
+ /* Crack hex-coded RGB */
+
+ if (str[0] == '#')
+ {
+ str ++;
+
+ if (strlen(str) == 3)
+ {
+ rgb[0] = (unhex(str[0]) * 16 + unhex(str[0])) / 255.0;
+ rgb[1] = (unhex(str[1]) * 16 + unhex(str[1])) / 255.0;
+ rgb[2] = (unhex(str[2]) * 16 + unhex(str[2])) / 255.0;
+ return;
+ }
+
+ if (strlen(str) == 6)
+ {
+ rgb[0] = (unhex(str[0]) * 16 + unhex(str[1])) / 255.0;
+ rgb[1] = (unhex(str[2]) * 16 + unhex(str[3])) / 255.0;
+ rgb[2] = (unhex(str[4]) * 16 + unhex(str[5])) / 255.0;
+ return;
+ }
+
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in color - wrong length of string after #");
+ }
+
+ /* rgb(X,Y,Z) -- whitespace allowed around numbers */
+
+ else if (strstr(str, "rgb("))
+ {
+ int numberlen = 0;
+ char numberbuf[50];
+
+ str = str + 4;
+
+ for (i = 0; i < 3; i++)
+ {
+ while (svg_is_whitespace_or_comma(*str))
+ str ++;
+
+ if (svg_is_digit(*str))
+ {
+ numberlen = 0;
+ while (svg_is_digit(*str) && numberlen < sizeof(numberbuf) - 1)
+ numberbuf[numberlen++] = *str++;
+ numberbuf[numberlen] = 0;
+
+ if (*str == '%')
+ {
+ str ++;
+ rgb[i] = atof(numberbuf) / 100.0;
+ }
+ else
+ {
+ rgb[i] = atof(numberbuf) / 255.0;
+ }
+ }
+ }
+
+ return;
+ }
+
+ /* TODO: parse icc-profile(X,Y,Z,W) syntax */
+
+ /* Search for a pre-defined color */
+
+ else
+ {
+ l = 0;
+ r = sizeof(svg_predefined_colors) / sizeof(svg_predefined_colors[0]);
+
+ while (l <= r)
+ {
+ m = (l + r) / 2;
+ cmp = strcmp(svg_predefined_colors[m].name, str);
+ if (cmp > 0)
+ r = m - 1;
+ else if (cmp < 0)
+ l = m + 1;
+ else
+ {
+ rgb[0] = svg_predefined_colors[m].red;
+ rgb[1] = svg_predefined_colors[m].green;
+ rgb[2] = svg_predefined_colors[m].blue;
+ return;
+ }
+ }
+ }
+
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot recognize color syntax: '%s'", str);
+}
diff --git a/source/svg/svg-doc.c b/source/svg/svg-doc.c
new file mode 100644
index 00000000..f08cceb2
--- /dev/null
+++ b/source/svg/svg-doc.c
@@ -0,0 +1,143 @@
+#include "mupdf/svg.h"
+
+typedef struct svg_page_s svg_page;
+
+struct svg_page_s
+{
+ fz_page super;
+ svg_document *doc;
+};
+
+static void
+svg_close_document(fz_context *ctx, fz_document *doc_)
+{
+ svg_document *doc = (svg_document*)doc_;
+ fz_drop_tree(ctx, doc->idmap, NULL);
+ fz_drop_xml(ctx, doc->root);
+ fz_free(ctx, doc);
+}
+
+static int
+svg_count_pages(fz_context *ctx, fz_document *doc_)
+{
+ return 1;
+}
+
+static fz_rect *
+svg_bound_page(fz_context *ctx, fz_page *page_, fz_rect *rect)
+{
+ svg_page *page = (svg_page*)page_;
+ svg_document *doc = page->doc;
+
+ svg_parse_document_bounds(ctx, doc, doc->root);
+
+ rect->x0 = 0;
+ rect->y0 = 0;
+ rect->x1 = doc->width;
+ rect->y1 = doc->height;
+ return rect;
+}
+
+static void
+svg_run_page(fz_context *ctx, fz_page *page_, fz_device *dev, const fz_matrix *ctm, fz_cookie *cookie)
+{
+ svg_page *page = (svg_page*)page_;
+ svg_document *doc = page->doc;
+ svg_run_document(ctx, doc, doc->root, dev, ctm);
+}
+
+static void
+svg_drop_page_imp(fz_context *ctx, fz_page *page_)
+{
+ /* nothing */
+}
+
+static fz_page *
+svg_load_page(fz_context *ctx, fz_document *doc_, int number)
+{
+ svg_document *doc = (svg_document*)doc_;
+ svg_page *page;
+
+ if (number != 0)
+ return NULL;
+
+ page = fz_new_page(ctx, sizeof *page);
+ page->super.bound_page = svg_bound_page;
+ page->super.run_page_contents = svg_run_page;
+ page->super.drop_page_imp = svg_drop_page_imp;
+ page->doc = doc;
+
+ return (fz_page*)page;
+}
+
+static void
+svg_build_id_map(fz_context *ctx, svg_document *doc, fz_xml *root)
+{
+ fz_xml *node;
+
+ char *id_att = fz_xml_att(root, "id");
+ if (id_att)
+ doc->idmap = fz_tree_insert(ctx, doc->idmap, id_att, root);
+
+ for (node = fz_xml_down(root); node; node = fz_xml_next(node))
+ svg_build_id_map(ctx, doc, node);
+}
+
+static fz_document *
+svg_open_document_with_stream(fz_context *ctx, fz_stream *file)
+{
+ svg_document *doc;
+ fz_buffer *buf;
+ fz_xml *root;
+
+ buf = fz_read_all(ctx, file, 0);
+ root = fz_parse_xml(ctx, buf->data, buf->len, 0);
+ fz_drop_buffer(ctx, buf);
+
+ doc = fz_malloc_struct(ctx, svg_document);
+ doc->super.close = svg_close_document;
+ doc->super.count_pages = svg_count_pages;
+ doc->super.load_page = svg_load_page;
+
+ doc->root = root;
+ doc->idmap = NULL;
+
+ svg_build_id_map(ctx, doc, root);
+
+ return (fz_document*)doc;
+}
+
+static fz_document *
+svg_open_document(fz_context *ctx, const char *filename)
+{
+ fz_stream *file;
+ fz_document *doc;
+
+ file = fz_open_file(ctx, filename);
+ fz_try(ctx)
+ doc = svg_open_document_with_stream(ctx, file);
+ fz_always(ctx)
+ fz_drop_stream(ctx, file);
+ fz_catch(ctx)
+ fz_rethrow(ctx);
+
+ return doc;
+}
+
+static int
+svg_recognize(fz_context *doc, const char *magic)
+{
+ char *ext = strrchr(magic, '.');
+ if (ext && !fz_strcasecmp(ext, ".svg"))
+ return 100;
+ if (!strcmp(magic, "svg") || !strcmp(magic, "image/svg+xml"))
+ return 100;
+ return 0;
+}
+
+fz_document_handler svg_document_handler =
+{
+ &svg_recognize,
+ &svg_open_document,
+ &svg_open_document_with_stream
+};
diff --git a/source/svg/svg-parse.c b/source/svg/svg-parse.c
new file mode 100644
index 00000000..b8db376f
--- /dev/null
+++ b/source/svg/svg-parse.c
@@ -0,0 +1,276 @@
+#include "mupdf/svg.h"
+
+int svg_is_whitespace_or_comma(int c)
+{
+ return (c == 0x20) || (c == 0x9) || (c == 0xD) || (c == 0xA) || (c == ',');
+}
+
+int svg_is_whitespace(int c)
+{
+ return (c == 0x20) || (c == 0x9) || (c == 0xD) || (c == 0xA);
+}
+
+int svg_is_alpha(int c)
+{
+ return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
+}
+
+int svg_is_digit(int c)
+{
+ return (c >= '0' && c <= '9') ||
+ (c == 'e') || (c == 'E') ||
+ (c == '+') || (c == '-') || (c == '.');
+}
+
+const char *
+svg_lex_number(float *fp, const char *ss)
+{
+ const char *s = ss;
+ if (*s == '-')
+ ++s;
+ while (*s >= '0' && *s <= '9')
+ ++s;
+ if (*s == '.') {
+ ++s;
+ while (*s >= '0' && *s <= '9')
+ ++s;
+ }
+ if (*s == 'e' || *s == 'E') {
+ ++s;
+ if (*s == '+' || *s == '-')
+ ++s;
+ while (*s >= '0' && *s <= '9')
+ ++s;
+ }
+ *fp = atof(ss);
+ return s;
+}
+
+float
+svg_parse_number(const char *str, float min, float max, float inherit)
+{
+ float x;
+ if (!strcmp(str, "inherit"))
+ return inherit;
+ x = atof(str);
+ if (x < min) return min;
+ if (x > max) return max;
+ return x;
+}
+
+/* Return length/coordinate in points */
+float
+svg_parse_length(const char *str, float percent, float font_size)
+{
+ char *end;
+ float val;
+
+ val = (float)strtod(str, &end);
+ if (end == str)
+ return 0; /* failed */
+
+ if (!strcmp(end, "px")) return val;
+
+ if (!strcmp(end, "pt")) return val * 1.0;
+ if (!strcmp(end, "pc")) return val * 12.0;
+ if (!strcmp(end, "mm")) return val * 2.83464567;
+ if (!strcmp(end, "cm")) return val * 28.3464567;
+ if (!strcmp(end, "in")) return val * 72.0;
+
+ if (!strcmp(end, "em")) return val * font_size;
+ if (!strcmp(end, "ex")) return val * font_size * 0.5;
+
+ if (!strcmp(end, "%"))
+ return val * percent * 0.01;
+
+ if (end[0] == 0)
+ return val;
+
+ return 0;
+}
+
+/* Return angle in degrees */
+float
+svg_parse_angle(const char *str)
+{
+ char *end;
+ float val;
+
+ val = (float)strtod(str, &end);
+ if (end == str)
+ return 0; /* failed */
+
+ if (!strcmp(end, "deg"))
+ return val;
+
+ if (!strcmp(end, "grad"))
+ return val * 0.9;
+
+ if (!strcmp(end, "rad"))
+ return val * 57.2957795;
+
+ return val;
+}
+
+/* Coordinate transformations */
+void
+svg_parse_transform(fz_context *ctx, svg_document *doc, char *str, fz_matrix *transform)
+{
+ char keyword[20];
+ int keywordlen;
+ char number[20];
+ int numberlen;
+ float args[6];
+ int nargs;
+
+ nargs = 0;
+ keywordlen = 0;
+
+ while (*str)
+ {
+ keywordlen = 0;
+ nargs = 0;
+
+ /*
+ * Parse keyword and opening parenthesis.
+ */
+
+ while (svg_is_whitespace(*str))
+ str ++;
+
+ if (*str == 0)
+ break;
+
+ while (svg_is_alpha(*str) && keywordlen < sizeof(keyword) - 1)
+ keyword[keywordlen++] = *str++;
+ keyword[keywordlen] = 0;
+
+ if (keywordlen == 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in transform attribute - no keyword");
+
+ while (svg_is_whitespace(*str))
+ str ++;
+
+ if (*str != '(')
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in transform attribute - no open paren");
+ str ++;
+
+ while (svg_is_whitespace(*str))
+ str ++;
+
+ /*
+ * Parse list of numbers until closing parenthesis
+ */
+
+ while (nargs < 6)
+ {
+ numberlen = 0;
+
+ while (svg_is_digit(*str) && numberlen < sizeof(number) - 1)
+ number[numberlen++] = *str++;
+ number[numberlen] = 0;
+
+ args[nargs++] = atof(number);
+
+ while (svg_is_whitespace_or_comma(*str))
+ str ++;
+
+ if (*str == ')')
+ {
+ str ++;
+ break;
+ }
+
+ if (*str == 0)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "syntax error in transform attribute - no close paren");
+ }
+
+ while (svg_is_whitespace_or_comma(*str))
+ str ++;
+
+ /*
+ * Execute the transform.
+ */
+
+ if (!strcmp(keyword, "matrix"))
+ {
+ fz_matrix m;
+
+ if (nargs != 6)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "wrong number of arguments to matrix(): %d", nargs);
+
+ m.a = args[0];
+ m.b = args[1];
+ m.c = args[2];
+ m.d = args[3];
+ m.e = args[4];
+ m.f = args[5];
+
+ fz_concat(transform, transform, &m);
+ }
+
+ else if (!strcmp(keyword, "translate"))
+ {
+ if (nargs != 2)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "wrong number of arguments to translate(): %d", nargs);
+
+ fz_pre_translate(transform, args[0], args[1]);
+ }
+
+ else if (!strcmp(keyword, "scale"))
+ {
+ if (nargs == 1)
+ fz_pre_scale(transform, args[0], args[0]);
+ else if (nargs == 2)
+ fz_pre_scale(transform, args[0], args[1]);
+ else
+ fz_throw(ctx, FZ_ERROR_GENERIC, "wrong number of arguments to scale(): %d", nargs);
+ }
+
+ else if (!strcmp(keyword, "rotate"))
+ {
+ if (nargs != 1)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "wrong number of arguments to rotate(): %d", nargs);
+ fz_pre_rotate(transform, args[0]);
+ }
+
+ else if (!strcmp(keyword, "skewX"))
+ {
+ fz_matrix m;
+
+ if (nargs != 1)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "wrong number of arguments to skewX(): %d", nargs);
+
+ m.a = 1.0;
+ m.b = 0.0;
+ m.c = tan(args[0] * 0.0174532925);
+ m.d = 1.0;
+ m.e = 0.0;
+ m.f = 0.0;
+
+ fz_concat(transform, transform, &m);
+ }
+
+ else if (!strcmp(keyword, "skewY"))
+ {
+ fz_matrix m;
+
+ if (nargs != 1)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "wrong number of arguments to skewY(): %d", nargs);
+
+ m.a = 1.0;
+ m.b = tan(args[0] * 0.0174532925);
+ m.c = 0.0;
+ m.d = 1.0;
+ m.e = 0.0;
+ m.f = 0.0;
+
+ fz_concat(transform, transform, &m);
+ }
+
+ else
+ {
+ fz_throw(ctx, FZ_ERROR_GENERIC, "unknown transform function: %s", keyword);
+ }
+ }
+}
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);
+}