diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/live_effects/Makefile_insert | 6 | ||||
| -rw-r--r-- | src/live_effects/effect-enum.h | 3 | ||||
| -rw-r--r-- | src/live_effects/effect.cpp | 13 | ||||
| -rwxr-xr-x | src/live_effects/lpe-jointype.h | 43 | ||||
| -rw-r--r-- | src/live_effects/lpe-taperstroke.cpp | 536 | ||||
| -rw-r--r-- | src/live_effects/lpe-taperstroke.h | 73 | ||||
| -rwxr-xr-x | src/live_effects/pathoutlineprovider.h | 766 | ||||
| -rw-r--r-- | src/ui/clipboard.cpp | 9 |
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); |
