Merge branch 'circle-contour' into 'main'

path: Add a circle contour

See merge request GNOME/gtk!6345
This commit is contained in:
Matthias Clasen 2023-08-27 11:12:51 +00:00
commit 2297a353b8
5 changed files with 695 additions and 94 deletions

View File

@ -103,23 +103,141 @@ struct _GskContourClass
static void
_g_string_append_double (GString *string,
const char *prefix,
double d)
{
char buf[G_ASCII_DTOSTR_BUF_SIZE];
g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, d);
g_string_append (string, prefix);
g_string_append (string, buf);
}
static void
_g_string_append_point (GString *string,
const char *prefix,
const graphene_point_t *pt)
{
_g_string_append_double (string, pt->x);
g_string_append_c (string, ' ');
_g_string_append_double (string, pt->y);
_g_string_append_double (string, prefix, pt->x);
_g_string_append_double (string, " ", pt->y);
}
static gboolean
add_segment (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskPathBuilder *builder = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builder,
pts[1].x, pts[1].y,
pts[2].x, pts[2].y);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builder,
pts[1].x, pts[1].y,
pts[2].x, pts[2].y,
pts[3].x, pts[3].y);
break;
case GSK_PATH_CONIC:
gsk_path_builder_conic_to (builder,
pts[1].x, pts[1].y,
pts[2].x, pts[2].y,
weight);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builder);
break;
default:
g_assert_not_reached ();
}
return TRUE;
}
static GskPath *
convert_to_standard_contour (const GskContour *contour)
{
GskPathBuilder *builder;
builder = gsk_path_builder_new ();
gsk_contour_foreach (contour, 0.5, add_segment, builder);
return gsk_path_builder_free_to_path (builder);
}
/* }}} */
/* {{{ Default implementations */
static gsize
gsk_contour_get_size_default (const GskContour *contour)
{
return contour->klass->struct_size;
}
static gboolean
foreach_print (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GString *string = data;
switch (op)
{
case GSK_PATH_MOVE:
_g_string_append_point (string, "M ", &pts[0]);
break;
case GSK_PATH_CLOSE:
g_string_append (string, " Z");
break;
case GSK_PATH_LINE:
_g_string_append_point (string, " L ", &pts[1]);
break;
case GSK_PATH_QUAD:
_g_string_append_point (string, " Q ", &pts[1]);
_g_string_append_point (string, ", ", &pts[2]);
break;
case GSK_PATH_CUBIC:
_g_string_append_point (string, " C ", &pts[1]);
_g_string_append_point (string, ", ", &pts[2]);
_g_string_append_point (string, ", ", &pts[3]);
break;
case GSK_PATH_CONIC:
_g_string_append_point (string, " O ", &pts[1]);
_g_string_append_point (string, ", ", &pts[2]);
_g_string_append_double (string, ", ", weight);
break;
default:
g_assert_not_reached ();
}
return TRUE;
}
static void
gsk_contour_print_default (const GskContour *contour,
GString *string)
{
gsk_contour_foreach (contour, 0.5, foreach_print, string);
}
/* }}} */
/* {{{ Standard */
@ -257,65 +375,6 @@ gsk_standard_contour_get_flags (const GskContour *contour)
return self->flags;
}
static void
gsk_standard_contour_print (const GskContour *contour,
GString *string)
{
const GskStandardContour *self = (const GskStandardContour *) contour;
gsize i;
for (i = 0; i < self->n_ops; i ++)
{
const graphene_point_t *pt = gsk_pathop_points (self->ops[i]);
switch (gsk_pathop_op (self->ops[i]))
{
case GSK_PATH_MOVE:
g_string_append (string, "M ");
_g_string_append_point (string, &pt[0]);
break;
case GSK_PATH_CLOSE:
g_string_append (string, " Z");
break;
case GSK_PATH_LINE:
g_string_append (string, " L ");
_g_string_append_point (string, &pt[1]);
break;
case GSK_PATH_QUAD:
g_string_append (string, " Q ");
_g_string_append_point (string, &pt[1]);
g_string_append (string, ", ");
_g_string_append_point (string, &pt[2]);
break;
case GSK_PATH_CUBIC:
g_string_append (string, " C ");
_g_string_append_point (string, &pt[1]);
g_string_append (string, ", ");
_g_string_append_point (string, &pt[2]);
g_string_append (string, ", ");
_g_string_append_point (string, &pt[3]);
break;
case GSK_PATH_CONIC:
g_string_append (string, " O ");
_g_string_append_point (string, &pt[1]);
g_string_append (string, ", ");
_g_string_append_point (string, &pt[3]);
g_string_append (string, ", ");
_g_string_append_double (string, pt[2].x);
break;
default:
g_assert_not_reached();
return;
}
}
}
static gboolean
gsk_standard_contour_get_bounds (const GskContour *contour,
GskBoundingBox *bounds)
@ -956,7 +1015,7 @@ static const GskContourClass GSK_STANDARD_CONTOUR_CLASS =
gsk_standard_contour_copy,
gsk_standard_contour_get_size,
gsk_standard_contour_get_flags,
gsk_standard_contour_print,
gsk_contour_print_default,
gsk_standard_contour_get_bounds,
gsk_standard_contour_get_stroke_bounds,
gsk_standard_contour_get_start_end,
@ -1025,6 +1084,422 @@ gsk_standard_contour_new (GskPathFlags flags,
return contour;
}
/* }}} */
/* {{{ Circle */
typedef struct _GskCircleContour GskCircleContour;
struct _GskCircleContour
{
GskContour contour;
graphene_point_t center;
float radius;
gboolean ccw;
};
static void
gsk_circle_contour_copy (const GskContour *contour,
GskContour *dest)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
GskCircleContour *target = (GskCircleContour *) dest;
*target = *self;
}
static GskPathFlags
gsk_circle_contour_get_flags (const GskContour *contour)
{
return GSK_PATH_CLOSED;
}
static gboolean
gsk_circle_contour_get_bounds (const GskContour *contour,
GskBoundingBox *bounds)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
gsk_bounding_box_init (bounds,
&GRAPHENE_POINT_INIT (self->center.x - self->radius,
self->center.y - self->radius),
&GRAPHENE_POINT_INIT (self->center.x + self->radius,
self->center.y + self->radius));
return TRUE;
}
static gboolean
gsk_circle_contour_get_stroke_bounds (const GskContour *contour,
const GskStroke *stroke,
GskBoundingBox *bounds)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
gsk_bounding_box_init (bounds,
&GRAPHENE_POINT_INIT (self->center.x - self->radius - stroke->line_width/2,
self->center.y - self->radius - stroke->line_width/2),
&GRAPHENE_POINT_INIT (self->center.x + self->radius + stroke->line_width/2,
self->center.y + self->radius + stroke->line_width/2));
return TRUE;
}
static void
gsk_circle_contour_get_start_end (const GskContour *contour,
graphene_point_t *start,
graphene_point_t *end)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
if (start)
*start = GRAPHENE_POINT_INIT (self->center.x + self->radius, self->center.y);
if (end)
*end = GRAPHENE_POINT_INIT (self->center.x + self->radius, self->center.y);
}
static gboolean
gsk_circle_contour_foreach (const GskContour *contour,
float tolerance,
GskPathForeachFunc func,
gpointer user_data)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
float rx, ry;
rx = ry = self->radius;
if (self->ccw)
ry = - self->radius;
if (!func (GSK_PATH_MOVE,
(const graphene_point_t[1]) {
GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y),
},
1, 0.f, user_data))
return FALSE;
if (!func (GSK_PATH_CONIC,
(const graphene_point_t[3]) {
GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y),
GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y + ry),
GRAPHENE_POINT_INIT (self->center.x, self->center.y + ry),
},
3, M_SQRT1_2, user_data))
return FALSE;
if (!func (GSK_PATH_CONIC,
(const graphene_point_t[3]) {
GRAPHENE_POINT_INIT (self->center.x, self->center.y + ry),
GRAPHENE_POINT_INIT (self->center.x - rx, self->center.y + ry),
GRAPHENE_POINT_INIT (self->center.x - rx, self->center.y),
},
3, M_SQRT1_2, user_data))
return FALSE;
if (!func (GSK_PATH_CONIC,
(const graphene_point_t[3]) {
GRAPHENE_POINT_INIT (self->center.x - rx, self->center.y),
GRAPHENE_POINT_INIT (self->center.x - rx, self->center.y - ry),
GRAPHENE_POINT_INIT (self->center.x, self->center.y - ry),
},
3, M_SQRT1_2, user_data))
return FALSE;
if (!func (GSK_PATH_CONIC,
(const graphene_point_t[3]) {
GRAPHENE_POINT_INIT (self->center.x, self->center.y - ry),
GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y - ry),
GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y),
},
3, M_SQRT1_2, user_data))
return FALSE;
if (!func (GSK_PATH_CLOSE,
(const graphene_point_t[2]) {
GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y),
GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y),
},
2, 0.f, user_data))
return FALSE;
return TRUE;
}
static GskContour *
gsk_circle_contour_reverse (const GskContour *contour)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
GskCircleContour *copy;
copy = g_new0 (GskCircleContour, 1);
gsk_circle_contour_copy (contour, (GskContour *)copy);
copy->ccw = !self->ccw;
return (GskContour *)copy;
}
static int
gsk_circle_contour_get_winding (const GskContour *contour,
const graphene_point_t *point)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
if (graphene_point_distance (point, &self->center, NULL, NULL) >= self->radius)
return 0;
if (self->ccw)
return -1;
else
return 1;
}
static gsize
gsk_circle_contour_get_n_ops (const GskContour *contour)
{
/* Not related to how many curves foreach produces.
* GskPath assumes that the start- and endpoints
* of a contour are { x, 1, 0 } and { x, n_ops - 1, 1 }.
*
* The circle contour uses a single 'segment' in path
* points, with a t that ranges from 0 to 1 to cover
* the angles from 0 to 360 (or 360 to 0 in the ccw
* case).
*/
return 2;
}
static gboolean
gsk_circle_contour_get_closest_point (const GskContour *contour,
const graphene_point_t *point,
float threshold,
GskRealPathPoint *result,
float *out_dist)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
float dist, angle, t;
dist = fabsf (graphene_point_distance (&self->center, point, NULL, NULL) - self->radius);
if (dist > threshold)
return FALSE;
angle = RAD_TO_DEG (atan2f (point->y - self->center.y, point->x - self->center.x));
if (angle < 0)
angle = 360 - angle;
t = CLAMP (angle / 360, 0, 1);
if (self->ccw)
t = 1 - t;
result->idx = 1;
result->t = t;
return TRUE;
}
#define GSK_CIRCLE_POINT_INIT(self, angle) \
GRAPHENE_POINT_INIT ((self)->center.x + cosf (DEG_TO_RAD (angle)) * self->radius, \
(self)->center.y + sinf (DEG_TO_RAD (angle)) * self->radius)
static void
gsk_circle_contour_get_position (const GskContour *contour,
GskRealPathPoint *point,
graphene_point_t *position)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
float t;
t = point->t;
if (self->ccw)
t = 1 - t;
if (t == 0 || t == 1)
*position = GRAPHENE_POINT_INIT (self->center.x + self->radius, self->center.y);
else
*position = GSK_CIRCLE_POINT_INIT (self, t * 360);
}
static void
gsk_circle_contour_get_tangent (const GskContour *contour,
GskRealPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
graphene_point_t p;
gsk_circle_contour_get_position (contour, point, &p);
graphene_vec2_init (tangent, p.y - self->center.y, - p.x + self->center.x);
graphene_vec2_normalize (tangent, tangent);
}
static float
gsk_circle_contour_get_curvature (const GskContour *contour,
GskRealPathPoint *point,
GskPathDirection direction,
graphene_point_t *center)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
if (center)
*center = self->center;
return 1 / self->radius;
}
static void
gsk_circle_contour_add_segment (const GskContour *contour,
GskPathBuilder *builder,
gboolean emit_move_to,
GskRealPathPoint *start,
GskRealPathPoint *end)
{
GskPath *path;
graphene_point_t p;
GskRealPathPoint start2, end2;
const GskContour *std;
float dist;
/* This is a cheesy way of doing things: convert to a standard contour,
* and translate the path points from circle to standard. We just have
* to be careful to tell start- and endpoint apart.
*/
path = convert_to_standard_contour (contour);
std = gsk_path_get_contour (path, 0);
start2.contour = 0;
if (start->idx == 1 && start->t == 0)
{
start2.idx = 1;
start2.t = 0;
}
else
{
gsk_circle_contour_get_position (contour, start, &p);
gsk_standard_contour_get_closest_point (std, &p, INFINITY, &start2, &dist);
}
end2.contour = 0;
if (end->idx == 1 && end->t == 1)
{
end2.idx = 4;
end2.t = 1;
}
else
{
gsk_circle_contour_get_position (contour, end, &p);
gsk_standard_contour_get_closest_point (std, &p, INFINITY, &end2, &dist);
}
gsk_standard_contour_add_segment (std, builder, emit_move_to, &start2, &end2);
gsk_path_unref (path);
}
static gpointer
gsk_circle_contour_init_measure (const GskContour *contour,
float tolerance,
float *out_length)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
*out_length = 2 * M_PI * self->radius;
return NULL;
}
static void
gsk_circle_contour_free_measure (const GskContour *contour,
gpointer data)
{
}
static void
gsk_circle_contour_get_point (const GskContour *contour,
gpointer measure_data,
float distance,
GskRealPathPoint *result)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
float t;
t = distance / (2 * M_PI * self->radius);
if (self->ccw)
t = 1 - t;
result->idx = 1;
result->t = t;
}
static float
gsk_circle_contour_get_distance (const GskContour *contour,
GskRealPathPoint *point,
gpointer measure_data)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
float t;
t = point->t;
if (self->ccw)
t = 1 - t;
return 2 * M_PI * self->radius * t;
}
static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS =
{
sizeof (GskCircleContour),
"GskCircleContour",
gsk_circle_contour_copy,
gsk_contour_get_size_default,
gsk_circle_contour_get_flags,
gsk_contour_print_default,
gsk_circle_contour_get_bounds,
gsk_circle_contour_get_stroke_bounds,
gsk_circle_contour_get_start_end,
gsk_circle_contour_foreach,
gsk_circle_contour_reverse,
gsk_circle_contour_get_winding,
gsk_circle_contour_get_n_ops,
gsk_circle_contour_get_closest_point,
gsk_circle_contour_get_position,
gsk_circle_contour_get_tangent,
gsk_circle_contour_get_curvature,
gsk_circle_contour_add_segment,
gsk_circle_contour_init_measure,
gsk_circle_contour_free_measure,
gsk_circle_contour_get_point,
gsk_circle_contour_get_distance,
};
GskContour *
gsk_circle_contour_new (const graphene_point_t *center,
float radius)
{
GskCircleContour *self;
self = g_new0 (GskCircleContour, 1);
self->contour.klass = &GSK_CIRCLE_CONTOUR_CLASS;
self->contour.klass = &GSK_CIRCLE_CONTOUR_CLASS;
self->center = *center;
self->radius = radius;
self->ccw = FALSE;
return (GskContour *) self;
}
/* }}} */
/* {{{ API */

