From ab7d9b0ef86246a02a98cda86aa629f851c567fc Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sat, 8 Jul 2023 10:48:58 -0400 Subject: [PATCH] Add GskPathMeasure An object to do measuring operations on paths - determining their length, cutting off subpaths, things like that. --- gsk/gsk.h | 2 + gsk/gskcontour.c | 1423 ++++++++++++++++++++++++++++++++++++- gsk/gskcontourprivate.h | 34 + gsk/gskcurve.c | 180 +++++ gsk/gskcurveprivate.h | 4 + gsk/gskpathbuilder.h | 6 + gsk/gskpathmeasure.c | 489 +++++++++++++ gsk/gskpathmeasure.h | 73 ++ gsk/gskpathpoint.c | 195 +++++ gsk/gskpathpoint.h | 43 ++ gsk/gskpathpointprivate.h | 16 + gsk/gsktypes.h | 2 + gsk/meson.build | 4 + 13 files changed, 2468 insertions(+), 3 deletions(-) create mode 100644 gsk/gskpathmeasure.c create mode 100644 gsk/gskpathmeasure.h create mode 100644 gsk/gskpathpoint.c create mode 100644 gsk/gskpathpoint.h create mode 100644 gsk/gskpathpointprivate.h diff --git a/gsk/gsk.h b/gsk/gsk.h index adaf6435e7..48dfbeb979 100644 --- a/gsk/gsk.h +++ b/gsk/gsk.h @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/gsk/gskcontour.c b/gsk/gskcontour.c index 63ef6576f0..b5e76be4b9 100644 --- a/gsk/gskcontour.c +++ b/gsk/gskcontour.c @@ -54,6 +54,39 @@ struct _GskContourClass GskPathForeachFunc func, gpointer user_data); GskContour * (* reverse) (const GskContour *contour); + gpointer (* init_measure) (const GskContour *contour, + float tolerance, + float *out_length); + void (* free_measure) (const GskContour *contour, + gpointer measure_data); + void (* get_point) (const GskContour *contour, + gpointer measure_data, + float distance, + GskPathDirection direction, + graphene_point_t *pos, + graphene_vec2_t *tangent); + float (* get_curvature) (const GskContour *contour, + gpointer measure_data, + float distance, + graphene_point_t *center); + gboolean (* get_closest_point) (const GskContour *contour, + gpointer measure_data, + float tolerance, + const graphene_point_t *point, + float threshold, + float *out_offset, + graphene_point_t *out_pos, + float *out_distance, + graphene_vec2_t *out_tangent); + void (* add_segment) (const GskContour *contour, + GskPathBuilder *builder, + gpointer measure_data, + gboolean emit_move_to, + float start, + float end); + int (* get_winding) (const GskContour *contour, + gpointer measure_data, + const graphene_point_t *point); }; static gsize @@ -83,6 +116,39 @@ _g_string_append_point (GString *string, _g_string_append_double (string, pt->y); } +static void +gsk_find_point_on_line (const graphene_point_t *a, + const graphene_point_t *b, + const graphene_point_t *p, + float *offset, + graphene_point_t *pos) +{ + graphene_vec2_t n; + graphene_vec2_t ap; + float t; + + graphene_vec2_init (&n, b->x - a->x, b->y - a->y); + graphene_vec2_init (&ap, p->x - a->x, p->y - a->y); + + t = graphene_vec2_dot (&ap, &n) / graphene_vec2_dot (&n, &n); + + if (t <= 0) + { + *pos = *a; + *offset = 0; + } + else if (t >= 1) + { + *pos = *b; + *offset = 1; + } + else + { + graphene_point_interpolate (a, b, t, pos); + *offset = t; + } +} + /* }}} */ /* {{{ Standard */ @@ -115,7 +181,7 @@ gsk_standard_contour_init (GskContour *contour, gsize n_points, const gskpathop *ops, gsize n_ops, - gssize offset); + ptrdiff_t offset); static void gsk_standard_contour_copy (const GskContour *contour, @@ -334,6 +400,536 @@ gsk_standard_contour_get_start_end (const GskContour *contour, *end = self->points[self->n_points - 1]; } +typedef struct +{ + float start; + float end; + float start_progress; + float end_progress; + GskCurveLineReason reason; + graphene_point_t start_point; + graphene_point_t end_point; + gsize op; +} GskStandardContourMeasure; + +typedef struct +{ + GArray *array; + GskStandardContourMeasure measure; +} LengthDecompose; + +static void +gsk_standard_contour_measure_get_point_at (GskStandardContourMeasure *measure, + float progress, + graphene_point_t *out_point) +{ + graphene_point_interpolate (&measure->start_point, + &measure->end_point, + (progress - measure->start) / (measure->end - measure->start), + out_point); +} + +static gboolean +gsk_standard_contour_measure_add_point (const graphene_point_t *from, + const graphene_point_t *to, + float from_progress, + float to_progress, + GskCurveLineReason reason, + gpointer user_data) +{ + LengthDecompose *decomp = user_data; + float seg_length; + + seg_length = graphene_point_distance (from, to, NULL, NULL); + if (seg_length == 0) + return TRUE; + + decomp->measure.end += seg_length; + decomp->measure.start_progress = from_progress; + decomp->measure.end_progress = to_progress; + decomp->measure.start_point = *from; + decomp->measure.end_point = *to; + decomp->measure.reason = reason; + + g_array_append_val (decomp->array, decomp->measure); + + decomp->measure.start += seg_length; + + return TRUE; +} + +static gpointer +gsk_standard_contour_init_measure (const GskContour *contour, + float tolerance, + float *out_length) +{ + const GskStandardContour *self = (const GskStandardContour *) contour; + gsize i; + float length; + GArray *array; + + array = g_array_new (FALSE, FALSE, sizeof (GskStandardContourMeasure)); + length = 0; + + for (i = 1; i < self->n_ops; i ++) + { + GskCurve curve; + LengthDecompose decomp = { array, { length, length, 0, 0, GSK_CURVE_LINE_REASON_SHORT, { 0, 0 }, { 0, 0 }, i } }; + + gsk_curve_init (&curve, self->ops[i]); + gsk_curve_decompose (&curve, tolerance, gsk_standard_contour_measure_add_point, &decomp); + length = decomp.measure.start; + } + + *out_length = length; + + return array; +} + +static void +gsk_standard_contour_free_measure (const GskContour *contour, + gpointer data) +{ + g_array_free (data, TRUE); +} + +static int +gsk_standard_contour_find_measure (gconstpointer m, + gconstpointer l) +{ + const GskStandardContourMeasure *measure = m; + float length = *(const float *) l; + + if (measure->start > length) + return 1; + else if (measure->end <= length) + return -1; + else + return 0; +} + +static void +gsk_standard_contour_get_point (const GskContour *contour, + gpointer measure_data, + float distance, + GskPathDirection direction, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + GskStandardContour *self = (GskStandardContour *) contour; + GArray *array = measure_data; + guint index; + float progress; + GskStandardContourMeasure *measure; + GskCurve curve; + + if (array->len == 0) + { + g_assert (distance == 0); + g_assert (gsk_pathop_op (self->ops[0]) == GSK_PATH_MOVE); + if (pos) + *pos = self->points[0]; + if (tangent) + graphene_vec2_init (tangent, 1.f, 0.f); + return; + } + + if (!g_array_binary_search (array, &distance, gsk_standard_contour_find_measure, &index)) + index = array->len - 1; + + measure = &g_array_index (array, GskStandardContourMeasure, index); + progress = (distance - measure->start) / (measure->end - measure->start); + + if (distance == measure->start && direction == GSK_PATH_START && + index > 0) + { + measure = &g_array_index (array, GskStandardContourMeasure, index - 1); + progress = 1.f; + } + else if (distance == measure->start && direction == GSK_PATH_START && + index == 0 && + (self->flags & GSK_PATH_CLOSED) != 0) + { + measure = &g_array_index (array, GskStandardContourMeasure, array->len - 1); + progress = 1.f; + } + + progress = measure->start_progress + (measure->end_progress - measure->start_progress) * progress; + g_assert (progress >= 0 && progress <= 1); + + gsk_curve_init (&curve, self->ops[measure->op]); + + if (pos) + gsk_curve_get_point (&curve, progress, pos); + if (tangent) + gsk_curve_get_tangent (&curve, progress, tangent); +} + +static float +gsk_standard_contour_get_curvature (const GskContour *contour, + gpointer measure_data, + float distance, + graphene_point_t *center) +{ + GskStandardContour *self = (GskStandardContour *) contour; + GArray *array = measure_data; + guint index; + float progress; + GskStandardContourMeasure *measure; + GskCurve curve; + + if (array->len == 0) + { + g_assert (distance == 0); + g_assert (gsk_pathop_op (self->ops[0]) == GSK_PATH_MOVE); + return 0; + } + + if (!g_array_binary_search (array, &distance, gsk_standard_contour_find_measure, &index)) + index = array->len - 1; + measure = &g_array_index (array, GskStandardContourMeasure, index); + progress = (distance - measure->start) / (measure->end - measure->start); + progress = measure->start_progress + (measure->end_progress - measure->start_progress) * progress; + g_assert (progress >= 0 && progress <= 1); + + gsk_curve_init (&curve, self->ops[measure->op]); + + return gsk_curve_get_curvature (&curve, progress, center); +} + +static gboolean +gsk_standard_contour_get_closest_point (const GskContour *contour, + gpointer measure_data, + float tolerance, + const graphene_point_t *point, + float threshold, + float *out_distance, + graphene_point_t *out_pos, + float *out_offset, + graphene_vec2_t *out_tangent) +{ + GskStandardContour *self = (GskStandardContour *) contour; + GskStandardContourMeasure *measure; + float progress, dist; + GArray *array = measure_data; + graphene_point_t p, last_point; + gsize i; + gboolean result = FALSE; + + g_assert (gsk_pathop_op (self->ops[0]) == GSK_PATH_MOVE); + last_point = self->points[0]; + + if (array->len == 0) + { + /* This is the special case for point-only */ + dist = graphene_point_distance (&last_point, point, NULL, NULL); + + if (dist > threshold) + return FALSE; + + if (out_offset) + *out_offset = 0; + + if (out_distance) + *out_distance = dist; + + if (out_pos) + *out_pos = last_point; + + if (out_tangent) + *out_tangent = *graphene_vec2_x_axis (); + + return TRUE; + } + + for (i = 0; i < array->len; i++) + { + measure = &g_array_index (array, GskStandardContourMeasure, i); + + gsk_find_point_on_line (&last_point, + &measure->end_point, + point, + &progress, + &p); + last_point = measure->end_point; + dist = graphene_point_distance (point, &p, NULL, NULL); + /* add some wiggleroom for the accurate check below */ + //g_print ("%zu: (%g-%g) dist %g\n", i, measure->start, measure->end, dist); + if (dist <= threshold + 1.0f) + { + GskCurve curve; + graphene_point_t p2; + float found_progress, test_progress, test_dist; + const float step = 1/1024.f; + + gsk_curve_init (&curve, self->ops[measure->op]); + + found_progress = measure->start_progress + (measure->end_progress - measure->start_progress) * progress; + gsk_curve_get_point (&curve, found_progress, &p); + dist = graphene_point_distance (point, &p, NULL, NULL); + //g_print ("!!! %zu: (%g-%g @ %g) dist %g\n", i, measure->start_progress, measure->end_progress, progress, dist); + + /* The progress is non-uniform, so simple translation of progress doesn't work. + * Check if larger values inch closer towards minimal distance. */ + while (progress + step < 1.0f) { + test_progress = measure->start_progress + (measure->end_progress - measure->start_progress) * (progress + step); + gsk_curve_get_point (&curve, test_progress, &p2); + test_dist = graphene_point_distance (point, &p2, NULL, NULL); + if (test_dist > dist) + break; + progress += step; + p = p2; + found_progress = test_progress; + dist = test_dist; + } + /* Also check smaller ones */ + while (progress - step > 0.0f) { + test_progress = measure->start_progress + (measure->end_progress - measure->start_progress) * (progress - step); + gsk_curve_get_point (&curve, test_progress, &p2); + test_dist = graphene_point_distance (point, &p2, NULL, NULL); + if (test_dist > dist) + break; + progress -= step; + p = p2; + found_progress = test_progress; + dist = test_dist; + } + //g_print ("!!! %zu: (%g-%g @ %g) dist %g\n", i, measure->start_progress, measure->end_progress, progress, dist); + /* double check that the point actually is closer */ + if (dist <= threshold) + { + if (out_distance) + *out_distance = dist; + if (out_pos) + *out_pos = p; + if (out_offset) + *out_offset = measure->start + (measure->end - measure->start) * progress; + if (out_tangent) + gsk_curve_get_tangent (&curve, found_progress, out_tangent); + result = TRUE; + if (tolerance >= dist) + return TRUE; + threshold = dist - tolerance; + } + } + } + + return result; +} + +static void +gsk_standard_contour_add_segment (const GskContour *contour, + GskPathBuilder *builder, + gpointer measure_data, + gboolean emit_move_to, + float start, + float end) +{ + GskStandardContour *self = (GskStandardContour *) contour; + GArray *array = measure_data; + guint start_index, end_index; + float start_progress, end_progress; + GskStandardContourMeasure *start_measure, *end_measure; + gsize i; + + if (start > 0) + { + if (!g_array_binary_search (array, (float[1]) { start }, gsk_standard_contour_find_measure, &start_index)) + start_index = array->len - 1; + start_measure = &g_array_index (array, GskStandardContourMeasure, start_index); + start_progress = (start - start_measure->start) / (start_measure->end - start_measure->start); + start_progress = start_measure->start_progress + (start_measure->end_progress - start_measure->start_progress) * start_progress; + g_assert (start_progress >= 0 && start_progress <= 1); + } + else + { + start_measure = NULL; + start_progress = 0.0; + } + + if (g_array_binary_search (array, (float[1]) { end }, gsk_standard_contour_find_measure, &end_index)) + { + end_measure = &g_array_index (array, GskStandardContourMeasure, end_index); + end_progress = (end - end_measure->start) / (end_measure->end - end_measure->start); + end_progress = end_measure->start_progress + (end_measure->end_progress - end_measure->start_progress) * end_progress; + g_assert (end_progress >= 0 && end_progress <= 1); + } + else + { + end_measure = NULL; + end_progress = 1.0; + } + + /* Add the first partial operation, + * taking care that first and last operation might be identical */ + if (start_measure) + { + GskCurve curve, cut; + const graphene_point_t *start_point; + + gsk_curve_init (&curve, self->ops[start_measure->op]); + + if (start_measure->reason == GSK_CURVE_LINE_REASON_STRAIGHT) + { + graphene_point_t p; + + gsk_standard_contour_measure_get_point_at (start_measure, start, &p); + if (emit_move_to) + gsk_path_builder_move_to (builder, p.x, p.y); + + if (end_measure == start_measure) + { + gsk_standard_contour_measure_get_point_at (end_measure, end, &p); + gsk_path_builder_line_to (builder, p.x, p.y); + return; + } + else + { + gsk_path_builder_line_to (builder, + start_measure->end_point.x, + start_measure->end_point.y); + start_index++; + if (start_index >= array->len) + return; + + start_measure++; + start_progress = start_measure->start_progress; + emit_move_to = FALSE; + gsk_curve_init (&curve, self->ops[start_measure->op]); + } + } + + if (end_measure && end_measure->op == start_measure->op) + { + if (end_measure->reason == GSK_CURVE_LINE_REASON_SHORT) + { + gsk_curve_segment (&curve, start_progress, end_progress, &cut); + if (emit_move_to) + { + start_point = gsk_curve_get_start_point (&cut); + gsk_path_builder_move_to (builder, start_point->x, start_point->y); + } + gsk_curve_builder_to (&cut, builder); + } + else + { + graphene_point_t p; + + gsk_curve_segment (&curve, start_progress, end_measure->start_progress, &cut); + if (emit_move_to) + { + start_point = gsk_curve_get_start_point (&cut); + gsk_path_builder_move_to (builder, start_point->x, start_point->y); + } + gsk_curve_builder_to (&cut, builder); + + gsk_standard_contour_measure_get_point_at (end_measure, end, &p); + gsk_path_builder_line_to (builder, p.x, p.y); + } + return; + } + + gsk_curve_split (&curve, start_progress, NULL, &cut); + + start_point = gsk_curve_get_start_point (&cut); + if (emit_move_to) + gsk_path_builder_move_to (builder, start_point->x, start_point->y); + gsk_curve_builder_to (&cut, builder); + i = start_measure->op + 1; + } + else + i = emit_move_to ? 0 : 1; + + for (; i < (end_measure ? end_measure->op : self->n_ops - 1); i++) + { + gsk_path_builder_pathop_to (builder, self->ops[i]); + } + + /* Add the last partial operation */ + if (end_measure) + { + GskCurve curve, cut; + + gsk_curve_init (&curve, self->ops[end_measure->op]); + + if (end_measure->reason == GSK_CURVE_LINE_REASON_SHORT) + { + gsk_curve_split (&curve, end_progress, &cut, NULL); + gsk_curve_builder_to (&cut, builder); + } + else + { + graphene_point_t p; + gsk_curve_split (&curve, end_measure->start_progress, &cut, NULL); + gsk_curve_builder_to (&cut, builder); + + gsk_standard_contour_measure_get_point_at (end_measure, end, &p); + gsk_path_builder_line_to (builder, p.x, p.y); + } + } + else if (i == self->n_ops - 1) + { + gskpathop op = self->ops[i]; + if (gsk_pathop_op (op) == GSK_PATH_CLOSE) + gsk_path_builder_pathop_to (builder, gsk_pathop_encode (GSK_PATH_LINE, gsk_pathop_points (op))); + else + gsk_path_builder_pathop_to (builder, op); + } +} + +static inline int +line_get_crossing (const graphene_point_t *p, + const graphene_point_t *p1, + const graphene_point_t *p2) +{ + if (p1->y <= p->y) + { + if (p2->y > p->y) + { + if ((p2->x - p1->x) * (p->y - p1->y) - (p->x - p1->x) * (p2->y - p1->y) > 0) + return 1; + } + } + else if (p2->y <= p->y) + { + if ((p2->x - p1->x) * (p->y - p1->y) - (p->x - p1->x) * (p2->y - p1->y) < 0) + return -1; + } + + return 0; +} + +static int +gsk_standard_contour_get_winding (const GskContour *contour, + gpointer measure_data, + const graphene_point_t *point) +{ + GskStandardContour *self = (GskStandardContour *) contour; + GArray *array = measure_data; + graphene_point_t last_point; + int winding; + int i; + + if (array->len == 0) + return 0; + + winding = 0; + last_point = self->points[0]; + for (i = 0; i < array->len; i++) + { + GskStandardContourMeasure *measure; + + measure = &g_array_index (array, GskStandardContourMeasure, i); + winding += line_get_crossing (point, &last_point, &measure->end_point); + + last_point = measure->end_point; + } + + winding += line_get_crossing (point, &last_point, &self->points[0]); + + return winding; +} + static const GskContourClass GSK_STANDARD_CONTOUR_CLASS = { sizeof (GskStandardContour), @@ -346,8 +942,18 @@ static const GskContourClass GSK_STANDARD_CONTOUR_CLASS = gsk_standard_contour_get_start_end, gsk_standard_contour_foreach, gsk_standard_contour_reverse, + gsk_standard_contour_init_measure, + gsk_standard_contour_free_measure, + gsk_standard_contour_get_point, + gsk_standard_contour_get_curvature, + gsk_standard_contour_get_closest_point, + gsk_standard_contour_add_segment, + gsk_standard_contour_get_winding, }; +/* You must ensure the contour has enough size allocated, + * see gsk_standard_contour_compute_size() + */ static void gsk_standard_contour_init (GskContour *contour, GskPathFlags flags, @@ -371,8 +977,10 @@ gsk_standard_contour_init (GskContour *contour, offset += self->points - points; for (i = 0; i < n_ops; i++) - self->ops[i] = gsk_pathop_encode (gsk_pathop_op (ops[i]), - gsk_pathop_points (ops[i]) + offset); + { + self->ops[i] = gsk_pathop_encode (gsk_pathop_op (ops[i]), + gsk_pathop_points (ops[i]) + offset); + } } GskContour * @@ -498,6 +1106,330 @@ gsk_rect_contour_reverse (const GskContour *contour) self->height)); } +static gpointer +gsk_rect_contour_init_measure (const GskContour *contour, + float tolerance, + float *out_length) +{ + const GskRectContour *self = (const GskRectContour *) contour; + + *out_length = 2 * ABS (self->width) + 2 * ABS (self->height); + + return NULL; +} + +static void +gsk_rect_contour_free_measure (const GskContour *contour, + gpointer data) +{ +} + +static void +gsk_rect_contour_get_point (const GskContour *contour, + gpointer measure_data, + float distance, + GskPathDirection direction, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + const GskRectContour *self = (const GskRectContour *) contour; + + if (distance == 0) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x, self->y); + + if (tangent) + { + if (direction == GSK_PATH_START) + graphene_vec2_init (tangent, 0.0f, - copysignf (1.0f, self->height)); + else + graphene_vec2_init (tangent, copysignf (1.0f, self->width), 0.0f); + } + return; + } + + if (distance < fabsf (self->width)) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + copysignf (distance, self->width), self->y); + if (tangent) + graphene_vec2_init (tangent, copysignf (1.0f, self->width), 0.0f); + return; + } + distance -= fabsf (self->width); + + if (distance == 0) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + self->width, self->y); + + if (tangent) + { + if (direction == GSK_PATH_START) + graphene_vec2_init (tangent, copysignf (1.0f, self->width), 0.0f); + else + graphene_vec2_init (tangent, 0.0f, copysignf (1.0f, self->height)); + } + return; + } + + if (distance < fabsf (self->height)) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + self->width, self->y + copysignf (distance, self->height)); + if (tangent) + graphene_vec2_init (tangent, 0.0f, copysignf (1.0f, self->height)); + return; + } + distance -= fabs (self->height); + + if (distance == 0) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + self->width, self->y + self->height); + + if (tangent) + { + if (direction == GSK_PATH_START) + graphene_vec2_init (tangent, 0.0f, copysignf (1.0f, self->height)); + else + graphene_vec2_init (tangent, - copysignf (1.0f, self->width), 0.0f); + } + 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) + graphene_vec2_init (tangent, - copysignf (1.0f, self->width), 0.0f); + return; + } + distance -= fabsf (self->width); + + if (distance == 0) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x, self->y + self->height); + + if (tangent) + { + if (direction == GSK_PATH_START) + graphene_vec2_init (tangent, - copysignf (1.0f, self->width), 0.0f); + else + graphene_vec2_init (tangent, 0.0f, - copysignf (1.0f, self->height)); + } + return; + } + + if (distance < fabsf (self->height)) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x, self->y + self->height - copysignf (distance, self->height)); + if (tangent) + graphene_vec2_init (tangent, 0.0f, - copysignf (1.0f, self->height)); + return; + } + + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x, self->y); + + if (tangent) + { + if (direction == GSK_PATH_START) + graphene_vec2_init (tangent, 0.0f, - copysignf (1.0f, self->height)); + else + graphene_vec2_init (tangent, copysignf (1.0f, self->width), 0.0f); + } +} + +static float +gsk_rect_contour_get_curvature (const GskContour *contour, + gpointer measure_data, + float distance, + graphene_point_t *center) +{ + return 0; +} + +static gboolean +gsk_rect_contour_get_closest_point (const GskContour *contour, + gpointer measure_data, + float tolerance, + const graphene_point_t *point, + float threshold, + float *out_distance, + graphene_point_t *out_pos, + float *out_offset, + graphene_vec2_t *out_tangent) +{ + const GskRectContour *self = (const GskRectContour *) contour; + 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) * ABS (self->width) - MIN (t.y, 1.f - t.y) * ABS (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 = fabs (t.x); + t.y = fabs (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; + + if (out_distance) + *out_distance = distance; + + if (out_pos) + *out_pos = p; + + if (out_offset) + *out_offset = ((t.x == 0.0 && (t.y > 0 && self->width != 0)) ? 2 - t.y : t.y) * ABS (self->height) + + ((t.y == 1.0 || (t.y > 0.0 && t.x == 0.0)) ? 2 - t.x : t.x) * ABS (self->width); + + if (out_tangent) + { + if (t.y == 0 && t.x < 1) + graphene_vec2_init (out_tangent, copysignf(1.0, self->width), 0); + else if (t.x == 0) + graphene_vec2_init (out_tangent, 0, - copysignf(1.0, self->height)); + else if (t.y == 1) + graphene_vec2_init (out_tangent, - copysignf(1.0, self->width), 0); + else if (t.x == 1) + graphene_vec2_init (out_tangent, 0, copysignf(1.0, self->height)); + } + + return TRUE; +} + +static void +gsk_rect_contour_add_segment (const GskContour *contour, + GskPathBuilder *builder, + gpointer measure_data, + gboolean emit_move_to, + float start, + float end) +{ + const GskRectContour *self = (const GskRectContour *) contour; + float w = ABS (self->width); + float h = ABS (self->height); + + 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); + } +} + +static int +gsk_rect_contour_get_winding (const GskContour *contour, + gpointer measure_data, + const graphene_point_t *point) +{ + const GskRectContour *self = (const GskRectContour *) contour; + graphene_rect_t rect; + + graphene_rect_init (&rect, self->x, self->y, self->width, self->height); + + if (graphene_rect_contains_point (&rect, point)) + return -1; + + return 0; +} + static const GskContourClass GSK_RECT_CONTOUR_CLASS = { sizeof (GskRectContour), @@ -510,6 +1442,13 @@ static const GskContourClass GSK_RECT_CONTOUR_CLASS = gsk_rect_contour_get_start_end, gsk_rect_contour_foreach, gsk_rect_contour_reverse, + gsk_rect_contour_init_measure, + gsk_rect_contour_free_measure, + gsk_rect_contour_get_point, + gsk_rect_contour_get_curvature, + gsk_rect_contour_get_closest_point, + gsk_rect_contour_add_segment, + gsk_rect_contour_get_winding, }; GskContour * @@ -727,6 +1666,174 @@ gsk_rounded_rect_contour_reverse (const GskContour *contour) return (GskContour *)copy; } +typedef struct +{ + GskPath *path; + const GskContour *contour; + gpointer measure_data; +} RoundedRectMeasureData; + +static gboolean +add_cb (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[3].x, pts[3].y, + weight); + break; + case GSK_PATH_CLOSE: + gsk_path_builder_close (builder); + break; + default: + g_assert_not_reached (); + } + + return TRUE; +} + +static gpointer +gsk_rounded_rect_contour_init_measure (const GskContour *contour, + float tolerance, + float *out_length) +{ + GskPathBuilder *builder; + RoundedRectMeasureData *data; + + builder = gsk_path_builder_new (); + + gsk_contour_foreach (contour, tolerance, add_cb, builder); + + data = g_new (RoundedRectMeasureData, 1); + data->path = gsk_path_builder_free_to_path (builder); + data->contour = gsk_path_get_contour (data->path, 0); + data->measure_data = gsk_standard_contour_init_measure (data->contour, tolerance, out_length); + + return data; +} + +static void +gsk_rounded_rect_contour_free_measure (const GskContour *contour, + gpointer measure_data) +{ + RoundedRectMeasureData *data = measure_data; + + gsk_standard_contour_free_measure (data->contour, data->measure_data); + gsk_path_unref (data->path); + g_free (data); +} + +static void +gsk_rounded_rect_contour_get_point (const GskContour *contour, + gpointer measure_data, + float distance, + GskPathDirection direction, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + RoundedRectMeasureData *data = measure_data; + + gsk_standard_contour_get_point (data->contour, + data->measure_data, + direction, + distance, + pos, + tangent); +} + +static float +gsk_rounded_rect_contour_get_curvature (const GskContour *contour, + gpointer measure_data, + float distance, + graphene_point_t *center) +{ + RoundedRectMeasureData *data = measure_data; + + return gsk_standard_contour_get_curvature (data->contour, + data->measure_data, + distance, + center); +} + +static gboolean +gsk_rounded_rect_contour_get_closest_point (const GskContour *contour, + gpointer measure_data, + float tolerance, + const graphene_point_t *point, + float threshold, + float *out_distance, + graphene_point_t *out_pos, + float *out_offset, + graphene_vec2_t *out_tangent) +{ + RoundedRectMeasureData *data = measure_data; + + return gsk_standard_contour_get_closest_point (data->contour, + data->measure_data, + tolerance, + point, + threshold, + out_distance, + out_pos, + out_offset, + out_tangent); +} + +static void +gsk_rounded_rect_contour_add_segment (const GskContour *contour, + GskPathBuilder *builder, + gpointer measure_data, + gboolean emit_move_to, + float start, + float end) +{ + RoundedRectMeasureData *data = measure_data; + + gsk_standard_contour_add_segment (data->contour, + builder, + data->measure_data, + emit_move_to, + start, + end); +} + +static int +gsk_rounded_rect_contour_get_winding (const GskContour *contour, + gpointer measure_data, + const graphene_point_t *point) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + + if (gsk_rounded_rect_contains_point (&self->rect, point)) + return self->ccw ? 1 : -1; + + return 0; +} + static const GskContourClass GSK_ROUNDED_RECT_CONTOUR_CLASS = { sizeof (GskRoundedRectContour), @@ -739,6 +1846,13 @@ static const GskContourClass GSK_ROUNDED_RECT_CONTOUR_CLASS = gsk_rounded_rect_contour_get_start_end, gsk_rounded_rect_contour_foreach, gsk_rounded_rect_contour_reverse, + gsk_rounded_rect_contour_init_measure, + gsk_rounded_rect_contour_free_measure, + gsk_rounded_rect_contour_get_point, + gsk_rounded_rect_contour_get_curvature, + gsk_rounded_rect_contour_get_closest_point, + gsk_rounded_rect_contour_add_segment, + gsk_rounded_rect_contour_get_winding, }; GskContour * @@ -907,6 +2021,226 @@ gsk_circle_contour_reverse (const GskContour *contour) self->start_angle); } +static gpointer +gsk_circle_contour_init_measure (const GskContour *contour, + float tolerance, + float *out_length) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + + *out_length = DEG_TO_RAD (fabs (self->start_angle - self->end_angle)) * 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, + GskPathDirection direction, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + float delta = self->end_angle - self->start_angle; + float length = self->radius * DEG_TO_RAD (delta); + float angle = self->start_angle + distance/length * delta; + graphene_point_t p; + + p = GSK_CIRCLE_POINT_INIT (self, angle); + + if (pos) + *pos = p; + + if (tangent) + { + 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, + gpointer measure_data, + float distance, + graphene_point_t *center) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + + if (center) + *center = self->center; + + return 1 / self->radius; +} + +static gboolean +gsk_circle_contour_get_closest_point (const GskContour *contour, + gpointer measure_data, + float tolerance, + const graphene_point_t *point, + float threshold, + float *out_distance, + graphene_point_t *out_pos, + float *out_offset, + graphene_vec2_t *out_tangent) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + float angle; + float closest_angle; + float offset; + graphene_point_t pos; + graphene_vec2_t tangent; + float distance; + + if (graphene_point_distance (point, &self->center, NULL, NULL) > threshold + self->radius) + return FALSE; + + angle = atan2f (point->y - self->center.y, point->x - self->center.x); + angle = RAD_TO_DEG (angle); + if (angle < 0) + angle += 360; + + if ((self->start_angle <= angle && angle <= self->end_angle) || + (self->end_angle <= angle && angle <= self->start_angle)) + { + closest_angle = angle; + } + else + { + float d1, d2; + + d1 = fabs (self->start_angle - angle); + d1 = MIN (d1, 360 - d1); + d2 = fabs (self->end_angle - angle); + d2 = MIN (d2, 360 - d2); + if (d1 < d2) + closest_angle = self->start_angle; + else + closest_angle = self->end_angle; + } + + offset = self->radius * 2 * M_PI * (closest_angle - self->start_angle) / (self->end_angle - self->start_angle); + + gsk_circle_contour_get_point (contour, NULL, offset, GSK_PATH_END, &pos, out_tangent ? &tangent : NULL); + + distance = graphene_point_distance (&pos, point, NULL, NULL); + if (threshold < distance) + return FALSE; + + if (out_offset) + *out_offset = offset; + if (out_pos) + *out_pos = pos; + if (out_distance) + *out_distance = distance; + if (out_tangent) + *out_tangent = tangent; + + return TRUE; +} + +static gboolean +add_curve_to_segment (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer data) +{ + GskPathBuilder *builder = data; + GskCurve curve; + + gsk_curve_init_foreach (&curve, op, pts, n_pts, weight); + gsk_curve_builder_to (&curve, builder); + + return TRUE; +} + +static void +gsk_circle_contour_add_segment (const GskContour *contour, + GskPathBuilder *builder, + gpointer measure_data, + gboolean emit_move_to, + float start, + float end) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + float delta = self->end_angle - self->start_angle; + float length = self->radius * DEG_TO_RAD (delta); + float start_angle = self->start_angle + start/length * delta; + float end_angle = self->start_angle + end/length * delta; + + if (emit_move_to) + { + GskContour *segment; + + segment = gsk_circle_contour_new (&self->center, self->radius, + start_angle, end_angle); + gsk_path_builder_add_contour (builder, segment); + } + else + { + /* convert to a standard contour */ + gsk_spline_decompose_arc (&self->center, + self->radius, + GSK_PATH_TOLERANCE_DEFAULT, + DEG_TO_RAD (start_angle), + DEG_TO_RAD (end_angle), + gsk_circle_contour_curve, + &(ForeachWrapper) { add_curve_to_segment, builder }); + } +} + +static int +gsk_circle_contour_get_winding (const GskContour *contour, + gpointer measure_data, + 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 (fabs (self->start_angle - self->end_angle) >= 360) + { + return -1; + } + else + { + /* Check if the point and the midpoint are on the same side + * of the chord through start and end. + */ + double mid_angle = (self->end_angle - self->start_angle) / 2; + graphene_point_t start = GRAPHENE_POINT_INIT (self->center.x + cos (DEG_TO_RAD (self->start_angle)) * self->radius, + self->center.y + sin (DEG_TO_RAD (self->start_angle)) * self->radius); + graphene_point_t mid = GRAPHENE_POINT_INIT (self->center.x + cos (DEG_TO_RAD (mid_angle)) * self->radius, + self->center.y + sin (DEG_TO_RAD (mid_angle)) * self->radius); + graphene_point_t end = GRAPHENE_POINT_INIT (self->center.x + cos (DEG_TO_RAD (self->end_angle)) * self->radius, + self->center.y + sin (DEG_TO_RAD (self->end_angle)) * self->radius); + + graphene_vec2_t n, m; + float a, b; + + graphene_vec2_init (&n, start.y - end.y, end.x - start.x); + graphene_vec2_init (&m, mid.x, mid.y); + a = graphene_vec2_dot (&m, &n); + graphene_vec2_init (&m, point->x, point->y); + b = graphene_vec2_dot (&m, &n); + + if ((a < 0) != (b < 0)) + return -1; + } + + return 0; +} + static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS = { sizeof (GskCircleContour), @@ -919,6 +2253,13 @@ static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS = gsk_circle_contour_get_start_end, gsk_circle_contour_foreach, gsk_circle_contour_reverse, + gsk_circle_contour_init_measure, + gsk_circle_contour_free_measure, + gsk_circle_contour_get_point, + gsk_circle_contour_get_curvature, + gsk_circle_contour_get_closest_point, + gsk_circle_contour_add_segment, + gsk_circle_contour_get_winding, }; GskContour * @@ -1006,6 +2347,21 @@ gsk_contour_foreach (const GskContour *self, return self->klass->foreach (self, tolerance, func, user_data); } +gpointer +gsk_contour_init_measure (const GskContour *self, + float tolerance, + float *out_length) +{ + return self->klass->init_measure (self, tolerance, out_length); +} + +void +gsk_contour_free_measure (const GskContour *self, + gpointer data) +{ + self->klass->free_measure (self, data); +} + void gsk_contour_get_start_end (const GskContour *self, graphene_point_t *start, @@ -1014,6 +2370,67 @@ gsk_contour_get_start_end (const GskContour *self, self->klass->get_start_end (self, start, end); } +void +gsk_contour_get_point (const GskContour *self, + gpointer measure_data, + float distance, + GskPathDirection direction, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + self->klass->get_point (self, measure_data, distance, direction, pos, tangent); +} + +gboolean +gsk_contour_get_closest_point (const GskContour *self, + gpointer measure_data, + float tolerance, + const graphene_point_t *point, + float threshold, + float *out_distance, + graphene_point_t *out_pos, + float *out_offset, + graphene_vec2_t *out_tangent) +{ + return self->klass->get_closest_point (self, + measure_data, + tolerance, + point, + threshold, + out_distance, + out_pos, + out_offset, + out_tangent); +} + +void +gsk_contour_add_segment (const GskContour *self, + GskPathBuilder *builder, + gpointer measure_data, + gboolean emit_move_to, + float start, + float end) +{ + self->klass->add_segment (self, builder, measure_data, emit_move_to, start, end); +} + +int +gsk_contour_get_winding (const GskContour *self, + gpointer measure_data, + const graphene_point_t *point) +{ + return self->klass->get_winding (self, measure_data, point); +} + +float +gsk_contour_get_curvature (const GskContour *self, + gpointer measure_data, + float distance, + graphene_point_t *center) +{ + return self->klass->get_curvature (self, measure_data, distance, center); +} + /* }}} */ /* vim:set foldmethod=marker expandtab: */ diff --git a/gsk/gskcontourprivate.h b/gsk/gskcontourprivate.h index 34f4a0f4a2..cfd6124bc6 100644 --- a/gsk/gskcontourprivate.h +++ b/gsk/gskcontourprivate.h @@ -58,6 +58,11 @@ void gsk_contour_print (const GskContou GString *string); gboolean gsk_contour_get_bounds (const GskContour *self, graphene_rect_t *bounds); +gpointer gsk_contour_init_measure (const GskContour *self, + float tolerance, + float *out_length); +void gsk_contour_free_measure (const GskContour *self, + gpointer data); gboolean gsk_contour_foreach (const GskContour *self, float tolerance, GskPathForeachFunc func, @@ -65,5 +70,34 @@ gboolean gsk_contour_foreach (const GskContou void gsk_contour_get_start_end (const GskContour *self, graphene_point_t *start, graphene_point_t *end); +void gsk_contour_get_point (const GskContour *self, + gpointer measure_data, + float distance, + GskPathDirection direction, + graphene_point_t *pos, + graphene_vec2_t *tangent); +gboolean gsk_contour_get_closest_point (const GskContour *self, + gpointer measure_data, + float tolerance, + const graphene_point_t *point, + float threshold, + float *out_distance, + graphene_point_t *out_pos, + float *out_offset, + graphene_vec2_t *out_tangent); +int gsk_contour_get_winding (const GskContour *self, + gpointer measure_data, + const graphene_point_t *point); +void gsk_contour_add_segment (const GskContour *self, + GskPathBuilder *builder, + gpointer measure_data, + gboolean emit_move_to, + float start, + float end); +float gsk_contour_get_curvature (const GskContour *self, + gpointer measure_data, + float distance, + graphene_point_t *center); + G_END_DECLS diff --git a/gsk/gskcurve.c b/gsk/gskcurve.c index 35b7dece5f..c91376fcb8 100644 --- a/gsk/gskcurve.c +++ b/gsk/gskcurve.c @@ -51,6 +51,8 @@ struct _GskCurveClass graphene_vec2_t *tangent); void (* reverse) (const GskCurve *curve, GskCurve *reverse); + float (* get_curvature) (const GskCurve *curve, + float t); void (* split) (const GskCurve *curve, float progress, GskCurve *result1, @@ -221,6 +223,13 @@ gsk_line_curve_get_tangent (const GskCurve *curve, get_tangent (&self->points[0], &self->points[1], tangent); } +static float +gsk_line_curve_get_curvature (const GskCurve *curve, + float t) +{ + return 0; +} + static void gsk_line_curve_reverse (const GskCurve *curve, GskCurve *reverse) @@ -299,6 +308,7 @@ static const GskCurveClass GSK_LINE_CURVE_CLASS = { gsk_line_curve_get_point, gsk_line_curve_get_tangent, gsk_line_curve_reverse, + gsk_line_curve_get_curvature, gsk_line_curve_split, gsk_line_curve_segment, gsk_line_curve_decompose, @@ -441,6 +451,17 @@ gsk_quad_curve_get_tangent (const GskCurve *curve, graphene_vec2_normalize (tangent, tangent); } + +static float gsk_cubic_curve_get_curvature (const GskCurve *curve, + float t); + +static float +gsk_quad_curve_get_curvature (const GskCurve *curve, + float t) +{ + return gsk_cubic_curve_get_curvature (curve, t); +} + static void gsk_quad_curve_reverse (const GskCurve *curve, GskCurve *reverse) @@ -600,6 +621,7 @@ static const GskCurveClass GSK_QUAD_CURVE_CLASS = { gsk_quad_curve_get_point, gsk_quad_curve_get_tangent, gsk_quad_curve_reverse, + gsk_quad_curve_get_curvature, gsk_quad_curve_split, gsk_quad_curve_segment, gsk_quad_curve_decompose, @@ -775,6 +797,105 @@ gsk_cubic_curve_reverse (const GskCurve *curve, reverse->cubic.has_coefficients = FALSE; } +static void +gsk_curve_get_derivative (const GskCurve *curve, + GskCurve *deriv) +{ + switch (curve->op) + { + case GSK_PATH_LINE: + { + const GskLineCurve *self = &curve->line; + graphene_point_t p; + + p.x = self->points[1].x - self->points[0].x; + p.y = self->points[1].y - self->points[0].y; + + gsk_line_curve_init_from_points (&deriv->line, GSK_PATH_LINE, &p, &p); + } + break; + + case GSK_PATH_QUAD: + { + const GskQuadCurve *self = &curve->quad; + graphene_point_t p[2]; + + p[0].x = 2.f * (self->points[1].x - self->points[0].x); + p[0].y = 2.f * (self->points[1].y - self->points[0].y); + p[1].x = 2.f * (self->points[2].x - self->points[1].x); + p[1].y = 2.f * (self->points[2].y - self->points[1].y); + + gsk_line_curve_init_from_points (&deriv->line, GSK_PATH_LINE, &p[0], &p[1]); + } + break; + + case GSK_PATH_CUBIC: + { + const GskCubicCurve *self = &curve->cubic; + graphene_point_t p[3]; + + p[0].x = 3.f * (self->points[1].x - self->points[0].x); + p[0].y = 3.f * (self->points[1].y - self->points[0].y); + p[1].x = 3.f * (self->points[2].x - self->points[1].x); + p[1].y = 3.f * (self->points[2].y - self->points[1].y); + p[2].x = 3.f * (self->points[3].x - self->points[2].x); + p[2].y = 3.f * (self->points[3].y - self->points[2].y); + + gsk_quad_curve_init_from_points (&deriv->quad, p); + } + break; + + case GSK_PATH_MOVE: + case GSK_PATH_CLOSE: + case GSK_PATH_CONIC: + default: + g_assert_not_reached (); + } +} + +static inline float +cross (const graphene_vec2_t *v1, + const graphene_vec2_t *v2) +{ + return graphene_vec2_get_x (v1) * graphene_vec2_get_y (v2) + - graphene_vec2_get_y (v1) * graphene_vec2_get_x (v2); +} + +static inline float +pow3 (float w) +{ + return w * w * w; +} + +static float +gsk_cubic_curve_get_curvature (const GskCurve *curve, + float t) +{ + GskCurve c1, c2; + graphene_point_t p, pp; + graphene_vec2_t d, dd; + float num, denom; + + gsk_curve_get_derivative (curve, &c1); + gsk_curve_get_derivative (&c1, &c2); + + gsk_curve_get_point (&c1, t, &p); + gsk_curve_get_point (&c2, t, &pp); + + graphene_vec2_init (&d, p.x, p.y); + graphene_vec2_init (&dd, pp.x, pp.y); + + num = cross (&d, &dd); + if (num == 0) + return 0; + + denom = pow3 (graphene_vec2_length (&d)); + if (denom == 0) + return 0; + + return num / denom; +} + static void gsk_cubic_curve_split (const GskCurve *curve, float progress, @@ -895,6 +1016,7 @@ static const GskCurveClass GSK_CUBIC_CURVE_CLASS = { gsk_cubic_curve_get_point, gsk_cubic_curve_get_tangent, gsk_cubic_curve_reverse, + gsk_cubic_curve_get_curvature, gsk_cubic_curve_split, gsk_cubic_curve_segment, gsk_cubic_curve_decompose, @@ -1094,6 +1216,38 @@ gsk_conic_curve_get_tangent (const GskCurve *curve, graphene_vec2_normalize (tangent, tangent); } +/* See M. Floater, Derivatives of rational Bezier curves */ +static float +gsk_conic_curve_get_curvature (const GskCurve *curve, + float t) +{ + graphene_point_t p[3], p1[2]; + float w, w1[2], w2; + graphene_vec2_t t1, t2, t3; + + w = curve->conic.points[2].x; + + p[0] = curve->conic.points[0]; + p[1] = curve->conic.points[1]; + p[2] = curve->conic.points[3]; + + w1[0] = (1 - t) + t*w; + w1[1] = (1 - t)*w + t; + + w2 = (1 - t)*w1[0] + t*w1[1]; + + p1[0].x = ((1 - t)*p[0].x + t*w*p[1].x)/w1[0]; + p1[0].y = ((1 - t)*p[0].y + t*w*p[1].y)/w1[0]; + p1[1].x = ((1 - t)*w*p[1].x + t*p[2].x)/w1[1]; + p1[1].y = ((1 - t)*w*p[1].y + t*p[2].y)/w1[1]; + + graphene_vec2_init (&t1, p[1].x - p[0].x, p[1].y - p[0].y); + graphene_vec2_init (&t2, p[2].x - p[1].x, p[2].y - p[1].y); + graphene_vec2_init (&t3, p1[1].x - p1[0].x, p1[1].y - p1[0].y); + + return 0.5 * ((w*pow3 (w2))/(pow3 (w1[0])*pow3 (w1[1]))) * (cross (&t1, &t2) / pow3 (graphene_vec2_length (&t3))); +} + static void gsk_conic_curve_reverse (const GskCurve *curve, GskCurve *reverse) @@ -1443,6 +1597,7 @@ static const GskCurveClass GSK_CONIC_CURVE_CLASS = { gsk_conic_curve_get_point, gsk_conic_curve_get_tangent, gsk_conic_curve_reverse, + gsk_conic_curve_get_curvature, gsk_conic_curve_split, gsk_conic_curve_segment, gsk_conic_curve_decompose, @@ -1550,6 +1705,31 @@ gsk_curve_get_tangent (const GskCurve *curve, get_class (curve->op)->get_tangent (curve, progress, tangent); } +float +gsk_curve_get_curvature (const GskCurve *curve, + float t, + graphene_point_t *center) +{ + float k; + + k = get_class (curve->op)->get_curvature (curve, t); + + if (center != NULL && k != 0) + { + graphene_point_t p; + graphene_vec2_t tangent; + float r; + + r = 1/k; + gsk_curve_get_point (curve, t, &p); + gsk_curve_get_tangent (curve, t, &tangent); + center->x = p.x - r * graphene_vec2_get_y (&tangent); + center->y = p.y + r * graphene_vec2_get_x (&tangent); + } + + return k; +} + void gsk_curve_reverse (const GskCurve *curve, GskCurve *reverse) diff --git a/gsk/gskcurveprivate.h b/gsk/gskcurveprivate.h index 43b045fa97..0dd70cebbe 100644 --- a/gsk/gskcurveprivate.h +++ b/gsk/gskcurveprivate.h @@ -155,5 +155,9 @@ gboolean gsk_curve_decompose_curve (const GskCurve #define gsk_curve_builder_to(curve, builder) gsk_path_builder_pathop_to ((builder), gsk_curve_pathop (curve)) +float gsk_curve_get_curvature (const GskCurve *curve, + float t, + graphene_point_t *center); + G_END_DECLS diff --git a/gsk/gskpathbuilder.h b/gsk/gskpathbuilder.h index f20e62258e..f61fe79cfd 100644 --- a/gsk/gskpathbuilder.h +++ b/gsk/gskpathbuilder.h @@ -75,6 +75,12 @@ void gsk_path_builder_add_ellipse (GskPathBuilder const graphene_point_t *center, const graphene_size_t *radius); +GDK_AVAILABLE_IN_ALL +void gsk_path_builder_add_segment (GskPathBuilder *self, + GskPathMeasure *measure, + float start, + float end); + GDK_AVAILABLE_IN_ALL void gsk_path_builder_move_to (GskPathBuilder *self, float x, diff --git a/gsk/gskpathmeasure.c b/gsk/gskpathmeasure.c new file mode 100644 index 0000000000..5e3a3932c2 --- /dev/null +++ b/gsk/gskpathmeasure.c @@ -0,0 +1,489 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gskpathmeasure.h" + +#include "gskpathbuilder.h" +#include "gskpathpointprivate.h" +#include "gskpathprivate.h" + +/** + * `GskPathMeasure` is an object that allows measurements + * on `GskPath`s such as determining the length of the path. + * + * Many measuring operations require approximating the path + * with simpler shapes. Therefore, a `GskPathMeasure` has + * a tolerance that determines what amount is required + * for such approximations. + * + * A `GskPathMeasure` struct is a reference counted struct + * and should be treated as opaque. + */ + +typedef struct _GskContourMeasure GskContourMeasure; + +struct _GskContourMeasure +{ + float length; + gpointer contour_data; +}; + +struct _GskPathMeasure +{ + /*< private >*/ + guint ref_count; + + GskPath *path; + float tolerance; + + float length; + gsize n_contours; + GskContourMeasure measures[]; +}; + +G_DEFINE_BOXED_TYPE (GskPathMeasure, gsk_path_measure, + gsk_path_measure_ref, + gsk_path_measure_unref) + +/** + * gsk_path_measure_new: + * @path: the path to measure + * + * Creates a measure object for the given @path. + * + * Returns: a new `GskPathMeasure` representing @path + */ +GskPathMeasure * +gsk_path_measure_new (GskPath *path) +{ + return gsk_path_measure_new_with_tolerance (path, GSK_PATH_TOLERANCE_DEFAULT); +} + +/** + * gsk_path_measure_new_with_tolerance: + * @path: the path to measure + * @tolerance: the tolerance for measuring operations + * + * Creates a measure object for the given @path and @tolerance. + * + * Returns: a new `GskPathMeasure` representing @path + */ +GskPathMeasure * +gsk_path_measure_new_with_tolerance (GskPath *path, + float tolerance) +{ + GskPathMeasure *self; + gsize i, n_contours; + + g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (tolerance > 0, NULL); + + n_contours = gsk_path_get_n_contours (path); + + self = g_malloc0 (sizeof (GskPathMeasure) + n_contours * sizeof (GskContourMeasure)); + + self->ref_count = 1; + self->path = gsk_path_ref (path); + self->tolerance = tolerance; + self->n_contours = n_contours; + + for (i = 0; i < n_contours; i++) + { + self->measures[i].contour_data = gsk_contour_init_measure (gsk_path_get_contour (path, i), + self->tolerance, + &self->measures[i].length); + self->length += self->measures[i].length; + } + + return self; +} + +/** + * gsk_path_measure_ref: + * @self: a `GskPathMeasure` + * + * Increases the reference count of a `GskPathMeasure` by one. + * + * Returns: the passed in `GskPathMeasure`. + */ +GskPathMeasure * +gsk_path_measure_ref (GskPathMeasure *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + self->ref_count++; + + return self; +} + +/** + * gsk_path_measure_unref: + * @self: a `GskPathMeasure` + * + * Decreases the reference count of a `GskPathMeasure` by one. + * + * If the resulting reference count is zero, frees the object. + */ +void +gsk_path_measure_unref (GskPathMeasure *self) +{ + gsize i; + + g_return_if_fail (self != NULL); + g_return_if_fail (self->ref_count > 0); + + self->ref_count--; + if (self->ref_count > 0) + return; + + for (i = 0; i < self->n_contours; i++) + { + gsk_contour_free_measure (gsk_path_get_contour (self->path, i), + self->measures[i].contour_data); + } + + gsk_path_unref (self->path); + g_free (self); +} + +/** + * gsk_path_measure_get_path: + * @self: a `GskPathMeasure` + * + * Returns the path that the measure was created for. + * + * Returns: (transfer none): the path of @self + */ +GskPath * +gsk_path_measure_get_path (GskPathMeasure *self) +{ + return self->path; +} + +/** + * gsk_path_measure_get_tolerance: + * @self: a `GskPathMeasure` + * + * Returns the tolerance that the measure was created with. + * + * Returns: the tolerance of @self + */ +float +gsk_path_measure_get_tolerance (GskPathMeasure *self) +{ + return self->tolerance; +} + +/** + * gsk_path_measure_get_length: + * @self: a `GskPathMeasure` + * + * Gets the length of the path being measured. + * + * The length is cached, so this function does not do any work. + * + * Returns: The length of the path measured by @self + */ +float +gsk_path_measure_get_length (GskPathMeasure *self) +{ + g_return_val_if_fail (self != NULL, 0); + + return self->length; +} + +/** + * gsk_path_measure_is_closed: + * @self: a `GskPathMeasure` + * + * Returns if the path being measured represents a single closed + * contour. + * + * Returns: `TRUE` if the current path is closed + */ +gboolean +gsk_path_measure_is_closed (GskPathMeasure *self) +{ + const GskContour *contour; + + g_return_val_if_fail (self != NULL, FALSE); + + /* XXX: is the empty path closed? Currently it's not */ + if (self->n_contours != 1) + return FALSE; + + contour = gsk_path_get_contour (self->path, 0); + return gsk_contour_get_flags (contour) & GSK_PATH_CLOSED ? TRUE : FALSE; +} + +static float +gsk_path_measure_clamp_distance (GskPathMeasure *self, + float distance) +{ + if (isnan (distance)) + return 0; + + return CLAMP (distance, 0, self->length); +} + +/** + * gsk_path_measure_in_fill: + * @self: a `GskPathMeasure` + * @point: the point to test + * @fill_rule: the fill rule to follow + * + * Returns whether the given point is inside the area + * that would be affected if the path was filled according + * to @fill_rule. + * + * Returns: `TRUE` if @point is inside + */ +gboolean +gsk_path_measure_in_fill (GskPathMeasure *self, + const graphene_point_t *point, + GskFillRule fill_rule) +{ + int winding = 0; + int i; + + for (i = 0; i < self->n_contours; i++) + winding += gsk_contour_get_winding (gsk_path_get_contour (self->path, i), + self->measures[i].contour_data, + point); + + switch (fill_rule) + { + case GSK_FILL_RULE_EVEN_ODD: + return winding & 1; + case GSK_FILL_RULE_WINDING: + return winding != 0; + default: + g_assert_not_reached (); + } +} + +static void +gsk_path_builder_add_segment_chunk (GskPathBuilder *self, + GskPathMeasure *measure, + gboolean emit_move_to, + float start, + float end) +{ + g_assert (start < end); + + for (gsize i = 0; i < measure->n_contours; i++) + { + if (measure->measures[i].length < start) + { + start -= measure->measures[i].length; + end -= measure->measures[i].length; + } + else if (start > 0 || end < measure->measures[i].length) + { + float len = MIN (end, measure->measures[i].length); + gsk_contour_add_segment (gsk_path_get_contour (measure->path, i), + self, + measure->measures[i].contour_data, + emit_move_to, + start, + len); + end -= len; + start = 0; + if (end <= 0) + break; + } + else + { + end -= measure->measures[i].length; + gsk_path_builder_add_contour (self, gsk_contour_dup (gsk_path_get_contour (measure->path, i))); + } + emit_move_to = TRUE; + } +} + +/** + * gsk_path_builder_add_segment: + * @self: a `GskPathBuilder` + * @measure: the `GskPathMeasure` to take the segment to + * @start: start distance into the path + * @end: end distance into the path + * + * Adds to @self the segment of @measure from @start to @end. + * + * The distances are given relative to the length of @measure's path, + * from 0 for the beginning of the path to its length for the end + * of the path. The values will be clamped to that range. The length + * can be obtained with [method@Gsk.PathMeasure.get_length]. + * + * If @start >= @end after clamping, the path will first add the segment + * from @start to the end of the path, and then add the segment from + * the beginning to @end. If the path is closed, these segments will + * be connected. + */ +void +gsk_path_builder_add_segment (GskPathBuilder *self, + GskPathMeasure *measure, + float start, + float end) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (measure != NULL); + + start = gsk_path_measure_clamp_distance (measure, start); + end = gsk_path_measure_clamp_distance (measure, end); + + if (start < end) + { + gsk_path_builder_add_segment_chunk (self, measure, TRUE, start, end); + } + else + { + /* If the path is closed, we can connect the 2 subpaths. */ + gboolean closed = gsk_path_measure_is_closed (measure); + gboolean need_move_to = !closed; + + if (start < measure->length) + gsk_path_builder_add_segment_chunk (self, measure, + TRUE, + start, measure->length); + else + need_move_to = TRUE; + + if (end > 0) + gsk_path_builder_add_segment_chunk (self, measure, + need_move_to, + 0, end); + if (start == end && closed) + gsk_path_builder_close (self); + } +} + +/** + * gsk_path_measure_get_point: + * @self: a `GskPathMeasure` + * @distance: the distance + * + * Returns a `GskPathPoint` representing the point at the given + * distance into the path. + * + * An empty path has no points, so `NULL` is returned in that case. + * + * Returns: (transfer full) (nullable): a newly allocated `GskPathPoint` + */ +GskPathPoint * +gsk_path_measure_get_point (GskPathMeasure *self, + float distance) +{ + gsize i; + float contour_offset; + float offset; + const GskContour *contour; + + g_return_val_if_fail (self != NULL, NULL); + + if (self->n_contours == 0) + return NULL; + + contour_offset = 0; + offset = gsk_path_measure_clamp_distance (self, distance); + + for (i = 0; i < self->n_contours - 1; i++) + { + if (offset < self->measures[i].length) + break; + + contour_offset += self->measures[i].length; + offset -= self->measures[i].length; + } + + g_assert (0 <= i && i < self->n_contours); + + offset = CLAMP (offset, 0, self->measures[i].length); + + contour = gsk_path_get_contour (self->path, i); + + return gsk_path_point_new (self, + contour, self->measures[i].contour_data, + contour_offset, offset); +} + +/** + * gsk_path_measure_get_closest_point: + * @self: a `GskPathMeasure` + * @point: the point to fond the closest point to + * @threshold: The maximum allowed distance between the path and @point. + * Use `INFINITY` to look for any point. + * + * Returns a `GskPathPoint` representing the point on the path + * that is closest to the given point. + * + * If no point on the path is closer than @threshold, `NULL` is returned. + * + * Returns: (transfer full) (nullable): a newly allocated `GskPathPoint` + */ +GskPathPoint * +gsk_path_measure_get_closest_point (GskPathMeasure *self, + const graphene_point_t *point, + float threshold) +{ + gssize best_idx; + float best_offset; + float best_contour_offset; + float contour_offset; + + contour_offset = 0; + best_idx = -1; + + for (gsize i = 0; i < self->n_contours; i++) + { + float distance, offset; + + if (gsk_contour_get_closest_point (gsk_path_get_contour (self->path, i), + self->measures[i].contour_data, + self->tolerance, + point, + threshold, + &distance, + NULL, + &offset, + NULL)) + { + best_idx = i; + best_offset = offset; + best_contour_offset = contour_offset; + + if (distance < self->tolerance) + break; + + threshold = distance - self->tolerance; + } + + contour_offset += self->measures[i].length; + } + + if (best_idx != -1) + return gsk_path_point_new (self, + gsk_path_get_contour (self->path, best_idx), + self->measures[best_idx].contour_data, + best_contour_offset, best_offset); + + return NULL; +} diff --git a/gsk/gskpathmeasure.h b/gsk/gskpathmeasure.h new file mode 100644 index 0000000000..cdf2740915 --- /dev/null +++ b/gsk/gskpathmeasure.h @@ -0,0 +1,73 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#pragma once + +#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + + +#include +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_PATH_MEASURE (gsk_path_measure_get_type ()) + +GDK_AVAILABLE_IN_ALL +GType gsk_path_measure_get_type (void) G_GNUC_CONST; +GDK_AVAILABLE_IN_ALL +GskPathMeasure * gsk_path_measure_new (GskPath *path); +GDK_AVAILABLE_IN_ALL +GskPathMeasure * gsk_path_measure_new_with_tolerance (GskPath *path, + float tolerance); + +GDK_AVAILABLE_IN_ALL +GskPathMeasure * gsk_path_measure_ref (GskPathMeasure *self); +GDK_AVAILABLE_IN_ALL +void gsk_path_measure_unref (GskPathMeasure *self); + +GDK_AVAILABLE_IN_ALL +GskPath * gsk_path_measure_get_path (GskPathMeasure *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_ALL +float gsk_path_measure_get_tolerance (GskPathMeasure *self) G_GNUC_PURE; + +GDK_AVAILABLE_IN_ALL +float gsk_path_measure_get_length (GskPathMeasure *self); +GDK_AVAILABLE_IN_ALL +gboolean gsk_path_measure_is_closed (GskPathMeasure *self); + +GDK_AVAILABLE_IN_ALL +gboolean gsk_path_measure_in_fill (GskPathMeasure *self, + const graphene_point_t *point, + GskFillRule fill_rule); + +GDK_AVAILABLE_IN_ALL +GskPathPoint * gsk_path_measure_get_point (GskPathMeasure *self, + float distance); + +GDK_AVAILABLE_IN_ALL +GskPathPoint * gsk_path_measure_get_closest_point (GskPathMeasure *self, + const graphene_point_t *point, + float threshold); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathMeasure, gsk_path_measure_unref) + +G_END_DECLS diff --git a/gsk/gskpathpoint.c b/gsk/gskpathpoint.c new file mode 100644 index 0000000000..86ee5ea22b --- /dev/null +++ b/gsk/gskpathpoint.c @@ -0,0 +1,195 @@ +#include "config.h" + +#include "gskpathpointprivate.h" + +#include "gskcontourprivate.h" +#include "gskpathmeasure.h" + +#include "gdk/gdkprivate.h" + + +/** + * GskPathPoint: + * + * `GskPathPoint` is an opaque, immutable type representing a point on a path. + * + * It can be queried for properties of the path at that point, such as its + * tangent or its curvature. + * + * To obtain a `GskPathPoint`, use [method@Gsk.PathMeasure.get_path_point] + * or [method@Gsk.PathMeasure.get_closest_point]. + */ + +struct _GskPathPoint +{ + guint ref_count; + + GskPathMeasure *measure; + const GskContour *contour; + gpointer measure_data; + float contour_offset; /* distance from beginning of path to contour */ + float offset; /* offset of point inside contour */ +}; + +G_DEFINE_BOXED_TYPE (GskPathPoint, gsk_path_point, + gsk_path_point_ref, + gsk_path_point_unref) + +GskPathPoint * +gsk_path_point_new (GskPathMeasure *measure, + const GskContour *contour, + gpointer measure_data, + float contour_offset, + float offset) +{ + GskPathPoint *self; + + self = g_new0 (GskPathPoint, 1); + + self->ref_count = 1; + + self->measure = gsk_path_measure_ref (measure); + self->contour = contour; + self->measure_data = measure_data; + self->contour_offset = contour_offset; + self->offset = offset; + + return self; +} + +/** + * gsk_path_point_ref: + * @self: a `GskPathPoint` + * + * Increases the reference count of a `GskPathPoint` by one. + * + * Returns: the passed in `GskPathPoint` + */ +GskPathPoint * +gsk_path_point_ref (GskPathPoint *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + self->ref_count++; + + return self; +} + +/** + * gsk_path_point_unref: + * @self: a `GskPathPoint` + * + * Decreases the reference count of a `GskPathPoint` by one. + * + * If the resulting reference count is zero, frees the path_measure. + */ +void +gsk_path_point_unref (GskPathPoint *self) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (self->ref_count > 0); + + self->ref_count--; + if (self->ref_count > 0) + return; + + gsk_path_measure_unref (self->measure); + + g_free (self); +} + +GskPathMeasure * +gsk_path_point_get_measure (GskPathPoint *self) +{ + return self->measure; +} + +/** + * gsk_path_point_get_distance: + * @self: a `GskPathPoint` + * + * Returns the distance of the given point from the start of the path. + * + * This is the length of the contour from the beginning of the path + * to the point. + * + * Returns: The offset of point in path + */ +float +gsk_path_point_get_distance (GskPathPoint *self) +{ + return self->contour_offset + self->offset; +} + +/** + * gsk_path_point_get_position: + * @self: a `GskPathPoint` + * @position: (out caller-allocates): Return location for + * the coordinates of the point + * + * Gets the position of the point. + */ +void +gsk_path_point_get_position (GskPathPoint *self, + graphene_point_t *position) +{ + gsk_contour_get_point (self->contour, + self->measure_data, + self->offset, + GSK_PATH_END, + position, NULL); +} + +/** + * gsk_path_point_get_tangent: + * @self: a `GskPathPoint` + * @direction: the direction for which to return the tangent + * @tangent: (out caller-allocates): Return location for + * the tangent at the point + * + * Gets the tangent of the path at the point. + * + * Note that certain points on a path may not have a single + * tangent, such as sharp turns. At such points, there are + * two tangents -- the direction of the path going into the + * point, and the direction coming out of it. + * + * The @direction argument lets you choose which one to get. + */ +void +gsk_path_point_get_tangent (GskPathPoint *self, + GskPathDirection direction, + graphene_vec2_t *tangent) +{ + gsk_contour_get_point (self->contour, + self->measure_data, + self->offset, + direction, + NULL, tangent); +} + +/** + * gsk_path_point_get_curvature: + * @self: a `GskPathPoint` + * @center: (out caller-allocates): Return location for + * the center of the osculating circle + * + * Calculates the curvature at the point @distance units into + * the path. + * + * Optionally, returns the center of the osculating circle as well. + * + * If the curvature is infinite (at line segments), zero is returned, + * and @center is not modified. + * + * Returns: The curvature of the path at the given point + */ +float +gsk_path_point_get_curvature (GskPathPoint *self, + graphene_point_t *center) +{ + return gsk_contour_get_curvature (self->contour, + self->measure_data, + self->offset, + center); +} diff --git a/gsk/gskpathpoint.h b/gsk/gskpathpoint.h new file mode 100644 index 0000000000..87962a6dca --- /dev/null +++ b/gsk/gskpathpoint.h @@ -0,0 +1,43 @@ +#pragma once + +#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + + +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_PATH_POINT (gsk_path_point_get_type ()) + +GDK_AVAILABLE_IN_ALL +GType gsk_path_point_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_ALL +GskPathPoint * gsk_path_point_ref (GskPathPoint *self); +GDK_AVAILABLE_IN_ALL +void gsk_path_point_unref (GskPathPoint *self); + +GDK_AVAILABLE_IN_ALL +GskPathMeasure * gsk_path_point_get_measure (GskPathPoint *self); + +GDK_AVAILABLE_IN_ALL +float gsk_path_point_get_distance (GskPathPoint *self); + +GDK_AVAILABLE_IN_ALL +void gsk_path_point_get_position (GskPathPoint *self, + graphene_point_t *position); + +GDK_AVAILABLE_IN_ALL +void gsk_path_point_get_tangent (GskPathPoint *self, + GskPathDirection direction, + graphene_vec2_t *tangent); + +GDK_AVAILABLE_IN_ALL +float gsk_path_point_get_curvature (GskPathPoint *self, + graphene_point_t *center); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathPoint, gsk_path_point_unref) + +G_END_DECLS diff --git a/gsk/gskpathpointprivate.h b/gsk/gskpathpointprivate.h new file mode 100644 index 0000000000..f81d1792d4 --- /dev/null +++ b/gsk/gskpathpointprivate.h @@ -0,0 +1,16 @@ +#pragma once + +#include "gskpathpoint.h" +#include "gskcontourprivate.h" +#include "gskpathmeasure.h" + +G_BEGIN_DECLS + +GskPathPoint * gsk_path_point_new (GskPathMeasure *measure, + const GskContour *contour, + gpointer measure_data, + float contour_offset, + float offset); + +G_END_DECLS + diff --git a/gsk/gsktypes.h b/gsk/gsktypes.h index efe66b5086..1a3b3e32c5 100644 --- a/gsk/gsktypes.h +++ b/gsk/gsktypes.h @@ -27,6 +27,8 @@ typedef struct _GskPath GskPath; typedef struct _GskPathBuilder GskPathBuilder; +typedef struct _GskPathMeasure GskPathMeasure; +typedef struct _GskPathPoint GskPathPoint; typedef struct _GskRenderer GskRenderer; typedef struct _GskTransform GskTransform; diff --git a/gsk/meson.build b/gsk/meson.build index e36924dfc1..574b25a265 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -28,6 +28,8 @@ gsk_public_sources = files([ 'gskglshader.c', 'gskpath.c', 'gskpathbuilder.c', + 'gskpathmeasure.c', + 'gskpathpoint.c', 'gskrenderer.c', 'gskrendernode.c', 'gskrendernodeimpl.c', @@ -73,6 +75,8 @@ gsk_public_headers = files([ 'gskglshader.h', 'gskpath.h', 'gskpathbuilder.h', + 'gskpathmeasure.h', + 'gskpathpoint.h', 'gskrenderer.h', 'gskrendernode.h', 'gskroundedrect.h',