contour: Fix some corner cases

Adding segments of rects or rounded rects was
not working right in cases where some of the
curves are trivial. Fix that.
This commit is contained in:
Matthias Clasen 2023-09-14 23:15:29 -04:00
parent 061637f4eb
commit de724b2a57

View File

@ -278,6 +278,106 @@ contour_get_closest_point (const GskContour *contour,
return FALSE;
}
static void
add_curve (GskCurve *curve,
GskPathBuilder *builder,
gboolean *emit_move_to)
{
if (*emit_move_to)
{
const graphene_point_t *s;
s = gsk_curve_get_start_point (curve);
gsk_path_builder_move_to (builder, s->x, s->y);
*emit_move_to = FALSE;
}
gsk_curve_builder_to (curve, builder);
}
typedef struct
{
GskPathBuilder *builder;
gboolean emit_move_to;
GskPathPoint start;
GskPathPoint end;
gsize idx;
} AddSegmentData;
static gboolean
add_segment_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
AddSegmentData *sd = data;
GskCurve c, c1, c2;
if (op == GSK_PATH_MOVE)
return TRUE;
sd->idx++;
if (sd->start.idx > sd->idx)
return TRUE;
if (sd->end.idx < sd->idx)
return FALSE;
if (op == GSK_PATH_CLOSE)
op = GSK_PATH_LINE;
gsk_curve_init_foreach (&c, op, pts, n_pts, weight);
if (sd->start.idx == sd->idx)
{
if (sd->end.idx == sd->idx)
{
gsk_curve_segment (&c, sd->start.t, sd->end.t, &c1);
add_curve (&c1, sd->builder, &sd->emit_move_to);
return FALSE;
}
else
{
gsk_curve_split (&c, sd->start.t, &c1, &c2);
add_curve (&c2, sd->builder, &sd->emit_move_to);
return TRUE;
}
}
else
{
if (sd->end.idx == sd->idx)
{
gsk_curve_split (&c, sd->end.t, &c1, &c2);
add_curve (&c1, sd->builder, &sd->emit_move_to);
return FALSE;
}
else
{
add_curve (&c, sd->builder, &sd->emit_move_to);
return TRUE;
}
}
}
static void
contour_add_segment (const GskContour *contour,
GskPathBuilder *builder,
gboolean emit_move_to,
const GskPathPoint *start,
const GskPathPoint *end)
{
AddSegmentData sd;
sd.builder = builder;
sd.emit_move_to = emit_move_to;
sd.start = *start;
sd.end = *end;
sd.idx = 0;
gsk_contour_foreach (contour, add_segment_cb, &sd);
}
/* }}} */
/* {{{ Default implementations */
@ -683,7 +783,6 @@ gsk_standard_contour_get_tangent (const GskContour *contour,
gsk_curve_init (&curve, self->ops[idx]);
gsk_curve_get_tangent (&curve, t, tangent);
if (direction == GSK_PATH_TO_START || direction == GSK_PATH_FROM_END)
graphene_vec2_negate (tangent, tangent);
}
@ -722,22 +821,6 @@ gsk_standard_contour_get_curvature (const GskContour *contour,
return gsk_curve_get_curvature (&curve, t, center);
}
static void
add_curve (GskCurve *curve,
GskPathBuilder *builder,
gboolean *emit_move_to)
{
if (*emit_move_to)
{
const graphene_point_t *s;
s = gsk_curve_get_start_point (curve);
gsk_path_builder_move_to (builder, s->x, s->y);
*emit_move_to = FALSE;
}
gsk_curve_builder_to (curve, builder);
}
static void
gsk_standard_contour_add_segment (const GskContour *contour,
GskPathBuilder *builder,
@ -749,6 +832,7 @@ gsk_standard_contour_add_segment (const GskContour *contour,
GskCurve c, c1, c2;
g_assert (start->idx < self->n_ops);
g_assert (end->idx < self->n_ops);
gsk_curve_init (&c, self->ops[start->idx]);
@ -1567,7 +1651,6 @@ struct _GskRectContour
float y;
float width;
float height;
float length;
};
static void
@ -1682,84 +1765,7 @@ gsk_rect_contour_get_winding (const GskContour *contour,
static gsize
gsk_rect_contour_get_n_ops (const GskContour *contour)
{
return 2;
}
static gboolean
gsk_rect_contour_closest_point (const GskRectContour *self,
const graphene_point_t *point,
float threshold,
float *out_distance,
float *out_offset)
{
graphene_point_t t, p;
float distance;
/* offset coords to be relative to rectangle */
t.x = point->x - self->x;
t.y = point->y - self->y;
if (self->width)
{
/* do unit square math */
t.x /= self->width;
/* move point onto the square */
t.x = CLAMP (t.x, 0.f, 1.f);
}
else
t.x = 0.f;
if (self->height)
{
t.y /= self->height;
t.y = CLAMP (t.y, 0.f, 1.f);
}
else
t.y = 0.f;
if (t.x > 0 && t.x < 1 && t.y > 0 && t.y < 1)
{
float diff = MIN (t.x, 1.f - t.x) * fabsf (self->width) - MIN (t.y, 1.f - t.y) * fabsf (self->height);
if (diff < 0.f)
t.x = ceilf (t.x - 0.5f); /* round 0.5 down */
else if (diff > 0.f)
t.y = roundf (t.y); /* round 0.5 up */
else
{
/* at least 2 points match, return the first one in the stroke */
if (t.y <= 1.f - t.y)
t.y = 0.f;
else if (1.f - t.x <= t.x)
t.x = 1.f;
else
t.y = 1.f;
}
}
/* Don't let -0 confuse us */
t.x = fabsf (t.x);
t.y = fabsf (t.y);
p = GRAPHENE_POINT_INIT (self->x + t.x * self->width,
self->y + t.y * self->height);
distance = graphene_point_distance (point, &p, NULL, NULL);
if (distance > threshold)
return FALSE;
*out_distance = distance;
if (t.y == 0)
*out_offset = t.x * fabsf (self->width);
else if (t.y == 1)
*out_offset = (2 - t.x) * fabsf (self->width) + fabsf (self->height);
else if (t.x == 1)
*out_offset = fabsf (self->width) + t.y * fabsf (self->height);
else
*out_offset = 2 * fabsf (self->width) + (2 - t.y) * fabsf (self->height);
return TRUE;
return 5;
}
static gboolean
@ -1769,149 +1775,7 @@ gsk_rect_contour_get_closest_point (const GskContour *contour,
GskPathPoint *result,
float *out_dist)
{
const GskRectContour *self = (const GskRectContour *) contour;
float distance;
if (gsk_rect_contour_closest_point (self, point, threshold, out_dist, &distance))
{
result->idx = 1;
if (self->length == 0)
result->t = 0;
else
result->t = distance / self->length;
return TRUE;
}
return FALSE;
}
static void
set_tangent (float xs,
float ys,
float xe,
float ye,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
if (direction == GSK_PATH_TO_START || direction == GSK_PATH_FROM_START)
graphene_vec2_init (tangent, xs, ys);
else
graphene_vec2_init (tangent, xe, ye);
if (direction == GSK_PATH_TO_START || direction == GSK_PATH_FROM_END)
graphene_vec2_negate (tangent, tangent);
}
static void
gsk_rect_contour_pos_tangent (const GskRectContour *self,
float distance,
GskPathDirection direction,
graphene_point_t *pos,
graphene_vec2_t *tangent)
{
if (distance == 0)
{
if (pos)
*pos = GRAPHENE_POINT_INIT (self->x, self->y);
if (tangent)
set_tangent (0.f, - copysignf (1.f, self->height),
copysignf (1.f, self->width), 0.f,
direction, tangent);
return;
}
if (distance < fabsf (self->width))
{
if (pos)
*pos = GRAPHENE_POINT_INIT (self->x + copysignf (distance, self->width), self->y);
if (tangent)
set_tangent (copysignf (1.f, self->width), 0.f,
copysignf (1.f, self->width), 0.f,
direction, tangent);
return;
}
distance -= fabsf (self->width);
if (distance == 0)
{
if (pos)
*pos = GRAPHENE_POINT_INIT (self->x + self->width, self->y);
if (tangent)
set_tangent (copysignf (1.f, self->width), 0.f,
0.f, copysignf (1.f, self->height),
direction, tangent);
return;
}
if (distance < fabsf (self->height))
{
if (pos)
*pos = GRAPHENE_POINT_INIT (self->x + self->width, self->y + copysignf (distance, self->height));
if (tangent)
set_tangent (0.f, copysignf (1.f, self->height),
0.f, copysignf (1.f, self->height),
direction, tangent);
return;
}
distance -= fabs (self->height);
if (distance == 0)
{
if (pos)
*pos = GRAPHENE_POINT_INIT (self->x + self->width, self->y + self->height);
if (tangent)
set_tangent (0.f, copysignf (1.f, self->height),
- copysignf (1.f, self->width), 0.f,
direction, tangent);
return;
}
if (distance < fabsf (self->width))
{
if (pos)
*pos = GRAPHENE_POINT_INIT (self->x + self->width - copysignf (distance, self->width), self->y + self->height);
if (tangent)
set_tangent (- copysignf (1.f, self->width), 0.f,
- copysignf (1.f, self->width), 0.f,
direction, tangent);
return;
}
distance -= fabsf (self->width);
if (distance == 0)
{
if (pos)
*pos = GRAPHENE_POINT_INIT (self->x, self->y + self->height);
if (tangent)
set_tangent (- copysignf (1.f, self->width), 0.f,
0.f, - copysignf (1.f, self->height),
direction, tangent);
return;
}
if (distance < fabsf (self->height))
{
if (pos)
*pos = GRAPHENE_POINT_INIT (self->x, self->y + self->height - copysignf (distance, self->height));
if (tangent)
set_tangent (0.f, - copysignf (1.f, self->height),
0.f, - copysignf (1.f, self->height),
direction, tangent);
return;
}
if (pos)
*pos = GRAPHENE_POINT_INIT (self->x, self->y);
if (tangent)
set_tangent (0.f, - copysignf (1.f, self->height),
copysignf (1.f, self->width), 0.f,
direction, tangent);
return contour_get_closest_point (contour, point, threshold, result, out_dist);
}
static void
@ -1919,9 +1783,10 @@ gsk_rect_contour_get_position (const GskContour *contour,
const GskPathPoint *point,
graphene_point_t *position)
{
const GskRectContour *self = (const GskRectContour *) contour;
GskCurve curve;
gsk_rect_contour_pos_tangent (self, point->t * self->length, GSK_PATH_TO_END, position, NULL);
contour_init_curve (contour, point->idx, &curve);
gsk_curve_get_point (&curve, point->t, position);
}
static void
@ -1930,9 +1795,33 @@ gsk_rect_contour_get_tangent (const GskContour *contour,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
const GskRectContour *self = (const GskRectContour *) contour;
gsize idx = point->idx;
float t = point->t;
GskCurve curve;
gsk_rect_contour_pos_tangent (self, point->t * self->length, direction, NULL, tangent);
if (t == 0 &&
(direction == GSK_PATH_FROM_START || direction == GSK_PATH_TO_START))
{
if (idx == 1)
idx = 4;
else
idx--;
t = 1;
}
else if (t == 1 &&
(direction == GSK_PATH_FROM_END || direction == GSK_PATH_TO_END))
{
if (idx == 4)
idx = 1;
else
idx++;
t = 0;
}
contour_init_curve (contour, idx, &curve);
gsk_curve_get_tangent (&curve, point->t, tangent);
if (direction == GSK_PATH_TO_START || direction == GSK_PATH_FROM_END)
graphene_vec2_negate (tangent, tangent);
}
static float
@ -1948,68 +1837,10 @@ static void
gsk_rect_contour_add_segment (const GskContour *contour,
GskPathBuilder *builder,
gboolean emit_move_to,
const GskPathPoint *start_point,
const GskPathPoint *end_point)
const GskPathPoint *start,
const GskPathPoint *end)
{
const GskRectContour *self = (const GskRectContour *) contour;
float w = fabsf (self->width);
float h = fabsf (self->height);
float start = start_point->t * self->length;
float end = end_point->t * self->length;
if (start < w)
{
if (emit_move_to)
gsk_path_builder_move_to (builder, self->x + start * (w / self->width), self->y);
if (end <= w)
{
gsk_path_builder_line_to (builder, self->x + end * (w / self->width), self->y);
return;
}
gsk_path_builder_line_to (builder, self->x + self->width, self->y);
}
start -= w;
end -= w;
if (start < h)
{
if (start >= 0 && emit_move_to)
gsk_path_builder_move_to (builder, self->x + self->width, self->y + start * (h / self->height));
if (end <= h)
{
gsk_path_builder_line_to (builder, self->x + self->width, self->y + end * (h / self->height));
return;
}
gsk_path_builder_line_to (builder, self->x + self->width, self->y + self->height);
}
start -= h;
end -= h;
if (start < w)
{
if (start >= 0 && emit_move_to)
gsk_path_builder_move_to (builder, self->x + (w - start) * (w / self->width), self->y + self->height);
if (end <= w)
{
gsk_path_builder_line_to (builder, self->x + (w - end) * (w / self->width), self->y + self->height);
return;
}
gsk_path_builder_line_to (builder, self->x, self->y + self->height);
}
start -= w;
end -= w;
if (start < h)
{
if (start >= 0 && emit_move_to)
gsk_path_builder_move_to (builder, self->x, self->y + (h - start) * (h / self->height));
if (end <= h)
{
gsk_path_builder_line_to (builder, self->x, self->y + (h - end) * (h / self->height));
return;
}
gsk_path_builder_line_to (builder, self->x, self->y);
}
contour_add_segment (contour, builder, emit_move_to, start, end);
}
static gpointer
@ -2019,7 +1850,7 @@ gsk_rect_contour_init_measure (const GskContour *contour,
{
const GskRectContour *self = (const GskRectContour *) contour;
*out_length = self->length;
*out_length = 2 * (fabsf (self->width) + fabsf (self->height));
return NULL;
}
@ -2038,12 +1869,35 @@ gsk_rect_contour_get_point (const GskContour *contour,
{
const GskRectContour *self = (const GskRectContour *) contour;
result->idx = 1;
if (self->length == 0)
result->t = 0;
else
result->t = CLAMP (distance / self->length, 0, 1);
g_assert (0 <= result->t && result->t <= 1);
if (distance <= fabsf (self->width))
{
result->idx = 1;
result->t = distance / fabsf (self->width);
return;
}
distance -= fabsf (self->width);
if (distance <= fabs (self->height))
{
result->idx = 2;
result->t = distance / fabsf (self->height);
return;
}
distance -= fabs (self->height);
if (distance <= fabsf (self->width))
{
result->idx = 3;
result->t = distance / fabsf (self->width);
return;
}
distance -= fabsf (self->width);
result->idx = 4;
result->t = CLAMP (distance / fabsf (self->height), 0, 1);
}
static float
@ -2053,7 +1907,23 @@ gsk_rect_contour_get_distance (const GskContour *contour,
{
const GskRectContour *self = (const GskRectContour *) contour;
return point->t * self->length;
switch (point->idx)
{
case 1:
return point->t * fabsf (self->width);
case 2:
return fabsf (self->width) + point->t * fabsf (self->height);
case 3:
return (1 + point->t) * fabsf (self->width) + fabsf (self->height);
case 4:
return 2 * fabsf (self->width) + (1 + point->t) * fabsf (self->height);
default:
g_assert_not_reached ();
}
}
static const GskContourClass GSK_RECT_CONTOUR_CLASS =
@ -2094,7 +1964,6 @@ gsk_rect_contour_new (const graphene_rect_t *rect)
self->y = rect->origin.y;
self->width = rect->size.width;
self->height = rect->size.height;
self->length = 2 * (fabsf (self->width) + fabsf (self->height));
return (GskContour *) self;
}
@ -2284,6 +2153,8 @@ gsk_rounded_rect_contour_get_tangent (const GskContour *contour,
contour_init_curve (contour, point->idx, &curve);
gsk_curve_get_tangent (&curve, point->t, tangent);
if (direction == GSK_PATH_TO_START || direction == GSK_PATH_FROM_END)
graphene_vec2_negate (tangent, tangent);
}
static float
@ -2305,15 +2176,7 @@ gsk_rounded_rect_contour_add_segment (const GskContour *contour,
const GskPathPoint *start,
const GskPathPoint *end)
{
GskPath *path;
const GskContour *std;
path = convert_to_standard_contour (contour);
std = gsk_path_get_contour (path, 0);
gsk_standard_contour_add_segment (std, builder, emit_move_to, start, end);
gsk_path_unref (path);
contour_add_segment (contour, builder, emit_move_to, start, end);
}
typedef struct