~centralelyon2010/inkscape/imagelinks2

« back to all changes in this revision

Viewing changes to src/splivarot.cpp

  • Committer: mental
  • Date: 2006-01-16 02:36:01 UTC
  • Revision ID: mental@users.sourceforge.net-20060116023601-wkr0h7edl5veyudq
moving trunk for module inkscape

Show diffs side-by-side

added added

removed removed

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