]> gitweb.michael.orlitzky.com - libsvgtiny.git/commitdiff
refactor path parse
authorVincent Sanders <vince@kyllikki.org>
Wed, 17 Jul 2024 21:47:45 +0000 (22:47 +0100)
committerVincent Sanders <vince@kyllikki.org>
Wed, 17 Jul 2024 21:55:15 +0000 (22:55 +0100)
14 files changed:
src/Makefile
src/svgtiny.c
src/svgtiny_internal.h
src/svgtiny_parse.c
src/svgtiny_parse.h [new file with mode: 0644]
src/svgtiny_path.c [new file with mode: 0644]
test/data/arcs01.mvg [new file with mode: 0644]
test/data/arcs01.svg [new file with mode: 0644]
test/data/arcs02.svg [new file with mode: 0644]
test/data/cubic01.mvg [new file with mode: 0644]
test/data/cubic01.svg [new file with mode: 0644]
test/data/cubic02.svg [new file with mode: 0644]
test/data/quad01.mvg [new file with mode: 0644]
test/data/quad01.svg [new file with mode: 0644]

index 15cdbe78572e4100c19bc928e9130f7b90540df2..25be07121c07d5385fdc4de8e9a20ac2f08641ba 100644 (file)
@@ -1,5 +1,5 @@
 # Sources
-DIR_SOURCES := svgtiny.c svgtiny_gradient.c svgtiny_list.c svgtiny_parse.c
+DIR_SOURCES := svgtiny.c svgtiny_gradient.c svgtiny_list.c svgtiny_parse.c svgtiny_path.c
 
 SOURCES := $(SOURCES)
 
index cf8854473a89177978fe758f73acb70eaf79ae66..979a140202ecad2091784e3fca62a9b9989cecc4 100644 (file)
@@ -4,6 +4,7 @@
  *                http://opensource.org/licenses/mit-license.php
  * Copyright 2008-2009 James Bursa <james@semichrome.net>
  * Copyright 2012 Daniel Silverstone <dsilvers@netsurf-browser.org>
+ * Copyright 2024 Vincent Sanders <vince@netsurf-browser.org>
  */
 
 #include <assert.h>
 #include "svgtiny_internal.h"
 
 
-#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
+/* circles are approximated with four bezier curves
+ *
+ * The optimal distance to the control points is the constant (4/3)*tan(pi/(2n))
+ * (where n is 4)
+ */
+#define KAPPA 0.5522847498
 
-#define degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0)
-#define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI)
 
 #if (defined(_GNU_SOURCE) && !defined(__APPLE__) || defined(__amigaos4__) || defined(__HAIKU__) || (defined(_POSIX_C_SOURCE) && ((_POSIX_C_SOURCE - 0) >= 200809L)))
 #define HAVE_STRNDUP
@@ -44,474 +38,73 @@ char *svgtiny_strndup(const char *s, size_t n);
 #define strndup svgtiny_strndup
 #endif
 
-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_rect(dom_element *rect,
-               struct svgtiny_parse_state state);
-static svgtiny_code svgtiny_parse_circle(dom_element *circle,
-               struct svgtiny_parse_state state);
-static svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
-               struct svgtiny_parse_state state);
-static svgtiny_code svgtiny_parse_line(dom_element *line,
-               struct svgtiny_parse_state state);
-static svgtiny_code svgtiny_parse_poly(dom_element *poly,
-               struct svgtiny_parse_state state, bool polygon);
-static svgtiny_code svgtiny_parse_text(dom_element *text,
-               struct svgtiny_parse_state state);
-static void svgtiny_parse_position_attributes(dom_element *node,
-               struct svgtiny_parse_state state,
-               float *x, float *y, float *width, float *height);
-static void svgtiny_parse_paint_attributes(dom_element *node,
-               struct svgtiny_parse_state *state);
-static void svgtiny_parse_font_attributes(dom_element *node,
-               struct svgtiny_parse_state *state);
-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);
-
-/**
- * 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)
+#ifndef HAVE_STRNDUP
+char *svgtiny_strndup(const char *s, size_t n)
 {
-       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)
-        */
+       size_t len;
+       char *s2;
 
-       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);
-       }
-}
+       for (len = 0; len != n && s[len]; len++)
+               continue;
 
+       s2 = malloc(len + 1);
+       if (s2 == NULL)
+               return NULL;
 
-/**
- * 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;
+       memcpy(s2, s, len);
+       s2[len] = '\0';
 
-       }
-       return bzsegments;
+       return s2;
 }
+#endif
 
 
 /**
- * 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
+ * Parse x, y, width, and height attributes, if present.
  */
 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)
+svgtiny_parse_position_attributes(dom_element *node,
+                                 struct svgtiny_parse_state state,
+                                 float *x, float *y,
+                                 float *width, float *height)
 {
-       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);
+       struct svgtiny_parse_internal_operation styles[] = {
+               {
+                       /* x */
+                       state.interned_x,
+                       SVGTIOP_LENGTH,
+                       &state.viewport_width,
+                       x
+               },{
+                       /* y */
+                       state.interned_y,
+                       SVGTIOP_LENGTH,
+                       &state.viewport_height,
+                       y
+               },{
+                       /* width */
+                       state.interned_width,
+                       SVGTIOP_LENGTH,
+                       &state.viewport_width,
+                       width
+               },{
+                       /* height */
+                       state.interned_height,
+                       SVGTIOP_LENGTH,
+                       &state.viewport_height,
+                       height
+               },{
+                       NULL, SVGTIOP_NONE, NULL, NULL
+               },
+       };
 
-       /* transform the bezier curves */
-       scale_rotate_translate_points(rx, ry,
-                                     radangle,
-                                     cx, cy,
-                                     bzsegments * 6,
-                                     bzpoints);
+       *x = 0;
+       *y = 0;
+       *width = state.viewport_width;
+       *height = state.viewport_height;
 
-       return bzsegments;
+       svgtiny_parse_attributes(node, &state, styles);
 }
 
 
@@ -534,11 +127,12 @@ static void svgtiny_grad_string_ref(struct svgtiny_parse_state_gradient *grad)
        }
 }
 
+
 /**
  * Call this to clean up the strings in a gradient state.
  */
 static void svgtiny_grad_string_cleanup(
-               struct svgtiny_parse_state_gradient *grad)
+       struct svgtiny_parse_state_gradient *grad)
 {
        if (grad->gradient_x1 != NULL) {
                dom_string_unref(grad->gradient_x1);
@@ -583,24 +177,6 @@ static void svgtiny_cleanup_state_local(struct svgtiny_parse_state *state)
 }
 
 
-/**
- * Create a new svgtiny_diagram structure.
- */
-
-struct svgtiny_diagram *svgtiny_create(void)
-{
-       struct svgtiny_diagram *diagram;
-
-       diagram = calloc(1, sizeof(*diagram));
-       if (!diagram)
-               return 0;
-
-       return diagram;
-       free(diagram);
-       return NULL;
-}
-
-
 static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...)
 {
        UNUSED(severity);
@@ -610,254 +186,140 @@ static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...)
 
 
 /**
- * Parse a block of memory into a svgtiny_diagram.
+ * Parse paint attributes, if present.
  */
+static void
+svgtiny_parse_paint_attributes(dom_element *node,
+                              struct svgtiny_parse_state *state)
+{
+       struct svgtiny_parse_internal_operation ops[] = {
+               {
+                       /* fill color */
+                       state->interned_fill,
+                       SVGTIOP_PAINT,
+                       &state->fill_grad,
+                       &state->fill
+               }, {
+                       /* stroke color */
+                       state->interned_stroke,
+                       SVGTIOP_PAINT,
+                       &state->stroke_grad,
+                       &state->stroke
+               }, {
+                       /* stroke width */
+                       state->interned_stroke_width,
+                       SVGTIOP_INTLENGTH,
+                       &state->viewport_width,
+                       &state->stroke_width
+               },{
+                       NULL, SVGTIOP_NONE, NULL, NULL
+               },
+       };
 
-svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram,
-               const char *buffer, size_t size, const char *url,
-               int viewport_width, int viewport_height)
+       svgtiny_parse_attributes(node, state, ops);
+       svgtiny_parse_inline_style(node, state, ops);
+}
+
+
+/**
+ * Parse font attributes, if present.
+ */
+static void
+svgtiny_parse_font_attributes(dom_element *node,
+                             struct svgtiny_parse_state *state)
 {
-       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;
+       /* TODO: Implement this, it never used to be */
+       UNUSED(node);
+       UNUSED(state);
+#ifdef WRITTEN_THIS_PROPERLY
+       const xmlAttr *attr;
 
-       assert(diagram);
-       assert(buffer);
-       assert(url);
+       UNUSED(state);
 
-       UNUSED(url);
+       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;
+                         }*/
+               }
+        }
+#endif
+}
 
-       parser = dom_xml_parser_create(NULL, NULL,
-                                      ignore_msg, NULL, &document);
 
-       if (parser == NULL)
-               return svgtiny_LIBDOM_ERROR;
+/**
+ * Parse transform attributes, if present.
+ *
+ * http://www.w3.org/TR/SVG11/coords#TransformAttribute
+ */
+static void
+svgtiny_parse_transform_attributes(dom_element *node,
+                                  struct svgtiny_parse_state *state)
+{
+       dom_string *attr;
+       dom_exception exc;
 
-       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;
-       }
+       exc = dom_element_get_attribute(node, state->interned_transform, &attr);
+       if (exc == DOM_NO_ERR && attr != NULL) {
+               svgtiny_parse_transform(dom_string_data(attr),
+                                       dom_string_byte_length(attr),
+                                       &state->ctm);
 
-       err = dom_xml_parser_completed(parser);
-       if (err != DOM_XML_OK) {
-               dom_node_unref(document);
-               dom_xml_parser_destroy(parser);
-               return svgtiny_LIBDOM_ERROR;
+               dom_string_unref(attr);
        }
-
-       /* We're done parsing, drop the parser.
-        * We now own the document entirely.
-        */
-       dom_xml_parser_destroy(parser);
-
-       /* find root <svg> element */
-       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);
-
-       /* get graphic dimensions */
-       memset(&state, 0, sizeof(state));
-       state.diagram = diagram;
-       state.document = document;
-       state.viewport_width = viewport_width;
-       state.viewport_height = viewport_height;
-
-#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
-
-       svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
-       diagram->width = width;
-       diagram->height = height;
-
-       /* set up parsing state */
-       state.viewport_width = width;
-       state.viewport_height = height;
-       state.ctm.a = 1; /*(float) viewport_width / (float) width;*/
-       state.ctm.b = 0;
-       state.ctm.c = 0;
-       state.ctm.d = 1; /*(float) viewport_height / (float) height;*/
-       state.ctm.e = 0; /*x;*/
-       state.ctm.f = 0; /*y;*/
-       /*state.style = css_base_style;
-       state.style.font_size.value.length.value = option_font_size * 0.1;*/
-       state.fill = 0x000000;
-       state.stroke = svgtiny_TRANSPARENT;
-       state.stroke_width = 1;
-
-       /* parse tree */
-       code = svgtiny_parse_svg(svg, state);
-
-       dom_node_unref(svg);
-       dom_node_unref(document);
-
-cleanup:
-       svgtiny_cleanup_state_local(&state);
-#define SVGTINY_STRING_ACTION2(s,n)                    \
-       if (state.interned_##s != NULL)                 \
-               dom_string_unref(state.interned_##s);
-#include "svgtiny_strings.h"
-#undef SVGTINY_STRING_ACTION2
-       return code;
-}
+}
 
 
 /**
- * Parse a <svg> or <g> element node.
+ * Add a path to the svgtiny_diagram.
  */
-
-svgtiny_code svgtiny_parse_svg(dom_element *svg,
-               struct svgtiny_parse_state state)
+static svgtiny_code
+svgtiny_add_path(float *p, unsigned int n, 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);
+       struct svgtiny_shape *shape;
+       svgtiny_code res = svgtiny_OK;
 
-       svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
-       svgtiny_parse_paint_attributes(svg, &state);
-       svgtiny_parse_font_attributes(svg, &state);
+       if (state->fill == svgtiny_LINEAR_GRADIENT) {
+               /* adds a shape to fill the path with a linear gradient */
+               res = svgtiny_gradient_add_fill_path(p, n, state);
+       }
+       if (res != svgtiny_OK) {
+               free(p);
+               return res;
+       }
 
-       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 (state->stroke == svgtiny_LINEAR_GRADIENT) {
+               /* adds a shape to stroke the path with a linear gradient */
+               res = svgtiny_gradient_add_stroke_path(p, n, state);
+       }
+       if (res != svgtiny_OK) {
+               free(p);
+               return res;
        }
 
-       if (view_box) {
-               svgtiny_parse_viewbox(dom_string_data(view_box),
-                                     dom_string_byte_length(view_box),
-                                     state.viewport_width,
-                                     state.viewport_height,
-                                     &state.ctm);
-               dom_string_unref(view_box);
+       /* if stroke and fill are transparent do not add a shape */
+       if ((state->fill == svgtiny_TRANSPARENT) &&
+           (state->stroke == svgtiny_TRANSPARENT)) {
+               free(p);
+               return res;
        }
 
-       svgtiny_parse_transform_attributes(svg, &state);
+       svgtiny_transform_path(p, n, state);
 
-       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;
+       shape = svgtiny_add_shape(state);
+       if (shape == NULL) {
+               free(p);
+               return svgtiny_OUT_OF_MEMORY;
        }
-       while (child != NULL) {
-               dom_element *next;
-               dom_node_type nodetype;
-               svgtiny_code code = svgtiny_OK;
+       shape->path = p;
+       shape->path_length = n;
+       state->diagram->shape_count++;
 
-               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 (dom_string_caseless_isequal(state.interned_g,
-                                                            nodename))
-                               code = svgtiny_parse_svg(child, state);
-                       else if (dom_string_caseless_isequal(state.interned_a,
-                                                            nodename))
-                               code = svgtiny_parse_svg(child, state);
-                       else if (dom_string_caseless_isequal(state.interned_path,
-                                                            nodename))
-                               code = svgtiny_parse_path(child, state);
-                       else if (dom_string_caseless_isequal(state.interned_rect,
-                                                            nodename))
-                               code = svgtiny_parse_rect(child, state);
-                       else if (dom_string_caseless_isequal(state.interned_circle,
-                                                            nodename))
-                               code = svgtiny_parse_circle(child, state);
-                       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 (dom_string_caseless_isequal(state.interned_polyline,
-                                                            nodename))
-                               code = svgtiny_parse_poly(child, state, false);
-                       else if (dom_string_caseless_isequal(state.interned_polygon,
-                                                            nodename))
-                               code = svgtiny_parse_poly(child, state, true);
-                       else if (dom_string_caseless_isequal(state.interned_text,
-                                                            nodename))
-                               code = svgtiny_parse_text(child, state);
-                       dom_string_unref(nodename);
-               }
-               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;
 }
 
@@ -865,23 +327,16 @@ svgtiny_code svgtiny_parse_svg(dom_element *svg,
 /**
  * Parse a <path> element node.
  *
- * http://www.w3.org/TR/SVG11/paths#PathElement
+ * https://svgwg.org/svg2-draft/paths.html#PathElement
  */
-
-svgtiny_code svgtiny_parse_path(dom_element *path,
-               struct svgtiny_parse_state state)
+static svgtiny_code
+svgtiny_parse_path(dom_element *path, struct svgtiny_parse_state state)
 {
-       svgtiny_code err;
+       svgtiny_code res;
        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);
 
@@ -904,322 +359,28 @@ svgtiny_code svgtiny_parse_path(dom_element *path,
                return svgtiny_SVG_ERROR;
        }
 
-        /* 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);
+       res = svgtiny_parse_path_data(dom_string_data(path_d_str),
+                                     dom_string_byte_length(path_d_str),
+                                     &p,
+                                     &i);
+       if (res != svgtiny_OK) {
                svgtiny_cleanup_state_local(&state);
-               return svgtiny_OUT_OF_MEMORY;
-       }
-
-       /* parse d and build path */
-       for (i = 0; s[i]; i++)
-               if (s[i] == ',')
-                       s[i] = ' ';
-       i = 0;
-       while (*s) {
-               char command[2];
-               int plot_command;
-               float x, y, x1, y1, x2, y2, rx, ry, rotation, large_arc, sweep;
-               int n;
-
-                /* 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"));*/
-                       if (*command == 'M' || *command == 'm')
-                               plot_command = svgtiny_PATH_MOVE;
-                       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
-                                               = y;
-                               s += n;
-                               plot_command = svgtiny_PATH_LINE;
-                       } while (sscanf(s, "%f %f %n", &x, &y, &n) == 2);
-
-               /* closepath (Z, z) (no arguments) */
-               } else if (sscanf(s, " %1[Zz] %n", command, &n) == 1) {
-                       /*LOG(("closepath"));*/
-                        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;
-                               p[i++] = last_cubic_x = last_quad_x = last_x
-                                               = x;
-                               p[i++] = last_cubic_y = last_quad_y = last_y;
-                               s += n;
-                       } while (sscanf(s, "%f %n", &x, &n) == 1);
-
-               /* vertical lineto (V, v) (1 argument) */
-               } else if (sscanf(s, " %1[Vv] %f %n", command, &y, &n) == 2) {
-                       /*LOG(("vertical lineto"));*/
-                       do {
-                                ALLOC_PATH_ELEMENTS(3);
-
-                               p[i++] = svgtiny_PATH_LINE;
-                               if (*command == 'v')
-                                       y += last_y;
-                               p[i++] = last_cubic_x = last_quad_x = last_x;
-                               p[i++] = last_cubic_y = last_quad_y = last_y
-                                               = y;
-                               s += n;
-                       } while (sscanf(s, "%f %n", &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;
-                                       y1 += last_y;
-                                       x2 += last_x;
-                                       y2 += last_y;
-                                       x += last_x;
-                                       y += last_y;
-                               }
-                               p[i++] = x1;
-                               p[i++] = y1;
-                               p[i++] = last_cubic_x = x2;
-                               p[i++] = last_cubic_y = y2;
-                               p[i++] = last_quad_x = last_x = x;
-                               p[i++] = last_quad_y = last_y = y;
-                               s += n;
-                       } while (sscanf(s, "%f %f %f %f %f %f %n",
-                                       &x1, &y1, &x2, &y2, &x, &y, &n) == 6);
-
-               /* shorthand/smooth curveto (S, s) (4 arguments) */
-               } else if (sscanf(s, " %1[Ss] %f %f %f %f %n", command,
-                               &x2, &y2, &x, &y, &n) == 5) {
-                       /*LOG(("shorthand/smooth curveto"));*/
-                       do {
-                                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);
-                               if (*command == 's') {
-                                       x2 += last_x;
-                                       y2 += last_y;
-                                       x += last_x;
-                                       y += last_y;
-                               }
-                               p[i++] = x1;
-                               p[i++] = y1;
-                               p[i++] = last_cubic_x = x2;
-                               p[i++] = last_cubic_y = y2;
-                               p[i++] = last_quad_x = last_x = x;
-                               p[i++] = last_quad_y = last_y = y;
-                               s += n;
-                       } while (sscanf(s, "%f %f %f %f %n",
-                                       &x2, &y2, &x, &y, &n) == 4);
-
-               /* quadratic Bezier curveto (Q, q) (4 arguments) */
-               } else if (sscanf(s, " %1[Qq] %f %f %f %f %n", command,
-                               &x1, &y1, &x, &y, &n) == 5) {
-                       /*LOG(("quadratic Bezier curveto"));*/
-                       do {
-                                ALLOC_PATH_ELEMENTS(7);
-
-                               p[i++] = svgtiny_PATH_BEZIER;
-                               last_quad_x = x1;
-                               last_quad_y = y1;
-                               if (*command == 'q') {
-                                       x1 += last_x;
-                                       y1 += last_y;
-                                       x += last_x;
-                                       y += last_y;
-                               }
-                               p[i++] = 1./3 * last_x + 2./3 * x1;
-                               p[i++] = 1./3 * last_y + 2./3 * y1;
-                               p[i++] = 2./3 * x1 + 1./3 * x;
-                               p[i++] = 2./3 * y1 + 1./3 * y;
-                               p[i++] = last_cubic_x = last_x = x;
-                               p[i++] = last_cubic_y = last_y = y;
-                               s += n;
-                       } while (sscanf(s, "%f %f %f %f %n",
-                                       &x1, &y1, &x, &y, &n) == 4);
-
-               /* shorthand/smooth quadratic Bezier curveto (T, t)
-                  (2 arguments) */
-               } else if (sscanf(s, " %1[Tt] %f %f %n", command,
-                               &x, &y, &n) == 3) {
-                       /*LOG(("shorthand/smooth quadratic Bezier curveto"));*/
-                       do {
-                                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);
-                               last_quad_x = x1;
-                               last_quad_y = y1;
-                               if (*command == 't') {
-                                       x1 += last_x;
-                                       y1 += last_y;
-                                       x += last_x;
-                                       y += last_y;
-                               }
-                               p[i++] = 1./3 * last_x + 2./3 * x1;
-                               p[i++] = 1./3 * last_y + 2./3 * y1;
-                               p[i++] = 2./3 * x1 + 1./3 * x;
-                               p[i++] = 2./3 * y1 + 1./3 * y;
-                               p[i++] = last_cubic_x = last_x = x;
-                               p[i++] = last_cubic_y = last_y = y;
-                               s += n;
-                       } while (sscanf(s, "%f %f %n",
-                                       &x, &y, &n) == 2);
-
-               /* elliptical arc (A, a) (7 arguments) */
-               } else if (sscanf(s, " %1[Aa] %f %f %f %f %f %f %f %n", command,
-                               &rx, &ry, &rotation, &large_arc, &sweep,
-                               &x, &y, &n) == 8) {
-                       do {
-                               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 {
-                       /* fprintf(stderr, "parse failed at \"%s\"\n", s); */
-                       break;
-               }
+               return res;
        }
 
-       free(path_d);
-
        if (i <= 4) {
-               /* no real segments in path */
-               free(p);
-               svgtiny_cleanup_state_local(&state);
-               return svgtiny_OK;
+               /* insufficient segments in path treated as none */
+               if (i > 0) {
+                       free(p);
+               }
+               res = svgtiny_OK;
+       } else {
+               res = svgtiny_add_path(p, i, &state);
        }
 
-        /* resize path element array to not be over allocated */
-        if (palloc != i) {
-                float *tp;
-
-                /* try the resize, if it fails just continue to use the old
-                 * allocation
-                 */
-                tp = realloc(p, sizeof p[0] * i);
-                if (tp != NULL) {
-                        p = tp;
-                }
-        }
-
-       err = svgtiny_add_path(p, i, &state);
-
        svgtiny_cleanup_state_local(&state);
 
-       return err;
+       return res;
 }
 
 
