diff options
author | Matthias Clasen <mclasen@redhat.com> | 2020-11-23 20:38:15 -0500 |
---|---|---|
committer | Matthias Clasen <mclasen@redhat.com> | 2020-11-24 00:22:34 -0500 |
commit | 5f4850a4f47a37324a865eee787e3c51501b4b31 (patch) | |
tree | 8604b9ab75a6b517780d699f94c6717cd7596dbb | |
parent | 5a3daa924defa11a17287e56db712dc4611dd0ac (diff) | |
download | gtk+-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.c | 629 | ||||
-rw-r--r-- | gsk/gskpath.h | 4 | ||||
-rw-r--r-- | gsk/gskpathprivate.h | 1 | ||||
-rw-r--r-- | testsuite/gsk/path.c | 246 |
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 (); } |