diff --git a/gsk/gsk.h b/gsk/gsk.h index fc5f3962f9..af1f0987d2 100644 --- a/gsk/gsk.h +++ b/gsk/gsk.h @@ -20,6 +20,9 @@ #define __GSK_H_INSIDE__ #include +#include +#include +#include #include #include #include diff --git a/gsk/gskcontour.c b/gsk/gskcontour.c new file mode 100644 index 0000000000..48c218305a --- /dev/null +++ b/gsk/gskcontour.c @@ -0,0 +1,1819 @@ +/* + * 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 "gskcontourprivate.h" + +#include "gskcurveprivate.h" +#include "gskpathbuilder.h" +#include "gskpathprivate.h" +#include "gskpathpointprivate.h" +#include "gsksplineprivate.h" + +typedef struct _GskContourClass GskContourClass; + +struct _GskContour +{ + const GskContourClass *klass; +}; + +struct _GskContourClass +{ + gsize struct_size; + const char *type_name; + + void (* copy) (const GskContour *contour, + GskContour *dest); + gsize (* get_size) (const GskContour *contour); + GskPathFlags (* get_flags) (const GskContour *contour); + void (* print) (const GskContour *contour, + GString *string); + gboolean (* get_bounds) (const GskContour *contour, + graphene_rect_t *bounds); + void (* get_start_end) (const GskContour *self, + graphene_point_t *start, + graphene_point_t *end); + gboolean (* foreach) (const GskContour *contour, + float tolerance, + GskPathForeachFunc func, + gpointer user_data); + GskContour * (* reverse) (const GskContour *contour); + int (* get_winding) (const GskContour *contour, + const graphene_point_t *point); + gboolean (* get_closest_point) (const GskContour *contour, + const graphene_point_t *point, + float threshold, + GskPathPoint *result, + float *out_dist); + void (* get_position) (const GskContour *contour, + GskPathPoint *point, + graphene_point_t *position); + void (* get_tangent) (const GskContour *contour, + GskPathPoint *point, + GskPathDirection direction, + graphene_vec2_t *tangent); + float (* get_curvature) (const GskContour *contour, + GskPathPoint *point, + graphene_point_t *center); +}; + +static gsize +gsk_contour_get_size_default (const GskContour *contour) +{ + return contour->klass->struct_size; +} + +/* {{{ Utilities */ + +#define DEG_TO_RAD(x) ((x) * (G_PI / 180.f)) +#define RAD_TO_DEG(x) ((x) / (G_PI / 180.f)) + +static void +_g_string_append_double (GString *string, + double d) +{ + char buf[G_ASCII_DTOSTR_BUF_SIZE]; + + g_ascii_dtostr (buf, G_ASCII_DTOSTR_BUF_SIZE, d); + g_string_append (string, buf); +} + +static void +_g_string_append_point (GString *string, + const graphene_point_t *pt) +{ + _g_string_append_double (string, pt->x); + g_string_append_c (string, ' '); + _g_string_append_double (string, pt->y); +} + + +/* }}} */ +/* {{{ Standard */ + +typedef struct _GskStandardContour GskStandardContour; +struct _GskStandardContour +{ + GskContour contour; + + GskPathFlags flags; + + gsize n_ops; + gsize n_points; + graphene_point_t *points; + gskpathop ops[]; +}; + +static gsize +gsk_standard_contour_compute_size (gsize n_ops, + gsize n_points) +{ + return sizeof (GskStandardContour) + + sizeof (gskpathop) * n_ops + + sizeof (graphene_point_t) * n_points; +} + +static void +gsk_standard_contour_init (GskContour *contour, + GskPathFlags flags, + const graphene_point_t *points, + gsize n_points, + const gskpathop *ops, + gsize n_ops, + ptrdiff_t offset); + +static void +gsk_standard_contour_copy (const GskContour *contour, + GskContour *dest) +{ + const GskStandardContour *self = (const GskStandardContour *) contour; + + gsk_standard_contour_init (dest, self->flags, self->points, self->n_points, self->ops, self->n_ops, 0); +} + +static gsize +gsk_standard_contour_get_size (const GskContour *contour) +{ + const GskStandardContour *self = (const GskStandardContour *) contour; + + return gsk_standard_contour_compute_size (self->n_ops, self->n_points); +} + +static gboolean +gsk_standard_contour_foreach (const GskContour *contour, + float tolerance, + GskPathForeachFunc func, + gpointer user_data) +{ + const GskStandardContour *self = (const GskStandardContour *) contour; + gsize i; + + for (i = 0; i < self->n_ops; i ++) + { + if (!gsk_pathop_foreach (self->ops[i], func, user_data)) + return FALSE; + } + + return TRUE; +} + +static gboolean +add_reverse (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer user_data) +{ + GskPathBuilder *builder = user_data; + GskCurve c, r; + + if (op == GSK_PATH_MOVE) + return TRUE; + + if (op == GSK_PATH_CLOSE) + op = GSK_PATH_LINE; + + gsk_curve_init_foreach (&c, op, pts, n_pts, weight); + gsk_curve_reverse (&c, &r); + gsk_curve_builder_to (&r, builder); + + return TRUE; +} + +static GskContour * +gsk_standard_contour_reverse (const GskContour *contour) +{ + const GskStandardContour *self = (const GskStandardContour *) contour; + GskPathBuilder *builder; + GskPath *path; + GskContour *res; + + builder = gsk_path_builder_new (); + + gsk_path_builder_move_to (builder, self->points[self->n_points - 1].x, + self->points[self->n_points - 1].y); + + for (int i = self->n_ops - 1; i >= 0; i--) + gsk_pathop_foreach (self->ops[i], add_reverse, builder); + + if (self->flags & GSK_PATH_CLOSED) + gsk_path_builder_close (builder); + + path = gsk_path_builder_free_to_path (builder); + + g_assert (gsk_path_get_n_contours (path) == 1); + + res = gsk_contour_dup (gsk_path_get_contour (path, 0)); + + gsk_path_unref (path); + + return res; +} + +static GskPathFlags +gsk_standard_contour_get_flags (const GskContour *contour) +{ + const GskStandardContour *self = (const GskStandardContour *) contour; + + return self->flags; +} + +static void +gsk_standard_contour_print (const GskContour *contour, + GString *string) +{ + const GskStandardContour *self = (const GskStandardContour *) contour; + gsize i; + + for (i = 0; i < self->n_ops; i ++) + { + const graphene_point_t *pt = gsk_pathop_points (self->ops[i]); + + switch (gsk_pathop_op (self->ops[i])) + { + case GSK_PATH_MOVE: + g_string_append (string, "M "); + _g_string_append_point (string, &pt[0]); + break; + + case GSK_PATH_CLOSE: + g_string_append (string, " Z"); + break; + + case GSK_PATH_LINE: + g_string_append (string, " L "); + _g_string_append_point (string, &pt[1]); + break; + + case GSK_PATH_QUAD: + g_string_append (string, " Q "); + _g_string_append_point (string, &pt[1]); + g_string_append (string, ", "); + _g_string_append_point (string, &pt[2]); + break; + + case GSK_PATH_CUBIC: + g_string_append (string, " C "); + _g_string_append_point (string, &pt[1]); + g_string_append (string, ", "); + _g_string_append_point (string, &pt[2]); + g_string_append (string, ", "); + _g_string_append_point (string, &pt[3]); + break; + + case GSK_PATH_CONIC: + /* This is not valid SVG */ + g_string_append (string, " O "); + _g_string_append_point (string, &pt[1]); + g_string_append (string, ", "); + _g_string_append_point (string, &pt[3]); + g_string_append (string, ", "); + _g_string_append_double (string, pt[2].x); + break; + + default: + g_assert_not_reached(); + return; + } + } +} + +static void +rect_add_point (graphene_rect_t *rect, + const graphene_point_t *point) +{ + if (point->x < rect->origin.x) + { + rect->size.width += rect->origin.x - point->x; + rect->origin.x = point->x; + } + else if (point->x > rect->origin.x + rect->size.width) + { + rect->size.width = point->x - rect->origin.x; + } + + if (point->y < rect->origin.y) + { + rect->size.height += rect->origin.y - point->y; + rect->origin.y = point->y; + } + else if (point->y > rect->origin.y + rect->size.height) + { + rect->size.height = point->y - rect->origin.y; + } +} + +static gboolean +gsk_standard_contour_get_bounds (const GskContour *contour, + graphene_rect_t *bounds) +{ + const GskStandardContour *self = (const GskStandardContour *) contour; + gsize i; + + if (self->n_points == 0) + return FALSE; + + graphene_rect_init (bounds, + self->points[0].x, self->points[0].y, + 0, 0); + + for (i = 1; i < self->n_points; i ++) + { + rect_add_point (bounds, &self->points[i]); + } + + return bounds->size.width > 0 && bounds->size.height > 0; +} + +static void +gsk_standard_contour_get_start_end (const GskContour *contour, + graphene_point_t *start, + graphene_point_t *end) +{ + const GskStandardContour *self = (const GskStandardContour *) contour; + + if (start) + *start = self->points[0]; + + if (end) + *end = self->points[self->n_points - 1]; +} + +static int +gsk_standard_contour_get_winding (const GskContour *contour, + const graphene_point_t *point) +{ + GskStandardContour *self = (GskStandardContour *) contour; + int winding = 0; + + for (gsize i = 0; i < self->n_ops; i ++) + { + GskCurve c; + + if (gsk_pathop_op (self->ops[i]) == GSK_PATH_MOVE) + continue; + + gsk_curve_init (&c, self->ops[i]); + winding += gsk_curve_get_crossing (&c, point); + } + + if ((self->flags & GSK_PATH_CLOSED) == 0) + { + GskCurve c; + + gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CLOSE, + (const graphene_point_t[]) { self->points[self->n_points - 1], + self->points[0] })); + + winding += gsk_curve_get_crossing (&c, point); + } + + return winding; +} + +static gboolean +gsk_standard_contour_get_closest_point (const GskContour *contour, + const graphene_point_t *point, + float threshold, + GskPathPoint *result, + float *out_dist) +{ + GskStandardContour *self = (GskStandardContour *) contour; + unsigned int best_idx = G_MAXUINT; + float best_t = 0; + + g_assert (gsk_pathop_op (self->ops[0]) == GSK_PATH_MOVE); + + if (self->n_ops == 1) + { + float dist; + + dist = graphene_point_distance (point, &self->points[0], NULL, NULL); + if (dist <= threshold) + { + *out_dist = dist; + result->data.std.idx = 0; + result->data.std.t = 0; + return TRUE; + } + + return FALSE; + } + + for (gsize i = 0; i < self->n_ops; i ++) + { + GskCurve c; + float distance, t; + + if (gsk_pathop_op (self->ops[i]) == GSK_PATH_MOVE) + continue; + + gsk_curve_init (&c, self->ops[i]); + if (gsk_curve_get_closest_point (&c, point, threshold, &distance, &t)) + { + best_idx = i; + best_t = t; + threshold = distance; + } + } + + if (best_idx != G_MAXUINT) + { + *out_dist = threshold; + result->data.std.idx = best_idx; + result->data.std.t = best_t; + return TRUE; + } + + return FALSE; +} + +static void +gsk_standard_contour_get_position (const GskContour *contour, + GskPathPoint *point, + graphene_point_t *position) +{ + GskStandardContour *self = (GskStandardContour *) contour; + GskCurve curve; + + if (G_UNLIKELY (point->data.std.idx == 0)) + { + *position = self->points[0]; + return; + } + + gsk_curve_init (&curve, self->ops[point->data.std.idx]); + gsk_curve_get_point (&curve, point->data.std.t, position); +} + +static void +gsk_standard_contour_get_tangent (const GskContour *contour, + GskPathPoint *point, + GskPathDirection direction, + graphene_vec2_t *tangent) +{ + GskStandardContour *self = (GskStandardContour *) contour; + GskCurve curve; + + if (G_UNLIKELY (point->data.std.idx == 0)) + { + graphene_vec2_init (tangent, 1, 0); + return; + } + + gsk_curve_init (&curve, self->ops[point->data.std.idx]); + gsk_curve_get_tangent (&curve, point->data.std.t, tangent); +} + +static float +gsk_standard_contour_get_curvature (const GskContour *contour, + GskPathPoint *point, + graphene_point_t *center) +{ + GskStandardContour *self = (GskStandardContour *) contour; + GskCurve curve; + + if (G_UNLIKELY (point->data.std.idx == 0)) + return 0; + + gsk_curve_init (&curve, self->ops[point->data.std.idx]); + return gsk_curve_get_curvature (&curve, point->data.std.t, center); +} + +static const GskContourClass GSK_STANDARD_CONTOUR_CLASS = +{ + sizeof (GskStandardContour), + "GskStandardContour", + gsk_standard_contour_copy, + gsk_standard_contour_get_size, + gsk_standard_contour_get_flags, + gsk_standard_contour_print, + gsk_standard_contour_get_bounds, + gsk_standard_contour_get_start_end, + gsk_standard_contour_foreach, + gsk_standard_contour_reverse, + gsk_standard_contour_get_winding, + gsk_standard_contour_get_closest_point, + gsk_standard_contour_get_position, + gsk_standard_contour_get_tangent, + gsk_standard_contour_get_curvature, +}; + +/* You must ensure the contour has enough size allocated, + * see gsk_standard_contour_compute_size() + */ +static void +gsk_standard_contour_init (GskContour *contour, + GskPathFlags flags, + const graphene_point_t *points, + gsize n_points, + const gskpathop *ops, + gsize n_ops, + gssize offset) + +{ + GskStandardContour *self = (GskStandardContour *) contour; + gsize i; + + self->contour.klass = &GSK_STANDARD_CONTOUR_CLASS; + + self->flags = flags; + self->n_ops = n_ops; + self->n_points = n_points; + self->points = (graphene_point_t *) &self->ops[n_ops]; + memcpy (self->points, points, sizeof (graphene_point_t) * n_points); + + offset += self->points - points; + for (i = 0; i < n_ops; i++) + { + self->ops[i] = gsk_pathop_encode (gsk_pathop_op (ops[i]), + gsk_pathop_points (ops[i]) + offset); + } +} + +GskContour * +gsk_standard_contour_new (GskPathFlags flags, + const graphene_point_t *points, + gsize n_points, + const gskpathop *ops, + gsize n_ops, + gssize offset) +{ + GskContour *contour; + + contour = g_malloc0 (gsk_standard_contour_compute_size (n_ops, n_points)); + + gsk_standard_contour_init (contour, flags, points, n_points, ops, n_ops, offset); + + return contour; +} + +/* }}} */ +/* {{{ Rectangle */ + +typedef struct _GskRectContour GskRectContour; +struct _GskRectContour +{ + GskContour contour; + + float x; + float y; + float width; + float height; +}; + +static void +gsk_rect_contour_copy (const GskContour *contour, + GskContour *dest) +{ + const GskRectContour *self = (const GskRectContour *) contour; + GskRectContour *target = (GskRectContour *) dest; + + *target = *self; +} + +static GskPathFlags +gsk_rect_contour_get_flags (const GskContour *contour) +{ + return GSK_PATH_FLAT | GSK_PATH_CLOSED; +} + +static void +gsk_rect_contour_print (const GskContour *contour, + GString *string) +{ + const GskRectContour *self = (const GskRectContour *) contour; + + g_string_append (string, "M "); + _g_string_append_point (string, &GRAPHENE_POINT_INIT (self->x, self->y)); + g_string_append (string, " h "); + _g_string_append_double (string, self->width); + g_string_append (string, " v "); + _g_string_append_double (string, self->height); + g_string_append (string, " h "); + _g_string_append_double (string, - self->width); + g_string_append (string, " z"); +} + +static gboolean +gsk_rect_contour_get_bounds (const GskContour *contour, + graphene_rect_t *rect) +{ + const GskRectContour *self = (const GskRectContour *) contour; + + graphene_rect_init (rect, self->x, self->y, self->width, self->height); + + return TRUE; +} + +static void +gsk_rect_contour_get_start_end (const GskContour *contour, + graphene_point_t *start, + graphene_point_t *end) +{ + const GskRectContour *self = (const GskRectContour *) contour; + + if (start) + *start = GRAPHENE_POINT_INIT (self->x, self->y); + + if (end) + *end = GRAPHENE_POINT_INIT (self->x, self->y); +} + +static gboolean +gsk_rect_contour_foreach (const GskContour *contour, + float tolerance, + GskPathForeachFunc func, + gpointer user_data) +{ + const GskRectContour *self = (const GskRectContour *) contour; + + graphene_point_t pts[] = { + GRAPHENE_POINT_INIT (self->x, self->y), + GRAPHENE_POINT_INIT (self->x + self->width, self->y), + GRAPHENE_POINT_INIT (self->x + self->width, self->y + self->height), + GRAPHENE_POINT_INIT (self->x, self->y + self->height), + GRAPHENE_POINT_INIT (self->x, self->y) + }; + + return func (GSK_PATH_MOVE, &pts[0], 1, 0, user_data) + && func (GSK_PATH_LINE, &pts[0], 2, 0, user_data) + && func (GSK_PATH_LINE, &pts[1], 2, 0, user_data) + && func (GSK_PATH_LINE, &pts[2], 2, 0, user_data) + && func (GSK_PATH_CLOSE, &pts[3], 2, 0, user_data); +} + +static GskContour * +gsk_rect_contour_reverse (const GskContour *contour) +{ + const GskRectContour *self = (const GskRectContour *) contour; + + return gsk_rect_contour_new (&GRAPHENE_RECT_INIT (self->x + self->width, + self->y, + - self->width, + self->height)); +} + +static void +gsk_rect_contour_point_tangent (const GskRectContour *self, + float distance, + GskPathDirection direction, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + if (distance == 0) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x, self->y); + + if (tangent) + { + if (direction == GSK_PATH_START) + graphene_vec2_init (tangent, 0.0f, - copysignf (1.0f, self->height)); + else + graphene_vec2_init (tangent, copysignf (1.0f, self->width), 0.0f); + } + return; + } + + if (distance < fabsf (self->width)) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + copysignf (distance, self->width), self->y); + if (tangent) + graphene_vec2_init (tangent, copysignf (1.0f, self->width), 0.0f); + return; + } + distance -= fabsf (self->width); + + if (distance == 0) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + self->width, self->y); + + if (tangent) + { + if (direction == GSK_PATH_START) + graphene_vec2_init (tangent, copysignf (1.0f, self->width), 0.0f); + else + graphene_vec2_init (tangent, 0.0f, copysignf (1.0f, self->height)); + } + return; + } + + if (distance < fabsf (self->height)) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + self->width, self->y + copysignf (distance, self->height)); + if (tangent) + graphene_vec2_init (tangent, 0.0f, copysignf (1.0f, self->height)); + return; + } + distance -= fabs (self->height); + + if (distance == 0) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + self->width, self->y + self->height); + + if (tangent) + { + if (direction == GSK_PATH_START) + graphene_vec2_init (tangent, 0.0f, copysignf (1.0f, self->height)); + else + graphene_vec2_init (tangent, - copysignf (1.0f, self->width), 0.0f); + } + return; + } + + if (distance < fabsf (self->width)) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + self->width - copysignf (distance, self->width), self->y + self->height); + if (tangent) + graphene_vec2_init (tangent, - copysignf (1.0f, self->width), 0.0f); + return; + } + distance -= fabsf (self->width); + + if (distance == 0) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x, self->y + self->height); + + if (tangent) + { + if (direction == GSK_PATH_START) + graphene_vec2_init (tangent, - copysignf (1.0f, self->width), 0.0f); + else + graphene_vec2_init (tangent, 0.0f, - copysignf (1.0f, self->height)); + } + return; + } + + if (distance < fabsf (self->height)) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x, self->y + self->height - copysignf (distance, self->height)); + if (tangent) + graphene_vec2_init (tangent, 0.0f, - copysignf (1.0f, self->height)); + return; + } + + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x, self->y); + + if (tangent) + { + if (direction == GSK_PATH_START) + graphene_vec2_init (tangent, 0.0f, - copysignf (1.0f, self->height)); + else + graphene_vec2_init (tangent, copysignf (1.0f, self->width), 0.0f); + } +} + +static gboolean +gsk_rect_contour_closest_point (const GskRectContour *self, + const graphene_point_t *point, + float threshold, + float *out_distance, + float *out_offset) +{ + graphene_point_t t, p; + float distance; + + /* offset coords to be relative to rectangle */ + t.x = point->x - self->x; + t.y = point->y - self->y; + + if (self->width) + { + /* do unit square math */ + t.x /= self->width; + /* move point onto the square */ + t.x = CLAMP (t.x, 0.f, 1.f); + } + else + t.x = 0.f; + + if (self->height) + { + t.y /= self->height; + t.y = CLAMP (t.y, 0.f, 1.f); + } + else + t.y = 0.f; + + if (t.x > 0 && t.x < 1 && t.y > 0 && t.y < 1) + { + float diff = MIN (t.x, 1.f - t.x) * ABS (self->width) - MIN (t.y, 1.f - t.y) * ABS (self->height); + + if (diff < 0.f) + t.x = ceilf (t.x - 0.5f); /* round 0.5 down */ + else if (diff > 0.f) + t.y = roundf (t.y); /* round 0.5 up */ + else + { + /* at least 2 points match, return the first one in the stroke */ + if (t.y <= 1.f - t.y) + t.y = 0.f; + else if (1.f - t.x <= t.x) + t.x = 1.f; + else + t.y = 1.f; + } + } + + /* Don't let -0 confuse us */ + t.x = fabs (t.x); + t.y = fabs (t.y); + + p = GRAPHENE_POINT_INIT (self->x + t.x * self->width, + self->y + t.y * self->height); + + distance = graphene_point_distance (point, &p, NULL, NULL); + if (distance > threshold) + return FALSE; + + *out_distance = distance; + + if (t.y == 0) + *out_offset = t.x * fabs (self->width); + else if (t.y == 1) + *out_offset = (2 - t.x) * fabs (self->width) + fabs (self->height); + else if (t.x == 1) + *out_offset = fabs (self->width) + t.y * fabs (self->height); + else + *out_offset = 2 * fabs (self->width) + (2 - t.y) * fabs (self->height); + + return TRUE; +} + +static int +gsk_rect_contour_get_winding (const GskContour *contour, + const graphene_point_t *point) +{ + const GskRectContour *self = (const GskRectContour *) contour; + graphene_rect_t rect; + + graphene_rect_init (&rect, self->x, self->y, self->width, self->height); + + if (graphene_rect_contains_point (&rect, point)) + return -1; + + return 0; +} + +static gboolean +gsk_rect_contour_get_closest_point (const GskContour *contour, + const graphene_point_t *point, + float threshold, + GskPathPoint *result, + float *out_dist) +{ + const GskRectContour *self = (const GskRectContour *) contour; + float distance; + + if (gsk_rect_contour_closest_point (self, point, threshold, out_dist, &distance)) + { + result->data.rect.distance = distance; + return TRUE; + } + + return FALSE; +} + +static void +gsk_rect_contour_get_position (const GskContour *contour, + GskPathPoint *point, + graphene_point_t *position) +{ + const GskRectContour *self = (const GskRectContour *) contour; + + gsk_rect_contour_point_tangent (self, point->data.rect.distance, GSK_PATH_END, position, NULL); +} + +static void +gsk_rect_contour_get_tangent (const GskContour *contour, + GskPathPoint *point, + GskPathDirection direction, + graphene_vec2_t *tangent) +{ + const GskRectContour *self = (const GskRectContour *) contour; + + gsk_rect_contour_point_tangent (self, point->data.rect.distance, direction, NULL, tangent); +} + +static float +gsk_rect_contour_get_curvature (const GskContour *contour, + GskPathPoint *point, + graphene_point_t *center) +{ + return 0; +} + +static const GskContourClass GSK_RECT_CONTOUR_CLASS = +{ + sizeof (GskRectContour), + "GskRectContour", + gsk_rect_contour_copy, + gsk_contour_get_size_default, + gsk_rect_contour_get_flags, + gsk_rect_contour_print, + gsk_rect_contour_get_bounds, + gsk_rect_contour_get_start_end, + gsk_rect_contour_foreach, + gsk_rect_contour_reverse, + gsk_rect_contour_get_winding, + gsk_rect_contour_get_closest_point, + gsk_rect_contour_get_position, + gsk_rect_contour_get_tangent, + gsk_rect_contour_get_curvature, +}; + +GskContour * +gsk_rect_contour_new (const graphene_rect_t *rect) +{ + GskRectContour *self; + + self = g_new0 (GskRectContour, 1); + + self->contour.klass = &GSK_RECT_CONTOUR_CLASS; + + self->x = rect->origin.x; + self->y = rect->origin.y; + self->width = rect->size.width; + self->height = rect->size.height; + + return (GskContour *) self; +} + +/* }}} */ +/* {{{ Circle */ + +typedef struct _GskCircleContour GskCircleContour; +struct _GskCircleContour +{ + GskContour contour; + + graphene_point_t center; + float radius; + float start_angle; /* in degrees */ + float end_angle; /* start_angle +/- 360 */ +}; + +static void +gsk_circle_contour_copy (const GskContour *contour, + GskContour *dest) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + GskCircleContour *target = (GskCircleContour *) dest; + + *target = *self; +} + +static GskPathFlags +gsk_circle_contour_get_flags (const GskContour *contour) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + + /* XXX: should we explicitly close paths? */ + if (fabs (self->start_angle - self->end_angle) >= 360) + return GSK_PATH_CLOSED; + else + return 0; +} + +#define GSK_CIRCLE_POINT_INIT(self, angle) \ + GRAPHENE_POINT_INIT ((self)->center.x + cos (DEG_TO_RAD (angle)) * self->radius, \ + (self)->center.y + sin (DEG_TO_RAD (angle)) * self->radius) + +static void +gsk_circle_contour_print (const GskContour *contour, + GString *string) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + float mid_angle = (self->end_angle - self->start_angle) / 2; + + g_string_append (string, "M "); + _g_string_append_point (string, &GSK_CIRCLE_POINT_INIT (self, self->start_angle)); + g_string_append (string, " A "); + _g_string_append_point (string, &GRAPHENE_POINT_INIT (self->radius, self->radius)); + g_string_append_printf (string, " 0 0 %u ", + self->start_angle < self->end_angle ? 0 : 1); + _g_string_append_point (string, &GSK_CIRCLE_POINT_INIT (self, mid_angle)); + g_string_append (string, " A "); + _g_string_append_point (string, &GRAPHENE_POINT_INIT (self->radius, self->radius)); + g_string_append_printf (string, " 0 0 %u ", + self->start_angle < self->end_angle ? 0 : 1); + _g_string_append_point (string, &GSK_CIRCLE_POINT_INIT (self, self->end_angle)); + if (fabs (self->start_angle - self->end_angle) >= 360) + g_string_append (string, " z"); +} + +static gboolean +gsk_circle_contour_get_bounds (const GskContour *contour, + graphene_rect_t *rect) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + + /* XXX: handle partial circles */ + graphene_rect_init (rect, + self->center.x - self->radius, + self->center.y - self->radius, + 2 * self->radius, + 2 * self->radius); + + return TRUE; +} + +static void +gsk_circle_contour_get_start_end (const GskContour *contour, + graphene_point_t *start, + graphene_point_t *end) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + + if (start) + *start = GSK_CIRCLE_POINT_INIT (self, self->start_angle); + + if (end) + *end = GSK_CIRCLE_POINT_INIT (self, self->end_angle); +} + +typedef struct +{ + GskPathForeachFunc func; + gpointer user_data; +} ForeachWrapper; + +static gboolean +gsk_circle_contour_curve (const graphene_point_t curve[4], + gpointer data) +{ + ForeachWrapper *wrapper = data; + + return wrapper->func (GSK_PATH_CUBIC, curve, 4, 0, wrapper->user_data); +} + +static gboolean +gsk_circle_contour_foreach (const GskContour *contour, + float tolerance, + GskPathForeachFunc func, + gpointer user_data) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + graphene_point_t start = GSK_CIRCLE_POINT_INIT (self, self->start_angle); + + if (!func (GSK_PATH_MOVE, &start, 1, 0, user_data)) + return FALSE; + + if (!gsk_spline_decompose_arc (&self->center, + self->radius, + tolerance, + DEG_TO_RAD (self->start_angle), + DEG_TO_RAD (self->end_angle), + gsk_circle_contour_curve, + &(ForeachWrapper) { func, user_data })) + return FALSE; + + if (fabs (self->start_angle - self->end_angle) >= 360) + { + if (!func (GSK_PATH_CLOSE, (graphene_point_t[2]) { start, start }, 2, 0, user_data)) + return FALSE; + } + + return TRUE; +} + +static GskContour * +gsk_circle_contour_reverse (const GskContour *contour) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + + return gsk_circle_contour_new (&self->center, + self->radius, + self->end_angle, + self->start_angle); +} + +static gboolean +gsk_circle_contour_closest_point (const GskCircleContour *self, + const graphene_point_t *point, + float threshold, + float *out_distance, + float *out_angle) +{ + float angle; + float closest_angle; + graphene_point_t pos; + float distance; + + if (graphene_point_distance (point, &self->center, NULL, NULL) > threshold + self->radius) + return FALSE; + + angle = atan2f (point->y - self->center.y, point->x - self->center.x); + angle = RAD_TO_DEG (angle); + if (angle < 0) + angle += 360; + + if ((self->start_angle <= angle && angle <= self->end_angle) || + (self->end_angle <= angle && angle <= self->start_angle)) + { + closest_angle = angle; + } + else + { + float d1, d2; + + d1 = fabs (self->start_angle - angle); + d1 = MIN (d1, 360 - d1); + d2 = fabs (self->end_angle - angle); + d2 = MIN (d2, 360 - d2); + if (d1 < d2) + closest_angle = self->start_angle; + else + closest_angle = self->end_angle; + } + + pos = GSK_CIRCLE_POINT_INIT (self, closest_angle); + + distance = graphene_point_distance (&pos, point, NULL, NULL); + if (threshold < distance) + return FALSE; + + *out_distance = distance; + *out_angle = closest_angle; + + return TRUE; +} + +static int +gsk_circle_contour_get_winding (const GskContour *contour, + const graphene_point_t *point) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + + if (graphene_point_distance (point, &self->center, NULL, NULL) >= self->radius) + return 0; + + if (fabs (self->start_angle - self->end_angle) >= 360) + { + return -1; + } + else + { + /* Check if the point and the midpoint are on the same side + * of the chord through start and end. + */ + double mid_angle = (self->end_angle - self->start_angle) / 2; + graphene_point_t start = GRAPHENE_POINT_INIT (self->center.x + cos (DEG_TO_RAD (self->start_angle)) * self->radius, + self->center.y + sin (DEG_TO_RAD (self->start_angle)) * self->radius); + graphene_point_t mid = GRAPHENE_POINT_INIT (self->center.x + cos (DEG_TO_RAD (mid_angle)) * self->radius, + self->center.y + sin (DEG_TO_RAD (mid_angle)) * self->radius); + graphene_point_t end = GRAPHENE_POINT_INIT (self->center.x + cos (DEG_TO_RAD (self->end_angle)) * self->radius, + self->center.y + sin (DEG_TO_RAD (self->end_angle)) * self->radius); + + graphene_vec2_t n, m; + float a, b; + + graphene_vec2_init (&n, start.y - end.y, end.x - start.x); + graphene_vec2_init (&m, mid.x, mid.y); + a = graphene_vec2_dot (&m, &n); + graphene_vec2_init (&m, point->x, point->y); + b = graphene_vec2_dot (&m, &n); + + if ((a < 0) != (b < 0)) + return -1; + } + + return 0; +} + +typedef struct +{ + float angle; +} CirclePointData; + +static gboolean +gsk_circle_contour_get_closest_point (const GskContour *contour, + const graphene_point_t *point, + float threshold, + GskPathPoint *result, + float *out_dist) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + float dist, angle; + + if (gsk_circle_contour_closest_point (self, point, threshold, &dist, &angle)) + { + result->data.circle.angle = angle; + *out_dist = dist; + return TRUE; + } + + return FALSE; +} + +static void +gsk_circle_contour_get_position (const GskContour *contour, + GskPathPoint *point, + graphene_point_t *position) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + + *position = GSK_CIRCLE_POINT_INIT (self, point->data.circle.angle); +} + +static void +gsk_circle_contour_get_tangent (const GskContour *contour, + GskPathPoint *point, + GskPathDirection direction, + graphene_vec2_t *tangent) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + graphene_point_t p = GSK_CIRCLE_POINT_INIT (self, point->data.circle.angle); + + graphene_vec2_init (tangent, p.y - self->center.y, - p.x + self->center.x); + graphene_vec2_normalize (tangent, tangent); +} + +static float +gsk_circle_contour_get_curvature (const GskContour *contour, + GskPathPoint *point, + graphene_point_t *center) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + + if (center) + *center = self->center; + + return 1 / self->radius; +} + +static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS = +{ + sizeof (GskCircleContour), + "GskCircleContour", + gsk_circle_contour_copy, + gsk_contour_get_size_default, + gsk_circle_contour_get_flags, + gsk_circle_contour_print, + gsk_circle_contour_get_bounds, + gsk_circle_contour_get_start_end, + gsk_circle_contour_foreach, + gsk_circle_contour_reverse, + gsk_circle_contour_get_winding, + gsk_circle_contour_get_closest_point, + gsk_circle_contour_get_position, + gsk_circle_contour_get_tangent, + gsk_circle_contour_get_curvature, +}; + +GskContour * +gsk_circle_contour_new (const graphene_point_t *center, + float radius, + float start_angle, + float end_angle) +{ + GskCircleContour *self; + + self = g_new0 (GskCircleContour, 1); + + self->contour.klass = &GSK_CIRCLE_CONTOUR_CLASS; + + g_assert (fabs (start_angle - end_angle) <= 360); + + self->contour.klass = &GSK_CIRCLE_CONTOUR_CLASS; + self->center = *center; + self->radius = radius; + self->start_angle = start_angle; + self->end_angle = end_angle; + + return (GskContour *) self; +} + +/* }}} */ +/* {{{ Rounded Rectangle */ + +typedef struct _GskRoundedRectContour GskRoundedRectContour; +struct _GskRoundedRectContour +{ + GskContour contour; + + GskRoundedRect rect; + gboolean ccw; +}; + +static void +gsk_rounded_rect_contour_copy (const GskContour *contour, + GskContour *dest) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + GskRoundedRectContour *target = (GskRoundedRectContour *) dest; + + *target = *self; +} + +static GskPathFlags +gsk_rounded_rect_contour_get_flags (const GskContour *contour) +{ + return GSK_PATH_CLOSED; +} + +static inline void +get_rounded_rect_points (const GskRoundedRect *rect, + graphene_point_t *pts) +{ + pts[0] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width, rect->bounds.origin.y); + pts[1] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width, rect->bounds.origin.y); + pts[2] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width, rect->bounds.origin.y); + pts[3] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width, rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height); + pts[4] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width, rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_RIGHT].height); + pts[5] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width, rect->bounds.origin.y + rect->bounds.size.height); + pts[6] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width, rect->bounds.origin.y + rect->bounds.size.height); + pts[7] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->corner[GSK_CORNER_BOTTOM_LEFT].width, rect->bounds.origin.y + rect->bounds.size.height); + pts[8] = GRAPHENE_POINT_INIT (rect->bounds.origin.x, rect->bounds.origin.y + rect->bounds.size.height); + pts[9] = GRAPHENE_POINT_INIT (rect->bounds.origin.x, rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height); + pts[10] = GRAPHENE_POINT_INIT (rect->bounds.origin.x, rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_LEFT].height); + pts[11] = GRAPHENE_POINT_INIT (rect->bounds.origin.x, rect->bounds.origin.y); + pts[12] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width, rect->bounds.origin.y); +} + +static void +gsk_rounded_rect_contour_print (const GskContour *contour, + GString *string) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + graphene_point_t pts[13]; + + get_rounded_rect_points (&self->rect, pts); + +#define APPEND_MOVE(p) \ + g_string_append (string, "M "); \ + _g_string_append_point (string, p); +#define APPEND_LINE(p) \ + g_string_append (string, " L "); \ + _g_string_append_point (string, p); +#define APPEND_CONIC(p1, p2) \ + g_string_append (string, " O "); \ + _g_string_append_point (string, p1); \ + g_string_append (string, ", "); \ + _g_string_append_point (string, p2); \ + g_string_append (string, ", "); \ + _g_string_append_double (string, M_SQRT1_2); +#define APPEND_CLOSE g_string_append (string, " z"); + if (self->ccw) + { + graphene_point_t p; +#define SWAP(a,b,c) a = b; b = c; c = a; + SWAP (p, pts[1], pts[11]); + SWAP (p, pts[2], pts[10]); + SWAP (p, pts[3], pts[9]); + SWAP (p, pts[4], pts[8]); + SWAP (p, pts[5], pts[7]); +#undef SWAP + APPEND_MOVE (&pts[0]); + APPEND_CONIC (&pts[1], &pts[2]); + APPEND_LINE (&pts[3]); + APPEND_CONIC (&pts[4], &pts[5]); + APPEND_LINE (&pts[6]); + APPEND_CONIC (&pts[7], &pts[8]); + APPEND_LINE (&pts[9]); + APPEND_CONIC (&pts[10], &pts[11]); + APPEND_LINE (&pts[12]); + APPEND_CLOSE + } + else + { + APPEND_MOVE (&pts[0]); + APPEND_LINE (&pts[1]); + APPEND_CONIC (&pts[2], &pts[3]); + APPEND_LINE (&pts[4]); + APPEND_CONIC (&pts[5], &pts[6]); + APPEND_LINE (&pts[7]); + APPEND_CONIC (&pts[8], &pts[9]); + APPEND_LINE (&pts[10]); + APPEND_CONIC (&pts[11], &pts[12]); + APPEND_CLOSE; + } +#undef APPEND_MOVE +#undef APPEND_LINE +#undef APPEND_CONIC +#undef APPEND_CLOSE +} + +static gboolean +gsk_rounded_rect_contour_get_bounds (const GskContour *contour, + graphene_rect_t *rect) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + + graphene_rect_init_from_rect (rect, &self->rect.bounds); + + return TRUE; +} + +static void +gsk_rounded_rect_contour_get_start_end (const GskContour *contour, + graphene_point_t *start, + graphene_point_t *end) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + + if (start) + *start = GRAPHENE_POINT_INIT (self->rect.bounds.origin.x + self->rect.corner[GSK_CORNER_TOP_LEFT].width, + self->rect.bounds.origin.y); + + if (end) + *end = GRAPHENE_POINT_INIT (self->rect.bounds.origin.x + self->rect.corner[GSK_CORNER_TOP_LEFT].width, + self->rect.bounds.origin.y); +} + +static gboolean +gsk_rounded_rect_contour_foreach (const GskContour *contour, + float tolerance, + GskPathForeachFunc func, + gpointer user_data) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + graphene_point_t pts[13]; + + get_rounded_rect_points (&self->rect, pts); + if (self->ccw) + { + graphene_point_t p; +#define SWAP(a,b,c) a = b; b = c; c = a; + SWAP (p, pts[1], pts[11]); + SWAP (p, pts[2], pts[10]); + SWAP (p, pts[3], pts[9]); + SWAP (p, pts[4], pts[8]); + SWAP (p, pts[5], pts[7]); +#undef SWAP + + return func (GSK_PATH_MOVE, &pts[0], 1, 0, user_data) && + func (GSK_PATH_CONIC, &pts[0], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[2], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[3], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[5], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[6], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[8], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[9], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[11], 2, 0, user_data) && + func (GSK_PATH_CLOSE, &pts[12], 2, 0, user_data); + } + else + { + return func (GSK_PATH_MOVE, &pts[0], 1, 0, user_data) && + func (GSK_PATH_LINE, &pts[0], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[1], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[3], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[4], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[6], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[7], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_LINE, &pts[9], 2, 0, user_data) && + func (GSK_PATH_CONIC, &pts[10], 3, M_SQRT1_2, user_data) && + func (GSK_PATH_CLOSE, &pts[12], 2, 0, user_data); + } +} + +typedef struct +{ + GskCurve *curve; + unsigned int idx; + unsigned int count; +} InitCurveData; + +static gboolean +init_curve_cb (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer user_data) +{ + InitCurveData *data = user_data; + + if (data->idx == data->count) + { + gsk_curve_init_foreach (data->curve, op, pts, n_pts, weight); + return FALSE; + } + + data->count++; + return TRUE; +} + +static void +gsk_rounded_rect_contour_init_curve (const GskContour *contour, + unsigned int idx, + GskCurve *curve) +{ + InitCurveData data; + + data.curve = curve; + data.idx = idx; + data.count = 0; + + gsk_contour_foreach (contour, GSK_PATH_TOLERANCE_DEFAULT, init_curve_cb, &data); +} + +static GskContour * +gsk_rounded_rect_contour_reverse (const GskContour *contour) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + GskRoundedRectContour *copy; + + copy = g_new0 (GskRoundedRectContour, 1); + gsk_rounded_rect_contour_copy (contour, (GskContour *)copy); + copy->ccw = !self->ccw; + + return (GskContour *)copy; +} + +static gboolean +add_cb (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer user_data) +{ + GskPathBuilder *builder = user_data; + switch (op) + { + case GSK_PATH_MOVE: + gsk_path_builder_move_to (builder, pts[0].x, pts[0].y); + break; + case GSK_PATH_LINE: + gsk_path_builder_line_to (builder, pts[1].x, pts[1].y); + break; + case GSK_PATH_QUAD: + gsk_path_builder_quad_to (builder, + pts[1].x, pts[1].y, + pts[2].x, pts[2].y); + break; + case GSK_PATH_CUBIC: + gsk_path_builder_cubic_to (builder, + pts[1].x, pts[1].y, + pts[2].x, pts[2].y, + pts[3].x, pts[3].y); + break; + case GSK_PATH_CONIC: + gsk_path_builder_conic_to (builder, + pts[1].x, pts[1].y, + pts[2].x, pts[2].y, + weight); + break; + case GSK_PATH_CLOSE: + gsk_path_builder_close (builder); + break; + default: + g_assert_not_reached (); + } + + return TRUE; +} + +static GskPath * +convert_to_standard_contour (const GskContour *contour, + float tolerance) +{ + GskPathBuilder *builder; + GskPath *path; + + builder = gsk_path_builder_new (); + gsk_contour_foreach (contour, tolerance, add_cb, builder); + path = gsk_path_builder_free_to_path (builder); + + return path; +} + +static int +gsk_rounded_rect_contour_get_winding (const GskContour *contour, + const graphene_point_t *point) +{ + const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour; + + if (gsk_rounded_rect_contains_point (&self->rect, point)) + return self->ccw ? 1 : -1; + + return 0; +} + +static gboolean +gsk_rounded_rect_contour_get_closest_point (const GskContour *contour, + const graphene_point_t *point, + float threshold, + GskPathPoint *result, + float *out_dist) +{ + GskPath *path; + const GskContour *std; + gboolean ret; + + path = convert_to_standard_contour (contour, GSK_PATH_TOLERANCE_DEFAULT); + std = gsk_path_get_contour (path, 0); + ret = gsk_standard_contour_get_closest_point (std, point, threshold, result, out_dist); + gsk_path_unref (path); + + return ret; +} + +static void +gsk_rounded_rect_contour_get_position (const GskContour *contour, + GskPathPoint *point, + graphene_point_t *position) +{ + GskCurve curve; + + gsk_rounded_rect_contour_init_curve (contour, point->data.std.idx, &curve); + gsk_curve_get_point (&curve, point->data.std.t, position); +} + +static void +gsk_rounded_rect_contour_get_tangent (const GskContour *contour, + GskPathPoint *point, + GskPathDirection direction, + graphene_vec2_t *tangent) +{ + GskCurve curve; + + gsk_rounded_rect_contour_init_curve (contour, point->data.std.idx, &curve); + gsk_curve_get_tangent (&curve, point->data.std.t, tangent); +} + +static float +gsk_rounded_rect_contour_get_curvature (const GskContour *contour, + GskPathPoint *point, + graphene_point_t *center) +{ + GskCurve curve; + + gsk_rounded_rect_contour_init_curve (contour, point->data.std.idx, &curve); + return gsk_curve_get_curvature (&curve, point->data.std.t, center); +} + +static const GskContourClass GSK_ROUNDED_RECT_CONTOUR_CLASS = +{ + sizeof (GskRoundedRectContour), + "GskRoundedRectContour", + gsk_rounded_rect_contour_copy, + gsk_contour_get_size_default, + gsk_rounded_rect_contour_get_flags, + gsk_rounded_rect_contour_print, + gsk_rounded_rect_contour_get_bounds, + gsk_rounded_rect_contour_get_start_end, + gsk_rounded_rect_contour_foreach, + gsk_rounded_rect_contour_reverse, + gsk_rounded_rect_contour_get_winding, + gsk_rounded_rect_contour_get_closest_point, + gsk_rounded_rect_contour_get_position, + gsk_rounded_rect_contour_get_tangent, + gsk_rounded_rect_contour_get_curvature, +}; + +GskContour * +gsk_rounded_rect_contour_new (const GskRoundedRect *rect) +{ + GskRoundedRectContour *self; + + self = g_new0 (GskRoundedRectContour, 1); + + self->contour.klass = &GSK_ROUNDED_RECT_CONTOUR_CLASS; + + self->rect = *rect; + + return (GskContour *) self; +} + +/* }}} */ +/* {{{ API */ + +gsize +gsk_contour_get_size (const GskContour *self) +{ + return self->klass->get_size (self); +} + +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; +} + +GskContour * +gsk_contour_reverse (const GskContour *src) +{ + return src->klass->reverse (src); +} + +GskPathFlags +gsk_contour_get_flags (const GskContour *self) +{ + return self->klass->get_flags (self); +} + +void +gsk_contour_print (const GskContour *self, + GString *string) +{ + self->klass->print (self, string); +} + +gboolean +gsk_contour_get_bounds (const GskContour *self, + graphene_rect_t *bounds) +{ + return self->klass->get_bounds (self, bounds); +} + +gboolean +gsk_contour_foreach (const GskContour *self, + float tolerance, + GskPathForeachFunc func, + gpointer user_data) +{ + return self->klass->foreach (self, tolerance, func, user_data); +} + +void +gsk_contour_get_start_end (const GskContour *self, + graphene_point_t *start, + graphene_point_t *end) +{ + self->klass->get_start_end (self, start, end); +} + +int +gsk_contour_get_winding (const GskContour *self, + const graphene_point_t *point) +{ + return self->klass->get_winding (self, point); +} + +gboolean +gsk_contour_get_closest_point (const GskContour *self, + const graphene_point_t *point, + float threshold, + GskPathPoint *result, + float *out_dist) +{ + return self->klass->get_closest_point (self, point, threshold, result, out_dist); +} + +void +gsk_contour_get_position (const GskContour *self, + GskPathPoint *point, + graphene_point_t *pos) +{ + self->klass->get_position (self, point, pos); +} + +void +gsk_contour_get_tangent (const GskContour *self, + GskPathPoint *point, + GskPathDirection direction, + graphene_vec2_t *tangent) +{ + self->klass->get_tangent (self, point, direction, tangent); +} + +float +gsk_contour_get_curvature (const GskContour *self, + GskPathPoint *point, + graphene_point_t *center) +{ + return self->klass->get_curvature (self, point, center); +} + +/* }}} */ + +/* vim:set foldmethod=marker expandtab: */ diff --git a/gsk/gskcontourprivate.h b/gsk/gskcontourprivate.h new file mode 100644 index 0000000000..f3443eb995 --- /dev/null +++ b/gsk/gskcontourprivate.h @@ -0,0 +1,86 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + + +#pragma once + +#include "gskpath.h" + +#include "gskpathopprivate.h" + +G_BEGIN_DECLS + +typedef enum +{ + GSK_PATH_FLAT, + GSK_PATH_CLOSED +} GskPathFlags; + +typedef struct _GskContour GskContour; + +GskContour * gsk_rect_contour_new (const graphene_rect_t *rect); +GskContour * gsk_rounded_rect_contour_new (const GskRoundedRect *rounded_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 graphene_point_t *points, + gsize n_points, + const gskpathop *ops, + gsize n_ops, + gssize offset); + +void gsk_contour_copy (GskContour * dest, + const GskContour *src); +GskContour * gsk_contour_dup (const GskContour *src); +GskContour * gsk_contour_reverse (const GskContour *src); + +gsize gsk_contour_get_size (const GskContour *self); +GskPathFlags gsk_contour_get_flags (const GskContour *self); +void gsk_contour_print (const GskContour *self, + GString *string); +gboolean gsk_contour_get_bounds (const GskContour *self, + graphene_rect_t *bounds); +gboolean gsk_contour_foreach (const GskContour *self, + float tolerance, + GskPathForeachFunc func, + gpointer user_data); +void gsk_contour_get_start_end (const GskContour *self, + graphene_point_t *start, + graphene_point_t *end); +int gsk_contour_get_winding (const GskContour *self, + const graphene_point_t *point); +gboolean gsk_contour_get_closest_point (const GskContour *self, + const graphene_point_t *point, + float threshold, + GskPathPoint *result, + float *out_dist); +void gsk_contour_get_position (const GskContour *self, + GskPathPoint *point, + graphene_point_t *pos); +void gsk_contour_get_tangent (const GskContour *self, + GskPathPoint *point, + GskPathDirection direction, + graphene_vec2_t *tangent); +float gsk_contour_get_curvature (const GskContour *self, + GskPathPoint *point, + graphene_point_t *center); + +G_END_DECLS diff --git a/gsk/gskcurve.c b/gsk/gskcurve.c new file mode 100644 index 0000000000..3015fb2506 --- /dev/null +++ b/gsk/gskcurve.c @@ -0,0 +1,2206 @@ +/* + * 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 "gskcurveprivate.h" +#include "gskboundingboxprivate.h" + +/* GskCurve collects all the functionality we need for Bézier segments */ + +#define MIN_PROGRESS (1/1024.f) + +typedef struct _GskCurveClass GskCurveClass; + +struct _GskCurveClass +{ + void (* init) (GskCurve *curve, + gskpathop op); + void (* init_foreach) (GskCurve *curve, + GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight); + void (* print) (const GskCurve *curve, + GString *string); + gskpathop (* pathop) (const GskCurve *curve); + const graphene_point_t * (* get_start_point) (const GskCurve *curve); + const graphene_point_t * (* get_end_point) (const GskCurve *curve); + void (* get_start_tangent) (const GskCurve *curve, + graphene_vec2_t *tangent); + void (* get_end_tangent) (const GskCurve *curve, + graphene_vec2_t *tangent); + void (* get_point) (const GskCurve *curve, + float t, + graphene_point_t *pos); + void (* get_tangent) (const GskCurve *curve, + float t, + graphene_vec2_t *tangent); + void (* reverse) (const GskCurve *curve, + GskCurve *reverse); + float (* get_curvature) (const GskCurve *curve, + float t); + void (* split) (const GskCurve *curve, + float progress, + GskCurve *result1, + GskCurve *result2); + void (* segment) (const GskCurve *curve, + float start, + float end, + GskCurve *segment); + gboolean (* decompose) (const GskCurve *curve, + float tolerance, + GskCurveAddLineFunc add_line_func, + gpointer user_data); + gboolean (* decompose_curve) (const GskCurve *curve, + GskPathForeachFlags flags, + float tolerance, + GskCurveAddCurveFunc add_curve_func, + gpointer user_data); + void (* get_bounds) (const GskCurve *curve, + GskBoundingBox *bounds); + void (* get_tight_bounds) (const GskCurve *curve, + GskBoundingBox *bounds); +}; + +/* {{{ Utilities */ + +static void +get_tangent (const graphene_point_t *p0, + const graphene_point_t *p1, + graphene_vec2_t *t) +{ + graphene_vec2_init (t, p1->x - p0->x, p1->y - p0->y); + graphene_vec2_normalize (t, t); +} + +/* Replace a line by an equivalent quad, + * and a quad by an equivalent cubic. + */ +static void +gsk_curve_elevate (const GskCurve *curve, + GskCurve *elevated) +{ + if (curve->op == GSK_PATH_LINE) + { + graphene_point_t p[3]; + + p[0] = curve->line.points[0]; + graphene_point_interpolate (&curve->line.points[0], + &curve->line.points[1], + 0.5, + &p[1]); + p[2] = curve->line.points[1]; + gsk_curve_init (elevated, gsk_pathop_encode (GSK_PATH_QUAD, p)); + } + else if (curve->op == GSK_PATH_QUAD) + { + graphene_point_t p[4]; + + p[0] = curve->quad.points[0]; + graphene_point_interpolate (&curve->quad.points[0], + &curve->quad.points[1], + 2/3., + &p[1]); + graphene_point_interpolate (&curve->quad.points[2], + &curve->quad.points[1], + 2/3., + &p[2]); + p[3] = curve->quad.points[2]; + gsk_curve_init (elevated, gsk_pathop_encode (GSK_PATH_CUBIC, p)); + } + else + g_assert_not_reached (); +} + +/* }}} */ +/* {{{ Line */ + +static void +gsk_line_curve_init_from_points (GskLineCurve *self, + GskPathOperation op, + const graphene_point_t *start, + const graphene_point_t *end) +{ + self->op = op; + self->points[0] = *start; + self->points[1] = *end; +} + +static void +gsk_line_curve_init (GskCurve *curve, + gskpathop op) +{ + GskLineCurve *self = &curve->line; + const graphene_point_t *pts = gsk_pathop_points (op); + + gsk_line_curve_init_from_points (self, gsk_pathop_op (op), &pts[0], &pts[1]); +} + +static void +gsk_line_curve_init_foreach (GskCurve *curve, + GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight) +{ + GskLineCurve *self = &curve->line; + + g_assert (n_pts == 2); + + gsk_line_curve_init_from_points (self, op, &pts[0], &pts[1]); +} + +static void +gsk_line_curve_print (const GskCurve *curve, + GString *string) +{ + g_string_append_printf (string, + "M %g %g L %g %g", + curve->line.points[0].x, curve->line.points[0].y, + curve->line.points[1].x, curve->line.points[1].y); +} + +static gskpathop +gsk_line_curve_pathop (const GskCurve *curve) +{ + const GskLineCurve *self = &curve->line; + + return gsk_pathop_encode (self->op, self->points); +} + +static const graphene_point_t * +gsk_line_curve_get_start_point (const GskCurve *curve) +{ + const GskLineCurve *self = &curve->line; + + return &self->points[0]; +} + +static const graphene_point_t * +gsk_line_curve_get_end_point (const GskCurve *curve) +{ + const GskLineCurve *self = &curve->line; + + return &self->points[1]; +} + +static void +gsk_line_curve_get_start_end_tangent (const GskCurve *curve, + graphene_vec2_t *tangent) +{ + const GskLineCurve *self = &curve->line; + + get_tangent (&self->points[0], &self->points[1], tangent); +} + +static void +gsk_line_curve_get_point (const GskCurve *curve, + float t, + graphene_point_t *pos) +{ + const GskLineCurve *self = &curve->line; + + graphene_point_interpolate (&self->points[0], &self->points[1], t, pos); +} + +static void +gsk_line_curve_get_tangent (const GskCurve *curve, + float t, + graphene_vec2_t *tangent) +{ + const GskLineCurve *self = &curve->line; + + get_tangent (&self->points[0], &self->points[1], tangent); +} + +static float +gsk_line_curve_get_curvature (const GskCurve *curve, + float t) +{ + return 0; +} + +static void +gsk_line_curve_reverse (const GskCurve *curve, + GskCurve *reverse) +{ + const GskLineCurve *self = &curve->line; + + reverse->op = GSK_PATH_LINE; + reverse->line.points[0] = self->points[1]; + reverse->line.points[1] = self->points[0]; +} + +static void +gsk_line_curve_split (const GskCurve *curve, + float progress, + GskCurve *start, + GskCurve *end) +{ + const GskLineCurve *self = &curve->line; + graphene_point_t point; + + graphene_point_interpolate (&self->points[0], &self->points[1], progress, &point); + + if (start) + gsk_line_curve_init_from_points (&start->line, GSK_PATH_LINE, &self->points[0], &point); + if (end) + gsk_line_curve_init_from_points (&end->line, GSK_PATH_LINE, &point, &self->points[1]); +} + +static void +gsk_line_curve_segment (const GskCurve *curve, + float start, + float end, + GskCurve *segment) +{ + const GskLineCurve *self = &curve->line; + graphene_point_t start_point, end_point; + + graphene_point_interpolate (&self->points[0], &self->points[1], start, &start_point); + graphene_point_interpolate (&self->points[0], &self->points[1], end, &end_point); + + gsk_line_curve_init_from_points (&segment->line, GSK_PATH_LINE, &start_point, &end_point); +} + +static gboolean +gsk_line_curve_decompose (const GskCurve *curve, + float tolerance, + GskCurveAddLineFunc add_line_func, + gpointer user_data) +{ + const GskLineCurve *self = &curve->line; + + return add_line_func (&self->points[0], &self->points[1], 0.0f, 1.0f, GSK_CURVE_LINE_REASON_STRAIGHT, user_data); +} + +static gboolean +gsk_line_curve_decompose_curve (const GskCurve *curve, + GskPathForeachFlags flags, + float tolerance, + GskCurveAddCurveFunc add_curve_func, + gpointer user_data) +{ + const GskLineCurve *self = &curve->line; + + return add_curve_func (GSK_PATH_LINE, self->points, 2, 0.f, user_data); +} + +static void +gsk_line_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskLineCurve *self = &curve->line; + const graphene_point_t *pts = self->points; + + gsk_bounding_box_init (bounds, &pts[0], &pts[1]); +} + +static const GskCurveClass GSK_LINE_CURVE_CLASS = { + gsk_line_curve_init, + gsk_line_curve_init_foreach, + gsk_line_curve_print, + gsk_line_curve_pathop, + gsk_line_curve_get_start_point, + gsk_line_curve_get_end_point, + gsk_line_curve_get_start_end_tangent, + gsk_line_curve_get_start_end_tangent, + gsk_line_curve_get_point, + gsk_line_curve_get_tangent, + gsk_line_curve_reverse, + gsk_line_curve_get_curvature, + gsk_line_curve_split, + gsk_line_curve_segment, + gsk_line_curve_decompose, + gsk_line_curve_decompose_curve, + gsk_line_curve_get_bounds, + gsk_line_curve_get_bounds, +}; + +/* }}} */ +/* {{{ Quadratic */ + +static void +gsk_quad_curve_ensure_coefficients (const GskQuadCurve *curve) +{ + GskQuadCurve *self = (GskQuadCurve *) curve; + const graphene_point_t *pts = self->points; + + if (self->has_coefficients) + return; + + self->coeffs[2] = pts[0]; + self->coeffs[1] = GRAPHENE_POINT_INIT (2 * (pts[1].x - pts[0].x), + 2 * (pts[1].y - pts[0].y)); + self->coeffs[0] = GRAPHENE_POINT_INIT (pts[2].x - 2 * pts[1].x + pts[0].x, + pts[2].y - 2 * pts[1].y + pts[0].y); + + self->has_coefficients = TRUE; +} + +static void +gsk_quad_curve_init_from_points (GskQuadCurve *self, + const graphene_point_t pts[3]) +{ + self->op = GSK_PATH_QUAD; + self->has_coefficients = FALSE; + memcpy (self->points, pts, sizeof (graphene_point_t) * 3); +} + +static void +gsk_quad_curve_init (GskCurve *curve, + gskpathop op) +{ + GskQuadCurve *self = &curve->quad; + + gsk_quad_curve_init_from_points (self, gsk_pathop_points (op)); +} + +static void +gsk_quad_curve_init_foreach (GskCurve *curve, + GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight) +{ + GskQuadCurve *self = &curve->quad; + + g_assert (n_pts == 3); + + gsk_quad_curve_init_from_points (self, pts); +} + +static void +gsk_quad_curve_print (const GskCurve *curve, + GString *string) +{ + g_string_append_printf (string, + "M %g %g Q %g %g %g %g", + curve->quad.points[0].x, curve->quad.points[0].y, + curve->quad.points[1].x, curve->cubic.points[1].y, + curve->quad.points[2].x, curve->cubic.points[2].y); +} + +static gskpathop +gsk_quad_curve_pathop (const GskCurve *curve) +{ + const GskQuadCurve *self = &curve->quad; + + return gsk_pathop_encode (self->op, self->points); +} + +static const graphene_point_t * +gsk_quad_curve_get_start_point (const GskCurve *curve) +{ + const GskQuadCurve *self = &curve->quad; + + return &self->points[0]; +} + +static const graphene_point_t * +gsk_quad_curve_get_end_point (const GskCurve *curve) +{ + const GskQuadCurve *self = &curve->quad; + + return &self->points[2]; +} + +static void +gsk_quad_curve_get_start_tangent (const GskCurve *curve, + graphene_vec2_t *tangent) +{ + const GskQuadCurve *self = &curve->quad; + + get_tangent (&self->points[0], &self->points[1], tangent); +} + +static void +gsk_quad_curve_get_end_tangent (const GskCurve *curve, + graphene_vec2_t *tangent) +{ + const GskQuadCurve *self = &curve->quad; + + get_tangent (&self->points[1], &self->points[2], tangent); +} + +static void +gsk_quad_curve_get_point (const GskCurve *curve, + float t, + graphene_point_t *pos) +{ + GskQuadCurve *self = (GskQuadCurve *) &curve->quad; + const graphene_point_t *c = self->coeffs; + + gsk_quad_curve_ensure_coefficients (self); + + *pos = GRAPHENE_POINT_INIT ((c[0].x * t + c[1].x) * t + c[2].x, + (c[0].y * t + c[1].y) * t + c[2].y); +} + +static void +gsk_quad_curve_get_tangent (const GskCurve *curve, + float t, + graphene_vec2_t *tangent) +{ + GskQuadCurve *self = (GskQuadCurve *) &curve->quad; + const graphene_point_t *c = self->coeffs; + + gsk_quad_curve_ensure_coefficients (self); + + graphene_vec2_init (tangent, + 2.0f * c[0].x * t + c[1].x, + 2.0f * c[0].y * t + c[1].y); + graphene_vec2_normalize (tangent, tangent); +} + + +static float gsk_cubic_curve_get_curvature (const GskCurve *curve, + float t); + +static float +gsk_quad_curve_get_curvature (const GskCurve *curve, + float t) +{ + return gsk_cubic_curve_get_curvature (curve, t); +} + +static void +gsk_quad_curve_reverse (const GskCurve *curve, + GskCurve *reverse) +{ + const GskCubicCurve *self = &curve->cubic; + + reverse->op = GSK_PATH_QUAD; + reverse->cubic.points[0] = self->points[2]; + reverse->cubic.points[1] = self->points[1]; + reverse->cubic.points[2] = self->points[0]; + reverse->cubic.has_coefficients = FALSE; +} + +static void +gsk_quad_curve_split (const GskCurve *curve, + float progress, + GskCurve *start, + GskCurve *end) +{ + GskQuadCurve *self = (GskQuadCurve *) &curve->quad; + const graphene_point_t *pts = self->points; + graphene_point_t ab, bc; + graphene_point_t final; + + graphene_point_interpolate (&pts[0], &pts[1], progress, &ab); + graphene_point_interpolate (&pts[1], &pts[2], progress, &bc); + graphene_point_interpolate (&ab, &bc, progress, &final); + + if (start) + gsk_quad_curve_init_from_points (&start->quad, (graphene_point_t[3]) { pts[0], ab, final }); + if (end) + gsk_quad_curve_init_from_points (&end->quad, (graphene_point_t[3]) { final, bc, pts[2] }); +} + +static void +gsk_quad_curve_segment (const GskCurve *curve, + float start, + float end, + GskCurve *segment) +{ + GskCurve tmp; + + gsk_quad_curve_split (curve, start, NULL, &tmp); + gsk_quad_curve_split (&tmp, (end - start) / (1.0f - start), segment, NULL); +} + +/* taken from Skia, including the very descriptive name */ +static gboolean +gsk_quad_curve_too_curvy (const GskQuadCurve *self, + float tolerance) +{ + const graphene_point_t *pts = self->points; + float dx, dy; + + dx = fabs (pts[1].x / 2 - (pts[0].x + pts[2].x) / 4); + dy = fabs (pts[1].y / 2 - (pts[0].y + pts[2].y) / 4); + + return MAX (dx, dy) > tolerance; +} + +static gboolean +gsk_quad_curve_decompose_step (const GskCurve *curve, + float start_progress, + float end_progress, + float tolerance, + GskCurveAddLineFunc add_line_func, + gpointer user_data) +{ + const GskQuadCurve *self = &curve->quad; + GskCurve left, right; + float mid_progress; + + if (!gsk_quad_curve_too_curvy (self, tolerance)) + return add_line_func (&self->points[0], &self->points[2], start_progress, end_progress, GSK_CURVE_LINE_REASON_STRAIGHT, user_data); + if (end_progress - start_progress <= MIN_PROGRESS) + return add_line_func (&self->points[0], &self->points[2], start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data); + + gsk_quad_curve_split ((const GskCurve *) self, 0.5, &left, &right); + mid_progress = (start_progress + end_progress) / 2; + + return gsk_quad_curve_decompose_step (&left, start_progress, mid_progress, tolerance, add_line_func, user_data) + && gsk_quad_curve_decompose_step (&right, mid_progress, end_progress, tolerance, add_line_func, user_data); +} + +static gboolean +gsk_quad_curve_decompose (const GskCurve *curve, + float tolerance, + GskCurveAddLineFunc add_line_func, + gpointer user_data) +{ + return gsk_quad_curve_decompose_step (curve, 0.0, 1.0, tolerance, add_line_func, user_data); +} + +typedef struct +{ + GskCurveAddCurveFunc add_curve; + gpointer user_data; +} AddLineData; + +static gboolean +gsk_curve_add_line_cb (const graphene_point_t *from, + const graphene_point_t *to, + float from_progress, + float to_progress, + GskCurveLineReason reason, + gpointer user_data) +{ + AddLineData *data = user_data; + graphene_point_t p[2] = { *from, *to }; + + return data->add_curve (GSK_PATH_LINE, p, 2, 0.f, data->user_data); +} + +static gboolean +gsk_quad_curve_decompose_curve (const GskCurve *curve, + GskPathForeachFlags flags, + float tolerance, + GskCurveAddCurveFunc add_curve_func, + gpointer user_data) +{ + const GskQuadCurve *self = &curve->quad; + + if (flags & GSK_PATH_FOREACH_ALLOW_QUAD) + return add_curve_func (GSK_PATH_QUAD, self->points, 3, 0.f, user_data); + else if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC) + { + GskCurve c; + + gsk_curve_elevate (curve, &c); + return add_curve_func (GSK_PATH_CUBIC, c.cubic.points, 4, 0.f, user_data); + } + else if (flags & GSK_PATH_FOREACH_ALLOW_CONIC) + { + GskCurve c; + + gsk_curve_init_foreach (&c, GSK_PATH_CONIC, self->points, 3, 1.f); + return add_curve_func (GSK_PATH_CUBIC, c.cubic.points, 4, 0.f, user_data); + } + else + { + return gsk_quad_curve_decompose (curve, + tolerance, + gsk_curve_add_line_cb, + &(AddLineData) { add_curve_func, user_data }); + } +} + +static void +gsk_quad_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskQuadCurve *self = &curve->quad; + const graphene_point_t *pts = self->points; + + gsk_bounding_box_init (bounds, &pts[0], &pts[2]); + gsk_bounding_box_expand (bounds, &pts[1]); +} + +/* Solve P' = 0 where P is + * P = (1-t)^2*pa + 2*t*(1-t)*pb + t^2*pc + */ +static int +get_quadratic_extrema (float pa, float pb, float pc, float t[1]) +{ + float d = pa - 2 * pb + pc; + + if (fabs (d) > 0.0001) + { + t[0] = (pa - pb) / d; + return 1; + } + + return 0; +} + +static void +gsk_quad_curve_get_tight_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskQuadCurve *self = &curve->quad; + const graphene_point_t *pts = self->points; + float t[4]; + int n; + + gsk_bounding_box_init (bounds, &pts[0], &pts[2]); + + n = 0; + n += get_quadratic_extrema (pts[0].x, pts[1].x, pts[2].x, &t[n]); + n += get_quadratic_extrema (pts[0].y, pts[1].y, pts[2].y, &t[n]); + + for (int i = 0; i < n; i++) + { + graphene_point_t p; + + gsk_quad_curve_get_point (curve, t[i], &p); + gsk_bounding_box_expand (bounds, &p); + } +} + +static const GskCurveClass GSK_QUAD_CURVE_CLASS = { + gsk_quad_curve_init, + gsk_quad_curve_init_foreach, + gsk_quad_curve_print, + gsk_quad_curve_pathop, + gsk_quad_curve_get_start_point, + gsk_quad_curve_get_end_point, + gsk_quad_curve_get_start_tangent, + gsk_quad_curve_get_end_tangent, + gsk_quad_curve_get_point, + gsk_quad_curve_get_tangent, + gsk_quad_curve_reverse, + gsk_quad_curve_get_curvature, + gsk_quad_curve_split, + gsk_quad_curve_segment, + gsk_quad_curve_decompose, + gsk_quad_curve_decompose_curve, + gsk_quad_curve_get_bounds, + gsk_quad_curve_get_tight_bounds, +}; + +/* }}} */ +/* {{{ Cubic */ + +static void +gsk_cubic_curve_ensure_coefficients (const GskCubicCurve *curve) +{ + GskCubicCurve *self = (GskCubicCurve *) curve; + const graphene_point_t *pts = &self->points[0]; + + if (self->has_coefficients) + return; + + self->coeffs[0] = GRAPHENE_POINT_INIT (pts[3].x - 3.0f * pts[2].x + 3.0f * pts[1].x - pts[0].x, + pts[3].y - 3.0f * pts[2].y + 3.0f * pts[1].y - pts[0].y); + self->coeffs[1] = GRAPHENE_POINT_INIT (3.0f * pts[2].x - 6.0f * pts[1].x + 3.0f * pts[0].x, + 3.0f * pts[2].y - 6.0f * pts[1].y + 3.0f * pts[0].y); + self->coeffs[2] = GRAPHENE_POINT_INIT (3.0f * pts[1].x - 3.0f * pts[0].x, + 3.0f * pts[1].y - 3.0f * pts[0].y); + self->coeffs[3] = pts[0]; + + self->has_coefficients = TRUE; +} + +static void +gsk_cubic_curve_init_from_points (GskCubicCurve *self, + const graphene_point_t pts[4]) +{ + self->op = GSK_PATH_CUBIC; + self->has_coefficients = FALSE; + memcpy (self->points, pts, sizeof (graphene_point_t) * 4); +} + +static void +gsk_cubic_curve_init (GskCurve *curve, + gskpathop op) +{ + GskCubicCurve *self = &curve->cubic; + + gsk_cubic_curve_init_from_points (self, gsk_pathop_points (op)); +} + +static void +gsk_cubic_curve_init_foreach (GskCurve *curve, + GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight) +{ + GskCubicCurve *self = &curve->cubic; + + g_assert (n_pts == 4); + + gsk_cubic_curve_init_from_points (self, pts); +} + +static void +gsk_cubic_curve_print (const GskCurve *curve, + GString *string) +{ + g_string_append_printf (string, + "M %f %f C %f %f %f %f %f %f", + curve->cubic.points[0].x, curve->cubic.points[0].y, + curve->cubic.points[1].x, curve->cubic.points[1].y, + curve->cubic.points[2].x, curve->cubic.points[2].y, + curve->cubic.points[3].x, curve->cubic.points[3].y); +} + +static gskpathop +gsk_cubic_curve_pathop (const GskCurve *curve) +{ + const GskCubicCurve *self = &curve->cubic; + + return gsk_pathop_encode (self->op, self->points); +} + +static const graphene_point_t * +gsk_cubic_curve_get_start_point (const GskCurve *curve) +{ + const GskCubicCurve *self = &curve->cubic; + + return &self->points[0]; +} + +static const graphene_point_t * +gsk_cubic_curve_get_end_point (const GskCurve *curve) +{ + const GskCubicCurve *self = &curve->cubic; + + return &self->points[3]; +} + +static void +gsk_cubic_curve_get_start_tangent (const GskCurve *curve, + graphene_vec2_t *tangent) +{ + const GskCubicCurve *self = &curve->cubic; + + if (graphene_point_near (&self->points[0], &self->points[1], 0.0001)) + { + if (graphene_point_near (&self->points[0], &self->points[2], 0.0001)) + get_tangent (&self->points[0], &self->points[3], tangent); + else + get_tangent (&self->points[0], &self->points[2], tangent); + } + else + get_tangent (&self->points[0], &self->points[1], tangent); +} + +static void +gsk_cubic_curve_get_end_tangent (const GskCurve *curve, + graphene_vec2_t *tangent) +{ + const GskCubicCurve *self = &curve->cubic; + + if (graphene_point_near (&self->points[2], &self->points[3], 0.0001)) + { + if (graphene_point_near (&self->points[1], &self->points[3], 0.0001)) + get_tangent (&self->points[0], &self->points[3], tangent); + else + get_tangent (&self->points[1], &self->points[3], tangent); + } + else + get_tangent (&self->points[2], &self->points[3], tangent); +} + +static void +gsk_cubic_curve_get_point (const GskCurve *curve, + float t, + graphene_point_t *pos) +{ + const GskCubicCurve *self = &curve->cubic; + const graphene_point_t *c = self->coeffs; + + gsk_cubic_curve_ensure_coefficients (self); + + *pos = GRAPHENE_POINT_INIT (((c[0].x * t + c[1].x) * t +c[2].x) * t + c[3].x, + ((c[0].y * t + c[1].y) * t +c[2].y) * t + c[3].y); +} + +static void +gsk_cubic_curve_get_tangent (const GskCurve *curve, + float t, + graphene_vec2_t *tangent) +{ + const GskCubicCurve *self = &curve->cubic; + const graphene_point_t *c = self->coeffs; + + gsk_cubic_curve_ensure_coefficients (self); + + graphene_vec2_init (tangent, + (3.0f * c[0].x * t + 2.0f * c[1].x) * t + c[2].x, + (3.0f * c[0].y * t + 2.0f * c[1].y) * t + c[2].y); + graphene_vec2_normalize (tangent, tangent); +} + +static void +gsk_cubic_curve_reverse (const GskCurve *curve, + GskCurve *reverse) +{ + const GskCubicCurve *self = &curve->cubic; + + reverse->op = GSK_PATH_CUBIC; + reverse->cubic.points[0] = self->points[3]; + reverse->cubic.points[1] = self->points[2]; + reverse->cubic.points[2] = self->points[1]; + reverse->cubic.points[3] = self->points[0]; + reverse->cubic.has_coefficients = FALSE; +} + +static void +gsk_curve_get_derivative (const GskCurve *curve, + GskCurve *deriv) +{ + switch (curve->op) + { + case GSK_PATH_LINE: + { + const GskLineCurve *self = &curve->line; + graphene_point_t p; + + p.x = self->points[1].x - self->points[0].x; + p.y = self->points[1].y - self->points[0].y; + + gsk_line_curve_init_from_points (&deriv->line, GSK_PATH_LINE, &p, &p); + } + break; + + case GSK_PATH_QUAD: + { + const GskQuadCurve *self = &curve->quad; + graphene_point_t p[2]; + + p[0].x = 2.f * (self->points[1].x - self->points[0].x); + p[0].y = 2.f * (self->points[1].y - self->points[0].y); + p[1].x = 2.f * (self->points[2].x - self->points[1].x); + p[1].y = 2.f * (self->points[2].y - self->points[1].y); + + gsk_line_curve_init_from_points (&deriv->line, GSK_PATH_LINE, &p[0], &p[1]); + } + break; + + case GSK_PATH_CUBIC: + { + const GskCubicCurve *self = &curve->cubic; + graphene_point_t p[3]; + + p[0].x = 3.f * (self->points[1].x - self->points[0].x); + p[0].y = 3.f * (self->points[1].y - self->points[0].y); + p[1].x = 3.f * (self->points[2].x - self->points[1].x); + p[1].y = 3.f * (self->points[2].y - self->points[1].y); + p[2].x = 3.f * (self->points[3].x - self->points[2].x); + p[2].y = 3.f * (self->points[3].y - self->points[2].y); + + gsk_quad_curve_init_from_points (&deriv->quad, p); + } + break; + + case GSK_PATH_MOVE: + case GSK_PATH_CLOSE: + case GSK_PATH_CONIC: + default: + g_assert_not_reached (); + } +} + +static inline float +cross (const graphene_vec2_t *v1, + const graphene_vec2_t *v2) +{ + return graphene_vec2_get_x (v1) * graphene_vec2_get_y (v2) + - graphene_vec2_get_y (v1) * graphene_vec2_get_x (v2); +} + +static inline float +pow3 (float w) +{ + return w * w * w; +} + +static float +gsk_cubic_curve_get_curvature (const GskCurve *curve, + float t) +{ + GskCurve c1, c2; + graphene_point_t p, pp; + graphene_vec2_t d, dd; + float num, denom; + + gsk_curve_get_derivative (curve, &c1); + gsk_curve_get_derivative (&c1, &c2); + + gsk_curve_get_point (&c1, t, &p); + gsk_curve_get_point (&c2, t, &pp); + + graphene_vec2_init (&d, p.x, p.y); + graphene_vec2_init (&dd, pp.x, pp.y); + + num = cross (&d, &dd); + if (num == 0) + return 0; + + denom = pow3 (graphene_vec2_length (&d)); + if (denom == 0) + return 0; + + return num / denom; +} + +static void +gsk_cubic_curve_split (const GskCurve *curve, + float progress, + GskCurve *start, + GskCurve *end) +{ + const GskCubicCurve *self = &curve->cubic; + const graphene_point_t *pts = self->points; + graphene_point_t ab, bc, cd; + graphene_point_t abbc, bccd; + graphene_point_t final; + + graphene_point_interpolate (&pts[0], &pts[1], progress, &ab); + graphene_point_interpolate (&pts[1], &pts[2], progress, &bc); + graphene_point_interpolate (&pts[2], &pts[3], progress, &cd); + graphene_point_interpolate (&ab, &bc, progress, &abbc); + graphene_point_interpolate (&bc, &cd, progress, &bccd); + graphene_point_interpolate (&abbc, &bccd, progress, &final); + + if (start) + gsk_cubic_curve_init_from_points (&start->cubic, (graphene_point_t[4]) { pts[0], ab, abbc, final }); + if (end) + gsk_cubic_curve_init_from_points (&end->cubic, (graphene_point_t[4]) { final, bccd, cd, pts[3] }); +} + +static void +gsk_cubic_curve_segment (const GskCurve *curve, + float start, + float end, + GskCurve *segment) +{ + GskCurve tmp; + + gsk_cubic_curve_split (curve, start, NULL, &tmp); + gsk_cubic_curve_split (&tmp, (end - start) / (1.0f - start), segment, NULL); +} + +/* taken from Skia, including the very descriptive name */ +static gboolean +gsk_cubic_curve_too_curvy (const GskCubicCurve *self, + float tolerance) +{ + const graphene_point_t *pts = self->points; + graphene_point_t p; + + graphene_point_interpolate (&pts[0], &pts[3], 1.0f / 3, &p); + if (MAX (ABS (p.x - pts[1].x), ABS (p.y - pts[1].y)) > tolerance) + return TRUE; + + graphene_point_interpolate (&pts[0], &pts[3], 2.0f / 3, &p); + if (MAX (ABS (p.x - pts[2].x), ABS (p.y - pts[2].y)) > tolerance) + return TRUE; + + return FALSE; +} + +static gboolean +gsk_cubic_curve_decompose_step (const GskCurve *curve, + float start_progress, + float end_progress, + float tolerance, + GskCurveAddLineFunc add_line_func, + gpointer user_data) +{ + const GskCubicCurve *self = &curve->cubic; + GskCurve left, right; + float mid_progress; + + if (!gsk_cubic_curve_too_curvy (self, tolerance)) + return add_line_func (&self->points[0], &self->points[3], start_progress, end_progress, GSK_CURVE_LINE_REASON_STRAIGHT, user_data); + if (end_progress - start_progress <= MIN_PROGRESS) + return add_line_func (&self->points[0], &self->points[3], start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data); + + gsk_cubic_curve_split ((const GskCurve *) self, 0.5, &left, &right); + mid_progress = (start_progress + end_progress) / 2; + + return gsk_cubic_curve_decompose_step (&left, start_progress, mid_progress, tolerance, add_line_func, user_data) + && gsk_cubic_curve_decompose_step (&right, mid_progress, end_progress, tolerance, add_line_func, user_data); +} + +static gboolean +gsk_cubic_curve_decompose (const GskCurve *curve, + float tolerance, + GskCurveAddLineFunc add_line_func, + gpointer user_data) +{ + return gsk_cubic_curve_decompose_step (curve, 0.0, 1.0, tolerance, add_line_func, user_data); +} + +static gboolean +gsk_cubic_curve_decompose_curve (const GskCurve *curve, + GskPathForeachFlags flags, + float tolerance, + GskCurveAddCurveFunc add_curve_func, + gpointer user_data) +{ + const GskCubicCurve *self = &curve->cubic; + + if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC) + return add_curve_func (GSK_PATH_CUBIC, self->points, 4, 0.f, user_data); + + /* FIXME: Quadratic (or conic?) approximation */ + return gsk_cubic_curve_decompose (curve, + tolerance, + gsk_curve_add_line_cb, + &(AddLineData) { add_curve_func, user_data }); +} + +static void +gsk_cubic_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskCubicCurve *self = &curve->cubic; + const graphene_point_t *pts = self->points; + + gsk_bounding_box_init (bounds, &pts[0], &pts[3]); + gsk_bounding_box_expand (bounds, &pts[1]); + gsk_bounding_box_expand (bounds, &pts[2]); +} + +static inline gboolean +acceptable (float t) +{ + return 0 <= t && t <= 1; +} + +/* Solve P' = 0 where P is + * P = (1-t)^3*pa + 3*t*(1-t)^2*pb + 3*t^2*(1-t)*pc + t^3*pd + */ +static int +get_cubic_extrema (float pa, float pb, float pc, float pd, float t[2]) +{ + float a, b, c; + float d, tt; + int n = 0; + + a = 3 * (pd - 3*pc + 3*pb - pa); + b = 6 * (pc - 2*pb + pa); + c = 3 * (pb - pa); + + if (fabs (a) > 0.0001) + { + if (b*b > 4*a*c) + { + d = sqrt (b*b - 4*a*c); + tt = (-b + d)/(2*a); + if (acceptable (tt)) + t[n++] = tt; + tt = (-b - d)/(2*a); + if (acceptable (tt)) + t[n++] = tt; + } + else + { + tt = -b / (2*a); + if (acceptable (tt)) + t[n++] = tt; + } + } + else if (fabs (b) > 0.0001) + { + tt = -c / b; + if (acceptable (tt)) + t[n++] = tt; + } + + return n; +} + +static void +gsk_cubic_curve_get_tight_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskCubicCurve *self = &curve->cubic; + const graphene_point_t *pts = self->points; + float t[4]; + int n; + + gsk_bounding_box_init (bounds, &pts[0], &pts[3]); + + n = 0; + n += get_cubic_extrema (pts[0].x, pts[1].x, pts[2].x, pts[3].x, &t[n]); + n += get_cubic_extrema (pts[0].y, pts[1].y, pts[2].y, pts[3].y, &t[n]); + + for (int i = 0; i < n; i++) + { + graphene_point_t p; + + gsk_cubic_curve_get_point (curve, t[i], &p); + gsk_bounding_box_expand (bounds, &p); + } +} + +static const GskCurveClass GSK_CUBIC_CURVE_CLASS = { + gsk_cubic_curve_init, + gsk_cubic_curve_init_foreach, + gsk_cubic_curve_print, + gsk_cubic_curve_pathop, + gsk_cubic_curve_get_start_point, + gsk_cubic_curve_get_end_point, + gsk_cubic_curve_get_start_tangent, + gsk_cubic_curve_get_end_tangent, + gsk_cubic_curve_get_point, + gsk_cubic_curve_get_tangent, + gsk_cubic_curve_reverse, + gsk_cubic_curve_get_curvature, + gsk_cubic_curve_split, + gsk_cubic_curve_segment, + gsk_cubic_curve_decompose, + gsk_cubic_curve_decompose_curve, + gsk_cubic_curve_get_bounds, + gsk_cubic_curve_get_tight_bounds, +}; + +/* }}} */ +/* {{{ Conic */ + +static inline float +gsk_conic_curve_get_weight (const GskConicCurve *self) +{ + return self->points[2].x; +} + +static void +gsk_conic_curve_ensure_coefficents (const GskConicCurve *curve) +{ + GskConicCurve *self = (GskConicCurve *) curve; + float w = gsk_conic_curve_get_weight (self); + const graphene_point_t *pts = self->points; + graphene_point_t pw = GRAPHENE_POINT_INIT (w * pts[1].x, w * pts[1].y); + + if (self->has_coefficients) + return; + + self->num[2] = pts[0]; + self->num[1] = GRAPHENE_POINT_INIT (2 * (pw.x - pts[0].x), + 2 * (pw.y - pts[0].y)); + self->num[0] = GRAPHENE_POINT_INIT (pts[3].x - 2 * pw.x + pts[0].x, + pts[3].y - 2 * pw.y + pts[0].y); + + self->denom[2] = GRAPHENE_POINT_INIT (1, 1); + self->denom[1] = GRAPHENE_POINT_INIT (2 * (w - 1), 2 * (w - 1)); + self->denom[0] = GRAPHENE_POINT_INIT (-self->denom[1].x, -self->denom[1].y); + + self->has_coefficients = TRUE; +} + +static void +gsk_conic_curve_init_from_points (GskConicCurve *self, + const graphene_point_t pts[4]) +{ + self->op = GSK_PATH_CONIC; + self->has_coefficients = FALSE; + + memcpy (self->points, pts, sizeof (graphene_point_t) * 4); +} + +static void +gsk_conic_curve_init (GskCurve *curve, + gskpathop op) +{ + GskConicCurve *self = &curve->conic; + + gsk_conic_curve_init_from_points (self, gsk_pathop_points (op)); +} + +static void +gsk_conic_curve_init_foreach (GskCurve *curve, + GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight) +{ + GskConicCurve *self = &curve->conic; + + g_assert (n_pts == 3); + + gsk_conic_curve_init_from_points (self, + (graphene_point_t[4]) { + pts[0], + pts[1], + GRAPHENE_POINT_INIT (weight, 0), + pts[2] + }); +} + +static void +gsk_conic_curve_print (const GskCurve *curve, + GString *string) +{ + g_string_append_printf (string, + "M %g %g O %g %g %g %g %g", + curve->conic.points[0].x, curve->conic.points[0].y, + curve->conic.points[1].x, curve->conic.points[1].y, + curve->conic.points[3].x, curve->conic.points[3].y, + curve->conic.points[2].x); +} + +static gskpathop +gsk_conic_curve_pathop (const GskCurve *curve) +{ + const GskConicCurve *self = &curve->conic; + + return gsk_pathop_encode (self->op, self->points); +} + +static const graphene_point_t * +gsk_conic_curve_get_start_point (const GskCurve *curve) +{ + const GskConicCurve *self = &curve->conic; + + return &self->points[0]; +} + +static const graphene_point_t * +gsk_conic_curve_get_end_point (const GskCurve *curve) +{ + const GskConicCurve *self = &curve->conic; + + return &self->points[3]; +} + +static void +gsk_conic_curve_get_start_tangent (const GskCurve *curve, + graphene_vec2_t *tangent) +{ + const GskConicCurve *self = &curve->conic; + + get_tangent (&self->points[0], &self->points[1], tangent); +} + +static void +gsk_conic_curve_get_end_tangent (const GskCurve *curve, + graphene_vec2_t *tangent) +{ + const GskConicCurve *self = &curve->conic; + + get_tangent (&self->points[1], &self->points[3], tangent); +} + +static inline void +gsk_curve_eval_quad (const graphene_point_t quad[3], + float progress, + graphene_point_t *result) +{ + *result = GRAPHENE_POINT_INIT ((quad[0].x * progress + quad[1].x) * progress + quad[2].x, + (quad[0].y * progress + quad[1].y) * progress + quad[2].y); +} + +static inline void +gsk_conic_curve_eval_point (const GskConicCurve *self, + float progress, + graphene_point_t *point) +{ + graphene_point_t num, denom; + + gsk_curve_eval_quad (self->num, progress, &num); + gsk_curve_eval_quad (self->denom, progress, &denom); + + *point = GRAPHENE_POINT_INIT (num.x / denom.x, num.y / denom.y); +} + +static void +gsk_conic_curve_get_point (const GskCurve *curve, + float t, + graphene_point_t *pos) +{ + const GskConicCurve *self = &curve->conic; + + gsk_conic_curve_ensure_coefficents (self); + + gsk_conic_curve_eval_point (self, t, pos); +} + +static void +gsk_conic_curve_get_tangent (const GskCurve *curve, + float t, + graphene_vec2_t *tangent) +{ + const GskConicCurve *self = &curve->conic; + graphene_point_t tmp; + float w = gsk_conic_curve_get_weight (self); + const graphene_point_t *pts = self->points; + + /* The tangent will be 0 in these corner cases, just + * treat it like a line here. */ + if ((t <= 0.f && graphene_point_equal (&pts[0], &pts[1])) || + (t >= 1.f && graphene_point_equal (&pts[1], &pts[3]))) + { + graphene_vec2_init (tangent, pts[3].x - pts[0].x, pts[3].y - pts[0].y); + return; + } + + gsk_curve_eval_quad ((graphene_point_t[3]) { + GRAPHENE_POINT_INIT ((w - 1) * (pts[3].x - pts[0].x), + (w - 1) * (pts[3].y - pts[0].y)), + GRAPHENE_POINT_INIT (pts[3].x - pts[0].x - 2 * w * (pts[1].x - pts[0].x), + pts[3].y - pts[0].y - 2 * w * (pts[1].y - pts[0].y)), + GRAPHENE_POINT_INIT (w * (pts[1].x - pts[0].x), + w * (pts[1].y - pts[0].y)) + }, + t, + &tmp); + graphene_vec2_init (tangent, tmp.x, tmp.y); + graphene_vec2_normalize (tangent, tangent); +} + +/* See M. Floater, Derivatives of rational Bezier curves */ +static float +gsk_conic_curve_get_curvature (const GskCurve *curve, + float t) +{ + graphene_point_t p[3], p1[2]; + float w, w1[2], w2; + graphene_vec2_t t1, t2, t3; + + w = curve->conic.points[2].x; + + p[0] = curve->conic.points[0]; + p[1] = curve->conic.points[1]; + p[2] = curve->conic.points[3]; + + w1[0] = (1 - t) + t*w; + w1[1] = (1 - t)*w + t; + + w2 = (1 - t)*w1[0] + t*w1[1]; + + p1[0].x = ((1 - t)*p[0].x + t*w*p[1].x)/w1[0]; + p1[0].y = ((1 - t)*p[0].y + t*w*p[1].y)/w1[0]; + p1[1].x = ((1 - t)*w*p[1].x + t*p[2].x)/w1[1]; + p1[1].y = ((1 - t)*w*p[1].y + t*p[2].y)/w1[1]; + + graphene_vec2_init (&t1, p[1].x - p[0].x, p[1].y - p[0].y); + graphene_vec2_init (&t2, p[2].x - p[1].x, p[2].y - p[1].y); + graphene_vec2_init (&t3, p1[1].x - p1[0].x, p1[1].y - p1[0].y); + + return 0.5 * ((w*pow3 (w2))/(pow3 (w1[0])*pow3 (w1[1]))) * (cross (&t1, &t2) / pow3 (graphene_vec2_length (&t3))); +} + +static void +gsk_conic_curve_reverse (const GskCurve *curve, + GskCurve *reverse) +{ + const GskConicCurve *self = &curve->conic; + + reverse->op = GSK_PATH_CONIC; + reverse->conic.points[0] = self->points[3]; + reverse->conic.points[1] = self->points[1]; + reverse->conic.points[2] = self->points[2]; + reverse->conic.points[3] = self->points[0]; + reverse->conic.has_coefficients = FALSE; +} + +static void +split_bezier3d_recurse (const graphene_point3d_t *p, + int l, + float t, + graphene_point3d_t *left, + graphene_point3d_t *right, + int *lpos, + int *rpos) +{ + if (l == 1) + { + left[*lpos] = p[0]; + right[*rpos] = p[0]; + } + else + { + graphene_point3d_t *np; + int i; + + np = g_alloca (sizeof (graphene_point3d_t) * (l - 1)); + for (i = 0; i < l - 1; i++) + { + if (i == 0) + { + left[*lpos] = p[i]; + (*lpos)++; + } + if (i + 1 == l - 1) + { + right[*rpos] = p[i + 1]; + (*rpos)--; + } + graphene_point3d_interpolate (&p[i], &p[i + 1], t, &np[i]); + } + split_bezier3d_recurse (np, l - 1, t, left, right, lpos, rpos); + } +} + +static void +split_bezier3d (const graphene_point3d_t *p, + int l, + float t, + graphene_point3d_t *left, + graphene_point3d_t *right) +{ + int lpos = 0; + int rpos = l - 1; + split_bezier3d_recurse (p, l, t, left, right, &lpos, &rpos); +} + +static void +gsk_conic_curve_split (const GskCurve *curve, + float progress, + GskCurve *start, + GskCurve *end) +{ + const GskConicCurve *self = &curve->conic; + graphene_point3d_t p[3]; + graphene_point3d_t l[3], r[3]; + graphene_point_t left[4], right[4]; + float w; + + /* do de Casteljau in homogeneous coordinates... */ + w = self->points[2].x; + p[0] = GRAPHENE_POINT3D_INIT (self->points[0].x, self->points[0].y, 1); + p[1] = GRAPHENE_POINT3D_INIT (self->points[1].x * w, self->points[1].y * w, w); + p[2] = GRAPHENE_POINT3D_INIT (self->points[3].x, self->points[3].y, 1); + + split_bezier3d (p, 3, progress, l, r); + + /* then project the control points down */ + left[0] = GRAPHENE_POINT_INIT (l[0].x / l[0].z, l[0].y / l[0].z); + left[1] = GRAPHENE_POINT_INIT (l[1].x / l[1].z, l[1].y / l[1].z); + left[3] = GRAPHENE_POINT_INIT (l[2].x / l[2].z, l[2].y / l[2].z); + + right[0] = GRAPHENE_POINT_INIT (r[0].x / r[0].z, r[0].y / r[0].z); + right[1] = GRAPHENE_POINT_INIT (r[1].x / r[1].z, r[1].y / r[1].z); + right[3] = GRAPHENE_POINT_INIT (r[2].x / r[2].z, r[2].y / r[2].z); + + /* normalize the outer weights to be 1 by using + * the fact that weights w_i and c*w_i are equivalent + * for any nonzero constant c + */ + for (int i = 0; i < 3; i++) + { + l[i].z /= l[0].z; + r[i].z /= r[2].z; + } + + /* normalize the inner weight to be 1 by using + * the fact that w_0*w_2/w_1^2 is a constant for + * all equivalent weights. + */ + left[2] = GRAPHENE_POINT_INIT (l[1].z / sqrt (l[2].z), 0); + right[2] = GRAPHENE_POINT_INIT (r[1].z / sqrt (r[0].z), 0); + + if (start) + gsk_curve_init (start, gsk_pathop_encode (GSK_PATH_CONIC, left)); + if (end) + gsk_curve_init (end, gsk_pathop_encode (GSK_PATH_CONIC, right)); +} + +static void +gsk_conic_curve_segment (const GskCurve *curve, + float start, + float end, + GskCurve *segment) +{ + const GskConicCurve *self = &curve->conic; + graphene_point_t start_num, start_denom; + graphene_point_t mid_num, mid_denom; + graphene_point_t end_num, end_denom; + graphene_point_t ctrl_num, ctrl_denom; + float mid; + + if (start <= 0.0f) + return gsk_conic_curve_split (curve, end, segment, NULL); + else if (end >= 1.0f) + return gsk_conic_curve_split (curve, start, NULL, segment); + + gsk_conic_curve_ensure_coefficents (self); + + gsk_curve_eval_quad (self->num, start, &start_num); + gsk_curve_eval_quad (self->denom, start, &start_denom); + mid = (start + end) / 2; + gsk_curve_eval_quad (self->num, mid, &mid_num); + gsk_curve_eval_quad (self->denom, mid, &mid_denom); + gsk_curve_eval_quad (self->num, end, &end_num); + gsk_curve_eval_quad (self->denom, end, &end_denom); + ctrl_num = GRAPHENE_POINT_INIT (2 * mid_num.x - (start_num.x + end_num.x) / 2, + 2 * mid_num.y - (start_num.y + end_num.y) / 2); + ctrl_denom = GRAPHENE_POINT_INIT (2 * mid_denom.x - (start_denom.x + end_denom.x) / 2, + 2 * mid_denom.y - (start_denom.y + end_denom.y) / 2); + + gsk_conic_curve_init_from_points (&segment->conic, + (graphene_point_t[4]) { + GRAPHENE_POINT_INIT (start_num.x / start_denom.x, + start_num.y / start_denom.y), + GRAPHENE_POINT_INIT (ctrl_num.x / ctrl_denom.x, + ctrl_num.y / ctrl_denom.y), + GRAPHENE_POINT_INIT (ctrl_denom.x / sqrtf (start_denom.x * end_denom.x), + 0), + GRAPHENE_POINT_INIT (end_num.x / end_denom.x, + end_num.y / end_denom.y) + }); + +} + +/* taken from Skia, including the very descriptive name */ +static gboolean +gsk_conic_curve_too_curvy (const graphene_point_t *start, + const graphene_point_t *mid, + const graphene_point_t *end, + float tolerance) +{ + return fabs ((start->x + end->x) * 0.5 - mid->x) > tolerance + || fabs ((start->y + end->y) * 0.5 - mid->y) > tolerance; +} + +static gboolean +gsk_conic_curve_decompose_subdivide (const GskConicCurve *self, + float tolerance, + const graphene_point_t *start, + float start_progress, + const graphene_point_t *end, + float end_progress, + GskCurveAddLineFunc add_line_func, + gpointer user_data) +{ + graphene_point_t mid; + float mid_progress; + + mid_progress = (start_progress + end_progress) / 2; + gsk_conic_curve_eval_point (self, mid_progress, &mid); + + if (!gsk_conic_curve_too_curvy (start, &mid, end, tolerance)) + return add_line_func (start, end, start_progress, end_progress, GSK_CURVE_LINE_REASON_STRAIGHT, user_data); + if (end_progress - start_progress <= MIN_PROGRESS) + return add_line_func (start, end, start_progress, end_progress, GSK_CURVE_LINE_REASON_SHORT, user_data); + + return gsk_conic_curve_decompose_subdivide (self, tolerance, + start, start_progress, &mid, mid_progress, + add_line_func, user_data) + && gsk_conic_curve_decompose_subdivide (self, tolerance, + &mid, mid_progress, end, end_progress, + add_line_func, user_data); +} + +static gboolean +gsk_conic_curve_decompose (const GskCurve *curve, + float tolerance, + GskCurveAddLineFunc add_line_func, + gpointer user_data) +{ + const GskConicCurve *self = &curve->conic; + graphene_point_t mid; + + gsk_conic_curve_ensure_coefficents (self); + + gsk_conic_curve_eval_point (self, 0.5, &mid); + + return gsk_conic_curve_decompose_subdivide (self, + tolerance, + &self->points[0], + 0.0f, + &mid, + 0.5f, + add_line_func, + user_data) + && gsk_conic_curve_decompose_subdivide (self, + tolerance, + &mid, + 0.5f, + &self->points[3], + 1.0f, + add_line_func, + user_data); +} + +/* See Floater, M: An analysis of cubic approximation schemes + * for conic sections + */ +static void +cubic_approximation (const GskCurve *curve, + GskCurve *cubic) +{ + const GskConicCurve *self = &curve->conic; + graphene_point_t p[4]; + float w = self->points[2].x; + float w2 = w*w; + float lambda; + + lambda = 2 * (6*w2 + 1 - sqrt (3*w2 + 1)) / (12*w2 + 3); + + p[0] = self->points[0]; + p[3] = self->points[3]; + graphene_point_interpolate (&self->points[0], &self->points[1], lambda, &p[1]); + graphene_point_interpolate (&self->points[3], &self->points[1], lambda, &p[2]); + + gsk_curve_init (cubic, gsk_pathop_encode (GSK_PATH_CUBIC, p)); +} + +static gboolean +gsk_conic_is_close_to_cubic (const GskCurve *conic, + const GskCurve *cubic, + float tolerance) +{ + float t[] = { 0.1, 0.5, 0.9 }; + graphene_point_t p0, p1; + + for (int i = 0; i < G_N_ELEMENTS (t); i++) + { + gsk_curve_get_point (conic, t[i], &p0); + gsk_curve_get_point (cubic, t[i], &p1); + if (graphene_point_distance (&p0, &p1, NULL, NULL) > tolerance) + return FALSE; + } + + return TRUE; +} + +static gboolean gsk_conic_curve_decompose_curve (const GskCurve *curve, + GskPathForeachFlags flags, + float tolerance, + GskCurveAddCurveFunc add_curve_func, + gpointer user_data); + +static gboolean +gsk_conic_curve_decompose_or_add (const GskCurve *curve, + const GskCurve *cubic, + float tolerance, + GskCurveAddCurveFunc add_curve_func, + gpointer user_data) +{ + if (gsk_conic_is_close_to_cubic (curve, cubic, tolerance)) + return add_curve_func (GSK_PATH_CUBIC, cubic->cubic.points, 4, 0.f, user_data); + else + { + GskCurve c1, c2; + GskCurve cc1, cc2; + + gsk_conic_curve_split (curve, 0.5, &c1, &c2); + + cubic_approximation (&c1, &cc1); + cubic_approximation (&c2, &cc2); + + return gsk_conic_curve_decompose_or_add (&c1, &cc1, tolerance, add_curve_func, user_data) && + gsk_conic_curve_decompose_or_add (&c2, &cc2, tolerance, add_curve_func, user_data); + } +} + +static gboolean +gsk_conic_curve_decompose_curve (const GskCurve *curve, + GskPathForeachFlags flags, + float tolerance, + GskCurveAddCurveFunc add_curve_func, + gpointer user_data) +{ + const GskConicCurve *self = &curve->conic; + GskCurve c; + + if (flags & GSK_PATH_FOREACH_ALLOW_CONIC) + return add_curve_func (GSK_PATH_CONIC, + (const graphene_point_t[3]) { self->points[0], + self->points[1], + self->points[3] }, + 3, + self->points[2].x, + user_data); + + if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC) + { + cubic_approximation (curve, &c); + return gsk_conic_curve_decompose_or_add (curve, &c, tolerance, add_curve_func, user_data); + } + + /* FIXME: Quadratic (or conic?) approximation */ + return gsk_conic_curve_decompose (curve, + tolerance, + gsk_curve_add_line_cb, + &(AddLineData) { add_curve_func, user_data }); +} + +static void +gsk_conic_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskConicCurve *self = &curve->conic; + const graphene_point_t *pts = self->points; + + gsk_bounding_box_init (bounds, &pts[0], &pts[3]); + gsk_bounding_box_expand (bounds, &pts[1]); +} + +/* Solve N = 0 where N is the numerator of (P/Q)', with + * P = (1-t)^2*a + 2*t*(1-t)*w*b + t^2*c + * Q = (1-t)^2 + 2*t*(1-t)*w + t^2 + */ +static int +get_conic_extrema (float a, float b, float c, float w, float t[4]) +{ + float q, tt; + int n = 0; + float w2 = w*w; + float wac = (w - 1)*(a - c); + + if (wac != 0) + { + q = - sqrt (a*a - 4*a*b*w2 + 4*a*c*w2 - 2*a*c + 4*b*b*w2 - 4*b*c*w2 + c*c); + + tt = (- q + 2*a*w - a - 2*b*w + c)/(2*wac); + + if (acceptable (tt)) + t[n++] = tt; + + tt = (q + 2*a*w - a - 2*b*w + c)/(2*wac); + + if (acceptable (tt)) + t[n++] = tt; + } + + if (w * (b - c) != 0 && a == c) + t[n++] = 0.5; + + if (w == 1 && a - 2*b + c != 0) + { + tt = (a - b) / (a - 2*b + c); + if (acceptable (tt)) + t[n++] = tt; + } + + return n; +} + +static void +gsk_conic_curve_get_tight_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskConicCurve *self = &curve->conic; + float w = gsk_conic_curve_get_weight (self); + const graphene_point_t *pts = self->points; + float t[8]; + int n; + + gsk_bounding_box_init (bounds, &pts[0], &pts[3]); + + n = 0; + n += get_conic_extrema (pts[0].x, pts[1].x, pts[3].x, w, &t[n]); + n += get_conic_extrema (pts[0].y, pts[1].y, pts[3].y, w, &t[n]); + + for (int i = 0; i < n; i++) + { + graphene_point_t p; + + gsk_conic_curve_get_point (curve, t[i], &p); + gsk_bounding_box_expand (bounds, &p); + } +} + +static const GskCurveClass GSK_CONIC_CURVE_CLASS = { + gsk_conic_curve_init, + gsk_conic_curve_init_foreach, + gsk_conic_curve_print, + gsk_conic_curve_pathop, + gsk_conic_curve_get_start_point, + gsk_conic_curve_get_end_point, + gsk_conic_curve_get_start_tangent, + gsk_conic_curve_get_end_tangent, + gsk_conic_curve_get_point, + gsk_conic_curve_get_tangent, + gsk_conic_curve_reverse, + gsk_conic_curve_get_curvature, + gsk_conic_curve_split, + gsk_conic_curve_segment, + gsk_conic_curve_decompose, + gsk_conic_curve_decompose_curve, + gsk_conic_curve_get_bounds, + gsk_conic_curve_get_tight_bounds, +}; + +/* }}} */ +/* {{{ API */ + +static const GskCurveClass * +get_class (GskPathOperation op) +{ + const GskCurveClass *klasses[] = { + [GSK_PATH_CLOSE] = &GSK_LINE_CURVE_CLASS, + [GSK_PATH_LINE] = &GSK_LINE_CURVE_CLASS, + [GSK_PATH_QUAD] = &GSK_QUAD_CURVE_CLASS, + [GSK_PATH_CUBIC] = &GSK_CUBIC_CURVE_CLASS, + [GSK_PATH_CONIC] = &GSK_CONIC_CURVE_CLASS, + }; + + g_assert (op < G_N_ELEMENTS (klasses) && klasses[op] != NULL); + + return klasses[op]; +} + +void +gsk_curve_init (GskCurve *curve, + gskpathop op) +{ + memset (curve, 0, sizeof (GskCurve)); + get_class (gsk_pathop_op (op))->init (curve, op); +} + +void +gsk_curve_init_foreach (GskCurve *curve, + GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight) +{ + memset (curve, 0, sizeof (GskCurve)); + get_class (op)->init_foreach (curve, op, pts, n_pts, weight); +} + +void +gsk_curve_print (const GskCurve *curve, + GString *string) +{ + get_class (curve->op)->print (curve, string); +} + +char * +gsk_curve_to_string (const GskCurve *curve) +{ + GString *s = g_string_new (""); + gsk_curve_print (curve, s); + return g_string_free (s, FALSE); +} + +gskpathop +gsk_curve_pathop (const GskCurve *curve) +{ + return get_class (curve->op)->pathop (curve); +} + +const graphene_point_t * +gsk_curve_get_start_point (const GskCurve *curve) +{ + return get_class (curve->op)->get_start_point (curve); +} + +const graphene_point_t * +gsk_curve_get_end_point (const GskCurve *curve) +{ + return get_class (curve->op)->get_end_point (curve); +} + +void +gsk_curve_get_start_tangent (const GskCurve *curve, + graphene_vec2_t *tangent) +{ + get_class (curve->op)->get_start_tangent (curve, tangent); +} + +void +gsk_curve_get_end_tangent (const GskCurve *curve, + graphene_vec2_t *tangent) +{ + get_class (curve->op)->get_end_tangent (curve, tangent); +} + +void +gsk_curve_get_point (const GskCurve *curve, + float progress, + graphene_point_t *pos) +{ + get_class (curve->op)->get_point (curve, progress, pos); +} + +void +gsk_curve_get_tangent (const GskCurve *curve, + float progress, + graphene_vec2_t *tangent) +{ + get_class (curve->op)->get_tangent (curve, progress, tangent); +} + +float +gsk_curve_get_curvature (const GskCurve *curve, + float t, + graphene_point_t *center) +{ + float k; + + k = get_class (curve->op)->get_curvature (curve, t); + + if (center != NULL && k != 0) + { + graphene_point_t p; + graphene_vec2_t tangent; + float r; + + r = 1/k; + gsk_curve_get_point (curve, t, &p); + gsk_curve_get_tangent (curve, t, &tangent); + center->x = p.x - r * graphene_vec2_get_y (&tangent); + center->y = p.y + r * graphene_vec2_get_x (&tangent); + } + + return k; +} + +void +gsk_curve_reverse (const GskCurve *curve, + GskCurve *reverse) +{ + get_class (curve->op)->reverse (curve, reverse); +} + +void +gsk_curve_split (const GskCurve *curve, + float progress, + GskCurve *start, + GskCurve *end) +{ + get_class (curve->op)->split (curve, progress, start, end); +} + +void +gsk_curve_segment (const GskCurve *curve, + float start, + float end, + GskCurve *segment) +{ + if (start <= 0 && end >= 1) + { + *segment = *curve; + return; + } + + get_class (curve->op)->segment (curve, start, end, segment); +} + +gboolean +gsk_curve_decompose (const GskCurve *curve, + float tolerance, + GskCurveAddLineFunc add_line_func, + gpointer user_data) +{ + return get_class (curve->op)->decompose (curve, tolerance, add_line_func, user_data); +} + +gboolean +gsk_curve_decompose_curve (const GskCurve *curve, + GskPathForeachFlags flags, + float tolerance, + GskCurveAddCurveFunc add_curve_func, + gpointer user_data) +{ + return get_class (curve->op)->decompose_curve (curve, flags, tolerance, add_curve_func, user_data); +} + +void +gsk_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + get_class (curve->op)->get_bounds (curve, bounds); +} + +void +gsk_curve_get_tight_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + get_class (curve->op)->get_tight_bounds (curve, bounds); +} + +static inline int +line_get_crossing (const graphene_point_t *p, + const graphene_point_t *p1, + const graphene_point_t *p2) +{ + if (p1->y <= p->y) + { + if (p2->y > p->y) + { + if ((p2->x - p1->x) * (p->y - p1->y) - (p->x - p1->x) * (p2->y - p1->y) > 0) + return 1; + } + } + else if (p2->y <= p->y) + { + if ((p2->x - p1->x) * (p->y - p1->y) - (p->x - p1->x) * (p2->y - p1->y) < 0) + return -1; + } + + return 0; +} + +int +gsk_curve_get_crossing (const GskCurve *curve, + const graphene_point_t *point) +{ + GskBoundingBox bounds; + GskCurve c1, c2; + + if (curve->op == GSK_PATH_LINE || curve->op == GSK_PATH_CLOSE) + return line_get_crossing (point, gsk_curve_get_start_point (curve), gsk_curve_get_end_point (curve)); + + gsk_curve_get_bounds (curve, &bounds); + + if (bounds.max.y < point->y || bounds.min.y > point->y || bounds.max.x < point->x) + return 0; + + if (bounds.min.x > point->x) + return line_get_crossing (point, gsk_curve_get_start_point (curve), gsk_curve_get_end_point (curve)); + + if (graphene_point_distance (&bounds.min, &bounds.max, NULL, NULL) < 0.001) + return line_get_crossing (point, gsk_curve_get_start_point (curve), gsk_curve_get_end_point (curve)); + + gsk_curve_split (curve, 0.5, &c1, &c2); + + return gsk_curve_get_crossing (&c1, point) + gsk_curve_get_crossing (&c2, point); +} + +static gboolean +project_point_onto_line (const GskCurve *curve, + const graphene_point_t *point, + float threshold, + float *out_distance, + float *out_t) +{ + const graphene_point_t *a = gsk_curve_get_start_point (curve); + const graphene_point_t *b = gsk_curve_get_end_point (curve); + graphene_vec2_t n, ap; + graphene_point_t p; + + if (graphene_point_equal (a, b)) + { + *out_t = 0; + *out_distance = graphene_point_distance (point, a, NULL, NULL); + } + else + { + graphene_vec2_init (&n, b->x - a->x, b->y - a->y); + graphene_vec2_init (&ap, point->x - a->x, point->y - a->y); + + *out_t = graphene_vec2_dot (&n, &ap) / graphene_vec2_dot (&n, &n); + *out_t = CLAMP (*out_t, 0, 1); + + graphene_point_interpolate (a, b, *out_t, &p); + *out_distance = graphene_point_distance (point, &p, NULL, NULL); + } + + return *out_distance <= threshold; +} + +static float +get_segment_bounding_sphere (const GskCurve *curve, + float t1, + float t2, + graphene_point_t *center) +{ + GskCurve c; + GskBoundingBox bounds; + + gsk_curve_segment (curve, t1, t2, &c); + gsk_curve_get_tight_bounds (&c, &bounds); + graphene_point_interpolate (&bounds.min, &bounds.max, 0.5, center); + return graphene_point_distance (center, &bounds.min, NULL, NULL); +} + +static gboolean +find_closest_point (const GskCurve *curve, + const graphene_point_t *point, + float threshold, + float t1, + float t2, + float *out_distance, + float *out_t) +{ + graphene_point_t center; + float radius; + float t, d, nt; + + radius = get_segment_bounding_sphere (curve, t1, t2, ¢er); + if (graphene_point_distance (¢er, point, NULL, NULL) > threshold + radius) + return FALSE; + + d = INFINITY; + t = (t1 + t2) / 2; + + if (radius < 1) + { + graphene_point_t p; + gsk_curve_get_point (curve, t, &p); + d = graphene_point_distance (point, &p, NULL, NULL); + nt = t; + } + else + { + float dd, tt; + + dd = INFINITY; + nt = 0; + + if (find_closest_point (curve, point, threshold, t1, t, &dd, &tt)) + { + d = dd; + nt = tt; + } + + if (find_closest_point (curve, point, MIN (dd, threshold), t, t2, &dd, &tt)) + { + d = dd; + nt = tt; + } + } + + if (d < threshold) + { + *out_distance = d; + *out_t = nt; + return TRUE; + } + else + { + *out_distance = INFINITY; + *out_t = 0; + return FALSE; + } +} + +gboolean +gsk_curve_get_closest_point (const GskCurve *curve, + const graphene_point_t *point, + float threshold, + float *out_dist, + float *out_t) +{ + if (curve->op == GSK_PATH_LINE || curve->op == GSK_PATH_CLOSE) + return project_point_onto_line (curve, point, threshold, out_dist, out_t); + else + return find_closest_point (curve, point, threshold, 0, 1, out_dist, out_t); +} + +/* }}} */ + +/* vim:set foldmethod=marker expandtab: */ diff --git a/gsk/gskcurveprivate.h b/gsk/gskcurveprivate.h new file mode 100644 index 0000000000..db7bff18ee --- /dev/null +++ b/gsk/gskcurveprivate.h @@ -0,0 +1,176 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + + +#pragma once + +#include "gskpathopprivate.h" +#include "gskpath.h" +#include "gskboundingboxprivate.h" + +G_BEGIN_DECLS + +typedef gpointer gskpathop; + +typedef union _GskCurve GskCurve; + +typedef struct _GskLineCurve GskLineCurve; +typedef struct _GskQuadCurve GskQuadCurve; +typedef struct _GskCubicCurve GskCubicCurve; +typedef struct _GskConicCurve GskConicCurve; + +struct _GskLineCurve +{ + GskPathOperation op; + + gboolean padding; + + graphene_point_t points[2]; +}; + +struct _GskQuadCurve +{ + GskPathOperation op; + + gboolean has_coefficients; + + graphene_point_t points[3]; + + graphene_point_t coeffs[3]; +}; + +struct _GskCubicCurve +{ + GskPathOperation op; + + gboolean has_coefficients; + + graphene_point_t points[4]; + + graphene_point_t coeffs[4]; +}; + +struct _GskConicCurve +{ + GskPathOperation op; + + gboolean has_coefficients; + + /* points[0], points[1], points[3] are the control points, + * points[2].x is the weight + */ + graphene_point_t points[4]; + + graphene_point_t num[3]; + graphene_point_t denom[3]; +}; + +union _GskCurve +{ + GskPathOperation op; + GskLineCurve line; + GskQuadCurve quad; + GskCubicCurve cubic; + GskConicCurve conic; +}; + +typedef enum { + GSK_CURVE_LINE_REASON_STRAIGHT, + GSK_CURVE_LINE_REASON_SHORT +} GskCurveLineReason; + +typedef gboolean (* GskCurveAddLineFunc) (const graphene_point_t *from, + const graphene_point_t *to, + float from_progress, + float to_progress, + GskCurveLineReason reason, + gpointer user_data); + +typedef gboolean (* GskCurveAddCurveFunc) (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer user_data); + +void gsk_curve_init (GskCurve *curve, + gskpathop op); +void gsk_curve_init_foreach (GskCurve *curve, + GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight); + +void gsk_curve_print (const GskCurve *curve, + GString *string); + +char * gsk_curve_to_string (const GskCurve *curve); + +gskpathop gsk_curve_pathop (const GskCurve *curve); +const graphene_point_t *gsk_curve_get_start_point (const GskCurve *curve); +const graphene_point_t *gsk_curve_get_end_point (const GskCurve *curve); +void gsk_curve_get_start_tangent (const GskCurve *curve, + graphene_vec2_t *tangent); +void gsk_curve_get_end_tangent (const GskCurve *curve, + graphene_vec2_t *tangent); +void gsk_curve_get_point (const GskCurve *curve, + float progress, + graphene_point_t *pos); +void gsk_curve_get_tangent (const GskCurve *curve, + float progress, + graphene_vec2_t *tangent); +void gsk_curve_reverse (const GskCurve *curve, + GskCurve *reverse); +void gsk_curve_split (const GskCurve *curve, + float progress, + GskCurve *start, + GskCurve *end); +void gsk_curve_segment (const GskCurve *curve, + float start, + float end, + GskCurve *segment); +gboolean gsk_curve_decompose (const GskCurve *curve, + float tolerance, + GskCurveAddLineFunc add_line_func, + gpointer user_data); +gboolean gsk_curve_decompose_curve (const GskCurve *curve, + GskPathForeachFlags flags, + float tolerance, + GskCurveAddCurveFunc add_curve_func, + gpointer user_data); + +#define gsk_curve_builder_to(curve, builder) gsk_path_builder_pathop_to ((builder), gsk_curve_pathop (curve)) + +float gsk_curve_get_curvature (const GskCurve *curve, + float t, + graphene_point_t *center); +void gsk_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds); +void gsk_curve_get_tight_bounds (const GskCurve *curve, + GskBoundingBox *bounds); + +int gsk_curve_get_crossing (const GskCurve *curve, + const graphene_point_t *point); +gboolean gsk_curve_get_closest_point (const GskCurve *curve, + const graphene_point_t *point, + float threshold, + float *out_dist, + float *out_t); + +G_END_DECLS + diff --git a/gsk/gskenums.h b/gsk/gskenums.h index 5b0ac88193..40003648e4 100644 --- a/gsk/gskenums.h +++ b/gsk/gskenums.h @@ -170,6 +170,85 @@ typedef enum { GSK_CORNER_BOTTOM_LEFT } GskCorner; +/** + * GskFillRule: + * @GSK_FILL_RULE_WINDING: If the path crosses the ray from + * left-to-right, counts +1. If the path crosses the ray + * from right to left, counts -1. (Left and right are determined + * from the perspective of looking along the ray from the starting + * point.) If the total count is non-zero, the point will be filled. + * @GSK_FILL_RULE_EVEN_ODD: Counts the total number of + * intersections, without regard to the orientation of the contour. If + * the total number of intersections is odd, the point will be + * filled. + * + * `GskFillRule` is used to select how paths are filled. + * + * Whether or not a point is included in the fill is determined by taking + * a ray from that point to infinity and looking at intersections with the + * path. The ray can be in any direction, as long as it doesn't pass through + * the end point of a segment or have a tricky intersection such as + * intersecting tangent to the path. + * + * (Note that filling is not actually implemented in this way. This + * is just a description of the rule that is applied.) + * + * New entries may be added in future versions. + * + * Since: 4.14 + */ +typedef enum { + GSK_FILL_RULE_WINDING, + GSK_FILL_RULE_EVEN_ODD +} GskFillRule; + +/** + * GskPathOperation: + * @GSK_PATH_MOVE: A move-to operation, with 1 point describing the target point. + * @GSK_PATH_CLOSE: A close operation ending the current contour with a line back + * to the starting point. Two points describe the start and end of the line. + * @GSK_PATH_LINE: A line-to operation, with 2 points describing the start and + * end point of a straight line. + * @GSK_PATH_QUAD: A curve-to operation describing a quadratic Bézier curve + * with 3 points describing the start point, the control point and the end + * point of the curve. + * @GSK_PATH_CUBIC: A curve-to operation describing a cubic Bézier curve with 4 + * points describing the start point, the two control points and the end point + * of the curve. + * @GSK_PATH_CONIC: A weighted quadratic Bézier curve with 3 points describing + * the start point, control point and end point of the curve. A weight for the + * curve will be passed, too. + * + * Path operations are used to described segments of a `GskPath`. + * + * More values may be added in the future. + * + * Since: 4.14 + */ +typedef enum { + GSK_PATH_MOVE, + GSK_PATH_CLOSE, + GSK_PATH_LINE, + GSK_PATH_QUAD, + GSK_PATH_CUBIC, + GSK_PATH_CONIC, +} GskPathOperation; + +/** + * GskPathDirection: + * @GSK_PATH_START: The side that leads to the start of the path + * @GSK_PATH_END: The side that leads to the end of the path + * + * The values of the `GskPathDirection` enum are used to pick one + * of the two sides of the path that at a given point on the path. + * + * Since: 4.14 + */ +typedef enum { + GSK_PATH_START, + GSK_PATH_END +} GskPathDirection; + /** * GskSerializationError: * @GSK_SERIALIZATION_UNSUPPORTED_FORMAT: The format can not be identified @@ -274,4 +353,3 @@ typedef enum GSK_MASK_MODE_LUMINANCE, GSK_MASK_MODE_INVERTED_LUMINANCE } GskMaskMode; - diff --git a/gsk/gskpath.c b/gsk/gskpath.c new file mode 100644 index 0000000000..a62b681c92 --- /dev/null +++ b/gsk/gskpath.c @@ -0,0 +1,1322 @@ +/* + * 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 "gskpathprivate.h" + +#include "gskcurveprivate.h" +#include "gskpathbuilder.h" +#include "gskpathpointprivate.h" + +/** + * GskPath: + * + * A `GskPath` describes lines and curves that are more complex + * than simple rectangles. Paths can used for rendering (filling or + * stroking) and for animations (e.g. as trajectories). + * + * `GskPath` is an immutable, opaque, reference-counted struct. + * After creation, you cannot change the types it represents. + * Instead, new `GskPath` objects have to be created. + * + * The [struct@Gsk.PathBuilder] structure is meant to help in this endeavor. + * + * Conceptually, a path consists of zero or more contours (continous, connected + * curves), each of which may or may not be closed. Contours are typically + * constructed from Bézier segments. + */ + +struct _GskPath +{ + /*< private >*/ + guint ref_count; + + GskPathFlags flags; + + gsize n_contours; + GskContour *contours[]; + /* followed by the contours data */ +}; + +G_DEFINE_BOXED_TYPE (GskPath, gsk_path, gsk_path_ref, gsk_path_unref) + +GskPath * +gsk_path_new_from_contours (const GSList *contours) +{ + GskPath *path; + const GSList *l; + gsize size; + gsize n_contours; + guint8 *contour_data; + GskPathFlags flags; + + flags = GSK_PATH_CLOSED | GSK_PATH_FLAT; + size = 0; + n_contours = 0; + for (l = contours; l; l = l->next) + { + GskContour *contour = l->data; + + n_contours++; + size += sizeof (GskContour *); + size += gsk_contour_get_size (contour); + flags &= gsk_contour_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; +} + +/** + * gsk_path_ref: + * @self: a `GskPath` + * + * Increases the reference count of a `GskPath` by one. + * + * Returns: the passed in `GskPath`. + * + * Since: 4.14 + */ +GskPath * +gsk_path_ref (GskPath *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + self->ref_count++; + + return self; +} + +/** + * gsk_path_unref: + * @self: a `GskPath` + * + * Decreases the reference count of a `GskPath` by one. + * + * If the resulting reference count is zero, frees the path. + * + * Since: 4.14 + */ +void +gsk_path_unref (GskPath *self) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (self->ref_count > 0); + + self->ref_count--; + if (self->ref_count > 0) + return; + + g_free (self); +} + +const GskContour * +gsk_path_get_contour (GskPath *path, + gsize i) +{ + return path->contours[i]; +} + +GskPathFlags +gsk_path_get_flags (GskPath *self) +{ + return self->flags; +} + +/** + * gsk_path_print: + * @self: a `GskPath` + * @string: The string to print into + * + * Converts @self into a human-readable string representation suitable + * for printing. + * + * The string is compatible with + * [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData), + * with the exception that conic curves will generate a string of the + * form "O x1 y1, x2 y2, w" where x1, y1 is the control point, x2, y2 + * is the end point, and w is the weight. + * + * Since: 4.14 + */ +void +gsk_path_print (GskPath *self, + GString *string) +{ + gsize i; + + g_return_if_fail (self != NULL); + g_return_if_fail (string != NULL); + + for (i = 0; i < self->n_contours; i++) + { + if (i > 0) + g_string_append_c (string, ' '); + gsk_contour_print (self->contours[i], string); + } +} + +/** + * gsk_path_to_string: + * @self: a `GskPath` + * + * Converts the path into a string that is suitable for printing. + * + * You can use this function in a debugger to get a quick overview + * of the path. + * + * This is a wrapper around [method@Gsk.Path.print], see that function + * for details. + * + * Returns: A new string for @self + * + * Since: 4.14 + */ +char * +gsk_path_to_string (GskPath *self) +{ + GString *string; + + g_return_val_if_fail (self != NULL, NULL); + + string = g_string_new (""); + + gsk_path_print (self, string); + + return g_string_free (string, FALSE); +} + +static gboolean +gsk_path_to_cairo_add_op (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer cr) +{ + switch (op) + { + case GSK_PATH_MOVE: + cairo_move_to (cr, pts[0].x, pts[0].y); + break; + + case GSK_PATH_CLOSE: + cairo_close_path (cr); + break; + + case GSK_PATH_LINE: + cairo_line_to (cr, pts[1].x, pts[1].y); + break; + + case GSK_PATH_QUAD: + { + double x, y; + cairo_get_current_point (cr, &x, &y); + cairo_curve_to (cr, + 1/3.f * x + 2/3.f * pts[1].x, 1/3.f * y + 2/3.f * pts[1].y, + 2/3.f * pts[1].x + 1/3.f * pts[2].x, 2/3.f * pts[1].y + 1/3.f * pts[2].y, + pts[2].x, pts[2].y); + } + break; + + case GSK_PATH_CUBIC: + cairo_curve_to (cr, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y); + break; + + case GSK_PATH_CONIC: + default: + g_assert_not_reached (); + return FALSE; + } + + return TRUE; +} + +/** + * gsk_path_to_cairo: + * @self: a `GskPath` + * @cr: a cairo context + * + * Appends the given @path to the given cairo context for drawing + * with Cairo. + * + * This may cause some suboptimal conversions to be performed as + * Cairo does not support all features of `GskPath`. + * + * This function does not clear the existing Cairo path. Call + * cairo_new_path() if you want this. + * + * Since: 4.14 + */ +void +gsk_path_to_cairo (GskPath *self, + cairo_t *cr) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (cr != NULL); + + gsk_path_foreach_with_tolerance (self, + GSK_PATH_FOREACH_ALLOW_CUBIC, + cairo_get_tolerance (cr), + gsk_path_to_cairo_add_op, + cr); +} + +/*< private > + * gsk_path_get_n_contours: + * @path: a `GskPath` + * + * Gets the number of contours @path is composed out of. + * + * Returns: the number of contours in @path + */ +gsize +gsk_path_get_n_contours (GskPath *path) +{ + return path->n_contours; +} + +/** + * gsk_path_is_empty: + * @self: a `GskPath` + * + * Checks if the path is empty, i.e. contains no lines or curves. + * + * Returns: `TRUE` if the path is empty + * + * Since: 4.14 + */ +gboolean +gsk_path_is_empty (GskPath *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + + return self->n_contours == 0; +} + +/** + * gsk_path_is_closed: + * @self: a `GskPath` + * + * Returns if the path represents a single closed + * contour. + * + * Returns: `TRUE` if the path is closed + * + * Since: 4.14 + */ +gboolean +gsk_path_is_closed (GskPath *self) +{ + g_return_val_if_fail (self != NULL, FALSE); + + /* XXX: is the empty path closed? Currently it's not */ + if (self->n_contours != 1) + return FALSE; + + return gsk_contour_get_flags (self->contours[0]) & GSK_PATH_CLOSED ? TRUE : FALSE; +} + +/** + * gsk_path_get_bounds: + * @self: a `GskPath` + * @bounds: (out caller-allocates): the bounds of the given path + * + * Computes the bounds of the given path. + * + * The returned bounds may be larger than necessary, because this + * function aims to be fast, not accurate. The bounds are guaranteed + * to contain the path. + * + * It is possible that the returned rectangle has 0 width and/or height. + * This can happen when the path only describes a point or an + * axis-aligned line. + * + * If the path is empty, `FALSE` is returned and @bounds are set to + * graphene_rect_zero(). This is different from the case where the path + * is a single point at the origin, where the @bounds will also be set to + * the zero rectangle but 0 will be returned. + * + * Returns: `TRUE` if the path has bounds, `FALSE` if the path is known + * to be empty and have no bounds. + * + * Since: 4.14 + */ +gboolean +gsk_path_get_bounds (GskPath *self, + graphene_rect_t *bounds) +{ + gsize i; + + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (bounds != NULL, FALSE); + + for (i = 0; i < self->n_contours; i++) + { + if (gsk_contour_get_bounds (self->contours[i], bounds)) + break; + } + + if (i >= self->n_contours) + { + graphene_rect_init_from_rect (bounds, graphene_rect_zero ()); + return FALSE; + } + + for (i++; i < self->n_contours; i++) + { + graphene_rect_t tmp; + + if (gsk_contour_get_bounds (self->contours[i], &tmp)) + graphene_rect_union (bounds, &tmp, bounds); + } + + return TRUE; +} + +/** + * gsk_path_in_fill: + * @self: a `GskPath` + * @point: the point to test + * @fill_rule: the fill rule to follow + * + * Returns whether the given point is inside the area + * that would be affected if the path was filled according + * to @fill_rule. + * + * Note that this function assumes that filling a contour + * implicitly closes it. + * + * Returns: `TRUE` if @point is inside + * + * Since: 4.14 + */ +gboolean +gsk_path_in_fill (GskPath *self, + const graphene_point_t *point, + GskFillRule fill_rule) +{ + int winding = 0; + + for (int i = 0; i < self->n_contours; i++) + winding += gsk_contour_get_winding (self->contours[i], point); + + switch (fill_rule) + { + case GSK_FILL_RULE_EVEN_ODD: + return winding & 1; + case GSK_FILL_RULE_WINDING: + return winding != 0; + default: + g_assert_not_reached (); + } +} + +/** + * gsk_path_get_closest_point: + * @self: a `GskPath` + * @point: the point + * @threshold: maximum allowed distance + * + * Computes the closest point on the path to the given point + * and returns a `GskPathPoint` for it. + * + * If there is no point closer than the given threshold, `NULL` + * is returned. + * + * Returns: (transfer full) (nullable): the closest point + * on @self, or `NULL` if no point is closer than @threshold + * + * Since: 4.14 + */ +GskPathPoint * +gsk_path_get_closest_point (GskPath *self, + const graphene_point_t *point, + float threshold) +{ + GskPathPoint *p; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (point != NULL, NULL); + g_return_val_if_fail (threshold >= 0, NULL); + + p = gsk_path_point_new (self); + + for (int i = 0; i < self->n_contours; i++) + { + float distance; + + if (gsk_contour_get_closest_point (self->contours[i], point, threshold, p, &distance)) + { + p->contour = self->contours[i]; + threshold = distance; + } + } + + if (p->contour != NULL) + return p; + + gsk_path_point_unref (p); + + return NULL; +} + +/** + * gsk_path_foreach: + * @self: a `GskPath` + * @flags: flags to pass to the foreach function. See [flags@Gsk.PathForeachFlags] + * for details about flags + * @func: (scope call) (closure user_data): the function to call for operations + * @user_data: (nullable): user data passed to @func + * + * Calls @func for every operation of the path. + * + * Note that this only approximates @self, because paths can contain + * optimizations for various specialized contours, and depending on + * the @flags, the path may be decomposed into simpler curves than + * the ones that it contained originally. + * + * Returns: `FALSE` if @func returned FALSE`, `TRUE` otherwise. + * + * Since: 4.14 + */ +gboolean +gsk_path_foreach (GskPath *self, + GskPathForeachFlags flags, + GskPathForeachFunc func, + gpointer user_data) +{ + g_return_val_if_fail (self != NULL, FALSE); + g_return_val_if_fail (func, FALSE); + + return gsk_path_foreach_with_tolerance (self, + flags, + GSK_PATH_TOLERANCE_DEFAULT, + func, + user_data); +} + +typedef struct _GskPathForeachTrampoline GskPathForeachTrampoline; +struct _GskPathForeachTrampoline +{ + GskPathForeachFlags flags; + double tolerance; + GskPathForeachFunc func; + gpointer user_data; +}; + +static gboolean +gsk_path_foreach_trampoline_add_line (const graphene_point_t *from, + const graphene_point_t *to, + float from_progress, + float to_progress, + GskCurveLineReason reason, + gpointer data) +{ + GskPathForeachTrampoline *trampoline = data; + + return trampoline->func (GSK_PATH_LINE, + (graphene_point_t[2]) { *from, *to }, + 2, + 0.0f, + trampoline->user_data); +} + +static gboolean +gsk_path_foreach_trampoline_add_curve (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer data) +{ + GskPathForeachTrampoline *trampoline = data; + + return trampoline->func (op, pts, n_pts, weight, trampoline->user_data); +} + +static gboolean +gsk_path_foreach_trampoline (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer data) +{ + GskPathForeachTrampoline *trampoline = data; + + switch (op) + { + case GSK_PATH_MOVE: + case GSK_PATH_CLOSE: + case GSK_PATH_LINE: + return trampoline->func (op, pts, n_pts, weight, trampoline->user_data); + + case GSK_PATH_QUAD: + { + GskCurve curve; + + if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_QUAD) + return trampoline->func (op, pts, n_pts, weight, trampoline->user_data); + + gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_QUAD, pts)); + return gsk_curve_decompose (&curve, + trampoline->tolerance, + gsk_path_foreach_trampoline_add_line, + trampoline); + } + + case GSK_PATH_CUBIC: + { + GskCurve curve; + + if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CUBIC) + return trampoline->func (op, pts, n_pts, weight, trampoline->user_data); + + gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_CUBIC, pts)); + if (trampoline->flags & (GSK_PATH_FOREACH_ALLOW_QUAD|GSK_PATH_FOREACH_ALLOW_CONIC)) + return gsk_curve_decompose_curve (&curve, + trampoline->flags, + trampoline->tolerance, + gsk_path_foreach_trampoline_add_curve, + trampoline); + + return gsk_curve_decompose (&curve, + trampoline->tolerance, + gsk_path_foreach_trampoline_add_line, + trampoline); + } + + case GSK_PATH_CONIC: + { + GskCurve curve; + + if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_CONIC) + return trampoline->func (op, pts, n_pts, weight, trampoline->user_data); + + gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_CONIC, (graphene_point_t[4]) { pts[0], pts[1], { weight, }, pts[2] } )); + if (trampoline->flags & (GSK_PATH_FOREACH_ALLOW_QUAD|GSK_PATH_FOREACH_ALLOW_CUBIC)) + return gsk_curve_decompose_curve (&curve, + trampoline->flags, + trampoline->tolerance, + gsk_path_foreach_trampoline_add_curve, + trampoline); + + return gsk_curve_decompose (&curve, + trampoline->tolerance, + gsk_path_foreach_trampoline_add_line, + trampoline); + } + + + default: + g_assert_not_reached (); + return FALSE; + } +} + +#define ALLOW_ANY (GSK_PATH_FOREACH_ALLOW_QUAD| \ + GSK_PATH_FOREACH_ALLOW_CUBIC| \ + GSK_PATH_FOREACH_ALLOW_CONIC) + +gboolean +gsk_path_foreach_with_tolerance (GskPath *self, + GskPathForeachFlags flags, + double tolerance, + GskPathForeachFunc func, + gpointer user_data) +{ + GskPathForeachTrampoline trampoline; + gsize i; + + /* If we need to massage the data, set up a trampoline here */ + if ((flags & ALLOW_ANY) != ALLOW_ANY) + { + trampoline = (GskPathForeachTrampoline) { flags, tolerance, func, user_data }; + func = gsk_path_foreach_trampoline; + user_data = &trampoline; + } + + for (i = 0; i < self->n_contours; i++) + { + if (!gsk_contour_foreach (self->contours[i], tolerance, func, user_data)) + return FALSE; + } + + return TRUE; +} + +/* path parser and utilities */ + +static void +skip_whitespace (const char **p) +{ + while (g_ascii_isspace (**p)) + (*p)++; +} + +static void +skip_optional_comma (const char **p) +{ + skip_whitespace (p); + if (**p == ',') + (*p)++; +} + +static gboolean +parse_number (const char **p, + double *c) +{ + char *e; + *c = g_ascii_strtod (*p, &e); + if (e == *p) + return FALSE; + *p = e; + skip_optional_comma (p); + return TRUE; +} + +static gboolean +parse_coordinate (const char **p, + double *c) +{ + return parse_number (p, c); +} + +static gboolean +parse_coordinate_pair (const char **p, + double *x, + double *y) +{ + double xx, yy; + const char *o = *p; + + if (!parse_coordinate (p, &xx)) + { + *p = o; + return FALSE; + } + if (!parse_coordinate (p, &yy)) + { + *p = o; + return FALSE; + } + + *x = xx; + *y = yy; + + return TRUE; +} + +static gboolean +parse_nonnegative_number (const char **p, + double *x) +{ + const char *o = *p; + double n; + + if (!parse_number (p, &n)) + return FALSE; + + if (n < 0) + { + *p = o; + return FALSE; + } + + *x = n; + + return TRUE; +} + +/* This fixes a flaw in our use of strchr() below: + * + * If p already points at the end of the string, + * we misinterpret strchr ("xyz", *p) returning + * non-NULL to mean that we can increment p. + * + * But strchr() will return a pointer to the + * final NUL byte in this case, and we walk off + * the end of the string. Oops + */ +static inline char * +_strchr (const char *str, + int c) +{ + if (c == 0) + return NULL; + else + return strchr (str, c); +} + +static gboolean +parse_flag (const char **p, + gboolean *f) +{ + skip_whitespace (p); + if (_strchr ("01", **p)) + { + *f = **p == '1'; + (*p)++; + skip_optional_comma (p); + return TRUE; + } + + return FALSE; +} + +static gboolean +parse_command (const char **p, + char *cmd) +{ + char *s; + const char *allowed; + + if (*cmd == 'X') + allowed = "mM"; + else + allowed = "mMhHvVzZlLcCsStTqQoOaA"; + + skip_whitespace (p); + s = _strchr (allowed, **p); + if (s) + { + *cmd = *s; + (*p)++; + return TRUE; + } + return FALSE; +} + +static gboolean +parse_string (const char **p, + const char *s) +{ + int len = strlen (s); + if (strncmp (*p, s, len) != 0) + return FALSE; + (*p) += len; + return TRUE; +} + +static gboolean +parse_rectangle (const char **p, + double *x, + double *y, + double *w, + double *h) +{ + const char *o = *p; + double w2; + + /* Check for M%g,%gh%gv%gh%gz without any intervening whitespace */ + if (parse_coordinate_pair (p, x, y) && + parse_string (p, "h") && + parse_coordinate (p, w) && + parse_string (p, "v") && + parse_coordinate (p, h) && + parse_string (p, "h") && + parse_coordinate (p, &w2) && + parse_string (p, "z") && + w2 == - *w) + { + skip_whitespace (p); + + return TRUE; + } + + *p = o; + return FALSE; +} + +static gboolean +parse_circle (const char **p, + double *sx, + double *sy, + double *r) +{ + const char *o = *p; + double r1, r2, r3, mx, my, ex, ey; + + /* Check for M%g,%gA%g,%g,0,1,0,%g,%gA%g,%g,0,1,0,%g,%g + * without any intervening whitespace + */ + if (parse_coordinate_pair (p, sx, sy) && + parse_string (p, "A") && + parse_coordinate_pair (p, r, &r1) && + parse_string (p, "0 0 0") && + parse_coordinate_pair (p, &mx, &my) && + parse_string (p, "A") && + parse_coordinate_pair (p, &r2, &r3) && + parse_string (p, "0 0 0") && + parse_coordinate_pair (p, &ex, &ey) && + parse_string (p, "z") && + *r == r1 && r1 == r2 && r2 == r3 && + *sx == ex && *sy == ey) + { + skip_whitespace (p); + + return TRUE; + } + + *p = o; + return FALSE; +} + +/** + * gsk_path_parse: + * @string: a string + * + * This is a convenience function that constructs a `GskPath` + * from a serialized form. + * + * The string is expected to be in + * [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData), + * as e.g. produced by [method@Gsk.Path.to_string]. + * + * Returns: (nullable): a new `GskPath`, or `NULL` + * if @string could not be parsed + * + * Since: 4.14 + */ +GskPath * +gsk_path_parse (const char *string) +{ + GskPathBuilder *builder; + double x, y; + double prev_x1, prev_y1; + double path_x, path_y; + const char *p; + char cmd; + char prev_cmd; + gboolean after_comma; + gboolean repeat; + + builder = gsk_path_builder_new (); + + cmd = 'X'; + path_x = path_y = 0; + x = y = 0; + prev_x1 = prev_y1 = 0; + after_comma = FALSE; + + p = string; + while (*p) + { + prev_cmd = cmd; + repeat = !parse_command (&p, &cmd); + + if (after_comma && !repeat) + goto error; + + switch (cmd) + { + case 'X': + goto error; + + case 'Z': + case 'z': + if (repeat) + goto error; + else + { + gsk_path_builder_close (builder); + x = path_x; + y = path_y; + } + break; + + case 'M': + case 'm': + { + double x1, y1, w, h, r; + + if (parse_rectangle (&p, &x1, &y1, &w, &h)) + { + gsk_path_builder_add_rect (builder, &GRAPHENE_RECT_INIT (x1, y1, w, h)); + if (_strchr ("zZX", prev_cmd)) + { + path_x = x1; + path_y = y1; + } + x = x1; + y = y1; + } + else if (parse_circle (&p, &x1, &y1, &r)) + { + gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (x1 - r, y1), r); + if (_strchr ("zZX", prev_cmd)) + { + path_x = x1; + path_y = y1; + } + x = x1; + y = y1; + } + else if (parse_coordinate_pair (&p, &x1, &y1)) + { + if (cmd == 'm') + { + x1 += x; + y1 += y; + } + if (repeat) + gsk_path_builder_line_to (builder, x1, y1); + else + { + gsk_path_builder_move_to (builder, x1, y1); + if (_strchr ("zZX", prev_cmd)) + { + path_x = x1; + path_y = y1; + } + } + x = x1; + y = y1; + } + else + goto error; + } + break; + + case 'L': + case 'l': + { + double x1, y1; + + if (parse_coordinate_pair (&p, &x1, &y1)) + { + if (cmd == 'l') + { + x1 += x; + y1 += y; + } + + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_line_to (builder, x1, y1); + x = x1; + y = y1; + } + else + goto error; + } + break; + + case 'H': + case 'h': + { + double x1; + + if (parse_coordinate (&p, &x1)) + { + if (cmd == 'h') + x1 += x; + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_line_to (builder, x1, y); + x = x1; + } + else + goto error; + } + break; + + case 'V': + case 'v': + { + double y1; + + if (parse_coordinate (&p, &y1)) + { + if (cmd == 'v') + y1 += y; + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_line_to (builder, x, y1); + y = y1; + } + else + goto error; + } + break; + + case 'C': + case 'c': + { + double x0, y0, x1, y1, x2, y2; + + if (parse_coordinate_pair (&p, &x0, &y0) && + parse_coordinate_pair (&p, &x1, &y1) && + parse_coordinate_pair (&p, &x2, &y2)) + { + if (cmd == 'c') + { + x0 += x; + y0 += y; + x1 += x; + y1 += y; + x2 += x; + y2 += y; + } + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_cubic_to (builder, x0, y0, x1, y1, x2, y2); + prev_x1 = x1; + prev_y1 = y1; + x = x2; + y = y2; + } + else + goto error; + } + break; + + case 'S': + case 's': + { + double x0, y0, x1, y1, x2, y2; + + if (parse_coordinate_pair (&p, &x1, &y1) && + parse_coordinate_pair (&p, &x2, &y2)) + { + if (cmd == 's') + { + x1 += x; + y1 += y; + x2 += x; + y2 += y; + } + if (_strchr ("CcSs", prev_cmd)) + { + x0 = 2 * x - prev_x1; + y0 = 2 * y - prev_y1; + } + else + { + x0 = x; + y0 = y; + } + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_cubic_to (builder, x0, y0, x1, y1, x2, y2); + prev_x1 = x1; + prev_y1 = y1; + x = x2; + y = y2; + } + else + goto error; + } + break; + + case 'Q': + case 'q': + { + double x1, y1, x2, y2; + + if (parse_coordinate_pair (&p, &x1, &y1) && + parse_coordinate_pair (&p, &x2, &y2)) + { + if (cmd == 'q') + { + x1 += x; + y1 += y; + x2 += x; + y2 += y; + } + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_quad_to (builder, x1, y1, x2, y2); + prev_x1 = x1; + prev_y1 = y1; + x = x2; + y = y2; + } + else + goto error; + } + break; + + case 'T': + case 't': + { + double x1, y1, x2, y2; + + if (parse_coordinate_pair (&p, &x2, &y2)) + { + if (cmd == 't') + { + x2 += x; + y2 += y; + } + if (_strchr ("QqTt", prev_cmd)) + { + x1 = 2 * x - prev_x1; + y1 = 2 * y - prev_y1; + } + else + { + x1 = x; + y1 = y; + } + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_quad_to (builder, x1, y1, x2, y2); + prev_x1 = x1; + prev_y1 = y1; + x = x2; + y = y2; + } + else + goto error; + } + break; + + case 'O': + case 'o': + { + double x1, y1, x2, y2, weight; + + if (parse_coordinate_pair (&p, &x1, &y1) && + parse_coordinate_pair (&p, &x2, &y2) && + parse_nonnegative_number (&p, &weight)) + { + if (cmd == 'c') + { + x1 += x; + y1 += y; + x2 += x; + y2 += y; + } + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_conic_to (builder, x1, y1, x2, y2, weight); + x = x2; + y = y2; + } + else + goto error; + } + break; + + case 'A': + case 'a': + { + double rx, ry; + double x_axis_rotation; + int large_arc, sweep; + double x1, y1; + + if (parse_nonnegative_number (&p, &rx) && + parse_nonnegative_number (&p, &ry) && + parse_number (&p, &x_axis_rotation) && + parse_flag (&p, &large_arc) && + parse_flag (&p, &sweep) && + parse_coordinate_pair (&p, &x1, &y1)) + { + if (cmd == 'a') + { + x1 += x; + y1 += y; + } + + if (_strchr ("zZ", prev_cmd)) + { + gsk_path_builder_move_to (builder, x, y); + path_x = x; + path_y = y; + } + gsk_path_builder_svg_arc_to (builder, + rx, ry, x_axis_rotation, + large_arc, sweep, + x1, y1); + x = x1; + y = y1; + } + else + goto error; + } + break; + + default: + goto error; + } + + after_comma = (p > string) && p[-1] == ','; + } + + if (after_comma) + goto error; + + return gsk_path_builder_free_to_path (builder); + +error: + //g_warning ("Can't parse string '%s' as GskPath, error at %ld", string, p - string); + gsk_path_builder_unref (builder); + + return NULL; +} diff --git a/gsk/gskpath.h b/gsk/gskpath.h new file mode 100644 index 0000000000..246c958019 --- /dev/null +++ b/gsk/gskpath.h @@ -0,0 +1,122 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#pragma once + +#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + + +#include + +G_BEGIN_DECLS + +/** + * GskPathForeachFlags: + * @GSK_PATH_FOREACH_ALLOW_QUAD: Allow emission of `GSK_PATH_QUAD` operations + * @GSK_PATH_FOREACH_ALLOW_CUBIC: Allow emission of `GSK_PATH_CUBIC` operations. + * @GSK_PATH_FOREACH_ALLOW_CONIC: Allow emission of `GSK_PATH_CONIC` operations. + * + * Flags that can be passed to gsk_path_foreach() to enable additional + * features. + * + * By default, [method@Gsk.Path.foreach] will only emit a path with all operations + * flattened to straight lines to allow for maximum compatibility. The only + * operations emitted will be `GSK_PATH_MOVE`, `GSK_PATH_LINE` and `GSK_PATH_CLOSE`. + */ +typedef enum +{ + GSK_PATH_FOREACH_ALLOW_QUAD = (1 << 0), + GSK_PATH_FOREACH_ALLOW_CUBIC = (1 << 1), + GSK_PATH_FOREACH_ALLOW_CONIC = (1 << 2), +} GskPathForeachFlags; + +/** + * GskPathForeachFunc: + * @op: The operation to perform + * @pts: The points of the operation + * @n_pts: The number of points + * @weight: The weight for conic curves, or unused if not a conic curve. + * @user_data: The user data provided with the function + * + * Prototype of the callback to iterate throught the operations of + * a path. + * + * Returns: %TRUE to continue evaluating the path, %FALSE to + * immediately abort and not call the function again. + */ +typedef gboolean (* GskPathForeachFunc) (GskPathOperation op, + const graphene_point_t *pts, + gsize n_pts, + float weight, + gpointer user_data); + +#define GSK_TYPE_PATH (gsk_path_get_type ()) + +GDK_AVAILABLE_IN_4_14 +GType gsk_path_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_4_14 +GskPath * gsk_path_ref (GskPath *self); +GDK_AVAILABLE_IN_4_14 +void gsk_path_unref (GskPath *self); + +GDK_AVAILABLE_IN_4_14 +void gsk_path_print (GskPath *self, + GString *string); +GDK_AVAILABLE_IN_4_14 +char * gsk_path_to_string (GskPath *self); + +GDK_AVAILABLE_IN_4_14 +GskPath * gsk_path_parse (const char *string); + +GDK_AVAILABLE_IN_4_14 +void gsk_path_to_cairo (GskPath *self, + cairo_t *cr); + +GDK_AVAILABLE_IN_4_14 +gboolean gsk_path_is_empty (GskPath *self); + +GDK_AVAILABLE_IN_4_14 +gboolean gsk_path_is_closed (GskPath *self); + +GDK_AVAILABLE_IN_4_14 +gboolean gsk_path_get_bounds (GskPath *self, + graphene_rect_t *bounds); + +GDK_AVAILABLE_IN_4_14 +gboolean gsk_path_in_fill (GskPath *self, + const graphene_point_t *point, + GskFillRule fill_rule); + +GDK_AVAILABLE_IN_4_14 +GskPathPoint * gsk_path_get_closest_point (GskPath *self, + const graphene_point_t *point, + float threshold); + +GDK_AVAILABLE_IN_4_14 +gboolean gsk_path_foreach (GskPath *self, + GskPathForeachFlags flags, + GskPathForeachFunc func, + gpointer user_data); + +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..9e96e6a1e5 --- /dev/null +++ b/gsk/gskpathbuilder.c @@ -0,0 +1,1106 @@ +/* + * 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 + +#include "gskpathbuilder.h" + +#include "gskpathprivate.h" + +/** + * GskPathBuilder: + * + * `GskPathBuilder` is an auxiliary object for constructing + * `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 */ + 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). + * + * 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) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (path != NULL); + + 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_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. + * + * 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) +{ + GskContour *contour; + + g_return_if_fail (self != NULL); + + contour = gsk_rect_contour_new (rect); + gsk_path_builder_add_contour (self, contour); + + gsk_contour_get_start_end (contour, NULL, &self->current_point); +} + +/** + * 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. + * + * Since: 4.14 + */ +void +gsk_path_builder_add_rounded_rect (GskPathBuilder *self, + const GskRoundedRect *rect) +{ + GskContour *contour; + + g_return_if_fail (self != NULL); + g_return_if_fail (rect != NULL); + + contour = gsk_rounded_rect_contour_new (rect); + gsk_path_builder_add_contour (self, contour); +} + +/** + * 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. + * + * Since: 4.14 + */ +void +gsk_path_builder_add_circle (GskPathBuilder *self, + const graphene_point_t *center, + float radius) +{ + GskContour *contour; + + g_return_if_fail (self != 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 (self, contour); +} + +/** + * gsk_path_builder_add_ellipse: + * @self: a #GskPathBuilder + * @center: the center point of the ellipse + * @radius: the radius of the ellipse in x/y direction + * + * Adds an ellipse with the given @center and the @radius in + * x/y direction. + * + * Since: 4.14 + */ +void +gsk_path_builder_add_ellipse (GskPathBuilder *self, + const graphene_point_t *center, + const graphene_size_t *radius) +{ + const float weight = M_SQRT1_2; + graphene_point_t pts[8]; + float w, h; + + g_return_if_fail (self != NULL); + g_return_if_fail (center != NULL); + g_return_if_fail (radius != NULL); + + w = radius->width / 2; + h = radius->height / 2; + + pts[0] = GRAPHENE_POINT_INIT (center->x + w, center->y); + pts[1] = GRAPHENE_POINT_INIT (center->x + w, center->y + h); + pts[2] = GRAPHENE_POINT_INIT (center->x, center->y + h); + pts[3] = GRAPHENE_POINT_INIT (center->x - w, center->y + h); + pts[4] = GRAPHENE_POINT_INIT (center->x - w, center->y); + pts[5] = GRAPHENE_POINT_INIT (center->x - w, center->y - h); + pts[6] = GRAPHENE_POINT_INIT (center->x, center->y - h); + pts[7] = GRAPHENE_POINT_INIT (center->x + w, center->y - h); + + gsk_path_builder_move_to (self, pts[0].x, pts[0].y); + gsk_path_builder_conic_to (self, pts[1].x, pts[1].y, pts[2].x, pts[2].y, weight); + gsk_path_builder_conic_to (self, pts[3].x, pts[3].y, pts[4].x, pts[4].y, weight); + gsk_path_builder_conic_to (self, pts[5].x, pts[5].y, pts[6].x, pts[6].y, weight); + gsk_path_builder_conic_to (self, pts[7].x, pts[7].y, pts[0].x, pts[0].y, weight); + gsk_path_builder_close (self); +} + +/** + * 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_conic_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 + * @weight: weight of the curve + * + * Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline) + * from the current point to @x2, @y2 with the given + * @weight and @x1, @y1 as the single control point. + * + * Conic curves can be used to draw ellipses and circles. + * + * After this, @x2, @y2 will be the new current point. + * + * Since: 4.14 + */ +void +gsk_path_builder_conic_to (GskPathBuilder *self, + float x1, + float y1, + float x2, + float y2, + float weight) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (weight >= 0); + + self->flags &= ~GSK_PATH_FLAT; + gsk_path_builder_append_current (self, + GSK_PATH_CONIC, + 3, (graphene_point_t[3]) { + GRAPHENE_POINT_INIT (x1, y1), + GRAPHENE_POINT_INIT (weight, 0), + GRAPHENE_POINT_INIT (x2, y2) + }); +} + +/** + * gsk_path_builder_rel_conic_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 + * @weight: weight of the curve + * + * Adds a [conic curve](https://en.wikipedia.org/wiki/Non-uniform_rational_B-spline) + * from the current point to @x2, @y2 with the given + * @weight and @x1, @y1 as the single control point. + * + * This is the relative version of [method@Gsk.PathBuilder.conic_to]. + * + * Since: 4.14 + */ +void +gsk_path_builder_rel_conic_to (GskPathBuilder *self, + float x1, + float y1, + float x2, + float y2, + float weight) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (weight >= 0); + + gsk_path_builder_conic_to (self, + self->current_point.x + x1, + self->current_point.y + y1, + self->current_point.x + x2, + self->current_point.y + y2, + weight); +} + +/** + * 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); +} diff --git a/gsk/gskpathbuilder.h b/gsk/gskpathbuilder.h new file mode 100644 index 0000000000..cbd0991f4c --- /dev/null +++ b/gsk/gskpathbuilder.h @@ -0,0 +1,141 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#pragma once + +#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + + +#include +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_PATH_BUILDER (gsk_path_builder_get_type ()) + +GDK_AVAILABLE_IN_4_14 +GType gsk_path_builder_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_4_14 +GskPathBuilder * gsk_path_builder_new (void); +GDK_AVAILABLE_IN_4_14 +GskPathBuilder * gsk_path_builder_ref (GskPathBuilder *self); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_unref (GskPathBuilder *self); +GDK_AVAILABLE_IN_4_14 +GskPath * gsk_path_builder_free_to_path (GskPathBuilder *self) G_GNUC_WARN_UNUSED_RESULT; +GDK_AVAILABLE_IN_4_14 +GskPath * gsk_path_builder_to_path (GskPathBuilder *self) G_GNUC_WARN_UNUSED_RESULT; + +GDK_AVAILABLE_IN_4_14 +const graphene_point_t *gsk_path_builder_get_current_point (GskPathBuilder *self); + +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_add_path (GskPathBuilder *self, + GskPath *path); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_add_reverse_path (GskPathBuilder *self, + GskPath *path); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_add_cairo_path (GskPathBuilder *self, + const cairo_path_t *path); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_add_layout (GskPathBuilder *self, + PangoLayout *layout); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_add_rect (GskPathBuilder *self, + const graphene_rect_t *rect); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_add_rounded_rect (GskPathBuilder *self, + const GskRoundedRect *rect); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_add_circle (GskPathBuilder *self, + const graphene_point_t *center, + float radius); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_add_ellipse (GskPathBuilder *self, + const graphene_point_t *center, + const graphene_size_t *radius); + +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_move_to (GskPathBuilder *self, + float x, + float y); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_rel_move_to (GskPathBuilder *self, + float x, + float y); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_line_to (GskPathBuilder *self, + float x, + float y); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_rel_line_to (GskPathBuilder *self, + float x, + float y); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_quad_to (GskPathBuilder *self, + float x1, + float y1, + float x2, + float y2); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_rel_quad_to (GskPathBuilder *self, + float x1, + float y1, + float x2, + float y2); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_cubic_to (GskPathBuilder *self, + float x1, + float y1, + float x2, + float y2, + float x3, + float y3); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_rel_cubic_to (GskPathBuilder *self, + float x1, + float y1, + float x2, + float y2, + float x3, + float y3); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_conic_to (GskPathBuilder *self, + float x1, + float y1, + float x2, + float y2, + float weight); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_rel_conic_to (GskPathBuilder *self, + float x1, + float y1, + float x2, + float y2, + float weight); +GDK_AVAILABLE_IN_4_14 +void gsk_path_builder_close (GskPathBuilder *self); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathBuilder, gsk_path_builder_unref) + +G_END_DECLS diff --git a/gsk/gskpathopprivate.h b/gsk/gskpathopprivate.h new file mode 100644 index 0000000000..593842271e --- /dev/null +++ b/gsk/gskpathopprivate.h @@ -0,0 +1,186 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + + +#pragma once + +#include "gskpath.h" +#include "gskpathbuilder.h" + +G_BEGIN_DECLS + +typedef gpointer gskpathop; + +static inline +gskpathop gsk_pathop_encode (GskPathOperation op, + const graphene_point_t *pts); +static inline +const graphene_point_t *gsk_pathop_points (gskpathop pop); +static inline +GskPathOperation gsk_pathop_op (gskpathop pop); + +static inline +gboolean gsk_pathop_foreach (gskpathop pop, + GskPathForeachFunc func, + gpointer user_data); + +/* included inline so tests can use them */ +static inline +void gsk_path_builder_pathop_to (GskPathBuilder *builder, + gskpathop op); +static inline +void gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder, + gskpathop op); + +/* IMPLEMENTATION */ + +#define GSK_PATHOP_OPERATION_MASK (0x7) + +static inline gskpathop +gsk_pathop_encode (GskPathOperation op, + const graphene_point_t *pts) +{ + /* g_assert (op & GSK_PATHOP_OPERATION_MASK == op); */ + g_assert ((GPOINTER_TO_SIZE (pts) & GSK_PATHOP_OPERATION_MASK) == 0); + + return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pts) | op); +} + +static inline const graphene_point_t * +gsk_pathop_points (gskpathop pop) +{ + return GSIZE_TO_POINTER (GPOINTER_TO_SIZE (pop) & ~GSK_PATHOP_OPERATION_MASK); +} + +static inline +GskPathOperation gsk_pathop_op (gskpathop pop) +{ + return GPOINTER_TO_SIZE (pop) & GSK_PATHOP_OPERATION_MASK; +} + +static inline gboolean +gsk_pathop_foreach (gskpathop pop, + GskPathForeachFunc func, + gpointer user_data) +{ + switch (gsk_pathop_op (pop)) + { + case GSK_PATH_MOVE: + return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 1, 0, user_data); + + case GSK_PATH_CLOSE: + case GSK_PATH_LINE: + return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 2, 0, user_data); + + case GSK_PATH_QUAD: + return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, 0, user_data); + + case GSK_PATH_CUBIC: + return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, 0, user_data); + + case GSK_PATH_CONIC: + { + const graphene_point_t *pts = gsk_pathop_points (pop); + return func (gsk_pathop_op (pop), (graphene_point_t[3]) { pts[0], pts[1], pts[3] }, 3, pts[2].x, user_data); + } + + default: + g_assert_not_reached (); + return TRUE; + } +} + +static inline void +gsk_path_builder_pathop_to (GskPathBuilder *builder, + gskpathop op) +{ + const graphene_point_t *pts = gsk_pathop_points (op); + + switch (gsk_pathop_op (op)) + { + case GSK_PATH_MOVE: + gsk_path_builder_move_to (builder, pts[0].x, pts[0].y); + break; + + case GSK_PATH_CLOSE: + gsk_path_builder_close (builder); + break; + + case GSK_PATH_LINE: + gsk_path_builder_line_to (builder, pts[1].x, pts[1].y); + break; + + case GSK_PATH_QUAD: + gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y); + break; + + case GSK_PATH_CUBIC: + gsk_path_builder_cubic_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y); + break; + + case GSK_PATH_CONIC: + gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[3].x, pts[3].y, pts[2].x); + break; + + default: + g_assert_not_reached (); + break; + } +} + +static inline void +gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder, + gskpathop op) +{ + const graphene_point_t *pts = gsk_pathop_points (op); + + switch (gsk_pathop_op (op)) + { + case GSK_PATH_MOVE: + gsk_path_builder_move_to (builder, pts[0].x, pts[0].y); + break; + + case GSK_PATH_CLOSE: + gsk_path_builder_line_to (builder, pts[0].x, pts[0].y); + break; + + case GSK_PATH_LINE: + gsk_path_builder_line_to (builder, pts[1].x, pts[1].y); + break; + + case GSK_PATH_QUAD: + gsk_path_builder_quad_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y); + break; + + case GSK_PATH_CUBIC: + gsk_path_builder_cubic_to (builder, pts[2].x, pts[2].y, pts[1].x, pts[1].y, pts[0].x, pts[0].y); + break; + + case GSK_PATH_CONIC: + gsk_path_builder_conic_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y, pts[2].x); + break; + + default: + g_assert_not_reached (); + break; + } +} + + +G_END_DECLS diff --git a/gsk/gskpathpoint.c b/gsk/gskpathpoint.c new file mode 100644 index 0000000000..096193d911 --- /dev/null +++ b/gsk/gskpathpoint.c @@ -0,0 +1,179 @@ +/* + * Copyright © 2023 Red Hat, Inc. + * + * 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: Matthias Clasen + */ + +#include "config.h" + +#include "gskpathpointprivate.h" + +#include "gskcontourprivate.h" + +#include "gdk/gdkprivate.h" + + +/** + * GskPathPoint: + * + * `GskPathPoint` is an opaque, immutable type representing a point on a path. + * + * It can be queried for properties of the path at that point, such as its + * tangent or its curvature. + * + * To obtain a `GskPathPoint`, use [method@Gsk.Path.get_closest_point]. + */ + +G_DEFINE_BOXED_TYPE (GskPathPoint, gsk_path_point, + gsk_path_point_ref, + gsk_path_point_unref) + + +GskPathPoint * +gsk_path_point_new (GskPath *path) +{ + GskPathPoint *self; + + self = g_new0 (GskPathPoint, 1); + + self->ref_count = 1; + + self->path = gsk_path_ref (path); + + return self; +} + +GskPath * +gsk_path_point_get_path (GskPathPoint *self) +{ + return self->path; +} + +const GskContour * +gsk_path_point_get_contour (GskPathPoint *self) +{ + return self->contour; +} + +/** + * gsk_path_point_ref: + * @self: a `GskPathPoint` + * + * Increases the reference count of a `GskPathPoint` by one. + * + * Returns: the passed in `GskPathPoint` + * + * Since: 4.14 + */ +GskPathPoint * +gsk_path_point_ref (GskPathPoint *self) +{ + g_return_val_if_fail (self != NULL, NULL); + + self->ref_count++; + + return self; +} + +/** + * gsk_path_point_unref: + * @self: a `GskPathPoint` + * + * Decreases the reference count of a `GskPathPoint` by one. + * + * If the resulting reference count is zero, frees the object. + * + * Since: 4.14 + */ +void +gsk_path_point_unref (GskPathPoint *self) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (self->ref_count > 0); + + self->ref_count--; + if (self->ref_count > 0) + return; + + gsk_path_unref (self->path); + g_free (self); +} + +/** + * gsk_path_point_get_position: + * @self: a `GskPathPoint` + * @position: (out caller-allocates): Return location for + * the coordinates of the point + * + * Gets the position of the point. + * + * Since: 4.14 + */ +void +gsk_path_point_get_position (GskPathPoint *self, + graphene_point_t *position) +{ + gsk_contour_get_position (self->contour, self, position); +} + +/** + * gsk_path_point_get_tangent: + * @self: a `GskPathPoint` + * @direction: the direction for which to return the tangent + * @tangent: (out caller-allocates): Return location for + * the tangent at the point + * + * Gets the tangent of the path at the point. + * + * Note that certain points on a path may not have a single + * tangent, such as sharp turns. At such points, there are + * two tangents -- the direction of the path going into the + * point, and the direction coming out of it. The @direction + * argument lets you choose which one to get. + * + * Since: 4.14 + */ +void +gsk_path_point_get_tangent (GskPathPoint *self, + GskPathDirection direction, + graphene_vec2_t *tangent) +{ + gsk_contour_get_tangent (self->contour, self, direction, tangent); +} + +/** + * gsk_path_point_get_curvature: + * @self: a `GskPathPoint` + * @center: (out caller-allocates): Return location for + * the center of the osculating circle + * + * Calculates the curvature of the path at the point. + * + * Optionally, returns the center of the osculating circle as well. + * + * If the curvature is infinite (at line segments), zero is returned, + * and @center is not modified. + * + * Returns: The curvature of the path at the given point + * + * Since: 4.14 + */ +float +gsk_path_point_get_curvature (GskPathPoint *self, + graphene_point_t *center) +{ + return gsk_contour_get_curvature (self->contour, self, center); +} diff --git a/gsk/gskpathpoint.h b/gsk/gskpathpoint.h new file mode 100644 index 0000000000..2828631f02 --- /dev/null +++ b/gsk/gskpathpoint.h @@ -0,0 +1,56 @@ +/* + * Copyright © 2023 Red Hat, Inc. + * + * 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: Matthias Clasen + */ + +#pragma once + +#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION) +#error "Only can be included directly." +#endif + + +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_PATH_POINT (gsk_path_point_get_type ()) + +GDK_AVAILABLE_IN_4_14 +GType gsk_path_point_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_4_14 +GskPathPoint * gsk_path_point_ref (GskPathPoint *self); +GDK_AVAILABLE_IN_4_14 +void gsk_path_point_unref (GskPathPoint *self); + +GDK_AVAILABLE_IN_4_14 +void gsk_path_point_get_position (GskPathPoint *self, + graphene_point_t *position); + +GDK_AVAILABLE_IN_4_14 +void gsk_path_point_get_tangent (GskPathPoint *self, + GskPathDirection direction, + graphene_vec2_t *tangent); + +GDK_AVAILABLE_IN_4_14 +float gsk_path_point_get_curvature (GskPathPoint *self, + graphene_point_t *center); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathPoint, gsk_path_point_unref) + +G_END_DECLS diff --git a/gsk/gskpathpointprivate.h b/gsk/gskpathpointprivate.h new file mode 100644 index 0000000000..587478244f --- /dev/null +++ b/gsk/gskpathpointprivate.h @@ -0,0 +1,35 @@ +#pragma once + +#include "gskpathpoint.h" +#include "gskcontourprivate.h" + +G_BEGIN_DECLS + +struct _GskPathPoint +{ + guint ref_count; + + GskPath *path; + const GskContour *contour; + + union { + struct { + unsigned int idx; + float t; + } std; + struct { + float distance; + } rect; + struct { + float angle; + } circle; + } data; +}; + +GskPathPoint * gsk_path_point_new (GskPath *path); + +GskPath * gsk_path_point_get_path (GskPathPoint *self); +const GskContour * gsk_path_point_get_contour (GskPathPoint *self); + +G_END_DECLS + diff --git a/gsk/gskpathprivate.h b/gsk/gskpathprivate.h new file mode 100644 index 0000000000..34262a8d6e --- /dev/null +++ b/gsk/gskpathprivate.h @@ -0,0 +1,62 @@ +/* + * Copyright © 2020 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + + +#pragma once + +#include "gskpath.h" + +#include "gskcontourprivate.h" +#include "gskpathopprivate.h" + +G_BEGIN_DECLS + +/* Same as Skia, so looks like a good value. ¯\_(ツ)_/¯ */ +#define GSK_PATH_TOLERANCE_DEFAULT (0.5) + +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); + +GskPathFlags gsk_path_get_flags (GskPath *self); + +gboolean gsk_path_foreach_with_tolerance (GskPath *self, + GskPathForeachFlags flags, + double tolerance, + GskPathForeachFunc func, + gpointer user_data); + + +void gsk_path_builder_add_contour (GskPathBuilder *builder, + GskContour *contour); + +void gsk_path_builder_svg_arc_to (GskPathBuilder *builder, + float rx, + float ry, + float x_axis_rotation, + gboolean large_arc, + gboolean positive_sweep, + float x, + float y); + + +G_END_DECLS + diff --git a/gsk/gskpointprivate.h b/gsk/gskpointprivate.h new file mode 100644 index 0000000000..b72c768876 --- /dev/null +++ b/gsk/gskpointprivate.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +static inline void G_GNUC_PURE +gsk_point_interpolate (const graphene_point_t *p1, + const graphene_point_t *p2, + float t, + graphene_point_t *p) +{ + p->x = p1->x * (1 - t) + p2->x * t; + p->Y = p1->y * (1 - t) + p2->y * t; +} + +static inline float G_GNUC_PURE +gsk_point_distance (const graphene_point_t *p1, + const graphene_point_t *p2) +{ + return sqrtf ((p1->x - p2->x)*(p1->x - p2->x) + (p1->y - p2->y)*(p1->y - p2->y)); +} diff --git a/gsk/gskspline.c b/gsk/gskspline.c new file mode 100644 index 0000000000..5590c7f7e0 --- /dev/null +++ b/gsk/gskspline.c @@ -0,0 +1,208 @@ +/* + * Copyright © 2002 University of Southern California + * 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 + * Carl D. Worth + */ + +#include "config.h" + +#include "gsksplineprivate.h" + +#include + +/* Spline deviation from the circle in radius would be given by: + + error = sqrt (x**2 + y**2) - 1 + + A simpler error function to work with is: + + e = x**2 + y**2 - 1 + + From "Good approximation of circles by curvature-continuous Bezier + curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric + Design 8 (1990) 22-41, we learn: + + abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4) + + and + abs (error) =~ 1/2 * e + + Of course, this error value applies only for the particular spline + approximation that is used in _cairo_gstate_arc_segment. +*/ +static float +arc_error_normalized (float angle) +{ + return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2); +} + +static float +arc_max_angle_for_tolerance_normalized (float tolerance) +{ + float angle, error; + guint i; + + /* Use table lookup to reduce search time in most cases. */ + struct { + float angle; + float error; + } table[] = { + { G_PI / 1.0, 0.0185185185185185036127 }, + { G_PI / 2.0, 0.000272567143730179811158 }, + { G_PI / 3.0, 2.38647043651461047433e-05 }, + { G_PI / 4.0, 4.2455377443222443279e-06 }, + { G_PI / 5.0, 1.11281001494389081528e-06 }, + { G_PI / 6.0, 3.72662000942734705475e-07 }, + { G_PI / 7.0, 1.47783685574284411325e-07 }, + { G_PI / 8.0, 6.63240432022601149057e-08 }, + { G_PI / 9.0, 3.2715520137536980553e-08 }, + { G_PI / 10.0, 1.73863223499021216974e-08 }, + { G_PI / 11.0, 9.81410988043554039085e-09 }, + }; + + for (i = 0; i < G_N_ELEMENTS (table); i++) + { + if (table[i].error < tolerance) + return table[i].angle; + } + + i++; + do { + angle = G_PI / i++; + error = arc_error_normalized (angle); + } while (error > tolerance); + + return angle; +} + +static guint +arc_segments_needed (float angle, + float radius, + float tolerance) +{ + float max_angle; + + /* the error is amplified by at most the length of the + * major axis of the circle; see cairo-pen.c for a more detailed analysis + * of this. */ + max_angle = arc_max_angle_for_tolerance_normalized (tolerance / radius); + + return ceil (fabs (angle) / max_angle); +} + +/* We want to draw a single spline approximating a circular arc radius + R from angle A to angle B. Since we want a symmetric spline that + matches the endpoints of the arc in position and slope, we know + that the spline control points must be: + + (R * cos(A), R * sin(A)) + (R * cos(A) - h * sin(A), R * sin(A) + h * cos (A)) + (R * cos(B) + h * sin(B), R * sin(B) - h * cos (B)) + (R * cos(B), R * sin(B)) + + for some value of h. + + "Approximation of circular arcs by cubic polynomials", Michael + Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides + various values of h along with error analysis for each. + + From that paper, a very practical value of h is: + + h = 4/3 * R * tan(angle/4) + + This value does not give the spline with minimal error, but it does + provide a very good approximation, (6th-order convergence), and the + error expression is quite simple, (see the comment for + _arc_error_normalized). +*/ +static gboolean +gsk_spline_decompose_arc_segment (const graphene_point_t *center, + float radius, + float angle_A, + float angle_B, + GskSplineAddCurveFunc curve_func, + gpointer user_data) +{ + float r_sin_A, r_cos_A; + float r_sin_B, r_cos_B; + float h; + + r_sin_A = radius * sin (angle_A); + r_cos_A = radius * cos (angle_A); + r_sin_B = radius * sin (angle_B); + r_cos_B = radius * cos (angle_B); + + h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0); + + return curve_func ((graphene_point_t[4]) { + GRAPHENE_POINT_INIT ( + center->x + r_cos_A, + center->y + r_sin_A + ), + GRAPHENE_POINT_INIT ( + center->x + r_cos_A - h * r_sin_A, + center->y + r_sin_A + h * r_cos_A + ), + GRAPHENE_POINT_INIT ( + center->x + r_cos_B + h * r_sin_B, + center->y + r_sin_B - h * r_cos_B + ), + GRAPHENE_POINT_INIT ( + center->x + r_cos_B, + center->y + r_sin_B + ) + }, + user_data); +} + +gboolean +gsk_spline_decompose_arc (const graphene_point_t *center, + float radius, + float tolerance, + float start_angle, + float end_angle, + GskSplineAddCurveFunc curve_func, + gpointer user_data) +{ + float step = start_angle - end_angle; + guint i, n_segments; + + /* Recurse if drawing arc larger than pi */ + if (ABS (step) > G_PI) + { + float mid_angle = (start_angle + end_angle) / 2.0; + + return gsk_spline_decompose_arc (center, radius, tolerance, start_angle, mid_angle, curve_func, user_data) + && gsk_spline_decompose_arc (center, radius, tolerance, mid_angle, end_angle, curve_func, user_data); + } + else if (ABS (step) < tolerance) + { + return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data); + } + + n_segments = arc_segments_needed (ABS (step), radius, tolerance); + step = (end_angle - start_angle) / n_segments; + + for (i = 0; i < n_segments - 1; i++, start_angle += step) + { + if (!gsk_spline_decompose_arc_segment (center, radius, start_angle, start_angle + step, curve_func, user_data)) + return FALSE; + } + return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data); +} + diff --git a/gsk/gsksplineprivate.h b/gsk/gsksplineprivate.h new file mode 100644 index 0000000000..c9da32734c --- /dev/null +++ b/gsk/gsksplineprivate.h @@ -0,0 +1,41 @@ +/* + * 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_SPLINE_PRIVATE_H__ +#define __GSK_SPLINE_PRIVATE_H__ + +#include "gskpath.h" + +G_BEGIN_DECLS + +typedef gboolean (* GskSplineAddCurveFunc) (const graphene_point_t curve[4], + gpointer user_data); +gboolean gsk_spline_decompose_arc (const graphene_point_t *center, + float radius, + float tolerance, + float start_angle, + float end_angle, + GskSplineAddCurveFunc curve_func, + gpointer user_data); + +G_END_DECLS + +#endif /* __GSK_SPLINE_PRIVATE_H__ */ + diff --git a/gsk/gsktypes.h b/gsk/gsktypes.h index acc8bc841b..656513c39c 100644 --- a/gsk/gsktypes.h +++ b/gsk/gsktypes.h @@ -25,6 +25,9 @@ #include #include +typedef struct _GskPath GskPath; +typedef struct _GskPathBuilder GskPathBuilder; +typedef struct _GskPathPoint GskPathPoint; typedef struct _GskRenderer GskRenderer; typedef struct _GskTransform GskTransform; diff --git a/gsk/meson.build b/gsk/meson.build index 489024572a..7072fc8b5a 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -23,9 +23,12 @@ gsk_private_gl_shaders = [ ] gsk_public_sources = files([ - 'gskdiff.c', 'gskcairorenderer.c', + 'gskdiff.c', 'gskglshader.c', + 'gskpath.c', + 'gskpathbuilder.c', + 'gskpathpoint.c', 'gskrenderer.c', 'gskrendernode.c', 'gskrendernodeimpl.c', @@ -37,9 +40,12 @@ gsk_public_sources = files([ gsk_private_sources = files([ 'gskcairoblur.c', + 'gskcontour.c', + 'gskcurve.c', 'gskdebug.c', 'gskprivate.c', 'gskprofiler.c', + 'gskspline.c', 'gl/gskglattachmentstate.c', 'gl/gskglbuffer.c', 'gl/gskglcommandqueue.c', @@ -66,6 +72,9 @@ gsk_public_headers = files([ 'gskcairorenderer.h', 'gskenums.h', 'gskglshader.h', + 'gskpath.h', + 'gskpathbuilder.h', + 'gskpathpoint.h', 'gskrenderer.h', 'gskrendernode.h', 'gskroundedrect.h',