summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthias Clasen <mclasen@redhat.com>2020-11-24 20:01:07 -0500
committerMatthias Clasen <mclasen@redhat.com>2020-11-25 21:37:42 -0500
commit43468790fe827fa24d7bf69bccb5c37394623fae (patch)
tree152e46e3227d7135a58f987904f78cfcfadebb60
parenteec1e74dfb2272581b26ae344bd159bd51f3997d (diff)
downloadgtk+-43468790fe827fa24d7bf69bccb5c37394623fae.tar.gz
path: Implement gsk_path_parse
Implement the SVG path syntax to read back the strings that we generate when serializing paths. The tests for this code are taken from librsvg.
-rw-r--r--gsk/gskpath.c498
-rw-r--r--gsk/gskpath.h6
-rw-r--r--gsk/gskrendernodeparser.c64
-rw-r--r--testsuite/gsk/path.c246
4 files changed, 799 insertions, 15 deletions
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index f72eefa844..d1e19fd470 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -158,7 +158,6 @@ gsk_find_point_on_line (const graphene_point_t *a,
*offset = t;
}
}
->>>>>>> 6bed13717b... path: Add gsk_path_measure_get_closest_point()
/* RECT CONTOUR */
@@ -2439,3 +2438,500 @@ arc_to (GskPathBuilder *builder,
t);
}
}
+
+static void
+skip_whitespace (const char **p)
+{
+ while (g_ascii_isspace (**p))
+ (*p)++;
+}
+
+static void
+skip_optional_comma (const char **p)
+{
+ skip_whitespace (p);
+ if (**p == ',')
+ (*p)++;
+}
+
+static gboolean
+parse_number (const char **p,
+ double *c)
+{
+ char *e;
+ *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;
+}
+
+/**
+ * gsk_path_parse:
+ * @string: a string
+ *
+ * This is a convenience function that constructs a #GskPath
+ * from a serialized form. The string is expected to be in
+ * [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData),
+ * as e.g. produced by gsk_path_to_string().
+ *
+ * Returns: (nullable): a new #GskPath, or %NULL if @string could not be parsed
+ **/
+GskPath *
+gsk_path_parse (const char *string)
+{
+ GskPathBuilder *builder;
+ double x, y;
+ double prev_x1, prev_y1;
+ double path_x, path_y;
+ const char *p;
+ char cmd;
+ char prev_cmd;
+ gboolean after_comma;
+ gboolean repeat;
+
+ builder = gsk_path_builder_new ();
+
+ cmd = 'X';
+ path_x = path_y = 0;
+ x = y = 0;
+ prev_x1 = prev_y1 = 0;
+ after_comma = FALSE;
+
+ p = string;
+ while (*p)
+ {
+ prev_cmd = cmd;
+ repeat = !parse_command (&p, &cmd);
+
+ if (after_comma && !repeat)
+ goto error;
+
+ switch (cmd)
+ {
+ case 'X':
+ goto error;
+
+ case 'Z':
+ case 'z':
+ if (repeat)
+ goto error;
+ else
+ {
+ gsk_path_builder_close (builder);
+ x = path_x;
+ y = path_y;
+ }
+ 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);
+ if (strchr ("zZX", prev_cmd))
+ {
+ path_x = x1;
+ path_y = y1;
+ }
+ }
+ x = x1;
+ y = y1;
+ }
+ else
+ goto error;
+ }
+ break;
+
+ case 'L':
+ case 'l':
+ {
+ double x1, y1;
+
+ if (parse_coordinate_pair (&p, &x1, &y1))
+ {
+ if (cmd == 'l')
+ {
+ x1 += x;
+ y1 += y;
+ }
+
+ if (strchr ("zZ", prev_cmd))
+ {
+ gsk_path_builder_move_to (builder, x, y);
+ path_x = x;
+ path_y = y;
+ }
+ gsk_path_builder_line_to (builder, x1, y1);
+ x = x1;
+ y = y1;
+ }
+ else
+ goto error;
+ }
+ break;
+
+ case 'H':
+ case 'h':
+ {
+ double x1;
+
+ if (parse_coordinate (&p, &x1))
+ {
+ if (cmd == 'h')
+ x1 += x;
+ if (strchr ("zZ", prev_cmd))
+ {
+ gsk_path_builder_move_to (builder, x, y);
+ path_x = x;
+ path_y = y;
+ }
+ 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;
+ if (strchr ("zZ", prev_cmd))
+ {
+ gsk_path_builder_move_to (builder, x, y);
+ path_x = x;
+ path_y = y;
+ }
+ gsk_path_builder_line_to (builder, x, y1);
+ y = y1;
+ }
+ else
+ goto error;
+ }
+ break;
+
+ case 'C':
+ 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;
+ }
+ if (strchr ("zZ", prev_cmd))
+ {
+ gsk_path_builder_move_to (builder, x, y);
+ path_x = x;
+ path_y = 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;
+ }
+ if (strchr ("zZ", prev_cmd))
+ {
+ gsk_path_builder_move_to (builder, x, y);
+ path_x = x;
+ path_y = 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;
+ if (strchr ("zZ", prev_cmd))
+ {
+ gsk_path_builder_move_to (builder, x, y);
+ path_x = x;
+ path_y = y;
+ }
+ 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;
+ if (strchr ("zZ", prev_cmd))
+ {
+ gsk_path_builder_move_to (builder, x, y);
+ path_x = x;
+ path_y = y;
+ }
+ 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;
+ }
+
+ if (strchr ("zZ", prev_cmd))
+ {
+ gsk_path_builder_move_to (builder, x, y);
+ path_x = x;
+ path_y = y;
+ }
+ 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 > string) && p[-1] == ',';
+ }
+
+ if (after_comma)
+ goto error;
+
+ return gsk_path_builder_free_to_path (builder);
+
+error:
+ //g_warning ("Can't parse string '%s' as GskPath, error at %ld", string, p - string);
+ gsk_path_builder_unref (builder);
+
+ return NULL;
+}
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index f590c4c452..415a21c2d8 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -69,6 +69,11 @@ void gsk_path_print (GskPath
GString *string);
GDK_AVAILABLE_IN_ALL
char * gsk_path_to_string (GskPath *self);
+
+GDK_AVAILABLE_IN_ALL
+GskPath * gsk_path_parse (const char *string);
+
+
GDK_AVAILABLE_IN_ALL
void gsk_path_to_cairo (GskPath *self,
cairo_t *cr);
@@ -84,6 +89,7 @@ gboolean gsk_path_foreach (GskPath
GskPathForeachFunc func,
gpointer user_data);
+
#define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ())
typedef struct _GskPathBuilder GskPathBuilder;
diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c
index b36ca99a27..b08cc44189 100644
--- a/gsk/gskrendernodeparser.c
+++ b/gsk/gskrendernodeparser.c
@@ -1732,7 +1732,22 @@ static gboolean
parse_path (GtkCssParser *parser,
gpointer out_path)
{
- return FALSE;
+ char *str = NULL;
+
+ if (!parse_string (parser, &str))
+ return FALSE;
+
+ *((GskPath **) out_path) = gsk_path_parse (str);
+
+ g_free (str);
+
+ return TRUE;
+}
+
+static void
+clear_path (gpointer inout_path)
+{
+ g_clear_pointer ((GskPath **) inout_path, gsk_path_unref);
}
static gboolean
@@ -1775,7 +1790,7 @@ parse_fill_node (GtkCssParser *parser)
GskFillRule rule = GSK_FILL_RULE_WINDING;
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
- { "path", parse_path, NULL, &path },
+ { "path", parse_path, clear_path, &path },
{ "fill-rule", parse_fill_rule, NULL, &rule },
};
GskRenderNode *result;
@@ -1786,6 +1801,8 @@ parse_fill_node (GtkCssParser *parser)
result = gsk_fill_node_new (child, path, rule);
+ gsk_path_unref (path);
+
gsk_render_node_unref (child);
return result;
@@ -1817,7 +1834,7 @@ parse_stroke_node (GtkCssParser *parser)
const Declaration declarations[] = {
{ "child", parse_node, clear_node, &child },
- { "path", parse_path, NULL, &path },
+ { "path", parse_path, clear_path, &path },
{ "line-width", parse_double, NULL, &line_width },
{ "line-cap", parse_line_cap, NULL, &line_cap },
{ "line-join", parse_line_join, NULL, &line_join },
@@ -1834,6 +1851,7 @@ parse_stroke_node (GtkCssParser *parser)
result = gsk_stroke_node_new (child, path, stroke);
+ gsk_path_unref (path);
gsk_stroke_free (stroke);
gsk_render_node_unref (child);
@@ -2327,8 +2345,11 @@ append_escaping_newlines (GString *str,
len = strcspn (string, "\n");
g_string_append_len (str, string, len);
string += len;
- g_string_append (str, "\\\n");
- string++;
+ if (*string)
+ {
+ g_string_append (str, "\\\n");
+ string++;
+ }
} while (*string);
}
@@ -2390,6 +2411,28 @@ append_enum_param (Printer *p,
}
static void
+append_path_param (Printer *p,
+ const char *param_name,
+ GskPath *path)
+{
+ char *str, *s;
+
+ _indent (p);
+ g_string_append (p->str, "path: \"\\\n");
+ str = gsk_path_to_string (path);
+ /* Put each command on a new line */
+ for (s = str; *s; s++)
+ {
+ if (*s == ' ' &&
+ (s[1] == 'M' || s[1] == 'C' || s[1] == 'Z' || s[1] == 'L'))
+ *s = '\n';
+ }
+ append_escaping_newlines (p->str, str);
+ g_string_append (p->str, "\";\n");
+ g_free (str);
+}
+
+static void
render_node_print (Printer *p,
GskRenderNode *node)
{
@@ -2557,14 +2600,10 @@ render_node_print (Printer *p,
case GSK_FILL_NODE:
{
- char *path_str;
-
start_node (p, "fill");
append_node_param (p, "child", gsk_fill_node_get_child (node));
- path_str = gsk_path_to_string (gsk_fill_node_get_path (node));
- append_string_param (p, "path", path_str);
- g_free (path_str);
+ append_path_param (p, "path", gsk_fill_node_get_path (node));
append_enum_param (p, "fill-rule", GSK_TYPE_FILL_RULE, gsk_fill_node_get_fill_rule (node));
end_node (p);
@@ -2573,15 +2612,12 @@ render_node_print (Printer *p,
case GSK_STROKE_NODE:
{
- char *path_str;
const GskStroke *stroke;
start_node (p, "stroke");
append_node_param (p, "child", gsk_stroke_node_get_child (node));
- path_str = gsk_path_to_string (gsk_stroke_node_get_path (node));
- append_string_param (p, "path", path_str);
- g_free (path_str);
+ append_path_param (p, "path", gsk_stroke_node_get_path (node));
stroke = gsk_stroke_node_get_stroke (node);
append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0);
diff --git a/testsuite/gsk/path.c b/testsuite/gsk/path.c
index e50212f24b..aa6ed87efe 100644
--- a/testsuite/gsk/path.c
+++ b/testsuite/gsk/path.c
@@ -348,6 +348,251 @@ test_closest_point (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_parse (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_parse (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[])
@@ -360,6 +605,7 @@ main (int argc,
g_test_add_func ("/path/segment_chunk", test_segment_chunk);
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);
return g_test_run ();
}