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.
This commit is contained in:
145
gsk/gskpath.c
145
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')
|
||||
{
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user