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 "preferences.h"
35 #include "sp-text.h"
36 #include "sp-offset.h"
37 #include "sp-use-reference.h"
38 #include "uri.h"
40 #include <libnr/nr-matrix-fns.h>
41 #include <2geom/pathvector.h>
43 #include "xml/repr.h"
45 class SPDocument;
47 #define noOFFSET_VERBOSE
49 /** \note
50 * SPOffset is a derivative of SPShape, much like the SPSpiral or SPRect.
51 * The goal is to have a source shape (= originalPath), an offset (= radius)
52 * and compute the offset of the source by the radius. To get it to work,
53 * one needs to know what the source is and what the radius is, and how it's
54 * stored in the xml representation. The object itself is a "path" element,
55 * to get lots of shape functionality for free. The source is the easy part:
56 * it's stored in a "inkscape:original" attribute in the path. In case of
57 * "linked" offset, as they've been dubbed, there is an additional
58 * "inkscape:href" that contains the id of an element of the svg.
59 * When built, the object will attach a listener vector to that object and
60 * rebuild the "inkscape:original" whenever the href'd object changes. This
61 * is of course grossly inefficient, and also does not react to changes
62 * to the href'd during context stuff (like changing the shape of a star by
63 * dragging control points) unless the path of that object is changed during
64 * the context (seems to be the case for SPEllipse). The computation of the
65 * offset is done in sp_offset_set_shape(), a function that is called whenever
66 * a change occurs to the offset (change of source or change of radius).
67 * just like the sp-star and other, this path derivative can make control
68 * points, or more precisely one control point, that's enough to define the
69 * radius (look in object-edit).
70 */
72 static void sp_offset_class_init (SPOffsetClass * klass);
73 static void sp_offset_init (SPOffset * offset);
74 static void sp_offset_finalize(GObject *obj);
76 static void sp_offset_build (SPObject * object, SPDocument * document,
77 Inkscape::XML::Node * repr);
78 static Inkscape::XML::Node *sp_offset_write (SPObject * object, Inkscape::XML::Document *doc, Inkscape::XML::Node * repr,
79 guint flags);
80 static void sp_offset_set (SPObject * object, unsigned int key,
81 const gchar * value);
82 static void sp_offset_update (SPObject * object, SPCtx * ctx, guint flags);
83 static void sp_offset_release (SPObject * object);
85 static gchar *sp_offset_description (SPItem * item);
86 static void sp_offset_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs);
87 static void sp_offset_set_shape (SPShape * shape);
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(Geom::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)
115 {
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;
137 }
139 /**
140 * SPOffset vtable initialization.
141 */
142 static void
143 sp_offset_class_init(SPOffsetClass *klass)
144 {
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;
164 }
166 /**
167 * Callback for SPOffset object initialization.
168 */
169 static void
170 sp_offset_init(SPOffset *offset)
171 {
172 offset->rad = 1.0;
173 offset->original = NULL;
174 offset->originalPath = NULL;
175 offset->knotSet = false;
176 offset->sourceDirty=false;
177 offset->isUpdating=false;
178 // init various connections
179 offset->sourceHref = NULL;
180 offset->sourceRepr = NULL;
181 offset->sourceObject = NULL;
182 new (&offset->_modified_connection) sigc::connection();
183 new (&offset->_delete_connection) sigc::connection();
184 new (&offset->_changed_connection) sigc::connection();
185 new (&offset->_transformed_connection) sigc::connection();
186 // set up the uri reference
187 offset->sourceRef = new SPUseReference(SP_OBJECT(offset));
188 offset->_changed_connection = offset->sourceRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_offset_href_changed), offset));
189 }
191 /**
192 * Callback for SPOffset finalization.
193 */
194 static void
195 sp_offset_finalize(GObject *obj)
196 {
197 SPOffset *offset = (SPOffset *) obj;
199 delete offset->sourceRef;
201 offset->_modified_connection.disconnect();
202 offset->_modified_connection.~connection();
203 offset->_delete_connection.disconnect();
204 offset->_delete_connection.~connection();
205 offset->_changed_connection.disconnect();
206 offset->_changed_connection.~connection();
207 offset->_transformed_connection.disconnect();
208 offset->_transformed_connection.~connection();
209 }
211 /**
212 * Virtual build: set offset attributes from corresponding repr.
213 */
214 static void
215 sp_offset_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
216 {
217 if (((SPObjectClass *) parent_class)->build)
218 ((SPObjectClass *) parent_class)->build (object, document, repr);
220 //XML Tree being used directly here while it shouldn't be.
221 if (object->getRepr()->attribute("inkscape:radius")) {
222 object->readAttr( "inkscape:radius");
223 } else {
226 //XML Tree being used directly here (as object->getRepr)
227 //in all the below lines in the block while it shouldn't be.
228 gchar const *oldA = object->getRepr()->attribute("sodipodi:radius");
229 object->getRepr()->setAttribute("inkscape:radius",oldA);
230 object->getRepr()->setAttribute("sodipodi:radius",NULL);
232 object->readAttr( "inkscape:radius");
233 }
234 if (object->getRepr()->attribute("inkscape:original")) {
235 object->readAttr( "inkscape:original");
236 } else {
237 gchar const *oldA = object->getRepr()->attribute("sodipodi:original");
238 object->getRepr()->setAttribute("inkscape:original",oldA);
239 object->getRepr()->setAttribute("sodipodi:original",NULL);
241 object->readAttr( "inkscape:original");
242 }
243 if (object->getRepr()->attribute("xlink:href")) {
244 object->readAttr( "xlink:href");
245 } else {
246 gchar const *oldA = object->getRepr()->attribute("inkscape:href");
247 if (oldA) {
248 size_t lA = strlen(oldA);
249 char *nA=(char*)malloc((1+lA+1)*sizeof(char));
250 memcpy(nA+1,oldA,lA*sizeof(char));
251 nA[0]='#';
252 nA[lA+1]=0;
253 object->getRepr()->setAttribute("xlink:href",nA);
254 free(nA);
255 object->getRepr()->setAttribute("inkscape:href",NULL);
256 }
257 object->readAttr( "xlink:href");
258 }
259 }
261 /**
262 * Virtual write: write offset attributes to corresponding repr.
263 */
264 static Inkscape::XML::Node *
265 sp_offset_write(SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
266 {
267 SPOffset *offset = SP_OFFSET (object);
269 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
270 repr = xml_doc->createElement("svg:path");
271 }
273 if (flags & SP_OBJECT_WRITE_EXT) {
274 /** \todo
275 * Fixme: we may replace these attributes by
276 * inkscape:offset="cx cy exp revo rad arg t0"
277 */
278 repr->setAttribute("sodipodi:type", "inkscape:offset");
279 sp_repr_set_svg_double(repr, "inkscape:radius", offset->rad);
280 repr->setAttribute("inkscape:original", offset->original);
281 repr->setAttribute("inkscape:href", offset->sourceHref);
282 }
285 // Make sure the object has curve
286 SPCurve *curve = SP_SHAPE (offset)->getCurve();
287 if (curve == NULL) {
288 sp_offset_set_shape (SP_SHAPE (offset));
289 }
291 // write that curve to "d"
292 char *d = sp_svg_write_path (((SPShape *) offset)->curve->get_pathvector());
293 repr->setAttribute("d", d);
294 g_free (d);
296 if (((SPObjectClass *) (parent_class))->write)
297 ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr,
298 flags | SP_SHAPE_WRITE_PATH);
300 return repr;
301 }
303 /**
304 * Virtual release callback.
305 */
306 static void
307 sp_offset_release(SPObject *object)
308 {
309 SPOffset *offset = (SPOffset *) object;
311 if (offset->original) free (offset->original);
312 if (offset->originalPath) delete ((Path *) offset->originalPath);
313 offset->original = NULL;
314 offset->originalPath = NULL;
316 sp_offset_quit_listening(offset);
318 offset->_changed_connection.disconnect();
319 g_free(offset->sourceHref);
320 offset->sourceHref = NULL;
321 offset->sourceRef->detach();
323 if (((SPObjectClass *) parent_class)->release) {
324 ((SPObjectClass *) parent_class)->release (object);
325 }
327 }
329 /**
330 * Set callback: the function that is called whenever a change is made to
331 * the description of the object.
332 */
333 static void
334 sp_offset_set(SPObject *object, unsigned key, gchar const *value)
335 {
336 SPOffset *offset = SP_OFFSET (object);
338 if ( offset->sourceDirty ) refresh_offset_source(offset);
340 /* fixme: we should really collect updates */
341 switch (key)
342 {
343 case SP_ATTR_INKSCAPE_ORIGINAL:
344 case SP_ATTR_SODIPODI_ORIGINAL:
345 if (value == NULL) {
346 } else {
347 if (offset->original) {
348 free (offset->original);
349 delete ((Path *) offset->originalPath);
350 offset->original = NULL;
351 offset->originalPath = NULL;
352 }
354 offset->original = strdup (value);
356 Geom::PathVector pv = sp_svg_read_pathv(offset->original);
357 offset->originalPath = new Path;
358 reinterpret_cast<Path *>(offset->originalPath)->LoadPathVector(pv);
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 }
399 }
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)
406 {
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 ((SPShape *) object)->setShape ();
414 }
415 offset->isUpdating=false;
417 if (((SPObjectClass *) parent_class)->update)
418 ((SPObjectClass *) parent_class)->update (object, ctx, flags);
419 }
421 /**
422 * Returns a textual description of object.
423 */
424 static gchar *
425 sp_offset_description(SPItem *item)
426 {
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 }
438 }
440 /**
441 * Compute and set shape's offset.
442 */
443 static void
444 sp_offset_set_shape(SPShape *shape)
445 {
446 SPOffset *offset = SP_OFFSET (shape);
448 if ( offset->originalPath == NULL ) {
449 // oops : no path?! (the offset object should do harakiri)
450 return;
451 }
452 #ifdef OFFSET_VERBOSE
453 g_print ("rad=%g\n", offset->rad);
454 #endif
455 // au boulot
457 if ( fabs(offset->rad) < 0.01 ) {
458 // grosso modo: 0
459 // just put the source shape as the offseted one, no one will notice
460 // it's also useless to compute the offset with a 0 radius
462 //XML Tree being used directly here while it shouldn't be.
463 const char *res_d = SP_OBJECT(shape)->getRepr()->attribute("inkscape:original");
464 if ( res_d ) {
465 Geom::PathVector pv = sp_svg_read_pathv(res_d);
466 SPCurve *c = new SPCurve(pv);
467 g_assert(c != NULL);
468 ((SPShape *) offset)->setCurveInsync (c, TRUE);
469 c->unref();
470 }
471 return;
472 }
474 // extra paraniac careful check. the preceding if () should take care of this case
475 if (fabs (offset->rad) < 0.01)
476 offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
478 Path *orig = new Path;
479 orig->Copy ((Path *) offset->originalPath);
481 if ( use_slow_but_correct_offset_method == false ) {
482 // version par outline
483 Shape *theShape = new Shape;
484 Shape *theRes = new Shape;
485 Path *originaux[1];
486 Path *res = new Path;
487 res->SetBackData (false);
489 // and now: offset
490 float o_width;
491 if (offset->rad >= 0)
492 {
493 o_width = offset->rad;
494 orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0);
495 }
496 else
497 {
498 o_width = -offset->rad;
499 orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0);
500 }
502 if (o_width >= 1.0)
503 {
504 // res->ConvertForOffset (1.0, orig, offset->rad);
505 res->ConvertWithBackData (1.0);
506 }
507 else
508 {
509 // res->ConvertForOffset (o_width, orig, offset->rad);
510 res->ConvertWithBackData (o_width);
511 }
512 res->Fill (theShape, 0);
513 theRes->ConvertToShape (theShape, fill_positive);
514 originaux[0] = res;
516 theRes->ConvertToForme (orig, 1, originaux);
518 SPItem *item = shape;
519 Geom::OptRect bbox = item->getBboxDesktop ();
520 if ( bbox ) {
521 gdouble size = L2(bbox->dimensions());
522 gdouble const exp = item->transform.descrim();
523 if (exp != 0)
524 size /= exp;
525 orig->Coalesce (size * 0.001);
526 //g_print ("coa %g exp %g item %p\n", size * 0.001, exp, item);
527 }
530 // if (o_width >= 1.0)
531 // {
532 // orig->Coalesce (0.1); // small treshhold, since we only want to get rid of small segments
533 // the curve should already be computed by the Outline() function
534 // orig->ConvertEvenLines (1.0);
535 // orig->Simplify (0.5);
536 // }
537 // else
538 // {
539 // orig->Coalesce (0.1*o_width);
540 // orig->ConvertEvenLines (o_width);
541 // orig->Simplify (0.5 * o_width);
542 // }
544 delete theShape;
545 delete theRes;
546 delete res;
547 } else {
548 // version par makeoffset
549 Shape *theShape = new Shape;
550 Shape *theRes = new Shape;
553 // and now: offset
554 float o_width;
555 if (offset->rad >= 0)
556 {
557 o_width = offset->rad;
558 }
559 else
560 {
561 o_width = -offset->rad;
562 }
564 // one has to have a measure of the details
565 if (o_width >= 1.0)
566 {
567 orig->ConvertWithBackData (0.5);
568 }
569 else
570 {
571 orig->ConvertWithBackData (0.5*o_width);
572 }
573 orig->Fill (theShape, 0);
574 theRes->ConvertToShape (theShape, fill_positive);
575 Path *originaux[1];
576 originaux[0]=orig;
577 Path *res = new Path;
578 theRes->ConvertToForme (res, 1, originaux);
579 int nbPart=0;
580 Path** parts=res->SubPaths(nbPart,true);
581 char *holes=(char*)malloc(nbPart*sizeof(char));
582 // we offset contours separately, because we can.
583 // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes
584 {
585 Shape* onePart=new Shape;
586 Shape* oneCleanPart=new Shape;
587 theShape->Reset();
588 for (int i=0;i<nbPart;i++) {
589 double partSurf=parts[i]->Surface();
590 parts[i]->Convert(1.0);
591 {
592 // raffiner si besoin
593 double bL,bT,bR,bB;
594 parts[i]->PolylineBoundingBox(bL,bT,bR,bB);
595 double mesure=((bR-bL)+(bB-bT))*0.5;
596 if ( mesure < 10.0 ) {
597 parts[i]->Convert(0.02*mesure);
598 }
599 }
600 if ( partSurf < 0 ) { // inverse par rapport a la realite
601 // plein
602 holes[i]=0;
603 parts[i]->Fill(oneCleanPart,0);
604 onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges
605 oneCleanPart->MakeOffset(onePart,offset->rad,join_round,20.0);
606 onePart->ConvertToShape(oneCleanPart,fill_positive);
608 onePart->CalcBBox();
609 double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
610 if ( typicalSize < 0.05 ) typicalSize=0.05;
611 typicalSize*=0.01;
612 if ( typicalSize > 1.0 ) typicalSize=1.0;
613 onePart->ConvertToForme (parts[i]);
614 parts[i]->ConvertEvenLines (typicalSize);
615 parts[i]->Simplify (typicalSize);
616 double nPartSurf=parts[i]->Surface();
617 if ( nPartSurf >= 0 ) {
618 // inversion de la surface -> disparait
619 delete parts[i];
620 parts[i]=NULL;
621 } else {
622 }
623 /* int firstP=theShape->nbPt;
624 for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
625 for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/
626 } else {
627 // trou
628 holes[i]=1;
629 parts[i]->Fill(oneCleanPart,0,false,true,true);
630 onePart->ConvertToShape(oneCleanPart,fill_positive);
631 oneCleanPart->MakeOffset(onePart,-offset->rad,join_round,20.0);
632 onePart->ConvertToShape(oneCleanPart,fill_positive);
633 // for (int j=0;j<onePart->nbAr;j++) onePart->Inverse(j); // pas oublier de reinverser
635 onePart->CalcBBox();
636 double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
637 if ( typicalSize < 0.05 ) typicalSize=0.05;
638 typicalSize*=0.01;
639 if ( typicalSize > 1.0 ) typicalSize=1.0;
640 onePart->ConvertToForme (parts[i]);
641 parts[i]->ConvertEvenLines (typicalSize);
642 parts[i]->Simplify (typicalSize);
643 double nPartSurf=parts[i]->Surface();
644 if ( nPartSurf >= 0 ) {
645 // inversion de la surface -> disparait
646 delete parts[i];
647 parts[i]=NULL;
648 } else {
649 }
651 /* int firstP=theShape->nbPt;
652 for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
653 for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/
654 }
655 // delete parts[i];
656 }
657 // theShape->MakeOffset(theRes,offset->rad,join_round,20.0);
658 delete onePart;
659 delete oneCleanPart;
660 }
661 if ( nbPart > 1 ) {
662 theShape->Reset();
663 for (int i=0;i<nbPart;i++) {
664 if ( parts[i] ) {
665 parts[i]->ConvertWithBackData(1.0);
666 if ( holes[i] ) {
667 parts[i]->Fill(theShape,i,true,true,true);
668 } else {
669 parts[i]->Fill(theShape,i,true,true,false);
670 }
671 }
672 }
673 theRes->ConvertToShape (theShape, fill_positive);
674 theRes->ConvertToForme (orig,nbPart,parts);
675 for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
676 } else if ( nbPart == 1 ) {
677 orig->Copy(parts[0]);
678 for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
679 } else {
680 orig->Reset();
681 }
682 // theRes->ConvertToShape (theShape, fill_positive);
683 // theRes->ConvertToForme (orig);
685 /* if (o_width >= 1.0) {
686 orig->ConvertEvenLines (1.0);
687 orig->Simplify (1.0);
688 } else {
689 orig->ConvertEvenLines (1.0*o_width);
690 orig->Simplify (1.0 * o_width);
691 }*/
693 if ( parts ) free(parts);
694 if ( holes ) free(holes);
695 delete res;
696 delete theShape;
697 delete theRes;
698 }
699 {
700 char *res_d = NULL;
701 if (orig->descr_cmd.size() <= 1)
702 {
703 // Aie.... nothing left.
704 res_d = strdup ("M 0 0 L 0 0 z");
705 //printf("%s\n",res_d);
706 }
707 else
708 {
710 res_d = orig->svg_dump_path ();
711 }
712 delete orig;
714 Geom::PathVector pv = sp_svg_read_pathv(res_d);
715 SPCurve *c = new SPCurve(pv);
716 g_assert(c != NULL);
717 ((SPShape *) offset)->setCurveInsync (c, TRUE);
718 c->unref();
720 free (res_d);
721 }
722 }
724 /**
725 * Virtual snappoints function.
726 */
727 static void sp_offset_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs)
728 {
729 if (((SPItemClass *) parent_class)->snappoints) {
730 ((SPItemClass *) parent_class)->snappoints (item, p, snapprefs);
731 }
732 }
735 // utilitaires pour les poignees
736 // used to get the distance to the shape: distance to polygon give the fabs(radius), we still need
737 // the sign. for edges, it's easy to determine which side the point is on, for points of the polygon
738 // it's trickier: we need to identify which angle the point is in; to that effect, we take each
739 // successive clockwise angle (A,C) and check if the vector B given by the point is in the angle or
740 // outside.
741 // another method would be to use the Winding() function to test whether the point is inside or outside
742 // the polygon (it would be wiser to do so, in fact, but i like being stupid)
744 /**
745 *
746 * \todo
747 * FIXME: This can be done using linear operations, more stably and
748 * faster. method: transform A and C into B's space, A should be
749 * negative and B should be positive in the orthogonal component. I
750 * think this is equivalent to
751 * dot(A, rot90(B))*dot(C, rot90(B)) == -1.
752 * -- njh
753 */
754 bool
755 vectors_are_clockwise (Geom::Point A, Geom::Point B, Geom::Point C)
756 {
757 using Geom::rot90;
758 double ab_s = dot(A, rot90(B));
759 double ab_c = dot(A, B);
760 double bc_s = dot(B, rot90(C));
761 double bc_c = dot(B, C);
762 double ca_s = dot(C, rot90(A));
763 double ca_c = dot(C, A);
765 double ab_a = acos (ab_c);
766 if (ab_c <= -1.0)
767 ab_a = M_PI;
768 if (ab_c >= 1.0)
769 ab_a = 0;
770 if (ab_s < 0)
771 ab_a = 2 * M_PI - ab_a;
772 double bc_a = acos (bc_c);
773 if (bc_c <= -1.0)
774 bc_a = M_PI;
775 if (bc_c >= 1.0)
776 bc_a = 0;
777 if (bc_s < 0)
778 bc_a = 2 * M_PI - bc_a;
779 double ca_a = acos (ca_c);
780 if (ca_c <= -1.0)
781 ca_a = M_PI;
782 if (ca_c >= 1.0)
783 ca_a = 0;
784 if (ca_s < 0)
785 ca_a = 2 * M_PI - ca_a;
787 double lim = 2 * M_PI - ca_a;
789 if (ab_a < lim)
790 return true;
791 return false;
792 }
794 /**
795 * Distance to the original path; that function is called from object-edit
796 * to set the radius when the control knot moves.
797 *
798 * The sign of the result is the radius we're going to offset the shape with,
799 * so result > 0 ==outset and result < 0 ==inset. thus result<0 means
800 * 'px inside source'.
801 */
802 double
803 sp_offset_distance_to_original (SPOffset * offset, Geom::Point px)
804 {
805 if (offset == NULL || offset->originalPath == NULL
806 || ((Path *) offset->originalPath)->descr_cmd.size() <= 1)
807 return 1.0;
808 double dist = 1.0;
809 Shape *theShape = new Shape;
810 Shape *theRes = new Shape;
812 /** \todo
813 * Awfully damn stupid method: uncross the source path EACH TIME you
814 * need to compute the distance. The good way to do this would be to
815 * store the uncrossed source path somewhere, and delete it when the
816 * context is finished. Hopefully this part is much faster than actually
817 * computing the offset (which happen just after), so the time spent in
818 * this function should end up being negligible with respect to the
819 * delay of one context.
820 */
821 // move
822 ((Path *) offset->originalPath)->Convert (1.0);
823 ((Path *) offset->originalPath)->Fill (theShape, 0);
824 theRes->ConvertToShape (theShape, fill_oddEven);
826 if (theRes->numberOfEdges() <= 1)
827 {
829 }
830 else
831 {
832 double ptDist = -1.0;
833 bool ptSet = false;
834 double arDist = -1.0;
835 bool arSet = false;
836 // first get the minimum distance to the points
837 for (int i = 0; i < theRes->numberOfPoints(); i++)
838 {
839 if (theRes->getPoint(i).totalDegree() > 0)
840 {
841 Geom::Point nx = theRes->getPoint(i).x;
842 Geom::Point nxpx = px-nx;
843 double ndist = sqrt (dot(nxpx,nxpx));
844 if (ptSet == false || fabs (ndist) < fabs (ptDist))
845 {
846 // we have a new minimum distance
847 // now we need to wheck if px is inside or outside (for the sign)
848 nx = px - to_2geom(theRes->getPoint(i).x);
849 double nlen = sqrt (dot(nx , nx));
850 nx /= nlen;
851 int pb, cb, fb;
852 fb = theRes->getPoint(i).incidentEdge[LAST];
853 pb = theRes->getPoint(i).incidentEdge[LAST];
854 cb = theRes->getPoint(i).incidentEdge[FIRST];
855 do
856 {
857 // one angle
858 Geom::Point prx, nex;
859 prx = theRes->getEdge(pb).dx;
860 nlen = sqrt (dot(prx, prx));
861 prx /= nlen;
862 nex = theRes->getEdge(cb).dx;
863 nlen = sqrt (dot(nex , nex));
864 nex /= nlen;
865 if (theRes->getEdge(pb).en == i)
866 {
867 prx = -prx;
868 }
869 if (theRes->getEdge(cb).en == i)
870 {
871 nex = -nex;
872 }
874 if (vectors_are_clockwise (nex, nx, prx))
875 {
876 // we're in that angle. set the sign, and exit that loop
877 if (theRes->getEdge(cb).st == i)
878 {
879 ptDist = -ndist;
880 ptSet = true;
881 }
882 else
883 {
884 ptDist = ndist;
885 ptSet = true;
886 }
887 break;
888 }
889 pb = cb;
890 cb = theRes->NextAt (i, cb);
891 }
892 while (cb >= 0 && pb >= 0 && pb != fb);
893 }
894 }
895 }
896 // loop over the edges to try to improve the distance
897 for (int i = 0; i < theRes->numberOfEdges(); i++)
898 {
899 Geom::Point sx = theRes->getPoint(theRes->getEdge(i).st).x;
900 Geom::Point ex = theRes->getPoint(theRes->getEdge(i).en).x;
901 Geom::Point nx = ex - sx;
902 double len = sqrt (dot(nx,nx));
903 if (len > 0.0001)
904 {
905 Geom::Point pxsx=px-sx;
906 double ab = dot(nx,pxsx);
907 if (ab > 0 && ab < len * len)
908 {
909 // we're in the zone of influence of the segment
910 double ndist = (cross(pxsx,nx)) / len;
911 if (arSet == false || fabs (ndist) < fabs (arDist))
912 {
913 arDist = ndist;
914 arSet = true;
915 }
916 }
917 }
918 }
919 if (arSet || ptSet)
920 {
921 if (arSet == false)
922 arDist = ptDist;
923 if (ptSet == false)
924 ptDist = arDist;
925 if (fabs (ptDist) < fabs (arDist))
926 dist = ptDist;
927 else
928 dist = arDist;
929 }
930 }
932 delete theShape;
933 delete theRes;
935 return dist;
936 }
938 /**
939 * Computes a point on the offset; used to set a "seed" position for
940 * the control knot.
941 *
942 * \return the topmost point on the offset.
943 */
944 void
945 sp_offset_top_point (SPOffset * offset, Geom::Point *px)
946 {
947 (*px) = Geom::Point(0, 0);
948 if (offset == NULL)
949 return;
951 if (offset->knotSet)
952 {
953 (*px) = offset->knot;
954 return;
955 }
957 SPCurve *curve = SP_SHAPE (offset)->getCurve();
958 if (curve == NULL)
959 {
960 sp_offset_set_shape (SP_SHAPE (offset));
961 curve = SP_SHAPE (offset)->getCurve();
962 if (curve == NULL)
963 return;
964 }
965 if (curve->is_empty())
966 {
967 curve->unref();
968 return;
969 }
971 Path *finalPath = new Path;
972 finalPath->LoadPathVector(curve->get_pathvector());
974 Shape *theShape = new Shape;
976 finalPath->Convert (1.0);
977 finalPath->Fill (theShape, 0);
979 if (theShape->hasPoints())
980 {
981 theShape->SortPoints ();
982 *px = theShape->getPoint(0).x;
983 }
985 delete theShape;
986 delete finalPath;
987 curve->unref();
988 }
990 // the listening functions
991 static void sp_offset_start_listening(SPOffset *offset,SPObject* to)
992 {
993 if ( to == NULL )
994 return;
996 offset->sourceObject = to;
997 offset->sourceRepr = SP_OBJECT_REPR(to);
999 offset->_delete_connection = SP_OBJECT(to)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_offset_delete_self), offset));
1000 offset->_transformed_connection = SP_ITEM(to)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_offset_move_compensate), offset));
1001 offset->_modified_connection = SP_OBJECT(to)->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_offset_source_modified), offset));
1002 }
1004 static void sp_offset_quit_listening(SPOffset *offset)
1005 {
1006 if ( offset->sourceObject == NULL )
1007 return;
1009 offset->_modified_connection.disconnect();
1010 offset->_delete_connection.disconnect();
1011 offset->_transformed_connection.disconnect();
1013 offset->sourceRepr = NULL;
1014 offset->sourceObject = NULL;
1015 }
1017 static void
1018 sp_offset_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPOffset *offset)
1019 {
1020 sp_offset_quit_listening(offset);
1021 if (offset->sourceRef) {
1022 SPItem *refobj = offset->sourceRef->getObject();
1023 if (refobj) sp_offset_start_listening(offset,refobj);
1024 offset->sourceDirty=true;
1025 SP_OBJECT(offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1026 }
1027 }
1029 static void
1030 sp_offset_move_compensate(Geom::Matrix const *mp, SPItem */*original*/, SPOffset *self)
1031 {
1032 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1033 guint mode = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_PARALLEL);
1034 if (mode == SP_CLONE_COMPENSATION_NONE) return;
1036 Geom::Matrix m(*mp);
1037 if (!(m.isTranslation())) return;
1039 // calculate the compensation matrix and the advertized movement matrix
1040 SPItem *item = SP_ITEM(self);
1042 Geom::Matrix compensate;
1043 Geom::Matrix advertized_move;
1045 if (mode == SP_CLONE_COMPENSATION_UNMOVED) {
1046 compensate = Geom::identity();
1047 advertized_move.setIdentity();
1048 } else if (mode == SP_CLONE_COMPENSATION_PARALLEL) {
1049 compensate = m;
1050 advertized_move = m;
1051 } else {
1052 g_assert_not_reached();
1053 }
1055 item->transform *= compensate;
1057 // commit the compensation
1058 item->doWriteTransform(SP_OBJECT_REPR(item), item->transform, &advertized_move);
1059 SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1060 }
1062 static void
1063 sp_offset_delete_self(SPObject */*deleted*/, SPOffset *offset)
1064 {
1065 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1066 guint const mode = prefs->getInt("/options/cloneorphans/value", SP_CLONE_ORPHANS_UNLINK);
1068 if (mode == SP_CLONE_ORPHANS_UNLINK) {
1069 // leave it be. just forget about the source
1070 sp_offset_quit_listening(offset);
1071 if ( offset->sourceHref ) g_free(offset->sourceHref);
1072 offset->sourceHref = NULL;
1073 offset->sourceRef->detach();
1074 } else if (mode == SP_CLONE_ORPHANS_DELETE) {
1075 SP_OBJECT(offset)->deleteObject();
1076 }
1077 }
1079 static void
1080 sp_offset_source_modified (SPObject */*iSource*/, guint /*flags*/, SPItem *item)
1081 {
1082 SPOffset *offset = SP_OFFSET(item);
1083 offset->sourceDirty=true;
1084 refresh_offset_source(offset);
1085 ((SPShape *) offset)->setShape ();
1086 }
1088 static void
1089 refresh_offset_source(SPOffset* offset)
1090 {
1091 if ( offset == NULL ) return;
1092 offset->sourceDirty=false;
1094 // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour
1095 // The bad case: no d attribute. Must check that it's an SPShape and then take the outline.
1096 SPObject *refobj=offset->sourceObject;
1097 if ( refobj == NULL ) return;
1098 SPItem *item = SP_ITEM (refobj);
1100 SPCurve *curve=NULL;
1101 if (!SP_IS_SHAPE (item) && !SP_IS_TEXT (item)) return;
1102 if (SP_IS_SHAPE (item)) {
1103 curve = SP_SHAPE (item)->getCurve ();
1104 if (curve == NULL)
1105 return;
1106 }
1107 if (SP_IS_TEXT (item)) {
1108 curve = SP_TEXT (item)->getNormalizedBpath ();
1109 if (curve == NULL)
1110 return;
1111 }
1112 Path *orig = new Path;
1113 orig->LoadPathVector(curve->get_pathvector());
1114 curve->unref();
1117 // Finish up.
1118 {
1119 SPCSSAttr *css;
1120 const gchar *val;
1121 Shape *theShape = new Shape;
1122 Shape *theRes = new Shape;
1124 orig->ConvertWithBackData (1.0);
1125 orig->Fill (theShape, 0);
1127 css = sp_repr_css_attr (offset->sourceRepr , "style");
1128 val = sp_repr_css_property (css, "fill-rule", NULL);
1129 if (val && strcmp (val, "nonzero") == 0)
1130 {
1131 theRes->ConvertToShape (theShape, fill_nonZero);
1132 }
1133 else if (val && strcmp (val, "evenodd") == 0)
1134 {
1135 theRes->ConvertToShape (theShape, fill_oddEven);
1136 }
1137 else
1138 {
1139 theRes->ConvertToShape (theShape, fill_nonZero);
1140 }
1142 Path *originaux[1];
1143 originaux[0] = orig;
1144 Path *res = new Path;
1145 theRes->ConvertToForme (res, 1, originaux);
1147 delete theShape;
1148 delete theRes;
1150 char *res_d = res->svg_dump_path ();
1151 delete res;
1152 delete orig;
1154 //XML Tree being used diectly here while it shouldn't be.
1155 SP_OBJECT (offset)->getRepr()->setAttribute("inkscape:original", res_d);
1157 free (res_d);
1158 }
1159 }
1161 SPItem *
1162 sp_offset_get_source (SPOffset *offset)
1163 {
1164 if (offset && offset->sourceRef) {
1165 SPItem *refobj = offset->sourceRef->getObject();
1166 if (SP_IS_ITEM (refobj))
1167 return (SPItem *) refobj;
1168 }
1169 return NULL;
1170 }
1173 /*
1174 Local Variables:
1175 mode:c++
1176 c-file-style:"stroustrup"
1177 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1178 indent-tabs-mode:nil
1179 fill-column:99
1180 End:
1181 */
1182 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :