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