1
#define __SP_OFFSET_C__
4
* Implementation of <path sodipodi:type="inkscape:offset">.
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>
12
* Copyright (C) 1999-2002 Lauris Kaplinski
13
* Copyright (C) 2000-2001 Ximian, Inc.
15
* Released under GNU GPL, read the file 'COPYING' for more information
24
#include "attributes.h"
25
#include "display/curve.h"
26
#include <glibmm/i18n.h>
28
#include "livarot/Path.h"
29
#include "livarot/Shape.h"
32
#include "prefs-utils.h"
34
#include "sp-offset.h"
35
#include "sp-use-reference.h"
38
#include "libnr/n-art-bpath.h"
39
#include <libnr/nr-matrix-fns.h>
45
#define noOFFSET_VERBOSE
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).
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,
78
static void sp_offset_set (SPObject * object, unsigned int key,
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;
111
* Register SPOffset class and return its type number.
114
sp_offset_get_type (void)
116
static GType offset_type = 0;
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 */
128
16, /* n_preallocs */
129
(GInstanceInitFunc) sp_offset_init,
130
NULL, /* value_table */
133
g_type_register_static (SP_TYPE_SHAPE, "SPOffset", &offset_info,
140
* SPOffset vtable initialization.
143
sp_offset_class_init(SPOffsetClass *klass)
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;
167
* Callback for SPOffset object initialization.
170
sp_offset_init(SPOffset *offset)
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));
191
* Callback for SPOffset finalization.
194
sp_offset_finalize(GObject *obj)
196
SPOffset *offset = (SPOffset *) obj;
198
delete offset->sourceRef;
199
offset->_delete_connection.~connection();
200
offset->_changed_connection.~connection();
201
offset->_transformed_connection.~connection();
205
* Virtual build: set offset attributes from corresponding repr.
208
sp_offset_build(SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
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");
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");
222
if (object->repr->attribute("inkscape:original")) {
223
sp_object_read_attr (object, "inkscape:original");
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");
231
if (object->repr->attribute("xlink:href")) {
232
sp_object_read_attr(object, "xlink:href");
234
gchar const *oldA = object->repr->attribute("inkscape:href");
236
size_t lA = strlen(oldA);
237
char *nA=(char*)malloc((lA+1)*sizeof(char));
238
memcpy(nA+1,oldA,lA*sizeof(char));
241
object->repr->setAttribute("xlink:href",nA);
243
object->repr->setAttribute("inkscape:href",NULL);
245
sp_object_read_attr (object, "xlink:href");
250
* Virtual write: write offset attributes to corresponding repr.
252
static Inkscape::XML::Node *
253
sp_offset_write(SPObject *object, Inkscape::XML::Node *repr, guint flags)
255
SPOffset *offset = SP_OFFSET (object);
257
if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
258
repr = sp_repr_new ("svg:path");
261
if (flags & SP_OBJECT_WRITE_EXT) {
263
* Fixme: we may replace these attributes by
264
* inkscape:offset="cx cy exp revo rad arg t0"
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);
273
// Make sure the object has curve
274
SPCurve *curve = sp_shape_get_curve (SP_SHAPE (offset));
276
sp_offset_set_shape (SP_SHAPE (offset));
279
// write that curve to "d"
280
char *d = sp_svg_write_path (((SPShape *) offset)->curve->bpath);
281
repr->setAttribute("d", d);
284
if (((SPObjectClass *) (parent_class))->write)
285
((SPObjectClass *) (parent_class))->write (object, repr,
286
flags | SP_SHAPE_WRITE_PATH);
292
* Virtual release callback.
295
sp_offset_release(SPObject *object)
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);
318
* Set callback: the function that is called whenever a change is made to
319
* the description of the object.
322
sp_offset_set(SPObject *object, unsigned key, gchar const *value)
324
SPOffset *offset = SP_OFFSET (object);
326
if ( offset->sourceDirty ) refresh_offset_source(offset);
328
/* fixme: we should really collect updates */
331
case SP_ATTR_INKSCAPE_ORIGINAL:
332
case SP_ATTR_SODIPODI_ORIGINAL:
335
if (offset->original) {
336
free (offset->original);
337
delete ((Path *) offset->originalPath);
338
offset->original = NULL;
339
offset->originalPath = NULL;
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);
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
363
if ( offset->isUpdating == false ) object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
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();
373
if ( offset->sourceHref && ( strcmp(value, offset->sourceHref) == 0 ) ) {
375
if ( offset->sourceHref ) g_free(offset->sourceHref);
376
offset->sourceHref = g_strdup(value);
378
offset->sourceRef->attach(Inkscape::URI(value));
379
} catch (Inkscape::BadURIException &e) {
380
g_warning("%s", e.what());
381
offset->sourceRef->detach();
387
if (((SPObjectClass *) parent_class)->set)
388
((SPObjectClass *) parent_class)->set (object, key, value);
394
* Update callback: the object has changed, recompute its shape.
397
sp_offset_update(SPObject *object, SPCtx *ctx, guint flags)
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);
403
(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG |
404
SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
405
sp_shape_set_shape ((SPShape *) object);
407
offset->isUpdating=false;
409
if (((SPObjectClass *) parent_class)->update)
410
((SPObjectClass *) parent_class)->update (object, ctx, flags);
414
* Returns a textual description of object.
417
sp_offset_description(SPItem *item)
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));
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));
433
* Converts an NArtBpath (like the one stored in a SPCurve) into a
434
* livarot Path. Duplicate of splivarot.
437
bpath_to_liv_path(NArtBpath *bpath)
442
Path *dest = new Path;
443
dest->SetBackData (false);
450
for (i = 0; bpath[i].code != NR_END; i++)
452
switch (bpath[i].code)
458
NR::Point tmp(lastX,lastY);
465
NR::Point tmp(bpath[i].x3, bpath[i].y3);
467
tms[0]=3 * (bpath[i].x1 - lastX);
468
tms[1]=3 * (bpath[i].y1 - lastY);
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);
482
closed = (bpath[i].code == NR_MOVETO);
486
NR::Point tmp(lastX,lastY);
502
* Compute and set shape's offset.
505
sp_offset_set_shape(SPShape *shape)
507
SPOffset *offset = SP_OFFSET (shape);
509
if ( offset->originalPath == NULL ) {
510
// oops : no path?! (the offset object should do harakiri)
513
#ifdef OFFSET_VERBOSE
514
g_print ("rad=%g\n", offset->rad);
518
if ( fabs(offset->rad) < 0.01 ) {
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");
525
NArtBpath *bpath = sp_svg_read_path (res_d);
526
SPCurve *c = sp_curve_new_from_bpath (bpath);
528
sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE);
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;
546
Path *res = new Path;
547
res->SetBackData (false);
551
if (offset->rad >= 0)
553
o_width = offset->rad;
554
orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0);
558
o_width = -offset->rad;
559
orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0);
564
// res->ConvertForOffset (1.0, orig, offset->rad);
565
res->ConvertWithBackData (1.0);
569
// res->ConvertForOffset (o_width, orig, offset->rad);
570
res->ConvertWithBackData (o_width);
572
res->Fill (theShape, 0);
573
theRes->ConvertToShape (theShape, fill_positive);
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);
585
orig->Coalesce (size * 0.001);
586
//g_print ("coa %g exp %g item %p\n", size * 0.001, exp, item);
590
// if (o_width >= 1.0)
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);
599
// orig->Coalesce (0.1*o_width);
600
// orig->ConvertEvenLines (o_width);
601
// orig->Simplify (0.5 * o_width);
608
// version par makeoffset
609
Shape *theShape = new Shape;
610
Shape *theRes = new Shape;
615
if (offset->rad >= 0)
617
o_width = offset->rad;
621
o_width = -offset->rad;
624
// one has to have a measure of the details
627
orig->ConvertWithBackData (0.5);
631
orig->ConvertWithBackData (0.5*o_width);
633
orig->Fill (theShape, 0);
634
theRes->ConvertToShape (theShape, fill_positive);
637
Path *res = new Path;
638
theRes->ConvertToForme (res, 1, originaux);
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
645
Shape* onePart=new Shape;
646
Shape* oneCleanPart=new Shape;
648
for (int i=0;i<nbPart;i++) {
649
double partSurf=parts[i]->Surface();
650
parts[i]->Convert(1.0);
652
// raffiner si besoin
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);
660
if ( partSurf < 0 ) { // inverse par rapport a la realite
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);
669
double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
670
if ( typicalSize < 0.05 ) typicalSize=0.05;
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
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);*/
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
696
double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
697
if ( typicalSize < 0.05 ) typicalSize=0.05;
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
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);*/
717
// theShape->MakeOffset(theRes,offset->rad,join_round,20.0);
723
for (int i=0;i<nbPart;i++) {
725
parts[i]->ConvertWithBackData(1.0);
727
parts[i]->Fill(theShape,i,true,true,true);
729
parts[i]->Fill(theShape,i,true,true,false);
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];
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);
749
orig->ConvertEvenLines (1.0*o_width);
750
orig->Simplify (1.0 * o_width);
753
if ( parts ) free(parts);
754
if ( holes ) free(holes);
761
if (orig->descr_cmd.size() <= 1)
763
// Aie.... nothing left.
764
res_d = strdup ("M 0 0 L 0 0 z");
765
//printf("%s\n",res_d);
770
res_d = orig->svg_dump_path ();
774
NArtBpath *bpath = sp_svg_read_path (res_d);
775
SPCurve *c = sp_curve_new_from_bpath (bpath);
777
sp_shape_set_curve_insync ((SPShape *) offset, c, TRUE);
785
* Virtual snappoints function.
787
static void sp_offset_snappoints(SPItem const *item, SnapPointsIter p)
789
if (((SPItemClass *) parent_class)->snappoints) {
790
((SPItemClass *) parent_class)->snappoints (item, p);
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
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)
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.
815
vectors_are_clockwise (NR::Point A, NR::Point B, NR::Point C)
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);
831
ab_a = 2 * M_PI - ab_a;
832
double bc_a = acos (bc_c);
838
bc_a = 2 * M_PI - bc_a;
839
double ca_a = acos (ca_c);
845
ca_a = 2 * M_PI - ca_a;
847
double lim = 2 * M_PI - ca_a;
855
* Distance to the original path; that function is called from object-edit
856
* to set the radius when the control knot moves.
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'.
863
sp_offset_distance_to_original (SPOffset * offset, NR::Point px)
865
if (offset == NULL || offset->originalPath == NULL
866
|| ((Path *) offset->originalPath)->descr_cmd.size() <= 1)
869
Shape *theShape = new Shape;
870
Shape *theRes = new Shape;
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.
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)
892
double ptDist = -1.0;
894
double arDist = -1.0;
896
// first get the minimum distance to the points
897
for (int i = 0; i < theRes->numberOfPoints(); i++)
899
if (theRes->getPoint(i).totalDegree() > 0)
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))
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));
912
fb = theRes->getPoint(i).incidentEdge[LAST];
913
pb = theRes->getPoint(i).incidentEdge[LAST];
914
cb = theRes->getPoint(i).incidentEdge[FIRST];
919
prx = theRes->getEdge(pb).dx;
920
nlen = sqrt (dot(prx, prx));
922
nex = theRes->getEdge(cb).dx;
923
nlen = sqrt (dot(nex , nex));
925
if (theRes->getEdge(pb).en == i)
929
if (theRes->getEdge(cb).en == i)
934
if (vectors_are_clockwise (nex, nx, prx))
936
// we're in that angle. set the sign, and exit that loop
937
if (theRes->getEdge(cb).st == i)
950
cb = theRes->NextAt (i, cb);
952
while (cb >= 0 && pb >= 0 && pb != fb);
956
// loop over the edges to try to improve the distance
957
for (int i = 0; i < theRes->numberOfEdges(); i++)
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));
965
NR::Point pxsx=px-sx;
966
double ab = dot(nx,pxsx);
967
if (ab > 0 && ab < len * len)
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))
985
if (fabs (ptDist) < fabs (arDist))
999
* Computes a point on the offset; used to set a "seed" position for
1002
* \return the topmost point on the offset.
1005
sp_offset_top_point (SPOffset * offset, NR::Point *px)
1007
(*px) = NR::Point(0, 0);
1011
if (offset->knotSet)
1013
(*px) = offset->knot;
1017
SPCurve *curve = sp_shape_get_curve (SP_SHAPE (offset));
1020
sp_offset_set_shape (SP_SHAPE (offset));
1021
curve = sp_shape_get_curve (SP_SHAPE (offset));
1026
Path *finalPath = bpath_to_liv_path (curve->bpath);
1027
if (finalPath == NULL)
1029
sp_curve_unref (curve);
1033
Shape *theShape = new Shape;
1035
finalPath->Convert (1.0);
1036
finalPath->Fill (theShape, 0);
1038
if (theShape->hasPoints())
1040
theShape->SortPoints ();
1041
*px = theShape->getPoint(0).x;
1046
sp_curve_unref (curve);
1049
// the listening functions
1050
static void sp_offset_start_listening(SPOffset *offset,SPObject* to)
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);
1063
static void sp_offset_quit_listening(SPOffset *offset)
1065
if ( offset->sourceObject == NULL )
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;
1077
sp_offset_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPOffset *offset)
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);
1089
sp_offset_move_compensate(NR::Matrix const *mp, SPItem *original, SPOffset *self)
1091
guint mode = prefs_get_int_attribute("options.clonecompensation", "value", SP_CLONE_COMPENSATION_PARALLEL);
1092
if (mode == SP_CLONE_COMPENSATION_NONE) return;
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) {
1108
advertized_move = m;
1110
g_assert_not_reached();
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);
1121
sp_offset_delete_self(SPObject */*deleted*/, SPOffset *offset)
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();
1137
sp_offset_source_modified (SPObject *iSource, guint flags, SPItem *item)
1139
SPOffset *offset = SP_OFFSET(item);
1140
offset->sourceDirty=true;
1141
refresh_offset_source(offset);
1142
sp_shape_set_shape ((SPShape *) offset);
1146
refresh_offset_source(SPOffset* offset)
1148
if ( offset == NULL ) return;
1149
offset->sourceDirty=false;
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));
1165
if (SP_IS_TEXT (item)) {
1166
curve = SP_TEXT (item)->getNormalizedBpath ();
1170
orig = bpath_to_liv_path (curve->bpath);
1171
sp_curve_unref (curve);
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)
1188
theRes->ConvertToShape (theShape, fill_nonZero);
1190
else if (val && strcmp (val, "evenodd") == 0)
1192
theRes->ConvertToShape (theShape, fill_oddEven);
1196
theRes->ConvertToShape (theShape, fill_nonZero);
1200
originaux[0] = orig;
1201
Path *res = new Path;
1202
theRes->ConvertToForme (res, 1, originaux);
1207
char *res_d = res->svg_dump_path ();
1211
SP_OBJECT (offset)->repr->setAttribute("inkscape:original", res_d);
1218
sp_offset_get_source (SPOffset *offset)
1220
if (offset && offset->sourceRef) {
1221
SPItem *refobj = offset->sourceRef->getObject();
1222
if (SP_IS_ITEM (refobj))
1223
return (SPItem *) refobj;
1232
c-file-style:"stroustrup"
1233
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1234
indent-tabs-mode:nil
1238
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :