Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
sp-offset.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/*
7 * Authors: (of the sp-spiral.c upon which this file was constructed):
8 * Mitsuru Oka <oka326@parkcity.ne.jp>
9 * Lauris Kaplinski <lauris@kaplinski.com>
10 * Abhishek Sharma
11 *
12 * Copyright (C) 1999-2002 Lauris Kaplinski
13 * Copyright (C) 2000-2001 Ximian, Inc.
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18#include "sp-offset.h"
19
20#include <cstring>
21#include <string>
22
23#include <glibmm/i18n.h>
24
25#include "bad-uri-exception.h"
26#include "svg/svg.h"
27#include "attributes.h"
28#include "display/curve.h"
29
30#include "livarot/Path.h"
31#include "livarot/Shape.h"
32
33#include "enums.h"
34#include "preferences.h"
35#include "sp-text.h"
36#include "sp-use-reference.h"
37#include "uri.h"
38
39class SPDocument;
40
41#define noOFFSET_VERBOSE
42
67
70static void sp_offset_move_compensate(Geom::Affine const *mp, SPItem *original, SPOffset *self);
71static void sp_offset_delete_self(SPObject *deleted, SPOffset *self);
72static void sp_offset_source_modified (SPObject *iSource, guint flags, SPItem *item);
73
74
75// slow= source path->polygon->offset of polygon->polygon->path
76// fast= source path->offset of source path->polygon->path
77// fast is not mathematically correct, because computing the offset of a single
78// cubic bezier patch is not trivial; in particular, there are problems with holes
79// reappearing in offset when the radius becomes too large
80//TODO: need fix for bug: #384688 with fix released in r.14156
81//but reverted because bug #1507049 seems has more priority.
83
85 this->rad = 1.0;
86 this->original = nullptr;
87 this->originalPath = nullptr;
88 this->knotSet = false;
89 this->sourceDirty=false;
90 this->isUpdating=false;
91 // init various connections
92 this->sourceHref = nullptr;
93 this->sourceRepr = nullptr;
94 this->sourceObject = nullptr;
95
96 // set up the uri reference
97 this->sourceRef = new SPUseReference(this);
98 this->_changed_connection = this->sourceRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_offset_href_changed), this));
99}
100
102 delete this->sourceRef;
103
104 this->_modified_connection.disconnect();
105 this->_delete_connection.disconnect();
106 this->_changed_connection.disconnect();
107 this->_transformed_connection.disconnect();
108}
109
112
113 //XML Tree being used directly here while it shouldn't be.
114 if (this->getRepr()->attribute("inkscape:radius")) {
116 } else {
117 //XML Tree being used directly here (as object->getRepr)
118 //in all the below lines in the block while it shouldn't be.
119 gchar const *oldA = this->getRepr()->attribute("sodipodi:radius");
120 this->setAttribute("inkscape:radius", oldA);
121 this->removeAttribute("sodipodi:radius");
122
124 }
125
126 if (this->getRepr()->attribute("inkscape:original")) {
128 } else {
129 gchar const *oldA = this->getRepr()->attribute("sodipodi:original");
130 this->setAttribute("inkscape:original", oldA);
131 this->removeAttribute("sodipodi:original");
132
134 }
135
136 if (this->getRepr()->attribute("xlink:href")) {
138 } else {
139 gchar const *oldA = this->getRepr()->attribute("inkscape:href");
140
141 if (oldA) {
142 size_t lA = strlen(oldA);
143 char *nA=(char*)malloc((1+lA+1)*sizeof(char));
144
145 memcpy(nA+1,oldA,lA*sizeof(char));
146
147 nA[0]='#';
148 nA[lA+1]=0;
149
150 this->setAttribute("xlink:href", nA);
151
152 free(nA);
153
154 this->removeAttribute("inkscape:href");
155 }
156
158 }
159}
160
162 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
163 repr = xml_doc->createElement("svg:path");
164 }
165
166 if (flags & SP_OBJECT_WRITE_EXT) {
171 repr->setAttribute("sodipodi:type", "inkscape:offset");
172 repr->setAttributeSvgDouble("inkscape:radius", this->rad);
173 repr->setAttribute("inkscape:original", this->original);
174 repr->setAttribute("inkscape:href", this->sourceHref);
175 }
176
177
178 // Make sure the offset has curve
179 if (!_curve) {
180 set_shape();
181 }
182
183 // write that curve to "d"
184 repr->setAttribute("d", sp_svg_write_path(_curve->get_pathvector()));
185
186 SPShape::write(xml_doc, repr, flags | SP_SHAPE_WRITE_PATH);
187
188 return repr;
189}
190
192 if (this->original) {
193 free (this->original);
194 }
195
196 if (this->originalPath) {
197 delete ((Path *) this->originalPath);
198 }
199
200 this->original = nullptr;
201 this->originalPath = nullptr;
202
204
205 this->_changed_connection.disconnect();
206
207 g_free(this->sourceHref);
208
209 this->sourceHref = nullptr;
210 this->sourceRef->detach();
211
213}
214
215void SPOffset::set(SPAttr key, const gchar* value) {
216 if ( this->sourceDirty ) {
218 }
219
220 /* fixme: we should really collect updates */
221 switch (key)
222 {
225 if (value == nullptr) {
226 } else {
227 if (this->original) {
228 free (this->original);
229 delete ((Path *) this->originalPath);
230
231 this->original = nullptr;
232 this->originalPath = nullptr;
233 }
234
235 this->original = strdup (value);
236
238
239 this->originalPath = new Path;
240 reinterpret_cast<Path *>(this->originalPath)->LoadPathVector(pv);
241
242 this->knotSet = false;
243
244 if ( this->isUpdating == false ) {
245 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
246 }
247 }
248 break;
249
252 if (!sp_svg_length_read_computed_absolute (value, &this->rad)) {
253 if (fabs (this->rad) < 0.01) {
254 this->rad = (this->rad < 0) ? -0.01 : 0.01;
255 }
256
257 this->knotSet = false; // knotset=false because it's not set from the context
258 }
259
260 if ( this->isUpdating == false ) {
261 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
262 }
263 break;
264
267 if ( value == nullptr ) {
269 if ( this->sourceHref ) {
270 g_free(this->sourceHref);
271 }
272
273 this->sourceHref = nullptr;
274 this->sourceRef->detach();
275 } else {
276 if ( this->sourceHref && ( strcmp(value, this->sourceHref) == 0 ) ) {
277 } else {
278 if ( this->sourceHref ) {
279 g_free(this->sourceHref);
280 }
281
282 this->sourceHref = g_strdup(value);
283
284 try {
285 this->sourceRef->attach(Inkscape::URI(value));
286 } catch (Inkscape::BadURIException &e) {
287 g_warning("%s", e.what());
288 this->sourceRef->detach();
289 }
290 }
291 }
292 break;
293
294 default:
295 SPShape::set(key, value);
296 break;
297 }
298}
299
300void SPOffset::update(SPCtx *ctx, guint flags) {
301 this->isUpdating=true; // prevent sp_offset_set from requesting updates
302
303 if ( this->sourceDirty ) {
305 }
306
307 if (flags &
308 (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG |
309 SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
310
311 this->set_shape();
312 }
313
314 this->isUpdating=false;
315
316 SPShape::update(ctx, flags);
317}
318
319const char* SPOffset::displayName() const {
320 if ( this->sourceHref ) {
321 return _("Linked Offset");
322 } else {
323 return _("Dynamic Offset");
324 }
325}
326
327const char* SPOffset::typeName() const {
328 return "offset";
329}
330
331gchar* SPOffset::description() const {
332 // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
333 return g_strdup_printf(_("%s by %f pt"), (this->rad >= 0) ?
334 _("outset") : _("inset"), fabs (this->rad));
335}
336
338 if ( this->originalPath == nullptr ) {
339 // oops : no path?! (the offset object should do harakiri)
340 return;
341 }
342#ifdef OFFSET_VERBOSE
343 g_print ("rad=%g\n", offset->rad);
344#endif
345 // au boulot
346
347 if ( fabs(this->rad) < 0.01 ) {
348 // grosso modo: 0
349 // just put the source of this (almost-non-offsetted) object as being the actual offset,
350 // no one will notice. it's also useless to compute the offset with a 0 radius
351
352 //XML Tree being used directly here while it shouldn't be.
353 const char *res_d = this->getRepr()->attribute("inkscape:original");
354
355 if ( res_d ) {
358 }
359
360 return;
361 }
362
363 // extra paranoiac careful check. the preceding if () should take care of this case
364 if (fabs (this->rad) < 0.01) {
365 this->rad = (this->rad < 0) ? -0.01 : 0.01;
366 }
367
368 Path *orig = new Path;
369 orig->Copy ((Path *)this->originalPath);
370
371 if ( use_slow_but_correct_offset_method == false ) {
372 // version par outline
373 Shape *theShape = new Shape;
374 Shape *theRes = new Shape;
375 Path *originaux[1];
376 Path *res = new Path;
377 res->SetBackData (false);
378
379 // and now: offset
380 float o_width;
381 if (this->rad >= 0)
382 {
383 o_width = this->rad;
384 orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0);
385 }
386 else
387 {
388 o_width = -this->rad;
389 orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0);
390 }
391
392 if (o_width >= 1.0)
393 {
394 // res->ConvertForOffset (1.0, orig, offset->rad);
395 res->ConvertWithBackData (1.0);
396 }
397 else
398 {
399 // res->ConvertForOffset (o_width, orig, offset->rad);
400 res->ConvertWithBackData (o_width);
401 }
402 res->Fill (theShape, 0);
403 theRes->ConvertToShape (theShape, fill_positive);
404 originaux[0] = res;
405
406 theRes->ConvertToForme (orig, 1, originaux);
407
409
410 if ( bbox ) {
411 gdouble size = L2(bbox->dimensions());
412 gdouble const exp = this->transform.descrim();
413
414 if (exp != 0) {
415 size /= exp;
416 }
417
418 orig->Coalesce (size * 0.001);
419 //g_print ("coa %g exp %g item %p\n", size * 0.001, exp, item);
420 }
421
422
423 // if (o_width >= 1.0)
424 // {
425 // orig->Coalesce (0.1); // small threshold, since we only want to get rid of small segments
426 // the curve should already be computed by the Outline() function
427 // orig->ConvertEvenLines (1.0);
428 // orig->Simplify (0.5);
429 // }
430 // else
431 // {
432 // orig->Coalesce (0.1*o_width);
433 // orig->ConvertEvenLines (o_width);
434 // orig->Simplify (0.5 * o_width);
435 // }
436
437 delete theShape;
438 delete theRes;
439 delete res;
440 } else {
441 // version par makeoffset
442 Shape *theShape = new Shape;
443 Shape *theRes = new Shape;
444
445
446 // and now: offset
447 float o_width;
448 if (this->rad >= 0)
449 {
450 o_width = this->rad;
451 }
452 else
453 {
454 o_width = -this->rad;
455 }
456
457 // one has to have a measure of the details
458 if (o_width >= 1.0)
459 {
460 orig->ConvertWithBackData (0.5);
461 }
462 else
463 {
464 orig->ConvertWithBackData (0.5*o_width);
465 }
466
467 orig->Fill (theShape, 0);
468 theRes->ConvertToShape (theShape, fill_positive);
469
470 Path *originaux[1];
471 originaux[0]=orig;
472
473 Path *res = new Path;
474 theRes->ConvertToForme (res, 1, originaux);
475
476 int nbPart=0;
477 Path** parts=res->SubPaths(nbPart,true);
478 char *holes=(char*)malloc(nbPart*sizeof(char));
479
480 // we offset contours separately, because we can.
481 // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes
482 {
483 Shape* onePart=new Shape;
484 Shape* oneCleanPart=new Shape;
485
486 theShape->Reset();
487
488 for (int i=0;i<nbPart;i++) {
489 double partSurf=parts[i]->Surface();
490 parts[i]->Convert(1.0);
491
492 {
493 // raffiner si besoin
494 double bL,bT,bR,bB;
495 parts[i]->PolylineBoundingBox(bL,bT,bR,bB);
496 double measure=((bR-bL)+(bB-bT))*0.5;
497 if ( measure < 10.0 ) {
498 parts[i]->Convert(0.02*measure);
499 }
500 }
501
502 if ( partSurf < 0 ) { // inverse par rapport a la realite
503 // plein
504 holes[i]=0;
505 parts[i]->Fill(oneCleanPart,0);
506 onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges
507 oneCleanPart->MakeOffset(onePart,this->rad,join_round,20.0);
508 onePart->ConvertToShape(oneCleanPart,fill_positive);
509
510 onePart->CalcBBox();
511 double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
512
513 if ( typicalSize < 0.05 ) {
514 typicalSize=0.05;
515 }
516
517 typicalSize*=0.01;
518
519 if ( typicalSize > 1.0 ) {
520 typicalSize=1.0;
521 }
522
523 onePart->ConvertToForme (parts[i]);
524 parts[i]->ConvertEvenLines (typicalSize);
525 parts[i]->Simplify (typicalSize);
526
527 double nPartSurf=parts[i]->Surface();
528
529 if ( nPartSurf >= 0 ) {
530 // inversion de la surface -> disparait
531 delete parts[i];
532 parts[i]=nullptr;
533 } else {
534 }
535
536/* int firstP=theShape->nbPt;
537 for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
538 for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/
539 } else {
540 // trou
541 holes[i]=1;
542 parts[i]->Fill(oneCleanPart,0,false,true,true);
543 onePart->ConvertToShape(oneCleanPart,fill_positive);
544 oneCleanPart->MakeOffset(onePart,-this->rad,join_round,20.0);
545 onePart->ConvertToShape(oneCleanPart,fill_positive);
546// for (int j=0;j<onePart->nbAr;j++) onePart->Inverse(j); // pas oublier de reinverser
547
548 onePart->CalcBBox();
549 double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY));
550
551 if ( typicalSize < 0.05 ) {
552 typicalSize=0.05;
553 }
554
555 typicalSize*=0.01;
556
557 if ( typicalSize > 1.0 ) {
558 typicalSize=1.0;
559 }
560
561 onePart->ConvertToForme (parts[i]);
562 parts[i]->ConvertEvenLines (typicalSize);
563 parts[i]->Simplify (typicalSize);
564 double nPartSurf=parts[i]->Surface();
565
566 if ( nPartSurf >= 0 ) {
567 // inversion de la surface -> disparait
568 delete parts[i];
569 parts[i]=nullptr;
570 } else {
571 }
572
573 /* int firstP=theShape->nbPt;
574 for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x);
575 for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/
576 }
577// delete parts[i];
578 }
579// theShape->MakeOffset(theRes,offset->rad,join_round,20.0);
580 delete onePart;
581 delete oneCleanPart;
582 }
583
584 if ( nbPart > 1 ) {
585 theShape->Reset();
586
587 for (int i=0;i<nbPart;i++) {
588 if ( parts[i] ) {
589 parts[i]->ConvertWithBackData(1.0);
590
591 if ( holes[i] ) {
592 parts[i]->Fill(theShape,i,true,true,true);
593 } else {
594 parts[i]->Fill(theShape,i,true,true,false);
595 }
596 }
597 }
598
599 theRes->ConvertToShape (theShape, fill_positive);
600 theRes->ConvertToForme (orig,nbPart,parts);
601
602 for (int i=0;i<nbPart;i++) {
603 if ( parts[i] ) {
604 delete parts[i];
605 }
606 }
607 } else if ( nbPart == 1 ) {
608 orig->Copy(parts[0]);
609
610 for (int i=0;i<nbPart;i++) {
611 if ( parts[i] ) {
612 delete parts[i];
613 }
614 }
615 } else {
616 orig->Reset();
617 }
618// theRes->ConvertToShape (theShape, fill_positive);
619// theRes->ConvertToForme (orig);
620
621/* if (o_width >= 1.0) {
622 orig->ConvertEvenLines (1.0);
623 orig->Simplify (1.0);
624 } else {
625 orig->ConvertEvenLines (1.0*o_width);
626 orig->Simplify (1.0 * o_width);
627 }*/
628
629 if ( parts ) {
630 free(parts);
631 }
632
633 if ( holes ) {
634 free(holes);
635 }
636
637 delete res;
638 delete theShape;
639 delete theRes;
640 }
641 {
642 Geom::PathVector res_pv;
643
644 if (orig->descr_cmd.size() <= 1) {
645 // Aie.... nothing left.
646 res_pv = sp_svg_read_pathv("M 0 0 L 0 0 z");
647 } else {
648 res_pv = orig->MakePathVector();
649 }
650
651 delete orig;
652
653 setCurveInsync(SPCurve(std::move(res_pv)));
655 }
656}
657
658void SPOffset::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const {
659 SPShape::snappoints(p, snapprefs);
660}
661
662
663// utilitaires pour les poignees
664// used to get the distance to the shape: distance to polygon give the fabs(radius), we still need
665// the sign. for edges, it's easy to determine which side the point is on, for points of the polygon
666// it's trickier: we need to identify which angle the point is in; to that effect, we take each
667// successive clockwise angle (A,C) and check if the vector B given by the point is in the angle or
668// outside.
669// another method would be to use the Winding() function to test whether the point is inside or outside
670// the polygon (it would be wiser to do so, in fact, but i like being stupid)
671
682static bool
684{
685 using Geom::rot90;
686 double ab_s = dot(A, rot90(B));
687 double ab_c = dot(A, B);
688 double ca_s = dot(C, rot90(A));
689 double ca_c = dot(C, A);
690
691 double ab_a = acos (ab_c);
692
693 if (ab_c <= -1.0) {
694 ab_a = M_PI;
695 }
696
697 if (ab_c >= 1.0) {
698 ab_a = 0;
699 }
700
701 if (ab_s < 0) {
702 ab_a = 2 * M_PI - ab_a;
703 }
704
705 double ca_a = acos (ca_c);
706
707 if (ca_c <= -1.0) {
708 ca_a = M_PI;
709 }
710
711 if (ca_c >= 1.0) {
712 ca_a = 0;
713 }
714
715 if (ca_s < 0) {
716 ca_a = 2 * M_PI - ca_a;
717 }
718
719 double lim = 2 * M_PI - ca_a;
720
721 if (ab_a < lim) {
722 return true;
723 }
724
725 return false;
726}
727
736double
738{
739 if (offset == nullptr || offset->originalPath == nullptr || ((Path *) offset->originalPath)->descr_cmd.size() <= 1) {
740 return 1.0;
741 }
742
743 double dist = 1.0;
744 Shape *theShape = new Shape;
745 Shape *theRes = new Shape;
746
756 // move
757 ((Path *) offset->originalPath)->Convert (1.0);
758 ((Path *) offset->originalPath)->Fill (theShape, 0);
759 theRes->ConvertToShape (theShape, fill_oddEven);
760
761 if (theRes->numberOfEdges() <= 1)
762 {
763
764 }
765 else
766 {
767 double ptDist = -1.0;
768 bool ptSet = false;
769 double arDist = -1.0;
770 bool arSet = false;
771
772 // first get the minimum distance to the points
773 for (int i = 0; i < theRes->numberOfPoints(); i++)
774 {
775 if (theRes->getPoint(i).totalDegree() > 0)
776 {
777 Geom::Point nx = theRes->getPoint(i).x;
778 Geom::Point nxpx = px-nx;
779 double ndist = sqrt (dot(nxpx,nxpx));
780
781 if (ptSet == false || fabs (ndist) < fabs (ptDist))
782 {
783 // we have a new minimum distance
784 // now we need to wheck if px is inside or outside (for the sign)
785 nx = px - theRes->getPoint(i).x;
786 double nlen = sqrt (dot(nx , nx));
787 nx /= nlen;
788 int pb, cb, fb;
789 fb = theRes->getPoint(i).incidentEdge[LAST];
790 pb = theRes->getPoint(i).incidentEdge[LAST];
791 cb = theRes->getPoint(i).incidentEdge[FIRST];
792
793 do
794 {
795 // one angle
796 Geom::Point prx, nex;
797 prx = theRes->getEdge(pb).dx;
798 nlen = sqrt (dot(prx, prx));
799 prx /= nlen;
800 nex = theRes->getEdge(cb).dx;
801 nlen = sqrt (dot(nex , nex));
802 nex /= nlen;
803
804 if (theRes->getEdge(pb).en == i)
805 {
806 prx = -prx;
807 }
808
809 if (theRes->getEdge(cb).en == i)
810 {
811 nex = -nex;
812 }
813
814 if (vectors_are_clockwise (nex, nx, prx))
815 {
816 // we're in that angle. set the sign, and exit that loop
817 if (theRes->getEdge(cb).st == i)
818 {
819 ptDist = -ndist;
820 ptSet = true;
821 }
822 else
823 {
824 ptDist = ndist;
825 ptSet = true;
826 }
827 break;
828 }
829
830 pb = cb;
831 cb = theRes->NextAt (i, cb);
832 }
833
834 while (cb >= 0 && pb >= 0 && pb != fb);
835 }
836 }
837 }
838
839 // loop over the edges to try to improve the distance
840 for (int i = 0; i < theRes->numberOfEdges(); i++)
841 {
842 Geom::Point sx = theRes->getPoint(theRes->getEdge(i).st).x;
843 Geom::Point ex = theRes->getPoint(theRes->getEdge(i).en).x;
844 Geom::Point nx = ex - sx;
845 double len = sqrt (dot(nx,nx));
846
847 if (len > 0.0001)
848 {
849 Geom::Point pxsx=px-sx;
850 double ab = dot(nx,pxsx);
851
852 if (ab > 0 && ab < len * len)
853 {
854 // we're in the zone of influence of the segment
855 double ndist = (cross(nx, pxsx)) / len;
856
857 if (arSet == false || fabs (ndist) < fabs (arDist))
858 {
859 arDist = ndist;
860 arSet = true;
861 }
862 }
863 }
864 }
865
866 if (arSet || ptSet)
867 {
868 if (arSet == false) {
869 arDist = ptDist;
870 }
871
872 if (ptSet == false) {
873 ptDist = arDist;
874 }
875
876 if (fabs (ptDist) < fabs (arDist)) {
877 dist = ptDist;
878 } else {
879 dist = arDist;
880 }
881 }
882 }
883
884 delete theShape;
885 delete theRes;
886
887 return dist;
888}
889
896void
898{
899 (*px) = Geom::Point(0, 0);
900
901 if (offset == nullptr) {
902 return;
903 }
904
905 if (offset->knotSet)
906 {
907 (*px) = offset->knot;
908 return;
909 }
910
911 SPCurve const *curve = offset->curve();
912
913 if (curve == nullptr)
914 {
915 const_cast<SPOffset*>(offset)->set_shape();
916
917 curve = offset->curve();
918
919 if (curve == nullptr)
920 return;
921 }
922
923 if (curve->is_empty())
924 {
925 return;
926 }
927
928 Path *finalPath = new Path;
929 finalPath->LoadPathVector(curve->get_pathvector());
930
931 Shape *theShape = new Shape;
932
933 finalPath->Convert (1.0);
934 finalPath->Fill (theShape, 0);
935
936 if (theShape->hasPoints())
937 {
938 theShape->SortPoints ();
939 *px = theShape->getPoint(0).x;
940 }
941
942 delete theShape;
943 delete finalPath;
944}
945
946// the listening functions
948{
949 if ( to == nullptr ) {
950 return;
951 }
952
953 offset->sourceObject = to;
954 offset->sourceRepr = to->getRepr();
955
956 offset->_delete_connection = to->connectDelete(sigc::bind(sigc::ptr_fun(&sp_offset_delete_self), offset));
957 offset->_transformed_connection = to->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_offset_move_compensate), offset));
958 offset->_modified_connection = to->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_offset_source_modified), offset));
959}
960
962{
963 if ( offset->sourceObject == nullptr ) {
964 return;
965 }
966
967 offset->_modified_connection.disconnect();
968 offset->_delete_connection.disconnect();
969 offset->_transformed_connection.disconnect();
970
971 offset->sourceRepr = nullptr;
972 offset->sourceObject = nullptr;
973}
974
975static void
977{
979
980 if (offset->sourceRef) {
981 SPItem *refobj = offset->sourceRef->getObject();
982
983 if (refobj) {
985 }
986
987 offset->sourceDirty=true;
988 offset->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
989 }
990}
991
992static void sp_offset_move_compensate(Geom::Affine const *mp, SPItem */*original*/, SPOffset *self)
993{
995 guint mode = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_PARALLEL);
996
997 Geom::Affine m(*mp);
998
1000 self->sourceDirty=true;
1001 self->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1002 return;
1003 }
1004
1005 // calculate the compensation matrix and the advertized movement matrix
1007
1008 Geom::Affine t = self->transform;
1009 Geom::Affine offset_move = t.inverse() * m * t;
1010
1011 Geom::Affine advertized_move;
1013 offset_move = offset_move.inverse() * m;
1014 advertized_move = m;
1015 } else if (mode == SP_CLONE_COMPENSATION_UNMOVED) {
1016 offset_move = offset_move.inverse();
1017 advertized_move.setIdentity();
1018 } else {
1019 g_assert_not_reached();
1020 }
1021
1022 self->sourceDirty=true;
1023
1024 // commit the compensation
1025 self->transform *= offset_move;
1026 self->doWriteTransform(self->transform, &advertized_move);
1027 self->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1028}
1029
1030static void
1032{
1034 guint const mode = prefs->getInt("/options/cloneorphans/value", SP_CLONE_ORPHANS_UNLINK);
1035
1037 // leave it be. just forget about the source
1039
1040 if ( offset->sourceHref ) {
1041 g_free(offset->sourceHref);
1042 }
1043
1044 offset->sourceHref = nullptr;
1045 offset->sourceRef->detach();
1046 } else if (mode == SP_CLONE_ORPHANS_DELETE) {
1047 offset->deleteObject();
1048 }
1049}
1050
1051static void
1052sp_offset_source_modified (SPObject */*iSource*/, guint flags, SPItem *item)
1053{
1054 auto offset = cast<SPOffset>(item);
1055 offset->sourceDirty=true;
1056
1057 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)) {
1058 offset->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1059 }
1060}
1061
1062static void
1064{
1065 if ( offset == nullptr ) {
1066 return;
1067 }
1068
1069 offset->sourceDirty=false;
1070
1071 // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour
1072 // The bad case: no d attribute. Must check that it's an SPShape and then take the outline.
1073 SPItem *item = offset->sourceObject;
1074
1075 if (item == nullptr) {
1076 return;
1077 }
1078
1079 SPCurve curve;
1080
1081 if (auto shape = cast<SPShape>(item)) {
1082 if (!shape->curve()) {
1083 return;
1084 }
1085 curve = *shape->curve();
1086 } else if (auto text = cast<SPText>(item)) {
1087 curve = text->getNormalizedBpath();
1088 } else {
1089 return;
1090 }
1091
1092 Path *orig = new Path;
1093 orig->LoadPathVector(curve.get_pathvector());
1094
1095 if (!item->transform.isIdentity()) {
1096 gchar const *t_attr = item->getRepr()->attribute("transform");
1097
1098 if (t_attr) {
1099 Geom::Affine t;
1100
1101 if (sp_svg_transform_read(t_attr, &t)) {
1102 orig->Transform(t);
1103 }
1104 }
1105 }
1106
1107 // Finish up.
1108 {
1109 SPCSSAttr *css;
1110 const gchar *val;
1111 Shape *theShape = new Shape;
1112 Shape *theRes = new Shape;
1113
1114 orig->ConvertWithBackData (1.0);
1115 orig->Fill (theShape, 0);
1116
1117 css = sp_repr_css_attr (offset->sourceRepr , "style");
1118 val = sp_repr_css_property (css, "fill-rule", nullptr);
1119
1120 if (val && strcmp (val, "nonzero") == 0)
1121 {
1122 theRes->ConvertToShape (theShape, fill_nonZero);
1123 }
1124 else if (val && strcmp (val, "evenodd") == 0)
1125 {
1126 theRes->ConvertToShape (theShape, fill_oddEven);
1127 }
1128 else
1129 {
1130 theRes->ConvertToShape (theShape, fill_nonZero);
1131 }
1132
1133 Path *originaux[1];
1134 originaux[0] = orig;
1135 Path *res = new Path;
1136 theRes->ConvertToForme (res, 1, originaux);
1137
1138 delete theShape;
1139 delete theRes;
1140
1141 // TODO fix:
1142 //XML Tree being used directly here while it shouldn't be.
1143 offset->setAttribute("inkscape:original", res->svg_dump_path().c_str());
1144
1145 delete res;
1146 delete orig;
1147 }
1148}
1149
1151{
1152 if (offset && offset->sourceRef) {
1153 return offset->sourceRef->getObject();
1154 }
1155
1156 return nullptr;
1157}
1158
1159/*
1160 Local Variables:
1161 mode:c++
1162 c-file-style:"stroustrup"
1163 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1164 indent-tabs-mode:nil
1165 fill-column:99
1166 End:
1167*/
1168// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
@ butt_straight
Definition LivarotDefs.h:52
@ fill_oddEven
Definition LivarotDefs.h:68
@ fill_nonZero
Definition LivarotDefs.h:69
@ fill_positive
Definition LivarotDefs.h:70
@ FIRST
Definition LivarotDefs.h:91
@ LAST
Definition LivarotDefs.h:92
@ join_round
Definition LivarotDefs.h:62
TODO: insert short description here.
TODO: insert short description here.
Lookup dictionary for attributes/properties.
SPAttr
Definition attributes.h:27
@ INKSCAPE_ORIGINAL
@ INKSCAPE_RADIUS
@ XLINK_HREF
@ SODIPODI_ORIGINAL
@ INKSCAPE_HREF
@ SODIPODI_RADIUS
TODO: insert short description here.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
bool isIdentity(Coord eps=EPSILON) const
Check whether this matrix is an identity matrix.
Definition affine.cpp:109
void setIdentity()
Sets this matrix to be the Identity Affine.
Definition affine.cpp:96
bool isTranslation(Coord eps=EPSILON) const
Check whether this matrix represents a pure translation.
Definition affine.cpp:123
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of subpaths.
Definition pathvector.h:122
Two-dimensional point that doubles as a vector.
Definition point.h:66
Preference storage class.
Definition preferences.h:66
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
Storing of snapping preferences.
void detach()
Detaches from the currently attached URI target, if any; the current referrent is signaled as NULL.
sigc::signal< void(SPObject *, SPObject *)> changedSignal()
Accessor for the referrent change notification signal; this signal is emitted whenever the URIReferen...
void attach(URI const &uri)
Attaches to a URI, relative to the specified document.
Represents an URI as per RFC 2396.
Definition uri.h:36
Interface for refcounted XML nodes.
Definition node.h:80
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
Path and its polyline approximation.
Definition Path.h:93
void ConvertWithBackData(double threshhold, bool relative=false)
Creates a polyline approximation of the path.
void SetBackData(bool nVal)
Sets the back variable to the value passed in and clears the polyline approximation.
Definition Path.cpp:232
double Surface()
void OutsideOutline(Path *dest, double width, JoinType join, ButtType butt, double miter)
void Fill(Shape *dest, int pathID=-1, bool justAdd=false, bool closeIfNeeded=true, bool invert=false)
Fills the shape with the polyline approximation stored in this object.
std::string svg_dump_path() const
Definition Path.cpp:577
void ConvertEvenLines(double treshhold)
Creates a polyline approximation of the path.
void LoadPathVector(Geom::PathVector const &pv, Geom::Affine const &tr, bool doTransformation)
Load a lib2geom Geom::PathVector in this path object.
Path ** SubPaths(int &outNb, bool killNoSurf)
void Simplify(double treshhold)
Simplify the path.
void Convert(double treshhold)
Creates a polyline approximation of the path.
void PolylineBoundingBox(double &l, double &t, double &r, double &b)
Definition Path.cpp:301
Wrapper around a Geom::PathVector object.
Definition curve.h:26
Typed SVG document implementation.
Definition document.h:103
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::OptRect documentVisualBounds() const
Get item's visual bbox in document coordinate system.
Definition sp-item.cpp:1018
sigc::connection connectTransformed(sigc::slot< void(Geom::Affine const *, SPItem *)> slot)
Definition sp-item.h:234
Geom::Affine transform
Definition sp-item.h:138
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Definition sp-item.cpp:1658
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
Inkscape::XML::Node * repr
Definition sp-object.h:193
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
void removeAttribute(char const *key)
sigc::connection connectDelete(sigc::slot< void(SPObject *)> slot)
Connects a slot to be called when an object is deleted.
Definition sp-object.h:545
SPDocument * document
Definition sp-object.h:188
void readAttr(char const *key)
Read value of key attribute from XML node into object.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
sigc::connection connectModified(sigc::slot< void(SPObject *, unsigned int)> slot)
Connects to the modification notification signal.
Definition sp-object.h:705
void requestDisplayUpdate(unsigned int flags)
Queues an deferred update of this object's display.
SPOffset class.
Definition sp-offset.h:50
Inkscape::XML::Node * write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned flags) override
char * description() const override
bool knotSet
for interactive setting of the radius
Definition sp-offset.h:61
void build(SPDocument *document, Inkscape::XML::Node *repr) override
float rad
offset radius
Definition sp-offset.h:58
SPUseReference * sourceRef
Definition sp-offset.h:68
const char * typeName() const override
The item's type name, not node tag name.
bool isUpdating
Definition sp-offset.h:65
void set_shape() override
~SPOffset() override
sigc::connection _modified_connection
Definition sp-offset.h:72
void snappoints(std::vector< Inkscape::SnapCandidatePoint > &p, Inkscape::SnapPreferences const *snapprefs) const override
void release() override
bool sourceDirty
Definition sp-offset.h:64
Inkscape::XML::Node * sourceRepr
the repr associated with that id
Definition sp-offset.h:69
char * sourceHref
Definition sp-offset.h:67
sigc::connection _transformed_connection
Definition sp-offset.h:75
void update(SPCtx *ctx, unsigned int flags) override
sigc::connection _changed_connection
Definition sp-offset.h:74
SPItem * sourceObject
Definition sp-offset.h:70
const char * displayName() const override
The item's type name as a translated human string.
char * original
SVG description of the source path.
Definition sp-offset.h:57
void * originalPath
will be a livarot Path, just don't declare it here to please the gcc linker FIXME what?
Definition sp-offset.h:56
sigc::connection _delete_connection
Definition sp-offset.h:73
void set(SPAttr key, char const *value) override
Base class for shapes, including <path> element.
Definition sp-shape.h:38
SPCurve const * curve() const
Return a borrowed pointer to the curve (if any exists) or NULL if there is no curve.
Definition sp-shape.cpp:970
void update(SPCtx *ctx, unsigned int flags) override
Definition sp-shape.cpp:125
void setCurveInsync(SPCurve const *)
Definition sp-shape.cpp:958
void snappoints(std::vector< Inkscape::SnapCandidatePoint > &p, Inkscape::SnapPreferences const *snapprefs) const override
Definition sp-shape.cpp:991
Geom::OptRect bbox(Geom::Affine const &transform, SPItem::BBoxType bboxtype) const override
Definition sp-shape.cpp:499
void release() override
Removes, releases and unrefs all children of object.
Definition sp-shape.cpp:92
void set(SPAttr key, char const *value) override
Definition sp-shape.cpp:115
void setCurveBeforeLPE(SPCurve const *)
Definition sp-shape.cpp:942
void build(SPDocument *document, Inkscape::XML::Node *repr) override
Definition sp-shape.cpp:63
std::shared_ptr< SPCurve const > _curve
Definition sp-shape.h:70
Inkscape::XML::Node * write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags) override
Definition sp-shape.cpp:120
A class to store/manipulate directed graphs.
Definition Shape.h:65
void ConvertToForme(Path *dest)
Extract contours from a directed graph.
Definition ShapeMisc.cpp:42
double leftX
Definition Shape.h:434
bool hasPoints() const
Do we have points?
Definition Shape.h:491
void SortPoints()
Sort the points (all points) only if needed.
Definition Shape.cpp:467
double topY
Definition Shape.h:434
double rightX
Definition Shape.h:434
void CalcBBox(bool strict_degree=false)
Definition Shape.cpp:1963
int numberOfEdges() const
Returns number of edges.
Definition Shape.h:498
int NextAt(int p, int b) const
Definition Shape.h:190
int numberOfPoints() const
Returns number of points.
Definition Shape.h:484
dg_arete const & getEdge(int n) const
Get an edge.
Definition Shape.h:548
void Reset(int n=0, int m=0)
Clear all data.
Definition Shape.cpp:235
int MakeOffset(Shape *of, double dec, JoinType join, double miter, bool do_profile=false, double cx=0, double cy=0, double radius=0, Geom::Affine *i2doc=nullptr)
int ConvertToShape(Shape *a, FillRule directed=fill_nonZero, bool invert=false)
Using a given fill rule, find all intersections in the shape given, create a new intersection free sh...
dg_point const & getPoint(int n) const
Get a point.
Definition Shape.h:537
double bottomY
Definition Shape.h:434
std::shared_ptr< Css const > css
Geom::Point orig
@ SP_CLONE_ORPHANS_UNLINK
Definition enums.h:95
@ SP_CLONE_ORPHANS_DELETE
Definition enums.h:96
@ SP_CLONE_COMPENSATION_UNMOVED
Definition enums.h:90
@ SP_CLONE_COMPENSATION_PARALLEL
Definition enums.h:89
@ SP_CLONE_COMPENSATION_NONE
Definition enums.h:91
SPItem * item
std::string original
double offset
D2< T > rot90(D2< T > const &a)
Definition d2.h:397
static cairo_user_data_key_t key
int mode
int size
Singleton class to access the preferences file in a convenient way.
Ocnode ** ref
Definition quantize.cpp:32
SPCSSAttr * sp_repr_css_attr(Node const *repr, gchar const *attr)
Creates a new SPCSSAttr with one attribute (i.e.
Definition repr-css.cpp:88
char const * sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval)
Returns a character string of the value of a given style property or a default value if the attribute...
Definition repr-css.cpp:147
auto len
Definition safe-printf.h:21
static void refresh_offset_source(SPOffset *offset)
static void sp_offset_source_modified(SPObject *iSource, guint flags, SPItem *item)
double sp_offset_distance_to_original(SPOffset *offset, Geom::Point px)
Distance to the original path; that function is called from shape-editor-knotholders to set the radiu...
SPItem * sp_offset_get_source(SPOffset *offset)
static void sp_offset_delete_self(SPObject *deleted, SPOffset *self)
static void sp_offset_quit_listening(SPOffset *offset)
static void sp_offset_start_listening(SPOffset *offset, SPItem *to)
static bool vectors_are_clockwise(Geom::Point A, Geom::Point B, Geom::Point C)
static bool use_slow_but_correct_offset_method
Definition sp-offset.cpp:82
static void sp_offset_move_compensate(Geom::Affine const *mp, SPItem *original, SPOffset *self)
static void sp_offset_href_changed(SPObject *old_ref, SPObject *ref, SPOffset *offset)
void sp_offset_top_point(SPOffset const *offset, Geom::Point *px)
Computes a point on the offset; used to set a "seed" position for the control knot.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
Unused.
Definition sp-object.h:94
Geom::Point dx
Definition Shape.h:463
int incidentEdge[2]
Definition Shape.h:452
Geom::Point x
Definition Shape.h:449
int totalDegree() const
Definition Shape.h:455
Definition curve.h:24
bool sp_svg_transform_read(gchar const *str, Geom::Affine *transform)
unsigned int sp_svg_length_read_computed_absolute(gchar const *str, float *length)
Geom::PathVector sp_svg_read_pathv(char const *str)
Definition svg-path.cpp:37
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
void dot(Cairo::RefPtr< Cairo::Context > &cr, double x, double y)