Code

new command: relink clone to copied object
[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>
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, SnapPointsIter p);
87 static void sp_offset_set_shape (SPShape * shape);
89 Path *bpath_to_liv_path (NArtBpath const * bpath);
91 static void refresh_offset_source(SPOffset* offset);
93 static void sp_offset_start_listening(SPOffset *offset,SPObject* to);
94 static void sp_offset_quit_listening(SPOffset *offset);
95 static void sp_offset_href_changed(SPObject *old_ref, SPObject *ref, SPOffset *offset);
96 static void sp_offset_move_compensate(NR::Matrix const *mp, SPItem *original, SPOffset *self);
97 static void sp_offset_delete_self(SPObject *deleted, SPOffset *self);
98 static void sp_offset_source_modified (SPObject *iSource, guint flags, SPItem *item);
101 // slow= source path->polygon->offset of polygon->polygon->path
102 // fast= source path->offset of source path->polygon->path
103 // fast is not mathematically correct, because computing the offset of a single
104 // cubic bezier patch is not trivial; in particular, there are problems with holes
105 // reappearing in offset when the radius becomes too large
106 static bool   use_slow_but_correct_offset_method=false;
109 // nothing special here, same for every class in sodipodi/inkscape
110 static SPShapeClass *parent_class;
112 /**
113  * Register SPOffset class and return its type number.
114  */
115 GType
116 sp_offset_get_type (void)
118     static GType offset_type = 0;
120     if (!offset_type)
121     {
122         GTypeInfo offset_info = {
123             sizeof (SPOffsetClass),
124             NULL,                       /* base_init */
125             NULL,                       /* base_finalize */
126             (GClassInitFunc) sp_offset_class_init,
127             NULL,                       /* class_finalize */
128             NULL,                       /* class_data */
129             sizeof (SPOffset),
130             16,                 /* n_preallocs */
131             (GInstanceInitFunc) sp_offset_init,
132             NULL,                       /* value_table */
133         };
134         offset_type =
135             g_type_register_static (SP_TYPE_SHAPE, "SPOffset", &offset_info,
136                                     (GTypeFlags) 0);
137     }
138     return offset_type;
141 /**
142  * SPOffset vtable initialization.
143  */
144 static void
145 sp_offset_class_init(SPOffsetClass *klass)
147     GObjectClass  *gobject_class = (GObjectClass *) klass;
148     SPObjectClass *sp_object_class = (SPObjectClass *) klass;
149     SPItemClass   *item_class = (SPItemClass *) klass;
150     SPShapeClass  *shape_class = (SPShapeClass *) klass;
152     parent_class = (SPShapeClass *) g_type_class_ref (SP_TYPE_SHAPE);
154     gobject_class->finalize = sp_offset_finalize;
156     sp_object_class->build = sp_offset_build;
157     sp_object_class->write = sp_offset_write;
158     sp_object_class->set = sp_offset_set;
159     sp_object_class->update = sp_offset_update;
160     sp_object_class->release = sp_offset_release;
162     item_class->description = sp_offset_description;
163     item_class->snappoints = sp_offset_snappoints;
165     shape_class->set_shape = sp_offset_set_shape;
168 /**
169  * Callback for SPOffset object initialization.
170  */
171 static void
172 sp_offset_init(SPOffset *offset)
174     offset->rad = 1.0;
175     offset->original = NULL;
176     offset->originalPath = NULL;
177     offset->knotSet = false;
178     offset->sourceDirty=false;
179     offset->isUpdating=false;
180     // init various connections
181     offset->sourceHref = NULL;
182     offset->sourceRepr = NULL;
183     offset->sourceObject = NULL;
184     new (&offset->_modified_connection) sigc::connection();
185     new (&offset->_delete_connection) sigc::connection();
186     new (&offset->_changed_connection) sigc::connection();
187     new (&offset->_transformed_connection) sigc::connection();
188     // set up the uri reference
189     offset->sourceRef = new SPUseReference(SP_OBJECT(offset));
190     offset->_changed_connection = offset->sourceRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_offset_href_changed), offset));
193 /**
194  * Callback for SPOffset finalization.
195  */
196 static void
197 sp_offset_finalize(GObject *obj)
199     SPOffset *offset = (SPOffset *) obj;
201     delete offset->sourceRef;
203     offset->_modified_connection.disconnect();
204     offset->_modified_connection.~connection();
205     offset->_delete_connection.disconnect();
206     offset->_delete_connection.~connection();
207     offset->_changed_connection.disconnect();
208     offset->_changed_connection.~connection();
209     offset->_transformed_connection.disconnect();
210     offset->_transformed_connection.~connection();
213 /**
214  * Virtual build: set offset attributes from corresponding repr.
215  */
216 static void
217 sp_offset_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
219     if (((SPObjectClass *) parent_class)->build)
220         ((SPObjectClass *) parent_class)->build (object, document, repr);
222     if (object->repr->attribute("inkscape:radius")) {
223         sp_object_read_attr (object, "inkscape:radius");
224     } else {
225         gchar const *oldA = object->repr->attribute("sodipodi:radius");
226         object->repr->setAttribute("inkscape:radius",oldA);
227         object->repr->setAttribute("sodipodi:radius",NULL);
229         sp_object_read_attr (object, "inkscape:radius");
230     }
231     if (object->repr->attribute("inkscape:original")) {
232         sp_object_read_attr (object, "inkscape:original");
233     } else {
234         gchar const *oldA = object->repr->attribute("sodipodi:original");
235         object->repr->setAttribute("inkscape:original",oldA);
236         object->repr->setAttribute("sodipodi:original",NULL);
238         sp_object_read_attr (object, "inkscape:original");
239     }
240     if (object->repr->attribute("xlink:href")) {
241         sp_object_read_attr(object, "xlink:href");
242     } else {
243         gchar const *oldA = object->repr->attribute("inkscape:href");
244         if (oldA) {
245             size_t lA = strlen(oldA);
246             char *nA=(char*)malloc((lA+1)*sizeof(char));
247             memcpy(nA+1,oldA,lA*sizeof(char));
248             nA[0]='#';
249             nA[lA+1]=0;
250             object->repr->setAttribute("xlink:href",nA);
251             free(nA);
252             object->repr->setAttribute("inkscape:href",NULL);
253         }
254         sp_object_read_attr (object, "xlink:href");
255     }
258 /**
259  * Virtual write: write offset attributes to corresponding repr.
260  */
261 static Inkscape::XML::Node *
262 sp_offset_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
264     SPOffset *offset = SP_OFFSET (object);
266     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
267         repr = xml_doc->createElement("svg:path");
268     }
270     if (flags & SP_OBJECT_WRITE_EXT) {
271         /** \todo
272          * Fixme: we may replace these attributes by
273          * inkscape:offset="cx cy exp revo rad arg t0"
274          */
275         repr->setAttribute("sodipodi:type", "inkscape:offset");
276         sp_repr_set_svg_double(repr, "inkscape:radius", offset->rad);
277         repr->setAttribute("inkscape:original", offset->original);
278         repr->setAttribute("inkscape:href", offset->sourceHref);
279     }
282     // Make sure the object has curve
283     SPCurve *curve = sp_shape_get_curve (SP_SHAPE (offset));
284     if (curve == NULL) {
285         sp_offset_set_shape (SP_SHAPE (offset));
286     }
288     // write that curve to "d"
289     char *d = sp_svg_write_path (((SPShape *) offset)->curve->get_pathvector());
290     repr->setAttribute("d", d);
291     g_free (d);
293     if (((SPObjectClass *) (parent_class))->write)
294         ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr,
295                                                    flags | SP_SHAPE_WRITE_PATH);
297     return repr;
300 /**
301  * Virtual release callback.
302  */
303 static void
304 sp_offset_release(SPObject *object)
306     SPOffset *offset = (SPOffset *) object;
308     if (offset->original) free (offset->original);
309     if (offset->originalPath) delete ((Path *) offset->originalPath);
310     offset->original = NULL;
311     offset->originalPath = NULL;
313     sp_offset_quit_listening(offset);
315     offset->_changed_connection.disconnect();
316     g_free(offset->sourceHref);
317     offset->sourceHref = NULL;
318     offset->sourceRef->detach();
320     if (((SPObjectClass *) parent_class)->release) {
321         ((SPObjectClass *) parent_class)->release (object);
322     }
326 /**
327  * Set callback: the function that is called whenever a change is made to
328  * the description of the object.
329  */
330 static void
331 sp_offset_set(SPObject *object, unsigned key, gchar const *value)
333     SPOffset *offset = SP_OFFSET (object);
335     if ( offset->sourceDirty ) refresh_offset_source(offset);
337     /* fixme: we should really collect updates */
338     switch (key)
339     {
340         case SP_ATTR_INKSCAPE_ORIGINAL:
341         case SP_ATTR_SODIPODI_ORIGINAL:
342             if (value == NULL) {
343             } else {
344                 if (offset->original) {
345                     free (offset->original);
346                     delete ((Path *) offset->originalPath);
347                     offset->original = NULL;
348                     offset->originalPath = NULL;
349                 }
350                 NArtBpath *bpath;
351                 SPCurve *curve;
353                 offset->original = strdup (value);
355                 bpath = sp_svg_read_path (offset->original);
356                 curve = SPCurve::new_from_bpath (bpath);        // curve se chargera de detruire bpath
357                 g_assert (curve != NULL);
358                 offset->originalPath = bpath_to_liv_path (SP_CURVE_BPATH(curve));
359                 curve->unref();
361                 offset->knotSet = false;
362                 if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
363             }
364             break;
365         case SP_ATTR_INKSCAPE_RADIUS:
366         case SP_ATTR_SODIPODI_RADIUS:
367             if (!sp_svg_length_read_computed_absolute (value, &offset->rad)) {
368                 if (fabs (offset->rad) < 0.01)
369                     offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
370                 offset->knotSet = false; // knotset=false because it's not set from the context
371             }
372             if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
373             break;
374         case SP_ATTR_INKSCAPE_HREF:
375         case SP_ATTR_XLINK_HREF:
376             if ( value == NULL ) {
377                 sp_offset_quit_listening(offset);
378                 if ( offset->sourceHref ) g_free(offset->sourceHref);
379                 offset->sourceHref = NULL;
380                 offset->sourceRef->detach();
381             } else {
382                 if ( offset->sourceHref && ( strcmp(value, offset->sourceHref) == 0 ) ) {
383                 } else {
384                     if ( offset->sourceHref ) g_free(offset->sourceHref);
385                     offset->sourceHref = g_strdup(value);
386                     try {
387                         offset->sourceRef->attach(Inkscape::URI(value));
388                     } catch (Inkscape::BadURIException &e) {
389                         g_warning("%s", e.what());
390                         offset->sourceRef->detach();
391                     }
392                 }
393             }
394             break;
395         default:
396             if (((SPObjectClass *) parent_class)->set)
397                 ((SPObjectClass *) parent_class)->set (object, key, value);
398             break;
399     }
402 /**
403  * Update callback: the object has changed, recompute its shape.
404  */
405 static void
406 sp_offset_update(SPObject *object, SPCtx *ctx, guint flags)
408     SPOffset* offset = SP_OFFSET(object);
409     offset->isUpdating=true; // prevent sp_offset_set from requesting updates
410     if ( offset->sourceDirty ) refresh_offset_source(offset);
411     if (flags &
412         (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG |
413          SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
414         sp_shape_set_shape ((SPShape *) object);
415     }
416     offset->isUpdating=false;
418     if (((SPObjectClass *) parent_class)->update)
419         ((SPObjectClass *) parent_class)->update (object, ctx, flags);
422 /**
423  * Returns a textual description of object.
424  */
425 static gchar *
426 sp_offset_description(SPItem *item)
428     SPOffset *offset = SP_OFFSET (item);
430     if ( offset->sourceHref ) {
431         // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
432         return g_strdup_printf(_("<b>Linked offset</b>, %s by %f pt"),
433                                (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad));
434     } else {
435         // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
436         return g_strdup_printf(_("<b>Dynamic offset</b>, %s by %f pt"),
437                                (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad));
438     }
441 /**
442  * Converts an NArtBpath (like the one stored in a SPCurve) into a
443  * livarot Path. Duplicate of splivarot.
444  */
445 Path *
446 bpath_to_liv_path(NArtBpath const *bpath)
448     if (bpath == NULL)
449         return NULL;
451     Path *dest = new Path;
452     dest->SetBackData (false);
453     {
454         int i;
455         bool closed = false;
456         float lastX = 0.0;
457         float lastY = 0.0;
459         for (i = 0; bpath[i].code != NR_END; i++)
460         {
461             switch (bpath[i].code)
462             {
463                 case NR_LINETO:
464                     lastX = bpath[i].x3;
465                     lastY = bpath[i].y3;
466                     {
467                         NR::Point  tmp(lastX,lastY);
468                         dest->LineTo (tmp);
469                     }
470                     break;
472                 case NR_CURVETO:
473                 {
474                     NR::Point  tmp(bpath[i].x3, bpath[i].y3);
475                     NR::Point  tms;
476                     tms[0]=3 * (bpath[i].x1 - lastX);
477                     tms[1]=3 * (bpath[i].y1 - lastY);
478                     NR::Point  tme;
479                     tme[0]=3 * (bpath[i].x3 - bpath[i].x2);
480                     tme[1]= 3 * (bpath[i].y3 - bpath[i].y2);
481                     dest->CubicTo (tmp,tms,tme);
482                 }
483                 lastX = bpath[i].x3;
484                 lastY = bpath[i].y3;
485                 break;
487                 case NR_MOVETO_OPEN:
488                 case NR_MOVETO:
489                     if (closed)
490                         dest->Close ();
491                     closed = (bpath[i].code == NR_MOVETO);
492                     lastX = bpath[i].x3;
493                     lastY = bpath[i].y3;
494                     {
495                         NR::Point tmp(lastX,lastY);
496                         dest->MoveTo(tmp);
497                     }
498                     break;
499                 default:
500                     break;
501             }
502         }
503         if (closed)
504             dest->Close ();
505     }
507     return dest;
510 /**
511  * Compute and set shape's offset.
512  */
513 static void
514 sp_offset_set_shape(SPShape *shape)
516     SPOffset *offset = SP_OFFSET (shape);
518     if ( offset->originalPath == NULL ) {
519         // oops : no path?! (the offset object should do harakiri)
520         return;
521     }
522 #ifdef OFFSET_VERBOSE
523     g_print ("rad=%g\n", offset->rad);
524 #endif
525     // au boulot
527     if ( fabs(offset->rad) < 0.01 ) {
528         // grosso modo: 0
529         // just put the source shape as the offseted one, no one will notice
530         // it's also useless to compute the offset with a 0 radius
532         const char *res_d = SP_OBJECT(shape)->repr->attribute("inkscape:original");
533         if ( res_d ) {
534             NArtBpath *bpath = sp_svg_read_path (res_d);
535             SPCurve *c = SPCurve::new_from_bpath (bpath);
536             g_assert(c != NULL);
537             sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE);
538             c->unref();
539         }
540         return;
541     }
543     // extra paraniac careful check. the preceding if () should take care of this case
544     if (fabs (offset->rad) < 0.01)
545         offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
547     Path *orig = new Path;
548     orig->Copy ((Path *) offset->originalPath);
550     if ( use_slow_but_correct_offset_method == false ) {
551         // version par outline
552         Shape *theShape = new Shape;
553         Shape *theRes = new Shape;
554         Path *originaux[1];
555         Path *res = new Path;
556         res->SetBackData (false);
558         // and now: offset
559         float o_width;
560         if (offset->rad >= 0)
561         {
562             o_width = offset->rad;
563             orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0);
564         }
565         else
566         {
567             o_width = -offset->rad;
568             orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0);
569         }
571         if (o_width >= 1.0)
572         {
573             //      res->ConvertForOffset (1.0, orig, offset->rad);
574             res->ConvertWithBackData (1.0);
575         }
576         else
577         {
578             //      res->ConvertForOffset (o_width, orig, offset->rad);
579             res->ConvertWithBackData (o_width);
580         }
581         res->Fill (theShape, 0);
582         theRes->ConvertToShape (theShape, fill_positive);
583         originaux[0] = res;
585         theRes->ConvertToForme (orig, 1, originaux);
587         SPItem *item = shape;
588         NR::Maybe<NR::Rect> bbox = sp_item_bbox_desktop (item);
589         if ( bbox && !bbox->isEmpty() ) {
590             gdouble size = L2(bbox->dimensions());
591             gdouble const exp = NR::expansion(item->transform);
592             if (exp != 0)
593                 size /= exp;
594             orig->Coalesce (size * 0.001);
595             //g_print ("coa %g    exp %g    item %p\n", size * 0.001, exp, item);
596         }
599         //  if (o_width >= 1.0)
600         //  {
601         //    orig->Coalesce (0.1);  // small treshhold, since we only want to get rid of small segments
602         // the curve should already be computed by the Outline() function
603         //   orig->ConvertEvenLines (1.0);
604         //   orig->Simplify (0.5);
605         //  }
606         //  else
607         //  {
608         //          orig->Coalesce (0.1*o_width);
609         //   orig->ConvertEvenLines (o_width);
610         //   orig->Simplify (0.5 * o_width);
611         //  }
613         delete theShape;
614         delete theRes;
615         delete res;
616     } else {
617         // version par makeoffset
618         Shape *theShape = new Shape;
619         Shape *theRes = new Shape;
622         // and now: offset
623         float o_width;
624         if (offset->rad >= 0)
625         {
626             o_width = offset->rad;
627         }
628         else
629         {
630             o_width = -offset->rad;
631         }
633         // one has to have a measure of the details
634         if (o_width >= 1.0)
635         {
636             orig->ConvertWithBackData (0.5);
637         }
638         else
639         {
640             orig->ConvertWithBackData (0.5*o_width);
641         }
642         orig->Fill (theShape, 0);
643         theRes->ConvertToShape (theShape, fill_positive);
644         Path *originaux[1];
645         originaux[0]=orig;
646         Path *res = new Path;
647         theRes->ConvertToForme (res, 1, originaux);
648         int    nbPart=0;
649         Path** parts=res->SubPaths(nbPart,true);
650         char   *holes=(char*)malloc(nbPart*sizeof(char));
651         // we offset contours separately, because we can.
652         // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes
653         {
654             Shape* onePart=new Shape;
655             Shape* oneCleanPart=new Shape;
656             theShape->Reset();
657             for (int i=0;i<nbPart;i++) {
658                 double partSurf=parts[i]->Surface();
659                 parts[i]->Convert(1.0);
660                 {
661                     // raffiner si besoin
662                     double  bL,bT,bR,bB;
663                     parts[i]->PolylineBoundingBox(bL,bT,bR,bB);
664                     double  mesure=((bR-bL)+(bB-bT))*0.5;
665                     if ( mesure < 10.0 ) {
666                         parts[i]->Convert(0.02*mesure);
667                     }
668                 }
669                 if ( partSurf < 0 ) { // inverse par rapport a la realite
670                     // plein
671                     holes[i]=0;
672                     parts[i]->Fill(oneCleanPart,0);
673                     onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges
674                     oneCleanPart->MakeOffset(onePart,offset->rad,join_round,20.0);
675                     onePart->ConvertToShape(oneCleanPart,fill_positive);
677                     onePart->CalcBBox();
678                     double  typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
679                     if ( typicalSize < 0.05 ) typicalSize=0.05;
680                     typicalSize*=0.01;
681                     if ( typicalSize > 1.0 ) typicalSize=1.0;
682                     onePart->ConvertToForme (parts[i]);
683                     parts[i]->ConvertEvenLines (typicalSize);
684                     parts[i]->Simplify (typicalSize);
685                     double nPartSurf=parts[i]->Surface();
686                     if ( nPartSurf >= 0 ) {
687                         // inversion de la surface -> disparait
688                         delete parts[i];
689                         parts[i]=NULL;
690                     } else {
691                     }
692 /*          int  firstP=theShape->nbPt;
693             for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
694             for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/
695                 } else {
696                     // trou
697                     holes[i]=1;
698                     parts[i]->Fill(oneCleanPart,0,false,true,true);
699                     onePart->ConvertToShape(oneCleanPart,fill_positive);
700                     oneCleanPart->MakeOffset(onePart,-offset->rad,join_round,20.0);
701                     onePart->ConvertToShape(oneCleanPart,fill_positive);
702 //          for (int j=0;j<onePart->nbAr;j++) onePart->Inverse(j); // pas oublier de reinverser
704                     onePart->CalcBBox();
705                     double  typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
706                     if ( typicalSize < 0.05 ) typicalSize=0.05;
707                     typicalSize*=0.01;
708                     if ( typicalSize > 1.0 ) typicalSize=1.0;
709                     onePart->ConvertToForme (parts[i]);
710                     parts[i]->ConvertEvenLines (typicalSize);
711                     parts[i]->Simplify (typicalSize);
712                     double nPartSurf=parts[i]->Surface();
713                     if ( nPartSurf >= 0 ) {
714                         // inversion de la surface -> disparait
715                         delete parts[i];
716                         parts[i]=NULL;
717                     } else {
718                     }
720                     /*         int  firstP=theShape->nbPt;
721                                for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
722                                for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/
723                 }
724 //        delete parts[i];
725             }
726 //      theShape->MakeOffset(theRes,offset->rad,join_round,20.0);
727             delete onePart;
728             delete oneCleanPart;
729         }
730         if ( nbPart > 1 ) {
731             theShape->Reset();
732             for (int i=0;i<nbPart;i++) {
733                 if ( parts[i] ) {
734                     parts[i]->ConvertWithBackData(1.0);
735                     if ( holes[i] ) {
736                         parts[i]->Fill(theShape,i,true,true,true);
737                     } else {
738                         parts[i]->Fill(theShape,i,true,true,false);
739                     }
740                 }
741             }
742             theRes->ConvertToShape (theShape, fill_positive);
743             theRes->ConvertToForme (orig,nbPart,parts);
744             for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
745         } else if ( nbPart == 1 ) {
746             orig->Copy(parts[0]);
747             for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
748         } else {
749             orig->Reset();
750         }
751 //    theRes->ConvertToShape (theShape, fill_positive);
752 //    theRes->ConvertToForme (orig);
754 /*    if (o_width >= 1.0) {
755       orig->ConvertEvenLines (1.0);
756       orig->Simplify (1.0);
757       } else {
758       orig->ConvertEvenLines (1.0*o_width);
759       orig->Simplify (1.0 * o_width);
760       }*/
762         if ( parts ) free(parts);
763         if ( holes ) free(holes);
764         delete res;
765         delete theShape;
766         delete theRes;
767     }
768     {
769         char *res_d = NULL;
770         if (orig->descr_cmd.size() <= 1)
771         {
772             // Aie.... nothing left.
773             res_d = strdup ("M 0 0 L 0 0 z");
774             //printf("%s\n",res_d);
775         }
776         else
777         {
779             res_d = orig->svg_dump_path ();
780         }
781         delete orig;
783         NArtBpath *bpath = sp_svg_read_path (res_d);
784         SPCurve *c = SPCurve::new_from_bpath (bpath);
785         g_assert(c != NULL);
786         sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE);
787         c->unref();
789         free (res_d);
790     }
793 /**
794  * Virtual snappoints function.
795  */
796 static void sp_offset_snappoints(SPItem const *item, SnapPointsIter p)
798     if (((SPItemClass *) parent_class)->snappoints) {
799         ((SPItemClass *) parent_class)->snappoints (item, p);
800     }
804 // utilitaires pour les poignees
805 // used to get the distance to the shape: distance to polygon give the fabs(radius), we still need
806 // the sign. for edges, it's easy to determine which side the point is on, for points of the polygon
807 // it's trickier: we need to identify which angle the point is in; to that effect, we take each
808 // successive clockwise angle (A,C) and check if the vector B given by the point is in the angle or
809 // outside.
810 // another method would be to use the Winding() function to test whether the point is inside or outside
811 // the polygon (it would be wiser to do so, in fact, but i like being stupid)
813 /**
814  *
815  * \todo
816  * FIXME: This can be done using linear operations, more stably and
817  *  faster.  method: transform A and C into B's space, A should be
818  *  negative and B should be positive in the orthogonal component.  I
819  *  think this is equivalent to
820  *  dot(A, rot90(B))*dot(C, rot90(B)) == -1.
821  *    -- njh
822  */
823 bool
824 vectors_are_clockwise (NR::Point A, NR::Point B, NR::Point C)
826     using NR::rot90;
827     double ab_s = dot(A, rot90(B));
828     double ab_c = dot(A, B);
829     double bc_s = dot(B, rot90(C));
830     double bc_c = dot(B, C);
831     double ca_s = dot(C, rot90(A));
832     double ca_c = dot(C, A);
834     double ab_a = acos (ab_c);
835     if (ab_c <= -1.0)
836         ab_a = M_PI;
837     if (ab_c >= 1.0)
838         ab_a = 0;
839     if (ab_s < 0)
840         ab_a = 2 * M_PI - ab_a;
841     double bc_a = acos (bc_c);
842     if (bc_c <= -1.0)
843         bc_a = M_PI;
844     if (bc_c >= 1.0)
845         bc_a = 0;
846     if (bc_s < 0)
847         bc_a = 2 * M_PI - bc_a;
848     double ca_a = acos (ca_c);
849     if (ca_c <= -1.0)
850         ca_a = M_PI;
851     if (ca_c >= 1.0)
852         ca_a = 0;
853     if (ca_s < 0)
854         ca_a = 2 * M_PI - ca_a;
856     double lim = 2 * M_PI - ca_a;
858     if (ab_a < lim)
859         return true;
860     return false;
863 /**
864  * Distance to the original path; that function is called from object-edit
865  * to set the radius when the control knot moves.
866  *
867  * The sign of the result is the radius we're going to offset the shape with,
868  * so result > 0 ==outset and result < 0 ==inset. thus result<0 means
869  * 'px inside source'.
870  */
871 double
872 sp_offset_distance_to_original (SPOffset * offset, NR::Point px)
874     if (offset == NULL || offset->originalPath == NULL
875         || ((Path *) offset->originalPath)->descr_cmd.size() <= 1)
876         return 1.0;
877     double dist = 1.0;
878     Shape *theShape = new Shape;
879     Shape *theRes = new Shape;
881     /** \todo
882      * Awfully damn stupid method: uncross the source path EACH TIME you
883      * need to compute the distance. The good way to do this would be to
884      * store the uncrossed source path somewhere, and delete it when the
885      * context is finished. Hopefully this part is much faster than actually
886      * computing the offset (which happen just after), so the time spent in
887      * this function should end up being negligible with respect to the
888      * delay of one context.
889      */
890     // move
891     ((Path *) offset->originalPath)->Convert (1.0);
892     ((Path *) offset->originalPath)->Fill (theShape, 0);
893     theRes->ConvertToShape (theShape, fill_oddEven);
895     if (theRes->numberOfEdges() <= 1)
896     {
898     }
899     else
900     {
901         double ptDist = -1.0;
902         bool ptSet = false;
903         double arDist = -1.0;
904         bool arSet = false;
905         // first get the minimum distance to the points
906         for (int i = 0; i < theRes->numberOfPoints(); i++)
907         {
908             if (theRes->getPoint(i).totalDegree() > 0)
909             {
910                 NR::Point nx = theRes->getPoint(i).x;
911                 NR::Point nxpx = px-nx;
912                 double ndist = sqrt (dot(nxpx,nxpx));
913                 if (ptSet == false || fabs (ndist) < fabs (ptDist))
914                 {
915                     // we have a new minimum distance
916                     // now we need to wheck if px is inside or outside (for the sign)
917                     nx = px - theRes->getPoint(i).x;
918                     double nlen = sqrt (dot(nx , nx));
919                     nx /= nlen;
920                     int pb, cb, fb;
921                     fb = theRes->getPoint(i).incidentEdge[LAST];
922                     pb = theRes->getPoint(i).incidentEdge[LAST];
923                     cb = theRes->getPoint(i).incidentEdge[FIRST];
924                     do
925                     {
926                         // one angle
927                         NR::Point prx, nex;
928                         prx = theRes->getEdge(pb).dx;
929                         nlen = sqrt (dot(prx, prx));
930                         prx /= nlen;
931                         nex = theRes->getEdge(cb).dx;
932                         nlen = sqrt (dot(nex , nex));
933                         nex /= nlen;
934                         if (theRes->getEdge(pb).en == i)
935                         {
936                             prx = -prx;
937                         }
938                         if (theRes->getEdge(cb).en == i)
939                         {
940                             nex = -nex;
941                         }
943                         if (vectors_are_clockwise (nex, nx, prx))
944                         {
945                             // we're in that angle. set the sign, and exit that loop
946                             if (theRes->getEdge(cb).st == i)
947                             {
948                                 ptDist = -ndist;
949                                 ptSet = true;
950                             }
951                             else
952                             {
953                                 ptDist = ndist;
954                                 ptSet = true;
955                             }
956                             break;
957                         }
958                         pb = cb;
959                         cb = theRes->NextAt (i, cb);
960                     }
961                     while (cb >= 0 && pb >= 0 && pb != fb);
962                 }
963             }
964         }
965         // loop over the edges to try to improve the distance
966         for (int i = 0; i < theRes->numberOfEdges(); i++)
967         {
968             NR::Point sx = theRes->getPoint(theRes->getEdge(i).st).x;
969             NR::Point ex = theRes->getPoint(theRes->getEdge(i).en).x;
970             NR::Point nx = ex - sx;
971             double len = sqrt (dot(nx,nx));
972             if (len > 0.0001)
973             {
974                 NR::Point   pxsx=px-sx;
975                 double ab = dot(nx,pxsx);
976                 if (ab > 0 && ab < len * len)
977                 {
978                     // we're in the zone of influence of the segment
979                     double ndist = (cross(pxsx,nx)) / len;
980                     if (arSet == false || fabs (ndist) < fabs (arDist))
981                     {
982                         arDist = ndist;
983                         arSet = true;
984                     }
985                 }
986             }
987         }
988         if (arSet || ptSet)
989         {
990             if (arSet == false)
991                 arDist = ptDist;
992             if (ptSet == false)
993                 ptDist = arDist;
994             if (fabs (ptDist) < fabs (arDist))
995                 dist = ptDist;
996             else
997                 dist = arDist;
998         }
999     }
1001     delete theShape;
1002     delete theRes;
1004     return dist;
1007 /**
1008  * Computes a point on the offset;  used to set a "seed" position for
1009  * the control knot.
1010  *
1011  * \return the topmost point on the offset.
1012  */
1013 void
1014 sp_offset_top_point (SPOffset * offset, NR::Point *px)
1016     (*px) = NR::Point(0, 0);
1017     if (offset == NULL)
1018         return;
1020     if (offset->knotSet)
1021     {
1022         (*px) = offset->knot;
1023         return;
1024     }
1026     SPCurve *curve = sp_shape_get_curve (SP_SHAPE (offset));
1027     if (curve == NULL)
1028     {
1029         sp_offset_set_shape (SP_SHAPE (offset));
1030         curve = sp_shape_get_curve (SP_SHAPE (offset));
1031         if (curve == NULL)
1032             return;
1033     }
1035     Path *finalPath = bpath_to_liv_path (SP_CURVE_BPATH(curve));
1036     if (finalPath == NULL)
1037     {
1038         curve->unref();
1039         return;
1040     }
1042     Shape *theShape = new Shape;
1044     finalPath->Convert (1.0);
1045     finalPath->Fill (theShape, 0);
1047     if (theShape->hasPoints())
1048     {
1049         theShape->SortPoints ();
1050         *px = theShape->getPoint(0).x;
1051     }
1053     delete theShape;
1054     delete finalPath;
1055     curve->unref();
1058 // the listening functions
1059 static void sp_offset_start_listening(SPOffset *offset,SPObject* to)
1061     if ( to == NULL )
1062         return;
1064     offset->sourceObject = to;
1065     offset->sourceRepr = SP_OBJECT_REPR(to);
1067     offset->_delete_connection = SP_OBJECT(to)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_offset_delete_self), offset));
1068     offset->_transformed_connection = SP_ITEM(to)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_offset_move_compensate), offset));
1069     offset->_modified_connection = SP_OBJECT(to)->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_offset_source_modified), offset));
1072 static void sp_offset_quit_listening(SPOffset *offset)
1074     if ( offset->sourceObject == NULL )
1075         return;
1077     offset->_modified_connection.disconnect();
1078     offset->_delete_connection.disconnect();
1079     offset->_transformed_connection.disconnect();
1081     offset->sourceRepr = NULL;
1082     offset->sourceObject = NULL;
1085 static void
1086 sp_offset_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPOffset *offset)
1088     sp_offset_quit_listening(offset);
1089     if (offset->sourceRef) {
1090         SPItem *refobj = offset->sourceRef->getObject();
1091         if (refobj) sp_offset_start_listening(offset,refobj);
1092         offset->sourceDirty=true;
1093         SP_OBJECT(offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1094     }
1097 static void
1098 sp_offset_move_compensate(NR::Matrix const *mp, SPItem */*original*/, SPOffset *self)
1100     guint mode = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_PARALLEL);
1101     if (mode == SP_CLONE_COMPENSATION_NONE) return;
1103     NR::Matrix m(*mp);
1104     if (!(m.is_translation())) return;
1106     // calculate the compensation matrix and the advertized movement matrix
1107     SPItem *item = SP_ITEM(self);
1109     NR::Matrix compensate;
1110     NR::Matrix advertized_move;
1112     if (mode == SP_CLONE_COMPENSATION_UNMOVED) {
1113         compensate = NR::identity();
1114         advertized_move.set_identity();
1115     } else if (mode == SP_CLONE_COMPENSATION_PARALLEL) {
1116         compensate = m;
1117         advertized_move = m;
1118     } else {
1119         g_assert_not_reached();
1120     }
1122     item->transform *= compensate;
1124     // commit the compensation
1125     sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform, &advertized_move);
1126     SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1129 static void
1130 sp_offset_delete_self(SPObject */*deleted*/, SPOffset *offset)
1132     guint const mode = prefs_get_int_attribute("options.cloneorphans", "value", SP_CLONE_ORPHANS_UNLINK);
1134     if (mode == SP_CLONE_ORPHANS_UNLINK) {
1135         // leave it be. just forget about the source
1136         sp_offset_quit_listening(offset);
1137         if ( offset->sourceHref ) g_free(offset->sourceHref);
1138         offset->sourceHref = NULL;
1139         offset->sourceRef->detach();
1140     } else if (mode == SP_CLONE_ORPHANS_DELETE) {
1141         SP_OBJECT(offset)->deleteObject();
1142     }
1145 static void
1146 sp_offset_source_modified (SPObject */*iSource*/, guint /*flags*/, SPItem *item)
1148     SPOffset *offset = SP_OFFSET(item);
1149     offset->sourceDirty=true;
1150     refresh_offset_source(offset);
1151     sp_shape_set_shape ((SPShape *) offset);
1154 static void
1155 refresh_offset_source(SPOffset* offset)
1157     if ( offset == NULL ) return;
1158     offset->sourceDirty=false;
1159     Path *orig = NULL;
1161     // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour
1162     // The bad case: no d attribute.  Must check that it's an SPShape and then take the outline.
1163     SPObject *refobj=offset->sourceObject;
1164     if ( refobj == NULL ) return;
1165     SPItem *item = SP_ITEM (refobj);
1167     SPCurve *curve=NULL;
1168     if (!SP_IS_SHAPE (item) && !SP_IS_TEXT (item)) return;
1169     if (SP_IS_SHAPE (item)) {
1170         curve = sp_shape_get_curve (SP_SHAPE (item));
1171         if (curve == NULL)
1172             return;
1173     }
1174     if (SP_IS_TEXT (item)) {
1175         curve = SP_TEXT (item)->getNormalizedBpath ();
1176         if (curve == NULL)
1177             return;
1178     }
1179     orig = bpath_to_liv_path (SP_CURVE_BPATH(curve));
1180     curve->unref();
1183     // Finish up.
1184     {
1185         SPCSSAttr *css;
1186         const gchar *val;
1187         Shape *theShape = new Shape;
1188         Shape *theRes = new Shape;
1190         orig->ConvertWithBackData (1.0);
1191         orig->Fill (theShape, 0);
1193         css = sp_repr_css_attr (offset->sourceRepr , "style");
1194         val = sp_repr_css_property (css, "fill-rule", NULL);
1195         if (val && strcmp (val, "nonzero") == 0)
1196         {
1197             theRes->ConvertToShape (theShape, fill_nonZero);
1198         }
1199         else if (val && strcmp (val, "evenodd") == 0)
1200         {
1201             theRes->ConvertToShape (theShape, fill_oddEven);
1202         }
1203         else
1204         {
1205             theRes->ConvertToShape (theShape, fill_nonZero);
1206         }
1208         Path *originaux[1];
1209         originaux[0] = orig;
1210         Path *res = new Path;
1211         theRes->ConvertToForme (res, 1, originaux);
1213         delete theShape;
1214         delete theRes;
1216         char *res_d = res->svg_dump_path ();
1217         delete res;
1218         delete orig;
1220         SP_OBJECT (offset)->repr->setAttribute("inkscape:original", res_d);
1222         free (res_d);
1223     }
1226 SPItem *
1227 sp_offset_get_source (SPOffset *offset)
1229     if (offset && offset->sourceRef) {
1230         SPItem *refobj = offset->sourceRef->getObject();
1231         if (SP_IS_ITEM (refobj))
1232             return (SPItem *) refobj;
1233     }
1234     return NULL;
1238 /*
1239   Local Variables:
1240   mode:c++
1241   c-file-style:"stroustrup"
1242   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1243   indent-tabs-mode:nil
1244   fill-column:99
1245   End:
1246 */
1247 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :