1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** \file
3  * LPE <offset> implementation
4  */
5 /*
6  * Authors:
7  *   Maximilian Albert
8  *   Jabiertxo Arraiza
9  *
10  * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
11  * Copyright (C) Maximilian Albert 2008 <maximilian.albert@gmail.com>
12  * Copyright (C) Jabierto Arraiza 2015 <jabier.arraiza@marker.es>
13  *
14  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
15  */
16 
17 #include "lpe-offset.h"
18 
19 #include <2geom/path-intersection.h>
20 #include <2geom/piecewise.h>
21 #include <2geom/svg-path-parser.h>
22 
23 #include "inkscape.h"
24 #include "style.h"
25 
26 #include "display/curve.h"
27 #include "helper/geom-pathstroke.h"
28 #include "helper/geom.h"
29 #include "live_effects/parameter/enum.h"
30 #include "object/sp-shape.h"
31 #include "path/path-boolop.h"
32 #include "path/path-util.h"
33 #include "svg/svg.h"
34 #include "ui/knot/knot-holder.h"
35 #include "ui/knot/knot-holder-entity.h"
36 #include "util/units.h"
37 
38 // TODO due to internal breakage in glibmm headers, this must be last:
39 #include <glibmm/i18n.h>
40 
41 namespace Inkscape {
42 namespace LivePathEffect {
43 
44 namespace OfS {
45     class KnotHolderEntityOffsetPoint : public LPEKnotHolderEntity {
46     public:
KnotHolderEntityOffsetPoint(LPEOffset * effect)47         KnotHolderEntityOffsetPoint(LPEOffset * effect) : LPEKnotHolderEntity(effect) {}
~KnotHolderEntityOffsetPoint()48         ~KnotHolderEntityOffsetPoint() override
49         {
50             LPEOffset *lpe = dynamic_cast<LPEOffset *>(_effect);
51             if (lpe) {
52                 lpe->_knot_entity = nullptr;
53             }
54         }
55         void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) override;
56         void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override;
57         Geom::Point knot_get() const override;
58     private:
59     };
60 } // OfS
61 
62 
63 static const Util::EnumData<unsigned> JoinTypeData[] = {
64     // clang-format off
65     {JOIN_BEVEL,       N_("Beveled"),    "bevel"},
66     {JOIN_ROUND,       N_("Rounded"),    "round"},
67     {JOIN_MITER,       N_("Miter"),      "miter"},
68     {JOIN_MITER_CLIP,  N_("Miter Clip"), "miter-clip"},
69     {JOIN_EXTRAPOLATE, N_("Extrapolated arc"), "extrp_arc"},
70     {JOIN_EXTRAPOLATE1, N_("Extrapolated arc Alt1"), "extrp_arc1"},
71     {JOIN_EXTRAPOLATE2, N_("Extrapolated arc Alt2"), "extrp_arc2"},
72     {JOIN_EXTRAPOLATE3, N_("Extrapolated arc Alt3"), "extrp_arc3"},
73     // clang-format on
74 };
75 
76 static const Util::EnumDataConverter<unsigned> JoinTypeConverter(JoinTypeData, sizeof(JoinTypeData)/sizeof(*JoinTypeData));
77 
78 
LPEOffset(LivePathEffectObject * lpeobject)79 LPEOffset::LPEOffset(LivePathEffectObject *lpeobject) :
80     Effect(lpeobject),
81     unit(_("Unit"), _("Unit of measurement"), "unit", &wr, this, "mm"),
82     offset(_("Offset:"), _("Offset"), "offset", &wr, this, 0.0),
83     linejoin_type(_("Join:"), _("Determines the shape of the path's corners"),  "linejoin_type", JoinTypeConverter, &wr, this, JOIN_MITER),
84     miter_limit(_("Miter limit:"), _("Maximum length of the miter join (in units of stroke width)"), "miter_limit", &wr, this, 4.0),
85     attempt_force_join(_("Force miter"), _("Overrides the miter limit and forces a join."), "attempt_force_join", &wr, this, false),
86     update_on_knot_move(_("Live update"), _("Update while moving handle"), "update_on_knot_move", &wr, this, true)
87 {
88     show_orig_path = true;
89     registerParameter(&linejoin_type);
90     registerParameter(&unit);
91     registerParameter(&offset);
92     registerParameter(&miter_limit);
93     registerParameter(&attempt_force_join);
94     registerParameter(&update_on_knot_move);
95     offset.param_set_increments(0.1, 0.1);
96     offset.param_set_digits(6);
97     offset_pt = Geom::Point(Geom::infinity(), Geom::infinity());
98     _knot_entity = nullptr;
99     _provides_knotholder_entities = true;
100     apply_to_clippath_and_mask = true;
101     prev_unit = unit.get_abbreviation();
102     liveknot = false;
103     fillrule = fill_nonZero;
104 }
105 
~LPEOffset()106 LPEOffset::~LPEOffset()
107 {
108     modified_connection.disconnect();
109 };
110 
111 void
modified(SPObject * obj,guint flags)112 LPEOffset::modified(SPObject *obj, guint flags)
113 {
114     if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
115         // Get the used fillrule
116         SPCSSAttr *css;
117         const gchar *val;
118         css = sp_repr_css_attr (sp_lpe_item->getRepr() , "style");
119         val = sp_repr_css_property (css, "fill-rule", nullptr);
120         FillRuleFlatten fillrule_chan = fill_nonZero;
121         if (val && strcmp (val, "evenodd") == 0)
122         {
123             fillrule_chan = fill_oddEven;
124         }
125         if (fillrule != fillrule_chan) {
126             sp_lpe_item_update_patheffect (sp_lpe_item, true, true);
127         }
128     }
129 }
130 
131 static void
sp_flatten(Geom::PathVector & pathvector,FillRuleFlatten fillkind)132 sp_flatten(Geom::PathVector &pathvector, FillRuleFlatten fillkind)
133 {
134     Path *orig = new Path;
135     orig->LoadPathVector(pathvector);
136     Shape *theShape = new Shape;
137     Shape *theRes = new Shape;
138     orig->ConvertWithBackData (1.0);
139     orig->Fill (theShape, 0);
140     theRes->ConvertToShape (theShape, FillRule(fillkind));
141     Path *originaux[1];
142     originaux[0] = orig;
143     Path *res = new Path;
144     theRes->ConvertToForme (res, 1, originaux, true);
145 
146     delete theShape;
147     delete theRes;
148     char *res_d = res->svg_dump_path ();
149     delete res;
150     delete orig;
151     pathvector  = sp_svg_read_pathv(res_d);
152 }
153 
get_nearest_point(Geom::PathVector pathv,Geom::Point point)154 Geom::Point get_nearest_point(Geom::PathVector pathv, Geom::Point point)
155 {
156     Geom::Point res = Geom::Point(Geom::infinity(), Geom::infinity());
157     std::optional< Geom::PathVectorTime > pathvectortime = pathv.nearestTime(point);
158     if (pathvectortime) {
159         Geom::PathTime pathtime = pathvectortime->asPathTime();
160         res = pathv[(*pathvectortime).path_index].pointAt(pathtime.curve_index + pathtime.t);
161     }
162     return res;
163 }
164 
165 /* double get_separation(Geom::PathVector pathv, Geom::Point point, Geom::Point point_b)
166 {
167     Geom::Point res = Geom::Point(Geom::infinity(), Geom::infinity());
168     std::optional<Geom::PathVectorTime> pathvectortime = pathv.nearestTime(point);
169     std::optional<Geom::PathVectorTime> pathvectortime_b = pathv.nearestTime(point_b);
170     if (pathvectortime && pathvectortime_b) {
171         Geom::PathTime pathtime = pathvectortime->asPathTime();
172         Geom::PathTime pathtime_b = pathvectortime_b->asPathTime();
173         if ((*pathvectortime).path_index == (*pathvectortime_b).path_index) {
174             return std::abs((pathtime.curve_index + pathtime.t) - (pathtime_b.curve_index + pathtime_b.t));
175         }
176     }
177     return -1;
178 } */
179 
transform_multiply(Geom::Affine const & postmul,bool)180 void LPEOffset::transform_multiply(Geom::Affine const &postmul, bool /*set*/)
181 {
182     refresh_widgets = true;
183     if (!postmul.isTranslation()) {
184         Geom::Affine current_affine = sp_item_transform_repr(sp_lpe_item);
185         offset.param_transform_multiply(postmul * current_affine.inverse(), true);
186     }
187     offset_pt *= postmul;
188 }
189 
get_default_point(Geom::PathVector pathv)190 Geom::Point LPEOffset::get_default_point(Geom::PathVector pathv)
191 {
192     Geom::Point origin = Geom::Point(Geom::infinity(), Geom::infinity());
193     Geom::OptRect bbox = pathv.boundsFast();
194     if (bbox) {
195         origin = Geom::Point((*bbox).midpoint()[Geom::X], (*bbox).top());
196         origin = get_nearest_point(pathv, origin);
197     }
198     return origin;
199 }
200 
201 double
sp_get_offset(Geom::Point origin)202 LPEOffset::sp_get_offset(Geom::Point origin)
203 {
204     double ret_offset = 0;
205     int winding_value = mix_pathv_all.winding(origin);
206     bool inset = false;
207     if (winding_value % 2 != 0) {
208         inset = true;
209     }
210     ret_offset = Geom::distance(origin, get_nearest_point(mix_pathv_all, origin));
211     if (inset) {
212         ret_offset *= -1;
213     }
214     return Inkscape::Util::Quantity::convert(ret_offset, "px", unit.get_abbreviation()) * this->scale;
215 }
216 
217 void
addCanvasIndicators(SPLPEItem const * lpeitem,std::vector<Geom::PathVector> & hp_vec)218 LPEOffset::addCanvasIndicators(SPLPEItem const *lpeitem, std::vector<Geom::PathVector> &hp_vec)
219 {
220     hp_vec.push_back(helper_path);
221 }
222 
223 void
doBeforeEffect(SPLPEItem const * lpeitem)224 LPEOffset::doBeforeEffect (SPLPEItem const* lpeitem)
225 {
226     SPObject *obj = dynamic_cast<SPObject *>(sp_lpe_item);
227     if (is_load && obj) {
228         modified_connection = obj->connectModified(sigc::mem_fun(*this, &LPEOffset::modified));
229     }
230     original_bbox(lpeitem);
231     SPGroup *group = dynamic_cast<SPGroup *>(sp_lpe_item);
232     if (group) {
233         mix_pathv_all.clear();
234     }
235     this->scale = lpeitem->i2doc_affine().descrim();
236     if (!is_load && prev_unit != unit.get_abbreviation()) {
237         offset.param_set_value(Inkscape::Util::Quantity::convert(offset, prev_unit, unit.get_abbreviation()));
238     }
239     prev_unit = unit.get_abbreviation();
240 }
241 
offset_winding(Geom::PathVector pathvector,Geom::Path path)242 int offset_winding(Geom::PathVector pathvector, Geom::Path path)
243 {
244     int wind = 0;
245     Geom::Point p = path.initialPoint();
246     for (auto i:pathvector) {
247         if (i == path)  continue;
248         if (!i.boundsFast().contains(p)) continue;
249         wind += i.winding(p);
250     }
251     return wind;
252 }
253 
doAfterEffect(SPLPEItem const *,SPCurve * curve)254 void LPEOffset::doAfterEffect(SPLPEItem const * /*lpeitem*/, SPCurve *curve)
255 {
256     if (offset_pt == Geom::Point(Geom::infinity(), Geom::infinity())) {
257         if (_knot_entity) {
258             _knot_entity->knot_get();
259         }
260     }
261     if (is_load) {
262         offset_pt = Geom::Point(Geom::infinity(), Geom::infinity());
263     }
264     if (_knot_entity && sp_lpe_item && !liveknot) {
265         Geom::PathVector out;
266         // we don do this on groups, editing is joining ito so no need to update knot
267         SPShape *shape = dynamic_cast<SPShape *>(sp_lpe_item);
268         if (shape) {
269             out = SP_SHAPE(sp_lpe_item)->curve()->get_pathvector();
270             offset_pt = get_nearest_point(out, offset_pt);
271             _knot_entity->knot_get();
272         }
273     }
274     is_load = false;
275 }
276 
277 // TODO: find a way to not remove wanted self intersections
278 // previouly are some failed attemps
279 
280 /* // Taked from Knot LPE duple code
281 static Geom::Path::size_type size_nondegenerate(Geom::Path const &path)
282 {
283     Geom::Path::size_type retval = path.size_default();
284     const Geom::Curve &closingline = path.back_closed();
285     // the closing line segment is always of type
286     // Geom::LineSegment.
287     if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
288         // closingline.isDegenerate() did not work, because it only checks for
289         // *exact* zero length, which goes wrong for relative coordinates and
290         // rounding errors...
291         // the closing line segment has zero-length. So stop before that one!
292         retval = path.size_open();
293     }
294     return retval;
295 }
296 gint get_nearest_corner(Geom::OptRect bbox, Geom::Point point)
297 {
298     if (bbox) {
299         double distance_a = Geom::distance(point, (*bbox).corner(0));
300         double distance_b = Geom::distance(point, (*bbox).corner(1));
301         double distance_c = Geom::distance(point, (*bbox).corner(2));
302         double distance_d = Geom::distance(point, (*bbox).corner(3));
303         std::vector<double> distances{distance_a, distance_b, distance_c, distance_d};
304         std::vector<double>::iterator mindistance = std::min_element(distances.begin(), distances.end());
305         return std::distance(distances.begin(), mindistance);
306     }
307     return -1;
308 }
309 
310 // This way not work with good selfintersections on consecutive curves
311 // and when there is nodes nearest to points
312 // I try diferent methods to cleanup without luky
313 // if anyone is interested there is a way to explore
314 // if in original path the section into the 2 nearest point dont have
315 // self intersections we can supose this is a intersection to remove
316 // it works very well but in good selfintersections work only in one offset direction
317 bool consecutiveCurves(Geom::Path pathin, Geom::Point p) {
318     Geom::Coord mindist = std::numeric_limits<Geom::Coord>::max();
319     size_t pos;
320     for (size_t i = 0; i < pathin.size_default(); ++i) {
321         Geom::Curve const &c = pathin.at(i);
322         double dist = Geom::distance(p, c.boundsFast());
323         if (dist >= mindist) {
324             continue;
325         }
326         Geom::Coord t = c.nearestTime(p);
327         Geom::Coord d = Geom::distance(c.pointAt(t), p);
328         if (d < mindist) {
329             pos = i;
330             mindist = d;
331         }
332     }
333     size_t pos_b;
334     mindist = std::numeric_limits<Geom::Coord>::max();
335     for (size_t i = 0; i < pathin.size_default(); ++i) {
336         Geom::Curve const &c = pathin.at(i);
337         double dist = Geom::distance(p, c.boundsFast());
338         if (dist >= mindist || pos == i) {
339             continue;
340         }
341         Geom::Coord t = c.nearestTime(p);
342         Geom::Coord d = Geom::distance(c.pointAt(t), p);
343         if (d < mindist) {
344             pos_b = i;
345             mindist = d;
346         }
347     }
348     if (Geom::are_near(Geom::distance(pos,pos_b), 1)) {
349         return true;
350     }
351     return false;
352 }
353 
354 Geom::Path removeIntersects(Geom::Path pathin, Geom::Path pathorig, size_t skipcross)
355 {
356     Geom::Path out;
357     Geom::Crossings crossings = Geom::self_crossings(pathin);
358     size_t counter = 0;
359     for (auto cross : crossings) {
360         if (!Geom::are_near(cross.ta, cross.tb, 0.01)) {
361             size_t sizepath = size_nondegenerate(pathin);
362             double ta = cross.ta > cross.tb ? cross.tb : cross.ta;
363             double tb = cross.ta > cross.tb ? cross.ta : cross.tb;
364             if (!pathin.closed()) {
365                 counter++;
366                 if (skipcross >= counter) {
367                     continue;
368                 }
369                 bool removeint = consecutiveCurves(pathorig, pathin.pointAt(ta));
370                 Geom::Path p0 = pathin;
371                 if (removeint) {
372                     Geom::Path p1 = pathin.portion(ta, tb);
373                     if ((*p1.boundsFast()).maxExtent() > 0.01) {
374                         p0 = pathin.portion(0, ta);
375                         if (!Geom::are_near(tb, sizepath, 0.01)) {
376                             Geom::Path p2 = pathin.portion(tb, sizepath);
377                             p0.setFinal(p2.initialPoint());
378                             p0.append(p2);
379                         }
380                     } else {
381                         skipcross++;
382                     }
383                 } else {
384                     skipcross++;
385                 }
386                 out = removeIntersects(p0, pathorig, skipcross);
387                 return out;
388             }
389         }
390     }
391     return pathin;
392 } */
393 
removeIntersects(Geom::Path pathin)394 Geom::Path removeIntersects(Geom::Path pathin)
395 {
396     // I have a pending ping to moazin for 1.2 to fix open paths offeset self intesections (Jabiertxof)
397     // For 1.1 I comment the code because simply slow a lot or crash sometimes and never work realy well
398     /* Geom::Path out;
399     Geom::Crossings crossings = Geom::self_crossings(pathin);
400     static size_t maxiter = 0;
401     if (!maxiter) {
402         maxiter = crossings.size();
403     }
404     for (auto cross : crossings) {
405         maxiter--;
406         if (!maxiter) {
407             return pathin;
408         }
409         if (!Geom::are_near(cross.ta, cross.tb, 0.01)) {
410             size_t sizepath = size_nondegenerate(pathin);
411             double ta = cross.ta > cross.tb ? cross.tb : cross.ta;
412             double tb = cross.ta > cross.tb ? cross.ta : cross.tb;
413             if (!pathin.closed()) {
414                 Geom::Path p0 = pathin;
415                 Geom::Path p1 = pathin.portion(ta, tb);
416                 p0 = pathin.portion(0, ta);
417                 if (!Geom::are_near(tb, sizepath, 0.01)) {
418                     Geom::Path p2 = pathin.portion(tb, sizepath);
419                     p0.setFinal(p2.initialPoint());
420                     p0.append(p2);
421                 }
422                 out = removeIntersects(p0);
423                 return out;
424             }
425         }
426     } */
427     return pathin;
428 }
429 
430 static Geom::PathVector
sp_simplify_pathvector(Geom::PathVector original_pathv,double threshold)431 sp_simplify_pathvector(Geom::PathVector original_pathv, double threshold)
432 {
433     Path* pathliv = Path_for_pathvector(original_pathv);
434     pathliv->ConvertEvenLines(threshold);
435     pathliv->Simplify(threshold);
436     return Geom::parse_svg_path(pathliv->svg_dump_path());
437 }
438 
439 Geom::PathVector
doEffect_path(Geom::PathVector const & path_in)440 LPEOffset::doEffect_path(Geom::PathVector const & path_in)
441 {
442     Geom::PathVector ret_closed;
443     Geom::PathVector ret_open;
444     SPItem * item = SP_ITEM(current_shape);
445     SPDocument *document = getSPDoc();
446     if (!item || !document) {
447         return path_in;
448     }
449     // Get the used fillrule
450     SPCSSAttr *css;
451     const gchar *val;
452     css = sp_repr_css_attr (item->getRepr() , "style");
453     val = sp_repr_css_property (css, "fill-rule", nullptr);
454 
455     fillrule = fill_nonZero;
456     if (val && strcmp (val, "evenodd") == 0)
457     {
458         fillrule = fill_oddEven;
459     }
460 
461     double tolerance = -1;
462     if (liveknot) {
463         tolerance = 3;
464     }
465     // Get the doc units offset
466     double to_offset = Inkscape::Util::Quantity::convert(offset, unit.get_abbreviation(), "px") / this->scale;
467     Geom::PathVector open_pathv;
468     Geom::PathVector closed_pathv;
469     Geom::PathVector mix_pathv;
470     Geom::PathVector mix_pathv_workon;
471     Geom::PathVector orig_pathv = pathv_to_linear_and_cubic_beziers(path_in);
472     helper_path = orig_pathv;
473     // Store separated open/closed paths
474     Geom::PathVector splitter;
475     for (auto &i : orig_pathv) {
476         // this improve offset in near closed paths
477         if (Geom::are_near(i.initialPoint(), i.finalPoint())) {
478             i.close(true);
479         }
480         if (i.closed()) {
481             closed_pathv.push_back(i);
482         } else {
483             open_pathv.push_back(i);
484         }
485     }
486     sp_flatten(closed_pathv, fillrule);
487 
488     // we flatten using original fill rule
489     mix_pathv = open_pathv;
490     for (auto path : closed_pathv) {
491         mix_pathv.push_back(path);
492     }
493     SPGroup *group = dynamic_cast<SPGroup *>(sp_lpe_item);
494     // Calculate the original pathvector used outside this function
495     // to calculate the offset
496     if (group) {
497         mix_pathv_all.insert(mix_pathv_all.begin(), mix_pathv.begin(), mix_pathv.end());
498     } else {
499         mix_pathv_all = mix_pathv;
500     }
501     if (to_offset < 0) {
502         Geom::OptRect bbox = mix_pathv.boundsFast();
503         if (bbox) {
504             (*bbox).expandBy(to_offset / 2.0);
505             if ((*bbox).hasZeroArea()) {
506                 Geom::PathVector empty;
507                 return empty;
508             }
509         }
510     }
511     for (auto pathin : closed_pathv) {
512         // Geom::OptRect bbox = pathin.boundsFast();
513         // if (pbbox && (*pbbox).minExtent() > to_offset) {
514         mix_pathv_workon.push_back(pathin);
515         //}
516     }
517     mix_pathv_workon.insert(mix_pathv_workon.begin(), open_pathv.begin(), open_pathv.end());
518 
519     if (offset == 0.0) {
520         if (is_load && offset_pt == Geom::Point(Geom::infinity(), Geom::infinity())) {
521             offset_pt = get_default_point(path_in);
522             if (_knot_entity) {
523                 _knot_entity->knot_get();
524             }
525         }
526         return path_in;
527     }
528     Geom::OptRect bbox = closed_pathv.boundsFast();
529     double bboxsize = 0;
530     if (bbox) {
531         bboxsize = (*bbox).maxExtent();
532     }
533     LineJoinType join = static_cast<LineJoinType>(linejoin_type.get_value());
534     Geom::PathVector ret_closed_tmp;
535     if (to_offset > 0) {
536         for (auto &i : mix_pathv_workon) {
537             Geom::Path tmp = half_outline(
538                 i, to_offset, (attempt_force_join ? std::numeric_limits<double>::max() : miter_limit), join, tolerance);
539             if (tmp.closed()) {
540                 Geom::OptRect pbbox = tmp.boundsFast();
541                 if (pbbox && (*pbbox).minExtent() > to_offset) {
542                     ret_closed_tmp.push_back(tmp);
543                 }
544             } else {
545                 Geom::Path tmp_b = half_outline(i.reversed(), to_offset,
546                                                 (attempt_force_join ? std::numeric_limits<double>::max() : miter_limit),
547                                                 join, tolerance);
548                 Geom::PathVector switch_pv_a(tmp);
549                 Geom::PathVector switch_pv_b(tmp_b);
550                 double distance_b = Geom::distance(offset_pt, get_nearest_point(switch_pv_a, offset_pt));
551                 double distance_a = Geom::distance(offset_pt, get_nearest_point(switch_pv_b, offset_pt));
552                 if (distance_b < distance_a) {
553                     ret_open.push_back(removeIntersects(tmp));
554                 } else {
555                     ret_open.push_back(removeIntersects(tmp_b));
556                 }
557             }
558         }
559         sp_flatten(ret_closed_tmp, fill_nonZero);
560         for (auto path : ret_closed_tmp) {
561             ret_closed.push_back(path);
562         }
563     } else if (to_offset < 0) {
564         for (auto &i : mix_pathv_workon) {
565             Geom::Path tmp =
566                 half_outline(i.reversed(), std::abs(to_offset),
567                              (attempt_force_join ? std::numeric_limits<double>::max() : miter_limit), join, tolerance);
568             if (i.closed()) {
569                 Geom::PathVector out(tmp);
570                 for (auto path : out) {
571                     ret_closed.push_back(path);
572                 }
573             } else {
574                 Geom::Path tmp_b = half_outline(i, std::abs(to_offset),
575                                                 (attempt_force_join ? std::numeric_limits<double>::max() : miter_limit),
576                                                 join, tolerance);
577                 Geom::PathVector switch_pv_a(tmp);
578                 Geom::PathVector switch_pv_b(tmp_b);
579                 double distance_b = Geom::distance(offset_pt, get_nearest_point(switch_pv_a, offset_pt));
580                 double distance_a = Geom::distance(offset_pt, get_nearest_point(switch_pv_b, offset_pt));
581                 if (distance_b < distance_a) {
582                     ret_open.push_back(removeIntersects(tmp));
583                 } else {
584                     ret_open.push_back(removeIntersects(tmp_b));
585                 }
586             }
587         }
588         if (!ret_closed.empty()) {
589             Geom::PathVector outline;
590             for (const auto &i : mix_pathv_workon) {
591                 if (i.closed()) {
592                     Geom::PathVector tmp = Inkscape::outline(i, std::abs(to_offset * 2), 4.0, join,
593                                                              static_cast<LineCapType>(BUTT_FLAT), tolerance);
594                     outline.insert(outline.begin(), tmp.begin(), tmp.end());
595                 }
596             }
597             sp_flatten(outline, fill_nonZero);
598             double size = Geom::L2(Geom::bounds_fast(ret_closed)->dimensions());
599             size /= sp_lpe_item->i2doc_affine().descrim();
600             ret_closed = sp_pathvector_boolop(outline, ret_closed, bool_op_diff, fill_nonZero, fill_nonZero);
601             if (!liveknot) {
602                 ret_closed = sp_simplify_pathvector(ret_closed, 0.0003 * size);
603             }
604         }
605     }
606     ret_closed.insert(ret_closed.begin(), ret_open.begin(), ret_open.end());
607     return ret_closed;
608 }
609 
addKnotHolderEntities(KnotHolder * knotholder,SPItem * item)610 void LPEOffset::addKnotHolderEntities(KnotHolder *knotholder, SPItem *item)
611 {
612     _knot_entity = new OfS::KnotHolderEntityOffsetPoint(this);
613     _knot_entity->create(nullptr, item, knotholder, Inkscape::CANVAS_ITEM_CTRL_TYPE_LPE,
614                          "LPEOffset", _("Offset point"));
615     _knot_entity->knot->setMode(Inkscape::CANVAS_ITEM_CTRL_MODE_COLOR);
616     _knot_entity->knot->setShape(Inkscape::CANVAS_ITEM_CTRL_SHAPE_CIRCLE);
617     _knot_entity->knot->setFill(0xFF6600FF, 0x4BA1C7FF, 0xCF1410FF, 0xFF6600FF);
618     _knot_entity->knot->setStroke(0x000000FF, 0x000000FF, 0x000000FF, 0x000000FF);
619     _knot_entity->knot->updateCtrl();
620     offset_pt = Geom::Point(Geom::infinity(), Geom::infinity());
621     knotholder->add(_knot_entity);
622 }
623 
624 namespace OfS {
knot_set(Geom::Point const & p,Geom::Point const &,guint state)625 void KnotHolderEntityOffsetPoint::knot_set(Geom::Point const &p, Geom::Point const& /*origin*/, guint state)
626 {
627     using namespace Geom;
628     LPEOffset* lpe = dynamic_cast<LPEOffset *>(_effect);
629     lpe->refresh_widgets = true;
630     Geom::Point s = snap_knot_position(p, state);
631     double offset = lpe->sp_get_offset(s);
632     lpe->offset_pt = s;
633     if (lpe->update_on_knot_move) {
634         lpe->liveknot = true;
635         lpe->offset.param_set_value(offset);
636         sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, false);
637     } else {
638         lpe->liveknot = false;
639     }
640 }
641 
knot_ungrabbed(Geom::Point const & p,Geom::Point const & origin,guint state)642 void KnotHolderEntityOffsetPoint::knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state)
643 {
644     LPEOffset *lpe = dynamic_cast<LPEOffset *>(_effect);
645     lpe->refresh_widgets = true;
646     lpe->liveknot = false;
647     using namespace Geom;
648     Geom::Point s = lpe->offset_pt;
649     double offset = lpe->sp_get_offset(s);
650     lpe->offset.param_set_value(offset);
651     sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false);
652 }
653 
knot_get() const654 Geom::Point KnotHolderEntityOffsetPoint::knot_get() const
655 {
656     LPEOffset *lpe = dynamic_cast<LPEOffset *>(_effect);
657     if (!lpe) {
658         return Geom::Point();
659     }
660     if (!lpe->update_on_knot_move) {
661         return lpe->offset_pt;
662     }
663     Geom::Point nearest = lpe->offset_pt;
664     if (nearest == Geom::Point(Geom::infinity(), Geom::infinity())) {
665         Geom::PathVector out;
666         SPGroup *group = dynamic_cast<SPGroup *>(item);
667         SPShape *shape = dynamic_cast<SPShape *>(item);
668         if (group) {
669             std::vector<SPItem *> item_list = sp_item_group_item_list(group);
670             for (auto child : item_list) {
671                 SPShape *subchild = dynamic_cast<SPShape *>(child);
672                 if (subchild) {
673                     Geom::PathVector tmp = subchild->curve()->get_pathvector();
674                     out.insert(out.begin(), tmp.begin(), tmp.end());
675                     sp_flatten(out, fill_oddEven);
676                 }
677             }
678         } else if (shape) {
679             SPCurve const *c = shape->curve();
680             if (c) {
681                 out = c->get_pathvector();
682             }
683         }
684         if (!out.empty()) {
685             nearest = lpe->get_default_point(out);
686         }
687     }
688     lpe->offset_pt = nearest;
689     return lpe->offset_pt;
690 }
691 
692 } // namespace OfS
693 } //namespace LivePathEffect
694 } /* namespace Inkscape */
695 
696 /*
697   Local Variables:
698   mode:c++
699   c-file-style:"stroustrup"
700   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
701   indent-tabs-mode:nil
702   fill-column:99
703   End:
704 */
705 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
706