Code

Fixed signed/unsigned problem with precision calc. Fixes bug #399604.
[inkscape.git] / src / sp-offset.cpp
1 #define __SP_OFFSET_C__
3 /** \file
4  * Implementation of <path sodipodi:type="inkscape:offset">.
5  */
7 /*
8  * Authors: (of the sp-spiral.c upon which this file was constructed):
9  *   Mitsuru Oka <oka326@parkcity.ne.jp>
10  *   Lauris Kaplinski <lauris@kaplinski.com>
11  *
12  * Copyright (C) 1999-2002 Lauris Kaplinski
13  * Copyright (C) 2000-2001 Ximian, Inc.
14  *
15  * Released under GNU GPL, read the file 'COPYING' for more information
16  */
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
22 #include <cstring>
23 #include <string>
25 #include "svg/svg.h"
26 #include "attributes.h"
27 #include "display/curve.h"
28 #include <glibmm/i18n.h>
30 #include "livarot/Path.h"
31 #include "livarot/Shape.h"
33 #include "enums.h"
34 #include "preferences.h"
35 #include "sp-text.h"
36 #include "sp-offset.h"
37 #include "sp-use-reference.h"
38 #include "uri.h"
40 #include <libnr/nr-matrix-fns.h>
41 #include <2geom/pathvector.h>
43 #include "xml/repr.h"
45 class SPDocument;
47 #define noOFFSET_VERBOSE
49 /** \note
50  * SPOffset is a derivative of SPShape, much like the SPSpiral or SPRect.
51  * The goal is to have a source shape (= originalPath), an offset (= radius)
52  * and compute the offset of the source by the radius. To get it to work,
53  * one needs to know what the source is and what the radius is, and how it's
54  * stored in the xml representation. The object itself is a "path" element,
55  * to get lots of shape functionality for free. The source is the easy part:
56  * it's stored in a "inkscape:original" attribute in the path. In case of
57  * "linked" offset, as they've been dubbed, there is an additional
58  * "inkscape:href" that contains the id of an element of the svg.
59  * When built, the object will attach a listener vector to that object and
60  * rebuild the "inkscape:original" whenever the href'd object changes. This
61  * is of course grossly inefficient, and also does not react to changes
62  * to the href'd during context stuff (like changing the shape of a star by
63  * dragging control points) unless the path of that object is changed during
64  * the context (seems to be the case for SPEllipse). The computation of the
65  * offset is done in sp_offset_set_shape(), a function that is called whenever
66  * a change occurs to the offset (change of source or change of radius).
67  * just like the sp-star and other, this path derivative can make control
68  * points, or more precisely one control point, that's enough to define the
69  * radius (look in object-edit).
70  */
72 static void sp_offset_class_init (SPOffsetClass * klass);
73 static void sp_offset_init (SPOffset * offset);
74 static void sp_offset_finalize(GObject *obj);
76 static void sp_offset_build (SPObject * object, SPDocument * document,
77                              Inkscape::XML::Node * repr);
78 static Inkscape::XML::Node *sp_offset_write (SPObject * object, Inkscape::XML::Document *doc, Inkscape::XML::Node * repr,
79                                 guint flags);
80 static void sp_offset_set (SPObject * object, unsigned int key,
81                            const gchar * value);
82 static void sp_offset_update (SPObject * object, SPCtx * ctx, guint flags);
83 static void sp_offset_release (SPObject * object);
85 static gchar *sp_offset_description (SPItem * item);
86 static void sp_offset_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs);
87 static void sp_offset_set_shape (SPShape * shape);
89 static void refresh_offset_source(SPOffset* offset);
91 static void sp_offset_start_listening(SPOffset *offset,SPObject* to);
92 static void sp_offset_quit_listening(SPOffset *offset);
93 static void sp_offset_href_changed(SPObject *old_ref, SPObject *ref, SPOffset *offset);
94 static void sp_offset_move_compensate(Geom::Matrix const *mp, SPItem *original, SPOffset *self);
95 static void sp_offset_delete_self(SPObject *deleted, SPOffset *self);
96 static void sp_offset_source_modified (SPObject *iSource, guint flags, SPItem *item);
99 // slow= source path->polygon->offset of polygon->polygon->path
100 // fast= source path->offset of source path->polygon->path
101 // fast is not mathematically correct, because computing the offset of a single
102 // cubic bezier patch is not trivial; in particular, there are problems with holes
103 // reappearing in offset when the radius becomes too large
104 static bool   use_slow_but_correct_offset_method=false;
107 // nothing special here, same for every class in sodipodi/inkscape
108 static SPShapeClass *parent_class;
110 /**
111  * Register SPOffset class and return its type number.
112  */
113 GType
114 sp_offset_get_type (void)
116     static GType offset_type = 0;
118     if (!offset_type)
119     {
120         GTypeInfo offset_info = {
121             sizeof (SPOffsetClass),
122             NULL,                       /* base_init */
123             NULL,                       /* base_finalize */
124             (GClassInitFunc) sp_offset_class_init,
125             NULL,                       /* class_finalize */
126             NULL,                       /* class_data */
127             sizeof (SPOffset),
128             16,                 /* n_preallocs */
129             (GInstanceInitFunc) sp_offset_init,
130             NULL,                       /* value_table */
131         };
132         offset_type =
133             g_type_register_static (SP_TYPE_SHAPE, "SPOffset", &offset_info,
134                                     (GTypeFlags) 0);
135     }
136     return offset_type;
139 /**
140  * SPOffset vtable initialization.
141  */
142 static void
143 sp_offset_class_init(SPOffsetClass *klass)
145     GObjectClass  *gobject_class = (GObjectClass *) klass;
146     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
147     SPItemClass   *item_class = (SPItemClass *) klass;
148     SPShapeClass  *shape_class = (SPShapeClass *) klass;
150     parent_class = (SPShapeClass *) g_type_class_ref (SP_TYPE_SHAPE);
152     gobject_class->finalize = sp_offset_finalize;
154     sp_object_class->build = sp_offset_build;
155     sp_object_class->write = sp_offset_write;
156     sp_object_class->set = sp_offset_set;
157     sp_object_class->update = sp_offset_update;
158     sp_object_class->release = sp_offset_release;
160     item_class->description = sp_offset_description;
161     item_class->snappoints = sp_offset_snappoints;
163     shape_class->set_shape = sp_offset_set_shape;
166 /**
167  * Callback for SPOffset object initialization.
168  */
169 static void
170 sp_offset_init(SPOffset *offset)
172     offset->rad = 1.0;
173     offset->original = NULL;
174     offset->originalPath = NULL;
175     offset->knotSet = false;
176     offset->sourceDirty=false;
177     offset->isUpdating=false;
178     // init various connections
179     offset->sourceHref = NULL;
180     offset->sourceRepr = NULL;
181     offset->sourceObject = NULL;
182     new (&offset->_modified_connection) sigc::connection();
183     new (&offset->_delete_connection) sigc::connection();
184     new (&offset->_changed_connection) sigc::connection();
185     new (&offset->_transformed_connection) sigc::connection();
186     // set up the uri reference
187     offset->sourceRef = new SPUseReference(SP_OBJECT(offset));
188     offset->_changed_connection = offset->sourceRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_offset_href_changed), offset));
191 /**
192  * Callback for SPOffset finalization.
193  */
194 static void
195 sp_offset_finalize(GObject *obj)
197     SPOffset *offset = (SPOffset *) obj;
199     delete offset->sourceRef;
201     offset->_modified_connection.disconnect();
202     offset->_modified_connection.~connection();
203     offset->_delete_connection.disconnect();
204     offset->_delete_connection.~connection();
205     offset->_changed_connection.disconnect();
206     offset->_changed_connection.~connection();
207     offset->_transformed_connection.disconnect();
208     offset->_transformed_connection.~connection();
211 /**
212  * Virtual build: set offset attributes from corresponding repr.
213  */
214 static void
215 sp_offset_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
217     if (((SPObjectClass *) parent_class)->build)
218         ((SPObjectClass *) parent_class)->build (object, document, repr);
220     if (object->repr->attribute("inkscape:radius")) {
221         sp_object_read_attr (object, "inkscape:radius");
222     } else {
223         gchar const *oldA = object->repr->attribute("sodipodi:radius");
224         object->repr->setAttribute("inkscape:radius",oldA);
225         object->repr->setAttribute("sodipodi:radius",NULL);
227         sp_object_read_attr (object, "inkscape:radius");
228     }
229     if (object->repr->attribute("inkscape:original")) {
230         sp_object_read_attr (object, "inkscape:original");
231     } else {
232         gchar const *oldA = object->repr->attribute("sodipodi:original");
233         object->repr->setAttribute("inkscape:original",oldA);
234         object->repr->setAttribute("sodipodi:original",NULL);
236         sp_object_read_attr (object, "inkscape:original");
237     }
238     if (object->repr->attribute("xlink:href")) {
239         sp_object_read_attr(object, "xlink:href");
240     } else {
241         gchar const *oldA = object->repr->attribute("inkscape:href");
242         if (oldA) {
243             size_t lA = strlen(oldA);
244             char *nA=(char*)malloc((1+lA+1)*sizeof(char));
245             memcpy(nA+1,oldA,lA*sizeof(char));
246             nA[0]='#';
247             nA[lA+1]=0;
248             object->repr->setAttribute("xlink:href",nA);
249             free(nA);
250             object->repr->setAttribute("inkscape:href",NULL);
251         }
252         sp_object_read_attr (object, "xlink:href");
253     }
256 /**
257  * Virtual write: write offset attributes to corresponding repr.
258  */
259 static Inkscape::XML::Node *
260 sp_offset_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
262     SPOffset *offset = SP_OFFSET (object);
264     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
265         repr = xml_doc->createElement("svg:path");
266     }
268     if (flags & SP_OBJECT_WRITE_EXT) {
269         /** \todo
270          * Fixme: we may replace these attributes by
271          * inkscape:offset="cx cy exp revo rad arg t0"
272          */
273         repr->setAttribute("sodipodi:type", "inkscape:offset");
274         sp_repr_set_svg_double(repr, "inkscape:radius", offset->rad);
275         repr->setAttribute("inkscape:original", offset->original);
276         repr->setAttribute("inkscape:href", offset->sourceHref);
277     }
280     // Make sure the object has curve
281     SPCurve *curve = sp_shape_get_curve (SP_SHAPE (offset));
282     if (curve == NULL) {
283         sp_offset_set_shape (SP_SHAPE (offset));
284     }
286     // write that curve to "d"
287     char *d = sp_svg_write_path (((SPShape *) offset)->curve->get_pathvector());
288     repr->setAttribute("d", d);
289     g_free (d);
291     if (((SPObjectClass *) (parent_class))->write)
292         ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr,
293                                                    flags | SP_SHAPE_WRITE_PATH);
295     return repr;
298 /**
299  * Virtual release callback.
300  */
301 static void
302 sp_offset_release(SPObject *object)
304     SPOffset *offset = (SPOffset *) object;
306     if (offset->original) free (offset->original);
307     if (offset->originalPath) delete ((Path *) offset->originalPath);
308     offset->original = NULL;
309     offset->originalPath = NULL;
311     sp_offset_quit_listening(offset);
313     offset->_changed_connection.disconnect();
314     g_free(offset->sourceHref);
315     offset->sourceHref = NULL;
316     offset->sourceRef->detach();
318     if (((SPObjectClass *) parent_class)->release) {
319         ((SPObjectClass *) parent_class)->release (object);
320     }
324 /**
325  * Set callback: the function that is called whenever a change is made to
326  * the description of the object.
327  */
328 static void
329 sp_offset_set(SPObject *object, unsigned key, gchar const *value)
331     SPOffset *offset = SP_OFFSET (object);
333     if ( offset->sourceDirty ) refresh_offset_source(offset);
335     /* fixme: we should really collect updates */
336     switch (key)
337     {
338         case SP_ATTR_INKSCAPE_ORIGINAL:
339         case SP_ATTR_SODIPODI_ORIGINAL:
340             if (value == NULL) {
341             } else {
342                 if (offset->original) {
343                     free (offset->original);
344                     delete ((Path *) offset->originalPath);
345                     offset->original = NULL;
346                     offset->originalPath = NULL;
347                 }
349                 offset->original = strdup (value);
351                 Geom::PathVector pv = sp_svg_read_pathv(offset->original);
352                 offset->originalPath = new Path;
353                 reinterpret_cast<Path *>(offset->originalPath)->LoadPathVector(pv);
355                 offset->knotSet = false;
356                 if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
357             }
358             break;
359         case SP_ATTR_INKSCAPE_RADIUS:
360         case SP_ATTR_SODIPODI_RADIUS:
361             if (!sp_svg_length_read_computed_absolute (value, &offset->rad)) {
362                 if (fabs (offset->rad) < 0.01)
363                     offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
364                 offset->knotSet = false; // knotset=false because it's not set from the context
365             }
366             if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
367             break;
368         case SP_ATTR_INKSCAPE_HREF:
369         case SP_ATTR_XLINK_HREF:
370             if ( value == NULL ) {
371                 sp_offset_quit_listening(offset);
372                 if ( offset->sourceHref ) g_free(offset->sourceHref);
373                 offset->sourceHref = NULL;
374                 offset->sourceRef->detach();
375             } else {
376                 if ( offset->sourceHref && ( strcmp(value, offset->sourceHref) == 0 ) ) {
377                 } else {
378                     if ( offset->sourceHref ) g_free(offset->sourceHref);
379                     offset->sourceHref = g_strdup(value);
380                     try {
381                         offset->sourceRef->attach(Inkscape::URI(value));
382                     } catch (Inkscape::BadURIException &e) {
383                         g_warning("%s", e.what());
384                         offset->sourceRef->detach();
385                     }
386                 }
387             }
388             break;
389         default:
390             if (((SPObjectClass *) parent_class)->set)
391                 ((SPObjectClass *) parent_class)->set (object, key, value);
392             break;
393     }
396 /**
397  * Update callback: the object has changed, recompute its shape.
398  */
399 static void
400 sp_offset_update(SPObject *object, SPCtx *ctx, guint flags)
402     SPOffset* offset = SP_OFFSET(object);
403     offset->isUpdating=true; // prevent sp_offset_set from requesting updates
404     if ( offset->sourceDirty ) refresh_offset_source(offset);
405     if (flags &
406         (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG |
407          SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
408         sp_shape_set_shape ((SPShape *) object);
409     }
410     offset->isUpdating=false;
412     if (((SPObjectClass *) parent_class)->update)
413         ((SPObjectClass *) parent_class)->update (object, ctx, flags);
416 /**
417  * Returns a textual description of object.
418  */
419 static gchar *
420 sp_offset_description(SPItem *item)
422     SPOffset *offset = SP_OFFSET (item);
424     if ( offset->sourceHref ) {
425         // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
426         return g_strdup_printf(_("<b>Linked offset</b>, %s by %f pt"),
427                                (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad));
428     } else {
429         // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
430         return g_strdup_printf(_("<b>Dynamic offset</b>, %s by %f pt"),
431                                (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad));
432     }
435 /**
436  * Compute and set shape's offset.
437  */
438 static void
439 sp_offset_set_shape(SPShape *shape)
441     SPOffset *offset = SP_OFFSET (shape);
443     if ( offset->originalPath == NULL ) {
444         // oops : no path?! (the offset object should do harakiri)
445         return;
446     }
447 #ifdef OFFSET_VERBOSE
448     g_print ("rad=%g\n", offset->rad);
449 #endif
450     // au boulot
452     if ( fabs(offset->rad) < 0.01 ) {
453         // grosso modo: 0
454         // just put the source shape as the offseted one, no one will notice
455         // it's also useless to compute the offset with a 0 radius
457         const char *res_d = SP_OBJECT(shape)->repr->attribute("inkscape:original");
458         if ( res_d ) {
459             Geom::PathVector pv = sp_svg_read_pathv(res_d);
460             SPCurve *c = new SPCurve(pv);
461             g_assert(c != NULL);
462             sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE);
463             c->unref();
464         }
465         return;
466     }
468     // extra paraniac careful check. the preceding if () should take care of this case
469     if (fabs (offset->rad) < 0.01)
470         offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
472     Path *orig = new Path;
473     orig->Copy ((Path *) offset->originalPath);
475     if ( use_slow_but_correct_offset_method == false ) {
476         // version par outline
477         Shape *theShape = new Shape;
478         Shape *theRes = new Shape;
479         Path *originaux[1];
480         Path *res = new Path;
481         res->SetBackData (false);
483         // and now: offset
484         float o_width;
485         if (offset->rad >= 0)
486         {
487             o_width = offset->rad;
488             orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0);
489         }
490         else
491         {
492             o_width = -offset->rad;
493             orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0);
494         }
496         if (o_width >= 1.0)
497         {
498             //      res->ConvertForOffset (1.0, orig, offset->rad);
499             res->ConvertWithBackData (1.0);
500         }
501         else
502         {
503             //      res->ConvertForOffset (o_width, orig, offset->rad);
504             res->ConvertWithBackData (o_width);
505         }
506         res->Fill (theShape, 0);
507         theRes->ConvertToShape (theShape, fill_positive);
508         originaux[0] = res;
510         theRes->ConvertToForme (orig, 1, originaux);
512         SPItem *item = shape;
513         Geom::OptRect bbox = sp_item_bbox_desktop (item);
514         if ( bbox ) {
515             gdouble size = L2(bbox->dimensions());
516             gdouble const exp = item->transform.descrim();
517             if (exp != 0)
518                 size /= exp;
519             orig->Coalesce (size * 0.001);
520             //g_print ("coa %g    exp %g    item %p\n", size * 0.001, exp, item);
521         }
524         //  if (o_width >= 1.0)
525         //  {
526         //    orig->Coalesce (0.1);  // small treshhold, since we only want to get rid of small segments
527         // the curve should already be computed by the Outline() function
528         //   orig->ConvertEvenLines (1.0);
529         //   orig->Simplify (0.5);
530         //  }
531         //  else
532         //  {
533         //          orig->Coalesce (0.1*o_width);
534         //   orig->ConvertEvenLines (o_width);
535         //   orig->Simplify (0.5 * o_width);
536         //  }
538         delete theShape;
539         delete theRes;
540         delete res;
541     } else {
542         // version par makeoffset
543         Shape *theShape = new Shape;
544         Shape *theRes = new Shape;
547         // and now: offset
548         float o_width;
549         if (offset->rad >= 0)
550         {
551             o_width = offset->rad;
552         }
553         else
554         {
555             o_width = -offset->rad;
556         }
558         // one has to have a measure of the details
559         if (o_width >= 1.0)
560         {
561             orig->ConvertWithBackData (0.5);
562         }
563         else
564         {
565             orig->ConvertWithBackData (0.5*o_width);
566         }
567         orig->Fill (theShape, 0);
568         theRes->ConvertToShape (theShape, fill_positive);
569         Path *originaux[1];
570         originaux[0]=orig;
571         Path *res = new Path;
572         theRes->ConvertToForme (res, 1, originaux);
573         int    nbPart=0;
574         Path** parts=res->SubPaths(nbPart,true);
575         char   *holes=(char*)malloc(nbPart*sizeof(char));
576         // we offset contours separately, because we can.
577         // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes
578         {
579             Shape* onePart=new Shape;
580             Shape* oneCleanPart=new Shape;
581             theShape->Reset();
582             for (int i=0;i<nbPart;i++) {
583                 double partSurf=parts[i]->Surface();
584                 parts[i]->Convert(1.0);
585                 {
586                     // raffiner si besoin
587                     double  bL,bT,bR,bB;
588                     parts[i]->PolylineBoundingBox(bL,bT,bR,bB);
589                     double  mesure=((bR-bL)+(bB-bT))*0.5;
590                     if ( mesure < 10.0 ) {
591                         parts[i]->Convert(0.02*mesure);
592                     }
593                 }
594                 if ( partSurf < 0 ) { // inverse par rapport a la realite
595                     // plein
596                     holes[i]=0;
597                     parts[i]->Fill(oneCleanPart,0);
598                     onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges
599                     oneCleanPart->MakeOffset(onePart,offset->rad,join_round,20.0);
600                     onePart->ConvertToShape(oneCleanPart,fill_positive);
602                     onePart->CalcBBox();
603                     double  typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
604                     if ( typicalSize < 0.05 ) typicalSize=0.05;
605                     typicalSize*=0.01;
606                     if ( typicalSize > 1.0 ) typicalSize=1.0;
607                     onePart->ConvertToForme (parts[i]);
608                     parts[i]->ConvertEvenLines (typicalSize);
609                     parts[i]->Simplify (typicalSize);
610                     double nPartSurf=parts[i]->Surface();
611                     if ( nPartSurf >= 0 ) {
612                         // inversion de la surface -> disparait
613                         delete parts[i];
614                         parts[i]=NULL;
615                     } else {
616                     }
617 /*          int  firstP=theShape->nbPt;
618             for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
619             for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/
620                 } else {
621                     // trou
622                     holes[i]=1;
623                     parts[i]->Fill(oneCleanPart,0,false,true,true);
624                     onePart->ConvertToShape(oneCleanPart,fill_positive);
625                     oneCleanPart->MakeOffset(onePart,-offset->rad,join_round,20.0);
626                     onePart->ConvertToShape(oneCleanPart,fill_positive);
627 //          for (int j=0;j<onePart->nbAr;j++) onePart->Inverse(j); // pas oublier de reinverser
629                     onePart->CalcBBox();
630                     double  typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
631                     if ( typicalSize < 0.05 ) typicalSize=0.05;
632                     typicalSize*=0.01;
633                     if ( typicalSize > 1.0 ) typicalSize=1.0;
634                     onePart->ConvertToForme (parts[i]);
635                     parts[i]->ConvertEvenLines (typicalSize);
636                     parts[i]->Simplify (typicalSize);
637                     double nPartSurf=parts[i]->Surface();
638                     if ( nPartSurf >= 0 ) {
639                         // inversion de la surface -> disparait
640                         delete parts[i];
641                         parts[i]=NULL;
642                     } else {
643                     }
645                     /*         int  firstP=theShape->nbPt;
646                                for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
647                                for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/
648                 }
649 //        delete parts[i];
650             }
651 //      theShape->MakeOffset(theRes,offset->rad,join_round,20.0);
652             delete onePart;
653             delete oneCleanPart;
654         }
655         if ( nbPart > 1 ) {
656             theShape->Reset();
657             for (int i=0;i<nbPart;i++) {
658                 if ( parts[i] ) {
659                     parts[i]->ConvertWithBackData(1.0);
660                     if ( holes[i] ) {
661                         parts[i]->Fill(theShape,i,true,true,true);
662                     } else {
663                         parts[i]->Fill(theShape,i,true,true,false);
664                     }
665                 }
666             }
667             theRes->ConvertToShape (theShape, fill_positive);
668             theRes->ConvertToForme (orig,nbPart,parts);
669             for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
670         } else if ( nbPart == 1 ) {
671             orig->Copy(parts[0]);
672             for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
673         } else {
674             orig->Reset();
675         }
676 //    theRes->ConvertToShape (theShape, fill_positive);
677 //    theRes->ConvertToForme (orig);
679 /*    if (o_width >= 1.0) {
680       orig->ConvertEvenLines (1.0);
681       orig->Simplify (1.0);
682       } else {
683       orig->ConvertEvenLines (1.0*o_width);
684       orig->Simplify (1.0 * o_width);
685       }*/
687         if ( parts ) free(parts);
688         if ( holes ) free(holes);
689         delete res;
690         delete theShape;
691         delete theRes;
692     }
693     {
694         char *res_d = NULL;
695         if (orig->descr_cmd.size() <= 1)
696         {
697             // Aie.... nothing left.
698             res_d = strdup ("M 0 0 L 0 0 z");
699             //printf("%s\n",res_d);
700         }
701         else
702         {
704             res_d = orig->svg_dump_path ();
705         }
706         delete orig;
708         Geom::PathVector pv = sp_svg_read_pathv(res_d);
709         SPCurve *c = new SPCurve(pv);
710         g_assert(c != NULL);
711         sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE);
712         c->unref();
714         free (res_d);
715     }
718 /**
719  * Virtual snappoints function.
720  */
721 static void sp_offset_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs)
723     if (((SPItemClass *) parent_class)->snappoints) {
724         ((SPItemClass *) parent_class)->snappoints (item, target, p, snapprefs);
725     }
729 // utilitaires pour les poignees
730 // used to get the distance to the shape: distance to polygon give the fabs(radius), we still need
731 // the sign. for edges, it's easy to determine which side the point is on, for points of the polygon
732 // it's trickier: we need to identify which angle the point is in; to that effect, we take each
733 // successive clockwise angle (A,C) and check if the vector B given by the point is in the angle or
734 // outside.
735 // another method would be to use the Winding() function to test whether the point is inside or outside
736 // the polygon (it would be wiser to do so, in fact, but i like being stupid)
738 /**
739  *
740  * \todo
741  * FIXME: This can be done using linear operations, more stably and
742  *  faster.  method: transform A and C into B's space, A should be
743  *  negative and B should be positive in the orthogonal component.  I
744  *  think this is equivalent to
745  *  dot(A, rot90(B))*dot(C, rot90(B)) == -1.
746  *    -- njh
747  */
748 bool
749 vectors_are_clockwise (Geom::Point A, Geom::Point B, Geom::Point C)
751     using Geom::rot90;
752     double ab_s = dot(A, rot90(B));
753     double ab_c = dot(A, B);
754     double bc_s = dot(B, rot90(C));
755     double bc_c = dot(B, C);
756     double ca_s = dot(C, rot90(A));
757     double ca_c = dot(C, A);
759     double ab_a = acos (ab_c);
760     if (ab_c <= -1.0)
761         ab_a = M_PI;
762     if (ab_c >= 1.0)
763         ab_a = 0;
764     if (ab_s < 0)
765         ab_a = 2 * M_PI - ab_a;
766     double bc_a = acos (bc_c);
767     if (bc_c <= -1.0)
768         bc_a = M_PI;
769     if (bc_c >= 1.0)
770         bc_a = 0;
771     if (bc_s < 0)
772         bc_a = 2 * M_PI - bc_a;
773     double ca_a = acos (ca_c);
774     if (ca_c <= -1.0)
775         ca_a = M_PI;
776     if (ca_c >= 1.0)
777         ca_a = 0;
778     if (ca_s < 0)
779         ca_a = 2 * M_PI - ca_a;
781     double lim = 2 * M_PI - ca_a;
783     if (ab_a < lim)
784         return true;
785     return false;
788 /**
789  * Distance to the original path; that function is called from object-edit
790  * to set the radius when the control knot moves.
791  *
792  * The sign of the result is the radius we're going to offset the shape with,
793  * so result > 0 ==outset and result < 0 ==inset. thus result<0 means
794  * 'px inside source'.
795  */
796 double
797 sp_offset_distance_to_original (SPOffset * offset, Geom::Point px)
799     if (offset == NULL || offset->originalPath == NULL
800         || ((Path *) offset->originalPath)->descr_cmd.size() <= 1)
801         return 1.0;
802     double dist = 1.0;
803     Shape *theShape = new Shape;
804     Shape *theRes = new Shape;
806     /** \todo
807      * Awfully damn stupid method: uncross the source path EACH TIME you
808      * need to compute the distance. The good way to do this would be to
809      * store the uncrossed source path somewhere, and delete it when the
810      * context is finished. Hopefully this part is much faster than actually
811      * computing the offset (which happen just after), so the time spent in
812      * this function should end up being negligible with respect to the
813      * delay of one context.
814      */
815     // move
816     ((Path *) offset->originalPath)->Convert (1.0);
817     ((Path *) offset->originalPath)->Fill (theShape, 0);
818     theRes->ConvertToShape (theShape, fill_oddEven);
820     if (theRes->numberOfEdges() <= 1)
821     {
823     }
824     else
825     {
826         double ptDist = -1.0;
827         bool ptSet = false;
828         double arDist = -1.0;
829         bool arSet = false;
830         // first get the minimum distance to the points
831         for (int i = 0; i < theRes->numberOfPoints(); i++)
832         {
833             if (theRes->getPoint(i).totalDegree() > 0)
834             {
835                 Geom::Point nx = theRes->getPoint(i).x;
836                 Geom::Point nxpx = px-nx;
837                 double ndist = sqrt (dot(nxpx,nxpx));
838                 if (ptSet == false || fabs (ndist) < fabs (ptDist))
839                 {
840                     // we have a new minimum distance
841                     // now we need to wheck if px is inside or outside (for the sign)
842                     nx = px - to_2geom(theRes->getPoint(i).x);
843                     double nlen = sqrt (dot(nx , nx));
844                     nx /= nlen;
845                     int pb, cb, fb;
846                     fb = theRes->getPoint(i).incidentEdge[LAST];
847                     pb = theRes->getPoint(i).incidentEdge[LAST];
848                     cb = theRes->getPoint(i).incidentEdge[FIRST];
849                     do
850                     {
851                         // one angle
852                         Geom::Point prx, nex;
853                         prx = theRes->getEdge(pb).dx;
854                         nlen = sqrt (dot(prx, prx));
855                         prx /= nlen;
856                         nex = theRes->getEdge(cb).dx;
857                         nlen = sqrt (dot(nex , nex));
858                         nex /= nlen;
859                         if (theRes->getEdge(pb).en == i)
860                         {
861                             prx = -prx;
862                         }
863                         if (theRes->getEdge(cb).en == i)
864                         {
865                             nex = -nex;
866                         }
868                         if (vectors_are_clockwise (nex, nx, prx))
869                         {
870                             // we're in that angle. set the sign, and exit that loop
871                             if (theRes->getEdge(cb).st == i)
872                             {
873                                 ptDist = -ndist;
874                                 ptSet = true;
875                             }
876                             else
877                             {
878                                 ptDist = ndist;
879                                 ptSet = true;
880                             }
881                             break;
882                         }
883                         pb = cb;
884                         cb = theRes->NextAt (i, cb);
885                     }
886                     while (cb >= 0 && pb >= 0 && pb != fb);
887                 }
888             }
889         }
890         // loop over the edges to try to improve the distance
891         for (int i = 0; i < theRes->numberOfEdges(); i++)
892         {
893             Geom::Point sx = theRes->getPoint(theRes->getEdge(i).st).x;
894             Geom::Point ex = theRes->getPoint(theRes->getEdge(i).en).x;
895             Geom::Point nx = ex - sx;
896             double len = sqrt (dot(nx,nx));
897             if (len > 0.0001)
898             {
899                 Geom::Point   pxsx=px-sx;
900                 double ab = dot(nx,pxsx);
901                 if (ab > 0 && ab < len * len)
902                 {
903                     // we're in the zone of influence of the segment
904                     double ndist = (cross(pxsx,nx)) / len;
905                     if (arSet == false || fabs (ndist) < fabs (arDist))
906                     {
907                         arDist = ndist;
908                         arSet = true;
909                     }
910                 }
911             }
912         }
913         if (arSet || ptSet)
914         {
915             if (arSet == false)
916                 arDist = ptDist;
917             if (ptSet == false)
918                 ptDist = arDist;
919             if (fabs (ptDist) < fabs (arDist))
920                 dist = ptDist;
921             else
922                 dist = arDist;
923         }
924     }
926     delete theShape;
927     delete theRes;
929     return dist;
932 /**
933  * Computes a point on the offset;  used to set a "seed" position for
934  * the control knot.
935  *
936  * \return the topmost point on the offset.
937  */
938 void
939 sp_offset_top_point (SPOffset * offset, Geom::Point *px)
941     (*px) = Geom::Point(0, 0);
942     if (offset == NULL)
943         return;
945     if (offset->knotSet)
946     {
947         (*px) = offset->knot;
948         return;
949     }
951     SPCurve *curve = sp_shape_get_curve (SP_SHAPE (offset));
952     if (curve == NULL)
953     {
954         sp_offset_set_shape (SP_SHAPE (offset));
955         curve = sp_shape_get_curve (SP_SHAPE (offset));
956         if (curve == NULL)
957             return;
958     }
959     if (curve->is_empty())
960     {
961         curve->unref();
962         return;
963     }
965     Path *finalPath = new Path;
966     finalPath->LoadPathVector(curve->get_pathvector());
968     Shape *theShape = new Shape;
970     finalPath->Convert (1.0);
971     finalPath->Fill (theShape, 0);
973     if (theShape->hasPoints())
974     {
975         theShape->SortPoints ();
976         *px = theShape->getPoint(0).x;
977     }
979     delete theShape;
980     delete finalPath;
981     curve->unref();
984 // the listening functions
985 static void sp_offset_start_listening(SPOffset *offset,SPObject* to)
987     if ( to == NULL )
988         return;
990     offset->sourceObject = to;
991     offset->sourceRepr = SP_OBJECT_REPR(to);
993     offset->_delete_connection = SP_OBJECT(to)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_offset_delete_self), offset));
994     offset->_transformed_connection = SP_ITEM(to)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_offset_move_compensate), offset));
995     offset->_modified_connection = SP_OBJECT(to)->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_offset_source_modified), offset));
998 static void sp_offset_quit_listening(SPOffset *offset)
1000     if ( offset->sourceObject == NULL )
1001         return;
1003     offset->_modified_connection.disconnect();
1004     offset->_delete_connection.disconnect();
1005     offset->_transformed_connection.disconnect();
1007     offset->sourceRepr = NULL;
1008     offset->sourceObject = NULL;
1011 static void
1012 sp_offset_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPOffset *offset)
1014     sp_offset_quit_listening(offset);
1015     if (offset->sourceRef) {
1016         SPItem *refobj = offset->sourceRef->getObject();
1017         if (refobj) sp_offset_start_listening(offset,refobj);
1018         offset->sourceDirty=true;
1019         SP_OBJECT(offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1020     }
1023 static void
1024 sp_offset_move_compensate(Geom::Matrix const *mp, SPItem */*original*/, SPOffset *self)
1026     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1027     guint mode = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_PARALLEL);
1028     if (mode == SP_CLONE_COMPENSATION_NONE) return;
1030     Geom::Matrix m(*mp);
1031     if (!(m.isTranslation())) return;
1033     // calculate the compensation matrix and the advertized movement matrix
1034     SPItem *item = SP_ITEM(self);
1036     Geom::Matrix compensate;
1037     Geom::Matrix advertized_move;
1039     if (mode == SP_CLONE_COMPENSATION_UNMOVED) {
1040         compensate = Geom::identity();
1041         advertized_move.setIdentity();
1042     } else if (mode == SP_CLONE_COMPENSATION_PARALLEL) {
1043         compensate = m;
1044         advertized_move = m;
1045     } else {
1046         g_assert_not_reached();
1047     }
1049     item->transform *= compensate;
1051     // commit the compensation
1052     sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform, &advertized_move);
1053     SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1056 static void
1057 sp_offset_delete_self(SPObject */*deleted*/, SPOffset *offset)
1059     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1060     guint const mode = prefs->getInt("/options/cloneorphans/value", SP_CLONE_ORPHANS_UNLINK);
1062     if (mode == SP_CLONE_ORPHANS_UNLINK) {
1063         // leave it be. just forget about the source
1064         sp_offset_quit_listening(offset);
1065         if ( offset->sourceHref ) g_free(offset->sourceHref);
1066         offset->sourceHref = NULL;
1067         offset->sourceRef->detach();
1068     } else if (mode == SP_CLONE_ORPHANS_DELETE) {
1069         SP_OBJECT(offset)->deleteObject();
1070     }
1073 static void
1074 sp_offset_source_modified (SPObject */*iSource*/, guint /*flags*/, SPItem *item)
1076     SPOffset *offset = SP_OFFSET(item);
1077     offset->sourceDirty=true;
1078     refresh_offset_source(offset);
1079     sp_shape_set_shape ((SPShape *) offset);
1082 static void
1083 refresh_offset_source(SPOffset* offset)
1085     if ( offset == NULL ) return;
1086     offset->sourceDirty=false;
1088     // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour
1089     // The bad case: no d attribute.  Must check that it's an SPShape and then take the outline.
1090     SPObject *refobj=offset->sourceObject;
1091     if ( refobj == NULL ) return;
1092     SPItem *item = SP_ITEM (refobj);
1094     SPCurve *curve=NULL;
1095     if (!SP_IS_SHAPE (item) && !SP_IS_TEXT (item)) return;
1096     if (SP_IS_SHAPE (item)) {
1097         curve = sp_shape_get_curve (SP_SHAPE (item));
1098         if (curve == NULL)
1099             return;
1100     }
1101     if (SP_IS_TEXT (item)) {
1102         curve = SP_TEXT (item)->getNormalizedBpath ();
1103         if (curve == NULL)
1104         return;
1105     }
1106     Path *orig = new Path;
1107     orig->LoadPathVector(curve->get_pathvector());
1108     curve->unref();
1111     // Finish up.
1112     {
1113         SPCSSAttr *css;
1114         const gchar *val;
1115         Shape *theShape = new Shape;
1116         Shape *theRes = new Shape;
1118         orig->ConvertWithBackData (1.0);
1119         orig->Fill (theShape, 0);
1121         css = sp_repr_css_attr (offset->sourceRepr , "style");
1122         val = sp_repr_css_property (css, "fill-rule", NULL);
1123         if (val && strcmp (val, "nonzero") == 0)
1124         {
1125             theRes->ConvertToShape (theShape, fill_nonZero);
1126         }
1127         else if (val && strcmp (val, "evenodd") == 0)
1128         {
1129             theRes->ConvertToShape (theShape, fill_oddEven);
1130         }
1131         else
1132         {
1133             theRes->ConvertToShape (theShape, fill_nonZero);
1134         }
1136         Path *originaux[1];
1137         originaux[0] = orig;
1138         Path *res = new Path;
1139         theRes->ConvertToForme (res, 1, originaux);
1141         delete theShape;
1142         delete theRes;
1144         char *res_d = res->svg_dump_path ();
1145         delete res;
1146         delete orig;
1148         SP_OBJECT (offset)->repr->setAttribute("inkscape:original", res_d);
1150         free (res_d);
1151     }
1154 SPItem *
1155 sp_offset_get_source (SPOffset *offset)
1157     if (offset && offset->sourceRef) {
1158         SPItem *refobj = offset->sourceRef->getObject();
1159         if (SP_IS_ITEM (refobj))
1160             return (SPItem *) refobj;
1161     }
1162     return NULL;
1166 /*
1167   Local Variables:
1168   mode:c++
1169   c-file-style:"stroustrup"
1170   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1171   indent-tabs-mode:nil
1172   fill-column:99
1173   End:
1174 */
1175 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :