doc: update copyright line for 2021
[adg.git] / src / cpml / cpml-arc.c
blob9566207f041438b9ce8e72a07e71e74a4dc14b9a
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.
21 /**
22 * SECTION:cpml-arc
23 * @Section_Id:CpmlArc
24 * @title: CpmlArc
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
37 * the arc.
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.
43 * <important>
44 * <para>
45 * An arc is not a native cairo primitive and should be treated specially.
46 * </para>
47 * </important>
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.
63 * <important>
64 * <title>TODO</title>
65 * <itemizedlist>
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>
70 * </itemizedlist>
71 * </important>
73 * Since: 1.0
74 **/
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"
82 #include "cpml-arc.h"
83 #include "cpml-curve.h"
84 #include <math.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,
100 double pos,
101 CpmlPair *pair);
102 static void put_vector_at (const CpmlPrimitive *arc,
103 double pos,
104 CpmlVector *vector);
105 static size_t put_intersections (const CpmlPrimitive *line,
106 const CpmlPrimitive *primitive,
107 size_t n_dest,
108 CpmlPair *dest);
109 static void offset (CpmlPrimitive *arc,
110 double offset);
111 static int get_center (const CpmlPair *p,
112 CpmlPair *dest);
113 static void get_angles (const CpmlPair *p,
114 const CpmlPair *center,
115 double *start,
116 double *end);
117 static void arc_to_curve (CpmlPrimitive *curve,
118 const CpmlPair *center,
119 double r,
120 double start,
121 double end);
122 static int circle_line (const CpmlPair *center,
123 double r,
124 const CpmlPair *p1,
125 const CpmlPair *p2,
126 CpmlPair *dest);
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 = {
136 "arc to", 3,
137 get_length,
138 put_extents,
139 put_pair_at,
140 put_vector_at,
141 NULL,
142 put_intersections,
143 offset,
144 NULL
146 p_class = &class_data;
149 return p_class;
154 * cpml_arc_info:
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:
172 * the arc direction.
174 * If @start &lt; @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 * &lt; <varname>value</varname> &lt; 3 M_PI</constant> inclusive instead of
180 * the usual <constant>-M_PI &lt; <varname>value</varname> &lt; M_PI</constant>.
182 * Returns: (type boolean): 1 if the function worked succesfully, 0 on errors.
184 * Since: 1.0
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))
197 return 0;
199 if (center)
200 *center = l_center;
202 if (r != NULL)
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);
210 if (start != NULL)
211 *start = l_start;
212 if (end != NULL)
213 *end = l_end;
216 return 1;
220 * cpml_arc_to_cairo:
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.
233 * Since: 1.0
235 void
236 cpml_arc_to_cairo(const CpmlPrimitive *arc, cairo_t *cr)
238 CpmlPair center;
239 double r, start, end;
240 size_t n_curves;
241 double step, angle;
242 CpmlPrimitive curve;
243 cairo_path_data_t data[4];
245 if (!cpml_arc_info(arc, &center, &r, &start, &end))
246 return;
248 n_curves = ceil(fabs(end-start) / ARC_MAX_ANGLE);
249 step = (end-start) / (double) n_curves;
250 curve.data = data;
252 for (angle = start; n_curves--; angle += step) {
253 arc_to_curve(&curve, &center, r, angle, angle+step);
254 cairo_curve_to(cr,
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.
279 * Since: 1.0
281 void
282 cpml_arc_to_curves(const CpmlPrimitive *arc, CpmlSegment *segment,
283 size_t n_curves)
285 CpmlPair center;
286 double r, start, end;
287 double step, angle;
288 CpmlPrimitive curve;
290 if (!cpml_arc_info(arc, &center, &r, &start, &end))
291 return;
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, &center, r, angle, angle+step);
300 curve.data += 4;
305 static double
306 get_length(const CpmlPrimitive *arc)
308 double r, start, end, delta;
310 if (!cpml_arc_info(arc, NULL, &r, &start, &end) || start == end)
311 return 0.;
313 delta = end - start;
314 if (delta < 0)
315 delta += M_PI*2;
317 return r*delta;
320 static void
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, &center, &r, &start, &end))
329 return;
331 /* Add the right quadrant point if needed */
332 if (ANGLE_INCLUDED(0) || ANGLE_INCLUDED(M_PI * 2)) {
333 pair.x = center.x + r;
334 pair.y = center.y;
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)) {
340 pair.x = center.x;
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;
348 pair.y = center.y;
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)) {
354 pair.x = center.x;
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);
368 static void
369 put_pair_at(const CpmlPrimitive *arc, double pos, CpmlPair *pair)
371 if (pos == 0.) {
372 cpml_pair_from_cairo(pair, arc->org);
373 } else if (pos == 1.) {
374 cpml_pair_from_cairo(pair, &arc->data[2]);
375 } else {
376 CpmlPair center;
377 double r, start, end, angle;
379 if (!cpml_arc_info(arc, &center, &r, &start, &end))
380 return;
382 angle = (end-start)*pos + start;
383 cpml_vector_from_angle(pair, angle);
384 cpml_vector_set_length(pair, r);
386 pair->x += center.x;
387 pair->y += center.y;
391 static void
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))
397 return;
399 angle = (end-start)*pos + start;
400 cpml_vector_from_angle(vector, angle);
401 cpml_vector_normal(vector);
403 if (start > end) {
404 vector->x = -vector->x;
405 vector->y = -vector->y;
409 static size_t
410 put_intersections(const CpmlPrimitive *arc, const CpmlPrimitive *primitive,
411 size_t n_dest, CpmlPair *dest)
413 CpmlPair center;
414 double r;
416 cpml_arc_info(arc, &center, &r, NULL, NULL);
418 switch ((int) cpml_primitive_type(primitive)) {
420 case CPML_LINE: {
421 CpmlPair p1, p2;
422 cpml_primitive_put_point(primitive, 0, &p1);
423 cpml_primitive_put_point(primitive, -1, &p2);
424 return circle_line(&center, r, &p1, &p2, dest);
427 case CPML_ARC:
428 /* TODO: Not Yet Implemented */
429 return 0;
432 return 0;
435 static void
436 offset(CpmlPrimitive *arc, double offset)
438 CpmlPair p[3], center;
439 double r;
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, &center))
446 return;
448 r = cpml_pair_distance(&p[0], &center) + offset;
450 /* Offset the three points by calculating their vector from the center,
451 * setting the new radius as length and readding the center */
452 p[0].x -= center.x;
453 p[0].y -= center.y;
454 p[1].x -= center.x;
455 p[1].y -= center.y;
456 p[2].x -= center.x;
457 p[2].y -= center.y;
459 cpml_vector_set_length(&p[0], r);
460 cpml_vector_set_length(&p[1], r);
461 cpml_vector_set_length(&p[2], r);
463 p[0].x += center.x;
464 p[0].y += center.y;
465 p[1].x += center.x;
466 p[1].y += center.y;
467 p[2].x += center.x;
468 p[2].y += center.y;
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]);
475 static int
476 get_center(const CpmlPair *p, CpmlPair *dest)
478 CpmlPair b, c;
479 double d, b2, c2;
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;
485 return 1;
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;
497 if (d == 0.)
498 return 0;
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;
506 return 1;
509 static void
510 get_angles(const CpmlPair *p, const CpmlPair *center,
511 double *start, double *end)
513 CpmlVector vector;
514 double mid;
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;
525 } else {
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);
535 if (*end > *start) {
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)
540 *start += M_PI*2;
541 } else {
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)
547 *end += M_PI*2;
552 static void
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;
558 double h;
560 r_sin1 = r*sin(start);
561 r_cos1 = r*cos(start);
562 r_sin2 = r*sin(end);
563 r_cos2 = r*cos(end);
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;
577 static int
578 circle_line(const CpmlPair *center, double r,
579 const CpmlPair *p1, const CpmlPair *p2,
580 CpmlPair *dest)
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² */
593 a = y2 - y1;
594 b = x1 - x2;
595 c = x1*y2 - x2*y1;
597 d = r*r * (a*a + b*b) - c*c;
598 if (d < 0) {
599 /* No intersections found */
600 return 0;
603 d = sqrt(d);
604 e = a*a + b*b;
606 dest->x = (a*c - b*d) / e + center->x;
607 dest->y = (b*c + a*d) / e + center->y;
608 if (d == 0) {
609 /* Only one soultion found (tangent line) */
610 return 1;
613 ++ dest;
614 dest->x = (a*c + b*d) / e + center->x;
615 dest->y = (b*c - a*d) / e + center->y;
616 return 2;