1 /* Libart_LGPL - library of basic graphic primitives
2 * Copyright (C) 1998-2000 Raph Levien
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.
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.
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.
22 #include "art_svp_vpath_stroke.h"
29 #include "art_vpath.h"
31 #ifdef ART_USE_NEW_INTERSECTOR
32 #include "art_svp_intersect.h"
34 #include "art_svp_wind.h"
36 #include "art_svp_vpath.h"
39 #define EPSILON_2 1e-12
41 #define yes_OPTIMIZE_INNER
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.
47 A positive value of radius means curve to the left, negative means
51 art_svp_vpath_stroke_arc (ArtVpath
**p_vpath
, int *pn
, int *pn_max
,
64 aradius
= fabs (radius
);
65 theta
= 2 * M_SQRT2
* sqrt (flatness
/ aradius
);
66 th_0
= atan2 (y0
, x0
);
67 th_1
= atan2 (y1
, x1
);
70 /* curve to the left */
71 if (th_0
< th_1
) th_0
+= M_PI
* 2;
72 n_pts
= ceil ((th_0
- th_1
) / theta
);
76 /* curve to the right */
77 if (th_1
< th_0
) th_1
+= M_PI
* 2;
78 n_pts
= ceil ((th_1
- th_0
) / theta
);
81 printf ("start %f %f; th_0 = %f, th_1 = %f, r = %f, theta = %f\n", x0
, y0
, th_0
, th_1
, radius
, theta
);
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
++)
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
);
92 printf ("mid %f %f\n", cos (theta
) * radius
, sin (theta
) * radius
);
95 art_vpath_add_point (p_vpath
, pn
, pn_max
,
96 ART_LINETO
, xc
+ x1
, yc
+ y1
);
98 printf ("end %f %f\n", x1
, y1
);
102 /* Assume that forw and rev are at point i0. Bring them to i1,
103 joining with the vector i1 - i2.
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.}
111 forw is to the line's right and rev is to its left.
113 Precondition: no zero-length vectors, otherwise a divide by
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
)
132 printf ("join style = %d\n", join
);
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
;
139 dx1
= vpath
[i2
].x
- vpath
[i1
].x
;
140 dy1
= vpath
[i2
].y
- vpath
[i1
].y
;
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
);
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
);
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
);
160 printf ("%% render_seg: d[xy]0 = (%g, %g), dl[xy]0 = (%g, %g)\n",
161 dx0
, dy0
, dlx0
, dly0
);
163 printf ("%% render_seg: d[xy]1 = (%g, %g), dl[xy]1 = (%g, %g)\n",
164 dx1
, dy1
, dlx1
, dly1
);
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. */
170 /* positive for positive area (i.e. left turn) */
171 cross
= dx1
* dy0
- dx0
* dy1
;
173 dmx
= (dlx0
+ dlx1
) * 0.5;
174 dmy
= (dly0
+ dly1
) * 0.5;
175 dmr2
= dmx
* dmx
+ dmy
* dmy
;
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
;
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
)
188 scale
= line_width
* line_width
/ dmr2
;
193 if (cross
* cross
< EPSILON_2
&& dx0
* dx1
+ dy0
* dy1
>= 0)
197 printf ("%% render_seg: straight\n");
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
);
206 /* left turn, forw is outside and rev is inside */
209 printf ("%% render_seg: left\n");
212 #ifdef NO_OPTIMIZE_INNER
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
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)
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
);
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
);
244 if (join
== ART_PATH_STROKE_JOIN_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
);
252 else if (join
== ART_PATH_STROKE_JOIN_MITER
)
254 art_vpath_add_point (p_forw
, pn_forw
, pn_forw_max
,
255 ART_LINETO
, vpath
[i1
].x
- dmx
, vpath
[i1
].y
- dmy
);
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
,
268 /* right turn, rev is outside and forw is inside */
270 printf ("%% render_seg: right\n");
274 #ifdef NO_OPTIMIZE_INNER
277 (dmr2
> EPSILON_2
) &&
278 /* check that i1 - dm[xy] is inside i0-i1 rectangle */
279 (dx0
- dmx
) * dx0
+ (dy0
- dmy
) * dy0
> 0 &&
280 /* and that i1 - dm[xy] is inside i1-i2 rectangle */
281 ((dx1
+ dmx
) * dx1
+ (dy1
+ dmy
) * dy1
> 0)
282 #ifdef PEDANTIC_INNER
284 /* check that i1 - dl[xy]1 is inside i0-i1 rectangle */
285 (dx0
- dlx1
) * dx0
+ (dy0
- dly1
) * dy0
> 0 &&
286 /* and that i1 - dl[xy]0 is inside i1-i2 rectangle */
287 ((dx1
+ dlx0
) * dx1
+ (dy1
+ dly0
) * dy1
> 0)
291 /* can safely add single intersection point */
292 art_vpath_add_point (p_forw
, pn_forw
, pn_forw_max
,
293 ART_LINETO
, vpath
[i1
].x
- dmx
, vpath
[i1
].y
- dmy
);
297 /* need to loop-de-loop the inside */
298 art_vpath_add_point (p_forw
, pn_forw
, pn_forw_max
,
299 ART_LINETO
, vpath
[i1
].x
- dlx0
, vpath
[i1
].y
- dly0
);
300 art_vpath_add_point (p_forw
, pn_forw
, pn_forw_max
,
301 ART_LINETO
, vpath
[i1
].x
, vpath
[i1
].y
);
302 art_vpath_add_point (p_forw
, pn_forw
, pn_forw_max
,
303 ART_LINETO
, vpath
[i1
].x
- dlx1
, vpath
[i1
].y
- dly1
);
306 if (join
== ART_PATH_STROKE_JOIN_BEVEL
)
309 art_vpath_add_point (p_rev
, pn_rev
, pn_rev_max
,
310 ART_LINETO
, vpath
[i1
].x
+ dlx0
, vpath
[i1
].y
+ dly0
);
311 art_vpath_add_point (p_rev
, pn_rev
, pn_rev_max
,
312 ART_LINETO
, vpath
[i1
].x
+ dlx1
, vpath
[i1
].y
+ dly1
);
314 else if (join
== ART_PATH_STROKE_JOIN_MITER
)
316 art_vpath_add_point (p_rev
, pn_rev
, pn_rev_max
,
317 ART_LINETO
, vpath
[i1
].x
+ dmx
, vpath
[i1
].y
+ dmy
);
319 else if (join
== ART_PATH_STROKE_JOIN_ROUND
) {
320 art_svp_vpath_stroke_arc (p_rev
, pn_rev
, pn_rev_max
,
321 vpath
[i1
].x
, vpath
[i1
].y
,
331 /* caps i1, under the assumption of a vector from i0 */
333 render_cap (ArtVpath
**p_result
, int *pn_result
, int *pn_result_max
,
334 ArtVpath
*vpath
, int i0
, int i1
,
335 ArtPathStrokeCapType cap
, double line_width
, double flatness
)
343 dx0
= vpath
[i1
].x
- vpath
[i0
].x
;
344 dy0
= vpath
[i1
].y
- vpath
[i0
].y
;
346 /* Set dl[xy]0 to the vector from i0 to i1, rotated counterclockwise
347 90 degrees, and scaled to the length of line_width. */
348 scale
= line_width
/ sqrt (dx0
* dx0
+ dy0
* dy0
);
353 printf ("cap style = %d\n", cap
);
358 case ART_PATH_STROKE_CAP_BUTT
:
359 art_vpath_add_point (p_result
, pn_result
, pn_result_max
,
360 ART_LINETO
, vpath
[i1
].x
- dlx0
, vpath
[i1
].y
- dly0
);
361 art_vpath_add_point (p_result
, pn_result
, pn_result_max
,
362 ART_LINETO
, vpath
[i1
].x
+ dlx0
, vpath
[i1
].y
+ dly0
);
364 case ART_PATH_STROKE_CAP_ROUND
:
365 n_pts
= ceil (M_PI
/ (2.0 * M_SQRT2
* sqrt (flatness
/ line_width
)));
366 art_vpath_add_point (p_result
, pn_result
, pn_result_max
,
367 ART_LINETO
, vpath
[i1
].x
- dlx0
, vpath
[i1
].y
- dly0
);
368 for (i
= 1; i
< n_pts
; i
++)
370 double theta
, c_th
, s_th
;
372 theta
= M_PI
* i
/ n_pts
;
375 art_vpath_add_point (p_result
, pn_result
, pn_result_max
,
377 vpath
[i1
].x
- dlx0
* c_th
- dly0
* s_th
,
378 vpath
[i1
].y
- dly0
* c_th
+ dlx0
* s_th
);
380 art_vpath_add_point (p_result
, pn_result
, pn_result_max
,
381 ART_LINETO
, vpath
[i1
].x
+ dlx0
, vpath
[i1
].y
+ dly0
);
383 case ART_PATH_STROKE_CAP_SQUARE
:
384 art_vpath_add_point (p_result
, pn_result
, pn_result_max
,
386 vpath
[i1
].x
- dlx0
- dly0
,
387 vpath
[i1
].y
- dly0
+ dlx0
);
388 art_vpath_add_point (p_result
, pn_result
, pn_result_max
,
390 vpath
[i1
].x
+ dlx0
- dly0
,
391 vpath
[i1
].y
+ dly0
+ dlx0
);
397 * art_svp_from_vpath_raw: Stroke a vector path, raw version
398 * @vpath: #ArtVPath to stroke.
401 * @line_width: Width of stroke.
402 * @miter_limit: Miter limit.
403 * @flatness: Flatness.
405 * Exactly the same as art_svp_vpath_stroke(), except that the resulting
406 * stroke outline may self-intersect and have regions of winding number
409 * Return value: Resulting raw stroked outline in svp format.
412 art_svp_vpath_stroke_raw (ArtVpath
*vpath
,
413 ArtPathStrokeJoinType join
,
414 ArtPathStrokeCapType cap
,
419 int begin_idx
, end_idx
;
421 ArtVpath
*forw
, *rev
;
423 int n_forw_max
, n_rev_max
;
425 int n_result
, n_result_max
;
426 double half_lw
= 0.5 * line_width
;
428 int last
, xthis
, next
, second
;
432 forw
= art_new (ArtVpath
, n_forw_max
);
435 rev
= art_new (ArtVpath
, n_rev_max
);
439 result
= art_new (ArtVpath
, n_result_max
);
441 for (begin_idx
= 0; vpath
[begin_idx
].code
!= ART_END
; begin_idx
= end_idx
)
446 closed
= (vpath
[begin_idx
].code
== ART_MOVETO
);
448 /* we don't know what the first point joins with until we get to the
449 last point and see if it's closed. So we start with the second
452 Note: this is not strictly true (we now know it's closed from
453 the opening pathcode), but why fix code that isn't broken?
457 /* skip over identical points at the beginning of the subpath */
458 for (i
= xthis
+ 1; vpath
[i
].code
== ART_LINETO
; i
++)
460 dx
= vpath
[i
].x
- vpath
[xthis
].x
;
461 dy
= vpath
[i
].y
- vpath
[xthis
].y
;
462 if (dx
* dx
+ dy
* dy
> EPSILON_2
)
468 /* invariant: this doesn't coincide with next */
469 while (vpath
[next
].code
== ART_LINETO
)
473 /* skip over identical points after the beginning of the subpath */
474 for (i
= xthis
+ 1; vpath
[i
].code
== ART_LINETO
; i
++)
476 dx
= vpath
[i
].x
- vpath
[xthis
].x
;
477 dy
= vpath
[i
].y
- vpath
[xthis
].y
;
478 if (dx
* dx
+ dy
* dy
> EPSILON_2
)
482 if (vpath
[next
].code
!= ART_LINETO
)
484 /* reached end of path */
485 /* make "closed" detection conform to PostScript
486 semantics (i.e. explicit closepath code rather than
487 just the fact that end of the path is the beginning) */
489 vpath
[xthis
].x
== vpath
[begin_idx
].x
&&
490 vpath
[xthis
].y
== vpath
[begin_idx
].y
)
494 /* path is closed, render join to beginning */
495 render_seg (&forw
, &n_forw
, &n_forw_max
,
496 &rev
, &n_rev
, &n_rev_max
,
497 vpath
, last
, xthis
, second
,
498 join
, half_lw
, miter_limit
, flatness
);
501 printf ("%% forw %d, rev %d\n", n_forw
, n_rev
);
503 /* do forward path */
504 art_vpath_add_point (&result
, &n_result
, &n_result_max
,
505 ART_MOVETO
, forw
[n_forw
- 1].x
,
507 for (j
= 0; j
< n_forw
; j
++)
508 art_vpath_add_point (&result
, &n_result
, &n_result_max
,
509 ART_LINETO
, forw
[j
].x
,
512 /* do reverse path, reversed */
513 art_vpath_add_point (&result
, &n_result
, &n_result_max
,
514 ART_MOVETO
, rev
[0].x
,
516 for (j
= n_rev
- 1; j
>= 0; j
--)
517 art_vpath_add_point (&result
, &n_result
, &n_result_max
,
518 ART_LINETO
, rev
[j
].x
,
526 /* add to forw rather than result to ensure that
527 forw has at least one point. */
528 render_cap (&forw
, &n_forw
, &n_forw_max
,
530 cap
, half_lw
, flatness
);
531 art_vpath_add_point (&result
, &n_result
, &n_result_max
,
532 ART_MOVETO
, forw
[0].x
,
534 for (j
= 1; j
< n_forw
; j
++)
535 art_vpath_add_point (&result
, &n_result
, &n_result_max
,
536 ART_LINETO
, forw
[j
].x
,
538 for (j
= n_rev
- 1; j
>= 0; j
--)
539 art_vpath_add_point (&result
, &n_result
, &n_result_max
,
540 ART_LINETO
, rev
[j
].x
,
542 render_cap (&result
, &n_result
, &n_result_max
,
543 vpath
, second
, begin_idx
,
544 cap
, half_lw
, flatness
);
545 art_vpath_add_point (&result
, &n_result
, &n_result_max
,
546 ART_LINETO
, forw
[0].x
,
551 render_seg (&forw
, &n_forw
, &n_forw_max
,
552 &rev
, &n_rev
, &n_rev_max
,
553 vpath
, last
, xthis
, next
,
554 join
, half_lw
, miter_limit
, flatness
);
562 printf ("%% n_result = %d\n", n_result
);
564 art_vpath_add_point (&result
, &n_result
, &n_result_max
, ART_END
, 0, 0);
576 print_ps_vpath (ArtVpath
*vpath
)
580 for (i
= 0; vpath
[i
].code
!= ART_END
; i
++)
582 switch (vpath
[i
].code
)
585 printf ("%g %g moveto\n", XOFF
+ vpath
[i
].x
, YOFF
- vpath
[i
].y
);
588 printf ("%g %g lineto\n", XOFF
+ vpath
[i
].x
, YOFF
- vpath
[i
].y
);
594 printf ("stroke showpage\n");
598 print_ps_svp (ArtSVP
*vpath
)
602 printf ("%% begin\n");
603 for (i
= 0; i
< vpath
->n_segs
; i
++)
605 printf ("%g setgray\n", vpath
->segs
[i
].dir
? 0.7 : 0);
606 for (j
= 0; j
< vpath
->segs
[i
].n_points
; j
++)
608 printf ("%g %g %s\n",
609 XOFF
+ vpath
->segs
[i
].points
[j
].x
,
610 YOFF
- vpath
->segs
[i
].points
[j
].y
,
611 j
? "lineto" : "moveto");
616 printf ("showpage\n");
620 /* Render a vector path into a stroked outline.
622 Status of this routine:
624 Basic correctness: Only miter and bevel line joins are implemented,
625 and only butt line caps. Otherwise, seems to be fine.
627 Numerical stability: We cheat (adding random perturbation). Thus,
628 it seems very likely that no numerical stability problems will be
631 Speed: Should be pretty good.
633 Precision: The perturbation fuzzes the coordinates slightly,
634 but not enough to be visible. */
636 * art_svp_vpath_stroke: Stroke a vector path.
637 * @vpath: #ArtVPath to stroke.
640 * @line_width: Width of stroke.
641 * @miter_limit: Miter limit.
642 * @flatness: Flatness.
644 * Computes an svp representing the stroked outline of @vpath. The
645 * width of the stroked line is @line_width.
647 * Lines are joined according to the @join rule. Possible values are
648 * ART_PATH_STROKE_JOIN_MITER (for mitered joins),
649 * ART_PATH_STROKE_JOIN_ROUND (for round joins), and
650 * ART_PATH_STROKE_JOIN_BEVEL (for bevelled joins). The mitered join
651 * is converted to a bevelled join if the miter would extend to a
652 * distance of more than @miter_limit * @line_width from the actual
655 * If there are open subpaths, the ends of these subpaths are capped
656 * according to the @cap rule. Possible values are
657 * ART_PATH_STROKE_CAP_BUTT (squared cap, extends exactly to end
658 * point), ART_PATH_STROKE_CAP_ROUND (rounded half-circle centered at
659 * the end point), and ART_PATH_STROKE_CAP_SQUARE (squared cap,
660 * extending half @line_width past the end point).
662 * The @flatness parameter controls the accuracy of the rendering. It
663 * is most important for determining the number of points to use to
664 * approximate circular arcs for round lines and joins. In general, the
665 * resulting vector path will be within @flatness pixels of the "ideal"
666 * path containing actual circular arcs. I reserve the right to use
667 * the @flatness parameter to convert bevelled joins to miters for very
668 * small turn angles, as this would reduce the number of points in the
669 * resulting outline path.
671 * The resulting path is "clean" with respect to self-intersections, i.e.
672 * the winding number is 0 or 1 at each point.
674 * Return value: Resulting stroked outline in svp format.
677 art_svp_vpath_stroke (ArtVpath
*vpath
,
678 ArtPathStrokeJoinType join
,
679 ArtPathStrokeCapType cap
,
684 #ifdef ART_USE_NEW_INTERSECTOR
685 ArtVpath
*vpath_stroke
;
689 vpath_stroke
= art_svp_vpath_stroke_raw (vpath
, join
, cap
,
690 line_width
, miter_limit
, flatness
);
692 //print_ps_vpath (vpath_stroke);
694 svp
= art_svp_from_vpath (vpath_stroke
);
696 //print_ps_svp (svp);
698 art_free (vpath_stroke
);
700 swr
= art_svp_writer_rewind_new (ART_WIND_RULE_NONZERO
);
701 art_svp_intersector (svp
, swr
);
703 svp2
= art_svp_writer_rewind_reap (swr
);
705 //print_ps_svp (svp2);
710 ArtVpath
*vpath_stroke
, *vpath2
;
711 ArtSVP
*svp
, *svp2
, *svp3
;
713 vpath_stroke
= art_svp_vpath_stroke_raw (vpath
, join
, cap
,
714 line_width
, miter_limit
, flatness
);
716 //print_ps_vpath (vpath_stroke);
718 vpath2
= art_vpath_perturb (vpath_stroke
);
720 //print_ps_vpath (vpath2);
722 art_free (vpath_stroke
);
723 svp
= art_svp_from_vpath (vpath2
);
725 //print_ps_svp (svp);
728 svp2
= art_svp_uncross (svp
);
730 //print_ps_svp (svp2);
733 svp3
= art_svp_rewind_uncrossed (svp2
, ART_WIND_RULE_NONZERO
);
735 //print_ps_svp (svp3);