View File

@ -34,6 +34,9 @@ GskContour * gsk_standard_contour_new (GskPathFlags
gsize n_ops,
gssize offset);
GskContour * gsk_circle_contour_new (const graphene_point_t *center,
float radius);
void gsk_contour_copy (GskContour * dest,
const GskContour *src);
GskContour * gsk_contour_dup (const GskContour *src);

View File

@ -953,6 +953,78 @@ parse_command (const char **p,
return FALSE;
}
static gboolean
parse_string (const char **p,
const char *s)
{
int len = strlen (s);
if (strncmp (*p, s, len) != 0)
return FALSE;
(*p) += len;
return TRUE;
}
static gboolean
parse_circle (const char **p,
double *cx,
double *cy,
double *r)
{
const char *o = *p;
double x0, y0, x1, y1, x2, y2, x3, y3;
double x4, y4, x5, y5, x6, y6, x7, y7;
double x8, y8, w0, w1, w2, w3;
double xx, yy;
if (parse_coordinate_pair (p, &x0, &y0) &&
parse_string (p, "O") &&
parse_coordinate_pair (p, &x1, &y1) &&
parse_coordinate_pair (p, &x2, &y2) &&
parse_nonnegative_number (p, &w0) &&
parse_string (p, "O") &&
parse_coordinate_pair (p, &x3, &y3) &&
parse_coordinate_pair (p, &x4, &y4) &&
parse_nonnegative_number (p, &w1) &&
parse_string (p, "O") &&
parse_coordinate_pair (p, &x5, &y5) &&
parse_coordinate_pair (p, &x6, &y6) &&
parse_nonnegative_number (p, &w2) &&
parse_string (p, "O") &&
parse_coordinate_pair (p, &x7, &y7) &&
parse_coordinate_pair (p, &x8, &y8) &&
parse_nonnegative_number (p, &w3) &&
parse_string (p, "Z"))
{
xx = (x0 + x4) / 2;
yy = (y2 + y6) / 2;
#define NEAR(x, y) (fabs ((x) - (y)) < 0.001)
if (NEAR (x0, x1) && NEAR (x0, x8) && NEAR (x0, x7) &&
NEAR (x2, x6) && NEAR (x3, x4) && NEAR (x4, x5) &&
NEAR (y5, y6) && NEAR (y6, y7) && NEAR (y4, y8) &&
NEAR (y8, y0) && NEAR (y3, y2) && NEAR (y2, y1) &&
NEAR (x2, xx) && NEAR (yy, y4) &&
NEAR (w0, M_SQRT1_2) && NEAR (w1, M_SQRT1_2) &&
NEAR (w2, M_SQRT1_2) && NEAR (w3, M_SQRT1_2) &&
x1 > x2 && x2 > x3 && y3 > y4 && y4 > y5)
{
*cx = xx;
*cy = yy;
*r = x0 - xx;
skip_whitespace (p);
return TRUE;
}
#undef NEAR
}
*p = o;
return FALSE;
}
/**
* gsk_path_parse:
* @string: a string
@ -1037,9 +1109,20 @@ gsk_path_parse (const char *string)
case 'M':
case 'm':
{
double x1, y1;
double x1, y1, r;
if (parse_coordinate_pair (&p, &x1, &y1))
if (parse_circle (&p, &x1, &y1, &r))
{
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (x1, y1), r);
if (_strchr ("zZX", prev_cmd))
{
path_x = x1 + r;
path_y = y1;
}
x = x1 + r;
y = y1;
}
else if (parse_coordinate_pair (&p, &x1, &y1))
{
if (cmd == 'm')
{

View File

@ -560,34 +560,11 @@ gsk_path_builder_add_circle (GskPathBuilder *self,
const graphene_point_t *center,
float radius)
{
graphene_point_t current;
g_return_if_fail (self != NULL);
g_return_if_fail (center != NULL);
g_return_if_fail (radius > 0);
current = self->current_point;
gsk_path_builder_move_to (self, center->x + radius, center->y);
// bottom right quarter
gsk_path_builder_arc_to (self,
center->x + radius, center->y + radius,
center->x, center->y + radius);
// bottom left quarter
gsk_path_builder_arc_to (self,
center->x - radius, center->y + radius,
center->x - radius, center->y);
// top left quarter
gsk_path_builder_arc_to (self,
center->x - radius, center->y - radius,
center->x, center->y - radius);
// top right quarter
gsk_path_builder_arc_to (self,
center->x + radius, center->y - radius,
center->x + radius, center->y);
// done
gsk_path_builder_close (self);
self->current_point = current;
gsk_path_builder_add_contour (self, gsk_circle_contour_new (center, radius));
}
/**

View File

@ -813,9 +813,10 @@ static void
test_circle (void)
{
GskPathBuilder *builder;
GskPath *path;
GskPathMeasure *measure;
float length;
GskPath *path, *path1, *path2, *path3, *path4, *path5, *path6;
GskPathMeasure *measure, *measure1, *measure2, *measure3;
float length, length1, length2, length3;
GskPathPoint point0, point1;
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 1);
@ -826,8 +827,70 @@ test_circle (void)
g_assert_cmpfloat_with_epsilon (length, 2 * M_PI, 0.001);
gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (1, 1), INFINITY, &point0);
gsk_path_get_closest_point (path, &GRAPHENE_POINT_INIT (-1, 1), INFINITY, &point1);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &point0, &point1);
path1 = gsk_path_builder_free_to_path (builder);
measure1 = gsk_path_measure_new (path1);
length1 = gsk_path_measure_get_length (measure1);
g_assert_cmpfloat_with_epsilon (length1, 2 * M_PI * 0.25, 0.001);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, path, &point1, &point0);
path2 = gsk_path_builder_free_to_path (builder);
measure2 = gsk_path_measure_new (path2);
length2 = gsk_path_measure_get_length (measure2);
g_assert_cmpfloat_with_epsilon (length2, 2 * M_PI * 0.75, 0.001);
builder = gsk_path_builder_new ();
gsk_path_builder_add_reverse_path (builder, path);
path3 = gsk_path_builder_free_to_path (builder);
measure3 = gsk_path_measure_new (path3);
length3 = gsk_path_measure_get_length (measure3);
g_assert_cmpfloat_with_epsilon (length3, 2 * M_PI, 0.001);
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (0, 0), GSK_FILL_RULE_WINDING));
g_assert_true (gsk_path_in_fill (path, &GRAPHENE_POINT_INIT (0, 0), GSK_FILL_RULE_EVEN_ODD));
g_assert_true (gsk_path_in_fill (path3, &GRAPHENE_POINT_INIT (0, 0), GSK_FILL_RULE_WINDING));
g_assert_true (gsk_path_in_fill (path3, &GRAPHENE_POINT_INIT (0, 0), GSK_FILL_RULE_EVEN_ODD));
builder = gsk_path_builder_new ();
gsk_path_builder_add_path (builder, path);
gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (-2, -2, 4, 4));
path4 = gsk_path_builder_free_to_path (builder);
g_assert_true (gsk_path_in_fill (path4, &GRAPHENE_POINT_INIT (0, 0), GSK_FILL_RULE_WINDING));
g_assert_false (gsk_path_in_fill (path4, &GRAPHENE_POINT_INIT (0, 0), GSK_FILL_RULE_EVEN_ODD));
path5 = gsk_path_parse ("M 2 0 O 2 2 0 2 0.707 O -2 2 -2 0 0.707 O -2 -2 0 -2 0.707 O 2 -2 2 0 0.707 Z");
builder = gsk_path_builder_new ();
gsk_path_builder_add_path (builder, path);
gsk_path_builder_add_path (builder, path5);
path6 = gsk_path_builder_free_to_path (builder);
g_assert_true (gsk_path_in_fill (path6, &GRAPHENE_POINT_INIT (0, 0), GSK_FILL_RULE_WINDING));
g_assert_false (gsk_path_in_fill (path6, &GRAPHENE_POINT_INIT (0, 0), GSK_FILL_RULE_EVEN_ODD));
gsk_path_measure_unref (measure);
gsk_path_measure_unref (measure1);
gsk_path_measure_unref (measure2);
gsk_path_measure_unref (measure3);
gsk_path_unref (path);
gsk_path_unref (path1);
gsk_path_unref (path2);
gsk_path_unref (path3);
gsk_path_unref (path4);
gsk_path_unref (path5);
gsk_path_unref (path6);
}
static void