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