diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml
index 8aa02b53b7..6a1b760c4b 100644
--- a/demos/gtk-demo/demo.gresource.xml
+++ b/demos/gtk-demo/demo.gresource.xml
@@ -336,6 +336,7 @@
panes.c
password_entry.c
path_fill.c
+ path_spinner.c
path_walk.c
peg_solitaire.c
pickers.c
diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build
index 22252bd375..dd8b4fd894 100644
--- a/demos/gtk-demo/meson.build
+++ b/demos/gtk-demo/meson.build
@@ -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',
diff --git a/demos/gtk-demo/path_spinner.c b/demos/gtk-demo/path_spinner.c
new file mode 100644
index 0000000000..cbe9fc148e
--- /dev/null
+++ b/demos/gtk-demo/path_spinner.c
@@ -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
+#include
+
+#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;
+}
diff --git a/docs/reference/gsk/gsk4.toml.in b/docs/reference/gsk/gsk4.toml.in
index e56a5b2ffc..be0d478958 100644
--- a/docs/reference/gsk/gsk4.toml.in
+++ b/docs/reference/gsk/gsk4.toml.in
@@ -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",
diff --git a/docs/reference/gsk/images/arc-dark.png b/docs/reference/gsk/images/arc-dark.png
new file mode 100644
index 0000000000..8e728d664b
Binary files /dev/null and b/docs/reference/gsk/images/arc-dark.png differ
diff --git a/docs/reference/gsk/images/arc-light.png b/docs/reference/gsk/images/arc-light.png
new file mode 100644
index 0000000000..1f0d5d2cb1
Binary files /dev/null and b/docs/reference/gsk/images/arc-light.png differ
diff --git a/docs/reference/gsk/images/arc.svg b/docs/reference/gsk/images/arc.svg
new file mode 100644
index 0000000000..9488b7036f
--- /dev/null
+++ b/docs/reference/gsk/images/arc.svg
@@ -0,0 +1,92 @@
+
+
+
+
diff --git a/docs/reference/gtk/gtk4-path-tool.rst b/docs/reference/gtk/gtk4-path-tool.rst
index 512be64edc..ab584ccaed 100644
--- a/docs/reference/gtk/gtk4-path-tool.rst
+++ b/docs/reference/gtk/gtk4-path-tool.rst
@@ -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
^^^^^^^
diff --git a/gsk/gskcontour.c b/gsk/gskcontour.c
index 90693f9429..a8ec1f60d3 100644
--- a/gsk/gskcontour.c
+++ b/gsk/gskcontour.c
@@ -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;
diff --git a/gsk/gskcurve.c b/gsk/gskcurve.c
index 2d4753e10c..198e1e8f23 100644
--- a/gsk/gskcurve.c
+++ b/gsk/gskcurve.c
@@ -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);
diff --git a/gsk/gskcurveprivate.h b/gsk/gskcurveprivate.h
index 0a6b43e270..090a28ab89 100644
--- a/gsk/gskcurveprivate.h
+++ b/gsk/gskcurveprivate.h
@@ -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 {
diff --git a/gsk/gskenums.h b/gsk/gskenums.h
index ebccc33976..2fcdb5c541 100644
--- a/gsk/gskenums.h
+++ b/gsk/gskenums.h
@@ -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;
/**
diff --git a/gsk/gskpath.c b/gsk/gskpath.c
index 5173b7b6a6..1f8d5059d7 100644
--- a/gsk/gskpath.c
+++ b/gsk/gskpath.c
@@ -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;
}
diff --git a/gsk/gskpath.h b/gsk/gskpath.h
index 293181fc6e..5ea38c29e2 100644
--- a/gsk/gskpath.h
+++ b/gsk/gskpath.h
@@ -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;
/**
diff --git a/gsk/gskpathbuilder.c b/gsk/gskpathbuilder.c
index 2933361728..36198b4c1f 100644
--- a/gsk/gskpathbuilder.c
+++ b/gsk/gskpathbuilder.c
@@ -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.
+ *
+ *
+ *
+ *
+ *
+ *
+ * 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
diff --git a/gsk/gskpathbuilder.h b/gsk/gskpathbuilder.h
index 5eda90b00a..fac40ba52b 100644
--- a/gsk/gskpathbuilder.h
+++ b/gsk/gskpathbuilder.h
@@ -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)
diff --git a/gsk/gskpathopprivate.h b/gsk/gskpathopprivate.h
index a679f7eb15..2b3ae613ce 100644
--- a/gsk/gskpathopprivate.h
+++ b/gsk/gskpathopprivate.h
@@ -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;
diff --git a/gsk/gskpathprivate.h b/gsk/gskpathprivate.h
index 9a889ba0a1..1daaafd965 100644
--- a/gsk/gskpathprivate.h
+++ b/gsk/gskpathprivate.h
@@ -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
diff --git a/testsuite/gsk/curve-special-cases.c b/testsuite/gsk/curve-special-cases.c
index 8c4bd131bc..68b470df55 100644
--- a/testsuite/gsk/curve-special-cases.c
+++ b/testsuite/gsk/curve-special-cases.c
@@ -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 ();
}
diff --git a/testsuite/gsk/curve.c b/testsuite/gsk/curve.c
index 5310324c93..24baebb440 100644
--- a/testsuite/gsk/curve.c
+++ b/testsuite/gsk/curve.c
@@ -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 ();
}
diff --git a/testsuite/gsk/path-special-cases.c b/testsuite/gsk/path-special-cases.c
index ed80371cf6..30230ba6b9 100644
--- a/testsuite/gsk/path-special-cases.c
+++ b/testsuite/gsk/path-special-cases.c
@@ -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 ();
}
diff --git a/testsuite/gsk/path.c b/testsuite/gsk/path.c
index ceff9f0a84..a5929ad02b 100644
--- a/testsuite/gsk/path.c
+++ b/testsuite/gsk/path.c
@@ -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));
diff --git a/tools/gtk-path-tool-decompose.c b/tools/gtk-path-tool-decompose.c
index 54da81e217..dbf381a7aa 100644
--- a/tools/gtk-path-tool-decompose.c
+++ b/tools/gtk-path-tool-decompose.c
@@ -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 ();
diff --git a/tools/gtk-path-tool-info.c b/tools/gtk-path-tool-info.c
index dc05ba910f..9444eb78fa 100644
--- a/tools/gtk-path-tool-info.c
+++ b/tools/gtk-path-tool-info.c
@@ -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");
+ }
}
}