Code

bulk trailing spaces removal. consistency through MD5 of binary
[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->_delete_connection) sigc::connection();
183     new (&offset->_changed_connection) sigc::connection();
184     new (&offset->_transformed_connection) sigc::connection();
185     // set up the uri reference
186     offset->sourceRef = new SPUseReference(SP_OBJECT(offset));
187     offset->_changed_connection = offset->sourceRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_offset_href_changed), offset));
190 /**
191  * Callback for SPOffset finalization.
192  */
193 static void
194 sp_offset_finalize(GObject *obj)
196     SPOffset *offset = (SPOffset *) obj;
198     delete offset->sourceRef;
199     offset->_delete_connection.~connection();
200     offset->_changed_connection.~connection();
201     offset->_transformed_connection.~connection();
204 /**
205  * Virtual build: set offset attributes from corresponding repr.
206  */
207 static void
208 sp_offset_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
210     if (((SPObjectClass *) parent_class)->build)
211         ((SPObjectClass *) parent_class)->build (object, document, repr);
213     if (object->repr->attribute("inkscape:radius")) {
214         sp_object_read_attr (object, "inkscape:radius");
215     } else {
216         gchar const *oldA = object->repr->attribute("sodipodi:radius");
217         object->repr->setAttribute("inkscape:radius",oldA);
218         object->repr->setAttribute("sodipodi:radius",NULL);
220         sp_object_read_attr (object, "inkscape:radius");
221     }
222     if (object->repr->attribute("inkscape:original")) {
223         sp_object_read_attr (object, "inkscape:original");
224     } else {
225         gchar const *oldA = object->repr->attribute("sodipodi:original");
226         object->repr->setAttribute("inkscape:original",oldA);
227         object->repr->setAttribute("sodipodi:original",NULL);
229         sp_object_read_attr (object, "inkscape:original");
230     }
231     if (object->repr->attribute("xlink:href")) {
232         sp_object_read_attr(object, "xlink:href");
233     } else {
234         gchar const *oldA = object->repr->attribute("inkscape:href");
235         if (oldA) {
236             size_t lA = strlen(oldA);
237             char *nA=(char*)malloc((lA+1)*sizeof(char));
238             memcpy(nA+1,oldA,lA*sizeof(char));
239             nA[0]='#';
240             nA[lA+1]=0;
241             object->repr->setAttribute("xlink:href",nA);
242             free(nA);
243             object->repr->setAttribute("inkscape:href",NULL);
244         }
245         sp_object_read_attr (object, "xlink:href");
246     }
249 /**
250  * Virtual write: write offset attributes to corresponding repr.
251  */
252 static Inkscape::XML::Node *
253 sp_offset_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
255     SPOffset *offset = SP_OFFSET (object);
257     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
258         repr = sp_repr_new ("svg:path");
259     }
261     if (flags & SP_OBJECT_WRITE_EXT) {
262         /** \todo
263          * Fixme: we may replace these attributes by
264          * inkscape:offset="cx cy exp revo rad arg t0"
265          */
266         repr->setAttribute("sodipodi:type", "inkscape:offset");
267         sp_repr_set_svg_double(repr, "inkscape:radius", offset->rad);
268         repr->setAttribute("inkscape:original", offset->original);
269         repr->setAttribute("inkscape:href", offset->sourceHref);
270     }
273     // Make sure the object has curve
274     SPCurve *curve = sp_shape_get_curve (SP_SHAPE (offset));
275     if (curve == NULL) {
276         sp_offset_set_shape (SP_SHAPE (offset));
277     }
279     // write that curve to "d"
280     char *d = sp_svg_write_path (((SPShape *) offset)->curve->bpath);
281     repr->setAttribute("d", d);
282     g_free (d);
284     if (((SPObjectClass *) (parent_class))->write)
285         ((SPObjectClass *) (parent_class))->write (object, repr,
286                                                    flags | SP_SHAPE_WRITE_PATH);
288     return repr;
291 /**
292  * Virtual release callback.
293  */
294 static void
295 sp_offset_release(SPObject *object)
297     SPOffset *offset = (SPOffset *) object;
299     if (offset->original) free (offset->original);
300     if (offset->originalPath) delete ((Path *) offset->originalPath);
301     offset->original = NULL;
302     offset->originalPath = NULL;
304     sp_offset_quit_listening(offset);
306     offset->_changed_connection.disconnect();
307     g_free(offset->sourceHref);
308     offset->sourceHref = NULL;
309     offset->sourceRef->detach();
311     if (((SPObjectClass *) parent_class)->release) {
312         ((SPObjectClass *) parent_class)->release (object);
313     }
317 /**
318  * Set callback: the function that is called whenever a change is made to
319  * the description of the object.
320  */
321 static void
322 sp_offset_set(SPObject *object, unsigned key, gchar const *value)
324     SPOffset *offset = SP_OFFSET (object);
326     if ( offset->sourceDirty ) refresh_offset_source(offset);
328     /* fixme: we should really collect updates */
329     switch (key)
330     {
331         case SP_ATTR_INKSCAPE_ORIGINAL:
332         case SP_ATTR_SODIPODI_ORIGINAL:
333             if (value == NULL) {
334             } else {
335                 if (offset->original) {
336                     free (offset->original);
337                     delete ((Path *) offset->originalPath);
338                     offset->original = NULL;
339                     offset->originalPath = NULL;
340                 }
341                 NArtBpath *bpath;
342                 SPCurve *curve;
344                 offset->original = strdup (value);
346                 bpath = sp_svg_read_path (offset->original);
347                 curve = sp_curve_new_from_bpath (bpath);        // curve se chargera de detruire bpath
348                 g_assert (curve != NULL);
349                 offset->originalPath = bpath_to_liv_path (curve->bpath);
350                 sp_curve_unref (curve);
352                 offset->knotSet = false;
353                 if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
354             }
355             break;
356         case SP_ATTR_INKSCAPE_RADIUS:
357         case SP_ATTR_SODIPODI_RADIUS:
358             if (!sp_svg_length_read_computed_absolute (value, &offset->rad)) {
359                 if (fabs (offset->rad) < 0.01)
360                     offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
361                 offset->knotSet = false; // knotset=false because it's not set from the context
362             }
363             if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
364             break;
365         case SP_ATTR_INKSCAPE_HREF:
366         case SP_ATTR_XLINK_HREF:
367             if ( value == NULL ) {
368                 sp_offset_quit_listening(offset);
369                 if ( offset->sourceHref ) g_free(offset->sourceHref);
370                 offset->sourceHref = NULL;
371                 offset->sourceRef->detach();
372             } else {
373                 if ( offset->sourceHref && ( strcmp(value, offset->sourceHref) == 0 ) ) {
374                 } else {
375                     if ( offset->sourceHref ) g_free(offset->sourceHref);
376                     offset->sourceHref = g_strdup(value);
377                     try {
378                         offset->sourceRef->attach(Inkscape::URI(value));
379                     } catch (Inkscape::BadURIException &e) {
380                         g_warning("%s", e.what());
381                         offset->sourceRef->detach();
382                     }
383                 }
384             }
385             break;
386         default:
387             if (((SPObjectClass *) parent_class)->set)
388                 ((SPObjectClass *) parent_class)->set (object, key, value);
389             break;
390     }
393 /**
394  * Update callback: the object has changed, recompute its shape.
395  */
396 static void
397 sp_offset_update(SPObject *object, SPCtx *ctx, guint flags)
399     SPOffset* offset = SP_OFFSET(object);
400     offset->isUpdating=true; // prevent sp_offset_set from requesting updates
401     if ( offset->sourceDirty ) refresh_offset_source(offset);
402     if (flags &
403         (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG |
404          SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
405         sp_shape_set_shape ((SPShape *) object);
406     }
407     offset->isUpdating=false;
409     if (((SPObjectClass *) parent_class)->update)
410         ((SPObjectClass *) parent_class)->update (object, ctx, flags);
413 /**
414  * Returns a textual description of object.
415  */
416 static gchar *
417 sp_offset_description(SPItem *item)
419     SPOffset *offset = SP_OFFSET (item);
421     if ( offset->sourceHref ) {
422         // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
423         return g_strdup_printf(_("<b>Linked offset</b>, %s by %f pt"),
424                                (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad));
425     } else {
426         // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
427         return g_strdup_printf(_("<b>Dynamic offset</b>, %s by %f pt"),
428                                (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad));
429     }
432 /**
433  * Converts an NArtBpath (like the one stored in a SPCurve) into a
434  * livarot Path. Duplicate of splivarot.
435  */
436 Path *
437 bpath_to_liv_path(NArtBpath *bpath)
439     if (bpath == NULL)
440         return NULL;
442     Path *dest = new Path;
443     dest->SetBackData (false);
444     {
445         int i;
446         bool closed = false;
447         float lastX = 0.0;
448         float lastY = 0.0;
450         for (i = 0; bpath[i].code != NR_END; i++)
451         {
452             switch (bpath[i].code)
453             {
454                 case NR_LINETO:
455                     lastX = bpath[i].x3;
456                     lastY = bpath[i].y3;
457                     {
458                         NR::Point  tmp(lastX,lastY);
459                         dest->LineTo (tmp);
460                     }
461                     break;
463                 case NR_CURVETO:
464                 {
465                     NR::Point  tmp(bpath[i].x3, bpath[i].y3);
466                     NR::Point  tms;
467                     tms[0]=3 * (bpath[i].x1 - lastX);
468                     tms[1]=3 * (bpath[i].y1 - lastY);
469                     NR::Point  tme;
470                     tme[0]=3 * (bpath[i].x3 - bpath[i].x2);
471                     tme[1]= 3 * (bpath[i].y3 - bpath[i].y2);
472                     dest->CubicTo (tmp,tms,tme);
473                 }
474                 lastX = bpath[i].x3;
475                 lastY = bpath[i].y3;
476                 break;
478                 case NR_MOVETO_OPEN:
479                 case NR_MOVETO:
480                     if (closed)
481                         dest->Close ();
482                     closed = (bpath[i].code == NR_MOVETO);
483                     lastX = bpath[i].x3;
484                     lastY = bpath[i].y3;
485                     {
486                         NR::Point tmp(lastX,lastY);
487                         dest->MoveTo(tmp);
488                     }
489                     break;
490                 default:
491                     break;
492             }
493         }
494         if (closed)
495             dest->Close ();
496     }
498     return dest;
501 /**
502  * Compute and set shape's offset.
503  */
504 static void
505 sp_offset_set_shape(SPShape *shape)
507     SPOffset *offset = SP_OFFSET (shape);
509     if ( offset->originalPath == NULL ) {
510         // oops : no path?! (the offset object should do harakiri)
511         return;
512     }
513 #ifdef OFFSET_VERBOSE
514     g_print ("rad=%g\n", offset->rad);
515 #endif
516     // au boulot
518     if ( fabs(offset->rad) < 0.01 ) {
519         // grosso modo: 0
520         // just put the source shape as the offseted one, no one will notice
521         // it's also useless to compute the offset with a 0 radius
523         const char *res_d = SP_OBJECT(shape)->repr->attribute("inkscape:original");
524         if ( res_d ) {
525             NArtBpath *bpath = sp_svg_read_path (res_d);
526             SPCurve *c = sp_curve_new_from_bpath (bpath);
527             g_assert(c != NULL);
528             sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE);
529             sp_curve_unref (c);
530         }
531         return;
532     }
534     // extra paraniac careful check. the preceding if () should take care of this case
535     if (fabs (offset->rad) < 0.01)
536         offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
538     Path *orig = new Path;
539     orig->Copy ((Path *) offset->originalPath);
541     if ( use_slow_but_correct_offset_method == false ) {
542         // version par outline
543         Shape *theShape = new Shape;
544         Shape *theRes = new Shape;
545         Path *originaux[1];
546         Path *res = new Path;
547         res->SetBackData (false);
549         // and now: offset
550         float o_width;
551         if (offset->rad >= 0)
552         {
553             o_width = offset->rad;
554             orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0);
555         }
556         else
557         {
558             o_width = -offset->rad;
559             orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0);
560         }
562         if (o_width >= 1.0)
563         {
564             //      res->ConvertForOffset (1.0, orig, offset->rad);
565             res->ConvertWithBackData (1.0);
566         }
567         else
568         {
569             //      res->ConvertForOffset (o_width, orig, offset->rad);
570             res->ConvertWithBackData (o_width);
571         }
572         res->Fill (theShape, 0);
573         theRes->ConvertToShape (theShape, fill_positive);
574         originaux[0] = res;
576         theRes->ConvertToForme (orig, 1, originaux);
578         SPItem *item = shape;
579         NR::Rect bbox = sp_item_bbox_desktop (item);
580         if (!bbox.isEmpty()) {
581             gdouble size = L2(bbox.dimensions());
582             gdouble const exp = NR::expansion(item->transform);
583             if (exp != 0)
584                 size /= exp;
585             orig->Coalesce (size * 0.001);
586             //g_print ("coa %g    exp %g    item %p\n", size * 0.001, exp, item);
587         }
590         //  if (o_width >= 1.0)
591         //  {
592         //    orig->Coalesce (0.1);  // small treshhold, since we only want to get rid of small segments
593         // the curve should already be computed by the Outline() function
594         //   orig->ConvertEvenLines (1.0);
595         //   orig->Simplify (0.5);
596         //  }
597         //  else
598         //  {
599         //          orig->Coalesce (0.1*o_width);
600         //   orig->ConvertEvenLines (o_width);
601         //   orig->Simplify (0.5 * o_width);
602         //  }
604         delete theShape;
605         delete theRes;
606         delete res;
607     } else {
608         // version par makeoffset
609         Shape *theShape = new Shape;
610         Shape *theRes = new Shape;
613         // and now: offset
614         float o_width;
615         if (offset->rad >= 0)
616         {
617             o_width = offset->rad;
618         }
619         else
620         {
621             o_width = -offset->rad;
622         }
624         // one has to have a measure of the details
625         if (o_width >= 1.0)
626         {
627             orig->ConvertWithBackData (0.5);
628         }
629         else
630         {
631             orig->ConvertWithBackData (0.5*o_width);
632         }
633         orig->Fill (theShape, 0);
634         theRes->ConvertToShape (theShape, fill_positive);
635         Path *originaux[1];
636         originaux[0]=orig;
637         Path *res = new Path;
638         theRes->ConvertToForme (res, 1, originaux);
639         int    nbPart=0;
640         Path** parts=res->SubPaths(nbPart,true);
641         char   *holes=(char*)malloc(nbPart*sizeof(char));
642         // we offset contours separately, because we can.
643         // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes
644         {
645             Shape* onePart=new Shape;
646             Shape* oneCleanPart=new Shape;
647             theShape->Reset();
648             for (int i=0;i<nbPart;i++) {
649                 double partSurf=parts[i]->Surface();
650                 parts[i]->Convert(1.0);
651                 {
652                     // raffiner si besoin
653                     double  bL,bT,bR,bB;
654                     parts[i]->PolylineBoundingBox(bL,bT,bR,bB);
655                     double  mesure=((bR-bL)+(bB-bT))*0.5;
656                     if ( mesure < 10.0 ) {
657                         parts[i]->Convert(0.02*mesure);
658                     }
659                 }
660                 if ( partSurf < 0 ) { // inverse par rapport a la realite
661                     // plein
662                     holes[i]=0;
663                     parts[i]->Fill(oneCleanPart,0);
664                     onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges
665                     oneCleanPart->MakeOffset(onePart,offset->rad,join_round,20.0);
666                     onePart->ConvertToShape(oneCleanPart,fill_positive);
668                     onePart->CalcBBox();
669                     double  typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
670                     if ( typicalSize < 0.05 ) typicalSize=0.05;
671                     typicalSize*=0.01;
672                     if ( typicalSize > 1.0 ) typicalSize=1.0;
673                     onePart->ConvertToForme (parts[i]);
674                     parts[i]->ConvertEvenLines (typicalSize);
675                     parts[i]->Simplify (typicalSize);
676                     double nPartSurf=parts[i]->Surface();
677                     if ( nPartSurf >= 0 ) {
678                         // inversion de la surface -> disparait
679                         delete parts[i];
680                         parts[i]=NULL;
681                     } else {
682                     }
683 /*          int  firstP=theShape->nbPt;
684             for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
685             for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/
686                 } else {
687                     // trou
688                     holes[i]=1;
689                     parts[i]->Fill(oneCleanPart,0,false,true,true);
690                     onePart->ConvertToShape(oneCleanPart,fill_positive);
691                     oneCleanPart->MakeOffset(onePart,-offset->rad,join_round,20.0);
692                     onePart->ConvertToShape(oneCleanPart,fill_positive);
693 //          for (int j=0;j<onePart->nbAr;j++) onePart->Inverse(j); // pas oublier de reinverser
695                     onePart->CalcBBox();
696                     double  typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
697                     if ( typicalSize < 0.05 ) typicalSize=0.05;
698                     typicalSize*=0.01;
699                     if ( typicalSize > 1.0 ) typicalSize=1.0;
700                     onePart->ConvertToForme (parts[i]);
701                     parts[i]->ConvertEvenLines (typicalSize);
702                     parts[i]->Simplify (typicalSize);
703                     double nPartSurf=parts[i]->Surface();
704                     if ( nPartSurf >= 0 ) {
705                         // inversion de la surface -> disparait
706                         delete parts[i];
707                         parts[i]=NULL;
708                     } else {
709                     }
711                     /*         int  firstP=theShape->nbPt;
712                                for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
713                                for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/
714                 }
715 //        delete parts[i];
716             }
717 //      theShape->MakeOffset(theRes,offset->rad,join_round,20.0);
718             delete onePart;
719             delete oneCleanPart;
720         }
721         if ( nbPart > 1 ) {
722             theShape->Reset();
723             for (int i=0;i<nbPart;i++) {
724                 if ( parts[i] ) {
725                     parts[i]->ConvertWithBackData(1.0);
726                     if ( holes[i] ) {
727                         parts[i]->Fill(theShape,i,true,true,true);
728                     } else {
729                         parts[i]->Fill(theShape,i,true,true,false);
730                     }
731                 }
732             }
733             theRes->ConvertToShape (theShape, fill_positive);
734             theRes->ConvertToForme (orig,nbPart,parts);
735             for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
736         } else if ( nbPart == 1 ) {
737             orig->Copy(parts[0]);
738             for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
739         } else {
740             orig->Reset();
741         }
742 //    theRes->ConvertToShape (theShape, fill_positive);
743 //    theRes->ConvertToForme (orig);
745 /*    if (o_width >= 1.0) {
746       orig->ConvertEvenLines (1.0);
747       orig->Simplify (1.0);
748       } else {
749       orig->ConvertEvenLines (1.0*o_width);
750       orig->Simplify (1.0 * o_width);
751       }*/
753         if ( parts ) free(parts);
754         if ( holes ) free(holes);
755         delete res;
756         delete theShape;
757         delete theRes;
758     }
759     {
760         char *res_d = NULL;
761         if (orig->descr_cmd.size() <= 1)
762         {
763             // Aie.... nothing left.
764             res_d = strdup ("M 0 0 L 0 0 z");
765             //printf("%s\n",res_d);
766         }
767         else
768         {
770             res_d = orig->svg_dump_path ();
771         }
772         delete orig;
774         NArtBpath *bpath = sp_svg_read_path (res_d);
775         SPCurve *c = sp_curve_new_from_bpath (bpath);
776         g_assert(c != NULL);
777         sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE);
778         sp_curve_unref (c);
780         free (res_d);
781     }
784 /**
785  * Virtual snappoints function.
786  */
787 static void sp_offset_snappoints(SPItem const *item, SnapPointsIter p)
789     if (((SPItemClass *) parent_class)->snappoints) {
790         ((SPItemClass *) parent_class)->snappoints (item, p);
791     }
795 // utilitaires pour les poignees
796 // used to get the distance to the shape: distance to polygon give the fabs(radius), we still need
797 // the sign. for edges, it's easy to determine which side the point is on, for points of the polygon
798 // it's trickier: we need to identify which angle the point is in; to that effect, we take each
799 // successive clockwise angle (A,C) and check if the vector B given by the point is in the angle or
800 // outside.
801 // another method would be to use the Winding() function to test whether the point is inside or outside
802 // the polygon (it would be wiser to do so, in fact, but i like being stupid)
804 /**
805  *
806  * \todo
807  * FIXME: This can be done using linear operations, more stably and
808  *  faster.  method: transform A and C into B's space, A should be
809  *  negative and B should be positive in the orthogonal component.  I
810  *  think this is equivalent to
811  *  dot(A, rot90(B))*dot(C, rot90(B)) == -1.
812  *    -- njh
813  */
814 bool
815 vectors_are_clockwise (NR::Point A, NR::Point B, NR::Point C)
817     using NR::rot90;
818     double ab_s = dot(A, rot90(B));
819     double ab_c = dot(A, B);
820     double bc_s = dot(B, rot90(C));
821     double bc_c = dot(B, C);
822     double ca_s = dot(C, rot90(A));
823     double ca_c = dot(C, A);
825     double ab_a = acos (ab_c);
826     if (ab_c <= -1.0)
827         ab_a = M_PI;
828     if (ab_c >= 1.0)
829         ab_a = 0;
830     if (ab_s < 0)
831         ab_a = 2 * M_PI - ab_a;
832     double bc_a = acos (bc_c);
833     if (bc_c <= -1.0)
834         bc_a = M_PI;
835     if (bc_c >= 1.0)
836         bc_a = 0;
837     if (bc_s < 0)
838         bc_a = 2 * M_PI - bc_a;
839     double ca_a = acos (ca_c);
840     if (ca_c <= -1.0)
841         ca_a = M_PI;
842     if (ca_c >= 1.0)
843         ca_a = 0;
844     if (ca_s < 0)
845         ca_a = 2 * M_PI - ca_a;
847     double lim = 2 * M_PI - ca_a;
849     if (ab_a < lim)
850         return true;
851     return false;
854 /**
855  * Distance to the original path; that function is called from object-edit
856  * to set the radius when the control knot moves.
857  *
858  * The sign of the result is the radius we're going to offset the shape with,
859  * so result > 0 ==outset and result < 0 ==inset. thus result<0 means
860  * 'px inside source'.
861  */
862 double
863 sp_offset_distance_to_original (SPOffset * offset, NR::Point px)
865     if (offset == NULL || offset->originalPath == NULL
866         || ((Path *) offset->originalPath)->descr_cmd.size() <= 1)
867         return 1.0;
868     double dist = 1.0;
869     Shape *theShape = new Shape;
870     Shape *theRes = new Shape;
872     /** \todo
873      * Awfully damn stupid method: uncross the source path EACH TIME you
874      * need to compute the distance. The good way to do this would be to
875      * store the uncrossed source path somewhere, and delete it when the
876      * context is finished. Hopefully this part is much faster than actually
877      * computing the offset (which happen just after), so the time spent in
878      * this function should end up being negligible with respect to the
879      * delay of one context.
880      */
881     // move
882     ((Path *) offset->originalPath)->Convert (1.0);
883     ((Path *) offset->originalPath)->Fill (theShape, 0);
884     theRes->ConvertToShape (theShape, fill_oddEven);
886     if (theRes->numberOfEdges() <= 1)
887     {
889     }
890     else
891     {
892         double ptDist = -1.0;
893         bool ptSet = false;
894         double arDist = -1.0;
895         bool arSet = false;
896         // first get the minimum distance to the points
897         for (int i = 0; i < theRes->numberOfPoints(); i++)
898         {
899             if (theRes->getPoint(i).totalDegree() > 0)
900             {
901                 NR::Point nx = theRes->getPoint(i).x;
902                 NR::Point nxpx = px-nx;
903                 double ndist = sqrt (dot(nxpx,nxpx));
904                 if (ptSet == false || fabs (ndist) < fabs (ptDist))
905                 {
906                     // we have a new minimum distance
907                     // now we need to wheck if px is inside or outside (for the sign)
908                     nx = px - theRes->getPoint(i).x;
909                     double nlen = sqrt (dot(nx , nx));
910                     nx /= nlen;
911                     int pb, cb, fb;
912                     fb = theRes->getPoint(i).incidentEdge[LAST];
913                     pb = theRes->getPoint(i).incidentEdge[LAST];
914                     cb = theRes->getPoint(i).incidentEdge[FIRST];
915                     do
916                     {
917                         // one angle
918                         NR::Point prx, nex;
919                         prx = theRes->getEdge(pb).dx;
920                         nlen = sqrt (dot(prx, prx));
921                         prx /= nlen;
922                         nex = theRes->getEdge(cb).dx;
923                         nlen = sqrt (dot(nex , nex));
924                         nex /= nlen;
925                         if (theRes->getEdge(pb).en == i)
926                         {
927                             prx = -prx;
928                         }
929                         if (theRes->getEdge(cb).en == i)
930                         {
931                             nex = -nex;
932                         }
934                         if (vectors_are_clockwise (nex, nx, prx))
935                         {
936                             // we're in that angle. set the sign, and exit that loop
937                             if (theRes->getEdge(cb).st == i)
938                             {
939                                 ptDist = -ndist;
940                                 ptSet = true;
941                             }
942                             else
943                             {
944                                 ptDist = ndist;
945                                 ptSet = true;
946                             }
947                             break;
948                         }
949                         pb = cb;
950                         cb = theRes->NextAt (i, cb);
951                     }
952                     while (cb >= 0 && pb >= 0 && pb != fb);
953                 }
954             }
955         }
956         // loop over the edges to try to improve the distance
957         for (int i = 0; i < theRes->numberOfEdges(); i++)
958         {
959             NR::Point sx = theRes->getPoint(theRes->getEdge(i).st).x;
960             NR::Point ex = theRes->getPoint(theRes->getEdge(i).en).x;
961             NR::Point nx = ex - sx;
962             double len = sqrt (dot(nx,nx));
963             if (len > 0.0001)
964             {
965                 NR::Point   pxsx=px-sx;
966                 double ab = dot(nx,pxsx);
967                 if (ab > 0 && ab < len * len)
968                 {
969                     // we're in the zone of influence of the segment
970                     double ndist = (cross(pxsx,nx)) / len;
971                     if (arSet == false || fabs (ndist) < fabs (arDist))
972                     {
973                         arDist = ndist;
974                         arSet = true;
975                     }
976                 }
977             }
978         }
979         if (arSet || ptSet)
980         {
981             if (arSet == false)
982                 arDist = ptDist;
983             if (ptSet == false)
984                 ptDist = arDist;
985             if (fabs (ptDist) < fabs (arDist))
986                 dist = ptDist;
987             else
988                 dist = arDist;
989         }
990     }
992     delete theShape;
993     delete theRes;
995     return dist;
998 /**
999  * Computes a point on the offset;  used to set a "seed" position for
1000  * the control knot.
1001  *
1002  * \return the topmost point on the offset.
1003  */
1004 void
1005 sp_offset_top_point (SPOffset * offset, NR::Point *px)
1007     (*px) = NR::Point(0, 0);
1008     if (offset == NULL)
1009         return;
1011     if (offset->knotSet)
1012     {
1013         (*px) = offset->knot;
1014         return;
1015     }
1017     SPCurve *curve = sp_shape_get_curve (SP_SHAPE (offset));
1018     if (curve == NULL)
1019     {
1020         sp_offset_set_shape (SP_SHAPE (offset));
1021         curve = sp_shape_get_curve (SP_SHAPE (offset));
1022         if (curve == NULL)
1023             return;
1024     }
1026     Path *finalPath = bpath_to_liv_path (curve->bpath);
1027     if (finalPath == NULL)
1028     {
1029         sp_curve_unref (curve);
1030         return;
1031     }
1033     Shape *theShape = new Shape;
1035     finalPath->Convert (1.0);
1036     finalPath->Fill (theShape, 0);
1038     if (theShape->hasPoints())
1039     {
1040         theShape->SortPoints ();
1041         *px = theShape->getPoint(0).x;
1042     }
1044     delete theShape;
1045     delete finalPath;
1046     sp_curve_unref (curve);
1049 // the listening functions
1050 static void sp_offset_start_listening(SPOffset *offset,SPObject* to)
1052     if ( to == NULL )
1053         return;
1055     offset->sourceObject = to;
1056     offset->sourceRepr = SP_OBJECT_REPR(to);
1058     offset->_delete_connection = SP_OBJECT(to)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_offset_delete_self), offset));
1059     offset->_transformed_connection = SP_ITEM(to)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_offset_move_compensate), offset));
1060     offset->_modified_connection = g_signal_connect (G_OBJECT (to), "modified", G_CALLBACK (sp_offset_source_modified), offset);
1063 static void sp_offset_quit_listening(SPOffset *offset)
1065     if ( offset->sourceObject == NULL )
1066         return;
1068     g_signal_handler_disconnect (offset->sourceObject, offset->_modified_connection);
1069     offset->_delete_connection.disconnect();
1070     offset->_transformed_connection.disconnect();
1072     offset->sourceRepr = NULL;
1073     offset->sourceObject = NULL;
1076 static void
1077 sp_offset_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPOffset *offset)
1079     sp_offset_quit_listening(offset);
1080     if (offset->sourceRef) {
1081         SPItem *refobj = offset->sourceRef->getObject();
1082         if (refobj) sp_offset_start_listening(offset,refobj);
1083         offset->sourceDirty=true;
1084         SP_OBJECT(offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1085     }
1088 static void
1089 sp_offset_move_compensate(NR::Matrix const *mp, SPItem *original, SPOffset *self)
1091     guint mode = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_PARALLEL);
1092     if (mode == SP_CLONE_COMPENSATION_NONE) return;
1094     NR::Matrix m(*mp);
1095     if (!(m.is_translation())) return;
1097     // calculate the compensation matrix and the advertized movement matrix
1098     SPItem *item = SP_ITEM(self);
1100     NR::Matrix compensate;
1101     NR::Matrix advertized_move;
1103     if (mode == SP_CLONE_COMPENSATION_UNMOVED) {
1104         compensate = NR::identity();
1105         advertized_move.set_identity();
1106     } else if (mode == SP_CLONE_COMPENSATION_PARALLEL) {
1107         compensate = m;
1108         advertized_move = m;
1109     } else {
1110         g_assert_not_reached();
1111     }
1113     item->transform *= compensate;
1115     // commit the compensation
1116     sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform, &advertized_move);
1117     SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1120 static void
1121 sp_offset_delete_self(SPObject */*deleted*/, SPOffset *offset)
1123     guint const mode = prefs_get_int_attribute("options.cloneorphans", "value", SP_CLONE_ORPHANS_UNLINK);
1125     if (mode == SP_CLONE_ORPHANS_UNLINK) {
1126         // leave it be. just forget about the source
1127         sp_offset_quit_listening(offset);
1128         if ( offset->sourceHref ) g_free(offset->sourceHref);
1129         offset->sourceHref = NULL;
1130         offset->sourceRef->detach();
1131     } else if (mode == SP_CLONE_ORPHANS_DELETE) {
1132         SP_OBJECT(offset)->deleteObject();
1133     }
1136 static void
1137 sp_offset_source_modified (SPObject *iSource, guint flags, SPItem *item)
1139     SPOffset *offset = SP_OFFSET(item);
1140     offset->sourceDirty=true;
1141     refresh_offset_source(offset);
1142     sp_shape_set_shape ((SPShape *) offset);
1145 static void
1146 refresh_offset_source(SPOffset* offset)
1148     if ( offset == NULL ) return;
1149     offset->sourceDirty=false;
1150     Path *orig = NULL;
1152     // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour
1153     // The bad case: no d attribute.  Must check that it's an SPShape and then take the outline.
1154     SPObject *refobj=offset->sourceObject;
1155     if ( refobj == NULL ) return;
1156     SPItem *item = SP_ITEM (refobj);
1158     SPCurve *curve=NULL;
1159     if (!SP_IS_SHAPE (item) && !SP_IS_TEXT (item)) return;
1160     if (SP_IS_SHAPE (item)) {
1161         curve = sp_shape_get_curve (SP_SHAPE (item));
1162         if (curve == NULL)
1163             return;
1164     }
1165     if (SP_IS_TEXT (item)) {
1166         curve = SP_TEXT (item)->getNormalizedBpath ();
1167         if (curve == NULL)
1168             return;
1169     }
1170     orig = bpath_to_liv_path (curve->bpath);
1171     sp_curve_unref (curve);
1174     // Finish up.
1175     {
1176         SPCSSAttr *css;
1177         const gchar *val;
1178         Shape *theShape = new Shape;
1179         Shape *theRes = new Shape;
1181         orig->ConvertWithBackData (1.0);
1182         orig->Fill (theShape, 0);
1184         css = sp_repr_css_attr (offset->sourceRepr , "style");
1185         val = sp_repr_css_property (css, "fill-rule", NULL);
1186         if (val && strcmp (val, "nonzero") == 0)
1187         {
1188             theRes->ConvertToShape (theShape, fill_nonZero);
1189         }
1190         else if (val && strcmp (val, "evenodd") == 0)
1191         {
1192             theRes->ConvertToShape (theShape, fill_oddEven);
1193         }
1194         else
1195         {
1196             theRes->ConvertToShape (theShape, fill_nonZero);
1197         }
1199         Path *originaux[1];
1200         originaux[0] = orig;
1201         Path *res = new Path;
1202         theRes->ConvertToForme (res, 1, originaux);
1204         delete theShape;
1205         delete theRes;
1207         char *res_d = res->svg_dump_path ();
1208         delete res;
1209         delete orig;
1211         SP_OBJECT (offset)->repr->setAttribute("inkscape:original", res_d);
1213         free (res_d);
1214     }
1217 SPItem *
1218 sp_offset_get_source (SPOffset *offset)
1220     if (offset && offset->sourceRef) {
1221         SPItem *refobj = offset->sourceRef->getObject();
1222         if (SP_IS_ITEM (refobj))
1223             return (SPItem *) refobj;
1224     }
1225     return NULL;
1229 /*
1230   Local Variables:
1231   mode:c++
1232   c-file-style:"stroustrup"
1233   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1234   indent-tabs-mode:nil
1235   fill-column:99
1236   End:
1237 */
1238 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :