1 /* CPML - Cairo Path Manipulation Library
2 * Copyright (C) 2007-2021 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
25 * @short_description: Manipulation of circular arcs
27 * The following functions manipulate %CPML_ARC #CpmlPrimitive.
28 * No validation is made on the input so use the following methods
29 * only when you are sure the <varname>primitive</varname> argument
30 * is effectively an arc-to.
32 * The arc primitive is defined by 3 points: the first one is the usual
33 * implicit point got from the previous primitive, the second point is
34 * an arbitrary intermediate point laying on the arc and the third point
35 * is the end of the arc. These points identify univocally an arc:
36 * furthermore, the intermediate point also gives the side of
39 * As a special case, when the first point is coincident with the end
40 * point the primitive is considered a circle with diameter defined by
41 * the segment between the first and the intermediate point.
45 * An arc is not a native cairo primitive and should be treated specially.
49 * Using these CPML APIs you are free to use %CPML_ARC whenever
50 * you want but, if you are directly accessing the struct fields, you
51 * are responsible of converting arcs to curves before passing them
52 * to cairo. In other words, do not directly feed #cairo_data_path_t
53 * got from CPML to cairo (i.e. by using cairo_append_path()) or at
54 * least do not expect it will work if an arc is present.
56 * The conversion is provided by two APIs: cpml_arc_to_cairo() and
57 * cpml_arc_to_curves(). The former directly renders to a cairo context
58 * and is internally used by all the ..._to_cairo() functions when an
59 * arc is met. The latter provided a more powerful (and more complex)
60 * approach as it allows to specify the number of curves to use and do
61 * not need a cairo context.
66 * <listitem>the <function>get_closest_pos</function> method must be
67 * implemented;</listitem>
68 * <listitem>the <function>put_intersections</function> method must
69 * implement arc-arc intersections.</listitem>
77 #include "cpml-internal.h"
78 #include "cpml-extents.h"
79 #include "cpml-segment.h"
80 #include "cpml-primitive.h"
81 #include "cpml-primitive-private.h"
83 #include "cpml-curve.h"
87 /* Hardcoded max angle of the arc to be approximated by a Bézier curve:
88 * this influence the arc quality (the default value is got from cairo) */
89 #define ARC_MAX_ANGLE M_PI_2
91 /* Macro to save typing and make put_extents() code cleaner */
92 #define ANGLE_INCLUDED(d) \
93 ((start < (d) && end > (d)) || (start > (d) && end < (d)))
96 static double get_length (const CpmlPrimitive
*arc
);
97 static void put_extents (const CpmlPrimitive
*arc
,
98 CpmlExtents
*extents
);
99 static void put_pair_at (const CpmlPrimitive
*arc
,
102 static void put_vector_at (const CpmlPrimitive
*arc
,
105 static size_t put_intersections (const CpmlPrimitive
*line
,
106 const CpmlPrimitive
*primitive
,
109 static void offset (CpmlPrimitive
*arc
,
111 static int get_center (const CpmlPair
*p
,
113 static void get_angles (const CpmlPair
*p
,
114 const CpmlPair
*center
,
117 static void arc_to_curve (CpmlPrimitive
*curve
,
118 const CpmlPair
*center
,
122 static int circle_line (const CpmlPair
*center
,
129 const _CpmlPrimitiveClass
*
130 _cpml_arc_get_class(void)
132 static _CpmlPrimitiveClass
*p_class
= NULL
;
134 if (p_class
== NULL
) {
135 static _CpmlPrimitiveClass class_data
= {
146 p_class
= &class_data
;
155 * @arc: (in): the #CpmlPrimitive arc data
156 * @center: (out) (allow-none): where to store the center coordinates
157 * @r: (out) (allow-none): where to store the radius
158 * @start: (out) (allow-none): where to store the starting angle
159 * @end: (out) (allow-none): where to store the ending angle
161 * Given an @arc, this function calculates and returns its basic data.
162 * Any pointer can be <constant>NULL</constant>, in which case the requested
163 * info is not returned. This function can fail (when the three points lay on
164 * a straight line, for example) in which case 0 is returned and no data can
165 * be considered valid.
167 * The radius @r can be 0 when the three points are coincidents: a
168 * circle with radius 0 is considered a valid path.
170 * When the start and end angle are returned, together with their
171 * values these angles implicitely gives another important information:
174 * If @start < @end the arc must be rendered with increasing angle
175 * value (clockwise direction using the ordinary cairo coordinate
176 * system) while if @start > @end the arc must be rendered in reverse
177 * order (that is counterclockwise in the cairo world). This is the
178 * reason the angle values are returned in the range <constant>-M_PI
179 * < <varname>value</varname> < 3 M_PI</constant> inclusive instead of
180 * the usual <constant>-M_PI < <varname>value</varname> < M_PI</constant>.
182 * Returns: (type boolean): 1 if the function worked succesfully, 0 on errors.
187 cpml_arc_info(const CpmlPrimitive
*arc
, CpmlPair
*center
,
188 double *r
, double *start
, double *end
)
190 CpmlPair p
[3], l_center
;
192 cpml_pair_from_cairo(&p
[0], arc
->org
);
193 cpml_pair_from_cairo(&p
[1], &arc
->data
[1]);
194 cpml_pair_from_cairo(&p
[2], &arc
->data
[2]);
196 if (! get_center(p
, &l_center
))
203 *r
= cpml_pair_distance(&p
[0], &l_center
);
205 if (start
!= NULL
|| end
!= NULL
) {
206 double l_start
, l_end
;
208 get_angles(p
, &l_center
, &l_start
, &l_end
);
221 * @arc: (in): the #CpmlPrimitive arc data
222 * @cr: (inout): the destination cairo context
224 * Renders @arc to the @cr cairo context. As cairo does not support
225 * arcs natively, it is approximated using one or more Bézier curves.
227 * The number of curves used is dependent from the angle of the arc.
228 * Anyway, this function uses internally the hardcoded M_PI_2 value
229 * as threshold value. This means the maximum arc approximated by a
230 * single curve will be a quarter of a circle and, consequently, a
231 * whole circle will be approximated by 4 Bézier curves.
236 cpml_arc_to_cairo(const CpmlPrimitive
*arc
, cairo_t
*cr
)
239 double r
, start
, end
;
243 cairo_path_data_t data
[4];
245 if (!cpml_arc_info(arc
, ¢er
, &r
, &start
, &end
))
248 n_curves
= ceil(fabs(end
-start
) / ARC_MAX_ANGLE
);
249 step
= (end
-start
) / (double) n_curves
;
252 for (angle
= start
; n_curves
--; angle
+= step
) {
253 arc_to_curve(&curve
, ¢er
, r
, angle
, angle
+step
);
255 curve
.data
[1].point
.x
, curve
.data
[1].point
.y
,
256 curve
.data
[2].point
.x
, curve
.data
[2].point
.y
,
257 curve
.data
[3].point
.x
, curve
.data
[3].point
.y
);
262 * cpml_arc_to_curves:
263 * @arc: (in): the #CpmlPrimitive arc data
264 * @segment: (out): the destination #CpmlSegment
265 * @n_curves: (in): number of Bézier to use
267 * Converts @arc to a serie of @n_curves Bézier curves and puts them
268 * inside @segment. Obviously, @segment must have enough space to
269 * contain at least @n_curves curves.
271 * This function works in a similar way as cpml_arc_to_cairo() but
272 * has two important differences: it does not need a cairo context
273 * and the number of curves to be generated is explicitely defined.
274 * The latter difference allows a more specific error control from
275 * the application: in the file src/cairo-arc.c, found in the cairo
276 * tarball (at least in cairo-1.9.1), there is a table showing the
277 * magnitude of error of this curve approximation algorithm.
282 cpml_arc_to_curves(const CpmlPrimitive
*arc
, CpmlSegment
*segment
,
286 double r
, start
, end
;
290 if (!cpml_arc_info(arc
, ¢er
, &r
, &start
, &end
))
293 step
= (end
-start
) / (double) n_curves
;
294 segment
->num_data
= n_curves
*4;
295 curve
.segment
= segment
;
296 curve
.data
= segment
->data
;
298 for (angle
= start
; n_curves
--; angle
+= step
) {
299 arc_to_curve(&curve
, ¢er
, r
, angle
, angle
+step
);
306 get_length(const CpmlPrimitive
*arc
)
308 double r
, start
, end
, delta
;
310 if (!cpml_arc_info(arc
, NULL
, &r
, &start
, &end
) || start
== end
)
321 put_extents(const CpmlPrimitive
*arc
, CpmlExtents
*extents
)
323 double r
, start
, end
;
324 CpmlPair center
, pair
;
326 extents
->is_defined
= 0;
328 if (!cpml_arc_info(arc
, ¢er
, &r
, &start
, &end
))
331 /* Add the right quadrant point if needed */
332 if (ANGLE_INCLUDED(0) || ANGLE_INCLUDED(M_PI
* 2)) {
333 pair
.x
= center
.x
+ r
;
335 cpml_extents_pair_add(extents
, &pair
);
338 /* Add the bottom quadrant point if needed */
339 if (ANGLE_INCLUDED(M_PI_2
) || ANGLE_INCLUDED(M_PI_2
* 5)) {
341 pair
.y
= center
.y
+ r
;
342 cpml_extents_pair_add(extents
, &pair
);
345 /* Add the left quadrant point if needed */
346 if (ANGLE_INCLUDED(M_PI
)) {
347 pair
.x
= center
.x
- r
;
349 cpml_extents_pair_add(extents
, &pair
);
352 /* Add the top quadrant point if needed */
353 if (ANGLE_INCLUDED(M_PI_2
* 3) || ANGLE_INCLUDED(-M_PI_2
)) {
355 pair
.y
= center
.y
- r
;
356 cpml_extents_pair_add(extents
, &pair
);
359 /* Add the start point */
360 cpml_primitive_put_point(arc
, 0, &pair
);
361 cpml_extents_pair_add(extents
, &pair
);
363 /* Add the end point */
364 cpml_primitive_put_point(arc
, -1, &pair
);
365 cpml_extents_pair_add(extents
, &pair
);
369 put_pair_at(const CpmlPrimitive
*arc
, double pos
, CpmlPair
*pair
)
372 cpml_pair_from_cairo(pair
, arc
->org
);
373 } else if (pos
== 1.) {
374 cpml_pair_from_cairo(pair
, &arc
->data
[2]);
377 double r
, start
, end
, angle
;
379 if (!cpml_arc_info(arc
, ¢er
, &r
, &start
, &end
))
382 angle
= (end
-start
)*pos
+ start
;
383 cpml_vector_from_angle(pair
, angle
);
384 cpml_vector_set_length(pair
, r
);
392 put_vector_at(const CpmlPrimitive
*arc
, double pos
, CpmlVector
*vector
)
394 double start
, end
, angle
;
396 if (!cpml_arc_info(arc
, NULL
, NULL
, &start
, &end
))
399 angle
= (end
-start
)*pos
+ start
;
400 cpml_vector_from_angle(vector
, angle
);
401 cpml_vector_normal(vector
);
404 vector
->x
= -vector
->x
;
405 vector
->y
= -vector
->y
;
410 put_intersections(const CpmlPrimitive
*arc
, const CpmlPrimitive
*primitive
,
411 size_t n_dest
, CpmlPair
*dest
)
416 cpml_arc_info(arc
, ¢er
, &r
, NULL
, NULL
);
418 switch ((int) cpml_primitive_type(primitive
)) {
422 cpml_primitive_put_point(primitive
, 0, &p1
);
423 cpml_primitive_put_point(primitive
, -1, &p2
);
424 return circle_line(¢er
, r
, &p1
, &p2
, dest
);
428 /* TODO: Not Yet Implemented */
436 offset(CpmlPrimitive
*arc
, double offset
)
438 CpmlPair p
[3], center
;
441 cpml_pair_from_cairo(&p
[0], arc
->org
);
442 cpml_pair_from_cairo(&p
[1], &arc
->data
[1]);
443 cpml_pair_from_cairo(&p
[2], &arc
->data
[2]);
445 if (!get_center(p
, ¢er
))
448 r
= cpml_pair_distance(&p
[0], ¢er
) + offset
;
450 /* Offset the three points by calculating their vector from the center,
451 * setting the new radius as length and readding the center */
459 cpml_vector_set_length(&p
[0], r
);
460 cpml_vector_set_length(&p
[1], r
);
461 cpml_vector_set_length(&p
[2], r
);
470 cpml_pair_to_cairo(&p
[0], arc
->org
);
471 cpml_pair_to_cairo(&p
[1], &arc
->data
[1]);
472 cpml_pair_to_cairo(&p
[2], &arc
->data
[2]);
476 get_center(const CpmlPair
*p
, CpmlPair
*dest
)
481 /* When p[0] == p[2], p[0]..p[1] is considered the diameter of a circle */
482 if (p
[0].x
== p
[2].x
&& p
[0].y
== p
[2].y
) {
483 dest
->x
= (p
[0].x
+ p
[1].x
) / 2;
484 dest
->y
= (p
[0].y
+ p
[1].y
) / 2;
488 /* Translate the 3 points of -p0, to simplify the formula */
489 b
.x
= p
[1].x
- p
[0].x
;
490 b
.y
= p
[1].y
- p
[0].y
;
491 c
.x
= p
[2].x
- p
[0].x
;
492 c
.y
= p
[2].y
- p
[0].y
;
494 /* Check for division by 0, that is the case where the 3 given points
495 * are laying on a straight line and there is no fitting circle */
496 d
= (b
.x
*c
.y
- b
.y
*c
.x
) * 2;
500 b2
= b
.x
*b
.x
+ b
.y
*b
.y
;
501 c2
= c
.x
*c
.x
+ c
.y
*c
.y
;
503 dest
->x
= (c
.y
*b2
- b
.y
*c2
) / d
+ p
[0].x
;
504 dest
->y
= (b
.x
*c2
- c
.x
*b2
) / d
+ p
[0].y
;
510 get_angles(const CpmlPair
*p
, const CpmlPair
*center
,
511 double *start
, double *end
)
516 /* Calculate the starting angle */
517 vector
.x
= p
[0].x
- center
->x
;
518 vector
.y
= p
[0].y
- center
->y
;
519 *start
= cpml_vector_angle(&vector
);
521 if (p
[0].x
== p
[2].x
&& p
[0].y
== p
[2].y
) {
522 /* When p[0] and p[2] are cohincidents, p[0]..p[1] is the diameter
523 * of a circle: return by convention start=start end=start+2PI */
524 *end
= *start
+ M_PI
*2;
526 /* Calculate the mid and end angle: cpml_vector_angle()
527 * returns an angle between -M_PI and M_PI */
528 vector
.x
= p
[1].x
- center
->x
;
529 vector
.y
= p
[1].y
- center
->y
;
530 mid
= cpml_vector_angle(&vector
);
531 vector
.x
= p
[2].x
- center
->x
;
532 vector
.y
= p
[2].y
- center
->y
;
533 *end
= cpml_vector_angle(&vector
);
536 /* If the middle angle is outside the start..end range,
537 * the arc should be reversed (that is, start must
538 * be greather than end) */
539 if (mid
< *start
|| mid
> *end
)
542 /* Here the arc is reversed: if the middle angle is
543 * outside the end..start range, the arc should be
544 * re-reversed to get a straight arc (that is, end
545 * must be greather than start) */
546 if (mid
< *end
|| mid
> *start
)
553 arc_to_curve(CpmlPrimitive
*curve
, const CpmlPair
*center
,
554 double r
, double start
, double end
)
556 double r_sin1
, r_cos1
;
557 double r_sin2
, r_cos2
;
560 r_sin1
= r
*sin(start
);
561 r_cos1
= r
*cos(start
);
565 h
= 4./3. * tan((end
-start
) / 4.);
567 curve
->data
[0].header
.type
= CPML_CURVE
;
568 curve
->data
[0].header
.length
= 4;
569 curve
->data
[1].point
.x
= center
->x
+ r_cos1
- h
*r_sin1
;
570 curve
->data
[1].point
.y
= center
->y
+ r_sin1
+ h
*r_cos1
;
571 curve
->data
[2].point
.x
= center
->x
+ r_cos2
+ h
*r_sin2
;
572 curve
->data
[2].point
.y
= center
->y
+ r_sin2
- h
*r_cos2
;
573 curve
->data
[3].point
.x
= center
->x
+ r_cos2
;
574 curve
->data
[3].point
.y
= center
->y
+ r_sin2
;
578 circle_line(const CpmlPair
*center
, double r
,
579 const CpmlPair
*p1
, const CpmlPair
*p2
,
582 double x1
, y1
, x2
, y2
;
583 double a
, b
, c
, d
, e
;
585 /* Translate to have the circle origin in (0, 0) */
586 x1
= p1
->x
- center
->x
;
587 y1
= p1
->y
- center
->y
;
588 x2
= p2
->x
- center
->x
;
589 y2
= p2
->y
- center
->y
;
591 /* Equation of line: ax + by = c
592 * Equation of circle: x² + y² = r² */
597 d
= r
*r
* (a
*a
+ b
*b
) - c
*c
;
599 /* No intersections found */
606 dest
->x
= (a
*c
- b
*d
) / e
+ center
->x
;
607 dest
->y
= (b
*c
+ a
*d
) / e
+ center
->y
;
609 /* Only one soultion found (tangent line) */
614 dest
->x
= (a
*c
+ b
*d
) / e
+ center
->x
;
615 dest
->y
= (b
*c
- a
*d
) / e
+ center
->y
;