Code

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