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.

This includes an elliptical arc implementation according
to the SVG spec. The code is mostly taken from librsvg,
but pretty directly follows the SVG spec implementation
notes. We don't export this, since the parametrization
is inconvenient. We do want an arc_to API, but
these are not the arcs we are looking for.
This commit is contained in:
Matthias Clasen
2020-11-24 20:01:07 -05:00
parent 69d6eb73e7
commit 0d5c270dd4
4 changed files with 798 additions and 14 deletions

View File

@@ -2065,3 +2065,501 @@ gsk_path_foreach_with_tolerance (GskPath *self,
return TRUE;
}
/* path parser and utilities */
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;
}
gsk_path_builder_svg_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;
}

View File

@@ -66,6 +66,10 @@ 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);

View File

@@ -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);
}
@@ -2389,6 +2410,28 @@ append_enum_param (Printer *p,
g_string_append_c (p->str, '\n');
}
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);

View File

@@ -614,6 +614,251 @@ test_closest_point_for_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[])
@@ -628,6 +873,7 @@ main (int argc,
g_test_add_func ("/path/get_point", test_get_point);
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);
return g_test_run ();
}