1
/* cairo - a vector graphics library with display and print output
2
 *
3
 * Copyright © 2002 University of Southern California
4
 *
5
 * This library is free software; you can redistribute it and/or
6
 * modify it either under the terms of the GNU Lesser General Public
7
 * License version 2.1 as published by the Free Software Foundation
8
 * (the "LGPL") or, at your option, under the terms of the Mozilla
9
 * Public License Version 1.1 (the "MPL"). If you do not alter this
10
 * notice, a recipient may use your version of this file under either
11
 * the MPL or the LGPL.
12
 *
13
 * You should have received a copy of the LGPL along with this library
14
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
15
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
16
 * You should have received a copy of the MPL along with this library
17
 * in the file COPYING-MPL-1.1
18
 *
19
 * The contents of this file are subject to the Mozilla Public License
20
 * Version 1.1 (the "License"); you may not use this file except in
21
 * compliance with the License. You may obtain a copy of the License at
22
 * http://www.mozilla.org/MPL/
23
 *
24
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
25
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
26
 * the specific language governing rights and limitations.
27
 *
28
 * The Original Code is the cairo graphics library.
29
 *
30
 * The Initial Developer of the Original Code is University of Southern
31
 * California.
32
 *
33
 * Contributor(s):
34
 *	Carl D. Worth <cworth@cworth.org>
35
 */
36

            
37
#include "cairoint.h"
38

            
39
#include "cairo-arc-private.h"
40

            
41
#define MAX_FULL_CIRCLES 65536
42

            
43
/* Spline deviation from the circle in radius would be given by:
44

            
45
	error = sqrt (x**2 + y**2) - 1
46

            
47
   A simpler error function to work with is:
48

            
49
	e = x**2 + y**2 - 1
50

            
51
   From "Good approximation of circles by curvature-continuous Bezier
52
   curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric
53
   Design 8 (1990) 22-41, we learn:
54

            
55
	abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4)
56

            
57
   and
58
	abs (error) =~ 1/2 * e
59

            
60
   Of course, this error value applies only for the particular spline
61
   approximation that is used in _cairo_gstate_arc_segment.
62
*/
63
static double
64
_arc_error_normalized (double angle)
65
{
66
    return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2);
67
}
68

            
69
static double
70
494707
_arc_max_angle_for_tolerance_normalized (double tolerance)
71
{
72
    double angle, error;
73
    int i;
74

            
75
    /* Use table lookup to reduce search time in most cases. */
76
    struct {
77
	double angle;
78
	double error;
79
494707
    } table[] = {
80
	{ M_PI / 1.0,   0.0185185185185185036127 },
81
	{ M_PI / 2.0,   0.000272567143730179811158 },
82
	{ M_PI / 3.0,   2.38647043651461047433e-05 },
83
	{ M_PI / 4.0,   4.2455377443222443279e-06 },
84
	{ M_PI / 5.0,   1.11281001494389081528e-06 },
85
	{ M_PI / 6.0,   3.72662000942734705475e-07 },
86
	{ M_PI / 7.0,   1.47783685574284411325e-07 },
87
	{ M_PI / 8.0,   6.63240432022601149057e-08 },
88
	{ M_PI / 9.0,   3.2715520137536980553e-08 },
89
	{ M_PI / 10.0,  1.73863223499021216974e-08 },
90
	{ M_PI / 11.0,  9.81410988043554039085e-09 },
91
    };
92
494707
    int table_size = ARRAY_LENGTH (table);
93
494707
    const int max_segments = 1000; /* this value is chosen arbitrarily. this gives an error of about 1.74909e-20 */
94

            
95
499783
    for (i = 0; i < table_size; i++)
96
499783
	if (table[i].error < tolerance)
97
494707
	    return table[i].angle;
98

            
99
    ++i;
100

            
101
    do {
102
	angle = M_PI / i++;
103
	error = _arc_error_normalized (angle);
104
    } while (error > tolerance && i < max_segments);
105

            
106
    return angle;
107
}
108

            
109
static int
110
494707
_arc_segments_needed (double	      angle,
111
		      double	      radius,
112
		      cairo_matrix_t *ctm,
113
		      double	      tolerance)
114
{
115
    double major_axis, max_angle;
116

            
117
    /* the error is amplified by at most the length of the
118
     * major axis of the circle; see cairo-pen.c for a more detailed analysis
119
     * of this. */
120
494707
    major_axis = _cairo_matrix_transformed_circle_major_axis (ctm, radius);
121
494707
    max_angle = _arc_max_angle_for_tolerance_normalized (tolerance / major_axis);
122

            
123
494707
    return ceil (fabs (angle) / max_angle);
124
}
125

            
126
/* We want to draw a single spline approximating a circular arc radius
127
   R from angle A to angle B. Since we want a symmetric spline that
128
   matches the endpoints of the arc in position and slope, we know
129
   that the spline control points must be:
130

            
131
	(R * cos(A), R * sin(A))
132
	(R * cos(A) - h * sin(A), R * sin(A) + h * cos (A))
133
	(R * cos(B) + h * sin(B), R * sin(B) - h * cos (B))
134
	(R * cos(B), R * sin(B))
135

            
136
   for some value of h.
137

            
138
   "Approximation of circular arcs by cubic polynomials", Michael
139
   Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides
140
   various values of h along with error analysis for each.
141

            
142
   From that paper, a very practical value of h is:
143

            
144
	h = 4/3 * R * tan(angle/4)
145

            
146
   This value does not give the spline with minimal error, but it does
147
   provide a very good approximation, (6th-order convergence), and the
148
   error expression is quite simple, (see the comment for
149
   _arc_error_normalized).
150
*/
151
static void
152
499521
_cairo_arc_segment (cairo_t *cr,
153
		    double   xc,
154
		    double   yc,
155
		    double   radius,
156
		    double   angle_A,
157
		    double   angle_B)
158
{
159
    double r_sin_A, r_cos_A;
160
    double r_sin_B, r_cos_B;
161
    double h;
162

            
163
499521
    r_sin_A = radius * sin (angle_A);
164
499521
    r_cos_A = radius * cos (angle_A);
165
499521
    r_sin_B = radius * sin (angle_B);
166
499521
    r_cos_B = radius * cos (angle_B);
167

            
168
499521
    h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0);
169

            
170
499521
    cairo_curve_to (cr,
171
499521
		    xc + r_cos_A - h * r_sin_A,
172
499521
		    yc + r_sin_A + h * r_cos_A,
173
499521
		    xc + r_cos_B + h * r_sin_B,
174
499521
		    yc + r_sin_B - h * r_cos_B,
175
		    xc + r_cos_B,
176
		    yc + r_sin_B);
177
499521
}
178

            
179
static void
180
986191
_cairo_arc_in_direction (cairo_t	  *cr,
181
			 double		   xc,
182
			 double		   yc,
183
			 double		   radius,
184
			 double		   angle_min,
185
			 double		   angle_max,
186
			 cairo_direction_t dir)
