1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * @file
4  * PowerStroke LPE implementation. Creates curves with modifiable stroke width.
5  */
6 /* Authors:
7  *   Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
8  *
9  * Copyright (C) 2010-2012 Authors
10  *
11  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12  */
13 
14 #include <2geom/elliptical-arc.h>
15 #include <2geom/path-sink.h>
16 #include <2geom/path-intersection.h>
17 #include <2geom/circle.h>
18 
19 #include "live_effects/lpe-powerstroke.h"
20 #include "live_effects/lpe-powerstroke-interpolators.h"
21 #include "live_effects/lpe-simplify.h"
22 #include "live_effects/lpeobject.h"
23 #include "live_effects/fill-conversion.h"
24 
25 #include "desktop-style.h"
26 #include "style.h"
27 
28 #include "display/curve.h"
29 #include "display/control/canvas-item-enums.h"
30 #include "helper/geom.h"
31 #include "object/sp-shape.h"
32 #include "svg/css-ostringstream.h"
33 #include "svg/svg-color.h"
34 
35 // TODO due to internal breakage in glibmm headers, this must be last:
36 #include <glibmm/i18n.h>
37 
38 namespace Geom {
39 // should all be moved to 2geom at some point
40 
41 /** Find the point where two straight lines cross.
42 */
intersection_point(Point const & origin_a,Point const & vector_a,Point const & origin_b,Point const & vector_b)43 static std::optional<Point> intersection_point( Point const & origin_a, Point const & vector_a,
44                                            Point const & origin_b, Point const & vector_b)
45 {
46     Coord denom = cross(vector_a, vector_b);
47     if (!are_near(denom,0.)){
48         Coord t = (cross(vector_b, origin_a) + cross(origin_b, vector_b)) / denom;
49         return origin_a + t * vector_a;
50     }
51     return std::nullopt;
52 }
53 
sbasis_to_cubicbezier(Geom::D2<Geom::SBasis> const & sbasis_in)54 static Geom::CubicBezier sbasis_to_cubicbezier(Geom::D2<Geom::SBasis> const & sbasis_in)
55 {
56     std::vector<Geom::Point> temp;
57     sbasis_to_bezier(temp, sbasis_in, 4);
58     return Geom::CubicBezier( temp );
59 }
60 
61 /**
62  * document this!
63  * very quick: this finds the ellipse with minimum eccentricity
64    passing through point P and Q, with tangent PO at P and QO at Q
65    http://mathforum.org/kb/message.jspa?messageID=7471596&tstart=0
66  */
find_ellipse(Point P,Point Q,Point O)67 static Ellipse find_ellipse(Point P, Point Q, Point O)
68 {
69     Point p = P - O;
70     Point q = Q - O;
71     Coord K = 4 * dot(p,q) / (L2sq(p) + L2sq(q));
72 
73     double cross = p[Y]*q[X] - p[X]*q[Y];
74     double a = -q[Y]/cross;
75     double b = q[X]/cross;
76     double c = (O[X]*q[Y] - O[Y]*q[X])/cross;
77 
78     double d = p[Y]/cross;
79     double e = -p[X]/cross;
80     double f = (-O[X]*p[Y] + O[Y]*p[X])/cross;
81 
82     // Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0
83     double A = (a*d*K+d*d+a*a);
84     double B = (a*e*K+b*d*K+2*d*e+2*a*b);
85     double C = (b*e*K+e*e+b*b);
86     double D = (a*f*K+c*d*K+2*d*f-2*d+2*a*c-2*a);
87     double E = (b*f*K+c*e*K+2*e*f-2*e+2*b*c-2*b);
88     double F = c*f*K+f*f-2*f+c*c-2*c+1;
89 
90     return Ellipse(A, B, C, D, E, F);
91 }
92 
93 /**
94  * Find circle that touches inside of the curve, with radius matching the curvature, at time value \c t.
95  * Because this method internally uses unitTangentAt, t should be smaller than 1.0 (see unitTangentAt).
96  */
touching_circle(D2<SBasis> const & curve,double t,double tol=0.01)97 static Circle touching_circle( D2<SBasis> const &curve, double t, double tol=0.01 )
98 {
99     //Piecewise<SBasis> k = curvature(curve, tol);
100     D2<SBasis> dM=derivative(curve);
101     if ( are_near(L2sq(dM(t)),0.) && (dM[0].size() > 1) && (dM[1].size() > 1) ) {
102         dM=derivative(dM);
103     }
104     if ( are_near(L2sq(dM(t)),0.) && (dM[0].size() > 1) && (dM[1].size() > 1) ) {   // try second time
105         dM=derivative(dM);
106     }
107     if ( dM.isZero(tol) || (are_near(L2sq(dM(t)),0.) && (dM[0].size() > 1) && (dM[1].size() > 1) )) {   // admit defeat
108         return Geom::Circle(Geom::Point(0., 0.), 0.);
109     }
110     Piecewise<D2<SBasis> > unitv = unitVector(dM,tol);
111     if (unitv.empty()) {   // admit defeat
112         return Geom::Circle(Geom::Point(0., 0.), 0.);
113     }
114     Piecewise<SBasis> dMlength = dot(Piecewise<D2<SBasis> >(dM),unitv);
115     Piecewise<SBasis> k = cross(derivative(unitv),unitv);
116     k = divide(k,dMlength,tol,3);
117     double curv = k(t); // note that this value is signed
118 
119     Geom::Point normal = unitTangentAt(curve, t).cw();
120     double radius = 1/curv;
121     Geom::Point center = curve(t) + radius*normal;
122     return Geom::Circle(center, fabs(radius));
123 }
124 
125 } // namespace Geom
126 
127 namespace Inkscape {
128 namespace LivePathEffect {
129 
130 static const Util::EnumData<unsigned> InterpolatorTypeData[] = {
131     {Geom::Interpolate::INTERP_CUBICBEZIER_SMOOTH,  N_("CubicBezierSmooth"), "CubicBezierSmooth"},
132     {Geom::Interpolate::INTERP_LINEAR          , N_("Linear"), "Linear"},
133     {Geom::Interpolate::INTERP_CUBICBEZIER          , N_("CubicBezierFit"), "CubicBezierFit"},
134     {Geom::Interpolate::INTERP_CUBICBEZIER_JOHAN     , N_("CubicBezierJohan"), "CubicBezierJohan"},
135     {Geom::Interpolate::INTERP_SPIRO  , N_("SpiroInterpolator"), "SpiroInterpolator"},
136     {Geom::Interpolate::INTERP_CENTRIPETAL_CATMULLROM, N_("Centripetal Catmull-Rom"), "CentripetalCatmullRom"}
137 };
138 static const Util::EnumDataConverter<unsigned> InterpolatorTypeConverter(InterpolatorTypeData, sizeof(InterpolatorTypeData)/sizeof(*InterpolatorTypeData));
139 
140 enum LineJoinType {
141   LINEJOIN_BEVEL,
142   LINEJOIN_ROUND,
143   LINEJOIN_EXTRP_MITER,
144   LINEJOIN_MITER,
145   LINEJOIN_SPIRO,
146   LINEJOIN_EXTRP_MITER_ARC
147 };
148 static const Util::EnumData<unsigned> LineJoinTypeData[] = {
149     {LINEJOIN_BEVEL, N_("Beveled"),   "bevel"},
150     {LINEJOIN_ROUND, N_("Rounded"),   "round"},
151 //    {LINEJOIN_EXTRP_MITER,  N_("Extrapolated"),      "extrapolated"}, // disabled because doesn't work well
152     {LINEJOIN_EXTRP_MITER_ARC, N_("Extrapolated arc"),     "extrp_arc"},
153     {LINEJOIN_MITER, N_("Miter"),     "miter"},
154     {LINEJOIN_SPIRO, N_("Spiro"),     "spiro"},
155 };
156 static const Util::EnumDataConverter<unsigned> LineJoinTypeConverter(LineJoinTypeData, sizeof(LineJoinTypeData)/sizeof(*LineJoinTypeData));
157 
LPEPowerStroke(LivePathEffectObject * lpeobject)158 LPEPowerStroke::LPEPowerStroke(LivePathEffectObject *lpeobject) :
159     Effect(lpeobject),
160     offset_points(_("Offset points"), _("Offset points"), "offset_points", &wr, this),
161     not_jump(_("No jumping handles"), _("Allow to move handles along the path without them automatically attaching to the nearest path segment"), "not_jump", &wr, this, false),
162     sort_points(_("Sort points"), _("Sort offset points according to their time value along the curve"), "sort_points", &wr, this, true),
163     interpolator_type(_("Interpolator type:"), _("Determines which kind of interpolator will be used to interpolate between stroke width along the path"), "interpolator_type", InterpolatorTypeConverter, &wr, this, Geom::Interpolate::INTERP_CENTRIPETAL_CATMULLROM),
164     interpolator_beta(_("Smoothness:"), _("Sets the smoothness for the CubicBezierJohan interpolator; 0 = linear interpolation, 1 = smooth"), "interpolator_beta", &wr, this, 0.2),
165     scale_width(_("Width factor:"), _("Scale the stroke's width uniformly along the whole path"), "scale_width", &wr, this, 1.0),
166     start_linecap_type(_("Start cap:"), _("Determines the shape of the path's start"), "start_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_ZERO_WIDTH),
167     linejoin_type(_("Join:"), _("Determines the shape of the path's corners"), "linejoin_type", LineJoinTypeConverter, &wr, this, LINEJOIN_ROUND),
168     miter_limit(_("Miter limit:"), _("Maximum length of the miter (in units of stroke width)"), "miter_limit", &wr, this, 4.),
169     end_linecap_type(_("End cap:"), _("Determines the shape of the path's end"), "end_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_ZERO_WIDTH)
170 {
171     show_orig_path = true;
172 
173     /// @todo offset_points are initialized with empty path, is that bug-save?
174 
175     interpolator_beta.addSlider(true);
176     interpolator_beta.param_set_range(0.,1.);
177 
178     registerParameter(&offset_points);
179     registerParameter(&not_jump);
180     registerParameter(&sort_points);
181     registerParameter(&interpolator_type);
182     registerParameter(&interpolator_beta);
183     registerParameter(&start_linecap_type);
184     registerParameter(&linejoin_type);
185     registerParameter(&miter_limit);
186     registerParameter(&scale_width);
187     registerParameter(&end_linecap_type);
188     scale_width.param_set_range(0.0, std::numeric_limits<double>::max());
189     scale_width.param_set_increments(0.1, 0.1);
190     scale_width.param_set_digits(4);
191     recusion_limit = 0;
192     has_recursion = false;
193 }
194 
195 LPEPowerStroke::~LPEPowerStroke() = default;
196 
197 void
doBeforeEffect(SPLPEItem const * lpeItem)198 LPEPowerStroke::doBeforeEffect(SPLPEItem const *lpeItem)
199 {
200     offset_points.set_scale_width(scale_width);
201     if (has_recursion) {
202         has_recursion = false;
203         adjustForNewPath(pathvector_before_effect);
204     }
205 }
206 
applyStyle(SPLPEItem * lpeitem)207 void LPEPowerStroke::applyStyle(SPLPEItem *lpeitem)
208 {
209     lpe_shape_convert_stroke_and_fill(SP_SHAPE(lpeitem));
210 }
211 
212 void
doOnApply(SPLPEItem const * lpeitem)213 LPEPowerStroke::doOnApply(SPLPEItem const* lpeitem)
214 {
215     if (auto shape = dynamic_cast<SPShape const *>(lpeitem)) {
216         SPLPEItem* item = const_cast<SPLPEItem*>(lpeitem);
217         std::vector<Geom::Point> points;
218         Geom::PathVector const &pathv = pathv_to_linear_and_cubic_beziers(shape->curve()->get_pathvector());
219         double width = (lpeitem && lpeitem->style) ? lpeitem->style->stroke_width.computed / 2 : 1.;
220         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
221         Glib::ustring pref_path_pp = "/live_effects/powerstroke/powerpencil";
222         bool powerpencil = prefs->getBool(pref_path_pp, false);
223         bool clipboard = offset_points.data().size() > 0;
224         if (!powerpencil) {
225             applyStyle(item);
226         }
227         if (!clipboard && !powerpencil) {
228             item->updateRepr();
229             if (pathv.empty()) {
230                 points.emplace_back(0.2,width );
231                 points.emplace_back(0.5, width);
232                 points.emplace_back(0.8, width);
233             } else {
234                 Geom::Path const &path = pathv.front();
235                 Geom::Path::size_type const size = path.size_default();
236                 if (!path.closed()) {
237                     points.emplace_back(0.2, width);
238                 }
239                 points.emplace_back(0.5 * size, width);
240                 if (!path.closed()) {
241                     points.emplace_back(size - 0.2, width);
242                 }
243             }
244             offset_points.param_set_and_write_new_value(points);
245         }
246         offset_points.set_scale_width(scale_width);
247     } else {
248         if (!SP_IS_SHAPE(lpeitem)) {
249             g_warning("LPE Powerstroke can only be applied to shapes (not groups).");
250         }
251     }
252 }
253 
doOnRemove(SPLPEItem const * lpeitem)254 void LPEPowerStroke::doOnRemove(SPLPEItem const* lpeitem)
255 {
256     if (SP_IS_SHAPE(lpeitem) && !keep_paths) {
257         lpe_shape_revert_stroke_and_fill(SP_SHAPE(lpeitem), offset_points.median_width()*2);
258     }
259 }
260 
261 void
adjustForNewPath(Geom::PathVector const & path_in)262 LPEPowerStroke::adjustForNewPath(Geom::PathVector const & path_in)
263 {
264     if (!path_in.empty()) {
265         offset_points.recalculate_controlpoints_for_new_pwd2(path_in[0].toPwSb());
266     }
267 }
268 
compare_offsets(Geom::Point first,Geom::Point second)269 static bool compare_offsets (Geom::Point first, Geom::Point second)
270 {
271     return first[Geom::X] < second[Geom::X];
272 }
273 
path_from_piecewise_fix_cusps(Geom::Piecewise<Geom::D2<Geom::SBasis>> const & B,Geom::Piecewise<Geom::SBasis> const & y,LineJoinType jointype,double miter_limit,double tol=Geom::EPSILON)274 static Geom::Path path_from_piecewise_fix_cusps( Geom::Piecewise<Geom::D2<Geom::SBasis> > const & B,
275                                                  Geom::Piecewise<Geom::SBasis> const & y, // width path
276                                                  LineJoinType jointype,
277                                                  double miter_limit,
278                                                  double tol=Geom::EPSILON)
279 {
280 /* per definition, each discontinuity should be fixed with a join-ending, as defined by linejoin_type
281 */
282     Geom::PathBuilder pb;
283     Geom::OptRect bbox = bounds_fast(B);
284     if (B.empty() || !bbox) {
285         return pb.peek().front();
286     }
287 
288     pb.setStitching(true);
289 
290     Geom::Point start = B[0].at0();
291     pb.moveTo(start);
292     build_from_sbasis(pb, B[0], tol, false);
293     unsigned prev_i = 0;
294     for (unsigned i=1; i < B.size(); i++) {
295         // Skip degenerate segments. The number below was determined, after examining
296         // very many paths with powerstrokes of all shapes and sizes, to allow filtering out most
297         // degenerate segments without losing significant quality; it is close to 1/256.
298         if (B[i].isConstant(4e-3)) {
299             continue;
300         }
301         if (!are_near(B[prev_i].at1(), B[i].at0(), tol) )
302         { // discontinuity found, so fix it :-)
303             double width = y( B.cuts[i] );
304 
305             Geom::Point tang1 = -unitTangentAt(reverse(B[prev_i]),0.); // = unitTangentAt(B[prev_i],1);
306             Geom::Point tang2 = unitTangentAt(B[i],0);
307             Geom::Point discontinuity_vec = B[i].at0() - B[prev_i].at1();
308             bool on_outside = ( dot(tang1, discontinuity_vec) >= 0. );
309 
310             if (on_outside) {
311                 // we are on the outside: add some type of join!
312                 switch (jointype) {
313                 case LINEJOIN_ROUND: {
314                     /* for constant width paths, the rounding is a circular arc (rx == ry),
315                        for non-constant width paths, the rounding can be done with an ellipse but is hard and ambiguous.
316                        The elliptical arc should go through the discontinuity's start and end points (of course!)
317                        and also should match the discontinuity tangents at those start and end points.
318                        To resolve the ambiguity, the elliptical arc with minimal eccentricity should be chosen.
319                        A 2Geom method was created to do exactly this :)
320                        */
321 
322                     std::optional<Geom::Point> O = intersection_point( B[prev_i].at1(), tang1,
323                                                                               B[i].at0(), tang2 );
324                     if (!O) {
325                         // no center found, i.e. 180 degrees round
326                        pb.lineTo(B[i].at0()); // default to bevel for too shallow cusp angles
327                        break;
328                     }
329 
330                     Geom::Ellipse ellipse;
331                     try {
332                         ellipse = find_ellipse(B[prev_i].at1(), B[i].at0(), *O);
333                     }
334                     catch (Geom::LogicalError &e) {
335                         // 2geom did not find a fitting ellipse, this happens for weird thick paths :)
336                         // do bevel, and break
337                          pb.lineTo(B[i].at0());
338                          break;
339                     }
340 
341                     // check if ellipse.ray is within 'sane' range.
342                     if ( ( fabs(ellipse.ray(Geom::X)) > 1e6 ) ||
343                          ( fabs(ellipse.ray(Geom::Y)) > 1e6 ) )
344                     {
345                         // do bevel, and break
346                          pb.lineTo(B[i].at0());
347                          break;
348                     }
349 
350                     pb.arcTo( ellipse.ray(Geom::X), ellipse.ray(Geom::Y), ellipse.rotationAngle(),
351                               false, width < 0, B[i].at0() );
352 
353                     break;
354                 }
355                 case LINEJOIN_EXTRP_MITER: {
356                     Geom::D2<Geom::SBasis> newcurve1 = B[prev_i] * Geom::reflection(rot90(tang1), B[prev_i].at1());
357                     Geom::CubicBezier bzr1 = sbasis_to_cubicbezier( reverse(newcurve1) );
358 
359                     Geom::D2<Geom::SBasis> newcurve2 = B[i] * Geom::reflection(rot90(tang2), B[i].at0());
360                     Geom::CubicBezier bzr2 = sbasis_to_cubicbezier( reverse(newcurve2) );
361 
362                     Geom::Crossings cross = crossings(bzr1, bzr2);
363                     if (cross.empty()) {
364                         // empty crossing: default to bevel
365                         pb.lineTo(B[i].at0());
366                     } else {
367                         // check size of miter
368                         Geom::Point point_on_path = B[prev_i].at1() - rot90(tang1) * width;
369                         Geom::Coord len = distance(bzr1.pointAt(cross[0].ta), point_on_path);
370                         if (len > fabs(width) * miter_limit) {
371                             // miter too big: default to bevel
372                             pb.lineTo(B[i].at0());
373                         } else {
374                             std::pair<Geom::CubicBezier, Geom::CubicBezier> sub1 = bzr1.subdivide(cross[0].ta);
375                             std::pair<Geom::CubicBezier, Geom::CubicBezier> sub2 = bzr2.subdivide(cross[0].tb);
376                             pb.curveTo(sub1.first[1], sub1.first[2], sub1.first[3]);
377                             pb.curveTo(sub2.second[1], sub2.second[2], sub2.second[3]);
378                         }
379                     }
380                     break;
381                 }
382                 case LINEJOIN_EXTRP_MITER_ARC: {
383                     // Extrapolate using the curvature at the end of the path segments to join
384                     Geom::Circle circle1 = Geom::touching_circle(reverse(B[prev_i]), 0.0);
385                     Geom::Circle circle2 = Geom::touching_circle(B[i], 0.0);
386                     std::vector<Geom::ShapeIntersection> solutions;
387                     solutions = circle1.intersect(circle2);
388                     if (solutions.size() == 2) {
389                         Geom::Point sol(0.,0.);
390                         bool solok = true;
391                         bool point0bad = false;
392                         bool point1bad = false;
393                         if ( dot(tang2, solutions[0].point() - B[i].at0()) > 0)
394                         {
395                             // points[0] is bad, choose points[1]
396                             point0bad = true;
397                         }
398                         if ( dot(tang2, solutions[1].point() - B[i].at0()) > 0)
399                         {
400                             // points[1] is bad, choose points[0]
401                             point1bad = true;
402                         }
403                         if (!point0bad && !point1bad ) {
404                             // both points are good, choose nearest
405                             sol = ( distanceSq(B[i].at0(), solutions[0].point()) < distanceSq(B[i].at0(), solutions[1].point()) ) ?
406                                     solutions[0].point() : solutions[1].point();
407                         } else if (!point0bad) {
408                             sol = solutions[0].point();
409                         } else if (!point1bad) {
410                             sol = solutions[1].point();
411                         } else {
412                             solok = false;
413                         }
414                         (*bbox).expandBy (bbox->width()/4);
415 
416                         if (!(*bbox).contains(sol)) {
417                             solok = false;
418                         }
419                         Geom::EllipticalArc *arc0 = nullptr;
420                         Geom::EllipticalArc *arc1 = nullptr;
421                         bool build = false;
422                         if (solok) {
423                             arc0 = circle1.arc(B[prev_i].at1(), 0.5*(B[prev_i].at1()+sol), sol);
424                             arc1 = circle2.arc(sol, 0.5*(sol+B[i].at0()), B[i].at0());
425                             if (arc0) {
426                                 // FIX: Some assertions errors here
427                                 build_from_sbasis(pb,arc0->toSBasis(), tol, false);
428                                 build = true;
429                             } else if (arc1) {
430                                 std::optional<Geom::Point> p = intersection_point( B[prev_i].at1(), tang1,
431                                                                                 B[i].at0(), tang2 );
432                                 if (p) {
433                                     // check size of miter
434                                     Geom::Point point_on_path = B[prev_i].at1() - rot90(tang1) * width;
435                                     Geom::Coord len = distance(*p, point_on_path);
436                                     if (len <= fabs(width) * miter_limit) {
437                                         // miter OK
438                                         pb.lineTo(*p);
439                                         build = true;
440                                     }
441                                 }
442                             }
443                             if (build) {
444                                 build_from_sbasis(pb,arc1->toSBasis(), tol, false);
445                             } else if (arc0) {
446                                 pb.lineTo(B[i].at0());
447                             }
448                         }
449                         if (!solok || !(arc0 && build)) {
450                             // fall back to miter
451                             std::optional<Geom::Point> p = intersection_point( B[prev_i].at1(), tang1,
452                                                                                 B[i].at0(), tang2 );
453                             if (p) {
454                                 // check size of miter
455                                 Geom::Point point_on_path = B[prev_i].at1() - rot90(tang1) * width;
456                                 Geom::Coord len = distance(*p, point_on_path);
457                                 if (len <= fabs(width) * miter_limit) {
458                                     // miter OK
459                                     pb.lineTo(*p);
460                                 }
461                             }
462                             pb.lineTo(B[i].at0());
463                         }
464                         if (arc0) {
465                             delete arc0;
466                             arc0 = nullptr;
467                         }
468                         if (arc1) {
469                             delete arc1;
470                             arc1 = nullptr;
471                         }
472                     } else {
473                         // fall back to miter
474                         std::optional<Geom::Point> p = intersection_point( B[prev_i].at1(), tang1,
475                                                                             B[i].at0(), tang2 );
476                         if (p) {
477                             // check size of miter
478                             Geom::Point point_on_path = B[prev_i].at1() - rot90(tang1) * width;
479                             Geom::Coord len = distance(*p, point_on_path);
480                             if (len <= fabs(width) * miter_limit) {
481                                 // miter OK
482                                 pb.lineTo(*p);
483                             }
484                         }
485                         pb.lineTo(B[i].at0());
486                     }
487                     /*else if (solutions == 1) { // one circle is inside the other
488                         // don't know what to do: default to bevel
489                         pb.lineTo(B[i].at0());
490                     } else { // no intersections
491                         // don't know what to do: default to bevel
492                         pb.lineTo(B[i].at0());
493                     } */
494 
495                     break;
496                 }
497                 case LINEJOIN_MITER: {
498                     std::optional<Geom::Point> p = intersection_point( B[prev_i].at1(), tang1,
499                                                                          B[i].at0(), tang2 );
500                     if (p) {
501                         // check size of miter
502                         Geom::Point point_on_path = B[prev_i].at1() - rot90(tang1) * width;
503                         Geom::Coord len = distance(*p, point_on_path);
504                         if (len <= fabs(width) * miter_limit) {
505                             // miter OK
506                             pb.lineTo(*p);
507                         }
508                     }
509                     pb.lineTo(B[i].at0());
510                     break;
511                 }
512                 case LINEJOIN_SPIRO: {
513                     Geom::Point direction = B[i].at0() - B[prev_i].at1();
514                     double tang1_sign = dot(direction,tang1);
515                     double tang2_sign = dot(direction,tang2);
516 
517                     Spiro::spiro_cp *controlpoints = g_new (Spiro::spiro_cp, 4);
518                     controlpoints[0].x = (B[prev_i].at1() - tang1_sign*tang1)[Geom::X];
519                     controlpoints[0].y = (B[prev_i].at1() - tang1_sign*tang1)[Geom::Y];
520                     controlpoints[0].ty = '{';
521                     controlpoints[1].x = B[prev_i].at1()[Geom::X];
522                     controlpoints[1].y = B[prev_i].at1()[Geom::Y];
523                     controlpoints[1].ty = ']';
524                     controlpoints[2].x = B[i].at0()[Geom::X];
525                     controlpoints[2].y = B[i].at0()[Geom::Y];
526                     controlpoints[2].ty = '[';
527                     controlpoints[3].x = (B[i].at0() + tang2_sign*tang2)[Geom::X];
528                     controlpoints[3].y = (B[i].at0() + tang2_sign*tang2)[Geom::Y];
529                     controlpoints[3].ty = '}';
530 
531                     Geom::Path spiro;
532                     Spiro::spiro_run(controlpoints, 4, spiro);
533                     pb.append(spiro.portion(1, spiro.size_open() - 1));
534                     break;
535                 }
536                 case LINEJOIN_BEVEL:
537                 default:
538                     pb.lineTo(B[i].at0());
539                     break;
540                 }
541 
542                 build_from_sbasis(pb, B[i], tol, false);
543 
544             } else {
545                 // we are on inside of corner!
546                 Geom::Path bzr1 = path_from_sbasis( B[prev_i], tol );
547                 Geom::Path bzr2 = path_from_sbasis( B[i], tol );
548                 Geom::Crossings cross = crossings(bzr1, bzr2);
549                 if (cross.size() != 1) {
550                     // empty crossing or too many crossings: default to bevel
551                     pb.lineTo(B[i].at0());
552                     pb.append(bzr2);
553                 } else {
554                     // :-) quick hack:
555                     for (unsigned i=0; i < bzr1.size_open(); ++i) {
556                         pb.backspace();
557                     }
558 
559                     pb.append( bzr1.portion(0, cross[0].ta) );
560                     pb.append( bzr2.portion(cross[0].tb, bzr2.size_open()) );
561                 }
562             }
563         } else {
564             build_from_sbasis(pb, B[i], tol, false);
565         }
566 
567         prev_i = i;
568     }
569     pb.flush();
570     return pb.peek().front();
571 }
572 
573 Geom::PathVector
doEffect_path(Geom::PathVector const & path_in)574 LPEPowerStroke::doEffect_path (Geom::PathVector const & path_in)
575 {
576     using namespace Geom;
577 
578     Geom::PathVector path_out;
579     if (path_in.empty()) {
580         return path_in;
581     }
582     Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers(path_in);
583     Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = pathv[0].toPwSb();
584     if (pwd2_in.empty()) {
585         return path_in;
586     }
587     Piecewise<D2<SBasis> > der = derivative(pwd2_in);
588     if (der.empty()) {
589         return path_in;
590     }
591     Piecewise<D2<SBasis> > n = unitVector(der,0.00001);
592     if (n.empty()) {
593         return path_in;
594     }
595 
596     n = rot90(n);
597     offset_points.set_pwd2(pwd2_in, n);
598 
599     LineCapType end_linecap = static_cast<LineCapType>(end_linecap_type.get_value());
600     LineCapType start_linecap = static_cast<LineCapType>(start_linecap_type.get_value());
601 
602     std::vector<Geom::Point> ts_no_scale = offset_points.data();
603     if (ts_no_scale.empty()) {
604         return path_in;
605     }
606     std::vector<Geom::Point> ts;
607     for (auto & tsp : ts_no_scale) {
608         Geom::Point p = Geom::Point(tsp[Geom::X], tsp[Geom::Y] * scale_width);
609         ts.push_back(p);
610     }
611     if (sort_points) {
612         sort(ts.begin(), ts.end(), compare_offsets);
613     }
614     // create stroke path where points (x,y) := (t, offset)
615     Geom::Interpolate::Interpolator *interpolator = Geom::Interpolate::Interpolator::create(static_cast<Geom::Interpolate::InterpolatorType>(interpolator_type.get_value()));
616     if (Geom::Interpolate::CubicBezierJohan *johan = dynamic_cast<Geom::Interpolate::CubicBezierJohan*>(interpolator)) {
617         johan->setBeta(interpolator_beta);
618     }
619     if (Geom::Interpolate::CubicBezierSmooth *smooth = dynamic_cast<Geom::Interpolate::CubicBezierSmooth*>(interpolator)) {
620         smooth->setBeta(interpolator_beta);
621     }
622     if (pathv[0].closed()) {
623         std::vector<Geom::Point> ts_close;
624         //we have only one knot or overwrite before
625         Geom::Point start = Geom::Point( pwd2_in.domain().min(), ts.front()[Geom::Y]);
626         Geom::Point end   = Geom::Point( pwd2_in.domain().max(), ts.front()[Geom::Y]);
627         if (ts.size() > 1) {
628             end = Geom::Point(pwd2_in.domain().max(), 0);
629             Geom::Point tmpstart(0, 0);
630             tmpstart[Geom::X] = end[Geom::X] + ts.front()[Geom::X];
631             tmpstart[Geom::Y] = ts.front()[Geom::Y];
632             ts_close.push_back(ts.back());
633             ts_close.push_back(middle_point(tmpstart, ts.back()));
634             ts_close.push_back(tmpstart);
635             Geom::Path closepath = interpolator->interpolateToPath(ts_close);
636             end = closepath.pointAt(Geom::nearest_time(end, closepath));
637             end[Geom::X] = pwd2_in.domain().max();
638             start = end;
639             start[Geom::X] = pwd2_in.domain().min();
640         }
641         ts.insert(ts.begin(), start );
642         ts.push_back( end );
643         ts_close.clear();
644     } else {
645         // add width data for first and last point on the path
646         // depending on cap type, these first and last points have width zero or take the width from the closest width point.
647         ts.insert(ts.begin(), Point( pwd2_in.domain().min(),
648                                     (start_linecap==LINECAP_ZERO_WIDTH) ? 0. : ts.front()[Geom::Y]) );
649         ts.emplace_back( pwd2_in.domain().max(),
650                              (end_linecap==LINECAP_ZERO_WIDTH) ? 0. : ts.back()[Geom::Y] );
651     }
652 
653     // do the interpolation in a coordinate system that is more alike to the on-canvas knots,
654     // instead of the heavily compressed coordinate system of (segment_no offset, Y) in which the knots are stored
655     double pwd2_in_arclength = length(pwd2_in);
656     double xcoord_scaling = pwd2_in_arclength / ts.back()[Geom::X];
657     for (auto & t : ts) {
658         t[Geom::X] *= xcoord_scaling;
659     }
660 
661     Geom::Path strokepath = interpolator->interpolateToPath(ts);
662     delete interpolator;
663 
664     // apply the inverse knot-xcoord scaling that was applied before the interpolation
665     strokepath *= Scale(1/xcoord_scaling, 1);
666 
667     D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(strokepath.toPwSb());
668     Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
669     Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
670     // find time values for which x lies outside path domain
671     // and only take portion of x and y that lies within those time values
672     std::vector< double > rtsmin = roots (x - pwd2_in.domain().min());
673     std::vector< double > rtsmax = roots (x + pwd2_in.domain().max());
674     if ( !rtsmin.empty() && !rtsmax.empty() ) {
675         x = portion(x, rtsmin.at(0), rtsmax.at(0));
676         y = portion(y, rtsmin.at(0), rtsmax.at(0));
677     }
678 
679     LineJoinType jointype = static_cast<LineJoinType>(linejoin_type.get_value());
680     if (x.empty() || y.empty()) {
681         return path_in;
682     }
683     Piecewise<D2<SBasis> > pwd2_out   = compose(pwd2_in,x) + y*compose(n,x);
684     Piecewise<D2<SBasis> > mirrorpath = reverse( compose(pwd2_in,x) - y*compose(n,x));
685 
686     Geom::Path fixed_path       = path_from_piecewise_fix_cusps( pwd2_out,   y,          jointype, miter_limit, LPE_CONVERSION_TOLERANCE);
687     Geom::Path fixed_mirrorpath = path_from_piecewise_fix_cusps( mirrorpath, reverse(y), jointype, miter_limit, LPE_CONVERSION_TOLERANCE);
688     if (pathv[0].closed()) {
689         fixed_path.close(true);
690         path_out.push_back(fixed_path);
691         fixed_mirrorpath.close(true);
692         path_out.push_back(fixed_mirrorpath);
693     } else {
694         // add linecaps...
695         switch (end_linecap) {
696             case LINECAP_ZERO_WIDTH:
697                 // do nothing
698                 break;
699             case LINECAP_PEAK:
700             {
701                 Geom::Point end_deriv = -unitTangentAt( reverse(pwd2_in.segs.back()), 0.);
702                 double radius = 0.5 * distance(fixed_path.finalPoint(), fixed_mirrorpath.initialPoint());
703                 Geom::Point midpoint = 0.5*(fixed_path.finalPoint() + fixed_mirrorpath.initialPoint()) + radius*end_deriv;
704                 fixed_path.appendNew<LineSegment>(midpoint);
705                 fixed_path.appendNew<LineSegment>(fixed_mirrorpath.initialPoint());
706                 break;
707             }
708             case LINECAP_SQUARE:
709             {
710                 Geom::Point end_deriv = -unitTangentAt( reverse(pwd2_in.segs.back()), 0.);
711                 double radius = 0.5 * distance(fixed_path.finalPoint(), fixed_mirrorpath.initialPoint());
712                 fixed_path.appendNew<LineSegment>( fixed_path.finalPoint() + radius*end_deriv );
713                 fixed_path.appendNew<LineSegment>( fixed_mirrorpath.initialPoint() + radius*end_deriv );
714                 fixed_path.appendNew<LineSegment>( fixed_mirrorpath.initialPoint() );
715                 break;
716             }
717             case LINECAP_BUTT:
718             {
719                 fixed_path.appendNew<LineSegment>( fixed_mirrorpath.initialPoint() );
720                 break;
721             }
722             case LINECAP_ROUND:
723             default:
724             {
725                 double radius1 = 0.5 * distance(fixed_path.finalPoint(), fixed_mirrorpath.initialPoint());
726                 fixed_path.appendNew<EllipticalArc>( radius1, radius1, M_PI/2., false, y.lastValue() < 0, fixed_mirrorpath.initialPoint() );
727                 break;
728             }
729         }
730 
731         fixed_path.append(fixed_mirrorpath);
732         switch (start_linecap) {
733             case LINECAP_ZERO_WIDTH:
734                 // do nothing
735                 break;
736             case LINECAP_PEAK:
737             {
738                 Geom::Point start_deriv = unitTangentAt( pwd2_in.segs.front(), 0.);
739                 double radius = 0.5 * distance(fixed_path.initialPoint(), fixed_mirrorpath.finalPoint());
740                 Geom::Point midpoint = 0.5*(fixed_mirrorpath.finalPoint() + fixed_path.initialPoint()) - radius*start_deriv;
741                 fixed_path.appendNew<LineSegment>( midpoint );
742                 fixed_path.appendNew<LineSegment>( fixed_path.initialPoint() );
743                 break;
744             }
745             case LINECAP_SQUARE:
746             {
747                 Geom::Point start_deriv = unitTangentAt( pwd2_in.segs.front(), 0.);
748                 double radius = 0.5 * distance(fixed_path.initialPoint(), fixed_mirrorpath.finalPoint());
749                 fixed_path.appendNew<LineSegment>( fixed_mirrorpath.finalPoint() - radius*start_deriv );
750                 fixed_path.appendNew<LineSegment>( fixed_path.initialPoint() - radius*start_deriv );
751                 fixed_path.appendNew<LineSegment>( fixed_path.initialPoint() );
752                 break;
753             }
754             case LINECAP_BUTT:
755             {
756                 fixed_path.appendNew<LineSegment>( fixed_path.initialPoint() );
757                 break;
758             }
759             case LINECAP_ROUND:
760             default:
761             {
762                 double radius2 = 0.5 * distance(fixed_path.initialPoint(), fixed_mirrorpath.finalPoint());
763                 fixed_path.appendNew<EllipticalArc>( radius2, radius2, M_PI/2., false, y.firstValue() < 0, fixed_path.initialPoint() );
764                 break;
765             }
766         }
767         fixed_path.close(true);
768         path_out.push_back(fixed_path);
769     }
770     if (path_out.empty()) {
771         return path_in;
772         // doEffect_path (path_in);
773     }
774     return path_out;
775 }
776 
transform_multiply(Geom::Affine const & postmul,bool)777 void LPEPowerStroke::transform_multiply(Geom::Affine const &postmul, bool /*set*/)
778 {
779     offset_points.param_transform_multiply(postmul, false);
780 }
781 
doAfterEffect(SPLPEItem const * lpeitem,SPCurve * curve)782 void LPEPowerStroke::doAfterEffect(SPLPEItem const *lpeitem, SPCurve *curve)
783 {
784     if (pathvector_before_effect[0].size() == pathvector_after_effect[0].size()) {
785         if (recusion_limit < 6) {
786             Inkscape::LivePathEffect::Effect *effect =
787                 sp_lpe_item->getPathEffectOfType(Inkscape::LivePathEffect::SIMPLIFY);
788             if (effect) {
789                 LivePathEffect::LPESimplify *simplify =
790                     dynamic_cast<LivePathEffect::LPESimplify *>(effect->getLPEObj()->get_lpe());
791                 double threshold = simplify->threshold * 1.2;
792                 simplify->threshold.param_set_value(threshold);
793                 simplify->threshold.write_to_SVG();
794                 has_recursion = true;
795             }
796         }
797         ++recusion_limit;
798     } else {
799         recusion_limit = 0;
800     }
801 }
802 
803 /* ######################## */
804 
805 } //namespace LivePathEffect
806 } /* namespace Inkscape */
807 
808 /*
809   Local Variables:
810   mode:c++
811   c-file-style:"stroustrup"
812   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
813   indent-tabs-mode:nil
814   fill-column:99
815   End:
816 */
817 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
818