Merge branch 'wip/matthiasc/arcs' into 'main'

path: Add arcs

See merge request GNOME/gtk!6313
This commit is contained in:
Matthias Clasen
2023-08-23 19:17:01 +00:00
24 changed files with 1476 additions and 78 deletions

View File

@@ -336,6 +336,7 @@
<file>panes.c</file>
<file>password_entry.c</file>
<file>path_fill.c</file>
<file>path_spinner.c</file>
<file>path_walk.c</file>
<file>peg_solitaire.c</file>
<file>pickers.c</file>

View File

@@ -73,6 +73,7 @@ demos = files([
'panes.c',
'password_entry.c',
'path_fill.c',
'path_spinner.c',
'path_walk.c',
'peg_solitaire.c',
'pickers.c',

View File

@@ -0,0 +1,322 @@
/* Path/Spinner
*
* This demo shows how to use GskPath to draw a simple animation
* that could be used as a spinner.
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "paintable.h"
#define GTK_TYPE_SPINNER_PAINTABLE (gtk_spinner_paintable_get_type ())
G_DECLARE_FINAL_TYPE (GtkSpinnerPaintable, gtk_spinner_paintable, GTK, SPINNER_PAINTABLE, GObject)
struct _GtkSpinnerPaintable
{
GObject parent_instance;
gint64 start_time;
int width;
double angle;
double completion;
GskPath *circle;
GskPath *path;
GskStroke *stroke;
GdkRGBA color;
GdkRGBA circle_color;
#ifdef SHOW_CONTROLS
GskPath *controls;
GdkRGBA control_color;
#endif
};
struct _GtkSpinnerPaintableClass
{
GObjectClass parent_class;
};
static int
gtk_spinner_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (paintable);
return self->width;
}
static int
gtk_spinner_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (paintable);
return self->width;
}
static void
gtk_spinner_paintable_snapshot (GdkPaintable *paintable,
GdkSnapshot *snapshot,
double width,
double height)
{
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (paintable);
gtk_snapshot_append_stroke (snapshot, self->circle, self->stroke, &self->circle_color);
gtk_snapshot_append_stroke (snapshot, self->path, self->stroke, &self->color);
#ifdef SHOW_CONTROLS
GskStroke *stroke = gsk_stroke_new (1);
gtk_snapshot_append_stroke (snapshot, self->controls, stroke, &self->control_color);
gsk_stroke_free (stroke);
#endif
}
static GdkPaintableFlags
gtk_spinner_paintable_get_flags (GdkPaintable *paintable)
{
return GDK_PAINTABLE_STATIC_SIZE;
}
static void
gtk_spinner_paintable_paintable_init (GdkPaintableInterface *iface)
{
iface->get_intrinsic_width = gtk_spinner_paintable_get_intrinsic_width;
iface->get_intrinsic_height = gtk_spinner_paintable_get_intrinsic_height;
iface->snapshot = gtk_spinner_paintable_snapshot;
iface->get_flags = gtk_spinner_paintable_get_flags;
}
/* When defining the GType, we need to implement the GdkPaintable interface */
G_DEFINE_TYPE_WITH_CODE (GtkSpinnerPaintable, gtk_spinner_paintable, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
gtk_spinner_paintable_paintable_init))
static void
gtk_spinner_paintable_dispose (GObject *object)
{
GtkSpinnerPaintable *self = GTK_SPINNER_PAINTABLE (object);
gsk_path_unref (self->circle);
gsk_path_unref (self->path);
#ifdef SHOW_CONTROLS
gsk_path_unref (self->controls);
#endif
gsk_stroke_free (self->stroke);
G_OBJECT_CLASS (gtk_spinner_paintable_parent_class)->dispose (object);
}
static void
gtk_spinner_paintable_class_init (GtkSpinnerPaintableClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = gtk_spinner_paintable_dispose;
}
static void
gtk_spinner_paintable_init (GtkSpinnerPaintable *self)
{
}
static GdkPaintable *
gtk_spinner_paintable_new (void)
{
GtkSpinnerPaintable *self;
GskPathBuilder *builder;
self = g_object_new (GTK_TYPE_SPINNER_PAINTABLE, NULL);
builder = gsk_path_builder_new ();
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (50, 50), 40);
self->circle = gsk_path_builder_free_to_path (builder);
self->width = 100;
self->angle = 0;
self->completion = 1;
gdk_rgba_parse (&self->color, "green");
gdk_rgba_parse (&self->circle_color, "lightgray");
#ifdef SHOW_CONTROLS
gdk_rgba_parse (&self->control_color, "black");
#endif
self->stroke = gsk_stroke_new (5);
return GDK_PAINTABLE (self);
}
#ifdef SHOW_CONTROLS
static gboolean
add_controls (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
gpointer data)
{
GskPathBuilder *builder = data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builder, pts[0].x, pts[0].y);
break;
case GSK_PATH_CLOSE:
case GSK_PATH_LINE:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
break;
case GSK_PATH_QUAD:
case GSK_PATH_ARC:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
gsk_path_builder_line_to (builder, pts[2].x, pts[2].y);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_line_to (builder, pts[1].x, pts[1].y);
gsk_path_builder_line_to (builder, pts[2].x, pts[2].y);
gsk_path_builder_line_to (builder, pts[3].x, pts[3].y);
break;
default:
g_assert_not_reached ();
}
return TRUE;
}
#endif
static void
update_path (GtkSpinnerPaintable *self)
{
GskPathBuilder *builder;
GskPathPoint start, end;
GskTransform *t;
graphene_point_t p, p0, p1;
float start_angle, end_angle;
p = GRAPHENE_POINT_INIT (40, 0);
start_angle = self->angle;
end_angle = fmod (self->angle + 360 * self->completion / 100, 360);
t = gsk_transform_translate (
gsk_transform_rotate (
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (50, 50)),
start_angle),
&GRAPHENE_POINT_INIT (-50, -50));
gsk_transform_transform_point (t, &p, &p0);
t = gsk_transform_translate (
gsk_transform_rotate (
gsk_transform_translate (NULL, &GRAPHENE_POINT_INIT (50, 50)),
end_angle),
&GRAPHENE_POINT_INIT (-50, -50));
gsk_transform_transform_point (t, &p, &p1);
g_clear_pointer (&self->path, gsk_path_unref);
gsk_path_get_closest_point (self->circle, &p0, INFINITY, &start);
gsk_path_get_closest_point (self->circle, &p1, INFINITY, &end);
builder = gsk_path_builder_new ();
gsk_path_builder_add_segment (builder, self->circle, &start, &end);
self->path = gsk_path_builder_free_to_path (builder);
#ifdef SHOW_CONTROLS
g_clear_pointer (&self->controls, gsk_path_unref);
builder = gsk_path_builder_new ();
gsk_path_foreach (self->path, -1, add_controls, builder);
self->controls = gsk_path_builder_free_to_path (builder);
#endif
gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));
}
static void
gtk_spinner_paintable_set_completion (GtkSpinnerPaintable *self,
float completion)
{
self->completion = CLAMP (completion, 0, 100);
update_path (self);
}
static float
gtk_spinner_paintable_get_completion (GtkSpinnerPaintable *self)
{
return self->completion;
}
static void
gtk_spinner_paintable_set_frame_time (GtkSpinnerPaintable *self,
gint64 time)
{
double delta;
if (self->start_time == 0)
self->start_time = time;
delta = (time - self->start_time) / (double) G_TIME_SPAN_SECOND;
self->angle = fmod (60 * delta, 360);
update_path (self);
}
static gboolean
tick_cb (GtkWidget *widget,
GdkFrameClock *clock,
gpointer data)
{
GtkSpinnerPaintable *self = data;
gtk_spinner_paintable_set_frame_time (self, gdk_frame_clock_get_frame_time (clock));
return G_SOURCE_CONTINUE;
}
static gboolean
progress_timeout (gpointer data)
{
GtkSpinnerPaintable *self = data;
static float progress_delta = 0.5;
float progress;
progress = gtk_spinner_paintable_get_completion (self);
if (progress >= 100 || progress <= 0)
progress_delta = -progress_delta;
gtk_spinner_paintable_set_completion (self, progress + progress_delta);
return G_SOURCE_CONTINUE;
}
GtkWidget *
do_path_spinner (GtkWidget *do_widget)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *picture;
GdkPaintable *paintable;
window = gtk_window_new ();
gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
gtk_window_set_title (GTK_WINDOW (window), "Path Spinner");
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
paintable = gtk_spinner_paintable_new ();
picture = gtk_picture_new_for_paintable (paintable);
gtk_picture_set_content_fit (GTK_PICTURE (picture), GTK_CONTENT_FIT_CONTAIN);
gtk_picture_set_can_shrink (GTK_PICTURE (picture), FALSE);
g_object_unref (paintable);
gtk_widget_add_tick_callback (picture, tick_cb, paintable, NULL);
g_timeout_add (100, progress_timeout, paintable);
gtk_window_set_child (GTK_WINDOW (window), picture);
}
if (!gtk_widget_get_visible (window))
gtk_window_present (GTK_WINDOW (window));
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}

