summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2020-11-23 20:38:15 -0500
committerMatthias Clasen <mclasen@redhat.com>2020-11-24 00:22:34 -0500
commit5f4850a4f47a37324a865eee787e3c51501b4b31 (patch)
tree8604b9ab75a6b517780d699f94c6717cd7596dbb
parent5a3daa924defa11a17287e56db712dc4611dd0ac (diff)
downloadgtk+-5f4850a4f47a37324a865eee787e3c51501b4b31.tar.gz
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.
-rw-r--r--gsk/gskpath.c629
-rw-r--r--gsk/gskpath.h4
-rw-r--r--gsk/gskpathprivate.h1
-rw-r--r--testsuite/gsk/path.c246
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 ();
}