Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
path-boolop.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
5 * Authors:
6 * see git history
7 * Created by fred on Fri Dec 05 2003.
8 * tweaked endlessly by bulia byak <buliabyak@users.sf.net>
9 *
10 * Copyright (C) 2018 Authors
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14#include "path-boolop.h"
15
16#include <vector>
17
18#include <glibmm/i18n.h>
19
21#include <2geom/svg-path-parser.h> // to get from SVG on boolean to Geom::Path
22#include <2geom/utils.h>
23
24#include "document-undo.h"
25#include "document.h"
26#include "message-stack.h"
27#include "path-chemistry.h" // copy_object_properties()
28#include "path-util.h"
29
30#include "display/curve.h"
31#include "livarot/Path.h"
32#include "livarot/Shape.h"
33#include "object/object-set.h" // This file defines some member functions of ObjectSet.
34#include "object/sp-flowtext.h"
35#include "object/sp-shape.h"
36#include "object/sp-text.h"
37#include "ui/icon-names.h"
38#include "xml/repr-sorting.h"
39#include "style.h"
40
42
43/*
44 * ObjectSet functions
45 */
46
47void Inkscape::ObjectSet::pathUnion(bool skip_undo, bool silent)
48{
49 _pathBoolOp(bool_op_union, INKSCAPE_ICON("path-union"), _("Union"), skip_undo, silent);
50}
51
52void Inkscape::ObjectSet::pathIntersect(bool skip_undo, bool silent)
53{
54 _pathBoolOp(bool_op_inters, INKSCAPE_ICON("path-intersection"), _("Intersection"), skip_undo, silent);
55}
56
57void Inkscape::ObjectSet::pathDiff(bool skip_undo, bool silent)
58{
59 _pathBoolOp(bool_op_diff, INKSCAPE_ICON("path-difference"), _("Difference"), skip_undo, silent);
60}
61
62void Inkscape::ObjectSet::pathSymDiff(bool skip_undo, bool silent)
63{
64 _pathBoolOp(bool_op_symdiff, INKSCAPE_ICON("path-exclusion"), _("Exclusion"), skip_undo, silent);
65}
66
67void Inkscape::ObjectSet::pathCut(bool skip_undo, bool silent)
68{
69 _pathBoolOp(bool_op_cut, INKSCAPE_ICON("path-division"), _("Division"), skip_undo, silent);
70}
71
72void Inkscape::ObjectSet::pathSlice(bool skip_undo, bool silent)
73{
74 _pathBoolOp(bool_op_slice, INKSCAPE_ICON("path-cut"), _("Cut path"), skip_undo, silent);
75}
76
77/*
78 * Utilities
79 */
80
89static Shape make_shape(Path &path, int path_id = -1, FillRule fill_rule = fill_nonZero, bool close_if_needed = true)
90{
92
93 Shape tmp;
94 path.Fill(&tmp, path_id, false, close_if_needed);
95 result.ConvertToShape(&tmp, fill_rule);
96
97 return result;
98}
99
100constexpr auto RELATIVE_THRESHOLD = 0.08;
101
106static Path make_path(Geom::PathVector const &pathv, std::vector<Geom::PathVectorTime> const &cuts)
107{
108 Path result;
109
110 result.LoadPathVector(pathv, cuts);
111 result.ConvertWithBackData(RELATIVE_THRESHOLD, true);
112
113 return result;
114}
115
119static bool is_line(Path const &path)
120{
121 return path.pts.size() == 2 && path.pts[0].isMoveTo && !path.pts[1].isMoveTo;
122}
123
124static inline void distribute_intersection_times(std::vector<Geom::PathVectorTime> &dst1, std::vector<Geom::PathVectorTime> &dst2, std::vector<Geom::PathVectorIntersection> const &intersections)
125{
126 auto filter_and_add = [] (auto const &x, auto &dst) {
127 if (x.t > Geom::EPSILON && x.t < 1.0 - Geom::EPSILON) {
128 dst.emplace_back(x);
129 }
130 };
131
132 for (auto const &x : intersections) {
133 filter_and_add(x.first, dst1);
134 filter_and_add(x.second, dst2);
135 }
136}
137
138static void sort_and_clean_intersection_times(std::vector<Geom::PathVectorTime> &vec)
139{
140 std::sort(begin(vec), end(vec));
141
142 auto prev = Geom::PathVectorTime{0, 0, 0.0};
143 for (auto it = vec.begin(); it != vec.end(); ) {
144 if (it->path_index == prev.path_index && it->curve_index == prev.curve_index && it->t < prev.t + Geom::EPSILON) {
145 it = vec.erase(it);
146 } else {
147 prev = *it;
148 ++it;
149 }
150 }
151}
152
153/*
154 * Flattening
155 */
156
158{
159 std::vector<Geom::PathVectorTime> times;
160 distribute_intersection_times(times, times, pathv.intersectSelf());
162
163 auto path = make_path(pathv, times);
164 auto shape = make_shape(path, 0, fill_rule);
165
166 Path res;
167 shape.ConvertToForme(&res, 1, std::begin({ &path }));
168
169 return res.MakePathVector();
170}
171
172void flatten(Geom::PathVector &pathv, FillRule fill_rule)
173{
174 pathv = flattened(pathv, fill_rule);
175}
176
177/*
178 * Boolean operations on pathvectors
179 */
180
181std::vector<Geom::PathVector> pathvector_cut(Geom::PathVector const &pathv, Geom::PathVector const &lines)
182{
183 std::vector<Geom::PathVectorTime> timesa, timesb;
184 distribute_intersection_times(timesa, timesa, pathv.intersectSelf());
185 distribute_intersection_times(timesb, timesb, lines.intersectSelf());
186 distribute_intersection_times(timesa, timesb, pathv.intersect(lines));
189
190 auto patha = make_path(pathv, timesa);
191 auto pathb = make_path(lines, timesb);
192 auto shapea = make_shape(patha, 0);
193 auto shapeb = make_shape(pathb, 1, fill_justDont, is_line(pathb));
194
195 Shape shape;
196 shape.Booleen(&shapeb, &shapea, bool_op_cut, 1);
197
198 Path path;
199 int num_nesting = 0;
200 int *nesting = nullptr;
201 int *conts = nullptr;
202 shape.ConvertToFormeNested(&path, 2, std::begin({ &patha, &pathb }), num_nesting, nesting, conts, true);
203
204 int num_paths;
205 auto paths = path.SubPathsWithNesting(num_paths, false, num_nesting, nesting, conts);
206
207 std::vector<Geom::PathVector> result;
208 result.reserve(num_paths);
209
210 for (int i = 0; i < num_paths; i++) {
211 result.emplace_back(paths[i]->MakePathVector());
212 delete paths[i];
213 }
214
215 g_free(paths);
216 g_free(conts);
217 g_free(nesting);
218
219 return result;
220}
221
223{
224 std::vector<Geom::PathVectorTime> timesa, timesb;
225 distribute_intersection_times(timesa, timesa, pathva.intersectSelf());
226 distribute_intersection_times(timesb, timesb, pathvb.intersectSelf());
227 distribute_intersection_times(timesa, timesb, pathva.intersect(pathvb));
230
231 auto patha = make_path(pathva, timesa);
232 auto pathb = make_path(pathvb, timesb);
233
234 Path result;
235
236 if (bop == bool_op_inters || bop == bool_op_union || bop == bool_op_diff || bop == bool_op_symdiff) {
237 // true boolean op
238 // get the polygons of each path, with the winding rule specified, and apply the operation iteratively
239 auto shapea = make_shape(patha, 0, fra);
240 auto shapeb = make_shape(pathb, 1, frb);
241
242 Shape shape;
243 shape.Booleen(&shapeb, &shapea, bop);
244
245 shape.ConvertToForme(&result, 2, std::begin({ &patha, &pathb }));
246
247 } else if (bop == bool_op_cut) {
248 // cuts= sort of a bastard boolean operation, thus not the axact same modus operandi
249 // technically, the cut path is not necessarily a polygon (thus has no winding rule)
250 // it is just uncrossed, and cleaned from duplicate edges and points
251 // then it's fed to Booleen() which will uncross it against the other path
252 // then comes the trick: each edge of the cut path is duplicated (one in each direction),
253 // thus making a polygon. the weight of the edges of the cut are all 0, but
254 // the Booleen need to invert the ones inside the source polygon (for the subsequent
255 // ConvertToForme)
256
257 // the cut path needs to have the highest pathID in the back data
258 // that's how the Booleen() function knows it's an edge of the cut
259 // fill_justDont doesn't compute winding numbers
260 // see LP Bug 177956 for why is_line is needed
261 auto shapea = make_shape(patha, 1, fill_justDont, is_line(patha));
262 auto shapeb = make_shape(pathb, 0, frb);
263
264 Shape shape;
265 shape.Booleen(&shapea, &shapeb, bool_op_cut, 1);
266
267 shape.ConvertToForme(&result, 2, std::begin({ &pathb, &patha }), true);
268
269 } else if (bop == bool_op_slice) {
270 // slice is not really a boolean operation
271 // you just put the 2 shapes in a single polygon, uncross it
272 // the points where the degree is > 2 are intersections
273 // just check it's an intersection on the path you want to cut, and keep it
274 // the intersections you have found are then fed to ConvertPositionsToMoveTo() which will
275 // make new subpath at each one of these positions
276 // inversion pour l'opération
277
278 Shape tmp;
279 pathb.Fill(&tmp, 0, false, false, false); // don't closeIfNeeded
280 patha.Fill(&tmp, 1, true, false, false); // don't closeIfNeeded and just dump in the shape, don't reset it
281
282 Shape shape;
283 shape.ConvertToShape(&tmp, fill_justDont);
284
285 std::vector<Path::cut_position> toCut;
286
287 assert(shape.hasBackData());
288
289 for (int i = 0; i < shape.numberOfPoints(); i++) {
290 if (shape.getPoint(i).totalDegree() > 2) {
291 // possibly an intersection
292 // we need to check that at least one edge from the source path is incident to it
293 // before we declare it's an intersection
294 int nbOrig = 0;
295 int nbOther = 0;
296 int piece = -1;
297 double t = 0.0;
298
299 int cb = shape.getPoint(i).incidentEdge[FIRST];
300 while (cb >= 0 && cb < shape.numberOfEdges()) {
301 if (shape.ebData[cb].pathID == 0) {
302 // the source has an edge incident to the point, get its position on the path
303 piece = shape.ebData[cb].pieceID;
304 t = shape.getEdge(cb).st == i ? shape.ebData[cb].tSt : shape.ebData[cb].tEn;
305 nbOrig++;
306 }
307 if (shape.ebData[cb].pathID == 1) {
308 nbOther++; // the cut is incident to this point
309 }
310 cb = shape.NextAt(i, cb);
311 }
312
313 if (nbOrig > 0 && nbOther > 0) {
314 // point incident to both path and cut: an intersection
315 // note that you only keep one position on the source; you could have degenerate
316 // cases where the source crosses itself at this point, and you wouyld miss an intersection
317 toCut.push_back({ .piece = piece, .t = t });
318 }
319 }
320 }
321
322 // I think it's useless now
323 for (int i = shape.numberOfEdges() - 1; i >= 0; i--) {
324 if (shape.ebData[i].pathID == 1) {
325 shape.SubEdge(i);
326 }
327 }
328
329 result.Copy(&pathb);
330 result.ConvertPositionsToMoveTo(toCut.size(), toCut.data()); // cut where you found intersections
331 }
332
333 return result.MakePathVector();
334}
335
336void Inkscape::ObjectSet::_pathBoolOp(BooleanOp bop, char const *icon_name, char const *description, bool skip_undo, bool silent)
337{
338 try {
340 if (!skip_undo) {
341 DocumentUndo::done(document(), description, icon_name);
342 }
343 } catch (char const *msg) {
344 if (!silent) {
345 if (desktop()) {
347 } else {
348 g_printerr("%s\n", msg);
349 }
350 }
351 }
352}
353
355{
356 auto const doc = document();
357
358 // Grab the items list.
359 auto const il = std::vector<SPItem*>(items().begin(), items().end());
360
361 // Validate number of items.
362 switch (bop) {
363 case bool_op_union:
364 if (il.size() < 1) { // Allow union of single item --> flatten.
365 throw _("Select <b>at least 1 path</b> to perform a boolean union.");
366 }
367 break;
368 case bool_op_inters:
369 case bool_op_symdiff:
370 if (il.size() < 2) {
371 throw _("Select <b>at least 2 paths</b> to perform an intersection or symmetric difference.");
372 }
373 break;
374 case bool_op_diff:
375 case bool_op_cut:
376 case bool_op_slice:
377 if (il.size() != 2) {
378 throw _("Select <b>exactly 2 paths</b> to perform difference, division, or path cut.");
379 }
380 break;
381 }
382 assert(!il.empty());
383
384 // reverseOrderForOp marks whether the order of the list is the top->down order
385 // it's only used when there are 2 objects, and for operations which need to know the
386 // topmost object (differences, cuts)
387 bool reverseOrderForOp = false;
388
389 if (bop == bool_op_diff || bop == bool_op_cut || bop == bool_op_slice) {
390 // check in the tree to find which element of the selection list is topmost (for 2-operand commands only)
391 Inkscape::XML::Node *a = il.front()->getRepr();
392 Inkscape::XML::Node *b = il.back()->getRepr();
393
394 if (!a || !b) {
395 return;
396 }
397
398 if (is_descendant_of(a, b)) {
399 // a is a child of b, already in the proper order
400 } else if (is_descendant_of(b, a)) {
401 // reverse order
402 reverseOrderForOp = true;
403 } else {
404
405 // objects are not in parent/child relationship;
406 // find their lowest common ancestor
408 if (!parent) {
409 return;
410 }
411
412 // find the children of the LCA that lead from it to the a and b
415
416 // find out which comes first
417 for (Inkscape::XML::Node *child = parent->firstChild(); child; child = child->next()) {
418 if (child == as) {
419 /* a first, so reverse. */
420 reverseOrderForOp = true;
421 break;
422 }
423 if (child == bs)
424 break;
425 }
426 }
427 }
428
429 // first check if all the input objects have shapes
430 // otherwise bail out
431 for (auto item : il) {
432 if (!is<SPShape>(item) && !is<SPText>(item) && !is<SPFlowtext>(item)) {
433 return;
434 }
435 }
436
437 struct Operand
438 {
439 // From source objects
440 FillRule fill_rule{};
441 Geom::PathVector pathv;
442
443 // Computed
444 std::vector<Geom::PathVectorTime> cuts;
445 std::unique_ptr<Path> path;
446 };
447
448 std::vector<Operand> operands;
449 operands.resize(il.size());
450
451 // Extract the fill rules and pathvectors from the source objects.
452 for (int i = 0; i < il.size(); i++) {
453 auto item = il[i];
454 auto &operand = operands[i];
455
456 // apply live path effects prior to performing boolean operation
457 char const *id = item->getAttribute("id");
458 if (auto lpeitem = cast<SPLPEItem>(item)) {
459 SPDocument * document = item->document;
460 lpeitem->removeAllPathEffects(true);
461 SPObject *elemref = document->getObjectById(id);
462 if (elemref && elemref != item) {
463 // If the LPE item is a shape, it is converted to a path
464 // so we need to reupdate the item
465 item = cast<SPItem>(elemref);
466 }
467 }
468
469 // Get the fill rule.
470 if (item->style->fill_rule.computed == SP_WIND_RULE_EVENODD) {
471 operand.fill_rule = fill_oddEven;
472 } else {
473 operand.fill_rule = fill_nonZero;
474 }
475
476 // Get the pathvector.
477 auto curve = curve_for_item(item);
478 if (!curve) {
479 return;
480 }
481
482 operand.pathv = curve->get_pathvector() * item->i2doc_affine();
483 }
484
485 // Compute the intersections and self-intersections, and use this information when converting to livarot paths.
486 for (int i = 0; i < operands.size(); i++) {
487 for (int j = 0; j < i; j++) {
488 distribute_intersection_times(operands[i].cuts, operands[j].cuts, operands[i].pathv.intersect(operands[j].pathv));
489 }
490 }
491
492 for (auto &operand : operands) {
493 distribute_intersection_times(operand.cuts, operand.cuts, operand.pathv.intersectSelf());
495
496 operand.path = std::make_unique<Path>();
497 operand.path->LoadPathVector(operand.pathv, operand.cuts);
498 operand.path->ConvertWithBackData(RELATIVE_THRESHOLD, true);
499
500 if (operand.path->descr_cmd.size() <= 1) {
501 return;
502 }
503 }
504
505 // reverse if needed
506 // note that the selection list keeps its order
507 if (reverseOrderForOp) {
508 std::swap(operands[0], operands[1]);
509 }
510
511 // and work
512 // some temporary instances, first
513 Shape *theShapeA = new Shape;
514 Shape *theShapeB = new Shape;
515 Shape *theShape = new Shape;
516 Path *res = new Path;
517 Path::cut_position *toCut=nullptr;
518 int nbToCut=0;
519
520 if (bop == bool_op_inters || bop == bool_op_union || bop == bool_op_diff || bop == bool_op_symdiff) {
521 // true boolean op
522 // get the polygons of each path, with the winding rule specified, and apply the operation iteratively
523
524 operands[0].path->Fill(theShape, 0);
525
526 theShapeA->ConvertToShape(theShape, operands[0].fill_rule);
527
528 for (int i = 1; i < operands.size(); i++) {
529 operands[i].path->Fill(theShape, i);
530
531 theShapeB->ConvertToShape(theShape, operands[i].fill_rule);
532
533 /* Due to quantization of the input shape coordinates, we may end up with A or B being empty.
534 * If this is a union or symdiff operation, we just use the non-empty shape as the result:
535 * A=0 => (0 or B) == B
536 * B=0 => (A or 0) == A
537 * A=0 => (0 xor B) == B
538 * B=0 => (A xor 0) == A
539 * If this is an intersection operation, we just use the empty shape as the result:
540 * A=0 => (0 and B) == 0 == A
541 * B=0 => (A and 0) == 0 == B
542 * If this a difference operation, and the upper shape (A) is empty, we keep B.
543 * If the lower shape (B) is empty, we still keep B, as it's empty:
544 * A=0 => (B - 0) == B
545 * B=0 => (0 - A) == 0 == B
546 *
547 * In any case, the output from this operation is stored in shape A, so we may apply
548 * the above rules simply by judicious use of swapping A and B where necessary.
549 */
550 bool zeroA = theShapeA->numberOfEdges() == 0;
551 bool zeroB = theShapeB->numberOfEdges() == 0;
552 if (zeroA || zeroB) {
553 // We might need to do a swap. Apply the above rules depending on operation type.
554 bool resultIsB = ((bop == bool_op_union || bop == bool_op_symdiff) && zeroA)
555 || ((bop == bool_op_inters) && zeroB)
556 || (bop == bool_op_diff);
557 if (resultIsB) {
558 // Swap A and B to use B as the result
559 std::swap(theShapeA, theShapeB);
560 }
561 } else {
562 // Just do the Boolean operation as usual
563 // les elements arrivent en ordre inverse dans la liste
564 theShape->Booleen(theShapeB, theShapeA, bop);
565 std::swap(theShape, theShapeA);
566 }
567 }
568
569 std::swap(theShape, theShapeA);
570
571 } else if (bop == bool_op_cut) {
572 // cuts= sort of a bastard boolean operation, thus not the axact same modus operandi
573 // technically, the cut path is not necessarily a polygon (thus has no winding rule)
574 // it is just uncrossed, and cleaned from duplicate edges and points
575 // then it's fed to Booleen() which will uncross it against the other path
576 // then comes the trick: each edge of the cut path is duplicated (one in each direction),
577 // thus making a polygon. the weight of the edges of the cut are all 0, but
578 // the Booleen need to invert the ones inside the source polygon (for the subsequent
579 // ConvertToForme)
580
581 // the cut path needs to have the highest pathID in the back data
582 // that's how the Booleen() function knows it's an edge of the cut
583 std::swap(operands[0], operands[1]);
584
585 operands[0].path->Fill(theShape, 0);
586
587 theShapeA->ConvertToShape(theShape, operands[0].fill_rule);
588
589 operands[1].path->Fill(theShape, 1, false, is_line(*operands[1].path), false);
590
591 theShapeB->ConvertToShape(theShape, fill_justDont);
592
593 // les elements arrivent en ordre inverse dans la liste
594 theShape->Booleen(theShapeB, theShapeA, bool_op_cut, 1);
595
596 } else if (bop == bool_op_slice) {
597 // slice is not really a boolean operation
598 // you just put the 2 shapes in a single polygon, uncross it
599 // the points where the degree is > 2 are intersections
600 // just check it's an intersection on the path you want to cut, and keep it
601 // the intersections you have found are then fed to ConvertPositionsToMoveTo() which will
602 // make new subpath at each one of these positions
603 // inversion pour l'opération
604 std::swap(operands[0], operands[1]);
605
606 operands[0].path->Fill(theShapeA, 0, false, false, false); // don't closeIfNeeded
607
608 operands[1].path->Fill(theShapeA, 1, true, false, false); // don't closeIfNeeded and just dump in the shape, don't reset it
609
610 theShape->ConvertToShape(theShapeA, fill_justDont);
611
612 if (theShape->hasBackData()) {
613 // should always be the case, but ya never know
614 {
615 for (int i = 0; i < theShape->numberOfPoints(); i++) {
616 if ( theShape->getPoint(i).totalDegree() > 2 ) {
617 // possibly an intersection
618 // we need to check that at least one edge from the source path is incident to it
619 // before we declare it's an intersection
620 int cb = theShape->getPoint(i).incidentEdge[FIRST];
621 int nbOrig=0;
622 int nbOther=0;
623 int piece=-1;
624 float t=0.0;
625 while ( cb >= 0 && cb < theShape->numberOfEdges() ) {
626 if ( theShape->ebData[cb].pathID == 0 ) {
627 // the source has an edge incident to the point, get its position on the path
628 piece=theShape->ebData[cb].pieceID;
629 if ( theShape->getEdge(cb).st == i ) {
630 t=theShape->ebData[cb].tSt;
631 } else {
632 t=theShape->ebData[cb].tEn;
633 }
634 nbOrig++;
635 }
636 if ( theShape->ebData[cb].pathID == 1 ) nbOther++; // the cut is incident to this point
637 cb=theShape->NextAt(i, cb);
638 }
639 if ( nbOrig > 0 && nbOther > 0 ) {
640 // point incident to both path and cut: an intersection
641 // note that you only keep one position on the source; you could have degenerate
642 // cases where the source crosses itself at this point, and you wouyld miss an intersection
643 toCut=(Path::cut_position*)realloc(toCut, (nbToCut+1)*sizeof(Path::cut_position));
644 toCut[nbToCut].piece=piece;
645 toCut[nbToCut].t=t;
646 nbToCut++;
647 }
648 }
649 }
650 }
651 {
652 // i think it's useless now
653 int i = theShape->numberOfEdges() - 1;
654 for (;i>=0;i--) {
655 if ( theShape->ebData[i].pathID == 1 ) {
656 theShape->SubEdge(i);
657 }
658 }
659 }
660
661 }
662 }
663
664 auto get_path_arr = [&] {
665 std::vector<Path *> result;
666 result.reserve(operands.size());
667 for (auto &operand : operands) {
668 result.emplace_back(operand.path.get());
669 }
670 return result;
671 };
672
673 int* nesting=nullptr;
674 int* conts=nullptr;
675 int nbNest=0;
676 // pour compenser le swap juste avant
677 if (bop == bool_op_slice) {
678 res->Copy(operands[0].path.get());
679 res->ConvertPositionsToMoveTo(nbToCut, toCut); // cut where you found intersections
680 free(toCut);
681 } else if (bop == bool_op_cut) {
682 // il faut appeler pour desallouer PointData (pas vital, mais bon)
683 // the Booleen() function did not deallocate the point_data array in theShape, because this
684 // function needs it.
685 // this function uses the point_data to get the winding number of each path (ie: is a hole or not)
686 // for later reconstruction in objects, you also need to extract which path is parent of holes (nesting info)
687 theShape->ConvertToFormeNested(res, operands.size(), get_path_arr().data(), nbNest, nesting, conts, true);
688 } else {
689 theShape->ConvertToForme(res, operands.size(), get_path_arr().data());
690 }
691
692 delete theShape;
693 delete theShapeA;
694 delete theShapeB;
695
696 if (res->descr_cmd.size() <= 1) {
697 // only one command, presumably a moveto: it isn't a path
698 for (auto l : il){
699 l->deleteObject();
700 }
701 clear();
702
703 delete res;
704 return;
705 }
706
707 // get the source path object
708 SPObject *source;
709 if ( bop == bool_op_diff || bop == bool_op_cut || bop == bool_op_slice ) {
710 if (reverseOrderForOp) {
711 source = il[0];
712 } else {
713 source = il.back();
714 }
715 } else {
716 // find out the bottom object
717 std::vector<Inkscape::XML::Node*> sorted(xmlNodes().begin(), xmlNodes().end());
718
719 sort(sorted.begin(),sorted.end(),sp_repr_compare_position_bool);
720
721 source = doc->getObjectByRepr(sorted.front());
722 }
723
724 // adjust style properties that depend on a possible transform in the source object in order
725 // to get a correct style attribute for the new path
726 auto item_source = cast<SPItem>(source);
727 Geom::Affine i2doc(item_source->i2doc_affine());
728
729 Inkscape::XML::Node *repr_source = source->getRepr();
730
731 // remember important aspects of the source path, to be restored
732 gint pos = repr_source->position();
733 Inkscape::XML::Node *parent = repr_source->parent();
734 // remove source paths
735 clear();
736 for (auto l : il){
737 if (l != item_source) {
738 // delete the object for real, so that its clones can take appropriate action
739 l->deleteObject();
740 }
741 }
742
743 auto const source2doc_inverse = i2doc.inverse();
744 char const *const old_transform_attibute = repr_source->attribute("transform");
745
746 // now that we have the result, add it on the canvas
747 if ( bop == bool_op_cut || bop == bool_op_slice ) {
748 int nbRP=0;
749 Path** resPath;
750 if ( bop == bool_op_slice ) {
751 // there are moveto's at each intersection, but it's still one unique path
752 // so break it down and add each subpath independently
753 // we could call break_apart to do this, but while we have the description...
754 resPath=res->SubPaths(nbRP, false);
755 } else {
756 // cut operation is a bit wicked: you need to keep holes
757 // that's why you needed the nesting
758 // ConvertToFormeNested() dumped all the subpath in a single Path "res", so we need
759 // to get the path for each part of the polygon. that's why you need the nesting info:
760 // to know in which subpath to add a subpath
761 resPath=res->SubPathsWithNesting(nbRP, true, nbNest, nesting, conts);
762
763 // cleaning
764 if ( conts ) free(conts);
765 if ( nesting ) free(nesting);
766 }
767
768 // add all the pieces resulting from cut or slice
769 std::vector <Inkscape::XML::Node*> selection;
770 for (int i=0;i<nbRP;i++) {
771 resPath[i]->Transform(source2doc_inverse);
772
773 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
774 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
775
776 Inkscape::copy_object_properties(repr, repr_source);
777
778 // Delete source on last iteration (after we don't need repr_source anymore). As a consequence, the last
779 // item will inherit the original's id.
780 if (i + 1 == nbRP) {
781 item_source->deleteObject(false);
782 }
783
784 repr->setAttribute("d", resPath[i]->svg_dump_path().c_str());
785
786 // for slice, remove fill
787 if (bop == bool_op_slice) {
788 SPCSSAttr *css;
789
791 sp_repr_css_set_property(css, "fill", "none");
792
793 sp_repr_css_change(repr, css, "style");
794
796 }
797
798 repr->setAttributeOrRemoveIfEmpty("transform", old_transform_attibute);
799
800 // add the new repr to the parent
801 // move to the saved position
802 parent->addChildAtPos(repr, pos);
803
804 selection.push_back(repr);
806
807 delete resPath[i];
808 }
809 setReprList(selection);
810 if ( resPath ) free(resPath);
811
812 } else {
813 res->Transform(source2doc_inverse);
814
815 Inkscape::XML::Document *xml_doc = doc->getReprDoc();
816 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
817
818 Inkscape::copy_object_properties(repr, repr_source);
819
820 // delete it so that its clones don't get alerted; this object will be restored shortly, with the same id
821 item_source->deleteObject(false);
822
823 repr->setAttribute("d", res->svg_dump_path().c_str());
824
825 repr->setAttributeOrRemoveIfEmpty("transform", old_transform_attibute);
826
827 parent->addChildAtPos(repr, pos);
828
829 set(repr);
831 }
832
833 delete res;
834}
835
836/*
837 Local Variables:
838 mode:c++
839 c-file-style:"stroustrup"
840 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
841 indent-tabs-mode:nil
842 fill-column:99
843 End:
844*/
845// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Various utility functions.
FillRule
Definition LivarotDefs.h:67
@ fill_oddEven
Definition LivarotDefs.h:68
@ fill_nonZero
Definition LivarotDefs.h:69
@ fill_justDont
Definition LivarotDefs.h:71
BooleanOp
Definition LivarotDefs.h:76
@ bool_op_symdiff
Definition LivarotDefs.h:80
@ bool_op_diff
Definition LivarotDefs.h:79
@ bool_op_cut
Definition LivarotDefs.h:81
@ bool_op_inters
Definition LivarotDefs.h:78
@ bool_op_union
Definition LivarotDefs.h:77
@ bool_op_slice
Definition LivarotDefs.h:82
@ FIRST
Definition LivarotDefs.h:91
TODO: insert short description here.
TODO: insert short description here.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Sequence of subpaths.
Definition pathvector.h:122
std::vector< PathVectorIntersection > intersectSelf(Coord precision=EPSILON) const
Get all intersections of the path-vector with itself.
void resize(size_type n)
Change the number of paths.
Definition pathvector.h:199
std::vector< PVIntersection > intersect(PathVector const &other, Coord precision=EPSILON) const
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
void pathUnion(bool skip_undo=false, bool silent=false)
void pathCut(bool skip_undo=false, bool silent=false)
void pathDiff(bool skip_undo=false, bool silent=false)
void pathIntersect(bool skip_undo=false, bool silent=false)
void pathSymDiff(bool skip_undo=false, bool silent=false)
void _pathBoolOp(BooleanOp bop, char const *icon_name, char const *description, bool skip_undo, bool silent)
void pathSlice(bool skip_undo=false, bool silent=false)
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:167
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual Node * firstChild()=0
Get the first child of this node.
virtual unsigned position() const =0
Get the index of this node in parent's child order.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
Path and its polyline approximation.
Definition Path.h:93
void Fill(Shape *dest, int pathID=-1, bool justAdd=false, bool closeIfNeeded=true, bool invert=false)
Fills the shape with the polyline approximation stored in this object.
std::string svg_dump_path() const
Definition Path.cpp:577
std::vector< path_lineto > pts
Definition Path.h:128
void Transform(const Geom::Affine &trans)
Apply a transformation on all path commands.
Definition Path.cpp:422
void LoadPathVector(Geom::PathVector const &pv, Geom::Affine const &tr, bool doTransformation)
Load a lib2geom Geom::PathVector in this path object.
Geom::PathVector MakePathVector() const
Create a lib2geom Geom::PathVector from this Path object.
void Copy(Path *who)
Clear all stored path commands, resets flags and imports path commands from the passed Path object.
Definition Path.cpp:57
Path ** SubPathsWithNesting(int &outNb, bool killNoSurf, int nbNest, int *nesting, int *conts)
void ConvertPositionsToMoveTo(int nbPos, cut_position *poss)
Path ** SubPaths(int &outNb, bool killNoSurf)
std::vector< PathDescr * > descr_cmd
Definition Path.h:108
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
Typed SVG document implementation.
Definition document.h:103
SPObject * getObjectById(std::string const &id) const
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1816
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPDocument * document
Definition sp-object.h:188
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
char const * getAttribute(char const *name) const
T< SPAttr::FILL_RULE, SPIEnum< SPWindRule > > fill_rule
fill-rule: 0 nonzero, 1 evenodd
Definition style.h:244
A class to store/manipulate directed graphs.
Definition Shape.h:65
void ConvertToForme(Path *dest)
Extract contours from a directed graph.
Definition ShapeMisc.cpp:42
int Booleen(Shape *a, Shape *b, BooleanOp mod, int cutPathID=-1)
std::vector< back_data > ebData
Definition Shape.h:422
void ConvertToFormeNested(Path *dest, int nbP, Path *const *orig, int &nbNest, int *&nesting, int *&contStart, bool never_split=false)
int numberOfEdges() const
Returns number of edges.
Definition Shape.h:498
int NextAt(int p, int b) const
Definition Shape.h:190
int numberOfPoints() const
Returns number of points.
Definition Shape.h:484
dg_arete const & getEdge(int n) const
Get an edge.
Definition Shape.h:548
int ConvertToShape(Shape *a, FillRule directed=fill_nonZero, bool invert=false)
Using a given fill rule, find all intersections in the shape given, create a new intersection free sh...
dg_point const & getPoint(int n) const
Get a point.
Definition Shape.h:537
bool hasBackData() const
Do we have back data?
Definition Shape.h:526
void SubEdge(int e)
Definition Shape.cpp:1177
std::shared_ptr< Css const > css
Css & result
Glib::ustring msg
static char const *const parent
Definition dir-util.cpp:70
TODO: insert short description here.
bool is_line(SPObject *i)
constexpr Coord EPSILON
Default "acceptably small" value.
Definition coord.h:84
Macro for icon names used in Inkscape.
SPItem * item
Path intersection graph.
Geom::Point end
Raw stack of active status messages.
vector< vector< Point > > paths
Definition metro.cpp:36
static R & release(R &r)
Decrements the reference count of a anchored object.
void copy_object_properties(XML::Node *dest, XML::Node const *src)
Copy generic object properties, like:
@ ERROR_MESSAGE
Definition message.h:29
void flatten(Geom::PathVector &pathv, FillRule fill_rule)
constexpr auto RELATIVE_THRESHOLD
static void sort_and_clean_intersection_times(std::vector< Geom::PathVectorTime > &vec)
static void distribute_intersection_times(std::vector< Geom::PathVectorTime > &dst1, std::vector< Geom::PathVectorTime > &dst2, std::vector< Geom::PathVectorIntersection > const &intersections)
Geom::PathVector sp_pathvector_boolop(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, BooleanOp bop, FillRule fra, FillRule frb)
Perform a boolean operation on two pathvectors.
static Path make_path(Geom::PathVector const &pathv, std::vector< Geom::PathVectorTime > const &cuts)
Create a path with backdata from a pathvector, automatically estimating a suitable conversion thresho...
static Shape make_shape(Path &path, int path_id=-1, FillRule fill_rule=fill_nonZero, bool close_if_needed=true)
Create a flattened shape from a path.
Geom::PathVector flattened(Geom::PathVector const &pathv, FillRule fill_rule)
Flatten a pathvector according to the given fill rule.
std::vector< Geom::PathVector > pathvector_cut(Geom::PathVector const &pathv, Geom::PathVector const &lines)
Cut a pathvector along a collection of lines into several smaller pathvectors.
Boolean operations.
std::optional< SPCurve > curve_for_item(SPItem *item)
Gets an SPCurve from the SPItem.
Definition path-util.cpp:73
Path utilities.
Ocnode * child[8]
Definition quantize.cpp:33
unsigned long bs
Definition quantize.cpp:38
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_change(Node *repr, SPCSSAttr *css, gchar const *attr)
Creates a new SPCSAttr with the values filled from a repr, merges in properties from the given SPCSAt...
Definition repr-css.cpp:358
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
void sp_repr_css_set_property(SPCSSAttr *css, gchar const *name, gchar const *value)
Set a style property to a new value (e.g.
Definition repr-css.cpp:191
bool is_descendant_of(Inkscape::XML::Node const *descendant, Inkscape::XML::Node const *ancestor)
Inkscape::XML::Node const * lowest_common_ancestor(Inkscape::XML::Node const *a, Inkscape::XML::Node const *b)
Inkscape::XML::Node const * find_containing_child(Inkscape::XML::Node const *descendant, Inkscape::XML::Node const *ancestor)
TODO: insert short description here.
bool sp_repr_compare_position_bool(Inkscape::XML::Node const *first, Inkscape::XML::Node const *second)
GList * items
TODO: insert short description here.
static const Point data[]
Generalized time value in the path vector.
Definition pathvector.h:58
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
int incidentEdge[2]
Definition Shape.h:452
int totalDegree() const
Definition Shape.h:455
Definition curve.h:24
@ SP_WIND_RULE_EVENODD
Definition style-enums.h:26
SPStyle - a style object for SPItem objects.
parse SVG path specifications
SPDesktop * desktop