]> gitweb.michael.orlitzky.com - libsvgtiny.git/blobdiff - src/svgtiny.c
src/svgtiny.c: eliminate pointless NULL check
[libsvgtiny.git] / src / svgtiny.c
index 56f0028ccecc315ec3211f7dc275907a5adc4374..a77e3b46d9f7c0ca382190075f6971af475c4da9 100644 (file)
@@ -2,10 +2,10 @@
  * 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 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));
+}
 
 
 /**
@@ -54,18 +594,21 @@ struct svgtiny_diagram *svgtiny_create(void)
 {
        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.
@@ -75,8 +618,14 @@ 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;
+       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;
@@ -85,26 +634,109 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *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;
+       UNUSED(url);
+
+       parser = dom_xml_parser_create(NULL, NULL,
+                                      ignore_msg, NULL, &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;
+       }
 
-       /*xmlDebugDumpDocument(stderr, document);*/
+       /* 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;
@@ -118,40 +750,233 @@ svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram,
        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);
+       }
 
-       /* free XML tree */
-       xmlFreeDoc(document);
+       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;
+       }
+
+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 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;
+
+       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 a <svg> or <g> element node.
  */
 
-svgtiny_code svgtiny_parse_svg(xmlNode *svg,
+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);
 
-       /* parse viewBox */
-       xmlAttr *view_box = xmlHasProp(svg, (const xmlChar *) "viewBox");
+       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;
+       }
+
        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 ||
@@ -162,41 +987,86 @@ svgtiny_code svgtiny_parse_svg(xmlNode *svg,
                        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;
 }
 
@@ -208,41 +1078,102 @@ svgtiny_code svgtiny_parse_svg(xmlNode *svg,
  * 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);
 
        /* 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;
+               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"));*/
@@ -251,11 +1182,16 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
                        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
@@ -267,13 +1203,19 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
                /* 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;
@@ -287,6 +1229,8 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
                } 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;
@@ -294,13 +1238,15 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
                                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;
@@ -325,6 +1271,8 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
                                &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);
@@ -349,6 +1297,8 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
                                &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;
@@ -374,6 +1324,8 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
                                &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);
@@ -395,15 +1347,89 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
                        } 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 {
+                               int bzsegments;
+                               double bzpoints[6*4]; /* allow for up to four bezier segments per arc */
+
+                               if (*command == 'a') {
+                                       x += last_x;
+                                       y += last_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 {
-                       /*LOG(("parse failed at \"%s\"", 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;
+       }
+
+        /* 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);
 
-       return svgtiny_add_path(p, i, &state);
+       svgtiny_cleanup_state_local(&state);
+
+       return err;
 }
 
 
@@ -413,19 +1439,25 @@ svgtiny_code svgtiny_parse_path(xmlNode *path,
  * 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);
 
-       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;
@@ -441,7 +1473,11 @@ svgtiny_code svgtiny_parse_rect(xmlNode *rect,
        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;
 }
 
 
@@ -449,66 +1485,222 @@ svgtiny_code svgtiny_parse_rect(xmlNode *rect,
  * 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);
 
-       float *p = malloc(32 * sizeof p[0]);
-       if (!p)
+       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;
+       }
+
+       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);
+
+       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;
 }
 
 
@@ -516,33 +1708,65 @@ svgtiny_code svgtiny_parse_circle(xmlNode *circle,
  * 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);
 
-       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;
@@ -552,7 +1776,11 @@ svgtiny_code svgtiny_parse_line(xmlNode *line,
        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;
 }
 
 
@@ -563,35 +1791,57 @@ svgtiny_code svgtiny_parse_line(xmlNode *line,
  * 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);
 
-       /* 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;
@@ -605,15 +1855,19 @@ svgtiny_code svgtiny_parse_poly(xmlNode *poly,
                        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;
 }
 
 
@@ -621,46 +1875,97 @@ svgtiny_code svgtiny_parse_poly(xmlNode *poly,
  * 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;
 }
 
@@ -669,30 +1974,42 @@ svgtiny_code svgtiny_parse_text(xmlNode *text,
  * 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);
        }
 }
 
@@ -701,13 +2018,13 @@ void svgtiny_parse_position_attributes(const xmlNode *node,
  * 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);
 
@@ -736,53 +2053,96 @@ float svgtiny_parse_length(const char *s, int viewport_size,
        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;
+
+       /* 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. We initialize inline_sheet to NULL to
+        * retain the same semantics. */
+       css_stylesheet *inline_sheet = NULL;
+
+       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) {
+               /* First parse the style attribute into a libcss stylesheet
+                  in case any of its properties are known to libcss. */
+               inline_sheet = svgtiny_parse_style_inline(
+                                       (uint8_t *)dom_string_data(attr),
+                                       dom_string_byte_length(attr));
+
+               /* Parse any other 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);
+       }
+
+       if (inline_sheet != NULL) {
+               css_stylesheet_destroy(inline_sheet);
        }
 }
 
@@ -791,7 +2151,8 @@ void svgtiny_parse_paint_attributes(const xmlNode *node,
  * 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;
@@ -823,21 +2184,21 @@ void svgtiny_parse_color(const char *s, svgtiny_colour *c,
 
        } 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;
                }
@@ -850,27 +2211,36 @@ void svgtiny_parse_color(const char *s, svgtiny_colour *c,
        }
 }
 
+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
 }
 
 
@@ -880,18 +2250,23 @@ void svgtiny_parse_font_attributes(const xmlNode *node,
  * 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);
        }
 }
 
@@ -907,8 +2282,9 @@ void svgtiny_parse_transform(char *s, float *ma, float *mb,
        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] = ' ';
 
@@ -916,23 +2292,24 @@ void svgtiny_parse_transform(char *s, float *ma, float *mb,
                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);
@@ -940,19 +2317,19 @@ void svgtiny_parse_transform(char *s, float *ma, float *mb,
                        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
@@ -981,12 +2358,14 @@ void svgtiny_parse_transform(char *s, float *ma, float *mb,
 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;
@@ -1018,8 +2397,10 @@ struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
        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->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;
 }
@@ -1032,8 +2413,11 @@ struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
 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:
@@ -1049,7 +2433,7 @@ void svgtiny_transform_path(float *p, unsigned int n,
                        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;
@@ -1069,15 +2453,35 @@ void svgtiny_transform_path(float *p, unsigned int n,
 
 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