Code

A simple layout document as to what, why and how is cppification.
[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, std::vector<Inkscape::SnapCandidatePoint> &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         //XML Tree being used directly here while it shouldn't be.
221     if (object->getRepr()->attribute("inkscape:radius")) {
222         object->readAttr( "inkscape:radius");
223     } else {
225                 
226                 //XML Tree being used directly here (as object->getRepr) 
227                 //in all the below lines in the block while it shouldn't be.
228         gchar const *oldA = object->getRepr()->attribute("sodipodi:radius");
229         object->getRepr()->setAttribute("inkscape:radius",oldA);
230         object->getRepr()->setAttribute("sodipodi:radius",NULL);
232         object->readAttr( "inkscape:radius");
233     }
234     if (object->getRepr()->attribute("inkscape:original")) {
235         object->readAttr( "inkscape:original");
236     } else {
237         gchar const *oldA = object->getRepr()->attribute("sodipodi:original");
238         object->getRepr()->setAttribute("inkscape:original",oldA);
239         object->getRepr()->setAttribute("sodipodi:original",NULL);
241         object->readAttr( "inkscape:original");
242     }
243     if (object->getRepr()->attribute("xlink:href")) {
244         object->readAttr( "xlink:href");
245     } else {
246         gchar const *oldA = object->getRepr()->attribute("inkscape:href");
247         if (oldA) {
248             size_t lA = strlen(oldA);
249             char *nA=(char*)malloc((1+lA+1)*sizeof(char));
250             memcpy(nA+1,oldA,lA*sizeof(char));
251             nA[0]='#';
252             nA[lA+1]=0;
253             object->getRepr()->setAttribute("xlink:href",nA);
254             free(nA);
255             object->getRepr()->setAttribute("inkscape:href",NULL);
256         }
257         object->readAttr( "xlink:href");
258     }
261 /**
262  * Virtual write: write offset attributes to corresponding repr.
263  */
264 static Inkscape::XML::Node *
265 sp_offset_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
267     SPOffset *offset = SP_OFFSET (object);
269     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
270         repr = xml_doc->createElement("svg:path");
271     }
273     if (flags & SP_OBJECT_WRITE_EXT) {
274         /** \todo
275          * Fixme: we may replace these attributes by
276          * inkscape:offset="cx cy exp revo rad arg t0"
277          */
278         repr->setAttribute("sodipodi:type", "inkscape:offset");
279         sp_repr_set_svg_double(repr, "inkscape:radius", offset->rad);
280         repr->setAttribute("inkscape:original", offset->original);
281         repr->setAttribute("inkscape:href", offset->sourceHref);
282     }
285     // Make sure the object has curve
286     SPCurve *curve = SP_SHAPE (offset)->getCurve();
287     if (curve == NULL) {
288         sp_offset_set_shape (SP_SHAPE (offset));
289     }
291     // write that curve to "d"
292     char *d = sp_svg_write_path (((SPShape *) offset)->curve->get_pathvector());
293     repr->setAttribute("d", d);
294     g_free (d);
296     if (((SPObjectClass *) (parent_class))->write)
297         ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr,
298                                                    flags | SP_SHAPE_WRITE_PATH);
300     return repr;
303 /**
304  * Virtual release callback.
305  */
306 static void
307 sp_offset_release(SPObject *object)
309     SPOffset *offset = (SPOffset *) object;
311     if (offset->original) free (offset->original);
312     if (offset->originalPath) delete ((Path *) offset->originalPath);
313     offset->original = NULL;
314     offset->originalPath = NULL;
316     sp_offset_quit_listening(offset);
318     offset->_changed_connection.disconnect();
319     g_free(offset->sourceHref);
320     offset->sourceHref = NULL;
321     offset->sourceRef->detach();
323     if (((SPObjectClass *) parent_class)->release) {
324         ((SPObjectClass *) parent_class)->release (object);
325     }
329 /**
330  * Set callback: the function that is called whenever a change is made to
331  * the description of the object.
332  */
333 static void
334 sp_offset_set(SPObject *object, unsigned key, gchar const *value)
336     SPOffset *offset = SP_OFFSET (object);
338     if ( offset->sourceDirty ) refresh_offset_source(offset);
340     /* fixme: we should really collect updates */
341     switch (key)
342     {
343         case SP_ATTR_INKSCAPE_ORIGINAL:
344         case SP_ATTR_SODIPODI_ORIGINAL:
345             if (value == NULL) {
346             } else {
347                 if (offset->original) {
348                     free (offset->original);
349                     delete ((Path *) offset->originalPath);
350                     offset->original = NULL;
351                     offset->originalPath = NULL;
352                 }
354                 offset->original = strdup (value);
356                 Geom::PathVector pv = sp_svg_read_pathv(offset->original);
357                 offset->originalPath = new Path;
358                 reinterpret_cast<Path *>(offset->originalPath)->LoadPathVector(pv);
360                 offset->knotSet = false;
361                 if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
362             }
363             break;
364         case SP_ATTR_INKSCAPE_RADIUS:
365         case SP_ATTR_SODIPODI_RADIUS:
366             if (!sp_svg_length_read_computed_absolute (value, &offset->rad)) {
367                 if (fabs (offset->rad) < 0.01)
368                     offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
369                 offset->knotSet = false; // knotset=false because it's not set from the context
370             }
371             if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
372             break;
373         case SP_ATTR_INKSCAPE_HREF:
374         case SP_ATTR_XLINK_HREF:
375             if ( value == NULL ) {
376                 sp_offset_quit_listening(offset);
377                 if ( offset->sourceHref ) g_free(offset->sourceHref);
378                 offset->sourceHref = NULL;
379                 offset->sourceRef->detach();
380             } else {
381                 if ( offset->sourceHref && ( strcmp(value, offset->sourceHref) == 0 ) ) {
382                 } else {
383                     if ( offset->sourceHref ) g_free(offset->sourceHref);
384                     offset->sourceHref = g_strdup(value);
385                     try {
386                         offset->sourceRef->attach(Inkscape::URI(value));
387                     } catch (Inkscape::BadURIException &e) {
388                         g_warning("%s", e.what());
389                         offset->sourceRef->detach();
390                     }
391                 }
392             }
393             break;
394         default:
395             if (((SPObjectClass *) parent_class)->set)
396                 ((SPObjectClass *) parent_class)->set (object, key, value);
397             break;
398     }
401 /**
402  * Update callback: the object has changed, recompute its shape.
403  */
404 static void
405 sp_offset_update(SPObject *object, SPCtx *ctx, guint flags)
407     SPOffset* offset = SP_OFFSET(object);
408     offset->isUpdating=true; // prevent sp_offset_set from requesting updates
409     if ( offset->sourceDirty ) refresh_offset_source(offset);
410     if (flags &
411         (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG |
412          SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
413         ((SPShape *) object)->setShape ();
414     }
415     offset->isUpdating=false;
417     if (((SPObjectClass *) parent_class)->update)
418         ((SPObjectClass *) parent_class)->update (object, ctx, flags);
421 /**
422  * Returns a textual description of object.
423  */
424 static gchar *
425 sp_offset_description(SPItem *item)
427     SPOffset *offset = SP_OFFSET (item);
429     if ( offset->sourceHref ) {
430         // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
431         return g_strdup_printf(_("<b>Linked offset</b>, %s by %f pt"),
432                                (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad));
433     } else {
434         // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
435         return g_strdup_printf(_("<b>Dynamic offset</b>, %s by %f pt"),
436                                (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad));
437     }
440 /**
441  * Compute and set shape's offset.
442  */
443 static void
444 sp_offset_set_shape(SPShape *shape)
446     SPOffset *offset = SP_OFFSET (shape);
448     if ( offset->originalPath == NULL ) {
449         // oops : no path?! (the offset object should do harakiri)
450         return;
451     }
452 #ifdef OFFSET_VERBOSE
453     g_print ("rad=%g\n", offset->rad);
454 #endif
455     // au boulot
457     if ( fabs(offset->rad) < 0.01 ) {
458         // grosso modo: 0
459         // just put the source shape as the offseted one, no one will notice
460         // it's also useless to compute the offset with a 0 radius
462                 //XML Tree being used directly here while it shouldn't be.
463         const char *res_d = SP_OBJECT(shape)->getRepr()->attribute("inkscape:original");
464         if ( res_d ) {
465             Geom::PathVector pv = sp_svg_read_pathv(res_d);
466             SPCurve *c = new SPCurve(pv);
467             g_assert(c != NULL);
468             ((SPShape *) offset)->setCurveInsync (c, TRUE);
469             c->unref();
470         }
471         return;
472     }
474     // extra paraniac careful check. the preceding if () should take care of this case
475     if (fabs (offset->rad) < 0.01)
476         offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
478     Path *orig = new Path;
479     orig->Copy ((Path *) offset->originalPath);
481     if ( use_slow_but_correct_offset_method == false ) {
482         // version par outline
483         Shape *theShape = new Shape;
484         Shape *theRes = new Shape;
485         Path *originaux[1];
486         Path *res = new Path;
487         res->SetBackData (false);
489         // and now: offset
490         float o_width;
491         if (offset->rad >= 0)
492         {
493             o_width = offset->rad;
494             orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0);
495         }
496         else
497         {
498             o_width = -offset->rad;
499             orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0);
500         }
502         if (o_width >= 1.0)
503         {
504             //      res->ConvertForOffset (1.0, orig, offset->rad);
505             res->ConvertWithBackData (1.0);
506         }
507         else
508         {
509             //      res->ConvertForOffset (o_width, orig, offset->rad);
510             res->ConvertWithBackData (o_width);
511         }
512         res->Fill (theShape, 0);
513         theRes->ConvertToShape (theShape, fill_positive);
514         originaux[0] = res;
516         theRes->ConvertToForme (orig, 1, originaux);
518         SPItem *item = shape;
519         Geom::OptRect bbox = item->getBboxDesktop ();
520         if ( bbox ) {
521             gdouble size = L2(bbox->dimensions());
522             gdouble const exp = item->transform.descrim();
523             if (exp != 0)
524                 size /= exp;
525             orig->Coalesce (size * 0.001);
526             //g_print ("coa %g    exp %g    item %p\n", size * 0.001, exp, item);
527         }
530         //  if (o_width >= 1.0)
531         //  {
532         //    orig->Coalesce (0.1);  // small treshhold, since we only want to get rid of small segments
533         // the curve should already be computed by the Outline() function
534         //   orig->ConvertEvenLines (1.0);
535         //   orig->Simplify (0.5);
536         //  }
537         //  else
538         //  {
539         //          orig->Coalesce (0.1*o_width);
540         //   orig->ConvertEvenLines (o_width);
541         //   orig->Simplify (0.5 * o_width);
542         //  }
544         delete theShape;
545         delete theRes;
546         delete res;
547     } else {
548         // version par makeoffset
549         Shape *theShape = new Shape;
550         Shape *theRes = new Shape;
553         // and now: offset
554         float o_width;
555         if (offset->rad >= 0)
556         {
557             o_width = offset->rad;
558         }
559         else
560         {
561             o_width = -offset->rad;
562         }
564         // one has to have a measure of the details
565         if (o_width >= 1.0)
566         {
567             orig->ConvertWithBackData (0.5);
568         }
569         else
570         {
571             orig->ConvertWithBackData (0.5*o_width);
572         }
573         orig->Fill (theShape, 0);
574         theRes->ConvertToShape (theShape, fill_positive);
575         Path *originaux[1];
576         originaux[0]=orig;
577         Path *res = new Path;
578         theRes->ConvertToForme (res, 1, originaux);
579         int    nbPart=0;
580         Path** parts=res->SubPaths(nbPart,true);
581         char   *holes=(char*)malloc(nbPart*sizeof(char));
582         // we offset contours separately, because we can.
583         // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes
584         {
585             Shape* onePart=new Shape;
586             Shape* oneCleanPart=new Shape;
587             theShape->Reset();
588             for (int i=0;i<nbPart;i++) {
589                 double partSurf=parts[i]->Surface();
590                 parts[i]->Convert(1.0);
591                 {
592                     // raffiner si besoin
593                     double  bL,bT,bR,bB;
594                     parts[i]->PolylineBoundingBox(bL,bT,bR,bB);
595                     double  mesure=((bR-bL)+(bB-bT))*0.5;
596                     if ( mesure < 10.0 ) {
597                         parts[i]->Convert(0.02*mesure);
598                     }
599                 }
600                 if ( partSurf < 0 ) { // inverse par rapport a la realite
601                     // plein
602                     holes[i]=0;
603                     parts[i]->Fill(oneCleanPart,0);
604                     onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges
605                     oneCleanPart->MakeOffset(onePart,offset->rad,join_round,20.0);
606                     onePart->ConvertToShape(oneCleanPart,fill_positive);
608                     onePart->CalcBBox();
609                     double  typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
610                     if ( typicalSize < 0.05 ) typicalSize=0.05;
611                     typicalSize*=0.01;
612                     if ( typicalSize > 1.0 ) typicalSize=1.0;
613                     onePart->ConvertToForme (parts[i]);
614                     parts[i]->ConvertEvenLines (typicalSize);
615                     parts[i]->Simplify (typicalSize);
616                     double nPartSurf=parts[i]->Surface();
617                     if ( nPartSurf >= 0 ) {
618                         // inversion de la surface -> disparait
619                         delete parts[i];
620                         parts[i]=NULL;
621                     } else {
622                     }
623 /*          int  firstP=theShape->nbPt;
624             for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
625             for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/
626                 } else {
627                     // trou
628                     holes[i]=1;
629                     parts[i]->Fill(oneCleanPart,0,false,true,true);
630                     onePart->ConvertToShape(oneCleanPart,fill_positive);
631                     oneCleanPart->MakeOffset(onePart,-offset->rad,join_round,20.0);
632                     onePart->ConvertToShape(oneCleanPart,fill_positive);
633 //          for (int j=0;j<onePart->nbAr;j++) onePart->Inverse(j); // pas oublier de reinverser
635                     onePart->CalcBBox();
636                     double  typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
637                     if ( typicalSize < 0.05 ) typicalSize=0.05;
638                     typicalSize*=0.01;
639                     if ( typicalSize > 1.0 ) typicalSize=1.0;
640                     onePart->ConvertToForme (parts[i]);
641                     parts[i]->ConvertEvenLines (typicalSize);
642                     parts[i]->Simplify (typicalSize);
643                     double nPartSurf=parts[i]->Surface();
644                     if ( nPartSurf >= 0 ) {
645                         // inversion de la surface -> disparait
646                         delete parts[i];
647                         parts[i]=NULL;
648                     } else {
649                     }
651                     /*         int  firstP=theShape->nbPt;
652                                for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
653                                for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/
654                 }
655 //        delete parts[i];
656             }
657 //      theShape->MakeOffset(theRes,offset->rad,join_round,20.0);
658             delete onePart;
659             delete oneCleanPart;
660         }
661         if ( nbPart > 1 ) {
662             theShape->Reset();
663             for (int i=0;i<nbPart;i++) {
664                 if ( parts[i] ) {
665                     parts[i]->ConvertWithBackData(1.0);
666                     if ( holes[i] ) {
667                         parts[i]->Fill(theShape,i,true,true,true);
668                     } else {
669                         parts[i]->Fill(theShape,i,true,true,false);
670                     }
671                 }
672             }
673             theRes->ConvertToShape (theShape, fill_positive);
674             theRes->ConvertToForme (orig,nbPart,parts);
675             for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
676         } else if ( nbPart == 1 ) {
677             orig->Copy(parts[0]);
678             for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
679         } else {
680             orig->Reset();
681         }
682 //    theRes->ConvertToShape (theShape, fill_positive);
683 //    theRes->ConvertToForme (orig);
685 /*    if (o_width >= 1.0) {
686       orig->ConvertEvenLines (1.0);
687       orig->Simplify (1.0);
688       } else {
689       orig->ConvertEvenLines (1.0*o_width);
690       orig->Simplify (1.0 * o_width);
691       }*/
693         if ( parts ) free(parts);
694         if ( holes ) free(holes);
695         delete res;
696         delete theShape;
697         delete theRes;
698     }
699     {
700         char *res_d = NULL;
701         if (orig->descr_cmd.size() <= 1)
702         {
703             // Aie.... nothing left.
704             res_d = strdup ("M 0 0 L 0 0 z");
705             //printf("%s\n",res_d);
706         }
707         else
708         {
710             res_d = orig->svg_dump_path ();
711         }
712         delete orig;
714         Geom::PathVector pv = sp_svg_read_pathv(res_d);
715         SPCurve *c = new SPCurve(pv);
716         g_assert(c != NULL);
717         ((SPShape *) offset)->setCurveInsync (c, TRUE);
718         c->unref();
720         free (res_d);
721     }
724 /**
725  * Virtual snappoints function.
726  */
727 static void sp_offset_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs)
729     if (((SPItemClass *) parent_class)->snappoints) {
730         ((SPItemClass *) parent_class)->snappoints (item, p, snapprefs);
731     }
735 // utilitaires pour les poignees
736 // used to get the distance to the shape: distance to polygon give the fabs(radius), we still need
737 // the sign. for edges, it's easy to determine which side the point is on, for points of the polygon
738 // it's trickier: we need to identify which angle the point is in; to that effect, we take each
739 // successive clockwise angle (A,C) and check if the vector B given by the point is in the angle or
740 // outside.
741 // another method would be to use the Winding() function to test whether the point is inside or outside
742 // the polygon (it would be wiser to do so, in fact, but i like being stupid)
744 /**
745  *
746  * \todo
747  * FIXME: This can be done using linear operations, more stably and
748  *  faster.  method: transform A and C into B's space, A should be
749  *  negative and B should be positive in the orthogonal component.  I
750  *  think this is equivalent to
751  *  dot(A, rot90(B))*dot(C, rot90(B)) == -1.
752  *    -- njh
753  */
754 bool
755 vectors_are_clockwise (Geom::Point A, Geom::Point B, Geom::Point C)
757     using Geom::rot90;
758     double ab_s = dot(A, rot90(B));
759     double ab_c = dot(A, B);
760     double bc_s = dot(B, rot90(C));
761     double bc_c = dot(B, C);
762     double ca_s = dot(C, rot90(A));
763     double ca_c = dot(C, A);
765     double ab_a = acos (ab_c);
766     if (ab_c <= -1.0)
767         ab_a = M_PI;
768     if (ab_c >= 1.0)
769         ab_a = 0;
770     if (ab_s < 0)
771         ab_a = 2 * M_PI - ab_a;
772     double bc_a = acos (bc_c);
773     if (bc_c <= -1.0)
774         bc_a = M_PI;
775     if (bc_c >= 1.0)
776         bc_a = 0;
777     if (bc_s < 0)
778         bc_a = 2 * M_PI - bc_a;
779     double ca_a = acos (ca_c);
780     if (ca_c <= -1.0)
781         ca_a = M_PI;
782     if (ca_c >= 1.0)
783         ca_a = 0;
784     if (ca_s < 0)
785         ca_a = 2 * M_PI - ca_a;
787     double lim = 2 * M_PI - ca_a;
789     if (ab_a < lim)
790         return true;
791     return false;
794 /**
795  * Distance to the original path; that function is called from object-edit
796  * to set the radius when the control knot moves.
797  *
798  * The sign of the result is the radius we're going to offset the shape with,
799  * so result > 0 ==outset and result < 0 ==inset. thus result<0 means
800  * 'px inside source'.
801  */
802 double
803 sp_offset_distance_to_original (SPOffset * offset, Geom::Point px)
805     if (offset == NULL || offset->originalPath == NULL
806         || ((Path *) offset->originalPath)->descr_cmd.size() <= 1)
807         return 1.0;
808     double dist = 1.0;
809     Shape *theShape = new Shape;
810     Shape *theRes = new Shape;
812     /** \todo
813      * Awfully damn stupid method: uncross the source path EACH TIME you
814      * need to compute the distance. The good way to do this would be to
815      * store the uncrossed source path somewhere, and delete it when the
816      * context is finished. Hopefully this part is much faster than actually
817      * computing the offset (which happen just after), so the time spent in
818      * this function should end up being negligible with respect to the
819      * delay of one context.
820      */
821     // move
822     ((Path *) offset->originalPath)->Convert (1.0);
823     ((Path *) offset->originalPath)->Fill (theShape, 0);
824     theRes->ConvertToShape (theShape, fill_oddEven);
826     if (theRes->numberOfEdges() <= 1)
827     {
829     }
830     else
831     {
832         double ptDist = -1.0;
833         bool ptSet = false;
834         double arDist = -1.0;
835         bool arSet = false;
836         // first get the minimum distance to the points
837         for (int i = 0; i < theRes->numberOfPoints(); i++)
838         {
839             if (theRes->getPoint(i).totalDegree() > 0)
840             {
841                 Geom::Point nx = theRes->getPoint(i).x;
842                 Geom::Point nxpx = px-nx;
843                 double ndist = sqrt (dot(nxpx,nxpx));
844                 if (ptSet == false || fabs (ndist) < fabs (ptDist))
845                 {
846                     // we have a new minimum distance
847                     // now we need to wheck if px is inside or outside (for the sign)
848                     nx = px - to_2geom(theRes->getPoint(i).x);
849                     double nlen = sqrt (dot(nx , nx));
850                     nx /= nlen;
851                     int pb, cb, fb;
852                     fb = theRes->getPoint(i).incidentEdge[LAST];
853                     pb = theRes->getPoint(i).incidentEdge[LAST];
854                     cb = theRes->getPoint(i).incidentEdge[FIRST];
855                     do
856                     {
857                         // one angle
858                         Geom::Point prx, nex;
859                         prx = theRes->getEdge(pb).dx;
860                         nlen = sqrt (dot(prx, prx));
861                         prx /= nlen;
862                         nex = theRes->getEdge(cb).dx;
863                         nlen = sqrt (dot(nex , nex));
864                         nex /= nlen;
865                         if (theRes->getEdge(pb).en == i)
866                         {
867                             prx = -prx;
868                         }
869                         if (theRes->getEdge(cb).en == i)
870                         {
871                             nex = -nex;
872                         }
874                         if (vectors_are_clockwise (nex, nx, prx))
875                         {
876                             // we're in that angle. set the sign, and exit that loop
877                             if (theRes->getEdge(cb).st == i)
878                             {
879                                 ptDist = -ndist;
880                                 ptSet = true;
881                             }
882                             else
883                             {
884                                 ptDist = ndist;
885                                 ptSet = true;
886                             }
887                             break;
888                         }
889                         pb = cb;
890                         cb = theRes->NextAt (i, cb);
891                     }
892                     while (cb >= 0 && pb >= 0 && pb != fb);
893                 }
894             }
895         }
896         // loop over the edges to try to improve the distance
897         for (int i = 0; i < theRes->numberOfEdges(); i++)
898         {
899             Geom::Point sx = theRes->getPoint(theRes->getEdge(i).st).x;
900             Geom::Point ex = theRes->getPoint(theRes->getEdge(i).en).x;
901             Geom::Point nx = ex - sx;
902             double len = sqrt (dot(nx,nx));
903             if (len > 0.0001)
904             {
905                 Geom::Point   pxsx=px-sx;
906                 double ab = dot(nx,pxsx);
907                 if (ab > 0 && ab < len * len)
908                 {
909                     // we're in the zone of influence of the segment
910                     double ndist = (cross(pxsx,nx)) / len;
911                     if (arSet == false || fabs (ndist) < fabs (arDist))
912                     {
913                         arDist = ndist;
914                         arSet = true;
915                     }
916                 }
917             }
918         }
919         if (arSet || ptSet)
920         {
921             if (arSet == false)
922                 arDist = ptDist;
923             if (ptSet == false)
924                 ptDist = arDist;
925             if (fabs (ptDist) < fabs (arDist))
926                 dist = ptDist;
927             else
928                 dist = arDist;
929         }
930     }
932     delete theShape;
933     delete theRes;
935     return dist;
938 /**
939  * Computes a point on the offset;  used to set a "seed" position for
940  * the control knot.
941  *
942  * \return the topmost point on the offset.
943  */
944 void
945 sp_offset_top_point (SPOffset * offset, Geom::Point *px)
947     (*px) = Geom::Point(0, 0);
948     if (offset == NULL)
949         return;
951     if (offset->knotSet)
952     {
953         (*px) = offset->knot;
954         return;
955     }
957     SPCurve *curve = SP_SHAPE (offset)->getCurve();
958     if (curve == NULL)
959     {
960         sp_offset_set_shape (SP_SHAPE (offset));
961         curve = SP_SHAPE (offset)->getCurve();
962         if (curve == NULL)
963             return;
964     }
965     if (curve->is_empty())
966     {
967         curve->unref();
968         return;
969     }
971     Path *finalPath = new Path;
972     finalPath->LoadPathVector(curve->get_pathvector());
974     Shape *theShape = new Shape;
976     finalPath->Convert (1.0);
977     finalPath->Fill (theShape, 0);
979     if (theShape->hasPoints())
980     {
981         theShape->SortPoints ();
982         *px = theShape->getPoint(0).x;
983     }
985     delete theShape;
986     delete finalPath;
987     curve->unref();
990 // the listening functions
991 static void sp_offset_start_listening(SPOffset *offset,SPObject* to)
993     if ( to == NULL )
994         return;
996     offset->sourceObject = to;
997     offset->sourceRepr = SP_OBJECT_REPR(to);
999     offset->_delete_connection = SP_OBJECT(to)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_offset_delete_self), offset));
1000     offset->_transformed_connection = SP_ITEM(to)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_offset_move_compensate), offset));
1001     offset->_modified_connection = SP_OBJECT(to)->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_offset_source_modified), offset));
1004 static void sp_offset_quit_listening(SPOffset *offset)
1006     if ( offset->sourceObject == NULL )
1007         return;
1009     offset->_modified_connection.disconnect();
1010     offset->_delete_connection.disconnect();
1011     offset->_transformed_connection.disconnect();
1013     offset->sourceRepr = NULL;
1014     offset->sourceObject = NULL;
1017 static void
1018 sp_offset_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPOffset *offset)
1020     sp_offset_quit_listening(offset);
1021     if (offset->sourceRef) {
1022         SPItem *refobj = offset->sourceRef->getObject();
1023         if (refobj) sp_offset_start_listening(offset,refobj);
1024         offset->sourceDirty=true;
1025         SP_OBJECT(offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1026     }
1029 static void
1030 sp_offset_move_compensate(Geom::Matrix const *mp, SPItem */*original*/, SPOffset *self)
1032     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1033     guint mode = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_PARALLEL);
1034     if (mode == SP_CLONE_COMPENSATION_NONE) return;
1036     Geom::Matrix m(*mp);
1037     if (!(m.isTranslation())) return;
1039     // calculate the compensation matrix and the advertized movement matrix
1040     SPItem *item = SP_ITEM(self);
1042     Geom::Matrix compensate;
1043     Geom::Matrix advertized_move;
1045     if (mode == SP_CLONE_COMPENSATION_UNMOVED) {
1046         compensate = Geom::identity();
1047         advertized_move.setIdentity();
1048     } else if (mode == SP_CLONE_COMPENSATION_PARALLEL) {
1049         compensate = m;
1050         advertized_move = m;
1051     } else {
1052         g_assert_not_reached();
1053     }
1055     item->transform *= compensate;
1057     // commit the compensation
1058     item->doWriteTransform(SP_OBJECT_REPR(item), item->transform, &advertized_move);
1059     SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1062 static void
1063 sp_offset_delete_self(SPObject */*deleted*/, SPOffset *offset)
1065     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1066     guint const mode = prefs->getInt("/options/cloneorphans/value", SP_CLONE_ORPHANS_UNLINK);
1068     if (mode == SP_CLONE_ORPHANS_UNLINK) {
1069         // leave it be. just forget about the source
1070         sp_offset_quit_listening(offset);
1071         if ( offset->sourceHref ) g_free(offset->sourceHref);
1072         offset->sourceHref = NULL;
1073         offset->sourceRef->detach();
1074     } else if (mode == SP_CLONE_ORPHANS_DELETE) {
1075         SP_OBJECT(offset)->deleteObject();
1076     }
1079 static void
1080 sp_offset_source_modified (SPObject */*iSource*/, guint /*flags*/, SPItem *item)
1082     SPOffset *offset = SP_OFFSET(item);
1083     offset->sourceDirty=true;
1084     refresh_offset_source(offset);
1085     ((SPShape *) offset)->setShape ();
1088 static void
1089 refresh_offset_source(SPOffset* offset)
1091     if ( offset == NULL ) return;
1092     offset->sourceDirty=false;
1094     // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour
1095     // The bad case: no d attribute.  Must check that it's an SPShape and then take the outline.
1096     SPObject *refobj=offset->sourceObject;
1097     if ( refobj == NULL ) return;
1098     SPItem *item = SP_ITEM (refobj);
1100     SPCurve *curve=NULL;
1101     if (!SP_IS_SHAPE (item) && !SP_IS_TEXT (item)) return;
1102     if (SP_IS_SHAPE (item)) {
1103         curve = SP_SHAPE (item)->getCurve ();
1104         if (curve == NULL)
1105             return;
1106     }
1107     if (SP_IS_TEXT (item)) {
1108         curve = SP_TEXT (item)->getNormalizedBpath ();
1109         if (curve == NULL)
1110         return;
1111     }
1112     Path *orig = new Path;
1113     orig->LoadPathVector(curve->get_pathvector());
1114     curve->unref();
1117     // Finish up.
1118     {
1119         SPCSSAttr *css;
1120         const gchar *val;
1121         Shape *theShape = new Shape;
1122         Shape *theRes = new Shape;
1124         orig->ConvertWithBackData (1.0);
1125         orig->Fill (theShape, 0);
1127         css = sp_repr_css_attr (offset->sourceRepr , "style");
1128         val = sp_repr_css_property (css, "fill-rule", NULL);
1129         if (val && strcmp (val, "nonzero") == 0)
1130         {
1131             theRes->ConvertToShape (theShape, fill_nonZero);
1132         }
1133         else if (val && strcmp (val, "evenodd") == 0)
1134         {
1135             theRes->ConvertToShape (theShape, fill_oddEven);
1136         }
1137         else
1138         {
1139             theRes->ConvertToShape (theShape, fill_nonZero);
1140         }
1142         Path *originaux[1];
1143         originaux[0] = orig;
1144         Path *res = new Path;
1145         theRes->ConvertToForme (res, 1, originaux);
1147         delete theShape;
1148         delete theRes;
1150         char *res_d = res->svg_dump_path ();
1151         delete res;
1152         delete orig;
1154                 //XML Tree being used diectly here while it shouldn't be.
1155         SP_OBJECT (offset)->getRepr()->setAttribute("inkscape:original", res_d);
1157         free (res_d);
1158     }
1161 SPItem *
1162 sp_offset_get_source (SPOffset *offset)
1164     if (offset && offset->sourceRef) {
1165         SPItem *refobj = offset->sourceRef->getObject();
1166         if (SP_IS_ITEM (refobj))
1167             return (SPItem *) refobj;
1168     }
1169     return NULL;
1173 /*
1174   Local Variables:
1175   mode:c++
1176   c-file-style:"stroustrup"
1177   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1178   indent-tabs-mode:nil
1179   fill-column:99
1180   End:
1181 */
1182 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :