sp-offset.cpp revision 0ead8bb426eb55e972503718b2a885a77d95405d
/** \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, read the file 'COPYING' for more information
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <cstring>
#include <string>
#include "attributes.h"
#include "enums.h"
#include "preferences.h"
#include "sp-text.h"
#include "sp-offset.h"
#include "sp-use-reference.h"
#include "uri.h"
class SPDocument;
#include "sp-factory.h"
namespace {
SPObject* createOffset() {
return new SPOffset();
}
}
#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 object-edit).
*/
// 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
static bool use_slow_but_correct_offset_method=false;
this->rad = 1.0;
this->originalPath = NULL;
this->knotSet = false;
this->sourceDirty=false;
this->isUpdating=false;
// init various connections
this->sourceHref = NULL;
this->sourceRepr = NULL;
this->sourceObject = NULL;
// 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));
}
delete this->sourceRef;
this->_modified_connection.disconnect();
this->_delete_connection.disconnect();
this->_changed_connection.disconnect();
this->_transformed_connection.disconnect();
}
//XML Tree being used directly here while it shouldn't be.
this->readAttr( "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.
this->readAttr( "inkscape:radius" );
}
this->readAttr( "inkscape:original" );
} else {
this->readAttr( "inkscape:original" );
}
this->readAttr( "xlink:href" );
} else {
if (oldA) {
nA[0]='#';
}
this->readAttr( "xlink:href" );
}
}
Inkscape::XML::Node* SPOffset::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
}
if (flags & SP_OBJECT_WRITE_EXT) {
/** \todo
* Fixme: we may replace these attributes by
* inkscape:offset="cx cy exp revo rad arg t0"
*/
}
// Make sure the offset has curve
this->set_shape();
}
// write that curve to "d"
g_free (d);
return repr;
}
if (this->original) {
}
if (this->originalPath) {
delete ((Path *) this->originalPath);
}
this->originalPath = NULL;
sp_offset_quit_listening(this);
this->_changed_connection.disconnect();
g_free(this->sourceHref);
this->sourceHref = NULL;
}
if ( this->sourceDirty ) {
refresh_offset_source(this);
}
/* fixme: we should really collect updates */
switch (key)
{
} else {
if (this->original) {
delete ((Path *) this->originalPath);
this->originalPath = NULL;
}
this->originalPath = new Path;
this->knotSet = false;
if ( this->isUpdating == false ) {
}
}
break;
case SP_ATTR_INKSCAPE_RADIUS:
case SP_ATTR_SODIPODI_RADIUS:
}
this->knotSet = false; // knotset=false because it's not set from the context
}
if ( this->isUpdating == false ) {
}
break;
case SP_ATTR_INKSCAPE_HREF:
case SP_ATTR_XLINK_HREF:
sp_offset_quit_listening(this);
if ( this->sourceHref ) {
g_free(this->sourceHref);
}
this->sourceHref = NULL;
} else {
} else {
if ( this->sourceHref ) {
g_free(this->sourceHref);
}
try {
} catch (Inkscape::BadURIException &e) {
}
}
}
break;
default:
break;
}
}
this->isUpdating=true; // prevent sp_offset_set from requesting updates
if ( this->sourceDirty ) {
refresh_offset_source(this);
}
if (flags &
this->set_shape();
}
this->isUpdating=false;
}
const char* SPOffset::displayName() const {
if ( this->sourceHref ) {
return _("Linked Offset");
} else {
return _("Dynamic Offset");
}
}
// TRANSLATORS COMMENT: %s is either "outset" or "inset" depending on sign
}
if ( this->originalPath == NULL ) {
// oops : no path?! (the offset object should do harakiri)
return;
}
#ifdef OFFSET_VERBOSE
#endif
// au boulot
// grosso modo: 0
// just put the source this as the offseted one, 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.
if ( res_d ) {
this->setCurveInsync (c, TRUE);
this->setCurveBeforeLPE(c);
c->unref();
}
return;
}
// extra paraniac careful check. the preceding if () should take care of this case
}
if ( use_slow_but_correct_offset_method == false ) {
// version par outline
res->SetBackData (false);
// and now: offset
float o_width;
if (this->rad >= 0)
{
}
else
{
}
if (o_width >= 1.0)
{
// res->ConvertForOffset (1.0, orig, offset->rad);
}
else
{
// res->ConvertForOffset (o_width, orig, offset->rad);
}
if ( bbox ) {
if (exp != 0) {
}
//g_print ("coa %g exp %g item %p\n", size * 0.001, exp, item);
}
// if (o_width >= 1.0)
// {
// orig->Coalesce (0.1); // small treshhold, 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
// and now: offset
float o_width;
if (this->rad >= 0)
{
}
else
{
}
// one has to have a measure of the details
if (o_width >= 1.0)
{
}
else
{
}
int nbPart=0;
// 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
{
for (int i=0;i<nbPart;i++) {
{
// raffiner si besoin
if ( mesure < 10.0 ) {
}
}
if ( partSurf < 0 ) { // inverse par rapport a la realite
// plein
holes[i]=0;
onePart->ConvertToShape(oneCleanPart,fill_positive); // there aren't intersections in that one, but maybe duplicate points and null edges
if ( typicalSize < 0.05 ) {
typicalSize=0.05;
}
typicalSize*=0.01;
if ( typicalSize > 1.0 ) {
typicalSize=1.0;
}
if ( nPartSurf >= 0 ) {
// inversion de la surface -> disparait
delete parts[i];
} 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;
// for (int j=0;j<onePart->nbAr;j++) onePart->Inverse(j); // pas oublier de reinverser
if ( typicalSize < 0.05 ) {
typicalSize=0.05;
}
typicalSize*=0.01;
if ( typicalSize > 1.0 ) {
typicalSize=1.0;
}
if ( nPartSurf >= 0 ) {
// inversion de la surface -> disparait
delete parts[i];
} 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 ) {
for (int i=0;i<nbPart;i++) {
if ( parts[i] ) {
if ( holes[i] ) {
} else {
}
}
}
for (int i=0;i<nbPart;i++) {
if ( parts[i] ) {
delete parts[i];
}
}
} else if ( nbPart == 1 ) {
for (int i=0;i<nbPart;i++) {
if ( parts[i] ) {
delete parts[i];
}
}
} else {
}
// 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 ) {
}
if ( holes ) {
}
delete res;
delete theShape;
delete theRes;
}
{
{
// Aie.... nothing left.
//printf("%s\n",res_d);
}
else
{
}
delete orig;
this->setCurveInsync (c, TRUE);
this->setCurveBeforeLPE(c);
c->unref();
}
}
void SPOffset::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const {
}
// 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
{
if (ab_c <= -1.0) {
}
if (ab_c >= 1.0) {
ab_a = 0;
}
if (ab_s < 0) {
}
if (bc_c <= -1.0) {
}
if (bc_c >= 1.0) {
bc_a = 0;
}
if (bc_s < 0) {
}
if (ca_c <= -1.0) {
}
if (ca_c >= 1.0) {
ca_a = 0;
}
if (ca_s < 0) {
}
return true;
}
return false;
}
/**
* Distance to the original path; that function is called from object-edit
* 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
{
if (offset == NULL || offset->originalPath == NULL || ((Path *) offset->originalPath)->descr_cmd.size() <= 1) {
return 1.0;
}
double dist = 1.0;
/** \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
{
}
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++)
{
{
{
// we have a new minimum distance
// now we need to wheck if px is inside or outside (for the sign)
do
{
// one angle
{
}
{
}
{
// we're in that angle. set the sign, and exit that loop
{
ptSet = true;
}
else
{
ptSet = true;
}
break;
}
}
}
}
}
// loop over the edges to try to improve the distance
for (int i = 0; i < theRes->numberOfEdges(); i++)
{
if (len > 0.0001)
{
{
// we're in the zone of influence of the segment
{
arSet = true;
}
}
}
}
{
if (arSet == false) {
}
if (ptSet == false) {
}
} else {
}
}
}
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
{
return;
}
{
return;
}
{
// CPPIFY
//offset->set_shape();
return;
}
{
return;
}
{
theShape->SortPoints ();
}
delete theShape;
delete finalPath;
}
// the listening functions
{
return;
}
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));
}
{
return;
}
}
static void
{
if (refobj) {
}
offset->sourceDirty=true;
}
}
{
self->sourceDirty=true;
return;
}
// calculate the compensation matrix and the advertized movement matrix
if (mode == SP_CLONE_COMPENSATION_PARALLEL) {
advertized_move = m;
} else if (mode == SP_CLONE_COMPENSATION_UNMOVED) {
} else {
}
self->sourceDirty=true;
// commit the compensation
}
static void
{
if (mode == SP_CLONE_ORPHANS_UNLINK) {
// leave it be. just forget about the source
if ( offset->sourceHref ) {
}
} else if (mode == SP_CLONE_ORPHANS_DELETE) {
offset->deleteObject();
}
}
static void
{
offset->sourceDirty=true;
}
}
static void
{
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.
return;
}
if (SP_IS_SHAPE (item)) {
}
else if (SP_IS_TEXT (item)) {
}
else {
return;
}
return;
}
if (t_attr) {
if (sp_svg_transform_read(t_attr, &t)) {
}
}
}
// Finish up.
{
{
}
{
}
else
{
}
delete theShape;
delete theRes;
delete res;
delete orig;
// TODO fix:
//XML Tree being used diectly here while it shouldn't be.
}
}
SPItem *
{
if (SP_IS_ITEM (refobj)) {
}
}
return NULL;
}
/*
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 :