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