View File

@@ -37,6 +37,8 @@ content_files = [
]
content_images = [
"gtk-logo.svg",
"images/arc-dark.png",
"images/arc-light.png",
"images/caps-dark.png",
"images/caps-light.png",
"images/cubic-dark.png",

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="arcto.svg"
inkscape:export-filename="cubic-light.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.75989759"
inkscape:cx="399.39592"
inkscape:cy="466.51023"
inkscape:window-width="1920"
inkscape:window-height="1123"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="false" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
d="M 58.033485,123.96862 C 75.231194,113.95411 92.489919,103.26728 107.81401,113.89786"
id="path3"
sodipodi:nodetypes="cc" />
<ellipse
style="fill:none;stroke:#000000;stroke-width:0.20061772;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
id="path2"
cx="-16.837238"
cy="154.55043"
rx="35.832706"
ry="17.920988"
transform="matrix(0.86643544,-0.49928912,0.59215321,0.8058254,0,0)" />
<path
style="fill:none;stroke:#000000;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
d="M 57.432798,124.38759 86.59638,102.1496 107.25717,113.71009"
id="path1"
sodipodi:nodetypes="ccc" />
<circle
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
id="path2-8-7-9"
cx="86.580566"
cy="102.81618"
r="1.5" />
<path
style="fill:#f90048;stroke:#a2a2a2;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none;stroke-opacity:1"
d="m 45.061871,134.70541 12.60825,-10.20893"
id="path4-4"
sodipodi:nodetypes="cc" />
<path
style="fill:#f90048;stroke:#a2a2a2;stroke-width:1;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:1, 3;stroke-dashoffset:0;stroke-opacity:1"
d="m 35.405008,142.35717 8.95641,-7.26298"
id="path4-8-8"
sodipodi:nodetypes="cc" />
<circle
style="fill:#818181;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
id="path2-8-7-61-1"
cx="57.597607"
cy="124.23325"
r="1.5" />
<circle
style="fill:#ff0404;fill-opacity:1;stroke:#000064;stroke-width:0.2;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-dasharray:none"
id="path2-8-7-6-5"
cx="108.01463"
cy="114.17829"
r="1.5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -44,6 +44,10 @@ segments.
Allow cubic Bézier curves to be used in the generated path.
``--allow-arc``
Allow elliptical arcs to be used in the generated path.
Showing
^^^^^^^

View File

@@ -287,6 +287,13 @@ gsk_standard_contour_print (const GskContour *contour,
_g_string_append_point (string, &pt[3]);
break;
case GSK_PATH_ARC:
g_string_append (string, " E ");
_g_string_append_point (string, &pt[1]);
g_string_append (string, ", ");
_g_string_append_point (string, &pt[2]);
break;
default:
g_assert_not_reached();
return;

View File

@@ -84,6 +84,9 @@ struct _GskCurveClass
/* {{{ Utilities */
#define RAD_TO_DEG(r) ((r)*180.f/M_PI)
#define DEG_TO_RAD(d) ((d)*M_PI/180.f)
static void
get_tangent (const graphene_point_t *p0,
const graphene_point_t *p1,
@@ -93,6 +96,37 @@ get_tangent (const graphene_point_t *p0,
graphene_vec2_normalize (t, t);
}
static int
line_intersection (const graphene_point_t *a,
const graphene_vec2_t *ta,
const graphene_point_t *c,
const graphene_vec2_t *tc,
graphene_point_t *p)
{
float a1 = graphene_vec2_get_y (ta);
float b1 = - graphene_vec2_get_x (ta);
float c1 = a1*a->x + b1*a->y;
float a2 = graphene_vec2_get_y (tc);
float b2 = - graphene_vec2_get_x (tc);
float c2 = a2*c->x+ b2*c->y;
float det = a1*b2 - a2*b1;
if (fabs (det) < 0.001)
{
p->x = NAN;
p->y = NAN;
return 0;
}
else
{
p->x = (b2*c1 - b1*c2) / det;
p->y = (a1*c2 - a2*c1) / det;
return 1;
}
}
static int
line_get_crossing (const graphene_point_t *p,
const graphene_point_t *p1,
@@ -177,8 +211,19 @@ gsk_curve_elevate (const GskCurve *curve,
g_assert_not_reached ();
}
static inline void
_sincosf (float angle, float *s, float *c)
{
#ifdef HAVE_SINCOSF
sincosf (angle, s, c);
#else
*s = sinf (angle);
*c = cosf (angle);
#endif
}
/* }}} */
/* {{{ Line */
/* {{{ Line */
static void
gsk_line_curve_init_from_points (GskLineCurve *self,
@@ -1101,7 +1146,7 @@ gsk_cubic_curve_decompose_curve (const GskCurve *curve,
if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
return add_curve_func (GSK_PATH_CUBIC, self->points, 4, user_data);
/* FIXME: Quadratic (or conic?) approximation */
/* FIXME: Quadratic or arc approximation */
return gsk_cubic_curve_decompose (curve,
tolerance,
gsk_curve_add_line_cb,
@@ -1240,6 +1285,448 @@ static const GskCurveClass GSK_CUBIC_CURVE_CLASS = {
gsk_cubic_curve_get_crossing,
};
/* }}} */
/* {{{ Arc */
static void
gsk_arc_curve_ensure_matrix (const GskArcCurve *curve)
{
GskArcCurve *self = (GskArcCurve *)curve;
const graphene_point_t *pts = self->points;
graphene_matrix_t m1, m2, m3, tmp;
if (self->has_matrix)
return;
/* Compute a matrix that maps (1, 0), (1, 1), (0, 1) to pts[0..2] */
graphene_matrix_init_from_2d (&m1, 1, 0, 0, 1, -1, -1);
graphene_matrix_init_from_2d (&m2, -pts[2].x + pts[1].x, -pts[2].y + pts[1].y,
-pts[0].x + pts[1].x, -pts[0].y + pts[1].y,
0, 0);
graphene_matrix_init_from_2d (&m3, 1, 0, 0, 1, pts[1].x, pts[1].y);
graphene_matrix_multiply (&m1, &m2, &tmp);
graphene_matrix_multiply (&tmp, &m3, &self->m);
self->has_matrix = TRUE;
}
static void
gsk_arc_curve_init_from_points (GskArcCurve *self,
const graphene_point_t pts[3])
{
self->op = GSK_PATH_ARC;
self->has_matrix = FALSE;
memcpy (self->points, pts, sizeof (graphene_point_t) * 3);
}
static void
gsk_arc_curve_init (GskCurve *curve,
gskpathop op)
{
GskArcCurve *self = &curve->arc;
gsk_arc_curve_init_from_points (self, gsk_pathop_points (op));
}
static void
gsk_arc_curve_init_foreach (GskCurve *curve,
GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts)
{
GskArcCurve *self = &curve->arc;
g_assert (n_pts == 3);
gsk_arc_curve_init_from_points (self, pts);
}
static void
gsk_arc_curve_print (const GskCurve *curve,
GString *string)
{
const GskArcCurve *self = &curve->arc;
g_string_append_printf (string,
"M %g %g E %g %g %g %g",
self->points[0].x, self->points[0].y,
self->points[1].x, self->points[1].y,
self->points[2].x, self->points[2].y);
}
static gskpathop
gsk_arc_curve_pathop (const GskCurve *curve)
{
const GskArcCurve *self = &curve->arc;
return gsk_pathop_encode (self->op, self->points);
}
static const graphene_point_t *
gsk_arc_curve_get_start_point (const GskCurve *curve)
{
const GskArcCurve *self = &curve->arc;
return &self->points[0];
}
static const graphene_point_t *
gsk_arc_curve_get_end_point (const GskCurve *curve)
{
const GskArcCurve *self = &curve->arc;
return &self->points[2];
}
static void
gsk_arc_curve_get_start_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskArcCurve *self = &curve->arc;
get_tangent (&self->points[0], &self->points[1], tangent);
}
static void
gsk_arc_curve_get_end_tangent (const GskCurve *curve,
graphene_vec2_t *tangent)
{
const GskArcCurve *self = &curve->arc;
get_tangent (&self->points[1], &self->points[2], tangent);
}
static void
gsk_arc_curve_get_point (const GskCurve *curve,
float t,
graphene_point_t *pos)
{
const GskArcCurve *self = &curve->arc;
float s, c;
gsk_arc_curve_ensure_matrix (self);
_sincosf (t * M_PI_2, &s, &c);
graphene_matrix_transform_point (&self->m, &GRAPHENE_POINT_INIT (c, s), pos);
}
static void
gsk_arc_curve_get_tangent (const GskCurve *curve,
float t,
graphene_vec2_t *tangent)
{
const GskArcCurve *self = &curve->arc;
graphene_vec3_t tmp, tmp2;
float s, c;
gsk_arc_curve_ensure_matrix (self);
_sincosf (t * M_PI_2, &s, &c);
graphene_matrix_transform_vec3 (&self->m, graphene_vec3_init (&tmp, -s, c, 0), &tmp2);
graphene_vec2_init (tangent, graphene_vec3_get_x (&tmp2), graphene_vec3_get_y (&tmp2));
graphene_vec2_normalize (tangent, tangent);
}
static float
gsk_arc_curve_get_curvature (const GskCurve *curve,
float t)
{
float t2;
graphene_point_t p, p2, q;
graphene_vec2_t tan, tan2;
if (t < 1)
t2 = CLAMP (t + 0.05, 0, 1);
else
t2 = CLAMP (t - 0.05, 0, 1);
gsk_arc_curve_get_point (curve, t, &p);
gsk_arc_curve_get_tangent (curve, t, &tan);
gsk_arc_curve_get_point (curve, t2, &p2);
gsk_arc_curve_get_tangent (curve, t2, &tan2);
graphene_vec2_init (&tan, - graphene_vec2_get_y (&tan), graphene_vec2_get_x (&tan));
graphene_vec2_init (&tan2, - graphene_vec2_get_y (&tan2), graphene_vec2_get_x (&tan2));
line_intersection (&p, &tan, &p2, &tan2, &q);
return 1.f / graphene_point_distance (&p, &q, NULL, NULL);
}
static void
gsk_arc_curve_reverse (const GskCurve *curve,
GskCurve *reverse)
{
const GskArcCurve *self = &curve->arc;
reverse->op = GSK_PATH_ARC;
reverse->arc.points[0] = self->points[2];
reverse->arc.points[1] = self->points[1];
reverse->arc.points[2] = self->points[0];
reverse->arc.has_matrix = FALSE;
}
static void
gsk_arc_curve_split (const GskCurve *curve,
float progress,
GskCurve *start,
GskCurve *end)
{
const graphene_point_t *p0, *p1;
graphene_point_t p, q;
graphene_vec2_t t0, t1, t;
p0 = gsk_curve_get_start_point (curve);
gsk_curve_get_start_tangent (curve, &t0);
p1 = gsk_curve_get_end_point (curve);
gsk_curve_get_end_tangent (curve, &t1);
gsk_arc_curve_get_point (curve, progress, &p);
gsk_arc_curve_get_tangent (curve, progress, &t);
if (start)
{
if (line_intersection (p0, &t0, &p, &t, &q))
gsk_arc_curve_init_from_points ((GskArcCurve *)start,
(const graphene_point_t[3]) { *p0, q, p });
else
gsk_line_curve_init_from_points ((GskLineCurve *)start, GSK_PATH_LINE, p0, &p);
}
if (end)
{
if (line_intersection (&p, &t, p1, &t1, &q))
gsk_arc_curve_init_from_points ((GskArcCurve *)end,
(const graphene_point_t[3]) { p, q, *p1 });
else
gsk_line_curve_init_from_points ((GskLineCurve *)end, GSK_PATH_LINE, &p, p1);
}
}
static void
gsk_arc_curve_segment (const GskCurve *curve,
float start,
float end,
GskCurve *segment)
{
graphene_point_t p0, p1, q;
graphene_vec2_t t0, t1;
if (start <= 0.0f)
return gsk_arc_curve_split (curve, end, segment, NULL);
else if (end >= 1.0f)
return gsk_arc_curve_split (curve, start, NULL, segment);
gsk_curve_get_point (curve, start, &p0);
gsk_curve_get_tangent (curve, start, &t0);
gsk_curve_get_point (curve, end, &p1);
gsk_curve_get_tangent (curve, end, &t1);
if (line_intersection (&p0, &t0, &p1, &t1, &q))
gsk_arc_curve_init_from_points ((GskArcCurve *)segment,
(const graphene_point_t[3]) { p0, q, p1 });
else
gsk_line_curve_init_from_points ((GskLineCurve *)segment, GSK_PATH_LINE, &p0, &p1);
}
/* taken from Skia, including the very descriptive name */
static gboolean
gsk_arc_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_arc_curve_decompose_subdivide (const GskArcCurve *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_arc_curve_get_point ((const GskCurve *)self, mid_progress, &mid);
if (!gsk_arc_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_arc_curve_decompose_subdivide (self, tolerance,
start, start_progress, &mid, mid_progress,
add_line_func, user_data)
&& gsk_arc_curve_decompose_subdivide (self, tolerance,
&mid, mid_progress, end, end_progress,
add_line_func, user_data);
}
static gboolean
gsk_arc_curve_decompose (const GskCurve *curve,
float tolerance,
GskCurveAddLineFunc add_line_func,
gpointer user_data)
{
const GskArcCurve *self = &curve->arc;
graphene_point_t mid;
gsk_arc_curve_get_point (curve, 0.5, &mid);
return gsk_arc_curve_decompose_subdivide (self,
tolerance,
&self->points[0],
0.0f,
&mid,
0.5f,
add_line_func,
user_data) &&
gsk_arc_curve_decompose_subdivide (self,
tolerance,
&mid,
0.5f,
&self->points[3],
1.0f,
add_line_func,
user_data);
}
static gboolean
gsk_arc_curve_decompose_curve (const GskCurve *curve,
GskPathForeachFlags flags,
float tolerance,
GskCurveAddCurveFunc add_curve_func,
gpointer user_data)
{
const GskArcCurve *self = &curve->arc;
gsk_arc_curve_ensure_matrix (self);
if (flags & GSK_PATH_FOREACH_ALLOW_ARC)
return add_curve_func (GSK_PATH_ARC, self->points, 3, user_data);
if (flags & GSK_PATH_FOREACH_ALLOW_CUBIC)
{
graphene_point_t p[4];
float k = 0.55228474983;
p[0] = GRAPHENE_POINT_INIT (1, 0);
p[1] = GRAPHENE_POINT_INIT (1, k);
p[2] = GRAPHENE_POINT_INIT (k, 1);
p[3] = GRAPHENE_POINT_INIT (0, 1);
for (int i = 0; i < 4; i++)
graphene_matrix_transform_point (&self->m, &p[i], &p[i]);
return add_curve_func (GSK_PATH_CUBIC, p, 4, user_data);
}
if (flags & GSK_PATH_FOREACH_ALLOW_QUAD)
{
graphene_point_t p[5];
float s, c, a;
_sincosf ((float) DEG_TO_RAD (45), &s, &c);
a = (1 - c) / s;
p[0] = GRAPHENE_POINT_INIT (1, 0);
p[1] = GRAPHENE_POINT_INIT (1, a);
p[2] = GRAPHENE_POINT_INIT (c, s);
p[3] = GRAPHENE_POINT_INIT (a, 1);
p[4] = GRAPHENE_POINT_INIT (0, 1);
for (int i = 0; i < 5; i++)
graphene_matrix_transform_point (&self->m, &p[i], &p[i]);
return add_curve_func (GSK_PATH_QUAD, p, 3, user_data) &&
add_curve_func (GSK_PATH_QUAD, &p[2], 3, user_data);
}
return gsk_arc_curve_decompose (curve,
tolerance,
gsk_curve_add_line_cb,
&(AddLineData) { add_curve_func, user_data });
}
static void
gsk_arc_curve_get_bounds (const GskCurve *curve,
GskBoundingBox *bounds)
{
const GskArcCurve *self = &curve->arc;
const graphene_point_t *pts = self->points;
gsk_bounding_box_init (bounds, &pts[0], &pts[2]);
gsk_bounding_box_expand (bounds, &pts[1]);
}
static void
gsk_arc_curve_get_tight_bounds (const GskCurve *curve,
GskBoundingBox *bounds)
{
// FIXME
gsk_arc_curve_get_bounds (curve, bounds);
}
static void
gsk_arc_curve_get_derivative (const GskCurve *curve,
GskCurve *derivative)
{
const GskArcCurve *self = &curve->arc;
graphene_vec3_t t, t1, t2, t3;
gsk_arc_curve_ensure_matrix (self);
graphene_matrix_transform_vec3 (&self->m, graphene_vec3_init (&t, 0, 1, 0), &t1);
graphene_matrix_transform_vec3 (&self->m, graphene_vec3_init (&t, -1, 1, 0), &t2);
graphene_matrix_transform_vec3 (&self->m, graphene_vec3_init (&t, -1, 0, 0), &t3);
gsk_arc_curve_init_from_points ((GskArcCurve *)derivative,
(const graphene_point_t[3]) {
GRAPHENE_POINT_INIT (graphene_vec3_get_x (&t1), graphene_vec3_get_y (&t1)),
GRAPHENE_POINT_INIT (graphene_vec3_get_x (&t2), graphene_vec3_get_y (&t2)),
GRAPHENE_POINT_INIT (graphene_vec3_get_x (&t3), graphene_vec3_get_y (&t3)),
});
}
static int
gsk_arc_curve_get_crossing (const GskCurve *curve,
const graphene_point_t *point)
{
return get_crossing_by_bisection (curve, point);
}
static const GskCurveClass GSK_ARC_CURVE_CLASS = {
gsk_arc_curve_init,
gsk_arc_curve_init_foreach,
gsk_arc_curve_print,
gsk_arc_curve_pathop,
gsk_arc_curve_get_start_point,
gsk_arc_curve_get_end_point,
gsk_arc_curve_get_start_tangent,
gsk_arc_curve_get_end_tangent,
gsk_arc_curve_get_point,
gsk_arc_curve_get_tangent,
gsk_arc_curve_reverse,
gsk_arc_curve_get_curvature,
gsk_arc_curve_split,
gsk_arc_curve_segment,
gsk_arc_curve_decompose,
gsk_arc_curve_decompose_curve,
gsk_arc_curve_get_bounds,
gsk_arc_curve_get_tight_bounds,
gsk_arc_curve_get_derivative,
gsk_arc_curve_get_crossing,
};
/* }}} */
/* {{{ API */
@@ -1251,6 +1738,7 @@ get_class (GskPathOperation op)
[GSK_PATH_LINE] = &GSK_LINE_CURVE_CLASS,
[GSK_PATH_QUAD] = &GSK_QUAD_CURVE_CLASS,
[GSK_PATH_CUBIC] = &GSK_CUBIC_CURVE_CLASS,
[GSK_PATH_ARC] = &GSK_ARC_CURVE_CLASS,
};
g_assert (op < G_N_ELEMENTS (klasses) && klasses[op] != NULL);
@@ -1509,7 +1997,7 @@ find_closest_point (const GskCurve *curve,
d = INFINITY;
t = (t1 + t2) / 2;
if (radius < 1)
if (fabs (t1 - t2) < 0.001 || radius < 1)
{
graphene_point_t p;
gsk_curve_get_point (curve, t, &p);

View File

@@ -33,6 +33,7 @@ typedef union _GskCurve GskCurve;
typedef struct _GskLineCurve GskLineCurve;
typedef struct _GskQuadCurve GskQuadCurve;
typedef struct _GskCubicCurve GskCubicCurve;
typedef struct _GskArcCurve GskArcCurve;
struct _GskLineCurve
{
@@ -65,12 +66,24 @@ struct _GskCubicCurve
graphene_point_t coeffs[4];
};
struct _GskArcCurve
{
GskPathOperation op;
gboolean has_matrix;
graphene_point_t points[3];
graphene_matrix_t m;
};
union _GskCurve
{
GskPathOperation op;
GskLineCurve line;
GskQuadCurve quad;
GskCubicCurve cubic;
GskArcCurve arc;
};
typedef enum {

View File

@@ -281,6 +281,10 @@ typedef enum {
* @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_ARC: A curve-to operation describing an elliptical arc with 3 points
* (more precisely, 2 points with their tangents). Note that an ellipse is not
* uniquely determined by this data; GTK picks the ellipse segment that is the
* affine transform of a quarter circle.
*
* Path operations are used to described segments of a `GskPath`.
*
@@ -294,6 +298,7 @@ typedef enum {
GSK_PATH_LINE,
GSK_PATH_QUAD,
GSK_PATH_CUBIC,
GSK_PATH_ARC,
} GskPathOperation;
/**

View File

@@ -178,7 +178,8 @@ gsk_path_get_flags (const GskPath *self)
* for printing.
*
* The string is compatible with
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData).
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData),
* see [func@Gsk.Path.parse] for a summary of the syntax.
*
* Since: 4.14
*/
@@ -249,21 +250,12 @@ gsk_path_to_cairo_add_op (GskPathOperation op,
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_QUAD:
case GSK_PATH_ARC:
default:
g_assert_not_reached ();
return FALSE;
@@ -749,14 +741,36 @@ gsk_path_foreach_trampoline (GskPathOperation op,
trampoline);
}
case GSK_PATH_ARC:
{
GskCurve curve;
if (trampoline->flags & GSK_PATH_FOREACH_ALLOW_ARC)
return trampoline->func (op, pts, n_pts, trampoline->user_data);
gsk_curve_init (&curve, gsk_pathop_encode (GSK_PATH_ARC, pts));
if (trampoline->flags & (GSK_PATH_FOREACH_ALLOW_CUBIC|GSK_PATH_FOREACH_ALLOW_QUAD))
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)
#define ALLOW_ANY (GSK_PATH_FOREACH_ALLOW_QUAD | \
GSK_PATH_FOREACH_ALLOW_CUBIC | \
GSK_PATH_FOREACH_ALLOW_ARC)
gboolean
gsk_path_foreach_with_tolerance (GskPath *self,
@@ -914,7 +928,7 @@ parse_command (const char **p,
if (*cmd == 'X')
allowed = "mM";
else
allowed = "mMhHvVzZlLcCsStTqQaA";
allowed = "mMhHvVzZlLcCsStTqQaAeE";
skip_whitespace (p);
s = _strchr (allowed, **p);
@@ -934,7 +948,7 @@ parse_command (const char **p,
* This is a convenience function that constructs a `GskPath`
* from a serialized form.
*
* The string is expected to be in
* The string is expected to be in (a superset of)
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData),
* as e.g. produced by [method@Gsk.Path.to_string].
*
@@ -950,10 +964,13 @@ parse_command (const char **p,
* - `T x2 y2` Add a quadratic Bézier, using the reflection of the previous segments' control point as control point
* - `S x2 y2 x3 y3` Add a cubic Bézier, using the reflection of the previous segments' second control point as first control point
* - `A rx ry r l s x y` Add an elliptical arc from the current point to `(x, y)` with radii rx and ry. See the SVG documentation for how the other parameters influence the arc.
* - `E x1 y1 x2 y2` Add an elliptical arc from the current point to `(x2, y2)` with tangents that are dermined by the point `(x1, y1)`.
*
* All the commands have lowercase variants that interpret coordinates
* relative to the current point.
*
* The `E` command is an extension that is not supported in SVG.
*
* Returns: (nullable): a new `GskPath`, or `NULL`
* if @string could not be parsed
*
@@ -1298,6 +1315,38 @@ gsk_path_parse (const char *string)
}
break;
case 'E':
case 'e':
{
double x1, y1, x2, y2;
if (parse_coordinate_pair (&p, &x1, &y1) &&
parse_coordinate_pair (&p, &x2, &y2))
{
if (cmd == 'e')
{
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_arc_to (builder, x1, y1, x2, y2);
prev_x1 = x1;
prev_y1 = y1;
x = x2;
y = y2;
}
else
goto error;
}
break;
default:
goto error;
}

View File

@@ -33,6 +33,7 @@ G_BEGIN_DECLS
* @GSK_PATH_FOREACH_ALLOW_ONLY_LINES: The default behavior, only allow lines.
* @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_ARC: Allow emission of `GSK_PATH_ARC` operations.
*
* Flags that can be passed to gsk_path_foreach() to enable additional
* features.
@@ -48,6 +49,7 @@ typedef enum
GSK_PATH_FOREACH_ALLOW_ONLY_LINES = 0,
GSK_PATH_FOREACH_ALLOW_QUAD = (1 << 0),
GSK_PATH_FOREACH_ALLOW_CUBIC = (1 << 1),
GSK_PATH_FOREACH_ALLOW_ARC = (1 << 2),
} GskPathForeachFlags;
/**

View File

@@ -25,7 +25,6 @@
#include "gskpathprivate.h"
#include "gskcontourprivate.h"
#include "gsksplineprivate.h"
/**
* GskPathBuilder:
@@ -502,64 +501,46 @@ gsk_path_builder_add_rounded_rect (GskPathBuilder *self,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width,
rect->bounds.origin.y);
/* topright corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_TOP_RIGHT].width,
rect->corner[GSK_CORNER_TOP_RIGHT].height,
0, FALSE, TRUE,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height);
gsk_path_builder_arc_to (self,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height);
/* right */
gsk_path_builder_line_to (self,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_RIGHT].height);
/* bottomright corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
rect->corner[GSK_CORNER_BOTTOM_RIGHT].height,
0, FALSE, TRUE,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
rect->bounds.origin.y + rect->bounds.size.height);
gsk_path_builder_arc_to (self,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->bounds.size.height,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
rect->bounds.origin.y + rect->bounds.size.height);
/* bottom */
gsk_path_builder_line_to (self,
rect->bounds.origin.x + rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
rect->bounds.origin.y + rect->bounds.size.height);
/* bottomleft corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
rect->corner[GSK_CORNER_BOTTOM_LEFT].height,
0, FALSE, TRUE,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height);
gsk_path_builder_arc_to (self,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->bounds.size.height,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height);
/* left */
gsk_path_builder_line_to (self,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_LEFT].height);
/* topleft corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->corner[GSK_CORNER_TOP_LEFT].height,
0, FALSE, TRUE,
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->bounds.origin.y);
gsk_path_builder_arc_to (self,
rect->bounds.origin.x,
rect->bounds.origin.y,
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->bounds.origin.y);
/* done */
gsk_path_builder_close (self);
self->current_point = current;
}
static gboolean
circle_contour_curve (const graphene_point_t pts[4],
gpointer data)
{
GskPathBuilder *self = data;
gsk_path_builder_cubic_to (self,
pts[1].x, pts[1].y,
pts[2].x, pts[2].y,
pts[3].x, pts[3].y);
return TRUE;
}
/**
* gsk_path_builder_add_circle:
* @self: a `GskPathBuilder`
@@ -586,11 +567,19 @@ gsk_path_builder_add_circle (GskPathBuilder *self,
current = self->current_point;
gsk_path_builder_move_to (self, center->x + radius, center->y);
gsk_spline_decompose_arc (center, radius,
GSK_PATH_TOLERANCE_DEFAULT,
0, 2 * M_PI,
circle_contour_curve, self);
// bottom right quarter
gsk_path_builder_arc_to (self, center->x + radius, center->y + radius,
center->x, center->y + radius);
// bottom left quarter
gsk_path_builder_arc_to (self, center->x - radius, center->y + radius,
center->x - radius, center->y);
// top left quarter
gsk_path_builder_arc_to (self, center->x - radius, center->y - radius,
center->x, center->y - radius);
// top right quarter
gsk_path_builder_arc_to (self, center->x + radius, center->y - radius,
center->x + radius, center->y);
// done
gsk_path_builder_close (self);
self->current_point = current;
}
@@ -861,6 +850,120 @@ gsk_path_builder_rel_cubic_to (GskPathBuilder *self,
self->current_point.y + y3);
}
/* Return the angle between t1 and t2 in radians, such that
* 0 means straight continuation
* < 0 means right turn
* > 0 means left turn
*/
static float
angle_between (const graphene_vec2_t *t1,
const graphene_vec2_t *t2)
{
float angle = atan2 (graphene_vec2_get_y (t2), graphene_vec2_get_x (t2))
- atan2 (graphene_vec2_get_y (t1), graphene_vec2_get_x (t1));
if (angle > M_PI)
angle -= 2 * M_PI;
if (angle < - M_PI)
angle += 2 * M_PI;
return angle;
}
#define RAD_TO_DEG(r) ((r)*180.f/M_PI)
#define DEG_TO_RAD(d) ((d)*M_PI/180.f)
static float
angle_between_points (const graphene_point_t *c,
const graphene_point_t *a,
const graphene_point_t *b)
{
graphene_vec2_t t1, t2;
graphene_vec2_init (&t1, a->x - c->x, a->y - c->y);
graphene_vec2_init (&t2, b->x - c->x, b->y - c->y);
return (float) RAD_TO_DEG (angle_between (&t1, &t2));
}
/**
* gsk_path_builder_arc_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
*
* Adds an elliptical arc from the current point to @x3, @y3
* with @x1, @y1 determining the tangent directions.
*
* After this, @x3, @y3 will be the new current point.
*
* <picture>
* <source srcset="arc-dark.png" media="(prefers-color-scheme: dark)">
* <img alt="Arc To" src="arc-light.png">
* </picture>
*
* Since: 4.14
*/
void
gsk_path_builder_arc_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2)
{
g_return_if_fail (self != NULL);
if (fabsf (angle_between_points (&GRAPHENE_POINT_INIT (x1, y1),
&self->current_point,
&GRAPHENE_POINT_INIT (x2, y2))) < 3)
{
gsk_path_builder_line_to (self, x2, y2);
return;
}
self->flags &= ~GSK_PATH_FLAT;
gsk_path_builder_append_current (self,
GSK_PATH_ARC,
2, (graphene_point_t[2]) {
GRAPHENE_POINT_INIT (x1, y1),
GRAPHENE_POINT_INIT (x2, y2),
});
}
/**
* gsk_path_builder_rel_arc_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
*
* Adds an elliptical arc from the current point to @x3, @y3
* with @x1, @y1 determining the tangent directions. All coordinates
* are given relative to the current point.
*
* This is the relative version of [method@Gsk.PathBuilder.arc_to].
*
* Since: 4.14
*/
void
gsk_path_builder_rel_arc_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2)
{
g_return_if_fail (self != NULL);
gsk_path_builder_arc_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_close:
* @self: a `GskPathBuilder`
@@ -938,6 +1041,25 @@ _sincos (double angle,
#endif
}
/**
* gsk_path_builder_svg_arc_to:
* @self: a `GskPathBuilder`
* @rx: X radius
* @ry: Y radius
* @x_axis_rotation: the rotation of the ellipsis
* @large_arc: whether to add the large arc
* @positive_sweep: whether to sweep in the positive direction
* @x: the X coordinate of the endpoint
* @y: the Y coordinate of the endpoint
*
* Implements arc-to according to the SVG spec.
*
* A convenience function that implements the
* [SVG arc_to](https://www.w3.org/TR/SVG11/paths.html#PathDataEllipticalArcCommands)
* functionality.
*
* Since: 4.14
*/
void
gsk_path_builder_svg_arc_to (GskPathBuilder *self,
float rx,
@@ -970,6 +1092,8 @@ gsk_path_builder_svg_arc_to (GskPathBuilder *self,
double th_half;
double t;
g_return_if_fail (self != NULL);
if (self->points->len > 0)
{
current = &g_array_index (self->points, graphene_point_t, self->points->len - 1);
@@ -1069,6 +1193,67 @@ gsk_path_builder_svg_arc_to (GskPathBuilder *self,
}
}
/**
* gsk_path_builder_html_arc_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
* @radius: Radius of the circle
*
* Implements arc-to according to the HTML Canvas spec.
*
* A convenience function that implements the
* [HTML arc_to](https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-arcto-dev)
* functionality.
*
* Since: 4.14
*/
void
gsk_path_builder_html_arc_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float radius)
{
float angle, b;
graphene_vec2_t t;
graphene_point_t p, q;
g_return_if_fail (self != NULL);
g_return_if_fail (radius > 0);
angle = angle_between_points (&GRAPHENE_POINT_INIT (x1, y1),
&self->current_point,
&GRAPHENE_POINT_INIT (x2, y2));
if (fabsf (angle) < 3)
{
gsk_path_builder_line_to (self, x2, y2);
return;
}
b = radius / tanf (fabsf ((float) DEG_TO_RAD (angle / 2)));
graphene_vec2_init (&t, self->current_point.x - x1, self->current_point.y - y1);
graphene_vec2_normalize (&t, &t);
p.x = x1 + b * graphene_vec2_get_x (&t);
p.y = y1 + b * graphene_vec2_get_y (&t);
graphene_vec2_init (&t, x2 - x1, y2 - y1);
graphene_vec2_normalize (&t, &t);
q.x = x1 + b * graphene_vec2_get_x (&t);
q.y = y1 + b * graphene_vec2_get_y (&t);
gsk_path_builder_line_to (self, p.x, p.y);
gsk_path_builder_svg_arc_to (self, radius, radius, 0, FALSE, angle < 0, q.x, q.y);
}
/**
* gsk_path_builder_add_layout:
* @self: a #GskPathBuilder

View File

@@ -121,6 +121,37 @@ void gsk_path_builder_rel_cubic_to (GskPathBuilder
float x3,
float y3);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_arc_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_rel_arc_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2);
GDK_AVAILABLE_IN_4_14
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);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_html_arc_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2,
float radius);
GDK_AVAILABLE_IN_4_14
void gsk_path_builder_close (GskPathBuilder *self);
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPathBuilder, gsk_path_builder_unref)

View File

@@ -94,6 +94,9 @@ gsk_pathop_foreach (gskpathop pop,
case GSK_PATH_CUBIC:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 4, user_data);
case GSK_PATH_ARC:
return func (gsk_pathop_op (pop), gsk_pathop_points (pop), 3, user_data);
default:
g_assert_not_reached ();
return TRUE;
@@ -128,6 +131,10 @@ gsk_path_builder_pathop_to (GskPathBuilder *builder,
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_ARC:
gsk_path_builder_arc_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
break;
default:
g_assert_not_reached ();
break;
@@ -162,6 +169,10 @@ gsk_path_builder_pathop_reverse_to (GskPathBuilder *builder,
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_ARC:
gsk_path_builder_arc_to (builder, pts[1].x, pts[1].y, pts[0].x, pts[0].y);
break;
default:
g_assert_not_reached ();
break;

View File

@@ -56,15 +56,5 @@ gboolean gsk_path_foreach_with_tolerance (GskPath
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

View File

@@ -145,6 +145,28 @@ test_curve_crossing (void)
}
}
static void
test_arc (void)
{
GskCurve c;
graphene_point_t p;
parse_curve (&c, "M 1 0 E 1 1 0 1");
g_assert_true (graphene_point_equal (gsk_curve_get_start_point (&c), &GRAPHENE_POINT_INIT (1, 0)));
g_assert_true (graphene_point_equal (gsk_curve_get_end_point (&c), &GRAPHENE_POINT_INIT (0, 1)));
gsk_curve_get_point (&c, 0.5, &p);
g_assert_true (graphene_point_near (&p,
&GRAPHENE_POINT_INIT (cos (M_PI/4), sin (M_PI/4)), 0.001));
parse_curve (&c, "M 100 100 E 200 100 200 200");
g_assert_true (graphene_point_equal (gsk_curve_get_start_point (&c), &GRAPHENE_POINT_INIT (100, 100)));
g_assert_true (graphene_point_equal (gsk_curve_get_end_point (&c), &GRAPHENE_POINT_INIT (200, 200)));
gsk_curve_get_point (&c, 0.5, &p);
g_assert_true (graphene_point_near (&p,
&GRAPHENE_POINT_INIT (100 + 100 * sin (M_PI/4), 100 + 100 * (1 - cos (M_PI/4))), 0.001));
}
int
main (int argc,
char *argv[])
@@ -154,6 +176,7 @@ main (int argc,
g_test_add_func ("/curve/special/tangents", test_curve_tangents);
g_test_add_func ("/curve/special/degenerate-tangents", test_curve_degenerate_tangents);
g_test_add_func ("/curve/special/crossing", test_curve_crossing);
g_test_add_func ("/curve/special/arc", test_arc);
return g_test_run ();
}

View File

@@ -249,6 +249,9 @@ test_curve_decompose_into (GskPathForeachFlags flags)
case GSK_PATH_CUBIC:
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_CUBIC);
break;
case GSK_PATH_ARC:
g_assert_true (flags & GSK_PATH_FOREACH_ALLOW_ARC);
break;
default:
g_assert_not_reached ();
}

View File

@@ -373,6 +373,11 @@ collect_path (GskPathOperation op,
pts[3].x, pts[3].y);
break;
case GSK_PATH_ARC:
gsk_path_builder_arc_to (builder, pts[1].x, pts[1].y,
pts[2].x, pts[2].y);
break;
default:
g_assert_not_reached ();
}
@@ -689,6 +694,118 @@ test_path_builder_add (void)
gsk_path_unref (path);
}
static gboolean
rotate_path_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
gpointer user_data)
{
GskPathBuilder **builders = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_path_builder_move_to (builders[0], pts[0].x, pts[0].y);
gsk_path_builder_move_to (builders[1], pts[0].y, -pts[0].x);
break;
case GSK_PATH_CLOSE:
gsk_path_builder_close (builders[0]);
gsk_path_builder_close (builders[1]);
break;
case GSK_PATH_LINE:
gsk_path_builder_line_to (builders[0], pts[1].x, pts[1].y);
gsk_path_builder_line_to (builders[1], pts[1].y, -pts[1].x);
break;
case GSK_PATH_QUAD:
gsk_path_builder_quad_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
gsk_path_builder_quad_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
break;
case GSK_PATH_CUBIC:
gsk_path_builder_cubic_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y, pts[3].x, pts[3].y);
gsk_path_builder_cubic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, pts[3].y, -pts[3].x);
break;
case GSK_PATH_ARC:
gsk_path_builder_arc_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
gsk_path_builder_arc_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
break;
default:
g_assert_not_reached ();
return FALSE;
}
return TRUE;
}
static void
test_rotated_arc (void)
{
GskPath *path;
GskPathBuilder *builders[2];
GskPath *paths[2];
float x, y;
GskFillRule fill_rule;
path = gsk_path_parse ("M -963 186 E -375 -757, 537 -607");
x = -626;
y = -274;
builders[0] = gsk_path_builder_new ();
builders[1] = gsk_path_builder_new ();
/* Use -1 here because we want all the flags, even future additions */
gsk_path_foreach (path, -1, rotate_path_cb, builders);
gsk_path_unref (path);
paths[0] = gsk_path_builder_free_to_path (builders[0]);
paths[1] = gsk_path_builder_free_to_path (builders[1]);
fill_rule = GSK_FILL_RULE_EVEN_ODD;
g_assert_true (gsk_path_in_fill (paths[0], &GRAPHENE_POINT_INIT (x, y), fill_rule)
==
gsk_path_in_fill (paths[1], &GRAPHENE_POINT_INIT (y, -x), fill_rule));
gsk_path_unref (paths[0]);
gsk_path_unref (paths[1]);
}
static void
test_rounded_rect (void)
{
GskRoundedRect rect;
GskPathBuilder *builder;
GskPath *path;
gsk_rounded_rect_init (&rect, &GRAPHENE_RECT_INIT (10, 10, 100, 50),
&GRAPHENE_SIZE_INIT (0, 0),
&GRAPHENE_SIZE_INIT (10, 10),
&GRAPHENE_SIZE_INIT (10, 30),
&GRAPHENE_SIZE_INIT (30, 0));
builder = gsk_path_builder_new ();
gsk_path_builder_add_rounded_rect (builder, &rect);
path = gsk_path_builder_free_to_path (builder);
for (int i = 0; i < 1000; i++)
{
graphene_point_t p = GRAPHENE_POINT_INIT (g_test_rand_double_range (0, 200),
g_test_rand_double_range (0, 200));
g_assert_true (gsk_rounded_rect_contains_point (&rect, &p) == gsk_path_in_fill (path, &p, GSK_FILL_RULE_WINDING));
}
gsk_path_unref (path);
}
int
main (int argc, char *argv[])
{
@@ -703,6 +820,8 @@ main (int argc, char *argv[])
g_test_add_func ("/path/bad-in-fill", test_bad_in_fill);
g_test_add_func ("/path/unclosed-in-fill", test_unclosed_in_fill);
g_test_add_func ("/path/builder/add", test_path_builder_add);
g_test_add_func ("/path/rotated-arc", test_rotated_arc);
g_test_add_func ("/path/rounded-rect", test_rounded_rect);
return g_test_run ();
}

View File

@@ -225,7 +225,7 @@ add_standard_contour (GskPathBuilder *builder)
n = g_test_rand_int_range (1, 20);
for (i = 0; i < n; i++)
{
switch (g_test_rand_int_range (0, 6))
switch (g_test_rand_int_range (0, 8))
{
case 0:
gsk_path_builder_line_to (builder,
@@ -275,6 +275,22 @@ add_standard_contour (GskPathBuilder *builder)
g_test_rand_double_range (-1000, 1000));
break;
case 6:
gsk_path_builder_arc_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
break;
case 7:
gsk_path_builder_rel_arc_to (builder,
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000),
g_test_rand_double_range (-1000, 1000));
break;
default:
g_assert_not_reached();
break;
@@ -371,6 +387,13 @@ path_operation_print (const PathOperation *p,
_g_string_append_point (string, &p->pts[3]);
break;
case GSK_PATH_ARC:
g_string_append (string, " E ");
_g_string_append_point (string, &p->pts[1]);
g_string_append (string, ", ");
_g_string_append_point (string, &p->pts[2]);
break;
default:
g_assert_not_reached();
return;
@@ -405,6 +428,10 @@ path_operation_equal (const PathOperation *p1,
&& graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon)
&& graphene_point_near (&p1->pts[3], &p2->pts[3], epsilon);
case GSK_PATH_ARC:
return graphene_point_near (&p1->pts[1], &p2->pts[1], epsilon)
&& graphene_point_near (&p1->pts[2], &p2->pts[2], epsilon);
default:
g_return_val_if_reached (FALSE);
}
@@ -689,6 +716,11 @@ rotate_path_cb (GskPathOperation op,
gsk_path_builder_cubic_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x, pts[3].y, -pts[3].x);
break;
case GSK_PATH_ARC:
gsk_path_builder_arc_to (builders[0], pts[1].x, pts[1].y, pts[2].x, pts[2].y);
gsk_path_builder_arc_to (builders[1], pts[1].y, -pts[1].x, pts[2].y, -pts[2].x);
break;
default:
g_assert_not_reached ();
return FALSE;
@@ -727,7 +759,7 @@ test_in_fill_rotated (void)
GskFillRule fill_rule = g_random_int_range (0, N_FILL_RULES);
float x = g_test_rand_double_range (-1000, 1000);
float y = g_test_rand_double_range (-1000, 1000);
g_assert_cmpint (gsk_path_in_fill (paths[0], &GRAPHENE_POINT_INIT (x, y), fill_rule),
==,
gsk_path_in_fill (paths[1], &GRAPHENE_POINT_INIT (y, -x), fill_rule));

View File

@@ -56,6 +56,10 @@ foreach_cb (GskPathOperation op,
pts[3].x, pts[3].y);
break;
case GSK_PATH_ARC:
gsk_path_builder_arc_to (builder, pts[1].x, pts[1].y, pts[2].x, pts[2].y);
break;
default:
g_assert_not_reached ();
}
@@ -68,12 +72,14 @@ do_decompose (int *argc, const char ***argv)
{
GError *error = NULL;
gboolean allow_quad = FALSE;
gboolean allow_curve = FALSE;
gboolean allow_cubic = FALSE;
gboolean allow_arc = FALSE;
char **args = NULL;
GOptionContext *context;
GOptionEntry entries[] = {
{ "allow-quad", 0, 0, G_OPTION_ARG_NONE, &allow_quad, N_("Allow quadratic Bézier curves"), NULL },
{ "allow-cubic", 0, 0, G_OPTION_ARG_NONE, &allow_curve, N_("Allow cubic Bézier curves"), NULL },
{ "allow-cubic", 0, 0, G_OPTION_ARG_NONE, &allow_cubic, N_("Allow cubic Bézier curves"), NULL },
{ "allow-arc", 0, 0, G_OPTION_ARG_NONE, &allow_arc, N_("Allow elliptical arcs"), NULL },
{ G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args, NULL, N_("PATH") },
{ NULL, },
};
@@ -108,8 +114,10 @@ do_decompose (int *argc, const char ***argv)
flags = 0;
if (allow_quad)
flags |= GSK_PATH_FOREACH_ALLOW_QUAD;
if (allow_curve)
if (allow_cubic)
flags |= GSK_PATH_FOREACH_ALLOW_CUBIC;
if (allow_arc)
flags |= GSK_PATH_FOREACH_ALLOW_ARC;
builder = gsk_path_builder_new ();

View File

@@ -31,6 +31,7 @@ typedef struct
int lines;
int quads;
int cubics;
int arcs;
} Statistics;
static gboolean
@@ -58,6 +59,9 @@ stats_cb (GskPathOperation op,
case GSK_PATH_CUBIC:
stats->cubics++;
break;
case GSK_PATH_ARC:
stats->arcs++;
break;
default:
g_assert_not_reached ();
}
@@ -74,6 +78,7 @@ collect_statistics (GskPath *path,
stats->lines = 0;
stats->quads = 0;
stats->cubics = 0;
stats->arcs = 0;
gsk_path_foreach (path, -1, stats_cb, stats);
}
@@ -150,5 +155,10 @@ do_info (int *argc, const char ***argv)
g_print (_("%d cubics"), stats.cubics);
g_print ("\n");
}
if (stats.arcs)
{
g_print (_("%d arcs"), stats.arcs);
g_print ("\n");
}
}
}