1 /* Copyright (C) 2000-2012 by George Williams, 2019 by Skef Iterum */
2 /*
3  * Redistribution and use in source and binary forms, with or without
4  * modification, are permitted provided that the following conditions are met:
5 
6  * Redistributions of source code must retain the above copyright notice, this
7  * list of conditions and the following disclaimer.
8 
9  * Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12 
13  * The name of the author may not be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15 
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <fontforge-config.h>
29 
30 #include "splinestroke.h"
31 
32 #include "baseviews.h"
33 #include "cvundoes.h"
34 #include "fontforge.h"
35 #include "splinefit.h"
36 #include "splinefont.h"
37 #include "splineorder2.h"
38 #include "splineoverlap.h"
39 #include "splineutil.h"
40 #include "splineutil2.h"
41 #include "utanvec.h"
42 
43 #include <assert.h>
44 #include <complex.h>
45 #include <math.h>
46 
47 #define CIRCOFF 0.551915
48 #define LOG_MAXDIM_ADJ (1.302585)
49 
50 // Rounding makes even slope-continuous splines only approximately so.
51 #define INTERSPLINE_MARGIN (1e-1)
52 #define INTRASPLINE_MARGIN (1e-8)
53 #define FIXUP_MARGIN (1e-1)
54 #define CUSPD_MARGIN (1e-5)
55 // About .25 degrees
56 #define COS_MARGIN (1e-5)
57 #define MIN_ACCURACY (1e-5)
58 
NormAngle(bigreal a)59 static inline bigreal NormAngle(bigreal a) {
60     if ( a > FF_PI )
61 	return a-2*FF_PI;
62     else if (a <= -FF_PI)
63 	return a+2*FF_PI;
64     else
65 	return a;
66 }
67 
BPNear(BasePoint bp1,BasePoint bp2)68 static inline int BPNear(BasePoint bp1, BasePoint bp2) {
69     return BPWithin(bp1, bp2, INTRASPLINE_MARGIN);
70 }
71 
72 enum nibtype { nib_ellip, nib_rect, nib_convex };
73 
74 // c->nibcorners is a structure that models the "corners" of the current nib
75 // treated as if all splines were linear, and therefore as a convex polygon.
76 
77 #define NC_IN_IDX 0
78 #define NC_OUT_IDX 1
79 
80 #define N_NEXTI(n, i) (((i)+1)%(n))
81 #define N_PREVI(n, i) (((n)+(i)-1)%(n))
82 #define NC_NEXTI(c, i) N_NEXTI((c)->n, i)
83 #define NC_PREVI(c, i) N_PREVI((c)->n, i)
84 
85 typedef struct nibcorner {
86     SplinePoint *on_nib;
87     BasePoint utv[2];    // Unit tangents entering and leaving corner
88     unsigned int linear: 1;
89 } NibCorner;
90 
91 typedef struct strokecontext {
92     enum nibtype nibtype;
93     enum linejoin join;
94     enum linecap cap;
95     enum stroke_rmov rmov;
96     bigreal joinlimit;
97     bigreal extendcap;
98     bigreal acctarget;
99     SplineSet *nib;
100     int n;
101     NibCorner *nibcorners;
102     BasePoint pseudo_origin;
103     bigreal log_maxdim;
104     unsigned int jlrelative: 1;
105     unsigned int ecrelative: 1;
106     unsigned int remove_inner: 1;
107     unsigned int remove_outer: 1;
108     unsigned int leave_users_center: 1;	// Don't center the nib when stroking
109     unsigned int simplify:1;
110     unsigned int extrema:1;
111     unsigned int ratio_al:1;
112     unsigned int contour_was_ccw:1;
113 } StrokeContext;
114 
115 #define NIBOFF_CW_IDX 0
116 #define NIBOFF_CCW_IDX 1
117 
118 typedef struct niboffset {
119     BasePoint utanvec;        // The reference angle
120     int nci[2];
121     BasePoint off[2];
122     bigreal nt;               // The t value on the nib spline for the angle
123     unsigned int at_line: 1;  // Whether the angle is ambiguous
124     unsigned int curve: 1;    // Whether the angle is on a curve or line
125     unsigned int reversed: 1; // Whether the calculations were reversed for
126                               // tracing the right side
127 } NibOffset;
128 
129 /******************************************************************************/
130 /* ****************************** Configuration ***************************** */
131 /******************************************************************************/
132 
InitializeStrokeInfo(StrokeInfo * sip)133 StrokeInfo *InitializeStrokeInfo(StrokeInfo *sip) {
134     if ( sip==NULL )
135 	sip = malloc(sizeof(StrokeInfo));
136 
137     memset(sip, 0, sizeof(StrokeInfo));
138 
139     sip->width = 50.0;
140     sip->join = lj_nib;
141     sip->cap = lc_nib;
142     sip->stroke_type = si_round;
143     sip->rmov = srmov_layer;
144     sip->simplify = true;
145     sip->extrema = true;
146     sip->jlrelative = true;
147     sip->ecrelative = true;
148     sip->leave_users_center = true;
149     sip->joinlimit = 10.0;
150     sip->accuracy_target = 0.25;
151 
152     return sip;
153 }
154 
SITranslatePSArgs(StrokeInfo * sip,enum linejoin lj,enum linecap lc)155 void SITranslatePSArgs(StrokeInfo *sip, enum linejoin lj, enum linecap lc) {
156     sip->extendcap = 0;
157     switch (lc) {
158 	case lc_round:
159 	    sip->cap = lc_nib;
160 	    break;
161 	case lc_square:
162 	    sip->cap = lc_butt;
163 	    sip->extendcap = 0.5;
164 	    sip->ecrelative = true;
165 	    break;
166 	default:
167 	    sip->cap = lc;
168     }
169     switch (lj) {
170 	case lj_round:
171 	    sip->join = lj_nib;
172 	    break;
173 	default:
174 	    sip->join = lj;
175     }
176 }
177 
178 #define CONVEX_SLOTS 1
179 #define FREEHAND_TOKNUM -10
180 #define CVSTROKE_TOKNUM -11
181 
182 static SplineSet *convex_nibs[CONVEX_SLOTS];
183 
ConvexNibID(const char * tok)184 int ConvexNibID(const char *tok) {
185     if ( tok!=NULL ) {
186 	if ( strcmp(tok, "default")==0 )
187 	    return 0;
188 	else if ( strcmp(tok, "freehand")==0 )
189 	    return FREEHAND_TOKNUM;
190 	else if ( strcmp(tok, "ui")==0 )
191 	    return CVSTROKE_TOKNUM;
192 	else {
193 	    return -1;
194 	}
195     } else
196 	return -1;
197 }
198 
StrokeSetConvex(SplineSet * ss,int toknum)199 int StrokeSetConvex(SplineSet *ss, int toknum) {
200     StrokeInfo *si = NULL;
201 
202     if ( toknum>=0 && toknum<CONVEX_SLOTS ) {
203 	if ( convex_nibs[toknum]!=NULL )
204 	    SplinePointListFree(convex_nibs[toknum]);
205 	convex_nibs[toknum] = ss;
206 	return true;
207     } else if ( no_windowing_ui )
208 	return false;
209     else if ( toknum==CVSTROKE_TOKNUM )
210 	si = CVStrokeInfo();
211     else if ( toknum==FREEHAND_TOKNUM )
212 	si = CVFreeHandInfo();
213     else
214 	return false;
215 
216     if ( si->nib!=NULL )
217 	SplinePointListFree(si->nib);
218     si->nib = ss;
219 
220     return true;
221 }
222 
StrokeGetConvex(int toknum,int cpy)223 SplineSet *StrokeGetConvex(int toknum, int cpy) {
224     SplineSet *ss = NULL;
225 
226     if ( toknum>=0 && toknum<CONVEX_SLOTS )
227 	ss = convex_nibs[toknum];
228     else if ( no_windowing_ui )
229 	return NULL;
230     else if ( toknum==CVSTROKE_TOKNUM )
231 	ss = CVStrokeInfo()->nib;
232     else if ( toknum==FREEHAND_TOKNUM )
233 	ss = CVFreeHandInfo()->nib;
234     else
235 	return NULL;
236 
237     if ( ss==NULL )
238 	return NULL;
239 
240     if ( cpy )
241 	return SplinePointListCopy(ss);
242     else
243 	return ss;
244 }
245 
CVStrokeInfo()246 StrokeInfo *CVStrokeInfo() {
247     static StrokeInfo *cv_si;
248     if ( cv_si==NULL ) {
249 	cv_si = InitializeStrokeInfo(NULL);
250 	cv_si->height = cv_si->width;
251 	cv_si->penangle = FF_PI/4;
252     }
253     return cv_si;
254 }
255 
CVFreeHandInfo()256 StrokeInfo *CVFreeHandInfo() {
257     static StrokeInfo *fv_si = NULL;
258 
259     if ( fv_si==NULL ) {
260 	fv_si = InitializeStrokeInfo(NULL);
261 	fv_si->cap = lc_butt;
262 	fv_si->stroke_type = si_centerline;
263 	fv_si->height = fv_si->width;
264 	fv_si->penangle = FF_PI/4;
265     }
266     return fv_si;
267 }
268 
269 /******************************************************************************/
270 /* ********************************* Utility ******************************** */
271 /******************************************************************************/
272 
LineSameSide(BasePoint l1,BasePoint l2,BasePoint p,BasePoint r,int on_line_ok)273 static int LineSameSide(BasePoint l1, BasePoint l2, BasePoint p, BasePoint r,
274                         int on_line_ok) {
275     bigreal tp, tr;
276 
277     tp = (l2.x-l1.x)*(p.y-l1.y) - (p.x-l1.x)*(l2.y-l1.y);
278     tr = (l2.x-l1.x)*(r.y-l1.y) - (r.x-l1.x)*(l2.y-l1.y);
279 
280     if ( RealWithin(tp, 0, 1e-5) )
281 	return on_line_ok;
282     // Rounding means we should be tolerant when on_line_ok is true.
283     // Being that tolerant when on_line_ok is false would
284     // yield too many false negatives.
285     if ( on_line_ok && RealWithin(tp, 0, 1) )
286 	return true;
287     return signbit(tr)==signbit(tp);
288 }
289 
ProjectPointOnLine(BasePoint p,BasePoint lp,BasePoint lut)290 static BasePoint ProjectPointOnLine(BasePoint p, BasePoint lp, BasePoint lut) {
291 
292     BasePoint t = BPSub(p, lp);
293     return BPAdd(lp, BPScale(lut, BPDot(t, lut)));
294 }
295 
296 /* Return values:
297  * > 0 -> number of intersections
298  *   0 -> No overlap
299  *  -1 -> Circle 2 is strictly inside circle 1
300  *  -2 -> Circle 1 is strictly inside circle 2
301  */
CirclesTest(BasePoint c1,bigreal r1,BasePoint c2,bigreal r2)302 static int CirclesTest(BasePoint c1, bigreal r1, BasePoint c2, bigreal r2) {
303     bigreal c_dist = BPDist(c1, c2);
304 
305     assert( r1 >= 0 && r2 >= 0 );
306     if (fabs(c_dist - r1 - r2) < 1e-3)
307 	return 1;
308     else if (c_dist > r1 + r2)
309 	return 0;
310     else if (c_dist < fabs(r1 - r2)) {
311 	if (r1 > r2)
312 	    return -1;
313 	else
314 	    return -2;
315     } else
316 	return 2;
317 }
318 
LineCircleTest(BasePoint lp,BasePoint lut,BasePoint c1,bigreal r1)319 static int LineCircleTest(BasePoint lp, BasePoint lut, BasePoint c1,
320                           bigreal r1) {
321     BasePoint t = ProjectPointOnLine(c1, lp, lut);
322     bigreal cl_dist = BPDist(c1, t);
323 
324     assert( r1 >= 0 );
325 
326     if ( fabs(cl_dist-r1) < 1e-3 )
327 	return 1;
328     else if ( cl_dist > r1 )
329 	return 0;
330     else
331 	return 2;
332 }
333 
PVPCircle(BasePoint p1,BasePoint ut1,BasePoint p2,BasePoint * c,bigreal * r)334 static int PVPCircle(BasePoint p1, BasePoint ut1, BasePoint p2, BasePoint *c,
335                      bigreal *r) {
336     BasePoint cv1, p3, cv3;
337 
338     assert( c!=NULL && r!=NULL );
339 
340     cv1 = BP90CCW(ut1);
341     p3 = BPAvg(p1, p2);
342     cv3 = BP90CCW(BPSub(p2, p1));
343     if ( !IntersectLinesSlopes(c, &p1, &cv1, &p3, &cv3) )
344 	return 0;
345     cv3 = BPSub(p1,*c);
346     *r = sqrt(BPLenSq(cv3));
347     return BPDot(cv3, cv1) < 0 ? -1 : 1;
348 }
349 
CloserPoint(BasePoint r,BasePoint p1,BasePoint p2)350 static BasePoint CloserPoint(BasePoint r, BasePoint p1, BasePoint p2) {
351     BasePoint d1 = BPSub(p1, r), d2 = BPSub(p2, r);
352 
353     return BPLenSq(d1) > BPLenSq(d2) ? p2 : p1;
354 }
355 
CloserIntersection(BasePoint p1,BasePoint p2,BasePoint i1,BasePoint i2,BasePoint refp)356 static BasePoint CloserIntersection(BasePoint p1, BasePoint p2, BasePoint i1,
357                                     BasePoint i2, BasePoint refp) {
358     if ( LineSameSide(p1, p2, i1, refp, false) )
359 	if ( LineSameSide(p1, p2, i2, refp, false) )
360 	    return CloserPoint(p1, i1, i2);
361 	else
362 	    return i1;
363     else
364 	return i2;
365 }
366 
IntersectLineCircle(BasePoint lp,BasePoint lut,BasePoint c1,bigreal r1,BasePoint * i1,BasePoint * i2)367 static void IntersectLineCircle(BasePoint lp, BasePoint lut, BasePoint c1,
368                                 bigreal r1, BasePoint *i1, BasePoint *i2) {
369     bigreal sqdiff;
370     BasePoint pp, t, perp;
371 
372     assert( i1!=NULL && i2!=NULL );
373     *i1 = *i2 = BPUNINIT;
374 
375     pp = ProjectPointOnLine(c1, lp, lut);
376     t = BPSub(c1, pp);
377     sqdiff = r1*r1 - BPLenSq(t);
378     if ( fabs(sqdiff) < 1e-4 ) {
379 	*i1 = *i2 = pp;
380         return;
381     }
382     perp = BPScale(lut, sqrt(sqdiff));
383     *i1 = BPAdd(pp, perp);
384     *i2 = BPSub(pp, perp);
385 }
386 
IntersectCircles(BasePoint c1,bigreal r1,BasePoint c2,bigreal r2,BasePoint * i1,BasePoint * i2)387 static void IntersectCircles(BasePoint c1, bigreal r1, BasePoint c2, bigreal r2,
388                              BasePoint *i1, BasePoint *i2) {
389     BasePoint t1, t2;
390     bigreal tmp, c_dist = BPDist(c1, c2), r2s = r2*r2, r1s = r1*r1;
391     bigreal cds = c_dist*c_dist;
392 
393     assert( i1!=NULL && i2!=NULL );
394     *i1 = *i2 = BPUNINIT;
395 
396     t1 = BPScale(BPSub(c1, c2), (r2s - r1s)/(2.0*cds));
397     t1 = BPAdd(t1, BPAvg(c2, c1));
398     tmp = 2.0 * (r2s + r1s)/cds - pow(r2s - r1s, 2.0)/(cds*cds) - 1;
399     if ( RealWithin(tmp, 0.0, 1e-8) ) {
400 	*i1 = t1;
401 	return;
402     }
403     t2 = BPScale(((BasePoint) { c1.y-c2.y, c2.x-c1.x }), sqrt(tmp)/2.0);
404     *i1 = BPAdd(t1,t2);
405     *i2 = BPSub(t1,t2);
406 }
407 
SplineSetLineTo(SplineSet * cur,BasePoint xy)408 static void SplineSetLineTo(SplineSet *cur, BasePoint xy) {
409     SplinePoint *sp = SplinePointCreate(xy.x, xy.y);
410     SplineMake3(cur->last, sp);
411     cur->last = sp;
412 }
413 
414 /* A hack for cleaning up cusps and intersections on isolated
415  * counter-clockwise contours -- builds an enclosing clockwise
416  * rectangle, runs remove-overlap, and removes the rectangle.
417  */
SplineContourOuterCCWRemoveOverlap(SplineSet * ss)418 static SplineSet *SplineContourOuterCCWRemoveOverlap(SplineSet *ss) {
419     DBounds b;
420     SplineSet *ss_tmp, *ss_last = NULL;
421 
422     SplineSetQuickBounds(ss,&b);
423     b.minx -= 100;
424     b.miny -= 100;
425     b.maxx += 100;
426     b.maxy += 100;
427 
428     ss_tmp = chunkalloc(sizeof(SplineSet));
429     ss_tmp->first = ss_tmp->last = SplinePointCreate(b.minx,b.miny);
430     SplineSetLineTo(ss_tmp, (BasePoint) { b.minx, b.maxy } );
431     SplineSetLineTo(ss_tmp, (BasePoint) { b.maxx, b.maxy } );
432     SplineSetLineTo(ss_tmp, (BasePoint) { b.maxx, b.miny } );
433     SplineMake3(ss_tmp->last, ss_tmp->first);
434     ss_tmp->last = ss_tmp->first;
435     ss->next = ss_tmp;
436     ss=SplineSetRemoveOverlap(NULL,ss,over_remove);
437     for ( ss_tmp=ss; ss_tmp!=NULL; ss_last=ss_tmp, ss_tmp=ss_tmp->next ) {
438 	if ( ss_tmp->first->me.x==b.minx || ss_tmp->first->me.x==b.maxx ) {
439 	    if ( ss_last==NULL )
440 		ss = ss->next;
441     	    else
442 		ss_last->next = ss_tmp->next;
443 	    ss_tmp->next = NULL;
444 	    SplinePointListFree(ss_tmp);
445 	    return ss;
446 	}
447     }
448     assert(0);
449     return ss;
450 }
451 
452 /******************************************************************************/
453 /* ****************************** Nib Geometry ****************************** */
454 /******************************************************************************/
455 
456 /* A contour is a valid convex polygon if:
457  *  1. It contains something (not a line or a single point
458  *     but a closed piecewise spline containing area)
459  *  2. All edges/splines are lines
460  *  3. It is convex
461  *  4. There are no extraneous points on the edges
462  *
463  * A contour is a valid nib shape if:
464  *  5. It's on-curve points would form a valid convex
465  *     polygon if there were no control points.
466  *  6. Every edge/spline is either:
467  *     6a. A line, or
468  *     6b. A spline where *both* control points are:
469  *         6bi.  Inside the triangle formed by the spline's from/to edge
470  *               and extending the lines of the adjacent edges**, and
471  *         6bii. The line segment of each control point does not
472  *               cross the extended line of the other control point.
473  *         ** Simplification -- see documentation and lref_cp below.
474  */
NibIsValid(SplineSet * ss)475 enum ShapeType NibIsValid(SplineSet *ss) {
476     Spline *s;
477     BasePoint lref_cp;
478     bigreal d, anglesum = 0, angle, last_angle;
479     int n = 1;
480 
481     if ( ss->first==NULL )
482 	return Shape_TooFewPoints;
483     if ( ss->first->prev==NULL )
484 	return Shape_NotClosed;
485     if ( SplinePointListIsClockwise(ss)!=1 )
486 	return Shape_CCW;
487     if ( ss->first->next->order2 )
488 	return Shape_Quadratic;
489 
490     s = ss->first->prev;
491     last_angle = atan2(s->to->me.y - s->from->me.y,
492                        s->to->me.x - s->from->me.x);
493     if ( SplineIsLinear(s) )
494 	lref_cp = s->from->me;
495     else
496 	lref_cp = s->to->prevcp;
497 
498     s = ss->first->next;
499     SplinePointListSelect(ss, false);
500     SplinePointListClearCPSel(ss);
501     // Polygonal checks
502     while ( true ) {
503 	s->from->selected = true;
504 	if ( BPWithin(s->from->me, s->to->me,1e-2) )
505 	    return Shape_TinySpline;
506 	angle = atan2(s->to->me.y - s->from->me.y, s->to->me.x - s->from->me.x);
507 	if ( RealWithin(angle, last_angle, 1e-4) )
508 	    return Shape_PointOnEdge;
509 	d = last_angle-angle;
510 	d = NormAngle(d);
511 	if ( d<0 )
512 	    return Shape_CCWTurn;
513 	anglesum += d;
514 	s->from->selected = false;
515 	last_angle = angle;
516 	s=s->to->next;
517 	if ( s==ss->first->next )
518 	    break;
519 	++n;
520     }
521     if ( n<3 )
522 	return Shape_TooFewPoints;
523     if ( !RealWithin(anglesum, 2*FF_PI, 1e-1) )
524 	return Shape_SelfIntersects;
525 
526     assert( s==ss->first->next );
527     // Curve/control point checks
528     while ( true ) {
529 	if ( SplineIsLinear(s) )
530 	    lref_cp = s->from->me;
531 	else {
532 	    if ( s->from->nonextcp || BPNear(s->from->nextcp, s->from->me) ||
533 	         s->to->noprevcp || BPNear(s->to->prevcp, s->to->me) )
534 		return Shape_HalfLinear;
535 	    s->from->nextcpselected = true;
536 	    if ( LineSameSide(s->from->me, s->to->me, s->from->nextcp,
537 	                      s->from->prev->from->me, false) )
538 		return Shape_BadCP_R1;
539 	    if ( !LineSameSide(lref_cp, s->from->me,
540 	                       s->from->nextcp, s->to->me, true) )
541 		return Shape_BadCP_R2;
542 	    if ( !LineSameSide(s->to->me, s->to->prevcp,
543 	                       s->from->nextcp, s->from->me, true) )
544 		return Shape_BadCP_R3;
545 	    s->from->nextcpselected = false;
546 	    s->from->selected = false;
547 	    s->to->selected = true;
548 	    s->to->prevcpselected = true;
549 	    if ( LineSameSide(s->from->me, s->to->me, s->to->prevcp,
550 	                      s->to->next->to->me, false) )
551 		return Shape_BadCP_R1;
552 	    if ( !LineSameSide(s->to->me, s->to->next->to->me,
553 	                       s->to->prevcp, s->from->me, true) )
554 		return Shape_BadCP_R2;
555 	    if ( !LineSameSide(s->from->me, s->from->nextcp,
556 	                       s->to->prevcp, s->to->me, true) )
557 		return Shape_BadCP_R3;
558 	    s->to->prevcpselected = false;
559 	    s->to->selected = false;
560 	    lref_cp = s->to->prevcp;
561 	}
562 	s=s->to->next;
563 	if ( s==ss->first->next )
564 	    break;
565     }
566     return Shape_Convex;
567 }
568 
NibShapeTypeMsg(enum ShapeType st)569 const char *NibShapeTypeMsg(enum ShapeType st) {
570     switch (st) {
571       case Shape_CCW:
572 	return _("The contour winds counter-clockwise; "
573 	         "a nib must wind clockwise.");
574       case Shape_CCWTurn:
575 	return _("The contour bends or curves counter-clockwise "
576 	         "at the selected point; "
577 	         "all on-curve points must bend or curve clockwise.");
578       case Shape_PointOnEdge:
579 	return _("The selected point is on a line; "
580 	         "all on-curve points must bend or curve clockwise.");
581       case Shape_TooFewPoints:
582 	return _("A nib must have at least three on-curve points.");
583       case Shape_Quadratic:
584 	return _("The contour is quadratic; a nib must be a cubic contour.");
585       case Shape_NotClosed:
586 	return _("The contour is open; a nib must be closed.");
587       case Shape_TinySpline:
588 	return _("The selected point is the start of a 'tiny' spline; "
589 	         "splines that small may cause inaccurate calculations.");
590       case Shape_HalfLinear:
591 	return _("The selected point starts a spline with one control point; "
592 	         "nib splines need a defined slope at both points.");
593       case Shape_BadCP_R1:
594 	return _("The selected control point's position violates Rule 1 "
595 	         "(see documentation).");
596       case Shape_BadCP_R2:
597 	return _("The selected control point's position violates Rule 2 "
598 	         "(see documentation).");
599       case Shape_BadCP_R3:
600 	return _("The selected control point's position violates Rule 3 "
601 	         "(see documentation).");
602       case Shape_SelfIntersects:
603 	return _("The contour intersects itself; a nib must non-intersecting.");
604       case Shape_Convex:
605 	return _("Unrecognized nib shape error.");
606     }
607 
608     return NULL;
609 }
610 
BuildNibCorners(NibCorner ** ncp,SplineSet * nib,int * maxp,int * n)611 static void BuildNibCorners(NibCorner **ncp, SplineSet *nib, int *maxp,
612                             int *n) {
613     int i, max_utan_index = -1;
614     BasePoint max_utanangle = UTMIN;
615     SplinePoint *sp;
616     NibCorner *nc = *ncp, *tpc;
617 
618     if ( nc==NULL )
619 	nc = calloc(*maxp,sizeof(NibCorner));
620 
621     for ( sp=nib->first, i=0; ; ) {
622 	if ( i==*maxp ) { // We guessed wrong
623 	    *maxp *= 2;
624 	    nc = realloc(nc, *maxp * sizeof(NibCorner));
625 	    memset(nc+i, 0, (*maxp-i) * sizeof(NibCorner));
626 	}
627 	nc[i].on_nib = sp;
628 	nc[i].linear = SplineIsLinear(sp->next);
629 	nc[i].utv[NC_IN_IDX] = SplineUTanVecAt(sp->prev, 1.0);
630 	nc[i].utv[NC_OUT_IDX] = SplineUTanVecAt(sp->next, 0.0);
631 	if ( JointBendsCW(nc[i].utv[NC_IN_IDX], nc[i].utv[NC_OUT_IDX]) )
632 	    // Flatten potential LineSameSide permissiveness
633 	    nc[i].utv[NC_OUT_IDX] = nc[i].utv[NC_IN_IDX];
634 	if (    UTanVecGreater(nc[i].utv[NC_IN_IDX], max_utanangle)
635 	     || BPNear(nc[i].utv[NC_IN_IDX], max_utanangle) ) {
636 	    max_utan_index = i;
637 	    max_utanangle = nc[i].utv[NC_IN_IDX];
638 	}
639 	++i;
640 	sp=sp->next->to;
641 	if ( sp==nib->first )
642 	    break;
643     }
644     *n = i;
645 
646     // Put in order of decreasing utanvec[NC_IN_IDX]
647     assert( max_utan_index != -1 );
648     if (max_utan_index != 0) {
649 	tpc = malloc(i*sizeof(NibCorner));
650 	memcpy(tpc, nc+max_utan_index, (i-max_utan_index)*sizeof(NibCorner));
651 	memcpy(tpc+(i-max_utan_index), nc, max_utan_index*sizeof(NibCorner));
652 	free(nc);
653 	nc = tpc;
654 	*maxp = i;
655     }
656 
657     *ncp = nc;
658 }
659 
660 /* The index into c->nibcorners associated with unit tangent ut
661  */
_IndexForUTanVec(NibCorner * nc,int n,BasePoint ut,int nci_hint)662 static int _IndexForUTanVec(NibCorner *nc, int n, BasePoint ut, int nci_hint) {
663     int nci;
664 
665     assert( nci_hint == -1 || (nci_hint >= 0 && nci_hint < n ) );
666 
667     if (   nci_hint != -1
668         && UTanVecsSequent(nc[nci_hint].utv[NC_IN_IDX], ut,
669 	                   nc[N_NEXTI(n, nci_hint)].utv[NC_IN_IDX],
670                            false) )
671 	nci = nci_hint;
672     else // XXX Replace with binary search
673 	for (nci = 0; nci<n; ++nci)
674 	    if ( UTanVecsSequent(nc[nci].utv[NC_IN_IDX], ut,
675 	                         nc[N_NEXTI(n, nci)].utv[NC_IN_IDX],
676 	                         false) )
677 		break;
678     assert(nci < n);
679     return nci;
680 }
681 
IndexForUTanVec(StrokeContext * c,BasePoint ut,int nci_hint)682 static int IndexForUTanVec(StrokeContext *c, BasePoint ut, int nci_hint) {
683     return _IndexForUTanVec(c->nibcorners, c->n, ut, nci_hint);
684 }
685 
686 /* The offset from the stroked curve associated with unit tangent ut
687  * (or it's reverse).
688  */
_CalcNibOffset(NibCorner * nc,int n,BasePoint ut,int reverse,NibOffset * no,int nci_hint)689 static NibOffset *_CalcNibOffset(NibCorner *nc, int n, BasePoint ut,
690                                  int reverse, NibOffset *no, int nci_hint) {
691     int nci, ncni, ncpi;
692     Spline *ns;
693 
694     if ( no==NULL )
695 	no = malloc(sizeof(NibOffset));
696 
697     memset(no,0,sizeof(NibOffset));
698     no->utanvec = ut; // Store the unreversed value for reference
699 
700     if ( reverse ) {
701 	ut = BPRev(ut);
702 	no->reversed = 1;
703     }
704 
705     nci = no->nci[0] = no->nci[1] = _IndexForUTanVec(nc, n, ut, nci_hint);
706     ncpi = N_PREVI(n, nci);
707     ncni = N_NEXTI(n, nci);
708 
709     // The two cases where one of two points might draw the same angle
710     // and therefore the only cases where the array values differ
711     if (   nc[nci].linear
712 	// "Capsule" case
713         && BPNear(ut, nc[ncni].utv[NC_IN_IDX])
714         && BPNear(ut, nc[nci].utv[NC_IN_IDX]) ) {
715 	no->nt = 0.0;
716 	no->off[NIBOFF_CCW_IDX] = nc[nci].on_nib->me;
717 	no->off[NIBOFF_CW_IDX] = nc[ncni].on_nib->me;
718 	no->nci[NIBOFF_CW_IDX] = ncni;
719 	no->at_line = true;
720 	no->curve = false;
721     } else if ( nc[ncpi].linear && BPNear(ut, nc[ncpi].utv[NC_OUT_IDX]) ) {
722 	// Other lines
723 	no->nt = 0.0;
724 	no->off[NIBOFF_CCW_IDX] = nc[ncpi].on_nib->me;
725 	no->nci[NIBOFF_CCW_IDX] = ncpi;
726 	no->off[NIBOFF_CW_IDX] = nc[nci].on_nib->me;
727 	no->at_line = true;
728 	no->curve = false;
729     // When ut is (effectively) between IN and OUT the point
730     // draws the offset curve
731     } else if (   UTanVecsSequent(nc[nci].utv[NC_IN_IDX], ut,
732                                   nc[nci].utv[NC_OUT_IDX], false)
733                || BPNear(ut, nc[nci].utv[NC_OUT_IDX] ) ) {
734 	no->nt = 0.0;
735 	no->off[0] = no->off[1] = nc[nci].on_nib->me;
736 	no->curve = false;
737     // Otherwise ut is on a spline curve clockwise from point nci
738     } else {
739 	assert( UTanVecsSequent(nc[nci].utv[NC_OUT_IDX], ut,
740 	                        nc[ncni].utv[NC_IN_IDX], false) );
741 	// Nib splines are locally convex and therefore have t value per slope
742 	ns = nc[nci].on_nib->next;
743 	no->nt = SplineSolveForUTanVec(ns, ut, 0.0, false);
744 	if ( no->nt<0 ) {
745 	    // At more extreme nib control point angles the solver may fail.
746 	    // In such cases the tangent angle should be near one of the
747 	    // endpoints, so pick the closer one
748 	    if (   BPLenSq(BPSub(nc[nci].utv[NC_OUT_IDX], ut))
749 	         < BPLenSq(BPSub(nc[ncni].utv[NC_IN_IDX], ut)) )
750 		no->nt = 0;
751 	    else
752 		no->nt = 1;
753 	}
754 	no->off[0] = no->off[1] = SPLINEPVAL(ns, no->nt);
755 	no->curve = true;
756     }
757     return no;
758 }
759 
CalcNibOffset(StrokeContext * c,BasePoint ut,int reverse,NibOffset * no,int nci_hint)760 static NibOffset *CalcNibOffset(StrokeContext *c, BasePoint ut, int reverse,
761                                 NibOffset *no, int nci_hint) {
762     return _CalcNibOffset(c->nibcorners, c->n, ut, reverse, no, nci_hint);
763 }
764 
SplineStrokeNextAngle(StrokeContext * c,BasePoint ut,int is_ccw,int * curved,int reverse,int nci_hint)765 static BasePoint SplineStrokeNextAngle(StrokeContext *c, BasePoint ut,
766                                        int is_ccw, int *curved, int reverse,
767 				       int nci_hint) {
768     int nci, ncni, ncpi, inout;
769 
770     ut = BPRevIf(reverse, ut);
771     nci = IndexForUTanVec(c, ut, nci_hint);
772     ncni = NC_NEXTI(c, nci);
773     ncpi = NC_PREVI(c, nci);
774 
775     if ( BPNear(ut, c->nibcorners[nci].utv[NC_IN_IDX]) ) {
776 	if ( is_ccw ) {
777 	    if ( BPNear(c->nibcorners[nci].utv[NC_IN_IDX],
778 	                c->nibcorners[ncpi].utv[NC_OUT_IDX]) ) {
779 		if ( BPNear(c->nibcorners[nci].utv[NC_IN_IDX],
780 		            c->nibcorners[ncpi].utv[NC_IN_IDX]) ) {
781 		    assert(c->nibcorners[ncpi].linear);
782 		    *curved = true;
783 		    inout = NC_OUT_IDX;
784 		    nci = NC_PREVI(c, ncpi);
785 		} else {
786 		    assert(c->nibcorners[ncpi].linear);
787 		    inout = NC_IN_IDX;
788 		    *curved = false;
789 		    nci = ncpi;
790 		}
791 	    } else {
792 		inout = NC_OUT_IDX;
793 		*curved = true;
794 		nci = ncpi;
795 	    }
796 	} else { // CW
797 	    if ( BPNear(c->nibcorners[nci].utv[NC_IN_IDX],
798 	                c->nibcorners[nci].utv[NC_OUT_IDX]) ) {
799 		if ( BPNear(c->nibcorners[nci].utv[NC_IN_IDX],
800 		            c->nibcorners[ncni].utv[NC_IN_IDX]) ) {
801 		    assert(c->nibcorners[nci].linear);
802 		    inout = NC_OUT_IDX;
803 		    *curved = true;
804 		} else {
805 		    inout = NC_IN_IDX;
806 		    *curved = !c->nibcorners[nci].linear;
807 		}
808 		nci = ncni;
809 	    } else {
810 		inout = NC_OUT_IDX;
811 		*curved = false;
812 	    }
813 	}
814     } else if ( BPNear(ut, c->nibcorners[nci].utv[NC_OUT_IDX]) ) {
815 	assert( ! BPNear(c->nibcorners[nci].utv[NC_IN_IDX],
816 	                 c->nibcorners[nci].utv[NC_OUT_IDX]) );
817 	if ( is_ccw ) {
818 	    inout = NC_IN_IDX;
819 	    *curved = false;
820 	} else { // CW
821 	    if ( BPNear(c->nibcorners[nci].utv[NC_OUT_IDX],
822 	                c->nibcorners[ncni].utv[NC_IN_IDX]) ) {
823 		assert(c->nibcorners[nci].linear);
824 		inout = NC_OUT_IDX;
825 		*curved = !c->nibcorners[ncni].linear;
826 	    } else {
827 		inout = NC_IN_IDX;
828 		*curved = true;
829 	    }
830 	    nci = ncni;
831 	}
832     } else if ( UTanVecsSequent(c->nibcorners[nci].utv[NC_IN_IDX], ut,
833                 c->nibcorners[nci].utv[NC_OUT_IDX], false) ) {
834 	if ( is_ccw )
835 	    inout = NC_IN_IDX;
836 	else
837 	    inout = NC_OUT_IDX;
838 	*curved = false;
839     } else {
840 	assert( UTanVecsSequent(c->nibcorners[nci].utv[NC_OUT_IDX], ut,
841 	        c->nibcorners[ncni].utv[NC_IN_IDX], false) );
842 	if ( is_ccw )
843 	    inout = NC_OUT_IDX;
844 	else {
845 	    nci = ncni;
846 	    inout = NC_IN_IDX;
847 	}
848 	*curved = true;
849     }
850     return BPRevIf(reverse, c->nibcorners[nci].utv[inout]);
851 }
852 
853 /******************************************************************************/
854 /* *************************** Spline Manipulation ************************** */
855 /******************************************************************************/
856 
857 /* This is a convenience function that also concisely demonstrates
858  * the calculation of an offseted point.
859  */
SplineOffsetAt(StrokeContext * c,Spline * s,bigreal t,int is_right)860 BasePoint SplineOffsetAt(StrokeContext *c, Spline *s, bigreal t, int is_right) {
861     int is_ccw;
862     BasePoint xy, ut;
863     NibOffset no;
864 
865     // The coordinate of the spline at t
866     xy = SPLINEPVAL(s, t);
867     // The turning direction of the spline at t
868     is_ccw = SplineTurningCCWAt(s, t);
869     // The tangent angle of the spline at t
870     ut = SplineUTanVecAt(s, t);
871 
872     // The offsets of the nib tracing at that angle (where is_right
873     // reverses the angle and therefore the side of the nib)
874     CalcNibOffset(c, ut, is_right, &no, -1);
875 
876     // (The spline coordinate at t) + (the offset). If the angle is on a
877     // nib line, use the turning direction to pick the corner that will be
878     // continuous with the next points to be drawn based on the turning
879     // direction (and therefore the next angle).
880     return BPAdd(xy, no.off[is_ccw]);
881 }
882 
883 /* Copies the portion of s from t_fm to t_to and then translates
884  * and appends it to tailp. The new end point is returned. "Reversing"
885  * t_fm and t_to reverses the copy's direction.
886  *
887  * Direct calculations cribbed from https://stackoverflow.com/a/879213
888  */
AppendCubicSplinePortion(Spline * s,bigreal t_fm,bigreal t_to,SplinePoint * tailp)889 SplinePoint *AppendCubicSplinePortion(Spline *s, bigreal t_fm, bigreal t_to,
890                                       SplinePoint *tailp) {
891     extended u_fm = 1-t_fm, u_to = 1-t_to;
892     SplinePoint *sp;
893     BasePoint v, qf, qcf, qct, qt;
894 
895     // XXX maybe this should be length based
896     if ( RealWithin(t_fm, t_to, 1e-4) )
897 	return tailp;
898 
899     // Intermediate calculations
900     qf.x =    s->from->me.x*u_fm*u_fm
901             + s->from->nextcp.x*2*t_fm*u_fm
902             + s->to->prevcp.x*t_fm*t_fm;
903     qcf.x =   s->from->me.x*u_to*u_to
904             + s->from->nextcp.x*2*t_to*u_to
905             + s->to->prevcp.x*t_to*t_to;
906     qct.x =   s->from->nextcp.x*u_fm*u_fm
907             + s->to->prevcp.x*2*t_fm*u_fm
908             + s->to->me.x*t_fm*t_fm;
909     qt.x =    s->from->nextcp.x*u_to*u_to
910             + s->to->prevcp.x*2*t_to*u_to
911             + s->to->me.x*t_to*t_to;
912 
913     qf.y =    s->from->me.y*u_fm*u_fm
914 	    + s->from->nextcp.y*2*t_fm*u_fm
915             + s->to->prevcp.y*t_fm*t_fm;
916     qcf.y =   s->from->me.y*u_to*u_to
917             + s->from->nextcp.y*2*t_to*u_to
918             + s->to->prevcp.y*t_to*t_to;
919     qct.y =   s->from->nextcp.y*u_fm*u_fm
920             + s->to->prevcp.y*2*t_fm*u_fm
921             + s->to->me.y*t_fm*t_fm;
922     qt.y =    s->from->nextcp.y*u_to*u_to
923             + s->to->prevcp.y*2*t_to*u_to
924             + s->to->me.y*t_to*t_to;
925 
926     // Difference vector to offset other points
927     v.x = tailp->me.x - (qf.x*u_fm + qct.x*t_fm);
928     v.y = tailp->me.y - (qf.y*u_fm + qct.y*t_fm);
929 
930     sp = SplinePointCreate(qcf.x*u_to + qt.x*t_to + v.x,
931                             qcf.y*u_to + qt.y*t_to + v.y);
932 
933     tailp->nonextcp = false; sp->noprevcp = false;
934     tailp->nextcp.x = qf.x*u_to + qct.x*t_to + v.x;
935     tailp->nextcp.y = qf.y*u_to + qct.y*t_to + v.y;
936     sp->prevcp.x = qcf.x*u_fm + qt.x*t_fm + v.x;
937     sp->prevcp.y = qcf.y*u_fm + qt.y*t_fm + v.y;
938 
939     SplineMake3(tailp,sp);
940 
941     if ( SplineIsLinear(tailp->next)) { // Linearish instead?
942         tailp->nextcp = tailp->me;
943         sp->prevcp = sp->me;
944         tailp->nonextcp = sp->noprevcp = true;
945         SplineRefigure(tailp->next);
946     }
947     return sp;
948 }
949 
950 /* Copies the splines between spline_fm (at t_fm) and spline_to (at t_to)
951  * after point tailp. "backward" (and therefore forward) is relative
952  * to next/prev rather than clockwise/counter-clockwise.
953  */
AppendCubicSplineSetPortion(Spline * spline_fm,bigreal t_fm,Spline * spline_to,bigreal t_to,SplinePoint * tailp,int backward)954 SplinePoint *AppendCubicSplineSetPortion(Spline *spline_fm, bigreal t_fm,
955                                          Spline *spline_to, bigreal t_to,
956 					 SplinePoint *tailp, int backward) {
957     Spline *s;
958 
959     if (    backward && RealWithin(t_fm, 0.0, 1e-4)
960          && spline_fm!=spline_to ) {
961 	t_fm = 1;
962 	spline_fm = spline_fm->from->prev;
963     } else if (    !backward && RealWithin(t_fm, 1.0, 1e-4)
964                 && spline_fm != spline_to ) {
965 	t_fm = 0;
966 	spline_fm = spline_fm->to->next;
967     }
968     if (    backward && RealWithin(t_to, 1.0, 1e-4)
969          && spline_fm != spline_to ) {
970 	t_to = 0.0;
971 	spline_to = spline_to->to->next;
972     } else if (    !backward && RealWithin(t_to, 0.0, 1e-4)
973                 && spline_fm != spline_to ) {
974 	t_to = 1.0;
975 	spline_to = spline_to->from->prev;
976     }
977     s = spline_fm;
978 
979     // Handle the single spline case
980     if (    s==spline_to
981          && (( t_fm<=t_to && !backward ) || (t_fm>=t_to && backward))) {
982 	tailp = AppendCubicSplinePortion(s, t_fm, t_to, tailp);
983 	return tailp;
984     }
985 
986     tailp = AppendCubicSplinePortion(s, t_fm, backward ? 0 : 1, tailp);
987 
988     while ( 1 ) {
989         s = backward ? s->from->prev : s->to->next;
990 	if ( s==spline_to )
991 	    break;
992 	assert( s!=NULL && s!=spline_fm ); // XXX turn into runtime warning?
993         tailp = AppendCubicSplinePortion(s, backward ? 1 : 0,
994 	                                  backward ? 0 : 1, tailp);
995     }
996     tailp = AppendCubicSplinePortion(s, backward ? 1 : 0, t_to, tailp);
997     return tailp;
998 }
999 
1000 /******************************************************************************/
1001 /* *********************** Tracing, Caps and Joins ************************** */
1002 /******************************************************************************/
1003 
AddNibPortion(NibCorner * nc,SplinePoint * tailp,NibOffset * no_fm,int is_ccw_fm,NibOffset * no_to,int is_ccw_to,int bk)1004 static SplinePoint *AddNibPortion(NibCorner *nc, SplinePoint *tailp,
1005                                   NibOffset *no_fm, int is_ccw_fm,
1006 				  NibOffset *no_to, int is_ccw_to, int bk) {
1007     SplinePoint *sp, *nibp_fm, *nibp_to;
1008 
1009     nibp_fm = nc[no_fm->nci[is_ccw_fm]].on_nib;
1010     nibp_to = nc[no_to->nci[is_ccw_to]].on_nib;
1011     sp = AppendCubicSplineSetPortion(nibp_fm->next, no_fm->nt, nibp_to->next,
1012                                      no_to->nt, tailp, bk);
1013     return sp;
1014 }
1015 
SSAppendArc(SplineSet * cur,bigreal major,bigreal minor,BasePoint ang,BasePoint ut_fm,BasePoint ut_to,int bk,int limit)1016 void SSAppendArc(SplineSet *cur, bigreal major, bigreal minor,
1017                  BasePoint ang, BasePoint ut_fm, BasePoint ut_to,
1018                  int bk, int limit) {
1019     SplineSet *ellip;
1020     real trans[6];
1021     SplinePoint *sp;
1022     NibOffset no_fm, no_to;
1023     NibCorner *nc = NULL;
1024     int n, mn=4;
1025 
1026     if ( ang.y==0 )
1027 	ang.x = 1;
1028     if ( minor==0 )
1029 	minor = major;
1030 
1031     ellip = UnitShape(0);
1032     trans[0] = ang.x * major;
1033     trans[1] = ang.y * major;
1034     trans[2] = -ang.y * minor;
1035     trans[3] = ang.x * minor;
1036     trans[4] = trans[5] = 0;
1037     SplinePointListTransformExtended(ellip, trans, tpt_AllPoints,
1038 	                             tpmask_dontTrimValues);
1039     BuildNibCorners(&nc, ellip, &mn, &n);
1040     assert( n==4 && nc!=NULL );
1041 
1042     _CalcNibOffset(nc, n, ut_fm, false, &no_fm, -1);
1043     _CalcNibOffset(nc, n, ut_to, false, &no_to, -1);
1044     if ( limit ) {
1045 	if (    !bk && no_fm.nci[0] == no_to.nci[0]
1046 	     && no_fm.nt > no_to.nt )
1047 	    bk = true;
1048 	else if ( !bk && no_fm.nci[0] == (no_to.nci[0]+1)%4 )
1049 	    bk = true;
1050 	else if (    bk && no_fm.nci[0] == no_to.nci[0]
1051 	     && no_fm.nt < no_to.nt )
1052 	    bk = false;
1053 	else if ( bk && (no_fm.nci[0]+1)%4 == no_to.nci[0] )
1054 	    bk = false;
1055     }
1056     sp = AddNibPortion(nc, cur->last, &no_fm, false, &no_to, false, bk);
1057     cur->last = sp;
1058     SplinePointListFree(ellip);
1059     free(nc);
1060 }
1061 
SplineStrokeSimpleFixup(SplinePoint * tailp,BasePoint p)1062 void SplineStrokeSimpleFixup(SplinePoint *tailp, BasePoint p) {
1063     BasePoint dxy = BPSub(p, tailp->me);
1064     tailp->prevcp = BPAdd(tailp->prevcp, dxy);
1065     tailp->me = p;
1066     SplineRefigure(tailp->prev);
1067 }
1068 
SplineStrokeVerifyCorner(BasePoint sxy,BasePoint txy,NibOffset * no,int is_ccw,BasePoint * dxyp,bigreal * mgp)1069 static BasePoint SplineStrokeVerifyCorner(BasePoint sxy, BasePoint txy,
1070                                           NibOffset *no, int is_ccw,
1071 				          BasePoint *dxyp, bigreal *mgp) {
1072     BasePoint oxy;
1073 
1074     oxy = BPAdd(sxy, no->off[is_ccw]);
1075     *dxyp = BPSub(oxy, txy);
1076     *mgp = fmax(fabs(dxyp->x), fabs(dxyp->y));
1077     return oxy;
1078 }
1079 
SplineStrokeFindCorner(BasePoint sxy,BasePoint txy,NibOffset * no)1080 static int SplineStrokeFindCorner(BasePoint sxy, BasePoint txy, NibOffset *no) {
1081     bigreal mg, mg2;
1082     BasePoint tmp;
1083 
1084     SplineStrokeVerifyCorner(sxy, txy, no, 0, &tmp, &mg);
1085     SplineStrokeVerifyCorner(sxy, txy, no, 1, &tmp, &mg2);
1086     return mg > mg2 ? 1 : 0;
1087 }
1088 
1089 /* Put the new endpoint exactly where the NibOffset calculation says it
1090  * should be to avoid cumulative append errors.
1091  */
SplineStrokeAppendFixup(SplinePoint * tailp,BasePoint sxy,NibOffset * no,int is_ccw,bigreal fudge)1092 static int SplineStrokeAppendFixup(SplinePoint *tailp, BasePoint sxy,
1093                                    NibOffset *no, int is_ccw, bigreal fudge) {
1094     bigreal mg, mg2;
1095     BasePoint oxy, oxy2, dxy, dxy2;
1096 
1097     if ( is_ccw==-1 ) {
1098 	oxy  = SplineStrokeVerifyCorner(sxy, tailp->me, no, 0, &dxy, &mg);
1099 	oxy2 = SplineStrokeVerifyCorner(sxy, tailp->me, no, 1, &dxy2, &mg2);
1100 	if ( mg2<mg ) {
1101 	    mg = mg2;
1102 	    oxy = oxy2;
1103 	    dxy = dxy2;
1104 	    is_ccw = 1;
1105 	} else
1106 	    is_ccw = 0;
1107     } else
1108 	oxy = SplineStrokeVerifyCorner(sxy, tailp->me, no, is_ccw, &dxy, &mg);
1109 
1110     // assert( mg < 1 );
1111     if ( mg > FIXUP_MARGIN*fudge ) {
1112 	LogError(_("Warning: Coordinate diff %lf greater than margin %lf\n"),
1113 	         mg, FIXUP_MARGIN*fudge);
1114     }
1115 
1116     tailp->prevcp = BPAdd(tailp->prevcp, dxy);
1117     tailp->me = oxy;
1118     SplineRefigure(tailp->prev);
1119     return is_ccw;
1120 }
1121 
1122 /* Note that is_ccw relates to the relevant NibOffset entries, and not
1123  * necessarily the direction of the curve at t.
1124  */
OffsetOnCuspAt(StrokeContext * c,Spline * s,bigreal t,NibOffset * nop,int is_right,int is_ccw)1125 static int OffsetOnCuspAt(StrokeContext *c, Spline *s, bigreal t,
1126                           NibOffset *nop, int is_right, int is_ccw)
1127 {
1128     bigreal cs, cn;
1129     NibOffset no;
1130 
1131     cs = SplineCurvature(s, t);
1132 
1133     // Cusps aren't a problem when increasing the radius of the curve
1134     if ( (cs>0)==is_right )
1135 	return false;
1136 
1137     if ( nop==NULL ) {
1138 	nop = &no;
1139 	CalcNibOffset(c, SplineUTanVecAt(s, t), is_right, nop, -1);
1140     }
1141     cn = SplineCurvature(c->nibcorners[nop->nci[is_ccw]].on_nib->next, nop->nt);
1142 
1143     return RealWithin(cn, 0, 1e-6) ? false : is_right ? (cn >= cs) : (cn >= -cs);
1144 }
1145 
1146 /* I didn't find or derive a closed form of this calculation, so this just
1147  * does a binary search within the range, which is assumed (here) to contain
1148  * a single transition.
1149  */
SplineFindCuspSing(StrokeContext * c,Spline * s,bigreal t_fm,bigreal t_to,int is_right,int is_ccw,bigreal margin,int on_cusp)1150 static bigreal SplineFindCuspSing(StrokeContext *c, Spline *s, bigreal t_fm,
1151                                   bigreal t_to, int is_right, int is_ccw,
1152 				  bigreal margin, int on_cusp)
1153 {
1154     bigreal t_mid;
1155     int cusp_mid;
1156     assert( t_fm >= 0.0 && t_fm <= 1.0 );
1157     assert( t_to >= 0.0 && t_to <= 1.0 );
1158     assert( t_fm < t_to );
1159     assert( OffsetOnCuspAt(c, s, t_fm, NULL, is_right, is_ccw) == on_cusp );
1160     assert( OffsetOnCuspAt(c, s, t_to, NULL, is_right, is_ccw) != on_cusp );
1161     while ( t_fm-t_to > margin ) {
1162 	t_mid = (t_fm+t_to)/2;
1163 	cusp_mid = OffsetOnCuspAt(c, s, t_mid, NULL, is_right, is_ccw);
1164 	if ( cusp_mid==on_cusp )
1165 	    t_fm = t_mid;
1166 	else
1167 	    t_to = t_mid;
1168     }
1169     return t_to;
1170 }
1171 
1172 typedef struct stroketraceinfo {
1173     StrokeContext *c;
1174     Spline *s;
1175     bigreal cusp_trans;
1176     int nci_hint;
1177     int num_points;
1178     unsigned int is_right: 1;
1179     unsigned int starts_on_cusp: 1;
1180     unsigned int first_pass: 1;
1181     unsigned int found_trans: 1;
1182 } StrokeTraceInfo;
1183 
GenStrokeTracePoints(void * vinfo,bigreal t_fm,bigreal t_to,FitPoint ** fpp)1184 int GenStrokeTracePoints(void *vinfo, bigreal t_fm, bigreal t_to,
1185                          FitPoint **fpp) {
1186     StrokeTraceInfo *stip = (StrokeTraceInfo *)vinfo;
1187     int i, nib_ccw, on_cusp;
1188     NibOffset no;
1189     FitPoint *fp;
1190     bigreal nidiff, t;
1191     BasePoint xy;
1192 
1193     *fpp = NULL;
1194     fp = calloc(stip->num_points, sizeof(FitPoint));
1195     nidiff = (t_to - t_fm) / (stip->num_points-1);
1196 
1197     nib_ccw = SplineTurningCCWAt(stip->s, t_fm);
1198     no.nci[0] = no.nci[1] = stip->nci_hint;
1199     for ( i=0, t=t_fm; i<stip->num_points; ++i, t+=nidiff ) {
1200 	if ( i==(stip->num_points-1) ) {
1201 	    nib_ccw = !nib_ccw; // Stop at the closer corner
1202 	    t = t_to; // side-step nidiff rounding errors
1203 	}
1204 	xy = SPLINEPVAL(stip->s, t);
1205 	fp[i].ut = SplineUTanVecAt(stip->s, t);
1206 	CalcNibOffset(stip->c, fp[i].ut, stip->is_right, &no, no.nci[nib_ccw]);
1207 	fp[i].p = BPAdd(xy, no.off[nib_ccw]);
1208 	fp[i].t = t;
1209 	if ( stip->first_pass ) {
1210 	    on_cusp = OffsetOnCuspAt(stip->c, stip->s, t, &no,
1211 	                             stip->is_right, nib_ccw);
1212 	    if ( on_cusp!=stip->starts_on_cusp ) {
1213 		stip->found_trans = true;
1214 		stip->cusp_trans = SplineFindCuspSing(stip->c, stip->s,
1215 		                                      t-nidiff, t,
1216 		                                      stip->is_right,
1217 		                                      nib_ccw, CUSPD_MARGIN,
1218 						      stip->starts_on_cusp);
1219 		free(fp);
1220 		return 0;
1221 	    }
1222 	} else {
1223 #ifndef NDEBUG
1224 	; // Could add consistency asserts for shorter passes here
1225 #else
1226 	;
1227 #endif // NDEBUG
1228 	}
1229 	if ( stip->starts_on_cusp ) // Cusp tangent point the other way
1230 	    fp[i].ut = BPRev(fp[i].ut);
1231     }
1232     *fpp = fp;
1233     stip->first_pass = false;
1234     return stip->num_points;
1235 }
1236 
1237 #define TRACE_CUSPS false
TraceAndFitSpline(StrokeContext * c,Spline * s,bigreal t_fm,bigreal t_to,SplinePoint * tailp,int nci_hint,int is_right,int on_cusp)1238 SplinePoint *TraceAndFitSpline(StrokeContext *c, Spline *s, bigreal t_fm,
1239                                bigreal t_to, SplinePoint *tailp,
1240 			       int nci_hint, int is_right, int on_cusp) {
1241     SplinePoint *sp = NULL;
1242     StrokeTraceInfo sti;
1243     FitPoint *fpp;
1244     BasePoint xy;
1245 
1246     sti.c = c;
1247     sti.s = s;
1248     sti.nci_hint = nci_hint;
1249     sti.num_points = 10;
1250     sti.is_right = is_right;
1251     sti.first_pass = true;
1252     sti.starts_on_cusp = on_cusp;
1253     sti.found_trans = false;
1254 
1255     if ( on_cusp && !TRACE_CUSPS ) {
1256 	GenStrokeTracePoints((void *)&sti, t_fm, t_to, &fpp);
1257 	free(fpp);
1258     } else
1259 	sp = ApproximateSplineSetFromGen(tailp, NULL, t_fm, t_to, c->acctarget,
1260 	                                 false, &GenStrokeTracePoints,
1261 	                                 (void *) &sti, false);
1262     if ( !sti.found_trans ) {
1263 	if ( sp==NULL ) {
1264 	    assert( on_cusp && !TRACE_CUSPS );
1265 	    xy = SplineOffsetAt(c, s, t_to, is_right);
1266 	    sp = SplinePointCreate(xy.x, xy.y);
1267 	    SplineMake3(tailp, sp);
1268 	}
1269    	return sp;
1270     }
1271 
1272     assert ( sti.found_trans && sp==NULL );
1273     assert ( sti.cusp_trans >= t_fm && sti.cusp_trans <= t_to );
1274     if ( on_cusp && !TRACE_CUSPS ) {
1275 	xy = SplineOffsetAt(c, s, sti.cusp_trans, is_right);
1276 	sp = SplinePointCreate(xy.x, xy.y);
1277 	SplineMake3(tailp, sp);
1278     } else {
1279 	sti.first_pass = false;
1280 	sp = ApproximateSplineSetFromGen(tailp, NULL, t_fm, sti.cusp_trans,
1281 	                                 c->acctarget, false,
1282 	                                 &GenStrokeTracePoints, (void *) &sti,
1283 	                                 false);
1284     }
1285     assert( sp!=NULL );
1286     sp->pointtype = pt_corner;
1287     on_cusp = !on_cusp;
1288     if ( RealWithin(sti.cusp_trans, t_to, CUSPD_MARGIN) )
1289 	return sp;
1290     return TraceAndFitSpline(c, s, sti.cusp_trans, t_to, sp, nci_hint,
1291                              is_right, on_cusp);
1292 }
1293 #undef TRACE_CUSPS
1294 
SplineStrokeNextT(StrokeContext * c,Spline * s,bigreal cur_t,int is_ccw,BasePoint * cur_ut,int * curved,int reverse,int nci_hint)1295 static bigreal SplineStrokeNextT(StrokeContext *c, Spline *s, bigreal cur_t,
1296 		                 int is_ccw, BasePoint *cur_ut,
1297 				 int *curved, int reverse, int nci_hint) {
1298     int next_curved, icnt, i;
1299     bigreal next_t;
1300     extended poi[2];
1301     BasePoint next_ut;
1302 
1303     assert( cur_ut!=NULL && curved!=NULL );
1304 
1305     next_ut = SplineStrokeNextAngle(c, *cur_ut, is_ccw, &next_curved,
1306                                     reverse, nci_hint);
1307     next_t = SplineSolveForUTanVec(s, next_ut, cur_t, false);
1308 
1309     // If there is an inflection point before next_t the spline will start
1310     // curving in the opposite direction, so stop and trace the next section
1311     // separately. An alternative would be find the next stop angle in the
1312     // other direction by returning:
1313     //
1314     // return SplineStrokeNextT(c, s, poi[i], !is_ccw, cur_ut, curved,
1315     //                          reverse, nci_hint);
1316     //
1317     // This would have one main advantage, which is that the offset inflection
1318     // points will not exactly correspond to the position on s. Therefore
1319     // stopping at the s inflection leads to an offset point frustratingly close
1320     // to but not at the inflection point. The disadvantage is that we would
1321     // need to track how many times the direction changes to feed the right
1322     // value to SplineStrokeAppendFixup, leading to more code complexity.
1323     if ( (icnt = Spline2DFindPointsOfInflection(s, poi)) ) {
1324 	assert ( icnt < 2 || poi[0] <= poi[1] );
1325 	for ( i=0; i<2; ++i )
1326 	    if (    poi[i] > cur_t
1327 	         && !RealNear(poi[i], cur_t)
1328 	         && (next_t==-1 || poi[i] < next_t) ) {
1329 		next_t = poi[i];
1330 		next_ut = SplineUTanVecAt(s, next_t);
1331 		break;
1332 	    }
1333     }
1334     if ( RealWithin(next_t, 1.0, 1e-4) ) // XXX distance-based?
1335 	next_t = 1.0;
1336 
1337     if ( next_t==-1 ) {
1338 	next_t = 1.0;
1339 	*cur_ut = SplineUTanVecAt(s, next_t);
1340     } else
1341 	*cur_ut = next_ut;
1342 
1343     *curved = next_curved;
1344     return next_t;
1345 }
1346 
HandleFlat(SplineSet * cur,BasePoint sxy,NibOffset * noi,int is_ccw)1347 static void HandleFlat(SplineSet *cur, BasePoint sxy, NibOffset *noi,
1348                        int is_ccw) {
1349     BasePoint oxy;
1350 
1351     oxy = BPAdd(sxy, noi->off[is_ccw]);
1352     if ( !BPNear(cur->last->me, oxy) ) {
1353 	assert( BPNear(cur->last->me, (BPAdd(sxy, noi->off[!is_ccw]))) );
1354 	SplineSetLineTo(cur, oxy);
1355     }
1356 }
1357 
FalseStrokeWidth(StrokeContext * c,BasePoint ut)1358 static bigreal FalseStrokeWidth(StrokeContext *c, BasePoint ut) {
1359     SplineSet *tss;
1360     DBounds b;
1361     real trans[6];
1362 
1363     assert( RealNear(BPLenSq(ut),1) );
1364     // Rotate nib and grab height as span
1365     trans[0] = trans[3] = ut.x;
1366     trans[1] = ut.y;
1367     trans[2] = -ut.y;
1368     trans[4] = trans[5] = 0;
1369     tss = SplinePointListCopy(c->nib);
1370     SplinePointListTransformExtended(tss, trans, tpt_AllPoints,
1371 	                             tpmask_dontTrimValues);
1372     SplineSetFindBounds(tss,&b);
1373     SplinePointListFree(tss);
1374     return b.maxy - b.miny;
1375 }
1376 
CalcJoinLength(bigreal fsw,BasePoint ut1,BasePoint ut2)1377 static bigreal CalcJoinLength(bigreal fsw, BasePoint ut1, BasePoint ut2) {
1378     bigreal costheta = BPDot(ut1, BPRev(ut2));
1379 
1380     // Formula is fsw/sin(theta/2) but sin(theta/2) = sqrt((1-costheta)/2)
1381     return fsw / sqrt((1 - costheta)/2);
1382 }
1383 
CalcJoinLimit(StrokeContext * c,bigreal fsw)1384 static bigreal CalcJoinLimit(StrokeContext *c, bigreal fsw) {
1385     if (c->joinlimit <= 0)
1386 	return DBL_MAX;
1387 
1388     if (!c->jlrelative)
1389 	return c->joinlimit;
1390 
1391     return c->joinlimit * fsw;
1392 }
1393 
CalcCapExtend(StrokeContext * c,bigreal fsw)1394 static bigreal CalcCapExtend(StrokeContext *c, bigreal fsw) {
1395     if (c->extendcap <= 0)
1396 	return 0;
1397 
1398     if (!c->ecrelative)
1399 	return c->extendcap;
1400 
1401     return c->extendcap * fsw;
1402 }
1403 
LineDist(BasePoint l1,BasePoint l2,BasePoint p)1404 static bigreal LineDist(BasePoint l1, BasePoint l2, BasePoint p) {
1405     assert (l1.x!=l2.x || l1.y!=l2.y);
1406     return fabs((l2.y-l1.y)*p.x - (l2.x-l1.x)*p.y + l2.x*l1.y -l2.y*l1.x)
1407            / BPDist(l1, l2);
1408 }
1409 
SplineEndCurvature(Spline * s,int at_end)1410 static bigreal SplineEndCurvature(Spline *s, int at_end) {
1411     BasePoint v1, v2;
1412     bigreal l1_3, crs, tmp;
1413 
1414     if (SplineIsLinearish(s))
1415 	return 0;
1416 
1417     if (at_end) {
1418 	v1 = BPSub(s->to->me, s->to->prevcp);
1419 	v2 = BPSub(s->from->nextcp, s->to->prevcp);
1420     } else {
1421 	v1 = BPSub(s->from->nextcp, s->from->me);
1422 	v2 = BPSub(s->to->prevcp, s->from->nextcp);
1423     }
1424 
1425     l1_3 = pow(BPLenSq(v1), 1.5);
1426     crs = BPCross(v1, v2);
1427 
1428     if ( RealWithin(l1_3, 0, 1e-15) )
1429 	return CURVATURE_ERROR;
1430 
1431     tmp = (2.0/3.0) * crs / l1_3;
1432 
1433     if ( RealWithin(tmp, 0, 1e-5) )
1434 	return 0;
1435     return tmp;
1436 }
1437 
AdjustLineCircle(BasePoint lp,BasePoint lut,BasePoint cp1,BasePoint cv1)1438 static bigreal AdjustLineCircle(BasePoint lp, BasePoint lut, BasePoint cp1,
1439                                 BasePoint cv1) {
1440     bigreal D, E, a, b, c, d, r;
1441 
1442     D = BPCross(lut, BPSub(lp, cp1));
1443     E = BPCross(cv1, lut);
1444     a = 1 - E*E;
1445     b = -2*D*E;
1446     c = -D*D;
1447     d = b*b - 4*a*c;
1448     if (fabs(d*a*a) < 1e-4)
1449 	d = 0;
1450     else
1451 	d = sqrt(d);
1452     r = (-b + d)/(2*a);
1453     if (r < 0)
1454 	r = (-b - d)/(2*a);
1455     return r;
1456 }
1457 
AdjustCircles(BasePoint cp1,BasePoint cv1,bigreal * r1,BasePoint cp2,BasePoint cv2,bigreal * r2,int ins)1458 static void AdjustCircles(BasePoint cp1, BasePoint cv1, bigreal *r1,
1459                           BasePoint cp2, BasePoint cv2, bigreal *r2, int ins) {
1460     bigreal fac = (ins ? -1.0 : 1.0), C, D, a, b, c, d, e;
1461     BasePoint A, B;
1462 
1463     A = BPAdd(BPSub(cp1, cp2),
1464                BPSub(BPScale(cv1, *r1), BPScale(cv2, *r2)));
1465     B = BPSub(BPScale(cv1, fac), cv2);
1466     C = *r1 + *r2*fac;
1467     D = 2*fac;
1468     a = BPLenSq(B) - D*D;
1469     b = 2*(BPDot(A, B) - C*D);
1470     c = BPLenSq(A) - C*C;
1471     d = b*b - 4*a*c;
1472     if (fabs(d*a*a) < 1e-4)
1473 	d = 0;
1474     else
1475 	d = sqrt(d);
1476     e = (-b + d)/(2*a);
1477     if (e < 0)
1478 	e = (-b - d)/(2*a);
1479     *r1 = *r1 + fac*e;
1480     *r2 = *r2 + e;
1481 }
1482 
SVGArcClip(BasePoint lp,BasePoint lut,BasePoint center,bigreal r,BasePoint p1,BasePoint p2,BasePoint pr)1483 BasePoint SVGArcClip(BasePoint lp, BasePoint lut, BasePoint center,
1484                      bigreal r, BasePoint p1, BasePoint p2, BasePoint pr) {
1485     BasePoint i, i2;
1486     int cnt = LineCircleTest(lp, lut, center, r);
1487     if (cnt > 0) {
1488 	IntersectLineCircle(lp, lut, center, r, &i, &i2);
1489 	i = CloserPoint(lp, i, i2);
1490 	if ( !LineSameSide(p1, p2, i, pr, false) )
1491 	    i = p1;
1492     } else {
1493 	i = p1;
1494     }
1495     return i;
1496 }
1497 
ArcClip(BasePoint center,bigreal r,int neg,BasePoint p,BasePoint i,bigreal clip_ratio)1498 BasePoint ArcClip(BasePoint center, bigreal r, int neg,
1499                   BasePoint p, BasePoint i, bigreal clip_ratio) {
1500     int s = neg ? -1 : 1;
1501     bigreal start_angle, end_angle, angle_diff;
1502     BasePoint v;
1503 
1504     start_angle = atan2(p.y-center.y, p.x-center.x);
1505     end_angle = atan2(i.y-center.y, i.x-center.x);
1506     angle_diff = NormAngle(s*(end_angle - start_angle));
1507     if ( angle_diff < 0 ) // Renormalize angle difference to 0->2*pi
1508 	angle_diff += 2*FF_PI;
1509     end_angle = NormAngle(start_angle + s * angle_diff * clip_ratio);
1510 
1511     v = (BasePoint) { cos(end_angle), sin(end_angle) };
1512     return BPAdd(center, BPScale(v, r));
1513 }
1514 
1515 /*static int IntersectLinesSlopes2(BasePoint *i, BasePoint lp1, BasePoint lut1,
1516                                  BasePoint lp2, BasePoint lut2) {
1517     bigreal cl1 = BPCross(lut1, lp1), cl2 = BPCross(lut2, lp2);
1518     bigreal cd = BPCross(lut1, lut2);
1519     if (fabs(cd) < 1e-12)
1520 	return 0;
1521     i->x = (cl1*lut2.x - cl2*lut1.x)/cd;
1522     i->y = (cl1*lut2.y - cl2*lut1.y)/cd;
1523     return 1;
1524 }
1525 
1526 static void CalcDualBasis(BasePoint v1, BasePoint v2, BasePoint *q1,
1527                           BasePoint *q2) {
1528     if ( v1.x==0 ) {
1529 	assert( v1.y!=0 && v2.x!=0 );
1530 	q2->y = 0;
1531 	q2->x = 1/v2.x;
1532 	q1->y = 1/v1.y;
1533 	q1->x = -v2.y / ( v1.y * v2.x );
1534     } else if ( v1.y==0 ) {
1535 	assert( v2.y!=0 );
1536 	q2->x = 0;
1537 	q2->y = 1/v2.y;
1538 	q1->x = 1/v1.x;
1539 	q1->y = -v2.x / ( v1.x * v2.y );
1540     } else if ( v2.x==0 ) {
1541 	assert( v2.y!=0 );
1542 	q1->y = 0;
1543 	q1->x = 1/v1.x;
1544 	q2->y = 1/v2.y;
1545 	q2->x = -v1.y / ( v2.y * v1.x );
1546     } else if ( v2.y==0 ) {
1547 	q1->x = 0;
1548 	q1->y = 1/v1.y;
1549 	q2->x = 1/v2.x;
1550 	q2->y = -v1.x / ( v2.x * v1.y );
1551     } else {
1552 	q1->y = 1 / (v1.y - (v1.x * v2.y / v2.x));
1553 	q2->y = 1 / (v2.y - (v2.x * v1.y / v1.x));
1554 	q1->x = -v2.y * q1->y / v2.x;
1555 	q2->x = -v1.y * q2->y / v1.x;
1556     }
1557     assert(    RealNear(BPDot(v1,*q1), 1) && RealNear(BPDot(v2,*q2), 1)
1558             && RealNear(BPDot(v1,*q2), 0) && RealNear(BPDot(v2,*q1), 0) );
1559 }
1560 */
1561 
1562 /* (When necessary) extends the splines drawn by an arbitrary nib at angle
1563  * ut (so either a cap or a 180 degree join) so have a butt geometry (for
1564  * a butt/square end or to add a semi-circle on to).
1565  */
CalcExtend(BasePoint refp,BasePoint ut,BasePoint op1,BasePoint op2,bigreal min,BasePoint * np1,BasePoint * np2)1566 static void CalcExtend(BasePoint refp, BasePoint ut, BasePoint op1,
1567                        BasePoint op2, bigreal min,
1568                        BasePoint *np1, BasePoint *np2) {
1569     bigreal extra=0, tmp;
1570     int intersects;
1571     BasePoint cop1 = BPAdd(op1, ut), cop2 = BPAdd(op2, ut);
1572     BasePoint clip1 = BPAdd(refp, BPScale(ut, min)), cliput = BP90CW(ut);
1573     BasePoint clip2 = BPAdd(clip1, cliput);
1574 
1575     if ( !LineSameSide(clip1, clip2, op1, refp, false) )
1576 	extra = LineDist(clip1, clip2, op1);
1577     if ( !LineSameSide(clip1, clip2, op2, refp, false) ) {
1578 	tmp = LineDist(clip1, clip2, op2);
1579 	if (tmp > extra)
1580 	    extra = tmp;
1581     }
1582     if (extra > 0) {
1583 	clip1 = BPAdd(refp, BPScale(ut, min+extra));
1584 	clip2 = BPAdd(clip1, cliput);
1585     }
1586     intersects = IntersectLines(np1, &op1, &cop1, &clip1, &clip2);
1587     VASSERT(intersects);
1588     intersects = IntersectLines(np2, &op2, &cop2, &clip1, &clip2);
1589     VASSERT(intersects);
1590 }
1591 
DoubleBackJC(StrokeContext * c,SplineSet * cur,BasePoint sxy,BasePoint oxy,BasePoint ut,int bk,int is_cap)1592 static void DoubleBackJC(StrokeContext *c, SplineSet *cur, BasePoint sxy,
1593                          BasePoint oxy, BasePoint ut, int bk, int is_cap) {
1594     bigreal fsw, ex;
1595     BasePoint p0, p1, p2, arcut;
1596 
1597     fsw = FalseStrokeWidth(c, BPNeg(ut));
1598     if ( is_cap )
1599         ex = CalcCapExtend(c, fsw);
1600     else if ( c->join==lj_miterclip )
1601 	ex = CalcJoinLimit(c, fsw);
1602     else
1603 	ex = 0;
1604 
1605     p0 = BPAdd(sxy, c->pseudo_origin);
1606     CalcExtend(p0, ut, cur->last->me, oxy, ex, &p1, &p2);
1607     if ( BPNear(cur->last->me, p1) )
1608 	cur->last->me = p1;
1609     else
1610 	SplineSetLineTo(cur, p1);
1611     if ( (is_cap && c->cap==lc_round) || (!is_cap && c->join==lj_round) ) {
1612 	arcut = BPRevIf(bk, ut);
1613 	SSAppendArc(cur, fsw/2, 0, UTZERO, arcut, BPRev(arcut), bk, false);
1614 	assert( BPWithin(cur->last->me, p2,
1615 	                 c->log_maxdim*INTERSPLINE_MARGIN*5) );
1616 	cur->last->me = p2;
1617     } else
1618 	SplineSetLineTo(cur, p2);
1619     if ( !BPNear(oxy, p2) )
1620 	SplineSetLineTo(cur, oxy);
1621 }
1622 
1623 typedef struct joinparams {
1624     StrokeContext *c;
1625     Spline *s;
1626     SplineSet *cur;
1627     BasePoint sxy, oxy, ut_fm;
1628     NibOffset  *no_to;
1629     int ccw_fm, ccw_to, is_right, bend_is_ccw;
1630 } JoinParams;
1631 
BevelJoin(JoinParams * jpp)1632 static void BevelJoin(JoinParams *jpp) {
1633     SplineSetLineTo(jpp->cur, jpp->oxy);
1634 }
1635 
NibJoin(JoinParams * jpp)1636 static void NibJoin(JoinParams *jpp) {
1637     NibOffset no_fm;
1638     SplinePoint *sp;
1639     int ccw_fm;
1640     bigreal lmd = jpp->c->log_maxdim;
1641 
1642     CalcNibOffset(jpp->c, jpp->ut_fm, jpp->is_right, &no_fm, -1);
1643     ccw_fm = SplineStrokeAppendFixup(jpp->cur->last, jpp->sxy, &no_fm, -1, lmd);
1644     sp = AddNibPortion(jpp->c->nibcorners, jpp->cur->last, &no_fm, ccw_fm,
1645                        jpp->no_to, jpp->ccw_to, !jpp->bend_is_ccw);
1646     SplineStrokeAppendFixup(sp, jpp->sxy, jpp->no_to, jpp->ccw_to, lmd);
1647     jpp->cur->last = sp;
1648 }
1649 
1650 static void MiterJoin(JoinParams *jpp);
1651 /* With non-circular nibs it would be more accurate to call this
1652  * "RoundishJoin". A join geometry is determined by the last
1653  * point of the previous offset spline, the first point of the next
1654  * offset spline, and the starting and ending tangent angles. This
1655  * function closes the join with the arc of the least eccentric ellipse
1656  * compatible with those points and angles, which is the most commonly
1657  * suggested solution.
1658  *
1659  * There are a number of stack-exchange-type proposed solutions to this
1660  * problem, some with pseudocode, but I had a hard time getting any of
1661  * them working. The problem became much simpler in light of two papers.
1662  *
1663  * One is "Least eccentric ellipses for geometric Hermite interpolation" by
1664  * Femiani, Chuang, and Razdan (Computer Aided Geometric Design 29, 2012,
1665  * pp. 141-9). The paper models the ellipse as a quadratic *rational* bezier
1666  * curve and calculates the "weight" corresponding to the least eccentric
1667  * ellipse.  The first part of the code below calculates x1 and y1 from
1668  * section 3.1 of the paper (by different means, and possibly with different
1669  * signs) and uses those to calculate w. Any potential problems should be
1670  * checked against the paper's method.
1671  *
1672  * Once the correct ellipse is identified it is easiest (in terms of code
1673  * reuse) to produce a contour of that shape and append the arc with
1674  * AddNibPortion, and this is what SSAppendArc does. In order to use it that
1675  * way one must convert the rational bezier curve parameters into major and
1676  * minor axis lengths and an angle. Happily the second paper "Characteristics
1677  * of conic segments in Bezier form" by Javier-Sanchez-Reyes (Proceedings
1678  * of the IMProVe 2011, pp. 231-4) makes this quite easy. That paper and
1679  * the implementation below use complex arithmetic to calculate the center,
1680  * foci, and axes.
1681  */
RoundJoin(JoinParams * jpp,int rv)1682 static void RoundJoin(JoinParams *jpp, int rv) {
1683     BasePoint p0, p1, p2, p1p, p12a, angle, h0, h2;
1684     bigreal p12d, x1, y1, w, major, minor;
1685     complex double b0, b1, b2, alpha, m, C, d, c, F1, F2;
1686     int intersects;
1687 
1688     p0 = jpp->cur->last->me;
1689     p2 = jpp->oxy;
1690     h0 = BPRevIf(jpp->is_right, jpp->ut_fm);
1691     h2 = BPRevIf(jpp->is_right, jpp->no_to->utanvec);
1692 
1693     if ( rv ) {
1694 	DoubleBackJC(jpp->c, jpp->cur, jpp->sxy, jpp->oxy, jpp->ut_fm,
1695 	             jpp->is_right, 0);
1696 	return;
1697     }
1698 
1699     // First paper
1700     intersects = IntersectLinesSlopes(&p1, &p0, &h0, &p2, &h2);
1701     VASSERT(intersects);
1702     p1p = ProjectPointOnLine(p1, p0, NormVec(BPSub(p2,p0)));
1703     p12a = BPAvg(p0, p2);
1704     p12d = BPDist(p0, p2);
1705     x1 = 2 * BPDist(p1p, p12a) / p12d;
1706     y1 = 2 * BPDist(p1p, p1) / p12d;
1707     w = 1 / sqrt( x1*x1 + y1*y1 + 1);
1708 
1709     // Second paper
1710     b0 = p0.x + p0.y * I;
1711     b1 = p1.x + p1.y * I;
1712     b2 = p2.x + p2.y * I;
1713     alpha = 1/(1 - w*w);
1714     m = (b0 + b2)/2;
1715     C = (1 - alpha)*b1 + alpha*m;
1716     d = ((1-alpha)*(b1*b1)) + alpha*(b0*b2);
1717     c = csqrt(C*C - d);
1718     F1 = C + c;
1719     F2 = C - c;
1720     major = (cabs(F1-b0) + cabs(F2-b0))/2;
1721     minor = major * major - pow(cabs(c), 2); // note change of order
1722     if ( RealWithin(minor, 0, 1e-8) || minor < 0 ) {
1723 	MiterJoin(jpp);
1724 	return;
1725     }
1726     minor = sqrt(minor);
1727     angle = MakeUTanVec(creal(c), cimag(c));
1728 
1729     SSAppendArc(jpp->cur, major, minor, angle, h0, h2, jpp->is_right, true);
1730 }
1731 
MiterJoin(JoinParams * jpp)1732 static void MiterJoin(JoinParams *jpp) {
1733     BasePoint ixy, refp, cow, coi, clip1, clip2, ut;
1734     int intersects;
1735     SplineSet *cur = jpp->cur;
1736     bigreal fsw, jlen, jlim;
1737 
1738     cow = BPAdd(cur->last->me, jpp->ut_fm);
1739     coi = BPAdd(jpp->oxy, jpp->no_to->utanvec);
1740     intersects = IntersectLines(&ixy, &cur->last->me, &cow, &coi, &jpp->oxy);
1741     assert(intersects); // Shouldn't be called with parallel tangents
1742     fsw = (  FalseStrokeWidth(jpp->c, BPNeg(jpp->ut_fm))
1743            + FalseStrokeWidth(jpp->c, BPNeg(jpp->no_to->utanvec)))/2;
1744     jlim = CalcJoinLimit(jpp->c, fsw);
1745     jlen = CalcJoinLength(fsw, jpp->ut_fm, jpp->no_to->utanvec);
1746 
1747     if ( jlen<=jlim ) {
1748 	// Normal miter join
1749 	SplineSetLineTo(cur, ixy);
1750 	SplineSetLineTo(cur, jpp->oxy);
1751     } else {
1752 	if ( jpp->c->join==lj_miter ) {
1753 	    BevelJoin(jpp);
1754 	    return;
1755 	}
1756 	refp = BPAdd(jpp->sxy, jpp->c->pseudo_origin);
1757 	ut = NormVec(BPSub(cur->last->me, jpp->oxy));
1758 	ut = jpp->bend_is_ccw ? BP90CW(ut) : BP90CCW(ut);
1759 	clip1 = BPAdd(refp, BPScale(ut, jlim/2));
1760 	clip2 = BPAdd(clip1, BP90CW(ut));
1761 	if ( !LineSameSide(clip1, clip2, jpp->oxy, refp, false) ) {
1762 	    // Don't trim past bevel
1763 	    BevelJoin(jpp);
1764 	    return;
1765 	}
1766 	if ( LineSameSide(clip1, clip2, ixy, refp, false) ) {
1767 	    // Backup normal miter join (rounding problems)
1768 	    SplineSetLineTo(cur, ixy);
1769 	    SplineSetLineTo(cur, jpp->oxy);
1770 	    return;
1771 	}
1772 	// Clipped miter join
1773 	intersects = IntersectLines(&ixy, &cur->last->me, &cow, &clip1, &clip2);
1774 	VASSERT(intersects);
1775 	SplineSetLineTo(cur, ixy);
1776 	intersects = IntersectLines(&ixy, &jpp->oxy, &coi, &clip1, &clip2);
1777 	VASSERT(intersects);
1778 	SplineSetLineTo(cur, ixy);
1779 	SplineSetLineTo(cur, jpp->oxy);
1780     }
1781 }
1782 
ArcsJoin(JoinParams * jpp)1783 static void ArcsJoin(JoinParams *jpp) {
1784     bigreal t, K_fm, K_to, fsw_fm, fsw_to, fsw_avg, r_fm, r_to, i_dist;
1785     bigreal jlim, r_clip, clip_ratio, start_angle, end_angle;
1786     bigreal angle_diff, max_angle_diff, bevel_angle_diff;
1787     BasePoint ut_to, p_fm, p_to, cv_fm, cv_to, center_fm, center_to;
1788     BasePoint i, i2, cut_fm, cut_to;
1789     BasePoint orig_p, ut_avg, iut, p_cref, ut_cref, center_clip;
1790     BasePoint ci_fm, ci_to, ci2, p_ref;
1791     SplinePoint *tmp_st, *tmp_end;
1792     int curved = false, cnt, note, neg_fm, neg_to, clipped = false, s_clip;
1793     int flat_fm, flat_to;
1794 
1795     p_fm = jpp->cur->last->me;
1796     p_to = jpp->oxy;
1797     ut_to = jpp->no_to->utanvec;
1798 
1799     /* Arcs is the only join style with parameters beyond the position and
1800      * slope at the offset spline endpoints. The "from" spline is available
1801      * as jpp->cur->last->from but the "to" spline is not yet built.
1802      *
1803      * We could calculate its curvature by the curvature of the
1804      * source spline and the nib point, but the point fitting/estimation
1805      * process may yield a visibly different result. We could instead
1806      * reorganize the code so that joins are retroactively applied, but
1807      * that would both take effort and leave the code less readable.
1808      * Instead we build the "to" spline here (when necessary), extract the
1809      * parameters, and delete it.
1810      *
1811      * Regardless if either "join point" is on a cusp the curvature of the
1812      * source spline has been "overwhelmed" to the opposite sign by the
1813      * curvature of the nib. In that case we just use the unadjusted source
1814      * curvature, which (because the radius is relatively small) will either
1815      * be adjusted to an appropriate larger value or subject to the Round
1816      * join fallback.
1817      */
1818 
1819     t = SplineStrokeNextT(jpp->c, jpp->s, 0.0, jpp->ccw_to, &ut_to, &curved,
1820                           jpp->is_right, -1);
1821     ut_to = jpp->no_to->utanvec; // Set back after call
1822 
1823     if ( curved ) {
1824 	if ( OffsetOnCuspAt(jpp->c, jpp->s, 0.0, jpp->no_to,
1825 	                    jpp->is_right, jpp->ccw_to) ) {
1826 	    // printf("Outgoing on cusp - using source curvature\n");
1827 	    K_to = SplineEndCurvature(jpp->s, false);
1828 	} else {
1829 	    tmp_st = SplinePointCreate(p_to.x, p_to.y);
1830 	    tmp_end = TraceAndFitSpline(jpp->c, jpp->s, 0.0, t, tmp_st, -1,
1831 	                                jpp->is_right, false);
1832 	    K_to = SplineEndCurvature(tmp_st->next, false);
1833 	    while ( tmp_st!=tmp_end ) {
1834 		tmp_st = tmp_st->next->to;
1835 		free(tmp_st->prev->from);
1836 		free(tmp_st->prev);
1837 	    }
1838 	    free(tmp_end);
1839 	}
1840     } else {
1841 	// If the spline will be drawn by a nib point as opposed to a nib curve
1842 	// then the curvature will just be that of the source spline.
1843 	K_to = SplineEndCurvature(jpp->s, false);
1844     }
1845     if ( OffsetOnCuspAt(jpp->c, jpp->s->from->prev, 1.0, NULL,
1846 	                jpp->is_right, jpp->ccw_fm) ) {
1847 	//printf("Incoming on cusp - using source curvature\n");
1848 	K_fm = SplineEndCurvature(jpp->s->from->prev, false);
1849     } else
1850 	K_fm = SplineEndCurvature(jpp->cur->last->prev, true);
1851 
1852     fsw_fm = FalseStrokeWidth(jpp->c, BPNeg(jpp->ut_fm));
1853     fsw_to = FalseStrokeWidth(jpp->c, BPNeg(jpp->no_to->utanvec));
1854     fsw_avg = (fsw_fm + fsw_to) / 2.0;
1855 
1856     flat_fm = K_fm==0;
1857     flat_to = K_to==0;
1858 
1859     // printf(" K_fm: %.15lf, K_to: %.15lf\n", K_fm, K_to);
1860     if ( flat_fm && flat_to ) {
1861 	MiterJoin(jpp);
1862 	return;
1863     }
1864 
1865     // Calculate derived parameters
1866     neg_fm = (K_fm < 0);
1867     neg_to = (K_to < 0);
1868 
1869     if ( !flat_fm ) {
1870 	r_fm = fabs(1.0/K_fm);
1871 	cv_fm = BPRevIf(neg_fm, BP90CCW(jpp->ut_fm));
1872 	center_fm = BPAdd(p_fm, BPScale(cv_fm, r_fm));
1873     }
1874     if ( !flat_to ) {
1875 	r_to = fabs(1.0/K_to);
1876 	cv_to = BPRevIf(neg_to, BP90CCW(ut_to));
1877 	center_to = BPAdd(p_to, BPScale(cv_to, r_to));
1878     }
1879 
1880     // Find unclipped intersection
1881     if ( flat_fm ) {
1882 	cnt = LineCircleTest(p_fm, jpp->ut_fm, center_to, r_to);
1883 	if ( cnt > 0 )
1884 	    IntersectLineCircle(p_fm, jpp->ut_fm, center_to, r_to, &i, &i2);
1885 	else {
1886 	    r_to = AdjustLineCircle(p_fm, jpp->ut_fm, p_to, cv_to);
1887 	    center_to = BPAdd(p_to, BPScale(cv_to, r_to));
1888 	    cnt = 1;
1889 	    i = ProjectPointOnLine(center_to, p_fm, jpp->ut_fm);
1890 	}
1891     } else if ( flat_to ) {
1892 	cnt = LineCircleTest(p_to, ut_to, center_fm, r_fm);
1893 	if ( cnt > 0 )
1894 	    IntersectLineCircle(p_to, ut_to, center_fm, r_fm, &i, &i2);
1895 	else {
1896 	    r_fm = AdjustLineCircle(p_to, ut_to, p_fm, cv_fm);
1897 	    center_fm = BPAdd(p_fm, BPScale(cv_fm, r_fm));
1898 	    cnt = 1;
1899 	    i = ProjectPointOnLine(center_fm, p_to, ut_to);
1900 	}
1901     } else {
1902 	note = cnt = CirclesTest(center_fm, r_fm, center_to, r_to);
1903 	if ( cnt > 0 )
1904 	    IntersectCircles(center_fm, r_fm, center_to, r_to, &i, &i2);
1905 	else {
1906 	    if ( note==-2 )
1907 		AdjustCircles(p_to, cv_to, &r_to, p_fm, cv_fm, &r_fm, 1);
1908 	    else
1909 		AdjustCircles(p_fm, cv_fm, &r_fm, p_to, cv_to, &r_to, note<0);
1910 	    center_fm = BPAdd(p_fm, BPScale(cv_fm, r_fm));
1911 	    center_to = BPAdd(p_to, BPScale(cv_to, r_to));
1912 	    cnt = 1;
1913 	    if ( note==-2 )
1914 		i = BPAdd(center_to,
1915 		           BPScale(NormVec(BPSub(center_fm, center_to)),
1916 		                    r_to));
1917 	    else
1918 		i = BPAdd(center_fm,
1919 		           BPScale(NormVec(BPSub(center_to, center_fm)),
1920 		                    r_fm));
1921 	}
1922     }
1923 
1924     // A point definitely on the far side of the bevel line relative to
1925     // the source spline
1926     p_ref = BPAdd(p_fm, BPRevIf(jpp->bend_is_ccw,
1927                                    BP90CW(BPSub(p_to, p_fm))));
1928 
1929     if ( cnt > 1 )
1930 	i = CloserIntersection(p_fm, p_to, i, i2, p_ref);
1931 
1932     // A variant of the SVG round fallback. It is different in that a) we
1933     // fall back after the intersection adjustment, b) we check each
1934     // curvature against its own false stroke width (as these can be
1935     // different with arbitrary nibs), and c) we also fall back if both
1936     // intersection points are behind the bevel line (which can happen
1937     // in certain unusual situations). Only the first difference applies
1938     // in the case of a circular nib.
1939     if (   ( !flat_fm && r_fm<fsw_fm/2 ) || ( !flat_to && r_to<fsw_to/2 )
1940         || !LineSameSide(p_fm, p_to, i, p_ref, false) ) {
1941 	// printf("Falling back to Round\n", r_fm, r_to);
1942 	RoundJoin(jpp, false);
1943 	return;
1944     }
1945 
1946     // Clipping pre-calculations
1947     orig_p = BPAdd(jpp->sxy, jpp->c->pseudo_origin);
1948     ut_avg = NormVec(BPScale(BPSub(jpp->ut_fm, ut_to), 0.5)),
1949     iut = BPSub(i, orig_p);
1950     i_dist = sqrt(BPLenSq(iut));
1951     iut = BPScale(iut, 1.0/i_dist);
1952     jlim = CalcJoinLimit(jpp->c, fsw_avg);
1953 
1954     // There are two clipping styles. SVG 2 specifies a clip line normal
1955     // to a circle at an arc distance away from the source join point.
1956     //
1957     // The alternate style uses the same circle to measure the total length
1958     // of the join and the distance from the source join point to the bevel
1959     // line, and uses these to calculate the portion of each line between
1960     // the offset join points and the calculated intersection that should
1961     // not be clipped. The latter style works for any nib while the former
1962     // will fail for some combinations of non-circular nib and source contour.
1963     s_clip = PVPCircle(orig_p, ut_avg, i, &center_clip, &r_clip);
1964     if ( s_clip==0 ) {
1965 	// If there is no clipping circle use a line instead
1966 	if ( jlim/2.0 < i_dist ) { // Linear test
1967 	    clipped = true;
1968 	    if ( !jpp->c->ratio_al ) {
1969 		p_cref = BPAdd(orig_p, BPScale(iut, jlim/2.0));
1970 		ut_cref = BP90CCW(iut);
1971 	    } else {
1972 		p_cref = ProjectPointOnLine(orig_p, p_fm,
1973 		                            NormVec(BPSub(p_to, p_fm)));
1974 		t = BPDist(orig_p, p_cref);
1975 		if ( t >= jlim/2.0 )
1976 		    clip_ratio = 0;
1977 		else
1978 		    clip_ratio = (jlim/2.0 - t)/(i_dist - t);
1979 	    }
1980 	}
1981     } else if ( jlim/2.0 < i_dist*FF_PI/2.0 ) { // Conservative half-circle test
1982 	// Could probably do this with utanvecs but radian angles are less
1983 	// confusing to add and subtract
1984 	max_angle_diff = jlim/(r_clip*2);
1985 	start_angle = atan2(orig_p.y-center_clip.y, orig_p.x-center_clip.x);
1986 	end_angle = atan2(i.y-center_clip.y, i.x-center_clip.x);
1987 	angle_diff = NormAngle(s_clip*(start_angle - end_angle));
1988 	if ( angle_diff < 0 ) // Renormalize angle difference to 0->2*pi
1989 	    angle_diff += 2*FF_PI;
1990 
1991 	// Accurate test
1992 	if ( angle_diff > max_angle_diff ) {
1993 	    clipped = true;
1994 	    if ( !jpp->c->ratio_al ) {
1995 		end_angle = NormAngle(start_angle - s_clip * max_angle_diff);
1996 		ut_cref = (BasePoint) { cos(end_angle), sin(end_angle) };
1997 		p_cref = BPAdd(center_clip, BPScale(ut_cref, r_clip));
1998 	    } else {
1999 		IntersectLineCircle(p_fm, NormVec(BPSub(p_to, p_fm)),
2000 		                    center_clip, r_clip, &ci_fm, &ci2);
2001 		ci_fm = CloserPoint(p_fm, ci_fm, ci2);
2002 		end_angle = atan2(ci_fm.y-center_clip.y, ci_fm.x-center_clip.x);
2003 		bevel_angle_diff = NormAngle(s_clip*(start_angle - end_angle));
2004 		if (bevel_angle_diff >= max_angle_diff)
2005 		    clip_ratio = 0;
2006 		else
2007 		    clip_ratio =   (max_angle_diff - bevel_angle_diff)
2008 		                 / (angle_diff - bevel_angle_diff);
2009 	    }
2010 	}
2011     }
2012 
2013     if ( flat_fm ) {
2014 	if ( clipped && !jpp->c->ratio_al ) {
2015 	    IntersectLinesSlopes(&ci_fm, &p_fm, &jpp->ut_fm, &p_cref, &ut_cref);
2016 	    if ( !LineSameSide(p_fm, p_to, ci_fm, p_ref, false) )
2017 		ci_fm = p_fm;
2018 	} else if ( clipped ) {
2019 	    i_dist = BPDist(i, p_fm);
2020 	    ci_fm = BPAdd(p_fm, BPScale(jpp->ut_fm, i_dist * clip_ratio));
2021 	} else
2022 	    ci_fm = i;
2023 	if ( !BPEq(ci_fm, p_fm) )
2024 	    SplineSetLineTo(jpp->cur, ci_fm);
2025     } else {
2026 	if ( clipped && !jpp->c->ratio_al )
2027 	    ci_fm = SVGArcClip(p_cref, ut_cref, center_fm, r_fm, p_fm, p_to,
2028 	                       p_ref);
2029 	else if ( clipped )
2030 	    ci_fm = ArcClip(center_fm, r_fm, neg_fm, p_fm, i, clip_ratio);
2031 	else
2032 	    ci_fm = i;
2033 	if ( !BPEq(ci_fm, p_fm) ) {
2034 	    cut_fm = BP90CW(NormVec(BPSub(ci_fm, center_fm)));
2035 	    SSAppendArc(jpp->cur, r_fm, r_fm, UTZERO,
2036 	                BPRevIf(!neg_fm, jpp->ut_fm), cut_fm, !neg_fm, true);
2037 	    SplineStrokeSimpleFixup(jpp->cur->last, ci_fm);
2038 	}
2039     }
2040     if ( flat_to ) {
2041 	if ( clipped ) {
2042 	    if ( !jpp->c->ratio_al ) {
2043 		IntersectLinesSlopes(&ci_to, &p_to, &ut_to, &p_cref, &ut_cref);
2044 		if ( !LineSameSide(p_fm, p_to, ci_to, p_ref, false) )
2045 		    ci_to = p_to;
2046 	    } else {
2047 		i_dist = BPDist(i, p_to);
2048 		ci_to = BPAdd(p_to, BPScale(BPRev(ut_to),
2049 		                            i_dist * clip_ratio));
2050 	    }
2051 	    SplineSetLineTo(jpp->cur, ci_to);
2052 	} else
2053 	    ci_to = i;
2054 	if ( !BPEq(ci_to, p_to) )
2055 	    SplineSetLineTo(jpp->cur, p_to);
2056     } else {
2057 	if ( clipped ) {
2058 	    if ( !jpp->c->ratio_al )
2059 		ci_to = SVGArcClip(p_cref, ut_cref, center_to, r_to, p_to, p_fm,
2060 		                   p_ref);
2061 	    else
2062 		ci_to = ArcClip(center_to, r_to, !neg_to, p_to, i, clip_ratio);
2063 	    SplineSetLineTo(jpp->cur, ci_to);
2064 	} else
2065 	    ci_to = i;
2066 	if ( !BPEq(ci_to, p_to) ) {
2067 	    cut_to = BP90CW(NormVec(BPSub(ci_to, center_to)));
2068 	    SSAppendArc(jpp->cur, r_to, r_to, UTZERO, cut_to,
2069 	                BPRevIf(!neg_to, ut_to), !neg_to, true);
2070 	    SplineStrokeSimpleFixup(jpp->cur->last, p_to);
2071 	}
2072     }
2073 }
2074 
_HandleJoin(JoinParams * jpp)2075 static int _HandleJoin(JoinParams *jpp) {
2076     SplineSet *cur = jpp->cur;
2077     BasePoint oxy = jpp->oxy;
2078     StrokeContext *c = jpp->c;
2079     int is_flat = false;
2080     bigreal costheta = BPDot(jpp->ut_fm, jpp->no_to->utanvec);
2081 
2082     assert( cur->first!=NULL );
2083 
2084     if ( BPWithin(cur->last->me, oxy, jpp->c->log_maxdim*INTERSPLINE_MARGIN) ) {
2085 	// Close enough to just move the point
2086 	SplineStrokeAppendFixup(cur->last, jpp->sxy, jpp->no_to, jpp->ccw_to,
2087 	                        c->log_maxdim);
2088 	is_flat = true;
2089     } else if ( RealWithin(costheta, 1, COS_MARGIN) ) {
2090 	SplineSetLineTo(cur, oxy);
2091 	is_flat = true;
2092     } else if ( !jpp->bend_is_ccw == !jpp->is_right ) {
2093 	// Join is under nib, just connect for later removal
2094 	BevelJoin(jpp);
2095     } else if ( c->join==lj_bevel ) {
2096 	BevelJoin(jpp);
2097     } else if ( c->join==lj_round ) {
2098 	RoundJoin(jpp, RealWithin(costheta, -1, COS_MARGIN));
2099     } else if (    c->join==lj_miter || c->join==lj_miterclip
2100                 || c->join==lj_arcs ) {
2101 	if ( RealWithin(costheta, -1, COS_MARGIN) ) {
2102 	    if ( c->join==lj_miter )
2103 		BevelJoin(jpp);
2104 	    else
2105 		DoubleBackJC(jpp->c, jpp->cur, jpp->sxy, jpp->oxy, jpp->ut_fm,
2106 		             jpp->is_right, 0);
2107 	} else if (c->join==lj_arcs && !RealWithin(costheta, 1, 30*COS_MARGIN))
2108 	    ArcsJoin(jpp);
2109 	else
2110 	    MiterJoin(jpp);
2111     } else {
2112 	if ( c->join!=lj_nib && c->join!=lj_inherited )
2113 	    LogError( _("Warning: Unrecognized or unsupported join type, defaulting to 'nib'.\n") );
2114 	NibJoin(jpp);
2115     }
2116     return is_flat;
2117 }
2118 
HandleJoin(StrokeContext * c,Spline * s,SplineSet * cur,BasePoint sxy,NibOffset * no_to,int ccw_to,BasePoint ut_fm,int ccw_fm,int is_right)2119 static int HandleJoin(StrokeContext *c, Spline *s, SplineSet *cur,
2120                       BasePoint sxy, NibOffset *no_to, int ccw_to,
2121                       BasePoint ut_fm, int ccw_fm, int is_right) {
2122     JoinParams jp = { c, s, cur, sxy, BPUNINIT, ut_fm, no_to,
2123                       ccw_fm, ccw_to, is_right, false };
2124     jp.oxy = BPAdd(sxy, no_to->off[ccw_to]);
2125     if ( cur->first==NULL ) {
2126 	// Create initial spline point
2127 	cur->first = SplinePointCreate(jp.oxy.x, jp.oxy.y);
2128 	cur->last = cur->first;
2129 	return false;
2130     }
2131     jp.bend_is_ccw = !JointBendsCW(ut_fm, no_to->utanvec);
2132     return _HandleJoin(&jp);
2133 }
2134 
2135 
HandleCap(StrokeContext * c,SplineSet * cur,BasePoint sxy,BasePoint ut,BasePoint oxy,int is_right)2136 static void HandleCap(StrokeContext *c, SplineSet *cur, BasePoint sxy,
2137                       BasePoint ut, BasePoint oxy, int is_right) {
2138     NibOffset no_fm, no_to;
2139     SplinePoint *sp;
2140     int corner_fm, corner_to;
2141 
2142     if ( c->cap==lc_bevel ) {
2143 	SplineSetLineTo(cur, oxy);
2144 	return;
2145     }
2146     if ( c->cap==lc_butt || c->cap==lc_round ) {
2147 	DoubleBackJC(c, cur, sxy, oxy, BPRevIf(!is_right, ut), !is_right, 1);
2148     } else {
2149 	if ( c->cap!=lc_nib && c->cap!=lc_inherited )
2150 	    LogError( _("Warning: Unrecognized or unsupported cap type, defaulting to 'nib'.\n") );
2151 
2152 	CalcNibOffset(c, ut, false, &no_fm, -1);
2153 	corner_fm = SplineStrokeFindCorner(sxy, cur->last->me, &no_fm);
2154 	CalcNibOffset(c, ut, true, &no_to, -1);
2155 	corner_to = SplineStrokeFindCorner(sxy, oxy, &no_to);
2156 	sp = AddNibPortion(c->nibcorners, cur->last, &no_fm, corner_fm, &no_to,
2157 	                   corner_to, !is_right);
2158 	SplineStrokeAppendFixup(sp, sxy, &no_to, corner_to, c->log_maxdim);
2159 	cur->last = sp;
2160     }
2161 }
2162 
2163 /******************************************************************************/
2164 /* ******************************* Main Loop ******************************** */
2165 /******************************************************************************/
2166 
OffsetSplineSet(SplineSet * ss,StrokeContext * c)2167 static SplineSet *OffsetSplineSet(SplineSet *ss, StrokeContext *c) {
2168     NibOffset no;
2169     Spline *s, *first=NULL;
2170     SplineSet *left=NULL, *right=NULL, *cur;
2171     SplinePoint *sp;
2172     BasePoint ut_ini = BPUNINIT, ut_start, ut_mid, ut_endlast;
2173     BasePoint sxy;
2174     bigreal last_t, t;
2175     int is_right, linear, curved, on_cusp;
2176     int is_ccw_ini = false, is_ccw_start, is_ccw_mid, was_ccw = false;
2177     int closed = ss->first->prev!=NULL;
2178 
2179     if ( (c->contour_was_ccw ? !c->remove_inner : !c->remove_outer) || !closed )
2180 	left = chunkalloc(sizeof(SplineSet));
2181     if ( (c->contour_was_ccw ? !c->remove_outer : !c->remove_inner) || !closed )
2182 	right = chunkalloc(sizeof(SplineSet));
2183 
2184     for ( s=ss->first->next; s!=NULL && s!=first; s=s->to->next ) {
2185 	if ( first==NULL )
2186 	    first = s;
2187 
2188 	if ( SplineLength(s)==0 ) // Can ignore zero length splines
2189 	    continue;
2190 
2191 	ut_start = SplineUTanVecAt(s, 0.0);
2192 	linear = SplineIsLinearish(s);
2193 	if ( linear ) {
2194 	    is_ccw_start = 0;
2195 	} else {
2196 	    is_ccw_start = SplineTurningCCWAt(s, 0.0);
2197 	}
2198 	if ( BPIsUninit(ut_ini) ) {
2199 	    ut_ini = ut_start;
2200 	    is_ccw_ini = is_ccw_start;
2201 	}
2202 
2203 	// Left then right
2204 	for ( is_right=0; is_right<=1; ++is_right ) {
2205 
2206 	    if ( is_right ) {
2207 		if ( right==NULL )
2208 		    continue;
2209 		cur = right;
2210 	    } else {
2211 		if ( left==NULL )
2212 		    continue;
2213 		cur = left;
2214 	    }
2215 
2216 	    sxy = SPLINEPVAL(s, 0.0);
2217 	    CalcNibOffset(c, ut_start, is_right, &no, -1);
2218 
2219 	    HandleJoin(c, s, cur, sxy, &no, is_ccw_start,
2220 	               ut_endlast, was_ccw, is_right);
2221 
2222 	    on_cusp = OffsetOnCuspAt(c, s, 0.0, &no, is_right, is_ccw_start);
2223 
2224 	    // The path for this spline
2225 	    if ( linear ) {
2226 		sxy = SPLINEPVAL(s, 1.0);
2227 		SplineSetLineTo(cur, BPAdd(sxy, no.off[is_ccw_start]));
2228 	    } else {
2229 		t = 0.0;
2230 		ut_mid = ut_start;
2231 		is_ccw_mid = is_ccw_start;
2232 		while ( t < 1.0 ) {
2233 		    last_t = t;
2234 		    t = SplineStrokeNextT(c, s, t, is_ccw_mid, &ut_mid, &curved,
2235 		                          is_right, no.nci[is_ccw_mid]);
2236 		    assert( t > last_t && t <= 1.0 );
2237 
2238 		    if ( curved )
2239 			sp = TraceAndFitSpline(c, s, last_t, t, cur->last,
2240 			                       no.nci[is_ccw_mid], is_right,
2241 			                       on_cusp);
2242 		    else
2243 			sp = AppendCubicSplinePortion(s, last_t, t, cur->last);
2244 
2245 		    cur->last = sp;
2246 
2247 		    sxy = SPLINEPVAL(s, t);
2248 		    CalcNibOffset(c, ut_mid, is_right, &no, no.nci[is_ccw_mid]);
2249 		    is_ccw_mid = SplineTurningCCWAt(s, t);
2250 		    SplineStrokeAppendFixup(cur->last, sxy, &no, -1,
2251 		                            c->log_maxdim);
2252 
2253 		    if ( t < 1.0 )
2254 			HandleFlat(cur, sxy, &no, is_ccw_mid);
2255 		    on_cusp = OffsetOnCuspAt(c, s, t, &no, is_right, is_ccw_mid);
2256 		}
2257 	    }
2258 	}
2259 	ut_endlast = SplineUTanVecAt(s, 1.0);
2260         was_ccw = SplineTurningCCWAt(s, 1.0);
2261     }
2262     if (    (left!=NULL && left->first==NULL)
2263          || (right!=NULL && right->first==NULL) ) {
2264 	// The path (presumably) had only zero-length splines
2265 	LogError( _("Warning: No stroke output for contour\n") );
2266 	assert(    (left==NULL || left->first==NULL)
2267 	        && (right==NULL || right->first==NULL) );
2268 	chunkfree(left, sizeof(SplineSet));
2269 	chunkfree(right, sizeof(SplineSet));
2270 	return NULL;
2271     }
2272     cur = NULL;
2273     if ( !closed ) {
2274 	HandleCap(c, left, ss->last->me, ut_endlast, right->last->me, true);
2275 	SplineSetReverse(right);
2276 	left->next = right;
2277 	right = NULL;
2278 	SplineSetJoin(left, true, FIXUP_MARGIN*c->log_maxdim, &closed);
2279 	if ( !closed )
2280 	     LogError( _("Warning: Contour end did not close\n") );
2281 	else {
2282 	    HandleCap(c, left, ss->first->me, ut_ini, left->first->me, false);
2283 	    SplineSetJoin(left, true, FIXUP_MARGIN*c->log_maxdim, &closed);
2284 	    if ( !closed )
2285 		LogError( _("Warning: Contour start did not close\n") );
2286 	    else {
2287 		if ( c->rmov==srmov_contour )
2288 		    left = SplineSetRemoveOverlap(NULL,left,over_remove);
2289 		// Open paths don't always produce clockwise output
2290 		is_ccw_mid = SplinePointListIsClockwise(left);
2291 		if ( is_ccw_mid==-1 && c->rmov!=srmov_none ) {
2292 		    assert( c->rmov!=srmov_contour );
2293 		    left = SplineSetRemoveOverlap(NULL,left,over_remove);
2294 		    is_ccw_mid = SplinePointListIsClockwise(left);
2295 		    assert( is_ccw_mid!=-1 );
2296 		} else if ( is_ccw_mid==-1 ) {
2297 		    LogError( _("Warning: Can't identify contour direction, "
2298 		                "assuming clockwise\n") );
2299 		}
2300 		if ( is_ccw_mid==0 )
2301 		    SplineSetReverse(left);
2302 	    }
2303 	}
2304 	cur = left;
2305 	left = NULL;
2306     } else {
2307 	// This can fail if the source contour is closed in a strange way
2308 	if ( left!=NULL ) {
2309 	    CalcNibOffset(c, ut_ini, false, &no, -1);
2310 	    HandleJoin(c, ss->first->next, left, ss->first->me, &no,
2311 	               is_ccw_ini, ut_endlast, was_ccw, false);
2312             left = SplineSetJoin(left, true, FIXUP_MARGIN*c->log_maxdim,
2313 	                         &closed);
2314 	    if ( !closed )
2315 		LogError( _("Warning: Left contour did not close\n") );
2316 	    else if ( c->rmov==srmov_contour )
2317 		left = SplineSetRemoveOverlap(NULL,left,over_remove);
2318 	    cur = left;
2319 	    left = NULL;
2320 	}
2321 	if ( right!=NULL ) {
2322 	    CalcNibOffset(c, ut_ini, true, &no, -1);
2323 	    HandleJoin(c, ss->first->next, right, ss->first->me, &no,
2324 	               is_ccw_ini, ut_endlast, was_ccw, true);
2325             right = SplineSetJoin(right, true, FIXUP_MARGIN*c->log_maxdim,
2326 	                          &closed);
2327 	    if ( !closed )
2328 		LogError( _("Warning: Right contour did not close\n") );
2329 	    else {
2330 		SplineSetReverse(right);
2331 		if ( c->rmov!=srmov_none )
2332 		    // Need to do this for either srmov_contour or srmov_layer
2333 		    right = SplineContourOuterCCWRemoveOverlap(right);
2334 	    }
2335 	    if ( cur != NULL ) {
2336 		cur->next = right;
2337 	    } else
2338 		cur = right;
2339 	    right = NULL;
2340 	}
2341     }
2342     return cur;
2343 }
2344 
2345 /******************************************************************************/
2346 /* ******************************* Unit Stuff ******************************* */
2347 /******************************************************************************/
2348 
2349 static BasePoint SquareCorners[] = {
2350     { -1,  1 },
2351     {  1,  1 },
2352     {  1, -1 },
2353     { -1, -1 },
2354 };
2355 
2356 static struct shapedescrip {
2357     BasePoint me, prevcp, nextcp;
2358 }
2359 unitcircle[] = {
2360     { { -1, 0 }, { -1, -CIRCOFF }, { -1, CIRCOFF } },
2361     { { 0 , 1 }, { -CIRCOFF, 1 }, { CIRCOFF, 1 } },
2362     { { 1, 0 }, { 1, CIRCOFF }, { 1, -CIRCOFF } },
2363     { { 0, -1 }, { CIRCOFF, -1 }, { -CIRCOFF, -1 } },
2364     { { 0, 0 }, { 0, 0 }, { 0, 0 } }
2365 };
2366 
SpOnCircle(int i,bigreal radius,BasePoint * center)2367 static SplinePoint *SpOnCircle(int i,bigreal radius,BasePoint *center) {
2368     SplinePoint *sp = SplinePointCreate(unitcircle[i].me.x*radius + center->x,
2369 					unitcircle[i].me.y*radius + center->y);
2370     sp->pointtype = pt_curve;
2371     sp->prevcp.x = unitcircle[i].prevcp.x*radius + center->x;
2372     sp->prevcp.y = unitcircle[i].prevcp.y*radius + center->y;
2373     sp->nextcp.x = unitcircle[i].nextcp.x*radius + center->x;
2374     sp->nextcp.y = unitcircle[i].nextcp.y*radius + center->y;
2375     sp->nonextcp = sp->noprevcp = false;
2376 return( sp );
2377 }
2378 
UnitShape(int n)2379 SplineSet *UnitShape(int n) {
2380     SplineSet *ret;
2381     SplinePoint *sp1, *sp2;
2382     int i;
2383     BasePoint origin;
2384 
2385     ret = chunkalloc(sizeof(SplineSet));
2386     if ( n>=3 || n<=-3 ) {
2387 	/* Regular n-gon with n sides */
2388 	/* Inscribed in a unit circle, if n<0 then circumscribed around */
2389 	bigreal angle = 2*FF_PI/(2*n);
2390 	bigreal factor=1;
2391 	if ( n<0 ) {
2392 	    angle = -angle;
2393 	    n = -n;
2394 	    factor = 1/cos(angle);
2395 	}
2396 	angle -= FF_PI/2;
2397 	ret->first = sp1 = SplinePointCreate(factor*cos(angle), factor*sin(angle));
2398 	sp1->pointtype = pt_corner;
2399 	for ( i=1; i<n; ++i ) {
2400 	    angle = 2*FF_PI/(2*n) + i*2*FF_PI/n - FF_PI/2;
2401 	    sp2 = SplinePointCreate(factor*cos(angle),factor*sin(angle));
2402 	    sp2->pointtype = pt_corner;
2403 	    SplineMake3(sp1,sp2);
2404 	    sp1 = sp2;
2405 	}
2406 	SplineMake3(sp1,ret->first);
2407 	ret->last = ret->first;
2408 	SplineSetReverse(ret);		/* Drat, just drew it counter-clockwise */
2409     } else if ( n ) {
2410 	ret->first = sp1 = SplinePointCreate(SquareCorners[0].x,
2411 			    SquareCorners[0].y);
2412 	sp1->pointtype = pt_corner;
2413 	for ( i=1; i<4; ++i ) {
2414 	    sp2 = SplinePointCreate(SquareCorners[i].x,
2415 				SquareCorners[i].y);
2416 	    sp2->pointtype = pt_corner;
2417 	    SplineMake3(sp1,sp2);
2418 	    sp1 = sp2;
2419 	}
2420 	SplineMake3(sp1,ret->first);
2421 	ret->last = ret->first;
2422     } else {
2423 	/* Turn into a circle */
2424 	origin.x = origin.y = 0;
2425 	ret->first = sp1 = SpOnCircle(0,1,&origin);
2426 	for ( i=1; i<4; ++i ) {
2427 	    sp2 = SpOnCircle(i,1,&origin);
2428 	    SplineMake3(sp1,sp2);
2429 	    sp1 = sp2;
2430 	}
2431 	SplineMake3(sp1,ret->first);
2432 	ret->last = ret->first;
2433     }
2434 return( ret );
2435 }
2436 
2437 /******************************************************************************/
2438 /* ******************************* Higher-Level ***************************** */
2439 /******************************************************************************/
2440 
SinglePointStroke(SplinePoint * sp,struct strokecontext * c)2441 static SplinePointList *SinglePointStroke(SplinePoint *sp,
2442                                           struct strokecontext *c) {
2443     SplineSet *ret;
2444     real trans[6];
2445 
2446     if ( c->nibtype==nib_ellip && c->cap==lc_butt ) {
2447 	// Leave as a single point
2448 	ret = chunkalloc(sizeof(SplineSet));
2449 	ret->first = ret->last = SplinePointCreate(sp->me.x,sp->me.y);
2450 	ret->first->pointtype = pt_corner;
2451     } else {
2452 	 // Draw a nib
2453 	memset(&trans, 0, sizeof(trans));
2454 	trans[0] = trans[3] = 1;
2455 	trans[4] = sp->me.x;
2456 	trans[5] = sp->me.y;
2457 	ret = SplinePointListCopy(c->nib);
2458 	SplinePointListTransformExtended(ret, trans, tpt_AllPoints,
2459 	                                 tpmask_dontTrimValues);
2460     }
2461     return( ret );
2462 }
2463 
SplineSet_Stroke(SplineSet * ss,struct strokecontext * c,int order2)2464 static SplineSet *SplineSet_Stroke(SplineSet *ss,struct strokecontext *c,
2465                                    int order2) {
2466     SplineSet *base = ss;
2467     SplineSet *ret;
2468     int copied = 0;
2469 
2470     c->contour_was_ccw = (   base->first->prev!=NULL
2471                           && !SplinePointListIsClockwise(base) );
2472 
2473     if ( base->first->next==NULL )
2474 	ret = SinglePointStroke(base->first, c);
2475     else {
2476 	if ( base->first->next->order2 ) {
2477 	    base = SSPSApprox(ss);
2478 	    copied = 1;
2479 	}
2480 	if ( c->contour_was_ccw )
2481 	    SplineSetReverse(base);
2482 	ret = OffsetSplineSet(base, c);
2483     }
2484     if ( order2 )
2485 	ret = SplineSetsConvertOrder(ret,order2);
2486     if ( copied )
2487 	SplinePointListFree(base);
2488     else if ( c->contour_was_ccw )
2489 	SplineSetReverse(base);
2490 
2491     return(ret);
2492 }
2493 
SplineSets_Stroke(SplineSet * ss,struct strokecontext * c,int order2)2494 static SplineSet *SplineSets_Stroke(SplineSet *ss,struct strokecontext *c,
2495                                     int order2) {
2496     SplineSet *first=NULL, *last=NULL, *cur;
2497 
2498     while ( ss!=NULL ) {
2499 	cur = SplineSet_Stroke(ss,c,order2);
2500 	if ( first==NULL )
2501 	    first = last = cur;
2502 	else
2503 	    last->next = cur;
2504 	if ( last!=NULL )
2505 	    while ( last->next!=NULL )
2506 		last = last->next;
2507 	ss = ss->next;
2508     }
2509 return( first );
2510 }
2511 
SplineSetStroke(SplineSet * ss,StrokeInfo * si,int order2)2512 SplineSet *SplineSetStroke(SplineSet *ss,StrokeInfo *si, int order2) {
2513     int max_pc;
2514     StrokeContext c;
2515     SplineSet *nibs, *nib, *first, *last, *cur;
2516     bigreal sn = 0.0, co = 1.0, mr, wh_ratio, maxdim;
2517     DBounds b;
2518     real trans[6];
2519     struct simplifyinfo smpl = { sf_forcelines | sf_mergelines |
2520                                  sf_smoothcurves | sf_setstart2extremum,
2521                                  0.25, 0.1, .005, 0, 0, 0 };
2522 
2523     if ( si->stroke_type==si_centerline )
2524 	IError("centerline not handled");
2525 
2526     memset(&c,0,sizeof(c));
2527     c.join = si->join;
2528     c.cap  = si->cap;
2529     c.joinlimit = si->joinlimit;
2530     c.extendcap = si->extendcap;
2531     c.acctarget = si->accuracy_target;
2532     c.remove_inner = si->removeinternal;
2533     c.remove_outer = si->removeexternal;
2534     c.leave_users_center = si->leave_users_center;
2535     c.extrema = si->extrema;
2536     c.simplify = si->simplify;
2537     c.ecrelative = si->ecrelative;
2538     c.jlrelative = si->jlrelative;
2539     c.rmov = si->rmov;
2540     if ( si->height!=0 )
2541 	mr = si->height/2;
2542     else
2543 	mr = si->width/2;
2544     if ( si->al==sal_auto ) {
2545 	if ( si->stroke_type==si_round || si->stroke_type==si_calligraphic ) {
2546 	    wh_ratio = si->height/si->width;
2547 	    c.ratio_al = (   c.joinlimit < 4.0 && c.jlrelative
2548 	                  && (wh_ratio >= 2.0 || wh_ratio <= 0.5));
2549 	} else {
2550 	    c.ratio_al = (c.joinlimit < 4.0 && c.jlrelative);
2551 	}
2552     } else
2553 	c.ratio_al = ( si->al==sal_ratio );
2554 
2555     if ( !c.extrema )
2556 	smpl.flags |= sf_ignoreextremum;
2557 
2558     if ( c.acctarget<MIN_ACCURACY ) {
2559          LogError( _("Warning: Accuracy target %lf less than minimum %lf, "
2560 	             "using %lf instead\n"), c.acctarget, MIN_ACCURACY );
2561 	 c.acctarget = MIN_ACCURACY;
2562     }
2563     smpl.err = c.acctarget;
2564 
2565     if ( si->penangle!=0 ) {
2566 	sn = sin(si->penangle);
2567 	co = cos(si->penangle);
2568     }
2569     trans[0] = co;
2570     trans[1] = sn;
2571     trans[2] = -sn;
2572     trans[3] = co;
2573     trans[4] = trans[5] = 0;
2574 
2575     if ( si->stroke_type==si_round || si->stroke_type==si_calligraphic ) {
2576 	c.nibtype = si->stroke_type==si_round ? nib_ellip : nib_rect;
2577 	max_pc = 4;
2578 	nibs = UnitShape(si->stroke_type==si_round ? 0 : -4);
2579 	trans[0] *= si->width/2;
2580 	trans[1] *= si->width/2;
2581 	trans[2] *= mr;
2582 	trans[3] *= mr;
2583 	maxdim = fmax(si->width/2, mr);
2584     } else {
2585 	c.nibtype = nib_convex;
2586 	max_pc = 20; // a guess, will be reallocated if needed
2587 	nibs = SplinePointListCopy(si->nib);
2588 	SplineSetFindBounds(nibs,&b);
2589 	if ( !c.leave_users_center ) {
2590 	    trans[4] = -(b.minx+b.maxx)/2;
2591 	    trans[5] = -(b.miny+b.maxy)/2;
2592 	} else {
2593 	    c.pseudo_origin.x = (b.minx+b.maxx)/2;
2594 	    c.pseudo_origin.y = (b.miny+b.maxy)/2;
2595 	}
2596 	// Close enough
2597 	maxdim = sqrt(pow(b.maxx-b.minx, 2) + pow(b.maxy-b.miny, 2))/2.0;
2598     }
2599     // Increases at the rate of the natural log, but with the "1 point" set
2600     // to radius 10
2601     c.log_maxdim = fmax(1.0, log(fmax(maxdim, mr))-LOG_MAXDIM_ADJ);
2602 
2603     SplinePointListTransformExtended(nibs,trans,tpt_AllPoints,
2604 	                             tpmask_dontTrimValues);
2605 
2606     first = last = NULL;
2607     for ( nib=nibs; nib!=NULL; nib=nib->next ) {
2608 	assert( NibIsValid(nib)==Shape_Convex );
2609 	c.nib = nib;
2610 	BuildNibCorners(&c.nibcorners, nib, &max_pc, &c.n);
2611 	cur = SplineSets_Stroke(ss,&c,order2);
2612         if ( first==NULL )
2613 	    first = last = cur;
2614 	else
2615 	    last->next = cur;
2616 	if ( last!=NULL )
2617 	    while ( last->next!=NULL )
2618 	        last = last->next;
2619     }
2620     if ( c.rmov==srmov_layer )
2621 	first=SplineSetRemoveOverlap(NULL,first,over_remove);
2622     if ( c.extrema )
2623 	SplineCharAddExtrema(NULL,first,ae_all,1000);
2624     if ( c.simplify )
2625 	first = SplineCharSimplify(NULL,first,&smpl);
2626     else
2627 	SPLCategorizePoints(first);
2628 
2629     free(c.nibcorners);
2630     SplinePointListFree(nibs);
2631     nibs = NULL;
2632 
2633     return( first );
2634 }
2635 
FVStrokeItScript(void * _fv,StrokeInfo * si,int UNUSED (pointless_argument))2636 void FVStrokeItScript(void *_fv, StrokeInfo *si,
2637                       int UNUSED(pointless_argument)) {
2638     FontViewBase *fv = _fv;
2639     int layer = fv->active_layer;
2640     SplineSet *temp;
2641     int i, cnt=0, gid;
2642     SplineChar *sc;
2643 
2644     for ( i=0; i<fv->map->enccount; ++i ) if ( (gid=fv->map->map[i])!=-1 && fv->sf->glyphs[gid]!=NULL && fv->selected[i] )
2645 	++cnt;
2646     ff_progress_start_indicator(10,_("Stroking..."),_("Stroking..."),0,cnt,1);
2647 
2648     SFUntickAll(fv->sf);
2649     for ( i=0; i<fv->map->enccount; ++i ) {
2650 	if ( (gid=fv->map->map[i])!=-1 && (sc = fv->sf->glyphs[gid])!=NULL &&
2651 		!sc->ticked && fv->selected[i] ) {
2652 	    sc->ticked = true;
2653 	    if ( sc->parent->multilayer ) {
2654 		SCPreserveState(sc,false);
2655 		for ( layer = ly_fore; layer<sc->layer_cnt; ++layer ) {
2656 		    temp = SplineSetStroke(sc->layers[layer].splines,si,sc->layers[layer].order2);
2657 		    SplinePointListsFree( sc->layers[layer].splines );
2658 		    sc->layers[layer].splines = temp;
2659 		}
2660 		SCCharChangedUpdate(sc,ly_all);
2661 	    } else {
2662 		SCPreserveLayer(sc,layer,false);
2663 		temp = SplineSetStroke(sc->layers[layer].splines,si,sc->layers[layer].order2);
2664 		SplinePointListsFree( sc->layers[layer].splines );
2665 		sc->layers[layer].splines = temp;
2666 		SCCharChangedUpdate(sc,layer);
2667 	    }
2668 	    if ( !ff_progress_next())
2669     break;
2670 	}
2671     }
2672     ff_progress_end_indicator();
2673 }
2674