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..84ecbb1013 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 quarter ellipse that is the
+ * 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..f370643a82 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
*/
@@ -229,6 +230,20 @@ gsk_path_to_string (GskPath *self)
return g_string_free (string, FALSE);
}
+static gboolean
+add_curve_func (GskPathOperation op,
+ const graphene_point_t *pts,
+ gsize n_pts,
+ gpointer user_data)
+{
+ cairo_t *cr = user_data;
+
+ g_assert (op == 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);
+
+ return TRUE;
+}
+
static gboolean
gsk_path_to_cairo_add_op (GskPathOperation op,
const graphene_point_t *pts,
@@ -264,6 +279,15 @@ gsk_path_to_cairo_add_op (GskPathOperation op,
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_ARC:
+ {
+ GskCurve curve;
+
+ gsk_curve_init_foreach (&curve, op, pts, n_pts);
+ gsk_curve_decompose_curve (&curve, GSK_PATH_FOREACH_ALLOW_CUBIC, 0.5, add_curve_func, cr);
+ }
+ break;
+
default:
g_assert_not_reached ();
return FALSE;
@@ -749,6 +773,27 @@ 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;
@@ -914,7 +959,7 @@ parse_command (const char **p,
if (*cmd == 'X')
allowed = "mM";
else
- allowed = "mMhHvVzZlLcCsStTqQaA";
+ allowed = "mMhHvVzZlLcCsStTqQaAeE";
skip_whitespace (p);
s = _strchr (allowed, **p);
@@ -950,10 +995,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 +1346,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..e6857834dc 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,119 @@ 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.0/M_PI)
+
+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 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`
diff --git a/gsk/gskpathbuilder.h b/gsk/gskpathbuilder.h
index 5eda90b00a..74ff5ce877 100644
--- a/gsk/gskpathbuilder.h
+++ b/gsk/gskpathbuilder.h
@@ -121,6 +121,19 @@ 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_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/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..19624d986f 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,88 @@ 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]);
+}
+
int
main (int argc, char *argv[])
{
@@ -703,6 +790,7 @@ 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);
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");
+ }
}
}