]> gitweb.michael.orlitzky.com - libsvgtiny.git/commitdiff
Improve gradient parsing and generation
authorVincent Sanders <vince@kyllikki.org>
Thu, 4 Jul 2024 13:09:35 +0000 (14:09 +0100)
committerVincent Sanders <vince@kyllikki.org>
Thu, 11 Jul 2024 19:31:00 +0000 (20:31 +0100)
src/svgtiny.c
src/svgtiny_gradient.c
src/svgtiny_internal.h
test/data/gradient.mvg [new file with mode: 0644]
test/data/gradient.svg [new file with mode: 0644]

index b2171275905cf5a9bd0c1d6883a7e9542f01025e..cf8854473a89177978fe758f73acb70eaf79ae66 100644 (file)
 #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
+#else
+#undef HAVE_STRNDUP
+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,
@@ -1855,14 +1863,37 @@ svgtiny_code svgtiny_add_path(float *p, unsigned int n,
                struct svgtiny_parse_state *state)
 {
        struct svgtiny_shape *shape;
+       svgtiny_code res = svgtiny_OK;
+
+       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;
+       }
+
+       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 (state->fill == svgtiny_LINEAR_GRADIENT)
-               return svgtiny_add_path_linear_gradient(p, n, state);
+       /* 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_transform_path(p, n, state);
 
        shape = svgtiny_add_shape(state);
-       if (!shape) {
+       if (shape == NULL) {
                free(p);
                return svgtiny_OUT_OF_MEMORY;
        }
@@ -1870,6 +1901,7 @@ svgtiny_code svgtiny_add_path(float *p, unsigned int n,
        shape->path_length = n;
        state->diagram->shape_count++;
 
+
        return svgtiny_OK;
 }
 
@@ -1880,24 +1912,25 @@ svgtiny_code svgtiny_add_path(float *p, unsigned int n,
 
 struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state)
 {
-       struct svgtiny_shape *shape = realloc(state->diagram->shape,
+       struct svgtiny_shape *shape;
+
+       shape = realloc(state->diagram->shape,
                        (state->diagram->shape_count + 1) *
                        sizeof (state->diagram->shape[0]));
-       if (!shape)
-               return 0;
-       state->diagram->shape = shape;
-
-       shape += state->diagram->shape_count;
-       shape->path = 0;
-       shape->path_length = 0;
-       shape->text = 0;
-       shape->fill = state->fill;
-       shape->stroke = state->stroke;
-       shape->stroke_width = lroundf((float) state->stroke_width *
-                       (state->ctm.a + state->ctm.d) / 2.0);
-       if (0 < state->stroke_width && shape->stroke_width == 0)
-               shape->stroke_width = 1;
-
+       if (shape != NULL) {
+               state->diagram->shape = shape;
+
+               shape += state->diagram->shape_count;
+               shape->path = 0;
+               shape->path_length = 0;
+               shape->text = 0;
+               shape->fill = state->fill;
+               shape->stroke = state->stroke;
+               shape->stroke_width = lroundf((float) state->stroke_width *
+                                             (state->ctm.a + state->ctm.d) / 2.0);
+               if (0 < state->stroke_width && shape->stroke_width == 0)
+                       shape->stroke_width = 1;
+       }
        return shape;
 }
 
index ed20985e2a8905cdca958b68774e85725a933894..9e5b7d2fa0c622dbc0d163a3fabf45e89bd06d34 100644 (file)
 #include "svgtiny_internal.h"
 
 #undef GRADIENT_DEBUG
-
-static svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear,
-               struct svgtiny_parse_state_gradient *grad,
-               struct svgtiny_parse_state *state);
-static float svgtiny_parse_gradient_offset(const char *s);
-static void svgtiny_path_bbox(float *p, unsigned int n,
-               float *x0, float *y0, float *x1, float *y1);
-static void svgtiny_invert_matrix(float *m, float *inv);
+/* set to add vector shape in output */
+#undef GRADIENT_DEBUG_VECTOR
 
 
 /**
- * Find a gradient by id and parse it.
+ * Get the bounding box of path.
  */
-
-svgtiny_code svgtiny_find_gradient(const char *id,
-               size_t idlen,
-               struct svgtiny_parse_state_gradient *grad,
-               struct svgtiny_parse_state *state)
+static void
+svgtiny_path_bbox(float *p,
+                 unsigned int n,
+                 float *x0, float *y0, float *x1, float *y1)
 {
-       dom_element *gradient;
-       dom_string *id_str, *name;
-       dom_exception exc;
-       svgtiny_code res = svgtiny_OK;
+       unsigned int j;
 
-       #ifdef GRADIENT_DEBUG
-       fprintf(stderr, "svgtiny_find_gradient: id \"%.*s\"\n", idlen, id);
-       #endif
+       *x0 = *x1 = p[1];
+       *y0 = *y1 = p[2];
 
-       grad->linear_gradient_stop_count = 0;
-       if (grad->gradient_x1 != NULL)
-               dom_string_unref(grad->gradient_x1);
-       if (grad->gradient_y1 != NULL)
-               dom_string_unref(grad->gradient_y1);
-       if (grad->gradient_x2 != NULL)
-               dom_string_unref(grad->gradient_x2);
-       if (grad->gradient_y2 != NULL)
-               dom_string_unref(grad->gradient_y2);
-       grad->gradient_x1 = dom_string_ref(state->interned_zero_percent);
-       grad->gradient_y1 = dom_string_ref(state->interned_zero_percent);
-       grad->gradient_x2 = dom_string_ref(state->interned_hundred_percent);
-       grad->gradient_y2 = dom_string_ref(state->interned_zero_percent);
-       grad->gradient_user_space_on_use = false;
-       grad->gradient_transform.a = 1;
-       grad->gradient_transform.b = 0;
-       grad->gradient_transform.c = 0;
-       grad->gradient_transform.d = 1;
-       grad->gradient_transform.e = 0;
-       grad->gradient_transform.f = 0;
+       for (j = 0; j != n; ) {
+               unsigned int points = 0;
+               unsigned int k;
+               switch ((int) p[j]) {
+               case svgtiny_PATH_MOVE:
+               case svgtiny_PATH_LINE:
+                       points = 1;
+                       break;
+               case svgtiny_PATH_CLOSE:
+                       points = 0;
+                       break;
+               case svgtiny_PATH_BEZIER:
+                       points = 3;
+                       break;
+               default:
+                       assert(0);
+               }
+               j++;
+               for (k = 0; k != points; k++) {
+                       float x = p[j], y = p[j + 1];
+                       if (x < *x0)
+                               *x0 = x;
+                       else if (*x1 < x)
+                               *x1 = x;
+                       if (y < *y0)
+                               *y0 = y;
+                       else if (*y1 < y)
+                               *y1 = y;
+                       j += 2;
+               }
+       }
+}
 
-       exc = dom_string_create_interned((const uint8_t *) id, idlen, &id_str);
-       if (exc != DOM_NO_ERR)
-               return svgtiny_SVG_ERROR;
 
-       exc = dom_document_get_element_by_id(state->document, id_str, &gradient);
-       dom_string_unref(id_str);
-       if (exc != DOM_NO_ERR || gradient == NULL) {
-               #ifdef GRADIENT_DEBUG
-               fprintf(stderr, "gradient \"%.*s\" not found\n", idlen, id);
-               #endif
-               return svgtiny_SVG_ERROR;
+/**
+ * Invert a transformation matrix.
+ *
+ * svg transform matrix
+ * | a c e |
+ * | b d f |
+ * | 0 0 1 |
+ */
+static inline void
+svgtiny_invert_matrix(const struct svgtiny_transformation_matrix *m,
+                     struct svgtiny_transformation_matrix *inv)
+{
+       float determinant = m->a * m->d - m->b * m->c;
+       inv->a = m->d / determinant;
+       inv->b = -m->b / determinant;
+       inv->c = -m->c / determinant;
+       inv->d = m->a / determinant;
+       inv->e = (m->c * m->f - m->d * m->e) / determinant;
+       inv->f = (m->b * m->e - m->a * m->f) / determinant;
+}
+
+
+static svgtiny_code
+parse_gradient_stops(dom_element *linear,
+                    struct svgtiny_parse_state_gradient *grad,
+                    struct svgtiny_parse_state *state)
+{
+       unsigned int i = 0;
+       dom_exception exc;
+       dom_nodelist *stops;
+       uint32_t listlen, stopnr;
+
+       exc = dom_element_get_elements_by_tag_name(linear,
+                                                  state->interned_stop,
+                                                  &stops);
+       if (exc != DOM_NO_ERR) {
+               return svgtiny_LIBDOM_ERROR;
+       }
+       if (stops == NULL) {
+               /* no stops */
+               return svgtiny_OK;
        }
 
-       exc = dom_node_get_node_name(gradient, &name);
+       exc = dom_nodelist_get_length(stops, &listlen);
        if (exc != DOM_NO_ERR) {
-               dom_node_unref(gradient);
-               return svgtiny_SVG_ERROR;
+               dom_nodelist_unref(stops);
+               return svgtiny_LIBDOM_ERROR;
        }
 
-       if (dom_string_isequal(name, state->interned_linearGradient)) {
-               res = svgtiny_parse_linear_gradient(gradient, grad, state);
+       for (stopnr = 0; stopnr < listlen; ++stopnr) {
+               dom_element *stop;
+               float offset = -1;
+               svgtiny_colour color = svgtiny_TRANSPARENT;
+               svgtiny_code res;
+               struct svgtiny_parse_internal_operation ops[] = {
+                       {
+                               state->interned_stop_color,
+                               SVGTIOP_COLOR,
+                               NULL,
+                               &color
+                       }, {
+                               state->interned_offset,
+                               SVGTIOP_OFFSET,
+                               NULL,
+                               &offset
+                       }, {
+                               NULL, SVGTIOP_NONE, NULL, NULL
+                       },
+               };
+
+
+               exc = dom_nodelist_item(stops, stopnr,
+                                       (dom_node **)(void *)&stop);
+               if (exc != DOM_NO_ERR)
+                       continue;
+
+               res = svgtiny_parse_attributes(stop, state, ops);
+               if (res != svgtiny_OK) {
+                       /* stop attributes produced error, skip stop */
+                       continue;
+               }
+               ops[1].key = NULL; /* offset is not a style */
+               svgtiny_parse_inline_style(stop, state, ops);
+
+               if (offset != -1 && color != svgtiny_TRANSPARENT) {
+#ifdef GRADIENT_DEBUG
+                       fprintf(stderr, "stop %g %x\n", offset, color);
+#endif
+                       grad->gradient_stop[i].offset = offset;
+                       grad->gradient_stop[i].color = color;
+                       i++;
+               }
+               dom_node_unref(stop);
+               if (i == svgtiny_MAX_STOPS)
+                       break;
        }
 
-       dom_node_unref(gradient);
-       dom_string_unref(name);
+       dom_nodelist_unref(stops);
 
-       #ifdef GRADIENT_DEBUG
-       fprintf(stderr, "linear_gradient_stop_count %i\n",
-                       grad->linear_gradient_stop_count);
-       #endif
+       if (i > 0) {
+               grad->linear_gradient_stop_count = i;
+       }
 
-       return res;
+       return svgtiny_OK;
 }
 
 
@@ -104,15 +178,13 @@ svgtiny_code svgtiny_find_gradient(const char *id,
  *
  * http://www.w3.org/TR/SVG11/pservers#LinearGradients
  */
-
-svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear,
-               struct svgtiny_parse_state_gradient *grad,
-               struct svgtiny_parse_state *state)
+static svgtiny_code
+svgtiny_parse_linear_gradient(dom_element *linear,
+                             struct svgtiny_parse_state_gradient *grad,
+                             struct svgtiny_parse_state *state)
 {
-       unsigned int i = 0;
        dom_string *attr;
        dom_exception exc;
-       dom_nodelist *stops;
 
        exc = dom_element_get_attribute(linear, state->interned_href, &attr);
        if (exc == DOM_NO_ERR && attr != NULL) {
@@ -173,10 +245,10 @@ svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear,
                                        dom_string_byte_length(attr),
                                        &tm);
 
-               #ifdef GRADIENT_DEBUG
+#ifdef GRADIENT_DEBUG
                fprintf(stderr, "transform %g %g %g %g %g %g\n",
                        tm.a, tm.b, tm.c, tm.d, tm.e, tm.f);
-               #endif
+#endif
                grad->gradient_transform.a = tm.a;
                grad->gradient_transform.b = tm.b;
                grad->gradient_transform.c = tm.c;
@@ -186,257 +258,115 @@ svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear,
                dom_string_unref(attr);
         }
 
-       exc = dom_element_get_elements_by_tag_name(linear,
-                                                  state->interned_stop,
-                                                  &stops);
-       if (exc == DOM_NO_ERR && stops != NULL) {
-               uint32_t listlen, stopnr;
-               exc = dom_nodelist_get_length(stops, &listlen);
-               if (exc != DOM_NO_ERR) {
-                       dom_nodelist_unref(stops);
-                       goto no_more_stops;
-               }
-
-               for (stopnr = 0; stopnr < listlen; ++stopnr) {
-                       dom_element *stop;
-                       float offset = -1;
-                       svgtiny_colour color = svgtiny_TRANSPARENT;
-                       exc = dom_nodelist_item(stops, stopnr,
-                                               (dom_node **) (void *) &stop);
-                       if (exc != DOM_NO_ERR)
-                               continue;
-                       exc = dom_element_get_attribute(stop,
-                                                       state->interned_offset,
-                                                       &attr);
-                       if (exc == DOM_NO_ERR && attr != NULL) {
-                               char *s = strndup(dom_string_data(attr),
-                                                 dom_string_byte_length(attr));
-                               offset = svgtiny_parse_gradient_offset(s);
-                               free(s);
-                               dom_string_unref(attr);
-                       }
-                       exc = dom_element_get_attribute(stop,
-                                                       state->interned_stop_color,
-                                                       &attr);
-                       if (exc == DOM_NO_ERR && attr != NULL) {
-                               svgtiny_parse_color(dom_string_data(attr),
-                                   dom_string_byte_length(attr), &color);
-                               dom_string_unref(attr);
-                       }
-                       exc = dom_element_get_attribute(stop,
-                                                       state->interned_style,
-                                                       &attr);
-                       if (exc == DOM_NO_ERR && attr != NULL) {
-                               struct svgtiny_parse_inline_style_op styles[]={
-                                       {
-                                               state->interned_stop_color,
-                                               ISTYLEOP_COLOR,
-                                               NULL,
-                                               &color
-                                       },{
-                                               NULL, ISTYLEOP_NONE, NULL, NULL
-                                       },
-                               };
-                               svgtiny_parse_inline_style(dom_string_data(attr),
-                                                          dom_string_byte_length(attr),
-                                                          state,
-                                                          styles);
-                               dom_string_unref(attr);
-                       }
-                       if (offset != -1 && color != svgtiny_TRANSPARENT) {
-                               #ifdef GRADIENT_DEBUG
-                               fprintf(stderr, "stop %g %x\n", offset, color);
-                               #endif
-                               grad->gradient_stop[i].offset = offset;
-                               grad->gradient_stop[i].color = color;
-                               i++;
-                       }
-                       dom_node_unref(stop);
-                       if (i == svgtiny_MAX_STOPS)
-                               break;
-               }
-
-               dom_nodelist_unref(stops);
-       }
-no_more_stops:
-       if (i > 0)
-               grad->linear_gradient_stop_count = i;
-
-       return svgtiny_OK;
+       return parse_gradient_stops(linear, grad, state);
 }
 
 
-float svgtiny_parse_gradient_offset(const char *s)
-{
-       int num_length = strspn(s, "0123456789+-.");
-       const char *unit = s + num_length;
-       float n = atof((const char *) s);
-
-       if (unit[0] == 0)
-               ;
-       else if (unit[0] == '%')
-               n /= 100.0;
-       else
-               return -1;
-
-       if (n < 0)
-               n = 0;
-       if (1 < n)
-               n = 1;
-       return n;
-}
+struct grad_point {
+       float x, y, r;
+};
 
+struct grad_vector {
+       float x0;
+       float y0;
+       float x1;
+       float y1;
+};
 
-/**
- * Add a path with a linear gradient fill to the svgtiny_diagram.
- */
 
-svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
-               struct svgtiny_parse_state *state)
+static void
+compute_gradient_vector(float *p,
+                       unsigned int n,
+                       struct svgtiny_parse_state *state,
+                       struct svgtiny_parse_state_gradient *grad,
+                       struct grad_vector *vector)
 {
-       struct grad_point {
-               float x, y, r;
-       };
        float object_x0, object_y0, object_x1, object_y1;
-       float gradient_x0, gradient_y0, gradient_x1, gradient_y1,
-             gradient_dx, gradient_dy;
-       float trans[6];
-       unsigned int steps = 10;
-       float x0 = 0, y0 = 0, x0_trans, y0_trans, r0; /* segment start point */
-       float x1, y1, x1_trans, y1_trans, r1; /* segment end point */
-       /* segment control points (beziers only) */
-       float c0x = 0, c0y = 0, c1x = 0, c1y = 0;
-       float gradient_norm_squared;
-       struct svgtiny_list *pts;
-       float min_r = 1000;
-       unsigned int min_pt = 0;
-       unsigned int j;
-       unsigned int stop_count;
-       unsigned int current_stop;
-       float last_stop_r;
-       float current_stop_r;
-       int red0, green0, blue0, red1, green1, blue1;
-       unsigned int t, a, b;
-       struct svgtiny_parse_state_gradient *grad;
-
-       assert(state->fill == svgtiny_LINEAR_GRADIENT);
-       grad = &state->fill_grad;
 
        /* determine object bounding box */
        svgtiny_path_bbox(p, n, &object_x0, &object_y0, &object_x1, &object_y1);
-       #ifdef GRADIENT_DEBUG
+#ifdef GRADIENT_DEBUG_VECTOR
        fprintf(stderr, "object bbox: (%g %g) (%g %g)\n",
-                       object_x0, object_y0, object_x1, object_y1);
-       #endif
+               object_x0, object_y0, object_x1, object_y1);
+#endif
 
        if (!grad->gradient_user_space_on_use) {
                svgtiny_parse_length(dom_string_data(grad->gradient_x1),
                                     dom_string_byte_length(grad->gradient_x1),
                                     object_x1 - object_x0,
-                                    &gradient_x0);
-               gradient_x0 += object_x0;
+                                    &vector->x0);
 
                svgtiny_parse_length(dom_string_data(grad->gradient_y1),
                                     dom_string_byte_length(grad->gradient_y1),
                                     object_y1 - object_y0,
-                                    &gradient_y0);
-               gradient_y0 += object_y0;
+                                    &vector->y0);
 
                svgtiny_parse_length(dom_string_data(grad->gradient_x2),
                                     dom_string_byte_length(grad->gradient_x2),
                                     object_x1 - object_x0,
-                                    &gradient_x1);
-               gradient_x1 += object_x0;
+                                    &vector->x1);
 
                svgtiny_parse_length(dom_string_data(grad->gradient_y2),
                                     dom_string_byte_length(grad->gradient_y2),
                                     object_y1 - object_y0,
-                                    &gradient_y1);
-               gradient_y1 += object_y0;
+                                    &vector->y1);
+
+               vector->x0 += object_x0;
+               vector->y0 += object_y0;
+               vector->x1 += object_x0;
+               vector->y1 += object_y0;
        } else {
                svgtiny_parse_length(dom_string_data(grad->gradient_x1),
                                     dom_string_byte_length(grad->gradient_x1),
                                     state->viewport_width,
-                                    &gradient_x0);
+                                    &vector->x0);
                svgtiny_parse_length(dom_string_data(grad->gradient_y1),
                                     dom_string_byte_length(grad->gradient_y1),
                                     state->viewport_height,
-                                    &gradient_y0);
+                                    &vector->y0);
                svgtiny_parse_length(dom_string_data(grad->gradient_x2),
                                     dom_string_byte_length(grad->gradient_x2),
                                     state->viewport_width,
-                                    &gradient_x1);
+                                    &vector->x1);
                svgtiny_parse_length(dom_string_data(grad->gradient_y2),
                                     dom_string_byte_length(grad->gradient_y2),
                                     state->viewport_height,
-                                    &gradient_y1);
+                                    &vector->y1);
        }
-       gradient_dx = gradient_x1 - gradient_x0;
-       gradient_dy = gradient_y1 - gradient_y0;
-       #ifdef GRADIENT_DEBUG
+
+#ifdef GRADIENT_DEBUG_VECTOR
        fprintf(stderr, "gradient vector: (%g %g) => (%g %g)\n",
-                       gradient_x0, gradient_y0, gradient_x1, gradient_y1);
-       #endif
+               vector->x0, vector->y0, vector->x1, vector->y1);
+#endif
 
-       /* show theoretical gradient strips for debugging */
-       /*unsigned int strips = 10;
-       for (unsigned int z = 0; z != strips; z++) {
-               float f0, fd, strip_x0, strip_y0, strip_dx, strip_dy;
-               f0 = (float) z / (float) strips;
-               fd = (float) 1 / (float) strips;
-               strip_x0 = gradient_x0 + f0 * gradient_dx;
-               strip_y0 = gradient_y0 + f0 * gradient_dy;
-               strip_dx = fd * gradient_dx;
-               strip_dy = fd * gradient_dy;
-               fprintf(stderr, "strip %i vector: (%g %g) + (%g %g)\n",
-                               z, strip_x0, strip_y0, strip_dx, strip_dy);
+}
 
-               float *p = malloc(13 * sizeof p[0]);
-               if (!p)
-                       return svgtiny_OUT_OF_MEMORY;
-               p[0] = svgtiny_PATH_MOVE;
-               p[1] = strip_x0 + (strip_dy * 3);
-               p[2] = strip_y0 - (strip_dx * 3);
-               p[3] = svgtiny_PATH_LINE;
-               p[4] = p[1] + strip_dx;
-               p[5] = p[2] + strip_dy;
-               p[6] = svgtiny_PATH_LINE;
-               p[7] = p[4] - (strip_dy * 6);
-               p[8] = p[5] + (strip_dx * 6);
-               p[9] = svgtiny_PATH_LINE;
-               p[10] = p[7] - strip_dx;
-               p[11] = p[8] - strip_dy;
-               p[12] = svgtiny_PATH_CLOSE;
-               svgtiny_transform_path(p, 13, state);
-               struct svgtiny_shape *shape = svgtiny_add_shape(state);
-               if (!shape) {
-                       free(p);
-                       return svgtiny_OUT_OF_MEMORY;
-               }
-               shape->path = p;
-               shape->path_length = 13;
-               shape->fill = svgtiny_TRANSPARENT;
-               shape->stroke = svgtiny_RGB(0, 0xff, 0);
-               state->diagram->shape_count++;
-       }*/
 
-       /* invert gradient transform for applying to vertices */
-       svgtiny_invert_matrix(&grad->gradient_transform.a, trans);
-       #ifdef GRADIENT_DEBUG
-       fprintf(stderr, "inverse transform %g %g %g %g %g %g\n",
-                       trans[0], trans[1], trans[2], trans[3],
-                       trans[4], trans[5]);
-       #endif
+/* compute points on the path */
+static svgtiny_code
+compute_grad_points(float *p,
+                   unsigned int n,
+                   struct svgtiny_transformation_matrix *trans,
+                   struct grad_vector *vector,
+                   struct svgtiny_list *pts,
+                   unsigned int *min_pt)
+{
+       float gradient_norm_squared;
+       float gradient_dx;
+       float gradient_dy;
+       unsigned int j;
+       float min_r = 1000;
+       float x0 = 0, y0 = 0, x0_trans, y0_trans, r0; /* segment start point */
+       float x1, y1, x1_trans, y1_trans, r1; /* segment end point */
+       float c0x = 0, c0y = 0, c1x = 0, c1y = 0; /* segment control points (beziers only) */
+       unsigned int steps = 10;
+
+       gradient_dx = vector->x1 - vector->x0;
+       gradient_dy = vector->y1 - vector->y0;
+
+       gradient_norm_squared = gradient_dx * gradient_dx + gradient_dy * gradient_dy;
 
-       /* compute points on the path for triangle vertices */
        /* r, r0, r1 are distance along gradient vector */
-       gradient_norm_squared = gradient_dx * gradient_dx +
-                                     gradient_dy * gradient_dy;
-       pts = svgtiny_list_create(sizeof (struct grad_point));
-       if (!pts) {
-               free(p);
-               return svgtiny_OUT_OF_MEMORY;
-       }
        for (j = 0; j != n; ) {
                int segment_type = (int) p[j];
                struct grad_point *point;
@@ -450,19 +380,17 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
                }
 
                assert(segment_type == svgtiny_PATH_CLOSE ||
-                               segment_type == svgtiny_PATH_LINE ||
-                               segment_type == svgtiny_PATH_BEZIER);
+                      segment_type == svgtiny_PATH_LINE ||
+                      segment_type == svgtiny_PATH_BEZIER);
 
                /* start point (x0, y0) */
-               x0_trans = trans[0]*x0 + trans[2]*y0 + trans[4];
-               y0_trans = trans[1]*x0 + trans[3]*y0 + trans[5];
-               r0 = ((x0_trans - gradient_x0) * gradient_dx +
-                               (y0_trans - gradient_y0) * gradient_dy) /
-                               gradient_norm_squared;
+               x0_trans = trans->a * x0 + trans->c * y0 + trans->e;
+               y0_trans = trans->b * x0 + trans->d * y0 + trans->f;
+               r0 = ((x0_trans - vector->x0) * gradient_dx +
+                     (y0_trans - vector->y0) * gradient_dy) /
+                       gradient_norm_squared;
                point = svgtiny_list_push(pts);
                if (!point) {
-                       free(p);
-                       svgtiny_list_free(pts);
                        return svgtiny_OUT_OF_MEMORY;
                }
                point->x = x0;
@@ -470,7 +398,7 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
                point->r = r0;
                if (r0 < min_r) {
                        min_r = r0;
-                       min_pt = svgtiny_list_size(pts) - 1;
+                       *min_pt = svgtiny_list_size(pts) - 1;
                }
 
                /* end point (x1, y1) */
@@ -491,11 +419,11 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
                        y1 = p[j + 6];
                        j += 7;
                }
-               x1_trans = trans[0]*x1 + trans[2]*y1 + trans[4];
-               y1_trans = trans[1]*x1 + trans[3]*y1 + trans[5];
-               r1 = ((x1_trans - gradient_x0) * gradient_dx +
-                               (y1_trans - gradient_y0) * gradient_dy) /
-                               gradient_norm_squared;
+               x1_trans = trans->a * x1 + trans->c * y1 + trans->e;
+               y1_trans = trans->b * x1 + trans->d * y1 + trans->f;
+               r1 = ((x1_trans - vector->x0) * gradient_dx +
+                     (y1_trans - vector->y0) * gradient_dy) /
+                       gradient_norm_squared;
 
                /* determine steps from change in r */
 
@@ -507,10 +435,9 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
 
                if (steps == 0)
                        steps = 1;
-               #ifdef GRADIENT_DEBUG
-               fprintf(stderr, "r0 %g, r1 %g, steps %i\n",
-                               r0, r1, steps);
-               #endif
+#ifdef GRADIENT_DEBUG
+               fprintf(stderr, "r0 %g, r1 %g, steps %i\n", r0, r1, steps);
+#endif
 
                /* loop through intermediate points */
                for (z = 1; z != steps; z++) {
@@ -530,18 +457,16 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
                                x = (1-t) * x0 + t * x1;
                                y = (1-t) * y0 + t * y1;
                        }
-                       x_trans = trans[0]*x + trans[2]*y + trans[4];
-                       y_trans = trans[1]*x + trans[3]*y + trans[5];
-                       r = ((x_trans - gradient_x0) * gradient_dx +
-                                       (y_trans - gradient_y0) * gradient_dy) /
-                                       gradient_norm_squared;
-                       #ifdef GRADIENT_DEBUG
+                       x_trans = trans->a * x + trans->c * y + trans->e;
+                       y_trans = trans->b * x + trans->d * y + trans->f;
+                       r = ((x_trans - vector->x0) * gradient_dx +
+                            (y_trans - vector->y0) * gradient_dy) /
+                               gradient_norm_squared;
+#ifdef GRADIENT_DEBUG
                        fprintf(stderr, "(%g %g [%g]) ", x, y, r);
-                       #endif
+#endif
                        point = svgtiny_list_push(pts);
                        if (!point) {
-                               free(p);
-                               svgtiny_list_free(pts);
                                return svgtiny_OUT_OF_MEMORY;
                        }
                        point->x = x;
@@ -549,148 +474,88 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
                        point->r = r;
                        if (r < min_r) {
                                min_r = r;
-                               min_pt = svgtiny_list_size(pts) - 1;
+                               *min_pt = svgtiny_list_size(pts) - 1;
                        }
                }
-               #ifdef GRADIENT_DEBUG
+#ifdef GRADIENT_DEBUG
                fprintf(stderr, "\n");
-               #endif
+#endif
 
                /* next segment start point is this segment end point */
                x0 = x1;
                y0 = y1;
        }
-       #ifdef GRADIENT_DEBUG
+#ifdef GRADIENT_DEBUG
        fprintf(stderr, "pts size %i, min_pt %i, min_r %.3f\n",
-                       svgtiny_list_size(pts), min_pt, min_r);
-       #endif
+               svgtiny_list_size(pts), *min_pt, min_r);
+#endif
+       return svgtiny_OK;
+}
 
