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);
}
}
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);
}
#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;
float viewport_height;
/* current transformation matrix */
- struct {
- float a, b, c, d, e, f;
- } ctm;
+ struct svgtiny_transformation_matrix ctm;
/*struct css_style style;*/
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);
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,
#include <stddef.h>
#include <math.h>
#include <float.h>
+#include <string.h>
#include "svgtiny.h"
#include "svgtiny_internal.h"
#define EXPONENT_MAX 38
#define EXPONENT_MIN -38
+
/**
* parse text string into a float
*
}
+/**
+ * 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
*
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;
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;
+}
--- /dev/null
+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 '
--- /dev/null
+<?xml version="1.0"?>
+<svg width="400" height="400" viewPort="0 0 400 400" version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+
+ <!-- transforms with a simple triangle -->
+
+
+ <g transform="translate(0,0,0)"> <!-- too many parameters -->
+ <g transform="translate(0,0"> <!-- missing close bracket -->
+ <g transform="translate 0,0)"> <!-- missing open bracket -->
+ <g transform="translata (0,0)"> <!-- invalid function -->
+ <g transform="rotate (0,0)"> <!-- invalid number of parameters -->
+ <polygon fill="red" stroke="blue" stroke-width="1" points="0 0 0 100 100 100"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+
+ <!-- simple translate -->
+ <g transform="translate(100,100)">
+ <polygon fill="red" stroke="blue" stroke-width="1" points="0 0 0 100 100 100"/>
+ </g>
+
+ <!-- series of transformations which should all cancel out -->
+ <g transform="translate(100,200) scale(2) rotate(45) translate(5,10)
+ translate ( -5,-10) rotate (-45 ) scale(0.5 0.5) translate(-100 -200)">
+ <polygon fill="red" stroke="blue" stroke-width="1" points="0 0 0 100 100 100"/>
+ </g>
+
+ <!-- series of transformations -->
+ <g transform="translate(100,200) scale(2) rotate(45) translate(-5,-10)">
+ <polygon fill="red" stroke="blue" stroke-width="1" points="0 0 0 100 100 100"/>
+ </g>
+
+ <!-- 90 rotation -->
+ <g transform="translate(100,100) rotate(90) ">
+ <polygon fill="red" stroke="blue" stroke-width="1" points="0 0 0 100 100 100"/>
+ </g>
+
+ <!-- 180 rotation about square center -->
+ <g transform="rotate(180,50,50)">
+ <polygon fill="red" stroke="blue" stroke-width="1" points="0 0 0 100 100 100"/>
+ </g>
+
+ <g transform="translate(100,0) rotate(-30)">
+ <polygon fill="blue" stroke="red" stroke-width="1" points="0 0 0 100 100 100"/>
+ </g>
+
+ <!-- rotation with skews -->
+ <g transform="translate(100,0) skewX(15.5) skewY(154) skewX(15.5)">
+ <polygon fill="red" stroke="blue" stroke-width="1" points="0 0 0 100 100 100"/>
+ </g>
+
+
+</svg>