summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/live_effects/Makefile_insert6
-rw-r--r--src/live_effects/effect-enum.h3
-rw-r--r--src/live_effects/effect.cpp13
-rwxr-xr-xsrc/live_effects/lpe-jointype.h43
-rw-r--r--src/live_effects/lpe-taperstroke.cpp536
-rw-r--r--src/live_effects/lpe-taperstroke.h73
-rwxr-xr-xsrc/live_effects/pathoutlineprovider.h766
-rw-r--r--src/ui/clipboard.cpp9
8 files changed, 1443 insertions, 6 deletions
diff --git a/src/live_effects/Makefile_insert b/src/live_effects/Makefile_insert
index 35a85b4aa..b19f417ae 100644
--- a/src/live_effects/Makefile_insert
+++ b/src/live_effects/Makefile_insert
@@ -92,4 +92,8 @@ ink_common_sources += \
live_effects/lpe-fill-between-many.cpp \
live_effects/lpe-fill-between-many.h \
live_effects/lpe-ellipse_5pts.cpp \
- live_effects/lpe-ellipse_5pts.h
+ live_effects/lpe-ellipse_5pts.h \
+ live_effects/lpe-jointype.cpp \
+ live_effects/lpe-jointype.h \
+ live_effects/lpe-taperstroke.cpp \
+ live_effects/lpe-taperstroke.h
diff --git a/src/live_effects/effect-enum.h b/src/live_effects/effect-enum.h
index 9c76875e9..c53d64699 100644
--- a/src/live_effects/effect-enum.h
+++ b/src/live_effects/effect-enum.h
@@ -55,7 +55,8 @@ enum EffectType {
FILL_BETWEEN_MANY,
ELLIPSE_5PTS,
BOUNDING_BOX,
-// JOIN_TYPE,
+ JOIN_TYPE,
+ TAPER_STROKE,
INVALID_LPE // This must be last (I made it such that it is not needed anymore I think..., Don't trust on it being last. - johan)
};
diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp
index eac0f79f4..d6840e5b8 100644
--- a/src/live_effects/effect.cpp
+++ b/src/live_effects/effect.cpp
@@ -52,7 +52,8 @@
#include "live_effects/lpe-fill-between-many.h"
#include "live_effects/lpe-ellipse_5pts.h"
#include "live_effects/lpe-bounding-box.h"
-//#include "live_effects/lpe-jointype.h"
+#include "live_effects/lpe-jointype.h"
+#include "live_effects/lpe-taperstroke.h"
#include "xml/node-event-vector.h"
#include "sp-object.h"
@@ -109,7 +110,8 @@ const Util::EnumData<EffectType> LPETypeData[] = {
{RECURSIVE_SKELETON, N_("Recursive skeleton"), "recursive_skeleton"},
{TANGENT_TO_CURVE, N_("Tangent to curve"), "tangent_to_curve"},
{TEXT_LABEL, N_("Text label"), "text_label"},
-// {JOIN_TYPE, N_("Join type"), "join_type"},
+ {JOIN_TYPE, N_("Join type"), "join_type"},
+ {TAPER_STROKE, N_("Taper stroke"), "taper_stroke"},
#endif
/* 0.46 */
{BEND_PATH, N_("Bend"), "bend_path"},
@@ -273,9 +275,12 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj)
case BOUNDING_BOX:
neweffect = static_cast<Effect*> ( new LPEBoundingBox(lpeobj) );
break;
- /*case JOIN_TYPE:
+ case JOIN_TYPE:
neweffect = static_cast<Effect*> ( new LPEJoinType(lpeobj) );
- break;*/
+ break;
+ case TAPER_STROKE:
+ neweffect = static_cast<Effect*> ( new LPETaperStroke(lpeobj) );
+ break;
default:
g_warning("LivePathEffect::Effect::New called with invalid patheffect type (%d)", lpenr);
neweffect = NULL;
diff --git a/src/live_effects/lpe-jointype.h b/src/live_effects/lpe-jointype.h
new file mode 100755
index 000000000..db113c66a
--- /dev/null
+++ b/src/live_effects/lpe-jointype.h
@@ -0,0 +1,43 @@
+/* Authors:
+ * Liam P White
+ *
+ * Copyright (C) 2014 Authors
+ *
+ * Released under GNU GPL v2, read the file COPYING for more information
+ */
+#ifndef INKSCAPE_LPE_JOINTYPE_H
+#define INKSCAPE_LPE_JOINTYPE_H
+
+#include "live_effects/effect.h"
+#include "live_effects/parameter/parameter.h"
+#include "live_effects/parameter/point.h"
+#include "live_effects/parameter/enum.h"
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+class LPEJoinType : public Effect {
+public:
+ LPEJoinType(LivePathEffectObject *lpeobject);
+ virtual ~LPEJoinType();
+
+ virtual void doOnApply(SPLPEItem const* lpeitem);
+ virtual void doOnRemove(SPLPEItem const* lpeitem);
+ virtual std::vector <Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in);
+
+private:
+ LPEJoinType(const LPEJoinType&);
+ LPEJoinType& operator=(const LPEJoinType&);
+
+ ScalarParam line_width;
+ EnumParam<unsigned> linecap_type;
+ EnumParam<unsigned> linejoin_type;
+ ScalarParam miter_limit;
+ BoolParam attempt_force_join;
+ bool was_initialized;
+};
+
+} //namespace LivePathEffect
+} //namespace Inkscape
+
+#endif
diff --git a/src/live_effects/lpe-taperstroke.cpp b/src/live_effects/lpe-taperstroke.cpp
new file mode 100644
index 000000000..e6eed3abe
--- /dev/null
+++ b/src/live_effects/lpe-taperstroke.cpp
@@ -0,0 +1,536 @@
+/**
+ * @file
+ * Taper Stroke path effect, provided as an alternative to Power Strokes
+ * for otherwise constant-width paths.
+ *
+ * Authors:
+ * Liam P White <inkscapebrony@gmail.com>
+ *
+ * Copyright (C) 2014 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "live_effects/lpe-taperstroke.h"
+
+// You might need to include other 2geom files. You can add them here:
+#include <2geom/path.h>
+#include <2geom/shape.h>
+#include "pathoutlineprovider.h"
+#include "display/curve.h"
+#include "svg/svg.h"
+
+//#include <glibmm/i18n.h>
+
+#include "knot-holder-entity.h"
+#include "knotholder.h"
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+namespace TpS {
+ class KnotHolderEntityAttachBegin : public LPEKnotHolderEntity {
+ public:
+ KnotHolderEntityAttachBegin(LPETaperStroke * effect) : LPEKnotHolderEntity(effect) {}
+ virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
+ virtual Geom::Point knot_get() const;
+ };
+ class KnotHolderEntityAttachEnd : public LPEKnotHolderEntity {
+ public:
+ KnotHolderEntityAttachEnd(LPETaperStroke * effect) : LPEKnotHolderEntity(effect) {}
+ virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
+ virtual Geom::Point knot_get() const;
+ };
+} // TpS
+
+static const Util::EnumData<unsigned> JoinType[] = {
+ {LINEJOIN_STRAIGHT, N_("Beveled"), "bevel"},
+ {LINEJOIN_ROUND, N_("Rounded"), "round"},
+ {LINEJOIN_REFLECTED, N_("Reflected"), "reflected"},
+ {LINEJOIN_POINTY, N_("Miter"), "miter"},
+ {LINEJOIN_EXTRAPOLATED, N_("Extrapolated"), "extrapolated"}
+};
+
+static const Util::EnumDataConverter<unsigned> JoinTypeConverter(JoinType, sizeof (JoinType)/sizeof(*JoinType));
+
+LPETaperStroke::LPETaperStroke(LivePathEffectObject *lpeobject) :
+ Effect(lpeobject),
+ attach_start(_("Start offset"), _("Taper distance from path start"), "attach_start", &wr, this, 0.2),
+ attach_end(_("End offset"), _("The ending position of the taper"), "end_offset", &wr, this, 0.2),
+ smoothing(_("Taper smoothing"), _("Amount of smoothing to apply to the tapers"), "smoothing", &wr, this, 0.2),
+ join_type(_("Join type"), _("Join type for non-smooth nodes"), "jointype", JoinTypeConverter, &wr, this, LINEJOIN_EXTRAPOLATED),
+ miter_limit(_("Miter limit"), _("Limit for miter joins"), "miter_limit", &wr, this, 30.)
+{
+ /* uncomment the following line to have the original path displayed while the item is selected */
+ show_orig_path = true;
+ _provides_knotholder_entities = true;
+
+ attach_start.param_set_digits(3);
+ attach_end.param_set_digits(3);
+
+ registerParameter( dynamic_cast<Parameter *>(&attach_start) );
+ registerParameter( dynamic_cast<Parameter *>(&attach_end) );
+ registerParameter( dynamic_cast<Parameter *>(&smoothing) );
+ registerParameter( dynamic_cast<Parameter *>(&join_type) );
+ registerParameter( dynamic_cast<Parameter *>(&miter_limit) );
+}
+
+LPETaperStroke::~LPETaperStroke()
+{
+
+}
+
+unsigned curveOrder (const Geom::Curve* curve_in)
+{
+ using namespace Geom;
+ //cast it
+ const CubicBezier *cbc = dynamic_cast<const CubicBezier*>(curve_in);
+ if (cbc) return 3;
+ const QuadraticBezier * qbc = dynamic_cast<const QuadraticBezier*>(curve_in);
+ if (qbc) return 2;
+ const BezierCurveN<1U> * lbc = dynamic_cast<const BezierCurveN<1U> *>(curve_in);
+ if (lbc) return 1;
+ //BezierCurveN<0> * dbc = dynamic_cast<BezierCurveN<0> *> (curve_in);
+ return 0;
+}
+
+Geom::Path return_at_first_cusp (Geom::Path const & path_in, double smooth_tolerance = 0.01)
+{
+ Geom::Path path_out = Geom::Path();
+
+ for (unsigned i = 0; i < path_in.size(); i++)
+ {
+ path_out.append(path_in[i]);
+ if (path_in.size() == 1)
+ break;
+
+ //determine order of curve
+ int order = curveOrder(&path_in[i]);
+
+ Geom::Point start_point;
+ Geom::Point cross_point = path_in[i].finalPoint();
+ Geom::Point end_point;
+
+ g_assert(path_in[i].finalPoint() == path_in[i+1].initialPoint());
+
+ //can you tell that the following expressions have been shaped by
+ //repeated compiler errors? ;)
+ switch (order)
+ {
+ case 3:
+ start_point = (dynamic_cast<const Geom::CubicBezier*>(&path_in[i]))->operator[] (2);
+ break;
+ case 2:
+ start_point = (dynamic_cast<const Geom::QuadraticBezier*>(&path_in[i]))->operator[] (1);
+ break;
+ case 1:
+ default:
+ start_point = path_in[i].initialPoint();
+ }
+
+ order = curveOrder(&path_in[i+1]);
+
+ switch (order)
+ {
+ case 3:
+ end_point = (dynamic_cast<const Geom::CubicBezier*>(&path_in[i+1]))->operator[] (1);
+ break;
+ case 2:
+ end_point = (dynamic_cast<const Geom::QuadraticBezier*>(&path_in[i+1]))->operator[] (1);
+ break;
+ case 1:
+ default:
+ end_point = path_in[i+1].finalPoint();
+ }
+ if (!are_collinear(start_point, cross_point, end_point, smooth_tolerance))
+ break;
+ }
+ return path_out;
+}
+
+Geom::Curve * subdivide_at(const Geom::Curve* curve_in, Geom::Coord time, bool first)
+{
+ //the only reason for this function is the lack of a subdivide function in the Curve class.
+ //you have to cast to Beziers to be able to use subdivide(t)
+ unsigned order = curveOrder(curve_in);
+ Geom::Curve* curve_out = curve_in->duplicate();
+ switch (order)
+ {
+ //these need to be scoped because of the variable 'c'
+ case 3:
+ {
+ Geom::CubicBezier c = first ? (dynamic_cast<Geom::CubicBezier*> (curve_out))->subdivide(time).first :
+ (dynamic_cast<Geom::CubicBezier*> (curve_out))->subdivide(time).second;
+ if (curve_out) delete curve_out;
+ curve_out = c.duplicate();
+ break;
+ }
+ case 2:
+ {
+ Geom::QuadraticBezier c = first ? (dynamic_cast<Geom::QuadraticBezier*>(curve_out))->subdivide(time).first :
+ (dynamic_cast<Geom::QuadraticBezier*>(curve_out))->subdivide(time).second;
+ if (curve_out) delete curve_out;
+ curve_out = c.duplicate();
+ break;
+ }
+ case 1:
+ {
+ Geom::BezierCurveN<1> c = first ? (dynamic_cast<Geom::BezierCurveN<1>* >(curve_out))->subdivide(time).first :
+ (dynamic_cast<Geom::BezierCurveN<1>* >(curve_out))->subdivide(time).second;
+ if (curve_out) delete curve_out;
+ curve_out = c.duplicate();
+ break;
+ }
+ }
+ return curve_out;
+}
+
+Geom::Piecewise<Geom::D2<Geom::SBasis> > stretch_along(Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in, Geom::Path pattern, double width);
+
+Geom::PathVector LPETaperStroke::doEffect_path(Geom::PathVector const& path_in)
+{
+ //there is a pretty good chance that people will try to drag the knots
+ //on top of each other, so block it
+
+ unsigned size = path_in[0].size();
+ if (size == return_at_first_cusp(path_in[0]).size()) {
+ //check to see if the knots were dragged over each other
+ //if so, reset the end offset
+ if ( attach_start >= (size - attach_end) ) {
+ attach_end.param_set_value( size - attach_start );
+ }
+ }
+
+ //don't ever let it be zero
+ if (attach_start <= 0) {
+ attach_start.param_set_value( 0.0001 );
+ }
+ if (attach_end <= 0) {
+ attach_end.param_set_value( 0.0001 );
+ }
+
+ /*if (size != return_at_first_cusp(path_in[0]).size()) { //will get to this in a bit
+ //check to see if either knot was dragged past their allowed amount
+ volatile unsigned size_p_start = (unsigned)attach_start;
+ volatile unsigned size_p_end = (unsigned)attach_end;
+
+ //maximum allowed value in either direction is return_at_first_cusp(path_in[0]).size
+ volatile unsigned allowed_p_start = return_at_first_cusp(path_in[0]).size();
+ volatile unsigned allowed_p_end = return_at_first_cusp(path_in[0].reverse()).size();
+
+ if (size_p_start > allowed_p_start) {
+ attach_start.param_set_value(allowed_p_start - 0.0001);
+ } else if (size_p_end > allowed_p_end) {
+ attach_end.param_set_value(allowed_p_end - 0.0001);
+ }
+ }*/
+
+ //Path::operator () means get point at time t
+ start_attach_point = return_at_first_cusp(path_in[0])(attach_start);
+ end_attach_point = return_at_first_cusp(path_in[0].reverse())(attach_end);
+ Geom::PathVector pathv_out;
+
+ pathv_out = doEffect_simplePath(path_in);
+
+
+ //now for the fun stuff. Right? RIGHT?
+
+ //decide on case
+
+ //first case: taper on both sides
+ if (true/*pathv_out[0].length() != 0.001 && pathv_out[2].length() != 0.001*/) {
+ Geom::PathVector real_pathv;
+
+ //Construct the pattern
+
+ Geom::PathVector pat_vec = sp_svg_read_pathv("M 1,0 1,1 C 0.5,1 0,0.5 0,0.5 0,0.5 0.5,0 1,0 Z");
+
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2;
+ pwd2.concat(stretch_along(pathv_out[0].toPwSb(), pat_vec[0], 40));
+
+ real_pathv.push_back(path_from_piecewise(pwd2, 0.001)[0]);
+
+ Geom::PathVector sht_path;
+ sht_path.push_back(pathv_out[1]);
+ sht_path = Outline::outlinePath_extr(sht_path, 40, LINEJOIN_STRAIGHT, butt_straight, 30);
+
+ real_pathv.push_back(sht_path[0]);
+
+ pat_vec = sp_svg_read_pathv("M 0,0 0,1 C 0.5,1 1,0.5 1,0.5 1,0.5 0.5,0 0,0 Z");
+
+ pwd2 = Geom::Piecewise<Geom::D2<Geom::SBasis> > ();
+ pwd2.concat(stretch_along(pathv_out[2].toPwSb(), pat_vec[0], 40));
+ real_pathv.push_back(path_from_piecewise(pwd2, 0.001)[0].reverse());
+
+ //clever union
+ //Geom::Shape shape1 = Geom::sanitize(Geom::PathVector(1, real_pathv[0]));
+ //Geom::Shape shape2 = Geom::sanitize(Geom::PathVector(1, real_pathv[1]));
+ //Geom::Shape shape3 = Geom::boolop(shape1, shape2, Geom::BOOLOP_UNION);
+
+ //shape2 = Geom::sanitize(Geom::PathVector(1, real_pathv[2]));
+ //shape1 = Geom::boolop(shape3, shape2, Geom::BOOLOP_UNION);
+
+ //real_pathv = Geom::desanitize(shape1);
+ return real_pathv;
+ }
+
+ return pathv_out;
+}
+
+//in all cases, this should return a PathVector with three elements.
+Geom::PathVector LPETaperStroke::doEffect_simplePath(Geom::PathVector const & path_in)
+{
+ unsigned size = path_in[0].size();
+
+ //do subdivision and get out
+ unsigned loc = (unsigned)attach_start;
+ Geom::Curve * curve_start = path_in[0] [loc].duplicate();
+
+ std::vector<Geom::Path> pathv_out;
+ Geom::Path path_out = Geom::Path();
+
+ Geom::Path trimmed_start = Geom::Path();
+ Geom::Path trimmed_end = Geom::Path();
+
+ for (unsigned i = 0; i < loc; i++) {
+ trimmed_start.append(path_in[0] [i]);
+ }
+
+ #define OVERLAP 0.001
+
+ trimmed_start.append(*subdivide_at(curve_start, (attach_start - loc) + OVERLAP, true));
+ curve_start = subdivide_at(curve_start, attach_start - loc, false);
+
+ //special case: path is one segment long
+ //special case: what if the two knots occupy the same segment?
+ if ((size == 1) || ( size - unsigned(attach_end) - 1 == loc ))
+ {
+ Geom::Coord t = Geom::nearest_point(end_attach_point, *curve_start);
+ //it is just a dumb segment
+ //we have to do some shifting here because the value changed when we reduced the length
+ //of the previous segment.
+ trimmed_end.append(*subdivide_at(curve_start, t - OVERLAP, false));
+ for (unsigned j = (size - attach_end) + 1; j < size; j++) {
+ trimmed_end.append(path_in[0] [j]);
+ }
+
+ curve_start = subdivide_at(curve_start, t, true);
+ path_out.append(*curve_start);
+ pathv_out.push_back(trimmed_start);
+ pathv_out.push_back(path_out);
+ pathv_out.push_back(trimmed_end);
+ return pathv_out;
+ }
+
+ pathv_out.push_back(trimmed_start);
+
+ //append almost all of the rest of the path, ignore the curves that the knot is past (we'll get to it in a minute)
+ path_out.append(*curve_start);
+
+ for (unsigned k = loc + 1; k < (size - unsigned(attach_end)) - 1; k++) {
+ path_out.append(path_in[0] [k]);
+ }
+
+ //deal with the last segment in a very similar fashion to the first
+ loc = size - attach_end;
+
+ Geom::Curve * curve_end = path_in[0] [loc].duplicate();
+
+ Geom::Coord t = Geom::nearest_point(end_attach_point, *curve_end);
+
+ trimmed_end.append(*subdivide_at(curve_end, t - OVERLAP, false));
+ curve_end = subdivide_at(curve_end, t, true);
+
+ for (unsigned j = (size - attach_end) + 1; j < size; j++) {
+ trimmed_end.append(path_in[0] [j]);
+ }
+
+ path_out.append(*curve_end);
+ pathv_out.push_back(path_out);
+
+ pathv_out.push_back(trimmed_end);
+
+ if (curve_end) delete curve_end;
+ if (curve_start) delete curve_start;
+ return pathv_out;
+}
+
+
+//most of the below code is verbatim from Pattern Along Path. However, it needed a little
+//tweaking to get it to work right in this case.
+Geom::Piecewise<Geom::D2<Geom::SBasis> > stretch_along(Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in, Geom::Path pattern, double prop_scale)
+{
+ using namespace Geom;
+
+ // Don't allow empty path parameter:
+ if ( pattern.empty() ) {
+ return pwd2_in;
+ }
+
+/* Much credit should go to jfb and mgsloan of lib2geom development for the code below! */
+ Piecewise<D2<SBasis> > output;
+ std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > pre_output;
+
+ D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(pattern.toPwSb());
+ Piecewise<SBasis> x0 = Piecewise<SBasis>(patternd2[0]);
+ Piecewise<SBasis> y0 = Piecewise<SBasis>(patternd2[1]);
+ OptInterval pattBndsX = bounds_exact(x0);
+ OptInterval pattBndsY = bounds_exact(y0);
+ if (pattBndsX && pattBndsY) {
+ x0 -= pattBndsX->min();
+ y0 -= pattBndsY->middle();
+
+ double xspace = 0;
+ double noffset = 0;
+ double toffset = 0;
+ /*if (prop_units.get_value() && pattBndsY){
+ xspace *= pattBndsX->extent();
+ noffset *= pattBndsY->extent();
+ toffset *= pattBndsX->extent();
+ }*/
+
+ //Prevent more than 90% overlap...
+ if (xspace < -pattBndsX->extent()*.9) {
+ xspace = -pattBndsX->extent()*.9;
+ }
+
+ y0+=noffset;
+
+ std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > paths_in;
+ paths_in = split_at_discontinuities(pwd2_in);
+
+ for (unsigned idx = 0; idx < paths_in.size(); idx++){
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > path_i = paths_in[idx];
+ Piecewise<SBasis> x = x0;
+ Piecewise<SBasis> y = y0;
+ Piecewise<D2<SBasis> > uskeleton = arc_length_parametrization(path_i,2,.1);
+ uskeleton = remove_short_cuts(uskeleton,.01);
+ Piecewise<D2<SBasis> > n = rot90(derivative(uskeleton));
+ n = force_continuity(remove_short_cuts(n,.1));
+
+ int nbCopies = 0;
+ double scaling = 1;
+ nbCopies = 1;
+ scaling = (uskeleton.domain().extent() - toffset)/pattBndsX->extent();
+
+ double pattWidth = pattBndsX->extent() * scaling;
+
+ if (scaling != 1.0) {
+ x*=scaling;
+ }
+ if ( false ) {
+ y*=(scaling*prop_scale);
+ } else {
+ if (prop_scale != 1.0) y *= prop_scale;
+ }
+ x += toffset;
+
+ double offs = 0;
+ for (int i=0; i<nbCopies; i++){
+ if (false){
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > output_piece = compose(uskeleton,x+offs)+y*compose(n,x+offs);
+ std::vector<Geom::Piecewise<Geom::D2<Geom::SBasis> > > splited_output_piece = split_at_discontinuities(output_piece);
+ pre_output.insert(pre_output.end(), splited_output_piece.begin(), splited_output_piece.end() );
+ }else{
+ output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs));
+ }
+ offs+=pattWidth;
+ }
+ }
+ /*if (false){
+ pre_output = fuse_nearby_ends(pre_output, fuse_tolerance);
+ for (unsigned i=0; i<pre_output.size(); i++){
+ output.concat(pre_output[i]);
+ }
+ }*/
+ return output;
+ } else {
+ return pwd2_in;
+ }
+}
+
+void LPETaperStroke::addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item)
+{
+ {
+ KnotHolderEntity *e = new TpS::KnotHolderEntityAttachBegin(this);
+ e->create( desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN,
+ _("Start point of the taper"), SP_KNOT_SHAPE_CIRCLE );
+ knotholder->add(e);
+ }
+ {
+ KnotHolderEntity *e = new TpS::KnotHolderEntityAttachEnd(this);
+ e->create( desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN,
+ _("End point of the taper"), SP_KNOT_SHAPE_CIRCLE );
+ knotholder->add(e);
+ }
+}
+
+namespace TpS {
+ void KnotHolderEntityAttachBegin::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
+ {
+ using namespace Geom;
+
+ LPETaperStroke* lpe = dynamic_cast<LPETaperStroke *>(_effect);
+
+ Geom::Point const s = snap_knot_position(p, state);
+
+ SPCurve *curve = SP_PATH(item)->get_curve_for_edit();
+ Geom::PathVector pathv = curve->get_pathvector();
+ Piecewise<D2<SBasis> > pwd2;
+ Geom::Path p_in = return_at_first_cusp(pathv[0]);
+ pwd2.concat(p_in.toPwSb());
+
+ double t0 = nearest_point(s, pwd2);
+ lpe->attach_start.param_set_value(t0);
+
+ // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
+ sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
+ }
+ void KnotHolderEntityAttachEnd::knot_set(Geom::Point const &p, Geom::Point const& /*origin*/, guint state)
+ {
+ using namespace Geom;
+
+ LPETaperStroke* lpe = dynamic_cast<LPETaperStroke *>(_effect);
+
+ Geom::Point const s = snap_knot_position(p, state);
+
+ SPCurve *curve = SP_PATH(item)->get_curve_for_edit();
+ Geom::PathVector pathv = curve->get_pathvector();
+ Piecewise<D2<SBasis> > pwd2;
+ Geom::Path p_in = return_at_first_cusp(pathv[0].reverse());
+ pwd2.concat(p_in.toPwSb());
+
+ double t0 = nearest_point(s, pwd2);
+ lpe->attach_end.param_set_value(t0);
+
+ // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
+ sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true);
+ }
+ Geom::Point KnotHolderEntityAttachBegin::knot_get() const
+ {
+ LPETaperStroke const * lpe = dynamic_cast<LPETaperStroke const*> (_effect);
+ return lpe->start_attach_point;
+ }
+ Geom::Point KnotHolderEntityAttachEnd::knot_get() const
+ {
+ LPETaperStroke const * lpe = dynamic_cast<LPETaperStroke const*> (_effect);
+ return lpe->end_attach_point;
+ }
+}
+
+
+/* ######################## */
+
+} //namespace LivePathEffect
+} /* namespace Inkscape */
+
+/*
+ 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 :
diff --git a/src/live_effects/lpe-taperstroke.h b/src/live_effects/lpe-taperstroke.h
new file mode 100644
index 000000000..f2fe03533
--- /dev/null
+++ b/src/live_effects/lpe-taperstroke.h
@@ -0,0 +1,73 @@
+/** @file
+ * @brief Taper Stroke path effect (meant as a replacement for using Power Strokes for tapering)
+ */
+/* Authors:
+ * Liam P White <inkscapebrony@gmail.com>
+ * Copyright (C) 2014 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef INKSCAPE_LPE_SKELETON_H
+#define INKSCAPE_LPE_SKELETON_H
+
+#include "live_effects/effect.h"
+#include "live_effects/parameter/parameter.h"
+#include "live_effects/parameter/enum.h"
+#include "live_effects/parameter/vector.h"
+
+namespace Inkscape {
+namespace LivePathEffect {
+
+namespace TpS {
+ // we need a separate namespace to avoid clashes with other LPEs
+ class KnotHolderEntityAttachBegin;
+ class KnotHolderEntityAttachEnd;
+}
+
+class LPETaperStroke : public Effect {
+public:
+ LPETaperStroke(LivePathEffectObject *lpeobject);
+ virtual ~LPETaperStroke();
+
+ virtual Geom::PathVector doEffect_path (Geom::PathVector const& path_in);
+ Geom::PathVector doEffect_simplePath(Geom::PathVector const& path_in);
+
+ virtual void addKnotHolderEntities(KnotHolder * knotholder, SPDesktop * desktop, SPItem * item);
+
+ friend class TpS::KnotHolderEntityAttachBegin;
+ friend class TpS::KnotHolderEntityAttachEnd;
+private:
+ ScalarParam attach_start;
+ ScalarParam attach_end;
+ ScalarParam smoothing;
+ EnumParam<unsigned> join_type;
+ ScalarParam miter_limit;
+
+ Geom::Point start_attach_point;
+ Geom::Point end_attach_point;
+
+ LPETaperStroke(const LPETaperStroke&);
+ LPETaperStroke& operator=(const LPETaperStroke&);
+};
+
+} //namespace LivePathEffect
+} //namespace Inkscape
+
+//because Windoze is stupid
+# ifdef WIN32
+# include "lpe-taperstroke.cpp"
+# endif
+
+#endif
+
+/*
+ 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 :
diff --git a/src/live_effects/pathoutlineprovider.h b/src/live_effects/pathoutlineprovider.h
new file mode 100755
index 000000000..8aa2e38ad
--- /dev/null
+++ b/src/live_effects/pathoutlineprovider.h
@@ -0,0 +1,766 @@
+#pragma once
+
+#include <2geom/path.h>
+#include <2geom/circle.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/shape.h>
+#include <2geom/transforms.h>
+#include <2geom/path-sink.h>
+
+#include <livarot/Path.h>
+#include <livarot/LivarotDefs.h>
+
+enum LineJoinType {
+ LINEJOIN_STRAIGHT,
+ LINEJOIN_ROUND,
+ LINEJOIN_POINTY,
+ LINEJOIN_REFLECTED,
+ LINEJOIN_EXTRAPOLATED
+};
+
+namespace Geom
+{
+ /**
+ * Refer to: Weisstein, Eric W. "Circle-Circle Intersection."
+ From MathWorld--A Wolfram Web Resource.
+ http://mathworld.wolfram.com/Circle-CircleIntersection.html
+ *
+ * @return 0 if no intersection
+ * @return 1 if one circle is contained in the other
+ * @return 2 if intersections are found (they are written to p0 and p1)
+ */
+ static int circle_circle_intersection(Circle const &circle0, Circle const &circle1,
+ Point & p0, Point & p1)
+ {
+ Point X0 = circle0.center();
+ double r0 = circle0.ray();
+ Point X1 = circle1.center();
+ double r1 = circle1.ray();
+
+ /* dx and dy are the vertical and horizontal distances between
+ * the circle centers.
+ */
+ Point D = X1 - X0;
+
+ /* Determine the straight-line distance between the centers. */
+ double d = L2(D);
+
+ /* Check for solvability. */
+ if (d > (r0 + r1))
+ {
+ /* no solution. circles do not intersect. */
+ return 0;
+ }
+ if (d <= fabs(r0 - r1))
+ {
+ /* no solution. one circle is contained in the other */
+ return 1;
+ }
+
+ /* 'point 2' is the point where the line through the circle
+ * intersection points crosses the line between the circle
+ * centers.
+ */
+
+ /* Determine the distance from point 0 to point 2. */
+ double a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ;
+
+ /* Determine the coordinates of point 2. */
+ Point p2 = X0 + D * (a/d);
+
+ /* Determine the distance from point 2 to either of the
+ * intersection points.
+ */
+ double h = std::sqrt((r0*r0) - (a*a));
+
+ /* Now determine the offsets of the intersection points from
+ * point 2.
+ */
+ Point r = (h/d)*rot90(D);
+
+ /* Determine the absolute intersection points. */
+ p0 = p2 + r;
+ p1 = p2 - r;
+
+ return 2;
+ }
+ /**
+ * Find circle that touches inside of the curve, with radius matching the curvature, at time value \c t.
+ * Because this method internally uses unitTangentAt, t should be smaller than 1.0 (see unitTangentAt).
+ */
+ static Circle touching_circle( D2<SBasis> const &curve, double t, double tol=0.01 )
+ {
+ D2<SBasis> dM=derivative(curve);
+ if ( are_near(L2sq(dM(t)),0.) ) {
+ dM=derivative(dM);
+ }
+ if ( are_near(L2sq(dM(t)),0.) ) { // try second time
+ dM=derivative(dM);
+ }
+ Piecewise<D2<SBasis> > unitv = unitVector(dM,tol);
+ Piecewise<SBasis> dMlength = dot(Piecewise<D2<SBasis> >(dM),unitv);
+ Piecewise<SBasis> k = cross(derivative(unitv),unitv);
+ k = divide(k,dMlength,tol,3);
+ double curv = k(t); // note that this value is signed
+
+ Geom::Point normal = unitTangentAt(curve, t).cw();
+ double radius = 1/curv;
+ Geom::Point center = curve(t) + radius*normal;
+ return Geom::Circle(center, fabs(radius));
+ }
+
+ static std::vector<Geom::Path> split_at_cusps(const Geom::Path& in)
+ {
+ Geom::PathVector out = Geom::PathVector();
+ Geom::Path temp = Geom::Path();
+
+ for (unsigned path_descr = 0; path_descr < in.size(); path_descr++)
+ {
+ temp = Geom::Path();
+ temp.append(in[path_descr]);
+ out.push_back(temp);
+ }
+
+ return out;
+ }
+
+ static Geom::CubicBezier sbasis_to_cubicbezier(Geom::D2<Geom::SBasis> const & sbasis_in)
+ {
+ std::vector<Geom::Point> temp;
+ sbasis_to_bezier(temp, sbasis_in, 4);
+ return Geom::CubicBezier( temp );
+ }
+
+ static boost::optional<Geom::Point> intersection_point( Geom::Point const & origin_a, Geom::Point const & vector_a,
+ Geom::Point const & origin_b, Geom::Point const & vector_b)
+ {
+ Geom::Coord denom = cross(vector_b, vector_a);
+ if (!Geom::are_near(denom,0.)){
+ Geom::Coord t = (cross(origin_a,vector_b) + cross(vector_b,origin_b)) / denom;
+ return origin_a + t * vector_a;
+ }
+ return boost::none;
+ }
+}
+
+namespace Outline
+{
+
+ typedef Geom::D2<Geom::SBasis> D2SB;
+ typedef Geom::Piecewise<D2SB> PWD2;
+
+ static void extrapolate_curves(Geom::Path& path_builder, Geom::Curve* cbc1, Geom::Curve*cbc2, Geom::Point endPt, double miter_limit)
+{
+ Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2);
+ if (cross.empty())
+ {
+ Geom::Path pth;
+ pth.append(*cbc1);
+
+ Geom::Point tang1 = Geom::unitTangentAt(pth.toPwSb()[0], 1);
+
+ pth = Geom::Path();
+ pth.append( *cbc2 );
+ Geom::Point tang2 = Geom::unitTangentAt(pth.toPwSb()[0], 0);
+
+
+ Geom::Circle circle1 = Geom::touching_circle(Geom::reverse(cbc1->toSBasis()), 0.);
+ Geom::Circle circle2 = Geom::touching_circle(cbc2->toSBasis(), 0);
+
+ Geom::Point points[2];
+ int solutions = Geom::circle_circle_intersection(circle1, circle2, points[0], points[1]);
+ if (solutions == 2)
+ {
+ Geom::Point sol(0,0);
+ if ( dot(tang2,points[0]-endPt) > 0 )
+ {
+ // points[0] is bad, choose points[1]
+ sol = points[1];
+ }
+ else if ( dot(tang2,points[1]-endPt) > 0 ) { // points[0] could be good, now check points[1]
+ // points[1] is bad, choose points[0]
+ sol = points[0];
+ }
+ else
+ {
+ // both points are good, choose nearest
+ sol = ( distanceSq(endPt, points[0]) < distanceSq(endPt, points[1]) ) ?
+ points[0] : points[1];
+ }
+ Geom::EllipticalArc *arc0 = circle1.arc(cbc1->finalPoint(), 0.5*(cbc1->finalPoint()+sol), sol, true);
+ Geom::EllipticalArc *arc1 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true);
+
+ if (arc0)
+ {
+ path_builder.append (arc0->toSBasis());
+ delete arc0;
+ arc0 = NULL;
+ }
+
+ if (arc1)
+ {
+ path_builder.append (arc1->toSBasis());
+ delete arc1;
+ arc1 = NULL;
+ }
+ }
+ else
+ {
+ path_builder.appendNew<Geom::LineSegment> (endPt);
+ }
+ }
+ else
+ {
+ path_builder.appendNew<Geom::LineSegment> (endPt);
+ }
+}
+ static Geom::Path half_outline_extrp(const Geom::Path& path_in, double line_width, ButtType linecap_type, double miter_limit)
+ {
+ Geom::PathVector pv = split_at_cusps(path_in);
+ unsigned m;
+ Path path_outline = Path();
+ Path path_tangent = Path();
+
+ Geom::Point initialPoint;
+ Geom::Point endPoint;
+
+ Geom::Path path_builder = Geom::Path();
+ Geom::PathVector * pathvec;
+
+ //load the first portion in before the loop starts
+ {
+ path_outline = Path();
+ path_outline.LoadPath(pv[0], Geom::Affine(), false, false);
+ path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10);
+ //now half of first cusp has been loaded
+
+ pathvec = path_tangent.MakePathVector();
+ path_tangent = Path();
+
+ //instead of array accessing twice, dereferencing used for clarity
+ initialPoint = (*pathvec)[0].initialPoint();
+
+ path_builder.start(initialPoint);
+ path_builder.append( (*pathvec)[0] );
+
+ path_outline = Path();
+ path_outline.LoadPath(pv[1], Geom::Affine(), false, false);
+ path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10);
+
+ delete pathvec; pathvec = NULL;
+ pathvec = path_tangent.MakePathVector();
+ path_tangent = Path();
+
+ Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate();
+ Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate();
+
+ extrapolate_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit );
+
+ path_builder.append( (*pathvec)[0] );
+
+ //always set pointers null after deleting
+ delete pathvec; pathvec = NULL;
+ delete cbc1; delete cbc2; cbc1 = cbc2 = NULL;
+ }
+
+ for (m = 2; m < pv.size(); m++)
+ {
+ path_outline = Path();
+ path_outline.LoadPath(pv[m], Geom::Affine(), false, false);
+ path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10);
+
+ delete pathvec; pathvec = NULL;
+ pathvec = path_tangent.MakePathVector();
+
+ Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate();
+ Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate();
+
+ extrapolate_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit );
+ path_builder.append( (*pathvec)[0] );
+
+ delete pathvec; pathvec = NULL;
+ delete cbc1; delete cbc2; cbc1 = cbc2 = NULL;
+ }
+
+ return path_builder;
+ }
+
+ //Create a reflected outline join.
+ //Note: it is generally recommended to let half_outline do this for you!
+ //path_builder: the path to append the curves to
+ //cbc1: the curve before the join
+ //cbc2: the curve after the join
+ //endPt: the point to end at
+ //miter_limit: the miter parameter
+ static void reflect_curves(Geom::Path& path_builder, Geom::Curve* cbc1, Geom::Curve* cbc2, Geom::Point endPt, double miter_limit)
+ {
+ //the most important work for the reflected join is done here
+
+ //determine where we are in the path. If we're on the inside, ignore
+ //and just lineTo. On the outside, we'll do a little reflection magic :)
+ Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2);
+ if (cross.empty())
+ {
+ //probably on the outside of the corner
+ Geom::Path pth;
+ pth.append(*cbc1);
+
+ Geom::Point tang1 = Geom::unitTangentAt(pth.toPwSb()[0], 1);
+
+ //reflect curves along the bevel
+ D2SB newcurve1 = pth.toPwSb()[0] *
+ Geom::reflection ( -Geom::rot90(tang1) ,
+ cbc1->finalPoint() );
+
+ Geom::CubicBezier bzr1 = sbasis_to_cubicbezier(Geom::reverse(newcurve1));
+
+ pth = Geom::Path();
+ pth.append( *cbc2 );
+ Geom::Point tang2 = Geom::unitTangentAt(pth.toPwSb()[0], 0);
+
+ D2SB newcurve2 = pth.toPwSb()[0] *
+ Geom::reflection ( -Geom::rot90(tang2) ,
+ cbc2->initialPoint() );
+ Geom::CubicBezier bzr2 = sbasis_to_cubicbezier(Geom::reverse(newcurve2));
+
+ cross = Geom::crossings(bzr1, bzr2);
+ if ( cross.empty() )
+ {
+ //std::cout << "Oops, no crossings!" << std::endl;
+ //curves didn't cross; default to miter
+ /*boost::optional <Geom::Point> p = intersection_point (cbc1->finalPoint(), tang1,
+ cbc2->initialPoint(), tang2);
+ if (p)
+ {
+ path_builder.appendNew<Geom::LineSegment> (*p);
+ }*/
+ //bevel
+ path_builder.appendNew<Geom::LineSegment>( endPt );
+ }
+ else
+ {
+ //join
+ std::pair<Geom::CubicBezier, Geom::CubicBezier> sub1 = bzr1.subdivide(cross[0].ta);
+ std::pair<Geom::CubicBezier, Geom::CubicBezier> sub2 = bzr2.subdivide(cross[0].tb);
+
+ //@TODO joins have a strange tendency to cross themselves twice. Check this.
+
+ //sections commented out are for general stability
+ path_builder.appendNew <Geom::CubicBezier> (sub1.first[1], sub1.first[2], /*sub1.first[3]*/ sub2.second[0] );
+ path_builder.appendNew <Geom::CubicBezier> (sub2.second[1], sub2.second[2], /*sub2.second[3]*/ endPt );
+ }
+ }
+ else // cross.empty()
+ {
+ //probably on the inside of the corner
+ path_builder.appendNew<Geom::LineSegment> ( endPt );
+ }
+ }
+
+ /** @brief Converts a path to one half of an outline.
+ * path_in: The input path to use. (To create the other side use path_in.reverse() )
+ * line_width: the line width to use (usually you want to divide this by 2)
+ * linecap_type: (not used here) the cap to apply. Passed to libvarot.
+ * miter_limit: the miter parameter
+ */
+ static Geom::Path half_outline(const Geom::Path& path_in, double line_width, ButtType linecap_type, double miter_limit)
+ {
+ Geom::PathVector pv = split_at_cusps(path_in);
+ unsigned m;
+ Path path_outline = Path();
+ Path path_tangent = Path();
+ //needed for closing the path
+ Geom::Point initialPoint;
+ Geom::Point endPoint;
+
+ //some issues prevented me from using a PathBuilder here
+ //it seems like PathBuilder::peek() gave me a null reference exception
+ //and I was unable to get a stack trace on Windows, so had to switch to Linux
+ //to see what the hell was wrong. :(
+ //I wasted five hours opening it in IDAPro, VS2012, and GDB Windows
+
+ /*Program received signal SIGSEGV, Segmentation fault.
+ 0x00000000006539ac in get_curves (this=0x0)
+ at /usr/include/c++/4.6/bits/locale_facets.h:1077
+ 1077 { return __c; }
+ */
+
+ Geom::Path path_builder = Geom::Path();
+ Geom::PathVector * pathvec;
+
+ //load the first portion in before the loop starts
+ {
+ path_outline = Path();
+ path_outline.LoadPath(pv[0], Geom::Affine(), false, false);
+ path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10);
+ //now half of first cusp has been loaded
+
+ pathvec = path_tangent.MakePathVector();
+ path_tangent = Path();
+
+ //instead of array accessing twice, dereferencing used for clarity
+ initialPoint = (*pathvec)[0].initialPoint();
+
+ path_builder.start(initialPoint);
+ path_builder.append( (*pathvec)[0] );
+
+ path_outline = Path();
+ path_outline.LoadPath(pv[1], Geom::Affine(), false, false);
+ path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10);
+
+ delete pathvec; pathvec = NULL;
+ pathvec = path_tangent.MakePathVector();
+ path_tangent = Path();
+
+ Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate();
+ Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate();
+
+ reflect_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit );
+
+ path_builder.append( (*pathvec)[0] );
+
+ //always set pointers null after deleting
+ delete pathvec; pathvec = NULL;
+ delete cbc1; delete cbc2; cbc1 = cbc2 = NULL;
+ }
+
+ for (m = 2; m < pv.size(); m++)
+ {
+ path_outline = Path();
+ path_outline.LoadPath(pv[m], Geom::Affine(), false, false);
+ path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10);
+
+ delete pathvec; pathvec = NULL;
+ pathvec = path_tangent.MakePathVector();
+
+ Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate();
+ Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate();
+
+ reflect_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit );
+ path_builder.append( (*pathvec)[0] );
+
+ delete pathvec; pathvec = NULL;
+ delete cbc1; delete cbc2; cbc1 = cbc2 = NULL;
+ }
+
+ return path_builder;
+ }
+
+ static Geom::PathVector outlinePath(const Geom::PathVector& path_in, double line_width, JoinType join, ButtType butt, double miter_lim)
+ {
+ Path p = Path();
+ Path outlinepath = Path();
+ for (unsigned i = 0; i < path_in.size(); i++)
+ {
+ p.LoadPath(path_in[i], Geom::Affine(), false, ( (i==0) ? false : true));
+ }
+
+ Geom::PathVector path_out;
+ for (unsigned lmnop = 0; lmnop < path_in.size(); lmnop++)
+ {
+ if (path_in[lmnop].size() > 1)
+ {
+ Geom::Path p_init;
+ Geom::Path p_rev;
+ Geom::PathBuilder pb = Geom::PathBuilder();
+
+ if ( !path_in[lmnop].closed() )
+ {
+ p_init = Outline::half_outline( path_in[lmnop], -line_width, butt,
+ miter_lim );
+ p_rev = Outline::half_outline( path_in[lmnop].reverse(), -line_width, butt,
+ miter_lim );
+
+ pb.moveTo(p_init.initialPoint() );
+ pb.append(p_init);
+
+ //cap
+ if (butt == butt_straight) {
+ pb.lineTo(p_rev.initialPoint() );
+ } else if (butt == butt_round) {
+ pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_rev.initialPoint() );
+ } else if (butt == butt_square) {
+ //don't know what to do
+ pb.lineTo(p_rev.initialPoint() );
+ } else if (butt == butt_pointy) {
+ //don't know what to do
+ pb.lineTo(p_rev.initialPoint() );
+ }
+
+ pb.append(p_rev);
+
+ if (butt == butt_straight) {
+ pb.lineTo(p_init.initialPoint() );
+ } else if (butt == butt_round) {
+ pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_init.initialPoint() );
+ } else if (butt == butt_square) {
+ //don't know what to do
+ pb.lineTo(p_init.initialPoint() );
+ } else if (butt == butt_pointy) {
+ //don't know what to do
+ //Geom::Point end_deriv = Geom::unitTangentAt( Geom::reverse(path_in[lmnop].toPwSb()[path_in[lmnop].size()]), 0);
+ //double radius = 0.5 * Geom::distance(path_in[lmnop].finalPoint(), p_rev.initialPoint());
+
+ pb.lineTo(p_init.initialPoint() );
+ }
+ }
+ else
+ {
+ //final join
+ //refer to half_outline for documentation
+ Geom::Path p_almost = path_in[lmnop];
+ p_almost.appendNew<Geom::LineSegment> ( path_in[lmnop].initialPoint() );
+ p_init = Outline::half_outline( p_almost, -line_width, butt,
+ miter_lim );
+ p_rev = Outline::half_outline( p_almost.reverse(), -line_width, butt,
+ miter_lim );
+ p.LoadPath(path_in[lmnop], Geom::Affine(), false, false);
+
+ //this is a kludge, because I can't find how to make this work properly
+ bool lastIsLinear = ( (Geom::distance(path_in[lmnop].finalPoint(),
+ path_in[lmnop] [path_in[lmnop].size() - 1].finalPoint())) ==
+ (path_in[lmnop] [path_in[lmnop].size()].length()));
+
+ p_almost = p_init;
+ if (lastIsLinear)
+ {
+ p_almost.erase_last(); p_almost.erase_last();
+ }
+
+ //outside test
+ Geom::Curve* cbc1 = p_almost[p_almost.size() - 1].duplicate();
+ Geom::Curve* cbc2 = p_almost[0].duplicate();
+
+ Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2);
+
+ if (cross.empty())
+ {
+ //this is the outside path
+
+ //reuse the old one
+ p_init = p_almost;
+ Outline::reflect_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim );
+ pb.moveTo(p_init.initialPoint()); pb.append(p_init);
+ }
+ else
+ {
+ //inside, carry on :-)
+ pb.moveTo(p_almost.initialPoint()); pb.append(p_almost);
+ }
+
+ p_almost = p_rev;
+ if (lastIsLinear)
+ {
+ p_almost.erase(p_almost.begin() );
+ p_almost.erase(p_almost.begin() );
+ }
+
+ delete cbc1; delete cbc2; cbc1 = cbc2 = NULL;
+
+ cbc1 = p_almost[p_almost.size() - 1].duplicate();
+ cbc2 = p_almost[0].duplicate();
+
+ cross = Geom::crossings(*cbc1, *cbc2);
+
+ if (cross.empty())
+ {
+ //outside path
+
+ p_init = p_almost;
+ reflect_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim );
+ pb.moveTo(p_init.initialPoint()); pb.append(p_init);
+ }
+ else
+ {
+ //inside
+ pb.moveTo(p_almost.initialPoint()); pb.append(p_almost);
+ }
+ delete cbc1; delete cbc2; cbc1 = cbc2 = NULL;
+ }
+ //pb.closePath();
+ pb.flush();
+ Geom::PathVector pv_np = pb.peek();
+ //hack
+ for (unsigned abcd = 0; abcd < pv_np.size(); abcd++)
+ {
+ path_out.push_back( pv_np[abcd] );
+ }
+ }
+ else
+ {
+ p.LoadPath(path_in[lmnop], Geom::Affine(), false, false);
+ p.Outline(&outlinepath, line_width / 2, join, butt, miter_lim);
+ std::vector<Geom::Path> *pv_p = outlinepath.MakePathVector();
+ //hack
+ path_out.push_back( (*pv_p)[0].reverse() );
+ delete pv_p;
+ }
+ }
+ return path_out;
+ }
+ static Geom::PathVector outlinePath_extr(const Geom::PathVector& path_in, double line_width, LineJoinType join, ButtType butt, double miter_lim)
+ {
+ Path p = Path();
+ Path outlinepath = Path();
+ for (unsigned i = 0; i < path_in.size(); i++)
+ {
+ p.LoadPath(path_in[i], Geom::Affine(), false, ( (i==0) ? false : true));
+ }
+
+ Geom::PathVector path_out;
+ for (unsigned lmnop = 0; lmnop < path_in.size(); lmnop++)
+ {
+ if (path_in[lmnop].size() > 1)
+ {
+ Geom::Path p_init;
+ Geom::Path p_rev;
+ Geom::PathBuilder pb = Geom::PathBuilder();
+
+ if ( !path_in[lmnop].closed() )
+ {
+ p_init = Outline::half_outline_extrp( path_in[lmnop], -line_width, butt,
+ miter_lim );
+ p_rev = Outline::half_outline_extrp( path_in[lmnop].reverse(), -line_width, butt,
+ miter_lim );
+
+ pb.moveTo(p_init.initialPoint() );
+ pb.append(p_init);
+
+ //cap
+ if (butt == butt_straight) {
+ pb.lineTo(p_rev.initialPoint() );
+ } else if (butt == butt_round) {
+ pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_rev.initialPoint() );
+ } else if (butt == butt_square) {
+ //don't know what to do
+ pb.lineTo(p_rev.initialPoint() );
+ } else if (butt == butt_pointy) {
+ //don't know what to do
+ pb.lineTo(p_rev.initialPoint() );
+ }
+
+ pb.append(p_rev);
+
+ if (butt == butt_straight) {
+ pb.lineTo(p_init.initialPoint() );
+ } else if (butt == butt_round) {
+ pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_init.initialPoint() );
+ } else if (butt == butt_square) {
+ //don't know what to do
+ pb.lineTo(p_init.initialPoint() );
+ } else if (butt == butt_pointy) {
+ //don't know what to do
+ //Geom::Point end_deriv = Geom::unitTangentAt( Geom::reverse(path_in[lmnop].toPwSb()[path_in[lmnop].size()]), 0);
+ //double radius = 0.5 * Geom::distance(path_in[lmnop].finalPoint(), p_rev.initialPoint());
+
+ pb.lineTo(p_init.initialPoint() );
+ }
+ }
+ else
+ {
+ //final join
+ //refer to half_outline for documentation
+ Geom::Path p_almost = path_in[lmnop];
+ p_almost.appendNew<Geom::LineSegment> ( path_in[lmnop].initialPoint() );
+ p_init = Outline::half_outline_extrp( p_almost, -line_width, butt,
+ miter_lim );
+ p_rev = Outline::half_outline_extrp( p_almost.reverse(), -line_width, butt,
+ miter_lim );
+ p.LoadPath(path_in[lmnop], Geom::Affine(), false, false);
+
+ //this is a kludge, because I can't find how to make this work properly
+ bool lastIsLinear = ( (Geom::distance(path_in[lmnop].finalPoint(),
+ path_in[lmnop] [path_in[lmnop].size() - 1].finalPoint())) ==
+ (path_in[lmnop] [path_in[lmnop].size()].length()));
+
+ p_almost = p_init;
+ if (lastIsLinear)
+ {
+ p_almost.erase_last(); p_almost.erase_last();
+ }
+
+ //outside test
+ Geom::Curve* cbc1 = p_almost[p_almost.size() - 1].duplicate();
+ Geom::Curve* cbc2 = p_almost[0].duplicate();
+
+ Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2);
+
+ if (cross.empty())
+ {
+ //this is the outside path
+
+ //reuse the old one
+ p_init = p_almost;
+ Outline::extrapolate_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim );
+ pb.moveTo(p_init.initialPoint()); pb.append(p_init);
+ }
+ else
+ {
+ //inside, carry on :-)
+ pb.moveTo(p_almost.initialPoint()); pb.append(p_almost);
+ }
+
+ p_almost = p_rev;
+ if (lastIsLinear)
+ {
+ p_almost.erase(p_almost.begin() );
+ p_almost.erase(p_almost.begin() );
+ }
+
+ delete cbc1; delete cbc2; cbc1 = cbc2 = NULL;
+
+ cbc1 = p_almost[p_almost.size() - 1].duplicate();
+ cbc2 = p_almost[0].duplicate();
+
+ cross = Geom::crossings(*cbc1, *cbc2);
+
+ if (cross.empty())
+ {
+ //outside path
+
+ p_init = p_almost;
+ extrapolate_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim );
+ pb.moveTo(p_init.initialPoint()); pb.append(p_init);
+ }
+ else
+ {
+ //inside
+ pb.moveTo(p_almost.initialPoint()); pb.append(p_almost);
+ }
+ delete cbc1; delete cbc2; cbc1 = cbc2 = NULL;
+ }
+ //pb.closePath();
+ pb.flush();
+ Geom::PathVector pv_np = pb.peek();
+ //hack
+ for (unsigned abcd = 0; abcd < pv_np.size(); abcd++)
+ {
+ path_out.push_back( pv_np[abcd] );
+ }
+ }
+ else
+ {
+ p.LoadPath(path_in[lmnop], Geom::Affine(), false, false);
+ p.Outline(&outlinepath, line_width / 2, join_pointy, butt, miter_lim);
+ std::vector<Geom::Path> *pv_p = outlinepath.MakePathVector();
+ //hack
+ path_out.push_back( (*pv_p)[0].reverse() );
+ delete pv_p;
+ }
+ }
+ return path_out;
+ }
+
+
+} // namespace Outline
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp
index 8e2502545..2dabf1884 100644
--- a/src/ui/clipboard.cpp
+++ b/src/ui/clipboard.cpp
@@ -601,6 +601,12 @@ Glib::ustring ClipboardManagerImpl::getPathParameter(SPDesktop* desktop)
*/
Glib::ustring ClipboardManagerImpl::getShapeOrTextObjectId(SPDesktop *desktop)
{
+ //https://bugs.launchpad.net/inkscape/+bug/1293979
+ //basically, when we do a depth-first search, we're stopping
+ //at the first object to be <svg:path> or <svg:text>.
+ //but that could then return the id of the object's
+ //clip path or mask, not the original path!
+
SPDocument *tempdoc = _retrieveClipboard(); // any target will do here
if ( tempdoc == NULL ) {
_userWarn(desktop, _("Nothing on the clipboard."));
@@ -608,6 +614,9 @@ Glib::ustring ClipboardManagerImpl::getShapeOrTextObjectId(SPDesktop *desktop)
}
Inkscape::XML::Node *root = tempdoc->getReprRoot();
+ //1293979: strip out the defs of the document
+ root->removeChild(tempdoc->getDefs()->getRepr());
+
Inkscape::XML::Node *repr = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth
if ( repr == NULL ) {
repr = sp_repr_lookup_name(root, "svg:text", -1);