mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-08 17:50:10 +00:00
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:
parent
43c8e136b5
commit
d33ed4f9ab
@ -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",
|
||||
|
BIN
docs/reference/gsk/images/arc-dark.png
Normal file
BIN
docs/reference/gsk/images/arc-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
docs/reference/gsk/images/arc-light.png
Normal file
BIN
docs/reference/gsk/images/arc-light.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.4 KiB |
92
docs/reference/gsk/images/arc.svg
Normal file
92
docs/reference/gsk/images/arc.svg
Normal 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 |
@ -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
|
||||
^^^^^^^
|
||||
|
||||
|
@ -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;
|
||||
|
494
gsk/gskcurve.c
494
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);
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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`
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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 ();
|
||||
}
|
||||
|
@ -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 ();
|
||||
}
|
||||
|
@ -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 ();
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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 ();
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user