diff options
| author | Jabier Arraiza Cenoz <jabier.arraiza@marker.es> | 2015-04-10 22:54:49 +0000 |
|---|---|---|
| committer | Jabiertxof <jtx@jtx.marker.es> | 2015-04-10 22:54:49 +0000 |
| commit | 1df5d1b28c59070eeabeac749e2fad0ceb6a781d (patch) | |
| tree | 544be4e0eb7b62bc1fc5f62e2829ad996027acda /src/helper/geom-pathstroke.cpp | |
| parent | Fix coding style issues in transform by two points LPE (diff) | |
| parent | added info about multiple pen feature in gui (diff) | |
| download | inkscape-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.cpp | 535 |
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; } |
