[gtk/matthiasc/lottie-serialization: 3/5] path: Implement gsk_path_parse




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

    path: Implement gsk_path_parse
    
    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.
    
    This includes an elliptical arc implementation according
    to the SVG spec. The code is mostly taken from librsvg,
    but pretty directly follows the SVG spec implementation
    notes. We don't export this, since the parametrization
    is inconvenient. We do want an arc_to API, but
    these are not the arcs we are looking for.

 gsk/gskpath.c             | 498 ++++++++++++++++++++++++++++++++++++++++++++++
 gsk/gskpath.h             |   4 +
 gsk/gskrendernodeparser.c |  64 ++++--
 testsuite/gsk/path.c      | 246 +++++++++++++++++++++++
 4 files changed, 798 insertions(+), 14 deletions(-)
---
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index c15d457ab4..6356e97090 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -2065,3 +2065,501 @@ gsk_path_foreach_with_tolerance (GskPath            *self,
   return TRUE;
 }
 
+/* path parser and utilities */
+
+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_parse:
+ * @string: 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: (nullable): a new #GskPath, or %NULL if @string could not be parsed
+ **/
+GskPath *
+gsk_path_parse (const char *string)
+{
+  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 = string;
+  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_svg_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 > string) && 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", string, p - string);
+  gsk_path_builder_unref (builder);
+
+  return NULL;
+}
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index d0116e7ba4..27b01d4a44 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -66,6 +66,10 @@ void                    gsk_path_print                          (GskPath
                                                                  GString                *string);
 GDK_AVAILABLE_IN_ALL
 char *                  gsk_path_to_string                      (GskPath                *self);
+
+GDK_AVAILABLE_IN_ALL
+GskPath *               gsk_path_parse                          (const char             *string);
+
 GDK_AVAILABLE_IN_ALL
 void                    gsk_path_to_cairo                       (GskPath                *self,
                                                                  cairo_t                *cr);
diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c
index b36ca99a27..b08cc44189 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_parse (str);
+
+  g_free (str);
+
+  return TRUE;
+}
+
+static void
+clear_path (gpointer inout_path)
+{
+  g_clear_pointer ((GskPath **) inout_path, gsk_path_unref);
 }
 
 static gboolean
@@ -1775,7 +1790,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;
@@ -1786,6 +1801,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 2d30af5858..7eca487f4a 100644
--- a/testsuite/gsk/path.c
+++ b/testsuite/gsk/path.c
@@ -614,6 +614,251 @@ test_closest_point_for_point (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_parse (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_parse (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[])
@@ -628,6 +873,7 @@ main (int   argc,
   g_test_add_func ("/path/get_point", test_get_point);
   g_test_add_func ("/path/closest_point", test_closest_point);
   g_test_add_func ("/path/closest_point_for_point", test_closest_point_for_point);
+  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]