1 /*
2 * PathSimplify.cpp
3 * nlivarot
4 *
5 * Created by fred on Fri Dec 12 2003.
6 *
7 */
9 #include <glib/gmem.h>
10 #include <libnr/nr-point-matrix-ops.h>
11 #include "livarot/Path.h"
12 #include "livarot/path-description.h"
14 /*
15 * Reassembling polyline segments into cubic bezier patches
16 * thes functions do not need the back data. but they are slower than recomposing
17 * path descriptions when you have said back data (it's always easier with a model)
18 * there's a bezier fitter in bezier-utils.cpp too. the main difference is the way bezier patch are split
19 * here: walk on the polyline, trying to extend the portion you can fit by respecting the treshhold, split when
20 * treshhold is exceeded. when encountering a "forced" point, lower the treshhold to favor splitting at that point
21 * in bezier-utils: fit the whole polyline, get the position with the higher deviation to the fitted curve, split
22 * there and recurse
23 */
26 // algo d'origine: http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/INT-APP/CURVE-APP-global.html
28 // need the b-spline basis for cubic splines
29 // pas oublier que c'est une b-spline clampee
30 // et que ca correspond a une courbe de bezier normale
31 #define N03(t) ((1.0-t)*(1.0-t)*(1.0-t))
32 #define N13(t) (3*t*(1.0-t)*(1.0-t))
33 #define N23(t) (3*t*t*(1.0-t))
34 #define N33(t) (t*t*t)
35 // quadratic b-splines (jsut in case)
36 #define N02(t) ((1.0-t)*(1.0-t))
37 #define N12(t) (2*t*(1.0-t))
38 #define N22(t) (t*t)
39 // linear interpolation b-splines
40 #define N01(t) ((1.0-t))
41 #define N11(t) (t)
45 void Path::Simplify(double treshhold)
46 {
47 if (pts.size() <= 1) {
48 return;
49 }
51 Reset();
53 int lastM = 0;
54 while (lastM < int(pts.size())) {
55 int lastP = lastM + 1;
56 while (lastP < int(pts.size())
57 && (pts[lastP].isMoveTo == polyline_lineto
58 || pts[lastP].isMoveTo == polyline_forced))
59 {
60 lastP++;
61 }
63 DoSimplify(lastM, lastP - lastM, treshhold);
65 lastM = lastP;
66 }
67 }
70 // dichomtomic method to get distance to curve approximation
71 // a real polynomial solver would get the minimum more efficiently, but since the polynom
72 // would likely be of degree >= 5, that would imply using some generic solver, liek using the sturm metod
73 double RecDistanceToCubic(NR::Point const &iS, NR::Point const &isD,
74 NR::Point const &iE, NR::Point const &ieD,
75 NR::Point &pt, double current, int lev, double st, double et)
76 {
77 if ( lev <= 0 ) {
78 return current;
79 }
81 NR::Point const m = 0.5 * (iS + iE) + 0.125 * (isD - ieD);
82 NR::Point const md = 0.75 * (iE - iS) - 0.125 * (isD + ieD);
83 double const mt = (st + et) / 2;
85 NR::Point const hisD = 0.5 * isD;
86 NR::Point const hieD = 0.5 * ieD;
88 NR::Point const mp = pt - m;
89 double nle = NR::dot(mp, mp);
91 if ( nle < current ) {
93 current = nle;
94 nle = RecDistanceToCubic(iS, hisD, m, md, pt, current, lev - 1, st, mt);
95 if ( nle < current ) {
96 current = nle;
97 }
98 nle = RecDistanceToCubic(m, md, iE, hieD, pt, current, lev - 1, mt, et);
99 if ( nle < current ) {
100 current = nle;
101 }
103 } else if ( nle < 2 * current ) {
105 nle = RecDistanceToCubic(iS, hisD, m, md, pt, current, lev - 1, st, mt);
106 if ( nle < current ) {
107 current = nle;
108 }
109 nle = RecDistanceToCubic(m, md, iE, hieD, pt, current, lev - 1, mt, et);
110 if ( nle < current ) {
111 current = nle;
112 }
113 }
115 return current;
116 }
119 double DistanceToCubic(NR::Point const &start, PathDescrCubicTo res, NR::Point &pt)
120 {
121 NR::Point const sp = pt - start;
122 NR::Point const ep = pt - res.p;
123 double nle = NR::dot(sp, sp);
124 double nnle = NR::dot(ep, ep);
125 if ( nnle < nle ) {
126 nle = nnle;
127 }
129 NR::Point seg = res.p - start;
130 nnle = NR::cross(seg, sp);
131 nnle *= nnle;
132 nnle /= NR::dot(seg, seg);
133 if ( nnle < nle ) {
134 if ( NR::dot(sp,seg) >= 0 ) {
135 seg = start - res.p;
136 if ( NR::dot(ep,seg) >= 0 ) {
137 nle = nnle;
138 }
139 }
140 }
142 return nle;
143 }
146 /**
147 * Simplification on a subpath.
148 */
150 void Path::DoSimplify(int off, int N, double treshhold)
151 {
152 // non-dichotomic method: grow an interval of points approximated by a curve, until you reach the treshhold, and repeat
153 if (N <= 1) {
154 return;
155 }
157 int curP = 0;
159 fitting_tables data;
160 data.Xk = data.Yk = data.Qk = NULL;
161 data.tk = data.lk = NULL;
162 data.fk = NULL;
163 data.totLen = 0;
164 data.nbPt = data.maxPt = data.inPt = 0;
166 NR::Point const moveToPt = pts[off].p;
167 MoveTo(moveToPt);
168 NR::Point endToPt = moveToPt;
170 while (curP < N - 1) {
172 int lastP = curP + 1;
173 int M = 2;
175 // remettre a zero
176 data.inPt = data.nbPt = 0;
178 PathDescrCubicTo res(NR::Point(0, 0), NR::Point(0, 0), NR::Point(0, 0));
179 bool contains_forced = false;
180 int step = 64;
182 while ( step > 0 ) {
183 int forced_pt = -1;
184 int worstP = -1;
186 do {
187 if (pts[off + lastP].isMoveTo == polyline_forced) {
188 contains_forced = true;
189 }
190 forced_pt = lastP;
191 lastP += step;
192 M += step;
193 } while (lastP < N && ExtendFit(off + curP, M, data,
194 (contains_forced) ? 0.05 * treshhold : treshhold,
195 res, worstP) );
196 if (lastP >= N) {
198 lastP -= step;
199 M -= step;
201 } else {
202 // le dernier a echoue
203 lastP -= step;
204 M -= step;
206 if ( contains_forced ) {
207 lastP = forced_pt;
208 M = lastP - curP + 1;
209 }
211 AttemptSimplify(off + curP, M, treshhold, res, worstP); // ca passe forcement
212 }
213 step /= 2;
214 }
216 endToPt = pts[off + lastP].p;
217 if (M <= 2) {
218 LineTo(endToPt);
219 } else {
220 CubicTo(endToPt, res.start, res.end);
221 }
223 curP = lastP;
224 }
226 if (NR::LInfty(endToPt - moveToPt) < 0.00001) {
227 Close();
228 }
230 g_free(data.Xk);
231 g_free(data.Yk);
232 g_free(data.Qk);
233 g_free(data.tk);
234 g_free(data.lk);
235 g_free(data.fk);
236 }
239 // warning: slow
240 // idea behing this feature: splotches appear when trying to fit a small number of points: you can
241 // get a cubic bezier that fits the points very well but doesn't fit the polyline itself
242 // so we add a bit of the error at the middle of each segment of the polyline
243 // also we restrict this to <=20 points, to avoid unnecessary computations
244 #define with_splotch_killer
246 // primitive= calc the cubic bezier patche that fits Xk and Yk best
247 // Qk est deja alloue
248 // retourne false si probleme (matrice non-inversible)
249 bool Path::FitCubic(NR::Point const &start, PathDescrCubicTo &res,
250 double *Xk, double *Yk, double *Qk, double *tk, int nbPt)
251 {
252 NR::Point const end = res.p;
254 // la matrice tNN
255 NR::Matrix M(0, 0, 0, 0, 0, 0);
256 for (int i = 1; i < nbPt - 1; i++) {
257 M[0] += N13(tk[i]) * N13(tk[i]);
258 M[1] += N23(tk[i]) * N13(tk[i]);
259 M[2] += N13(tk[i]) * N23(tk[i]);
260 M[3] += N23(tk[i]) * N23(tk[i]);
261 }
263 double const det = M.det();
264 if (fabs(det) < 0.000001) {
265 res.start[0]=res.start[1]=0.0;
266 res.end[0]=res.end[1]=0.0;
267 return false;
268 }
270 NR::Matrix const iM = M.inverse();
271 M = iM;
273 // phase 1: abcisses
274 // calcul des Qk
275 Xk[0] = start[0];
276 Yk[0] = start[1];
277 Xk[nbPt - 1] = end[0];
278 Yk[nbPt - 1] = end[1];
280 for (int i = 1; i < nbPt - 1; i++) {
281 Qk[i] = Xk[i] - N03 (tk[i]) * Xk[0] - N33 (tk[i]) * Xk[nbPt - 1];
282 }
284 // le vecteur Q
285 NR::Point Q(0, 0);
286 for (int i = 1; i < nbPt - 1; i++) {
287 Q[0] += N13 (tk[i]) * Qk[i];
288 Q[1] += N23 (tk[i]) * Qk[i];
289 }
291 NR::Point P = Q * M;
292 NR::Point cp1;
293 NR::Point cp2;
294 cp1[NR::X] = P[NR::X];
295 cp2[NR::X] = P[NR::Y];
297 // phase 2: les ordonnees
298 for (int i = 1; i < nbPt - 1; i++) {
299 Qk[i] = Yk[i] - N03 (tk[i]) * Yk[0] - N33 (tk[i]) * Yk[nbPt - 1];
300 }
302 // le vecteur Q
303 Q = NR::Point(0, 0);
304 for (int i = 1; i < nbPt - 1; i++) {
305 Q[0] += N13 (tk[i]) * Qk[i];
306 Q[1] += N23 (tk[i]) * Qk[i];
307 }
309 P = Q * M;
310 cp1[NR::Y] = P[NR::X];
311 cp2[NR::Y] = P[NR::Y];
313 res.start = 3.0 * (cp1 - start);
314 res.end = 3.0 * (end - cp2 );
316 return true;
317 }
320 bool Path::ExtendFit(int off, int N, fitting_tables &data, double treshhold, PathDescrCubicTo &res, int &worstP)
321 {
322 if ( N >= data.maxPt ) {
323 data.maxPt = 2 * N + 1;
324 data.Xk = (double *) g_realloc(data.Xk, data.maxPt * sizeof(double));
325 data.Yk = (double *) g_realloc(data.Yk, data.maxPt * sizeof(double));
326 data.Qk = (double *) g_realloc(data.Qk, data.maxPt * sizeof(double));
327 data.tk = (double *) g_realloc(data.tk, data.maxPt * sizeof(double));
328 data.lk = (double *) g_realloc(data.lk, data.maxPt * sizeof(double));
329 data.fk = (char *) g_realloc(data.fk, data.maxPt * sizeof(char));
330 }
332 if ( N > data.inPt ) {
333 for (int i = data.inPt; i < N; i++) {
334 data.Xk[i] = pts[off + i].p[NR::X];
335 data.Yk[i] = pts[off + i].p[NR::Y];
336 data.fk[i] = ( pts[off + i].isMoveTo == polyline_forced ) ? 0x01 : 0x00;
337 }
338 data.lk[0] = 0;
339 data.tk[0] = 0;
341 double prevLen = 0;
342 for (int i = 0; i < data.inPt; i++) {
343 prevLen += data.lk[i];
344 }
345 data.totLen = prevLen;
347 for (int i = ( (data.inPt > 0) ? data.inPt : 1); i < N; i++) {
348 NR::Point diff;
349 diff[NR::X] = data.Xk[i] - data.Xk[i - 1];
350 diff[NR::Y] = data.Yk[i] - data.Yk[i - 1];
351 data.lk[i] = NR::L2(diff);
352 data.totLen += data.lk[i];
353 data.tk[i] = data.totLen;
354 }
356 for (int i = 0; i < data.inPt; i++) {
357 data.tk[i] *= prevLen;
358 data.tk[i] /= data.totLen;
359 }
361 for (int i = data.inPt; i < N; i++) {
362 data.tk[i] /= data.totLen;
363 }
364 data.inPt = N;
365 }
367 if ( N < data.nbPt ) {
368 // We've gone too far; we'll have to recalulate the .tk.
369 data.totLen = 0;
370 data.tk[0] = 0;
371 data.lk[0] = 0;
372 for (int i = 1; i < N; i++) {
373 data.totLen += data.lk[i];
374 data.tk[i] = data.totLen;
375 }
377 for (int i = 1; i < N; i++) {
378 data.tk[i] /= data.totLen;
379 }
380 }
382 data.nbPt = N;
384 if ( data.nbPt <= 0 ) {
385 return false;
386 }
388 res.p[0] = data.Xk[data.nbPt - 1];
389 res.p[1] = data.Yk[data.nbPt - 1];
390 res.start[0] = res.start[1] = 0;
391 res.end[0] = res.end[1] = 0;
392 worstP = 1;
393 if ( N <= 2 ) {
394 return true;
395 }
397 if ( data.totLen < 0.0001 ) {
398 double worstD = 0;
399 NR::Point start;
400 worstP = -1;
401 start[0] = data.Xk[0];
402 start[1] = data.Yk[0];
403 for (int i = 1; i < N; i++) {
404 NR::Point nPt;
405 bool isForced = data.fk[i];
406 nPt[0] = data.Xk[i];
407 nPt[1] = data.Yk[i];
409 double nle = DistanceToCubic(start, res, nPt);
410 if ( isForced ) {
411 // forced points are favored for splitting the recursion; we do this by increasing their distance
412 if ( worstP < 0 || 2*nle > worstD ) {
413 worstP = i;
414 worstD = 2*nle;
415 }
416 } else {
417 if ( worstP < 0 || nle > worstD ) {
418 worstP = i;
419 worstD = nle;
420 }
421 }
422 }
424 return true;
425 }
427 return AttemptSimplify(data, treshhold, res, worstP);
428 }
431 // fit a polyline to a bezier patch, return true is treshhold not exceeded (ie: you can continue)
432 // version that uses tables from the previous iteration, to minimize amount of work done
433 bool Path::AttemptSimplify (fitting_tables &data,double treshhold, PathDescrCubicTo & res,int &worstP)
434 {
435 NR::Point start,end;
436 // pour une coordonnee
437 NR::Point cp1, cp2;
439 worstP = 1;
440 if (pts.size() == 2) {
441 return true;
442 }
444 start[0] = data.Xk[0];
445 start[1] = data.Yk[0];
446 cp1[0] = data.Xk[1];
447 cp1[1] = data.Yk[1];
448 end[0] = data.Xk[data.nbPt - 1];
449 end[1] = data.Yk[data.nbPt - 1];
450 cp2 = cp1;
452 if (pts.size() == 3) {
453 // start -> cp1 -> end
454 res.start = cp1 - start;
455 res.end = end - cp1;
456 worstP = 1;
457 return true;
458 }
460 if ( FitCubic(start, res, data.Xk, data.Yk, data.Qk, data.tk, data.nbPt) ) {
461 cp1 = start + res.start / 3;
462 cp2 = end - res.end / 3;
463 } else {
464 // aie, non-inversible
465 double worstD = 0;
466 worstP = -1;
467 for (int i = 1; i < data.nbPt; i++) {
468 NR::Point nPt;
469 nPt[NR::X] = data.Xk[i];
470 nPt[NR::Y] = data.Yk[i];
471 double nle = DistanceToCubic(start, res, nPt);
472 if ( data.fk[i] ) {
473 // forced points are favored for splitting the recursion; we do this by increasing their distance
474 if ( worstP < 0 || 2 * nle > worstD ) {
475 worstP = i;
476 worstD = 2 * nle;
477 }
478 } else {
479 if ( worstP < 0 || nle > worstD ) {
480 worstP = i;
481 worstD = nle;
482 }
483 }
484 }
485 return false;
486 }
488 // calcul du delta= pondere par les longueurs des segments
489 double delta = 0;
490 {
491 double worstD = 0;
492 worstP = -1;
493 NR::Point prevAppP;
494 NR::Point prevP;
495 double prevDist;
496 prevP[NR::X] = data.Xk[0];
497 prevP[NR::Y] = data.Yk[0];
498 prevAppP = prevP; // le premier seulement
499 prevDist = 0;
500 #ifdef with_splotch_killer
501 if ( data.nbPt <= 20 ) {
502 for (int i = 1; i < data.nbPt - 1; i++) {
503 NR::Point curAppP;
504 NR::Point curP;
505 double curDist;
506 NR::Point midAppP;
507 NR::Point midP;
508 double midDist;
510 curAppP[NR::X] = N13(data.tk[i]) * cp1[NR::X] +
511 N23(data.tk[i]) * cp2[NR::X] +
512 N03(data.tk[i]) * data.Xk[0] +
513 N33(data.tk[i]) * data.Xk[data.nbPt - 1];
515 curAppP[NR::Y] = N13(data.tk[i]) * cp1[NR::Y] +
516 N23(data.tk[i]) * cp2[NR::Y] +
517 N03(data.tk[i]) * data.Yk[0] +
518 N33(data.tk[i]) * data.Yk[data.nbPt - 1];
520 curP[NR::X] = data.Xk[i];
521 curP[NR::Y] = data.Yk[i];
522 double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]);
524 midAppP[NR::X] = N13(mtk) * cp1[NR::X] +
525 N23(mtk) * cp2[NR::X] +
526 N03(mtk) * data.Xk[0] +
527 N33(mtk) * data.Xk[data.nbPt - 1];
529 midAppP[NR::Y] = N13(mtk) * cp1[NR::Y] +
530 N23(mtk) * cp2[NR::Y] +
531 N03(mtk) * data.Yk[0] +
532 N33(mtk) * data.Yk[data.nbPt - 1];
534 midP = 0.5 * (curP + prevP);
536 NR::Point diff = curAppP - curP;
537 curDist = dot(diff, diff);
538 diff = midAppP - midP;
539 midDist = dot(diff, diff);
541 delta += 0.3333 * (curDist + prevDist + midDist) * data.lk[i];
542 if ( curDist > worstD ) {
543 worstD = curDist;
544 worstP = i;
545 } else if ( data.fk[i] && 2 * curDist > worstD ) {
546 worstD = 2*curDist;
547 worstP = i;
548 }
549 prevP = curP;
550 prevAppP = curAppP;
551 prevDist = curDist;
552 }
553 delta /= data.totLen;
555 } else {
556 #endif
557 for (int i = 1; i < data.nbPt - 1; i++) {
558 NR::Point curAppP;
559 NR::Point curP;
560 double curDist;
562 curAppP[NR::X] = N13(data.tk[i]) * cp1[NR::X] +
563 N23(data.tk[i]) * cp2[NR::X] +
564 N03(data.tk[i]) * data.Xk[0] +
565 N33(data.tk[i]) * data.Xk[data.nbPt - 1];
567 curAppP[NR::Y] = N13(data.tk[i]) * cp1[NR::Y] +
568 N23(data.tk[i]) * cp2[NR::Y] +
569 N03(data.tk[i]) * data.Yk[0] +
570 N33(data.tk[i]) * data.Yk[data.nbPt - 1];
572 curP[NR::X] = data.Xk[i];
573 curP[NR::Y] = data.Yk[i];
575 NR::Point diff = curAppP-curP;
576 curDist = dot(diff, diff);
577 delta += curDist;
579 if ( curDist > worstD ) {
580 worstD = curDist;
581 worstP = i;
582 } else if ( data.fk[i] && 2 * curDist > worstD ) {
583 worstD = 2*curDist;
584 worstP = i;
585 }
586 prevP = curP;
587 prevAppP = curAppP;
588 prevDist = curDist;
589 }
590 #ifdef with_splotch_killer
591 }
592 #endif
593 }
595 if (delta < treshhold * treshhold) {
596 // premier jet
598 // Refine a little.
599 for (int i = 1; i < data.nbPt - 1; i++) {
600 NR::Point pt(data.Xk[i], data.Yk[i]);
601 data.tk[i] = RaffineTk(pt, start, cp1, cp2, end, data.tk[i]);
602 if (data.tk[i] < data.tk[i - 1]) {
603 // Force tk to be monotonic non-decreasing.
604 data.tk[i] = data.tk[i - 1];
605 }
606 }
608 if ( FitCubic(start, res, data.Xk, data.Yk, data.Qk, data.tk, data.nbPt) == false) {
609 // ca devrait jamais arriver, mais bon
610 res.start = 3.0 * (cp1 - start);
611 res.end = 3.0 * (end - cp2 );
612 return true;
613 }
615 double ndelta = 0;
616 {
617 double worstD = 0;
618 worstP = -1;
619 NR::Point prevAppP;
620 NR::Point prevP(data.Xk[0], data.Yk[0]);
621 double prevDist = 0;
622 prevAppP = prevP; // le premier seulement
623 #ifdef with_splotch_killer
624 if ( data.nbPt <= 20 ) {
625 for (int i = 1; i < data.nbPt - 1; i++) {
626 NR::Point curAppP;
627 NR::Point curP;
628 double curDist;
629 NR::Point midAppP;
630 NR::Point midP;
631 double midDist;
633 curAppP[NR::X] = N13(data.tk[i]) * cp1[NR::X] +
634 N23(data.tk[i]) * cp2[NR::X] +
635 N03(data.tk[i]) * data.Xk[0] +
636 N33(data.tk[i]) * data.Xk[data.nbPt - 1];
638 curAppP[NR::Y] = N13(data.tk[i]) * cp1[NR::Y] +
639 N23(data.tk[i]) * cp2[NR::Y] +
640 N03(data.tk[i]) * data.Yk[0] +
641 N33(data.tk[i]) * data.Yk[data.nbPt - 1];
643 curP[NR::X] = data.Xk[i];
644 curP[NR::Y] = data.Yk[i];
645 double mtk = 0.5 * (data.tk[i] + data.tk[i - 1]);
647 midAppP[NR::X] = N13(mtk) * cp1[NR::X] +
648 N23(mtk) * cp2[NR::X] +
649 N03(mtk) * data.Xk[0] +
650 N33(mtk) * data.Xk[data.nbPt - 1];
652 midAppP[NR::Y] = N13(mtk) * cp1[NR::Y] +
653 N23(mtk) * cp2[NR::Y] +
654 N03(mtk) * data.Yk[0] +
655 N33(mtk) * data.Yk[data.nbPt - 1];
657 midP = 0.5 * (curP + prevP);
659 NR::Point diff = curAppP - curP;
660 curDist = dot(diff, diff);
662 diff = midAppP - midP;
663 midDist = dot(diff, diff);
665 ndelta += 0.3333 * (curDist + prevDist + midDist) * data.lk[i];
667 if ( curDist > worstD ) {
668 worstD = curDist;
669 worstP = i;
670 } else if ( data.fk[i] && 2 * curDist > worstD ) {
671 worstD = 2*curDist;
672 worstP = i;
673 }
675 prevP = curP;
676 prevAppP = curAppP;
677 prevDist = curDist;
678 }
679 ndelta /= data.totLen;
680 } else {
681 #endif
682 for (int i = 1; i < data.nbPt - 1; i++) {
683 NR::Point curAppP;
684 NR::Point curP;
685 double curDist;
687 curAppP[NR::X] = N13(data.tk[i]) * cp1[NR::X] +
688 N23(data.tk[i]) * cp2[NR::X] +
689 N03(data.tk[i]) * data.Xk[0] +
690 N33(data.tk[i]) * data.Xk[data.nbPt - 1];
692 curAppP[NR::Y] = N13(data.tk[i]) * cp1[NR::Y] +
693 N23(data.tk[i]) * cp2[1] +
694 N03(data.tk[i]) * data.Yk[0] +
695 N33(data.tk[i]) * data.Yk[data.nbPt - 1];
697 curP[NR::X] = data.Xk[i];
698 curP[NR::Y] = data.Yk[i];
700 NR::Point diff = curAppP - curP;
701 curDist = dot(diff, diff);
703 ndelta += curDist;
705 if ( curDist > worstD ) {
706 worstD = curDist;
707 worstP = i;
708 } else if ( data.fk[i] && 2 * curDist > worstD ) {
709 worstD = 2 * curDist;
710 worstP = i;
711 }
712 prevP = curP;
713 prevAppP = curAppP;
714 prevDist = curDist;
715 }
716 #ifdef with_splotch_killer
717 }
718 #endif
719 }
721 if (ndelta < delta + 0.00001) {
722 return true;
723 } else {
724 // nothing better to do
725 res.start = 3.0 * (cp1 - start);
726 res.end = 3.0 * (end - cp2 );
727 }
729 return true;
730 }
732 return false;
733 }
736 bool Path::AttemptSimplify(int off, int N, double treshhold, PathDescrCubicTo &res,int &worstP)
737 {
738 NR::Point start;
739 NR::Point end;
741 // pour une coordonnee
742 double *Xk; // la coordonnee traitee (x puis y)
743 double *Yk; // la coordonnee traitee (x puis y)
744 double *lk; // les longueurs de chaque segment
745 double *tk; // les tk
746 double *Qk; // les Qk
747 char *fk; // si point force
749 NR::Point cp1;
750 NR::Point cp2;
752 if (N == 2) {
753 worstP = 1;
754 return true;
755 }
757 start = pts[off].p;
758 cp1 = pts[off + 1].p;
759 end = pts[off + N - 1].p;
761 res.p = end;
762 res.start[0] = res.start[1] = 0;
763 res.end[0] = res.end[1] = 0;
764 if (N == 3) {
765 // start -> cp1 -> end
766 res.start = cp1 - start;
767 res.end = end - cp1;
768 worstP = 1;
769 return true;
770 }
772 // Totally inefficient, allocates & deallocates all the time.
773 tk = (double *) g_malloc(N * sizeof(double));
774 Qk = (double *) g_malloc(N * sizeof(double));
775 Xk = (double *) g_malloc(N * sizeof(double));
776 Yk = (double *) g_malloc(N * sizeof(double));
777 lk = (double *) g_malloc(N * sizeof(double));
778 fk = (char *) g_malloc(N * sizeof(char));
780 // chord length method
781 tk[0] = 0.0;
782 lk[0] = 0.0;
783 {
784 NR::Point prevP = start;
785 for (int i = 1; i < N; i++) {
786 Xk[i] = pts[off + i].p[NR::X];
787 Yk[i] = pts[off + i].p[NR::Y];
789 if ( pts[off + i].isMoveTo == polyline_forced ) {
790 fk[i] = 0x01;
791 } else {
792 fk[i] = 0;
793 }
795 NR::Point diff(Xk[i] - prevP[NR::X], Yk[i] - prevP[1]);
796 prevP[0] = Xk[i];
797 prevP[1] = Yk[i];
798 lk[i] = NR::L2(diff);
799 tk[i] = tk[i - 1] + lk[i];
800 }
801 }
803 if (tk[N - 1] < 0.00001) {
804 // longueur nulle
805 res.start[0] = res.start[1] = 0;
806 res.end[0] = res.end[1] = 0;
807 double worstD = 0;
808 worstP = -1;
809 for (int i = 1; i < N; i++) {
810 NR::Point nPt;
811 bool isForced = fk[i];
812 nPt[0] = Xk[i];
813 nPt[1] = Yk[i];
815 double nle = DistanceToCubic(start, res, nPt);
816 if ( isForced ) {
817 // forced points are favored for splitting the recursion; we do this by increasing their distance
818 if ( worstP < 0 || 2 * nle > worstD ) {
819 worstP = i;
820 worstD = 2 * nle;
821 }
822 } else {
823 if ( worstP < 0 || nle > worstD ) {
824 worstP = i;
825 worstD = nle;
826 }
827 }
828 }
830 g_free(tk);
831 g_free(Qk);
832 g_free(Xk);
833 g_free(Yk);
834 g_free(fk);
835 g_free(lk);
837 return false;
838 }
840 double totLen = tk[N - 1];
841 for (int i = 1; i < N - 1; i++) {
842 tk[i] /= totLen;
843 }
845 res.p = end;
846 if ( FitCubic(start, res, Xk, Yk, Qk, tk, N) ) {
847 cp1 = start + res.start / 3;
848 cp2 = end + res.end / 3;
849 } else {
850 // aie, non-inversible
851 res.start[0] = res.start[1] = 0;
852 res.end[0] = res.end[1] = 0;
853 double worstD = 0;
854 worstP = -1;
855 for (int i = 1; i < N; i++) {
856 NR::Point nPt(Xk[i], Yk[i]);
857 bool isForced = fk[i];
858 double nle = DistanceToCubic(start, res, nPt);
859 if ( isForced ) {
860 // forced points are favored for splitting the recursion; we do this by increasing their distance
861 if ( worstP < 0 || 2 * nle > worstD ) {
862 worstP = i;
863 worstD = 2 * nle;
864 }
865 } else {
866 if ( worstP < 0 || nle > worstD ) {
867 worstP = i;
868 worstD = nle;
869 }
870 }
871 }
873 g_free(tk);
874 g_free(Qk);
875 g_free(Xk);
876 g_free(Yk);
877 g_free(fk);
878 g_free(lk);
879 return false;
880 }
882 // calcul du delta= pondere par les longueurs des segments
883 double delta = 0;
884 {
885 double worstD = 0;
886 worstP = -1;
887 NR::Point prevAppP;
888 NR::Point prevP;
889 double prevDist;
890 prevP[0] = Xk[0];
891 prevP[1] = Yk[0];
892 prevAppP = prevP; // le premier seulement
893 prevDist = 0;
894 #ifdef with_splotch_killer
895 if ( N <= 20 ) {
896 for (int i = 1; i < N - 1; i++)
897 {
898 NR::Point curAppP;
899 NR::Point curP;
900 double curDist;
901 NR::Point midAppP;
902 NR::Point midP;
903 double midDist;
905 curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1];
906 curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1];
907 curP[0] = Xk[i];
908 curP[1] = Yk[i];
909 midAppP[0] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[0] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[0] + N03 (0.5*(tk[i]+tk[i-1])) * Xk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Xk[N - 1];
910 midAppP[1] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[1] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[1] + N03 (0.5*(tk[i]+tk[i-1])) * Yk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Yk[N - 1];
911 midP=0.5*(curP+prevP);
913 NR::Point diff;
914 diff = curAppP-curP;
915 curDist = dot(diff,diff);
917 diff = midAppP-midP;
918 midDist = dot(diff,diff);
920 delta+=0.3333*(curDist+prevDist+midDist)/**lk[i]*/;
922 if ( curDist > worstD ) {
923 worstD = curDist;
924 worstP = i;
925 } else if ( fk[i] && 2*curDist > worstD ) {
926 worstD = 2*curDist;
927 worstP = i;
928 }
929 prevP = curP;
930 prevAppP = curAppP;
931 prevDist = curDist;
932 }
933 delta/=totLen;
934 } else {
935 #endif
936 for (int i = 1; i < N - 1; i++)
937 {
938 NR::Point curAppP;
939 NR::Point curP;
940 double curDist;
942 curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1];
943 curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1];
944 curP[0] = Xk[i];
945 curP[1] = Yk[i];
947 NR::Point diff;
948 diff = curAppP-curP;
949 curDist = dot(diff,diff);
950 delta += curDist;
951 if ( curDist > worstD ) {
952 worstD = curDist;
953 worstP = i;
954 } else if ( fk[i] && 2*curDist > worstD ) {
955 worstD = 2*curDist;
956 worstP = i;
957 }
958 prevP = curP;
959 prevAppP = curAppP;
960 prevDist = curDist;
961 }
962 #ifdef with_splotch_killer
963 }
964 #endif
965 }
967 if (delta < treshhold * treshhold)
968 {
969 // premier jet
970 res.start = 3.0 * (cp1 - start);
971 res.end = -3.0 * (cp2 - end);
972 res.p = end;
974 // Refine a little.
975 for (int i = 1; i < N - 1; i++)
976 {
977 NR::Point
978 pt;
979 pt[0] = Xk[i];
980 pt[1] = Yk[i];
981 tk[i] = RaffineTk (pt, start, cp1, cp2, end, tk[i]);
982 if (tk[i] < tk[i - 1])
983 {
984 // Force tk to be monotonic non-decreasing.
985 tk[i] = tk[i - 1];
986 }
987 }
989 if ( FitCubic(start,res,Xk,Yk,Qk,tk,N) ) {
990 } else {
991 // ca devrait jamais arriver, mais bon
992 res.start = 3.0 * (cp1 - start);
993 res.end = -3.0 * (cp2 - end);
994 g_free(tk);
995 g_free(Qk);
996 g_free(Xk);
997 g_free(Yk);
998 g_free(fk);
999 g_free(lk);
1000 return true;
1001 }
1002 double ndelta = 0;
1003 {
1004 double worstD = 0;
1005 worstP = -1;
1006 NR::Point prevAppP;
1007 NR::Point prevP;
1008 double prevDist;
1009 prevP[0] = Xk[0];
1010 prevP[1] = Yk[0];
1011 prevAppP = prevP; // le premier seulement
1012 prevDist = 0;
1013 #ifdef with_splotch_killer
1014 if ( N <= 20 ) {
1015 for (int i = 1; i < N - 1; i++)
1016 {
1017 NR::Point curAppP;
1018 NR::Point curP;
1019 double curDist;
1020 NR::Point midAppP;
1021 NR::Point midP;
1022 double midDist;
1024 curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1];
1025 curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1];
1026 curP[0] = Xk[i];
1027 curP[1] = Yk[i];
1028 midAppP[0] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[0] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[0] + N03 (0.5*(tk[i]+tk[i-1])) * Xk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Xk[N - 1];
1029 midAppP[1] = N13 (0.5*(tk[i]+tk[i-1])) * cp1[1] + N23 (0.5*(tk[i]+tk[i-1])) * cp2[1] + N03 (0.5*(tk[i]+tk[i-1])) * Yk[0] + N33 (0.5*(tk[i]+tk[i-1])) * Yk[N - 1];
1030 midP = 0.5*(curP+prevP);
1032 NR::Point diff;
1033 diff = curAppP-curP;
1034 curDist = dot(diff,diff);
1035 diff = midAppP-midP;
1036 midDist = dot(diff,diff);
1038 ndelta+=0.3333*(curDist+prevDist+midDist)/**lk[i]*/;
1040 if ( curDist > worstD ) {
1041 worstD = curDist;
1042 worstP = i;
1043 } else if ( fk[i] && 2*curDist > worstD ) {
1044 worstD = 2*curDist;
1045 worstP = i;
1046 }
1047 prevP = curP;
1048 prevAppP = curAppP;
1049 prevDist = curDist;
1050 }
1051 ndelta /= totLen;
1052 } else {
1053 #endif
1054 for (int i = 1; i < N - 1; i++)
1055 {
1056 NR::Point curAppP;
1057 NR::Point curP;
1058 double curDist;
1060 curAppP[0] = N13 (tk[i]) * cp1[0] + N23 (tk[i]) * cp2[0] + N03 (tk[i]) * Xk[0] + N33 (tk[i]) * Xk[N - 1];
1061 curAppP[1] = N13 (tk[i]) * cp1[1] + N23 (tk[i]) * cp2[1] + N03 (tk[i]) * Yk[0] + N33 (tk[i]) * Yk[N - 1];
1062 curP[0]=Xk[i];
1063 curP[1]=Yk[i];
1065 NR::Point diff;
1066 diff=curAppP-curP;
1067 curDist=dot(diff,diff);
1068 ndelta+=curDist;
1070 if ( curDist > worstD ) {
1071 worstD=curDist;
1072 worstP=i;
1073 } else if ( fk[i] && 2*curDist > worstD ) {
1074 worstD=2*curDist;
1075 worstP=i;
1076 }
1077 prevP=curP;
1078 prevAppP=curAppP;
1079 prevDist=curDist;
1080 }
1081 #ifdef with_splotch_killer
1082 }
1083 #endif
1084 }
1086 g_free(tk);
1087 g_free(Qk);
1088 g_free(Xk);
1089 g_free(Yk);
1090 g_free(fk);
1091 g_free(lk);
1093 if (ndelta < delta + 0.00001)
1094 {
1095 return true;
1096 } else {
1097 // nothing better to do
1098 res.start = 3.0 * (cp1 - start);
1099 res.end = -3.0 * (cp2 - end);
1100 }
1101 return true;
1102 } else {
1103 // nothing better to do
1104 }
1106 g_free(tk);
1107 g_free(Qk);
1108 g_free(Xk);
1109 g_free(Yk);
1110 g_free(fk);
1111 g_free(lk);
1112 return false;
1113 }
1115 double Path::RaffineTk (NR::Point pt, NR::Point p0, NR::Point p1, NR::Point p2, NR::Point p3, double it)
1116 {
1117 // Refinement of the tk values.
1118 // Just one iteration of Newtow Raphson, given that we're approaching the curve anyway.
1119 // [fr: vu que de toute facon la courbe est approchC)e]
1120 double const Ax = pt[NR::X] -
1121 p0[NR::X] * N03(it) -
1122 p1[NR::X] * N13(it) -
1123 p2[NR::X] * N23(it) -
1124 p3[NR::X] * N33(it);
1126 double const Bx = (p1[NR::X] - p0[NR::X]) * N02(it) +
1127 (p2[NR::X] - p1[NR::X]) * N12(it) +
1128 (p3[NR::X] - p2[NR::X]) * N22(it);
1130 double const Cx = (p0[NR::X] - 2 * p1[NR::X] + p2[NR::X]) * N01(it) +
1131 (p3[NR::X] - 2 * p2[NR::X] + p1[NR::X]) * N11(it);
1133 double const Ay = pt[NR::Y] -
1134 p0[NR::Y] * N03(it) -
1135 p1[NR::Y] * N13(it) -
1136 p2[NR::Y] * N23(it) -
1137 p3[NR::Y] * N33(it);
1139 double const By = (p1[NR::Y] - p0[NR::Y]) * N02(it) +
1140 (p2[NR::Y] - p1[NR::Y]) * N12(it) +
1141 (p3[NR::Y] - p2[NR::Y]) * N22(it);
1143 double const Cy = (p0[NR::Y] - 2 * p1[NR::Y] + p2[NR::Y]) * N01(it) +
1144 (p3[NR::Y] - 2 * p2[NR::Y] + p1[NR::Y]) * N11(it);
1146 double const dF = -6 * (Ax * Bx + Ay * By);
1147 double const ddF = 18 * (Bx * Bx + By * By) - 12 * (Ax * Cx + Ay * Cy);
1148 if (fabs (ddF) > 0.0000001) {
1149 return it - dF / ddF;
1150 }
1152 return it;
1153 }
1155 // Variation on the fitting theme: try to merge path commands into cubic bezier patches.
1156 // The goal is to reduce the number of path commands, especially when operations on path produce
1157 // lots of small path elements; ideally you could get rid of very small segments at reduced visual cost.
1158 void Path::Coalesce(double tresh)
1159 {
1160 if ( descr_flags & descr_adding_bezier ) {
1161 CancelBezier();
1162 }
1164 if ( descr_flags & descr_doing_subpath ) {
1165 CloseSubpath();
1166 }
1168 if (descr_cmd.size() <= 2) {
1169 return;
1170 }
1172 SetBackData(false);
1173 Path* tempDest = new Path();
1174 tempDest->SetBackData(false);
1176 ConvertEvenLines(0.25*tresh);
1178 int lastP = 0;
1179 int lastAP = -1;
1180 // As the elements are stored in a separate array, it's no longer worth optimizing
1181 // the rewriting in the same array.
1182 // [[comme les elements sont stockes dans un tableau a part, plus la peine d'optimiser
1183 // la réécriture dans la meme tableau]]
1185 int lastA = descr_cmd[0]->associated;
1186 int prevA = lastA;
1187 NR::Point firstP;
1189 /* FIXME: the use of this variable probably causes a leak or two.
1190 ** It's a hack anyway, and probably only needs to be a type rather than
1191 ** a full PathDescr.
1192 */
1193 PathDescr *lastAddition = new PathDescrMoveTo(NR::Point(0, 0));
1194 bool containsForced = false;
1195 PathDescrCubicTo pending_cubic(NR::Point(0, 0), NR::Point(0, 0), NR::Point(0, 0));
1197 for (int curP = 0; curP < int(descr_cmd.size()); curP++) {
1198 int typ = descr_cmd[curP]->getType();
1199 int nextA = lastA;
1201 if (typ == descr_moveto) {
1203 if (lastAddition->flags != descr_moveto) {
1204 FlushPendingAddition(tempDest,lastAddition,pending_cubic,lastAP);
1205 }
1206 lastAddition = descr_cmd[curP];
1207 lastAP = curP;
1208 FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP);
1209 // Added automatically (too bad about multiple moveto's).
1210 // [fr: (tant pis pour les moveto multiples)]
1211 containsForced = false;
1213 PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[curP]);
1214 firstP = nData->p;
1215 lastA = descr_cmd[curP]->associated;
1216 prevA = lastA;
1217 lastP = curP;
1219 } else if (typ == descr_close) {
1220 nextA = descr_cmd[curP]->associated;
1221 if (lastAddition->flags != descr_moveto) {
1223 PathDescrCubicTo res(NR::Point(0, 0), NR::Point(0, 0), NR::Point(0, 0));
1224 int worstP = -1;
1225 if (AttemptSimplify(lastA, nextA - lastA + 1, (containsForced) ? 0.05 * tresh : tresh, res, worstP)) {
1226 lastAddition = new PathDescrCubicTo(NR::Point(0, 0),
1227 NR::Point(0, 0),
1228 NR::Point(0, 0));
1229 pending_cubic = res;
1230 lastAP = -1;
1231 }
1233 FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP);
1234 FlushPendingAddition(tempDest, descr_cmd[curP], pending_cubic, curP);
1236 } else {
1237 FlushPendingAddition(tempDest,descr_cmd[curP],pending_cubic,curP);
1238 }
1240 containsForced = false;
1241 lastAddition = new PathDescrMoveTo(NR::Point(0, 0));
1242 prevA = lastA = nextA;
1243 lastP = curP;
1244 lastAP = curP;
1246 } else if (typ == descr_forced) {
1248 nextA = descr_cmd[curP]->associated;
1249 if (lastAddition->flags != descr_moveto) {
1251 PathDescrCubicTo res(NR::Point(0, 0), NR::Point(0, 0), NR::Point(0, 0));
1252 int worstP = -1;
1253 if (AttemptSimplify(lastA, nextA - lastA + 1, 0.05 * tresh, res, worstP)) {
1254 // plus sensible parce que point force
1255 // ca passe
1256 /* (Possible translation: More sensitive because contains a forced point.) */
1257 containsForced = true;
1258 } else {
1259 // Force the addition.
1260 FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP);
1261 lastAddition = new PathDescrMoveTo(NR::Point(0, 0));
1262 prevA = lastA = nextA;
1263 lastP = curP;
1264 lastAP = curP;
1265 containsForced = false;
1266 }
1267 }
1269 } else if (typ == descr_lineto || typ == descr_cubicto || typ == descr_arcto) {
1271 nextA = descr_cmd[curP]->associated;
1272 if (lastAddition->flags != descr_moveto) {
1274 PathDescrCubicTo res(NR::Point(0, 0), NR::Point(0, 0), NR::Point(0, 0));
1275 int worstP = -1;
1276 if (AttemptSimplify(lastA, nextA - lastA + 1, tresh, res, worstP)) {
1277 lastAddition = new PathDescrCubicTo(NR::Point(0, 0),
1278 NR::Point(0, 0),
1279 NR::Point(0, 0));
1280 pending_cubic = res;
1281 lastAddition->associated = lastA;
1282 lastP = curP;
1283 lastAP = -1;
1284 } else {
1285 lastA = descr_cmd[lastP]->associated; // pourrait etre surecrit par la ligne suivante
1286 /* (possible translation: Could be overwritten by the next line.) */
1287 FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP);
1288 lastAddition = descr_cmd[curP];
1289 if ( typ == descr_cubicto ) {
1290 pending_cubic = *(dynamic_cast<PathDescrCubicTo*>(descr_cmd[curP]));
1291 }
1292 lastAP = curP;
1293 containsForced = false;
1294 }
1296 } else {
1297 lastA = prevA /*descr_cmd[curP-1]->associated */ ;
1298 lastAddition = descr_cmd[curP];
1299 if ( typ == descr_cubicto ) {
1300 pending_cubic = *(dynamic_cast<PathDescrCubicTo*>(descr_cmd[curP]));
1301 }
1302 lastAP = curP;
1303 containsForced = false;
1304 }
1305 prevA = nextA;
1307 } else if (typ == descr_bezierto) {
1309 if (lastAddition->flags != descr_moveto) {
1310 FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP);
1311 lastAddition = new PathDescrMoveTo(NR::Point(0, 0));
1312 }
1313 lastAP = -1;
1314 lastA = descr_cmd[curP]->associated;
1315 lastP = curP;
1316 PathDescrBezierTo *nBData = dynamic_cast<PathDescrBezierTo*>(descr_cmd[curP]);
1317 for (int i = 1; i <= nBData->nb; i++) {
1318 FlushPendingAddition(tempDest, descr_cmd[curP + i], pending_cubic, curP + i);
1319 }
1320 curP += nBData->nb;
1321 prevA = nextA;
1323 } else if (typ == descr_interm_bezier) {
1324 continue;
1325 } else {
1326 continue;
1327 }
1328 }
1330 if (lastAddition->flags != descr_moveto) {
1331 FlushPendingAddition(tempDest, lastAddition, pending_cubic, lastAP);
1332 }
1334 Copy(tempDest);
1335 delete tempDest;
1336 }
1339 void Path::FlushPendingAddition(Path *dest, PathDescr *lastAddition,
1340 PathDescrCubicTo &lastCubic, int lastAP)
1341 {
1342 switch (lastAddition->getType()) {
1344 case descr_moveto:
1345 if ( lastAP >= 0 ) {
1346 PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[lastAP]);
1347 dest->MoveTo(nData->p);
1348 }
1349 break;
1351 case descr_close:
1352 dest->Close();
1353 break;
1355 case descr_cubicto:
1356 dest->CubicTo(lastCubic.p, lastCubic.start, lastCubic.end);
1357 break;
1359 case descr_lineto:
1360 if ( lastAP >= 0 ) {
1361 PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[lastAP]);
1362 dest->LineTo(nData->p);
1363 }
1364 break;
1366 case descr_arcto:
1367 if ( lastAP >= 0 ) {
1368 PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[lastAP]);
1369 dest->ArcTo(nData->p, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise);
1370 }
1371 break;
1373 case descr_bezierto:
1374 if ( lastAP >= 0 ) {
1375 PathDescrBezierTo *nData = dynamic_cast<PathDescrBezierTo *>(descr_cmd[lastAP]);
1376 dest->BezierTo(nData->p);
1377 }
1378 break;
1380 case descr_interm_bezier:
1381 if ( lastAP >= 0 ) {
1382 PathDescrIntermBezierTo *nData = dynamic_cast<PathDescrIntermBezierTo*>(descr_cmd[lastAP]);
1383 dest->IntermBezierTo(nData->p);
1384 }
1385 break;
1386 }
1387 }
1389 /*
1390 Local Variables:
1391 mode:c++
1392 c-file-style:"stroustrup"
1393 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1394 indent-tabs-mode:nil
1395 fill-column:99
1396 End:
1397 */
1398 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :