mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 22:20:24 +00:00
1b5dfcba7e
This commit adds the basic infrastructure for paths. The public APIs consists of GskPath, GskPathPoint and GskPathBuilder. GskPath is a data structure for paths that consists of contours, which in turn might contain Bézier curves. The Bezier data structure is inspired by Skia, with separate arrays for points and operations. One advantage of this arrangement is that start and end points are shared between adjacent curves. A GskPathPoint represents a point on a path, which can be queried for various properties. GskPathBuilder is an auxiliary builder object for paths.
636 lines
18 KiB
C
636 lines
18 KiB
C
/*
|
|
* Copyright © 2020 Benjamin Otte
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Authors: Benjamin Otte <otte@gnome.org>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "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,
|
|
GskBoundingBox *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,
|
|
GskRealPathPoint *result,
|
|
float *out_dist);
|
|
void (* get_position) (const GskContour *contour,
|
|
GskRealPathPoint *point,
|
|
graphene_point_t *position);
|
|
void (* get_tangent) (const GskContour *contour,
|
|
GskRealPathPoint *point,
|
|
GskPathDirection direction,
|
|
graphene_vec2_t *tangent);
|
|
float (* get_curvature) (const GskContour *contour,
|
|
GskRealPathPoint *point,
|
|
graphene_point_t *center);
|
|
};
|
|
|
|
/* {{{ 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,
|
|
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);
|
|
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;
|
|
|
|
default:
|
|
g_assert_not_reached();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gsk_standard_contour_get_bounds (const GskContour *contour,
|
|
GskBoundingBox *bounds)
|
|
{
|
|
const GskStandardContour *self = (const GskStandardContour *) contour;
|
|
gsize i;
|
|
|
|
if (self->n_points == 0)
|
|
return FALSE;
|
|
|
|
gsk_bounding_box_init (bounds, &self->points[0], &self->points[0]);
|
|
for (i = 1; i < self->n_points; i ++)
|
|
gsk_bounding_box_expand (bounds, &self->points[i]);
|
|
|
|
return bounds->max.x > bounds->min.x && bounds->max.y > bounds->min.y;
|
|
}
|
|
|
|
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,
|
|
GskRealPathPoint *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,
|
|
GskRealPathPoint *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,
|
|
GskRealPathPoint *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,
|
|
GskRealPathPoint *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;
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ 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,
|
|
GskBoundingBox *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,
|
|
GskRealPathPoint *result,
|
|
float *out_dist)
|
|
{
|
|
return self->klass->get_closest_point (self, point, threshold, result, out_dist);
|
|
}
|
|
|
|
void
|
|
gsk_contour_get_position (const GskContour *self,
|
|
GskRealPathPoint *point,
|
|
graphene_point_t *pos)
|
|
{
|
|
self->klass->get_position (self, point, pos);
|
|
}
|
|
|
|
void
|
|
gsk_contour_get_tangent (const GskContour *self,
|
|
GskRealPathPoint *point,
|
|
GskPathDirection direction,
|
|
graphene_vec2_t *tangent)
|
|
{
|
|
self->klass->get_tangent (self, point, direction, tangent);
|
|
}
|
|
|
|
float
|
|
gsk_contour_get_curvature (const GskContour *self,
|
|
GskRealPathPoint *point,
|
|
graphene_point_t *center)
|
|
{
|
|
return self->klass->get_curvature (self, point, center);
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* vim:set foldmethod=marker expandtab: */
|