Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
PathConversion.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
5 * Authors:
6 * see git history
7 * Fred
8 *
9 * Copyright (C) 2018 Authors
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include <glib.h>
14#include <2geom/bezier.h>
15#include <2geom/transforms.h>
16#include "Path.h"
17#include "Shape.h"
19
20/*
21 * path description -> polyline
22 * and Path -> Shape (the Fill() function at the bottom)
23 * nathing fancy here: take each command and append an approximation of it to the polyline
24 */
25
26void Path::ConvertWithBackData(double treshhold, bool relative)
27{
28 // are we doing a sub path? if yes, clear the flags. CloseSubPath just clears the flags
29 // it doesn't close a sub path
32 }
33
34 // set the backdata flag to true since this function will be calculating and storing backdata stuff
35 SetBackData(true);
36 // clears any pre-existing polyline approximation stuff
38
39 // nothing to approximate so return
40 if (descr_cmd.empty()) {
41 return;
42 }
43
44 Geom::Point curX; // the last point added
45 // the description to process. We start with 1 usually since the first is always a MoveTo that
46 // we handle before the loop (see below). In the case that the first is not a moveTo, We set
47 // curP to 0.
48 int curP = 1;
49 // index of the last moveto point. Useful when a close path command is encountered.
50 int lastMoveTo = -1;
51
52 // The initial moveto.
53 // if the first command is a moveTo, set that as the lastPoint (curX) otherwise add a point at
54 // the origin as a moveTo.
55 if (descr_cmd[0]->getType() == descr_moveto) {
56 curX = static_cast<PathDescrMoveTo *>(descr_cmd[0])->p;
57 } else {
58 curP = 0;
59 }
60 // tiny detail to see here is that piece (the index of the path command this point comes from) is set to 0 which
61 // may or may not be true. If there was not a MoveTo, index 0 can have other description types.
62 lastMoveTo = AddPoint(curX, 0, 0.0, true);
63
64 // Prepare the iterator which moves along forced_subdivisions as we iterate over path commands.
65 auto cuts_it = forced_subdivisions.begin();
66 std::vector<double> cuts;
67
68 // And the rest, one by one.
69 // within this loop, curP holds the current path command index, curX holds the last point added
70 for (; curP < descr_cmd.size(); curP++) {
71 Geom::Point nextX;
72
73 // Collect the list of cut times for this path command, in ascending order.
74 cuts.clear();
75 while (cuts_it != forced_subdivisions.end() && cuts_it->piece == curP) {
76 cuts.emplace_back(cuts_it->t);
77 ++cuts_it;
78 }
79
80 switch (descr_cmd[curP]->getType()) {
81 case descr_forced: {
82 // just add a forced point (at the last point added).
84 break;
85 }
86
87 case descr_moveto: {
88 auto cmd = static_cast<PathDescrMoveTo *>(descr_cmd[curP]);
89 nextX = cmd->p;
90 // add the moveTo point and also store this in lastMoveTo
91 lastMoveTo = AddPoint(nextX, curP, 0.0, true);
92 break;
93 }
94
95 case descr_close: {
96 // add the lastMoveTo point again
97 nextX = pts[lastMoveTo].p;
98 int n = AddPoint(nextX, curP, 1.0, false);
99 // we check if n > 0 because in some cases the last point has already been added so AddPoint would
100 // return -1 .. but then that last point won't get marked with closed = true .. I wonder if that would cause
101 // problems. Just to explain this say:
102 // MoveTo(0, 0); LineTo(10, 0); LineTo(10, 10); LineTo(0, 10); LineTo(0, 0); Close();
103 // LineTo(0, 0) would have already added a point at origin. So AddPoint won't add anything. But my point is that
104 // then the last point (0,0) won't get marked as closed = true which it should be.
105 // But then maybe this doesn't matter because closed variable is barely used.
106 if (n > 0) pts[n].closed = true;
107 break;
108 }
109
110 case descr_lineto: {
111 auto cmd = static_cast<PathDescrLineTo *>(descr_cmd[curP]);
112 nextX = cmd->p;
113 AddPoint(nextX, curP, 1.0, false);
114 break;
115 }
116
117 case descr_cubicto: {
118 auto cmd = static_cast<PathDescrCubicTo *>(descr_cmd[curP]);
119 nextX = cmd->p;
120 // RecCubicTo will see if threshold is fine with approximating this cubic bezier with
121 // a line segment through the start and end points. If no, it'd split the cubic at its
122 // center point and recursively call itself on the left and right side. The center point
123 // gets added in the points list too.
124 RecCubicTo(curX, cmd->start, nextX, cmd->end, treshhold, 8, 0, 1, curP, relative, cuts);
125 // RecCubicTo adds any points inside the cubic and last one is added here
126 AddPoint(nextX, curP, 1.0, false);
127 break;
128 }
129
130 case descr_arcto: {
131 auto cmd = static_cast<PathDescrArcTo *>(descr_cmd[curP]);
132 nextX = cmd->p;
133 // Similar to RecCubicTo, just for Arcs
134 DoArc(curX, nextX, cmd->rx, cmd->ry, cmd->angle, cmd->large, cmd->clockwise, treshhold, curP, relative, cuts);
135 AddPoint(nextX, curP, 1.0, false);
136 break;
137 }
138 }
139 curX = nextX;
140 }
141
142 assert(cuts_it == forced_subdivisions.end());
143}
144
145void Path::Convert(double treshhold)
146{
148 CloseSubpath();
149 }
150
151 SetBackData(false);
152 ResetPoints();
153 if ( descr_cmd.empty() ) {
154 return;
155 }
156
157 Geom::Point curX;
158 int curP = 1;
159 int lastMoveTo = 0;
160
161 // le moveto
162 {
163 int const firstTyp = descr_cmd[0]->getType();
164 if ( firstTyp == descr_moveto ) {
165 curX = dynamic_cast<PathDescrMoveTo *>(descr_cmd[0])->p;
166 } else {
167 curP = 0;
168 curX[0] = curX[1] = 0;
169 }
170 lastMoveTo = AddPoint(curX, true);
171 }
172 descr_cmd[0]->associated = lastMoveTo;
173
174 // et le reste, 1 par 1
175 while ( curP < int(descr_cmd.size()) ) {
176
177 int const nType = descr_cmd[curP]->getType();
178 Geom::Point nextX;
179
180 switch (nType) {
181 case descr_forced: {
182 descr_cmd[curP]->associated = AddForcedPoint();
183 curP++;
184 break;
185 }
186
187 case descr_moveto: {
188 PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[curP]);
189 nextX = nData->p;
190 lastMoveTo = AddPoint(nextX, true);
191 descr_cmd[curP]->associated = lastMoveTo;
192
193 // et on avance
194 curP++;
195 break;
196 }
197
198 case descr_close: {
199 nextX = pts[lastMoveTo].p;
200 descr_cmd[curP]->associated = AddPoint(nextX, false);
201 if ( descr_cmd[curP]->associated < 0 ) {
202 if ( curP == 0 ) {
203 descr_cmd[curP]->associated = 0;
204 } else {
205 descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
206 }
207 }
208 if ( descr_cmd[curP]->associated > 0 ) {
209 pts[descr_cmd[curP]->associated].closed = true;
210 }
211 curP++;
212 break;
213 }
214
215 case descr_lineto: {
216 PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[curP]);
217 nextX = nData->p;
218 descr_cmd[curP]->associated = AddPoint(nextX, false);
219 if ( descr_cmd[curP]->associated < 0 ) {
220 if ( curP == 0 ) {
221 descr_cmd[curP]->associated = 0;
222 } else {
223 descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
224 }
225 }
226 // et on avance
227 curP++;
228 break;
229 }
230
231 case descr_cubicto: {
232 PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[curP]);
233 nextX = nData->p;
234 RecCubicTo(curX, nData->start, nextX, nData->end, treshhold, 8);
235 descr_cmd[curP]->associated = AddPoint(nextX,false);
236 if ( descr_cmd[curP]->associated < 0 ) {
237 if ( curP == 0 ) {
238 descr_cmd[curP]->associated = 0;
239 } else {
240 descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
241 }
242 }
243 // et on avance
244 curP++;
245 break;
246 }
247
248 case descr_arcto: {
249 PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[curP]);
250 nextX = nData->p;
251 DoArc(curX, nextX, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise, treshhold);
252 descr_cmd[curP]->associated = AddPoint(nextX, false);
253 if ( descr_cmd[curP]->associated < 0 ) {
254 if ( curP == 0 ) {
255 descr_cmd[curP]->associated = 0;
256 } else {
257 descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
258 }
259 }
260 // et on avance
261 curP++;
262 break;
263 }
264 }
265
266 curX = nextX;
267 }
268}
269
270void Path::ConvertEvenLines(double treshhold)
271{
273 CloseSubpath();
274 }
275
276 SetBackData(false);
277 ResetPoints();
278 if ( descr_cmd.empty() ) {
279 return;
280 }
281
282 Geom::Point curX;
283 int curP = 1;
284 int lastMoveTo = 0;
285
286 // le moveto
287 {
288 int const firstTyp = descr_cmd[0]->getType();
289 if ( firstTyp == descr_moveto ) {
290 curX = dynamic_cast<PathDescrMoveTo *>(descr_cmd[0])->p;
291 } else {
292 curP = 0;
293 curX[0] = curX[1] = 0;
294 }
295 lastMoveTo = AddPoint(curX, true);
296 }
297 descr_cmd[0]->associated = lastMoveTo;
298
299 // et le reste, 1 par 1
300 while ( curP < int(descr_cmd.size()) ) {
301
302 int const nType = descr_cmd[curP]->getType();
303 Geom::Point nextX;
304
305 switch (nType) {
306 case descr_forced: {
307 descr_cmd[curP]->associated = AddForcedPoint();
308 curP++;
309 break;
310 }
311
312 case descr_moveto: {
313 PathDescrMoveTo* nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[curP]);
314 nextX = nData->p;
315 lastMoveTo = AddPoint(nextX,true);
316 descr_cmd[curP]->associated = lastMoveTo;
317
318 // et on avance
319 curP++;
320 break;
321 }
322
323 case descr_close: {
324 nextX = pts[lastMoveTo].p;
325 {
326 Geom::Point nexcur;
327 nexcur = nextX - curX;
328 const double segL = Geom::L2(nexcur);
329 if ( (segL > treshhold) && (treshhold > 0) ) {
330 for (double i = treshhold; i < segL; i += treshhold) {
331 Geom::Point nX;
332 nX = (segL - i) * curX + i * nextX;
333 nX /= segL;
334 AddPoint(nX);
335 }
336 }
337 }
338
339 descr_cmd[curP]->associated = AddPoint(nextX,false);
340 if ( descr_cmd[curP]->associated < 0 ) {
341 if ( curP == 0 ) {
342 descr_cmd[curP]->associated = 0;
343 } else {
344 descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
345 }
346 }
347 if ( descr_cmd[curP]->associated > 0 ) {
348 pts[descr_cmd[curP]->associated].closed = true;
349 }
350 curP++;
351 break;
352 }
353
354 case descr_lineto: {
355 PathDescrLineTo* nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[curP]);
356 nextX = nData->p;
357 Geom::Point nexcur = nextX - curX;
358 const double segL = L2(nexcur);
359 if ( (segL > treshhold) && (treshhold > 0)) {
360 for (double i = treshhold; i < segL; i += treshhold) {
361 Geom::Point nX = ((segL - i) * curX + i * nextX) / segL;
362 AddPoint(nX);
363 }
364 }
365
366 descr_cmd[curP]->associated = AddPoint(nextX,false);
367 if ( descr_cmd[curP]->associated < 0 ) {
368 if ( curP == 0 ) {
369 descr_cmd[curP]->associated = 0;
370 } else {
371 descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
372 }
373 }
374 // et on avance
375 curP++;
376 break;
377 }
378
379 case descr_cubicto: {
380 PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[curP]);
381 nextX = nData->p;
382 RecCubicTo(curX, nData->start, nextX, nData->end, treshhold, 8, 4 * treshhold);
383 descr_cmd[curP]->associated = AddPoint(nextX, false);
384 if ( descr_cmd[curP]->associated < 0 ) {
385 if ( curP == 0 ) {
386 descr_cmd[curP]->associated = 0;
387 } else {
388 descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
389 }
390 }
391 // et on avance
392 curP++;
393 break;
394 }
395
396 case descr_arcto: {
397 PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[curP]);
398 nextX = nData->p;
399 DoArc(curX, nextX, nData->rx, nData->ry, nData->angle, nData->large, nData->clockwise, treshhold);
400 descr_cmd[curP]->associated =AddPoint(nextX, false);
401 if ( descr_cmd[curP]->associated < 0 ) {
402 if ( curP == 0 ) {
403 descr_cmd[curP]->associated = 0;
404 } else {
405 descr_cmd[curP]->associated = descr_cmd[curP - 1]->associated;
406 }
407 }
408
409 // et on avance
410 curP++;
411 break;
412 }
413 }
414 if ( Geom::LInfty(curX - nextX) > 0.00001 ) {
415 curX = nextX;
416 }
417 }
418}
419
420const Geom::Point Path::PrevPoint(int i) const
421{
422 /* TODO: I suspect this should assert `(unsigned) i < descr_nb'. We can probably change
423 the argument to unsigned. descr_nb should probably be changed to unsigned too. */
424 g_assert( i >= 0 );
425 switch ( descr_cmd[i]->getType() ) {
426 case descr_moveto: {
427 PathDescrMoveTo *nData = dynamic_cast<PathDescrMoveTo *>(descr_cmd[i]);
428 return nData->p;
429 }
430 case descr_lineto: {
431 PathDescrLineTo *nData = dynamic_cast<PathDescrLineTo *>(descr_cmd[i]);
432 return nData->p;
433 }
434 case descr_arcto: {
435 PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]);
436 return nData->p;
437 }
438 case descr_cubicto: {
439 PathDescrCubicTo *nData = dynamic_cast<PathDescrCubicTo *>(descr_cmd[i]);
440 return nData->p;
441 }
442 case descr_close:
443 case descr_forced:
444 return PrevPoint(i - 1);
445 default:
446 g_assert_not_reached();
447 return Geom::Point(0, 0);
448 }
449}
450
451// idem for cubic bezier patch
452void Path::CubicTangent(double t, Geom::Point &oPt, const Geom::Point &iS, const Geom::Point &isD,
453 const Geom::Point &iE, const Geom::Point &ieD)
454{
455 Geom::Point const ax = ieD - 2 * iE + 2 * iS + isD;
456 Geom::Point const bx = 3 * iE - ieD - 2 * isD - 3 * iS;
457 Geom::Point const cx = isD;
458
459 oPt = 3 * t * t * ax + 2 * t * bx + cx;
460}
461
462// extract interesting info of a SVG arc description
463static void ArcAnglesAndCenter(Geom::Point const &iS, Geom::Point const &iE,
464 double rx, double ry, double angle,
465 bool large, bool wise,
466 double &sang, double &eang, Geom::Point &dr);
467
468void Path::ArcAngles(const Geom::Point &iS, const Geom::Point &iE,
469 double rx, double ry, double angle, bool large, bool wise, double &sang, double &eang)
470{
471 Geom::Point dr;
472 ArcAnglesAndCenter(iS, iE, rx, ry, angle, large, wise, sang, eang, dr);
473}
474
475/* N.B. If iS == iE then sang,eang,dr each become NaN. Probably a bug. */
476static void ArcAnglesAndCenter(Geom::Point const &iS, Geom::Point const &iE,
477 double rx, double ry, double angle,
478 bool large, bool wise,
479 double &sang, double &eang, Geom::Point &dr)
480{
481 Geom::Point se = iE - iS;
482 Geom::Point ca(cos(angle), sin(angle));
483 Geom::Point cse(dot(ca, se), cross(ca, se));
484 cse[0] /= rx;
485 cse[1] /= ry;
486 double const lensq = dot(cse,cse);
487 Geom::Point csd = ( ( lensq < 4
488 ? sqrt( 1/lensq - .25 )
489 : 0.0 )
490 * cse.ccw() );
491
492 Geom::Point ra = -csd - 0.5 * cse;
493 if ( ra[0] <= -1 ) {
494 sang = M_PI;
495 } else if ( ra[0] >= 1 ) {
496 sang = 0;
497 } else {
498 sang = acos(ra[0]);
499 if ( ra[1] < 0 ) {
500 sang = 2 * M_PI - sang;
501 }
502 }
503
504 ra = -csd + 0.5 * cse;
505 if ( ra[0] <= -1 ) {
506 eang = M_PI;
507 } else if ( ra[0] >= 1 ) {
508 eang = 0;
509 } else {
510 eang = acos(ra[0]);
511 if ( ra[1] < 0 ) {
512 eang = 2 * M_PI - eang;
513 }
514 }
515
516 csd[0] *= rx;
517 csd[1] *= ry;
518 ca[1] = -ca[1]; // because it's the inverse rotation
519
520 dr[0] = dot(ca, csd);
521 dr[1] = cross(ca, csd);
522
523 ca[1] = -ca[1];
524
525 if ( wise ) {
526
527 if (large) {
528 dr = -dr;
529 double swap = eang;
530 eang = sang;
531 sang = swap;
532 eang += M_PI;
533 sang += M_PI;
534 if ( eang >= 2*M_PI ) {
535 eang -= 2*M_PI;
536 }
537 if ( sang >= 2*M_PI ) {
538 sang -= 2*M_PI;
539 }
540 }
541
542 } else {
543 if (!large) {
544 dr = -dr;
545 double swap = eang;
546 eang = sang;
547 sang = swap;
548 eang += M_PI;
549 sang += M_PI;
550 if ( eang >= 2*M_PI ) {
551 eang -= 2 * M_PI;
552 }
553 if ( sang >= 2*M_PI ) {
554 sang -= 2 * M_PI;
555 }
556 }
557 }
558
559 dr += 0.5 * (iS + iE);
560}
561
562
563
564void Path::DoArc(Geom::Point const &iS, Geom::Point const &iE,
565 double const rx, double const ry, double const angle,
566 bool const large, bool const wise, double const tresh)
567{
568 /* TODO: Check that our behaviour is standards-conformant if iS and iE are (much) further
569 apart than the diameter. Also check that we do the right thing for negative radius.
570 (Same for the other DoArc functions in this file.) */
571 if ( rx <= 0.0001 || ry <= 0.0001 || tresh <= 1e-8) {
572 return;
573 // We always add a lineto afterwards, so this is fine.
574 // [on ajoute toujours un lineto apres, donc c bon]
575 }
576
577 double sang;
578 double eang;
579 Geom::Point dr_temp;
580 ArcAnglesAndCenter(iS, iE, rx, ry, angle*M_PI/180.0, large, wise, sang, eang, dr_temp);
581 Geom::Point dr = dr_temp;
582 /* TODO: This isn't as good numerically as treating iS and iE as primary. E.g. consider
583 the case of low curvature (i.e. very large radius). */
584
585 Geom::Scale const ar(rx, ry);
586 Geom::Rotate cb(sang);
587 Geom::Rotate cbangle(angle*M_PI/180.0);
588 double max_ang = 2 * acos ( 1 - tresh / (fmax(rx, ry) ) );
589 max_ang = fmin (max_ang, M_PI / 2 );
590 int const num_sectors = abs(sang - eang) / max_ang + 1;
591
592 if (wise) {
593
594
595 if ( sang < eang ) {
596 sang += 2*M_PI;
597 }
598 double const incr = (eang - sang) / num_sectors;
599 Geom::Rotate const omega(incr);
600 for (double b = sang + incr ; b > eang ; b += incr) {
601 cb = omega * cb;
602 AddPoint( cb.vector() * ar * cbangle + dr );
603 }
604
605 } else {
606
607 if ( sang > eang ) {
608 sang -= 2*M_PI;
609 }
610 double const incr = (eang - sang) / num_sectors;
611 Geom::Rotate const omega(incr);
612 for (double b = sang + incr ; b < eang ; b += incr) {
613 cb = omega * cb;
614 AddPoint( cb.vector() * ar * cbangle + dr);
615 }
616 }
617}
618
619
620void Path::RecCubicTo( Geom::Point const &iS, Geom::Point const &isD,
621 Geom::Point const &iE, Geom::Point const &ieD,
622 double tresh, int lev, double maxL)
623{
624 // vector from start to end point
625 Geom::Point se = iE - iS;
626 // length of that vector
627 const double dC = Geom::L2(se);
628 // if the vector from start to end point is smaller than 0.01
629 if ( dC < 0.01 ) {
630 // we still need to get an idea of how far away the curve goes from the start to end line segment se
631 // for that, we measure lengths of isD and ieD
632 const double sC = dot(isD,isD);
633 const double eC = dot(ieD,ieD);
634 // if they are limited by tresh, great
635 if ( sC < tresh && eC < tresh ) {
636 return;
637 }
638 // otherwise proceed
639
640 } else {
641 // okay so length is greater than or equal to 0.01, we can still check the perpendicular component
642 // of the control handles and see if they are limited by tresh
643 const double sC = fabs(cross(se, isD)) / dC;
644 const double eC = fabs(cross(se, ieD)) / dC;
645 if ( sC < tresh && eC < tresh ) {
646 // presque tt droit -> attention si on nous demande de bien subdiviser les petits segments
647 // if the perpendicular is limited and a maxL is set, check if maxL is being respected, if yes
648 // return otherwise we split
649 if ( maxL > 0 && dC > maxL ) {
650 if ( lev <= 0 ) {
651 return;
652 }
653 // maths for splitting one cubic bezier into two
654 Geom::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD);
655 Geom::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD);
656
657 Geom::Point hisD = 0.5 * isD;
658 Geom::Point hieD = 0.5 * ieD;
659
660 RecCubicTo(iS, hisD, m, md, tresh, lev - 1, maxL);
661 AddPoint(m);
662 RecCubicTo(m, md, iE, hieD, tresh, lev - 1,maxL);
663 }
664 return;
665 }
666 }
667
668 if ( lev <= 0 ) {
669 return;
670 }
671
672 {
673 Geom::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD);
674 Geom::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD);
675
676 Geom::Point hisD = 0.5 * isD;
677 Geom::Point hieD = 0.5 * ieD;
678
679 RecCubicTo(iS, hisD, m, md, tresh, lev - 1, maxL);
680 AddPoint(m);
681 RecCubicTo(m, md, iE, hieD, tresh, lev - 1,maxL);
682 }
683}
684
685void Path::DoArc(Geom::Point const &iS, Geom::Point const &iE,
686 double const rx, double const ry, double const angle,
687 bool const large, bool const wise, double const thresh, int const piece,
688 bool relative, std::vector<double> const &cuts)
689{
690 /* TODO: Check that our behaviour is standards-conformant if iS and iE are (much) further
691 apart than the diameter. Also check that we do the right thing for negative radius.
692 (Same for the other DoArc functions in this file.) */
693 if (rx <= 0.0001 || ry <= 0.0001 || thresh <= 1e-8) {
694 return;
695 // We always add a lineto afterwards, so this is fine.
696 // [on ajoute toujours un lineto apres, donc c bon]
697 }
698
699 auto const angle_rad = Geom::rad_from_deg(angle);
700 auto const angle_rot = Geom::Rotate(angle_rad);
701
702 double sang;
703 double eang;
704 Geom::Point center;
705 ArcAnglesAndCenter(iS, iE, rx, ry, angle_rad, large, wise, sang, eang, center);
706 /* TODO: This isn't as good numerically as treating iS and iE as primary. E.g. consider
707 the case of low curvature (i.e. very large radius). */
708
709 if (wise) {
710 if (sang < eang) {
711 sang += 2 * M_PI;
712 }
713 } else {
714 if (sang > eang) {
715 sang -= 2 * M_PI;
716 }
717 }
718
719 // max angle is basically the maximum arc angle you can have that won't create
720 // an arc that exceeds the threshold
721 auto const max_ang = std::min(relative
722 ? std::sqrt(thresh) / 2
723 : 2 * std::acos(1 - thresh / std::max(rx, ry)),
724 M_PI / 2);
725
726 auto rot = Geom::Rotate(sang);
727
728 auto arc = [&, this] (double st, double et, bool last) {
729 // divide the arc range into sectors such that each sector
730 // is no bigger than max_ang
731 int const num_sectors = std::ceil(std::abs(eang - sang) * (et - st) / max_ang);
732
733 auto const dt = (et - st) / num_sectors;
734 auto const drot = Geom::Rotate((eang - sang) * dt);
735
736 auto t = st;
737
738 for (int i = 1; i < num_sectors + last; i++) {
739 t += dt;
740 rot *= drot;
741 AddPoint(rot.vector() * Geom::Scale(rx, ry) * angle_rot + center, piece, t);
742 }
743 };
744
745 double prev = 0.0;
746 for (auto c : cuts) {
747 arc(prev, c, true);
748 prev = c;
749 }
750 arc(prev, 1.0, false);
751}
752
753void Path::RecCubicTo(Geom::Point const &iS, Geom::Point const &isD,
754 Geom::Point const &iE, Geom::Point const &ieD,
755 double thresh, int lev, double st, double et, int piece,
756 bool relative, std::span<double> const &cuts)
757{
758 if (cuts.empty()) {
759 RecCubicTo(iS, isD, iE, ieD, thresh, lev, st, et, piece, relative);
760 return;
761 }
762
763 int const mid = cuts.size() / 2;
764 double const midt = cuts[mid];
765 double const midt_scaled = (midt - st) / (et - st);
766
767 auto split = [&] (std::array<double, 4> &left, std::array<double, 4> &right, Geom::Dim2 dim) {
768 auto const in = std::array<double, 4>{iS[dim], iS[dim] + isD[dim] / 3, iE[dim] - ieD[dim] / 3, iE[dim]};
769 Geom::casteljau_subdivision(midt_scaled, in.data(), left.data(), right.data(), 3);
770 };
771
772 std::array<double, 4> xsl, ysl, xsr, ysr;
773 split(xsl, xsr, Geom::X);
774 split(ysl, ysr, Geom::Y);
775
776 auto l = [&] (int i) { return Geom::Point(xsl[i], ysl[i]); };
777 auto r = [&] (int i) { return Geom::Point(xsr[i], ysr[i]); };
778
779 auto cutsl = cuts.subspan(0, mid);
780 auto cutsr = cuts.subspan(mid + 1);
781
782 RecCubicTo(l(0), 3 * (l(1) - l(0)), l(3), 3 * (l(3) - l(2)), thresh, lev, st, midt, piece, relative, cutsl);
783 AddPoint(r(0), piece, midt);
784 RecCubicTo(r(0), 3 * (r(1) - r(0)), r(3), 3 * (r(3) - r(2)), thresh, lev, midt, et, piece, relative, cutsr);
785}
786
787void Path::RecCubicTo(Geom::Point const &iS, Geom::Point const &isD,
788 Geom::Point const &iE, Geom::Point const &ieD,
789 double tresh, int lev, double st, double et, int piece,
790 bool relative)
791{
792 if (lev <= 0) {
793 return;
794 }
795
796 auto const se = iE - iS;
797 auto const y1 = std::abs(Geom::cross(isD, se));
798 auto const y2 = std::abs(Geom::cross(ieD, se));
799 if (relative) {
800 auto const bound = se.lengthSq() * tresh;
801 if (y1 < bound && y2 < bound) {
802 return;
803 }
804 } else if (se.lengthSq() < Geom::sqr(0.01)) {
805 if (isD.lengthSq() < tresh && ieD.lengthSq() < tresh) {
806 return;
807 }
808 } else {
809 auto const bound = se.length() * tresh;
810 if (y1 < bound && y2 < bound) {
811 return;
812 }
813 }
814
815 Geom::Point m = 0.5 * (iS + iE) + 0.125 * (isD - ieD);
816 Geom::Point md = 0.75 * (iE - iS) - 0.125 * (isD + ieD);
817 double mt = (st + et) / 2;
818
819 Geom::Point hisD = 0.5 * isD;
820 Geom::Point hieD = 0.5 * ieD;
821
822 RecCubicTo(iS, hisD, m, md, tresh, lev - 1, st, mt, piece, relative);
823 AddPoint(m, piece, mt);
824 RecCubicTo(m, md, iE, hieD, tresh, lev - 1, mt, et, piece, relative);
825}
826
827/*
828 * put a polyline in a Shape instance, for further fun
829 * pathID is the ID you want this Path instance to be associated with, for when you're going to recompose the polyline
830 * in a path description ( you need to have prepared the back data for that, of course)
831 */
832
833void Path::Fill(Shape* dest, int pathID, bool justAdd, bool closeIfNeeded, bool invert)
834{
835 if ( dest == nullptr ) {
836 return;
837 }
838
839 if ( justAdd == false ) {
840 dest->Reset(pts.size(), pts.size());
841 }
842
843 if ( pts.size() <= 1 ) {
844 return;
845 }
846
847 int first = dest->numberOfPoints();
848
849 if ( back ) {
850 dest->MakeBackData(true);
851 }
852
853 if ( invert ) {
854 if ( back ) {
855 {
856 // invert && back && !weighted
857 for (auto & pt : pts) {
858 dest->AddPoint(pt.p);
859 }
860 int lastM = 0;
861 int curP = 1;
862 int pathEnd = 0;
863 bool closed = false;
864 int lEdge = -1;
865
866 while ( curP < int(pts.size()) ) {
867 int sbp = curP;
868 int lm = lastM;
869 int prp = pathEnd;
870
871 if ( pts[sbp].isMoveTo == polyline_moveto ) {
872
873 if ( closeIfNeeded ) {
874 if ( closed && lEdge >= 0 ) {
875 dest->DisconnectStart(lEdge);
876 dest->ConnectStart(first + lastM, lEdge);
877 } else {
878 lEdge = dest->AddEdge(first + lastM, first+pathEnd);
879 if ( lEdge >= 0 ) {
880 dest->ebData[lEdge].pathID = pathID;
881 dest->ebData[lEdge].pieceID = pts[lm].piece;
882 dest->ebData[lEdge].tSt = 1.0;
883 dest->ebData[lEdge].tEn = 0.0;
884 }
885 }
886 }
887
888 lastM = curP;
889 pathEnd = curP;
890 closed = false;
891 lEdge = -1;
892
893 } else {
894
895 if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) {
896 lEdge = dest->AddEdge(first + curP, first + pathEnd);
897 if ( lEdge >= 0 ) {
898 dest->ebData[lEdge].pathID = pathID;
899 dest->ebData[lEdge].pieceID = pts[sbp].piece;
900 if ( pts[sbp].piece == pts[prp].piece ) {
901 dest->ebData[lEdge].tSt = pts[sbp].t;
902 dest->ebData[lEdge].tEn = pts[prp].t;
903 } else {
904 dest->ebData[lEdge].tSt = pts[sbp].t;
905 dest->ebData[lEdge].tEn = 0.0;
906 }
907 }
908 pathEnd = curP;
909 if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) {
910 closed = true;
911 } else {
912 closed = false;
913 }
914 }
915 }
916
917 curP++;
918 }
919
920 if ( closeIfNeeded ) {
921 if ( closed && lEdge >= 0 ) {
922 dest->DisconnectStart(lEdge);
923 dest->ConnectStart(first + lastM, lEdge);
924 } else {
925 int lm = lastM;
926 lEdge = dest->AddEdge(first + lastM, first + pathEnd);
927 if ( lEdge >= 0 ) {
928 dest->ebData[lEdge].pathID = pathID;
929 dest->ebData[lEdge].pieceID = pts[lm].piece;
930 dest->ebData[lEdge].tSt = 1.0;
931 dest->ebData[lEdge].tEn = 0.0;
932 }
933 }
934 }
935 }
936
937 } else {
938
939 {
940 // invert && !back && !weighted
941 for (auto & pt : pts) {
942 dest->AddPoint(pt.p);
943 }
944 int lastM = 0;
945 int curP = 1;
946 int pathEnd = 0;
947 bool closed = false;
948 int lEdge = -1;
949 while ( curP < int(pts.size()) ) {
950 int sbp = curP;
951 int lm = lastM;
952 int prp = pathEnd;
953 if ( pts[sbp].isMoveTo == polyline_moveto ) {
954 if ( closeIfNeeded ) {
955 if ( closed && lEdge >= 0 ) {
956 dest->DisconnectStart(lEdge);
957 dest->ConnectStart(first + lastM, lEdge);
958 } else {
959 dest->AddEdge(first + lastM, first + pathEnd);
960 }
961 }
962 lastM = curP;
963 pathEnd = curP;
964 closed = false;
965 lEdge = -1;
966 } else {
967 if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) {
968 lEdge = dest->AddEdge(first+curP, first+pathEnd);
969 pathEnd = curP;
970 if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) {
971 closed = true;
972 } else {
973 closed = false;
974 }
975 }
976 }
977 curP++;
978 }
979
980 if ( closeIfNeeded ) {
981 if ( closed && lEdge >= 0 ) {
982 dest->DisconnectStart(lEdge);
983 dest->ConnectStart(first + lastM, lEdge);
984 } else {
985 dest->AddEdge(first + lastM, first + pathEnd);
986 }
987 }
988
989 }
990 }
991
992 } else {
993
994 if ( back ) {
995 {
996 // !invert && back && !weighted
997
998 // add all points to the shape
999 for (auto & pt : pts) {
1000 dest->AddPoint(pt.p);
1001 }
1002
1003 int lastM = 0;
1004 int curP = 1;
1005 int pathEnd = 0;
1006 bool closed = false;
1007 int lEdge = -1;
1008 while ( curP < int(pts.size()) ) {
1009 int sbp = curP;
1010 int lm = lastM;
1011 int prp = pathEnd;
1012 if ( pts[sbp].isMoveTo == polyline_moveto ) {
1013 if ( closeIfNeeded ) {
1014 if ( closed && lEdge >= 0 ) {
1015 dest->DisconnectEnd(lEdge);
1016 dest->ConnectEnd(first + lastM, lEdge);
1017 } else {
1018 lEdge = dest->AddEdge(first + pathEnd, first+lastM);
1019 if ( lEdge >= 0 ) {
1020 dest->ebData[lEdge].pathID = pathID;
1021 dest->ebData[lEdge].pieceID = pts[lm].piece;
1022 dest->ebData[lEdge].tSt = 0.0;
1023 dest->ebData[lEdge].tEn = 1.0;
1024 }
1025 }
1026 }
1027 lastM = curP;
1028 pathEnd = curP;
1029 closed = false;
1030 lEdge = -1;
1031 } else {
1032 if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) {
1033 lEdge = dest->AddEdge(first + pathEnd, first + curP);
1034 dest->ebData[lEdge].pathID = pathID;
1035 dest->ebData[lEdge].pieceID = pts[sbp].piece;
1036 if ( pts[sbp].piece == pts[prp].piece ) {
1037 dest->ebData[lEdge].tSt = pts[prp].t;
1038 dest->ebData[lEdge].tEn = pts[sbp].t;
1039 } else {
1040 dest->ebData[lEdge].tSt = 0.0;
1041 dest->ebData[lEdge].tEn = pts[sbp].t;
1042 }
1043 pathEnd = curP;
1044 if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) {
1045 closed = true;
1046 } else {
1047 closed = false;
1048 }
1049 }
1050 }
1051 curP++;
1052 }
1053
1054 if ( closeIfNeeded ) {
1055 if ( closed && lEdge >= 0 ) {
1056 dest->DisconnectEnd(lEdge);
1057 dest->ConnectEnd(first + lastM, lEdge);
1058 } else {
1059 int lm = lastM;
1060 lEdge = dest->AddEdge(first + pathEnd, first + lastM);
1061 if ( lEdge >= 0 ) {
1062 dest->ebData[lEdge].pathID = pathID;
1063 dest->ebData[lEdge].pieceID = pts[lm].piece;
1064 dest->ebData[lEdge].tSt = 0.0;
1065 dest->ebData[lEdge].tEn = 1.0;
1066 }
1067 }
1068 }
1069 }
1070
1071 } else {
1072 {
1073 // !invert && !back && !weighted
1074 for (auto & pt : pts) {
1075 dest->AddPoint(pt.p);
1076 }
1077
1078 int lastM = 0;
1079 int curP = 1;
1080 int pathEnd = 0;
1081 bool closed = false;
1082 int lEdge = -1;
1083 while ( curP < int(pts.size()) ) {
1084 int sbp = curP;
1085 int lm = lastM;
1086 int prp = pathEnd;
1087 if ( pts[sbp].isMoveTo == polyline_moveto ) {
1088 if ( closeIfNeeded ) {
1089 if ( closed && lEdge >= 0 ) {
1090 dest->DisconnectEnd(lEdge);
1091 dest->ConnectEnd(first + lastM, lEdge);
1092 } else {
1093 dest->AddEdge(first + pathEnd, first + lastM);
1094 }
1095 }
1096 lastM = curP;
1097 pathEnd = curP;
1098 closed = false;
1099 lEdge = -1;
1100 } else {
1101 if ( Geom::LInfty(pts[sbp].p - pts[prp].p) >= 0.00001 ) {
1102 lEdge = dest->AddEdge(first+pathEnd, first+curP);
1103 pathEnd = curP;
1104 if ( Geom::LInfty(pts[sbp].p - pts[lm].p) < 0.00001 ) {
1105 closed = true;
1106 } else {
1107 closed = false;
1108 }
1109 }
1110 }
1111 curP++;
1112 }
1113
1114 if ( closeIfNeeded ) {
1115 if ( closed && lEdge >= 0 ) {
1116 dest->DisconnectEnd(lEdge);
1117 dest->ConnectEnd(first + lastM, lEdge);
1118 } else {
1119 dest->AddEdge(first + pathEnd, first + lastM);
1120 }
1121 }
1122
1123 }
1124 }
1125 }
1126}
1127
1128/*
1129 Local Variables:
1130 mode:c++
1131 c-file-style:"stroustrup"
1132 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1133 indent-tabs-mode:nil
1134 fill-column:99
1135 End:
1136*/
1137// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
static void ArcAnglesAndCenter(Geom::Point const &iS, Geom::Point const &iE, double rx, double ry, double angle, bool large, bool wise, double &sang, double &eang, Geom::Point &dr)
TODO: insert short description here.
@ polyline_moveto
Definition Path.h:47
TODO: insert short description here.
Bernstein-Bezier polynomial.
Two-dimensional point that doubles as a vector.
Definition point.h:66
Coord length() const
Compute the distance from origin.
Definition point.h:118
constexpr Point ccw() const
Return a point like this point but rotated -90 degrees.
Definition point.h:130
constexpr Coord lengthSq() const
Definition point.h:119
Rotation around the origin.
Definition transforms.h:187
Scaling from the origin.
Definition transforms.h:150
@ descr_doing_subpath
Definition Path.h:100
void ConvertWithBackData(double threshhold, bool relative=false)
Creates a polyline approximation of the path.
void SetBackData(bool nVal)
Sets the back variable to the value passed in and clears the polyline approximation.
Definition Path.cpp:232
void ResetPoints()
Clears the polyline approximation.
Definition Path.cpp:248
void Fill(Shape *dest, int pathID=-1, bool justAdd=false, bool closeIfNeeded=true, bool invert=false)
Fills the shape with the polyline approximation stored in this object.
int AddForcedPoint()
Adds a forced point to the polyline approximation's list of points.
Definition Path.cpp:284
std::vector< path_lineto > pts
Definition Path.h:128
void ConvertEvenLines(double treshhold)
Creates a polyline approximation of the path.
int descr_flags
Definition Path.h:105
std::vector< ForcedSubdivision > forced_subdivisions
Definition Path.h:140
bool back
Definition Path.h:131
const Geom::Point PrevPoint(const int i) const
void DoArc(Geom::Point const &iS, Geom::Point const &iE, double rx, double ry, double angle, bool large, bool wise, double tresh)
The function is quite similar to RecCubicTo.
std::vector< PathDescr * > descr_cmd
Definition Path.h:108
void CloseSubpath()
Definition Path.cpp:75
int AddPoint(Geom::Point const &iPt, bool mvto=false)
Adds a point to the polyline approximation's list of points.
Definition Path.cpp:254
void Convert(double treshhold)
Creates a polyline approximation of the path.
A class to store/manipulate directed graphs.
Definition Shape.h:65
void MakeBackData(bool nVal)
Definition Shape.cpp:169
void DisconnectStart(int b)
Definition Shape.cpp:1853
int AddEdge(int st, int en)
Definition Shape.cpp:1052
std::vector< back_data > ebData
Definition Shape.h:422
void ConnectEnd(int p, int b)
Definition Shape.cpp:1828
int numberOfPoints() const
Returns number of points.
Definition Shape.h:484
void Reset(int n=0, int m=0)
Clear all data.
Definition Shape.cpp:235
void ConnectStart(int p, int b)
Definition Shape.cpp:1802
int AddPoint(const Geom::Point x)
Definition Shape.cpp:266
void DisconnectEnd(int b)
Definition Shape.cpp:1888
double c[8][4]
Dim2
2D axis enumeration (X or Y).
Definition coord.h:48
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
T casteljau_subdivision(double t, T const *v, T *left, T *right, unsigned order)
Perform Casteljau subdivision of a Bezier polynomial.
Definition bezier.h:78
MultiDegree< n > max(MultiDegree< n > const &p, MultiDegree< n > const &q)
Returns the maximal degree appearing in the two arguments for each variables.
Definition sbasisN.h:158
void split(vector< Point > const &p, double t, vector< Point > &left, vector< Point > &right)
T sqr(const T &x)
Definition math-utils.h:57
Piecewise< SBasis > cross(Piecewise< D2< SBasis > > const &a, Piecewise< D2< SBasis > > const &b)
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
Coord LInfty(Point const &p)
STL namespace.
TODO: insert short description here.
@ descr_lineto
@ descr_arcto
@ descr_moveto
@ descr_cubicto
@ descr_close
@ descr_forced
void invert(const double v[16], double alpha[16])
Elliptical Arc path command.
Cubic Bezier path command.
A LineTo path command.
A MoveTo path command.
void dot(Cairo::RefPtr< Cairo::Context > &cr, double x, double y)
Affine transformation classes.