[gtk/matthiasc/lottie2: 2/5] path: Implement gsk_path_new_from_string




commit bfe4bdb1e46d7a392f72fdbcb39443205061a912
Author: Matthias Clasen <mclasen redhat com>
Date:   Tue Nov 24 20:01:07 2020 -0500

    path: Implement gsk_path_new_from_string
    
    Implement the SVG path syntax to read back the strings
    that we generate when serializing paths. The tests for
    this code are taken from librsvg.

 gsk/gskpath.c             | 497 ++++++++++++++++++++++++++++++++++++++++++++++
 gsk/gskpath.h             |   4 +
 gsk/gskrendernodeparser.c |  64 ++++--
 testsuite/gsk/path.c      | 246 +++++++++++++++++++++++
 4 files changed, 797 insertions(+), 14 deletions(-)
---
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index 5aaae65c7e..e06e8e80cd 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -2081,3 +2081,500 @@ gsk_path_builder_arc_to (GskPathBuilder *builder,
                    t);
     }
 }
+
+static void
+skip_whitespace (const char **p)
+{
+  while (g_ascii_isspace (**p))
+    (*p)++;
+}
+
+static void
+skip_optional_comma (const char **p)
+{
+  skip_whitespace (p);
+  if (**p == ',')
+    (*p)++;
+}
+
+static gboolean
+parse_number (const char **p,
+              double      *c)
+{
+  char *e;
+  *c = g_ascii_strtod (*p, &e);
+  if (e == *p)
+    return FALSE;
+  *p = e;
+  skip_optional_comma (p);
+  return TRUE;
+}
+
+static gboolean
+parse_coordinate (const char **p,
+                  double      *c)
+{
+  return parse_number (p, c);
+}
+
+static gboolean
+parse_coordinate_pair (const char **p,
+                       double      *x,
+                       double      *y)
+{
+  double xx, yy;
+  const char *o = *p;
+
+  if (!parse_coordinate (p, &xx))
+    {
+      *p = o;
+      return FALSE;
+    }
+  if (!parse_coordinate (p, &yy))
+    {
+      *p = o;
+      return FALSE;
+    }
+
+  *x = xx;
+  *y = yy;
+
+  return TRUE;
+}
+
+static gboolean
+parse_nonnegative_number (const char **p,
+                          double      *x)
+{
+  const char *o = *p;
+  double n;
+
+  if (!parse_number (p, &n))
+    return FALSE;
+
+  if (n < 0)
+    {
+      *p = o;
+      return FALSE;
+    }
+
+  *x = n;
+
+  return TRUE;
+}
+
+static gboolean
+parse_flag (const char **p,
+            gboolean    *f)
+{
+  skip_whitespace (p);
+  if (strchr ("01", **p))
+    {
+      *f = **p == '1';
+      (*p)++;
+      skip_optional_comma (p);
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static gboolean
+parse_command (const char **p,
+               char        *cmd)
+{
+  char *s;
+  const char *allowed;
+
+  if (*cmd == 'X')
+    allowed = "mM";
+  else
+    allowed = "mMhHvVzZlLcCsStTqQaA";
+
+  skip_whitespace (p);
+  s = strchr (allowed, **p);
+  if (s)
+    {
+      *cmd = *s;
+      (*p)++;
+      return TRUE;
+    }
+  return FALSE;
+}
+
+/**
+ * gsk_path_new_from_string:
+ * @path: a string
+ *
+ * This is a convenience function that constructs a #GskPath
+ * from a serialized form. The string is expected to be in
+ * [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData),
+ * as e.g. produced by gsk_path_to_string().
+ *
+ * Returns: a new #GskPath
+ **/
+GskPath *
+gsk_path_new_from_string (const char *s)
+{
+  GskPathBuilder *builder;
+  double x, y;
+  double prev_x1, prev_y1;
+  double path_x, path_y;
+  const char *p;
+  char cmd;
+  char prev_cmd;
+  gboolean after_comma;
+  gboolean repeat;
+
+  builder = gsk_path_builder_new ();
+
+  cmd = 'X';
+  path_x = path_y = 0;
+  x = y = 0;
+  prev_x1 = prev_y1 = 0;
+  after_comma = FALSE;
+
+  p = s;
+  while (*p)
+    {
+      prev_cmd = cmd;
+      repeat = !parse_command (&p, &cmd);
+
+      if (after_comma && !repeat)
+        goto error;
+
+      switch (cmd)
+        {
+        case 'X':
+          goto error;
+
+        case 'Z':
+        case 'z':
+          if (repeat)
+            goto error;
+          else
+            {
+              gsk_path_builder_close (builder);
+              x = path_x;
+              y = path_y;
+            }
+          break;
+
+        case 'M':
+        case 'm':
+          {
+            double x1, y1;
+
+            if (parse_coordinate_pair (&p, &x1, &y1))
+              {
+                if (cmd == 'm')
+                  {
+                    x1 += x;
+                    y1 += y;
+                  }
+                if (repeat)
+                  gsk_path_builder_line_to (builder, x1, y1);
+                else
+                  {
+                    gsk_path_builder_move_to (builder, x1, y1);
+                    if (strchr ("zZX", prev_cmd))
+                      {
+                        path_x = x1;
+                        path_y = y1;
+                      }
+                  }
+                x = x1;
+                y = y1;
+              }
+            else
+              goto error;
+          }
+          break;
+
+        case 'L':
+        case 'l':
+          {
+            double x1, y1;
+
+            if (parse_coordinate_pair (&p, &x1, &y1))
+              {
+                if (cmd == 'l')
+                  {
+                    x1 += x;
+                    y1 += y;
+                  }
+
+                if (strchr ("zZ", prev_cmd))
+                  {
+                    gsk_path_builder_move_to (builder, x, y);
+                    path_x = x;
+                    path_y = y;
+                  }
+                gsk_path_builder_line_to (builder, x1, y1);
+                x = x1;
+                y = y1;
+              }
+            else
+              goto error;
+          }
+          break;
+
+        case 'H':
+        case 'h':
+          {
+            double x1;
+
+            if (parse_coordinate (&p, &x1))
+              {
+                if (cmd == 'h')
+                  x1 += x;
+                if (strchr ("zZ", prev_cmd))
+                  {
+                    gsk_path_builder_move_to (builder, x, y);
+                    path_x = x;
+                    path_y = y;
+                  }
+                gsk_path_builder_line_to (builder, x1, y);
+                x = x1;
+              }
+            else
+              goto error;
+          }
+          break;
+
+        case 'V':
+        case 'v':
+          {
+            double y1;
+
+            if (parse_coordinate (&p, &y1))
+              {
+                if (cmd == 'v')
+                  y1 += y;
+                if (strchr ("zZ", prev_cmd))
+                  {
+                    gsk_path_builder_move_to (builder, x, y);
+                    path_x = x;
+                    path_y = y;
+                  }
+                gsk_path_builder_line_to (builder, x, y1);
+                y = y1;
+              }
+            else
+              goto error;
+          }
+          break;
+
+        case 'C':
+        case 'c':
+          {
+            double x0, y0, x1, y1, x2, y2;
+
+            if (parse_coordinate_pair (&p, &x0, &y0) &&
+                parse_coordinate_pair (&p, &x1, &y1) &&
+                parse_coordinate_pair (&p, &x2, &y2))
+              {
+                if (cmd == 'c')
+                  {
+                    x0 += x;
+                    y0 += y;
+                    x1 += x;
+                    y1 += y;
+                    x2 += x;
+                    y2 += y;
+                  }
+                if (strchr ("zZ", prev_cmd))
+                  {
+                    gsk_path_builder_move_to (builder, x, y);
+                    path_x = x;
+                    path_y = y;
+                  }
+                gsk_path_builder_curve_to (builder, x0, y0, x1, y1, x2, y2);
+                prev_x1 = x1;
+                prev_y1 = y1;
+                x = x2;
+                y = y2;
+              }
+            else
+              goto error;
+          }
+          break;
+
+        case 'S':
+        case 's':
+          {
+            double x0, y0, x1, y1, x2, y2;
+
+            if (parse_coordinate_pair (&p, &x1, &y1) &&
+                parse_coordinate_pair (&p, &x2, &y2))
+              {
+                if (cmd == 's')
+                  {
+                    x1 += x;
+                    y1 += y;
+                    x2 += x;
+                    y2 += y;
+                  }
+                if (strchr ("CcSs", prev_cmd))
+                  {
+                    x0 = 2 * x - prev_x1;
+                    y0 = 2 * y - prev_y1;
+                  }
+                else
+                  {
+                    x0 = x;
+                    y0 = y;
+                  }
+                if (strchr ("zZ", prev_cmd))
+                  {
+                    gsk_path_builder_move_to (builder, x, y);
+                    path_x = x;
+                    path_y = y;
+                  }
+                gsk_path_builder_curve_to (builder, x0, y0, x1, y1, x2, y2);
+                prev_x1 = x1;
+                prev_y1 = y1;
+                x = x2;
+                y = y2;
+              }
+            else
+              goto error;
+          }
+          break;
+
+        case 'Q':
+        case 'q':
+          {
+            double x1, y1, x2, y2, xx1, yy1, xx2, yy2;
+
+            if (parse_coordinate_pair (&p, &x1, &y1) &&
+                parse_coordinate_pair (&p, &x2, &y2))
+              {
+                if (cmd == 'q')
+                  {
+                    x1 += x;
+                    y1 += y;
+                    x2 += x;
+                    y2 += y;
+                  }
+                xx1 = (x + 2.0 * x1) / 3.0;
+                yy1 = (y + 2.0 * y1) / 3.0;
+                xx2 = (x2 + 2.0 * x1) / 3.0;
+                yy2 = (y2 + 2.0 * y1) / 3.0;
+                if (strchr ("zZ", prev_cmd))
+                  {
+                    gsk_path_builder_move_to (builder, x, y);
+                    path_x = x;
+                    path_y = y;
+                  }
+                gsk_path_builder_curve_to (builder, xx1, yy1, xx2, yy2, x2, y2);
+                prev_x1 = x1;
+                prev_y1 = y1;
+                x = x2;
+                y = y2;
+              }
+            else
+              goto error;
+          }
+          break;
+
+        case 'T':
+        case 't':
+          {
+            double x1, y1, x2, y2, xx1, yy1, xx2, yy2;
+
+            if (parse_coordinate_pair (&p, &x2, &y2))
+              {
+                if (cmd == 't')
+                  {
+                    x2 += x;
+                    y2 += y;
+                  }
+                if (strchr ("QqTt", prev_cmd))
+                  {
+                    x1 = 2 * x - prev_x1;
+                    y1 = 2 * y - prev_y1;
+                  }
+                else
+                  {
+                    x1 = x;
+                    y1 = y;
+                  }
+                xx1 = (x + 2.0 * x1) / 3.0;
+                yy1 = (y + 2.0 * y1) / 3.0;
+                xx2 = (x2 + 2.0 * x1) / 3.0;
+                yy2 = (y2 + 2.0 * y1) / 3.0;
+                if (strchr ("zZ", prev_cmd))
+                  {
+                    gsk_path_builder_move_to (builder, x, y);
+                    path_x = x;
+                    path_y = y;
+                  }
+                gsk_path_builder_curve_to (builder, xx1, yy1, xx2, yy2, x2, y2);
+                prev_x1 = x1;
+                prev_y1 = y1;
+                x = x2;
+                y = y2;
+              }
+            else
+              goto error;
+          }
+          break;
+
+        case 'A':
+        case 'a':
+          {
+            double rx, ry;
+            double x_axis_rotation;
+            int large_arc, sweep;
+            double x1, y1;
+
+            if (parse_nonnegative_number (&p, &rx) &&
+                parse_nonnegative_number (&p, &ry) &&
+                parse_number (&p, &x_axis_rotation) &&
+                parse_flag (&p, &large_arc) &&
+                parse_flag (&p, &sweep) &&
+                parse_coordinate_pair (&p, &x1, &y1))
+              {
+                if (cmd == 'a')
+                  {
+                    x1 += x;
+                    y1 += y;
+                  }
+
+                if (strchr ("zZ", prev_cmd))
+                  {
+                    gsk_path_builder_move_to (builder, x, y);
+                    path_x = x;
+                    path_y = y;
+                  }
+                gsk_path_builder_arc_to (builder,
+                                         rx, ry, x_axis_rotation,
+                                         large_arc, sweep,
+                                         x1, y1);
+                x = x1;
+                y = y1;
+              }
+            else
+              goto error;
+          }
+          break;
+
+        default:
+          goto error;
+        }
+
+      after_comma = (p > s) && p[-1] == ',';
+    }
+
+  if (after_comma)
+    goto error;
+
+  return gsk_path_builder_free_to_path (builder);
+
+error:
+  //g_warning ("Can't parse string '%s' as GskPath, error at %ld", s, p - s);
+  gsk_path_builder_unref (builder);
+
+  return NULL;
+}
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index d2dd03c927..6ee3e918bd 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -59,6 +59,9 @@ GskPath *               gsk_path_new_rect                       (float
 GDK_AVAILABLE_IN_ALL
 GskPath *               gsk_path_new_from_cairo                 (const cairo_path_t   *path);
 
+GDK_AVAILABLE_IN_ALL
+GskPath *               gsk_path_new_from_string                (const char *string);
+
 GDK_AVAILABLE_IN_ALL
 GskPath *               gsk_path_ref                            (GskPath              *self);
 GDK_AVAILABLE_IN_ALL
@@ -84,6 +87,7 @@ gboolean                gsk_path_foreach                        (GskPath
                                                                  GskPathForeachFunc    func,
                                                                  gpointer              user_data);
 
+
 #define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ())
 
 typedef struct _GskPathBuilder GskPathBuilder;
diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c
index 613b503bcc..069b6308b4 100644
--- a/gsk/gskrendernodeparser.c
+++ b/gsk/gskrendernodeparser.c
@@ -1732,7 +1732,22 @@ static gboolean
 parse_path (GtkCssParser *parser,
             gpointer      out_path)
 {
-  return FALSE;
+  char *str = NULL;
+
+  if (!parse_string (parser, &str))
+    return FALSE;
+
+  *((GskPath **) out_path) = gsk_path_new_from_string (str);
+
+  g_free (str);
+
+  return TRUE;
+}
+
+static void
+clear_path (gpointer inout_path)
+{
+  g_clear_pointer ((GskPath **) inout_path, gsk_path_unref);
 }
 
 static gboolean
@@ -1789,7 +1804,7 @@ parse_fill_node (GtkCssParser *parser)
   GskFillRule rule = GSK_FILL_RULE_WINDING;
   const Declaration declarations[] = {
     { "child", parse_node, clear_node, &child },
-    { "path", parse_path, NULL, &path },
+    { "path", parse_path, clear_path, &path },
     { "fill-rule", parse_fill_rule, NULL, &rule },
   };
   GskRenderNode *result;
@@ -1800,6 +1815,8 @@ parse_fill_node (GtkCssParser *parser)
 
   result = gsk_fill_node_new (child, path, rule);
 
+  gsk_path_unref (path);
+
   gsk_render_node_unref (child);
 
   return result;
@@ -1817,7 +1834,7 @@ parse_stroke_node (GtkCssParser *parser)
 
   const Declaration declarations[] = {
     { "child", parse_node, clear_node, &child },
-    { "path", parse_path, NULL, &path },
+    { "path", parse_path, clear_path, &path },
     { "line-width", parse_double, NULL, &line_width },
     { "line-cap", parse_line_cap, NULL, &line_cap },
     { "line-join", parse_line_join, NULL, &line_join },
@@ -1834,6 +1851,7 @@ parse_stroke_node (GtkCssParser *parser)
 
   result = gsk_stroke_node_new (child, path, stroke);
 
+  gsk_path_unref (path);
   gsk_stroke_free (stroke);
   gsk_render_node_unref (child);
 
@@ -2327,8 +2345,11 @@ append_escaping_newlines (GString    *str,
     len = strcspn (string, "\n");
     g_string_append_len (str, string, len);
     string += len;
-    g_string_append (str, "\\\n");
-    string++;
+    if (*string)
+      {
+        g_string_append (str, "\\\n");
+        string++;
+      }
   } while (*string);
 }
 
@@ -2389,6 +2410,28 @@ append_enum_param (Printer    *p,
   g_string_append_c (p->str, '\n');
 }
 
+static void
+append_path_param (Printer    *p,
+                   const char *param_name,
+                   GskPath    *path)
+{
+  char *str, *s;
+
+  _indent (p);
+  g_string_append (p->str, "path: \"\\\n");
+  str = gsk_path_to_string (path);
+  /* Put each command on a new line */
+  for (s = str; *s; s++)
+    {
+      if (*s == ' ' &&
+          (s[1] == 'M' || s[1] == 'C' || s[1] == 'Z' || s[1] == 'L'))
+        *s = '\n';
+    }
+  append_escaping_newlines (p->str, str);
+  g_string_append (p->str, "\";\n");
+  g_free (str);
+}
+
 static void
 render_node_print (Printer       *p,
                    GskRenderNode *node)
@@ -2557,14 +2600,10 @@ render_node_print (Printer       *p,
 
     case GSK_FILL_NODE:
       {
-        char *path_str;
-
         start_node (p, "fill");
 
         append_node_param (p, "child", gsk_fill_node_get_child (node));
-        path_str = gsk_path_to_string (gsk_fill_node_get_path (node));
-        append_string_param (p, "path", path_str);
-        g_free (path_str);
+        append_path_param (p, "path", gsk_fill_node_get_path (node));
         append_enum_param (p, "fill-rule", GSK_TYPE_FILL_RULE, gsk_fill_node_get_fill_rule (node));
 
         end_node (p);
@@ -2573,15 +2612,12 @@ render_node_print (Printer       *p,
 
     case GSK_STROKE_NODE:
       {
-        char *path_str;
         const GskStroke *stroke;
 
         start_node (p, "stroke");
 
         append_node_param (p, "child", gsk_stroke_node_get_child (node));
-        path_str = gsk_path_to_string (gsk_stroke_node_get_path (node));
-        append_string_param (p, "path", path_str);
-        g_free (path_str);
+        append_path_param (p, "path", gsk_stroke_node_get_path (node));
 
         stroke = gsk_stroke_node_get_stroke (node);
         append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0);
diff --git a/testsuite/gsk/path.c b/testsuite/gsk/path.c
index fc2397e98d..f79d3b4d33 100644
--- a/testsuite/gsk/path.c
+++ b/testsuite/gsk/path.c
@@ -117,6 +117,251 @@ test_create (void)
     }
 }
 
+/* testcases from path_parser.rs in librsvg */
+static void
+test_from_string (void)
+{
+  struct {
+    const char *in;
+    const char *out;
+  } tests[] = {
+    { "", "" },
+    // numbers
+    { "M 10 20", "M 10 20" },
+    { "M -10 -20", "M -10 -20" },
+    { "M .10 0.20", "M 0.1 0.2" },
+    { "M -.10 -0.20", "M -0.1 -0.2" },
+    { "M-.10-0.20", "M -0.1 -0.2" },
+    { "M10.5.50", "M 10.5 0.5" },
+    { "M.10.20", "M 0.1 0.2" },
+    { "M .10E1 .20e-4", "M 1 2e-05" },
+    { "M-.10E1-.20", "M -1 -0.2" },
+    { "M10.10E2 -0.20e3", "M 1010 -200" },
+    { "M-10.10E2-0.20e-3", "M -1010 -0.0002" },
+    { "M1e2.5", "M 100 0.5" },
+    { "M1e-2.5", "M 0.01 0.5" },
+    { "M1e+2.5", "M 100 0.5" },
+    // bogus numbers
+    { "M+", NULL },
+    { "M-", NULL },
+    { "M+x", NULL },
+    { "M10e", NULL },
+    { "M10ex", NULL },
+    { "M10e-", NULL },
+    { "M10e+x", NULL },
+    // numbers with comma
+    { "M 10, 20", "M 10 20" },
+    { "M -10,-20", "M -10 -20" },
+    { "M.10    ,     0.20", "M 0.1 0.2" },
+    { "M -.10, -0.20   ", "M -0.1 -0.2" },
+    { "M-.10-0.20", "M -0.1 -0.2" },
+    { "M.10.20", "M 0.1 0.2" },
+    { "M .10E1,.20e-4", "M 1 2e-05" },
+    { "M-.10E-2,-.20", "M -0.001 -0.2" },
+    { "M10.10E2,-0.20e3", "M 1010 -200" },
+    { "M-10.10E2,-0.20e-3", "M -1010 -0.0002" },
+    // single moveto
+    { "M 10 20 ", "M 10 20" },
+    { "M10,20  ", "M 10 20" },
+    { "M10 20   ", "M 10 20" },
+    { "    M10,20     ", "M 10 20" },
+    // relative moveto
+    { "m10 20", "M 10 20" },
+    // absolute moveto with implicit lineto
+    { "M10 20 30 40", "M 10 20 L 30 40" },
+    { "M10,20,30,40", "M 10 20 L 30 40" },
+    { "M.1-2,3E2-4", "M 0.1 -2 L 300 -4" },
+    // relative moveto with implicit lineto
+    { "m10 20 30 40", "M 10 20 L 40 60" },
+    // relative moveto with relative lineto sequence
+    { "m 46,447 l 0,0.5 -1,0 -1,0 0,1 0,12",
+      "M 46 447 L 46 447.5 L 45 447.5 L 44 447.5 L 44 448.5 L 44 460.5" },
+    // absolute moveto with implicit linetos
+    { "M10,20 30,40,50 60", "M 10 20 L 30 40 L 50 60" },
+    // relative moveto with implicit linetos
+    { "m10 20 30 40 50 60", "M 10 20 L 40 60 L 90 120" },
+    // absolute moveto moveto
+    { "M10 20 M 30 40", "M 10 20 M 30 40" },
+    // relative moveto moveto
+    { "m10 20 m 30 40", "M 10 20 M 40 60" },
+    // relative moveto lineto moveto
+    { "m10 20 30 40 m 50 60", "M 10 20 L 40 60 M 90 120" },
+    // absolute moveto lineto
+    { "M10 20 L30,40", "M 10 20 L 30 40" },
+    // relative moveto lineto
+    { "m10 20 l30,40", "M 10 20 L 40 60" },
+    // relative moveto lineto lineto abs lineto
+    { "m10 20 30 40l30,40,50 60L200,300",
+      "M 10 20 L 40 60 L 70 100 L 120 160 L 200 300" },
+    // horizontal lineto
+    { "M10 20 H30", "M 10 20 L 30 20" },
+    { "M 10 20 H 30 40",  "M 10 20 L 30 20 L 40 20" },
+    { "M10 20 H30,40-50", "M 10 20 L 30 20 L 40 20 L -50 20" },
+    { "m10 20 h30,40-50", "M 10 20 L 40 20 L 80 20 L 30 20" },
+    // vertical lineto
+    { "M10 20 V30", "M 10 20 L 10 30" },
+    { "M10 20 V30 40", "M 10 20 L 10 30 L 10 40" },
+    { "M10 20 V30,40-50", "M 10 20 L 10 30 L 10 40 L 10 -50" },
+    { "m10 20 v30,40-50", "M 10 20 L 10 50 L 10 90 L 10 40" },
+    // curveto
+    { "M10 20 C 30,40 50 60-70,80", "M 10 20 C 30 40, 50 60, -70 80" },
+    { "M10 20 C 30,40 50 60-70,80,90 100,110 120,130,140",
+      "M 10 20 C 30 40, 50 60, -70 80 C 90 100, 110 120, 130 140" },
+    { "m10 20 c 30,40 50 60-70,80,90 100,110 120,130,140",
+      "M 10 20 C 40 60, 60 80, -60 100 C 30 200, 50 220, 70 240" },
+    { "m10 20 c 30,40 50 60-70,80 90 100,110 120,130,140",
+      "M 10 20 C 40 60, 60 80, -60 100 C 30 200, 50 220, 70 240" },
+    // smooth curveto
+    { "M10 20 S 30,40-50,60", "M 10 20 C 10 20, 30 40, -50 60" },
+    { "M10 20 S 30,40 50 60-70,80,90 100",
+      "M 10 20 C 10 20, 30 40, 50 60 C 70 80, -70 80, 90 100" },
+    // quadratic curveto
+    { "M10 20 Q30 40 50 60", "M 10 20 C 23.3333 33.3333, 36.6667 46.6667, 50 60" },
+    { "M10 20 Q30 40 50 60,70,80-90 100",
+      "M 10 20 C 23.3333 33.3333, 36.6667 46.6667, 50 60 C 63.3333 73.3333, 16.6667 86.6667, -90 100" },
+    { "m10 20 q 30,40 50 60-70,80 90 100",
+      "M 10 20 C 30 46.6667, 46.6667 66.6667, 60 80 C 13.3333 133.333, 43.3333 166.667, 150 180" },
+    // smooth quadratic curveto
+    { "M10 20 T30 40", "M 10 20 C 10 20, 16.6667 26.6667, 30 40" },
+    { "M10 20 Q30 40 50 60 T70 80",
+      "M 10 20 C 23.3333 33.3333, 36.6667 46.6667, 50 60 C 63.3333 73.3333, 70 80, 70 80" },
+    { "m10 20 q 30,40 50 60t-70,80",
+      "M 10 20 C 30 46.6667, 46.6667 66.6667, 60 80 C 73.3333 93.3333, 50 120, -10 160" },
+    // elliptical arc. Exact numbers depend on too much math, so just verify
+    // that these parse successfully
+    { "M 1 3 A 1 2 3 00 6 7", "path" },
+    { "M 1 2 A 1 2 3 016 7", "path" },
+    { "M 1 2 A 1 2 3 10,6 7", "path" },
+    { "M 1 2 A 1 2 3 1,1 6 7", "path" },
+    { "M 1 2 A 1 2 3 1 1 6 7", "path" },
+    { "M 1 2 A 1 2 3 1 16 7", "path" },
+    // close path
+    { "M10 20 Z", "M 10 20 Z" },
+    { "m10 20 30 40 m 50 60 70 80 90 100z", "M 10 20 L 40 60 M 90 120 L 160 200 L 250 300 Z" },
+    // must start with moveto
+    { " L10 20", NULL },
+    // moveto args
+    { "M", NULL },
+    { "M,", NULL },
+    { "M10", NULL },
+    { "M10,", NULL },
+    { "M10x", NULL },
+    { "M10,x", NULL },
+    { "M10-20,", NULL },
+    { "M10-20-30", NULL },
+    { "M10-20-30 x", NULL },
+    // closepath args
+    { "M10-20z10", NULL },
+    { "M10-20z,", NULL },
+    // lineto args
+    { "M10-20L10", NULL },
+    { "M 10,10 L 20,20,30", NULL },
+    { "M 10,10 L 20,20,", NULL },
+    // horizontal lineto args
+    { "M10-20H", NULL },
+    { "M10-20H,", NULL },
+    { "M10-20H30,", NULL },
+    // vertical lineto args
+    { "M10-20v", NULL },
+    { "M10-20v,", NULL },
+    { "M10-20v30,", NULL },
+    // curveto args
+    { "M10-20C1", NULL },
+    { "M10-20C1,", NULL },
+    { "M10-20C1 2", NULL },
+    { "M10-20C1,2,", NULL },
+    { "M10-20C1 2 3", NULL },
+    { "M10-20C1,2,3", NULL },
+    { "M10-20C1,2,3,", NULL },
+    { "M10-20C1 2 3 4", NULL },
+    { "M10-20C1,2,3,4", NULL },
+    { "M10-20C1,2,3,4,", NULL },
+    { "M10-20C1 2 3 4 5", NULL },
+    { "M10-20C1,2,3,4,5", NULL },
+    { "M10-20C1,2,3,4,5,", NULL },
+    { "M10-20C1,2,3,4,5,6,", NULL },
+    // smooth curveto args
+    { "M10-20S1", NULL },
+    { "M10-20S1,", NULL },
+    { "M10-20S1 2", NULL },
+    { "M10-20S1,2,", NULL },
+    { "M10-20S1 2 3", NULL },
+    { "M10-20S1,2,3,", NULL },
+    { "M10-20S1,2,3,4,", NULL },
+    // quadratic curveto args
+    { "M10-20Q1", NULL },
+    { "M10-20Q1,", NULL },
+    { "M10-20Q1 2", NULL },
+    { "M10-20Q1,2,", NULL },
+    { "M10-20Q1 2 3", NULL },
+    { "M10-20Q1,2,3", NULL },
+    { "M10-20Q1,2,3,", NULL },
+    { "M10 20 Q30 40 50 60,", NULL },
+    // smooth quadratic curveto args
+    { "M10-20T1", NULL },
+    { "M10-20T1,", NULL },
+    { "M10 20 T 30 40,", NULL },
+    // elliptical arc args
+    { "M10-20A1", NULL },
+    { "M10-20A1,", NULL },
+    { "M10-20A1 2", NULL },
+    { "M10-20A1 2,", NULL },
+    { "M10-20A1 2 3", NULL },
+    { "M10-20A1 2 3,", NULL },
+    { "M10-20A1 2 3 4", NULL },
+    { "M10-20A1 2 3 1", NULL },
+    { "M10-20A1 2 3,1,", NULL },
+    { "M10-20A1 2 3 1 5", NULL },
+    { "M10-20A1 2 3 1 1", NULL },
+    { "M10-20A1 2 3,1,1,", NULL },
+    { "M10-20A1 2 3 1 1 6", NULL },
+    { "M10-20A1 2 3,1,1,6,", NULL },
+    { "M 1 2 A 1 2 3 1.0 0.0 6 7", NULL },
+    { "M10-20A1 2 3,1,1,6,7,", NULL },
+    // misc
+    { "M.. 1,0 0,100000", NULL },
+    { "M 10 20,M 10 20", NULL },
+    { "M 10 20, M 10 20", NULL },
+    { "M 10 20, M 10 20", NULL },
+    { "M 10 20, ", NULL },
+  };
+  int i;
+
+  for (i = 0; i < G_N_ELEMENTS (tests); i++)
+    {
+      GskPath *path;
+      char *string;
+      char *string2;
+
+      if (g_test_verbose ())
+        g_print ("%d: %s\n", i, tests[i].in);
+
+      path = gsk_path_new_from_string (tests[i].in);
+      if (tests[i].out)
+        {
+          g_assert_nonnull (path);
+          string = gsk_path_to_string (path);
+          gsk_path_unref (path);
+
+          if (strcmp (tests[i].out, "path") != 0)
+            g_assert_cmpstr (tests[i].out, ==, string);
+
+          path = gsk_path_new_from_string (string);
+          g_assert_nonnull (path);
+
+          string2 = gsk_path_to_string (path);
+          gsk_path_unref (path);
+
+          g_assert_cmpstr (string, ==, string2);
+
+          g_free (string);
+          g_free (string2);
+        }
+      else
+        g_assert_null (path);
+    }
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -124,6 +369,7 @@ main (int   argc,
   gtk_test_init (&argc, &argv, NULL);
 
   g_test_add_func ("/path/create", test_create);
+  g_test_add_func ("/path/from-string", test_from_string);
 
   return g_test_run ();
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]