Code

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