[gtk/matthiasc/lottie-serialization: 5/5] path: Special-case rects and circles
- From: Matthias Clasen <matthiasc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/matthiasc/lottie-serialization: 5/5] path: Special-case rects and circles
- Date: Sat, 28 Nov 2020 17:33:14 +0000 (UTC)
commit 1af40af3cb7172cd7c48d9f7a2f99afd96410c7a
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 | 172 ++++++++++++++++++++++++++++++++++++++++++++-------
testsuite/gsk/path.c | 153 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 304 insertions(+), 21 deletions(-)
---
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index 7da1ed1f3b..00c8aaf949 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -195,14 +195,11 @@ gsk_rect_contour_print (const GskContour *contour,
{
const GskRectContour *self = (const GskRectContour *) contour;
- g_string_append (string, "M ");
- _g_string_append_point (string, &GRAPHENE_POINT_INIT (self->x, self->y));
- g_string_append (string, " h ");
- _g_string_append_double (string, self->width);
- g_string_append (string, " v ");
- _g_string_append_double (string, self->height);
- g_string_append (string, " h ");
- _g_string_append_double (string, - self->width);
+ /* 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);
}
static gboolean
@@ -554,18 +551,20 @@ gsk_circle_contour_print (const GskContour *contour,
const GskCircleContour *self = (const GskCircleContour *) contour;
float mid_angle = (self->end_angle - self->start_angle) / 2;
- g_string_append (string, "M ");
+ g_string_append_printf (string, "M%g,%g",
+ self->center.x + cos (DEG_TO_RAD (self->start_angle)) * self->radius,
+ self->center.y + sin (DEG_TO_RAD (self->start_angle)) * self->radius);
_g_string_append_point (string, &GSK_CIRCLE_POINT_INIT (self, self->start_angle));
- g_string_append (string, " A ");
- _g_string_append_point (string, &GRAPHENE_POINT_INIT (self->radius, self->radius));
- g_string_append_printf (string, " 0 0 %u",
- self->start_angle < self->end_angle ? 0 : 1);
- _g_string_append_point (string, &GSK_CIRCLE_POINT_INIT (self, mid_angle));
- g_string_append (string, " A ");
- _g_string_append_point (string, &GRAPHENE_POINT_INIT (self->radius, self->radius));
- g_string_append_printf (string, " 0 0 %u",
- self->start_angle < self->end_angle ? 0 : 1);
- _g_string_append_point (string, &GSK_CIRCLE_POINT_INIT (self, self->end_angle));
+ g_string_append_printf (string, "A%g,%g,0,0,%u,%g,%g",
+ self->radius, self->radius,
+ self->start_angle < self->end_angle ? 0 : 1,
+ self->center.x + cos (DEG_TO_RAD (mid_angle)) * self->radius,
+ self->center.y + sin (DEG_TO_RAD (mid_angle)) * self->radius);
+ g_string_append_printf (string, "A%g,%g,0,0,%u,%g,%g",
+ self->radius, self->radius,
+ self->start_angle < self->end_angle ? 0 : 1,
+ self->center.x + cos (DEG_TO_RAD (self->end_angle)) * self->radius,
+ self->center.y + sin (DEG_TO_RAD (self->end_angle)) * self->radius);
if (fabs (self->start_angle - self->end_angle >= 360))
g_string_append (string, "Z");
}
@@ -2194,6 +2193,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
@@ -2255,9 +2363,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, &GRAPHENE_RECT_INIT (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 7eca487f4a..119bae4433 100644
--- a/testsuite/gsk/path.c
+++ b/testsuite/gsk/path.c
@@ -859,6 +859,157 @@ 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 (G_MAXUINT);
+ 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,
+ float weight,
+ 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, &GRAPHENE_RECT_INIT (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[])
@@ -874,6 +1025,8 @@ main (int argc,
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);
+ 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]