From dce77dc2a5032d100dfb313a23b143cc893681a6 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 28 Nov 2020 05:36:53 +0100 Subject: [PATCH] path: Split GskPathBuilder into its own file ... and add missing API docs. --- gsk/gsk.h | 1 + gsk/gskpath.c | 632 +++++++++---------------------------------- gsk/gskpath.h | 48 ---- gsk/gskpathbuilder.c | 458 +++++++++++++++++++++++++++++++ gsk/gskpathbuilder.h | 80 ++++++ gsk/gskpathmeasure.c | 13 +- gsk/gskpathprivate.h | 48 +++- gsk/gsktypes.h | 1 + gsk/meson.build | 2 + 9 files changed, 715 insertions(+), 568 deletions(-) create mode 100644 gsk/gskpathbuilder.c create mode 100644 gsk/gskpathbuilder.h diff --git a/gsk/gsk.h b/gsk/gsk.h index 9b9ddaceae..b26fdc66be 100644 --- a/gsk/gsk.h +++ b/gsk/gsk.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/gsk/gskpath.c b/gsk/gskpath.c index d87e967af6..48f51fabc1 100644 --- a/gsk/gskpath.c +++ b/gsk/gskpath.c @@ -21,15 +21,10 @@ #include "gskpathprivate.h" +#include "gskpathbuilder.h" #include "gsksplineprivate.h" -typedef enum -{ - GSK_PATH_FLAT, - GSK_PATH_CLOSED -} GskPathFlags; - typedef struct _GskContour GskContour; typedef struct _GskContourClass GskContourClass; @@ -117,10 +112,6 @@ gsk_contour_get_size_default (const GskContour *contour) return contour->klass->struct_size; } -static GskContour * -gsk_path_builder_add_contour_by_klass (GskPathBuilder *builder, - const GskContourClass *klass); - static void gsk_find_point_on_line (const graphene_point_t *a, const graphene_point_t *b, @@ -320,7 +311,7 @@ gsk_rect_contour_get_closest_point (const GskContour *contour, 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; @@ -377,7 +368,7 @@ gsk_rect_contour_get_closest_point (const GskContour *contour, *out_pos = p; if (out_offset) - *out_offset = (t.x == 0.0 && self->width > 0 ? 2 - t.y : t.y) * ABS (self->height) + + *out_offset = (t.x == 0.0 && self->width > 0 ? 2 - t.y : t.y) * ABS (self->height) + (t.y == 1.0 ? 2 - t.x : t.x) * ABS (self->width); if (out_tangent) @@ -487,20 +478,21 @@ static const GskContourClass GSK_RECT_CONTOUR_CLASS = gsk_rect_contour_add_segment }; -static void -gsk_rect_contour_init (GskContour *contour, - float x, - float y, - float width, - float height) +GskContour * +gsk_rect_contour_new (const graphene_rect_t *rect) { - GskRectContour *self = (GskRectContour *) contour; + GskRectContour *self; + + self = g_new0 (GskRectContour, 1); self->contour.klass = &GSK_RECT_CONTOUR_CLASS; - self->x = x; - self->y = y; - self->width = width; - self->height = height; + + self->x = rect->origin.x; + self->y = rect->origin.y; + self->width = rect->size.width; + self->height = rect->size.height; + + return (GskContour *) self; } /* CIRCLE CONTOUR */ @@ -737,13 +729,6 @@ gsk_circle_contour_copy (const GskContour *contour, *target = *self; } -static void -gsk_circle_contour_init (GskContour *contour, - const graphene_point_t *center, - float radius, - float start_angle, - float end_angle); - static void gsk_circle_contour_add_segment (const GskContour *contour, GskPathBuilder *builder, @@ -756,12 +741,10 @@ gsk_circle_contour_add_segment (const GskContour *contour, float length = self->radius * DEG_TO_RAD (delta); GskContour *segment; - segment = gsk_path_builder_add_contour_by_klass (builder, contour->klass); - - gsk_circle_contour_init (segment, - &self->center, self->radius, - self->start_angle + start/length * delta, - self->start_angle + end/length * delta); + segment = gsk_circle_contour_new (&self->center, self->radius, + self->start_angle + start/length * delta, + self->start_angle + end/length * delta); + gsk_path_builder_add_contour (builder, segment); } static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS = @@ -781,14 +764,17 @@ static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS = gsk_circle_contour_add_segment }; -static void -gsk_circle_contour_init (GskContour *contour, - const graphene_point_t *center, - float radius, - float start_angle, - float end_angle) +GskContour * +gsk_circle_contour_new (const graphene_point_t *center, + float radius, + float start_angle, + float end_angle) { - GskCircleContour *self = (GskCircleContour *) contour; + GskCircleContour *self; + + self = g_new0 (GskCircleContour, 1); + + self->contour.klass = &GSK_CIRCLE_CONTOUR_CLASS; g_assert (fabs (start_angle - end_angle) <= 360); @@ -797,17 +783,12 @@ gsk_circle_contour_init (GskContour *contour, self->radius = radius; self->start_angle = start_angle; self->end_angle = end_angle; + + return (GskContour *) self; } /* STANDARD CONTOUR */ -typedef struct _GskStandardOperation GskStandardOperation; - -struct _GskStandardOperation { - GskPathOperation op; - gsize point; /* index into points array of the start point (last point of previous op) */ -}; - typedef struct _GskStandardContour GskStandardContour; struct _GskStandardContour { @@ -1212,7 +1193,7 @@ gsk_standard_contour_get_closest_point (const GskContour *contour, gsk_standard_contour_measure_get_point (self, measure->op, found_progress, &p, out_tangent ? &t : NULL); dist = graphene_point_distance (point, &p, NULL, NULL); - /* double check that the point actually is closer */ + /* double check that the point actually is closer */ //g_print ("!!! %zu: (%g-%g) dist %g\n", i, measure->start, measure->end, dist); if (dist <= threshold) { @@ -1427,12 +1408,12 @@ static const GskContourClass GSK_STANDARD_CONTOUR_CLASS = * see gsk_standard_contour_compute_size() */ static void -gsk_standard_contour_init (GskContour *contour, - GskPathFlags flags, - const GskStandardOperation *ops, - gsize n_ops, - const graphene_point_t *points, - gsize n_points) +gsk_standard_contour_init (GskContour *contour, + GskPathFlags flags, + const GskStandardOperation *ops, + gsize n_ops, + const graphene_point_t *points, + gsize n_points) { GskStandardContour *self = (GskStandardContour *) contour; @@ -1446,6 +1427,22 @@ gsk_standard_contour_init (GskContour *contour, memcpy (self->points, points, sizeof (graphene_point_t) * n_points); } +GskContour * +gsk_standard_contour_new (GskPathFlags flags, + const GskStandardOperation *ops, + gsize n_ops, + const graphene_point_t *points, + gsize n_points) +{ + GskContour *contour; + + contour = g_malloc0 (gsk_standard_contour_compute_size (n_ops, n_points)); + + gsk_standard_contour_init (contour, flags, ops, n_ops, points, n_points); + + return contour; +} + /* CONTOUR */ static gsize @@ -1454,24 +1451,6 @@ gsk_contour_get_size (const GskContour *contour) return contour->klass->get_size (contour); } -static void -gsk_contour_copy (GskContour *dest, - const GskContour *src) -{ - src->klass->copy (src, dest); -} - -static GskContour * -gsk_contour_dup (const GskContour *src) -{ - GskContour *copy; - - copy = g_malloc0 (gsk_contour_get_size (src)); - gsk_contour_copy (copy, src); - - return copy; -} - static gboolean gsk_contour_foreach (const GskContour *contour, float tolerance, @@ -1540,18 +1519,78 @@ gsk_contour_get_closest_point (GskPath *path, out_tangent); } +void +gsk_contour_add_segment (const GskContour *self, + GskPathBuilder *builder, + gpointer measure_data, + float start, + float end) +{ + self->klass->add_segment (self, builder, measure_data, start, end); +} + +static inline void +gsk_contour_copy (GskContour *dest, + const GskContour *src) +{ + src->klass->copy (src, dest); +} + +GskContour * +gsk_contour_dup (const GskContour *src) +{ + GskContour *copy; + + copy = g_malloc0 (gsk_contour_get_size (src)); + gsk_contour_copy (copy, src); + + return copy; +} + /* PATH */ -static GskPath * -gsk_path_alloc (gsize extra_size) +GskPath * +gsk_path_new_from_contours (const GSList *contours) { - GskPath *self; + GskPath *path; + const GSList *l; + gsize size; + gsize n_contours; + guint8 *contour_data; + GskPathFlags flags; - self = g_malloc0 (sizeof (GskPath) + extra_size); - self->ref_count = 1; - self->n_contours = 0; + flags = GSK_PATH_CLOSED | GSK_PATH_FLAT; + size = 0; + n_contours = 0; + for (l = contours; l; l = l->next) + { + GskContour *contour = l->data; - return self; + n_contours++; + size += sizeof (GskContour *); + size += gsk_contour_get_size (contour); + flags &= contour->klass->get_flags (contour); + } + + path = g_malloc0 (sizeof (GskPath) + size); + path->ref_count = 1; + path->flags = flags; + path->n_contours = n_contours; + contour_data = (guint8 *) &path->contours[n_contours]; + n_contours = 0; + + for (l = contours; l; l = l->next) + { + GskContour *contour = l->data; + + path->contours[n_contours] = (GskContour *) contour_data; + gsk_contour_copy ((GskContour *) contour_data, contour); + size = gsk_contour_get_size (contour); + contour_data += size; + n_contours++; + } + + return path; } /** @@ -1647,6 +1686,13 @@ gsk_path_unref (GskPath *self) g_free (self); } +const GskContour * +gsk_path_get_contour (GskPath *path, + gsize i) +{ + return path->contours[i]; +} + /** * gsk_path_print: * @self: a `GskPath` @@ -1890,425 +1936,3 @@ gsk_path_foreach_with_tolerance (GskPath *self, return TRUE; } - -/* BUILDER */ - -/** - * GskPathBuilder: - * - * A `GskPathBuilder` struct is an opaque struct. It is meant to - * not be kept around and only be used to create new `GskPath` - * objects. - */ - -struct _GskPathBuilder -{ - int ref_count; - - GSList *contours; /* (reverse) list of already recorded contours */ - - GskPathFlags flags; /* flags for the current path */ - GArray *ops; /* operations for current contour - size == 0 means no current contour */ - GArray *points; /* points for the operations */ -}; - -G_DEFINE_BOXED_TYPE (GskPathBuilder, - gsk_path_builder, - gsk_path_builder_ref, - gsk_path_builder_unref) - - -void -gsk_path_builder_add_contour (GskPathBuilder *builder, - GskPath *path, - gsize i) -{ - GskContour *copy; - - copy = gsk_contour_dup (path->contours[i]); - builder->contours = g_slist_prepend (builder->contours, copy); -} - -void -gsk_path_builder_add_contour_segment (GskPathBuilder *builder, - GskPath *path, - gsize i, - gpointer measure_data, - float start, - float end) -{ - const GskContour *self = path->contours[i]; - - self->klass->add_segment (self, builder, measure_data, start, end); -} - -/** - * gsk_path_builder_new: - * - * Create a new `GskPathBuilder` object. The resulting builder - * would create an empty `GskPath`. Use addition functions to add - * types to it. - * - * Returns: a new `GskPathBuilder` - **/ -GskPathBuilder * -gsk_path_builder_new (void) -{ - GskPathBuilder *builder; - - builder = g_slice_new0 (GskPathBuilder); - builder->ref_count = 1; - - builder->ops = g_array_new (FALSE, FALSE, sizeof (GskStandardOperation)); - builder->points = g_array_new (FALSE, FALSE, sizeof (graphene_point_t)); - return builder; -} - -/** - * gsk_path_builder_ref: - * @builder: a `GskPathBuilder` - * - * Acquires a reference on the given @builder. - * - * This function is intended primarily for bindings. `GskPathBuilder` objects - * should not be kept around. - * - * Returns: (transfer none): the given `GskPathBuilder` with - * its reference count increased - */ -GskPathBuilder * -gsk_path_builder_ref (GskPathBuilder *builder) -{ - g_return_val_if_fail (builder != NULL, NULL); - g_return_val_if_fail (builder->ref_count > 0, NULL); - - builder->ref_count += 1; - - return builder; -} - -static void -gsk_path_builder_append_current (GskPathBuilder *builder, - GskPathOperation op, - gsize n_points, - const graphene_point_t *points) -{ - g_assert (builder->ops->len > 0); - g_assert (builder->points->len > 0); - g_assert (n_points > 0); - - g_array_append_vals (builder->ops, &(GskStandardOperation) { op, builder->points->len - 1 }, 1); - g_array_append_vals (builder->points, points, n_points); -} - -static void -gsk_path_builder_end_current (GskPathBuilder *builder) -{ - GskContour *contour; - - if (builder->ops->len == 0) - return; - - contour = g_malloc0 (gsk_standard_contour_compute_size (builder->ops->len, builder->points->len)); - gsk_standard_contour_init (contour, - 0, - (GskStandardOperation *) builder->ops->data, - builder->ops->len, - (graphene_point_t *) builder->points->data, - builder->points->len); - builder->contours = g_slist_prepend (builder->contours, contour); - - g_array_set_size (builder->ops, 0); - g_array_set_size (builder->points, 0); -} - -static void -gsk_path_builder_clear (GskPathBuilder *builder) -{ - gsk_path_builder_end_current (builder); - - g_slist_free_full (builder->contours, g_free); - builder->contours = NULL; -} - -/** - * gsk_path_builder_unref: - * @builder: a `GskPathBuilder` - * - * Releases a reference on the given @builder. - */ -void -gsk_path_builder_unref (GskPathBuilder *builder) -{ - g_return_if_fail (builder != NULL); - g_return_if_fail (builder->ref_count > 0); - - builder->ref_count -= 1; - - if (builder->ref_count > 0) - return; - - gsk_path_builder_clear (builder); - g_array_unref (builder->ops); - g_array_unref (builder->points); - g_slice_free (GskPathBuilder, builder); -} - -/** - * gsk_path_builder_free_to_path: (skip) - * @builder: a `GskPathBuilder` - * - * Creates a new `GskPath` from the current state of the - * given @builder, and frees the @builder instance. - * - * Returns: (transfer full): the newly created `GskPath` - * with all the contours added to @builder - */ -GskPath * -gsk_path_builder_free_to_path (GskPathBuilder *builder) -{ - GskPath *res; - - g_return_val_if_fail (builder != NULL, NULL); - - res = gsk_path_builder_to_path (builder); - - gsk_path_builder_unref (builder); - - return res; -} - -/** - * gsk_path_builder_to_path: - * @builder: a `GskPathBuilder` - * - * Creates a new `GskPath` from the given @builder. - * - * The given `GskPathBuilder` is reset once this function returns; - * you cannot call this function multiple times on the same @builder instance. - * - * This function is intended primarily for bindings. C code should use - * gsk_path_builder_free_to_path(). - * - * Returns: (transfer full): the newly created `GskPath` - * with all the contours added to @builder - */ -GskPath * -gsk_path_builder_to_path (GskPathBuilder *builder) -{ - GskPath *path; - GSList *l; - gsize size; - gsize n_contours; - guint8 *contour_data; - GskPathFlags flags; - - g_return_val_if_fail (builder != NULL, NULL); - - gsk_path_builder_end_current (builder); - - builder->contours = g_slist_reverse (builder->contours); - flags = GSK_PATH_CLOSED | GSK_PATH_FLAT; - size = 0; - n_contours = 0; - for (l = builder->contours; l; l = l->next) - { - GskContour *contour = l->data; - - n_contours++; - size += sizeof (GskContour *); - size += gsk_contour_get_size (contour); - flags &= contour->klass->get_flags (contour); - } - - path = gsk_path_alloc (size); - path->flags = flags; - path->n_contours = n_contours; - contour_data = (guint8 *) &path->contours[n_contours]; - n_contours = 0; - - for (l = builder->contours; l; l = l->next) - { - GskContour *contour = l->data; - - path->contours[n_contours] = (GskContour *) contour_data; - gsk_contour_copy ((GskContour *) contour_data, contour); - size = gsk_contour_get_size (contour); - contour_data += size; - n_contours++; - } - - gsk_path_builder_clear (builder); - - return path; -} - -/** - * gsk_path_builder_add_path: - * @builder: a #GskPathBuilder - * @path: (transfer none): the path to append - * - * Appends all of @path to @builder. - **/ -void -gsk_path_builder_add_path (GskPathBuilder *builder, - GskPath *path) -{ - gsize i; - - g_return_if_fail (builder != NULL); - g_return_if_fail (path != NULL); - - for (i = 0; i < path->n_contours; i++) - { - gsk_path_builder_add_contour (builder, path, i); - } -} - -static GskContour * -gsk_path_builder_add_contour_by_klass (GskPathBuilder *builder, - const GskContourClass *klass) -{ - GskContour *contour; - - gsk_path_builder_end_current (builder); - - contour = g_malloc0 (klass->struct_size); - builder->contours = g_slist_prepend (builder->contours, contour); - - return contour; -} - -/** - * gsk_path_builder_add_rect: - * @builder: A `GskPathBuilder` - * @rect: The rectangle to create a path for - * - * Creates a path representing the given rectangle. - * - * If the width or height of the rectangle is negative, the start - * point will be on the right or bottom, respectively. - * - * If the the width or height are 0, the path will be a closed - * horizontal or vertical line. If both are 0, it'll be a closed dot. - * - * Returns: a new `GskPath` representing a rectangle - **/ -void -gsk_path_builder_add_rect (GskPathBuilder *builder, - const graphene_rect_t *rect) -{ - GskContour *contour; - - g_return_if_fail (builder != NULL); - - contour = gsk_path_builder_add_contour_by_klass (builder, &GSK_RECT_CONTOUR_CLASS); - gsk_rect_contour_init (contour, - rect->origin.x, rect->origin.y, - rect->size.width, rect->size.height); -} - -/** - * gsk_path_builder_add_circle: - * @builder: a #GskPathBuilder - * @center: the center of the circle - * @radius: the radius of the circle - * - * Adds a circle with the @center and @radius. - **/ -void -gsk_path_builder_add_circle (GskPathBuilder *builder, - const graphene_point_t *center, - float radius) -{ - GskContour *contour; - - g_return_if_fail (builder != NULL); - g_return_if_fail (center != NULL); - g_return_if_fail (radius > 0); - - contour = gsk_path_builder_add_contour_by_klass (builder, &GSK_CIRCLE_CONTOUR_CLASS); - gsk_circle_contour_init (contour, center, radius, 0, 360); -} - -void -gsk_path_builder_move_to (GskPathBuilder *builder, - float x, - float y) -{ - g_return_if_fail (builder != NULL); - - gsk_path_builder_end_current (builder); - - builder->flags = GSK_PATH_FLAT; - g_array_append_vals (builder->ops, &(GskStandardOperation) { GSK_PATH_MOVE, 0 }, 1); - g_array_append_val (builder->points, GRAPHENE_POINT_INIT(x, y)); -} - -void -gsk_path_builder_line_to (GskPathBuilder *builder, - float x, - float y) -{ - g_return_if_fail (builder != NULL); - - if (builder->ops->len == 0) - { - gsk_path_builder_move_to (builder, x, y); - return; - } - - /* skip the line if it goes to the same point */ - if (graphene_point_equal (&g_array_index (builder->points, graphene_point_t, builder->points->len - 1), - &GRAPHENE_POINT_INIT (x, y))) - return; - - gsk_path_builder_append_current (builder, - GSK_PATH_LINE, - 1, (graphene_point_t[1]) { - GRAPHENE_POINT_INIT (x, y) - }); -} - -void -gsk_path_builder_curve_to (GskPathBuilder *builder, - float x1, - float y1, - float x2, - float y2, - float x3, - float y3) -{ - g_return_if_fail (builder != NULL); - - if (builder->ops->len == 0) - gsk_path_builder_move_to (builder, x1, y1); - - builder->flags &= ~GSK_PATH_FLAT; - gsk_path_builder_append_current (builder, - GSK_PATH_CURVE, - 3, (graphene_point_t[3]) { - GRAPHENE_POINT_INIT (x1, y1), - GRAPHENE_POINT_INIT (x2, y2), - GRAPHENE_POINT_INIT (x3, y3) - }); -} - -void -gsk_path_builder_close (GskPathBuilder *builder) -{ - g_return_if_fail (builder != NULL); - - if (builder->ops->len == 0) - return; - - builder->flags |= GSK_PATH_CLOSED; - gsk_path_builder_append_current (builder, - GSK_PATH_CLOSE, - 1, (graphene_point_t[1]) { - g_array_index (builder->points, graphene_point_t, 0) - }); - - gsk_path_builder_end_current (builder); -} - diff --git a/gsk/gskpath.h b/gsk/gskpath.h index c8b62568e2..9fb24f14d7 100644 --- a/gsk/gskpath.h +++ b/gsk/gskpath.h @@ -79,54 +79,6 @@ gboolean gsk_path_foreach (GskPath GskPathForeachFunc func, gpointer user_data); -#define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ()) - -typedef struct _GskPathBuilder GskPathBuilder; - -GDK_AVAILABLE_IN_ALL -GType gsk_path_builder_get_type (void) G_GNUC_CONST; - -GDK_AVAILABLE_IN_ALL -GskPathBuilder * gsk_path_builder_new (void); -GDK_AVAILABLE_IN_ALL -GskPathBuilder * gsk_path_builder_ref (GskPathBuilder *builder); -GDK_AVAILABLE_IN_ALL -void gsk_path_builder_unref (GskPathBuilder *builder); -GDK_AVAILABLE_IN_ALL -GskPath * gsk_path_builder_free_to_path (GskPathBuilder *builder) G_GNUC_WARN_UNUSED_RESULT; -GDK_AVAILABLE_IN_ALL -GskPath * gsk_path_builder_to_path (GskPathBuilder *builder) G_GNUC_WARN_UNUSED_RESULT; - -GDK_AVAILABLE_IN_ALL -void gsk_path_builder_add_path (GskPathBuilder *builder, - GskPath *path); -GDK_AVAILABLE_IN_ALL -void gsk_path_builder_add_rect (GskPathBuilder *builder, - const graphene_rect_t *rect); -GDK_AVAILABLE_IN_ALL -void gsk_path_builder_add_circle (GskPathBuilder *builder, - const graphene_point_t *center, - float radius); - -GDK_AVAILABLE_IN_ALL -void gsk_path_builder_move_to (GskPathBuilder *builder, - float x, - float y); -GDK_AVAILABLE_IN_ALL -void gsk_path_builder_line_to (GskPathBuilder *builder, - float x, - float y); -GDK_AVAILABLE_IN_ALL -void gsk_path_builder_curve_to (GskPathBuilder *builder, - float x1, - float y1, - float x2, - float y2, - float x3, - float y3); -GDK_AVAILABLE_IN_ALL -void gsk_path_builder_close (GskPathBuilder *builder); - G_END_DECLS #endif /* __GSK_PATH_H__ */ diff --git a/gsk/gskpathbuilder.c b/gsk/gskpathbuilder.c new file mode 100644 index 0000000000..f296959dde --- /dev/null +++ b/gsk/gskpathbuilder.c @@ -0,0 +1,458 @@ +/* + * 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 "gskpathbuilder.h" + +#include "gskpathprivate.h" + +/** + * GskPathBuilder: + * + * A `GskPathBuilder` is an auxiliary object that is used to + * create new `GskPath` objects. + * + * A path is constructed like this: + * + * ``` + * GskPath * + * construct_path (void) + * { + * GskPathBuilder *builder; + * + * builder = gsk_path_builder_new (); + * + * // add contours to the path here + * + * return gsk_path_builder_free_to_path (builder); + * } + * ``` + * + * Adding contours to the path can be done in two ways. + * The easiest option is to use the `gsk_path_builder_add_*` group + * of functions that add predefined contours to the current path, + * either common shapes like [method@Gsk.PathBuilder.add_circle] + * or by adding from other paths like [method@Gsk.PathBuilder.add_path]. + * + * The other option is to define each line and curve manually with + * the `gsk_path_builder_*_to` group of functions. You start with + * a call to [method@Gsk.PathBuilder.move_to] to set the starting point + * and then use multiple calls to any of the drawing functions to + * move the pen along the plane. Once you are done, you can call + * [method@Gsk.PathBuilder.close] to close the path by connecting it + * back with a line to the starting point. + * This is similar for how paths are drawn in Cairo. + */ + +struct _GskPathBuilder +{ + int ref_count; + + GSList *contours; /* (reverse) list of already recorded contours */ + + GskPathFlags flags; /* flags for the current path */ + GArray *ops; /* operations for current contour - size == 0 means no current contour */ + GArray *points; /* points for the operations */ +}; + +G_DEFINE_BOXED_TYPE (GskPathBuilder, + gsk_path_builder, + gsk_path_builder_ref, + gsk_path_builder_unref) + + +/** + * gsk_path_builder_new: + * + * Create a new `GskPathBuilder` object. The resulting builder + * would create an empty `GskPath`. Use addition functions to add + * types to it. + * + * Returns: a new `GskPathBuilder` + **/ +GskPathBuilder * +gsk_path_builder_new (void) +{ + GskPathBuilder *builder; + + builder = g_slice_new0 (GskPathBuilder); + builder->ref_count = 1; + + builder->ops = g_array_new (FALSE, FALSE, sizeof (GskStandardOperation)); + builder->points = g_array_new (FALSE, FALSE, sizeof (graphene_point_t)); + return builder; +} + +/** + * gsk_path_builder_ref: + * @builder: a `GskPathBuilder` + * + * Acquires a reference on the given @builder. + * + * This function is intended primarily for bindings. `GskPathBuilder` objects + * should not be kept around. + * + * Returns: (transfer none): the given `GskPathBuilder` with + * its reference count increased + */ +GskPathBuilder * +gsk_path_builder_ref (GskPathBuilder *builder) +{ + g_return_val_if_fail (builder != NULL, NULL); + g_return_val_if_fail (builder->ref_count > 0, NULL); + + builder->ref_count += 1; + + return builder; +} + +static void +gsk_path_builder_append_current (GskPathBuilder *builder, + GskPathOperation op, + gsize n_points, + const graphene_point_t *points) +{ + g_assert (builder->ops->len > 0); + g_assert (builder->points->len > 0); + g_assert (n_points > 0); + + g_array_append_vals (builder->ops, &(GskStandardOperation) { op, builder->points->len - 1 }, 1); + g_array_append_vals (builder->points, points, n_points); +} + +static void +gsk_path_builder_end_current (GskPathBuilder *builder) +{ + GskContour *contour; + + if (builder->ops->len == 0) + return; + + contour = gsk_standard_contour_new (builder->flags, + (GskStandardOperation *) builder->ops->data, + builder->ops->len, + (graphene_point_t *) builder->points->data, + builder->points->len); + + g_array_set_size (builder->ops, 0); + g_array_set_size (builder->points, 0); + + /* do this at the end to avoid inflooping when add_contour calls back here */ + gsk_path_builder_add_contour (builder, contour); +} + +static void +gsk_path_builder_clear (GskPathBuilder *builder) +{ + gsk_path_builder_end_current (builder); + + g_slist_free_full (builder->contours, g_free); + builder->contours = NULL; +} + +/** + * gsk_path_builder_unref: + * @builder: a `GskPathBuilder` + * + * Releases a reference on the given @builder. + */ +void +gsk_path_builder_unref (GskPathBuilder *builder) +{ + g_return_if_fail (builder != NULL); + g_return_if_fail (builder->ref_count > 0); + + builder->ref_count -= 1; + + if (builder->ref_count > 0) + return; + + gsk_path_builder_clear (builder); + g_array_unref (builder->ops); + g_array_unref (builder->points); + g_slice_free (GskPathBuilder, builder); +} + +/** + * gsk_path_builder_free_to_path: (skip) + * @builder: a `GskPathBuilder` + * + * Creates a new `GskPath` from the current state of the + * given @builder, and frees the @builder instance. + * + * Returns: (transfer full): the newly created `GskPath` + * with all the contours added to @builder + */ +GskPath * +gsk_path_builder_free_to_path (GskPathBuilder *builder) +{ + GskPath *res; + + g_return_val_if_fail (builder != NULL, NULL); + + res = gsk_path_builder_to_path (builder); + + gsk_path_builder_unref (builder); + + return res; +} + +/** + * gsk_path_builder_to_path: + * @builder: a `GskPathBuilder` + * + * Creates a new `GskPath` from the given @builder. + * + * The given `GskPathBuilder` is reset once this function returns; + * you cannot call this function multiple times on the same @builder instance. + * + * This function is intended primarily for bindings. C code should use + * gsk_path_builder_free_to_path(). + * + * Returns: (transfer full): the newly created `GskPath` + * with all the contours added to @builder + */ +GskPath * +gsk_path_builder_to_path (GskPathBuilder *builder) +{ + GskPath *path; + + g_return_val_if_fail (builder != NULL, NULL); + + gsk_path_builder_end_current (builder); + + builder->contours = g_slist_reverse (builder->contours); + + path = gsk_path_new_from_contours (builder->contours); + + gsk_path_builder_clear (builder); + + return path; +} + +void +gsk_path_builder_add_contour (GskPathBuilder *builder, + GskContour *contour) +{ + gsk_path_builder_end_current (builder); + + builder->contours = g_slist_prepend (builder->contours, contour); +} + +/** + * gsk_path_builder_add_path: + * @builder: a `GskPathBuilder` + * @path: (transfer none): the path to append + * + * Appends all of @path to @builder. + **/ +void +gsk_path_builder_add_path (GskPathBuilder *builder, + GskPath *path) +{ + gsize i; + + g_return_if_fail (builder != NULL); + g_return_if_fail (path != NULL); + + for (i = 0; i < gsk_path_get_n_contours (path); i++) + { + const GskContour *contour = gsk_path_get_contour (path, i); + + gsk_path_builder_add_contour (builder, gsk_contour_dup (contour)); + } +} + +/** + * gsk_path_builder_add_rect: + * @builder: A `GskPathBuilder` + * @rect: The rectangle to add to @builder + * + * Adds a path representing the given rectangle. + * + * If the width or height of the rectangle is negative, the start + * point will be on the right or bottom, respectively. + * + * If the the width or height are 0, the path will be a closed + * horizontal or vertical line. If both are 0, it'll be a closed dot. + **/ +void +gsk_path_builder_add_rect (GskPathBuilder *builder, + const graphene_rect_t *rect) +{ + GskContour *contour; + + g_return_if_fail (builder != NULL); + + contour = gsk_rect_contour_new (rect); + gsk_path_builder_add_contour (builder, contour); +} + +/** + * gsk_path_builder_add_circle: + * @builder: a `GskPathBuilder` + * @center: the center of the circle + * @radius: the radius of the circle + * + * Adds a circle with the @center and @radius. + **/ +void +gsk_path_builder_add_circle (GskPathBuilder *builder, + const graphene_point_t *center, + float radius) +{ + GskContour *contour; + + g_return_if_fail (builder != NULL); + g_return_if_fail (center != NULL); + g_return_if_fail (radius > 0); + + contour = gsk_circle_contour_new (center, radius, 0, 360); + gsk_path_builder_add_contour (builder, contour); +} + +/** + * gsk_path_builder_move_to: + * @builder: a `GskPathBuilder` + * @x: x coordinate + * @y: y coordinate + * + * Starts a new contour by placing the pen at @x, @y. + * + * If `gsk_path_builder_move_to()` is called twice in succession, + * the first call will result in a contour made up of a single point. + * The second call will start a new contour. + **/ +void +gsk_path_builder_move_to (GskPathBuilder *builder, + float x, + float y) +{ + g_return_if_fail (builder != NULL); + + gsk_path_builder_end_current (builder); + + builder->flags = GSK_PATH_FLAT; + g_array_append_vals (builder->ops, &(GskStandardOperation) { GSK_PATH_MOVE, 0 }, 1); + g_array_append_val (builder->points, GRAPHENE_POINT_INIT(x, y)); +} + +/** + * gsk_path_builder_line_to: + * @builder: a `GskPathBuilder` + * @x: x coordinate + * @y: y coordinate + * + * Draws a line from the current point to @x, @y and makes it the new current + * point. + **/ +void +gsk_path_builder_line_to (GskPathBuilder *builder, + float x, + float y) +{ + g_return_if_fail (builder != NULL); + + if (builder->ops->len == 0) + { + gsk_path_builder_move_to (builder, x, y); + return; + } + + /* skip the line if it goes to the same point */ + if (graphene_point_equal (&g_array_index (builder->points, graphene_point_t, builder->points->len - 1), + &GRAPHENE_POINT_INIT (x, y))) + return; + + gsk_path_builder_append_current (builder, + GSK_PATH_LINE, + 1, (graphene_point_t[1]) { + GRAPHENE_POINT_INIT (x, y) + }); +} + +/** + * gsk_path_builder_curve_to: + * @builder: a `GskPathBuilder` + * @x1: x coordinate of first control point + * @y1: y coordinate of first control point + * @x2: x coordinate of second control point + * @y2: y coordinate of second control point + * @x3: x coordinate of the end of the curve + * @y3: y coordinate of the end of the curve + * + * Adds a [cubic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve) + * from the current point to @x3, @y3 with @x1, @y1 and @x2, @y2 as the control + * points. + **/ +void +gsk_path_builder_curve_to (GskPathBuilder *builder, + float x1, + float y1, + float x2, + float y2, + float x3, + float y3) +{ + g_return_if_fail (builder != NULL); + + if (builder->ops->len == 0) + gsk_path_builder_move_to (builder, x1, y1); + + builder->flags &= ~GSK_PATH_FLAT; + gsk_path_builder_append_current (builder, + GSK_PATH_CURVE, + 3, (graphene_point_t[3]) { + GRAPHENE_POINT_INIT (x1, y1), + GRAPHENE_POINT_INIT (x2, y2), + GRAPHENE_POINT_INIT (x3, y3) + }); +} + +/** + * gsk_path_builder_close: + * @builder: a `GskPathBuilder` + * + * Ends the current contour with a line back to the start point. + * + * Note that this is different from calling [method@Gsk.PathBuilder.line_to] + * with the start point in that the contour will be closed. A closed + * contour behaves different from an open one when stroking its start + * and end point are considered connected, so they will be joined + * via the line join, and not ended with line caps. + **/ +void +gsk_path_builder_close (GskPathBuilder *builder) +{ + g_return_if_fail (builder != NULL); + + if (builder->ops->len == 0) + return; + + builder->flags |= GSK_PATH_CLOSED; + gsk_path_builder_append_current (builder, + GSK_PATH_CLOSE, + 1, (graphene_point_t[1]) { + g_array_index (builder->points, graphene_point_t, 0) + }); + + gsk_path_builder_end_current (builder); +} + diff --git a/gsk/gskpathbuilder.h b/gsk/gskpathbuilder.h new file mode 100644 index 0000000000..2bc7a3933d --- /dev/null +++ b/gsk/gskpathbuilder.h @@ -0,0 +1,80 @@ +/* + * 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 + */ + +#ifndef __GSK_PATH_BUILDER_H__ +#define __GSK_PATH_BUILDER_H__ + +#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + + +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ()) + +GDK_AVAILABLE_IN_ALL +GType gsk_path_builder_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_ALL +GskPathBuilder * gsk_path_builder_new (void); +GDK_AVAILABLE_IN_ALL +GskPathBuilder * gsk_path_builder_ref (GskPathBuilder *builder); +GDK_AVAILABLE_IN_ALL +void gsk_path_builder_unref (GskPathBuilder *builder); +GDK_AVAILABLE_IN_ALL +GskPath * gsk_path_builder_free_to_path (GskPathBuilder *builder) G_GNUC_WARN_UNUSED_RESULT; +GDK_AVAILABLE_IN_ALL +GskPath * gsk_path_builder_to_path (GskPathBuilder *builder) G_GNUC_WARN_UNUSED_RESULT; + +GDK_AVAILABLE_IN_ALL +void gsk_path_builder_add_path (GskPathBuilder *builder, + GskPath *path); +GDK_AVAILABLE_IN_ALL +void gsk_path_builder_add_rect (GskPathBuilder *builder, + const graphene_rect_t *rect); +GDK_AVAILABLE_IN_ALL +void gsk_path_builder_add_circle (GskPathBuilder *builder, + const graphene_point_t *center, + float radius); + +GDK_AVAILABLE_IN_ALL +void gsk_path_builder_move_to (GskPathBuilder *builder, + float x, + float y); +GDK_AVAILABLE_IN_ALL +void gsk_path_builder_line_to (GskPathBuilder *builder, + float x, + float y); +GDK_AVAILABLE_IN_ALL +void gsk_path_builder_curve_to (GskPathBuilder *builder, + float x1, + float y1, + float x2, + float y2, + float x3, + float y3); +GDK_AVAILABLE_IN_ALL +void gsk_path_builder_close (GskPathBuilder *builder); + +G_END_DECLS + +#endif /* __GSK_PATH_BUILDER_H__ */ diff --git a/gsk/gskpathmeasure.c b/gsk/gskpathmeasure.c index a86838a78e..539c4220c3 100644 --- a/gsk/gskpathmeasure.c +++ b/gsk/gskpathmeasure.c @@ -410,12 +410,11 @@ gsk_path_measure_add_segment (GskPathMeasure *self, else if (start > 0 || end < self->measures[i].length) { float len = MIN (end, self->measures[i].length); - gsk_path_builder_add_contour_segment (builder, - self->path, - i, - self->measures[i].contour_data, - start, - len); + gsk_contour_add_segment (gsk_path_get_contour (self->path, i), + builder, + self->measures[i].contour_data, + start, + len); end -= len; start = 0; if (end <= 0) @@ -424,7 +423,7 @@ gsk_path_measure_add_segment (GskPathMeasure *self, else { end -= self->measures[i].length; - gsk_path_builder_add_contour (builder, self->path, i); + gsk_path_builder_add_contour (builder, gsk_contour_dup (gsk_path_get_contour (self->path, i))); } } } diff --git a/gsk/gskpathprivate.h b/gsk/gskpathprivate.h index ed8db4a49d..d987c90fc1 100644 --- a/gsk/gskpathprivate.h +++ b/gsk/gskpathprivate.h @@ -28,12 +28,44 @@ G_BEGIN_DECLS /* Same as Skia, so looks like a good value. ¯\_(ツ)_/¯ */ #define GSK_PATH_TOLERANCE_DEFAULT (0.5) +typedef enum +{ + GSK_PATH_FLAT, + GSK_PATH_CLOSED +} GskPathFlags; + +typedef struct _GskContour GskContour; +typedef struct _GskContourClass GskContourClass; + +typedef struct _GskStandardOperation GskStandardOperation; + +struct _GskStandardOperation { + GskPathOperation op; + gsize point; /* index into points array of the start point (last point of previous op) */ +}; + +GskContour * gsk_rect_contour_new (const graphene_rect_t *rect); +GskContour * gsk_circle_contour_new (const graphene_point_t *center, + float radius, + float start_angle, + float end_angle); +GskContour * gsk_standard_contour_new (GskPathFlags flags, + const GskStandardOperation *ops, + gsize n_ops, + const graphene_point_t *points, + gsize n_points); + +GskPath * gsk_path_new_from_contours (const GSList *contours); + gsize gsk_path_get_n_contours (GskPath *path); +const GskContour * gsk_path_get_contour (GskPath *path, + gsize i); gboolean gsk_path_foreach_with_tolerance (GskPath *self, double tolerance, GskPathForeachFunc func, gpointer user_data); +GskContour * gsk_contour_dup (const GskContour *src); gpointer gsk_contour_init_measure (GskPath *path, gsize i, float tolerance, @@ -57,16 +89,14 @@ gboolean gsk_contour_get_closest_point (GskPath graphene_point_t *out_pos, float *out_offset, graphene_vec2_t *out_tangent); +void gsk_contour_add_segment (const GskContour *self, + GskPathBuilder *builder, + gpointer measure_data, + float start, + float end); -void gsk_path_builder_add_contour (GskPathBuilder *builder, - GskPath *path, - gsize i); -void gsk_path_builder_add_contour_segment (GskPathBuilder *builder, - GskPath *path, - gsize i, - gpointer measure_data, - float start, - float end); +void gsk_path_builder_add_contour (GskPathBuilder *builder, + GskContour *contour); G_END_DECLS diff --git a/gsk/gsktypes.h b/gsk/gsktypes.h index d31e5d9be4..345847a6ed 100644 --- a/gsk/gsktypes.h +++ b/gsk/gsktypes.h @@ -27,6 +27,7 @@ #include typedef struct _GskPath GskPath; +typedef struct _GskPathBuilder GskPathBuilder; typedef struct _GskPathMeasure GskPathMeasure; typedef struct _GskRenderer GskRenderer; typedef struct _GskStroke GskStroke; diff --git a/gsk/meson.build b/gsk/meson.build index 72f70b2b9f..de11a64c23 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -26,6 +26,7 @@ gsk_public_sources = files([ 'gskcairorenderer.c', 'gskglshader.c', 'gskpath.c', + 'gskpathbuilder.c', 'gskpathmeasure.c', 'gskrenderer.c', 'gskrendernode.c', @@ -71,6 +72,7 @@ gsk_public_headers = files([ 'gskenums.h', 'gskglshader.h', 'gskpath.h', + 'gskpathbuilder.h', 'gskpathmeasure.h', 'gskrenderer.h', 'gskrendernode.h',