Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
geom-pathstroke.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/* Authors:
3 * Liam P. White
4 * Tavmjong Bah
5 * Alexander Brock
6 *
7 * Copyright (C) 2014-2015 Authors
8 *
9 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
10 */
11
12#include <iomanip>
13#include <random>
14#include <2geom/path-sink.h>
15#include <2geom/sbasis-to-bezier.h> // cubicbezierpath_from_sbasis
17#include <2geom/circle.h>
18#include <2geom/sweeper.h>
19
21#include "helper/geom.h"
22#include "path/path-boolop.h"
23#include "util/treeify.h"
24
25namespace Geom {
26
27static Point intersection_point(Point origin_a, Point vector_a, Point origin_b, Point vector_b)
28{
29 Coord denom = cross(vector_a, vector_b);
30 if (!are_near(denom,0.)) {
31 Coord t = (cross(vector_b, origin_a) + cross(origin_b, vector_b)) / denom;
32 return origin_a + vector_a*t;
33 }
34 return Point(infinity(), infinity());
35}
36
41static Circle touching_circle( D2<SBasis> const &curve, double t, double tol=0.01 )
42{
44 if ( are_near(L2sq(dM(t)), tol) ) {
45 dM=derivative(dM);
46 }
47 if ( are_near(L2sq(dM(t)), tol) ) { // try second time
48 dM=derivative(dM);
49 }
50 Piecewise<D2<SBasis> > unitv = unitVector(dM,tol);
51 Piecewise<SBasis> dMlength = dot(Piecewise<D2<SBasis> >(dM),unitv);
52 Piecewise<SBasis> k = cross(derivative(unitv),unitv);
53 k = divide(k,dMlength,tol,3);
54 double curv = k(t); // note that this value is signed
55
56 Geom::Point normal = unitTangentAt(curve, t).cw();
57 double radius = 1/curv;
58 Geom::Point center = curve(t) + radius*normal;
59 return Geom::Circle(center, fabs(radius));
60}
61
62
63// Area of triangle given three corner points
64static double area( Geom::Point a, Geom::Point b, Geom::Point c ) {
65
66 using Geom::X;
67 using Geom::Y;
68 return( 0.5 * fabs( ( a[X]*(b[Y]-c[Y]) + b[X]*(c[Y]-a[Y]) + c[X]*(a[Y]-b[Y]) ) ) );
69}
70
71// Alternative touching circle routine directly using Beziers. Works only at end points.
73
74 double k = 0;
76 Geom::Point normal;
77 if ( start ) {
78 double distance = Geom::distance( curve[1], curve[0] );
79 k = 4.0/3.0 * area( curve[0], curve[1], curve[2] ) /
81 if( Geom::cross(curve[0]-curve[1], curve[1]-curve[2]) < 0 ) {
82 k = -k;
83 }
84 p = curve[0];
85 normal = Geom::Point(curve[1] - curve[0]).cw();
86 normal.normalize();
87 // std::cout << "Start k: " << k << " d: " << distance << " normal: " << normal << std::endl;
88 } else {
89 double distance = Geom::distance( curve[3], curve[2] );
90 k = 4.0/3.0 * area( curve[1], curve[2], curve[3] ) /
92 if( Geom::cross(curve[1]-curve[2], curve[2]-curve[3]) < 0 ) {
93 k = -k;
94 }
95 p = curve[3];
96 normal = Geom::Point(curve[3] - curve[2]).cw();
97 normal.normalize();
98 // std::cout << "End k: " << k << " d: " << distance << " normal: " << normal << std::endl;
99 }
100 if( k == 0 ) {
101 return Geom::Circle( Geom::Point(0,std::numeric_limits<float>::infinity()),
102 std::numeric_limits<float>::infinity());
103 } else {
104 double radius = 1/k;
105 Geom::Point center = p + normal * radius;
106 return Geom::Circle( center, fabs(radius) );
107 }
108}
109}
110
111namespace {
112
113// Internal data structure
114
115struct join_data {
116 join_data(Geom::Path &_res, Geom::Path const&_outgoing, Geom::Point _in_tang, Geom::Point _out_tang, double _miter, double _width)
117 : res(_res), outgoing(_outgoing), in_tang(_in_tang), out_tang(_out_tang), miter(_miter), width(_width) {};
118
119 // contains the current path that is being built on
120 Geom::Path &res;
121
122 // contains the next curve to append
123 Geom::Path const& outgoing;
124
125 // input tangents
126 Geom::Point in_tang;
127 Geom::Point out_tang;
128
129 // line parameters
130 double miter;
131 double width; // half stroke width
132};
133
134// Join functions must append the outgoing path
135
136typedef void join_func(join_data jd);
137
138void bevel_join(join_data jd)
139{
140 jd.res.appendNew<Geom::LineSegment>(jd.outgoing.initialPoint());
141 jd.res.append(jd.outgoing);
142}
143
144void round_join(join_data jd)
145{
146 jd.res.appendNew<Geom::EllipticalArc>(jd.width, jd.width, 0, false, jd.width <= 0, jd.outgoing.initialPoint());
147 jd.res.append(jd.outgoing);
148}
149
150void miter_join_internal(join_data const &jd, bool clip)
151{
152 using namespace Geom;
153
154 Curve const& incoming = jd.res.back();
155 Curve const& outgoing = jd.outgoing.front();
156 Path &res = jd.res;
157 double width = jd.width, miter = jd.miter;
158
159 Point tang1 = jd.in_tang;
160 Point tang2 = jd.out_tang;
161 Point p = intersection_point(incoming.finalPoint(), tang1, outgoing.initialPoint(), tang2);
162
163 bool satisfied = false;
164 bool inc_ls = res.back_open().degreesOfFreedom() <= 4;
165
166 if (p.isFinite()) {
167 // check size of miter
168 Point point_on_path = incoming.finalPoint() + rot90(tang1)*width;
169 // SVG defines miter length as distance between inner intersection and outer intersection,
170 // which is twice the distance from p to point_on_path but width is half stroke width.
171 satisfied = distance(p, point_on_path) <= miter * width;
172 if (satisfied) {
173 // miter OK, check to see if we can do a relocation
174 if (inc_ls) {
175 res.setFinal(p);
176 } else {
177 res.appendNew<LineSegment>(p);
178 }
179 } else if (clip) {
180 // std::cout << " Clipping ------------ " << std::endl;
181 // miter needs clipping, find two points
182 Point bisector_versor = Line(point_on_path, p).versor();
183 Point point_limit = point_on_path + miter * width * bisector_versor;
184 // std::cout << " bisector_versor: " << bisector_versor << std::endl;
185 // std::cout << " point_limit: " << point_limit << std::endl;
186 Point p1 = intersection_point(incoming.finalPoint(), tang1, point_limit, bisector_versor.cw());
187 Point p2 = intersection_point(outgoing.initialPoint(), tang2, point_limit, bisector_versor.cw());
188 // std::cout << " p1: " << p1 << std::endl;
189 // std::cout << " p2: " << p2 << std::endl;
190 if (inc_ls) {
191 res.setFinal(p1);
192 } else {
193 res.appendNew<LineSegment>(p1);
194 }
195 res.appendNew<LineSegment>(p2);
196 }
197 }
198
199 res.appendNew<LineSegment>(outgoing.initialPoint());
200
201 // check if we can do another relocation
202 bool out_ls = outgoing.degreesOfFreedom() <= 4;
203
204 if ((satisfied || clip) && out_ls) {
205 res.setFinal(outgoing.finalPoint());
206 } else {
207 res.append(outgoing);
208 }
209
210 // either way, add the rest of the path
211 res.insert(res.end(), ++jd.outgoing.begin(), jd.outgoing.end());
212}
213
214void miter_join(join_data jd) { miter_join_internal(jd, false); }
215void miter_clip_join(join_data jd) { miter_join_internal(jd, true); }
216
217Geom::Point pick_solution(std::vector<Geom::ShapeIntersection> points, Geom::Point tang2, Geom::Point endPt)
218{
219 assert(points.size() == 2);
220 Geom::Point sol;
221 if ( dot(tang2, points[0].point() - endPt) > 0 ) {
222 // points[0] is bad, choose points[1]
223 sol = points[1];
224 } else if ( dot(tang2, points[1].point() - endPt) > 0 ) { // points[0] could be good, now check points[1]
225 // points[1] is bad, choose points[0]
226 sol = points[0];
227 } else {
228 // both points are good, choose nearest
229 sol = ( distanceSq(endPt, points[0].point()) < distanceSq(endPt, points[1].point()) )
230 ? points[0].point() : points[1].point();
231 }
232 return sol;
233}
234
235// Arcs line join. If two circles don't intersect, expand inner circle.
236Geom::Point expand_circle( Geom::Circle &inner_circle, Geom::Circle const &outer_circle, Geom::Point const &start_pt, Geom::Point const &start_tangent ) {
237 // std::cout << "expand_circle:" << std::endl;
238 // std::cout << " outer_circle: radius: " << outer_circle.radius() << " center: " << outer_circle.center() << std::endl;
239 // std::cout << " start: point: " << start_pt << " tangent: " << start_tangent << std::endl;
240
241 if( !(outer_circle.contains(start_pt) ) ) {
242 // std::cout << " WARNING: Outer circle does not contain starting point!" << std::endl;
243 return Geom::Point(0,0);
244 }
245
246 Geom::Line secant1(start_pt, start_pt + start_tangent);
247 std::vector<Geom::ShapeIntersection> chord1_pts = outer_circle.intersect(secant1);
248 // std::cout << " chord1: " << chord1_pts[0].point() << ", " << chord1_pts[1].point() << std::endl;
249 Geom::LineSegment chord1(chord1_pts[0].point(), chord1_pts[1].point());
250
251 Geom::Line bisector = make_bisector_line( chord1 );
252 std::vector<Geom::ShapeIntersection> chord2_pts = outer_circle.intersect(bisector);
253 // std::cout << " chord2: " << chord2_pts[0].point() << ", " << chord2_pts[1].point() << std::endl;
254 Geom::LineSegment chord2(chord2_pts[0].point(), chord2_pts[1].point());
255
256 // Find D, point on chord2 and on circle closest to start point
257 Geom::Coord d0 = Geom::distance(chord2_pts[0].point(),start_pt);
258 Geom::Coord d1 = Geom::distance(chord2_pts[1].point(),start_pt);
259 // std::cout << " d0: " << d0 << " d1: " << d1 << std::endl;
260 Geom::Point d = (d0 < d1) ? chord2_pts[0].point() : chord2_pts[1].point();
261 Geom::Line da(d,start_pt);
262
263 // Chord through start point and point D
264 std::vector<Geom::ShapeIntersection> chord3_pts = outer_circle.intersect(da);
265 // std::cout << " chord3: " << chord3_pts[0].point() << ", " << chord3_pts[1].point() << std::endl;
266
267 // Find farthest point on chord3 and on circle (could be more robust)
268 Geom::Coord d2 = Geom::distance(chord3_pts[0].point(),d);
269 Geom::Coord d3 = Geom::distance(chord3_pts[1].point(),d);
270 // std::cout << " d2: " << d2 << " d3: " << d3 << std::endl;
271
272 // Find point P, the intersection of outer circle and new inner circle
273 Geom::Point p = (d2 > d3) ? chord3_pts[0].point() : chord3_pts[1].point();
274
275 // Find center of new circle: it is at the intersection of the bisector
276 // of the chord defined by the start point and point P and a line through
277 // the start point and parallel to the first bisector.
278 Geom::LineSegment chord4(start_pt,p);
279 Geom::Line bisector2 = make_bisector_line( chord4 );
280 Geom::Line diameter = make_parallel_line( start_pt, bisector );
281 std::vector<Geom::ShapeIntersection> center_new = bisector2.intersect( diameter );
282 // std::cout << " center_new: " << center_new[0].point() << std::endl;
283 Geom::Coord r_new = Geom::distance( center_new[0].point(), start_pt );
284 // std::cout << " r_new: " << r_new << std::endl;
285
286 inner_circle.setCenter( center_new[0].point() );
287 inner_circle.setRadius( r_new );
288 return p;
289}
290
291// Arcs line join. If two circles don't intersect, adjust both circles so they just touch.
292// Increase (decrease) the radius of circle 1 and decrease (increase) of circle 2 by the same amount keeping the given points and tangents fixed.
293Geom::Point adjust_circles( Geom::Circle &circle1, Geom::Circle &circle2, Geom::Point const &point1, Geom::Point const &point2, Geom::Point const &tan1, Geom::Point const &tan2 ) {
294
295 Geom::Point n1 = (circle1.center() - point1).normalized(); // Always points towards center
296 Geom::Point n2 = (circle2.center() - point2).normalized();
297 Geom::Point sum_n = n1 + n2;
298
299 double r1 = circle1.radius();
300 double r2 = circle2.radius();
301 double delta_r = r2 - r1;
302 Geom::Point c1 = circle1.center();
303 Geom::Point c2 = circle2.center();
304 Geom::Point delta_c = c2 - c1;
305
306 // std::cout << "adjust_circles:" << std::endl;
307 // std::cout << " norm: " << n1 << "; " << n2 << std::endl;
308 // std::cout << " sum_n: " << sum_n << std::endl;
309 // std::cout << " delta_r: " << delta_r << std::endl;
310 // std::cout << " delta_c: " << delta_c << std::endl;
311
312 // Quadratic equation
313 double A = 4 - sum_n.length() * sum_n.length();
314 double B = 4.0 * delta_r - 2.0 * Geom::dot( delta_c, sum_n );
315 double C = delta_r * delta_r - delta_c.length() * delta_c.length();
316
317 double s1 = 0;
318 double s2 = 0;
319
320 if( fabs(A) < 0.01 ) {
321 // std::cout << " A near zero! $$$$$$$$$$$$$$$$$$" << std::endl;
322 if( B != 0 ) {
323 s1 = -C/B;
324 s2 = -s1;
325 }
326 } else {
327 s1 = (-B + sqrt(B*B - 4*A*C))/(2*A);
328 s2 = (-B - sqrt(B*B - 4*A*C))/(2*A);
329 }
330
331 double dr = (fabs(s1)<=fabs(s2)?s1:s2);
332
333 // std::cout << " A: " << A << std::endl;
334 // std::cout << " B: " << B << std::endl;
335 // std::cout << " C: " << C << std::endl;
336 // std::cout << " s1: " << s1 << " s2: " << s2 << " dr: " << dr << std::endl;
337
338 circle1 = Geom::Circle( c1 - dr*n1, r1-dr );
339 circle2 = Geom::Circle( c2 + dr*n2, r2+dr );
340
341 // std::cout << " C1: " << circle1 << std::endl;
342 // std::cout << " C2: " << circle2 << std::endl;
343 // std::cout << " d': " << Geom::Point( circle1.center() - circle2.center() ).length() << std::endl;
344
345 Geom::Line bisector( circle1.center(), circle2.center() );
346 std::vector<Geom::ShapeIntersection> points = circle1.intersect( bisector );
347 Geom::Point p0 = points[0].point();
348 Geom::Point p1 = points[1].point();
349 // std::cout << " points: " << p0 << "; " << p1 << std::endl;
350 if( std::abs( Geom::distance( p0, circle2.center() ) - circle2.radius() ) <
351 std::abs( Geom::distance( p1, circle2.center() ) - circle2.radius() ) ) {
352 return p0;
353 } else {
354 return p1;
355 }
356}
357
358void extrapolate_join_internal(join_data const &jd, int alternative)
359{
360 // std::cout << "\nextrapolate_join: entrance: alternative: " << alternative << std::endl;
361 using namespace Geom;
362
363 Geom::Path &res = jd.res;
364 Geom::Curve const& incoming = res.back();
365 Geom::Curve const& outgoing = jd.outgoing.front();
366 Geom::Point startPt = incoming.finalPoint();
367 Geom::Point endPt = outgoing.initialPoint();
368 Geom::Point tang1 = jd.in_tang;
369 Geom::Point tang2 = jd.out_tang;
370 // width is half stroke-width
371 double width = jd.width, miter = jd.miter;
372
373 // std::cout << " startPt: " << startPt << " endPt: " << endPt << std::endl;
374 // std::cout << " tang1: " << tang1 << " tang2: " << tang2 << std::endl;
375 // std::cout << " dot product: " << Geom::dot( tang1, tang2 ) << std::endl;
376 // std::cout << " width: " << width << std::endl;
377
378 // CIRCLE CALCULATION TESTING
379 Geom::Circle circle1 = touching_circle(Geom::reverse(incoming.toSBasis()), 0.);
380 Geom::Circle circle2 = touching_circle(outgoing.toSBasis(), 0);
381 // std::cout << " circle1: " << circle1 << std::endl;
382 // std::cout << " circle2: " << circle2 << std::endl;
383 if( Geom::CubicBezier const * in_bezier = dynamic_cast<Geom::CubicBezier const*>(&incoming) ) {
384 Geom::Circle circle_test1 = touching_circle(*in_bezier, false);
385 if( !Geom::are_near( circle1, circle_test1, 0.01 ) ) {
386 // std::cout << " Circle 1 error!!!!!!!!!!!!!!!!!" << std::endl;
387 // std::cout << " " << circle_test1 << std::endl;
388 }
389 }
390 if( Geom::CubicBezier const * out_bezier = dynamic_cast<Geom::CubicBezier const*>(&outgoing) ) {
391 Geom::Circle circle_test2 = touching_circle(*out_bezier, true);
392 if( !Geom::are_near( circle2, circle_test2, 0.01 ) ) {
393 // std::cout << " Circle 2 error!!!!!!!!!!!!!!!!!" << std::endl;
394 // std::cout << " " << circle_test2 << std::endl;
395 }
396 }
397 // END TESTING
398
399 Geom::Point center1 = circle1.center();
400 double side1 = tang1[Geom::X]*(startPt[Geom::Y]-center1[Geom::Y]) - tang1[Geom::Y]*(startPt[Geom::X]-center1[Geom::X]);
401 // std::cout << " side1: " << side1 << std::endl;
402
403 bool inc_ls = !circle1.center().isFinite();
404 bool out_ls = !circle2.center().isFinite();
405
406 std::vector<Geom::ShapeIntersection> points;
407
408 Geom::EllipticalArc *arc1 = nullptr;
409 Geom::EllipticalArc *arc2 = nullptr;
410 Geom::LineSegment *seg1 = nullptr;
411 Geom::LineSegment *seg2 = nullptr;
412 Geom::Point sol;
413 Geom::Point p1;
414 Geom::Point p2;
415
416 if (!inc_ls && !out_ls) {
417 // std::cout << " two circles" << std::endl;
418
419 // See if tangent is backwards (radius < width/2 and circle is inside stroke).
420 Geom::Point node_on_path = startPt + Geom::rot90(tang1)*width;
421 // std::cout << " node_on_path: " << node_on_path << " -------------- " << std::endl;
422 bool b1 = false;
423 bool b2 = false;
424 if (circle1.radius() < width && distance( circle1.center(), node_on_path ) < width) {
425 b1 = true;
426 }
427 if (circle2.radius() < width && distance( circle2.center(), node_on_path ) < width) {
428 b2 = true;
429 }
430 // std::cout << " b1: " << (b1?"true":"false")
431 // << " b2: " << (b2?"true":"false") << std::endl;
432
433 // Two circles
434 points = circle1.intersect(circle2);
435
436 if (points.size() != 2) {
437 // std::cout << " Circles do not intersect, do backup" << std::endl;
438 switch (alternative) {
439 case 1:
440 {
441 // Fallback to round if one path has radius smaller than half line width.
442 if(b1 || b2) {
443 // std::cout << "At one least path has radius smaller than half line width." << std::endl;
444 return( round_join(jd) );
445 }
446
447 Point p;
448 if( circle2.contains( startPt ) && !circle1.contains( endPt ) ) {
449 // std::cout << "Expand circle1" << std::endl;
450 p = expand_circle( circle1, circle2, startPt, tang1 );
451 points.emplace_back( 0, 0, p );
452 points.emplace_back( 0, 0, p );
453 } else if( circle1.contains( endPt ) && !circle2.contains( startPt ) ) {
454 // std::cout << "Expand circle2" << std::endl;
455 p = expand_circle( circle2, circle1, endPt, tang2 );
456 points.emplace_back( 0, 0, p );
457 points.emplace_back( 0, 0, p );
458 } else {
459 // std::cout << "Either both points inside or both outside" << std::endl;
460 return( miter_clip_join(jd) );
461 }
462 break;
463
464 }
465 case 2:
466 {
467 // Fallback to round if one path has radius smaller than half line width.
468 if(b1 || b2) {
469 // std::cout << "At one least path has radius smaller than half line width." << std::endl;
470 return( round_join(jd) );
471 }
472
473 if( ( circle2.contains( startPt ) && !circle1.contains( endPt ) ) ||
474 ( circle1.contains( endPt ) && !circle2.contains( startPt ) ) ) {
475
476 Geom::Point apex = adjust_circles( circle1, circle2, startPt, endPt, tang1, tang2 );
477 points.emplace_back( 0, 0, apex );
478 points.emplace_back( 0, 0, apex );
479 } else {
480 // std::cout << "Either both points inside or both outside" << std::endl;
481 return( miter_clip_join(jd) );
482 }
483
484 break;
485 }
486 case 3:
487 if( side1 > 0 ) {
488 Geom::Line secant(startPt, startPt + tang1);
489 points = circle2.intersect(secant);
490 circle1.setRadius(std::numeric_limits<float>::infinity());
491 circle1.setCenter(Geom::Point(0,std::numeric_limits<float>::infinity()));
492 } else {
493 Geom::Line secant(endPt, endPt + tang2);
494 points = circle1.intersect(secant);
495 circle2.setRadius(std::numeric_limits<float>::infinity());
496 circle2.setCenter(Geom::Point(0,std::numeric_limits<float>::infinity()));
497 }
498 break;
499
500
501 case 0:
502 default:
503 // Do nothing
504 break;
505 }
506 }
507
508 if (points.size() == 2) {
509 sol = pick_solution(points, tang2, endPt);
510 if( circle1.radius() != std::numeric_limits<float>::infinity() ) {
511 arc1 = circle1.arc(startPt, 0.5*(startPt+sol), sol);
512 } else {
513 seg1 = new Geom::LineSegment(startPt, sol);
514 }
515 if( circle2.radius() != std::numeric_limits<float>::infinity() ) {
516 arc2 = circle2.arc(sol, 0.5*(sol+endPt), endPt);
517 } else {
518 seg2 = new Geom::LineSegment(sol, endPt);
519 }
520 }
521 } else if (inc_ls && !out_ls) {
522 // Line and circle
523 // std::cout << " line circle" << std::endl;
524 points = circle2.intersect(Line(incoming.initialPoint(), incoming.finalPoint()));
525 if (points.size() == 2) {
526 sol = pick_solution(points, tang2, endPt);
527 arc2 = circle2.arc(sol, 0.5*(sol+endPt), endPt);
528 }
529 } else if (!inc_ls && out_ls) {
530 // Circle and line
531 // std::cout << " circle line" << std::endl;
532 points = circle1.intersect(Line(outgoing.initialPoint(), outgoing.finalPoint()));
533 if (points.size() == 2) {
534 sol = pick_solution(points, tang2, endPt);
535 arc1 = circle1.arc(startPt, 0.5*(sol+startPt), sol);
536 }
537 }
538 if (points.size() != 2) {
539 // std::cout << " no solutions" << std::endl;
540 // no solutions available, fall back to miter
541 return miter_join(jd);
542 }
543
544 // We have a solution, thus sol is defined.
545 p1 = sol;
546
547 // See if we need to clip. Miter length is measured along a circular arc that is tangent to the
548 // bisector of the incoming and out going angles and passes through the end point (sol) of the
549 // line join.
550
551 // Center of circle is intersection of a line orthogonal to bisector and a line bisecting
552 // a chord connecting the path end point (point_on_path) and the join end point (sol).
553 Geom::Point point_on_path = startPt + Geom::rot90(tang1)*width;
554 Geom::Line bisector = make_angle_bisector_line(startPt, point_on_path, endPt);
555 Geom::Line ortho = make_orthogonal_line(point_on_path, bisector);
556
557 Geom::LineSegment chord(point_on_path, sol);
558 Geom::Line bisector_chord = make_bisector_line(chord);
559
560 Geom::Line limit_line;
561 double miter_limit = width * miter;
562 bool clipped = false;
563
564 if (are_parallel(bisector_chord, ortho)) {
565 // No intersection (can happen if curvatures are equal but opposite)
566 if (Geom::distance(point_on_path, sol) > miter_limit) {
567 clipped = true;
568 Geom::Point temp = bisector.versor();
569 Geom::Point limit_point = point_on_path + miter_limit * temp;
570 limit_line = make_parallel_line( limit_point, ortho );
571 }
572 } else {
573 Geom::Point center =
574 Geom::intersection_point( bisector_chord.pointAt(0), bisector_chord.versor(),
575 ortho.pointAt(0), ortho.versor() );
576 Geom::Coord radius = distance(center, point_on_path);
577 Geom::Circle circle_center(center, radius);
578
579 double limit_angle = miter_limit / radius;
580
581 Geom::Ray start_ray(center, point_on_path);
582 Geom::Ray end_ray(center, sol);
583 Geom::Line limit_line(center, 0); // Angle set below
584
585 if (Geom::cross(start_ray.versor(), end_ray.versor()) < 0) {
586 limit_line.setAngle(start_ray.angle() - limit_angle);
587 } else {
588 limit_line.setAngle(start_ray.angle() + limit_angle);
589 }
590
591 Geom::EllipticalArc *arc_center = circle_center.arc(point_on_path, 0.5*(point_on_path + sol), sol);
592 if (arc_center && arc_center->sweepAngle() > limit_angle) {
593 // We need to clip
594 clipped = true;
595
596 if (!inc_ls) {
597 // Incoming circular
598 points = circle1.intersect(limit_line);
599 if (points.size() == 2) {
600 p1 = pick_solution(points, tang2, endPt);
601 delete arc1;
602 arc1 = circle1.arc(startPt, 0.5*(p1+startPt), p1);
603 }
604 } else {
605 p1 = Geom::intersection_point(startPt, tang1, limit_line.pointAt(0), limit_line.versor());
606 }
607
608 if (!out_ls) {
609 // Outgoing circular
610 points = circle2.intersect(limit_line);
611 if (points.size() == 2) {
612 p2 = pick_solution(points, tang1, endPt);
613 delete arc2;
614 arc2 = circle2.arc(p2, 0.5*(p2+endPt), endPt);
615 }
616 } else {
617 p2 = Geom::intersection_point(endPt, tang2, limit_line.pointAt(0), limit_line.versor());
618 }
619 }
620 }
621
622 // Add initial
623 if (arc1) {
624 res.append(*arc1);
625 } else if (seg1 ) {
626 res.append(*seg1);
627 } else {
628 // Straight line segment: move last point
629 res.setFinal(p1);
630 }
631
632 if (clipped) {
634 }
635
636 // Add outgoing
637 if (arc2) {
638 res.append(*arc2);
639 res.append(outgoing);
640 } else if (seg2 ) {
641 res.append(*seg2);
642 res.append(outgoing);
643 } else {
644 // Straight line segment:
645 res.appendNew<Geom::LineSegment>(outgoing.finalPoint());
646 }
647
648 // add the rest of the path
649 res.insert(res.end(), ++jd.outgoing.begin(), jd.outgoing.end());
650
651 delete arc1;
652 delete arc2;
653 delete seg1;
654 delete seg2;
655}
656
657void extrapolate_join( join_data jd) { extrapolate_join_internal(jd, 0); }
658void extrapolate_join_alt1(join_data jd) { extrapolate_join_internal(jd, 1); }
659void extrapolate_join_alt2(join_data jd) { extrapolate_join_internal(jd, 2); }
660void extrapolate_join_alt3(join_data jd) { extrapolate_join_internal(jd, 3); }
661
662
663void tangents(Geom::Point tang[2], Geom::Curve const& incoming, Geom::Curve const& outgoing)
664{
665 Geom::Point tang1 = Geom::unitTangentAt(reverse(incoming.toSBasis()), 0.);
666 Geom::Point tang2 = outgoing.unitTangentAt(0.);
667 tang[0] = tang1, tang[1] = tang2;
668}
669
670// Offsetting a line segment is mathematically stable and quick to do
671Geom::LineSegment offset_line(Geom::LineSegment const& l, double width)
672{
675
676 Geom::Point start = l.initialPoint() + tang1 * width;
677 Geom::Point end = l.finalPoint() - tang2 * width;
678
679 return Geom::LineSegment(start, end);
680}
681
682void get_cubic_data(Geom::CubicBezier const& bez, double time, double& len, double& rad)
683{
684 // get derivatives
685 std::vector<Geom::Point> derivs = bez.pointAndDerivatives(time, 3);
686
687 Geom::Point der1 = derivs[1]; // first deriv (tangent vector)
688 Geom::Point der2 = derivs[2]; // second deriv (tangent's tangent)
689 double l = Geom::L2(der1); // length
690
691 len = rad = 0;
692
693 // TODO: we might want to consider using Geom::touching_circle to determine the
694 // curvature radius here. Less code duplication, but slower
695
696 if (Geom::are_near(l, 0, 1e-4)) {
697 l = Geom::L2(der2) / 2;
698 Geom::Point der3 = derivs.at(3); // try second time
699 if (Geom::are_near(l, 0, 1e-4)) {
700 l = Geom::L2(der3);
701 if (Geom::are_near(l, 0)) {
702 return; // this isn't a segment...
703 }
704 rad = 1e8;
705 } else {
706 rad = -l * (Geom::dot(der2, der2) / Geom::cross(der2, der3));
707 }
708 } else {
709 rad = -l * (Geom::dot(der1, der1) / Geom::cross(der1, der2));
710 }
711 len = l;
712}
713
714double _offset_cubic_stable_sub(
715 Geom::CubicBezier const& bez,
717 const Geom::Point& start_normal,
718 const Geom::Point& end_normal,
719 const Geom::Point& start_new,
720 const Geom::Point& end_new,
721 const double start_rad,
722 const double end_rad,
723 const double start_len,
724 const double end_len,
725 const double width,
726 const double width_correction) {
727 using Geom::X;
728 using Geom::Y;
729
730 double start_off = 1, end_off = 1;
731 // correction of the lengths of the tangent to the offset
732 // start_off / end_off can also be negative. This is intended and
733 // is the case when *_radius is negative and its absolute value smaller then width.
734 if (!Geom::are_near(start_rad, 0)) {
735 start_off += width / start_rad;
736 }
737 if (!Geom::are_near(end_rad, 0)) {
738 end_off += width / end_rad;
739 }
740
741 // the correction factor should not change the sign of the factors
742 // as it is only a scaling heuristic to make the approximation better.
743 const auto correction_factor = 1 + width_correction / width;
744 if (correction_factor > 0) {
745 start_off *= correction_factor;
746 end_off *= correction_factor;
747 }
748
749 start_off *= start_len;
750 end_off *= end_len;
751
752 Geom::Point mid1_new = start_normal.ccw()*start_off;
753 mid1_new = Geom::Point(start_new[X] + mid1_new[X]/3., start_new[Y] + mid1_new[Y]/3.);
754 Geom::Point mid2_new = end_normal.ccw()*end_off;
755 mid2_new = Geom::Point(end_new[X] - mid2_new[X]/3., end_new[Y] - mid2_new[Y]/3.);
756
757 // create the estimate curve
758 c = Geom::CubicBezier(start_new, mid1_new, mid2_new, end_new);
759
760 // check the tolerance for our estimate to be a parallel curve
761 // both directions have to be checked, as we are computing a hausdorff distance with offset
762 double worst_residual = 0;
763 const auto min_offset_difference = [width, &worst_residual](Geom::CubicBezier const &bez1,
764 Geom::CubicBezier const &bez2, const double time) {
765 const Geom::Point requested_point = bez1.pointAt(time);
766 const Geom::Point closest_point = bez2.pointAt(bez2.nearestTime(requested_point));
767 const double current_residual = (requested_point - closest_point).length() - std::abs(width);
768 if (std::abs(current_residual) > std::abs(worst_residual)) {
769 worst_residual = current_residual;
770 }
771 };
772 for (size_t ii = 3; ii <= 7; ii += 2) {
773 const double t = static_cast<double>(ii) / 10;
774
775 min_offset_difference(bez, c, t);
776 min_offset_difference(c, bez, t);
777 }
778 return worst_residual;
779}
780
781void offset_cubic(Geom::Path& p, Geom::CubicBezier const& bez, double width, double tol, size_t levels)
782{
783 using Geom::X;
784 using Geom::Y;
785
786 const Geom::Point start_pos = bez.initialPoint();
787 const Geom::Point end_pos = bez.finalPoint();
788
789 const Geom::Point start_normal = Geom::rot90(bez.unitTangentAt(0));
790 const Geom::Point end_normal = -Geom::rot90(Geom::unitTangentAt(Geom::reverse(bez.toSBasis()), 0.));
791
792 // offset the start and end control points out by the width
793 const Geom::Point start_new = start_pos + start_normal*width;
794 const Geom::Point end_new = end_pos + end_normal*width;
795
796 // --------
797 double start_rad, end_rad;
798 double start_len, end_len; // tangent lengths
799 get_cubic_data(bez, 0, start_len, start_rad);
800 get_cubic_data(bez, 1, end_len, end_rad);
801
802
804
805 double best_width_correction = 0;
806 double best_residual = _offset_cubic_stable_sub(
807 bez, c,
808 start_normal, end_normal,
809 start_new, end_new,
810 start_rad, end_rad,
811 start_len, end_len,
812 width, best_width_correction);
813 double stepsize = std::abs(width)/2;
814 bool seen_success = false;
815 double stepsize_threshold = 0;
816 // std::cout << "Residual from " << best_residual << " ";
817 size_t ii = 0;
818 for (; ii < 100 && stepsize > stepsize_threshold; ++ii) {
819 bool success = false;
820 const double width_correction = best_width_correction - (best_residual > 0 ? 1 : -1) * stepsize;
821 Geom::CubicBezier current_curve;
822 const double residual = _offset_cubic_stable_sub(
823 bez, current_curve,
824 start_normal, end_normal,
825 start_new, end_new,
826 start_rad, end_rad,
827 start_len, end_len,
828 width, width_correction);
829 if (std::abs(residual) < std::abs(best_residual)) {
830 best_residual = residual;
831 best_width_correction = width_correction;
832 c = current_curve;
833 success = true;
834 if (std::abs(best_residual) < tol/4) {
835 break;
836 }
837 }
838
839 if (success) {
840 if (!seen_success) {
841 seen_success = true;
842 //std::cout << "Stepsize factor: " << std::abs(width) / stepsize << std::endl;
843 stepsize_threshold = stepsize / 1000;
844 }
845 }
846 else {
847 stepsize /= 2;
848 }
849 if (std::abs(best_width_correction) >= std::abs(width)/2) {
850 //break; // Seems to prevent some numerical instabilities, not clear if useful
851 }
852 }
853
854 // reached maximum recursive depth
855 // don't bother with any more correction
856 if (levels == 0) {
857 try {
858 p.append(c);
859 }
860 catch (...) {
861 if ((p.finalPoint() - c.initialPoint()).length() < 1e-6) {
862 c.setInitial(p.finalPoint());
863 }
864 else {
865 auto line = Geom::LineSegment(p.finalPoint(), c.initialPoint());
866 p.append(line);
867 }
868 p.append(c);
869 }
870
871 return;
872 }
873
874 // We find the point on (bez) for which the distance between
875 // (c) and (bez) differs the most from the desired distance (width).
876 // both directions have to be checked, as we are computing a hausdorff distance with offset
877 double worst_err = std::abs(best_residual);
878 double worst_time = .5;
879 const auto min_offset_difference = [width, &worst_err, &worst_time](Geom::CubicBezier const &bez1,
880 Geom::CubicBezier const &bez2,
881 const double time) {
882 const Geom::Point requested_point = bez1.pointAt(time);
883 const Geom::Point closest_point = bez2.pointAt(bez2.nearestTime(requested_point));
884 const double current_residual = std::abs((requested_point - closest_point).length() - std::abs(width));
885 if (current_residual > worst_err) {
886 worst_err = current_residual;
887 worst_time = time;
888 }
889 };
890 for (size_t ii = 1; ii <= 9; ++ii) {
891 const double t = static_cast<double>(ii) / 10;
892
893 min_offset_difference(bez, c, t);
894 min_offset_difference(c, bez, t);
895 }
896
897 if (worst_err < tol) {
898 if (Geom::are_near(start_new, p.finalPoint())) {
899 p.setFinal(start_new); // if it isn't near, we throw
900 }
901
902 // we're good, curve is accurate enough
903 p.append(c);
904 return;
905 } else {
906 // split the curve in two
907 std::pair<Geom::CubicBezier, Geom::CubicBezier> s = bez.subdivide(worst_time);
908 offset_cubic(p, s.first, width, tol, levels - 1);
909 offset_cubic(p, s.second, width, tol, levels - 1);
910 }
911}
912
913void offset_quadratic(Geom::Path& p, Geom::QuadraticBezier const& bez, double width, double tol, size_t levels)
914{
915 // cheat
916 // it's faster
917 // seriously
918 std::vector<Geom::Point> points = bez.controlPoints();
919 Geom::Point b1 = points[0] + (2./3) * (points[1] - points[0]);
920 Geom::Point b2 = b1 + (1./3) * (points[2] - points[0]);
921 Geom::CubicBezier cub = Geom::CubicBezier(points[0], b1, b2, points[2]);
922 offset_cubic(p, cub, width, tol, levels);
923}
924
925void offset_curve(Geom::Path& res, Geom::Curve const* current, double width, double tolerance)
926{
927 size_t levels = 8;
928
929 if (current->isDegenerate()) return; // don't do anything
930
931 // TODO: we can handle SVGEllipticalArc here as well, do that!
932
933 if (Geom::BezierCurve const *b = dynamic_cast<Geom::BezierCurve const*>(current)) {
934 size_t order = b->order();
935 switch (order) {
936 case 1:
937 res.append(offset_line(static_cast<Geom::LineSegment const&>(*current), width));
938 break;
939 case 2: {
940 Geom::QuadraticBezier const& q = static_cast<Geom::QuadraticBezier const&>(*current);
941 offset_quadratic(res, q, width, tolerance, levels);
942 break;
943 }
944 case 3: {
945 Geom::CubicBezier const& cb = static_cast<Geom::CubicBezier const&>(*current);
946 offset_cubic(res, cb, width, tolerance, levels);
947 break;
948 }
949 default: {
950 Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(current->toSBasis(), tolerance);
951 for (const auto & i : sbasis_path)
952 offset_curve(res, &i, width, tolerance);
953 break;
954 }
955 }
956 } else {
957 Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(current->toSBasis(), 0.1);
958 for (const auto & i : sbasis_path)
959 offset_curve(res, &i, width, tolerance);
960 }
961}
962
963typedef void cap_func(Geom::PathBuilder& res, Geom::Path const& with_dir, Geom::Path const& against_dir, double width);
964
965void flat_cap(Geom::PathBuilder& res, Geom::Path const&, Geom::Path const& against_dir, double)
966{
967 res.lineTo(against_dir.initialPoint());
968}
969
970void round_cap(Geom::PathBuilder& res, Geom::Path const&, Geom::Path const& against_dir, double width)
971{
972 res.arcTo(width / 2., width / 2., 0., true, false, against_dir.initialPoint());
973}
974
975void square_cap(Geom::PathBuilder& res, Geom::Path const& with_dir, Geom::Path const& against_dir, double width)
976{
977 width /= 2.;
978 Geom::Point normal_1 = -Geom::unitTangentAt(Geom::reverse(with_dir.back().toSBasis()), 0.);
979 Geom::Point normal_2 = -against_dir[0].unitTangentAt(0.);
980 res.lineTo(with_dir.finalPoint() + normal_1*width);
981 res.lineTo(against_dir.initialPoint() + normal_2*width);
982 res.lineTo(against_dir.initialPoint());
983}
984
985void peak_cap(Geom::PathBuilder& res, Geom::Path const& with_dir, Geom::Path const& against_dir, double width)
986{
987 width /= 2.;
988 Geom::Point normal_1 = -Geom::unitTangentAt(Geom::reverse(with_dir.back().toSBasis()), 0.);
989 Geom::Point normal_2 = -against_dir[0].unitTangentAt(0.);
990 Geom::Point midpoint = ((with_dir.finalPoint() + normal_1*width) + (against_dir.initialPoint() + normal_2*width)) * 0.5;
991 res.lineTo(midpoint);
992 res.lineTo(against_dir.initialPoint());
993}
994
995} // namespace
996
997namespace Inkscape {
998
1000 Geom::Path const& input,
1001 double width,
1002 double miter,
1004 LineCapType butt,
1005 double tolerance)
1006{
1007 if (input.size() == 0) return Geom::PathVector(); // nope, don't even try
1008
1010 Geom::Path with_dir = half_outline(input, width/2., miter, join, tolerance);
1011 Geom::Path against_dir = half_outline(input.reversed(), width/2., miter, join, tolerance);
1012 res.moveTo(with_dir[0].initialPoint());
1013 res.append(with_dir);
1014
1015 cap_func *cf;
1016 switch (butt) {
1017 case BUTT_ROUND:
1018 cf = &round_cap;
1019 break;
1020 case BUTT_SQUARE:
1021 cf = &square_cap;
1022 break;
1023 case BUTT_PEAK:
1024 cf = &peak_cap;
1025 break;
1026 default:
1027 cf = &flat_cap;
1028 }
1029
1030 // glue caps
1031 if (!input.closed()) {
1032 cf(res, with_dir, against_dir, width);
1033 } else {
1034 res.closePath();
1035 res.moveTo(against_dir.initialPoint());
1036 }
1037
1038 res.append(against_dir);
1039
1040 if (!input.closed()) {
1041 cf(res, against_dir, with_dir, width);
1042 }
1043
1044 res.closePath();
1045 res.flush();
1046 return res.peek();
1047}
1048
1050 Geom::Path const& input,
1051 double width,
1052 double miter,
1054 double tolerance)
1055{
1056 if (tolerance <= 0) {
1057 if (std::abs(width) > 0) {
1058 tolerance = std::abs(width) / 100;
1059 }
1060 else {
1061 tolerance = 1e-4;
1062 }
1063 }
1064 Geom::Path res;
1065 if (input.size() == 0) return res;
1066
1067 Geom::Point tang1 = input[0].unitTangentAt(0);
1068 Geom::Point start = input.initialPoint() + tang1 * width;
1069 Geom::Path temp;
1070 Geom::Point tang[2];
1071
1072 res.setStitching(true);
1073 temp.setStitching(true);
1074
1075 res.start(start);
1076
1077 // Do two curves at a time for efficiency, since the join function needs to know the outgoing curve as well
1078 const Geom::Curve &closingline = input.back_closed();
1079 const size_t k = (are_near(closingline.initialPoint(), closingline.finalPoint()) && input.closed() )
1080 ?input.size_open():input.size_default();
1081
1082 for (size_t u = 0; u < k; u += 2) {
1083 temp.clear();
1084
1085 offset_curve(temp, &input[u], width, tolerance);
1086
1087 // on the first run through, there isn't a join
1088 if (u == 0) {
1089 res.append(temp);
1090 } else {
1091 tangents(tang, input[u-1], input[u]);
1092 outline_join(res, temp, tang[0], tang[1], width, miter, join);
1093 }
1094
1095 // odd number of paths
1096 if (u < k - 1) {
1097 temp.clear();
1098 offset_curve(temp, &input[u+1], width, tolerance);
1099 tangents(tang, input[u], input[u+1]);
1100 outline_join(res, temp, tang[0], tang[1], width, miter, join);
1101 }
1102 }
1103 if (input.closed()) {
1104 Geom::Curve const &c1 = res.back();
1105 Geom::Curve const &c2 = res.front();
1106 temp.clear();
1107 temp.append(c1);
1108 Geom::Path temp2;
1109 temp2.append(c2);
1110 tangents(tang, input.back(), input.front());
1111 outline_join(temp, temp2, tang[0], tang[1], width, miter, join);
1112 res.erase(res.begin());
1113 res.erase_last();
1114 res.append(temp);
1115 res.close();
1116 }
1117 return res;
1118}
1119
1120void outline_join(Geom::Path &res, Geom::Path const& temp, Geom::Point in_tang, Geom::Point out_tang, double width, double miter, Inkscape::LineJoinType join)
1121{
1122 if (res.size() == 0 || temp.size() == 0)
1123 return;
1124 Geom::Curve const& outgoing = temp.front();
1125 if (Geom::are_near(res.finalPoint(), outgoing.initialPoint(), 0.01)) {
1126 // if the points are /that/ close, just ignore this one
1127 res.setFinal(temp.initialPoint());
1128 res.append(temp);
1129 return;
1130 }
1131
1132 join_data jd(res, temp, in_tang, out_tang, miter, width);
1133 if (!(Geom::cross(in_tang, out_tang) > 0)) {
1135 }
1136 join_func *jf;
1137 switch (join) {
1139 jf = &bevel_join;
1140 break;
1142 jf = &round_join;
1143 break;
1145 jf = &extrapolate_join;
1146 break;
1148 jf = &extrapolate_join_alt1;
1149 break;
1151 jf = &extrapolate_join_alt2;
1152 break;
1154 jf = &extrapolate_join_alt3;
1155 break;
1157 jf = &miter_clip_join;
1158 break;
1159 default:
1160 jf = &miter_join;
1161 }
1162 jf(jd);
1163}
1164
1168bool is_path_empty(Geom::Path const &path)
1169{
1170 double area;
1171 Geom::Point pt;
1172 Geom::centroid(path.toPwSb(), pt, area);
1173 return std::abs(area) < 1e-3;
1174}
1175
1177static std::optional<Geom::Point> find_interior_point(Geom::Path const &path, FillRule fill_rule, std::default_random_engine &gen)
1178{
1179 auto ran = [&] { return std::uniform_real_distribution()(gen); };
1180
1181 auto const bounds = path.boundsFast();
1182 if (!bounds) {
1183 return {};
1184 }
1185
1186 constexpr int max_iterations = 10; // arbitrary cutoff, typically one iteration needed
1187 for (int i = 0; i < max_iterations; i++) {
1188 // Pick a random horizontal line through the path.
1189 auto const y = Geom::lerp(ran(), bounds->top(), bounds->bottom());
1190 auto const line = Geom::LineSegment{Geom::Point{bounds->left() - bounds->width(), y}, Geom::Point{bounds->right() + bounds->width(), y}};
1191
1192 // Intersect the line with the path, recording the intersection times on the line.
1193 std::vector<double> times;
1194 for (auto const &curve : path) {
1195 for (auto const &intersection : line.intersect(curve)) {
1196 times.emplace_back(intersection.first);
1197 }
1198 }
1199
1200 // Interior-test the points exactly halfway between intersections.
1201 for (int j = 1; j < times.size(); j++) {
1202 auto const t = (times[j - 1] + times[j]) / 2;
1203 auto const p = line.pointAt(t);
1204 if (is_point_inside(fill_rule, path.winding(p))) {
1205 return p;
1206 }
1207 }
1208 }
1209
1210 return {};
1211}
1212
1213namespace {
1214
1220class PathContainmentSweeper
1221{
1222public:
1223 using ItemIterator = Geom::PathVector::const_iterator;
1224
1225 PathContainmentSweeper(Geom::PathVector const &pathv, FillRule fill_rule, double precision = Geom::EPSILON)
1226 : _pathv{pathv}
1227 , _fill_rule{fill_rule}
1228 , _precision{precision}
1229 , _contains(_pathv.size() * _pathv.size(), false) // allocate space for two-dimensional array
1230 {}
1231
1232 Geom::PathVector const &items() const { return _pathv; }
1233
1234 Geom::Interval itemBounds(ItemIterator path) const
1235 {
1236 auto const r = path->boundsFast();
1237 return r ? (*r)[Geom::X] : Geom::Interval{};
1238 }
1239
1240 void addActiveItem(ItemIterator incoming)
1241 {
1242 for (auto const &path : _active) {
1243 _checkPair(path, incoming);
1244 _checkPair(incoming, path);
1245 }
1246 _active.push_back(incoming);
1247 }
1248
1249 void removeActiveItem(ItemIterator to_remove)
1250 {
1251 auto const it = std::find(_active.begin(), _active.end(), to_remove);
1252 std::swap(*it, _active.back());
1253 _active.pop_back();
1254 }
1255
1257 bool contains(int i, int j) const { return _contains[index(i, j)]; }
1258
1259private:
1262 double const _precision;
1263
1264 std::vector<ItemIterator> _active;
1265 std::vector<bool> _contains;
1266
1267 int index(int i, int j) const { return i * _pathv.size() + j; }
1268
1269 void _checkPair(ItemIterator a, ItemIterator b)
1270 {
1271 if (pathv_fully_contains(*a, *b, _fill_rule)) {
1272 auto const ia = std::distance(_pathv.begin(), a);
1273 auto const ib = std::distance(_pathv.begin(), b);
1274 _contains[index(ia, ib)] = true;
1275 }
1276 }
1277};
1278
1283class PathContainmentTraverser
1284{
1285public:
1286 PathContainmentTraverser(Geom::PathVector &paths, Util::TreeifyResult const &tree, FillRule fill_rule)
1287 : _paths{paths}
1288 , _tree{tree}
1289 , _fill_rule{fill_rule}
1290 {
1291 while (_pos < tree.preorder.size()) {
1292 _visit(nullptr, 0, nullptr);
1293 }
1294 }
1295
1296 std::vector<Geom::PathVector> &&moveResult() { return std::move(_result); }
1297
1298private:
1299 // Input
1302 FillRule const _fill_rule;
1303
1304 // State
1305 std::default_random_engine _gen{std::random_device()()};
1306 int _pos = 0;
1307
1308 // Output
1309 std::vector<Geom::PathVector> _result;
1310
1311 void _visit(Geom::Path const *parent, int winding, Geom::PathVector *component)
1312 {
1313 // Visit the next node in the preorder traversal.
1314 int const x = _tree.preorder[_pos];
1315 _pos++;
1316
1317 // Determine winding number at paths[x] of its parents in the tree.
1318 // Skip the computation for fill_justDont, as it would be unused.
1319 if (parent && _fill_rule != fill_justDont) {
1320 if (auto const p = find_interior_point(_paths[x], _fill_rule, _gen)) {
1321 winding += parent->winding(*p);
1322 }
1323 }
1324
1325 // Determine if paths[x] is inside a hole. If so, we start a new component.
1326 bool const boundary = !is_point_inside(_fill_rule, winding);
1327
1328 std::optional<Geom::PathVector> pathv;
1329 if (boundary) {
1330 pathv.emplace();
1331 component = &*pathv;
1332 }
1333
1334 assert(component);
1335 component->push_back(std::move(_paths[x]));
1336
1337 for (int i = 0; i < _tree.num_children[x]; i++) {
1338 _visit(&_paths[x], winding, component);
1339 }
1340
1341 if (boundary) {
1342 _result.emplace_back(std::move(*pathv));
1343 }
1344 }
1345};
1346
1347} // namespace
1348
1349std::vector<Geom::PathVector> split_non_intersecting_paths(Geom::PathVector &&paths, FillRule fill_rule)
1350{
1351 auto path_containment = PathContainmentSweeper{paths, fill_nonZero};
1352 Geom::Sweeper{path_containment}.process();
1353
1354 auto const tree = Util::treeify(paths.size(), [&] (int i, int j) {
1355 return path_containment.contains(i, j);
1356 });
1357
1358 return PathContainmentTraverser(paths, tree, fill_rule).moveResult();
1359}
1360
1363 , double to_offset
1364 , double tolerance
1365 , double miter_limit
1366 , FillRule fillrule
1368 , Geom::Point point // knot on LPE
1369 , Geom::PathVector &helper_path
1370 , Geom::PathVector &mix_pathv_all)
1371{
1372 Geom::PathVector ret_closed;
1373 Geom::PathVector ret_open;
1374 Geom::PathVector open_pathv;
1375 Geom::PathVector closed_pathv;
1377 Geom::PathVector outline; // return path
1378 helper_path.insert(helper_path.end(), orig_pathv.begin(), orig_pathv.end());
1379 // Store separated open/closed paths
1380 for (auto &i : orig_pathv) {
1381 // this improve offset in near closed paths
1382 if (Geom::are_near(i.initialPoint(), i.finalPoint())) {
1383 i.close(true);
1384 }
1385 if (i.closed()) {
1386 closed_pathv.push_back(i);
1387 } else {
1388 open_pathv.push_back(i);
1389 }
1390 }
1391 // flatten order the direcions and remove self intersections
1392 // we use user fill rule to match original view
1393 // after flatten all elements has the same direction in his widding
1394 flatten(closed_pathv, fillrule);
1395 if (Geom::are_near(to_offset,0.0)) {
1396 // this is to keep reference to multiple pathvectors like in a group. Used by knot position in LPE Offset
1397 mix_pathv_all.insert(mix_pathv_all.end(), path_in.begin(), path_in.end());
1398 closed_pathv.insert(closed_pathv.end(), open_pathv.begin(), open_pathv.end());
1399 return closed_pathv;
1400 }
1401 if (to_offset < 0) {
1402 Geom::OptRect bbox = closed_pathv.boundsFast();
1403 if (bbox) {
1404 (*bbox).expandBy(to_offset / 2.0);
1405 if ((*bbox).hasZeroArea()) {
1406 closed_pathv.clear();
1407 }
1408 }
1409 }
1410 // this is to keep reference to multiple pathvectors like in a group. Used by knot position in LPE Offset
1411 mix_pathv_all.insert(mix_pathv_all.end(), closed_pathv.begin(), closed_pathv.end());
1412 Geom::PathVector outline_tmp; // full outline to operate
1413 double gap = to_offset > 0 ? 0 : 0.01;
1414 for (auto &input : closed_pathv) {
1415 // input dir is 1 on fill and 0 in holes. garanteed by flatten
1416 bool dir = Geom::path_direction(input);
1417 Geom::Path with_dir = half_outline(input, std::abs(to_offset) + gap, miter_limit, join, tolerance);
1418 if (to_offset > 0) {
1419 //we remove artifacts in a manual way not the best way but there is no other way without a clean offset line
1420 if (!dir) {
1421 auto bbox = input.boundsFast();
1422 if (bbox) {
1423 double sizei = std::min((*bbox).width(),(*bbox).height());
1424 if (sizei > to_offset * 2) {
1425 outline_tmp.push_back(with_dir);
1426 }
1427 }
1428 } else {
1429 auto with_dir_pv = Geom::PathVector(with_dir);
1430 flatten(with_dir_pv, fill_positive);
1431 for (auto path : with_dir_pv) {
1432 auto bbox = path.boundsFast();
1433 if (bbox) {
1434 double sizei = std::min((*bbox).width(),(*bbox).height());
1435 if (sizei > to_offset * 2) {
1436 outline_tmp.push_back(path);
1437 }
1438 }
1439 }
1440 }
1441 } else {
1442 Geom::Path against_dir = half_outline(input.reversed(), std::abs(to_offset) + gap, miter_limit, join, tolerance);
1443 outline_tmp.push_back(with_dir);
1444 outline_tmp.push_back(against_dir);
1445 outline.push_back(input);
1446 }
1447 }
1448 if (!closed_pathv.empty()) {
1449 if (to_offset > 0) {
1450 outline.insert(outline.end(), outline_tmp.begin(), outline_tmp.end());
1451 // this make a union propely without calling boolops
1453 } else {
1454 // this flatten in a fill_positive way that allow us erase it from the otiginal outline alwais (smaller)
1455 flatten(outline_tmp, fill_positive);
1456 // this can produce small satellites that become removed after new offset impletation work in 1.4
1458 }
1459 }
1460 // this is to keep reference to multiple pathvectors like in a group. Used by knot position in LPE Offset
1461 mix_pathv_all.insert(mix_pathv_all.end(), open_pathv.begin(), open_pathv.end());
1462 for (auto &i : open_pathv) {
1463 Geom::Path tmp_a = half_outline(i, to_offset, miter_limit, join, tolerance);
1464 if (point != Geom::Point(Geom::infinity(), Geom::infinity())) {
1465 Geom::Path tmp_b = half_outline(i.reversed(), to_offset, miter_limit, join, tolerance);
1466 double distance_b = Geom::distance(point, tmp_a.pointAt(tmp_a.nearestTime(point)));
1467 double distance_a = Geom::distance(point, tmp_b.pointAt(tmp_b.nearestTime(point)));
1468 if (distance_b < distance_a) {
1469 outline.push_back(tmp_a);
1470 } else {
1471 outline.push_back(tmp_b);
1472 }
1473 } else {
1474 outline.push_back(tmp_a);
1475 }
1476 }
1477 return outline;
1478}
1479
1482 , double to_offset
1483 , double tolerance
1484 , double miter_limit
1485 , FillRule fillrule
1487{
1488 Geom::PathVector not_used;
1489 return do_offset(path_in, to_offset, tolerance, miter_limit, fillrule, join, Geom::Point(Geom::infinity(), Geom::infinity()), not_used, not_used);
1490}
1491
1492} // namespace Inkscape
1493
1494/*
1495 Local Variables:
1496 mode:c++
1497 c-file-style:"stroustrup"
1498 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1499 indent-tabs-mode:nil
1500 fill-column:99
1501 End:
1502*/
1503// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
pair< double, double > Point
Definition parser.cpp:7
bool is_point_inside(FillRule fill_rule, int winding)
Return whether a point is inside a shape, given the point's winding number and the shape's fill rule.
FillRule
Definition LivarotDefs.h:68
@ fill_nonZero
Definition LivarotDefs.h:70
@ fill_justDont
Definition LivarotDefs.h:72
@ fill_positive
Definition LivarotDefs.h:71
@ bool_op_diff
Definition LivarotDefs.h:80
double distance(Shape const *s, Geom::Point const &p)
Definition Shape.cpp:2136
Geom::IntRect bounds
Definition canvas.cpp:182
Circle shape.
Bezier curve with compile-time specified order.
std::pair< BezierCurveN, BezierCurveN > subdivide(Coord t) const
Divide a Bezier curve into two curves.
Two-dimensional Bezier curve of arbitrary order.
D2< SBasis > toSBasis() const override
Convert the curve to a symmetric power basis polynomial.
Point finalPoint() const override
Retrieve the end of the curve.
Point initialPoint() const override
Retrieve the start of the curve.
std::vector< Point > controlPoints() const
Get the control points.
std::vector< Point > pointAndDerivatives(Coord t, unsigned n) const override
Evaluate the curve and its derivatives.
Set of all points at a fixed distance from the center.
Definition circle.h:55
Point center() const
Definition circle.h:75
EllipticalArc * arc(Point const &initial, Point const &inner, Point const &final) const
Definition circle.cpp:254
std::vector< ShapeIntersection > intersect(Line const &other) const
Definition circle.cpp:163
void setRadius(Coord c)
Definition circle.h:82
void setCenter(Point const &p)
Definition circle.h:81
bool contains(Point const &p) const
Definition circle.h:94
Coord radius() const
Definition circle.h:77
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
virtual D2< SBasis > toSBasis() const =0
Convert the curve to a symmetric power basis polynomial.
virtual Point initialPoint() const =0
Retrieve the start of the curve.
virtual Point finalPoint() const =0
Retrieve the end of the curve.
virtual Point unitTangentAt(Coord t, unsigned n=3) const
Compute a vector tangent to the curve.
Definition curve.cpp:201
virtual int degreesOfFreedom() const
Return the number of independent parameters required to represent all variations of this curve.
Definition curve.h:333
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
Elliptical arc curve.
Coord sweepAngle() const
Compute the amount by which the angle parameter changes going from start to end.
C right() const
Return rightmost coordinate of the rectangle (+X is to the right).
C top() const
Return top coordinate of the rectangle (+Y is downwards).
C left() const
Return leftmost coordinate of the rectangle (+X is to the right).
C width() const
Get the horizontal extent of the rectangle.
C bottom() const
Return bottom coordinate of the rectangle (+Y is downwards).
Range of real numbers that is never empty.
Definition interval.h:59
Infinite line on a plane.
Definition line.h:53
void setAngle(Coord angle)
Set the angle the line makes with the X axis.
Definition line.h:160
std::vector< ShapeIntersection > intersect(Line const &other) const
Definition line.cpp:253
Point pointAt(Coord t) const
Definition line.h:231
Point versor() const
Get the line's normalized direction vector.
Definition line.h:135
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Store paths to a PathVector.
Definition path-sink.h:226
PathVector const & peek() const
Retrieve the path.
Definition path-sink.h:236
void moveTo(Point const &p) override
Move to a different point without creating a segment.
Definition path-sink.h:121
void append(Path const &other)
Definition path-sink.h:181
void arcTo(Coord rx, Coord ry, Coord angle, bool large_arc, bool sweep, Point const &p) override
Output an elliptical arc segment.
Definition path-sink.h:161
void lineTo(Point const &p) override
Output a line segment.
Definition path-sink.h:137
void flush() override
Flush any internal state of the generator.
Definition path-sink.h:196
void closePath() override
Close the current path with a line segment.
Definition path-sink.h:189
Sequence of subpaths.
Definition pathvector.h:122
size_type size() const
Get the number of paths in the vector.
Definition pathvector.h:147
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
Sequence::const_iterator const_iterator
Definition pathvector.h:127
OptRect boundsFast() const
void clear()
Remove all paths from the vector.
Definition pathvector.h:195
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
iterator insert(iterator pos, Path const &p)
Definition pathvector.h:179
iterator begin()
Definition pathvector.h:151
iterator end()
Definition pathvector.h:152
Sequence of contiguous curves, aka spline.
Definition path.h:353
bool closed() const
Check whether the path is closed.
Definition path.h:503
Point finalPoint() const
Get the last point in the path.
Definition path.h:709
void setStitching(bool x)
Enable or disable the throwing of exceptions when stitching discontinuities.
Definition path.h:827
void close(bool closed=true)
Set whether the path is closed.
Definition path.cpp:322
Piecewise< D2< SBasis > > toPwSb() const
Definition path.cpp:388
Curve const & back_closed() const
Definition path.h:452
const_iterator end() const
Definition path.h:465
Curve const & back() const
Access the last curve in the path.
Definition path.h:447
PathTime nearestTime(Point const &p, Coord *dist=NULL) const
Definition path.cpp:733
Point pointAt(Coord t) const
Get the point at the specified time value.
Definition path.cpp:449
OptRect boundsFast() const
Get the approximate bounding box.
Definition path.cpp:348
size_type size_open() const
Size without the closing segment, even if the path is closed.
Definition path.h:476
void clear()
Remove all curves from the path.
Definition path.cpp:337
size_type size_default() const
Natural size of the path.
Definition path.h:486
int winding(Point const &p) const
Determine the winding number at the specified point.
Definition path.cpp:595
void append(Curve *curve)
Add a new curve to the end of the path.
Definition path.h:750
Point initialPoint() const
Get the first point in the path.
Definition path.h:705
Curve const & front() const
Access the first curve in the path.
Definition path.h:443
Path reversed() const
Obtain a reversed version of the current path.
Definition path.cpp:866
void setFinal(Point const &p)
Definition path.h:740
void erase_last()
Definition path.h:700
size_type size() const
Natural size of the path.
Definition path.h:490
const_iterator begin() const
Definition path.h:464
void erase(iterator pos)
Definition path.cpp:913
void appendNew(Args &&... args)
Append a new curve to the path.
Definition path.h:804
void insert(iterator pos, Curve const &curve)
Definition path.cpp:904
void start(Point const &p)
Definition path.cpp:426
Function defined as discrete pieces.
Definition piecewise.h:71
Two-dimensional point that doubles as a vector.
Definition point.h:66
bool isFinite() const
Check whether both coordinates are finite.
Definition point.h:218
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 Point cw() const
Return a point like this point but rotated +90 degrees.
Definition point.h:137
Straight ray from a specific point to infinity.
Definition ray.h:53
Generic sweepline algorithm.
Definition sweeper.h:93
void process()
Process entry and exit events.
Definition sweeper.h:109
Path and its polyline approximation.
Definition Path.h:93
double c[8][4]
const unsigned order
static char const *const current
Definition dir-util.cpp:71
static char const *const parent
Definition dir-util.cpp:70
std::vector< ItemIterator > _active
std::default_random_engine _gen
std::vector< bool > _contains
Geom::PathVector & _paths
double const _precision
Util::TreeifyResult const & _tree
std::vector< Geom::PathVector > _result
int _pos
FillRule const _fill_rule
Geom::PathVector const & _pathv
BezierCurveN< 3 > CubicBezier
Cubic (order 3) Bezier curve.
BezierCurveN< 1 > LineSegment
Line segment.
constexpr Coord lerp(Coord t, Coord a, Coord b)
Numerically stable linear interpolation.
Definition coord.h:97
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
constexpr Coord EPSILON
Default "acceptably small" value.
Definition coord.h:84
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
bool pathv_fully_contains(Geom::PathVector const &a, Geom::PathVector const &b, FillRule fill_rule, double precision)
Checks whether the filled region defined by pathvector a and fill rule fill_rule completely contains ...
Definition geom.cpp:551
Geom::PathVector pathv_to_linear_and_cubic_beziers(Geom::PathVector const &pathv)
Definition geom.cpp:586
Specific geometry functions for Inkscape, not provided my lib2geom.
Geom::Point start
Geom::Point end
vector< vector< Point > > paths
Definition metro.cpp:36
Point midpoint(Point a, Point b)
Various utility functions.
Definition affine.h:22
Coord length(LineSegment const &seg)
Bezier reverse(const Bezier &a)
Definition bezier.h:342
SBasisN< n > divide(SBasisN< n > const &a, SBasisN< n > const &b, int k)
Line make_parallel_line(Point const &p, Line const &line)
Definition line.h:488
bool path_direction(Path const &p)
This function should only be applied to simple paths (regions), as otherwise a boolean winding direct...
Coord distanceSq(Point const &p, Rect const &rect)
Definition rect.cpp:158
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
int centroid(std::vector< Geom::Point > const &p, Geom::Point &centroid, double &area)
polyCentroid: Calculates the centroid (xCentroid, yCentroid) and area of a polygon,...
Definition geom.cpp:366
SBasisN< n > sqrt(SBasisN< n > const &a, int k)
static double area(Geom::Point a, Geom::Point b, Geom::Point c)
static Point intersection_point(Point origin_a, Point vector_a, Point origin_b, Point vector_b)
Line make_bisector_line(LineSegment const &_segment)
Definition line.h:497
bool contains(Path const &p, Point const &i, bool evenodd=true)
Path cubicbezierpath_from_sbasis(D2< SBasis > const &B, double tol)
static Circle touching_circle(D2< SBasis > const &curve, double t, double tol=0.01)
Find circle that touches inside of the curve, with radius matching the curvature, at time value t.
Line make_orthogonal_line(Point const &p, Line const &line)
Definition line.h:479
Bezier derivative(Bezier const &a)
Definition bezier.cpp:282
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
bool are_parallel(Line const &l1, Line const &l2, double eps=EPSILON)
Definition line.h:426
Point unitTangentAt(D2< SBasis > const &a, Coord t, unsigned n=3)
Line make_angle_bisector_line(Point const &A, Point const &O, Point const &B)
Definition line.h:504
T dot(D2< T > const &a, D2< T > const &b)
Definition d2.h:355
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
Piecewise< D2< SBasis > > unitVector(D2< SBasis > const &vect, double tol=.01, unsigned order=3)
TreeifyResult treeify(int N, std::function< bool(int, int)> const &contains)
Given a collection of nodes 0 ... N - 1 and a containment function, attempt to organise the nodes int...
Definition treeify.cpp:9
Helper class to stream background task notifications as a series of messages.
Geom::PathVector outline(Geom::Path const &input, double width, double miter, LineJoinType join, LineCapType butt, double tolerance)
Strokes the path given by input.
void outline_join(Geom::Path &res, Geom::Path const &temp, Geom::Point in_tang, Geom::Point out_tang, double width, double miter, Inkscape::LineJoinType join)
Builds a join on the provided path.
static Glib::ustring join(std::vector< Glib::ustring > const &accels, char const separator)
Geom::PathVector do_offset(Geom::PathVector const &path_in, double to_offset, double tolerance, double miter_limit, FillRule fillrule, Inkscape::LineJoinType join, Geom::Point point, Geom::PathVector &helper_path, Geom::PathVector &mix_pathv_all)
Create a user spected offset from a pathvector.
std::vector< Geom::PathVector > split_non_intersecting_paths(Geom::PathVector &&paths, FillRule fill_rule)
Split a pathvector into its connected components when filled using the given fill rule.
Geom::Path half_outline(Geom::Path const &input, double width, double miter, LineJoinType join, double tolerance)
Offset the input path by width.
static std::optional< Geom::Point > find_interior_point(Geom::Path const &path, FillRule fill_rule, std::default_random_engine &gen)
Attempts to find a point in the interior of a filled path.
bool is_path_empty(Geom::Path const &path)
Check for an empty path.
static T clip(T const &v, T const &a, T const &b)
void flatten(Geom::PathVector &pathv, FillRule fill_rule)
Geom::PathVector sp_pathvector_boolop(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, BooleanOp bop, FillRule fra, FillRule frb)
Perform a boolean operation on two pathvectors.
Boolean operations.
Path intersection.
callback interface for SVG path data
unsigned n1
unsigned n2
GList * items
SpatialIndex::ISpatialIndex * tree
auto len
Definition safe-printf.h:21
Conversion between SBasis and Bezier basis polynomials.
std::vector< int > preorder
The preorder traversal of the nodes, a permutation of {0, ..., N - 1}.
Definition treeify.h:12
std::vector< int > num_children
For each node, the number of direct children.
Definition treeify.h:13
Definition curve.h:24
Class for implementing sweepline algorithms.
int index
void dot(Cairo::RefPtr< Cairo::Context > &cr, double x, double y)
double width
int winding(PathVector ps, Point p)
Definition uncross.cpp:57