diff options
| author | Krzysztof Kosi??ski <tweenk.pl@gmail.com> | 2015-04-27 23:39:29 +0000 |
|---|---|---|
| committer | Krzysztof Kosiński <tweenk.pl@gmail.com> | 2015-04-27 23:39:29 +0000 |
| commit | c883d7627a479c8c5b6a9f77b9841fa5631572ad (patch) | |
| tree | fba1186e26a8cc85a1b0728425bef6f2e9aeccd9 /src/2geom | |
| parent | extensions. ink2canvas.py - do not parse html comments. (Bug 1446204) (diff) | |
| download | inkscape-c883d7627a479c8c5b6a9f77b9841fa5631572ad.tar.gz inkscape-c883d7627a479c8c5b6a9f77b9841fa5631572ad.zip | |
2Geom sync - initial commit
(bzr r14059.2.1)
Diffstat (limited to 'src/2geom')
119 files changed, 12004 insertions, 6564 deletions
diff --git a/src/2geom/2geom.h b/src/2geom/2geom.h index 000f3423d..813e243b3 100644 --- a/src/2geom/2geom.h +++ b/src/2geom/2geom.h @@ -31,8 +31,8 @@ * the specific language governing rights and limitations. */ -#ifndef SEEN_LIB2GEOM_2GEOM_H -#define SEEN_LIB2GEOM_2GEOM_H +#ifndef LIB2GEOM_SEEN_2GEOM_H +#define LIB2GEOM_SEEN_2GEOM_H #include <2geom/forward.h> @@ -62,7 +62,7 @@ #include <2geom/math-utils.h> #include <2geom/utils.h> -#endif // SEEN_LIB2GEOM_HEADER_H +#endif // LIB2GEOM_SEEN_2GEOM_H /* Local Variables: mode:c++ diff --git a/src/2geom/CMakeLists.txt b/src/2geom/CMakeLists.txt index eeaecaa39..119c7aa71 100644 --- a/src/2geom/CMakeLists.txt +++ b/src/2geom/CMakeLists.txt @@ -2,23 +2,27 @@ set(2geom_SRC affine.cpp basic-intersection.cpp + bezier.cpp bezier-clipping.cpp bezier-curve.cpp bezier-utils.cpp + cairo-path-sink.cpp circle-circle.cpp circle.cpp # conic_section_clipper_impl.cpp # conicsec.cpp conjugate_gradient.cpp - convex-cover.cpp + convex-hull.cpp + coord.cpp crossing.cpp curve.cpp d2-sbasis.cpp ellipse.cpp elliptical-arc.cpp geom.cpp + intersection-graph.cpp line.cpp - nearest-point.cpp + nearest-time.cpp numeric/matrix.cpp path-intersection.cpp path-sink.cpp @@ -48,6 +52,7 @@ set(2geom_SRC toposweep.cpp transforms.cpp utils.cpp + viewbox.cpp # ------- diff --git a/src/2geom/Makefile_insert b/src/2geom/Makefile_insert index e77f413cb..86e64333d 100644 --- a/src/2geom/Makefile_insert +++ b/src/2geom/Makefile_insert @@ -15,10 +15,13 @@ 2geom/bezier-clipping.cpp \ 2geom/bezier-curve.cpp \ 2geom/bezier-curve.h \ + 2geom/bezier.cpp \ 2geom/bezier.h \ 2geom/bezier-to-sbasis.h \ 2geom/bezier-utils.cpp \ 2geom/bezier-utils.h \ + 2geom/cairo-path-sink.cpp \ + 2geom/cairo-path-sink.h \ 2geom/choose.h \ 2geom/circle-circle.cpp \ 2geom/circle.cpp \ @@ -32,10 +35,9 @@ 2geom/conic_section_clipper.h \ 2geom/conic_section_clipper_impl.cpp \ 2geom/conic_section_clipper_impl.h \ - 2geom/conjugate_gradient.cpp \ - 2geom/conjugate_gradient.h \ - 2geom/convex-cover.cpp \ - 2geom/convex-cover.h \ + 2geom/convex-hull.cpp \ + 2geom/convex-hull.h \ + 2geom/coord.cpp \ 2geom/coord.h \ 2geom/crossing.cpp \ 2geom/crossing.h \ @@ -56,6 +58,9 @@ 2geom/geom.cpp \ 2geom/geom.h \ 2geom/hvlinesegment.h \ + 2geom/intersection.h \ + 2geom/intersection-graph.cpp \ + 2geom/intersection-graph.h \ 2geom/interval.h \ 2geom/int-interval.h \ 2geom/int-point.h \ @@ -64,8 +69,8 @@ 2geom/line.cpp \ 2geom/line.h \ 2geom/math-utils.h \ - 2geom/nearest-point.cpp \ - 2geom/nearest-point.h \ + 2geom/nearest-time.cpp \ + 2geom/nearest-time.h \ 2geom/ord.h \ 2geom/path.cpp \ 2geom/path.h \ @@ -88,8 +93,6 @@ 2geom/rect.cpp \ 2geom/rect.h \ 2geom/recursive-bezier-intersection.cpp \ - 2geom/region.cpp \ - 2geom/region.h \ 2geom/sbasis-2d.cpp \ 2geom/sbasis-2d.h \ 2geom/sbasis.cpp \ @@ -104,8 +107,6 @@ 2geom/sbasis-roots.cpp \ 2geom/sbasis-to-bezier.cpp \ 2geom/sbasis-to-bezier.h \ - 2geom/shape.cpp \ - 2geom/shape.h \ 2geom/solve-bezier.cpp \ 2geom/solve-bezier-one-d.cpp \ 2geom/solve-bezier-parametric.cpp \ @@ -114,6 +115,8 @@ 2geom/svg-elliptical-arc.h \ 2geom/svg-path-parser.cpp \ 2geom/svg-path-parser.h \ + 2geom/svg-path-writer.cpp \ + 2geom/svg-path-writer.h \ 2geom/sweep.cpp \ 2geom/sweep.h \ 2geom/toposweep.cpp \ @@ -122,6 +125,8 @@ 2geom/transforms.h \ 2geom/utils.cpp \ 2geom/utils.h \ + 2geom/viewbox.cpp \ + 2geom/viewbox.h \ 2geom/numeric/fitting-model.h \ 2geom/numeric/fitting-tool.h \ 2geom/numeric/linear_system.h \ diff --git a/src/2geom/affine.cpp b/src/2geom/affine.cpp index 738d0fc48..e9ca5748e 100644 --- a/src/2geom/affine.cpp +++ b/src/2geom/affine.cpp @@ -222,6 +222,29 @@ bool Affine::isNonzeroRotation(Coord eps) const { are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps); } +/** @brief Check whether this matrix represents a non-zero rotation about any point. + * @param eps Numerical tolerance + * @return True iff the matrix is of the form + * \f$\left[\begin{array}{ccc} + a & b & 0 \\ + -b & a & 0 \\ + c & d & 1 \end{array}\right]\f$, \f$a^2 + b^2 = 1\f$ and \f$a \neq 1\f$. */ +bool Affine::isNonzeroNonpureRotation(Coord eps) const { + return !are_near(_c[0], 1.0, eps) && + are_near(_c[0], _c[3], eps) && are_near(_c[1], -_c[2], eps) && + are_near(_c[0]*_c[0] + _c[1]*_c[1], 1.0, eps); +} + +/** @brief For a (possibly non-pure) non-zero-rotation matrix, calculate the rotation center. + * @pre The matrix must be a non-zero-rotation matrix to prevent division by zero, see isNonzeroNonpureRotation(). + * @return The rotation center x, the solution to the equation + * \f$A x = x\f$. */ +Point Affine::rotationCenter() const { + Coord x = (_c[2]*_c[5]+_c[4]-_c[4]*_c[3]) / (1-_c[3]-_c[0]+_c[0]*_c[3]-_c[2]*_c[1]); + Coord y = (_c[1]*x + _c[5]) / (1 - _c[3]); + return Point(x,y); +}; + /** @brief Check whether this matrix represents pure horizontal shearing. * @param eps Numerical tolerance * @return True iff the matrix is of the form @@ -342,8 +365,7 @@ bool Affine::preservesDistances(Coord eps) const /** @brief Check whether this transformation flips objects. * A transformation flips objects if it has a negative scaling component. */ bool Affine::flips() const { - // TODO shouldn't this be det() < 0? - return cross(xAxis(), yAxis()) > 0; + return det() < 0; } /** @brief Check whether this matrix is singular. @@ -356,7 +378,7 @@ bool Affine::isSingular(Coord eps) const { } /** @brief Compute the inverse matrix. - * Inverse is a matrix (denoted \f$A^{-1}) such that \f$AA^{-1} = A^{-1}A = I\f$. + * Inverse is a matrix (denoted \f$A^{-1}\f$) such that \f$AA^{-1} = A^{-1}A = I\f$. * Singular matrices have no inverse (for example a matrix that has two of its columns equal). * For such matrices, the identity matrix will be returned instead. * @param eps Numerical tolerance @@ -369,7 +391,7 @@ Affine Affine::inverse() const { fabs(_c[2]) + fabs(_c[3])); // a random matrix norm (either l1 or linfty if(mx > 0) { Geom::Coord const determ = det(); - if (!rel_error_bound(determ, mx*mx)) { + if (!rel_error_bound(std::sqrt(fabs(determ)), mx)) { Geom::Coord const ideterm = 1.0 / (determ); d._c[0] = _c[3] * ideterm; diff --git a/src/2geom/affine.h b/src/2geom/affine.h index af7b39360..470d5fc40 100644 --- a/src/2geom/affine.h +++ b/src/2geom/affine.h @@ -11,8 +11,8 @@ * This code is in public domain. */ -#ifndef SEEN_LIB2GEOM_AFFINE_H -#define SEEN_LIB2GEOM_AFFINE_H +#ifndef LIB2GEOM_SEEN_AFFINE_H +#define LIB2GEOM_SEEN_AFFINE_H #include <boost/operators.hpp> #include <2geom/forward.h> @@ -150,6 +150,8 @@ public: bool isNonzeroScale(Coord eps = EPSILON) const; bool isNonzeroUniformScale(Coord eps = EPSILON) const; bool isNonzeroRotation(Coord eps = EPSILON) const; + bool isNonzeroNonpureRotation(Coord eps = EPSILON) const; + Point rotationCenter() const; bool isNonzeroHShear(Coord eps = EPSILON) const; bool isNonzeroVShear(Coord eps = EPSILON) const; diff --git a/src/2geom/angle.h b/src/2geom/angle.h index 1faf63c3f..77ecd5eb4 100644 --- a/src/2geom/angle.h +++ b/src/2geom/angle.h @@ -69,8 +69,8 @@ class Angle public: Angle() : _angle(0) {} //added default constructor because of cython Angle(Coord v) : _angle(v) { _normalize(); } // this can be called implicitly - explicit Angle(Point p) : _angle(atan2(p)) { _normalize(); } - Angle(Point a, Point b) : _angle(angle_between(a, b)) { _normalize(); } + explicit Angle(Point const &p) : _angle(atan2(p)) { _normalize(); } + Angle(Point const &a, Point const &b) : _angle(angle_between(a, b)) { _normalize(); } operator Coord() const { return radians(); } Angle &operator+=(Angle const &o) { _angle += o._angle; diff --git a/src/2geom/basic-intersection.cpp b/src/2geom/basic-intersection.cpp index 379ec597c..54374e282 100644 --- a/src/2geom/basic-intersection.cpp +++ b/src/2geom/basic-intersection.cpp @@ -1,3 +1,38 @@ +/** @file + * @brief Basic intersection routines + *//* + * Authors: + * Nathan Hurst <njh@njhurst.com> + * Marco Cecchetti <mrcekets at gmail.com> + * Jean-François Barraud <jf.barraud@gmail.com> + * + * Copyright 2008-2009 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + #include <2geom/basic-intersection.h> #include <2geom/sbasis-to-bezier.h> #include <2geom/exception.h> @@ -33,24 +68,35 @@ namespace Geom { //#else namespace detail{ namespace bezier_clipping { -void portion (std::vector<Point> & B, Interval const& I); +void portion(std::vector<Point> &B, Interval const &I); +void derivative(std::vector<Point> &D, std::vector<Point> const &B); }; }; void find_intersections(std::vector<std::pair<double, double> > &xs, + D2<Bezier> const & A, + D2<Bezier> const & B, + double precision) +{ + find_intersections_bezier_clipping(xs, bezier_points(A), bezier_points(B), precision); +} + +void find_intersections(std::vector<std::pair<double, double> > &xs, D2<SBasis> const & A, - D2<SBasis> const & B) { + D2<SBasis> const & B, + double precision) +{ vector<Point> BezA, BezB; sbasis_to_bezier(BezA, A); sbasis_to_bezier(BezB, B); - - xs.clear(); - find_intersections_bezier_clipping(xs, BezA, BezB); + find_intersections_bezier_clipping(xs, BezA, BezB, precision); } + void find_intersections(std::vector< std::pair<double, double> > & xs, - std::vector<Point> const& A, - std::vector<Point> const& B, - double precision){ + std::vector<Point> const& A, + std::vector<Point> const& B, + double precision) +{ find_intersections_bezier_clipping(xs, A, B, precision); } @@ -61,6 +107,7 @@ void find_intersections(std::vector< std::pair<double, double> > & xs, * Temporary storage is minimized by using part of the storage for the result * to hold an intermediate value until it is no longer needed. */ +// TODO replace with Bezier method void split(vector<Point> const &p, double t, vector<Point> &left, vector<Point> &right) { const unsigned sz = p.size(); @@ -88,44 +135,48 @@ void split(vector<Point> const &p, double t, } -void -find_self_intersections(std::vector<std::pair<double, double> > &xs, - D2<SBasis> const & A) { - vector<double> dr = roots(derivative(A[X])); + +void find_self_intersections(std::vector<std::pair<double, double> > &xs, + D2<Bezier> const &A, + double precision) +{ + std::vector<double> dr = derivative(A[X]).roots(); { - vector<double> dyr = roots(derivative(A[Y])); + std::vector<double> dyr = derivative(A[Y]).roots(); dr.insert(dr.begin(), dyr.begin(), dyr.end()); } dr.push_back(0); dr.push_back(1); // We want to be sure that we have no empty segments - sort(dr.begin(), dr.end()); - vector<double>::iterator new_end = unique(dr.begin(), dr.end()); + std::sort(dr.begin(), dr.end()); + std::vector<double>::iterator new_end = std::unique(dr.begin(), dr.end()); dr.resize( new_end - dr.begin() ); - vector<vector<Point> > pieces; - { - vector<Point> in, l, r; - sbasis_to_bezier(in, A); + std::vector< D2<Bezier> > pieces; + for (unsigned i = 0; i < dr.size() - 1; ++i) { + pieces.push_back(portion(A, dr[i], dr[i+1])); + } + /*{ + vector<Point> l, r, in = A; for(unsigned i = 0; i < dr.size()-1; i++) { split(in, (dr[i+1]-dr[i]) / (1 - dr[i]), l, r); pieces.push_back(l); in = r; } - } + }*/ for(unsigned i = 0; i < dr.size()-1; i++) { for(unsigned j = i+1; j < dr.size()-1; j++) { std::vector<std::pair<double, double> > section; - find_intersections( section, pieces[i], pieces[j]); + find_intersections(section, pieces[i], pieces[j], precision); for(unsigned k = 0; k < section.size(); k++) { double l = section[k].first; double r = section[k].second; // XXX: This condition will prune out false positives, but it might create some false negatives. Todo: Confirm it is correct. if(j == i+1) //if((l == 1) && (r == 0)) - if( ( l > 1-1e-4 ) && (r < 1e-4) )//FIXME: what precision should be used here??? + if( ( l > precision ) && (r < precision) )//FIXME: what precision should be used here??? continue; xs.push_back(std::make_pair((1-l)*dr[i] + l*dr[i+1], (1-r)*dr[j] + r*dr[j+1])); @@ -138,6 +189,46 @@ find_self_intersections(std::vector<std::pair<double, double> > &xs, //unique(xs.begin(), xs.end()); } +void find_self_intersections(std::vector<std::pair<double, double> > &xs, + D2<SBasis> const &A, + double precision) +{ + D2<Bezier> in; + sbasis_to_bezier(in, A); + find_self_intersections(xs, in, precision); +} + + +void subdivide(D2<Bezier> const &a, + D2<Bezier> const &b, + std::vector< std::pair<double, double> > const &xs, + std::vector< D2<Bezier> > &av, + std::vector< D2<Bezier> > &bv) +{ + if (xs.empty()) { + av.push_back(a); + bv.push_back(b); + return; + } + + std::pair<double, double> prev = std::make_pair(0., 0.); + for (unsigned i = 0; i < xs.size(); ++i) { + av.push_back(portion(a, prev.first, xs[i].first)); + bv.push_back(portion(b, prev.second, xs[i].second)); + av.back()[X].at0() = bv.back()[X].at0() = lerp(0.5, av.back()[X].at0(), bv.back()[X].at0()); + av.back()[X].at1() = bv.back()[X].at1() = lerp(0.5, av.back()[X].at1(), bv.back()[X].at1()); + av.back()[Y].at0() = bv.back()[Y].at0() = lerp(0.5, av.back()[Y].at0(), bv.back()[Y].at0()); + av.back()[Y].at1() = bv.back()[Y].at1() = lerp(0.5, av.back()[Y].at1(), bv.back()[Y].at1()); + prev = xs[i]; + } + av.push_back(portion(a, prev.first, 1)); + bv.push_back(portion(b, prev.second, 1)); + av.back()[X].at0() = bv.back()[X].at0() = lerp(0.5, av.back()[X].at0(), bv.back()[X].at0()); + av.back()[X].at1() = bv.back()[X].at1() = lerp(0.5, av.back()[X].at1(), bv.back()[X].at1()); + av.back()[Y].at0() = bv.back()[Y].at0() = lerp(0.5, av.back()[Y].at0(), bv.back()[Y].at0()); + av.back()[Y].at1() = bv.back()[Y].at1() = lerp(0.5, av.back()[Y].at1(), bv.back()[Y].at1()); +} + #ifdef HAVE_GSL #include <gsl/gsl_multiroots.h> @@ -304,38 +395,6 @@ void polish_intersections(std::vector<std::pair<double, double> > &xs, B, xs[i].second); } - - /** - * Compute the Hausdorf distance from A to B only. - */ - - -#if 0 -/** Compute the value of a bezier - Todo: find a good palce for this. - */ -// suggested by Sederberg. -Point OldBezier::operator()(double t) const { - int n = p.size()-1; - double u, bc, tn, tmp; - int i; - Point r; - for(int dim = 0; dim < 2; dim++) { - u = 1.0 - t; - bc = 1; - tn = 1; - tmp = p[0][dim]*u; - for(i=1; i<n; i++){ - tn = tn*t; - bc = bc*(n-i+1)/i; - tmp = (tmp + tn*bc*p[i][dim])*u; - } - r[dim] = (tmp + tn*t*p[n][dim]); - } - return r; -} -#endif - /** * Compute the Hausdorf distance from A to B only. */ @@ -350,7 +409,7 @@ double hausdorfl(D2<SBasis>& A, D2<SBasis> const& B, double h_dist = 0, h_a_t = 0, h_b_t = 0; double dist = 0; Point Ax = A.at0(); - double t = Geom::nearest_point(Ax, B); + double t = Geom::nearest_time(Ax, B); dist = Geom::distance(Ax, B(t)); if (dist > h_dist) { h_a_t = 0; @@ -358,7 +417,7 @@ double hausdorfl(D2<SBasis>& A, D2<SBasis> const& B, h_dist = dist; } Ax = A.at1(); - t = Geom::nearest_point(Ax, B); + t = Geom::nearest_time(Ax, B); dist = Geom::distance(Ax, B(t)); if (dist > h_dist) { h_a_t = 1; @@ -370,7 +429,7 @@ double hausdorfl(D2<SBasis>& A, D2<SBasis> const& B, Point At = A(xs[i].first); Point Bu = B(xs[i].second); double distAtBu = Geom::distance(At, Bu); - t = Geom::nearest_point(At, B); + t = Geom::nearest_time(At, B); dist = Geom::distance(At, B(t)); //FIXME: we might miss it due to floating point precision... if (dist >= distAtBu-.1 && distAtBu > h_dist) { @@ -396,7 +455,7 @@ double hausdorf(D2<SBasis>& A, D2<SBasis> const& B, double dist = 0; Point Bx = B.at0(); - double t = Geom::nearest_point(Bx, A); + double t = Geom::nearest_time(Bx, A); dist = Geom::distance(Bx, A(t)); if (dist > h_dist) { if(a_t) *a_t = t; @@ -404,7 +463,7 @@ double hausdorf(D2<SBasis>& A, D2<SBasis> const& B, h_dist = dist; } Bx = B.at1(); - t = Geom::nearest_point(Bx, A); + t = Geom::nearest_time(Bx, A); dist = Geom::distance(Bx, A(t)); if (dist > h_dist) { if(a_t) *a_t = t; diff --git a/src/2geom/basic-intersection.h b/src/2geom/basic-intersection.h index 5a813ae99..0b0bf8930 100644 --- a/src/2geom/basic-intersection.h +++ b/src/2geom/basic-intersection.h @@ -1,11 +1,12 @@ -/** - * \file - * \brief Basic intersection routines - * +/** @file + * @brief Basic intersection routines + *//* * Authors: - * ? <?@?.?> + * Nathan Hurst <njh@njhurst.com> + * Marco Cecchetti <mrcekets at gmail.com> + * Jean-François Barraud <jf.barraud@gmail.com> * - * Copyright ?-? authors + * Copyright 2008-2009 Authors * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -32,10 +33,11 @@ * */ -#ifndef SEEN_GEOM_BASICINTERSECTION_H -#define SEEN_GEOM_BASICINTERSECTION_H +#ifndef LIB2GEOM_SEEN_BASIC_INTERSECTION_H +#define LIB2GEOM_SEEN_BASIC_INTERSECTION_H #include <2geom/point.h> +#include <2geom/bezier.h> #include <2geom/sbasis.h> #include <2geom/d2.h> @@ -47,41 +49,29 @@ namespace Geom { -//why not allowing precision to be set here? -void find_intersections(std::vector<std::pair<double, double> >& xs, - D2<SBasis> const & A, - D2<SBasis> const & B); +void find_intersections(std::vector<std::pair<double, double> > &xs, + D2<Bezier> const &A, + D2<Bezier> const &B, + double precision = EPSILON); + +void find_intersections(std::vector<std::pair<double, double> > &xs, + D2<SBasis> const &A, + D2<SBasis> const &B, + double precision = EPSILON); + +void find_intersections(std::vector< std::pair<double, double> > &xs, + std::vector<Point> const &A, + std::vector<Point> const &B, + double precision = EPSILON); + +void find_self_intersections(std::vector<std::pair<double, double> > &xs, + D2<SBasis> const &A, + double precision = EPSILON); + +void find_self_intersections(std::vector<std::pair<double, double> > &xs, + D2<Bezier> const &A, + double precision = EPSILON); -void find_intersections(std::vector< std::pair<double, double> > & xs, - std::vector<Point> const& A, - std::vector<Point> const& B, - double precision = 1e-5); - -//why not allowing precision to be set here? -void find_self_intersections(std::vector<std::pair<double, double> >& xs, - D2<SBasis> const & A); - - -//--not implemented -//void find_self_intersections(std::vector<std::pair<double, double> >& xs, -// std::vector<Point> const & A); - - -//TODO: this should be moved to .cpp, shouldn't it? -// #ifdef USE_RECURSIVE_INTERSECTOR -// /* -// * find_intersection -// * -// * input: A, B - set of control points of two Bezier curve -// * input: precision - required precision of computation -// * output: xs - set of pairs of parameter values -// * at which crossing happens -// */ -// void find_intersections_bezier_recursive (std::vector< std::pair<double, double> > & xs, -// std::vector<Point> const& A, -// std::vector<Point> const& B, -// double precision = 1e-5); -// #else /* * find_intersection * @@ -96,10 +86,14 @@ void find_self_intersections(std::vector<std::pair<double, double> >& xs, void find_intersections_bezier_clipping (std::vector< std::pair<double, double> > & xs, std::vector<Point> const& A, std::vector<Point> const& B, - double precision = 1e-5); + double precision = EPSILON); //#endif - +void subdivide(D2<Bezier> const &a, + D2<Bezier> const &b, + std::vector< std::pair<double, double> > const &xs, + std::vector< D2<Bezier> > &av, + std::vector< D2<Bezier> > &bv); /* * find_collinear_normal @@ -115,7 +109,7 @@ void find_intersections_bezier_clipping (std::vector< std::pair<double, double> void find_collinear_normal (std::vector< std::pair<double, double> >& xs, std::vector<Point> const& A, std::vector<Point> const& B, - double precision = 1e-5); + double precision = EPSILON); void polish_intersections(std::vector<std::pair<double, double> > &xs, D2<SBasis> const &A, @@ -125,19 +119,19 @@ void polish_intersections(std::vector<std::pair<double, double> > &xs, /** * Compute the Hausdorf distance from A to B only. */ -double hausdorfl(D2<SBasis>& A, D2<SBasis> const& B, +double hausdorfl(D2<SBasis>& A, D2<SBasis> const &B, double m_precision, - double *a_t=0, double* b_t=0); + double *a_t=NULL, double *b_t=NULL); /** * Compute the symmetric Hausdorf distance. */ -double hausdorf(D2<SBasis>& A, D2<SBasis> const& B, +double hausdorf(D2<SBasis> &A, D2<SBasis> const &B, double m_precision, - double *a_t=0, double* b_t=0); + double *a_t=NULL, double *b_t=NULL); } -#endif // !SEEN_GEOM_BASICINTERSECTION_H +#endif // !LIB2GEOM_SEEN_BASIC_INTERSECTION_H /* Local Variables: diff --git a/src/2geom/bezier-clipping.cpp b/src/2geom/bezier-clipping.cpp index 9a055204f..be8dd5a5f 100644 --- a/src/2geom/bezier-clipping.cpp +++ b/src/2geom/bezier-clipping.cpp @@ -39,8 +39,9 @@ #include <2geom/point.h> #include <2geom/interval.h> #include <2geom/bezier.h> -//#include <2geom/convex-cover.h> #include <2geom/numeric/matrix.h> +#include <2geom/convex-hull.h> +#include <2geom/line.h> #include <cassert> #include <vector> @@ -48,7 +49,7 @@ #include <utility> //#include <iomanip> - +using std::swap; #define VERBOSE 0 @@ -63,7 +64,6 @@ namespace detail { namespace bezier_clipping { // for debugging // -inline void print(std::vector<Point> const& cp, const char* msg = "") { std::cerr << msg << std::endl; @@ -72,7 +72,6 @@ void print(std::vector<Point> const& cp, const char* msg = "") } template< class charT > -inline std::basic_ostream<charT> & operator<< (std::basic_ostream<charT> & os, const Interval & I) { @@ -80,7 +79,6 @@ operator<< (std::basic_ostream<charT> & os, const Interval & I) return os; } -inline double angle (std::vector<Point> const& A) { size_t n = A.size() -1; @@ -88,7 +86,6 @@ double angle (std::vector<Point> const& A) return (180 * a / M_PI); } -inline size_t get_precision(Interval const& I) { double d = I.extent(); @@ -103,7 +100,6 @@ size_t get_precision(Interval const& I) return n; } -inline void range_assertion(int k, int m, int n, const char* msg) { if ( k < m || k > n) @@ -118,92 +114,11 @@ void range_assertion(int k, int m, int n, const char* msg) //////////////////////////////////////////////////////////////////////////////// -// convex hull - -/* - * return true in case the oriented polyline p0, p1, p2 is a right turn - */ -inline -bool is_a_right_turn (Point const& p0, Point const& p1, Point const& p2) -{ - if (p1 == p2) return false; - Point q1 = p1 - p0; - Point q2 = p2 - p0; - if (q1 == -q2) return false; - return (cross (q1, q2) < 0); -} - -/* - * return true if p < q wrt the lexicographyc order induced by the coordinates - */ -struct lex_less -{ - bool operator() (Point const& p, Point const& q) - { - return ((p[X] < q[X]) || (p[X] == q[X] && p[Y] < q[Y])); - } -}; - -/* - * return true if p > q wrt the lexicographyc order induced by the coordinates - */ -struct lex_greater -{ - bool operator() (Point const& p, Point const& q) - { - return ((p[X] > q[X]) || (p[X] == q[X] && p[Y] > q[Y])); - } -}; - -/* - * Compute the convex hull of a set of points. - * The implementation is based on the Andrew's scan algorithm - * note: in the Bezier clipping for collinear normals it seems - * to be more stable wrt the Graham's scan algorithm and in general - * a bit quikier - */ -void convex_hull (std::vector<Point> & P) -{ - size_t n = P.size(); - if (n < 2) return; - std::sort(P.begin(), P.end(), lex_less()); - if (n < 4) return; - // upper hull - size_t u = 2; - for (size_t i = 2; i < n; ++i) - { - while (u > 1 && !is_a_right_turn(P[u-2], P[u-1], P[i])) - { - --u; - } - std::swap(P[u], P[i]); - ++u; - } - std::sort(P.begin() + u, P.end(), lex_greater()); - std::rotate(P.begin(), P.begin() + 1, P.end()); - // lower hull - size_t l = u; - size_t k = u - 1; - for (size_t i = l; i < n; ++i) - { - while (l > k && !is_a_right_turn(P[l-2], P[l-1], P[i])) - { - --l; - } - std::swap(P[l], P[i]); - ++l; - } - P.resize(l); -} - - -//////////////////////////////////////////////////////////////////////////////// // numerical routines /* * Compute the binomial coefficient (n, k) */ -inline double binomial(unsigned int n, unsigned int k) { return choose<double>(n, k); @@ -212,7 +127,6 @@ double binomial(unsigned int n, unsigned int k) /* * Compute the determinant of the 2x2 matrix with column the point P1, P2 */ -inline double det(Point const& P1, Point const& P2) { return P1[X]*P2[Y] - P1[Y]*P2[X]; @@ -222,7 +136,6 @@ double det(Point const& P1, Point const& P2) * Solve the linear system [P1,P2] * P = Q * in case there isn't exactly one solution the routine returns false */ -inline bool solve(Point & P, Point const& P1, Point const& P2, Point const& Q) { double d = det(P1, P2); @@ -239,27 +152,11 @@ bool solve(Point & P, Point const& P1, Point const& P2, Point const& Q) /* * Map the sub-interval I in [0,1] into the interval J and assign it to J */ -inline void map_to(Interval & J, Interval const& I) { - double length = J.extent(); - J[1] = I.max() * length + J[0]; - J[0] = I.min() * length + J[0]; + J.setEnds(J.valueAt(I.min()), J.valueAt(I.max())); } -/* - * The interval [1,0] is used to represent the empty interval, this routine - * is just an helper function for creating such an interval - */ -inline -Interval make_empty_interval() -{ - Interval I(0); - I[0] = 1; - return I; -} - - //////////////////////////////////////////////////////////////////////////////// // bezier curve routines @@ -267,8 +164,8 @@ Interval make_empty_interval() * Return true if all the Bezier curve control points are near, * false otherwise */ -inline -bool is_constant(std::vector<Point> const& A, double precision = EPSILON) +// Bezier.isConstant(precision) +bool is_constant(std::vector<Point> const& A, double precision) { for (unsigned int i = 1; i < A.size(); ++i) { @@ -281,7 +178,7 @@ bool is_constant(std::vector<Point> const& A, double precision = EPSILON) /* * Compute the hodograph of the bezier curve B and return it in D */ -inline +// derivative(Bezier) void derivative(std::vector<Point> & D, std::vector<Point> const& B) { D.clear(); @@ -304,7 +201,7 @@ void derivative(std::vector<Point> & D, std::vector<Point> const& B) * Compute the hodograph of the Bezier curve B rotated of 90 degree * and return it in D; we have N(t) orthogonal to B(t) for any t */ -inline +// rot90(derivative(Bezier)) void normal(std::vector<Point> & N, std::vector<Point> const& B) { derivative(N,B); @@ -317,7 +214,7 @@ void normal(std::vector<Point> & N, std::vector<Point> const& B) /* * Compute the portion of the Bezier curve "B" wrt the interval [0,t] */ -inline +// portion(Bezier, 0, t) void left_portion(Coord t, std::vector<Point> & B) { size_t n = B.size(); @@ -333,7 +230,7 @@ void left_portion(Coord t, std::vector<Point> & B) /* * Compute the portion of the Bezier curve "B" wrt the interval [t,1] */ -inline +// portion(Bezier, t, 1) void right_portion(Coord t, std::vector<Point> & B) { size_t n = B.size(); @@ -349,7 +246,7 @@ void right_portion(Coord t, std::vector<Point> & B) /* * Compute the portion of the Bezier curve "B" wrt the interval "I" */ -inline +// portion(Bezier, I) void portion (std::vector<Point> & B , Interval const& I) { if (I.min() == 0) @@ -371,9 +268,9 @@ void portion (std::vector<Point> & B , Interval const& I) struct intersection_point_tag; struct collinear_normal_tag; template <typename Tag> -void clip(Interval & dom, - std::vector<Point> const& A, - std::vector<Point> const& B); +OptInterval clip(std::vector<Point> const& A, + std::vector<Point> const& B, + double precision); template <typename Tag> void iterate(std::vector<Interval>& domsA, std::vector<Interval>& domsB, @@ -392,14 +289,14 @@ void iterate(std::vector<Interval>& domsA, * the line is returned in the output parameter "l" in the form of a 3 element * vector : l[0] * x + l[1] * y + l[2] == 0; the line is normalized. */ -inline +// Line(c[i], c[j]) void orientation_line (std::vector<double> & l, std::vector<Point> const& c, size_t i, size_t j) { l[0] = c[j][Y] - c[i][Y]; l[1] = c[i][X] - c[j][X]; - l[2] = cross(c[i], c[j]); + l[2] = cross(c[j], c[i]); double length = std::sqrt(l[0] * l[0] + l[1] * l[1]); assert (length != 0); l[0] /= length; @@ -411,22 +308,20 @@ void orientation_line (std::vector<double> & l, * Pick up an orientation line for the Bezier curve "c" and return it in * the output parameter "l" */ -inline -void pick_orientation_line (std::vector<double> & l, - std::vector<Point> const& c) +Line pick_orientation_line (std::vector<Point> const &c, double precision) { size_t i = c.size(); - while (--i > 0 && are_near(c[0], c[i])) + while (--i > 0 && are_near(c[0], c[i], precision)) {} - if (i == 0) - { - // this should never happen because when a new curve portion is created - // we check that it is not constant; - // however this requires that the precision used in the is_constant - // routine has to be the same used here in the are_near test - assert(i != 0); - } - orientation_line(l, c, 0, i); + + // this should never happen because when a new curve portion is created + // we check that it is not constant; + // however this requires that the precision used in the is_constant + // routine has to be the same used here in the are_near test + assert(i != 0); + + Line line(c[0], c[i]); + return line; //std::cerr << "i = " << i << std::endl; } @@ -436,29 +331,25 @@ void pick_orientation_line (std::vector<double> & l, * the line is returned in the output parameter "l" in the form of a 3 element * vector : l[0] * x + l[1] * y + l[2] == 0; the line is normalized. */ -inline -void orthogonal_orientation_line (std::vector<double> & l, - std::vector<Point> const& c, - Point const& p) +Line orthogonal_orientation_line (std::vector<Point> const &c, + Point const &p, + double precision) { - if (is_constant(c)) - { - // this should never happen - assert(!is_constant(c)); - } - std::vector<Point> ol(2); - ol[0] = p; - ol[1] = (c.back() - c.front()).cw() + p; - orientation_line(l, ol, 0, 1); + // this should never happen + assert(!is_constant(c, precision)); + + Line line(p, (c.back() - c.front()).cw() + p); + return line; } /* * Compute the signed distance of the point "P" from the normalized line l */ -inline -double distance (Point const& P, std::vector<double> const& l) +double signed_distance(Point const &p, Line const &l) { - return l[X] * P[X] + l[Y] * P[Y] + l[2]; + Coord a, b, c; + l.coefficients(a, b, c); + return a * p[X] + b * p[Y] + c; } /* @@ -466,26 +357,20 @@ double distance (Point const& P, std::vector<double> const& l) * curve "c" from the normalized orientation line "l". * This bounds are returned through the output Interval parameter"bound". */ -inline -void fat_line_bounds (Interval& bound, - std::vector<Point> const& c, - std::vector<double> const& l) +Interval fat_line_bounds (std::vector<Point> const &c, + Line const &l) { - bound[0] = 0; - bound[1] = 0; - for (size_t i = 0; i < c.size(); ++i) - { - const double d = distance(c[i], l); - if (bound[0] > d) bound[0] = d; - if (bound[1] < d) bound[1] = d; + Interval bound(0, 0); + for (size_t i = 0; i < c.size(); ++i) { + bound.expandTo(signed_distance(c[i], l)); } + return bound; } /* * return the x component of the intersection point between the line * passing through points p1, p2 and the line Y = "y" */ -inline double intersect (Point const& p1, Point const& p2, double y) { // we are sure that p2[Y] != p1[Y] because this routine is called @@ -500,23 +385,22 @@ double intersect (Point const& p1, Point const& p2, double y) * line "l" and the interval range "bound", the new parameter interval for * the clipped curve is returned through the output parameter "dom" */ -void clip_interval (Interval& dom, - std::vector<Point> const& B, - std::vector<double> const& l, - Interval const& bound) +OptInterval clip_interval (std::vector<Point> const& B, + Line const &l, + Interval const &bound) { double n = B.size() - 1; // number of sub-intervals std::vector<Point> D; // distance curve control points D.reserve (B.size()); for (size_t i = 0; i < B.size(); ++i) { - const double d = distance (B[i], l); + const double d = signed_distance(B[i], l); D.push_back (Point(i/n, d)); } //print(D); - convex_hull(D); - std::vector<Point> & p = D; + ConvexHull p; + p.swap(D); //print(p); bool plower, phigher; @@ -589,8 +473,11 @@ void clip_interval (Interval& dom, // << " : tmin = " << tmin << ", tmax = " << tmax << std::endl; } - dom[0] = tmin; - dom[1] = tmax; + if (tmin == 1 && tmax == 0) { + return OptInterval(); + } else { + return Interval(tmin, tmax); + } } /* @@ -599,24 +486,20 @@ void clip_interval (Interval& dom, * is returned through the output parameter "dom" */ template <> -inline -void clip<intersection_point_tag> (Interval & dom, - std::vector<Point> const& A, - std::vector<Point> const& B) +OptInterval clip<intersection_point_tag> (std::vector<Point> const& A, + std::vector<Point> const& B, + double precision) { - std::vector<double> bl(3); - Interval bound; - if (is_constant(A)) - { + Line bl; + if (is_constant(A, precision)) { Point M = middle_point(A.front(), A.back()); - orthogonal_orientation_line(bl, B, M); + bl = orthogonal_orientation_line(B, M, precision); + } else { + bl = pick_orientation_line(A, precision); } - else - { - pick_orientation_line(bl, A); - } - fat_line_bounds(bound, A, bl); - clip_interval(dom, B, bl, bound); + bl.normalize(); + Interval bound = fat_line_bounds(A, bl); + return clip_interval(B, bl, bound); } @@ -627,7 +510,6 @@ void clip<intersection_point_tag> (Interval & dom, * Compute a closed focus for the Bezier curve B and return it in F * A focus is any curve through which all lines perpendicular to B(t) pass. */ -inline void make_focus (std::vector<Point> & F, std::vector<Point> const& B) { assert (B.size() > 2); @@ -743,9 +625,8 @@ void distance_control_points (std::vector<Point> & D, * Clip the Bezier curve "B" wrt the focus "F"; the new parameter interval for * the clipped curve is returned through the output parameter "dom" */ -void clip_interval (Interval& dom, - std::vector<Point> const& B, - std::vector<Point> const& F) +OptInterval clip_interval (std::vector<Point> const& B, + std::vector<Point> const& F) { std::vector<Point> D; // distance curve control points distance_control_points(D, B, F); @@ -753,8 +634,8 @@ void clip_interval (Interval& dom, // ConvexHull chD(D); // std::vector<Point>& p = chD.boundary; // convex hull vertices - convex_hull(D); - std::vector<Point> & p = D; + ConvexHull p; + p.swap(D); //print(p, "CH(D)"); bool plower, clower; @@ -803,8 +684,11 @@ void clip_interval (Interval& dom, // std::cerr << "0 : lower " << p[0] // << " : tmin = " << tmin << ", tmax = " << tmax << std::endl; } - dom[0] = tmin; - dom[1] = tmax; + if (tmin == 1 && tmax == 0) { + return OptInterval(); + } else { + return Interval(tmin, tmax); + } } /* @@ -813,14 +697,13 @@ void clip_interval (Interval& dom, * for the clipped curve is returned through the output parameter "dom" */ template <> -inline -void clip<collinear_normal_tag> (Interval & dom, - std::vector<Point> const& A, - std::vector<Point> const& B) +OptInterval clip<collinear_normal_tag> (std::vector<Point> const& A, + std::vector<Point> const& B, + double /*precision*/) { std::vector<Point> F; make_focus(F, A); - clip_interval(dom, B, F); + return clip_interval(B, F); } @@ -828,9 +711,9 @@ void clip<collinear_normal_tag> (Interval & dom, const double MAX_PRECISION = 1e-8; const double MIN_CLIPPED_SIZE_THRESHOLD = 0.8; const Interval UNIT_INTERVAL(0,1); -const Interval EMPTY_INTERVAL = make_empty_interval(); +const OptInterval EMPTY_INTERVAL; const Interval H1_INTERVAL(0, 0.5); -const Interval H2_INTERVAL(0.5 + MAX_PRECISION, 1.0); +const Interval H2_INTERVAL(nextafter(0.5, 1.0), 1.0); /* * iterate @@ -884,9 +767,9 @@ void iterate<intersection_point_tag> (std::vector<Interval>& domsA, Interval* dom1 = &dompA; Interval* dom2 = &dompB; - Interval dom; + OptInterval dom; - if ( is_constant(A) && is_constant(B) ){ + if ( is_constant(A, precision) && is_constant(B, precision) ){ Point M1 = middle_point(C1->front(), C1->back()); Point M2 = middle_point(C2->front(), C2->back()); if (are_near(M1,M2)){ @@ -903,10 +786,9 @@ void iterate<intersection_point_tag> (std::vector<Interval>& domsA, #if VERBOSE std::cerr << "iter: " << iter << std::endl; #endif - clip<intersection_point_tag>(dom, *C1, *C2); + dom = clip<intersection_point_tag>(*C1, *C2, precision); - // [1,0] is utilized to represent an empty interval - if (dom == EMPTY_INTERVAL) + if (dom.isEmpty()) { #if VERBOSE std::cerr << "dom: empty" << std::endl; @@ -917,15 +799,12 @@ void iterate<intersection_point_tag> (std::vector<Interval>& domsA, std::cerr << "dom : " << dom << std::endl; #endif // all other cases where dom[0] > dom[1] are invalid - if (dom.min() > dom.max()) - { - assert(dom.min() < dom.max()); - } + assert(dom->min() <= dom->max()); - map_to(*dom2, dom); + map_to(*dom2, *dom); - portion(*C2, dom); - if (is_constant(*C2) && is_constant(*C1)) + portion(*C2, *dom); + if (is_constant(*C2, precision) && is_constant(*C1, precision)) { Point M1 = middle_point(C1->front(), C1->back()); Point M2 = middle_point(C2->front(), C2->back()); @@ -945,10 +824,10 @@ void iterate<intersection_point_tag> (std::vector<Interval>& domsA, // if we have clipped less than 20% than we need to subdive the curve // with the largest domain into two sub-curves - if ( dom.extent() > MIN_CLIPPED_SIZE_THRESHOLD) + if (dom->extent() > MIN_CLIPPED_SIZE_THRESHOLD) { #if VERBOSE - std::cerr << "clipped less than 20% : " << dom.extent() << std::endl; + std::cerr << "clipped less than 20% : " << dom->extent() << std::endl; std::cerr << "angle(pA) : " << angle(pA) << std::endl; std::cerr << "angle(pB) : " << angle(pB) << std::endl; #endif @@ -983,8 +862,8 @@ void iterate<intersection_point_tag> (std::vector<Interval>& domsA, return; } - std::swap(C1, C2); - std::swap(dom1, dom2); + swap(C1, C2); + swap(dom1, dom2); #if VERBOSE std::cerr << "dom(pA) : " << dompA << std::endl; std::cerr << "dom(pB) : " << dompB << std::endl; @@ -1047,7 +926,7 @@ void iterate<collinear_normal_tag> (std::vector<Interval>& domsA, Interval* dom1 = &dompA; Interval* dom2 = &dompB; - Interval dom; + OptInterval dom; size_t iter = 0; while (++iter < 100 @@ -1056,11 +935,9 @@ void iterate<collinear_normal_tag> (std::vector<Interval>& domsA, #if VERBOSE std::cerr << "iter: " << iter << std::endl; #endif - clip<collinear_normal_tag>(dom, *C1, *C2); + dom = clip<collinear_normal_tag>(*C1, *C2, precision); - // [1,0] is utilized to represent an empty interval - if (dom == EMPTY_INTERVAL) - { + if (dom.isEmpty()) { #if VERBOSE std::cerr << "dom: empty" << std::endl; #endif @@ -1069,13 +946,9 @@ void iterate<collinear_normal_tag> (std::vector<Interval>& domsA, #if VERBOSE std::cerr << "dom : " << dom << std::endl; #endif - // all other cases where dom[0] > dom[1] are invalid - if (dom.min() > dom.max()) - { - assert(dom.min() < dom.max()); - } + assert(dom->min() <= dom->max()); - map_to(*dom2, dom); + map_to(*dom2, *dom); // it's better to stop before losing computational precision if (iter > 1 && (dom2->extent() <= MAX_PRECISION)) @@ -1086,8 +959,8 @@ void iterate<collinear_normal_tag> (std::vector<Interval>& domsA, break; } - portion(*C2, dom); - if (iter > 1 && is_constant(*C2)) + portion(*C2, *dom); + if (iter > 1 && is_constant(*C2, precision)) { #if VERBOSE std::cerr << "new curve portion pC1 is constant" << std::endl; @@ -1098,10 +971,10 @@ void iterate<collinear_normal_tag> (std::vector<Interval>& domsA, // if we have clipped less than 20% than we need to subdive the curve // with the largest domain into two sub-curves - if ( dom.extent() > MIN_CLIPPED_SIZE_THRESHOLD) + if ( dom->extent() > MIN_CLIPPED_SIZE_THRESHOLD) { #if VERBOSE - std::cerr << "clipped less than 20% : " << dom.extent() << std::endl; + std::cerr << "clipped less than 20% : " << dom->extent() << std::endl; std::cerr << "angle(pA) : " << angle(pA) << std::endl; std::cerr << "angle(pB) : " << angle(pB) << std::endl; #endif @@ -1115,7 +988,7 @@ void iterate<collinear_normal_tag> (std::vector<Interval>& domsA, } pC1 = pC2 = pA; portion(pC1, H1_INTERVAL); - if (false && is_constant(pC1)) + if (false && is_constant(pC1, precision)) { #if VERBOSE std::cerr << "new curve portion pC1 is constant" << std::endl; @@ -1123,7 +996,7 @@ void iterate<collinear_normal_tag> (std::vector<Interval>& domsA, break; } portion(pC2, H2_INTERVAL); - if (is_constant(pC2)) + if (is_constant(pC2, precision)) { #if VERBOSE std::cerr << "new curve portion pC2 is constant" << std::endl; @@ -1146,7 +1019,7 @@ void iterate<collinear_normal_tag> (std::vector<Interval>& domsA, } pC1 = pC2 = pB; portion(pC1, H1_INTERVAL); - if (is_constant(pC1)) + if (is_constant(pC1, precision)) { #if VERBOSE std::cerr << "new curve portion pC1 is constant" << std::endl; @@ -1154,7 +1027,7 @@ void iterate<collinear_normal_tag> (std::vector<Interval>& domsA, break; } portion(pC2, H2_INTERVAL); - if (is_constant(pC2)) + if (is_constant(pC2, precision)) { #if VERBOSE std::cerr << "new curve portion pC2 is constant" << std::endl; @@ -1172,8 +1045,8 @@ void iterate<collinear_normal_tag> (std::vector<Interval>& domsA, return; } - std::swap(C1, C2); - std::swap(dom1, dom2); + swap(C1, C2); + swap(dom1, dom2); #if VERBOSE std::cerr << "dom(pA) : " << dompA << std::endl; std::cerr << "dom(pB) : " << dompB << std::endl; diff --git a/src/2geom/bezier-curve.cpp b/src/2geom/bezier-curve.cpp index 6dfb0f0b3..b81041f29 100644 --- a/src/2geom/bezier-curve.cpp +++ b/src/2geom/bezier-curve.cpp @@ -32,6 +32,9 @@ */ #include <2geom/bezier-curve.h> +#include <2geom/path-sink.h> +#include <2geom/basic-intersection.h> +#include <2geom/nearest-time.h> namespace Geom { @@ -97,7 +100,7 @@ namespace Geom * @class BezierCurveN * @brief Bezier curve with compile-time specified order. * - * @tparam degree unsigned value indicating the order of the bezier curve + * @tparam degree unsigned value indicating the order of the Bezier curve * * @relates BezierCurve * @ingroup Curves @@ -105,12 +108,10 @@ namespace Geom BezierCurve::BezierCurve(std::vector<Point> const &pts) + : inner(pts) { - inner = D2<Bezier>(Bezier::Order(pts.size() - 1), Bezier::Order(pts.size() - 1)); - for (unsigned d = 0; d < 2; ++d) { - for (unsigned i = 0; i < pts.size(); i++) { - inner[d][i] = pts[i][d]; - } + if (pts.size() < 2) { + THROW_RANGEERROR("Bezier curve must have at least 2 control points"); } } @@ -124,16 +125,87 @@ Coord BezierCurve::length(Coord tolerance) const return distance(initialPoint(), finalPoint()); case 2: { - std::vector<Point> pts = points(); + std::vector<Point> pts = controlPoints(); return bezier_length(pts[0], pts[1], pts[2], tolerance); } case 3: { - std::vector<Point> pts = points(); + std::vector<Point> pts = controlPoints(); return bezier_length(pts[0], pts[1], pts[2], pts[3], tolerance); } default: - return bezier_length(points(), tolerance); + return bezier_length(controlPoints(), tolerance); + } +} + +std::vector<CurveIntersection> +BezierCurve::intersect(Curve const &other, Coord eps) const +{ + std::vector<CurveIntersection> result; + + // optimization for the common case of no intersections + if (!boundsFast().intersects(other.boundsFast())) return result; + + BezierCurve const *bez = dynamic_cast<BezierCurve const *>(&other); + if (bez) { + std::vector<std::pair<double, double> > xs; + find_intersections(xs, inner, bez->inner, eps); + for (unsigned i = 0; i < xs.size(); ++i) { + CurveIntersection x(*this, other, xs[i].first, xs[i].second); + result.push_back(x); + } + } else { + THROW_NOTIMPLEMENTED(); + } + + return result; +} + +bool BezierCurve::operator==(Curve const &c) const +{ + if (this == &c) return true; + + BezierCurve const *other = dynamic_cast<BezierCurve const *>(&c); + if (!other) return false; + if (size() != other->size()) return false; + + for (unsigned i = 0; i < size(); ++i) { + if (controlPoint(i) != other->controlPoint(i)) return false; + } + return true; +} + +Coord BezierCurve::nearestTime(Point const &p, Coord from, Coord to) const +{ + return nearest_time(p, inner, from, to); +} + +void BezierCurve::feed(PathSink &sink, bool moveto_initial) const +{ + if (size() > 4) { + Curve::feed(sink, moveto_initial); + return; + } + + Point ip = controlPoint(0); + if (moveto_initial) { + sink.moveTo(ip); + } + switch (size()) { + case 2: + sink.lineTo(controlPoint(1)); + break; + case 3: + sink.quadTo(controlPoint(1), controlPoint(2)); + break; + case 4: + sink.curveTo(controlPoint(1), controlPoint(2), controlPoint(3)); + break; + default: + // TODO: add a path sink method that accepts a vector of control points + // and converts to cubic spline by default + assert(false); + break; } } @@ -164,9 +236,11 @@ Curve *BezierCurveN<1>::derivative() const { } template<> -Coord BezierCurveN<1>::nearestPoint(Point const& p, Coord from, Coord to) const +Coord BezierCurveN<1>::nearestTime(Point const& p, Coord from, Coord to) const { - if ( from > to ) std::swap(from, to); + using std::swap; + + if ( from > to ) swap(from, to); Point ip = pointAt(from); Point fp = pointAt(to); Point v = fp - ip; @@ -178,6 +252,33 @@ Coord BezierCurveN<1>::nearestPoint(Point const& p, Coord from, Coord to) const else return from + t*(to-from); } +template <> +void BezierCurveN<1>::feed(PathSink &sink, bool moveto_initial) const +{ + if (moveto_initial) { + sink.moveTo(controlPoint(0)); + } + sink.lineTo(controlPoint(1)); +} + +template <> +void BezierCurveN<2>::feed(PathSink &sink, bool moveto_initial) const +{ + if (moveto_initial) { + sink.moveTo(controlPoint(0)); + } + sink.quadTo(controlPoint(1), controlPoint(2)); +} + +template <> +void BezierCurveN<3>::feed(PathSink &sink, bool moveto_initial) const +{ + if (moveto_initial) { + sink.moveTo(controlPoint(0)); + } + sink.curveTo(controlPoint(1), controlPoint(2), controlPoint(3)); +} + static Coord bezier_length_internal(std::vector<Point> &v1, Coord tolerance) { @@ -186,7 +287,7 @@ static Coord bezier_length_internal(std::vector<Point> &v1, Coord tolerance) * but shorter than the length of the polyline formed by its control * points. When the difference between the two values is smaller than the * error tolerance, we can be sure that the true value is no further than - * 2*tolerance from their arithmetic mean. When it's larger, we recursively + * 0.5 * tolerance from their arithmetic mean. When it's larger, we recursively * subdivide the Bezier curve into two parts and add their lengths. */ Coord lower = distance(v1.front(), v1.back()); diff --git a/src/2geom/bezier-curve.h b/src/2geom/bezier-curve.h index d379526fa..9b08466f8 100644 --- a/src/2geom/bezier-curve.h +++ b/src/2geom/bezier-curve.h @@ -33,8 +33,8 @@ * the specific language governing rights and limitations. */ -#ifndef SEEN_LIB2GEOM_BEZIER_CURVE_H -#define SEEN_LIB2GEOM_BEZIER_CURVE_H +#ifndef LIB2GEOM_SEEN_BEZIER_CURVE_H +#define LIB2GEOM_SEEN_BEZIER_CURVE_H #include <2geom/curve.h> #include <2geom/sbasis-curve.h> // for non-native winding method @@ -48,25 +48,34 @@ class BezierCurve : public Curve { protected: D2<Bezier> inner; BezierCurve() {} - BezierCurve(D2<Bezier> const &b) : inner(b) {} BezierCurve(Bezier const &x, Bezier const &y) : inner(x, y) {} BezierCurve(std::vector<Point> const &pts); public: + explicit BezierCurve(D2<Bezier> const &b) : inner(b) {} + /// @name Access and modify control points /// @{ /** @brief Get the order of the Bezier curve. * A Bezier curve has order() + 1 control points. */ unsigned order() const { return inner[X].order(); } + /** @brief Get the number of control points. */ + unsigned size() const { return inner[X].order() + 1; } + /** @brief Access control points of the curve. + * @param ix The (zero-based) index of the control point. Note that the caller is responsible for checking that this value is <= order(). + * @return The control point. No-reference return, use setPoint() to modify control points. */ + Point controlPoint(unsigned ix) const { return Point(inner[X][ix], inner[Y][ix]); } + Point operator[](unsigned ix) const { return Point(inner[X][ix], inner[Y][ix]); } /** @brief Get the control points. * @return Vector with order() + 1 control points. */ - std::vector<Point> points() const { return bezier_points(inner); } + std::vector<Point> controlPoints() const { return bezier_points(inner); } + /** @brief Modify a control point. * @param ix The zero-based index of the point to modify. Note that the caller is responsible for checking that this value is <= order(). * @param v The new value of the point */ - void setPoint(unsigned ix, Point v) { - inner[X].setPoint(ix, v[X]); - inner[Y].setPoint(ix, v[Y]); + void setPoint(unsigned ix, Point const &v) { + inner[X][ix] = v[X]; + inner[Y][ix] = v[Y]; } /** @brief Set new control points. * @param ps Vector which must contain order() + 1 points. @@ -80,23 +89,21 @@ public: setPoint(i, ps[i]); } } - /** @brief Access control points of the curve. - * @param ix The (zero-based) index of the control point. Note that the caller is responsible for checking that this value is <= order(). - * @return The control point. No-reference return, use setPoint() to modify control points. */ - Point const operator[](unsigned ix) const { return Point(inner[X][ix], inner[Y][ix]); } /// @} /// @name Construct a Bezier curve with runtime-determined order. /// @{ - /** @brief Construct a curve from a vector of control points. */ + /** @brief Construct a curve from a vector of control points. + * This will construct the appropriate specialization of BezierCurve (i.e. LineSegment, + * QuadraticBezier or Cubic Bezier) if the number of control points in the passed vector + * does not exceed 4. */ static BezierCurve *create(std::vector<Point> const &pts); /// @} // implementation of virtual methods goes here -#ifndef DOXYGEN_SHOULD_SKIP_THIS virtual Point initialPoint() const { return inner.at0(); } virtual Point finalPoint() const { return inner.at1(); } - virtual bool isDegenerate() const { return inner.isConstant(); } + virtual bool isDegenerate() const { return inner.isConstant(0); } virtual void setInitial(Point const &v) { setPoint(0, v); } virtual void setFinal(Point const &v) { setPoint(order(), v); } virtual Rect boundsFast() const { return *bounds_fast(inner); } @@ -120,19 +127,11 @@ public: return new BezierCurve(Geom::reverse(inner)); } - virtual Curve *transformed(Affine const &m) const { - BezierCurve *ret = new BezierCurve(); - std::vector<Point> ps = points(); - for (unsigned i = 0; i <= order(); i++) { - ps[i] = ps[i] * m; + virtual void transform(Affine const &m) { + for (unsigned i = 0; i < size(); ++i) { + setPoint(i, controlPoint(i) * m); } - ret->setPoints(ps); - return ret; } - virtual Curve &operator*=(Translate const &m) { - inner += m.vector(); - return *this; - }; virtual Curve *derivative() const { return new BezierCurve(Geom::derivative(inner[X]), Geom::derivative(inner[Y])); @@ -143,26 +142,32 @@ public: virtual std::vector<Coord> roots(Coord v, Dim2 d) const { return (inner[d] - v).roots(); } + virtual Coord nearestTime(Point const &p, Coord from = 0, Coord to = 1) const; virtual Coord length(Coord tolerance) const; + virtual std::vector<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const; virtual Point pointAt(Coord t) const { return inner.valueAt(t); } - virtual std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const { return inner.valueAndDerivatives(t, n); } + virtual std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const { + return inner.valueAndDerivatives(t, n); + } virtual Coord valueAt(Coord t, Dim2 d) const { return inner[d].valueAt(t); } virtual D2<SBasis> toSBasis() const {return inner.toSBasis(); } -#endif + virtual bool operator==(Curve const &c) const; + virtual void feed(PathSink &sink, bool) const; }; template <unsigned degree> -class BezierCurveN : public BezierCurve { +class BezierCurveN + : public BezierCurve +{ + template <unsigned required_degree> + static void assert_degree(BezierCurveN<required_degree> const *) {} public: - template <unsigned required_degree> - static void assert_degree(BezierCurveN<required_degree> const *) {} - /// @name Construct Bezier curves /// @{ /** @brief Construct a Bezier curve of the specified order with all points zero. */ BezierCurveN() { - inner = D2<Bezier> (Bezier::Order(degree), Bezier::Order(degree)); + inner = D2<Bezier>(Bezier(Bezier::Order(degree)), Bezier(Bezier::Order(degree))); } /** @brief Construct from 2D Bezier polynomial. */ @@ -224,7 +229,6 @@ public: BezierCurveN(sx.second, sy.second)); } -#ifndef DOXYGEN_SHOULD_SKIP_THIS virtual Curve *duplicate() const { return new BezierCurveN(*this); } @@ -242,30 +246,15 @@ public: return new BezierCurveN(Geom::reverse(inner)); } } - virtual Curve *transformed(Affine const &m) const { - if (degree == 1) { - return new BezierCurveN<1>(initialPoint() * m, finalPoint() * m); - } else { - BezierCurveN *ret = new BezierCurveN(); - std::vector<Point> ps = points(); - for (unsigned i = 0; i <= degree; i++) { - ps[i] = ps[i] * m; - } - ret->setPoints(ps); - return ret; - } - } - virtual Curve &operator*=(Translate const &m) { - inner += m.vector(); - return *this; - } virtual Curve *derivative() const; - - // the method below is defined so that LineSegment can specialize it - virtual Coord nearestPoint(Point const& p, Coord from = 0, Coord to = 1) const { - return Curve::nearestPoint(p, from, to); + + virtual Coord nearestTime(Point const &p, Coord from = 0, Coord to = 1) const { + return BezierCurve::nearestTime(p, from, to); + } + virtual void feed(PathSink &sink, bool moveto_initial) const { + // call super. this is implemented only to allow specializations + BezierCurve::feed(sink, moveto_initial); } -#endif }; // BezierCurveN<0> is meaningless; specialize it out @@ -291,9 +280,12 @@ Curve *BezierCurveN<degree>::derivative() const { return new BezierCurveN<degree-1>(Geom::derivative(inner[X]), Geom::derivative(inner[Y])); } -// optimized specializations for LineSegment +// optimized specializations template <> Curve *BezierCurveN<1>::derivative() const; -template <> Coord BezierCurveN<1>::nearestPoint(Point const &, Coord, Coord) const; +template <> Coord BezierCurveN<1>::nearestTime(Point const &, Coord, Coord) const; +template <> void BezierCurveN<1>::feed(PathSink &sink, bool moveto_initial) const; +template <> void BezierCurveN<2>::feed(PathSink &sink, bool moveto_initial) const; +template <> void BezierCurveN<3>::feed(PathSink &sink, bool moveto_initial) const; inline Point middle_point(LineSegment const& _segment) { return ( _segment.initialPoint() + _segment.finalPoint() ) / 2; @@ -309,7 +301,7 @@ Coord bezier_length(Point p0, Point p1, Point p2, Point p3, Coord tolerance = 0. } // end namespace Geom -#endif // _2GEOM_BEZIER_CURVE_H_ +#endif // LIB2GEOM_SEEN_BEZIER_CURVE_H /* Local Variables: diff --git a/src/2geom/bezier-to-sbasis.h b/src/2geom/bezier-to-sbasis.h index 8cd4bf444..73c55d9b2 100644 --- a/src/2geom/bezier-to-sbasis.h +++ b/src/2geom/bezier-to-sbasis.h @@ -29,8 +29,8 @@ * */ -#ifndef _BEZIER_TO_SBASIS -#define _BEZIER_TO_SBASIS +#ifndef LIB2GEOM_SEEN_BEZIER_TO_SBASIS_H +#define LIB2GEOM_SEEN_BEZIER_TO_SBASIS_H #include <2geom/coord.h> #include <2geom/point.h> @@ -79,13 +79,9 @@ D2<SBasis> handles_to_sbasis(T const& handles, unsigned order) return sbc; } - } // end namespace Geom - - - -#endif +#endif // LIB2GEOM_SEEN_BEZIER_TO_SBASIS_H /* Local Variables: mode:c++ diff --git a/src/2geom/bezier-utils.cpp b/src/2geom/bezier-utils.cpp index ec17f6869..816bcdeb4 100644 --- a/src/2geom/bezier-utils.cpp +++ b/src/2geom/bezier-utils.cpp @@ -666,9 +666,9 @@ Point bezier_pt(unsigned const degree, Point const V[], double const t) { /** Pascal's triangle. */ - static int const pascal[4][4] = {{1}, - {1, 1}, - {1, 2, 1}, + static int const pascal[4][4] = {{1, 0, 0, 0}, + {1, 1, 0, 0}, + {1, 2, 1, 0}, {1, 3, 3, 1}}; assert( degree < 4); double const s = 1.0 - t; diff --git a/src/2geom/bezier.cpp b/src/2geom/bezier.cpp new file mode 100644 index 000000000..45496b75c --- /dev/null +++ b/src/2geom/bezier.cpp @@ -0,0 +1,324 @@ +/** + * @file + * @brief Bernstein-Bezier polynomial + *//* + * Authors: + * MenTaLguY <mental@rydia.net> + * Michael Sloan <mgsloan@gmail.com> + * Nathan Hurst <njh@njhurst.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2007-2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#include <2geom/bezier.h> +#include <2geom/solver.h> +#include <2geom/concepts.h> + +namespace Geom { + +std::vector<Coord> Bezier::valueAndDerivatives(Coord t, unsigned n_derivs) const { + /* This is inelegant, as it uses several extra stores. I think there might be a way to + * evaluate roughly in situ. */ + + // initialize return vector with zeroes, such that we only need to replace the non-zero derivs + std::vector<Coord> val_n_der(n_derivs + 1, Coord(0.0)); + + // initialize temp storage variables + std::valarray<Coord> d_(order()+1); + for(unsigned i = 0; i < size(); i++) { + d_[i] = c_[i]; + } + + unsigned nn = n_derivs + 1; + if(n_derivs > order()) { + nn = order()+1; // only calculate the non zero derivs + } + for(unsigned di = 0; di < nn; di++) { + //val_n_der[di] = (casteljau_subdivision(t, &d_[0], NULL, NULL, order() - di)); + val_n_der[di] = bernstein_value_at(t, &d_[0], order() - di); + for(unsigned i = 0; i < order() - di; i++) { + d_[i] = (order()-di)*(d_[i+1] - d_[i]); + } + } + + return val_n_der; +} + +void Bezier::subdivide(Coord t, Bezier *left, Bezier *right) const +{ + if (left) { + left->c_.resize(size()); + if (right) { + right->c_.resize(size()); + casteljau_subdivision<double>(t, &const_cast<std::valarray<Coord>&>(c_)[0], + &left->c_[0], &right->c_[0], order()); + } else { + casteljau_subdivision<double>(t, &const_cast<std::valarray<Coord>&>(c_)[0], + &left->c_[0], NULL, order()); + } + } else if (right) { + right->c_.resize(size()); + casteljau_subdivision<double>(t, &const_cast<std::valarray<Coord>&>(c_)[0], + NULL, &right->c_[0], order()); + } +} + +std::pair<Bezier, Bezier> Bezier::subdivide(Coord t) const +{ + std::pair<Bezier, Bezier> ret; + subdivide(t, &ret.first, &ret.second); + return ret; +} + +std::vector<Coord> Bezier::roots() const +{ + std::vector<Coord> solutions; + find_bezier_roots(solutions, 0, 1); + std::sort(solutions.begin(), solutions.end()); + return solutions; +} + +std::vector<Coord> Bezier::roots(Interval const &ivl) const +{ + std::vector<Coord> solutions; + find_bernstein_roots(&const_cast<std::valarray<Coord>&>(c_)[0], order(), solutions, 0, ivl.min(), ivl.max()); + std::sort(solutions.begin(), solutions.end()); + return solutions; +} + +Bezier Bezier::forward_difference(unsigned k) const +{ + Bezier fd(Order(order()-k)); + unsigned n = fd.size(); + + for(unsigned i = 0; i < n; i++) { + fd[i] = 0; + for(unsigned j = i; j < n; j++) { + fd[i] += (((j)&1)?-c_[j]:c_[j])*choose<double>(n, j-i); + } + } + return fd; +} + +Bezier Bezier::elevate_degree() const +{ + Bezier ed(Order(order()+1)); + unsigned n = size(); + ed[0] = c_[0]; + ed[n] = c_[n-1]; + for(unsigned i = 1; i < n; i++) { + ed[i] = (i*c_[i-1] + (n - i)*c_[i])/(n); + } + return ed; +} + +Bezier Bezier::reduce_degree() const +{ + if(order() == 0) return *this; + Bezier ed(Order(order()-1)); + unsigned n = size(); + ed[0] = c_[0]; + ed[n-1] = c_[n]; // ensure exact endpoints + unsigned middle = n/2; + for(unsigned i = 1; i < middle; i++) { + ed[i] = (n*c_[i] - i*ed[i-1])/(n-i); + } + for(unsigned i = n-1; i >= middle; i--) { + ed[i] = (n*c_[i] - i*ed[n-i])/(i); + } + return ed; +} + +Bezier Bezier::elevate_to_degree(unsigned newDegree) const +{ + Bezier ed = *this; + for(unsigned i = degree(); i < newDegree; i++) { + ed = ed.elevate_degree(); + } + return ed; +} + +Bezier Bezier::deflate() const +{ + if(order() == 0) return *this; + unsigned n = order(); + Bezier b(Order(n-1)); + for(unsigned i = 0; i < n; i++) { + b[i] = (n*c_[i+1])/(i+1); + } + return b; +} + +SBasis Bezier::toSBasis() const +{ + SBasis sb; + bezier_to_sbasis(sb, (*this)); + return sb; + //return bezier_to_sbasis(&c_[0], order()); +} + +Bezier &Bezier::operator+=(Bezier const &other) +{ + if (c_.size() > other.size()) { + c_ += other.elevate_to_degree(degree()).c_; + } else if (c_.size() < other.size()) { + *this = elevate_to_degree(other.degree()); + c_ += other.c_; + } else { + c_ += other.c_; + } + return *this; +} + +Bezier &Bezier::operator-=(Bezier const &other) +{ + if (c_.size() > other.size()) { + c_ -= other.elevate_to_degree(degree()).c_; + } else if (c_.size() < other.size()) { + *this = elevate_to_degree(other.degree()); + c_ -= other.c_; + } else { + c_ -= other.c_; + } + return *this; +} + + + +Bezier multiply(Bezier const &f, Bezier const &g) +{ + unsigned m = f.order(); + unsigned n = g.order(); + Bezier h(Bezier::Order(m+n)); + // h_k = sum_(i+j=k) (m i)f_i (n j)g_j / (m+n k) + + for(unsigned i = 0; i <= m; i++) { + const double fi = choose<double>(m,i)*f[i]; + for(unsigned j = 0; j <= n; j++) { + h[i+j] += fi * choose<double>(n,j)*g[j]; + } + } + for(unsigned k = 0; k <= m+n; k++) { + h[k] /= choose<double>(m+n, k); + } + return h; +} + +Bezier portion(Bezier const &a, double from, double to) +{ + Bezier ret(a); + + bool reverse_result = false; + if (from > to) { + std::swap(from, to); + reverse_result = true; + } + + do { + if (from == 0) { + if (to == 1) { + break; + } + casteljau_subdivision<double>(to, &ret.c_[0], &ret.c_[0], NULL, ret.order()); + break; + } + casteljau_subdivision<double>(from, &ret.c_[0], NULL, &ret.c_[0], ret.order()); + if (to == 1) break; + casteljau_subdivision<double>((to - from) / (1 - from), &ret.c_[0], &ret.c_[0], NULL, ret.order()); + // to protect against numerical inaccuracy in the above expression, we manually set + // the last coefficient to a value evaluated directly from the original polynomial + ret.c_[ret.order()] = a.valueAt(to); + } while(0); + + if (reverse_result) { + std::reverse(&ret.c_[0], &ret.c_[0] + ret.c_.size()); + } + return ret; +} + +Bezier derivative(Bezier const &a) +{ + //if(a.order() == 1) return Bezier(0.0); + if(a.order() == 1) return Bezier(a.c_[1]-a.c_[0]); + Bezier der(Bezier::Order(a.order()-1)); + + for(unsigned i = 0; i < a.order(); i++) { + der.c_[i] = a.order()*(a.c_[i+1] - a.c_[i]); + } + return der; +} + +Bezier integral(Bezier const &a) +{ + Bezier inte(Bezier::Order(a.order()+1)); + + inte[0] = 0; + for(unsigned i = 0; i < inte.order(); i++) { + inte[i+1] = inte[i] + a[i]/(inte.order()); + } + return inte; +} + +OptInterval bounds_fast(Bezier const &b) +{ + OptInterval ret = Interval::from_array(&const_cast<Bezier&>(b).c_[0], b.size()); + return ret; +} + +OptInterval bounds_exact(Bezier const &b) +{ + OptInterval ret(b.at0(), b.at1()); + std::vector<Coord> r = derivative(b).roots(); + for (unsigned i = 0; i < r.size(); ++i) { + ret->expandTo(b.valueAt(r[i])); + } + return ret; +} + +OptInterval bounds_local(Bezier const &b, OptInterval const &i) +{ + //return bounds_local(b.toSBasis(), i); + if (i) { + return bounds_fast(portion(b, i->min(), i->max())); + } else { + return OptInterval(); + } +} + +} // end namespace Geom + +/* + 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/2geom/bezier.h b/src/2geom/bezier.h index 51d5211d9..be7df1a6b 100644 --- a/src/2geom/bezier.h +++ b/src/2geom/bezier.h @@ -1,13 +1,14 @@ /** * @file - * @brief Bezier polynomial + * @brief Bernstein-Bezier polynomial *//* * Authors: * MenTaLguY <mental@rydia.net> * Michael Sloan <mgsloan@gmail.com> * Nathan Hurst <njh@njhurst.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> * - * Copyright 2007 Authors + * Copyright 2007-2015 Authors * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -37,70 +38,23 @@ #ifndef LIB2GEOM_SEEN_BEZIER_H #define LIB2GEOM_SEEN_BEZIER_H +#include <algorithm> #include <valarray> #include <boost/optional.hpp> -#include <2geom/coord.h> #include <2geom/choose.h> -#include <valarray> -#include <2geom/math-utils.h> +#include <2geom/coord.h> #include <2geom/d2.h> -#include <2geom/solver.h> +#include <2geom/math-utils.h> namespace Geom { -inline Coord subdivideArr(Coord t, Coord const *v, Coord *left, Coord *right, unsigned order) { -/* - * Bernstein : - * Evaluate a Bernstein function at a particular parameter value - * Fill in control points for resulting sub-curves. - * - */ - - unsigned N = order+1; - std::valarray<Coord> row(N); - for (unsigned i = 0; i < N; i++) - row[i] = v[i]; - - // Triangle computation - const double omt = (1-t); - if(left) - left[0] = row[0]; - if(right) - right[order] = row[order]; - for (unsigned i = 1; i < N; i++) { - for (unsigned j = 0; j < N - i; j++) { - row[j] = omt*row[j] + t*row[j+1]; - } - if(left) - left[i] = row[0]; - if(right) - right[order-i] = row[order-i]; - } - return (row[0]); -/* - Coord vtemp[order+1][order+1]; - - // Copy control points - std::copy(v, v+order+1, vtemp[0]); - - // Triangle computation - for (unsigned i = 1; i <= order; i++) { - for (unsigned j = 0; j <= order - i; j++) { - vtemp[i][j] = lerp(t, vtemp[i-1][j], vtemp[i-1][j+1]); - } - } - if(left != NULL) - for (unsigned j = 0; j <= order; j++) - left[j] = vtemp[j][0]; - if(right != NULL) - for (unsigned j = 0; j <= order; j++) - right[j] = vtemp[order-j][j]; - - return (vtemp[order][0]);*/ -} - +/** @brief Compute the value of a Bernstein-Bezier polynomial. + * This method uses a Horner-like fast evaluation scheme. + * @param t Time value + * @param c_ Pointer to coefficients + * @param n Degree of the polynomial (number of coefficients minus one) */ template <typename T> -inline T bernsteinValueAt(double t, T const *c_, unsigned n) { +inline T bernstein_value_at(double t, T const *c_, unsigned n) { double u = 1.0 - t; double bc = 1; double tn = 1; @@ -113,16 +67,71 @@ inline T bernsteinValueAt(double t, T const *c_, unsigned n) { return (tmp + tn*t*c_[n]); } -class Bezier { +/** @brief Perform Casteljau subdivision of a Bezier polynomial. + * Given an array of coefficients and a time value, computes two new Bernstein-Bezier basis + * polynomials corresponding to the \f$[0, t]\f$ and \f$[t, 1]\f$ intervals of the original one. + * @param t Time value + * @param v Array of input coordinates + * @param left Output polynomial corresponding to \f$[0, t]\f$ + * @param right Output polynomial corresponding to \f$[t, 1]\f$ + * @param order Order of the input polynomial, equal to one less the number of coefficients + * @return Value of the polynomial at @a t */ +template <typename T> +inline T casteljau_subdivision(double t, T const *v, T *left, T *right, unsigned order) { + // The Horner-like scheme gives very slightly different results, but we need + // the result of subdivision to match exactly with Bezier's valueAt function. + T val = bernstein_value_at(t, v, order); + + if (!left && !right) { + return val; + } + + if (!right) { + if (left != v) { + std::copy(v, v + order + 1, left); + } + for (std::size_t i = order; i > 0; --i) { + for (std::size_t j = i; j <= order; ++j) { + left[j] = lerp(t, left[j-1], left[j]); + } + } + left[order] = val; + return left[order]; + } + + if (right != v) { + std::copy(v, v + order + 1, right); + } + for (std::size_t i = 1; i <= order; ++i) { + if (left) { + left[i-1] = right[0]; + } + for (std::size_t j = i; j > 0; --j) { + right[j-1] = lerp(t, right[j-1], right[j]); + } + } + right[0] = val; + if (left) { + left[order] = right[0]; + } + return right[0]; +} + +/** + * @brief Polynomial in Bernstein-Bezier basis + * @ingroup Fragments + */ +class Bezier + : boost::arithmetic< Bezier, double + , boost::additive< Bezier + > > +{ private: std::valarray<Coord> c_; friend Bezier portion(const Bezier & a, Coord from, Coord to); - friend OptInterval bounds_fast(Bezier const & b); - friend Bezier derivative(const Bezier & a); - friend class Bernstein; void @@ -130,13 +139,14 @@ private: double l, double r) const; protected: - Bezier(Coord const c[], unsigned ord) : c_(c, ord+1){ - //std::copy(c, c+order()+1, &c_[0]); - } + Bezier(Coord const c[], unsigned ord) + : c_(c, ord+1) + {} public: - unsigned int order() const { return c_.size()-1;} - unsigned int size() const { return c_.size();} + unsigned order() const { return c_.size()-1;} + unsigned degree() const { return order(); } + unsigned size() const { return c_.size();} Bezier() {} Bezier(const Bezier& b) :c_(b.c_) {} @@ -160,262 +170,145 @@ public: assert(ord.order == order()); } + /// @name Construct Bezier polynomials from their control points + /// @{ explicit Bezier(Coord c0) : c_(0., 1) { c_[0] = c0; } - - //Construct an order-1 bezier (linear Bézier) Bezier(Coord c0, Coord c1) : c_(0., 2) { c_[0] = c0; c_[1] = c1; } - - //Construct an order-2 bezier (quadratic Bézier) Bezier(Coord c0, Coord c1, Coord c2) : c_(0., 3) { c_[0] = c0; c_[1] = c1; c_[2] = c2; } - - //Construct an order-3 bezier (cubic Bézier) Bezier(Coord c0, Coord c1, Coord c2, Coord c3) : c_(0., 4) { c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4) : c_(0., 5) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, + Coord c5) : c_(0., 6) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + c_[5] = c5; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, + Coord c5, Coord c6) : c_(0., 7) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + c_[5] = c5; c_[6] = c6; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, + Coord c5, Coord c6, Coord c7) : c_(0., 8) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + c_[5] = c5; c_[6] = c6; c_[7] = c7; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, + Coord c5, Coord c6, Coord c7, Coord c8) : c_(0., 9) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + c_[5] = c5; c_[6] = c6; c_[7] = c7; c_[8] = c8; + } + Bezier(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, + Coord c5, Coord c6, Coord c7, Coord c8, Coord c9) : c_(0., 10) { + c_[0] = c0; c_[1] = c1; c_[2] = c2; c_[3] = c3; c_[4] = c4; + c_[5] = c5; c_[6] = c6; c_[7] = c7; c_[8] = c8; c_[9] = c9; + } - void resize (unsigned int n, Coord v = 0) - { - c_.resize (n, v); + template <typename Iter> + Bezier(Iter first, Iter last) { + c_.resize(std::distance(first, last)); + for (std::size_t i = 0; first != last; ++first, ++i) { + c_[i] = *first; + } } + Bezier(std::vector<Coord> const &vec) + : c_(&vec[0], vec.size()) + {} + /// @} - void clear() - { + void resize (unsigned int n, Coord v = 0) { + c_.resize (n, v); + } + void clear() { c_.resize(0); } - inline unsigned degree() const { return order(); } - //IMPL: FragmentConcept typedef Coord output_type; - inline bool isZero(double eps=EPSILON) const { + bool isZero(double eps=EPSILON) const { for(unsigned i = 0; i <= order(); i++) { if( ! are_near(c_[i], 0., eps) ) return false; } return true; } - inline bool isConstant(double eps=EPSILON) const { + bool isConstant(double eps=EPSILON) const { for(unsigned i = 1; i <= order(); i++) { if( ! are_near(c_[i], c_[0], eps) ) return false; } return true; } - inline bool isFinite() const { + bool isFinite() const { for(unsigned i = 0; i <= order(); i++) { if(!IS_FINITE(c_[i])) return false; } return true; } - inline Coord at0() const { return c_[0]; } - inline Coord at1() const { return c_[order()]; } - - inline Coord valueAt(double t) const { - int n = order(); - double u, bc, tn, tmp; - int i; - u = 1.0 - t; - bc = 1; - tn = 1; - tmp = c_[0]*u; - for(i=1; i<n; i++){ - tn = tn*t; - bc = bc*(n-i+1)/i; - tmp = (tmp + tn*bc*c_[i])*u; - } - return (tmp + tn*t*c_[n]); + Coord at0() const { return c_[0]; } + Coord &at0() { return c_[0]; } + Coord at1() const { return c_[order()]; } + Coord &at1() { return c_[order()]; } + + Coord valueAt(double t) const { + return bernstein_value_at(t, &c_[0], order()); } - inline Coord operator()(double t) const { return valueAt(t); } + Coord operator()(double t) const { return valueAt(t); } SBasis toSBasis() const; - //Only mutator - inline Coord &operator[](unsigned ix) { return c_[ix]; } - inline Coord const &operator[](unsigned ix) const { return const_cast<std::valarray<Coord>&>(c_)[ix]; } - //inline Coord const &operator[](unsigned ix) const { return c_[ix]; } - inline void setPoint(unsigned ix, double val) { c_[ix] = val; } - - /** - * The size of the returned vector equals n_derivs+1. - */ - std::vector<Coord> valueAndDerivatives(Coord t, unsigned n_derivs) const { - /* This is inelegant, as it uses several extra stores. I think there might be a way to - * evaluate roughly in situ. */ - - // initialize return vector with zeroes, such that we only need to replace the non-zero derivs - std::vector<Coord> val_n_der(n_derivs + 1, Coord(0.0)); - - // initialize temp storage variables - std::valarray<Coord> d_(order()+1); - for(unsigned i = 0; i < size(); i++) { - d_[i] = c_[i]; - } + Coord &operator[](unsigned ix) { return c_[ix]; } + Coord const &operator[](unsigned ix) const { return const_cast<std::valarray<Coord>&>(c_)[ix]; } - unsigned nn = n_derivs + 1; - if(n_derivs > order()) { - nn = order()+1; // only calculate the non zero derivs - } - for(unsigned di = 0; di < nn; di++) { - //val_n_der[di] = (subdivideArr(t, &d_[0], NULL, NULL, order() - di)); - val_n_der[di] = bernsteinValueAt(t, &d_[0], order() - di); - for(unsigned i = 0; i < order() - di; i++) { - d_[i] = (order()-di)*(d_[i+1] - d_[i]); - } - } + void setCoeff(unsigned ix, double val) { c_[ix] = val; } - return val_n_der; - } + // The size of the returned vector equals n_derivs+1. + std::vector<Coord> valueAndDerivatives(Coord t, unsigned n_derivs) const; - std::pair<Bezier, Bezier > subdivide(Coord t) const { - Bezier a(Bezier::Order(*this)), b(Bezier::Order(*this)); - subdivideArr(t, &const_cast<std::valarray<Coord>&>(c_)[0], &a.c_[0], &b.c_[0], order()); - return std::pair<Bezier, Bezier >(a, b); - } + void subdivide(Coord t, Bezier *left, Bezier *right) const; + std::pair<Bezier, Bezier> subdivide(Coord t) const; - std::vector<double> roots() const { - std::vector<double> solutions; - find_bezier_roots(solutions, 0, 1); - return solutions; - } - std::vector<double> roots(Interval const ivl) const { - std::vector<double> solutions; - find_bernstein_roots(&const_cast<std::valarray<Coord>&>(c_)[0], order(), solutions, 0, ivl.min(), ivl.max()); - return solutions; - } + std::vector<Coord> roots() const; + std::vector<Coord> roots(Interval const &ivl) const; - Bezier forward_difference(unsigned k) { - Bezier fd(Order(order()-k)); - unsigned n = fd.size(); - - for(unsigned i = 0; i < n; i++) { - fd[i] = 0; - for(unsigned j = i; j < n; j++) { - fd[i] += (((j)&1)?-c_[j]:c_[j])*choose<double>(n, j-i); - } - } - return fd; - } - - Bezier elevate_degree() const { - Bezier ed(Order(order()+1)); - unsigned n = size(); - ed[0] = c_[0]; - ed[n] = c_[n-1]; - for(unsigned i = 1; i < n; i++) { - ed[i] = (i*c_[i-1] + (n - i)*c_[i])/(n); - } - return ed; - } - - Bezier reduce_degree() const { - if(order() == 0) return *this; - Bezier ed(Order(order()-1)); - unsigned n = size(); - ed[0] = c_[0]; - ed[n-1] = c_[n]; // ensure exact endpoints - unsigned middle = n/2; - for(unsigned i = 1; i < middle; i++) { - ed[i] = (n*c_[i] - i*ed[i-1])/(n-i); - } - for(unsigned i = n-1; i >= middle; i--) { - ed[i] = (n*c_[i] - i*ed[n-i])/(i); - } - return ed; - } + Bezier forward_difference(unsigned k) const; + Bezier elevate_degree() const; + Bezier reduce_degree() const; + Bezier elevate_to_degree(unsigned newDegree) const; + Bezier deflate() const; - Bezier elevate_to_degree(unsigned newDegree) const { - Bezier ed = *this; - for(unsigned i = degree(); i < newDegree; i++) { - ed = ed.elevate_degree(); - } - return ed; + // basic arithmetic operators + Bezier &operator+=(double v) { + c_ += v; + return *this; } - - Bezier deflate() { - if(order() == 0) return *this; - unsigned n = order(); - Bezier b(Order(n-1)); - for(unsigned i = 0; i < n; i++) { - b[i] = (n*c_[i+1])/(i+1); - } - return b; + Bezier &operator-=(double v) { + c_ -= v; + return *this; } -}; - - -void bezier_to_sbasis (SBasis & sb, Bezier const& bz); - -inline -Bezier multiply(Bezier const& f, Bezier const& g) { - unsigned m = f.order(); - unsigned n = g.order(); - Bezier h(Bezier::Order(m+n)); - // h_k = sum_(i+j=k) (m i)f_i (n j)g_j / (m+n k) - - for(unsigned i = 0; i <= m; i++) { - const double fi = choose<double>(m,i)*f[i]; - for(unsigned j = 0; j <= n; j++) { - h[i+j] += fi * choose<double>(n,j)*g[j]; - } + Bezier &operator*=(double v) { + c_ *= v; + return *this; } - for(unsigned k = 0; k <= m+n; k++) { - h[k] /= choose<double>(m+n, k); + Bezier &operator/=(double v) { + c_ /= v; + return *this; } - return h; -} - -inline -SBasis Bezier::toSBasis() const { - SBasis sb; - bezier_to_sbasis(sb, (*this)); - return sb; - //return bezier_to_sbasis(&c_[0], order()); -} - -//TODO: implement others -inline Bezier operator+(const Bezier & a, double v) { - Bezier result = Bezier(Bezier::Order(a)); - for(unsigned i = 0; i <= a.order(); i++) - result[i] = a[i] + v; - return result; -} - -inline Bezier operator-(const Bezier & a, double v) { - Bezier result = Bezier(Bezier::Order(a)); - for(unsigned i = 0; i <= a.order(); i++) - result[i] = a[i] - v; - return result; -} - -inline Bezier& operator+=(Bezier & a, double v) { - for(unsigned i = 0; i <= a.order(); ++i) - a[i] = a[i] + v; - return a; -} + Bezier &operator+=(Bezier const &other); + Bezier &operator-=(Bezier const &other); +}; -inline Bezier& operator-=(Bezier & a, double v) { - for(unsigned i = 0; i <= a.order(); ++i) - a[i] = a[i] - v; - return a; -} -inline Bezier operator*(const Bezier & a, double v) { - Bezier result = Bezier(Bezier::Order(a)); - for(unsigned i = 0; i <= a.order(); i++) - result[i] = a[i] * v; - return result; -} +void bezier_to_sbasis (SBasis &sb, Bezier const &bz); -inline Bezier operator/(const Bezier & a, double v) { - Bezier result = Bezier(Bezier::Order(a)); - for(unsigned i = 0; i <= a.order(); i++) - result[i] = a[i] / v; - return result; -} +Bezier multiply(Bezier const &f, Bezier const &g); inline Bezier reverse(const Bezier & a) { Bezier result = Bezier(Bezier::Order(a)); @@ -424,20 +317,7 @@ inline Bezier reverse(const Bezier & a) { return result; } -inline Bezier portion(const Bezier & a, double from, double to) { - //TODO: implement better? - std::valarray<Coord> res(a.order() + 1); - if(from == 0) { - if(to == 1) { return Bezier(a); } - subdivideArr(to, &const_cast<Bezier&>(a).c_[0], &res[0], NULL, a.order()); - return Bezier(&res[0], a.order()); - } - subdivideArr(from, &const_cast<Bezier&>(a).c_[0], NULL, &res[0], a.order()); - if(to == 1) return Bezier(&res[0], a.order()); - std::valarray<Coord> res2(a.order()+1); - subdivideArr((to - from)/(1 - from), &res[0], &res2[0], NULL, a.order()); - return Bezier(&res2[0], a.order()); -} +Bezier portion(const Bezier & a, double from, double to); // XXX Todo: how to handle differing orders inline std::vector<Point> bezier_points(const D2<Bezier > & a) { @@ -450,52 +330,19 @@ inline std::vector<Point> bezier_points(const D2<Bezier > & a) { return result; } -inline Bezier derivative(const Bezier & a) { - //if(a.order() == 1) return Bezier(0.0); - if(a.order() == 1) return Bezier(a.c_[1]-a.c_[0]); - Bezier der(Bezier::Order(a.order()-1)); - - for(unsigned i = 0; i < a.order(); i++) { - der.c_[i] = a.order()*(a.c_[i+1] - a.c_[i]); - } - return der; -} - -inline Bezier integral(const Bezier & a) { - Bezier inte(Bezier::Order(a.order()+1)); - - inte[0] = 0; - for(unsigned i = 0; i < inte.order(); i++) { - inte[i+1] = inte[i] + a[i]/(inte.order()); - } - return inte; -} - -inline OptInterval bounds_fast(Bezier const & b) { - OptInterval ret = Interval::from_array(&const_cast<Bezier&>(b).c_[0], b.size()); - return ret; -} - -//TODO: better bounds exact -inline OptInterval bounds_exact(Bezier const & b) { - return bounds_exact(b.toSBasis()); -} - -inline OptInterval bounds_local(Bezier const & b, OptInterval i) { - //return bounds_local(b.toSBasis(), i); - if (i) { - return bounds_fast(portion(b, i->min(), i->max())); - } else { - return OptInterval(); - } -} +Bezier derivative(Bezier const &a); +Bezier integral(Bezier const &a); +OptInterval bounds_fast(Bezier const &b); +OptInterval bounds_exact(Bezier const &b); +OptInterval bounds_local(Bezier const &b, OptInterval const &i); -inline std::ostream &operator<< (std::ostream &out_file, const Bezier & b) { - out_file << "Bezier("; - for(unsigned i = 0; i < b.size(); i++) { - out_file << b[i] << ", "; +inline std::ostream &operator<< (std::ostream &os, const Bezier & b) { + os << "Bezier("; + for(unsigned i = 0; i < b.order(); i++) { + os << format_coord_nice(b[i]) << ", "; } - return out_file << ")"; + os << format_coord_nice(b[b.order()]) << ")"; + return os; } } diff --git a/src/2geom/cairo-path-sink.cpp b/src/2geom/cairo-path-sink.cpp new file mode 100644 index 000000000..244a08ba4 --- /dev/null +++ b/src/2geom/cairo-path-sink.cpp @@ -0,0 +1,123 @@ +/** + * @file + * @brief Path sink for Cairo contexts + *//* + * Copyright 2014 Krzysztof Kosiński + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, output to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#include <cairo.h> +#include <2geom/cairo-path-sink.h> +#include <2geom/elliptical-arc.h> + +namespace Geom { + +CairoPathSink::CairoPathSink(cairo_t *cr) + : _cr(cr) +{} + +void CairoPathSink::moveTo(Point const &p) +{ + cairo_move_to(_cr, p[X], p[Y]); + _current_point = p; +} + +void CairoPathSink::lineTo(Point const &p) +{ + cairo_line_to(_cr, p[X], p[Y]); + _current_point = p; +} + +void CairoPathSink::curveTo(Point const &p1, Point const &p2, Point const &p3) +{ + cairo_curve_to(_cr, p1[X], p1[Y], p2[X], p2[Y], p3[X], p3[Y]); + _current_point = p3; +} + +void CairoPathSink::quadTo(Point const &p1, Point const &p2) +{ + // degree-elevate to cubic Bezier, since Cairo doesn't do quad Beziers + // google "Bezier degree elevation" for more info + Point q1 = (1./3.) * _current_point + (2./3.) * p1; + Point q2 = (2./3.) * p1 + (1./3.) * p2; + // q3 = p2 + cairo_curve_to(_cr, q1[X], q1[Y], q2[X], q2[Y], p2[X], p2[Y]); + _current_point = p2; +} + +void CairoPathSink::arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Point const &p) +{ + EllipticalArc arc(_current_point, rx, ry, angle, large_arc, sweep, p); + // Cairo only does circular arcs. + // To do elliptical arcs, we must use a temporary transform. + Affine uct = arc.unitCircleTransform(); + + // TODO move Cairo-2Geom matrix conversion into a common location + cairo_matrix_t cm; + cm.xx = uct[0]; + cm.xy = uct[2]; + cm.x0 = uct[4]; + cm.yx = uct[1]; + cm.yy = uct[3]; + cm.y0 = uct[5]; + + cairo_save(_cr); + cairo_transform(_cr, &cm); + if (sweep) { + cairo_arc(_cr, 0, 0, 1, arc.initialAngle(), arc.finalAngle()); + } else { + cairo_arc_negative(_cr, 0, 0, 1, arc.initialAngle(), arc.finalAngle()); + } + _current_point = p; + cairo_restore(_cr); + + /* Note that an extra linear segment will be inserted before the arc + * if Cairo considers the current point distinct from the initial point + * of the arc; we could partially alleviate this by not emitting + * linear segments that are followed by arc segments, but this would require + * buffering the input curves. */ +} + +void CairoPathSink::closePath() +{ + cairo_close_path(_cr); +} + +void CairoPathSink::flush() {} + +} // namespace Geom + +/* + 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/2geom/conjugate_gradient.h b/src/2geom/cairo-path-sink.h index 4f500c0e6..9fec7e0ab 100644 --- a/src/2geom/conjugate_gradient.h +++ b/src/2geom/cairo-path-sink.h @@ -1,8 +1,8 @@ /** * @file - * @brief Routines for solving a system of linear equations using the conjugate gradient method + * @brief Path sink for Cairo contexts *//* - * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au> + * Copyright 2014 Krzysztof Kosiński * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -29,29 +29,52 @@ * */ -#ifndef _2GEOM_CONJUGATE_GRADIENT_H -#define _2GEOM_CONJUGATE_GRADIENT_H +#ifndef LIB2GEOM_SEEN_CAIRO_PATH_SINK_H +#define LIB2GEOM_SEEN_CAIRO_PATH_SINK_H -#include <valarray> +#include <2geom/path-sink.h> +#include <cairo.h> + +namespace Geom { -namespace Geom -{ -double -inner(std::valarray<double> const &x, - std::valarray<double> const &y); +/** @brief Output paths to a Cairo drawing context + * + * This class converts from 2Geom path representation to the Cairo representation. + * Use it to simplify visualizing the results of 2Geom operations with the Cairo library, + * for example: + * @code + * CairoPathSink sink(cr); + * sink.feed(pv); + * cairo_stroke(cr); + * @endcode + * + * Currently the flush method is a no-op, but this is not guaranteed + * to hold forever. + */ +class CairoPathSink + : public PathSink +{ +public: + CairoPathSink(cairo_t *cr); -void -conjugate_gradient(std::valarray<double> const &A, - std::valarray<double> &x, - std::valarray<double> const &b, - unsigned n, double tol, - unsigned max_iterations, bool ortho1); + void moveTo(Point const &p); + void lineTo(Point const &p); + void curveTo(Point const &c0, Point const &c1, Point const &p); + void quadTo(Point const &c, Point const &p); + void arcTo(Coord rx, Coord ry, Coord angle, + bool large_arc, bool sweep, Point const &p); + void closePath(); + void flush(); -} // namespace Geom +private: + cairo_t *_cr; + Point _current_point; +}; -#endif // _2GEOM_CONJUGATE_GRADIENT_H +} +#endif // !LIB2GEOM_SEEN_CAIRO_PATH_SINK_H /* Local Variables: mode:c++ diff --git a/src/2geom/circle.cpp b/src/2geom/circle.cpp index d021882ea..0b1dddc8e 100644 --- a/src/2geom/circle.cpp +++ b/src/2geom/circle.cpp @@ -1,10 +1,11 @@ -/* - * Circle Curve - * +/** @file + * @brief Circle shape + *//* * Authors: - * Marco Cecchetti <mrcekets at gmail.com> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> * - * Copyright 2008 authors + * Copyright 2008-2014 Authors * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -30,56 +31,54 @@ * the specific language governing rights and limitations. */ - #include <2geom/circle.h> #include <2geom/ellipse.h> #include <2geom/svg-elliptical-arc.h> #include <2geom/numeric/fitting-tool.h> #include <2geom/numeric/fitting-model.h> +namespace Geom { - -namespace Geom +void Circle::setCoefficients(Coord A, Coord B, Coord C, Coord D) { - -void Circle::set(double A, double B, double C, double D) -{ - if (A == 0) - { + if (A == 0) { THROW_RANGEERROR("square term coefficient == 0"); } //std::cerr << "B = " << B << " C = " << C << " D = " << D << std::endl; - double b = B / A; - double c = C / A; - double d = D / A; + Coord b = B / A; + Coord c = C / A; + Coord d = D / A; - m_centre[X] = -b/2; - m_centre[Y] = -c/2; - double r2 = m_centre[X] * m_centre[X] + m_centre[Y] * m_centre[Y] - d; + _center[X] = -b/2; + _center[Y] = -c/2; + Coord r2 = _center[X] * _center[X] + _center[Y] * _center[Y] - d; - if (r2 < 0) - { + if (r2 < 0) { THROW_RANGEERROR("ray^2 < 0"); } - m_ray = std::sqrt(r2); + _radius = std::sqrt(r2); } -void Circle::set(std::vector<Point> const& points) +void Circle::fit(std::vector<Point> const& points) { size_t sz = points.size(); - if (sz < 3) - { + if (sz < 2) { THROW_RANGEERROR("fitting error: too few points passed"); } + if (sz == 2) { + _center = points[0] * 0.5 + points[1] * 0.5; + _radius = distance(points[0], points[1]) / 2; + return; + } + NL::LFMCircle model; NL::least_squeares_fitter<NL::LFMCircle> fitter(model, sz); - for (size_t i = 0; i < sz; ++i) - { + for (size_t i = 0; i < sz; ++i) { fitter.append(points[i]); } fitter.update(); @@ -93,11 +92,11 @@ void Circle::set(std::vector<Point> const& points) */ EllipticalArc * Circle::arc(Point const& initial, Point const& inner, Point const& final, - bool _svg_compliant) + bool svg_compliant) { // TODO native implementation! - Ellipse e(center(X), center(Y), ray(), ray(), 0); - return e.arc(initial, inner, final, _svg_compliant); + Ellipse e(_center[X], _center[Y], _radius, _radius, 0); + return e.arc(initial, inner, final, svg_compliant); } D2<SBasis> Circle::toSBasis() @@ -108,13 +107,13 @@ D2<SBasis> Circle::toSBasis() B[0] = cos(bo,4); B[1] = sin(bo,4); - B = B * m_ray + m_centre; + B = B * _radius + _center; return B; } void -Circle::getPath(std::vector<Path> &path_out) { +Circle::getPath(PathVector &path_out) { Path pb; D2<SBasis> B = toSBasis(); diff --git a/src/2geom/circle.h b/src/2geom/circle.h index ca9241047..3c2115b12 100644 --- a/src/2geom/circle.h +++ b/src/2geom/circle.h @@ -1,11 +1,11 @@ -/** - * \file - * \brief Circles +/** @file + * @brief Circle shape *//* * Authors: - * Marco Cecchetti <mrcekets at gmail.com> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> * - * Copyright 2008 authors + * Copyright 2008-2014 Authors * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -34,83 +34,72 @@ #ifndef LIB2GEOM_SEEN_CIRCLE_H #define LIB2GEOM_SEEN_CIRCLE_H -#include <vector> #include <2geom/point.h> -#include <2geom/exception.h> -#include <2geom/path.h> +#include <2geom/forward.h> +#include <2geom/transforms.h> namespace Geom { class EllipticalArc; +/** @brief Set of all points at a fixed distance from the center + * @ingroup Shapes */ class Circle + : MultipliableNoncommutative< Circle, Translate + , MultipliableNoncommutative< Circle, Rotate + , MultipliableNoncommutative< Circle, Zoom + > > > { - public: - Circle() - {} - - Circle(double cx, double cy, double r) - : m_centre(cx, cy), m_ray(r) - { - } - - Circle(Point center, double r) - : m_centre(center), m_ray(r) - { - } + Point _center; + Coord _radius; - Circle(double A, double B, double C, double D) - { - set(A, B, C, D); - } - - Circle(std::vector<Point> const& points) - { - set(points); - } +public: + Circle() {} + Circle(Coord cx, Coord cy, Coord r) + : _center(cx, cy), _radius(r) + {} + Circle(Point const ¢er, Coord r) + : _center(center), _radius(r) + {} - void set(double cx, double cy, double r) - { - m_centre[X] = cx; - m_centre[Y] = cy; - m_ray = r; + Circle(Coord A, Coord B, Coord C, Coord D) { + setCoefficients(A, B, C, D); } - // build a circle by its implicit equation: // Ax^2 + Ay^2 + Bx + Cy + D = 0 - void set(double A, double B, double C, double D); + void setCoefficients(Coord A, Coord B, Coord C, Coord D); - // build up the best fitting circle wrt the passed points - // prerequisite: at least 3 points must be passed - void set(std::vector<Point> const& points); + /** @brief Fit the circle to the passed points using the least squares method. + * @param points Samples at the perimeter of the circle */ + void fit(std::vector<Point> const &points); EllipticalArc * arc(Point const& initial, Point const& inner, Point const& final, - bool _svg_compliant = true); + bool svg_compliant = true); D2<SBasis> toSBasis(); - void getPath(std::vector<Path> &path_out); + void getPath(PathVector &path_out); - Point center() const - { - return m_centre; - } + Point center() const { return _center; } + Coord center(Dim2 d) const { return _center[d]; } + Coord radius() const { return _radius; } - Coord center(Dim2 d) const - { - return m_centre[d]; - } + void setCenter(Point const &p) { _center = p; } + void setRadius(Coord c) { _radius = c; } - Coord ray() const - { - return m_ray; + Circle &operator*=(Translate const &t) { + _center *= t; + return *this; + } + Circle &operator*=(Rotate const &) { + return *this; + } + Circle &operator*=(Zoom const &z) { + _center *= z; + _radius *= z.scale(); + return *this; } - - - private: - Point m_centre; - Coord m_ray; }; } // end namespace Geom diff --git a/src/2geom/circulator.h b/src/2geom/circulator.h index 9671ce4a9..06e4d2c4e 100644 --- a/src/2geom/circulator.h +++ b/src/2geom/circulator.h @@ -1,5 +1,4 @@ -/** - * @file circulator.h +/** @file * @brief Circular iterator adapter *//* * Copyright 2006 MenTaLguY <mental@rydia.net> @@ -29,8 +28,8 @@ * */ -#ifndef SEEN_Circulator_H -#define SEEN_Circulator_H +#ifndef LIB2GEOM_SEEN_CIRCULATOR_H +#define LIB2GEOM_SEEN_CIRCULATOR_H #include <iterator> @@ -137,7 +136,7 @@ Geom::Circulator<T> operator+(int n, Geom::Circulator<T> const &c) { return c + n; } -#endif // SEEN_Circulator_H +#endif // LIB2GEOM_SEEN_CIRCULATOR_H /* Local Variables: diff --git a/src/2geom/concepts.h b/src/2geom/concepts.h index c89c3a224..f571ddc60 100644 --- a/src/2geom/concepts.h +++ b/src/2geom/concepts.h @@ -35,8 +35,9 @@ #include <2geom/interval.h> #include <2geom/point.h> #include <2geom/rect.h> +#include <2geom/intersection.h> #include <vector> -#include <boost/concept_check.hpp> +#include <boost/concept/assert.hpp> #include <2geom/forward.h> namespace Geom { @@ -71,11 +72,13 @@ struct FragmentConcept { SbType sb; void constraints() { t = T(o); - b = t.isZero(); - b = t.isConstant(); + b = t.isZero(d); + b = t.isConstant(d); b = t.isFinite(); o = t.at0(); o = t.at1(); + t.at0() = o; + t.at1() = o; o = t.valueAt(d); o = t(d); v = t.valueAndDerivatives(d, u-1); @@ -97,6 +100,34 @@ struct FragmentConcept { }; template <typename T> +struct ShapeConcept { + typedef typename ShapeTraits<T>::TimeType Time; + typedef typename ShapeTraits<T>::IntervalType Interval; + typedef typename ShapeTraits<T>::AffineClosureType AffineClosure; + //typedef typename ShapeTraits<T>::IntersectionType Isect; + + T shape; + Time t; + Point p; + AffineClosure ac; + Affine m; + Coord c; + bool bool_; + + void constraints() { + p = shape.pointAt(t); + c = shape.valueAt(t, X); + //ivec = shape.intersect(other); + t = shape.nearestTime(p); + ac = shape; + ac *= m; + bool_ = (shape == shape); + bool_ = (shape != shape); + bool_ = shape.isDegenerate(); + } +}; + +template <typename T> inline T portion(const T& t, const Interval& i) { return portion(t, i.min(), i.max()); } template <typename T> diff --git a/src/2geom/conic_section_clipper.h b/src/2geom/conic_section_clipper.h index a02cda4d3..38bba338e 100644 --- a/src/2geom/conic_section_clipper.h +++ b/src/2geom/conic_section_clipper.h @@ -1,7 +1,6 @@ -/** - * \file - * \brief Conic section clipping with respect to a rectangle - * +/** @file + * @brief Conic section clipping with respect to a rectangle + *//* * Authors: * Marco Cecchetti <mrcekets at gmail> * @@ -34,8 +33,8 @@ -#ifndef _2GEOM_CONIC_SECTION_CLIPPER_H_ -#define _2GEOM_CONIC_SECTION_CLIPPER_H_ +#ifndef LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_H +#define LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_H #undef CLIP_WITH_CAIRO_SUPPORT diff --git a/src/2geom/conic_section_clipper_cr.h b/src/2geom/conic_section_clipper_cr.h index 31f5a4269..6c62494de 100644 --- a/src/2geom/conic_section_clipper_cr.h +++ b/src/2geom/conic_section_clipper_cr.h @@ -1,7 +1,6 @@ -/** - * \file - * \brief Conic section clipping with respect to a rectangle - * +/** @file + * @brief Conic section clipping with respect to a rectangle + *//* * Authors: * Marco Cecchetti <mrcekets at gmail> * @@ -39,8 +38,8 @@ //////////////////////////////////////////////////////////////////////////////// -#ifndef _2GEOM_CONIC_SECTION_CLIPPER_CR_H_ -#define _2GEOM_CONIC_SECTION_CLIPPER_CR_H_ +#ifndef LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_CR_H +#define LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_CR_H #define CLIP_WITH_CAIRO_SUPPORT diff --git a/src/2geom/conic_section_clipper_impl.cpp b/src/2geom/conic_section_clipper_impl.cpp index 2867e243c..c57307974 100644 --- a/src/2geom/conic_section_clipper_impl.cpp +++ b/src/2geom/conic_section_clipper_impl.cpp @@ -173,7 +173,7 @@ bool CLIPPER_CLASS::intersect (std::vector<Point> & crossing_points) const cpts.size()) // remove duplicates - std::sort (cpts.begin(), cpts.end(), Point::LexOrder<X>()); + std::sort (cpts.begin(), cpts.end(), Point::LexLess<X>()); cpts.erase (std::unique (cpts.begin(), cpts.end()), cpts.end()); @@ -203,7 +203,7 @@ bool CLIPPER_CLASS::intersect (std::vector<Point> & crossing_points) const inline double signed_triangle_area (Point const& p1, Point const& p2, Point const& p3) { - return (cross(p3, p2) - cross(p3, p1) + cross(p2, p1)); + return (cross(p2, p3) - cross(p1, p3) + cross(p1, p2)); } @@ -216,6 +216,8 @@ double signed_triangle_area (Point const& p1, Point const& p2, Point const& p3) */ bool CLIPPER_CLASS::are_paired (Point& M, const Point & P1, const Point & P2) const { + using std::swap; + /* * we looks for the points on the conic whose tangent is parallel to the * arc chord P1P2, they will be extrema of the conic arc P1P2 wrt the @@ -257,9 +259,8 @@ bool CLIPPER_CLASS::are_paired (Point& M, const Point & P1, const Point & P2) co if (sgn(side0) == sgn(side1)) { - if (std::fabs(side0) > std::fabs(side1)) - { - std::swap (extrema[0], extrema[1]); + if (std::fabs(side0) > std::fabs(side1)) { + swap(extrema[0], extrema[1]); } extrema.pop_back(); } @@ -371,6 +372,8 @@ void CLIPPER_CLASS::pairing (std::vector<Point> & paired_points, */ bool CLIPPER_CLASS::clip (std::vector<RatQuad> & arcs) { + using std::swap; + arcs.clear(); std::vector<Point> crossing_points; std::vector<Point> paired_points; @@ -454,14 +457,14 @@ bool CLIPPER_CLASS::clip (std::vector<RatQuad> & arcs) double angle = cs.axis_angle(); Line axis1 (*c, angle); rts = cs.roots (axis1); - if (rts[0] > rts[1]) std::swap (rts[0], rts[1]); + if (rts[0] > rts[1]) swap (rts[0], rts[1]); paired_points[0] = axis1.pointAt (rts[0]); paired_points[1] = axis1.pointAt (rts[1]); paired_points[2] = paired_points[1]; paired_points[3] = paired_points[0]; Line axis2 (*c, angle + M_PI/2); rts = cs.roots (axis2); - if (rts[0] > rts[1]) std::swap (rts[0], rts[1]); + if (rts[0] > rts[1]) swap (rts[0], rts[1]); inner_points.push_back (axis2.pointAt (rts[0])); inner_points.push_back (axis2.pointAt (rts[1])); } diff --git a/src/2geom/conic_section_clipper_impl.h b/src/2geom/conic_section_clipper_impl.h index 37415df97..e38a6d416 100644 --- a/src/2geom/conic_section_clipper_impl.h +++ b/src/2geom/conic_section_clipper_impl.h @@ -1,7 +1,6 @@ -/** - * \file - * \brief Conic section clipping with respect to a rectangle - * +/** @file + * @brief Conic section clipping with respect to a rectangle + *//* * Authors: * Marco Cecchetti <mrcekets at gmail> * @@ -31,11 +30,8 @@ * the specific language governing rights and limitations. */ - - - -#ifndef _2GEOM_CONIC_SECTION_CLIPPER_IMPL_H_ -#define _2GEOM_CONIC_SECTION_CLIPPER_IMPL_H_ +#ifndef LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_IMPL_H +#define LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_IMPL_H #include <2geom/conicsec.h> @@ -336,13 +332,7 @@ void CLIPPER_CLASS::rsplit (std::list<Point> & points, } // end namespace Geom - - - -#endif // _2GEOM_CONIC_SECTION_CLIPPER_IMPL_H_ - - - +#endif // LIB2GEOM_SEEN_CONIC_SECTION_CLIPPER_IMPL_H /* Local Variables: diff --git a/src/2geom/conicsec.cpp b/src/2geom/conicsec.cpp index 367dc2503..889797de3 100644 --- a/src/2geom/conicsec.cpp +++ b/src/2geom/conicsec.cpp @@ -40,44 +40,16 @@ #include <sstream> #include <stdexcept> - - - - namespace Geom { LineSegment intersection(Line l, Rect r) { - Point p0, p1; - double a,b,c; - std::vector<double> ifc = l.coefficients(); - a = ifc[0]; - b = ifc[1]; - c = ifc[2]; - if (fabs(b) > fabs(a)) { - p0 = Point(r[0][0], (-c - a*r[0][0])/b); - if (p0[1] < r[1][0]) - p0 = Point((-c - b*r[1][0])/a, r[1][0]); - if (p0[1] > r[1][1]) - p0 = Point((-c - b*r[1][1])/a, r[1][1]); - p1 = Point(r[0][1], (-c - a*r[0][1])/b); - if (p1[1] < r[1][0]) - p1 = Point((-c - b*r[1][0])/a, r[1][0]); - if (p1[1] > r[1][1]) - p1 = Point((-c - b*r[1][1])/a, r[1][1]); + boost::optional<LineSegment> seg = l.clip(r); + if (seg) { + return *seg; } else { - p0 = Point((-c - b*r[1][0])/a, r[1][0]); - if (p0[0] < r[0][0]) - p0 = Point(r[0][0], (-c - a*r[0][0])/b); - if (p0[0] > r[0][1]) - p0 = Point(r[0][1], (-c - a*r[0][1])/b); - p1 = Point((-c - b*r[1][1])/a, r[1][1]); - if (p1[0] < r[0][0]) - p1 = Point(r[0][0], (-c - a*r[0][0])/b); - if (p1[0] > r[0][1]) - p1 = Point(r[0][1], (-c - a*r[0][1])/b); + return LineSegment(Point(0,0), Point(0,0)); } - return LineSegment(p0, p1); } static double det(Point a, Point b) { @@ -673,7 +645,7 @@ std::vector<double> xAx::roots(Line const &l) const { Interval xAx::quad_ex(double a, double b, double c, Interval ivl) { double cx = -b*0.5/a; - Interval bnds((a*ivl[0]+b)*ivl[0]+c, (a*ivl[1]+b)*ivl[1]+c); + Interval bnds((a*ivl.min()+b)*ivl.min()+c, (a*ivl.max()+b)*ivl.max()+c); if(ivl.contains(cx)) bnds.expandTo((a*cx+b)*cx+c); return bnds; @@ -714,14 +686,14 @@ Interval xAx::extrema(Rect r) const { ext |= Interval(valueAt(r.corner(i))); return ext; } - double k = r[0][0]; - Interval ext = quad_ex(c[2], c[1]*k+c[4], (c[0]*k + c[3])*k + c[5], r[1]); - k = r[0][1]; - ext |= quad_ex(c[2], c[1]*k+c[4], (c[0]*k + c[3])*k + c[5], r[1]); - k = r[1][0]; - ext |= quad_ex(c[0], c[1]*k+c[3], (c[2]*k + c[4])*k + c[5], r[0]); - k = r[1][1]; - ext |= quad_ex(c[0], c[1]*k+c[3], (c[2]*k + c[4])*k + c[5], r[0]); + double k = r[X].min(); + Interval ext = quad_ex(c[2], c[1]*k+c[4], (c[0]*k + c[3])*k + c[5], r[Y]); + k = r[X].max(); + ext |= quad_ex(c[2], c[1]*k+c[4], (c[0]*k + c[3])*k + c[5], r[Y]); + k = r[Y].min(); + ext |= quad_ex(c[0], c[1]*k+c[3], (c[2]*k + c[4])*k + c[5], r[X]); + k = r[Y].max(); + ext |= quad_ex(c[0], c[1]*k+c[3], (c[2]*k + c[4])*k + c[5], r[X]); boost::optional<Point> B0 = bottom(); if (B0 && r.contains(*B0)) ext.expandTo(0); @@ -753,7 +725,7 @@ bool at_infinity (Point const& p) inline double signed_triangle_area (Point const& p1, Point const& p2, Point const& p3) { - return (cross(p3, p2) - cross(p3, p1) + cross(p2, p1)); + return (cross(p2, p3) - cross(p1, p3) + cross(p1, p2)); } @@ -801,6 +773,8 @@ void xAx::set(std::vector<Point> const& points) */ void xAx::set (const Point& _vertex, double _angle, double _dist1, double _dist2) { + using std::swap; + if (_dist2 == infinity() || _dist2 == -infinity()) // parabola { if (_dist1 == infinity()) // degenerate to a line @@ -842,7 +816,7 @@ void xAx::set (const Point& _vertex, double _angle, double _dist1, double _dist2 if (std::fabs(_dist1) > std::fabs(_dist2)) { - std::swap (_dist1, _dist2); + swap (_dist1, _dist2); } if (_dist1 < 0) { @@ -1442,38 +1416,35 @@ Rect xAx::arc_bound (const Point & P1, const Point & Q, const Point & P2) const if (sgn(Mside) == sgn(Qside)) { //std::cout << "BOUND: M.size() == 1" << std::endl; - if (M[0][dim] > B[dim][1]) - B[dim][1] = M[0][dim]; - else if (M[0][dim] < B[dim][0]) - B[dim][0] = M[0][dim]; + B[dim].expandTo(M[0][dim]); } } else if (M.size() == 2) { //std::cout << "BOUND: M.size() == 2" << std::endl; if (M[0][dim] > M[1][dim]) - std::swap (M[0], M[1]); + swap (M[0], M[1]); - if (M[0][dim] > B[dim][1]) + if (M[0][dim] > B[dim].max()) { double Mside = signed_triangle_area (P1, M[0], P2); if (sgn(Mside) == sgn(Qside)) - B[dim][1] = M[0][dim]; + B[dim].setMax(M[0][dim]); } - else if (M[1][dim] < B[dim][0]) + else if (M[1][dim] < B[dim].min()) { double Mside = signed_triangle_area (P1, M[1], P2); if (sgn(Mside) == sgn(Qside)) - B[dim][0] = M[1][dim]; + B[dim].setMin(M[1][dim]); } else { double Mside = signed_triangle_area (P1, M[0], P2); if (sgn(Mside) == sgn(Qside)) - B[dim][0] = M[0][dim]; + B[dim].setMin(M[0][dim]); Mside = signed_triangle_area (P1, M[1], P2); if (sgn(Mside) == sgn(Qside)) - B[dim][1] = M[1][dim]; + B[dim].setMax(M[1][dim]); } } } @@ -1486,7 +1457,7 @@ Rect xAx::arc_bound (const Point & P1, const Point & Q, const Point & P2) const * * P: the point to compute the nearest one */ -std::vector<Point> xAx::allNearestPoints (const Point &P) const +std::vector<Point> xAx::allNearestTimes (const Point &P) const { // TODO: manage the circle - centre case std::vector<Point> points; diff --git a/src/2geom/conicsec.h b/src/2geom/conicsec.h index ec9a430d9..e9c466978 100644 --- a/src/2geom/conicsec.h +++ b/src/2geom/conicsec.h @@ -1,7 +1,6 @@ -/** - * \file - * \brief Conic Section - * +/** @file + * @brief Conic Section + *//* * Authors: * Nathan Hurst <njh@njhurst.com> * @@ -32,8 +31,8 @@ */ -#ifndef _2GEOM_CONIC_SECTION_H_ -#define _2GEOM_CONIC_SECTION_H_ +#ifndef LIB2GEOM_SEEN_CONICSEC_H +#define LIB2GEOM_SEEN_CONICSEC_H #include <2geom/exception.h> #include <2geom/angle.h> @@ -474,22 +473,22 @@ public: Rect arc_bound (const Point & P1, const Point & Q, const Point & P2) const; - std::vector<Point> allNearestPoints (const Point &P) const; + std::vector<Point> allNearestTimes (const Point &P) const; /* * Return the point on the conic section nearest to the passed point "P". * * P: the point to compute the nearest one */ - Point nearestPoint (const Point &P) const + Point nearestTime (const Point &P) const { - std::vector<Point> points = allNearestPoints (P); + std::vector<Point> points = allNearestTimes (P); if ( !points.empty() ) { return points.front(); } // else - THROW_LOGICALERROR ("nearestPoint: no nearest point found"); + THROW_LOGICALERROR ("nearestTime: no nearest point found"); return Point(); } @@ -509,8 +508,7 @@ inline std::ostream &operator<< (std::ostream &out_file, const xAx &x) { }; -#endif // _2GEOM_CONIC_SECTION_H_ - +#endif // LIB2GEOM_SEEN_CONICSEC_H /* Local Variables: diff --git a/src/2geom/conjugate_gradient.cpp b/src/2geom/conjugate_gradient.cpp deleted file mode 100644 index 588513414..000000000 --- a/src/2geom/conjugate_gradient.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * conjugate_gradient.cpp - * - * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au> - * - * This library is free software; you can redistribute it and/or - * modify it either under the terms of the GNU Lesser General Public - * License version 2.1 as published by the Free Software Foundation - * (the "LGPL") or, at your option, under the terms of the Mozilla - * Public License Version 1.1 (the "MPL"). If you do not alter this - * notice, a recipient may use your version of this file under either - * the MPL or the LGPL. - * - * You should have received a copy of the LGPL along with this library - * in the file COPYING-LGPL-2.1; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * You should have received a copy of the MPL along with this library - * in the file COPYING-MPL-1.1 - * - * The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY - * OF ANY KIND, either express or implied. See the LGPL or the MPL for - * the specific language governing rights and limitations. - * - */ - -#include <math.h> -#include <stdlib.h> -#include <valarray> -#include <cassert> -#include <2geom/conjugate_gradient.h> - -/* lifted wholely from wikipedia. */ - -namespace Geom -{ - -using std::valarray; - -static void -matrix_times_vector(valarray<double> const &matrix, /* m * n */ - valarray<double> const &vec, /* n */ - valarray<double> &result) /* m */ -{ - unsigned n = vec.size(); - unsigned m = result.size(); - assert(m*n == matrix.size()); - const double* mp = &const_cast<valarray<double>&>(matrix)[0]; - for (unsigned i = 0; i < m; i++) { - double res = 0; - for (unsigned j = 0; j < n; j++) - res += *mp++ * vec[j]; - result[i] = res; - } -} - -/** -// only used in commented code below -static double Linfty(valarray<double> const &vec) { - return std::max(vec.max(), -vec.min()); -} -**/ - -double -inner(valarray<double> const &x, - valarray<double> const &y) { - double total = 0; - for(unsigned i = 0; i < x.size(); i++) - total += x[i]*y[i]; - return total;// (x*y).sum(); <- this is more concise, but ineff -} - -void -conjugate_gradient(double **A, - double *x, - double *b, - unsigned n, - double tol, - int max_iterations, - bool ortho1) { - valarray<double> vA(n*n); - valarray<double> vx(n); - valarray<double> vb(n); - for(unsigned i=0;i<n;i++) { - vx[i]=x[i]; - vb[i]=b[i]; - for(unsigned j=0;j<n;j++) { - vA[i*n+j]=A[i][j]; - } - } - conjugate_gradient(vA,vx,vb,n,tol,max_iterations,ortho1); - for(unsigned i=0;i<n;i++) { - x[i]=vx[i]; - } -} -void -conjugate_gradient(valarray<double> const &A, - valarray<double> &x, - valarray<double> const &b, - unsigned n, double tol, - unsigned max_iterations, bool /*ortho1*/) { - valarray<double> Ap(n), p(n), r(n); - matrix_times_vector(A,x,Ap); - r=b-Ap; - double r_r = inner(r,r); - unsigned k = 0; - tol *= tol; - while(k < max_iterations && r_r > tol) { - k++; - double r_r_new = r_r; - if(k == 1) - p = r; - else { - r_r_new = inner(r,r); - p = r + (r_r_new/r_r)*p; - } - matrix_times_vector(A, p, Ap); - double alpha_k = r_r_new / inner(p, Ap); - x += alpha_k*p; - r -= alpha_k*Ap; - r_r = r_r_new; - } - //printf("njh: %d iters, Linfty = %g L2 = %g\n", k, - //std::max(-r.min(), r.max()), sqrt(r_r)); - // x is solution -} - -} // namespace Geom - -/* - 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/2geom/convex-cover.h b/src/2geom/convex-cover.h deleted file mode 100644 index d290b7e80..000000000 --- a/src/2geom/convex-cover.h +++ /dev/null @@ -1,204 +0,0 @@ -/** - * \file - * \brief Dynamic convex hull structure - * - * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au> - * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com> - * - * This library is free software; you can redistribute it and/or - * modify it either under the terms of the GNU Lesser General Public - * License version 2.1 as published by the Free Software Foundation - * (the "LGPL") or, at your option, under the terms of the Mozilla - * Public License Version 1.1 (the "MPL"). If you do not alter this - * notice, a recipient may use your version of this file under either - * the MPL or the LGPL. - * - * You should have received a copy of the LGPL along with this library - * in the file COPYING-LGPL-2.1; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * You should have received a copy of the MPL along with this library - * in the file COPYING-MPL-1.1 - * - * The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY - * OF ANY KIND, either express or implied. See the LGPL or the MPL for - * the specific language governing rights and limitations. - * - */ - -#ifndef GEOM_CONVEX_COVER_H -#define GEOM_CONVEX_COVER_H - -#include <2geom/point.h> -#include <vector> - -namespace Geom{ - -/* A convex cover is a sequence of convex polygons that completely cover the path. For now a - * convex hull class is included here (the convex-hull header is wrong) - */ - -/** ConvexHull - * A convexhull is a convex region - every point between two points in the convex hull is also in - * the convex hull. It is defined by a set of points travelling in a clockwise direction. We require the first point to be top most, and of the topmost, leftmost. - - * An empty hull has no points, we allow a single point or two points degenerate cases. - - * We could provide the centroid as a member for efficient direction determination. We can update the - * centroid with all operations with the same time complexity as the operation. - */ - -class ConvexHull{ -public: // XXX: should be private :) - // extracts the convex hull of boundary. internal use only - void find_pivot(); - void angle_sort(); - void graham_scan(); - void andrew_scan(); - void graham(); -public: - std::vector<Point> boundary; - //Point centroid; - - void merge(Point p); - bool contains_point(Point p); - bool strict_contains_point(Point p); - - inline size_t size() const { return boundary.size();} - inline Point operator[](int i) const { - - int l = boundary.size(); - if(l == 0) return Point(); - return boundary[i >= 0 ? i % l : (i % l) + l]; - } - - /*inline Point &operator[](unsigned i) { - int l = boundary.size(); - if(l == 0) return Point(); - return boundary[i >= 0 ? i % l : i % l + l]; - }*/ - -public: - ConvexHull() {} - ConvexHull(std::vector<Point> const & points) : - boundary (points) - { - graham(); - } - - template <typename T> - ConvexHull(T b, T e) :boundary(b,e) {} - - ~ConvexHull() - { - } - -public: - /** Is the convex hull clockwise? We use the definition of clockwise from point.h - **/ - bool is_clockwise() const; - bool top_point_first() const; - bool meets_invariants() const; - - // contains no points - bool empty() const { return boundary.empty();} - - // contains exactly one point - bool singular() const { return boundary.size() == 1;} - - // all points are on a line - bool linear() const { return boundary.size() == 2;} - bool is_degenerate() const; - - // area of the convex hull - double centroid_and_area(Geom::Point& centroid) const; - double area() const { - Point tmp; - return centroid_and_area(tmp); - } - - // furthest point in a direction (lg time) - Point const * furthest(Point direction) const; - - bool is_left(Point p, int n); - bool is_strict_left(Point p, int n); - int find_left(Point p); - int find_strict_left(Point p); - double narrowest_diameter(Point &a, Point &b, Point &c); - -}; -/** @brief Output operator for points. - * Prints out all the coordinates. */ -inline std::ostream &operator<< (std::ostream &out_file, const Geom::ConvexHull &in_cvx) { - out_file << "ConvexHull("; - for(unsigned i = 0; i < in_cvx.size(); i++) { - out_file << in_cvx.boundary[i] << ", "; - } - out_file << ")"; - return out_file; -} - -// do two convex hulls intersect? -bool intersectp(ConvexHull a, ConvexHull b); - -std::vector<std::pair<int, int> > bridges(ConvexHull a, ConvexHull b); - -// find the convex hull intersection -ConvexHull intersection(ConvexHull a, ConvexHull b); -ConvexHull sweepline_intersection(ConvexHull const &a, ConvexHull const &b); - -// find the convex hull of a set of convex hulls -ConvexHull merge(ConvexHull a, ConvexHull b); - -// naive approach -ConvexHull graham_merge(ConvexHull a, ConvexHull b); - -// naive approach -ConvexHull andrew_merge(ConvexHull a, ConvexHull b); - -unsigned find_bottom_right(ConvexHull const &a); - -/*** Arbitrary transform operator. - * Take a convex hull and apply an arbitrary convexity preserving transform. - * we should be concerned about singular tranforms here. - */ -template <class T> ConvexHull operator*(ConvexHull const &p, T const &m) { - ConvexHull pr; - - pr.boundary.reserve(p.boundary.size()); - - for(unsigned i = 0; i < p.boundary.size(); i++) { - pr.boundary.push_back(p.boundary[i]*m); - } - return pr; -} - -ConvexHull clip(ConvexHull const & ch, Point n, double d); - -//TODO: reinstate -/*class ConvexCover{ -public: - Path const* path; - std::vector<ConvexHull> cc; - - ConvexCover(Path const &sp); -};*/ - -}; - -#endif //2GEOM_CONVEX_COVER_H - -/* - 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/2geom/convex-cover.cpp b/src/2geom/convex-hull.cpp index 5e599fdde..4f5e06733 100644 --- a/src/2geom/convex-cover.cpp +++ b/src/2geom/convex-hull.cpp @@ -1,8 +1,11 @@ -/* - * convex-cover.cpp - * - * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au> - * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com> +/** @file + * @brief Convex hull of a set of points + *//* + * Authors: + * Nathan Hurst <njh@mail.csse.monash.edu.au> + * Michael G. Sloan <mgsloan@gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * Copyright 2006-2015 Authors * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -29,11 +32,13 @@ * */ -#include <2geom/convex-cover.h> +#include <2geom/convex-hull.h> #include <2geom/exception.h> #include <algorithm> #include <map> -#include <assert.h> +#include <iostream> +#include <cassert> +#include <boost/array.hpp> /** Todo: + modify graham scan to work top to bottom, rather than around angles @@ -50,9 +55,237 @@ using std::vector; using std::map; using std::pair; using std::make_pair; +using std::swap; + +namespace Geom { + +ConvexHull::ConvexHull(Point const &a, Point const &b) + : _boundary(2) + , _lower(0) +{ + _boundary[0] = a; + _boundary[1] = b; + std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>()); + _construct(); +} + +ConvexHull::ConvexHull(Point const &a, Point const &b, Point const &c) + : _boundary(3) + , _lower(0) +{ + _boundary[0] = a; + _boundary[1] = b; + _boundary[2] = c; + std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>()); + _construct(); +} + +ConvexHull::ConvexHull(Point const &a, Point const &b, Point const &c, Point const &d) + : _boundary(4) + , _lower(0) +{ + _boundary[0] = a; + _boundary[1] = b; + _boundary[2] = c; + _boundary[3] = d; + std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>()); + _construct(); +} + +ConvexHull::ConvexHull(std::vector<Point> const &pts) + : _lower(0) +{ + //if (pts.size() > 16) { // arbitrary threshold + // _prune(pts.begin(), pts.end(), _boundary); + //} else { + _boundary = pts; + std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>()); + //} + _construct(); +} + +bool ConvexHull::_is_clockwise_turn(Point const &a, Point const &b, Point const &c) +{ + if (b == c) return false; + return cross(b-a, c-a) > 0; +} + +void ConvexHull::_construct() +{ + // _boundary must already be sorted in LexLess<X> order + if (_boundary.empty()) { + _lower = 0; + return; + } + if (_boundary.size() == 1 || (_boundary.size() == 2 && _boundary[0] == _boundary[1])) { + _boundary.resize(1); + _lower = 1; + return; + } + if (_boundary.size() == 2) { + _lower = 2; + return; + } + + std::size_t k = 2; + for (std::size_t i = 2; i < _boundary.size(); ++i) { + while (k >= 2 && !_is_clockwise_turn(_boundary[k-2], _boundary[k-1], _boundary[i])) { + --k; + } + std::swap(_boundary[k++], _boundary[i]); + } + + _lower = k; + std::sort(_boundary.begin() + k, _boundary.end(), Point::LexGreater<X>()); + _boundary.push_back(_boundary.front()); + for (std::size_t i = _lower; i < _boundary.size(); ++i) { + while (k > _lower && !_is_clockwise_turn(_boundary[k-2], _boundary[k-1], _boundary[i])) { + --k; + } + std::swap(_boundary[k++], _boundary[i]); + } + + _boundary.resize(k-1); +} + +double ConvexHull::area() const +{ + if (size() <= 2) return 0; + + double a = 0; + for (std::size_t i = 0; i < size()-1; ++i) { + a += cross(_boundary[i], _boundary[i+1]); + } + a += cross(_boundary.back(), _boundary.front()); + return fabs(a * 0.5); +} + +OptRect ConvexHull::bounds() const +{ + OptRect ret; + if (empty()) return ret; + ret = Rect(left(), top(), right(), bottom()); + return ret; +} + +Point ConvexHull::topPoint() const +{ + Point ret; + ret[Y] = std::numeric_limits<Coord>::infinity(); + + for (UpperIterator i = upperHull().begin(); i != upperHull().end(); ++i) { + if (ret[Y] >= i->y()) { + ret = *i; + } else { + break; + } + } + + return ret; +} + +Point ConvexHull::bottomPoint() const +{ + Point ret; + ret[Y] = -std::numeric_limits<Coord>::infinity(); + + for (LowerIterator j = lowerHull().begin(); j != lowerHull().end(); ++j) { + if (ret[Y] <= j->y()) { + ret = *j; + } else { + break; + } + } + + return ret; +} + +template <typename Iter, typename Lex> +bool below_x_monotonic_polyline(Point const &p, Iter first, Iter last, Lex lex) +{ + typename Lex::Secondary above; + Iter f = std::lower_bound(first, last, p, lex); + if (f == last) return false; + if (f == first) { + if (p == *f) return true; + return false; + } + + Point a = *(f-1), b = *f; + if (a[X] == b[X]) { + if (above(p[Y], a[Y]) || above(b[Y], p[Y])) return false; + } else { + // TODO: maybe there is a more numerically stable method + Coord y = lerp((p[X] - a[X]) / (b[X] - a[X]), a[Y], b[Y]); + if (above(p[Y], y)) return false; + } + return true; +} + +bool ConvexHull::contains(Point const &p) const +{ + if (_boundary.empty()) return false; + if (_boundary.size() == 1) { + if (_boundary[0] == p) return true; + return false; + } + + // 1. verify that the point is in the relevant X range + if (p[X] < _boundary[0][X] || p[X] > _boundary[_lower-1][X]) return false; + + // 2. check whether it is below the upper hull + UpperIterator ub = upperHull().begin(), ue = upperHull().end(); + if (!below_x_monotonic_polyline(p, ub, ue, Point::LexLess<X>())) return false; + + // 3. check whether it is above the lower hull + LowerIterator lb = lowerHull().begin(), le = lowerHull().end(); + if (!below_x_monotonic_polyline(p, lb, le, Point::LexGreater<X>())) return false; + + return true; +} + +bool ConvexHull::contains(Rect const &r) const +{ + for (unsigned i = 0; i < 4; ++i) { + if (!contains(r.corner(i))) return false; + } + return true; +} + +bool ConvexHull::contains(ConvexHull const &ch) const +{ + // TODO: requires interiorContains. + // We have to check all points of ch, and each point takes logarithmic time. + // If there are more points in ch that here, it is faster to make the check + // the other way around. + /*if (ch.size() > size()) { + for (iterator i = begin(); i != end(); ++i) { + if (ch.interiorContains(*i)) return false; + } + return true; + }*/ + + for (iterator i = ch.begin(); i != ch.end(); ++i) { + if (!contains(*i)) return false; + } + return true; +} + +void ConvexHull::swap(ConvexHull &other) +{ + _boundary.swap(other._boundary); + std::swap(_lower, other._lower); +} -namespace Geom{ +void ConvexHull::swap(std::vector<Point> &pts) +{ + _boundary.swap(pts); + _lower = 0; + std::sort(_boundary.begin(), _boundary.end(), Point::LexLess<X>()); + _construct(); +} +#if 0 /*** SignedTriangleArea * returns the area of the triangle defined by p0, p1, p2. A clockwise triangle has positive area. */ @@ -118,147 +351,6 @@ public: #endif }; -void -ConvexHull::find_pivot() { - // Find pivot P; - unsigned pivot = 0; - for (unsigned i = 1; i < boundary.size(); i++) - if(boundary[i] <= boundary[pivot]) - pivot = i; - - std::swap(boundary[0], boundary[pivot]); -} - -void -ConvexHull::angle_sort() { -// sort points by angle (resolve ties in favor of point farther from P); -// we leave the first one in place as our pivot - std::sort(boundary.begin()+1, boundary.end(), angle_cmp(boundary[0])); -} - - -void -ConvexHull::graham_scan() { - // prune out equal points. points are sorted, so equals are adjacent - std::vector<Point>::iterator e = - std::unique(boundary.begin(), boundary.end()); - boundary.resize(e - boundary.begin()); - for(unsigned int i = 2; i < boundary.size(); i++) { - - } - if (boundary.size() < 4) { - return; - } - unsigned stac = 2; - for(unsigned int i = 2; i < boundary.size(); i++) { - double o = SignedTriangleArea(boundary[stac-2], - boundary[stac-1], - boundary[i]); - if(o == 0) { // colinear - dangerous... - stac--; - } else if(o < 0) { // anticlockwise - } else { // remove concavity - while(o >= 0 && stac > 2) { - stac--; - o = SignedTriangleArea(boundary[stac-2], - boundary[stac-1], - boundary[i]); - } - } - boundary[stac++] = boundary[i]; - } - boundary.resize(stac); -} - -// following code is from marco. - -/* - * return true in case the oriented polyline p0, p1, p2 is a right turn - */ -inline -bool is_a_right_turn (Point const& p0, Point const& p1, Point const& p2) -{ - if (p1 == p2) return false; - Point q1 = p1 - p0; - Point q2 = p2 - p0; - if (q1 == -q2) return false; - return (cross (q1, q2) < 0); -} - -/* - * return true if p < q wrt the lexicographyc order induced by the coordinates - */ -struct lex_less -{ - bool operator() (Point const& p, Point const& q) - { - return ((p[Y] < q[Y]) || (p[Y] == q[Y] && p[X] < q[X])); - } -}; - -/* - * return true if p > q wrt the lexicographyc order induced by the coordinates - */ -struct lex_greater -{ - bool operator() (Point const& p, Point const& q) - { - return ((p[Y] > q[Y]) || (p[Y] == q[Y] && p[X] > q[X])); - } -}; - -/* - * Compute the convex hull of a set of points. - * The implementation is based on the Andrew's scan algorithm - * note: in the Bezier clipping for collinear normals it seems - * to be more stable wrt the Graham's scan algorithm and in general - * a bit quikier - */ -void ConvexHull::andrew_scan () -{ - vector<Point> & P = boundary; - size_t n = P.size(); - if (n < 2) return; - std::sort(P.begin(), P.end(), lex_less()); - if (n < 4) return; - // upper hull - size_t u = 2; - for (size_t i = 2; i < n; ++i) - { - while (u > 1 && !is_a_right_turn(P[u-2], P[u-1], P[i])) - { - --u; - } - std::swap(P[u], P[i]); - ++u; - } - std::sort(P.begin() + u, P.end(), lex_greater()); - std::rotate(P.begin(), P.begin() + 1, P.end()); - // lower hull - size_t l = u; - size_t k = u - 1; - for (size_t i = l; i < n; ++i) - { - while (l > k && !is_a_right_turn(P[l-2], P[l-1], P[i])) - { - --l; - } - std::swap(P[l], P[i]); - ++l; - } - P.resize(l); -} - -void -ConvexHull::graham() { - /*if(is_degenerate()) // nothing to do - return;*/ - //find_pivot(); - //angle_sort(); - //graham_scan(); - andrew_scan(); -} - //Mathematically incorrect mod, but more useful. int mod(int i, int l) { return i >= 0 ? @@ -266,65 +358,6 @@ int mod(int i, int l) { } //OPT: usages can often be replaced by conditions -/*** ConvexHull::left - * Tests if a point is left (outside) of a particular segment, n. */ -bool -ConvexHull::is_left(Point p, int n) { - return SignedTriangleArea((*this)[n], (*this)[n+1], p) > 0; -} - -/*** ConvexHull::strict_left - * Tests if a point is left (outside) of a particular segment, n. */ -bool -ConvexHull::is_strict_left(Point p, int n) { - return SignedTriangleArea((*this)[n], (*this)[n+1], p) >= 0; -} - -/*** ConvexHull::find_left - * May return any number n where the segment n -> n + 1 (possibly looped around) in the hull such - * that the point is on the wrong side to be within the hull. Returns -1 if it is within the hull.*/ -int -ConvexHull::find_left(Point p) { - int l = boundary.size(); //Who knows if C++ is smart enough to optimize this? - for(int i = 0; i < l; i++) { - if(is_left(p, i)) return i; - } - return -1; -} - - -/*** ConvexHull::find_positive - * May return any number n where the segment n -> n + 1 (possibly looped around) in the hull such - * that the point is on the wrong side to be within the hull. Returns -1 if it is within the hull.*/ -int -ConvexHull::find_strict_left(Point p) { - int l = boundary.size(); //Who knows if C++ is smart enough to optimize this? - for(int i = 0; i < l; i++) { - if(is_strict_left(p, i)) return i; - } - return -1; -} - -//OPT: do a spread iteration - quasi-random with no repeats and full coverage. - -/*** ConvexHull::contains_point - * In order to test whether a point is inside a convex hull we can travel once around the outside making - * sure that each triangle made from an edge and the point has positive area. */ -bool -ConvexHull::contains_point(Point p) { - if(size() == 0) return false; - return find_left(p) == -1; -} - -/*** ConvexHull::strict_contains_point - * In order to test whether a point is strictly inside (not on the boundary) a convex hull we can travel once around the outside making - * sure that each triangle made from an edge and the point has positive area. */ -bool -ConvexHull::strict_contains_point(Point p) { - if(size() == 0) return false; - return find_strict_left(p) == -1; -} - /*** ConvexHull::add_point * to add a point we need to find whether the new point extends the boundary, and if so, what it * obscures. Tarjan? Jarvis?*/ @@ -332,18 +365,18 @@ void ConvexHull::merge(Point p) { std::vector<Point> out; - int l = boundary.size(); + int len = boundary.size(); - if(l < 2) { + if(len < 2) { if(boundary.empty() || boundary[0] != p) boundary.push_back(p); return; } bool pushed = false; - + bool pre = is_left(p, -1); - for(int i = 0; i < l; i++) { + for(int i = 0; i < len; i++) { bool cur = is_left(p, i); if(pre) { if(cur) { @@ -595,7 +628,7 @@ ConvexHull graham_merge(ConvexHull a, ConvexHull b) { // we can avoid the find pivot step because of top_point_first if(b.boundary[0] <= a.boundary[0]) - std::swap(a, b); + swap(a, b); result.boundary = a.boundary; result.boundary.insert(result.boundary.end(), @@ -615,7 +648,7 @@ ConvexHull andrew_merge(ConvexHull a, ConvexHull b) { // we can avoid the find pivot step because of top_point_first if(b.boundary[0] <= a.boundary[0]) - std::swap(a, b); + swap(a, b); result.boundary = a.boundary; result.boundary.insert(result.boundary.end(), @@ -697,6 +730,7 @@ double ConvexHull::narrowest_diameter(Point &a, Point &b, Point &c) { } return d; } +#endif }; diff --git a/src/2geom/convex-hull.h b/src/2geom/convex-hull.h new file mode 100644 index 000000000..4f4d10bd6 --- /dev/null +++ b/src/2geom/convex-hull.h @@ -0,0 +1,346 @@ +/** @file + * @brief Convex hull data structures + *//* + * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au> + * Copyright 2006 Michael G. Sloan <mgsloan@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + +#ifndef LIB2GEOM_SEEN_CONVEX_HULL_H +#define LIB2GEOM_SEEN_CONVEX_HULL_H + +#include <2geom/point.h> +#include <2geom/rect.h> +#include <vector> +#include <algorithm> +#include <boost/operators.hpp> +#include <boost/optional.hpp> +#include <boost/range/iterator_range.hpp> + +namespace Geom { + +namespace { + +/** @brief Iterator for the lower convex hull. + * This iterator allows us to avoid duplicating any points in the hull + * boundary and still express most algorithms in a concise way. */ +class ConvexHullLowerIterator + : public boost::random_access_iterator_helper + < ConvexHullLowerIterator + , Point + , std::ptrdiff_t + , Point const * + , Point const & + > +{ +public: + typedef ConvexHullLowerIterator Self; + ConvexHullLowerIterator() + : _data(NULL) + , _size(0) + , _x(0) + {} + ConvexHullLowerIterator(std::vector<Point> const &pts, std::size_t x) + : _data(&pts[0]) + , _size(pts.size()) + , _x(x) + {} + + Self &operator++() { + *this += 1; + return *this; + } + Self &operator--() { + *this -= 1; + return *this; + } + Self &operator+=(std::ptrdiff_t d) { + _x += d; + return *this; + } + Self &operator-=(std::ptrdiff_t d) { + _x -= d; + return *this; + } + std::ptrdiff_t operator-(Self const &other) const { + return _x - other._x; + } + Point const &operator*() const { + if (_x < _size) { + return _data[_x]; + } else { + return *_data; + } + } + bool operator==(Self const &other) const { + return _data == other._data && _x == other._x; + } + bool operator<(Self const &other) const { + return _data == other._data && _x < other._x; + } + +private: + Point const *_data; + std::size_t _size; + std::size_t _x; +}; + +} // end anonymous namespace + +/** + * @brief Convex hull based on the Andrew's monotone chain algorithm. + * @ingroup Shapes + */ +class ConvexHull { +public: + typedef std::vector<Point>::const_iterator iterator; + typedef std::vector<Point>::const_iterator const_iterator; + typedef std::vector<Point>::const_iterator UpperIterator; + typedef ConvexHullLowerIterator LowerIterator; + + /// @name Construct a convex hull. + /// @{ + + /// Create an empty convex hull. + ConvexHull() {} + /// Construct a singular convex hull. + explicit ConvexHull(Point const &a) + : _boundary(1, a) + , _lower(1) + {} + /// Construct a convex hull of two points. + ConvexHull(Point const &a, Point const &b); + /// Construct a convex hull of three points. + ConvexHull(Point const &a, Point const &b, Point const &c); + /// Construct a convex hull of four points. + ConvexHull(Point const &a, Point const &b, Point const &c, Point const &d); + /// Create a convex hull of a vector of points. + ConvexHull(std::vector<Point> const &pts); + + /// Create a convex hull of a range of points. + template <typename Iter> + ConvexHull(Iter first, Iter last) + : _lower(0) + { + _prune(first, last, _boundary); + _construct(); + } + /// @} + + /// @name Inspect basic properties. + /// @{ + + /// Check for emptiness. + bool empty() const { return _boundary.empty(); } + /// Get the number of points in the hull. + size_t size() const { return _boundary.size(); } + /// Check whether the hull contains only one point. + bool isSingular() const { return _boundary.size() == 1; } + /// Check whether the hull is a line. + bool isLinear() const { return _boundary.size() == 2; } + /// Check whether the hull has zero area. + bool isDegenerate() const { return _boundary.size() < 3; } + /// Calculate the area of the convex hull. + double area() const; + //Point centroid() const; + //double areaAndCentroid(Point &c); + //FatLine maxDiameter() const; + //FatLine minDiameter() const; + /// @} + + /// @name Inspect bounds and extreme points. + /// @{ + + /// Compute the bounding rectangle of the convex hull. + OptRect bounds() const; + + /// Get the leftmost (minimum X) coordinate of the hull. + Coord left() const { return _boundary[0][X]; } + /// Get the rightmost (maximum X) coordinate of the hull. + Coord right() const { return _boundary[_lower-1][X]; } + /// Get the topmost (minimum Y) coordinate of the hull. + Coord top() const { return topPoint()[Y]; } + /// Get the bottommost (maximum Y) coordinate of the hull. + Coord bottom() const { return bottomPoint()[Y]; } + + /// Get the leftmost (minimum X) point of the hull. + /// If the leftmost edge is vertical, the top point of the edge is returned. + Point leftPoint() const { return _boundary[0]; } + /// Get the rightmost (maximum X) point of the hull. + /// If the rightmost edge is vertical, the bottom point edge is returned. + Point rightPoint() const { return _boundary[_lower-1]; } + /// Get the topmost (minimum Y) point of the hull. + /// If the topmost edge is horizontal, the right point of the edge is returned. + Point topPoint() const; + /// Get the bottommost (maximum Y) point of the hull. + /// If the bottommost edge is horizontal, the left point of the edge is returned. + Point bottomPoint() const; + ///@} + + /// @name Iterate over points. + /// @{ + /** @brief Get the begin iterator to the points that form the hull. + * Points are are returned beginning the the leftmost one, going along + * the upper (minimum Y) side, and then along the bottom. + * Thus the points are always ordered clockwise. No point is + * repeated. */ + iterator begin() const { return _boundary.begin(); } + /// Get the end iterator to the points that form the hull. + iterator end() const { return _boundary.end(); } + /// Get the first, leftmost point in the hull. + Point const &front() const { return _boundary.front(); } + /// Get the penultimate point of the lower hull. + Point const &back() const { return _boundary.back(); } + Point const &operator[](std::size_t i) const { + return _boundary[i]; + } + + /** @brief Get an iterator range to the upper part of the hull. + * This returns a range that includes the leftmost point, + * all points of the upper hull, and the rightmost point. */ + boost::iterator_range<UpperIterator> upperHull() const { + boost::iterator_range<UpperIterator> r(_boundary.begin(), _boundary.begin() + _lower); + return r; + } + + /** @brief Get an iterator range to the lower part of the hull. + * This returns a range that includes the leftmost point, + * all points of the lower hull, and the rightmost point. */ + boost::iterator_range<LowerIterator> lowerHull() const { + if (_boundary.empty()) { + boost::iterator_range<LowerIterator> r(LowerIterator(_boundary, 0), + LowerIterator(_boundary, 0)); + return r; + } + if (_boundary.size() == 1) { + boost::iterator_range<LowerIterator> r(LowerIterator(_boundary, 0), + LowerIterator(_boundary, 1)); + return r; + } + boost::iterator_range<LowerIterator> r(LowerIterator(_boundary, _lower - 1), + LowerIterator(_boundary, _boundary.size() + 1)); + return r; + } + /// @} + + /// @name Check for containment and intersection. + /// @{ + /** @brief Check whether the given point is inside the hull. + * This takes logarithmic time. */ + bool contains(Point const &p) const; + /** @brief Check whether the given axis-aligned rectangle is inside the hull. + * A rectangle is inside the hull if all of its corners are inside. */ + bool contains(Rect const &r) const; + /// Check whether the given convex hull is completely contained in this one. + bool contains(ConvexHull const &other) const; + //bool interiorContains(Point const &p) const; + //bool interiorContains(Rect const &r) const; + //bool interiorContains(ConvexHull const &other) const; + //bool intersects(Rect const &r) const; + //bool intersects(ConvexHull const &other) const; + + //ConvexHull &operator|=(ConvexHull const &other); + //ConvexHull &operator&=(ConvexHull const &other); + //ConvexHull &operator*=(Affine const &m); + + //ConvexHull &expand(Point const &p); + //void unifyWith(ConvexHull const &other); + //void intersectWith(ConvexHull const &other); + /// @} + + void swap(ConvexHull &other); + void swap(std::vector<Point> &pts); + +private: + void _construct(); + static bool _is_clockwise_turn(Point const &a, Point const &b, Point const &c); + + /// Take a vector of points and produce a pruned sorted vector. + template <typename Iter> + static void _prune(Iter first, Iter last, std::vector<Point> &out) { + boost::optional<Point> ymin, ymax, xmin, xmax; + for (Iter i = first; i != last; ++i) { + Point p = *i; + if (!ymin || Point::LexLess<Y>()(p, *ymin)) { + ymin = p; + } + if (!xmin || Point::LexLess<X>()(p, *xmin)) { + xmin = p; + } + if (!ymax || Point::LexGreater<Y>()(p, *ymax)) { + ymax = p; + } + if (!xmax || Point::LexGreater<X>()(p, *xmax)) { + xmax = p; + } + } + if (!ymin) return; + + ConvexHull qhull(*xmin, *xmax, *ymin, *ymax); + for (Iter i = first; i != last; ++i) { + if (qhull.contains(*i)) continue; + out.push_back(*i); + } + + out.push_back(*xmin); + out.push_back(*xmax); + out.push_back(*ymin); + out.push_back(*ymax); + std::sort(out.begin(), out.end(), Point::LexLess<X>()); + out.erase(std::unique(out.begin(), out.end()), out.end()); + } + + /// Sequence of points forming the convex hull polygon. + std::vector<Point> _boundary; + /// Index one past the rightmost point, where the lower part of the boundary starts. + std::size_t _lower; +}; + +/** @brief Output operator for convex hulls. + * Prints out all the coordinates. */ +inline std::ostream &operator<< (std::ostream &out_file, const Geom::ConvexHull &in_cvx) { + out_file << "ConvexHull("; + for(unsigned i = 0; i < in_cvx.size(); i++) { + out_file << in_cvx[i] << ", "; + } + out_file << ")"; + return out_file; +} + +} // end namespace Geom + +#endif // LIB2GEOM_SEEN_CONVEX_HULL_H + +/* + 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/2geom/coord.cpp b/src/2geom/coord.cpp new file mode 100644 index 000000000..9ee8066f2 --- /dev/null +++ b/src/2geom/coord.cpp @@ -0,0 +1,3688 @@ +/** @file + * @brief Conversion between Coord and strings + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2014 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +// Most of the code in this file is derived from: +// https://code.google.com/p/double-conversion/ +// The copyright notice for that code is attached below. +// +// Copyright 2010 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include <2geom/coord.h> +#include <stdint.h> +#include <cstdlib> +#include <cassert> +#include <cstring> +#include <climits> +#include <cstdarg> +#include <cmath> + +#ifndef ASSERT +#define ASSERT(condition) \ + assert(condition); +#endif +#ifndef UNIMPLEMENTED +#define UNIMPLEMENTED() (abort()) +#endif +#ifndef UNREACHABLE +#define UNREACHABLE() (abort()) +#endif + +#define UINT64_2PART_C(a, b) (((static_cast<uint64_t>(a) << 32) + 0x##b##u)) + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) \ + ((sizeof(a) / sizeof(*(a))) / \ + static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))) +#endif + +#ifndef DISALLOW_COPY_AND_ASSIGN +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) +#endif + +#ifndef DISALLOW_IMPLICIT_CONSTRUCTORS +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName(); \ + DISALLOW_COPY_AND_ASSIGN(TypeName) +#endif + +#if defined(__GNUC__) +#define DOUBLE_CONVERSION_UNUSED __attribute__((unused)) +#else +#define DOUBLE_CONVERSION_UNUSED +#endif + +namespace Geom { + +namespace { + +inline int StrLength(const char* string) { + size_t length = strlen(string); + ASSERT(length == static_cast<size_t>(static_cast<int>(length))); + return static_cast<int>(length); +} + +template <typename T> +class Vector { + public: + Vector() : start_(NULL), length_(0) {} + Vector(T* data, int length) : start_(data), length_(length) { + ASSERT(length == 0 || (length > 0 && data != NULL)); + } + + Vector<T> SubVector(int from, int to) { + ASSERT(to <= length_); + ASSERT(from < to); + ASSERT(0 <= from); + return Vector<T>(start() + from, to - from); + } + int length() const { return length_; } + bool is_empty() const { return length_ == 0; } + + T* start() const { return start_; } + + T& operator[](int index) const { + ASSERT(0 <= index && index < length_); + return start_[index]; + } + T& first() { return start_[0]; } + T& last() { return start_[length_ - 1]; } + + private: + T* start_; + int length_; +}; + +template <class Dest, class Source> +inline Dest BitCast(const Source& source) { + DOUBLE_CONVERSION_UNUSED + typedef char VerifySizesAreEqual[sizeof(Dest) == sizeof(Source) ? 1 : -1]; + Dest dest; + memmove(&dest, &source, sizeof(dest)); + return dest; +} + +template <class Dest, class Source> +inline Dest BitCast(Source* source) { + return BitCast<Dest>(reinterpret_cast<uintptr_t>(source)); +} + +// We assume that doubles and uint64_t have the same endianness. +static uint64_t double_to_uint64(double d) { return BitCast<uint64_t>(d); } +static double uint64_to_double(uint64_t d64) { return BitCast<double>(d64); } + +// This "Do It Yourself Floating Point" class +class DiyFp { + public: + static const int kSignificandSize = 64; + + DiyFp() : f_(0), e_(0) {} + DiyFp(uint64_t f, int e) : f_(f), e_(e) {} + + void Subtract(const DiyFp& other) { + ASSERT(e_ == other.e_); + ASSERT(f_ >= other.f_); + f_ -= other.f_; + } + + static DiyFp Minus(const DiyFp& a, const DiyFp& b) { + DiyFp result = a; + result.Subtract(b); + return result; + } + + void Multiply(const DiyFp& other) { + const uint64_t kM32 = 0xFFFFFFFFU; + uint64_t a = f_ >> 32; + uint64_t b = f_ & kM32; + uint64_t c = other.f_ >> 32; + uint64_t d = other.f_ & kM32; + uint64_t ac = a * c; + uint64_t bc = b * c; + uint64_t ad = a * d; + uint64_t bd = b * d; + uint64_t tmp = (bd >> 32) + (ad & kM32) + (bc & kM32); + // By adding 1U << 31 to tmp we round the final result. + // Halfway cases will be round up. + tmp += 1U << 31; + uint64_t result_f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32); + e_ += other.e_ + 64; + f_ = result_f; + } + + static DiyFp Times(const DiyFp& a, const DiyFp& b) { + DiyFp result = a; + result.Multiply(b); + return result; + } + + void Normalize() { + ASSERT(f_ != 0); + uint64_t f = f_; + int e = e_; + + const uint64_t k10MSBits = UINT64_2PART_C(0xFFC00000, 00000000); + while ((f & k10MSBits) == 0) { + f <<= 10; + e -= 10; + } + while ((f & kUint64MSB) == 0) { + f <<= 1; + e--; + } + f_ = f; + e_ = e; + } + + static DiyFp Normalize(const DiyFp& a) { + DiyFp result = a; + result.Normalize(); + return result; + } + + uint64_t f() const { return f_; } + int e() const { return e_; } + + void set_f(uint64_t new_value) { f_ = new_value; } + void set_e(int new_value) { e_ = new_value; } + + private: + static const uint64_t kUint64MSB = UINT64_2PART_C(0x80000000, 00000000); + + uint64_t f_; + int e_; +}; + +class Double { + public: + static const uint64_t kSignMask = UINT64_2PART_C(0x80000000, 00000000); + static const uint64_t kExponentMask = UINT64_2PART_C(0x7FF00000, 00000000); + static const uint64_t kSignificandMask = UINT64_2PART_C(0x000FFFFF, FFFFFFFF); + static const uint64_t kHiddenBit = UINT64_2PART_C(0x00100000, 00000000); + static const int kPhysicalSignificandSize = 52; // Excludes the hidden bit. + static const int kSignificandSize = 53; + + Double() : d64_(0) {} + explicit Double(double d) : d64_(double_to_uint64(d)) {} + explicit Double(uint64_t d64) : d64_(d64) {} + explicit Double(DiyFp diy_fp) + : d64_(DiyFpToUint64(diy_fp)) {} + + DiyFp AsDiyFp() const { + ASSERT(Sign() > 0); + ASSERT(!IsSpecial()); + return DiyFp(Significand(), Exponent()); + } + + DiyFp AsNormalizedDiyFp() const { + ASSERT(value() > 0.0); + uint64_t f = Significand(); + int e = Exponent(); + + // The current double could be a denormal. + while ((f & kHiddenBit) == 0) { + f <<= 1; + e--; + } + // Do the final shifts in one go. + f <<= DiyFp::kSignificandSize - kSignificandSize; + e -= DiyFp::kSignificandSize - kSignificandSize; + return DiyFp(f, e); + } + + uint64_t AsUint64() const { + return d64_; + } + + double NextDouble() const { + if (d64_ == kInfinity) return Double(kInfinity).value(); + if (Sign() < 0 && Significand() == 0) { + // -0.0 + return 0.0; + } + if (Sign() < 0) { + return Double(d64_ - 1).value(); + } else { + return Double(d64_ + 1).value(); + } + } + + double PreviousDouble() const { + if (d64_ == (kInfinity | kSignMask)) return -Double::Infinity(); + if (Sign() < 0) { + return Double(d64_ + 1).value(); + } else { + if (Significand() == 0) return -0.0; + return Double(d64_ - 1).value(); + } + } + + int Exponent() const { + if (IsDenormal()) return kDenormalExponent; + + uint64_t d64 = AsUint64(); + int biased_e = + static_cast<int>((d64 & kExponentMask) >> kPhysicalSignificandSize); + return biased_e - kExponentBias; + } + + uint64_t Significand() const { + uint64_t d64 = AsUint64(); + uint64_t significand = d64 & kSignificandMask; + if (!IsDenormal()) { + return significand + kHiddenBit; + } else { + return significand; + } + } + + bool IsDenormal() const { + uint64_t d64 = AsUint64(); + return (d64 & kExponentMask) == 0; + } + + // We consider denormals not to be special. + // Hence only Infinity and NaN are special. + bool IsSpecial() const { + uint64_t d64 = AsUint64(); + return (d64 & kExponentMask) == kExponentMask; + } + + bool IsNan() const { + uint64_t d64 = AsUint64(); + return ((d64 & kExponentMask) == kExponentMask) && + ((d64 & kSignificandMask) != 0); + } + + bool IsInfinite() const { + uint64_t d64 = AsUint64(); + return ((d64 & kExponentMask) == kExponentMask) && + ((d64 & kSignificandMask) == 0); + } + + int Sign() const { + uint64_t d64 = AsUint64(); + return (d64 & kSignMask) == 0? 1: -1; + } + + DiyFp UpperBoundary() const { + ASSERT(Sign() > 0); + return DiyFp(Significand() * 2 + 1, Exponent() - 1); + } + + void NormalizedBoundaries(DiyFp* out_m_minus, DiyFp* out_m_plus) const { + ASSERT(value() > 0.0); + DiyFp v = this->AsDiyFp(); + DiyFp m_plus = DiyFp::Normalize(DiyFp((v.f() << 1) + 1, v.e() - 1)); + DiyFp m_minus; + if (LowerBoundaryIsCloser()) { + m_minus = DiyFp((v.f() << 2) - 1, v.e() - 2); + } else { + m_minus = DiyFp((v.f() << 1) - 1, v.e() - 1); + } + m_minus.set_f(m_minus.f() << (m_minus.e() - m_plus.e())); + m_minus.set_e(m_plus.e()); + *out_m_plus = m_plus; + *out_m_minus = m_minus; + } + + bool LowerBoundaryIsCloser() const { + bool physical_significand_is_zero = ((AsUint64() & kSignificandMask) == 0); + return physical_significand_is_zero && (Exponent() != kDenormalExponent); + } + + double value() const { return uint64_to_double(d64_); } + + static int SignificandSizeForOrderOfMagnitude(int order) { + if (order >= (kDenormalExponent + kSignificandSize)) { + return kSignificandSize; + } + if (order <= kDenormalExponent) return 0; + return order - kDenormalExponent; + } + + static double Infinity() { + return Double(kInfinity).value(); + } + + static double NaN() { + return Double(kNaN).value(); + } + + private: + static const int kExponentBias = 0x3FF + kPhysicalSignificandSize; + static const int kDenormalExponent = -kExponentBias + 1; + static const int kMaxExponent = 0x7FF - kExponentBias; + static const uint64_t kInfinity = UINT64_2PART_C(0x7FF00000, 00000000); + static const uint64_t kNaN = UINT64_2PART_C(0x7FF80000, 00000000); + + const uint64_t d64_; + + static uint64_t DiyFpToUint64(DiyFp diy_fp) { + uint64_t significand = diy_fp.f(); + int exponent = diy_fp.e(); + while (significand > kHiddenBit + kSignificandMask) { + significand >>= 1; + exponent++; + } + if (exponent >= kMaxExponent) { + return kInfinity; + } + if (exponent < kDenormalExponent) { + return 0; + } + while (exponent > kDenormalExponent && (significand & kHiddenBit) == 0) { + significand <<= 1; + exponent--; + } + uint64_t biased_exponent; + if (exponent == kDenormalExponent && (significand & kHiddenBit) == 0) { + biased_exponent = 0; + } else { + biased_exponent = static_cast<uint64_t>(exponent + kExponentBias); + } + return (significand & kSignificandMask) | + (biased_exponent << kPhysicalSignificandSize); + } + + DISALLOW_COPY_AND_ASSIGN(Double); +}; + +template<typename S> +static int BitSize(S value) { + (void) value; // Mark variable as used. + return 8 * sizeof(value); +} + +class Bignum { + public: + // 3584 = 128 * 28. We can represent 2^3584 > 10^1000 accurately. + // This bignum can encode much bigger numbers, since it contains an + // exponent. + static const int kMaxSignificantBits = 3584; + + Bignum() + : bigits_(bigits_buffer_, kBigitCapacity), used_digits_(0), exponent_(0) + { + for (int i = 0; i < kBigitCapacity; ++i) { + bigits_[i] = 0; + } + } + void AssignUInt16(uint16_t value) { + ASSERT(kBigitSize >= BitSize(value)); + Zero(); + if (value == 0) return; + + EnsureCapacity(1); + bigits_[0] = value; + used_digits_ = 1; + } + void AssignUInt64(uint64_t value) { + const int kUInt64Size = 64; + + Zero(); + if (value == 0) return; + + int needed_bigits = kUInt64Size / kBigitSize + 1; + EnsureCapacity(needed_bigits); + for (int i = 0; i < needed_bigits; ++i) { + bigits_[i] = value & kBigitMask; + value = value >> kBigitSize; + } + used_digits_ = needed_bigits; + Clamp(); + } + void AssignBignum(const Bignum& other) { + exponent_ = other.exponent_; + for (int i = 0; i < other.used_digits_; ++i) { + bigits_[i] = other.bigits_[i]; + } + // Clear the excess digits (if there were any). + for (int i = other.used_digits_; i < used_digits_; ++i) { + bigits_[i] = 0; + } + used_digits_ = other.used_digits_; + } + + void AssignDecimalString(Vector<const char> value); + void AssignHexString(Vector<const char> value); + + void AssignPowerUInt16(uint16_t base, int exponent); + + void AddUInt16(uint16_t operand); + void AddUInt64(uint64_t operand); + void AddBignum(const Bignum& other); + // Precondition: this >= other. + void SubtractBignum(const Bignum& other); + + void Square(); + void ShiftLeft(int shift_amount); + void MultiplyByUInt32(uint32_t factor); + void MultiplyByUInt64(uint64_t factor); + void MultiplyByPowerOfTen(int exponent); + void Times10() { return MultiplyByUInt32(10); } + // Pseudocode: + // int result = this / other; + // this = this % other; + // In the worst case this function is in O(this/other). + uint16_t DivideModuloIntBignum(const Bignum& other); + + bool ToHexString(char* buffer, int buffer_size) const; + + // Returns + // -1 if a < b, + // 0 if a == b, and + // +1 if a > b. + static int Compare(const Bignum& a, const Bignum& b); + static bool Equal(const Bignum& a, const Bignum& b) { + return Compare(a, b) == 0; + } + static bool LessEqual(const Bignum& a, const Bignum& b) { + return Compare(a, b) <= 0; + } + static bool Less(const Bignum& a, const Bignum& b) { + return Compare(a, b) < 0; + } + // Returns Compare(a + b, c); + static int PlusCompare(const Bignum& a, const Bignum& b, const Bignum& c); + // Returns a + b == c + static bool PlusEqual(const Bignum& a, const Bignum& b, const Bignum& c) { + return PlusCompare(a, b, c) == 0; + } + // Returns a + b <= c + static bool PlusLessEqual(const Bignum& a, const Bignum& b, const Bignum& c) { + return PlusCompare(a, b, c) <= 0; + } + // Returns a + b < c + static bool PlusLess(const Bignum& a, const Bignum& b, const Bignum& c) { + return PlusCompare(a, b, c) < 0; + } + private: + typedef uint32_t Chunk; + typedef uint64_t DoubleChunk; + + static const int kChunkSize = sizeof(Chunk) * 8; + static const int kDoubleChunkSize = sizeof(DoubleChunk) * 8; + // With bigit size of 28 we loose some bits, but a double still fits easily + // into two chunks, and more importantly we can use the Comba multiplication. + static const int kBigitSize = 28; + static const Chunk kBigitMask = (1 << kBigitSize) - 1; + // Every instance allocates kBigitLength chunks on the stack. Bignums cannot + // grow. There are no checks if the stack-allocated space is sufficient. + static const int kBigitCapacity = kMaxSignificantBits / kBigitSize; + + void EnsureCapacity(int size) { + if (size > kBigitCapacity) { + UNREACHABLE(); + } + } + void Align(const Bignum& other); + void Clamp(); + bool IsClamped() const; + void Zero(); + // Requires this to have enough capacity (no tests done). + // Updates used_digits_ if necessary. + // shift_amount must be < kBigitSize. + void BigitsShiftLeft(int shift_amount); + // BigitLength includes the "hidden" digits encoded in the exponent. + int BigitLength() const { return used_digits_ + exponent_; } + Chunk BigitAt(int index) const; + void SubtractTimes(const Bignum& other, int factor); + + Chunk bigits_buffer_[kBigitCapacity]; + // A vector backed by bigits_buffer_. This way accesses to the array are + // checked for out-of-bounds errors. + Vector<Chunk> bigits_; + int used_digits_; + // The Bignum's value equals value(bigits_) * 2^(exponent_ * kBigitSize). + int exponent_; + + DISALLOW_COPY_AND_ASSIGN(Bignum); +}; + +static uint64_t ReadUInt64(Vector<const char> buffer, + int from, + int digits_to_read) { + uint64_t result = 0; + for (int i = from; i < from + digits_to_read; ++i) { + int digit = buffer[i] - '0'; + ASSERT(0 <= digit && digit <= 9); + result = result * 10 + digit; + } + return result; +} + + +void Bignum::AssignDecimalString(Vector<const char> value) { + // 2^64 = 18446744073709551616 > 10^19 + const int kMaxUint64DecimalDigits = 19; + Zero(); + int length = value.length(); + int pos = 0; + // Let's just say that each digit needs 4 bits. + while (length >= kMaxUint64DecimalDigits) { + uint64_t digits = ReadUInt64(value, pos, kMaxUint64DecimalDigits); + pos += kMaxUint64DecimalDigits; + length -= kMaxUint64DecimalDigits; + MultiplyByPowerOfTen(kMaxUint64DecimalDigits); + AddUInt64(digits); + } + uint64_t digits = ReadUInt64(value, pos, length); + MultiplyByPowerOfTen(length); + AddUInt64(digits); + Clamp(); +} + + +static int HexCharValue(char c) { + if ('0' <= c && c <= '9') return c - '0'; + if ('a' <= c && c <= 'f') return 10 + c - 'a'; + ASSERT('A' <= c && c <= 'F'); + return 10 + c - 'A'; +} + + +void Bignum::AssignHexString(Vector<const char> value) { + Zero(); + int length = value.length(); + + int needed_bigits = length * 4 / kBigitSize + 1; + EnsureCapacity(needed_bigits); + int string_index = length - 1; + for (int i = 0; i < needed_bigits - 1; ++i) { + // These bigits are guaranteed to be "full". + Chunk current_bigit = 0; + for (int j = 0; j < kBigitSize / 4; j++) { + current_bigit += HexCharValue(value[string_index--]) << (j * 4); + } + bigits_[i] = current_bigit; + } + used_digits_ = needed_bigits - 1; + + Chunk most_significant_bigit = 0; // Could be = 0; + for (int j = 0; j <= string_index; ++j) { + most_significant_bigit <<= 4; + most_significant_bigit += HexCharValue(value[j]); + } + if (most_significant_bigit != 0) { + bigits_[used_digits_] = most_significant_bigit; + used_digits_++; + } + Clamp(); +} + + +void Bignum::AddUInt64(uint64_t operand) { + if (operand == 0) return; + Bignum other; + other.AssignUInt64(operand); + AddBignum(other); +} + + +void Bignum::AddBignum(const Bignum& other) { + ASSERT(IsClamped()); + ASSERT(other.IsClamped()); + + Align(other); + + EnsureCapacity(1 + std::max(BigitLength(), other.BigitLength()) - exponent_); + Chunk carry = 0; + int bigit_pos = other.exponent_ - exponent_; + ASSERT(bigit_pos >= 0); + for (int i = 0; i < other.used_digits_; ++i) { + Chunk sum = bigits_[bigit_pos] + other.bigits_[i] + carry; + bigits_[bigit_pos] = sum & kBigitMask; + carry = sum >> kBigitSize; + bigit_pos++; + } + + while (carry != 0) { + Chunk sum = bigits_[bigit_pos] + carry; + bigits_[bigit_pos] = sum & kBigitMask; + carry = sum >> kBigitSize; + bigit_pos++; + } + used_digits_ = std::max(bigit_pos, used_digits_); + ASSERT(IsClamped()); +} + + +void Bignum::SubtractBignum(const Bignum& other) { + ASSERT(IsClamped()); + ASSERT(other.IsClamped()); + // We require this to be bigger than other. + ASSERT(LessEqual(other, *this)); + + Align(other); + + int offset = other.exponent_ - exponent_; + Chunk borrow = 0; + int i; + for (i = 0; i < other.used_digits_; ++i) { + ASSERT((borrow == 0) || (borrow == 1)); + Chunk difference = bigits_[i + offset] - other.bigits_[i] - borrow; + bigits_[i + offset] = difference & kBigitMask; + borrow = difference >> (kChunkSize - 1); + } + while (borrow != 0) { + Chunk difference = bigits_[i + offset] - borrow; + bigits_[i + offset] = difference & kBigitMask; + borrow = difference >> (kChunkSize - 1); + ++i; + } + Clamp(); +} + + +void Bignum::ShiftLeft(int shift_amount) { + if (used_digits_ == 0) return; + exponent_ += shift_amount / kBigitSize; + int local_shift = shift_amount % kBigitSize; + EnsureCapacity(used_digits_ + 1); + BigitsShiftLeft(local_shift); +} + + +void Bignum::MultiplyByUInt32(uint32_t factor) { + if (factor == 1) return; + if (factor == 0) { + Zero(); + return; + } + if (used_digits_ == 0) return; + + ASSERT(kDoubleChunkSize >= kBigitSize + 32 + 1); + DoubleChunk carry = 0; + for (int i = 0; i < used_digits_; ++i) { + DoubleChunk product = static_cast<DoubleChunk>(factor) * bigits_[i] + carry; + bigits_[i] = static_cast<Chunk>(product & kBigitMask); + carry = (product >> kBigitSize); + } + while (carry != 0) { + EnsureCapacity(used_digits_ + 1); + bigits_[used_digits_] = carry & kBigitMask; + used_digits_++; + carry >>= kBigitSize; + } +} + + +void Bignum::MultiplyByUInt64(uint64_t factor) { + if (factor == 1) return; + if (factor == 0) { + Zero(); + return; + } + ASSERT(kBigitSize < 32); + uint64_t carry = 0; + uint64_t low = factor & 0xFFFFFFFF; + uint64_t high = factor >> 32; + for (int i = 0; i < used_digits_; ++i) { + uint64_t product_low = low * bigits_[i]; + uint64_t product_high = high * bigits_[i]; + uint64_t tmp = (carry & kBigitMask) + product_low; + bigits_[i] = tmp & kBigitMask; + carry = (carry >> kBigitSize) + (tmp >> kBigitSize) + + (product_high << (32 - kBigitSize)); + } + while (carry != 0) { + EnsureCapacity(used_digits_ + 1); + bigits_[used_digits_] = carry & kBigitMask; + used_digits_++; + carry >>= kBigitSize; + } +} + + +void Bignum::MultiplyByPowerOfTen(int exponent) { + const uint64_t kFive27 = UINT64_2PART_C(0x6765c793, fa10079d); + const uint16_t kFive1 = 5; + const uint16_t kFive2 = kFive1 * 5; + const uint16_t kFive3 = kFive2 * 5; + const uint16_t kFive4 = kFive3 * 5; + const uint16_t kFive5 = kFive4 * 5; + const uint16_t kFive6 = kFive5 * 5; + const uint32_t kFive7 = kFive6 * 5; + const uint32_t kFive8 = kFive7 * 5; + const uint32_t kFive9 = kFive8 * 5; + const uint32_t kFive10 = kFive9 * 5; + const uint32_t kFive11 = kFive10 * 5; + const uint32_t kFive12 = kFive11 * 5; + const uint32_t kFive13 = kFive12 * 5; + const uint32_t kFive1_to_12[] = + { kFive1, kFive2, kFive3, kFive4, kFive5, kFive6, + kFive7, kFive8, kFive9, kFive10, kFive11, kFive12 }; + + ASSERT(exponent >= 0); + if (exponent == 0) return; + if (used_digits_ == 0) return; + + int remaining_exponent = exponent; + while (remaining_exponent >= 27) { + MultiplyByUInt64(kFive27); + remaining_exponent -= 27; + } + while (remaining_exponent >= 13) { + MultiplyByUInt32(kFive13); + remaining_exponent -= 13; + } + if (remaining_exponent > 0) { + MultiplyByUInt32(kFive1_to_12[remaining_exponent - 1]); + } + ShiftLeft(exponent); +} + + +void Bignum::Square() { + ASSERT(IsClamped()); + int product_length = 2 * used_digits_; + EnsureCapacity(product_length); + + if ((1 << (2 * (kChunkSize - kBigitSize))) <= used_digits_) { + UNIMPLEMENTED(); + } + DoubleChunk accumulator = 0; + // First shift the digits so we don't overwrite them. + int copy_offset = used_digits_; + for (int i = 0; i < used_digits_; ++i) { + bigits_[copy_offset + i] = bigits_[i]; + } + // We have two loops to avoid some 'if's in the loop. + for (int i = 0; i < used_digits_; ++i) { + // Process temporary digit i with power i. + // The sum of the two indices must be equal to i. + int bigit_index1 = i; + int bigit_index2 = 0; + // Sum all of the sub-products. + while (bigit_index1 >= 0) { + Chunk chunk1 = bigits_[copy_offset + bigit_index1]; + Chunk chunk2 = bigits_[copy_offset + bigit_index2]; + accumulator += static_cast<DoubleChunk>(chunk1) * chunk2; + bigit_index1--; + bigit_index2++; + } + bigits_[i] = static_cast<Chunk>(accumulator) & kBigitMask; + accumulator >>= kBigitSize; + } + for (int i = used_digits_; i < product_length; ++i) { + int bigit_index1 = used_digits_ - 1; + int bigit_index2 = i - bigit_index1; + + while (bigit_index2 < used_digits_) { + Chunk chunk1 = bigits_[copy_offset + bigit_index1]; + Chunk chunk2 = bigits_[copy_offset + bigit_index2]; + accumulator += static_cast<DoubleChunk>(chunk1) * chunk2; + bigit_index1--; + bigit_index2++; + } + bigits_[i] = static_cast<Chunk>(accumulator) & kBigitMask; + accumulator >>= kBigitSize; + } + + ASSERT(accumulator == 0); + + used_digits_ = product_length; + exponent_ *= 2; + Clamp(); +} + + +void Bignum::AssignPowerUInt16(uint16_t base, int power_exponent) { + ASSERT(base != 0); + ASSERT(power_exponent >= 0); + if (power_exponent == 0) { + AssignUInt16(1); + return; + } + Zero(); + int shifts = 0; + + while ((base & 1) == 0) { + base >>= 1; + shifts++; + } + int bit_size = 0; + int tmp_base = base; + while (tmp_base != 0) { + tmp_base >>= 1; + bit_size++; + } + int final_size = bit_size * power_exponent; + + EnsureCapacity(final_size / kBigitSize + 2); + + // Left to Right exponentiation. + int mask = 1; + while (power_exponent >= mask) mask <<= 1; + + mask >>= 2; + uint64_t this_value = base; + + bool delayed_multipliciation = false; + const uint64_t max_32bits = 0xFFFFFFFF; + while (mask != 0 && this_value <= max_32bits) { + this_value = this_value * this_value; + // Verify that there is enough space in this_value to perform the + // multiplication. The first bit_size bits must be 0. + if ((power_exponent & mask) != 0) { + uint64_t base_bits_mask = + ~((static_cast<uint64_t>(1) << (64 - bit_size)) - 1); + bool high_bits_zero = (this_value & base_bits_mask) == 0; + if (high_bits_zero) { + this_value *= base; + } else { + delayed_multipliciation = true; + } + } + mask >>= 1; + } + AssignUInt64(this_value); + if (delayed_multipliciation) { + MultiplyByUInt32(base); + } + + // Now do the same thing as a bignum. + while (mask != 0) { + Square(); + if ((power_exponent & mask) != 0) { + MultiplyByUInt32(base); + } + mask >>= 1; + } + + // And finally add the saved shifts. + ShiftLeft(shifts * power_exponent); +} + + +uint16_t Bignum::DivideModuloIntBignum(const Bignum& other) { + ASSERT(IsClamped()); + ASSERT(other.IsClamped()); + ASSERT(other.used_digits_ > 0); + + if (BigitLength() < other.BigitLength()) { + return 0; + } + + Align(other); + + uint16_t result = 0; + + while (BigitLength() > other.BigitLength()) { + ASSERT(other.bigits_[other.used_digits_ - 1] >= ((1 << kBigitSize) / 16)); + ASSERT(bigits_[used_digits_ - 1] < 0x10000); + result += static_cast<uint16_t>(bigits_[used_digits_ - 1]); + SubtractTimes(other, bigits_[used_digits_ - 1]); + } + + ASSERT(BigitLength() == other.BigitLength()); + + Chunk this_bigit = bigits_[used_digits_ - 1]; + Chunk other_bigit = other.bigits_[other.used_digits_ - 1]; + + if (other.used_digits_ == 1) { + int quotient = this_bigit / other_bigit; + bigits_[used_digits_ - 1] = this_bigit - other_bigit * quotient; + ASSERT(quotient < 0x10000); + result += static_cast<uint16_t>(quotient); + Clamp(); + return result; + } + + int division_estimate = this_bigit / (other_bigit + 1); + ASSERT(division_estimate < 0x10000); + result += static_cast<uint16_t>(division_estimate); + SubtractTimes(other, division_estimate); + + if (other_bigit * (division_estimate + 1) > this_bigit) { + return result; + } + + while (LessEqual(other, *this)) { + SubtractBignum(other); + result++; + } + return result; +} + + +template<typename S> +static int SizeInHexChars(S number) { + ASSERT(number > 0); + int result = 0; + while (number != 0) { + number >>= 4; + result++; + } + return result; +} + + +static char HexCharOfValue(int value) { + ASSERT(0 <= value && value <= 16); + if (value < 10) return static_cast<char>(value + '0'); + return static_cast<char>(value - 10 + 'A'); +} + + +bool Bignum::ToHexString(char* buffer, int buffer_size) const { + ASSERT(IsClamped()); + // Each bigit must be printable as separate hex-character. + ASSERT(kBigitSize % 4 == 0); + const int kHexCharsPerBigit = kBigitSize / 4; + + if (used_digits_ == 0) { + if (buffer_size < 2) return false; + buffer[0] = '0'; + buffer[1] = '\0'; + return true; + } + // We add 1 for the terminating '\0' character. + int needed_chars = (BigitLength() - 1) * kHexCharsPerBigit + + SizeInHexChars(bigits_[used_digits_ - 1]) + 1; + if (needed_chars > buffer_size) return false; + int string_index = needed_chars - 1; + buffer[string_index--] = '\0'; + for (int i = 0; i < exponent_; ++i) { + for (int j = 0; j < kHexCharsPerBigit; ++j) { + buffer[string_index--] = '0'; + } + } + for (int i = 0; i < used_digits_ - 1; ++i) { + Chunk current_bigit = bigits_[i]; + for (int j = 0; j < kHexCharsPerBigit; ++j) { + buffer[string_index--] = HexCharOfValue(current_bigit & 0xF); + current_bigit >>= 4; + } + } + // And finally the last bigit. + Chunk most_significant_bigit = bigits_[used_digits_ - 1]; + while (most_significant_bigit != 0) { + buffer[string_index--] = HexCharOfValue(most_significant_bigit & 0xF); + most_significant_bigit >>= 4; + } + return true; +} + + +Bignum::Chunk Bignum::BigitAt(int index) const { + if (index >= BigitLength()) return 0; + if (index < exponent_) return 0; + return bigits_[index - exponent_]; +} + + +int Bignum::Compare(const Bignum& a, const Bignum& b) { + ASSERT(a.IsClamped()); + ASSERT(b.IsClamped()); + int bigit_length_a = a.BigitLength(); + int bigit_length_b = b.BigitLength(); + if (bigit_length_a < bigit_length_b) return -1; + if (bigit_length_a > bigit_length_b) return +1; + for (int i = bigit_length_a - 1; i >= std::min(a.exponent_, b.exponent_); --i) { + Chunk bigit_a = a.BigitAt(i); + Chunk bigit_b = b.BigitAt(i); + if (bigit_a < bigit_b) return -1; + if (bigit_a > bigit_b) return +1; + } + return 0; +} + + +int Bignum::PlusCompare(const Bignum& a, const Bignum& b, const Bignum& c) { + ASSERT(a.IsClamped()); + ASSERT(b.IsClamped()); + ASSERT(c.IsClamped()); + if (a.BigitLength() < b.BigitLength()) { + return PlusCompare(b, a, c); + } + if (a.BigitLength() + 1 < c.BigitLength()) return -1; + if (a.BigitLength() > c.BigitLength()) return +1; + + if (a.exponent_ >= b.BigitLength() && a.BigitLength() < c.BigitLength()) { + return -1; + } + + Chunk borrow = 0; + // Starting at min_exponent all digits are == 0. So no need to compare them. + int min_exponent = std::min(std::min(a.exponent_, b.exponent_), c.exponent_); + for (int i = c.BigitLength() - 1; i >= min_exponent; --i) { + Chunk chunk_a = a.BigitAt(i); + Chunk chunk_b = b.BigitAt(i); + Chunk chunk_c = c.BigitAt(i); + Chunk sum = chunk_a + chunk_b; + if (sum > chunk_c + borrow) { + return +1; + } else { + borrow = chunk_c + borrow - sum; + if (borrow > 1) return -1; + borrow <<= kBigitSize; + } + } + if (borrow == 0) return 0; + return -1; +} + + +void Bignum::Clamp() { + while (used_digits_ > 0 && bigits_[used_digits_ - 1] == 0) { + used_digits_--; + } + if (used_digits_ == 0) { + // Zero. + exponent_ = 0; + } +} + + +bool Bignum::IsClamped() const { + return used_digits_ == 0 || bigits_[used_digits_ - 1] != 0; +} + + +void Bignum::Zero() { + for (int i = 0; i < used_digits_; ++i) { + bigits_[i] = 0; + } + used_digits_ = 0; + exponent_ = 0; +} + + +void Bignum::Align(const Bignum& other) { + if (exponent_ > other.exponent_) { + int zero_digits = exponent_ - other.exponent_; + EnsureCapacity(used_digits_ + zero_digits); + for (int i = used_digits_ - 1; i >= 0; --i) { + bigits_[i + zero_digits] = bigits_[i]; + } + for (int i = 0; i < zero_digits; ++i) { + bigits_[i] = 0; + } + used_digits_ += zero_digits; + exponent_ -= zero_digits; + ASSERT(used_digits_ >= 0); + ASSERT(exponent_ >= 0); + } +} + + +void Bignum::BigitsShiftLeft(int shift_amount) { + ASSERT(shift_amount < kBigitSize); + ASSERT(shift_amount >= 0); + Chunk carry = 0; + for (int i = 0; i < used_digits_; ++i) { + Chunk new_carry = bigits_[i] >> (kBigitSize - shift_amount); + bigits_[i] = ((bigits_[i] << shift_amount) + carry) & kBigitMask; + carry = new_carry; + } + if (carry != 0) { + bigits_[used_digits_] = carry; + used_digits_++; + } +} + + +void Bignum::SubtractTimes(const Bignum& other, int factor) { + ASSERT(exponent_ <= other.exponent_); + if (factor < 3) { + for (int i = 0; i < factor; ++i) { + SubtractBignum(other); + } + return; + } + Chunk borrow = 0; + int exponent_diff = other.exponent_ - exponent_; + for (int i = 0; i < other.used_digits_; ++i) { + DoubleChunk product = static_cast<DoubleChunk>(factor) * other.bigits_[i]; + DoubleChunk remove = borrow + product; + Chunk difference = bigits_[i + exponent_diff] - (remove & kBigitMask); + bigits_[i + exponent_diff] = difference & kBigitMask; + borrow = static_cast<Chunk>((difference >> (kChunkSize - 1)) + + (remove >> kBigitSize)); + } + for (int i = other.used_digits_ + exponent_diff; i < used_digits_; ++i) { + if (borrow == 0) return; + Chunk difference = bigits_[i] - borrow; + bigits_[i] = difference & kBigitMask; + borrow = difference >> (kChunkSize - 1); + } + Clamp(); +} + +class PowersOfTenCache { +public: + static const int kDecimalExponentDistance; + + static const int kMinDecimalExponent; + static const int kMaxDecimalExponent; + + static void GetCachedPowerForBinaryExponentRange(int min_exponent, + int max_exponent, + DiyFp* power, + int* decimal_exponent); + + static void GetCachedPowerForDecimalExponent(int requested_exponent, + DiyFp* power, + int* found_exponent); +}; + +struct CachedPower { + uint64_t significand; + int16_t binary_exponent; + int16_t decimal_exponent; +}; + +static const CachedPower kCachedPowers[] = { + {UINT64_2PART_C(0xfa8fd5a0, 081c0288), -1220, -348}, + {UINT64_2PART_C(0xbaaee17f, a23ebf76), -1193, -340}, + {UINT64_2PART_C(0x8b16fb20, 3055ac76), -1166, -332}, + {UINT64_2PART_C(0xcf42894a, 5dce35ea), -1140, -324}, + {UINT64_2PART_C(0x9a6bb0aa, 55653b2d), -1113, -316}, + {UINT64_2PART_C(0xe61acf03, 3d1a45df), -1087, -308}, + {UINT64_2PART_C(0xab70fe17, c79ac6ca), -1060, -300}, + {UINT64_2PART_C(0xff77b1fc, bebcdc4f), -1034, -292}, + {UINT64_2PART_C(0xbe5691ef, 416bd60c), -1007, -284}, + {UINT64_2PART_C(0x8dd01fad, 907ffc3c), -980, -276}, + {UINT64_2PART_C(0xd3515c28, 31559a83), -954, -268}, + {UINT64_2PART_C(0x9d71ac8f, ada6c9b5), -927, -260}, + {UINT64_2PART_C(0xea9c2277, 23ee8bcb), -901, -252}, + {UINT64_2PART_C(0xaecc4991, 4078536d), -874, -244}, + {UINT64_2PART_C(0x823c1279, 5db6ce57), -847, -236}, + {UINT64_2PART_C(0xc2109436, 4dfb5637), -821, -228}, + {UINT64_2PART_C(0x9096ea6f, 3848984f), -794, -220}, + {UINT64_2PART_C(0xd77485cb, 25823ac7), -768, -212}, + {UINT64_2PART_C(0xa086cfcd, 97bf97f4), -741, -204}, + {UINT64_2PART_C(0xef340a98, 172aace5), -715, -196}, + {UINT64_2PART_C(0xb23867fb, 2a35b28e), -688, -188}, + {UINT64_2PART_C(0x84c8d4df, d2c63f3b), -661, -180}, + {UINT64_2PART_C(0xc5dd4427, 1ad3cdba), -635, -172}, + {UINT64_2PART_C(0x936b9fce, bb25c996), -608, -164}, + {UINT64_2PART_C(0xdbac6c24, 7d62a584), -582, -156}, + {UINT64_2PART_C(0xa3ab6658, 0d5fdaf6), -555, -148}, + {UINT64_2PART_C(0xf3e2f893, dec3f126), -529, -140}, + {UINT64_2PART_C(0xb5b5ada8, aaff80b8), -502, -132}, + {UINT64_2PART_C(0x87625f05, 6c7c4a8b), -475, -124}, + {UINT64_2PART_C(0xc9bcff60, 34c13053), -449, -116}, + {UINT64_2PART_C(0x964e858c, 91ba2655), -422, -108}, + {UINT64_2PART_C(0xdff97724, 70297ebd), -396, -100}, + {UINT64_2PART_C(0xa6dfbd9f, b8e5b88f), -369, -92}, + {UINT64_2PART_C(0xf8a95fcf, 88747d94), -343, -84}, + {UINT64_2PART_C(0xb9447093, 8fa89bcf), -316, -76}, + {UINT64_2PART_C(0x8a08f0f8, bf0f156b), -289, -68}, + {UINT64_2PART_C(0xcdb02555, 653131b6), -263, -60}, + {UINT64_2PART_C(0x993fe2c6, d07b7fac), -236, -52}, + {UINT64_2PART_C(0xe45c10c4, 2a2b3b06), -210, -44}, + {UINT64_2PART_C(0xaa242499, 697392d3), -183, -36}, + {UINT64_2PART_C(0xfd87b5f2, 8300ca0e), -157, -28}, + {UINT64_2PART_C(0xbce50864, 92111aeb), -130, -20}, + {UINT64_2PART_C(0x8cbccc09, 6f5088cc), -103, -12}, + {UINT64_2PART_C(0xd1b71758, e219652c), -77, -4}, + {UINT64_2PART_C(0x9c400000, 00000000), -50, 4}, + {UINT64_2PART_C(0xe8d4a510, 00000000), -24, 12}, + {UINT64_2PART_C(0xad78ebc5, ac620000), 3, 20}, + {UINT64_2PART_C(0x813f3978, f8940984), 30, 28}, + {UINT64_2PART_C(0xc097ce7b, c90715b3), 56, 36}, + {UINT64_2PART_C(0x8f7e32ce, 7bea5c70), 83, 44}, + {UINT64_2PART_C(0xd5d238a4, abe98068), 109, 52}, + {UINT64_2PART_C(0x9f4f2726, 179a2245), 136, 60}, + {UINT64_2PART_C(0xed63a231, d4c4fb27), 162, 68}, + {UINT64_2PART_C(0xb0de6538, 8cc8ada8), 189, 76}, + {UINT64_2PART_C(0x83c7088e, 1aab65db), 216, 84}, + {UINT64_2PART_C(0xc45d1df9, 42711d9a), 242, 92}, + {UINT64_2PART_C(0x924d692c, a61be758), 269, 100}, + {UINT64_2PART_C(0xda01ee64, 1a708dea), 295, 108}, + {UINT64_2PART_C(0xa26da399, 9aef774a), 322, 116}, + {UINT64_2PART_C(0xf209787b, b47d6b85), 348, 124}, + {UINT64_2PART_C(0xb454e4a1, 79dd1877), 375, 132}, + {UINT64_2PART_C(0x865b8692, 5b9bc5c2), 402, 140}, + {UINT64_2PART_C(0xc83553c5, c8965d3d), 428, 148}, + {UINT64_2PART_C(0x952ab45c, fa97a0b3), 455, 156}, + {UINT64_2PART_C(0xde469fbd, 99a05fe3), 481, 164}, + {UINT64_2PART_C(0xa59bc234, db398c25), 508, 172}, + {UINT64_2PART_C(0xf6c69a72, a3989f5c), 534, 180}, + {UINT64_2PART_C(0xb7dcbf53, 54e9bece), 561, 188}, + {UINT64_2PART_C(0x88fcf317, f22241e2), 588, 196}, + {UINT64_2PART_C(0xcc20ce9b, d35c78a5), 614, 204}, + {UINT64_2PART_C(0x98165af3, 7b2153df), 641, 212}, + {UINT64_2PART_C(0xe2a0b5dc, 971f303a), 667, 220}, + {UINT64_2PART_C(0xa8d9d153, 5ce3b396), 694, 228}, + {UINT64_2PART_C(0xfb9b7cd9, a4a7443c), 720, 236}, + {UINT64_2PART_C(0xbb764c4c, a7a44410), 747, 244}, + {UINT64_2PART_C(0x8bab8eef, b6409c1a), 774, 252}, + {UINT64_2PART_C(0xd01fef10, a657842c), 800, 260}, + {UINT64_2PART_C(0x9b10a4e5, e9913129), 827, 268}, + {UINT64_2PART_C(0xe7109bfb, a19c0c9d), 853, 276}, + {UINT64_2PART_C(0xac2820d9, 623bf429), 880, 284}, + {UINT64_2PART_C(0x80444b5e, 7aa7cf85), 907, 292}, + {UINT64_2PART_C(0xbf21e440, 03acdd2d), 933, 300}, + {UINT64_2PART_C(0x8e679c2f, 5e44ff8f), 960, 308}, + {UINT64_2PART_C(0xd433179d, 9c8cb841), 986, 316}, + {UINT64_2PART_C(0x9e19db92, b4e31ba9), 1013, 324}, + {UINT64_2PART_C(0xeb96bf6e, badf77d9), 1039, 332}, + {UINT64_2PART_C(0xaf87023b, 9bf0ee6b), 1066, 340}, +}; + +static const int kCachedPowersLength = ARRAY_SIZE(kCachedPowers); +static const int kCachedPowersOffset = 348; // -1 * the first decimal_exponent. +static const double kD_1_LOG2_10 = 0.30102999566398114; // 1 / lg(10) +// Difference between the decimal exponents in the table above. +const int PowersOfTenCache::kDecimalExponentDistance = 8; +const int PowersOfTenCache::kMinDecimalExponent = -348; +const int PowersOfTenCache::kMaxDecimalExponent = 340; + +void PowersOfTenCache::GetCachedPowerForBinaryExponentRange( + int min_exponent, + int max_exponent, + DiyFp* power, + int* decimal_exponent) { + int kQ = DiyFp::kSignificandSize; + double k = ceil((min_exponent + kQ - 1) * kD_1_LOG2_10); + int foo = kCachedPowersOffset; + int index = + (foo + static_cast<int>(k) - 1) / kDecimalExponentDistance + 1; + ASSERT(0 <= index && index < kCachedPowersLength); + CachedPower cached_power = kCachedPowers[index]; + ASSERT(min_exponent <= cached_power.binary_exponent); + (void) max_exponent; // Mark variable as used. + ASSERT(cached_power.binary_exponent <= max_exponent); + *decimal_exponent = cached_power.decimal_exponent; + *power = DiyFp(cached_power.significand, cached_power.binary_exponent); +} + + +void PowersOfTenCache::GetCachedPowerForDecimalExponent(int requested_exponent, + DiyFp* power, + int* found_exponent) { + ASSERT(kMinDecimalExponent <= requested_exponent); + ASSERT(requested_exponent < kMaxDecimalExponent + kDecimalExponentDistance); + int index = + (requested_exponent + kCachedPowersOffset) / kDecimalExponentDistance; + CachedPower cached_power = kCachedPowers[index]; + *power = DiyFp(cached_power.significand, cached_power.binary_exponent); + *found_exponent = cached_power.decimal_exponent; + ASSERT(*found_exponent <= requested_exponent); + ASSERT(requested_exponent < *found_exponent + kDecimalExponentDistance); +} + +enum BignumDtoaMode { + BIGNUM_DTOA_SHORTEST, + BIGNUM_DTOA_FIXED, + BIGNUM_DTOA_PRECISION +}; + +static int NormalizedExponent(uint64_t significand, int exponent) { + ASSERT(significand != 0); + while ((significand & Double::kHiddenBit) == 0) { + significand = significand << 1; + exponent = exponent - 1; + } + return exponent; +} + +static int EstimatePower(int exponent); + +static void InitialScaledStartValues(uint64_t significand, + int exponent, + bool lower_boundary_is_closer, + int estimated_power, + bool need_boundary_deltas, + Bignum* numerator, + Bignum* denominator, + Bignum* delta_minus, + Bignum* delta_plus); + +static void FixupMultiply10(int estimated_power, bool is_even, + int* decimal_point, + Bignum* numerator, Bignum* denominator, + Bignum* delta_minus, Bignum* delta_plus); + +static void GenerateShortestDigits(Bignum* numerator, Bignum* denominator, + Bignum* delta_minus, Bignum* delta_plus, + bool is_even, + Vector<char> buffer, int* length); + +static void BignumToFixed(int requested_digits, int* decimal_point, + Bignum* numerator, Bignum* denominator, + Vector<char>(buffer), int* length); + +static void GenerateCountedDigits(int count, int* decimal_point, + Bignum* numerator, Bignum* denominator, + Vector<char>(buffer), int* length); + + +void BignumDtoa(double v, BignumDtoaMode mode, int requested_digits, + Vector<char> buffer, int* length, int* decimal_point) { + ASSERT(v > 0); + ASSERT(!Double(v).IsSpecial()); + uint64_t significand; + int exponent; + bool lower_boundary_is_closer; + + significand = Double(v).Significand(); + exponent = Double(v).Exponent(); + lower_boundary_is_closer = Double(v).LowerBoundaryIsCloser(); + + bool need_boundary_deltas = + (mode == BIGNUM_DTOA_SHORTEST); + + bool is_even = (significand & 1) == 0; + int normalized_exponent = NormalizedExponent(significand, exponent); + // estimated_power might be too low by 1. + int estimated_power = EstimatePower(normalized_exponent); + + if (mode == BIGNUM_DTOA_FIXED && -estimated_power - 1 > requested_digits) { + buffer[0] = '\0'; + *length = 0; + *decimal_point = -requested_digits; + return; + } + + Bignum numerator; + Bignum denominator; + Bignum delta_minus; + Bignum delta_plus; + + ASSERT(Bignum::kMaxSignificantBits >= 324*4); + InitialScaledStartValues(significand, exponent, lower_boundary_is_closer, + estimated_power, need_boundary_deltas, + &numerator, &denominator, + &delta_minus, &delta_plus); + + FixupMultiply10(estimated_power, is_even, decimal_point, + &numerator, &denominator, + &delta_minus, &delta_plus); + + switch (mode) { + case BIGNUM_DTOA_SHORTEST: + GenerateShortestDigits(&numerator, &denominator, + &delta_minus, &delta_plus, + is_even, buffer, length); + break; + case BIGNUM_DTOA_FIXED: + BignumToFixed(requested_digits, decimal_point, + &numerator, &denominator, + buffer, length); + break; + case BIGNUM_DTOA_PRECISION: + GenerateCountedDigits(requested_digits, decimal_point, + &numerator, &denominator, + buffer, length); + break; + default: + UNREACHABLE(); + } + buffer[*length] = '\0'; +} + +static void GenerateShortestDigits(Bignum* numerator, Bignum* denominator, + Bignum* delta_minus, Bignum* delta_plus, + bool is_even, + Vector<char> buffer, int* length) { + if (Bignum::Equal(*delta_minus, *delta_plus)) { + delta_plus = delta_minus; + } + *length = 0; + for (;;) { + uint16_t digit; + digit = numerator->DivideModuloIntBignum(*denominator); + ASSERT(digit <= 9); + + buffer[(*length)++] = static_cast<char>(digit + '0'); + + bool in_delta_room_minus; + bool in_delta_room_plus; + if (is_even) { + in_delta_room_minus = Bignum::LessEqual(*numerator, *delta_minus); + } else { + in_delta_room_minus = Bignum::Less(*numerator, *delta_minus); + } + if (is_even) { + in_delta_room_plus = + Bignum::PlusCompare(*numerator, *delta_plus, *denominator) >= 0; + } else { + in_delta_room_plus = + Bignum::PlusCompare(*numerator, *delta_plus, *denominator) > 0; + } + if (!in_delta_room_minus && !in_delta_room_plus) { + numerator->Times10(); + delta_minus->Times10(); + + if (delta_minus != delta_plus) { + delta_plus->Times10(); + } + } else if (in_delta_room_minus && in_delta_room_plus) { + + int compare = Bignum::PlusCompare(*numerator, *numerator, *denominator); + if (compare < 0) { + // Remaining digits are less than .5. -> Round down (== do nothing). + } else if (compare > 0) { + // Remaining digits are more than .5 of denominator. -> Round up. + ASSERT(buffer[(*length) - 1] != '9'); + buffer[(*length) - 1]++; + } else { + if ((buffer[(*length) - 1] - '0') % 2 == 0) { + // Round down => Do nothing. + } else { + ASSERT(buffer[(*length) - 1] != '9'); + buffer[(*length) - 1]++; + } + } + return; + } else if (in_delta_room_minus) { + return; + } else { // in_delta_room_plus + // Round up + ASSERT(buffer[(*length) -1] != '9'); + buffer[(*length) - 1]++; + return; + } + } +} + +static void GenerateCountedDigits(int count, int* decimal_point, + Bignum* numerator, Bignum* denominator, + Vector<char> buffer, int* length) { + ASSERT(count >= 0); + for (int i = 0; i < count - 1; ++i) { + uint16_t digit; + digit = numerator->DivideModuloIntBignum(*denominator); + ASSERT(digit <= 9); + + buffer[i] = static_cast<char>(digit + '0'); + numerator->Times10(); + } + + uint16_t digit; + digit = numerator->DivideModuloIntBignum(*denominator); + if (Bignum::PlusCompare(*numerator, *numerator, *denominator) >= 0) { + digit++; + } + ASSERT(digit <= 10); + buffer[count - 1] = static_cast<char>(digit + '0'); + + for (int i = count - 1; i > 0; --i) { + if (buffer[i] != '0' + 10) break; + buffer[i] = '0'; + buffer[i - 1]++; + } + if (buffer[0] == '0' + 10) { + buffer[0] = '1'; + (*decimal_point)++; + } + *length = count; +} + +static void BignumToFixed(int requested_digits, int* decimal_point, + Bignum* numerator, Bignum* denominator, + Vector<char>(buffer), int* length) +{ + if (-(*decimal_point) > requested_digits) { + *decimal_point = -requested_digits; + *length = 0; + return; + } else if (-(*decimal_point) == requested_digits) { + ASSERT(*decimal_point == -requested_digits); + + denominator->Times10(); + if (Bignum::PlusCompare(*numerator, *numerator, *denominator) >= 0) { + buffer[0] = '1'; + *length = 1; + (*decimal_point)++; + } else { + *length = 0; + } + return; + } else { + int needed_digits = (*decimal_point) + requested_digits; + GenerateCountedDigits(needed_digits, decimal_point, + numerator, denominator, + buffer, length); + } +} + +static int EstimatePower(int exponent) { + const double k1Log10 = 0.30102999566398114; // 1/lg(10) + + // For doubles len(f) == 53 (don't forget the hidden bit). + const int kSignificandSize = Double::kSignificandSize; + double estimate = ceil((exponent + kSignificandSize - 1) * k1Log10 - 1e-10); + return static_cast<int>(estimate); +} + +static void InitialScaledStartValuesPositiveExponent( + uint64_t significand, int exponent, + int estimated_power, bool need_boundary_deltas, + Bignum* numerator, Bignum* denominator, + Bignum* delta_minus, Bignum* delta_plus) +{ + ASSERT(estimated_power >= 0); + + numerator->AssignUInt64(significand); + numerator->ShiftLeft(exponent); + denominator->AssignPowerUInt16(10, estimated_power); + + if (need_boundary_deltas) { + denominator->ShiftLeft(1); + numerator->ShiftLeft(1); + delta_plus->AssignUInt16(1); + delta_plus->ShiftLeft(exponent); + delta_minus->AssignUInt16(1); + delta_minus->ShiftLeft(exponent); + } +} + +static void InitialScaledStartValuesNegativeExponentPositivePower( + uint64_t significand, int exponent, + int estimated_power, bool need_boundary_deltas, + Bignum* numerator, Bignum* denominator, + Bignum* delta_minus, Bignum* delta_plus) +{ + numerator->AssignUInt64(significand); + denominator->AssignPowerUInt16(10, estimated_power); + denominator->ShiftLeft(-exponent); + + if (need_boundary_deltas) { + denominator->ShiftLeft(1); + numerator->ShiftLeft(1); + delta_plus->AssignUInt16(1); + delta_minus->AssignUInt16(1); + } +} + +static void InitialScaledStartValuesNegativeExponentNegativePower( + uint64_t significand, int exponent, + int estimated_power, bool need_boundary_deltas, + Bignum* numerator, Bignum* denominator, + Bignum* delta_minus, Bignum* delta_plus) +{ + Bignum* power_ten = numerator; + power_ten->AssignPowerUInt16(10, -estimated_power); + + if (need_boundary_deltas) { + delta_plus->AssignBignum(*power_ten); + delta_minus->AssignBignum(*power_ten); + } + + ASSERT(numerator == power_ten); + numerator->MultiplyByUInt64(significand); + + denominator->AssignUInt16(1); + denominator->ShiftLeft(-exponent); + + if (need_boundary_deltas) { + numerator->ShiftLeft(1); + denominator->ShiftLeft(1); + } +} + +static void InitialScaledStartValues(uint64_t significand, + int exponent, + bool lower_boundary_is_closer, + int estimated_power, + bool need_boundary_deltas, + Bignum* numerator, + Bignum* denominator, + Bignum* delta_minus, + Bignum* delta_plus) +{ + if (exponent >= 0) { + InitialScaledStartValuesPositiveExponent( + significand, exponent, estimated_power, need_boundary_deltas, + numerator, denominator, delta_minus, delta_plus); + } else if (estimated_power >= 0) { + InitialScaledStartValuesNegativeExponentPositivePower( + significand, exponent, estimated_power, need_boundary_deltas, + numerator, denominator, delta_minus, delta_plus); + } else { + InitialScaledStartValuesNegativeExponentNegativePower( + significand, exponent, estimated_power, need_boundary_deltas, + numerator, denominator, delta_minus, delta_plus); + } + + if (need_boundary_deltas && lower_boundary_is_closer) { + denominator->ShiftLeft(1); // *2 + numerator->ShiftLeft(1); // *2 + delta_plus->ShiftLeft(1); // *2 + } +} + +static void FixupMultiply10(int estimated_power, bool is_even, + int* decimal_point, + Bignum* numerator, Bignum* denominator, + Bignum* delta_minus, Bignum* delta_plus) { + bool in_range; + if (is_even) { + in_range = Bignum::PlusCompare(*numerator, *delta_plus, *denominator) >= 0; + } else { + in_range = Bignum::PlusCompare(*numerator, *delta_plus, *denominator) > 0; + } + if (in_range) { + *decimal_point = estimated_power + 1; + } else { + *decimal_point = estimated_power; + numerator->Times10(); + if (Bignum::Equal(*delta_minus, *delta_plus)) { + delta_minus->Times10(); + delta_plus->AssignBignum(*delta_minus); + } else { + delta_minus->Times10(); + delta_plus->Times10(); + } + } +} + +enum FastDtoaMode { + FAST_DTOA_SHORTEST, + FAST_DTOA_PRECISION +}; + +static const int kFastDtoaMaximalLength = 17; + +bool FastDtoa(double d, + FastDtoaMode mode, + int requested_digits, + Vector<char> buffer, + int* length, + int* decimal_point); + +static const int kMinimalTargetExponent = -60; +static const int kMaximalTargetExponent = -32; + +static bool RoundWeed(Vector<char> buffer, int length, + uint64_t distance_too_high_w, uint64_t unsafe_interval, + uint64_t rest, uint64_t ten_kappa, uint64_t unit) +{ + uint64_t small_distance = distance_too_high_w - unit; + uint64_t big_distance = distance_too_high_w + unit; + + ASSERT(rest <= unsafe_interval); + while (rest < small_distance && // Negated condition 1 + unsafe_interval - rest >= ten_kappa && // Negated condition 2 + (rest + ten_kappa < small_distance || // buffer{-1} > w_high + small_distance - rest >= rest + ten_kappa - small_distance)) { + buffer[length - 1]--; + rest += ten_kappa; + } + + if (rest < big_distance && + unsafe_interval - rest >= ten_kappa && + (rest + ten_kappa < big_distance || + big_distance - rest > rest + ten_kappa - big_distance)) { + return false; + } + + return (2 * unit <= rest) && (rest <= unsafe_interval - 4 * unit); +} + +static bool RoundWeedCounted(Vector<char> buffer, int length, + uint64_t rest, uint64_t ten_kappa, uint64_t unit, + int* kappa) +{ + ASSERT(rest < ten_kappa); + + if (unit >= ten_kappa) return false; + if (ten_kappa - unit <= unit) return false; + if ((ten_kappa - rest > rest) && (ten_kappa - 2 * rest >= 2 * unit)) { + return true; + } + + if ((rest > unit) && (ten_kappa - (rest - unit) <= (rest - unit))) { + buffer[length - 1]++; + for (int i = length - 1; i > 0; --i) { + if (buffer[i] != '0' + 10) break; + buffer[i] = '0'; + buffer[i - 1]++; + } + if (buffer[0] == '0' + 10) { + buffer[0] = '1'; + (*kappa) += 1; + } + return true; + } + return false; +} + +static unsigned int const kSmallPowersOfTen[] = + {0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, + 1000000000}; + +static void BiggestPowerTen(uint32_t number, + int number_bits, + uint32_t* power, + int* exponent_plus_one) { + ASSERT(number < (1u << (number_bits + 1))); + + int exponent_plus_one_guess = ((number_bits + 1) * 1233 >> 12); + exponent_plus_one_guess++; + + if (number < kSmallPowersOfTen[exponent_plus_one_guess]) { + exponent_plus_one_guess--; + } + *power = kSmallPowersOfTen[exponent_plus_one_guess]; + *exponent_plus_one = exponent_plus_one_guess; +} + +static bool DigitGen(DiyFp low, DiyFp w, DiyFp high, Vector<char> buffer, + int* length, int* kappa) +{ + ASSERT(low.e() == w.e() && w.e() == high.e()); + ASSERT(low.f() + 1 <= high.f() - 1); + ASSERT(kMinimalTargetExponent <= w.e() && w.e() <= kMaximalTargetExponent); + + uint64_t unit = 1; + DiyFp too_low = DiyFp(low.f() - unit, low.e()); + DiyFp too_high = DiyFp(high.f() + unit, high.e()); + DiyFp unsafe_interval = DiyFp::Minus(too_high, too_low); + DiyFp one = DiyFp(static_cast<uint64_t>(1) << -w.e(), w.e()); + + uint32_t integrals = static_cast<uint32_t>(too_high.f() >> -one.e()); + uint64_t fractionals = too_high.f() & (one.f() - 1); + uint32_t divisor; + int divisor_exponent_plus_one; + BiggestPowerTen(integrals, DiyFp::kSignificandSize - (-one.e()), + &divisor, &divisor_exponent_plus_one); + *kappa = divisor_exponent_plus_one; + *length = 0; + + while (*kappa > 0) { + int digit = integrals / divisor; + ASSERT(digit <= 9); + buffer[*length] = static_cast<char>('0' + digit); + (*length)++; + integrals %= divisor; + (*kappa)--; + + uint64_t rest = + (static_cast<uint64_t>(integrals) << -one.e()) + fractionals; + + if (rest < unsafe_interval.f()) { + return RoundWeed(buffer, *length, DiyFp::Minus(too_high, w).f(), + unsafe_interval.f(), rest, + static_cast<uint64_t>(divisor) << -one.e(), unit); + } + divisor /= 10; + } + + ASSERT(one.e() >= -60); + ASSERT(fractionals < one.f()); + ASSERT(UINT64_2PART_C(0xFFFFFFFF, FFFFFFFF) / 10 >= one.f()); + + for (;;) { + fractionals *= 10; + unit *= 10; + unsafe_interval.set_f(unsafe_interval.f() * 10); + // Integer division by one. + int digit = static_cast<int>(fractionals >> -one.e()); + ASSERT(digit <= 9); + buffer[*length] = static_cast<char>('0' + digit); + (*length)++; + fractionals &= one.f() - 1; // Modulo by one. + (*kappa)--; + if (fractionals < unsafe_interval.f()) { + return RoundWeed(buffer, *length, DiyFp::Minus(too_high, w).f() * unit, + unsafe_interval.f(), fractionals, one.f(), unit); + } + } +} + +static bool DigitGenCounted(DiyFp w, int requested_digits, Vector<char> buffer, + int* length, int* kappa) +{ + ASSERT(kMinimalTargetExponent <= w.e() && w.e() <= kMaximalTargetExponent); + ASSERT(kMinimalTargetExponent >= -60); + ASSERT(kMaximalTargetExponent <= -32); + + uint64_t w_error = 1; + DiyFp one = DiyFp(static_cast<uint64_t>(1) << -w.e(), w.e()); + uint32_t integrals = static_cast<uint32_t>(w.f() >> -one.e()); + uint64_t fractionals = w.f() & (one.f() - 1); + uint32_t divisor; + int divisor_exponent_plus_one; + BiggestPowerTen(integrals, DiyFp::kSignificandSize - (-one.e()), + &divisor, &divisor_exponent_plus_one); + *kappa = divisor_exponent_plus_one; + *length = 0; + + while (*kappa > 0) { + int digit = integrals / divisor; + ASSERT(digit <= 9); + buffer[*length] = static_cast<char>('0' + digit); + (*length)++; + requested_digits--; + integrals %= divisor; + (*kappa)--; + if (requested_digits == 0) break; + divisor /= 10; + } + + if (requested_digits == 0) { + uint64_t rest = + (static_cast<uint64_t>(integrals) << -one.e()) + fractionals; + return RoundWeedCounted(buffer, *length, rest, + static_cast<uint64_t>(divisor) << -one.e(), w_error, + kappa); + } + + ASSERT(one.e() >= -60); + ASSERT(fractionals < one.f()); + ASSERT(UINT64_2PART_C(0xFFFFFFFF, FFFFFFFF) / 10 >= one.f()); + + while (requested_digits > 0 && fractionals > w_error) { + fractionals *= 10; + w_error *= 10; + // Integer division by one. + int digit = static_cast<int>(fractionals >> -one.e()); + ASSERT(digit <= 9); + buffer[*length] = static_cast<char>('0' + digit); + (*length)++; + requested_digits--; + fractionals &= one.f() - 1; // Modulo by one. + (*kappa)--; + } + if (requested_digits != 0) return false; + return RoundWeedCounted(buffer, *length, fractionals, one.f(), w_error, + kappa); +} + +static bool Grisu3(double v, FastDtoaMode mode, Vector<char> buffer, + int* length, int* decimal_exponent) +{ + DiyFp w = Double(v).AsNormalizedDiyFp(); + DiyFp boundary_minus, boundary_plus; + + ASSERT(mode == FAST_DTOA_SHORTEST); + Double(v).NormalizedBoundaries(&boundary_minus, &boundary_plus); + + ASSERT(boundary_plus.e() == w.e()); + DiyFp ten_mk; // Cached power of ten: 10^-k + int mk; // -k + int ten_mk_minimal_binary_exponent = + kMinimalTargetExponent - (w.e() + DiyFp::kSignificandSize); + int ten_mk_maximal_binary_exponent = + kMaximalTargetExponent - (w.e() + DiyFp::kSignificandSize); + PowersOfTenCache::GetCachedPowerForBinaryExponentRange( + ten_mk_minimal_binary_exponent, + ten_mk_maximal_binary_exponent, + &ten_mk, &mk); + + ASSERT((kMinimalTargetExponent <= w.e() + ten_mk.e() + + DiyFp::kSignificandSize) && + (kMaximalTargetExponent >= w.e() + ten_mk.e() + + DiyFp::kSignificandSize)); + + DiyFp scaled_w = DiyFp::Times(w, ten_mk); + ASSERT(scaled_w.e() == + boundary_plus.e() + ten_mk.e() + DiyFp::kSignificandSize); + + DiyFp scaled_boundary_minus = DiyFp::Times(boundary_minus, ten_mk); + DiyFp scaled_boundary_plus = DiyFp::Times(boundary_plus, ten_mk); + + int kappa; + bool result = DigitGen(scaled_boundary_minus, scaled_w, scaled_boundary_plus, + buffer, length, &kappa); + *decimal_exponent = -mk + kappa; + return result; +} + +static bool Grisu3Counted(double v, int requested_digits, Vector<char> buffer, + int* length, int* decimal_exponent) +{ + DiyFp w = Double(v).AsNormalizedDiyFp(); + DiyFp ten_mk; // Cached power of ten: 10^-k + int mk; // -k + int ten_mk_minimal_binary_exponent = + kMinimalTargetExponent - (w.e() + DiyFp::kSignificandSize); + int ten_mk_maximal_binary_exponent = + kMaximalTargetExponent - (w.e() + DiyFp::kSignificandSize); + PowersOfTenCache::GetCachedPowerForBinaryExponentRange( + ten_mk_minimal_binary_exponent, + ten_mk_maximal_binary_exponent, + &ten_mk, &mk); + ASSERT((kMinimalTargetExponent <= w.e() + ten_mk.e() + + DiyFp::kSignificandSize) && + (kMaximalTargetExponent >= w.e() + ten_mk.e() + + DiyFp::kSignificandSize)); + + DiyFp scaled_w = DiyFp::Times(w, ten_mk); + + int kappa; + bool result = DigitGenCounted(scaled_w, requested_digits, + buffer, length, &kappa); + *decimal_exponent = -mk + kappa; + return result; +} + + +bool FastDtoa(double v, + FastDtoaMode mode, + int requested_digits, + Vector<char> buffer, + int* length, + int* decimal_point) { + ASSERT(v > 0); + ASSERT(!Double(v).IsSpecial()); + + bool result = false; + int decimal_exponent = 0; + switch (mode) { + case FAST_DTOA_SHORTEST: + result = Grisu3(v, mode, buffer, length, &decimal_exponent); + break; + case FAST_DTOA_PRECISION: + result = Grisu3Counted(v, requested_digits, + buffer, length, &decimal_exponent); + break; + default: + UNREACHABLE(); + } + if (result) { + *decimal_point = *length + decimal_exponent; + buffer[*length] = '\0'; + } + return result; +} + +// Represents a 128bit type. This class should be replaced by a native type on +// platforms that support 128bit integers. +class UInt128 { + public: + UInt128() : high_bits_(0), low_bits_(0) { } + UInt128(uint64_t high, uint64_t low) : high_bits_(high), low_bits_(low) { } + + void Multiply(uint32_t multiplicand) { + uint64_t accumulator; + + accumulator = (low_bits_ & kMask32) * multiplicand; + uint32_t part = static_cast<uint32_t>(accumulator & kMask32); + accumulator >>= 32; + accumulator = accumulator + (low_bits_ >> 32) * multiplicand; + low_bits_ = (accumulator << 32) + part; + accumulator >>= 32; + accumulator = accumulator + (high_bits_ & kMask32) * multiplicand; + part = static_cast<uint32_t>(accumulator & kMask32); + accumulator >>= 32; + accumulator = accumulator + (high_bits_ >> 32) * multiplicand; + high_bits_ = (accumulator << 32) + part; + ASSERT((accumulator >> 32) == 0); + } + + void Shift(int shift_amount) { + ASSERT(-64 <= shift_amount && shift_amount <= 64); + if (shift_amount == 0) { + return; + } else if (shift_amount == -64) { + high_bits_ = low_bits_; + low_bits_ = 0; + } else if (shift_amount == 64) { + low_bits_ = high_bits_; + high_bits_ = 0; + } else if (shift_amount <= 0) { + high_bits_ <<= -shift_amount; + high_bits_ += low_bits_ >> (64 + shift_amount); + low_bits_ <<= -shift_amount; + } else { + low_bits_ >>= shift_amount; + low_bits_ += high_bits_ << (64 - shift_amount); + high_bits_ >>= shift_amount; + } + } + + // Modifies *this to *this MOD (2^power). + // Returns *this DIV (2^power). + int DivModPowerOf2(int power) { + if (power >= 64) { + int result = static_cast<int>(high_bits_ >> (power - 64)); + high_bits_ -= static_cast<uint64_t>(result) << (power - 64); + return result; + } else { + uint64_t part_low = low_bits_ >> power; + uint64_t part_high = high_bits_ << (64 - power); + int result = static_cast<int>(part_low + part_high); + high_bits_ = 0; + low_bits_ -= part_low << power; + return result; + } + } + + bool IsZero() const { + return high_bits_ == 0 && low_bits_ == 0; + } + + int BitAt(int position) { + if (position >= 64) { + return static_cast<int>(high_bits_ >> (position - 64)) & 1; + } else { + return static_cast<int>(low_bits_ >> position) & 1; + } + } + + private: + static const uint64_t kMask32 = 0xFFFFFFFF; + // Value == (high_bits_ << 64) + low_bits_ + uint64_t high_bits_; + uint64_t low_bits_; +}; + + +static const int kDoubleSignificandSize = 53; // Includes the hidden bit. + + +static void FillDigits32FixedLength(uint32_t number, int requested_length, + Vector<char> buffer, int* length) { + for (int i = requested_length - 1; i >= 0; --i) { + buffer[(*length) + i] = '0' + number % 10; + number /= 10; + } + *length += requested_length; +} + + +static void FillDigits32(uint32_t number, Vector<char> buffer, int* length) { + int number_length = 0; + // We fill the digits in reverse order and exchange them afterwards. + while (number != 0) { + int digit = number % 10; + number /= 10; + buffer[(*length) + number_length] = static_cast<char>('0' + digit); + number_length++; + } + // Exchange the digits. + int i = *length; + int j = *length + number_length - 1; + while (i < j) { + char tmp = buffer[i]; + buffer[i] = buffer[j]; + buffer[j] = tmp; + i++; + j--; + } + *length += number_length; +} + + +static void FillDigits64FixedLength(uint64_t number, + Vector<char> buffer, int* length) { + const uint32_t kTen7 = 10000000; + // For efficiency cut the number into 3 uint32_t parts, and print those. + uint32_t part2 = static_cast<uint32_t>(number % kTen7); + number /= kTen7; + uint32_t part1 = static_cast<uint32_t>(number % kTen7); + uint32_t part0 = static_cast<uint32_t>(number / kTen7); + + FillDigits32FixedLength(part0, 3, buffer, length); + FillDigits32FixedLength(part1, 7, buffer, length); + FillDigits32FixedLength(part2, 7, buffer, length); +} + + +static void FillDigits64(uint64_t number, Vector<char> buffer, int* length) { + const uint32_t kTen7 = 10000000; + // For efficiency cut the number into 3 uint32_t parts, and print those. + uint32_t part2 = static_cast<uint32_t>(number % kTen7); + number /= kTen7; + uint32_t part1 = static_cast<uint32_t>(number % kTen7); + uint32_t part0 = static_cast<uint32_t>(number / kTen7); + + if (part0 != 0) { + FillDigits32(part0, buffer, length); + FillDigits32FixedLength(part1, 7, buffer, length); + FillDigits32FixedLength(part2, 7, buffer, length); + } else if (part1 != 0) { + FillDigits32(part1, buffer, length); + FillDigits32FixedLength(part2, 7, buffer, length); + } else { + FillDigits32(part2, buffer, length); + } +} + + +static void RoundUp(Vector<char> buffer, int* length, int* decimal_point) { + // An empty buffer represents 0. + if (*length == 0) { + buffer[0] = '1'; + *decimal_point = 1; + *length = 1; + return; + } + + buffer[(*length) - 1]++; + for (int i = (*length) - 1; i > 0; --i) { + if (buffer[i] != '0' + 10) { + return; + } + buffer[i] = '0'; + buffer[i - 1]++; + } + + if (buffer[0] == '0' + 10) { + buffer[0] = '1'; + (*decimal_point)++; + } +} + +static void FillFractionals(uint64_t fractionals, int exponent, + int fractional_count, Vector<char> buffer, + int* length, int* decimal_point) +{ + ASSERT(-128 <= exponent && exponent <= 0); + + if (-exponent <= 64) { + ASSERT(fractionals >> 56 == 0); + int point = -exponent; + for (int i = 0; i < fractional_count; ++i) { + if (fractionals == 0) break; + fractionals *= 5; + point--; + int digit = static_cast<int>(fractionals >> point); + ASSERT(digit <= 9); + buffer[*length] = static_cast<char>('0' + digit); + (*length)++; + fractionals -= static_cast<uint64_t>(digit) << point; + } + + if (((fractionals >> (point - 1)) & 1) == 1) { + RoundUp(buffer, length, decimal_point); + } + } else { // We need 128 bits. + ASSERT(64 < -exponent && -exponent <= 128); + UInt128 fractionals128 = UInt128(fractionals, 0); + fractionals128.Shift(-exponent - 64); + int point = 128; + for (int i = 0; i < fractional_count; ++i) { + if (fractionals128.IsZero()) break; + fractionals128.Multiply(5); + point--; + int digit = fractionals128.DivModPowerOf2(point); + ASSERT(digit <= 9); + buffer[*length] = static_cast<char>('0' + digit); + (*length)++; + } + if (fractionals128.BitAt(point - 1) == 1) { + RoundUp(buffer, length, decimal_point); + } + } +} + + +// Removes leading and trailing zeros. +// If leading zeros are removed then the decimal point position is adjusted. +static void TrimZeros(Vector<char> buffer, int* length, int* decimal_point) { + while (*length > 0 && buffer[(*length) - 1] == '0') { + (*length)--; + } + int first_non_zero = 0; + while (first_non_zero < *length && buffer[first_non_zero] == '0') { + first_non_zero++; + } + if (first_non_zero != 0) { + for (int i = first_non_zero; i < *length; ++i) { + buffer[i - first_non_zero] = buffer[i]; + } + *length -= first_non_zero; + *decimal_point -= first_non_zero; + } +} + + +bool FastFixedDtoa(double v, + int fractional_count, + Vector<char> buffer, + int* length, + int* decimal_point) { + const uint32_t kMaxUInt32 = 0xFFFFFFFF; + uint64_t significand = Double(v).Significand(); + int exponent = Double(v).Exponent(); + + if (exponent > 20) return false; + if (fractional_count > 20) return false; + *length = 0; + + if (exponent + kDoubleSignificandSize > 64) { + const uint64_t kFive17 = UINT64_2PART_C(0xB1, A2BC2EC5); // 5^17 + uint64_t divisor = kFive17; + int divisor_power = 17; + uint64_t dividend = significand; + uint32_t quotient; + uint64_t remainder; + + if (exponent > divisor_power) { + dividend <<= exponent - divisor_power; + quotient = static_cast<uint32_t>(dividend / divisor); + remainder = (dividend % divisor) << divisor_power; + } else { + divisor <<= divisor_power - exponent; + quotient = static_cast<uint32_t>(dividend / divisor); + remainder = (dividend % divisor) << exponent; + } + FillDigits32(quotient, buffer, length); + FillDigits64FixedLength(remainder, buffer, length); + *decimal_point = *length; + } else if (exponent >= 0) { + // 0 <= exponent <= 11 + significand <<= exponent; + FillDigits64(significand, buffer, length); + *decimal_point = *length; + } else if (exponent > -kDoubleSignificandSize) { + uint64_t integrals = significand >> -exponent; + uint64_t fractionals = significand - (integrals << -exponent); + if (integrals > kMaxUInt32) { + FillDigits64(integrals, buffer, length); + } else { + FillDigits32(static_cast<uint32_t>(integrals), buffer, length); + } + *decimal_point = *length; + FillFractionals(fractionals, exponent, fractional_count, + buffer, length, decimal_point); + } else if (exponent < -128) { + // This configuration (with at most 20 digits) means that all digits must be + // 0. + ASSERT(fractional_count <= 20); + buffer[0] = '\0'; + *length = 0; + *decimal_point = -fractional_count; + } else { + *decimal_point = 0; + FillFractionals(significand, exponent, fractional_count, + buffer, length, decimal_point); + } + TrimZeros(buffer, length, decimal_point); + buffer[*length] = '\0'; + if ((*length) == 0) { + *decimal_point = -fractional_count; + } + return true; +} + +static const int kMaxExactDoubleIntegerDecimalDigits = 15; +static const int kMaxUint64DecimalDigits = 19; + +static const int kMaxDecimalPower = 309; +static const int kMinDecimalPower = -324; + +// 2^64 = 18446744073709551616 +static const uint64_t kMaxUint64 = UINT64_2PART_C(0xFFFFFFFF, FFFFFFFF); +static const int kMaxSignificantDecimalDigits = 780; + +static Vector<const char> TrimLeadingZeros(Vector<const char> buffer) { + for (int i = 0; i < buffer.length(); i++) { + if (buffer[i] != '0') { + return buffer.SubVector(i, buffer.length()); + } + } + return Vector<const char>(buffer.start(), 0); +} + + +static Vector<const char> TrimTrailingZeros(Vector<const char> buffer) { + for (int i = buffer.length() - 1; i >= 0; --i) { + if (buffer[i] != '0') { + return buffer.SubVector(0, i + 1); + } + } + return Vector<const char>(buffer.start(), 0); +} + + +static void CutToMaxSignificantDigits(Vector<const char> buffer, + int exponent, + char* significant_buffer, + int* significant_exponent) { + for (int i = 0; i < kMaxSignificantDecimalDigits - 1; ++i) { + significant_buffer[i] = buffer[i]; + } + + ASSERT(buffer[buffer.length() - 1] != '0'); + + significant_buffer[kMaxSignificantDecimalDigits - 1] = '1'; + *significant_exponent = + exponent + (buffer.length() - kMaxSignificantDecimalDigits); +} + +static void TrimAndCut(Vector<const char> buffer, int exponent, + char* buffer_copy_space, int space_size, + Vector<const char>* trimmed, int* updated_exponent) { + Vector<const char> left_trimmed = TrimLeadingZeros(buffer); + Vector<const char> right_trimmed = TrimTrailingZeros(left_trimmed); + exponent += left_trimmed.length() - right_trimmed.length(); + if (right_trimmed.length() > kMaxSignificantDecimalDigits) { + (void) space_size; // Mark variable as used. + ASSERT(space_size >= kMaxSignificantDecimalDigits); + CutToMaxSignificantDigits(right_trimmed, exponent, + buffer_copy_space, updated_exponent); + *trimmed = Vector<const char>(buffer_copy_space, + kMaxSignificantDecimalDigits); + } else { + *trimmed = right_trimmed; + *updated_exponent = exponent; + } +} + +static uint64_t ReadUint64(Vector<const char> buffer, + int* number_of_read_digits) { + uint64_t result = 0; + int i = 0; + while (i < buffer.length() && result <= (kMaxUint64 / 10 - 1)) { + int digit = buffer[i++] - '0'; + ASSERT(0 <= digit && digit <= 9); + result = 10 * result + digit; + } + *number_of_read_digits = i; + return result; +} + +static void ReadDiyFp(Vector<const char> buffer, + DiyFp* result, + int* remaining_decimals) { + int read_digits; + uint64_t significand = ReadUint64(buffer, &read_digits); + if (buffer.length() == read_digits) { + *result = DiyFp(significand, 0); + *remaining_decimals = 0; + } else { + // Round the significand. + if (buffer[read_digits] >= '5') { + significand++; + } + // Compute the binary exponent. + int exponent = 0; + *result = DiyFp(significand, exponent); + *remaining_decimals = buffer.length() - read_digits; + } +} + +static DiyFp AdjustmentPowerOfTen(int exponent) { + ASSERT(0 < exponent); + ASSERT(exponent < PowersOfTenCache::kDecimalExponentDistance); + // Simply hardcode the remaining powers for the given decimal exponent + // distance. + ASSERT(PowersOfTenCache::kDecimalExponentDistance == 8); + switch (exponent) { + case 1: return DiyFp(UINT64_2PART_C(0xa0000000, 00000000), -60); + case 2: return DiyFp(UINT64_2PART_C(0xc8000000, 00000000), -57); + case 3: return DiyFp(UINT64_2PART_C(0xfa000000, 00000000), -54); + case 4: return DiyFp(UINT64_2PART_C(0x9c400000, 00000000), -50); + case 5: return DiyFp(UINT64_2PART_C(0xc3500000, 00000000), -47); + case 6: return DiyFp(UINT64_2PART_C(0xf4240000, 00000000), -44); + case 7: return DiyFp(UINT64_2PART_C(0x98968000, 00000000), -40); + default: + UNREACHABLE(); + } +} + +static bool DiyFpStrtod(Vector<const char> buffer, + int exponent, + double* result) { + DiyFp input; + int remaining_decimals; + ReadDiyFp(buffer, &input, &remaining_decimals); + + const int kDenominatorLog = 3; + const int kDenominator = 1 << kDenominatorLog; + // Move the remaining decimals into the exponent. + exponent += remaining_decimals; + int error = (remaining_decimals == 0 ? 0 : kDenominator / 2); + + int old_e = input.e(); + input.Normalize(); + error <<= old_e - input.e(); + + ASSERT(exponent <= PowersOfTenCache::kMaxDecimalExponent); + if (exponent < PowersOfTenCache::kMinDecimalExponent) { + *result = 0.0; + return true; + } + DiyFp cached_power; + int cached_decimal_exponent; + PowersOfTenCache::GetCachedPowerForDecimalExponent(exponent, + &cached_power, + &cached_decimal_exponent); + + if (cached_decimal_exponent != exponent) { + int adjustment_exponent = exponent - cached_decimal_exponent; + DiyFp adjustment_power = AdjustmentPowerOfTen(adjustment_exponent); + input.Multiply(adjustment_power); + if (kMaxUint64DecimalDigits - buffer.length() >= adjustment_exponent) { + // The product of input with the adjustment power fits into a 64 bit + // integer. + ASSERT(DiyFp::kSignificandSize == 64); + } else { + // The adjustment power is exact. There is hence only an error of 0.5. + error += kDenominator / 2; + } + } + + input.Multiply(cached_power); + + int error_b = kDenominator / 2; + int error_ab = (error == 0 ? 0 : 1); // We round up to 1. + int fixed_error = kDenominator / 2; + error += error_b + error_ab + fixed_error; + + old_e = input.e(); + input.Normalize(); + error <<= old_e - input.e(); + + int order_of_magnitude = DiyFp::kSignificandSize + input.e(); + int effective_significand_size = + Double::SignificandSizeForOrderOfMagnitude(order_of_magnitude); + int precision_digits_count = + DiyFp::kSignificandSize - effective_significand_size; + if (precision_digits_count + kDenominatorLog >= DiyFp::kSignificandSize) { + int shift_amount = (precision_digits_count + kDenominatorLog) - + DiyFp::kSignificandSize + 1; + input.set_f(input.f() >> shift_amount); + input.set_e(input.e() + shift_amount); + error = (error >> shift_amount) + 1 + kDenominator; + precision_digits_count -= shift_amount; + } + + ASSERT(DiyFp::kSignificandSize == 64); + ASSERT(precision_digits_count < 64); + uint64_t one64 = 1; + uint64_t precision_bits_mask = (one64 << precision_digits_count) - 1; + uint64_t precision_bits = input.f() & precision_bits_mask; + uint64_t half_way = one64 << (precision_digits_count - 1); + precision_bits *= kDenominator; + half_way *= kDenominator; + DiyFp rounded_input(input.f() >> precision_digits_count, + input.e() + precision_digits_count); + if (precision_bits >= half_way + error) { + rounded_input.set_f(rounded_input.f() + 1); + } + + *result = Double(rounded_input).value(); + if (half_way - error < precision_bits && precision_bits < half_way + error) { + return false; + } else { + return true; + } +} + +static int CompareBufferWithDiyFp(Vector<const char> buffer, + int exponent, + DiyFp diy_fp) { + ASSERT(buffer.length() + exponent <= kMaxDecimalPower + 1); + ASSERT(buffer.length() + exponent > kMinDecimalPower); + ASSERT(buffer.length() <= kMaxSignificantDecimalDigits); + ASSERT(((kMaxDecimalPower + 1) * 333 / 100) < Bignum::kMaxSignificantBits); + + Bignum buffer_bignum; + Bignum diy_fp_bignum; + buffer_bignum.AssignDecimalString(buffer); + diy_fp_bignum.AssignUInt64(diy_fp.f()); + if (exponent >= 0) { + buffer_bignum.MultiplyByPowerOfTen(exponent); + } else { + diy_fp_bignum.MultiplyByPowerOfTen(-exponent); + } + if (diy_fp.e() > 0) { + diy_fp_bignum.ShiftLeft(diy_fp.e()); + } else { + buffer_bignum.ShiftLeft(-diy_fp.e()); + } + return Bignum::Compare(buffer_bignum, diy_fp_bignum); +} + +static bool ComputeGuess(Vector<const char> trimmed, int exponent, + double* guess) +{ + if (trimmed.length() == 0) { + *guess = 0.0; + return true; + } + if (exponent + trimmed.length() - 1 >= kMaxDecimalPower) { + *guess = Double::Infinity(); + return true; + } + if (exponent + trimmed.length() <= kMinDecimalPower) { + *guess = 0.0; + return true; + } + + if (DiyFpStrtod(trimmed, exponent, guess)) { + return true; + } + if (*guess == Double::Infinity()) { + return true; + } + return false; +} + +double Strtod(Vector<const char> buffer, int exponent) +{ + char copy_buffer[kMaxSignificantDecimalDigits]; + Vector<const char> trimmed; + int updated_exponent; + TrimAndCut(buffer, exponent, copy_buffer, kMaxSignificantDecimalDigits, + &trimmed, &updated_exponent); + exponent = updated_exponent; + + double guess; + bool is_correct = ComputeGuess(trimmed, exponent, &guess); + if (is_correct) return guess; + + DiyFp upper_boundary = Double(guess).UpperBoundary(); + int comparison = CompareBufferWithDiyFp(trimmed, exponent, upper_boundary); + if (comparison < 0) { + return guess; + } else if (comparison > 0) { + return Double(guess).NextDouble(); + } else if ((Double(guess).Significand() & 1) == 0) { + // Round towards even. + return guess; + } else { + return Double(guess).NextDouble(); + } +} + +class DoubleToStringConverter { +public: + static const int kMaxFixedDigitsBeforePoint = 60; + static const int kMaxFixedDigitsAfterPoint = 60; + static const int kMaxExponentialDigits = 120; + static const int kMinPrecisionDigits = 1; + static const int kMaxPrecisionDigits = 120; + + enum Flags { + NO_FLAGS = 0, + EMIT_POSITIVE_EXPONENT_SIGN = 1, + EMIT_TRAILING_DECIMAL_POINT = 2, + EMIT_TRAILING_ZERO_AFTER_POINT = 4, + UNIQUE_ZERO = 8 + }; + + DoubleToStringConverter(int flags, + const char* infinity_symbol, + const char* nan_symbol, + char exponent_character, + int decimal_in_shortest_low, + int decimal_in_shortest_high, + int max_leading_padding_zeroes_in_precision_mode, + int max_trailing_padding_zeroes_in_precision_mode) + : flags_(flags), + infinity_symbol_(infinity_symbol), + nan_symbol_(nan_symbol), + exponent_character_(exponent_character), + decimal_in_shortest_low_(decimal_in_shortest_low), + decimal_in_shortest_high_(decimal_in_shortest_high), + max_leading_padding_zeroes_in_precision_mode_( + max_leading_padding_zeroes_in_precision_mode), + max_trailing_padding_zeroes_in_precision_mode_( + max_trailing_padding_zeroes_in_precision_mode) { + // When 'trailing zero after the point' is set, then 'trailing point' + // must be set too. + ASSERT(((flags & EMIT_TRAILING_DECIMAL_POINT) != 0) || + !((flags & EMIT_TRAILING_ZERO_AFTER_POINT) != 0)); + } + + bool ToShortest(double value, std::string &s) const { + return ToShortestIeeeNumber(value, s, SHORTEST); + } + + bool ToFixed(double value, + int requested_digits, + std::string &s) const; + + bool ToExponential(double value, + int requested_digits, + std::string &s) const; + + bool ToPrecision(double value, + int precision, + std::string &s) const; + + enum DtoaMode { + SHORTEST, + FIXED, // Produce a fixed number of digits after the decimal point + PRECISION // Fixed number of digits (independent of the decimal point) + }; + + static const int kBase10MaximalLength = 17; + + // The result should be interpreted as buffer * 10^(point-length). + static void DoubleToAscii(double v, + DtoaMode mode, + int requested_digits, + char* buffer, + int buffer_length, + bool* sign, + int* length, + int* point); + + private: + // Implementation for ToShortest. + bool ToShortestIeeeNumber(double value, + std::string &s, + DtoaMode mode) const; + + bool HandleSpecialValues(double value, std::string &s) const; + + void CreateExponentialRepresentation(const char* decimal_digits, + int length, + int exponent, + std::string &s) const; + + void CreateDecimalRepresentation(const char* decimal_digits, + int length, + int decimal_point, + int digits_after_point, + std::string &s) const; + + const int flags_; + const char* const infinity_symbol_; + const char* const nan_symbol_; + const char exponent_character_; + const int decimal_in_shortest_low_; + const int decimal_in_shortest_high_; + const int max_leading_padding_zeroes_in_precision_mode_; + const int max_trailing_padding_zeroes_in_precision_mode_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(DoubleToStringConverter); +}; + + +class StringToDoubleConverter { + public: + enum Flags { + NO_FLAGS = 0, + ALLOW_HEX = 1, + ALLOW_OCTALS = 2, + ALLOW_TRAILING_JUNK = 4, + ALLOW_LEADING_SPACES = 8, + ALLOW_TRAILING_SPACES = 16, + ALLOW_SPACES_AFTER_SIGN = 32 + }; + + StringToDoubleConverter(int flags, + double empty_string_value, + double junk_string_value, + const char* infinity_symbol, + const char* nan_symbol) + : flags_(flags), + empty_string_value_(empty_string_value), + junk_string_value_(junk_string_value), + infinity_symbol_(infinity_symbol), + nan_symbol_(nan_symbol) { + } + + double StringToDouble(const char* buffer, + int length, + int* processed_characters_count) const; + + private: + const int flags_; + const double empty_string_value_; + const double junk_string_value_; + const char* const infinity_symbol_; + const char* const nan_symbol_; + + double StringToIeee(const char *start_pointer, + int length, + int* processed_characters_count) const; + + DISALLOW_IMPLICIT_CONSTRUCTORS(StringToDoubleConverter); +}; + +bool DoubleToStringConverter::HandleSpecialValues( + double value, + std::string &result) const { + Double double_inspect(value); + if (double_inspect.IsInfinite()) { + if (infinity_symbol_ == NULL) return false; + if (value < 0) { + result += '-'; + } + result += infinity_symbol_; + return true; + } + if (double_inspect.IsNan()) { + if (nan_symbol_ == NULL) return false; + result = nan_symbol_; + return true; + } + return false; +} + + +void DoubleToStringConverter::CreateExponentialRepresentation( + const char* decimal_digits, + int length, + int exponent, + std::string &result) const { + ASSERT(length != 0); + result += decimal_digits[0]; + if (length != 1) { + result += '.'; + result.append(&decimal_digits[1], length-1); + } + result += exponent_character_; + if (exponent < 0) { + result += '-'; + exponent = -exponent; + } else { + if ((flags_ & EMIT_POSITIVE_EXPONENT_SIGN) != 0) { + result += '+'; + } + } + if (exponent == 0) { + result += '0'; + return; + } + ASSERT(exponent < 1e4); + const int kMaxExponentLength = 5; + char buffer[kMaxExponentLength + 1]; + buffer[kMaxExponentLength] = '\0'; + int first_char_pos = kMaxExponentLength; + while (exponent > 0) { + buffer[--first_char_pos] = '0' + (exponent % 10); + exponent /= 10; + } + result.append(&buffer[first_char_pos], + kMaxExponentLength - first_char_pos); +} + + +void DoubleToStringConverter::CreateDecimalRepresentation( + const char* decimal_digits, + int length, + int decimal_point, + int digits_after_point, + std::string &result) const { + // Create a representation that is padded with zeros if needed. + if (decimal_point <= 0) { + // "0.00000decimal_rep". + result += '0'; + if (digits_after_point > 0) { + result += '.'; + result.append(-decimal_point, '0'); + ASSERT(length <= digits_after_point - (-decimal_point)); + result.append(decimal_digits, length); + int remaining_digits = digits_after_point - (-decimal_point) - length; + result.append(remaining_digits, '0'); + } + } else if (decimal_point >= length) { + // "decimal_rep0000.00000" or "decimal_rep.0000" + result.append(decimal_digits, length); + result.append(decimal_point - length, '0'); + if (digits_after_point > 0) { + result += '.'; + result.append(digits_after_point, '0'); + } + } else { + // "decima.l_rep000" + ASSERT(digits_after_point > 0); + result.append(decimal_digits, decimal_point); + result += '.'; + ASSERT(length - decimal_point <= digits_after_point); + result.append(&decimal_digits[decimal_point], length - decimal_point); + int remaining_digits = digits_after_point - (length - decimal_point); + result.append(remaining_digits, '0'); + } + if (digits_after_point == 0) { + if ((flags_ & EMIT_TRAILING_DECIMAL_POINT) != 0) { + result += '.'; + } + if ((flags_ & EMIT_TRAILING_ZERO_AFTER_POINT) != 0) { + result += '0'; + } + } +} + + +bool DoubleToStringConverter::ToShortestIeeeNumber( + double value, + std::string &result, + DoubleToStringConverter::DtoaMode mode) const { + ASSERT(mode == SHORTEST); + if (Double(value).IsSpecial()) { + return HandleSpecialValues(value, result); + } + + int decimal_point; + bool sign; + const int kDecimalRepCapacity = kBase10MaximalLength + 1; + char decimal_rep[kDecimalRepCapacity]; + int decimal_rep_length; + + DoubleToAscii(value, mode, 0, decimal_rep, kDecimalRepCapacity, + &sign, &decimal_rep_length, &decimal_point); + + bool unique_zero = (flags_ & UNIQUE_ZERO) != 0; + if (sign && (value != 0.0 || !unique_zero)) { + result += '-'; + } + + int exponent = decimal_point - 1; + if ((decimal_in_shortest_low_ <= exponent) && + (exponent < decimal_in_shortest_high_)) { + CreateDecimalRepresentation(decimal_rep, decimal_rep_length, decimal_point, + std::max(0, decimal_rep_length - decimal_point), + result); + } else { + CreateExponentialRepresentation(decimal_rep, decimal_rep_length, exponent, result); + } + return true; +} + + +bool DoubleToStringConverter::ToFixed(double value, + int requested_digits, + std::string &result) const +{ + ASSERT(kMaxFixedDigitsBeforePoint == 60); + const double kFirstNonFixed = 1e60; + + if (Double(value).IsSpecial()) { + return HandleSpecialValues(value, result); + } + + if (requested_digits > kMaxFixedDigitsAfterPoint) return false; + if (value >= kFirstNonFixed || value <= -kFirstNonFixed) return false; + + // Find a sufficiently precise decimal representation of n. + int decimal_point; + bool sign; + // Add space for the '\0' byte. + const int kDecimalRepCapacity = + kMaxFixedDigitsBeforePoint + kMaxFixedDigitsAfterPoint + 1; + char decimal_rep[kDecimalRepCapacity]; + int decimal_rep_length; + DoubleToAscii(value, FIXED, requested_digits, + decimal_rep, kDecimalRepCapacity, + &sign, &decimal_rep_length, &decimal_point); + + bool unique_zero = ((flags_ & UNIQUE_ZERO) != 0); + if (sign && (value != 0.0 || !unique_zero)) { + result += '-'; + } + + CreateDecimalRepresentation(decimal_rep, decimal_rep_length, decimal_point, + requested_digits, result); + return true; +} + + +bool DoubleToStringConverter::ToExponential( + double value, + int requested_digits, + std::string &result) const { + if (Double(value).IsSpecial()) { + return HandleSpecialValues(value, result); + } + + if (requested_digits < -1) return false; + if (requested_digits > kMaxExponentialDigits) return false; + + int decimal_point; + bool sign; + // Add space for digit before the decimal point and the '\0' character. + const int kDecimalRepCapacity = kMaxExponentialDigits + 2; + ASSERT(kDecimalRepCapacity > kBase10MaximalLength); + char decimal_rep[kDecimalRepCapacity]; + int decimal_rep_length; + + if (requested_digits == -1) { + DoubleToAscii(value, SHORTEST, 0, + decimal_rep, kDecimalRepCapacity, + &sign, &decimal_rep_length, &decimal_point); + } else { + DoubleToAscii(value, PRECISION, requested_digits + 1, + decimal_rep, kDecimalRepCapacity, + &sign, &decimal_rep_length, &decimal_point); + ASSERT(decimal_rep_length <= requested_digits + 1); + + for (int i = decimal_rep_length; i < requested_digits + 1; ++i) { + decimal_rep[i] = '0'; + } + decimal_rep_length = requested_digits + 1; + } + + bool unique_zero = ((flags_ & UNIQUE_ZERO) != 0); + if (sign && (value != 0.0 || !unique_zero)) { + result += '-'; + } + + int exponent = decimal_point - 1; + CreateExponentialRepresentation(decimal_rep, + decimal_rep_length, + exponent, result); + return true; +} + + +bool DoubleToStringConverter::ToPrecision(double value, + int precision, + std::string &result) const { + if (Double(value).IsSpecial()) { + return HandleSpecialValues(value, result); + } + + if (precision < kMinPrecisionDigits || precision > kMaxPrecisionDigits) { + return false; + } + + // Find a sufficiently precise decimal representation of n. + int decimal_point; + bool sign; + // Add one for the terminating null character. + const int kDecimalRepCapacity = kMaxPrecisionDigits + 1; + char decimal_rep[kDecimalRepCapacity]; + int decimal_rep_length; + + DoubleToAscii(value, PRECISION, precision, + decimal_rep, kDecimalRepCapacity, + &sign, &decimal_rep_length, &decimal_point); + ASSERT(decimal_rep_length <= precision); + + bool unique_zero = ((flags_ & UNIQUE_ZERO) != 0); + if (sign && (value != 0.0 || !unique_zero)) { + result += '-'; + } + + // The exponent if we print the number as x.xxeyyy. That is with the + // decimal point after the first digit. + int exponent = decimal_point - 1; + + int extra_zero = ((flags_ & EMIT_TRAILING_ZERO_AFTER_POINT) != 0) ? 1 : 0; + if ((-decimal_point + 1 > max_leading_padding_zeroes_in_precision_mode_) || + (decimal_point - precision + extra_zero > + max_trailing_padding_zeroes_in_precision_mode_)) { + for (int i = decimal_rep_length; i < precision; ++i) { + decimal_rep[i] = '0'; + } + + CreateExponentialRepresentation(decimal_rep, + precision, + exponent, + result); + } else { + CreateDecimalRepresentation(decimal_rep, decimal_rep_length, decimal_point, + std::max(0, precision - decimal_point), + result); + } + return true; +} + + +static BignumDtoaMode DtoaToBignumDtoaMode( + DoubleToStringConverter::DtoaMode dtoa_mode) { + switch (dtoa_mode) { + case DoubleToStringConverter::SHORTEST: return BIGNUM_DTOA_SHORTEST; + case DoubleToStringConverter::FIXED: return BIGNUM_DTOA_FIXED; + case DoubleToStringConverter::PRECISION: return BIGNUM_DTOA_PRECISION; + default: + UNREACHABLE(); + } +} + + +void DoubleToStringConverter::DoubleToAscii(double v, DtoaMode mode, int requested_digits, + char* buffer, int buffer_length, + bool* sign, int* length, int* point) +{ + Vector<char> vector(buffer, buffer_length); + ASSERT(!Double(v).IsSpecial()); + ASSERT(mode == SHORTEST || requested_digits >= 0); + + if (Double(v).Sign() < 0) { + *sign = true; + v = -v; + } else { + *sign = false; + } + + if (mode == PRECISION && requested_digits == 0) { + vector[0] = '\0'; + *length = 0; + return; + } + + if (v == 0) { + vector[0] = '0'; + vector[1] = '\0'; + *length = 1; + *point = 1; + return; + } + + bool fast_worked; + switch (mode) { + case SHORTEST: + fast_worked = FastDtoa(v, FAST_DTOA_SHORTEST, 0, vector, length, point); + break; + case FIXED: + fast_worked = FastFixedDtoa(v, requested_digits, vector, length, point); + break; + case PRECISION: + fast_worked = FastDtoa(v, FAST_DTOA_PRECISION, requested_digits, + vector, length, point); + break; + default: + fast_worked = false; + UNREACHABLE(); + } + if (fast_worked) return; + + // If the fast dtoa didn't succeed use the slower bignum version. + BignumDtoaMode bignum_mode = DtoaToBignumDtoaMode(mode); + BignumDtoa(v, bignum_mode, requested_digits, vector, length, point); + vector[*length] = '\0'; +} + +template <class Iterator> +static bool ConsumeSubString(Iterator* current, + Iterator end, + const char* substring) { + ASSERT(**current == *substring); + for (substring++; *substring != '\0'; substring++) { + ++*current; + if (*current == end || **current != *substring) return false; + } + ++*current; + return true; +} + +const int kMaxSignificantDigits = 772; + +static const char kWhitespaceTable7[] = { 32, 13, 10, 9, 11, 12 }; +static const int kWhitespaceTable7Length = ARRAY_SIZE(kWhitespaceTable7); + +static bool isWhitespace(int x) { + if (x < 128) { + for (int i = 0; i < kWhitespaceTable7Length; i++) { + if (kWhitespaceTable7[i] == x) return true; + } + } + return false; +} + +// Returns true if a nonspace found and false if the end has reached. +template <class Iterator> +static inline bool AdvanceToNonspace(Iterator* current, Iterator end) { + while (*current != end) { + if (!isWhitespace(**current)) return true; + ++*current; + } + return false; +} + +static bool isDigit(int x, int radix) { + return (x >= '0' && x <= '9' && x < '0' + radix) + || (radix > 10 && x >= 'a' && x < 'a' + radix - 10) + || (radix > 10 && x >= 'A' && x < 'A' + radix - 10); +} + +static double SignedZero(bool sign) { + return sign ? -0.0 : 0.0; +} + +static bool IsDecimalDigitForRadix(int c, int radix) { + return '0' <= c && c <= '9' && (c - '0') < radix; +} + +static bool IsCharacterDigitForRadix(int c, int radix, char a_character) { + return radix > 10 && c >= a_character && c < a_character + radix - 10; +} + +template <int radix_log_2, class Iterator> +static double RadixStringToIeee(Iterator* current, Iterator end, + bool sign, bool allow_trailing_junk, double junk_string_value, + bool* result_is_junk) +{ + ASSERT(*current != end); + + const int kSignificandSize = Double::kSignificandSize; + + *result_is_junk = true; + + // Skip leading 0s. + while (**current == '0') { + ++(*current); + if (*current == end) { + *result_is_junk = false; + return SignedZero(sign); + } + } + + int64_t number = 0; + int exponent = 0; + const int radix = (1 << radix_log_2); + + do { + int digit; + if (IsDecimalDigitForRadix(**current, radix)) { + digit = static_cast<char>(**current) - '0'; + } else if (IsCharacterDigitForRadix(**current, radix, 'a')) { + digit = static_cast<char>(**current) - 'a' + 10; + } else if (IsCharacterDigitForRadix(**current, radix, 'A')) { + digit = static_cast<char>(**current) - 'A' + 10; + } else { + if (allow_trailing_junk || !AdvanceToNonspace(current, end)) { + break; + } else { + return junk_string_value; + } + } + + number = number * radix + digit; + int overflow = static_cast<int>(number >> kSignificandSize); + if (overflow != 0) { + // Overflow occurred. Need to determine which direction to round the + // result. + int overflow_bits_count = 1; + while (overflow > 1) { + overflow_bits_count++; + overflow >>= 1; + } + + int dropped_bits_mask = ((1 << overflow_bits_count) - 1); + int dropped_bits = static_cast<int>(number) & dropped_bits_mask; + number >>= overflow_bits_count; + exponent = overflow_bits_count; + + bool zero_tail = true; + for (;;) { + ++(*current); + if (*current == end || !isDigit(**current, radix)) break; + zero_tail = zero_tail && **current == '0'; + exponent += radix_log_2; + } + + if (!allow_trailing_junk && AdvanceToNonspace(current, end)) { + return junk_string_value; + } + + int middle_value = (1 << (overflow_bits_count - 1)); + if (dropped_bits > middle_value) { + number++; // Rounding up. + } else if (dropped_bits == middle_value) { + // Rounding to even to consistency with decimals: half-way case rounds + // up if significant part is odd and down otherwise. + if ((number & 1) != 0 || !zero_tail) { + number++; // Rounding up. + } + } + + // Rounding up may cause overflow. + if ((number & ((int64_t)1 << kSignificandSize)) != 0) { + exponent++; + number >>= 1; + } + break; + } + ++(*current); + } while (*current != end); + + ASSERT(number < ((int64_t)1 << kSignificandSize)); + ASSERT(static_cast<int64_t>(static_cast<double>(number)) == number); + + *result_is_junk = false; + + if (exponent == 0) { + if (sign) { + if (number == 0) return -0.0; + number = -number; + } + return static_cast<double>(number); + } + + ASSERT(number != 0); + return Double(DiyFp(number, exponent)).value(); +} + +double StringToDoubleConverter::StringToIeee( + const char *input, + int length, + int* processed_characters_count) const { + const char *current = input; + const char *end = input + length; + + *processed_characters_count = 0; + + const bool allow_trailing_junk = (flags_ & ALLOW_TRAILING_JUNK) != 0; + const bool allow_leading_spaces = (flags_ & ALLOW_LEADING_SPACES) != 0; + const bool allow_trailing_spaces = (flags_ & ALLOW_TRAILING_SPACES) != 0; + const bool allow_spaces_after_sign = (flags_ & ALLOW_SPACES_AFTER_SIGN) != 0; + + if (current == end) return empty_string_value_; + + if (allow_leading_spaces || allow_trailing_spaces) { + if (!AdvanceToNonspace(¤t, end)) { + *processed_characters_count = static_cast<int>(current - input); + return empty_string_value_; + } + if (!allow_leading_spaces && (input != current)) { + return junk_string_value_; + } + } + + const int kBufferSize = kMaxSignificantDigits + 10; + char buffer[kBufferSize]; // NOLINT: size is known at compile time. + int buffer_pos = 0; + + int exponent = 0; + int significant_digits = 0; + int insignificant_digits = 0; + bool nonzero_digit_dropped = false; + + bool sign = false; + + if (*current == '+' || *current == '-') { + sign = (*current == '-'); + ++current; + const char *next_non_space = current; + + if (!AdvanceToNonspace(&next_non_space, end)) return junk_string_value_; + if (!allow_spaces_after_sign && (current != next_non_space)) { + return junk_string_value_; + } + current = next_non_space; + } + + if (infinity_symbol_ != NULL) { + if (*current == infinity_symbol_[0]) { + if (!ConsumeSubString(¤t, end, infinity_symbol_)) { + return junk_string_value_; + } + + if (!(allow_trailing_spaces || allow_trailing_junk) && (current != end)) { + return junk_string_value_; + } + if (!allow_trailing_junk && AdvanceToNonspace(¤t, end)) { + return junk_string_value_; + } + + ASSERT(buffer_pos == 0); + *processed_characters_count = static_cast<int>(current - input); + return sign ? -Double::Infinity() : Double::Infinity(); + } + } + + if (nan_symbol_ != NULL) { + if (*current == nan_symbol_[0]) { + if (!ConsumeSubString(¤t, end, nan_symbol_)) { + return junk_string_value_; + } + + if (!(allow_trailing_spaces || allow_trailing_junk) && (current != end)) { + return junk_string_value_; + } + if (!allow_trailing_junk && AdvanceToNonspace(¤t, end)) { + return junk_string_value_; + } + + ASSERT(buffer_pos == 0); + *processed_characters_count = static_cast<int>(current - input); + return sign ? -Double::NaN() : Double::NaN(); + } + } + + bool leading_zero = false; + if (*current == '0') { + ++current; + if (current == end) { + *processed_characters_count = static_cast<int>(current - input); + return SignedZero(sign); + } + + leading_zero = true; + + // It could be hexadecimal value. + if ((flags_ & ALLOW_HEX) && (*current == 'x' || *current == 'X')) { + ++current; + if (current == end || !isDigit(*current, 16)) { + return junk_string_value_; // "0x". + } + + bool result_is_junk; + double result = RadixStringToIeee<4>(¤t, + end, + sign, + allow_trailing_junk, + junk_string_value_, + &result_is_junk); + if (!result_is_junk) { + if (allow_trailing_spaces) AdvanceToNonspace(¤t, end); + *processed_characters_count = static_cast<int>(current - input); + } + return result; + } + + // Ignore leading zeros in the integer part. + while (*current == '0') { + ++current; + if (current == end) { + *processed_characters_count = static_cast<int>(current - input); + return SignedZero(sign); + } + } + } + + bool octal = leading_zero && (flags_ & ALLOW_OCTALS) != 0; + + // Copy significant digits of the integer part (if any) to the buffer. + while (*current >= '0' && *current <= '9') { + if (significant_digits < kMaxSignificantDigits) { + ASSERT(buffer_pos < kBufferSize); + buffer[buffer_pos++] = static_cast<char>(*current); + significant_digits++; + // Will later check if it's an octal in the buffer. + } else { + insignificant_digits++; // Move the digit into the exponential part. + nonzero_digit_dropped = nonzero_digit_dropped || *current != '0'; + } + octal = octal && *current < '8'; + ++current; + if (current == end) goto parsing_done; + } + + if (significant_digits == 0) { + octal = false; + } + + if (*current == '.') { + if (octal && !allow_trailing_junk) return junk_string_value_; + if (octal) goto parsing_done; + + ++current; + if (current == end) { + if (significant_digits == 0 && !leading_zero) { + return junk_string_value_; + } else { + goto parsing_done; + } + } + + if (significant_digits == 0) { + // octal = false; + // Integer part consists of 0 or is absent. Significant digits start after + // leading zeros (if any). + while (*current == '0') { + ++current; + if (current == end) { + *processed_characters_count = static_cast<int>(current - input); + return SignedZero(sign); + } + exponent--; // Move this 0 into the exponent. + } + } + + // There is a fractional part. + // We don't emit a '.', but adjust the exponent instead. + while (*current >= '0' && *current <= '9') { + if (significant_digits < kMaxSignificantDigits) { + ASSERT(buffer_pos < kBufferSize); + buffer[buffer_pos++] = static_cast<char>(*current); + significant_digits++; + exponent--; + } else { + // Ignore insignificant digits in the fractional part. + nonzero_digit_dropped = nonzero_digit_dropped || *current != '0'; + } + ++current; + if (current == end) goto parsing_done; + } + } + + if (!leading_zero && exponent == 0 && significant_digits == 0) { + // If leading_zeros is true then the string contains zeros. + // If exponent < 0 then string was [+-]\.0*... + // If significant_digits != 0 the string is not equal to 0. + // Otherwise there are no digits in the string. + return junk_string_value_; + } + + // Parse exponential part. + if (*current == 'e' || *current == 'E') { + if (octal && !allow_trailing_junk) return junk_string_value_; + if (octal) goto parsing_done; + ++current; + if (current == end) { + if (allow_trailing_junk) { + goto parsing_done; + } else { + return junk_string_value_; + } + } + char sign = '+'; + if (*current == '+' || *current == '-') { + sign = static_cast<char>(*current); + ++current; + if (current == end) { + if (allow_trailing_junk) { + goto parsing_done; + } else { + return junk_string_value_; + } + } + } + + if (current == end || *current < '0' || *current > '9') { + if (allow_trailing_junk) { + goto parsing_done; + } else { + return junk_string_value_; + } + } + + const int max_exponent = INT_MAX / 2; + ASSERT(-max_exponent / 2 <= exponent && exponent <= max_exponent / 2); + int num = 0; + do { + // Check overflow. + int digit = *current - '0'; + if (num >= max_exponent / 10 + && !(num == max_exponent / 10 && digit <= max_exponent % 10)) { + num = max_exponent; + } else { + num = num * 10 + digit; + } + ++current; + } while (current != end && *current >= '0' && *current <= '9'); + + exponent += (sign == '-' ? -num : num); + } + + if (!(allow_trailing_spaces || allow_trailing_junk) && (current != end)) { + return junk_string_value_; + } + if (!allow_trailing_junk && AdvanceToNonspace(¤t, end)) { + return junk_string_value_; + } + if (allow_trailing_spaces) { + AdvanceToNonspace(¤t, end); + } + + parsing_done: + exponent += insignificant_digits; + + if (octal) { + double result; + bool result_is_junk; + char* start = buffer; + result = RadixStringToIeee<3>(&start, + buffer + buffer_pos, + sign, + allow_trailing_junk, + junk_string_value_, + &result_is_junk); + ASSERT(!result_is_junk); + *processed_characters_count = static_cast<int>(current - input); + return result; + } + + if (nonzero_digit_dropped) { + buffer[buffer_pos++] = '1'; + exponent--; + } + + ASSERT(buffer_pos < kBufferSize); + buffer[buffer_pos] = '\0'; + + double converted = Strtod(Vector<const char>(buffer, buffer_pos), exponent); + *processed_characters_count = static_cast<int>(current - input); + return sign? -converted: converted; +} + + +double StringToDoubleConverter::StringToDouble( + const char* buffer, + int length, + int* processed_characters_count) const { + return StringToIeee(buffer, length, processed_characters_count); +} + +} // end anonymous namespace + +std::string format_coord_shortest(Coord x) +{ + char buf[20]; + bool sign; + int length, point; + + DoubleToStringConverter::DoubleToAscii(x, DoubleToStringConverter::SHORTEST, + 0, buf, 20, &sign, &length, &point); + + int exponent = point - length; + + std::string ret; + ret.reserve(32); + + if (sign) { + ret += '-'; + } + + if (exponent == 0) { + // return digits without any changes + ret += buf; + } else if (point >= 0 && point <= length) { + // insert decimal point + ret.append(buf, point); + ret += '.'; + ret.append(&buf[point], length - point); + } else if (exponent > 0 && exponent <= 2) { + // add trailing zeroes + ret += buf; + ret.append(exponent, '0'); + } else if (point >= -3 && point <= -1) { + // add leading zeroes + ret += '.'; + ret.append(-point, '0'); + ret += buf; + } else { + // exponential form + ret += buf; + ret += 'e'; + if (exponent < 0) { + ret += '-'; + exponent = -exponent; + } + + /* Convert exponent by hand. + * Using ostringstream is ~3x slower */ + int const buflen = 6; + int i = 0; + char expdigits[buflen+1]; + expdigits[buflen] = 0; + + for (; exponent && i < buflen; ++i) { + expdigits[buflen - 1 - i] = '0' + (exponent % 10); + exponent /= 10; + } + ret.append(&expdigits[buflen - i]); + } + + return ret; +} + +std::string format_coord_nice(Coord x) +{ + static DoubleToStringConverter conv( + DoubleToStringConverter::UNIQUE_ZERO, + "Inf", "NaN", 'e', -6, 21, 0, 0); + std::string ret; + ret.reserve(32); + conv.ToShortest(x, ret); + return ret; +} + +Coord parse_coord(std::string const &s) +{ + static StringToDoubleConverter conv( + StringToDoubleConverter::ALLOW_LEADING_SPACES | + StringToDoubleConverter::ALLOW_TRAILING_SPACES | + StringToDoubleConverter::ALLOW_SPACES_AFTER_SIGN, + 0.0, nan(""), "Inf", "NaN"); + int dummy; + return conv.StringToDouble(s.c_str(), s.length(), &dummy); +} + +} // namespace Geom + +/* + 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/2geom/coord.h b/src/2geom/coord.h index 78a852f32..416e6443d 100644 --- a/src/2geom/coord.h +++ b/src/2geom/coord.h @@ -1,6 +1,5 @@ -/** - * \file - * \brief Defines the Coord "real" type with sufficient precision for coordinates. +/** @file + * @brief Integral and real coordinate types and some basic utilities *//* * Copyright 2006 Nathan Hurst <njh@mail.csse.monash.edu.au> * @@ -34,14 +33,45 @@ #include <cmath> #include <limits> +#include <string> +#include <functional> #include <boost/operators.hpp> #include <2geom/forward.h> namespace Geom { -/** @brief 2D axis enumeration (X or Y). */ +/// 2D axis enumeration (X or Y). enum Dim2 { X=0, Y=1 }; +/// Get the other (perpendicular) dimension. +inline Dim2 other_dimension(Dim2 d) { return d == Y ? X : Y; } + +// TODO: make a smarter implementation with C++11 +template <typename T> +struct D2Traits { + typedef typename T::D1Value D1Value; + typedef typename T::D1Reference D1Reference; + typedef typename T::D1ConstReference D1ConstReference; +}; + +// for use with things such as transform_iterator +template <typename T> +struct GetX { + typedef typename D2Traits<T>::D1Value result_type; + typedef T argument_type; + typename D2Traits<T>::D1Value operator()(T const &a) const { + return a[X]; + } +}; +template <typename T> +struct GetY { + typedef typename D2Traits<T>::D1Value result_type; + typedef T argument_type; + typename D2Traits<T>::D1Value operator()(T const &a) const { + return a[Y]; + } +}; + /** * @brief Floating point type used to store coordinates. * @@ -60,8 +90,32 @@ inline Coord infinity() { return std::numeric_limits<Coord>::infinity(); } inline bool are_near(Coord a, Coord b, double eps=EPSILON) { return a-b <= eps && a-b >= -eps; } inline bool rel_error_bound(Coord a, Coord b, double eps=EPSILON) { return a <= eps*b && a >= -eps*b; } +/// Numerically stable linear interpolation. +inline Coord lerp(Coord t, Coord a, Coord b) { + return (1 - t) * a + t * b; +} + template <typename C> -struct CoordTraits {}; +struct CoordTraits { + typedef D2<C> PointType; + typedef GenericInterval<C> IntervalType; + typedef GenericOptInterval<C> OptIntervalType; + typedef GenericRect<C> RectType; + typedef GenericOptRect<C> OptRectType; + + typedef + boost::equality_comparable< IntervalType + , boost::orable< IntervalType + > > + IntervalOps; + + typedef + boost::equality_comparable< RectType + , boost::orable< RectType + , boost::orable< RectType, OptRectType + > > > + RectOps; +}; // NOTE: operator helpers for Rect and Interval are defined here. // This is to avoid increasing their size through multiple inheritance. @@ -120,6 +174,21 @@ struct CoordTraits<Coord> { RectOps; }; +/** @brief Convert coordinate to shortest possible string + * @return The shortest string that parses back to the original value. */ +std::string format_coord_shortest(Coord x); + +/** @brief Convert coordinate to human-readable string + * Unlike format_coord_shortest, this function will not omit a leading zero + * before a decimal point or use small negative exponents. The output format + * is similar to Javascript functions. */ +std::string format_coord_nice(Coord x); + +/** @brief Parse coordinate string + * When using this function in conjunction with format_coord_shortest() + * or format_coord_nice(), the value is guaranteed to be preserved exactly. */ +Coord parse_coord(std::string const &s); + } // end namespace Geom #endif // LIB2GEOM_SEEN_COORD_H diff --git a/src/2geom/crossing.cpp b/src/2geom/crossing.cpp index 513327271..e27a2fc43 100644 --- a/src/2geom/crossing.cpp +++ b/src/2geom/crossing.cpp @@ -28,7 +28,7 @@ double wrap_dist(double from, double to, double size, bool rev) { } } /* -CrossingGraph create_crossing_graph(std::vector<Path> const &p, Crossings const &crs) { +CrossingGraph create_crossing_graph(PathVector const &p, Crossings const &crs) { std::vector<Point> locs; CrossingGraph ret; for(unsigned i = 0; i < crs.size(); i++) { diff --git a/src/2geom/crossing.h b/src/2geom/crossing.h index d5012ae2b..be41fe893 100644 --- a/src/2geom/crossing.h +++ b/src/2geom/crossing.h @@ -33,14 +33,14 @@ * */ -#ifndef __GEOM_CROSSING_H -#define __GEOM_CROSSING_H +#ifndef LIB2GEOM_SEEN_CROSSING_H +#define LIB2GEOM_SEEN_CROSSING_H #include <vector> #include <2geom/rect.h> #include <2geom/sweep.h> #include <boost/optional/optional.hpp> -#include <2geom/path.h> +#include <2geom/pathvector.h> namespace Geom { @@ -99,7 +99,7 @@ struct TimeOrder { }; class Path; -CrossingGraph create_crossing_graph(std::vector<Path> const &p, Crossings const &crs); +CrossingGraph create_crossing_graph(PathVector const &p, Crossings const &crs); */ /*inline bool are_near(Crossing a, Crossing b) { @@ -143,11 +143,24 @@ std::vector<Rect> bounds(Path const &a); inline void sort_crossings(Crossings &cr, unsigned ix) { std::sort(cr.begin(), cr.end(), CrossingOrder(ix)); } +template <typename T> +struct CrossingTraits { + typedef std::vector<T> VectorT; + static inline VectorT init(T const &x) { return VectorT(1, x); } +}; +template <> +struct CrossingTraits<Path> { + typedef PathVector VectorT; + static inline VectorT vector_one(Path const &x) { return VectorT(x); } +}; + template<typename T> struct Crosser { + typedef typename CrossingTraits<T>::VectorT VectorT; virtual ~Crosser() {} - virtual Crossings crossings(T const &a, T const &b) { return crossings(std::vector<T>(1,a), std::vector<T>(1,b))[0]; } - virtual CrossingSet crossings(std::vector<T> const &a, std::vector<T> const &b) { + virtual Crossings crossings(T const &a, T const &b) { + return crossings(CrossingTraits<T>::vector_one(a), CrossingTraits<T>::vector_one(b))[0]; } + virtual CrossingSet crossings(VectorT const &a, VectorT const &b) { CrossingSet results(a.size() + b.size(), Crossings()); std::vector<std::vector<unsigned> > cull = sweep_bounds(bounds(a), bounds(b)); @@ -185,7 +198,7 @@ CrossingSet reverse_tb(CrossingSet const &cr, unsigned split, std::vector<double void clean(Crossings &cr_a, Crossings &cr_b); void delete_duplicates(Crossings &crs); -} +} // end namespace Geom #endif /* diff --git a/src/2geom/curve.cpp b/src/2geom/curve.cpp index c0f2bf883..b45228514 100644 --- a/src/2geom/curve.cpp +++ b/src/2geom/curve.cpp @@ -32,62 +32,26 @@ */ #include <2geom/curve.h> -#include <2geom/nearest-point.h> +#include <2geom/exception.h> +#include <2geom/nearest-time.h> #include <2geom/sbasis-geometric.h> +#include <2geom/sbasis-to-bezier.h> #include <2geom/ord.h> +#include <2geom/path-sink.h> + +//#include <iostream> namespace Geom { -int CurveHelpers::root_winding(Curve const &c, Point p) { - std::vector<double> ts = c.roots(p[Y], Y); - - if(ts.empty()) return 0; - - double const fudge = 0.01; //fudge factor used on first and last - - std::sort(ts.begin(), ts.end()); - - // winding determined by crossings at roots - int wind=0; - // previous time - double pt = ts.front() - fudge; - for ( std::vector<double>::iterator ti = ts.begin() - ; ti != ts.end() - ; ++ti ) - { - double t = *ti; - if ( t <= 0. || t >= 1. ) continue; //skip endpoint roots - if ( c.valueAt(t, X) > p[X] ) { // root is ray intersection - // Get t of next: - std::vector<double>::iterator next = ti; - ++next; - double nt; - if(next == ts.end()) nt = t + fudge; else nt = *next; - - // Check before in time and after in time for positions - // Currently we're using the average times between next and previous segs - Cmp after_to_ray = cmp(c.valueAt((t + nt) / 2, Y), p[Y]); - Cmp before_to_ray = cmp(c.valueAt((t + pt) / 2, Y), p[Y]); - // if y is included, these will have opposite values, giving order. - Cmp dt = cmp(after_to_ray, before_to_ray); - if(dt != EQUAL_TO) //Should always be true, but yah never know.. - wind += dt; - pt = t; - } - } - - return wind; -} - -Coord Curve::nearestPoint(Point const& p, Coord a, Coord b) const +Coord Curve::nearestTime(Point const& p, Coord a, Coord b) const { - return nearest_point(p, toSBasis(), a, b); + return nearest_time(p, toSBasis(), a, b); } -std::vector<Coord> Curve::allNearestPoints(Point const& p, Coord from, Coord to) const +std::vector<Coord> Curve::allNearestTimes(Point const& p, Coord from, Coord to) const { - return all_nearest_points(p, toSBasis(), from, to); + return all_nearest_times(p, toSBasis(), from, to); } Coord Curve::length(Coord tolerance) const @@ -95,6 +59,97 @@ Coord Curve::length(Coord tolerance) const return ::Geom::length(toSBasis(), tolerance); } +int Curve::winding(Point const &p) const +{ + try { + std::vector<Coord> ts = roots(p[Y], Y); + if(ts.empty()) return 0; + std::sort(ts.begin(), ts.end()); + + // skip endpoint roots when they are local maxima on the Y axis + // this follows the convention used in other winding routines, + // i.e. that the bottommost coordinate is not part of the shape + bool ingore_0 = unitTangentAt(0)[Y] <= 0; + bool ignore_1 = unitTangentAt(1)[Y] >= 0; + + int wind = 0; + for (std::size_t i = 0; i < ts.size(); ++i) { + Coord t = ts[i]; + //std::cout << t << std::endl; + if ((t == 0 && ingore_0) || (t == 1 && ignore_1)) continue; + if (valueAt(t, X) > p[X]) { // root is ray intersection + Point tangent = unitTangentAt(t); + if (tangent[Y] > 0) { + // at the point of intersection, curve goes in +Y direction, + // so it winds in the direction of positive angles + ++wind; + } else if (tangent[Y] < 0) { + --wind; + } + } + } + return wind; + } catch (InfiniteSolutions const &e) { + // this means we encountered a line segment exactly coincident with the point + // skip, since this will be taken care of by endpoint roots in other segments + return 0; + } +} + +std::vector<CurveIntersection> Curve::intersect(Curve const &/*other*/, Coord /*eps*/) const +{ + // TODO: approximate as Bezier + THROW_NOTIMPLEMENTED(); +} + +std::vector<CurveIntersection> Curve::intersectSelf(Coord eps) const +{ + std::vector<CurveIntersection> result; + // Monotonic segments cannot have self-intersections. + // Thus, we can split the curve at roots and intersect the portions. + std::vector<Coord> splits; + std::auto_ptr<Curve> deriv(derivative()); + splits = deriv->roots(0, X); + if (splits.empty()) { + return result; + } + deriv.reset(); + splits.push_back(1.); + + boost::ptr_vector<Curve> parts; + Coord previous = 0; + for (unsigned i = 0; i < splits.size(); ++i) { + if (splits[i] == 0.) continue; + parts.push_back(portion(previous, splits[i])); + previous = splits[i]; + } + + Coord prev_i = 0; + for (unsigned i = 0; i < parts.size()-1; ++i) { + Interval dom_i(prev_i, splits[i]); + prev_i = splits[i]; + + Coord prev_j = 0; + for (unsigned j = i+1; j < parts.size(); ++j) { + Interval dom_j(prev_j, splits[j]); + prev_j = splits[j]; + + std::vector<CurveIntersection> xs = parts[i].intersect(parts[j], eps); + for (unsigned k = 0; k < xs.size(); ++k) { + // to avoid duplicated intersections, skip values at exactly 1 + if (xs[k].first == 1. || xs[k].second == 1.) continue; + + Coord ti = dom_i.valueAt(xs[k].first); + Coord tj = dom_j.valueAt(xs[k].second); + + CurveIntersection real(ti, tj, xs[k].point()); + result.push_back(real); + } + } + } + return result; +} + Point Curve::unitTangentAt(Coord t, unsigned n) const { std::vector<Point> derivs = pointAndDerivatives(t, n); @@ -108,6 +163,16 @@ Point Curve::unitTangentAt(Coord t, unsigned n) const return Point (0,0); }; +void Curve::feed(PathSink &sink, bool moveto_initial) const +{ + std::vector<Point> pts; + sbasis_to_bezier(pts, toSBasis(), 2); //TODO: use something better! + if (moveto_initial) { + sink.moveTo(initialPoint()); + } + sink.curveTo(pts[0], pts[1], pts[2]); +} + } // namespace Geom /* diff --git a/src/2geom/curve.h b/src/2geom/curve.h index 172fd7ddc..893dc6bdb 100644 --- a/src/2geom/curve.h +++ b/src/2geom/curve.h @@ -35,27 +35,23 @@ */ -#ifndef _2GEOM_CURVE_H_ -#define _2GEOM_CURVE_H_ +#ifndef LIB2GEOM_SEEN_CURVE_H +#define LIB2GEOM_SEEN_CURVE_H #include <vector> +#include <boost/operators.hpp> #include <2geom/coord.h> #include <2geom/point.h> #include <2geom/interval.h> #include <2geom/sbasis.h> #include <2geom/d2.h> #include <2geom/affine.h> +#include <2geom/intersection.h> -namespace Geom -{ - -class Curve; - -struct CurveHelpers { -protected: - static int root_winding(Curve const &c, Point p); -}; +namespace Geom { +class PathSink; +typedef Intersection<> CurveIntersection; /** * @brief Abstract continuous curve on a plane defined on [0,1]. @@ -77,7 +73,9 @@ protected: * * @ingroup Curves */ -class Curve : private CurveHelpers { +class Curve + : boost::equality_comparable<Curve> +{ public: virtual ~Curve() {} @@ -86,26 +84,38 @@ public: /** @brief Retrieve the start of the curve. * @return The point corresponding to \f$\mathbf{C}(0)\f$. */ virtual Point initialPoint() const = 0; + /** Retrieve the end of the curve. * @return The point corresponding to \f$\mathbf{C}(1)\f$. */ virtual Point finalPoint() const = 0; + /** @brief Check whether the curve has exactly zero length. * @return True if the curve's initial point is exactly the same as its final point, and it contains - * no other points (its value set contains only one element). - */ + * no other points (its value set contains only one element). */ virtual bool isDegenerate() const = 0; + + /** @brief Get the interval of allowed time values. + * @return \f$[0, 1]\f$ */ + virtual Interval timeRange() const { + Interval tr(0, 1); + return tr; + } + /** @brief Evaluate the curve at a specified time value. * @param t Time value * @return \f$\mathbf{C}(t)\f$ */ virtual Point pointAt(Coord t) const { return pointAndDerivatives(t, 0).front(); } + /** @brief Evaluate one of the coordinates at the specified time value. * @param t Time value * @param d The dimension to evaluate * @return The specified coordinate of \f$\mathbf{C}(t)\f$ */ virtual Coord valueAt(Coord t, Dim2 d) const { return pointAt(t)[d]; } + /** @brief Evaluate the function at the specified time value. Allows curves to be used * as functors. */ virtual Point operator() (Coord t) const { return pointAt(t); } + /** @brief Evaluate the curve and its derivatives. * This will return a vector that contains the value of the curve and the specified number * of derivatives. However, the returned vector might contain less elements than specified @@ -125,6 +135,7 @@ public: * type. * @param p New starting point of the curve */ virtual void setInitial(Point const &v) = 0; + /** @brief Change the ending point of the curve. * After calling this method, it is guaranteed that \f$\mathbf{C}(0) = \mathbf{p}\f$, * and the curve is still continuous. The precise new shape of the curve varies @@ -140,12 +151,15 @@ public: * but it might not be the smallest such rectangle. This method is usually fast. * @return A rectangle that contains all points belonging to the curve. */ virtual Rect boundsFast() const = 0; + /** @brief Compute the curve's exact bounding box. * This method can be dramatically slower than boundsExact() depending on the curve type. * @return The smallest possible rectangle containing all of the curve's points. */ virtual Rect boundsExact() const = 0; + // I have no idea what the 'deg' parameter is for, so this is undocumented for now. virtual OptRect boundsLocal(OptInterval const &i, unsigned deg) const = 0; + /** @brief Compute the bounding box of a part of the curve. * Since this method returns the smallest possible bounding rectangle of the specified portion, * it can also be rather slow. @@ -163,21 +177,21 @@ public: * @return Pointer to a newly allocated curve, identical to the original */ virtual Curve *duplicate() const = 0; + /** @brief Transform this curve by an affine transformation. + * Because of this method, all curve types must be closed under affine + * transformations. + * @param m Affine describing the affine transformation */ + virtual void transform(Affine const &m) = 0; + /** @brief Create a curve transformed by an affine transformation. - * This method returns a new curve instead modifying the existing one, because some curve - * types are not closed under affine transformations. The returned curve may be of different - * underlying type (as is the case for horizontal and vertical line segments). + * This method returns a new curve instead modifying the existing one. * @param m Affine describing the affine transformation * @return Pointer to a new, transformed curve */ - virtual Curve *transformed(Affine const &m) const = 0; - - /** @brief Translate the curve (i.e. displace by Point) - * This method modifies the curve; all curve types are closed under - * translations (the result can be expressed in its own curve type). - * This function yields the same result as transformed(m). - * @param p Point by which to translate the curve - * @return reference to self */ - virtual Curve &operator*=(Translate const &m) = 0; + virtual Curve *transformed(Affine const &m) const { + Curve *ret = duplicate(); + ret->transform(m); + return ret; + } /** @brief Create a curve that corresponds to a part of this curve. * For \f$a > b\f$, the returned portion will be reversed with respect to the original. @@ -190,13 +204,16 @@ public: * - \f$\mathbf{D}[ [0, 1] ] = \mathbf{C}[ [a?b] ]\f$, * where \f$[a?b] = [\min(a, b), \max(a, b)]\f$ */ virtual Curve *portion(Coord a, Coord b) const = 0; + /** @brief A version of that accepts an Interval. */ Curve *portion(Interval const &i) const { return portion(i.min(), i.max()); } + /** @brief Create a reversed version of this curve. * The result corresponds to <code>portion(1, 0)</code>, but this method might be faster. * @return Pointer to a new curve \f$\mathbf{D}\f$ such that * \f$\forall_{x \in [0, 1]} \mathbf{D}(x) = \mathbf{C}(1-x)\f$ */ virtual Curve *reverse() const { return portion(1, 0); } + /** @brief Create a derivative of this curve. * It's best to think of the derivative in physical terms: if the curve describes * the position of some object on the plane from time \f$t=0\f$ to \f$t=1\f$ as said in the @@ -215,22 +232,26 @@ public: * @param b Maximum time value to consider; \f$a < b\f$ * @return \f$q \in [a, b]: ||\mathbf{C}(q) - \mathbf{p}|| = \inf(\{r \in \mathbb{R} : ||\mathbf{C}(r) - \mathbf{p}||\})\f$ */ - virtual Coord nearestPoint( Point const& p, Coord a = 0, Coord b = 1 ) const; + virtual Coord nearestTime( Point const& p, Coord a = 0, Coord b = 1 ) const; + /** @brief A version that takes an Interval. */ - Coord nearestPoint(Point const &p, Interval const &i) const { - return nearestPoint(p, i.min(), i.max()); + Coord nearestTime(Point const &p, Interval const &i) const { + return nearestTime(p, i.min(), i.max()); } + /** @brief Compute time values at which the curve comes closest to a specified point. * @param p Query point * @param a Minimum time value to consider * @param b Maximum time value to consider; \f$a < b\f$ * @return Vector of points closest and equally far away from the query point */ - virtual std::vector<Coord> allNearestPoints( Point const& p, Coord from = 0, + virtual std::vector<Coord> allNearestTimes( Point const& p, Coord from = 0, Coord to = 1 ) const; + /** @brief A version that takes an Interval. */ - std::vector<Coord> allNearestPoints(Point const &p, Interval const &i) { - return allNearestPoints(p, i.min(), i.max()); + std::vector<Coord> allNearestTimes(Point const &p, Interval const &i) { + return allNearestTimes(p, i.min(), i.max()); } + /** @brief Compute the arc length of this curve. * For a curve \f$\mathbf{C}(t) = (C_x(t), C_y(t))\f$, arc length is defined for 2D curves as * \f[ \ell = \int_{0}^{1} \sqrt { [C_x'(t)]^2 + [C_y'(t)]^2 }\, \text{d}t \f] @@ -242,13 +263,28 @@ public: * @param tolerance Maximum allowed error * @return Total distance the curve's value travels on the plane when going from 0 to 1 */ virtual Coord length(Coord tolerance=0.01) const; + /** @brief Computes time values at which the curve intersects an axis-aligned line. * @param v The coordinate of the line * @param d Which axis the coordinate is on. X means a vertical line, Y a horizontal line. */ virtual std::vector<Coord> roots(Coord v, Dim2 d) const = 0; - /** @brief Compute the winding number of the curve at the specified point. - * @todo Does this makes sense for curves at all? */ - virtual int winding(Point const &p) const { return root_winding(*this, p); } + + /** @brief Compute the partial winding number of this curve. + * The partial winding number is equal to the difference between the number + * of roots at which the curve goes in the +Y direction and the number of roots + * at which the curve goes in the -Y direction. This method is mainly useful + * for implementing path winding calculation. It will ignore roots which + * are local maxima on the Y axis. + * @param p Point where the winding number should be determined + * @return Winding number contribution at p */ + virtual int winding(Point const &p) const; + + /// Compute intersections with another curve. + virtual std::vector<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const; + + /// Compute intersections of this curve with itself. + virtual std::vector<CurveIntersection> intersectSelf(Coord eps = EPSILON) const; + /** @brief Compute a vector tangent to the curve. * This will return an unit vector (a Point with length() equal to 1) that denotes a vector * tangent to the curve. This vector is defined as @@ -266,6 +302,7 @@ public: Point tangent = - c_reverse->unitTangentAt(0); delete c_reverse; @endcode */ virtual Point unitTangentAt(Coord t, unsigned n = 3) const; + /** @brief Convert the curve to a symmetric power basis polynomial. * Symmetric power basis polynomials (S-basis for short) are numerical representations * of curves with excellent numerical properties. Most high level operations provided by 2Geom @@ -281,15 +318,22 @@ public: * of this curve. For example, for Bezier curves it returns the curve's order * multiplied by 2. */ virtual int degreesOfFreedom() const { return 0;} + /** @brief Test equality of two curves. + * Equality means that for any time value, the evaluation of either curve will yield + * the same value. This means non-degenerate curves are not equal to their reverses. + * Note that this tests for exact equality. * @return True if the curves are identical, false otherwise */ - virtual bool operator==(Curve const &c) const { return this == &c;} + virtual bool operator==(Curve const &c) const = 0; + + /** @brief Feed the curve to a PathSink */ + virtual void feed(PathSink &sink, bool moveto_initial) const; /// @} }; inline -Coord nearest_point(Point const& p, Curve const& c) { - return c.nearestPoint(p); +Coord nearest_time(Point const& p, Curve const& c) { + return c.nearestTime(p); } // for make benefit glorious library of Boost Pointer Container diff --git a/src/2geom/curves.h b/src/2geom/curves.h index 319b1924d..6022c71d8 100644 --- a/src/2geom/curves.h +++ b/src/2geom/curves.h @@ -1,10 +1,9 @@ -/** - * \file - * \brief Include all curve types +/** @file + * @brief Include all curve types *//* * Authors: - * MenTaLguY <mental@rydia.net> - * Marco Cecchetti <mrcekets at gmail.com> + * MenTaLguY <mental@rydia.net> + * Marco Cecchetti <mrcekets at gmail.com> * * Copyright 2007-2008 authors * @@ -38,7 +37,6 @@ #include <2geom/curve.h> #include <2geom/sbasis-curve.h> #include <2geom/bezier-curve.h> -#include <2geom/hvlinesegment.h> #include <2geom/elliptical-arc.h> #include <2geom/svg-elliptical-arc.h> diff --git a/src/2geom/d2-sbasis.cpp b/src/2geom/d2-sbasis.cpp index 486ada9a2..ebec16fdd 100644 --- a/src/2geom/d2-sbasis.cpp +++ b/src/2geom/d2-sbasis.cpp @@ -200,7 +200,7 @@ Point unitTangentAt(D2<SBasis> const & a, Coord t, unsigned n) static void set_first_point(Piecewise<D2<SBasis> > &f, Point a){ if ( f.empty() ){ - f.concat(Piecewise<D2<SBasis> >(D2<SBasis>(Linear(a[X]),Linear(a[Y])))); + f.concat(Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(Linear(a[X])), SBasis(Linear(a[Y]))))); return; } for (unsigned dim=0; dim<2; dim++){ @@ -213,7 +213,7 @@ static void set_first_point(Piecewise<D2<SBasis> > &f, Point a){ } static void set_last_point(Piecewise<D2<SBasis> > &f, Point a){ if ( f.empty() ){ - f.concat(Piecewise<D2<SBasis> >(D2<SBasis>(Linear(a[X]),Linear(a[Y])))); + f.concat(Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(Linear(a[X])), SBasis(Linear(a[Y]))))); return; } for (unsigned dim=0; dim<2; dim++){ diff --git a/src/2geom/d2-sbasis.h b/src/2geom/d2-sbasis.h index 965ec8746..a0769b314 100644 --- a/src/2geom/d2-sbasis.h +++ b/src/2geom/d2-sbasis.h @@ -35,11 +35,11 @@ * */ -#ifdef SEEN_LIB2GEOM_D2_H /*This is intentional: we don't actually want anyone to +#ifdef LIB2GEOM_SEEN_D2_H /*This is intentional: we don't actually want anyone to include this, other than D2.h. If somone else tries, D2 won't be defined. If it is, this will already be included. */ -#ifndef SEEN_LIB2GEOM_D2_SBASIS_H -#define SEEN_LIB2GEOM_D2_SBASIS_H +#ifndef LIB2GEOM_SEEN_D2_SBASIS_H +#define LIB2GEOM_SEEN_D2_SBASIS_H #include <2geom/sbasis.h> #include <2geom/sbasis-2d.h> @@ -149,8 +149,8 @@ std::vector<std::vector<Interval> > level_sets( D2<SBasis> const &f, std::vector } -#endif -#endif +#endif // LIB2GEOM_SEEN_D2_SBASIS_H +#endif // LIB2GEOM_SEEN_D2_H /* diff --git a/src/2geom/d2.h b/src/2geom/d2.h index 5fc955854..714319f99 100644 --- a/src/2geom/d2.h +++ b/src/2geom/d2.h @@ -29,18 +29,19 @@ * */ -#ifndef SEEN_LIB2GEOM_D2_H -#define SEEN_LIB2GEOM_D2_H +#ifndef LIB2GEOM_SEEN_D2_H +#define LIB2GEOM_SEEN_D2_H +#include <iterator> +#include <boost/concept_check.hpp> +#include <boost/iterator/transform_iterator.hpp> #include <2geom/point.h> #include <2geom/interval.h> #include <2geom/affine.h> #include <2geom/rect.h> - -#include <boost/concept_check.hpp> #include <2geom/concepts.h> -namespace Geom{ +namespace Geom { /** * The D2 class takes two instances of a scalar data type and treats them * like a point. All operations which make sense on a point are defined for D2. @@ -55,6 +56,11 @@ class D2{ T f[2]; public: + + typedef T D1Value; + typedef T &D1Reference; + typedef T const &D1ConstReference; + D2() {f[X] = f[Y] = T();} explicit D2(Point const &a) { f[X] = T(a[X]); f[Y] = T(a[Y]); @@ -65,10 +71,38 @@ class D2{ f[Y] = b; } + template <typename Iter> + D2(Iter first, Iter last) { + typedef typename std::iterator_traits<Iter>::value_type V; + typedef typename boost::transform_iterator<GetX<V>, Iter> XIter; + typedef typename boost::transform_iterator<GetY<V>, Iter> YIter; + + XIter xfirst(first, GetX<V>()), xlast(last, GetX<V>()); + f[X] = T(xfirst, xlast); + YIter yfirst(first, GetY<V>()), ylast(last, GetY<V>()); + f[Y] = T(yfirst, ylast); + } + + D2(std::vector<Point> const &vec) { + typedef Point V; + typedef std::vector<Point>::const_iterator Iter; + typedef boost::transform_iterator<GetX<V>, Iter> XIter; + typedef boost::transform_iterator<GetY<V>, Iter> YIter; + + XIter xfirst(vec.begin(), GetX<V>()), xlast(vec.end(), GetX<V>()); + f[X] = T(xfirst, xlast); + YIter yfirst(vec.begin(), GetY<V>()), ylast(vec.end(), GetY<V>()); + f[Y] = T(yfirst, ylast); + } + //TODO: ask mental about operator= as seen in Point T& operator[](unsigned i) { return f[i]; } T const & operator[](unsigned i) const { return f[i]; } + Point point(unsigned i) const { + Point ret(f[X][i], f[Y][i]); + return ret; + } //IMPL: FragmentConcept typedef Point output_type; @@ -429,7 +463,7 @@ inline std::ostream &operator<< (std::ostream &out_file, const Geom::D2<T> &in_d #include <2geom/d2-sbasis.h> -namespace Geom{ +namespace Geom { //Some D2 Fragment implementation which requires rect: template <typename T> @@ -447,8 +481,10 @@ OptRect bounds_local(const D2<T> &a, const OptInterval &t) { boost::function_requires<FragmentConcept<T> >(); return OptRect(bounds_local(a[X], t), bounds_local(a[Y], t)); } -}; +} // end namespace Geom + +#endif /* Local Variables: mode:c++ @@ -459,4 +495,3 @@ OptRect bounds_local(const D2<T> &a, const OptInterval &t) { End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : -#endif diff --git a/src/2geom/ellipse.cpp b/src/2geom/ellipse.cpp index 2686844b2..4e26707ef 100644 --- a/src/2geom/ellipse.cpp +++ b/src/2geom/ellipse.cpp @@ -1,10 +1,11 @@ -/* - * Ellipse Curve - * +/** @file + * @brief Ellipse shape + *//* * Authors: - * Marco Cecchetti <mrcekets at gmail.com> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> * - * Copyright 2008 authors + * Copyright 2008-2014 Authors * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -30,33 +31,28 @@ * the specific language governing rights and limitations. */ - #include <2geom/ellipse.h> #include <2geom/svg-elliptical-arc.h> #include <2geom/numeric/fitting-tool.h> #include <2geom/numeric/fitting-model.h> -using std::swap; +namespace Geom { -namespace Geom -{ - -void Ellipse::set(double A, double B, double C, double D, double E, double F) +void Ellipse::setCoefficients(double A, double B, double C, double D, double E, double F) { double den = 4*A*C - B*B; - if ( den == 0 ) - { - THROW_LOGICALERROR("den == 0, while computing ellipse centre"); + if (den == 0) { + THROW_RANGEERROR("den == 0, while computing ellipse centre"); } - m_centre[X] = (B*E - 2*C*D) / den; - m_centre[Y] = (B*D - 2*A*E) / den; + _center[X] = (B*E - 2*C*D) / den; + _center[Y] = (B*D - 2*A*E) / den; // evaluate the a coefficient of the ellipse equation in normal form // E(x,y) = a*(x-cx)^2 + b*(x-cx)*(y-cy) + c*(y-cy)^2 = 1 // where b = a*B , c = a*C, (cx,cy) == centre - double num = A * sqr(m_centre[X]) - + B * m_centre[X] * m_centre[Y] - + C * sqr(m_centre[Y]) + double num = A * sqr(_center[X]) + + B * _center[X] * _center[Y] + + C * sqr(_center[Y]) - F; @@ -64,73 +60,74 @@ void Ellipse::set(double A, double B, double C, double D, double E, double F) double rot = std::atan2( -B, -(A - C) )/2; // std::cerr << "rot = " << rot << std::endl; bool swap_axes = false; - if ( are_near(rot, 0) ) rot = 0; - if ( are_near(rot, M_PI/2) || rot < 0 ) - { + + if (rot >= M_PI/2 || rot < 0) { swap_axes = true; } // evaluate the length of the ellipse rays - double cosrot = std::cos(rot); - double sinrot = std::sin(rot); + double sinrot, cosrot; + sincos(rot, sinrot, cosrot); double cos2 = cosrot * cosrot; double sin2 = sinrot * sinrot; double cossin = cosrot * sinrot; den = A * cos2 + B * cossin + C * sin2; - if ( den == 0 ) - { - THROW_LOGICALERROR("den == 0, while computing 'rx' coefficient"); + if (den == 0) { + THROW_RANGEERROR("den == 0, while computing 'rx' coefficient"); } - double rx2 = num/den; - if ( rx2 < 0 ) - { - THROW_LOGICALERROR("rx2 < 0, while computing 'rx' coefficient"); + double rx2 = num / den; + if (rx2 < 0) { + THROW_RANGEERROR("rx2 < 0, while computing 'rx' coefficient"); } double rx = std::sqrt(rx2); den = C * cos2 - B * cossin + A * sin2; - if ( den == 0 ) - { - THROW_LOGICALERROR("den == 0, while computing 'ry' coefficient"); + if (den == 0) { + THROW_RANGEERROR("den == 0, while computing 'ry' coefficient"); } - double ry2 = num/den; - if ( ry2 < 0 ) - { - THROW_LOGICALERROR("ry2 < 0, while computing 'rx' coefficient"); + double ry2 = num / den; + if (ry2 < 0) { + THROW_RANGEERROR("ry2 < 0, while computing 'rx' coefficient"); } double ry = std::sqrt(ry2); // the solution is not unique so we choose always the ellipse // with a rotation angle between 0 and PI/2 - if ( swap_axes ) swap(rx, ry); - if ( are_near(rot, M_PI/2) - || are_near(rot, -M_PI/2) - || are_near(rx, ry) ) - { + if (swap_axes) { + std::swap(rx, ry); + } + + if (rx == ry) { rot = 0; } - else if ( rot < 0 ) - { + if (rot < 0) { rot += M_PI/2; } - m_ray[X] = rx; - m_ray[Y] = ry; - m_angle = rot; + _rays[X] = rx; + _rays[Y] = ry; + _angle = rot; +} + + +Affine Ellipse::unitCircleTransform() const +{ + Affine ret = Scale(ray(X), ray(Y)) * Rotate(_angle); + ret.setTranslation(center()); + return ret; } -std::vector<double> Ellipse::implicit_form_coefficients() const +std::vector<double> Ellipse::coefficients() const { - if (ray(X) == 0 || ray(Y) == 0) - { - THROW_LOGICALERROR("a degenerate ellipse doesn't own an implicit form"); + if (ray(X) == 0 || ray(Y) == 0) { + THROW_RANGEERROR("a degenerate ellipse doesn't have an implicit form"); } std::vector<double> coeff(6); - double cosrot = std::cos(rot_angle()); - double sinrot = std::sin(rot_angle()); + double cosrot, sinrot; + sincos(_angle, sinrot, cosrot); double cos2 = cosrot * cosrot; double sin2 = sinrot * sinrot; double cossin = cosrot * sinrot; @@ -150,18 +147,16 @@ std::vector<double> Ellipse::implicit_form_coefficients() const } -void Ellipse::set(std::vector<Point> const& points) +void Ellipse::fit(std::vector<Point> const &points) { size_t sz = points.size(); - if (sz < 5) - { + if (sz < 5) { THROW_RANGEERROR("fitting error: too few points passed"); } NL::LFMEllipse model; NL::least_squeares_fitter<NL::LFMEllipse> fitter(model, sz); - for (size_t i = 0; i < sz; ++i) - { + for (size_t i = 0; i < sz; ++i) { fitter.append(points[i]); } fitter.update(); @@ -172,12 +167,12 @@ void Ellipse::set(std::vector<Point> const& points) EllipticalArc * -Ellipse::arc(Point const& initial, Point const& inner, Point const& final, +Ellipse::arc(Point const &ip, Point const &inner, Point const &fp, bool _svg_compliant) { - Point sp_cp = initial - center(); - Point ep_cp = final - center(); - Point ip_cp = inner - center(); + Point sp_cp = ip - center(); + Point ep_cp = fp - center(); + Point ip_cp = inner - center(); double angle1 = angle_between(sp_cp, ep_cp); double angle2 = angle_between(sp_cp, ip_cp); @@ -186,28 +181,19 @@ Ellipse::arc(Point const& initial, Point const& inner, Point const& final, bool large_arc_flag = true; bool sweep_flag = true; - if ( angle1 > 0 ) - { - if ( angle2 > 0 && angle3 > 0 ) - { + if (angle1 > 0) { + if (angle2 > 0 && angle3 > 0) { large_arc_flag = false; sweep_flag = true; - } - else - { + } else { large_arc_flag = true; sweep_flag = false; } - } - else - { - if ( angle2 < 0 && angle3 < 0 ) - { + } else { + if (angle2 < 0 && angle3 < 0) { large_arc_flag = false; sweep_flag = false; - } - else - { + } else { large_arc_flag = true; sweep_flag = true; } @@ -215,66 +201,73 @@ Ellipse::arc(Point const& initial, Point const& inner, Point const& final, EllipticalArc *ret_arc; if (_svg_compliant) { - ret_arc = new SVGEllipticalArc(initial, ray(X), ray(Y), rot_angle(), - large_arc_flag, sweep_flag, final); + ret_arc = new SVGEllipticalArc(ip, ray(X), ray(Y), rotationAngle(), + large_arc_flag, sweep_flag, fp); } else { - ret_arc = new EllipticalArc(initial, ray(X), ray(Y), rot_angle(), - large_arc_flag, sweep_flag, final); + ret_arc = new EllipticalArc(ip, ray(X), ray(Y), rotationAngle(), + large_arc_flag, sweep_flag, fp); } return ret_arc; } -Ellipse Ellipse::transformed(Affine const& m) const +Ellipse &Ellipse::operator*=(Rotate const &r) { - double cosrot = std::cos(rot_angle()); - double sinrot = std::sin(rot_angle()); - Affine A( ray(X) * cosrot, ray(X) * sinrot, - -ray(Y) * sinrot, ray(Y) * cosrot, - 0, 0 ); - Point new_center = center() * m; - Affine M = m.withoutTranslation(); - Affine AM = A * M; - if ( are_near(std::sqrt(fabs(AM.det())), 0) ) - { + _angle += r.angle(); + // keep the angle in the first quadrant + if (_angle < 0) { + _angle += M_PI; + } + if (_angle >= M_PI/2) { + std::swap(_rays[X], _rays[Y]); + _angle -= M_PI/2; + } + _center *= r; + return *this; +} + +Ellipse &Ellipse::operator*=(Affine const& m) +{ + Rotate a(_angle); + Affine mwot = m.withoutTranslation(); + Affine am = a * mwot; + if (are_near(am.descrim(), 0)) { double angle; - if (AM[0] != 0) - { - angle = std::atan2(AM[2], AM[0]); - } - else if (AM[1] != 0) - { - angle = std::atan2(AM[3], AM[1]); - } - else - { + if (am[0] != 0) { + angle = std::atan2(am[2], am[0]); + } else if (am[1] != 0) { + angle = std::atan2(am[3], am[1]); + } else { angle = M_PI/2; } - Point V(std::cos(angle), std::sin(angle)); - V *= AM; - double rx = L2(V); - angle = atan2(V); - return Ellipse(new_center[X], new_center[Y], rx, 0, angle); + Point v; + sincos(angle, v[X], v[Y]); + v *= am; + _angle = atan2(v); + _center *= m; + _rays[X] = L2(v); + _rays[Y] = 0; + return *this; } - std::vector<double> coeff = implicit_form_coefficients(); - Affine Q( coeff[0], coeff[1]/2, + std::vector<double> coeff = coefficients(); + Affine q( coeff[0], coeff[1]/2, coeff[1]/2, coeff[2], 0, 0 ); - Affine invm = M.inverse(); - Q = invm * Q ; - swap( invm[1], invm[2] ); - Q *= invm; - Ellipse e(Q[0], 2*Q[1], Q[3], 0, 0, -1); - e.m_centre = new_center; + Affine invm = mwot.inverse(); + q = invm * q ; + std::swap(invm[1], invm[2]); + q *= invm; + setCoefficients(q[0], 2*q[1], q[3], 0, 0, -1); + _center *= m; - return e; + return *this; } Ellipse::Ellipse(Geom::Circle const &c) { - m_centre = c.center(); - m_ray[X] = m_ray[Y] = c.ray(); + _center = c.center(); + _rays[X] = _rays[Y] = c.radius(); } } // end namespace Geom diff --git a/src/2geom/ellipse.h b/src/2geom/ellipse.h index c971c6065..a67969933 100644 --- a/src/2geom/ellipse.h +++ b/src/2geom/ellipse.h @@ -1,9 +1,9 @@ -/** - * \file - * \brief Ellipse Curve - * +/** @file + * @brief Ellipse shape + *//* * Authors: - * Marco Cecchetti <mrcekets at gmail.com> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> * * Copyright 2008 authors * @@ -32,104 +32,109 @@ */ -#ifndef _2GEOM_ELLIPSE_H_ -#define _2GEOM_ELLIPSE_H_ +#ifndef LIB2GEOM_SEEN_ELLIPSE_H +#define LIB2GEOM_SEEN_ELLIPSE_H #include <vector> -#include <2geom/point.h> +#include <2geom/angle.h> #include <2geom/exception.h> -#include <2geom/affine.h> +#include <2geom/point.h> +#include <2geom/transforms.h> -namespace Geom -{ +namespace Geom { class EllipticalArc; class Circle; +/** @brief Set of points with a constant sum of distances from two foci + * @ingroup Shapes */ class Ellipse + : boost::multipliable< Ellipse, Translate + , boost::multipliable< Ellipse, Scale + , boost::multipliable< Ellipse, Rotate + , boost::multipliable< Ellipse, Zoom + , boost::multipliable< Ellipse, Affine + > > > > > { - public: - Ellipse(): - m_centre(), - m_ray(), - m_angle(0) + Point _center; + Point _rays; + Angle _angle; +public: + Ellipse() {} + Ellipse(Point const &c, Point const &r, Coord angle) + : _center(c) + , _rays(r) + , _angle(angle) {} - - Ellipse(double cx, double cy, double rx, double ry, double a) - : m_centre(cx, cy), m_ray(rx, ry), m_angle(a) - { - } - - // build an ellipse by its implicit equation: - // Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0 - Ellipse(double A, double B, double C, double D, double E, double F) - { - set(A, B, C, D, E, F); - } - - Ellipse(std::vector<Point> const& points) - { - set(points); + Ellipse(Coord cx, Coord cy, Coord rx, Coord ry, Coord angle) + : _center(cx, cy) + , _rays(rx, ry) + , _angle(angle) + {} + Ellipse(double A, double B, double C, double D, double E, double F) { + setCoefficients(A, B, C, D, E, F); } - Ellipse(Geom::Circle const &c); - void set(double cx, double cy, double rx, double ry, double a) - { - m_centre[X] = cx; - m_centre[Y] = cy; - m_ray[X] = rx; - m_ray[Y] = ry; - m_angle = a; + void set(Point const &c, Point const &r, Coord angle) { + _center = c; + _rays = r; + _angle = angle; + } + void set(Coord cx, Coord cy, Coord rx, Coord ry, Coord a) { + _center[X] = cx; + _center[Y] = cy; + _rays[X] = rx; + _rays[Y] = ry; + _angle = a; } // build an ellipse by its implicit equation: // Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0 - void set(double A, double B, double C, double D, double E, double F); + void setCoefficients(double A, double B, double C, double D, double E, double F); // biuld up the best fitting ellipse wrt the passed points // prerequisite: at least 5 points must be passed - void set(std::vector<Point> const& points); + void fit(std::vector<Point> const& points); - EllipticalArc * - arc(Point const& initial, Point const& inner, Point const& final, bool svg_compliant = true); + EllipticalArc *arc(Point const &ip, Point const &inner, Point const &fp, + bool svg_compliant = true); - Point center() const - { - return m_centre; - } + Point center() const { return _center; } + Coord center(Dim2 d) const { return _center[d]; } + Point rays() const { return _rays; } + Coord ray(Dim2 d) const { return _rays[d]; } + Angle rotationAngle() const { return _angle; } - Coord center(Dim2 d) const - { - return m_centre[d]; - } + /** @brief Compute the transform that maps the unit circle to this ellipse. + * Each ellipse can be interpreted as a translated, scaled and rotate unit circle. + * This function returns the transform that maps the unit circle to this ellipse. + * @return Transform from unit circle to the ellipse */ + Affine unitCircleTransform() const; - Coord ray(Dim2 d) const - { - return m_ray[d]; - } + std::vector<double> coefficients() const; - Coord rot_angle() const - { - return m_angle; + Ellipse &operator*=(Translate const &t) { + _center *= t; + return *this; } - - std::vector<double> implicit_form_coefficients() const; - - Ellipse transformed(Affine const& m) const; - - private: - Point m_centre, m_ray; - double m_angle; + Ellipse &operator*=(Scale const &s) { + _center *= s; + _rays *= s; + return *this; + } + Ellipse &operator*=(Zoom const &z) { + _center *= z; + _rays *= z.scale(); + return *this; + } + Ellipse &operator*=(Rotate const &r); + Ellipse &operator*=(Affine const &m); }; - } // end namespace Geom - - -#endif // _2GEOM_ELLIPSE_H_ - +#endif // LIB2GEOM_SEEN_ELLIPSE_H /* Local Variables: diff --git a/src/2geom/elliptical-arc.cpp b/src/2geom/elliptical-arc.cpp index c96d5f1a6..601f0c8c8 100644 --- a/src/2geom/elliptical-arc.cpp +++ b/src/2geom/elliptical-arc.cpp @@ -34,11 +34,12 @@ #include <limits> #include <memory> -#include <2geom/elliptical-arc.h> -#include <2geom/ellipse.h> -#include <2geom/sbasis-geometric.h> #include <2geom/bezier-curve.h> +#include <2geom/ellipse.h> +#include <2geom/elliptical-arc.h> +#include <2geom/path-sink.h> #include <2geom/poly.h> +#include <2geom/sbasis-geometric.h> #include <2geom/transforms.h> #include <2geom/utils.h> @@ -46,7 +47,6 @@ #include <2geom/numeric/fitting-tool.h> #include <2geom/numeric/fitting-model.h> - namespace Geom { @@ -93,6 +93,8 @@ namespace Geom Rect EllipticalArc::boundsExact() const { + using std::swap; + double extremes[4]; double sinrot, cosrot; sincos(_rot_angle, sinrot, cosrot); @@ -109,11 +111,11 @@ Rect EllipticalArc::boundsExact() const arc_extremes[0] = initialPoint()[X]; arc_extremes[1] = finalPoint()[X]; if ( arc_extremes[0] < arc_extremes[1] ) - std::swap(arc_extremes[0], arc_extremes[1]); + swap(arc_extremes[0], arc_extremes[1]); arc_extremes[2] = initialPoint()[Y]; arc_extremes[3] = finalPoint()[Y]; if ( arc_extremes[2] < arc_extremes[3] ) - std::swap(arc_extremes[2], arc_extremes[3]); + swap(arc_extremes[2], arc_extremes[3]); if ( !are_near(initialPoint(), finalPoint()) ) { for (unsigned i = 0; i < 4; ++i) { @@ -266,8 +268,12 @@ std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const } double rotx, roty; - sincos(_rot_angle, roty, rotx); /// \todo sin and cos are calculated in many places in this function, optimize this a bit! - if (d == X) roty = -roty; + if (d == X) { + sincos(_rot_angle, roty, rotx); + roty = -roty; + } else { + sincos(_rot_angle, rotx, roty); + } double rxrotx = ray(X) * rotx; double c_v = center(d) - v; @@ -428,7 +434,7 @@ Curve *EllipticalArc::reverse() const { } #ifdef HAVE_GSL // GSL is required for function "solve_reals" -std::vector<double> EllipticalArc::allNearestPoints( Point const& p, double from, double to ) const +std::vector<double> EllipticalArc::allNearestTimes( Point const& p, double from, double to ) const { std::vector<double> result; @@ -446,7 +452,7 @@ std::vector<double> EllipticalArc::allNearestPoints( Point const& p, double from else if ( are_near(ray(X), 0) || are_near(ray(Y), 0) ) { LineSegment seg(pointAt(from), pointAt(to)); - Point np = seg.pointAt( seg.nearestPoint(p) ); + Point np = seg.pointAt( seg.nearestTime(p) ); if ( are_near(ray(Y), 0) ) { if ( are_near(_rot_angle, M_PI/2) @@ -570,7 +576,7 @@ std::vector<double> EllipticalArc::allNearestPoints( Point const& p, double from double mindistsq1 = std::numeric_limits<double>::max(); double mindistsq2 = std::numeric_limits<double>::max(); - double dsq; + double dsq = 0; unsigned int mi1 = 0, mi2 = 0; for ( unsigned int i = 0; i < real_sol.size(); ++i ) { @@ -887,16 +893,50 @@ D2<SBasis> EllipticalArc::toSBasis() const return arc; } - -Curve *EllipticalArc::transformed(Affine const& m) const +void EllipticalArc::transform(Affine const& m) { + if (isChord()) { + _initial_point *= m; + _final_point *= m; + _center = middle_point(_initial_point, _final_point); + _rays = Point(0,0); + _rot_angle = 0; + return; + } + + // TODO avoid allocating a new arc here Ellipse e(center(X), center(Y), ray(X), ray(Y), _rot_angle); - Ellipse et = e.transformed(m); + e *= m; Point inner_point = pointAt(0.5); - return et.arc( initialPoint() * m, - inner_point * m, - finalPoint() * m, - isSVGCompliant() ); + EllipticalArc *arc = e.arc(initialPoint() * m, + inner_point * m, + finalPoint() * m, + isSVGCompliant()); + *this = *arc; + delete arc; +} + +bool EllipticalArc::operator==(Curve const &c) const +{ + EllipticalArc const *other = dynamic_cast<EllipticalArc const *>(&c); + if (!other) return false; + if (_initial_point != other->_initial_point) return false; + if (_final_point != other->_final_point) return false; + // TODO: all arcs with ellipse rays which are too small + // and fall back to a line should probably be equal + if (_rays != other->_rays) return false; + if (_rot_angle != other->_rot_angle) return false; + if (_large_arc != other->_large_arc) return false; + if (_sweep != other->_sweep) return false; + return true; +} + +void EllipticalArc::feed(PathSink &sink, bool moveto_initial) const +{ + if (moveto_initial) { + sink.moveTo(_initial_point); + } + sink.arcTo(_rays[X], _rays[Y], _rot_angle, _large_arc, _sweep, _final_point); } Coord EllipticalArc::map_to_01(Coord angle) const diff --git a/src/2geom/elliptical-arc.h b/src/2geom/elliptical-arc.h index 5527aa6bc..342b8c8c9 100644 --- a/src/2geom/elliptical-arc.h +++ b/src/2geom/elliptical-arc.h @@ -34,8 +34,8 @@ * the specific language governing rights and limitations. */ -#ifndef _2GEOM_ELLIPTICAL_ARC_H_ -#define _2GEOM_ELLIPTICAL_ARC_H_ +#ifndef LIB2GEOM_SEEN_ELLIPTICAL_ARC_H +#define LIB2GEOM_SEEN_ELLIPTICAL_ARC_H #include <algorithm> #include <2geom/angle.h> @@ -71,9 +71,9 @@ public: * @param sweep If true, the clockwise arc is chosen, otherwise the counter-clockwise * arc is chosen * @param fp Final point of the arc */ - EllipticalArc( Point ip, Coord rx, Coord ry, + EllipticalArc( Point const &ip, Coord rx, Coord ry, Coord rot_angle, bool large_arc, bool sweep, - Point fp + Point const &fp ) : AngleInterval(0,0,sweep) , _initial_point(ip) @@ -137,7 +137,7 @@ public: * recalculations of the center and extreme angles. * @param ip New initial point * @param fp New final point */ - void setExtremes(Point const &ip, Point const &fp) { + void setEndpoints(Point const &ip, Point const &fp) { _initial_point = ip; _final_point = fp; _updateCenterAndAngles(isSVGCompliant()); @@ -182,6 +182,11 @@ public: /** @brief Check whether the arc adheres to SVG 1.1 implementation guidelines */ virtual bool isSVGCompliant() const { return false; } + /// Check whether both rays are nonzero + bool isChord() const { + return _rays[0] == 0 || _rays[Y] == 0; + } + std::pair<EllipticalArc, EllipticalArc> subdivide(Coord t) const { EllipticalArc* arc1 = static_cast<EllipticalArc*>(portion(0, t)); EllipticalArc* arc2 = static_cast<EllipticalArc*>(portion(t, 1)); @@ -193,7 +198,6 @@ public: } // implementation of overloads goes here -#ifndef DOXYGEN_SHOULD_SKIP_THIS virtual Point initialPoint() const { return _initial_point; } virtual Point finalPoint() const { return _final_point; } virtual Curve* duplicate() const { return new EllipticalArc(*this); } @@ -206,7 +210,7 @@ public: _updateCenterAndAngles(isSVGCompliant()); } virtual bool isDegenerate() const { - return ( are_near(ray(X), 0) || are_near(ray(Y), 0) ); + return _initial_point == _final_point; } virtual Rect boundsFast() const { return boundsExact(); @@ -218,17 +222,17 @@ public: } virtual std::vector<double> roots(double v, Dim2 d) const; #ifdef HAVE_GSL - virtual std::vector<double> allNearestPoints( Point const& p, double from = 0, double to = 1 ) const; + virtual std::vector<double> allNearestTimes( Point const& p, double from = 0, double to = 1 ) const; #endif - virtual double nearestPoint( Point const& p, double from = 0, double to = 1 ) const { + virtual double nearestTime( Point const& p, double from = 0, double to = 1 ) const { if ( are_near(ray(X), ray(Y)) && are_near(center(), p) ) { return from; } - return allNearestPoints(p, from, to).front(); + return allNearestTimes(p, from, to).front(); } virtual int degreesOfFreedom() const { return 7; } virtual Curve *derivative() const; - virtual Curve *transformed(Affine const &m) const; + virtual void transform(Affine const &m); virtual Curve &operator*=(Translate const &m) { _initial_point += m.vector(); _final_point += m.vector(); @@ -251,7 +255,8 @@ public: } virtual Curve* portion(double f, double t) const; virtual Curve* reverse() const; -#endif + virtual bool operator==(Curve const &c) const; + virtual void feed(PathSink &sink, bool moveto_initial) const; protected: void _updateCenterAndAngles(bool svg); @@ -267,7 +272,7 @@ private: } // end namespace Geom -#endif // _2GEOM_ELLIPTICAL_ARC_H_ +#endif // LIB2GEOM_SEEN_ELLIPTICAL_ARC_H /* Local Variables: diff --git a/src/2geom/exception.h b/src/2geom/exception.h index 12736d639..ab6f82142 100644 --- a/src/2geom/exception.h +++ b/src/2geom/exception.h @@ -38,8 +38,8 @@ * */ -#ifndef LIB2GEOM_EXCEPTION_HEADER -#define LIB2GEOM_EXCEPTION_HEADER +#ifndef LIB2GEOM_SEEN_EXCEPTION_H +#define LIB2GEOM_SEEN_EXCEPTION_H #include <exception> #include <sstream> diff --git a/src/2geom/forward.h b/src/2geom/forward.h index 70cac1f7d..f3ab657e8 100644 --- a/src/2geom/forward.h +++ b/src/2geom/forward.h @@ -32,10 +32,8 @@ * the specific language governing rights and limitations. */ -#ifndef SEEN_GEOM_FORWARD_H -#define SEEN_GEOM_FORWARD_H - -#include <vector> // include this dependency so PathVector can be defined more explicitly +#ifndef LIB2GEOM_SEEN_FORWARD_H +#define LIB2GEOM_SEEN_FORWARD_H namespace Geom { @@ -79,7 +77,10 @@ class SVGEllipticalArc; // paths and path sequences class Path; -typedef std::vector<Path> PathVector; +class PathVector; +struct PathPosition; +class PathInterval; +struct PathVectorPosition; // errors class Exception; @@ -103,11 +104,6 @@ class Zoom; template <typename> class D2; template <typename> class Piecewise; -class Shape; -class Region; -class Hat; -class Tri; - // misc class SVGPathSink; template <typename> class SVGPathGenerator; diff --git a/src/2geom/generic-interval.h b/src/2geom/generic-interval.h index f6d4718de..716390e57 100644 --- a/src/2geom/generic-interval.h +++ b/src/2geom/generic-interval.h @@ -90,13 +90,24 @@ public: } /// @} - /// @name Inspect endpoints. + /// @name Inspect contained values. /// @{ C min() const { return _b[0]; } C max() const { return _b[1]; } C extent() const { return max() - min(); } C middle() const { return (max() + min()) / 2; } bool isSingular() const { return min() == max(); } + C operator[](unsigned i) const { assert(i < 2); return _b[i]; } + C clamp(C val) const { + if (val < min()) return min(); + if (val > max()) return max(); + return val; + } + /// Return the closer end of the interval. + C nearestEnd(C val) const { + C dmin = std::abs(val - min()), dmax = std::abs(val - max()); + return dmin <= dmax ? min() : max(); + } /// @} /// @name Test coordinates and other intervals for inclusion. @@ -138,6 +149,16 @@ public: _b[1] = val; } } + /// Set both ends of the interval simultaneously + void setEnds(C a, C b) { + if (a <= b) { + _b[0] = a; + _b[1] = b; + } else { + _b[0] = b; + _b[1] = a; + } + } /** @brief Extend the interval to include the given number. */ void expandTo(C val) { if(val < _b[0]) _b[0] = val; @@ -271,6 +292,8 @@ public: /** @brief Check whether this interval is empty. */ bool isEmpty() { return !*this; }; + /// Alias of isEmpty() for STL similarity. + bool empty() { return !*this; } /** @brief Union with another interval, gracefully handling empty ones. */ void unionWith(GenericOptInterval<C> const &a) { @@ -317,14 +340,14 @@ inline GenericOptInterval<C> operator&(GenericInterval<C> const &a, GenericInter return GenericOptInterval<C>(a) & GenericOptInterval<C>(b); } -//#ifdef _GLIBCXX_IOSTREAM +#ifdef _GLIBCXX_IOSTREAM template <typename C> inline std::ostream &operator<< (std::ostream &os, Geom::GenericInterval<C> const &I) { os << "Interval("<<I.min() << ", "<<I.max() << ")"; return os; } -//#endif +#endif } // namespace Geom #endif // !LIB2GEOM_SEEN_GENERIC_INTERVAL_H diff --git a/src/2geom/generic-rect.h b/src/2geom/generic-rect.h index 9ad0e60b0..f7d722757 100644 --- a/src/2geom/generic-rect.h +++ b/src/2geom/generic-rect.h @@ -64,6 +64,10 @@ class GenericRect protected: CInterval f[2]; public: + typedef CInterval D1Value; + typedef CInterval &D1Reference; + typedef CInterval const &D1ConstReference; + /// @name Create rectangles. /// @{ /** @brief Create a rectangle that contains only the point at (0,0). */ @@ -180,12 +184,34 @@ public: /** @brief Compute rectangle's area. */ C area() const { return f[X].extent() * f[Y].extent(); } /** @brief Check whether the rectangle has zero area. */ - bool hasZeroArea() const { return (area() == 0); } + bool hasZeroArea() const { return f[X].isSingular() || f[Y].isSingular(); } /** @brief Get the larger extent (width or height) of the rectangle. */ C maxExtent() const { return std::max(f[X].extent(), f[Y].extent()); } /** @brief Get the smaller extent (width or height) of the rectangle. */ C minExtent() const { return std::min(f[X].extent(), f[Y].extent()); } + + /** @brief Clamp point to the rectangle. */ + CPoint clamp(CPoint const &p) const { + CPoint result(f[X].clamp(p[X]), f[Y].clamp(p[Y])); + return result; + } + /** @brief Get the nearest point on the edge of the rectangle. */ + CPoint nearestEdgePoint(CPoint const &p) const { + CPoint result = p; + if (!contains(p)) { + result = clamp(p); + } else { + C cx = f[X].nearestEnd(p[X]); + C cy = f[Y].nearestEnd(p[Y]); + if (std::abs(cx - p[X]) <= std::abs(cy - p[Y])) { + result[X] = cx; + } else { + result[Y] = cy; + } + } + return result; + } /// @} /// @name Test other rectangles and points for inclusion. @@ -328,6 +354,10 @@ class GenericOptRect typedef typename CoordTraits<C>::OptRectType OptCRect; typedef boost::optional<CRect> Base; public: + typedef CInterval D1Value; + typedef CInterval &D1Reference; + typedef CInterval const &D1ConstReference; + /// @name Create potentially empty rectangles. /// @{ GenericOptRect() : Base() {} @@ -483,13 +513,13 @@ inline bool GenericRect<C>::contains(OptCRect const &r) const { return !r || contains(*r); } -//#ifdef _GLIBCXX_IOSTREAM +#ifdef _GLIBCXX_IOSTREAM template <typename C> inline std::ostream &operator<<(std::ostream &out, GenericRect<C> const &r) { out << "X: " << r[X] << " Y: " << r[Y]; return out; } -//#endif +#endif } // end namespace Geom diff --git a/src/2geom/geom.cpp b/src/2geom/geom.cpp index d3cf0ca73..bf9b0a721 100644 --- a/src/2geom/geom.cpp +++ b/src/2geom/geom.cpp @@ -10,8 +10,17 @@ #include <algorithm> #include <2geom/rect.h> +using std::swap; + namespace Geom { +enum IntersectorKind { + intersects = 0, + parallel, + coincident, + no_intersection +}; + /** * Finds the intersection of the two (infinite) lines * defined by the points p such that dot(n0, p) == d0 and dot(n1, p) == d1. @@ -305,7 +314,7 @@ rect_line_intersect(Geom::Point const &c0, Geom::Point const &c1, Point dir1 (results[1] - results[0]); Point dir2 (p1 - p0); if (dot(dir1, dir2) < 0) { - std::swap(results[0], results[1]); + swap(results[0], results[1]); } } @@ -363,7 +372,7 @@ int centroid(std::vector<Geom::Point> const &p, Geom::Point& centroid, double &a Geom::Point centroid_tmp(0,0); double atmp = 0; for (unsigned i = n-1, j = 0; j < n; i = j, j++) { - const double ai = -cross(p[j], p[i]); + const double ai = cross(p[j], p[i]); atmp += ai; centroid_tmp += (p[j] + p[i])*ai; // first moment. } diff --git a/src/2geom/geom.h b/src/2geom/geom.h index 5aeded23d..6ba812254 100644 --- a/src/2geom/geom.h +++ b/src/2geom/geom.h @@ -44,52 +44,10 @@ namespace Geom { -enum IntersectorKind { - intersects = 0, - parallel, - coincident, - no_intersection -}; - -int -intersector_ccw(const Geom::Point& p0, const Geom::Point& p1, - const Geom::Point& p2); - -/* intersectors */ - -#if 0 -// Use the new routines provided in line.h - -IntersectorKind -line_intersection(Geom::Point const &n0, double const d0, - Geom::Point const &n1, double const d1, - Geom::Point &result); - -IntersectorKind -segment_intersect(Geom::Point const &p00, Geom::Point const &p01, - Geom::Point const &p10, Geom::Point const &p11, - Geom::Point &result); - -IntersectorKind -line_twopoint_intersect(Geom::Point const &p00, Geom::Point const &p01, - Geom::Point const &p10, Geom::Point const &p11, - Geom::Point &result); -#endif - -#if 0 -std::vector<Geom::Point> -rect_line_intersect(Geom::Point const &E, Geom::Point const &F, - Geom::Point const &p0, Geom::Point const &p1); -#endif - boost::optional<Geom::LineSegment> rect_line_intersect(Geom::Rect &r, Geom::LineSegment ls); -boost::optional<Geom::LineSegment> -rect_line_intersect(Geom::Rect &r, - Geom::Line l); - int centroid(std::vector<Geom::Point> const &p, Geom::Point& centroid, double &area); } diff --git a/src/2geom/hvlinesegment.h b/src/2geom/hvlinesegment.h deleted file mode 100644 index d2b9d6310..000000000 --- a/src/2geom/hvlinesegment.h +++ /dev/null @@ -1,283 +0,0 @@ -/** - * \file - * \brief Horizontal and vertical line segment - *//* - * Authors: - * Marco Cecchetti <mrcekets at gmail.com> - * Krzysztof Kosiński <tweenk.pl@gmail.com> - * Copyright 2008-2011 Authors - * - * This library is free software; you can redistribute it and/or - * modify it either under the terms of the GNU Lesser General Public - * License version 2.1 as published by the Free Software Foundation - * (the "LGPL") or, at your option, under the terms of the Mozilla - * Public License Version 1.1 (the "MPL"). If you do not alter this - * notice, a recipient may use your version of this file under either - * the MPL or the LGPL. - * - * You should have received a copy of the LGPL along with this library - * in the file COPYING-LGPL-2.1; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * You should have received a copy of the MPL along with this library - * in the file COPYING-MPL-1.1 - * - * The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY - * OF ANY KIND, either express or implied. See the LGPL or the MPL for - * the specific language governing rights and limitations. - */ - -#ifndef LIB2GEOM_SEEN_HVLINESEGMENT_H -#define LIB2GEOM_SEEN_HVLINESEGMENT_H - -#include <2geom/bezier-curve.h> - -namespace Geom -{ - -template <Dim2 axis> -class AxisLineSegment : public LineSegment -{ -public: - static const Dim2 other_axis = static_cast<Dim2>((axis + 1) % 2); -#ifndef DOXYGEN_SHOULD_SKIP_THIS - virtual void setInitial(Point const &p) { - Point f = finalPoint(); - f[axis] = p[axis]; - LineSegment::setInitial(p); - LineSegment::setFinal(f); - } - virtual void setFinal(Point const &p) { - Point i = initialPoint(); - i[axis] = p[axis]; - LineSegment::setFinal(p); - LineSegment::setInitial(i); - } - virtual Rect boundsFast() const { return boundsExact(); } - virtual Rect boundsExact() const { Rect r(initialPoint(), finalPoint()); return r; } - virtual int degreesOfFreedom() const { return 3; } - virtual std::vector<Coord> roots(Coord v, Dim2 d) const { - std::vector<Coord> result; - if (d == axis) { - if ( v >= initialPoint()[axis] && v <= finalPoint()[axis] ) { - Coord t = 0; - if (!isDegenerate()) - t = (v - initialPoint()[axis]) / (finalPoint()[axis] - initialPoint()[axis]); - result.push_back(t); - } - } else { - if (v == initialPoint()[other_axis]) { - if (!isDegenerate()) - THROW_INFINITESOLUTIONS(0); - result.push_back(0); - } - } - return result; - } - virtual Coord nearestPoint( Point const &p, Coord from = 0, Coord to = 1 ) const { - if ( from > to ) std::swap(from, to); - Coord xfrom = valueAt(from, axis); - Coord xto = valueAt(to, axis); - if ( xfrom > xto ) { - std::swap(xfrom, xto); - std::swap(from, to); - } - if ( p[axis] > xfrom && p[axis] < xto ) { - return (p[axis] - initialPoint()[axis]) / (finalPoint()[axis] - initialPoint()[axis]); - } - else if ( p[X] <= xfrom ) - return from; - else - return to; - } - virtual Point pointAt(Coord t) const { - if ( t < 0 || t > 1 ) - THROW_RANGEERROR("AxisLineSegment: Time value out of range"); - Point ret = initialPoint() + t * (finalPoint() - initialPoint()); - return ret; - } - virtual Coord valueAt(Coord t, Dim2 d) const { - if ( t < 0 || t > 1 ) - THROW_RANGEERROR("AxisLineSegment: Time value out of range"); - if (d != axis) return initialPoint()[other_axis]; - return initialPoint()[axis] + t * (finalPoint()[axis] - initialPoint()[axis]); - } - virtual std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const { - std::vector<Point> result; - result.push_back(pointAt(t)); - if (n > 0) { - Point der = finalPoint() - initialPoint(); - result.push_back( der ); - } - if (n > 1) { - /* higher order derivatives are zero, - * so the other n-1 vector elements are (0,0) */ - result.insert( result.end(), n-1, Point(0, 0) ); - } - return result; - } -#endif -protected: - AxisLineSegment(Point const &p0, Point const &p1) : LineSegment(p0, p1) {} - AxisLineSegment() {} -}; - -class HLineSegment : public AxisLineSegment<X> -{ -public: - HLineSegment() {} - HLineSegment(Coord x0, Coord x1, Coord y) - : AxisLineSegment<X>(Point(x0, y), Point(x1, y)) - {} - - HLineSegment(Point const& p, Coord len) - : AxisLineSegment<X>(p, Point(p[X] + len, p[Y])) - {} - - HLineSegment(Point const& p0, Point const& p1) - : AxisLineSegment<X>(p0, p1) - { - if ( p0[Y] != p1[Y] ) { - THROW_RANGEERROR("HLineSegment::HLineSegment passed points should " - "have the same Y value"); - } - } - - Coord getY() { return initialPoint()[Y]; } - void setInitialX(Coord x) { - LineSegment::setInitial(Point(x, initialPoint()[Y])); - } - void setFinalX(Coord x) { - LineSegment::setFinal(Point(x, initialPoint()[Y])); - } - void setY(Coord y) { - LineSegment::setInitial( Point(initialPoint()[X], y) ); - LineSegment::setFinal( Point(finalPoint()[X], y) ); - } - std::pair<HLineSegment, HLineSegment> subdivide(Coord t) const { - std::pair<HLineSegment, HLineSegment> result; - Point p = pointAt(t); - result.first.setInitial(initialPoint()); - result.first.setFinal(p); - result.second.setInitial(p); - result.second.setFinal(finalPoint()); - return result; - } - -#ifndef DOXYGEN_SHOULD_SKIP_THIS - virtual Curve* duplicate() const { return new HLineSegment(*this); } - virtual Curve *portion(Coord f, Coord t) const { - Point ip = pointAt(f); - Point ep = pointAt(t); - return new HLineSegment(ip[X], ep[X], ip[Y]); - } - virtual Curve *reverse() const { - Point ip = initialPoint(); - return new HLineSegment(finalPoint()[X], ip[X], ip[Y]); - } - virtual Curve *transformed(Affine const & m) const { - Point ip = initialPoint() * m; - Point ep = finalPoint() * m; - // cannot afford to lose precision here since it can lead to discontinuous paths - if (m.isZoom(0)) { - return new HLineSegment(ip[X], ep[X], ip[Y]); - } else { - return new LineSegment(ip, ep); - } - } - virtual Curve *derivative() const { - Coord x = finalPoint()[X] - initialPoint()[X]; - return new HLineSegment(x, x, 0); - } -#endif -}; // end class HLineSegment - - -class VLineSegment : public AxisLineSegment<Y> -{ -public: - VLineSegment() {} - - VLineSegment(Coord x, Coord y0, Coord y1) - : AxisLineSegment<Y>(Point(x, y0), Point(x, y1)) - {} - - VLineSegment(Point const& _p, Coord _length) - : AxisLineSegment<Y>(_p, Point(_p[X], _p[Y] + _length)) - {} - - VLineSegment(Point const& _p0, Point const& _p1) - : AxisLineSegment<Y>(_p0, _p1) - { - if ( _p0[X] != _p1[X] ) { - THROW_RANGEERROR("VLineSegment::VLineSegment passed points should " - "have the same X value"); - } - } - - Coord getX() { return initialPoint()[X]; } - void setInitialY(Coord _y) { - LineSegment::setInitial( Point(initialPoint()[X], _y) ); - } - void setFinalY(Coord _y) { - LineSegment::setFinal( Point(finalPoint()[Y], _y) ); - } - void setX(Coord _x) { - LineSegment::setInitial( Point(_x, initialPoint()[Y]) ); - LineSegment::setFinal( Point(_x, finalPoint()[Y]) ); - } - std::pair<VLineSegment, VLineSegment> subdivide(Coord t) const { - std::pair<VLineSegment, VLineSegment> result; - Point p = pointAt(t); - result.first.setInitial(initialPoint()); - result.first.setFinal(p); - result.second.setInitial(p); - result.second.setFinal(finalPoint()); - return result; - } - -#ifndef DOXYGEN_SHOULD_SKIP_THIS - virtual Curve *duplicate() const { return new VLineSegment(*this); } - virtual Curve *portion(Coord f, Coord t) const { - Point ip = pointAt(f); - Coord epy = valueAt(t, Y); - return new VLineSegment(ip[X], ip[Y], epy); - } - virtual Curve *reverse() const { - Point ip = initialPoint(); - return new VLineSegment(ip[X], finalPoint()[Y], ip[Y]); - } - virtual Curve *transformed(Affine const & m) const { - Point ip = initialPoint() * m; - Point ep = finalPoint() * m; - if (m.isZoom()) { - return new VLineSegment(ip[X], ip[Y], ep[Y]); - } else { - return new LineSegment(ip, ep); - } - } - virtual Curve* derivative() const { - Coord y = finalPoint()[Y] - initialPoint()[Y]; - return new VLineSegment(0, y, y); - } -#endif -}; // end class VLineSegment - -} // end namespace Geom - -#endif // LIB2GEOM_SEEN_HVLINESEGMENT_H - -/* - 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/2geom/int-point.h b/src/2geom/int-point.h index 1a16ecb7a..7c737b1d7 100644 --- a/src/2geom/int-point.h +++ b/src/2geom/int-point.h @@ -120,30 +120,53 @@ public: } /// @} - /** @brief Lexicographical ordering functor. */ - template <Dim2 d> struct LexOrder; + /** @brief Lexicographical ordering functor. + * @param d The more significant dimension */ + template <Dim2 d> struct LexLess; + /** @brief Lexicographical ordering functor. + * @param d The more significant dimension */ + template <Dim2 d> struct LexGreater; /** @brief Lexicographical ordering functor with runtime dimension. */ - class LexOrderRt { - public: - LexOrderRt(Dim2 d) : dim(d) {} - inline bool operator()(IntPoint const &a, IntPoint const &b); + struct LexLessRt { + LexLessRt(Dim2 d) : dim(d) {} + inline bool operator()(IntPoint const &a, IntPoint const &b) const; + private: + Dim2 dim; + }; + /** @brief Lexicographical ordering functor with runtime dimension. */ + struct LexGreaterRt { + LexGreaterRt(Dim2 d) : dim(d) {} + inline bool operator()(IntPoint const &a, IntPoint const &b) const; private: Dim2 dim; }; }; -template<> struct IntPoint::LexOrder<X> { - bool operator()(IntPoint const &a, IntPoint const &b) { +template<> struct IntPoint::LexLess<X> { + bool operator()(IntPoint const &a, IntPoint const &b) const { return a[X] < b[X] || (a[X] == b[X] && a[Y] < b[Y]); } }; -template<> struct IntPoint::LexOrder<Y> { - bool operator()(IntPoint const &a, IntPoint const &b) { +template<> struct IntPoint::LexLess<Y> { + bool operator()(IntPoint const &a, IntPoint const &b) const { return a[Y] < b[Y] || (a[Y] == b[Y] && a[X] < b[X]); } }; -inline bool IntPoint::LexOrderRt::operator()(IntPoint const &a, IntPoint const &b) { - return dim ? IntPoint::LexOrder<Y>()(a, b) : IntPoint::LexOrder<X>()(a, b); +template<> struct IntPoint::LexGreater<X> { + bool operator()(IntPoint const &a, IntPoint const &b) const { + return a[X] > b[X] || (a[X] == b[X] && a[Y] > b[Y]); + } +}; +template<> struct IntPoint::LexGreater<Y> { + bool operator()(IntPoint const &a, IntPoint const &b) const { + return a[Y] > b[Y] || (a[Y] == b[Y] && a[X] > b[X]); + } +}; +inline bool IntPoint::LexLessRt::operator()(IntPoint const &a, IntPoint const &b) const { + return dim ? IntPoint::LexLess<Y>()(a, b) : IntPoint::LexLess<X>()(a, b); +} +inline bool IntPoint::LexGreaterRt::operator()(IntPoint const &a, IntPoint const &b) const { + return dim ? IntPoint::LexGreater<Y>()(a, b) : IntPoint::LexGreater<X>()(a, b); } } // namespace Geom diff --git a/src/2geom/intersection-graph.cpp b/src/2geom/intersection-graph.cpp new file mode 100644 index 000000000..abe462e3f --- /dev/null +++ b/src/2geom/intersection-graph.cpp @@ -0,0 +1,296 @@ +/** + * \file + * \brief Intersection graph for Boolean operations + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <2geom/intersection-graph.h> +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <iostream> + +namespace Geom { + +struct IntersectionVertexLess { + bool operator()(IntersectionVertex const &a, IntersectionVertex const &b) const { + return a.pos < b.pos; + } +}; + +/** @class PathIntersectionGraph + * @brief Intermediate data for computing Boolean operations on paths. + * + * This class implements the Greiner-Hormann clipping algorithm, + * with improvements by Foster and Overfelt. + * + * @ingroup Paths + */ + +PathIntersectionGraph::PathIntersectionGraph(PathVector const &a, PathVector const &b, Coord precision) + : _a(a) + , _b(b) +{ + if (a.empty() || b.empty()) return; + + // all paths must be closed, otherwise we will miss some intersections + for (std::size_t i = 0; i < a.size(); ++i) { + _a[i].close(); + } + for (std::size_t i = 0; i < b.size(); ++i) { + _b[i].close(); + } + + std::vector<PVIntersection> pxs = _a.intersect(_b, precision); + if (pxs.empty()) return; + if (pxs.size() % 2) return; + + // prepare intersection lists for each path component + for (std::size_t i = 0; i < _a.size(); ++i) { + _xalists.push_back(new IntersectionList()); + } + for (std::size_t i = 0; i < _b.size(); ++i) { + _xblists.push_back(new IntersectionList()); + } + + for (std::size_t i = 0; i < pxs.size(); ++i) { + IntersectionVertex *xa, *xb; + xa = new IntersectionVertex(); + xb = new IntersectionVertex(); + xa->processed = xb->processed = false; + xa->pos = pxs[i].first; + xb->pos = pxs[i].second; + xa->p = xb->p = pxs[i].point(); + xa->neighbor = xb; + xb->neighbor = xa; + _xs.push_back(xa); + _xs.push_back(xb); + _xalists[xa->pos.path_index].push_back(*xa); + _xblists[xb->pos.path_index].push_back(*xb); + } + + for (std::size_t i = 0; i < _xalists.size(); ++i) { + _xalists[i].sort(IntersectionVertexLess()); + } + for (std::size_t i = 0; i < _xblists.size(); ++i) { + _xblists[i].sort(IntersectionVertexLess()); + } + + typedef IntersectionList::iterator Iter; + + // determine in/out/on flags using winding + for (unsigned npv = 0; npv < 2; ++npv) { + boost::ptr_vector<IntersectionList> &ls = npv ? _xblists : _xalists; + PathVector const &pv = npv ? b : a; + PathVector const &other = npv ? a : b; + + for (unsigned li = 0; li < ls.size(); ++li) { + for (Iter i = ls[li].begin(); i != ls[li].end(); ++i) { + Iter n = boost::next(i); + if (n == ls[li].end()) { + n = ls[li].begin(); + } + std::size_t pi = i->pos.path_index; + + PathInterval ival = forward_interval(i->pos, n->pos, pv[pi].size()); + PathPosition mid = ival.inside(precision); + + // TODO check for degenerate cases + // requires changes in the winding routine + int w = other.winding(pv[pi].pointAt(mid)); + if (w % 2) { + i->next = POINT_INSIDE; + n->previous = POINT_INSIDE; + } else { + i->next = POINT_OUTSIDE; + n->previous = POINT_OUTSIDE; + } + } + + // assign exit / entry flags + for (Iter i = ls[li].begin(); i != ls[li].end(); ++i) { + i->entry = ((i->next == POINT_INSIDE) && (i->previous == POINT_OUTSIDE)); + } + } + } +} + +PathVector PathIntersectionGraph::getUnion() +{ + PathVector result = _getResult(false, false); + _handleNonintersectingPaths(result, 0, false); + _handleNonintersectingPaths(result, 1, false); + return result; +} + +PathVector PathIntersectionGraph::getIntersection() +{ + PathVector result = _getResult(true, true); + _handleNonintersectingPaths(result, 0, true); + _handleNonintersectingPaths(result, 1, true); + return result; +} + +PathVector PathIntersectionGraph::_getResult(bool enter_a, bool enter_b) +{ + typedef IntersectionList::iterator Iter; + PathVector result; + if (_xs.empty()) return result; + + // reset processed status + for (unsigned npv = 0; npv < 2; ++npv) { + boost::ptr_vector<IntersectionList> &ls = npv ? _xblists : _xalists; + for (std::size_t li = 0; li < ls.size(); ++li) { + for (Iter k = ls[li].begin(); k != ls[li].end(); ++k) { + k->processed = false; + } + } + } + + unsigned n_processed = 0; + + while (true) { + PathVector const *cur = &_a, *other = &_b; + boost::ptr_vector<IntersectionList> *lscur = &_xalists, *lsother = &_xblists; + + // find unprocessed intersection + Iter i; + if (!_findUnprocessed(i)) break; + + result.push_back(Path(i->p)); + result.back().setStitching(true); + + while (!i->processed) { + Iter prev = i; + std::size_t pi = i->pos.path_index; + // determine which direction to go + // union: always go outside + // intersection: always go inside + // a minus b: go inside in b, outside in a + // b minus a: go inside in a, outside in b + bool reverse = false; + if (cur == &_a) { + reverse = i->entry ^ enter_a; + } else { + reverse = i->entry ^ enter_b; + } + + // get next intersection + if (reverse) { + if (i == (*lscur)[pi].begin()) { + i = (*lscur)[pi].end(); + } + --i; + } else { + ++i; + if (i == (*lscur)[pi].end()) { + i = (*lscur)[pi].begin(); + } + } + + // append portion of path + PathInterval ival = PathInterval::from_direction( + prev->pos.asPathPosition(), i->pos.asPathPosition(), + reverse, (*cur)[pi].size()); + + (*cur)[pi].appendPortionTo(result.back(), ival, prev->p, i->p); + + // mark both vertices as processed + prev->processed = true; + i->processed = true; + n_processed += 2; + + // switch to the other path + i = (*lsother)[i->neighbor->pos.path_index].iterator_to(*i->neighbor); + std::swap(lscur, lsother); + std::swap(cur, other); + } + + assert(!result.back().empty()); + } + + assert(n_processed == _xs.size()); + + return result; +} + +void PathIntersectionGraph::_handleNonintersectingPaths(PathVector &result, int which, bool inside) +{ + /* Every component that has any intersections will be processed by _getResult. + * Here we take care of paths that don't have any intersections. They are either + * completely inside or completely outside the other pathvector. We test this by + * evaluating the winding rule at the initial point. If inside is true and + * the path is inside, we add it to the result. + */ + boost::ptr_vector<IntersectionList> const &ls = which ? _xblists : _xalists; + PathVector const &cur = which ? _b : _a; + PathVector const &other = which ? _a : _b; + + for (std::size_t i = 0; i < cur.size(); ++i) { + if (!ls.empty() && !ls[i].empty()) continue; + + int w = other.winding(cur[i].initialPoint()); + bool path_inside = w % 2 != 0; + if (path_inside == inside) { + result.push_back(cur[i]); + } + } +} + +bool PathIntersectionGraph::_findUnprocessed(IntersectionList::iterator &result) +{ + typedef IntersectionList::iterator Iter; + + Iter it; + + for (std::size_t k = 0; k < _xalists.size(); ++k) { + it = _xalists[k].begin(); + for (; it != _xalists[k].end(); ++it) { + if (!it->processed) { + result = it; + return true; + } + } + } + + return false; +} + +} // namespace Geom + +/* + 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/2geom/intersection-graph.h b/src/2geom/intersection-graph.h new file mode 100644 index 000000000..2fe858c5d --- /dev/null +++ b/src/2geom/intersection-graph.h @@ -0,0 +1,106 @@ +/** + * \file + * \brief Path intersection graph + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef SEEN_LIB2GEOM_INTERSECTION_GRAPH_H +#define SEEN_LIB2GEOM_INTERSECTION_GRAPH_H + +#include <set> +#include <vector> +#include <boost/ptr_container/ptr_vector.hpp> +#include <boost/intrusive/list.hpp> +#include <2geom/forward.h> +#include <2geom/pathvector.h> + +namespace Geom { + +enum InOutFlag { + POINT_INSIDE, + POINT_OUTSIDE, + POINT_ON_EDGE +}; + +struct IntersectionVertex { + boost::intrusive::list_member_hook<> _hook; + PathVectorPosition pos; + Point p; // guarantees that endpoints are exact + IntersectionVertex *neighbor; + bool entry; // going in +t direction enters the other path + InOutFlag previous; + InOutFlag next; + bool processed; // TODO: use intrusive unprocessed list instead +}; + +typedef boost::intrusive::list + < IntersectionVertex + , boost::intrusive::member_hook + < IntersectionVertex + , boost::intrusive::list_member_hook<> + , &IntersectionVertex::_hook + > + > IntersectionList; + + +class PathIntersectionGraph +{ + // this is called PathIntersectionGraph so that we can also have a class for polygons, + // e.g. PolygonIntersectionGraph, which is going to be significantly faster +public: + PathIntersectionGraph(PathVector const &a, PathVector const &b, Coord precision = EPSILON); + + PathVector getUnion(); + PathVector getIntersection(); + +private: + PathVector _getResult(bool enter_a, bool enter_b); + void _handleNonintersectingPaths(PathVector &result, int which, bool inside); + bool _findUnprocessed(IntersectionList::iterator &result); + + PathVector _a, _b; + boost::ptr_vector<IntersectionVertex> _xs; + boost::ptr_vector<IntersectionList> _xalists; + boost::ptr_vector<IntersectionList> _xblists; +}; + +} // namespace Geom + +#endif // SEEN_LIB2GEOM_PATH_GRAPH_H +/* + 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/2geom/intersection.h b/src/2geom/intersection.h new file mode 100644 index 000000000..848797682 --- /dev/null +++ b/src/2geom/intersection.h @@ -0,0 +1,117 @@ +/** + * \file + * \brief Intersection utilities + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2015 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef SEEN_LIB2GEOM_INTERSECTION_H +#define SEEN_LIB2GEOM_INTERSECTION_H + +#include <2geom/coord.h> +#include <2geom/point.h> + +namespace Geom { + + +/** @brief Intersection between two shapes. + */ +template <typename TimeA = Coord, typename TimeB = TimeA> +class Intersection +{ +public: + /** @brief Construct from shape references and time values. + * By default, the intersection point will be halfway between the evaluated + * points on the two shapes. */ + template <typename TA, typename TB> + Intersection(TA const &sa, TB const &sb, TimeA const &ta, TimeB const &tb) + : first(ta) + , second(tb) + , _point(lerp(0.5, sa.pointAt(ta), sb.pointAt(tb))) + {} + + /// Additionally report the intersection point. + Intersection(TimeA const &ta, TimeB const &tb, Point const &p) + : first(ta) + , second(tb) + , _point(p) + {} + + /// Intersection point, as calculated by the intersection algorithm. + Point point() const { + return _point; + } + /// Implicit conversion to Point. + operator Point() const { + return _point; + } + + friend inline void swap(Intersection &a, Intersection &b) { + using std::swap; + swap(a.first, b.first); + swap(a.second, b.second); + swap(a._point, b._point); + } + +public: + /// First shape and time value. + TimeA first; + /// Second shape and time value. + TimeB second; +private: + // Recalculation of the intersection point from the time values is in many cases + // less precise than the value obtained directly from the intersection algorithm, + // so we need to store it. + Point _point; +}; + + +// TODO: move into new header +template <typename T> +struct ShapeTraits { + typedef Coord TimeType; + typedef Interval IntervalType; + typedef T AffineClosureType; + typedef Intersection<> IntersectionType; +}; + + +} // namespace Geom + +#endif // SEEN_LIB2GEOM_INTERSECTION_H +/* + 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/2geom/interval.h b/src/2geom/interval.h index 1714435be..09ea46923 100644 --- a/src/2geom/interval.h +++ b/src/2geom/interval.h @@ -91,19 +91,23 @@ public: } /// @} - /// @name Inspect endpoints. + /// @name Inspect contained values. /// @{ - /** @brief Access endpoints by value. - * @deprecated Use min() and max() instead */ - Coord operator[](unsigned i) const { return _b[i]; } - /** @brief Access endpoints by reference. - * @deprecated Use min() and max() instead - * @todo Remove Interval index operator, which can be used to break the invariant */ - Coord& operator[](unsigned i) { return _b[i]; } - + /** @brief Check whether both endpoints are finite. */ bool isFinite() const { return IS_FINITE(min()) && IS_FINITE(max()); } + /** @brief Map the interval [0,1] onto this one. + * This method simply performs 1D linear interpolation between endpoints. */ + Coord valueAt(Coord t) { + return lerp(t, min(), max()); + } + /** @brief Find closest time in [0,1] that maps to the given value. */ + Coord nearestTime(Coord t) { + if (t < min()) return 0; + if (t > max()) return 1; + return (t - min()) / extent(); + } /// @} /// @name Test coordinates and other intervals for inclusion. @@ -114,7 +118,16 @@ public: /** @brief Check whether the interior of the interval includes the given interval. * Interior means all numbers in the interval except its ends. */ bool interiorContains(Interval const &val) const { return min() < val.min() && val.max() < max(); } - /** @brief Check whether the interiors of the intervals have any common elements. A single point in common is not considered an intersection. */ + /// Check whether the number is contained in the union of the interior and the lower boundary. + bool lowerContains(Coord val) { return min() <= val && val < max(); } + /// Check whether the given interval is contained in the union of the interior and the lower boundary. + bool lowerContains(Interval const &val) const { return min() <= val.min() && val.max() < max(); } + /// Check whether the number is contained in the union of the interior and the upper boundary. + bool upperContains(Coord val) { return min() < val && val <= max(); } + /// Check whether the given interval is contained in the union of the interior and the upper boundary. + bool upperContains(Interval const &val) const { return min() < val.min() && val.max() <= max(); } + /** @brief Check whether the interiors of the intervals have any common elements. + * A single point in common is not considered an intersection. */ bool interiorIntersects(Interval const &val) const { return std::max(min(), val.min()) < std::min(max(), val.max()); } @@ -125,16 +138,18 @@ public: // IMPL: ScalableConcept /** @brief Scale an interval */ Interval &operator*=(Coord s) { + using std::swap; _b[0] *= s; _b[1] *= s; - if(s < 0) std::swap(_b[0], _b[1]); + if(s < 0) swap(_b[0], _b[1]); return *this; } /** @brief Scale an interval by the inverse of the specified value */ Interval &operator/=(Coord s) { + using std::swap; _b[0] /= s; _b[1] /= s; - if(s < 0) std::swap(_b[0], _b[1]); + if(s < 0) swap(_b[0], _b[1]); return *this; } /** @brief Multiply two intervals. diff --git a/src/2geom/line.cpp b/src/2geom/line.cpp index 097365245..35cf2d379 100644 --- a/src/2geom/line.cpp +++ b/src/2geom/line.cpp @@ -28,11 +28,9 @@ * the specific language governing rights and limitations. */ - -#include <2geom/line.h> - #include <algorithm> - +#include <2geom/line.h> +#include <2geom/math-utils.h> namespace Geom { @@ -41,12 +39,18 @@ namespace Geom * @class Line * @brief Infinite line on a plane. * - * Every line in 2Geom has a special point on it, called the origin. The direction of the line - * is stored as a unit vector (versor). This way a line can be interpreted as a function - * \f$ f: (-\infty, \infty) \to \mathbb{R}^2\f$. Zero corresponds to the origin point, - * positive values to the points in the direction of the unit vector, and negative values - * to points in the opposite direction. - * + * A line is specified as two points through which it passes. Lines can be interpreted as functions + * \f$ f: (-\infty, \infty) \to \mathbb{R}^2\f$. Zero corresponds to the first (origin) point, + * one corresponds to the second (final) point. All other points are computed as a linear + * interpolation between those two: \f$p = (1-t) a + t b\f$. Many such functions have the same + * image and therefore represent the same lines; for example, adding \f$b-a\f$ to both points + * yields the same line. + * + * 2Geom can represent the same line in many ways by design: using a different representation + * would lead to precision loss. For example, a line from (1e30, 1e30) to (10,0) would actually + * evaluate to (0,0) at time 1 if it was stored as origin and normalized versor, + * or origin and angle. + * * @ingroup Primitives */ @@ -54,35 +58,50 @@ namespace Geom * A line is a set of points that satisfies the line equation * \f$Ax + By + C = 0\f$. This function changes the line so that its points * satisfy the line equation with the given coefficients. */ -void Line::setCoefficients (double a, double b, double c) { +void Line::setCoefficients (Coord a, Coord b, Coord c) +{ if (a == 0 && b == 0) { if (c != 0) { THROW_LOGICALERROR("the passed coefficients gives the empty set"); } - m_versor = Point(0,0); - m_origin = Point(0,0); - } else { - double l = hypot(a,b); - a /= l; - b /= l; - c /= l; - Point N(a, b); - m_versor = N.ccw(); - m_origin = -c * N; - } + _initial = Point(0,0); + _final = Point(0,0); + return; + } + if (a == 0) { + // b must be nonzero + _initial = Point(0, c / b); + _final = _initial; + _final[X] = 1; + return; + } + if (b == 0) { + _initial = Point(c / a, 0); + _final = _initial; + _final[Y] = 1; + return; + } + + _initial = Point(c / a, 0); + _final = Point(0, c / b); +} + +void Line::coefficients(Coord &a, Coord &b, Coord &c) const +{ + Point v = versor().cw(); + a = v[X]; + b = v[Y]; + c = cross(_initial, _final); } /** @brief Get the line equation coefficients of this line. * @return Vector with three values corresponding to the A, B and C * coefficients of the line equation for this line. */ -std::vector<double> Line::coefficients() const { - std::vector<double> coeff; - coeff.reserve(3); - Point N = versor().cw(); - coeff.push_back (N[X]); - coeff.push_back (N[Y]); - double d = - dot (N, origin()); - coeff.push_back (d); +std::vector<Coord> Line::coefficients() const +{ + Coord c[3]; + coefficients(c[0], c[1], c[2]); + std::vector<Coord> coeff(c, c+3); return coeff; } @@ -91,87 +110,153 @@ std::vector<double> Line::coefficients() const { * @param d Which axis the coordinate is on. X means a vertical line, Y means a horizontal line. * @return Time values at which this line intersects the query line. */ std::vector<Coord> Line::roots(Coord v, Dim2 d) const { - if (d < 0 || d > 1) - THROW_RANGEERROR("Line::roots, dimension argument out of range"); std::vector<Coord> result; - if ( m_versor[d] != 0 ) - { - result.push_back( (v - m_origin[d]) / m_versor[d] ); + Coord r = root(v, d); + if (IS_FINITE(r)) { + result.push_back(r); } - // TODO: else ? return result; } +Coord Line::root(Coord v, Dim2 d) const +{ + assert(d == X || d == Y); + Point vs = versor(); + if (vs[d] != 0) { + return (v - _initial[d]) / vs[d]; + } else { + return nan(""); + } +} + +boost::optional<LineSegment> Line::clip(Rect const &r) const +{ + Point v = versor(); + // handle horizontal and vertical lines first, + // since the root-based code below will break for them + for (unsigned i = 0; i < 2; ++i) { + Dim2 d = (Dim2) i; + Dim2 o = other_dimension(d); + if (v[d] != 0) continue; + if (r[d].contains(_initial[d])) { + Point a, b; + a[o] = r[o].min(); + b[o] = r[o].max(); + a[d] = b[d] = _initial[d]; + if (v[o] > 0) { + return LineSegment(a, b); + } else { + return LineSegment(b, a); + } + } else { + return boost::none; + } + } + + Interval xpart(root(r[X].min(), X), root(r[X].max(), X)); + Interval ypart(root(r[Y].min(), Y), root(r[Y].max(), Y)); + if (!xpart.isFinite() || !ypart.isFinite()) { + return boost::none; + } + + OptInterval common = xpart & ypart; + if (common) { + Point p1 = pointAt(common->min()), p2 = pointAt(common->max()); + LineSegment result(r.clamp(p1), r.clamp(p2)); + return result; + } else { + return boost::none; + } + + /* old implementation using coefficients: + + if (fabs(b) > fabs(a)) { + p0 = Point(r[X].min(), (-c - a*r[X].min())/b); + if (p0[Y] < r[Y].min()) + p0 = Point((-c - b*r[Y].min())/a, r[Y].min()); + if (p0[Y] > r[Y].max()) + p0 = Point((-c - b*r[Y].max())/a, r[Y].max()); + p1 = Point(r[X].max(), (-c - a*r[X].max())/b); + if (p1[Y] < r[Y].min()) + p1 = Point((-c - b*r[Y].min())/a, r[Y].min()); + if (p1[Y] > r[Y].max()) + p1 = Point((-c - b*r[Y].max())/a, r[Y].max()); + } else { + p0 = Point((-c - b*r[Y].min())/a, r[Y].min()); + if (p0[X] < r[X].min()) + p0 = Point(r[X].min(), (-c - a*r[X].min())/b); + if (p0[X] > r[X].max()) + p0 = Point(r[X].max(), (-c - a*r[X].max())/b); + p1 = Point((-c - b*r[Y].max())/a, r[Y].max()); + if (p1[X] < r[X].min()) + p1 = Point(r[X].min(), (-c - a*r[X].min())/b); + if (p1[X] > r[X].max()) + p1 = Point(r[X].max(), (-c - a*r[X].max())/b); + } + return LineSegment(p0, p1); */ +} + /** @brief Get a time value corresponding to a point. * @param p Point on the line. If the point is not on the line, * the returned value will be meaningless. * @return Time value t such that \f$f(t) = p\f$. * @see timeAtProjection */ -Coord Line::timeAt(Point const& _point) const { - Coord t; - if ( m_versor[X] != 0 ) { - t = (_point[X] - m_origin[X]) / m_versor[X]; - } - else if ( m_versor[Y] != 0 ) { - t = (_point[Y] - m_origin[Y]) / m_versor[Y]; +Coord Line::timeAt(Point const &p) const +{ + Point v = versor(); + // degenerate case + if (v[X] == 0 && v[Y] == 0) { + return 0; } - else { // degenerate case - t = 0; + + // use the coordinate that will give better precision + if (fabs(v[X]) > fabs(v[Y])) { + return (p[X] - _initial[X]) / v[X]; + } else { + return (p[Y] - _initial[Y]) / v[Y]; } - return t; } namespace detail { inline -OptCrossing intersection_impl(Point const& V1, Point const O1, - Point const& V2, Point const O2 ) +OptCrossing intersection_impl(Point const &v1, Point const &o1, + Point const &v2, Point const &o2) { - double detV1V2 = V1[X] * V2[Y] - V2[X] * V1[Y]; - if (are_near(detV1V2, 0)) return OptCrossing(); + Coord cp = cross(v1, v2); + if (cp == 0) return OptCrossing(); - Point B = O2 - O1; - double detBV2 = B[X] * V2[Y] - V2[X] * B[Y]; - double detV1B = B[X] * V1[Y] - V1[X] * B[Y]; - double inv_detV1V2 = 1 / detV1V2; + Point odiff = o2 - o1; Crossing c; - c.ta = detBV2 * inv_detV1V2; - c.tb = detV1B * inv_detV1V2; -// std::cerr << "ta = " << c.ta << std::endl; -// std::cerr << "tb = " << c.tb << std::endl; - return OptCrossing(c); + c.ta = cross(odiff, v2) / cp; + c.tb = cross(odiff, v1) / cp; + return c; } OptCrossing intersection_impl(Ray const& r1, Line const& l2, unsigned int i) { + using std::swap; + OptCrossing crossing = intersection_impl(r1.versor(), r1.origin(), l2.versor(), l2.origin() ); - if (crossing) - { - if (crossing->ta < 0) - { + if (crossing) { + if (crossing->ta < 0) { return OptCrossing(); - } - else - { - if (i != 0) - { - std::swap(crossing->ta, crossing->tb); + } else { + if (i != 0) { + swap(crossing->ta, crossing->tb); } return crossing; } } - if (are_near(r1.origin(), l2)) - { + if (are_near(r1.origin(), l2)) { THROW_INFINITESOLUTIONS(); - } - else - { + } else { return OptCrossing(); } } @@ -181,34 +266,29 @@ OptCrossing intersection_impl( LineSegment const& ls1, Line const& l2, unsigned int i ) { + using std::swap; + OptCrossing crossing = intersection_impl(ls1.finalPoint() - ls1.initialPoint(), ls1.initialPoint(), l2.versor(), l2.origin() ); - if (crossing) - { + if (crossing) { if ( crossing->getTime(0) < 0 || crossing->getTime(0) > 1 ) { return OptCrossing(); - } - else - { - if (i != 0) - { - std::swap((*crossing).ta, (*crossing).tb); + } else { + if (i != 0) { + swap((*crossing).ta, (*crossing).tb); } return crossing; } } - if (are_near(ls1.initialPoint(), l2)) - { + if (are_near(ls1.initialPoint(), l2)) { THROW_INFINITESOLUTIONS(); - } - else - { + } else { return OptCrossing(); } } @@ -218,6 +298,8 @@ OptCrossing intersection_impl( LineSegment const& ls1, Ray const& r2, unsigned int i ) { + using std::swap; + Point direction = ls1.finalPoint() - ls1.initialPoint(); OptCrossing crossing = intersection_impl( direction, @@ -225,57 +307,40 @@ OptCrossing intersection_impl( LineSegment const& ls1, r2.versor(), r2.origin() ); - if (crossing) - { + if (crossing) { if ( (crossing->getTime(0) < 0) || (crossing->getTime(0) > 1) || (crossing->getTime(1) < 0) ) { return OptCrossing(); - } - else - { - if (i != 0) - { - std::swap(crossing->ta, crossing->tb); + } else { + if (i != 0) { + swap(crossing->ta, crossing->tb); } return crossing; } } - if ( are_near(r2.origin(), ls1) ) - { + if ( are_near(r2.origin(), ls1) ) { bool eqvs = (dot(direction, r2.versor()) > 0); - if ( are_near(ls1.initialPoint(), r2.origin()) && !eqvs ) - { + if ( are_near(ls1.initialPoint(), r2.origin()) && !eqvs) { crossing->ta = crossing->tb = 0; return crossing; - } - else if ( are_near(ls1.finalPoint(), r2.origin()) && eqvs ) - { - if (i == 0) - { + } else if ( are_near(ls1.finalPoint(), r2.origin()) && eqvs) { + if (i == 0) { crossing->ta = 1; crossing->tb = 0; - } - else - { + } else { crossing->ta = 0; crossing->tb = 1; } return crossing; - } - else - { + } else { THROW_INFINITESOLUTIONS(); } - } - else if ( are_near(ls1.initialPoint(), r2) ) - { + } else if ( are_near(ls1.initialPoint(), r2) ) { THROW_INFINITESOLUTIONS(); - } - else - { + } else { OptCrossing no_crossing; return no_crossing; } @@ -287,24 +352,16 @@ OptCrossing intersection_impl( LineSegment const& ls1, OptCrossing intersection(Line const& l1, Line const& l2) { - OptCrossing crossing = - detail::intersection_impl( l1.versor(), l1.origin(), - l2.versor(), l2.origin() ); - if (crossing) - { - return crossing; - } - if (are_near(l1.origin(), l2)) - { + OptCrossing c = detail::intersection_impl( + l1.versor(), l1.origin(), + l2.versor(), l2.origin()); + + if (!c && distance(l1.origin(), l2) == 0) { THROW_INFINITESOLUTIONS(); } - else - { - return crossing; - } + return c; } - OptCrossing intersection(Ray const& r1, Ray const& r2) { OptCrossing crossing = @@ -416,64 +473,6 @@ OptCrossing intersection( LineSegment const& ls1, LineSegment const& ls2 ) } } - -boost::optional<LineSegment> clip (Line const& l, Rect const& r) -{ - typedef boost::optional<LineSegment> opt_linesegment; - LineSegment result; - //size_t index = 0; - std::vector<Point> points; - LineSegment ls (r.corner(0), r.corner(1)); - try - { - OptCrossing oc = intersection (ls, l); - if (oc) - { - points.push_back (l.pointAt (oc->tb)); - } - } - catch (InfiniteSolutions const &e) - { - return opt_linesegment(ls); - } - - for (size_t i = 2; i < 5; ++i) - { - ls.setInitial (ls[1]); - ls.setFinal (r.corner(i)); - try - { - OptCrossing oc = intersection (ls, l); - if (oc) - { - points.push_back (l.pointAt (oc->tb)); - if (points.size() > 1) - { - size_t sz = points.size(); - if (!are_near (points[sz - 2], points[sz - 1], 1e-10)) - { - result.setInitial (points[sz - 2]); - result.setFinal (points[sz - 1]); - return opt_linesegment(result); - } - } - } - } - catch (InfiniteSolutions const &e) - { - return opt_linesegment(ls); - } - } - if ( !points.empty() ) - { - result.setInitial (points[0]); - result.setFinal (points[0]); - return opt_linesegment(result); - } - return opt_linesegment(); -} - - Line make_angle_bisector_line(Line const& l1, Line const& l2) { OptCrossing crossing; diff --git a/src/2geom/line.h b/src/2geom/line.h index cbd68fa08..2c47b4486 100644 --- a/src/2geom/line.h +++ b/src/2geom/line.h @@ -35,7 +35,6 @@ #define LIB2GEOM_SEEN_LINE_H #include <cmath> -#include <iostream> #include <boost/optional.hpp> #include <2geom/bezier-curve.h> // for LineSegment #include <2geom/rect.h> @@ -43,36 +42,48 @@ #include <2geom/exception.h> #include <2geom/ray.h> #include <2geom/angle.h> +#include <2geom/intersection.h> namespace Geom { -class Line { +// class docs in cpp file +class Line + : boost::equality_comparable1<Line + , MultipliableNoncommutative<Line, Affine + > > +{ private: - Point m_origin; - Point m_versor; + Point _initial; + Point _final; public: /// @name Creating lines. /// @{ - /** @brief Create a default horizontal line. */ + /** @brief Create a default horizontal line. + * Creates a line with unit speed going in +X direction. */ Line() - : m_origin(0,0), m_versor(1,0) + : _initial(0,0), _final(1,0) {} /** @brief Create a line with the specified inclination. - * @param _origin One of the points on the line + * @param origin One of the points on the line * @param angle Angle of the line in mathematical convention */ - Line(Point const& _origin, Coord angle ) - : m_origin(_origin) + Line(Point const &origin, Coord angle) + : _initial(origin) { - sincos(angle, m_versor[Y], m_versor[X]); + Point v; + sincos(angle, v[Y], v[X]); + _final = _initial + v; } /** @brief Create a line going through two points. - * @param A First point - * @param B Second point */ - Line(Point const& A, Point const& B) { - setPoints(A, B); - } + * The first point will be at time 0, while the second one + * will be at time 1. + * @param a Initial point + * @param b First point */ + Line(Point const &a, Point const &b) + : _initial(a) + , _final(b) + {} /** @brief Create a line based on the coefficients of its equation. @see Line::setCoefficients() */ @@ -80,30 +91,30 @@ public: setCoefficients(a, b, c); } - /** @brief Create a line by extending a line segment. */ - explicit Line(LineSegment const& _segment) { - setPoints(_segment.initialPoint(), _segment.finalPoint()); - } + /// Create a line by extending a line segment. + explicit Line(LineSegment const &seg) + : _initial(seg.initialPoint()) + , _final(seg.finalPoint()) + {} - /** @brief Create a line by extending a ray. */ - explicit Line(Ray const& _ray) - : m_origin(_ray.origin()), m_versor(_ray.versor()) + /// Create a line by extending a ray. + explicit Line(Ray const &r) + : _initial(r.origin()) + , _final(r.origin() + r.versor()) {} - // huh? - static Line from_normal_distance(Point n, double c) { - Point P = n * c / dot(n,n); - Line l(P, P+rot90(n)); + /// Create a line normal to a vector at a specified distance from origin. + static Line from_normal_distance(Point const &n, Coord c) { + Point start = c * n.normalized(); + Line l(start, start + rot90(n)); return l; } /** @brief Create a line from origin and unit vector. * Note that each line direction has two possible unit vectors. * @param o Point through which the line will pass * @param v Unit vector of the line's direction */ - static Line from_origin_and_versor(Point o, Point v) { - Line l; - l.m_origin = o; - l.m_versor = v; + static Line from_origin_and_versor(Point const &o, Point const &v) { + Line l(o, o + v); return l; } @@ -114,63 +125,99 @@ public: /// @name Retrieve and set the line's parameters. /// @{ - /** @brief Get the line's origin point. */ - Point origin() const { return m_origin; } - /** @brief Get the line's direction unit vector. */ - Point versor() const { return m_versor; } - // return the angle described by rotating the X-axis in cw direction - // until it overlaps the line - // the returned value is in the interval [0, PI[ + + /// Get the line's origin point. + Point origin() const { return _initial; } + /** @brief Get the line's direction vector. + * Note that the retrieved vector is not normalized to unit length. */ + Point versor() const { return _final - _initial; } + /// Angle the line makes with the X axis, in mathematical convention. Coord angle() const { - double a = std::atan2(m_versor[Y], m_versor[X]); + Point d = _final - _initial; + double a = std::atan2(d[Y], d[X]); if (a < 0) a += M_PI; if (a == M_PI) a = 0; return a; } - void setOrigin(Point const& _point) { - m_origin = _point; + /** @brief Set the point at zero time. + * The orientation remains unchanged, modulo numeric errors during addition. */ + void setOrigin(Point const &p) { + Point d = p - _initial; + _initial = p; + _final += d; } - void setVersor(Point const& _versor) { - m_versor = _versor; + /** @brief Set the speed of the line. + * Origin remains unchanged. */ + void setVersor(Point const &v) { + _final = _initial + v; } - void setAngle(Coord _angle) { - sincos(_angle, m_versor[Y], m_versor[X]); + /** @brief Set the angle the line makes with the X axis. + * Origin remains unchanged. */ + void setAngle(Coord angle) { + Point v; + sincos(angle, v[Y], v[X]); + v *= distance(_initial, _final); + _final = _initial + v; } - /** @brief Set a line based on two points it should pass through. */ - void setPoints(Point const& A, Point const& B) { - m_origin = A; - if ( are_near(A, B) ) - m_versor = Point(0,0); - else - m_versor = B - A; - m_versor.normalize(); + /// Set a line based on two points it should pass through. + void setPoints(Point const &a, Point const &b) { + _initial = a; + _final = b; } - void setCoefficients (double a, double b, double c); + /** @brief Set the coefficients of the line equation. + * The line equation is: \f$ax + by = c\f$. Points that satisfy the equation + * are on the line. */ + void setCoefficients(double a, double b, double c); + + /** @brief Get the coefficients of the line equation as a vector. + * @return STL vector @a v such that @a v[0] contains \f$a\f$, @a v[1] contains \f$b\f$, + * and @a v[2] contains \f$c\f$. */ std::vector<double> coefficients() const; - /** @brief Check if the line has any points. + /// Get the coefficients of the line equation by reference. + void coefficients(Coord &a, Coord &b, Coord &c) const; + + /** @brief Check if the line has more than one point. * A degenerate line can be created if the line is created from a line equation * that has no solutions. - * @return True if the line has no points */ + * @return True if the line has no points or exactly one point */ bool isDegenerate() const { - return ( m_versor[X] == 0 && m_versor[Y] == 0 ); + return _initial == _final; + } + + /** @brief Reparametrize the line so that it has unit speed. */ + void normalize() { + Point v = _final - _initial; + v.normalize(); + _final = _initial + v; + } + /** @brief Return a new line reparametrized for unit speed. */ + Line normalized() const { + Point v = _final - _initial; + v.normalize(); + Line ret(_initial, _initial + v); + return ret; } /// @} /// @name Evaluate the line as a function. ///@{ + Point initialPoint() const { + return _initial; + } + Point finalPoint() const { + return _final; + } Point pointAt(Coord t) const { - return m_origin + m_versor * t; + return lerp(t, _initial, _final);; } Coord valueAt(Coord t, Dim2 d) const { - if (d < 0 || d > 1) - THROW_RANGEERROR("Line::valueAt, dimension argument out of range"); - return m_origin[d] + m_versor[d] * t; + return lerp(t, _initial[d], _final[d]); } Coord timeAt(Point const &p) const; @@ -180,27 +227,29 @@ public: * @return Time value corresponding to a point closest to @c p. */ Coord timeAtProjection(Point const& p) const { if ( isDegenerate() ) return 0; - return dot( p - m_origin, m_versor ); + Point v = versor(); + return dot(p - _initial, v) / dot(v, v); } /** @brief Find a point on the line closest to the query point. * This is an alias for timeAtProjection(). */ - Coord nearestPoint(Point const& _point) const { - return timeAtProjection(_point); + Coord nearestTime(Point const &p) const { + return timeAtProjection(p); } std::vector<Coord> roots(Coord v, Dim2 d) const; + Coord root(Coord v, Dim2 d) const; /// @} /// @name Create other objects based on this line. /// @{ - /** @brief Create a line containing the same points, but with negated time values. - * @return Line \f$g\f$ such that \f$g(t) = f(-t)\f$ */ - Line reverse() const - { - Line result; - result.setOrigin(m_origin); - result.setVersor(-m_versor); + void reverse() { + std::swap(_final, _initial); + } + /** @brief Create a line containing the same points, but in opposite direction. + * @return Line \f$g\f$ such that \f$g(t) = f(1-t)\f$ */ + Line reversed() const { + Line result(_final, _initial); return result; } @@ -219,6 +268,9 @@ public: return LineSegment(pointAt(f), pointAt(t)); } + /// Return the portion of the line that is inside the given rectangle + boost::optional<LineSegment> clip(Rect const &r) const; + /** @brief Create a ray starting at the specified time value. * The created ray will go in the direction of the line's versor (in the direction * of increasing time values). @@ -227,7 +279,7 @@ public: Ray ray(Coord t) { Ray result; result.setOrigin(pointAt(t)); - result.setVersor(m_versor); + result.setVersor(versor()); return result; } @@ -235,88 +287,126 @@ public: * The new line will always be degenerate. Its origin will be equal to this * line's versor. */ Line derivative() const { - Line result; - result.setOrigin(m_versor); - result.setVersor(Point(0,0)); + Point v = versor(); + Line result(v, v); return result; } - /** @brief Create a line transformed by an affine transformation. */ + /// Create a line transformed by an affine transformation. Line transformed(Affine const& m) const { - return Line(m_origin * m, (m_origin + m_versor) * m); + Line l(_initial * m, _final * m); + return l; } - /** @brief Get a vector normal to the line. + /** @brief Get a unit vector normal to the line. * If Y grows upwards, then this is the left normal. If Y grows downwards, * then this is the right normal. */ Point normal() const { - return rot90(m_versor); + return rot90(versor()).normalized(); } // what does this do? Point normalAndDist(double & dist) const { Point n = normal(); - dist = -dot(n, m_origin); + dist = -dot(n, _initial); return n; } - friend inline std::ostream &operator<< (std::ostream &out_file, const Geom::Line &in_line); -/// @} -}; // end class Line + /// Compute an affine matrix representing a reflection about the line. + Affine reflection() const { + Point v = versor().normalized(); + Coord x2 = v[X]*v[X], y2 = v[Y]*v[Y], xy = v[X]*v[Y]; + Affine m(x2-y2, 2.*xy, + 2.*xy, y2-x2, + _initial[X], _initial[Y]); + m = Translate(-_initial) * m; + return m; + } -/** @brief Output operator for lines. - * Prints out representation (point + versor) - */ -inline std::ostream &operator<< (std::ostream &out_file, const Geom::Line &in_line) { - out_file << "X: " << in_line.m_origin[X] << " Y: " << in_line.m_origin[Y] - << " dX: " << in_line.m_versor[X] << " dY: " << in_line.m_versor[Y]; - return out_file; -} + /** @brief Compute an affine which transforms all points on the line to zero X or Y coordinate. + * This operation is useful in reducing intersection problems to root-finding problems. + * There are many affines which do this transformation. This function returns one that + * preserves angles, areas and distances - a rotation combined with a translation, and + * additionaly moves the initial point of the line to (0,0). This way it works without + * problems even for lines perpendicular to the target, though may in some cases have + * lower precision than e.g. a shear transform. + * @param d Which coordinate of points on the line should be zero after the transformation */ + Affine rotationToZero(Dim2 d) const { + Point v = versor(); + if (d == X) { + std::swap(v[X], v[Y]); + } else { + v[Y] = -v[Y]; + } + Affine m = Translate(-_initial) * Rotate(v); + return m; + } + /** @brief Compute a rotation affine which transforms the line to one of the axes. + * @param d Which line should be the axis */ + Affine rotationToAxis(Dim2 d) const { + Affine m = rotationToZero(other_dimension(d)); + return m; + } + /// @} -inline -double distance(Point const& _point, Line const& _line) -{ - if ( _line.isDegenerate() ) - { - return ::Geom::distance( _point, _line.origin() ); + //std::vector<LineIntersection> intersect(Line const &other, Coord precision = EPSILON) const; + + Line &operator*=(Affine const &m) { + _initial *= m; + _final *= m; + return *this; } - else - { - return fabs( dot(_point - _line.origin(), _line.versor().ccw()) ); + bool operator==(Line const &other) const { + if (distance(pointAt(nearestTime(other._initial)), other._initial) != 0) return false; + if (distance(pointAt(nearestTime(other._final)), other._final) != 0) return false; + return true; } -} +}; // end class Line +/// @brief Compute distance from point to line. +/// @relates Line inline -bool are_near(Point const& _point, Line const& _line, double eps = EPSILON) +double distance(Point const &p, Line const &line) { - return are_near(distance(_point, _line), 0, eps); + if (line.isDegenerate()) { + return ::Geom::distance(p, line.initialPoint()); + } else { + Coord t = line.nearestTime(p); + return ::Geom::distance(line.pointAt(t), p); + } } inline -bool are_parallel(Line const& l1, Line const& l2, double eps = EPSILON) +bool are_near(Point const &p, Line const &line, double eps = EPSILON) { - return ( are_near(l1.versor(), l2.versor(), eps) - || are_near(l1.versor(), -l2.versor(), eps) ); + return are_near(distance(p, line), 0, eps); } inline -bool are_same(Line const& l1, Line const& l2, double eps = EPSILON) +bool are_parallel(Line const &l1, Line const &l2, double eps = EPSILON) { - return are_parallel(l1, l2, eps) && are_near(l1.origin(), l2, eps); + return are_near(cross(l1.versor(), l2.versor()), 0, eps); } +/** @brief Test whether two lines are approximately the same. + * This tests for being parallel and the origin of one line being close to the other, + * so it tests whether the images of the lines are similar, not whether the same time values + * correspond to similar points. For example a line from (1,1) to (2,2) and a line from + * (-1,-1) to (0,0) will the the same, because their images match, even though there is + * no time value for which the lines give similar points. + * @relates Line */ inline -bool are_orthogonal(Line const& l1, Line const& l2, double eps = EPSILON) +bool are_same(Line const &l1, Line const &l2, double eps = EPSILON) { - return ( are_near(l1.versor(), l2.versor().cw(), eps) - || are_near(l1.versor(), l2.versor().ccw(), eps) ); + return are_parallel(l1, l2, eps) && are_near(l1.origin(), l2, eps); } +/// Test whether two lines are perpendicular. +/// @relates Line inline -bool are_collinear(Point const& p1, Point const& p2, Point const& p3, - double eps = EPSILON) +bool are_orthogonal(Line const &l1, Line const &l2, double eps = EPSILON) { - return are_near( cross(p3, p2) - cross(p3, p1) + cross(p2, p1), 0, eps); + return are_near(dot(l1.versor(), l2.versor()), 0, eps); } // evaluate the angle between l1 and l2 rotating l1 in cw direction @@ -332,36 +422,34 @@ double angle_between(Line const& l1, Line const& l2) } inline -double distance(Point const& _point, LineSegment const& _segment) +double distance(Point const &p, LineSegment const &seg) { - double t = _segment.nearestPoint(_point); - return L2(_point - _segment.pointAt(t)); + double t = seg.nearestTime(p); + return distance(p, seg.pointAt(t)); } inline -bool are_near(Point const& _point, LineSegment const& _segment, - double eps = EPSILON) +bool are_near(Point const &p, LineSegment const &seg, double eps = EPSILON) { - return are_near(distance(_point, _segment), 0, eps); + return are_near(distance(p, seg), 0, eps); } // build a line passing by _point and orthogonal to _line inline -Line make_orthogonal_line(Point const& _point, Line const& _line) +Line make_orthogonal_line(Point const &p, Line const &line) { - Line l; - l.setOrigin(_point); - l.setVersor(_line.versor().cw()); + Point d = line.versor().cw(); + Line l(p, p + d); return l; } // build a line passing by _point and parallel to _line inline -Line make_parallel_line(Point const& _point, Line const& _line) +Line make_parallel_line(Point const &p, Line const &line) { - Line l(_line); - l.setOrigin(_point); - return l; + Line result(line); + result.setOrigin(p); + return result; } // build a line passing by the middle point of _segment and orthogonal to it. @@ -373,31 +461,31 @@ Line make_bisector_line(LineSegment const& _segment) // build the bisector line of the angle between ray(O,A) and ray(O,B) inline -Line make_angle_bisector_line(Point const& A, Point const& O, Point const& B) +Line make_angle_bisector_line(Point const &A, Point const &O, Point const &B) { - Point M = middle_point(A,B); - if (are_near(O,M)) { - Line l(A,B); - M += (make_orthogonal_line(O,l)).versor(); - } - return Line(O,M); + AngleInterval ival(Angle(A-O), Angle(B-O)); + Angle bisect = ival.angleAt(0.5); + return Line(O, bisect); } // prj(P) = rot(v, Point( rot(-v, P-O)[X], 0 )) + O inline -Point projection(Point const& _point, Line const& _line) +Point projection(Point const &p, Line const &line) { - return _line.pointAt( _line.nearestPoint(_point) ); + return line.pointAt(line.nearestTime(p)); } inline -LineSegment projection(LineSegment const& _segment, Line const& _line) +LineSegment projection(LineSegment const &seg, Line const &line) { - return _line.segment( _line.nearestPoint(_segment.initialPoint()), - _line.nearestPoint(_segment.finalPoint()) ); + return line.segment(line.nearestTime(seg.initialPoint()), + line.nearestTime(seg.finalPoint())); } -boost::optional<LineSegment> clip (Line const& l, Rect const& r); +inline +boost::optional<LineSegment> clip(Line const &l, Rect const &r) { + return l.clip(r); +} namespace detail diff --git a/src/2geom/linear.h b/src/2geom/linear.h index 8c154364e..cd7108835 100644 --- a/src/2geom/linear.h +++ b/src/2geom/linear.h @@ -1,7 +1,7 @@ /** * \file * \brief Linear fragment function class - * + *//* * Authors: * Nathan Hurst <njh@mail.csse.monash.edu.au> * Michael Sloan <mgsloan@gmail.com> @@ -32,12 +32,12 @@ * the specific language governing rights and limitations. */ -#ifndef SEEN_LINEAR_H -#define SEEN_LINEAR_H +#ifndef LIB2GEOM_SEEN_LINEAR_H +#define LIB2GEOM_SEEN_LINEAR_H + #include <2geom/interval.h> #include <2geom/math-utils.h> - //#define USE_SBASIS_OF #ifdef USE_SBASIS_OF @@ -46,48 +46,60 @@ #else -namespace Geom{ - -inline double lerp(double t, double a, double b) { return a*(1-t) + b*t; } +namespace Geom { class SBasis; -class Linear{ +/** + * @brief Linear function fragment + * @ingroup Fragments + */ +class Linear { public: double a[2]; Linear() {a[0]=0; a[1]=0;} Linear(double aa, double b) {a[0] = aa; a[1] = b;} Linear(double aa) {a[0] = aa; a[1] = aa;} - double operator[](const int i) const { - assert(i >= 0); + double operator[](unsigned i) const { assert(i < 2); return a[i]; } - double& operator[](const int i) { - assert(i >= 0); + double &operator[](unsigned i) { assert(i < 2); return a[i]; } //IMPL: FragmentConcept typedef double output_type; - inline bool isZero(double eps=EPSILON) const { return are_near(a[0], 0., eps) && are_near(a[1], 0., eps); } - inline bool isConstant(double eps=EPSILON) const { return are_near(a[0], a[1], eps); } - inline bool isFinite() const { return IS_FINITE(a[0]) && IS_FINITE(a[1]); } - - inline double at0() const { return a[0]; } - inline double at1() const { return a[1]; } - - inline double valueAt(double t) const { return lerp(t, a[0], a[1]); } - inline double operator()(double t) const { return valueAt(t); } + bool isZero(double eps=EPSILON) const { return are_near(a[0], 0., eps) && are_near(a[1], 0., eps); } + bool isConstant(double eps=EPSILON) const { return are_near(a[0], a[1], eps); } + bool isFinite() const { return IS_FINITE(a[0]) && IS_FINITE(a[1]); } + + Coord at0() const { return a[0]; } + Coord &at0() { return a[0]; } + Coord at1() const { return a[1]; } + Coord &at1() { return a[1]; } + + double valueAt(double t) const { return lerp(t, a[0], a[1]); } + double operator()(double t) const { return valueAt(t); } + + // not very useful, but required for ShapeConcept + std::vector<Coord> valueAndDerivatives(Coord t, unsigned n) { + std::vector<Coord> result(n+1, 0.0); + result[0] = valueAt(t); + if (n >= 1) { + result[1] = a[1] - a[0]; + } + return result; + } //defined in sbasis.h inline SBasis toSBasis() const; - inline OptInterval bounds_exact() const { return Interval(a[0], a[1]); } - inline OptInterval bounds_fast() const { return bounds_exact(); } - inline OptInterval bounds_local(double u, double v) const { return Interval(valueAt(u), valueAt(v)); } + OptInterval bounds_exact() const { return Interval(a[0], a[1]); } + OptInterval bounds_fast() const { return bounds_exact(); } + OptInterval bounds_local(double u, double v) const { return Interval(valueAt(u), valueAt(v)); } double tri() const { return a[1] - a[0]; @@ -98,6 +110,10 @@ public: }; inline Linear reverse(Linear const &a) { return Linear(a[1], a[0]); } +inline Linear portion(Linear const &a, Coord from, Coord to) { + Linear result(a.valueAt(from), a.valueAt(to)); + return result; +} //IMPL: AddableConcept inline Linear operator+(Linear const & a, Linear const & b) { @@ -158,7 +174,7 @@ inline Linear operator/=(Linear & a, double b) { } #endif -#endif //SEEN_LINEAR_H +#endif //LIB2GEOM_SEEN_LINEAR_H /* Local Variables: diff --git a/src/2geom/nearest-point.cpp b/src/2geom/nearest-time.cpp index c5dfc133c..0b21e51a2 100644 --- a/src/2geom/nearest-point.cpp +++ b/src/2geom/nearest-time.cpp @@ -1,9 +1,8 @@ -/* - * nearest point routines for D2<SBasis> and Piecewise<D2<SBasis>> - * +/** @file + * @brief Nearest time routines for D2<SBasis> and Piecewise<D2<SBasis>> + *//* * Authors: - * - * Marco Cecchetti <mrcekets at gmail.com> + * Marco Cecchetti <mrcekets at gmail.com> * * Copyright 2007-2008 authors * @@ -32,27 +31,79 @@ */ -#include <2geom/nearest-point.h> +#include <2geom/nearest-time.h> #include <algorithm> - namespace Geom { +Coord nearest_time(Point const &p, D2<Bezier> const &input, Coord from, Coord to) +{ + Interval domain(from, to); + bool partial = false; + + if (domain.min() < 0 || domain.max() > 1) { + THROW_RANGEERROR("[from,to] interval out of bounds"); + } + + if (input.isConstant(0)) return from; + + D2<Bezier> bez; + if (domain.min() != 0 || domain.max() != 1) { + bez = portion(input, domain) - p; + partial = true; + } else { + bez = input - p; + } + + // find extrema of the function x(t)^2 + y(t)^2 + // use the fact that (f^2)' = 2 f f' + // this reduces the order of the distance function by 1 + D2<Bezier> deriv = derivative(bez); + std::vector<Coord> ts = (multiply(bez[X], deriv[X]) + multiply(bez[Y], deriv[Y])).roots(); + + Coord t = -1, mind = infinity(); + for (unsigned i = 0; i < ts.size(); ++i) { + Coord droot = L2sq(bez.valueAt(ts[i])); + if (droot < mind) { + mind = droot; + t = ts[i]; + } + } + + // also check endpoints + Coord dinitial = L2sq(bez.at0()); + Coord dfinal = L2sq(bez.at1()); + + if (dinitial < mind) { + mind = dinitial; + t = 0; + } + if (dfinal < mind) { + mind = dfinal; + t = 1; + } + + if (partial) { + t = domain.valueAt(t); + } + return t; +} + //////////////////////////////////////////////////////////////////////////////// // D2<SBasis> versions /* - * Return the parameter t of a nearest point on the portion of the curve "c", + * Return the parameter t of the nearest time value on the portion of the curve "c", * related to the interval [from, to], to the point "p". * The needed curve derivative "dc" is passed as parameter. - * The function return the first nearest point to "p" that is found. + * The function return the first nearest time value to "p" that is found. */ -double nearest_point( Point const& p, - D2<SBasis> const& c, - D2<SBasis> const& dc, - double from, double to ) +double nearest_time(Point const& p, + D2<SBasis> const& c, + D2<SBasis> const& dc, + double from, double to ) { if ( from > to ) std::swap(from, to); if ( from < 0 || to > 1 ) @@ -88,21 +139,20 @@ double nearest_point( Point const& p, */ std::vector<double> -all_nearest_points( Point const& p, - D2<SBasis> const& c, - D2<SBasis> const& dc, - double from, double to ) +all_nearest_times(Point const &p, + D2<SBasis> const &c, + D2<SBasis> const &dc, + double from, double to) { - std::swap(from, to); - if ( from > to ) std::swap(from, to); - if ( from < 0 || to > 1 ) - { + if (from > to) { + std::swap(from, to); + } + if (from < 0 || to > 1) { THROW_RANGEERROR("[from,to] interval out of bounds"); } std::vector<double> result; - if (c.isConstant()) - { + if (c.isConstant()) { result.push_back(from); return result; } @@ -115,24 +165,19 @@ all_nearest_points( Point const& p, candidates.push_back(to); std::vector<double> distsq; distsq.reserve(candidates.size()); - for ( unsigned int i = 0; i < candidates.size(); ++i ) - { - distsq.push_back( L2sq(c(candidates[i]) - p) ); + for (unsigned i = 0; i < candidates.size(); ++i) { + distsq.push_back(L2sq(c(candidates[i]) - p)); } - unsigned int closest = 0; + unsigned closest = 0; double dsq = distsq[0]; - for ( unsigned int i = 1; i < candidates.size(); ++i ) - { - if ( dsq > distsq[i] ) - { + for (unsigned i = 1; i < candidates.size(); ++i) { + if (dsq > distsq[i]) { closest = i; dsq = distsq[i]; } } - for ( unsigned int i = 0; i < candidates.size(); ++i ) - { - if( distsq[closest] == distsq[i] ) - { + for (unsigned i = 0; i < candidates.size(); ++i) { + if (distsq[closest] == distsq[i]) { result.push_back(candidates[i]); } } @@ -144,39 +189,37 @@ all_nearest_points( Point const& p, // Piecewise< D2<SBasis> > versions -double nearest_point( Point const& p, - Piecewise< D2<SBasis> > const& c, - double from, double to ) +double nearest_time(Point const &p, + Piecewise< D2<SBasis> > const &c, + double from, double to) { - if ( from > to ) std::swap(from, to); - if ( from < c.cuts[0] || to > c.cuts[c.size()] ) - { + if (from > to) std::swap(from, to); + if (from < c.cuts[0] || to > c.cuts[c.size()]) { THROW_RANGEERROR("[from,to] interval out of bounds"); } - unsigned int si = c.segN(from); - unsigned int ei = c.segN(to); - if ( si == ei ) - { - double nearest= - nearest_point(p, c[si], c.segT(from, si), c.segT(to, si)); + unsigned si = c.segN(from); + unsigned ei = c.segN(to); + if (si == ei) { + double nearest = + nearest_time(p, c[si], c.segT(from, si), c.segT(to, si)); return c.mapToDomain(nearest, si); } + double t; - double nearest = nearest_point(p, c[si], c.segT(from, si)); + double nearest = nearest_time(p, c[si], c.segT(from, si)); unsigned int ni = si; double dsq; double mindistsq = distanceSq(p, c[si](nearest)); - Rect bb(Geom::Point(0,0),Geom::Point(0,0)); - for ( unsigned int i = si + 1; i < ei; ++i ) - { + Rect bb; + for (unsigned i = si + 1; i < ei; ++i) { bb = *bounds_fast(c[i]); dsq = distanceSq(p, bb); if ( mindistsq <= dsq ) continue; - t = nearest_point(p, c[i]); + + t = nearest_time(p, c[i]); dsq = distanceSq(p, c[i](t)); - if ( mindistsq > dsq ) - { + if (mindistsq > dsq) { nearest = t; ni = i; mindistsq = dsq; @@ -184,12 +227,10 @@ double nearest_point( Point const& p, } bb = *bounds_fast(c[ei]); dsq = distanceSq(p, bb); - if ( mindistsq > dsq ) - { - t = nearest_point(p, c[ei], 0, c.segT(to, ei)); + if (mindistsq > dsq) { + t = nearest_time(p, c[ei], 0, c.segT(to, ei)); dsq = distanceSq(p, c[ei](t)); - if ( mindistsq > dsq ) - { + if (mindistsq > dsq) { nearest = t; ni = ei; } @@ -198,22 +239,23 @@ double nearest_point( Point const& p, } std::vector<double> -all_nearest_points( Point const& p, - Piecewise< D2<SBasis> > const& c, - double from, double to ) +all_nearest_times(Point const &p, + Piecewise< D2<SBasis> > const &c, + double from, double to) { - if ( from > to ) std::swap(from, to); - if ( from < c.cuts[0] || to > c.cuts[c.size()] ) - { + if (from > to) { + std::swap(from, to); + } + if (from < c.cuts[0] || to > c.cuts[c.size()]) { THROW_RANGEERROR("[from,to] interval out of bounds"); } - unsigned int si = c.segN(from); - unsigned int ei = c.segN(to); + unsigned si = c.segN(from); + unsigned ei = c.segN(to); if ( si == ei ) { std::vector<double> all_nearest = - all_nearest_points(p, c[si], c.segT(from, si), c.segT(to, si)); + all_nearest_times(p, c[si], c.segT(from, si), c.segT(to, si)); for ( unsigned int i = 0; i < all_nearest.size(); ++i ) { all_nearest[i] = c.mapToDomain(all_nearest[i], si); @@ -222,18 +264,18 @@ all_nearest_points( Point const& p, } std::vector<double> all_t; std::vector< std::vector<double> > all_np; - all_np.push_back( all_nearest_points(p, c[si], c.segT(from, si)) ); - std::vector<unsigned int> ni; + all_np.push_back( all_nearest_times(p, c[si], c.segT(from, si)) ); + std::vector<unsigned> ni; ni.push_back(si); double dsq; double mindistsq = distanceSq( p, c[si](all_np.front().front()) ); - Rect bb(Geom::Point(0,0),Geom::Point(0,0)); - for ( unsigned int i = si + 1; i < ei; ++i ) - { + Rect bb; + + for (unsigned i = si + 1; i < ei; ++i) { bb = *bounds_fast(c[i]); dsq = distanceSq(p, bb); if ( mindistsq < dsq ) continue; - all_t = all_nearest_points(p, c[i]); + all_t = all_nearest_times(p, c[i]); dsq = distanceSq( p, c[i](all_t.front()) ); if ( mindistsq > dsq ) { @@ -251,29 +293,22 @@ all_nearest_points( Point const& p, } bb = *bounds_fast(c[ei]); dsq = distanceSq(p, bb); - if ( mindistsq >= dsq ) - { - all_t = all_nearest_points(p, c[ei], 0, c.segT(to, ei)); + if (mindistsq >= dsq) { + all_t = all_nearest_times(p, c[ei], 0, c.segT(to, ei)); dsq = distanceSq( p, c[ei](all_t.front()) ); - if ( mindistsq > dsq ) - { - for ( unsigned int i = 0; i < all_t.size(); ++i ) - { + if (mindistsq > dsq) { + for (unsigned int i = 0; i < all_t.size(); ++i) { all_t[i] = c.mapToDomain(all_t[i], ei); } return all_t; - } - else if ( mindistsq == dsq ) - { + } else if (mindistsq == dsq) { all_np.push_back(all_t); ni.push_back(ei); } } std::vector<double> all_nearest; - for ( unsigned int i = 0; i < all_np.size(); ++i ) - { - for ( unsigned int j = 0; j < all_np[i].size(); ++j ) - { + for (unsigned i = 0; i < all_np.size(); ++i) { + for (unsigned int j = 0; j < all_np[i].size(); ++j) { all_nearest.push_back( c.mapToDomain(all_np[i][j], ni[i]) ); } } diff --git a/src/2geom/nearest-point.h b/src/2geom/nearest-time.h index 19485242c..007cd27ba 100644 --- a/src/2geom/nearest-point.h +++ b/src/2geom/nearest-time.h @@ -1,10 +1,8 @@ -/** - * \file - * \brief nearest point routines for D2<SBasis> and Piecewise<D2<SBasis>> - * +/** @file + * @brief Nearest time routines for D2<SBasis> and Piecewise<D2<SBasis>> + *//* * Authors: - * - * Marco Cecchetti <mrcekets at gmail.com> + * Marco Cecchetti <mrcekets at gmail.com> * * Copyright 2007-2008 authors * @@ -33,8 +31,8 @@ */ -#ifndef _NEAREST_POINT_H_ -#define _NEAREST_POINT_H_ +#ifndef LIB2GEOM_SEEN_NEAREST_TIME_H +#define LIB2GEOM_SEEN_NEAREST_TIME_H #include <vector> @@ -42,7 +40,7 @@ #include <2geom/d2.h> #include <2geom/piecewise.h> #include <2geom/exception.h> - +#include <2geom/bezier.h> namespace Geom @@ -53,81 +51,91 @@ namespace Geom * return the point on L nearest to p. Note that the returned value * is with respect to the _normalized_ direction of v! */ -inline double nearest_point(Point const &p, Point const &A, Point const &v) +inline double nearest_time(Point const &p, Point const &A, Point const &v) { Point d(p - A); return d[0] * v[0] + d[1] * v[1]; } +Coord nearest_time(Point const &p, D2<Bezier> const &bez, Coord from = 0, Coord to = 1); + //////////////////////////////////////////////////////////////////////////////// // D2<SBasis> versions /* * Return the parameter t of a nearest point on the portion of the curve "c", * related to the interval [from, to], to the point "p". - * The needed curve derivative "dc" is passed as parameter. + * The needed curve derivative "deriv" is passed as parameter. * The function return the first nearest point to "p" that is found. */ -double nearest_point( Point const& p, - D2<SBasis> const& c, D2<SBasis> const& dc, - double from = 0, double to = 1 ); +double nearest_time(Point const &p, + D2<SBasis> const &c, D2<SBasis> const &deriv, + double from = 0, double to = 1); inline -double nearest_point( Point const& p, - D2<SBasis> const& c, - double from = 0, double to = 1 ) +double nearest_time(Point const &p, + D2<SBasis> const &c, + double from = 0, double to = 1 ) { - return nearest_point(p, c, Geom::derivative(c), from, to); + return nearest_time(p, c, Geom::derivative(c), from, to); } /* - * Return the parameters t of all the nearest points on the portion of + * Return the parameters t of all the nearest times on the portion of * the curve "c", related to the interval [from, to], to the point "p". * The needed curve derivative "dc" is passed as parameter. */ std::vector<double> -all_nearest_points( Point const& p, - D2<SBasis> const& c, D2<SBasis> const& dc, - double from = 0, double to = 1 ); +all_nearest_times(Point const& p, + D2<SBasis> const& c, D2<SBasis> const& dc, + double from = 0, double to = 1 ); inline std::vector<double> -all_nearest_points( Point const& p, - D2<SBasis> const& c, - double from = 0, double to = 1 ) +all_nearest_times(Point const &p, + D2<SBasis> const &c, + double from = 0, double to = 1) { - return all_nearest_points(p, c, Geom::derivative(c), from, to); + return all_nearest_times(p, c, Geom::derivative(c), from, to); } //////////////////////////////////////////////////////////////////////////////// // Piecewise< D2<SBasis> > versions -double nearest_point( Point const& p, - Piecewise< D2<SBasis> > const& c, - double from, double to ); +double nearest_time(Point const &p, + Piecewise< D2<SBasis> > const &c, + double from, double to); inline -double nearest_point( Point const& p, Piecewise< D2<SBasis> > const& c ) +double nearest_time(Point const& p, Piecewise< D2<SBasis> > const &c) { - return nearest_point(p, c, c.cuts[0], c.cuts[c.size()]); + return nearest_time(p, c, c.cuts[0], c.cuts[c.size()]); } std::vector<double> -all_nearest_points( Point const& p, - Piecewise< D2<SBasis> > const& c, - double from, double to ); +all_nearest_times(Point const &p, + Piecewise< D2<SBasis> > const &c, + double from, double to); inline std::vector<double> -all_nearest_points( Point const& p, Piecewise< D2<SBasis> > const& c ) +all_nearest_times( Point const& p, Piecewise< D2<SBasis> > const& c ) { - return all_nearest_points(p, c, c.cuts[0], c.cuts[c.size()]); + return all_nearest_times(p, c, c.cuts[0], c.cuts[c.size()]); } } // end namespace Geom - - -#endif /*_NEAREST_POINT_H_*/ +#endif // LIB2GEOM_SEEN_NEAREST_TIME_H +/* + 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/2geom/numeric/fitting-model.h b/src/2geom/numeric/fitting-model.h index a44c1ddac..5cc6dde7a 100644 --- a/src/2geom/numeric/fitting-model.h +++ b/src/2geom/numeric/fitting-model.h @@ -298,7 +298,7 @@ class LFMEllipse public: void instance(Ellipse & e, ConstVectorView const& coeff) const { - e.set(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]); + e.setCoefficients(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]); } }; @@ -333,7 +333,7 @@ class LFMCircle public: void instance(Circle & c, ConstVectorView const& coeff) const { - c.set(1, coeff[0], coeff[1], coeff[2]); + c.setCoefficients(1, coeff[0], coeff[1], coeff[2]); } }; diff --git a/src/2geom/numeric/matrix.h b/src/2geom/numeric/matrix.h index a130bd748..ddca35cd6 100644 --- a/src/2geom/numeric/matrix.h +++ b/src/2geom/numeric/matrix.h @@ -432,16 +432,17 @@ class Matrix: public detail::MatrixImpl inline void swap(Matrix & m1, Matrix & m2) { - assert( m1.rows() == m2.rows() && m1.columns() == m2.columns() ); - std::swap(m1.m_matrix, m2.m_matrix); + assert(m1.rows() == m2.rows() && m1.columns() == m2.columns()); + using std::swap; + swap(m1.m_matrix, m2.m_matrix); } -inline -void swap_any(Matrix & m1, Matrix & m2) +inline void swap_any(Matrix &m1, Matrix &m2) { - std::swap(m1.m_matrix, m2.m_matrix); - std::swap(m1.m_rows, m2.m_rows); - std::swap(m1.m_columns, m2.m_columns); + using std::swap; + swap(m1.m_matrix, m2.m_matrix); + swap(m1.m_rows, m2.m_rows); + swap(m1.m_columns, m2.m_columns); } @@ -569,8 +570,9 @@ class MatrixView : public detail::MatrixImpl inline void swap_view(MatrixView & m1, MatrixView & m2) { - assert( m1.rows() == m2.rows() && m1.columns() == m2.columns() ); - std::swap(m1.m_matrix_view, m2.m_matrix_view); + assert(m1.rows() == m2.rows() && m1.columns() == m2.columns()); + using std::swap; + swap(m1.m_matrix_view, m2.m_matrix_view); } Vector operator*( detail::BaseMatrixImpl const& A, diff --git a/src/2geom/numeric/symmetric-matrix-fs-operation.h b/src/2geom/numeric/symmetric-matrix-fs-operation.h index 37ece56ae..c5aaa724b 100644 --- a/src/2geom/numeric/symmetric-matrix-fs-operation.h +++ b/src/2geom/numeric/symmetric-matrix-fs-operation.h @@ -43,12 +43,7 @@ namespace Geom { namespace NL { template <size_t N> -inline -SymmetricMatrix<N> adj(const ConstBaseSymmetricMatrix<N> & /*S*/) -{ - THROW_NOTIMPLEMENTED(); - return SymmetricMatrix<N>(); -} +SymmetricMatrix<N> adj(const ConstBaseSymmetricMatrix<N> & S); template <> inline diff --git a/src/2geom/numeric/symmetric-matrix-fs-trace.h b/src/2geom/numeric/symmetric-matrix-fs-trace.h index dbabecf6e..eff3dd24d 100644 --- a/src/2geom/numeric/symmetric-matrix-fs-trace.h +++ b/src/2geom/numeric/symmetric-matrix-fs-trace.h @@ -73,13 +73,7 @@ bool abs_less (double x, double y) template <size_t K, size_t N> struct trace { - static - double evaluate (const ConstBaseSymmetricMatrix<N> & /*S*/) - { - THROW_NOTIMPLEMENTED(); - return K; - } - + static double evaluate(const ConstBaseSymmetricMatrix<N> &S); }; template <size_t N> diff --git a/src/2geom/numeric/symmetric-matrix-fs.h b/src/2geom/numeric/symmetric-matrix-fs.h index c1de27afd..2fadd6915 100644 --- a/src/2geom/numeric/symmetric-matrix-fs.h +++ b/src/2geom/numeric/symmetric-matrix-fs.h @@ -339,7 +339,10 @@ class BaseSymmetricMatrix : public ConstBaseSymmetricMatrix<N> { } - using base_type::operator(); + double operator() (size_t i, size_t j) const + { + return m_data[base_type::get_index(i,j)]; + } double& operator() (size_t i, size_t j) { diff --git a/src/2geom/numeric/vector.h b/src/2geom/numeric/vector.h index 6ab898f29..f28289f0f 100644 --- a/src/2geom/numeric/vector.h +++ b/src/2geom/numeric/vector.h @@ -348,15 +348,17 @@ class Vector : public detail::VectorImpl inline void swap(Vector & v1, Vector & v2) { - assert( v1.size() == v2.size() ); - std::swap(v1.m_vector, v2.m_vector); + assert(v1.size() == v2.size()); + using std::swap; + swap(v1.m_vector, v2.m_vector); } inline void swap_any(Vector & v1, Vector & v2) { - std::swap(v1.m_vector, v2.m_vector); - std::swap(v1.m_size, v2.m_size); + using std::swap; + swap(v1.m_vector, v2.m_vector); + swap(v1.m_size, v2.m_size); } @@ -552,7 +554,8 @@ inline void swap_view(VectorView & v1, VectorView & v2) { assert( v1.size() == v2.size() ); - std::swap(v1.m_vector_view, v2.m_vector_view); // not swap m_vector too + using std::swap; + swap(v1.m_vector_view, v2.m_vector_view); // not swap m_vector too } inline diff --git a/src/2geom/ord.h b/src/2geom/ord.h index 0add83da4..e190a4a1e 100644 --- a/src/2geom/ord.h +++ b/src/2geom/ord.h @@ -1,6 +1,5 @@ -/** - * \file - * \brief Comparator template +/** @file + * @brief Comparator template *//* * Authors: * ? <?@?.?> @@ -32,8 +31,8 @@ * */ -#ifndef __2GEOM_ORD__ -#define __2GEOM_ORD__ +#ifndef LIB2GEOM_SEEN_ORD_H +#define LIB2GEOM_SEEN_ORD_H namespace { diff --git a/src/2geom/path-intersection.cpp b/src/2geom/path-intersection.cpp index 63a29423d..07e38ba9e 100644 --- a/src/2geom/path-intersection.cpp +++ b/src/2geom/path-intersection.cpp @@ -12,98 +12,9 @@ namespace Geom { -/** - * This function computes the winding of the path, given a reference point. - * Positive values correspond to counter-clockwise in the mathematical coordinate system, - * and clockwise in screen coordinates. This particular implementation casts a ray in - * the positive x direction. It iterates the path, checking for intersection with the - * bounding boxes. If an intersection is found, the initial/final Y value of the curve is - * used to derive a delta on the winding value. If the point is within the bounding box, - * the curve specific winding function is called. - */ -int winding(Path const &path, Point p) { - //start on a segment which is not a horizontal line with y = p[y] - Path::const_iterator start; - for(Path::const_iterator iter = path.begin(); ; ++iter) { - if(iter == path.end_closed()) { return 0; } - if(iter->initialPoint()[Y]!=p[Y]) { start = iter; break; } - if(iter->finalPoint()[Y]!=p[Y]) { start = iter; break; } - if(iter->boundsFast().height()!=0.){ start = iter; break; } - } - int wind = 0; - unsigned cnt = 0; - bool starting = true; - for (Path::const_iterator iter = start; iter != start || starting - ; ++iter, iter = (iter == path.end_closed()) ? path.begin() : iter ) - { - cnt++; - if(cnt > path.size()) return wind; //some bug makes this required - starting = false; - Rect bounds = (iter->boundsFast()); - Coord x = p[X], y = p[Y]; - - if(x > bounds.right() || !bounds[Y].contains(y)) continue; //ray doesn't intersect box - - Point final = iter->finalPoint(); - Point initial = iter->initialPoint(); - Cmp final_to_ray = cmp(final[Y], y); - Cmp initial_to_ray = cmp(initial[Y], y); - - // if y is included, these will have opposite values, giving order. - Cmp c = cmp(final_to_ray, initial_to_ray); - if(x < bounds.left()) { - // ray goes through bbox - // winding delta determined by position of endpoints - if(final_to_ray != EQUAL_TO) { - wind += int(c); // GT = counter-clockwise = 1; LT = clockwise = -1; EQ = not-included = 0 - //std::cout << int(c) << " "; - goto cont; - } - } else { - //inside bbox, use custom per-curve winding thingie - int delt = iter->winding(p); - wind += delt; - //std::cout << "n" << delt << " "; - } - //Handling the special case of an endpoint on the ray: - if(final[Y] == y) { - //Traverse segments until it breaks away from y - //99.9% of the time this will happen the first go - Path::const_iterator next = iter; - ++next; - for(; ; ++next) { - if(next == path.end_closed()) next = path.begin(); - Rect bnds = (next->boundsFast()); - //TODO: X considerations - if(bnds.height() > 0) { - //It has diverged - if(bnds.contains(p)) { - const double fudge = 0.01; - if(cmp(y, next->valueAt(fudge, Y)) == initial_to_ray) { - wind += int(c); - //std::cout << "!!!!!" << int(c) << " "; - } - iter = next; // No increment, as the rest of the thing hasn't been counted. - } else { - Coord ny = next->initialPoint()[Y]; - if(cmp(y, ny) == initial_to_ray) { - //Is a continuation through the ray, so counts windingwise - wind += int(c); - //std::cout << "!!!!!" << int(c) << " "; - } - iter = ++next; - } - goto cont; - } - if(next==start) return wind; - } - //Looks like it looped, which means everything's flat - return 0; - } - - cont:(void)0; - } - return wind; +/// Compute winding number of the path at the specified point +int winding(Path const &path, Point const &p) { + return path.winding(p); } /** @@ -162,7 +73,7 @@ void append(T &a, T const &b) { * indicates if the time values are within their proper range on the line segments. */ bool -linear_intersect(Point A0, Point A1, Point B0, Point B1, +linear_intersect(Point const &A0, Point const &A1, Point const &B0, Point const &B1, double &tA, double &tB, double &det) { bool both_lines_non_zero = (!are_near(A0, A1)) && (!are_near(B0, B1)); @@ -521,7 +432,7 @@ std::vector<double> path_mono_splits(Path const &p) { * Applies path_mono_splits to multiple paths, and returns the results such that * time-set i corresponds to Path i. */ -std::vector<std::vector<double> > paths_mono_splits(std::vector<Path> const &ps) { +std::vector<std::vector<double> > paths_mono_splits(PathVector const &ps) { std::vector<std::vector<double> > ret; for(unsigned i = 0; i < ps.size(); i++) ret.push_back(path_mono_splits(ps[i])); @@ -533,7 +444,7 @@ std::vector<std::vector<double> > paths_mono_splits(std::vector<Path> const &ps) * Each entry i corresponds to path i of the input. The number of rects in each entry is guaranteed to be the * number of splits for that path, subtracted by one. */ -std::vector<std::vector<Rect> > split_bounds(std::vector<Path> const &p, std::vector<std::vector<double> > splits) { +std::vector<std::vector<Rect> > split_bounds(PathVector const &p, std::vector<std::vector<double> > splits) { std::vector<std::vector<Rect> > ret; for(unsigned i = 0; i < p.size(); i++) { std::vector<Rect> res; @@ -553,7 +464,7 @@ std::vector<std::vector<Rect> > split_bounds(std::vector<Path> const &p, std::ve * This function does two sweeps, one on the bounds of each path, and after that cull, one on the curves within. * This leads to a certain amount of code complexity, however, most of that is factored into the above functions */ -CrossingSet MonoCrosser::crossings(std::vector<Path> const &a, std::vector<Path> const &b) { +CrossingSet MonoCrosser::crossings(PathVector const &a, PathVector const &b) { if(b.empty()) return CrossingSet(a.size(), Crossings()); CrossingSet results(a.size() + b.size(), Crossings()); if(a.empty()) return results; @@ -596,7 +507,7 @@ CrossingSet MonoCrosser::crossings(std::vector<Path> const &a, std::vector<Path> /* This function is similar codewise to the MonoCrosser, the main difference is that it deals with * only one set of paths and includes self intersection -CrossingSet crossings_among(std::vector<Path> const &p) { +CrossingSet crossings_among(PathVector const &p) { CrossingSet results(p.size(), Crossings()); if(p.empty()) return results; @@ -780,7 +691,7 @@ void flip_crossings(Crossings &crs) { crs[i] = Crossing(crs[i].tb, crs[i].ta, crs[i].b, crs[i].a, !crs[i].dir); } -CrossingSet crossings_among(std::vector<Path> const &p) { +CrossingSet crossings_among(PathVector const &p) { CrossingSet results(p.size(), Crossings()); if(p.empty()) return results; diff --git a/src/2geom/path-intersection.h b/src/2geom/path-intersection.h index 512c31167..e3a8d1de3 100644 --- a/src/2geom/path-intersection.h +++ b/src/2geom/path-intersection.h @@ -32,8 +32,8 @@ * */ -#ifndef __GEOM_PATH_INTERSECTION_H -#define __GEOM_PATH_INTERSECTION_H +#ifndef LIB2GEOM_SEEN_PATH_INTERSECTION_H +#define LIB2GEOM_SEEN_PATH_INTERSECTION_H #include <2geom/path.h> @@ -43,10 +43,10 @@ namespace Geom { -int winding(Path const &path, Point p); +int winding(Path const &path, Point const &p); bool path_direction(Path const &p); -inline bool contains(Path const & p, Point i, bool evenodd = true) { +inline bool contains(Path const & p, Point const &i, bool evenodd = true) { return (evenodd ? winding(p, i) % 2 : winding(p, i)) != 0; } @@ -74,19 +74,19 @@ Crossings mono_intersect(Curve const & A, Interval const &Ad, struct SimpleCrosser : public Crosser<Path> { Crossings crossings(Curve const &a, Curve const &b); Crossings crossings(Path const &a, Path const &b) { return curve_sweep<SimpleCrosser>(a, b); } - CrossingSet crossings(std::vector<Path> const &a, std::vector<Path> const &b) { return Crosser<Path>::crossings(a, b); } + CrossingSet crossings(PathVector const &a, PathVector const &b) { return Crosser<Path>::crossings(a, b); } }; struct MonoCrosser : public Crosser<Path> { - Crossings crossings(Path const &a, Path const &b) { return crossings(std::vector<Path>(1,a), std::vector<Path>(1,b))[0]; } - CrossingSet crossings(std::vector<Path> const &a, std::vector<Path> const &b); + Crossings crossings(Path const &a, Path const &b) { return crossings(PathVector(a), PathVector(b))[0]; } + CrossingSet crossings(PathVector const &a, PathVector const &b); }; typedef SimpleCrosser DefaultCrosser; std::vector<double> path_mono_splits(Path const &p); -CrossingSet crossings_among(std::vector<Path> const & p); +CrossingSet crossings_among(PathVector const & p); Crossings self_crossings(Path const & a); inline Crossings crossings(Curve const & a, Curve const & b) { @@ -99,7 +99,7 @@ inline Crossings crossings(Path const & a, Path const & b) { return c.crossings(a, b); } -inline CrossingSet crossings(std::vector<Path> const & a, std::vector<Path> const & b) { +inline CrossingSet crossings(PathVector const & a, PathVector const & b) { DefaultCrosser c = DefaultCrosser(); return c.crossings(a, b); } diff --git a/src/2geom/path-sink.cpp b/src/2geom/path-sink.cpp index 6acd9508c..6ccc21e7b 100644 --- a/src/2geom/path-sink.cpp +++ b/src/2geom/path-sink.cpp @@ -34,77 +34,40 @@ namespace Geom { -void output(Curve const &curve, PathSink &sink) { - std::vector<Point> pts; - sbasis_to_bezier(pts, curve.toSBasis(), 2); //TODO: use something better! - sink.curveTo(pts[0], pts[1], pts[2]); +void PathSink::feed(Curve const &c, bool moveto_initial) +{ + c.feed(*this, moveto_initial); } -void output(HLineSegment const &curve, PathSink &sink) { - sink.hlineTo(curve.finalPoint()[X]); -} - -void output(VLineSegment const &curve, PathSink &sink) { - sink.vlineTo(curve.finalPoint()[Y]); -} - -void output(LineSegment const &curve, PathSink &sink) { - sink.lineTo(curve[1]); -} - -void output(CubicBezier const &curve, PathSink &sink) { - sink.curveTo(curve[1], curve[2], curve[3]); -} - -void output(QuadraticBezier const &curve, PathSink &sink) { - sink.quadTo(curve[1], curve[2]); -} - -void output(SVGEllipticalArc const &curve, PathSink &sink) { - sink.arcTo( curve.ray(X), curve.ray(Y), curve.rotationAngle(), - curve.largeArc(), curve.sweep(), - curve.finalPoint() ); -} - -template <typename T> -bool output_as(Curve const &curve, PathSink &sink) { - T const *t = dynamic_cast<T const *>(&curve); - if (t) { - output(*t, sink); - return true; - } else { - return false; - } -} - -void PathSink::path(Path const &path) { +void PathSink::feed(Path const &path) { flush(); moveTo(path.front().initialPoint()); - Path::const_iterator iter; - for (iter = path.begin(); iter != path.end(); ++iter) { - output_as<HLineSegment>(*iter, *this) || - output_as<VLineSegment>(*iter, *this) || - output_as<LineSegment>(*iter, *this) || - output_as<CubicBezier>(*iter, *this) || - output_as<QuadraticBezier>(*iter, *this) || - output_as<SVGEllipticalArc>(*iter, *this) || - output_as<Curve>(*iter, *this); + // never output the closing segment to the sink + Path::const_iterator iter = path.begin(), last = path.end_open(); + for (; iter != last; ++iter) { + iter->feed(*this, false); } - if (path.closed()) { closePath(); } flush(); } -void PathSink::pathvector(PathVector const &pv) { - flush(); +void PathSink::feed(PathVector const &pv) { for (PathVector::const_iterator i = pv.begin(); i != pv.end(); ++i) { - path(*i); + feed(*i); } } +void PathSink::feed(Rect const &r) { + moveTo(r.corner(0)); + lineTo(r.corner(1)); + lineTo(r.corner(2)); + lineTo(r.corner(3)); + closePath(); +} + } /* diff --git a/src/2geom/path-sink.h b/src/2geom/path-sink.h index 949369b80..5913593d2 100644 --- a/src/2geom/path-sink.h +++ b/src/2geom/path-sink.h @@ -29,8 +29,8 @@ * */ -#ifndef SEEN_SVG_PATH_H -#define SEEN_SVG_PATH_H +#ifndef LIB2GEOM_SEEN_PATH_SINK_H +#define LIB2GEOM_SEEN_PATH_SINK_H #include <2geom/pathvector.h> #include <2geom/curves.h> @@ -44,58 +44,69 @@ namespace Geom { * PathSink provides an interface that allows one to easily write * code which processes path data, for instance when converting * between path formats used by different graphics libraries. + * It is also useful for writing algorithms which must do something + * for each curve in the path. * * To store a path in a new format, implement the virtual methods - * for segments in a derived class and call path() or pathvector(). + * for segments in a derived class and call feed(). + * + * @ingroup Paths */ class PathSink { public: - /** Move to a different point without creating a segment. + /** @brief Move to a different point without creating a segment. * Usually starts a new subpath. */ virtual void moveTo(Point const &p) = 0; - /// Output a horizontal line segment. Only the X coordinate of the final point is given. - virtual void hlineTo(Coord v) = 0; - /// Output a vertical line segment. Only the Y coordinate of the final point is given. - virtual void vlineTo(Coord v) = 0; /// Output a line segment. virtual void lineTo(Point const &p) = 0; /// Output a quadratic Bezier segment. virtual void curveTo(Point const &c0, Point const &c1, Point const &p) = 0; /// Output a cubic Bezier segment. virtual void quadTo(Point const &c, Point const &p) = 0; - /** Output an elliptical arc segment. + /** @brief Output an elliptical arc segment. * See the EllipticalArc class for the documentation of parameters. */ - virtual void arcTo(double rx, double ry, double angle, + virtual void arcTo(Coord rx, Coord ry, Coord angle, bool large_arc, bool sweep, Point const &p) = 0; /// Close the current path with a line segment. virtual void closePath() = 0; - /** Flush any internal state of the generator. - * + /** @brief Flush any internal state of the generator. * This call should implicitly finish the current subpath. * Calling this method should be idempotent, because the default - * implementations of path() and pathvector() will be call it + * implementations of path() and pathvector() will call it * multiple times in a row. */ virtual void flush() = 0; + // Get the current point, e.g. where the initial point of the next segment will be. + //virtual Point currentPoint() const = 0; - /** Undo the last segment. + /** @brief Undo the last segment. * This method is optional. * @return true true if a segment was erased, false otherwise. */ virtual bool backspace() { return false; } // these have a default implementation - /** Output a subpath. - * Calls the appropriate segment methods according to the contents - * of the passed subpath. You can override this function. */ - virtual void path(Path const &p); - /** Output a path. + virtual void feed(Curve const &c, bool moveto_initial = true); + /** @brief Output a subpath. * Calls the appropriate segment methods according to the contents - * of the passed path. You can override this function. */ - virtual void pathvector(PathVector const &v); + * of the passed subpath. You can override this function. + * NOTE: if you override only some of the feed() functions, + * always write this in the derived class: + * @code + using PathSink::feed; + @endcode + * Otherwise the remaining methods will be hidden. */ + virtual void feed(Path const &p); + /** @brief Output a path. + * Calls feed() on each path in the vector. You can override this function. */ + virtual void feed(PathVector const &v); + /// Output an axis-aligned rectangle, using moveTo, lineTo and closePath. + virtual void feed(Rect const &); virtual ~PathSink() {} }; +/** @brief Store paths to an output iterator + * @ingroup Paths */ template <typename OutputIterator> class PathIteratorSink : public PathSink { public: @@ -110,22 +121,6 @@ public: } //TODO: what if _in_path = false? - void hlineTo(Coord v) { - // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" - if (!_in_path) { - moveTo(_start_p); - } - _path.template appendNew<HLineSegment>(Point(v, _path.finalPoint()[Y])); - } - - void vlineTo(Coord v) { - // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" - if (!_in_path) { - moveTo(_start_p); - } - _path.template appendNew<VLineSegment>(Point(_path.finalPoint()[X], v)); - } - void lineTo(Point const &p) { // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" if (!_in_path) { @@ -150,7 +145,7 @@ public: _path.template appendNew<CubicBezier>(c0, c1, p); } - void arcTo(double rx, double ry, double angle, + void arcTo(Coord rx, Coord ry, Coord angle, bool large_arc, bool sweep, Point const &p) { // check for implicit moveto, like in: "M 1,1 L 2,2 z l 2,2 z" @@ -170,12 +165,12 @@ public: return false; } - void append(Path const &other, Path::Stitching stitching = Path::NO_STITCHING) + void append(Path const &other) { if (!_in_path) { moveTo(other.initialPoint()); } - _path.append(other, stitching); + _path.append(other); } void closePath() { @@ -188,11 +183,15 @@ public: _in_path = false; *_out++ = _path; _path.clear(); - _path.close(false); } } + + void setStitching(bool s) { + _path.setStitching(s); + } - void path(Path const &other) + using PathSink::feed; + void feed(Path const &other) { flush(); *_out++ = other; @@ -205,14 +204,19 @@ protected: Point _start_p; }; -typedef std::back_insert_iterator<std::vector<Path> > iter; +typedef std::back_insert_iterator<PathVector> SubpathInserter; -class PathBuilder : public PathIteratorSink<iter> { +/** @brief Store paths to a PathVector + * @ingroup Paths */ +class PathBuilder : public PathIteratorSink<SubpathInserter> { private: - std::vector<Path> _pathset; + PathVector _pathset; public: - PathBuilder() : PathIteratorSink<iter>(iter(_pathset)) {} - std::vector<Path> const &peek() const {return _pathset;} + PathBuilder() : PathIteratorSink<SubpathInserter>(SubpathInserter(_pathset)) {} + /// Retrieve the path + PathVector const &peek() const {return _pathset;} + /// Clear the stored path vector + void clear() { _pathset.clear(); } }; } diff --git a/src/2geom/path.cpp b/src/2geom/path.cpp index 3558af3b3..cf8b15d60 100644 --- a/src/2geom/path.cpp +++ b/src/2geom/path.cpp @@ -1,11 +1,12 @@ -/* - * Path - Series of continuous curves - * +/** @file + * @brief Path - a sequence of contiguous curves (implementation file) + *//* * Authors: - * MenTaLguY <mental@rydia.net> - * Marco Cecchetti <mrcekets at gmail.com> + * MenTaLguY <mental@rydia.net> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> * - * Copyright 2007-2008 authors + * Copyright 2007-2014 authors * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -31,414 +32,861 @@ * the specific language governing rights and limitations. */ - - - #include <2geom/path.h> +#include <2geom/pathvector.h> #include <2geom/transforms.h> +#include <2geom/convex-hull.h> #include <algorithm> +#include <limits> using std::swap; using namespace Geom::PathInternal; -namespace Geom +namespace Geom { + +// this represents an empty interval +PathInterval::PathInterval() + : _from(0, 0.0) + , _to(0, 0.0) + , _path_size(1) + , _cross_start(false) + , _reverse(false) +{} + +PathInterval::PathInterval(Position const &from, Position const &to, bool cross_start, size_type path_size) + : _from(from) + , _to(to) + , _path_size(path_size) + , _cross_start(cross_start) + , _reverse(cross_start ? to >= from : to < from) +{ + if (_reverse) { + _to.normalizeForward(_path_size); + if (_from != _to) { + _from.normalizeBackward(_path_size); + } + } else { + _from.normalizeForward(_path_size); + if (_from != _to) { + _to.normalizeBackward(_path_size); + } + } + + if (_from == _to) { + _reverse = false; + _cross_start = false; + } +} + +bool PathInterval::contains(Position const &pos) const { + if (_cross_start) { + if (_reverse) { + return pos >= _to || _from >= pos; + } else { + return pos >= _from || _to >= pos; + } + } else { + if (_reverse) { + return _to <= pos && pos <= _from; + } else { + return _from <= pos && pos <= _to; + } + } +} + +PathPosition PathInterval::inside(Coord min_dist) const +{ + // If there is some node further than min_dist (in time coord) from the ends, + // return that node. Otherwise, return the middle. + PathPosition result(0, 0.0); + + if (!_cross_start && _from.curve_index == _to.curve_index) { + PathPosition result(_from.curve_index, lerp(0.5, _from.t, _to.t)); + return result; + } + // If _cross_start, then we can be sure that at least one node is in the domain. + // If dcurve == 0, it actually means that all curves are included in the domain + + if (_reverse) { + size_type dcurve = (_path_size + _from.curve_index - _to.curve_index) % _path_size; + bool from_close = _from.t < min_dist; + bool to_close = _to.t > 1 - min_dist; + + if (dcurve == 0) { + dcurve = _path_size; + } + + if (dcurve == 1) { + if (from_close || to_close) { + result.curve_index = _from.curve_index; + Coord tmid = _from.t - ((1 - _to.t) + _from.t) * 0.5; + if (tmid < 0) { + result.curve_index = (_path_size + result.curve_index - 1) % _path_size; + tmid += 1; + } + result.t = tmid; + return result; + } + + result.curve_index = _from.curve_index; + return result; + } + + result.curve_index = (_to.curve_index + 1) % _path_size; + if (to_close) { + if (dcurve == 2) { + result.t = 0.5; + } else { + result.curve_index = (result.curve_index + 1) % _path_size; + } + } + return result; + } else { + size_type dcurve = (_path_size + _to.curve_index - _from.curve_index) % _path_size; + bool from_close = _from.t > 1 - min_dist; + bool to_close = _to.t < min_dist; + + if (dcurve == 0) { + dcurve = _path_size; + } + + if (dcurve == 1) { + if (from_close || to_close) { + result.curve_index = _from.curve_index; + Coord tmid = ((1 - _from.t) + _to.t) * 0.5 + _from.t; + if (tmid >= 1) { + result.curve_index = (result.curve_index + 1) % _path_size; + tmid -= 1; + } + result.t = tmid; + return result; + } + + result.curve_index = _to.curve_index; + return result; + } + + result.curve_index = (_from.curve_index + 1) % _path_size; + if (from_close) { + if (dcurve == 2) { + result.t = 0.5; + } else { + result.curve_index = (result.curve_index + 1) % _path_size; + } + } + return result; + } + + result.curve_index = _reverse ? _from.curve_index : _to.curve_index; + return result; +} + +PathInterval PathInterval::from_direction(Position const &from, Position const &to, bool reversed, size_type path_size) +{ + PathInterval result; + result._from = from; + result._to = to; + result._path_size = path_size; + + if (reversed) { + result._to.normalizeForward(path_size); + if (result._from != result._to) { + result._from.normalizeBackward(path_size); + } + } else { + result._from.normalizeForward(path_size); + if (result._from != result._to) { + result._to.normalizeBackward(path_size); + } + } + + if (result._from == result._to) { + result._reverse = false; + result._cross_start = false; + } else { + result._reverse = reversed; + if (reversed) { + result._cross_start = from < to; + } else { + result._cross_start = to < from; + } + } + return result; +} + + +Path::Path(ConvexHull const &ch) + : _curves(new Sequence()) + , _closing_seg(new ClosingSegment(Point(), Point())) + , _closed(false) + , _exception_on_stitch(true) +{ + if (ch.empty()) { + _curves->push_back(_closing_seg); + return; + } + + _closing_seg->setInitial(ch.back()); + _closing_seg->setFinal(ch.front()); + + Point last = ch.front(); + + for (std::size_t i = 1; i < ch.size(); ++i) { + _curves->push_back(new LineSegment(last, ch[i])); + last = ch[i]; + } + + _curves->push_back(_closing_seg); + _closed = true; +} + +void Path::clear() +{ + _unshare(); + _curves->pop_back().release(); + _curves->clear(); + _closing_seg->setInitial(Point(0, 0)); + _closing_seg->setFinal(Point(0, 0)); + _curves->push_back(_closing_seg); + _closed = false; +} + +OptRect Path::boundsFast() const { + OptRect bounds; + if (empty()) + return bounds; + bounds = front().boundsFast(); + const_iterator iter = begin(); + // the closing path segment can be ignored, because it will always lie within the bbox of the rest of the path + if (iter != end()) { + for (++iter; iter != end(); ++iter) { + bounds.unionWith(iter->boundsFast()); + } + } + return bounds; +} -OptRect Path::boundsFast() const { - OptRect bounds; - if (empty()) return bounds; - bounds = front().boundsFast(); - const_iterator iter = begin(); - // the closing path segment can be ignored, because it will always lie within the bbox of the rest of the path - if ( iter != end() ) { - for ( ++iter; iter != end() ; ++iter ) { - bounds.unionWith(iter->boundsFast()); +OptRect Path::boundsExact() const +{ + OptRect bounds; + if (empty()) + return bounds; + bounds = front().boundsExact(); + const_iterator iter = begin(); + // the closing path segment can be ignored, because it will always lie within the bbox of the rest of the path + if (iter != end()) { + for (++iter; iter != end(); ++iter) { + bounds.unionWith(iter->boundsExact()); + } } - } - return bounds; + return bounds; } -OptRect Path::boundsExact() const { - OptRect bounds; - if (empty()) return bounds; - bounds = front().boundsExact(); - const_iterator iter = begin(); - // the closing path segment can be ignored, because it will always lie within the bbox of the rest of the path - if ( iter != end() ) { - for ( ++iter; iter != end() ; ++iter ) { - bounds.unionWith(iter->boundsExact()); +Piecewise<D2<SBasis> > Path::toPwSb() const +{ + Piecewise<D2<SBasis> > ret; + ret.push_cut(0); + unsigned i = 1; + bool degenerate = true; + // pw<d2<>> is always open. so if path is closed, add closing segment as well to pwd2. + for (const_iterator it = begin(); it != end_default(); ++it) { + if (!it->isDegenerate()) { + ret.push(it->toSBasis(), i++); + degenerate = false; + } } - } - return bounds; + if (degenerate) { + // if path only contains degenerate curves, no second cut is added + // so we need to create at least one segment manually + ret = Piecewise<D2<SBasis> >(initialPoint()); + } + return ret; } -template<typename iter> +template <typename iter> iter inc(iter const &x, unsigned n) { - iter ret = x; - for(unsigned i = 0; i < n; i++) - ret++; - return ret; -} - -Path &Path::operator*=(Affine const &m) { - unshare(); - Sequence::iterator last = get_curves().end() - 1; - Sequence::iterator it; - Point prev; - for (it = get_curves().begin() ; it != last ; ++it) { - *it = boost::shared_ptr<Curve>((*it)->transformed(m)); - if ( it != get_curves().begin() && (*it)->initialPoint() != prev ) { - THROW_CONTINUITYERROR(); - } - prev = (*it)->finalPoint(); - } - for ( int i = 0 ; i < 2 ; ++i ) { - final_->setPoint(i, (*final_)[i] * m); - } - if (get_curves().size() > 1) { - if ( front().initialPoint() != initialPoint() || back().finalPoint() != finalPoint() ) { - THROW_CONTINUITYERROR(); - } - } - return *this; -} - -Path &Path::operator*=(Translate const &m) { -/* Somehow there is something wrong here, LPE Construct grid fails with this code - unshare(); - Sequence::iterator last = get_curves().end() - 1; - Sequence::iterator it; - Point prev; - for (it = get_curves().begin() ; it != last ; ++it) { - // *(const_cast<Curve*>(&**it)) *= m; - const_cast<Curve*>(it->get())->operator*=(m); - if ( it != get_curves().begin() && (*it)->initialPoint() != prev ) { - THROW_CONTINUITYERROR(); - } - prev = (*it)->finalPoint(); - } - for ( int i = 0 ; i < 2 ; ++i ) { - final_->setPoint(i, (*final_)[i] + m.vector()); - } - if (get_curves().size() > 1) { - if ( front().initialPoint() != initialPoint() || back().finalPoint() != finalPoint() ) { - THROW_CONTINUITYERROR(); - } - } - return *this; -*/ - return this->operator*=(static_cast<Affine>(m)); -} - -std::vector<double> -Path::allNearestPoints(Point const& _point, double from, double to) const -{ - using std::swap; - - if ( from > to ) swap(from, to); - const Path& _path = *this; - unsigned int sz = _path.size(); - if ( _path.closed() ) ++sz; - if ( from < 0 || to > sz ) - { - THROW_RANGEERROR("[from,to] interval out of bounds"); - } - double sif, st = modf(from, &sif); - double eif, et = modf(to, &eif); - unsigned int si = static_cast<unsigned int>(sif); - unsigned int ei = static_cast<unsigned int>(eif); - if ( si == sz ) - { - --si; - st = 1; - } - if ( ei == sz ) - { - --ei; - et = 1; - } - if ( si == ei ) - { - std::vector<double> all_nearest = - _path[si].allNearestPoints(_point, st, et); - for ( unsigned int i = 0; i < all_nearest.size(); ++i ) - { - all_nearest[i] = si + all_nearest[i]; - } - return all_nearest; - } - std::vector<double> all_t; - std::vector< std::vector<double> > all_np; - all_np.push_back( _path[si].allNearestPoints(_point, st) ); - std::vector<unsigned int> ni; - ni.push_back(si); - double dsq; - double mindistsq - = distanceSq( _point, _path[si].pointAt( all_np.front().front() ) ); - Rect bb(Geom::Point(0,0),Geom::Point(0,0)); - for ( unsigned int i = si + 1; i < ei; ++i ) - { - bb = (_path[i].boundsFast()); - dsq = distanceSq(_point, bb); - if ( mindistsq < dsq ) continue; - all_t = _path[i].allNearestPoints(_point); - dsq = distanceSq( _point, _path[i].pointAt( all_t.front() ) ); - if ( mindistsq > dsq ) - { - all_np.clear(); - all_np.push_back(all_t); - ni.clear(); - ni.push_back(i); - mindistsq = dsq; - } - else if ( mindistsq == dsq ) - { - all_np.push_back(all_t); - ni.push_back(i); - } - } - bb = (_path[ei].boundsFast()); - dsq = distanceSq(_point, bb); - if ( mindistsq >= dsq ) - { - all_t = _path[ei].allNearestPoints(_point, 0, et); - dsq = distanceSq( _point, _path[ei].pointAt( all_t.front() ) ); - if ( mindistsq > dsq ) - { - for ( unsigned int i = 0; i < all_t.size(); ++i ) - { - all_t[i] = ei + all_t[i]; - } - return all_t; - } - else if ( mindistsq == dsq ) - { - all_np.push_back(all_t); - ni.push_back(ei); - } - } - std::vector<double> all_nearest; - for ( unsigned int i = 0; i < all_np.size(); ++i ) - { - for ( unsigned int j = 0; j < all_np[i].size(); ++j ) - { - all_nearest.push_back( ni[i] + all_np[i][j] ); - } - } - all_nearest.erase(std::unique(all_nearest.begin(), all_nearest.end()), - all_nearest.end()); - return all_nearest; -} - -std::vector<double> -Path::nearestPointPerCurve(Point const& _point) const -{ - //return a single nearest point for each curve in this path - std::vector<double> np; - for (const_iterator it = begin() ; it != end_default() ; ++it) - //for (std::vector<Path>::const_iterator it = _path.begin(); it != _path.end(), ++it){ - { - np.push_back(it->nearestPoint(_point)); - } - return np; -} - -double Path::nearestPoint(Point const &_point, double from, double to, double *distance_squared) const -{ - using std::swap; - - if ( from > to ) swap(from, to); - const Path& _path = *this; - unsigned int sz = _path.size(); - if ( _path.closed() ) ++sz; - if ( from < 0 || to > sz ) - { - THROW_RANGEERROR("[from,to] interval out of bounds"); - } - double sif, st = modf(from, &sif); - double eif, et = modf(to, &eif); - unsigned int si = static_cast<unsigned int>(sif); - unsigned int ei = static_cast<unsigned int>(eif); - if(sz == 0) {// naked moveto - if (distance_squared != NULL) - *distance_squared = distanceSq(_point, _path.initialPoint()); - return 0; - } - if ( si == sz ) - { - --si; - st = 1; - } - if ( ei == sz ) - { - --ei; - et = 1; - } - if ( si == ei ) - { - double nearest = _path[si].nearestPoint(_point, st, et); - if (distance_squared != NULL) - *distance_squared = distanceSq(_point, _path[si].pointAt(nearest)); - return si + nearest; - } - - double t; - double nearest = _path[si].nearestPoint(_point, st); - unsigned int ni = si; - double dsq; - double mindistsq = distanceSq(_point, _path[si].pointAt(nearest)); - for ( unsigned int i = si + 1; i < ei; ++i ) - { - Rect bb = (_path[i].boundsFast()); - dsq = distanceSq(_point, bb); - if ( mindistsq <= dsq ) continue; - t = _path[i].nearestPoint(_point); - dsq = distanceSq(_point, _path[i].pointAt(t)); - if ( mindistsq > dsq ) - { - nearest = t; - ni = i; - mindistsq = dsq; - } - } - Rect bb = (_path[ei].boundsFast()); - dsq = distanceSq(_point, bb); - if ( mindistsq > dsq ) - { - t = _path[ei].nearestPoint(_point, 0, et); - dsq = distanceSq(_point, _path[ei].pointAt(t)); - if ( mindistsq > dsq ) - { - nearest = t; - ni = ei; - mindistsq = dsq; - } - } - - if (distance_squared != NULL) - *distance_squared = mindistsq; - - return ni + nearest; -} - -void Path::appendPortionTo(Path &ret, double from, double to) const { - if (!(from >= 0 && to >= 0)) { - THROW_RANGEERROR("from and to must be >=0 in Path::appendPortionTo"); - } - if(to == 0) to = size()+0.999999; - if(from == to) { return; } - double fi, ti; - double ff = modf(from, &fi), tf = modf(to, &ti); - if(tf == 0) { ti--; tf = 1; } - const_iterator fromi = inc(begin(), (unsigned)fi); - if(fi == ti && from < to) { - Curve *v = fromi->portion(ff, tf); - ret.append(*v, STITCH_DISCONTINUOUS); - delete v; - return; - } - const_iterator toi = inc(begin(), (unsigned)ti); - if(ff != 1.) { - Curve *fromv = fromi->portion(ff, 1.); - //fromv->setInitial(ret.finalPoint()); - ret.append(*fromv, STITCH_DISCONTINUOUS); - delete fromv; - } - if(from >= to) { - const_iterator ender = end(); - if(ender->initialPoint() == ender->finalPoint()) ++ender; - ret.insert(ret.end(), ++fromi, ender, STITCH_DISCONTINUOUS); - ret.insert(ret.end(), begin(), toi, STITCH_DISCONTINUOUS); - } else { - ret.insert(ret.end(), ++fromi, toi, STITCH_DISCONTINUOUS); - } - Curve *tov = toi->portion(0., tf); - ret.append(*tov, STITCH_DISCONTINUOUS); - delete tov; -} - -void Path::do_update(Sequence::iterator first_replaced, - Sequence::iterator last_replaced, - Sequence::iterator first, - Sequence::iterator last) -{ - // note: modifies the contents of [first,last) - check_continuity(first_replaced, last_replaced, first, last); - if ( ( last - first ) == ( last_replaced - first_replaced ) ) { - std::copy(first, last, first_replaced); - } else { - // this approach depends on std::vector's behavior WRT iterator stability - get_curves().erase(first_replaced, last_replaced); - get_curves().insert(first_replaced, first, last); - } - - if ( get_curves().front().get() != final_ ) { - final_->setPoint(0, back().finalPoint()); - final_->setPoint(1, front().initialPoint()); - } -} - -void Path::do_append(Curve *c) { - if ( get_curves().front().get() == final_ ) { - final_->setPoint(1, c->initialPoint()); - } else { - if (c->initialPoint() != finalPoint()) { - THROW_CONTINUITYERROR(); - } - } - get_curves().insert(get_curves().end()-1, boost::shared_ptr<Curve>(c)); - final_->setPoint(0, c->finalPoint()); -} - -void Path::stitch(Sequence::iterator first_replaced, - Sequence::iterator last_replaced, - Sequence &source) -{ - if (!source.empty()) { - if ( first_replaced != get_curves().begin() ) { - if ( (*first_replaced)->initialPoint() != source.front()->initialPoint() ) { - Curve *stitch = new StitchSegment((*first_replaced)->initialPoint(), - source.front()->initialPoint()); - source.insert(source.begin(), boost::shared_ptr<Curve>(stitch)); - } - } - if ( last_replaced != (get_curves().end()-1) ) { - if ( (*last_replaced)->finalPoint() != source.back()->finalPoint() ) { - Curve *stitch = new StitchSegment(source.back()->finalPoint(), - (*last_replaced)->finalPoint()); - source.insert(source.end(), boost::shared_ptr<Curve>(stitch)); - } - } - } else if ( first_replaced != last_replaced && first_replaced != get_curves().begin() && last_replaced != get_curves().end()-1) { - if ( (*first_replaced)->initialPoint() != (*(last_replaced-1))->finalPoint() ) { - Curve *stitch = new StitchSegment((*(last_replaced-1))->finalPoint(), - (*first_replaced)->initialPoint()); - source.insert(source.begin(), boost::shared_ptr<Curve>(stitch)); - } - } -} - -void Path::check_continuity(Sequence::iterator first_replaced, - Sequence::iterator last_replaced, - Sequence::iterator first, - Sequence::iterator last) -{ - if ( first != last ) { - if ( first_replaced != get_curves().begin() ) { - if ( (*first_replaced)->initialPoint() != (*first)->initialPoint() ) { - THROW_CONTINUITYERROR(); - } + iter ret = x; + for (unsigned i = 0; i < n; i++) + ret++; + return ret; +} + +bool Path::operator==(Path const &other) const +{ + if (this == &other) + return true; + if (_closed != other._closed) + return false; + return *_curves == *other._curves; +} + +Path &Path::operator*=(Affine const &m) +{ + _unshare(); + Sequence::iterator last = _curves->end() - 1; + Sequence::iterator it; + + for (it = _curves->begin(); it != last; ++it) { + it->transform(m); + } + _closing_seg->transform(m); + checkContinuity(); + return *this; +} + +void Path::start(Point const &p) { + if (_curves->size() > 1) { + clear(); + } + _closing_seg->setInitial(p); + _closing_seg->setFinal(p); +} + +Interval Path::timeRange() const +{ + Interval ret(0, size_default()); + return ret; +} + +Curve const &Path::curveAt(Coord t, Coord *rest) const +{ + Position pos = _getPosition(t); + if (rest) { + *rest = pos.t; + } + return at(pos.curve_index); +} + +Point Path::pointAt(Coord t) const +{ + return pointAt(_getPosition(t)); +} + +Coord Path::valueAt(Coord t, Dim2 d) const +{ + return valueAt(_getPosition(t), d); +} + +Curve const &Path::curveAt(Position const &pos) const +{ + return at(pos.curve_index); +} +Point Path::pointAt(Position const &pos) const +{ + return at(pos.curve_index).pointAt(pos.t); +} +Coord Path::valueAt(Position const &pos, Dim2 d) const +{ + return at(pos.curve_index).valueAt(pos.t, d); +} + +std::vector<PathPosition> Path::roots(Coord v, Dim2 d) const +{ + std::vector<PathPosition> res; + for (unsigned i = 0; i <= size(); i++) { + std::vector<Coord> temp = (*this)[i].roots(v, d); + for (unsigned j = 0; j < temp.size(); j++) + res.push_back(PathPosition(i, temp[j])); + } + return res; +} + +std::vector<PathIntersection> Path::intersect(Path const &other, Coord precision) const +{ + std::vector<PathIntersection> result; + + // TODO: sweepline optimization + // TODO: remove multiple intersections within precision of each other? + for (size_type i = 0; i < size(); ++i) { + for (size_type j = 0; j < other.size(); ++j) { + std::vector<CurveIntersection> cx = (*this)[i].intersect(other[j], precision); + for (std::size_t ci = 0; ci < cx.size(); ++ci) { + PathPosition a(i, cx[ci].first), b(j, cx[ci].second); + PathIntersection px(a, b, cx[ci].point()); + result.push_back(px); + } + } + } + + return result; +} + +int Path::winding(Point const &p) const { + int wind = 0; + + /* To handle all the edge cases, we consider the maximum Y edge of the bounding box + * as not included in box. This way paths that contain linear horizontal + * segments will be treated correctly. */ + for (const_iterator i = begin(); i != end_closed(); ++i) { + Rect bounds = i->boundsFast(); + + if (bounds.height() == 0) continue; + if (p[X] > bounds.right() || !bounds[Y].lowerContains(p[Y])) { + // Ray doesn't intersect bbox, so we ignore this segment + continue; + } + + if (p[X] < bounds.left()) { + /* Ray intersects the curve's bbox, but the point is outside it. + * The winding contribution is exactly the same as that + * of a linear segment with the same initial and final points. */ + Point ip = i->initialPoint(); + Point fp = i->finalPoint(); + Rect eqbox(ip, fp); + + if (eqbox[Y].lowerContains(p[Y])) { + /* The ray intersects the equivalent linear segment. + * Determine winding contribution based on its derivative. */ + if (ip[Y] < fp[Y]) { + wind += 1; + } else if (ip[Y] > fp[Y]) { + wind -= 1; + } else { + // should never happen, because bounds.height() was not zero + assert(false); + } + } + } else { + // point is inside bbox + wind += i->winding(p); + } } - if ( last_replaced != (get_curves().end()-1) ) { - if ( (*(last_replaced-1))->finalPoint() != (*(last-1))->finalPoint() ) { + return wind; +} + +std::vector<double> Path::allNearestTimes(Point const &_point, double from, double to) const +{ + // TODO from and to are not used anywhere. + // rewrite this to simplify. + using std::swap; + + if (from > to) + swap(from, to); + const Path &_path = *this; + unsigned int sz = _path.size(); + if (_path.closed()) + ++sz; + if (from < 0 || to > sz) { + THROW_RANGEERROR("[from,to] interval out of bounds"); + } + double sif, st = modf(from, &sif); + double eif, et = modf(to, &eif); + unsigned int si = static_cast<unsigned int>(sif); + unsigned int ei = static_cast<unsigned int>(eif); + if (si == sz) { + --si; + st = 1; + } + if (ei == sz) { + --ei; + et = 1; + } + if (si == ei) { + std::vector<double> all_nearest = _path[si].allNearestTimes(_point, st, et); + for (unsigned int i = 0; i < all_nearest.size(); ++i) { + all_nearest[i] = si + all_nearest[i]; + } + return all_nearest; + } + std::vector<double> all_t; + std::vector<std::vector<double> > all_np; + all_np.push_back(_path[si].allNearestTimes(_point, st)); + std::vector<unsigned int> ni; + ni.push_back(si); + double dsq; + double mindistsq = distanceSq(_point, _path[si].pointAt(all_np.front().front())); + Rect bb(Geom::Point(0, 0), Geom::Point(0, 0)); + for (unsigned int i = si + 1; i < ei; ++i) { + bb = (_path[i].boundsFast()); + dsq = distanceSq(_point, bb); + if (mindistsq < dsq) + continue; + all_t = _path[i].allNearestTimes(_point); + dsq = distanceSq(_point, _path[i].pointAt(all_t.front())); + if (mindistsq > dsq) { + all_np.clear(); + all_np.push_back(all_t); + ni.clear(); + ni.push_back(i); + mindistsq = dsq; + } else if (mindistsq == dsq) { + all_np.push_back(all_t); + ni.push_back(i); + } + } + bb = (_path[ei].boundsFast()); + dsq = distanceSq(_point, bb); + if (mindistsq >= dsq) { + all_t = _path[ei].allNearestTimes(_point, 0, et); + dsq = distanceSq(_point, _path[ei].pointAt(all_t.front())); + if (mindistsq > dsq) { + for (unsigned int i = 0; i < all_t.size(); ++i) { + all_t[i] = ei + all_t[i]; + } + return all_t; + } else if (mindistsq == dsq) { + all_np.push_back(all_t); + ni.push_back(ei); + } + } + std::vector<double> all_nearest; + for (unsigned int i = 0; i < all_np.size(); ++i) { + for (unsigned int j = 0; j < all_np[i].size(); ++j) { + all_nearest.push_back(ni[i] + all_np[i][j]); + } + } + all_nearest.erase(std::unique(all_nearest.begin(), all_nearest.end()), all_nearest.end()); + return all_nearest; +} + +std::vector<Coord> Path::nearestTimePerCurve(Point const &p) const +{ + // return a single nearest time for each curve in this path + std::vector<Coord> np; + for (const_iterator it = begin(); it != end_default(); ++it) { + np.push_back(it->nearestTime(p)); + } + return np; +} + +Coord Path::nearestTime(Point const &p, Coord *dist) const +{ + Position pos = nearestPosition(p, dist); + return pos.curve_index + pos.t; +} + +PathPosition Path::nearestPosition(Point const &p, Coord *dist) const +{ + Coord mindist = std::numeric_limits<Coord>::max(); + Position ret; + + if (_curves->size() == 1) { + // naked moveto + ret.curve_index = 0; + ret.t = 0; + if (dist) { + *dist = distance(_closing_seg->initialPoint(), p); + } + return ret; + } + + for (size_type i = 0; i < size_default(); ++i) { + Curve const &c = at(i); + if (distance(p, c.boundsFast()) >= mindist) continue; + + Coord t = c.nearestTime(p); + Coord d = distance(c.pointAt(t), p); + if (d < mindist) { + mindist = d; + ret.curve_index = i; + ret.t = t; + } + } + if (dist) { + *dist = mindist; + } + + return ret; +} + +void Path::appendPortionTo(Path &ret, double from, double to) const +{ + if (!(from >= 0 && to >= 0)) { + THROW_RANGEERROR("from and to must be >=0 in Path::appendPortionTo"); + } + if (to == 0) + to = size() + 0.999999; + if (from == to) { + return; + } + double fi, ti; + double ff = modf(from, &fi), tf = modf(to, &ti); + if (tf == 0) { + ti--; + tf = 1; + } + const_iterator fromi = inc(begin(), (unsigned)fi); + if (fi == ti && from < to) { + ret.append(fromi->portion(ff, tf)); + return; + } + const_iterator toi = inc(begin(), (unsigned)ti); + if (ff != 1.) { + // fromv->setInitial(ret.finalPoint()); + ret.append(fromi->portion(ff, 1.)); + } + if (from >= to) { + const_iterator ender = end(); + if (ender->initialPoint() == ender->finalPoint()) + ++ender; + ret.insert(ret.end(), ++fromi, ender); + ret.insert(ret.end(), begin(), toi); + } else { + ret.insert(ret.end(), ++fromi, toi); + } + ret.append(toi->portion(0., tf)); +} + +void Path::appendPortionTo(Path &target, PathInterval const &ival, + boost::optional<Point> const &p_from, boost::optional<Point> const &p_to) const +{ + assert(ival.pathSize() == size_closed()); + + if (ival.isDegenerate()) { + Point stitch_to = p_from ? *p_from : pointAt(ival.from()); + target.stitchTo(stitch_to); + return; + } + + Position const &from = ival.from(), &to = ival.to(); + + bool reverse = ival.reverse(); + int di = reverse ? -1 : 1; + size_type s = size_closed(); + + if (!ival.crossesStart() && from.curve_index == to.curve_index) { + Curve *c = (*this)[from.curve_index].portion(from.t, to.t); + if (p_from) { + c->setInitial(*p_from); + } + if (p_to) { + c->setFinal(*p_to); + } + target.append(c); + } else { + Curve *c_first = (*this)[from.curve_index].portion(from.t, reverse ? 0 : 1); + if (p_from) { + c_first->setInitial(*p_from); + } + target.append(c_first); + + for (size_type i = (from.curve_index + s + di) % s; i != to.curve_index; + i = (i + s + di) % s) + { + if (reverse) { + target.append((*this)[i].reverse()); + } else { + target.append((*this)[i].duplicate()); + } + } + + Curve *c_last = (*this)[to.curve_index].portion(reverse ? 1 : 0, to.t); + if (p_to) { + c_last->setFinal(*p_to); + } + target.append(c_last); + } +} + +Path Path::reversed() const +{ + typedef std::reverse_iterator<Sequence::const_iterator> RIter; + + Path ret; + ret._curves->pop_back(); + RIter iter(_curves->end()), rend(_curves->begin()); + for (; iter != rend; ++iter) { + ret._curves->push_back(iter->reverse()); + } + ret._closing_seg = static_cast<ClosingSegment *>(ret._closing_seg->reverse()); + ret._curves->push_back(ret._closing_seg); + return ret; +} + + +void Path::insert(iterator pos, Curve const &curve) +{ + _unshare(); + Sequence::iterator seq_pos(seq_iter(pos)); + Sequence source; + source.push_back(curve.duplicate()); + do_update(seq_pos, seq_pos, source); +} + +void Path::erase(iterator pos) +{ + _unshare(); + Sequence::iterator seq_pos(seq_iter(pos)); + + Sequence stitched; + do_update(seq_pos, seq_pos + 1, stitched); +} + +void Path::erase(iterator first, iterator last) +{ + _unshare(); + Sequence::iterator seq_first = seq_iter(first); + Sequence::iterator seq_last = seq_iter(last); + + Sequence stitched; + do_update(seq_first, seq_last, stitched); +} + +void Path::stitchTo(Point const &p) +{ + if (!empty() && _closing_seg->initialPoint() != p) { + if (_exception_on_stitch) { + THROW_CONTINUITYERROR(); + } + _unshare(); + do_append(new StitchSegment(_closing_seg->initialPoint(), p)); + } +} + +void Path::replace(iterator replaced, Curve const &curve) +{ + replace(replaced, replaced + 1, curve); +} + +void Path::replace(iterator first_replaced, iterator last_replaced, Curve const &curve) +{ + _unshare(); + Sequence::iterator seq_first_replaced(seq_iter(first_replaced)); + Sequence::iterator seq_last_replaced(seq_iter(last_replaced)); + Sequence source(1); + source.push_back(curve.duplicate()); + + do_update(seq_first_replaced, seq_last_replaced, source); +} + +void Path::replace(iterator replaced, Path const &path) +{ + replace(replaced, path.begin(), path.end()); +} + +void Path::replace(iterator first, iterator last, Path const &path) +{ + replace(first, last, path.begin(), path.end()); +} + +// replace curves between first and last with contents of source, +// +void Path::do_update(Sequence::iterator first, Sequence::iterator last, Sequence &source) +{ + // TODO: handle cases where first > last in closed paths? + bool last_beyond_closing_segment = (last == _curves->end()); + + // special case: + // if do_update replaces the closing segment, we have to regenerate it + if (source.empty()) { + if (first == last) return; // nothing to do + + // only removing some segments + if ((!_closed && first == _curves->begin()) || (!_closed && last == _curves->end() - 1) || last_beyond_closing_segment) { + // just adjust the closing segment + // do nothing + } else if (first->initialPoint() != (last - 1)->finalPoint()) { + if (_exception_on_stitch) { + THROW_CONTINUITYERROR(); + } + source.push_back(new StitchSegment(first->initialPoint(), (last - 1)->finalPoint())); + } + } else { + // replacing + if (first == _curves->begin() && last == _curves->end()) { + // special case: replacing everything should work the same in open and closed curves + _curves->erase(_curves->begin(), _curves->end() - 1); + _closing_seg->setFinal(source.front().initialPoint()); + _closing_seg->setInitial(source.back().finalPoint()); + _curves->transfer(_curves->begin(), source.begin(), source.end(), source); + return; + } + + // stitch in front + if (!_closed && first == _curves->begin()) { + // not necessary to stitch in front + } else if (first->initialPoint() != source.front().initialPoint()) { + if (_exception_on_stitch) { + THROW_CONTINUITYERROR(); + } + source.insert(source.begin(), new StitchSegment(first->initialPoint(), source.front().initialPoint())); + } + + // stitch at the end + if ((!_closed && last == _curves->end() - 1) || last_beyond_closing_segment) { + // repurpose the closing segment as the stitch segment + // do nothing + } else if (source.back().finalPoint() != (last - 1)->finalPoint()) { + if (_exception_on_stitch) { + THROW_CONTINUITYERROR(); + } + source.push_back(new StitchSegment(source.back().finalPoint(), (last - 1)->finalPoint())); + } + } + + // do not erase the closing segment, adjust it instead + if (last_beyond_closing_segment) { + --last; + } + _curves->erase(first, last); + _curves->transfer(first, source.begin(), source.end(), source); + + // adjust closing segment + if (size_open() == 0) { + _closing_seg->setFinal(_closing_seg->initialPoint()); + } else { + _closing_seg->setInitial(back_open().finalPoint()); + _closing_seg->setFinal(front().initialPoint()); + } + + checkContinuity(); +} + +void Path::do_append(Curve *c) +{ + if (&_curves->front() == _closing_seg) { + _closing_seg->setFinal(c->initialPoint()); + } else { + // if we can't freely move the closing segment, we check whether + // the new curve connects with the last non-closing curve + if (c->initialPoint() != _closing_seg->initialPoint()) { + THROW_CONTINUITYERROR(); + } + } + _curves->insert(_curves->end() - 1, c); + _closing_seg->setInitial(c->finalPoint()); +} + +void Path::checkContinuity() const +{ + Sequence::const_iterator i = _curves->begin(), j = _curves->begin(); + ++j; + for (; j != _curves->end(); ++i, ++j) { + if (i->finalPoint() != j->initialPoint()) { + THROW_CONTINUITYERROR(); + } + } + if (_curves->front().initialPoint() != _curves->back().finalPoint()) { THROW_CONTINUITYERROR(); - } } - } else if ( first_replaced != last_replaced && first_replaced != get_curves().begin() && last_replaced != get_curves().end()-1) { - if ( (*first_replaced)->initialPoint() != (*(last_replaced-1))->finalPoint() ) { - THROW_CONTINUITYERROR(); +} + +PathPosition Path::_getPosition(Coord t) const +{ + size_type sz = size_default(); + if (t < 0 || t > sz) { + THROW_RANGEERROR("parameter t out of bounds"); + } + + Position ret; + Coord k; + ret.t = modf(t, &k); + ret.curve_index = k; + if (ret.curve_index == sz) { + --ret.curve_index; + ret.t = 1; + } + return ret; +} + +Piecewise<D2<SBasis> > paths_to_pw(PathVector const &paths) +{ + Piecewise<D2<SBasis> > ret = paths[0].toPwSb(); + for (unsigned i = 1; i < paths.size(); i++) { + ret.concat(paths[i].toPwSb()); } - } + return ret; } } // end namespace Geom diff --git a/src/2geom/path.h b/src/2geom/path.h index 28d2a25e4..1940aa580 100644 --- a/src/2geom/path.h +++ b/src/2geom/path.h @@ -1,43 +1,47 @@ -/** - * \file - * \brief Path - Series of continuous curves +/** @file + * @brief Path - a sequence of contiguous curves *//* - * Authors: - * MenTaLguY <mental@rydia.net> - * Marco Cecchetti <mrcekets at gmail.com> - * - * Copyright 2007-2008 Authors - * - * This library is free software; you can redistribute it and/or - * modify it either under the terms of the GNU Lesser General Public - * License version 2.1 as published by the Free Software Foundation - * (the "LGPL") or, at your option, under the terms of the Mozilla - * Public License Version 1.1 (the "MPL"). If you do not alter this - * notice, a recipient may use your version of this file under either - * the MPL or the LGPL. - * - * You should have received a copy of the LGPL along with this library - * in the file COPYING-LGPL-2.1; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * You should have received a copy of the MPL along with this library - * in the file COPYING-MPL-1.1 - * - * The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY - * OF ANY KIND, either express or implied. See the LGPL or the MPL for - * the specific language governing rights and limitations. - */ + * Authors: + * MenTaLguY <mental@rydia.net> + * Marco Cecchetti <mrcekets at gmail.com> + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2007-2014 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ #ifndef LIB2GEOM_SEEN_PATH_H #define LIB2GEOM_SEEN_PATH_H #include <iterator> #include <algorithm> +#include <iostream> +#include <boost/operators.hpp> +#include <boost/ptr_container/ptr_vector.hpp> #include <boost/shared_ptr.hpp> +#include <2geom/intersection.h> #include <2geom/curve.h> #include <2geom/bezier-curve.h> #include <2geom/transforms.h> @@ -45,656 +49,757 @@ namespace Geom { class Path; +class ConvexHull; namespace PathInternal { -typedef std::vector<boost::shared_ptr<Curve const> > Sequence; +typedef boost::ptr_vector<Curve> Sequence; + +template <typename P> +class BaseIterator + : public boost::random_access_iterator_helper + < BaseIterator<P> + , Curve const + , std::ptrdiff_t + , Curve const * + , Curve const & + > +{ + protected: + BaseIterator(P &p, unsigned i) : path(&p), index(i) {} + // default copy, default assign + typedef BaseIterator<P> Self; -template <typename C, typename P> -class BaseIterator { -protected: - BaseIterator() : path(NULL), index(0) {} - BaseIterator(P *p, unsigned i) : path(p), index(i) {} - // default copy, default assign + public: + BaseIterator() : path(NULL), index(0) {} -public: - bool operator==(BaseIterator const &other) { - return path == other.path && index == other.index; - } - bool operator!=(BaseIterator const &other) { - return path != other.path || index != other.index; - } - - Curve const &operator*() const { return (*path)[index]; } - Curve const *operator->() const { return &(*path)[index]; } - boost::shared_ptr<Curve const> get_ref() const { - return path->get_ref_at_index(index); - } - - C &operator++() { - ++index; - return static_cast<C &>(*this); - } - C operator++(int) { - C old(static_cast<C &>(*this)); - ++(*this); - return old; - } - - C &operator--() { - --index; - return static_cast<C &>(*this); - } - C operator--(int) { - C old(static_cast<C &>(*this)); - --(*this); - return old; - } + bool operator<(BaseIterator const &other) const { + return path == other.path && index < other.index; + } + bool operator==(BaseIterator const &other) const { + return path == other.path && index == other.index; + } + Curve const &operator*() const { + return (*path)[index]; + } -private: - P *path; - unsigned index; + Self &operator++() { + ++index; + return *this; + } + Self &operator--() { + --index; + return *this; + } + Self &operator+=(std::ptrdiff_t d) { + index += d; + return *this; + } + Self &operator-=(std::ptrdiff_t d) { + index -= d; + return *this; + } - friend class ::Geom::Path; + private: + P *path; + unsigned index; + + friend class ::Geom::Path; }; -class ConstIterator : public BaseIterator<ConstIterator, Path const> { -public: - typedef BaseIterator<ConstIterator, Path const> Base; +} - ConstIterator() : Base() {} - // default copy, default assign +/** @brief Position (generalized time value) in the path. + * + * This class exists because when mapping the range of multiple curves onto the same interval + * as the curve index, we lose some precision. For instance, a path with 16 curves will + * have 4 bits less precision than a path with 1 curve. If you need high precision results + * in long paths, either use this class and related methods instead of the standard methods + * pointAt(), nearestTime() and so on, or use curveAt() to first obtain the curve, then + * call the method again to obtain a high precision result. + * + * @ingroup Paths */ +struct PathPosition + : boost::totally_ordered<PathPosition> +{ + typedef PathInternal::Sequence::size_type size_type; -private: - ConstIterator(Path const &p, unsigned i) : Base(&p, i) {} - friend class ::Geom::Path; + Coord t; ///< Time value in the curve + size_type curve_index; ///< Index of the curve in the path + + PathPosition() : t(0), curve_index(0) {} + PathPosition(size_type idx, Coord tval) : t(tval), curve_index(idx) {} + + bool operator<(PathPosition const &other) const { + if (curve_index < other.curve_index) return true; + if (curve_index == other.curve_index) { + return t < other.t; + } + return false; + } + bool operator==(PathPosition const &other) const { + return curve_index == other.curve_index && t == other.t; + } + /// Convert positions at or beyond 1 to 0 on the next curve. + void normalizeForward(size_type path_size) { + if (t >= 1) { + curve_index = (curve_index + 1) % path_size; + t = 0; + } + } + /// Convert positions at or before 0 to 1 on the previous curve. + void normalizeBackward(size_type path_size) { + if (t <= 0) { + curve_index = (curve_index - 1) % path_size; + t = 1; + } + } }; -class Iterator : public BaseIterator<Iterator, Path> { -public: - typedef BaseIterator<Iterator, Path> Base; +inline std::ostream &operator<<(std::ostream &os, PathPosition const &pos) { + os << pos.curve_index << ": " << pos.t; + return os; +} - Iterator() : Base() {} - // default copy, default assign - operator ConstIterator const &() const { - return reinterpret_cast<ConstIterator const &>(*this); - } +/** @brief Contiguous subset of the path's parameter domain. + * This is a directed interval, which allows one to specify any contiguous subset + * of the path's domain, including subsets that wrap around the initial point + * of the path. + * @ingroup Paths */ +class PathInterval { +public: + typedef PathPosition Position; + typedef PathInternal::Sequence::size_type size_type; + + /** @brief Default interval. + * Default-constructed PathInterval includes only the initial point of the initial segment. */ + PathInterval(); + + /** @brief Construct an interval in the path's parameter domain. + * @param from Initial position + * @param to Final position + * @param cross_start If true, the interval will proceed from the initial to final + * position through the initial point of the path, wrapping around the closing segment; + * otherwise it will not wrap around the closing segment. + * @param path_size Size of the path to which this interval applies, required + * to clean up degenerate cases */ + PathInterval(Position const &from, Position const &to, bool cross_start, size_type path_size); + + /// Get the position of the initial point. + Position const &initialPosition() const { return _from; } + /// Get the position of the final point. + Position const &finalPosition() const { return _to; } + + Position const &from() const { return _from; } + Position const &to() const { return _to; } + + /// Check whether the interval has only one point. + bool isDegenerate() const { return _from == _to; } + /// True if the interval goes in the direction of decreasing time values. + bool reverse() const { return _reverse; } + /// True if the interior of the interval contains the initial point of the path. + bool crossesStart() const { return _cross_start; } + + /// Test a path position for inclusion. + bool contains(Position const &pos) const; + + /// Get a position at least @a min_dist away in parameter space from the ends. + /// If no such position exists, the middle point between these positions is returned. + Position inside(Coord min_dist = EPSILON) const; + + /// Select one of two intervals with given endpoints by parameter direction. + static PathInterval from_direction(Position const &from, Position const &to, + bool reversed, size_type path_size); + + /// Select one of two intervals with given endpoints by whether it includes the initial point. + static PathInterval from_start_crossing(Position const &from, Position const &to, + bool cross_start, size_type path_size) { + PathInterval result(from, to, cross_start, path_size); + return result; + } + + size_type pathSize() const { return _path_size; } private: - Iterator(Path &p, unsigned i) : Base(&p, i) {} - friend class ::Geom::Path; + Position _from, _to; + size_type _path_size; + bool _cross_start, _reverse; }; +/// Create an interval in the direction of increasing time value. +/// @relates PathInterval +inline PathInterval forward_interval(PathPosition const &from, PathPosition const &to, + PathInterval::size_type path_size) +{ + PathInterval result = PathInterval::from_direction(from, to, false, path_size); + return result; +} + +/// Create an interval in the direction of decreasing time value. +/// @relates PathInterval +inline PathInterval backward_interval(PathPosition const &from, PathPosition const &to, + PathInterval::size_type path_size) +{ + PathInterval result = PathInterval::from_direction(from, to, true, path_size); + return result; } -/* - * Open and closed paths: all paths, whether open or closed, store a final - * segment which connects the initial and final endpoints of the "real" - * path data. While similar to the "z" in an SVG path, it exists for - * both open and closed paths, and is not considered part of the "normal" - * path data, which is always covered by the range [begin(), end_open()). - * Conversely, the range [begin(), end_closed()) always contains the "extra" - * closing segment. +/// Output an interval in the path's domain. +/// @relates PathInterval +inline std::ostream &operator<<(std::ostream &os, PathInterval const &ival) { + os << "PathInterval["; + if (ival.crossesStart()) { + os << ival.from() << " -> 0: 0.0 -> " << ival.to(); + } else { + os << ival.from() << " -> " << ival.to(); + } + os << "]"; + return os; +} + +typedef Intersection<PathPosition> PathIntersection; + +template <> +struct ShapeTraits<Path> { + typedef PathPosition TimeType; + typedef PathInterval IntervalType; + typedef Path AffineClosureType; + typedef PathIntersection IntersectionType; +}; + +/** @brief Sequence of contiguous curves, aka spline. + * + * Path represents a sequence of contiguous curves, also known as a spline. + * It corresponds to a "subpath" in SVG terminology. It can represent both + * open and closed subpaths. The final point of each curve is exactly + * equal to the initial point of the next curve. + * + * The path always contains a linear closing segment that connects + * the final point of the last "real" curve to the initial point of the + * first curve. This way the curves form a closed loop even for open paths. + * If the closing segment has nonzero length and the path is closed, it is + * considered a normal part of the path data. There are three distinct sets + * of end iterators one can use to iterate over the segments: + * + * - Iterating between @a begin() and @a end() will iterate over segments + * which are part of the path. + * - Iterating between @a begin() and @a end_closed() + * will always iterate over a closed loop of segments. + * - Iterating between @a begin() and @a end_open() will always skip + * the closing segment. * - * The only difference between a closed and an open path is whether - * end_default() returns end_closed() or end_open(). The idea behind this - * is to let any path be stroked using [begin(), end_default()), and filled - * using [begin(), end_closed()), without requiring a separate "filled" version - * of the path to use for filling. + * Normally, an exception will be thrown when you try to insert a curve + * that makes the path non-continuous. If you are working with unsanitized + * curve data, you can call setStitching(true), which will insert line segments + * to make the path continuous. * - * \invariant : curves_ always contains at least one segment. The last segment - * is always of type ClosingSegment. All constructors take care of this. - (curves_.size() > 0) && dynamic_cast<ClosingSegment>(curves_.back()) - */ -class Path { + * Internally, Path uses copy-on-write data. This is done for two reasons: first, + * copying a Curve requires calling a virtual function, so it's a little more expensive + * that normal copying; and second, it reduces the memory cost of copying the path. + * Therefore you can return Path and PathVector from functions without worrying + * about temporary copies. + * + * Note that this class cannot represent arbitrary shapes, which may contain holes. + * To do that, use PathVector, which is more generic. + * + * It's not very convenient to create a Path directly. To construct paths more easily, + * use PathBuilder. + * + * @ingroup Paths */ +class Path + : boost::equality_comparable1< Path + , MultipliableNoncommutative< Path, Affine + > > +{ public: - typedef PathInternal::Sequence Sequence; - typedef PathInternal::Iterator iterator; - typedef PathInternal::ConstIterator const_iterator; - typedef Sequence::size_type size_type; - typedef Sequence::difference_type difference_type; + typedef PathPosition Position; + typedef PathInternal::Sequence Sequence; + typedef PathInternal::BaseIterator<Path> iterator; + typedef PathInternal::BaseIterator<Path const> const_iterator; + typedef Sequence::size_type size_type; + typedef Sequence::difference_type difference_type; + + class ClosingSegment : public LineSegment { + public: + ClosingSegment() : LineSegment() {} + ClosingSegment(Point const &p1, Point const &p2) : LineSegment(p1, p2) {} + virtual Curve *duplicate() const { return new ClosingSegment(*this); } + virtual Curve *reverse() const { return new ClosingSegment((*this)[1], (*this)[0]); } + }; + + class StitchSegment : public LineSegment { + public: + StitchSegment() : LineSegment() {} + StitchSegment(Point const &p1, Point const &p2) : LineSegment(p1, p2) {} + virtual Curve *duplicate() const { return new StitchSegment(*this); } + virtual Curve *reverse() const { return new StitchSegment((*this)[1], (*this)[0]); } + }; + + // Path(Path const &other) - use default copy constructor + + /// Construct an empty path starting at the specified point. + explicit Path(Point const &p = Point()) + : _curves(new Sequence()) + , _closing_seg(new ClosingSegment(p, p)) + , _closed(false) + , _exception_on_stitch(true) + { + _curves->push_back(_closing_seg); + } - class ClosingSegment : public LineSegment { - public: - ClosingSegment() : LineSegment() {} - ClosingSegment(Point const &p1, Point const &p2) : LineSegment(p1, p2) {} - virtual Curve *duplicate() const { return new ClosingSegment(*this); } - virtual Curve *reverse() const { return new ClosingSegment((*this)[1], (*this)[0]); } - }; - - enum Stitching { - NO_STITCHING=0, - STITCH_DISCONTINUOUS - }; - - class StitchSegment : public LineSegment { - public: - StitchSegment() : LineSegment() {} - StitchSegment(Point const &p1, Point const &p2) : LineSegment(p1, p2) {} - virtual Curve *duplicate() const { return new StitchSegment(*this); } - virtual Curve *reverse() const { return new StitchSegment((*this)[1], (*this)[0]); } - }; - - // Path(Path const &other) - use default copy constructor - - explicit Path(Point p=Point()) - : curves_(boost::shared_ptr<Sequence>(new Sequence(1, boost::shared_ptr<Curve>()))), - final_(new ClosingSegment(p, p)), - closed_(false) - { - get_curves().back() = boost::shared_ptr<Curve>(final_); - } - - Path(const_iterator const &first, - const_iterator const &last, - bool closed=false) - : curves_(boost::shared_ptr<Sequence>(new Sequence(seq_iter(first), - seq_iter(last)))), - closed_(closed) - { - if (!get_curves().empty()) { - final_ = new ClosingSegment(get_curves().back()->finalPoint(), - get_curves().front()->initialPoint()); - } else { - final_ = new ClosingSegment(); - } - get_curves().push_back(boost::shared_ptr<Curve>(final_)); - } - - virtual ~Path() {} - - // Path &operator=(Path const &other) - use default assignment operator - - /// \todo Add noexcept specifiers for C++11 - void swap(Path &other) { - using std::swap; - swap(other.curves_, curves_); - swap(other.final_, final_); - swap(other.closed_, closed_); - } - friend inline void swap(Path &a, Path &b) { a.swap(b); } - - Curve const &operator[](unsigned i) const { return *get_curves()[i]; } - Curve const &at_index(unsigned i) const { return *get_curves()[i]; } - boost::shared_ptr<Curve const> get_ref_at_index(unsigned i) { - return get_curves()[i]; - } - - Curve const &front() const { return *get_curves()[0]; } - Curve const &back() const { return back_open(); } - Curve const &back_open() const { - if (empty()) { THROW_RANGEERROR("Path contains not enough segments"); } - return *get_curves()[get_curves().size()-2]; - } - Curve const &back_closed() const { return *get_curves()[get_curves().size()-1]; } - Curve const &back_default() const { - return ( closed_ ? back_closed() : back_open() ); - } - - const_iterator begin() const { return const_iterator(*this, 0); } - const_iterator end() const { return const_iterator(*this, size()); } - iterator begin() { return iterator(*this, 0); } - iterator end() { return iterator(*this, size()); } - - const_iterator end_open() const { return const_iterator(*this, size()); } - const_iterator end_closed() const { return const_iterator(*this, size()+1); } - const_iterator end_default() const { - return ( closed_ ? end_closed() : end_open() ); - } - - size_type size_open() const { return get_curves().size()-1; } - size_type size_closed() const { return get_curves().size(); } - size_type size_default() const { - return ( closed_ ? size_closed() : size_open() ); - } - size_type size() const { return size_open(); } - - size_type max_size() const { return get_curves().max_size()-1; } - - bool empty() const { return (get_curves().size() == 1); } - bool closed() const { return closed_; } - void close(bool closed=true) { closed_ = closed; } - - OptRect boundsFast() const; - OptRect boundsExact() const; - - Piecewise<D2<SBasis> > toPwSb() const { - Piecewise<D2<SBasis> > ret; - ret.push_cut(0); - unsigned i = 1; - bool degenerate = true; - // pw<d2<>> is always open. so if path is closed, add closing segment as well to pwd2. - for(const_iterator it = begin(); it != end_default(); ++it) { - if (!it->isDegenerate()) { - ret.push(it->toSBasis(), i++); - degenerate = false; - } - } - if (degenerate) { - // if path only contains degenerate curves, no second cut is added - // so we need to create at least one segment manually - ret = Piecewise<D2<SBasis> >(initialPoint()); - } - return ret; - } - - bool operator==(Path const &other) const { - if (this == &other) return true; - if (closed_ != other.closed_) return false; - return get_curves() == other.get_curves(); - } - bool operator!=(Path const &other) const { - return !( *this == other ); - } - - Path operator*(Affine const &m) const { - Path ret(*this); - ret *= m; - return ret; - } - Path operator*(Translate const &m) const { // specialization over Affine, for faster computation - Path ret(*this); - ret *= m; - return ret; - } - - Path &operator*=(Affine const &m); - Path &operator*=(Translate const &m); // specialization over Affine, for faster computation - - Point pointAt(double t) const - { - unsigned int sz = size(); - if ( closed() ) ++sz; - if ( t < 0 || t > sz ) - { - THROW_RANGEERROR("parameter t out of bounds"); - } - if ( empty() ) return initialPoint(); // naked moveto - double k, lt = modf(t, &k); - unsigned int i = static_cast<unsigned int>(k); - if ( i == sz ) - { - --i; - lt = 1; - } - return (*this)[i].pointAt(lt); - } - - double valueAt(double t, Dim2 d) const - { - unsigned int sz = size(); - if ( closed() ) ++sz; - if ( t < 0 || t > sz ) - { - THROW_RANGEERROR("parameter t out of bounds"); - } - if ( empty() ) return initialPoint()[d]; // naked moveto - double k, lt = modf(t, &k); - unsigned int i = static_cast<unsigned int>(k); - if ( i == sz ) - { - --i; - lt = 1; - } - return (*this)[i].valueAt(lt, d); - } - - - Point operator() (double t) const - { - return pointAt(t); - } - - std::vector<double> roots(double v, Dim2 d) const { - std::vector<double> res; - for(unsigned i = 0; i <= size(); i++) { - std::vector<double> temp = (*this)[i].roots(v, d); - for(unsigned j = 0; j < temp.size(); j++) - res.push_back(temp[j] + i); - } - return res; - } - - std::vector<double> - allNearestPoints(Point const& _point, double from, double to) const; - - std::vector<double> - allNearestPoints(Point const& _point) const - { - unsigned int sz = size(); - if ( closed() ) ++sz; - return allNearestPoints(_point, 0, sz); - } - - std::vector<double> - nearestPointPerCurve(Point const& _point) const; - - double nearestPoint(Point const& _point, double from, double to, double *distance_squared = NULL) const; - - double nearestPoint(Point const& _point, double *distance_squared = NULL) const - { - unsigned int sz = size(); - if ( closed() ) ++sz; - return nearestPoint(_point, 0, sz, distance_squared); - } - - void appendPortionTo(Path &p, double f, double t) const; - - Path portion(double f, double t) const { - Path ret; - ret.close(false); - appendPortionTo(ret, f, t); - return ret; - } - Path portion(Interval i) const { return portion(i.min(), i.max()); } - - Path reverse() const { - Path ret(*this); - ret.unshare(); - for ( Sequence::iterator iter = ret.get_curves().begin() ; - iter != ret.get_curves().end()-1 ; ++iter ) + /// Construct a path containing a range of curves. + template <typename Iter> + Path(Iter first, Iter last, bool closed = false, bool stitch = false) + : _curves(new Sequence()) + , _closed(closed) + , _exception_on_stitch(!stitch) { - *iter = boost::shared_ptr<Curve>((*iter)->reverse()); - } - std::reverse(ret.get_curves().begin(), ret.get_curves().end()-1); - ret.final_ = static_cast<ClosingSegment *>(ret.final_->reverse()); - ret.get_curves().back() = boost::shared_ptr<Curve>(ret.final_); - return ret; - } - - void insert(iterator const &pos, - Curve const &curve, Stitching stitching=NO_STITCHING) - { - unshare(); - Sequence::iterator seq_pos(seq_iter(pos)); - Sequence source(1, boost::shared_ptr<Curve>(curve.duplicate())); - if (stitching) stitch(seq_pos, seq_pos, source); - do_update(seq_pos, seq_pos, source.begin(), source.end()); - } - - void insert(iterator const &pos, - const_iterator const &first, - const_iterator const &last, - Stitching stitching=NO_STITCHING) - { - unshare(); - Sequence::iterator seq_pos(seq_iter(pos)); - Sequence source(seq_iter(first), seq_iter(last)); - if (stitching) stitch(seq_pos, seq_pos, source); - do_update(seq_pos, seq_pos, source.begin(), source.end()); - } - - void clear() { - unshare(); - do_update(get_curves().begin(), get_curves().end()-1, - get_curves().begin(), get_curves().begin()); - } - - void erase(iterator const &pos, Stitching stitching=NO_STITCHING) { - unshare(); - Sequence::iterator seq_pos(seq_iter(pos)); - if (stitching) { - Sequence stitched; - stitch(seq_pos, seq_pos+1, stitched); - do_update(seq_pos, seq_pos+1, stitched.begin(), stitched.end()); - } else { - do_update(seq_pos, seq_pos+1, get_curves().begin(), get_curves().begin()); - } - } - - void erase(iterator const &first, - iterator const &last, - Stitching stitching=NO_STITCHING) - { - unshare(); - Sequence::iterator seq_first=seq_iter(first); - Sequence::iterator seq_last=seq_iter(last); - if (stitching) { - Sequence stitched; - stitch(seq_first, seq_last, stitched); - do_update(seq_first, seq_last, stitched.begin(), stitched.end()); - } else { - do_update(seq_first, seq_last, - get_curves().begin(), get_curves().begin()); - } - } - - // erase last segment of path - void erase_last() { - erase(iterator(*this, size()-1)); - } - - void replace(iterator const &replaced, - Curve const &curve, - Stitching stitching=NO_STITCHING) - { - unshare(); - Sequence::iterator seq_replaced(seq_iter(replaced)); - Sequence source(1, boost::shared_ptr<Curve>(curve.duplicate())); - if (stitching) stitch(seq_replaced, seq_replaced+1, source); - do_update(seq_replaced, seq_replaced+1, source.begin(), source.end()); - } - - void replace(iterator const &first_replaced, - iterator const &last_replaced, - Curve const &curve, Stitching stitching=NO_STITCHING) - { - unshare(); - Sequence::iterator seq_first_replaced(seq_iter(first_replaced)); - Sequence::iterator seq_last_replaced(seq_iter(last_replaced)); - Sequence source(1, boost::shared_ptr<Curve>(curve.duplicate())); - if (stitching) stitch(seq_first_replaced, seq_last_replaced, source); - do_update(seq_first_replaced, seq_last_replaced, - source.begin(), source.end()); - } - - void replace(iterator const &replaced, - const_iterator const &first, - const_iterator const &last, - Stitching stitching=NO_STITCHING) - { - unshare(); - Sequence::iterator seq_replaced(seq_iter(replaced)); - Sequence source(seq_iter(first), seq_iter(last)); - if (stitching) stitch(seq_replaced, seq_replaced+1, source); - do_update(seq_replaced, seq_replaced+1, source.begin(), source.end()); - } - - void replace(iterator const &first_replaced, - iterator const &last_replaced, - const_iterator const &first, - const_iterator const &last, - Stitching stitching=NO_STITCHING) - { - unshare(); - Sequence::iterator seq_first_replaced(seq_iter(first_replaced)); - Sequence::iterator seq_last_replaced(seq_iter(last_replaced)); - Sequence source(seq_iter(first), seq_iter(last)); - if (stitching) stitch(seq_first_replaced, seq_last_replaced, source); - do_update(seq_first_replaced, seq_last_replaced, - source.begin(), source.end()); - } - - void start(Point p) { - clear(); - final_->setPoint(0, p); - final_->setPoint(1, p); - } - - Point initialPoint() const { return (*final_)[1]; } - Point finalPoint() const { return (*final_)[0]; } - - void setInitial(Point const& p) - { - if ( empty() ) return; - unshare(); - boost::shared_ptr<Curve> head(front().duplicate()); - head->setInitial(p); - Sequence::iterator replaced = get_curves().begin(); - Sequence source(1, head); - do_update(replaced, replaced + 1, source.begin(), source.end()); - } - - void setFinal(Point const& p) - { - if ( empty() ) return; - unshare(); - boost::shared_ptr<Curve> tail(back().duplicate()); - tail->setFinal(p); - Sequence::iterator replaced = get_curves().end() - 2; - Sequence source(1, tail); - do_update(replaced, replaced + 1, source.begin(), source.end()); - } - - void append(Curve const &curve, Stitching stitching=NO_STITCHING) { - unshare(); - if (stitching) stitchTo(curve.initialPoint()); - do_append(curve.duplicate()); - } - void append(D2<SBasis> const &curve, Stitching stitching=NO_STITCHING) { - unshare(); - if (stitching) stitchTo(Point(curve[X][0][0], curve[Y][0][0])); - do_append(new SBasisCurve(curve)); - } - void append(Path const &other, Stitching stitching=NO_STITCHING) { - insert(end(), other.begin(), other.end(), stitching); - } - - void stitchTo(Point const &p) { - if (!empty() && finalPoint() != p) { - unshare(); - do_append(new StitchSegment(finalPoint(), p)); - } - } - - - /** - * It is important to note that the coordinates passed to appendNew should be finite! - * If one of the coordinates is infinite, 2geom will throw a ContinuityError exception. - */ - - template <typename CurveType, typename A> - void appendNew(A a) { - unshare(); - do_append(new CurveType(finalPoint(), a)); - } - - template <typename CurveType, typename A, typename B> - void appendNew(A a, B b) { - unshare(); - do_append(new CurveType(finalPoint(), a, b)); - } - - template <typename CurveType, typename A, typename B, typename C> - void appendNew(A a, B b, C c) { - unshare(); - do_append(new CurveType(finalPoint(), a, b, c)); - } - - template <typename CurveType, typename A, typename B, typename C, - typename D> - void appendNew(A a, B b, C c, D d) { - unshare(); - do_append(new CurveType(finalPoint(), a, b, c, d)); - } - - template <typename CurveType, typename A, typename B, typename C, - typename D, typename E> - void appendNew(A a, B b, C c, D d, E e) { - unshare(); - do_append(new CurveType(finalPoint(), a, b, c, d, e)); - } - - template <typename CurveType, typename A, typename B, typename C, - typename D, typename E, typename F> - void appendNew(A a, B b, C c, D d, E e, F f) { - unshare(); - do_append(new CurveType(finalPoint(), a, b, c, d, e, f)); - } - - template <typename CurveType, typename A, typename B, typename C, - typename D, typename E, typename F, - typename G> - void appendNew(A a, B b, C c, D d, E e, F f, G g) { - unshare(); - do_append(new CurveType(finalPoint(), a, b, c, d, e, f, g)); - } - - template <typename CurveType, typename A, typename B, typename C, - typename D, typename E, typename F, - typename G, typename H> - void appendNew(A a, B b, C c, D d, E e, F f, G g, H h) { - unshare(); - do_append(new CurveType(finalPoint(), a, b, c, d, e, f, g, h)); - } - - template <typename CurveType, typename A, typename B, typename C, - typename D, typename E, typename F, - typename G, typename H, typename I> - void appendNew(A a, B b, C c, D d, E e, F f, G g, H h, I i) { - unshare(); - do_append(new CurveType(finalPoint(), a, b, c, d, e, f, g, h, i)); - } + for (Iter i = first; i != last; ++i) { + _curves->push_back(i->duplicate()); + } + if (!_curves->empty()) { + _closing_seg = new ClosingSegment(_curves->back().finalPoint(), + _curves->front().initialPoint()); + } else { + _closing_seg = new ClosingSegment(); + } + _curves->push_back(_closing_seg); + } + + /// Construct a path from a convex hull. + Path(ConvexHull const &); + + virtual ~Path() {} + + // Path &operator=(Path const &other) - use default assignment operator + + /** @brief Swap contents with another path + * @todo Add noexcept specifiers for C++11 */ + void swap(Path &other) throw() { + using std::swap; + swap(other._curves, _curves); + swap(other._closing_seg, _closing_seg); + swap(other._closed, _closed); + } + /** @brief Swap contents of two paths. + * @relates Path */ + friend inline void swap(Path &a, Path &b) throw() { a.swap(b); } + + /** @brief Access a curve by index */ + Curve const &operator[](size_type i) const { return (*_curves)[i]; } + /** @brief Access a curve by index */ + Curve const &at(size_type i) const { return _curves->at(i); } + + /** @brief Access the first curve in the path. + * Since the curve always contains at least a degenerate closing segment, + * it is always safe to use this method. */ + Curve const &front() const { return _curves->front(); } + /** @brief Access the last curve in the path. */ + Curve const &back() const { return back_default(); } + Curve const &back_open() const { + if (empty()) return _curves->back(); + return (*_curves)[_curves->size() - 2]; + } + Curve const &back_closed() const { + return _closing_seg->isDegenerate() + ? (*_curves)[_curves->size() - 2] + : (*_curves)[_curves->size() - 1]; + } + Curve const &back_default() const { return (_closed ? back_closed() : back_open()); } + + const_iterator begin() const { return const_iterator(*this, 0); } + const_iterator end() const { return end_default(); } + const_iterator end_default() const { return const_iterator(*this, size_default()); } + const_iterator end_open() const { return const_iterator(*this, size_open()); } + const_iterator end_closed() const { return const_iterator(*this, size_closed()); } + iterator begin() { return iterator(*this, 0); } + iterator end() { return end_default(); } + iterator end_default() { return iterator(*this, size_default()); } + iterator end_open() { return iterator(*this, size_open()); } + iterator end_closed() { return iterator(*this, size_closed()); } + + /// Size without the closing segment, even if the path is closed. + size_type size_open() const { return _curves->size() - 1; } + + /** @brief Size with the closing segment, if it makes a difference. + * If the closing segment is degenerate, i.e. its initial and final points + * are exactly equal, then it is not included in this size. */ + size_type size_closed() const { + return _closing_seg->isDegenerate() ? _curves->size() - 1 : _curves->size(); + } + + /// Natural size of the path. + size_type size_default() const { + return _includesClosingSegment() ? size_closed() : size_open(); + } + /// Natural size of the path. + size_type size() const { return size_default(); } + + size_type max_size() const { return _curves->max_size() - 1; } + + /** @brief Check whether path is empty. + * The path is empty if it contains only the closing segment, which according + * to the continuity invariant must be degenerate. Note that unlike standard + * containers, two empty paths are not necessarily identical, because the + * degenerate closing segment may be at a different point, affecting the operation + * of methods such as appendNew(). */ + bool empty() const { return (_curves->size() == 1); } + + /// Check whether the path is closed. + bool closed() const { return _closed; } + + /// Set whether the path is closed. + void close(bool closed = true) { _closed = closed; } + + /** @brief Remove all curves from the path. + * The initial and final points of the closing segment are set to (0,0). + * The stitching flag remains unchanged. */ + void clear(); + + /** @brief Get the approximate bounding box. + * The rectangle returned by this method will contain all the curves, but it's not + * guaranteed to be the smallest possible one */ + OptRect boundsFast() const; + + /** @brief Get a tight-fitting bounding box. + * This will return the smallest possible axis-aligned rectangle containing + * all the curves in the path. */ + OptRect boundsExact() const; + + Piecewise<D2<SBasis> > toPwSb() const; + + /// Test paths for exact equality. + bool operator==(Path const &other) const; + + /// Apply an affine transform. + Path &operator*=(Affine const &m); + + /** @brief Get the allowed range of time values. + * @return Values for which pointAt() and valueAt() yield valid results. */ + Interval timeRange() const; + + /** Get the curve at the specified time value. + * @param t Time value + * @param rest Optional storage for the corresponding time value in the curve */ + Curve const &curveAt(Coord t, Coord *rest = NULL) const; + + /// Get the closing segment of the path. + LineSegment const &closingSegment() const { return *_closing_seg; } + + /** @brief Get the point at the specified time value. + * Note that this method has reduced precision with respect to calling pointAt() + * directly on the curve. If you want high precision results, use the version + * that takes a Position parameter. + * + * Allowed time values range from zero to the number of curves; you can retrieve + * the allowed range of values with timeRange(). */ + Point pointAt(Coord t) const; + + /// Get one coordinate (X or Y) at the specified time value. + Coord valueAt(Coord t, Dim2 d) const; + + /// Get the curve at the specified position. + Curve const &curveAt(Position const &pos) const; + /// Get the point at the specified position. + Point pointAt(Position const &pos) const; + /// Get one coordinate at the specified position. + Coord valueAt(Position const &pos, Dim2 d) const; + + Point operator()(Coord t) const { return pointAt(t); } + + /// Compute intersections with axis-aligned line. + std::vector<Position> roots(Coord v, Dim2 d) const; + + /// Compute intersections with another path. + std::vector<PathIntersection> intersect(Path const &other, Coord precision = EPSILON) const; + + /** @brief Determine the winding number at the specified point. + * + * The winding number is the number of full turns made by a ray that connects the passed + * point and the path's value (i.e. the result of the pointAt() method) as the time increases + * from 0 to the maximum valid value. Positive numbers indicate turns in the direction + * of increasing angles. + * + * Winding numbers are often used as the definition of what is considered "inside" + * the shape. Typically points with either nonzero winding or odd winding are + * considered to be inside the path. */ + int winding(Point const &p) const; + + std::vector<Coord> allNearestTimes(Point const &p, Coord from, Coord to) const; + std::vector<Coord> allNearestTimes(Point const &p) const { + return allNearestTimes(p, 0, size_default()); + } + + Coord nearestTime(Point const &p, Coord *dist = NULL) const; + std::vector<Coord> nearestTimePerCurve(Point const &p) const; + Position nearestPosition(Point const &p, Coord *dist = NULL) const; + + void appendPortionTo(Path &p, Coord f, Coord t) const; + + /** @brief Append a subset of this path to another path. + * An extra stitching segment will be inserted if the start point of the portion + * and the final point of the target path do not match exactly. + * The closing segment of the target path will be modified. */ + void appendPortionTo(Path &p, Position const &from, Position const &to, bool cross_start = false) const { + PathInterval ival(from, to, cross_start, size_closed()); + appendPortionTo(p, ival, boost::none, boost::none); + } + + /** @brief Append a subset of this path to another path. + * This version allows you to explicitly pass a PathInterval. */ + void appendPortionTo(Path &p, PathInterval const &ival) const { + appendPortionTo(p, ival, boost::none, boost::none); + } + + /** @brief Append a subset of this path to another path, specifying endpoints. + * This method is for use in situations where endpoints of the portion segments + * have to be set exactly, for instance when computing Boolean operations. */ + void appendPortionTo(Path &p, PathInterval const &ival, + boost::optional<Point> const &p_from, boost::optional<Point> const &p_to) const; + + Path portion(Coord f, Coord t) const { + Path ret; + ret.close(false); + appendPortionTo(ret, f, t); + return ret; + } + + Path portion(Interval const &i) const { return portion(i.min(), i.max()); } + + /** @brief Get a subset of the current path with full precision. + * When @a from is larger (later in the path) than @a to, the returned portion + * will be reversed. If @a cross_start is true, the portion will be reversed + * and will cross the initial point of the path. Therefore, when @a from is larger + * than @a to and @a cross_start is true, the returned portion will not be reversed, + * but will "wrap around" the end of the path. */ + Path portion(Position const &from, Position const &to, bool cross_start = false) const { + Path ret; + ret.close(false); + appendPortionTo(ret, from, to, cross_start); + return ret; + } + + /** @brief Get a subset of the current path with full precision. + * This version allows you to explicitly pass a PathInterval. */ + Path portion(PathInterval const &ival) const { + Path ret; + ret.close(false); + appendPortionTo(ret, ival); + return ret; + } + + /** @brief Obtain a reversed version of the current path. + * The final point of the current path will become the initial point + * of the reversed path, unless it is closed and has a non-degenerate + * closing segment. In that case, the new initial point will be the final point + * of the last "real" segment. */ + Path reversed() const; + + void insert(iterator pos, Curve const &curve); + + template <typename Iter> + void insert(iterator pos, Iter first, Iter last) { + _unshare(); + Sequence::iterator seq_pos(seq_iter(pos)); + Sequence source; + for (; first != last; ++first) { + source.push_back(first->duplicate()); + } + do_update(seq_pos, seq_pos, source); + } + + void erase(iterator pos); + void erase(iterator first, iterator last); + + // erase last segment of path + void erase_last() { erase(iterator(*this, size() - 1)); } + + void start(Point const &p); + + /** @brief Get the first point in the path. */ + Point initialPoint() const { return (*_closing_seg)[1]; } + + /** @brief Get the last point in the path. + * If the path is closed, this is always the same as the initial point. */ + Point finalPoint() const { return (*_closing_seg)[_closed ? 1 : 0]; } + + void setInitial(Point const &p) { + _unshare(); + _closed = false; + _curves->front().setInitial(p); + _closing_seg->setFinal(p); + } + void setFinal(Point const &p) { + _unshare(); + _closed = false; + (*_curves)[size_open() - 1].setFinal(p); + _closing_seg->setInitial(p); + } + + /** @brief Add a new curve to the end of the path. + * This inserts the new curve right before the closing segment. + * The path takes ownership of the passed pointer, which should not be freed. */ + void append(Curve *curve) { + _unshare(); + stitchTo(curve->initialPoint()); + do_append(curve); + } + + void append(Curve const &curve) { + _unshare(); + stitchTo(curve.initialPoint()); + do_append(curve.duplicate()); + } + void append(D2<SBasis> const &curve) { + _unshare(); + stitchTo(Point(curve[X][0][0], curve[Y][0][0])); + do_append(new SBasisCurve(curve)); + } + void append(Path const &other) { + replace(end_open(), other.begin(), other.end()); + } + + void replace(iterator replaced, Curve const &curve); + void replace(iterator first, iterator last, Curve const &curve); + void replace(iterator replaced, Path const &path); + void replace(iterator first, iterator last, Path const &path); + + template <typename Iter> + void replace(iterator replaced, Iter first, Iter last) { + replace(replaced, replaced + 1, first, last); + } + + template <typename Iter> + void replace(iterator first_replaced, iterator last_replaced, Iter first, Iter last) { + _unshare(); + Sequence::iterator seq_first_replaced(seq_iter(first_replaced)); + Sequence::iterator seq_last_replaced(seq_iter(last_replaced)); + Sequence source; + for (; first != last; ++first) { + source.push_back(first->duplicate()); + } + do_update(seq_first_replaced, seq_last_replaced, source); + } + + /** @brief Append a new curve to the path. + * + * This family of methods will automaticaly use the current final point of the path + * as the first argument of the new curve's constructor. To call this method, + * you'll need to write e.g.: + * @code + path.template appendNew<CubicBezier>(control1, control2, end_point); + @endcode + * It is important to note that the coordinates passed to appendNew should be finite! + * If one of the coordinates is infinite, 2geom will throw a ContinuityError exception. + */ + template <typename CurveType, typename A> + void appendNew(A a) { + _unshare(); + do_append(new CurveType(finalPoint(), a)); + } + + template <typename CurveType, typename A, typename B> + void appendNew(A a, B b) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b)); + } + + template <typename CurveType, typename A, typename B, typename C> + void appendNew(A a, B b, C c) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D> + void appendNew(A a, B b, C c, D d) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D, typename E> + void appendNew(A a, B b, C c, D d, E e) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d, e)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D, typename E, typename F> + void appendNew(A a, B b, C c, D d, E e, F f) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d, e, f)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D, typename E, typename F, typename G> + void appendNew(A a, B b, C c, D d, E e, F f, G g) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d, e, f, g)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D, typename E, typename F, typename G, + typename H> + void appendNew(A a, B b, C c, D d, E e, F f, G g, H h) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d, e, f, g, h)); + } + + template <typename CurveType, typename A, typename B, typename C, typename D, typename E, typename F, typename G, + typename H, typename I> + void appendNew(A a, B b, C c, D d, E e, F f, G g, H h, I i) { + _unshare(); + do_append(new CurveType(finalPoint(), a, b, c, d, e, f, g, h, i)); + } + + /// Append a stitching segment ending at the specified point. + void stitchTo(Point const &p); + + /** @brief Verify the continuity invariant. + * If the path is not contiguous, this will throw a CountinuityError. */ + void checkContinuity() const; + + /** @brief Enable or disable the throwing of exceptions when stitching discontinuities. + * Normally stitching will cause exceptions, but when you are working with unsanitized + * curve data, you can disable these exceptions. */ + void setStitching(bool x) { + _exception_on_stitch = !x; + } private: - static Sequence::iterator seq_iter(iterator const &iter) { - return iter.path->get_curves().begin() + iter.index; - } - static Sequence::const_iterator seq_iter(const_iterator const &iter) { - return iter.path->get_curves().begin() + iter.index; - } - - Sequence &get_curves() { return *curves_; } - Sequence const &get_curves() const { return *curves_; } - - void unshare() { - if (!curves_.unique()) { - curves_ = boost::shared_ptr<Sequence>(new Sequence(*curves_)); - } - if (!get_curves().back().unique()) { - final_ = static_cast<ClosingSegment *>(final_->duplicate()); - get_curves().back() = boost::shared_ptr<Curve>(final_); - } - } - - void stitch(Sequence::iterator first_replaced, - Sequence::iterator last_replaced, - Sequence &sequence); - - void do_update(Sequence::iterator first_replaced, - Sequence::iterator last_replaced, - Sequence::iterator first, - Sequence::iterator last); - - // n.b. takes ownership of curve object - void do_append(Curve *curve); - - void check_continuity(Sequence::iterator first_replaced, - Sequence::iterator last_replaced, - Sequence::iterator first, - Sequence::iterator last); - - boost::shared_ptr<Sequence> curves_; - ClosingSegment *final_; - bool closed_; -}; // end class Path - -inline static Piecewise<D2<SBasis> > paths_to_pw(std::vector<Path> paths) { - Piecewise<D2<SBasis> > ret = paths[0].toPwSb(); - for(unsigned i = 1; i < paths.size(); i++) { - ret.concat(paths[i].toPwSb()); - } - return ret; -} + static Sequence::iterator seq_iter(iterator const &iter) { + return iter.path->_curves->begin() + iter.index; + } + static Sequence::const_iterator seq_iter(const_iterator const &iter) { + return iter.path->_curves->begin() + iter.index; + } -inline -Coord nearest_point(Point const& p, Path const& c) -{ - return c.nearestPoint(p); -} + // whether the closing segment is part of the path + bool _includesClosingSegment() const { + return _closed && !_closing_seg->isDegenerate(); + } + void _unshare() { + if (!_curves.unique()) { + _curves.reset(new Sequence(*_curves)); + _closing_seg = static_cast<ClosingSegment*>(&_curves->back()); + } + } + Position _getPosition(Coord t) const; + + void stitch(Sequence::iterator first_replaced, Sequence::iterator last_replaced, Sequence &sequence); + void do_update(Sequence::iterator first, Sequence::iterator last, Sequence &source); + + // n.b. takes ownership of curve object + void do_append(Curve *curve); + + boost::shared_ptr<Sequence> _curves; + ClosingSegment *_closing_seg; + bool _closed; + bool _exception_on_stitch; +}; // end class Path + +Piecewise<D2<SBasis> > paths_to_pw(PathVector const &paths); + +inline Coord nearest_time(Point const &p, Path const &c) { return c.nearestTime(p); } -} // end namespace Geom +} // end namespace Geom #endif // LIB2GEOM_SEEN_PATH_H diff --git a/src/2geom/pathvector.cpp b/src/2geom/pathvector.cpp index fc0ad75c4..36364de9b 100644 --- a/src/2geom/pathvector.cpp +++ b/src/2geom/pathvector.cpp @@ -1,12 +1,11 @@ -/* - * PathVector - std::vector containing Geom::Path - * This file provides a set of operations that can be performed on PathVector, - * e.g. an affine transform. - * +/** @file + * @brief PathVector - a sequence of subpaths + *//* * Authors: - * Johan Engelen <goejendaagh@zonnet.nl> + * Johan Engelen <goejendaagh@zonnet.nl> + * Krzysztof Kosiński <tweenk.pl@gmail.com> * - * Copyright 2008 authors + * Copyright 2008-2014 Authors * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -32,9 +31,6 @@ * the specific language governing rights and limitations. */ -#ifndef SEEN_GEOM_PATHVECTOR_CPP -#define SEEN_GEOM_PATHVECTOR_CPP - #include <2geom/pathvector.h> #include <2geom/path.h> @@ -42,104 +38,184 @@ namespace Geom { -// TODO: see which of these functions can be inlined for optimization +//PathVector &PathVector::operator+=(PathVector const &other); + +PathVector::size_type PathVector::curveCount() const +{ + size_type n = 0; + for (const_iterator it = begin(); it != end(); ++it) { + n += it->size_default(); + } + return n; +} + +void PathVector::reverse(bool reverse_paths) +{ + if (reverse_paths) { + std::reverse(begin(), end()); + } + for (iterator i = begin(); i != end(); ++i) { + *i = i->reversed(); + } +} -/** - * Reverses all Paths and the order of paths in the vector as well - **/ -PathVector reverse_paths_and_order (PathVector const & path_in) +PathVector PathVector::reversed(bool reverse_paths) const { - PathVector path_out; - for (PathVector::const_reverse_iterator it = path_in.rbegin(); it != path_in.rend(); ++it) { - path_out.push_back( (*it).reverse() ); + PathVector ret; + for (const_iterator i = begin(); i != end(); ++i) { + ret.push_back(i->reversed()); + } + if (reverse_paths) { + std::reverse(ret.begin(), ret.end()); } - return path_out; + return ret; } -OptRect bounds_fast( PathVector const& pv ) +Path &PathVector::pathAt(Coord t, Coord *rest) +{ + return const_cast<Path &>(static_cast<PathVector const*>(this)->pathAt(t, rest)); +} +Path const &PathVector::pathAt(Coord t, Coord *rest) const +{ + Position pos = _getPosition(t); + if (rest) { + *rest = Coord(pos.curve_index) + pos.t; + } + return at(pos.path_index); +} +Curve const &PathVector::curveAt(Coord t, Coord *rest) const +{ + Position pos = _getPosition(t); + if (rest) { + *rest = pos.t; + } + return at(pos.path_index).at(pos.curve_index); +} +Coord PathVector::valueAt(Coord t, Dim2 d) const +{ + Position pos = _getPosition(t); + return at(pos.path_index).at(pos.curve_index).valueAt(pos.t, d); +} +Point PathVector::pointAt(Coord t) const +{ + Position pos = _getPosition(t); + return at(pos.path_index).at(pos.curve_index).pointAt(pos.t); +} + +OptRect PathVector::boundsFast() const { - typedef PathVector::const_iterator const_iterator; - OptRect bound; - if (pv.empty()) return bound; - - bound = (pv.begin())->boundsFast(); - for (const_iterator it = ++(pv.begin()); it != pv.end(); ++it) - { + if (empty()) return bound; + + bound = front().boundsFast(); + for (const_iterator it = ++begin(); it != end(); ++it) { bound.unionWith(it->boundsFast()); } return bound; } -OptRect bounds_exact( PathVector const& pv ) +OptRect PathVector::boundsExact() const { - typedef PathVector::const_iterator const_iterator; - OptRect bound; - if (pv.empty()) return bound; - - bound = (pv.begin())->boundsExact(); - for (const_iterator it = ++(pv.begin()); it != pv.end(); ++it) - { + if (empty()) return bound; + + bound = front().boundsExact(); + for (const_iterator it = ++begin(); it != end(); ++it) { bound.unionWith(it->boundsExact()); } return bound; } -/* Note: undefined for empty pathvectors or pathvectors with empty paths. - * */ -boost::optional<PathVectorPosition> nearestPoint(PathVector const & path_in, Point const& _point, double *distance_squared) +std::vector<PVIntersection> PathVector::intersect(PathVector const &other, Coord precision) const { - boost::optional<PathVectorPosition> retval; - - double mindsq = infinity(); - unsigned int i = 0; - for (Geom::PathVector::const_iterator pit = path_in.begin(); pit != path_in.end(); ++pit) { - double dsq; - double t = pit->nearestPoint(_point, &dsq); - //std::cout << t << "," << dsq << std::endl; - if (dsq < mindsq) { - mindsq = dsq; - retval = PathVectorPosition(i, t); + typedef PathVectorPosition PVPos; + std::vector<PVIntersection> result; + for (std::size_t i = 0; i < size(); ++i) { + for (std::size_t j = 0; j < other.size(); ++j) { + std::vector<PathIntersection> xs = (*this)[i].intersect(other[j]); + for (std::size_t k = 0; k < xs.size(); ++k) { + PVIntersection pvx(PVPos(i, xs[k].first), PVPos(j, xs[k].second), xs[k].point()); + result.push_back(pvx); + } } + } + return result; +} - ++i; +int PathVector::winding(Point const &p) const +{ + int wind = 0; + for (const_iterator i = begin(); i != end(); ++i) { + wind += i->winding(p); } + return wind; +} - if (distance_squared) { - *distance_squared = mindsq; +boost::optional<PathVectorPosition> PathVector::nearestPosition(Point const &p, Coord *dist) const +{ + boost::optional<Position> retval; + + Coord mindist = infinity(); + for (size_type i = 0; i < size(); ++i) { + Coord d; + PathPosition pos = (*this)[i].nearestPosition(p, &d); + if (d < mindist) { + mindist = d; + retval = Position(i, pos.curve_index, pos.t); + } + } + + if (dist) { + *dist = mindist; } return retval; } -std::vector<PathVectorPosition> allNearestPoints(PathVector const & path_in, Point const& _point, double *distance_squared) +std::vector<PathVectorPosition> PathVector::allNearestPositions(Point const &p, Coord *dist) const { - std::vector<PathVectorPosition> retval; - - double mindsq = infinity(); - unsigned int i = 0; - for (Geom::PathVector::const_iterator pit = path_in.begin(); pit != path_in.end(); ++pit) { - double dsq; - double t = pit->nearestPoint(_point, &dsq); - if (dsq < mindsq) { - mindsq = dsq; - retval.push_back(PathVectorPosition(i, t)); + std::vector<Position> retval; + + Coord mindist = infinity(); + for (size_type i = 0; i < size(); ++i) { + Coord d; + PathPosition pos = (*this)[i].nearestPosition(p, &d); + if (d < mindist) { + mindist = d; + retval.clear(); + } + if (d <= mindist) { + retval.push_back(Position(i, pos.curve_index, pos.t)); } - - ++i; } - if (distance_squared) { - *distance_squared = mindsq; + if (dist) { + *dist = mindist; } return retval; +} +PathVectorPosition PathVector::_getPosition(Coord t) const +{ + Position ret; + Coord rest = 0; + ret.t = modf(t, &rest); + ret.curve_index = rest; + for (; ret.path_index < size(); ++ret.path_index) { + unsigned s = _data.at(ret.path_index).size_default(); + if (s > ret.curve_index) break; + // special case for the last point + if (s == ret.curve_index && ret.path_index + 1 == size()) { + --ret.curve_index; + ret.t = 1; + break; + } + ret.curve_index -= s; + } + return ret; } } // namespace Geom -#endif // SEEN_GEOM_PATHVECTOR_CPP - /* Local Variables: mode:c++ diff --git a/src/2geom/pathvector.h b/src/2geom/pathvector.h index e875e915f..6765d6bc0 100644 --- a/src/2geom/pathvector.h +++ b/src/2geom/pathvector.h @@ -1,13 +1,11 @@ -/** - * \file - * \brief PathVector - std::vector containing Geom::Path. - * This file provides a set of operations that can be performed on PathVector, - * e.g. an affine transform. +/** @file + * @brief PathVector - a sequence of subpaths *//* * Authors: - * Johan Engelen <goejendaagh@zonnet.nl> + * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl> + * Krzysztof Kosiński <tweenk.pl@gmail.com> * - * Copyright 2008 authors + * Copyright 2008-2014 authors * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public @@ -36,93 +34,247 @@ #ifndef LIB2GEOM_SEEN_PATHVECTOR_H #define LIB2GEOM_SEEN_PATHVECTOR_H +#include <boost/concept/requires.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/range/algorithm/equal.hpp> #include <2geom/forward.h> #include <2geom/path.h> #include <2geom/transforms.h> namespace Geom { -typedef std::vector<Geom::Path> PathVector; - -/* general path transformation: */ -inline -void operator*= (PathVector & path_in, Affine const &m) { - for(PathVector::iterator it = path_in.begin(); it != path_in.end(); ++it) { - (*it) *= m; - } -} -inline -PathVector operator*(PathVector const & path_in, Affine const &m) { - PathVector ret(path_in); - ret *= m; - return ret; -} - -/* specific path transformations: Translation: - * This makes it possible to make optimized implementations for Translate transforms */ -inline -void operator*= (PathVector & path_in, Translate const &m) { - for(PathVector::iterator it = path_in.begin(); it != path_in.end(); ++it) { - (*it) *= m; - } -} -inline -PathVector operator*(PathVector const & path_in, Translate const &m) { - PathVector ret(path_in); - ret *= m; - return ret; -} - -/* user friendly approach to Translate transforms: just add an offset Point to the whole path */ -inline -void operator+=(PathVector &path_in, Point const &p) { - for(PathVector::iterator it = path_in.begin(); it != path_in.end(); ++it) { - (*it) *= Translate(p); - } -} -inline -PathVector operator+(PathVector const &path_in, Point const &p) { - PathVector ret(path_in); - ret *= Translate(p); - return ret; -} - -inline -Geom::Point initialPoint(PathVector const &path_in) +/** @brief Position (generalized time value) in the path vector. + * + * This class exists because mapping the range of multiple curves onto the same interval + * as the curve index, we lose some precision. For instance, a path with 16 curves will + * have 4 bits less precision than a path with 1 curve. If you need high precision results + * in long paths, use this class and related methods instead of the standard methods + * pointAt(), nearestTime() and so on. + * + * @ingroup Paths */ +struct PathVectorPosition + : public PathPosition + , boost::totally_ordered<PathVectorPosition> { - return path_in.front().initialPoint(); -} + size_type path_index; ///< Index of the path in the vector -inline -Geom::Point finalPoint(PathVector const &path_in) -{ - return path_in.back().finalPoint(); -} - -PathVector reverse_paths_and_order (PathVector const & path_in); - -OptRect bounds_fast( PathVector const & pv ); -OptRect bounds_exact( PathVector const & pv ); - -struct PathVectorPosition { - // pathvector[path_nr].pointAt(t) is the position - unsigned int path_nr; - double t; - PathVectorPosition() : - path_nr(0), - t(0) - {} - PathVectorPosition(unsigned int path_nr, - double t) : path_nr(path_nr), t(t) {} + PathVectorPosition() : PathPosition(0, 0), path_index(0) {} + PathVectorPosition(size_type _i, size_type _c, Coord _t) + : PathPosition(_c, _t), path_index(_i) {} + PathVectorPosition(size_type _i, PathPosition const &pos) + : PathPosition(pos), path_index(_i) {} + + bool operator<(PathVectorPosition const &other) const { + if (path_index < other.path_index) return true; + if (path_index == other.path_index) { + return static_cast<PathPosition const &>(*this) < static_cast<PathPosition const &>(other); + } + return false; + } + bool operator==(PathVectorPosition const &other) const { + return path_index == other.path_index + && static_cast<PathPosition const &>(*this) == static_cast<PathPosition const &>(other); + } + + PathPosition const &asPathPosition() const { + return *static_cast<PathPosition const *>(this); + } +}; + +typedef Intersection<PathVectorPosition> PathVectorIntersection; +typedef PathVectorIntersection PVIntersection; ///< Alias to save typing + +template <> +struct ShapeTraits<PathVector> { + typedef PathVectorPosition TimeType; + //typedef PathVectorInterval IntervalType; + typedef PathVector AffineClosureType; + typedef PathVectorIntersection IntersectionType; }; -boost::optional<PathVectorPosition> nearestPoint(PathVector const & path_in, Point const& _point, double *distance_squared = NULL); -std::vector<PathVectorPosition> allNearestPoints(PathVector const & path_in, Point const& _point, double *distance_squared = NULL); +/** @brief Sequence of subpaths. + * + * This class corresponds to the SVG notion of a path: + * a sequence of any number of open or closed contiguous subpaths. + * Unlike Path, this class is closed under boolean operations. + * + * If you want to represent an arbitrary shape, this is the best class to use. + * Shapes with a boundary that is composed of only a single contiguous + * component can be represented with Path instead. + * + * @ingroup Paths + */ +class PathVector + : MultipliableNoncommutative< PathVector, Affine + , MultipliableNoncommutative< PathVector, Translate + , MultipliableNoncommutative< PathVector, Scale + , MultipliableNoncommutative< PathVector, Rotate + , MultipliableNoncommutative< PathVector, HShear + , MultipliableNoncommutative< PathVector, VShear + , MultipliableNoncommutative< PathVector, Zoom + , boost::addable< PathVector + , boost::equality_comparable< PathVector + > > > > > > > > > +{ + typedef std::vector<Path> Sequence; +public: + typedef PathVectorPosition Position; + typedef Sequence::iterator iterator; + typedef Sequence::const_iterator const_iterator; + typedef Sequence::size_type size_type; + typedef Path value_type; + typedef Path &reference; + typedef Path const &const_reference; + typedef Path *pointer; + typedef std::ptrdiff_t difference_type; + + PathVector() {} + PathVector(Path const &p) + : _data(1, p) + {} + template <typename InputIter> + PathVector(InputIter first, InputIter last) + : _data(first, last) + {} + + /// Check whether the vector contains any paths. + bool empty() const { return _data.empty(); } + /// Get the number of paths in the vector. + size_type size() const { return _data.size(); } + /// Get the total number of curves in the vector. + size_type curveCount() const; + + iterator begin() { return _data.begin(); } + iterator end() { return _data.end(); } + const_iterator begin() const { return _data.begin(); } + const_iterator end() const { return _data.end(); } + Path &operator[](size_type index) { + return _data[index]; + } + Path const &operator[](size_type index) const { + return _data[index]; + } + Path &at(size_type index) { + return _data.at(index); + } + Path const &at(size_type index) const { + return _data.at(index); + } + Path &front() { return _data.front(); } + Path const &front() const { return _data.front(); } + Path &back() { return _data.back(); } + Path const &back() const { return _data.back(); } + /// Append a path at the end. + void push_back(Path const &path) { + _data.push_back(path); + } + /// Remove the last path. + void pop_back() { + _data.pop_back(); + } + iterator insert(iterator pos, Path const &p) { + return _data.insert(pos, p); + } + template <typename InputIter> + void insert(iterator out, InputIter first, InputIter last) { + _data.insert(out, first, last); + } + /// Remove a path from the vector. + iterator erase(iterator i) { + return _data.erase(i); + } + /// Remove a range of paths from the vector. + iterator erase(iterator first, iterator last) { + return _data.erase(first, last); + } + /// Remove all paths from the vector. + void clear() { _data.clear(); } + /** @brief Change the number of paths. + * If the vector size increases, it is passed with paths that contain only + * a degenerate closing segment at (0,0). */ + void resize(size_type n) { _data.resize(n); } + /** @brief Reverse the direction of paths in the vector. + * @param reverse_paths If this is true, the order of paths is reversed as well; + * otherwise each path is reversed, but their order in the + * PathVector stays the same */ + void reverse(bool reverse_paths = true); + /** @brief Get a new vector with reversed direction of paths. + * @param reverse_paths If this is true, the order of paths is reversed as well; + * otherwise each path is reversed, but their order in the + * PathVector stays the same */ + PathVector reversed(bool reverse_paths = true) const; + + /// Get the range of allowed time values. + Interval timeRange() const { + Interval ret(0, curveCount()); return ret; + } + /** @brief Get the first point in the first path of the vector. + * This method will throw an exception if the vector doesn't contain any paths. */ + Point initialPoint() const { + return _data.front().initialPoint(); + } + /** @brief Get the last point in the last path of the vector. + * This method will throw an exception if the vector doesn't contain any paths. */ + Point finalPoint() const { + return _data.back().finalPoint(); + } + Path &pathAt(Coord t, Coord *rest = NULL); + Path const &pathAt(Coord t, Coord *rest = NULL) const; + Curve const &curveAt(Coord t, Coord *rest = NULL) const; + Coord valueAt(Coord t, Dim2 d) const; + Point pointAt(Coord t) const; + + Path &pathAt(Position const &pos) { + return const_cast<Path &>(static_cast<PathVector const*>(this)->pathAt(pos)); + } + Path const &pathAt(Position const &pos) const { + return at(pos.path_index); + } + Curve const &curveAt(Position const &pos) const { + return at(pos.path_index).at(pos.curve_index); + } + Point pointAt(Position const &pos) const { + return at(pos.path_index).at(pos.curve_index).pointAt(pos.t); + } + Coord valueAt(Position const &pos, Dim2 d) const { + return at(pos.path_index).at(pos.curve_index).valueAt(pos.t, d); + } + + OptRect boundsFast() const; + OptRect boundsExact() const; + + template <typename T> + BOOST_CONCEPT_REQUIRES(((TransformConcept<T>)), (PathVector &)) + operator*=(T const &t) { + if (empty()) return *this; + for (iterator i = begin(); i != end(); ++i) { + *i *= t; + } + return *this; + } + + bool operator==(PathVector const &other) const { + return boost::range::equal(_data, other._data); + } + + std::vector<PVIntersection> intersect(PathVector const &other, Coord precision = EPSILON) const; + + /** @brief Determine the winding number at the specified point. + * This is simply the sum of winding numbers for constituent paths. */ + int winding(Point const &p) const; + + Coord nearestTime(Point const &p) const; + boost::optional<Position> nearestPosition(Point const &p, Coord *dist = NULL) const; + std::vector<Position> allNearestPositions(Point const &p, Coord *dist = NULL) const; + +private: + Position _getPosition(Coord t) const; + + Sequence _data; +}; -inline -Point pointAt(PathVector const & path_in, PathVectorPosition const &pvp) { - return path_in[pvp.path_nr].pointAt(pvp.t); -} +inline OptRect bounds_fast(PathVector const &pv) { return pv.boundsFast(); } +inline OptRect bounds_exact(PathVector const &pv) { return pv.boundsExact(); } } // end namespace Geom diff --git a/src/2geom/piecewise.h b/src/2geom/piecewise.h index ab8417254..981d67144 100644 --- a/src/2geom/piecewise.h +++ b/src/2geom/piecewise.h @@ -1,7 +1,6 @@ -/** - * \file - * \brief Piecewise function class - * +/** @file + * @brief Piecewise function class + *//* * Copyright 2007 Michael Sloan <mgsloan@gmail.com> * * This library is free software; you can redistribute it and/or @@ -29,8 +28,8 @@ * */ -#ifndef SEEN_GEOM_PW_SB_H -#define SEEN_GEOM_PW_SB_H +#ifndef LIB2GEOM_SEEN_PIECEWISE_H +#define LIB2GEOM_SEEN_PIECEWISE_H #include <vector> #include <map> @@ -933,7 +932,7 @@ Piecewise<T> lerp(double t, Piecewise<T> const &a, Piecewise<T> b) { } } -#endif //SEEN_GEOM_PW_SB_H +#endif //LIB2GEOM_SEEN_PIECEWISE_H /* Local Variables: mode:c++ diff --git a/src/2geom/point.cpp b/src/2geom/point.cpp index b0b00b5da..e2662becd 100644 --- a/src/2geom/point.cpp +++ b/src/2geom/point.cpp @@ -146,8 +146,8 @@ bool is_zero(Point const &p) { /** @brief True if the point has a length near 1. The are_near() function is used. * @relates Point */ -bool is_unit_vector(Point const &p) { - return are_near(L2(p), 1.0); +bool is_unit_vector(Point const &p, Coord eps) { + return are_near(L2(p), 1.0, eps); } /** @brief Return the angle between the point and the +X axis. * @return Angle in \f$(-\pi, \pi]\f$. @@ -161,7 +161,7 @@ Coord atan2(Point const &p) { * @return Angle in \f$(-\pi, \pi]\f$. * @relates Point */ Coord angle_between(Point const &a, Point const &b) { - return std::atan2(cross(b,a), dot(b,a)); + return std::atan2(cross(a,b), dot(a,b)); } /** @brief Create a normalized version of a point. diff --git a/src/2geom/point.h b/src/2geom/point.h index 23dcfd54f..3e56ae358 100644 --- a/src/2geom/point.h +++ b/src/2geom/point.h @@ -1,6 +1,5 @@ -/** - * \file - * \brief Cartesian point / 2D vector and related operations +/** @file + * @brief Cartesian point / 2D vector and related operations *//* * Authors: * Michael G. Sloan <mgsloan@gmail.com> @@ -33,8 +32,8 @@ * the specific language governing rights and limitations. */ -#ifndef SEEN_Geom_POINT_H -#define SEEN_Geom_POINT_H +#ifndef LIB2GEOM_SEEN_POINT_H +#define LIB2GEOM_SEEN_POINT_H #include "config.h" #include <iostream> @@ -59,10 +58,14 @@ class Point , MultipliableNoncommutative< Point, HShear , MultipliableNoncommutative< Point, VShear , MultipliableNoncommutative< Point, Zoom - > > > > > > > > > > // this uses chaining so it looks weird, but works + > > > > > > > > > > // base class chaining, see documentation for Boost.Operator { Coord _pt[2]; public: + typedef Coord D1Value; + typedef Coord &D1Reference; + typedef Coord const &D1ConstReference; + /// @name Create points /// @{ /** Construct a point on the origin. */ @@ -116,6 +119,11 @@ public: * @return Length of the vector from origin to this point */ Coord length() const { return hypot(_pt[0], _pt[1]); } void normalize(); + Point normalized() const { + Point ret(*this); + ret.normalize(); + return ret; + } /** @brief Return a point like this point but rotated -90 degrees. * If the y axis grows downwards and the x axis grows to the @@ -223,16 +231,25 @@ public: } /// @} - /** @brief Lexicographical ordering functor. */ - template <Dim2 d> struct LexOrder; + /** @brief Lexicographical ordering functor. + * @param d The dimension with higher significance */ + template <Dim2 DIM> struct LexLess; + template <Dim2 DIM> struct LexGreater; + //template <Dim2 DIM, typename First = std::less<Coord>, typename Second = std::less<Coord> > LexOrder; /** @brief Lexicographical ordering functor with runtime dimension. */ - class LexOrderRt { - public: - LexOrderRt(Dim2 d) : dim(d) {} - inline bool operator()(Point const &a, Point const &b); + struct LexLessRt { + LexLessRt(Dim2 d) : dim(d) {} + inline bool operator()(Point const &a, Point const &b) const; + private: + Dim2 dim; + }; + struct LexGreaterRt { + LexGreaterRt(Dim2 d) : dim(d) {} + inline bool operator()(Point const &a, Point const &b) const; private: Dim2 dim; }; + //template <typename First = std::less<Coord>, typename Second = std::less<Coord> > LexOrder friend inline std::ostream &operator<< (std::ostream &out_file, const Geom::Point &in_pnt); }; @@ -245,18 +262,47 @@ inline std::ostream &operator<< (std::ostream &out_file, const Geom::Point &in_p return out_file; } -template<> struct Point::LexOrder<X> { - bool operator()(Point const &a, Point const &b) { +template<> struct Point::LexLess<X> { + typedef std::less<Coord> Primary; + typedef std::less<Coord> Secondary; + typedef std::less<Coord> XOrder; + typedef std::less<Coord> YOrder; + bool operator()(Point const &a, Point const &b) const { return a[X] < b[X] || (a[X] == b[X] && a[Y] < b[Y]); } }; -template<> struct Point::LexOrder<Y> { - bool operator()(Point const &a, Point const &b) { +template<> struct Point::LexLess<Y> { + typedef std::less<Coord> Primary; + typedef std::less<Coord> Secondary; + typedef std::less<Coord> XOrder; + typedef std::less<Coord> YOrder; + bool operator()(Point const &a, Point const &b) const { return a[Y] < b[Y] || (a[Y] == b[Y] && a[X] < b[X]); } }; -inline bool Point::LexOrderRt::operator()(Point const &a, Point const &b) { - return dim ? Point::LexOrder<Y>()(a, b) : Point::LexOrder<X>()(a, b); +template<> struct Point::LexGreater<X> { + typedef std::greater<Coord> Primary; + typedef std::greater<Coord> Secondary; + typedef std::greater<Coord> XOrder; + typedef std::greater<Coord> YOrder; + bool operator()(Point const &a, Point const &b) const { + return a[X] > b[X] || (a[X] == b[X] && a[Y] > b[Y]); + } +}; +template<> struct Point::LexGreater<Y> { + typedef std::greater<Coord> Primary; + typedef std::greater<Coord> Secondary; + typedef std::greater<Coord> XOrder; + typedef std::greater<Coord> YOrder; + bool operator()(Point const &a, Point const &b) const { + return a[Y] > b[Y] || (a[Y] == b[Y] && a[X] > b[X]); + } +}; +inline bool Point::LexLessRt::operator()(Point const &a, Point const &b) const { + return dim ? Point::LexLess<Y>()(a, b) : Point::LexLess<X>()(a, b); +} +inline bool Point::LexGreaterRt::operator()(Point const &a, Point const &b) const { + return dim ? Point::LexGreater<Y>()(a, b) : Point::LexGreater<X>()(a, b); } /** @brief Compute the second (Euclidean) norm of @a p. @@ -265,8 +311,7 @@ inline bool Point::LexOrderRt::operator()(Point const &a, Point const &b) { * in a <code>double</code>. * @return \f$\sqrt{p_X^2 + p_Y^2}\f$ * @relates Point */ -inline Coord L2(Point const &p) -{ +inline Coord L2(Point const &p) { return p.length(); } @@ -274,28 +319,10 @@ inline Coord L2(Point const &p) * Warning: this can overflow where L2 won't. * @return \f$p_X^2 + p_Y^2\f$ * @relates Point */ -inline Coord L2sq(Point const &p) -{ +inline Coord L2sq(Point const &p) { return p[0]*p[0] + p[1]*p[1]; } -//IMPL: NearConcept -/** @brief Nearness predicate for points. - * True if neither coordinate of @a a is further than @a eps from the corresponding - * coordinate of @a b. - * @relates Point */ -inline bool are_near(Point const &a, Point const &b, double const eps=EPSILON) -{ - return ( are_near(a[X],b[X],eps) && are_near(a[Y],b[Y],eps) ); -} - -/** @brief Return a point halfway between the specified ones. - * @relates Point */ -inline Point middle_point(Point const& P1, Point const& P2) -{ - return (P1 + P2) / 2; -} - /** @brief Returns p * Geom::rotate_degrees(90), but more efficient. * * Angle direction in 2Geom: If you use the traditional mathematics convention that y @@ -305,8 +332,7 @@ inline Point middle_point(Point const& P1, Point const& P2) * * There is no function to rotate by -90 degrees: use -rot90(p) instead. * @relates Point */ -inline Point rot90(Point const &p) -{ +inline Point rot90(Point const &p) { return Point(-p[Y], p[X]); } @@ -317,9 +343,14 @@ inline Point rot90(Point const &p) * @return Point on a line between a and b. The ratio of its distance from a * and the distance between a and b will be equal to t. * @relates Point */ -inline Point lerp(double const t, Point const &a, Point const &b) -{ - return (a * (1 - t) + b * t); +inline Point lerp(Coord t, Point const &a, Point const &b) { + return (1 - t) * a + t * b; +} + +/** @brief Return a point halfway between the specified ones. + * @relates Point */ +inline Point middle_point(Point const &p1, Point const &p2) { + return lerp(0.5, p1, p2); } /** @brief Compute the dot product of a and b. @@ -328,45 +359,60 @@ inline Point lerp(double const t, Point const &a, Point const &b) * and the sign depends on whether they point in the same direction (+) or opposite ones (-). * @return \f$a \cdot b = a_X b_X + a_Y b_Y\f$. * @relates Point */ -inline Coord dot(Point const &a, Point const &b) -{ - return a[0] * b[0] + a[1] * b[1]; +inline Coord dot(Point const &a, Point const &b) { + return a[X] * b[X] + a[Y] * b[Y]; } /** @brief Compute the 2D cross product. - * Defined as dot(a, b.cw()). This means it will be zero for parallel vectors, - * and its absolute value highest for perpendicular vectors. + * This is also known as "perp dot product". It will be zero for parallel vectors, + * and the absolute value will be highest for perpendicular vectors. + * @return \f$a \times b = a_X b_Y - a_Y b_X\f$. * @relates Point*/ inline Coord cross(Point const &a, Point const &b) { - return dot(a, b.cw()); + // equivalent implementation: + // return dot(a, b.ccw()); + return a[X] * b[Y] - a[Y] * b[X]; } -/** @brief Compute the (Euclidean) distance between points. - * @relates Point */ -inline Coord distance (Point const &a, Point const &b) -{ - return L2(a - b); +/// Compute the (Euclidean) distance between points. +/// @relates Point +inline Coord distance (Point const &a, Point const &b) { + return (a - b).length(); } -/** @brief Compute the square of the distance between points. - * @relates Point */ -inline Coord distanceSq (Point const &a, Point const &b) -{ +/// Compute the square of the distance between points. +/// @relates Point +inline Coord distanceSq (Point const &a, Point const &b) { return L2sq(a - b); } +//IMPL: NearConcept +/// Test whether two points are no further apart than some threshold. +/// @relates Point +inline bool are_near(Point const &a, Point const &b, double eps = EPSILON) { + return are_near(distance(a, b), 0, eps); +} + +/// Test whether three points lie approximately on the same line. +/// @relates Point +inline bool are_collinear(Point const& p1, Point const& p2, Point const& p3, + double eps = EPSILON) +{ + return are_near( cross(p3, p2) - cross(p3, p1) + cross(p2, p1), 0, eps); +} + Point unit_vector(Point const &a); Coord L1(Point const &p); Coord LInfty(Point const &p); bool is_zero(Point const &p); -bool is_unit_vector(Point const &p); +bool is_unit_vector(Point const &p, Coord eps = EPSILON); double atan2(Point const &p); double angle_between(Point const &a, Point const &b); Point abs(Point const &b); Point constrain_angle(Point const &A, Point const &B, unsigned int n = 4, Geom::Point const &dir = Geom::Point(1,0)); -} /* namespace Geom */ +} // end namespace Geom // This is required to fix a bug in GCC 4.3.3 (and probably others) that causes the compiler // to try to instantiate the iterator_traits template and fail. Probably it thinks that Point @@ -375,7 +421,7 @@ namespace std { template <> class iterator_traits<Geom::Point> {}; } -#endif /* !SEEN_Geom_POINT_H */ +#endif // LIB2GEOM_SEEN_POINT_H /* Local Variables: diff --git a/src/2geom/quadtree.cpp b/src/2geom/quadtree.cpp index 98030c424..76dc68774 100644 --- a/src/2geom/quadtree.cpp +++ b/src/2geom/quadtree.cpp @@ -211,10 +211,6 @@ void QuadTree::insert(double x0, double y0, double x1, double y1, int shape) { // check if rect's bounding box has size 1x1. This means that rect is defined by 2 points // that are in the same place. if( ( fabs(bxx0 - bxx1) < 1.0 ) && ( fabs(byy0 - byy1) < 1.0 )){ - bxx0 = floor(bxx0); - bxx1 = floor(bxx1); - byy0 = floor(byy0); - byy1 = floor(byy1); break; } diff --git a/src/2geom/quadtree.h b/src/2geom/quadtree.h index 949a9b898..62f2b8321 100644 --- a/src/2geom/quadtree.h +++ b/src/2geom/quadtree.h @@ -32,6 +32,9 @@ * */ +#ifndef LIB2GEOM_SEEN_QUADTREE_H +#define LIB2GEOM_SEEN_QUADTREE_H + #include <vector> #include <cassert> @@ -64,6 +67,8 @@ public: * this case should be never reached */ assert(false); } + assert(false); + return Rect(); } }; @@ -87,6 +92,7 @@ private: }; +#endif /* Local Variables: mode:c++ diff --git a/src/2geom/ray.h b/src/2geom/ray.h index 2fda06ff5..a336e3132 100644 --- a/src/2geom/ray.h +++ b/src/2geom/ray.h @@ -98,7 +98,7 @@ public: } return result; } - Coord nearestPoint(Point const& point) const { + Coord nearestTime(Point const& point) const { if ( isDegenerate() ) return 0; double t = dot(point - _origin, _versor); if (t < 0) t = 0; @@ -123,7 +123,7 @@ public: inline double distance(Point const& _point, Ray const& _ray) { - double t = _ray.nearestPoint(_point); + double t = _ray.nearestTime(_point); return ::Geom::distance(_point, _ray.pointAt(t)); } diff --git a/src/2geom/rect.cpp b/src/2geom/rect.cpp index 0cb842d29..383e72c8e 100644 --- a/src/2geom/rect.cpp +++ b/src/2geom/rect.cpp @@ -84,6 +84,17 @@ Coord distance(Point const &p, Rect const &rect) return hypot(dx, dy); } +Coord distanceSq(Point const &p, OptRect const &rect) +{ + if (!rect) return std::numeric_limits<Coord>::max(); + return distanceSq(p, *rect); +} +Coord distance(Point const &p, OptRect const &rect) +{ + if (!rect) return std::numeric_limits<Coord>::max(); + return distance(p, *rect); +} + } // namespace Geom /* diff --git a/src/2geom/rect.h b/src/2geom/rect.h index 2516bcfa6..51daf6b5a 100644 --- a/src/2geom/rect.h +++ b/src/2geom/rect.h @@ -75,6 +75,10 @@ public: * @param eps Maximum value of the area to consider empty * @return True if rectangle has an area smaller than tolerance, false otherwise */ bool hasZeroArea(Coord eps = EPSILON) const { return (area() <= eps); } + /// Check whether the rectangle has finite area + bool isFinite() const { return (*this)[X].isFinite() && (*this)[Y].isFinite(); } + /// Calculate the diameter of the smallest circle that would contain the rectangle. + Coord diameter() const { return distance(corner(0), corner(2)); } /// @} /// @name Test other rectangles and points for inclusion. @@ -153,6 +157,10 @@ public: Coord distanceSq(Point const &p, Rect const &rect); Coord distance(Point const &p, Rect const &rect); +/// Minimum square of distance to rectangle, or infinity if empty. +Coord distanceSq(Point const &p, OptRect const &rect); +/// Minimum distance to rectangle, or infinity if empty. +Coord distance(Point const &p, OptRect const &rect); inline bool Rect::interiorContains(OptRect const &r) const { return !r || interiorContains(static_cast<Rect const &>(*r)); diff --git a/src/2geom/recursive-bezier-intersection.cpp b/src/2geom/recursive-bezier-intersection.cpp index 548065196..e86192f54 100644 --- a/src/2geom/recursive-bezier-intersection.cpp +++ b/src/2geom/recursive-bezier-intersection.cpp @@ -13,7 +13,6 @@ unsigned intersect_steps = 0; using std::vector; -using std::swap; namespace Geom { @@ -33,7 +32,7 @@ public: minax = p[0][X]; // These are the most likely to be extremal maxax = p.back()[X]; if( minax > maxax ) - swap(minax, maxax); + std::swap(minax, maxax); for(unsigned i = 1; i < p.size()-1; i++) { if( p[i][X] < minax ) minax = p[i][X]; @@ -44,7 +43,7 @@ public: minay = p[0][Y]; // These are the most likely to be extremal maxay = p.back()[Y]; if( minay > maxay ) - swap(minay, maxay); + std::swap(minay, maxay); for(unsigned i = 1; i < p.size()-1; i++) { if( p[i][Y] < minay ) minay = p[i][Y]; diff --git a/src/2geom/region.cpp b/src/2geom/region.cpp deleted file mode 100644 index 8cfb1c68c..000000000 --- a/src/2geom/region.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include <2geom/region.h> -#include <2geom/utils.h> - -#include <2geom/shape.h> - -namespace Geom { - -Region Region::operator*(Affine const &m) const { - Region r((m.flips() ? boundary.reverse() : boundary) * m, fill); - if(box && m.isZoom()) r.box = (*box) * m; - return r; -} - -bool Region::invariants() const { - return self_crossings(boundary).empty(); -} - -unsigned outer_index(Regions const &ps) { - if(ps.size() <= 1 || ps[0].contains(ps[1])) { - return 0; - } else { - /* Since we've already shown that chunks[0] is not outside - it can be used as an exemplar inner. */ - Point exemplar = Path(ps[0]).initialPoint(); - for(unsigned i = 1; i < ps.size(); i++) { - if(ps[i].contains(exemplar)) { - return i; - } - } - } - return ps.size(); -} - -} - -/* - 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/2geom/region.h b/src/2geom/region.h deleted file mode 100644 index 06a4f63e9..000000000 --- a/src/2geom/region.h +++ /dev/null @@ -1,130 +0,0 @@ -/** - * \file - * \brief Uncrossed path for boolean algorithms - *//* - * Authors: - * ? <?@?.?> - * - * Copyright ?-? authors - * - * This library is free software; you can redistribute it and/or - * modify it either under the terms of the GNU Lesser General Public - * License version 2.1 as published by the Free Software Foundation - * (the "LGPL") or, at your option, under the terms of the Mozilla - * Public License Version 1.1 (the "MPL"). If you do not alter this - * notice, a recipient may use your version of this file under either - * the MPL or the LGPL. - * - * You should have received a copy of the LGPL along with this library - * in the file COPYING-LGPL-2.1; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * You should have received a copy of the MPL along with this library - * in the file COPYING-MPL-1.1 - * - * The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY - * OF ANY KIND, either express or implied. See the LGPL or the MPL for - * the specific language governing rights and limitations. - * - */ - -#ifndef __2GEOM_REGION_H -#define __2GEOM_REGION_H - -#include <2geom/path.h> -#include <2geom/path-intersection.h> - -namespace Geom { - -class Shape; - -class Region { - friend Crossings crossings(Region const &a, Region const &b); - friend class Shape; - friend Shape shape_boolean(bool rev, Shape const & a, Shape const & b, CrossingSet const & crs); - - Path boundary; - mutable OptRect box; - bool fill; - public: - Region() : fill(true) {} - explicit Region(Path const &p) : boundary(p) { fill = path_direction(p); } - Region(Path const &p, bool dir) : boundary(p), fill(dir) {} - Region(Path const &p, OptRect const &b) : boundary(p), box(b) { fill = path_direction(p); } - Region(Path const &p, OptRect const &b, bool dir) : boundary(p), box(b), fill(dir) {} - - unsigned size() const { return boundary.size(); } - - bool isFill() const { return fill; } - Region asFill() const { if(fill) return Region(*this); else return inverse(); } - Region asHole() const { if(fill) return inverse(); else return Region(*this); } - - operator Path() const { return boundary; } - Rect boundsFast() const { - if(!box) box = boundary.boundsFast(); /// \todo this doesn't look right at all... - return *box; - } - - bool contains(Point const &p) const { - if(box && !box->contains(p)) return false; - return Geom::contains(boundary, p); - } - bool contains(Region const &other) const { return contains(other.boundary.initialPoint()); } - - bool includes(Point const &p) const { - return logical_xor(!fill, contains(p)); - } - - Region inverse() const { return Region(boundary.reverse(), box, !fill); } - - Region operator*(Affine const &m) const; - - bool invariants() const; -}; - -typedef std::vector<Region> Regions; - -unsigned outer_index(Regions const &ps); - -//assumes they're already sanitized somewhat -inline Regions regions_from_paths(std::vector<Path> const &ps) { - Regions res; - for(unsigned i = 0; i < ps.size(); i++) - res.push_back(Region(ps[i])); - return res; -} - -inline std::vector<Path> paths_from_regions(Regions const &rs) { - std::vector<Path> res; - for(unsigned i = 0; i < rs.size(); i++) - res.push_back(rs[i]); - return res; -} - -Regions sanitize_path(Path const &p); - -Regions region_boolean(bool rev, Region const & a, Region const & b, Crossings const &cr); -Regions region_boolean(bool rev, Region const & a, Region const & b, Crossings const & cr_a, Crossings const & cr_b); - -inline Regions region_boolean(bool rev, Region const & a, Region const & b) { - return region_boolean(rev, a, b, crossings(a, b)); -} - -} - -#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/2geom/sbasis-2d.cpp b/src/2geom/sbasis-2d.cpp index aa5018e9e..53b09cd35 100644 --- a/src/2geom/sbasis-2d.cpp +++ b/src/2geom/sbasis-2d.cpp @@ -169,7 +169,7 @@ sb2d_cubic_solve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B){ std::vector<D2<SBasis> > candidates = cubics_fitting_curvature(A,B,V0,V1,D2fVV0,D2fVV1); if (candidates.empty()) { - return D2<SBasis>(Linear(A[X],B[X]),Linear(A[Y],B[Y])); + return D2<SBasis>(SBasis(Linear(A[X],B[X])), SBasis(Linear(A[Y],B[Y]))); } //TODO: I'm sure std algorithm could do that for me... double error = -1; diff --git a/src/2geom/sbasis-2d.h b/src/2geom/sbasis-2d.h index 00429e259..c7d9b000a 100644 --- a/src/2geom/sbasis-2d.h +++ b/src/2geom/sbasis-2d.h @@ -33,8 +33,8 @@ * */ -#ifndef SEEN_SBASIS_2D_H -#define SEEN_SBASIS_2D_H +#ifndef LIB2GEOM_SEEN_SBASIS_2D_H +#define LIB2GEOM_SEEN_SBASIS_2D_H #include <vector> #include <cassert> #include <algorithm> @@ -356,8 +356,9 @@ sb2dsolve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B, unsigne D2<SBasis> sb2d_cubic_solve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B); -}; +} // end namespace Geom +#endif /* Local Variables: mode:c++ @@ -368,4 +369,3 @@ sb2d_cubic_solve(SBasis2d const &f, Geom::Point const &A, Geom::Point const &B); End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : -#endif diff --git a/src/2geom/sbasis-curve.h b/src/2geom/sbasis-curve.h index a5c3f2ca7..a7b74dcee 100644 --- a/src/2geom/sbasis-curve.h +++ b/src/2geom/sbasis-curve.h @@ -37,7 +37,7 @@ #define LIB2GEOM_SEEN_SBASIS_CURVE_H #include <2geom/curve.h> -#include <2geom/nearest-point.h> +#include <2geom/nearest-time.h> #include <2geom/sbasis-geometric.h> #include <2geom/transforms.h> @@ -84,11 +84,10 @@ public: explicit SBasisCurve(D2<SBasis> const &sb) : inner(sb) {} explicit SBasisCurve(Curve const &other) : inner(other.toSBasis()) {} -#ifndef DOXYGEN_SHOULD_SKIP_THIS virtual Curve *duplicate() const { return new SBasisCurve(*this); } virtual Point initialPoint() const { return inner.at0(); } virtual Point finalPoint() const { return inner.at1(); } - virtual bool isDegenerate() const { return inner.isConstant(); } + virtual bool isDegenerate() const { return inner.isConstant(0); } virtual Point pointAt(Coord t) const { return inner.valueAt(t); } virtual std::vector<Point> pointAndDerivatives(Coord t, unsigned n) const { return inner.valueAndDerivatives(t, n); @@ -106,33 +105,33 @@ public: return bounds_local(inner, i, deg); } virtual std::vector<Coord> roots(Coord v, Dim2 d) const { return Geom::roots(inner[d] - v); } - virtual Coord nearestPoint( Point const& p, Coord from = 0, Coord to = 1 ) const { - return nearest_point(p, inner, from, to); + virtual Coord nearestTime( Point const& p, Coord from = 0, Coord to = 1 ) const { + return nearest_time(p, inner, from, to); } - virtual std::vector<Coord> allNearestPoints( Point const& p, Coord from = 0, + virtual std::vector<Coord> allNearestTimes( Point const& p, Coord from = 0, Coord to = 1 ) const { - return all_nearest_points(p, inner, from, to); + return all_nearest_times(p, inner, from, to); } virtual Coord length(Coord tolerance) const { return ::Geom::length(inner, tolerance); } virtual Curve *portion(Coord f, Coord t) const { return new SBasisCurve(Geom::portion(inner, f, t)); } - virtual Curve *transformed(Affine const &m) const { return new SBasisCurve(inner * m); } - virtual Curve &operator*=(Translate const &m) { - inner += m.vector(); - return *this; - }; + virtual void transform(Affine const &m) { inner = inner * m; } virtual Curve *derivative() const { return new SBasisCurve(Geom::derivative(inner)); } virtual D2<SBasis> toSBasis() const { return inner; } + virtual bool operator==(Curve const &c) const { + SBasisCurve const *other = dynamic_cast<SBasisCurve const *>(&c); + if (!other) return false; + return inner == other->inner; + } virtual int degreesOfFreedom() const { return inner[0].degreesOfFreedom() + inner[1].degreesOfFreedom(); } -#endif }; } // end namespace Geom diff --git a/src/2geom/sbasis-geometric.h b/src/2geom/sbasis-geometric.h index 43c624fb4..7f1e8aaba 100644 --- a/src/2geom/sbasis-geometric.h +++ b/src/2geom/sbasis-geometric.h @@ -1,15 +1,6 @@ -#ifndef _SBASIS_GEOMETRIC -#define _SBASIS_GEOMETRIC -#include <2geom/d2.h> -#include <2geom/piecewise.h> -#include <vector> - /** * \file * \brief two-dimensional geometric operators. - * - * Copyright 2007, JFBarraud - * Copyright 2007, njh * * These operators are built on a more 'polynomially robust' * transformation to map a function that takes a [0,1] parameter to a @@ -21,9 +12,42 @@ * from the various tangent directions at each end (angular jet). As * a result, the final path has a convergence behaviour derived from * that of the sin and cos series. -- njh + *//* + * Copyright 2007, JFBarraud + * Copyright 2007, njh + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. */ -namespace Geom{ +#ifndef LIB2GEOM_SEEN_SBASIS_GEOMETRIC_H +#define LIB2GEOM_SEEN_SBASIS_GEOMETRIC_H + +#include <2geom/d2.h> +#include <2geom/piecewise.h> +#include <vector> + +namespace Geom { Piecewise<D2<SBasis> > cutAtRoots(Piecewise<D2<SBasis> > const &M, double tol=1e-4); diff --git a/src/2geom/sbasis-math.h b/src/2geom/sbasis-math.h index e6d40a3de..e191dae62 100644 --- a/src/2geom/sbasis-math.h +++ b/src/2geom/sbasis-math.h @@ -1,7 +1,6 @@ -/** - * \file - * \brief some std functions to work with (pw)s-basis - * +/** @file + * @brief some std functions to work with (pw)s-basis + *//* * Authors: * Jean-Francois Barraud * @@ -36,8 +35,8 @@ //TODO: in all these functions, compute 'order' according to 'tol'. //TODO: use template to define the pw version automatically from the sb version? -#ifndef SEEN_GEOM_SB_CALCULS_H -#define SEEN_GEOM_SB_CALCULS_H +#ifndef LIB2GEOM_SEEN_SBASIS_MATH_H +#define LIB2GEOM_SEEN_SBASIS_MATH_H #include <2geom/sbasis.h> diff --git a/src/2geom/sbasis-poly.h b/src/2geom/sbasis-poly.h index e0bef9333..81eeca72c 100644 --- a/src/2geom/sbasis-poly.h +++ b/src/2geom/sbasis-poly.h @@ -1,13 +1,6 @@ -#ifndef _SBASIS_TO_POLY -#define _SBASIS_TO_POLY - -#include <2geom/poly.h> -#include <2geom/sbasis.h> - -/** - * \file - * \brief Conversion between SBasis and Poly. Not recommended for general use due to instability. - * +/** @file + * @brief Conversion between SBasis and Poly. Not recommended for general use due to instability. + *//* * Authors: * ? <?@?.?> * @@ -35,9 +28,14 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. - * */ +#ifndef LIB2GEOM_SEEN_SBASIS_POLY_H +#define LIB2GEOM_SEEN_SBASIS_POLY_H + +#include <2geom/poly.h> +#include <2geom/sbasis.h> + namespace Geom{ SBasis poly_to_sbasis(Poly const & p); @@ -45,6 +43,7 @@ Poly sbasis_to_poly(SBasis const & s); }; +#endif /* Local Variables: mode:c++ @@ -55,5 +54,3 @@ Poly sbasis_to_poly(SBasis const & s); End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : - -#endif diff --git a/src/2geom/sbasis-roots.cpp b/src/2geom/sbasis-roots.cpp index acf4e1abc..57bef4c0f 100644 --- a/src/2geom/sbasis-roots.cpp +++ b/src/2geom/sbasis-roots.cpp @@ -1,7 +1,38 @@ -/** root finding for sbasis functions. - * Copyright 2006 N Hurst - * Copyright 2007 JF Barraud +/** + * @file + * @brief Root finding for sbasis functions. + *//* + * Authors: + * Nathan Hurst <njh@njhurst.com> + * JF Barraud + * Copyright 2006-2007 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + * + */ + + /* * It is more efficient to find roots of f(t) = c_0, c_1, ... all at once, rather than iterating. * * Todo/think about: @@ -98,20 +129,20 @@ OptInterval bounds_fast(const SBasis &sb, int order) { double b=sb[j][1]; double v, t = 0; - v = res[0]; + v = res.min(); if (v<0) t = ((b-a)/v+1)*0.5; if (v>=0 || t<0 || t>1) { - res[0] = std::min(a,b); - }else{ - res[0]=lerp(t, a+v*t, b); + res.setMin(std::min(a,b)); + } else { + res.setMin(lerp(t, a+v*t, b)); } - v = res[1]; + v = res.max(); if (v>0) t = ((b-a)/v+1)*0.5; if (v<=0 || t<0 || t>1) { - res[1] = std::max(a,b); + res.setMax(std::max(a,b)); }else{ - res[1]=lerp(t, a+v*t, b); + res.setMax(lerp(t, a+v*t, b)); } } if (order>0) res*=std::pow(.25,order); @@ -576,6 +607,7 @@ std::vector<double> roots1(SBasis const & s, Interval const ivl) { /** Find all t s.t s(t) = 0 \param a sbasis function + \see Bezier::roots \returns vector of zeros (roots) */ diff --git a/src/2geom/sbasis-to-bezier.cpp b/src/2geom/sbasis-to-bezier.cpp index a2e4253d2..fe7bbc91c 100644 --- a/src/2geom/sbasis-to-bezier.cpp +++ b/src/2geom/sbasis-to-bezier.cpp @@ -37,7 +37,7 @@ #include <2geom/choose.h> #include <2geom/path-sink.h> #include <2geom/exception.h> -#include <2geom/convex-cover.h> +#include <2geom/convex-hull.h> #include <iostream> @@ -153,6 +153,15 @@ void sbasis_to_bezier (Bezier & bz, SBasis const& sb, size_t sz) bz[n] = sb[0][1]; } +void sbasis_to_bezier(D2<Bezier> &bz, D2<SBasis> const &sb, size_t sz) +{ + if (sz == 0) { + sz = std::max(sb[X].size(), sb[Y].size())*2; + } + sbasis_to_bezier(bz[X], sb[X], sz); + sbasis_to_bezier(bz[Y], sb[Y], sz); +} + /** Changes the basis of p to be Bernstein. \param p the D2 Symmetric basis polynomial \returns the D2 Bernstein basis polynomial @@ -161,24 +170,9 @@ void sbasis_to_bezier (Bezier & bz, SBasis const& sb, size_t sz) */ void sbasis_to_bezier (std::vector<Point> & bz, D2<SBasis> const& sb, size_t sz) { - Bezier bzx, bzy; - if(sz == 0) { - sz = std::max(sb[X].size(), sb[Y].size())*2; - } - sbasis_to_bezier(bzx, sb[X], sz); - sbasis_to_bezier(bzy, sb[Y], sz); - assert(bzx.size() == bzy.size()); - size_t n = (bzx.size() >= bzy.size()) ? bzx.size() : bzy.size(); - - bz.resize(n, Point(0,0)); - for (size_t i = 0; i < bzx.size(); ++i) - { - bz[i][X] = bzx[i]; - } - for (size_t i = 0; i < bzy.size(); ++i) - { - bz[i][Y] = bzy[i]; - } + D2<Bezier> bez; + sbasis_to_bezier(bez, sb, sz); + bz = bezier_points(bez); } /** Changes the basis of p to be Bernstein. @@ -238,7 +232,7 @@ void sbasis_to_cubic_bezier (std::vector<Point> & bz, D2<SBasis> const& sb) // is midpoint in hull: if not, the solution will be ill-conditioned, LP Bug 1428683 - if (!bezhull.contains_point(Geom::Point(midx, midy))) + if (!bezhull.contains(Geom::Point(midx, midy))) return; // calculate Bezier control arms @@ -264,11 +258,11 @@ void sbasis_to_cubic_bezier (std::vector<Point> & bz, D2<SBasis> const& sb) dely[1] = 0; } else if (std::abs(xprime[1]*yprime[0] - yprime[1]*xprime[0]) > // general case : fit mid fxn value 0.002 * std::abs(xprime[1]*xprime[0] + yprime[1]*yprime[0])) { // approx. 0.1 degree of angle - denom = xprime[1]*yprime[0] - yprime[1]*xprime[0]; + denom = 3.0*(xprime[1]*yprime[0] - yprime[1]*xprime[0]); for (int i = 0; i < 2; ++i) { numer = xprime[1 - i]*midy - yprime[1 - i]*midx; - delx[i] = xprime[i]*numer/denom/3; - dely[i] = yprime[i]*numer/denom/3; + delx[i] = xprime[i]*numer/denom; + dely[i] = yprime[i]*numer/denom; } } else if ((xprime[0]*xprime[1] < 0) || (yprime[0]*yprime[1] < 0)) { // symmetric case : use distance of closest approach numer = midx*xprime[0] + midy*yprime[0]; @@ -514,7 +508,7 @@ path_from_sbasis(D2<SBasis> const &B, double tol, bool only_cubicbeziers) { If only_cubicbeziers is true, the resulting path may only contain CubicBezier curves. TODO: some of this logic should be lifted into svg-path */ -std::vector<Geom::Path> +PathVector path_from_piecewise(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &B, double tol, bool only_cubicbeziers) { Geom::PathBuilder pb; if(B.size() == 0) return pb.peek(); diff --git a/src/2geom/sbasis-to-bezier.h b/src/2geom/sbasis-to-bezier.h index 07511f4a4..eadb47bff 100644 --- a/src/2geom/sbasis-to-bezier.h +++ b/src/2geom/sbasis-to-bezier.h @@ -32,11 +32,11 @@ * */ -#ifndef _SBASIS_TO_BEZIER -#define _SBASIS_TO_BEZIER +#ifndef LIB2GEOM_SEEN_SBASIS_TO_BEZIER_H +#define LIB2GEOM_SEEN_SBASIS_TO_BEZIER_H #include <2geom/d2.h> -#include <2geom/path.h> +#include <2geom/pathvector.h> #include <vector> @@ -44,7 +44,8 @@ namespace Geom { class PathBuilder; -void sbasis_to_bezier (Bezier & bz, SBasis const& sb, size_t sz = 0); +void sbasis_to_bezier (Bezier &bz, SBasis const &sb, size_t sz = 0); +void sbasis_to_bezier (D2<Bezier> &bz, D2<SBasis> const &sb, size_t sz = 0); void sbasis_to_bezier (std::vector<Point> & bz, D2<SBasis> const& sb, size_t sz = 0); void sbasis_to_cubic_bezier (std::vector<Point> & bz, D2<SBasis> const& sb); void bezier_to_sbasis (SBasis & sb, Bezier const& bz); @@ -65,7 +66,7 @@ sbasis_to_bezier(D2<SBasis> const &B, unsigned q = 0); #endif -std::vector<Path> path_from_piecewise(Piecewise<D2<SBasis> > const &B, double tol, bool only_cubicbeziers = false); +PathVector path_from_piecewise(Piecewise<D2<SBasis> > const &B, double tol, bool only_cubicbeziers = false); Path path_from_sbasis(D2<SBasis> const &B, double tol, bool only_cubicbeziers = false); inline Path cubicbezierpath_from_sbasis(D2<SBasis> const &B, double tol) @@ -73,10 +74,7 @@ inline Path cubicbezierpath_from_sbasis(D2<SBasis> const &B, double tol) } // end namespace Geom - - #endif - /* Local Variables: mode:c++ diff --git a/src/2geom/sbasis.cpp b/src/2geom/sbasis.cpp index b56e03c74..4f1df621e 100644 --- a/src/2geom/sbasis.cpp +++ b/src/2geom/sbasis.cpp @@ -466,6 +466,15 @@ SBasis compose(SBasis const &a, SBasis const &b, unsigned k) { return r; } +SBasis portion(const SBasis &t, double from, double to) { + double fv = t.valueAt(from); + double tv = t.valueAt(to); + SBasis ret = compose(t, Linear(from, to)); + ret.at0() = fv; + ret.at1() = tv; + return ret; +} + /* Inversion algorithm. The notation is certainly very misleading. The pseudocode should say: @@ -632,7 +641,6 @@ SBasis compose_inverse(SBasis const &f, SBasis const &g, unsigned order, double //TODO: handle det~0!! if (fabs(det)<zero){ - det = zero; a=b=0; }else{ a=( q01*r10-q10*r01)/det; diff --git a/src/2geom/sbasis.h b/src/2geom/sbasis.h index ca864ac7c..787e8b722 100644 --- a/src/2geom/sbasis.h +++ b/src/2geom/sbasis.h @@ -1,7 +1,6 @@ -/** - * \file - * \brief Defines S-power basis function class - * +/** @file + * @brief Polynomial in symmetric power basis (S-basis) + *//* * Authors: * Nathan Hurst <njh@mail.csse.monash.edu.au> * Michael Sloan <mgsloan@gmail.com> @@ -32,8 +31,8 @@ * the specific language governing rights and limitations. */ -#ifndef SEEN_SBASIS_H -#define SEEN_SBASIS_H +#ifndef LIB2GEOM_SEEN_SBASIS_H +#define LIB2GEOM_SEEN_SBASIS_H #include <vector> #include <cassert> #include <iostream> @@ -61,13 +60,13 @@ class SBasis : public SBasisN<1>; }; #else -namespace Geom{ +namespace Geom { /** -* \brief S-power basis function class -* -* An empty SBasis is identically 0. */ -class SBasis{ + * @brief Polynomial in symmetric power basis + * @ingroup Fragments + */ +class SBasis { std::vector<Linear> d; void push_back(Linear const&l) { d.push_back(l); } @@ -101,11 +100,17 @@ public: SBasis() {} - explicit SBasis(double a) { - push_back(Linear(a,a)); + explicit SBasis(double a) + : d(1) + { + d[0][0] = a; + d[0][1] = a; } - explicit SBasis(double a, double b) { - push_back(Linear(a,b)); + explicit SBasis(double a, double b) + : d(1) + { + d[0][0] = a; + d[0][1] = b; } SBasis(SBasis const & a) : d(a.d) @@ -117,10 +122,69 @@ public: push_back(bo); } SBasis(Linear* bo) { - push_back(*bo); + if (bo) { + push_back(*bo); + } } explicit SBasis(size_t n, Linear const&l) : d(n, l) {} + SBasis(Coord c0, Coord c1, Coord c2, Coord c3) + : d(2) + { + d[0][0] = c0; + d[1][0] = c1; + d[1][1] = c2; + d[0][1] = c3; + } + SBasis(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5) + : d(3) + { + d[0][0] = c0; + d[1][0] = c1; + d[2][0] = c2; + d[2][1] = c3; + d[1][1] = c4; + d[0][1] = c5; + } + SBasis(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5, + Coord c6, Coord c7) + : d(4) + { + d[0][0] = c0; + d[1][0] = c1; + d[2][0] = c2; + d[3][0] = c3; + d[3][1] = c4; + d[2][1] = c5; + d[1][1] = c6; + d[0][1] = c7; + } + SBasis(Coord c0, Coord c1, Coord c2, Coord c3, Coord c4, Coord c5, + Coord c6, Coord c7, Coord c8, Coord c9) + : d(5) + { + d[0][0] = c0; + d[1][0] = c1; + d[2][0] = c2; + d[3][0] = c3; + d[4][0] = c4; + d[4][1] = c5; + d[3][1] = c6; + d[2][1] = c7; + d[1][1] = c8; + d[0][1] = c9; + } + + // construct from a sequence of coefficients + template <typename Iter> + SBasis(Iter first, Iter last) { + assert(std::distance(first, last) % 2 == 0); + for (; first != last; ++first) { + --last; + push_back(Linear(*first, *last)); + } + } + //IMPL: FragmentConcept typedef double output_type; inline bool isZero(double eps=EPSILON) const { @@ -140,12 +204,10 @@ public: } bool isFinite() const; - inline double at0() const { - if(empty()) return 0; else return (*this)[0][0]; - } - inline double at1() const{ - if(empty()) return 0; else return (*this)[0][1]; - } + inline Coord at0() const { return (*this)[0][0]; } + inline Coord &at0() { return (*this)[0][0]; } + inline Coord at1() const { return (*this)[0][1]; } + inline Coord &at1() { return (*this)[0][1]; } int degreesOfFreedom() const { return size()*2;} @@ -348,8 +410,8 @@ SBasis compose_inverse(SBasis const &f, SBasis const &g, unsigned order=2, doubl \return sbasis \relates SBasis */ -inline SBasis portion(const SBasis &t, double from, double to) { return compose(t, Linear(from, to)); } -inline SBasis portion(const SBasis &t, Interval ivl) { return compose(t, Linear(ivl.min(), ivl.max())); } +SBasis portion(const SBasis &t, double from, double to); +inline SBasis portion(const SBasis &t, Interval const &ivl) { return portion(t, ivl.min(), ivl.max()); } // compute f(g) inline SBasis @@ -364,7 +426,10 @@ inline std::ostream &operator<< (std::ostream &out_file, const Linear &bo) { inline std::ostream &operator<< (std::ostream &out_file, const SBasis & p) { for(unsigned i = 0; i < p.size(); i++) { - out_file << p[i] << "s^" << i << " + "; + if (i != 0) { + out_file << " + "; + } + out_file << p[i] << "s^" << i; } return out_file; } diff --git a/src/2geom/shape.cpp b/src/2geom/shape.cpp deleted file mode 100644 index e9f5e55dc..000000000 --- a/src/2geom/shape.cpp +++ /dev/null @@ -1,689 +0,0 @@ -/** - * \brief Shapes are special paths on which boolops can be performed - * - * Authors: - * Michael G. Sloan <mgsloan@gmail.com> - * Nathan Hurst <njh@mail.csse.monash.edu.au> - * MenTaLguY <mental@rydia.net> - * - * Copyright 2007-2009 Authors - * - * This library is free software; you can redistribute it and/or - * modify it either under the terms of the GNU Lesser General Public - * License version 2.1 as published by the Free Software Foundation - * (the "LGPL") or, at your option, under the terms of the Mozilla - * Public License Version 1.1 (the "MPL"). If you do not alter this - * notice, a recipient may use your version of this file under either - * the MPL or the LGPL. - * - * You should have received a copy of the LGPL along with this library - * in the file COPYING-LGPL-2.1; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * You should have received a copy of the MPL along with this library - * in the file COPYING-MPL-1.1 - * - * The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY - * OF ANY KIND, either express or implied. See the LGPL or the MPL for - * the specific language governing rights and limitations. - * - */ - -#include <2geom/shape.h> - -#include <2geom/utils.h> -#include <2geom/sweep.h> -#include <2geom/ord.h> - -#include <iostream> -#include <algorithm> -#include <cstdlib> - -//#define SHAPE_DEBUG // turns on debug outputting to cout. - -namespace Geom { - -// A little sugar for appending a list to another -template<typename T> -void append(T &a, T const &b) { - a.insert(a.end(), b.begin(), b.end()); -} - -//Orders a list of indices according to their containment within eachother. -struct ContainmentOrder { - std::vector<Region> const *rs; - explicit ContainmentOrder(std::vector<Region> const *r) : rs(r) {} - bool operator()(unsigned a, unsigned b) const { return (*rs)[b].contains((*rs)[a]); } -}; - -//Returns the list of regions containing a particular point. Useful in tandem with ContainmentOrder -std::vector<unsigned> Shape::containment_list(Point p) const { - std::vector<Rect> pnt; - pnt.push_back(Rect(p, p)); - std::vector<std::vector<unsigned> > cull = sweep_bounds(pnt, bounds(*this)); - std::vector<unsigned> containers; - if(cull[0].size() == 0) return containers; - for(unsigned i = 0; i < cull[0].size(); i++) - if(content[cull[0][i]].contains(p)) containers.push_back(cull[0][i]); - return containers; -} - -/* Used within shape_boolean and related functions, as the name describes, finds the - * first false within the list of lists of booleans. - */ -void first_false(std::vector<std::vector<bool> > visited, unsigned &i, unsigned &j) { - for(i = 0, j = 0; i < visited.size(); i++) { - std::vector<bool>::iterator unvisited = std::find(visited[i].begin(), visited[i].end(), false); - if(unvisited != visited[i].end()) { - j = unvisited - visited[i].begin(); - break; - } - } -} - -// Finds a crossing in a list of them, given the sorting index. -unsigned find_crossing(Crossings const &cr, Crossing x, unsigned i) { - return std::lower_bound(cr.begin(), cr.end(), x, CrossingOrder(i)) - cr.begin(); -} - -/* This function handles boolean ops on shapes. The first parameter is a bool - * which determines its behavior in each combination of cases. For proper - * fill information and noncrossing behavior, the fill data of the regions - * must be correct. The boolean parameter determines whether the operation - * is a union or a subtraction. Reversed paths represent inverse regions, - * where everything is included in the fill except for the insides. - * - * Here is a chart of the behavior under various circumstances: - * - * rev = false (union) - * A - * F H - * F A+B -> F A-B -> H - *B - * H B-A -> H AxB -> H - * - * rev = true (intersect) - * A - * F H - * F AxB -> F B-A -> F - *B - * H A-B -> F A+B -> H - * - * F/H = Fill outer / Hole outer - * A/B specify operands - * + = union, - = subtraction, x = intersection - * -> read as "produces" - * - * This is the main function of boolops, yet its operation isn't very complicated. - * It traverses the crossings, and uses the crossing direction to decide whether - * the next segment should be taken from A or from B. The second half of the - * function deals with figuring out what to do with bits that have no intersection. - */ -Shape shape_boolean(bool rev, Shape const & a, Shape const & b, CrossingSet const & crs) { - const Regions ac = a.content, bc = b.content; - - //Keep track of which crossings we've hit. - std::vector<std::vector<bool> > visited; - for(unsigned i = 0; i < crs.size(); i++) - visited.push_back(std::vector<bool>(crs[i].size(), false)); - - //Traverse the crossings, creating chunks - Regions chunks; - while(true) { - unsigned i, j; - first_false(visited, i, j); - if(i == visited.size()) break; - - Path res; - do { - Crossing cur = crs[i][j]; - visited[i][j] = true; - - //get indices of the dual: - unsigned io = cur.getOther(i), jo = find_crossing(crs[io], cur, io); - if(jo < visited[io].size()) visited[io][jo] = true; - - //main driving logic - if(logical_xor(cur.dir, rev)) { - if(i >= ac.size()) { i = io; j = jo; } - j++; - if(j >= crs[i].size()) j = 0; - Crossing next = crs[i][j]; - ac[next.a].boundary.appendPortionTo(res, cur.ta, next.ta); - } else { - if(i < ac.size()) { i = io; j = jo; } - j++; - if(j >= crs[i].size()) j = 0; - Crossing next = crs[i][j]; - bc[next.b - ac.size()].boundary.appendPortionTo(res, cur.tb, next.tb); - } - } while (!visited[i][j]); - if(res.size() > 0) chunks.push_back(Region(res)); - } - - //If true, then we are on the 'subtraction diagonal' - bool const on_sub = logical_xor(a.fill, b.fill); - //If true, outer paths are filled - bool const res_fill = rev ? (on_sub || (a.fill && b.fill)) : (a.fill && b.fill); - - //Handle unintersecting portions - for(unsigned i = 0; i < crs.size(); i++) { - if(crs[i].size() == 0) { - bool env; - bool on_a = i < ac.size(); - Region const & r(on_a ? ac[i] : bc[i - ac.size()]); - Shape const & other(on_a ? b : a); - - std::vector<unsigned> containers = other.containment_list(r.boundary.initialPoint()); - if(containers.empty()) { - //not included in any container, the environment fill is the opposite of the outer fill - env = !res_fill; - if(on_sub && logical_xor(other.fill, res_fill)) env = !env; //If on the subtractor, invert the environment fill - } else { - //environment fill is the same as the inner-most container - std::vector<unsigned>::iterator cit = std::min_element(containers.begin(), containers.end(), ContainmentOrder(&other.content)); - env = other[*cit].isFill(); - } - if(!logical_xor(rev, env)) chunks.push_back(r); //When unioning, environment must be hole for inclusion, when intersecting, it must be filled - } - } - - return Shape(chunks, res_fill); -} - -// Just a convenience wrapper for shape_boolean, which handles the crossings -Shape shape_boolean(bool rev, Shape const & a, Shape const & b) { - CrossingSet crs = crossings_between(a, b); - - return shape_boolean(rev, a, b, crs); -} - - -// Some utility functions for boolop: - -std::vector<double> region_sizes(Shape const &a) { - std::vector<double> ret; - for(unsigned i = 0; i < a.size(); i++) { - ret.push_back(double(a[i].size())); - } - return ret; -} - -Shape shape_boolean_ra(bool rev, Shape const &a, Shape const &b, CrossingSet const &crs) { - return shape_boolean(rev, a.inverse(), b, reverse_ta(crs, a.size(), region_sizes(a))); -} - -Shape shape_boolean_rb(bool rev, Shape const &a, Shape const &b, CrossingSet const &crs) { - return shape_boolean(rev, a, b.inverse(), reverse_tb(crs, a.size(), region_sizes(b))); -} - -/* This is a function based on shape_boolean which allows boolean operations - * to be specified as a logic table. This logic table is 4 bit-flags, which - * correspond to the elements of the 'truth table' for a particular operation. - * These flags are defined with the enums starting with BOOLOP_ . - * - * NOTE: currently doesn't work, as the CrossingSet reversal functions crash - */ -Shape boolop(Shape const &a, Shape const &b, unsigned flags, CrossingSet const &crs) { - THROW_NOTIMPLEMENTED(); - flags &= 15; - if(flags <= BOOLOP_UNION) { - switch(flags) { - case BOOLOP_INTERSECT: return shape_boolean(true, a, b, crs); - case BOOLOP_SUBTRACT_A_B: return shape_boolean_rb(true, a, b, crs); - case BOOLOP_IDENTITY_A: return a; - case BOOLOP_SUBTRACT_B_A: return shape_boolean_ra(true, a, b, crs); - case BOOLOP_IDENTITY_B: return b; - case BOOLOP_EXCLUSION: { - Shape res = shape_boolean_rb(true, a, b, crs); - append(res.content, shape_boolean_ra(true, a, b, crs).content); - return res; - } - case BOOLOP_UNION: return shape_boolean(false, a, b); - } - } else { - flags = ~flags & 15; - switch(flags - BOOLOP_NEITHER) { - case BOOLOP_SUBTRACT_A_B: return shape_boolean_ra(false, a, b, crs); - case BOOLOP_SUBTRACT_B_A: return shape_boolean_rb(false, a, b, crs); - case BOOLOP_EXCLUSION: { - Shape res = shape_boolean_ra(false, a, b, CrossingSet(crs)); - append(res.content, shape_boolean_rb(false, a, b, CrossingSet(crs)).content); - return res; - } - } - return boolop(a, b, flags, crs).inverse(); - } - return Shape(); -} - -/* This version of the boolop function doesn't require a set of crossings, as - * it computes them for you. This is more efficient in some cases, as the - * shape can be inverted before finding crossings. In the special case of - * exclusion it uses the other version of boolop. - */ -Shape boolop(Shape const &a, Shape const &b, unsigned flags) { - flags &= 15; - if(flags <= BOOLOP_UNION) { - switch(flags) { - case BOOLOP_INTERSECT: return shape_boolean(true, a, b); - case BOOLOP_SUBTRACT_A_B: return shape_boolean(true, a, b.inverse()); - case BOOLOP_IDENTITY_A: return a; - case BOOLOP_SUBTRACT_B_A: return shape_boolean(true, b, a.inverse()); - case BOOLOP_IDENTITY_B: return b; - case BOOLOP_EXCLUSION: { - Shape res = shape_boolean(true, a, b.inverse()); - append(res.content, shape_boolean(true, b, a.inverse()).content); - return res; - } //return boolop(a, b, flags, crossings_between(a, b)); - case BOOLOP_UNION: return shape_boolean(false, a, b); - } - } else { - flags = ~flags & 15; - switch(flags) { - case BOOLOP_SUBTRACT_A_B: return shape_boolean(false, b, a.inverse()); - case BOOLOP_SUBTRACT_B_A: return shape_boolean(false, a, b.inverse()); - case BOOLOP_EXCLUSION: { - Shape res = shape_boolean(false, a, b.inverse()); - append(res.content, shape_boolean(false, b, a.inverse()).content); - return res; - } //return boolop(a, b, flags, crossings_between(a, b)); - } - return boolop(a, b, flags).inverse(); - } - return Shape(); -} - -int paths_winding(std::vector<Path> const &ps, Point p) { - int ret = 0; - for(unsigned i = 0; i < ps.size(); i++) - ret += winding(ps[i], p); - return ret; -} - -void add_to_shape(Shape &s, Path const &p, bool fill) { - if(fill) - s.content.push_back(Region(p).asFill()); - else - s.content.push_back(Region(p).asHole()); -} - -int inner_winding(Path const & p, std::vector<Path> const &ps) { - Point pnt = p.initialPoint(); - return paths_winding(ps, pnt) - winding(p, pnt) + 1; -} - -double fudgerize(double d, bool rev) { - double ret = rev ? d - 0.01 : d + 0.01; - if(ret < 0) ret = 0; - return ret; -} - -unsigned pick_coincident(unsigned ix, unsigned jx, bool &rev, std::vector<Path> const &ps, CrossingSet const &crs) { - unsigned ex_jx = jx; - unsigned oix = crs[ix][jx].getOther(ix); - double otime = crs[ix][jx].getTime(oix); - Point cross_point = ps[oix].pointAt(otime), - along = ps[oix].pointAt(fudgerize(otime, rev)) - cross_point, - prev = -along; - bool ex_dir = rev; - for(unsigned k = jx; k < crs[ix].size(); k++) { - unsigned koix = crs[ix][k].getOther(ix); - if(koix == oix) { - if(!are_near(otime, crs[ix][k].getTime(oix))) break; - for(unsigned dir = 0; dir < 2; dir++) { - Point val = ps[ix].pointAt(fudgerize(crs[ix][k].getTime(ix), dir)) - cross_point; - Cmp to_prev = cmp(cross(val, prev), 0); - Cmp from_along = cmp(cross(along, val), 0); - Cmp c = cmp(from_along, to_prev); - if(c == EQUAL_TO && from_along == LESS_THAN) { - ex_jx = k; - prev = val; - ex_dir = dir; - } - } - } - } - rev = ex_dir; - return ex_jx; -} - -unsigned crossing_along(double t, unsigned ix, unsigned jx, bool dir, Crossings const & crs) { - Crossing cur = Crossing(t, t, ix, ix, false); - if(jx < crs.size()) { - double ct = crs[jx].getTime(ix); - if(t == ct) { - cur = crs[jx]; - if(cur.a == cur.b) { - if(jx+1 <= crs.size() && crs[jx+1].getOther(ix) == ix) return jx+1; - if(jx > 0 && crs[jx-1].getOther(ix) == ix) return jx-1; - } - } - } - if(!dir) { - jx = std::upper_bound(crs.begin(), crs.end(), cur, CrossingOrder(ix)) - crs.begin(); - } else { - jx = std::lower_bound(crs.begin(), crs.end(), cur, CrossingOrder(ix)) - crs.begin(); - if(jx == 0) jx = crs.size() - 1; else jx--; - jx = std::lower_bound(crs.begin(), crs.end(), crs[jx], CrossingOrder(ix)) - crs.begin(); - } - if(jx >= crs.size()) jx = 0; - return jx; -} - -void crossing_dual(unsigned &i, unsigned &j, CrossingSet const & crs) { - Crossing cur = crs[i][j]; - i = cur.getOther(i); -#ifdef SHAPE_DEBUG - std::cout << i << "\n"; -#endif - if(crs[i].empty()) - j = 0; - else - j = std::lower_bound(crs[i].begin(), crs[i].end(), cur, CrossingOrder(i)) - crs[i].begin(); -} - -//locate a crossing on the outside, by casting a ray through the middle of the bbox -void outer_crossing(unsigned &ix, unsigned &jx, bool & dir, std::vector<Path> const & ps, CrossingSet const & crs) { - Rect bounds = *(ps[ix].boundsFast()); - double ry = bounds[Y].middle(); - double max_val = bounds.left(), max_t = 0; - ix = ps.size(); - for(unsigned i = 0; i < ps.size(); i++) { - if(!crs[i].empty()) { - std::vector<double> rts = ps[i].roots(ry, Y); - for(unsigned j = 0; j < rts.size(); j++) { - double val = ps[i].valueAt(rts[j], X); - if(val > max_val) { - ix = i; - max_val = val; - max_t = rts[j]; - } - } - } - } - if(ix != ps.size()) { - dir = ps[ix].valueAt(max_t + 0.01, Y) > - ps[ix].valueAt(max_t - 0.01, Y); - jx = crossing_along(max_t, ix, jx, dir, crs[ix]); - } -} - -std::vector<Path> inner_sanitize(std::vector<Path> const & ps) { - CrossingSet crs(crossings_among(ps)); - - Regions chunks; - - std::vector<bool> used_path(ps.size(), false); - std::vector<std::vector<bool> > visited; - for(unsigned i = 0; i < crs.size(); i++) - visited.push_back(std::vector<bool>(crs[i].size(), false)); - - std::vector<Path> result_paths; - - while(true) { - unsigned ix = 0, jx = 0; - bool dir = false; - - //find an outer crossing by trying various paths and checking if the crossings are used - for(; ix < crs.size(); ix++) { - //TODO: optimize so it doesn't unecessarily check stuff - bool cont = true; - for(unsigned j = 0; j < crs[ix].size(); j++) { - if(!visited[ix][j]) { cont = false; break; } - } - if(cont) continue; - unsigned rix = ix, rjx = jx; - outer_crossing(rix, rjx, dir, ps, crs); - if(rix >= crs.size() || visited[rix][rjx]) continue; - ix = rix; jx = rjx; - break; - } - if(ix == crs.size()) break; - crossing_dual(ix, jx, crs); - - dir = !dir; - - Path res; - do { - visited[ix][jx] = true; - //unsigned nix = ix, njx = jx; - //crossing_dual(nix, njx, crs); - //visited[nix][njx] = true; - unsigned fix = ix, fjx = jx; - - bool new_dir = dir; - - jx = crossing_along(crs[ix][jx].getTime(ix), ix, jx, dir, crs[ix]); - if(crs[ix][jx].a != crs[ix][jx].b) crossing_dual(ix, jx, crs); else new_dir = !new_dir; - jx = pick_coincident(ix, jx, new_dir, ps, crs); - - //unsigned nix = ix, njx = jx; - //crossing_dual(nix, njx, crs); - - Crossing from = crs[fix][fjx], - to = crs[ix][jx]; - if(dir) { - // backwards -#ifdef SHAPE_DEBUG - std::cout << "r" << ix << "[" << from.getTime(ix) << ", " << to.getTime(ix) << "]\n"; -#endif - Path p = ps[ix].portion(from.getTime(ix), to.getTime(ix)).reverse(); - for(unsigned i = 0; i < p.size(); i++) - res.append(p[i], Path::STITCH_DISCONTINUOUS); - } else { - // forwards -#ifdef SHAPE_DEBUG - std::cout << "f" << ix << "[" << from.getTime(ix) << ", " << to.getTime(ix) << "]\n"; -#endif - ps[ix].appendPortionTo(res, from.getTime(ix), to.getTime(ix)); - } - dir = new_dir; - } while(!visited[ix][jx]); -#ifdef SHAPE_DEBUG - std::cout << "added " << res.size() << "\n"; -#endif - result_paths.push_back(res); - } - for(unsigned i = 0; i < crs.size(); i++) { - if(crs[i].empty() && !used_path[i]) - result_paths.push_back(ps[i]); - } - return result_paths; -} - -Shape sanitize(std::vector<Path> const & ps) { - std::vector<Path> res; - for(unsigned i = 0; i < ps.size(); i++) { - append(res, inner_sanitize(std::vector<Path>(1, ps[i]))); - } - return stopgap_cleaner(res); -} - -/* WIP sanitizer: -unsigned pick_coincident(unsigned ix, unsigned jx, bool pref, bool &rev, std::vector<Path> const &ps, CrossingSet const &crs) { - unsigned ex_jx = jx; - unsigned oix = crs[ix][jx].getOther(ix); - double otime = crs[ix][jx].getTime(oix); - Point cross_point = ps[oix].pointAt(otime), - along = ps[oix].pointAt(otime + (rev ? -0.01 : 0.01)) - cross_point, - prev = -along; - bool ex_dir = rev; - for(unsigned k = jx; k < crs[ix].size(); k++) { - unsigned koix = crs[ix][k].getOther(ix); - if(koix == oix) { - if(!are_near(otime, crs[ix][k].getTime(oix))) break; - for(unsigned dir = 0; dir < 2; dir++) { - Point val = ps[ix].pointAt(crs[ix][k].getTime(ix) + (dir ? -0.01 : 0.01)) - cross_point; - Cmp to_prev = cmp(cross(val, prev), 0); - Cmp from_along = cmp(cross(along, val), 0); - Cmp c = cmp(from_along, to_prev); - if(c == EQUAL_TO && (from_along == LESS_THAN) == pref) { - ex_jx = k; - prev = val; - ex_dir = dir; - } - } - } - } - rev = ex_dir; - return ex_jx; -} - -unsigned corner_index(unsigned &i) { - div_t div_res = div(i, 4); - i = div_res.quot; - return div_res.rem; -} - -bool corner_direction(unsigned ix, unsigned jc, unsigned corner, CrossingSet const &crs) { - if(crs[ix][jc].a == ix) return corner > 1; else return corner %2 == 1; -} - -Shape sanitize(std::vector<Path> const & ps) { - CrossingSet crs = crossings_among(ps); - - //Keep track of which CORNERS we've hit. - // FF FR RF RR, first is A dir, second B dir - std::vector<std::vector<bool> > visited; - for(unsigned i = 0; i < crs.size(); i++) - visited.push_back(std::vector<bool>(crs[i].size()*4, false)); - - Regions chunks; - while(true) { - unsigned i, j; - first_false(visited, i, j); - unsigned corner = corner_index(j); - - if(i == visited.size()) break; - - bool dir = corner_direction(i, j, corner, crs); - - //Figure out whether we hug the path cw or ccw, based on the orientation of the initial corner: - unsigned oix = crs[i][j].getOther(i); - double otime = crs[i][j].getTime(oix); - bool odir = (oix == crs[i][j].a) ? corner > 1 : corner % 2 == 1; - Point cross_point = ps[oix].pointAt(otime), - along = ps[oix].pointAt(otime + (odir ? -0.01 : 0.01)) - cross_point, - val = ps[i].pointAt(crs[i][j].getTime(i) + (dir ? -0.01 : 0.01)) - cross_point; - - Cmp from_along = cmp(cross(along, val), 0); - bool cw = from_along == LESS_THAN; - std::cout << "cw = " << cw << "\n"; - Path res; - do { - Crossing cur = crs[i][j]; - visited[i][j*4+corner] = true; - - unsigned fix = i, fjx = j; - crossing_dual(i, j, crs); - visited[i][j*4+corner] = true; - i = fix; j = fjx; - - j = crossing_along(crs[i][j].getTime(i), i, j, dir, crs[i]); - - crossing_dual(i, j, crs); - - bool new_dir = dir; - pick_coincident(i, j, cw, new_dir, ps, crs); - - Crossing from = crs[fix][fjx], - to = crs[i][j]; - if(dir) { - // backwards - std::cout << "r" << i << "[" << to.getTime(i) << ", " << from.getTime(i) << "]\n"; - Path p = ps[i].portion(to.getTime(i) + 0.001, from.getTime(i)).reverse(); - for(unsigned k = 0; k < p.size(); k++) - res.append(p[k]); - } else { - // forwards - std::cout << "f" << i << "[" << from.getTime(i) << ", " << to.getTime(i) << "]\n"; - ps[i].appendPortionTo(res, from.getTime(i) + 0.001, to.getTime(i)); - } - if(i == to.a) - corner = (new_dir ? 2 : 0) + (dir ? 1 : 0); - else - corner = (new_dir ? 1 : 0) + (dir ? 2 : 0); - dir = new_dir; - } while(!visited[i][j*4+corner]); - chunks.push_back(Region(res)); -// if(use) { -// chunks.push_back(Region(res, true)); -// } - } - return Shape(chunks); -// return ret; -} */ - -/* This transforms a shape by a matrix. In the case that the matrix flips - * the shape, it reverses the paths in order to preserve the fill. - */ -Shape Shape::operator*(Affine const &m) const { - Shape ret; - for(unsigned i = 0; i < size(); i++) - ret.content.push_back(content[i] * m); - ret.fill = fill; - return ret; -} - -// Inverse is a boolean not, and simply reverses all the paths & fill flags -Shape Shape::inverse() const { - Shape ret; - for(unsigned i = 0; i < size(); i++) - ret.content.push_back(content[i].inverse()); - ret.fill = !fill; - return ret; -} - -bool Shape::contains(Point const &p) const { - std::vector<unsigned> containers = containment_list(p); - if(containers.empty()) return !isFill(); - unsigned ix = *min_element(containers.begin(), containers.end(), ContainmentOrder(&content)); - return content[ix].isFill(); -} - -Shape stopgap_cleaner(std::vector<Path> const &ps) { - if(ps.empty()) return Shape(false); - Shape ret; - for(unsigned i = 0; i < ps.size(); i++) - add_to_shape(ret, ps[i], inner_winding(ps[i], ps) % 2 != 0); - return ret; -} - -bool Shape::inside_invariants() const { //semi-slow & easy to violate - for(unsigned i = 0; i < size(); i++) - if( logical_xor(content[i].isFill(), contains(content[i].boundary.initialPoint())) ) return false; - return true; -} -bool Shape::region_invariants() const { //semi-slow - for(unsigned i = 0; i < size(); i++) - if(!content[i].invariants()) return false; - return true; -} -bool Shape::cross_invariants() const { //slow - CrossingSet crs; // = crossings_among(paths_from_regions(content)); - for(unsigned i = 0; i < crs.size(); i++) - if(!crs[i].empty()) return false; - return true; -} - -bool Shape::invariants() const { - return inside_invariants() && region_invariants() && cross_invariants(); -} - -} - -/* - 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/2geom/shape.h b/src/2geom/shape.h deleted file mode 100644 index 0a7ee9709..000000000 --- a/src/2geom/shape.h +++ /dev/null @@ -1,148 +0,0 @@ -/** - * \brief Shapes are special paths on which boolops can be performed - * - * Authors: - * Michael G. Sloan <mgsloan@gmail.com> - * Nathan Hurst <njh@mail.csse.monash.edu.au> - * MenTaLguY <mental@rydia.net> - * - * Copyright 2007-2009 Authors - * - * This library is free software; you can redistribute it and/or - * modify it either under the terms of the GNU Lesser General Public - * License version 2.1 as published by the Free Software Foundation - * (the "LGPL") or, at your option, under the terms of the Mozilla - * Public License Version 1.1 (the "MPL"). If you do not alter this - * notice, a recipient may use your version of this file under either - * the MPL or the LGPL. - * - * You should have received a copy of the LGPL along with this library - * in the file COPYING-LGPL-2.1; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * You should have received a copy of the MPL along with this library - * in the file COPYING-MPL-1.1 - * - * The contents of this file are subject to the Mozilla Public License - * Version 1.1 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY - * OF ANY KIND, either express or implied. See the LGPL or the MPL for - * the specific language governing rights and limitations. - * - */ - -#ifndef __2GEOM_SHAPE_H -#define __2GEOM_SHAPE_H - -#include <vector> -#include <set> - -#include <2geom/region.h> - -//TODO: BBOX optimizations - -namespace Geom { - -enum { - BOOLOP_JUST_A = 1, - BOOLOP_JUST_B = 2, - BOOLOP_BOTH = 4, - BOOLOP_NEITHER = 8 -}; - -enum { - BOOLOP_NULL = 0, - BOOLOP_INTERSECT = BOOLOP_BOTH, - BOOLOP_SUBTRACT_A_B = BOOLOP_JUST_B, - BOOLOP_IDENTITY_A = BOOLOP_JUST_A | BOOLOP_BOTH, - BOOLOP_SUBTRACT_B_A = BOOLOP_JUST_A, - BOOLOP_IDENTITY_B = BOOLOP_JUST_B | BOOLOP_BOTH, - BOOLOP_EXCLUSION = BOOLOP_JUST_A | BOOLOP_JUST_B, - BOOLOP_UNION = BOOLOP_JUST_A | BOOLOP_JUST_B | BOOLOP_BOTH -}; - -class Shape { - Regions content; - mutable bool fill; - //friend Shape shape_region_boolean(bool rev, Shape const & a, Region const & b); - friend CrossingSet crossings_between(Shape const &a, Shape const &b); - friend Shape shape_boolean(bool rev, Shape const &, Shape const &, CrossingSet const &); - friend Shape boolop(Shape const &a, Shape const &b, unsigned); - friend Shape boolop(Shape const &a, Shape const &b, unsigned, CrossingSet const &); - friend void add_to_shape(Shape &s, Path const &p, bool); - public: - Shape() : fill(true) {} - explicit Shape(Region const & r) { - content = Regions(1, r); - fill = r.fill; - } - explicit Shape(Regions const & r) : content(r) { update_fill(); } - explicit Shape(bool f) : fill(f) {} - Shape(Regions const & r, bool f) : content(r), fill(f) {} - - Regions getContent() const { return content; } - bool isFill() const { return fill; } - - unsigned size() const { return content.size(); } - const Region &operator[](unsigned ix) const { return content[ix]; } - - Shape inverse() const; - Shape operator*(Affine const &m) const; - - bool contains(Point const &p) const; - - bool inside_invariants() const; //semi-slow & easy to violate : checks that the insides are inside, the outsides are outside - bool region_invariants() const; //semi-slow : checks for self crossing - bool cross_invariants() const; //slow : checks that everything is disjoint - bool invariants() const; //vera slow (combo, checks the above) - - private: - std::vector<unsigned> containment_list(Point p) const; - void update_fill() const { - unsigned ix = outer_index(content); - if(ix < size()) - fill = content[ix].fill; - else if(size() > 0) - fill = content.front().fill; - else - fill = true; - } -}; - -inline CrossingSet crossings_between(Shape const &a, Shape const &b) { return crossings(paths_from_regions(a.content), paths_from_regions(b.content)); } - -Shape shape_boolean(bool rev, Shape const &, Shape const &, CrossingSet const &); -Shape shape_boolean(bool rev, Shape const &, Shape const &); - -//unsigned pick_coincident(unsigned ix, unsigned jx, bool &rev, std::vector<Path> const &ps, CrossingSet const &crs); -//void outer_crossing(unsigned &ix, unsigned &jx, bool & dir, std::vector<Path> const & ps, CrossingSet const & crs); -void crossing_dual(unsigned &i, unsigned &j, CrossingSet const & crs); -unsigned crossing_along(double t, unsigned ix, unsigned jx, bool dir, Crossings const & crs); - -Shape boolop(Shape const &, Shape const &, unsigned flags); -Shape boolop(Shape const &, Shape const &, unsigned flags, CrossingSet &); - -Shape sanitize(std::vector<Path> const &ps); - -Shape stopgap_cleaner(std::vector<Path> const &ps); - -inline std::vector<Path> desanitize(Shape const & s) { - return paths_from_regions(s.getContent()); -} - -} - -#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/2geom/solve-bezier-one-d.cpp b/src/2geom/solve-bezier-one-d.cpp index 3d87d4926..3a25e9808 100644 --- a/src/2geom/solve-bezier-one-d.cpp +++ b/src/2geom/solve-bezier-one-d.cpp @@ -27,7 +27,7 @@ static int SGN(t x) { return (x > 0 ? 1 : (x < 0 ? -1 : 0)); } **/ class Bernsteins{ public: - static const size_t MAX_DEPTH = 22; + static const size_t MAX_DEPTH = 53; size_t degree, N; std::vector<double> &solutions; //std::vector<double> bc; @@ -38,17 +38,9 @@ public: { } - void subdivide(double const *V, - double t, - double *Left, - double *Right); - unsigned control_poly_flat_enough(double const *V); - double horner(const double *b, double t); - - void find_bernstein_roots(double const *w, /* The control points */ unsigned depth, /* The depth of the recursion */ @@ -134,7 +126,7 @@ void Bernsteins::find_bernstein_roots(double const *w, /* The control points */ r = (fs*t - ft*s) / (fs - ft); if (fabs(t-s) < e * fabs(t+s)) break; - double fr = horner(w, r); + double fr = bernstein_value_at(r, w, degree); if (fr * ft > 0) { @@ -191,24 +183,6 @@ void Bernsteins::find_bernstein_roots(double const *w, /* The control points */ delete[] LR; } - -// suggested by Sederberg. -double Bernsteins::horner(const double *b, double t) -{ - double u, tn, tmp; - u = 1.0 - t; - tn = 1.0; - tmp = b[0] * u; - for(size_t i = 1; i < degree; ++i) - { - tn *= t; - tmp = (tmp + tn*bc[i]*b[i]) * u; - } - return (tmp + tn*t*b[degree]); -} - - - #if 0 /* * control_poly_flat_enough : diff --git a/src/2geom/solve-bezier-parametric.cpp b/src/2geom/solve-bezier-parametric.cpp index 9b0feaee4..2fb3f41da 100644 --- a/src/2geom/solve-bezier-parametric.cpp +++ b/src/2geom/solve-bezier-parametric.cpp @@ -1,8 +1,9 @@ -#include <2geom/solver.h> +#include <2geom/bezier.h> #include <2geom/point.h> +#include <2geom/solver.h> #include <algorithm> -namespace Geom{ +namespace Geom { /*** Find the zeros of the parametric function in 2d defined by two beziers X(t), Y(t). The code subdivides until it happy with the linearity of the bezier. This requires an n^2 subdivision for each step, even when there is only one solution. * @@ -14,13 +15,6 @@ namespace Geom{ /* * Forward declarations */ -static Geom::Point -Bezier(Geom::Point const *V, - unsigned degree, - double t, - Geom::Point *Left, - Geom::Point *Right); - unsigned crossing_count(Geom::Point const *V, unsigned degree); static unsigned @@ -74,7 +68,7 @@ find_parametric_bezier_roots(Geom::Point const *w, /* The control points */ // Right[degree+1]; /* control polygons */ std::vector<Geom::Point> Left( degree+1 ), Right(degree+1); - Bezier(w, degree, 0.5, Left.data(), Right.data()); + casteljau_subdivision(0.5, w, Left.data(), Right.data(), degree); total_subs ++; find_parametric_bezier_roots(Left.data(), degree, solutions, depth+1); find_parametric_bezier_roots(Right.data(), degree, solutions, depth+1); @@ -181,43 +175,6 @@ compute_x_intercept(Geom::Point const *V, /* Control points */ return (A[Geom::X]*V[0][Geom::Y] - A[Geom::Y]*V[0][Geom::X]) / -A[Geom::Y]; } - -/* - * Bezier : - * Evaluate a Bezier curve at a particular parameter value - * Fill in control points for resulting sub-curves. - * - */ -static Geom::Point -Bezier(Geom::Point const *V, /* Control pts */ - unsigned degree, /* Degree of bezier curve */ - double t, /* Parameter value */ - Geom::Point *Left, /* RETURN left half ctl pts */ - Geom::Point *Right) /* RETURN right half ctl pts */ -{ - //Geom::Point Vtemp[degree+1][degree+1]; - std::vector<std::vector<Geom::Point> > Vtemp(degree+1); - for ( size_t i = 0; i < degree + 1; ++i ) - Vtemp.reserve(degree+1); - - /* Copy control points */ - std::copy(V, V+degree+1, Vtemp[0].begin()); - - /* Triangle computation */ - for (unsigned i = 1; i <= degree; i++) { - for (unsigned j = 0; j <= degree - i; j++) { - Vtemp[i][j] = lerp(t, Vtemp[i-1][j], Vtemp[i-1][j+1]); - } - } - - for (unsigned j = 0; j <= degree; j++) - Left[j] = Vtemp[j][0]; - for (unsigned j = 0; j <= degree; j++) - Right[j] = Vtemp[degree-j][j]; - - return (Vtemp[degree][0]); -} - }; /* diff --git a/src/2geom/solve-bezier.cpp b/src/2geom/solve-bezier.cpp index adf3c9ac0..4732169cb 100644 --- a/src/2geom/solve-bezier.cpp +++ b/src/2geom/solve-bezier.cpp @@ -37,12 +37,10 @@ public: double *Left, double *Right); - double secant(Bezier bz); + double secant(Bezier const &bz); - double horner(Bezier bz, double t); - - void find_bernstein_roots(Bezier bz, unsigned depth, + void find_bernstein_roots(Bezier const &bz, unsigned depth, double left_t, double right_t); }; @@ -55,27 +53,7 @@ inline std::ostream &operator<< (std::ostream &out_file, const std::vector<T> & return out_file << "]"; } -Bezier subRight(Bezier bz, double t) { - unsigned order = bz.order(); - unsigned N = order+1; - std::valarray<Coord> row(N); - for (unsigned i = 0; i < N; i++) - row[i] = bz[i]; - - // Triangle computation - const double omt = (1-t); - Bezier Right = bz; - Right[order] = row[order]; - for (unsigned i = 1; i < N; i++) { - for (unsigned j = 0; j < N - i; j++) { - row[j] = omt*row[j] + t*row[j+1]; - } - Right[order-i] = row[order-i]; - } - return Right; -} - -void convex_hull_marching(Bezier src_bz, Bezier bz, +void convex_hull_marching(Bezier const &src_bz, Bezier bz, std::vector<double> &solutions, double left_t, double right_t) @@ -111,7 +89,7 @@ void convex_hull_marching(Bezier src_bz, Bezier bz, << " = " << bz(left_bound) << std::endl; double new_left_t = left_bound * (right_t - left_t) + left_t; std::cout << "new_left_t = " << new_left_t << std::endl; - Bezier bzr = subRight(src_bz, new_left_t); + Bezier bzr = portion(src_bz, new_left_t, 1); while(bzr.order() > 0 && bzr[0] == 0) { std::cout << "deflate\n"; bzr = bzr.deflate(); @@ -171,7 +149,7 @@ Bezier::find_bezier_roots(std::vector<double> &solutions, //std::cout << solutions << std::endl; } -void Bernsteins::find_bernstein_roots(Bezier bz, +void Bernsteins::find_bernstein_roots(Bezier const &bz, unsigned depth, double left_t, double right_t) @@ -194,6 +172,11 @@ void Bernsteins::find_bernstein_roots(Bezier bz, old_sign = sign; } } + // if last control point is zero, that counts as crossing too + if (SGN(bz[bz.size()-1]) == 0) { + ++n_crossings; + } + //std::cout << "n_crossings = " << n_crossings << std::endl; if (n_crossings == 0) return; // no solutions here @@ -271,23 +254,7 @@ void Bernsteins::find_bernstein_roots(Bezier bz, } } - -// suggested by Sederberg. -double Bernsteins::horner(Bezier bz, double t) -{ - double u, tn, tmp; - u = 1.0 - t; - tn = 1.0; - tmp = bz.at0() * u; - for(size_t i = 1; i < bz.degree(); ++i) - { - tn *= t; - tmp = (tmp + tn*choose<double>(bz.order(), (unsigned)i)*bz[i]) * u; - } - return (tmp + tn*t*bz.at1()); -} - -double Bernsteins::secant(Bezier bz) { +double Bernsteins::secant(Bezier const &bz) { double s = 0, t = 1; double e = 1e-14; int side = 0; @@ -303,7 +270,7 @@ double Bernsteins::secant(Bezier bz) { return r; } - double fr = horner(bz, r); + double fr = bz.valueAt(r); if (fr * ft > 0) { diff --git a/src/2geom/solver.h b/src/2geom/solver.h index 793939b2a..5b082cb83 100644 --- a/src/2geom/solver.h +++ b/src/2geom/solver.h @@ -32,16 +32,14 @@ * */ -#ifndef _SOLVE_SBASIS_H -#define _SOLVE_SBASIS_H +#ifndef LIB2GEOM_SEEN_SOLVER_H +#define LIB2GEOM_SEEN_SOLVER_H + #include <2geom/point.h> #include <2geom/sbasis.h> - - #include <vector> - -namespace Geom{ +namespace Geom { class Point; class Bezier; @@ -78,7 +76,6 @@ find_bernstein_roots(std::vector<double> &solutions, /* RETURN candidate t-value double left_t, double right_t); #endif - /* Local Variables: mode:c++ diff --git a/src/2geom/svg-elliptical-arc.cpp b/src/2geom/svg-elliptical-arc.cpp index 96a4f99d8..3a5154d08 100644 --- a/src/2geom/svg-elliptical-arc.cpp +++ b/src/2geom/svg-elliptical-arc.cpp @@ -207,7 +207,7 @@ bool make_elliptical_arc::make_elliptiarc() Ellipse e; try { - e.set(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]); + e.setCoefficients(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]); } catch(LogicalError const &exc) { diff --git a/src/2geom/svg-elliptical-arc.h b/src/2geom/svg-elliptical-arc.h index ba0a18257..90e16a999 100644 --- a/src/2geom/svg-elliptical-arc.h +++ b/src/2geom/svg-elliptical-arc.h @@ -70,59 +70,57 @@ public: _updateCenterAndAngles(true); } -#ifndef DOXYGEN_SHOULD_SKIP_THIS virtual Curve *duplicate() const { return new SVGEllipticalArc(*this); } virtual Coord valueAt(Coord t, Dim2 d) const { - if (isDegenerate()) return chord().valueAt(t, d); + if (isChord()) return chord().valueAt(t, d); return EllipticalArc::valueAt(t, d); } virtual Point pointAt(Coord t) const { - if (isDegenerate()) return chord().pointAt(t); + if (isChord()) return chord().pointAt(t); return EllipticalArc::pointAt(t); } virtual std::vector<Point> pointAndDerivatives(Coord t, unsigned int n) const { - if (isDegenerate()) return chord().pointAndDerivatives(t, n); + if (isChord()) return chord().pointAndDerivatives(t, n); return EllipticalArc::pointAndDerivatives(t, n); } virtual Rect boundsExact() const { - if (isDegenerate()) return chord().boundsExact(); + if (isChord()) return chord().boundsExact(); return EllipticalArc::boundsExact(); } virtual OptRect boundsLocal(OptInterval const &i, unsigned int deg) const { - if (isDegenerate()) return chord().boundsLocal(i, deg); + if (isChord()) return chord().boundsLocal(i, deg); return EllipticalArc::boundsLocal(i, deg); } virtual Curve *derivative() const { - if (isDegenerate()) return chord().derivative(); + if (isChord()) return chord().derivative(); return EllipticalArc::derivative(); } virtual std::vector<Coord> roots(Coord v, Dim2 d) const { - if (isDegenerate()) return chord().roots(v, d); + if (isChord()) return chord().roots(v, d); return EllipticalArc::roots(v, d); } #ifdef HAVE_GSL - virtual std::vector<Coord> allNearestPoints( Point const& p, double from = 0, double to = 1 ) const { - if (isDegenerate()) { + virtual std::vector<Coord> allNearestTimes( Point const& p, double from = 0, double to = 1 ) const { + if (isChord()) { std::vector<Coord> result; - result.push_back(chord().nearestPoint(p, from, to)); + result.push_back(chord().nearestTime(p, from, to)); return result; } - return EllipticalArc::allNearestPoints(p, from, to); + return EllipticalArc::allNearestTimes(p, from, to); } #endif virtual D2<SBasis> toSBasis() const { - if (isDegenerate()) return chord().toSBasis(); + if (isChord()) return chord().toSBasis(); return EllipticalArc::toSBasis(); } virtual bool isSVGCompliant() const { return true; } // TODO move SVG-specific behavior here. //protected: //virtual void _updateCenterAndAngles(); -#endif }; // end class SVGEllipticalArc /* diff --git a/src/2geom/svg-path-parser.cpp b/src/2geom/svg-path-parser.cpp index 932f95829..89a912695 100644 --- a/src/2geom/svg-path-parser.cpp +++ b/src/2geom/svg-path-parser.cpp @@ -1,3 +1,5 @@ + +#line 1 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" /** * \file * \brief parse SVG path specifications @@ -30,7 +32,7 @@ * */ - +#include <cstdio> #include <cmath> #include <vector> #include <glib.h> @@ -41,229 +43,108 @@ namespace Geom { -namespace { - -class Parser { -public: - Parser(PathSink &sink) : _absolute(false), _sink(sink) {} - - void parse(char const *str) throw(SVGPathParseError); - -private: - bool _absolute; - Point _current; - Point _initial; - Point _cubic_tangent; - Point _quad_tangent; - std::vector<double> _params; - PathSink &_sink; - - void _reset() { - _absolute = false; - _current = _initial = Point(0, 0); - _quad_tangent = _cubic_tangent = Point(0, 0); - _params.clear(); - } - - void _push(double value) { - _params.push_back(value); - } - - double _pop() { - double value = _params.back(); - _params.pop_back(); - return value; - } - - bool _pop_flag() { - return _pop() != 0.0; - } - - double _pop_coord(Geom::Dim2 axis) { - if (_absolute) { - return _pop(); - } else { - return _pop() + _current[axis]; - } - } - - Point _pop_point() { - double y = _pop_coord(Geom::Y); - double x = _pop_coord(Geom::X); - return Point(x, y); - } - - void _moveTo(Point p) { - _quad_tangent = _cubic_tangent = _current = _initial = p; - _sink.moveTo(p); - } - - void _hlineTo(Point p) { - _quad_tangent = _cubic_tangent = _current = p; - _sink.hlineTo(p[Geom::X]); - } - - void _vlineTo(Point p) { - _quad_tangent = _cubic_tangent = _current = p; - _sink.vlineTo(p[Geom::Y]); - } - - void _lineTo(Point p) { - _quad_tangent = _cubic_tangent = _current = p; - _sink.lineTo(p); - } - - void _curveTo(Point c0, Point c1, Point p) { - _quad_tangent = _current = p; - _cubic_tangent = p + ( p - c1 ); - _sink.curveTo(c0, c1, p); - } - - void _quadTo(Point c, Point p) { - _cubic_tangent = _current = p; - _quad_tangent = p + ( p - c ); - _sink.quadTo(c, p); - } - - void _arcTo(double rx, double ry, double angle, - bool large_arc, bool sweep, Point p) - { - if (are_near(_current, p)) { - return; - } - _quad_tangent = _cubic_tangent = _current = p; - _sink.arcTo(rx, ry, angle, large_arc, sweep, p); - } - - void _closePath() { - _quad_tangent = _cubic_tangent = _current = _initial; - _sink.closePath(); - } -}; - +#line 48 "D:/lib2geom/trunk/src/2geom/svg-path-parser.cpp" static const char _svg_path_actions[] = { 0, 1, 0, 1, 1, 1, 2, 1, - 3, 1, 4, 1, 5, 1, 15, 1, - 16, 2, 1, 0, 2, 1, 6, 2, - 1, 7, 2, 1, 8, 2, 1, 9, - 2, 1, 10, 2, 1, 11, 2, 1, - 12, 2, 1, 13, 2, 1, 14, 2, - 4, 0, 2, 5, 0, 2, 15, 16, - 3, 1, 6, 0, 3, 1, 6, 16, - 3, 1, 7, 0, 3, 1, 7, 16, - 3, 1, 8, 0, 3, 1, 8, 16, - 3, 1, 9, 0, 3, 1, 9, 16, - 3, 1, 10, 0, 3, 1, 10, 16, - 3, 1, 11, 0, 3, 1, 11, 16, - 3, 1, 12, 0, 3, 1, 12, 16, - 3, 1, 13, 0, 3, 1, 13, 16, - 3, 1, 14, 0, 3, 1, 14, 16 - + 3, 1, 4, 1, 5, 1, 15, 2, + 1, 0, 2, 1, 6, 2, 1, 7, + 2, 1, 8, 2, 1, 9, 2, 1, + 10, 2, 1, 11, 2, 1, 12, 2, + 1, 13, 2, 1, 14, 2, 2, 0, + 2, 3, 0, 2, 4, 0, 2, 5, + 0, 3, 1, 6, 0, 3, 1, 7, + 0, 3, 1, 8, 0, 3, 1, 9, + 0, 3, 1, 10, 0, 3, 1, 11, + 0, 3, 1, 12, 0, 3, 1, 13, + 0, 3, 1, 14, 0 }; static const short _svg_path_key_offsets[] = { - 0, 0, 7, 16, 25, 28, 30, 42, - 52, 55, 57, 90, 121, 124, 126, 138, - 148, 151, 153, 186, 195, 207, 216, 249, - 256, 263, 265, 275, 283, 290, 292, 304, - 314, 317, 319, 328, 335, 341, 346, 353, - 359, 364, 374, 377, 379, 391, 401, 404, - 406, 437, 466, 476, 480, 482, 490, 499, - 508, 511, 513, 525, 535, 538, 540, 552, - 562, 565, 567, 579, 589, 592, 594, 606, - 616, 619, 621, 633, 643, 646, 648, 681, - 712, 724, 733, 745, 754, 766, 775, 787, - 796, 808, 817, 850, 854, 856, 887, 896, - 905, 908, 910, 943, 974, 1007, 1011, 1013, - 1044, 1053, 1062, 1071, 1074, 1076, 1088, 1098, - 1101, 1103, 1115, 1125, 1128, 1130, 1142, 1152, - 1155, 1157, 1190, 1221, 1233, 1242, 1254, 1263, - 1275, 1284, 1317, 1321, 1323, 1354, 1363, 1372, - 1375, 1377, 1389, 1399, 1402, 1404, 1416, 1426, - 1429, 1431, 1443, 1453, 1456, 1458, 1491, 1522, - 1534, 1543, 1555, 1564, 1576, 1585, 1618, 1622, - 1624, 1655, 1664, 1673, 1676, 1678, 1690, 1700, - 1703, 1705, 1738, 1769, 1781, 1790, 1823, 1827, - 1829, 1860, 1869, 1878, 1881, 1883, 1916, 1947, - 1980, 1984, 1986, 2017, 2042, 2067, 2074, 2083, - 2092, 2101, 2110, 2122, 2131, 2164, 2168, 2170, - 2201, 2210, 2219, 2228, 2237, 2241, 2243, 2253, - 2257, 2259, 2269, 2273, 2275, 2285, 2289, 2291, - 2301, 2305, 2307, 2317, 2321, 2323, 2333, 2337, - 2339, 2349, 2353, 2355, 2365, 2369, 2371, 2381, - 2385, 2387, 2397, 2401, 2403, 2413, 2417, 2419, - 2429, 2433, 2435, 2445, 2449, 2451, 2480, 2511, - 2520, 2524, 2526, 2536, 2548, 2557, 2562, 2567, - 2571, 2573, 2580, 2590, 2599, 2603, 2605, 2615, - 2627, 2631, 2633, 2664, 2668, 2670, 2680 + 0, 0, 9, 18, 21, 23, 35, 45, + 48, 50, 53, 55, 67, 77, 80, 82, + 91, 103, 112, 119, 126, 128, 138, 146, + 153, 155, 167, 177, 180, 182, 191, 198, + 204, 211, 218, 224, 234, 244, 247, 249, + 261, 271, 274, 276, 286, 290, 292, 300, + 309, 318, 321, 323, 335, 345, 348, 350, + 362, 372, 375, 377, 389, 399, 402, 404, + 416, 426, 429, 431, 443, 453, 456, 458, + 470, 479, 491, 500, 512, 521, 533, 542, + 554, 563, 567, 569, 578, 587, 590, 592, + 596, 598, 607, 616, 625, 628, 630, 642, + 652, 655, 657, 669, 679, 682, 684, 696, + 706, 709, 711, 723, 732, 744, 753, 765, + 774, 778, 780, 789, 798, 801, 803, 815, + 825, 828, 830, 842, 852, 855, 857, 869, + 879, 882, 884, 896, 905, 917, 926, 938, + 947, 951, 953, 962, 971, 974, 976, 988, + 998, 1001, 1003, 1015, 1024, 1028, 1030, 1039, + 1048, 1051, 1053, 1057, 1059, 1066, 1075, 1084, + 1093, 1102, 1114, 1123, 1127, 1129, 1138, 1147, + 1156, 1165, 1169, 1171, 1181, 1185, 1187, 1197, + 1201, 1203, 1213, 1217, 1219, 1229, 1233, 1235, + 1245, 1249, 1251, 1261, 1265, 1267, 1277, 1281, + 1283, 1293, 1297, 1299, 1309, 1313, 1315, 1325, + 1329, 1331, 1341, 1345, 1347, 1357, 1361, 1363, + 1373, 1377, 1379, 1388, 1392, 1394, 1404, 1416, + 1425, 1435, 1442, 1446, 1448, 1455, 1465, 1474, + 1478, 1480, 1490, 1502, 1506, 1508, 1512, 1514, + 1524, 1530, 1562, 1592, 1624, 1656, 1686, 1714, + 1746, 1776, 1808, 1838, 1870, 1900, 1932, 1962, + 1994, 2024, 2056, 2086, 2118, 2148, 2180, 2210, + 2242, 2272, 2304, 2334, 2366, 2396, 2428, 2458, + 2482, 2506, 2538, 2568, 2596, 2626 }; static const char _svg_path_trans_keys[] = { - 0, 13, 32, 77, 109, 9, 10, 13, - 32, 43, 45, 46, 9, 10, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, - 57, 46, 48, 57, 48, 57, 13, 32, - 44, 46, 69, 101, 9, 10, 43, 45, - 48, 57, 13, 32, 44, 46, 9, 10, - 43, 45, 48, 57, 46, 48, 57, 48, - 57, 0, 13, 32, 44, 46, 65, 67, - 69, 72, 76, 77, 81, 83, 84, 86, - 90, 97, 99, 101, 104, 108, 109, 113, - 115, 116, 118, 122, 9, 10, 43, 45, - 48, 57, 0, 13, 32, 44, 46, 65, - 67, 72, 76, 77, 81, 83, 84, 86, - 90, 97, 99, 104, 108, 109, 113, 115, - 116, 118, 122, 9, 10, 43, 45, 48, - 57, 46, 48, 57, 48, 57, 13, 32, - 44, 46, 69, 101, 9, 10, 43, 45, - 48, 57, 13, 32, 44, 46, 9, 10, - 43, 45, 48, 57, 46, 48, 57, 48, - 57, 0, 13, 32, 44, 46, 65, 67, - 69, 72, 76, 77, 81, 83, 84, 86, - 90, 97, 99, 101, 104, 108, 109, 113, - 115, 116, 118, 122, 9, 10, 43, 45, + 57, 13, 32, 43, 45, 46, 9, 10, + 48, 57, 46, 48, 57, 48, 57, 13, + 32, 44, 46, 69, 101, 9, 10, 43, + 45, 48, 57, 13, 32, 44, 46, 9, + 10, 43, 45, 48, 57, 46, 48, 57, + 48, 57, 46, 48, 57, 48, 57, 13, + 32, 44, 46, 69, 101, 9, 10, 43, + 45, 48, 57, 13, 32, 44, 46, 9, + 10, 43, 45, 48, 57, 46, 48, 57, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, 57, - 0, 13, 32, 44, 46, 65, 67, 69, - 72, 76, 77, 81, 83, 84, 86, 90, - 97, 99, 101, 104, 108, 109, 113, 115, - 116, 118, 122, 9, 10, 43, 45, 48, - 57, 13, 32, 46, 9, 10, 48, 57, - 13, 32, 46, 9, 10, 48, 57, 48, + 13, 32, 46, 9, 10, 48, 57, 13, + 32, 46, 9, 10, 48, 57, 48, 57, + 13, 32, 44, 46, 69, 101, 9, 10, + 48, 57, 13, 32, 44, 46, 9, 10, + 48, 57, 13, 32, 46, 9, 10, 48, + 57, 48, 57, 13, 32, 44, 46, 69, + 101, 9, 10, 43, 45, 48, 57, 13, + 32, 44, 46, 9, 10, 43, 45, 48, + 57, 46, 48, 57, 48, 57, 13, 32, + 44, 69, 101, 9, 10, 48, 57, 13, + 32, 44, 48, 49, 9, 10, 13, 32, + 48, 49, 9, 10, 13, 32, 44, 48, + 49, 9, 10, 13, 32, 44, 48, 49, + 9, 10, 13, 32, 48, 49, 9, 10, + 13, 32, 44, 46, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 9, 10, + 43, 45, 48, 57, 46, 48, 57, 48, 57, 13, 32, 44, 46, 69, 101, 9, - 10, 48, 57, 13, 32, 44, 46, 9, - 10, 48, 57, 13, 32, 46, 9, 10, + 10, 43, 45, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 46, 48, 57, 48, 57, 13, 32, 44, 46, - 69, 101, 9, 10, 43, 45, 48, 57, - 13, 32, 44, 46, 9, 10, 43, 45, - 48, 57, 46, 48, 57, 48, 57, 13, - 32, 44, 69, 101, 9, 10, 48, 57, - 13, 32, 44, 48, 49, 9, 10, 13, - 32, 48, 49, 9, 10, 13, 32, 44, - 9, 10, 13, 32, 44, 48, 49, 9, - 10, 13, 32, 48, 49, 9, 10, 13, - 32, 44, 9, 10, 13, 32, 44, 46, - 9, 10, 43, 45, 48, 57, 46, 48, + 69, 101, 9, 10, 48, 57, 43, 45, + 48, 57, 48, 57, 13, 32, 44, 46, + 9, 10, 48, 57, 13, 32, 43, 45, + 46, 9, 10, 48, 57, 13, 32, 43, + 45, 46, 9, 10, 48, 57, 46, 48, 57, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 44, 46, 9, 10, 43, 45, 48, - 57, 46, 48, 57, 48, 57, 0, 13, - 32, 44, 46, 65, 67, 69, 72, 76, - 77, 81, 83, 84, 86, 90, 97, 99, - 101, 104, 108, 109, 113, 115, 116, 118, - 122, 9, 10, 48, 57, 0, 13, 32, - 44, 46, 65, 67, 72, 76, 77, 81, - 83, 84, 86, 90, 97, 99, 104, 108, - 109, 113, 115, 116, 118, 122, 9, 10, - 48, 57, 13, 32, 44, 46, 69, 101, - 9, 10, 48, 57, 43, 45, 48, 57, + 57, 46, 48, 57, 48, 57, 13, 32, + 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 44, 46, 9, 10, - 48, 57, 13, 32, 43, 45, 46, 9, - 10, 48, 57, 13, 32, 43, 45, 46, - 9, 10, 48, 57, 46, 48, 57, 48, + 43, 45, 48, 57, 46, 48, 57, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 44, 46, 9, 10, 43, 45, 48, 57, 46, @@ -276,102 +157,29 @@ static const char _svg_path_trans_keys[] = { 10, 43, 45, 48, 57, 46, 48, 57, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, - 44, 46, 9, 10, 43, 45, 48, 57, - 46, 48, 57, 48, 57, 13, 32, 44, - 46, 69, 101, 9, 10, 43, 45, 48, - 57, 13, 32, 44, 46, 9, 10, 43, - 45, 48, 57, 46, 48, 57, 48, 57, - 0, 13, 32, 44, 46, 65, 67, 69, - 72, 76, 77, 81, 83, 84, 86, 90, - 97, 99, 101, 104, 108, 109, 113, 115, - 116, 118, 122, 9, 10, 43, 45, 48, - 57, 0, 13, 32, 44, 46, 65, 67, - 72, 76, 77, 81, 83, 84, 86, 90, - 97, 99, 104, 108, 109, 113, 115, 116, - 118, 122, 9, 10, 43, 45, 48, 57, - 13, 32, 44, 46, 69, 101, 9, 10, - 43, 45, 48, 57, 13, 32, 43, 45, - 46, 9, 10, 48, 57, 13, 32, 44, - 46, 69, 101, 9, 10, 43, 45, 48, - 57, 13, 32, 43, 45, 46, 9, 10, - 48, 57, 13, 32, 44, 46, 69, 101, - 9, 10, 43, 45, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, - 57, 0, 13, 32, 44, 46, 65, 67, - 69, 72, 76, 77, 81, 83, 84, 86, - 90, 97, 99, 101, 104, 108, 109, 113, - 115, 116, 118, 122, 9, 10, 43, 45, - 48, 57, 43, 45, 48, 57, 48, 57, - 0, 13, 32, 44, 46, 65, 67, 72, - 76, 77, 81, 83, 84, 86, 90, 97, - 99, 104, 108, 109, 113, 115, 116, 118, - 122, 9, 10, 43, 45, 48, 57, 13, - 32, 43, 45, 46, 9, 10, 48, 57, - 13, 32, 43, 45, 46, 9, 10, 48, - 57, 46, 48, 57, 48, 57, 0, 13, - 32, 44, 46, 65, 67, 69, 72, 76, - 77, 81, 83, 84, 86, 90, 97, 99, - 101, 104, 108, 109, 113, 115, 116, 118, - 122, 9, 10, 43, 45, 48, 57, 0, - 13, 32, 44, 46, 65, 67, 72, 76, - 77, 81, 83, 84, 86, 90, 97, 99, - 104, 108, 109, 113, 115, 116, 118, 122, - 9, 10, 43, 45, 48, 57, 0, 13, - 32, 44, 46, 65, 67, 69, 72, 76, - 77, 81, 83, 84, 86, 90, 97, 99, - 101, 104, 108, 109, 113, 115, 116, 118, - 122, 9, 10, 43, 45, 48, 57, 43, - 45, 48, 57, 48, 57, 0, 13, 32, - 44, 46, 65, 67, 72, 76, 77, 81, - 83, 84, 86, 90, 97, 99, 104, 108, - 109, 113, 115, 116, 118, 122, 9, 10, - 43, 45, 48, 57, 13, 32, 43, 45, - 46, 9, 10, 48, 57, 13, 32, 43, + 57, 13, 32, 44, 46, 69, 101, 9, + 10, 43, 45, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, 57, 13, 32, - 43, 45, 46, 9, 10, 48, 57, 46, - 48, 57, 48, 57, 13, 32, 44, 46, - 69, 101, 9, 10, 43, 45, 48, 57, - 13, 32, 44, 46, 9, 10, 43, 45, - 48, 57, 46, 48, 57, 48, 57, 13, - 32, 44, 46, 69, 101, 9, 10, 43, - 45, 48, 57, 13, 32, 44, 46, 9, - 10, 43, 45, 48, 57, 46, 48, 57, - 48, 57, 13, 32, 44, 46, 69, 101, - 9, 10, 43, 45, 48, 57, 13, 32, - 44, 46, 9, 10, 43, 45, 48, 57, - 46, 48, 57, 48, 57, 0, 13, 32, - 44, 46, 65, 67, 69, 72, 76, 77, - 81, 83, 84, 86, 90, 97, 99, 101, - 104, 108, 109, 113, 115, 116, 118, 122, - 9, 10, 43, 45, 48, 57, 0, 13, - 32, 44, 46, 65, 67, 72, 76, 77, - 81, 83, 84, 86, 90, 97, 99, 104, - 108, 109, 113, 115, 116, 118, 122, 9, - 10, 43, 45, 48, 57, 13, 32, 44, - 46, 69, 101, 9, 10, 43, 45, 48, + 44, 46, 69, 101, 9, 10, 43, 45, + 48, 57, 13, 32, 43, 45, 46, 9, + 10, 48, 57, 43, 45, 48, 57, 48, 57, 13, 32, 43, 45, 46, 9, 10, - 48, 57, 13, 32, 44, 46, 69, 101, - 9, 10, 43, 45, 48, 57, 13, 32, - 43, 45, 46, 9, 10, 48, 57, 13, - 32, 44, 46, 69, 101, 9, 10, 43, - 45, 48, 57, 13, 32, 43, 45, 46, - 9, 10, 48, 57, 0, 13, 32, 44, - 46, 65, 67, 69, 72, 76, 77, 81, - 83, 84, 86, 90, 97, 99, 101, 104, - 108, 109, 113, 115, 116, 118, 122, 9, - 10, 43, 45, 48, 57, 43, 45, 48, - 57, 48, 57, 0, 13, 32, 44, 46, - 65, 67, 72, 76, 77, 81, 83, 84, - 86, 90, 97, 99, 104, 108, 109, 113, - 115, 116, 118, 122, 9, 10, 43, 45, 48, 57, 13, 32, 43, 45, 46, 9, - 10, 48, 57, 13, 32, 43, 45, 46, - 9, 10, 48, 57, 46, 48, 57, 48, + 10, 48, 57, 46, 48, 57, 48, 57, + 43, 45, 48, 57, 48, 57, 13, 32, + 43, 45, 46, 9, 10, 48, 57, 13, + 32, 43, 45, 46, 9, 10, 48, 57, + 13, 32, 43, 45, 46, 9, 10, 48, + 57, 46, 48, 57, 48, 57, 13, 32, + 44, 46, 69, 101, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 9, 10, + 43, 45, 48, 57, 46, 48, 57, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 44, 46, 9, 10, 43, 45, 48, 57, 46, @@ -380,85 +188,49 @@ static const char _svg_path_trans_keys[] = { 13, 32, 44, 46, 9, 10, 43, 45, 48, 57, 46, 48, 57, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, - 45, 48, 57, 13, 32, 44, 46, 9, - 10, 43, 45, 48, 57, 46, 48, 57, - 48, 57, 0, 13, 32, 44, 46, 65, - 67, 69, 72, 76, 77, 81, 83, 84, - 86, 90, 97, 99, 101, 104, 108, 109, - 113, 115, 116, 118, 122, 9, 10, 43, - 45, 48, 57, 0, 13, 32, 44, 46, - 65, 67, 72, 76, 77, 81, 83, 84, - 86, 90, 97, 99, 104, 108, 109, 113, - 115, 116, 118, 122, 9, 10, 43, 45, - 48, 57, 13, 32, 44, 46, 69, 101, - 9, 10, 43, 45, 48, 57, 13, 32, - 43, 45, 46, 9, 10, 48, 57, 13, - 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, - 57, 0, 13, 32, 44, 46, 65, 67, - 69, 72, 76, 77, 81, 83, 84, 86, - 90, 97, 99, 101, 104, 108, 109, 113, - 115, 116, 118, 122, 9, 10, 43, 45, - 48, 57, 43, 45, 48, 57, 48, 57, - 0, 13, 32, 44, 46, 65, 67, 72, - 76, 77, 81, 83, 84, 86, 90, 97, - 99, 104, 108, 109, 113, 115, 116, 118, - 122, 9, 10, 43, 45, 48, 57, 13, - 32, 43, 45, 46, 9, 10, 48, 57, - 13, 32, 43, 45, 46, 9, 10, 48, + 57, 13, 32, 44, 46, 69, 101, 9, + 10, 43, 45, 48, 57, 13, 32, 43, + 45, 46, 9, 10, 48, 57, 43, 45, + 48, 57, 48, 57, 13, 32, 43, 45, + 46, 9, 10, 48, 57, 13, 32, 43, + 45, 46, 9, 10, 48, 57, 46, 48, + 57, 48, 57, 13, 32, 44, 46, 69, + 101, 9, 10, 43, 45, 48, 57, 13, + 32, 44, 46, 9, 10, 43, 45, 48, 57, 46, 48, 57, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 44, 46, 9, 10, 43, 45, 48, 57, 46, 48, 57, 48, - 57, 0, 13, 32, 44, 46, 65, 67, - 69, 72, 76, 77, 81, 83, 84, 86, - 90, 97, 99, 101, 104, 108, 109, 113, - 115, 116, 118, 122, 9, 10, 43, 45, - 48, 57, 0, 13, 32, 44, 46, 65, - 67, 72, 76, 77, 81, 83, 84, 86, - 90, 97, 99, 104, 108, 109, 113, 115, - 116, 118, 122, 9, 10, 43, 45, 48, + 57, 13, 32, 44, 46, 69, 101, 9, + 10, 43, 45, 48, 57, 13, 32, 44, + 46, 9, 10, 43, 45, 48, 57, 46, + 48, 57, 48, 57, 13, 32, 44, 46, + 69, 101, 9, 10, 43, 45, 48, 57, + 13, 32, 43, 45, 46, 9, 10, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 43, - 45, 46, 9, 10, 48, 57, 0, 13, - 32, 44, 46, 65, 67, 69, 72, 76, - 77, 81, 83, 84, 86, 90, 97, 99, - 101, 104, 108, 109, 113, 115, 116, 118, - 122, 9, 10, 43, 45, 48, 57, 43, - 45, 48, 57, 48, 57, 0, 13, 32, - 44, 46, 65, 67, 72, 76, 77, 81, - 83, 84, 86, 90, 97, 99, 104, 108, - 109, 113, 115, 116, 118, 122, 9, 10, - 43, 45, 48, 57, 13, 32, 43, 45, - 46, 9, 10, 48, 57, 13, 32, 43, - 45, 46, 9, 10, 48, 57, 46, 48, - 57, 48, 57, 0, 13, 32, 44, 46, - 65, 67, 69, 72, 76, 77, 81, 83, - 84, 86, 90, 97, 99, 101, 104, 108, - 109, 113, 115, 116, 118, 122, 9, 10, - 43, 45, 48, 57, 0, 13, 32, 44, - 46, 65, 67, 72, 76, 77, 81, 83, - 84, 86, 90, 97, 99, 104, 108, 109, - 113, 115, 116, 118, 122, 9, 10, 43, - 45, 48, 57, 0, 13, 32, 44, 46, - 65, 67, 69, 72, 76, 77, 81, 83, - 84, 86, 90, 97, 99, 101, 104, 108, - 109, 113, 115, 116, 118, 122, 9, 10, - 43, 45, 48, 57, 43, 45, 48, 57, - 48, 57, 0, 13, 32, 44, 46, 65, - 67, 72, 76, 77, 81, 83, 84, 86, - 90, 97, 99, 104, 108, 109, 113, 115, - 116, 118, 122, 9, 10, 43, 45, 48, - 57, 0, 13, 32, 65, 67, 72, 76, - 77, 81, 83, 84, 86, 90, 97, 99, - 104, 108, 109, 113, 115, 116, 118, 122, - 9, 10, 0, 13, 32, 65, 67, 72, - 76, 77, 81, 83, 84, 86, 90, 97, - 99, 104, 108, 109, 113, 115, 116, 118, - 122, 9, 10, 13, 32, 46, 9, 10, + 45, 46, 9, 10, 48, 57, 13, 32, + 44, 46, 69, 101, 9, 10, 43, 45, + 48, 57, 13, 32, 43, 45, 46, 9, + 10, 48, 57, 43, 45, 48, 57, 48, + 57, 13, 32, 43, 45, 46, 9, 10, + 48, 57, 13, 32, 43, 45, 46, 9, + 10, 48, 57, 46, 48, 57, 48, 57, + 13, 32, 44, 46, 69, 101, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 9, 10, 43, 45, 48, 57, 46, 48, + 57, 48, 57, 13, 32, 44, 46, 69, + 101, 9, 10, 43, 45, 48, 57, 13, + 32, 43, 45, 46, 9, 10, 48, 57, + 43, 45, 48, 57, 48, 57, 13, 32, + 43, 45, 46, 9, 10, 48, 57, 13, + 32, 43, 45, 46, 9, 10, 48, 57, + 46, 48, 57, 48, 57, 43, 45, 48, + 57, 48, 57, 13, 32, 46, 9, 10, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, 57, 13, 32, 43, 45, @@ -466,15 +238,7 @@ static const char _svg_path_trans_keys[] = { 45, 46, 9, 10, 48, 57, 13, 32, 44, 46, 69, 101, 9, 10, 43, 45, 48, 57, 13, 32, 43, 45, 46, 9, - 10, 48, 57, 0, 13, 32, 44, 46, - 65, 67, 69, 72, 76, 77, 81, 83, - 84, 86, 90, 97, 99, 101, 104, 108, - 109, 113, 115, 116, 118, 122, 9, 10, - 43, 45, 48, 57, 43, 45, 48, 57, - 48, 57, 0, 13, 32, 44, 46, 65, - 67, 72, 76, 77, 81, 83, 84, 86, - 90, 97, 99, 104, 108, 109, 113, 115, - 116, 118, 122, 9, 10, 43, 45, 48, + 10, 48, 57, 43, 45, 48, 57, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, 57, 13, 32, 43, 45, 46, 9, 10, 48, 57, 13, 32, 43, 45, 46, @@ -506,661 +270,944 @@ static const char _svg_path_trans_keys[] = { 10, 43, 45, 48, 57, 43, 45, 48, 57, 48, 57, 13, 32, 44, 46, 9, 10, 43, 45, 48, 57, 43, 45, 48, - 57, 48, 57, 0, 13, 32, 44, 46, + 57, 48, 57, 13, 32, 43, 45, 46, + 9, 10, 48, 57, 43, 45, 48, 57, + 48, 57, 13, 32, 44, 46, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 69, 101, 9, 10, 43, 45, 48, 57, + 13, 32, 43, 45, 46, 9, 10, 48, + 57, 13, 32, 44, 46, 9, 10, 43, + 45, 48, 57, 13, 32, 44, 48, 49, + 9, 10, 43, 45, 48, 57, 48, 57, + 13, 32, 44, 9, 10, 48, 57, 13, + 32, 44, 46, 69, 101, 9, 10, 48, + 57, 13, 32, 43, 45, 46, 9, 10, + 48, 57, 43, 45, 48, 57, 48, 57, + 13, 32, 44, 46, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 69, 101, + 9, 10, 43, 45, 48, 57, 43, 45, + 48, 57, 48, 57, 43, 45, 48, 57, + 48, 57, 13, 32, 44, 46, 9, 10, + 43, 45, 48, 57, 13, 32, 77, 109, + 9, 10, 13, 32, 44, 46, 65, 67, + 69, 72, 76, 77, 81, 83, 84, 86, + 90, 97, 99, 101, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 72, 76, 77, 81, 83, 84, 86, 90, + 97, 99, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 69, 72, + 76, 77, 81, 83, 84, 86, 90, 97, + 99, 101, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 69, 72, + 76, 77, 81, 83, 84, 86, 90, 97, + 99, 101, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 69, 72, + 76, 77, 81, 83, 84, 86, 90, 97, + 99, 101, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 48, 57, 13, 32, + 44, 46, 65, 67, 72, 76, 77, 81, + 83, 84, 86, 90, 97, 99, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 48, 57, 13, 32, 44, 46, 65, 67, + 69, 72, 76, 77, 81, 83, 84, 86, + 90, 97, 99, 101, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 72, 76, 77, 81, 83, 84, 86, 90, + 97, 99, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 69, 72, + 76, 77, 81, 83, 84, 86, 90, 97, + 99, 101, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 72, 76, + 77, 81, 83, 84, 86, 90, 97, 99, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 69, 72, 76, 77, + 81, 83, 84, 86, 90, 97, 99, 101, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 72, 76, 77, 81, + 83, 84, 86, 90, 97, 99, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 69, 72, 76, 77, 81, 83, + 84, 86, 90, 97, 99, 101, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, 65, 67, 72, 76, 77, 81, 83, 84, 86, 90, 97, 99, 104, 108, 109, 113, - 115, 116, 118, 122, 9, 10, 48, 57, - 0, 13, 32, 44, 46, 65, 67, 69, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 69, 72, 76, 77, 81, 83, 84, 86, + 90, 97, 99, 101, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, 72, 76, 77, 81, 83, 84, 86, 90, - 97, 99, 101, 104, 108, 109, 113, 115, - 116, 118, 122, 9, 10, 48, 57, 13, - 32, 43, 45, 46, 9, 10, 48, 57, - 43, 45, 48, 57, 48, 57, 13, 32, - 44, 46, 9, 10, 43, 45, 48, 57, - 13, 32, 44, 46, 69, 101, 9, 10, - 43, 45, 48, 57, 13, 32, 43, 45, - 46, 9, 10, 48, 57, 13, 32, 44, - 9, 10, 13, 32, 44, 9, 10, 43, - 45, 48, 57, 48, 57, 13, 32, 44, + 97, 99, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 69, 72, + 76, 77, 81, 83, 84, 86, 90, 97, + 99, 101, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 72, 76, + 77, 81, 83, 84, 86, 90, 97, 99, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 69, 72, 76, 77, + 81, 83, 84, 86, 90, 97, 99, 101, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 72, 76, 77, 81, + 83, 84, 86, 90, 97, 99, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 69, 72, 76, 77, 81, 83, + 84, 86, 90, 97, 99, 101, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 72, 76, 77, 81, 83, 84, + 86, 90, 97, 99, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 69, 72, 76, 77, 81, 83, 84, 86, + 90, 97, 99, 101, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 72, 76, 77, 81, 83, 84, 86, 90, + 97, 99, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 69, 72, + 76, 77, 81, 83, 84, 86, 90, 97, + 99, 101, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 72, 76, + 77, 81, 83, 84, 86, 90, 97, 99, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 69, 72, 76, 77, + 81, 83, 84, 86, 90, 97, 99, 101, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 43, 45, 48, 57, 13, 32, + 44, 46, 65, 67, 72, 76, 77, 81, + 83, 84, 86, 90, 97, 99, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 69, 72, 76, 77, 81, 83, + 84, 86, 90, 97, 99, 101, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 43, 45, 48, 57, 13, 32, 44, 46, + 65, 67, 72, 76, 77, 81, 83, 84, + 86, 90, 97, 99, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 65, 67, 72, 76, + 77, 81, 83, 84, 86, 90, 97, 99, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 13, 32, 65, 67, 72, 76, + 77, 81, 83, 84, 86, 90, 97, 99, + 104, 108, 109, 113, 115, 116, 118, 122, + 9, 10, 13, 32, 44, 46, 65, 67, + 69, 72, 76, 77, 81, 83, 84, 86, + 90, 97, 99, 101, 104, 108, 109, 113, + 115, 116, 118, 122, 9, 10, 43, 45, + 48, 57, 13, 32, 44, 46, 65, 67, + 72, 76, 77, 81, 83, 84, 86, 90, + 97, 99, 104, 108, 109, 113, 115, 116, + 118, 122, 9, 10, 43, 45, 48, 57, + 13, 32, 44, 46, 65, 67, 72, 76, + 77, 81, 83, 84, 86, 90, 97, 99, + 104, 108, 109, 113, 115, 116, 118, 122, 9, 10, 48, 57, 13, 32, 44, 46, - 69, 101, 9, 10, 48, 57, 13, 32, - 43, 45, 46, 9, 10, 48, 57, 43, - 45, 48, 57, 48, 57, 13, 32, 44, - 46, 9, 10, 43, 45, 48, 57, 13, - 32, 44, 46, 69, 101, 9, 10, 43, - 45, 48, 57, 43, 45, 48, 57, 48, - 57, 0, 13, 32, 44, 46, 65, 67, + 65, 67, 69, 72, 76, 77, 81, 83, + 84, 86, 90, 97, 99, 101, 104, 108, + 109, 113, 115, 116, 118, 122, 9, 10, + 48, 57, 13, 32, 44, 46, 65, 67, 72, 76, 77, 81, 83, 84, 86, 90, 97, 99, 104, 108, 109, 113, 115, 116, 118, 122, 9, 10, 43, 45, 48, 57, - 43, 45, 48, 57, 48, 57, 13, 32, - 44, 46, 9, 10, 43, 45, 48, 57, 0 }; static const char _svg_path_single_lengths[] = { + 0, 5, 5, 1, 0, 6, 4, 1, + 0, 1, 0, 6, 4, 1, 0, 5, + 6, 5, 3, 3, 0, 6, 4, 3, + 0, 6, 4, 1, 0, 5, 5, 4, + 5, 5, 4, 4, 4, 1, 0, 6, + 4, 1, 0, 6, 2, 0, 4, 5, + 5, 1, 0, 6, 4, 1, 0, 6, + 4, 1, 0, 6, 4, 1, 0, 6, + 4, 1, 0, 6, 4, 1, 0, 6, + 5, 6, 5, 6, 5, 6, 5, 6, + 5, 2, 0, 5, 5, 1, 0, 2, 0, 5, 5, 5, 1, 0, 6, 4, - 1, 0, 27, 25, 1, 0, 6, 4, - 1, 0, 27, 5, 6, 5, 27, 3, - 3, 0, 6, 4, 3, 0, 6, 4, - 1, 0, 5, 5, 4, 3, 5, 4, - 3, 4, 1, 0, 6, 4, 1, 0, - 27, 25, 6, 2, 0, 4, 5, 5, 1, 0, 6, 4, 1, 0, 6, 4, + 1, 0, 6, 5, 6, 5, 6, 5, + 2, 0, 5, 5, 1, 0, 6, 4, 1, 0, 6, 4, 1, 0, 6, 4, - 1, 0, 6, 4, 1, 0, 27, 25, - 6, 5, 6, 5, 6, 5, 6, 5, - 6, 5, 27, 2, 0, 25, 5, 5, - 1, 0, 27, 25, 27, 2, 0, 25, - 5, 5, 5, 1, 0, 6, 4, 1, - 0, 6, 4, 1, 0, 6, 4, 1, - 0, 27, 25, 6, 5, 6, 5, 6, - 5, 27, 2, 0, 25, 5, 5, 1, - 0, 6, 4, 1, 0, 6, 4, 1, - 0, 6, 4, 1, 0, 27, 25, 6, - 5, 6, 5, 6, 5, 27, 2, 0, - 25, 5, 5, 1, 0, 6, 4, 1, - 0, 27, 25, 6, 5, 27, 2, 0, - 25, 5, 5, 1, 0, 27, 25, 27, - 2, 0, 25, 23, 23, 3, 5, 5, - 5, 5, 6, 5, 27, 2, 0, 25, - 5, 5, 5, 5, 2, 0, 4, 2, + 1, 0, 6, 5, 6, 5, 6, 5, + 2, 0, 5, 5, 1, 0, 6, 4, + 1, 0, 6, 5, 2, 0, 5, 5, + 1, 0, 2, 0, 3, 5, 5, 5, + 5, 6, 5, 2, 0, 5, 5, 5, + 5, 2, 0, 4, 2, 0, 4, 2, 0, 4, 2, 0, 4, 2, 0, 4, 2, 0, 4, 2, 0, 4, 2, 0, 4, 2, 0, 4, 2, 0, 4, 2, 0, 4, 2, 0, 4, 2, 0, 4, - 2, 0, 4, 2, 0, 25, 27, 5, - 2, 0, 4, 6, 5, 3, 3, 2, - 0, 3, 6, 5, 2, 0, 4, 6, - 2, 0, 25, 2, 0, 4, 0 + 2, 0, 5, 2, 0, 4, 6, 5, + 4, 5, 2, 0, 3, 6, 5, 2, + 0, 4, 6, 2, 0, 2, 0, 4, + 4, 26, 24, 26, 26, 26, 24, 26, + 24, 26, 24, 26, 24, 26, 24, 26, + 24, 26, 24, 26, 24, 26, 24, 26, + 24, 26, 24, 26, 24, 26, 24, 22, + 22, 26, 24, 24, 26, 24 }; static const char _svg_path_range_lengths[] = { - 0, 1, 2, 2, 1, 1, 3, 3, + 0, 2, 2, 1, 1, 3, 3, 1, + 1, 1, 1, 3, 3, 1, 1, 2, + 3, 2, 2, 2, 1, 2, 2, 2, + 1, 3, 3, 1, 1, 2, 1, 1, + 1, 1, 1, 3, 3, 1, 1, 3, + 3, 1, 1, 2, 1, 1, 2, 2, + 2, 1, 1, 3, 3, 1, 1, 3, + 3, 1, 1, 3, 3, 1, 1, 3, + 3, 1, 1, 3, 3, 1, 1, 3, + 2, 3, 2, 3, 2, 3, 2, 3, + 2, 1, 1, 2, 2, 1, 1, 1, + 1, 2, 2, 2, 1, 1, 3, 3, 1, 1, 3, 3, 1, 1, 3, 3, 1, 1, 3, 2, 3, 2, 3, 2, - 2, 1, 2, 2, 2, 1, 3, 3, - 1, 1, 2, 1, 1, 1, 1, 1, - 1, 3, 1, 1, 3, 3, 1, 1, - 2, 2, 2, 1, 1, 2, 2, 2, + 1, 1, 2, 2, 1, 1, 3, 3, 1, 1, 3, 3, 1, 1, 3, 3, - 1, 1, 3, 3, 1, 1, 3, 3, - 1, 1, 3, 3, 1, 1, 3, 3, - 3, 2, 3, 2, 3, 2, 3, 2, - 3, 2, 3, 1, 1, 3, 2, 2, - 1, 1, 3, 3, 3, 1, 1, 3, - 2, 2, 2, 1, 1, 3, 3, 1, - 1, 3, 3, 1, 1, 3, 3, 1, - 1, 3, 3, 3, 2, 3, 2, 3, - 2, 3, 1, 1, 3, 2, 2, 1, - 1, 3, 3, 1, 1, 3, 3, 1, - 1, 3, 3, 1, 1, 3, 3, 3, - 2, 3, 2, 3, 2, 3, 1, 1, - 3, 2, 2, 1, 1, 3, 3, 1, - 1, 3, 3, 3, 2, 3, 1, 1, - 3, 2, 2, 1, 1, 3, 3, 3, - 1, 1, 3, 1, 1, 2, 2, 2, - 2, 2, 3, 2, 3, 1, 1, 3, - 2, 2, 2, 2, 1, 1, 3, 1, + 1, 1, 3, 2, 3, 2, 3, 2, + 1, 1, 2, 2, 1, 1, 3, 3, + 1, 1, 3, 2, 1, 1, 2, 2, + 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 2, 1, 1, 2, 2, 2, + 2, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3, 1, 1, 3, - 1, 1, 3, 1, 1, 2, 2, 2, - 1, 1, 3, 3, 2, 1, 1, 1, - 1, 2, 2, 2, 1, 1, 3, 3, - 1, 1, 3, 1, 1, 3, 0 + 1, 1, 2, 1, 1, 3, 3, 2, + 3, 1, 1, 1, 2, 2, 2, 1, + 1, 3, 3, 1, 1, 1, 1, 3, + 1, 3, 3, 3, 3, 2, 2, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 1, + 1, 3, 3, 2, 2, 3 }; static const short _svg_path_index_offsets[] = { - 0, 0, 7, 15, 23, 26, 28, 38, - 46, 49, 51, 82, 111, 114, 116, 126, - 134, 137, 139, 170, 178, 188, 196, 227, - 233, 239, 241, 250, 257, 263, 265, 275, - 283, 286, 288, 296, 303, 309, 314, 321, - 327, 332, 340, 343, 345, 355, 363, 366, - 368, 398, 426, 435, 439, 441, 448, 456, - 464, 467, 469, 479, 487, 490, 492, 502, - 510, 513, 515, 525, 533, 536, 538, 548, - 556, 559, 561, 571, 579, 582, 584, 615, - 644, 654, 662, 672, 680, 690, 698, 708, - 716, 726, 734, 765, 769, 771, 800, 808, - 816, 819, 821, 852, 881, 912, 916, 918, - 947, 955, 963, 971, 974, 976, 986, 994, - 997, 999, 1009, 1017, 1020, 1022, 1032, 1040, - 1043, 1045, 1076, 1105, 1115, 1123, 1133, 1141, - 1151, 1159, 1190, 1194, 1196, 1225, 1233, 1241, - 1244, 1246, 1256, 1264, 1267, 1269, 1279, 1287, - 1290, 1292, 1302, 1310, 1313, 1315, 1346, 1375, - 1385, 1393, 1403, 1411, 1421, 1429, 1460, 1464, - 1466, 1495, 1503, 1511, 1514, 1516, 1526, 1534, - 1537, 1539, 1570, 1599, 1609, 1617, 1648, 1652, - 1654, 1683, 1691, 1699, 1702, 1704, 1735, 1764, - 1795, 1799, 1801, 1830, 1855, 1880, 1886, 1894, - 1902, 1910, 1918, 1928, 1936, 1967, 1971, 1973, - 2002, 2010, 2018, 2026, 2034, 2038, 2040, 2048, - 2052, 2054, 2062, 2066, 2068, 2076, 2080, 2082, - 2090, 2094, 2096, 2104, 2108, 2110, 2118, 2122, - 2124, 2132, 2136, 2138, 2146, 2150, 2152, 2160, - 2164, 2166, 2174, 2178, 2180, 2188, 2192, 2194, - 2202, 2206, 2208, 2216, 2220, 2222, 2250, 2280, - 2288, 2292, 2294, 2302, 2312, 2320, 2325, 2330, - 2334, 2336, 2342, 2351, 2359, 2363, 2365, 2373, - 2383, 2387, 2389, 2418, 2422, 2424, 2432 + 0, 0, 8, 16, 19, 21, 31, 39, + 42, 44, 47, 49, 59, 67, 70, 72, + 80, 90, 98, 104, 110, 112, 121, 128, + 134, 136, 146, 154, 157, 159, 167, 174, + 180, 187, 194, 200, 208, 216, 219, 221, + 231, 239, 242, 244, 253, 257, 259, 266, + 274, 282, 285, 287, 297, 305, 308, 310, + 320, 328, 331, 333, 343, 351, 354, 356, + 366, 374, 377, 379, 389, 397, 400, 402, + 412, 420, 430, 438, 448, 456, 466, 474, + 484, 492, 496, 498, 506, 514, 517, 519, + 523, 525, 533, 541, 549, 552, 554, 564, + 572, 575, 577, 587, 595, 598, 600, 610, + 618, 621, 623, 633, 641, 651, 659, 669, + 677, 681, 683, 691, 699, 702, 704, 714, + 722, 725, 727, 737, 745, 748, 750, 760, + 768, 771, 773, 783, 791, 801, 809, 819, + 827, 831, 833, 841, 849, 852, 854, 864, + 872, 875, 877, 887, 895, 899, 901, 909, + 917, 920, 922, 926, 928, 934, 942, 950, + 958, 966, 976, 984, 988, 990, 998, 1006, + 1014, 1022, 1026, 1028, 1036, 1040, 1042, 1050, + 1054, 1056, 1064, 1068, 1070, 1078, 1082, 1084, + 1092, 1096, 1098, 1106, 1110, 1112, 1120, 1124, + 1126, 1134, 1138, 1140, 1148, 1152, 1154, 1162, + 1166, 1168, 1176, 1180, 1182, 1190, 1194, 1196, + 1204, 1208, 1210, 1218, 1222, 1224, 1232, 1242, + 1250, 1258, 1265, 1269, 1271, 1277, 1286, 1294, + 1298, 1300, 1308, 1318, 1322, 1324, 1328, 1330, + 1338, 1344, 1374, 1402, 1432, 1462, 1491, 1518, + 1548, 1576, 1606, 1634, 1664, 1692, 1722, 1750, + 1780, 1808, 1838, 1866, 1896, 1924, 1954, 1982, + 2012, 2040, 2070, 2098, 2128, 2156, 2186, 2214, + 2238, 2262, 2292, 2320, 2347, 2376 }; static const short _svg_path_indicies[] = { - 0, 2, 2, 3, 4, 2, 1, 5, - 5, 6, 6, 7, 5, 8, 1, 9, - 9, 10, 10, 11, 9, 12, 1, 13, - 14, 1, 15, 1, 16, 16, 18, 19, - 20, 20, 16, 17, 15, 1, 21, 21, - 23, 24, 21, 22, 25, 1, 26, 27, - 1, 28, 1, 29, 30, 30, 32, 33, - 34, 35, 36, 37, 38, 39, 40, 41, - 42, 43, 44, 45, 46, 36, 47, 48, - 49, 50, 51, 52, 53, 44, 30, 31, - 28, 1, 0, 54, 54, 56, 57, 59, - 60, 61, 62, 3, 63, 64, 65, 66, - 67, 68, 69, 70, 71, 4, 72, 73, - 74, 75, 67, 54, 55, 58, 1, 76, - 77, 1, 78, 1, 79, 79, 81, 82, - 83, 83, 79, 80, 78, 1, 84, 84, - 86, 87, 84, 85, 88, 1, 89, 90, - 1, 91, 1, 92, 93, 93, 95, 96, - 97, 98, 99, 100, 101, 102, 103, 104, - 105, 106, 107, 108, 109, 99, 110, 111, - 112, 113, 114, 115, 116, 107, 93, 94, - 91, 1, 56, 56, 55, 55, 57, 56, - 58, 1, 79, 79, 81, 78, 83, 83, - 79, 80, 77, 1, 86, 86, 85, 85, - 87, 86, 88, 1, 92, 93, 93, 95, - 91, 97, 98, 99, 100, 101, 102, 103, - 104, 105, 106, 107, 108, 109, 99, 110, - 111, 112, 113, 114, 115, 116, 107, 93, - 94, 90, 1, 117, 117, 118, 117, 119, - 1, 120, 120, 121, 120, 122, 1, 123, - 1, 124, 124, 125, 126, 127, 127, 124, - 123, 1, 128, 128, 129, 130, 128, 131, - 1, 129, 129, 130, 129, 131, 1, 132, - 1, 133, 133, 135, 136, 137, 137, 133, - 134, 132, 1, 138, 138, 140, 141, 138, - 139, 142, 1, 143, 144, 1, 145, 1, - 146, 146, 147, 148, 148, 146, 145, 1, - 149, 149, 150, 151, 152, 149, 1, 150, - 150, 151, 152, 150, 1, 153, 153, 154, - 153, 1, 155, 155, 156, 157, 158, 155, - 1, 156, 156, 157, 158, 156, 1, 159, - 159, 160, 159, 1, 161, 161, 163, 164, - 161, 162, 165, 1, 166, 167, 1, 168, - 1, 169, 169, 171, 172, 173, 173, 169, - 170, 168, 1, 174, 174, 176, 177, 174, - 175, 178, 1, 179, 180, 1, 181, 1, - 182, 183, 183, 184, 185, 186, 187, 188, - 189, 190, 191, 192, 193, 194, 195, 196, - 197, 198, 188, 199, 200, 201, 202, 203, - 204, 205, 196, 183, 181, 1, 0, 206, - 206, 120, 121, 59, 60, 61, 62, 3, - 63, 64, 65, 66, 67, 68, 69, 70, - 71, 4, 72, 73, 74, 75, 67, 206, - 122, 1, 124, 124, 125, 123, 127, 127, - 124, 207, 1, 208, 208, 209, 1, 209, - 1, 124, 124, 125, 126, 124, 209, 1, - 210, 210, 211, 211, 212, 210, 213, 1, - 214, 214, 215, 215, 216, 214, 217, 1, - 218, 219, 1, 220, 1, 221, 221, 223, - 224, 225, 225, 221, 222, 220, 1, 226, - 226, 228, 229, 226, 227, 230, 1, 231, - 232, 1, 233, 1, 234, 234, 236, 237, - 238, 238, 234, 235, 233, 1, 239, 239, - 241, 242, 239, 240, 243, 1, 244, 245, - 1, 246, 1, 247, 247, 249, 250, 251, - 251, 247, 248, 246, 1, 252, 252, 254, - 255, 252, 253, 256, 1, 257, 258, 1, - 259, 1, 260, 260, 262, 263, 264, 264, - 260, 261, 259, 1, 265, 265, 267, 268, - 265, 266, 269, 1, 270, 271, 1, 272, - 1, 273, 273, 275, 276, 277, 277, 273, - 274, 272, 1, 278, 278, 280, 281, 278, - 279, 282, 1, 283, 284, 1, 285, 1, - 286, 287, 287, 289, 290, 291, 292, 293, - 294, 295, 296, 297, 298, 299, 300, 301, - 302, 303, 293, 304, 305, 306, 307, 308, - 309, 310, 301, 287, 288, 285, 1, 0, - 311, 311, 214, 216, 59, 60, 61, 62, - 3, 63, 64, 65, 66, 67, 68, 69, - 70, 71, 4, 72, 73, 74, 75, 67, - 311, 215, 217, 1, 221, 221, 223, 220, - 225, 225, 221, 222, 219, 1, 228, 228, - 227, 227, 229, 228, 230, 1, 234, 234, - 236, 233, 238, 238, 234, 235, 232, 1, - 241, 241, 240, 240, 242, 241, 243, 1, - 247, 247, 249, 246, 251, 251, 247, 248, - 245, 1, 254, 254, 253, 253, 255, 254, - 256, 1, 260, 260, 262, 259, 264, 264, - 260, 261, 258, 1, 267, 267, 266, 266, - 268, 267, 269, 1, 273, 273, 275, 272, - 277, 277, 273, 274, 271, 1, 280, 280, - 279, 279, 281, 280, 282, 1, 286, 287, - 287, 289, 285, 291, 292, 293, 294, 295, - 296, 297, 298, 299, 300, 301, 302, 303, - 293, 304, 305, 306, 307, 308, 309, 310, - 301, 287, 288, 284, 1, 312, 312, 313, - 1, 313, 1, 286, 287, 287, 289, 290, - 291, 292, 294, 295, 296, 297, 298, 299, - 300, 301, 302, 303, 304, 305, 306, 307, - 308, 309, 310, 301, 287, 288, 313, 1, - 314, 314, 315, 315, 316, 314, 317, 1, - 318, 318, 319, 319, 320, 318, 321, 1, - 322, 323, 1, 324, 1, 325, 326, 326, - 328, 329, 330, 331, 332, 333, 334, 335, - 336, 337, 338, 339, 340, 341, 342, 332, - 343, 344, 345, 346, 347, 348, 349, 340, - 326, 327, 324, 1, 0, 350, 350, 318, - 320, 59, 60, 61, 62, 3, 63, 64, - 65, 66, 67, 68, 69, 70, 71, 4, - 72, 73, 74, 75, 67, 350, 319, 321, - 1, 325, 326, 326, 328, 324, 330, 331, - 332, 333, 334, 335, 336, 337, 338, 339, - 340, 341, 342, 332, 343, 344, 345, 346, - 347, 348, 349, 340, 326, 327, 323, 1, - 351, 351, 352, 1, 352, 1, 325, 326, - 326, 328, 329, 330, 331, 333, 334, 335, - 336, 337, 338, 339, 340, 341, 342, 343, - 344, 345, 346, 347, 348, 349, 340, 326, - 327, 352, 1, 353, 353, 354, 354, 355, - 353, 356, 1, 357, 357, 358, 358, 359, - 357, 360, 1, 361, 361, 362, 362, 363, - 361, 364, 1, 365, 366, 1, 367, 1, - 368, 368, 370, 371, 372, 372, 368, 369, - 367, 1, 373, 373, 375, 376, 373, 374, - 377, 1, 378, 379, 1, 380, 1, 381, - 381, 383, 384, 385, 385, 381, 382, 380, - 1, 386, 386, 388, 389, 386, 387, 390, - 1, 391, 392, 1, 393, 1, 394, 394, - 396, 397, 398, 398, 394, 395, 393, 1, - 399, 399, 401, 402, 399, 400, 403, 1, - 404, 405, 1, 406, 1, 407, 408, 408, - 410, 411, 412, 413, 414, 415, 416, 417, - 418, 419, 420, 421, 422, 423, 424, 414, - 425, 426, 427, 428, 429, 430, 431, 422, - 408, 409, 406, 1, 0, 432, 432, 361, - 363, 59, 60, 61, 62, 3, 63, 64, - 65, 66, 67, 68, 69, 70, 71, 4, - 72, 73, 74, 75, 67, 432, 362, 364, - 1, 368, 368, 370, 367, 372, 372, 368, - 369, 366, 1, 375, 375, 374, 374, 376, - 375, 377, 1, 381, 381, 383, 380, 385, - 385, 381, 382, 379, 1, 388, 388, 387, - 387, 389, 388, 390, 1, 394, 394, 396, - 393, 398, 398, 394, 395, 392, 1, 401, - 401, 400, 400, 402, 401, 403, 1, 407, - 408, 408, 410, 406, 412, 413, 414, 415, - 416, 417, 418, 419, 420, 421, 422, 423, - 424, 414, 425, 426, 427, 428, 429, 430, - 431, 422, 408, 409, 405, 1, 433, 433, - 434, 1, 434, 1, 407, 408, 408, 410, - 411, 412, 413, 415, 416, 417, 418, 419, - 420, 421, 422, 423, 424, 425, 426, 427, - 428, 429, 430, 431, 422, 408, 409, 434, - 1, 435, 435, 436, 436, 437, 435, 438, - 1, 439, 439, 440, 440, 441, 439, 442, - 1, 443, 444, 1, 445, 1, 446, 446, - 448, 449, 450, 450, 446, 447, 445, 1, - 451, 451, 453, 454, 451, 452, 455, 1, - 456, 457, 1, 458, 1, 459, 459, 461, - 462, 463, 463, 459, 460, 458, 1, 464, - 464, 466, 467, 464, 465, 468, 1, 469, - 470, 1, 471, 1, 472, 472, 474, 475, - 476, 476, 472, 473, 471, 1, 477, 477, - 479, 480, 477, 478, 481, 1, 482, 483, - 1, 484, 1, 485, 486, 486, 488, 489, - 490, 491, 492, 493, 494, 495, 496, 497, - 498, 499, 500, 501, 502, 492, 503, 504, - 505, 506, 507, 508, 509, 500, 486, 487, - 484, 1, 0, 510, 510, 439, 441, 59, - 60, 61, 62, 3, 63, 64, 65, 66, - 67, 68, 69, 70, 71, 4, 72, 73, - 74, 75, 67, 510, 440, 442, 1, 446, - 446, 448, 445, 450, 450, 446, 447, 444, - 1, 453, 453, 452, 452, 454, 453, 455, - 1, 459, 459, 461, 458, 463, 463, 459, - 460, 457, 1, 466, 466, 465, 465, 467, - 466, 468, 1, 472, 472, 474, 471, 476, - 476, 472, 473, 470, 1, 479, 479, 478, - 478, 480, 479, 481, 1, 485, 486, 486, - 488, 484, 490, 491, 492, 493, 494, 495, - 496, 497, 498, 499, 500, 501, 502, 492, - 503, 504, 505, 506, 507, 508, 509, 500, - 486, 487, 483, 1, 511, 511, 512, 1, - 512, 1, 485, 486, 486, 488, 489, 490, - 491, 493, 494, 495, 496, 497, 498, 499, - 500, 501, 502, 503, 504, 505, 506, 507, - 508, 509, 500, 486, 487, 512, 1, 513, - 513, 514, 514, 515, 513, 516, 1, 517, - 517, 518, 518, 519, 517, 520, 1, 521, - 522, 1, 523, 1, 524, 524, 526, 527, - 528, 528, 524, 525, 523, 1, 529, 529, - 531, 532, 529, 530, 533, 1, 534, 535, - 1, 536, 1, 537, 538, 538, 540, 541, + 0, 0, 2, 2, 3, 0, 4, 1, + 5, 5, 6, 6, 7, 5, 8, 1, + 9, 10, 1, 11, 1, 12, 12, 14, + 15, 16, 16, 12, 13, 11, 1, 17, + 17, 19, 20, 17, 18, 21, 1, 22, + 23, 1, 24, 1, 25, 26, 1, 27, + 1, 28, 28, 30, 31, 32, 32, 28, + 29, 27, 1, 33, 33, 35, 36, 33, + 34, 37, 1, 38, 39, 1, 40, 1, + 41, 41, 42, 42, 43, 41, 44, 1, + 28, 28, 30, 27, 32, 32, 28, 29, + 26, 1, 35, 35, 34, 34, 36, 35, + 37, 1, 45, 45, 46, 45, 47, 1, + 48, 48, 49, 48, 50, 1, 51, 1, + 52, 52, 53, 54, 55, 55, 52, 51, + 1, 56, 56, 57, 58, 56, 59, 1, + 57, 57, 58, 57, 59, 1, 60, 1, + 61, 61, 63, 64, 65, 65, 61, 62, + 60, 1, 66, 66, 68, 69, 66, 67, + 70, 1, 71, 72, 1, 73, 1, 74, + 74, 75, 76, 76, 74, 73, 1, 77, + 77, 78, 79, 80, 77, 1, 78, 78, + 79, 80, 78, 1, 81, 81, 82, 83, + 84, 81, 1, 85, 85, 86, 87, 88, + 85, 1, 86, 86, 87, 88, 86, 1, + 89, 89, 91, 92, 89, 90, 93, 1, + 94, 94, 96, 97, 94, 95, 98, 1, + 99, 100, 1, 101, 1, 102, 102, 104, + 105, 106, 106, 102, 103, 101, 1, 107, + 107, 109, 110, 107, 108, 111, 1, 112, + 113, 1, 114, 1, 52, 52, 53, 51, + 55, 55, 52, 115, 1, 116, 116, 117, + 1, 117, 1, 52, 52, 53, 54, 52, + 117, 1, 118, 118, 119, 119, 120, 118, + 121, 1, 122, 122, 123, 123, 124, 122, + 125, 1, 126, 127, 1, 128, 1, 129, + 129, 131, 132, 133, 133, 129, 130, 128, + 1, 134, 134, 136, 137, 134, 135, 138, + 1, 139, 140, 1, 141, 1, 142, 142, + 144, 145, 146, 146, 142, 143, 141, 1, + 147, 147, 149, 150, 147, 148, 151, 1, + 152, 153, 1, 154, 1, 155, 155, 157, + 158, 159, 159, 155, 156, 154, 1, 160, + 160, 162, 163, 160, 161, 164, 1, 165, + 166, 1, 167, 1, 168, 168, 170, 171, + 172, 172, 168, 169, 167, 1, 173, 173, + 175, 176, 173, 174, 177, 1, 178, 179, + 1, 180, 1, 181, 181, 183, 184, 185, + 185, 181, 182, 180, 1, 186, 186, 188, + 189, 186, 187, 190, 1, 191, 192, 1, + 193, 1, 129, 129, 131, 128, 133, 133, + 129, 130, 127, 1, 136, 136, 135, 135, + 137, 136, 138, 1, 142, 142, 144, 141, + 146, 146, 142, 143, 140, 1, 149, 149, + 148, 148, 150, 149, 151, 1, 155, 155, + 157, 154, 159, 159, 155, 156, 153, 1, + 162, 162, 161, 161, 163, 162, 164, 1, + 168, 168, 170, 167, 172, 172, 168, 169, + 166, 1, 175, 175, 174, 174, 176, 175, + 177, 1, 181, 181, 183, 180, 185, 185, + 181, 182, 179, 1, 188, 188, 187, 187, + 189, 188, 190, 1, 194, 194, 195, 1, + 195, 1, 196, 196, 197, 197, 198, 196, + 199, 1, 200, 200, 201, 201, 202, 200, + 203, 1, 204, 205, 1, 206, 1, 207, + 207, 208, 1, 208, 1, 209, 209, 210, + 210, 211, 209, 212, 1, 213, 213, 214, + 214, 215, 213, 216, 1, 217, 217, 218, + 218, 219, 217, 220, 1, 221, 222, 1, + 223, 1, 224, 224, 226, 227, 228, 228, + 224, 225, 223, 1, 229, 229, 231, 232, + 229, 230, 233, 1, 234, 235, 1, 236, + 1, 237, 237, 239, 240, 241, 241, 237, + 238, 236, 1, 242, 242, 244, 245, 242, + 243, 246, 1, 247, 248, 1, 249, 1, + 250, 250, 252, 253, 254, 254, 250, 251, + 249, 1, 255, 255, 257, 258, 255, 256, + 259, 1, 260, 261, 1, 262, 1, 224, + 224, 226, 223, 228, 228, 224, 225, 222, + 1, 231, 231, 230, 230, 232, 231, 233, + 1, 237, 237, 239, 236, 241, 241, 237, + 238, 235, 1, 244, 244, 243, 243, 245, + 244, 246, 1, 250, 250, 252, 249, 254, + 254, 250, 251, 248, 1, 257, 257, 256, + 256, 258, 257, 259, 1, 263, 263, 264, + 1, 264, 1, 265, 265, 266, 266, 267, + 265, 268, 1, 269, 269, 270, 270, 271, + 269, 272, 1, 273, 274, 1, 275, 1, + 276, 276, 278, 279, 280, 280, 276, 277, + 275, 1, 281, 281, 283, 284, 281, 282, + 285, 1, 286, 287, 1, 288, 1, 289, + 289, 291, 292, 293, 293, 289, 290, 288, + 1, 294, 294, 296, 297, 294, 295, 298, + 1, 299, 300, 1, 301, 1, 302, 302, + 304, 305, 306, 306, 302, 303, 301, 1, + 307, 307, 309, 310, 307, 308, 311, 1, + 312, 313, 1, 314, 1, 276, 276, 278, + 275, 280, 280, 276, 277, 274, 1, 283, + 283, 282, 282, 284, 283, 285, 1, 289, + 289, 291, 288, 293, 293, 289, 290, 287, + 1, 296, 296, 295, 295, 297, 296, 298, + 1, 302, 302, 304, 301, 306, 306, 302, + 303, 300, 1, 309, 309, 308, 308, 310, + 309, 311, 1, 315, 315, 316, 1, 316, + 1, 317, 317, 318, 318, 319, 317, 320, + 1, 321, 321, 322, 322, 323, 321, 324, + 1, 325, 326, 1, 327, 1, 328, 328, + 330, 331, 332, 332, 328, 329, 327, 1, + 333, 333, 335, 336, 333, 334, 337, 1, + 338, 339, 1, 340, 1, 328, 328, 330, + 327, 332, 332, 328, 329, 326, 1, 335, + 335, 334, 334, 336, 335, 337, 1, 341, + 341, 342, 1, 342, 1, 343, 343, 344, + 344, 345, 343, 346, 1, 347, 347, 348, + 348, 349, 347, 350, 1, 351, 352, 1, + 353, 1, 354, 354, 355, 1, 355, 1, + 356, 356, 357, 356, 358, 1, 359, 359, + 360, 360, 361, 359, 362, 1, 363, 363, + 364, 364, 365, 363, 366, 1, 367, 367, + 368, 368, 369, 367, 370, 1, 371, 371, + 372, 372, 373, 371, 374, 1, 12, 12, + 14, 11, 16, 16, 12, 13, 10, 1, + 19, 19, 18, 18, 20, 19, 21, 1, + 375, 375, 376, 1, 376, 1, 377, 377, + 378, 378, 379, 377, 380, 1, 381, 381, + 382, 382, 383, 381, 384, 1, 385, 385, + 386, 386, 387, 385, 388, 1, 389, 389, + 390, 390, 391, 389, 392, 1, 393, 393, + 394, 1, 394, 1, 12, 12, 14, 15, + 12, 13, 394, 1, 395, 395, 396, 1, + 396, 1, 328, 328, 330, 331, 328, 329, + 396, 1, 397, 397, 398, 1, 398, 1, + 302, 302, 304, 305, 302, 303, 398, 1, + 399, 399, 400, 1, 400, 1, 289, 289, + 291, 292, 289, 290, 400, 1, 401, 401, + 402, 1, 402, 1, 276, 276, 278, 279, + 276, 277, 402, 1, 403, 403, 404, 1, + 404, 1, 250, 250, 252, 253, 250, 251, + 404, 1, 405, 405, 406, 1, 406, 1, + 237, 237, 239, 240, 237, 238, 406, 1, + 407, 407, 408, 1, 408, 1, 224, 224, + 226, 227, 224, 225, 408, 1, 409, 409, + 410, 1, 410, 1, 181, 181, 183, 184, + 181, 182, 410, 1, 411, 411, 412, 1, + 412, 1, 168, 168, 170, 171, 168, 169, + 412, 1, 413, 413, 414, 1, 414, 1, + 155, 155, 157, 158, 155, 156, 414, 1, + 415, 415, 416, 1, 416, 1, 142, 142, + 144, 145, 142, 143, 416, 1, 417, 417, + 418, 1, 418, 1, 129, 129, 131, 132, + 129, 130, 418, 1, 419, 419, 420, 1, + 420, 1, 109, 109, 108, 108, 110, 109, + 111, 1, 421, 421, 422, 1, 422, 1, + 102, 102, 104, 105, 102, 103, 422, 1, + 102, 102, 104, 101, 106, 106, 102, 103, + 100, 1, 96, 96, 95, 95, 97, 96, + 98, 1, 423, 423, 425, 426, 423, 424, + 427, 1, 428, 428, 429, 430, 431, 428, + 1, 432, 432, 433, 1, 433, 1, 74, + 74, 75, 74, 433, 1, 74, 74, 75, + 73, 76, 76, 74, 72, 1, 68, 68, + 67, 67, 69, 68, 70, 1, 434, 434, + 435, 1, 435, 1, 61, 61, 63, 64, + 61, 62, 435, 1, 61, 61, 63, 60, + 65, 65, 61, 62, 436, 1, 437, 437, + 438, 1, 438, 1, 439, 439, 440, 1, + 440, 1, 28, 28, 30, 31, 28, 29, + 440, 1, 441, 441, 442, 443, 441, 1, + 444, 444, 446, 447, 448, 449, 450, 451, + 452, 453, 454, 455, 456, 457, 458, 459, + 460, 450, 461, 462, 463, 464, 465, 466, + 467, 458, 444, 445, 24, 1, 468, 468, + 41, 43, 469, 470, 471, 472, 442, 473, + 474, 475, 476, 477, 478, 479, 480, 481, + 443, 482, 483, 484, 485, 477, 468, 42, + 44, 1, 486, 486, 488, 489, 490, 491, + 492, 493, 494, 495, 496, 497, 498, 499, + 500, 501, 502, 492, 503, 504, 505, 506, + 507, 508, 509, 500, 486, 487, 40, 1, + 486, 486, 488, 40, 490, 491, 492, 493, + 494, 495, 496, 497, 498, 499, 500, 501, + 502, 492, 503, 504, 505, 506, 507, 508, + 509, 500, 486, 487, 39, 1, 510, 510, + 511, 512, 513, 514, 515, 516, 517, 518, + 519, 520, 521, 522, 523, 524, 525, 515, + 526, 527, 528, 529, 530, 531, 532, 523, + 510, 114, 1, 533, 533, 48, 49, 469, + 470, 471, 472, 442, 473, 474, 475, 476, + 477, 478, 479, 480, 481, 443, 482, 483, + 484, 485, 477, 533, 50, 1, 534, 534, + 536, 537, 538, 539, 540, 541, 542, 543, + 544, 545, 546, 547, 548, 549, 550, 540, + 551, 552, 553, 554, 555, 556, 557, 548, + 534, 535, 193, 1, 558, 558, 122, 124, + 469, 470, 471, 472, 442, 473, 474, 475, + 476, 477, 478, 479, 480, 481, 443, 482, + 483, 484, 485, 477, 558, 123, 125, 1, + 534, 534, 536, 193, 538, 539, 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, - 550, 551, 552, 553, 554, 544, 555, 556, - 557, 558, 559, 560, 561, 552, 538, 539, - 536, 1, 0, 562, 562, 517, 519, 59, - 60, 61, 62, 3, 63, 64, 65, 66, - 67, 68, 69, 70, 71, 4, 72, 73, - 74, 75, 67, 562, 518, 520, 1, 524, - 524, 526, 523, 528, 528, 524, 525, 522, - 1, 531, 531, 530, 530, 532, 531, 533, - 1, 537, 538, 538, 540, 536, 542, 543, - 544, 545, 546, 547, 548, 549, 550, 551, - 552, 553, 554, 544, 555, 556, 557, 558, - 559, 560, 561, 552, 538, 539, 535, 1, - 563, 563, 564, 1, 564, 1, 537, 538, - 538, 540, 541, 542, 543, 545, 546, 547, - 548, 549, 550, 551, 552, 553, 554, 555, - 556, 557, 558, 559, 560, 561, 552, 538, - 539, 564, 1, 565, 565, 566, 566, 567, - 565, 568, 1, 569, 569, 570, 570, 571, - 569, 572, 1, 573, 574, 1, 575, 1, - 576, 577, 577, 579, 580, 581, 582, 583, - 584, 585, 586, 587, 588, 589, 590, 591, - 592, 593, 583, 594, 595, 596, 597, 598, - 599, 600, 591, 577, 578, 575, 1, 0, - 601, 601, 569, 571, 59, 60, 61, 62, - 3, 63, 64, 65, 66, 67, 68, 69, - 70, 71, 4, 72, 73, 74, 75, 67, - 601, 570, 572, 1, 576, 577, 577, 579, - 575, 581, 582, 583, 584, 585, 586, 587, - 588, 589, 590, 591, 592, 593, 583, 594, - 595, 596, 597, 598, 599, 600, 591, 577, - 578, 574, 1, 602, 602, 603, 1, 603, - 1, 576, 577, 577, 579, 580, 581, 582, - 584, 585, 586, 587, 588, 589, 590, 591, + 550, 540, 551, 552, 553, 554, 555, 556, + 557, 548, 534, 535, 192, 1, 534, 534, + 536, 537, 538, 539, 541, 542, 543, 544, + 545, 546, 547, 548, 549, 550, 551, 552, + 553, 554, 555, 556, 557, 548, 534, 535, + 195, 1, 559, 559, 561, 562, 563, 564, + 565, 566, 567, 568, 569, 570, 571, 572, + 573, 574, 575, 565, 576, 577, 578, 579, + 580, 581, 582, 573, 559, 560, 206, 1, + 583, 583, 200, 202, 469, 470, 471, 472, + 442, 473, 474, 475, 476, 477, 478, 479, + 480, 481, 443, 482, 483, 484, 485, 477, + 583, 201, 203, 1, 559, 559, 561, 206, + 563, 564, 565, 566, 567, 568, 569, 570, + 571, 572, 573, 574, 575, 565, 576, 577, + 578, 579, 580, 581, 582, 573, 559, 560, + 205, 1, 559, 559, 561, 562, 563, 564, + 566, 567, 568, 569, 570, 571, 572, 573, + 574, 575, 576, 577, 578, 579, 580, 581, + 582, 573, 559, 560, 208, 1, 584, 584, + 586, 587, 588, 589, 590, 591, 592, 593, + 594, 595, 596, 597, 598, 599, 600, 590, + 601, 602, 603, 604, 605, 606, 607, 598, + 584, 585, 262, 1, 608, 608, 217, 219, + 469, 470, 471, 472, 442, 473, 474, 475, + 476, 477, 478, 479, 480, 481, 443, 482, + 483, 484, 485, 477, 608, 218, 220, 1, + 584, 584, 586, 262, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, - 600, 591, 577, 578, 603, 1, 604, 605, - 605, 606, 607, 608, 609, 610, 611, 612, + 600, 590, 601, 602, 603, 604, 605, 606, + 607, 598, 584, 585, 261, 1, 584, 584, + 586, 587, 588, 589, 591, 592, 593, 594, + 595, 596, 597, 598, 599, 600, 601, 602, + 603, 604, 605, 606, 607, 598, 584, 585, + 264, 1, 609, 609, 611, 612, 613, 614, + 615, 616, 617, 618, 619, 620, 621, 622, + 623, 624, 625, 615, 626, 627, 628, 629, + 630, 631, 632, 623, 609, 610, 314, 1, + 633, 633, 269, 271, 469, 470, 471, 472, + 442, 473, 474, 475, 476, 477, 478, 479, + 480, 481, 443, 482, 483, 484, 485, 477, + 633, 270, 272, 1, 609, 609, 611, 314, 613, 614, 615, 616, 617, 618, 619, 620, - 621, 622, 623, 624, 615, 605, 1, 0, - 625, 625, 59, 60, 61, 62, 3, 63, - 64, 65, 66, 67, 68, 69, 70, 71, - 4, 72, 73, 74, 75, 67, 625, 1, - 626, 626, 627, 626, 628, 1, 629, 629, - 630, 630, 631, 629, 632, 1, 633, 633, - 634, 634, 635, 633, 636, 1, 637, 637, - 638, 638, 639, 637, 640, 1, 641, 641, - 642, 642, 643, 641, 644, 1, 16, 16, - 18, 15, 20, 20, 16, 17, 14, 1, - 23, 23, 22, 22, 24, 23, 25, 1, - 29, 30, 30, 32, 28, 34, 35, 36, - 37, 38, 39, 40, 41, 42, 43, 44, - 45, 46, 36, 47, 48, 49, 50, 51, - 52, 53, 44, 30, 31, 27, 1, 645, - 645, 646, 1, 646, 1, 29, 30, 30, - 32, 33, 34, 35, 37, 38, 39, 40, - 41, 42, 43, 44, 45, 46, 47, 48, - 49, 50, 51, 52, 53, 44, 30, 31, - 646, 1, 647, 647, 648, 648, 649, 647, - 650, 1, 651, 651, 652, 652, 653, 651, - 654, 1, 655, 655, 656, 656, 657, 655, - 658, 1, 659, 659, 660, 660, 661, 659, - 662, 1, 663, 663, 664, 1, 664, 1, - 16, 16, 18, 19, 16, 17, 664, 1, - 665, 665, 666, 1, 666, 1, 524, 524, - 526, 527, 524, 525, 666, 1, 667, 667, - 668, 1, 668, 1, 472, 472, 474, 475, - 472, 473, 668, 1, 669, 669, 670, 1, - 670, 1, 459, 459, 461, 462, 459, 460, - 670, 1, 671, 671, 672, 1, 672, 1, - 446, 446, 448, 449, 446, 447, 672, 1, - 673, 673, 674, 1, 674, 1, 394, 394, - 396, 397, 394, 395, 674, 1, 675, 675, - 676, 1, 676, 1, 381, 381, 383, 384, - 381, 382, 676, 1, 677, 677, 678, 1, - 678, 1, 368, 368, 370, 371, 368, 369, - 678, 1, 679, 679, 680, 1, 680, 1, - 273, 273, 275, 276, 273, 274, 680, 1, - 681, 681, 682, 1, 682, 1, 260, 260, - 262, 263, 260, 261, 682, 1, 683, 683, - 684, 1, 684, 1, 247, 247, 249, 250, - 247, 248, 684, 1, 685, 685, 686, 1, - 686, 1, 234, 234, 236, 237, 234, 235, - 686, 1, 687, 687, 688, 1, 688, 1, - 221, 221, 223, 224, 221, 222, 688, 1, - 689, 689, 690, 1, 690, 1, 182, 183, - 183, 184, 185, 186, 187, 189, 190, 191, - 192, 193, 194, 195, 196, 197, 198, 199, - 200, 201, 202, 203, 204, 205, 196, 183, - 690, 1, 182, 183, 183, 184, 181, 186, - 187, 188, 189, 190, 191, 192, 193, 194, - 195, 196, 197, 198, 188, 199, 200, 201, - 202, 203, 204, 205, 196, 183, 180, 1, - 176, 176, 175, 175, 177, 176, 178, 1, - 691, 691, 692, 1, 692, 1, 169, 169, - 171, 172, 169, 170, 692, 1, 169, 169, - 171, 168, 173, 173, 169, 170, 167, 1, - 163, 163, 162, 162, 164, 163, 165, 1, - 693, 693, 694, 693, 1, 695, 695, 696, - 695, 1, 697, 697, 698, 1, 698, 1, - 146, 146, 147, 146, 698, 1, 146, 146, - 147, 145, 148, 148, 146, 144, 1, 140, - 140, 139, 139, 141, 140, 142, 1, 699, - 699, 700, 1, 700, 1, 133, 133, 135, - 136, 133, 134, 700, 1, 133, 133, 135, - 132, 137, 137, 133, 134, 701, 1, 702, - 702, 703, 1, 703, 1, 92, 93, 93, - 95, 96, 97, 98, 100, 101, 102, 103, - 104, 105, 106, 107, 108, 109, 110, 111, - 112, 113, 114, 115, 116, 107, 93, 94, - 703, 1, 704, 704, 705, 1, 705, 1, - 79, 79, 81, 82, 79, 80, 705, 1, - 1, 0 + 621, 622, 623, 624, 625, 615, 626, 627, + 628, 629, 630, 631, 632, 623, 609, 610, + 313, 1, 609, 609, 611, 612, 613, 614, + 616, 617, 618, 619, 620, 621, 622, 623, + 624, 625, 626, 627, 628, 629, 630, 631, + 632, 623, 609, 610, 316, 1, 634, 634, + 636, 637, 638, 639, 640, 641, 642, 643, + 644, 645, 646, 647, 648, 649, 650, 640, + 651, 652, 653, 654, 655, 656, 657, 648, + 634, 635, 340, 1, 658, 658, 321, 323, + 469, 470, 471, 472, 442, 473, 474, 475, + 476, 477, 478, 479, 480, 481, 443, 482, + 483, 484, 485, 477, 658, 322, 324, 1, + 634, 634, 636, 340, 638, 639, 640, 641, + 642, 643, 644, 645, 646, 647, 648, 649, + 650, 640, 651, 652, 653, 654, 655, 656, + 657, 648, 634, 635, 339, 1, 634, 634, + 636, 637, 638, 639, 641, 642, 643, 644, + 645, 646, 647, 648, 649, 650, 651, 652, + 653, 654, 655, 656, 657, 648, 634, 635, + 342, 1, 659, 659, 661, 662, 663, 664, + 665, 666, 667, 668, 669, 670, 671, 672, + 673, 674, 675, 665, 676, 677, 678, 679, + 680, 681, 682, 673, 659, 660, 353, 1, + 683, 683, 347, 349, 469, 470, 471, 472, + 442, 473, 474, 475, 476, 477, 478, 479, + 480, 481, 443, 482, 483, 484, 485, 477, + 683, 348, 350, 1, 659, 659, 661, 353, + 663, 664, 665, 666, 667, 668, 669, 670, + 671, 672, 673, 674, 675, 665, 676, 677, + 678, 679, 680, 681, 682, 673, 659, 660, + 352, 1, 659, 659, 661, 662, 663, 664, + 666, 667, 668, 669, 670, 671, 672, 673, + 674, 675, 676, 677, 678, 679, 680, 681, + 682, 673, 659, 660, 355, 1, 684, 684, + 685, 686, 687, 688, 689, 690, 691, 692, + 693, 694, 695, 696, 697, 698, 699, 700, + 701, 702, 703, 694, 684, 1, 704, 704, + 469, 470, 471, 472, 442, 473, 474, 475, + 476, 477, 478, 479, 480, 481, 443, 482, + 483, 484, 485, 477, 704, 1, 444, 444, + 446, 24, 448, 449, 450, 451, 452, 453, + 454, 455, 456, 457, 458, 459, 460, 450, + 461, 462, 463, 464, 465, 466, 467, 458, + 444, 445, 23, 1, 444, 444, 446, 447, + 448, 449, 451, 452, 453, 454, 455, 456, + 457, 458, 459, 460, 461, 462, 463, 464, + 465, 466, 467, 458, 444, 445, 376, 1, + 510, 510, 511, 512, 513, 514, 516, 517, + 518, 519, 520, 521, 522, 523, 524, 525, + 526, 527, 528, 529, 530, 531, 532, 523, + 510, 420, 1, 510, 510, 511, 114, 513, + 514, 515, 516, 517, 518, 519, 520, 521, + 522, 523, 524, 525, 515, 526, 527, 528, + 529, 530, 531, 532, 523, 510, 113, 1, + 486, 486, 488, 489, 490, 491, 493, 494, + 495, 496, 497, 498, 499, 500, 501, 502, + 503, 504, 505, 506, 507, 508, 509, 500, + 486, 487, 438, 1, 0 }; static const short _svg_path_trans_targs[] = { - 270, 0, 1, 2, 193, 3, 4, 5, - 194, 3, 4, 5, 194, 5, 194, 6, - 7, 8, 195, 9, 204, 7, 8, 195, - 9, 196, 9, 196, 10, 270, 11, 12, - 19, 13, 23, 54, 197, 94, 104, 2, - 105, 133, 161, 177, 187, 189, 190, 191, - 192, 193, 200, 201, 202, 203, 11, 12, - 19, 13, 20, 23, 54, 94, 104, 105, - 133, 161, 177, 187, 189, 190, 191, 192, - 200, 201, 202, 203, 13, 20, 14, 15, - 16, 21, 17, 267, 15, 16, 21, 17, - 22, 17, 22, 18, 270, 11, 12, 19, - 13, 23, 54, 264, 94, 104, 2, 105, - 133, 161, 177, 187, 189, 190, 191, 192, - 193, 200, 201, 202, 203, 24, 25, 50, - 24, 25, 50, 26, 27, 28, 29, 51, - 27, 28, 29, 263, 30, 31, 32, 259, - 33, 260, 31, 32, 259, 33, 258, 33, - 258, 34, 35, 36, 255, 35, 36, 37, - 254, 38, 39, 38, 39, 40, 253, 41, - 252, 41, 42, 252, 43, 251, 43, 251, - 44, 45, 46, 247, 47, 248, 45, 46, - 247, 47, 246, 47, 246, 48, 270, 49, - 24, 25, 23, 54, 243, 94, 104, 2, - 105, 133, 161, 177, 187, 189, 190, 191, - 192, 193, 200, 201, 202, 203, 49, 50, - 52, 53, 55, 56, 57, 80, 55, 56, - 57, 80, 57, 80, 58, 59, 60, 81, - 61, 240, 59, 60, 81, 61, 82, 61, - 82, 62, 63, 64, 83, 65, 237, 63, - 64, 83, 65, 84, 65, 84, 66, 67, - 68, 85, 69, 234, 67, 68, 85, 69, - 86, 69, 86, 70, 71, 72, 87, 73, - 231, 71, 72, 87, 73, 88, 73, 88, - 74, 75, 76, 89, 77, 228, 75, 76, - 89, 77, 90, 77, 90, 78, 270, 79, - 56, 55, 57, 23, 54, 91, 94, 104, - 2, 105, 133, 161, 177, 187, 189, 190, - 191, 192, 193, 200, 201, 202, 203, 79, - 92, 93, 95, 96, 97, 100, 95, 96, - 97, 100, 97, 100, 98, 270, 99, 96, - 95, 97, 23, 54, 101, 94, 104, 2, - 105, 133, 161, 177, 187, 189, 190, 191, - 192, 193, 200, 201, 202, 203, 99, 102, - 103, 19, 12, 13, 20, 106, 107, 108, - 123, 106, 107, 108, 123, 108, 123, 109, - 110, 111, 124, 112, 225, 110, 111, 124, - 112, 125, 112, 125, 113, 114, 115, 126, - 116, 222, 114, 115, 126, 116, 127, 116, - 127, 117, 118, 119, 128, 120, 219, 118, - 119, 128, 120, 129, 120, 129, 121, 270, - 122, 107, 106, 108, 23, 54, 130, 94, - 104, 2, 105, 133, 161, 177, 187, 189, - 190, 191, 192, 193, 200, 201, 202, 203, - 122, 131, 132, 134, 135, 136, 151, 134, - 135, 136, 151, 136, 151, 137, 138, 139, - 152, 140, 216, 138, 139, 152, 140, 153, - 140, 153, 141, 142, 143, 154, 144, 213, - 142, 143, 154, 144, 155, 144, 155, 145, - 146, 147, 156, 148, 210, 146, 147, 156, - 148, 157, 148, 157, 149, 270, 150, 135, - 134, 136, 23, 54, 158, 94, 104, 2, - 105, 133, 161, 177, 187, 189, 190, 191, - 192, 193, 200, 201, 202, 203, 150, 159, - 160, 162, 163, 164, 171, 162, 163, 164, - 171, 164, 171, 165, 166, 167, 172, 168, - 207, 166, 167, 172, 168, 173, 168, 173, - 169, 270, 170, 163, 162, 164, 23, 54, - 174, 94, 104, 2, 105, 133, 161, 177, - 187, 189, 190, 191, 192, 193, 200, 201, - 202, 203, 170, 175, 176, 178, 179, 180, - 183, 178, 179, 180, 183, 180, 183, 181, - 270, 182, 179, 178, 180, 23, 54, 184, - 94, 104, 2, 105, 133, 161, 177, 187, - 189, 190, 191, 192, 193, 200, 201, 202, - 203, 182, 185, 186, 270, 188, 23, 54, - 94, 104, 2, 105, 133, 161, 177, 187, - 189, 190, 191, 192, 193, 200, 201, 202, - 203, 188, 24, 25, 50, 55, 56, 57, - 80, 95, 96, 97, 100, 19, 12, 13, - 20, 3, 4, 5, 194, 198, 199, 106, - 107, 108, 123, 134, 135, 136, 151, 162, - 163, 164, 171, 178, 179, 180, 183, 205, - 206, 208, 209, 211, 212, 214, 215, 217, - 218, 220, 221, 223, 224, 226, 227, 229, - 230, 232, 233, 235, 236, 238, 239, 241, - 242, 244, 245, 249, 250, 41, 252, 38, - 39, 256, 257, 261, 262, 263, 265, 266, - 268, 269 + 2, 0, 3, 4, 161, 2, 3, 4, + 161, 4, 161, 5, 6, 7, 162, 8, + 169, 6, 7, 162, 8, 265, 8, 265, + 233, 10, 16, 11, 12, 13, 17, 14, + 229, 12, 13, 17, 14, 236, 14, 236, + 235, 15, 9, 10, 16, 19, 20, 43, + 19, 20, 43, 21, 22, 23, 24, 44, + 22, 23, 24, 226, 25, 26, 27, 222, + 28, 223, 26, 27, 222, 28, 221, 28, + 221, 29, 30, 31, 218, 30, 31, 32, + 217, 33, 34, 35, 216, 33, 34, 35, + 216, 36, 37, 215, 38, 214, 36, 37, + 215, 38, 214, 38, 214, 39, 40, 41, + 210, 42, 211, 40, 41, 210, 42, 268, + 42, 268, 237, 43, 45, 46, 48, 49, + 50, 71, 48, 49, 50, 71, 50, 71, + 51, 52, 53, 72, 54, 205, 52, 53, + 72, 54, 73, 54, 73, 55, 56, 57, + 74, 58, 202, 56, 57, 74, 58, 75, + 58, 75, 59, 60, 61, 76, 62, 199, + 60, 61, 76, 62, 77, 62, 77, 63, + 64, 65, 78, 66, 196, 64, 65, 78, + 66, 79, 66, 79, 67, 68, 69, 80, + 70, 193, 68, 69, 80, 70, 241, 70, + 241, 239, 82, 242, 84, 85, 86, 245, + 84, 85, 86, 245, 86, 245, 243, 88, + 246, 15, 9, 10, 16, 91, 92, 93, + 106, 91, 92, 93, 106, 93, 106, 94, + 95, 96, 107, 97, 190, 95, 96, 107, + 97, 108, 97, 108, 98, 99, 100, 109, + 101, 187, 99, 100, 109, 101, 110, 101, + 110, 102, 103, 104, 111, 105, 184, 103, + 104, 111, 105, 249, 105, 249, 247, 113, + 250, 115, 116, 117, 130, 115, 116, 117, + 130, 117, 130, 118, 119, 120, 131, 121, + 181, 119, 120, 131, 121, 132, 121, 132, + 122, 123, 124, 133, 125, 178, 123, 124, + 133, 125, 134, 125, 134, 126, 127, 128, + 135, 129, 175, 127, 128, 135, 129, 253, + 129, 253, 251, 137, 254, 139, 140, 141, + 146, 139, 140, 141, 146, 141, 146, 142, + 143, 144, 147, 145, 172, 143, 144, 147, + 145, 257, 145, 257, 255, 149, 258, 151, + 152, 153, 261, 151, 152, 153, 261, 153, + 261, 259, 155, 262, 19, 20, 43, 48, + 49, 50, 71, 84, 85, 86, 245, 15, + 9, 10, 16, 2, 3, 4, 161, 164, + 266, 91, 92, 93, 106, 115, 116, 117, + 130, 139, 140, 141, 146, 151, 152, 153, + 261, 170, 171, 173, 174, 176, 177, 179, + 180, 182, 183, 185, 186, 188, 189, 191, + 192, 194, 195, 197, 198, 200, 201, 203, + 204, 206, 207, 209, 267, 212, 213, 36, + 37, 215, 38, 214, 33, 34, 35, 216, + 219, 220, 224, 225, 226, 228, 269, 230, + 231, 232, 1, 160, 234, 9, 15, 10, + 18, 47, 163, 83, 89, 1, 90, 114, + 138, 150, 263, 156, 157, 158, 159, 160, + 165, 166, 167, 168, 234, 18, 47, 83, + 89, 90, 114, 138, 150, 263, 156, 157, + 158, 159, 165, 166, 167, 168, 234, 9, + 15, 10, 18, 47, 227, 83, 89, 1, + 90, 114, 138, 150, 263, 156, 157, 158, + 159, 160, 165, 166, 167, 168, 238, 19, + 20, 18, 47, 208, 83, 89, 1, 90, + 114, 138, 150, 263, 156, 157, 158, 159, + 160, 165, 166, 167, 168, 238, 240, 49, + 48, 50, 18, 47, 81, 83, 89, 1, + 90, 114, 138, 150, 263, 156, 157, 158, + 159, 160, 165, 166, 167, 168, 240, 244, + 85, 84, 86, 18, 47, 87, 83, 89, + 1, 90, 114, 138, 150, 263, 156, 157, + 158, 159, 160, 165, 166, 167, 168, 244, + 248, 92, 91, 93, 18, 47, 112, 83, + 89, 1, 90, 114, 138, 150, 263, 156, + 157, 158, 159, 160, 165, 166, 167, 168, + 248, 252, 116, 115, 117, 18, 47, 136, + 83, 89, 1, 90, 114, 138, 150, 263, + 156, 157, 158, 159, 160, 165, 166, 167, + 168, 252, 256, 140, 139, 141, 18, 47, + 148, 83, 89, 1, 90, 114, 138, 150, + 263, 156, 157, 158, 159, 160, 165, 166, + 167, 168, 256, 260, 152, 151, 153, 18, + 47, 154, 83, 89, 1, 90, 114, 138, + 150, 263, 156, 157, 158, 159, 160, 165, + 166, 167, 168, 260, 264, 18, 47, 83, + 89, 1, 90, 114, 138, 150, 263, 156, + 157, 158, 159, 160, 165, 166, 167, 168, + 264 }; static const char _svg_path_trans_actions[] = { - 15, 0, 0, 0, 0, 9, 47, 47, - 47, 0, 1, 1, 1, 0, 0, 0, - 3, 17, 3, 17, 0, 0, 1, 0, - 1, 1, 0, 0, 0, 60, 20, 56, - 20, 56, 20, 20, 0, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 20, 0, 1, - 0, 1, 1, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 3, - 17, 3, 17, 0, 0, 1, 0, 1, - 1, 0, 0, 0, 68, 23, 64, 23, - 64, 23, 23, 0, 23, 23, 23, 23, - 23, 23, 23, 23, 23, 23, 23, 23, - 23, 23, 23, 23, 23, 9, 47, 47, - 0, 1, 1, 0, 3, 3, 17, 0, - 0, 0, 1, 1, 0, 3, 17, 3, - 17, 0, 0, 1, 0, 1, 1, 0, - 0, 0, 3, 3, 0, 0, 0, 0, - 0, 7, 7, 0, 0, 0, 0, 7, - 7, 0, 1, 0, 1, 1, 0, 0, - 0, 3, 17, 3, 17, 0, 0, 1, - 0, 1, 1, 0, 0, 0, 124, 44, - 44, 120, 44, 44, 0, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 44, 44, - 44, 44, 44, 44, 44, 44, 0, 0, - 0, 0, 9, 47, 47, 47, 0, 1, - 1, 1, 0, 0, 0, 3, 17, 3, - 17, 0, 0, 1, 0, 1, 1, 0, - 0, 0, 3, 17, 3, 17, 0, 0, - 1, 0, 1, 1, 0, 0, 0, 3, - 17, 3, 17, 0, 0, 1, 0, 1, - 1, 0, 0, 0, 3, 17, 3, 17, + 9, 0, 51, 51, 51, 0, 1, 1, + 1, 0, 0, 0, 3, 15, 3, 15, 0, 0, 1, 0, 1, 1, 0, 0, - 0, 3, 17, 3, 17, 0, 0, 1, - 0, 1, 1, 0, 0, 0, 92, 32, - 88, 32, 88, 32, 32, 0, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 32, - 32, 32, 32, 32, 32, 32, 32, 0, - 0, 0, 9, 47, 47, 47, 0, 1, - 1, 1, 0, 0, 0, 76, 26, 72, - 26, 72, 26, 26, 0, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 26, 26, - 26, 26, 26, 26, 26, 26, 0, 0, - 0, 9, 47, 47, 47, 9, 47, 47, - 47, 0, 1, 1, 1, 0, 0, 0, - 3, 17, 3, 17, 0, 0, 1, 0, - 1, 1, 0, 0, 0, 3, 17, 3, - 17, 0, 0, 1, 0, 1, 1, 0, - 0, 0, 3, 17, 3, 17, 0, 0, - 1, 0, 1, 1, 0, 0, 0, 108, - 38, 104, 38, 104, 38, 38, 0, 38, - 38, 38, 38, 38, 38, 38, 38, 38, - 38, 38, 38, 38, 38, 38, 38, 38, - 0, 0, 0, 9, 47, 47, 47, 0, - 1, 1, 1, 0, 0, 0, 3, 17, - 3, 17, 0, 0, 1, 0, 1, 1, - 0, 0, 0, 3, 17, 3, 17, 0, + 0, 0, 0, 0, 3, 15, 3, 15, + 0, 0, 1, 0, 1, 1, 0, 0, + 0, 0, 1, 1, 1, 9, 51, 51, + 0, 1, 1, 0, 3, 3, 15, 0, + 0, 0, 1, 1, 0, 3, 15, 3, + 15, 0, 0, 1, 0, 1, 1, 0, + 0, 0, 3, 3, 0, 0, 0, 0, + 0, 7, 7, 7, 7, 0, 0, 0, + 0, 7, 48, 7, 48, 48, 0, 1, + 0, 1, 1, 0, 0, 0, 3, 15, + 3, 15, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 0, 9, 51, + 51, 51, 0, 1, 1, 1, 0, 0, + 0, 3, 15, 3, 15, 0, 0, 1, + 0, 1, 1, 0, 0, 0, 3, 15, + 3, 15, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 3, 15, 3, 15, 0, 0, 1, 0, 1, 1, 0, 0, 0, - 3, 17, 3, 17, 0, 0, 1, 0, - 1, 1, 0, 0, 0, 100, 35, 96, - 35, 96, 35, 35, 0, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 0, 0, - 0, 9, 47, 47, 47, 0, 1, 1, - 1, 0, 0, 0, 3, 17, 3, 17, + 3, 15, 3, 15, 0, 0, 1, 0, + 1, 1, 0, 0, 0, 3, 15, 3, + 15, 0, 0, 1, 0, 1, 1, 0, + 0, 0, 0, 0, 9, 51, 51, 51, + 0, 1, 1, 1, 0, 0, 0, 0, + 0, 9, 51, 51, 51, 9, 51, 51, + 51, 0, 1, 1, 1, 0, 0, 0, + 3, 15, 3, 15, 0, 0, 1, 0, + 1, 1, 0, 0, 0, 3, 15, 3, + 15, 0, 0, 1, 0, 1, 1, 0, + 0, 0, 3, 15, 3, 15, 0, 0, + 1, 0, 1, 1, 0, 0, 0, 0, + 0, 9, 51, 51, 51, 0, 1, 1, + 1, 0, 0, 0, 3, 15, 3, 15, 0, 0, 1, 0, 1, 1, 0, 0, - 0, 116, 41, 112, 41, 112, 41, 41, - 0, 41, 41, 41, 41, 41, 41, 41, - 41, 41, 41, 41, 41, 41, 41, 41, - 41, 41, 0, 0, 0, 9, 47, 47, - 47, 0, 1, 1, 1, 0, 0, 0, - 84, 29, 80, 29, 80, 29, 29, 0, - 29, 29, 29, 29, 29, 29, 29, 29, - 29, 29, 29, 29, 29, 29, 29, 29, - 29, 0, 0, 0, 53, 13, 13, 13, + 0, 3, 15, 3, 15, 0, 0, 1, + 0, 1, 1, 0, 0, 0, 3, 15, + 3, 15, 0, 0, 1, 0, 1, 1, + 0, 0, 0, 0, 0, 9, 51, 51, + 51, 0, 1, 1, 1, 0, 0, 0, + 3, 15, 3, 15, 0, 0, 1, 0, + 1, 1, 0, 0, 0, 0, 0, 9, + 51, 51, 51, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 11, 54, 54, 11, + 54, 54, 54, 11, 54, 54, 54, 11, + 54, 54, 54, 11, 54, 54, 54, 0, + 0, 11, 54, 54, 54, 11, 54, 54, + 54, 11, 54, 54, 54, 11, 54, 54, + 54, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 5, + 45, 5, 45, 45, 5, 5, 5, 5, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 18, 57, 18, 57, + 18, 18, 0, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 21, 61, + 21, 61, 21, 21, 0, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 21, 21, + 21, 21, 21, 21, 21, 21, 42, 42, + 89, 42, 42, 0, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 42, 42, 42, + 42, 42, 42, 42, 42, 0, 30, 73, + 30, 73, 30, 30, 0, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 30, 30, + 30, 30, 30, 30, 30, 30, 0, 24, + 65, 24, 65, 24, 24, 0, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 0, + 36, 81, 36, 81, 36, 36, 0, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 36, 36, 36, 36, 36, 36, 36, 36, + 0, 33, 77, 33, 77, 33, 33, 0, + 33, 33, 33, 33, 33, 33, 33, 33, + 33, 33, 33, 33, 33, 33, 33, 33, + 33, 0, 39, 85, 39, 85, 39, 39, + 0, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 0, 27, 69, 27, 69, 27, + 27, 0, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 27, 27, 27, 27, 27, + 27, 27, 27, 0, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 13, 0, 11, 50, 50, 11, 50, 50, - 50, 11, 50, 50, 50, 11, 50, 50, - 50, 11, 50, 50, 50, 0, 0, 11, - 50, 50, 50, 11, 50, 50, 50, 11, - 50, 50, 50, 11, 50, 50, 50, 0, + 0 +}; + +static const char _svg_path_eof_actions[] = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 5, 5, 5, - 5, 0, 0, 0, 0, 0, 0, 0, - 0, 0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 18, 0, 21, 21, 42, 0, 30, + 0, 30, 30, 24, 0, 24, 24, 36, + 0, 36, 36, 33, 0, 33, 33, 39, + 0, 39, 39, 27, 0, 27, 27, 13, + 0, 18, 18, 42, 42, 21 }; -static const int svg_path_start = 1; -static const int svg_path_first_final = 270; +static const int svg_path_start = 232; +static const int svg_path_first_final = 232; + +static const int svg_path_en_main = 232; -//static const int svg_path_en_main = 1; +#line 47 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" -void Parser::parse(char const *str) -throw(SVGPathParseError) + +SVGPathParser::SVGPathParser(PathSink &sink) + : _absolute(false) + , _sink(sink) { - char const *p = str; - char const *start = NULL; - int cs; + reset(); +} - _reset(); +void SVGPathParser::reset() { + _absolute = false; + _current = _initial = Point(0, 0); + _quad_tangent = _cubic_tangent = Point(0, 0); + _params.clear(); + +#line 1096 "D:/lib2geom/trunk/src/2geom/svg-path-parser.cpp" { cs = svg_path_start; } +#line 64 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + +} + +void SVGPathParser::parse(char const *str, int len) +{ + if (len < 0) { + len = std::strlen(str); + } + _parse(str, str + len, true); +} + +void SVGPathParser::parse(std::string const &s) +{ + _parse(s.c_str(), s.c_str() + s.size(), true); +} + +void SVGPathParser::feed(char const *str, int len) +{ + if (len < 0) { + len = std::strlen(str); + } + _parse(str, str + len, false); +} + +void SVGPathParser::feed(std::string const &s) +{ + _parse(s.c_str(), s.c_str() + s.size(), false); +} +void SVGPathParser::finish() +{ + char const *empty = ""; + _parse(empty, empty, true); +} + +void SVGPathParser::_push(Coord value) { + _params.push_back(value); +} + +Coord SVGPathParser::_pop() { + Coord value = _params.back(); + _params.pop_back(); + return value; +} + +bool SVGPathParser::_pop_flag() { + return _pop() != 0.0; +} + +Coord SVGPathParser::_pop_coord(Dim2 axis) { + if (_absolute) { + return _pop(); + } else { + return _pop() + _current[axis]; + } +} + +Point SVGPathParser::_pop_point() { + Coord y = _pop_coord(Y); + Coord x = _pop_coord(X); + return Point(x, y); +} + +void SVGPathParser::_moveTo(Point const &p) { + _sink.moveTo(p); + _quad_tangent = _cubic_tangent = _current = _initial = p; +} + +void SVGPathParser::_lineTo(Point const &p) { + _sink.lineTo(p); + _quad_tangent = _cubic_tangent = _current = p; +} + +void SVGPathParser::_curveTo(Point const &c0, Point const &c1, Point const &p) { + _sink.curveTo(c0, c1, p); + _quad_tangent = _current = p; + _cubic_tangent = p + ( p - c1 ); +} + +void SVGPathParser::_quadTo(Point const &c, Point const &p) { + _sink.quadTo(c, p); + _cubic_tangent = _current = p; + _quad_tangent = p + ( p - c ); +} + +void SVGPathParser::_arcTo(Coord rx, Coord ry, Coord angle, + bool large_arc, bool sweep, Point const &p) +{ + if (are_near(_current, p)) { + return; // ignore invalid (ambiguous) arc segments where start and end point are the same (per SVG spec) + } + + _sink.arcTo(rx, ry, angle, large_arc, sweep, p); + _quad_tangent = _cubic_tangent = _current = p; +} + +void SVGPathParser::_closePath() { + _sink.closePath(); + _quad_tangent = _cubic_tangent = _current = _initial; +} + +void SVGPathParser::_parse(char const *str, char const *strend, bool finish) +{ + char const *p = str; + char const *pe = strend; + char const *eof = finish ? pe : NULL; + char const *start = NULL; + + +#line 1211 "D:/lib2geom/trunk/src/2geom/svg-path-parser.cpp" { int _klen; unsigned int _trans; @@ -1168,6 +1215,8 @@ throw(SVGPathParseError) unsigned int _nacts; const char *_keys; + if ( p == pe ) + goto _test_eof; if ( cs == 0 ) goto _out; _resume: @@ -1233,63 +1282,75 @@ _match: switch ( *_acts++ ) { case 0: +#line 173 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { start = p; } break; case 1: - +#line 177 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { - char const *end=p; - std::string buf(start, end); - _push(g_ascii_strtod(buf.c_str(), NULL)); - start = NULL; + if (start) { + std::string buf(start, p); + _push(g_ascii_strtod(buf.c_str(), NULL)); + start = NULL; + } else { + std::string buf(str, p); + _push(g_ascii_strtod((_number_part + buf).c_str(), NULL)); + _number_part.clear(); + } } break; case 2: - +#line 189 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { _push(1.0); } break; case 3: - +#line 193 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { _push(0.0); } break; case 4: - +#line 197 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { _absolute = true; } break; case 5: +#line 201 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { _absolute = false; } break; case 6: +#line 205 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { _moveTo(_pop_point()); } break; case 7: +#line 209 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { _lineTo(_pop_point()); } break; case 8: +#line 213 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { - _hlineTo(Point(_pop_coord(X), _current[Y])); + _lineTo(Point(_pop_coord(X), _current[Y])); } break; case 9: +#line 217 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { - _vlineTo(Point(_current[X], _pop_coord(Y))); + _lineTo(Point(_current[X], _pop_coord(Y))); } break; case 10: +#line 221 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { Point p = _pop_point(); Point c1 = _pop_point(); @@ -1298,6 +1359,7 @@ _match: } break; case 11: +#line 228 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { Point p = _pop_point(); Point c1 = _pop_point(); @@ -1305,6 +1367,7 @@ _match: } break; case 12: +#line 234 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { Point p = _pop_point(); Point c = _pop_point(); @@ -1312,12 +1375,14 @@ _match: } break; case 13: +#line 240 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { Point p = _pop_point(); _quadTo(_quad_tangent, p); } break; case 14: +#line 245 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { Point point = _pop_point(); bool sweep = _pop_flag(); @@ -1330,42 +1395,167 @@ _match: } break; case 15: +#line 256 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" { _closePath(); } break; - case 16: - {{p++; goto _out; }} - break; +#line 1404 "D:/lib2geom/trunk/src/2geom/svg-path-parser.cpp" } } _again: if ( cs == 0 ) goto _out; - p += 1; - goto _resume; + if ( ++p != pe ) + goto _resume; + _test_eof: {} + if ( p == eof ) + { + const char *__acts = _svg_path_actions + _svg_path_eof_actions[cs]; + unsigned int __nacts = (unsigned int) *__acts++; + while ( __nacts-- > 0 ) { + switch ( *__acts++ ) { + case 1: +#line 177 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + { + if (start) { + std::string buf(start, p); + _push(g_ascii_strtod(buf.c_str(), NULL)); + start = NULL; + } else { + std::string buf(str, p); + _push(g_ascii_strtod((_number_part + buf).c_str(), NULL)); + _number_part.clear(); + } + } + break; + case 6: +#line 205 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + { + _moveTo(_pop_point()); + } + break; + case 7: +#line 209 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + { + _lineTo(_pop_point()); + } + break; + case 8: +#line 213 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + { + _lineTo(Point(_pop_coord(X), _current[Y])); + } + break; + case 9: +#line 217 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + { + _lineTo(Point(_current[X], _pop_coord(Y))); + } + break; + case 10: +#line 221 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + Point c1 = _pop_point(); + Point c0 = _pop_point(); + _curveTo(c0, c1, p); + } + break; + case 11: +#line 228 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + Point c1 = _pop_point(); + _curveTo(_cubic_tangent, c1, p); + } + break; + case 12: +#line 234 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + Point c = _pop_point(); + _quadTo(c, p); + } + break; + case 13: +#line 240 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + _quadTo(_quad_tangent, p); + } + break; + case 14: +#line 245 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + { + Point point = _pop_point(); + bool sweep = _pop_flag(); + bool large_arc = _pop_flag(); + double angle = deg_to_rad(_pop()); + double ry = _pop(); + double rx = _pop(); + + _arcTo(rx, ry, angle, large_arc, sweep, point); + } + break; + case 15: +#line 256 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" + { + _closePath(); + } + break; +#line 1509 "D:/lib2geom/trunk/src/2geom/svg-path-parser.cpp" + } + } + } + _out: {} } +#line 398 "D:/lib2geom/trunk/src/2geom/svg-path-parser.rl" - if ( cs < svg_path_first_final ) { - throw SVGPathParseError(); + + if (finish) { + if (cs < svg_path_first_final) { + throw SVGPathParseError(); + } + } else if (start != NULL) { + _number_part = std::string(start, pe); } -} + if (finish) { + _sink.flush(); + reset(); + } } void parse_svg_path(char const *str, PathSink &sink) -throw(SVGPathParseError) { - Parser parser(sink); + SVGPathParser parser(sink); parser.parse(str); - sink.flush(); } +void parse_svg_path_file(FILE *fi, PathSink &sink) +{ + static const size_t BUFFER_SIZE = 4096; + char buffer[BUFFER_SIZE]; + size_t bytes_read; + SVGPathParser parser(sink); + + while (true) { + bytes_read = fread(buffer, 1, BUFFER_SIZE, fi); + if (bytes_read < BUFFER_SIZE) { + parser.parse(buffer, bytes_read); + break; + } else { + parser.feed(buffer, bytes_read); + } + } } +} // namespace Geom + /* Local Variables: mode:c++ diff --git a/src/2geom/svg-path-parser.h b/src/2geom/svg-path-parser.h index 163fbe5c4..6478ba468 100644 --- a/src/2geom/svg-path-parser.h +++ b/src/2geom/svg-path-parser.h @@ -30,48 +30,143 @@ * */ -#ifndef SEEN_SVG_PATH_PARSER_H -#define SEEN_SVG_PATH_PARSER_H +#ifndef LIB2GEOM_SEEN_SVG_PATH_PARSER_H +#define LIB2GEOM_SEEN_SVG_PATH_PARSER_H -#include <vector> +#include <iostream> #include <iterator> #include <stdexcept> +#include <vector> #include <2geom/exception.h> #include <2geom/point.h> #include <2geom/path-sink.h> namespace Geom { -void parse_svg_path(char const *str, PathSink &sink) throw(SVGPathParseError); +/** @brief Read SVG path data and feed it to a PathSink + * + * This class provides an interface to an SVG path data parser written in Ragel. + * It supports parsing the path data either at once or block-by-block. + * Use the parse() functions to parse complete data and the feed() and finish() + * functions to parse partial data. + * + * The parser will call the appropriate methods on the PathSink supplied + * at construction. To store the path in memory as a PathVector, pass + * a PathBuilder. You can also use one of the freestanding helper functions + * if you don't need to parse data block-by-block. + * + * @ingroup Paths + */ +class SVGPathParser { +public: + SVGPathParser(PathSink &sink); + + /** @brief Reset internal state. + * Discards the internal state associated with partially parsed data, + * letting you start from scratch. Note that any partial data written + * to the path sink is not affected - you need to clear it yourself. */ + void reset(); + + /** @brief Parse a C-style string. + * The path sink is flushed and the internal state is reset after this call. + * Note that the state is not reset before this method, so you can use it to + * process the last block of partial data. + * @param str String to parse + * @param len Length of string or -1 if null-terminated */ + void parse(char const *str, int len = -1); + /** @brief Parse an STL string. */ + void parse(std::string const &s); + + /** @brief Parse a part of path data stored in a C-style string. + * This method does not reset internal state, so it can be called multiple + * times to parse successive blocks of a longer SVG path data string. + * To finish parsing, call finish() after the final block or call parse() + * with the last block of data. + * @param str String to parse + * @param len Length of string or -1 if null-terminated */ + void feed(char const *str, int len = -1); + /** @brief Parse a part of path data stored in an STL string. */ + void feed(std::string const &s); -inline std::vector<Path> parse_svg_path(char const *str) throw(SVGPathParseError) { - typedef std::vector<Path> Subpaths; - typedef std::back_insert_iterator<Subpaths> Inserter; - - Subpaths subpaths; - Inserter iter(subpaths); - PathIteratorSink<Inserter> generator(iter); + /** @brief Finalize parsing. + * After the last block of data was submitted with feed(), call this method + * to finalize parsing, flush the path sink and reset internal state. + * You should not call this after parse(). */ + void finish(); + +private: + bool _absolute; + Point _current; + Point _initial; + Point _cubic_tangent; + Point _quad_tangent; + std::vector<Coord> _params; + PathSink &_sink; + + int cs; + std::string _number_part; + + void _push(Coord value); + Coord _pop(); + bool _pop_flag(); + Coord _pop_coord(Geom::Dim2 axis); + Point _pop_point(); + void _moveTo(Point const &p); + void _lineTo(Point const &p); + void _curveTo(Point const &c0, Point const &c1, Point const &p); + void _quadTo(Point const &c, Point const &p); + void _arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Point const &p); + void _closePath(); + + void _parse(char const *str, char const *strend, bool finish); +}; + +/** @brief Feed SVG path data to the specified sink + * @ingroup Paths */ +void parse_svg_path(char const *str, PathSink &sink); +/** @brief Feed SVG path data to the specified sink + * @ingroup Paths */ +inline void parse_svg_path(std::string const &str, PathSink &sink) { + parse_svg_path(str.c_str(), sink); +} +/** Feed SVG path data from a C stream to the specified sink + * @ingroup Paths */ +void parse_svg_path_file(FILE *fi, PathSink &sink); + +/** @brief Create path vector from SVG path data stored in a C string + * @ingroup Paths */ +inline PathVector parse_svg_path(char const *str) { + PathVector ret; + SubpathInserter iter(ret); + PathIteratorSink<SubpathInserter> generator(iter); parse_svg_path(str, generator); - return subpaths; + return ret; } -inline std::vector<Path> read_svgd_f(FILE * fi) throw(SVGPathParseError) { - /// @bug The 10kB length limit should be removed - char input[1024 * 10]; - fgets(input, 1024 * 10, fi); - return parse_svg_path(input); +/** @brief Create path vector from a C stream with SVG path data + * @ingroup Paths */ +inline PathVector read_svgd_f(FILE * fi) { + PathVector ret; + SubpathInserter iter(ret); + PathIteratorSink<SubpathInserter> generator(iter); + + parse_svg_path_file(fi, generator); + return ret; } -inline std::vector<Path> read_svgd(char const * name) throw(SVGPathParseError) { - FILE* fi = fopen(name, "r"); +/** @brief Create path vector from SVG path data stored in a file + * @ingroup Paths */ +inline PathVector read_svgd(char const *filename) { + FILE* fi = fopen(filename, "r"); if(fi == NULL) throw(std::runtime_error("Error opening file")); - std::vector<Path> out = read_svgd_f(fi); + PathVector out = read_svgd_f(fi); fclose(fi); return out; } -} +} // end namespace Geom #endif /* diff --git a/src/2geom/svg-path-writer.cpp b/src/2geom/svg-path-writer.cpp new file mode 100644 index 000000000..e8083a8d1 --- /dev/null +++ b/src/2geom/svg-path-writer.cpp @@ -0,0 +1,296 @@ +/** @file + * @brief Path sink which writes an SVG-compatible command string + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2014 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <cmath> +#include <iomanip> +#include <2geom/coord.h> +#include <2geom/svg-path-writer.h> +#include <glib.h> + +namespace Geom { + +static inline bool is_digit(char c) { + return c >= '0' && c <= '9'; +} + +SVGPathWriter::SVGPathWriter() + : _epsilon(0) + , _precision(-1) + , _optimize(false) + , _use_shorthands(true) + , _command(0) +{ + // always use C locale for number formatting + _ns.imbue(std::locale::classic()); + _ns.unsetf(std::ios::floatfield); +} + +void SVGPathWriter::moveTo(Point const &p) +{ + _setCommand('M'); + _current_pars.push_back(p[X]); + _current_pars.push_back(p[Y]); + + _current = _subpath_start = _quad_tangent = _cubic_tangent = p; + if (!_optimize) { + flush(); + } +} + +void SVGPathWriter::lineTo(Point const &p) +{ + // The weird setting of _current is to avoid drift with many almost-aligned segments + // The additional conditions ensure that the smaller dimension is rounded to zero + bool written = false; + if (_use_shorthands) { + Point r = _current - p; + if (are_near(p[X], _current[X], _epsilon) && std::abs(r[X]) < std::abs(r[Y])) { + // emit vlineto + _setCommand('V'); + _current_pars.push_back(p[Y]); + _current[Y] = p[Y]; + written = true; + } else if (are_near(p[Y], _current[Y], _epsilon) && std::abs(r[Y]) < std::abs(r[X])) { + // emit hlineto + _setCommand('H'); + _current_pars.push_back(p[X]); + _current[X] = p[X]; + written = true; + } + } + + if (!written) { + // emit normal lineto + if (_command != 'M' && _command != 'L') { + _setCommand('L'); + } + _current_pars.push_back(p[X]); + _current_pars.push_back(p[Y]); + _current = p; + } + + _cubic_tangent = _quad_tangent = _current; + if (!_optimize) { + flush(); + } +} + +void SVGPathWriter::quadTo(Point const &c, Point const &p) +{ + bool shorthand = _use_shorthands && are_near(c, _quad_tangent, _epsilon); + + _setCommand(shorthand ? 'T' : 'Q'); + if (!shorthand) { + _current_pars.push_back(c[X]); + _current_pars.push_back(c[Y]); + } + _current_pars.push_back(p[X]); + _current_pars.push_back(p[Y]); + + _current = _cubic_tangent = p; + _quad_tangent = p + (p - c); + if (!_optimize) { + flush(); + } +} + +void SVGPathWriter::curveTo(Point const &p1, Point const &p2, Point const &p3) +{ + bool shorthand = _use_shorthands && are_near(p1, _cubic_tangent, _epsilon); + + _setCommand(shorthand ? 'S' : 'C'); + if (!shorthand) { + _current_pars.push_back(p1[X]); + _current_pars.push_back(p1[Y]); + } + _current_pars.push_back(p2[X]); + _current_pars.push_back(p2[Y]); + _current_pars.push_back(p3[X]); + _current_pars.push_back(p3[Y]); + + _current = _quad_tangent = p3; + _cubic_tangent = p3 + (p3 - p2); + if (!_optimize) { + flush(); + } +} + +void SVGPathWriter::arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Point const &p) +{ + _setCommand('A'); + _current_pars.push_back(rx); + _current_pars.push_back(ry); + _current_pars.push_back(angle); + _current_pars.push_back(large_arc ? 1. : 0.); + _current_pars.push_back(sweep ? 1. : 0.); + _current_pars.push_back(p[X]); + _current_pars.push_back(p[Y]); + + _current = _quad_tangent = _cubic_tangent = p; + if (!_optimize) { + flush(); + } +} + +void SVGPathWriter::closePath() +{ + flush(); + if (_optimize) { + _s << "z"; + } else { + _s << " z"; + } + _current = _quad_tangent = _cubic_tangent = _subpath_start; +} + +void SVGPathWriter::flush() +{ + if (_command == 0 || _current_pars.empty()) return; + + if (_optimize) { + _s << _command; + } else { + if (_s.tellp() != 0) { + _s << ' '; + } + _s << _command; + } + + char lastchar = _command; + bool contained_dot = false; + + for (unsigned i = 0; i < _current_pars.size(); ++i) { + // TODO: optimize the use of absolute / relative coords + std::string cs = _formatCoord(_current_pars[i]); + + // Separator handling logic. + // Floating point values can end with a digit or dot + // and start with a digit, a plus or minus sign, or a dot. + // The following cases require a separator: + // * digit-digit + // * digit-dot (only if the previous number didn't contain a dot) + // * dot-digit + if (_optimize) { + // C++11: change to front() + char firstchar = cs[0]; + if (is_digit(lastchar)) { + if (is_digit(firstchar)) { + _s << " "; + } else if (firstchar == '.' && !contained_dot) { + _s << " "; + } + } else if (lastchar == '.' && is_digit(firstchar)) { + _s << " "; + } + _s << cs; + + // C++11: change to back() + lastchar = cs[cs.length()-1]; + contained_dot = cs.find('.') != std::string::npos; + } else { + _s << " " << cs; + } + } + _current_pars.clear(); + _command = 0; +} + +void SVGPathWriter::clear() +{ + _s.clear(); + _s.str(""); + _ns.clear(); + _ns.str(""); + _command = 0; + _current_pars.clear(); + _current = Point(0,0); + _subpath_start = Point(0,0); +} + +void SVGPathWriter::setPrecision(int prec) +{ + _precision = prec; + if (prec < 0) { + _epsilon = 0; + } else { + _epsilon = std::pow(10., -prec); + _ns << std::setprecision(_precision); + } +} + +void SVGPathWriter::_setCommand(char cmd) +{ + if (_command != 0 && _command != cmd) { + flush(); + } + _command = cmd; +} + +std::string SVGPathWriter::_formatCoord(Coord par) +{ + std::string ret; + if (_precision < 0) { + ret = format_coord_shortest(par); + } else { + _ns << par; + ret = _ns.str(); + _ns.clear(); + _ns.str(""); + } + return ret; +} + + +std::string write_svg_path(PathVector const &pv, int prec, bool optimize, bool shorthands) +{ + SVGPathWriter writer; + writer.setPrecision(prec); + writer.setOptimize(optimize); + writer.setUseShorthands(shorthands); + + writer.feed(pv); + return writer.str(); +} + +} // namespace Geom + +/* + 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/2geom/svg-path-writer.h b/src/2geom/svg-path-writer.h new file mode 100644 index 000000000..e639541ab --- /dev/null +++ b/src/2geom/svg-path-writer.h @@ -0,0 +1,122 @@ +/** @file + * @brief Path sink which writes an SVG-compatible command string + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright 2014 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_SVG_PATH_WRITER_H +#define LIB2GEOM_SEEN_SVG_PATH_WRITER_H + +#include <2geom/path-sink.h> +#include <sstream> + +namespace Geom { + +/** @brief Serialize paths to SVG path data strings. + * You can access the generated string by calling the str() method. + * @ingroup Paths + */ +class SVGPathWriter + : public PathSink +{ +public: + SVGPathWriter(); + ~SVGPathWriter() {} + + void moveTo(Point const &p); + void lineTo(Point const &p); + void quadTo(Point const &c, Point const &p); + void curveTo(Point const &c0, Point const &c1, Point const &p); + void arcTo(double rx, double ry, double angle, + bool large_arc, bool sweep, Point const &p); + void closePath(); + void flush(); + + /// Clear any path data written so far. + void clear(); + + /** @brief Set output precision. + * When the parameter is negative, the path writer enters a verbatim mode + * which preserves all values exactly. */ + void setPrecision(int prec); + + /** @brief Enable or disable length optimization. + * + * When set to true, the path writer will optimize the generated path data + * for minimum length. However, this will make the data less readable, + * because spaces between commands and coordinates will be omitted where + * unnecessary for correct parsing. + * + * When set to false, the string will be a straightforward, partially redundant + * representation of the passed commands, optimized for readability. + * Commands and coordinates will always be separated by spaces and the command + * symbol will not be omitted for multiple consecutive commands of the same type. + * + * Length optimization is turned off by default. */ + void setOptimize(bool opt) { _optimize = opt; } + + /** @brief Enable or disable the use of V, H, T and S commands where possible. + * Shorthands are turned on by default. */ + void setUseShorthands(bool use) { _use_shorthands = use; } + + /// Retrieve the generated path data string. + std::string str() const { return _s.str(); } + +private: + void _setCommand(char cmd); + std::string _formatCoord(Coord par); + + std::ostringstream _s, _ns; + std::vector<Coord> _current_pars; + Point _subpath_start; + Point _current; + Point _quad_tangent; + Point _cubic_tangent; + Coord _epsilon; + int _precision; + bool _optimize; + bool _use_shorthands; + char _command; +}; + +std::string write_svg_path(PathVector const &pv, int prec = -1, bool optimize = false, bool shorthands = true); + +} // namespace Geom + +#endif // LIB2GEOM_SEEN_SVG_PATH_WRITER_H +/* + 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/2geom/sweep.cpp b/src/2geom/sweep.cpp index f25894282..6910d5d02 100644 --- a/src/2geom/sweep.cpp +++ b/src/2geom/sweep.cpp @@ -19,8 +19,8 @@ std::vector<std::vector<unsigned> > sweep_bounds(std::vector<Rect> rs, Dim2 d) { std::vector<std::vector<unsigned> > pairs(rs.size()); for(unsigned i = 0; i < rs.size(); i++) { - events.push_back(Event(rs[i][d][0], i, false)); - events.push_back(Event(rs[i][d][1], i, true)); + events.push_back(Event(rs[i][d].min(), i, false)); + events.push_back(Event(rs[i][d].max(), i, true)); } std::sort(events.begin(), events.end()); @@ -66,8 +66,8 @@ std::vector<std::vector<unsigned> > sweep_bounds(std::vector<Rect> a, std::vecto events[n].reserve(sz*2); for(unsigned i = 0; i < sz; i++) { Rect r = n ? b[i] : a[i]; - events[n].push_back(Event(r[d][0], i, false)); - events[n].push_back(Event(r[d][1], i, true)); + events[n].push_back(Event(r[d].min(), i, false)); + events[n].push_back(Event(r[d].max(), i, true)); } std::sort(events[n].begin(), events[n].end()); } diff --git a/src/2geom/sweep.h b/src/2geom/sweep.h index 91371e6fb..9c012958b 100644 --- a/src/2geom/sweep.h +++ b/src/2geom/sweep.h @@ -32,8 +32,8 @@ * */ -#ifndef __2GEOM_SWEEP_H__ -#define __2GEOM_SWEEP_H__ +#ifndef LIB2GEOM_SEEN_SWEEP_H +#define LIB2GEOM_SEEN_SWEEP_H #include <vector> #include <2geom/d2.h> diff --git a/src/2geom/toposweep.cpp b/src/2geom/toposweep.cpp index 4da3f6922..3cb231142 100644 --- a/src/2geom/toposweep.cpp +++ b/src/2geom/toposweep.cpp @@ -189,7 +189,7 @@ bool SectionSorter::operator()(Section const &a, Section const &b) const { } } - return Point::LexOrderRt(dim)(a.fp, b.fp); + return Point::LexLessRt(dim)(a.fp, b.fp); } // splits a section into pieces, as specified by an array of doubles, mutating the section to @@ -203,7 +203,7 @@ std::vector<boost::shared_ptr<Section> > split_section(boost::shared_ptr<Section s->t = cuts[1]; s->tp = s->curve.get(ps)(cuts[1]); - assert(Point::LexOrderRt(d)(s->fp, s->tp)); + assert(Point::LexLessRt(d)(s->fp, s->tp)); ret.reserve(cuts.size() - 2); for(int i = cuts.size() - 1; i > 1; i--) ret.push_back(boost::shared_ptr<Section>(new Section(s->curve, cuts[i-1], cuts[i], ps, d))); @@ -380,7 +380,7 @@ TopoGraph::TopoGraph(PathVector const &ps, Dim2 d, double t) : dim(d), tol(t) { //find all sections to remove for(int i = context.size() - 1; i >= 0; i--) { boost::shared_ptr<Section> sec = context[i].section; - if(Point::LexOrderRt(d)(lim, sec->tp)) { + if(Point::LexLessRt(d)(lim, sec->tp)) { //sec->tp is less than or equal to lim if(context[i].to_vert == -1) { //we need to create a new vertex; add everything that enters it @@ -639,6 +639,7 @@ void remove_area_whiskers(Areas &areas) { Path area_to_path(PathVector const &ps, Area const &area) { Path ret; + ret.setStitching(true); if(area.size() == 0) return ret; Point prev = area[0]->fp; for(unsigned i = 0; i < area.size(); i++) { @@ -646,17 +647,18 @@ Path area_to_path(PathVector const &ps, Area const &area) { Curve *curv = area[i]->curve.get(ps).portion( forward ? area[i]->f : area[i]->t, forward ? area[i]->t : area[i]->f); - ret.append(*curv, Path::STITCH_DISCONTINUOUS); + ret.append(*curv); delete curv; prev = forward ? area[i]->tp : area[i]->fp; } + ret.setStitching(false); return ret; } PathVector areas_to_paths(PathVector const &ps, Areas const &areas) { - std::vector<Path> ret; - ret.reserve(areas.size()); - for(unsigned i = 0; i < areas.size(); i++) + PathVector ret; + //ret.reserve(areas.size()); + for(unsigned i = 0; i < areas.size(); ++i) ret.push_back(area_to_path(ps, areas[i])); return ret; } diff --git a/src/2geom/toposweep.h b/src/2geom/toposweep.h index b6a55b154..7faf890e1 100644 --- a/src/2geom/toposweep.h +++ b/src/2geom/toposweep.h @@ -1,8 +1,7 @@ - /** * \file * \brief TopoSweep - topology / graph representation of a PathVector, for boolean operations and related tasks - * + *//* * Authors: * Michael Sloan <mgsloan at gmail.com> * Nathan Hurst <njhurst at njhurst.com> @@ -33,8 +32,8 @@ * the specific language governing rights and limitations. */ -#ifndef SEEN_GEOM_TOPOSWEEP_H -#define SEEN_GEOM_TOPOSWEEP_H +#ifndef LIB2GEOM_SEEN_TOPOSWEEP_H +#define LIB2GEOM_SEEN_TOPOSWEEP_H #include <2geom/coord.h> #include <2geom/point.h> @@ -69,10 +68,11 @@ struct Section { Section(CurveIx cix, double fd, double td, Point fdp, Point tdp) : curve(cix), f(fd), t(td), fp(fdp), tp(tdp) { } Section(CurveIx cix, double fd, double td, PathVector ps, Dim2 d) : curve(cix), f(fd), t(td) { fp = curve.get(ps).pointAt(f), tp = curve.get(ps).pointAt(t); - if (Point::LexOrderRt(d)(tp, fp)) { + if (Point::LexLessRt(d)(tp, fp)) { //swap from and to, since tp is left or above fp - std::swap(f, t); - std::swap(fp, tp); + using std::swap; + swap(f, t); + swap(fp, tp); } } Rect bbox() const { return Rect(fp, tp); } @@ -167,7 +167,7 @@ struct SweepSorter { Dim2 dim; SweepSorter(Dim2 d) : dim(d) {} bool operator()(const Section &a, const Section &b) const { - return Point::LexOrderRt(dim)(a.fp, b.fp); + return Point::LexLessRt(dim)(a.fp, b.fp); } }; @@ -208,7 +208,7 @@ Areas filter_areas(PathVector const &ps, Areas const & areas, Z const &z) { } // end namespace Geom -#endif // SEEN_GEOM_TOPOSWEEP_H +#endif // LIB2GEOM_SEEN_TOPOSWEEP_H /* Local Variables: diff --git a/src/2geom/transforms.h b/src/2geom/transforms.h index 7f5635747..2e805786d 100644 --- a/src/2geom/transforms.h +++ b/src/2geom/transforms.h @@ -199,6 +199,7 @@ public: /** @brief Get the characteristic vector of the rotation. * @return A vector that would be obtained by applying this transform to the X versor. */ Point vector() const { return vec; } + Coord angle() const { return atan2(vec); } Coord operator[](Dim2 dim) const { return vec[dim]; } Coord operator[](unsigned dim) const { return vec[dim]; } Rotate &operator*=(Rotate const &o) { vec *= o; return *this; } @@ -343,6 +344,8 @@ inline Translate pow(Translate const &t, int n) { /** @brief Reflects objects about line. * The line, defined by a vector along the line and a point on it, acts as a mirror. + * @ingroup Transforms + * @see Line::reflection() */ Affine reflection(Point const & vector, Point const & origin); diff --git a/src/2geom/utils.h b/src/2geom/utils.h index fe955dd41..bc0ad74b8 100644 --- a/src/2geom/utils.h +++ b/src/2geom/utils.h @@ -30,11 +30,12 @@ * */ -#ifndef SEEN_LIB2GEOM_UTILS_H -#define SEEN_LIB2GEOM_UTILS_H +#ifndef LIB2GEOM_SEEN_UTILS_H +#define LIB2GEOM_SEEN_UTILS_H #include <cstddef> #include <vector> +#include <boost/operators.hpp> namespace Geom { @@ -59,9 +60,20 @@ struct MultipliableNoncommutative : B } }; +/** @brief Null output iterator + * Use this if you want to discard a result returned through an output iterator. */ +struct NullIterator + : public boost::output_iterator_helper<NullIterator> +{ + NullIterator() {} + + template <typename T> + void operator=(T const &v) {} +}; + } // end namespace Geom -#endif // SEEN_LIB2GEOM_UTILS_H +#endif // LIB2GEOM_SEEN_UTILS_H /* Local Variables: diff --git a/src/2geom/viewbox.cpp b/src/2geom/viewbox.cpp new file mode 100644 index 000000000..69bd0c487 --- /dev/null +++ b/src/2geom/viewbox.cpp @@ -0,0 +1,133 @@ +/** + * \file + * \brief Convenience class for SVG viewBox handling + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright (C) 2013 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#include <2geom/transforms.h> +#include <2geom/viewbox.h> + +namespace Geom { + +/** Convert an align specification to coordinate fractions. */ +Point align_factors(Align g) { + Point p; + switch (g) { + case ALIGN_XMIN_YMIN: + p[X] = 0.0; + p[Y] = 0.0; + break; + case ALIGN_XMID_YMIN: + p[X] = 0.5; + p[Y] = 0.0; + break; + case ALIGN_XMAX_YMIN: + p[X] = 1.0; + p[Y] = 0.0; + break; + case ALIGN_XMIN_YMID: + p[X] = 0.0; + p[Y] = 0.5; + break; + case ALIGN_XMID_YMID: + p[X] = 0.5; + p[Y] = 0.5; + break; + case ALIGN_XMAX_YMID: + p[X] = 1.0; + p[Y] = 0.5; + break; + case ALIGN_XMIN_YMAX: + p[X] = 0.0; + p[Y] = 1.0; + break; + case ALIGN_XMID_YMAX: + p[X] = 0.5; + p[Y] = 1.0; + break; + case ALIGN_XMAX_YMAX: + p[X] = 1.0; + p[Y] = 1.0; + break; + default: + break; + } + return p; +} + +/** Obtain transformation from the viewbox to the specified viewport. */ +Affine ViewBox::transformTo(Geom::Rect const &viewport) const +{ + if (!_box) { + return Geom::Affine::identity(); + } + + // 1. translate viewbox to origin + Geom::Affine total = Translate(-_box->min()); + + // 2. compute scale + Geom::Point vdims = viewport.dimensions(); + Geom::Point bdims = _box->dimensions(); + Geom::Scale scale(vdims[X] / bdims[X], vdims[Y] / bdims[Y]); + + if (_align == ALIGN_NONE) { + // apply non-uniform scale + // = Scale(_box->dimensions()).inverse() * Scale(viewport.dimensions()) + total *= scale * Translate(viewport.min()); + } else { + double uscale = 0; + if (_expansion == EXPANSION_MEET) { + uscale = std::min(scale[X], scale[Y]); + } else { + uscale = std::max(scale[X], scale[Y]); + } + scale = Scale(uscale); + + // compute offset for align + Geom::Point offset = bdims * scale - vdims; + offset *= Scale(align_factors(_align)); + total *= Translate(-offset); + } + + return total; +} + +} // namespace Geom + +/* + 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/2geom/viewbox.h b/src/2geom/viewbox.h new file mode 100644 index 000000000..81f59ee36 --- /dev/null +++ b/src/2geom/viewbox.h @@ -0,0 +1,102 @@ +/** + * \file + * \brief Convenience class for SVG viewBox handling + *//* + * Authors: + * Krzysztof Kosiński <tweenk.pl@gmail.com> + * + * Copyright (C) 2013 Authors + * + * This library is free software; you can redistribute it and/or + * modify it either under the terms of the GNU Lesser General Public + * License version 2.1 as published by the Free Software Foundation + * (the "LGPL") or, at your option, under the terms of the Mozilla + * Public License Version 1.1 (the "MPL"). If you do not alter this + * notice, a recipient may use your version of this file under either + * the MPL or the LGPL. + * + * You should have received a copy of the LGPL along with this library + * in the file COPYING-LGPL-2.1; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * You should have received a copy of the MPL along with this library + * in the file COPYING-MPL-1.1 + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY + * OF ANY KIND, either express or implied. See the LGPL or the MPL for + * the specific language governing rights and limitations. + */ + +#ifndef LIB2GEOM_SEEN_VIEWBOX_H +#define LIB2GEOM_SEEN_VIEWBOX_H + +#include <2geom/affine.h> +#include <2geom/rect.h> + +namespace Geom { + +/** Values for the <align> parameter of preserveAspectRatio. + * See: http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute */ +enum Align { + ALIGN_NONE, + ALIGN_XMIN_YMIN, + ALIGN_XMID_YMIN, + ALIGN_XMAX_YMIN, + ALIGN_XMIN_YMID, + ALIGN_XMID_YMID, + ALIGN_XMAX_YMID, + ALIGN_XMIN_YMAX, + ALIGN_XMID_YMAX, + ALIGN_XMAX_YMAX +}; + +/** Values for the <meetOrSlice> parameter of preserveAspectRatio. + * See: http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute */ +enum Expansion { + EXPANSION_MEET, + EXPANSION_SLICE +}; + +Point align_factors(Align align); + +class ViewBox { + OptRect _box; + Align _align; + Expansion _expansion; + +public: + explicit ViewBox(OptRect const &r = OptRect(), Align a = ALIGN_XMID_YMID, Expansion ex = EXPANSION_MEET) + : _box(r) + , _align(a) + , _expansion(ex) + {} + + void setBox(OptRect const &r) { _box = r; } + void setAlign(Align a) { _align = a; } + void setExpansion(Expansion ex) { _expansion = ex; } + OptRect const &box() const { return _box; } + Align align() const { return _align; } + Expansion expansion() const { return _expansion; } + + /** Obtain transformation from the viewbox to the specified viewport. */ + Affine transformTo(Geom::Rect const &viewport) const; +}; + +} // namespace Geom + +#endif // !LIB2GEOM_SEEN_VIEWBOX_H + +/* + 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 : |
