From: Vincent Sanders Date: Mon, 1 Jul 2024 14:03:41 +0000 (+0100) Subject: Reimplement color parsing X-Git-Url: https://gitweb.michael.orlitzky.com/?a=commitdiff_plain;h=080cf9516cccb58d2e3cca427f93409109fd23d9;p=libsvgtiny.git Reimplement color parsing --- diff --git a/src/svgtiny.c b/src/svgtiny.c index d8d8bf0..dacf2c8 100644 --- a/src/svgtiny.c +++ b/src/svgtiny.c @@ -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 element node. * @@ -1609,7 +1607,6 @@ svgtiny_code svgtiny_parse_line(dom_element *line, } - /** * Parse a or 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. */ diff --git a/src/svgtiny_gradient.c b/src/svgtiny_gradient.c index db67e77..b6b2084 100644 --- a/src/svgtiny_gradient.c +++ b/src/svgtiny_gradient.c @@ -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); } } diff --git a/src/svgtiny_internal.h b/src/svgtiny_internal.h index f74d5ae..4e58002 100644 --- a/src/svgtiny_internal.h +++ b/src/svgtiny_internal.h @@ -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, diff --git a/src/svgtiny_parse.c b/src/svgtiny_parse.c index 491b323..650a52d 100644 --- a/src/svgtiny_parse.c +++ b/src/svgtiny_parse.c @@ -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 + * + * = | currentColor | + * = | | | transparent + * = | | | | | + * | | | | + * + * \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 index 0000000..f7bcc5c --- /dev/null +++ b/test/data/colors.mvg @@ -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 index 0000000..8ddbe07 --- /dev/null +++ b/test/data/colors.svg @@ -0,0 +1,7 @@ + + + + + + +