From 95b10e0707ee0e16055d5e514e1d05c929d49b5a Mon Sep 17 00:00:00 2001 From: "Liam P. White" Date: Tue, 18 Mar 2014 17:34:31 -0600 Subject: Added in new toy effect "Taper Strokes," readded a missing header file, bugfixes (bzr r13090.1.25) --- src/live_effects/Makefile_insert | 6 +- src/live_effects/effect-enum.h | 3 +- src/live_effects/effect.cpp | 13 +- src/live_effects/lpe-jointype.h | 43 ++ src/live_effects/lpe-taperstroke.cpp | 536 +++++++++++++++++++++++ src/live_effects/lpe-taperstroke.h | 73 ++++ src/live_effects/pathoutlineprovider.h | 766 +++++++++++++++++++++++++++++++++ src/ui/clipboard.cpp | 9 + 8 files changed, 1443 insertions(+), 6 deletions(-) create mode 100755 src/live_effects/lpe-jointype.h create mode 100644 src/live_effects/lpe-taperstroke.cpp create mode 100644 src/live_effects/lpe-taperstroke.h create mode 100755 src/live_effects/pathoutlineprovider.h (limited to 'src') 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 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 ( new LPEBoundingBox(lpeobj) ); break; - /*case JOIN_TYPE: + case JOIN_TYPE: neweffect = static_cast ( new LPEJoinType(lpeobj) ); - break;*/ + break; + case TAPER_STROKE: + neweffect = static_cast ( 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 doEffect_path (std::vector const & path_in); + +private: + LPEJoinType(const LPEJoinType&); + LPEJoinType& operator=(const LPEJoinType&); + + ScalarParam line_width; + EnumParam linecap_type; + EnumParam 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 + * + * 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 + +#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 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 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(&attach_start) ); + registerParameter( dynamic_cast(&attach_end) ); + registerParameter( dynamic_cast(&smoothing) ); + registerParameter( dynamic_cast(&join_type) ); + registerParameter( dynamic_cast(&miter_limit) ); +} + +LPETaperStroke::~LPETaperStroke() +{ + +} + +unsigned curveOrder (const Geom::Curve* curve_in) +{ + using namespace Geom; + //cast it + const CubicBezier *cbc = dynamic_cast(curve_in); + if (cbc) return 3; + const QuadraticBezier * qbc = dynamic_cast(curve_in); + if (qbc) return 2; + const BezierCurveN<1U> * lbc = dynamic_cast *>(curve_in); + if (lbc) return 1; + //BezierCurveN<0> * dbc = dynamic_cast *> (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(&path_in[i]))->operator[] (2); + break; + case 2: + start_point = (dynamic_cast(&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(&path_in[i+1]))->operator[] (1); + break; + case 2: + end_point = (dynamic_cast(&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 (curve_out))->subdivide(time).first : + (dynamic_cast (curve_out))->subdivide(time).second; + if (curve_out) delete curve_out; + curve_out = c.duplicate(); + break; + } + case 2: + { + Geom::QuadraticBezier c = first ? (dynamic_cast(curve_out))->subdivide(time).first : + (dynamic_cast(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* >(curve_out))->subdivide(time).first : + (dynamic_cast* >(curve_out))->subdivide(time).second; + if (curve_out) delete curve_out; + curve_out = c.duplicate(); + break; + } + } + return curve_out; +} + +Geom::Piecewise > stretch_along(Geom::Piecewise > 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 > 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 > (); + 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 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 > stretch_along(Geom::Piecewise > 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 > output; + std::vector > > pre_output; + + D2 > patternd2 = make_cuts_independent(pattern.toPwSb()); + Piecewise x0 = Piecewise(patternd2[0]); + Piecewise y0 = Piecewise(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 > > paths_in; + paths_in = split_at_discontinuities(pwd2_in); + + for (unsigned idx = 0; idx < paths_in.size(); idx++){ + Geom::Piecewise > path_i = paths_in[idx]; + Piecewise x = x0; + Piecewise y = y0; + Piecewise > uskeleton = arc_length_parametrization(path_i,2,.1); + uskeleton = remove_short_cuts(uskeleton,.01); + Piecewise > 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 > output_piece = compose(uskeleton,x+offs)+y*compose(n,x+offs); + std::vector > > 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; icreate( 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(_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 > 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(_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 > 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 (_effect); + return lpe->start_attach_point; + } + Geom::Point KnotHolderEntityAttachEnd::knot_get() const + { + LPETaperStroke const * lpe = dynamic_cast (_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 + * 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 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 +#include + +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 const &curve, double t, double tol=0.01 ) + { + D2 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 > unitv = unitVector(dM,tol); + Piecewise dMlength = dot(Piecewise >(dM),unitv); + Piecewise 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 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 const & sbasis_in) + { + std::vector temp; + sbasis_to_bezier(temp, sbasis_in, 4); + return Geom::CubicBezier( temp ); + } + + static boost::optional 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 D2SB; + typedef Geom::Piecewise 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 (endPt); + } + } + else + { + path_builder.appendNew (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 p = intersection_point (cbc1->finalPoint(), tang1, + cbc2->initialPoint(), tang2); + if (p) + { + path_builder.appendNew (*p); + }*/ + //bevel + path_builder.appendNew( endPt ); + } + else + { + //join + std::pair sub1 = bzr1.subdivide(cross[0].ta); + std::pair 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 (sub1.first[1], sub1.first[2], /*sub1.first[3]*/ sub2.second[0] ); + path_builder.appendNew (sub2.second[1], sub2.second[2], /*sub2.second[3]*/ endPt ); + } + } + else // cross.empty() + { + //probably on the inside of the corner + path_builder.appendNew ( 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 ( 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 *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 ( 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 *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 or . + //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); -- cgit v1.2.3