187
{
188
986191
    if (cairo_status (cr))
189
        return;
190

            
191
986191
    if (! ISFINITE (angle_max) || ! ISFINITE (angle_min))
192
        return;
193

            
194
986191
    assert (angle_max >= angle_min);
195

            
196
986191
    if (angle_max - angle_min > 2 * M_PI * MAX_FULL_CIRCLES) {
197
3
	angle_max = fmod (angle_max - angle_min, 2 * M_PI);
198
3
	angle_min = fmod (angle_min, 2 * M_PI);
199
3
	angle_max += angle_min + 2 * M_PI * MAX_FULL_CIRCLES;
200
    }
201

            
202
    /* Recurse if drawing arc larger than pi */
203
986191
    if (angle_max - angle_min > M_PI) {
204
491421
	double angle_mid = angle_min + (angle_max - angle_min) / 2.0;
205
491421
	if (dir == CAIRO_DIRECTION_FORWARD) {
206
491340
	    _cairo_arc_in_direction (cr, xc, yc, radius,
207
				     angle_min, angle_mid,
208
				     dir);
209

            
210
491340
	    _cairo_arc_in_direction (cr, xc, yc, radius,
211
				     angle_mid, angle_max,
212
				     dir);
213
	} else {
214
81
	    _cairo_arc_in_direction (cr, xc, yc, radius,
215
				     angle_mid, angle_max,
216
				     dir);
217

            
218
81
	    _cairo_arc_in_direction (cr, xc, yc, radius,
219
				     angle_min, angle_mid,
220
				     dir);
221
	}
222
494770
    } else if (angle_max != angle_min) {
223
	cairo_matrix_t ctm;
224
	int i, segments;
225
	double step;
226

            
227
494707
	cairo_get_matrix (cr, &ctm);
228
494707
	segments = _arc_segments_needed (angle_max - angle_min,
229
					 radius, &ctm,
230
					 cairo_get_tolerance (cr));
231
494707
	step = (angle_max - angle_min) / segments;
232
494707
	segments -= 1;
233

            
234
494707
	if (dir == CAIRO_DIRECTION_REVERSE) {
235
	    double t;
236

            
237
168
	    t = angle_min;
238
168
	    angle_min = angle_max;
239
168
	    angle_max = t;
240

            
241
168
	    step = -step;
242
	}
243

            
244
494707
	cairo_line_to (cr,
245
494707
		       xc + radius * cos (angle_min),
246
494707
		       yc + radius * sin (angle_min));
247

            
248
499521
	for (i = 0; i < segments; i++, angle_min += step) {
249
4814
	    _cairo_arc_segment (cr, xc, yc, radius,
250
				angle_min, angle_min + step);
251
	}
252

            
253
494707
	_cairo_arc_segment (cr, xc, yc, radius,
254
			    angle_min, angle_max);
255
    } else {
256
63
	cairo_line_to (cr,
257
63
		       xc + radius * cos (angle_min),
258
63
		       yc + radius * sin (angle_min));
259
    }
260
}
261

            
262
/**
263
 * _cairo_arc_path:
264
 * @cr: a cairo context
265
 * @xc: X position of the center of the arc
266
 * @yc: Y position of the center of the arc
267
 * @radius: the radius of the arc
268
 * @angle1: the start angle, in radians
269
 * @angle2: the end angle, in radians
270
 *
271
 * Compute a path for the given arc and append it onto the current
272
 * path within @cr. The arc will be accurate within the current
273
 * tolerance and given the current transformation.
274
 **/
275
void
276
3253
_cairo_arc_path (cairo_t *cr,
277
		 double	  xc,
278
		 double	  yc,
279
		 double	  radius,
280
		 double	  angle1,
281
		 double	  angle2)
282
{
283
3253
    _cairo_arc_in_direction (cr, xc, yc,
284
			     radius,
285
			     angle1, angle2,
286
			     CAIRO_DIRECTION_FORWARD);
287
3253
}
288

            
289
/**
290
 * _cairo_arc_path_negative:
291
 * @xc: X position of the center of the arc
292
 * @yc: Y position of the center of the arc
293
 * @radius: the radius of the arc
294
 * @angle1: the start angle, in radians
295
 * @angle2: the end angle, in radians
296
 * @ctm: the current transformation matrix
297
 * @tolerance: the current tolerance value
298
 * @path: the path onto which the arc will be appended
299
 *
300
 * Compute a path for the given arc (defined in the negative
301
 * direction) and append it onto the current path within @cr. The arc
302
 * will be accurate within the current tolerance and given the current
303
 * transformation.
304
 **/
305
void
306
96
_cairo_arc_path_negative (cairo_t *cr,
307
			  double   xc,
308
			  double   yc,
309
			  double   radius,
310
			  double   angle1,
311
			  double   angle2)
312
{
313
96
    _cairo_arc_in_direction (cr, xc, yc,
314
			     radius,
315
			     angle2, angle1,
316
			     CAIRO_DIRECTION_REVERSE);
317
96
}