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.
This commit is contained in:
627
gsk/gskpath.c
627
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)
|
||||
{
|
||||
while (g_ascii_isspace (*p))
|
||||
p++;
|
||||
return p;
|
||||
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
|
||||
parse_point (const char *p,
|
||||
char **end,
|
||||
double *x,
|
||||
double *y)
|
||||
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)++;
|
||||
}
|
||||
|
||||
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;
|
||||
*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;
|
||||
|
||||
gsk_path_builder_line_to (builder, x0, y0);
|
||||
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;
|
||||
|
||||
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);
|
||||
case 'c':
|
||||
{
|
||||
double x0, y0, x1, y1, x2, y2;
|
||||
|
||||
gsk_path_builder_curve_to (builder, 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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user