[gtk/matthiasc/lottie] Finish path parsing



commit 5f4850a4f47a37324a865eee787e3c51501b4b31
Author: Matthias Clasen <mclasen redhat com>
Date:   Mon Nov 23 20:38:15 2020 -0500

    Finish path parsing
    
    Make the path parser handle the complete SVG path
    language. The code for elliptical arcs is taken from
    librsvg. Make gsk_path_from_string public, and add
    tests for it.

 gsk/gskpath.c        | 629 +++++++++++++++++++++++++++++++++++++++++++--------
 gsk/gskpath.h        |   4 +
 gsk/gskpathprivate.h |   1 -
 testsuite/gsk/path.c | 246 ++++++++++++++++++++
 4 files changed, 784 insertions(+), 96 deletions(-)
---
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index 6187527feb..a718380042 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -1919,139 +1919,578 @@ gsk_path_builder_close (GskPathBuilder *builder)
   gsk_path_builder_end_current (builder);
 }
 
-static const char *
-skip_whitespace (const char *p)
+static void
+arc_segment (GskPathBuilder *builder,
+             double          cx,
+             double          cy,
+             double          rx,
+             double          ry,
+             double          sin_phi,
+             double          cos_phi,
+             double          sin_th0,
+             double          cos_th0,
+             double          sin_th1,
+             double          cos_th1,
+             double          t)
+{
+  double x1, y1, x2, y2, x3, y3;
+
+  x1 = rx * (cos_th0 - t * sin_th0);
+  y1 = ry * (sin_th0 + t * cos_th0);
+  x3 = rx * cos_th1;
+  y3 = ry * sin_th1;
+  x2 = x3 + rx * (t * sin_th1);
+  y2 = y3 + ry * (-t * cos_th1);
+
+  gsk_path_builder_curve_to (builder,
+                             cx + cos_phi * x1 - sin_phi * y1,
+                             cy + sin_phi * x1 + cos_phi * y1,
+                             cx + cos_phi * x2 - sin_phi * y2,
+                             cy + sin_phi * x2 + cos_phi * y2,
+                             cx + cos_phi * x3 - sin_phi * y3,
+                             cy + sin_phi * x3 + cos_phi * y3);
+}
+
+static void
+gsk_path_builder_arc_to (GskPathBuilder *builder,
+                         float           rx,
+                         float           ry,
+                         float           x_axis_rotation,
+                         gboolean        large_arc,
+                         gboolean        positive_sweep,
+                         float           x,
+                         float           y)
+{
+  graphene_point_t *current;
+  double x1, y1, x2, y2;
+  double phi, sin_phi, cos_phi;
+  double mid_x, mid_y;
+  double lambda;
+  double d;
+  double k;
+  double x1_, y1_;
+  double cx_, cy_;
+  double  cx, cy;
+  double ux, uy, u_len;
+  double cos_theta1, theta1;
+  double vx, vy, v_len;
+  double dp_uv;
+  double cos_delta_theta, delta_theta;
+  int i, n_segs;
+  double d_theta, theta;
+  double sin_th0, cos_th0;
+  double sin_th1, cos_th1;
+  double th_half;
+  double t;
+
+  current = &g_array_index (builder->points, graphene_point_t, builder->points->len - 1);
+  x1 = current->x;
+  y1 = current->y;
+  x2 = x;
+  y2 = y;
+
+  phi = x_axis_rotation * M_PI / 180.0;
+  sincos (phi, &sin_phi, &cos_phi);
+
+  rx = fabs (rx);
+  ry = fabs (ry);
+
+  mid_x = (x1 - x2) / 2;
+  mid_y = (y1 - y2) / 2;
+
+  x1_ = cos_phi * mid_x + sin_phi * mid_y;
+  y1_ = - sin_phi * mid_x + cos_phi * mid_y;
+
+  lambda = (x1_ / rx) * (x1_ / rx) + (y1_ / ry) * (y1_ / ry);
+  if (lambda > 1)
+    {
+      lambda = sqrt (lambda);
+      rx *= lambda;
+      ry *= lambda;
+    }
+
+  d = (rx * y1_) * (rx * y1_) + (ry * x1_) * (ry * x1_);
+  if (d == 0)
+    return;
+
+  k = sqrt (fabs ((rx * ry) * (rx * ry) / d - 1.0));
+  if (positive_sweep == large_arc)
+    k = -k;
+
+  cx_ = k * rx * y1_ / ry;
+  cy_ = -k * ry * x1_ / rx;
+
+  cx = cos_phi * cx_ - sin_phi * cy_ + (x1 + x2) / 2;
+  cy = sin_phi * cx_ + cos_phi * cy_ + (y1 + y2) / 2;
+
+  ux = (x1_ - cx_) / rx;
+  uy = (y1_ - cy_) / ry;
+  u_len = sqrt (ux * ux + uy * uy);
+  if (u_len == 0)
+    return;
+
+  cos_theta1 = CLAMP (ux / u_len, -1, 1);
+  theta1 = acos (cos_theta1);
+  if (uy < 0)
+    theta1 = - theta1;
+
+  vx = (- x1_ - cx_) / rx;
+  vy = (- y1_ - cy_) / ry;
+  v_len = sqrt (vx * vx + vy * vy);
+  if (v_len == 0)
+    return;
+
+  dp_uv = ux * vx + uy * vy;
+  cos_delta_theta = CLAMP (dp_uv / (u_len * v_len), -1, 1);
+  delta_theta = acos (cos_delta_theta);
+  if (ux * vy - uy * vx < 0)
+    delta_theta = - delta_theta;
+  if (positive_sweep && delta_theta < 0)
+    delta_theta += 2 * M_PI;
+  else if (!positive_sweep && delta_theta > 0)
+    delta_theta -= 2 * M_PI;
+
+  n_segs = ceil (fabs (delta_theta / (M_PI_2 + 0.001)));
+  d_theta = delta_theta / n_segs;
+  theta = theta1;
+  sincos (theta1, &sin_th1, &cos_th1);
+
+  th_half = d_theta / 2;
+  t = (8.0 / 3.0) * sin (th_half / 2) * sin (th_half / 2) / sin (th_half);
+
+  for (i = 0; i < n_segs; i++)
+    {
+      theta = theta1;
+      theta1 = theta + d_theta;
+      sin_th0 = sin_th1;
+      cos_th0 = cos_th1;
+      sincos (theta1, &sin_th1, &cos_th1);
+      arc_segment (builder,
+                   cx, cy, rx, ry,
+                   sin_phi, cos_phi,
+                   sin_th0, cos_th0,
+                   sin_th1, cos_th1,
+                   t);
+    }
+}
+
+static void
+skip_whitespace (const char **p)
 {
-  while (g_ascii_isspace (*p))
-    p++;
-  return p;
+  while (g_ascii_isspace (**p))
+    (*p)++;
 }
 
 static void
-parse_point (const char  *p,
-             char       **end,
-             double      *x,
-             double      *y)
+skip_optional_comma (const char **p)
+{
+  skip_whitespace (p);
+  if (**p == ',')
+    (*p)++;
+}
+
+static gboolean
+parse_number (const char **p,
+              double      *c)
 {
   char *e;
-  *x = g_ascii_strtod (p, &e);
-  *y = g_ascii_strtod (e, end);
+  *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;
 }
 
 GskPath *
 gsk_path_from_string (const char *s)
 {
   GskPathBuilder *builder;
-  double x0, y0, x1, y1, x2, y2;
-  double w, h, r;
+  double x, y;
+  double prev_x1, prev_y1;
   const char *p;
-  char *end;
-  int i;
+  char cmd;
+  char prev_cmd;
+  gboolean after_comma;
+  gboolean repeat;
 
   builder = gsk_path_builder_new ();
 
-  /* Commands that we produce:
-   * M x y h w v h h -w z  (rectangle)
-   * M x y A r r 0 i i x1 y1 (circle)
-   * M x y (move)
-   * Z (close)
-   * L x y (line)
-   * C x0 y0, x1 y1, x2 y2 (curve)
-   */
+  cmd = 'X';
+  x = y = 0;
+  prev_x1 = prev_y1 = 0;
+  after_comma = FALSE;
+
   p = s;
-  while (p)
+  while (*p)
     {
-      p = skip_whitespace (p);
-      if (*p == '\0')
-        break;
-
-      switch (*p)
+      prev_cmd = cmd;
+      repeat = !parse_command (&p, &cmd);
+      if (after_comma && !repeat)
+        goto error;
+      switch (cmd)
         {
-        case 'M':
-          p++;
-          parse_point (p, &end, &x0, &y0);
-          p = skip_whitespace (end);
-          switch (*p)
-            {
-            case 'h':
-              p++;
-              w = g_ascii_strtod (p, &end);
-              p = skip_whitespace (end);
-              if (*p != 'v')
-                goto error;
-              p++;
-              h = g_ascii_strtod (p, &end);
-              p = skip_whitespace (end);
-              if (*p != 'h')
-                goto error;
-              p++;
-              g_ascii_strtod (p, &end);
-              p = skip_whitespace (end);
-              if (*p != 'z')
-                goto error;
-              p++;
-
-              gsk_path_builder_add_rect (builder, x0, y0, w, h);
-              break;
-
-            case 'A':
-              p++;
-              parse_point (p, &end, &r, &r);
-              p = skip_whitespace (end);
-              /* FIXME reconstruct partial circles */
-              for (i = 0; i < 5; i++)
-                {
-                  g_ascii_strtod (p, &end);
-                  p = skip_whitespace (end);
-                }
-
-              gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (x0 - r, y0), r);
-              break;
-
-            default:
-              gsk_path_builder_move_to (builder, x0, y0);
-              break;
-            }
-          break;
+        case 'X':
+          goto error;
 
         case 'Z':
-          p++;
-          gsk_path_builder_close (builder);
+        case 'z':
+          if (repeat)
+            goto error;
+          else
+            gsk_path_builder_close (builder);
+          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);
+                x = x1;
+                y = y1;
+              }
+            else
+              goto error;
+          }
           break;
 
         case 'L':
