[gtk/matthiasc/lottie2: 17/24] path: Special-case rects and circles




commit ebc31fe6853edaaf39aff877dce11d0d7697769a
Author: Matthias Clasen <mclasen redhat com>
Date:   Wed Nov 25 01:03:13 2020 -0500

    path: Special-case rects and circles
    
    Write out the commands for rects and circles in a special
    way (without whitespace), and add code in the parser to
    recognize this, so we can successfully round-trip these
    through the SVG path format.
    
    Tests included.

 gsk/gskpath.c        | 145 ++++++++++++++++++++++++++++++++++++++++++++++--
 testsuite/gsk/path.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 291 insertions(+), 6 deletions(-)
---
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index dbf46c7e9e..b20ad2528c 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -184,7 +184,8 @@ gsk_rect_contour_print (const GskContour *contour,
 {
   const GskRectContour *self = (const GskRectContour *) contour;
 
-  g_string_append_printf (string, "M %g %g h %g v %g h %g z",
+  /* Write commands in a form that gsk_path_new_from_string() recognizes */
+  g_string_append_printf (string, "M%g,%gh%gv%gh%gz",
                           self->x, self->y,
                           self->width, self->height,
                           -self->width);
@@ -513,10 +514,11 @@ gsk_circle_contour_print (const GskContour *contour,
   graphene_point_t end = GRAPHENE_POINT_INIT (self->center.x + cos (DEG_TO_RAD (self->end_angle)) * 
self->radius,
                                               self->center.y + sin (DEG_TO_RAD (self->end_angle)) * 
self->radius);
 
-  g_string_append_printf (string, "M %g %g ", start.x, start.y);
-  g_string_append_printf (string, "A %g %g 0 1 0 %g %g ",
+  /* Write commands in a form that gsk_path_new_from_string() recognizes */
+  g_string_append_printf (string, "M%g,%g", start.x, start.y);
+  g_string_append_printf (string, "A%g,%g,0,1,0,%g,%g",
                           self->radius, self->radius, mid.x, mid.y);
-  g_string_append_printf (string, "A %g %g 0 1 0 %g %g ",
+  g_string_append_printf (string, "A%g,%g,0,1,0,%g,%g",
                           self->radius, self->radius, end.x, end.y);
 
   if (fabs (self->start_angle - self->end_angle) >= 360)
@@ -2564,6 +2566,115 @@ parse_command (const char **p,
   return FALSE;
 }
 
+static gboolean
+parse_char (const char **p,
+            char         ch)
+{
+  if (**p != ch)
+    return FALSE;
+  (*p)++;
+  return TRUE;
+}
+
+static gboolean
+parse_string (const char **p,
+              const char  *s)
+{
+  int len = strlen (s);
+  if (strncmp (*p, s, len) != 0)
+    return FALSE;
+  (*p) += len;
+  return TRUE;
+}
+
+static gboolean
+parse_rectangle (const char **p,
+                 double      *x,
+                 double      *y,
+                 double      *w,
+                 double      *h)
+{
+  const char *o = *p;
+  double w2;
+
+  /* Check for M%g,%gh%gv%gh%gz without any intervening whitespace */
+  if (parse_coordinate_pair (p, x, y) &&
+      parse_char (p, 'h') &&
+      parse_coordinate (p, w) &&
+      parse_char (p, 'v') &&
+      parse_coordinate (p, h) &&
+      parse_char (p, 'h') &&
+      parse_coordinate (p, &w2) &&
+      parse_char (p, 'z') &&
+      w2 == - *w)
+    {
+      const char *s;
+
+      for (s = o; s != *p; s++)
+        {
+          if (g_ascii_isspace (*s))
+            {
+              *p = o;
+              return FALSE;
+            }
+        }
+
+      skip_whitespace (p);
+
+      return TRUE;
+    }
+
+  *p = o;
+  return FALSE;
+}
+
+static gboolean
+parse_circle (const char **p,
+              double      *sx,
+              double      *sy,
+              double      *r)
+{
+  const char *o = *p;
+  double r1, r2, r3, mx, my, ex, ey;
+
+  /* Check for M%g,%gA%g,%g,0,1,0,%g,%gA%g,%g,0,1,0,%g,%g
+   * without any intervening whitespace
+   */
+  if (parse_coordinate_pair (p, sx, sy) &&
+      parse_char (p, 'A') &&
+      parse_coordinate_pair (p, r, &r1) &&
+      parse_string (p, "0,1,0,") &&
+      parse_coordinate_pair (p, &mx, &my) &&
+      parse_char (p, 'A') &&
+      parse_coordinate_pair (p, &r2, &r3) &&
+      parse_string (p, "0,1,0,") &&
+      parse_coordinate_pair (p, &ex, &ey) &&
+      parse_char (p, 'Z') &&
+      *r == r1 && r1 == r2 && r2 == r3 &&
+      *sx == ex && *sy == ey)
+    {
+      const char *s;
+
+      for (s = o; s != *p; s++)
+        {
+          if (g_ascii_isspace (*s))
+            {
+              *p = o;
+              return FALSE;
+            }
+        }
+
+      *r = r1;
+
+      skip_whitespace (p);
+
+      return TRUE;
+    }
+
+  *p = o;
+  return FALSE;
+}
+
 /**
  * gsk_path_parse:
  * @string: a string
@@ -2625,9 +2736,31 @@ gsk_path_parse (const char *string)
         case 'M':
         case 'm':
           {
-            double x1, y1;
+            double x1, y1, w, h, r;
 
-            if (parse_coordinate_pair (&p, &x1, &y1))
+            if (parse_rectangle (&p, &x1, &y1, &w, &h))
+              {
+                gsk_path_builder_add_rect (builder, x1, y1, w, h);
+                if (strchr ("zZX", prev_cmd))
+                  {
+                    path_x = x1;
+                    path_y = y1;
+                  }
+                x = x1;
+                y = y1;
+              }
+            else if (parse_circle (&p, &x1, &y1, &r))
+              {
+                gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (x1 - r, y1), r);
+                if (strchr ("zZX", prev_cmd))
+                  {
+                    path_x = x1;
+                    path_y = y1;
+                  }
+                x = x1;
+                y = y1;
+              }
+            else if (parse_coordinate_pair (&p, &x1, &y1))
               {
                 if (cmd == 'm')
                   {
diff --git a/testsuite/gsk/path.c b/testsuite/gsk/path.c
index bb8ae1ce5f..9ce9766830 100644
--- a/testsuite/gsk/path.c
+++ b/testsuite/gsk/path.c
@@ -603,6 +603,156 @@ test_from_string (void)
     }
 }
 
+/* test that the parser can handle random paths */
+static void
+test_from_random_string (void)
+{
+  int i;
+
+  for (i = 0; i < 1000; i++)
+    {
+      GskPath *path = create_random_path ();
+      char *string = gsk_path_to_string (path);
+      GskPath *path1;
+
+      g_assert_nonnull (string);
+
+      path1 = gsk_path_parse (string);
+      g_assert_nonnull (path1);
+
+      gsk_path_unref (path1);
+      g_free (string);
+      gsk_path_unref (path);
+    }
+}
+
+typedef struct
+{
+  GskPathOperation op;
+  graphene_point_t pts[4];
+  gsize n_pts;
+} Contour;
+
+static gboolean
+append_contour (GskPathOperation        op,
+                const graphene_point_t *pts,
+                gsize                   n_pts,
+                gpointer                user_data)
+{
+  GArray *a = user_data;
+  Contour c;
+  int i;
+
+  g_assert (n_pts <= 4);
+  c.op = op;
+  c.n_pts = n_pts;
+  for (i = 0; i < n_pts; i++)
+    c.pts[i] = pts[i];
+
+  g_array_append_val (a, c);
+  return  TRUE;
+}
+
+static GArray *
+path_to_contours (GskPath *path)
+{
+  GArray *a;
+
+  a = g_array_new (FALSE, FALSE, sizeof (Contour));
+  gsk_path_foreach (path, append_contour, a);
+
+  return a;
+}
+
+static gboolean
+contour_equal (Contour *c1,
+               Contour *c2)
+{
+  int i;
+
+  if (c1->op != c2->op)
+    return FALSE;
+
+  if (c1->n_pts != c2->n_pts)
+    return FALSE;
+
+  for (i = 0; i < c1->n_pts; i++)
+    {
+      if (c1->pts[i].x != c2->pts[i].x ||
+          c1->pts[i].y != c2->pts[i].y)
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+contours_equal (GArray *a1,
+                GArray *a2)
+{
+  int i;
+
+  if (a1->len != a2->len)
+    return FALSE;
+
+  for (i = 0; i < a1->len; i++)
+    {
+      Contour *c1 = &g_array_index (a1, Contour, i);
+      Contour *c2 = &g_array_index (a2, Contour, i);
+
+      if (!contour_equal (c1, c2))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+path_equal (GskPath *path1,
+            GskPath *path2)
+{
+  GArray *a1, *a2;
+  gboolean ret;
+
+  a1 = path_to_contours (path1);
+  a2 = path_to_contours (path2);
+
+  ret = contours_equal (a1, a2);
+
+  g_array_unref (a1);
+  g_array_unref (a2);
+
+  return ret;
+}
+
+/* Test that circles and rectangles serialize as expected and can be
+ * round-tripped through strings.
+ */
+static void
+test_serialize (void)
+{
+  GskPathBuilder *builder;
+  GskPath *path;
+  GskPath *path1;
+  char *string;
+
+  builder = gsk_path_builder_new ();
+  gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 50);
+  gsk_path_builder_add_rect (builder, 111, 222, 333, 444);
+  path = gsk_path_builder_free_to_path (builder);
+
+  string = gsk_path_to_string (path);
+  g_assert_cmpstr ("M150,100A50,50,0,1,0,50,100A50,50,0,1,0,150,100Z M111,222h333v444h-333z", ==, string);
+
+  path1 = gsk_path_parse (string);
+
+  g_assert_true (path_equal (path, path1));
+
+  g_free (string);
+  gsk_path_unref (path);
+  gsk_path_unref (path1);
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -616,6 +766,8 @@ main (int   argc,
   g_test_add_func ("/path/segment", test_segment);
   g_test_add_func ("/path/closest_point", test_closest_point);
   g_test_add_func ("/path/from-string", test_from_string);
+  g_test_add_func ("/path/from-random-string", test_from_random_string);
+  g_test_add_func ("/path/serialize", test_serialize);
 
   return g_test_run ();
 }


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