From 1aee6d9e28c0e144560bbef9e6f861d98b5d29d8 Mon Sep 17 00:00:00 2001 From: su_v Date: Sun, 23 Sep 2012 19:00:33 +0200 Subject: From bug #1048845: proposed addition to livarot for boolean operations (bzr r11668.1.3) --- src/splivarot.cpp | 220 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 217 insertions(+), 3 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 4c1421684..ec82306af 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -53,6 +53,7 @@ #include "splivarot.h" #include "verbs.h" +#include "2geom/svg-path-parser.h" // to get from SVG on boolean to Geom::Path using Inkscape::DocumentUndo; @@ -109,7 +110,205 @@ sp_selected_path_slice(SPDesktop *desktop) } -// boolean operations +// boolean operations PathVectors A,B -> PathVector result. +// This is derived from sp_selected_path_boolop +// take the source paths from the file, do the operation, delete the originals and add the results +// fra,fra are fill_rules for PathVectors a,b +Geom::PathVector +sp_pathvector_boolop(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, bool_op bop, fill_typ fra, fill_typ frb) +{ + + // extract the livarot Paths from the source objects + // also get the winding rule specified in the style + int nbOriginaux = 2; + std::vector originaux(nbOriginaux); + std::vector origWind(nbOriginaux); + origWind[0]=fra; + origWind[1]=frb; + Geom::PathVector patht; + // Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly. + originaux[0] = Path_for_pathvector(pathv_to_linear_and_cubic_beziers( pathva)); + originaux[1] = Path_for_pathvector(pathv_to_linear_and_cubic_beziers( pathvb)); + + // some temporary instances, first + Shape *theShapeA = new Shape; + Shape *theShapeB = new Shape; + Shape *theShape = new Shape; + Path *res = new Path; + res->SetBackData(false); + Path::cut_position *toCut=NULL; + int nbToCut=0; + + if ( bop == bool_op_inters || bop == bool_op_union || bop == bool_op_diff || bop == bool_op_symdiff ) { + // true boolean op + // get the polygons of each path, with the winding rule specified, and apply the operation iteratively + originaux[0]->ConvertWithBackData(0.1); + + originaux[0]->Fill(theShape, 0); + + theShapeA->ConvertToShape(theShape, origWind[0]); + + originaux[1]->ConvertWithBackData(0.1); + + originaux[1]->Fill(theShape, 1); + + theShapeB->ConvertToShape(theShape, origWind[1]); + + theShape->Booleen(theShapeB, theShapeA, bop); + + } else if ( bop == bool_op_cut ) { + // cuts= sort of a bastard boolean operation, thus not the axact same modus operandi + // technically, the cut path is not necessarily a polygon (thus has no winding rule) + // it is just uncrossed, and cleaned from duplicate edges and points + // then it's fed to Booleen() which will uncross it against the other path + // then comes the trick: each edge of the cut path is duplicated (one in each direction), + // thus making a polygon. the weight of the edges of the cut are all 0, but + // the Booleen need to invert the ones inside the source polygon (for the subsequent + // ConvertToForme) + + // the cut path needs to have the highest pathID in the back data + // that's how the Booleen() function knows it's an edge of the cut + + // FIXME: this gives poor results, the final paths are full of extraneous nodes. Decreasing + // ConvertWithBackData parameter below simply increases the number of nodes, so for now I + // left it at 1.0. Investigate replacing this by a combination of difference and + // intersection of the same two paths. -- bb + { + Path* swap=originaux[0];originaux[0]=originaux[1];originaux[1]=swap; + int swai=origWind[0];origWind[0]=origWind[1];origWind[1]=(fill_typ)swai; + } + originaux[0]->ConvertWithBackData(1.0); + + originaux[0]->Fill(theShape, 0); + + theShapeA->ConvertToShape(theShape, origWind[0]); + + originaux[1]->ConvertWithBackData(1.0); + + originaux[1]->Fill(theShape, 1,false,false,false); //do not closeIfNeeded + + theShapeB->ConvertToShape(theShape, fill_justDont); // fill_justDont doesn't computes winding numbers + + // les elements arrivent en ordre inverse dans la liste + theShape->Booleen(theShapeB, theShapeA, bool_op_cut, 1); + + } else if ( bop == bool_op_slice ) { + // slice is not really a boolean operation + // you just put the 2 shapes in a single polygon, uncross it + // the points where the degree is > 2 are intersections + // just check it's an intersection on the path you want to cut, and keep it + // the intersections you have found are then fed to ConvertPositionsToMoveTo() which will + // make new subpath at each one of these positions + // inversion pour l'opŽration + { + Path* swap=originaux[0];originaux[0]=originaux[1];originaux[1]=swap; + int swai=origWind[0];origWind[0]=origWind[1];origWind[1]=(fill_typ)swai; + } + originaux[0]->ConvertWithBackData(1.0); + + originaux[0]->Fill(theShapeA, 0,false,false,false); // don't closeIfNeeded + + originaux[1]->ConvertWithBackData(1.0); + + originaux[1]->Fill(theShapeA, 1,true,false,false);// don't closeIfNeeded and just dump in the shape, don't reset it + + theShape->ConvertToShape(theShapeA, fill_justDont); + + if ( theShape->hasBackData() ) { + // should always be the case, but ya never know + { + for (int i = 0; i < theShape->numberOfPoints(); i++) { + if ( theShape->getPoint(i).totalDegree() > 2 ) { + // possibly an intersection + // we need to check that at least one edge from the source path is incident to it + // before we declare it's an intersection + int cb = theShape->getPoint(i).incidentEdge[FIRST]; + int nbOrig=0; + int nbOther=0; + int piece=-1; + float t=0.0; + while ( cb >= 0 && cb < theShape->numberOfEdges() ) { + if ( theShape->ebData[cb].pathID == 0 ) { + // the source has an edge incident to the point, get its position on the path + piece=theShape->ebData[cb].pieceID; + if ( theShape->getEdge(cb).st == i ) { + t=theShape->ebData[cb].tSt; + } else { + t=theShape->ebData[cb].tEn; + } + nbOrig++; + } + if ( theShape->ebData[cb].pathID == 1 ) nbOther++; // the cut is incident to this point + cb=theShape->NextAt(i, cb); + } + if ( nbOrig > 0 && nbOther > 0 ) { + // point incident to both path and cut: an intersection + // note that you only keep one position on the source; you could have degenerate + // cases where the source crosses itself at this point, and you wouyld miss an intersection + toCut=(Path::cut_position*)realloc(toCut, (nbToCut+1)*sizeof(Path::cut_position)); + toCut[nbToCut].piece=piece; + toCut[nbToCut].t=t; + nbToCut++; + } + } + } + } + { + // i think it's useless now + int i = theShape->numberOfEdges() - 1; + for (;i>=0;i--) { + if ( theShape->ebData[i].pathID == 1 ) { + theShape->SubEdge(i); + } + } + } + + } + } + + int* nesting=NULL; + int* conts=NULL; + int nbNest=0; + // pour compenser le swap juste avant + if ( bop == bool_op_slice ) { +// theShape->ConvertToForme(res, nbOriginaux, originaux, true); +// res->ConvertForcedToMoveTo(); + res->Copy(originaux[0]); + res->ConvertPositionsToMoveTo(nbToCut, toCut); // cut where you found intersections + free(toCut); + } else if ( bop == bool_op_cut ) { + // il faut appeler pour desallouer PointData (pas vital, mais bon) + // the Booleen() function did not deallocated the point_data array in theShape, because this + // function needs it. + // this function uses the point_data to get the winding number of each path (ie: is a hole or not) + // for later reconstruction in objects, you also need to extract which path is parent of holes (nesting info) + theShape->ConvertToFormeNested(res, nbOriginaux, &originaux[0], 1, nbNest, nesting, conts); + } else { + theShape->ConvertToForme(res, nbOriginaux, &originaux[0]); + } + + delete theShape; + delete theShapeA; + delete theShapeB; + delete originaux[0]; + delete originaux[1]; + + std::vector outres = Geom::parse_svg_path(res->svg_dump_path()); + + + delete res; + return outres; +} + + +/* Convert from a livarot path to a 2geom PathVector */ +Geom::PathVector pathliv_to_pathvector(Path *pathliv){ + std::vector outres = Geom::parse_svg_path(pathliv->svg_dump_path()); + return outres; +} + + +// boolean operations on the desktop // take the source paths from the file, do the operation, delete the originals and add the results void sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb, const Glib::ustring description) @@ -746,7 +945,7 @@ Geom::PathVector* item_outline(SPItem const *item, bool bbox_only) } } - // Livarots outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly. + // Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly. Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( curve->get_pathvector() ); Path *orig = new Path; @@ -1012,7 +1211,7 @@ sp_selected_path_outline(SPDesktop *desktop) curve->unref(); continue; } - // Livarots outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly. + // Livarot's outline of arcs is broken. So convert the path to linear and cubics only, for which the outline is created correctly. Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( curvetemp->get_pathvector() ); curvetemp->unref(); @@ -2065,6 +2264,21 @@ Ancetre(Inkscape::XML::Node *a, Inkscape::XML::Node *who) return Ancetre(a->parent(), who); } +// derived from Path_for_item +// there must be some other way to load dest directly from epathv, without going through pathv... +Path * +Path_for_pathvector(Geom::PathVector const &epathv) +{ + Geom::PathVector *pathv = new Geom::PathVector; + std::copy(epathv.begin(), epathv.end(), std::back_inserter(*pathv)); + + Path *dest = new Path; + dest->LoadPathVector(*pathv); + delete pathv; + + return dest; +} + Path * Path_for_item(SPItem *item, bool doTransformation, bool transformFull) { -- cgit v1.2.3 From 370a3f5cc9e39352a081e5d5dd8c43676547a6e6 Mon Sep 17 00:00:00 2001 From: Campbell Barton Date: Thu, 4 Oct 2012 11:45:44 +1000 Subject: code cleanup: add own includes to cpp files or make the functions static if they are not used elsewhere. (bzr r11735) --- src/splivarot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 4c1421684..f1360adda 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -1778,7 +1778,7 @@ sp_selected_path_simplify_items(SPDesktop *desktop, //return true if we changed something, else false -bool +static bool sp_selected_path_simplify_item(SPDesktop *desktop, Inkscape::Selection *selection, SPItem *item, float threshold, bool justCoalesce, @@ -1982,7 +1982,7 @@ sp_selected_path_simplify_items(SPDesktop *desktop, return didSomething; } -void +static void sp_selected_path_simplify_selection(SPDesktop *desktop, float threshold, bool justCoalesce, float angleLimit, bool breakableAngles) { -- cgit v1.2.3 From 81f4c47e7a114dce1fe88da175e7c70083565917 Mon Sep 17 00:00:00 2001 From: Alvin Penner Date: Sat, 5 Jan 2013 10:44:11 -0500 Subject: visual bbox minimum width (Bug 1094802) Fixed bugs: - https://launchpad.net/bugs/1094802 (bzr r12010) --- src/splivarot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index f1360adda..f04d92616 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -716,8 +716,8 @@ Geom::PathVector* item_outline(SPItem const *item, bool bbox_only) ButtType o_butt; { o_width = i_style->stroke_width.computed; - if (o_width < 0.1) { - o_width = 0.1; + if (o_width < 0.01) { + o_width = 0.01; } o_miter = i_style->stroke_miterlimit.value * o_width; -- cgit v1.2.3 From bb82bced04efb9fd9b35e06c76cc8043d0306c44 Mon Sep 17 00:00:00 2001 From: Alvin Penner Date: Mon, 28 Jan 2013 15:53:29 -0500 Subject: Path::OutlineJoin. remove redundant nodes which are causing glitches (Bug 820425) Fixed bugs: - https://launchpad.net/bugs/820425 (bzr r12069) --- src/splivarot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index f04d92616..68664cb3c 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -1002,8 +1002,8 @@ sp_selected_path_outline(SPDesktop *desktop) break; } - if (o_width < 0.1) - o_width = 0.1; + if (o_width < 0.01) + o_width = 0.01; o_miter = i_style->stroke_miterlimit.value * o_width; } -- cgit v1.2.3 From 2fd49c7b03821f690f088a9478ee4780c211d748 Mon Sep 17 00:00:00 2001 From: Alvin Penner Date: Tue, 29 Jan 2013 16:00:01 -0500 Subject: for Stroke to Path, change minimum stroke width from 0.01 to 0.032 (Bug 820425, comment 5) Fixed bugs: - https://launchpad.net/bugs/820425 (bzr r12072) --- src/splivarot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 68664cb3c..7cbd92eeb 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -1002,8 +1002,8 @@ sp_selected_path_outline(SPDesktop *desktop) break; } - if (o_width < 0.01) - o_width = 0.01; + if (o_width < 0.032) + o_width = 0.032; o_miter = i_style->stroke_miterlimit.value * o_width; } -- cgit v1.2.3 From 9683c4e36d15967f3dfab5d40257a303f863a392 Mon Sep 17 00:00:00 2001 From: Alvin Penner Date: Fri, 22 Feb 2013 08:40:57 -0500 Subject: Path->Division. patch for case where cutter is a line (Bug 177956) Fixed bugs: - https://launchpad.net/bugs/177956 (bzr r12138) --- src/splivarot.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 7cbd92eeb..8ce9a012b 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -307,7 +307,10 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb originaux[1]->ConvertWithBackData(1.0); - originaux[1]->Fill(theShape, 1,false,false,false); //do not closeIfNeeded + if ((originaux[1]->pts.size() == 2) && originaux[1]->pts[0].isMoveTo && !originaux[1]->pts[1].isMoveTo) + originaux[1]->Fill(theShape, 1,false,true,false); // see LP Bug 177956 + else + originaux[1]->Fill(theShape, 1,false,false,false); //do not closeIfNeeded theShapeB->ConvertToShape(theShape, fill_justDont); // fill_justDont doesn't computes winding numbers -- cgit v1.2.3 From 13b79813920ca1d94f4ccfbb0dc3c602307a6491 Mon Sep 17 00:00:00 2001 From: Alvin Penner Date: Wed, 6 Mar 2013 15:09:54 -0500 Subject: boolops union. fix for disappearing objects (Bug 168907) Fixed bugs: - https://launchpad.net/bugs/168907 (bzr r12176) --- src/splivarot.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 8ce9a012b..061d32554 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -261,10 +261,13 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb theShapeB->ConvertToShape(theShape, origWind[curOrig]); - // les elements arrivent en ordre inverse dans la liste - theShape->Booleen(theShapeB, theShapeA, bop); - - { + if (theShapeA->numberOfEdges() == 0) { + Shape *swap = theShapeB; + theShapeB = theShapeA; + theShapeA = swap; + } else if (theShapeB->numberOfEdges() > 0) { + // les elements arrivent en ordre inverse dans la liste + theShape->Booleen(theShapeB, theShapeA, bop); Shape *swap = theShape; theShape = theShapeA; theShapeA = swap; -- cgit v1.2.3 From 27732efd761b727c736829b13492ef33fc41199d Mon Sep 17 00:00:00 2001 From: Kris De Gussem Date: Sat, 16 Mar 2013 18:17:57 +0100 Subject: fix/suppress build warnings (bzr r12215) --- src/splivarot.cpp | 36 +++--------------------------------- 1 file changed, 3 insertions(+), 33 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 061d32554..2015ffd27 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -1369,48 +1369,18 @@ void sp_selected_path_create_offset_object(SPDesktop *desktop, int expand, bool Inkscape::XML::Node *parent = item->getRepr()->parent(); float o_width = 0; - JoinType o_join = join_straight; - ButtType o_butt = butt_straight; - float o_miter = 0; { SPStyle *i_style = item->style; - int jointype = i_style->stroke_linejoin.value; - int captype = i_style->stroke_linecap.value; o_width = i_style->stroke_width.computed; - if (jointype == SP_STROKE_LINEJOIN_MITER) - { - o_join = join_pointy; - } - else if (jointype == SP_STROKE_LINEJOIN_ROUND) - { - o_join = join_round; - } - else - { - o_join = join_straight; - } - if (captype == SP_STROKE_LINECAP_SQUARE) - { - o_butt = butt_square; - } - else if (captype == SP_STROKE_LINECAP_ROUND) - { - o_butt = butt_round; - } - else - { - o_butt = butt_straight; - } - { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); o_width = prefs->getDouble("/options/defaultoffsetwidth/value", 1.0, "px"); } - if (o_width < 0.01) + if (o_width < 0.01){ o_width = 0.01; - o_miter = i_style->stroke_miterlimit.value * o_width; + } } Path *orig = Path_for_item(item, true, false); @@ -1508,7 +1478,7 @@ void sp_selected_path_create_offset_object(SPDesktop *desktop, int expand, bool // move to the saved position repr->setPosition(pos > 0 ? pos : 0); - SPItem *nitem = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr); + SPItem *nitem = reinterpret_cast(sp_desktop_document(desktop)->getObjectByRepr(repr)); if ( !updating ) { // delete original, apply the transform to the offset -- cgit v1.2.3 From 4b0f58e1d91b89bd44a187c150f068f01fa7bd87 Mon Sep 17 00:00:00 2001 From: Nicolas Dufour Date: Wed, 1 May 2013 07:55:48 +0200 Subject: Fix for Bug #629081 (Spiro and path operations fails) by Matthew Petroff. Fixed bugs: - https://launchpad.net/bugs/629081 (bzr r12314) --- src/splivarot.cpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 2015ffd27..e45712134 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -207,6 +207,11 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb curOrig = 0; for (GSList *l = il; l != NULL; l = l->next) { + // apply live path effects prior to performing boolean operation + if (SP_IS_LPE_ITEM(l->data)) { + sp_lpe_item_remove_all_path_effects(SP_LPE_ITEM(l->data), true); + } + SPCSSAttr *css = sp_repr_css_attr(reinterpret_cast(il->data)->getRepr(), "style"); gchar const *val = sp_repr_css_property(css, "fill-rule", NULL); if (val && strcmp(val, "nonzero") == 0) { -- cgit v1.2.3 From c3a160589a9cb41c70a56e5e7b66a65857a0d10e Mon Sep 17 00:00:00 2001 From: Eric Greveson Date: Mon, 1 Jul 2013 21:04:32 +0100 Subject: Factored layer model out into new Inkscape::LayerModel class. This allows Inkscape::Selection to use a LayerModel that is not associated with a UI. Changed the interface of verbs (SPAction) to use a new ActionContext rather than UI::View::View, again so that verbs may be used in a console mode. Modified boolean operation verbs to work in console-only mode. Fixed up DESKTOP_IS_ACTIVE macro to work in the case of no desktops. Modified main.cpp to process selections and verbs in no-GUI mode. Other changes are all consequences of the SPDesktop, Selection and LayerModel interface changes. (bzr r12387.1.1) --- src/splivarot.cpp | 81 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 36 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index e45712134..0ec9da2a7 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -34,6 +34,7 @@ #include "style.h" #include "document.h" #include "document-undo.h" +#include "layer-model.h" #include "message-stack.h" #include "selection.h" #include "desktop-handles.h" @@ -58,79 +59,89 @@ using Inkscape::DocumentUndo; bool Ancetre(Inkscape::XML::Node *a, Inkscape::XML::Node *who); -void sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb=SP_VERB_NONE, const Glib::ustring description=""); +void sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool_op bop, const unsigned int verb=SP_VERB_NONE, const Glib::ustring description=""); void sp_selected_path_do_offset(SPDesktop *desktop, bool expand, double prefOffset); void sp_selected_path_create_offset_object(SPDesktop *desktop, int expand, bool updating); void -sp_selected_path_union(SPDesktop *desktop) +sp_selected_path_union(Inkscape::Selection *selection, SPDesktop *desktop) { - sp_selected_path_boolop(desktop, bool_op_union, SP_VERB_SELECTION_UNION, _("Union")); + sp_selected_path_boolop(selection, desktop, bool_op_union, SP_VERB_SELECTION_UNION, _("Union")); } void -sp_selected_path_union_skip_undo(SPDesktop *desktop) +sp_selected_path_union_skip_undo(Inkscape::Selection *selection, SPDesktop *desktop) { - sp_selected_path_boolop(desktop, bool_op_union, SP_VERB_NONE, _("Union")); + sp_selected_path_boolop(selection, desktop, bool_op_union, SP_VERB_NONE, _("Union")); } void -sp_selected_path_intersect(SPDesktop *desktop) +sp_selected_path_intersect(Inkscape::Selection *selection, SPDesktop *desktop) { - sp_selected_path_boolop(desktop, bool_op_inters, SP_VERB_SELECTION_INTERSECT, _("Intersection")); + sp_selected_path_boolop(selection, desktop, bool_op_inters, SP_VERB_SELECTION_INTERSECT, _("Intersection")); } void -sp_selected_path_diff(SPDesktop *desktop) +sp_selected_path_diff(Inkscape::Selection *selection, SPDesktop *desktop) { - sp_selected_path_boolop(desktop, bool_op_diff, SP_VERB_SELECTION_DIFF, _("Difference")); + sp_selected_path_boolop(selection, desktop, bool_op_diff, SP_VERB_SELECTION_DIFF, _("Difference")); } void -sp_selected_path_diff_skip_undo(SPDesktop *desktop) +sp_selected_path_diff_skip_undo(Inkscape::Selection *selection, SPDesktop *desktop) { - sp_selected_path_boolop(desktop, bool_op_diff, SP_VERB_NONE, _("Difference")); + sp_selected_path_boolop(selection, desktop, bool_op_diff, SP_VERB_NONE, _("Difference")); } void -sp_selected_path_symdiff(SPDesktop *desktop) +sp_selected_path_symdiff(Inkscape::Selection *selection, SPDesktop *desktop) { - sp_selected_path_boolop(desktop, bool_op_symdiff, SP_VERB_SELECTION_SYMDIFF, _("Exclusion")); + sp_selected_path_boolop(selection, desktop, bool_op_symdiff, SP_VERB_SELECTION_SYMDIFF, _("Exclusion")); } void -sp_selected_path_cut(SPDesktop *desktop) +sp_selected_path_cut(Inkscape::Selection *selection, SPDesktop *desktop) { - sp_selected_path_boolop(desktop, bool_op_cut, SP_VERB_SELECTION_CUT, _("Division")); + sp_selected_path_boolop(selection, desktop, bool_op_cut, SP_VERB_SELECTION_CUT, _("Division")); } void -sp_selected_path_slice(SPDesktop *desktop) +sp_selected_path_slice(Inkscape::Selection *selection, SPDesktop *desktop) { - sp_selected_path_boolop(desktop, bool_op_slice, SP_VERB_SELECTION_SLICE, _("Cut path")); + sp_selected_path_boolop(selection, desktop, bool_op_slice, SP_VERB_SELECTION_SLICE, _("Cut path")); } +// helper for printing error messages, regardless of whether we have a GUI or not +// If desktop == NULL, errors will be shown on stderr +static void +boolop_display_error_message(SPDesktop *desktop, Glib::ustring const &msg) +{ + if (desktop) { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, msg); + } else { + g_printerr("%s\n", msg.c_str()); + } +} // boolean operations // take the source paths from the file, do the operation, delete the originals and add the results void -sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb, const Glib::ustring description) +sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool_op bop, const unsigned int verb, const Glib::ustring description) { - Inkscape::Selection *selection = sp_desktop_selection(desktop); - + SPDocument *doc = selection->layerModel()->getDocument(); GSList *il = (GSList *) selection->itemList(); // allow union on a single object for the purpose of removing self overlapse (svn log, revision 13334) if ( (g_slist_length(il) < 2) && (bop != bool_op_union)) { - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Select at least 2 paths to perform a boolean operation.")); + boolop_display_error_message(desktop, _("Select at least 2 paths to perform a boolean operation.")); return; } else if ( g_slist_length(il) < 1 ) { - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Select at least 1 path to perform a boolean union.")); + boolop_display_error_message(desktop, _("Select at least 1 path to perform a boolean union.")); return; } if (g_slist_length(il) > 2) { if (bop == bool_op_diff || bop == bool_op_cut || bop == bool_op_slice ) { - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Select exactly 2 paths to perform difference, division, or path cut.")); + boolop_display_error_message(desktop, _("Select exactly 2 paths to perform difference, division, or path cut.")); return; } } @@ -146,7 +157,7 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb Inkscape::XML::Node *b = reinterpret_cast(il->next->data)->getRepr(); if (a == NULL || b == NULL) { - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Unable to determine the z-order of the objects selected for difference, XOR, division, or path cut.")); + boolop_display_error_message(desktop, _("Unable to determine the z-order of the objects selected for difference, XOR, division, or path cut.")); return; } @@ -161,7 +172,7 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb // find their lowest common ancestor Inkscape::XML::Node *dad = LCA(a, b); if (dad == NULL) { - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Unable to determine the z-order of the objects selected for difference, XOR, division, or path cut.")); + boolop_display_error_message(desktop, _("Unable to determine the z-order of the objects selected for difference, XOR, division, or path cut.")); return; } @@ -191,7 +202,7 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb SPItem *item = SP_ITEM(l->data); if (!SP_IS_SHAPE(item) && !SP_IS_TEXT(item) && !SP_IS_FLOWTEXT(item)) { - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("One of the objects is not a path, cannot perform boolean operation.")); + boolop_display_error_message(desktop, _("One of the objects is not a path, cannot perform boolean operation.")); g_slist_free(il); return; } @@ -432,8 +443,7 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb { SP_OBJECT(l->data)->deleteObject(); } - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_NONE, - description); + DocumentUndo::done(doc, SP_VERB_NONE, description); selection->clear(); delete res; @@ -455,8 +465,7 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb sorted = g_slist_sort(sorted, (GCompareFunc) sp_repr_compare_position); - source = sp_desktop_document(desktop)-> - getObjectByRepr((Inkscape::XML::Node *)sorted->data); + source = doc->getObjectByRepr((Inkscape::XML::Node *)sorted->data); g_slist_free(sorted); } @@ -496,7 +505,7 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb g_slist_free(il); // premultiply by the inverse of parent's repr - SPItem *parent_item = SP_ITEM(sp_desktop_document(desktop)->getObjectByRepr(parent)); + SPItem *parent_item = SP_ITEM(doc->getObjectByRepr(parent)); Geom::Affine local (parent_item->i2doc_affine()); gchar *transform = sp_svg_transform_write(local.inverse()); @@ -526,7 +535,7 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb for (int i=0;isvg_dump_path(); - Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); repr->setAttribute("style", style); if (mask) @@ -572,7 +581,7 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb } else { gchar *d = res->svg_dump_path(); - Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); repr->setAttribute("style", style); @@ -590,10 +599,10 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb repr->setAttribute("id", id); parent->appendChild(repr); if (title) { - sp_desktop_document(desktop)->getObjectByRepr(repr)->setTitle(title); + doc->getObjectByRepr(repr)->setTitle(title); } if (desc) { - sp_desktop_document(desktop)->getObjectByRepr(repr)->setDesc(desc); + doc->getObjectByRepr(repr)->setDesc(desc); } repr->setPosition(pos > 0 ? pos : 0); @@ -606,7 +615,7 @@ sp_selected_path_boolop(SPDesktop *desktop, bool_op bop, const unsigned int verb if (desc) g_free(desc); if (verb != SP_VERB_NONE) { - DocumentUndo::done(sp_desktop_document(desktop), verb, description); + DocumentUndo::done(doc, verb, description); } delete res; -- cgit v1.2.3 From 104efe4e3ecadc975ab76748c66f041abf8ee7b1 Mon Sep 17 00:00:00 2001 From: Eric Greveson Date: Thu, 4 Jul 2013 15:01:44 +0100 Subject: Code readability improvements and licence changes for action-context.* based on merge request code review and feedback (bzr r12387.1.7) --- src/splivarot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 0ec9da2a7..356cf0161 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -126,7 +126,7 @@ boolop_display_error_message(SPDesktop *desktop, Glib::ustring const &msg) void sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool_op bop, const unsigned int verb, const Glib::ustring description) { - SPDocument *doc = selection->layerModel()->getDocument(); + SPDocument *doc = selection->layers()->getDocument(); GSList *il = (GSList *) selection->itemList(); // allow union on a single object for the purpose of removing self overlapse (svn log, revision 13334) -- cgit v1.2.3 From 2f7ea8f8ae067cbb3406169d22a84426cabd43f6 Mon Sep 17 00:00:00 2001 From: Eric Greveson Date: Thu, 8 Aug 2013 17:01:03 +0100 Subject: Fix to do the "right thing" for difference/intersection boolean ops when one or more input paths are truncated to zero-size by the quantization step (coordinate rounding). Previously this had only been fixed for union ops (which happened to work for exclusion (XOR) ops as well). (bzr r12472.1.1) --- src/splivarot.cpp | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 356cf0161..6423129b9 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -277,11 +277,37 @@ sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool theShapeB->ConvertToShape(theShape, origWind[curOrig]); - if (theShapeA->numberOfEdges() == 0) { - Shape *swap = theShapeB; - theShapeB = theShapeA; - theShapeA = swap; - } else if (theShapeB->numberOfEdges() > 0) { + // Due to quantization of the input shape coordinates, we may end up with A or B being empty. + // If this is a union or symdiff operation, we just use the non-empty shape as the result: + // A=0 => (0 or B) == B + // B=0 => (A or 0) == A + // A=0 => (0 xor B) == B + // B=0 => (A xor 0) == A + // If this is an intersection operation, we just use the empty shape as the result: + // A=0 => (0 and B) == 0 == A + // B=0 => (A and 0) == 0 == B + // If this a difference operation, and the upper shape (A) is empty, we keep B. + // If the lower shape (B) is empty, we still keep B, as it's empty: + // A=0 => (B - 0) == B + // B=0 => (0 - A) == 0 == B + // + // In any case, the output from this operation is stored in shape A, so we may apply + // the above rules simply by judicious use of swapping A and B where necessary. + bool zeroA = theShapeA->numberOfEdges() == 0; + bool zeroB = theShapeB->numberOfEdges() == 0; + if (zeroA || zeroB) { + // We might need to do a swap. Apply the above rules depending on operation type. + bool resultIsB = ((bop == bool_op_union || bop == bool_op_symdiff) && zeroA) + || ((bop == bool_op_inters) && zeroB) + || (bop == bool_op_diff); + if (resultIsB) { + // Swap A and B to use B as the result + Shape *swap = theShapeB; + theShapeB = theShapeA; + theShapeA = swap; + } + } else { + // Just do the Boolean operation as usual // les elements arrivent en ordre inverse dans la liste theShape->Booleen(theShapeB, theShapeA, bop); Shape *swap = theShape; -- cgit v1.2.3 From 2ec94efdaa8e757acb2afb4c2c683b51bf08cf82 Mon Sep 17 00:00:00 2001 From: "Johan B. C. Engelen" Date: Mon, 30 Sep 2013 22:46:18 +0200 Subject: memleaks! (bzr r12639) --- src/splivarot.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 8a57fa98a..319928d99 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -2183,6 +2183,7 @@ sp_selected_path_simplify_items(SPDesktop *desktop, gchar *message = g_strdup_printf(_("%s %d of %d paths simplified..."), simplificationType, pathsSimplified, totalPathCount); desktop->messageStack()->flash(Inkscape::IMMEDIATE_MESSAGE, message); + g_free(message); } didSomething |= sp_selected_path_simplify_item(desktop, selection, item, @@ -2192,7 +2193,7 @@ sp_selected_path_simplify_items(SPDesktop *desktop, desktop->clearWaitingCursor(); if (pathsSimplified > 20) { - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, g_strdup_printf(_("%d paths simplified."), pathsSimplified)); + desktop->messageStack()->flashF(Inkscape::NORMAL_MESSAGE, _("%d paths simplified."), pathsSimplified); } return didSomething; -- cgit v1.2.3 From 4f857ae84089672e9c7378d327e54889f64c62fb Mon Sep 17 00:00:00 2001 From: "Johan B. C. Engelen" Date: Fri, 4 Oct 2013 23:57:28 +0200 Subject: C++ (bzr r12660) --- src/splivarot.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 319928d99..ad40178fb 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -419,7 +419,7 @@ sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool { // apply live path effects prior to performing boolean operation if (SP_IS_LPE_ITEM(l->data)) { - sp_lpe_item_remove_all_path_effects(SP_LPE_ITEM(l->data), true); + SP_LPE_ITEM(l->data)->removeAllPathEffects(true); } SPCSSAttr *css = sp_repr_css_attr(reinterpret_cast(il->data)->getRepr(), "style"); -- cgit v1.2.3 From 7476da8cb27f25300274d7cab45cd8bde1df5a98 Mon Sep 17 00:00:00 2001 From: Alex Valavanis Date: Tue, 12 Nov 2013 19:28:51 +0000 Subject: splivarot: scan-build fixes and cleanup (bzr r12798) --- src/splivarot.cpp | 36 ++++++++++++++---------------------- 1 file changed, 14 insertions(+), 22 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index ad40178fb..7bf556aa1 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -338,6 +338,8 @@ sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool return; } + g_assert(il != NULL); + if (g_slist_length(il) > 2) { if (bop == bool_op_diff || bop == bool_op_cut || bop == bool_op_slice ) { boolop_display_error_message(desktop, _("Select exactly 2 paths to perform difference, division, or path cut.")); @@ -352,8 +354,8 @@ sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool if (bop == bool_op_diff || bop == bool_op_cut || bop == bool_op_slice) { // check in the tree to find which element of the selection list is topmost (for 2-operand commands only) - Inkscape::XML::Node *a = reinterpret_cast(il->data)->getRepr(); - Inkscape::XML::Node *b = reinterpret_cast(il->next->data)->getRepr(); + Inkscape::XML::Node *a = SP_OBJECT(il->data)->getRepr(); + Inkscape::XML::Node *b = SP_OBJECT(il->next->data)->getRepr(); if (a == NULL || b == NULL) { boolop_display_error_message(desktop, _("Unable to determine the z-order of the objects selected for difference, XOR, division, or path cut.")); @@ -393,6 +395,7 @@ sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool } il = g_slist_copy(il); + g_assert(il != NULL); // first check if all the input objects have shapes // otherwise bail out @@ -1160,7 +1163,7 @@ sp_selected_path_outline(SPDesktop *desktop) items != NULL; items = items->next) { - SPItem *item = (SPItem *) items->data; + SPItem *item = SP_ITEM(items->data); if (!SP_IS_SHAPE(item) && !SP_IS_TEXT(item)) continue; @@ -1177,6 +1180,8 @@ sp_selected_path_outline(SPDesktop *desktop) continue; } + g_assert(curve != NULL); + if (curve->get_pathvector().empty()) { continue; } @@ -1595,6 +1600,8 @@ void sp_selected_path_create_offset_object(SPDesktop *desktop, int expand, bool } } + g_assert(curve != NULL); + Geom::Affine const transform(item->transform); item->doWriteTransform(item->getRepr(), Geom::identity()); @@ -1610,9 +1617,6 @@ void sp_selected_path_create_offset_object(SPDesktop *desktop, int expand, bool float o_width = 0; { - SPStyle *i_style = item->style; - - o_width = i_style->stroke_width.computed; { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); o_width = prefs->getDouble("/options/defaultoffsetwidth/value", 1.0, "px"); @@ -1774,7 +1778,7 @@ sp_selected_path_do_offset(SPDesktop *desktop, bool expand, double prefOffset) items != NULL; items = items->next) { - SPItem *item = (SPItem *) items->data; + SPItem *item = SP_ITEM(items->data); if (!SP_IS_SHAPE(item) && !SP_IS_TEXT(item)) continue; @@ -1791,6 +1795,9 @@ sp_selected_path_do_offset(SPDesktop *desktop, bool expand, double prefOffset) continue; } + // We've now checked that there is a curve for this item + g_assert(curve != NULL); + Geom::Affine const transform(item->transform); item->doWriteTransform(item->getRepr(), Geom::identity()); @@ -1805,9 +1812,6 @@ sp_selected_path_do_offset(SPDesktop *desktop, bool expand, double prefOffset) { SPStyle *i_style = item->style; int jointype = i_style->stroke_linejoin.value; - //int captype = i_style->stroke_linecap.value; - - o_width = i_style->stroke_width.computed; switch (jointype) { case SP_STROKE_LINEJOIN_MITER: @@ -1821,18 +1825,6 @@ sp_selected_path_do_offset(SPDesktop *desktop, bool expand, double prefOffset) break; } - /*switch (captype) { - case SP_STROKE_LINECAP_SQUARE: - o_butt = butt_square; - break; - case SP_STROKE_LINECAP_ROUND: - o_butt = butt_round; - break; - default: - o_butt = butt_straight; - break; - }*/ - o_width = prefOffset; if (o_width < 0.1) -- cgit v1.2.3 From bfa22b6db8210782ead109809f166726314f09ac Mon Sep 17 00:00:00 2001 From: Alex Valavanis Date: Thu, 14 Nov 2013 15:36:14 +0000 Subject: splivarot: Clean up assertions where possible (bzr r12813) --- src/splivarot.cpp | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 7bf556aa1..e5006884c 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -1585,22 +1585,18 @@ void sp_selected_path_create_offset_object(SPDesktop *desktop, int expand, bool desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Selected object is not a path, cannot inset/outset.")); return; } - if (SP_IS_SHAPE(item)) + else if (SP_IS_SHAPE(item)) { curve = SP_SHAPE(item)->getCurve(); - if (curve == NULL) { - return; - } } - if (SP_IS_TEXT(item)) + else // Item must be SP_TEXT { curve = SP_TEXT(item)->getNormalizedBpath(); - if (curve == NULL) { - return; - } } - - g_assert(curve != NULL); + + if (curve == NULL) { + return; + } Geom::Affine const transform(item->transform); @@ -1779,24 +1775,19 @@ sp_selected_path_do_offset(SPDesktop *desktop, bool expand, double prefOffset) items = items->next) { SPItem *item = SP_ITEM(items->data); + SPCurve *curve = NULL; if (!SP_IS_SHAPE(item) && !SP_IS_TEXT(item)) continue; - - SPCurve *curve = NULL; - if (SP_IS_SHAPE(item)) { + else if (SP_IS_SHAPE(item)) { curve = SP_SHAPE(item)->getCurve(); - if (curve == NULL) - continue; } - if (SP_IS_TEXT(item)) { + else { // Item must be SP_TEXT curve = SP_TEXT(item)->getNormalizedBpath(); - if (curve == NULL) - continue; } - // We've now checked that there is a curve for this item - g_assert(curve != NULL); + if (curve == NULL) + continue; Geom::Affine const transform(item->transform); -- cgit v1.2.3 From c7731033ef400fccb6a49d830ded86068fcd3432 Mon Sep 17 00:00:00 2001 From: Nicolas Dufour Date: Wed, 5 Feb 2014 12:29:11 +0100 Subject: Fix for Bug #1250685 (Unnecessary gender-specific terms in code). Fixed bugs: - https://launchpad.net/bugs/1250685 (bzr r12997) --- src/splivarot.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index e5006884c..5c7b389ff 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -371,18 +371,18 @@ sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool // objects are not in parent/child relationship; // find their lowest common ancestor - Inkscape::XML::Node *dad = LCA(a, b); - if (dad == NULL) { + Inkscape::XML::Node *parent = LCA(a, b); + if (parent == NULL) { boolop_display_error_message(desktop, _("Unable to determine the z-order of the objects selected for difference, XOR, division, or path cut.")); return; } // find the children of the LCA that lead from it to the a and b - Inkscape::XML::Node *as = AncetreFils(a, dad); - Inkscape::XML::Node *bs = AncetreFils(b, dad); + Inkscape::XML::Node *as = AncetreFils(a, parent); + Inkscape::XML::Node *bs = AncetreFils(b, parent); // find out which comes first - for (Inkscape::XML::Node *child = dad->firstChild(); child; child = child->next()) { + for (Inkscape::XML::Node *child = parent->firstChild(); child; child = child->next()) { if (child == as) { /* a first, so reverse. */ reverseOrderForOp = true; -- cgit v1.2.3 From 1d31728fb7399f48d272560a290dc990b75a197e Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Tue, 11 Mar 2014 15:52:08 +0100 Subject: Change stroke-dasharray and stroke-dashoffset handling to match other properties. Split style.h into more manageable size files. (bzr r13135) --- src/splivarot.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/splivarot.cpp') diff --git a/src/splivarot.cpp b/src/splivarot.cpp index 5c7b389ff..6aec42c5b 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -1004,7 +1004,7 @@ Geom::PathVector* item_outline(SPItem const *item, bool bbox_only) Path *res = new Path; res->SetBackData(false); - if (i_style->stroke_dash.n_dash) { + if (!i_style->stroke_dasharray.values.empty()) { // For dashed strokes, use Stroke method, because Outline can't do dashes // However Stroke adds lots of extra nodes _or_ makes the path crooked, so consider this a temporary workaround @@ -1273,7 +1273,7 @@ sp_selected_path_outline(SPDesktop *desktop) Path *res = new Path; res->SetBackData(false); - if (i_style->stroke_dash.n_dash) { + if (!i_style->stroke_dasharray.values.empty()) { // For dashed strokes, use Stroke method, because Outline can't do dashes // However Stroke adds lots of extra nodes _or_ makes the path crooked, so consider this a temporary workaround -- cgit v1.2.3