@@ -1228,9 +389,8 @@ svgtiny_code svgtiny_parse_path(dom_element *path,
  *
  * http://www.w3.org/TR/SVG11/shapes#RectElement
  */
-
-svgtiny_code svgtiny_parse_rect(dom_element *rect,
-               struct svgtiny_parse_state state)
+static svgtiny_code
+svgtiny_parse_rect(dom_element *rect, struct svgtiny_parse_state state)
 {
        svgtiny_code err;
        float x, y, width, height;
@@ -1239,7 +399,7 @@ svgtiny_code svgtiny_parse_rect(dom_element *rect,
        svgtiny_setup_state_local(&state);
 
        svgtiny_parse_position_attributes(rect, state,
-                       &x, &y, &width, &height);
+                                         &x, &y, &width, &height);
        svgtiny_parse_paint_attributes(rect, &state);
        svgtiny_parse_transform_attributes(rect, &state);
 
@@ -1274,9 +434,8 @@ svgtiny_code svgtiny_parse_rect(dom_element *rect,
 /**
  * Parse a <circle> element node.
  */
-
-svgtiny_code svgtiny_parse_circle(dom_element *circle,
-               struct svgtiny_parse_state state)
+static svgtiny_code
+svgtiny_parse_circle(dom_element *circle, struct svgtiny_parse_state state)
 {
        svgtiny_code err;
        float x = 0, y = 0, r = -1;
@@ -1374,9 +533,8 @@ svgtiny_code svgtiny_parse_circle(dom_element *circle,
 /**
  * Parse an <ellipse> element node.
  */
-
-svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
-               struct svgtiny_parse_state state)
+static 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;
@@ -1421,7 +579,7 @@ svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
        if (rx < 0 || ry < 0) {
                state.diagram->error_line = -1; /* ellipse->line; */
                state.diagram->error_message = "ellipse: rx or ry missing "
-                               "or negative";
+                       "or negative";
                svgtiny_cleanup_state_local(&state);
                return svgtiny_SVG_ERROR;
        }
@@ -1480,9 +638,8 @@ svgtiny_code svgtiny_parse_ellipse(dom_element *ellipse,
 /**
  * Parse a <line> element node.
  */
-
-svgtiny_code svgtiny_parse_line(dom_element *line,
-               struct svgtiny_parse_state state)
+static 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;
@@ -1552,9 +709,10 @@ svgtiny_code svgtiny_parse_line(dom_element *line,
  * http://www.w3.org/TR/SVG11/shapes#PolylineElement
  * http://www.w3.org/TR/SVG11/shapes#PolygonElement
  */
-
-svgtiny_code svgtiny_parse_poly(dom_element *poly,
-               struct svgtiny_parse_state state, bool polygon)
+static svgtiny_code
+svgtiny_parse_poly(dom_element *poly,
+                  struct svgtiny_parse_state state,
+                  bool polygon)
 {
        svgtiny_code err;
        dom_string *points_str;
@@ -1577,7 +735,7 @@ svgtiny_code svgtiny_parse_poly(dom_element *poly,
        if (points_str == NULL) {
                state.diagram->error_line = -1; /* poly->line; */
                state.diagram->error_message =
-                               "polyline/polygon: missing points attribute";
+                       "polyline/polygon: missing points attribute";
                svgtiny_cleanup_state_local(&state);
                return svgtiny_SVG_ERROR;
        }
@@ -1601,7 +759,7 @@ svgtiny_code svgtiny_parse_poly(dom_element *poly,
                free(pointv);
                state.diagram->error_line = -1; /* poly->line; */
                state.diagram->error_message =
-                               "polyline/polygon: failed to parse points";
+                       "polyline/polygon: failed to parse points";
        } else {
                if (pointc > 0) {
                        pointv[0] = svgtiny_PATH_MOVE;
@@ -1621,9 +779,8 @@ svgtiny_code svgtiny_parse_poly(dom_element *poly,
 /**
  * Parse a <text> or <tspan> element node.
  */
-
-svgtiny_code svgtiny_parse_text(dom_element *text,
-               struct svgtiny_parse_state state)
+static svgtiny_code
+svgtiny_parse_text(dom_element *text, struct svgtiny_parse_state state)
 {
        float x, y, width, height;
        float px, py;
@@ -1633,7 +790,7 @@ svgtiny_code svgtiny_parse_text(dom_element *text,
        svgtiny_setup_state_local(&state);
 
        svgtiny_parse_position_attributes(text, state,
-                       &x, &y, &width, &height);
+                                         &x, &y, &width, &height);
        svgtiny_parse_font_attributes(text, &state);
        svgtiny_parse_transform_attributes(text, &state);
 
@@ -1643,7 +800,7 @@ svgtiny_code svgtiny_parse_text(dom_element *text,
 /*     state.ctm.f = py - state.origin_y; */
 
        /*struct css_style style = state.style;
-       style.font_size.value.length.value *= state.ctm.a;*/
+         style.font_size.value.length.value *= state.ctm.a;*/
 
         exc = dom_node_get_first_child(text, &child);
        if (exc != DOM_NO_ERR) {
@@ -1721,195 +878,123 @@ svgtiny_code svgtiny_parse_text(dom_element *text,
 
 
 /**
- * Parse x, y, width, and height attributes, if present.
+ * Parse a <svg> or <g> element node.
  */
-
-void svgtiny_parse_position_attributes(dom_element *node,
-               struct svgtiny_parse_state state,
-               float *x, float *y, float *width, float *height)
+static svgtiny_code
+svgtiny_parse_svg(dom_element *svg, struct svgtiny_parse_state state)
 {
-       struct svgtiny_parse_internal_operation styles[] = {
-               {
-                       /* x */
-                       state.interned_x,
-                       SVGTIOP_LENGTH,
-                       &state.viewport_width,
-                       x
-               },{
-                       /* y */
-                       state.interned_y,
-                       SVGTIOP_LENGTH,
-                       &state.viewport_height,
-                       y
-               },{
-                       /* width */
-                       state.interned_width,
-                       SVGTIOP_LENGTH,
-                       &state.viewport_width,
-                       width
-               },{
-                       /* height */
-                       state.interned_height,
-                       SVGTIOP_LENGTH,
-                       &state.viewport_height,
-                       height
-               },{
-                       NULL, SVGTIOP_NONE, NULL, NULL
-               },
-       };
-
-       *x = 0;
-       *y = 0;
-       *width = state.viewport_width;
-       *height = state.viewport_height;
-
-       svgtiny_parse_attributes(node, &state, styles);
-}
-
-
-/**
- * Parse paint attributes, if present.
- */
-void svgtiny_parse_paint_attributes(dom_element *node,
-               struct svgtiny_parse_state *state)
-{
-       struct svgtiny_parse_internal_operation ops[] = {
-               {
-                       /* fill color */
-                       state->interned_fill,
-                       SVGTIOP_PAINT,
-                       &state->fill_grad,
-                       &state->fill
-               }, {
-                       /* stroke color */
-                       state->interned_stroke,
-                       SVGTIOP_PAINT,
-                       &state->stroke_grad,
-                       &state->stroke
-               }, {
-                       /* stroke width */
-                       state->interned_stroke_width,
-                       SVGTIOP_INTLENGTH,
-                       &state->viewport_width,
-                       &state->stroke_width
-               },{
-                       NULL, SVGTIOP_NONE, NULL, NULL
-               },
-       };
-
-       svgtiny_parse_attributes(node, state, ops);
-       svgtiny_parse_inline_style(node, state, ops);
-}
-
-
-/**
- * Parse font attributes, if present.
- */
-
-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 (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;
-                       }*/
-               }
-        }
-#endif
-}
-
-
-/**
- * Parse transform attributes, if present.
- *
- * http://www.w3.org/TR/SVG11/coords#TransformAttribute
- */
-
-void svgtiny_parse_transform_attributes(dom_element *node,
-               struct svgtiny_parse_state *state)
-{
-       dom_string *attr;
+       float x, y, width, height;
+       dom_string *view_box;
+       dom_element *child;
        dom_exception exc;
 
-       exc = dom_element_get_attribute(node, state->interned_transform, &attr);
-       if (exc == DOM_NO_ERR && attr != NULL) {
-               svgtiny_parse_transform(dom_string_data(attr),
-                                       dom_string_byte_length(attr),
-                                       &state->ctm);
-
-               dom_string_unref(attr);
-       }
-}
-
-
-/**
- * Add a path to the svgtiny_diagram.
- */
-
-svgtiny_code svgtiny_add_path(float *p, unsigned int n,
-               struct svgtiny_parse_state *state)
-{
-       struct svgtiny_shape *shape;
-       svgtiny_code res = svgtiny_OK;
+       svgtiny_setup_state_local(&state);
 
-       if (state->fill == svgtiny_LINEAR_GRADIENT) {
-               /* adds a shape to fill the path with a linear gradient */
-               res = svgtiny_gradient_add_fill_path(p, n, state);
-       }
-       if (res != svgtiny_OK) {
-               free(p);
-               return res;
-       }
+       svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
+       svgtiny_parse_paint_attributes(svg, &state);
+       svgtiny_parse_font_attributes(svg, &state);
 
-       if (state->stroke == svgtiny_LINEAR_GRADIENT) {
-               /* adds a shape to stroke the path with a linear gradient */
-               res = svgtiny_gradient_add_stroke_path(p, n, state);
-       }
-       if (res != svgtiny_OK) {
-               free(p);
-               return res;
+       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 stroke and fill are transparent do not add a shape */
-       if ((state->fill == svgtiny_TRANSPARENT) &&
-           (state->stroke == svgtiny_TRANSPARENT)) {
-               free(p);
-               return res;
+       if (view_box) {
+               svgtiny_parse_viewbox(dom_string_data(view_box),
+                                     dom_string_byte_length(view_box),
+                                     state.viewport_width,
+                                     state.viewport_height,
+                                     &state.ctm);
+               dom_string_unref(view_box);
        }
 
-       svgtiny_transform_path(p, n, state);
+       svgtiny_parse_transform_attributes(svg, &state);
 
-       shape = svgtiny_add_shape(state);
-       if (shape == NULL) {
-               free(p);
-               return svgtiny_OUT_OF_MEMORY;
+       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;
        }
-       shape->path = p;
-       shape->path_length = n;
-       state->diagram->shape_count++;
+       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);
+                               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 (dom_string_caseless_isequal(state.interned_g,
+                                                            nodename))
+                               code = svgtiny_parse_svg(child, state);
+                       else if (dom_string_caseless_isequal(state.interned_a,
+                                                            nodename))
+                               code = svgtiny_parse_svg(child, state);
+                       else if (dom_string_caseless_isequal(state.interned_path,
+                                                            nodename))
+                               code = svgtiny_parse_path(child, state);
+                       else if (dom_string_caseless_isequal(state.interned_rect,
+                                                            nodename))
+                               code = svgtiny_parse_rect(child, state);
+                       else if (dom_string_caseless_isequal(state.interned_circle,
+                                                            nodename))
+                               code = svgtiny_parse_circle(child, state);
+                       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 (dom_string_caseless_isequal(state.interned_polyline,
+                                                            nodename))
+                               code = svgtiny_parse_poly(child, state, false);
+                       else if (dom_string_caseless_isequal(state.interned_polygon,
+                                                            nodename))
+                               code = svgtiny_parse_poly(child, state, true);
+                       else if (dom_string_caseless_isequal(state.interned_text,
+                                                            nodename))
+                               code = svgtiny_parse_text(child, state);
+                       dom_string_unref(nodename);
+               }
+               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;
 }
 
 
 /**
  * Add a svgtiny_shape to the svgtiny_diagram.
+ *
+ * library internal interface
  */
-
 struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
 {
        struct svgtiny_shape *shape;
@@ -1937,10 +1022,13 @@ struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
 
 /**
  * Apply the current transformation matrix to a path.
+ *
+ * library internal interface
  */
-
-void svgtiny_transform_path(float *p, unsigned int n,
-               struct svgtiny_parse_state *state)
+void
+svgtiny_transform_path(float *p,
+                      unsigned int n,
+                      struct svgtiny_parse_state *state)
 {
        unsigned int j;
 
@@ -1977,9 +1065,163 @@ void svgtiny_transform_path(float *p, unsigned int n,
 
 
 /**
- * Free all memory used by a diagram.
+ * Create a new svgtiny_diagram structure.
+ */
+struct svgtiny_diagram *svgtiny_create(void)
+{
+       struct svgtiny_diagram *diagram;
+
+       diagram = calloc(1, sizeof(*diagram));
+       if (!diagram)
+               return 0;
+
+       return diagram;
+       free(diagram);
+       return NULL;
+}
+
+
+/**
+ * Parse a block of memory into a svgtiny_diagram.
  */
+svgtiny_code svgtiny_parse(struct svgtiny_diagram *diagram,
+                          const char *buffer, size_t size, const char *url,
+                          int viewport_width, int viewport_height)
+{
+       dom_document *document;
+       dom_exception exc;
+       dom_xml_parser *parser;
+       dom_xml_error err;
+       dom_element *svg;
+       dom_string *svg_name;
+       lwc_string *svg_name_lwc;
+       struct svgtiny_parse_state state;
+       float x, y, width, height;
+       svgtiny_code code;
+
+       assert(diagram);
+       assert(buffer);
+       assert(url);
+
+       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;
+       }
 
+       /* We're done parsing, drop the parser.
+        * We now own the document entirely.
+        */
+       dom_xml_parser_destroy(parser);
+
+       /* find root <svg> element */
+       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);
+
+       /* get graphic dimensions */
+       memset(&state, 0, sizeof(state));
+       state.diagram = diagram;
+       state.document = document;
+       state.viewport_width = viewport_width;
+       state.viewport_height = viewport_height;
+
+#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
+
+       svgtiny_parse_position_attributes(svg, state, &x, &y, &width, &height);
+       diagram->width = width;
+       diagram->height = height;
+
+       /* set up parsing state */
+       state.viewport_width = width;
+       state.viewport_height = height;
+       state.ctm.a = 1; /*(float) viewport_width / (float) width;*/
+       state.ctm.b = 0;
+       state.ctm.c = 0;
+       state.ctm.d = 1; /*(float) viewport_height / (float) height;*/
+       state.ctm.e = 0; /*x;*/
+       state.ctm.f = 0; /*y;*/
+       /*state.style = css_base_style;
+         state.style.font_size.value.length.value = option_font_size * 0.1;*/
+       state.fill = 0x000000;
+       state.stroke = svgtiny_TRANSPARENT;
+       state.stroke_width = 1;
+
+       /* parse tree */
+       code = svgtiny_parse_svg(svg, state);
+
+       dom_node_unref(svg);
+       dom_node_unref(document);
+
+cleanup:
+       svgtiny_cleanup_state_local(&state);
+#define SVGTINY_STRING_ACTION2(s,n)                    \
+       if (state.interned_##s != NULL)                 \
+               dom_string_unref(state.interned_##s);
+#include "svgtiny_strings.h"
+#undef SVGTINY_STRING_ACTION2
+       return code;
+}
+
+
+/**
+ * Free all memory used by a diagram.
+ */
 void svgtiny_free(struct svgtiny_diagram *svg)
 {
        unsigned int i;
@@ -1994,23 +1236,3 @@ void svgtiny_free(struct svgtiny_diagram *svg)
 
        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
index 41879f25895f952282bbdef85711054dc8112073..6a57140ab584553ee16c60fbff420932327b9097 100644 (file)
@@ -113,6 +113,14 @@ svgtiny_code svgtiny_parse_inline_style(dom_element *node,
 svgtiny_code svgtiny_parse_attributes(dom_element *node,
                struct svgtiny_parse_state *state,
                struct svgtiny_parse_internal_operation *ops);
+svgtiny_code svgtiny_parse_none(const char *cursor, const char *textend);
+svgtiny_code svgtiny_parse_number(const char *text, const char **textend,
+               float *value);
+
+
+/* svgtiny_path.c */
+svgtiny_code svgtiny_parse_path_data(const char *text, size_t textlen,
+               float **pointv, unsigned int *pointc);
 
 /* svgtiny_gradient.c */
 svgtiny_code svgtiny_find_gradient(const char *id,
index 974df8cd1917efb484b4df8bfc555981f878dbeb..3d33cc73ce6cb7c87a6f465f8074eda6185ba6d9 100644 (file)
 #include <float.h>
 #include <string.h>
 
+
 #include "svgtiny.h"
 #include "svgtiny_internal.h"
+#include "svgtiny_parse.h"
 
 /* Source file generated by `gperf`. */
 #include "autogenerated_colors.c"
 
+
 #define SIGNIFICAND_MAX 100000000
 #define EXPONENT_MAX 38
 #define EXPONENT_MIN -38
@@ -54,8 +57,8 @@
  * decimal numbers (not hex etc.). These limitations are necessary to process
  * the input correctly.
  */
-static svgtiny_code
-parse_number(const char *text, const char **textend, float *value)
+svgtiny_code
+svgtiny_parse_number(const char *text, const char **textend, float *value)
 {
        const char *cur; /* text cursor */
        enum {
@@ -281,42 +284,6 @@ svgtiny_parse_number_end:
 }
 
 
-/**
- * skip SVG spec defined whitespace
- *
- * \param cursor current cursor
- * \param textend end of buffer
- */
-static inline void advance_whitespace(const char **cursor, const char *textend)
-{
-       while((*cursor) < textend) {
-               if ((**cursor != 0x20) &&
-                   (**cursor != 0x09) &&
-                   (**cursor != 0x0A) &&
-                   (**cursor != 0x0D)) {
-                       break;
-               }
-               (*cursor)++;
-       }
-}
-
-
-/**
- * advance cursor across SVG spec defined comma and whitespace
- *
- * \param cursor current cursor
- * \param textend end of buffer
- */
-static inline void advance_comma_whitespace(const char **cursor, const char *textend)
-{
-       advance_whitespace(cursor, textend);
-       if (((*cursor) < textend) && (**cursor == 0x2C /* , */)) {
-               (*cursor)++;
-               advance_whitespace(cursor, textend);
-       }
-}
-
-
 /**
  * advance across hexidecimal characters
  *
@@ -575,7 +542,7 @@ parse_transform_parameters(const char **cursor,
 
        for(param_idx = 0; param_idx < param_max; param_idx++) {
                tokend = textend;
-               err = parse_number(*cursor, &tokend, &paramv[param_idx]);
+               err = svgtiny_parse_number(*cursor, &tokend, &paramv[param_idx]);
                if (err != svgtiny_OK) {
                        /* failed to parse number */
                        return err;
@@ -742,7 +709,7 @@ parse_color_function(const char **cursorout,
 
        for (idx = 0; idx < 4; idx++) {
                argend = textend;
-               res = parse_number(cursor, &argend, &argf[idx]);
+               res = svgtiny_parse_number(cursor, &argend, &argf[idx]);
                if (res != svgtiny_OK) {
                        break;
                }
@@ -863,13 +830,13 @@ parse_paint_url(const char **cursorout,
        return res;
 }
 
+
 /**
  * parse a none token
  *
  * \return svgtiny_OK if none found else svgtiny_SVG_ERROR if not
  */
-static svgtiny_code
-parse_none(const char *cursor, const char *textend)
+svgtiny_code svgtiny_parse_none(const char *cursor, const char *textend)
 {
        const char *noneend;
        if ((textend - cursor) < 4) {
@@ -914,7 +881,7 @@ svgtiny_parse_paint(const char *text,
 
        advance_whitespace(&cursor, textend);
 
-       res = parse_none(cursor, textend);
+       res = svgtiny_parse_none(cursor, textend);
        if (res == svgtiny_OK) {
                *c = svgtiny_TRANSPARENT;
                return res;
@@ -941,7 +908,7 @@ svgtiny_parse_offset(const char *text, size_t textlen, float *offset)
        const char *numend;
 
        numend = text + textlen;
-       err = parse_number(text, &numend, &number);
+       err = svgtiny_parse_number(text, &numend, &number);
        if (err != svgtiny_OK) {
                return err;
        }
@@ -1011,7 +978,6 @@ dispatch_op(const char *value,
        return res;
 }
 
-
 /**
  * parse a declaration in a style
  *
@@ -1110,7 +1076,7 @@ svgtiny_parse_poly_points(const char *text,
 
        while (cursor < textend) {
                numberend=textend;
-               err = parse_number(cursor, &numberend, &point);
+               err = svgtiny_parse_number(cursor, &numberend, &point);
                if (err != svgtiny_OK) {
                        break;
                }
@@ -1150,7 +1116,7 @@ svgtiny_parse_length(const char *text,
        float font_size = 20; /*css_len2px(&state.style.font_size.value.length, 0);*/
 
        unit = text + textlen;
-       err = parse_number(text, &unit, &number);
+       err = svgtiny_parse_number(text, &unit, &number);
        if (err != svgtiny_OK) {
                unitlen = -1;
        } else {
@@ -1336,8 +1302,6 @@ transform_parse_complete:
 }
 
 
-
-
 /**
  * Parse a color.
  *
@@ -1374,7 +1338,7 @@ svgtiny_parse_color(const char *text, size_t textlen, svgtiny_colour *c)
                return res;
        }
 
-       named_color = svgtiny_color_lookup(text, textlen);
+       named_color = svgtiny_color_lookup(cursor, textend - cursor);
        if (named_color) {
                *c = named_color->color;
                return svgtiny_OK;
@@ -1412,7 +1376,7 @@ svgtiny_parse_viewbox(const char *text,
 
        for (paramidx = 0; paramidx < 3; paramidx++) {
                paramend = textend;
-               res = parse_number(cursor, &paramend, &paramv[paramidx]);
+               res = svgtiny_parse_number(cursor, &paramend, &paramv[paramidx]);
                if (res != svgtiny_OK) {
                        /* failed to parse number */
                        return res;
@@ -1421,7 +1385,7 @@ svgtiny_parse_viewbox(const char *text,
                advance_comma_whitespace(&cursor, textend);
        }
        paramend = textend;
-       res = parse_number(cursor, &paramend, &paramv[paramidx]);
+       res = svgtiny_parse_number(cursor, &paramend, &paramv[paramidx]);
        if (res != svgtiny_OK) {
                /* failed to parse number */
                return res;
diff --git a/src/svgtiny_parse.h b/src/svgtiny_parse.h
new file mode 100644 (file)
index 0000000..6de8e50
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * This file is part of Libsvgtiny
+ * Licensed under the MIT License,
+ *                http://opensource.org/licenses/mit-license.php
+ * Copyright 2024 Vincent Sanders <vince@netsurf-browser.org>
+ */
+
+#ifndef SVGTINY_PARSE_H
+#define SVGTINY_PARSE_H
+
+/**
+ * skip SVG spec defined whitespace
+ *
+ * \param cursor current cursor
+ * \param textend end of buffer
+ */
+static inline void advance_whitespace(const char **cursor, const char *textend)
+{
+       while((*cursor) < textend) {
+               if ((**cursor != 0x20) &&
+                   (**cursor != 0x09) &&
+                   (**cursor != 0x0A) &&
+                   (**cursor != 0x0D)) {
+                       break;
+               }
+               (*cursor)++;
+       }
+}
+
+
+/**
+ * advance cursor across SVG spec defined comma and whitespace
+ *
+ * \param cursor current cursor
+ * \param textend end of buffer
+ */
+static inline void advance_comma_whitespace(const char **cursor, const char *textend)
+{
+       advance_whitespace(cursor, textend);
+       if (((*cursor) < textend) && (**cursor == 0x2C /* , */)) {
+               (*cursor)++;
+               advance_whitespace(cursor, textend);
+       }
+}
+
+#endif
diff --git a/src/svgtiny_path.c b/src/svgtiny_path.c
new file mode 100644 (file)
index 0000000..e193d0c
--- /dev/null
@@ -0,0 +1,1183 @@
+/*
+ * This file is part of Libsvgtiny
+ * Licensed under the MIT License,
+ *                http://opensource.org/licenses/mit-license.php
+ * Copyright 2024 Vincent Sanders <vince@netsurf-browser.org>
+ */
+
+#include <stddef.h>
+#include <math.h>
+#include <stdlib.h>
+
+#include "svgtiny.h"
+#include "svgtiny_internal.h"
+#include "svgtiny_parse.h"
+
+#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 degToRad(angleInDegrees) ((angleInDegrees) * M_PI / 180.0)
+#define radToDeg(angleInRadians) ((angleInRadians) * 180.0 / M_PI)
+
+
+struct internal_points {
+       float *p; /* points */
+       unsigned int used; /* number of used points */
+       size_t alloc; /* number of allocated points */
+};
+
+/**
+ * internal path representation
+ */
+struct internal_path_state {
+       struct internal_points path;/* generated path */
+       struct internal_points cmd; /* parameters to current command */
+       float prev_x; /* previous x coordinate */
+       float prev_y; /* previous y coordinate */
+       float subpath_x; /* subpath start x coordinate */
+       float subpath_y; /* subpath start y coordinate */
+       float cubic_x; /* previous cubic x control point */
+       float cubic_y; /* previous cubic y control point */
+       float quad_x; /* previous quadratic x control point */
+       float quad_y; /* previous quadratic y control point */
+};
+
+/**
+ * ensure there is enough storage allocated to make points available
+ *
+ * \param ipts internal points to enaure space in
+ * \param count The number of points of space to ensure.
+ * \return svgtiny_OK on success else svgtiny_OUT_OF_MEMORY if allocation fails
+ */
+static inline svgtiny_code
+ensure_internal_points(struct internal_points *ipts, unsigned int count)
+{
+       svgtiny_code res = svgtiny_OK;
+       float *nalloc;
+
+       if ((ipts->used + count) > ipts->alloc) {
+               nalloc = realloc(ipts->p, sizeof ipts->p[0] * (ipts->alloc + 84));
+               if (nalloc == NULL) {
+                       return svgtiny_OUT_OF_MEMORY;
+               }
+               ipts->p = nalloc;
+               ipts->alloc = ipts->alloc + 84;
+       }
+       return res;
+}
+
+/**
+ * moveto
+ *
+ * command and parameters: M|m (x, y)+
+ */
+static inline svgtiny_code
+generate_path_move(struct internal_path_state *state, int relative)
+{
+       svgtiny_code res;
+       unsigned int cmdpc = 0; /* command parameter count */
+
+       if ((state->cmd.used < 2) || ((state->cmd.used % 2) != 0)) {
+               /* wrong number of parameters */
+               return svgtiny_SVG_ERROR;
+       }
+
+       for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 2) {
+               res = ensure_internal_points(&state->path, 3);
+               if (res != svgtiny_OK) {
+                       return res;
+               }
+
+               if (relative != 0) {
+                       state->cmd.p[cmdpc] += state->prev_x;
+                       state->cmd.p[cmdpc + 1] += state->prev_y;
+               }
+
+               if (cmdpc == 0) {
+                       state->path.p[state->path.used++] = svgtiny_PATH_MOVE;
+                       /* the move starts a subpath */
+                       state->path.p[state->path.used++] =
+                               state->subpath_x =
+                               state->cubic_x =
+                               state->prev_x = state->cmd.p[cmdpc];
+                       state->path.p[state->path.used++] =
+                               state->subpath_y =
+                               state->cubic_y =
+                               state->prev_y = state->cmd.p[cmdpc + 1];
+               } else {
+                       state->path.p[state->path.used++] = svgtiny_PATH_LINE;
+
+                       state->path.p[state->path.used++] =
+                               state->cubic_x =
+                               state->quad_x =
+                               state->prev_x = state->cmd.p[cmdpc];
+                       state->path.p[state->path.used++] =
+                               state->cubic_y =
+                               state->quad_y =
+                               state->prev_y = state->cmd.p[cmdpc + 1];
+               }
+
+       }
+       return svgtiny_OK;
+}
+
+/**
+ * closepath
+ *
+ * command and parameters: Z|z
+ */
+static inline svgtiny_code
+generate_path_close(struct internal_path_state *state)
+{
+       svgtiny_code res;
+
+       if (state->cmd.used != 0) {
+               /* wrong number of parameters */
+               return svgtiny_SVG_ERROR;
+       }
+
+       res = ensure_internal_points(&state->path, 1);
+       if (res != svgtiny_OK) {
+               return res;
+       }
+
+       state->path.p[state->path.used++] = svgtiny_PATH_CLOSE;
+
+       state->cubic_x = state->quad_x = state->prev_x = state->subpath_x;
+       state->cubic_y = state->quad_y = state->prev_y = state->subpath_y;
+
+       return svgtiny_OK;
+}
+
+
+/**
+ * lineto
+ *
+ * command and parameters: L|l (x, y)+
+ */
+static inline svgtiny_code
+generate_path_line(struct internal_path_state *state, int relative)
+{
+       svgtiny_code res;
+       unsigned int cmdpc = 0; /* command parameter count */
+
+       if ((state->cmd.used < 2) || ((state->cmd.used % 2) != 0)) {
+               /* wrong number of parameters */
+               return svgtiny_SVG_ERROR;
+       }
+
+       for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 2) {
+               res = ensure_internal_points(&state->path, 3);
+               if (res != svgtiny_OK) {
+                       return res;
+               }
+
+               if (relative != 0) {
+                       state->cmd.p[cmdpc] += state->prev_x;
+                       state->cmd.p[cmdpc + 1] += state->prev_y;
+               }
+               state->path.p[state->path.used++] = svgtiny_PATH_LINE;
+               state->path.p[state->path.used++] =
+                       state->cubic_x =
+                       state->quad_x =
+                       state->prev_x = state->cmd.p[cmdpc];
+               state->path.p[state->path.used++] =
+                       state->cubic_y =
+                       state->quad_y =
+                       state->prev_y = state->cmd.p[cmdpc + 1];
+       }
+       return svgtiny_OK;
+}
+
+
+/**
+ * horizontal lineto
+ *
+ * command and parameters: H|h x+
+ */
+static inline svgtiny_code
+generate_path_hline(struct internal_path_state *state, int relative)
+{
+       svgtiny_code res;
+       unsigned int cmdpc = 0; /* command parameter count */
+
+       if (state->cmd.used < 1) {
+               /* wrong number of parameters */
+               return svgtiny_SVG_ERROR;
+       }
+
+       for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc++) {
+               res = ensure_internal_points(&state->path, 3);
+               if (res != svgtiny_OK) {
+                       return res;
+               }
+
+               if (relative != 0) {
+                       state->cmd.p[cmdpc] += state->prev_x;
+               }
+               state->path.p[state->path.used++] = svgtiny_PATH_LINE;
+               state->path.p[state->path.used++] =
+                       state->cubic_x =
+                       state->quad_x =
+                       state->prev_x = state->cmd.p[cmdpc];
+               state->path.p[state->path.used++] =
+                       state->cubic_y =
+                       state->quad_x = state->prev_y;
+       }
+       return svgtiny_OK;
+}
+
+/**
+ * vertical lineto
+ *
+ * command and parameters: V|v y+
+ */
+static inline svgtiny_code
+generate_path_vline(struct internal_path_state *state, int relative)
+{
+       svgtiny_code res;
+       unsigned int cmdpc = 0; /* command parameter count */
+
+       if (state->cmd.used < 1) {
+               /* wrong number of parameters */
+               return svgtiny_SVG_ERROR;
+       }
+
+       for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc++) {
+               res = ensure_internal_points(&state->path, 3);
+               if (res != svgtiny_OK) {
+                       return res;
+               }
+
+               if (relative != 0) {
+                       state->cmd.p[cmdpc] += state->prev_y;
+               }
+               state->path.p[state->path.used++] = svgtiny_PATH_LINE;
+               state->path.p[state->path.used++] =
+                       state->cubic_x =
+                       state->quad_x = state->prev_x;
+               state->path.p[state->path.used++] =
+                       state->cubic_y =
+                       state->quad_y =
+                       state->prev_y = state->cmd.p[cmdpc];
+       }
+       return svgtiny_OK;
+}
+
+/**
+ * curveto
+ *
+ * command and parameters: C|c (x1, y1, x2, y2, x, y)+
+ */
+static inline svgtiny_code
+generate_path_curveto(struct internal_path_state *state, int relative)
+{
+       svgtiny_code res;
+       unsigned int cmdpc = 0; /* command parameter count */
+
+       if ((state->cmd.used < 6) || ((state->cmd.used % 6) != 0)) {
+               /* wrong number of parameters */
+               return svgtiny_SVG_ERROR;
+       }
+
+       for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 6) {
+               res = ensure_internal_points(&state->path, 7);
+               if (res != svgtiny_OK) {
+                       return res;
+               }
+
+               if (relative != 0) {
+                       state->cmd.p[cmdpc + 0] += state->prev_x; /* x1 */
+                       state->cmd.p[cmdpc + 1] += state->prev_y; /* y1 */
+                       state->cmd.p[cmdpc + 2] += state->prev_x; /* x2 */
+                       state->cmd.p[cmdpc + 3] += state->prev_y; /* y2 */
+                       state->cmd.p[cmdpc + 4] += state->prev_x; /* x */
+                       state->cmd.p[cmdpc + 5] += state->prev_y; /* y */
+               }
+
+               state->path.p[state->path.used++] = svgtiny_PATH_BEZIER;
+               state->path.p[state->path.used++] = state->cmd.p[cmdpc + 0];
+               state->path.p[state->path.used++] = state->cmd.p[cmdpc + 1];
+               state->path.p[state->path.used++] =
+                       state->cubic_x = state->cmd.p[cmdpc + 2];
+               state->path.p[state->path.used++] =
+                       state->cubic_y = state->cmd.p[cmdpc + 3];
+               state->path.p[state->path.used++] =
+                       state->quad_x = state->prev_x = state->cmd.p[cmdpc + 4];
+               state->path.p[state->path.used++] =
+                       state->quad_y = state->prev_y = state->cmd.p[cmdpc + 5];
+       }
+       return svgtiny_OK;
+}
+
+/**
+ * smooth curveto
+ *
+ * command and parameters: S|s (x2, y2, x, y)+
+ */
+static inline svgtiny_code
+generate_path_scurveto(struct internal_path_state *state, int relative)
+{
+       svgtiny_code res;
+       unsigned int cmdpc = 0; /* command parameter count */
+
+       if ((state->cmd.used < 4) || ((state->cmd.used % 4) != 0)) {
+               /* wrong number of parameters */
+               return svgtiny_SVG_ERROR;
+       }
+
+       for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 4) {
+               float x1;
+               float y1;
+
+               res = ensure_internal_points(&state->path, 7);
+               if (res != svgtiny_OK) {
+                       return res;
+               }
+
+               x1 = state->prev_x + (state->prev_x - state->cubic_x);
+               y1 = state->prev_y + (state->prev_y - state->cubic_y);
+               if (relative != 0) {
+                       state->cmd.p[cmdpc + 0] += state->prev_x; /* x2 */
+                       state->cmd.p[cmdpc + 1] += state->prev_y; /* y2 */
+                       state->cmd.p[cmdpc + 2] += state->prev_x; /* x */
+                       state->cmd.p[cmdpc + 3] += state->prev_y; /* y */
+               }
+
+               state->path.p[state->path.used++] = svgtiny_PATH_BEZIER;
+               state->path.p[state->path.used++] = x1;
+               state->path.p[state->path.used++] = y1;
+               state->path.p[state->path.used++] =
+                       state->cubic_x = state->cmd.p[cmdpc + 0];
+               state->path.p[state->path.used++] =
+                       state->cubic_x = state->cmd.p[cmdpc + 1];
+               state->path.p[state->path.used++] =
+                       state->quad_x =
+                       state->prev_x = state->cmd.p[cmdpc + 2];
+               state->path.p[state->path.used++] =
+                       state->quad_y =
+                       state->prev_y = state->cmd.p[cmdpc + 3];
+       }
+       return svgtiny_OK;
+}
+
+/**
+ * quadratic Bezier curveto
+ *
+ * command and parameters: Q|q (x1, y1, x ,y)+
+ */
+static inline svgtiny_code
+generate_path_bcurveto(struct internal_path_state *state, int relative)
+{
+       svgtiny_code res;
+       unsigned int cmdpc = 0; /* command parameter count */
+
+       if ((state->cmd.used < 4) || ((state->cmd.used % 4) != 0)) {
+               /* wrong number of parameters */
+               return svgtiny_SVG_ERROR;
+       }
+
+       for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 4) {
+               res = ensure_internal_points(&state->path, 7);
+               if (res != svgtiny_OK) {
+                       return res;
+               }
+
+               state->quad_x = state->cmd.p[cmdpc + 0] /* x1 */;
+               state->quad_y = state->cmd.p[cmdpc + 1] /* y1 */;
+               if (relative != 0) {
+                       state->cmd.p[cmdpc + 0] += state->prev_x; /* x1 */
+                       state->cmd.p[cmdpc + 1] += state->prev_y; /* y1 */
+                       state->cmd.p[cmdpc + 2] += state->prev_x; /* x */
+                       state->cmd.p[cmdpc + 3] += state->prev_y; /* y */
+               }
+
+               state->path.p[state->path.used++] = svgtiny_PATH_BEZIER;
+               state->path.p[state->path.used++] =
+                       1./3 * state->prev_x +
+                       2./3 * state->cmd.p[cmdpc + 0];
+               state->path.p[state->path.used++] =
+                       1./3 * state->prev_y +
+                       2./3 * state->cmd.p[cmdpc + 1];
+               state->path.p[state->path.used++] =
+                       2./3 * state->cmd.p[cmdpc + 0] +
+                       1./3 * state->cmd.p[cmdpc + 2];
+               state->path.p[state->path.used++] =
+                       2./3 * state->cmd.p[cmdpc + 1] +
+                       1./3 * state->cmd.p[cmdpc + 3];
+               state->path.p[state->path.used++] =
+                       state->cubic_x =
+                       state->prev_x = state->cmd.p[cmdpc + 2];
+               state->path.p[state->path.used++] =
+                       state->cubic_y =
+                       state->prev_y = state->cmd.p[cmdpc + 3];
+       }
+       return svgtiny_OK;
+}
+
+/**
+ * smooth quadratic Bezier curveto
+ *
+ * command and parameters: T|t (x ,y)+
+ */
+static inline svgtiny_code
+generate_path_sbcurveto(struct internal_path_state *state, int relative)
+{
+       svgtiny_code res;
+       unsigned int cmdpc = 0; /* command parameter count */
+
+       if ((state->cmd.used < 2) || ((state->cmd.used % 2) != 0)) {
+               /* wrong number of parameters */
+               return svgtiny_SVG_ERROR;
+       }
+
+       for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 2) {
+               float x1;
+               float y1;
+
+               res = ensure_internal_points(&state->path, 7);
+               if (res != svgtiny_OK) {
+                       return res;
+               }
+
+               x1 = state->prev_x + (state->prev_x - state->quad_x);
+               y1 = state->prev_y + (state->prev_y - state->quad_y);
+               state->quad_x = x1;
+               state->quad_y = y1;
+
+               if (relative != 0) {
+                       x1 += state->prev_x;
+                       y1 += state->prev_y;
+                       state->cmd.p[cmdpc + 0] += state->prev_x; /* x */
+                       state->cmd.p[cmdpc + 1] += state->prev_y; /* y */
+               }
+
+               state->path.p[state->path.used++] = svgtiny_PATH_BEZIER;
+               state->path.p[state->path.used++] =
+                       1./3 * state->prev_x +
+                       2./3 * x1;
+               state->path.p[state->path.used++] =
+                       1./3 * state->prev_y +
+                       2./3 * y1;
+               state->path.p[state->path.used++] =
+                       2./3 * x1 +
+                       1./3 * state->cmd.p[cmdpc + 0];
+               state->path.p[state->path.used++] =
+                       2./3 * y1 +
+                       1./3 * state->cmd.p[cmdpc + 1];
+               state->path.p[state->path.used++] =
+                       state->cubic_x =
+                       state->prev_x = state->cmd.p[cmdpc + 0];
+               state->path.p[state->path.used++] =
+                       state->cubic_y =
+                       state->prev_y = state->cmd.p[cmdpc + 1];
+       }
+       return svgtiny_OK;
+}
+
+
+/**
+ * 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;
+}
+
+/**
+ * elliptical arc
+ *
+ * command and parameters: A|a (rx, ry, rotation, largearc, sweep, x, y)+
+ */
+static svgtiny_code
+generate_path_ellipticalarc(struct internal_path_state *state, int relative)
+{
+       svgtiny_code res;
+       unsigned int cmdpc = 0; /* command parameter count */
+
+       if ((state->cmd.used < 7) || ((state->cmd.used % 7) != 0)) {
+               /* wrong number of parameters */
+               return svgtiny_SVG_ERROR;
+       }
+
+       for (cmdpc = 0; cmdpc < state->cmd.used; cmdpc += 7) {
+               int bzsegments;
+               double bzpoints[6*4]; /* allow for up to four bezier segments per arc */
+               if (relative != 0) {
+                       state->cmd.p[cmdpc + 5] += state->prev_x; /* x */
+                       state->cmd.p[cmdpc + 6] += state->prev_y; /* y */
+               }
+
+               bzsegments = svgarc_to_bezier(state->prev_x, state->prev_y,
+                                             state->cmd.p[cmdpc + 5], /* x */
+                                             state->cmd.p[cmdpc + 6], /* y */
+                                             state->cmd.p[cmdpc + 0], /* rx */
+                                             state->cmd.p[cmdpc + 1], /* ry */
+                                             state->cmd.p[cmdpc + 2], /* rotation */
+                                             state->cmd.p[cmdpc + 3], /* large_arc */
+                                             state->cmd.p[cmdpc + 4], /* sweep */
+                                             bzpoints);
+
+               if (bzsegments == -1) {
+                       /* failed to convert arc to bezier replace with line */
+                       res = ensure_internal_points(&state->path, 3);
+                       if (res != svgtiny_OK) {
+                               return res;
+                       }
+                       state->path.p[state->path.used++] = svgtiny_PATH_LINE;
+                       state->path.p[state->path.used++] =
+                               state->cubic_x =
+                               state->quad_x =
+                               state->prev_x = state->cmd.p[cmdpc + 5] /* x */;
+                       state->path.p[state->path.used++] =
+                               state->cubic_y =
+                               state->quad_y =
+                               state->prev_y = state->cmd.p[cmdpc + 6] /* y */;
+               } else if (bzsegments > 0){
+                       int bzpnt;
+                       for (bzpnt = 0;bzpnt < (bzsegments * 6); bzpnt+=6) {
+                               res = ensure_internal_points(&state->path, 7);
+                               if (res != svgtiny_OK) {
+                                       return res;
+                               }
+                               state->path.p[state->path.used++] = svgtiny_PATH_BEZIER;
+                               state->path.p[state->path.used++] = bzpoints[bzpnt];
+                               state->path.p[state->path.used++] = bzpoints[bzpnt+1];
+                               state->path.p[state->path.used++] = bzpoints[bzpnt+2];
+                               state->path.p[state->path.used++] = bzpoints[bzpnt+3];
+                               state->path.p[state->path.used++] =
+                                       state->cubic_y =
+                                       state->quad_x =
+                                       state->prev_x = bzpoints[bzpnt+4];
+                               state->path.p[state->path.used++] =
+                                       state->cubic_y =
+                                       state->quad_y =
+                                       state->prev_y = bzpoints[bzpnt+5];
+                       }
+               }
+       }
+       return svgtiny_OK;
+}
+
+
+/**
+ * parse parameters to a path command
+ */
+static inline svgtiny_code
+parse_path_parameters(const char **cursor,
+                     const char *textend,
+                     struct internal_points *cmdp)
+{
+       const char *numend;
+       svgtiny_code res;
+
+       cmdp->used = 0;
+
+       do {
+               res = ensure_internal_points(cmdp, 1);
+               if (res != svgtiny_OK) {
+                       return res;
+               }
+
+               numend = textend;
+               res = svgtiny_parse_number(*cursor,
+                                          &numend,
+                                          cmdp->p + cmdp->used);
+               if (res != svgtiny_OK) {
+                       break;
+               }
+               cmdp->used++;
+               *cursor = numend;
+
+               advance_comma_whitespace(cursor, textend);
+
+       } while (res == svgtiny_OK);
+
+       return svgtiny_OK;
+}
+
+
+/**
+ * parse a path command
+ */
+static inline svgtiny_code
+parse_path_command(const char **cursor,
+                  const char *textend,
+                  char *pathcommand,
+                  int *relative)
+{
+       advance_whitespace(cursor, textend);
+
+       if ((**cursor != 'M') && (**cursor != 'm') &&
+           (**cursor != 'Z') && (**cursor != 'z') &&
+           (**cursor != 'L') && (**cursor != 'l') &&
+           (**cursor != 'H') && (**cursor != 'h') &&
+           (**cursor != 'V') && (**cursor != 'v') &&
+           (**cursor != 'C') && (**cursor != 'c') &&
+           (**cursor != 'S') && (**cursor != 's') &&
+           (**cursor != 'Q') && (**cursor != 'q') &&
+           (**cursor != 'T') && (**cursor != 't') &&
+           (**cursor != 'A') && (**cursor != 'a')) {
+               return svgtiny_SVG_ERROR;
+       }
+
+       if ((**cursor >= 0x61 /* a */) && (**cursor <= 0x7A /* z */)) {
+               *pathcommand = (**cursor) & ~(1 << 5); /* upper case char */
+               *relative = 1;
+       } else {
+               *pathcommand = **cursor;
+               *relative = 0;
+       }
+       (*cursor)++;
+
+       advance_whitespace(cursor, textend);
+
+       return svgtiny_OK;
+}
+
+
+/**
+ * parse path data attribute into a shape list
+ */
+svgtiny_code
+svgtiny_parse_path_data(const char *text,
+                       size_t textlen,
+                       float **pointv,
+                       unsigned int *pointc)
+{
+       const char *cursor = text; /* text cursor */
+       const char *textend = text + textlen;
+       svgtiny_code res;
+       char pathcmd = 0;
+       int relative = 0;
+       struct internal_path_state pathstate = {
+               {NULL, 0, 0},
+               {NULL, 0, 0},
+               0.0, 0.0,
+               0.0, 0.0,
+               0.0, 0.0,
+               0.0, 0.0
+       };
+
+       advance_whitespace(&cursor, textend);
+
+       /* empty path data - equivalent to none */
+       if (cursor == textend) {
+               *pointc = 0;
+               return svgtiny_OK;
+       }
+
+       /* none */
+       res = svgtiny_parse_none(cursor, textend);
+       if (res == svgtiny_OK) {
+               *pointc = 0;
+               return res;
+       }
+
+       while (cursor < textend) {
+               res = parse_path_command(&cursor, textend, &pathcmd, &relative);
+               if (res != svgtiny_OK) {
+                       goto parse_path_data_error;
+               }
+
+               res = parse_path_parameters(&cursor, textend, &pathstate.cmd);
+               if (res != svgtiny_OK) {
+                       goto parse_path_data_error;
+               }
+
+               switch (pathcmd) {
+               case 'M':
+                       res = generate_path_move(&pathstate, relative);
+                       break;
+               case 'Z':
+                       res = generate_path_close(&pathstate);
+                       break;
+               case 'L':
+                       res = generate_path_line(&pathstate, relative);
+                       break;
+               case 'H':
+                       res = generate_path_hline(&pathstate, relative);
+                       break;
+               case 'V':
+                       res = generate_path_vline(&pathstate, relative);
+                       break;
+               case 'C':
+                       res = generate_path_curveto(&pathstate, relative);
+                       break;
+               case 'S':
+                       res = generate_path_scurveto(&pathstate, relative);
+                       break;
+               case 'Q':
+                       res = generate_path_bcurveto(&pathstate, relative);
+                       break;
+               case 'T':
+                       res = generate_path_sbcurveto(&pathstate, relative);
+                       break;
+               case 'A':
+                       res = generate_path_ellipticalarc(&pathstate, relative);
+                       break;
+               }
+
+               if (res != svgtiny_OK) {
+                       goto parse_path_data_error;
+               }
+
+       }
+       *pointv = pathstate.path.p;
+       *pointc = pathstate.path.used;
+
+       if (pathstate.cmd.alloc > 0) {
+               free(pathstate.cmd.p);
+       }
+       return svgtiny_OK;
+
+parse_path_data_error:
+       /* free any local state */
+       if (pathstate.path.alloc > 0) {
+               free(pathstate.path.p);
+       }
+       if (pathstate.cmd.alloc > 0) {
+               free(pathstate.cmd.p);
+       }
+       return res;
+}
diff --git a/test/data/arcs01.mvg b/test/data/arcs01.mvg
new file mode 100644 (file)
index 0000000..02562b2
--- /dev/null
@@ -0,0 +1,5 @@
+viewbox 0 0 425 186
+fill none stroke #0000ff stroke-width 1 path 'M 0.354331 0.465059 L 424.842 0.465059 L 424.842 185.559 L 0.354331 185.559 Z ' 
+fill #ff0000 stroke #0000ff stroke-width 2 path 'M 106.299 93.0118 L 53.1496 93.0118 C 53.1496 131.539 76.9455 162.771 106.299 162.771 C 135.653 162.771 159.449 131.539 159.449 93.0118 C 159.449 54.4851 135.653 23.253 106.299 23.253 Z ' 
+fill #ffff00 stroke #0000ff stroke-width 2 path 'M 97.4409 81.3853 L 97.4409 11.6265 C 68.0872 11.6265 44.2913 42.8586 44.2913 81.3853 Z ' 
+fill none stroke #ff0000 stroke-width 2 path 'M 212.598 162.771 L 230.315 151.144 C 227.898 144.729 229.887 136.957 234.764 133.756 C 239.641 130.556 245.571 133.131 248.031 139.518 L 265.748 127.891 C 260.469 115.397 260.143 102.711 265.02 99.5103 C 269.897 96.3099 278.142 103.799 283.465 116.265 L 301.181 104.638 C 293.133 85.9932 290.57 68.3356 295.447 65.1351 C 300.324 61.9347 310.806 74.3954 318.898 93.0118 L 336.614 81.3853 C 325.821 56.5716 321.04 33.9279 325.916 30.7275 C 330.793 27.527 343.494 44.9738 354.331 69.7589 L 372.047 58.1324 ' 
diff --git a/test/data/arcs01.svg b/test/data/arcs01.svg
new file mode 100644 (file)
index 0000000..edec92e
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="12cm" height="5.25cm" viewBox="0 0 1200 400"
+     xmlns="http://www.w3.org/2000/svg" version="1.1">
+  <title>Example arcs01 - arc commands in path data</title>
+  <desc>Picture of a pie chart with two pie wedges and
+        a picture of a line with arc blips</desc>
+  <rect x="1" y="1" width="1198" height="398"
+        fill="none" stroke="blue" stroke-width="1" />
+
+  <path d="M300,200 h-150 a150,150 0 1,0 150,-150 z"
+        fill="red" stroke="blue" stroke-width="5" />
+  <path d="M275,175 v-150 a150,150 0 0,0 -150,150 z"
+        fill="yellow" stroke="blue" stroke-width="5" />
+
+  <path d="M600,350 l 50,-25 
+           a25,25 -30 0,1 50,-25 l 50,-25 
+           a25,50 -30 0,1 50,-25 l 50,-25 
+           a25,75 -30 0,1 50,-25 l 50,-25 
+           a25,100 -30 0,1 50,-25 l 50,-25"
+        fill="none" stroke="red" stroke-width="5"  />
+</svg>
diff --git a/test/data/arcs02.svg b/test/data/arcs02.svg
new file mode 100644 (file)
index 0000000..f58f5d6
--- /dev/null
@@ -0,0 +1,57 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="12cm" height="5.25cm" viewBox="0 0 1200 525" version="1.1"
+     xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <title>Example arcs02 - arc options in paths</title>
+  <desc>Pictures showing the result of setting
+        large-arc-flag and sweep-flag to the four
+        possible combinations of 0 and 1.</desc>
+  <g font-family="Verdana" >
+    <defs>
+      <g id="baseEllipses" font-size="20" >
+        <ellipse cx="125" cy="125" rx="100" ry="50"
+                 fill="none" stroke="#888888" stroke-width="2" />
+        <ellipse cx="225" cy="75" rx="100" ry="50"
+                 fill="none" stroke="#888888" stroke-width="2" />
+        <text x="35" y="70">Arc start</text>
+        <text x="225" y="145">Arc end</text>
+      </g>
+    </defs>
+    <rect x="1" y="1" width="1198" height="523"
+          fill="none" stroke="blue" stroke-width="1" />
+
+    <g font-size="30" >
+      <g transform="translate(0,0)">
+        <use xlink:href="#baseEllipses"/>
+      </g>
+      <g transform="translate(400,0)">
+        <text x="50" y="210">large-arc-flag=0</text>
+        <text x="50" y="250">sweep-flag=0</text>
+        <use xlink:href="#baseEllipses"/>
+        <path d="M 125,75 a100,50 0 0,0 100,50"
+              fill="none" stroke="red" stroke-width="6" />
+      </g>
+      <g transform="translate(800,0)">
+        <text x="50" y="210">large-arc-flag=0</text>
+        <text x="50" y="250">sweep-flag=1</text>
+        <use xlink:href="#baseEllipses"/>
+        <path d="M 125,75 a100,50 0 0,1 100,50"
+              fill="none" stroke="red" stroke-width="6" />
+      </g>
+      <g transform="translate(400,250)">
+        <text x="50" y="210">large-arc-flag=1</text>
+        <text x="50" y="250">sweep-flag=0</text>
+        <use xlink:href="#baseEllipses"/>
+        <path d="M 125,75 a100,50 0 1,0 100,50"
+              fill="none" stroke="red" stroke-width="6" />
+      </g>
+      <g transform="translate(800,250)">
+        <text x="50" y="210">large-arc-flag=1</text>
+        <text x="50" y="250">sweep-flag=1</text>
+        <use xlink:href="#baseEllipses"/>
+        <path d="M 125,75 a100,50 0 1,1 100,50"
+              fill="none" stroke="red" stroke-width="6" />
+      </g>
+    </g>
+  </g>
+</svg>
+
diff --git a/test/data/cubic01.mvg b/test/data/cubic01.mvg
new file mode 100644 (file)
index 0000000..f5d41a9
--- /dev/null
@@ -0,0 +1,16 @@
+viewbox 0 0 177 141
+fill none stroke #0000ff stroke-width 1 path 'M 0.354331 0.354331 L 176.811 0.354331 L 176.811 141.378 L 0.354331 141.378 Z ' 
+fill none stroke #888888 stroke-width 1 path 'M 35.4331 70.8661 L 35.4331 35.4331 ' 
+fill none stroke #888888 stroke-width 1 path 'M 88.5827 35.4331 L 88.5827 70.8661 ' 
+fill none stroke #888888 stroke-width 1 path 'M 88.5827 70.8661 L 88.5827 106.299 ' 
+fill none stroke #888888 stroke-width 1 path 'M 141.732 106.299 L 141.732 70.8661 ' 
+fill none stroke #ff0000 stroke-width 2 path 'M 35.4331 70.8661 C 35.4331 35.4331 88.5827 35.4331 88.5827 70.8661 C 88.5827 106.299 141.732 106.299 141.732 70.8661 ' 
+fill none stroke #888888 stroke-width 1 path 'M 38.9764 70.8661 C 38.9764 72.8231 37.39 74.4095 35.4331 74.4095 C 33.4762 74.4095 31.8898 72.8231 31.8898 70.8661 C 31.8898 68.9092 33.4762 67.3228 35.4331 67.3228 C 37.39 67.3228 38.9764 68.9092 38.9764 70.8661 Z ' 
+fill none stroke #888888 stroke-width 1 path 'M 92.126 70.8661 C 92.126 72.8231 90.5396 74.4095 88.5827 74.4095 C 86.6258 74.4095 85.0394 72.8231 85.0394 70.8661 C 85.0394 68.9092 86.6258 67.3228 88.5827 67.3228 C 90.5396 67.3228 92.126 68.9092 92.126 70.8661 Z ' 
+fill none stroke #888888 stroke-width 1 path 'M 145.276 70.8661 C 145.276 72.8231 143.689 74.4095 141.732 74.4095 C 139.775 74.4095 138.189 72.8231 138.189 70.8661 C 138.189 68.9092 139.775 67.3228 141.732 67.3228 C 143.689 67.3228 145.276 68.9092 145.276 70.8661 Z ' 
+fill #888888 stroke none stroke-width 1 path 'M 38.9764 35.4331 C 38.9764 37.39 37.39 38.9764 35.4331 38.9764 C 33.4762 38.9764 31.8898 37.39 31.8898 35.4331 C 31.8898 33.4762 33.4762 31.8898 35.4331 31.8898 C 37.39 31.8898 38.9764 33.4762 38.9764 35.4331 Z ' 
+fill #888888 stroke none stroke-width 1 path 'M 92.126 35.4331 C 92.126 37.39 90.5396 38.9764 88.5827 38.9764 C 86.6258 38.9764 85.0394 37.39 85.0394 35.4331 C 85.0394 33.4762 86.6258 31.8898 88.5827 31.8898 C 90.5396 31.8898 92.126 33.4762 92.126 35.4331 Z ' 
+fill #888888 stroke none stroke-width 1 path 'M 145.276 106.299 C 145.276 108.256 143.689 109.843 141.732 109.843 C 139.775 109.843 138.189 108.256 138.189 106.299 C 138.189 104.342 139.775 102.756 141.732 102.756 C 143.689 102.756 145.276 104.342 145.276 106.299 Z ' 
+fill none stroke #0000ff stroke-width 1 path 'M 91.7716 106.299 C 91.7716 108.06 90.3439 109.488 88.5827 109.488 C 86.8214 109.488 85.3937 108.06 85.3937 106.299 C 85.3937 104.538 86.8214 103.11 88.5827 103.11 C 90.3439 103.11 91.7716 104.538 91.7716 106.299 Z ' 
+fill #000000 stroke none stroke-width 1 text 8.85827 24.8032 'M100,200 C100,100 250,100 250,200' 
+fill #000000 stroke none stroke-width 1 text 115.157 124.016 'S400,300 400,200' 
diff --git a/test/data/cubic01.svg b/test/data/cubic01.svg
new file mode 100644 (file)
index 0000000..c818009
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="5cm" height="4cm" viewBox="0 0 500 400"
+     xmlns="http://www.w3.org/2000/svg" version="1.1">
+  <title>Example cubic01- cubic Bézier commands in path data</title>
+  <desc>Picture showing a simple example of path data
+        using both a "C" and an "S" command,
+        along with annotations showing the control points
+        and end points</desc>
+
+  <rect style="fill:none; stroke:blue; stroke-width:1" x="1" y="1" width="498" height="398" />
+
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="100,200 100,100" />
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="250,100 250,200" />
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="250,200 250,300" />
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="400,300 400,200" />
+  <path style="fill:none; stroke:red; stroke-width:5"
+       d="M100,200 C100,100 250,100 250,200 S400,300 400,200" />
+  <circle style="fill:none; stroke:#888888; stroke-width:2" cx="100" cy="200" r="10" />
+  <circle style="fill:none; stroke:#888888; stroke-width:2" cx="250" cy="200" r="10" />
+  <circle style="fill:none; stroke:#888888; stroke-width:2" cx="400" cy="200" r="10" />
+  <circle style="fill:#888888; stroke:none" cx="100" cy="100" r="10" />
+  <circle style="fill:#888888; stroke:none" cx="250" cy="100" r="10" />
+  <circle style="fill:#888888; stroke:none" cx="400" cy="300" r="10" />
+  <circle style="fill:none; stroke:blue; stroke-width:4" cx="250" cy="300" r="9" />
+  <text style="font-size:22; font-family:Verdana" x="25" y="70">M100,200 C100,100 250,100 250,200</text>
+  <text style="font-size:22; font-family:Verdana; text-anchor:middle" x="325" y="350" >S400,300 400,200</text>
+</svg>
diff --git a/test/data/cubic02.svg b/test/data/cubic02.svg
new file mode 100644 (file)
index 0000000..a6728f1
--- /dev/null
@@ -0,0 +1,91 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="10cm" height="10cm" viewBox="0 0 1000 1000"
+     xmlns="http://www.w3.org/2000/svg" version="1.1">
+  <title>Example cubic02 - cubic Bézier commands in path data</title>
+  <desc>Picture showing examples of "C" and "S" commands,
+        along with annotations showing the control points
+        and end points</desc>
+  <style type="text/css"><![CDATA[
+    .Border { fill:none; stroke:blue; stroke-width:1 }
+    .Connect { fill:none; stroke:#888888; stroke-width:2 }
+    .SamplePath { fill:none; stroke:red; stroke-width:5 }
+    .EndPoint { fill:none; stroke:#888888; stroke-width:2 }
+    .CtlPoint { fill:#888888; stroke:none }
+    .AutoCtlPoint { fill:none; stroke:blue; stroke-width:4 }
+    .Label { text-anchor:middle; font-size:22; font-family:Verdana }
+  ]]></style>
+
+  <rect style="fill:none; stroke:blue; stroke-width:1" x="1" y="1" width="998" height="998" />
+
+  <!-- Path 1 -->
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="100,200 100,100" />
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="400,100 400,200" />
+  <path style="fill:none; stroke:red; stroke-width:5"
+       d="M100,200 C100,100 400,100 400,200" />
+  <circle class="EndPoint" cx="100" cy="200" r="10" />
+  <circle class="EndPoint" cx="400" cy="200" r="10" />
+  <circle class="CtlPoint" cx="100" cy="100" r="10" />
+  <circle class="CtlPoint" cx="400" cy="100" r="10" />
+  <text class="Label" x="250" y="275">M100,200 C100,100 400,100 400,200</text>
+
+  <!-- Path 2 -->
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="100,500 25,400" />
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="475,400 400,500" />
+  <path style="fill:none; stroke:red; stroke-width:5"
+       d="M100,500 C25,400 475,400 400,500" />
+  <circle class="EndPoint" cx="100" cy="500" r="10" />
+  <circle class="EndPoint" cx="400" cy="500" r="10" />
+  <circle class="CtlPoint" cx="25" cy="400" r="10" />
+  <circle class="CtlPoint" cx="475" cy="400" r="10" />
+  <text class="Label" x="250" y="575">M100,500 C25,400 475,400 400,500</text>
+
+  <!-- Path 3 -->
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="100,800 175,700" />
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="325,700 400,800" />
+  <path style="fill:none; stroke:red; stroke-width:5"
+       d="M100,800 C175,700 325,700 400,800" />
+  <circle class="EndPoint" cx="100" cy="800" r="10" />
+  <circle class="EndPoint" cx="400" cy="800" r="10" />
+  <circle class="CtlPoint" cx="175" cy="700" r="10" />
+  <circle class="CtlPoint" cx="325" cy="700" r="10" />
+  <text class="Label" x="250" y="875">M100,800 C175,700 325,700 400,800</text>
+
+  <!-- Path 4 -->
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="600,200 675,100" />
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="975,100 900,200" />
+  <path style="fill:none; stroke:red; stroke-width:5"
+       d="M600,200 C675,100 975,100 900,200" />
+  <circle class="EndPoint" cx="600" cy="200" r="10" />
+  <circle class="EndPoint" cx="900" cy="200" r="10" />
+  <circle class="CtlPoint" cx="675" cy="100" r="10" />
+  <circle class="CtlPoint" cx="975" cy="100" r="10" />
+  <text class="Label" x="750" y="275">M600,200 C675,100 975,100 900,200</text>
+
+  <!-- Path 5 -->
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="600,500 600,350" />
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="900,650 900,500" />
+  <path style="fill:none; stroke:red; stroke-width:5"
+       d="M600,500 C600,350 900,650 900,500" />
+  <circle class="EndPoint" cx="600" cy="500" r="10" />
+  <circle class="EndPoint" cx="900" cy="500" r="10" />
+  <circle class="CtlPoint" cx="600" cy="350" r="10" />
+  <circle class="CtlPoint" cx="900" cy="650" r="10" />
+  <text class="Label" x="750" y="575">M600,500 C600,350 900,650 900,500</text>
+
+  <!-- Path 6 (C and S command) -->
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="600,800 625,700" />
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="725,700 750,800" />
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="750,800 775,900" />
+  <polyline style="fill:none; stroke:#888888; stroke-width:2" points="875,900 900,800" />
+  <path style="fill:none; stroke:red; stroke-width:5"
+       d="M600,800 C625,700 725,700 750,800 S875,900 900,800" />
+  <circle class="EndPoint" cx="600" cy="800" r="10" />
+  <circle class="EndPoint" cx="750" cy="800" r="10" />
+  <circle class="EndPoint" cx="900" cy="800" r="10" />
+  <circle class="CtlPoint" cx="625" cy="700" r="10" />
+  <circle class="CtlPoint" cx="725" cy="700" r="10" />
+  <circle class="CtlPoint" cx="875" cy="900" r="10" />
+  <circle style="fill:none; stroke:blue; stroke-width:4" cx="775" cy="900" r="9" />
+  <text class="Label" x="750" y="945">M600,800 C625,700 725,700 750,800</text>
+  <text class="Label" x="750" y="975">S875,900 900,800</text>
+</svg>
diff --git a/test/data/quad01.mvg b/test/data/quad01.mvg
new file mode 100644 (file)
index 0000000..0567ba3
--- /dev/null
@@ -0,0 +1,9 @@
+viewbox 0 0 425 212
+fill none stroke #0000ff stroke-width 1 path 'M 0.354331 0.354331 L 424.842 0.354331 L 424.842 212.244 L 0.354331 212.244 Z ' 
+fill none stroke #ff0000 stroke-width 2 path 'M 70.8661 106.299 C 118.11 47.2441 165.354 47.2441 212.598 106.299 C 259.842 165.354 307.087 165.354 354.331 106.299 ' 
+fill #000000 stroke none stroke-width 1 path 'M 74.4094 106.299 C 74.4094 108.256 72.8231 109.843 70.8661 109.843 C 68.9092 109.843 67.3228 108.256 67.3228 106.299 C 67.3228 104.342 68.9092 102.756 70.8661 102.756 C 72.8231 102.756 74.4094 104.342 74.4094 106.299 Z ' 
+fill #000000 stroke none stroke-width 1 path 'M 216.142 106.299 C 216.142 108.256 214.555 109.843 212.598 109.843 C 210.642 109.843 209.055 108.256 209.055 106.299 C 209.055 104.342 210.642 102.756 212.598 102.756 C 214.555 102.756 216.142 104.342 216.142 106.299 Z ' 
+fill #000000 stroke none stroke-width 1 path 'M 357.874 106.299 C 357.874 108.256 356.288 109.843 354.331 109.843 C 352.374 109.843 350.787 108.256 350.787 106.299 C 350.787 104.342 352.374 102.756 354.331 102.756 C 356.288 102.756 357.874 104.342 357.874 106.299 Z ' 
+fill #888888 stroke none stroke-width 1 path 'M 145.276 17.7165 C 145.276 19.6734 143.689 21.2598 141.732 21.2598 C 139.775 21.2598 138.189 19.6734 138.189 17.7165 C 138.189 15.7596 139.775 14.1732 141.732 14.1732 C 143.689 14.1732 145.276 15.7596 145.276 17.7165 Z ' 
+fill #888888 stroke none stroke-width 1 path 'M 287.008 194.882 C 287.008 196.839 285.421 198.425 283.465 198.425 C 281.508 198.425 279.921 196.839 279.921 194.882 C 279.921 192.925 281.508 191.339 283.465 191.339 C 285.421 191.339 287.008 192.925 287.008 194.882 Z ' 
+fill none stroke #888888 stroke-width 1 path 'M 70.8661 106.299 L 141.732 17.7165 L 212.598 106.299 L 283.465 194.882 L 354.331 106.299 ' 
diff --git a/test/data/quad01.svg b/test/data/quad01.svg
new file mode 100644 (file)
index 0000000..48f0dfc
--- /dev/null
@@ -0,0 +1,27 @@
+<?xml version="1.0" standalone="no"?>
+<svg width="12cm" height="6cm" viewBox="0 0 1200 600"
+     xmlns="http://www.w3.org/2000/svg" version="1.1">
+  <title>Example quad01 - quadratic Bézier commands in path data</title>
+  <desc>Picture showing a "Q" a "T" command,
+        along with annotations showing the control points
+        and end points</desc>
+  <rect x="1" y="1" width="1198" height="598"
+        fill="none" stroke="blue" stroke-width="1" />
+
+  <path d="M200,300 Q400,50 600,300 T1000,300"
+        fill="none" stroke="red" stroke-width="5"  />
+  <!-- End points -->
+  <g fill="black" >
+    <circle cx="200" cy="300" r="10"/>
+    <circle cx="600" cy="300" r="10"/>
+    <circle cx="1000" cy="300" r="10"/>
+  </g>
+  <!-- Control points and lines from end points to control points -->
+  <g fill="#888888" >
+    <circle cx="400" cy="50" r="10"/>
+    <circle cx="800" cy="550" r="10"/>
+  </g>
+  <path d="M200,300 L400,50 L600,300
+           L800,550 L1000,300"
+        fill="none" stroke="#888888" stroke-width="2" />
+</svg>