]> gitweb.michael.orlitzky.com - libsvgtiny.git/commitdiff
Reimplement color parsing
authorVincent Sanders <vince@kyllikki.org>
Mon, 1 Jul 2024 14:03:41 +0000 (15:03 +0100)
committerVincent Sanders <vince@kyllikki.org>
Thu, 4 Jul 2024 13:15:31 +0000 (14:15 +0100)
src/svgtiny.c
src/svgtiny_gradient.c
src/svgtiny_internal.h
src/svgtiny_parse.c
test/data/colors.mvg [new file with mode: 0644]
test/data/colors.svg [new file with mode: 0644]

index d8d8bf0553a71f02acdf6ef1810d765e10db2fa6..dacf2c854a5132de6a124b17b403a633aa0444a7 100644 (file)
@@ -20,8 +20,6 @@
 #include "svgtiny.h"
 #include "svgtiny_internal.h"
 
-/* Source file generated by `gperf`. */
-#include "autogenerated_colors.c"
 
 #define TAU             6.28318530717958647692
 
@@ -65,9 +63,6 @@ static void svgtiny_parse_transform_attributes(dom_element *node,
                struct svgtiny_parse_state *state);
 static svgtiny_code svgtiny_add_path(float *p, unsigned int n,
                struct svgtiny_parse_state *state);
-static void _svgtiny_parse_color(const char *s, svgtiny_colour *c,
-               struct svgtiny_parse_state_gradient *grad,
-               struct svgtiny_parse_state *state);
 
 /**
  * rotate midpoint vector
@@ -555,6 +550,7 @@ static void svgtiny_grad_string_cleanup(
        }
 }
 
+
 /**
  * Set the local externally-stored parts of a parse state.
  * Call this in functions that made a new state on the stack.
@@ -566,6 +562,7 @@ static void svgtiny_setup_state_local(struct svgtiny_parse_state *state)
        svgtiny_grad_string_ref(&(state->stroke_grad));
 }
 
+
 /**
  * Cleanup the local externally-stored parts of a parse state.
  * Call this in functions that made a new state on the stack.
@@ -595,6 +592,7 @@ struct svgtiny_diagram *svgtiny_create(void)
        return NULL;
 }
 
+
 static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...)
 {
        UNUSED(severity);
@@ -602,6 +600,7 @@ static void ignore_msg(uint32_t severity, void *ctx, const char *msg, ...)
        UNUSED(msg);
 }
 
+
 /**
  * Parse a block of memory into a svgtiny_diagram.
  */
@@ -863,7 +862,6 @@ svgtiny_code svgtiny_parse_svg(dom_element *svg,
 }
 
 
-
 /**
  * Parse a <path> element node.
  *
@@ -1609,7 +1607,6 @@ svgtiny_code svgtiny_parse_line(dom_element *line,
 }
 
 
-
 /**
  * Parse a <polyline> or <polygon> element node.
  *
@@ -1847,18 +1844,29 @@ void svgtiny_parse_paint_attributes(dom_element *node,
        dom_string *attr;
        dom_exception exc;
 
+       /* fill color */
        exc = dom_element_get_attribute(node, state->interned_fill, &attr);
        if (exc == DOM_NO_ERR && attr != NULL) {
-               svgtiny_parse_color(attr, &state->fill, &state->fill_grad, state);
+               svgtiny_parse_paint(dom_string_data(attr),
+                                   dom_string_byte_length(attr),
+                                   &state->fill_grad,
+                                   state,
+                                   &state->fill);
                dom_string_unref(attr);
        }
 
+       /* stroke color */
        exc = dom_element_get_attribute(node, state->interned_stroke, &attr);
        if (exc == DOM_NO_ERR && attr != NULL) {
-               svgtiny_parse_color(attr, &state->stroke, &state->stroke_grad, state);
+               svgtiny_parse_paint(dom_string_data(attr),
+                                   dom_string_byte_length(attr),
+                                   &state->stroke_grad,
+                                   state,
+                                   &state->stroke);
                dom_string_unref(attr);
        }
 
+       /* stroke width */
        exc = dom_element_get_attribute(node, state->interned_stroke_width, &attr);
        if (exc == DOM_NO_ERR && attr != NULL) {
                float stroke_width;
@@ -1875,26 +1883,25 @@ void svgtiny_parse_paint_attributes(dom_element *node,
                char *style = strndup(dom_string_data(attr),
                                      dom_string_byte_length(attr));
                const char *s;
-               char *value;
                if ((s = strstr(style, "fill:"))) {
                        s += 5;
-                       while (*s == ' ')
-                               s++;
-                       value = strndup(s, strcspn(s, "; "));
-                       _svgtiny_parse_color(value, &state->fill, &state->fill_grad, state);
-                       free(value);
+                       svgtiny_parse_paint(s,
+                                           strcspn(s, "; "),
+                                           &state->fill_grad,
+                                           state,
+                                           &state->fill);
                }
                if ((s = strstr(style, "stroke:"))) {
                        s += 7;
-                       while (*s == ' ')
-                               s++;
-                       value = strndup(s, strcspn(s, "; "));
-                       _svgtiny_parse_color(value, &state->stroke, &state->stroke_grad, state);
-                       free(value);
+                       svgtiny_parse_paint(s,
+                                           strcspn(s, "; "),
+                                           &state->stroke_grad,
+                                           state,
+                                           &state->stroke);
                }
                if ((s = strstr(style, "stroke-width:"))) {
-                       s += 13;
                        float stroke_width;
+                       s += 13;
                        svgtiny_parse_length(s,
                                             strcspn(s, "; "),
                                             state->viewport_width,
@@ -1907,79 +1914,6 @@ void svgtiny_parse_paint_attributes(dom_element *node,
 }
 
 
-/**
- * Parse a colour.
- */
-
-static void _svgtiny_parse_color(const char *s, svgtiny_colour *c,
-               struct svgtiny_parse_state_gradient *grad,
-               struct svgtiny_parse_state *state)
-{
-       unsigned int r, g, b;
-       float rf, gf, bf;
-       size_t len = strlen(s);
-       char *id = 0, *rparen;
-
-       if (len == 4 && s[0] == '#') {
-               if (sscanf(s + 1, "%1x%1x%1x", &r, &g, &b) == 3)
-                       *c = svgtiny_RGB(r | r << 4, g | g << 4, b | b << 4);
-
-       } else if (len == 7 && s[0] == '#') {
-               if (sscanf(s + 1, "%2x%2x%2x", &r, &g, &b) == 3)
-                       *c = svgtiny_RGB(r, g, b);
-
-       } else if (10 <= len && s[0] == 'r' && s[1] == 'g' && s[2] == 'b' &&
-                       s[3] == '(' && s[len - 1] == ')') {
-               if (sscanf(s + 4, "%u,%u,%u", &r, &g, &b) == 3)
-                       *c = svgtiny_RGB(r, g, b);
-               else if (sscanf(s + 4, "%f%%,%f%%,%f%%", &rf, &gf, &bf) == 3) {
-                       b = bf * 255 / 100;
-                       g = gf * 255 / 100;
-                       r = rf * 255 / 100;
-                       *c = svgtiny_RGB(r, g, b);
-               }
-
-       } else if (len == 4 && strcmp(s, "none") == 0) {
-               *c = svgtiny_TRANSPARENT;
-
-       } else if (5 < len && s[0] == 'u' && s[1] == 'r' && s[2] == 'l' &&
-                       s[3] == '(') {
-               if (grad == NULL) {
-                       *c = svgtiny_RGB(0, 0, 0);
-               } else if (s[4] == '#') {
-                       id = strdup(s + 5);
-                       if (!id)
-                               return;
-                       rparen = strchr(id, ')');
-                       if (rparen)
-                               *rparen = 0;
-                       svgtiny_find_gradient(id, grad, state);
-                       free(id);
-                       if (grad->linear_gradient_stop_count == 0)
-                               *c = svgtiny_TRANSPARENT;
-                       else if (grad->linear_gradient_stop_count == 1)
-                               *c = grad->gradient_stop[0].color;
-                       else
-                               *c = svgtiny_LINEAR_GRADIENT;
-               }
-
-       } else {
-               const struct svgtiny_named_color *named_color;
-               named_color = svgtiny_color_lookup(s, strlen(s));
-               if (named_color)
-                       *c = named_color->color;
-       }
-}
-
-void svgtiny_parse_color(dom_string *s, svgtiny_colour *c,
-               struct svgtiny_parse_state_gradient *grad,
-               struct svgtiny_parse_state *state)
-{
-       dom_string_ref(s);
-       _svgtiny_parse_color(dom_string_data(s), c, grad, state);
-       dom_string_unref(s);
-}
-
 /**
  * Parse font attributes, if present.
  */
index db67e77122d01aabfc74ae5688b1e1d8b0ad3db6..b6b2084ad625f0fc44719406d74eb640b8906046 100644 (file)
@@ -29,16 +29,18 @@ static void svgtiny_invert_matrix(float *m, float *inv);
  * Find a gradient by id and parse it.
  */
 
-void svgtiny_find_gradient(const char *id,
+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", id);
+       fprintf(stderr, "svgtiny_find_gradient: id \"%.*s\"\n", idlen, id);
        #endif
 
        grad->linear_gradient_stop_count = 0;
@@ -62,29 +64,28 @@ void svgtiny_find_gradient(const char *id,
        grad->gradient_transform.e = 0;
        grad->gradient_transform.f = 0;
 
-       exc = dom_string_create_interned((const uint8_t *) id,
-                       strlen(id), &id_str);
+       exc = dom_string_create_interned((const uint8_t *) id, idlen, &id_str);
        if (exc != DOM_NO_ERR)
-               return;
+               return svgtiny_SVG_ERROR;
 
-       exc = dom_document_get_element_by_id(state->document, id_str,
-                                            &gradient);
+       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", id);
+               fprintf(stderr, "gradient \"%.*s\" not found\n", idlen, id);
                #endif
-               return;
+               return svgtiny_SVG_ERROR;
        }
 
        exc = dom_node_get_node_name(gradient, &name);
        if (exc != DOM_NO_ERR) {
                dom_node_unref(gradient);
-               return;
+               return svgtiny_SVG_ERROR;
        }
 
-       if (dom_string_isequal(name, state->interned_linearGradient))
-               svgtiny_parse_linear_gradient(gradient, grad, state);
+       if (dom_string_isequal(name, state->interned_linearGradient)) {
+               res = svgtiny_parse_linear_gradient(gradient, grad, state);
+       }
 
        dom_node_unref(gradient);
        dom_string_unref(name);
@@ -93,6 +94,8 @@ void svgtiny_find_gradient(const char *id,
        fprintf(stderr, "linear_gradient_stop_count %i\n",
                        grad->linear_gradient_stop_count);
        #endif
+
+       return res;
 }
 
 
@@ -114,10 +117,10 @@ svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear,
        exc = dom_element_get_attribute(linear, state->interned_href, &attr);
        if (exc == DOM_NO_ERR && attr != NULL) {
                if (dom_string_data(attr)[0] == (uint8_t) '#') {
-                       char *s = strndup(dom_string_data(attr) + 1,
-                                         dom_string_byte_length(attr) - 1);
-                       svgtiny_find_gradient(s, grad, state);
-                       free(s);
+                       svgtiny_find_gradient(dom_string_data(attr) + 1,
+                                             dom_string_byte_length(attr) - 1,
+                                             grad,
+                                             state);
                }
                dom_string_unref(attr);
        }
@@ -216,7 +219,8 @@ svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear,
                                                        state->interned_stop_color,
                                                        &attr);
                        if (exc == DOM_NO_ERR && attr != NULL) {
-                               svgtiny_parse_color(attr, &color, NULL, state);
+                               svgtiny_parse_color(dom_string_data(attr),
+                                   dom_string_byte_length(attr), &color);
                                dom_string_unref(attr);
                        }
                        exc = dom_element_get_attribute(stop,
@@ -237,10 +241,9 @@ svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear,
                                                &value);
                                        if (exc == DOM_NO_ERR &&
                                            value != NULL) {
-                                               svgtiny_parse_color(value,
-                                                                   &color,
-                                                                   NULL,
-                                                                   state);
+                                               svgtiny_parse_color(dom_string_data(value),
+                                                                   dom_string_byte_length(value),
+                                                                   &color);
                                                dom_string_unref(value);
                                        }
                                }
index f74d5ae7d62e3a42b422fe80d9213d323326f209..4e58002907658b175ccbad16f89262b71af8de52 100644 (file)
@@ -74,9 +74,6 @@ struct svgtiny_parse_state {
 struct svgtiny_list;
 
 /* svgtiny.c */
-void svgtiny_parse_color(dom_string *s, svgtiny_colour *c,
-               struct svgtiny_parse_state_gradient *grad,
-               struct svgtiny_parse_state *state);
 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);
@@ -95,9 +92,16 @@ svgtiny_code svgtiny_parse_length(const char *text, size_t textlen,
                int viewport_size, float *length);
 svgtiny_code svgtiny_parse_transform(const char *text, size_t textlen,
                struct svgtiny_transformation_matrix *tm);
+svgtiny_code svgtiny_parse_paint(const char *text, size_t textlen,
+               struct svgtiny_parse_state_gradient *grad,
+               struct svgtiny_parse_state *state,
+               svgtiny_colour *c);
+svgtiny_code svgtiny_parse_color(const char *text, size_t textlen,
+               svgtiny_colour *c);
 
 /* svgtiny_gradient.c */
-void svgtiny_find_gradient(const char *id,
+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,
index 491b32330d787c22cf7ae00b188227a3c6443f77..650a52d3b1e0123d4e2a5ef43e3d59a6c9ae411d 100644 (file)
@@ -13,6 +13,9 @@
 #include "svgtiny.h"
 #include "svgtiny_internal.h"
 
+/* Source file generated by `gperf`. */
+#include "autogenerated_colors.c"
+
 #define SIGNIFICAND_MAX 100000000
 #define EXPONENT_MAX 38
 #define EXPONENT_MIN -38
@@ -295,19 +298,56 @@ static inline void advance_whitespace(const char **cursor, const char *textend)
 
 
 /**
- * skip SVG spec defined comma and whitespace
+ * 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
+ *
+ * \param cursor current cursor
+ * \param textend end of buffer
+ */
+static inline void advance_hex(const char **cursor, const char *textend)
+{
+       while ((*cursor) < textend) {
+               if (((**cursor < 0x30 /* 0 */) || (**cursor > 0x39 /* 9 */)) &&
+                   ((**cursor < 0x41 /* A */) || (**cursor > 0x46 /* F */)) &&
+                   ((**cursor < 0x61 /* a */) || (**cursor > 0x66 /* f */))) {
+                       break;
+               }
+               (*cursor)++;
+       }
+}
+
+
+/**
+ * advance over SVG id
+ *
+ * \param cursor current cursor
+ * \param textend end of buffer
+ *
+ * https://www.w3.org/TR/SVG2/struct.html#IDAttribute
+ */
+static inline void advance_id(const char **cursor, const char *textend)
 {
        while((*cursor) < textend) {
-               if ((**cursor != 0x20) &&
-                   (**cursor != 0x09) &&
-                   (**cursor != 0x0A) &&
-                   (**cursor != 0x0D) &&
-                   (**cursor != 0x2C /* , */)) {
+               if ((**cursor == 0x20) ||
+                   (**cursor == 0x09) ||
+                   (**cursor == 0x0A) ||
+                   (**cursor == 0x0D) ||
+                   (**cursor == ')')) {
                        break;
                }
                (*cursor)++;
@@ -699,6 +739,7 @@ parse_transform_parameters(const char **cursor,
        return svgtiny_SVG_ERROR;
 }
 
+
 /**
  * Parse and apply a transform attribute.
  *
@@ -817,8 +858,350 @@ svgtiny_parse_transform(const char *text,
                        /* no comma or whitespace between transforms */
                        goto transform_parse_complete;
                }
-
        }
+
 transform_parse_complete:
        return svgtiny_OK;
 }
+
+
+/**
+ * convert ascii hex digit to an integer
+ */
+static inline unsigned int hexd_to_int(const char *digit)
+{
+       unsigned int value = *digit;
+
+       if ((*digit >= 0x30 /* 0 */) && (*digit <= 0x39 /* 9 */)) {
+               value -= 0x30;
+       } else if ((*digit >= 0x41 /* A */) && (*digit <= 0x5A /* Z */) ) {
+               value -= 0x37;
+       } else if (((*digit >= 0x61 /* a */) && (*digit <= 0x7A /* z */))) {
+               value -= 0x57;
+       }
+       return value;
+}
+
+
+/**
+ * convert two ascii hex digits to an integer
+ */
+static inline unsigned int hexdd_to_int(const char *digits)
+{
+       return (hexd_to_int(digits) << 4) | hexd_to_int(digits + 1);
+}
+
+
+/**
+ * parse a hex colour
+ * https://www.w3.org/TR/css-color-4/#typedef-hex-color
+ */
+static inline svgtiny_code
+parse_hex_color(const char **cursor, const char *textend, svgtiny_colour *c)
+{
+       unsigned int r, g, b, a=0xff;
+       const char *tokstart;
+
+       /* hex-color */
+       if ((**cursor != '#') || ((textend - (*cursor)) < 4)) {
+               return svgtiny_SVG_ERROR;
+       }
+
+       (*cursor)++;
+       tokstart = *cursor;
+
+       advance_hex(cursor, textend);
+
+       switch ((*cursor) - tokstart) {
+       case 3:
+               r = hexd_to_int(tokstart);
+               g = hexd_to_int(tokstart + 1);
+               b = hexd_to_int(tokstart + 2);
+               r |= r << 4;
+               g |= g << 4;
+               b |= b << 4;
+               break;
+       case 4:
+               r = hexd_to_int(tokstart);
+               g = hexd_to_int(tokstart + 1);
+               b = hexd_to_int(tokstart + 2);
+               a = hexd_to_int(tokstart + 3);
+               r |= r << 4;
+               g |= g << 4;
+               b |= b << 4;
+               break;
+       case 6:
+               r = hexdd_to_int(tokstart);
+               g = hexdd_to_int(tokstart + 2);
+               b = hexdd_to_int(tokstart + 4);
+               *c = svgtiny_RGB(r, g, b);
+               break;
+       case 8:
+               r = hexdd_to_int(tokstart);
+               g = hexdd_to_int(tokstart + 2);
+               b = hexdd_to_int(tokstart + 4);
+               a = hexdd_to_int(tokstart + 6);
+               break;
+       default:
+               /* unparsable hex color */
+               *cursor = tokstart - 1; /* put cursor back */
+               return svgtiny_SVG_ERROR;
+       }
+
+       /* \todo do something with the alpha */
+       UNUSED(a);
+
+       *c = svgtiny_RGB(r, g, b);
+       return svgtiny_OK;
+}
+
+
+/**
+ * parse a color function
+ *
+ * https://www.w3.org/TR/css-color-5/#typedef-color-function
+ *
+ * The only actual supported color function is rgb
+ */
+static inline svgtiny_code
+parse_color_function(const char **cursorout,
+                    const char *textend,
+                    svgtiny_colour *c)
+{
+       const char *cursor = *cursorout;
+       const char *argend = cursor;
+       svgtiny_code res;
+       float argf[4];
+       int idx; /* argument index */
+
+       if ((textend - cursor) < 10) {
+               /* must be at least ten characters to be a valid function */
+               return svgtiny_SVG_ERROR;
+       }
+
+       if (((cursor[0] != 'r') && (cursor[0] != 'R')) ||
+           ((cursor[1] != 'g') && (cursor[1] != 'G')) ||
+           ((cursor[2] != 'b') && (cursor[2] != 'B')) ||
+           (cursor[3] != '('))
+       {
+               /* only function currently supported is rgb */
+               return svgtiny_SVG_ERROR;
+       }
+       cursor+=4;
+
+       for (idx = 0; idx < 4; idx++) {
+               res = svgtiny_parse_number(cursor, textend - cursor, &argend, &argf[idx]);
+               if (res != svgtiny_OK) {
+                       break;
+               }
+               cursor = argend;
+               if (cursor >= textend) {
+                       /* no more input */
+                       break;
+               }
+               if (*cursor == '%') {
+                       /* percentage */
+                       argf[idx] = argf[idx] * 255 / 100;
+                       cursor++;
+               }
+               /* value must be clamped */
+               if (argf[idx] < 0) {
+                       argf[idx] = 0;
+               }
+               if (argf[idx] > 255) {
+                       argf[idx] = 255;
+               }
+               /* advance cursor to next argument */
+               advance_whitespace(&cursor, textend);
+               if (cursor >= textend) {
+                       /* no more input */
+                       break;
+               }
+               if (*cursor == ')') {
+                       /* close parenthesis, arguments are complete */
+                       cursor++;
+                       break;
+               }
+               if (*cursor == ',') {
+                       /* skip optional comma */
+                       cursor++;
+               }
+       }
+       if (idx < 2 || idx > 3) {
+               return svgtiny_SVG_ERROR;
+       }
+
+       *cursorout = cursor;
+       *c = svgtiny_RGB((unsigned int)argf[0],
+                        (unsigned int)argf[1],
+                        (unsigned int)argf[2]);
+       return svgtiny_OK;
+}
+
+
+/**
+ * parse a paint url
+ *
+ * /todo this does not cope with any url that is not a gradient paint server.
+ */
+static inline svgtiny_code
+parse_paint_url(const char **cursorout,
+               const char *textend,
+               struct svgtiny_parse_state_gradient *grad,
+               struct svgtiny_parse_state *state,
+               svgtiny_colour *c)
+{
+       const char *cursor = *cursorout;
+       const char *idstart = cursor;
+       const char *idend = cursor;
+       svgtiny_code res;
+
+       if (grad == NULL) {
+               return svgtiny_SVG_ERROR;
+       }
+
+       if ((textend - cursor) < 6) {
+               /* must be at least six characters to be a url */
+               return svgtiny_SVG_ERROR;
+       }
+
+       if (((cursor[0] != 'u') && (cursor[0] != 'U')) ||
+           ((cursor[1] != 'r') && (cursor[1] != 'R')) ||
+           ((cursor[2] != 'l') && (cursor[2] != 'L')) ||
+           (cursor[3] != '('))
+       {
+               /* only function currently supported is rgb */
+               return svgtiny_SVG_ERROR;
+       }
+       cursor += 4;
+       advance_whitespace(&cursor, textend);
+
+       if (*cursor != '#') {
+               /* not a paint server */
+               return svgtiny_SVG_ERROR;
+       }
+       cursor++;
+
+       idstart = cursor;
+       advance_id(&cursor, textend);
+       idend = cursor;
+
+       if ((idend - idstart) == 0) {
+               /* no id */
+               return svgtiny_SVG_ERROR;
+       }
+
+       advance_whitespace(&cursor, textend);
+
+       if ((cursor >= textend) || (*cursor != ')')) {
+               /* no close bracket on url */
+               return svgtiny_SVG_ERROR;
+       }
+       cursor++;
+
+       /* url syntax is correct update cursor */
+       *cursorout = cursor;
+
+       /* find and update gradient */
+       res = svgtiny_find_gradient(idstart, idend - idstart, grad, state);
+
+       if (res == svgtiny_OK) {
+               /* only set the color if the gradient was processed ok */
+               if (grad->linear_gradient_stop_count == 0) {
+                       *c = svgtiny_TRANSPARENT;
+               } else if (grad->linear_gradient_stop_count == 1) {
+                       *c = grad->gradient_stop[0].color;
+               } else {
+                       *c = svgtiny_LINEAR_GRADIENT;
+               }
+       }
+
+       return res;
+}
+
+
+/**
+ * Parse a paint.
+ *
+ * https://www.w3.org/TR/SVG11/painting.html#SpecifyingPaint
+ * https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint
+ *
+ */
+svgtiny_code
+svgtiny_parse_paint(const char *text,
+                   size_t textlen,
+                   struct svgtiny_parse_state_gradient *grad,
+                   struct svgtiny_parse_state *state,
+                   svgtiny_colour *c)
+{
+       const char *cursor = text; /* cursor */
+       const char *textend = text+textlen;
+       svgtiny_code res;
+
+       advance_whitespace(&cursor, textend);
+
+       if (((textend - cursor) == 4) &&
+           cursor[0] == 'n' &&
+           cursor[1] == 'o' &&
+           cursor[2] == 'n' &&
+           cursor[3] == 'e') {
+               *c = svgtiny_TRANSPARENT;
+               return svgtiny_OK;
+       }
+
+       /* attempt to parse element as a paint url */
+       res = parse_paint_url(&cursor, textend, grad, state, c);
+       if (res == svgtiny_OK) {
+               return res;
+       }
+
+       return svgtiny_parse_color(cursor, textend - cursor, c);
+}
+
+
+/**
+ * Parse a color.
+ *
+ * https://www.w3.org/TR/SVG11/types.html#DataTypeColor
+ * https://www.w3.org/TR/css-color-5/#typedef-color
+ *
+ * <color> = <color-base> | currentColor | <system-color>
+ * <color-base> = <hex-color> | <color-function> | <named-color> | transparent
+ * <color-function> = <rgb()> | <rgba()> | <hsl()> | <hsla()> | <hwb()> |
+ *                    <lab()> | <lch()> | <oklab()> | <oklch()> | <color()>
+ *
+ * \todo this does not cope with currentColor or transparent and supports only
+ *       the rgb color function.
+ */
+svgtiny_code
+svgtiny_parse_color(const char *text, size_t textlen, svgtiny_colour *c)
+{
+       const struct svgtiny_named_color *named_color;
+       const char *cursor = text; /* cursor */
+       const char *textend = text + textlen;
+       svgtiny_code res;
+
+       advance_whitespace(&cursor, textend);
+
+       /* attempt to parse element as a hex color */
+       res = parse_hex_color(&cursor, textend, c);
+       if (res == svgtiny_OK) {
+               return res;
+       }
+
+       /* attempt to parse element as a color function */
+       res = parse_color_function(&cursor, textend, c);
+       if (res == svgtiny_OK) {
+               return res;
+       }
+
+       named_color = svgtiny_color_lookup(text, textlen);
+       if (named_color) {
+               *c = named_color->color;
+               return svgtiny_OK;
+       }
+
+       /* did not parse as a color */
+       *c = svgtiny_RGB(0, 0, 0);
+       return svgtiny_SVG_ERROR;
+}
diff --git a/test/data/colors.mvg b/test/data/colors.mvg
new file mode 100644 (file)
index 0000000..f7bcc5c
--- /dev/null
@@ -0,0 +1,5 @@
+viewbox 0 0 1200 1200
+fill #012345 stroke #89abcd stroke-width 10 path 'M 100 100 L 1100 100 L 1100 200 L 100 200 Z ' 
+fill #eeffaa stroke #ccddee stroke-width 10 path 'M 100 200 L 1100 200 L 1100 300 L 100 300 Z ' 
+fill #ff6677 stroke #bb6677 stroke-width 10 path 'M 100 300 L 1100 300 L 1100 400 L 100 400 Z ' 
+fill #0080ff stroke #ff7f00 stroke-width 10 path 'M 100 400 L 1100 400 L 1100 500 L 100 500 Z ' 
diff --git a/test/data/colors.svg b/test/data/colors.svg
new file mode 100644 (file)
index 0000000..8ddbe07
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<svg width="1200" height="1200" viewPort="0 0 1200 1200" version="1.1" xmlns="http://www.w3.org/2000/svg">
+    <rect fill="#01234567"      stroke=" #89ABCD"         x="100" y="100" width="1000" height="100" stroke-width="10" />
+    <rect fill="#EFab "         stroke=" #cde "           x="100" y="200" width="1000" height="100" stroke-width="10" />
+    <rect fill="#f67 "          stroke="#bB6677 "         x="100" y="300" width="1000" height="100" stroke-width="10" />
+    <rect fill="rgb(0,128,255)" stroke="RGB(100%,50%,0%)" x="100" y="400" width="1000" height="100" stroke-width="10" />
+</svg>