[gtk/matthiasc/lottie2: 8/8] path: Special-case rect and circles
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/matthiasc/lottie2: 8/8] path: Special-case rect and circles
- Date: Wed, 25 Nov 2020 15:17:26 +0000 (UTC)
commit ba799d9370e8e7c7f3d3bb4688035ee9a94bea55
Author: Matthias Clasen <mclasen redhat com>
Date: Wed Nov 25 01:03:13 2020 -0500
path: Special-case rect and circles
Add code in the path parser to recognize the way
in which we write out rects and circles, so we
can successfully round-trip these through the
SVG path format.
gsk/gskpath.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++---
testsuite/gsk/path.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 292 insertions(+), 7 deletions(-)
---
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index 3485f3fb56..60c52e8d5b 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -180,7 +180,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);
@@ -497,10 +498,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)
@@ -2456,6 +2458,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_new_from_string:
* @path: a string
@@ -2517,9 +2628,31 @@ gsk_path_new_from_string (const char *s)
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')
{
@@ -2827,7 +2960,7 @@ gsk_path_new_from_string (const char *s)
return gsk_path_builder_free_to_path (builder);
error:
- //g_warning ("Can't parse string '%s' as GskPath, error at %ld", s, p - s);
+ //g_warning ("Can't parse string '%s' as GskPath, error at %ld: %s", s, p - s, p);
gsk_path_builder_unref (builder);
return NULL;
diff --git a/testsuite/gsk/path.c b/testsuite/gsk/path.c
index be74d8abb6..0126727ad8 100644
--- a/testsuite/gsk/path.c
+++ b/testsuite/gsk/path.c
@@ -608,6 +608,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_new_from_string (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_new_from_string (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[])
@@ -621,6 +771,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]