]> gitweb.michael.orlitzky.com - libsvgtiny.git/commitdiff
Reimplement transform processing
authorVincent Sanders <vince@kyllikki.org>
Wed, 19 Jun 2024 23:07:45 +0000 (00:07 +0100)
committerVincent Sanders <vince@kyllikki.org>
Tue, 2 Jul 2024 23:12:33 +0000 (00:12 +0100)
remove use of sscanf and string duplication in transform processing

src/svgtiny.c
src/svgtiny_gradient.c
src/svgtiny_internal.h
src/svgtiny_parse.c
test/data/transform.mvg [new file with mode: 0644]
test/data/transform.svg [new file with mode: 0644]

index 61909a2d9b09827a2094b542730451dbd89e8705..d8d8bf0553a71f02acdf6ef1810d765e10db2fa6 100644 (file)
@@ -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);
        }
 }
 
index e7b122287f34b95a500ae9b6d5cbe7952222a5f7..db67e77122d01aabfc74ae5688b1e1d8b0ad3db6 100644 (file)
@@ -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);
         }
 
index 1f01f1f315e2ecec91ece9a5215261f8510ce279..f74d5ae7d62e3a42b422fe80d9213d323326f209 100644 (file)
@@ -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,
index 49cbf42946b185650582f59094be662429ce1ac3..491b32330d787c22cf7ae00b188227a3c6443f77 100644 (file)
@@ -8,6 +8,7 @@
 #include <stddef.h>
 #include <math.h>
 #include <float.h>
+#include <string.h>
 
 #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,
+                                          &paramv[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, &paramc, 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 (file)
index 0000000..eac33c1
--- /dev/null
@@ -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 (file)
index 0000000..da05c42
--- /dev/null
@@ -0,0 +1,56 @@
+<?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>