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->_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 if (object->repr->attribute("inkscape:radius")) {
221 sp_object_read_attr (object, "inkscape:radius");
222 } else {
223 gchar const *oldA = object->repr->attribute("sodipodi:radius");
224 object->repr->setAttribute("inkscape:radius",oldA);
225 object->repr->setAttribute("sodipodi:radius",NULL);
227 sp_object_read_attr (object, "inkscape:radius");
228 }
229 if (object->repr->attribute("inkscape:original")) {
230 sp_object_read_attr (object, "inkscape:original");
231 } else {
232 gchar const *oldA = object->repr->attribute("sodipodi:original");
233 object->repr->setAttribute("inkscape:original",oldA);
234 object->repr->setAttribute("sodipodi:original",NULL);
236 sp_object_read_attr (object, "inkscape:original");
237 }
238 if (object->repr->attribute("xlink:href")) {
239 sp_object_read_attr(object, "xlink:href");
240 } else {
241 gchar const *oldA = object->repr->attribute("inkscape:href");
242 if (oldA) {
243 size_t lA = strlen(oldA);
244 char *nA=(char*)malloc((lA+1)*sizeof(char));
245 memcpy(nA+1,oldA,lA*sizeof(char));
246 nA[0]='#';
247 nA[lA+1]=0;
248 object->repr->setAttribute("xlink:href",nA);
249 free(nA);
250 object->repr->setAttribute("inkscape:href",NULL);
251 }
252 sp_object_read_attr (object, "xlink:href");
253 }
254 }
256 /**
257 * Virtual write: write offset attributes to corresponding repr.
258 */
259 static Inkscape::XML::Node *
260 sp_offset_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
261 {
262 SPOffset *offset = SP_OFFSET (object);
264 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
265 Inkscape::XML::Document *xml_doc = SP_OBJECT_REPR(object)->document();
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 (SP_CURVE_BPATH(((SPShape *) offset)->curve));
289 repr->setAttribute("d", d);
290 g_free (d);
292 if (((SPObjectClass *) (parent_class))->write)
293 ((SPObjectClass *) (parent_class))->write (object, 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 }
349 NArtBpath *bpath;
350 SPCurve *curve;
352 offset->original = strdup (value);
354 bpath = sp_svg_read_path (offset->original);
355 curve = sp_curve_new_from_bpath (bpath); // curve se chargera de detruire bpath
356 g_assert (curve != NULL);
357 offset->originalPath = bpath_to_liv_path (SP_CURVE_BPATH(curve));
358 sp_curve_unref (curve);
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 sp_shape_set_shape ((SPShape *) object);
414 }
415 offset->isUpdating=false;
417 if (((SPObjectClass *) parent_class)->update)
418 ((SPObjectClass *) parent_class)->update (object, ctx, flags);
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 * Converts an NArtBpath (like the one stored in a SPCurve) into a
442 * livarot Path. Duplicate of splivarot.
443 */
444 Path *
445 bpath_to_liv_path(NArtBpath *bpath)
446 {
447 if (bpath == NULL)
448 return NULL;
450 Path *dest = new Path;
451 dest->SetBackData (false);
452 {
453 int i;
454 bool closed = false;
455 float lastX = 0.0;
456 float lastY = 0.0;
458 for (i = 0; bpath[i].code != NR_END; i++)
459 {
460 switch (bpath[i].code)
461 {
462 case NR_LINETO:
463 lastX = bpath[i].x3;
464 lastY = bpath[i].y3;
465 {
466 NR::Point tmp(lastX,lastY);
467 dest->LineTo (tmp);
468 }
469 break;
471 case NR_CURVETO:
472 {
473 NR::Point tmp(bpath[i].x3, bpath[i].y3);
474 NR::Point tms;
475 tms[0]=3 * (bpath[i].x1 - lastX);
476 tms[1]=3 * (bpath[i].y1 - lastY);
477 NR::Point tme;
478 tme[0]=3 * (bpath[i].x3 - bpath[i].x2);
479 tme[1]= 3 * (bpath[i].y3 - bpath[i].y2);
480 dest->CubicTo (tmp,tms,tme);
481 }
482 lastX = bpath[i].x3;
483 lastY = bpath[i].y3;
484 break;
486 case NR_MOVETO_OPEN:
487 case NR_MOVETO:
488 if (closed)
489 dest->Close ();
490 closed = (bpath[i].code == NR_MOVETO);
491 lastX = bpath[i].x3;
492 lastY = bpath[i].y3;
493 {
494 NR::Point tmp(lastX,lastY);
495 dest->MoveTo(tmp);
496 }
497 break;
498 default:
499 break;
500 }
501 }
502 if (closed)
503 dest->Close ();
504 }
506 return dest;
507 }
509 /**
510 * Compute and set shape's offset.
511 */
512 static void
513 sp_offset_set_shape(SPShape *shape)
514 {
515 SPOffset *offset = SP_OFFSET (shape);
517 if ( offset->originalPath == NULL ) {
518 // oops : no path?! (the offset object should do harakiri)
519 return;
520 }
521 #ifdef OFFSET_VERBOSE
522 g_print ("rad=%g\n", offset->rad);
523 #endif
524 // au boulot
526 if ( fabs(offset->rad) < 0.01 ) {
527 // grosso modo: 0
528 // just put the source shape as the offseted one, no one will notice
529 // it's also useless to compute the offset with a 0 radius
531 const char *res_d = SP_OBJECT(shape)->repr->attribute("inkscape:original");
532 if ( res_d ) {
533 NArtBpath *bpath = sp_svg_read_path (res_d);
534 SPCurve *c = sp_curve_new_from_bpath (bpath);
535 g_assert(c != NULL);
536 sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE);
537 sp_curve_unref (c);
538 }
539 return;
540 }
542 // extra paraniac careful check. the preceding if () should take care of this case
543 if (fabs (offset->rad) < 0.01)
544 offset->rad = (offset->rad < 0) ? -0.01 : 0.01;
546 Path *orig = new Path;
547 orig->Copy ((Path *) offset->originalPath);
549 if ( use_slow_but_correct_offset_method == false ) {
550 // version par outline
551 Shape *theShape = new Shape;
552 Shape *theRes = new Shape;
553 Path *originaux[1];
554 Path *res = new Path;
555 res->SetBackData (false);
557 // and now: offset
558 float o_width;
559 if (offset->rad >= 0)
560 {
561 o_width = offset->rad;
562 orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0);
563 }
564 else
565 {
566 o_width = -offset->rad;
567 orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0);
568 }
570 if (o_width >= 1.0)
571 {
572 // res->ConvertForOffset (1.0, orig, offset->rad);
573 res->ConvertWithBackData (1.0);
574 }
575 else
576 {
577 // res->ConvertForOffset (o_width, orig, offset->rad);
578 res->ConvertWithBackData (o_width);
579 }
580 res->Fill (theShape, 0);
581 theRes->ConvertToShape (theShape, fill_positive);
582 originaux[0] = res;
584 theRes->ConvertToForme (orig, 1, originaux);
586 SPItem *item = shape;
587 NR::Maybe<NR::Rect> bbox = sp_item_bbox_desktop (item);
588 if ( bbox && !bbox->isEmpty() ) {
589 gdouble size = L2(bbox->dimensions());
590 gdouble const exp = NR::expansion(item->transform);
591 if (exp != 0)
592 size /= exp;
593 orig->Coalesce (size * 0.001);
594 //g_print ("coa %g exp %g item %p\n", size * 0.001, exp, item);
595 }
598 // if (o_width >= 1.0)
599 // {
600 // orig->Coalesce (0.1); // small treshhold, since we only want to get rid of small segments
601 // the curve should already be computed by the Outline() function
602 // orig->ConvertEvenLines (1.0);
603 // orig->Simplify (0.5);
604 // }
605 // else
606 // {
607 // orig->Coalesce (0.1*o_width);
608 // orig->ConvertEvenLines (o_width);
609 // orig->Simplify (0.5 * o_width);
610 // }
612 delete theShape;
613 delete theRes;
614 delete res;
615 } else {
616 // version par makeoffset
617 Shape *theShape = new Shape;
618 Shape *theRes = new Shape;
621 // and now: offset
622 float o_width;
623 if (offset->rad >= 0)
624 {
625 o_width = offset->rad;
626 }
627 else
628 {
629 o_width = -offset->rad;
630 }
632 // one has to have a measure of the details
633 if (o_width >= 1.0)
634 {
635 orig->ConvertWithBackData (0.5);
636 }
637 else
638 {
639 orig->ConvertWithBackData (0.5*o_width);
640 }
641 orig->Fill (theShape, 0);
642 theRes->ConvertToShape (theShape, fill_positive);
643 Path *originaux[1];
644 originaux[0]=orig;
645 Path *res = new Path;
646 theRes->ConvertToForme (res, 1, originaux);
647 int nbPart=0;
648 Path** parts=res->SubPaths(nbPart,true);
649 char *holes=(char*)malloc(nbPart*sizeof(char));
650 // we offset contours separately, because we can.
651 // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes
652 {
653 Shape* onePart=new Shape;
654 Shape* oneCleanPart=new Shape;
655 theShape->Reset();
656 for (int i=0;i<nbPart;i++) {
657 double partSurf=parts[i]->Surface();
658 parts[i]->Convert(1.0);
659 {
660 // raffiner si besoin
661 double bL,bT,bR,bB;
662 parts[i]->PolylineBoundingBox(bL,bT,bR,bB);
663 double mesure=((bR-bL)+(bB-bT))*0.5;
664 if ( mesure < 10.0 ) {
665 parts[i]->Convert(0.02*mesure);
666 }
667 }
668 if ( partSurf < 0 ) { // inverse par rapport a la realite
669 // plein
670 holes[i]=0;
671 parts[i]->Fill(oneCleanPart,0);
672 onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges
673 oneCleanPart->MakeOffset(onePart,offset->rad,join_round,20.0);
674 onePart->ConvertToShape(oneCleanPart,fill_positive);
676 onePart->CalcBBox();
677 double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
678 if ( typicalSize < 0.05 ) typicalSize=0.05;
679 typicalSize*=0.01;
680 if ( typicalSize > 1.0 ) typicalSize=1.0;
681 onePart->ConvertToForme (parts[i]);
682 parts[i]->ConvertEvenLines (typicalSize);
683 parts[i]->Simplify (typicalSize);
684 double nPartSurf=parts[i]->Surface();
685 if ( nPartSurf >= 0 ) {
686 // inversion de la surface -> disparait
687 delete parts[i];
688 parts[i]=NULL;
689 } else {
690 }
691 /* int firstP=theShape->nbPt;
692 for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
693 for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/
694 } else {
695 // trou
696 holes[i]=1;
697 parts[i]->Fill(oneCleanPart,0,false,true,true);
698 onePart->ConvertToShape(oneCleanPart,fill_positive);
699 oneCleanPart->MakeOffset(onePart,-offset->rad,join_round,20.0);
700 onePart->ConvertToShape(oneCleanPart,fill_positive);
701 // for (int j=0;j<onePart->nbAr;j++) onePart->Inverse(j); // pas oublier de reinverser
703 onePart->CalcBBox();
704 double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
705 if ( typicalSize < 0.05 ) typicalSize=0.05;
706 typicalSize*=0.01;
707 if ( typicalSize > 1.0 ) typicalSize=1.0;
708 onePart->ConvertToForme (parts[i]);
709 parts[i]->ConvertEvenLines (typicalSize);
710 parts[i]->Simplify (typicalSize);
711 double nPartSurf=parts[i]->Surface();
712 if ( nPartSurf >= 0 ) {
713 // inversion de la surface -> disparait
714 delete parts[i];
715 parts[i]=NULL;
716 } else {
717 }
719 /* int firstP=theShape->nbPt;
720 for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
721 for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/
722 }
723 // delete parts[i];
724 }
725 // theShape->MakeOffset(theRes,offset->rad,join_round,20.0);
726 delete onePart;
727 delete oneCleanPart;
728 }
729 if ( nbPart > 1 ) {
730 theShape->Reset();
731 for (int i=0;i<nbPart;i++) {
732 if ( parts[i] ) {
733 parts[i]->ConvertWithBackData(1.0);
734 if ( holes[i] ) {
735 parts[i]->Fill(theShape,i,true,true,true);
736 } else {
737 parts[i]->Fill(theShape,i,true,true,false);
738 }
739 }
740 }
741 theRes->ConvertToShape (theShape, fill_positive);
742 theRes->ConvertToForme (orig,nbPart,parts);
743 for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
744 } else if ( nbPart == 1 ) {
745 orig->Copy(parts[0]);
746 for (int i=0;i<nbPart;i++) if ( parts[i] ) delete parts[i];
747 } else {
748 orig->Reset();
749 }
750 // theRes->ConvertToShape (theShape, fill_positive);
751 // theRes->ConvertToForme (orig);
753 /* if (o_width >= 1.0) {
754 orig->ConvertEvenLines (1.0);
755 orig->Simplify (1.0);
756 } else {
757 orig->ConvertEvenLines (1.0*o_width);
758 orig->Simplify (1.0 * o_width);
759 }*/
761 if ( parts ) free(parts);
762 if ( holes ) free(holes);
763 delete res;
764 delete theShape;
765 delete theRes;
766 }
767 {
768 char *res_d = NULL;
769 if (orig->descr_cmd.size() <= 1)
770 {
771 // Aie.... nothing left.
772 res_d = strdup ("M 0 0 L 0 0 z");
773 //printf("%s\n",res_d);
774 }
775 else
776 {
778 res_d = orig->svg_dump_path ();
779 }
780 delete orig;
782 NArtBpath *bpath = sp_svg_read_path (res_d);
783 SPCurve *c = sp_curve_new_from_bpath (bpath);
784 g_assert(c != NULL);
785 sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE);
786 sp_curve_unref (c);
788 free (res_d);
789 }
790 }
792 /**
793 * Virtual snappoints function.
794 */
795 static void sp_offset_snappoints(SPItem const *item, SnapPointsIter p)
796 {
797 if (((SPItemClass *) parent_class)->snappoints) {
798 ((SPItemClass *) parent_class)->snappoints (item, p);
799 }
800 }
803 // utilitaires pour les poignees
804 // used to get the distance to the shape: distance to polygon give the fabs(radius), we still need
805 // the sign. for edges, it's easy to determine which side the point is on, for points of the polygon
806 // it's trickier: we need to identify which angle the point is in; to that effect, we take each
807 // successive clockwise angle (A,C) and check if the vector B given by the point is in the angle or
808 // outside.
809 // another method would be to use the Winding() function to test whether the point is inside or outside
810 // the polygon (it would be wiser to do so, in fact, but i like being stupid)
812 /**
813 *
814 * \todo
815 * FIXME: This can be done using linear operations, more stably and
816 * faster. method: transform A and C into B's space, A should be
817 * negative and B should be positive in the orthogonal component. I
818 * think this is equivalent to
819 * dot(A, rot90(B))*dot(C, rot90(B)) == -1.
820 * -- njh
821 */
822 bool
823 vectors_are_clockwise (NR::Point A, NR::Point B, NR::Point C)
824 {
825 using NR::rot90;
826 double ab_s = dot(A, rot90(B));
827 double ab_c = dot(A, B);
828 double bc_s = dot(B, rot90(C));
829 double bc_c = dot(B, C);
830 double ca_s = dot(C, rot90(A));
831 double ca_c = dot(C, A);
833 double ab_a = acos (ab_c);
834 if (ab_c <= -1.0)
835 ab_a = M_PI;
836 if (ab_c >= 1.0)
837 ab_a = 0;
838 if (ab_s < 0)
839 ab_a = 2 * M_PI - ab_a;
840 double bc_a = acos (bc_c);
841 if (bc_c <= -1.0)
842 bc_a = M_PI;
843 if (bc_c >= 1.0)
844 bc_a = 0;
845 if (bc_s < 0)
846 bc_a = 2 * M_PI - bc_a;
847 double ca_a = acos (ca_c);
848 if (ca_c <= -1.0)
849 ca_a = M_PI;
850 if (ca_c >= 1.0)
851 ca_a = 0;
852 if (ca_s < 0)
853 ca_a = 2 * M_PI - ca_a;
855 double lim = 2 * M_PI - ca_a;
857 if (ab_a < lim)
858 return true;
859 return false;
860 }
862 /**
863 * Distance to the original path; that function is called from object-edit
864 * to set the radius when the control knot moves.
865 *
866 * The sign of the result is the radius we're going to offset the shape with,
867 * so result > 0 ==outset and result < 0 ==inset. thus result<0 means
868 * 'px inside source'.
869 */
870 double
871 sp_offset_distance_to_original (SPOffset * offset, NR::Point px)
872 {
873 if (offset == NULL || offset->originalPath == NULL
874 || ((Path *) offset->originalPath)->descr_cmd.size() <= 1)
875 return 1.0;
876 double dist = 1.0;
877 Shape *theShape = new Shape;
878 Shape *theRes = new Shape;
880 /** \todo
881 * Awfully damn stupid method: uncross the source path EACH TIME you
882 * need to compute the distance. The good way to do this would be to
883 * store the uncrossed source path somewhere, and delete it when the
884 * context is finished. Hopefully this part is much faster than actually
885 * computing the offset (which happen just after), so the time spent in
886 * this function should end up being negligible with respect to the
887 * delay of one context.
888 */
889 // move
890 ((Path *) offset->originalPath)->Convert (1.0);
891 ((Path *) offset->originalPath)->Fill (theShape, 0);
892 theRes->ConvertToShape (theShape, fill_oddEven);
894 if (theRes->numberOfEdges() <= 1)
895 {
897 }
898 else
899 {
900 double ptDist = -1.0;
901 bool ptSet = false;
902 double arDist = -1.0;
903 bool arSet = false;
904 // first get the minimum distance to the points
905 for (int i = 0; i < theRes->numberOfPoints(); i++)
906 {
907 if (theRes->getPoint(i).totalDegree() > 0)
908 {
909 NR::Point nx = theRes->getPoint(i).x;
910 NR::Point nxpx = px-nx;
911 double ndist = sqrt (dot(nxpx,nxpx));
912 if (ptSet == false || fabs (ndist) < fabs (ptDist))
913 {
914 // we have a new minimum distance
915 // now we need to wheck if px is inside or outside (for the sign)
916 nx = px - theRes->getPoint(i).x;
917 double nlen = sqrt (dot(nx , nx));
918 nx /= nlen;
919 int pb, cb, fb;
920 fb = theRes->getPoint(i).incidentEdge[LAST];
921 pb = theRes->getPoint(i).incidentEdge[LAST];
922 cb = theRes->getPoint(i).incidentEdge[FIRST];
923 do
924 {
925 // one angle
926 NR::Point prx, nex;
927 prx = theRes->getEdge(pb).dx;
928 nlen = sqrt (dot(prx, prx));
929 prx /= nlen;
930 nex = theRes->getEdge(cb).dx;
931 nlen = sqrt (dot(nex , nex));
932 nex /= nlen;
933 if (theRes->getEdge(pb).en == i)
934 {
935 prx = -prx;
936 }
937 if (theRes->getEdge(cb).en == i)
938 {
939 nex = -nex;
940 }
942 if (vectors_are_clockwise (nex, nx, prx))
943 {
944 // we're in that angle. set the sign, and exit that loop
945 if (theRes->getEdge(cb).st == i)
946 {
947 ptDist = -ndist;
948 ptSet = true;
949 }
950 else
951 {
952 ptDist = ndist;
953 ptSet = true;
954 }
955 break;
956 }
957 pb = cb;
958 cb = theRes->NextAt (i, cb);
959 }
960 while (cb >= 0 && pb >= 0 && pb != fb);
961 }
962 }
963 }
964 // loop over the edges to try to improve the distance
965 for (int i = 0; i < theRes->numberOfEdges(); i++)
966 {
967 NR::Point sx = theRes->getPoint(theRes->getEdge(i).st).x;
968 NR::Point ex = theRes->getPoint(theRes->getEdge(i).en).x;
969 NR::Point nx = ex - sx;
970 double len = sqrt (dot(nx,nx));
971 if (len > 0.0001)
972 {
973 NR::Point pxsx=px-sx;
974 double ab = dot(nx,pxsx);
975 if (ab > 0 && ab < len * len)
976 {
977 // we're in the zone of influence of the segment
978 double ndist = (cross(pxsx,nx)) / len;
979 if (arSet == false || fabs (ndist) < fabs (arDist))
980 {
981 arDist = ndist;
982 arSet = true;
983 }
984 }
985 }
986 }
987 if (arSet || ptSet)
988 {
989 if (arSet == false)
990 arDist = ptDist;
991 if (ptSet == false)
992 ptDist = arDist;
993 if (fabs (ptDist) < fabs (arDist))
994 dist = ptDist;
995 else
996 dist = arDist;
997 }
998 }
1000 delete theShape;
1001 delete theRes;
1003 return dist;
1004 }
1006 /**
1007 * Computes a point on the offset; used to set a "seed" position for
1008 * the control knot.
1009 *
1010 * \return the topmost point on the offset.
1011 */
1012 void
1013 sp_offset_top_point (SPOffset * offset, NR::Point *px)
1014 {
1015 (*px) = NR::Point(0, 0);
1016 if (offset == NULL)
1017 return;
1019 if (offset->knotSet)
1020 {
1021 (*px) = offset->knot;
1022 return;
1023 }
1025 SPCurve *curve = sp_shape_get_curve (SP_SHAPE (offset));
1026 if (curve == NULL)
1027 {
1028 sp_offset_set_shape (SP_SHAPE (offset));
1029 curve = sp_shape_get_curve (SP_SHAPE (offset));
1030 if (curve == NULL)
1031 return;
1032 }
1034 Path *finalPath = bpath_to_liv_path (SP_CURVE_BPATH(curve));
1035 if (finalPath == NULL)
1036 {
1037 sp_curve_unref (curve);
1038 return;
1039 }
1041 Shape *theShape = new Shape;
1043 finalPath->Convert (1.0);
1044 finalPath->Fill (theShape, 0);
1046 if (theShape->hasPoints())
1047 {
1048 theShape->SortPoints ();
1049 *px = theShape->getPoint(0).x;
1050 }
1052 delete theShape;
1053 delete finalPath;
1054 sp_curve_unref (curve);
1055 }
1057 // the listening functions
1058 static void sp_offset_start_listening(SPOffset *offset,SPObject* to)
1059 {
1060 if ( to == NULL )
1061 return;
1063 offset->sourceObject = to;
1064 offset->sourceRepr = SP_OBJECT_REPR(to);
1066 offset->_delete_connection = SP_OBJECT(to)->connectDelete(sigc::bind(sigc::ptr_fun(&sp_offset_delete_self), offset));
1067 offset->_transformed_connection = SP_ITEM(to)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_offset_move_compensate), offset));
1068 offset->_modified_connection = SP_OBJECT(to)->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_offset_source_modified), offset));
1069 }
1071 static void sp_offset_quit_listening(SPOffset *offset)
1072 {
1073 if ( offset->sourceObject == NULL )
1074 return;
1076 offset->_modified_connection.disconnect();
1077 offset->_delete_connection.disconnect();
1078 offset->_transformed_connection.disconnect();
1080 offset->sourceRepr = NULL;
1081 offset->sourceObject = NULL;
1082 }
1084 static void
1085 sp_offset_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPOffset *offset)
1086 {
1087 sp_offset_quit_listening(offset);
1088 if (offset->sourceRef) {
1089 SPItem *refobj = offset->sourceRef->getObject();
1090 if (refobj) sp_offset_start_listening(offset,refobj);
1091 offset->sourceDirty=true;
1092 SP_OBJECT(offset)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1093 }
1094 }
1096 static void
1097 sp_offset_move_compensate(NR::Matrix const *mp, SPItem */*original*/, SPOffset *self)
1098 {
1099 guint mode = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_PARALLEL);
1100 if (mode == SP_CLONE_COMPENSATION_NONE) return;
1102 NR::Matrix m(*mp);
1103 if (!(m.is_translation())) return;
1105 // calculate the compensation matrix and the advertized movement matrix
1106 SPItem *item = SP_ITEM(self);
1108 NR::Matrix compensate;
1109 NR::Matrix advertized_move;
1111 if (mode == SP_CLONE_COMPENSATION_UNMOVED) {
1112 compensate = NR::identity();
1113 advertized_move.set_identity();
1114 } else if (mode == SP_CLONE_COMPENSATION_PARALLEL) {
1115 compensate = m;
1116 advertized_move = m;
1117 } else {
1118 g_assert_not_reached();
1119 }
1121 item->transform *= compensate;
1123 // commit the compensation
1124 sp_item_write_transform(item, SP_OBJECT_REPR(item), item->transform, &advertized_move);
1125 SP_OBJECT(item)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1126 }
1128 static void
1129 sp_offset_delete_self(SPObject */*deleted*/, SPOffset *offset)
1130 {
1131 guint const mode = prefs_get_int_attribute("options.cloneorphans", "value", SP_CLONE_ORPHANS_UNLINK);
1133 if (mode == SP_CLONE_ORPHANS_UNLINK) {
1134 // leave it be. just forget about the source
1135 sp_offset_quit_listening(offset);
1136 if ( offset->sourceHref ) g_free(offset->sourceHref);
1137 offset->sourceHref = NULL;
1138 offset->sourceRef->detach();
1139 } else if (mode == SP_CLONE_ORPHANS_DELETE) {
1140 SP_OBJECT(offset)->deleteObject();
1141 }
1142 }
1144 static void
1145 sp_offset_source_modified (SPObject */*iSource*/, guint /*flags*/, SPItem *item)
1146 {
1147 SPOffset *offset = SP_OFFSET(item);
1148 offset->sourceDirty=true;
1149 refresh_offset_source(offset);
1150 sp_shape_set_shape ((SPShape *) offset);
1151 }
1153 static void
1154 refresh_offset_source(SPOffset* offset)
1155 {
1156 if ( offset == NULL ) return;
1157 offset->sourceDirty=false;
1158 Path *orig = NULL;
1160 // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour
1161 // The bad case: no d attribute. Must check that it's an SPShape and then take the outline.
1162 SPObject *refobj=offset->sourceObject;
1163 if ( refobj == NULL ) return;
1164 SPItem *item = SP_ITEM (refobj);
1166 SPCurve *curve=NULL;
1167 if (!SP_IS_SHAPE (item) && !SP_IS_TEXT (item)) return;
1168 if (SP_IS_SHAPE (item)) {
1169 curve = sp_shape_get_curve (SP_SHAPE (item));
1170 if (curve == NULL)
1171 return;
1172 }
1173 if (SP_IS_TEXT (item)) {
1174 curve = SP_TEXT (item)->getNormalizedBpath ();
1175 if (curve == NULL)
1176 return;
1177 }
1178 orig = bpath_to_liv_path (SP_CURVE_BPATH(curve));
1179 sp_curve_unref (curve);
1182 // Finish up.
1183 {
1184 SPCSSAttr *css;
1185 const gchar *val;
1186 Shape *theShape = new Shape;
1187 Shape *theRes = new Shape;
1189 orig->ConvertWithBackData (1.0);
1190 orig->Fill (theShape, 0);
1192 css = sp_repr_css_attr (offset->sourceRepr , "style");
1193 val = sp_repr_css_property (css, "fill-rule", NULL);
1194 if (val && strcmp (val, "nonzero") == 0)
1195 {
1196 theRes->ConvertToShape (theShape, fill_nonZero);
1197 }
1198 else if (val && strcmp (val, "evenodd") == 0)
1199 {
1200 theRes->ConvertToShape (theShape, fill_oddEven);
1201 }
1202 else
1203 {
1204 theRes->ConvertToShape (theShape, fill_nonZero);
1205 }
1207 Path *originaux[1];
1208 originaux[0] = orig;
1209 Path *res = new Path;
1210 theRes->ConvertToForme (res, 1, originaux);
1212 delete theShape;
1213 delete theRes;
1215 char *res_d = res->svg_dump_path ();
1216 delete res;
1217 delete orig;
1219 SP_OBJECT (offset)->repr->setAttribute("inkscape:original", res_d);
1221 free (res_d);
1222 }
1223 }
1225 SPItem *
1226 sp_offset_get_source (SPOffset *offset)
1227 {
1228 if (offset && offset->sourceRef) {
1229 SPItem *refobj = offset->sourceRef->getObject();
1230 if (SP_IS_ITEM (refobj))
1231 return (SPItem *) refobj;
1232 }
1233 return NULL;
1234 }
1237 /*
1238 Local Variables:
1239 mode:c++
1240 c-file-style:"stroustrup"
1241 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1242 indent-tabs-mode:nil
1243 fill-column:99
1244 End:
1245 */
1246 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :