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.
This commit is contained in:
Matthias Clasen 2023-08-18 13:32:37 -04:00
parent 43c8e136b5
commit d33ed4f9ab
20 changed files with 1037 additions and 54 deletions

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

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

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

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

View File

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

View File

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

View File

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

View File

@ -281,6 +281,10 @@ typedef enum {
* @GSK_PATH_CUBIC: A curve-to operation describing a cubic Bézier curve with 4
* points describing the start point, the two control points and the end point
* of the curve.
* @GSK_PATH_ARC: A curve-to operation describing an elliptical arc with 3 points
* (more precisely, 2 points with their tangents). Note that an ellipse is not
* uniquely determined by this data; GTK picks the 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;
/**

View File

@ -178,7 +178,8 @@ gsk_path_get_flags (const GskPath *self)
* for printing.
*
* The string is compatible with
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData).
* [SVG path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData),
* see [func@Gsk.Path.parse] for a summary of the syntax.
*
* Since: 4.14
*/
@ -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;
}

View File

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

View File

@ -25,7 +25,6 @@
#include "gskpathprivate.h"
#include "gskcontourprivate.h"
#include "gsksplineprivate.h"
/**
* GskPathBuilder:
@ -502,64 +501,46 @@ gsk_path_builder_add_rounded_rect (GskPathBuilder *self,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_TOP_RIGHT].width,
rect->bounds.origin.y);
/* topright corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_TOP_RIGHT].width,
rect->corner[GSK_CORNER_TOP_RIGHT].height,
0, FALSE, TRUE,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height);
gsk_path_builder_arc_to (self,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_RIGHT].height);
/* right */
gsk_path_builder_line_to (self,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_RIGHT].height);
/* bottomright corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
rect->corner[GSK_CORNER_BOTTOM_RIGHT].height,
0, FALSE, TRUE,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
rect->bounds.origin.y + rect->bounds.size.height);
gsk_path_builder_arc_to (self,
rect->bounds.origin.x + rect->bounds.size.width,
rect->bounds.origin.y + rect->bounds.size.height,
rect->bounds.origin.x + rect->bounds.size.width - rect->corner[GSK_CORNER_BOTTOM_RIGHT].width,
rect->bounds.origin.y + rect->bounds.size.height);
/* bottom */
gsk_path_builder_line_to (self,
rect->bounds.origin.x + rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
rect->bounds.origin.y + rect->bounds.size.height);
/* bottomleft corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_BOTTOM_LEFT].width,
rect->corner[GSK_CORNER_BOTTOM_LEFT].height,
0, FALSE, TRUE,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height);
gsk_path_builder_arc_to (self,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->bounds.size.height,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->bounds.size.height - rect->corner[GSK_CORNER_BOTTOM_LEFT].height);
/* left */
gsk_path_builder_line_to (self,
rect->bounds.origin.x,
rect->bounds.origin.y + rect->corner[GSK_CORNER_TOP_LEFT].height);
/* topleft corner */
gsk_path_builder_svg_arc_to (self,
rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->corner[GSK_CORNER_TOP_LEFT].height,
0, FALSE, TRUE,
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->bounds.origin.y);
gsk_path_builder_arc_to (self,
rect->bounds.origin.x,
rect->bounds.origin.y,
rect->bounds.origin.x + rect->corner[GSK_CORNER_TOP_LEFT].width,
rect->bounds.origin.y);
/* done */
gsk_path_builder_close (self);
self->current_point = current;
}
static gboolean
circle_contour_curve (const graphene_point_t pts[4],
gpointer data)
{
GskPathBuilder *self = data;
gsk_path_builder_cubic_to (self,
pts[1].x, pts[1].y,
pts[2].x, pts[2].y,
pts[3].x, pts[3].y);
return TRUE;
}
/**
* gsk_path_builder_add_circle:
* @self: a `GskPathBuilder`
@ -586,11 +567,19 @@ gsk_path_builder_add_circle (GskPathBuilder *self,
current = self->current_point;
gsk_path_builder_move_to (self, center->x + radius, center->y);
gsk_spline_decompose_arc (center, radius,
GSK_PATH_TOLERANCE_DEFAULT,
0, 2 * M_PI,
circle_contour_curve, self);
// bottom right quarter
gsk_path_builder_arc_to (self, center->x + radius, center->y + radius,
center->x, center->y + radius);
// bottom left quarter
gsk_path_builder_arc_to (self, center->x - radius, center->y + radius,
center->x - radius, center->y);
// top left quarter
gsk_path_builder_arc_to (self, center->x - radius, center->y - radius,
center->x, center->y - radius);
// top right quarter
gsk_path_builder_arc_to (self, center->x + radius, center->y - radius,
center->x + radius, center->y);
// done
gsk_path_builder_close (self);
self->current_point = current;
}
@ -861,6 +850,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.
*
* <picture>
* <source srcset="arc-dark.png" media="(prefers-color-scheme: dark)">
* <img alt="Arc To" src="arc-light.png">
* </picture>
*
* Since: 4.14
*/
void
gsk_path_builder_arc_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2)
{
g_return_if_fail (self != NULL);
if (fabsf (angle_between_points (&GRAPHENE_POINT_INIT (x1, y1),
&self->current_point,
&GRAPHENE_POINT_INIT (x2, y2))) < 3)
{
gsk_path_builder_line_to (self, x2, y2);
return;
}
self->flags &= ~GSK_PATH_FLAT;
gsk_path_builder_append_current (self,
GSK_PATH_ARC,
2, (graphene_point_t[2]) {
GRAPHENE_POINT_INIT (x1, y1),
GRAPHENE_POINT_INIT (x2, y2),
});
}
/**
* gsk_path_builder_rel_arc_to:
* @self: a `GskPathBuilder`
* @x1: x coordinate of first control point
* @y1: y coordinate of first control point
* @x2: x coordinate of second control point
* @y2: y coordinate of second control point
*
* Adds an elliptical arc from the current point to @x3, @y3
* with @x1, @y1 determining the tangent directions. All coordinates
* are given relative to the current point.
*
* This is the relative version of [method@Gsk.PathBuilder.arc_to].
*
* Since: 4.14
*/
void
gsk_path_builder_rel_arc_to (GskPathBuilder *self,
float x1,
float y1,
float x2,
float y2)
{
g_return_if_fail (self != NULL);
gsk_path_builder_arc_to (self,
self->current_point.x + x1,
self->current_point.y + y1,
self->current_point.x + x2,
self->current_point.y + y2);
}
/**
* gsk_path_builder_close:
* @self: a `GskPathBuilder`

View File

@ -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)

View File

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

View File

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

View File

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

View File

@ -373,6 +373,11 @@ collect_path (GskPathOperation op,
pts[3].x, pts[3].y);
break;
case GSK_PATH_ARC:
gsk_path_builder_arc_to (builder, pts[1].x, pts[1].y,
pts[2].x, pts[2].y);
break;
default:
g_assert_not_reached ();
}
@ -689,6 +694,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 ();
}

View File

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

View File

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

View File

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