diff options
author | Matthias Clasen <mclasen@redhat.com> | 2020-11-24 20:01:07 -0500 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2020-11-25 21:37:42 -0500 |
commit | 43468790fe827fa24d7bf69bccb5c37394623fae (patch) | |
tree | 152e46e3227d7135a58f987904f78cfcfadebb60 | |
parent | eec1e74dfb2272581b26ae344bd159bd51f3997d (diff) | |
download | gtk+-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.c | 498 | ||||
-rw-r--r-- | gsk/gskpath.h | 6 | ||||
-rw-r--r-- | gsk/gskrendernodeparser.c | 64 | ||||
-rw-r--r-- | testsuite/gsk/path.c | 246 |
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 (); } |