* This file is part of Libsvgtiny
* Licensed under the MIT License,
* http://opensource.org/licenses/mit-license.php
- * Copyright 2008 James Bursa <james@semichrome.net>
+ * Copyright 2008-2009 James Bursa <james@semichrome.net>
+ * Copyright 2012 Daniel Silverstone <dsilvers@netsurf-browser.org>
*/
-#define _GNU_SOURCE /* for strndup */
#include <assert.h>
#include <math.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <libxml/parser.h>
-#include <libxml/debugXML.h>
+
+#include <dom/dom.h>
+#include <dom/bindings/xml/xmlparser.h>
+
+#include <libcss/libcss.h>
+
#include "svgtiny.h"
#include "svgtiny_internal.h"
+/* Source file generated by `gperf`. */
+#include "autogenerated_colors.c"
+
+#define TAU 6.28318530717958647692
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#ifndef M_PI_2
+#define M_PI_2 1.57079632679489661923
+#endif
+
+#define KAPPA 0.5522847498
-static svgtiny_code svgtiny_parse_svg(xmlNode *svg,
+#define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0)
+#define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI)
+
+static svgtiny_code svgtiny_parse_style_element(dom_element *style,
+ struct svgtiny_parse_state state);
+static css_stylesheet *svgtiny_parse_style_inline(const uint8_t *data,
+ size_t len);
+static svgtiny_code svgtiny_preparse_styles(dom_element *svg,
+ struct svgtiny_parse_state state);
+static svgtiny_code svgtiny_parse_svg(dom_element *svg,
+ struct svgtiny_parse_state state);
+static svgtiny_code svgtiny_parse_path(dom_element *path,
struct svgtiny_parse_state state);
-static svgtiny_code svgtiny_parse_path(xmlNode *path,
+static svgtiny_code svgtiny_parse_rect(dom_element *rect,
struct svgtiny_parse_state state);
-static svgtiny_code svgtiny_parse_rect(xmlNode *rect,
+static svgtiny_code svgtiny_parse_circle(dom_element *circle,
struct svgtiny_parse_state state);
-static svgtiny_code svgtiny_parse_circle(xmlNode *circle,
+static svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
struct svgtiny_parse_state state);
-static svgtiny_code svgtiny_parse_line(xmlNode *line,
+static svgtiny_code svgtiny_parse_line(dom_element *line,
struct svgtiny_parse_state state);
-static svgtiny_code svgtiny_parse_poly(xmlNode *poly,
+static svgtiny_code svgtiny_parse_poly(dom_element *poly,
struct svgtiny_parse_state state, bool polygon);
-static svgtiny_code svgtiny_parse_text(xmlNode *text,
+static svgtiny_code svgtiny_parse_text(dom_element *text,
struct svgtiny_parse_state state);
-static void svgtiny_parse_position_attributes(const xmlNode *node,
+static void svgtiny_parse_position_attributes(dom_element *node,
const struct svgtiny_parse_state state,
float *x, float *y, float *width, float *height);
-static void svgtiny_parse_paint_attributes(const xmlNode *node,
+static void svgtiny_parse_paint_attributes(dom_element *node,
struct svgtiny_parse_state *state);
-static void svgtiny_parse_font_attributes(const xmlNode *node,
+static void svgtiny_parse_font_attributes(dom_element *node,
struct svgtiny_parse_state *state);
-static void svgtiny_parse_transform_attributes(xmlNode *node,
+static void svgtiny_parse_transform_attributes(dom_element *node,
+ struct svgtiny_parse_state *state);
+static void svgtiny_parse_styles(dom_element *node,
struct svgtiny_parse_state *state);
static svgtiny_code svgtiny_add_path(float *p, unsigned int n,
struct svgtiny_parse_state *state);
+static void _svgtiny_parse_color(const char *s, svgtiny_colour *c,
+ struct svgtiny_parse_state_gradient *grad,
+ struct svgtiny_parse_state *state);
+
+/**
+ * rotate midpoint vector
+ */
+static void
+rotate_midpoint_vector(float ax, float ay,
+ float bx, float by,
+ double radangle,
+ double *x_out, double *y_out)
+{
+ double dx2; /* midpoint x coordinate */
+ double dy2; /* midpoint y coordinate */
+ double cosangle; /* cosine of rotation angle */
+ double sinangle; /* sine of rotation angle */
+
+ /* compute the sin and cos of the angle */
+ cosangle = cos(radangle);
+ sinangle = sin(radangle);
+
+ /* compute the midpoint between start and end points */
+ dx2 = (ax - bx) / 2.0;
+ dy2 = (ay - by) / 2.0;
+
+ /* rotate vector to remove angle */
+ *x_out = ((cosangle * dx2) + (sinangle * dy2));
+ *y_out = ((-sinangle * dx2) + (cosangle * dy2));
+}
+
+
+/**
+ * ensure the arc radii are large enough and scale as appropriate
+ *
+ * the radii need to be large enough if they are not they must be
+ * adjusted. This allows for elimination of differences between
+ * implementations especialy with rounding.
+ */
+static void
+ensure_radii_scale(double x1_sq, double y1_sq,
+ float *rx, float *ry,
+ double *rx_sq, double *ry_sq)
+{
+ double radiisum;
+ double radiiscale;
+
+ /* set radii square values */
+ (*rx_sq) = (*rx) * (*rx);
+ (*ry_sq) = (*ry) * (*ry);
+
+ radiisum = (x1_sq / (*rx_sq)) + (y1_sq / (*ry_sq));
+ if (radiisum > 0.99999) {
+ /* need to scale radii */
+ radiiscale = sqrt(radiisum) * 1.00001;
+ *rx = (float)(radiiscale * (*rx));
+ *ry = (float)(radiiscale * (*ry));
+ /* update squares too */
+ (*rx_sq) = (*rx) * (*rx);
+ (*ry_sq) = (*ry) * (*ry);
+ }
+}
+
+
+/**
+ * compute the transformed centre point
+ */
+static void
+compute_transformed_centre_point(double sign, float rx, float ry,
+ double rx_sq, double ry_sq,
+ double x1, double y1,
+ double x1_sq, double y1_sq,
+ double *cx1, double *cy1)
+{
+ double sq;
+ double coef;
+ sq = ((rx_sq * ry_sq) - (rx_sq * y1_sq) - (ry_sq * x1_sq)) /
+ ((rx_sq * y1_sq) + (ry_sq * x1_sq));
+ sq = (sq < 0) ? 0 : sq;
+
+ coef = (sign * sqrt(sq));
+
+ *cx1 = coef * ((rx * y1) / ry);
+ *cy1 = coef * -((ry * x1) / rx);
+}
+
+
+/**
+ * compute untransformed centre point
+ *
+ * \param ax The first point x coordinate
+ * \param ay The first point y coordinate
+ * \param bx The second point x coordinate
+ * \param ay The second point y coordinate
+ */
+static void
+compute_centre_point(float ax, float ay,
+ float bx, float by,
+ double cx1, double cy1,
+ double radangle,
+ double *x_out, double *y_out)
+{
+ double sx2;
+ double sy2;
+ double cosangle; /* cosine of rotation angle */
+ double sinangle; /* sine of rotation angle */
+
+ /* compute the sin and cos of the angle */
+ cosangle = cos(radangle);
+ sinangle = sin(radangle);
+
+ sx2 = (ax + bx) / 2.0;
+ sy2 = (ay + by) / 2.0;
+
+ *x_out = sx2 + (cosangle * cx1 - sinangle * cy1);
+ *y_out = sy2 + (sinangle * cx1 + cosangle * cy1);
+}
+
+
+/**
+ * compute the angle start and extent
+ */
+static void
+compute_angle_start_extent(float rx, float ry,
+ double x1, double y1,
+ double cx1, double cy1,
+ double *start, double *extent)
+{
+ double sign;
+ double ux;
+ double uy;
+ double vx;
+ double vy;
+ double p, n;
+ double actmp;
+
+ /*
+ * Angle betwen two vectors is +/- acos( u.v / len(u) * len(v))
+ * Where:
+ * '.' is the dot product.
+ * +/- is calculated from the sign of the cross product (u x v)
+ */
+
+ ux = (x1 - cx1) / rx;
+ uy = (y1 - cy1) / ry;
+ vx = (-x1 - cx1) / rx;
+ vy = (-y1 - cy1) / ry;
+
+ /* compute the start angle */
+ /* The angle between (ux, uy) and the 0 angle */
+
+ /* len(u) * len(1,0) == len(u) */
+ n = sqrt((ux * ux) + (uy * uy));
+ /* u.v == (ux,uy).(1,0) == (1 * ux) + (0 * uy) == ux */
+ p = ux;
+ /* u x v == (1 * uy - ux * 0) == uy */
+ sign = (uy < 0) ? -1.0 : 1.0;
+ /* (p >= n) so safe */
+ *start = sign * acos(p / n);
+
+ /* compute the extent angle */
+ n = sqrt(((ux * ux) + (uy * uy)) * ((vx * vx) + (vy * vy)));
+ p = (ux * vx) + (uy * vy);
+ sign = ((ux * vy) - (uy * vx) < 0) ? -1.0f : 1.0f;
+
+ /* arc cos must operate between -1 and 1 */
+ actmp = p / n;
+ if (actmp < -1.0) {
+ *extent = sign * M_PI;
+ } else if (actmp > 1.0) {
+ *extent = 0;
+ } else {
+ *extent = sign * acos(actmp);
+ }
+}
+
+
+/**
+ * converts a circle centered unit circle arc to a series of bezier curves
+ *
+ * Each bezier is stored as six values of three pairs of coordinates
+ *
+ * The beziers are stored without their start point as that is assumed
+ * to be the preceding elements end point.
+ *
+ * \param start The start angle of the arc (in radians)
+ * \param extent The size of the arc (in radians)
+ * \param bzpt The array to store the bezier values in
+ * \return The number of bezier segments output (max 4)
+ */
+static int
+circle_arc_to_bezier(double start, double extent, double *bzpt)
+{
+ int bzsegments;
+ double increment;
+ double controllen;
+ int pos = 0;
+ int segment;
+ double angle;
+ double dx, dy;
+
+ bzsegments = (int) ceil(fabs(extent) / M_PI_2);
+ increment = extent / bzsegments;
+ controllen = 4.0 / 3.0 * sin(increment / 2.0) / (1.0 + cos(increment / 2.0));
+
+ for (segment = 0; segment < bzsegments; segment++) {
+ /* first control point */
+ angle = start + (segment * increment);
+ dx = cos(angle);
+ dy = sin(angle);
+ bzpt[pos++] = dx - controllen * dy;
+ bzpt[pos++] = dy + controllen * dx;
+ /* second control point */
+ angle+=increment;
+ dx = cos(angle);
+ dy = sin(angle);
+ bzpt[pos++] = dx + controllen * dy;
+ bzpt[pos++] = dy - controllen * dx;
+ /* endpoint */
+ bzpt[pos++] = dx;
+ bzpt[pos++] = dy;
+
+ }
+ return bzsegments;
+}
+
+
+/**
+ * transform coordinate list
+ *
+ * perform a scale, rotate and translate on list of coordinates
+ *
+ * scale(rx,ry)
+ * rotate(an)
+ * translate (cx, cy)
+ *
+ * homogeneous transforms
+ *
+ * scaling
+ * | rx 0 0 |
+ * S = | 0 ry 0 |
+ * | 0 0 1 |
+ *
+ * rotate
+ * | cos(an) -sin(an) 0 |
+ * R = | sin(an) cos(an) 0 |
+ * | 0 0 1 |
+ *
+ * {{cos(a), -sin(a) 0}, {sin(a), cos(a),0}, {0,0,1}}
+ *
+ * translate
+ * | 1 0 cx |
+ * T = | 0 1 cy |
+ * | 0 0 1 |
+ *
+ * note order is significat here and the combined matrix is
+ * M = T.R.S
+ *
+ * | cos(an) -sin(an) cx |
+ * T.R = | sin(an) cos(an) cy |
+ * | 0 0 1 |
+ *
+ * | rx * cos(an) ry * -sin(an) cx |
+ * T.R.S = | rx * sin(an) ry * cos(an) cy |
+ * | 0 0 1 |
+ *
+ * {{Cos[a], -Sin[a], c}, {Sin[a], Cos[a], d}, {0, 0, 1}} . {{r, 0, 0}, {0, s, 0}, {0, 0, 1}}
+ *
+ * Each point
+ * | x1 |
+ * P = | y1 |
+ * | 1 |
+ *
+ * output
+ * | x2 |
+ * | y2 | = M . P
+ * | 1 |
+ *
+ * x2 = cx + (rx * x1 * cos(a)) + (ry * y1 * -1 * sin(a))
+ * y2 = cy + (ry * y1 * cos(a)) + (rx * x1 * sin(a))
+ *
+ *
+ * \param rx X scaling to apply
+ * \param ry Y scaling to apply
+ * \param radangle rotation to apply (in radians)
+ * \param cx X translation to apply
+ * \param cy Y translation to apply
+ * \param points The size of the bzpoints array
+ * \param bzpoints an array of x,y values to apply the transform to
+ */
+static void
+scale_rotate_translate_points(double rx, double ry,
+ double radangle,
+ double cx, double cy,
+ int pntsize,
+ double *points)
+{
+ int pnt;
+ double cosangle; /* cosine of rotation angle */
+ double sinangle; /* sine of rotation angle */
+ double rxcosangle, rxsinangle, rycosangle, rynsinangle;
+ double x2,y2;
+
+ /* compute the sin and cos of the angle */
+ cosangle = cos(radangle);
+ sinangle = sin(radangle);
+
+ rxcosangle = rx * cosangle;
+ rxsinangle = rx * sinangle;
+ rycosangle = ry * cosangle;
+ rynsinangle = ry * -1 * sinangle;
+
+ for (pnt = 0; pnt < pntsize; pnt+=2) {
+ x2 = cx + (points[pnt] * rxcosangle) + (points[pnt + 1] * rynsinangle);
+ y2 = cy + (points[pnt + 1] * rycosangle) + (points[pnt] * rxsinangle);
+ points[pnt] = x2;
+ points[pnt + 1] = y2;
+ }
+}
+
+
+/**
+ * convert an svg path arc to a bezier curve
+ *
+ * This function perfoms a transform on the nine arc parameters
+ * (coordinate pairs for start and end together with the radii of the
+ * elipse, the rotation angle and which of the four arcs to draw)
+ * which generates the parameters (coordinate pairs for start,
+ * end and their control points) for a set of up to four bezier curves.
+ *
+ * Obviously the start and end coordinates are not altered between
+ * representations so the aim is to calculate the coordinate pairs for
+ * the bezier control points.
+ *
+ * \param bzpoints the array to fill with bezier curves
+ * \return the number of bezier segments generated or -1 for a line
+ */
+static int
+svgarc_to_bezier(float start_x,
+ float start_y,
+ float end_x,
+ float end_y,
+ float rx,
+ float ry,
+ float angle,
+ bool largearc,
+ bool sweep,
+ double *bzpoints)
+{
+ double radangle; /* normalised elipsis rotation angle in radians */
+ double rx_sq; /* x radius squared */
+ double ry_sq; /* y radius squared */
+ double x1, y1; /* rotated midpoint vector */
+ double x1_sq, y1_sq; /* x1 vector squared */
+ double cx1,cy1; /* transformed circle center */
+ double cx,cy; /* circle center */
+ double start, extent;
+ int bzsegments;
+
+ if ((start_x == end_x) && (start_y == end_y)) {
+ /*
+ * if the start and end coordinates are the same the
+ * svg spec says this is equivalent to having no segment
+ * at all
+ */
+ return 0;
+ }
+
+ if ((rx == 0) || (ry == 0)) {
+ /*
+ * if either radii is zero the specified behaviour is a line
+ */
+ return -1;
+ }
+
+ /* obtain the absolute values of the radii */
+ rx = fabsf(rx);
+ ry = fabsf(ry);
+
+ /* convert normalised angle to radians */
+ radangle = degToRad(fmod(angle, 360.0));
+
+ /* step 1 */
+ /* x1,x2 is the midpoint vector rotated to remove the arc angle */
+ rotate_midpoint_vector(start_x, start_y, end_x, end_y, radangle, &x1, &y1);
+
+ /* step 2 */
+ /* get squared x1 values */
+ x1_sq = x1 * x1;
+ y1_sq = y1 * y1;
+
+ /* ensure radii are correctly scaled */
+ ensure_radii_scale(x1_sq, y1_sq, &rx, &ry, &rx_sq, &ry_sq);
+
+ /* compute the transformed centre point */
+ compute_transformed_centre_point(largearc == sweep?-1:1,
+ rx, ry,
+ rx_sq, ry_sq,
+ x1, y1,
+ x1_sq, y1_sq,
+ &cx1, &cy1);
+
+ /* step 3 */
+ /* get the untransformed centre point */
+ compute_centre_point(start_x, start_y,
+ end_x, end_y,
+ cx1, cy1,
+ radangle,
+ &cx, &cy);
+
+ /* step 4 */
+ /* compute anglestart and extent */
+ compute_angle_start_extent(rx,ry,
+ x1,y1,
+ cx1, cy1,
+ &start, &extent);
+
+ /* extent of 0 is a straight line */
+ if (extent == 0) {
+ return -1;
+ }
+
+ /* take account of sweep */
+ if (!sweep && extent > 0) {
+ extent -= TAU;
+ } else if (sweep && extent < 0) {
+ extent += TAU;
+ }
+
+ /* normalise start and extent */
+ extent = fmod(extent, TAU);
+ start = fmod(start, TAU);
+
+ /* convert the arc to unit circle bezier curves */
+ bzsegments = circle_arc_to_bezier(start, extent, bzpoints);
+
+ /* transform the bezier curves */
+ scale_rotate_translate_points(rx, ry,
+ radangle,
+ cx, cy,
+ bzsegments * 6,
+ bzpoints);
+
+ return bzsegments;
+}
+
+
+/**
+ * Call this to ref the strings in a gradient state.
+ */
+static void svgtiny_grad_string_ref(struct svgtiny_parse_state_gradient *grad)
+{
+ if (grad->gradient_x1 != NULL) {
+ dom_string_ref(grad->gradient_x1);
+ }
+ if (grad->gradient_y1 != NULL) {
+ dom_string_ref(grad->gradient_y1);
+ }
+ if (grad->gradient_x2 != NULL) {
+ dom_string_ref(grad->gradient_x2);
+ }
+ if (grad->gradient_y2 != NULL) {
+ dom_string_ref(grad->gradient_y2);
+ }
+}
+
+/**
+ * Call this to clean up the strings in a gradient state.
+ */
+static void svgtiny_grad_string_cleanup(
+ struct svgtiny_parse_state_gradient *grad)
+{
+ if (grad->gradient_x1 != NULL) {
+ dom_string_unref(grad->gradient_x1);
+ grad->gradient_x1 = NULL;
+ }
+ if (grad->gradient_y1 != NULL) {
+ dom_string_unref(grad->gradient_y1);
+ grad->gradient_y1 = NULL;
+ }
+ if (grad->gradient_x2 != NULL) {
+ dom_string_unref(grad->gradient_x2);
+ grad->gradient_x2 = NULL;
+ }
+ if (grad->gradient_y2 != NULL) {
+ dom_string_unref(grad->gradient_y2);
+ grad->gradient_y2 = NULL;
+ }
+}
+
+/**
+ * Set the local externally-stored parts of a parse state.
+ * Call this in functions that made a new state on the stack.
+ * Doesn't make own copy of global state, such as the interned string list.
+ */
+static void svgtiny_setup_state_local(struct svgtiny_parse_state *state)
+{
+ svgtiny_grad_string_ref(&(state->fill_grad));
+ svgtiny_grad_string_ref(&(state->stroke_grad));
+}
+
+/**
+ * Cleanup the local externally-stored parts of a parse state.
+ * Call this in functions that made a new state on the stack.
+ * Doesn't cleanup global state, such as the interned string list.
+ */
+static void svgtiny_cleanup_state_local(struct svgtiny_parse_state *state)
+{
+ svgtiny_grad_string_cleanup(&(state->fill_grad));
+ svgtiny_grad_string_cleanup(&(state->stroke_grad));
+}
/**
{
struct svgtiny_diagram *diagram;
- diagram = malloc(sizeof *diagram);
+ diagram = calloc(sizeof(*diagram), 1);
if (!diagram)
return 0;
- diagram->shape = 0;
- diagram->shape_count = 0;
- diagram->error_line = 0;
- diagram->error_message = 0;
-
return diagram;
+ free(diagram);
+ return NULL;
}
+static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...)
+{
+ UNUSED(severity);
+ UNUSED(ctx);
+ UNUSED(msg);
+}
/**
* Parse a block of memory into a svgtiny_diagram.
const char *buffer, size_t size, const char *url,
int viewport_width, int viewport_height)
{
- xmlDoc *document;
- xmlNode *svg;
+ css_error css_code;
+ dom_document *document;
+ dom_exception exc;
+ dom_xml_parser *parser;
+ dom_xml_error err;
+ dom_element *svg;
+ dom_string *svg_name;
+ lwc_string *svg_name_lwc;
struct svgtiny_parse_state state;
float x, y, width, height;
svgtiny_code code;
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;
+ UNUSED(url);
+
+ parser = dom_xml_parser_create(NULL, NULL,
+ ignore_msg, NULL, &document);
- /*xmlDebugDumpDocument(stderr, document);*/
+ if (parser == NULL)
+ return svgtiny_LIBDOM_ERROR;
+
+ err = dom_xml_parser_parse_chunk(parser, (uint8_t *)buffer, size);
+ if (err != DOM_XML_OK) {
+ dom_node_unref(document);
+ dom_xml_parser_destroy(parser);
+ return svgtiny_LIBDOM_ERROR;
+ }
+
+ err = dom_xml_parser_completed(parser);
+ if (err != DOM_XML_OK) {
+ dom_node_unref(document);
+ dom_xml_parser_destroy(parser);
+ return svgtiny_LIBDOM_ERROR;
+ }
+
+ /* We're done parsing, drop the parser.
+ * We now own the document entirely.
+ */
+ dom_xml_parser_destroy(parser);
/* find root <svg> element */
- svg = xmlDocGetRootElement(document);
- if (!svg)
- return svgtiny_NOT_SVG;
- if (strcmp((const char *) svg->name, "svg") != 0)
+ exc = dom_document_get_document_element(document, &svg);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(document);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (svg == NULL) {
+ /* no root svg element */
+ dom_node_unref(document);
+ return svgtiny_SVG_ERROR;
+ }
+
+ exc = dom_node_get_node_name(svg, &svg_name);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(svg);
+ dom_node_unref(document);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (lwc_intern_string("svg", 3 /* SLEN("svg") */,
+ &svg_name_lwc) != lwc_error_ok) {
+ dom_string_unref(svg_name);
+ dom_node_unref(svg);
+ dom_node_unref(document);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (!dom_string_caseless_lwc_isequal(svg_name, svg_name_lwc)) {
+ lwc_string_unref(svg_name_lwc);
+ dom_string_unref(svg_name);
+ dom_node_unref(svg);
+ dom_node_unref(document);
return svgtiny_NOT_SVG;
+ }
+
+ lwc_string_unref(svg_name_lwc);
+ dom_string_unref(svg_name);
+
+ /* initialize the state struct with zeros */
+ memset(&state, 0, sizeof(state));
/* get graphic dimensions */
state.diagram = diagram;
state.document = document;
state.viewport_width = viewport_width;
state.viewport_height = viewport_height;
+
+ /* Initialize CSS context */
+ css_code = css_select_ctx_create(&state.select_ctx);
+ if (css_code != CSS_OK) {
+ dom_node_unref(svg);
+ dom_node_unref(document);
+ return svgtiny_LIBCSS_ERROR;
+ }
+
+#define SVGTINY_STRING_ACTION2(s,n) \
+ if (dom_string_create_interned((const uint8_t *) #n, \
+ strlen(#n), &state.interned_##s) \
+ != DOM_NO_ERR) { \
+ code = svgtiny_LIBDOM_ERROR; \
+ goto cleanup; \
+ }
+#include "svgtiny_strings.h"
+#undef SVGTINY_STRING_ACTION2
+
+ /* Intern SVG's xmlns separately because it's an lwc_string
+ * and not a dom_string. We initialize its pointer to NULL
+ * because the "cleanup:" test to see if it needs to be free'd
+ * looks for NULL. Returning a LIBDOM_ERROR on failure is not
+ * perfect but it's the closest of the available options. */
+ state.interned_svg_xmlns = NULL;
+ if (lwc_intern_string("http://www.w3.org/2000/svg",
+ 26,
+ &state.interned_svg_xmlns) != lwc_error_ok) {
+ code = svgtiny_LIBDOM_ERROR;
+ goto cleanup;
+ }
+
svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
diagram->width = width;
diagram->height = height;
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);
+ code = svgtiny_preparse_styles(svg, state);
+ if (code == svgtiny_OK) {
+ code = svgtiny_parse_svg(svg, state);
+ }
+
+ dom_node_unref(svg);
+ dom_node_unref(document);
+ css_code = css_select_ctx_destroy(state.select_ctx);
+ if (css_code != CSS_OK) {
+ code = svgtiny_LIBCSS_ERROR;
+ }
- /* free XML tree */
- xmlFreeDoc(document);
+cleanup:
+ svgtiny_cleanup_state_local(&state);
+#define SVGTINY_STRING_ACTION2(s,n) \
+ if (state.interned_##s != NULL) \
+ dom_string_unref(state.interned_##s);
+#include "svgtiny_strings.h"
+#undef SVGTINY_STRING_ACTION2
+
+ if (state.interned_svg_xmlns != NULL) {
+ lwc_string_unref(state.interned_svg_xmlns);
+ }
return code;
}
/**
- * Parse a <svg> or <g> element node.
+ * Parse a single <style> element, appending the result to the CSS
+ * select context within the given parser state.
*/
+svgtiny_code svgtiny_parse_style_element(dom_element *style,
+ struct svgtiny_parse_state state)
+{
+ css_stylesheet *sheet;
+ css_error code;
+ dom_exception exc;
-svgtiny_code svgtiny_parse_svg(xmlNode *svg,
+ code = svgtiny_create_stylesheet(&sheet, false);
+ if (code != CSS_OK) {
+ return svgtiny_LIBCSS_ERROR;
+ }
+
+ /* Parse the style element's "media" attribute if it has
+ one. We don't do anything with it right now. */
+ dom_string *media_attr;
+ exc = dom_element_get_attribute(style, state.interned_media,
+ &media_attr);
+ if (exc != DOM_NO_ERR) {
+ css_stylesheet_destroy(sheet);
+ return svgtiny_LIBDOM_ERROR;
+ }
+
+ if (media_attr) {
+ /* Here's where we'd actually change the media type if
+ we were going to use it */
+ dom_string_unref(media_attr);
+ }
+
+ dom_string *data;
+ dom_node_get_text_content(style, &data);
+ if (data == NULL) {
+ /* Empty stylesheet? That's fine. */
+ css_stylesheet_destroy(sheet);
+ return svgtiny_OK;
+ }
+
+ code = css_stylesheet_append_data(sheet,
+ (uint8_t *)dom_string_data(data),
+ dom_string_byte_length(data));
+ if (code != CSS_OK && code != CSS_NEEDDATA) {
+ dom_string_unref(data);
+ css_stylesheet_destroy(sheet);
+ return svgtiny_LIBCSS_ERROR;
+ }
+
+ code = css_stylesheet_data_done(sheet);
+ if (code != CSS_OK) {
+ dom_string_unref(data);
+ css_stylesheet_destroy(sheet);
+ return svgtiny_LIBCSS_ERROR;
+ }
+
+ code = css_select_ctx_append_sheet(state.select_ctx,
+ sheet,
+ CSS_ORIGIN_AUTHOR,
+ NULL);
+ if (code != CSS_OK) {
+ dom_string_unref(data);
+ return svgtiny_LIBCSS_ERROR;
+ }
+
+ dom_string_unref(data);
+ return svgtiny_OK;
+}
+
+
+/**
+ * Parse the contents of an inline style and return (a pointer to) the
+ * corresponding stylesheet for use with css_select_style(). Returns
+ * NULL if anything goes wrong.
+ */
+css_stylesheet *svgtiny_parse_style_inline(const uint8_t *data,
+ size_t len)
+{
+ css_stylesheet *sheet;
+ css_error code;
+
+ code = svgtiny_create_stylesheet(&sheet, true);
+ if (code != CSS_OK) {
+ return NULL;
+ }
+
+ code = css_stylesheet_append_data(sheet, data, len);
+ if (code != CSS_OK && code != CSS_NEEDDATA) {
+ css_stylesheet_destroy(sheet);
+ return NULL;
+ }
+
+ code = css_stylesheet_data_done(sheet);
+ if (code != CSS_OK) {
+ css_stylesheet_destroy(sheet);
+ return NULL;
+ }
+
+ return sheet;
+}
+
+/**
+ * Parse all <style> elements within a root <svg> element. This
+ * should be called before svgtiny_parse_svg() because that function
+ * makes a single pass through the document and we'd like all style
+ * information to be available during that pass. Specifically, we'd
+ * like a <style> sheet at the end of the document to affect the
+ * rendering of elements at its beginning.
+ *
+ * The element-parsing inner loop here is essentially the same as
+ * that within svgtiny_parse_svg().
+ */
+svgtiny_code svgtiny_preparse_styles(dom_element *svg,
+ struct svgtiny_parse_state state)
+{
+ dom_element *child;
+ dom_exception exc;
+
+ exc = dom_node_get_first_child(svg, (dom_node **) (void *) &child);
+ if (exc != DOM_NO_ERR) {
+ return svgtiny_LIBDOM_ERROR;
+ }
+ while (child != NULL) {
+ dom_element *next;
+ dom_node_type nodetype;
+ svgtiny_code code = svgtiny_OK;
+
+ exc = dom_node_get_node_type(child, &nodetype);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(child);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (nodetype == DOM_ELEMENT_NODE) {
+ dom_string *nodename;
+ exc = dom_node_get_node_name(child, &nodename);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(child);
+ return svgtiny_LIBDOM_ERROR;
+ }
+
+ if (dom_string_caseless_isequal(state.interned_style,
+ nodename)) {
+ /* We have a <style> element, parse it */
+ code = svgtiny_parse_style_element(child,
+ state);
+ }
+
+
+ dom_string_unref(nodename);
+ }
+ if (code != svgtiny_OK) {
+ dom_node_unref(child);
+ return code;
+ }
+ exc = dom_node_get_next_sibling(child,
+ (dom_node **) (void *) &next);
+ dom_node_unref(child);
+ if (exc != DOM_NO_ERR) {
+ return svgtiny_LIBDOM_ERROR;
+ }
+ child = next;
+ }
+
+ return svgtiny_OK;
+}
+
+/**
+ * Parse <svg>, <g>, and <a> element nodes.
+ */
+
+svgtiny_code svgtiny_parse_svg(dom_element *svg,
struct svgtiny_parse_state state)
{
float x, y, width, height;
+ dom_string *view_box;
+ dom_element *child;
+ dom_exception exc;
+
+ svgtiny_setup_state_local(&state);
svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
svgtiny_parse_paint_attributes(svg, &state);
svgtiny_parse_font_attributes(svg, &state);
+ svgtiny_parse_styles(svg, &state);
+
+ exc = dom_element_get_attribute(svg, state.interned_viewBox,
+ &view_box);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
- /* parse viewBox */
- xmlAttr *view_box = xmlHasProp(svg, (const xmlChar *) "viewBox");
if (view_box) {
- const char *s = (const char *) view_box->children->content;
+ char *s = strndup(dom_string_data(view_box),
+ dom_string_byte_length(view_box));
float min_x, min_y, vwidth, vheight;
if (sscanf(s, "%f,%f,%f,%f",
&min_x, &min_y, &vwidth, &vheight) == 4 ||
state.ctm.e += -min_x * state.ctm.a;
state.ctm.f += -min_y * state.ctm.d;
}
+ free(s);
+ dom_string_unref(view_box);
}
svgtiny_parse_transform_attributes(svg, &state);
- for (xmlNode *child = svg->children; child; child = child->next) {
+ exc = dom_node_get_first_child(svg, (dom_node **) (void *) &child);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ while (child != NULL) {
+ dom_element *next;
+ dom_node_type nodetype;
svgtiny_code code = svgtiny_OK;
- if (child->type == XML_ELEMENT_NODE) {
- const char *name = (const char *) child->name;
- if (strcmp(name, "svg") == 0)
+ exc = dom_node_get_node_type(child, &nodetype);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(child);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (nodetype == DOM_ELEMENT_NODE) {
+ dom_string *nodename;
+ exc = dom_node_get_node_name(child, &nodename);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(child);
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (dom_string_caseless_isequal(state.interned_svg,
+ nodename))
code = svgtiny_parse_svg(child, state);
- else if (strcmp(name, "g") == 0)
+ else if (dom_string_caseless_isequal(state.interned_g,
+ nodename))
code = svgtiny_parse_svg(child, state);
- else if (strcmp(name, "a") == 0)
+ else if (dom_string_caseless_isequal(state.interned_a,
+ nodename))
code = svgtiny_parse_svg(child, state);
- else if (strcmp(name, "path") == 0)
+ else if (dom_string_caseless_isequal(state.interned_path,
+ nodename))
code = svgtiny_parse_path(child, state);
- else if (strcmp(name, "rect") == 0)
+ else if (dom_string_caseless_isequal(state.interned_rect,
+ nodename))
code = svgtiny_parse_rect(child, state);
- else if (strcmp(name, "circle") == 0)
+ else if (dom_string_caseless_isequal(state.interned_circle,
+ nodename))
code = svgtiny_parse_circle(child, state);
- else if (strcmp(name, "line") == 0)
+ else if (dom_string_caseless_isequal(state.interned_ellipse,
+ nodename))
+ code = svgtiny_parse_ellipse(child, state);
+ else if (dom_string_caseless_isequal(state.interned_line,
+ nodename))
code = svgtiny_parse_line(child, state);
- else if (strcmp(name, "polyline") == 0)
+ else if (dom_string_caseless_isequal(state.interned_polyline,
+ nodename))
code = svgtiny_parse_poly(child, state, false);
- else if (strcmp(name, "polygon") == 0)
+ else if (dom_string_caseless_isequal(state.interned_polygon,
+ nodename))
code = svgtiny_parse_poly(child, state, true);
- else if (strcmp(name, "text") == 0)
+ else if (dom_string_caseless_isequal(state.interned_text,
+ nodename))
code = svgtiny_parse_text(child, state);
+ dom_string_unref(nodename);
}
-
- if (code != svgtiny_OK)
+ if (code != svgtiny_OK) {
+ dom_node_unref(child);
+ svgtiny_cleanup_state_local(&state);
return code;
+ }
+ exc = dom_node_get_next_sibling(child,
+ (dom_node **) (void *) &next);
+ dom_node_unref(child);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ child = next;
}
+ svgtiny_cleanup_state_local(&state);
return svgtiny_OK;
}
* http://www.w3.org/TR/SVG11/paths#PathElement
*/
-svgtiny_code svgtiny_parse_path(xmlNode *path,
+svgtiny_code svgtiny_parse_path(dom_element *path,
struct svgtiny_parse_state state)
{
+ svgtiny_code err;
+ dom_string *path_d_str;
+ dom_exception exc;
char *s, *path_d;
+ float *p; /* path elemets */
+ unsigned int palloc; /* number of path elements allocated */
+ 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;
+ float subpath_first_x = 0, subpath_first_y = 0;
+
+ svgtiny_setup_state_local(&state);
svgtiny_parse_paint_attributes(path, &state);
svgtiny_parse_transform_attributes(path, &state);
+ svgtiny_parse_styles(path, &state);
/* read d attribute */
- s = path_d = (char *) xmlGetProp(path, (const xmlChar *) "d");
- if (!s) {
- state.diagram->error_line = path->line;
+ exc = dom_element_get_attribute(path, state.interned_d, &path_d_str);
+ if (exc != DOM_NO_ERR) {
+ state.diagram->error_line = -1; /* path->line; */
+ state.diagram->error_message = "path: error retrieving d attribute";
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_SVG_ERROR;
+ }
+
+ if (path_d_str == NULL) {
+ state.diagram->error_line = -1; /* path->line; */
state.diagram->error_message = "path: missing d attribute";
+ svgtiny_cleanup_state_local(&state);
return svgtiny_SVG_ERROR;
}
- /* allocate space for path: it will never have more elements than d */
- float *p = malloc(sizeof p[0] * strlen(s));
- if (!p)
+ /* empty path is permitted it just disables the path */
+ palloc = dom_string_byte_length(path_d_str);
+ if (palloc == 0) {
+ dom_string_unref(path_d_str);
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_OK;
+ }
+
+ /* local copy of the path data allowing in-place modification */
+ s = path_d = strndup(dom_string_data(path_d_str), palloc);
+ dom_string_unref(path_d_str);
+ if (s == NULL) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_OUT_OF_MEMORY;
+ }
+
+ /* ensure path element allocation is sensibly bounded */
+ if (palloc < 8) {
+ palloc = 8;
+ } else if (palloc > 64) {
+ palloc = palloc / 2;
+ }
+
+ /* allocate initial space for path elements */
+ p = malloc(sizeof p[0] * palloc);
+ if (p == NULL) {
+ free(path_d);
+ svgtiny_cleanup_state_local(&state);
return svgtiny_OUT_OF_MEMORY;
+ }
/* parse d and build path */
- for (unsigned int i = 0; s[i]; i++)
+ for (i = 0; s[i]; i++)
if (s[i] == ',')
s[i] = ' ';
- unsigned int i = 0;
- 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;
+ 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;
+ /* Ensure there is sufficient space for path elements */
+#define ALLOC_PATH_ELEMENTS(NUM_ELEMENTS) \
+ do { \
+ if ((palloc - i) < NUM_ELEMENTS) { \
+ float *tp; \
+ palloc = (palloc * 2) + (palloc / 2); \
+ tp = realloc(p, sizeof p[0] * palloc); \
+ if (tp == NULL) { \
+ free(p); \
+ free(path_d); \
+ svgtiny_cleanup_state_local(&state); \
+ return svgtiny_OUT_OF_MEMORY; \
+ } \
+ p = tp; \
+ } \
+ } while(0)
+
+
/* 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"));*/
else
plot_command = svgtiny_PATH_LINE;
do {
+ ALLOC_PATH_ELEMENTS(3);
p[i++] = plot_command;
if ('a' <= *command) {
x += last_x;
y += last_y;
}
+ if (plot_command == svgtiny_PATH_MOVE) {
+ subpath_first_x = x;
+ subpath_first_y = y;
+ }
p[i++] = last_cubic_x = last_quad_x = last_x
= x;
p[i++] = last_cubic_y = last_quad_y = last_y
/* closepath (Z, z) (no arguments) */
} else if (sscanf(s, " %1[Zz] %n", command, &n) == 1) {
/*LOG(("closepath"));*/
+ ALLOC_PATH_ELEMENTS(1);
+
p[i++] = svgtiny_PATH_CLOSE;
s += n;
+ last_cubic_x = last_quad_x = last_x = subpath_first_x;
+ last_cubic_y = last_quad_y = last_y = subpath_first_y;
/* horizontal lineto (H, h) (1 argument) */
} else if (sscanf(s, " %1[Hh] %f %n", command, &x, &n) == 2) {
/*LOG(("horizontal lineto"));*/
do {
+ ALLOC_PATH_ELEMENTS(3);
+
p[i++] = svgtiny_PATH_LINE;
if (*command == 'h')
x += last_x;
} else if (sscanf(s, " %1[Vv] %f %n", command, &y, &n) == 2) {
/*LOG(("vertical lineto"));*/
do {
+ ALLOC_PATH_ELEMENTS(3);
+
p[i++] = svgtiny_PATH_LINE;
if (*command == 'v')
y += last_y;
p[i++] = last_cubic_y = last_quad_y = last_y
= y;
s += n;
- } while (sscanf(s, "%f %n", &x, &n) == 1);
+ } while (sscanf(s, "%f %n", &y, &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 {
+ ALLOC_PATH_ELEMENTS(7);
+
p[i++] = svgtiny_PATH_BEZIER;
if (*command == 'c') {
x1 += last_x;
&x2, &y2, &x, &y, &n) == 5) {
/*LOG(("shorthand/smooth curveto"));*/
do {
+ ALLOC_PATH_ELEMENTS(7);
+
p[i++] = svgtiny_PATH_BEZIER;
x1 = last_x + (last_x - last_cubic_x);
y1 = last_y + (last_y - last_cubic_y);
&x1, &y1, &x, &y, &n) == 5) {
/*LOG(("quadratic Bezier curveto"));*/
do {
+ ALLOC_PATH_ELEMENTS(7);
+
p[i++] = svgtiny_PATH_BEZIER;
last_quad_x = x1;
last_quad_y = y1;
&x, &y, &n) == 3) {
/*LOG(("shorthand/smooth quadratic Bezier curveto"));*/
do {
+ ALLOC_PATH_ELEMENTS(7);
+
p[i++] = svgtiny_PATH_BEZIER;
x1 = last_x + (last_x - last_quad_x);
y1 = last_y + (last_y - last_quad_y);
&rx, &ry, &rotation, &large_arc, &sweep,
&x, &y, &n) == 8) {
do {
- p[i++] = svgtiny_PATH_LINE;
+ int bzsegments;
+ double bzpoints[6*4]; /* allow for up to four bezier segments per arc */
+
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;
+
+ bzsegments = svgarc_to_bezier(last_x, last_y,
+ x, y,
+ rx, ry,
+ rotation,
+ large_arc,
+ sweep,
+ bzpoints);
+ if (bzsegments == -1) {
+ /* draw a line */
+ ALLOC_PATH_ELEMENTS(3);
+ p[i++] = svgtiny_PATH_LINE;
+ p[i++] = x;
+ p[i++] = y;
+ } else if (bzsegments > 0) {
+ int bzpnt;
+ ALLOC_PATH_ELEMENTS((unsigned int)bzsegments * 7);
+ for (bzpnt = 0;bzpnt < (bzsegments * 6); bzpnt+=6) {
+ p[i++] = svgtiny_PATH_BEZIER;
+ p[i++] = bzpoints[bzpnt];
+ p[i++] = bzpoints[bzpnt+1];
+ p[i++] = bzpoints[bzpnt+2];
+ p[i++] = bzpoints[bzpnt+3];
+ p[i++] = bzpoints[bzpnt+4];
+ p[i++] = bzpoints[bzpnt+5];
+ }
+ }
+ if (bzsegments != 0) {
+ last_cubic_x = last_quad_x = last_x = p[i-2];
+ last_cubic_y = last_quad_y = last_y = p[i-1];
+ }
+
+
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);
+ /* fprintf(stderr, "parse failed at \"%s\"\n", s); */
break;
}
}
- xmlFree(path_d);
+ free(path_d);
if (i <= 4) {
/* no real segments in path */
free(p);
+ svgtiny_cleanup_state_local(&state);
return svgtiny_OK;
}
- return svgtiny_add_path(p, i, &state);
+ /* resize path element array to not be over allocated */
+ if (palloc != i) {
+ float *tp;
+
+ /* try the resize, if it fails just continue to use the old
+ * allocation
+ */
+ tp = realloc(p, sizeof p[0] * i);
+ if (tp != NULL) {
+ p = tp;
+ }
+ }
+
+ err = svgtiny_add_path(p, i, &state);
+
+ svgtiny_cleanup_state_local(&state);
+
+ return err;
}
* http://www.w3.org/TR/SVG11/shapes#RectElement
*/
-svgtiny_code svgtiny_parse_rect(xmlNode *rect,
+svgtiny_code svgtiny_parse_rect(dom_element *rect,
struct svgtiny_parse_state state)
{
+ svgtiny_code err;
float x, y, width, height;
+ float *p;
+
+ svgtiny_setup_state_local(&state);
svgtiny_parse_position_attributes(rect, state,
&x, &y, &width, &height);
svgtiny_parse_paint_attributes(rect, &state);
svgtiny_parse_transform_attributes(rect, &state);
+ svgtiny_parse_styles(rect, &state);
- float *p = malloc(13 * sizeof p[0]);
- if (!p)
+ p = malloc(13 * sizeof p[0]);
+ if (!p) {
+ svgtiny_cleanup_state_local(&state);
return svgtiny_OUT_OF_MEMORY;
+ }
p[0] = svgtiny_PATH_MOVE;
p[1] = x;
p[11] = y + height;
p[12] = svgtiny_PATH_CLOSE;
- return svgtiny_add_path(p, 13, &state);
+ err = svgtiny_add_path(p, 13, &state);
+
+ svgtiny_cleanup_state_local(&state);
+
+ return err;
}
* Parse a <circle> element node.
*/
-svgtiny_code svgtiny_parse_circle(xmlNode *circle,
+svgtiny_code svgtiny_parse_circle(dom_element *circle,
struct svgtiny_parse_state state)
{
- float x = 0, y = 0, r = 0;
- const float kappa = 0.5522847498;
-
- for (xmlAttr *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_code err;
+ float x = 0, y = 0, r = -1;
+ float *p;
+ dom_string *attr;
+ dom_exception exc;
+
+ svgtiny_setup_state_local(&state);
+
+ exc = dom_element_get_attribute(circle, state.interned_cx, &attr);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (attr != NULL) {
+ x = svgtiny_parse_length(attr, state.viewport_width, state);
+ }
+ dom_string_unref(attr);
+
+ exc = dom_element_get_attribute(circle, state.interned_cy, &attr);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (attr != NULL) {
+ y = svgtiny_parse_length(attr, state.viewport_height, state);
+ }
+ dom_string_unref(attr);
+
+ exc = dom_element_get_attribute(circle, state.interned_r, &attr);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (attr != NULL) {
+ r = svgtiny_parse_length(attr, state.viewport_width, state);
+ }
+ dom_string_unref(attr);
+
svgtiny_parse_paint_attributes(circle, &state);
svgtiny_parse_transform_attributes(circle, &state);
+ svgtiny_parse_styles(circle, &state);
+
+ if (r < 0) {
+ state.diagram->error_line = -1; /* circle->line; */
+ state.diagram->error_message = "circle: r missing or negative";
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_SVG_ERROR;
+ }
+ if (r == 0) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_OK;
+ }
- float *p = malloc(32 * sizeof p[0]);
- if (!p)
+ p = malloc(32 * sizeof p[0]);
+ if (!p) {
+ svgtiny_cleanup_state_local(&state);
return svgtiny_OUT_OF_MEMORY;
+ }
p[0] = svgtiny_PATH_MOVE;
- p[1] = x - r;
+ 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[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[11] = x - r * KAPPA;
p[12] = y + r;
- p[13] = x + r;
- p[14] = y + r * kappa;
- p[15] = x + 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[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[25] = x + r * KAPPA;
p[26] = y - r;
- p[27] = x - r;
- p[28] = y - r * kappa;
- p[29] = x - r;
+ p[27] = x + r;
+ p[28] = y - r * KAPPA;
+ p[29] = x + r;
+ p[30] = y;
+ p[31] = svgtiny_PATH_CLOSE;
+
+ err = svgtiny_add_path(p, 32, &state);
+
+ svgtiny_cleanup_state_local(&state);
+
+ return err;
+}
+
+
+/**
+ * Parse an <ellipse> element node.
+ */
+
+svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
+ struct svgtiny_parse_state state)
+{
+ svgtiny_code err;
+ float x = 0, y = 0, rx = -1, ry = -1;
+ float *p;
+ dom_string *attr;
+ dom_exception exc;
+
+ svgtiny_setup_state_local(&state);
+
+ exc = dom_element_get_attribute(ellipse, state.interned_cx, &attr);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (attr != NULL) {
+ x = svgtiny_parse_length(attr, state.viewport_width, state);
+ }
+ dom_string_unref(attr);
+
+ exc = dom_element_get_attribute(ellipse, state.interned_cy, &attr);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (attr != NULL) {
+ y = svgtiny_parse_length(attr, state.viewport_height, state);
+ }
+ dom_string_unref(attr);
+
+ exc = dom_element_get_attribute(ellipse, state.interned_rx, &attr);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (attr != NULL) {
+ rx = svgtiny_parse_length(attr, state.viewport_width, state);
+ }
+ dom_string_unref(attr);
+
+ exc = dom_element_get_attribute(ellipse, state.interned_ry, &attr);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (attr != NULL) {
+ ry = svgtiny_parse_length(attr, state.viewport_width, state);
+ }
+ dom_string_unref(attr);
+
+ svgtiny_parse_paint_attributes(ellipse, &state);
+ svgtiny_parse_transform_attributes(ellipse, &state);
+ svgtiny_parse_styles(ellipse, &state);
+
+ if (rx < 0 || ry < 0) {
+ state.diagram->error_line = -1; /* ellipse->line; */
+ state.diagram->error_message = "ellipse: rx or ry missing "
+ "or negative";
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_SVG_ERROR;
+ }
+ if (rx == 0 || ry == 0) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_OK;
+ }
+
+ p = malloc(32 * sizeof p[0]);
+ if (!p) {
+ svgtiny_cleanup_state_local(&state);
+ 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);
+
+ err = svgtiny_add_path(p, 32, &state);
+
+ svgtiny_cleanup_state_local(&state);
+
+ return err;
}
* Parse a <line> element node.
*/
-svgtiny_code svgtiny_parse_line(xmlNode *line,
+svgtiny_code svgtiny_parse_line(dom_element *line,
struct svgtiny_parse_state state)
{
+ svgtiny_code err;
float x1 = 0, y1 = 0, x2 = 0, y2 = 0;
+ float *p;
+ dom_string *attr;
+ dom_exception exc;
+
+ svgtiny_setup_state_local(&state);
+
+ exc = dom_element_get_attribute(line, state.interned_x1, &attr);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (attr != NULL) {
+ x1 = svgtiny_parse_length(attr, state.viewport_width, state);
+ }
+ dom_string_unref(attr);
+
+ exc = dom_element_get_attribute(line, state.interned_y1, &attr);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (attr != NULL) {
+ y1 = svgtiny_parse_length(attr, state.viewport_height, state);
+ }
+ dom_string_unref(attr);
+
+ exc = dom_element_get_attribute(line, state.interned_x2, &attr);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (attr != NULL) {
+ x2 = svgtiny_parse_length(attr, state.viewport_width, state);
+ }
+ dom_string_unref(attr);
+
+ exc = dom_element_get_attribute(line, state.interned_y2, &attr);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (attr != NULL) {
+ y2 = svgtiny_parse_length(attr, state.viewport_height, state);
+ }
+ dom_string_unref(attr);
- for (xmlAttr *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);
+ svgtiny_parse_styles(line, &state);
- float *p = malloc(7 * sizeof p[0]);
- if (!p)
+ p = malloc(7 * sizeof p[0]);
+ if (!p) {
+ svgtiny_cleanup_state_local(&state);
return svgtiny_OUT_OF_MEMORY;
+ }
p[0] = svgtiny_PATH_MOVE;
p[1] = x1;
p[5] = y2;
p[6] = svgtiny_PATH_CLOSE;
- return svgtiny_add_path(p, 7, &state);
+ err = svgtiny_add_path(p, 7, &state);
+
+ svgtiny_cleanup_state_local(&state);
+
+ return err;
}
* http://www.w3.org/TR/SVG11/shapes#PolygonElement
*/
-svgtiny_code svgtiny_parse_poly(xmlNode *poly,
+svgtiny_code svgtiny_parse_poly(dom_element *poly,
struct svgtiny_parse_state state, bool polygon)
{
+ svgtiny_code err;
+ dom_string *points_str;
+ dom_exception exc;
char *s, *points;
+ float *p;
+ unsigned int i;
+
+ svgtiny_setup_state_local(&state);
svgtiny_parse_paint_attributes(poly, &state);
svgtiny_parse_transform_attributes(poly, &state);
+ svgtiny_parse_styles(poly, &state);
- /* read points attribute */
- s = points = (char *) xmlGetProp(poly, (const xmlChar *) "points");
- if (!s) {
- state.diagram->error_line = poly->line;
+ exc = dom_element_get_attribute(poly, state.interned_points,
+ &points_str);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+
+ if (points_str == NULL) {
+ state.diagram->error_line = -1; /* poly->line; */
state.diagram->error_message =
"polyline/polygon: missing points attribute";
+ svgtiny_cleanup_state_local(&state);
return svgtiny_SVG_ERROR;
}
+ s = points = strndup(dom_string_data(points_str),
+ dom_string_byte_length(points_str));
+ dom_string_unref(points_str);
+ /* read points attribute */
+ if (s == NULL) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_OUT_OF_MEMORY;
+ }
/* allocate space for path: it will never have more elements than s */
- float *p = malloc(sizeof p[0] * strlen(s));
+ p = malloc(sizeof p[0] * strlen(s));
if (!p) {
- xmlFree(points);
+ free(points);
+ svgtiny_cleanup_state_local(&state);
return svgtiny_OUT_OF_MEMORY;
}
/* parse s and build path */
- for (unsigned int i = 0; s[i]; i++)
+ for (i = 0; s[i]; i++)
if (s[i] == ',')
s[i] = ' ';
- unsigned int i = 0;
+ i = 0;
while (*s) {
float x, y;
int n;
p[i++] = y;
s += n;
} else {
- break;
+ break;
}
}
if (polygon)
p[i++] = svgtiny_PATH_CLOSE;
- xmlFree(points);
+ free(points);
+
+ err = svgtiny_add_path(p, i, &state);
- return svgtiny_add_path(p, i, &state);
+ svgtiny_cleanup_state_local(&state);
+
+ return err;
}
* Parse a <text> or <tspan> element node.
*/
-svgtiny_code svgtiny_parse_text(xmlNode *text,
+svgtiny_code svgtiny_parse_text(dom_element *text,
struct svgtiny_parse_state state)
{
float x, y, width, height;
+ float px, py;
+ dom_node *child;
+ dom_exception exc;
+
+ svgtiny_setup_state_local(&state);
svgtiny_parse_position_attributes(text, state,
&x, &y, &width, &height);
svgtiny_parse_font_attributes(text, &state);
svgtiny_parse_transform_attributes(text, &state);
- float px = state.ctm.a * x + state.ctm.c * y + state.ctm.e;
- float 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; */
+ 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 (xmlNode *child = text->children; child; child = child->next) {
+ exc = dom_node_get_first_child(text, &child);
+ if (exc != DOM_NO_ERR) {
+ return svgtiny_LIBDOM_ERROR;
+ svgtiny_cleanup_state_local(&state);
+ }
+ while (child != NULL) {
+ dom_node *next;
+ dom_node_type nodetype;
svgtiny_code code = svgtiny_OK;
- if (child->type == XML_TEXT_NODE) {
+ exc = dom_node_get_node_type(child, &nodetype);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(child);
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (nodetype == DOM_ELEMENT_NODE) {
+ dom_string *nodename;
+ exc = dom_node_get_node_name(child, &nodename);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(child);
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (dom_string_caseless_isequal(nodename,
+ state.interned_tspan))
+ code = svgtiny_parse_text((dom_element *)child,
+ state);
+ dom_string_unref(nodename);
+ } else if (nodetype == DOM_TEXT_NODE) {
struct svgtiny_shape *shape = svgtiny_add_shape(&state);
- if (!shape)
+ dom_string *content;
+ if (shape == NULL) {
+ dom_node_unref(child);
+ svgtiny_cleanup_state_local(&state);
return svgtiny_OUT_OF_MEMORY;
- shape->text = strdup((const char *) child->content);
+ }
+ exc = dom_text_get_whole_text(child, &content);
+ if (exc != DOM_NO_ERR) {
+ dom_node_unref(child);
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ if (content != NULL) {
+ shape->text = strndup(dom_string_data(content),
+ dom_string_byte_length(content));
+ dom_string_unref(content);
+ } else {
+ shape->text = strdup("");
+ }
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)
+ if (code != svgtiny_OK) {
+ dom_node_unref(child);
+ svgtiny_cleanup_state_local(&state);
return code;
+ }
+ exc = dom_node_get_next_sibling(child, &next);
+ dom_node_unref(child);
+ if (exc != DOM_NO_ERR) {
+ svgtiny_cleanup_state_local(&state);
+ return svgtiny_LIBDOM_ERROR;
+ }
+ child = next;
}
+ svgtiny_cleanup_state_local(&state);
+
return svgtiny_OK;
}
* Parse x, y, width, and height attributes, if present.
*/
-void svgtiny_parse_position_attributes(const xmlNode *node,
+void svgtiny_parse_position_attributes(dom_element *node,
const struct svgtiny_parse_state state,
float *x, float *y, float *width, float *height)
{
+ dom_string *attr;
+ dom_exception exc;
+
*x = 0;
*y = 0;
*width = state.viewport_width;
*height = state.viewport_height;
- for (xmlAttr *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);
+ exc = dom_element_get_attribute(node, state.interned_x, &attr);
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ *x = svgtiny_parse_length(attr, state.viewport_width, state);
+ dom_string_unref(attr);
+ }
+
+ exc = dom_element_get_attribute(node, state.interned_y, &attr);
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ *y = svgtiny_parse_length(attr, state.viewport_height, state);
+ dom_string_unref(attr);
+ }
+
+ exc = dom_element_get_attribute(node, state.interned_width, &attr);
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ *width = svgtiny_parse_length(attr, state.viewport_width,
+ state);
+ dom_string_unref(attr);
+ }
+
+ exc = dom_element_get_attribute(node, state.interned_height, &attr);
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ *height = svgtiny_parse_length(attr, state.viewport_height,
+ state);
+ dom_string_unref(attr);
}
}
* Parse a length as a number of pixels.
*/
-float svgtiny_parse_length(const char *s, int viewport_size,
- const struct svgtiny_parse_state state)
+static 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);*/
+ float font_size = 20;
UNUSED(state);
return 0;
}
+float svgtiny_parse_length(dom_string *s, int viewport_size,
+ const struct svgtiny_parse_state state)
+{
+ char *ss = strndup(dom_string_data(s), dom_string_byte_length(s));
+ float ret = _svgtiny_parse_length(ss, viewport_size, state);
+ free(ss);
+ return ret;
+}
/**
* Parse paint attributes, if present.
*/
-void svgtiny_parse_paint_attributes(const xmlNode *node,
+void svgtiny_parse_paint_attributes(dom_element *node,
struct svgtiny_parse_state *state)
{
- for (const xmlAttr *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++;
- state->stroke_width = svgtiny_parse_length(s,
+ dom_string *attr;
+ dom_exception exc;
+
+ exc = dom_element_get_attribute(node, state->interned_fill, &attr);
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ svgtiny_parse_color(attr, &state->fill, &state->fill_grad, state);
+ dom_string_unref(attr);
+ }
+
+ exc = dom_element_get_attribute(node, state->interned_stroke, &attr);
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ svgtiny_parse_color(attr, &state->stroke, &state->stroke_grad, state);
+ dom_string_unref(attr);
+ }
+
+ exc = dom_element_get_attribute(node, state->interned_stroke_width, &attr);
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ state->stroke_width = svgtiny_parse_length(attr,
state->viewport_width, *state);
- }
+ dom_string_unref(attr);
+ }
+
+ exc = dom_element_get_attribute(node, state->interned_style, &attr);
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ /* Parse a few properties "by hand" until they can
+ be supported in libcss. */
+ char *style = strndup(dom_string_data(attr),
+ dom_string_byte_length(attr));
+ 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->fill_grad, 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->stroke_grad, 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);
+ }
+ free(style);
+ dom_string_unref(attr);
}
}
* Parse a colour.
*/
-void svgtiny_parse_color(const char *s, svgtiny_colour *c,
+static void _svgtiny_parse_color(const char *s, svgtiny_colour *c,
+ struct svgtiny_parse_state_gradient *grad,
struct svgtiny_parse_state *state)
{
unsigned int r, g, b;
} else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' &&
s[3] == '(') {
- if (s[4] == '#') {
+ if (grad == NULL) {
+ *c = svgtiny_RGB(0, 0, 0);
+ } else if (s[4] == '#') {
id = strdup(s + 5);
if (!id)
return;
rparen = strchr(id, ')');
if (rparen)
*rparen = 0;
- svgtiny_find_gradient(id, state);
+ svgtiny_find_gradient(id, grad, state);
free(id);
- fprintf(stderr, "linear_gradient_stop_count %i\n",
- state->linear_gradient_stop_count);
- if (state->linear_gradient_stop_count == 0)
+ if (grad->linear_gradient_stop_count == 0)
*c = svgtiny_TRANSPARENT;
- else if (state->linear_gradient_stop_count == 1)
- *c = state->gradient_stop[0].color;
+ else if (grad->linear_gradient_stop_count == 1)
+ *c = grad->gradient_stop[0].color;
else
*c = svgtiny_LINEAR_GRADIENT;
}
}
}
+void svgtiny_parse_color(dom_string *s, svgtiny_colour *c,
+ struct svgtiny_parse_state_gradient *grad,
+ struct svgtiny_parse_state *state)
+{
+ dom_string_ref(s);
+ _svgtiny_parse_color(dom_string_data(s), c, grad, state);
+ dom_string_unref(s);
+}
/**
* Parse font attributes, if present.
*/
-void svgtiny_parse_font_attributes(const xmlNode *node,
+void svgtiny_parse_font_attributes(dom_element *node,
struct svgtiny_parse_state *state)
{
+ /* TODO: Implement this, it never used to be */
+ UNUSED(node);
+ UNUSED(state);
+#ifdef WRITTEN_THIS_PROPERLY
+ const xmlAttr *attr;
+
UNUSED(state);
- for (const xmlAttr *attr = node->properties; attr; attr = attr->next) {
+ 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;
- }*/
+ /* TODO */
}
}
+#endif
}
* http://www.w3.org/TR/SVG11/coords#TransformAttribute
*/
-void svgtiny_parse_transform_attributes(xmlNode *node,
+void svgtiny_parse_transform_attributes(dom_element *node,
struct svgtiny_parse_state *state)
{
char *transform;
-
- /* parse transform */
- transform = (char *) xmlGetProp(node, (const xmlChar *) "transform");
- if (transform) {
+ dom_string *attr;
+ dom_exception exc;
+
+ exc = dom_element_get_attribute(node, state->interned_transform,
+ &attr);
+ if (exc == DOM_NO_ERR && attr != NULL) {
+ transform = strndup(dom_string_data(attr),
+ dom_string_byte_length(attr));
svgtiny_parse_transform(transform, &state->ctm.a, &state->ctm.b,
&state->ctm.c, &state->ctm.d,
&state->ctm.e, &state->ctm.f);
- xmlFree(transform);
+ free(transform);
+ dom_string_unref(attr);
}
}
+/**
+ * Parse element styles.
+ *
+ * First we parse any inline "style" attributes. We then compose the
+ * element's style with any parent styles. Finally, we compute any
+ * styles that we support and set the corresponding fields in the
+ * parser state.
+ */
+void svgtiny_parse_styles(dom_element *node,
+ struct svgtiny_parse_state *state)
+{
+ css_error code;
+ uint8_t fill_opacity_type;
+ css_fixed fill_opacity;
+ uint8_t stroke_opacity_type;
+ css_fixed stroke_opacity;
+
+ /* We store the result of svgtiny_parse_style_inline() in
+ * inline_sheet, and that function returns NULL on error; in
+ * particular you do not need to css_stylesheet_destroy() the
+ * result if it is NULL, and css_stylesheet_destroy() checks
+ * for that case. */
+ css_stylesheet *inline_sheet = NULL;
+
+ /* Initialize this to NULL for the same reason: so that we can
+ * safely destroy it later even if we never populated it. */
+ css_select_results *styles = NULL;
+
+
+ dom_exception exc;
+ dom_string *attr;
+
+ exc = dom_element_get_attribute(node, state->interned_style, &attr);
+ if (exc != DOM_NO_ERR) {
+ return NULL;
+ }
+ if (attr != NULL) {
+ inline_sheet = svgtiny_parse_style_inline(
+ (uint8_t *)dom_string_data(attr),
+ dom_string_byte_length(attr));
+ dom_string_unref(attr);
+ }
+
+ struct dom_element *parent;
+ dom_element_parent_node(node, &parent);
+ if (parent == NULL) {
+ /* This is the root <svg> node, skip it.
+ *
+ * While initialising its selection state, libcss sets its
+ * node_data->bloom pointer using css__get_parent_bloom().
+ * But if there is no parent, that function returns,
+ *
+ * static css_bloom empty_bloom[CSS_BLOOM_SIZE];
+ *
+ * A problem later arises because when libcss FINALISES its
+ * selection state, it frees node_data->bloom! That obviously
+ * won't work then node has no parent, i.e. if it's the root
+ * <svg> element.
+ */
+ css_stylesheet_destroy(inline_sheet);
+ return NULL;
+ }
+ else {
+ /* We only needed to know if it was NULL */
+ dom_node_unref(parent);
+ }
+
+ code = svgtiny_select_style(state, node, inline_sheet, &styles);
+ css_stylesheet_destroy(inline_sheet);
+ if (code != CSS_OK) {
+ return NULL;
+ }
+
+ fill_opacity_type = css_computed_fill_opacity(
+ styles->styles[CSS_PSEUDO_ELEMENT_NONE],
+ &fill_opacity);
+ stroke_opacity_type = css_computed_stroke_opacity(
+ styles->styles[CSS_PSEUDO_ELEMENT_NONE],
+ &stroke_opacity);
+ css_select_results_destroy(styles);
+
+ if (fill_opacity_type == CSS_FILL_OPACITY_SET) {
+ state->fill_opacity = FIXTOFLT(fill_opacity);
+ }
+ if (stroke_opacity_type == CSS_STROKE_OPACITY_SET) {
+ state->stroke_opacity = FIXTOFLT(stroke_opacity);
+ }
+}
/**
* Parse a transform string.
float za, zb, zc, zd, ze, zf;
float angle, x, y;
int n;
+ unsigned int i;
- for (unsigned int i = 0; s[i]; i++)
+ for (i = 0; s[i]; i++)
if (s[i] == ',')
s[i] = ' ';
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)
+ n = 0;
+ if ((sscanf(s, " matrix (%f %f %f %f %f %f ) %n",
+ &a, &b, &c, &d, &e, &f, &n) == 6) && (n > 0))
;
- else if (sscanf(s, "translate (%f %f) %n",
- &e, &f, &n) == 2)
+ else if ((sscanf(s, " translate (%f %f ) %n",
+ &e, &f, &n) == 2) && (n > 0))
;
- else if (sscanf(s, "translate (%f) %n",
- &e, &n) == 1)
+ else if ((sscanf(s, " translate (%f ) %n",
+ &e, &n) == 1) && (n > 0))
;
- else if (sscanf(s, "scale (%f %f) %n",
- &a, &d, &n) == 2)
+ else if ((sscanf(s, " scale (%f %f ) %n",
+ &a, &d, &n) == 2) && (n > 0))
;
- else if (sscanf(s, "scale (%f) %n",
- &a, &n) == 1)
+ else if ((sscanf(s, " scale (%f ) %n",
+ &a, &n) == 1) && (n > 0))
d = a;
- else if (sscanf(s, "rotate (%f %f %f) %n",
- &angle, &x, &y, &n) == 3) {
+ else if ((sscanf(s, " rotate (%f %f %f ) %n",
+ &angle, &x, &y, &n) == 3) && (n > 0)) {
angle = angle / 180 * M_PI;
a = cos(angle);
b = 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) {
+ } else if ((sscanf(s, " rotate (%f ) %n",
+ &angle, &n) == 1) && (n > 0)) {
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) {
+ } else if ((sscanf(s, " skewX (%f ) %n",
+ &angle, &n) == 1) && (n > 0)) {
angle = angle / 180 * M_PI;
c = tan(angle);
- } else if (sscanf(s, "skewY (%f) %n",
- &angle, &n) == 1) {
+ } else if ((sscanf(s, " skewY (%f ) %n",
+ &angle, &n) == 1) && (n > 0)) {
angle = angle / 180 * M_PI;
b = tan(angle);
} else
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);
- struct svgtiny_shape *shape = svgtiny_add_shape(state);
+ shape = svgtiny_add_shape(state);
if (!shape) {
free(p);
return svgtiny_OUT_OF_MEMORY;
shape->text = 0;
shape->fill = state->fill;
shape->stroke = state->stroke;
- shape->stroke_width = state->stroke_width *
- (state->ctm.a + state->ctm.d) / 2;
+ shape->fill_opacity = state->fill_opacity;
+ shape->stroke_opacity = state->stroke_opacity;
+ 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;
}
void svgtiny_transform_path(float *p, unsigned int n,
struct svgtiny_parse_state *state)
{
- for (unsigned int j = 0; j != n; ) {
+ 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:
assert(0);
}
j++;
- for (unsigned int k = 0; k != points; k++) {
+ 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;
void svgtiny_free(struct svgtiny_diagram *svg)
{
+ unsigned int i;
assert(svg);
- for (unsigned int i = 0; i != svg->shape_count; i++) {
+ for (i = 0; i != svg->shape_count; i++) {
free(svg->shape[i].path);
free(svg->shape[i].text);
}
-
+
free(svg->shape);
free(svg);
}
+#ifndef HAVE_STRNDUP
+char *svgtiny_strndup(const char *s, size_t n)
+{
+ size_t len;
+ char *s2;
+
+ for (len = 0; len != n && s[len]; len++)
+ continue;
+
+ s2 = malloc(len + 1);
+ if (s2 == NULL)
+ return NULL;
+
+ memcpy(s2, s, len);
+ s2[len] = '\0';
+
+ return s2;
+}
+#endif