Code

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