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. + * + * + * + * 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"); + } } }