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