gtk/gsk/gskcontour.c
Simon McVittie 214f5a6f98 gskpathop: Introduce a type to represent an aligned graphene_point_t
When we allocate a graphene_point_t on the stack, there's no guarantee
that it will be aligned at an 8-byte boundary, which is an assumption
made by gsk_pathop_encode() (which wants to use the lowest 3 bits to
encode the operation). In the places where it matters, force the
points on the stack and embedded in structs to be nicely aligned.

By using a distinct type for this (a union with a suitable size and
alignment), we ensure that the compiler will warn or error whenever we
can't prove that a particular point is, in fact, suitably aligned.
We can go from a `GskAlignedPoint *` to a `graphene_point_t *`
(which is always valid, because the `GskAlignedPoint` is aligned)
via &aligned_points[0].pt, but we cannot go back the other way
(which is not always valid, because the `graphene_point_t` is not
necessarily aligned nicely) without a cast.

In practice, it seems that a graphene_point_t on x86_64 *is* usually
placed at an 8-byte boundary, but this is not the case on 32-bit
architectures or on s390x.

In many cases we can avoid needing an explicit reference to the more
complicated type by making use of a transparent union. There's already
at least one transparent union in GSK's public API, so it's presumably
portable enough to match GTK's requirements.

Increasing the alignment of GskAlignedPoint also requires adjusting how
a GskStandardContour is allocated and initialized. This data structure
allocates extra memory to hold an array of GskAlignedPoint outside the
bounds of the struct itself, and that array now needs to be aligned
suitably. Previously the array started with at next byte after the
flexible array of gskpathop, but the alignment of a gskpathop is only
4 bytes on 32-bit architectures, so depending on the number of gskpathop
in the trailing flexible array, that pointer might be an unsuitable
location to allocate a GskAlignedPoint.

Resolves: https://gitlab.gnome.org/GNOME/gtk/-/issues/6395
Signed-off-by: Simon McVittie <smcv@debian.org>
2024-07-28 17:31:41 +01:00

2655 lines
76 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 "gskpathpoint.h"
#include "gskstrokeprivate.h"
#include <float.h>
/* This is C11 */
#ifndef FLT_DECIMAL_DIG
#define FLT_DECIMAL_DIG 9
#endif
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);
gboolean (* get_stroke_bounds) (const GskContour *contour,
const GskStroke *stroke,
GskBoundingBox *bounds);
gboolean (* foreach) (const GskContour *contour,
GskPathForeachFunc func,
gpointer user_data);
GskContour * (* reverse) (const GskContour *contour);
int (* get_winding) (const GskContour *contour,
const graphene_point_t *point);
gsize (* get_n_ops) (const GskContour *contour);
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,
const GskPathPoint *point,
graphene_point_t *position);
void (* get_tangent) (const GskContour *contour,
const GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent);
float (* get_curvature) (const GskContour *contour,
const GskPathPoint *point,
GskPathDirection direction,
graphene_point_t *center);
void (* add_segment) (const GskContour *contour,
GskPathBuilder *builder,
gboolean emit_move_to,
const GskPathPoint *start,
const GskPathPoint *end);
gpointer (* init_measure) (const GskContour *contour,
float tolerance,
float *out_length);
void (* free_measure) (const GskContour *contour,
gpointer measure_data);
void (* get_point) (const GskContour *contour,
gpointer measure_data,
float distance,
GskPathPoint *result);
float (* get_distance) (const GskContour *contour,
const GskPathPoint *point,
gpointer measure_data);
};
/* {{{ Utilities */
#define DEG_TO_RAD(x) ((x) * (G_PI / 180.f))
#define RAD_TO_DEG(x) ((x) / (G_PI / 180.f))
static inline void
_sincosf (float angle,
float *out_s,
float *out_c)
{
#ifdef HAVE_SINCOSF
sincosf (angle, out_s, out_c);
#else
*out_s = sinf (angle);
*out_c = cosf (angle);
#endif
}
static void
_g_string_append_float (GString *string,
const char *prefix,
float f)
{
char buf[G_ASCII_DTOSTR_BUF_SIZE];
g_string_append (string, prefix);
g_ascii_formatd (buf, G_ASCII_DTOSTR_BUF_SIZE, "%.9g", f);
g_string_append (string, buf);
}
static void
_g_string_append_point (GString *string,
const char *prefix,
const graphene_point_t *pt)
{
_g_string_append_float (string, prefix, pt->x);
_g_string_append_float (string, " ", pt->y);
}
static gboolean
add_segment (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)
{
GskPathBuilder *builder;
builder = gsk_path_builder_new ();
gsk_contour_foreach (contour, add_segment, builder);
return gsk_path_builder_free_to_path (builder);
}
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
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, init_curve_cb, &data);
}
typedef struct
{
graphene_point_t point;
float threshold;
gsize idx;
gsize best_idx;
gsize best_t;
} ClosestPointData;
static gboolean
get_closest_point_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GskCurve curve;
ClosestPointData *pd = data;
float distance, t;
if (op == GSK_PATH_MOVE)
return TRUE;
pd->idx++;
gsk_curve_init_foreach (&curve, op, pts, n_pts, weight);
if (gsk_curve_get_closest_point (&curve, &pd->point, pd->threshold, &distance, &t) &&
distance < pd->threshold)
{
pd->best_idx = pd->idx;
pd->best_t = t;
pd->threshold = distance;
}
return TRUE;
}
static gboolean
contour_get_closest_point (const GskContour *contour,
const graphene_point_t *point,
float threshold,
GskPathPoint *result,
float *out_dist)
{
ClosestPointData pd;
pd.point = *point;
pd.threshold = threshold;
pd.idx = 0;
pd.best_idx = G_MAXUINT;
gsk_contour_foreach (contour, get_closest_point_cb, &pd);
if (pd.best_idx != G_MAXUINT)
{
result->idx = pd.best_idx;
result->t = pd.best_t;
*out_dist = pd.threshold;
return TRUE;
}
return FALSE;
}
static void
add_curve (GskCurve *curve,
GskPathBuilder *builder,
gboolean *emit_move_to)
{
if (*emit_move_to)
{
const graphene_point_t *s;
s = gsk_curve_get_start_point (curve);
gsk_path_builder_move_to (builder, s->x, s->y);
*emit_move_to = FALSE;
}
gsk_curve_builder_to (curve, builder);
}
typedef struct
{
GskPathBuilder *builder;
gboolean emit_move_to;
GskPathPoint start;
GskPathPoint end;
gsize idx;
} AddSegmentData;
static gboolean
add_segment_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
AddSegmentData *sd = data;
GskCurve c, c1, c2;
if (op == GSK_PATH_MOVE)
return TRUE;
sd->idx++;
if (sd->start.idx > sd->idx)
return TRUE;
if (sd->end.idx < sd->idx)
return FALSE;
if (op == GSK_PATH_CLOSE)
op = GSK_PATH_LINE;
gsk_curve_init_foreach (&c, op, pts, n_pts, weight);
if (sd->start.idx == sd->idx)
{
if (sd->end.idx == sd->idx)
{
gsk_curve_segment (&c, sd->start.t, sd->end.t, &c1);
add_curve (&c1, sd->builder, &sd->emit_move_to);
return FALSE;
}
else
{
gsk_curve_split (&c, sd->start.t, &c1, &c2);
add_curve (&c2, sd->builder, &sd->emit_move_to);
return TRUE;
}
}
else
{
if (sd->end.idx == sd->idx)
{
gsk_curve_split (&c, sd->end.t, &c1, &c2);
add_curve (&c1, sd->builder, &sd->emit_move_to);
return FALSE;
}
else
{
add_curve (&c, sd->builder, &sd->emit_move_to);
return TRUE;
}
}
}
static void
contour_add_segment (const GskContour *contour,
GskPathBuilder *builder,
gboolean emit_move_to,
const GskPathPoint *start,
const GskPathPoint *end)
{
AddSegmentData sd;
sd.builder = builder;
sd.emit_move_to = emit_move_to;
sd.start = *start;
sd.end = *end;
sd.idx = 0;
gsk_contour_foreach (contour, add_segment_cb, &sd);
}
static inline gboolean
maybe_emit_line (const graphene_point_t pts[2],
GskPathForeachFunc func,
gpointer user_data)
{
if (graphene_point_equal (&pts[0], &pts[1]))
return TRUE;
return func (GSK_PATH_LINE, pts, 2, 0.f, user_data);
}
static inline gboolean
maybe_emit_conic (const graphene_point_t pts[3],
float weight,
GskPathForeachFunc func,
gpointer user_data)
{
if (graphene_point_equal (&pts[0], &pts[1]))
{
if (graphene_point_equal (&pts[1], &pts[2]))
return TRUE;
else
return func (GSK_PATH_LINE, &pts[1], 2, 0.f, user_data);
}
else if (graphene_point_equal (&pts[1], &pts[2]))
return func (GSK_PATH_LINE, &pts[0], 2, 0.f, user_data);
return func (GSK_PATH_CONIC, pts, 3, weight, user_data);
}
/* Assumes a closed contour */
static void
apply_corner_direction (GskPathDirection direction,
gsize *idx,
float *t,
gsize n_ops)
{
if (*t == 0 &&
(direction == GSK_PATH_FROM_START || direction == GSK_PATH_TO_START))
{
if (*idx > 1)
*idx = *idx - 1;
else
*idx = n_ops - 1;
*t = 1;
}
else if (*t == 1 &&
(direction == GSK_PATH_FROM_END || direction == GSK_PATH_TO_END))
{
if (*idx < n_ops - 1)
*idx = *idx + 1;
else
*idx = 1;
*t = 0;
}
}
/* }}} */
/* {{{ Default implementations */
static gsize
gsk_contour_get_size_default (const GskContour *contour)
{
return contour->klass->struct_size;
}
static gboolean
foreach_print (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer data)
{
GString *string = data;
switch (op)
{
case GSK_PATH_MOVE:
_g_string_append_point (string, "M ", &pts[0]);
break;
case GSK_PATH_CLOSE:
g_string_append (string, " Z");
break;
case GSK_PATH_LINE:
_g_string_append_point (string, " L ", &pts[1]);
break;
case GSK_PATH_QUAD:
_g_string_append_point (string, " Q ", &pts[1]);
_g_string_append_point (string, ", ", &pts[2]);
break;
case GSK_PATH_CUBIC:
_g_string_append_point (string, " C ", &pts[1]);
_g_string_append_point (string, ", ", &pts[2]);
_g_string_append_point (string, ", ", &pts[3]);
break;
case GSK_PATH_CONIC:
_g_string_append_point (string, " O ", &pts[1]);
_g_string_append_point (string, ", ", &pts[2]);
_g_string_append_float (string, ", ", weight);
break;
default:
g_assert_not_reached ();
}
return TRUE;
}
static void
gsk_contour_print_default (const GskContour *contour,
GString *string)
{
gsk_contour_foreach (contour, foreach_print, string);
}
/* }}} */
/* {{{ Standard */
typedef struct _GskStandardContour GskStandardContour;
struct _GskStandardContour
{
GskContour contour;
GskPathFlags flags;
GskBoundingBox bounds;
gsize n_ops;
gsize n_points;
GskAlignedPoint *points;
gskpathop ops[];
};
static gsize
gsk_standard_contour_compute_size (gsize n_ops,
gsize n_points)
{
const gsize point_align = G_ALIGNOF (GskAlignedPoint);
const gsize align = MAX (G_ALIGNOF (GskAlignedPoint),
MAX (G_ALIGNOF (gpointer),
G_ALIGNOF (GskStandardContour)));
gsize s = sizeof (GskStandardContour);
s += sizeof (gskpathop) * n_ops;
/* The array of points needs to be 8-byte aligned, but on 32-bit,
* a single entry in ops might only be 4 bytes, so we might need
* 4 bytes of padding before starting the array of points */
s += (point_align - (s % point_align));
s += sizeof (GskAlignedPoint) * n_points;
return s + (align - (s % align));
}
static void
gsk_standard_contour_init (GskContour *contour,
GskPathFlags flags,
const GskAlignedPoint *points,
gsize n_points,
const gskpathop *ops,
gsize n_ops,
gssize 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,
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].pt.x,
self->points[self->n_points - 1].pt.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 gboolean
gsk_standard_contour_get_bounds (const GskContour *contour,
GskBoundingBox *bounds)
{
const GskStandardContour *self = (const GskStandardContour *) contour;
if (self->n_points == 0)
return FALSE;
*bounds = self->bounds;
return bounds->max.x > bounds->min.x && bounds->max.y > bounds->min.y;
}
static gboolean
gsk_standard_contour_get_stroke_bounds (const GskContour *contour,
const GskStroke *stroke,
GskBoundingBox *bounds)
{
GskStandardContour *self = (GskStandardContour *) contour;
float extra;
if (self->n_points == 0)
return FALSE;
extra = MAX (stroke->line_width, gsk_stroke_get_join_width (stroke));
gsk_bounding_box_init (bounds, &GRAPHENE_POINT_INIT (self->bounds.min.x - extra,
self->bounds.min.y - extra),
&GRAPHENE_POINT_INIT (self->bounds.max.x + extra,
self->bounds.max.y + extra));
return TRUE;
}
static int
gsk_standard_contour_get_winding (const GskContour *contour,
const graphene_point_t *point)
{
GskStandardContour *self = (GskStandardContour *) contour;
int winding = 0;
if (!gsk_bounding_box_contains_point (&self->bounds, point))
return 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 GskAlignedPoint[]) { self->points[self->n_points - 1],
self->points[0] }));
winding += gsk_curve_get_crossing (&c, point);
}
return winding;
}
static gsize
gsk_standard_contour_get_n_ops (const GskContour *contour)
{
GskStandardContour *self = (GskStandardContour *) contour;
return self->n_ops;
}
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].pt, NULL, NULL);
if (dist <= threshold)
{
*out_dist = dist;
result->idx = 0;
result->t = 1;
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) &&
distance < threshold)
{
best_idx = i;
best_t = t;
threshold = distance;
}
}
if (best_idx != G_MAXUINT)
{
*out_dist = threshold;
result->idx = best_idx;
result->t = best_t;
return TRUE;
}
return FALSE;
}
static void
gsk_standard_contour_get_position (const GskContour *contour,
const GskPathPoint *point,
graphene_point_t *position)
{
GskStandardContour *self = (GskStandardContour *) contour;
GskCurve curve;
if (G_UNLIKELY (point->idx == 0))
{
*position = self->points[0].pt;
return;
}
gsk_curve_init (&curve, self->ops[point->idx]);
gsk_curve_get_point (&curve, point->t, position);
}
static void
gsk_standard_contour_get_tangent (const GskContour *contour,
const GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
GskStandardContour *self = (GskStandardContour *) contour;
GskCurve curve;
gsize idx;
float t;
if (G_UNLIKELY (point->idx == 0))
{
graphene_vec2_init (tangent, 0, 0);
return;
}
idx = point->idx;
t = point->t;
if (t == 0 && (direction == GSK_PATH_FROM_START ||
direction == GSK_PATH_TO_START))
{
/* Look at the previous segment */
if (idx > 1)
{
idx--;
t = 1;
}
else if (self->flags & GSK_PATH_CLOSED)
{
idx = self->n_ops - 1;
t = 1;
}
}
else if (t == 1 && (direction == GSK_PATH_TO_END ||
direction == GSK_PATH_FROM_END))
{
/* Look at the next segment */
if (idx < self->n_ops - 1)
{
idx++;
t = 0;
}
else if (self->flags & GSK_PATH_CLOSED)
{
idx = 1;
t = 0;
}
}
gsk_curve_init (&curve, self->ops[idx]);
gsk_curve_get_tangent (&curve, t, tangent);
if (direction == GSK_PATH_TO_START || direction == GSK_PATH_FROM_END)
graphene_vec2_negate (tangent, tangent);
}
static float
gsk_standard_contour_get_curvature (const GskContour *contour,
const GskPathPoint *point,
GskPathDirection direction,
graphene_point_t *center)
{
GskStandardContour *self = (GskStandardContour *) contour;
GskCurve curve;
gsize idx;
float t;
if (G_UNLIKELY (point->idx == 0))
return 0;
idx = point->idx;
t = point->t;
if (t == 0 && idx > 1 &&
(direction == GSK_PATH_FROM_START || direction == GSK_PATH_TO_START))
{
idx--;
t = 1;
}
else if (t == 1 && idx + 1 < self->n_ops &&
(direction == GSK_PATH_FROM_END || direction == GSK_PATH_TO_END))
{
idx++;
t = 0;
}
gsk_curve_init (&curve, self->ops[idx]);
return gsk_curve_get_curvature (&curve, t, center);
}
static void
gsk_standard_contour_add_segment (const GskContour *contour,
GskPathBuilder *builder,
gboolean emit_move_to,
const GskPathPoint *start,
const GskPathPoint *end)
{
GskStandardContour *self = (GskStandardContour *) contour;
GskCurve c, c1, c2;
g_assert (start->idx < self->n_ops);
g_assert (end->idx < self->n_ops);
gsk_curve_init (&c, self->ops[start->idx]);
if (start->idx == end->idx)
{
gsk_curve_segment (&c, start->t, end->t, &c1);
add_curve (&c1, builder, &emit_move_to);
return;
}
if (start->t == 0)
{
add_curve (&c, builder, &emit_move_to);
}
else if (start->t < 1)
{
gsk_curve_split (&c, start->t, &c1, &c2);
add_curve (&c2, builder, &emit_move_to);
}
for (gsize i = start->idx + 1; i < end->idx; i++)
{
gsk_curve_init (&c, self->ops[i]);
add_curve (&c, builder, &emit_move_to);
}
gsk_curve_init (&c, self->ops[end->idx]);
if (c.op == GSK_PATH_CLOSE)
c.op = GSK_PATH_LINE;
if (end->t == 1)
{
add_curve (&c, builder, &emit_move_to);
}
else if (end->t > 0)
{
gsk_curve_split (&c, end->t, &c1, &c2);
add_curve (&c1, builder, &emit_move_to);
}
}
typedef struct
{
gsize idx;
float length0;
float length1;
gsize n_samples;
gsize first;
} CurveMeasure;
typedef struct
{
float t;
float length;
} CurvePoint;
typedef struct
{
GArray *curves;
GArray *points;
float tolerance;
} GskStandardContourMeasure;
static void
add_measure (const GskCurve *curve,
gsize idx,
float length,
float tolerance,
float t1,
float l1,
GArray *array)
{
GskCurve c;
float ll, l0;
float t0;
CurvePoint *p;
/* Check if we can add (t1, length + l1) without further
* splitting. We check two things:
* - Is the curve close to a straight line (length-wise) ?
* - Does the roundtrip length<>t not deviate too much ?
*/
if (curve->op == GSK_PATH_LINE ||
curve->op == GSK_PATH_CLOSE)
goto done;
p = &g_array_index (array, CurvePoint, array->len - 1);
t0 = (p->t + t1) / 2;
if (t0 == p->t || t0 == t1)
goto done;
gsk_curve_split (curve, t0, &c, NULL);
l0 = gsk_curve_get_length (&c);
ll = (p->length + length + l1) / 2;
if (fabsf (length + l0 - ll) < tolerance)
{
done:
g_array_append_val (array, ((CurvePoint) { t1, length + l1 }));
}
else
{
add_measure (curve, idx, length, tolerance, t0, l0, array);
add_measure (curve, idx, length, tolerance, t1, l1, array);
}
}
static int
cmpfloat (const void *p1, const void *p2)
{
const float *f1 = p1;
const float *f2 = p2;
return *f1 < *f2 ? -1 : (*f1 > *f2 ? 1 : 0);
}
static void
add_samples (const GskStandardContour *self,
GskStandardContourMeasure *measure,
CurveMeasure *curve_measure)
{
gsize first;
GskCurve curve;
float l0, l1;
float t[3];
int n;
g_assert (curve_measure->n_samples == 0);
g_assert (0 < curve_measure->idx && curve_measure->idx < self->n_ops);
first = measure->points->len;
l0 = curve_measure->length0;
l1 = curve_measure->length1;
g_array_append_val (measure->points, ((CurvePoint) { 0, l0 } ));
gsk_curve_init (&curve, self->ops[curve_measure->idx]);
n = gsk_curve_get_curvature_points (&curve, t);
qsort (t, n, sizeof (float), cmpfloat);
for (int j = 0; j < n; j++)
{
float l = gsk_curve_get_length_to (&curve, t[j]);
add_measure (&curve, curve_measure->idx, l0, measure->tolerance, t[j], l, measure->points);
}
add_measure (&curve, curve_measure->idx, l0, measure->tolerance, 1, l1 - l0, measure->points);
curve_measure->first = first;
curve_measure->n_samples = measure->points->len - first;
}
static void
ensure_samples (const GskStandardContour *self,
GskStandardContourMeasure *measure,
CurveMeasure *curve_measure)
{
if (curve_measure->n_samples == 0)
add_samples (self, measure, curve_measure);
}
static gpointer
gsk_standard_contour_init_measure (const GskContour *contour,
float tolerance,
float *out_length)
{
const GskStandardContour *self = (const GskStandardContour *) contour;
GskStandardContourMeasure *measure;
float length;
measure = g_new (GskStandardContourMeasure, 1);
measure->curves = g_array_new (FALSE, FALSE, sizeof (CurveMeasure));
measure->points = g_array_new (FALSE, FALSE, sizeof (CurvePoint));
measure->tolerance = tolerance;
/* Add a placeholder for the move, so indexes match up */
g_array_append_val (measure->curves, ((CurveMeasure) { 0, -1, -1, 0, 0 } ));
length = 0;
for (gsize i = 1; i < self->n_ops; i++)
{
GskCurve curve;
float l;
gsk_curve_init (&curve, self->ops[i]);
l = gsk_curve_get_length (&curve);
g_array_append_val (measure->curves, ((CurveMeasure) { i, length, length + l, 0, 0 } ));
length += l;
}
*out_length = length;
return measure;
}
static void
gsk_standard_contour_free_measure (const GskContour *contour,
gpointer data)
{
GskStandardContourMeasure *measure = data;
g_array_free (measure->curves, TRUE);
g_array_free (measure->points, TRUE);
g_free (measure);
}
static int
find_curve (gconstpointer a,
gconstpointer b)
{
const CurveMeasure *m = a;
const float distance = *(const float *) b;
if (distance < m->length0)
return 1;
else if (distance > m->length1)
return -1;
else
return 0;
}
static void
gsk_standard_contour_get_point (const GskContour *contour,
gpointer measure_data,
float distance,
GskPathPoint *result)
{
const GskStandardContour *self = (const GskStandardContour *) contour;
GskStandardContourMeasure *measure = measure_data;
CurveMeasure *curve_measure;
gboolean found G_GNUC_UNUSED;
guint idx;
gsize i0, i1;
CurvePoint *p0, *p1;
if (self->n_ops == 1)
{
result->idx = 0;
result->t = 1;
return;
}
found = g_array_binary_search (measure->curves, &distance, find_curve, &idx);
g_assert (found);
curve_measure = &g_array_index (measure->curves, CurveMeasure, idx);
ensure_samples (self, measure, curve_measure);
i0 = curve_measure->first;
i1 = curve_measure->first + curve_measure->n_samples - 1;
while (i0 + 1 < i1)
{
gsize i = (i0 + i1) / 2;
CurvePoint *p = &g_array_index (measure->points, CurvePoint, i);
if (p->length < distance)
i0 = i;
else if (p->length > distance)
i1 = i;
else
{
result->idx = curve_measure->idx;
result->t = p->t;
g_assert (0 <= result->t && result->t <= 1);
return;
}
}
p0 = &g_array_index (measure->points, CurvePoint, i0);
p1 = &g_array_index (measure->points, CurvePoint, i1);
if (distance >= p1->length)
{
if (curve_measure->idx == self->n_ops - 1)
{
result->idx = curve_measure->idx;
result->t = 1;
}
else
{
result->idx = curve_measure->idx + 1;
result->t = 0;
}
}
else
{
float fraction;
result->idx = curve_measure->idx;
fraction = (distance - p0->length) / (p1->length - p0->length);
g_assert (fraction >= 0 && fraction <= 1);
result->t = p0->t * (1 - fraction) + p1->t * fraction;
g_assert (result->t >= 0 && result->t <= 1);
}
}
static float
gsk_standard_contour_get_distance (const GskContour *contour,
const GskPathPoint *point,
gpointer measure_data)
{
const GskStandardContour *self = (const GskStandardContour *) contour;
GskStandardContourMeasure *measure = measure_data;
CurveMeasure *curve_measure;
gsize i0, i1;
CurvePoint *p0, *p1;
float fraction;
if (G_UNLIKELY (point->idx == 0))
return 0;
curve_measure = &g_array_index (measure->curves, CurveMeasure, point->idx);
ensure_samples (self, measure, curve_measure);
i0 = curve_measure->first;
i1 = curve_measure->first + curve_measure->n_samples - 1;
while (i0 + 1 < i1)
{
gsize i = (i0 + i1) / 2;
CurvePoint *p = &g_array_index (measure->points, CurvePoint, i);
if (p->t > point->t)
i1 = i;
else if (p->t < point->t)
i0 = i;
else
return p->length;
}
p0 = &g_array_index (measure->points, CurvePoint, i0);
p1 = &g_array_index (measure->points, CurvePoint, i1);
g_assert (p0->t <= point->t && point->t <= p1->t);
fraction = (point->t - p0->t) / (p1->t - p0->t);
g_assert (fraction >= 0 && fraction <= 1);
return p0->length * (1 - fraction) + p1->length * fraction;
}
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_contour_print_default,
gsk_standard_contour_get_bounds,
gsk_standard_contour_get_stroke_bounds,
gsk_standard_contour_foreach,
gsk_standard_contour_reverse,
gsk_standard_contour_get_winding,
gsk_standard_contour_get_n_ops,
gsk_standard_contour_get_closest_point,
gsk_standard_contour_get_position,
gsk_standard_contour_get_tangent,
gsk_standard_contour_get_curvature,
gsk_standard_contour_add_segment,
gsk_standard_contour_init_measure,
gsk_standard_contour_free_measure,
gsk_standard_contour_get_point,
gsk_standard_contour_get_distance,
};
/* 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 GskAlignedPoint *points,
gsize n_points,
const gskpathop *ops,
gsize n_ops,
gssize offset)
{
const gsize align = G_ALIGNOF (GskAlignedPoint);
GskStandardContour *self = (GskStandardContour *) contour;
guint8 *points_addr;
self->contour.klass = &GSK_STANDARD_CONTOUR_CLASS;
self->flags = flags;
self->n_ops = n_ops;
self->n_points = n_points;
points_addr = (guint8 *) &self->ops[n_ops];
/* The array of points needs to be 8-byte aligned, but on 32-bit,
* a single entry in ops might only be 4 bytes, so we might need
* 4 bytes of padding before starting the array of points. */
points_addr += align - (((gsize) points_addr) % align);
self->points = (GskAlignedPoint *) points_addr;
memcpy (self->points, points, sizeof (graphene_point_t) * n_points);
offset += self->points - points;
for (gsize i = 0; i < n_ops; i++)
self->ops[i] = gsk_pathop_encode (gsk_pathop_op (ops[i]),
gsk_pathop_aligned_points (ops[i]) + offset);
gsk_bounding_box_init (&self->bounds, &self->points[0].pt, &self->points[0].pt);
for (gsize i = 1; i < self->n_points; i ++)
gsk_bounding_box_expand (&self->bounds, &self->points[i].pt);
}
GskContour *
gsk_standard_contour_new (GskPathFlags flags,
const GskAlignedPoint *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;
}
/* }}} */
/* {{{ Circle */
typedef struct _GskCircleContour GskCircleContour;
struct _GskCircleContour
{
GskContour contour;
graphene_point_t center;
float radius;
gboolean ccw;
};
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)
{
return GSK_PATH_CLOSED;
}
static void
gsk_circle_contour_print (const GskContour *contour,
GString *string)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
float radius, radius_neg;
if (self->radius > 0)
{
radius = self->radius;
radius_neg = - self->radius;
}
else
{
radius = 0.f;
radius_neg = 0.f;
}
_g_string_append_point (string, "M ", &GRAPHENE_POINT_INIT (self->center.x + radius, self->center.y));
_g_string_append_point (string, " o ", &GRAPHENE_POINT_INIT (0, radius));
_g_string_append_point (string, ", ", &GRAPHENE_POINT_INIT (radius_neg, radius));
_g_string_append_float (string, ", ", M_SQRT1_2);
_g_string_append_point (string, " o ", &GRAPHENE_POINT_INIT (radius_neg, 0));
_g_string_append_point (string, ", ", &GRAPHENE_POINT_INIT (radius_neg, radius_neg));
_g_string_append_float (string, ", ", M_SQRT1_2);
_g_string_append_point (string, " o ", &GRAPHENE_POINT_INIT (0, radius_neg));
_g_string_append_point (string, ", ", &GRAPHENE_POINT_INIT (radius, radius_neg));
_g_string_append_float (string, ", ", M_SQRT1_2);
_g_string_append_point (string, " o ", &GRAPHENE_POINT_INIT (radius, 0));
_g_string_append_point (string, ", ", &GRAPHENE_POINT_INIT (radius, radius));
_g_string_append_float (string, ", ", M_SQRT1_2);
g_string_append (string, " z");
}
static gboolean
gsk_circle_contour_get_bounds (const GskContour *contour,
GskBoundingBox *bounds)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
gsk_bounding_box_init (bounds,
&GRAPHENE_POINT_INIT (self->center.x - self->radius,
self->center.y - self->radius),
&GRAPHENE_POINT_INIT (self->center.x + self->radius,
self->center.y + self->radius));
return TRUE;
}
static gboolean
gsk_circle_contour_get_stroke_bounds (const GskContour *contour,
const GskStroke *stroke,
GskBoundingBox *bounds)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
gsk_bounding_box_init (bounds,
&GRAPHENE_POINT_INIT (self->center.x - self->radius - stroke->line_width,
self->center.y - self->radius - stroke->line_width),
&GRAPHENE_POINT_INIT (self->center.x + self->radius + stroke->line_width/2,
self->center.y + self->radius + stroke->line_width));
return TRUE;
}
static gboolean
gsk_circle_contour_foreach (const GskContour *contour,
GskPathForeachFunc func,
gpointer user_data)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
float rx, ry;
graphene_point_t pts[10];
rx = ry = self->radius;
if (self->ccw)
ry = - self->radius;
pts[0] = GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y);
pts[1] = GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y + ry);
pts[2] = GRAPHENE_POINT_INIT (self->center.x, self->center.y + ry);
pts[3] = GRAPHENE_POINT_INIT (self->center.x - rx, self->center.y + ry);
pts[4] = GRAPHENE_POINT_INIT (self->center.x - rx, self->center.y);
pts[5] = GRAPHENE_POINT_INIT (self->center.x - rx, self->center.y - ry);
pts[6] = GRAPHENE_POINT_INIT (self->center.x, self->center.y - ry);
pts[7] = GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y - ry);
pts[8] = GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y);
pts[9] = GRAPHENE_POINT_INIT (self->center.x + rx, self->center.y);
return func (GSK_PATH_MOVE, &pts[0], 1, 0.f, user_data) &&
maybe_emit_conic (&pts[0], M_SQRT1_2, func, user_data) &&
maybe_emit_conic (&pts[2], M_SQRT1_2, func, user_data) &&
maybe_emit_conic (&pts[4], M_SQRT1_2, func, user_data) &&
maybe_emit_conic (&pts[6], M_SQRT1_2, func, user_data) &&
func (GSK_PATH_CLOSE, &pts[8], 2, 0.f, user_data);
}
static GskContour *
gsk_circle_contour_reverse (const GskContour *contour)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
GskCircleContour *copy;
copy = g_new0 (GskCircleContour, 1);
gsk_circle_contour_copy (contour, (GskContour *)copy);
copy->ccw = !self->ccw;
return (GskContour *)copy;
}
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 self->ccw ? -1 : 1;
return 0;
}
static gsize
gsk_circle_contour_get_n_ops (const GskContour *contour)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
/* idx == 0 is the move (which does not really exist here,
* but gskpath.c assumes there is one).
*/
return self->radius > 0 ? 6 : 2;
}
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, t;
gsize idx;
dist = fabsf (graphene_point_distance (&self->center, point, NULL, NULL) - self->radius);
if (dist > threshold)
return FALSE;
angle = atan2f (point->y - self->center.y, point->x - self->center.x);
if (angle < 0)
angle = 2 * M_PI + angle;
t = CLAMP (angle / (2 * M_PI), 0, 1);
if (self->ccw)
t = 1 - t;
t = t * 4;
idx = 1;
do {
if (t < 1)
break;
t = t - 1;
idx = idx + 1;
} while (t != 0);
result->idx = idx;
result->t = t;
return TRUE;
}
static void
gsk_circle_contour_get_position (const GskContour *contour,
const GskPathPoint *point,
graphene_point_t *position)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
gsize idx = point->idx;
float t = point->t;
if (self->radius == 0)
{
*position = self->center;
return;
}
/* avoid the z */
if (idx == 5)
{
idx = 4;
t = 1;
}
if (self->ccw)
{
idx = 5 - idx;
t = 1 - t;
}
if ((idx == 1 && t == 0) || (idx == 4 && t == 1))
{
*position = GRAPHENE_POINT_INIT (self->center.x + self->radius, self->center.y);
}
else
{
float s, c;
_sincosf (M_PI_2 * ((idx - 1) + t), &s, &c);
*position = GRAPHENE_POINT_INIT (self->center.x + c * self->radius,
self->center.y + s * self->radius);
}
}
static void
gsk_circle_contour_get_tangent (const GskContour *contour,
const GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
graphene_point_t p;
gsk_circle_contour_get_position (contour, point, &p);
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,
const GskPathPoint *point,
GskPathDirection direction,
graphene_point_t *center)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
if (center)
*center = self->center;
if (self->radius == 0)
return INFINITY;
return 1.f / self->radius;
}
static void
gsk_circle_contour_add_segment (const GskContour *contour,
GskPathBuilder *builder,
gboolean emit_move_to,
const GskPathPoint *start,
const GskPathPoint *end)
{
GskPath *path;
const GskContour *std;
path = convert_to_standard_contour (contour);
std = gsk_path_get_contour (path, 0);
gsk_standard_contour_add_segment (std, builder, emit_move_to, start, end);
gsk_path_unref (path);
}
static gpointer
gsk_circle_contour_init_measure (const GskContour *contour,
float tolerance,
float *out_length)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
*out_length = 2 * M_PI * self->radius;
return NULL;
}
static void
gsk_circle_contour_free_measure (const GskContour *contour,
gpointer data)
{
}
static void
gsk_circle_contour_get_point (const GskContour *contour,
gpointer measure_data,
float distance,
GskPathPoint *result)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
float t;
gsize idx;
if (self->radius == 0)
{
result->idx = 1;
result->t = 0;
return;
}
t = distance / (M_PI_2 * self->radius);
idx = 1;
do {
if (t < 1)
break;
t = t - 1;
idx = idx + 1;
} while (t > 0);
if (self->ccw)
{
idx = 5 - idx;
t = 1 - t;
}
result->idx = idx;
result->t = t;
}
static float
gsk_circle_contour_get_distance (const GskContour *contour,
const GskPathPoint *point,
gpointer measure_data)
{
const GskCircleContour *self = (const GskCircleContour *) contour;
float t;
gsize idx;
if (self->radius == 0)
return 0;
idx = point->idx;
t = point->t;
if (self->ccw)
{
idx = 5 - idx;
t = 1 - t;
}
return M_PI_2 * self->radius * (idx - 1 + t);
}
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_stroke_bounds,
gsk_circle_contour_foreach,
gsk_circle_contour_reverse,
gsk_circle_contour_get_winding,
gsk_circle_contour_get_n_ops,
gsk_circle_contour_get_closest_point,
gsk_circle_contour_get_position,
gsk_circle_contour_get_tangent,
gsk_circle_contour_get_curvature,
gsk_circle_contour_add_segment,
gsk_circle_contour_init_measure,
gsk_circle_contour_free_measure,
gsk_circle_contour_get_point,
gsk_circle_contour_get_distance,
};
GskContour *
gsk_circle_contour_new (const graphene_point_t *center,
float radius)
{
GskCircleContour *self;
g_assert (radius >= 0);
self = g_new0 (GskCircleContour, 1);
self->contour.klass = &GSK_CIRCLE_CONTOUR_CLASS;
self->contour.klass = &GSK_CIRCLE_CONTOUR_CLASS;
self->center = *center;
self->radius = radius;
self->ccw = FALSE;
return (GskContour *) self;
}
/* }}} */
/* {{{ Rectangle */
typedef struct _GskRectContour GskRectContour;
struct _GskRectContour
{
GskContour contour;
float x;
float y;
float width;
float height;
gsize n_ops;
};
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_point (string, "M ", &GRAPHENE_POINT_INIT (self->x, self->y));
_g_string_append_float (string, " h ", self->width);
_g_string_append_float (string, " v ", self->height);
_g_string_append_float (string, " h ", - self->width);
g_string_append (string, " z");
}
static gboolean
gsk_rect_contour_get_bounds (const GskContour *contour,
GskBoundingBox *bounds)
{
const GskRectContour *self = (const GskRectContour *) contour;
gsk_bounding_box_init (bounds,
&GRAPHENE_POINT_INIT (self->x, self->y),
&GRAPHENE_POINT_INIT (self->x + self->width, self->y + self->height));
return TRUE;
}
static gboolean
gsk_rect_contour_get_stroke_bounds (const GskContour *contour,
const GskStroke *stroke,
GskBoundingBox *bounds)
{
const GskRectContour *self = (const GskRectContour *) contour;
graphene_rect_t rect;
graphene_rect_init (&rect, self->x, self->y, self->width, self->height);
graphene_rect_inset (&rect, - 0.5 * stroke->line_width, - 0.5 * stroke->line_width);
gsk_bounding_box_init_from_rect (bounds, &rect);
return TRUE;
}
static gboolean
gsk_rect_contour_foreach (const GskContour *contour,
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.f, user_data) &&
maybe_emit_line (&pts[0], func, user_data) &&
maybe_emit_line (&pts[1], func, user_data) &&
maybe_emit_line (&pts[2], func, user_data) &&
func (GSK_PATH_CLOSE, &pts[3], 2, 0.f, 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 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))
{
if ((self->width < 0) != (self->height < 0))
return -1;
else
return 1;
}
return 0;
}
static gsize
gsk_rect_contour_get_n_ops (const GskContour *contour)
{
const GskRectContour *self = (const GskRectContour *) contour;
return self->n_ops;
}
static gboolean
gsk_rect_contour_get_closest_point (const GskContour *contour,
const graphene_point_t *point,
float threshold,
GskPathPoint *result,
float *out_dist)
{
return contour_get_closest_point (contour, point, threshold, result, out_dist);
}
static void
gsk_rect_contour_get_position (const GskContour *contour,
const GskPathPoint *point,
graphene_point_t *position)
{
GskCurve curve;
contour_init_curve (contour, point->idx, &curve);
gsk_curve_get_point (&curve, point->t, position);
}
static void
gsk_rect_contour_get_tangent (const GskContour *contour,
const GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
const GskRectContour *self = (const GskRectContour *) contour;
gsize idx = point->idx;
float t = point->t;
GskCurve curve;
apply_corner_direction (direction, &idx, &t, self->n_ops);
contour_init_curve (contour, idx, &curve);
gsk_curve_get_tangent (&curve, t, tangent);
if (direction == GSK_PATH_TO_START || direction == GSK_PATH_FROM_END)
graphene_vec2_negate (tangent, tangent);
}
static float
gsk_rect_contour_get_curvature (const GskContour *contour,
const GskPathPoint *point,
GskPathDirection direction,
graphene_point_t *center)
{
return 0;
}
static void
gsk_rect_contour_add_segment (const GskContour *contour,
GskPathBuilder *builder,
gboolean emit_move_to,
const GskPathPoint *start,
const GskPathPoint *end)
{
contour_add_segment (contour, builder, emit_move_to, start, end);
}
static gpointer
gsk_rect_contour_init_measure (const GskContour *contour,
float tolerance,
float *out_length)
{
const GskRectContour *self = (const GskRectContour *) contour;
*out_length = 2 * (fabsf (self->width) + fabsf (self->height));
return NULL;
}
static void
gsk_rect_contour_free_measure (const GskContour *contour,
gpointer data)
{
}
static inline int
rect_contour_get_sides (const GskRectContour *self,
float sides[5])
{
int n_sides = 0;
sides[n_sides++] = 0;
if (self->width != 0)
sides[n_sides++] = fabsf (self->width);
if (self->height != 0)
sides[n_sides++] = fabsf (self->height);
if (self->width != 0)
sides[n_sides++] = fabsf (self->width);
sides[n_sides++] = fabsf (self->height);
return n_sides;
}
static void
gsk_rect_contour_get_point (const GskContour *contour,
gpointer measure_data,
float distance,
GskPathPoint *result)
{
const GskRectContour *self = (const GskRectContour *) contour;
float sides[5];
int n_sides = 0;
if (distance == 0)
{
result->idx = 1;
result->t = 0;
return;
}
n_sides = rect_contour_get_sides (self, sides);
for (int i = 0; i < n_sides; i++)
{
if (distance <= sides[i])
{
result->idx = i;
result->t = distance / sides[i];
return;
}
distance -= sides[i];
}
result->idx = n_sides - 1;
result->t = 1;
}
static float
gsk_rect_contour_get_distance (const GskContour *contour,
const GskPathPoint *point,
gpointer measure_data)
{
const GskRectContour *self = (const GskRectContour *) contour;
float sides[5];
int n_sides G_GNUC_UNUSED;
float distance;
n_sides = rect_contour_get_sides (self, sides);
g_assert (point->idx < n_sides);
distance = 0;
for (int i = 0; i < point->idx; i++)
distance += sides[i];
distance += point->t * sides[point->idx];
return distance;
}
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_stroke_bounds,
gsk_rect_contour_foreach,
gsk_rect_contour_reverse,
gsk_rect_contour_get_winding,
gsk_rect_contour_get_n_ops,
gsk_rect_contour_get_closest_point,
gsk_rect_contour_get_position,
gsk_rect_contour_get_tangent,
gsk_rect_contour_get_curvature,
gsk_rect_contour_add_segment,
gsk_rect_contour_init_measure,
gsk_rect_contour_free_measure,
gsk_rect_contour_get_point,
gsk_rect_contour_get_distance,
};
GskContour *
gsk_rect_contour_new (const graphene_rect_t *rect)
{
GskRectContour *self;
gsize n_ops[] = { 2, 3, 5 };
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;
self->n_ops = n_ops[(self->width != 0) + (self->height != 0)];
return (GskContour *) self;
}
/* }}} */
/* {{{ Rounded Rectangle */
typedef struct _GskRoundedRectContour GskRoundedRectContour;
struct _GskRoundedRectContour
{
GskContour contour;
GskRoundedRect rect;
gboolean ccw;
gsize n_ops;
};
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 gboolean
gsk_rounded_rect_contour_get_bounds (const GskContour *contour,
GskBoundingBox *bounds)
{
const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour;
gsk_bounding_box_init_from_rect (bounds, &self->rect.bounds);
return TRUE;
}
static gboolean
gsk_rounded_rect_contour_get_stroke_bounds (const GskContour *contour,
const GskStroke *stroke,
GskBoundingBox *bounds)
{
const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour;
GskBoundingBox b;
gsk_bounding_box_init_from_rect (&b, &self->rect.bounds);
gsk_bounding_box_init (bounds,
&GRAPHENE_POINT_INIT (b.min.x - stroke->line_width,
b.min.y - stroke->line_width),
&GRAPHENE_POINT_INIT (b.max.x + stroke->line_width,
b.max.y + stroke->line_width));
return TRUE;
}
static 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);
pts[13] = GRAPHENE_POINT_INIT (rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width, rect->bounds.origin.y);
}
static gboolean
gsk_rounded_rect_contour_foreach (const GskContour *contour,
GskPathForeachFunc func,
gpointer user_data)
{
const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour;
graphene_point_t pts[14];
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.f, user_data) &&
maybe_emit_conic (&pts[0], M_SQRT1_2, func, user_data) &&
maybe_emit_line (&pts[2], func, user_data) &&
maybe_emit_conic (&pts[3], M_SQRT1_2, func, user_data) &&
maybe_emit_line (&pts[5], func, user_data) &&
maybe_emit_conic (&pts[6], M_SQRT1_2, func, user_data) &&
maybe_emit_line (&pts[8], func, user_data) &&
maybe_emit_conic (&pts[9], M_SQRT1_2, func, user_data) &&
maybe_emit_line (&pts[11], func, user_data) &&
func (GSK_PATH_CLOSE, &pts[12], 2, 0.f, user_data);
}
else
{
return func (GSK_PATH_MOVE, &pts[0], 1, 0.f, user_data) &&
maybe_emit_line (&pts[0], func, user_data) &&
maybe_emit_conic (&pts[1], M_SQRT1_2, func, user_data) &&
maybe_emit_line (&pts[3], func, user_data) &&
maybe_emit_conic (&pts[4], M_SQRT1_2, func, user_data) &&
maybe_emit_line (&pts[6], func, user_data) &&
maybe_emit_conic (&pts[7], M_SQRT1_2, func, user_data) &&
maybe_emit_line (&pts[9], func, user_data) &&
maybe_emit_conic (&pts[10], M_SQRT1_2, func, user_data) &&
func (GSK_PATH_CLOSE, &pts[12], 2, 0.f, user_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 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 gsize
gsk_rounded_rect_contour_get_n_ops (const GskContour *contour)
{
const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour;
return self->n_ops;
}
static gboolean
gsk_rounded_rect_contour_get_closest_point (const GskContour *contour,
const graphene_point_t *point,
float threshold,
GskPathPoint *result,
float *out_dist)
{
return contour_get_closest_point (contour, point, threshold, result, out_dist);
}
static void
gsk_rounded_rect_contour_get_position (const GskContour *contour,
const GskPathPoint *point,
graphene_point_t *position)
{
GskCurve curve;
contour_init_curve (contour, point->idx, &curve);
gsk_curve_get_point (&curve, point->t, position);
}
static void
gsk_rounded_rect_contour_get_tangent (const GskContour *contour,
const GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour;
gsize idx = point->idx;
float t = point->t;
GskCurve curve;
/* Avoid the z, since it has length 0 and won't give us a tangent */
if (idx == self->n_ops - 1)
{
idx = self->n_ops - 2;
t = 1;
}
apply_corner_direction (direction, &idx, &t, self->n_ops - 1);
contour_init_curve (contour, idx, &curve);
gsk_curve_get_tangent (&curve, t, tangent);
if (direction == GSK_PATH_TO_START || direction == GSK_PATH_FROM_END)
graphene_vec2_negate (tangent, tangent);
}
static float
gsk_rounded_rect_contour_get_curvature (const GskContour *contour,
const GskPathPoint *point,
GskPathDirection direction,
graphene_point_t *center)
{
const GskRoundedRectContour *self = (const GskRoundedRectContour *) contour;
GskCurve curve;
gsize idx = point->idx;
float t = point->t;
/* Avoid the z, since it has length 0 and won't give us curvature */
if (idx == self->n_ops - 1)
{
idx = self->n_ops - 2;
t = 1;
}
apply_corner_direction (direction, &idx, &t, self->n_ops - 1);
contour_init_curve (contour, idx, &curve);
return gsk_curve_get_curvature (&curve, t, center);
}
static void
gsk_rounded_rect_contour_add_segment (const GskContour *contour,
GskPathBuilder *builder,
gboolean emit_move_to,
const GskPathPoint *start,
const GskPathPoint *end)
{
contour_add_segment (contour, builder, emit_move_to, start, end);
}
typedef struct
{
const GskContour *contour;
gpointer measure_data;
} RoundedRectMeasureData;
static gpointer
gsk_rounded_rect_contour_init_measure (const GskContour *contour,
float tolerance,
float *out_length)
{
RoundedRectMeasureData *data;
GskPath *path;
path = convert_to_standard_contour (contour);
data = g_new (RoundedRectMeasureData, 1);
data->contour = gsk_contour_dup (gsk_path_get_contour (path, 0));
data->measure_data = gsk_standard_contour_init_measure (data->contour, tolerance, out_length);
gsk_path_unref (path);
return data;
}
static void
gsk_rounded_rect_contour_free_measure (const GskContour *contour,
gpointer measure_data)
{
RoundedRectMeasureData *data = measure_data;
gsk_standard_contour_free_measure (data->contour, data->measure_data);
g_free (data);
}
static void
gsk_rounded_rect_contour_get_point (const GskContour *contour,
gpointer measure_data,
float distance,
GskPathPoint *result)
{
RoundedRectMeasureData *data = measure_data;
gsk_standard_contour_get_point (data->contour, data->measure_data, distance, result);
}
static float
gsk_rounded_rect_contour_get_distance (const GskContour *contour,
const GskPathPoint *point,
gpointer measure_data)
{
RoundedRectMeasureData *data = measure_data;
return gsk_standard_contour_get_distance (data->contour, point, data->measure_data);
}
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_contour_print_default,
gsk_rounded_rect_contour_get_bounds,
gsk_rounded_rect_contour_get_stroke_bounds,
gsk_rounded_rect_contour_foreach,
gsk_rounded_rect_contour_reverse,
gsk_rounded_rect_contour_get_winding,
gsk_rounded_rect_contour_get_n_ops,
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,
gsk_rounded_rect_contour_add_segment,
gsk_rounded_rect_contour_init_measure,
gsk_rounded_rect_contour_free_measure,
gsk_rounded_rect_contour_get_point,
gsk_rounded_rect_contour_get_distance,
};
static gsize
rounded_rect_compute_n_ops (const GskRoundedRect *rect)
{
graphene_point_t pts[14];
gsize n_ops;
get_rounded_rect_points (rect, pts);
n_ops = 2;
if (!graphene_point_equal (&pts[0], &pts[1]))
n_ops++;
if (!graphene_point_equal (&pts[1], &pts[2]) ||
!graphene_point_equal (&pts[2], &pts[3]))
n_ops++;
if (!graphene_point_equal (&pts[3], &pts[4]))
n_ops++;
if (!graphene_point_equal (&pts[4], &pts[5]) ||
!graphene_point_equal (&pts[5], &pts[6]))
n_ops++;
if (!graphene_point_equal (&pts[6], &pts[7]))
n_ops++;
if (!graphene_point_equal (&pts[7], &pts[8]) ||
!graphene_point_equal (&pts[8], &pts[9]))
n_ops++;
if (!graphene_point_equal (&pts[9], &pts[10]))
n_ops++;
if (!graphene_point_equal (&pts[10], &pts[11]) ||
!graphene_point_equal (&pts[11], &pts[12]))
n_ops++;
return n_ops;
}
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;
gsk_rounded_rect_normalize (&self->rect);
self->n_ops = rounded_rect_compute_n_ops (&self->rect);
return (GskContour *) self;
}
/* }}} */
/* {{{ API */
const char *
gsk_contour_get_type_name (const GskContour *self)
{
return self->klass->type_name;
}
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_get_stroke_bounds (const GskContour *self,
const GskStroke *stroke,
GskBoundingBox *bounds)
{
return self->klass->get_stroke_bounds (self, stroke, bounds);
}
gboolean
gsk_contour_foreach (const GskContour *self,
GskPathForeachFunc func,
gpointer user_data)
{
return self->klass->foreach (self, func, user_data);
}
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);
}
/* Not related to how many curves foreach produces.
*
* GskPath assumes that the start- and endpoints
* of a contour are { x, 1, 0 } and { x, n_ops - 1, 1 }.
*
* While the standard and rounded rect contours use
* one point per op, the circle contour uses a single
* 'segment' in path points, with a t that ranges
* from 0 to 1 to cover the angles from 0 to 360 (or
* 360 to 0 in the ccw case).
*/
gsize
gsk_contour_get_n_ops (const GskContour *self)
{
return self->klass->get_n_ops (self);
}
void
gsk_contour_get_position (const GskContour *self,
const GskPathPoint *point,
graphene_point_t *pos)
{
self->klass->get_position (self, point, pos);
}
void
gsk_contour_get_tangent (const GskContour *self,
const GskPathPoint *point,
GskPathDirection direction,
graphene_vec2_t *tangent)
{
self->klass->get_tangent (self, point, direction, tangent);
}
float
gsk_contour_get_curvature (const GskContour *self,
const GskPathPoint *point,
GskPathDirection direction,
graphene_point_t *center)
{
return self->klass->get_curvature (self, point, direction, center);
}
void
gsk_contour_add_segment (const GskContour *self,
GskPathBuilder *builder,
gboolean emit_move_to,
const GskPathPoint *start,
const GskPathPoint *end)
{
self->klass->add_segment (self, builder, emit_move_to, start, end);
}
gpointer
gsk_contour_init_measure (const GskContour *self,
float tolerance,
float *out_length)
{
return self->klass->init_measure (self, tolerance, out_length);
}
void
gsk_contour_free_measure (const GskContour *self,
gpointer data)
{
self->klass->free_measure (self, data);
}
void
gsk_contour_get_point (const GskContour *self,
gpointer measure_data,
float distance,
GskPathPoint *result)
{
self->klass->get_point (self, measure_data, distance, result);
}
float
gsk_contour_get_distance (const GskContour *self,
const GskPathPoint *point,
gpointer measure_data)
{
return self->klass->get_distance (self, point, measure_data);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */