diff --git a/gsk/gsk.h b/gsk/gsk.h index 90c4d1c259..9f2ee451dd 100644 --- a/gsk/gsk.h +++ b/gsk/gsk.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/gsk/gskcontour.c b/gsk/gskcontour.c index 9541a290ad..ca708d0a80 100644 --- a/gsk/gskcontour.c +++ b/gsk/gskcontour.c @@ -76,6 +76,24 @@ struct _GskContourClass float (* get_curvature) (const GskContour *contour, GskRealPathPoint *point, graphene_point_t *center); + gpointer (* init_measure) (const GskContour *contour, + float tolerance, + float *out_length); + void (* free_measure) (const GskContour *contour, + gpointer measure_data); + void (* add_segment) (const GskContour *contour, + GskPathBuilder *builder, + gpointer measure_data, + gboolean emit_move_to, + float start, + float end); + void (* get_point) (const GskContour *contour, + gpointer measure_data, + float offset, + GskRealPathPoint *result); + float (* get_distance) (const GskContour *contour, + GskRealPathPoint *point, + gpointer measure_data); }; /* {{{ Utilities */ @@ -367,6 +385,274 @@ 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_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 int gsk_standard_contour_get_winding (const GskContour *contour, const graphene_point_t *point) @@ -456,6 +742,38 @@ gsk_standard_contour_get_closest_point (const GskContour *contour, return FALSE; } +static void +gsk_standard_contour_get_point (const GskContour *contour, + gpointer measure_data, + float distance, + GskRealPathPoint *result) +{ + GArray *array = measure_data; + unsigned int idx; + GskStandardContourMeasure *measure; + float fraction, t; + + if (array->len == 0) + { + result->data.std.idx = 0; + result->data.std.t = 0; + return; + } + + if (!g_array_binary_search (array, &distance, gsk_standard_contour_find_measure, &idx)) + idx = array->len - 1; + + measure = &g_array_index (array, GskStandardContourMeasure, idx); + + fraction = (distance - measure->start) / (measure->end - measure->start); + t = measure->start_progress + fraction * (measure->end_progress - measure->start_progress); + + g_assert (t >= 0 && t <= 1); + + result->data.std.idx = measure->op; + result->data.std.t = t; +} + static void gsk_standard_contour_get_position (const GskContour *contour, GskRealPathPoint *point, @@ -542,6 +860,37 @@ gsk_standard_contour_get_curvature (const GskContour *contour, return gsk_curve_get_curvature (&curve, point->data.std.t, center); } +static float +gsk_standard_contour_get_distance (const GskContour *contour, + GskRealPathPoint *point, + gpointer measure_data) +{ + GArray *array = measure_data; + + if (G_UNLIKELY (point->data.std.idx == 0)) + return 0; + + for (unsigned int i = 0; i < array->len; i++) + { + GskStandardContourMeasure *measure = &g_array_index (array, GskStandardContourMeasure, i); + float fraction; + + if (measure->op != point->data.std.idx) + continue; + + if (measure->end_progress < point->data.std.t) + continue; + + g_assert (measure->op == point->data.std.idx); + g_assert (measure->start_progress <= point->data.std.t && point->data.std.t <= measure->end_progress); + + fraction = (point->data.std.t - measure->start_progress) / (measure->end_progress - measure->start_progress); + return measure->start + fraction * (measure->end - measure->start); + } + + return 0; +} + static const GskContourClass GSK_STANDARD_CONTOUR_CLASS = { sizeof (GskStandardContour), @@ -560,6 +909,11 @@ static const GskContourClass GSK_STANDARD_CONTOUR_CLASS = gsk_standard_contour_get_position, gsk_standard_contour_get_tangent, gsk_standard_contour_get_curvature, + gsk_standard_contour_init_measure, + gsk_standard_contour_free_measure, + gsk_standard_contour_add_segment, + gsk_standard_contour_get_point, + gsk_standard_contour_get_distance, }; /* You must ensure the contour has enough size allocated, @@ -681,6 +1035,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, @@ -689,6 +1058,17 @@ gsk_contour_get_start_end (const GskContour *self, self->klass->get_start_end (self, start, end); } +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, const graphene_point_t *point) @@ -706,6 +1086,15 @@ gsk_contour_get_closest_point (const GskContour *self, return self->klass->get_closest_point (self, point, threshold, result, out_dist); } +void +gsk_contour_get_point (const GskContour *self, + gpointer measure_data, + float offset, + GskRealPathPoint *result) +{ + self->klass->get_point (self, measure_data, offset, result); +} + void gsk_contour_get_position (const GskContour *self, GskRealPathPoint *point, @@ -731,6 +1120,14 @@ gsk_contour_get_curvature (const GskContour *self, return self->klass->get_curvature (self, point, center); } +float +gsk_contour_get_distance (const GskContour *self, + GskRealPathPoint *point, + gpointer measure_data) +{ + return self->klass->get_distance (self, point, measure_data); +} + /* }}} */ /* vim:set foldmethod=marker expandtab: */ diff --git a/gsk/gskcontourprivate.h b/gsk/gskcontourprivate.h index 3b6365de9a..b749c7f6e4 100644 --- a/gsk/gskcontourprivate.h +++ b/gsk/gskcontourprivate.h @@ -57,14 +57,17 @@ void gsk_contour_get_start_end (const GskContou graphene_point_t *end); int gsk_contour_get_winding (const GskContour *self, const graphene_point_t *point); + gboolean gsk_contour_get_closest_point (const GskContour *self, const graphene_point_t *point, float threshold, GskRealPathPoint *result, float *out_dist); + void gsk_contour_get_position (const GskContour *self, GskRealPathPoint *point, graphene_point_t *pos); + void gsk_contour_get_tangent (const GskContour *self, GskRealPathPoint *point, GskPathDirection direction, @@ -72,5 +75,23 @@ void gsk_contour_get_tangent (const GskContou float gsk_contour_get_curvature (const GskContour *self, GskRealPathPoint *point, graphene_point_t *center); +gpointer gsk_contour_init_measure (const GskContour *self, + float tolerance, + float *out_length); +void gsk_contour_free_measure (const GskContour *self, + gpointer data); +void gsk_contour_add_segment (const GskContour *self, + GskPathBuilder *builder, + gpointer measure_data, + gboolean emit_move_to, + float start, + float end); +void gsk_contour_get_point (const GskContour *self, + gpointer measure_data, + float offset, + GskRealPathPoint *result); +float gsk_contour_get_distance (const GskContour *self, + GskRealPathPoint *point, + gpointer measure_data); G_END_DECLS diff --git a/gsk/gskpathbuilder.h b/gsk/gskpathbuilder.h index 8e32564dc3..bd94ece72b 100644 --- a/gsk/gskpathbuilder.h +++ b/gsk/gskpathbuilder.h @@ -69,6 +69,12 @@ void gsk_path_builder_add_circle (GskPathBuilder const graphene_point_t *center, float radius); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_add_segment (GskPathBuilder *self, + GskPathMeasure *measure, + float start, + float end); + GDK_AVAILABLE_IN_4_14 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..5d46c051c8 --- /dev/null +++ b/gsk/gskpathmeasure.c @@ -0,0 +1,426 @@ +/* + * 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: + * + * `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 precision 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 + * + * Since: 4.14 + */ +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 + * + * Since: 4.14 + */ +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`. + * + * Since: 4.14 + */ +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. + * + * Since: 4.14 + */ +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 + * + * Since: 4.14 + */ +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 + * + * Since: 4.14 + */ +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 + * + * Since: 4.14 + */ +float +gsk_path_measure_get_length (GskPathMeasure *self) +{ + g_return_val_if_fail (self != NULL, 0); + + return self->length; +} + +static float +gsk_path_measure_clamp_distance (GskPathMeasure *self, + float distance) +{ + if (isnan (distance)) + return 0; + + return CLAMP (distance, 0, self->length); +} + +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. + * + * Since: 4.14 + */ +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_is_closed (measure->path); + 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 + * @result: (out caller-allocates): return location for the result + * + * Sets @result to the point at the given distance into the path. + * + * An empty path has no points, so `FALSE` is returned in that case. + * + * Returns: `TRUE` if @result was set + * + * Since: 4.14 + */ +gboolean +gsk_path_measure_get_point (GskPathMeasure *self, + float distance, + GskPathPoint *result) +{ + GskRealPathPoint *res = (GskRealPathPoint *) result; + gsize i; + float offset; + const GskContour *contour; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (result != NULL, FALSE); + + if (self->n_contours == 0) + return FALSE; + + offset = gsk_path_measure_clamp_distance (self, distance); + + for (i = 0; i < self->n_contours - 1; i++) + { + if (offset < self->measures[i].length) + break; + + 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); + + gsk_contour_get_point (contour, self->measures[i].contour_data, offset, res); + + res->path = self->path; + res->contour = i; + + return TRUE; +} + +/** + * gsk_path_measure_get_distance: + * @self: a `GskPathMeasure` + * @point: a `GskPathPoint on the path of @self + * + * Returns the distance from the beginning of the path + * to @point. + * + * Returns: the distance of @point + * + * Since: 4.14 + */ +float +gsk_path_measure_get_distance (GskPathMeasure *self, + const GskPathPoint *point) +{ + GskRealPathPoint *p = (GskRealPathPoint *)point; + const GskContour *contour = gsk_path_get_contour (self->path, p->contour); + float contour_offset = 0; + + g_return_val_if_fail (self != NULL, 0); + g_return_val_if_fail (self->path == p->path, 0); + g_return_val_if_fail (contour != NULL, 0); + + for (gsize i = 0; i < self->n_contours; i++) + { + if (contour == gsk_path_get_contour (self->path, i)) + return contour_offset + gsk_contour_get_distance (contour, + p, + self->measures[i].contour_data); + + contour_offset += self->measures[i].length; + } + + g_return_val_if_reached (0); +} diff --git a/gsk/gskpathmeasure.h b/gsk/gskpathmeasure.h new file mode 100644 index 0000000000..a377ed07ad --- /dev/null +++ b/gsk/gskpathmeasure.h @@ -0,0 +1,66 @@ +/* + * 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_4_14 +GType gsk_path_measure_get_type (void) G_GNUC_CONST; +GDK_AVAILABLE_IN_4_14 +GskPathMeasure * gsk_path_measure_new (GskPath *path); +GDK_AVAILABLE_IN_4_14 +GskPathMeasure * gsk_path_measure_new_with_tolerance (GskPath *path, + float tolerance); + +GDK_AVAILABLE_IN_4_14 +GskPathMeasure * gsk_path_measure_ref (GskPathMeasure *self); +GDK_AVAILABLE_IN_4_14 +void gsk_path_measure_unref (GskPathMeasure *self); + +GDK_AVAILABLE_IN_4_14 +GskPath * gsk_path_measure_get_path (GskPathMeasure *self) G_GNUC_PURE; +GDK_AVAILABLE_IN_4_14 +float gsk_path_measure_get_tolerance (GskPathMeasure *self) G_GNUC_PURE; + +GDK_AVAILABLE_IN_4_14 +float gsk_path_measure_get_length (GskPathMeasure *self); + +GDK_AVAILABLE_IN_4_14 +gboolean gsk_path_measure_get_point (GskPathMeasure *self, + float distance, + GskPathPoint *result); + +GDK_AVAILABLE_IN_4_14 +float gsk_path_measure_get_distance (GskPathMeasure *self, + const GskPathPoint *point); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathMeasure, gsk_path_measure_unref) + +G_END_DECLS diff --git a/gsk/gskpathpoint.c b/gsk/gskpathpoint.c index 1fa59332ea..ad504f4cdd 100644 --- a/gsk/gskpathpoint.c +++ b/gsk/gskpathpoint.c @@ -22,6 +22,7 @@ #include "gskpathpointprivate.h" #include "gskcontourprivate.h" +#include "gskpathmeasure.h" #include "gdk/gdkprivate.h" @@ -33,7 +34,8 @@ * 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.Path.get_closest_point]. + * To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point] + * or [method@Gsk.PathMeasure.get_point]. * * Note that `GskPathPoint` structs are meant to be stack-allocated, and * don't a reference to the path object they are obtained from. It is the diff --git a/gsk/gsktypes.h b/gsk/gsktypes.h index 2fba08a7cf..7733b46872 100644 --- a/gsk/gsktypes.h +++ b/gsk/gsktypes.h @@ -27,6 +27,7 @@ typedef struct _GskPath GskPath; typedef struct _GskPathBuilder GskPathBuilder; +typedef struct _GskPathMeasure GskPathMeasure; typedef struct _GskPathPoint GskPathPoint; typedef struct _GskRenderer GskRenderer; typedef struct _GskStroke GskStroke; diff --git a/gsk/meson.build b/gsk/meson.build index de2cd1bbe1..4c786cc8f7 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -28,6 +28,7 @@ gsk_public_sources = files([ 'gskglshader.c', 'gskpath.c', 'gskpathbuilder.c', + 'gskpathmeasure.c', 'gskpathpoint.c', 'gskrenderer.c', 'gskrendernode.c', @@ -75,6 +76,7 @@ gsk_public_headers = files([ 'gskglshader.h', 'gskpath.h', 'gskpathbuilder.h', + 'gskpathmeasure.h', 'gskpathpoint.h', 'gskrenderer.h', 'gskrendernode.h',