summaryrefslogtreecommitdiffstats
path: root/src/helper/geom-pathstroke.cpp
diff options
context:
space:
mode:
authorJabier Arraiza Cenoz <jabier.arraiza@marker.es>2015-04-10 22:54:49 +0000
committerJabiertxof <jtx@jtx.marker.es>2015-04-10 22:54:49 +0000
commit1df5d1b28c59070eeabeac749e2fad0ceb6a781d (patch)
tree544be4e0eb7b62bc1fc5f62e2829ad996027acda /src/helper/geom-pathstroke.cpp
parentFix coding style issues in transform by two points LPE (diff)
parentadded info about multiple pen feature in gui (diff)
downloadinkscape-1df5d1b28c59070eeabeac749e2fad0ceb6a781d.tar.gz
inkscape-1df5d1b28c59070eeabeac749e2fad0ceb6a781d.zip
update to trunk
(bzr r13879.1.16)
Diffstat (limited to 'src/helper/geom-pathstroke.cpp')
-rw-r--r--src/helper/geom-pathstroke.cpp535
1 files changed, 400 insertions, 135 deletions
diff --git a/src/helper/geom-pathstroke.cpp b/src/helper/geom-pathstroke.cpp
index f41732a51..eb0c432c6 100644
--- a/src/helper/geom-pathstroke.cpp
+++ b/src/helper/geom-pathstroke.cpp
@@ -6,11 +6,13 @@
* Released under GNU GPL, read the file 'COPYING' for more information
*/
+#include <iomanip>
#include <2geom/path-sink.h>
#include <2geom/point.h>
#include <2geom/bezier-curve.h>
#include <2geom/svg-elliptical-arc.h>
#include <2geom/sbasis-to-bezier.h> // cubicbezierpath_from_sbasis
+#include <2geom/path-intersection.h>
#include "helper/geom-pathstroke.h"
@@ -18,6 +20,74 @@ namespace Geom {
// 2geom/circle-circle.cpp, no header
int circle_circle_intersection(Point X0, double r0, Point X1, double r1, Point &p0, Point &p1);
+/**
+ * Determine the intersection points between a circle C0 and a line defined
+ * by two points, X0 and X1.
+ *
+ * Which intersection point is assigned to p0 or p1 is unspecified, and callers
+ * should not depend on any particular intersection always being assigned to p0.
+ *
+ * Returns:
+ * If the line and circle do not cross, 0 is returned.
+ * If solution(s) exist, 2 is returned, and the results are written to p0 and p1.
+ */
+static int circle_line_intersection(Circle C0, Point X0, Point X1, Point &p0, Point &p1)
+{
+ /* equation of a circle: (x - h)^2 + (y - k)^2 = r^2 */
+ Coord r = C0.ray();
+ Coord h = C0.center()[X];
+ Coord k = C0.center()[Y];
+
+ Coord x0, y0;
+ Coord x1, y1;
+
+ if (are_near(X1[X], X0[X])) {
+ /* slope is undefined (vertical line) */
+ Coord c = X0[X];
+ Coord det = r*r - (c-h)*(c-h);
+
+ /* no intersection */
+ if (det < 0)
+ return 0;
+
+ /* solve for y */
+ y0 = k + std::sqrt(det);
+ y1 = k - std::sqrt(det);
+
+ // x == c (always)
+ x0 = c;
+ x1 = c;
+ } else {
+ /* equation of a line: y = mx + b */
+ Coord m = (X1[Y] - X0[Y]) / (X1[X] - X0[X]);
+ Coord b = X0[Y] - m*X0[X];
+
+ /* obtain quadratic for x: */
+ Coord A = m*m + 1;
+ Coord B = 2*h - 2*b*m + 2*k*m;
+ Coord C = b*b + h*h + k*k - r*r - 2*b*k;
+
+ Coord det = B*B - 4*A*C;
+
+ /* no intersection, circle and line do not cross */
+ if (det < 0)
+ return 0;
+
+ /* solve quadratic */
+ x0 = (B + std::sqrt(det)) / (2*A);
+ x1 = (B - std::sqrt(det)) / (2*A);
+
+ /* substitute the calculated x times to determine the y values */
+ y0 = m*x0 + b;
+ y1 = m*x1 + b;
+ }
+
+ p0 = Point(x0, y0);
+ p1 = Point(x1, y1);
+
+ return 2;
+}
+
static Point intersection_point(Point origin_a, Point vector_a, Point origin_b, Point vector_b)
{
Coord denom = cross(vector_b, vector_a);
@@ -57,122 +127,306 @@ static Circle touching_circle( D2<SBasis> const &curve, double t, double tol=0.0
namespace {
+// Join functions may:
+// - inspect any curve of the current path
+// - append any type of curve to the current path
+// - inspect the outgoing path
+//
+// Join functions must:
+// - append the outgoing curve
+// OR
+// - end at outgoing.finalPoint
+
typedef void join_func(Geom::Path& res, Geom::Curve const& outgoing, double miter, double width);
void bevel_join(Geom::Path& res, Geom::Curve const& outgoing, double /*miter*/, double /*width*/)
{
res.appendNew<Geom::LineSegment>(outgoing.initialPoint());
+ res.append(outgoing);
}
void round_join(Geom::Path& res, Geom::Curve const& outgoing, double /*miter*/, double width)
{
- res.appendNew<Geom::SVGEllipticalArc>(width, width, 0, false, width > 0, outgoing.initialPoint());
+ res.appendNew<Geom::SVGEllipticalArc>(width, width, 0, false, width <= 0, outgoing.initialPoint());
+ res.append(outgoing);
}
-void miter_join(Geom::Path& res, Geom::Curve const& outgoing, double miter, double width)
+void miter_join_internal(Geom::Path& res, Geom::Curve const& outgoing, double miter, double width, bool clip)
{
Geom::Curve const& incoming = res.back();
Geom::Point tang1 = Geom::unitTangentAt(reverse(incoming.toSBasis()), 0.);
Geom::Point tang2 = outgoing.unitTangentAt(0);
Geom::Point p = Geom::intersection_point(incoming.finalPoint(), tang1, outgoing.initialPoint(), tang2);
+
+ bool satisfied = false;
+ bool inc_ls = res.back_open().degreesOfFreedom() <= 4;
+
if (p.isFinite()) {
// check size of miter
- Geom::Point point_on_path = incoming.finalPoint() - Geom::rot90(tang1)*width;
- double len = Geom::distance(p, point_on_path);
- if (len <= miter) {
+ Geom::Point point_on_path = incoming.finalPoint() + Geom::rot90(tang1)*width;
+ satisfied = Geom::distance(p, point_on_path) <= miter * 2.0 * width;
+ if (satisfied) {
// miter OK, check to see if we can do a relocation
- // TODO FIXME
- /*if (auto line = cast(const(LineSegment))res.back_open) {
- Curve copy = line.duplicate;
- copy.setFinal(p);
- res.erase_last();
- res.append(copy);
- } else {*/
+ if (inc_ls) {
+ res.setFinal(p);
+ } else {
res.appendNew<Geom::LineSegment>(p);
- //}
+ }
+ } else if (clip) {
+ // miter needs clipping, find two points
+ Geom::Point bisector_versor = Geom::Line(point_on_path, p).versor();
+ Geom::Point point_limit = point_on_path + miter * 2.0 * width * bisector_versor;
+
+ Geom::Point p1 = Geom::intersection_point(incoming.finalPoint(), tang1, point_limit, bisector_versor.cw());
+ Geom::Point p2 = Geom::intersection_point(outgoing.initialPoint(), tang2, point_limit, bisector_versor.cw());
+
+ if (inc_ls) {
+ res.setFinal(p1);
+ } else {
+ res.appendNew<Geom::LineSegment>(p1);
+ }
+ res.appendNew<Geom::LineSegment>(p2);
}
}
+
res.appendNew<Geom::LineSegment>(outgoing.initialPoint());
+
+ // check if we can do another relocation
+ bool out_ls = outgoing.degreesOfFreedom() <= 4;
+
+ if ( (satisfied || clip) && out_ls) {
+ res.setFinal(outgoing.finalPoint());
+ } else {
+ res.append(outgoing);
+ }
+}
+
+void miter_join(Geom::Path& res, Geom::Curve const& outgoing, double miter, double width) {
+ miter_join_internal( res, outgoing, miter, width, false );
+}
+
+void miter_clip_join(Geom::Path& res, Geom::Curve const& outgoing, double miter, double width) {
+ miter_join_internal( res, outgoing, miter, width, true );
+}
+
+Geom::Point pick_solution(Geom::Point points[2], Geom::Point tang2, Geom::Point endPt)
+{
+ Geom::Point sol;
+ 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];
+ }
+ return sol;
}
-// might need a little reworking
-void extrapolate_join(Geom::Path& path_builder, Geom::Curve const& outgoing, double miter_limit, double line_width)
+void extrapolate_join(Geom::Path& res, Geom::Curve const& outgoing, double miter, double width)
{
using namespace Geom;
- Geom::Curve const& incoming = path_builder.back();
+
+ Geom::Curve const& incoming = res.back();
+ Geom::Point startPt = incoming.finalPoint();
Geom::Point endPt = outgoing.initialPoint();
+ Geom::Point tang1 = Geom::unitTangentAt(reverse(incoming.toSBasis()), 0.);
+ Geom::Point tang2 = outgoing.unitTangentAt(0);
- // The method used when extrapolating curves fails to work when either side of the join to be extrapolated
- // is a line segment. When this situation is encountered, fall back to a regular miter join.
- bool lineProblem = (dynamic_cast<LineSegment const *>(&incoming)) || (dynamic_cast<LineSegment const*>(&outgoing));
- if (lineProblem == false) {
- // Geom::Point tang1 = Geom::unitTangentAt(Geom::reverse(incoming.toSBasis()), 0.);
- Geom::Point tang2 = Geom::unitTangentAt(outgoing.toSBasis(), 0);
+ Geom::Circle circle1 = Geom::touching_circle(Geom::reverse(incoming.toSBasis()), 0.);
+ Geom::Circle circle2 = Geom::touching_circle(outgoing.toSBasis(), 0);
- Geom::Circle circle1 = Geom::touching_circle(Geom::reverse(incoming.toSBasis()), 0.);
- Geom::Circle circle2 = Geom::touching_circle(outgoing.toSBasis(), 0);
+ bool inc_ls = !circle1.center().isFinite();
+ bool out_ls = !circle2.center().isFinite();
- Geom::Point points[2];
- int solutions = Geom::circle_circle_intersection(circle1.center(), circle1.ray(),
- circle2.center(), circle2.ray(),
- points[0], points[1]);
+ Geom::Point points[2];
+
+ int solutions = 0;
+ Geom::EllipticalArc *arc1 = NULL;
+ Geom::EllipticalArc *arc2 = NULL;
+ Geom::Point sol;
+ Geom::Point p1;
+ Geom::Point p2;
+
+ if (!inc_ls && !out_ls) {
+ // Two circles
+ solutions = Geom::circle_circle_intersection(circle1.center(), circle1.ray(),
+ circle2.center(), circle2.ray(),
+ 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];
- }
+ sol = pick_solution(points, tang2, endPt);
+ arc1 = circle1.arc(startPt, 0.5*(startPt+sol), sol, true);
+ arc2 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true);
+ }
+ } else if (inc_ls && !out_ls) {
+ // Line and circle
+ solutions = Geom::circle_line_intersection(circle2, incoming.initialPoint(), incoming.finalPoint(), points[0], points[1]);
- Geom::EllipticalArc *arc0 = circle1.arc(incoming.finalPoint(), 0.5*(incoming.finalPoint()+sol), sol, true);
- Geom::EllipticalArc *arc1 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true);
- try {
- if (arc0) {
- path_builder.append(*arc0);
- delete arc0;
- arc0 = NULL;
- } else {
- throw std::exception();
- }
+ if (solutions == 2) {
+ sol = pick_solution(points, tang2, endPt);
+ arc2 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true);
+ }
+ } else if (!inc_ls && out_ls) {
+ // Circle and line
+ solutions = Geom::circle_line_intersection(circle1, outgoing.initialPoint(), outgoing.finalPoint(), points[0], points[1]);
+
+ if (solutions == 2) {
+ sol = pick_solution(points, tang2, endPt);
+ arc1 = circle1.arc(startPt, 0.5*(sol+startPt), sol, true);
+ }
+ }
+
+ if (solutions != 2)
+ // no solutions available, fall back to miter
+ return miter_clip_join(res, outgoing, miter, width);
+
+ // We have a solution, thus sol is defined.
+ p1 = sol;
+
+ // See if we need to clip. Miter length is measured along a circular arc that is tangent to the
+ // bisector of the incoming and out going angles and passes through the end point (sol) of the
+ // line join.
+
+ // Center of circle is intersection of a line orthogonal to bisector and a line bisecting
+ // a chord connecting the path end point (point_on_path) and the join end point (sol).
+ Geom::Point point_on_path = startPt + Geom::rot90(tang1)*width;
+ Geom::Line bisector = make_angle_bisector_line(startPt, point_on_path, endPt);
+ Geom::Line ortho = make_orthogonal_line(point_on_path, bisector);
+
+ Geom::LineSegment chord(point_on_path, sol);
+ Geom::Line bisector_chord = make_bisector_line(chord);
+
+ Geom::Line limit_line;
+ double miter_limit = 2.0 * width * miter;
+ bool clipped = false;
+
+ if (are_parallel(bisector_chord, ortho)) {
+ // No intersection (can happen if curvatures are equal but opposite)
+ if (Geom::distance(point_on_path, sol) > miter_limit) {
+ clipped = true;
+ Geom::Point limit_point = point_on_path + miter_limit * bisector.versor();
+ limit_line = make_parallel_line( limit_point, ortho );
+ }
+ } else {
+ Geom::Point center =
+ Geom::intersection_point( bisector_chord.pointAt(0), bisector_chord.versor(),
+ ortho.pointAt(0), ortho.versor() );
+ Geom::Coord radius = distance(center, point_on_path);
+ Geom::Circle circle_center(center, radius);
+
+ double limit_angle = miter_limit / radius;
+
+ Geom::Ray start_ray(center, point_on_path);
+ Geom::Ray end_ray(center, sol);
+ Geom::Line limit_line(center, 0); // Angle set below
+
+ if (Geom::cross(start_ray.versor(), end_ray.versor()) > 0) {
+ limit_line.setAngle(start_ray.angle() - limit_angle);
+ } else {
+ limit_line.setAngle(start_ray.angle() + limit_angle);
+ }
- if (arc1) {
- path_builder.append(*arc1);
+ Geom::EllipticalArc *arc_center = circle_center.arc(point_on_path, 0.5*(point_on_path + sol), sol, true);
+ if (arc_center && arc_center->sweepAngle() > limit_angle) {
+ // We need to clip
+ clipped = true;
+
+ if (!inc_ls) {
+ // Incoming circular
+ solutions = Geom::circle_line_intersection(circle1, limit_line.pointAt(0), limit_line.pointAt(1), points[0], points[1]);
+
+ if (solutions == 2) {
+ p1 = pick_solution(points, tang2, endPt);
delete arc1;
- arc1 = NULL;
- } else {
- throw std::exception();
+ arc1 = circle1.arc(startPt, 0.5*(p1+startPt), p1, true);
}
+ } else {
+ p1 = Geom::intersection_point(startPt, tang1, limit_line.pointAt(0), limit_line.versor());
+ }
- } catch (std::exception const & ex) {
- printf("WARNING: Error extrapolating line join: %s\n", ex.what());
- path_builder.appendNew<Geom::LineSegment>(endPt);
+ if (!out_ls) {
+ // Outgoing circular
+ solutions = Geom::circle_line_intersection(circle2, limit_line.pointAt(0), limit_line.pointAt(1), points[0], points[1]);
+
+ if (solutions == 2) {
+ p2 = pick_solution(points, tang1, endPt);
+ delete arc2;
+ arc2 = circle2.arc(p2, 0.5*(p2+endPt), endPt, true);
+ }
+ } else {
+ p2 = Geom::intersection_point(endPt, tang2, limit_line.pointAt(0), limit_line.versor());
}
- } else {
- // 1 or no solutions found, default to miter
- miter_join(path_builder, outgoing, miter_limit, line_width);
}
+ }
+
+ // Add initial
+ if (arc1) {
+ res.append(*arc1);
} else {
- // Line segments exist
- miter_join(path_builder, outgoing, miter_limit, line_width);
+ // Straight line segment: move last point
+ res.setFinal(p1);
+ }
+
+ if (clipped) {
+ res.appendNew<Geom::LineSegment>(p2);
}
+
+ // Add outgoing
+ if (arc2) {
+ res.append(*arc2);
+ res.append(outgoing);
+ } else {
+ // Straight line segment:
+ res.appendNew<Geom::LineSegment>(outgoing.finalPoint());
+ }
+
+ delete arc1;
+ delete arc2;
}
void join_inside(Geom::Path& res, Geom::Curve const& outgoing)
{
- res.appendNew<Geom::LineSegment>(outgoing.initialPoint());
+ Geom::Curve const& incoming = res.back_open();
+ Geom::Crossings cross = Geom::crossings(incoming, outgoing);
+
+ if (!cross.empty()) {
+ // yeah if we could avoid allocing that'd be great
+ Geom::Curve *d1 = incoming.portion(0., cross[0].ta);
+ res.erase_last();
+ res.append(*d1);
+ delete d1;
+
+ Geom::Curve *d2 = outgoing.portion(cross[0].tb, 1.);
+ res.setFinal(d2->initialPoint());
+ res.append(*d2);
+ delete d2;
+ } else {
+ res.appendNew<Geom::LineSegment>(outgoing.initialPoint());
+ res.append(outgoing);
+ }
+}
+
+bool decide(Geom::Curve const& incoming, Geom::Curve const& outgoing)
+{
+ Geom::Point tang1 = Geom::unitTangentAt(reverse(incoming.toSBasis()), 0.);
+ Geom::Point tang2 = outgoing.unitTangentAt(0.);
+ return (Geom::cross(tang1, tang2) < 0);
}
-void outline_helper(Geom::Path& res, Geom::Path const& to_add, double width, double miter, Inkscape::LineJoinType join)
+void outline_helper(Geom::Path& res, Geom::Path const& to_add, double width, bool on_outside, double miter, Inkscape::LineJoinType join)
{
- Geom::Point tang1 = -Geom::unitTangentAt(reverse(res.back().toSBasis()), 0.);
- //Geom::Point tang2 = to_add[0].unitTangentAt(0);
- Geom::Point discontinuity_vec = to_add.initialPoint() - res.finalPoint();
- bool on_outside = (Geom::dot(tang1, discontinuity_vec) >= 0);
+ if (res.size() == 0 || to_add.size() == 0)
+ return;
+
+ Geom::Curve const& outgoing = to_add[0];
+ if (Geom::are_near(res.finalPoint(), outgoing.initialPoint())) {
+ // if the points are /that/ close, just ignore this one
+ res.setFinal(outgoing.initialPoint());
+ res.append(outgoing);
+ return;
+ }
if (on_outside) {
join_func *jf;
@@ -186,15 +440,16 @@ void outline_helper(Geom::Path& res, Geom::Path const& to_add, double width, dou
case Inkscape::JOIN_EXTRAPOLATE:
jf = &extrapolate_join;
break;
+ case Inkscape::JOIN_MITER_CLIP:
+ jf = &miter_clip_join;
+ break;
default:
jf = &miter_join;
}
- jf(res, to_add[0], miter, width);
+ jf(res, outgoing, miter, width);
} else {
- join_inside(res, to_add[0]);
+ join_inside(res, outgoing);
}
-
- res.append(to_add);
}
// Offsetting a line segment is mathematically stable and quick to do
@@ -318,15 +573,17 @@ void offset_quadratic(Geom::Path& p, Geom::QuadraticBezier const& bez, double wi
std::vector<Geom::Point> points = bez.points();
Geom::Point b1 = points[0] + (2./3) * (points[1] - points[0]);
Geom::Point b2 = b1 + (1./3) * (points[2] - points[0]);
- Geom::CubicBezier cub = Geom::CubicBezier(points[0], b1, b2, points[3]);
+ Geom::CubicBezier cub = Geom::CubicBezier(points[0], b1, b2, points[2]);
offset_cubic(p, cub, width, tol, levels);
}
void offset_curve(Geom::Path& res, Geom::Curve const* current, double width)
{
- double const tolerance = 0.0025;
+ double const tolerance = 0.005;
size_t levels = 8;
+ if (current->isDegenerate()) return; // don't do anything
+
// TODO: we can handle SVGEllipticalArc here as well, do that!
if (Geom::BezierCurve const *b = dynamic_cast<Geom::BezierCurve const*>(current)) {
@@ -346,7 +603,7 @@ void offset_curve(Geom::Path& res, Geom::Curve const* current, double width)
break;
}
default: {
- Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(current->toSBasis(), 0.1);
+ Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(current->toSBasis(), tolerance);
for (size_t i = 0; i < sbasis_path.size(); ++i)
offset_curve(res, &sbasis_path[i], width);
break;
@@ -359,8 +616,40 @@ void offset_curve(Geom::Path& res, Geom::Curve const* current, double width)
}
}
+typedef void cap_func(Geom::PathBuilder& res, Geom::Path const& with_dir, Geom::Path const& against_dir, double width);
+
+void flat_cap(Geom::PathBuilder& res, Geom::Path const&, Geom::Path const& against_dir, double)
+{
+ res.lineTo(against_dir.initialPoint());
}
+void round_cap(Geom::PathBuilder& res, Geom::Path const&, Geom::Path const& against_dir, double width)
+{
+ res.arcTo(width / 2., width / 2., 0., true, false, against_dir.initialPoint());
+}
+
+void square_cap(Geom::PathBuilder& res, Geom::Path const& with_dir, Geom::Path const& against_dir, double width)
+{
+ width /= 2.;
+ Geom::Point normal_1 = -Geom::unitTangentAt(Geom::reverse(with_dir.back().toSBasis()), 0.);
+ Geom::Point normal_2 = -against_dir[0].unitTangentAt(0.);
+ res.lineTo(with_dir.finalPoint() + normal_1*width);
+ res.lineTo(against_dir.initialPoint() + normal_2*width);
+ res.lineTo(against_dir.initialPoint());
+}
+
+void peak_cap(Geom::PathBuilder& res, Geom::Path const& with_dir, Geom::Path const& against_dir, double width)
+{
+ width /= 2.;
+ Geom::Point normal_1 = -Geom::unitTangentAt(Geom::reverse(with_dir.back().toSBasis()), 0.);
+ Geom::Point normal_2 = -against_dir[0].unitTangentAt(0.);
+ Geom::Point midpoint = ((with_dir.finalPoint() + normal_1*width) + (against_dir.initialPoint() + normal_2*width)) * 0.5;
+ res.lineTo(midpoint);
+ res.lineTo(against_dir.initialPoint());
+}
+
+} // namespace
+
namespace Inkscape {
Geom::PathVector outline(Geom::Path const& input, double width, double miter, LineJoinType join, LineCapType butt)
@@ -374,67 +663,36 @@ Geom::PathVector outline(Geom::Path const& input, double width, double miter, Li
res.moveTo(with_dir[0].initialPoint());
res.append(with_dir);
+ cap_func *cf;
+ switch (butt) {
+ case BUTT_ROUND:
+ cf = &round_cap;
+ break;
+ case BUTT_SQUARE:
+ cf = &square_cap;
+ break;
+ case BUTT_PEAK:
+ cf = &peak_cap;
+ break;
+ default:
+ cf = &flat_cap;
+ }
+
// glue caps
if (!input.closed()) {
- switch (butt) {
- case BUTT_ROUND:
- res.arcTo((-width) / 2., (-width) / 2., 0., true, true, against_dir.initialPoint());
- break;
- case BUTT_SQUARE: {
- Geom::Point end_deriv = -Geom::unitTangentAt(Geom::reverse(input[input.size()-1].toSBasis()), 0.);
- double radius = 0.5 * Geom::distance(with_dir.finalPoint(), against_dir.initialPoint());
- res.lineTo(with_dir.finalPoint() + end_deriv*radius);
- res.lineTo(against_dir.initialPoint() + end_deriv*radius);
- res.lineTo(against_dir.initialPoint());
- break;
- }
- case BUTT_PEAK: {
- Geom::Point end_deriv = -Geom::unitTangentAt(Geom::reverse(input[input.size()-1].toSBasis()), 0.);
- double radius = 0.5 * Geom::distance(with_dir.finalPoint(), against_dir.initialPoint());
- Geom::Point midpoint = ((with_dir.finalPoint() + against_dir.initialPoint()) * 0.5) + end_deriv*radius;
- res.lineTo(midpoint);
- res.lineTo(against_dir.initialPoint());
- break;
- }
- case BUTT_FLAT:
- default:
- res.lineTo(against_dir.initialPoint());
- break;
- }
+ cf(res, with_dir, against_dir, width);
} else {
+ res.closePath();
res.moveTo(against_dir.initialPoint());
}
res.append(against_dir);
if (!input.closed()) {
- switch(butt) {
- case BUTT_ROUND:
- res.arcTo((-width) / 2., (-width) / 2., 0., true, true, with_dir.initialPoint());
- break;
- case BUTT_SQUARE: {
- Geom::Point end_deriv = -input[0].unitTangentAt(0.);
- double radius = 0.5 * Geom::distance(against_dir.finalPoint(), with_dir.initialPoint());
- res.lineTo(against_dir.finalPoint() + end_deriv*radius);
- res.lineTo(with_dir.initialPoint() + end_deriv*radius);
- res.lineTo(with_dir.initialPoint());
- break;
- }
- case BUTT_PEAK: {
- Geom::Point end_deriv = -input[0].unitTangentAt(0.);
- double radius = 0.5 * Geom::distance(against_dir.finalPoint(), with_dir.initialPoint());
- Geom::Point midpoint = ((against_dir.finalPoint() + with_dir.initialPoint()) * 0.5) + end_deriv*radius;
- res.lineTo(midpoint);
- res.lineTo(with_dir.initialPoint());
- break;
- }
- case BUTT_FLAT:
- default:
- res.lineTo(with_dir.initialPoint());
- }
- res.closePath();
+ cf(res, against_dir, with_dir, width);
}
+ res.closePath();
res.flush();
return res.peek();
}
@@ -451,7 +709,8 @@ Geom::Path half_outline(Geom::Path const& input, double width, double miter, Lin
res.start(start);
// Do two curves at a time for efficiency, since the join function needs to know the outgoing curve as well
- const size_t k = input.size();
+ const size_t k = (input.back_closed().isDegenerate() && input.closed())
+ ?input.size_default()-1:input.size_default();
for (size_t u = 0; u < k; u += 2) {
temp = Geom::Path();
@@ -461,33 +720,39 @@ Geom::Path half_outline(Geom::Path const& input, double width, double miter, Lin
if (u == 0) {
res.append(temp);
} else {
- outline_helper(res, temp, width, miter, join);
+ bool on_outside = decide(input[u-1], input[u]);
+ outline_helper(res, temp, width, on_outside, miter, join);
+ if (temp.size() > 0)
+ res.insert(res.end(), ++temp.begin(), temp.end());
}
// odd number of paths
if (u < k - 1) {
temp = Geom::Path();
offset_curve(temp, &input[u+1], width);
- outline_helper(res, temp, width, miter, join);
+ bool on_outside = decide(input[u], input[u+1]);
+ outline_helper(res, temp, width, on_outside, miter, join);
+ if (temp.size() > 0)
+ res.insert(res.end(), ++temp.begin(), temp.end());
}
}
if (input.closed()) {
- Geom::Curve const &c1 = res[res.size()-1];
- Geom::Curve const &c2 = res[0];
+ Geom::Curve const &c1 = res.back();
+ Geom::Curve const &c2 = res.front();
temp = Geom::Path();
temp.append(c1);
Geom::Path temp2;
temp2.append(c2);
- outline_helper(temp, temp2, width, miter, join);
- temp.erase_last(); // we already outlined c2
- temp.erase(temp.begin()); // we already outlined c1
-
+ bool on_outside = decide(input.back(), input.front());
+ outline_helper(temp, temp2, width, on_outside, miter, join);
+ res.erase(res.begin());
+ res.erase_last();
//
res.append(temp);
+ res.close();
}
- res.close();
return res;
}