diff options
author | Tor Andersson <tor.andersson@artifex.com> | 2016-04-07 13:36:29 +0200 |
---|---|---|
committer | Tor Andersson <tor.andersson@artifex.com> | 2016-04-26 15:12:57 +0200 |
commit | d8ebcc7b59fc20b1dd3ea7887c6d3ad2859b3698 (patch) | |
tree | 74dc67e9c984bde63c4da7389d89b00e30b21182 | |
parent | 1b16995f277aefda88957e31c00d963fc9a59d7a (diff) | |
download | mupdf-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.
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | include/mupdf/fitz/document.h | 1 | ||||
-rw-r--r-- | include/mupdf/svg.h | 41 | ||||
-rw-r--r-- | platform/android/viewer/jni/Core.mk | 2 | ||||
-rw-r--r-- | platform/win32/libmupdf.vcproj | 20 | ||||
-rw-r--r-- | source/fitz/document-all.c | 1 | ||||
-rw-r--r-- | source/svg/svg-color.c | 273 | ||||
-rw-r--r-- | source/svg/svg-doc.c | 143 | ||||
-rw-r--r-- | source/svg/svg-parse.c | 276 | ||||
-rw-r--r-- | source/svg/svg-run.c | 990 |
10 files changed, 1754 insertions, 1 deletions
@@ -60,6 +60,7 @@ RM_CMD = $(QUIET_RM) rm -f $@ ALL_DIR := $(OUT)/fitz ALL_DIR += $(OUT)/pdf $(OUT)/pdf/js ALL_DIR += $(OUT)/xps +ALL_DIR += $(OUT)/svg ALL_DIR += $(OUT)/cbz ALL_DIR += $(OUT)/img ALL_DIR += $(OUT)/tiff @@ -74,11 +75,13 @@ ALL_DIR += $(OUT)/fonts FITZ_HDR := include/mupdf/fitz.h $(wildcard include/mupdf/fitz/*.h) PDF_HDR := include/mupdf/pdf.h $(wildcard include/mupdf/pdf/*.h) XPS_HDR := include/mupdf/xps.h +SVG_HDR := include/mupdf/svg.h HTML_HDR := include/mupdf/html.h FITZ_SRC := $(wildcard source/fitz/*.c) PDF_SRC := $(wildcard source/pdf/*.c) XPS_SRC := $(wildcard source/xps/*.c) +SVG_SRC := $(wildcard source/svg/*.c) CBZ_SRC := $(wildcard source/cbz/*.c) HTML_SRC := $(wildcard source/html/*.c) GPRF_SRC := $(wildcard source/gprf/*.c) @@ -86,12 +89,14 @@ GPRF_SRC := $(wildcard source/gprf/*.c) FITZ_SRC_HDR := $(wildcard source/fitz/*.h) PDF_SRC_HDR := $(wildcard source/pdf/*.h) source/pdf/pdf-name-table.h XPS_SRC_HDR := $(wildcard source/xps/*.h) +SVG_SRC_HDR := $(wildcard source/svg/*.h) HTML_SRC_HDR := $(wildcard source/html/*.h) GPRF_SRC_HDR := $(wildcard source/gprf/*.h) FITZ_OBJ := $(subst source/, $(OUT)/, $(addsuffix .o, $(basename $(FITZ_SRC)))) PDF_OBJ := $(subst source/, $(OUT)/, $(addsuffix .o, $(basename $(PDF_SRC)))) XPS_OBJ := $(subst source/, $(OUT)/, $(addsuffix .o, $(basename $(XPS_SRC)))) +SVG_OBJ := $(subst source/, $(OUT)/, $(addsuffix .o, $(basename $(SVG_SRC)))) CBZ_OBJ := $(subst source/, $(OUT)/, $(addsuffix .o, $(basename $(CBZ_SRC)))) HTML_OBJ := $(subst source/, $(OUT)/, $(addsuffix .o, $(basename $(HTML_SRC)))) GPRF_OBJ := $(subst source/, $(OUT)/, $(addsuffix .o, $(basename $(GPRF_SRC)))) @@ -105,6 +110,7 @@ endif $(FITZ_OBJ) : $(FITZ_HDR) $(FITZ_SRC_HDR) $(PDF_OBJ) : $(FITZ_HDR) $(PDF_HDR) $(PDF_SRC_HDR) $(XPS_OBJ) : $(FITZ_HDR) $(XPS_HDR) $(XPS_SRC_HDR) +$(SVG_OBJ) : $(FITZ_HDR) $(SVG_HDR) $(SVG_SRC_HDR) $(CBZ_OBJ) : $(FITZ_HDR) $(HTML_OBJ) : $(FITZ_HDR) $(HTML_HDR) $(HTML_SRC_HDR) $(GPRF_OBJ) : $(FITZ_HDR) $(GPRF_HDR) $(GPRF_SRC_HDR) @@ -144,7 +150,7 @@ $(FONT_GEN_URW) : $(FONT_BIN_URW) MUPDF_LIB = $(OUT)/libmupdf.a THIRD_LIB = $(OUT)/libmupdfthird.a -MUPDF_OBJ := $(FITZ_OBJ) $(FONT_OBJ) $(PDF_OBJ) $(XPS_OBJ) $(CBZ_OBJ) $(HTML_OBJ) $(GPRF_OBJ) +MUPDF_OBJ := $(FITZ_OBJ) $(FONT_OBJ) $(PDF_OBJ) $(XPS_OBJ) $(SVG_OBJ) $(CBZ_OBJ) $(HTML_OBJ) $(GPRF_OBJ) THIRD_OBJ := $(FREETYPE_OBJ) $(HARFBUZZ_OBJ) $(JBIG2DEC_OBJ) $(JPEG_OBJ) $(MUJS_OBJ) $(OPENJPEG_OBJ) $(ZLIB_OBJ) $(MUPDF_LIB) : $(MUPDF_OBJ) diff --git a/include/mupdf/fitz/document.h b/include/mupdf/fitz/document.h index 1964e41c..b70f7a8a 100644 --- a/include/mupdf/fitz/document.h +++ b/include/mupdf/fitz/document.h @@ -106,6 +106,7 @@ struct fz_document_handler_s extern fz_document_handler pdf_document_handler; extern fz_document_handler xps_document_handler; +extern fz_document_handler svg_document_handler; extern fz_document_handler cbz_document_handler; extern fz_document_handler img_document_handler; extern fz_document_handler tiff_document_handler; diff --git a/include/mupdf/svg.h b/include/mupdf/svg.h new file mode 100644 index 00000000..280f1726 --- /dev/null +++ b/include/mupdf/svg.h @@ -0,0 +1,41 @@ +#ifndef MUPDF_SVG_H +#define MUPDF_SVG_H + +#include "mupdf/fitz.h" + +/* Forward declarations. */ + +typedef struct svg_document_s svg_document; + +/* Parse basic data type units. */ + +const char *svg_lex_number(float *fp, const char *str); +float svg_parse_number(const char *str, float min,float max, float inherit); +float svg_parse_length(const char *str, float percent, float font_size); +float svg_parse_angle(const char *str); + +void svg_parse_color(fz_context *ctx, svg_document *doc, char *str, float *rgb); +void svg_parse_transform(fz_context *ctx, svg_document *doc, char *str, fz_matrix *ctm); + +int svg_is_whitespace_or_comma(int c); +int svg_is_whitespace(int c); +int svg_is_alpha(int c); +int svg_is_digit(int c); + +/* Graphics content parsing. */ + +void svg_parse_document_bounds(fz_context *ctx, svg_document *doc, fz_xml *root); +void svg_run_document(fz_context *ctx, svg_document *doc, fz_xml *root, fz_device *dev, const fz_matrix *ctm); + +/* Global context */ + +struct svg_document_s +{ + fz_document super; + fz_xml *root; + fz_tree *idmap; + float width; + float height; +}; + +#endif diff --git a/platform/android/viewer/jni/Core.mk b/platform/android/viewer/jni/Core.mk index 131e6535..cf85f108 100644 --- a/platform/android/viewer/jni/Core.mk +++ b/platform/android/viewer/jni/Core.mk @@ -41,6 +41,7 @@ LOCAL_C_INCLUDES := \ $(MY_ROOT)/source/fitz \ $(MY_ROOT)/source/pdf \ $(MY_ROOT)/source/xps \ + $(MY_ROOT)/source/svg \ $(MY_ROOT)/source/cbz \ $(MY_ROOT)/source/img \ $(MY_ROOT)/source/tiff \ @@ -62,6 +63,7 @@ LOCAL_SRC_FILES := \ $(wildcard $(MY_ROOT)/source/fitz/*.c) \ $(wildcard $(MY_ROOT)/source/pdf/*.c) \ $(wildcard $(MY_ROOT)/source/xps/*.c) \ + $(wildcard $(MY_ROOT)/source/svg/*.c) \ $(wildcard $(MY_ROOT)/source/cbz/*.c) \ $(wildcard $(MY_ROOT)/source/gprf/*.c) \ $(wildcard $(MY_ROOT)/source/html/*.c) \ diff --git a/platform/win32/libmupdf.vcproj b/platform/win32/libmupdf.vcproj index 8da9633d..d48d6ef6 100644 --- a/platform/win32/libmupdf.vcproj +++ b/platform/win32/libmupdf.vcproj @@ -668,6 +668,26 @@ </File> </Filter> <Filter + Name="svg" + > + <File + RelativePath="..\..\source\svg\svg-color.c" + > + </File> + <File + RelativePath="..\..\source\svg\svg-doc.c" + > + </File> + <File + RelativePath="..\..\source\svg\svg-parse.c" + > + </File> + <File + RelativePath="..\..\source\svg\svg-run.c" + > + </File> + </Filter> + <Filter Name="xps" > <File diff --git a/source/fitz/document-all.c b/source/fitz/document-all.c index c958d072..3c1398e6 100644 --- a/source/fitz/document-all.c +++ b/source/fitz/document-all.c @@ -4,6 +4,7 @@ void fz_register_document_handlers(fz_context *ctx) { fz_register_document_handler(ctx, &pdf_document_handler); fz_register_document_handler(ctx, &xps_document_handler); + fz_register_document_handler(ctx, &svg_document_handler); fz_register_document_handler(ctx, &cbz_document_handler); fz_register_document_handler(ctx, &img_document_handler); fz_register_document_handler(ctx, &tiff_document_handler); 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); +} |