mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-13 04:10:13 +00:00
8634d67cff
We must be careful with single-point contours that contain just a move. These never occur in practice, but our randomized tests produce them regularly.
1172 lines
33 KiB
C
1172 lines
33 KiB
C
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: Benjamin Otte <otte@gnome.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include "gskpathbuilder.h"
|
|
|
|
#include "gskpathprivate.h"
|
|
#include "gskcontourprivate.h"
|
|
#include "gsksplineprivate.h"
|
|
|
|
/**
|
|
* GskPathBuilder:
|
|
*
|
|
* `GskPathBuilder` is an auxiliary object for constructing
|
|
* `GskPath` objects.
|
|
*
|
|
* A path is constructed like this:
|
|
*
|
|
* |[<!-- language="C" -->
|
|
* 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 `gsk_path_builder_add_*` methods always add complete contours,
|
|
* and do not use or modify the current point.
|
|
*
|
|
* 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.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
|
|
struct _GskPathBuilder
|
|
{
|
|
int ref_count;
|
|
|
|
GSList *contours; /* (reverse) list of already recorded contours */
|
|
|
|
GskPathFlags flags; /* flags for the current path */
|
|
graphene_point_t current_point; /* the point all drawing ops start from */
|
|
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`
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskPathBuilder *
|
|
gsk_path_builder_new (void)
|
|
{
|
|
GskPathBuilder *self;
|
|
|
|
self = g_slice_new0 (GskPathBuilder);
|
|
self->ref_count = 1;
|
|
|
|
self->ops = g_array_new (FALSE, FALSE, sizeof (gskpathop));
|
|
self->points = g_array_new (FALSE, FALSE, sizeof (graphene_point_t));
|
|
|
|
/* Be explicit here */
|
|
self->current_point = GRAPHENE_POINT_INIT (0, 0);
|
|
|
|
return self;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_ref:
|
|
* @self: a `GskPathBuilder`
|
|
*
|
|
* Acquires a reference on the given builder.
|
|
*
|
|
* This function is intended primarily for language bindings.
|
|
* `GskPathBuilder` objects should not be kept around.
|
|
*
|
|
* Returns: (transfer none): the given `GskPathBuilder` with
|
|
* its reference count increased
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskPathBuilder *
|
|
gsk_path_builder_ref (GskPathBuilder *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
g_return_val_if_fail (self->ref_count > 0, NULL);
|
|
|
|
self->ref_count += 1;
|
|
|
|
return self;
|
|
}
|
|
|
|
/* We're cheating here. Out pathops are relative to the NULL pointer,
|
|
* so that we can not care about the points GArray reallocating itself
|
|
* until we create the contour.
|
|
* This does however mean that we need to not use gsk_pathop_get_points()
|
|
* without offsetting the returned pointer.
|
|
*/
|
|
static inline gskpathop
|
|
gsk_pathop_encode_index (GskPathOperation op,
|
|
gsize index)
|
|
{
|
|
return gsk_pathop_encode (op, ((graphene_point_t *) NULL) + index);
|
|
}
|
|
|
|
static void
|
|
gsk_path_builder_ensure_current (GskPathBuilder *self)
|
|
{
|
|
if (self->ops->len != 0)
|
|
return;
|
|
|
|
self->flags = GSK_PATH_FLAT;
|
|
g_array_append_vals (self->ops, (gskpathop[1]) { gsk_pathop_encode_index (GSK_PATH_MOVE, 0) }, 1);
|
|
g_array_append_val (self->points, self->current_point);
|
|
}
|
|
|
|
static void
|
|
gsk_path_builder_append_current (GskPathBuilder *self,
|
|
GskPathOperation op,
|
|
gsize n_points,
|
|
const graphene_point_t *points)
|
|
{
|
|
gsk_path_builder_ensure_current (self);
|
|
|
|
g_array_append_vals (self->ops, (gskpathop[1]) { gsk_pathop_encode_index (op, self->points->len - 1) }, 1);
|
|
g_array_append_vals (self->points, points, n_points);
|
|
|
|
self->current_point = points[n_points - 1];
|
|
}
|
|
|
|
static void
|
|
gsk_path_builder_end_current (GskPathBuilder *self)
|
|
{
|
|
GskContour *contour;
|
|
|
|
if (self->ops->len == 0)
|
|
return;
|
|
|
|
contour = gsk_standard_contour_new (self->flags,
|
|
(graphene_point_t *) self->points->data,
|
|
self->points->len,
|
|
(gskpathop *) self->ops->data,
|
|
self->ops->len,
|
|
(graphene_point_t *) self->points->data - (graphene_point_t *) NULL);
|
|
|
|
g_array_set_size (self->ops, 0);
|
|
g_array_set_size (self->points, 0);
|
|
|
|
/* do this at the end to avoid inflooping when add_contour calls back here */
|
|
gsk_path_builder_add_contour (self, contour);
|
|
}
|
|
|
|
static void
|
|
gsk_path_builder_clear (GskPathBuilder *self)
|
|
{
|
|
gsk_path_builder_end_current (self);
|
|
|
|
g_slist_free_full (self->contours, g_free);
|
|
self->contours = NULL;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_unref:
|
|
* @self: a `GskPathBuilder`
|
|
*
|
|
* Releases a reference on the given builder.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_unref (GskPathBuilder *self)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (self->ref_count > 0);
|
|
|
|
self->ref_count -= 1;
|
|
|
|
if (self->ref_count > 0)
|
|
return;
|
|
|
|
gsk_path_builder_clear (self);
|
|
g_array_unref (self->ops);
|
|
g_array_unref (self->points);
|
|
g_slice_free (GskPathBuilder, self);
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_free_to_path: (skip)
|
|
* @self: 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 the builder
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskPath *
|
|
gsk_path_builder_free_to_path (GskPathBuilder *self)
|
|
{
|
|
GskPath *res;
|
|
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
res = gsk_path_builder_to_path (self);
|
|
|
|
gsk_path_builder_unref (self);
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_to_path:
|
|
* @self: 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 language bindings.
|
|
* C code should use [method@Gsk.PathBuilder.free_to_path].
|
|
*
|
|
* Returns: (transfer full): the newly created `GskPath`
|
|
* with all the contours added to the builder
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
GskPath *
|
|
gsk_path_builder_to_path (GskPathBuilder *self)
|
|
{
|
|
GskPath *path;
|
|
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
gsk_path_builder_end_current (self);
|
|
|
|
self->contours = g_slist_reverse (self->contours);
|
|
|
|
path = gsk_path_new_from_contours (self->contours);
|
|
|
|
gsk_path_builder_clear (self);
|
|
|
|
return path;
|
|
}
|
|
|
|
void
|
|
gsk_path_builder_add_contour (GskPathBuilder *self,
|
|
GskContour *contour)
|
|
{
|
|
gsk_path_builder_end_current (self);
|
|
|
|
self->contours = g_slist_prepend (self->contours, contour);
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_get_current_point:
|
|
* @self: a `GskPathBuilder`
|
|
*
|
|
* Gets the current point. The current point is used for relative
|
|
* drawing commands and updated after every operation.
|
|
*
|
|
* When the builder is created, the default current point is set to (0, 0).
|
|
* Note that this is different from cairo, which starts out without
|
|
* a current point.
|
|
*
|
|
* Returns: (transfer none): The current point
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
const graphene_point_t *
|
|
gsk_path_builder_get_current_point (GskPathBuilder *self)
|
|
{
|
|
g_return_val_if_fail (self != NULL, NULL);
|
|
|
|
return &self->current_point;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_add_path:
|
|
* @self: a `GskPathBuilder`
|
|
* @path: (transfer none): the path to append
|
|
*
|
|
* Appends all of @path to the builder.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_add_path (GskPathBuilder *self,
|
|
GskPath *path)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (path != NULL);
|
|
|
|
for (gsize i = 0; i < gsk_path_get_n_contours (path); i++)
|
|
{
|
|
const GskContour *contour = gsk_path_get_contour (path, i);
|
|
|
|
gsk_path_builder_add_contour (self, gsk_contour_dup (contour));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_add_reverse_path:
|
|
* @self: a `GskPathBuilder`
|
|
* @path: (transfer none): the path to append
|
|
*
|
|
* Appends all of @path to the builder, in reverse order.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_add_reverse_path (GskPathBuilder *self,
|
|
GskPath *path)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (path != NULL);
|
|
|
|
for (gsize i = gsk_path_get_n_contours (path); i > 0; i--)
|
|
{
|
|
const GskContour *contour = gsk_path_get_contour (path, i - 1);
|
|
|
|
gsk_path_builder_add_contour (self, gsk_contour_reverse (contour));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_add_cairo_path:
|
|
* @self: a `GskPathBuilder`
|
|
*
|
|
* Adds a Cairo path to the builder.
|
|
*
|
|
* You can use cairo_copy_path() to access the path
|
|
* from a Cairo context.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_add_cairo_path (GskPathBuilder *self,
|
|
const cairo_path_t *path)
|
|
{
|
|
graphene_point_t current;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (path != NULL);
|
|
|
|
current = self->current_point;
|
|
|
|
for (gsize i = 0; i < path->num_data; i += path->data[i].header.length)
|
|
{
|
|
const cairo_path_data_t *data = &path->data[i];
|
|
|
|
switch (data->header.type)
|
|
{
|
|
case CAIRO_PATH_MOVE_TO:
|
|
gsk_path_builder_move_to (self, data[1].point.x, data[1].point.y);
|
|
break;
|
|
|
|
case CAIRO_PATH_LINE_TO:
|
|
gsk_path_builder_line_to (self, data[1].point.x, data[1].point.y);
|
|
break;
|
|
|
|
case CAIRO_PATH_CURVE_TO:
|
|
gsk_path_builder_cubic_to (self,
|
|
data[1].point.x, data[1].point.y,
|
|
data[2].point.x, data[2].point.y,
|
|
data[3].point.x, data[3].point.y);
|
|
break;
|
|
|
|
case CAIRO_PATH_CLOSE_PATH:
|
|
gsk_path_builder_close (self);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
}
|
|
|
|
gsk_path_builder_end_current (self);
|
|
self->current_point = current;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_add_rect:
|
|
* @self: A `GskPathBuilder`
|
|
* @rect: The rectangle to create a path for
|
|
*
|
|
* Adds @rect as a new contour to the path built by the builder.
|
|
*
|
|
* The path is going around the rectangle in clockwise direction.
|
|
*
|
|
* 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.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_add_rect (GskPathBuilder *self,
|
|
const graphene_rect_t *rect)
|
|
{
|
|
graphene_point_t current;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
|
|
current = self->current_point;
|
|
|
|
gsk_path_builder_move_to (self, rect->origin.x, rect->origin.y);
|
|
|
|
gsk_path_builder_rel_line_to (self, rect->size.width, 0);
|
|
gsk_path_builder_rel_line_to (self, 0, rect->size.height);
|
|
gsk_path_builder_rel_line_to (self, - rect->size.width, 0);
|
|
|
|
gsk_path_builder_close (self);
|
|
self->current_point = current;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_add_rounded_rect:
|
|
* @self: a #GskPathBuilder
|
|
* @rect: the rounded rect
|
|
*
|
|
* Adds @rect as a new contour to the path built in @self.
|
|
*
|
|
* The path is going around the rectangle in clockwise direction.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_add_rounded_rect (GskPathBuilder *self,
|
|
const GskRoundedRect *rect)
|
|
{
|
|
graphene_point_t current;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (rect != NULL);
|
|
|
|
current = self->current_point;
|
|
|
|
gsk_path_builder_move_to (self,
|
|
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
|
|
rect->bounds.origin.y);
|
|
/* top */
|
|
gsk_path_builder_line_to (self,
|
|
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width,
|
|
rect->bounds.origin.y);
|
|
/* topright corner */
|
|
gsk_path_builder_svg_arc_to (self,
|
|
rect->corner[GSK_CORNER_TOP_RIGHT].width,
|
|
rect->corner[GSK_CORNER_TOP_RIGHT].height,
|
|
0, FALSE, TRUE,
|
|
rect->bounds.origin.x + rect->bounds.size.width,
|
|
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height);
|
|
/* right */
|
|
gsk_path_builder_line_to (self,
|
|
rect->bounds.origin.x + rect->bounds.size.width,
|
|
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_RIGHT].height);
|
|
/* bottomright corner */
|
|
gsk_path_builder_svg_arc_to (self,
|
|
rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
|
|
rect->corner[GSK_CORNER_BOTTOM_RIGHT].height,
|
|
0, FALSE, TRUE,
|
|
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
|
|
rect->bounds.origin.y + rect->bounds.size.height);
|
|
/* bottom */
|
|
gsk_path_builder_line_to (self,
|
|
rect->bounds.origin.x + rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
|
|
rect->bounds.origin.y + rect->bounds.size.height);
|
|
/* bottomleft corner */
|
|
gsk_path_builder_svg_arc_to (self,
|
|
rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
|
|
rect->corner[GSK_CORNER_BOTTOM_LEFT].height,
|
|
0, FALSE, TRUE,
|
|
rect->bounds.origin.x,
|
|
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height);
|
|
/* left */
|
|
gsk_path_builder_line_to (self,
|
|
rect->bounds.origin.x,
|
|
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_LEFT].height);
|
|
/* topleft corner */
|
|
gsk_path_builder_svg_arc_to (self,
|
|
rect->corner[GSK_CORNER_TOP_LEFT].width,
|
|
rect->corner[GSK_CORNER_TOP_LEFT].height,
|
|
0, FALSE, TRUE,
|
|
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
|
|
rect->bounds.origin.y);
|
|
/* done */
|
|
gsk_path_builder_close (self);
|
|
self->current_point = current;
|
|
}
|
|
|
|
static gboolean
|
|
circle_contour_curve (const graphene_point_t pts[4],
|
|
gpointer data)
|
|
{
|
|
GskPathBuilder *self = data;
|
|
|
|
gsk_path_builder_cubic_to (self,
|
|
pts[1].x, pts[1].y,
|
|
pts[2].x, pts[2].y,
|
|
pts[3].x, pts[3].y);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_add_circle:
|
|
* @self: a `GskPathBuilder`
|
|
* @center: the center of the circle
|
|
* @radius: the radius of the circle
|
|
*
|
|
* Adds a circle with the @center and @radius.
|
|
*
|
|
* The path is going around the circle in clockwise direction.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_add_circle (GskPathBuilder *self,
|
|
const graphene_point_t *center,
|
|
float radius)
|
|
{
|
|
graphene_point_t current;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (center != NULL);
|
|
g_return_if_fail (radius > 0);
|
|
|
|
current = self->current_point;
|
|
|
|
gsk_path_builder_move_to (self, center->x + radius, center->y);
|
|
gsk_spline_decompose_arc (center, radius,
|
|
GSK_PATH_TOLERANCE_DEFAULT,
|
|
0, 2 * M_PI,
|
|
circle_contour_curve, self);
|
|
|
|
gsk_path_builder_close (self);
|
|
self->current_point = current;
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_move_to:
|
|
* @self: a `GskPathBuilder`
|
|
* @x: x coordinate
|
|
* @y: y coordinate
|
|
*
|
|
* Starts a new contour by placing the pen at @x, @y.
|
|
*
|
|
* If this function 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.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_move_to (GskPathBuilder *self,
|
|
float x,
|
|
float y)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
gsk_path_builder_end_current (self);
|
|
|
|
self->current_point = GRAPHENE_POINT_INIT(x, y);
|
|
|
|
gsk_path_builder_ensure_current (self);
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_rel_move_to:
|
|
* @self: a `GskPathBuilder`
|
|
* @x: x offset
|
|
* @y: y offset
|
|
*
|
|
* Starts a new contour by placing the pen at @x, @y relative to the current
|
|
* point.
|
|
*
|
|
* This is the relative version of [method@Gsk.PathBuilder.move_to].
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_rel_move_to (GskPathBuilder *self,
|
|
float x,
|
|
float y)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
gsk_path_builder_move_to (self,
|
|
self->current_point.x + x,
|
|
self->current_point.y + y);
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_line_to:
|
|
* @self: 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.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_line_to (GskPathBuilder *self,
|
|
float x,
|
|
float y)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
/* skip the line if it goes to the same point */
|
|
if (graphene_point_equal (&self->current_point,
|
|
&GRAPHENE_POINT_INIT (x, y)))
|
|
return;
|
|
|
|
gsk_path_builder_append_current (self,
|
|
GSK_PATH_LINE,
|
|
1, (graphene_point_t[1]) {
|
|
GRAPHENE_POINT_INIT (x, y)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_rel_line_to:
|
|
* @self: a `GskPathBuilder`
|
|
* @x: x offset
|
|
* @y: y offset
|
|
*
|
|
* Draws a line from the current point to a point offset to it by @x, @y
|
|
* and makes it the new current point.
|
|
*
|
|
* This is the relative version of [method@Gsk.PathBuilder.line_to].
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_rel_line_to (GskPathBuilder *self,
|
|
float x,
|
|
float y)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
gsk_path_builder_line_to (self,
|
|
self->current_point.x + x,
|
|
self->current_point.y + y);
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_quad_to:
|
|
* @self: a #GskPathBuilder
|
|
* @x1: x coordinate of control point
|
|
* @y1: y coordinate of control point
|
|
* @x2: x coordinate of the end of the curve
|
|
* @y2: y coordinate of the end of the curve
|
|
*
|
|
* Adds a [quadratic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
|
|
* from the current point to @x2, @y2 with @x1, @y1 as the control point.
|
|
*
|
|
* After this, @x2, @y2 will be the new current point.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_quad_to (GskPathBuilder *self,
|
|
float x1,
|
|
float y1,
|
|
float x2,
|
|
float y2)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
self->flags &= ~GSK_PATH_FLAT;
|
|
gsk_path_builder_append_current (self,
|
|
GSK_PATH_QUAD,
|
|
2, (graphene_point_t[2]) {
|
|
GRAPHENE_POINT_INIT (x1, y1),
|
|
GRAPHENE_POINT_INIT (x2, y2)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_rel_quad_to:
|
|
* @self: a `GskPathBuilder`
|
|
* @x1: x offset of control point
|
|
* @y1: y offset of control point
|
|
* @x2: x offset of the end of the curve
|
|
* @y2: y offset of the end of the curve
|
|
*
|
|
* Adds a [quadratic Bézier curve](https://en.wikipedia.org/wiki/B%C3%A9zier_curve)
|
|
* from the current point to @x2, @y2 with @x1, @y1 the control point.
|
|
*
|
|
* All coordinates are given relative to the current point.
|
|
*
|
|
* This is the relative version of [method@Gsk.PathBuilder.quad_to].
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_rel_quad_to (GskPathBuilder *self,
|
|
float x1,
|
|
float y1,
|
|
float x2,
|
|
float y2)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
gsk_path_builder_quad_to (self,
|
|
self->current_point.x + x1,
|
|
self->current_point.y + y1,
|
|
self->current_point.x + x2,
|
|
self->current_point.y + y2);
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_cubic_to:
|
|
* @self: 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.
|
|
*
|
|
* After this, @x3, @y3 will be the new current point.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_cubic_to (GskPathBuilder *self,
|
|
float x1,
|
|
float y1,
|
|
float x2,
|
|
float y2,
|
|
float x3,
|
|
float y3)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
self->flags &= ~GSK_PATH_FLAT;
|
|
gsk_path_builder_append_current (self,
|
|
GSK_PATH_CUBIC,
|
|
3, (graphene_point_t[3]) {
|
|
GRAPHENE_POINT_INIT (x1, y1),
|
|
GRAPHENE_POINT_INIT (x2, y2),
|
|
GRAPHENE_POINT_INIT (x3, y3)
|
|
});
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_rel_cubic_to:
|
|
* @self: a `GskPathBuilder`
|
|
* @x1: x offset of first control point
|
|
* @y1: y offset of first control point
|
|
* @x2: x offset of second control point
|
|
* @y2: y offset of second control point
|
|
* @x3: x offset of the end of the curve
|
|
* @y3: y offset 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. All coordinates are given relative to the current point.
|
|
*
|
|
* This is the relative version of [method@Gsk.PathBuilder.cubic_to].
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_rel_cubic_to (GskPathBuilder *self,
|
|
float x1,
|
|
float y1,
|
|
float x2,
|
|
float y2,
|
|
float x3,
|
|
float y3)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
gsk_path_builder_cubic_to (self,
|
|
self->current_point.x + x1,
|
|
self->current_point.y + y1,
|
|
self->current_point.x + x2,
|
|
self->current_point.y + y2,
|
|
self->current_point.x + x3,
|
|
self->current_point.y + y3);
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_close:
|
|
* @self: 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.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_close (GskPathBuilder *self)
|
|
{
|
|
g_return_if_fail (self != NULL);
|
|
|
|
if (self->ops->len == 0)
|
|
return;
|
|
|
|
self->flags |= GSK_PATH_CLOSED;
|
|
gsk_path_builder_append_current (self,
|
|
GSK_PATH_CLOSE,
|
|
1, (graphene_point_t[1]) {
|
|
g_array_index (self->points, graphene_point_t, 0)
|
|
});
|
|
|
|
gsk_path_builder_end_current (self);
|
|
}
|
|
|
|
static void
|
|
arc_segment (GskPathBuilder *self,
|
|
double cx,
|
|
double cy,
|
|
double rx,
|
|
double ry,
|
|
double sin_phi,
|
|
double cos_phi,
|
|
double sin_th0,
|
|
double cos_th0,
|
|
double sin_th1,
|
|
double cos_th1,
|
|
double t)
|
|
{
|
|
double x1, y1, x2, y2, x3, y3;
|
|
|
|
x1 = rx * (cos_th0 - t * sin_th0);
|
|
y1 = ry * (sin_th0 + t * cos_th0);
|
|
x3 = rx * cos_th1;
|
|
y3 = ry * sin_th1;
|
|
x2 = x3 + rx * (t * sin_th1);
|
|
y2 = y3 + ry * (-t * cos_th1);
|
|
|
|
gsk_path_builder_cubic_to (self,
|
|
cx + cos_phi * x1 - sin_phi * y1,
|
|
cy + sin_phi * x1 + cos_phi * y1,
|
|
cx + cos_phi * x2 - sin_phi * y2,
|
|
cy + sin_phi * x2 + cos_phi * y2,
|
|
cx + cos_phi * x3 - sin_phi * y3,
|
|
cy + sin_phi * x3 + cos_phi * y3);
|
|
}
|
|
|
|
static inline void
|
|
_sincos (double angle,
|
|
double *y,
|
|
double *x)
|
|
{
|
|
#ifdef HAVE_SINCOS
|
|
sincos (angle, y, x);
|
|
#else
|
|
*x = cos (angle);
|
|
*y = sin (angle);
|
|
#endif
|
|
}
|
|
|
|
void
|
|
gsk_path_builder_svg_arc_to (GskPathBuilder *self,
|
|
float rx,
|
|
float ry,
|
|
float x_axis_rotation,
|
|
gboolean large_arc,
|
|
gboolean positive_sweep,
|
|
float x,
|
|
float y)
|
|
{
|
|
graphene_point_t *current;
|
|
double x1, y1, x2, y2;
|
|
double phi, sin_phi, cos_phi;
|
|
double mid_x, mid_y;
|
|
double lambda;
|
|
double d;
|
|
double k;
|
|
double x1_, y1_;
|
|
double cx_, cy_;
|
|
double cx, cy;
|
|
double ux, uy, u_len;
|
|
double cos_theta1, theta1;
|
|
double vx, vy, v_len;
|
|
double dp_uv;
|
|
double cos_delta_theta, delta_theta;
|
|
int i, n_segs;
|
|
double d_theta, theta;
|
|
double sin_th0, cos_th0;
|
|
double sin_th1, cos_th1;
|
|
double th_half;
|
|
double t;
|
|
|
|
if (self->points->len > 0)
|
|
{
|
|
current = &g_array_index (self->points, graphene_point_t, self->points->len - 1);
|
|
x1 = current->x;
|
|
y1 = current->y;
|
|
}
|
|
else
|
|
{
|
|
x1 = 0;
|
|
y1 = 0;
|
|
}
|
|
x2 = x;
|
|
y2 = y;
|
|
|
|
phi = x_axis_rotation * M_PI / 180.0;
|
|
_sincos (phi, &sin_phi, &cos_phi);
|
|
|
|
rx = fabs (rx);
|
|
ry = fabs (ry);
|
|
|
|
mid_x = (x1 - x2) / 2;
|
|
mid_y = (y1 - y2) / 2;
|
|
|
|
x1_ = cos_phi * mid_x + sin_phi * mid_y;
|
|
y1_ = - sin_phi * mid_x + cos_phi * mid_y;
|
|
|
|
lambda = (x1_ / rx) * (x1_ / rx) + (y1_ / ry) * (y1_ / ry);
|
|
if (lambda > 1)
|
|
{
|
|
lambda = sqrt (lambda);
|
|
rx *= lambda;
|
|
ry *= lambda;
|
|
}
|
|
|
|
d = (rx * y1_) * (rx * y1_) + (ry * x1_) * (ry * x1_);
|
|
if (d == 0)
|
|
return;
|
|
|
|
k = sqrt (fabs ((rx * ry) * (rx * ry) / d - 1.0));
|
|
if (positive_sweep == large_arc)
|
|
k = -k;
|
|
|
|
cx_ = k * rx * y1_ / ry;
|
|
cy_ = -k * ry * x1_ / rx;
|
|
|
|
cx = cos_phi * cx_ - sin_phi * cy_ + (x1 + x2) / 2;
|
|
cy = sin_phi * cx_ + cos_phi * cy_ + (y1 + y2) / 2;
|
|
|
|
ux = (x1_ - cx_) / rx;
|
|
uy = (y1_ - cy_) / ry;
|
|
u_len = sqrt (ux * ux + uy * uy);
|
|
if (u_len == 0)
|
|
return;
|
|
|
|
cos_theta1 = CLAMP (ux / u_len, -1, 1);
|
|
theta1 = acos (cos_theta1);
|
|
if (uy < 0)
|
|
theta1 = - theta1;
|
|
|
|
vx = (- x1_ - cx_) / rx;
|
|
vy = (- y1_ - cy_) / ry;
|
|
v_len = sqrt (vx * vx + vy * vy);
|
|
if (v_len == 0)
|
|
return;
|
|
|
|
dp_uv = ux * vx + uy * vy;
|
|
cos_delta_theta = CLAMP (dp_uv / (u_len * v_len), -1, 1);
|
|
delta_theta = acos (cos_delta_theta);
|
|
if (ux * vy - uy * vx < 0)
|
|
delta_theta = - delta_theta;
|
|
if (positive_sweep && delta_theta < 0)
|
|
delta_theta += 2 * M_PI;
|
|
else if (!positive_sweep && delta_theta > 0)
|
|
delta_theta -= 2 * M_PI;
|
|
|
|
n_segs = ceil (fabs (delta_theta / (M_PI_2 + 0.001)));
|
|
d_theta = delta_theta / n_segs;
|
|
theta = theta1;
|
|
_sincos (theta1, &sin_th1, &cos_th1);
|
|
|
|
th_half = d_theta / 2;
|
|
t = (8.0 / 3.0) * sin (th_half / 2) * sin (th_half / 2) / sin (th_half);
|
|
|
|
for (i = 0; i < n_segs; i++)
|
|
{
|
|
theta = theta1;
|
|
theta1 = theta + d_theta;
|
|
sin_th0 = sin_th1;
|
|
cos_th0 = cos_th1;
|
|
_sincos (theta1, &sin_th1, &cos_th1);
|
|
arc_segment (self,
|
|
cx, cy, rx, ry,
|
|
sin_phi, cos_phi,
|
|
sin_th0, cos_th0,
|
|
sin_th1, cos_th1,
|
|
t);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_add_layout:
|
|
* @self: a #GskPathBuilder
|
|
* @layout: the pango layout to add
|
|
*
|
|
* Adds the outlines for the glyphs in @layout to
|
|
* the builder.
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_add_layout (GskPathBuilder *self,
|
|
PangoLayout *layout)
|
|
{
|
|
cairo_surface_t *surface;
|
|
cairo_t *cr;
|
|
cairo_path_t *cairo_path;
|
|
|
|
surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
|
|
cr = cairo_create (surface);
|
|
|
|
pango_cairo_layout_path (cr, layout);
|
|
cairo_path = cairo_copy_path (cr);
|
|
|
|
gsk_path_builder_add_cairo_path (self, cairo_path);
|
|
|
|
cairo_path_destroy (cairo_path);
|
|
cairo_destroy (cr);
|
|
cairo_surface_destroy (surface);
|
|
}
|
|
|
|
/**
|
|
* gsk_path_builder_add_segment:
|
|
* @self: a `GskPathBuilder`
|
|
* @path: the `GskPath` to take the segment to
|
|
* @start: the point on @path to start at
|
|
* @end: the point on @path to end at
|
|
*
|
|
* Adds to @self the segment of @path from @start to @end.
|
|
*
|
|
* If @start is equal to or after @end, 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.
|
|
*
|
|
* Note that this method always adds a path with the given start point
|
|
* and end point. To add a closed path, use [method@Gsk.PathBuilder.add_path].
|
|
*
|
|
* Since: 4.14
|
|
*/
|
|
void
|
|
gsk_path_builder_add_segment (GskPathBuilder *self,
|
|
GskPath *path,
|
|
const GskPathPoint *start,
|
|
const GskPathPoint *end)
|
|
{
|
|
GskRealPathPoint *s = (GskRealPathPoint *) start;
|
|
GskRealPathPoint *e = (GskRealPathPoint *) end;
|
|
const GskContour *contour;
|
|
gsize n_contours = gsk_path_get_n_contours (path);
|
|
graphene_point_t current;
|
|
gsize n_ops;
|
|
|
|
g_return_if_fail (self != NULL);
|
|
g_return_if_fail (path != NULL);
|
|
g_return_if_fail (start != NULL);
|
|
g_return_if_fail (end != NULL);
|
|
g_return_if_fail (s->contour < n_contours);
|
|
g_return_if_fail (e->contour < n_contours);
|
|
|
|
current = self->current_point;
|
|
|
|
contour = gsk_path_get_contour (path, s->contour);
|
|
n_ops = gsk_contour_get_n_ops (contour);
|
|
|
|
if (s->contour == e->contour)
|
|
{
|
|
if (gsk_path_point_compare (start, end) < 0)
|
|
{
|
|
gsk_contour_add_segment (contour, self, TRUE, s, e);
|
|
goto out;
|
|
}
|
|
else if (n_contours == 1)
|
|
{
|
|
if (n_ops > 1)
|
|
gsk_contour_add_segment (contour, self, TRUE,
|
|
s,
|
|
&(GskRealPathPoint) { s->contour, n_ops - 1, 1 });
|
|
gsk_contour_add_segment (contour, self, n_ops <= 1,
|
|
&(GskRealPathPoint) { s->contour, 1, 0 },
|
|
e);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (n_ops > 1)
|
|
gsk_contour_add_segment (contour, self, TRUE,
|
|
s,
|
|
&(GskRealPathPoint) { s->contour, n_ops - 1, 1. });
|
|
|
|
for (gsize i = (s->contour + 1) % n_contours; i != e->contour; i = (i + 1) % n_contours)
|
|
gsk_path_builder_add_contour (self, gsk_contour_dup (gsk_path_get_contour (path, i)));
|
|
|
|
contour = gsk_path_get_contour (path, e->contour);
|
|
n_ops = gsk_contour_get_n_ops (contour);
|
|
|
|
if (n_ops > 1)
|
|
gsk_contour_add_segment (contour, self, TRUE,
|
|
&(GskRealPathPoint) { e->contour, 1, 0 },
|
|
e);
|
|
|
|
out:
|
|
gsk_path_builder_end_current (self);
|
|
self->current_point = current;
|
|
}
|