diff options
Diffstat (limited to 'src/object/sp-offset.cpp')
-rw-r--r-- | src/object/sp-offset.cpp | 1201 |
1 files changed, 1201 insertions, 0 deletions
diff --git a/src/object/sp-offset.cpp b/src/object/sp-offset.cpp new file mode 100644 index 0000000..a0fd171 --- /dev/null +++ b/src/object/sp-offset.cpp @@ -0,0 +1,1201 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * Implementation of <path sodipodi:type="inkscape:offset">. + */ + +/* + * Authors: (of the sp-spiral.c upon which this file was constructed): + * Mitsuru Oka <oka326@parkcity.ne.jp> + * Lauris Kaplinski <lauris@kaplinski.com> + * Abhishek Sharma + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "sp-offset.h" + +#include <cstring> +#include <string> + +#include <glibmm/i18n.h> + +#include "bad-uri-exception.h" +#include "svg/svg.h" +#include "attributes.h" +#include "display/curve.h" + +#include "livarot/Path.h" +#include "livarot/Shape.h" + +#include "enums.h" +#include "preferences.h" +#include "sp-text.h" +#include "sp-use-reference.h" +#include "uri.h" + +class SPDocument; + +#define noOFFSET_VERBOSE + +/** \note + * SPOffset is a derivative of SPShape, much like the SPSpiral or SPRect. + * The goal is to have a source shape (= originalPath), an offset (= radius) + * and compute the offset of the source by the radius. To get it to work, + * one needs to know what the source is and what the radius is, and how it's + * stored in the xml representation. The object itself is a "path" element, + * to get lots of shape functionality for free. The source is the easy part: + * it's stored in a "inkscape:original" attribute in the path. In case of + * "linked" offset, as they've been dubbed, there is an additional + * "inkscape:href" that contains the id of an element of the svg. + * When built, the object will attach a listener vector to that object and + * rebuild the "inkscape:original" whenever the href'd object changes. This + * is of course grossly inefficient, and also does not react to changes + * to the href'd during context stuff (like changing the shape of a star by + * dragging control points) unless the path of that object is changed during + * the context (seems to be the case for SPEllipse). The computation of the + * offset is done in sp_offset_set_shape(), a function that is called whenever + * a change occurs to the offset (change of source or change of radius). + * just like the sp-star and other, this path derivative can make control + * points, or more precisely one control point, that's enough to define the + * radius (look in shape-editor-knotholders). + */ + +static void refresh_offset_source(SPOffset* offset); + +static void sp_offset_start_listening(SPOffset *offset,SPObject* to); +static void sp_offset_quit_listening(SPOffset *offset); +static void sp_offset_href_changed(SPObject *old_ref, SPObject *ref, SPOffset *offset); +static void sp_offset_move_compensate(Geom::Affine const *mp, SPItem *original, SPOffset *self); +static void sp_offset_delete_self(SPObject *deleted, SPOffset *self); +static void sp_offset_source_modified (SPObject *iSource, guint flags, SPItem *item); + + +// slow= source path->polygon->offset of polygon->polygon->path +// fast= source path->offset of source path->polygon->path +// fast is not mathematically correct, because computing the offset of a single +// cubic bezier patch is not trivial; in particular, there are problems with holes +// reappearing in offset when the radius becomes too large +//TODO: need fix for bug: #384688 with fix released in r.14156 +//but reverted because bug #1507049 seems has more priority. +static bool use_slow_but_correct_offset_method = false; + +SPOffset::SPOffset() : SPShape() { + this->rad = 1.0; + this->original = nullptr; + this->originalPath = nullptr; + this->knotSet = false; + this->sourceDirty=false; + this->isUpdating=false; + // init various connections + this->sourceHref = nullptr; + this->sourceRepr = nullptr; + this->sourceObject = nullptr; + + // set up the uri reference + this->sourceRef = new SPUseReference(this); + this->_changed_connection = this->sourceRef->changedSignal().connect(sigc::bind(sigc::ptr_fun(sp_offset_href_changed), this)); +} + +SPOffset::~SPOffset() { + delete this->sourceRef; + + this->_modified_connection.disconnect(); + this->_delete_connection.disconnect(); + this->_changed_connection.disconnect(); + this->_transformed_connection.disconnect(); +} + +void SPOffset::build(SPDocument *document, Inkscape::XML::Node *repr) { + SPShape::build(document, repr); + + //XML Tree being used directly here while it shouldn't be. + if (this->getRepr()->attribute("inkscape:radius")) { + this->readAttr(SPAttr::INKSCAPE_RADIUS); + } else { + //XML Tree being used directly here (as object->getRepr) + //in all the below lines in the block while it shouldn't be. + gchar const *oldA = this->getRepr()->attribute("sodipodi:radius"); + this->setAttribute("inkscape:radius", oldA); + this->removeAttribute("sodipodi:radius"); + + this->readAttr(SPAttr::INKSCAPE_RADIUS); + } + + if (this->getRepr()->attribute("inkscape:original")) { + this->readAttr(SPAttr::INKSCAPE_ORIGINAL); + } else { + gchar const *oldA = this->getRepr()->attribute("sodipodi:original"); + this->setAttribute("inkscape:original", oldA); + this->removeAttribute("sodipodi:original"); + + this->readAttr(SPAttr::INKSCAPE_ORIGINAL); + } + + if (this->getRepr()->attribute("xlink:href")) { + this->readAttr(SPAttr::XLINK_HREF); + } else { + gchar const *oldA = this->getRepr()->attribute("inkscape:href"); + + if (oldA) { + size_t lA = strlen(oldA); + char *nA=(char*)malloc((1+lA+1)*sizeof(char)); + + memcpy(nA+1,oldA,lA*sizeof(char)); + + nA[0]='#'; + nA[lA+1]=0; + + this->setAttribute("xlink:href", nA); + + free(nA); + + this->removeAttribute("inkscape:href"); + } + + this->readAttr(SPAttr::XLINK_HREF); + } +} + +Inkscape::XML::Node* SPOffset::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = xml_doc->createElement("svg:path"); + } + + if (flags & SP_OBJECT_WRITE_EXT) { + /** \todo + * Fixme: we may replace these attributes by + * inkscape:offset="cx cy exp revo rad arg t0" + */ + repr->setAttribute("sodipodi:type", "inkscape:offset"); + repr->setAttributeSvgDouble("inkscape:radius", this->rad); + repr->setAttribute("inkscape:original", this->original); + repr->setAttribute("inkscape:href", this->sourceHref); + } + + + // Make sure the offset has curve + if (_curve == nullptr) { + this->set_shape(); + } + + // write that curve to "d" + repr->setAttribute("d", sp_svg_write_path(this->_curve->get_pathvector())); + + SPShape::write(xml_doc, repr, flags | SP_SHAPE_WRITE_PATH); + + return repr; +} + +void SPOffset::release() { + if (this->original) { + free (this->original); + } + + if (this->originalPath) { + delete ((Path *) this->originalPath); + } + + this->original = nullptr; + this->originalPath = nullptr; + + sp_offset_quit_listening(this); + + this->_changed_connection.disconnect(); + + g_free(this->sourceHref); + + this->sourceHref = nullptr; + this->sourceRef->detach(); + + SPShape::release(); +} + +void SPOffset::set(SPAttr key, const gchar* value) { + if ( this->sourceDirty ) { + refresh_offset_source(this); + } + + /* fixme: we should really collect updates */ + switch (key) + { + case SPAttr::INKSCAPE_ORIGINAL: + case SPAttr::SODIPODI_ORIGINAL: + if (value == nullptr) { + } else { + if (this->original) { + free (this->original); + delete ((Path *) this->originalPath); + + this->original = nullptr; + this->originalPath = nullptr; + } + + this->original = strdup (value); + + Geom::PathVector pv = sp_svg_read_pathv(this->original); + + this->originalPath = new Path; + reinterpret_cast<Path *>(this->originalPath)->LoadPathVector(pv); + + this->knotSet = false; + + if ( this->isUpdating == false ) { + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + } + break; + + case SPAttr::INKSCAPE_RADIUS: + case SPAttr::SODIPODI_RADIUS: + if (!sp_svg_length_read_computed_absolute (value, &this->rad)) { + if (fabs (this->rad) < 0.01) { + this->rad = (this->rad < 0) ? -0.01 : 0.01; + } + + this->knotSet = false; // knotset=false because it's not set from the context + } + + if ( this->isUpdating == false ) { + this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } + break; + + case SPAttr::INKSCAPE_HREF: + case SPAttr::XLINK_HREF: + if ( value == nullptr ) { + sp_offset_quit_listening(this); + if ( this->sourceHref ) { + g_free(this->sourceHref); + } + + this->sourceHref = nullptr; + this->sourceRef->detach(); + } else { + if ( this->sourceHref && ( strcmp(value, this->sourceHref) == 0 ) ) { + } else { + if ( this->sourceHref ) { + g_free(this->sourceHref); + } + + this->sourceHref = g_strdup(value); + + try { + this->sourceRef->attach(Inkscape::URI(value)); + } catch (Inkscape::BadURIException &e) { + g_warning("%s", e.what()); + this->sourceRef->detach(); + } + } + } + break; + + default: + SPShape::set(key, value); + break; + } +} + +void SPOffset::update(SPCtx *ctx, guint flags) { + this->isUpdating=true; // prevent sp_offset_set from requesting updates + + if ( this->sourceDirty ) { + refresh_offset_source(this); + } + + if (flags & + (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | + SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) { + + this->set_shape(); + } + + this->isUpdating=false; + + SPShape::update(ctx, flags); +} + +const char* SPOffset::displayName() const { + if ( this->sourceHref ) { + return _("Linked Offset"); + } else { + return _("Dynamic Offset"); + } +} + +gchar* SPOffset::description() const { + // TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign + return g_strdup_printf(_("%s by %f pt"), (this->rad >= 0) ? + _("outset") : _("inset"), fabs (this->rad)); +} + +void SPOffset::set_shape() { + if ( this->originalPath == nullptr ) { + // oops : no path?! (the offset object should do harakiri) + return; + } +#ifdef OFFSET_VERBOSE + g_print ("rad=%g\n", offset->rad); +#endif + // au boulot + + if ( fabs(this->rad) < 0.01 ) { + // grosso modo: 0 + // just put the source of this (almost-non-offsetted) object as being the actual offset, + // no one will notice. it's also useless to compute the offset with a 0 radius + + //XML Tree being used directly here while it shouldn't be. + const char *res_d = this->getRepr()->attribute("inkscape:original"); + + if ( res_d ) { + Geom::PathVector pv = sp_svg_read_pathv(res_d); + setCurveInsync(std::make_unique<SPCurve>(pv)); + setCurveBeforeLPE(curve()); + } + + return; + } + + // extra paranoiac careful check. the preceding if () should take care of this case + if (fabs (this->rad) < 0.01) { + this->rad = (this->rad < 0) ? -0.01 : 0.01; + } + + Path *orig = new Path; + orig->Copy ((Path *)this->originalPath); + + if ( use_slow_but_correct_offset_method == false ) { + // version par outline + Shape *theShape = new Shape; + Shape *theRes = new Shape; + Path *originaux[1]; + Path *res = new Path; + res->SetBackData (false); + + // and now: offset + float o_width; + if (this->rad >= 0) + { + o_width = this->rad; + orig->OutsideOutline (res, o_width, join_round, butt_straight, 20.0); + } + else + { + o_width = -this->rad; + orig->OutsideOutline (res, -o_width, join_round, butt_straight, 20.0); + } + + if (o_width >= 1.0) + { + // res->ConvertForOffset (1.0, orig, offset->rad); + res->ConvertWithBackData (1.0); + } + else + { + // res->ConvertForOffset (o_width, orig, offset->rad); + res->ConvertWithBackData (o_width); + } + res->Fill (theShape, 0); + theRes->ConvertToShape (theShape, fill_positive); + originaux[0] = res; + + theRes->ConvertToForme (orig, 1, originaux); + + Geom::OptRect bbox = this->documentVisualBounds(); + + if ( bbox ) { + gdouble size = L2(bbox->dimensions()); + gdouble const exp = this->transform.descrim(); + + if (exp != 0) { + size /= exp; + } + + orig->Coalesce (size * 0.001); + //g_print ("coa %g exp %g item %p\n", size * 0.001, exp, item); + } + + + // if (o_width >= 1.0) + // { + // orig->Coalesce (0.1); // small threshold, since we only want to get rid of small segments + // the curve should already be computed by the Outline() function + // orig->ConvertEvenLines (1.0); + // orig->Simplify (0.5); + // } + // else + // { + // orig->Coalesce (0.1*o_width); + // orig->ConvertEvenLines (o_width); + // orig->Simplify (0.5 * o_width); + // } + + delete theShape; + delete theRes; + delete res; + } else { + // version par makeoffset + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + + // and now: offset + float o_width; + if (this->rad >= 0) + { + o_width = this->rad; + } + else + { + o_width = -this->rad; + } + + // one has to have a measure of the details + if (o_width >= 1.0) + { + orig->ConvertWithBackData (0.5); + } + else + { + orig->ConvertWithBackData (0.5*o_width); + } + + orig->Fill (theShape, 0); + theRes->ConvertToShape (theShape, fill_positive); + + Path *originaux[1]; + originaux[0]=orig; + + Path *res = new Path; + theRes->ConvertToForme (res, 1, originaux); + + int nbPart=0; + Path** parts=res->SubPaths(nbPart,true); + char *holes=(char*)malloc(nbPart*sizeof(char)); + + // we offset contours separately, because we can. + // this way, we avoid doing a unique big ConvertToShape when dealing with big shapes with lots of holes + { + Shape* onePart=new Shape; + Shape* oneCleanPart=new Shape; + + theShape->Reset(); + + for (int i=0;i<nbPart;i++) { + double partSurf=parts[i]->Surface(); + parts[i]->Convert(1.0); + + { + // raffiner si besoin + double bL,bT,bR,bB; + parts[i]->PolylineBoundingBox(bL,bT,bR,bB); + double measure=((bR-bL)+(bB-bT))*0.5; + if ( measure < 10.0 ) { + parts[i]->Convert(0.02*measure); + } + } + + if ( partSurf < 0 ) { // inverse par rapport a la realite + // plein + holes[i]=0; + parts[i]->Fill(oneCleanPart,0); + onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges + oneCleanPart->MakeOffset(onePart,this->rad,join_round,20.0); + onePart->ConvertToShape(oneCleanPart,fill_positive); + + onePart->CalcBBox(); + double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY)); + + if ( typicalSize < 0.05 ) { + typicalSize=0.05; + } + + typicalSize*=0.01; + + if ( typicalSize > 1.0 ) { + typicalSize=1.0; + } + + onePart->ConvertToForme (parts[i]); + parts[i]->ConvertEvenLines (typicalSize); + parts[i]->Simplify (typicalSize); + + double nPartSurf=parts[i]->Surface(); + + if ( nPartSurf >= 0 ) { + // inversion de la surface -> disparait + delete parts[i]; + parts[i]=nullptr; + } else { + } + +/* int firstP=theShape->nbPt; + for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x); + for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].st,firstP+onePart->aretes[j].en);*/ + } else { + // trou + holes[i]=1; + parts[i]->Fill(oneCleanPart,0,false,true,true); + onePart->ConvertToShape(oneCleanPart,fill_positive); + oneCleanPart->MakeOffset(onePart,-this->rad,join_round,20.0); + onePart->ConvertToShape(oneCleanPart,fill_positive); +// for (int j=0;j<onePart->nbAr;j++) onePart->Inverse(j); // pas oublier de reinverser + + onePart->CalcBBox(); + double typicalSize=0.5*((onePart->rightX-onePart->leftX)+(onePart->bottomY-onePart->topY)); + + if ( typicalSize < 0.05 ) { + typicalSize=0.05; + } + + typicalSize*=0.01; + + if ( typicalSize > 1.0 ) { + typicalSize=1.0; + } + + onePart->ConvertToForme (parts[i]); + parts[i]->ConvertEvenLines (typicalSize); + parts[i]->Simplify (typicalSize); + double nPartSurf=parts[i]->Surface(); + + if ( nPartSurf >= 0 ) { + // inversion de la surface -> disparait + delete parts[i]; + parts[i]=nullptr; + } else { + } + + /* int firstP=theShape->nbPt; + for (int j=0;j<onePart->nbPt;j++) theShape->AddPoint(onePart->pts[j].x); + for (int j=0;j<onePart->nbAr;j++) theShape->AddEdge(firstP+onePart->aretes[j].en,firstP+onePart->aretes[j].st);*/ + } +// delete parts[i]; + } +// theShape->MakeOffset(theRes,offset->rad,join_round,20.0); + delete onePart; + delete oneCleanPart; + } + + if ( nbPart > 1 ) { + theShape->Reset(); + + for (int i=0;i<nbPart;i++) { + if ( parts[i] ) { + parts[i]->ConvertWithBackData(1.0); + + if ( holes[i] ) { + parts[i]->Fill(theShape,i,true,true,true); + } else { + parts[i]->Fill(theShape,i,true,true,false); + } + } + } + + theRes->ConvertToShape (theShape, fill_positive); + theRes->ConvertToForme (orig,nbPart,parts); + + for (int i=0;i<nbPart;i++) { + if ( parts[i] ) { + delete parts[i]; + } + } + } else if ( nbPart == 1 ) { + orig->Copy(parts[0]); + + for (int i=0;i<nbPart;i++) { + if ( parts[i] ) { + delete parts[i]; + } + } + } else { + orig->Reset(); + } +// theRes->ConvertToShape (theShape, fill_positive); +// theRes->ConvertToForme (orig); + +/* if (o_width >= 1.0) { + orig->ConvertEvenLines (1.0); + orig->Simplify (1.0); + } else { + orig->ConvertEvenLines (1.0*o_width); + orig->Simplify (1.0 * o_width); + }*/ + + if ( parts ) { + free(parts); + } + + if ( holes ) { + free(holes); + } + + delete res; + delete theShape; + delete theRes; + } + { + char *res_d = nullptr; + + if (orig->descr_cmd.size() <= 1) + { + // Aie.... nothing left. + res_d = strdup ("M 0 0 L 0 0 z"); + //printf("%s\n",res_d); + } + else + { + + res_d = orig->svg_dump_path (); + } + + delete orig; + + Geom::PathVector pv = sp_svg_read_pathv(res_d); + setCurveInsync(std::make_unique<SPCurve>(pv)); + setCurveBeforeLPE(curve()); + + free (res_d); + } +} + +void SPOffset::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const { + SPShape::snappoints(p, snapprefs); +} + + +// utilitaires pour les poignees +// used to get the distance to the shape: distance to polygon give the fabs(radius), we still need +// the sign. for edges, it's easy to determine which side the point is on, for points of the polygon +// it's trickier: we need to identify which angle the point is in; to that effect, we take each +// successive clockwise angle (A,C) and check if the vector B given by the point is in the angle or +// outside. +// another method would be to use the Winding() function to test whether the point is inside or outside +// the polygon (it would be wiser to do so, in fact, but i like being stupid) + +/** + * + * \todo + * FIXME: This can be done using linear operations, more stably and + * faster. method: transform A and C into B's space, A should be + * negative and B should be positive in the orthogonal component. I + * think this is equivalent to + * dot(A, rot90(B))*dot(C, rot90(B)) == -1. + * -- njh + */ +static bool +vectors_are_clockwise (Geom::Point A, Geom::Point B, Geom::Point C) +{ + using Geom::rot90; + double ab_s = dot(A, rot90(B)); + double ab_c = dot(A, B); + double bc_s = dot(B, rot90(C)); + double bc_c = dot(B, C); + double ca_s = dot(C, rot90(A)); + double ca_c = dot(C, A); + + double ab_a = acos (ab_c); + + if (ab_c <= -1.0) { + ab_a = M_PI; + } + + if (ab_c >= 1.0) { + ab_a = 0; + } + + if (ab_s < 0) { + ab_a = 2 * M_PI - ab_a; + } + + double bc_a = acos (bc_c); + + if (bc_c <= -1.0) { + bc_a = M_PI; + } + + if (bc_c >= 1.0) { + bc_a = 0; + } + + if (bc_s < 0) { + bc_a = 2 * M_PI - bc_a; + } + + double ca_a = acos (ca_c); + + if (ca_c <= -1.0) { + ca_a = M_PI; + } + + if (ca_c >= 1.0) { + ca_a = 0; + } + + if (ca_s < 0) { + ca_a = 2 * M_PI - ca_a; + } + + double lim = 2 * M_PI - ca_a; + + if (ab_a < lim) { + return true; + } + + return false; +} + +/** + * Distance to the original path; that function is called from shape-editor-knotholders + * to set the radius when the control knot moves. + * + * The sign of the result is the radius we're going to offset the shape with, + * so result > 0 ==outset and result < 0 ==inset. thus result<0 means + * 'px inside source'. + */ +double +sp_offset_distance_to_original (SPOffset * offset, Geom::Point px) +{ + if (offset == nullptr || offset->originalPath == nullptr || ((Path *) offset->originalPath)->descr_cmd.size() <= 1) { + return 1.0; + } + + double dist = 1.0; + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + /** \todo + * Awfully damn stupid method: uncross the source path EACH TIME you + * need to compute the distance. The good way to do this would be to + * store the uncrossed source path somewhere, and delete it when the + * context is finished. Hopefully this part is much faster than actually + * computing the offset (which happen just after), so the time spent in + * this function should end up being negligible with respect to the + * delay of one context. + */ + // move + ((Path *) offset->originalPath)->Convert (1.0); + ((Path *) offset->originalPath)->Fill (theShape, 0); + theRes->ConvertToShape (theShape, fill_oddEven); + + if (theRes->numberOfEdges() <= 1) + { + + } + else + { + double ptDist = -1.0; + bool ptSet = false; + double arDist = -1.0; + bool arSet = false; + + // first get the minimum distance to the points + for (int i = 0; i < theRes->numberOfPoints(); i++) + { + if (theRes->getPoint(i).totalDegree() > 0) + { + Geom::Point nx = theRes->getPoint(i).x; + Geom::Point nxpx = px-nx; + double ndist = sqrt (dot(nxpx,nxpx)); + + if (ptSet == false || fabs (ndist) < fabs (ptDist)) + { + // we have a new minimum distance + // now we need to wheck if px is inside or outside (for the sign) + nx = px - theRes->getPoint(i).x; + double nlen = sqrt (dot(nx , nx)); + nx /= nlen; + int pb, cb, fb; + fb = theRes->getPoint(i).incidentEdge[LAST]; + pb = theRes->getPoint(i).incidentEdge[LAST]; + cb = theRes->getPoint(i).incidentEdge[FIRST]; + + do + { + // one angle + Geom::Point prx, nex; + prx = theRes->getEdge(pb).dx; + nlen = sqrt (dot(prx, prx)); + prx /= nlen; + nex = theRes->getEdge(cb).dx; + nlen = sqrt (dot(nex , nex)); + nex /= nlen; + + if (theRes->getEdge(pb).en == i) + { + prx = -prx; + } + + if (theRes->getEdge(cb).en == i) + { + nex = -nex; + } + + if (vectors_are_clockwise (nex, nx, prx)) + { + // we're in that angle. set the sign, and exit that loop + if (theRes->getEdge(cb).st == i) + { + ptDist = -ndist; + ptSet = true; + } + else + { + ptDist = ndist; + ptSet = true; + } + break; + } + + pb = cb; + cb = theRes->NextAt (i, cb); + } + + while (cb >= 0 && pb >= 0 && pb != fb); + } + } + } + + // loop over the edges to try to improve the distance + for (int i = 0; i < theRes->numberOfEdges(); i++) + { + Geom::Point sx = theRes->getPoint(theRes->getEdge(i).st).x; + Geom::Point ex = theRes->getPoint(theRes->getEdge(i).en).x; + Geom::Point nx = ex - sx; + double len = sqrt (dot(nx,nx)); + + if (len > 0.0001) + { + Geom::Point pxsx=px-sx; + double ab = dot(nx,pxsx); + + if (ab > 0 && ab < len * len) + { + // we're in the zone of influence of the segment + double ndist = (cross(nx, pxsx)) / len; + + if (arSet == false || fabs (ndist) < fabs (arDist)) + { + arDist = ndist; + arSet = true; + } + } + } + } + + if (arSet || ptSet) + { + if (arSet == false) { + arDist = ptDist; + } + + if (ptSet == false) { + ptDist = arDist; + } + + if (fabs (ptDist) < fabs (arDist)) { + dist = ptDist; + } else { + dist = arDist; + } + } + } + + delete theShape; + delete theRes; + + return dist; +} + +/** + * Computes a point on the offset; used to set a "seed" position for + * the control knot. + * + * \return the topmost point on the offset. + */ +void +sp_offset_top_point (SPOffset const * offset, Geom::Point *px) +{ + (*px) = Geom::Point(0, 0); + + if (offset == nullptr) { + return; + } + + if (offset->knotSet) + { + (*px) = offset->knot; + return; + } + + SPCurve const *curve = offset->curve(); + + if (curve == nullptr) + { + const_cast<SPOffset*>(offset)->set_shape(); + + curve = offset->curve(); + + if (curve == nullptr) + return; + } + + if (curve->is_empty()) + { + return; + } + + Path *finalPath = new Path; + finalPath->LoadPathVector(curve->get_pathvector()); + + Shape *theShape = new Shape; + + finalPath->Convert (1.0); + finalPath->Fill (theShape, 0); + + if (theShape->hasPoints()) + { + theShape->SortPoints (); + *px = theShape->getPoint(0).x; + } + + delete theShape; + delete finalPath; +} + +// the listening functions +static void sp_offset_start_listening(SPOffset *offset,SPObject* to) +{ + if ( to == nullptr ) { + return; + } + + offset->sourceObject = to; + offset->sourceRepr = to->getRepr(); + + offset->_delete_connection = to->connectDelete(sigc::bind(sigc::ptr_fun(&sp_offset_delete_self), offset)); + offset->_transformed_connection = SP_ITEM(to)->connectTransformed(sigc::bind(sigc::ptr_fun(&sp_offset_move_compensate), offset)); + offset->_modified_connection = to->connectModified(sigc::bind<2>(sigc::ptr_fun(&sp_offset_source_modified), offset)); +} + +static void sp_offset_quit_listening(SPOffset *offset) +{ + if ( offset->sourceObject == nullptr ) { + return; + } + + offset->_modified_connection.disconnect(); + offset->_delete_connection.disconnect(); + offset->_transformed_connection.disconnect(); + + offset->sourceRepr = nullptr; + offset->sourceObject = nullptr; +} + +static void +sp_offset_href_changed(SPObject */*old_ref*/, SPObject */*ref*/, SPOffset *offset) +{ + sp_offset_quit_listening(offset); + + if (offset->sourceRef) { + SPItem *refobj = offset->sourceRef->getObject(); + + if (refobj) { + sp_offset_start_listening(offset,refobj); + } + + offset->sourceDirty=true; + offset->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } +} + +static void sp_offset_move_compensate(Geom::Affine const *mp, SPItem */*original*/, SPOffset *self) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint mode = prefs->getInt("/options/clonecompensation/value", SP_CLONE_COMPENSATION_PARALLEL); + + Geom::Affine m(*mp); + + if (!(m.isTranslation()) || mode == SP_CLONE_COMPENSATION_NONE) { + self->sourceDirty=true; + self->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + return; + } + + // calculate the compensation matrix and the advertized movement matrix + self->readAttr(SPAttr::TRANSFORM); + + Geom::Affine t = self->transform; + Geom::Affine offset_move = t.inverse() * m * t; + + Geom::Affine advertized_move; + if (mode == SP_CLONE_COMPENSATION_PARALLEL) { + offset_move = offset_move.inverse() * m; + advertized_move = m; + } else if (mode == SP_CLONE_COMPENSATION_UNMOVED) { + offset_move = offset_move.inverse(); + advertized_move.setIdentity(); + } else { + g_assert_not_reached(); + } + + self->sourceDirty=true; + + // commit the compensation + self->transform *= offset_move; + self->doWriteTransform(self->transform, &advertized_move); + self->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static void +sp_offset_delete_self(SPObject */*deleted*/, SPOffset *offset) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint const mode = prefs->getInt("/options/cloneorphans/value", SP_CLONE_ORPHANS_UNLINK); + + if (mode == SP_CLONE_ORPHANS_UNLINK) { + // leave it be. just forget about the source + sp_offset_quit_listening(offset); + + if ( offset->sourceHref ) { + g_free(offset->sourceHref); + } + + offset->sourceHref = nullptr; + offset->sourceRef->detach(); + } else if (mode == SP_CLONE_ORPHANS_DELETE) { + offset->deleteObject(); + } +} + +static void +sp_offset_source_modified (SPObject */*iSource*/, guint flags, SPItem *item) +{ + SPOffset *offset = SP_OFFSET(item); + offset->sourceDirty=true; + + if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_CHILD_MODIFIED_FLAG)) { + offset->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + } +} + +static void +refresh_offset_source(SPOffset* offset) +{ + if ( offset == nullptr ) { + return; + } + + offset->sourceDirty=false; + + // le mauvais cas: pas d'attribut d => il faut verifier que c'est une SPShape puis prendre le contour + // The bad case: no d attribute. Must check that it's an SPShape and then take the outline. + SPObject *refobj=offset->sourceObject; + + if ( refobj == nullptr ) { + return; + } + + SPItem *item = SP_ITEM (refobj); + std::unique_ptr<SPCurve> curve; + + if (auto shape = dynamic_cast<SPShape const *>(item)) { + curve = SPCurve::copy(shape->curve()); + } else if (auto text = dynamic_cast<SPText const *>(item)) { + curve = text->getNormalizedBpath(); + } else { + return; + } + + if (curve == nullptr) { + return; + } + + Path *orig = new Path; + orig->LoadPathVector(curve->get_pathvector()); + + if (!item->transform.isIdentity()) { + gchar const *t_attr = item->getRepr()->attribute("transform"); + + if (t_attr) { + Geom::Affine t; + + if (sp_svg_transform_read(t_attr, &t)) { + orig->Transform(t); + } + } + } + + // Finish up. + { + SPCSSAttr *css; + const gchar *val; + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + orig->ConvertWithBackData (1.0); + orig->Fill (theShape, 0); + + css = sp_repr_css_attr (offset->sourceRepr , "style"); + val = sp_repr_css_property (css, "fill-rule", nullptr); + + if (val && strcmp (val, "nonzero") == 0) + { + theRes->ConvertToShape (theShape, fill_nonZero); + } + else if (val && strcmp (val, "evenodd") == 0) + { + theRes->ConvertToShape (theShape, fill_oddEven); + } + else + { + theRes->ConvertToShape (theShape, fill_nonZero); + } + + Path *originaux[1]; + originaux[0] = orig; + Path *res = new Path; + theRes->ConvertToForme (res, 1, originaux); + + delete theShape; + delete theRes; + + char *res_d = res->svg_dump_path (); + delete res; + delete orig; + + // TODO fix: + //XML Tree being used directly here while it shouldn't be. + offset->setAttribute("inkscape:original", res_d); + + free (res_d); + } +} + +SPItem * +sp_offset_get_source (SPOffset *offset) +{ + if (offset && offset->sourceRef) { + SPItem *refobj = offset->sourceRef->getObject(); + + if (SP_IS_ITEM (refobj)) { + return (SPItem *) refobj; + } + } + + return nullptr; +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |