Code

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