Code

Switch selection bounds and center to use NR::Maybe, addressing most of the
[inkscape.git] / src / splivarot.cpp
1 #define __SP_LIVAROT_C__
2 /*
3  *  splivarot.cpp
4  *  Inkscape
5  *
6  *  Created by fred on Fri Dec 05 2003.
7  *  tweaked endlessly by bulia byak <buliabyak@users.sf.net>
8  *  public domain
9  *
10  */
12 /*
13  * contains lots of stitched pieces of path-chemistry.c
14  */
16 #ifdef HAVE_CONFIG_H
17 # include <config.h>
18 #endif
20 #include <vector>
21 #include <glib/gmem.h>
22 #include "xml/repr.h"
23 #include "svg/svg.h"
24 #include "sp-path.h"
25 #include "sp-shape.h"
26 #include "marker.h"
27 #include "enums.h"
28 #include "sp-text.h"
29 #include "sp-item-group.h"
30 #include "style.h"
31 #include "inkscape.h"
32 #include "document.h"
33 #include "message-stack.h"
34 #include "selection.h"
35 #include "desktop-handles.h"
36 #include "desktop.h"
37 #include "display/canvas-bpath.h"
38 #include "display/curve.h"
39 #include <glibmm/i18n.h>
40 #include "prefs-utils.h"
42 #include "libnr/n-art-bpath.h"
43 #include "libnr/nr-path.h"
44 #include "xml/repr.h"
45 #include "xml/repr-sorting.h"
47 #include <libnr/nr-matrix-fns.h>
48 #include <libnr/nr-matrix-ops.h>
49 #include <libnr/nr-matrix-translate-ops.h>
50 #include <libnr/nr-scale-matrix-ops.h>
52 #include "livarot/Path.h"
53 #include "livarot/Shape.h"
55 #include "splivarot.h"
57 bool   Ancetre(Inkscape::XML::Node *a, Inkscape::XML::Node *who);
59 void sp_selected_path_boolop(bool_op bop, const unsigned int verb=SP_VERB_NONE, const Glib::ustring description="");
60 void sp_selected_path_do_offset(bool expand, double prefOffset);
61 void sp_selected_path_create_offset_object(int expand, bool updating);
63 void
64 sp_selected_path_union()
65 {
66     sp_selected_path_boolop(bool_op_union, SP_VERB_SELECTION_UNION, _("Union"));
67 }
69 void
70 sp_selected_path_intersect()
71 {
72     sp_selected_path_boolop(bool_op_inters, SP_VERB_SELECTION_INTERSECT, _("Intersection"));
73 }
75 void
76 sp_selected_path_diff()
77 {
78     sp_selected_path_boolop(bool_op_diff, SP_VERB_SELECTION_DIFF, _("Difference"));
79 }
81 void
82 sp_selected_path_symdiff()
83 {
84     sp_selected_path_boolop(bool_op_symdiff, SP_VERB_SELECTION_SYMDIFF, _("Exclusion"));
85 }
86 void
87 sp_selected_path_cut()
88 {
89     sp_selected_path_boolop(bool_op_cut, SP_VERB_SELECTION_CUT, _("Division"));
90 }
91 void
92 sp_selected_path_slice()
93 {
94     sp_selected_path_boolop(bool_op_slice, SP_VERB_SELECTION_SLICE,  _("Cut path"));
95 }
98 // boolean operations
99 // take the source paths from the file, do the operation, delete the originals and add the results
100 void
101 sp_selected_path_boolop(bool_op bop, const unsigned int verb, const Glib::ustring description)
103     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
105     Inkscape::Selection *selection = sp_desktop_selection(desktop);
106     
107     GSList *il = (GSList *) selection->itemList();
108     
109     // allow union on a single object for the purpose of removing self overlapse (svn log, revision 13334)
110     if ( (g_slist_length(il) < 2) && (bop != bool_op_union)) {
111         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Select <b>at least 2 paths</b> to perform a boolean operation."));
112         return;
113     }
114     else if ( g_slist_length(il) < 1 ) {
115         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Select <b>at least 1 path</b> to perform a boolean union."));
116         return;
117     }
119     if (g_slist_length(il) > 2) {
120         if (bop == bool_op_diff || bop == bool_op_symdiff || bop == bool_op_cut || bop == bool_op_slice ) {
121             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Select <b>exactly 2 paths</b> to perform difference, XOR, division, or path cut."));
122             return;
123         }
124     }
126     // reverseOrderForOp marks whether the order of the list is the top->down order
127     // it's only used when there are 2 objects, and for operations who need to know the
128     // topmost object (differences, cuts)
129     bool reverseOrderForOp = false;
131     // mettre les elements de la liste dans l'ordre pour ces operations
132     if (bop == bool_op_diff || bop == bool_op_symdiff || bop == bool_op_cut || bop == bool_op_slice) {
133         // check in the tree to find which element of the selection list is topmost (for 2-operand commands only)
134         Inkscape::XML::Node *a = SP_OBJECT_REPR(il->data);
135         Inkscape::XML::Node *b = SP_OBJECT_REPR(il->next->data);
137         if (a == NULL || b == NULL) {
138             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Unable to determine the <b>z-order</b> of the objects selected for difference, XOR, division, or path cut."));
139             return;
140         }
142         if (Ancetre(a, b)) {
143             // a is the parent of b, already in the proper order
144         } else if (Ancetre(b, a)) {
145             // reverse order
146             reverseOrderForOp = true;
147         } else {
149             // objects are not in parent/child relationship;
150             // find their lowest common ancestor
151             Inkscape::XML::Node *dad = LCA(a, b);
152             if (dad == NULL) {
153                 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Unable to determine the <b>z-order</b> of the objects selected for difference, XOR, division, or path cut."));
154                 return;
155             }
157             // find the children of the LCA that lead from it to the a and b
158             Inkscape::XML::Node *as = AncetreFils(a, dad);
159             Inkscape::XML::Node *bs = AncetreFils(b, dad);
161             // find out which comes first
162             for (Inkscape::XML::Node *child = dad->firstChild(); child; child = child->next()) {
163                 if (child == as) {
164                     /* a first, so reverse. */
165                     reverseOrderForOp = true;
166                     break;
167                 }
168                 if (child == bs)
169                     break;
170             }
171         }
172     }
174     il = g_slist_copy(il);
176     // first check if all the input objects have shapes
177     // otherwise bail out
178     for (GSList *l = il; l != NULL; l = l->next)
179     {
180         SPItem *item = SP_ITEM(l->data);
181         if (!SP_IS_SHAPE(item) && !SP_IS_TEXT(item))
182         {
183             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("One of the objects is <b>not a path</b>, cannot perform boolean operation."));
184             g_slist_free(il);
185             return;
186         }
187     }
189     // extract the livarot Paths from the source objects
190     // also get the winding rule specified in the style
191     int nbOriginaux = g_slist_length(il);
192     std::vector<Path *> originaux(nbOriginaux);
193     std::vector<FillRule> origWind(nbOriginaux);
194     int curOrig;
195     {
196         curOrig = 0;
197         for (GSList *l = il; l != NULL; l = l->next)
198         {
199             SPCSSAttr *css = sp_repr_css_attr(SP_OBJECT_REPR(il->data), "style");
200             gchar const *val = sp_repr_css_property(css, "fill-rule", NULL);
201             if (val && strcmp(val, "nonzero") == 0) {
202                 origWind[curOrig]= fill_nonZero;
203             } else if (val && strcmp(val, "evenodd") == 0) {
204                 origWind[curOrig]= fill_oddEven;
205             } else {
206                 origWind[curOrig]= fill_nonZero;
207             }
209             originaux[curOrig] = Path_for_item((SPItem *) l->data, true, true);
210             if (originaux[curOrig] == NULL || originaux[curOrig]->descr_cmd.size() <= 1)
211             {
212                 for (int i = curOrig; i >= 0; i--) delete originaux[i];
213                 g_slist_free(il);
214                 return;
215             }
216             curOrig++;
217         }
218     }
219     // reverse if needed
220     // note that the selection list keeps its order
221     if ( reverseOrderForOp ) {
222         Path* swap=originaux[0];originaux[0]=originaux[1];originaux[1]=swap;
223         FillRule swai=origWind[0]; origWind[0]=origWind[1]; origWind[1]=swai;
224     }
226     // and work
227     // some temporary instances, first
228     Shape *theShapeA = new Shape;
229     Shape *theShapeB = new Shape;
230     Shape *theShape = new Shape;
231     Path *res = new Path;
232     res->SetBackData(false);
233     Path::cut_position  *toCut=NULL;
234     int                  nbToCut=0;
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         originaux[0]->ConvertWithBackData(0.1);
241         originaux[0]->Fill(theShape, 0);
243         theShapeA->ConvertToShape(theShape, origWind[0]);
245         curOrig = 1;
246         for (GSList *l = il->next; l != NULL; l = l->next) {
247             originaux[curOrig]->ConvertWithBackData(0.1);
249             originaux[curOrig]->Fill(theShape, curOrig);
251             theShapeB->ConvertToShape(theShape, origWind[curOrig]);
253             // les elements arrivent en ordre inverse dans la liste
254             theShape->Booleen(theShapeB, theShapeA, bop);
256             {
257                 Shape *swap = theShape;
258                 theShape = theShapeA;
259                 theShapeA = swap;
260             }
261             curOrig++;
262         }
264         {
265             Shape *swap = theShape;
266             theShape = theShapeA;
267             theShapeA = swap;
268         }
270     } else if ( bop == bool_op_cut ) {
271         // cuts= sort of a bastard boolean operation, thus not the axact same modus operandi
272         // technically, the cut path is not necessarily a polygon (thus has no winding rule)
273         // it is just uncrossed, and cleaned from duplicate edges and points
274         // then it's fed to Booleen() which will uncross it against the other path
275         // then comes the trick: each edge of the cut path is duplicated (one in each direction),
276         // thus making a polygon. the weight of the edges of the cut are all 0, but
277         // the Booleen need to invert the ones inside the source polygon (for the subsequent
278         // ConvertToForme)
280         // the cut path needs to have the highest pathID in the back data
281         // that's how the Booleen() function knows it's an edge of the cut
283         // FIXME: this gives poor results, the final paths are full of extraneous nodes. Decreasing
284         // ConvertWithBackData parameter below simply increases the number of nodes, so for now I
285         // left it at 1.0. Investigate replacing this by a combination of difference and
286         // intersection of the same two paths. -- bb
287         {
288             Path* swap=originaux[0];originaux[0]=originaux[1];originaux[1]=swap;
289             int   swai=origWind[0];origWind[0]=origWind[1];origWind[1]=(fill_typ)swai;
290         }
291         originaux[0]->ConvertWithBackData(1.0);
293         originaux[0]->Fill(theShape, 0);
295         theShapeA->ConvertToShape(theShape, origWind[0]);
297         originaux[1]->ConvertWithBackData(1.0);
299         originaux[1]->Fill(theShape, 1,false,false,false); //do not closeIfNeeded
301         theShapeB->ConvertToShape(theShape, fill_justDont); // fill_justDont doesn't computes winding numbers
303         // les elements arrivent en ordre inverse dans la liste
304         theShape->Booleen(theShapeB, theShapeA, bool_op_cut, 1);
306     } else if ( bop == bool_op_slice ) {
307         // slice is not really a boolean operation
308         // you just put the 2 shapes in a single polygon, uncross it
309         // the points where the degree is > 2 are intersections
310         // just check it's an intersection on the path you want to cut, and keep it
311         // the intersections you have found are then fed to ConvertPositionsToMoveTo() which will
312         // make new subpath at each one of these positions
313         // inversion pour l'op\8eration
314         {
315             Path* swap=originaux[0];originaux[0]=originaux[1];originaux[1]=swap;
316             int   swai=origWind[0];origWind[0]=origWind[1];origWind[1]=(fill_typ)swai;
317         }
318         originaux[0]->ConvertWithBackData(1.0);
320         originaux[0]->Fill(theShapeA, 0,false,false,false); // don't closeIfNeeded
322         originaux[1]->ConvertWithBackData(1.0);
324         originaux[1]->Fill(theShapeA, 1,true,false,false);// don't closeIfNeeded and just dump in the shape, don't reset it
326         theShape->ConvertToShape(theShapeA, fill_justDont);
328         if ( theShape->hasBackData() ) {
329             // should always be the case, but ya never know
330             {
331                 for (int i = 0; i < theShape->numberOfPoints(); i++) {
332                     if ( theShape->getPoint(i).totalDegree() > 2 ) {
333                         // possibly an intersection
334                         // we need to check that at least one edge from the source path is incident to it
335                         // before we declare it's an intersection
336                         int cb = theShape->getPoint(i).incidentEdge[FIRST];
337                         int   nbOrig=0;
338                         int   nbOther=0;
339                         int   piece=-1;
340                         float t=0.0;
341                         while ( cb >= 0 && cb < theShape->numberOfEdges() ) {
342                             if ( theShape->ebData[cb].pathID == 0 ) {
343                                 // the source has an edge incident to the point, get its position on the path
344                                 piece=theShape->ebData[cb].pieceID;
345                                 if ( theShape->getEdge(cb).st == i ) {
346                                     t=theShape->ebData[cb].tSt;
347                                 } else {
348                                     t=theShape->ebData[cb].tEn;
349                                 }
350                                 nbOrig++;
351                             }
352                             if ( theShape->ebData[cb].pathID == 1 ) nbOther++; // the cut is incident to this point
353                             cb=theShape->NextAt(i, cb);
354                         }
355                         if ( nbOrig > 0 && nbOther > 0 ) {
356                             // point incident to both path and cut: an intersection
357                             // note that you only keep one position on the source; you could have degenerate
358                             // cases where the source crosses itself at this point, and you wouyld miss an intersection
359                             toCut=(Path::cut_position*)realloc(toCut, (nbToCut+1)*sizeof(Path::cut_position));
360                             toCut[nbToCut].piece=piece;
361                             toCut[nbToCut].t=t;
362                             nbToCut++;
363                         }
364                     }
365                 }
366             }
367             {
368                 // i think it's useless now
369                 int i = theShape->numberOfEdges() - 1;
370                 for (;i>=0;i--) {
371                     if ( theShape->ebData[i].pathID == 1 ) {
372                         theShape->SubEdge(i);
373                     }
374                 }
375             }
377         }
378     }
380     int*    nesting=NULL;
381     int*    conts=NULL;
382     int     nbNest=0;
383     // pour compenser le swap juste avant
384     if ( bop == bool_op_slice ) {
385 //    theShape->ConvertToForme(res, nbOriginaux, originaux, true);
386 //    res->ConvertForcedToMoveTo();
387         res->Copy(originaux[0]);
388         res->ConvertPositionsToMoveTo(nbToCut, toCut); // cut where you found intersections
389         free(toCut);
390     } else if ( bop == bool_op_cut ) {
391         // il faut appeler pour desallouer PointData (pas vital, mais bon)
392         // the Booleen() function did not deallocated the point_data array in theShape, because this
393         // function needs it.
394         // this function uses the point_data to get the winding number of each path (ie: is a hole or not)
395         // for later reconstruction in objects, you also need to extract which path is parent of holes (nesting info)
396         theShape->ConvertToFormeNested(res, nbOriginaux, &originaux[0], 1, nbNest, nesting, conts);
397     } else {
398         theShape->ConvertToForme(res, nbOriginaux, &originaux[0]);
399     }
401     delete theShape;
402     delete theShapeA;
403     delete theShapeB;
404     for (int i = 0; i < nbOriginaux; i++)  delete originaux[i];
406     if (res->descr_cmd.size() <= 1)
407     {
408         // only one command, presumably a moveto: it isn't a path
409         for (GSList *l = il; l != NULL; l = l->next)
410         {
411             SP_OBJECT(l->data)->deleteObject();
412         }
413         sp_document_done(sp_desktop_document(desktop), SP_VERB_NONE, 
414                          description);
415         selection->clear();
417         delete res;
418         g_slist_free(il);
419         return;
420     }
422     // get the source path object
423     SPObject *source;
424     if ( bop == bool_op_diff || bop == bool_op_symdiff || bop == bool_op_cut || bop == bool_op_slice ) {
425         if (reverseOrderForOp) {
426              source = SP_OBJECT(il->data);
427         } else {
428              source = SP_OBJECT(il->next->data);
429         }
430     } else {
431         // find out the bottom object
432         GSList *sorted = g_slist_copy((GSList *) selection->reprList());
434         sorted = g_slist_sort(sorted, (GCompareFunc) sp_repr_compare_position);
436         source = sp_desktop_document(desktop)->
437             getObjectByRepr((Inkscape::XML::Node *)sorted->data);
439         g_slist_free(sorted);
440     }
442     // adjust style properties that depend on a possible transform in the source object in order
443     // to get a correct style attribute for the new path
444     SPItem* item_source = SP_ITEM(source);
445     NR::Matrix i2d = sp_item_i2d_affine(item_source);
446     sp_item_adjust_stroke(item_source, i2d.expansion());
447     sp_item_adjust_pattern(item_source, i2d);
448     sp_item_adjust_gradient(item_source, i2d);
450     Inkscape::XML::Node *repr_source = SP_OBJECT_REPR(source);
452     // remember important aspects of the source path, to be restored
453     gint pos = repr_source->position();
454     Inkscape::XML::Node *parent = sp_repr_parent(repr_source);
455     gchar const *id = repr_source->attribute("id");
456     gchar const *style = repr_source->attribute("style");
457     gchar const *mask = repr_source->attribute("mask");
458     gchar const *clip_path = repr_source->attribute("clip-path");
460     // remove source paths
461     selection->clear();
462     for (GSList *l = il; l != NULL; l = l->next) {
463         // if this is the bottommost object,
464         if (!strcmp(SP_OBJECT_REPR(l->data)->attribute("id"), id)) {
465             // delete it so that its clones don't get alerted; this object will be restored shortly, with the same id
466             SP_OBJECT(l->data)->deleteObject(false);
467         } else {
468             // delete the object for real, so that its clones can take appropriate action
469             SP_OBJECT(l->data)->deleteObject();
470         }
471     }
472     g_slist_free(il);
474     // premultiply by the inverse of parent's repr
475     SPItem *parent_item = SP_ITEM(sp_desktop_document(desktop)->getObjectByRepr(parent));
476     NR::Matrix local = sp_item_i2doc_affine(parent_item);
477     gchar *transform = sp_svg_transform_write(local);
479     // now that we have the result, add it on the canvas
480     if ( bop == bool_op_cut || bop == bool_op_slice ) {
481         int    nbRP=0;
482         Path** resPath;
483         if ( bop == bool_op_slice ) {
484             // there are moveto's at each intersection, but it's still one unique path
485             // so break it down and add each subpath independently
486             // we could call break_apart to do this, but while we have the description...
487             resPath=res->SubPaths(nbRP, false);
488         } else {
489             // cut operation is a bit wicked: you need to keep holes
490             // that's why you needed the nesting
491             // ConvertToFormeNested() dumped all the subpath in a single Path "res", so we need
492             // to get the path for each part of the polygon. that's why you need the nesting info:
493             // to know in wich subpath to add a subpath
494             resPath=res->SubPathsWithNesting(nbRP, true, nbNest, nesting, conts);
496             // cleaning
497             if ( conts ) free(conts);
498             if ( nesting ) free(nesting);
499         }
501         // add all the pieces resulting from cut or slice
502         for (int i=0;i<nbRP;i++) {
503             gchar *d = resPath[i]->svg_dump_path();
505             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
506             Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
507             repr->setAttribute("style", style);
508             if (mask)
509                 repr->setAttribute("mask", mask);
510             if (clip_path)
511                 repr->setAttribute("clip-path", clip_path);
513             repr->setAttribute("d", d);
514             g_free(d);
516             // for slice, remove fill
517             if (bop == bool_op_slice) {
518                 SPCSSAttr *css;
520                 css = sp_repr_css_attr_new();
521                 sp_repr_css_set_property(css, "fill", "none");
523                 sp_repr_css_change(repr, css, "style");
525                 sp_repr_css_attr_unref(css);
526             }
528             // we assign the same id on all pieces, but it on adding to document, it will be changed on all except one
529             // this means it's basically random which of the pieces inherits the original's id and clones
530             // a better algorithm might figure out e.g. the biggest piece
531             repr->setAttribute("id", id);
533             repr->setAttribute("transform", transform);
535             // add the new repr to the parent
536             parent->appendChild(repr);
538             // move to the saved position
539             repr->setPosition(pos > 0 ? pos : 0);
541             selection->add(repr);
542             Inkscape::GC::release(repr);
544             delete resPath[i];
545         }
546         if ( resPath ) free(resPath);
548     } else {
549         gchar *d = res->svg_dump_path();
551         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
552         Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
553         repr->setAttribute("style", style);
555         if ( mask )
556             repr->setAttribute("mask", mask);
558         if ( clip_path )
559             repr->setAttribute("clip-path", clip_path);
561         repr->setAttribute("d", d);
562         g_free(d);
564         repr->setAttribute("transform", transform);
566         repr->setAttribute("id", id);
567         parent->appendChild(repr);
568         repr->setPosition(pos > 0 ? pos : 0);
570         selection->add(repr);
571         Inkscape::GC::release(repr);
572     }
574     g_free(transform);
576     sp_document_done(sp_desktop_document(desktop), verb, description);
578     delete res;
582 void
583 sp_selected_path_outline()
585     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
587     Inkscape::Selection *selection = sp_desktop_selection(desktop);
589     if (selection->isEmpty()) {
590         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>stroked path(s)</b> to convert stroke to path."));
591         return;
592     }
594     bool did = false;
596     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
597          items != NULL;
598          items = items->next) {
600         SPItem *item = (SPItem *) items->data;
602         if (!SP_IS_SHAPE(item) && !SP_IS_TEXT(item))
603             continue;
605         SPCurve *curve = NULL;
606         if (SP_IS_SHAPE(item)) {
607             curve = sp_shape_get_curve(SP_SHAPE(item));
608             if (curve == NULL)
609                 continue;
610         }
611         if (SP_IS_TEXT(item)) {
612             curve = SP_TEXT(item)->getNormalizedBpath();
613             if (curve == NULL)
614                 continue;
615         }
617         {   // pas de stroke pas de chocolat
618             SPCSSAttr *css = sp_repr_css_attr_inherited(SP_OBJECT_REPR(item), "style");
619             gchar const *val = sp_repr_css_property(css, "stroke", NULL);
621             if (val == NULL || strcmp(val, "none") == 0) {
622                 sp_curve_unref(curve);
623                 continue;
624             }
625         }
627         // remember old stroke style, to be set on fill
628         SPCSSAttr *ncss;
629         {
630             SPCSSAttr *ocss = sp_repr_css_attr_inherited(SP_OBJECT_REPR(item), "style");
631             gchar const *val = sp_repr_css_property(ocss, "stroke", NULL);
632             gchar const *opac = sp_repr_css_property(ocss, "stroke-opacity", NULL);
634             ncss = sp_repr_css_attr_new();
636             sp_repr_css_set_property(ncss, "stroke", "none");
637             sp_repr_css_set_property(ncss, "stroke-opacity", "1.0");
638             sp_repr_css_set_property(ncss, "fill", val);
639             if ( opac ) {
640                 sp_repr_css_set_property(ncss, "fill-opacity", opac);
641             } else {
642                 sp_repr_css_set_property(ncss, "fill-opacity", "1.0");
643             }
644             sp_repr_css_unset_property(ncss, "marker-start");
645             sp_repr_css_unset_property(ncss, "marker-mid");
646             sp_repr_css_unset_property(ncss, "marker-end");
647         }
649         NR::Matrix const transform(item->transform);
650         float const scale = transform.expansion();
651         gchar *style = g_strdup(SP_OBJECT_REPR(item)->attribute("style"));
652         SPStyle *i_style = SP_OBJECT(item)->style;
653         gchar const *mask = SP_OBJECT_REPR(item)->attribute("mask");
654         gchar const *clip_path = SP_OBJECT_REPR(item)->attribute("clip-path");
656         float o_width, o_miter;
657         JoinType o_join;
658         ButtType o_butt;
660         {
661             int jointype, captype;
663             jointype = i_style->stroke_linejoin.computed;
664             captype = i_style->stroke_linecap.computed;
665             o_width = i_style->stroke_width.computed;
667             switch (jointype) {
668                 case SP_STROKE_LINEJOIN_MITER:
669                     o_join = join_pointy;
670                     break;
671                 case SP_STROKE_LINEJOIN_ROUND:
672                     o_join = join_round;
673                     break;
674                 default:
675                     o_join = join_straight;
676                     break;
677             }
679             switch (captype) {
680                 case SP_STROKE_LINECAP_SQUARE:
681                     o_butt = butt_square;
682                     break;
683                 case SP_STROKE_LINECAP_ROUND:
684                     o_butt = butt_round;
685                     break;
686                 default:
687                     o_butt = butt_straight;
688                     break;
689             }
691             if (o_width < 0.1)
692                 o_width = 0.1;
693             o_miter = i_style->stroke_miterlimit.value * o_width;
694         }
696         Path *orig = Path_for_item(item, false);
697         if (orig == NULL) {
698             g_free(style);
699             sp_curve_unref(curve);
700             continue;
701         }
703         Path *res = new Path;
704         res->SetBackData(false);
706         if (i_style->stroke_dash.n_dash) {
707             // For dashed strokes, use Stroke method, because Outline can't do dashes
708             // However Stroke adds lots of extra nodes _or_ makes the path crooked, so consider this a temporary workaround
710             orig->ConvertWithBackData(0.1);
712             orig->DashPolylineFromStyle(i_style, scale, 0);
714             Shape* theShape = new Shape;
715             orig->Stroke(theShape, false, 0.5*o_width, o_join, o_butt,
716                          0.5 * o_miter);
717             orig->Outline(res, 0.5 * o_width, o_join, o_butt, 0.5 * o_miter);
719             Shape *theRes = new Shape;
721             theRes->ConvertToShape(theShape, fill_positive);
723             Path *originaux[1];
724             originaux[0] = res;
725             theRes->ConvertToForme(orig, 1, originaux);
727             res->Coalesce(5.0);
729             delete theShape;
730             delete theRes;
732         } else {
734             orig->Outline(res, 0.5 * o_width, o_join, o_butt, 0.5 * o_miter);
736             orig->Coalesce(0.5 * o_width);
738             Shape *theShape = new Shape;
739             Shape *theRes = new Shape;
741             res->ConvertWithBackData(1.0);
742             res->Fill(theShape, 0);
743             theRes->ConvertToShape(theShape, fill_positive);
745             Path *originaux[1];
746             originaux[0] = res;
747             theRes->ConvertToForme(orig, 1, originaux);
749             delete theShape;
750             delete theRes;
751         }
753         if (orig->descr_cmd.size() <= 1) {
754             // ca a merd\8e, ou bien le resultat est vide
755             delete res;
756             delete orig;
757             g_free(style);
758             continue;
759         }
761         did = true;
763         // remember the position of the item
764         gint pos = SP_OBJECT_REPR(item)->position();
765         // remember parent
766         Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
767         // remember id
768         char const *id = SP_OBJECT_REPR(item)->attribute("id");
770         if (res->descr_cmd.size() > 1) { // if there's 0 or 1 node left, drop this path altogether
772             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
773             Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
775             // restore old style
776             repr->setAttribute("style", style);
778             // set old stroke style on fill
779             sp_repr_css_change(repr, ncss, "style");
781             sp_repr_css_attr_unref(ncss);
783             gchar *str = orig->svg_dump_path();
784             repr->setAttribute("d", str);
785             g_free(str);
787             if (mask)
788                 repr->setAttribute("mask", mask);
789             if (clip_path)
790                 repr->setAttribute("clip-path", clip_path);
792             if (SP_IS_SHAPE(item) && sp_shape_has_markers (SP_SHAPE(item))) {
794                 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
795                 Inkscape::XML::Node *g_repr = xml_doc->createElement("svg:g");
797                 // add the group to the parent
798                 parent->appendChild(g_repr);
799                 // move to the saved position
800                 g_repr->setPosition(pos > 0 ? pos : 0);
802                 g_repr->appendChild(repr);
803                 repr->setAttribute("id", id);
804                 SPItem *newitem = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
805                 sp_item_write_transform(newitem, repr, transform);
807                 SPShape *shape = SP_SHAPE(item);
809                 for (NArtBpath* bp = SP_CURVE_BPATH(shape->curve); bp->code != NR_END; bp++) {
810                     for (int m = SP_MARKER_LOC_START; m < SP_MARKER_LOC_QTY; m++) {
811                         if (sp_shape_marker_required (shape, m, bp)) {
813                             SPMarker* marker = SP_MARKER (shape->marker[m]);
814                             SPItem* marker_item = sp_item_first_item_child (SP_OBJECT (shape->marker[m]));
816                             NR::Matrix tr(sp_shape_marker_get_transform(shape, bp));
818                             if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
819                                 tr = NR::scale(i_style->stroke_width.computed) * tr;
820                             }
822                             // total marker transform
823                             tr = marker_item->transform * marker->c2p * tr * transform;
825                             if (SP_OBJECT_REPR(marker_item)) {
826                                 Inkscape::XML::Node *m_repr = SP_OBJECT_REPR(marker_item)->duplicate();
827                                 g_repr->appendChild(m_repr);
828                                 SPItem *marker_item = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(m_repr);
829                                 sp_item_write_transform(marker_item, m_repr, tr);
830                             }
831                         }
832                     }
833                 }
836                 selection->add(g_repr);
838                 Inkscape::GC::release(g_repr);
841             } else {
843                 // add the new repr to the parent
844                 parent->appendChild(repr);
846                 // move to the saved position
847                 repr->setPosition(pos > 0 ? pos : 0);
849                 repr->setAttribute("id", id);
851                 SPItem *newitem = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
852                 sp_item_write_transform(newitem, repr, transform);
854                 selection->add(repr);
856             }
858             Inkscape::GC::release(repr);
860             sp_curve_unref(curve);
861             selection->remove(item);
862             SP_OBJECT(item)->deleteObject(false);
864         }
866         delete res;
867         delete orig;
868         g_free(style);
870     }
872     if (did) {
873         sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_OUTLINE, 
874                          _("Convert stroke to path"));
875     } else {
876         // TRANSLATORS: "to outline" means "to convert stroke to path"
877         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No stroked paths</b> in the selection."));
878         return;
879     }
883 void
884 sp_selected_path_offset()
886     double prefOffset = prefs_get_double_attribute("options.defaultoffsetwidth", "value", 1.0);
888     sp_selected_path_do_offset(true, prefOffset);
890 void
891 sp_selected_path_inset()
893     double prefOffset = prefs_get_double_attribute("options.defaultoffsetwidth", "value", 1.0);
895     sp_selected_path_do_offset(false, prefOffset);
898 void
899 sp_selected_path_offset_screen(double pixels)
901     sp_selected_path_do_offset(true,  pixels / SP_ACTIVE_DESKTOP->current_zoom());
904 void
905 sp_selected_path_inset_screen(double pixels)
907     sp_selected_path_do_offset(false,  pixels / SP_ACTIVE_DESKTOP->current_zoom());
911 void sp_selected_path_create_offset_object_zero()
913     sp_selected_path_create_offset_object(0, false);
916 void sp_selected_path_create_offset()
918     sp_selected_path_create_offset_object(1, false);
920 void sp_selected_path_create_inset()
922     sp_selected_path_create_offset_object(-1, false);
925 void sp_selected_path_create_updating_offset_object_zero()
927     sp_selected_path_create_offset_object(0, true);
930 void sp_selected_path_create_updating_offset()
932     sp_selected_path_create_offset_object(1, true);
934 void sp_selected_path_create_updating_inset()
936     sp_selected_path_create_offset_object(-1, true);
939 void
940 sp_selected_path_create_offset_object(int expand, bool updating)
942     Inkscape::Selection *selection;
943     Inkscape::XML::Node *repr;
944     SPItem *item;
945     SPCurve *curve;
946     gchar *style, *str;
947     SPDesktop *desktop;
948     float o_width, o_miter;
949     JoinType o_join;
950     ButtType o_butt;
952     curve = NULL;
954     desktop = SP_ACTIVE_DESKTOP;
956     selection = sp_desktop_selection(desktop);
958     item = selection->singleItem();
960     if (item == NULL || ( !SP_IS_SHAPE(item) && !SP_IS_TEXT(item) ) ) {
961         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Selected object is <b>not a path</b>, cannot inset/outset."));
962         return;
963     }
964     if (SP_IS_SHAPE(item))
965     {
966         curve = sp_shape_get_curve(SP_SHAPE(item));
967         if (curve == NULL)
968             return;
969     }
970     if (SP_IS_TEXT(item))
971     {
972         curve = SP_TEXT(item)->getNormalizedBpath();
973         if (curve == NULL)
974             return;
975     }
977     NR::Matrix const transform(item->transform);
979     sp_item_write_transform(item, SP_OBJECT_REPR(item), NR::identity());
981     style = g_strdup(SP_OBJECT(item)->repr->attribute("style"));
983     // remember the position of the item
984     gint pos = SP_OBJECT_REPR(item)->position();
985     // remember parent
986     Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
988     {
989         SPStyle *i_style = SP_OBJECT(item)->style;
990         int jointype, captype;
992         jointype = i_style->stroke_linejoin.value;
993         captype = i_style->stroke_linecap.value;
994         o_width = i_style->stroke_width.computed;
995         if (jointype == SP_STROKE_LINEJOIN_MITER)
996         {
997             o_join = join_pointy;
998         }
999         else if (jointype == SP_STROKE_LINEJOIN_ROUND)
1000         {
1001             o_join = join_round;
1002         }
1003         else
1004         {
1005             o_join = join_straight;
1006         }
1007         if (captype == SP_STROKE_LINECAP_SQUARE)
1008         {
1009             o_butt = butt_square;
1010         }
1011         else if (captype == SP_STROKE_LINECAP_ROUND)
1012         {
1013             o_butt = butt_round;
1014         }
1015         else
1016         {
1017             o_butt = butt_straight;
1018         }
1020         {
1021             double prefOffset = 1.0;
1022             prefOffset = prefs_get_double_attribute("options.defaultoffsetwidth", "value", prefOffset);
1023             o_width = prefOffset;
1024         }
1026         if (o_width < 0.01)
1027             o_width = 0.01;
1028         o_miter = i_style->stroke_miterlimit.value * o_width;
1029     }
1031     Path *orig = Path_for_item(item, true, false);
1032     if (orig == NULL)
1033     {
1034         g_free(style);
1035         sp_curve_unref(curve);
1036         return;
1037     }
1039     Path *res = new Path;
1040     res->SetBackData(false);
1042     {
1043         Shape *theShape = new Shape;
1044         Shape *theRes = new Shape;
1046         orig->ConvertWithBackData(1.0);
1047         orig->Fill(theShape, 0);
1049         SPCSSAttr *css = sp_repr_css_attr(SP_OBJECT_REPR(item), "style");
1050         gchar const *val = sp_repr_css_property(css, "fill-rule", NULL);
1051         if (val && strcmp(val, "nonzero") == 0)
1052         {
1053             theRes->ConvertToShape(theShape, fill_nonZero);
1054         }
1055         else if (val && strcmp(val, "evenodd") == 0)
1056         {
1057             theRes->ConvertToShape(theShape, fill_oddEven);
1058         }
1059         else
1060         {
1061             theRes->ConvertToShape(theShape, fill_nonZero);
1062         }
1064         Path *originaux[1];
1065         originaux[0] = orig;
1066         theRes->ConvertToForme(res, 1, originaux);
1068         delete theShape;
1069         delete theRes;
1070     }
1072     sp_curve_unref(curve);
1074     if (res->descr_cmd.size() <= 1)
1075     {
1076         // pas vraiment de points sur le resultat
1077         // donc il ne reste rien
1078         sp_document_done(sp_desktop_document(desktop), 
1079                          (updating ? SP_VERB_SELECTION_LINKED_OFFSET 
1080                           : SP_VERB_SELECTION_DYNAMIC_OFFSET),
1081                          (updating ? _("Create linked offset")
1082                           : _("Create dynamic offset")));
1083         selection->clear();
1085         delete res;
1086         delete orig;
1087         g_free(style);
1088         return;
1089     }
1091     {
1092         gchar tstr[80];
1094         tstr[79] = '\0';
1096         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
1097         repr = xml_doc->createElement("svg:path");
1098         repr->setAttribute("sodipodi:type", "inkscape:offset");
1099         sp_repr_set_svg_double(repr, "inkscape:radius", ( expand > 0
1100                                                           ? o_width
1101                                                           : expand < 0
1102                                                           ? -o_width
1103                                                           : 0 ));
1105         str = res->svg_dump_path();
1106         repr->setAttribute("inkscape:original", str);
1107         g_free(str);
1109         if ( updating ) {
1110             char const *id = SP_OBJECT(item)->repr->attribute("id");
1111             char const *uri = g_strdup_printf("#%s", id);
1112             repr->setAttribute("xlink:href", uri);
1113             g_free((void *) uri);
1114         } else {
1115             repr->setAttribute("inkscape:href", NULL);
1116         }
1118         repr->setAttribute("style", style);
1120         // add the new repr to the parent
1121         parent->appendChild(repr);
1123         // move to the saved position
1124         repr->setPosition(pos > 0 ? pos : 0);
1126         SPItem *nitem = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
1128         if ( updating ) {
1129             // on conserve l'original
1130             // we reapply the transform to the original (offset will feel it)
1131             sp_item_write_transform(item, SP_OBJECT_REPR(item), transform);
1132         } else {
1133             // delete original, apply the transform to the offset
1134             SP_OBJECT(item)->deleteObject(false);
1135             sp_item_write_transform(nitem, repr, transform);
1136         }
1138         // The object just created from a temporary repr is only a seed.
1139         // We need to invoke its write which will update its real repr (in particular adding d=)
1140         SP_OBJECT(nitem)->updateRepr();
1142         Inkscape::GC::release(repr);
1144         selection->set(nitem);
1145     }
1147     sp_document_done(sp_desktop_document(desktop), 
1148                      (updating ? SP_VERB_SELECTION_LINKED_OFFSET 
1149                       : SP_VERB_SELECTION_DYNAMIC_OFFSET),
1150                      (updating ? _("Create linked offset")
1151                       : _("Create dynamic offset")));
1153     delete res;
1154     delete orig;
1156     g_free(style);
1170 void
1171 sp_selected_path_do_offset(bool expand, double prefOffset)
1173     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1175     Inkscape::Selection *selection = sp_desktop_selection(desktop);
1177     if (selection->isEmpty()) {
1178         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to inset/outset."));
1179         return;
1180     }
1182     bool did = false;
1184     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
1185          items != NULL;
1186          items = items->next) {
1188         SPItem *item = (SPItem *) items->data;
1190         if (!SP_IS_SHAPE(item) && !SP_IS_TEXT(item))
1191             continue;
1193         SPCurve *curve = NULL;
1194         if (SP_IS_SHAPE(item)) {
1195             curve = sp_shape_get_curve(SP_SHAPE(item));
1196             if (curve == NULL)
1197                 continue;
1198         }
1199         if (SP_IS_TEXT(item)) {
1200             curve = SP_TEXT(item)->getNormalizedBpath();
1201             if (curve == NULL)
1202                 continue;
1203         }
1205         NR::Matrix const transform(item->transform);
1207         sp_item_write_transform(item, SP_OBJECT_REPR(item), NR::identity());
1209         gchar *style = g_strdup(SP_OBJECT_REPR(item)->attribute("style"));
1211         float o_width, o_miter;
1212         JoinType o_join;
1213         ButtType o_butt;
1215         {
1216             SPStyle *i_style = SP_OBJECT(item)->style;
1217             int jointype, captype;
1219             jointype = i_style->stroke_linejoin.value;
1220             captype = i_style->stroke_linecap.value;
1221             o_width = i_style->stroke_width.computed;
1223             switch (jointype) {
1224                 case SP_STROKE_LINEJOIN_MITER:
1225                     o_join = join_pointy;
1226                     break;
1227                 case SP_STROKE_LINEJOIN_ROUND:
1228                     o_join = join_round;
1229                     break;
1230                 default:
1231                     o_join = join_straight;
1232                     break;
1233             }
1235             switch (captype) {
1236                 case SP_STROKE_LINECAP_SQUARE:
1237                     o_butt = butt_square;
1238                     break;
1239                 case SP_STROKE_LINECAP_ROUND:
1240                     o_butt = butt_round;
1241                     break;
1242                 default:
1243                     o_butt = butt_straight;
1244                     break;
1245             }
1247             o_width = prefOffset;
1249             if (o_width < 0.1)
1250                 o_width = 0.1;
1251             o_miter = i_style->stroke_miterlimit.value * o_width;
1252         }
1254         Path *orig = Path_for_item(item, false);
1255         if (orig == NULL) {
1256             g_free(style);
1257             sp_curve_unref(curve);
1258             continue;
1259         }
1261         Path *res = new Path;
1262         res->SetBackData(false);
1264         {
1265             Shape *theShape = new Shape;
1266             Shape *theRes = new Shape;
1268             orig->ConvertWithBackData(0.03);
1269             orig->Fill(theShape, 0);
1271             SPCSSAttr *css = sp_repr_css_attr(SP_OBJECT_REPR(item), "style");
1272             gchar const *val = sp_repr_css_property(css, "fill-rule", NULL);
1273             if (val && strcmp(val, "nonzero") == 0)
1274             {
1275                 theRes->ConvertToShape(theShape, fill_nonZero);
1276             }
1277             else if (val && strcmp(val, "evenodd") == 0)
1278             {
1279                 theRes->ConvertToShape(theShape, fill_oddEven);
1280             }
1281             else
1282             {
1283                 theRes->ConvertToShape(theShape, fill_nonZero);
1284             }
1286             // et maintenant: offset
1287             // methode inexacte
1288 /*                      Path *originaux[1];
1289                         originaux[0] = orig;
1290                         theRes->ConvertToForme(res, 1, originaux);
1292                         if (expand) {
1293                         res->OutsideOutline(orig, 0.5 * o_width, o_join, o_butt, o_miter);
1294                         } else {
1295                         res->OutsideOutline(orig, -0.5 * o_width, o_join, o_butt, o_miter);
1296                         }
1298                         orig->ConvertWithBackData(1.0);
1299                         orig->Fill(theShape, 0);
1300                         theRes->ConvertToShape(theShape, fill_positive);
1301                         originaux[0] = orig;
1302                         theRes->ConvertToForme(res, 1, originaux);
1304                         if (o_width >= 0.5) {
1305                         //     res->Coalesce(1.0);
1306                         res->ConvertEvenLines(1.0);
1307                         res->Simplify(1.0);
1308                         } else {
1309                         //      res->Coalesce(o_width);
1310                         res->ConvertEvenLines(1.0*o_width);
1311                         res->Simplify(1.0 * o_width);
1312                         }    */
1313             // methode par makeoffset
1315             if (expand)
1316             {
1317                 theShape->MakeOffset(theRes, o_width, o_join, o_miter);
1318             }
1319             else
1320             {
1321                 theShape->MakeOffset(theRes, -o_width, o_join, o_miter);
1322             }
1323             theRes->ConvertToShape(theShape, fill_positive);
1325             res->Reset();
1326             theRes->ConvertToForme(res);
1328             if (o_width >= 1.0)
1329             {
1330                 res->ConvertEvenLines(1.0);
1331                 res->Simplify(1.0);
1332             }
1333             else
1334             {
1335                 res->ConvertEvenLines(1.0*o_width);
1336                 res->Simplify(1.0 * o_width);
1337             }
1339             delete theShape;
1340             delete theRes;
1341         }
1343         did = true;
1345         sp_curve_unref(curve);
1346         // remember the position of the item
1347         gint pos = SP_OBJECT_REPR(item)->position();
1348         // remember parent
1349         Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
1350         // remember id
1351         char const *id = SP_OBJECT_REPR(item)->attribute("id");
1353         selection->remove(item);
1354         SP_OBJECT(item)->deleteObject(false);
1356         if (res->descr_cmd.size() > 1) { // if there's 0 or 1 node left, drop this path altogether
1358             gchar tstr[80];
1360             tstr[79] = '\0';
1362             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
1363             Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
1365             repr->setAttribute("style", style);
1367             gchar *str = res->svg_dump_path();
1368             repr->setAttribute("d", str);
1369             g_free(str);
1371             // add the new repr to the parent
1372             parent->appendChild(repr);
1374             // move to the saved position
1375             repr->setPosition(pos > 0 ? pos : 0);
1377             SPItem *newitem = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
1379             // reapply the transform
1380             sp_item_write_transform(newitem, repr, transform);
1382             repr->setAttribute("id", id);
1384             selection->add(repr);
1386             Inkscape::GC::release(repr);
1387         }
1389         delete orig;
1390         delete res;
1391     }
1393     if (did) {
1394         sp_document_done(sp_desktop_document(desktop), 
1395                          (expand ? SP_VERB_SELECTION_OFFSET : SP_VERB_SELECTION_INSET),
1396                          (expand ? _("Outset path") : _("Inset path")));
1397     } else {
1398         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to inset/outset in the selection."));
1399         return;
1400     }
1404 static bool
1405 sp_selected_path_simplify_items(SPDesktop *desktop,
1406                                 Inkscape::Selection *selection, GSList *items,
1407                                 float threshold,  bool justCoalesce,
1408                                 float angleLimit, bool breakableAngles,
1409                                 bool modifySelection);
1412 //return true if we changed something, else false
1413 bool
1414 sp_selected_path_simplify_item(SPDesktop *desktop,
1415                  Inkscape::Selection *selection, SPItem *item,
1416                  float threshold,  bool justCoalesce,
1417                  float angleLimit, bool breakableAngles,
1418                  gdouble size,     bool modifySelection)
1420     if (!(SP_IS_GROUP(item) || SP_IS_SHAPE(item) || SP_IS_TEXT(item)))
1421         return false;
1423     //If this is a group, do the children instead
1424     if (SP_IS_GROUP(item)) {
1425         GSList *items = sp_item_group_item_list(SP_GROUP(item));
1426         
1427         return sp_selected_path_simplify_items(desktop, selection, items,
1428                                                threshold, justCoalesce,
1429                                                angleLimit, breakableAngles,
1430                                                false);
1431     }
1434     SPCurve *curve = NULL;
1436     if (SP_IS_SHAPE(item)) {
1437         curve = sp_shape_get_curve(SP_SHAPE(item));
1438         if (!curve)
1439             return false;
1440     }
1442     if (SP_IS_TEXT(item)) {
1443         curve = SP_TEXT(item)->getNormalizedBpath();
1444         if (!curve)
1445             return false;
1446     }
1448     // save the transform, to re-apply it after simplification
1449     NR::Matrix const transform(item->transform);
1451     /*
1452        reset the transform, effectively transforming the item by transform.inverse();
1453        this is necessary so that the item is transformed twice back and forth,
1454        allowing all compensations to cancel out regardless of the preferences
1455     */
1456     sp_item_write_transform(item, SP_OBJECT_REPR(item), NR::identity());
1458     gchar *style = g_strdup(SP_OBJECT_REPR(item)->attribute("style"));
1459     gchar *mask = g_strdup(SP_OBJECT_REPR(item)->attribute("mask"));
1460     gchar *clip_path = g_strdup(SP_OBJECT_REPR(item)->attribute("clip-path"));
1462     Path *orig = Path_for_item(item, false);
1463     if (orig == NULL) {
1464         g_free(style);
1465         sp_curve_unref(curve);
1466         return false;
1467     }
1469     sp_curve_unref(curve);
1470     // remember the position of the item
1471     gint pos = SP_OBJECT_REPR(item)->position();
1472     // remember parent
1473     Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
1474     // remember id
1475     char const *id = SP_OBJECT_REPR(item)->attribute("id");
1477     //If a group was selected, to not change the selection list
1478     if (modifySelection)
1479         selection->remove(item);
1481     SP_OBJECT(item)->deleteObject(false);
1483     if ( justCoalesce ) {
1484         orig->Coalesce(threshold * size);
1485     } else {
1486         orig->ConvertEvenLines(threshold * size);
1487         orig->Simplify(threshold * size);
1488     }
1490     Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
1491     Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
1493     // restore style, mask and clip-path
1494     repr->setAttribute("style", style);
1495     g_free(style);
1497     if ( mask ) {
1498         repr->setAttribute("mask", mask);
1499         g_free(mask);
1500     }
1502     if ( clip_path ) {
1503         repr->setAttribute("clip-path", clip_path);
1504         g_free(clip_path);
1505     }
1507     // path
1508     gchar *str = orig->svg_dump_path();
1509     repr->setAttribute("d", str);
1510     g_free(str);
1512     // restore id
1513     repr->setAttribute("id", id);
1515     // add the new repr to the parent
1516     parent->appendChild(repr);
1518     // move to the saved position
1519     repr->setPosition(pos > 0 ? pos : 0);
1521     SPItem *newitem = (SPItem *) sp_desktop_document(desktop)->getObjectByRepr(repr);
1523     // reapply the transform
1524     sp_item_write_transform(newitem, repr, transform);
1526     //If we are not in a selected group
1527     if (modifySelection)
1528         selection->add(repr);
1530     Inkscape::GC::release(repr);
1532     // clean up
1533     if (orig) delete orig;
1535     return true;
1539 bool
1540 sp_selected_path_simplify_items(SPDesktop *desktop,
1541                                 Inkscape::Selection *selection, GSList *items,
1542                                 float threshold,  bool justCoalesce,
1543                                 float angleLimit, bool breakableAngles,
1544                                 bool modifySelection)
1546   bool simplifyIndividualPaths =
1547     (bool) prefs_get_int_attribute("options.simplifyindividualpaths", "value", 0);
1548   
1549   gchar *simplificationType;
1550   if (simplifyIndividualPaths) {
1551     simplificationType = "individual paths";
1552   } else {
1553     simplificationType = "as a group";
1554   }
1556   bool didSomething = false;
1558   NR::Maybe<NR::Rect> selectionBbox = selection->bounds();
1559   if (!selectionBbox) {
1560     return false;
1561   }
1562   gdouble selectionSize  = L2(selectionBbox->dimensions());
1564   gdouble simplifySize  = selectionSize;
1565   
1566   int pathsSimplified = 0;
1567   int totalPathCount  = g_slist_length(items);
1568   
1569   desktop->disableInteraction();
1570   
1571   for (; items != NULL; items = items->next) {
1572       SPItem *item = (SPItem *) items->data;
1573       
1574       if (!(SP_IS_GROUP(item) || SP_IS_SHAPE(item) || SP_IS_TEXT(item)))
1575           continue;
1577       if (simplifyIndividualPaths) {
1578           NR::Maybe<NR::Rect> itemBbox = item->getBounds(sp_item_i2d_affine(item));        
1579           if (itemBbox) {
1580               simplifySize      = L2(itemBbox->dimensions());
1581           } else {
1582               simplifySize      = 0;
1583           }
1584       }
1587       pathsSimplified++;
1589       if (pathsSimplified % 20 == 0) {
1590         gchar *message = g_strdup_printf(_("Simplifying %s - <b>%d</b> of <b>%d</b> paths simplified..."), simplificationType, pathsSimplified, totalPathCount);
1591         desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, message);
1592         desktop->updateCanvasNow();
1593       }
1595       didSomething |= sp_selected_path_simplify_item(desktop, selection, item,
1596                           threshold, justCoalesce, angleLimit, breakableAngles, simplifySize, modifySelection);
1597   }
1599   desktop->enableInteraction();
1600   
1601   if (pathsSimplified > 20) {
1602     desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, g_strdup_printf(_("Done - <b>%d</b> paths simplified."), pathsSimplified));
1603   }
1604   
1605   return didSomething;
1608 void
1609 sp_selected_path_simplify_selection(float threshold, bool justCoalesce,
1610                        float angleLimit, bool breakableAngles)
1612     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1614     Inkscape::Selection *selection = sp_desktop_selection(desktop);
1616     if (selection->isEmpty()) {
1617         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE,
1618                          _("Select <b>path(s)</b> to simplify."));
1619         return;
1620     }
1622     GSList *items = g_slist_copy((GSList *) selection->itemList());
1624     bool didSomething = sp_selected_path_simplify_items(desktop, selection,
1625                                                         items, threshold,
1626                                                         justCoalesce,
1627                                                         angleLimit,
1628                                                         breakableAngles, true);
1630     if (didSomething)
1631         sp_document_done(sp_desktop_document(desktop), SP_VERB_SELECTION_SIMPLIFY, 
1632                          _("Simplify"));
1633     else
1634         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to simplify in the selection."));
1639 // globals for keeping track of accelerated simplify
1640 static double previousTime      = 0.0;
1641 static gdouble simplifyMultiply = 1.0;
1643 void
1644 sp_selected_path_simplify(void)
1646     gdouble simplifyThreshold =
1647         prefs_get_double_attribute("options.simplifythreshold", "value", 0.003);
1648     bool simplifyJustCoalesce =
1649         (bool) prefs_get_int_attribute("options.simplifyjustcoalesce", "value", 0);
1651     //Get the current time
1652     GTimeVal currentTimeVal;
1653     g_get_current_time(&currentTimeVal);
1654     double currentTime = currentTimeVal.tv_sec * 1000000 +
1655                 currentTimeVal.tv_usec;
1657     //Was the previous call to this function recent? (<0.5 sec)
1658     if (previousTime > 0.0 && currentTime - previousTime < 500000.0) {
1660         // add to the threshold 1/2 of its original value
1661         simplifyMultiply  += 0.5;
1662         simplifyThreshold *= simplifyMultiply;
1664     } else {
1665         // reset to the default
1666         simplifyMultiply = 1;
1667     }
1669     //remember time for next call
1670     previousTime = currentTime;
1672     //g_print("%g\n", simplify_threshold);
1674     //Make the actual call
1675     sp_selected_path_simplify_selection(simplifyThreshold,
1676                       simplifyJustCoalesce, 0.0, false);
1681 // fonctions utilitaires
1683 bool
1684 Ancetre(Inkscape::XML::Node *a, Inkscape::XML::Node *who)
1686     if (who == NULL || a == NULL)
1687         return false;
1688     if (who == a)
1689         return true;
1690     return Ancetre(sp_repr_parent(a), who);
1693 Path *
1694 Path_for_item(SPItem *item, bool doTransformation, bool transformFull)
1696     SPCurve *curve;
1698     if (!item)
1699         return NULL;
1701     if (SP_IS_SHAPE(item))
1702     {
1703         curve = sp_shape_get_curve(SP_SHAPE(item));
1704     }
1705     else if (SP_IS_TEXT(item))
1706     {
1707         curve = SP_TEXT(item)->getNormalizedBpath();
1708     }
1709     else
1710     {
1711         curve = NULL;
1712     }
1714     if (!curve)
1715         return NULL;
1716     NArtBpath *bpath = SP_CURVE_BPATH(curve);
1717     if (bpath == NULL)
1718         return NULL;
1720     if ( doTransformation ) {
1721         if (transformFull)
1722             bpath = nr_artpath_affine(SP_CURVE_BPATH(curve), sp_item_i2doc_affine(item));
1723         else
1724             bpath = nr_artpath_affine(SP_CURVE_BPATH(curve), item->transform);
1725         sp_curve_unref(curve);
1726         curve=NULL;
1727     } else {
1728         bpath=SP_CURVE_BPATH(curve);
1729     }
1731     Path *dest = bpath_to_Path(bpath);
1733     if ( doTransformation ) {
1734         if ( bpath ) g_free(bpath);
1735     } else {
1736         sp_curve_unref(curve);
1737     }
1738     return dest;
1741 Path *bpath_to_Path(NArtBpath const *bpath) {
1742     Path *dest = new Path;
1743     dest->SetBackData(false);
1744     {
1745         int   i;
1746         bool  closed = false;
1747         float lastX  = 0.0;
1748         float lastY  = 0.0;
1750         for (i = 0; bpath[i].code != NR_END; i++) {
1751             switch (bpath[i].code) {
1752                 case NR_LINETO:
1753                     lastX = bpath[i].x3;
1754                     lastY = bpath[i].y3;
1755                     {
1756                         NR::Point tmp(lastX, lastY);
1757                         dest->LineTo(tmp);
1758                     }
1759                     break;
1761                 case NR_CURVETO:
1762                 {
1763                     NR::Point tmp, tms, tme;
1764                     tmp[0]=bpath[i].x3;
1765                     tmp[1]=bpath[i].y3;
1766                     tms[0]=3 * (bpath[i].x1 - lastX);
1767                     tms[1]=3 * (bpath[i].y1 - lastY);
1768                     tme[0]=3 * (bpath[i].x3 - bpath[i].x2);
1769                     tme[1]=3 * (bpath[i].y3 - bpath[i].y2);
1770                     dest->CubicTo(tmp,tms,tme);
1771                 }
1772                 lastX = bpath[i].x3;
1773                 lastY = bpath[i].y3;
1774                 break;
1776                 case NR_MOVETO_OPEN:
1777                 case NR_MOVETO:
1778                     if (closed)
1779                         dest->Close();
1780                     closed = (bpath[i].code == NR_MOVETO);
1781                     lastX = bpath[i].x3;
1782                     lastY = bpath[i].y3;
1783                     {
1784                         NR::Point  tmp(lastX, lastY);
1785                         dest->MoveTo(tmp);
1786                     }
1787                     break;
1788                 default:
1789                     break;
1790             }
1791         }
1792         if (closed)
1793             dest->Close();
1794     }
1795     return dest;
1798 NR::Maybe<Path::cut_position> get_nearest_position_on_Path(Path *path, NR::Point p)
1800     //get nearest position on path
1801     Path::cut_position pos = path->PointToCurvilignPosition(p);
1802     return pos;
1805 NR::Point get_point_on_Path(Path *path, int piece, double t)
1807     NR::Point p;
1808     path->PointAt(piece, t, p);
1809     return p;
1813 /*
1814   Local Variables:
1815   mode:c++
1816   c-file-style:"stroustrup"
1817   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1818   indent-tabs-mode:nil
1819   fill-column:99
1820   End:
1821 */
1822 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :