diff --git a/gsk/gsk.h b/gsk/gsk.h index b432bee98f..db3bb20986 100644 --- a/gsk/gsk.h +++ b/gsk/gsk.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include diff --git a/gsk/gskpath.c b/gsk/gskpath.c index 8b619ae796..6827553881 100644 --- a/gsk/gskpath.c +++ b/gsk/gskpath.c @@ -21,13 +21,14 @@ #include "gskpathprivate.h" +#include "gskpathbuilder.h" #include "gsksplineprivate.h" /** * SECTION:gskpath * @Title: Path * @Short_description: Lines and Curves - * @See_also: #GskRenderNode + * @See_also: #GskRenderNode, #GskPathBuilder * * This section describes the #GskPath structure that is used to * describe lines and curves that are more complex than simple rectangles. @@ -37,15 +38,6 @@ * The #GskPathBuilder structure is meant to help in this endeavor. */ -typedef enum -{ - GSK_PATH_FLAT, - GSK_PATH_CLOSED -} GskPathFlags; - -typedef struct _GskContour GskContour; -typedef struct _GskContourClass GskContourClass; - struct _GskContour { const GskContourClass *klass; @@ -123,10 +115,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, @@ -493,20 +481,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 */ @@ -743,13 +732,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, @@ -762,12 +744,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 = @@ -787,14 +767,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); @@ -803,17 +786,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 { @@ -1433,12 +1411,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; @@ -1452,6 +1430,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 @@ -1460,24 +1454,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, @@ -1546,18 +1522,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; } /** @@ -1652,6 +1688,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 @@ -1893,422 +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 - * - * Adds @rect as a new contour to the path built by the builder. - * - * 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_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 feccbb8000..dc678fe09c 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_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref) G_END_DECLS diff --git a/gsk/gskpathbuilder.c b/gsk/gskpathbuilder.c new file mode 100644 index 0000000000..4fa86cf278 --- /dev/null +++ b/gsk/gskpathbuilder.c @@ -0,0 +1,467 @@ +/* + * 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" + +/** + * SECTION:gskpathbuilder + * @Title: Building paths + * @Short_description: Building paths of lines and curves + * @See_also: #GskPath, #GskPathMeasure + * + * This section describes how to construct #GskPath structures. + * + * 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 gsk_path_builder_add_circle() + * or by adding from other paths like gsk_path_builder_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 gsk_path_builder_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 + * gsk_path_builder_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. + */ + +/** + * 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) + + +/** + * 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 create a path for + * + * Adds @rect as a new contour to the path built by the builder. + * + * 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 gsk_path_builder_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..96fdf8a913 --- /dev/null +++ b/gsk/gskpathbuilder.h @@ -0,0 +1,82 @@ +/* + * 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_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathBuilder, gsk_path_builder_unref) + +G_END_DECLS + +#endif /* __GSK_PATH_BUILDER_H__ */ diff --git a/gsk/gskpathmeasure.c b/gsk/gskpathmeasure.c index 218af17ba1..9571cb572d 100644 --- a/gsk/gskpathmeasure.c +++ b/gsk/gskpathmeasure.c @@ -416,12 +416,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) @@ -430,7 +429,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 6197ccd6d6..f749da455f 100644 --- a/gsk/gsktypes.h +++ b/gsk/gsktypes.h @@ -26,6 +26,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 dc3819c817..b90cc1ade7 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -27,6 +27,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',