From d33ed4f9ab7889cfff131b6a2c4aeaf594e65826 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 18 Aug 2023 13:32:37 -0400 Subject: [PATCH] path: Add elliptical arcs Add a new curve type for elliptical arcs and use it for rounded rectangles and circles. We use the 'E' command to represent elliptical arcs in serialized paths. --- docs/reference/gsk/gsk4.toml.in | 2 + docs/reference/gsk/images/arc-dark.png | Bin 0 -> 11699 bytes docs/reference/gsk/images/arc-light.png | Bin 0 -> 8624 bytes docs/reference/gsk/images/arc.svg | 92 +++++ docs/reference/gtk/gtk4-path-tool.rst | 4 + gsk/gskcontour.c | 7 + gsk/gskcurve.c | 494 +++++++++++++++++++++++- gsk/gskcurveprivate.h | 13 + gsk/gskenums.h | 5 + gsk/gskpath.c | 84 +++- gsk/gskpath.h | 2 + gsk/gskpathbuilder.c | 190 ++++++--- gsk/gskpathbuilder.h | 13 + gsk/gskpathopprivate.h | 11 + testsuite/gsk/curve-special-cases.c | 23 ++ testsuite/gsk/curve.c | 3 + testsuite/gsk/path-special-cases.c | 88 +++++ testsuite/gsk/path.c | 36 +- tools/gtk-path-tool-decompose.c | 14 +- tools/gtk-path-tool-info.c | 10 + 20 files changed, 1037 insertions(+), 54 deletions(-) create mode 100644 docs/reference/gsk/images/arc-dark.png create mode 100644 docs/reference/gsk/images/arc-light.png create mode 100644 docs/reference/gsk/images/arc.svg 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 0000000000000000000000000000000000000000..8e728d664bfdea4037a4afa66d8a45403bfa8cea GIT binary patch literal 11699 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 literal 0 HcmV?d00001 diff --git a/docs/reference/gsk/images/arc-light.png b/docs/reference/gsk/images/arc-light.png new file mode 100644 index 0000000000000000000000000000000000000000..1f0d5d2cb14601dc69156d103f5ae957237123c9 GIT binary patch literal 8624 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 + + + + + + + + + + + + + + + + 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. + * + * + * + * Arc To + * + * + * 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"); + } } }