/* * This file is part of Libsvgtiny * Licensed under the MIT License, * http://opensource.org/licenses/mit-license.php * Copyright 2008-2009 James Bursa */ #define _GNU_SOURCE /* for strndup */ #include #include #include #include #include #include #include #include #include #include "svgtiny.h" #include "svgtiny_internal.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #define KAPPA 0.5522847498 static svgtiny_code svgtiny_parse_svg(xmlNode *svg, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_path(xmlNode *path, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_rect(xmlNode *rect, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_circle(xmlNode *circle, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_ellipse(xmlNode *ellipse, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_line(xmlNode *line, struct svgtiny_parse_state state); static svgtiny_code svgtiny_parse_poly(xmlNode *poly, struct svgtiny_parse_state state, bool polygon); static svgtiny_code svgtiny_parse_text(xmlNode *text, struct svgtiny_parse_state state); static void svgtiny_parse_position_attributes(const xmlNode *node, const struct svgtiny_parse_state state, float *x, float *y, float *width, float *height); static void svgtiny_parse_paint_attributes(const xmlNode *node, struct svgtiny_parse_state *state); static void svgtiny_parse_font_attributes(const xmlNode *node, struct svgtiny_parse_state *state); static void svgtiny_parse_transform_attributes(xmlNode *node, struct svgtiny_parse_state *state); static svgtiny_code svgtiny_add_path(float *p, unsigned int n, struct svgtiny_parse_state *state); /** * Create a new svgtiny_diagram structure. */ struct svgtiny_diagram *svgtiny_create(void) { struct svgtiny_diagram *diagram; diagram = malloc(sizeof *diagram); if (!diagram) return 0; diagram->shape = 0; diagram->shape_count = 0; diagram->error_line = 0; diagram->error_message = 0; return diagram; } /** * Parse a block of memory into a svgtiny_diagram. */ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram, const char *buffer, size_t size, const char *url, int viewport_width, int viewport_height) { xmlDoc *document; xmlNode *svg; struct svgtiny_parse_state state; float x, y, width, height; svgtiny_code code; assert(diagram); assert(buffer); assert(url); /* parse XML to tree */ document = xmlReadMemory(buffer, size, url, 0, XML_PARSE_NONET | XML_PARSE_COMPACT); if (!document) return svgtiny_LIBXML_ERROR; /*xmlDebugDumpDocument(stderr, document);*/ /* find root element */ svg = xmlDocGetRootElement(document); if (!svg) return svgtiny_NOT_SVG; if (strcmp((const char *) svg->name, "svg") != 0) return svgtiny_NOT_SVG; /* get graphic dimensions */ state.diagram = diagram; state.document = document; state.viewport_width = viewport_width; state.viewport_height = viewport_height; svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height); diagram->width = width; diagram->height = height; /* set up parsing state */ state.viewport_width = width; state.viewport_height = height; state.ctm.a = 1; /*(float) viewport_width / (float) width;*/ state.ctm.b = 0; state.ctm.c = 0; state.ctm.d = 1; /*(float) viewport_height / (float) height;*/ state.ctm.e = 0; /*x;*/ state.ctm.f = 0; /*y;*/ /*state.style = css_base_style; state.style.font_size.value.length.value = option_font_size * 0.1;*/ state.fill = 0x000000; state.stroke = svgtiny_TRANSPARENT; state.stroke_width = 1; state.linear_gradient_stop_count = 0; /* parse tree */ code = svgtiny_parse_svg(svg, state); /* free XML tree */ xmlFreeDoc(document); return code; } /** * Parse a or element node. */ svgtiny_code svgtiny_parse_svg(xmlNode *svg, struct svgtiny_parse_state state) { float x, y, width, height; xmlAttr *view_box; xmlNode *child; svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height); svgtiny_parse_paint_attributes(svg, &state); svgtiny_parse_font_attributes(svg, &state); /* parse viewBox */ view_box = xmlHasProp(svg, (const xmlChar *) "viewBox"); if (view_box) { const char *s = (const char *) view_box->children->content; float min_x, min_y, vwidth, vheight; if (sscanf(s, "%f,%f,%f,%f", &min_x, &min_y, &vwidth, &vheight) == 4 || sscanf(s, "%f %f %f %f", &min_x, &min_y, &vwidth, &vheight) == 4) { state.ctm.a = (float) state.viewport_width / vwidth; state.ctm.d = (float) state.viewport_height / vheight; state.ctm.e += -min_x * state.ctm.a; state.ctm.f += -min_y * state.ctm.d; } } svgtiny_parse_transform_attributes(svg, &state); for (child = svg->children; child; child = child->next) { svgtiny_code code = svgtiny_OK; if (child->type == XML_ELEMENT_NODE) { const char *name = (const char *) child->name; if (strcmp(name, "svg") == 0) code = svgtiny_parse_svg(child, state); else if (strcmp(name, "g") == 0) code = svgtiny_parse_svg(child, state); else if (strcmp(name, "a") == 0) code = svgtiny_parse_svg(child, state); else if (strcmp(name, "path") == 0) code = svgtiny_parse_path(child, state); else if (strcmp(name, "rect") == 0) code = svgtiny_parse_rect(child, state); else if (strcmp(name, "circle") == 0) code = svgtiny_parse_circle(child, state); else if (strcmp(name, "ellipse") == 0) code = svgtiny_parse_ellipse(child, state); else if (strcmp(name, "line") == 0) code = svgtiny_parse_line(child, state); else if (strcmp(name, "polyline") == 0) code = svgtiny_parse_poly(child, state, false); else if (strcmp(name, "polygon") == 0) code = svgtiny_parse_poly(child, state, true); else if (strcmp(name, "text") == 0) code = svgtiny_parse_text(child, state); } if (code != svgtiny_OK) return code; } return svgtiny_OK; } /** * Parse a element node. * * http://www.w3.org/TR/SVG11/paths#PathElement */ svgtiny_code svgtiny_parse_path(xmlNode *path, struct svgtiny_parse_state state) { char *s, *path_d; float *p; unsigned int i; float last_x = 0, last_y = 0; float last_cubic_x = 0, last_cubic_y = 0; float last_quad_x = 0, last_quad_y = 0; svgtiny_parse_paint_attributes(path, &state); svgtiny_parse_transform_attributes(path, &state); /* read d attribute */ s = path_d = (char *) xmlGetProp(path, (const xmlChar *) "d"); if (!s) { state.diagram->error_line = path->line; state.diagram->error_message = "path: missing d attribute"; return svgtiny_SVG_ERROR; } /* allocate space for path: it will never have more elements than d */ p = malloc(sizeof p[0] * strlen(s)); if (!p) return svgtiny_OUT_OF_MEMORY; /* parse d and build path */ for (i = 0; s[i]; i++) if (s[i] == ',') s[i] = ' '; i = 0; while (*s) { char command[2]; int plot_command; float x, y, x1, y1, x2, y2, rx, ry, rotation, large_arc, sweep; int n; /* moveto (M, m), lineto (L, l) (2 arguments) */ if (sscanf(s, " %1[MmLl] %f %f %n", command, &x, &y, &n) == 3) { /*LOG(("moveto or lineto"));*/ if (*command == 'M' || *command == 'm') plot_command = svgtiny_PATH_MOVE; else plot_command = svgtiny_PATH_LINE; do { p[i++] = plot_command; if ('a' <= *command) { x += last_x; y += last_y; } p[i++] = last_cubic_x = last_quad_x = last_x = x; p[i++] = last_cubic_y = last_quad_y = last_y = y; s += n; plot_command = svgtiny_PATH_LINE; } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2); /* closepath (Z, z) (no arguments) */ } else if (sscanf(s, " %1[Zz] %n", command, &n) == 1) { /*LOG(("closepath"));*/ p[i++] = svgtiny_PATH_CLOSE; s += n; /* horizontal lineto (H, h) (1 argument) */ } else if (sscanf(s, " %1[Hh] %f %n", command, &x, &n) == 2) { /*LOG(("horizontal lineto"));*/ do { p[i++] = svgtiny_PATH_LINE; if (*command == 'h') x += last_x; p[i++] = last_cubic_x = last_quad_x = last_x = x; p[i++] = last_cubic_y = last_quad_y = last_y; s += n; } while (sscanf(s, "%f %n", &x, &n) == 1); /* vertical lineto (V, v) (1 argument) */ } else if (sscanf(s, " %1[Vv] %f %n", command, &y, &n) == 2) { /*LOG(("vertical lineto"));*/ do { p[i++] = svgtiny_PATH_LINE; if (*command == 'v') y += last_y; p[i++] = last_cubic_x = last_quad_x = last_x; p[i++] = last_cubic_y = last_quad_y = last_y = y; s += n; } while (sscanf(s, "%f %n", &x, &n) == 1); /* curveto (C, c) (6 arguments) */ } else if (sscanf(s, " %1[Cc] %f %f %f %f %f %f %n", command, &x1, &y1, &x2, &y2, &x, &y, &n) == 7) { /*LOG(("curveto"));*/ do { p[i++] = svgtiny_PATH_BEZIER; if (*command == 'c') { x1 += last_x; y1 += last_y; x2 += last_x; y2 += last_y; x += last_x; y += last_y; } p[i++] = x1; p[i++] = y1; p[i++] = last_cubic_x = x2; p[i++] = last_cubic_y = y2; p[i++] = last_quad_x = last_x = x; p[i++] = last_quad_y = last_y = y; s += n; } while (sscanf(s, "%f %f %f %f %f %f %n", &x1, &y1, &x2, &y2, &x, &y, &n) == 6); /* shorthand/smooth curveto (S, s) (4 arguments) */ } else if (sscanf(s, " %1[Ss] %f %f %f %f %n", command, &x2, &y2, &x, &y, &n) == 5) { /*LOG(("shorthand/smooth curveto"));*/ do { p[i++] = svgtiny_PATH_BEZIER; x1 = last_x + (last_x - last_cubic_x); y1 = last_y + (last_y - last_cubic_y); if (*command == 's') { x2 += last_x; y2 += last_y; x += last_x; y += last_y; } p[i++] = x1; p[i++] = y1; p[i++] = last_cubic_x = x2; p[i++] = last_cubic_y = y2; p[i++] = last_quad_x = last_x = x; p[i++] = last_quad_y = last_y = y; s += n; } while (sscanf(s, "%f %f %f %f %n", &x2, &y2, &x, &y, &n) == 4); /* quadratic Bezier curveto (Q, q) (4 arguments) */ } else if (sscanf(s, " %1[Qq] %f %f %f %f %n", command, &x1, &y1, &x, &y, &n) == 5) { /*LOG(("quadratic Bezier curveto"));*/ do { p[i++] = svgtiny_PATH_BEZIER; last_quad_x = x1; last_quad_y = y1; if (*command == 'q') { x1 += last_x; y1 += last_y; x += last_x; y += last_y; } p[i++] = 1./3 * last_x + 2./3 * x1; p[i++] = 1./3 * last_y + 2./3 * y1; p[i++] = 2./3 * x1 + 1./3 * x; p[i++] = 2./3 * y1 + 1./3 * y; p[i++] = last_cubic_x = last_x = x; p[i++] = last_cubic_y = last_y = y; s += n; } while (sscanf(s, "%f %f %f %f %n", &x1, &y1, &x, &y, &n) == 4); /* shorthand/smooth quadratic Bezier curveto (T, t) (2 arguments) */ } else if (sscanf(s, " %1[Tt] %f %f %n", command, &x, &y, &n) == 3) { /*LOG(("shorthand/smooth quadratic Bezier curveto"));*/ do { p[i++] = svgtiny_PATH_BEZIER; x1 = last_x + (last_x - last_quad_x); y1 = last_y + (last_y - last_quad_y); last_quad_x = x1; last_quad_y = y1; if (*command == 't') { x1 += last_x; y1 += last_y; x += last_x; y += last_y; } p[i++] = 1./3 * last_x + 2./3 * x1; p[i++] = 1./3 * last_y + 2./3 * y1; p[i++] = 2./3 * x1 + 1./3 * x; p[i++] = 2./3 * y1 + 1./3 * y; p[i++] = last_cubic_x = last_x = x; p[i++] = last_cubic_y = last_y = y; s += n; } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2); /* elliptical arc (A, a) (7 arguments) */ } else if (sscanf(s, " %1[Aa] %f %f %f %f %f %f %f %n", command, &rx, &ry, &rotation, &large_arc, &sweep, &x, &y, &n) == 8) { do { p[i++] = svgtiny_PATH_LINE; if (*command == 'a') { x += last_x; y += last_y; } p[i++] = last_cubic_x = last_quad_x = last_x = x; p[i++] = last_cubic_y = last_quad_y = last_y = y; s += n; } while (sscanf(s, "%f %f %f %f %f %f %f %n", &rx, &ry, &rotation, &large_arc, &sweep, &x, &y, &n) == 7); } else { fprintf(stderr, "parse failed at \"%s\"\n", s); break; } } xmlFree(path_d); if (i <= 4) { /* no real segments in path */ free(p); return svgtiny_OK; } return svgtiny_add_path(p, i, &state); } /** * Parse a element node. * * http://www.w3.org/TR/SVG11/shapes#RectElement */ svgtiny_code svgtiny_parse_rect(xmlNode *rect, struct svgtiny_parse_state state) { float x, y, width, height; float *p; svgtiny_parse_position_attributes(rect, state, &x, &y, &width, &height); svgtiny_parse_paint_attributes(rect, &state); svgtiny_parse_transform_attributes(rect, &state); p = malloc(13 * sizeof p[0]); if (!p) return svgtiny_OUT_OF_MEMORY; p[0] = svgtiny_PATH_MOVE; p[1] = x; p[2] = y; p[3] = svgtiny_PATH_LINE; p[4] = x + width; p[5] = y; p[6] = svgtiny_PATH_LINE; p[7] = x + width; p[8] = y + height; p[9] = svgtiny_PATH_LINE; p[10] = x; p[11] = y + height; p[12] = svgtiny_PATH_CLOSE; return svgtiny_add_path(p, 13, &state); } /** * Parse a element node. */ svgtiny_code svgtiny_parse_circle(xmlNode *circle, struct svgtiny_parse_state state) { float x = 0, y = 0, r = -1; float *p; xmlAttr *attr; for (attr = circle->properties; attr; attr = attr->next) { const char *name = (const char *) attr->name; const char *content = (const char *) attr->children->content; if (strcmp(name, "cx") == 0) x = svgtiny_parse_length(content, state.viewport_width, state); else if (strcmp(name, "cy") == 0) y = svgtiny_parse_length(content, state.viewport_height, state); else if (strcmp(name, "r") == 0) r = svgtiny_parse_length(content, state.viewport_width, state); } svgtiny_parse_paint_attributes(circle, &state); svgtiny_parse_transform_attributes(circle, &state); if (r < 0) { state.diagram->error_line = circle->line; state.diagram->error_message = "circle: r missing or negative"; return svgtiny_SVG_ERROR; } if (r == 0) return svgtiny_OK; p = malloc(32 * sizeof p[0]); if (!p) return svgtiny_OUT_OF_MEMORY; p[0] = svgtiny_PATH_MOVE; p[1] = x + r; p[2] = y; p[3] = svgtiny_PATH_BEZIER; p[4] = x + r; p[5] = y + r * KAPPA; p[6] = x + r * KAPPA; p[7] = y + r; p[8] = x; p[9] = y + r; p[10] = svgtiny_PATH_BEZIER; p[11] = x - r * KAPPA; p[12] = y + r; p[13] = x - r; p[14] = y + r * KAPPA; p[15] = x - r; p[16] = y; p[17] = svgtiny_PATH_BEZIER; p[18] = x - r; p[19] = y - r * KAPPA; p[20] = x - r * KAPPA; p[21] = y - r; p[22] = x; p[23] = y - r; p[24] = svgtiny_PATH_BEZIER; p[25] = x + r * KAPPA; p[26] = y - r; p[27] = x + r; p[28] = y - r * KAPPA; p[29] = x + r; p[30] = y; p[31] = svgtiny_PATH_CLOSE; return svgtiny_add_path(p, 32, &state); } /** * Parse an element node. */ svgtiny_code svgtiny_parse_ellipse(xmlNode *ellipse, struct svgtiny_parse_state state) { float x = 0, y = 0, rx = -1, ry = -1; float *p; xmlAttr *attr; for (attr = ellipse->properties; attr; attr = attr->next) { const char *name = (const char *) attr->name; const char *content = (const char *) attr->children->content; if (strcmp(name, "cx") == 0) x = svgtiny_parse_length(content, state.viewport_width, state); else if (strcmp(name, "cy") == 0) y = svgtiny_parse_length(content, state.viewport_height, state); else if (strcmp(name, "rx") == 0) rx = svgtiny_parse_length(content, state.viewport_width, state); else if (strcmp(name, "ry") == 0) ry = svgtiny_parse_length(content, state.viewport_width, state); } svgtiny_parse_paint_attributes(ellipse, &state); svgtiny_parse_transform_attributes(ellipse, &state); if (rx < 0 || ry < 0) { state.diagram->error_line = ellipse->line; state.diagram->error_message = "ellipse: rx or ry missing " "or negative"; return svgtiny_SVG_ERROR; } if (rx == 0 || ry == 0) return svgtiny_OK; p = malloc(32 * sizeof p[0]); if (!p) return svgtiny_OUT_OF_MEMORY; p[0] = svgtiny_PATH_MOVE; p[1] = x + rx; p[2] = y; p[3] = svgtiny_PATH_BEZIER; p[4] = x + rx; p[5] = y + ry * KAPPA; p[6] = x + rx * KAPPA; p[7] = y + ry; p[8] = x; p[9] = y + ry; p[10] = svgtiny_PATH_BEZIER; p[11] = x - rx * KAPPA; p[12] = y + ry; p[13] = x - rx; p[14] = y + ry * KAPPA; p[15] = x - rx; p[16] = y; p[17] = svgtiny_PATH_BEZIER; p[18] = x - rx; p[19] = y - ry * KAPPA; p[20] = x - rx * KAPPA; p[21] = y - ry; p[22] = x; p[23] = y - ry; p[24] = svgtiny_PATH_BEZIER; p[25] = x + rx * KAPPA; p[26] = y - ry; p[27] = x + rx; p[28] = y - ry * KAPPA; p[29] = x + rx; p[30] = y; p[31] = svgtiny_PATH_CLOSE; return svgtiny_add_path(p, 32, &state); } /** * Parse a element node. */ svgtiny_code svgtiny_parse_line(xmlNode *line, struct svgtiny_parse_state state) { float x1 = 0, y1 = 0, x2 = 0, y2 = 0; float *p; xmlAttr *attr; for (attr = line->properties; attr; attr = attr->next) { const char *name = (const char *) attr->name; const char *content = (const char *) attr->children->content; if (strcmp(name, "x1") == 0) x1 = svgtiny_parse_length(content, state.viewport_width, state); else if (strcmp(name, "y1") == 0) y1 = svgtiny_parse_length(content, state.viewport_height, state); else if (strcmp(name, "x2") == 0) x2 = svgtiny_parse_length(content, state.viewport_width, state); else if (strcmp(name, "y2") == 0) y2 = svgtiny_parse_length(content, state.viewport_height, state); } svgtiny_parse_paint_attributes(line, &state); svgtiny_parse_transform_attributes(line, &state); p = malloc(7 * sizeof p[0]); if (!p) return svgtiny_OUT_OF_MEMORY; p[0] = svgtiny_PATH_MOVE; p[1] = x1; p[2] = y1; p[3] = svgtiny_PATH_LINE; p[4] = x2; p[5] = y2; p[6] = svgtiny_PATH_CLOSE; return svgtiny_add_path(p, 7, &state); } /** * Parse a or element node. * * http://www.w3.org/TR/SVG11/shapes#PolylineElement * http://www.w3.org/TR/SVG11/shapes#PolygonElement */ svgtiny_code svgtiny_parse_poly(xmlNode *poly, struct svgtiny_parse_state state, bool polygon) { char *s, *points; float *p; unsigned int i; svgtiny_parse_paint_attributes(poly, &state); svgtiny_parse_transform_attributes(poly, &state); /* read points attribute */ s = points = (char *) xmlGetProp(poly, (const xmlChar *) "points"); if (!s) { state.diagram->error_line = poly->line; state.diagram->error_message = "polyline/polygon: missing points attribute"; return svgtiny_SVG_ERROR; } /* allocate space for path: it will never have more elements than s */ p = malloc(sizeof p[0] * strlen(s)); if (!p) { xmlFree(points); return svgtiny_OUT_OF_MEMORY; } /* parse s and build path */ for (i = 0; s[i]; i++) if (s[i] == ',') s[i] = ' '; i = 0; while (*s) { float x, y; int n; if (sscanf(s, "%f %f %n", &x, &y, &n) == 2) { if (i == 0) p[i++] = svgtiny_PATH_MOVE; else p[i++] = svgtiny_PATH_LINE; p[i++] = x; p[i++] = y; s += n; } else { break; } } if (polygon) p[i++] = svgtiny_PATH_CLOSE; xmlFree(points); return svgtiny_add_path(p, i, &state); } /** * Parse a or element node. */ svgtiny_code svgtiny_parse_text(xmlNode *text, struct svgtiny_parse_state state) { float x, y, width, height; float px, py; xmlNode *child; svgtiny_parse_position_attributes(text, state, &x, &y, &width, &height); svgtiny_parse_font_attributes(text, &state); svgtiny_parse_transform_attributes(text, &state); px = state.ctm.a * x + state.ctm.c * y + state.ctm.e; py = state.ctm.b * x + state.ctm.d * y + state.ctm.f; /* state.ctm.e = px - state.origin_x; */ /* state.ctm.f = py - state.origin_y; */ /*struct css_style style = state.style; style.font_size.value.length.value *= state.ctm.a;*/ for (child = text->children; child; child = child->next) { svgtiny_code code = svgtiny_OK; if (child->type == XML_TEXT_NODE) { struct svgtiny_shape *shape = svgtiny_add_shape(&state); if (!shape) return svgtiny_OUT_OF_MEMORY; shape->text = strdup((const char *) child->content); shape->text_x = px; shape->text_y = py; state.diagram->shape_count++; } else if (child->type == XML_ELEMENT_NODE && strcmp((const char *) child->name, "tspan") == 0) { code = svgtiny_parse_text(child, state); } if (!code != svgtiny_OK) return code; } return svgtiny_OK; } /** * Parse x, y, width, and height attributes, if present. */ void svgtiny_parse_position_attributes(const xmlNode *node, const struct svgtiny_parse_state state, float *x, float *y, float *width, float *height) { xmlAttr *attr; *x = 0; *y = 0; *width = state.viewport_width; *height = state.viewport_height; for (attr = node->properties; attr; attr = attr->next) { const char *name = (const char *) attr->name; const char *content = (const char *) attr->children->content; if (strcmp(name, "x") == 0) *x = svgtiny_parse_length(content, state.viewport_width, state); else if (strcmp(name, "y") == 0) *y = svgtiny_parse_length(content, state.viewport_height, state); else if (strcmp(name, "width") == 0) *width = svgtiny_parse_length(content, state.viewport_width, state); else if (strcmp(name, "height") == 0) *height = svgtiny_parse_length(content, state.viewport_height, state); } } /** * Parse a length as a number of pixels. */ float svgtiny_parse_length(const char *s, int viewport_size, const struct svgtiny_parse_state state) { int num_length = strspn(s, "0123456789+-."); const char *unit = s + num_length; float n = atof((const char *) s); float font_size = 20; /*css_len2px(&state.style.font_size.value.length, 0);*/ UNUSED(state); if (unit[0] == 0) { return n; } else if (unit[0] == '%') { return n / 100.0 * viewport_size; } else if (unit[0] == 'e' && unit[1] == 'm') { return n * font_size; } else if (unit[0] == 'e' && unit[1] == 'x') { return n / 2.0 * font_size; } else if (unit[0] == 'p' && unit[1] == 'x') { return n; } else if (unit[0] == 'p' && unit[1] == 't') { return n * 1.25; } else if (unit[0] == 'p' && unit[1] == 'c') { return n * 15.0; } else if (unit[0] == 'm' && unit[1] == 'm') { return n * 3.543307; } else if (unit[0] == 'c' && unit[1] == 'm') { return n * 35.43307; } else if (unit[0] == 'i' && unit[1] == 'n') { return n * 90; } return 0; } /** * Parse paint attributes, if present. */ void svgtiny_parse_paint_attributes(const xmlNode *node, struct svgtiny_parse_state *state) { const xmlAttr *attr; for (attr = node->properties; attr; attr = attr->next) { const char *name = (const char *) attr->name; const char *content = (const char *) attr->children->content; if (strcmp(name, "fill") == 0) svgtiny_parse_color(content, &state->fill, state); else if (strcmp(name, "stroke") == 0) svgtiny_parse_color(content, &state->stroke, state); else if (strcmp(name, "stroke-width") == 0) state->stroke_width = svgtiny_parse_length(content, state->viewport_width, *state); else if (strcmp(name, "style") == 0) { const char *style = (const char *) attr->children->content; const char *s; char *value; if ((s = strstr(style, "fill:"))) { s += 5; while (*s == ' ') s++; value = strndup(s, strcspn(s, "; ")); svgtiny_parse_color(value, &state->fill, state); free(value); } if ((s = strstr(style, "stroke:"))) { s += 7; while (*s == ' ') s++; value = strndup(s, strcspn(s, "; ")); svgtiny_parse_color(value, &state->stroke, state); free(value); } if ((s = strstr(style, "stroke-width:"))) { s += 13; while (*s == ' ') s++; value = strndup(s, strcspn(s, "; ")); state->stroke_width = svgtiny_parse_length(value, state->viewport_width, *state); free(value); } } } } /** * Parse a colour. */ void svgtiny_parse_color(const char *s, svgtiny_colour *c, struct svgtiny_parse_state *state) { unsigned int r, g, b; float rf, gf, bf; size_t len = strlen(s); char *id = 0, *rparen; if (len == 4 && s[0] == '#') { if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3) *c = svgtiny_RGB(r | r << 4, g | g << 4, b | b << 4); } else if (len == 7 && s[0] == '#') { if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3) *c = svgtiny_RGB(r, g, b); } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' && s[3] == '(' && s[len - 1] == ')') { if (sscanf(s + 4, "%u,%u,%u", &r, &g, &b) == 3) *c = svgtiny_RGB(r, g, b); else if (sscanf(s + 4, "%f%%,%f%%,%f%%", &rf, &gf, &bf) == 3) { b = bf * 255 / 100; g = gf * 255 / 100; r = rf * 255 / 100; *c = svgtiny_RGB(r, g, b); } } else if (len == 4 && strcmp(s, "none") == 0) { *c = svgtiny_TRANSPARENT; } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' && s[3] == '(') { if (s[4] == '#') { id = strdup(s + 5); if (!id) return; rparen = strchr(id, ')'); if (rparen) *rparen = 0; svgtiny_find_gradient(id, state); free(id); fprintf(stderr, "linear_gradient_stop_count %i\n", state->linear_gradient_stop_count); if (state->linear_gradient_stop_count == 0) *c = svgtiny_TRANSPARENT; else if (state->linear_gradient_stop_count == 1) *c = state->gradient_stop[0].color; else *c = svgtiny_LINEAR_GRADIENT; } } else { const struct svgtiny_named_color *named_color; named_color = svgtiny_color_lookup(s, strlen(s)); if (named_color) *c = named_color->color; } } /** * Parse font attributes, if present. */ void svgtiny_parse_font_attributes(const xmlNode *node, struct svgtiny_parse_state *state) { const xmlAttr *attr; UNUSED(state); for (attr = node->properties; attr; attr = attr->next) { if (strcmp((const char *) attr->name, "font-size") == 0) { /*if (css_parse_length( (const char *) attr->children->content, &state->style.font_size.value.length, true, true)) { state->style.font_size.size = CSS_FONT_SIZE_LENGTH; }*/ } } } /** * Parse transform attributes, if present. * * http://www.w3.org/TR/SVG11/coords#TransformAttribute */ void svgtiny_parse_transform_attributes(xmlNode *node, struct svgtiny_parse_state *state) { char *transform; /* parse transform */ transform = (char *) xmlGetProp(node, (const xmlChar *) "transform"); if (transform) { svgtiny_parse_transform(transform, &state->ctm.a, &state->ctm.b, &state->ctm.c, &state->ctm.d, &state->ctm.e, &state->ctm.f); xmlFree(transform); } } /** * Parse a transform string. */ void svgtiny_parse_transform(char *s, float *ma, float *mb, float *mc, float *md, float *me, float *mf) { float a, b, c, d, e, f; float za, zb, zc, zd, ze, zf; float angle, x, y; int n; unsigned int i; for (i = 0; s[i]; i++) if (s[i] == ',') s[i] = ' '; while (*s) { a = d = 1; b = c = 0; e = f = 0; if (sscanf(s, "matrix (%f %f %f %f %f %f) %n", &a, &b, &c, &d, &e, &f, &n) == 6) ; else if (sscanf(s, "translate (%f %f) %n", &e, &f, &n) == 2) ; else if (sscanf(s, "translate (%f) %n", &e, &n) == 1) ; else if (sscanf(s, "scale (%f %f) %n", &a, &d, &n) == 2) ; else if (sscanf(s, "scale (%f) %n", &a, &n) == 1) d = a; else if (sscanf(s, "rotate (%f %f %f) %n", &angle, &x, &y, &n) == 3) { angle = angle / 180 * M_PI; a = cos(angle); b = sin(angle); c = -sin(angle); d = cos(angle); e = -x * cos(angle) + y * sin(angle) + x; f = -x * sin(angle) - y * cos(angle) + y; } else if (sscanf(s, "rotate (%f) %n", &angle, &n) == 1) { angle = angle / 180 * M_PI; a = cos(angle); b = sin(angle); c = -sin(angle); d = cos(angle); } else if (sscanf(s, "skewX (%f) %n", &angle, &n) == 1) { angle = angle / 180 * M_PI; c = tan(angle); } else if (sscanf(s, "skewY (%f) %n", &angle, &n) == 1) { angle = angle / 180 * M_PI; b = tan(angle); } else break; za = *ma * a + *mc * b; zb = *mb * a + *md * b; zc = *ma * c + *mc * d; zd = *mb * c + *md * d; ze = *ma * e + *mc * f + *me; zf = *mb * e + *md * f + *mf; *ma = za; *mb = zb; *mc = zc; *md = zd; *me = ze; *mf = zf; s += n; } } /** * Add a path to the svgtiny_diagram. */ svgtiny_code svgtiny_add_path(float *p, unsigned int n, struct svgtiny_parse_state *state) { struct svgtiny_shape *shape; if (state->fill == svgtiny_LINEAR_GRADIENT) return svgtiny_add_path_linear_gradient(p, n, state); svgtiny_transform_path(p, n, state); shape = svgtiny_add_shape(state); if (!shape) { free(p); return svgtiny_OUT_OF_MEMORY; } shape->path = p; shape->path_length = n; state->diagram->shape_count++; return svgtiny_OK; } /** * Add a svgtiny_shape to the svgtiny_diagram. */ struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state) { struct svgtiny_shape *shape = realloc(state->diagram->shape, (state->diagram->shape_count + 1) * sizeof (state->diagram->shape[0])); if (!shape) return 0; state->diagram->shape = shape; shape += state->diagram->shape_count; shape->path = 0; shape->path_length = 0; shape->text = 0; shape->fill = state->fill; shape->stroke = state->stroke; shape->stroke_width = lroundf((float) state->stroke_width * (state->ctm.a + state->ctm.d) / 2.0); if (0 < state->stroke_width && shape->stroke_width == 0) shape->stroke_width = 1; return shape; } /** * Apply the current transformation matrix to a path. */ void svgtiny_transform_path(float *p, unsigned int n, struct svgtiny_parse_state *state) { unsigned int j; for (j = 0; j != n; ) { unsigned int points = 0; unsigned int k; switch ((int) p[j]) { case svgtiny_PATH_MOVE: case svgtiny_PATH_LINE: points = 1; break; case svgtiny_PATH_CLOSE: points = 0; break; case svgtiny_PATH_BEZIER: points = 3; break; default: assert(0); } j++; for (k = 0; k != points; k++) { float x0 = p[j], y0 = p[j + 1]; float x = state->ctm.a * x0 + state->ctm.c * y0 + state->ctm.e; float y = state->ctm.b * x0 + state->ctm.d * y0 + state->ctm.f; p[j] = x; p[j + 1] = y; j += 2; } } } /** * Free all memory used by a diagram. */ void svgtiny_free(struct svgtiny_diagram *svg) { unsigned int i; assert(svg); for (i = 0; i != svg->shape_count; i++) { free(svg->shape[i].path); free(svg->shape[i].text); } free(svg->shape); free(svg); }