From 3a5da485287c9ba8c5e8fb6bde0162fcc74dc476 Mon Sep 17 00:00:00 2001 From: Vincent Sanders Date: Thu, 20 Jun 2024 00:07:45 +0100 Subject: [PATCH] Reimplement transform processing remove use of sscanf and string duplication in transform processing --- src/svgtiny.c | 94 +-------- src/svgtiny_gradient.c | 30 ++- src/svgtiny_internal.h | 23 ++- src/svgtiny_parse.c | 425 ++++++++++++++++++++++++++++++++++++++-- test/data/transform.mvg | 9 + test/data/transform.svg | 56 ++++++ 6 files changed, 513 insertions(+), 124 deletions(-) create mode 100644 test/data/transform.mvg create mode 100644 test/data/transform.svg diff --git a/src/svgtiny.c b/src/svgtiny.c index 61909a2..d8d8bf0 100644 --- a/src/svgtiny.c +++ b/src/svgtiny.c @@ -2019,100 +2019,16 @@ void svgtiny_parse_font_attributes(dom_element *node, void svgtiny_parse_transform_attributes(dom_element *node, struct svgtiny_parse_state *state) { - char *transform; dom_string *attr; dom_exception exc; - exc = dom_element_get_attribute(node, state->interned_transform, - &attr); + exc = dom_element_get_attribute(node, state->interned_transform, &attr); if (exc == DOM_NO_ERR && attr != NULL) { - transform = strndup(dom_string_data(attr), - dom_string_byte_length(attr)); - svgtiny_parse_transform(transform, &state->ctm.a, &state->ctm.b, - &state->ctm.c, &state->ctm.d, - &state->ctm.e, &state->ctm.f); - free(transform); - dom_string_unref(attr); - } -} + svgtiny_parse_transform(dom_string_data(attr), + dom_string_byte_length(attr), + &state->ctm); - -/** - * Parse a transform string. - */ - -void svgtiny_parse_transform(char *s, float *ma, float *mb, - float *mc, float *md, float *me, float *mf) -{ - float a, b, c, d, e, f; - float za, zb, zc, zd, ze, zf; - float angle, x, y; - int n; - unsigned int i; - - for (i = 0; s[i]; i++) - if (s[i] == ',') - s[i] = ' '; - - while (*s) { - a = d = 1; - b = c = 0; - e = f = 0; - n = 0; - if ((sscanf(s, " matrix (%f %f %f %f %f %f ) %n", - &a, &b, &c, &d, &e, &f, &n) == 6) && (n > 0)) - ; - else if ((sscanf(s, " translate (%f %f ) %n", - &e, &f, &n) == 2) && (n > 0)) - ; - else if ((sscanf(s, " translate (%f ) %n", - &e, &n) == 1) && (n > 0)) - ; - else if ((sscanf(s, " scale (%f %f ) %n", - &a, &d, &n) == 2) && (n > 0)) - ; - else if ((sscanf(s, " scale (%f ) %n", - &a, &n) == 1) && (n > 0)) - d = a; - else if ((sscanf(s, " rotate (%f %f %f ) %n", - &angle, &x, &y, &n) == 3) && (n > 0)) { - angle = angle / 180 * M_PI; - a = cos(angle); - b = sin(angle); - c = -sin(angle); - d = cos(angle); - e = -x * cos(angle) + y * sin(angle) + x; - f = -x * sin(angle) - y * cos(angle) + y; - } else if ((sscanf(s, " rotate (%f ) %n", - &angle, &n) == 1) && (n > 0)) { - angle = angle / 180 * M_PI; - a = cos(angle); - b = sin(angle); - c = -sin(angle); - d = cos(angle); - } else if ((sscanf(s, " skewX (%f ) %n", - &angle, &n) == 1) && (n > 0)) { - angle = angle / 180 * M_PI; - c = tan(angle); - } else if ((sscanf(s, " skewY (%f ) %n", - &angle, &n) == 1) && (n > 0)) { - angle = angle / 180 * M_PI; - b = tan(angle); - } else - break; - za = *ma * a + *mc * b; - zb = *mb * a + *md * b; - zc = *ma * c + *mc * d; - zd = *mb * c + *md * d; - ze = *ma * e + *mc * f + *me; - zf = *mb * e + *md * f + *mf; - *ma = za; - *mb = zb; - *mc = zc; - *md = zd; - *me = ze; - *mf = zf; - s += n; + dom_string_unref(attr); } } diff --git a/src/svgtiny_gradient.c b/src/svgtiny_gradient.c index e7b1222..db67e77 100644 --- a/src/svgtiny_gradient.c +++ b/src/svgtiny_gradient.c @@ -163,25 +163,23 @@ svgtiny_code svgtiny_parse_linear_gradient(dom_element *linear, state->interned_gradientTransform, &attr); if (exc == DOM_NO_ERR && attr != NULL) { - float a = 1, b = 0, c = 0, d = 1, e = 0, f = 0; - char *s = strndup(dom_string_data(attr), - dom_string_byte_length(attr)); - if (s == NULL) { - dom_string_unref(attr); - return svgtiny_OUT_OF_MEMORY; - } - svgtiny_parse_transform(s, &a, &b, &c, &d, &e, &f); - free(s); + struct svgtiny_transformation_matrix tm = { + .a = 1, .b = 0, .c = 0, .d = 1, .e = 0, .f = 0 + }; + svgtiny_parse_transform(dom_string_data(attr), + dom_string_byte_length(attr), + &tm); + #ifdef GRADIENT_DEBUG fprintf(stderr, "transform %g %g %g %g %g %g\n", - a, b, c, d, e, f); + tm.a, tm.b, tm.c, tm.d, tm.e, tm.f); #endif - grad->gradient_transform.a = a; - grad->gradient_transform.b = b; - grad->gradient_transform.c = c; - grad->gradient_transform.d = d; - grad->gradient_transform.e = e; - grad->gradient_transform.f = f; + grad->gradient_transform.a = tm.a; + grad->gradient_transform.b = tm.b; + grad->gradient_transform.c = tm.c; + grad->gradient_transform.d = tm.d; + grad->gradient_transform.e = tm.e; + grad->gradient_transform.f = tm.f; dom_string_unref(attr); } diff --git a/src/svgtiny_internal.h b/src/svgtiny_internal.h index 1f01f1f..f74d5ae 100644 --- a/src/svgtiny_internal.h +++ b/src/svgtiny_internal.h @@ -24,16 +24,25 @@ struct svgtiny_gradient_stop { #define svgtiny_MAX_STOPS 10 #define svgtiny_LINEAR_GRADIENT 0x2000000 +/** + * svg transform matrix + * | a c e | + * | b d f | + * | 0 0 1 | + */ +struct svgtiny_transformation_matrix { + float a, b, c, d, e, f; +}; + struct svgtiny_parse_state_gradient { unsigned int linear_gradient_stop_count; dom_string *gradient_x1, *gradient_y1, *gradient_x2, *gradient_y2; struct svgtiny_gradient_stop gradient_stop[svgtiny_MAX_STOPS]; bool gradient_user_space_on_use; - struct { - float a, b, c, d, e, f; - } gradient_transform; + struct svgtiny_transformation_matrix gradient_transform; }; + struct svgtiny_parse_state { struct svgtiny_diagram *diagram; dom_document *document; @@ -42,9 +51,7 @@ struct svgtiny_parse_state { float viewport_height; /* current transformation matrix */ - struct { - float a, b, c, d, e, f; - } ctm; + struct svgtiny_transformation_matrix ctm; /*struct css_style style;*/ @@ -70,8 +77,6 @@ struct svgtiny_list; void svgtiny_parse_color(dom_string *s, svgtiny_colour *c, struct svgtiny_parse_state_gradient *grad, struct svgtiny_parse_state *state); -void svgtiny_parse_transform(char *s, float *ma, float *mb, - float *mc, float *md, float *me, float *mf); 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); @@ -88,6 +93,8 @@ svgtiny_code svgtiny_parse_poly_points(const char *data, size_t datalen, float *pointv, unsigned int *pointc); 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_gradient.c */ void svgtiny_find_gradient(const char *id, diff --git a/src/svgtiny_parse.c b/src/svgtiny_parse.c index 49cbf42..491b323 100644 --- a/src/svgtiny_parse.c +++ b/src/svgtiny_parse.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "svgtiny.h" #include "svgtiny_internal.h" @@ -16,6 +17,7 @@ #define EXPONENT_MAX 38 #define EXPONENT_MIN -38 + /** * parse text string into a float * @@ -272,6 +274,47 @@ svgtiny_parse_number_end: } +/** + * skip SVG spec defined whitespace + * + * \param cursor current cursor + * \param textend end of buffer + */ +static inline void advance_whitespace(const char **cursor, const char *textend) +{ + while((*cursor) < textend) { + if ((**cursor != 0x20) && + (**cursor != 0x09) && + (**cursor != 0x0A) && + (**cursor != 0x0D)) { + break; + } + (*cursor)++; + } +} + + +/** + * skip 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) +{ + while((*cursor) < textend) { + if ((**cursor != 0x20) && + (**cursor != 0x09) && + (**cursor != 0x0A) && + (**cursor != 0x0D) && + (**cursor != 0x2C /* , */)) { + break; + } + (*cursor)++; + } +} + + /** * parse text points into path points * @@ -328,17 +371,8 @@ svgtiny_parse_poly_points(const char *text, oddpoint=point; } - /* skip whitespace or comma */ - while(cursor < textend) { - if ((*cursor != 0x20) && - (*cursor != 0x09) && - (*cursor != 0x0A) && - (*cursor != 0x0D) && - (*cursor != 0x2C)) { - break; - } - cursor++; - } + /* advance cursor past whitespace (or comma) */ + advance_comma_whitespace(&cursor, textend); } return svgtiny_OK; @@ -419,3 +453,372 @@ svgtiny_parse_length(const char *text, return svgtiny_OK; } + + +enum transform_type { + TRANSFORM_UNK, + TRANSFORM_MATRIX, + TRANSFORM_TRANSLATE, + TRANSFORM_SCALE, + TRANSFORM_ROTATE, + TRANSFORM_SKEWX, + TRANSFORM_SKEWY, +}; + +static inline svgtiny_code +apply_transform(enum transform_type transform, + int paramc, + float *paramv, + struct svgtiny_transformation_matrix *tm) +{ + /* initialise matrix to cartesian standard basis + * | 1 0 0 | + * | 0 1 0 | + * | 0 0 1 | + */ + float a = 1, b = 0, c = 0, d = 1, e = 0, f = 0; /* parameter matrix */ + float za,zb,zc,zd,ze,zf; /* temporary matrix */ + float angle; + + /* there must be at least one parameter */ + if (paramc < 1) { + return svgtiny_SVG_ERROR; + } + + switch (transform) { + case TRANSFORM_MATRIX: + if (paramc != 6) { + /* too few parameters */ + return svgtiny_SVG_ERROR; + } + a=paramv[0]; + b=paramv[1]; + c=paramv[2]; + d=paramv[3]; + e=paramv[4]; + f=paramv[5]; + break; + + case TRANSFORM_TRANSLATE: + e = paramv[0]; + if (paramc == 2) { + f = paramv[1]; + } + break; + + case TRANSFORM_SCALE: + a = d = paramv[0]; + if (paramc == 2) { + d = paramv[1]; + } + break; + + case TRANSFORM_ROTATE: + angle = paramv[0] / 180 * M_PI; + a = cos(angle); + b = sin(angle); + c = -sin(angle); + d = cos(angle); + + if (paramc == 3) { + e = -paramv[1] * cos(angle) + + paramv[2] * sin(angle) + + paramv[1]; + f = -paramv[1] * sin(angle) - + paramv[2] * cos(angle) + + paramv[2]; + } else if (paramc == 2) { + /* one or three paramters only*/ + return svgtiny_SVG_ERROR; + } + + break; + + case TRANSFORM_SKEWX: + angle = paramv[0] / 180 * M_PI; + c = tan(angle); + break; + + case TRANSFORM_SKEWY: + angle = paramv[0] / 180 * M_PI; + b = tan(angle); + break; + + default: + /* unknown transform (not be possible to be here) */ + return svgtiny_SVG_ERROR; + } + + za = tm->a * a + tm->c * b; + zb = tm->b * a + tm->d * b; + zc = tm->a * c + tm->c * d; + zd = tm->b * c + tm->d * d; + ze = tm->a * e + tm->c * f + tm->e; + zf = tm->b * e + tm->d * f + tm->f; + + tm->a = za; + tm->b = zb; + tm->c = zc; + tm->d = zd; + tm->e = ze; + tm->f = zf; + + return svgtiny_OK; +} + + +/* determine transform function */ +static inline svgtiny_code +parse_transform_function(const char **cursor, + const char *textend, + enum transform_type *transformout) +{ + const char *tokstart; + size_t toklen; + enum transform_type transform = TRANSFORM_UNK; + + tokstart = *cursor; + while ((*cursor) < textend) { + if ((**cursor != 0x61 /* a */) && + (**cursor != 0x65 /* e */) && + (**cursor != 0x74 /* t */) && + (**cursor != 0x73 /* s */) && + (**cursor != 0x72 /* r */) && + (**cursor != 0x6B /* k */) && + (**cursor != 0x6C /* l */) && + (**cursor != 0x77 /* w */) && + (**cursor != 0x63 /* c */) && + (**cursor != 0x69 /* i */) && + (**cursor != 0x6D /* m */) && + (**cursor != 0x6E /* n */) && + (**cursor != 0x6F /* o */) && + (**cursor != 0x78 /* x */) && + (**cursor != 0x58 /* X */) && + (**cursor != 0x59 /* Y */)) { + break; + } + (*cursor)++; + } + toklen = (*cursor) - tokstart; + + if (toklen == 5) { + /* scale, skewX, skewY */ + if (strncmp("scale", tokstart, 5) == 0) { + transform = TRANSFORM_SCALE; + } else if (strncmp("skewX", tokstart, 5) == 0) { + transform = TRANSFORM_SKEWX; + } else if (strncmp("skewY", tokstart, 5) == 0) { + transform = TRANSFORM_SKEWY; + } + } else if (toklen == 6) { + /* matrix, rotate */ + if (strncmp("matrix", tokstart, 6) == 0) { + transform = TRANSFORM_MATRIX; + } else if (strncmp("rotate", tokstart, 6) == 0) { + transform = TRANSFORM_ROTATE; + } + } else if (toklen == 9) { + /* translate */ + if (strncmp("translate", tokstart, 9) == 0) { + transform = TRANSFORM_TRANSLATE; + } + } + if (transform == TRANSFORM_UNK) { + /* invalid transform */ + return svgtiny_SVG_ERROR; + } + + *transformout = transform; + return svgtiny_OK; +} + + +/** + * parse transform function parameters + * + * \param cursor current cursor + * \param textend end of buffer + * \param paramc max number of permitted parameters on input and number found on output + * \param paramv vector of float point numbers to put result in must have space for paramc entries + * \return svgtiny_OK and paramc and paramv updated or svgtiny_SVG_ERROR on error + */ +static inline svgtiny_code +parse_transform_parameters(const char **cursor, + const char *textend, + int *paramc, + float *paramv) +{ + int param_idx = 0; + int param_max; + const char *tokend; + svgtiny_code err; + + param_max = *paramc; + + for(param_idx = 0; param_idx < param_max; param_idx++) { + err = svgtiny_parse_number(*cursor, + textend - (*cursor), + &tokend, + ¶mv[param_idx]); + if (err != svgtiny_OK) { + /* failed to parse number */ + return err; + } + *cursor = tokend; + + /* advance cursor past optional whitespace */ + advance_whitespace(cursor, textend); + + if (*cursor >= textend) { + /* parameter list without close parenteses */ + return svgtiny_SVG_ERROR; + } + + /* close parentheses ends parameters */ + if (**cursor == 0x29 /* ) */) { + (*cursor)++; + *paramc = param_idx + 1; + return svgtiny_OK; + } + + /* comma can be skipped */ + if (**cursor == 0x2C /* , */) { + (*cursor)++; + if ((*cursor) >= textend) { + /* parameter list without close parenteses */ + return svgtiny_SVG_ERROR; + } + } + + if ((*cursor) == tokend) { + /* no comma or whitespace between parameters */ + return svgtiny_SVG_ERROR; + } + } + /* too many parameters for transform given */ + return svgtiny_SVG_ERROR; +} + +/** + * Parse and apply a transform attribute. + * + * https://www.w3.org/TR/SVG11/coords.html#TransformAttribute + * + * parse transforms into transform matrix + * | a c e | + * | b d f | + * | 0 0 1 | + * + * transforms to parse are: + * + * matrix(a b c d e f) + * | a c e | + * | b d f | + * | 0 0 1 | + * + * translate(e f) + * | 1 0 e | + * | 0 1 f | + * | 0 0 1 | + * + * translate(e) + * | 1 0 e | + * | 0 1 0 | + * | 0 0 1 | + * + * scale(a d) + * | a 0 0 | + * | 0 d 0 | + * | 0 0 1 | + * + * scale(a) + * | a 0 0 | + * | 0 1 0 | + * | 0 0 1 | + * + * rotate(ang x y) + * | cos(ang) -sin(ang) (-x * cos(ang) + y * sin(ang) + x) | + * | sin(ang) cos(ang) (-x * sin(ang) - y * cos(ang) + y) | + * | 0 0 1 | + * + * rotate(ang) + * | cos(ang) -sin(ang) 0 | + * | sin(ang) cos(ang) 0 | + * | 0 0 1 | + * + * skewX(ang) + * | 1 tan(ang) 0 | + * | 0 1 0 | + * | 0 0 1 | + * + * skewY(ang) + * | 1 0 0 | + * | tan(ang) 1 0 | + * | 0 0 1 | + * + * + */ +svgtiny_code +svgtiny_parse_transform(const char *text, + size_t textlen, + struct svgtiny_transformation_matrix *tm) +{ + const char *cursor = text; /* text cursor */ + const char *textend = text + textlen; + enum transform_type transform = TRANSFORM_UNK; + /* mapping of maimum number of parameters for each transform */ + const int param_max[]={0,6,2,2,3,1,1}; + const char *paramend; + int paramc; + float paramv[6]; + svgtiny_code err; + + /* advance cursor past optional whitespace */ + advance_whitespace(&cursor, textend); + + /* zero or more transform followed by whitespace or comma */ + while (cursor < textend) { + err = parse_transform_function(&cursor, textend, &transform); + if (err != svgtiny_OK) { + /* invalid transform */ + goto transform_parse_complete; + } + + /* advance cursor past optional whitespace */ + advance_whitespace(&cursor, textend); + + /* open parentheses */ + if (*cursor != 0x28 /* ( */) { + /* invalid syntax */ + goto transform_parse_complete; + } + cursor++; + + paramc=param_max[transform]; + err = parse_transform_parameters(&cursor, textend, ¶mc, paramv); + if (err != svgtiny_OK) { + /* invalid parameters */ + goto transform_parse_complete; + } + paramend = cursor; + + /* have transform type and at least one parameter */ + + /* apply transform */ + err = apply_transform(transform, paramc, paramv, tm); + if (err != svgtiny_OK) { + /* transform failed */ + goto transform_parse_complete; + } + + /* advance cursor past whitespace (or comma) */ + advance_comma_whitespace(&cursor, textend); + if (cursor == paramend) { + /* no comma or whitespace between transforms */ + goto transform_parse_complete; + } + + } +transform_parse_complete: + return svgtiny_OK; +} diff --git a/test/data/transform.mvg b/test/data/transform.mvg new file mode 100644 index 0000000..eac33c1 --- /dev/null +++ b/test/data/transform.mvg @@ -0,0 +1,9 @@ +viewbox 0 0 400 400 +fill #ff0000 stroke #0000ff stroke-width 1 path 'M 0 0 L 0 100 L 100 100 Z ' +fill #ff0000 stroke #0000ff stroke-width 1 path 'M 100 100 L 100 200 L 200 200 Z ' +fill #ff0000 stroke #0000ff stroke-width 1 path 'M 7.62939e-06 1.52588e-05 L 7.62939e-06 100 L 100 100 Z ' +fill #ff0000 stroke #0000ff stroke-width 1 path 'M 107.071 178.787 L -34.3503 320.208 L 107.071 461.63 Z ' +fill #ff0000 stroke #0000ff stroke-width 1 path 'M 100 100 L 0 100 L -7.62939e-06 200 Z ' +fill #ff0000 stroke #0000ff stroke-width -1 path 'M 100 100 L 100 7.62939e-06 L 0 0 Z ' +fill #0000ff stroke #ff0000 stroke-width 1 path 'M 100 0 L 150 86.6025 L 236.603 36.6025 Z ' +fill #ff0000 stroke #0000ff stroke-width 1 path 'M 100 0 L 151.714 86.474 L 238.188 37.7007 Z ' diff --git a/test/data/transform.svg b/test/data/transform.svg new file mode 100644 index 0000000..da05c42 --- /dev/null +++ b/test/data/transform.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- 2.49.0