-        /* There must be at least a single point for the gradient */
-        if (svgtiny_list_size(pts) == 0) {
-            svgtiny_list_free(pts);
-            free(p);
 
-            return svgtiny_OK;
-        }
+#ifdef GRADIENT_DEBUG
+/**
+ * show theoretical gradient strips for debugging
+ */
+static svgtiny_code
+add_debug_gradient_strips(struct svgtiny_parse_state *state,
+                         struct grad_vector *vector)
+{
+       float gradient_dx = vector->x1 - vector->x0;
+       float gradient_dy = vector->y1 - vector->y0;
+       unsigned int strips = 10;
+       for (unsigned int z = 0; z != strips; z++) {
+               float f0, fd, strip_x0, strip_y0, strip_dx, strip_dy;
+               f0 = (float) z / (float) strips;
+               fd = (float) 1 / (float) strips;
+               strip_x0 = vector->x0 + f0 * gradient_dx;
+               strip_y0 = vector->y0 + f0 * gradient_dy;
+               strip_dx = fd * gradient_dx;
+               strip_dy = fd * gradient_dy;
+               fprintf(stderr, "strip %i vector: (%g %g) + (%g %g)\n",
+                       z, strip_x0, strip_y0, strip_dx, strip_dy);
 
-       /* render triangles */
-       stop_count = grad->linear_gradient_stop_count;
-       assert(2 <= stop_count);
-       current_stop = 0;
-       last_stop_r = 0;
-       current_stop_r = grad->gradient_stop[0].offset;
-       red0 = red1 = svgtiny_RED(grad->gradient_stop[0].color);
-       green0 = green1 = svgtiny_GREEN(grad->gradient_stop[0].color);
-       blue0 = blue1 = svgtiny_BLUE(grad->gradient_stop[0].color);
-       t = min_pt;
-       a = (min_pt + 1) % svgtiny_list_size(pts);
-       b = min_pt == 0 ? svgtiny_list_size(pts) - 1 : min_pt - 1;
-       while (a != b) {
-               struct grad_point *point_t = svgtiny_list_get(pts, t);
-               struct grad_point *point_a = svgtiny_list_get(pts, a);
-               struct grad_point *point_b = svgtiny_list_get(pts, b);
-               float mean_r = (point_t->r + point_a->r + point_b->r) / 3;
-               float *p;
-               struct svgtiny_shape *shape;
-               /*fprintf(stderr, "triangle: t %i %.3f a %i %.3f b %i %.3f "
-                               "mean_r %.3f\n",
-                               t, pts[t].r, a, pts[a].r, b, pts[b].r,
-                               mean_r);*/
-               while (current_stop != stop_count && current_stop_r < mean_r) {
-                       current_stop++;
-                       if (current_stop == stop_count)
-                               break;
-                       red0 = red1;
-                       green0 = green1;
-                       blue0 = blue1;
-                       red1 = svgtiny_RED(grad->
-                                       gradient_stop[current_stop].color);
-                       green1 = svgtiny_GREEN(grad->
-                                       gradient_stop[current_stop].color);
-                       blue1 = svgtiny_BLUE(grad->
-                                       gradient_stop[current_stop].color);
-                       last_stop_r = current_stop_r;
-                       current_stop_r = grad->
-                                       gradient_stop[current_stop].offset;
-               }
-               p = malloc(10 * sizeof p[0]);
+               float *p = malloc(13 * sizeof p[0]);
                if (!p)
                        return svgtiny_OUT_OF_MEMORY;
                p[0] = svgtiny_PATH_MOVE;
-               p[1] = point_t->x;
-               p[2] = point_t->y;
+               p[1] = strip_x0 + (strip_dy * 3);
+               p[2] = strip_y0 - (strip_dx * 3);
                p[3] = svgtiny_PATH_LINE;
-               p[4] = point_a->x;
-               p[5] = point_a->y;
+               p[4] = p[1] + strip_dx;
+               p[5] = p[2] + strip_dy;
                p[6] = svgtiny_PATH_LINE;
-               p[7] = point_b->x;
-               p[8] = point_b->y;
-               p[9] = svgtiny_PATH_CLOSE;
-               svgtiny_transform_path(p, 10, state);
-               shape = svgtiny_add_shape(state);
-               if (!shape) {
-                       free(p);
-                       return svgtiny_OUT_OF_MEMORY;
-               }
-               shape->path = p;
-               shape->path_length = 10;
-               /*shape->fill = svgtiny_TRANSPARENT;*/
-               if (current_stop == 0)
-                       shape->fill = grad->gradient_stop[0].color;
-               else if (current_stop == stop_count)
-                       shape->fill = grad->gradient_stop[stop_count - 1].color;
-               else {
-                       float stop_r = (mean_r - last_stop_r) /
-                               (current_stop_r - last_stop_r);
-                       shape->fill = svgtiny_RGB(
-                               (int) ((1 - stop_r) * red0 + stop_r * red1),
-                               (int) ((1 - stop_r) * green0 + stop_r * green1),
-                               (int) ((1 - stop_r) * blue0 + stop_r * blue1));
-               }
-               shape->stroke = svgtiny_TRANSPARENT;
-               #ifdef GRADIENT_DEBUG
-               shape->stroke = svgtiny_RGB(0, 0, 0xff);
-               #endif
-               state->diagram->shape_count++;
-               if (point_a->r < point_b->r) {
-                       t = a;
-                       a = (a + 1) % svgtiny_list_size(pts);
-               } else {
-                       t = b;
-                       b = b == 0 ? svgtiny_list_size(pts) - 1 : b - 1;
-               }
-       }
-
-       /* render gradient vector for debugging */
-       #ifdef GRADIENT_DEBUG
-       {
-               float *p = malloc(7 * sizeof p[0]);
-               if (!p)
-                       return svgtiny_OUT_OF_MEMORY;
-               p[0] = svgtiny_PATH_MOVE;
-               p[1] = gradient_x0;
-               p[2] = gradient_y0;
-               p[3] = svgtiny_PATH_LINE;
-               p[4] = gradient_x1;
-               p[5] = gradient_y1;
-               p[6] = svgtiny_PATH_CLOSE;
-               svgtiny_transform_path(p, 7, state);
+               p[7] = p[4] - (strip_dy * 6);
+               p[8] = p[5] + (strip_dx * 6);
+               p[9] = svgtiny_PATH_LINE;
+               p[10] = p[7] - strip_dx;
+               p[11] = p[8] - strip_dy;
+               p[12] = svgtiny_PATH_CLOSE;
+               svgtiny_transform_path(p, 13, state);
                struct svgtiny_shape *shape = svgtiny_add_shape(state);
                if (!shape) {
-                       free(p);
                        return svgtiny_OUT_OF_MEMORY;
                }
                shape->path = p;
-               shape->path_length = 7;
+               shape->path_length = 13;
                shape->fill = svgtiny_TRANSPARENT;
-               shape->stroke = svgtiny_RGB(0xff, 0, 0);
+               shape->stroke = svgtiny_RGB(0, 0xff, 0);
                state->diagram->shape_count++;
        }
-       #endif
+       return svgtiny_OK;
+}
 
-       /* render triangle vertices with r values for debugging */
-       #ifdef GRADIENT_DEBUG
-       for (unsigned int i = 0; i != svgtiny_list_size(pts); i++) {
+
+/**
+ * render triangle vertices with r values for debugging
+ */
+static svgtiny_code
+add_debug_gradient_vertices(struct svgtiny_parse_state *state,
+                           struct svgtiny_list *pts)
+{
+       unsigned int i;
+
+       for (i = 0; i != svgtiny_list_size(pts); i++) {
                struct grad_point *point = svgtiny_list_get(pts, i);
                struct svgtiny_shape *shape = svgtiny_add_shape(state);
                if (!shape)
@@ -701,95 +566,506 @@ svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
                sprintf(text, "%i=%.3f", i, point->r);
                shape->text = text;
                shape->text_x = state->ctm.a * point->x +
-                               state->ctm.c * point->y + state->ctm.e;
+                       state->ctm.c * point->y + state->ctm.e;
                shape->text_y = state->ctm.b * point->x +
-                               state->ctm.d * point->y + state->ctm.f;
+                       state->ctm.d * point->y + state->ctm.f;
                shape->fill = svgtiny_RGB(0, 0, 0);
                shape->stroke = svgtiny_TRANSPARENT;
                state->diagram->shape_count++;
        }
-       #endif
+       return svgtiny_OK;
+}
 
-       /* plot actual path outline */
-       if (state->stroke != svgtiny_TRANSPARENT) {
-               struct svgtiny_shape *shape;
-               svgtiny_transform_path(p, n, state);
+#endif
+
+
+#ifdef GRADIENT_DEBUG_VECTOR
+/**
+ * render gradient vector for debugging
+ */
+static svgtiny_code
+add_debug_gradient_vector(struct svgtiny_parse_state *state,
+                         struct grad_vector *vector)
+{
+       float *p = malloc(7 * sizeof p[0]);
+       if (!p)
+               return svgtiny_OUT_OF_MEMORY;
+       p[0] = svgtiny_PATH_MOVE;
+       p[1] = vector->x0;
+       p[2] = vector->y0;
+       p[3] = svgtiny_PATH_LINE;
+       p[4] = vector->x1;
+       p[5] = vector->y1;
+       p[6] = svgtiny_PATH_CLOSE;
+       svgtiny_transform_path(p, 7, state);
+       struct svgtiny_shape *shape = svgtiny_add_shape(state);
+       if (!shape) {
+               free(p);
+               return svgtiny_OUT_OF_MEMORY;
+       }
+       shape->path = p;
+       shape->path_length = 7;
+       shape->fill = svgtiny_TRANSPARENT;
+       shape->stroke = svgtiny_RGB(0xff, 0, 0);
+       state->diagram->shape_count++;
+       return svgtiny_OK;
+}
+#endif
+
+
+struct grad_color {
+       struct svgtiny_gradient_stop *stops;
+       unsigned int stop_count;
+       unsigned int current_stop;
+       float last_stop_r;
+       float current_stop_r;
+       int red0;
+       int green0;
+       int blue0;
+       int red1;
+       int green1;
+       int blue1;
+};
+
+/**
+ * initalise gradiant colour state to first stop
+ */
+static inline void
+init_grad_color(struct svgtiny_parse_state_gradient *grad, struct grad_color* gc)
+{
+       assert(2 <= grad->linear_gradient_stop_count);
+
+       gc->stops = grad->gradient_stop;
+       gc->stop_count = grad->linear_gradient_stop_count;
+       gc->current_stop = 0;
+       gc->last_stop_r = 0;
+       gc->current_stop_r = grad->gradient_stop[0].offset;
+       gc->red0 = gc->red1 = svgtiny_RED(grad->gradient_stop[0].color);
+       gc->green0 = gc->green1 = svgtiny_GREEN(grad->gradient_stop[0].color);
+       gc->blue0 = gc->blue1 = svgtiny_BLUE(grad->gradient_stop[0].color);
+}
+
+/**
+ * advance through stops to get to a target r value
+ *
+ * \param gc The gradient colour
+ * \param tgt_t distance along gradient vector to advance to
+ */
+static inline svgtiny_colour
+advance_grad_color(struct grad_color* gc, float tgt_r)
+{
+       svgtiny_colour current_color = 0;
+
+       while ((gc->current_stop != gc->stop_count) &&
+              (gc->current_stop_r < tgt_r)) {
+               gc->current_stop++;
+               if (gc->current_stop == gc->stop_count) {
+                       /* no more stops to try */
+                       break;
+               }
+               gc->red0 = gc->red1;
+               gc->green0 = gc->green1;
+               gc->blue0 = gc->blue1;
+
+               gc->red1 = svgtiny_RED(gc->stops[gc->current_stop].color);
+               gc->green1 = svgtiny_GREEN(gc->stops[gc->current_stop].color);
+               gc->blue1 = svgtiny_BLUE(gc->stops[gc->current_stop].color);
+
+               gc->last_stop_r = gc->current_stop_r;
+               gc->current_stop_r = gc->stops[gc->current_stop].offset;
+       }
+
+       /* compute the colour for the target r */
+       if (gc->current_stop == 0) {
+               current_color = gc->stops[0].color;
+       } else if (gc->current_stop == gc->stop_count) {
+               current_color = gc->stops[gc->stop_count - 1].color;
+       } else {
+               float stop_r = (tgt_r - gc->last_stop_r) /
+                       (gc->current_stop_r - gc->last_stop_r);
+               current_color = svgtiny_RGB(
+                       (int) ((1 - stop_r) * gc->red0   + stop_r * gc->red1),
+                       (int) ((1 - stop_r) * gc->green0 + stop_r * gc->green1),
+                       (int) ((1 - stop_r) * gc->blue0  + stop_r * gc->blue1));
+       }
+       return current_color;
+}
+
+
+/**
+ * add triangles to fill a gradient
+ */
+static svgtiny_code
+add_gradient_triangles(struct svgtiny_parse_state *state,
+                      struct svgtiny_parse_state_gradient *grad,
+                      struct svgtiny_list *pts,
+                      unsigned int min_pt)
+{
+       unsigned int t, a, b;
+       struct grad_color gc;
+
+       init_grad_color(grad, &gc);
 
+       t = min_pt;
+       a = (min_pt + 1) % svgtiny_list_size(pts);
+       b = min_pt == 0 ? svgtiny_list_size(pts) - 1 : min_pt - 1;
+       while (a != b) {
+               struct grad_point *point_t = svgtiny_list_get(pts, t);
+               struct grad_point *point_a = svgtiny_list_get(pts, a);
+               struct grad_point *point_b = svgtiny_list_get(pts, b);
+               float mean_r = (point_t->r + point_a->r + point_b->r) / 3;
+               float *p;
+               struct svgtiny_shape *shape;
+#ifdef GRADIENT_DEBUG
+               fprintf(stderr, "triangle: t %i %.3f a %i %.3f b %i %.3f "
+                       "mean_r %.3f\n",
+                       t, pts[t].r, a, pts[a].r, b, pts[b].r,
+                       mean_r);
+#endif
+               p = malloc(10 * sizeof p[0]);
+               if (!p)
+                       return svgtiny_OUT_OF_MEMORY;
+               p[0] = svgtiny_PATH_MOVE;
+               p[1] = point_t->x;
+               p[2] = point_t->y;
+               p[3] = svgtiny_PATH_LINE;
+               p[4] = point_a->x;
+               p[5] = point_a->y;
+               p[6] = svgtiny_PATH_LINE;
+               p[7] = point_b->x;
+               p[8] = point_b->y;
+               p[9] = svgtiny_PATH_CLOSE;
+               svgtiny_transform_path(p, 10, state);
                shape = svgtiny_add_shape(state);
                if (!shape) {
                        free(p);
                        return svgtiny_OUT_OF_MEMORY;
                }
                shape->path = p;
-               shape->path_length = n;
-               shape->fill = svgtiny_TRANSPARENT;
+               shape->path_length = 10;
+               shape->fill = advance_grad_color(&gc, mean_r);
+               shape->stroke = svgtiny_TRANSPARENT;
+#ifdef GRADIENT_DEBUG
+               shape->stroke = svgtiny_RGB(0, 0, 0xff);
+#endif
                state->diagram->shape_count++;
-       } else {
-               free(p);
+
+               if (point_a->r < point_b->r) {
+                       t = a;
+                       a = (a + 1) % svgtiny_list_size(pts);
+               } else {
+                       t = b;
+                       b = b == 0 ? svgtiny_list_size(pts) - 1 : b - 1;
+               }
        }
+       return svgtiny_OK;
+}
 
-       svgtiny_list_free(pts);
+
+/**
+ * add lines to stroke a gradient
+ */
+static svgtiny_code
+add_gradient_lines(struct svgtiny_parse_state *state,
+                  struct svgtiny_parse_state_gradient *grad,
+                  struct svgtiny_list *pts,
+                  unsigned int min_pt)
+{
+       struct grad_color gc;
+       unsigned int b;
+       struct grad_point *point_a;
+       struct grad_point *point_b;
+       int dir;
+
+       for (dir=0;dir <2;dir++) {
+               init_grad_color(grad, &gc);
+
+               point_a = svgtiny_list_get(pts, min_pt);
+               if (dir==0) {
+                       b = (min_pt + 1) % svgtiny_list_size(pts);
+               } else {
+                       b = min_pt == 0 ? svgtiny_list_size(pts) - 1 : min_pt - 1;
+               }
+               point_b = svgtiny_list_get(pts, b);
+               while (point_a->r <= point_b->r) {
+                       float mean_r = (point_a->r + point_b->r) / 2;
+                       float *p;
+                       struct svgtiny_shape *shape;
+
+#ifdef GRADIENT_DEBUG
+                       fprintf(stderr,
+                               "line: a (x:%.3f y:%.3f r:%.3f) "
+                               "b (i:%i x:%.3f y:%.3f r:%.3f) "
+                               "mean_r %.3f\n",
+                               point_a->x,point_a->y,point_a->r,
+                               b, point_b->x,point_b->y,point_b->r, mean_r);
+#endif
+
+                       p = malloc(7 * sizeof p[0]);
+                       if (!p) {
+                               return svgtiny_OUT_OF_MEMORY;
+                       }
+                       p[0] = svgtiny_PATH_MOVE;
+                       p[1] = point_a->x;
+                       p[2] = point_a->y;
+                       p[3] = svgtiny_PATH_LINE;
+                       p[4] = point_b->x;
+                       p[5] = point_b->y;
+                       p[6] = svgtiny_PATH_CLOSE;
+                       svgtiny_transform_path(p, 7, state);
+                       shape = svgtiny_add_shape(state);
+                       if (!shape) {
+                               free(p);
+                               return svgtiny_OUT_OF_MEMORY;
+                       }
+                       shape->path = p;
+                       shape->path_length = 7;
+                       shape->fill = svgtiny_TRANSPARENT;
+                       shape->stroke = advance_grad_color(&gc, mean_r);
+
+                       state->diagram->shape_count++;
+
+                       point_a = point_b;
+                       if (dir==0) {
+                               b = (b + 1) % svgtiny_list_size(pts);
+                       } else {
+                               b = b == 0 ? svgtiny_list_size(pts) - 1 : b - 1;
+                       }
+                       point_b = svgtiny_list_get(pts, b);
+
+               }
+       }
 
        return svgtiny_OK;
 }
 
 
 /**
- * Get the bounding box of path.
+ * Find a gradient by id and parse it.
  */
+svgtiny_code
+svgtiny_find_gradient(const char *id,
+                     size_t idlen,
+                     struct svgtiny_parse_state_gradient *grad,
+                     struct svgtiny_parse_state *state)
+{
+       dom_element *gradient;
+       dom_string *id_str, *name;
+       dom_exception exc;
+       svgtiny_code res = svgtiny_OK;
+
+#ifdef GRADIENT_DEBUG
+       fprintf(stderr, "svgtiny_find_gradient: id \"%.*s\"\n", (int)idlen, id);
+#endif
+
+       grad->linear_gradient_stop_count = 0;
+       if (grad->gradient_x1 != NULL)
+               dom_string_unref(grad->gradient_x1);
+       if (grad->gradient_y1 != NULL)
+               dom_string_unref(grad->gradient_y1);
+       if (grad->gradient_x2 != NULL)
+               dom_string_unref(grad->gradient_x2);
+       if (grad->gradient_y2 != NULL)
+               dom_string_unref(grad->gradient_y2);
+       grad->gradient_x1 = dom_string_ref(state->interned_zero_percent);
+       grad->gradient_y1 = dom_string_ref(state->interned_zero_percent);
+       grad->gradient_x2 = dom_string_ref(state->interned_hundred_percent);
+       grad->gradient_y2 = dom_string_ref(state->interned_zero_percent);
+       grad->gradient_user_space_on_use = false;
+       grad->gradient_transform.a = 1;
+       grad->gradient_transform.b = 0;
+       grad->gradient_transform.c = 0;
+       grad->gradient_transform.d = 1;
+       grad->gradient_transform.e = 0;
+       grad->gradient_transform.f = 0;
+
+       exc = dom_string_create_interned((const uint8_t *) id, idlen, &id_str);
+       if (exc != DOM_NO_ERR)
+               return svgtiny_SVG_ERROR;
 
-void svgtiny_path_bbox(float *p, unsigned int n,
-               float *x0, float *y0, float *x1, float *y1)
+       exc = dom_document_get_element_by_id(state->document, id_str, &gradient);
+       dom_string_unref(id_str);
+       if (exc != DOM_NO_ERR || gradient == NULL) {
+#ifdef GRADIENT_DEBUG
+               fprintf(stderr, "gradient \"%.*s\" not found\n", (int)idlen, id);
+#endif
+               return svgtiny_SVG_ERROR;
+       }
+
+       exc = dom_node_get_node_name(gradient, &name);
+       if (exc != DOM_NO_ERR) {
+               dom_node_unref(gradient);
+               return svgtiny_SVG_ERROR;
+       }
+
+       if (dom_string_isequal(name, state->interned_linearGradient)) {
+               res = svgtiny_parse_linear_gradient(gradient, grad, state);
+       }
+
+       dom_node_unref(gradient);
+       dom_string_unref(name);
+
+#ifdef GRADIENT_DEBUG
+       fprintf(stderr, "linear_gradient_stop_count %i\n",
+               grad->linear_gradient_stop_count);
+#endif
+
+       return res;
+}
+
+
+/**
+ * Add a path with a linear gradient stroke to the diagram.
+ *
+ */
+svgtiny_code
+svgtiny_gradient_add_stroke_path(float *p,
+                                unsigned int n,
+                                struct svgtiny_parse_state *state)
 {
-       unsigned int j;
+       struct grad_vector vector; /* gradient vector */
+       struct svgtiny_transformation_matrix trans;
+       struct svgtiny_list *pts;
+       unsigned int min_pt = 0;
+       struct svgtiny_parse_state_gradient *grad;
+       svgtiny_code res;
 
-       *x0 = *x1 = p[1];
-       *y0 = *y1 = p[2];
+       assert(state->stroke == svgtiny_LINEAR_GRADIENT);
+       /* original path should not be stroked as a shape is added here */
+       state->stroke = svgtiny_TRANSPARENT;
 
-       for (j = 0; j != n; ) {
-               unsigned int points = 0;
-               unsigned int k;
-               switch ((int) p[j]) {
-               case svgtiny_PATH_MOVE:
-               case svgtiny_PATH_LINE:
-                       points = 1;
-                       break;
-               case svgtiny_PATH_CLOSE:
-                       points = 0;
-                       break;
-               case svgtiny_PATH_BEZIER:
-                       points = 3;
-                       break;
-               default:
-                       assert(0);
-               }
-               j++;
-               for (k = 0; k != points; k++) {
-                       float x = p[j], y = p[j + 1];
-                       if (x < *x0)
-                               *x0 = x;
-                       else if (*x1 < x)
-                               *x1 = x;
-                       if (y < *y0)
-                               *y0 = y;
-                       else if (*y1 < y)
-                               *y1 = y;
-                       j += 2;
+       grad = &state->stroke_grad;
+
+       /* at least two stops are required to form a valid gradient */
+       if (grad->linear_gradient_stop_count < 2) {
+               if (grad->linear_gradient_stop_count == 1) {
+                       /* stroke the shape with first stop colour */
+                       state->stroke = grad->gradient_stop[0].color;
                }
+               return svgtiny_OK;
+       }
+
+       compute_gradient_vector(p, n, state, grad, &vector);
+
+       svgtiny_invert_matrix(&grad->gradient_transform, &trans);
+
+       /* compute points on the path for vertices */
+       pts = svgtiny_list_create(sizeof (struct grad_point));
+       if (!pts) {
+               return svgtiny_OUT_OF_MEMORY;
+       }
+       res = compute_grad_points(p, n, &trans, &vector, pts, &min_pt);
+       if (res != svgtiny_OK) {
+               svgtiny_list_free(pts);
+               return res;
        }
+
+       /* There must be at least a single point for the gradient */
+        if (svgtiny_list_size(pts) == 0) {
+               svgtiny_list_free(pts);
+               return svgtiny_OK;
+        }
+
+       /* render lines */
+       res = add_gradient_lines(state, grad, pts, min_pt);
+
+       svgtiny_list_free(pts);
+
+       return res;
 }
 
 
 /**
- * Invert a transformation matrix.
+ * Add a path with a linear gradient fill to the diagram.
  */
-void svgtiny_invert_matrix(float *m, float *inv)
+svgtiny_code
+svgtiny_gradient_add_fill_path(float *p,
+                              unsigned int n,
+                              struct svgtiny_parse_state *state)
 {
-       float determinant = m[0]*m[3] - m[1]*m[2];
-       inv[0] = m[3] / determinant;
-       inv[1] = -m[1] / determinant;
-       inv[2] = -m[2] / determinant;
-       inv[3] = m[0] / determinant;
-       inv[4] = (m[2]*m[5] - m[3]*m[4]) / determinant;
-       inv[5] = (m[1]*m[4] - m[0]*m[5]) / determinant;
+       struct grad_vector vector; /* gradient vector */
+       struct svgtiny_transformation_matrix trans;
+       struct svgtiny_list *pts;
+       unsigned int min_pt = 0;
+       struct svgtiny_parse_state_gradient *grad;
+       svgtiny_code res;
+
+       assert(state->fill == svgtiny_LINEAR_GRADIENT);
+       /* original path should not be filled as a shape is added here */
+       state->fill = svgtiny_TRANSPARENT;
+
+       grad = &state->fill_grad;
+
+       /* at least two stops are required to form a valid gradient */
+       if (grad->linear_gradient_stop_count < 2) {
+               if (grad->linear_gradient_stop_count == 1) {
+                       /* fill the shape with stop colour */
+                       state->fill = grad->gradient_stop[0].color;
+               }
+               return svgtiny_OK;
+       }
+
+       compute_gradient_vector(p, n, state, grad, &vector);
+
+#ifdef GRADIENT_DEBUG
+       /* debug strips */
+       res = add_debug_gradient_strips(state, &vector);
+       if (res != svgtiny_OK) {
+               return res;
+       }
+#endif
+
+       /* invert gradient transform for applying to vertices */
+       svgtiny_invert_matrix(&grad->gradient_transform, &trans);
+#ifdef GRADIENT_DEBUG
+       fprintf(stderr, "inverse transform %g %g %g %g %g %g\n",
+               trans[0], trans[1], trans[2], trans[3],
+               trans[4], trans[5]);
+#endif
+
+       /* compute points on the path for triangle vertices */
+       pts = svgtiny_list_create(sizeof (struct grad_point));
+       if (!pts) {
+               return svgtiny_OUT_OF_MEMORY;
+       }
+       res = compute_grad_points(p, n, &trans, &vector, pts, &min_pt);
+       if (res != svgtiny_OK) {
+               svgtiny_list_free(pts);
+               return res;
+       }
+
+        /* There must be at least a single point for the gradient */
+        if (svgtiny_list_size(pts) == 0) {
+               svgtiny_list_free(pts);
+               return svgtiny_OK;
+        }
+
+       /* render triangles */
+       res = add_gradient_triangles(state, grad, pts, min_pt);
+       if (res != svgtiny_OK) {
+               svgtiny_list_free(pts);
+               return res;
+       }
+
+#ifdef GRADIENT_DEBUG_VECTOR
+       /* render gradient vector for debugging */
+       res = add_debug_gradient_vector(state, &vector);
+       if (res != svgtiny_OK) {
+               svgtiny_list_free(pts);
+               return res;
+       }
+#endif
+
+#ifdef GRADIENT_DEBUG
+       /* render triangle vertices with r values for debugging */
+       res = add_debug_gradient_vertices(state, pts);
+       if (res != svgtiny_OK) {
+               svgtiny_list_free(pts);
+               return res;
+       }
+#endif
+
+       svgtiny_list_free(pts);
+
+       return svgtiny_OK;
 }
index d2ba4d377162e4abc06a06d573bab70cae78842d..41879f25895f952282bbdef85711054dc8112073 100644 (file)
@@ -94,13 +94,6 @@ struct svgtiny_parse_internal_operation {
 struct svgtiny_shape *svgtiny_add_shape(struct svgtiny_parse_state *state);
 void svgtiny_transform_path(float *p, unsigned int n,
                struct svgtiny_parse_state *state);
-#if (defined(_GNU_SOURCE) && !defined(__APPLE__) || defined(__amigaos4__) || defined(__HAIKU__) || (defined(_POSIX_C_SOURCE) && ((_POSIX_C_SOURCE - 0) >= 200809L)))
-#define HAVE_STRNDUP
-#else
-#undef HAVE_STRNDUP
-char *svgtiny_strndup(const char *s, size_t n);
-#define strndup svgtiny_strndup
-#endif
 
 /* svgtiny_parse.c */
 svgtiny_code svgtiny_parse_poly_points(const char *data, size_t datalen,
@@ -126,7 +119,9 @@ svgtiny_code svgtiny_find_gradient(const char *id,
                size_t idlen,
                struct svgtiny_parse_state_gradient *grad,
                struct svgtiny_parse_state *state);
-svgtiny_code svgtiny_add_path_linear_gradient(float *p, unsigned int n,
+svgtiny_code svgtiny_gradient_add_fill_path(float *p, unsigned int n,
+               struct svgtiny_parse_state *state);
+svgtiny_code svgtiny_gradient_add_stroke_path(float *p, unsigned int n,
                struct svgtiny_parse_state *state);
 
 /* svgtiny_list.c */
diff --git a/test/data/gradient.mvg b/test/data/gradient.mvg
new file mode 100644 (file)
index 0000000..182d37b
--- /dev/null
@@ -0,0 +1,84 @@
+viewbox 0 0 1000 1000
+fill #aa88ff stroke none stroke-width 21 path 'M 83.3333 125 L 916.667 125 L 83.3333 162.5 Z ' 
+fill #aa88ff stroke none stroke-width 21 path 'M 916.667 125 L 916.667 162.5 L 83.3333 162.5 Z ' 
+fill #ab89fe stroke none stroke-width 21 path 'M 83.3333 162.5 L 916.667 162.5 L 83.3333 200 Z ' 
+fill #ad8bfd stroke none stroke-width 21 path 'M 916.667 162.5 L 916.667 200 L 83.3333 200 Z ' 
+fill #b08efb stroke none stroke-width 21 path 'M 916.667 200 L 916.667 237.5 L 83.3333 200 Z ' 
+fill #b18ffa stroke none stroke-width 21 path 'M 83.3333 200 L 916.667 237.5 L 83.3333 237.5 Z ' 
+fill #b593f8 stroke none stroke-width 21 path 'M 83.3333 237.5 L 916.667 237.5 L 83.3333 275 Z ' 
+fill #b694f7 stroke none stroke-width 21 path 'M 916.667 237.5 L 916.667 275 L 83.3333 275 Z ' 
+fill #b997f5 stroke none stroke-width 21 path 'M 83.3333 275 L 916.667 275 L 83.3333 312.5 Z ' 
+fill #bb99f4 stroke none stroke-width 21 path 'M 916.667 275 L 916.667 312.5 L 83.3333 312.5 Z ' 
+fill #be9cf2 stroke none stroke-width 21 path 'M 83.3333 312.5 L 916.667 312.5 L 83.3333 350 Z ' 
+fill #c09ef1 stroke none stroke-width 21 path 'M 916.667 312.5 L 916.667 350 L 83.3333 350 Z ' 
+fill #c3a1ef stroke none stroke-width 21 path 'M 83.3333 350 L 916.667 350 L 83.3333 387.5 Z ' 
+fill #c4a2ee stroke none stroke-width 21 path 'M 916.667 350 L 916.667 387.5 L 83.3333 387.5 Z ' 
+fill #c7a5ed stroke none stroke-width 21 path 'M 83.3333 387.5 L 916.667 387.5 L 83.3333 425 Z ' 
+fill #c9a7ec stroke none stroke-width 21 path 'M 916.667 387.5 L 916.667 425 L 83.3333 425 Z ' 
+fill #ccaaea stroke none stroke-width 21 path 'M 83.3333 425 L 916.667 425 L 83.3333 462.5 Z ' 
+fill #ceace9 stroke none stroke-width 21 path 'M 916.667 425 L 916.667 462.5 L 83.3333 462.5 Z ' 
+fill #d1afe7 stroke none stroke-width 21 path 'M 83.3333 462.5 L 916.667 462.5 L 83.3333 500 Z ' 
+fill #d2b0e6 stroke none stroke-width 21 path 'M 916.667 462.5 L 916.667 500 L 83.3333 500 Z ' 
+fill #d6b4e4 stroke none stroke-width 21 path 'M 83.3333 500 L 916.667 500 L 83.3333 537.5 Z ' 
+fill #d7b5e3 stroke none stroke-width 21 path 'M 916.667 500 L 916.667 537.5 L 83.3333 537.5 Z ' 
+fill #dab8e1 stroke none stroke-width 21 path 'M 83.3333 537.5 L 916.667 537.5 L 83.3333 575 Z ' 
+fill #dcbae0 stroke none stroke-width 21 path 'M 916.667 537.5 L 916.667 575 L 83.3333 575 Z ' 
+fill #dfbdde stroke none stroke-width 21 path 'M 83.3333 575 L 916.667 575 L 83.3333 612.5 Z ' 
+fill #e1bfdd stroke none stroke-width 21 path 'M 916.667 575 L 916.667 612.5 L 83.3333 612.5 Z ' 
+fill #e4c2dc stroke none stroke-width 21 path 'M 83.3333 612.5 L 916.667 612.5 L 83.3333 650 Z ' 
+fill #e5c3db stroke none stroke-width 21 path 'M 916.667 612.5 L 916.667 650 L 83.3333 650 Z ' 
+fill #e8c6d9 stroke none stroke-width 21 path 'M 83.3333 650 L 916.667 650 L 83.3333 687.5 Z ' 
+fill #eac8d8 stroke none stroke-width 21 path 'M 916.667 650 L 916.667 687.5 L 83.3333 687.5 Z ' 
+fill #edcbd6 stroke none stroke-width 21 path 'M 83.3333 687.5 L 916.667 687.5 L 83.3333 725 Z ' 
+fill #efcdd5 stroke none stroke-width 21 path 'M 916.667 687.5 L 916.667 725 L 83.3333 725 Z ' 
+fill #f2d0d3 stroke none stroke-width 21 path 'M 83.3333 725 L 916.667 725 L 83.3333 762.5 Z ' 
+fill #f3d1d2 stroke none stroke-width 21 path 'M 916.667 725 L 916.667 762.5 L 83.3333 762.5 Z ' 
+fill #f7d5d0 stroke none stroke-width 21 path 'M 83.3333 762.5 L 916.667 762.5 L 83.3333 800 Z ' 
+fill #f8d6cf stroke none stroke-width 21 path 'M 916.667 762.5 L 916.667 800 L 83.3333 800 Z ' 
+fill #fbd9cd stroke none stroke-width 21 path 'M 83.3333 800 L 916.667 800 L 83.3333 837.5 Z ' 
+fill #fddbcc stroke none stroke-width 21 path 'M 916.667 800 L 916.667 837.5 L 83.3333 837.5 Z ' 
+fill #ffddcc stroke none stroke-width 21 path 'M 83.3333 837.5 L 916.667 837.5 L 83.3333 875 Z ' 
+fill #ffddcc stroke none stroke-width 21 path 'M 916.667 837.5 L 916.667 875 L 83.3333 875 Z ' 
+fill none stroke #ff6600 stroke-width 21 path 'M 83.3333 125 L 125 125 Z ' 
+fill none stroke #fe6a02 stroke-width 21 path 'M 125 125 L 166.667 125 Z ' 
+fill none stroke #ff7208 stroke-width 21 path 'M 166.667 125 L 208.333 125 Z ' 
+fill none stroke #ff7b0e stroke-width 21 path 'M 208.333 125 L 250 125 Z ' 
+fill none stroke #ff8313 stroke-width 21 path 'M 250 125 L 291.667 125 Z ' 
+fill none stroke #ff8c19 stroke-width 21 path 'M 291.667 125 L 333.333 125 Z ' 
+fill none stroke #ff941f stroke-width 21 path 'M 333.333 125 L 375 125 Z ' 
+fill none stroke #ff9d24 stroke-width 21 path 'M 375 125 L 416.667 125 Z ' 
+fill none stroke #ffa52a stroke-width 21 path 'M 416.667 125 L 458.333 125 Z ' 
+fill none stroke #ffae30 stroke-width 21 path 'M 458.333 125 L 500 125 Z ' 
+fill none stroke #ffb635 stroke-width 21 path 'M 500 125 L 541.667 125 Z ' 
+fill none stroke #ffbf3b stroke-width 21 path 'M 541.667 125 L 583.333 125 Z ' 
+fill none stroke #ffc741 stroke-width 21 path 'M 583.333 125 L 625 125 Z ' 
+fill none stroke #ffd046 stroke-width 21 path 'M 625 125 L 666.667 125 Z ' 
+fill none stroke #ffd84c stroke-width 21 path 'M 666.667 125 L 708.333 125 Z ' 
+fill none stroke #ffe152 stroke-width 21 path 'M 708.333 125 L 750 125 Z ' 
+fill none stroke #ffe957 stroke-width 21 path 'M 750 125 L 791.667 125 Z ' 
+fill none stroke #fff25d stroke-width 21 path 'M 791.667 125 L 833.333 125 Z ' 
+fill none stroke #fffa63 stroke-width 21 path 'M 833.333 125 L 875 125 Z ' 
+fill none stroke #ffff66 stroke-width 21 path 'M 875 125 L 916.667 125 Z ' 
+fill none stroke #ffff66 stroke-width 21 path 'M 916.667 125 L 916.667 875 Z ' 
+fill none stroke #ff6600 stroke-width 21 path 'M 83.3333 125 L 83.3333 875 Z ' 
+fill none stroke #ff6600 stroke-width 21 path 'M 83.3333 875 L 125 875 Z ' 
+fill none stroke #ff6a02 stroke-width 21 path 'M 125 875 L 166.667 875 Z ' 
+fill none stroke #ff7208 stroke-width 21 path 'M 166.667 875 L 208.333 875 Z ' 
+fill none stroke #ff7b0e stroke-width 21 path 'M 208.333 875 L 250 875 Z ' 
+fill none stroke #ff8313 stroke-width 21 path 'M 250 875 L 291.667 875 Z ' 
+fill none stroke #ff8c19 stroke-width 21 path 'M 291.667 875 L 333.333 875 Z ' 
+fill none stroke #ff941f stroke-width 21 path 'M 333.333 875 L 375 875 Z ' 
+fill none stroke #ff9d24 stroke-width 21 path 'M 375 875 L 416.667 875 Z ' 
+fill none stroke #ffa52a stroke-width 21 path 'M 416.667 875 L 458.333 875 Z ' 
+fill none stroke #ffae30 stroke-width 21 path 'M 458.333 875 L 500 875 Z ' 
+fill none stroke #ffb635 stroke-width 21 path 'M 500 875 L 541.667 875 Z ' 
+fill none stroke #ffbf3b stroke-width 21 path 'M 541.667 875 L 583.333 875 Z ' 
+fill none stroke #ffc741 stroke-width 21 path 'M 583.333 875 L 625 875 Z ' 
+fill none stroke #ffd046 stroke-width 21 path 'M 625 875 L 666.667 875 Z ' 
+fill none stroke #ffd84c stroke-width 21 path 'M 666.667 875 L 708.333 875 Z ' 
+fill none stroke #ffe152 stroke-width 21 path 'M 708.333 875 L 750 875 Z ' 
+fill none stroke #ffe957 stroke-width 21 path 'M 750 875 L 791.667 875 Z ' 
+fill none stroke #fff25d stroke-width 21 path 'M 791.667 875 L 833.333 875 Z ' 
+fill none stroke #fffa63 stroke-width 21 path 'M 833.333 875 L 875 875 Z ' 
+fill none stroke #ffff66 stroke-width 21 path 'M 875 875 L 916.667 875 Z ' 
+fill none stroke #ffff66 stroke-width 21 path 'M 916.667 875 L 916.667 125 Z ' 
diff --git a/test/data/gradient.svg b/test/data/gradient.svg
new file mode 100644 (file)
index 0000000..f093370
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     version="1.1"
+     viewBox="0 0 300 200" >
+
+  <title>Example lingrag01</title>
+  <desc>Fill a rectangle using a linear-gradient paint server.</desc>
+
+  <defs>
+    <linearGradient id="GradientA" x2="0%" y2="100%">
+      <stop offset="5%" stop-color="#A8F" />
+      <stop offset="95%" stop-color="#FDC" />
+    </linearGradient>
+    <linearGradient id="GradientB">
+      <stop offset="5%" stop-color="#F60" />
+      <stop offset="95%" stop-color="#Ff6" />
+    </linearGradient>
+  </defs>
+
+  <!-- The rectangle is filled and stroked using linear-gradient paint servers -->
+  <rect fill="url(#GradientA)"
+       stroke="url(#GradientB)"
+       stroke-width="5"
+       x="25" y="25" width="250" height="150"/>
+</svg>