Code

cleanup: Remove some commented-out code.
[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 "xml/repr.h"
21 #include "svg/svg.h"
22 #include "sp-path.h"
23 #include "sp-text.h"
24 #include "sp-item-group.h"
25 #include "style.h"
26 #include "inkscape.h"
27 #include "document.h"
28 #include "message-stack.h"
29 #include "selection.h"
30 #include "desktop-handles.h"
31 #include "desktop.h"
32 #include "display/canvas-bpath.h"
33 #include "display/curve.h"
34 #include <glibmm/i18n.h>
35 #include "prefs-utils.h"
37 #include "libnr/n-art-bpath.h"
38 #include "libnr/nr-path.h"
39 #include "xml/repr.h"
40 #include "xml/repr-sorting.h"
42 #include "livarot/Path.h"
43 #include "livarot/Shape.h"
45 #include "splivarot.h"
47 bool   Ancetre(Inkscape::XML::Node *a, Inkscape::XML::Node *who);
49 void sp_selected_path_boolop(bool_op bop);
50 void sp_selected_path_do_offset(bool expand, double prefOffset);
51 void sp_selected_path_create_offset_object(int expand, bool updating);
53 void
54 sp_selected_path_union()
55 {
56     sp_selected_path_boolop(bool_op_union);
57 }
59 void
60 sp_selected_path_intersect()
61 {
62     sp_selected_path_boolop(bool_op_inters);
63 }
65 void
66 sp_selected_path_diff()
67 {
68     sp_selected_path_boolop(bool_op_diff);
69 }
71 void
72 sp_selected_path_symdiff()
73 {
74     sp_selected_path_boolop(bool_op_symdiff);
75 }
76 void
77 sp_selected_path_cut()
78 {
79     sp_selected_path_boolop(bool_op_cut);
80 }
81 void
82 sp_selected_path_slice()
83 {
84     sp_selected_path_boolop(bool_op_slice);
85 }
88 // boolean operations
89 // take the source paths from the file, do the operation, delete the originals and add the results
90 void
91 sp_selected_path_boolop(bool_op bop)
92 {
93     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
95     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
97     GSList *il = (GSList *) selection->itemList();
99     if (g_slist_length(il) < 2) {
100         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Select <b>at least 2 paths</b> to perform a boolean operation."));
101         return;
102     }
104     if (g_slist_length(il) > 2) {
105         if (bop == bool_op_diff || bop == bool_op_symdiff || bop == bool_op_cut || bop == bool_op_slice ) {
106             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Select <b>exactly 2 paths</b> to perform difference, XOR, division, or path cut."));
107             return;
108         }
109     }
111     // reverseOrderForOp marks whether the order of the list is the top->down order
112     // it's only used when there are 2 objects, and for operations who need to know the
113     // topmost object (differences, cuts)
114     bool reverseOrderForOp = false;
116     // mettre les elements de la liste dans l'ordre pour ces operations
117     if (bop == bool_op_diff || bop == bool_op_symdiff || bop == bool_op_cut || bop == bool_op_slice) {
118         // check in the tree to find which element of the selection list is topmost (for 2-operand commands only)
119         Inkscape::XML::Node *a = SP_OBJECT_REPR(il->data);
120         Inkscape::XML::Node *b = SP_OBJECT_REPR(il->next->data);
122         if (a == NULL || b == NULL) {
123             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."));
124             return;
125         }
127         if (Ancetre(a, b)) {
128             // a is the parent of b, already in the proper order
129         } else if (Ancetre(b, a)) {
130             // reverse order
131             reverseOrderForOp = true;
132         } else {
134             // objects are not in parent/child relationship;
135             // find their lowest common ancestor
136             Inkscape::XML::Node *dad = LCA(a, b);
137             if (dad == 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             // find the children of the LCA that lead from it to the a and b
143             Inkscape::XML::Node *as = AncetreFils(a, dad);
144             Inkscape::XML::Node *bs = AncetreFils(b, dad);
146             // find out which comes first
147             for (Inkscape::XML::Node *child = dad->firstChild(); child; child = child->next()) {
148                 if (child == as) {
149                     /* a first, so reverse. */
150                     reverseOrderForOp = true;
151                     break;
152                 }
153                 if (child == bs)
154                     break;
155             }
156         }
157     }
159     il = g_slist_copy(il);
161     // first check if all the input objects have shapes
162     // otherwise bail out
163     for (GSList *l = il; l != NULL; l = l->next)
164     {
165         SPItem *item = SP_ITEM(l->data);
166         if (!SP_IS_SHAPE(item) && !SP_IS_TEXT(item))
167         {
168             desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("One of the objects is <b>not a path</b>, cannot perform boolean operation."));
169             g_slist_free(il);
170             return;
171         }
172     }
174     // extract the livarot Paths from the source objects
175     // also get the winding rule specified in the style
176     int nbOriginaux = g_slist_length(il);
177     Path *originaux[nbOriginaux];
178     FillRule  origWind[nbOriginaux];
179     int curOrig;
180     {
181         curOrig = 0;
182         for (GSList *l = il; l != NULL; l = l->next)
183         {
184             SPCSSAttr *css = sp_repr_css_attr(SP_OBJECT_REPR(il->data), "style");
185             gchar const *val = sp_repr_css_property(css, "fill-rule", NULL);
186             if (val && strcmp(val, "nonzero") == 0) {
187                 origWind[curOrig]= fill_nonZero;
188             } else if (val && strcmp(val, "evenodd") == 0) {
189                 origWind[curOrig]= fill_oddEven;
190             } else {
191                 origWind[curOrig]= fill_nonZero;
192             }
194             originaux[curOrig] = Path_for_item((SPItem *) l->data, true, true);
195             if (originaux[curOrig] == NULL || originaux[curOrig]->descr_cmd.size() <= 1)
196             {
197                 for (int i = curOrig; i >= 0; i--) delete originaux[i];
198                 g_slist_free(il);
199                 return;
200             }
201             curOrig++;
202         }
203     }
204     // reverse if needed
205     // note that the selection list keeps its order
206     if ( reverseOrderForOp ) {
207         Path* swap=originaux[0];originaux[0]=originaux[1];originaux[1]=swap;
208         FillRule swai=origWind[0]; origWind[0]=origWind[1]; origWind[1]=swai;
209     }
211     // and work
212     // some temporary instances, first
213     Shape *theShapeA = new Shape;
214     Shape *theShapeB = new Shape;
215     Shape *theShape = new Shape;
216     Path *res = new Path;
217     res->SetBackData(false);
218     Path::cut_position  *toCut=NULL;
219     int                  nbToCut=0;
221     if ( bop == bool_op_inters || bop == bool_op_union || bop == bool_op_diff || bop == bool_op_symdiff ) {
222         // true boolean op
223         // get the polygons of each path, with the winding rule specified, and apply the operation iteratively
224         originaux[0]->ConvertWithBackData(0.001);
226         originaux[0]->Fill(theShape, 0);
228         theShapeA->ConvertToShape(theShape, origWind[0]);
230         curOrig = 1;
231         for (GSList *l = il->next; l != NULL; l = l->next) {
232             originaux[curOrig]->ConvertWithBackData(0.001);
234             originaux[curOrig]->Fill(theShape, curOrig);
236             theShapeB->ConvertToShape(theShape, origWind[curOrig]);
238             // les elements arrivent en ordre inverse dans la liste
239             theShape->Booleen(theShapeB, theShapeA, bop);
241             {
242                 Shape *swap = theShape;
243                 theShape = theShapeA;
244                 theShapeA = swap;
245             }
246             curOrig++;
247         }
249         {
250             Shape *swap = theShape;
251             theShape = theShapeA;
252             theShapeA = swap;
253         }
255     } else if ( bop == bool_op_cut ) {
256         // cuts= sort of a bastard boolean operation, thus not the axact same modus operandi
257         // technically, the cut path is not necessarily a polygon (thus has no winding rule)
258         // it is just uncrossed, and cleaned from duplicate edges and points
259         // then it's fed to Booleen() which will uncross it against the other path
260         // then comes the trick: each edge of the cut path is duplicated (one in each direction),
261         // thus making a polygon. the weight of the edges of the cut are all 0, but
262         // the Booleen need to invert the ones inside the source polygon (for the subsequent
263         // ConvertToForme)
265         // the cut path needs to have the highest pathID in the back data
266         // that's how the Booleen() function knows it's an edge of the cut
268         // FIXME: this gives poor results, the final paths are full of extraneous nodes. Decreasing
269         // ConvertWithBackData parameter below simply increases the number of nodes, so for now I
270         // left it at 1.0. Investigate replacing this by a combination of difference and
271         // intersection of the same two paths. -- bb
272         {
273             Path* swap=originaux[0];originaux[0]=originaux[1];originaux[1]=swap;
274             int   swai=origWind[0];origWind[0]=origWind[1];origWind[1]=(fill_typ)swai;
275         }
276         originaux[0]->ConvertWithBackData(1.0);
278         originaux[0]->Fill(theShape, 0);
280         theShapeA->ConvertToShape(theShape, origWind[0]);
282         originaux[1]->ConvertWithBackData(1.0);
284         originaux[1]->Fill(theShape, 1,false,false,false); //do not closeIfNeeded
286         theShapeB->ConvertToShape(theShape, fill_justDont); // fill_justDont doesn't computes winding numbers
288         // les elements arrivent en ordre inverse dans la liste
289         theShape->Booleen(theShapeB, theShapeA, bool_op_cut, 1);
291     } else if ( bop == bool_op_slice ) {
292         // slice is not really a boolean operation
293         // you just put the 2 shapes in a single polygon, uncross it
294         // the points where the degree is > 2 are intersections
295         // just check it's an intersection on the path you want to cut, and keep it
296         // the intersections you have found are then fed to ConvertPositionsToMoveTo() which will
297         // make new subpath at each one of these positions
298         // inversion pour l'op\8eration
299         {
300             Path* swap=originaux[0];originaux[0]=originaux[1];originaux[1]=swap;
301             int   swai=origWind[0];origWind[0]=origWind[1];origWind[1]=(fill_typ)swai;
302         }
303         originaux[0]->ConvertWithBackData(1.0);
305         originaux[0]->Fill(theShapeA, 0,false,false,false); // don't closeIfNeeded
307         originaux[1]->ConvertWithBackData(1.0);
309         originaux[1]->Fill(theShapeA, 1,true,false,false);// don't closeIfNeeded and just dump in the shape, don't reset it
311         theShape->ConvertToShape(theShapeA, fill_justDont);
313         if ( theShape->hasBackData() ) {
314             // should always be the case, but ya never know
315             {
316                 for (int i = 0; i < theShape->numberOfPoints(); i++) {
317                     if ( theShape->getPoint(i).totalDegree() > 2 ) {
318                         // possibly an intersection
319                         // we need to check that at least one edge from the source path is incident to it
320                         // before we declare it's an intersection
321                         int cb = theShape->getPoint(i).incidentEdge[FIRST];
322                         int   nbOrig=0;
323                         int   nbOther=0;
324                         int   piece=-1;
325                         float t=0.0;
326                         while ( cb >= 0 && cb < theShape->numberOfEdges() ) {
327                             if ( theShape->ebData[cb].pathID == 0 ) {
328                                 // the source has an edge incident to the point, get its position on the path
329                                 piece=theShape->ebData[cb].pieceID;
330                                 if ( theShape->getEdge(cb).st == i ) {
331                                     t=theShape->ebData[cb].tSt;
332                                 } else {
333                                     t=theShape->ebData[cb].tEn;
334                                 }
335                                 nbOrig++;
336                             }
337                             if ( theShape->ebData[cb].pathID == 1 ) nbOther++; // the cut is incident to this point
338                             cb=theShape->NextAt(i, cb);
339                         }
340                         if ( nbOrig > 0 && nbOther > 0 ) {
341                             // point incident to both path and cut: an intersection
342                             // note that you only keep one position on the source; you could have degenerate
343                             // cases where the source crosses itself at this point, and you wouyld miss an intersection
344                             toCut=(Path::cut_position*)realloc(toCut, (nbToCut+1)*sizeof(Path::cut_position));
345                             toCut[nbToCut].piece=piece;
346                             toCut[nbToCut].t=t;
347                             nbToCut++;
348                         }
349                     }
350                 }
351             }
352             {
353                 // i think it's useless now
354                 int i = theShape->numberOfEdges() - 1;
355                 for (;i>=0;i--) {
356                     if ( theShape->ebData[i].pathID == 1 ) {
357                         theShape->SubEdge(i);
358                     }
359                 }
360             }
362         }
363     }
365     int*    nesting=NULL;
366     int*    conts=NULL;
367     int     nbNest=0;
368     // pour compenser le swap juste avant
369     if ( bop == bool_op_slice ) {
370 //    theShape->ConvertToForme(res, nbOriginaux, originaux, true);
371 //    res->ConvertForcedToMoveTo();
372         res->Copy(originaux[0]);
373         res->ConvertPositionsToMoveTo(nbToCut, toCut); // cut where you found intersections
374         free(toCut);
375     } else if ( bop == bool_op_cut ) {
376         // il faut appeler pour desallouer PointData (pas vital, mais bon)
377         // the Booleen() function did not deallocated the point_data array in theShape, because this
378         // function needs it.
379         // this function uses the point_data to get the winding number of each path (ie: is a hole or not)
380         // for later reconstruction in objects, you also need to extract which path is parent of holes (nesting info)
381         theShape->ConvertToFormeNested(res, nbOriginaux, originaux, 1, nbNest, nesting, conts);
382     } else {
383         theShape->ConvertToForme(res, nbOriginaux, originaux);
384     }
386     delete theShape;
387     delete theShapeA;
388     delete theShapeB;
389     for (int i = 0; i < nbOriginaux; i++)  delete originaux[i];
391     if (res->descr_cmd.size() <= 1)
392     {
393         // only one command, presumably a moveto: it isn't a path
394         for (GSList *l = il; l != NULL; l = l->next)
395         {
396             SP_OBJECT(l->data)->deleteObject();
397         }
398         sp_document_done(SP_DT_DOCUMENT(desktop));
399         selection->clear();
401         delete res;
402         g_slist_free(il);
403         return;
404     }
406     // remember important aspects of the source path, to be restored
407     Inkscape::XML::Node *repr_source;
408     if ( bop == bool_op_diff || bop == bool_op_symdiff || bop == bool_op_cut || bop == bool_op_slice ) {
409         if (reverseOrderForOp) {
410              repr_source = SP_OBJECT_REPR(il->data);
411         } else {
412              repr_source = SP_OBJECT_REPR(il->next->data);
413         }
414     } else {
415         // find out the bottom object
416         GSList *sorted = g_slist_copy((GSList *) selection->reprList());
418         sorted = g_slist_sort(sorted, (GCompareFunc) sp_repr_compare_position);
419         repr_source = ((Inkscape::XML::Node *) sorted->data);
420         g_slist_free(sorted);
421     }
422     gint pos = repr_source->position();
423     Inkscape::XML::Node *parent = sp_repr_parent(repr_source);
424     char const *id = repr_source->attribute("id");
425     char const *style = repr_source->attribute("style");
428     // remove source paths
429     selection->clear();
430     for (GSList *l = il; l != NULL; l = l->next) {
431         // if this is the bottommost object,
432         if (!strcmp(SP_OBJECT_REPR(l->data)->attribute("id"), id)) {
433             // delete it so that its clones don't get alerted; this object will be restored shortly, with the same id
434             SP_OBJECT(l->data)->deleteObject(false);
435         } else {
436             // delete the object for real, so that its clones can take appropriate action
437             SP_OBJECT(l->data)->deleteObject();
438         }
439     }
440     g_slist_free(il);
442     // premultiply by the inverse of parent's repr
443     SPItem *parent_item = SP_ITEM(SP_DT_DOCUMENT(desktop)->getObjectByRepr(parent));
444     NR::Matrix local = sp_item_i2doc_affine(parent_item);
445     gchar affinestr[80];
446     gchar *transform = NULL;
447     if (!local.test_identity() && sp_svg_transform_write(affinestr, 79, local.inverse())) {
448         transform = affinestr;
449     }
451     // now that we have the result, add it on the canvas
452     if ( bop == bool_op_cut || bop == bool_op_slice ) {
453         int    nbRP=0;
454         Path** resPath;
455         if ( bop == bool_op_slice ) {
456             // there are moveto's at each intersection, but it's still one unique path
457             // so break it down and add each subpath independently
458             // we could call break_apart to do this, but while we have the description...
459             resPath=res->SubPaths(nbRP, false);
460         } else {
461             // cut operation is a bit wicked: you need to keep holes
462             // that's why you needed the nesting
463             // ConvertToFormeNested() dumped all the subpath in a single Path "res", so we need
464             // to get the path for each part of the polygon. that's why you need the nesting info:
465             // to know in wich subpath to add a subpath
466             resPath=res->SubPathsWithNesting(nbRP, true, nbNest, nesting, conts);
468             // cleaning
469             if ( conts ) free(conts);
470             if ( nesting ) free(nesting);
471         }
473         // add all the pieces resulting from cut or slice
474         for (int i=0;i<nbRP;i++) {
475             gchar *d = resPath[i]->svg_dump_path();
477             Inkscape::XML::Node *repr = sp_repr_new("svg:path");
478             repr->setAttribute("style", style);
479             repr->setAttribute("d", d);
480             g_free(d);
482             // for slice, remove fill
483             if (bop == bool_op_slice) {
484                 SPCSSAttr *css;
486                 css = sp_repr_css_attr_new();
487                 sp_repr_css_set_property(css, "fill", "none");
489                 sp_repr_css_change(repr, css, "style");
491                 sp_repr_css_attr_unref(css);
492             }
494             // we assign the same id on all pieces, but it on adding to document, it will be changed on all except one
495             // this means it's basically random which of the pieces inherits the original's id and clones
496             // a better algorithm might figure out e.g. the biggest piece
497             repr->setAttribute("id", id);
499             repr->setAttribute("transform", transform);
501             // add the new repr to the parent
502             parent->appendChild(repr);
504             // move to the saved position
505             repr->setPosition(pos > 0 ? pos : 0);
507             selection->add(repr);
508             Inkscape::GC::release(repr);
510             delete resPath[i];
511         }
512         if ( resPath ) free(resPath);
514     } else {
515         gchar *d = res->svg_dump_path();
517         Inkscape::XML::Node *repr = sp_repr_new("svg:path");
518         repr->setAttribute("style", style);
520         repr->setAttribute("d", d);
521         g_free(d);
523         repr->setAttribute("transform", transform);
525         repr->setAttribute("id", id);
526         parent->appendChild(repr);
527         repr->setPosition(pos > 0 ? pos : 0);
529         selection->add(repr);
530         Inkscape::GC::release(repr);
531     }
533     sp_document_done(SP_DT_DOCUMENT(desktop));
535     delete res;
539 void
540 sp_selected_path_outline()
542     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
544     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
546     if (selection->isEmpty()) {
547         // TRANSLATORS: "to outline" means "to convert stroke to path"
548         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to outline."));
549         return;
550     }
552     bool did = false;
554     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
555          items != NULL;
556          items = items->next) {
558         SPItem *item = (SPItem *) items->data;
560         if (!SP_IS_SHAPE(item) && !SP_IS_TEXT(item))
561             continue;
563         SPCurve *curve = NULL;
564         if (SP_IS_SHAPE(item)) {
565             curve = sp_shape_get_curve(SP_SHAPE(item));
566             if (curve == NULL)
567                 continue;
568         }
569         if (SP_IS_TEXT(item)) {
570             curve = SP_TEXT(item)->getNormalizedBpath();
571             if (curve == NULL)
572                 continue;
573         }
575         {   // pas de stroke pas de chocolat
576             SPCSSAttr *css = sp_repr_css_attr(SP_OBJECT_REPR(item), "style");
577             gchar const *val = sp_repr_css_property(css, "stroke", NULL);
579             if (val == NULL || strcmp(val, "none") == 0) {
580                 sp_curve_unref(curve);
581                 continue;
582             }
583         }
585         // remember old stroke style, to be set on fill
586         SPCSSAttr *ncss;
587         {
588             SPCSSAttr *ocss = sp_repr_css_attr(SP_OBJECT_REPR(item), "style");
589             gchar const *val = sp_repr_css_property(ocss, "stroke", NULL);
590             gchar const *opac = sp_repr_css_property(ocss, "stroke-opacity", NULL);
592             ncss = sp_repr_css_attr_new();
594             sp_repr_css_set_property(ncss, "stroke", "none");
595             sp_repr_css_set_property(ncss, "stroke-opacity", "1.0");
596             sp_repr_css_set_property(ncss, "fill", val);
597             if ( opac ) {
598                 sp_repr_css_set_property(ncss, "fill-opacity", opac);
599             } else {
600                 sp_repr_css_set_property(ncss, "fill-opacity", "1.0");
601             }
602         }
604         NR::Matrix const transform(item->transform);
605         gchar *style = g_strdup(SP_OBJECT_REPR(item)->attribute("style"));
607         float o_width, o_miter;
608         JoinType o_join;
609         ButtType o_butt;
611         {
612             SPStyle *i_style = SP_OBJECT(item)->style;
613             int jointype, captype;
615             jointype = i_style->stroke_linejoin.computed;
616             captype = i_style->stroke_linecap.computed;
617             o_width = i_style->stroke_width.computed;
619             switch (jointype) {
620                 case SP_STROKE_LINEJOIN_MITER:
621                     o_join = join_pointy;
622                     break;
623                 case SP_STROKE_LINEJOIN_ROUND:
624                     o_join = join_round;
625                     break;
626                 default:
627                     o_join = join_straight;
628                     break;
629             }
631             switch (captype) {
632                 case SP_STROKE_LINECAP_SQUARE:
633                     o_butt = butt_square;
634                     break;
635                 case SP_STROKE_LINECAP_ROUND:
636                     o_butt = butt_round;
637                     break;
638                 default:
639                     o_butt = butt_straight;
640                     break;
641             }
643             if (o_width < 0.1)
644                 o_width = 0.1;
645             o_miter = i_style->stroke_miterlimit.value * o_width;
646         }
648         Path *orig = Path_for_item(item, false);
649         if (orig == NULL) {
650             g_free(style);
651             sp_curve_unref(curve);
652             continue;
653         }
655         Path *res = new Path;
656         res->SetBackData(false);
659         {
660             orig->Outline(res, 0.5 * o_width, o_join, o_butt, 0.5 * o_miter);
662             orig->Coalesce(0.5 * o_width);
664             Shape *theShape = new Shape;
665             Shape *theRes = new Shape;
667             res->ConvertWithBackData(1.0);
668             res->Fill(theShape, 0);
669             theRes->ConvertToShape(theShape, fill_positive);
671             Path *originaux[1];
672             originaux[0] = res;
673             theRes->ConvertToForme(orig, 1, originaux);
675             delete theShape;
676             delete theRes;
677         }
679         if (orig->descr_cmd.size() <= 1) {
680             // ca a merd\8e, ou bien le resultat est vide
681             delete res;
682             delete orig;
683             g_free(style);
684             continue;
685         }
687         did = true;
689         sp_curve_unref(curve);
690         // remember the position of the item
691         gint pos = SP_OBJECT_REPR(item)->position();
692         // remember parent
693         Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
694         // remember id
695         char const *id = SP_OBJECT_REPR(item)->attribute("id");
697         selection->remove(item);
698         SP_OBJECT(item)->deleteObject(false);
700         if (res->descr_cmd.size() > 1) { // if there's 0 or 1 node left, drop this path altogether
702             Inkscape::XML::Node *repr = sp_repr_new("svg:path");
704             // restore old style
705             repr->setAttribute("style", style);
707             // set old stroke style on fill
708             sp_repr_css_change(repr, ncss, "style");
710             sp_repr_css_attr_unref(ncss);
712             gchar *str = orig->svg_dump_path();
713             repr->setAttribute("d", str);
714             g_free(str);
716             // add the new repr to the parent
717             parent->appendChild(repr);
719             // move to the saved position
720             repr->setPosition(pos > 0 ? pos : 0);
722             repr->setAttribute("id", id);
724             SPItem *newitem = (SPItem *) SP_DT_DOCUMENT(desktop)->getObjectByRepr(repr);
725             sp_item_write_transform(newitem, repr, transform);
727             selection->add(repr);
729             Inkscape::GC::release(repr);
730         }
732         delete res;
733         delete orig;
734         g_free(style);
736     }
738     if (did) {
739         sp_document_done(SP_DT_DOCUMENT(desktop));
740     } else {
741         // TRANSLATORS: "to outline" means "to convert stroke to path"
742         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No stroked paths</b> to outline in the selection."));
743         return;
744     }
748 void
749 sp_selected_path_offset()
751     double prefOffset = prefs_get_double_attribute("options.defaultoffsetwidth", "value", 1.0);
753     sp_selected_path_do_offset(true, prefOffset);
755 void
756 sp_selected_path_inset()
758     double prefOffset = prefs_get_double_attribute("options.defaultoffsetwidth", "value", 1.0);
760     sp_selected_path_do_offset(false, prefOffset);
763 void
764 sp_selected_path_offset_screen(double pixels)
766     sp_selected_path_do_offset(true,  pixels / SP_ACTIVE_DESKTOP->current_zoom());
769 void
770 sp_selected_path_inset_screen(double pixels)
772     sp_selected_path_do_offset(false,  pixels / SP_ACTIVE_DESKTOP->current_zoom());
776 void sp_selected_path_create_offset_object_zero()
778     sp_selected_path_create_offset_object(0, false);
781 void sp_selected_path_create_offset()
783     sp_selected_path_create_offset_object(1, false);
785 void sp_selected_path_create_inset()
787     sp_selected_path_create_offset_object(-1, false);
790 void sp_selected_path_create_updating_offset_object_zero()
792     sp_selected_path_create_offset_object(0, true);
795 void sp_selected_path_create_updating_offset()
797     sp_selected_path_create_offset_object(1, true);
799 void sp_selected_path_create_updating_inset()
801     sp_selected_path_create_offset_object(-1, true);
804 void
805 sp_selected_path_create_offset_object(int expand, bool updating)
807     Inkscape::Selection *selection;
808     Inkscape::XML::Node *repr;
809     SPItem *item;
810     SPCurve *curve;
811     gchar *style, *str;
812     SPDesktop *desktop;
813     float o_width, o_miter;
814     JoinType o_join;
815     ButtType o_butt;
817     curve = NULL;
819     desktop = SP_ACTIVE_DESKTOP;
821     selection = SP_DT_SELECTION(desktop);
823     item = selection->singleItem();
825     if (item == NULL || ( !SP_IS_SHAPE(item) && !SP_IS_TEXT(item) ) ) {
826         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Selected object is <b>not a path</b>, cannot inset/outset."));
827         return;
828     }
829     if (SP_IS_SHAPE(item))
830     {
831         curve = sp_shape_get_curve(SP_SHAPE(item));
832         if (curve == NULL)
833             return;
834     }
835     if (SP_IS_TEXT(item))
836     {
837         curve = SP_TEXT(item)->getNormalizedBpath();
838         if (curve == NULL)
839             return;
840     }
842     NR::Matrix const transform(item->transform);
844     sp_item_write_transform(item, SP_OBJECT_REPR(item), NR::identity());
846     style = g_strdup(SP_OBJECT(item)->repr->attribute("style"));
848     // remember the position of the item
849     gint pos = SP_OBJECT_REPR(item)->position();
850     // remember parent
851     Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
853     {
854         SPStyle *i_style = SP_OBJECT(item)->style;
855         int jointype, captype;
857         jointype = i_style->stroke_linejoin.value;
858         captype = i_style->stroke_linecap.value;
859         o_width = i_style->stroke_width.computed;
860         if (jointype == SP_STROKE_LINEJOIN_MITER)
861         {
862             o_join = join_pointy;
863         }
864         else if (jointype == SP_STROKE_LINEJOIN_ROUND)
865         {
866             o_join = join_round;
867         }
868         else
869         {
870             o_join = join_straight;
871         }
872         if (captype == SP_STROKE_LINECAP_SQUARE)
873         {
874             o_butt = butt_square;
875         }
876         else if (captype == SP_STROKE_LINECAP_ROUND)
877         {
878             o_butt = butt_round;
879         }
880         else
881         {
882             o_butt = butt_straight;
883         }
885         {
886             double prefOffset = 1.0;
887             prefOffset = prefs_get_double_attribute("options.defaultoffsetwidth", "value", prefOffset);
888             o_width = prefOffset;
889         }
891         if (o_width < 0.01)
892             o_width = 0.01;
893         o_miter = i_style->stroke_miterlimit.value * o_width;
894     }
896     Path *orig = Path_for_item(item, true, false);
897     if (orig == NULL)
898     {
899         g_free(style);
900         sp_curve_unref(curve);
901         return;
902     }
904     Path *res = new Path;
905     res->SetBackData(false);
907     {
908         Shape *theShape = new Shape;
909         Shape *theRes = new Shape;
911         orig->ConvertWithBackData(1.0);
912         orig->Fill(theShape, 0);
914         SPCSSAttr *css = sp_repr_css_attr(SP_OBJECT_REPR(item), "style");
915         gchar const *val = sp_repr_css_property(css, "fill-rule", NULL);
916         if (val && strcmp(val, "nonzero") == 0)
917         {
918             theRes->ConvertToShape(theShape, fill_nonZero);
919         }
920         else if (val && strcmp(val, "evenodd") == 0)
921         {
922             theRes->ConvertToShape(theShape, fill_oddEven);
923         }
924         else
925         {
926             theRes->ConvertToShape(theShape, fill_nonZero);
927         }
929         Path *originaux[1];
930         originaux[0] = orig;
931         theRes->ConvertToForme(res, 1, originaux);
933         delete theShape;
934         delete theRes;
935     }
937     sp_curve_unref(curve);
939     if (res->descr_cmd.size() <= 1)
940     {
941         // pas vraiment de points sur le resultat
942         // donc il ne reste rien
943         sp_document_done(SP_DT_DOCUMENT(desktop));
944         selection->clear();
946         delete res;
947         delete orig;
948         g_free(style);
949         return;
950     }
952     {
953         gchar tstr[80];
955         tstr[79] = '\0';
957         repr = sp_repr_new("svg:path");
958         repr->setAttribute("sodipodi:type", "inkscape:offset");
959         sp_repr_set_svg_double(repr, "inkscape:radius", ( expand > 0
960                                                           ? o_width
961                                                           : expand < 0
962                                                           ? -o_width
963                                                           : 0 ));
965         str = res->svg_dump_path();
966         repr->setAttribute("inkscape:original", str);
967         g_free(str);
969         if ( updating ) {
970             char const *id = SP_OBJECT(item)->repr->attribute("id");
971             char const *uri = g_strdup_printf("#%s", id);
972             repr->setAttribute("xlink:href", uri);
973             g_free((void *) uri);
974         } else {
975             repr->setAttribute("inkscape:href", NULL);
976         }
978         repr->setAttribute("style", style);
980         // add the new repr to the parent
981         parent->appendChild(repr);
983         // move to the saved position
984         repr->setPosition(pos > 0 ? pos : 0);
986         SPItem *nitem = (SPItem *) SP_DT_DOCUMENT(desktop)->getObjectByRepr(repr);
988         if ( updating ) {
989             // on conserve l'original
990             // we reapply the transform to the original (offset will feel it)
991             sp_item_write_transform(item, SP_OBJECT_REPR(item), transform);
992         } else {
993             // delete original, apply the transform to the offset
994             SP_OBJECT(item)->deleteObject(false);
995             sp_item_write_transform(nitem, repr, transform);
996         }
998         // The object just created from a temporary repr is only a seed.
999         // We need to invoke its write which will update its real repr (in particular adding d=)
1000         SP_OBJECT(nitem)->updateRepr();
1002         Inkscape::GC::release(repr);
1004         selection->set(nitem);
1005     }
1007     sp_document_done(SP_DT_DOCUMENT(desktop));
1009     delete res;
1010     delete orig;
1012     g_free(style);
1026 void
1027 sp_selected_path_do_offset(bool expand, double prefOffset)
1029     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1031     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
1033     if (selection->isEmpty()) {
1034         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to inset/outset."));
1035         return;
1036     }
1038     bool did = false;
1040     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
1041          items != NULL;
1042          items = items->next) {
1044         SPItem *item = (SPItem *) items->data;
1046         if (!SP_IS_SHAPE(item) && !SP_IS_TEXT(item))
1047             continue;
1049         SPCurve *curve = NULL;
1050         if (SP_IS_SHAPE(item)) {
1051             curve = sp_shape_get_curve(SP_SHAPE(item));
1052             if (curve == NULL)
1053                 continue;
1054         }
1055         if (SP_IS_TEXT(item)) {
1056             curve = SP_TEXT(item)->getNormalizedBpath();
1057             if (curve == NULL)
1058                 continue;
1059         }
1061         NR::Matrix const transform(item->transform);
1063         sp_item_write_transform(item, SP_OBJECT_REPR(item), NR::identity());
1065         gchar *style = g_strdup(SP_OBJECT_REPR(item)->attribute("style"));
1067         float o_width, o_miter;
1068         JoinType o_join;
1069         ButtType o_butt;
1071         {
1072             SPStyle *i_style = SP_OBJECT(item)->style;
1073             int jointype, captype;
1075             jointype = i_style->stroke_linejoin.value;
1076             captype = i_style->stroke_linecap.value;
1077             o_width = i_style->stroke_width.computed;
1079             switch (jointype) {
1080                 case SP_STROKE_LINEJOIN_MITER:
1081                     o_join = join_pointy;
1082                     break;
1083                 case SP_STROKE_LINEJOIN_ROUND:
1084                     o_join = join_round;
1085                     break;
1086                 default:
1087                     o_join = join_straight;
1088                     break;
1089             }
1091             switch (captype) {
1092                 case SP_STROKE_LINECAP_SQUARE:
1093                     o_butt = butt_square;
1094                     break;
1095                 case SP_STROKE_LINECAP_ROUND:
1096                     o_butt = butt_round;
1097                     break;
1098                 default:
1099                     o_butt = butt_straight;
1100                     break;
1101             }
1103             o_width = prefOffset;
1105             if (o_width < 0.1)
1106                 o_width = 0.1;
1107             o_miter = i_style->stroke_miterlimit.value * o_width;
1108         }
1110         Path *orig = Path_for_item(item, false);
1111         if (orig == NULL) {
1112             g_free(style);
1113             sp_curve_unref(curve);
1114             continue;
1115         }
1117         Path *res = new Path;
1118         res->SetBackData(false);
1120         {
1121             Shape *theShape = new Shape;
1122             Shape *theRes = new Shape;
1124             orig->ConvertWithBackData(0.03);
1125             orig->Fill(theShape, 0);
1127             SPCSSAttr *css = sp_repr_css_attr(SP_OBJECT_REPR(item), "style");
1128             gchar const *val = sp_repr_css_property(css, "fill-rule", NULL);
1129             if (val && strcmp(val, "nonzero") == 0)
1130             {
1131                 theRes->ConvertToShape(theShape, fill_nonZero);
1132             }
1133             else if (val && strcmp(val, "evenodd") == 0)
1134             {
1135                 theRes->ConvertToShape(theShape, fill_oddEven);
1136             }
1137             else
1138             {
1139                 theRes->ConvertToShape(theShape, fill_nonZero);
1140             }
1142             // et maintenant: offset
1143             // methode inexacte
1144 /*                      Path *originaux[1];
1145                         originaux[0] = orig;
1146                         theRes->ConvertToForme(res, 1, originaux);
1148                         if (expand) {
1149                         res->OutsideOutline(orig, 0.5 * o_width, o_join, o_butt, o_miter);
1150                         } else {
1151                         res->OutsideOutline(orig, -0.5 * o_width, o_join, o_butt, o_miter);
1152                         }
1154                         orig->ConvertWithBackData(1.0);
1155                         orig->Fill(theShape, 0);
1156                         theRes->ConvertToShape(theShape, fill_positive);
1157                         originaux[0] = orig;
1158                         theRes->ConvertToForme(res, 1, originaux);
1160                         if (o_width >= 0.5) {
1161                         //     res->Coalesce(1.0);
1162                         res->ConvertEvenLines(1.0);
1163                         res->Simplify(1.0);
1164                         } else {
1165                         //      res->Coalesce(o_width);
1166                         res->ConvertEvenLines(1.0*o_width);
1167                         res->Simplify(1.0 * o_width);
1168                         }    */
1169             // methode par makeoffset
1171             if (expand)
1172             {
1173                 theShape->MakeOffset(theRes, o_width, o_join, o_miter);
1174             }
1175             else
1176             {
1177                 theShape->MakeOffset(theRes, -o_width, o_join, o_miter);
1178             }
1179             theRes->ConvertToShape(theShape, fill_positive);
1181             res->Reset();
1182             theRes->ConvertToForme(res);
1184             if (o_width >= 1.0)
1185             {
1186                 res->ConvertEvenLines(1.0);
1187                 res->Simplify(1.0);
1188             }
1189             else
1190             {
1191                 res->ConvertEvenLines(1.0*o_width);
1192                 res->Simplify(1.0 * o_width);
1193             }
1195             delete theShape;
1196             delete theRes;
1197         }
1199         did = true;
1201         sp_curve_unref(curve);
1202         // remember the position of the item
1203         gint pos = SP_OBJECT_REPR(item)->position();
1204         // remember parent
1205         Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
1206         // remember id
1207         char const *id = SP_OBJECT_REPR(item)->attribute("id");
1209         selection->remove(item);
1210         SP_OBJECT(item)->deleteObject(false);
1212         if (res->descr_cmd.size() > 1) { // if there's 0 or 1 node left, drop this path altogether
1214             gchar tstr[80];
1216             tstr[79] = '\0';
1218             Inkscape::XML::Node *repr = sp_repr_new("svg:path");
1220             repr->setAttribute("style", style);
1222             gchar *str = res->svg_dump_path();
1223             repr->setAttribute("d", str);
1224             g_free(str);
1226             // add the new repr to the parent
1227             parent->appendChild(repr);
1229             // move to the saved position
1230             repr->setPosition(pos > 0 ? pos : 0);
1232             SPItem *newitem = (SPItem *) SP_DT_DOCUMENT(desktop)->getObjectByRepr(repr);
1234             // reapply the transform
1235             sp_item_write_transform(newitem, repr, transform);
1237             repr->setAttribute("id", id);
1239             selection->add(repr);
1241             Inkscape::GC::release(repr);
1242         }
1244         delete orig;
1245         delete res;
1246     }
1248     if (did) {
1249         sp_document_done(SP_DT_DOCUMENT(desktop));
1250     } else {
1251         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to inset/outset in the selection."));
1252         return;
1253     }
1258 //return true if we changed something, else false
1259 bool
1260 sp_selected_path_simplify_item(SPDesktop *desktop, Inkscape::Selection *selection, SPItem *item,
1261                  float threshold,  bool justCoalesce,
1262                  float angleLimit, bool breakableAngles,
1263                  gdouble size,     bool modifySelection)
1265     if (!(SP_IS_GROUP(item) || SP_IS_SHAPE(item) || SP_IS_TEXT(item)))
1266         return false;
1268     //If this is a group, do the children instead
1269     if (SP_IS_GROUP(item)) {
1271         bool didSomething = false;
1273         for ( GSList *children = sp_item_group_item_list(SP_GROUP(item));
1274                  children  ; children = children->next) {
1276             SPItem *child = (SPItem *) children->data;
1277             didSomething |= sp_selected_path_simplify_item(desktop, selection, child, threshold, justCoalesce,
1278                    angleLimit, breakableAngles, size, false);
1279         }
1281         return didSomething;
1282     }
1285     SPCurve *curve = NULL;
1287     if (SP_IS_SHAPE(item)) {
1288         curve = sp_shape_get_curve(SP_SHAPE(item));
1289         if (!curve)
1290             return false;
1291     }
1293     if (SP_IS_TEXT(item)) {
1294         curve = SP_TEXT(item)->getNormalizedBpath();
1295         if (!curve)
1296             return false;
1297     }
1299     // save the transform, to re-apply it after simplification
1300     NR::Matrix const transform(item->transform);
1302     /*
1303        reset the transform, effectively transforming the item by transform.inverse();
1304        this is necessary so that the item is transformed twice back and forth,
1305        allowing all compensations to cancel out regardless of the preferences
1306     */
1307     sp_item_write_transform(item, SP_OBJECT_REPR(item), NR::identity());
1309     gchar *style = g_strdup(SP_OBJECT_REPR(item)->attribute("style"));
1311     Path *orig = Path_for_item(item, false);
1312     if (orig == NULL) {
1313         g_free(style);
1314         sp_curve_unref(curve);
1315         return false;
1316     }
1318     sp_curve_unref(curve);
1319     // remember the position of the item
1320     gint pos = SP_OBJECT_REPR(item)->position();
1321     // remember parent
1322     Inkscape::XML::Node *parent = SP_OBJECT_REPR(item)->parent();
1323     // remember id
1324     char const *id = SP_OBJECT_REPR(item)->attribute("id");
1326     //If a group was selected, to not change the selection list
1327     if (modifySelection)
1328         selection->remove(item);
1330     SP_OBJECT(item)->deleteObject(false);
1332     if ( justCoalesce ) {
1333         orig->Coalesce(threshold * size);
1334     } else {
1335         orig->ConvertEvenLines(threshold * size);
1336         orig->Simplify(threshold * size);
1337     }
1339     Inkscape::XML::Node *repr = sp_repr_new("svg:path");
1341     repr->setAttribute("style", style);
1343     gchar *str = orig->svg_dump_path();
1344     repr->setAttribute("d", str);
1345     g_free(str);
1347     // restore id
1348     repr->setAttribute("id", id);
1350     // add the new repr to the parent
1351     parent->appendChild(repr);
1353     // move to the saved position
1354     repr->setPosition(pos > 0 ? pos : 0);
1356     SPItem *newitem = (SPItem *) SP_DT_DOCUMENT(desktop)->getObjectByRepr(repr);
1358     // reapply the transform
1359     sp_item_write_transform(newitem, repr, transform);
1361     //If we are not in a selected group
1362     if (modifySelection)
1363         selection->add(repr);
1365     Inkscape::GC::release(repr);
1367     // clean up
1368     if (orig) delete orig;
1370     return true;
1374 void
1375 sp_selected_path_simplify_selection(float threshold, bool justCoalesce,
1376                        float angleLimit, bool breakableAngles)
1378     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1380     Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
1382     if (selection->isEmpty()) {
1383         desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE,
1384                          _("Select <b>path(s)</b> to simplify."));
1385         return;
1386     }
1388     // remember selection size
1389     NR::Rect bbox = selection->bounds();
1390     gdouble size  = L2(bbox.dimensions());
1392     bool didSomething = false;
1394     //Loop through all of the items in the selection
1395     for (GSList *items = g_slist_copy((GSList *) selection->itemList());
1396                         items != NULL; items = items->next) {
1398         SPItem *item = (SPItem *) items->data;
1400         if (!(SP_IS_GROUP(item) || SP_IS_SHAPE(item) || SP_IS_TEXT(item)))
1401             continue;
1403         didSomething |= sp_selected_path_simplify_item(desktop, selection, item,
1404                            threshold, justCoalesce, angleLimit, breakableAngles, size, true);
1405     }
1408     if (didSomething)
1409         sp_document_done(SP_DT_DOCUMENT(desktop));
1410     else
1411         desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to simplify in the selection."));
1416 // globals for keeping track of accelerated simplify
1417 static double previousTime      = 0.0;
1418 static gdouble simplifyMultiply = 1.0;
1420 void
1421 sp_selected_path_simplify(void)
1423     gdouble simplifyThreshold =
1424         prefs_get_double_attribute("options.simplifythreshold", "value", 0.003);
1425     bool simplifyJustCoalesce =
1426         (bool) prefs_get_int_attribute("options.simplifyjustcoalesce", "value", 0);
1428     //Get the current time
1429     GTimeVal currentTimeVal;
1430     g_get_current_time(&currentTimeVal);
1431     double currentTime = currentTimeVal.tv_sec * 1000000 +
1432                 currentTimeVal.tv_usec;
1434     //Was the previous call to this function recent? (<0.5 sec)
1435     if (previousTime > 0.0 && currentTime - previousTime < 500000.0) {
1437         // add to the threshold 1/2 of its original value
1438         simplifyMultiply  += 0.5;
1439         simplifyThreshold *= simplifyMultiply;
1441     } else {
1442         // reset to the default
1443         simplifyMultiply = 1;
1444     }
1446     //remember time for next call
1447     previousTime = currentTime;
1449     //g_print("%g\n", simplify_threshold);
1451     //Make the actual call
1452     sp_selected_path_simplify_selection(simplifyThreshold,
1453                       simplifyJustCoalesce, 0.0, false);
1458 // fonctions utilitaires
1460 bool
1461 Ancetre(Inkscape::XML::Node *a, Inkscape::XML::Node *who)
1463     if (who == NULL || a == NULL)
1464         return false;
1465     if (who == a)
1466         return true;
1467     return Ancetre(sp_repr_parent(a), who);
1470 Path *
1471 Path_for_item(SPItem *item, bool doTransformation, bool transformFull)
1473     SPCurve *curve;
1475     if (!item)
1476         return NULL;
1478     if (SP_IS_SHAPE(item))
1479     {
1480         curve = sp_shape_get_curve(SP_SHAPE(item));
1481     }
1482     else if (SP_IS_TEXT(item))
1483     {
1484         curve = SP_TEXT(item)->getNormalizedBpath();
1485     }
1486     else
1487     {
1488         curve = NULL;
1489     }
1491     if (!curve)
1492         return NULL;
1493     NArtBpath *bpath = curve->bpath;
1494     if (bpath == NULL)
1495         return NULL;
1497     if ( doTransformation ) {
1498         if (transformFull)
1499             bpath = nr_artpath_affine(curve->bpath, sp_item_i2doc_affine(item));
1500         else
1501             bpath = nr_artpath_affine(curve->bpath, item->transform);
1502         sp_curve_unref(curve);
1503         curve=NULL;
1504     } else {
1505         bpath=curve->bpath;
1506     }
1508     Path *dest = new Path;
1509     dest->SetBackData(false);
1510     {
1511         int   i;
1512         bool  closed = false;
1513         float lastX  = 0.0;
1514         float lastY  = 0.0;
1516         for (i = 0; bpath[i].code != NR_END; i++) {
1517             switch (bpath[i].code) {
1518                 case NR_LINETO:
1519                     lastX = bpath[i].x3;
1520                     lastY = bpath[i].y3;
1521                     {
1522                         NR::Point tmp(lastX, lastY);
1523                         dest->LineTo(tmp);
1524                     }
1525                     break;
1527                 case NR_CURVETO:
1528                 {
1529                     NR::Point tmp, tms, tme;
1530                     tmp[0]=bpath[i].x3;
1531                     tmp[1]=bpath[i].y3;
1532                     tms[0]=3 * (bpath[i].x1 - lastX);
1533                     tms[1]=3 * (bpath[i].y1 - lastY);
1534                     tme[0]=3 * (bpath[i].x3 - bpath[i].x2);
1535                     tme[1]=3 * (bpath[i].y3 - bpath[i].y2);
1536                     dest->CubicTo(tmp,tms,tme);
1537                 }
1538                 lastX = bpath[i].x3;
1539                 lastY = bpath[i].y3;
1540                 break;
1542                 case NR_MOVETO_OPEN:
1543                 case NR_MOVETO:
1544                     if (closed)
1545                         dest->Close();
1546                     closed = (bpath[i].code == NR_MOVETO);
1547                     lastX = bpath[i].x3;
1548                     lastY = bpath[i].y3;
1549                     {
1550                         NR::Point  tmp(lastX, lastY);
1551                         dest->MoveTo(tmp);
1552                     }
1553                     break;
1554                 default:
1555                     break;
1556             }
1557         }
1558         if (closed)
1559             dest->Close();
1560     }
1562     if ( doTransformation ) {
1563         if ( bpath ) nr_free(bpath);
1564     } else {
1565         sp_curve_unref(curve);
1566     }
1567     return dest;
1570 NR::Maybe<Path::cut_position> get_nearest_position_on_Path(Path *path, NR::Point p)
1572     //get nearest position on path
1573     Path::cut_position pos = path->PointToCurvilignPosition(p);
1574     return pos;
1577 NR::Point get_point_on_Path(Path *path, int piece, double t)
1579     NR::Point p;
1580     path->PointAt(piece, t, p);
1581     return p;
1585 /*
1586   Local Variables:
1587   mode:c++
1588   c-file-style:"stroustrup"
1589   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1590   indent-tabs-mode:nil
1591   fill-column:99
1592   End:
1593 */
1594 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :