1 /* Libart_LGPL - library of basic graphic primitives
2  * Copyright (C) 1998-2000 Raph Levien
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library 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.
8  *
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  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19 
20 
21 #include "config.h"
22 #include "art_svp_vpath_stroke.h"
23 
24 #include <stdlib.h>
25 #include <math.h>
26 
27 #include "art_misc.h"
28 
29 #include "art_vpath.h"
30 #include "art_svp.h"
31 #ifdef ART_USE_NEW_INTERSECTOR
32 #include "art_svp_intersect.h"
33 #else
34 #include "art_svp_wind.h"
35 #endif
36 #include "art_svp_vpath.h"
37 
38 #define EPSILON 1e-6
39 #define EPSILON_2 1e-12
40 
41 #define yes_OPTIMIZE_INNER
42 
43 /* Render an arc segment starting at (xc + x0, yc + y0) to (xc + x1,
44    yc + y1), centered at (xc, yc), and with given radius. Both x0^2 +
45    y0^2 and x1^2 + y1^2 should be equal to radius^2.
46 
47    A positive value of radius means curve to the left, negative means
48    curve to the right.
49 */
50 static void
art_svp_vpath_stroke_arc(ArtVpath ** p_vpath,int * pn,int * pn_max,double xc,double yc,double x0,double y0,double x1,double y1,double radius,double flatness)51 art_svp_vpath_stroke_arc (ArtVpath **p_vpath, int *pn, int *pn_max,
52 			  double xc, double yc,
53 			  double x0, double y0,
54 			  double x1, double y1,
55 			  double radius,
56 			  double flatness)
57 {
58   double theta;
59   double th_0, th_1;
60   int n_pts;
61   int i;
62   double aradius;
63 
64   aradius = fabs (radius);
65   theta = 2 * M_SQRT2 * sqrt (flatness / aradius);
66   th_0 = atan2 (y0, x0);
67   th_1 = atan2 (y1, x1);
68   if (radius > 0)
69     {
70       /* curve to the left */
71       if (th_0 < th_1) th_0 += M_PI * 2;
72       n_pts = (int)ceil ((th_0 - th_1) / theta);
73     }
74   else
75     {
76       /* curve to the right */
77       if (th_1 < th_0) th_1 += M_PI * 2;
78       n_pts = (int)ceil ((th_1 - th_0) / theta);
79     }
80 #ifdef VERBOSE
81   printf ("start %f %f; th_0 = %f, th_1 = %f, r = %f, theta = %f\n", x0, y0, th_0, th_1, radius, theta);
82 #endif
83   art_vpath_add_point (p_vpath, pn, pn_max,
84 		       ART_LINETO, xc + x0, yc + y0);
85   for (i = 1; i < n_pts; i++)
86     {
87       theta = th_0 + (th_1 - th_0) * i / n_pts;
88       art_vpath_add_point (p_vpath, pn, pn_max,
89 			   ART_LINETO, xc + cos (theta) * aradius,
90 			   yc + sin (theta) * aradius);
91 #ifdef VERBOSE
92       printf ("mid %f %f\n", cos (theta) * radius, sin (theta) * radius);
93 #endif
94     }
95   art_vpath_add_point (p_vpath, pn, pn_max,
96 		       ART_LINETO, xc + x1, yc + y1);
97 #ifdef VERBOSE
98   printf ("end %f %f\n", x1, y1);
99 #endif
100 }
101 
102 /* Assume that forw and rev are at point i0. Bring them to i1,
103    joining with the vector i1 - i2.
104 
105    This used to be true, but isn't now that the stroke_raw code is
106    filtering out (near)zero length vectors: {It so happens that all
107    invocations of this function maintain the precondition i1 = i0 + 1,
108    so we could decrease the number of arguments by one. We haven't
109    done that here, though.}
110 
111    forw is to the line's right and rev is to its left.
112 
113    Precondition: no zero-length vectors, otherwise a divide by
114    zero will happen.  */
115 static void
render_seg(ArtVpath ** p_forw,int * pn_forw,int * pn_forw_max,ArtVpath ** p_rev,int * pn_rev,int * pn_rev_max,ArtVpath * vpath,int i0,int i1,int i2,ArtPathStrokeJoinType join,double line_width,double miter_limit,double flatness)116 render_seg (ArtVpath **p_forw, int *pn_forw, int *pn_forw_max,
117 	    ArtVpath **p_rev, int *pn_rev, int *pn_rev_max,
118 	    ArtVpath *vpath, int i0, int i1, int i2,
119 	    ArtPathStrokeJoinType join,
120 	    double line_width, double miter_limit, double flatness)
121 {
122   double dx0, dy0;
123   double dx1, dy1;
124   double dlx0, dly0;
125   double dlx1, dly1;
126   double dmx, dmy;
127   double dmr2;
128   double scale;
129   double cross;
130 
131 #ifdef VERBOSE
132   printf ("join style = %d\n", join);
133 #endif
134 
135   /* The vectors of the lines from i0 to i1 and i1 to i2. */
136   dx0 = vpath[i1].x - vpath[i0].x;
137   dy0 = vpath[i1].y - vpath[i0].y;
138 
139   dx1 = vpath[i2].x - vpath[i1].x;
140   dy1 = vpath[i2].y - vpath[i1].y;
141 
142   /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise
143      90 degrees, and scaled to the length of line_width. */
144   scale = line_width / sqrt (dx0 * dx0 + dy0 * dy0);
145   dlx0 = dy0 * scale;
146   dly0 = -dx0 * scale;
147 
148   /* Set dl[xy]1 to the vector from i1 to i2, rotated counterclockwise
149      90 degrees, and scaled to the length of line_width. */
150   scale = line_width / sqrt (dx1 * dx1 + dy1 * dy1);
151   dlx1 = dy1 * scale;
152   dly1 = -dx1 * scale;
153 
154 #ifdef VERBOSE
155   printf ("%% render_seg: (%g, %g) - (%g, %g) - (%g, %g)\n",
156 	  vpath[i0].x, vpath[i0].y,
157 	  vpath[i1].x, vpath[i1].y,
158 	  vpath[i2].x, vpath[i2].y);
159 
160   printf ("%% render_seg: d[xy]0 = (%g, %g), dl[xy]0 = (%g, %g)\n",
161 	  dx0, dy0, dlx0, dly0);
162 
163   printf ("%% render_seg: d[xy]1 = (%g, %g), dl[xy]1 = (%g, %g)\n",
164 	  dx1, dy1, dlx1, dly1);
165 #endif
166 
167   /* now, forw's last point is expected to be colinear along d[xy]0
168      to point i0 - dl[xy]0, and rev with i0 + dl[xy]0. */
169 
170   /* positive for positive area (i.e. left turn) */
171   cross = dx1 * dy0 - dx0 * dy1;
172 
173   dmx = (dlx0 + dlx1) * 0.5;
174   dmy = (dly0 + dly1) * 0.5;
175   dmr2 = dmx * dmx + dmy * dmy;
176 
177   if (join == ART_PATH_STROKE_JOIN_MITER &&
178       dmr2 * miter_limit * miter_limit < line_width * line_width)
179     join = ART_PATH_STROKE_JOIN_BEVEL;
180 
181   /* the case when dmr2 is zero or very small bothers me
182      (i.e. near a 180 degree angle)
183      ALEX: So, we avoid the optimization when dmr2 is very small. This should
184      be safe since dmx/y is only used in optimization and in MITER case, and MITER
185      should be converted to BEVEL when dmr2 is very small. */
186   if (dmr2 > EPSILON_2)
187     {
188       scale = line_width * line_width / dmr2;
189       dmx *= scale;
190       dmy *= scale;
191     }
192 
193   if (cross * cross < EPSILON_2 && dx0 * dx1 + dy0 * dy1 >= 0)
194     {
195       /* going straight */
196 #ifdef VERBOSE
197       printf ("%% render_seg: straight\n");
198 #endif
199       art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
200 		       ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
201       art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
202 		       ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
203     }
204   else if (cross > 0)
205     {
206       /* left turn, forw is outside and rev is inside */
207 
208 #ifdef VERBOSE
209       printf ("%% render_seg: left\n");
210 #endif
211       if (
212 #ifdef NO_OPTIMIZE_INNER
213 	  0 &&
214 #endif
215 	  (dmr2 > EPSILON_2) &&
216 	  /* check that i1 + dm[xy] is inside i0-i1 rectangle */
217 	  (dx0 + dmx) * dx0 + (dy0 + dmy) * dy0 > 0 &&
218 	  /* and that i1 + dm[xy] is inside i1-i2 rectangle */
219 	  ((dx1 - dmx) * dx1 + (dy1 - dmy) * dy1 > 0)
220 #ifdef PEDANTIC_INNER
221 	  &&
222 	  /* check that i1 + dl[xy]1 is inside i0-i1 rectangle */
223 	  (dx0 + dlx1) * dx0 + (dy0 + dly1) * dy0 > 0 &&
224 	  /* and that i1 + dl[xy]0 is inside i1-i2 rectangle */
225 	  ((dx1 - dlx0) * dx1 + (dy1 - dly0) * dy1 > 0)
226 #endif
227 	  )
228 	{
229 	  /* can safely add single intersection point */
230 	  art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
231 			   ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy);
232 	}
233       else
234 	{
235 	  /* need to loop-de-loop the inside */
236 	  art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
237 			   ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
238 	  art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
239 			   ART_LINETO, vpath[i1].x, vpath[i1].y);
240 	  art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
241 			   ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1);
242 	}
243 
244       if (join == ART_PATH_STROKE_JOIN_BEVEL)
245 	{
246 	  /* bevel */
247 	  art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
248 			   ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
249 	  art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
250 			   ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1);
251 	}
252       else if (join == ART_PATH_STROKE_JOIN_MITER)
253 	{
254 	  art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
255 			   ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy);
256 	}
257       else if (join == ART_PATH_STROKE_JOIN_ROUND)
258 	art_svp_vpath_stroke_arc (p_forw, pn_forw, pn_forw_max,
259 				  vpath[i1].x, vpath[i1].y,
260 				  -dlx0, -dly0,
261 				  -dlx1, -dly1,
262 				  line_width,
263 				  flatness);
264     }
265   else
266     {
267       /* right turn, rev is outside and forw is inside */
268 #ifdef VERBOSE
269       printf ("%% render_seg: right\n");
270 #endif
271 
272       if (
273 #ifdef NO_OPTIMIZE_INNER
274 	  0 &&
275 #endif
276 	  (dmr2 > EPSILON_2) &&
277 	  /* check that i1 - dm[xy] is inside i0-i1 rectangle */
278 	  (dx0 - dmx) * dx0 + (dy0 - dmy) * dy0 > 0 &&
279 	  /* and that i1 - dm[xy] is inside i1-i2 rectangle */
280 	  ((dx1 + dmx) * dx1 + (dy1 + dmy) * dy1 > 0)
281 #ifdef PEDANTIC_INNER
282 	  &&
283 	  /* check that i1 - dl[xy]1 is inside i0-i1 rectangle */
284 	  (dx0 - dlx1) * dx0 + (dy0 - dly1) * dy0 > 0 &&
285 	  /* and that i1 - dl[xy]0 is inside i1-i2 rectangle */
286 	  ((dx1 + dlx0) * dx1 + (dy1 + dly0) * dy1 > 0)
287 #endif
288 	  )
289 	{
290 	  /* can safely add single intersection point */
291 	  art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
292 			   ART_LINETO, vpath[i1].x - dmx, vpath[i1].y - dmy);
293 	}
294       else
295 	{
296 	  /* need to loop-de-loop the inside */
297 	  art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
298 			   ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
299 	  art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
300 			   ART_LINETO, vpath[i1].x, vpath[i1].y);
301 	  art_vpath_add_point (p_forw, pn_forw, pn_forw_max,
302 			   ART_LINETO, vpath[i1].x - dlx1, vpath[i1].y - dly1);
303 	}
304 
305       if (join == ART_PATH_STROKE_JOIN_BEVEL)
306 	{
307 	  /* bevel */
308 	  art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
309 			   ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
310 	  art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
311 			   ART_LINETO, vpath[i1].x + dlx1, vpath[i1].y + dly1);
312 	}
313       else if (join == ART_PATH_STROKE_JOIN_MITER)
314 	{
315 	  art_vpath_add_point (p_rev, pn_rev, pn_rev_max,
316 			   ART_LINETO, vpath[i1].x + dmx, vpath[i1].y + dmy);
317 	}
318       else if (join == ART_PATH_STROKE_JOIN_ROUND)
319 	art_svp_vpath_stroke_arc (p_rev, pn_rev, pn_rev_max,
320 				  vpath[i1].x, vpath[i1].y,
321 				  dlx0, dly0,
322 				  dlx1, dly1,
323 				  -line_width,
324 				  flatness);
325 
326     }
327 }
328 
329 /* caps i1, under the assumption of a vector from i0 */
330 static void
render_cap(ArtVpath ** p_result,int * pn_result,int * pn_result_max,ArtVpath * vpath,int i0,int i1,ArtPathStrokeCapType cap,double line_width,double flatness)331 render_cap (ArtVpath **p_result, int *pn_result, int *pn_result_max,
332 	    ArtVpath *vpath, int i0, int i1,
333 	    ArtPathStrokeCapType cap, double line_width, double flatness)
334 {
335   double dx0, dy0;
336   double dlx0, dly0;
337   double scale;
338   int n_pts;
339   int i;
340 
341   dx0 = vpath[i1].x - vpath[i0].x;
342   dy0 = vpath[i1].y - vpath[i0].y;
343 
344   /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise
345      90 degrees, and scaled to the length of line_width. */
346   scale = line_width / sqrt (dx0 * dx0 + dy0 * dy0);
347   dlx0 = dy0 * scale;
348   dly0 = -dx0 * scale;
349 
350 #ifdef VERBOSE
351   printf ("cap style = %d\n", cap);
352 #endif
353 
354   switch (cap)
355     {
356     case ART_PATH_STROKE_CAP_BUTT:
357       art_vpath_add_point (p_result, pn_result, pn_result_max,
358 			   ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
359       art_vpath_add_point (p_result, pn_result, pn_result_max,
360 			   ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
361       break;
362     case ART_PATH_STROKE_CAP_ROUND:
363       n_pts = (int)ceil (M_PI / (2.0 * M_SQRT2 * sqrt (flatness / line_width)));
364       art_vpath_add_point (p_result, pn_result, pn_result_max,
365 			   ART_LINETO, vpath[i1].x - dlx0, vpath[i1].y - dly0);
366       for (i = 1; i < n_pts; i++)
367 	{
368 	  double theta, c_th, s_th;
369 
370 	  theta = M_PI * i / n_pts;
371 	  c_th = cos (theta);
372 	  s_th = sin (theta);
373 	  art_vpath_add_point (p_result, pn_result, pn_result_max,
374 			       ART_LINETO,
375 			       vpath[i1].x - dlx0 * c_th - dly0 * s_th,
376 			       vpath[i1].y - dly0 * c_th + dlx0 * s_th);
377 	}
378       art_vpath_add_point (p_result, pn_result, pn_result_max,
379 			   ART_LINETO, vpath[i1].x + dlx0, vpath[i1].y + dly0);
380       break;
381     case ART_PATH_STROKE_CAP_SQUARE:
382       art_vpath_add_point (p_result, pn_result, pn_result_max,
383 			   ART_LINETO,
384 			   vpath[i1].x - dlx0 - dly0,
385 			   vpath[i1].y - dly0 + dlx0);
386       art_vpath_add_point (p_result, pn_result, pn_result_max,
387 			   ART_LINETO,
388 			   vpath[i1].x + dlx0 - dly0,
389 			   vpath[i1].y + dly0 + dlx0);
390       break;
391     }
392 }
393 
394 /**
395  * art_svp_from_vpath_raw: Stroke a vector path, raw version
396  * @vpath: #ArtVPath to stroke.
397  * @join: Join style.
398  * @cap: Cap style.
399  * @line_width: Width of stroke.
400  * @miter_limit: Miter limit.
401  * @flatness: Flatness.
402  *
403  * Exactly the same as art_svp_vpath_stroke(), except that the resulting
404  * stroke outline may self-intersect and have regions of winding number
405  * greater than 1.
406  *
407  * Return value: Resulting raw stroked outline in svp format.
408  **/
409 ArtVpath *
art_svp_vpath_stroke_raw(ArtVpath * vpath,ArtPathStrokeJoinType join,ArtPathStrokeCapType cap,double line_width,double miter_limit,double flatness)410 art_svp_vpath_stroke_raw (ArtVpath *vpath,
411 			  ArtPathStrokeJoinType join,
412 			  ArtPathStrokeCapType cap,
413 			  double line_width,
414 			  double miter_limit,
415 			  double flatness)
416 {
417   int begin_idx, end_idx;
418   int i;
419   ArtVpath *forw, *rev;
420   int n_forw, n_rev;
421   int n_forw_max, n_rev_max;
422   ArtVpath *result;
423   int n_result, n_result_max;
424   double half_lw = 0.5 * line_width;
425   int closed;
426   int last, this, next, second;
427   double dx, dy;
428 
429   n_forw_max = 16;
430   forw = art_new (ArtVpath, n_forw_max);
431 
432   n_rev_max = 16;
433   rev = art_new (ArtVpath, n_rev_max);
434 
435   n_result = 0;
436   n_result_max = 16;
437   result = art_new (ArtVpath, n_result_max);
438 
439   for (begin_idx = 0; vpath[begin_idx].code != ART_END; begin_idx = end_idx)
440     {
441       n_forw = 0;
442       n_rev = 0;
443 
444       closed = (vpath[begin_idx].code == ART_MOVETO);
445 
446       /* we don't know what the first point joins with until we get to the
447 	 last point and see if it's closed. So we start with the second
448 	 line in the path.
449 
450 	 Note: this is not strictly true (we now know it's closed from
451 	 the opening pathcode), but why fix code that isn't broken?
452       */
453 
454       this = begin_idx;
455       /* skip over identical points at the beginning of the subpath */
456       for (i = this + 1; vpath[i].code == ART_LINETO; i++)
457 	{
458 	  dx = vpath[i].x - vpath[this].x;
459 	  dy = vpath[i].y - vpath[this].y;
460 	  if (dx * dx + dy * dy > EPSILON_2)
461 	    break;
462 	}
463       next = i;
464       second = next;
465 
466       /* invariant: this doesn't coincide with next */
467       while (vpath[next].code == ART_LINETO)
468 	{
469 	  last = this;
470 	  this = next;
471 	  /* skip over identical points after the beginning of the subpath */
472 	  for (i = this + 1; vpath[i].code == ART_LINETO; i++)
473 	    {
474 	      dx = vpath[i].x - vpath[this].x;
475 	      dy = vpath[i].y - vpath[this].y;
476 	      if (dx * dx + dy * dy > EPSILON_2)
477 		break;
478 	    }
479 	  next = i;
480 	  if (vpath[next].code != ART_LINETO)
481 	    {
482 	      /* reached end of path */
483 	      /* make "closed" detection conform to PostScript
484 		 semantics (i.e. explicit closepath code rather than
485 		 just the fact that end of the path is the beginning) */
486 	      if (closed &&
487 		  vpath[this].x == vpath[begin_idx].x &&
488 		  vpath[this].y == vpath[begin_idx].y)
489 		{
490 		  int j;
491 
492 		  /* path is closed, render join to beginning */
493 		  render_seg (&forw, &n_forw, &n_forw_max,
494 			      &rev, &n_rev, &n_rev_max,
495 			      vpath, last, this, second,
496 			      join, half_lw, miter_limit, flatness);
497 
498 #ifdef VERBOSE
499 		  printf ("%% forw %d, rev %d\n", n_forw, n_rev);
500 #endif
501 		  /* do forward path */
502 		  art_vpath_add_point (&result, &n_result, &n_result_max,
503 				   ART_MOVETO, forw[n_forw - 1].x,
504 				   forw[n_forw - 1].y);
505 		  for (j = 0; j < n_forw; j++)
506 		    art_vpath_add_point (&result, &n_result, &n_result_max,
507 				     ART_LINETO, forw[j].x,
508 				     forw[j].y);
509 
510 		  /* do reverse path, reversed */
511 		  art_vpath_add_point (&result, &n_result, &n_result_max,
512 				   ART_MOVETO, rev[0].x,
513 				   rev[0].y);
514 		  for (j = n_rev - 1; j >= 0; j--)
515 		    art_vpath_add_point (&result, &n_result, &n_result_max,
516 				     ART_LINETO, rev[j].x,
517 				     rev[j].y);
518 		}
519 	      else
520 		{
521 		  /* path is open */
522 		  int j;
523 
524 		  /* add to forw rather than result to ensure that
525 		     forw has at least one point. */
526 		  render_cap (&forw, &n_forw, &n_forw_max,
527 			      vpath, last, this,
528 			      cap, half_lw, flatness);
529 		  art_vpath_add_point (&result, &n_result, &n_result_max,
530 				   ART_MOVETO, forw[0].x,
531 				   forw[0].y);
532 		  for (j = 1; j < n_forw; j++)
533 		    art_vpath_add_point (&result, &n_result, &n_result_max,
534 				     ART_LINETO, forw[j].x,
535 				     forw[j].y);
536 		  for (j = n_rev - 1; j >= 0; j--)
537 		    art_vpath_add_point (&result, &n_result, &n_result_max,
538 				     ART_LINETO, rev[j].x,
539 				     rev[j].y);
540 		  render_cap (&result, &n_result, &n_result_max,
541 			      vpath, second, begin_idx,
542 			      cap, half_lw, flatness);
543 		  art_vpath_add_point (&result, &n_result, &n_result_max,
544 				   ART_LINETO, forw[0].x,
545 				   forw[0].y);
546 		}
547 	    }
548 	  else
549 	    render_seg (&forw, &n_forw, &n_forw_max,
550 			&rev, &n_rev, &n_rev_max,
551 			vpath, last, this, next,
552 			join, half_lw, miter_limit, flatness);
553 	}
554       end_idx = next;
555     }
556 
557   art_free (forw);
558   art_free (rev);
559 #ifdef VERBOSE
560   printf ("%% n_result = %d\n", n_result);
561 #endif
562   art_vpath_add_point (&result, &n_result, &n_result_max, ART_END, 0, 0);
563   return result;
564 }
565 
566 #define noVERBOSE
567 
568 #ifdef VERBOSE
569 
570 #define XOFF 50
571 #define YOFF 700
572 
573 static void
print_ps_vpath(ArtVpath * vpath)574 print_ps_vpath (ArtVpath *vpath)
575 {
576   int i;
577 
578   for (i = 0; vpath[i].code != ART_END; i++)
579     {
580       switch (vpath[i].code)
581 	{
582 	case ART_MOVETO:
583 	  printf ("%g %g moveto\n", XOFF + vpath[i].x, YOFF - vpath[i].y);
584 	  break;
585 	case ART_LINETO:
586 	  printf ("%g %g lineto\n", XOFF + vpath[i].x, YOFF - vpath[i].y);
587 	  break;
588 	default:
589 	  break;
590 	}
591     }
592   printf ("stroke showpage\n");
593 }
594 
595 static void
print_ps_svp(ArtSVP * vpath)596 print_ps_svp (ArtSVP *vpath)
597 {
598   int i, j;
599 
600   printf ("%% begin\n");
601   for (i = 0; i < vpath->n_segs; i++)
602     {
603       printf ("%g setgray\n", vpath->segs[i].dir ? 0.7 : 0);
604       for (j = 0; j < vpath->segs[i].n_points; j++)
605 	{
606 	  printf ("%g %g %s\n",
607 		  XOFF + vpath->segs[i].points[j].x,
608 		  YOFF - vpath->segs[i].points[j].y,
609 		  j ? "lineto" : "moveto");
610 	}
611       printf ("stroke\n");
612     }
613 
614   printf ("showpage\n");
615 }
616 #endif
617 
618 /* Render a vector path into a stroked outline.
619 
620    Status of this routine:
621 
622    Basic correctness: Only miter and bevel line joins are implemented,
623    and only butt line caps. Otherwise, seems to be fine.
624 
625    Numerical stability: We cheat (adding random perturbation). Thus,
626    it seems very likely that no numerical stability problems will be
627    seen in practice.
628 
629    Speed: Should be pretty good.
630 
631    Precision: The perturbation fuzzes the coordinates slightly,
632    but not enough to be visible.  */
633 /**
634  * art_svp_vpath_stroke: Stroke a vector path.
635  * @vpath: #ArtVPath to stroke.
636  * @join: Join style.
637  * @cap: Cap style.
638  * @line_width: Width of stroke.
639  * @miter_limit: Miter limit.
640  * @flatness: Flatness.
641  *
642  * Computes an svp representing the stroked outline of @vpath. The
643  * width of the stroked line is @line_width.
644  *
645  * Lines are joined according to the @join rule. Possible values are
646  * ART_PATH_STROKE_JOIN_MITER (for mitered joins),
647  * ART_PATH_STROKE_JOIN_ROUND (for round joins), and
648  * ART_PATH_STROKE_JOIN_BEVEL (for bevelled joins). The mitered join
649  * is converted to a bevelled join if the miter would extend to a
650  * distance of more than @miter_limit * @line_width from the actual
651  * join point.
652  *
653  * If there are open subpaths, the ends of these subpaths are capped
654  * according to the @cap rule. Possible values are
655  * ART_PATH_STROKE_CAP_BUTT (squared cap, extends exactly to end
656  * point), ART_PATH_STROKE_CAP_ROUND (rounded half-circle centered at
657  * the end point), and ART_PATH_STROKE_CAP_SQUARE (squared cap,
658  * extending half @line_width past the end point).
659  *
660  * The @flatness parameter controls the accuracy of the rendering. It
661  * is most important for determining the number of points to use to
662  * approximate circular arcs for round lines and joins. In general, the
663  * resulting vector path will be within @flatness pixels of the "ideal"
664  * path containing actual circular arcs. I reserve the right to use
665  * the @flatness parameter to convert bevelled joins to miters for very
666  * small turn angles, as this would reduce the number of points in the
667  * resulting outline path.
668  *
669  * The resulting path is "clean" with respect to self-intersections, i.e.
670  * the winding number is 0 or 1 at each point.
671  *
672  * Return value: Resulting stroked outline in svp format.
673  **/
674 ArtSVP *
art_svp_vpath_stroke(ArtVpath * vpath,ArtPathStrokeJoinType join,ArtPathStrokeCapType cap,double line_width,double miter_limit,double flatness)675 art_svp_vpath_stroke (ArtVpath *vpath,
676 		      ArtPathStrokeJoinType join,
677 		      ArtPathStrokeCapType cap,
678 		      double line_width,
679 		      double miter_limit,
680 		      double flatness)
681 {
682 #ifdef ART_USE_NEW_INTERSECTOR
683   ArtVpath *vpath_stroke;
684   ArtSVP *svp, *svp2;
685   ArtSvpWriter *swr;
686 
687   vpath_stroke = art_svp_vpath_stroke_raw (vpath, join, cap,
688 					   line_width, miter_limit, flatness);
689 #ifdef VERBOSE
690   print_ps_vpath (vpath_stroke);
691 #endif
692   svp = art_svp_from_vpath (vpath_stroke);
693 #ifdef VERBOSE
694   print_ps_svp (svp);
695 #endif
696   art_free (vpath_stroke);
697 
698   swr = art_svp_writer_rewind_new (ART_WIND_RULE_NONZERO);
699   art_svp_intersector (svp, swr);
700 
701   svp2 = art_svp_writer_rewind_reap (swr);
702 #ifdef VERBOSE
703   print_ps_svp (svp2);
704 #endif
705   art_svp_free (svp);
706   return svp2;
707 #else
708   ArtVpath *vpath_stroke, *vpath2;
709   ArtSVP *svp, *svp2, *svp3;
710 
711   vpath_stroke = art_svp_vpath_stroke_raw (vpath, join, cap,
712 					   line_width, miter_limit, flatness);
713 #ifdef VERBOSE
714   print_ps_vpath (vpath_stroke);
715 #endif
716   vpath2 = art_vpath_perturb (vpath_stroke);
717 #ifdef VERBOSE
718   print_ps_vpath (vpath2);
719 #endif
720   art_free (vpath_stroke);
721   svp = art_svp_from_vpath (vpath2);
722 #ifdef VERBOSE
723   print_ps_svp (svp);
724 #endif
725   art_free (vpath2);
726   svp2 = art_svp_uncross (svp);
727 #ifdef VERBOSE
728   print_ps_svp (svp2);
729 #endif
730   art_svp_free (svp);
731   svp3 = art_svp_rewind_uncrossed (svp2, ART_WIND_RULE_NONZERO);
732 #ifdef VERBOSE
733   print_ps_svp (svp3);
734 #endif
735   art_svp_free (svp2);
736 
737   return svp3;
738 #endif
739 }
740