1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Boolean operation live path effect
4  *
5  * Copyright (C) 2016-2017 Michael Soegtrop
6  *
7  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
8  */
9 
10 #include "live_effects/lpe-bool.h"
11 
12 #include <algorithm>
13 #include <cmath>
14 #include <cstring>
15 #include <glibmm/i18n.h>
16 
17 #include "2geom/affine.h"
18 #include "2geom/bezier-curve.h"
19 #include "2geom/path-sink.h"
20 #include "2geom/path.h"
21 #include "2geom/svg-path-parser.h"
22 #include "display/curve.h"
23 #include "helper/geom.h"
24 #include "inkscape.h"
25 #include "selection-chemistry.h"
26 #include "livarot/Path.h"
27 #include "livarot/Shape.h"
28 #include "livarot/path-description.h"
29 #include "live_effects/lpeobject.h"
30 #include "object/sp-clippath.h"
31 #include "object/sp-defs.h"
32 #include "object/sp-shape.h"
33 #include "object/sp-text.h"
34 #include "object/sp-root.h"
35 #include "path/path-boolop.h"
36 #include "path/path-util.h"
37 #include "snap.h"
38 #include "style.h"
39 #include "svg/svg.h"
40 #include "ui/tools/tool-base.h"
41 
42 namespace Inkscape {
43 namespace LivePathEffect {
44 
45 // Define an extended boolean operation type
46 
47 static const Util::EnumData<LPEBool::bool_op_ex> BoolOpData[LPEBool::bool_op_ex_count] = {
48     {LPEBool::bool_op_ex_union, N_("union"), "union"},
49     {LPEBool::bool_op_ex_inters, N_("intersection"), "inters"},
50     {LPEBool::bool_op_ex_diff, N_("difference"), "diff"},
51     {LPEBool::bool_op_ex_symdiff, N_("symmetric difference"), "symdiff"},
52     {LPEBool::bool_op_ex_cut, N_("division"), "cut"},
53     // Note on naming of operations:
54     // bool_op_cut is called "Division" in the manu, see sp_selected_path_cut
55     // bool_op_slice is called "Cut path" in the menu, see sp_selected_path_slice
56     // TODO: this 3 options are commented because dont work properly
57     // maybe in 1.2 can be fixed but need libarot base to do
58     // {LPEBool::bool_op_ex_slice, N_("cut"), "slice"},
59     // {LPEBool::bool_op_ex_slice_inside, N_("cut inside"), "slice-inside"},
60     // {LPEBool::bool_op_ex_slice_outside, N_("cut outside"), "slice-outside"},
61 };
62 
63 static const Util::EnumDataConverter<LPEBool::bool_op_ex> BoolOpConverter(BoolOpData, sizeof(BoolOpData) / sizeof(*BoolOpData));
64 
65 static const Util::EnumData<fill_typ> FillTypeData[] = {
66     { fill_oddEven, N_("even-odd"), "oddeven" },
67     { fill_nonZero, N_("non-zero"), "nonzero" },
68     { fill_positive, N_("positive"), "positive" },
69     { fill_justDont, N_("take from object"), "from-curve" }
70 };
71 
72 static const Util::EnumDataConverter<fill_typ> FillTypeConverter(FillTypeData, sizeof(FillTypeData) / sizeof(*FillTypeData));
73 
LPEBool(LivePathEffectObject * lpeobject)74 LPEBool::LPEBool(LivePathEffectObject *lpeobject)
75     : Effect(lpeobject)
76     , operand_path(_("Operand path:"), _("Operand for the boolean operation"), "operand-path", &wr, this)
77     , bool_operation(_("Operation:"), _("Boolean Operation"), "operation", BoolOpConverter, &wr, this, bool_op_ex_union)
78     , swap_operands(_("Swap operands"), _("Swap operands (useful e.g. for difference)"), "swap-operands", &wr, this)
79     , rmv_inner(
80           _("Remove inner"),
81           _("For cut operations: remove inner (non-contour) lines of cutting path to avoid invisible extra points"),
82           "rmv-inner", &wr, this)
83     , fill_type_this(_("Fill type this:"), _("Fill type (winding mode) for this path"), "filltype-this",
84                      FillTypeConverter, &wr, this, fill_justDont)
85     , fill_type_operand(_("Fill type operand:"), _("Fill type (winding mode) for operand path"), "filltype-operand",
86                         FillTypeConverter, &wr, this, fill_justDont)
87     , filter("Filter", "Previous filter", "filter", &wr, this, "", true)
88 {
89     registerParameter(&operand_path);
90     registerParameter(&bool_operation);
91     registerParameter(&swap_operands);
92     registerParameter(&rmv_inner);
93     registerParameter(&fill_type_this);
94     registerParameter(&filter);
95     registerParameter(&fill_type_operand);
96     show_orig_path = true;
97     is_load = true;
98     prev_affine = Geom::identity();
99     operand = dynamic_cast<SPItem *>(operand_path.getObject());
100     if (operand) {
101         operand_id = operand->getId();
102     }
103 }
104 
~LPEBool()105 LPEBool::~LPEBool() {
106     doOnRemove(nullptr);
107 }
cmp_cut_position(const Path::cut_position & a,const Path::cut_position & b)108 bool cmp_cut_position(const Path::cut_position &a, const Path::cut_position &b)
109 {
110     return a.piece == b.piece ? a.t < b.t : a.piece < b.piece;
111 }
112 
113 Geom::PathVector
sp_pathvector_boolop_slice_intersect(Geom::PathVector const & pathva,Geom::PathVector const & pathvb,bool inside,fill_typ fra,fill_typ frb)114 sp_pathvector_boolop_slice_intersect(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, bool inside, fill_typ fra, fill_typ frb)
115 {
116     // This is similar to sp_pathvector_boolop/bool_op_slice, but keeps only edges inside the cutter area.
117     // The code is also based on sp_pathvector_boolop_slice.
118     //
119     // We have two paths on input
120     // - a closed area which is used to cut out pieces from a contour (called area below)
121     // - a contour which is cut into pieces by the border of thr area (called contour below)
122     //
123     // The code below works in the following steps
124     // (a) Convert the area to a shape, so that we can ask the winding number for any point
125     // (b) Add both, the contour and the area to a single shape and intersect them
126     // (c) Find the intersection points between area border and contour (vector toCut)
127     // (d) Split the original contour at the intersection points
128     // (e) check for each contour edge in combined shape if its center is inside the area - if not discard it
129     // (f) create a vector of all inside edges
130     // (g) convert the piece numbers to the piece numbers after applying the cuts
131     // (h) fill a bool vector with information which pieces are in
132     // (i) filter the descr_cmd of the result path with this bool vector
133     //
134     // The main inefficiency here is step (e) because I use a winding function of the area-shape which goes
135     // through the complete edge list for each point I ask for, so effort is n-edges-contour * n-edges-area.
136     // It is tricky to improve this without building into the livarot code.
137     // One way might be to decide at the intersection points which edges touching the intersection points are
138     // in by making a loop through all edges on the intersection vertex. Since this is a directed non intersecting
139     // graph, this should provide sufficient information.
140     // But since I anyway will change this to the new mechanism some time speed is fairly ok, I didn't look into this.
141 
142 
143     // extract the livarot Paths from the source objects
144     // also get the winding rule specified in the style
145     // Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly.
146     Path *contour_path = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathva));
147     Path *area_path = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathvb));
148 
149     // Shapes from above paths
150     Shape *area_shape = new Shape;
151     Shape *combined_shape = new Shape;
152     Shape *combined_inters = new Shape;
153 
154     // Add the area (process to intersection free shape)
155     area_path->ConvertWithBackData(1.0);
156     area_path->Fill(combined_shape, 1);
157 
158     // Convert this to a shape with full winding information
159     area_shape->ConvertToShape(combined_shape, frb);
160 
161     // Add the contour to the combined path (just add, no winding processing)
162     contour_path->ConvertWithBackData(1.0);
163     contour_path->Fill(combined_shape, 0, true, false, false);
164 
165     // Intersect the area and the contour - no fill processing
166     combined_inters->ConvertToShape(combined_shape, fill_justDont);
167 
168     // Result path
169     Path *result_path = new Path;
170     result_path->SetBackData(false);
171 
172     // Cutting positions for contour
173     std::vector<Path::cut_position> toCut;
174 
175     if (combined_inters->hasBackData()) {
176         // should always be the case, but ya never know
177         {
178             for (int i = 0; i < combined_inters->numberOfPoints(); i++) {
179                 if (combined_inters->getPoint(i).totalDegree() > 2) {
180                     // possibly an intersection
181                     // we need to check that at least one edge from the source path is incident to it
182                     // before we declare it's an intersection
183                     int cb = combined_inters->getPoint(i).incidentEdge[FIRST];
184                     int   nbOrig = 0;
185                     int   nbOther = 0;
186                     int   piece = -1;
187                     float t = 0.0;
188                     while (cb >= 0 && cb < combined_inters->numberOfEdges()) {
189                         if (combined_inters->ebData[cb].pathID == 0) {
190                             // the source has an edge incident to the point, get its position on the path
191                             piece = combined_inters->ebData[cb].pieceID;
192                             if (combined_inters->getEdge(cb).st == i) {
193                                 t = combined_inters->ebData[cb].tSt;
194                             } else {
195                                 t = combined_inters->ebData[cb].tEn;
196                             }
197                             nbOrig++;
198                         }
199                         if (combined_inters->ebData[cb].pathID == 1) {
200                             nbOther++;    // the cut is incident to this point
201                         }
202                         cb = combined_inters->NextAt(i, cb);
203                     }
204                     if (nbOrig > 0 && nbOther > 0) {
205                         // point incident to both path and cut: an intersection
206                         // note that you only keep one position on the source; you could have degenerate
207                         // cases where the source crosses itself at this point, and you wouyld miss an intersection
208                         Path::cut_position cutpos;
209                         cutpos.piece = piece;
210                         cutpos.t = t;
211                         toCut.push_back(cutpos);
212                     }
213                 }
214             }
215         }
216         {
217             // remove the edges from the intersection polygon
218             int i = combined_inters->numberOfEdges() - 1;
219             for (; i >= 0; i--) {
220                 if (combined_inters->ebData[i].pathID == 1) {
221                     combined_inters->SubEdge(i);
222                 } else {
223                     const Shape::dg_arete &edge = combined_inters->getEdge(i);
224                     const Shape::dg_point &start = combined_inters->getPoint(edge.st);
225                     const Shape::dg_point &end = combined_inters->getPoint(edge.en);
226                     Geom::Point mid = 0.5 * (start.x + end.x);
227                     int wind = area_shape->PtWinding(mid);
228                     if (wind == 0) {
229                         combined_inters->SubEdge(i);
230                     }
231                 }
232             }
233         }
234     }
235 
236     // create a vector of pieces, which are in the intersection
237     std::vector<Path::cut_position> inside_pieces(combined_inters->numberOfEdges());
238     for (int i = 0; i < combined_inters->numberOfEdges(); i++) {
239         inside_pieces[i].piece = combined_inters->ebData[i].pieceID;
240         // Use the t middle point, this is safe to compare with values from toCut in the presence of roundoff errors
241         inside_pieces[i].t = 0.5 * (combined_inters->ebData[i].tSt + combined_inters->ebData[i].tEn);
242     }
243     std::sort(inside_pieces.begin(), inside_pieces.end(), cmp_cut_position);
244 
245     // sort cut positions
246     std::sort(toCut.begin(), toCut.end(), cmp_cut_position);
247 
248     // Compute piece ids after ConvertPositionsToMoveTo
249     {
250         int idIncr = 0;
251         std::vector<Path::cut_position>::iterator itPiece = inside_pieces.begin();
252         std::vector<Path::cut_position>::iterator itCut = toCut.begin();
253         while (itPiece != inside_pieces.end()) {
254             while (itCut != toCut.end() && cmp_cut_position(*itCut, *itPiece)) {
255                 ++itCut;
256                 idIncr += 2;
257             }
258             itPiece->piece += idIncr;
259             ++itPiece;
260         }
261     }
262 
263     // Copy the original path to result and cut at the intersection points
264     result_path->Copy(contour_path);
265     result_path->ConvertPositionsToMoveTo(toCut.size(), toCut.data());   // cut where you found intersections
266 
267     // Create an array of bools which states which pieces are in
268     std::vector<bool> inside_flags(result_path->descr_cmd.size(), false);
269     for (auto & inside_piece : inside_pieces) {
270         inside_flags[ inside_piece.piece ] = true;
271         // also enable the element -1 to get the MoveTo
272         if (inside_piece.piece >= 1) {
273             inside_flags[ inside_piece.piece - 1 ] = true;
274         }
275     }
276 
277 #if 0 // CONCEPT TESTING
278     //Check if the inside/outside verdict is consistent - just for testing the concept
279     // Retrieve the pieces
280     int nParts = 0;
281     Path **parts = result_path->SubPaths(nParts, false);
282 
283     // Each piece should be either fully in or fully out
284     int iPiece = 0;
285     for (int iPart = 0; iPart < nParts; iPart++) {
286         bool andsum = true;
287         bool orsum = false;
288         for (int iCmd = 0; iCmd < parts[iPart]->descr_cmd.size(); iCmd++, iPiece++) {
289             andsum = andsum && inside_flags[ iPiece ];
290             orsum = andsum || inside_flags[ iPiece ];
291         }
292 
293         if (andsum != orsum) {
294             g_warning("Inconsistent inside/outside verdict for part=%d", iPart);
295         }
296     }
297     g_free(parts);
298 #endif
299 
300     // iterate over the commands of a path and keep those which are inside
301     int iDest = 0;
302     for (int iSrc = 0; iSrc < result_path->descr_cmd.size(); iSrc++) {
303         if (inside_flags[iSrc] == inside) {
304             result_path->descr_cmd[iDest++] = result_path->descr_cmd[iSrc];
305         } else {
306             delete result_path->descr_cmd[iSrc];
307         }
308     }
309     result_path->descr_cmd.resize(iDest);
310 
311     delete combined_inters;
312     delete combined_shape;
313     delete area_shape;
314     delete contour_path;
315     delete area_path;
316 
317     gchar *result_str = result_path->svg_dump_path();
318     Geom::PathVector outres =  Geom::parse_svg_path(result_str);
319     // CONCEPT TESTING g_warning( "%s", result_str );
320     g_free(result_str);
321     delete result_path;
322 
323     return outres;
324 }
325 
326 // remove inner contours
327 Geom::PathVector
sp_pathvector_boolop_remove_inner(Geom::PathVector const & pathva,fill_typ fra)328 sp_pathvector_boolop_remove_inner(Geom::PathVector const &pathva, fill_typ fra)
329 {
330     Geom::PathVector patht;
331     Path *patha = Path_for_pathvector(pathv_to_linear_and_cubic_beziers(pathva));
332 
333     Shape *shape = new Shape;
334     Shape *shapeshape = new Shape;
335     Path *resultp = new Path;
336     resultp->SetBackData(false);
337 
338     patha->ConvertWithBackData(0.1);
339     patha->Fill(shape, 0);
340     shapeshape->ConvertToShape(shape, fra);
341     shapeshape->ConvertToForme(resultp, 1, &patha);
342 
343     delete shape;
344     delete shapeshape;
345     delete patha;
346 
347     gchar *result_str = resultp->svg_dump_path();
348     Geom::PathVector resultpv =  Geom::parse_svg_path(result_str);
349     g_free(result_str);
350 
351     delete resultp;
352     return resultpv;
353 }
354 
GetFillTyp(SPItem * item)355 static fill_typ GetFillTyp(SPItem *item)
356 {
357     SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style");
358     gchar const *val = sp_repr_css_property(css, "fill-rule", nullptr);
359     if (val && strcmp(val, "nonzero") == 0) {
360         return fill_nonZero;
361     } else if (val && strcmp(val, "evenodd") == 0) {
362         return fill_oddEven;
363     } else {
364         return fill_nonZero;
365     }
366 }
367 
add_filter()368 void LPEBool::add_filter()
369 {
370     if (operand) {
371         Inkscape::XML::Node *repr = operand->getRepr();
372         if (!repr) {
373             return;
374         }
375         SPFilter *filt = operand->style->getFilter();
376         if (filt && filt->getId() && strcmp(filt->getId(), "selectable_hidder_filter") != 0) {
377             filter.param_setValue(filt->getId(), true);
378         }
379         if (!filt || (filt->getId() && strcmp(filt->getId(), "selectable_hidder_filter") != 0)) {
380             SPCSSAttr *css = sp_repr_css_attr_new();
381             sp_repr_css_set_property(css, "filter", "url(#selectable_hidder_filter)");
382             sp_repr_css_change(repr, css, "style");
383             sp_repr_css_attr_unref(css);
384         }
385     }
386 }
387 
remove_filter()388 void LPEBool::remove_filter()
389 {
390     if (operand) {
391         Inkscape::XML::Node *repr = operand->getRepr();
392         if (!repr) {
393             return;
394         }
395         SPFilter *filt = operand->style->getFilter();
396         if (filt && (filt->getId() && strcmp(filt->getId(), "selectable_hidder_filter") == 0)) {
397             SPCSSAttr *css = sp_repr_css_attr_new();
398             Glib::ustring filtstr = filter.param_getSVGValue();
399             if (filtstr != "") {
400                 Glib::ustring url = "url(#";
401                 url += filtstr;
402                 url += ")";
403                 sp_repr_css_set_property(css, "filter", url.c_str());
404                 // blur is removed when no item using it
405                 /*SPDocument *document = getSPDoc();
406                 SPObject * filterobj = nullptr;
407                 if((filterobj = document->getObjectById(filtstr))) {
408                     for (auto obj:filterobj->childList(false)) {
409                         if (obj) {
410                             obj->deleteObject(false);
411                             break;
412                         }
413                     }
414                 } */
415                 filter.param_setValue("");
416             } else {
417                 sp_repr_css_unset_property(css, "filter");
418             }
419             sp_repr_css_change(repr, css, "style");
420             sp_repr_css_attr_unref(css);
421         }
422     }
423 }
424 
doBeforeEffect(SPLPEItem const * lpeitem)425 void LPEBool::doBeforeEffect(SPLPEItem const *lpeitem)
426 {
427     SPDocument *document = getSPDoc();
428     if (!document) {
429         return;
430     }
431     _hp.clear();
432     Inkscape::XML::Document *xml_doc = document->getReprDoc();
433     SPObject *elemref = nullptr;
434     Inkscape::XML::Node *boolfilter = nullptr;
435     if (!(elemref = document->getObjectById("selectable_hidder_filter"))) {
436         boolfilter = xml_doc->createElement("svg:filter");
437         boolfilter->setAttribute("id", "selectable_hidder_filter");
438         boolfilter->setAttribute("width", "1");
439         boolfilter->setAttribute("height", "1");
440         boolfilter->setAttribute("x", "0");
441         boolfilter->setAttribute("y", "0");
442         boolfilter->setAttribute("style", "color-interpolation-filters:sRGB;");
443         boolfilter->setAttribute("inkscape:label", "LPE boolean visibility");
444         /* Create <path> */
445         Inkscape::XML::Node *primitive = xml_doc->createElement("svg:feComposite");
446         primitive->setAttribute("id", "boolops_hidder_primitive");
447         primitive->setAttribute("result", "composite1");
448         primitive->setAttribute("operator", "arithmetic");
449         primitive->setAttribute("in2", "SourceGraphic");
450         primitive->setAttribute("in", "BackgroundImage");
451         Inkscape::XML::Node *defs = document->getDefs()->getRepr();
452         defs->addChild(boolfilter, nullptr);
453         Inkscape::GC::release(boolfilter);
454         boolfilter->addChild(primitive, nullptr);
455         Inkscape::GC::release(primitive);
456     } else {
457         for (auto obj : elemref->childList(false)) {
458             if (obj && strcmp(obj->getId(), "boolops_hidder_primitive") != 0) {
459                 obj->deleteObject(true);
460             }
461         }
462     }
463     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
464     SPItem *current_operand = dynamic_cast<SPItem *>(operand_path.getObject());
465     operand =  dynamic_cast<SPItem *>(lpeitem->document->getObjectById(operand_id));
466 
467     if (!current_operand && !operand) {
468         return;
469     }
470     if (!current_operand) {
471         operand_path.remove_link();
472         operand = nullptr;
473     }
474     if (current_operand && current_operand->getId()) {
475         if (!(document->getObjectById(current_operand->getId()))) {
476             operand_path.remove_link();
477             operand = nullptr;
478             operand_id = "";
479             current_operand = nullptr;
480         } else {
481             operand_id = current_operand->getId();
482         }
483     }
484     SPLPEItem *operandlpe = dynamic_cast<SPLPEItem *>(operand_path.getObject());
485     if (desktop &&
486         operand &&
487         sp_lpe_item &&
488         desktop->getSelection()->includes(operand) &&
489         desktop->getSelection()->includes(sp_lpe_item))
490     {
491         if (operandlpe && operandlpe->hasPathEffectOfType(Inkscape::LivePathEffect::EffectType::BOOL_OP)) {
492             sp_lpe_item_update_patheffect(operandlpe, false, false);
493         }
494         desktop->getSelection()->remove(operand);
495     }
496     if (!current_operand) {
497         if (operand) {
498             remove_filter();
499         }
500         operand = nullptr;
501         operand_id = "";
502     }
503 
504     if (current_operand && operand != current_operand) {
505         if (operand) {
506             remove_filter();
507         }
508         operand = current_operand;
509         remove_filter();
510         if (is_load && sp_lpe_item) {
511             sp_lpe_item_update_patheffect(sp_lpe_item, true, true);
512         }
513     }
514     if (operand) {
515         if (is_visible) {
516             add_filter();
517             if (operand->getPosition() - 1 != sp_lpe_item->getPosition()) {
518                 sp_lpe_item->parent->reorder(operand,sp_lpe_item);
519             }
520         } else {
521             remove_filter();
522         }
523     }
524 }
525 
transform_multiply(Geom::Affine const & postmul,bool)526 void LPEBool::transform_multiply(Geom::Affine const &postmul, bool /*set*/)
527 {
528     operand =  dynamic_cast<SPItem *>(sp_lpe_item->document->getObjectById(operand_id));
529     if (operand && !isOnClipboard()) {
530         SPDesktop *desktop = SP_ACTIVE_DESKTOP;
531         if (desktop && !desktop->getSelection()->includes(operand)) {
532             prev_affine = operand->transform * sp_item_transform_repr(sp_lpe_item).inverse() * postmul;
533             operand->doWriteTransform(prev_affine);
534         }
535     }
536 }
537 
get_union(SPObject * object)538 Geom::PathVector LPEBool::get_union(SPObject *object)
539 {
540     Geom::PathVector res;
541     Geom::PathVector clippv;
542     SPItem *objitem = dynamic_cast<SPItem *>(object);
543     if (objitem) {
544         SPObject *clip_path = objitem->getClipObject();
545         if (clip_path) {
546             std::vector<SPObject *> clip_path_list = clip_path->childList(true);
547             if (clip_path_list.size()) {
548                 for (auto clip : clip_path_list) {
549                     SPShape *clipshape = dynamic_cast<SPShape *>(clip);
550                     if (clipshape) {
551                         clippv = clipshape->curve()->get_pathvector();
552                     }
553                 }
554             }
555         }
556     }
557     SPGroup *group = dynamic_cast<SPGroup *>(object);
558     if (group) {
559         std::vector<SPItem *> item_list = sp_item_group_item_list(group);
560         for (auto iter : item_list) {
561             if (res.empty()) {
562                 res = get_union(SP_OBJECT(iter));
563             } else {
564                 res = sp_pathvector_boolop(res, get_union(SP_OBJECT(iter)), to_bool_op(bool_op_ex_union), fill_oddEven,
565                                            fill_oddEven);
566             }
567         }
568     }
569     SPShape *shape = dynamic_cast<SPShape *>(object);
570     if (shape) {
571         fill_typ originfill = fill_oddEven;
572         SPCurve *curve = shape->curve();
573         if (curve) {
574             if (res.empty()) {
575                 res = curve->get_pathvector();
576             } else {
577                 res = sp_pathvector_boolop(res, curve->get_pathvector(), to_bool_op(bool_op_ex_union), originfill,
578                                            GetFillTyp(shape));
579             }
580         }
581         originfill = GetFillTyp(shape);
582     }
583     SPText *text = dynamic_cast<SPText *>(object);
584     if (text) {
585         std::unique_ptr<SPCurve> curve = text->getNormalizedBpath();
586         if (curve) {
587             if (res.empty()) {
588                 res = curve->get_pathvector();
589             } else {
590                 res = sp_pathvector_boolop(res, curve->get_pathvector(), to_bool_op(bool_op_ex_union), fill_oddEven,
591                                            fill_oddEven);
592             }
593         }
594     }
595     if (!clippv.empty()) {
596         res = sp_pathvector_boolop(res, clippv, to_bool_op(bool_op_ex_inters), fill_oddEven, fill_oddEven);
597     }
598     return res;
599 }
600 
doEffect(SPCurve * curve)601 void LPEBool::doEffect(SPCurve *curve)
602 {
603     Geom::PathVector path_in = curve->get_pathvector();
604     if (operand == SP_ITEM(current_shape)) {
605         g_warning("operand and current shape are the same");
606         operand_path.param_set_default();
607         return;
608     }
609     if (operand_path.getObject() && operand) {
610         bool_op_ex op = bool_operation.get_value();
611         bool swap =  swap_operands.get_value();
612 
613         Geom::Affine current_affine = sp_lpe_item->transform;
614         Geom::Affine operand_affine = operand->transform;
615         Geom::PathVector operand_pv = get_union(operand);
616         if (operand_pv.empty()) {
617             return;
618         }
619         path_in *= current_affine;
620         operand_pv *= operand_affine;
621 
622         Geom::PathVector path_a = swap ? path_in : operand_pv;
623         Geom::PathVector path_b = swap ? operand_pv : path_in;
624         _hp = path_a;
625         _hp.insert(_hp.end(), path_b.begin(), path_b.end());
626         _hp *= current_affine.inverse();
627         fill_typ fill_this    = fill_type_this.get_value() != fill_justDont ? fill_type_this.get_value() : GetFillTyp(current_shape);
628         fill_typ fill_operand = fill_type_operand.get_value() != fill_justDont ? fill_type_operand.get_value() : GetFillTyp(operand_path.getObject());
629 
630         fill_typ fill_a = swap ? fill_this : fill_operand;
631         fill_typ fill_b = swap ? fill_operand : fill_this;
632 
633         if (rmv_inner.get_value()) {
634             path_b = sp_pathvector_boolop_remove_inner(path_b, fill_b);
635         }
636 
637         Geom::PathVector path_out;
638         if (op == bool_op_ex_cut) {
639             Geom::PathVector path_tmp = sp_pathvector_boolop(path_a, path_b, to_bool_op(op), fill_a, fill_b);
640             for (auto pathit : path_tmp) {
641                 if (pathit.size() != 2) {
642                     path_out.push_back(pathit);
643                 }
644             }
645         /* } else if (op == bool_op_ex_slice) {
646             path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, true, fill_a, fill_b);
647             Geom::PathVector path_tmp = sp_pathvector_boolop_slice_intersect(path_a, path_b, false, fill_a, fill_b);
648             for (auto pathit : path_tmp) {
649                 path_out.push_back(pathit);
650             }
651         } else if (op == bool_op_ex_slice_inside) {
652             path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, true, fill_a, fill_b);
653         } else if (op == bool_op_ex_slice_outside) {
654             path_out = sp_pathvector_boolop_slice_intersect(path_a, path_b, false, fill_a, fill_b);
655          */
656         } else {
657             path_out = sp_pathvector_boolop(path_a, path_b, to_bool_op(op), fill_a, fill_b);
658         }
659         /*
660         Maybe add a bit simplify?
661         Geom::PathVector pathv = path_out * current_affine.inverse();
662         gdouble size  = Geom::L2(pathv.boundsFast()->dimensions());
663         Path* pathliv = Path_for_pathvector(pathv);
664         pathliv->ConvertEvenLines(0.00002 * size);
665         pathliv->Simplify(0.00002 * size);
666         pathv = Geom::parse_svg_path(pathliv->svg_dump_path()); */
667         curve->set_pathvector(path_out * current_affine.inverse());
668     }
669 }
670 
addCanvasIndicators(SPLPEItem const *,std::vector<Geom::PathVector> & hp_vec)671 void LPEBool::addCanvasIndicators(SPLPEItem const * /*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
672 {
673     hp_vec.push_back(_hp);
674 }
675 
doOnRemove(SPLPEItem const *)676 void LPEBool::doOnRemove(SPLPEItem const * /*lpeitem*/)
677 {
678     // set "keep paths" hook on sp-lpe-item.cpp
679     std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
680     if (lpeitems.size() == 1) {
681         sp_lpe_item = lpeitems[0];
682         if (!sp_lpe_item->path_effects_enabled) {
683             return;
684         }
685         SPItem *operand = dynamic_cast<SPItem *>(operand_path.getObject());
686         if (operand) {
687             if (keep_paths) {
688                 if (is_visible) {
689                     operand->deleteObject(true);
690                 }
691             } else {
692                 if (is_visible) {
693                     remove_filter();
694                 }
695             }
696         }
697     }
698 }
699 
700 // TODO: Migrate the tree next function to effect.cpp/h to avoid duplication
doOnVisibilityToggled(SPLPEItem const *)701 void LPEBool::doOnVisibilityToggled(SPLPEItem const * /*lpeitem*/)
702 {
703     SPItem *operand = dynamic_cast<SPItem *>(operand_path.getObject());
704     if (operand) {
705         if (!is_visible) {
706             remove_filter();
707         }
708     }
709 }
710 
711 } // namespace LivePathEffect
712 } /* namespace Inkscape */
713