-          p++;
-          parse_point (p, &end, &x0, &y0);
-          p = skip_whitespace (end);
+        case 'l':
+          {
+            double x1, y1;
+
+            if (parse_coordinate_pair (&p, &x1, &y1))
+              {
+                if (cmd == 'l')
+                  {
+                    x1 += x;
+                    y1 += y;
+                  }
+                gsk_path_builder_line_to (builder, x1, y1);
+                x = x1;
+                y = y1;
+              }
+            else
+              goto error;
+          }
+          break;
+
+        case 'H':
+        case 'h':
+          {
+            double x1;
 
-          gsk_path_builder_line_to (builder, x0, y0);
+            if (parse_coordinate (&p, &x1))
+              {
+                if (cmd == 'h')
+                  x1 += x;
+                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;
+                gsk_path_builder_line_to (builder, x, y1);
+                y = y1;
+              }
+            else
+              goto error;
+          }
           break;
 
         case 'C':
-          p++;
-          parse_point (p, &end, &x0, &y0);
-          p = skip_whitespace (end);
-          if (*p == ',')
-            p++;
-          parse_point (p, &end, &x1, &y1);
-          p = skip_whitespace (end);
-          if (*p == ',')
-            p++;
-          parse_point (p, &end, &x2, &y2);
-          p = skip_whitespace (end);
-
-          gsk_path_builder_curve_to (builder, x0, y0, x1, y1, x2, y2);
+        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;
+                  }
+                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;
+                  }
+                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;
+                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;
+                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;
+                  }
+
+                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 as GskPath, error at %ld", p - s);
+  //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 f2010d5397..8ee27f5d25 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -84,6 +84,10 @@ gboolean                gsk_path_foreach                        (GskPath
                                                                  GskPathForeachFunc    func,
                                                                  gpointer              user_data);
 
+GDK_AVAILABLE_IN_ALL
+GskPath *               gsk_path_from_string                    (const char *string);
+
+
 #define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ())
 
 typedef struct _GskPathBuilder GskPathBuilder;
diff --git a/gsk/gskpathprivate.h b/gsk/gskpathprivate.h
index 101db67423..4663b3027a 100644
--- a/gsk/gskpathprivate.h
+++ b/gsk/gskpathprivate.h
@@ -58,7 +58,6 @@ void                    gsk_path_builder_add_contour_segment    (GskPathBuilder
                                                                  float                 start,
                                                                  float                 end);
 
-GskPath *               gsk_path_from_string                    (const char *string);
 
 G_END_DECLS
 
diff --git a/testsuite/gsk/path.c b/testsuite/gsk/path.c
index fc2397e98d..2c0779ee29 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_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_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]