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, ¢er_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