1 /** \file
2 * Implementation of <path sodipodi:type="inkscape:offset">.
3 */
5 /*
6 * Authors: (of the sp-spiral.c upon which this file was constructed):
7 * Mitsuru Oka <oka326@parkcity.ne.jp>
8 * Lauris Kaplinski <lauris@kaplinski.com>
9 * Abhishek Sharma
10 *
11 * Copyright (C) 1999-2002 Lauris Kaplinski
12 * Copyright (C) 2000-2001 Ximian, Inc.
13 *
14 * Released under GNU GPL, read the file 'COPYING' for more information
15 */
17 #ifdef HAVE_CONFIG_H
18 # include "config.h"
19 #endif
21 #include <cstring>
22 #include <string>
24 #include "svg/svg.h"
25 #include "attributes.h"
26 #include "display/curve.h"
27 #include <glibmm/i18n.h>
29 #include "livarot/Path.h"
30 #include "livarot/Shape.h"
32 #include "enums.h"
33 #include "preferences.h"
34 #include "sp-text.h"
35 #include "sp-offset.h"
36 #include "sp-use-reference.h"
37 #include "uri.h"
39 #include <libnr/nr-matrix-fns.h>
40 #include <2geom/pathvector.h>
42 #include "xml/repr.h"
44 class SPDocument;
46 #define noOFFSET_VERBOSE
48 /** \note
49 * SPOffset is a derivative of SPShape, much like the SPSpiral or SPRect.
50 * The goal is to have a source shape (= originalPath), an offset (= radius)
51 * and compute the offset of the source by the radius. To get it to work,
52 * one needs to know what the source is and what the radius is, and how it's
53 * stored in the xml representation. The object itself is a "path" element,
54 * to get lots of shape functionality for free. The source is the easy part:
55 * it's stored in a "inkscape:original" attribute in the path. In case of
56 * "linked" offset, as they've been dubbed, there is an additional
57 * "inkscape:href" that contains the id of an element of the svg.
58 * When built, the object will attach a listener vector to that object and
59 * rebuild the "inkscape:original" whenever the href'd object changes. This
60 * is of course grossly inefficient, and also does not react to changes
61 * to the href'd during context stuff (like changing the shape of a star by
62 * dragging control points) unless the path of that object is changed during
63 * the context (seems to be the case for SPEllipse). The computation of the
64 * offset is done in sp_offset_set_shape(), a function that is called whenever
65 * a change occurs to the offset (change of source or change of radius).
66 * just like the sp-star and other, this path derivative can make control
67 * points, or more precisely one control point, that's enough to define the
68 * radius (look in object-edit).
69 */
71 static void sp_offset_class_init (SPOffsetClass * klass);
72 static void sp_offset_init (SPOffset * offset);
73 static void sp_offset_finalize(GObject *obj);
75 static void sp_offset_build (SPObject * object, SPDocument * document,
76 Inkscape::XML::Node * repr);
77 static Inkscape::XML::Node *sp_offset_write (SPObject * object, Inkscape::XML::Document *doc, Inkscape::XML::Node * repr,
78 guint flags);
79 static void sp_offset_set (SPObject * object, unsigned int key,
80 const gchar * value);
81 static void sp_offset_update (SPObject * object, SPCtx * ctx, guint flags);
82 static void sp_offset_release (SPObject * object);
84 static gchar *sp_offset_description (SPItem * item);
85 static void sp_offset_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs);
86 static void sp_offset_set_shape (SPShape * shape);
88 static void refresh_offset_source(SPOffset* offset);
90 static void sp_offset_start_listening(SPOffset *offset,SPObject* to);
91 static void sp_offset_quit_listening(SPOffset *offset);
92 static void sp_offset_href_changed(SPObject *old_ref, SPObject *ref, SPOffset *offset);
93 static void sp_offset_move_compensate(Geom::Matrix const *mp, SPItem *original, SPOffset *self);
94 static void sp_offset_delete_self(SPObject *deleted, SPOffset *self);
95 static void sp_offset_source_modified (SPObject *iSource, guint flags, SPItem *item);
98 // slow= source path->polygon->offset of polygon->polygon->path
99 // fast= source path->offset of source path->polygon->path
100 // fast is not mathematically correct, because computing the offset of a single
101 // cubic bezier patch is not trivial; in particular, there are problems with holes
102 // reappearing in offset when the radius becomes too large
103 static bool use_slow_but_correct_offset_method=false;
106 // nothing special here, same for every class in sodipodi/inkscape
107 static SPShapeClass *parent_class;
109 /**
110 * Register SPOffset class and return its type number.
111 */
112 GType
113 sp_offset_get_type (void)
114 {
115 static GType offset_type = 0;
117 if (!offset_type)
118 {
119 GTypeInfo offset_info = {
120 sizeof (SPOffsetClass),
121 NULL, /* base_init */
122 NULL, /* base_finalize */
123 (GClassInitFunc) sp_offset_class_init,
124 NULL, /* class_finalize */
125 NULL, /* class_data */
126 sizeof (SPOffset),
127 16, /* n_preallocs */
128 (GInstanceInitFunc) sp_offset_init,
129 NULL, /* value_table */
130 };
131 offset_type =
132 g_type_register_static (SP_TYPE_SHAPE, "SPOffset", &offset_info,
133 (GTypeFlags) 0);
134 }
135 return offset_type;
136 }
138 /**
139 * SPOffset vtable initialization.
140 */
141 static void
142 sp_offset_class_init(SPOffsetClass *klass)
143 {
144 GObjectClass *gobject_class = (GObjectClass *) klass;
145 SPObjectClass *sp_object_class = (SPObjectClass *) klass;
146 SPItemClass *item_class = (SPItemClass *) klass;
147 SPShapeClass *shape_class = (SPShapeClass *) klass;
149 parent_class = (SPShapeClass *) g_type_class_ref (SP_TYPE_SHAPE);
151 gobject_class->finalize = sp_offset_finalize;
153 sp_object_class->build = sp_offset_build;
154 sp_object_class->write = sp_offset_write;
155 sp_object_class->set = sp_offset_set;
156 sp_object_class->update = sp_offset_update;
157 sp_object_class->release = sp_offset_release;
159 item_class->description = sp_offset_description;
160 item_class->snappoints = sp_offset_snappoints;
162 shape_class->set_shape = sp_offset_set_shape;
163 }
165 /**
166 * Callback for SPOffset object initialization.
167 */
168 static void
169 sp_offset_init(SPOffset *offset)
170 {
171 offset->rad = 1.0;
172 offset->original = NULL;
173 offset->originalPath = NULL;
174 offset->knotSet = false;
175 offset->sourceDirty=false;
176 offset->isUpdating=false;
177 // init various connections
178 offset->sourceHref = NULL;
179 offset->sourceRepr = NULL;
180 offset->sourceObject = NULL;
181 new (&offset->_modified_connection) sigc::connection();
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));
188 }
190 /**
191 * Callback for SPOffset finalization.
192 */
193 static void
194 sp_offset_finalize(GObject *obj)
195 {
196 SPOffset *offset = (SPOffset *) obj;
198 delete offset->sourceRef;
200 offset->_modified_connection.disconnect();
201 offset->_modified_connection.~connection();
202 offset->_delete_connection.disconnect();
203 offset->_delete_connection.~connection();
204 offset->_changed_connection.disconnect();
205 offset->_changed_connection.~connection();
206 offset->_transformed_connection.disconnect();
207 offset->_transformed_connection.~connection();
208 }
210 /**
211 * Virtual build: set offset attributes from corresponding repr.
212 */
213 static void
214 sp_offset_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
215 {
216 if (((SPObjectClass *) parent_class)->build)
217 ((SPObjectClass *) parent_class)->build (object, document, repr);
219 //XML Tree being used directly here while it shouldn't be.
220 if (object->getRepr()->attribute("inkscape:radius")) {
221 object->readAttr( "inkscape:radius" );
222 } else {
223 //XML Tree being used directly here (as object->getRepr)
224 //in all the below lines in the block while it shouldn't be.
225 gchar const *oldA = object->getRepr()->attribute("sodipodi:radius");
226 object->getRepr()->setAttribute("inkscape:radius",oldA);
227 object->getRepr()->setAttribute("sodipodi:radius",NULL);
229 object->readAttr( "inkscape:radius" );
230 }
231 if (object->getRepr()->attribute("inkscape:original")) {
232 object->readAttr( "inkscape:original" );
233 } else {
234 gchar const *oldA = object->getRepr()->attribute("sodipodi:original");
235 object->getRepr()->setAttribute("inkscape:original",oldA);
236 object->getRepr()->setAttribute("sodipodi:original",NULL);
238 object->readAttr( "inkscape:original" );
239 }
240 if (object->getRepr()->attribute("xlink:href")) {
241 object->readAttr( "xlink:href" );
242 } else {
243 gchar const *oldA = object->getRepr()->attribute("inkscape:href");
244 if (oldA) {
245 size_t lA = strlen(oldA);
246 char *nA=(char*)malloc((1+lA+1)*sizeof(char));
247 memcpy(nA+1,oldA,lA*sizeof(char));
248 nA[0]='#';
249 nA[lA+1]=0;
250 object->getRepr()->setAttribute("xlink:href",nA);
251 free(nA);
252 object->getRepr()->setAttribute("inkscape:href",NULL);
253 }
254 object->readAttr( "xlink:href" );
255 }
256 }
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)
263 {
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 (offset)->getCurve();
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;
298 }
300 /**
301 * Virtual release callback.
302 */
303 static void
304 sp_offset_release(SPObject *object)
305 {
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 }
324 }
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)
332 {
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 }
351 offset->original = strdup (value);
353 Geom::PathVector pv = sp_svg_read_pathv(offset->original);
354 offset->originalPath = new Path;
355 reinterpret_cast<Path *>(offset->originalPath)->LoadPathVector(pv);
357 offset->knotSet = false;
358 if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
359 }
360 break;
361 case SP_ATTR_INKSCAPE_RADIUS:
362 case SP_ATTR_SODIPODI_RADIUS:
363 if (!sp_svg_length_read_computed_absolute (value, &offset->rad)) {
364 if (fabs (offset->rad) < 0.01)
365 offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
366 offset->knotSet = false; // knotset=false because it's not set from the context
367 }
368 if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
369 break;
370 case SP_ATTR_INKSCAPE_HREF:
371 case SP_ATTR_XLINK_HREF:
372 if ( value == NULL ) {
373 sp_offset_quit_listening(offset);
374 if ( offset->sourceHref ) g_free(offset->sourceHref);
375 offset->sourceHref = NULL;
376 offset->sourceRef->detach();
377 } else {
378 if ( offset->sourceHref && ( strcmp(value, offset->sourceHref) == 0 ) ) {
379 } else {
380 if ( offset->sourceHref ) g_free(offset->sourceHref);
381 offset->sourceHref = g_strdup(value);
382 try {
383 offset->sourceRef->attach(Inkscape::URI(value));
384 } catch (Inkscape::BadURIException &e) {
385 g_warning("%s", e.what());
386 offset->sourceRef->detach();
387 }
388 }
389 }
390 break;
391 default:
392 if (((SPObjectClass *) parent_class)->set)
393 ((SPObjectClass *) parent_class)->set (object, key, value);
394 break;
395 }
396 }
398 /**
399 * Update callback: the object has changed, recompute its shape.
400 */
401 static void
402 sp_offset_update(SPObject *object, SPCtx *ctx, guint flags)
403 {
404 SPOffset* offset = SP_OFFSET(object);
405 offset->isUpdating=true; // prevent sp_offset_set from requesting updates
406 if ( offset->sourceDirty ) refresh_offset_source(offset);
407 if (flags &
408 (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG |
409 SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
410 ((SPShape *) object)->setShape ();
411 }
412 offset->isUpdating=false;
414 if (((SPObjectClass *) parent_class)->update)
415 ((SPObjectClass *) parent_class)->update (object, ctx, flags);
416 }
418 /**
419 * Returns a textual description of object.
420 */
421 static gchar *
422 sp_offset_description(SPItem *item)
423 {
424 SPOffset *offset = SP_OFFSET (item);
426 if ( offset->sourceHref ) {
427 // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
428 return g_strdup_printf(_("<b>Linked offset</b>, %s by %f pt"),
429 (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad));
430 } else {
431 // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
432 return g_strdup_printf(_("<b>Dynamic offset</b>, %s by %f pt"),
433 (offset->rad >= 0)? _("outset") : _("inset"), fabs (offset->rad));
434 }
435 }
437 /**
438 * Compute and set shape's offset.
439 */
440 static void
441 sp_offset_set_shape(SPShape *shape)
442 {
443 SPOffset *offset = SP_OFFSET (shape);
445 if ( offset->originalPath == NULL ) {
446 // oops : no path?! (the offset object should do harakiri)
447 return;
448 }
449 #ifdef OFFSET_VERBOSE
450 g_print ("rad=%g\n", offset->rad);
451 #endif
452 // au boulot
454 if ( fabs(offset->rad) < 0.01 ) {
455 // grosso modo: 0
456 // just put the source shape as the offseted one, no one will notice
457 // it's also useless to compute the offset with a 0 radius
459 //XML Tree being used directly here while it shouldn't be.
460 const char *res_d = SP_OBJECT(shape)->getRepr()->attribute("inkscape:original");
461 if ( res_d ) {
462 Geom::PathVector pv = sp_svg_read_pathv(res_d);
463 SPCurve *c = new SPCurve(pv);
464 g_assert(c != NULL);
465 ((SPShape *) offset)->setCurveInsync (c, TRUE);
466 c->unref();
467 }
468 return;
469 }
471 // extra paraniac careful check. the preceding if () should take care of this case
472 if (fabs (offset->rad) < 0.01)
473 offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
475 Path *orig = new Path;
476 orig->Copy ((Path *) offset->originalPath);
478 if ( use_slow_but_correct_offset_method == false ) {
479 // version par outline
480 Shape *theShape = new Shape;
481 Shape *theRes = new Shape;
482 Path *originaux[1];
483 Path *res = new Path;
484 res->SetBackData (false);
486 // and now: offset
487 float o_width;
488 if (offset->rad >= 0)
489 {
490 o_width = offset->rad;
491 orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0);
492 }
493 else
494 {
495 o_width = -offset->rad;
496 orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0);
497 }
499 if (o_width >= 1.0)
500 {
501 // res->ConvertForOffset (1.0, orig, offset->rad);
502 res->ConvertWithBackData (1.0);
503 }
504 else
505 {
506 // res->ConvertForOffset (o_width, orig, offset->rad);
507 res->ConvertWithBackData (o_width);
508 }
509 res->Fill (theShape, 0);
510 theRes->ConvertToShape (theShape, fill_positive);
511 originaux[0] = res;
513 theRes->ConvertToForme (orig, 1, originaux);
515 SPItem *item = shape;
516 Geom::OptRect bbox = item->getBboxDesktop ();
517 if ( bbox ) {
518 gdouble size = L2(bbox->dimensions());
519 gdouble const exp = item->transform.descrim();
520 if (exp != 0)
521 size /= exp;
522 orig->Coalesce (size * 0.001);
523 //g_print ("coa %g exp %g item %p\n", size * 0.001, exp, item);
524 }
527 // if (o_width >= 1.0)
528 // {
529 // orig->Coalesce (0.1); // small treshhold, since we only want to get rid of small segments
530 // the curve should already be computed by the Outline() function
531 // orig->ConvertEvenLines (1.0);
532 // orig->Simplify (0.5);
533 // }
534 // else
535 // {
536 // orig->Coalesce (0.1*o_width);
537 // orig->ConvertEvenLines (o_width);
538 // orig->Simplify (0.5 * o_width);
539 // }
541 delete theShape;
542 delete theRes;
543 delete res;
544 } else {
545 // version par makeoffset
546 Shape *theShape = new Shape;
547 Shape *theRes = new Shape;
550 // and now: offset
551 float o_width;
552 if (offset->rad >= 0)
553 {
554 o_width = offset->rad;
555 }
556 else
557 {
558 o_width = -offset->rad;
559 }
561 // one has to have a measure of the details
562 if (o_width >= 1.0)
563 {
564 orig->ConvertWithBackData (0.5);
565 }
566 else
567 {
568 orig->ConvertWithBackData (0.5*o_width);
569 }
570 orig->Fill (theShape, 0);
571 theRes->ConvertToShape (theShape, fill_positive);
572 Path *originaux[1];
573 originaux[0]=orig;
574 Path *res = new Path;
575 theRes->ConvertToForme (res, 1, originaux);
576 int nbPart=0;
577 Path** parts=res->SubPaths(nbPart,true);
578 char *holes=(char*)malloc(nbPart*sizeof(char));
579 // we offset contours separately, because we can.
580 // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes
581 {
582 Shape* onePart=new Shape;
583 Shape* oneCleanPart=new Shape;
584 theShape->Reset();
585 for (int i=0;i<nbPart;i++) {
586 double partSurf=parts[i]->Surface();
587 parts[i]->Convert(1.0);
588 {
589 // raffiner si besoin
590 double bL,bT,bR,bB;
591 parts[i]->PolylineBoundingBox(bL,bT,bR,bB);
592 double mesure=((bR-bL)+(bB-bT))*0.5;
593 if ( mesure < 10.0 ) {
594 parts[i]->Convert(0.02*mesure);
595 }
596 }
597 if ( partSurf < 0 ) { // inverse par rapport a la realite
598 // plein
599 holes[i]=0;
600 parts[i]->Fill(oneCleanPart,0);
601 onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges
602 oneCleanPart->MakeOffset(onePart,offset->rad,join_round,20.0);
603 onePart->ConvertToShape(oneCleanPart,fill_positive);
605 onePart->CalcBBox();
606 double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
607 if ( typicalSize < 0.05 ) typicalSize=0.05;
608 typicalSize*=0.01;
609 if ( typicalSize > 1.0 ) typicalSize=1.0;
610 onePart->ConvertToForme (parts[i]);
611 parts[i]->ConvertEvenLines (typicalSize);
612 parts[i]->Simplify (typicalSize);
613 double nPartSurf=parts[i]->Surface();
614 if ( nPartSurf >= 0 ) {
615 // inversion de la surface -> disparait
616 delete parts[i];
617 parts[i]=NULL;
618 } else {
619 }
620 /* int firstP=theShape->nbPt;
621 for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
622 for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/
623 } else {
624 // trou
625 holes[i]=1;
626 parts[i]->Fill(oneCleanPart,0,false,true,true);
627 onePart->ConvertToShape(oneCleanPart,fill_positive);
628 oneCleanPart->MakeOffset(onePart,-offset->rad,join_round,20.0);
629 onePart->ConvertToShape(oneCleanPart,fill_positive);
630 // for (int j=0;j<onePart->nbAr;j++) onePart->Inverse(j); // pas oublier de reinverser
632 onePart->CalcBBox();
633 double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
634 if ( typicalSize < 0.05 ) typicalSize=0.05;
635 typicalSize*=0.01;
636 if ( typicalSize > 1.0 ) typicalSize=1.0;
637 onePart->ConvertToForme (parts[i]);
638 parts[i]->ConvertEvenLines (typicalSize);
639 parts[i]->Simplify (typicalSize);
640 double nPartSurf=parts[i]->Surface();
641 if ( nPartSurf >= 0 ) {
642 // inversion de la surface -> disparait
643 delete parts[i];
644 parts[i]=NULL;
645 } else {
646 }
648 /* int firstP=theShape->nbPt;
649 for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
650 for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/
651 }
652 // delete parts[i];
653 }
654 // theShape->MakeOffset(theRes,offset->rad,join_round,20.0);
655 delete onePart;
656 delete oneCleanPart;
657 }
658 if ( nbPart > 1 ) {
659 theShape->Reset();
660 for (int i=0;i<nbPart;i++) {
661 if ( parts[i] ) {
662 parts[i]->ConvertWithBackData(1.0);
663 if ( holes[i] ) {
664 parts[i]->Fill(theShape,i,true,true,true);
665 } else {
666 parts[i]->Fill(theShape,i,true,true,false);
667 }
668 }
669 }
670 theRes->ConvertToShape (theShape, fill_positive);
671 theRes->ConvertToForme (orig,nbPart,parts);
672 for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
673 } else if ( nbPart == 1 ) {
674 orig->Copy(parts[0]);
675 for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
676 } else {
677 orig->Reset();
678 }
679 // theRes->ConvertToShape (theShape, fill_positive);
680 // theRes->ConvertToForme (orig);
682 /* if (o_width >= 1.0) {
683 orig->ConvertEvenLines (1.0);
684 orig->Simplify (1.0);
685 } else {
686 orig->ConvertEvenLines (1.0*o_width);
687 orig->Simplify (1.0 * o_width);
688 }*/
690 if ( parts ) free(parts);
691 if ( holes ) free(holes);
692 delete res;
693 delete theShape;
694 delete theRes;
695 }
696 {
697 char *res_d = NULL;
698 if (orig->descr_cmd.size() <= 1)
699 {
700 // Aie.... nothing left.
701 res_d = strdup ("M 0 0 L 0 0 z");
702 //printf("%s\n",res_d);
703 }
704 else
705 {
707 res_d = orig->svg_dump_path ();
708 }
709 delete orig;
711 Geom::PathVector pv = sp_svg_read_pathv(res_d);
712 SPCurve *c = new SPCurve(pv);
713 g_assert(c != NULL);
714 ((SPShape *) offset)->setCurveInsync (c, TRUE);
715 c->unref();
717 free (res_d);
718 }
719 }
721 /**
722 * Virtual snappoints function.
723 */
724 static void sp_offset_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs)
725 {
726 if (((SPItemClass *) parent_class)->snappoints) {
727 ((SPItemClass *) parent_class)->snappoints (item, p, snapprefs);
728 }
729 }
732 // utilitaires pour les poignees
733 // used to get the distance to the shape: distance to polygon give the fabs(radius), we still need
734 // the sign. for edges, it's easy to determine which side the point is on, for points of the polygon
735 // it's trickier: we need to identify which angle the point is in; to that effect, we take each
736 // successive clockwise angle (A,C) and check if the vector B given by the point is in the angle or
737 // outside.
738 // another method would be to use the Winding() function to test whether the point is inside or outside
739 // the polygon (it would be wiser to do so, in fact, but i like being stupid)
741 /**
742 *
743 * \todo
744 * FIXME: This can be done using linear operations, more stably and
745 * faster. method: transform A and C into B's space, A should be
746 * negative and B should be positive in the orthogonal component. I
747 * think this is equivalent to
748 * dot(A, rot90(B))*dot(C, rot90(B)) == -1.
749 * -- njh
750 */
751 bool
752 vectors_are_clockwise (Geom::Point A, Geom::Point B, Geom::Point C)
753 {
754 using Geom::rot90;
755 double ab_s = dot(A, rot90(B));
756 double ab_c = dot(A, B);
757 double bc_s = dot(B, rot90(C));
758 double bc_c = dot(B, C);
759 double ca_s = dot(C, rot90(A));
760 double ca_c = dot(C, A);
762 double ab_a = acos (ab_c);
763 if (ab_c <= -1.0)
764 ab_a = M_PI;
765 if (ab_c >= 1.0)
766 ab_a = 0;
767 if (ab_s < 0)
768 ab_a = 2 * M_PI - ab_a;
769 double bc_a = acos (bc_c);
770 if (bc_c <= -1.0)
771 bc_a = M_PI;
772 if (bc_c >= 1.0)
773 bc_a = 0;
774 if (bc_s < 0)
775 bc_a = 2 * M_PI - bc_a;
776 double ca_a = acos (ca_c);
777 if (ca_c <= -1.0)
778 ca_a = M_PI;
779 if (ca_c >= 1.0)
780 ca_a = 0;
781 if (ca_s < 0)
782 ca_a = 2 * M_PI - ca_a;
784 double lim = 2 * M_PI - ca_a;
786 if (ab_a < lim)
787 return true;
788 return false;
789 }
791 /**
792 * Distance to the original path; that function is called from object-edit
793 * to set the radius when the control knot moves.
794 *
795 * The sign of the result is the radius we're going to offset the shape with,
796 * so result > 0 ==outset and result < 0 ==inset. thus result<0 means
797 * 'px inside source'.
798 */
799 double
800 sp_offset_distance_to_original (SPOffset * offset, Geom::Point px)
801 {
802 if (offset == NULL || offset->originalPath == NULL
803 || ((Path *) offset->originalPath)->descr_cmd.size() <= 1)
804 return 1.0;
805 double dist = 1.0;
806 Shape *theShape = new Shape;
807 Shape *theRes = new Shape;
809 /** \todo
810 * Awfully damn stupid method: uncross the source path EACH TIME you
811 * need to compute the distance. The good way to do this would be to
812 * store the uncrossed source path somewhere, and delete it when the
813 * context is finished. Hopefully this part is much faster than actually
814 * computing the offset (which happen just after), so the time spent in
815 * this function should end up being negligible with respect to the
816 * delay of one context.
817 */
818 // move
819 ((Path *) offset->originalPath)->Convert (1.0);
820 ((Path *) offset->originalPath)->Fill (theShape, 0);
821 theRes->ConvertToShape (theShape, fill_oddEven);
823 if (theRes->numberOfEdges() <= 1)
824 {
826 }
827 else
828 {
829 double ptDist = -1.0;
830 bool ptSet = false;
831 double arDist = -1.0;
832 bool arSet = false;
833 // first get the minimum distance to the points
834 for (int i = 0; i < theRes->numberOfPoints(); i++)
835 {
836 if (theRes->getPoint(i).totalDegree() > 0)
837 {
838 Geom::Point nx = theRes->getPoint(i).x;
839 Geom::Point nxpx = px-nx;
840 double ndist = sqrt (dot(nxpx,nxpx));
841 if (ptSet == false || fabs (ndist) < fabs (ptDist))
842 {
843 // we have a new minimum distance
844 // now we need to wheck if px is inside or outside (for the sign)
845 nx = px - to_2geom(theRes->getPoint(i).x);
846 double nlen = sqrt (dot(nx , nx));
847 nx /= nlen;
848 int pb, cb, fb;
849 fb = theRes->getPoint(i).incidentEdge[LAST];
850 pb = theRes->getPoint(i).incidentEdge[LAST];
851 cb = theRes->getPoint(i).incidentEdge[FIRST];
852 do
853 {
854 // one angle
855 Geom::Point prx, nex;
856 prx = theRes->getEdge(pb).dx;
857 nlen = sqrt (dot(prx, prx));
858 prx /= nlen;
859 nex = theRes->getEdge(cb).dx;
860 nlen = sqrt (dot(nex , nex));
861 nex /= nlen;
862 if (theRes->getEdge(pb).en == i)
863 {
864 prx = -prx;
865 }
866 if (theRes->getEdge(cb).en == i)
867 {
868 nex = -nex;
869 }
871 if (vectors_are_clockwise (nex, nx, prx))
872 {
873 // we're in that angle. set the sign, and exit that loop
874 if (theRes->getEdge(cb).st == i)
875 {
876 ptDist = -ndist;
877 ptSet = true;
878 }
879 else
880 {
881 ptDist = ndist;
882 ptSet = true;
883 }
884 break;
885 }
886 pb = cb;
887 cb = theRes->NextAt (i, cb);
888 }
889 while (cb >= 0 && pb >= 0 && pb != fb);
890 }
891 }
892 }
893 // loop over the edges to try to improve the distance
894 for (int i = 0; i < theRes->numberOfEdges(); i++)
895 {
896 Geom::Point sx = theRes->getPoint(theRes->getEdge(i).st).x;
897 Geom::Point ex = theRes->getPoint(theRes->getEdge(i).en).x;
898 Geom::Point nx = ex - sx;
899 double len = sqrt (dot(nx,nx));
900 if (len > 0.0001)
901 {
902 Geom::Point pxsx=px-sx;
903 double ab = dot(nx,pxsx);
904 if (ab > 0 && ab < len * len)
905 {
906 // we're in the zone of influence of the segment
907 double ndist = (cross(pxsx,nx)) / len;
908 if (arSet == false || fabs (ndist) < fabs (arDist))
909 {
910 arDist = ndist;
911 arSet = true;
912 }
913 }
914 }
915 }
916 if (arSet || ptSet)
917 {
918 if (arSet == false)
919 arDist = ptDist;
920 if (ptSet == false)
921 ptDist = arDist;
922 if (fabs (ptDist) < fabs (arDist))
923 dist = ptDist;
924 else
925 dist = arDist;
926 }
927 }
929 delete theShape;
930 delete theRes;
932 return dist;
933 }
935 /**
936 * Computes a point on the offset; used to set a "seed" position for
937 * the control knot.
938 *
939 * \return the topmost point on the offset.
940 */
941 void
942 sp_offset_top_point (SPOffset * offset, Geom::Point *px)
943 {
944 (*px) = Geom::Point(0, 0);
945 if (offset == NULL)
946 return;
948 if (offset->knotSet)
949 {
950 (*px) = offset->knot;
951 return;
952 }
954 SPCurve *curve = SP_SHAPE (offset)->getCurve();
955 if (curve == NULL)
956 {
957 sp_offset_set_shape (SP_SHAPE (offset));
958 curve = SP_SHAPE (offset)->getCurve();
959 if (curve == NULL)
960 return;
961 }
962 if (curve->is_empty())
963 {
964 curve->unref();
965 return;
966 }
968 Path *finalPath = new Path;
969 finalPath->LoadPathVector(curve->get_pathvector());
971 Shape *theShape = new Shape;
973 finalPath->Convert (1.0);
974 finalPath->Fill (theShape, 0);
976 if (theShape->hasPoints())
977 {
978 theShape->SortPoints ();
979 *px = theShape->getPoint(0).x;
980 }
982 delete theShape;
983 delete finalPath;
984 curve->unref();
985 }
987 // the listening functions
988 static void sp_offset_start_listening(SPOffset *offset,SPObject* to)
989 {
990 if ( to == NULL )
991 return;
993 offset->sourceObject = to;
994 offset->sourceRepr = SP_OBJECT_REPR(to);
996 offset->_delete_connection = SP_OBJECT(to)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_offset_delete_self), offset));
997 offset->_transformed_connection = SP_ITEM(to)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_offset_move_compensate), offset));
998 offset->_modified_connection = SP_OBJECT(to)->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_offset_source_modified), offset));
999 }
1001 static void sp_offset_quit_listening(SPOffset *offset)
1002 {
1003 if ( offset->sourceObject == NULL )
1004 return;
1006 offset->_modified_connection.disconnect();
1007 offset->_delete_connection.disconnect();
1008 offset->_transformed_connection.disconnect();
1010 offset->sourceRepr = NULL;
1011 offset->sourceObject = NULL;
1012 }
1014 static void
1015 sp_offset_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPOffset *offset)
1016 {
1017 sp_offset_quit_listening(offset);
1018 if (offset->sourceRef) {
1019 SPItem *refobj = offset->sourceRef->getObject();
1020 if (refobj) sp_offset_start_listening(offset,refobj);
1021 offset->sourceDirty=true;
1022 SP_OBJECT(offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1023 }
1024 }
1026 static void
1027 sp_offset_move_compensate(Geom::Matrix const *mp, SPItem */*original*/, SPOffset *self)
1028 {
1029 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1030 guint mode = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_PARALLEL);
1031 if (mode == SP_CLONE_COMPENSATION_NONE) return;
1033 Geom::Matrix m(*mp);
1034 if (!(m.isTranslation())) return;
1036 // calculate the compensation matrix and the advertized movement matrix
1037 SPItem *item = SP_ITEM(self);
1039 Geom::Matrix compensate;
1040 Geom::Matrix advertized_move;
1042 if (mode == SP_CLONE_COMPENSATION_UNMOVED) {
1043 compensate = Geom::identity();
1044 advertized_move.setIdentity();
1045 } else if (mode == SP_CLONE_COMPENSATION_PARALLEL) {
1046 compensate = m;
1047 advertized_move = m;
1048 } else {
1049 g_assert_not_reached();
1050 }
1052 item->transform *= compensate;
1054 // commit the compensation
1055 item->doWriteTransform(SP_OBJECT_REPR(item), item->transform, &advertized_move);
1056 SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1057 }
1059 static void
1060 sp_offset_delete_self(SPObject */*deleted*/, SPOffset *offset)
1061 {
1062 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1063 guint const mode = prefs->getInt("/options/cloneorphans/value", SP_CLONE_ORPHANS_UNLINK);
1065 if (mode == SP_CLONE_ORPHANS_UNLINK) {
1066 // leave it be. just forget about the source
1067 sp_offset_quit_listening(offset);
1068 if ( offset->sourceHref ) g_free(offset->sourceHref);
1069 offset->sourceHref = NULL;
1070 offset->sourceRef->detach();
1071 } else if (mode == SP_CLONE_ORPHANS_DELETE) {
1072 SP_OBJECT(offset)->deleteObject();
1073 }
1074 }
1076 static void
1077 sp_offset_source_modified (SPObject */*iSource*/, guint /*flags*/, SPItem *item)
1078 {
1079 SPOffset *offset = SP_OFFSET(item);
1080 offset->sourceDirty=true;
1081 refresh_offset_source(offset);
1082 ((SPShape *) offset)->setShape ();
1083 }
1085 static void
1086 refresh_offset_source(SPOffset* offset)
1087 {
1088 if ( offset == NULL ) return;
1089 offset->sourceDirty=false;
1091 // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour
1092 // The bad case: no d attribute. Must check that it's an SPShape and then take the outline.
1093 SPObject *refobj=offset->sourceObject;
1094 if ( refobj == NULL ) return;
1095 SPItem *item = SP_ITEM (refobj);
1097 SPCurve *curve=NULL;
1098 if (!SP_IS_SHAPE (item) && !SP_IS_TEXT (item)) return;
1099 if (SP_IS_SHAPE (item)) {
1100 curve = SP_SHAPE (item)->getCurve ();
1101 if (curve == NULL)
1102 return;
1103 }
1104 if (SP_IS_TEXT (item)) {
1105 curve = SP_TEXT (item)->getNormalizedBpath ();
1106 if (curve == NULL)
1107 return;
1108 }
1109 Path *orig = new Path;
1110 orig->LoadPathVector(curve->get_pathvector());
1111 curve->unref();
1114 // Finish up.
1115 {
1116 SPCSSAttr *css;
1117 const gchar *val;
1118 Shape *theShape = new Shape;
1119 Shape *theRes = new Shape;
1121 orig->ConvertWithBackData (1.0);
1122 orig->Fill (theShape, 0);
1124 css = sp_repr_css_attr (offset->sourceRepr , "style");
1125 val = sp_repr_css_property (css, "fill-rule", NULL);
1126 if (val && strcmp (val, "nonzero") == 0)
1127 {
1128 theRes->ConvertToShape (theShape, fill_nonZero);
1129 }
1130 else if (val && strcmp (val, "evenodd") == 0)
1131 {
1132 theRes->ConvertToShape (theShape, fill_oddEven);
1133 }
1134 else
1135 {
1136 theRes->ConvertToShape (theShape, fill_nonZero);
1137 }
1139 Path *originaux[1];
1140 originaux[0] = orig;
1141 Path *res = new Path;
1142 theRes->ConvertToForme (res, 1, originaux);
1144 delete theShape;
1145 delete theRes;
1147 char *res_d = res->svg_dump_path ();
1148 delete res;
1149 delete orig;
1151 //XML Tree being used diectly here while it shouldn't be.
1152 SP_OBJECT (offset)->getRepr()->setAttribute("inkscape:original", res_d);
1154 free (res_d);
1155 }
1156 }
1158 SPItem *
1159 sp_offset_get_source (SPOffset *offset)
1160 {
1161 if (offset && offset->sourceRef) {
1162 SPItem *refobj = offset->sourceRef->getObject();
1163 if (SP_IS_ITEM (refobj))
1164 return (SPItem *) refobj;
1165 }
1166 return NULL;
1167 }
1170 /*
1171 Local Variables:
1172 mode:c++
1173 c-file-style:"stroustrup"
1174 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1175 indent-tabs-mode:nil
1176 fill-column:99
1177 End:
1178 */
1179 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :