diff options
| author | Jabier Arraiza Cenoz <jabier.arraiza@marker.es> | 2015-07-24 18:45:27 +0000 |
|---|---|---|
| committer | Jabiertxof <jtx@jtx.marker.es> | 2015-07-24 18:45:27 +0000 |
| commit | 50115a1e39ed4414dd9e684709253167072b29ee (patch) | |
| tree | a37ac591208a0f52e64ad7f5bddd45e773f138ee /src | |
| parent | fix minor bug (diff) | |
| parent | Fix a bug continuing a bezier path whith a LPE one like spiro or bspline on a... (diff) | |
| download | inkscape-50115a1e39ed4414dd9e684709253167072b29ee.tar.gz inkscape-50115a1e39ed4414dd9e684709253167072b29ee.zip | |
update to trunk
(bzr r13708.1.37)
Diffstat (limited to 'src')
308 files changed, 16329 insertions, 9574 deletions
diff --git a/src/2geom/!PLEASE DON'T MAKE CHANGES IN THESE FILES.README b/src/2geom/!PLEASE DON'T MAKE CHANGES IN THESE FILES.README index 3ced704c7..e94a549f6 100644 --- a/src/2geom/!PLEASE DON'T MAKE CHANGES IN THESE FILES.README +++ b/src/2geom/!PLEASE DON'T MAKE CHANGES IN THESE FILES.README @@ -1,9 +1,13 @@ -All code files in this directory are *direct* copies of the files in 2geom's BZR repo. -If you want to change the code, please change it in 2geom, then copy the files here. -Otherwise, I will probably miss that you changed something in Inkscape's copy, and -destroy your changes by copying 2geom's files over it during the next time I update -Inkscape's copy of 2geom. - - Johan Engelen +This is an in-tree copy of lib2geom, a 2D geometry library started +by Inkscape developers. If you want to change code in 2Geom, you should +commit it first to upstream repository and then execute this command: + +rsync -r --existing --exclude CMakeLists.txt /path/to/lib2geom/src/2geom/ /path/to/inkscape/src/2geom/ + +The command above will only update existing files. If you add new files +to 2Geom, you'll need to copy the new files manually. Same if you remove +some files. Note that the trailing slashes are required! 2geom's BZR repo = lp:lib2geom -http://lib2geom.sourceforge.net
\ No newline at end of file +http://lib2geom.sourceforge.net + 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..eb7abb614 100644 --- a/src/2geom/CMakeLists.txt +++ b/src/2geom/CMakeLists.txt @@ -2,23 +2,26 @@ set(2geom_SRC affine.cpp basic-intersection.cpp + bezier.cpp bezier-clipping.cpp bezier-curve.cpp bezier-utils.cpp - circle-circle.cpp + cairo-path-sink.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 + elliptical-arc-from-sbasis.cpp geom.cpp + intersection-graph.cpp line.cpp - nearest-point.cpp + nearest-time.cpp numeric/matrix.cpp path-intersection.cpp path-sink.cpp @@ -26,11 +29,9 @@ set(2geom_SRC pathvector.cpp piecewise.cpp point.cpp - poly.cpp - quadtree.cpp + polynomial.cpp rect.cpp # recursive-bezier-intersection.cpp - region.cpp sbasis-2d.cpp sbasis-geometric.cpp sbasis-math.cpp @@ -38,16 +39,16 @@ set(2geom_SRC sbasis-roots.cpp sbasis-to-bezier.cpp sbasis.cpp - shape.cpp solve-bezier.cpp solve-bezier-one-d.cpp solve-bezier-parametric.cpp - svg-elliptical-arc.cpp svg-path-parser.cpp - sweep.cpp + svg-path-writer.cpp + sweep-bounds.cpp toposweep.cpp transforms.cpp utils.cpp + viewbox.cpp # ------- @@ -62,14 +63,11 @@ set(2geom_SRC bezier.h choose.h circle.h - circulator.h concepts.h conic_section_clipper.h conic_section_clipper_cr.h conic_section_clipper_impl.h conicsec.h - conjugate_gradient.h - convex-cover.h coord.h crossing.h curve.h @@ -83,7 +81,6 @@ set(2geom_SRC generic-interval.h generic-rect.h geom.h - hvlinesegment.h int-interval.h int-point.h int-rect.h @@ -91,20 +88,16 @@ set(2geom_SRC line.h linear.h math-utils.h - nearest-point.h ord.h path-intersection.h path-sink.h path.h pathvector.h piecewise.h - point-ops.h point.h - poly.h - quadtree.h + polynomial.h ray.h rect.h - region.h sbasis-2d.h sbasis-curve.h sbasis-geometric.h @@ -112,11 +105,11 @@ set(2geom_SRC sbasis-poly.h sbasis-to-bezier.h sbasis.h - shape.h solver.h - svg-elliptical-arc.h svg-path-parser.h - sweep.h + svg-path-writer.h + sweeper.h + sweep-bounds.h toposweep.h transforms.h utils.h diff --git a/src/2geom/Makefile_insert b/src/2geom/Makefile_insert index e77f413cb..e3c6836fd 100644 --- a/src/2geom/Makefile_insert +++ b/src/2geom/Makefile_insert @@ -15,12 +15,14 @@ 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 \ 2geom/circle.h \ 2geom/circulator.h \ @@ -32,10 +34,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 \ @@ -49,13 +50,16 @@ 2geom/ellipse.h \ 2geom/elliptical-arc.cpp \ 2geom/elliptical-arc.h \ + 2geom/elliptical-arc-from-sbasis.cpp \ 2geom/exception.h \ 2geom/forward.h \ 2geom/generic-interval.h \ 2geom/generic-rect.h \ 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 +68,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 \ @@ -79,17 +83,12 @@ 2geom/piecewise.h \ 2geom/point.cpp \ 2geom/point.h \ - 2geom/point-ops.h \ - 2geom/poly.cpp \ - 2geom/poly.h \ - 2geom/quadtree.cpp \ - 2geom/quadtree.h \ + 2geom/polynomial.cpp \ + 2geom/polynomial.h \ 2geom/ray.h \ 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,24 +103,25 @@ 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 \ 2geom/solver.h \ - 2geom/svg-elliptical-arc.cpp \ - 2geom/svg-elliptical-arc.h \ 2geom/svg-path-parser.cpp \ 2geom/svg-path-parser.h \ - 2geom/sweep.cpp \ - 2geom/sweep.h \ + 2geom/svg-path-writer.cpp \ + 2geom/svg-path-writer.h \ + 2geom/sweep-bounds.cpp \ + 2geom/sweep-bounds.h \ + 2geom/sweeper.h \ 2geom/toposweep.cpp \ 2geom/toposweep.h \ 2geom/transforms.cpp \ 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..48179e864 100644 --- a/src/2geom/affine.cpp +++ b/src/2geom/affine.cpp @@ -6,9 +6,10 @@ * This code is in public domain */ -#include <2geom/utils.h> #include <2geom/affine.h> #include <2geom/point.h> +#include <2geom/polynomial.h> +#include <2geom/utils.h> namespace Geom { @@ -36,8 +37,7 @@ Point Affine::yAxis() const { return Point(_c[2], _c[3]); } -/** Gets the translation imparted by the Affine. - */ +/// Gets the translation imparted by the Affine. Point Affine::translation() const { return Point(_c[4], _c[5]); } @@ -52,8 +52,7 @@ void Affine::setYAxis(Point const &vec) { _c[i + 2] = vec[i]; } -/** Sets the translation imparted by the Affine. - */ +/// Sets the translation imparted by the Affine. void Affine::setTranslation(Point const &loc) { for(int i = 0; i < 2; i++) _c[i + 4] = loc[i]; @@ -61,33 +60,35 @@ void Affine::setTranslation(Point const &loc) { /** Calculates the amount of x-scaling imparted by the Affine. This is the scaling applied to * the original x-axis region. It is \emph{not} the overall x-scaling of the transformation. - * Equivalent to L2(m.xAxis()) - */ + * Equivalent to L2(m.xAxis()). */ double Affine::expansionX() const { return sqrt(_c[0] * _c[0] + _c[1] * _c[1]); } /** Calculates the amount of y-scaling imparted by the Affine. This is the scaling applied before * the other transformations. It is \emph{not} the overall y-scaling of the transformation. - * Equivalent to L2(m.yAxis()) - */ + * Equivalent to L2(m.yAxis()). */ double Affine::expansionY() const { return sqrt(_c[2] * _c[2] + _c[3] * _c[3]); } void Affine::setExpansionX(double val) { double exp_x = expansionX(); - if(!are_near(exp_x, 0.0)) { //TODO: best way to deal with it is to skip op? + if (exp_x != 0.0) { //TODO: best way to deal with it is to skip op? double coef = val / expansionX(); - for(unsigned i=0;i<2;i++) _c[i] *= coef; + for (unsigned i = 0; i < 2; ++i) { + _c[i] *= coef; + } } } void Affine::setExpansionY(double val) { double exp_y = expansionY(); - if(!are_near(exp_y, 0.0)) { //TODO: best way to deal with it is to skip op? + if (exp_y != 0.0) { //TODO: best way to deal with it is to skip op? double coef = val / expansionY(); - for(unsigned i=2; i<4; i++) _c[i] *= coef; + for (unsigned i = 2; i < 4; ++i) { + _c[i] *= coef; + } } } @@ -222,6 +223,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 +366,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 +379,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 +392,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; @@ -446,55 +469,38 @@ Affine elliptic_quadratic_form(Affine const &m) { Eigen::Eigen(Affine const &m) { double const B = -m[0] - m[3]; double const C = m[0]*m[3] - m[1]*m[2]; - double const center = -B/2.0; - double const delta = sqrt(B*B-4*C)/2.0; - values[0] = center + delta; values[1] = center - delta; - for (int i = 0; i < 2; i++) { - vectors[i] = unit_vector(rot90(Point(m[0]-values[i], m[1]))); - } -} -static void quadratic_roots(const double q0, const double q1, const double q2, int &n, double&r0, double&r1) { - if(q2 == 0) { - if(q1 == 0) { // zero or infinite roots - n = 0; - } else { - n = 1; - r0 = -q0/q1; - } - } else { - double desc = q1*q1 - 4*q2*q0; - if (desc < 0) - n = 0; - else if (desc == 0) { - n = 1; - r0 = -q1/(2*q2); - } else { - n = 2; - desc = std::sqrt(desc); - double t = -0.5*(q1+sgn(q1)*desc); - r0 = t/q2; - r1 = q0/t; - } + std::vector<double> v = solve_quadratic(1, B, C); + + for (unsigned i = 0; i < v.size(); ++i) { + values[i] = v[i]; + vectors[i] = unit_vector(rot90(Point(m[0] - values[i], m[1]))); + } + for (unsigned i = v.size(); i < 2; ++i) { + values[i] = 0; + vectors[i] = Point(0,0); } } Eigen::Eigen(double m[2][2]) { double const B = -m[0][0] - m[1][1]; double const C = m[0][0]*m[1][1] - m[1][0]*m[0][1]; - //double const desc = B*B-4*C; - //double t = -0.5*(B+sgn(B)*desc); - int n; - values[0] = values[1] = 0; - quadratic_roots(C, B, 1, n, values[0], values[1]); - for (int i = 0; i < n; i++) - vectors[i] = unit_vector(rot90(Point(m[0][0]-values[i], m[0][1]))); - for (int i = n; i < 2; i++) + + std::vector<double> v = solve_quadratic(1, B, C); + + for (unsigned i = 0; i < v.size(); ++i) { + values[i] = v[i]; + vectors[i] = unit_vector(rot90(Point(m[0][0] - values[i], m[0][1]))); + } + for (unsigned i = v.size(); i < 2; ++i) { + values[i] = 0; vectors[i] = Point(0,0); + } } -/** @brief Nearness predicate for affine transforms - * @returns True if all entries of matrices are within eps of each other */ +/** @brief Nearness predicate for affine transforms. + * @returns True if all entries of matrices are within eps of each other. + * @relates Affine */ bool are_near(Affine const &a, Affine const &b, Coord eps) { return are_near(a[0], b[0], eps) && are_near(a[1], b[1], eps) && 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..af4442e88 100644 --- a/src/2geom/angle.h +++ b/src/2geom/angle.h @@ -59,32 +59,48 @@ namespace Geom { * to <tt>double</tt> is in the range \f$[-\pi, \pi)\f$ - the convention used by C's * math library. * + * This class holds only a single floating point value, so passing it by value will generally + * be faster than passing it by const reference. + * * @ingroup Primitives */ class Angle : boost::additive< Angle + , boost::additive< Angle, Coord , boost::equality_comparable< Angle - > > + , boost::equality_comparable< Angle, Coord + > > > > { public: - Angle() : _angle(0) {} //added default constructor because of cython + Angle() : _angle(0) {} 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 &operator+=(Angle o) { _angle += o._angle; _normalize(); return *this; } - Angle &operator-=(Angle const &o) { + Angle &operator-=(Angle o) { _angle -= o._angle; _normalize(); return *this; } - bool operator==(Angle const &o) const { + Angle &operator+=(Coord a) { + *this += Angle(a); + return *this; + } + Angle &operator-=(Coord a) { + *this -= Angle(a); + return *this; + } + bool operator==(Angle o) const { return _angle == o._angle; } + bool operator==(Coord c) const { + return _angle == Angle(c)._angle; + } /** @brief Get the angle as radians. * @return Number in range \f$[-\pi, \pi)\f$. */ @@ -136,12 +152,22 @@ public: private: void _normalize() { - _angle -= floor(_angle * (1.0/(2*M_PI))) * 2*M_PI; + _angle = std::fmod(_angle, 2*M_PI); + if (_angle < 0) _angle += 2*M_PI; + //_angle -= floor(_angle * (1.0/(2*M_PI))) * 2*M_PI; } Coord _angle; // this is always in [0, 2pi) friend class AngleInterval; }; +inline Angle distance(Angle const &a, Angle const &b) { + // the distance cannot be larger than M_PI. + Coord ac = a.radians0(); + Coord bc = b.radians0(); + Coord d = fabs(ac - bc); + return Angle(d > M_PI ? 2*M_PI - d : d); +} + /** @brief Directed angular interval. * * Wrapper for directed angles with defined start and end values. Useful e.g. for representing @@ -149,50 +175,156 @@ private: * in the interval (it is a closed interval). Angular intervals can also be interptered * as functions \f$f: [0, 1] \to [-\pi, \pi)\f$, which return the start angle for 0, * the end angle for 1, and interpolate linearly for other values. Note that such functions - * are not continuous if the interval contains the zero angle. + * are not continuous if the interval crosses the angle \f$\pi\f$. * - * This class is immutable - you cannot change the values of start and end angles - * without creating a new instance of this class. + * This class can represent all directed angular intervals, including empty ones. + * However, not all possible intervals can be created with the constructors. + * For full control, use the setInitial(), setFinal() and setAngles() methods. * * @ingroup Primitives */ -class AngleInterval { +class AngleInterval + : boost::equality_comparable< AngleInterval > +{ public: - AngleInterval(Angle const &s, Angle const &e, bool cw = false) - : _start_angle(s), _end_angle(e), _sweep(cw) + AngleInterval() {} + /** @brief Create an angular interval from two angles and direction. + * If the initial and final angle are the same, a degenerate interval + * (containing only one angle) will be created. + * @param s Starting angle + * @param e Ending angle + * @param cw Which direction the interval goes. True means that it goes + * in the direction of increasing angles, while false means in the direction + * of decreasing angles. */ + AngleInterval(Angle s, Angle e, bool cw = false) + : _start_angle(s), _end_angle(e), _sweep(cw), _full(false) {} AngleInterval(double s, double e, bool cw = false) - : _start_angle(s), _end_angle(e), _sweep(cw) + : _start_angle(s), _end_angle(e), _sweep(cw), _full(false) {} - /** @brief Get the angular coordinate of the interval's initial point - * @return Angle in range \f$[0,2\pi)\f$ corresponding to the start of arc */ - Angle const &initialAngle() const { return _start_angle; } - /** @brief Get the angular coordinate of the interval's final point - * @return Angle in range \f$[0,2\pi)\f$ corresponding to the end of arc */ - Angle const &finalAngle() const { return _end_angle; } - bool isDegenerate() const { return initialAngle() == finalAngle(); } - /** @brief Get an angle corresponding to the specified time value. */ + /** @brief Create an angular interval from three angles. + * If the inner angle is exactly equal to initial or final angle, + * the sweep flag will be set to true, i.e. the interval will go + * in the direction of increasing angles. + * + * If the initial and final angle are the same, but the inner angle + * is different, a full angle in the direction of increasing angles + * will be created. + * + * @param s Initial angle + * @param inner Angle contained in the interval + * @param e Final angle */ + AngleInterval(Angle s, Angle inner, Angle e) + : _start_angle(s) + , _end_angle(e) + , _sweep((inner-s).radians0() <= (e-s).radians0()) + , _full(s == e && s != inner) + { + if (_full) { + _sweep = true; + } + } + + /// Get the start angle. + Angle initialAngle() const { return _start_angle; } + /// Get the end angle. + Angle finalAngle() const { return _end_angle; } + /// Check whether the interval goes in the direction of increasing angles. + bool sweep() const { return _sweep; } + /// Check whether the interval contains only a single angle. + bool isDegenerate() const { + return _start_angle == _end_angle && !_full; + } + /// Check whether the interval contains all angles. + bool isFull() const { + return _start_angle == _end_angle && _full; + } + + /** @brief Set the initial angle. + * @param a Angle to set + * @param prefer_full Whether to set a full angular interval when + * the initial angle is set to the final angle */ + void setInitial(Angle a, bool prefer_full = false) { + _start_angle = a; + _full = prefer_full && a == _end_angle; + } + + /** @brief Set the final angle. + * @param a Angle to set + * @param prefer_full Whether to set a full angular interval when + * the initial angle is set to the final angle */ + void setFinal(Angle a, bool prefer_full = false) { + _end_angle = a; + _full = prefer_full && a == _start_angle; + } + /** @brief Set both angles at once. + * The direction (sweep flag) is left unchanged. + * @param s Initial angle + * @param e Final angle + * @param prefer_full Whether to set a full interval when the passed + * initial and final angle are the same */ + void setAngles(Angle s, Angle e, bool prefer_full = false) { + _start_angle = s; + _end_angle = e; + _full = prefer_full && s == e; + } + /// Set whether the interval goes in the direction of increasing angles. + void setSweep(bool s) { _sweep = s; } + + /// Reverse the direction of the interval while keeping contained values the same. + void reverse() { + using std::swap; + swap(_start_angle, _end_angle); + _sweep = !_sweep; + } + /// Get a new interval with reversed direction. + AngleInterval reversed() const { + AngleInterval result(*this); + result.reverse(); + return result; + } + + /// Get an angle corresponding to the specified time value. Angle angleAt(Coord t) const { Coord span = extent(); Angle ret = _start_angle.radians0() + span * (_sweep ? t : -t); return ret; } Angle operator()(Coord t) const { return angleAt(t); } -#if 0 - /** @brief Find an angle nearest to the specified time value. - * @param a Query angle - * @return If the interval contains the query angle, a number from the range [0, 1] - * such that a = angleAt(t); otherwise, 0 or 1, depending on which extreme - * angle is nearer. */ - Coord nearestAngle(Angle const &a) const { - Coord dist = distance(_start_angle, a, _sweep).radians0(); - Coord span = distance(_start_angle, _end_angle, _sweep).radians0(); - if (dist <= span) return dist / span; - else return distance_abs(_start_angle, a).radians0() > distance_abs(_end_angle, a).radians0() ? 1.0 : 0.0; + + /** @brief Compute a time value that would evaluate to the given angle. + * If the start and end angle are exactly the same, NaN will be returned. + * Negative values will be returned for angles between the initial angle + * and the angle exactly opposite the midpoint of the interval. */ + Coord timeAtAngle(Angle a) const { + if (_full) { + Angle ta = _sweep ? a - _start_angle : _start_angle - a; + return ta.radians0() / (2*M_PI); + } + Coord ex = extent(); + Coord outex = 2*M_PI - ex; + if (_sweep) { + Angle midout = _start_angle - outex / 2; + Angle acmp = a - midout, scmp = _start_angle - midout; + if (acmp.radians0() >= scmp.radians0()) { + return (a - _start_angle).radians0() / ex; + } else { + return -(_start_angle - a).radians0() / ex; + } + } else { + Angle midout = _start_angle + outex / 2; + Angle acmp = a - midout, scmp = _start_angle - midout; + if (acmp.radians0() <= scmp.radians0()) { + return (_start_angle - a).radians0() / ex; + } else { + return -(a - _start_angle).radians0() / ex; + } + } } -#endif - /** @brief Check whether the interval includes the given angle. */ - bool contains(Angle const &a) const { + + /// Check whether the interval includes the given angle. + bool contains(Angle a) const { + if (_full) return true; Coord s = _start_angle.radians0(); Coord e = _end_angle.radians0(); Coord x = a.radians0(); @@ -205,18 +337,48 @@ public: } } /** @brief Extent of the angle interval. - * @return Extent in range \f$[0, 2\pi)\f$ */ + * Equivalent to the absolute value of the sweep angle. + * @return Extent in range \f$[0, 2\pi)\f$. */ Coord extent() const { - Coord d = _end_angle - _start_angle; - if (!_sweep) d = -d; - if (d < 0) d += 2*M_PI; - return d; + if (_full) return 2*M_PI; + return _sweep + ? (_end_angle - _start_angle).radians0() + : (_start_angle - _end_angle).radians0(); } -protected: - AngleInterval() {} + /** @brief Get the sweep angle of the interval. + * This is the value you need to add to the initial angle to get the final angle. + * It is positive when sweep is true. Denoted as \f$\Delta\theta\f$ in the SVG + * elliptical arc implementation notes. */ + Coord sweepAngle() const { + if (_full) return _sweep ? 2*M_PI : -2*M_PI; + Coord sa = _end_angle.radians0() - _start_angle.radians0(); + if (_sweep && sa < 0) sa += 2*M_PI; + if (!_sweep && sa > 0) sa -= 2*M_PI; + return sa; + } + + /// Check another interval for equality. + bool operator==(AngleInterval const &other) const { + if (_start_angle != other._start_angle) return false; + if (_end_angle != other._end_angle) return false; + if (_sweep != other._sweep) return false; + if (_full != other._full) return false; + return true; + } + + static AngleInterval create_full(Angle start, bool sweep = true) { + AngleInterval result; + result._start_angle = result._end_angle = start; + result._sweep = sweep; + result._full = true; + return result; + } + +private: Angle _start_angle; Angle _end_angle; bool _sweep; + bool _full; }; /** @brief Given an angle in degrees, return radians @@ -226,88 +388,12 @@ inline Coord deg_to_rad(Coord deg) { return deg*M_PI/180.0;} * @relates Angle */ inline Coord rad_to_deg(Coord rad) { return rad*180.0/M_PI;} -/* - * start_angle and angle must belong to [0, 2PI[ - * and angle must belong to the cirsular arc defined by - * start_angle, end_angle and with rotation direction cw - */ -inline -double map_circular_arc_on_unit_interval( double angle, double start_angle, double end_angle, bool cw = true ) -{ - double d = end_angle - start_angle; - double t = angle - start_angle; - if ( !cw ) - { - d = -d; - t = -t; - } - d = std::fmod(d, 2*M_PI); - t = std::fmod(t, 2*M_PI); - if ( d < 0 ) d += 2*M_PI; - if ( t < 0 ) t += 2*M_PI; - return t / d; -} - -inline -Coord map_unit_interval_on_circular_arc(Coord t, double start_angle, double end_angle, bool cw = true) -{ - double sweep_angle = end_angle - start_angle; - if ( !cw ) sweep_angle = -sweep_angle; - sweep_angle = std::fmod(sweep_angle, 2*M_PI); - if ( sweep_angle < 0 ) sweep_angle += 2*M_PI; - - Coord angle = start_angle; - if ( cw ) - { - angle += sweep_angle * t; - } - else - { - angle -= sweep_angle * t; - } - angle = std::fmod(angle, 2*M_PI); - if (angle < 0) angle += 2*M_PI; - return angle; -} - -/* - * Return true if the given angle is contained in the circular arc determined - * by the passed angles. - * - * a: the angle to be tested - * sa: the angle the arc start from - * ia: an angle strictly inner to the arc - * ea: the angle the arc end to - * - * prerequisite: the inner angle has to be not equal (mod 2PI) to the start - * or the end angle, except when they are equal each other, too. - * warning: when sa == ea (mod 2PI) they define a whole circle - * if ia != sa (mod 2PI), on the contrary if ia == sa == ea (mod 2PI) - * they define a single point. - */ -inline -bool arc_contains (double a, double sa, double ia, double ea) -{ - a -= sa; - a = std::fmod(a, 2*M_PI); - if (a < 0) a += 2*M_PI; - ia -= sa; - ia = std::fmod(ia, 2*M_PI); - if (ia < 0) ia += 2*M_PI; - ea -= sa; - ea = std::fmod(ea, 2*M_PI); - if (ea < 0) ea += 2*M_PI; +} // end namespace Geom - if (ia == 0 && ea == 0) return (a == 0); - if (ia == 0 || ia == ea) - { - THROW_RANGEERROR ("arc_contains: passed angles do not define an arc"); - } - return (ia < ea && a <= ea) || (ia > ea && (a >= ea || a == 0)); +namespace std { +template <> class iterator_traits<Geom::Angle> {}; } -} // end namespace Geom - #endif // LIB2GEOM_SEEN_ANGLE_H /* 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..17221264b 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,95 @@ 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; + + // in case we encounter an order-1 curve created from a vector + // or a degenerate elliptical arc + if (isLineSegment()) { + LineSegment ls(initialPoint(), finalPoint()); + result = ls.intersect(other); + return result; + } + + // here we are sure that this curve is at least a quadratic Bezier + 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); + } + return result; + } + + // pass other intersection types to the other curve + result = other.intersect(*this, eps); + transpose_in_place(result); + 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 +244,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 +260,53 @@ Coord BezierCurveN<1>::nearestPoint(Point const& p, Coord from, Coord to) const else return from + t*(to-from); } +template <> +std::vector<CurveIntersection> BezierCurveN<1>::intersect(Curve const &other, Coord eps) const +{ + std::vector<CurveIntersection> result; + + // only handle intersections with other LineSegments here + if (other.isLineSegment()) { + Line this_line(initialPoint(), finalPoint()); + Line other_line(other.initialPoint(), other.finalPoint()); + result = this_line.intersect(other_line); + filter_line_segment_intersections(result, true, true); + return result; + } + + // pass all other types to the other curve + result = other.intersect(*this, eps); + transpose_in_place(result); + return result; +} + +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 +315,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..9ac4d7b4d 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,35 @@ 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); } + D2<Bezier> const &fragment() const { return 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 +90,22 @@ 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 bool isLineSegment() const { return size() == 2; } 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 +129,24 @@ 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; + using Curve::operator*=; + virtual void operator*=(Translate const &tr) { + for (unsigned i = 0; i < size(); ++i) { + inner[X][i] += tr[X]; + inner[Y][i] += tr[Y]; + } + } + virtual void operator*=(Scale const &s) { + for (unsigned i = 0; i < size(); ++i) { + inner[X][i] *= s[X]; + inner[Y][i] *= s[Y]; + } + } + virtual void operator*=(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 +157,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 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<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const; + virtual Point pointAt(Coord t) const { return inner.pointAt(t); } + 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 +244,10 @@ public: BezierCurveN(sx.second, sy.second)); } -#ifndef DOXYGEN_SHOULD_SKIP_THIS + virtual bool isLineSegment() const { + return size() == 2; + } + virtual Curve *duplicate() const { return new BezierCurveN(*this); } @@ -242,30 +265,19 @@ 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 *derivative() const; + + virtual Coord nearestTime(Point const &p, Coord from = 0, Coord to = 1) const { + return BezierCurve::nearestTime(p, from, to); } - virtual Curve &operator*=(Translate const &m) { - inner += m.vector(); - return *this; + virtual std::vector<CurveIntersection> intersect(Curve const &other, Coord eps = EPSILON) const { + // call super. this is implemented only to allow specializations + return BezierCurve::intersect(other, eps); } - 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 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 +303,14 @@ 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 <> inline bool BezierCurveN<1>::isLineSegment() const { return true; } 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 <> std::vector<CurveIntersection> BezierCurveN<1>::intersect(Curve const &, 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 +326,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..0c9d12c3b --- /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 operator*(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..c41c2b3a7 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,260 +170,147 @@ 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; - } - - 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; - } + std::vector<Coord> roots() const; + std::vector<Coord> roots(Interval const &ivl) const; - 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; -} + 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-=(Bezier & a, double v) { - for(unsigned i = 0; i <= a.order(); ++i) - a[i] = a[i] - v; - return a; -} +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; -} - -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; +Bezier operator*(Bezier const &f, Bezier const &g); +inline Bezier multiply(Bezier const &f, Bezier const &g) { + Bezier result = f * g; return result; } @@ -424,20 +321,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 +334,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-circle.cpp b/src/2geom/circle-circle.cpp deleted file mode 100644 index 134fa33a2..000000000 --- a/src/2geom/circle-circle.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/* circle_circle_intersection() * - * Determine the points where 2 circles in a common plane intersect. - * - * int circle_circle_intersection( - * // center and radius of 1st circle - * double x0, double y0, double r0, - * // center and radius of 2nd circle - * double x1, double y1, double r1, - * // 1st intersection point - * double *xi, double *yi, - * // 2nd intersection point - * double *xi_prime, double *yi_prime) - * - * This is a public domain work. 3/26/2005 Tim Voght - * Ported to lib2geom, 2006 Nathan Hurst - * - * 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 <stdio.h> -#include <math.h> -#include <2geom/point.h> - -namespace Geom{ - -int circle_circle_intersection(Point X0, double r0, - Point X1, double r1, - Point & p0, Point & p1) -{ - /* dx and dy are the vertical and horizontal distances between - * the circle centers. - */ - Point D = X1 - X0; - - /* Determine the straight-line distance between the centers. */ - double d = L2(D); - - /* Check for solvability. */ - if (d > (r0 + r1)) - { - /* no solution. circles do not intersect. */ - return 0; - } - if (d <= fabs(r0 - r1)) - { - /* no solution. one circle is contained in the other */ - return 1; - } - - /* 'point 2' is the point where the line through the circle - * intersection points crosses the line between the circle - * centers. - */ - - /* Determine the distance from point 0 to point 2. */ - double a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ; - - /* Determine the coordinates of point 2. */ - Point p2 = X0 + D * (a/d); - - /* Determine the distance from point 2 to either of the - * intersection points. - */ - double h = sqrt((r0*r0) - (a*a)); - - /* Now determine the offsets of the intersection points from - * point 2. - */ - Point r = (h/d)*rot90(D); - - /* Determine the absolute intersection points. */ - p0 = p2 + r; - p1 = p2 - r; - - return 2; -} - -}; - - -#ifdef TEST - -void run_test(double x0, double y0, double r0, - double x1, double y1, double r1) -{ - printf("x0=%F, y0=%F, r0=%F, x1=%F, y1=%F, r1=%F :\n", - x0, y0, r0, x1, y1, r1); - Geom::Point p0, p1; - Geom::circle_circle_intersection(Geom::Point(x0, y0), r0, - Geom::Point(x1, y1), r1, - p0, p1); - printf(" x3=%F, y3=%F, x3_prime=%F, y3_prime=%F\n", - p0[0], p0[1], p1[0], p1[1]); -} - -int main(void) -{ - /* Add more! */ - run_test(-1.0, -1.0, 1.5, 1.0, 1.0, 2.0); - run_test(1.0, -1.0, 1.5, -1.0, 1.0, 2.0); - run_test(-1.0, 1.0, 1.5, 1.0, -1.0, 2.0); - run_test(1.0, 1.0, 1.5, -1.0, -1.0, 2.0); - exit(0); -} -#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/circle.cpp b/src/2geom/circle.cpp index d021882ea..553981a72 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,77 +31,241 @@ * the specific language governing rights and limitations. */ - #include <2geom/circle.h> #include <2geom/ellipse.h> -#include <2geom/svg-elliptical-arc.h> +#include <2geom/elliptical-arc.h> #include <2geom/numeric/fitting-tool.h> #include <2geom/numeric/fitting-model.h> +namespace Geom { - -namespace Geom +Rect Circle::boundsFast() const { + Point rr(_radius, _radius); + Rect bbox(_center - rr, _center + rr); + return bbox; +} -void Circle::set(double A, double B, double C, double D) +void Circle::setCoefficients(Coord A, Coord B, Coord C, Coord 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::coefficients(Coord &A, Coord &B, Coord &C, Coord &D) const +{ + A = 1; + B = -2 * _center[X]; + C = -2 * _center[Y]; + D = _center[X] * _center[X] + _center[Y] * _center[Y] - _radius * _radius; +} -void Circle::set(std::vector<Point> const& points) +std::vector<Coord> Circle::coefficients() const { - size_t sz = points.size(); - if (sz < 3) - { - THROW_RANGEERROR("fitting error: too few points passed"); + std::vector<Coord> c(4); + coefficients(c[0], c[1], c[2], c[3]); + return c; +} + + +Zoom Circle::unitCircleTransform() const +{ + Zoom ret(_radius, _center / _radius); + return ret; +} + +Zoom Circle::inverseUnitCircleTransform() const +{ + if (_radius == 0) { + THROW_RANGEERROR("degenerate circle does not have an inverse unit circle transform"); } - NL::LFMCircle model; - NL::least_squeares_fitter<NL::LFMCircle> fitter(model, sz); - for (size_t i = 0; i < sz; ++i) - { - fitter.append(points[i]); + Zoom ret(1/_radius, Translate(-_center)); + return ret; +} + +Point Circle::initialPoint() const +{ + Point p(_center); + p[X] += _radius; + return p; +} + +Point Circle::pointAt(Coord t) const { + return _center + Point::polar(t) * _radius; +} + +Coord Circle::valueAt(Coord t, Dim2 d) const { + Coord delta = (d == X ? std::cos(t) : std::sin(t)); + return _center[d] + delta * _radius; +} + +Coord Circle::timeAt(Point const &p) const { + if (_center == p) return 0; + return atan2(p - _center); +} + +Coord Circle::nearestTime(Point const &p) const { + return timeAt(p); +} + +bool Circle::contains(Rect const &r) const +{ + for (unsigned i = 0; i < 4; ++i) { + if (!contains(r.corner(i))) return false; } - fitter.update(); + return true; +} - NL::Vector z(sz, 0.0); - model.instance(*this, fitter.result(z)); +bool Circle::contains(Circle const &other) const +{ + Coord cdist = distance(_center, other._center); + Coord rdist = fabs(_radius - other._radius); + return cdist <= rdist; +} + +bool Circle::intersects(Line const &l) const +{ + // http://mathworld.wolfram.com/Circle-LineIntersection.html + Coord dr = l.versor().length(); + Coord r = _radius; + Coord D = cross(l.initialPoint(), l.finalPoint()); + Coord delta = r*r * dr*dr - D*D; + if (delta >= 0) return true; + return false; +} + +bool Circle::intersects(Circle const &other) const +{ + Coord cdist = distance(_center, other._center); + Coord rsum = _radius + other._radius; + return cdist <= rsum; +} + + +std::vector<ShapeIntersection> Circle::intersect(Line const &l) const +{ + // http://mathworld.wolfram.com/Circle-LineIntersection.html + Coord dr = l.versor().length(); + Coord dx = l.versor().x(); + Coord dy = l.versor().y(); + Coord D = cross(l.initialPoint() - _center, l.finalPoint() - _center); + Coord delta = _radius*_radius * dr*dr - D*D; + + std::vector<ShapeIntersection> result; + if (delta < 0) return result; + if (delta == 0) { + Coord ix = (D*dy) / (dr*dr); + Coord iy = (-D*dx) / (dr*dr); + Point ip(ix, iy); ip += _center; + result.push_back(ShapeIntersection(timeAt(ip), l.timeAt(ip), ip)); + return result; + } + + Coord sqrt_delta = std::sqrt(delta); + Coord signmod = dy < 0 ? -1 : 1; + + Coord i1x = (D*dy + signmod * dx * sqrt_delta) / (dr*dr); + Coord i1y = (-D*dx + fabs(dy) * sqrt_delta) / (dr*dr); + Point i1p(i1x, i1y); i1p += _center; + + Coord i2x = (D*dy - signmod * dx * sqrt_delta) / (dr*dr); + Coord i2y = (-D*dx - fabs(dy) * sqrt_delta) / (dr*dr); + Point i2p(i2x, i2y); i2p += _center; + + result.push_back(ShapeIntersection(timeAt(i1p), l.timeAt(i1p), i1p)); + result.push_back(ShapeIntersection(timeAt(i2p), l.timeAt(i2p), i2p)); + return result; +} + +std::vector<ShapeIntersection> Circle::intersect(LineSegment const &l) const +{ + std::vector<ShapeIntersection> result = intersect(Line(l)); + filter_line_segment_intersections(result); + return result; +} + +std::vector<ShapeIntersection> Circle::intersect(Circle const &other) const +{ + std::vector<ShapeIntersection> result; + + if (*this == other) { + THROW_INFINITESOLUTIONS(); + } + if (contains(other)) return result; + if (!intersects(other)) return result; + + // See e.g. http://mathworld.wolfram.com/Circle-CircleIntersection.html + // Basically, we figure out where is the third point of a triangle + // with two points in the centers and with edge lengths equal to radii + Point cv = other._center - _center; + Coord d = cv.length(); + Coord R = radius(), r = other.radius(); + + if (d == R + r) { + Point px = lerp(R / d, _center, other._center); + Coord T = timeAt(px), t = other.timeAt(px); + result.push_back(ShapeIntersection(T, t, px)); + return result; + } + + // q is the distance along the line between centers to the perpendicular line + // that goes through both intersections. + Coord q = (d*d - r*r + R*R) / (2*d); + Point qp = lerp(q/d, _center, other._center); + + // The triangle given by the points: + // _center, qp, intersection + // is a right triangle. Determine the distance between qp and intersection + // using the Pythagorean theorem. + Coord h = std::sqrt(R*R - q*q); + Point qd = (h/d) * cv.cw(); + + // now compute the intersection points + Point x1 = qp + qd; + Point x2 = qp - qd; + + result.push_back(ShapeIntersection(timeAt(x1), other.timeAt(x1), x1)); + result.push_back(ShapeIntersection(timeAt(x2), other.timeAt(x2), x2)); + return result; } /** @param inner a point whose angle with the circle center is inside the angle that the arc spans */ EllipticalArc * -Circle::arc(Point const& initial, Point const& inner, Point const& final, - bool _svg_compliant) +Circle::arc(Point const& initial, Point const& inner, Point const& final) const { // 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); } -D2<SBasis> Circle::toSBasis() +bool Circle::operator==(Circle const &other) const +{ + if (_center != other._center) return false; + if (_radius != other._radius) return false; + return true; +} + +D2<SBasis> Circle::toSBasis() const { D2<SBasis> B; Linear bo = Linear(0, 2 * M_PI); @@ -108,27 +273,57 @@ 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) { - Path pb; - D2<SBasis> B = toSBasis(); +void Circle::fit(std::vector<Point> const& points) +{ + size_t sz = points.size(); + 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; + } - pb.append(SBasisCurve(B)); + NL::LFMCircle model; + NL::least_squeares_fitter<NL::LFMCircle> fitter(model, sz); - path_out.push_back(pb); -} + for (size_t i = 0; i < sz; ++i) { + fitter.append(points[i]); + } + fitter.update(); + NL::Vector z(sz, 0.0); + model.instance(*this, fitter.result(z)); +} -} // end namespace Geom +bool are_near(Circle const &a, Circle const &b, Coord eps) +{ + // to check whether no point on a is further than eps from b, + // we check two things: + // 1. if radii differ by more than eps, there is definitely a point that fails + // 2. if they differ by less, we check the centers. They have to be closer + // together if the radius differs, since the maximum distance will be + // equal to sum of radius difference and distance between centers. + if (!are_near(a.radius(), b.radius(), eps)) return false; + Coord adjusted_eps = eps - fabs(a.radius() - b.radius()); + return are_near(a.center(), b.center(), adjusted_eps); +} +std::ostream &operator<<(std::ostream &out, Circle const &c) +{ + out << "Circle(" << c.center() << ", " << format_coord_nice(c.radius()) << ")"; + return out; +} +} // end namespace Geom /* Local Variables: diff --git a/src/2geom/circle.h b/src/2geom/circle.h index ca9241047..a4d5f2097 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,119 @@ #ifndef LIB2GEOM_SEEN_CIRCLE_H #define LIB2GEOM_SEEN_CIRCLE_H -#include <vector> +#include <2geom/forward.h> +#include <2geom/intersection.h> #include <2geom/point.h> -#include <2geom/exception.h> -#include <2geom/path.h> +#include <2geom/rect.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 + : boost::equality_comparable1< Circle + , MultipliableNoncommutative< Circle, Translate + , MultipliableNoncommutative< Circle, Rotate + , MultipliableNoncommutative< Circle, Zoom + > > > > { - public: - Circle() + Point _center; + Coord _radius; + +public: + Circle() {} + Circle(Coord cx, Coord cy, Coord r) + : _center(cx, cy), _radius(r) + {} + Circle(Point const ¢er, Coord r) + : _center(center), _radius(r) {} - Circle(double cx, double cy, double r) - : m_centre(cx, cy), m_ray(r) - { + Circle(Coord A, Coord B, Coord C, Coord D) { + setCoefficients(A, B, C, D); } - Circle(Point center, double r) - : m_centre(center), m_ray(r) - { - } + // Construct the unique circle passing through three points. + //Circle(Point const &a, Point const &b, Point const &c); - Circle(double A, double B, double C, double D) - { - set(A, B, C, D); - } + Point center() const { return _center; } + Coord center(Dim2 d) const { return _center[d]; } + Coord radius() const { return _radius; } + Coord area() const { return M_PI * _radius * _radius; } + bool isDegenerate() const { return _radius == 0; } - Circle(std::vector<Point> const& points) - { - set(points); - } + void setCenter(Point const &p) { _center = p; } + void setRadius(Coord c) { _radius = c; } - void set(double cx, double cy, double r) - { - m_centre[X] = cx; - m_centre[Y] = cy; - m_ray = r; - } + Rect boundsFast() const; + Rect boundsExact() const { return boundsFast(); } + + Point initialPoint() const; + Point finalPoint() const { return initialPoint(); } + Point pointAt(Coord t) const; + Coord valueAt(Coord t, Dim2 d) const; + Coord timeAt(Point const &p) const; + Coord nearestTime(Point const &p) const; + + bool contains(Point const &p) const { return distance(p, _center) <= _radius; } + bool contains(Rect const &other) const; + bool contains(Circle const &other) const; + bool intersects(Line const &l) const; + bool intersects(LineSegment const &l) const; + bool intersects(Circle const &other) const; + + std::vector<ShapeIntersection> intersect(Line const &other) const; + std::vector<ShapeIntersection> intersect(LineSegment const &other) const; + std::vector<ShapeIntersection> intersect(Circle const &other) const; // 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); + void coefficients(Coord &A, Coord &B, Coord &C, Coord &D) const; + std::vector<Coord> coefficients() const; - // 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); + Zoom unitCircleTransform() const; + Zoom inverseUnitCircleTransform() const; EllipticalArc * - arc(Point const& initial, Point const& inner, Point const& final, - bool _svg_compliant = true); + arc(Point const& initial, Point const& inner, Point const& final) const; - D2<SBasis> toSBasis(); - void getPath(std::vector<Path> &path_out); + D2<SBasis> toSBasis() const; - Point center() const - { - return m_centre; + Circle &operator*=(Translate const &t) { + _center *= t; + return *this; } - - Coord center(Dim2 d) const - { - return m_centre[d]; + Circle &operator*=(Rotate const &) { + return *this; } - - Coord ray() const - { - return m_ray; + Circle &operator*=(Zoom const &z) { + _center *= z; + _radius *= z.scale(); + return *this; } + bool operator==(Circle const &other) const; + + /** @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); +}; + +bool are_near(Circle const &a, Circle const &b, Coord eps=EPSILON); + +std::ostream &operator<<(std::ostream &out, Circle const &c); - private: - Point m_centre; - Coord m_ray; +template <> +struct ShapeTraits<Circle> { + typedef Coord TimeType; + typedef Interval IntervalType; + typedef Ellipse AffineClosureType; + typedef Intersection<> IntersectionType; }; } // 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..c331f078b 100644 --- a/src/2geom/concepts.h +++ b/src/2geom/concepts.h @@ -35,9 +35,11 @@ #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> +#include <2geom/transforms.h> namespace Geom { @@ -71,16 +73,18 @@ 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); - //Is a pure derivative (ignoring others) accessor ever much faster? - //u = number of values returned. first val is value. + //Is a pure derivative (ignoring others) accessor ever much faster? + //u = number of values returned. first val is value. sb = t.toSBasis(); t = reverse(t); i = bounds_fast(t); @@ -97,9 +101,51 @@ 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, other; + Time t; + Point p; + AffineClosure ac; + Affine m; + Translate tr; + Coord c; + bool bool_; + std::vector<Isect> ivec; + + void constraints() { + p = shape.pointAt(t); + c = shape.valueAt(t, X); + ivec = shape.intersect(other); + t = shape.nearestTime(p); + shape *= tr; + ac = shape; + ac *= m; + bool_ = (shape == shape); + bool_ = (shape != other); + bool_ = shape.isDegenerate(); + //bool_ = are_near(shape, other, c); + } +}; + +template <typename T> inline T portion(const T& t, const Interval& i) { return portion(t, i.min(), i.max()); } template <typename T> +struct EqualityComparableConcept { + T a, b; + bool bool_; + void constaints() { + bool_ = (a == b); + bool_ = (a != b); + } +}; + +template <typename T> struct NearConcept { T a, b; double tol; 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..089db71a4 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) { @@ -108,41 +80,6 @@ static double boxprod(Point a, Point b, Point c) { return det(a,b) - det(a,c) + det(b,c); } - -/** - * Find the roots of (q2x + q1)x+q0 = 0 - * Tries to be numerically robust. - */ -template <typename T> -static std::vector<T> quadratic_roots(T q0, T q1, T q2) { - std::vector<double> r; - if(q2 == 0) { - if(q1 == 0) { // zero or infinite roots - return r; - } - r.push_back(-q0/q1); - } else { - double desc = q1*q1 - 4*q2*q0; - /*cout << q2 << ", " - << q1 << ", " - << q0 << "; " - << desc << "\n";*/ - if (desc < 0) - return r; - else if (desc == 0) - r.push_back(-q1/(2*q2)); - else { - desc = std::sqrt(desc); - double t = -0.5*(q1+sgn(q1)*desc); - r.push_back(t/q2); - r.push_back(q0/t); - } - } - return r; -} - - - class BadConversion : public std::runtime_error { public: BadConversion(const std::string& s) @@ -673,7 +610,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 +651,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 +690,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 +738,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 +781,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) { @@ -1398,6 +1337,7 @@ bool xAx::decompose (Line& l1, Line& l2) const */ Rect xAx::arc_bound (const Point & P1, const Point & Q, const Point & P2) const { + using std::swap; //std::cout << "BOUND: P1 = " << P1 << std::endl; //std::cout << "BOUND: Q = " << Q << std::endl; //std::cout << "BOUND: P2 = " << P2 << std::endl; @@ -1442,38 +1382,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 +1423,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..dbe564872 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> @@ -463,33 +462,28 @@ public: bool arc_contains (const Point & _point, const Point & _initial, const Point & _inner, const Point & _final) const { - double pa = angle_at (_point); - double sa = angle_at (_initial); - double ia = angle_at (_inner); - double ea = angle_at (_final); - // we test if _point and _inner have the same position - // wrt _initial and _final - return Geom::arc_contains (pa, sa, ia, ea); + AngleInterval ai(angle_at(_initial), angle_at(_inner), angle_at(_final)); + return ai.contains(angle_at(_point)); } 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 +503,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..8b5e28586 --- /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..9cc220db7 100644 --- a/src/2geom/coord.h +++ b/src/2geom/coord.h @@ -1,8 +1,10 @@ -/** - * \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> + * Authors: + * Nathan Hurst <njh@mail.csse.monash.edu.au> + * 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 @@ -34,34 +36,92 @@ #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). */ +/** @brief 2D axis enumeration (X or Y). + * @ingroup Primitives */ enum Dim2 { X=0, Y=1 }; -/** - * @brief Floating point type used to store coordinates. - * - * You may safely assume that double (or even float) provides enough precision for storing - * on-canvas points, and hence that double provides enough precision for dot products of - * differences of on-canvas points. - */ +/** @brief Get the other (perpendicular) dimension. + * @ingroup Primitives */ +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; +}; + +/** @brief Axis extraction functor. + * For use with things such as Boost's transform_iterator. + * @ingroup Utilities */ +template <Dim2 D, typename T> +struct GetAxis { + typedef typename D2Traits<T>::D1Value result_type; + typedef T argument_type; + typename D2Traits<T>::D1Value operator()(T const &a) const { + return a[D]; + } +}; + +/** @brief Floating point type used to store coordinates. + * @ingroup Primitives */ typedef double Coord; + +/** @brief Type used for integral coordinates. + * @ingroup Primitives */ typedef int IntCoord; -const Coord EPSILON = 1e-5; //1e-18; +/** @brief Default "acceptably small" value. + * @ingroup Primitives */ +const Coord EPSILON = 1e-6; //1e-18; +/** @brief Get a value representing infinity. + * @ingroup Primitives */ inline Coord infinity() { return std::numeric_limits<Coord>::infinity(); } -//IMPL: NearConcept +/** @brief Nearness predicate for values. + * @ingroup Primitives */ 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; } +/** @brief Numerically stable linear interpolation. + * @ingroup Primitives */ +inline Coord lerp(Coord t, Coord a, Coord b) { + return (1 - t) * a + t * b; +} + +/** @brief Traits class used with coordinate types. + * Defines point, interval and rectangle types for the given coordinate type. + * @ingroup Utilities */ 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 +180,24 @@ struct CoordTraits<Coord> { RectOps; }; +/** @brief Convert coordinate to shortest possible string. + * @return The shortest string that parses back to the original value. + * @relates Coord */ +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. + * @relates Coord */ +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. + * @relates Coord */ +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..425fa58f5 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 <2geom/sweep-bounds.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..abbdb1100 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,41 @@ 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; + + /// Check whether the curve is a line segment. + virtual bool isLineSegment() const { return false; } + + /** @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 +138,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 +154,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 +180,31 @@ 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 */ + void transform(Affine const &m) { + *this *= m; + } + + virtual void operator*=(Translate const &tr) { *this *= Affine(tr); } + virtual void operator*=(Scale const &s) { *this *= Affine(s); } + virtual void operator*=(Rotate const &r) { *this *= Affine(r); } + virtual void operator*=(HShear const &hs) { *this *= Affine(hs); } + virtual void operator*=(VShear const &vs) { *this *= Affine(vs); } + virtual void operator*=(Zoom const &z) { *this *= Affine(z); } + virtual void operator*=(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 +217,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 +245,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 +276,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 @@ -258,14 +307,9 @@ public: * derivative could be found. * @param t Time value * @param n The maximum order of derivative to consider - * @return Unit tangent vector \f$\mathbf{v}(t)\f$ - * @bug This method might currently break for the case of t being exactly 1. A workaround - * is to reverse the curve and use the negated unit tangent at 0 like this: - * @code - Curve *c_reverse = c.reverse(); - Point tangent = - c_reverse->unitTangentAt(0); - delete c_reverse; @endcode */ + * @return Unit tangent vector \f$\mathbf{v}(t)\f$ */ 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 +325,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..46fb6d973 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,9 +37,7 @@ #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> #endif // LIB2GEOM_SEEN_CURVES_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..dca6fa614 100644 --- a/src/2geom/d2.h +++ b/src/2geom/d2.h @@ -29,32 +29,34 @@ * */ -#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/assert.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. - * A D2<double> is a Point. A D2<Interval> is a standard axis aligned rectangle. - * D2<SBasis> provides a 2d parametric function which maps t to a point - * x(t), y(t) + * @brief Adaptor that creates 2D functions from 1D ones. + * @ingroup Fragments */ -template <class T> -class D2{ - //BOOST_CLASS_REQUIRE(T, boost, AssignableConcept); - private: +template <typename T> +class D2 +{ +private: T f[2]; - public: +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,38 +67,72 @@ 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<GetAxis<X,V>, Iter> XIter; + typedef typename boost::transform_iterator<GetAxis<Y,V>, Iter> YIter; + + XIter xfirst(first, GetAxis<X,V>()), xlast(last, GetAxis<X,V>()); + f[X] = T(xfirst, xlast); + YIter yfirst(first, GetAxis<Y,V>()), ylast(last, GetAxis<Y,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<GetAxis<X,V>, Iter> XIter; + typedef boost::transform_iterator<GetAxis<Y,V>, Iter> YIter; + + XIter xfirst(vec.begin(), GetAxis<X,V>()), xlast(vec.end(), GetAxis<X,V>()); + f[X] = T(xfirst, xlast); + YIter yfirst(vec.begin(), GetAxis<Y,V>()), ylast(vec.end(), GetAxis<Y,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; bool isZero(double eps=EPSILON) const { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return f[X].isZero(eps) && f[Y].isZero(eps); } bool isConstant(double eps=EPSILON) const { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return f[X].isConstant(eps) && f[Y].isConstant(eps); } bool isFinite() const { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return f[X].isFinite() && f[Y].isFinite(); } Point at0() const { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return Point(f[X].at0(), f[Y].at0()); } Point at1() const { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return Point(f[X].at1(), f[Y].at1()); } + Point pointAt(double t) const { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); + return (*this)(t); + } Point valueAt(double t) const { - boost::function_requires<FragmentConcept<T> >(); + // TODO: remove this alias + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return (*this)(t); } std::vector<Point > valueAndDerivatives(double t, unsigned n) const { + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); std::vector<Coord> x = f[X].valueAndDerivatives(t, n), y = f[Y].valueAndDerivatives(t, n); // always returns a vector of size n+1 std::vector<Point> res(n+1); @@ -106,7 +142,7 @@ class D2{ return res; } D2<SBasis> toSBasis() const { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return D2<SBasis>(f[X].toSBasis(), f[Y].toSBasis()); } @@ -115,33 +151,33 @@ class D2{ }; template <typename T> inline D2<T> reverse(const D2<T> &a) { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return D2<T>(reverse(a[X]), reverse(a[Y])); } template <typename T> inline D2<T> portion(const D2<T> &a, Coord f, Coord t) { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return D2<T>(portion(a[X], f, t), portion(a[Y], f, t)); } template <typename T> inline D2<T> portion(const D2<T> &a, Interval i) { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return D2<T>(portion(a[X], i), portion(a[Y], i)); } -//IMPL: boost::EqualityComparableConcept +//IMPL: EqualityComparableConcept template <typename T> inline bool operator==(D2<T> const &a, D2<T> const &b) { - boost::function_requires<boost::EqualityComparableConcept<T> >(); + BOOST_CONCEPT_ASSERT((EqualityComparableConcept<T>)); return a[0]==b[0] && a[1]==b[1]; } template <typename T> inline bool operator!=(D2<T> const &a, D2<T> const &b) { - boost::function_requires<boost::EqualityComparableConcept<T> >(); + BOOST_CONCEPT_ASSERT((EqualityComparableConcept<T>)); return a[0]!=b[0] || a[1]!=b[1]; } @@ -149,7 +185,7 @@ operator!=(D2<T> const &a, D2<T> const &b) { template <typename T> inline bool are_near(D2<T> const &a, D2<T> const &b, double tol) { - boost::function_requires<NearConcept<T> >(); + BOOST_CONCEPT_ASSERT((NearConcept<T>)); return are_near(a[0], b[0], tol) && are_near(a[1], b[1], tol); } @@ -157,7 +193,7 @@ are_near(D2<T> const &a, D2<T> const &b, double tol) { template <typename T> inline D2<T> operator+(D2<T> const &a, D2<T> const &b) { - boost::function_requires<AddableConcept<T> >(); + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); D2<T> r; for(unsigned i = 0; i < 2; i++) @@ -167,7 +203,7 @@ operator+(D2<T> const &a, D2<T> const &b) { template <typename T> inline D2<T> operator-(D2<T> const &a, D2<T> const &b) { - boost::function_requires<AddableConcept<T> >(); + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); D2<T> r; for(unsigned i = 0; i < 2; i++) @@ -177,7 +213,7 @@ operator-(D2<T> const &a, D2<T> const &b) { template <typename T> inline D2<T> operator+=(D2<T> &a, D2<T> const &b) { - boost::function_requires<AddableConcept<T> >(); + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); for(unsigned i = 0; i < 2; i++) a[i] += b[i]; @@ -186,7 +222,7 @@ operator+=(D2<T> &a, D2<T> const &b) { template <typename T> inline D2<T> operator-=(D2<T> &a, D2<T> const & b) { - boost::function_requires<AddableConcept<T> >(); + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); for(unsigned i = 0; i < 2; i++) a[i] -= b[i]; @@ -197,7 +233,7 @@ operator-=(D2<T> &a, D2<T> const & b) { template <typename T> inline D2<T> operator-(D2<T> const & a) { - boost::function_requires<ScalableConcept<T> >(); + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); D2<T> r; for(unsigned i = 0; i < 2; i++) r[i] = -a[i]; @@ -206,7 +242,7 @@ operator-(D2<T> const & a) { template <typename T> inline D2<T> operator*(D2<T> const & a, Point const & b) { - boost::function_requires<ScalableConcept<T> >(); + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); D2<T> r; for(unsigned i = 0; i < 2; i++) @@ -216,7 +252,7 @@ operator*(D2<T> const & a, Point const & b) { template <typename T> inline D2<T> operator/(D2<T> const & a, Point const & b) { - boost::function_requires<ScalableConcept<T> >(); + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); //TODO: b==0? D2<T> r; for(unsigned i = 0; i < 2; i++) @@ -226,7 +262,7 @@ operator/(D2<T> const & a, Point const & b) { template <typename T> inline D2<T> operator*=(D2<T> &a, Point const & b) { - boost::function_requires<ScalableConcept<T> >(); + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); for(unsigned i = 0; i < 2; i++) a[i] *= b[i]; @@ -235,7 +271,7 @@ operator*=(D2<T> &a, Point const & b) { template <typename T> inline D2<T> operator/=(D2<T> &a, Point const & b) { - boost::function_requires<ScalableConcept<T> >(); + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); //TODO: b==0? for(unsigned i = 0; i < 2; i++) a[i] /= b[i]; @@ -253,8 +289,8 @@ inline D2<T> operator/=(D2<T> & a, double b) { a[0] /= b; a[1] /= b; return a; } template<typename T> D2<T> operator*(D2<T> const &v, Affine const &m) { - boost::function_requires<AddableConcept<T> >(); - boost::function_requires<ScalableConcept<T> >(); + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); D2<T> ret; for(unsigned i = 0; i < 2; i++) ret[i] = v[X] * m[i] + v[Y] * m[i + 2] + m[i + 4]; @@ -265,7 +301,7 @@ D2<T> operator*(D2<T> const &v, Affine const &m) { template <typename T> inline D2<T> operator*(D2<T> const & a, T const & b) { - boost::function_requires<MultiplicableConcept<T> >(); + BOOST_CONCEPT_ASSERT((MultiplicableConcept<T>)); D2<T> ret; for(unsigned i = 0; i < 2; i++) ret[i] = a[i] * b; @@ -278,7 +314,7 @@ operator*(D2<T> const & a, T const & b) { template <typename T> inline D2<T> operator+(D2<T> const & a, Point b) { - boost::function_requires<OffsetableConcept<T> >(); + BOOST_CONCEPT_ASSERT((OffsetableConcept<T>)); D2<T> r; for(unsigned i = 0; i < 2; i++) r[i] = a[i] + b[i]; @@ -287,7 +323,7 @@ operator+(D2<T> const & a, Point b) { template <typename T> inline D2<T> operator-(D2<T> const & a, Point b) { - boost::function_requires<OffsetableConcept<T> >(); + BOOST_CONCEPT_ASSERT((OffsetableConcept<T>)); D2<T> r; for(unsigned i = 0; i < 2; i++) r[i] = a[i] - b[i]; @@ -296,7 +332,7 @@ operator-(D2<T> const & a, Point b) { template <typename T> inline D2<T> operator+=(D2<T> & a, Point b) { - boost::function_requires<OffsetableConcept<T> >(); + BOOST_CONCEPT_ASSERT((OffsetableConcept<T>)); for(unsigned i = 0; i < 2; i++) a[i] += b[i]; return a; @@ -304,7 +340,7 @@ operator+=(D2<T> & a, Point b) { template <typename T> inline D2<T> operator-=(D2<T> & a, Point b) { - boost::function_requires<OffsetableConcept<T> >(); + BOOST_CONCEPT_ASSERT((OffsetableConcept<T>)); for(unsigned i = 0; i < 2; i++) a[i] -= b[i]; return a; @@ -313,8 +349,8 @@ operator-=(D2<T> & a, Point b) { template <typename T> inline T dot(D2<T> const & a, D2<T> const & b) { - boost::function_requires<AddableConcept<T> >(); - boost::function_requires<MultiplicableConcept<T> >(); + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); + BOOST_CONCEPT_ASSERT((MultiplicableConcept<T>)); T r; for(unsigned i = 0; i < 2; i++) @@ -328,8 +364,8 @@ dot(D2<T> const & a, D2<T> const & b) { template <typename T> inline T dot(D2<T> const & a, Point const & b) { - boost::function_requires<AddableConcept<T> >(); - boost::function_requires<ScalableConcept<T> >(); + BOOST_CONCEPT_ASSERT((AddableConcept<T>)); + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); T r; for(unsigned i = 0; i < 2; i++) { @@ -344,8 +380,8 @@ dot(D2<T> const & a, Point const & b) { template <typename T> inline T cross(D2<T> const & a, D2<T> const & b) { - boost::function_requires<ScalableConcept<T> >(); - boost::function_requires<MultiplicableConcept<T> >(); + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); + BOOST_CONCEPT_ASSERT((MultiplicableConcept<T>)); return a[1] * b[0] - a[0] * b[1]; } @@ -355,7 +391,7 @@ cross(D2<T> const & a, D2<T> const & b) { template <typename T> inline D2<T> rot90(D2<T> const & a) { - boost::function_requires<ScalableConcept<T> >(); + BOOST_CONCEPT_ASSERT((ScalableConcept<T>)); return D2<T>(-a[Y], a[X]); } @@ -429,26 +465,28 @@ 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> OptRect bounds_fast(const D2<T> &a) { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return OptRect(bounds_fast(a[X]), bounds_fast(a[Y])); } template <typename T> OptRect bounds_exact(const D2<T> &a) { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return OptRect(bounds_exact(a[X]), bounds_exact(a[Y])); } template <typename T> OptRect bounds_local(const D2<T> &a, const OptInterval &t) { - boost::function_requires<FragmentConcept<T> >(); + BOOST_CONCEPT_ASSERT((FragmentConcept<T>)); return OptRect(bounds_local(a[X], t), bounds_local(a[Y], t)); } -}; +} // end namespace Geom + +#endif /* Local Variables: mode:c++ @@ -459,4 +497,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..4b5ad9762 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,138 +31,161 @@ * the specific language governing rights and limitations. */ - #include <2geom/ellipse.h> -#include <2geom/svg-elliptical-arc.h> +#include <2geom/elliptical-arc.h> #include <2geom/numeric/fitting-tool.h> #include <2geom/numeric/fitting-model.h> -using std::swap; +namespace Geom { -namespace Geom -{ +Ellipse::Ellipse(Geom::Circle const &c) + : _center(c.center()) + , _rays(c.radius(), c.radius()) + , _angle(0) +{} -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; //evaluate ellipse rotation angle - 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 ) - { - swap_axes = true; - } + _angle = std::atan2( -B, -(A - C) )/2; // evaluate the length of the ellipse rays - double cosrot = std::cos(rot); - double sinrot = std::sin(rot); + double sinrot, cosrot; + sincos(_angle, 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); + _rays[X] = 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); + _rays[Y] = 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) ) - { - rot = 0; - } - else if ( rot < 0 ) - { - rot += M_PI/2; + makeCanonical(); +} + +Point Ellipse::initialPoint() const +{ + Coord sinrot, cosrot; + sincos(_angle, sinrot, cosrot); + Point p(ray(X) * cosrot + center(X), ray(X) * sinrot + center(Y)); + return p; +} + + +Affine Ellipse::unitCircleTransform() const +{ + Affine ret = Scale(ray(X), ray(Y)) * Rotate(_angle); + ret.setTranslation(center()); + return ret; +} + +Affine Ellipse::inverseUnitCircleTransform() const +{ + if (ray(X) == 0 || ray(Y) == 0) { + THROW_RANGEERROR("a degenerate ellipse doesn't have an inverse unit circle transform"); } + Affine ret = Translate(-center()) * Rotate(-_angle) * Scale(1/ray(X), 1/ray(Y)); + return ret; +} + + +LineSegment Ellipse::axis(Dim2 d) const +{ + Point a(0, 0), b(0, 0); + a[d] = -1; + b[d] = 1; + LineSegment ls(a, b); + ls.transform(unitCircleTransform()); + return ls; +} - m_ray[X] = rx; - m_ray[Y] = ry; - m_angle = rot; +LineSegment Ellipse::semiaxis(Dim2 d, int sign) const +{ + Point a(0, 0), b(0, 0); + b[d] = sgn(sign); + LineSegment ls(a, b); + ls.transform(unitCircleTransform()); + return ls; } -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"); + std::vector<double> c(6); + coefficients(c[0], c[1], c[2], c[3], c[4], c[5]); + return c; +} + +void Ellipse::coefficients(Coord &A, Coord &B, Coord &C, Coord &D, Coord &E, Coord &F) const +{ + 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; double invrx2 = 1 / (ray(X) * ray(X)); double invry2 = 1 / (ray(Y) * ray(Y)); - coeff[0] = invrx2 * cos2 + invry2 * sin2; - coeff[1] = 2 * (invrx2 - invry2) * cossin; - coeff[2] = invrx2 * sin2 + invry2 * cos2; - coeff[3] = -(2 * coeff[0] * center(X) + coeff[1] * center(Y)); - coeff[4] = -(2 * coeff[2] * center(Y) + coeff[1] * center(X)); - coeff[5] = coeff[0] * center(X) * center(X) - + coeff[1] * center(X) * center(Y) - + coeff[2] * center(Y) * center(Y) - - 1; - return coeff; + A = invrx2 * cos2 + invry2 * sin2; + B = 2 * (invrx2 - invry2) * cossin; + C = invrx2 * sin2 + invry2 * cos2; + D = -2 * A * center(X) - B * center(Y); + E = -2 * C * center(Y) - B * center(X); + F = A * center(X) * center(X) + + B * center(X) * center(Y) + + C * center(Y) * center(Y) + - 1; } -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,109 +196,443 @@ void Ellipse::set(std::vector<Point> const& points) EllipticalArc * -Ellipse::arc(Point const& initial, Point const& inner, Point const& final, - bool _svg_compliant) +Ellipse::arc(Point const &ip, Point const &inner, Point const &fp) { - Point sp_cp = initial - center(); - Point ep_cp = final - center(); - Point ip_cp = inner - center(); + // This is resistant to degenerate ellipses: + // both flags evaluate to false in that case. + + bool large_arc_flag = false; + bool sweep_flag = false; + + // Determination of large arc flag: + // large_arc is false when the inner point is on the same side + // of the center---initial point line as the final point, AND + // is on the same side of the center---final point line as the + // initial point. + // Additionally, large_arc is always false when we have exactly + // 1/2 of an arc, i.e. the cross product of the center -> initial point + // and center -> final point vectors is zero. + // Negating the above leads to the condition for large_arc being true. + Point fv = fp - _center; + Point iv = ip - _center; + Point innerv = inner - _center; + double ifcp = cross(fv, iv); + + if (ifcp != 0 && (sgn(cross(fv, innerv)) != sgn(ifcp) || + sgn(cross(iv, innerv)) != sgn(-ifcp))) + { + large_arc_flag = true; + } - double angle1 = angle_between(sp_cp, ep_cp); - double angle2 = angle_between(sp_cp, ip_cp); - double angle3 = angle_between(ip_cp, ep_cp); + //cross(-iv, fv) && large_arc_flag + + + // Determination of sweep flag: + // For clarity, let's assume that Y grows up. Then the cross product + // is positive for points on the left side of a vector and negative + // on the right side of a vector. + // + // cross(?, v) > 0 + // o-------------------> + // cross(?, v) < 0 + // + // If the arc is small (large_arc_flag is false) and the final point + // is on the right side of the vector initial point -> center, + // we have to go in the direction of increasing angles + // (counter-clockwise) and the sweep flag is true. + // If the arc is large, the opposite is true, since we have to reach + // the final point going the long way - in the other direction. + // We can express this observation as: + // cross(_center - ip, fp - _center) < 0 xor large_arc flag + // This is equal to: + // cross(-iv, fv) < 0 xor large_arc flag + // But cross(-iv, fv) is equal to cross(fv, iv) due to antisymmetry + // of the cross product, so we end up with the condition below. + if ((ifcp < 0) ^ large_arc_flag) { + sweep_flag = true; + } - bool large_arc_flag = true; - bool sweep_flag = true; + EllipticalArc *ret_arc = new EllipticalArc(ip, ray(X), ray(Y), rotationAngle(), + large_arc_flag, sweep_flag, fp); + return ret_arc; +} - if ( angle1 > 0 ) - { - if ( angle2 > 0 && angle3 > 0 ) - { - large_arc_flag = false; - sweep_flag = true; - } - else - { - large_arc_flag = true; - sweep_flag = false; +Ellipse &Ellipse::operator*=(Rotate const &r) +{ + _angle += r.angle(); + _center *= r; + return *this; +} + +Ellipse &Ellipse::operator*=(Affine const& m) +{ + Affine a = Scale(ray(X), ray(Y)) * Rotate(_angle); + Affine mwot = m.withoutTranslation(); + Affine am = a * mwot; + Point new_center = _center * m; + + 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 { + angle = M_PI/2; } + Point v = Point::polar(angle) * am; + _center = new_center; + _rays[X] = L2(v); + _rays[Y] = 0; + _angle = atan2(v); + return *this; } - else - { - if ( angle2 < 0 && angle3 < 0 ) - { - large_arc_flag = false; - sweep_flag = false; - } - else - { - large_arc_flag = true; - sweep_flag = true; - } + + std::vector<double> coeff = coefficients(); + Affine q( coeff[0], coeff[1]/2, + coeff[1]/2, coeff[2], + 0, 0 ); + + 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 = new_center; + + return *this; +} + +Ellipse Ellipse::canonicalForm() const +{ + Ellipse result(*this); + result.makeCanonical(); + return result; +} + +void Ellipse::makeCanonical() +{ + if (_rays[X] == _rays[Y]) { + _angle = 0; + return; + } + + if (_angle < 0) { + _angle += M_PI; + } + if (_angle >= M_PI/2) { + std::swap(_rays[X], _rays[Y]); + _angle -= M_PI/2; } +} - EllipticalArc *ret_arc; - if (_svg_compliant) { - ret_arc = new SVGEllipticalArc(initial, ray(X), ray(Y), rot_angle(), - large_arc_flag, sweep_flag, final); +Point Ellipse::pointAt(Coord t) const +{ + Point p = Point::polar(t); + p *= unitCircleTransform(); + return p; +} + +Coord Ellipse::valueAt(Coord t, Dim2 d) const +{ + Coord sinrot, cosrot, cost, sint; + sincos(rotationAngle(), sinrot, cosrot); + sincos(t, sint, cost); + + if ( d == X ) { + return ray(X) * cosrot * cost + - ray(Y) * sinrot * sint + + center(X); } else { - ret_arc = new EllipticalArc(initial, ray(X), ray(Y), rot_angle(), - large_arc_flag, sweep_flag, final); + return ray(X) * sinrot * cost + + ray(Y) * cosrot * sint + + center(Y); } - return ret_arc; } -Ellipse Ellipse::transformed(Affine const& m) const +Coord Ellipse::timeAt(Point const &p) const { - 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) ) - { - double angle; - if (AM[0] != 0) - { - angle = std::atan2(AM[2], AM[0]); + // degenerate ellipse is basically a reparametrized line segment + if (ray(X) == 0 || ray(Y) == 0) { + if (ray(X) != 0) { + return asin(Line(axis(X)).timeAt(p)); + } else if (ray(Y) != 0) { + return acos(Line(axis(Y)).timeAt(p)); + } else { + return 0; } - else if (AM[1] != 0) - { - angle = std::atan2(AM[3], AM[1]); + } + Affine iuct = inverseUnitCircleTransform(); + return Angle(atan2(p * iuct)).radians0(); // return a value in [0, 2pi) +} + +Point Ellipse::unitTangentAt(Coord t) const +{ + Point p = Point::polar(t + M_PI/2); + p *= unitCircleTransform().withoutTranslation(); + p.normalize(); + return p; +} + +bool Ellipse::contains(Point const &p) const +{ + Point tp = p * inverseUnitCircleTransform(); + return tp.length() <= 1; +} + +std::vector<ShapeIntersection> Ellipse::intersect(Line const &line) const +{ + + std::vector<ShapeIntersection> result; + + if (line.isDegenerate()) return result; + if (ray(X) == 0 || ray(Y) == 0) { + // TODO intersect with line segment. + return result; + } + + // Ax^2 + Bxy + Cy^2 + Dx + Ey + F + Coord A, B, C, D, E, F; + coefficients(A, B, C, D, E, F); + Affine iuct = inverseUnitCircleTransform(); + + if (line.isHorizontal()) { + // substitute y into the ellipse equation and solve + Coord y = line.initialPoint()[Y]; + std::vector<Coord> xs = solve_quadratic(A, B*y + D, C*y*y + E*y + F); + for (unsigned i = 0; i < xs.size(); ++i) { + Point p(xs[i], y); + result.push_back(ShapeIntersection(atan2(p * iuct), line.timeAt(p), p)); } - else - { - angle = M_PI/2; + return result; + } + if (line.isVertical()) { + // substitute y into the ellipse equation and solve + Coord x = line.initialPoint()[X]; + std::vector<Coord> ys = solve_quadratic(C, B*x + E, A*x*x + D*x + F); + for (unsigned i = 0; i < ys.size(); ++i) { + Point p(x, ys[i]); + result.push_back(ShapeIntersection(atan2(p * iuct), line.timeAt(p), p)); } - 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); + return result; } - std::vector<double> coeff = implicit_form_coefficients(); - Affine Q( coeff[0], coeff[1]/2, - coeff[1]/2, coeff[2], - 0, 0 ); + // generic case + Coord a, b, c; + line.coefficients(a, b, c); + + // y = -a/b x - C/B + // TODO: when is it better to substitute X? + Coord q = -a/b; + Coord r = -c/b; - 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; + // substitute that into the ellipse equation, making it quadratic in x + Coord I = A + B*q + C*q*q; // x^2 terms + Coord J = B*r + C*2*q*r + D + E*q; // x^1 terms + Coord K = C*r*r + E*r + F; // x^0 terms + std::vector<Coord> xs = solve_quadratic(I, J, K); - return e; + for (unsigned i = 0; i < xs.size(); ++i) { + Point p(xs[i], q*xs[i] + r); + result.push_back(ShapeIntersection(atan2(p * iuct), line.timeAt(p), p)); + } + return result; } -Ellipse::Ellipse(Geom::Circle const &c) +std::vector<ShapeIntersection> Ellipse::intersect(LineSegment const &seg) const +{ + // we simply re-use the procedure for lines and filter out + // results where the line time value is outside of the unit interval. + std::vector<ShapeIntersection> result = intersect(Line(seg)); + filter_line_segment_intersections(result); + return result; +} + +std::vector<ShapeIntersection> Ellipse::intersect(Ellipse const &other) const +{ + // handle degenerate cases first + if (ray(X) == 0 || ray(Y) == 0) { + + } + // intersection of two ellipses can be solved analytically. + // http://maptools.home.comcast.net/~maptools/BivariateQuadratics.pdf + + Coord A, B, C, D, E, F; + Coord a, b, c, d, e, f; + + // NOTE: the order of coefficients is different to match the convention in the PDF above + // Ax^2 + Bx^2 + Cx + Dy + Exy + F + this->coefficients(A, E, B, C, D, F); + other.coefficients(a, e, b, c, d, f); + + // Assume that Q is the ellipse equation given by uppercase letters + // and R is the equation given by lowercase ones. An intersection exists when + // there is a coefficient mu such that + // mu Q + R = 0 + // + // This can be written in the following way: + // + // | ff cc/2 dd/2 | |1| + // mu Q + R = [1 x y] | cc/2 aa ee/2 | |x| = 0 + // | dd/2 ee/2 bb | |y| + // + // where aa = mu A + a and so on. The determinant can be explicitly written out, + // giving an equation which is cubic in mu and can be solved analytically. + + Coord I, J, K, L; + I = (-E*E*F + 4*A*B*F + C*D*E - A*D*D - B*C*C) / 4; + J = -((E*E - 4*A*B) * f + (2*E*F - C*D) * e + (2*A*D - C*E) * d + + (2*B*C - D*E) * c + (C*C - 4*A*F) * b + (D*D - 4*B*F) * a) / 4; + K = -((e*e - 4*a*b) * F + (2*e*f - c*d) * E + (2*a*d - c*e) * D + + (2*b*c - d*e) * C + (c*c - 4*a*f) * B + (d*d - 4*b*f) * A) / 4; + L = (-e*e*f + 4*a*b*f + c*d*e - a*d*d - b*c*c) / 4; + + std::vector<Coord> mus = solve_cubic(I, J, K, L); + Coord mu = infinity(); + std::vector<ShapeIntersection> result; + + // Now that we have solved for mu, we need to check whether the conic + // determined by mu Q + R is reducible to a product of two lines. If it's not, + // it means that there are no intersections. If it is, the intersections of these + // lines with the original ellipses (if there are any) give the coordinates + // of intersections. + + // Prefer middle root if there are three. + // Out of three possible pairs of lines that go through four points of intersection + // of two ellipses, this corresponds to cross-lines. These intersect the ellipses + // at less shallow angles than the other two options. + if (mus.size() == 3) { + std::swap(mus[1], mus[0]); + } + for (unsigned i = 0; i < mus.size(); ++i) { + Coord aa = mus[i] * A + a; + Coord bb = mus[i] * B + b; + Coord ee = mus[i] * E + e; + Coord delta = ee*ee - 4*aa*bb; + if (delta < 0) continue; + mu = mus[i]; + break; + } + + // if no suitable mu was found, there are no intersections + if (mu == infinity()) return result; + + Coord aa = mu * A + a; + Coord bb = mu * B + b; + Coord cc = mu * C + c; + Coord dd = mu * D + d; + Coord ee = mu * E + e; + Coord ff = mu * F + f; + + Line lines[2]; + + if (aa != 0) { + bb /= aa; cc /= aa; dd /= aa; ee /= aa; /*ff /= aa;*/ + Coord s = (ee + std::sqrt(ee*ee - 4*bb)) / 2; + Coord q = ee - s; + Coord alpha = (dd - cc*q) / (s - q); + Coord beta = cc - alpha; + + lines[0] = Line(1, q, alpha); + lines[1] = Line(1, s, beta); + } else if (bb != 0) { + cc /= bb; /*dd /= bb;*/ ee /= bb; ff /= bb; + Coord s = ee; + Coord q = 0; + Coord alpha = cc / ee; + Coord beta = ff * ee / cc; + + lines[0] = Line(q, 1, alpha); + lines[1] = Line(s, 1, beta); + } else { + lines[0] = Line(ee, 0, dd); + lines[1] = Line(0, 1, cc/ee); + } + + // intersect with the obtained lines and report intersections + for (unsigned li = 0; li < 2; ++li) { + std::vector<ShapeIntersection> as = intersect(lines[li]); + std::vector<ShapeIntersection> bs = other.intersect(lines[li]); + + if (!as.empty() && as.size() == bs.size()) { + for (unsigned i = 0; i < as.size(); ++i) { + ShapeIntersection ix(as[i].first, bs[i].first, + middle_point(as[i].point(), bs[i].point())); + result.push_back(ix); + } + } + } + return result; +} + +std::vector<ShapeIntersection> Ellipse::intersect(D2<Bezier> const &b) const +{ + Coord A, B, C, D, E, F; + coefficients(A, B, C, D, E, F); + + Bezier x = A*b[X]*b[X] + B*b[X]*b[Y] + C*b[Y]*b[Y] + D*b[X] + E*b[Y] + F; + std::vector<Coord> r = x.roots(); + + std::vector<ShapeIntersection> result; + for (unsigned i = 0; i < r.size(); ++i) { + Point p = b.valueAt(r[i]); + result.push_back(ShapeIntersection(timeAt(p), r[i], p)); + } + return result; +} + +bool Ellipse::operator==(Ellipse const &other) const +{ + if (_center != other._center) return false; + + Ellipse a = this->canonicalForm(); + Ellipse b = other.canonicalForm(); + + if (a._rays != b._rays) return false; + if (a._angle != b._angle) return false; + + return true; +} + + +bool are_near(Ellipse const &a, Ellipse const &b, Coord precision) +{ + // We want to know whether no point on ellipse a is further than precision + // from the corresponding point on ellipse b. To check this, we compute + // the four extreme points at the end of each ray for each ellipse + // and check whether they are sufficiently close. + + // First, we need to correct the angles on the ellipses, so that they are + // no further than M_PI/4 apart. This can always be done by rotating + // and exchanging axes. + Ellipse ac = a, bc = b; + if (distance(ac.rotationAngle(), bc.rotationAngle()).radians0() >= M_PI/2) { + ac.setRotationAngle(ac.rotationAngle() + M_PI); + } + if (distance(ac.rotationAngle(), bc.rotationAngle()) >= M_PI/4) { + Angle d1 = distance(ac.rotationAngle() + M_PI/2, bc.rotationAngle()); + Angle d2 = distance(ac.rotationAngle() - M_PI/2, bc.rotationAngle()); + Coord adj = d1.radians0() < d2.radians0() ? M_PI/2 : -M_PI/2; + ac.setRotationAngle(ac.rotationAngle() + adj); + ac.setRays(ac.ray(Y), ac.ray(X)); + } + + // Do the actual comparison by computing four points on each ellipse. + Point tps[] = {Point(1,0), Point(0,1), Point(-1,0), Point(0,-1)}; + for (unsigned i = 0; i < 4; ++i) { + if (!are_near(tps[i] * ac.unitCircleTransform(), + tps[i] * bc.unitCircleTransform(), + precision)) + return false; + } + return true; +} + +std::ostream &operator<<(std::ostream &out, Ellipse const &e) { - m_centre = c.center(); - m_ray[X] = m_ray[Y] = c.ray(); + out << "Ellipse(" << e.center() << ", " << e.rays() + << ", " << format_coord_nice(e.rotationAngle()) << ")"; + return out; } } // end namespace Geom diff --git a/src/2geom/ellipse.h b/src/2geom/ellipse.h index c971c6065..4b1701cce 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,211 @@ */ -#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/bezier-curve.h> #include <2geom/exception.h> -#include <2geom/affine.h> +#include <2geom/forward.h> +#include <2geom/line.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. + * + * An ellipse can be specified in several ways. Internally, 2Geom uses + * the SVG style representation: center, rays and angle between the +X ray + * and the +X axis. Another popular way is to use an implicit equation, + * which is as follows: + * \f$Ax^2 + Bxy + Cy^2 + Dx + Ey + F = 0\f$ + * + * @ingroup Shapes */ class Ellipse + : boost::multipliable< Ellipse, Translate + , boost::multipliable< Ellipse, Scale + , boost::multipliable< Ellipse, Rotate + , boost::multipliable< Ellipse, Zoom + , boost::multipliable< Ellipse, Affine + , boost::equality_comparable< Ellipse + > > > > > > { - 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) - { + 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); } + /// Construct ellipse from a circle. + Ellipse(Geom::Circle const &c); - // 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); + /// Set center, rays and angle. + void set(Point const &c, Point const &r, Coord angle) { + _center = c; + _rays = r; + _angle = angle; } - - Ellipse(std::vector<Point> const& points) - { - set(points); + /// Set center, rays and angle as constituent values. + 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; } - - 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; + /// Set an ellipse by solving its implicit equation. + void setCoefficients(double A, double B, double C, double D, double E, double F); + /// Set the center. + void setCenter(Point const &p) { _center = p; } + /// Set the center by coordinates. + void setCenter(Coord cx, Coord cy) { _center[X] = cx; _center[Y] = cy; } + /// Set both rays of the ellipse. + void setRays(Point const &p) { _rays = p; } + /// Set both rays of the ellipse as coordinates. + void setRays(Coord x, Coord y) { _rays[X] = x; _rays[Y] = y; } + /// Set one of the rays of the ellipse. + void setRay(Coord r, Dim2 d) { _rays[d] = r; } + /// Set the angle the X ray makes with the +X axis. + void setRotationAngle(Angle a) { _angle = a; } + + Point center() const { return _center; } + Coord center(Dim2 d) const { return _center[d]; } + /// Get both rays as a point. + Point rays() const { return _rays; } + /// Get one ray of the ellipse. + Coord ray(Dim2 d) const { return _rays[d]; } + /// Get the angle the X ray makes with the +X axis. + Angle rotationAngle() const { return _angle; } + /// Get the point corresponding to the +X ray of the ellipse. + Point initialPoint() const; + /// Get the point corresponding to the +X ray of the ellipse. + Point finalPoint() const { return initialPoint(); } + + /** @brief Create an ellipse passing through the specified points + * At least five points have to be specified. */ + void fit(std::vector<Point> const& points); + + /** @brief Create an elliptical arc from a section of the ellipse. + * This is mainly useful to determine the flags of the new arc. + * The passed points should lie on the ellipse, otherwise the results + * will be undefined. + * @param ip Initial point of the arc + * @param inner Point in the middle of the arc, used to pick one of two possibilities + * @param fp Final point of the arc + * @return Newly allocated arc, delete when no longer used */ + EllipticalArc *arc(Point const &ip, Point const &inner, Point const &fp); + + /** @brief Return an ellipse with less degrees of freedom. + * The canonical form always has the angle less than \f$\frac{\pi}{2}\f$, + * and zero if the rays are equal (i.e. the ellipse is a circle). */ + Ellipse canonicalForm() const; + void makeCanonical(); + + /** @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; + /** @brief Compute the transform that maps this ellipse to the unit circle. + * This may be a little more precise and/or faster than simply using + * unitCircleTransform().inverse(). An exception will be thrown for + * degenerate ellipses. */ + Affine inverseUnitCircleTransform() const; + + LineSegment majorAxis() const { return ray(X) >= ray(Y) ? axis(X) : axis(Y); } + LineSegment minorAxis() const { return ray(X) < ray(Y) ? axis(X) : axis(Y); } + LineSegment semimajorAxis(int sign = 1) const { + return ray(X) >= ray(Y) ? semiaxis(X, sign) : semiaxis(Y, sign); } - - // 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); - - // 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); - - EllipticalArc * - arc(Point const& initial, Point const& inner, Point const& final, bool svg_compliant = true); - - Point center() const - { - return m_centre; + LineSegment semiminorAxis(int sign = 1) const { + return ray(X) < ray(Y) ? semiaxis(X, sign) : semiaxis(Y, sign); } - - Coord center(Dim2 d) const - { - return m_centre[d]; + LineSegment axis(Dim2 d) const; + LineSegment semiaxis(Dim2 d, int sign = 1) const; + + /// Get the coefficients of the ellipse's implicit equation. + std::vector<double> coefficients() const; + void coefficients(Coord &A, Coord &B, Coord &C, Coord &D, Coord &E, Coord &F) const; + + /** @brief Evaluate a point on the ellipse. + * The parameter range is \f$[0, 2\pi)\f$; larger and smaller values + * wrap around. */ + Point pointAt(Coord t) const; + /// Evaluate a single coordinate of a point on the ellipse. + Coord valueAt(Coord t, Dim2 d) const; + + /** @brief Find the time value of a point on an ellipse. + * If the point is not on the ellipse, the returned time value will correspond + * to an intersection with a ray from the origin passing through the point + * with the ellipse. Note that this is NOT the nearest point on the ellipse. */ + Coord timeAt(Point const &p) const; + + /// Get the value of the derivative at time t normalized to unit length. + Point unitTangentAt(Coord t) const; + + /// Check whether the ellipse contains the given point. + bool contains(Point const &p) const; + + /// Compute intersections with an infinite line. + std::vector<ShapeIntersection> intersect(Line const &line) const; + /// Compute intersections with a line segment. + std::vector<ShapeIntersection> intersect(LineSegment const &seg) const; + /// Compute intersections with another ellipse. + std::vector<ShapeIntersection> intersect(Ellipse const &other) const; + /// Compute intersections with a 2D Bezier polynomial. + std::vector<ShapeIntersection> intersect(D2<Bezier> const &other) const; + + Ellipse &operator*=(Translate const &t) { + _center *= t; + return *this; } - - Coord ray(Dim2 d) const - { - return m_ray[d]; + Ellipse &operator*=(Scale const &s) { + _center *= s; + _rays *= s; + return *this; } - - Coord rot_angle() const - { - return m_angle; + Ellipse &operator*=(Zoom const &z) { + _center *= z; + _rays *= z.scale(); + return *this; } + Ellipse &operator*=(Rotate const &r); + Ellipse &operator*=(Affine const &m); - std::vector<double> implicit_form_coefficients() const; - - Ellipse transformed(Affine const& m) const; - - private: - Point m_centre, m_ray; - double m_angle; + /// Compare ellipses for exact equality. + bool operator==(Ellipse const &other) const; }; +/** @brief Test whether two ellipses are approximately the same. + * This will check whether no point on ellipse a is further away from + * the corresponding point on ellipse b than precision. + * @relates Ellipse */ +bool are_near(Ellipse const &a, Ellipse const &b, Coord precision = EPSILON); -} // end namespace Geom - +/** @brief Outputs ellipse data, useful for debugging. + * @relates Ellipse */ +std::ostream &operator<<(std::ostream &out, Ellipse const &e); +} // end namespace Geom -#endif // _2GEOM_ELLIPSE_H_ - +#endif // LIB2GEOM_SEEN_ELLIPSE_H /* Local Variables: diff --git a/src/2geom/svg-elliptical-arc.cpp b/src/2geom/elliptical-arc-from-sbasis.cpp index 96a4f99d8..54f995fb6 100644 --- a/src/2geom/svg-elliptical-arc.cpp +++ b/src/2geom/elliptical-arc-from-sbasis.cpp @@ -1,6 +1,8 @@ -/* - * SVG Elliptical Arc Class +/** @file + * @brief Fitting elliptical arc to SBasis * + * This file contains the implementation of the function arc_from_sbasis. + *//* * Copyright 2008 Marco Cecchetti <mrcekets at gmail.com> * * This library is free software; you can redistribute it and/or @@ -27,33 +29,120 @@ * the specific language governing rights and limitations. */ - +#include <2geom/curve.h> +#include <2geom/angle.h> +#include <2geom/utils.h> #include <2geom/bezier-curve.h> -#include <2geom/ellipse.h> -#include <2geom/numeric/fitting-model.h> -#include <2geom/numeric/fitting-tool.h> +#include <2geom/elliptical-arc.h> +#include <2geom/sbasis-curve.h> // for non-native methods #include <2geom/numeric/vector.h> -#include <2geom/poly.h> -#include <2geom/sbasis-geometric.h> -#include <2geom/svg-elliptical-arc.h> - -#include <cfloat> -#include <limits> -#include <memory> +#include <2geom/numeric/fitting-tool.h> +#include <2geom/numeric/fitting-model.h> +#include <algorithm> +namespace Geom { -namespace Geom +// forward declation +namespace detail { + struct ellipse_equation; +} -/** - * @class SVGEllipticalArc - * @brief SVG 1.1-compliant elliptical arc. +/* + * make_elliptical_arc * - * This class is almost identical to the normal elliptical arc, but it differs slightly - * in the handling of degenerate arcs to be compliant with SVG 1.1 implementation guidelines. + * convert a parametric polynomial curve given in symmetric power basis form + * into an EllipticalArc type; in order to be successfull the input curve + * has to look like an actual elliptical arc even if a certain tolerance + * is allowed through an ad-hoc parameter. + * The conversion is performed through an interpolation on a certain amount of + * sample points computed on the input curve; + * the interpolation computes the coefficients of the general implicit equation + * of an ellipse (A*X^2 + B*XY + C*Y^2 + D*X + E*Y + F = 0), then from the + * implicit equation we compute the parametric form. * - * @ingroup Curves */ +class make_elliptical_arc +{ + public: + typedef D2<SBasis> curve_type; + + /* + * constructor + * + * it doesn't execute the conversion but set the input and output parameters + * + * _ea: the output EllipticalArc that will be generated; + * _curve: the input curve to be converted; + * _total_samples: the amount of sample points to be taken + * on the input curve for performing the conversion + * _tolerance: how much likelihood is required between the input curve + * and the generated elliptical arc; the smaller it is the + * the tolerance the higher it is the likelihood. + */ + make_elliptical_arc( EllipticalArc& _ea, + curve_type const& _curve, + unsigned int _total_samples, + double _tolerance ); + + private: + bool bound_exceeded( unsigned int k, detail::ellipse_equation const & ee, + double e1x, double e1y, double e2 ); + + bool check_bound(double A, double B, double C, double D, double E, double F); + + void fit(); + + bool make_elliptiarc(); + + void print_bound_error(unsigned int k) + { + std::cerr + << "tolerance error" << std::endl + << "at point: " << k << std::endl + << "error value: "<< dist_err << std::endl + << "bound: " << dist_bound << std::endl + << "angle error: " << angle_err + << " (" << angle_tol << ")" << std::endl; + } + + public: + /* + * perform the actual conversion + * return true if the conversion is successfull, false on the contrary + */ + bool operator()() + { + // initialize the reference + const NL::Vector & coeff = fitter.result(); + fit(); + if ( !check_bound(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]) ) + return false; + if ( !(make_elliptiarc()) ) return false; + return true; + } + + private: + EllipticalArc& ea; // output elliptical arc + const curve_type & curve; // input curve + Piecewise<D2<SBasis> > dcurve; // derivative of the input curve + NL::LFMEllipse model; // model used for fitting + // perform the actual fitting task + NL::least_squeares_fitter<NL::LFMEllipse> fitter; + // tolerance: the user-defined tolerance parameter; + // tol_at_extr: the tolerance at end-points automatically computed + // on the value of "tolerance", and usually more strict; + // tol_at_center: tolerance at the center of the ellipse + // angle_tol: tolerance for the angle btw the input curve tangent + // versor and the ellipse normal versor at the sample points + double tolerance, tol_at_extr, tol_at_center, angle_tol; + Point initial_point, final_point; // initial and final end-points + unsigned int N; // total samples + unsigned int last; // N-1 + double partitions; // N-1 + std::vector<Point> p; // sample points + double dist_err, dist_bound, angle_err; +}; namespace detail { @@ -97,7 +186,7 @@ struct ellipse_equation double A, B, C, D, E, F; }; -} +} // end namespace detail make_elliptical_arc:: make_elliptical_arc( EllipticalArc& _ea, @@ -110,8 +199,7 @@ make_elliptical_arc( EllipticalArc& _ea, tolerance(_tolerance), tol_at_extr(tolerance/2), tol_at_center(0.1), angle_tol(0.1), initial_point(curve.at0()), final_point(curve.at1()), - N(_total_samples), last(N-1), partitions(N-1), p(N), - svg_compliant(true) + N(_total_samples), last(N-1), partitions(N-1), p(N) { } @@ -207,7 +295,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) { @@ -216,33 +304,12 @@ bool make_elliptical_arc::make_elliptiarc() Point inner_point = curve(0.5); - if (svg_compliant_flag()) - { -#ifdef CPP11 - std::unique_ptr<EllipticalArc> arc( e.arc(initial_point, inner_point, final_point, true) ); -#else - std::auto_ptr<EllipticalArc> arc( e.arc(initial_point, inner_point, final_point, true) ); -#endif - ea = *arc; - } - else - { - try - { #ifdef CPP11 - std::unique_ptr<EllipticalArc> + std::unique_ptr<EllipticalArc> arc( e.arc(initial_point, inner_point, final_point) ); #else - std::auto_ptr<EllipticalArc> + std::auto_ptr<EllipticalArc> arc( e.arc(initial_point, inner_point, final_point) ); #endif - eap( e.arc(initial_point, inner_point, final_point, false) ); - ea = *eap; - } - catch(RangeError const &exc) - { - return false; - } - } - + ea = *arc; if ( !are_near( e.center(), ea.center(), @@ -257,10 +324,14 @@ bool make_elliptical_arc::make_elliptiarc() -} // end namespace Geom - - +bool arc_from_sbasis(EllipticalArc &ea, D2<SBasis> const &in, + double tolerance, unsigned num_samples) +{ + make_elliptical_arc convert(ea, in, num_samples, tolerance); + return convert(); +} +} // end namespace Geom /* Local Variables: @@ -272,4 +343,3 @@ bool make_elliptical_arc::make_elliptiarc() End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : - diff --git a/src/2geom/elliptical-arc.cpp b/src/2geom/elliptical-arc.cpp index c96d5f1a6..25a78b4ad 100644 --- a/src/2geom/elliptical-arc.cpp +++ b/src/2geom/elliptical-arc.cpp @@ -34,11 +34,11 @@ #include <limits> #include <memory> -#include <2geom/elliptical-arc.h> +#include <2geom/bezier-curve.h> #include <2geom/ellipse.h> +#include <2geom/elliptical-arc.h> +#include <2geom/path-sink.h> #include <2geom/sbasis-geometric.h> -#include <2geom/bezier-curve.h> -#include <2geom/poly.h> #include <2geom/transforms.h> #include <2geom/utils.h> @@ -46,7 +46,6 @@ #include <2geom/numeric/fitting-tool.h> #include <2geom/numeric/fitting-model.h> - namespace Geom { @@ -84,18 +83,27 @@ namespace Geom * and the ellipse's rotation. Each set of those parameters corresponds to four different arcs, * with two of them larger than half an ellipse and two of them turning clockwise while traveling * from initial to final point. The two flags disambiguate between them: "large arc flag" selects - * the bigger arc, while the "sweep flag" selects the clockwise arc. + * the bigger arc, while the "sweep flag" selects the arc going in the direction of positive + * angles. Angles always increase when going from the +X axis in the direction of the +Y axis, + * so if Y grows downwards, this means clockwise. * - * @image html elliptical-arc-flags.png "The four possible arcs and the meaning of flags" + * @image html elliptical-arc-flags.png "Meaning of arc flags (Y grows downwards)" * * @ingroup Curves */ Rect EllipticalArc::boundsExact() const { + if (isChord()) { + return chord().boundsExact(); + } + + using std::swap; + + // TODO: simplify / document what is going on here. double extremes[4]; double sinrot, cosrot; - sincos(_rot_angle, sinrot, cosrot); + sincos(rotationAngle(), sinrot, cosrot); extremes[0] = std::atan2( -ray(Y) * sinrot, ray(X) * cosrot ); extremes[1] = extremes[0] + M_PI; @@ -109,11 +117,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) { @@ -130,37 +138,23 @@ Rect EllipticalArc::boundsExact() const Point EllipticalArc::pointAtAngle(Coord t) const { - Point ret = Point::polar(t) * unitCircleTransform(); + Point ret = _ellipse.pointAt(t); return ret; } Coord EllipticalArc::valueAtAngle(Coord t, Dim2 d) const { - Coord sinrot, cosrot, cost, sint; - sincos(_rot_angle, sinrot, cosrot); - sincos(t, sint, cost); - - if ( d == X ) { - return ray(X) * cosrot * cost - - ray(Y) * sinrot * sint - + center(X); - } else { - return ray(X) * sinrot * cost - + ray(Y) * cosrot * sint - + center(Y); - } -} - -Affine EllipticalArc::unitCircleTransform() const -{ - Affine ret = Scale(ray(X), ray(Y)) * Rotate(_rot_angle); - ret.setTranslation(center()); - return ret; + return _ellipse.valueAt(t, d); } std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const { + if (isChord()) { + return chord().roots(v, d); + } + std::vector<Coord> sol; + Interval unit_interval(0, 1); if ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) { if ( center(d) == v ) @@ -188,7 +182,7 @@ std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const for ( unsigned int dim = 0; dim < 2; ++dim ) { - if ( are_near(ray((Dim2) dim), 0) ) + if (ray((Dim2) dim) == 0) { if ( initialPoint()[d] == v && finalPoint()[d] == v ) { @@ -210,18 +204,18 @@ std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const case X: switch(dim) { - case X: ray_prj = -ray(Y) * std::sin(_rot_angle); + case X: ray_prj = -ray(Y) * std::sin(rotationAngle()); break; - case Y: ray_prj = ray(X) * std::cos(_rot_angle); + case Y: ray_prj = ray(X) * std::cos(rotationAngle()); break; } break; case Y: switch(dim) { - case X: ray_prj = ray(Y) * std::cos(_rot_angle); + case X: ray_prj = ray(Y) * std::cos(rotationAngle()); break; - case Y: ray_prj = ray(X) * std::sin(_rot_angle); + case Y: ray_prj = ray(X) * std::sin(rotationAngle()); break; } break; @@ -236,7 +230,7 @@ std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const { case X: s = std::asin(s); // return a value in [-PI/2,PI/2] - if ( logical_xor( _sweep, are_near(initialAngle(), M_PI/2) ) ) + if ( logical_xor( sweep(), are_near(initialAngle(), M_PI/2) ) ) { if ( s < 0 ) s += 2*M_PI; } @@ -248,7 +242,7 @@ std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const break; case Y: s = std::acos(s); // return a value in [0,PI] - if ( logical_xor( _sweep, are_near(initialAngle(), 0) ) ) + if ( logical_xor( sweep(), are_near(initialAngle(), 0) ) ) { s = 2*M_PI - s; if ( !(s < 2*M_PI) ) s -= 2*M_PI; @@ -257,17 +251,22 @@ std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const } //std::cerr << "s = " << rad_to_deg(s); - s = map_to_01(s); + s = timeAtAngle(s); //std::cerr << " -> t: " << s << std::endl; - if ( !(s < 0 || s > 1) ) + if (unit_interval.contains(s)) { sol.push_back(s); + } return sol; } } 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(rotationAngle(), roty, rotx); + roty = -roty; + } else { + sincos(rotationAngle(), rotx, roty); + } double rxrotx = ray(X) * rotx; double c_v = center(d) - v; @@ -312,13 +311,13 @@ std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const } std::vector<double> arc_sol; - for (unsigned int i = 0; i < sol.size(); ++i ) - { + for (unsigned int i = 0; i < sol.size(); ++i ) { //std::cerr << "s = " << rad_to_deg(sol[i]); - sol[i] = map_to_01(sol[i]); + sol[i] = timeAtAngle(sol[i]); //std::cerr << " -> t: " << sol[i] << std::endl; - if ( !(sol[i] < 0 || sol[i] > 1) ) + if (unit_interval.contains(sol[i])) { arc_sol.push_back(sol[i]); + } } return arc_sol; } @@ -330,18 +329,14 @@ std::vector<Coord> EllipticalArc::roots(Coord v, Dim2 d) const // of such an angle in the cw direction Curve *EllipticalArc::derivative() const { - EllipticalArc *result = static_cast<EllipticalArc*>(duplicate()); - result->_center[X] = result->_center[Y] = 0; - result->_start_angle += M_PI/2; - if( !( result->_start_angle < 2*M_PI ) ) - { - result->_start_angle -= 2*M_PI; - } - result->_end_angle += M_PI/2; - if( !( result->_end_angle < 2*M_PI ) ) - { - result->_end_angle -= 2*M_PI; + if (isChord()) { + return chord().derivative(); } + + EllipticalArc *result = static_cast<EllipticalArc*>(duplicate()); + result->_ellipse.setCenter(0, 0); + result->_angles.setInitial(result->_angles.initialAngle() + M_PI/2); + result->_angles.setFinal(result->_angles.finalAngle() + M_PI/2); result->_initial_point = result->pointAtAngle( result->initialAngle() ); result->_final_point = result->pointAtAngle( result->finalAngle() ); return result; @@ -351,18 +346,21 @@ Curve *EllipticalArc::derivative() const std::vector<Point> EllipticalArc::pointAndDerivatives(Coord t, unsigned int n) const { + if (isChord()) { + return chord().pointAndDerivatives(t, n); + } + unsigned int nn = n+1; // nn represents the size of the result vector. std::vector<Point> result; result.reserve(nn); - double angle = map_unit_interval_on_circular_arc(t, initialAngle(), - finalAngle(), _sweep); + double angle = angleAt(t); std::auto_ptr<EllipticalArc> ea( static_cast<EllipticalArc*>(duplicate()) ); - ea->_center = Point(0,0); + ea->_ellipse.setCenter(0, 0); unsigned int m = std::min(nn, 4u); for ( unsigned int i = 0; i < m; ++i ) { result.push_back( ea->pointAtAngle(angle) ); - angle += (_sweep ? M_PI/2 : -M_PI/2); + angle += (sweep() ? M_PI/2 : -M_PI/2); if ( !(angle < 2*M_PI) ) angle -= 2*M_PI; } m = nn / 4; @@ -381,9 +379,16 @@ EllipticalArc::pointAndDerivatives(Coord t, unsigned int n) const return result; } -bool EllipticalArc::containsAngle(Coord angle) const +Point EllipticalArc::pointAt(Coord t) const { - return AngleInterval::contains(angle); + if (isChord()) return chord().pointAt(t); + return _ellipse.pointAt(angleAt(t)); +} + +Coord EllipticalArc::valueAt(Coord t, Dim2 d) const +{ + if (isChord()) return chord().valueAt(t, d); + return valueAtAngle(angleAt(t), d); } Curve* EllipticalArc::portion(double f, double t) const @@ -394,41 +399,35 @@ Curve* EllipticalArc::portion(double f, double t) const if (t < 0) t = 0; if (t > 1) t = 1; - if ( are_near(f, t) ) - { - EllipticalArc *arc = static_cast<EllipticalArc*>(duplicate()); - arc->_center = arc->_initial_point = arc->_final_point = pointAt(f); - arc->_start_angle = arc->_end_angle = _start_angle; - arc->_rot_angle = _rot_angle; - arc->_sweep = _sweep; - arc->_large_arc = _large_arc; + if (f == t) { + EllipticalArc *arc = new EllipticalArc(); + arc->_initial_point = arc->_final_point = pointAt(f); return arc; } EllipticalArc *arc = static_cast<EllipticalArc*>(duplicate()); arc->_initial_point = pointAt(f); arc->_final_point = pointAt(t); - if ( f > t ) arc->_sweep = !_sweep; - if ( _large_arc && fabs(sweepAngle() * (t-f)) < M_PI) + arc->_angles.setAngles(angleAt(f), angleAt(t)); + if (f > t) arc->_angles.setSweep(!sweep()); + if ( _large_arc && fabs(angularExtent() * (t-f)) <= M_PI) { arc->_large_arc = false; - arc->_updateCenterAndAngles(arc->isSVGCompliant()); //TODO: be more clever + } return arc; } // the arc is the same but traversed in the opposite direction -Curve *EllipticalArc::reverse() const { +Curve *EllipticalArc::reverse() const +{ + using std::swap; EllipticalArc *rarc = static_cast<EllipticalArc*>(duplicate()); - rarc->_sweep = !_sweep; - rarc->_initial_point = _final_point; - rarc->_final_point = _initial_point; - rarc->_start_angle = _end_angle; - rarc->_end_angle = _start_angle; - rarc->_updateCenterAndAngles(rarc->isSVGCompliant()); + rarc->_angles.reverse(); + swap(rarc->_initial_point, rarc->_final_point); return rarc; } #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,11 +445,11 @@ 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) - || are_near(_rot_angle, 3*M_PI/2) ) + if ( are_near(rotationAngle(), M_PI/2) + || are_near(rotationAngle(), 3*M_PI/2) ) { result = roots(np[Y], Y); } @@ -461,8 +460,8 @@ std::vector<double> EllipticalArc::allNearestPoints( Point const& p, double from } else { - if ( are_near(_rot_angle, M_PI/2) - || are_near(_rot_angle, 3*M_PI/2) ) + if ( are_near(rotationAngle(), M_PI/2) + || are_near(rotationAngle(), 3*M_PI/2) ) { result = roots(np[X], X); } @@ -521,7 +520,7 @@ std::vector<double> EllipticalArc::allNearestPoints( Point const& p, double from Point p_c = p - center(); double rx2_ry2 = (ray(X) - ray(Y)) * (ray(X) + ray(Y)); double sinrot, cosrot; - sincos(_rot_angle, sinrot, cosrot); + sincos(rotationAngle(), sinrot, cosrot); double expr1 = ray(X) * (p_c[X] * cosrot + p_c[Y] * sinrot); Poly coeff; coeff.resize(5); @@ -570,7 +569,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 ) { @@ -589,14 +588,14 @@ std::vector<double> EllipticalArc::allNearestPoints( Point const& p, double from } } - double t = map_to_01( real_sol[mi1] ); + double t = timeAtAngle(real_sol[mi1]); if ( !(t < from || t > to) ) { result.push_back(t); } bool second_sol = false; - t = map_to_01( real_sol[mi2] ); + t = timeAtAngle(real_sol[mi2]); if ( real_sol.size() == 4 && !(t < from || t > to) ) { if ( result.empty() || are_near(mindistsq1, mindistsq2) ) @@ -656,227 +655,155 @@ std::vector<double> EllipticalArc::allNearestPoints( Point const& p, double from } #endif -/* - * NOTE: this implementation follows Standard SVG 1.1 implementation guidelines - * for elliptical arc curves. See Appendix F.6. - */ -void EllipticalArc::_updateCenterAndAngles(bool svg) -{ - Point d = initialPoint() - finalPoint(); - // TODO move this to SVGElipticalArc? - if (svg) - { - if ( initialPoint() == finalPoint() ) - { - _rot_angle = _start_angle = _end_angle = 0; - _center = initialPoint(); - _rays = Geom::Point(0,0); - _large_arc = _sweep = false; - return; +void EllipticalArc::_filterIntersections(std::vector<ShapeIntersection> &xs, bool is_first) const +{ + Interval unit(0, 1); + std::vector<ShapeIntersection>::reverse_iterator i = xs.rbegin(), last = xs.rend(); + while (i != last) { + Coord &t = is_first ? i->first : i->second; + assert(are_near(_ellipse.pointAt(t), i->point(), 1e-6)); + t = timeAtAngle(t); + if (!unit.contains(t)) { + xs.erase((++i).base()); + continue; + } else { + assert(are_near(pointAt(t), i->point(), 1e-6)); + ++i; } + } +} - _rays[X] = std::fabs(_rays[X]); - _rays[Y] = std::fabs(_rays[Y]); - - if ( are_near(ray(X), 0) || are_near(ray(Y), 0) ) - { - _rays[X] = L2(d) / 2; - _rays[Y] = 0; - _rot_angle = std::atan2(d[Y], d[X]); - _start_angle = 0; - _end_angle = M_PI; - _center = middle_point(initialPoint(), finalPoint()); - _large_arc = false; - _sweep = false; - return; - } +std::vector<CurveIntersection> EllipticalArc::intersect(Curve const &other, Coord eps) const +{ + if (isLineSegment()) { + LineSegment ls(_initial_point, _final_point); + return ls.intersect(other, eps); } - else - { - if ( are_near(initialPoint(), finalPoint()) ) - { - if ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) - { - _start_angle = _end_angle = 0; - _center = initialPoint(); - return; - } - else - { - THROW_RANGEERROR("initial and final point are the same"); - } - } - if ( are_near(ray(X), 0) && are_near(ray(Y), 0) ) - { // but initialPoint != finalPoint - THROW_RANGEERROR( - "there is no ellipse that satisfies the given constraints: " - "ray(X) == 0 && ray(Y) == 0 but initialPoint != finalPoint" - ); - } - if ( are_near(ray(Y), 0) ) - { - Point v = initialPoint() - finalPoint(); - if ( are_near(L2sq(v), 4*ray(X)*ray(X)) ) - { - Angle angle(v); - if ( are_near( angle, _rot_angle ) ) - { - _start_angle = 0; - _end_angle = M_PI; - _center = v/2 + finalPoint(); - return; - } - angle -= M_PI; - if ( are_near( angle, _rot_angle ) ) - { - _start_angle = M_PI; - _end_angle = 0; - _center = v/2 + finalPoint(); - return; - } - THROW_RANGEERROR( - "there is no ellipse that satisfies the given constraints: " - "ray(Y) == 0 " - "and slope(initialPoint - finalPoint) != rotation_angle " - "and != rotation_angle + PI" - ); - } - if ( L2sq(v) > 4*ray(X)*ray(X) ) - { - THROW_RANGEERROR( - "there is no ellipse that satisfies the given constraints: " - "ray(Y) == 0 and distance(initialPoint, finalPoint) > 2*ray(X)" - ); - } - else - { - THROW_RANGEERROR( - "there is infinite ellipses that satisfy the given constraints: " - "ray(Y) == 0 and distance(initialPoint, finalPoint) < 2*ray(X)" - ); - } - } + std::vector<CurveIntersection> result; - if ( are_near(ray(X), 0) ) - { - Point v = initialPoint() - finalPoint(); - if ( are_near(L2sq(v), 4*ray(Y)*ray(Y)) ) - { - double angle = std::atan2(v[Y], v[X]); - if (angle < 0) angle += 2*M_PI; - double rot_angle = _rot_angle.radians() + M_PI/2; - if ( !(rot_angle < 2*M_PI) ) rot_angle -= 2*M_PI; - if ( are_near( angle, rot_angle ) ) - { - _start_angle = M_PI/2; - _end_angle = 3*M_PI/2; - _center = v/2 + finalPoint(); - return; - } - angle -= M_PI; - if ( angle < 0 ) angle += 2*M_PI; - if ( are_near( angle, rot_angle ) ) - { - _start_angle = 3*M_PI/2; - _end_angle = M_PI/2; - _center = v/2 + finalPoint(); - return; - } - THROW_RANGEERROR( - "there is no ellipse that satisfies the given constraints: " - "ray(X) == 0 " - "and slope(initialPoint - finalPoint) != rotation_angle + PI/2 " - "and != rotation_angle + (3/2)*PI" - ); - } - if ( L2sq(v) > 4*ray(Y)*ray(Y) ) - { - THROW_RANGEERROR( - "there is no ellipse that satisfies the given constraints: " - "ray(X) == 0 and distance(initialPoint, finalPoint) > 2*ray(Y)" - ); - } - else - { - THROW_RANGEERROR( - "there is infinite ellipses that satisfy the given constraints: " - "ray(X) == 0 and distance(initialPoint, finalPoint) < 2*ray(Y)" - ); - } + if (other.isLineSegment()) { + LineSegment ls(other.initialPoint(), other.finalPoint()); + result = _ellipse.intersect(ls); + _filterIntersections(result, true); + return result; + } - } + BezierCurve const *bez = dynamic_cast<BezierCurve const *>(&other); + if (bez) { + result = _ellipse.intersect(bez->fragment()); + _filterIntersections(result, true); + return result; + } + EllipticalArc const *arc = dynamic_cast<EllipticalArc const *>(&other); + if (arc) { + result = _ellipse.intersect(arc->_ellipse); + _filterIntersections(result, true); + arc->_filterIntersections(result, false); + return result; } - Rotate rm(_rot_angle); - Affine m(rm); - m[1] = -m[1]; - m[2] = -m[2]; - - Point p = (d / 2) * m; - double rx2 = _rays[X] * _rays[X]; - double ry2 = _rays[Y] * _rays[Y]; - double rxpy = _rays[X] * p[Y]; - double rypx = _rays[Y] * p[X]; - double rx2py2 = rxpy * rxpy; - double ry2px2 = rypx * rypx; - double num = rx2 * ry2; - double den = rx2py2 + ry2px2; - assert(den != 0); - double rad = num / den; - Point c(0,0); - if (rad > 1) - { - rad -= 1; - rad = std::sqrt(rad); + // in case someone wants to make a custom curve type + result = other.intersect(*this, eps); + transpose_in_place(result); + return result; +} - if (_large_arc == _sweep) rad = -rad; - c = rad * Point(rxpy / ray(Y), -rypx / ray(X)); - _center = c * rm + middle_point(initialPoint(), finalPoint()); - } - else if (rad == 1 || svg) - { - double lamda = std::sqrt(1 / rad); - _rays[X] *= lamda; - _rays[Y] *= lamda; - _center = middle_point(initialPoint(), finalPoint()); - } - else - { - THROW_RANGEERROR( - "there is no ellipse that satisfies the given constraints" - ); +void EllipticalArc::_updateCenterAndAngles() +{ + // See: http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes + Point d = initialPoint() - finalPoint(); + Point mid = middle_point(initialPoint(), finalPoint()); + + // if ip = sp, the arc contains no other points + if (initialPoint() == finalPoint()) { + _ellipse = Ellipse(); + _ellipse.setCenter(initialPoint()); + _angles = AngleInterval(); + _large_arc = false; + return; + } + + // rays should be positive + _ellipse.setRays(std::fabs(ray(X)), std::fabs(ray(Y))); + + if (isChord()) { + _ellipse.setRays(L2(d) / 2, 0); + _ellipse.setRotationAngle(atan2(d)); + _ellipse.setCenter(mid); + _angles.setAngles(0, M_PI); + _angles.setSweep(false); + _large_arc = false; + return; + } + + Rotate rot(rotationAngle()); // the matrix in F.6.5.3 + Rotate invrot = rot.inverse(); // the matrix in F.6.5.1 + + Point r = rays(); + Point p = (initialPoint() - mid) * invrot; // x', y' in F.6.5.1 + Point c(0,0); // cx', cy' in F.6.5.2 + + // Correct out-of-range radii + Coord lambda = hypot(p[X]/r[X], p[Y]/r[Y]); + if (lambda > 1) { + r *= lambda; + _ellipse.setRays(r); + _ellipse.setCenter(mid); + } else { + // evaluate F.6.5.2 + Coord rxry = r[X]*r[X] * r[Y]*r[Y]; + Coord pxry = p[X]*p[X] * r[Y]*r[Y]; + Coord rxpy = r[X]*r[X] * p[Y]*p[Y]; + Coord rad = (rxry - pxry - rxpy)/(rxpy + pxry); + // normally rad should never be negative, but numerical inaccuracy may cause this + if (rad > 0) { + rad = std::sqrt(rad); + if (sweep() == _large_arc) { + rad = -rad; + } + c = rad * Point(r[X]*p[Y]/r[Y], -r[Y]*p[X]/r[X]); + _ellipse.setCenter(c * rot + mid); + } else { + _ellipse.setCenter(mid); + } } - Point sp((p[X] - c[X]) / ray(X), (p[Y] - c[Y]) / ray(Y)); - Point ep((-p[X] - c[X]) / ray(X), (-p[Y] - c[Y]) / ray(Y)); + // Compute start and end angles. + // If the ellipse was enlarged, c will be zero - this is correct. + Point sp((p[X] - c[X]) / r[X], (p[Y] - c[Y]) / r[Y]); + Point ep((-p[X] - c[X]) / r[X], (-p[Y] - c[Y]) / r[Y]); Point v(1, 0); - _start_angle = angle_between(v, sp); - double sweep_angle = angle_between(sp, ep); - if (!_sweep && sweep_angle > 0) sweep_angle -= 2*M_PI; - if (_sweep && sweep_angle < 0) sweep_angle += 2*M_PI; - _end_angle = _start_angle; - _end_angle += sweep_angle; + _angles.setInitial(angle_between(v, sp)); + _angles.setFinal(angle_between(v, ep)); + + /*double sweep_angle = angle_between(sp, ep); + if (!sweep() && sweep_angle > 0) sweep_angle -= 2*M_PI; + if (sweep() && sweep_angle < 0) sweep_angle += 2*M_PI;*/ } D2<SBasis> EllipticalArc::toSBasis() const { + if (isChord()) { + return chord().toSBasis(); + } + D2<SBasis> arc; // the interval of parametrization has to be [0,1] - Coord et = initialAngle().radians() + ( _sweep ? sweepAngle() : -sweepAngle() ); - Linear param(initialAngle(), et); - Coord cos_rot_angle, sin_rot_angle; - sincos(_rot_angle, sin_rot_angle, cos_rot_angle); + Coord et = initialAngle().radians() + sweepAngle(); + Linear param(initialAngle().radians(), et); + Coord cosrot, sinrot; + sincos(rotationAngle(), sinrot, cosrot); // order = 4 seems to be enough to get a perfect looking elliptical arc SBasis arc_x = ray(X) * cos(param,4); SBasis arc_y = ray(Y) * sin(param,4); - arc[0] = arc_x * cos_rot_angle - arc_y * sin_rot_angle + Linear(center(X),center(X)); - arc[1] = arc_x * sin_rot_angle + arc_y * cos_rot_angle + Linear(center(Y),center(Y)); + arc[0] = arc_x * cosrot - arc_y * sinrot + Linear(center(X), center(X)); + arc[1] = arc_x * sinrot + arc_y * cosrot + Linear(center(Y), center(Y)); // ensure that endpoints remain exact for ( int d = 0 ; d < 2 ; d++ ) { @@ -887,22 +814,96 @@ D2<SBasis> EllipticalArc::toSBasis() const return arc; } +// All operations that do not contain skew can be evaulated +// without passing through the implicit form of the ellipse, +// which preserves precision. -Curve *EllipticalArc::transformed(Affine const& m) const +void EllipticalArc::operator*=(Translate const &tr) { - Ellipse e(center(X), center(Y), ray(X), ray(Y), _rot_angle); - Ellipse et = e.transformed(m); - Point inner_point = pointAt(0.5); - return et.arc( initialPoint() * m, - inner_point * m, - finalPoint() * m, - isSVGCompliant() ); + _initial_point *= tr; + _final_point *= tr; + _ellipse *= tr; +} + +void EllipticalArc::operator*=(Scale const &s) +{ + _initial_point *= s; + _final_point *= s; + _ellipse *= s; +} + +void EllipticalArc::operator*=(Rotate const &r) +{ + _initial_point *= r; + _final_point *= r; + _ellipse *= r; +} + +void EllipticalArc::operator*=(Zoom const &z) +{ + _initial_point *= z; + _final_point *= z; + _ellipse *= z; +} + +void EllipticalArc::operator*=(Affine const& m) +{ + if (isChord()) { + _initial_point *= m; + _final_point *= m; + _ellipse.setCenter(middle_point(_initial_point, _final_point)); + _ellipse.setRays(0, 0); + _ellipse.setRotationAngle(0); + return; + } + + _initial_point *= m; + _final_point *= m; + _ellipse *= m; + if (m.det() < 0) { + _angles.setSweep(!sweep()); + } + + // ellipse transformation does not preserve its functional form, + // i.e. e.pointAt(0.5)*m and (e*m).pointAt(0.5) can be different. + // We need to recompute start / end angles. + _angles.setInitial(_ellipse.timeAt(_initial_point)); + _angles.setFinal(_ellipse.timeAt(_final_point)); +} + +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 (rotationAngle() != other->rotationAngle()) 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(ray(X), ray(Y), rotationAngle(), _large_arc, sweep(), _final_point); } -Coord EllipticalArc::map_to_01(Coord angle) const +std::ostream &operator<<(std::ostream &out, EllipticalArc const &ea) { - return map_circular_arc_on_unit_interval(angle, initialAngle(), - finalAngle(), _sweep); + out << "EllipticalArc(" + << ea.initialPoint() << ", " + << format_coord_nice(ea.ray(X)) << ", " << format_coord_nice(ea.ray(Y)) << ", " + << format_coord_nice(ea.rotationAngle()) << ", " + << "large_arc=" << (ea.largeArc() ? "true" : "false") << ", " + << "sweep=" << (ea.sweep() ? "true" : "false") << ", " + << ea.finalPoint() << ")"; + return out; } } // end namespace Geom diff --git a/src/2geom/elliptical-arc.h b/src/2geom/elliptical-arc.h index 5527aa6bc..fbb290dca 100644 --- a/src/2geom/elliptical-arc.h +++ b/src/2geom/elliptical-arc.h @@ -34,90 +34,110 @@ * 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/affine.h> #include <2geom/angle.h> #include <2geom/bezier-curve.h> #include <2geom/curve.h> -#include <2geom/affine.h> +#include <2geom/ellipse.h> #include <2geom/sbasis-curve.h> // for non-native methods #include <2geom/utils.h> namespace Geom { -class EllipticalArc : public Curve, public AngleInterval +class EllipticalArc : public Curve { public: - /** @brief Creates an arc with all variables set to zero, and both flags to true. */ + /** @brief Creates an arc with all variables set to zero. */ EllipticalArc() - : AngleInterval(0, 0, true) - , _initial_point(0,0) + : _initial_point(0,0) , _final_point(0,0) - , _rays(0,0) - , _center(0,0) - , _rot_angle(0) - , _large_arc(true) + , _large_arc(false) {} /** @brief Create a new elliptical arc. * @param ip Initial point of the arc - * @param rx First ray of the ellipse - * @param ry Second ray of the ellipse + * @param r Rays of the ellipse as a point * @param rot Angle of rotation of the X axis of the ellipse in radians * @param large If true, the large arc is chosen (always >= 180 degrees), otherwise * the smaller arc is chosen * @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, Point const &r, Coord rot_angle, bool large_arc, bool sweep, - Point fp + Point const &fp ) - : AngleInterval(0,0,sweep) - , _initial_point(ip) + : _initial_point(ip) , _final_point(fp) - , _rays(rx, ry) - , _rot_angle(rot_angle) + , _ellipse(0, 0, r[X], r[Y], rot_angle) + , _angles(0, 0, sweep) , _large_arc(large_arc) { - _updateCenterAndAngles(false); + _updateCenterAndAngles(); } - // methods new to EllipticalArc go here + /// Create a new elliptical arc, giving the ellipse's rays as separate coordinates. + EllipticalArc( Point const &ip, Coord rx, Coord ry, + Coord rot_angle, bool large_arc, bool sweep, + Point const &fp + ) + : _initial_point(ip) + , _final_point(fp) + , _ellipse(0, 0, rx, ry, rot_angle) + , _angles(0, 0, sweep) + , _large_arc(large_arc) + { + _updateCenterAndAngles(); + } - /// @name Retrieve and modify parameters + /// @name Retrieve basic information /// @{ - /** @brief Get the interval of angles the arc contains - * @return The interval between the final and initial angles of the arc */ - Interval angleInterval() const { return Interval(initialAngle(), finalAngle()); } + /** @brief Get a coordinate of the elliptical arc's center. * @param d The dimension to retrieve * @return The selected coordinate of the center */ - /** @brief Get the defining ellipse's rotation - * @return Angle between the +X ray of the ellipse and the +X axis */ - Angle rotationAngle() const { - return _rot_angle; - } + Coord center(Dim2 d) const { return _ellipse.center(d); } + + /** @brief Get the arc's center + * @return The arc's center, situated on the intersection of the ellipse's rays */ + Point center() const { return _ellipse.center(); } + /** @brief Get one of the ellipse's rays * @param d Dimension to retrieve * @return The selected ray of the ellipse */ - Coord ray(Dim2 d) const { return _rays[d]; } + Coord ray(Dim2 d) const { return _ellipse.ray(d); } + /** @brief Get both rays as a point * @return Point with X equal to the X ray and Y to Y ray */ - Point rays() const { return _rays; } + Point rays() const { return _ellipse.rays(); } + + /** @brief Get the defining ellipse's rotation + * @return Angle between the +X ray of the ellipse and the +X axis */ + Angle rotationAngle() const { + return _ellipse.rotationAngle(); + } + /** @brief Whether the arc is larger than half an ellipse. * @return True if the arc is larger than \f$\pi\f$, false otherwise */ bool largeArc() const { return _large_arc; } + /** @brief Whether the arc turns clockwise * @return True if the arc makes a clockwise turn when going from initial to final * point, false otherwise */ - bool sweep() const { return _sweep; } - /** @brief Get the line segment connecting the arc's endpoints. - * @return A linear segment with initial and final point correspoding to those of the arc. */ - LineSegment chord() const { return LineSegment(_initial_point, _final_point); } - /** @brief Change the arc's parameters. */ + bool sweep() const { return _angles.sweep(); } + + Angle initialAngle() const { return _angles.initialAngle(); } + Angle finalAngle() const { return _angles.finalAngle(); } + /// @} + + /// @name Modify parameters + /// @{ + + /// Change all of the arc's parameters. void set( Point const &ip, double rx, double ry, double rot_angle, bool large_arc, bool sweep, Point const &fp @@ -125,89 +145,127 @@ public: { _initial_point = ip; _final_point = fp; - _rays[X] = rx; - _rays[Y] = ry; - _rot_angle = Angle(rot_angle); + _ellipse.setRays(rx, ry); + _ellipse.setRotationAngle(rot_angle); + _angles.setSweep(sweep); _large_arc = large_arc; - _sweep = sweep; - _updateCenterAndAngles(isSVGCompliant()); + _updateCenterAndAngles(); } + + /// Change all of the arc's parameters. + void set( Point const &ip, Point const &r, + Angle rot_angle, bool large_arc, bool sweep, + Point const &fp + ) + { + _initial_point = ip; + _final_point = fp; + _ellipse.setRays(r); + _ellipse.setRotationAngle(rot_angle); + _angles.setSweep(sweep); + _large_arc = large_arc; + _updateCenterAndAngles(); + } + /** @brief Change the initial and final point in one operation. * This method exists because modifying any of the endpoints causes rather costly * 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()); + _updateCenterAndAngles(); } /// @} - /// @name Access computed parameters of the arc - /// @{ - Coord center(Dim2 d) const { return _center[d]; } - /** @brief Get the arc's center - * @return The arc's center, situated on the intersection of the ellipse's rays */ - Point center() const { return _center; } - /** @brief Get the extent of the arc - * @return The angle between the initial and final point, in arc's angular coordinates */ - Coord sweepAngle() const { - return extent(); - } - /// @} - - /// @name Angular evaluation + /// @name Evaluate the arc as a function /// @{ /** Check whether the arc contains the given angle * @param t The angle to check * @return True if the arc contains the angle, false otherwise */ - bool containsAngle(Coord angle) const; + bool containsAngle(Angle angle) const { return _angles.contains(angle); } + /** @brief Evaluate the arc at the specified angular coordinate * @param t Angle * @return Point corresponding to the given angle */ Point pointAtAngle(Coord t) const; + /** @brief Evaluate one of the arc's coordinates at the specified angle * @param t Angle * @param d The dimension to retrieve * @return Selected coordinate of the arc at the specified angle */ Coord valueAtAngle(Coord t, Dim2 d) const; - /** @brief Retrieve the unit circle transform. + + /// Compute the curve time value corresponding to the given angular value. + Coord timeAtAngle(Angle a) const { return _angles.timeAtAngle(a); } + + /// Compute the angular domain value corresponding to the given time value. + Angle angleAt(Coord t) const { return _angles.angleAt(t); } + + /** @brief Compute the amount by which the angle parameter changes going from start to end. + * This has range \f$(-2\pi, 2\pi)\f$ and thus cannot be represented as instance + * of the class Angle. Add this to the initial angle to obtain the final angle. */ + Coord sweepAngle() const { return _angles.sweepAngle(); } + + /** @brief Get the elliptical angle spanned by the arc. + * This is basically the absolute value of sweepAngle(). */ + Coord angularExtent() const { return _angles.extent(); } + + /// Get the angular interval of the arc. + AngleInterval angularInterval() const { return _angles; } + + /// Evaluate the arc in the curve domain, i.e. \f$[0, 1]\$. + virtual Point pointAt(Coord t) const; + + /// Evaluate a single coordinate on the arc in the curve domain. + virtual Coord valueAt(Coord t, Dim2 d) const; + + /** @brief Compute a transform that maps the unit circle to the arc's 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 the arc's ellipse. * @return Transform from unit circle to the arc's ellipse */ - Affine unitCircleTransform() const; + Affine unitCircleTransform() const { + Affine result = _ellipse.unitCircleTransform(); + return result; + } + + /** @brief Compute a transform that maps the arc's ellipse to the unit circle. */ + Affine inverseUnitCircleTransform() const { + Affine result = _ellipse.inverseUnitCircleTransform(); + return result; + } /// @} - /** @brief Check whether the arc adheres to SVG 1.1 implementation guidelines */ - virtual bool isSVGCompliant() const { return false; } - - std::pair<EllipticalArc, EllipticalArc> subdivide(Coord t) const { - EllipticalArc* arc1 = static_cast<EllipticalArc*>(portion(0, t)); - EllipticalArc* arc2 = static_cast<EllipticalArc*>(portion(t, 1)); - assert( arc1 != NULL && arc2 != NULL); - std::pair<EllipticalArc, EllipticalArc> arc_pair(*arc1, *arc2); - delete arc1; - delete arc2; - return arc_pair; + /// @name Deal with degenerate ellipses. + /// @{ + /** @brief Check whether both rays are nonzero. + * If they are not, the arc is represented as a line segment instead. */ + bool isChord() const { + return ray(X) == 0 || ray(Y) == 0; } + /** @brief Get the line segment connecting the arc's endpoints. + * @return A linear segment with initial and final point correspoding to those of the arc. */ + LineSegment chord() const { return LineSegment(_initial_point, _final_point); } + /// @} + // 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); } virtual void setInitial(Point const &p) { _initial_point = p; - _updateCenterAndAngles(isSVGCompliant()); + _updateCenterAndAngles(); } virtual void setFinal(Point const &p) { _final_point = p; - _updateCenterAndAngles(isSVGCompliant()); + _updateCenterAndAngles(); } virtual bool isDegenerate() const { - return ( are_near(ray(X), 0) || are_near(ray(Y), 0) ); + return _initial_point == _final_point; } + virtual bool isLineSegment() const { return isChord(); } virtual Rect boundsFast() const { return boundsExact(); } @@ -218,56 +276,56 @@ 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; -#endif - virtual double nearestPoint( 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; + 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(); } +#endif + virtual std::vector<CurveIntersection> intersect(Curve const &other, Coord eps=EPSILON) const; virtual int degreesOfFreedom() const { return 7; } virtual Curve *derivative() const; - virtual Curve *transformed(Affine const &m) const; - virtual Curve &operator*=(Translate const &m) { - _initial_point += m.vector(); - _final_point += m.vector(); - _center += m.vector(); - return *this; - } + using Curve::operator*=; + virtual void operator*=(Translate const &tr); + virtual void operator*=(Scale const &s); + virtual void operator*=(Rotate const &r); + virtual void operator*=(Zoom const &z); + virtual void operator*=(Affine const &m); - /** - * The size of the returned vector equals n+1. - */ virtual std::vector<Point> pointAndDerivatives(Coord t, unsigned int n) const; - virtual D2<SBasis> toSBasis() const; - virtual double valueAt(Coord t, Dim2 d) const { - return valueAtAngle(angleAt(t), d); - } - virtual Point pointAt(Coord t) const { - return pointAtAngle(angleAt(t)); - } - virtual Curve* portion(double f, double t) const; - virtual Curve* reverse() const; -#endif + virtual Curve *portion(double f, double t) const; + virtual Curve *reverse() const; + virtual bool operator==(Curve const &c) const; + virtual void feed(PathSink &sink, bool moveto_initial) const; -protected: - void _updateCenterAndAngles(bool svg); +private: + void _updateCenterAndAngles(); + void _filterIntersections(std::vector<ShapeIntersection> &xs, bool is_first) const; Point _initial_point, _final_point; - Point _rays, _center; - Angle _rot_angle; + Ellipse _ellipse; + AngleInterval _angles; bool _large_arc; - -private: - Coord map_to_01(Coord angle) const; }; // end class EllipticalArc + +// implemented in elliptical-arc-from-sbasis.cpp +/** @brief Fit an elliptical arc to an SBasis fragment. + * @relates EllipticalArc */ +bool arc_from_sbasis(EllipticalArc &ea, D2<SBasis> const &in, + double tolerance = EPSILON, unsigned num_samples = 20); + +/** @brief Debug output for elliptical arcs. + * @relates EllipticalArc */ +std::ostream &operator<<(std::ostream &out, EllipticalArc const &ea); + } // 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..27909242c 100644 --- a/src/2geom/forward.h +++ b/src/2geom/forward.h @@ -32,14 +32,12 @@ * 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 { -// basic types +// primitives typedef double Coord; typedef int IntCoord; class Point; @@ -63,23 +61,29 @@ typedef GenericOptRect<IntCoord> OptIntRect; class Linear; class Bezier; class SBasis; +class Poly; + +// shapes +class Circle; +class Ellipse; +class ConvexHull; // curves class Curve; class SBasisCurve; class BezierCurve; -class HLineSegment; -class VLineSegment; template <unsigned degree> class BezierCurveN; typedef BezierCurveN<1> LineSegment; typedef BezierCurveN<2> QuadraticBezier; typedef BezierCurveN<3> CubicBezier; class EllipticalArc; -class SVGEllipticalArc; // paths and path sequences class Path; -typedef std::vector<Path> PathVector; +class PathVector; +struct PathTime; +class PathInterval; +struct PathVectorTime; // errors class Exception; @@ -103,11 +107,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..e18561f67 --- /dev/null +++ b/src/2geom/intersection-graph.cpp @@ -0,0 +1,394 @@ +/** + * \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> +#include <iterator> + +namespace Geom { + +struct PathIntersectionGraph::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 inspired by Foster and Overfelt as well as some + * original contributions. + * + * @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); + // NOTE: this early return means that the path data structures will not be created + // if there are no intersections at all! + if (pxs.empty()) return; + + // prepare intersection lists for each path component + for (std::size_t i = 0; i < _a.size(); ++i) { + _apaths.push_back(new PathData()); + } + for (std::size_t i = 0; i < _b.size(); ++i) { + _bpaths.push_back(new PathData()); + } + + 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); + _apaths[xa->pos.path_index].xlist.push_back(*xa); + _bpaths[xb->pos.path_index].xlist.push_back(*xb); + } + + for (std::size_t i = 0; i < _apaths.size(); ++i) { + _apaths[i].xlist.sort(IntersectionVertexLess()); + } + for (std::size_t i = 0; i < _bpaths.size(); ++i) { + _bpaths[i].xlist.sort(IntersectionVertexLess()); + } + + typedef IntersectionList::iterator Iter; + + // determine in/out/on flags using winding + for (unsigned npv = 0; npv < 2; ++npv) { + boost::ptr_vector<PathData> &ls = npv ? _bpaths : _apaths; + boost::ptr_vector<PathData> &ols = npv ? _apaths : _bpaths; + PathVector const &pv = npv ? b : a; + PathVector const &other = npv ? a : b; + + for (unsigned li = 0; li < ls.size(); ++li) { + IntersectionList &xl = ls[li].xlist; + for (Iter i = xl.begin(); i != xl.end(); ++i) { + Iter n = boost::next(i); + if (n == xl.end()) { + n = xl.begin(); + } + std::size_t pi = i->pos.path_index; + + PathInterval ival = forward_interval(i->pos, n->pos, pv[pi].size()); + PathTime mid = ival.inside(precision); + + // TODO check for degenerate cases + 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; + } + } + + // remove intersections that don't change between in/out + // and assign exit / entry flags + for (Iter i = xl.begin(); i != xl.end();) { + if (i->previous == i->next) { + IntersectionList &oxl = ols[i->neighbor->pos.path_index].xlist; + oxl.erase(oxl.iterator_to(*i->neighbor)); + xl.erase(i++); + if (i->next == POINT_INSIDE) { + ++ls[li].removed_in; + } else { + ++ls[li].removed_out; + } + } else { + i->entry = ((i->next == POINT_INSIDE) && (i->previous == POINT_OUTSIDE)); + ++i; + } + } + } + } +} + +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::getAminusB() +{ + PathVector result = _getResult(false, true); + _handleNonintersectingPaths(result, 0, false); + _handleNonintersectingPaths(result, 1, true); + return result; +} + +PathVector PathIntersectionGraph::getBminusA() +{ + PathVector result = _getResult(true, false); + _handleNonintersectingPaths(result, 1, false); + _handleNonintersectingPaths(result, 0, true); + return result; +} + +PathVector PathIntersectionGraph::getXOR() +{ + PathVector r1 = getAminusB(); + PathVector r2 = getBminusA(); + std::copy(r2.begin(), r2.end(), std::back_inserter(r1)); + return r1; +} + +std::size_t PathIntersectionGraph::size() const +{ + std::size_t result = 0; + for (std::size_t i = 0; i < _apaths.size(); ++i) { + result += _apaths[i].xlist.size(); + } + return result; +} + +std::vector<Point> PathIntersectionGraph::intersectionPoints() const +{ + std::vector<Point> result; + + typedef IntersectionList::const_iterator Iter; + for (std::size_t i = 0; i < _apaths.size(); ++i) { + for (Iter j = _apaths[i].xlist.begin(); j != _apaths[i].xlist.end(); ++j) { + result.push_back(j->p); + } + } + 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<PathData> &ls = npv ? _bpaths : _apaths; + for (std::size_t li = 0; li < ls.size(); ++li) { + for (Iter k = ls[li].xlist.begin(); k != ls[li].xlist.end(); ++k) { + k->processed = false; + } + } + } + + unsigned n_processed = 0; + + while (true) { + PathVector const *cur = &_a, *other = &_b; + boost::ptr_vector<PathData> *lscur = &_apaths, *lsother = &_bpaths; + + // 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].xlist.begin()) { + i = (*lscur)[pi].xlist.end(); + } + --i; + } else { + ++i; + if (i == (*lscur)[pi].xlist.end()) { + i = (*lscur)[pi].xlist.begin(); + } + } + + // append portion of path + PathInterval ival = PathInterval::from_direction( + prev->pos.asPathTime(), i->pos.asPathTime(), + 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].xlist.iterator_to(*i->neighbor); + std::swap(lscur, lsother); + std::swap(cur, other); + } + result.back().close(true); + + assert(!result.back().empty()); + } + + assert(n_processed == size() * 2); + + 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<PathData> const &ls = which ? _bpaths : _apaths; + PathVector const &cur = which ? _b : _a; + PathVector const &other = which ? _a : _b; + + for (std::size_t i = 0; i < cur.size(); ++i) { + // the path data vector might have been left empty if there were no intersections at all + bool has_path_data = !ls.empty(); + // Skip if the path has intersections + if (has_path_data && !ls[i].xlist.empty()) continue; + bool path_inside = false; + + // If the path had any intersections removed, use the result of that, + // since one of those might have been at the initial point. + // Also, it saves time. + if (has_path_data && ls[i].removed_in != 0) { + path_inside = true; + } else if (has_path_data && ls[i].removed_out != 0) { + path_inside = false; + } else { + int w = other.winding(cur[i].initialPoint()); + 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, last; + + for (std::size_t k = 0; k < _apaths.size(); ++k) { + it = _apaths[k].xlist.begin(); + last = _apaths[k].xlist.end(); + for (; it != last; ++it) { + if (!it->_hook.is_linked()) { + // this intersection was removed since it did not change inside/outside status + continue; + } + if (!it->processed) { + result = it; + return true; + } + } + } + + return false; +} + + +std::ostream &operator<<(std::ostream &os, PathIntersectionGraph const &pig) +{ + os << "Intersection graph:\n" + << pig._xs.size()/2 << " total intersections\n" + << pig.size() << " considered intersections\n"; + for (std::size_t i = 0; i < pig._apaths.size(); ++i) { + typedef PathIntersectionGraph::IntersectionList::const_iterator Iter; + for (Iter j = pig._apaths[i].xlist.begin(); j != pig._apaths[i].xlist.end(); ++j) { + os << j->pos << " - " << j->neighbor->pos << " @ " << j->p << "\n"; + } + } + return os; +} + +} // 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..bd9aaee81 --- /dev/null +++ b/src/2geom/intersection-graph.h @@ -0,0 +1,128 @@ +/** + * \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 { + +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(); + PathVector getAminusB(); + PathVector getBminusA(); + PathVector getXOR(); + + /// Returns the number of intersections used when computing Boolean operations. + std::size_t size() const; + std::vector<Point> intersectionPoints() const; + +private: + enum InOutFlag { + POINT_INSIDE, + POINT_OUTSIDE + }; + + struct IntersectionVertex { + boost::intrusive::list_member_hook<> _hook; + PathVectorTime 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; + + struct PathData { + IntersectionList xlist; + unsigned removed_in; + unsigned removed_out; + + PathData() + : removed_in(0) + , removed_out(0) + {} + }; + + struct IntersectionVertexLess; + + 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<PathData> _apaths; + boost::ptr_vector<PathData> _bpaths; + + friend std::ostream &operator<<(std::ostream &, PathIntersectionGraph const &); +}; + +std::ostream &operator<<(std::ostream &os, PathIntersectionGraph const &pig); + +} // 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..bbce19947 --- /dev/null +++ b/src/2geom/intersection.h @@ -0,0 +1,147 @@ +/** + * \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 + : boost::totally_ordered< Intersection<TimeA, TimeB> > +{ +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); + } + + bool operator==(Intersection const &other) const { + if (first != other.first) return false; + if (second != other.second) return false; + return true; + } + bool operator<(Intersection const &other) const { + if (first < other.first) return true; + if (first == other.first && second < other.second) return true; + return false; + } + +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; +}; + +template <typename A, typename B> inline +std::vector< Intersection<A, B> > transpose(std::vector< Intersection<B, A> > const &in) { + std::vector< Intersection<A, B> > result; + for (std::size_t i = 0; i < in.size(); ++i) { + result.push_back(Intersection<A, B>(in[i].second, in[i].first, in[i].point())); + } + return result; +} + +template <typename T> inline +void transpose_in_place(std::vector< Intersection<T, T> > &xs) { + for (std::size_t i = 0; i < xs.size(); ++i) { + std::swap(xs[i].first, xs[i].second); + } +} + +typedef Intersection<> ShapeIntersection; + + +} // 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..fd446a1c6 100644 --- a/src/2geom/interval.h +++ b/src/2geom/interval.h @@ -91,19 +91,28 @@ 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]; } - + /// 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 Compute a time value that maps to the given value. + * The supplied value does not need to be in the interval for this method to work. */ + Coord timeAt(Coord v) { + return (v - min()) / extent(); + } + /// Find closest time in [0,1] that maps to the given value. */ + Coord nearestTime(Coord v) { + if (v <= min()) return 0; + if (v >= max()) return 1; + return timeAt(v); + } /// @} /// @name Test coordinates and other intervals for inclusion. @@ -114,7 +123,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 +143,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..bada8ef38 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,36 +58,69 @@ 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) +{ + // degenerate case if (a == 0 && b == 0) { if (c != 0) { - THROW_LOGICALERROR("the passed coefficients gives the empty set"); + THROW_LOGICALERROR("the passed coefficients give 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; } + + // The way final / initial points are set based on coefficients is somewhat unusual. + // This is done to make sure that calling coefficients() will give back + // (almost) the same values. + + // vertical case + if (a == 0) { + // b must be nonzero + _initial = Point(-b/2, -c / b); + _final = _initial; + _final[X] = b/2; + return; + } + + // horizontal case + if (b == 0) { + _initial = Point(-c / a, a/2); + _final = _initial; + _final[Y] = -a/2; + return; + } + + // This gives reasonable results regardless of the magnitudes of a, b and c. + _initial = Point(-b/2,a/2); + _final = Point(b/2,-a/2); + + Point offset(-c/(2*a), -c/(2*b)); + + _initial += offset; + _final += offset; +} + +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. +/** @brief Get the implicit line equation coefficients. + * Note that conversion to implicit form always causes loss of + * precision when dealing with lines that start far from the origin + * and end very close to it. It is recommended to normalize the line + * before converting it to implicit form. * @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); - return coeff; +std::vector<Coord> Line::coefficients() const +{ + std::vector<Coord> c(3); + coefficients(c[0], c[1], c[2]); + return c; } /** @brief Find intersection with an axis-aligned line. @@ -91,87 +128,213 @@ 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]; +Coord Line::timeAt(Point const &p) const +{ + Point v = versor(); + // degenerate case + if (v[X] == 0 && v[Y] == 0) { + return 0; } - else if ( m_versor[Y] != 0 ) { - t = (_point[Y] - m_origin[Y]) / m_versor[Y]; + + // 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]; } - else { // degenerate case - t = 0; +} + +std::vector<ShapeIntersection> Line::intersect(Line const &other) const +{ + std::vector<ShapeIntersection> result; + + Point v1 = versor(); + Point v2 = other.versor(); + Coord cp = cross(v1, v2); + if (cp == 0) return result; + + Point odiff = other.initialPoint() - initialPoint(); + Coord t1 = cross(odiff, v2) / cp; + Coord t2 = cross(odiff, v1) / cp; + result.push_back(ShapeIntersection(*this, other, t1, t2)); + return result; +} + +std::vector<ShapeIntersection> Line::intersect(Ray const &r) const +{ + Line other(r); + std::vector<ShapeIntersection> result = intersect(other); + filter_ray_intersections(result, false, true); + return result; +} + +std::vector<ShapeIntersection> Line::intersect(LineSegment const &ls) const +{ + Line other(ls); + std::vector<ShapeIntersection> result = intersect(other); + filter_line_segment_intersections(result, false, true); + return result; +} + + + +void filter_line_segment_intersections(std::vector<ShapeIntersection> &xs, bool a, bool b) +{ + Interval unit(0, 1); + std::vector<ShapeIntersection>::reverse_iterator i = xs.rbegin(), last = xs.rend(); + while (i != last) { + if ((a && !unit.contains(i->first)) || (b && !unit.contains(i->second))) { + xs.erase((++i).base()); + } else { + ++i; + } + } +} + +void filter_ray_intersections(std::vector<ShapeIntersection> &xs, bool a, bool b) +{ + Interval unit(0, 1); + std::vector<ShapeIntersection>::reverse_iterator i = xs.rbegin(), last = xs.rend(); + while (i != last) { + if ((a && i->first < 0) || (b && i->second < 0)) { + xs.erase((++i).base()); + } else { + ++i; + } } - 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 +344,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 +376,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 +385,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 +430,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 +551,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..7d4766e12 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,46 @@ #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_comparable< Line > +{ 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 +89,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 +123,113 @@ 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; + } + /// Check if the line is horizontal (y is constant). + bool isHorizontal() const { + return _initial[Y] == _final[Y]; + } + /// Check if the line is vertical (x is constant). + bool isVertical() const { + return _initial[X] == _final[X]; + } + + /** @brief Reparametrize the line so that it has unit speed. + * Note that the direction of the line may also change. */ + void normalize() { + // this helps with the nasty case of a line that starts somewhere far + // and ends very close to the origin + if (L2sq(_final) < L2sq(_initial)) { + std::swap(_initial, _final); + } + 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 +239,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 +280,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 +291,7 @@ public: Ray ray(Coord t) { Ray result; result.setOrigin(pointAt(t)); - result.setVersor(m_versor); + result.setVersor(versor()); return result; } @@ -235,88 +299,148 @@ 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<ShapeIntersection> intersect(Line const &other) const; + std::vector<ShapeIntersection> intersect(Ray const &r) const; + std::vector<ShapeIntersection> intersect(LineSegment const &ls) const; + + template <typename T> + Line &operator*=(T const &tr) { + BOOST_CONCEPT_ASSERT((TransformConcept<T>)); + _initial *= tr; + _final *= tr; + 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; } -} + template <typename T> + friend Line operator*(Line const &l, T const &tr) { + BOOST_CONCEPT_ASSERT((TransformConcept<T>)); + Line result(l); + result *= tr; + return result; + } +}; // end class Line + +/** @brief Removes intersections outside of the unit interval. + * A helper used to implement line segment intersections. + * @param xs Line intersections + * @param a Whether the first time value has to be in the unit interval + * @param b Whether the second time value has to be in the unit interval + * @return Appropriately filtered intersections */ +void filter_line_segment_intersections(std::vector<ShapeIntersection> &xs, bool a=false, bool b=true); +void filter_ray_intersections(std::vector<ShapeIntersection> &xs, bool a=false, bool b=true); + +/// @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 +456,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 +495,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..b0306fb9f 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 Function that interpolates linearly between two values. + * @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..103921021 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..fb96d1d2a 100644 --- a/src/2geom/numeric/fitting-model.h +++ b/src/2geom/numeric/fitting-model.h @@ -39,7 +39,7 @@ #include <2geom/sbasis.h> #include <2geom/bezier.h> #include <2geom/bezier-curve.h> -#include <2geom/poly.h> +#include <2geom/polynomial.h> #include <2geom/ellipse.h> #include <2geom/circle.h> #include <2geom/utils.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..f06eeaf94 100644 --- a/src/2geom/path-intersection.h +++ b/src/2geom/path-intersection.h @@ -32,21 +32,19 @@ * */ -#ifndef __GEOM_PATH_INTERSECTION_H -#define __GEOM_PATH_INTERSECTION_H - -#include <2geom/path.h> +#ifndef LIB2GEOM_SEEN_PATH_INTERSECTION_H +#define LIB2GEOM_SEEN_PATH_INTERSECTION_H #include <2geom/crossing.h> - -#include <2geom/sweep.h> +#include <2geom/path.h> +#include <2geom/sweep-bounds.h> 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 +72,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 +97,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..3b8d407f8 100644 --- a/src/2geom/path-sink.cpp +++ b/src/2geom/path-sink.cpp @@ -31,80 +31,65 @@ #include <2geom/sbasis-to-bezier.h> #include <2geom/path-sink.h> #include <2geom/exception.h> +#include <2geom/circle.h> +#include <2geom/ellipse.h> 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(); +} + +void PathSink::feed(Circle const &e) { + Coord r = e.radius(); + Point c = e.center(); + Point a = c + Point(0, c[Y] + r); + Point b = c + Point(0, c[Y] - r); + + moveTo(a); + arcTo(r, r, 0, false, false, b); + arcTo(r, r, 0, false, false, a); + closePath(); +} + +void PathSink::feed(Ellipse const &e) { + Point s = e.pointAt(0); + moveTo(s); + arcTo(e.ray(X), e.ray(Y), e.rotationAngle(), false, false, e.pointAt(M_PI)); + arcTo(e.ray(X), e.ray(Y), e.rotationAngle(), false, false, s); + closePath(); +} + } /* diff --git a/src/2geom/path-sink.h b/src/2geom/path-sink.h index 949369b80..17ede18a4 100644 --- a/src/2geom/path-sink.h +++ b/src/2geom/path-sink.h @@ -29,9 +29,10 @@ * */ -#ifndef SEEN_SVG_PATH_H -#define SEEN_SVG_PATH_H +#ifndef LIB2GEOM_SEEN_PATH_SINK_H +#define LIB2GEOM_SEEN_PATH_SINK_H +#include <2geom/forward.h> #include <2geom/pathvector.h> #include <2geom/curves.h> #include <iterator> @@ -44,58 +45,73 @@ 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 &); + /// Output a circle as two elliptical arcs. + virtual void feed(Circle const &e); + /// Output an ellipse as two elliptical arcs. + virtual void feed(Ellipse const &e); virtual ~PathSink() {} }; +/** @brief Store paths to an output iterator + * @ingroup Paths */ template <typename OutputIterator> class PathIteratorSink : public PathSink { public: @@ -110,22 +126,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,15 +150,15 @@ 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" if (!_in_path) { moveTo(_start_p); } - _path.template appendNew<SVGEllipticalArc>(rx, ry, angle, - large_arc, sweep, p); + _path.template appendNew<EllipticalArc>(rx, ry, angle, + large_arc, sweep, p); } bool backspace() @@ -170,12 +170,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 +188,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 +209,23 @@ 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;} + /// Create a builder that outputs to an internal pathvector. + PathBuilder() : PathIteratorSink<SubpathInserter>(SubpathInserter(_pathset)) {} + /// Create a builder that outputs to pathvector given by reference. + PathBuilder(PathVector &pv) : PathIteratorSink<SubpathInserter>(SubpathInserter(pv)) {} + + /// 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..871441751 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,1020 @@ * the specific language governing rights and limitations. */ - - - #include <2geom/path.h> +#include <2geom/pathvector.h> #include <2geom/transforms.h> +#include <2geom/circle.h> +#include <2geom/ellipse.h> +#include <2geom/convex-hull.h> +#include <2geom/svg-path-writer.h> +#include <2geom/sweeper.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(PathTime const &from, PathTime 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(PathTime 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; + } + } +} + +PathTime 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. + PathTime result(0, 0.0); + + if (!_cross_start && _from.curve_index == _to.curve_index) { + PathTime 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(PathTime const &from, PathTime 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(Rect const &r) + : _curves(new Sequence()) + , _closing_seg(new ClosingSegment(r.corner(3), r.corner(0))) + , _closed(true) + , _exception_on_stitch(true) +{ + for (unsigned i = 0; i < 3; ++i) { + _curves->push_back(new LineSegment(r.corner(i), r.corner(i+1))); + } + _curves->push_back(_closing_seg); +} + +Path::Path(ConvexHull const &ch) + : _curves(new Sequence()) + , _closing_seg(new ClosingSegment(Point(), Point())) + , _closed(true) + , _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; +} + +Path::Path(Circle const &c) + : _curves(new Sequence()) + , _closing_seg(NULL) + , _closed(true) + , _exception_on_stitch(true) +{ + Point p1 = c.pointAt(0); + Point p2 = c.pointAt(M_PI); + _curves->push_back(new EllipticalArc(p1, c.radius(), c.radius(), 0, false, true, p2)); + _curves->push_back(new EllipticalArc(p2, c.radius(), c.radius(), 0, false, true, p1)); + _closing_seg = new ClosingSegment(p1, p1); + _curves->push_back(_closing_seg); +} + +Path::Path(Ellipse const &e) + : _curves(new Sequence()) + , _closing_seg(NULL) + , _closed(true) + , _exception_on_stitch(true) +{ + Point p1 = e.pointAt(0); + Point p2 = e.pointAt(M_PI); + _curves->push_back(new EllipticalArc(p1, e.rays(), e.rotationAngle(), false, true, p2)); + _curves->push_back(new EllipticalArc(p2, e.rays(), e.rotationAngle(), false, true, p1)); + _closing_seg = new ClosingSegment(p1, p1); + _curves->push_back(_closing_seg); +} + +void Path::close(bool c) +{ + if (c == _closed) return; + if (c && _curves->size() >= 2) { + // when closing, if last segment is linear and ends at initial point, + // replace it with the closing segment + Sequence::iterator last = _curves->end() - 2; + if (last->isLineSegment() && last->finalPoint() == initialPoint()) { + _closing_seg->setInitial(last->initialPoint()); + _curves->erase(last); + } + } + _closed = c; +} + +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()); +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; + 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()); +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; } -template<typename iter> +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; + } + } + 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> 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; +} + +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 +{ + PathTime pos = _factorTime(t); + if (rest) { + *rest = pos.t; + } + return at(pos.curve_index); +} + +Point Path::pointAt(Coord t) const +{ + return pointAt(_factorTime(t)); +} + +Coord Path::valueAt(Coord t, Dim2 d) const +{ + return valueAt(_factorTime(t), d); +} + +Curve const &Path::curveAt(PathTime const &pos) const +{ + return at(pos.curve_index); +} +Point Path::pointAt(PathTime const &pos) const +{ + return at(pos.curve_index).pointAt(pos.t); +} +Coord Path::valueAt(PathTime const &pos, Dim2 d) const +{ + return at(pos.curve_index).valueAt(pos.t, d); +} + +std::vector<PathTime> Path::roots(Coord v, Dim2 d) const +{ + std::vector<PathTime> 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(PathTime(i, temp[j])); + } + return res; +} + + +// The class below implements sweepline optimization for curve intersection in paths. +// Instead of O(N^2), this takes O(N + X), where X is the number of overlaps +// between the bounding boxes of curves. + +struct CurveSweepTraits { + struct Bound { + Rect r; + std::size_t index; + int which; + }; + typedef std::less<Coord> Compare; + inline static Coord entry_value(Bound const &b) { return b.r[X].min(); } + inline static Coord exit_value(Bound const &b) { return b.r[X].max(); } +}; + +class CurveSweeper + : public Sweeper<Curve const *, CurveSweepTraits> +{ +public: + CurveSweeper(Path const &a, Path const &b, std::vector<PathIntersection> &result, Coord prec) + : _result(result) + , _precision(prec) + { + for (std::size_t i = 0; i < a.size(); ++i) { + Bound bound; + bound.r = a[i].boundsFast(); + bound.index = i; + bound.which = 0; + insert(bound, &a[i]); + } + for (std::size_t i = 0; i < b.size(); ++i) { + Bound bound; + bound.r = b[i].boundsFast(); + bound.index = i; + bound.which = 1; + insert(bound, &b[i]); + } + } + +protected: + void _enter(Record const &record) { + int which = record.bound.which; + + for (RecordList::iterator i = _active_items.begin(); i != _active_items.end(); ++i) { + // do not intersect in the same path + if (i->bound.which == which) continue; + // do not intersect if boxes do not overlap in Y + if (!record.bound.r[Y].intersects(i->bound.r[Y])) continue; + + std::vector<CurveIntersection> cx; + int ia = record.bound.index; + int ib = i->bound.index; + + if (which == 0) { + cx = record.item->intersect(*i->item, _precision); + } else { + cx = i->item->intersect(*record.item, _precision); + std::swap(ia, ib); + } + + for (std::size_t ci = 0; ci < cx.size(); ++ci) { + PathTime a(ia, cx[ci].first), b(ib, cx[ci].second); + PathIntersection px(a, b, cx[ci].point()); + _result.push_back(px); + } + } + } + +private: + std::vector<PathIntersection> &_result; + Coord _precision; +}; + +std::vector<PathIntersection> Path::intersect(Path const &other, Coord precision) const +{ + std::vector<PathIntersection> result; + + CurveSweeper sweeper(*this, other, result, precision); + sweeper.process(); + + // preprocessing to remove duplicate intersections at endpoints + for (std::size_t i = 0; i < result.size(); ++i) { + result[i].first.normalizeForward(size()); + result[i].second.normalizeForward(other.size()); + } + std::sort(result.begin(), result.end()); + result.erase(std::unique(result.begin(), result.end()), result.end()); + + 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); + } + } + 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; +} + +PathTime Path::nearestTime(Point const &p, Coord *dist) const +{ + Coord mindist = std::numeric_limits<Coord>::max(); + PathTime 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; + } + + PathTime 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(finalPoint()); + if (empty()) return ret; + + ret._curves->pop_back(); // this also deletes the closing segment from ret + + RIter iter(_includesClosingSegment() ? _curves->end() : _curves->end() - 1); + RIter rend(_curves->begin()); + + if (_closed) { + // when the path is closed, there are two cases: + if (front().isLineSegment()) { + // 1. initial segment is linear: it becomes the new closing segment. + rend = RIter(_curves->begin() + 1); + ret._closing_seg = new ClosingSegment(front().finalPoint(), front().initialPoint()); + } else { + // 2. initial segment is not linear: the closing segment becomes degenerate. + // However, skip it if it's already degenerate. + Point fp = finalPoint(); + ret._closing_seg = new ClosingSegment(fp, fp); + } + } else { + // when the path is open, we reverse all real curves, and add a reversed closing segment. + ret._closing_seg = static_cast<ClosingSegment *>(_closing_seg->reverse()); + } + + for (; iter != rend; ++iter) { + ret._curves->push_back(iter->reverse()); + } + ret._curves->push_back(ret._closing_seg); + ret._closed = _closed; + 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)); } - if ( last_replaced != (get_curves().end()-1) ) { - if ( (*(last_replaced-1))->finalPoint() != (*(last-1))->finalPoint() ) { +} + +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()); +} + +void Path::snapEnds(Coord precision) +{ + if (!_closed) return; + if (_curves->size() > 1 && are_near(_closing_seg->length(precision), 0, precision)) { + _unshare(); + _closing_seg->setInitial(_closing_seg->finalPoint()); + (_curves->end() - 1)->setFinal(_closing_seg->finalPoint()); + } +} + +// 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(); + } + if (_closed && c->isLineSegment() && + c->finalPoint() == _closing_seg->finalPoint()) + { + // appending a curve that matches the closing segment has no effect + delete c; + return; + } + } + _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(); +} + +// breaks time value into integral and fractional part +PathTime Path::_factorTime(Coord t) const +{ + size_type sz = size_default(); + if (t < 0 || t > sz) { + THROW_RANGEERROR("parameter t out of bounds"); + } + + PathTime 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; +} + +std::ostream &operator<<(std::ostream &out, Path const &path) +{ + SVGPathWriter pw; + pw.feed(path); + out << pw.str(); + return out; } } // end namespace Geom diff --git a/src/2geom/path.h b/src/2geom/path.h index 28d2a25e4..3ca43e0e5 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,801 @@ 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 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 PathTime + : boost::totally_ordered<PathTime> +{ + 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 + + PathTime() : t(0), curve_index(0) {} + PathTime(size_type idx, Coord tval) : t(tval), curve_index(idx) {} + + bool operator<(PathTime 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==(PathTime const &other) const { + return curve_index == other.curve_index && t == other.t; + } + /// Convert times 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 times 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; + } + } + + Coord asFlatTime() const { return curve_index + t; } }; -class Iterator : public BaseIterator<Iterator, Path> { -public: - typedef BaseIterator<Iterator, Path> Base; +inline std::ostream &operator<<(std::ostream &os, PathTime const &pos) { + os << pos.curve_index << ": " << format_coord_nice(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 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 time + * @param to Final time + * @param cross_start If true, the interval will proceed from the initial to final + * time 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(PathTime const &from, PathTime const &to, bool cross_start, size_type path_size); + + /// Get the time value of the initial point. + PathTime const &initialTime() const { return _from; } + /// Get the time value of the final point. + PathTime const &finalTime() const { return _to; } + + PathTime const &from() const { return _from; } + PathTime 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 time for inclusion. + bool contains(PathTime const &pos) const; + + /// Get a time at least @a min_dist away in parameter space from the ends. + /// If no such time exists, the middle point is returned. + PathTime inside(Coord min_dist = EPSILON) const; + + /// Select one of two intervals with given endpoints by parameter direction. + static PathInterval from_direction(PathTime const &from, PathTime 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(PathTime const &from, PathTime 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; + PathTime _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(PathTime const &from, PathTime 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(PathTime const &from, PathTime 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<PathTime> PathIntersection; + +template <> +struct ShapeTraits<Path> { + typedef PathTime 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 final linear closing segment. + * + * If the final point of the last "real" segment coincides exactly with the initial + * point of the first segment, the closing segment will be absent from both + * [begin(), end_open()) and [begin(), end_closed()). + * + * 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. + * + * 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. * - * 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. + * It's not very convenient to create a Path directly. To construct paths more easily, + * use PathBuilder. * - * \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 { + * @ingroup Paths */ +class Path + : boost::equality_comparable< Path > +{ 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 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); + } + + /// Create a path from a rectangle. + explicit Path(Rect const &r); + /// Create a path from a convex hull. + explicit Path(ConvexHull const &); + /// Create a path from a circle, using two elliptical arcs. + explicit Path(Circle const &c); + /// Create a path from an ellipse, using two elliptical arcs. + explicit Path(Ellipse const &e); + + 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); + swap(other._exception_on_stitch, _exception_on_stitch); + } + /** @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(); } + /// Alias for front(). + Curve const &initialCurve() 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 _includesClosingSegment() + ? back_closed() + : back_open(); + } + Curve const &finalCurve() const { return back_default(); } + + 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; } + + /** @brief Set whether the path is closed. + * When closing a path where the last segment can be represented as a closing + * segment, the last segment will be removed. When opening a path, the closing + * segment will be erased. This means that closing and then opening a path + * will not always give back the original path. */ + void close(bool closed = true); + + /** @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 a transform to each curve. + template <typename T> + Path &operator*=(T const &tr) { + BOOST_CONCEPT_ASSERT((TransformConcept<T>)); + _unshare(); + for (std::size_t i = 0; i < _curves->size(); ++i) { + (*_curves)[i] *= tr; + } + return *this; + } + + template <typename T> + friend Path operator*(Path const &path, T const &tr) { + BOOST_CONCEPT_ASSERT((TransformConcept<T>)); + Path result(path); + result *= tr; + return result; + } + + /** @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 PathTime 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 path time. + Curve const &curveAt(PathTime const &pos) const; + /// Get the point at the specified path time. + Point pointAt(PathTime const &pos) const; + /// Get one coordinate at the specified path time. + Coord valueAt(PathTime const &pos, Dim2 d) const; + + Point operator()(Coord t) const { return pointAt(t); } + + /// Compute intersections with axis-aligned line. + std::vector<PathTime> 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()); + } + + PathTime nearestTime(Point const &p, Coord *dist = NULL) const; + std::vector<Coord> nearestTimePerCurve(Point const &p) 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, PathTime const &from, PathTime 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(PathTime const &from, PathTime 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)); + } + + /** @brief Reduce the closing segment to a point if it's shorter than precision. + * Do this by moving the final point. */ + void snapEnds(Coord precision = EPSILON); + + /// 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()); + } + } + PathTime _factorTime(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) { + PathTime pt = c.nearestTime(p); + return pt.curve_index + pt.t; } -} // end namespace Geom +std::ostream &operator<<(std::ostream &out, Path const &path); + +} // end namespace Geom #endif // LIB2GEOM_SEEN_PATH_H diff --git a/src/2geom/pathvector.cpp b/src/2geom/pathvector.cpp index fc0ad75c4..bff201c71 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,113 +31,205 @@ * 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> #include <2geom/affine.h> +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/svg-path-writer.h> 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 +{ + PathVectorTime pos = _factorTime(t); + if (rest) { + *rest = Coord(pos.curve_index) + pos.t; + } + return at(pos.path_index); +} +Curve const &PathVector::curveAt(Coord t, Coord *rest) const +{ + PathVectorTime pos = _factorTime(t); + if (rest) { + *rest = pos.t; + } + return at(pos.path_index).at(pos.curve_index); +} +Coord PathVector::valueAt(Coord t, Dim2 d) const +{ + PathVectorTime pos = _factorTime(t); + return at(pos.path_index).at(pos.curve_index).valueAt(pos.t, d); +} +Point PathVector::pointAt(Coord t) const +{ + PathVectorTime pos = _factorTime(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) +void PathVector::snapEnds(Coord precision) { - boost::optional<PathVectorPosition> retval; + for (std::size_t i = 0; i < size(); ++i) { + (*this)[i].snapEnds(precision); + } +} - 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); +std::vector<PVIntersection> PathVector::intersect(PathVector const &other, Coord precision) const +{ + typedef PathVectorTime 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], precision); + 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<PathVectorTime> PathVector::nearestTime(Point const &p, Coord *dist) const +{ + boost::optional<PathVectorTime> retval; + + Coord mindist = infinity(); + for (size_type i = 0; i < size(); ++i) { + Coord d; + PathTime pos = (*this)[i].nearestTime(p, &d); + if (d < mindist) { + mindist = d; + retval = PathVectorTime(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<PathVectorTime> PathVector::allNearestTimes(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<PathVectorTime> retval; + + Coord mindist = infinity(); + for (size_type i = 0; i < size(); ++i) { + Coord d; + PathTime pos = (*this)[i].nearestTime(p, &d); + if (d < mindist) { + mindist = d; + retval.clear(); + } + if (d <= mindist) { + retval.push_back(PathVectorTime(i, pos.curve_index, pos.t)); } - - ++i; } - if (distance_squared) { - *distance_squared = mindsq; + if (dist) { + *dist = mindist; } return retval; +} +PathVectorTime PathVector::_factorTime(Coord t) const +{ + PathVectorTime 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 +std::ostream &operator<<(std::ostream &out, PathVector const &pv) +{ + SVGPathWriter wr; + wr.feed(pv); + out << wr.str(); + return out; +} -#endif // SEEN_GEOM_PATHVECTOR_CPP +} // namespace Geom /* Local Variables: diff --git a/src/2geom/pathvector.h b/src/2geom/pathvector.h index e875e915f..108f2aa05 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,254 @@ #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; +/** @brief 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 PathVectorTime + : public PathTime + , boost::totally_ordered<PathVectorTime> +{ + size_type path_index; ///< Index of the path in the vector -/* 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; -} + PathVectorTime() : PathTime(0, 0), path_index(0) {} + PathVectorTime(size_type _i, size_type _c, Coord _t) + : PathTime(_c, _t), path_index(_i) {} + PathVectorTime(size_type _i, PathTime const &pos) + : PathTime(pos), path_index(_i) {} -/* 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; + bool operator<(PathVectorTime const &other) const { + if (path_index < other.path_index) return true; + if (path_index == other.path_index) { + return static_cast<PathTime const &>(*this) < static_cast<PathTime const &>(other); + } + return false; + } + bool operator==(PathVectorTime const &other) const { + return path_index == other.path_index + && static_cast<PathTime const &>(*this) == static_cast<PathTime const &>(other); } -} -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); + PathTime const &asPathTime() const { + return *static_cast<PathTime const *>(this); } -} -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) -{ - return path_in.front().initialPoint(); +inline std::ostream &operator<<(std::ostream &os, PathVectorTime const &pvt) { + os << pvt.path_index << ": " << pvt.asPathTime(); + return os; } -inline -Geom::Point finalPoint(PathVector const &path_in) +typedef Intersection<PathVectorTime> PathVectorIntersection; +typedef PathVectorIntersection PVIntersection; ///< Alias to save typing + +template <> +struct ShapeTraits<PathVector> { + typedef PathVectorTime TimeType; + //typedef PathVectorInterval IntervalType; + typedef PathVector AffineClosureType; + typedef PathVectorIntersection IntersectionType; +}; + +/** @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::equality_comparable< PathVector + > > > > > > > > { - return path_in.back().finalPoint(); -} + typedef std::vector<Path> Sequence; +public: + typedef PathVectorTime 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 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) {} + 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(PathVectorTime const &pos) { + return const_cast<Path &>(static_cast<PathVector const*>(this)->pathAt(pos)); + } + Path const &pathAt(PathVectorTime const &pos) const { + return at(pos.path_index); + } + Curve const &curveAt(PathVectorTime const &pos) const { + return at(pos.path_index).at(pos.curve_index); + } + Point pointAt(PathVectorTime const &pos) const { + return at(pos.path_index).at(pos.curve_index).pointAt(pos.t); + } + Coord valueAt(PathVectorTime 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); + } + + void snapEnds(Coord precision = EPSILON); + + 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; + + boost::optional<PathVectorTime> nearestTime(Point const &p, Coord *dist = NULL) const; + std::vector<PathVectorTime> allNearestTimes(Point const &p, Coord *dist = NULL) const; + +private: + PathVectorTime _factorTime(Coord t) const; + + Sequence _data; }; -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); +inline OptRect bounds_fast(PathVector const &pv) { return pv.boundsFast(); } +inline OptRect bounds_exact(PathVector const &pv) { return pv.boundsExact(); } -inline -Point pointAt(PathVector const & path_in, PathVectorPosition const &pvp) { - return path_in[pvp.path_nr].pointAt(pvp.t); -} +std::ostream &operator<<(std::ostream &out, PathVector const &pv); } // end namespace Geom diff --git a/src/2geom/piecewise.h b/src/2geom/piecewise.h index ab8417254..a5a65a9fe 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> @@ -43,12 +42,14 @@ namespace Geom { /** - * %Piecewise function class. + * @brief Function defined as discrete pieces. + * * The Piecewise class manages a sequence of elements of a type as segments and * the ’cuts’ between them. These cuts are time values which separate the pieces. * This function representation allows for more interesting functions, as it provides * a viable output for operations such as inversion, which may require multiple * SBasis to properly invert the original. + * * As for technical details, while the actual SBasis segments begin on the first * cut and end on the last, the function is defined throughout all inputs by ex- * tending the first and last segments. The exact switching between segments is @@ -63,6 +64,8 @@ namespace Geom { * s_n,& c_n <= t * \end{array}\right. * \f] + * + * @ingroup Fragments */ template <typename T> class Piecewise { @@ -933,7 +936,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-ops.h b/src/2geom/point-ops.h deleted file mode 100644 index 6f5eab56b..000000000 --- a/src/2geom/point-ops.h +++ /dev/null @@ -1,25 +0,0 @@ -//[[[cog -import operators - -setContext("Point", "Matrix", "Point") -make({'*':'*='}, {'/':'/='}) -apsnd({'*':'/'}, "b.inverse()") - -setContext("Point", "double", "Point") -make({'*=':'*'}, {'/=':'/'}, {'*':'*'}, {'*':'/'}) - -setContext("Point", "Point", "bool") -make({'==':'!='}) - -setContext("Point", "Point", "Point") -make({'+=':'+', '-=':'-'}) -]]] - -************** -GENERATED CODE -************** -If you wish to modify, move function out of generation region and remove the -cause of its generation. -*/ - -//[[[end]]] diff --git a/src/2geom/point.cpp b/src/2geom/point.cpp index b0b00b5da..bcdbab429 100644 --- a/src/2geom/point.cpp +++ b/src/2geom/point.cpp @@ -35,6 +35,7 @@ #include <assert.h> #include <math.h> +#include <2geom/coord.h> #include <2geom/point.h> #include <2geom/transforms.h> @@ -146,8 +147,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 +162,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. @@ -230,6 +231,13 @@ Point constrain_angle(Point const &A, Point const &B, unsigned int n, Point cons return A + dir * Rotate(k * 2.0 * M_PI / (double)n) * L2(diff); } +std::ostream &operator<<(std::ostream &out, const Geom::Point &p) +{ + out << "(" << format_coord_nice(p[X]) << ", " + << format_coord_nice(p[Y]) << ")"; + return out; +} + } // end namespace Geom /* diff --git a/src/2geom/point.h b/src/2geom/point.h index 23dcfd54f..a66e64647 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,40 +231,73 @@ 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; }; - - friend inline std::ostream &operator<< (std::ostream &out_file, const Geom::Point &in_pnt); + 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 }; /** @brief Output operator for points. * Prints out the coordinates. * @relates Point */ -inline std::ostream &operator<< (std::ostream &out_file, const Geom::Point &in_pnt) { - out_file << "X: " << in_pnt[X] << " Y: " << in_pnt[Y]; - return out_file; -} - -template<> struct Point::LexOrder<X> { - bool operator()(Point const &a, Point const &b) { +std::ostream &operator<<(std::ostream &out, const Geom::Point &p); + +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 +306,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 +314,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 +327,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 +338,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 +354,62 @@ 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) { + // do not use an unqualified calls to distance before the empty + // specialization of iterator_traits is defined - see end of file + return are_near((a - b).length(), 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 +418,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/poly.cpp b/src/2geom/polynomial.cpp index de0229172..ca2389f80 100644 --- a/src/2geom/poly.cpp +++ b/src/2geom/polynomial.cpp @@ -1,4 +1,41 @@ -#include <2geom/poly.h> +/** + * \file + * \brief Polynomial in canonical (monomial) basis + *//* + * Authors: + * MenTaLguY <mental@rydia.net> + * 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 <algorithm> +#include <2geom/polynomial.h> +#include <2geom/math-utils.h> +#include <math.h> #ifdef HAVE_GSL #include <gsl/gsl_poly.h> @@ -183,6 +220,100 @@ Poly gcd(Poly const &a, Poly const &b, const double /*tol*/) { + +std::vector<Coord> solve_quadratic(Coord a, Coord b, Coord c) +{ + std::vector<Coord> result; + + if (a == 0) { + // linear equation + if (b == 0) return result; + result.push_back(-c/b); + return result; + } + + Coord delta = b*b - 4*a*c; + + if (delta == 0) { + // one root + result.push_back(-b / (2*a)); + } else if (delta > 0) { + // two roots + Coord delta_sqrt = sqrt(delta); + + // Use different formulas depending on sign of b to preserve + // numerical stability. See e.g.: + // http://people.csail.mit.edu/bkph/articles/Quadratics.pdf + Coord t = -0.5 * (b + sgn(b) * delta_sqrt); + result.push_back(t / a); + result.push_back(c / t); + } + // no roots otherwise + + std::sort(result.begin(), result.end()); + return result; +} + + +std::vector<Coord> solve_cubic(Coord a, Coord b, Coord c, Coord d) +{ + // based on: + // http://mathworld.wolfram.com/CubicFormula.html + + if (a == 0) { + return solve_quadratic(b, c, d); + } + if (d == 0) { + // divide by x + std::vector<Coord> result = solve_quadratic(a, b, c); + result.push_back(0); + std::sort(result.begin(), result.end()); + return result; + } + + std::vector<Coord> result; + + // 1. divide everything by a to bring to canonical form + b /= a; + c /= a; + d /= a; + + // 2. eliminate x^2 term: x^3 + 3Qx - 2R = 0 + Coord Q = (3*c - b*b) / 9; + Coord R = (-27 * d + b * (9*c - 2*b*b)) / 54; + + // 3. compute polynomial discriminant + Coord D = Q*Q*Q + R*R; + Coord term1 = b/3; + + if (D > 0) { + // only one real root + Coord S = cbrt(R + sqrt(D)); + Coord T = cbrt(R - sqrt(D)); + result.push_back(-b/3 + S + T); + } else if (D == 0) { + // 3 real roots, 2 of which are equal + Coord rroot = cbrt(R); + result.reserve(3); + result.push_back(-term1 + 2*rroot); + result.push_back(-term1 - rroot); + result.push_back(-term1 - rroot); + } else { + // 3 distinct real roots + assert(Q < 0); + Coord theta = acos(R / sqrt(-Q*Q*Q)); + Coord rroot = 2 * sqrt(-Q); + result.reserve(3); + result.push_back(-term1 + rroot * cos(theta / 3)); + result.push_back(-term1 + rroot * cos((theta + 2*M_PI) / 3)); + result.push_back(-term1 + rroot * cos((theta + 4*M_PI) / 3)); + } + + std::sort(result.begin(), result.end()); + return result; +} + + /*Poly divide_out_root(Poly const & p, double x) { assert(1); }*/ diff --git a/src/2geom/poly.h b/src/2geom/polynomial.h index 7d93d0a85..5ab2aa4c8 100644 --- a/src/2geom/poly.h +++ b/src/2geom/polynomial.h @@ -3,9 +3,10 @@ * \brief Polynomial in canonical (monomial) basis *//* * Authors: - * ? <?@?.?> + * MenTaLguY <mental@rydia.net> + * Krzysztof Kosiński <tweenk.pl@gmail.com> * - * Copyright ?-? 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 @@ -29,7 +30,6 @@ * 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_POLY_H @@ -39,10 +39,13 @@ #include <iostream> #include <algorithm> #include <complex> +#include <2geom/coord.h> #include <2geom/utils.h> namespace Geom { +/** @brief Polynomial in canonical (monomial) basis. + * @ingroup Fragments */ class Poly : public std::vector<double>{ public: // coeff; // sum x^i*coeff[i] @@ -214,6 +217,18 @@ std::vector<double> solve_reals(const Poly & p); #endif double polish_root(Poly const & p, double guess, double tol); + +/** @brief Analytically solve quadratic equation. + * The equation is given in the standard form: ax^2 + bx + c = 0. + * Only real roots are returned. */ +std::vector<Coord> solve_quadratic(Coord a, Coord b, Coord c); + +/** @brief Analytically solve cubic equation. + * The equation is given in the standard form: ax^3 + bx^2 + cx + d = 0. + * Only real roots are returned. */ +std::vector<Coord> solve_cubic(Coord a, Coord b, Coord c, Coord d); + + inline std::ostream &operator<< (std::ostream &out_file, const Poly &in_poly) { if(in_poly.size() == 0) out_file << "0"; diff --git a/src/2geom/quadtree.cpp b/src/2geom/quadtree.cpp deleted file mode 100644 index 98030c424..000000000 --- a/src/2geom/quadtree.cpp +++ /dev/null @@ -1,290 +0,0 @@ -#include <2geom/quadtree.h> - -namespace Geom{ -Quad* QuadTree::search(Rect const &r) { - return search(r[0].min(), r[1].min(), - r[0].max(), r[1].max()); -} - -void QuadTree::insert(Rect const &r, int shape) { - insert(r[0].min(), r[1].min(), - r[0].max(), r[1].max(), shape); -} - - -Quad* QuadTree::search(double x0, double y0, double x1, double y1) { - Quad *q = root; - - double bxx0 = bx1, bxx1 = bx1; - double byy0 = by1, byy1 = by1; - while(q) { - double cx = (bxx0 + bxx1)/2; - double cy = (byy0 + byy1)/2; - unsigned i = 0; - if(x0 >= cx) { - i += 1; - bxx0 = cx; // zoom in a quad - } else if(x1 <= cx) { - bxx1 = cx; - } else - break; - if(y0 >= cy) { - i += 2; - byy0 = cy; - } else if(y1 <= cy) { - byy1 = cy; - } else - break; - - assert(i < 4); - Quad *qq = q->children[i]; - if(qq == 0) break; // last non-null - q = qq; - } - return q; -} - - -/* -Comments by Vangelis (use with caution :P ) - -Insert Rect (x0, y0), (x1, y1) in the QuadTree Q. - -=================================================================================== -* QuadTree Q has: Quadtree's Quad root R, QuadTree's bounding box B. - -* Each Quad has a Quad::data where we store the id of the Rect that belong to -this Quad. (In reality we'll store a pointer to the shape). - -* Each Quad has 4 Quad children: 0, 1, 2, 3. Each child Quad represents one of the following quarters -of the bounding box B: - -+---------------------+ -| | | -| NW=0 | NE=1 | -| | | -| | | -+---------------------+ -| | | -| SW=2 | SE=3 | -| | | -| | | -+---------------------+ - -Each Quad can further be divided in 4 Quads as above and so on. Below there is an example - - Root - / || \ - / / \ \ - 0 1 2 3 - /\ - / | | \ - 0 1 2 3 - -+---------------------+ -| | 1-0 | 1-1| -| 0 | | | -| |-----|----| -| | 1-2 | 1-3| -| | | | -+---------------------+ -| | | -| | | -| 2 | 3 | -| | | -+---------------------+ - - - -=================================================================================== -Insert Rect (x0, y0), (x1, y1) in the QuadTree Q. Algorithm: -1) check if Rect is bigger than QuadTree's bounding box -2) find in which Quad we should add the Rect: - - - ------------------------------------------------------------------------------------ -How we find in which Quad we should add the Rect R: - -Q = Quadtree's Quad root -B = QuadTree's bounding box B -WHILE (Q) { - IF ( Rect cannot fit in one unique quarter of B ){ - Q = current Quad ; - BREAK; - } - IF ( Rect can fit in the quarter I ) { - IF (Q.children[I] doesn't exist) { - create the Quad Q.children[I]; - } - B = bounding box of the Quad Q.children[I] ; - Q = Q.children[I] ; - CHECK(R, B) ; - } -} -add Rect R to Q ; - - -*/ - -void QuadTree::insert(double x0, double y0, double x1, double y1, int shape) { - // loop until a quad would break the box. - - // empty root => empty QuadTree. Create initial bounding box (0,0), (1,1) - if(root == NULL) { - root = new Quad; - - bx0 = 0; - bx1 = 1; - by0 = 0; - by1 = 1; - } - Quad *q = root; - - //A temp bounding box. Same as root's bounting box (ie of the whole QuadTree) - double bxx0 = bx0, bxx1 = bx1; - double byy0 = by0, byy1 = by1; - - while((bxx0 > x0) || - (bxx1 < x1) || - (byy0 > y0) || - (byy1 < y1)) { - // QuadTree has small size, can't accomodate new rect. Double the size: - unsigned i = 0; - - if(bxx0 > x0) { - bxx0 = 2*bxx0 - bxx1; - i += 1; - } else { - bxx1 = 2*bxx1 - bxx0; - } - if(byy0 > y0) { - byy0 = 2*byy0 - byy1; - i += 2; - } else { - byy1 = 2*byy1 - byy0; - } - q = new Quad; - //check if root is empty (no rects, no quad children) - if( clean_root() ){ - root = q; - } - else{ - q->children[i] = root; - root = q; - } - bx0 = bxx0; - bx1 = bxx1; - by0 = byy0; - by1 = byy1; - } - - while(true) { - // Find the center of the temp bounding box - double cx = (bxx0 + bxx1)/2; - double cy = (byy0 + byy1)/2; - unsigned i = 0; - assert(x0 >= bxx0); - assert(x1 <= bxx1); - assert(y0 >= byy0); - assert(y1 <= byy1); - - if(x0 >= cx) { - i += 1; - bxx0 = cx; // zoom in a quad - } else if(x1 <= cx) { - bxx1 = cx; - } else{ - // rect does not fit in one unique quarter (in X axis) of the temp bounding box - break; - } - if(y0 >= cy) { - i += 2; - byy0 = cy; - } else if(y1 <= cy) { - byy1 = cy; - } else{ - // rect does not fit in one unique quarter (in Y axis) of the temp bounding box - break; - } - - // 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; - } - - /* - 1 rect does fit in one unique quarter of the temp bounding box. And we have found which. - 2 temp bounding box = bounding box of this quarter. - 3 "Go in" this quarter (create if doesn't exist) - */ - assert(i < 4); - Quad *qq = q->children[i]; - if(qq == NULL) { - qq = new Quad; - q->children[i] = qq; - } - q = qq; - } - q->data.push_back(shape); -} - - -void QuadTree::erase(Quad *q, int shape) { - for(Quad::iterator i = q->data.begin(); i != q->data.end(); ++i) { - if(*i == shape) { - q->data.erase(i); - if(q->data.empty()) { - - } - } - } - return; -} - -/* -Returns: -false: if root isn't empty -true: if root is empty it cleans root -*/ -bool QuadTree::clean_root() { - assert(root); - - // false if root *has* rects assigned to it. - bool all_clean = root->data.empty(); - - // if root has children we get false - for(unsigned i = 0; i < 4; i++) - { - if(root->children[i]) - { - all_clean = false; - } - } - - if(all_clean) - { - delete root; - root=0; - return true; - } - return false; -} - -}; - -/* - 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/quadtree.h b/src/2geom/quadtree.h deleted file mode 100644 index 949a9b898..000000000 --- a/src/2geom/quadtree.h +++ /dev/null @@ -1,99 +0,0 @@ -/** - * \file - * \brief Quad tree data structure - *//* - * 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. - * - */ - -#include <vector> -#include <cassert> - -#include <2geom/d2.h> - -namespace Geom{ - -class Quad{ -public: - Quad* children[4]; - std::vector<int> data; - Quad() { - for(int i = 0; i < 4; i++) - children[i] = 0; - } - typedef std::vector<int>::iterator iterator; - Rect bounds(unsigned i, double x, double y, double d) { - double dd = d/2; - switch(i % 4) { - case 0: - return Rect(Interval(x, x+dd), Interval(y, y+dd)); - case 1: - return Rect(Interval(x+dd, x+d), Interval(y, y+dd)); - case 2: - return Rect(Interval(x, x+dd), Interval(y+dd, y+d)); - case 3: - return Rect(Interval(x+dd, x+d), Interval(y+dd, y+d)); - default: - /* just to suppress warning message - * this case should be never reached */ - assert(false); - } - } -}; - -class QuadTree{ -public: - Quad* root; - double scale; - double bx0, bx1; - double by0, by1; - - QuadTree() : root(0), scale(1) {} - - Quad* search(double x0, double y0, double x1, double y1); - void insert(double x0, double y0, double x1, double y1, int shape); - Quad* search(Geom::Rect const &r); - void insert(Geom::Rect const &r, int shape); - void erase(Quad *q, int shape); -private: - bool clean_root(); -}; - -}; - -/* - 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/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..affe7edc0 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,11 @@ 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 bool isLineSegment() const { return inner[X].size() == 1; } 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 +106,34 @@ 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; - }; + using Curve::operator*=; + virtual void operator*=(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.cpp b/src/2geom/sbasis-math.cpp index 97cdf45ce..896eb18a7 100644 --- a/src/2geom/sbasis-math.cpp +++ b/src/2geom/sbasis-math.cpp @@ -297,11 +297,11 @@ Piecewise<SBasis> reciprocalOnDomain(Interval range, double tol){ if (a<=tol){ reciprocal_fn.push_cut(0); int i0=(int) floor(std::log(tol)/std::log(R)); - a=pow(R,i0); + a = std::pow(R,i0); reciprocal_fn.push(Linear(1/a),a); }else{ int i0=(int) floor(std::log(a)/std::log(R)); - a=pow(R,i0); + a = std::pow(R,i0); reciprocal_fn.cuts.push_back(a); } 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..d18bc369e 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/polynomial.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 98c400197..09fbb03ef 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,15 +232,13 @@ 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 midx = 8*midx - 4*bz[0][X] - 4*bz[3][X]; // re-define relative to center midy = 8*midy - 4*bz[0][Y] - 4*bz[3][Y]; - if ((std::abs(midx) < EPSILON) && (std::abs(midy) < EPSILON)) - return; if ((std::abs(xprime[0]) < EPSILON) && (std::abs(yprime[0]) < EPSILON) && ((std::abs(xprime[1]) > EPSILON) || (std::abs(yprime[1]) > EPSILON))) { // degenerate handle at 0 : use distance of closest approach @@ -270,11 +262,11 @@ void sbasis_to_cubic_bezier (std::vector<Point> & bz, D2<SBasis> const& sb) double test2 = (bz[2][Y] - bz[0][Y])*(bz[3][X] - bz[0][X]) - (bz[2][X] - bz[0][X])*(bz[3][Y] - bz[0][Y]); if (test1*test2 < 0) // reject anti-symmetric case, LP Bug 1428267 & Bug 1428683 return; - 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]; @@ -520,7 +512,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..42d92d7b8 100644 --- a/src/2geom/sbasis.cpp +++ b/src/2geom/sbasis.cpp @@ -328,9 +328,9 @@ SBasis derivative(SBasis const &a) { } int k = a.size()-1; double d = (2*k+1)*(a[k][1] - a[k][0]); - if(d == 0) + if (d == 0 && k > 0) { c.pop_back(); - else { + } else { c[k][0] = d; c[k][1] = d; } @@ -351,9 +351,9 @@ void SBasis::derive() { // in place version } int k = size()-1; double d = (2*k+1)*((*this)[k][1] - (*this)[k][0]); - if(d == 0) + if (d == 0 && k > 0) { pop_back(); - else { + } else { (*this)[k][0] = d; (*this)[k][1] = d; } @@ -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.h b/src/2geom/svg-elliptical-arc.h deleted file mode 100644 index ba0a18257..000000000 --- a/src/2geom/svg-elliptical-arc.h +++ /dev/null @@ -1,282 +0,0 @@ -/** - * \file - * \brief SVG 1.1-compliant elliptical arc curve - *//* - * Authors: - * MenTaLguY <mental@rydia.net> - * Marco Cecchetti <mrcekets at gmail.com> - * Krzysztof Kosiński <tweenk.pl@gmail.com> - * - * 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 LIB2GEOM_SEEN_SVG_ELLIPTICAL_ARC_H -#define LIB2GEOM_SEEN_SVG_ELLIPTICAL_ARC_H - -#include <2geom/curve.h> -#include <2geom/angle.h> -#include <2geom/utils.h> -#include <2geom/bezier-curve.h> -#include <2geom/elliptical-arc.h> -#include <2geom/sbasis-curve.h> // for non-native methods -#include <2geom/numeric/vector.h> -#include <2geom/numeric/fitting-tool.h> -#include <2geom/numeric/fitting-model.h> -#include <algorithm> - -namespace Geom { - -class SVGEllipticalArc : public EllipticalArc { -public: - SVGEllipticalArc() - : EllipticalArc() - {} - SVGEllipticalArc( Point ip, double rx, double ry, - double rot_angle, bool large_arc, bool sweep, - Point fp - ) - : EllipticalArc() - { - _initial_point = ip; - _final_point = fp; - _rays[X] = rx; _rays[Y] = ry; - _rot_angle = rot_angle; - _large_arc = large_arc; - _sweep = sweep; - _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); - return EllipticalArc::valueAt(t, d); - } - virtual Point pointAt(Coord t) const { - if (isDegenerate()) 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); - return EllipticalArc::pointAndDerivatives(t, n); - } - virtual Rect boundsExact() const { - if (isDegenerate()) return chord().boundsExact(); - return EllipticalArc::boundsExact(); - } - virtual OptRect boundsLocal(OptInterval const &i, unsigned int deg) const { - if (isDegenerate()) return chord().boundsLocal(i, deg); - return EllipticalArc::boundsLocal(i, deg); - } - - virtual Curve *derivative() const { - if (isDegenerate()) return chord().derivative(); - return EllipticalArc::derivative(); - } - - virtual std::vector<Coord> roots(Coord v, Dim2 d) const { - if (isDegenerate()) 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()) { - std::vector<Coord> result; - result.push_back(chord().nearestPoint(p, from, to)); - return result; - } - return EllipticalArc::allNearestPoints(p, from, to); - } -#endif - virtual D2<SBasis> toSBasis() const { - if (isDegenerate()) 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 - -/* - * useful for testing and debugging - */ -template< class charT > -inline -std::basic_ostream<charT> & -operator<< (std::basic_ostream<charT> & os, const SVGEllipticalArc & ea) -{ - os << "{ cx: " << ea.center(X) << ", cy: " << ea.center(Y) - << ", rx: " << ea.ray(X) << ", ry: " << ea.ray(Y) - << ", rot angle: " << decimal_round(rad_to_deg(ea.rotationAngle()),2) - << ", start angle: " << decimal_round(rad_to_deg(ea.initialAngle()),2) - << ", end angle: " << decimal_round(rad_to_deg(ea.finalAngle()),2) - << " }"; - - return os; -} - - - - -// forward declation -namespace detail -{ - struct ellipse_equation; -} - -// TODO this needs to be rewritten and moved to EllipticalArc header -/* - * make_elliptical_arc - * - * convert a parametric polynomial curve given in symmetric power basis form - * into an SVGEllipticalArc type; in order to be successfull the input curve - * has to look like an actual elliptical arc even if a certain tolerance - * is allowed through an ad-hoc parameter. - * The conversion is performed through an interpolation on a certain amount of - * sample points computed on the input curve; - * the interpolation computes the coefficients of the general implicit equation - * of an ellipse (A*X^2 + B*XY + C*Y^2 + D*X + E*Y + F = 0), then from the - * implicit equation we compute the parametric form. - * - */ -class make_elliptical_arc -{ - public: - typedef D2<SBasis> curve_type; - - /* - * constructor - * - * it doesn't execute the conversion but set the input and output parameters - * - * _ea: the output SVGEllipticalArc that will be generated; - * _curve: the input curve to be converted; - * _total_samples: the amount of sample points to be taken - * on the input curve for performing the conversion - * _tolerance: how much likelihood is required between the input curve - * and the generated elliptical arc; the smaller it is the - * the tolerance the higher it is the likelihood. - */ - make_elliptical_arc( EllipticalArc& _ea, - curve_type const& _curve, - unsigned int _total_samples, - double _tolerance ); - - private: - bool bound_exceeded( unsigned int k, detail::ellipse_equation const & ee, - double e1x, double e1y, double e2 ); - - bool check_bound(double A, double B, double C, double D, double E, double F); - - void fit(); - - bool make_elliptiarc(); - - void print_bound_error(unsigned int k) - { - std::cerr - << "tolerance error" << std::endl - << "at point: " << k << std::endl - << "error value: "<< dist_err << std::endl - << "bound: " << dist_bound << std::endl - << "angle error: " << angle_err - << " (" << angle_tol << ")" << std::endl; - } - - public: - /* - * perform the actual conversion - * return true if the conversion is successfull, false on the contrary - */ - bool operator()() - { - // initialize the reference - const NL::Vector & coeff = fitter.result(); - fit(); - if ( !check_bound(1, coeff[0], coeff[1], coeff[2], coeff[3], coeff[4]) ) - return false; - if ( !(make_elliptiarc()) ) return false; - return true; - } - - /* - * you can set a boolean parameter to tell the conversion routine - * if the output elliptical arc has to be svg compliant or not; - * the default value is true - */ - bool svg_compliant_flag() const - { - return svg_compliant; - } - - void svg_compliant_flag(bool _svg_compliant) - { - svg_compliant = _svg_compliant; - } - - private: - EllipticalArc& ea; // output elliptical arc - const curve_type & curve; // input curve - Piecewise<D2<SBasis> > dcurve; // derivative of the input curve - NL::LFMEllipse model; // model used for fitting - // perform the actual fitting task - NL::least_squeares_fitter<NL::LFMEllipse> fitter; - // tolerance: the user-defined tolerance parameter; - // tol_at_extr: the tolerance at end-points automatically computed - // on the value of "tolerance", and usually more strict; - // tol_at_center: tolerance at the center of the ellipse - // angle_tol: tolerance for the angle btw the input curve tangent - // versor and the ellipse normal versor at the sample points - double tolerance, tol_at_extr, tol_at_center, angle_tol; - Point initial_point, final_point; // initial and final end-points - unsigned int N; // total samples - unsigned int last; // N-1 - double partitions; // N-1 - std::vector<Point> p; // sample points - double dist_err, dist_bound, angle_err; - bool svg_compliant; -}; - -} // end namespace Geom - -#endif // LIB2GEOM_SEEN_SVG_ELLIPTICAL_ARC_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/svg-path-parser.cpp b/src/2geom/svg-path-parser.cpp index 932f95829..b6e6da869 100644 --- a/src/2geom/svg-path-parser.cpp +++ b/src/2geom/svg-path-parser.cpp @@ -1,3 +1,5 @@ + +#line 1 "/home/tweenk/src/lib2geom/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 "/home/tweenk/src/lib2geom/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,980 @@ 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, 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, 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, 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, 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, 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 = 1; +static const int svg_path_en_main = 232; -void Parser::parse(char const *str) -throw(SVGPathParseError) +#line 47 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" + + +SVGPathParser::SVGPathParser(PathSink &sink) + : _absolute(false) + , _sink(sink) + , _z_snap_threshold(0) + , _curve(NULL) { - char const *p = str; - char const *start = NULL; - int cs; + reset(); +} + +SVGPathParser::~SVGPathParser() +{ + delete _curve; +} - _reset(); +void SVGPathParser::reset() { + _absolute = false; + _current = _initial = Point(0, 0); + _quad_tangent = _cubic_tangent = Point(0, 0); + _params.clear(); + delete _curve; + _curve = NULL; + +#line 1105 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.cpp" { cs = svg_path_start; } +#line 73 "/home/tweenk/src/lib2geom/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) +{ + _pushCurve(NULL); // flush + _sink.moveTo(p); + _quad_tangent = _cubic_tangent = _current = _initial = p; +} + +void SVGPathParser::_lineTo(Point const &p) +{ + _pushCurve(new LineSegment(_current, p)); + _quad_tangent = _cubic_tangent = _current = p; +} + +void SVGPathParser::_curveTo(Point const &c0, Point const &c1, Point const &p) +{ + _pushCurve(new CubicBezier(_current, c0, c1, p)); + _quad_tangent = _current = p; + _cubic_tangent = p + ( p - c1 ); +} + +void SVGPathParser::_quadTo(Point const &c, Point const &p) +{ + _pushCurve(new QuadraticBezier(_current, 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 (_current == p) { + return; // ignore invalid (ambiguous) arc segments where start and end point are the same (per SVG spec) + } + + _pushCurve(new EllipticalArc(_current, rx, ry, angle, large_arc, sweep, p)); + _quad_tangent = _cubic_tangent = _current = p; +} + +void SVGPathParser::_closePath() +{ + if (_curve && (!_absolute || !_moveto_was_absolute) && + are_near(_initial, _current, _z_snap_threshold)) + { + _curve->setFinal(_initial); + } + + _pushCurve(NULL); // flush + _sink.closePath(); + _quad_tangent = _cubic_tangent = _current = _initial; +} + +void SVGPathParser::_pushCurve(Curve *c) +{ + if (_curve) { + _sink.feed(*_curve, false); + delete _curve; + } + _curve = c; +} +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 1247 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.cpp" { int _klen; unsigned int _trans; @@ -1168,6 +1251,8 @@ throw(SVGPathParseError) unsigned int _nacts; const char *_keys; + if ( p == pe ) + goto _test_eof; if ( cs == 0 ) goto _out; _resume: @@ -1233,63 +1318,76 @@ _match: switch ( *_acts++ ) { case 0: +#line 209 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { start = p; } break; case 1: - +#line 213 "/home/tweenk/src/lib2geom/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 225 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { _push(1.0); } break; case 3: - +#line 229 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { _push(0.0); } break; case 4: - +#line 233 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { _absolute = true; } break; case 5: +#line 237 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { _absolute = false; } break; case 6: +#line 241 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { + _moveto_was_absolute = _absolute; _moveTo(_pop_point()); } break; case 7: +#line 246 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { _lineTo(_pop_point()); } break; case 8: +#line 250 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { - _hlineTo(Point(_pop_coord(X), _current[Y])); + _lineTo(Point(_pop_coord(X), _current[Y])); } break; case 9: +#line 254 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { - _vlineTo(Point(_current[X], _pop_coord(Y))); + _lineTo(Point(_current[X], _pop_coord(Y))); } break; case 10: +#line 258 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { Point p = _pop_point(); Point c1 = _pop_point(); @@ -1298,6 +1396,7 @@ _match: } break; case 11: +#line 265 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { Point p = _pop_point(); Point c1 = _pop_point(); @@ -1305,6 +1404,7 @@ _match: } break; case 12: +#line 271 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { Point p = _pop_point(); Point c = _pop_point(); @@ -1312,12 +1412,14 @@ _match: } break; case 13: +#line 277 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { Point p = _pop_point(); _quadTo(_quad_tangent, p); } break; case 14: +#line 282 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { Point point = _pop_point(); bool sweep = _pop_flag(); @@ -1330,42 +1432,169 @@ _match: } break; case 15: +#line 293 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" { _closePath(); } break; - case 16: - {{p++; goto _out; }} - break; +#line 1441 "/home/tweenk/src/lib2geom/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 213 "/home/tweenk/src/lib2geom/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 241 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" + { + _moveto_was_absolute = _absolute; + _moveTo(_pop_point()); + } + break; + case 7: +#line 246 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" + { + _lineTo(_pop_point()); + } + break; + case 8: +#line 250 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" + { + _lineTo(Point(_pop_coord(X), _current[Y])); + } + break; + case 9: +#line 254 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" + { + _lineTo(Point(_current[X], _pop_coord(Y))); + } + break; + case 10: +#line 258 "/home/tweenk/src/lib2geom/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 265 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + Point c1 = _pop_point(); + _curveTo(_cubic_tangent, c1, p); + } + break; + case 12: +#line 271 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + Point c = _pop_point(); + _quadTo(c, p); + } + break; + case 13: +#line 277 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" + { + Point p = _pop_point(); + _quadTo(_quad_tangent, p); + } + break; + case 14: +#line 282 "/home/tweenk/src/lib2geom/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 293 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.rl" + { + _closePath(); + } + break; +#line 1547 "/home/tweenk/src/lib2geom/src/2geom/svg-path-parser.cpp" + } + } + } + _out: {} } +#line 435 "/home/tweenk/src/lib2geom/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) { + _pushCurve(NULL); + _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..e25316cf8 100644 --- a/src/2geom/svg-path-parser.h +++ b/src/2geom/svg-path-parser.h @@ -30,48 +30,161 @@ * */ -#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 <cstdio> #include <2geom/exception.h> #include <2geom/point.h> #include <2geom/path-sink.h> +#include <2geom/forward.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); + ~SVGPathParser(); + + /** @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); + + /** @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(); -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 Set the threshold for considering the closing segment degenerate. + * When the current point was reached by a relative command, is closer + * to the initial point of the path than the specified threshold + * and a 'z' is encountered, the last segment will be adjusted instead so that + * the closing segment has exactly zero length. This is useful when reading + * SVG 1.1 paths that have non-linear final segments written in relative + * coordinates, which always suffer from some loss of precision. SVG 2 + * allows alternate placement of 'z' which does not have this problem. */ + void setZSnapThreshold(Coord threshold) { _z_snap_threshold = threshold; } + Coord zSnapThreshold() const { return _z_snap_threshold; } + +private: + bool _absolute; + bool _moveto_was_absolute; + Point _current; + Point _initial; + Point _cubic_tangent; + Point _quad_tangent; + std::vector<Coord> _params; + PathSink &_sink; + Coord _z_snap_threshold; + Curve *_curve; + + 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 _pushCurve(Curve *c); + + 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..1c40ba64e --- /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(rad_to_deg(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-bounds.cpp index f25894282..48f168b97 100644 --- a/src/2geom/sweep.cpp +++ b/src/2geom/sweep-bounds.cpp @@ -1,9 +1,27 @@ -#include <2geom/sweep.h> +#include <2geom/sweep-bounds.h> #include <algorithm> namespace Geom { +struct Event { + double x; + unsigned ix; + bool closing; + Event(double pos, unsigned i, bool c) : x(pos), ix(i), closing(c) {} +// Lexicographic ordering by x then closing + bool operator<(Event const &other) const { + if(x < other.x) return true; + if(x > other.x) return false; + return closing < other.closing; + } + bool operator==(Event const &other) const { + return other.x == x && other.ix == ix && other.closing == closing; + } +}; + +std::vector<std::vector<unsigned> > fake_cull(unsigned a, unsigned b); + /** * \brief Make a list of pairs of self intersections in a list of Rects. * @@ -19,8 +37,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 +84,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-bounds.h index 91371e6fb..e0ebf2975 100644 --- a/src/2geom/sweep.h +++ b/src/2geom/sweep-bounds.h @@ -32,37 +32,20 @@ * */ -#ifndef __2GEOM_SWEEP_H__ -#define __2GEOM_SWEEP_H__ +#ifndef LIB2GEOM_SEEN_SWEEP_H +#define LIB2GEOM_SEEN_SWEEP_H #include <vector> #include <2geom/d2.h> namespace Geom { -struct Event { - double x; - unsigned ix; - bool closing; - Event(double pos, unsigned i, bool c) : x(pos), ix(i), closing(c) {} -// Lexicographic ordering by x then closing - bool operator<(Event const &other) const { - if(x < other.x) return true; - if(x > other.x) return false; - return closing < other.closing; - } - bool operator==(Event const &other) const { - return other.x == x && other.ix == ix && other.closing == closing; - } -}; std::vector<std::vector<unsigned> > sweep_bounds(std::vector<Rect>, Dim2 dim = X); std::vector<std::vector<unsigned> > sweep_bounds(std::vector<Rect>, std::vector<Rect>, Dim2 dim = X); -std::vector<std::vector<unsigned> > fake_cull(unsigned a, unsigned b); - } #endif diff --git a/src/2geom/sweeper.h b/src/2geom/sweeper.h new file mode 100644 index 000000000..8c4e182a6 --- /dev/null +++ b/src/2geom/sweeper.h @@ -0,0 +1,252 @@ +/** @file + * @brief Class for implementing sweepline algorithms + *//* + * 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 LIB2GEOM_SEEN_SWEEPER_H +#define LIB2GEOM_SEEN_SWEEPER_H + +#include <2geom/coord.h> +#include <algorithm> +#include <vector> +#include <boost/intrusive/list.hpp> +#include <boost/range/algorithm/heap_algorithm.hpp> + +namespace Geom { + +/** @brief Sweep traits class for interval bounds. + * @relates Sweeper + * @ingroup Utilities */ +struct IntervalSweepTraits { + typedef Interval Bound; + typedef std::less<Coord> Compare; + inline static Coord entry_value(Bound const &b) { return b.min(); } + inline static Coord exit_value(Bound const &b) { return b.max(); } +}; + +/** @brief Sweep traits class for rectangle bounds. + * @tparam D Which axis to use for sweeping + * @ingroup Utilities */ +template <Dim2 D> +struct RectSweepTraits { + typedef Rect Bound; + typedef std::less<Coord> Compare; + inline static Coord entry_value(Bound const &b) { return b[D].min(); } + inline static Coord exit_value(Bound const &b) { return b[D].max(); } +}; + +template <typename T> +struct BoundsFast { + Rect operator()(T const &item) const { + return item.boundsFast(); + } +}; + +/** @brief Generic sweepline algorithm. + * + * This class encapsulates an algorithm that sorts the objects according + * to their bounds, then moves an imaginary line (sweepline) over those + * bounds from left to right. Objects are added to the active list when + * the line starts intersecting their bounds, and removed when it completely + * passes over them. + * + * To use this, create a derived class and reimplement the _enter() + * and/or _leave() virtual functions, insert all the objects, + * and finally call process(). Inside _enter() and _leave(), the items that have + * their bounds intersected by the sweepline are available in a list called + * _active_items. This is an intrusive linked list, so you should access it using + * iterators. Do not add or remove items from it. You can specify the bound type + * and how it should be accessed by defining a custom SweepTraits class. + * + * Look in path.cpp for example usage. + * + * @tparam Item The type of items to sweep + * @tparam SweepTraits Traits class that defines the items' bounds, + * how to interpret them and how to sort the events + * @ingroup Utilities + */ +template <typename Item, typename SweepTraits = IntervalSweepTraits> +class Sweeper { +public: + /// Type of the item's boundary - usually this will be an Interval or Rect. + typedef typename SweepTraits::Bound Bound; + + Sweeper() {} + + /** @brief Insert a single item for sweeping. + * @param bound Boundary of the item, as defined in sweep traits + * @param item The item itself */ + void insert(Bound const &bound, Item const &item) { + assert(!(typename SweepTraits::Compare()( + SweepTraits::exit_value(bound), + SweepTraits::entry_value(bound)))); + _items.push_back(Record(bound, item)); + } + + /** @brief Insert a range of items using the supplied bounds functor. + * The bounds are computed from items using the supplied bounds functor. + * @param first Start of range + * @param last End of range (one-past-the-end iterator) + * @param f Bounds functor */ + template <typename Iter, typename BoundFunc> + void insert(Iter first, Iter last, BoundFunc f = BoundFunc()) { + for (; first != last; ++first) { + Bound b = f(*first); + assert(!(typename SweepTraits::Compare()( + SweepTraits::exit_value(b), + SweepTraits::entry_value(b)))); + _items.push_back(Record(b, *first)); + } + } + + /** @brief Process entry and exit events. + * This will iterate over all inserted items, calling the virtual protected + * functions _enter() and _leave() according to the order of the boundaries + * of each item. */ + void process() { + if (_items.empty()) return; + + typename SweepTraits::Compare cmp; + + // we store the events in heaps, which is slightly more efficient + // than sorting them, since a heap requires linear time to construct + for (RecordIter i = _items.begin(); i != _items.end(); ++i) { + _entry_events.push_back(i); + _exit_events.push_back(i); + } + boost::make_heap(_entry_events, _entry_heap_compare); + boost::make_heap(_exit_events, _exit_heap_compare); + boost::pop_heap(_entry_events, _entry_heap_compare); + boost::pop_heap(_exit_events, _exit_heap_compare); + + RecordIter next_entry = _entry_events.back(); + RecordIter next_exit = _exit_events.back(); + _entry_events.pop_back(); + _exit_events.pop_back(); + + while (next_entry != _items.end() || next_exit != _items.end()) { + assert(next_exit != _items.end()); + + if (next_entry == _items.end() || + cmp(SweepTraits::exit_value(next_exit->bound), + SweepTraits::entry_value(next_entry->bound))) + { + // exit event - remove record from active list + _leave(*next_exit); + _active_items.erase(_active_items.iterator_to(*next_exit)); + if (!_exit_events.empty()) { + boost::pop_heap(_exit_events, _exit_heap_compare); + next_exit = _exit_events.back(); + _exit_events.pop_back(); + } else { + next_exit = _items.end(); + // we should end the loop after this happens + } + } else { + // entry event - add record to active list + _enter(*next_entry); + _active_items.push_back(*next_entry); + if (!_entry_events.empty()) { + boost::pop_heap(_entry_events, _entry_heap_compare); + next_entry = _entry_events.back(); + _entry_events.pop_back(); + } else { + next_entry = _items.end(); + } + } + } + + assert(_active_items.empty()); + } + +protected: + /// The item and its sweepline boundary. + struct Record { + boost::intrusive::list_member_hook<> _hook; + Bound bound; + Item item; + + Record(Bound const &b, Item const &i) + : bound(b), item(i) + {} + }; + typedef typename std::vector<Record>::iterator RecordIter; + + typedef boost::intrusive::list + < Record + , boost::intrusive::member_hook + < Record + , boost::intrusive::list_member_hook<> + , &Record::_hook + > + > RecordList; + + /** @brief Enter an item record. + * Override this to process an item as it is about to enter the active list. + * When called, the passed record will not be part of the active list. */ + virtual void _enter(Record const &) {} + /** @brief Leave an item record. + * Override this to process an item as it is about to leave the active list. + * When called, the passed record will be part of the active list. */ + virtual void _leave(Record const &) {} + + /// The list of all item records undergoing sweeping. + std::vector<Record> _items; + /// The list of active item records. + RecordList _active_items; + +private: + inline static bool _entry_heap_compare(RecordIter a, RecordIter b) { + typename SweepTraits::Compare cmp; + return cmp(SweepTraits::entry_value(b->bound), SweepTraits::entry_value(a->bound)); + } + inline static bool _exit_heap_compare(RecordIter a, RecordIter b) { + typename SweepTraits::Compare cmp; + return cmp(SweepTraits::exit_value(b->bound), SweepTraits::exit_value(a->bound)); + } + + std::vector<RecordIter> _entry_events; + std::vector<RecordIter> _exit_events; +}; + +} // namespace Geom + +#endif // !LIB2GEOM_SEEN_SWEEPER_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/sync.sh b/src/2geom/sync.sh new file mode 100755 index 000000000..a2c162903 --- /dev/null +++ b/src/2geom/sync.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +function usage { + echo "2Geom sync to upstream script" + echo "Usage: $0 path/to/2geom/checkout/dir" +} + +if [ "x$(which rsync)" = "x" ]; then + echo "rsync not found on your system, please install it" + exit 1 +fi + +if [ "x$1" = "x" ]; then + usage $0 + exit 64 +fi +if [ ! -d "$1" ]; then + usage $0 + exit 64 +fi +if [ ! -f "$1/src/2geom/path.h" ]; then + usage $0 + exit 64 +fi + +INK_2GEOM="$(dirname $0)/" +UPSTREAM_2GEOM="$1/src/2geom/" +rsync -r --existing \ + --exclude CMakeLists.txt --exclude sync.sh \ + "$UPSTREAM_2GEOM" "$INK_2GEOM" 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.cpp b/src/2geom/transforms.cpp index 091079d5a..41d395297 100644 --- a/src/2geom/transforms.cpp +++ b/src/2geom/transforms.cpp @@ -139,6 +139,24 @@ Affine &Affine::operator*=(Zoom const &z) { return *this; } +Affine Rotate::around(Point const &p, Coord angle) +{ + Affine result = Translate(-p) * Rotate(angle) * Translate(p); + return result; +} + +Affine reflection(Point const & vector, Point const & origin) +{ + Geom::Point vn = unit_vector(vector); + Coord cx2 = vn[X] * vn[X]; + Coord cy2 = vn[Y] * vn[Y]; + Coord c2xy = 2 * vn[X] * vn[Y]; + Affine mirror ( cx2 - cy2, c2xy, + c2xy, cy2 - cx2, + 0, 0 ); + return Translate(-origin) * mirror * Translate(origin); +} + // this checks whether the requirements of TransformConcept are satisfied for all transforms. // if you add a new transform type, include it here! void check_transforms() @@ -173,14 +191,6 @@ void check_transforms() m = z * t; m = z * s; m = z * r; m = z * h; m = z * v; m = z * z; } -Affine reflection(Point const & vector, Point const & origin) { - Geom::Point vec_norm = unit_vector(vector); - Affine mirror ( vec_norm[X]*vec_norm[X] - vec_norm[Y]*vec_norm[Y], 2 * vec_norm[X] * vec_norm[Y] , - 2 * vec_norm[X] * vec_norm[Y], vec_norm[Y]*vec_norm[Y] - vec_norm[X]*vec_norm[X] , - 0 ,0 ); - return Translate(-origin) * mirror * Translate(origin); -} - } // namespace Geom /* diff --git a/src/2geom/transforms.h b/src/2geom/transforms.h index 7f5635747..de4e6871f 100644 --- a/src/2geom/transforms.h +++ b/src/2geom/transforms.h @@ -40,6 +40,7 @@ #include <2geom/forward.h> #include <2geom/affine.h> #include <2geom/angle.h> +#include <boost/concept/assert.hpp> namespace Geom { @@ -95,6 +96,7 @@ public: * @ingroup Transforms */ template <typename T> T pow(T const &t, int n) { + BOOST_CONCEPT_ASSERT((TransformConcept<T>)); if (n == 0) return T::identity(); T result(T::identity()); T x(n < 0 ? t.inverse() : t); @@ -199,6 +201,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; } @@ -216,6 +219,7 @@ public: Coord rad = (deg / 180.0) * M_PI; return Rotate(rad); } + static Affine around(Point const &p, Coord angle); friend class Point; }; @@ -343,6 +347,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 : diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index efb604aca..c416b0dea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -166,6 +166,9 @@ set(sp_SRC sp-style-elem.h sp-switch.h sp-symbol.h + sp-tag.h + sp-tag-use.h + sp-tag-use-reference.h sp-text.h sp-textpath.h sp-title.h @@ -423,6 +426,10 @@ if(WIN32) #deptool.cpp winconsole.cpp winmain.cpp + + # ------- + # Headers + registrytool.h ) endif() diff --git a/src/Makefile.am b/src/Makefile.am index cfbbd4015..7a37f13e8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -23,7 +23,6 @@ endif noinst_LIBRARIES = \ - libinkscape.a \ libcroco/libcroco.a \ libavoid/libavoid.a \ $(internal_GDL) \ @@ -36,6 +35,7 @@ noinst_LIBRARIES = \ libdepixelize/libdepixelize.a \ util/libutil.a \ libinkversion.a +# libinkscape.a all_libs = \ $(noinst_LIBRARIES) \ @@ -198,15 +198,15 @@ DISTCLEANFILES = \ # ################################################ # this should speed up the build -libinkscape_a_SOURCES = $(ink_common_sources) +#libinkscape_a_SOURCES = $(ink_common_sources) -inkscape_SOURCES += main.cpp $(win32_sources) +inkscape_SOURCES += main.cpp $(ink_common_sources) $(win32_sources) inkscape_LDADD = $(all_libs) inkscape_LDFLAGS = $(kdeldflags) $(mwindows) -inkview_SOURCES += inkview.cpp $(win32_sources) +inkview_SOURCES += inkview.cpp $(ink_common_sources) $(win32_sources) inkview_LDADD = $(all_libs) -inkview_LDFLAGS = $(mwindows) +inkview_LDFLAGS = $(mwindows) # ################################################ # VERSION REPORTING diff --git a/src/color-profile.cpp b/src/color-profile.cpp index 85a6fa876..690a72654 100644 --- a/src/color-profile.cpp +++ b/src/color-profile.cpp @@ -313,7 +313,7 @@ void ColorProfile::set(unsigned key, gchar const *value) { //# 1. Get complete URI of document gchar const *docbase = doc->getURI(); - gchar* escaped = g_uri_escape_string(this->href, "!*'();:@=+$,/?#[]", TRUE); + gchar* escaped = g_uri_escape_string(this->href, "!*'();:@=+$,/?#", TRUE); //g_message("docbase:%s\n", docbase); //org::w3c::dom::URI docUri(docbase); diff --git a/src/conn-avoid-ref.cpp b/src/conn-avoid-ref.cpp index eb6694233..ec9aba793 100644 --- a/src/conn-avoid-ref.cpp +++ b/src/conn-avoid-ref.cpp @@ -19,7 +19,7 @@ #include "display/curve.h" #include "2geom/line.h" #include "2geom/crossing.h" -#include "2geom/convex-cover.h" +#include "2geom/convex-hull.h" #include "helper/geom-curves.h" #include "svg/stringstream.h" #include "conn-avoid-ref.h" @@ -297,7 +297,7 @@ static Avoid::Polygon avoid_item_poly(SPItem const *item) Geom::Line prev_parallel_hull_edge; prev_parallel_hull_edge.setOrigin(hull_edge.origin()+hull_edge.versor().ccw()*spacing); prev_parallel_hull_edge.setVersor(hull_edge.versor()); - int hull_size = hull.boundary.size(); + int hull_size = hull.size(); for (int i = 0; i < hull_size; ++i) { hull_edge.setPoints(hull[i], hull[i+1]); diff --git a/src/desktop-events.cpp b/src/desktop-events.cpp index e80e5f6c1..b685dacbf 100644 --- a/src/desktop-events.cpp +++ b/src/desktop-events.cpp @@ -354,7 +354,7 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) // the origin should still be constrained to the guide. So let's do // that explicitly first: Geom::Line line(guide->getPoint(), guide->angle()); - Geom::Coord t = line.nearestPoint(motion_dt); + Geom::Coord t = line.nearestTime(motion_dt); motion_dt = line.pointAt(t); if (!(event->motion.state & GDK_SHIFT_MASK)) { m.guideConstrainedSnap(motion_dt, *guide); @@ -435,7 +435,7 @@ gint sp_dt_guide_event(SPCanvasItem *item, GdkEvent *event, gpointer data) // the origin should still be constrained to the guide. So let's // do that explicitly first: Geom::Line line(guide->getPoint(), guide->angle()); - Geom::Coord t = line.nearestPoint(event_dt); + Geom::Coord t = line.nearestTime(event_dt); event_dt = line.pointAt(t); if (!(event->button.state & GDK_SHIFT_MASK)) { m.guideConstrainedSnap(event_dt, *guide); diff --git a/src/desktop-style.cpp b/src/desktop-style.cpp index d2109c03c..02c18339b 100644 --- a/src/desktop-style.cpp +++ b/src/desktop-style.cpp @@ -1179,7 +1179,7 @@ objects_query_fontvariants (const std::vector<SPItem*> &objects, SPStyle *style_ SPIEnum* position_res = &(style_res->font_variant_position); SPIEnum* caps_res = &(style_res->font_variant_caps); SPINumeric* numeric_res = &(style_res->font_variant_numeric); - + // Stores 'and' of all values ligatures_res->computed = SP_CSS_FONT_VARIANT_LIGATURES_NORMAL; position_res->computed = SP_CSS_FONT_VARIANT_POSITION_NORMAL; @@ -1256,6 +1256,63 @@ objects_query_fontvariants (const std::vector<SPItem*> &objects, SPStyle *style_ } +int +objects_query_fontfeaturesettings (const std::vector<SPItem*> &objects, SPStyle *style_res) +{ + bool different = false; + int texts = 0; + + if (style_res->font_feature_settings.value) { + g_free(style_res->font_feature_settings.value); + style_res->font_feature_settings.value = NULL; + } + style_res->font_feature_settings.set = FALSE; + + for (std::vector<SPItem*>::const_iterator i = objects.begin(); i != objects.end(); i++) { + SPObject *obj = *i; + + // std::cout << " " << reinterpret_cast<SPObject*>(i->data)->getId() << std::endl; + if (!isTextualItem(obj)) { + continue; + } + + SPStyle *style = obj->style; + if (!style) { + continue; + } + + texts ++; + + if (style_res->font_feature_settings.value && style->font_feature_settings.value && + strcmp (style_res->font_feature_settings.value, style->font_feature_settings.value)) { + different = true; // different fonts + } + + if (style_res->font_feature_settings.value) { + g_free(style_res->font_feature_settings.value); + style_res->font_feature_settings.value = NULL; + } + + style_res->font_feature_settings.set = TRUE; + style_res->font_feature_settings.value = g_strdup(style->font_feature_settings.value); + } + + if (texts == 0 || !style_res->font_feature_settings.set) { + return QUERY_STYLE_NOTHING; + } + + if (texts > 1) { + if (different) { + return QUERY_STYLE_MULTIPLE_DIFFERENT; + } else { + return QUERY_STYLE_MULTIPLE_SAME; + } + } else { + return QUERY_STYLE_SINGLE; + } +} + + /** * Write to style_res the baseline numbers. */ @@ -1667,6 +1724,8 @@ sp_desktop_query_style_from_list (const std::vector<SPItem*> &list, SPStyle *sty return objects_query_fontstyle (list, style); } else if (property == QUERY_STYLE_PROPERTY_FONTVARIANTS) { return objects_query_fontvariants (list, style); + } else if (property == QUERY_STYLE_PROPERTY_FONTFEATURESETTINGS) { + return objects_query_fontfeaturesettings (list, style); } else if (property == QUERY_STYLE_PROPERTY_FONTNUMBERS) { return objects_query_fontnumbers (list, style); } else if (property == QUERY_STYLE_PROPERTY_BASELINES) { diff --git a/src/desktop-style.h b/src/desktop-style.h index e5fe50440..95c434844 100644 --- a/src/desktop-style.h +++ b/src/desktop-style.h @@ -48,6 +48,7 @@ enum { // which property was queried (add when you need more) QUERY_STYLE_PROPERTY_FONTFAMILY, // font-family QUERY_STYLE_PROPERTY_FONTSTYLE, // font style QUERY_STYLE_PROPERTY_FONTVARIANTS, // font variants (OpenType features) + QUERY_STYLE_PROPERTY_FONTFEATURESETTINGS, // font feature settings (OpenType features) QUERY_STYLE_PROPERTY_FONTNUMBERS, // size, spacings QUERY_STYLE_PROPERTY_BASELINES, // baseline-shift QUERY_STYLE_PROPERTY_MASTEROPACITY, // opacity diff --git a/src/display/cairo-utils.cpp b/src/display/cairo-utils.cpp index e1f12b04b..59e190676 100644 --- a/src/display/cairo-utils.cpp +++ b/src/display/cairo-utils.cpp @@ -18,9 +18,7 @@ #include <glib/gstdio.h> #include <glibmm/fileutils.h> #include <2geom/pathvector.h> -#include <2geom/bezier-curve.h> -#include <2geom/elliptical-arc.h> -#include <2geom/hvlinesegment.h> +#include <2geom/curves.h> #include <2geom/affine.h> #include <2geom/point.h> #include <2geom/path.h> @@ -531,7 +529,7 @@ feed_curve_to_cairo(cairo_t *cr, Geom::Curve const &c, Geom::Affine const & tran case 2: { Geom::QuadraticBezier const *quadratic_bezier = static_cast<Geom::QuadraticBezier const*>(&c); - std::vector<Geom::Point> points = quadratic_bezier->points(); + std::vector<Geom::Point> points = quadratic_bezier->controlPoints(); points[0] *= trans; points[1] *= trans; points[2] *= trans; @@ -554,7 +552,7 @@ feed_curve_to_cairo(cairo_t *cr, Geom::Curve const &c, Geom::Affine const & tran case 3: { Geom::CubicBezier const *cubic_bezier = static_cast<Geom::CubicBezier const*>(&c); - std::vector<Geom::Point> points = cubic_bezier->points(); + std::vector<Geom::Point> points = cubic_bezier->controlPoints(); //points[0] *= trans; // don't do this one here for fun: it is only needed for optimized strokes points[1] *= trans; points[2] *= trans; diff --git a/src/display/curve-test.h b/src/display/curve-test.h index 3d698ca07..f921a5361 100644 --- a/src/display/curve-test.h +++ b/src/display/curve-test.h @@ -16,17 +16,18 @@ public: CurveTest() : path4(Geom::Point(3,5)) // Just a moveto { // Closed path - path1.append(Geom::HLineSegment(Geom::Point(0,0),1)); - path1.append(Geom::VLineSegment(Geom::Point(1,0),1)); + path1.append(Geom::LineSegment(Geom::Point(0,0),Geom::Point(1,0))); + path1.append(Geom::LineSegment(Geom::Point(1,0),Geom::Point(1,1))); path1.close(); // Closed path (ClosingSegment is zero length) path2.append(Geom::LineSegment(Geom::Point(2,0),Geom::Point(3,0))); - // TODO fix path2.append(Geom::BezierCurve<3>(Geom::Point(3,0),Geom::Point(2,1),Geom::Point(1,1),Geom::Point(2,0))); + path2.append(Geom::CubicBezier(Geom::Point(3,0),Geom::Point(2,1),Geom::Point(1,1),Geom::Point(2,0))); path2.close(); // Open path - path3.append(Geom::SVGEllipticalArc(Geom::Point(4,0),1,2,M_PI,false,false,Geom::Point(5,1))); - path3.append(Geom::VLineSegment(Geom::Point(5,1),2), Geom::Path::STITCH_DISCONTINUOUS); - path3.append(Geom::HLineSegment(Geom::Point(6,4),2), Geom::Path::STITCH_DISCONTINUOUS); + path3.setStitching(true); + path3.append(Geom::EllipticalArc(Geom::Point(4,0),1,2,M_PI,false,false,Geom::Point(5,1))); + path3.append(Geom::LineSegment(Geom::Point(5,1),Geom::Point(5,2))); + path3.append(Geom::LineSegment(Geom::Point(6,4),Geom::Point(2,4))); } virtual ~CurveTest() {} @@ -49,17 +50,17 @@ public: TS_ASSERT_EQUALS(curve.get_segment_count() , 0u); } { // Individual paths - Geom::PathVector pv(1, Geom::Path()); + Geom::PathVector pv((Geom::Path())); pv[0] = path1; TS_ASSERT_EQUALS(SPCurve(pv).get_segment_count() , 3u); pv[0] = path2; - TS_ASSERT_EQUALS(SPCurve(pv).get_segment_count() , 3u); + TS_ASSERT_EQUALS(SPCurve(pv).get_segment_count() , 2u); pv[0] = path3; TS_ASSERT_EQUALS(SPCurve(pv).get_segment_count() , 4u); pv[0] = path4; TS_ASSERT_EQUALS(SPCurve(pv).get_segment_count() , 0u); pv[0].close(); - TS_ASSERT_EQUALS(SPCurve(pv).get_segment_count() , 1u); + TS_ASSERT_EQUALS(SPCurve(pv).get_segment_count() , 0u); } { // Combination Geom::PathVector pv; @@ -68,7 +69,7 @@ public: pv.push_back(path3); pv.push_back(path4); SPCurve curve(pv); - TS_ASSERT_EQUALS(curve.get_segment_count() , 10u); + TS_ASSERT_EQUALS(curve.get_segment_count() , 9u); } } @@ -86,7 +87,7 @@ public: TS_ASSERT_EQUALS(curve.nodes_in_path() , 1u); } { // Individual paths - Geom::PathVector pv(1, Geom::Path()); + Geom::PathVector pv((Geom::Path())); pv[0] = path1; TS_ASSERT_EQUALS(SPCurve(pv).nodes_in_path() , 3u); pv[0] = path2; @@ -112,28 +113,28 @@ public: void testIsEmpty() { TS_ASSERT(SPCurve(Geom::PathVector()).is_empty()); - TS_ASSERT(!SPCurve(Geom::PathVector(1, path1)).is_empty()); - TS_ASSERT(!SPCurve(Geom::PathVector(1, path2)).is_empty()); - TS_ASSERT(!SPCurve(Geom::PathVector(1, path3)).is_empty()); - TS_ASSERT(!SPCurve(Geom::PathVector(1, path4)).is_empty()); + TS_ASSERT(!SPCurve(path1).is_empty()); + TS_ASSERT(!SPCurve(path2).is_empty()); + TS_ASSERT(!SPCurve(path3).is_empty()); + TS_ASSERT(!SPCurve(path4).is_empty()); } void testIsClosed() { TS_ASSERT(!SPCurve(Geom::PathVector()).is_closed()); - Geom::PathVector pv(1, Geom::Path()); + Geom::PathVector pv((Geom::Path())); TS_ASSERT(!SPCurve(pv).is_closed()); pv[0].close(); TS_ASSERT(SPCurve(pv).is_closed()); - TS_ASSERT(SPCurve(Geom::PathVector(1, path1)).is_closed()); - TS_ASSERT(SPCurve(Geom::PathVector(1, path2)).is_closed()); - TS_ASSERT(!SPCurve(Geom::PathVector(1, path3)).is_closed()); - TS_ASSERT(!SPCurve(Geom::PathVector(1, path4)).is_closed()); + TS_ASSERT(SPCurve(path1).is_closed()); + TS_ASSERT(SPCurve(path2).is_closed()); + TS_ASSERT(!SPCurve(path3).is_closed()); + TS_ASSERT(!SPCurve(path4).is_closed()); } void testLastFirstSegment() { - Geom::PathVector pv(1, path4); + Geom::PathVector pv(path4); TS_ASSERT_EQUALS(SPCurve(pv).first_segment() , (void*)0); TS_ASSERT_EQUALS(SPCurve(pv).last_segment() , (void*)0); pv[0].close(); @@ -185,10 +186,10 @@ public: void testFirstPoint() { - TS_ASSERT_EQUALS(*(SPCurve(Geom::PathVector(1, path1)).first_point()) , Geom::Point(0,0)); - TS_ASSERT_EQUALS(*(SPCurve(Geom::PathVector(1, path2)).first_point()) , Geom::Point(2,0)); - TS_ASSERT_EQUALS(*(SPCurve(Geom::PathVector(1, path3)).first_point()) , Geom::Point(4,0)); - TS_ASSERT_EQUALS(*(SPCurve(Geom::PathVector(1, path4)).first_point()) , Geom::Point(3,5)); + TS_ASSERT_EQUALS(*(SPCurve(path1).first_point()) , Geom::Point(0,0)); + TS_ASSERT_EQUALS(*(SPCurve(path2).first_point()) , Geom::Point(2,0)); + TS_ASSERT_EQUALS(*(SPCurve(path3).first_point()) , Geom::Point(4,0)); + TS_ASSERT_EQUALS(*(SPCurve(path4).first_point()) , Geom::Point(3,5)); Geom::PathVector pv; TS_ASSERT(!SPCurve(pv).first_point()); pv.push_back(path1); @@ -201,10 +202,10 @@ public: void testLastPoint() { - TS_ASSERT_EQUALS(*(SPCurve(Geom::PathVector(1, path1)).last_point()) , Geom::Point(0,0)); - TS_ASSERT_EQUALS(*(SPCurve(Geom::PathVector(1, path2)).last_point()) , Geom::Point(2,0)); - TS_ASSERT_EQUALS(*(SPCurve(Geom::PathVector(1, path3)).last_point()) , Geom::Point(8,4)); - TS_ASSERT_EQUALS(*(SPCurve(Geom::PathVector(1, path4)).last_point()) , Geom::Point(3,5)); + TS_ASSERT_EQUALS(*(SPCurve(path1).last_point()) , Geom::Point(0,0)); + TS_ASSERT_EQUALS(*(SPCurve(path2).last_point()) , Geom::Point(2,0)); + TS_ASSERT_EQUALS(*(SPCurve(path3).last_point()) , Geom::Point(8,4)); + TS_ASSERT_EQUALS(*(SPCurve(path4).last_point()) , Geom::Point(3,5)); Geom::PathVector pv; TS_ASSERT(!SPCurve(pv).last_point()); pv.push_back(path1); @@ -217,10 +218,10 @@ public: void testSecondPoint() { - TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(1, path1)).second_point()) , Geom::Point(1,0)); - TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(1, path2)).second_point()) , Geom::Point(3,0)); - TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(1, path3)).second_point()) , Geom::Point(5,1)); - TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(1, path4)).second_point()) , Geom::Point(3,5)); + TS_ASSERT_EQUALS( *(SPCurve(path1).second_point()) , Geom::Point(1,0)); + TS_ASSERT_EQUALS( *(SPCurve(path2).second_point()) , Geom::Point(3,0)); + TS_ASSERT_EQUALS( *(SPCurve(path3).second_point()) , Geom::Point(5,1)); + TS_ASSERT_EQUALS( *(SPCurve(path4).second_point()) , Geom::Point(3,5)); Geom::PathVector pv; pv.push_back(path1); pv.push_back(path2); @@ -232,10 +233,10 @@ public: void testPenultimatePoint() { - TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(1, path1)).penultimate_point()) , Geom::Point(1,1)); - TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(1, path2)).penultimate_point()) , Geom::Point(3,0)); - TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(1, path3)).penultimate_point()) , Geom::Point(6,4)); - TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(1, path4)).penultimate_point()) , Geom::Point(3,5)); + TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(path1)).penultimate_point()) , Geom::Point(1,1)); + TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(path2)).penultimate_point()) , Geom::Point(3,0)); + TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(path3)).penultimate_point()) , Geom::Point(6,4)); + TS_ASSERT_EQUALS( *(SPCurve(Geom::PathVector(path4)).penultimate_point()) , Geom::Point(3,5)); Geom::PathVector pv; pv.push_back(path1); pv.push_back(path2); diff --git a/src/display/curve.cpp b/src/display/curve.cpp index 54a62939d..3024d1276 100644 --- a/src/display/curve.cpp +++ b/src/display/curve.cpp @@ -31,14 +31,12 @@ SPCurve::SPCurve() : _refcount(1), _pathv() -{ -} +{} SPCurve::SPCurve(Geom::PathVector const& pathv) : _refcount(1), _pathv(pathv) -{ -} +{} SPCurve * SPCurve::new_from_rect(Geom::Rect const &rect, bool all_four_sides) @@ -90,13 +88,7 @@ SPCurve::get_pathvector() const size_t SPCurve::get_segment_count() const { - size_t nr = 0; - for(Geom::PathVector::const_iterator it = _pathv.begin(); it != _pathv.end(); ++it) { - nr += (*it).size(); - - if (it->closed()) nr += 1; - } - return nr; + return _pathv.curveCount(); } /** @@ -211,8 +203,9 @@ SPCurve::moveto(double x, double y) void SPCurve::moveto(Geom::Point const &p) { - _pathv.push_back( Geom::Path() ); // for some reason Geom::Path(p) does not work... - _pathv.back().start(p); + Geom::Path path(p); + path.setStitching(true); + _pathv.push_back(path); } /** @@ -476,7 +469,7 @@ SPCurve::last_point() const SPCurve * SPCurve::create_reverse() const { - SPCurve *new_curve = new SPCurve(Geom::reverse_paths_and_order(_pathv)); + SPCurve *new_curve = new SPCurve(_pathv.reversed()); return new_curve; } @@ -635,20 +628,10 @@ SPCurve::nodes_in_path() const { size_t nr = 0; for(Geom::PathVector::const_iterator it = _pathv.begin(); it != _pathv.end(); ++it) { - nr += (*it).size(); - - nr++; // count last node (this works also for closed paths because although they don't have a 'last node', they do have an extra segment - - // do not count closing knot double for zero-length closing line segments - // however, if the path is only a moveto, and is closed, do not subtract 1 (otherwise the result will be zero nodes) - if ( it->closed() - && ((*it).size() != 0) ) - { - Geom::Curve const &c = it->back_closed(); - if (are_near(c.initialPoint(), c.finalPoint())) { - nr--; - } - } + // if the path does not have any segments, it is a naked moveto, + // and therefore any path has at least one valid node + size_t psize = std::max<size_t>(1, it->size_closed()); + nr += psize; } return nr; diff --git a/src/display/curve.h b/src/display/curve.h index 5fad75b18..42b899210 100644 --- a/src/display/curve.h +++ b/src/display/curve.h @@ -13,7 +13,7 @@ #ifndef SEEN_DISPLAY_CURVE_H #define SEEN_DISPLAY_CURVE_H -#include <2geom/forward.h> +#include <2geom/pathvector.h> #include <cstddef> #include <boost/optional.hpp> diff --git a/src/display/drawing-image.cpp b/src/display/drawing-image.cpp index 8fe337959..1594614ac 100644 --- a/src/display/drawing-image.cpp +++ b/src/display/drawing-image.cpp @@ -172,7 +172,7 @@ static double distance_to_segment (Geom::Point const &p, Geom::Point const &a1, Geom::Point const &a2) { Geom::LineSegment l(a1, a2); - Geom::Point np = l.pointAt(l.nearestPoint(p)); + Geom::Point np = l.pointAt(l.nearestTime(p)); return Geom::distance(np, p); } diff --git a/src/display/snap-indicator.cpp b/src/display/snap-indicator.cpp index 8c0c8163f..926b35599 100644 --- a/src/display/snap-indicator.cpp +++ b/src/display/snap-indicator.cpp @@ -258,6 +258,19 @@ SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const &p, bool pre_snap const int timeout_val = 4000; + // The snap indicator will be deleted after some time-out, and sp_canvas_item_dispose + // will be called. This will set canvas->current_item to NULL if the snap indicator was + // the current item, after which any events will go to the root handler instead of any + // item handler. Dragging an object which has just snapped might therefore not be possible + // without selecting / repicking it again. To avoid this, we make sure here that the + // snap indicator will never be picked, and will therefore never be the current item. + // Reported bugs: + // - scrolling when hovering above a pre-snap indicator won't work (for example) + // (https://bugs.launchpad.net/inkscape/+bug/522335/comments/8) + // - dragging doesn't work without repicking + // (https://bugs.launchpad.net/inkscape/+bug/1420301/comments/15) + SP_CTRL(canvasitem)->pickable = false; + SP_CTRL(canvasitem)->moveto(p.getPoint()); _snaptarget = _desktop->add_temporary_canvasitem(canvasitem, timeout_val); _snaptarget_is_presnap = pre_snap; @@ -282,6 +295,7 @@ SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const &p, bool pre_snap SPCanvasItem *canvas_tooltip = sp_canvastext_new(_desktop->getTempGroup(), _desktop, tooltip_pos, tooltip_str); sp_canvastext_set_fontsize(SP_CANVASTEXT(canvas_tooltip), fontsize); + SP_CANVASTEXT(canvas_tooltip)->pickable = false; // See the extensive comment above SP_CANVASTEXT(canvas_tooltip)->rgba = 0xffffffff; SP_CANVASTEXT(canvas_tooltip)->outline = false; SP_CANVASTEXT(canvas_tooltip)->background = true; @@ -306,6 +320,7 @@ SnapIndicator::set_new_snaptarget(Inkscape::SnappedPoint const &p, bool pre_snap SP_CTRLRECT(box)->setRectangle(*bbox); SP_CTRLRECT(box)->setColor(pre_snap ? 0x7f7f7fff : 0xff0000ff, 0, 0); SP_CTRLRECT(box)->setDashed(true); + SP_CTRLRECT(box)->pickable = false; // See the extensive comment above sp_canvas_item_move_to_z(box, 0); _snaptarget_bbox = _desktop->add_temporary_canvasitem(box, timeout_val); } diff --git a/src/display/sp-canvas-item.h b/src/display/sp-canvas-item.h index 3b7b7bd4f..3e9e085a0 100644 --- a/src/display/sp-canvas-item.h +++ b/src/display/sp-canvas-item.h @@ -69,7 +69,12 @@ struct SPCanvasItem { gboolean visible; gboolean need_update; gboolean need_affine; - + + // If true, then SPCanvasGroup::point() and sp_canvas_item_invoke_point() will calculate + // the distance to the pointer, such that this item can be picked in pickCurrentItem() + // Only if an item can be picked, then it can be set as current_item and receive events! + bool pickable; + bool in_destruction; }; diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp index 305b0950a..5efc4ce86 100644 --- a/src/display/sp-canvas.cpp +++ b/src/display/sp-canvas.cpp @@ -375,6 +375,7 @@ sp_canvas_item_init(SPCanvasItem *item) // used for rubberbanding, path outline, etc. item->visible = TRUE; item->in_destruction = false; + item->pickable = true; } SPCanvasItem *sp_canvas_item_new(SPCanvasGroup *parent, GType type, gchar const *first_arg_name, ...) @@ -728,7 +729,6 @@ bool sp_canvas_item_is_visible(SPCanvasItem *item) return item->visible; } - /** * Sets visible flag on item and requests a redraw. */ @@ -1009,21 +1009,21 @@ double SPCanvasGroup::point(SPCanvasItem *item, Geom::Point p, SPCanvasItem **ac if ((child->x1 <= x2) && (child->y1 <= y2) && (child->x2 >= x1) && (child->y2 >= y1)) { SPCanvasItem *point_item = NULL; // cater for incomplete item implementations - int has_point; - if (child->visible && SP_CANVAS_ITEM_GET_CLASS(child)->point) { + int pickable; + if (child->visible && child->pickable && SP_CANVAS_ITEM_GET_CLASS(child)->point) { dist = sp_canvas_item_invoke_point(child, p, &point_item); - has_point = TRUE; + pickable = TRUE; } else { - has_point = FALSE; + pickable = FALSE; } - // This metric should be improved, because in case of (partly) overlapping items we will now + // TODO: This metric should be improved, because in case of (partly) overlapping items we will now // always select the last one that has been added to the group. We could instead select the one // of which the center is the closest, for example. One can then move to the center // of the item to be focused, and have that one selected. Of course this will only work if the // centers are not coincident, but at least it's better than what we have now. // See the extensive comment in Inkscape::SelTrans::_updateHandles() - if (has_point && point_item && ((int) (dist + 0.5) <= item->canvas->close_enough)) { + if (pickable && point_item && ((int) (dist + 0.5) <= item->canvas->close_enough)) { best = dist; *actual_item = point_item; } @@ -1480,20 +1480,6 @@ int SPCanvasImpl::emitEvent(SPCanvas *canvas, GdkEvent *event) if (canvas->grabbed_item && !is_descendant (canvas->current_item, canvas->grabbed_item)) { item = canvas->grabbed_item; } else { - // Make sure that current_item is up-to-date. If a snap indicator was just deleted, then - // sp_canvas_item_dispose has been called and there is no current_item specified. We need - // that though because otherwise we don't know where to send this event to, leading to a - // lost event. We can't wait for idle events to have current_item updated, we need it now! - // Otherwise, scrolling when hovering above a pre-snap indicator won't work (for example) - // See this bug report: https://bugs.launchpad.net/inkscape/+bug/522335/comments/8 - if (canvas->need_repick && !canvas->in_repick && event->type == GDK_SCROLL) { - // To avoid side effects, we'll only do this for scroll events, because this is the - // only thing we want to fix here. An example of a reported side effect is that - // otherwise selection of nodes in the node editor by dragging a rectangle using a - // tablet will break - canvas->need_repick = FALSE; - pickCurrentItem(canvas, reinterpret_cast<GdkEvent *>(event)); - } item = canvas->current_item; } @@ -1527,7 +1513,7 @@ int SPCanvasImpl::pickCurrentItem(SPCanvas *canvas, GdkEvent *event) { int button_down = 0; - if (!canvas->root) // canvas may have already be destroyed by closing desktop durring interrupted display! + if (!canvas->root) // canvas may have already be destroyed by closing desktop during interrupted display! return FALSE; int retval = FALSE; diff --git a/src/document-undo.cpp b/src/document-undo.cpp index d4015bafb..59e060cd5 100644 --- a/src/document-undo.cpp +++ b/src/document-undo.cpp @@ -217,6 +217,25 @@ static void finish_incomplete_transaction(SPDocument &doc) { } } +static void perform_document_update(SPDocument &doc) { + sp_repr_begin_transaction(doc.rdoc); + doc.ensureUpToDate(); + + Inkscape::XML::Event *update_log=sp_repr_commit_undoable(doc.rdoc); + if (update_log != NULL) { + g_warning("Document was modified while being updated after undo operation"); + sp_repr_debug_print_log(update_log); + + //Coalesce the update changes with the last action performed by user + Inkscape::Event* undo_stack_top = (Inkscape::Event *)doc.priv->undo->data; + if (undo_stack_top) { + undo_stack_top->event = sp_repr_coalesce_log(undo_stack_top->event, update_log); + } else { + sp_repr_free_log(update_log); + } + } +} + gboolean Inkscape::DocumentUndo::undo(SPDocument *doc) { using Inkscape::Debug::EventTracker; @@ -241,7 +260,8 @@ gboolean Inkscape::DocumentUndo::undo(SPDocument *doc) Inkscape::Event *log=(Inkscape::Event *)doc->priv->undo->data; doc->priv->undo = g_slist_remove (doc->priv->undo, log); sp_repr_undo_log (log->event); - //doc->_updateDocument(); + perform_document_update(*doc); + doc->priv->redo = g_slist_prepend (doc->priv->redo, log); doc->setModifiedSinceSave(); diff --git a/src/extension/implementation/implementation.h b/src/extension/implementation/implementation.h index fb323cd78..f6f933aaf 100644 --- a/src/extension/implementation/implementation.h +++ b/src/extension/implementation/implementation.h @@ -15,14 +15,7 @@ #include <vector> #include <sigc++/signal.h> #include <glibmm/value.h> - -namespace Geom { - class Affine; - class OptRect; - class Path; - typedef std::vector<Path> PathVector; - class Point; -} +#include <2geom/forward.h> namespace Gtk { class Widget; diff --git a/src/extension/internal/emf-print.cpp b/src/extension/internal/emf-print.cpp index 40d0955e3..5b8aae655 100644 --- a/src/extension/internal/emf-print.cpp +++ b/src/extension/internal/emf-print.cpp @@ -860,7 +860,7 @@ Geom::Path PrintEmf::pathv_to_rect(Geom::PathVector const &pathv, bool *is_rect, /* Get the ends of the LAST line segment. Find minimum rotation to align rectangle with X,Y axes. (Very degenerate if it is rotated 45 degrees.) */ *angle = 10.0; /* must be > than the actual angle in radians. */ - for(Geom::Path::const_iterator cit = pR.begin(); cit != pR.end_open(); ++cit){ + for(Geom::Path::iterator cit = pR.begin(); cit != pR.end_open(); ++cit){ P1_trail = cit->initialPoint(); P1 = cit->finalPoint(); v1 = unit_vector(P1 - P1_trail); @@ -874,7 +874,7 @@ Geom::Path PrintEmf::pathv_to_rect(Geom::PathVector const &pathv, bool *is_rect, double convert = 36000.0/ (2.0 * M_PI); *angle = round(*angle * convert)/convert; - for(Geom::Path::const_iterator cit = pR.begin(); cit != pR.end_open();++cit) { + for(Geom::Path::iterator cit = pR.begin(); cit != pR.end_open();++cit) { P1_lead = cit->finalPoint(); v1 = unit_vector(P1 - P1_trail); v2 = unit_vector(P1_lead - P1 ); @@ -924,7 +924,7 @@ int PrintEmf::vector_rect_alignment(double angle, Geom::Point vtest){ */ Geom::Point PrintEmf::get_pathrect_corner(Geom::Path pathRect, double angle, int corner){ Geom::Point center(0,0); - for(Geom::Path::const_iterator cit = pathRect.begin(); cit != pathRect.end_open(); ++cit) { + for(Geom::Path::iterator cit = pathRect.begin(); cit != pathRect.end_open(); ++cit) { center += cit->initialPoint()/4.0; } @@ -952,7 +952,7 @@ Geom::Point PrintEmf::get_pathrect_corner(Geom::Path pathRect, double angle, int Geom::Point v1 = Geom::Point(1,0) * Geom::Rotate(-angle); // unit horizontal side (sign change because Y increases DOWN) Geom::Point v2 = Geom::Point(0,1) * Geom::Rotate(-angle); // unit vertical side (sign change because Y increases DOWN) Geom::Point P1; - for(Geom::Path::const_iterator cit = pathRect.begin(); cit != pathRect.end_open(); ++cit) { + for(Geom::Path::iterator cit = pathRect.begin(); cit != pathRect.end_open(); ++cit) { P1 = cit->initialPoint(); if ( ( LR == (dot(P1 - center,v1) > 0 ? 0 : 1) ) @@ -1494,11 +1494,11 @@ bool PrintEmf::print_simple_shape(Geom::PathVector const &pathv, const Geom::Aff int curves = 0; char *rec = NULL; - for (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { + for (Geom::PathVector::iterator pit = pv.begin(); pit != pv.end(); ++pit) { moves++; nodes++; - for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) { + for (Geom::Path::iterator cit = pit->begin(); cit != pit->end_open(); ++cit) { nodes++; if (is_straight_curve(*cit)) { @@ -1519,7 +1519,7 @@ bool PrintEmf::print_simple_shape(Geom::PathVector const &pathv, const Geom::Aff /** * For all Subpaths in the <path> */ - for (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { + for (Geom::PathVector::iterator pit = pv.begin(); pit != pv.end(); ++pit) { using Geom::X; using Geom::Y; @@ -1538,7 +1538,7 @@ bool PrintEmf::print_simple_shape(Geom::PathVector const &pathv, const Geom::Aff /** * For all segments in the subpath */ - for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) { + for (Geom::Path::iterator cit = pit->begin(); cit != pit->end_open(); ++cit) { if (is_straight_curve(*cit)) { //Geom::Point p0 = cit->initialPoint(); Geom::Point p1 = cit->finalPoint(); @@ -1557,7 +1557,7 @@ bool PrintEmf::print_simple_shape(Geom::PathVector const &pathv, const Geom::Aff lpPoints[i].y = y1; i = i + 1; } else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*cit)) { - std::vector<Geom::Point> points = cubic->points(); + std::vector<Geom::Point> points = cubic->controlPoints(); //Geom::Point p0 = points[0]; Geom::Point p1 = points[1]; Geom::Point p2 = points[2]; @@ -1846,7 +1846,7 @@ unsigned int PrintEmf::draw_pathv_to_EMF(Geom::PathVector const &pathv, const Ge g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRLINETO_set"); } } else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*cit)) { - std::vector<Geom::Point> points = cubic->points(); + std::vector<Geom::Point> points = cubic->controlPoints(); //Geom::Point p0 = points[0]; Geom::Point p1 = points[1]; Geom::Point p2 = points[2]; diff --git a/src/extension/internal/javafx-out.cpp b/src/extension/internal/javafx-out.cpp index 19946022c..386bde1d6 100644 --- a/src/extension/internal/javafx-out.cpp +++ b/src/extension/internal/javafx-out.cpp @@ -35,8 +35,7 @@ #include <extension/system.h> #include <2geom/pathvector.h> #include <2geom/rect.h> -#include <2geom/bezier-curve.h> -#include <2geom/hvlinesegment.h> +#include <2geom/curves.h> #include "helper/geom.h" #include "helper/geom-curves.h" #include <io/sys.h> @@ -531,9 +530,7 @@ bool JavaFXOutput::doCurve(SPItem *item, const String &id) for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) { //### LINE - if ( dynamic_cast<Geom::LineSegment const *> (&*cit) || - dynamic_cast<Geom::HLineSegment const *> (&*cit) || - dynamic_cast<Geom::VLineSegment const *> (&*cit) ) + if ( dynamic_cast<Geom::LineSegment const *> (&*cit) ) { Geom::Point p = cit->finalPoint(); out(" LineTo {\n"); @@ -545,7 +542,7 @@ bool JavaFXOutput::doCurve(SPItem *item, const String &id) //### BEZIER else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit)) { - std::vector<Geom::Point> points = cubic->points(); + std::vector<Geom::Point> points = cubic->controlPoints(); Geom::Point p1 = points[1]; Geom::Point p2 = points[2]; Geom::Point p3 = points[3]; diff --git a/src/extension/internal/latex-pstricks.cpp b/src/extension/internal/latex-pstricks.cpp index 6aaa1bca4..ae8f30a5c 100644 --- a/src/extension/internal/latex-pstricks.cpp +++ b/src/extension/internal/latex-pstricks.cpp @@ -17,8 +17,7 @@ #include <2geom/pathvector.h> #include <2geom/sbasis-to-bezier.h> -#include <2geom/bezier-curve.h> -#include <2geom/hvlinesegment.h> +#include <2geom/curves.h> #include <errno.h> #include <signal.h> #include "util/units.h" @@ -300,7 +299,7 @@ PrintLatex::print_2geomcurve(SVGOStringStream &os, Geom::Curve const &c) os << "\\lineto(" << c.finalPoint()[X] << "," << c.finalPoint()[Y] << ")\n"; } else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&c)) { - std::vector<Geom::Point> points = cubic_bezier->points(); + std::vector<Geom::Point> points = cubic_bezier->controlPoints(); os << "\\curveto(" << points[1][X] << "," << points[1][Y] << ")(" << points[2][X] << "," << points[2][Y] << ")(" << points[3][X] << "," << points[3][Y] << ")\n"; diff --git a/src/extension/internal/latex-text-renderer.cpp b/src/extension/internal/latex-text-renderer.cpp index 1026f51ad..5933dd526 100644 --- a/src/extension/internal/latex-text-renderer.cpp +++ b/src/extension/internal/latex-text-renderer.cpp @@ -409,7 +409,8 @@ Flowing in rectangle is possible, not in arb shape. return; // don't know how to handle non-rect frames yet. is quite uncommon for latex users i think } - Geom::Rect framebox = frame->getRect() * transform(); + // We will transform the coordinates + Geom::Rect framebox = frame->getRect(); // get position and alignment // Align on topleft corner. @@ -429,7 +430,10 @@ Flowing in rectangle is possible, not in arb shape. // no need to add LaTeX code for standard justified output :) break; } - Geom::Point pos(framebox.corner(3)); //topleft corner + + // The topleft Corner was calculated after rotating the text which results in a wrong Coordinate. + // Now, the topleft Corner is rotated after calculating it + Geom::Point pos(framebox.corner(0) * transform()); //topleft corner // determine color and transparency (for now, use rgb color model as it is most native to Inkscape) bool has_color = false; // if the item has no color set, don't force black color @@ -472,7 +476,9 @@ Flowing in rectangle is possible, not in arb shape. os << "\\rotatebox{" << degrees << "}{"; } os << "\\makebox(0,0)" << alignment << "{"; - os << "\\begin{minipage}{" << framebox.width() << "\\unitlength}"; + + // Scale the x width correctly + os << "\\begin{minipage}{" << framebox.width() * transform().expansionX() << "\\unitlength}"; os << justification; // Walk through all spans in the text object. diff --git a/src/extension/internal/metafile-print.cpp b/src/extension/internal/metafile-print.cpp index 73d63f27d..2fb36be85 100644 --- a/src/extension/internal/metafile-print.cpp +++ b/src/extension/internal/metafile-print.cpp @@ -389,7 +389,7 @@ Geom::PathVector PrintMetafile::center_ellipse_as_SVG_PathV(Geom::Point ctr, dou char text[256]; sprintf(text, " M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z", x1, y1, rx, ry, F * 360. / (2.*M_PI), x2, y2, rx, ry, F * 360. / (2.*M_PI), x1, y1); - std::vector<Geom::Path> outres = Geom::parse_svg_path(text); + Geom::PathVector outres = Geom::parse_svg_path(text); return outres; } @@ -419,7 +419,7 @@ Geom::PathVector PrintMetafile::center_elliptical_ring_as_SVG_PathV(Geom::Point sprintf(text, " M %f,%f A %f %f %f 0 1 %f %f A %f %f %f 0 1 %f %f z M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z", x11, y11, rx1, ry1, degrot, x12, y12, rx1, ry1, degrot, x11, y11, x21, y21, rx2, ry2, degrot, x22, y22, rx2, ry2, degrot, x21, y21); - std::vector<Geom::Path> outres = Geom::parse_svg_path(text); + Geom::PathVector outres = Geom::parse_svg_path(text); return outres; } @@ -440,7 +440,7 @@ Geom::PathVector PrintMetafile::center_elliptical_hole_as_SVG_PathV(Geom::Point char text[256]; sprintf(text, " M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z M 50000,50000 50000,-50000 -50000,-50000 -50000,50000 z", x1, y1, rx, ry, F * 360. / (2.*M_PI), x2, y2, rx, ry, F * 360. / (2.*M_PI), x1, y1); - std::vector<Geom::Path> outres = Geom::parse_svg_path(text); + Geom::PathVector outres = Geom::parse_svg_path(text); return outres; } @@ -452,7 +452,7 @@ width vector to side edge */ Geom::PathVector PrintMetafile::rect_cutter(Geom::Point ctr, Geom::Point pos, Geom::Point neg, Geom::Point width) { - std::vector<Geom::Path> outres; + Geom::PathVector outres; Geom::Path cutter; cutter.start(ctr + pos - width); cutter.appendNew<Geom::LineSegment>(ctr + pos + width); diff --git a/src/extension/internal/odf.cpp b/src/extension/internal/odf.cpp index 52fabcf3c..6904eb2fd 100644 --- a/src/extension/internal/odf.cpp +++ b/src/extension/internal/odf.cpp @@ -55,8 +55,7 @@ #include <style.h> #include "display/curve.h" #include <2geom/pathvector.h> -#include <2geom/bezier-curve.h> -#include <2geom/hvlinesegment.h> +#include <2geom/curves.h> #include <2geom/transforms.h> #include <helper/geom.h> #include "helper/geom-curves.h" @@ -1299,7 +1298,7 @@ writePath(Writer &outs, Geom::PathVector const &pathv, outs.printf("L %.3f %.3f ", destx, desty); } else if(Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit)) { - std::vector<Geom::Point> points = cubic->points(); + std::vector<Geom::Point> points = cubic->controlPoints(); for (unsigned i = 1; i <= 3; i++) { if (fabs(points[i][X])<1.0) points[i][X] = 0.0; // Why is this needed? Shouldn't we just round all numbers then? if (fabs(points[i][Y])<1.0) points[i][Y] = 0.0; diff --git a/src/extension/internal/pov-out.cpp b/src/extension/internal/pov-out.cpp index 3d149928f..bd2168b68 100644 --- a/src/extension/internal/pov-out.cpp +++ b/src/extension/internal/pov-out.cpp @@ -30,8 +30,7 @@ #include <extension/system.h> #include <2geom/pathvector.h> #include <2geom/rect.h> -#include <2geom/bezier-curve.h> -#include <2geom/hvlinesegment.h> +#include <2geom/curves.h> #include "helper/geom.h" #include "helper/geom-curves.h" #include <io/sys.h> @@ -388,7 +387,7 @@ bool PovOutput::doCurve(SPItem *item, const String &id) } else if(Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit)) { - std::vector<Geom::Point> points = cubic->points(); + std::vector<Geom::Point> points = cubic->controlPoints(); Geom::Point p0 = points[0]; Geom::Point p1 = points[1]; Geom::Point p2 = points[2]; diff --git a/src/extension/internal/wmf-print.cpp b/src/extension/internal/wmf-print.cpp index 768909173..431053085 100644 --- a/src/extension/internal/wmf-print.cpp +++ b/src/extension/internal/wmf-print.cpp @@ -29,14 +29,13 @@ #endif -#include "2geom/sbasis-to-bezier.h" -#include "2geom/svg-elliptical-arc.h" - -#include "2geom/path.h" -#include "2geom/pathvector.h" -#include "2geom/rect.h" -#include "2geom/bezier-curve.h" -#include "2geom/hvlinesegment.h" +#include <2geom/sbasis-to-bezier.h> +#include <2geom/elliptical-arc.h> + +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/rect.h> +#include <2geom/curves.h> #include "helper/geom.h" #include "helper/geom-curves.h" #include "sp-item.h" @@ -59,7 +58,7 @@ #include "display/cairo-utils.h" #include "splivarot.h" // pieces for union on shapes -#include "2geom/svg-path-parser.h" // to get from SVG text to Geom::Path +#include <2geom/svg-path-parser.h> // to get from SVG text to Geom::Path #include "display/canvas-bpath.h" // for SPWindRule #include "display/cairo-utils.h" // for Inkscape::Pixbuf::PF_CAIRO @@ -984,7 +983,7 @@ bool PrintWmf::print_simple_shape(Geom::PathVector const &pathv, const Geom::Aff lpPoints[i].y = y1; i = i + 1; } else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*cit)) { - std::vector<Geom::Point> points = cubic->points(); + std::vector<Geom::Point> points = cubic->controlPoints(); //Geom::Point p0 = points[0]; Geom::Point p1 = points[1]; Geom::Point p2 = points[2]; diff --git a/src/extension/param/color.cpp b/src/extension/param/color.cpp index 3162e8a40..0b58c5011 100644 --- a/src/extension/param/color.cpp +++ b/src/extension/param/color.cpp @@ -86,9 +86,11 @@ Gtk::Widget *ParamColor::get_widget( SPDocument * /*doc*/, Inkscape::XML::Node * { using Inkscape::UI::Widget::ColorNotebook; - if (_gui_hidden) return NULL; + if (_gui_hidden) return NULL; - _changeSignal = new sigc::signal<void>(*changeSignal); + if (changeSignal) { + _changeSignal = new sigc::signal<void>(*changeSignal); + } if (_color.value() < 1) { _color_changed.block(true); diff --git a/src/gradient-chemistry.cpp b/src/gradient-chemistry.cpp index d90972961..409e9f0e6 100644 --- a/src/gradient-chemistry.cpp +++ b/src/gradient-chemistry.cpp @@ -1068,7 +1068,7 @@ void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint poi // using X-coordinates only to determine the offset, assuming p has been snapped to the vector from begin to end. Geom::Point begin(lg->x1.computed, lg->y1.computed); Geom::Point end(lg->x2.computed, lg->y2.computed); - double offset = Geom::LineSegment(begin, end).nearestPoint(p); + double offset = Geom::LineSegment(begin, end).nearestTime(p); SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (lg, false); lg->ensureVector(); lg->vector.stops.at(point_i).offset = offset; @@ -1163,7 +1163,7 @@ void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint poi { Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed); Geom::Point end = Geom::Point (rg->cx.computed + rg->r.computed, rg->cy.computed); - double offset = Geom::LineSegment(start, end).nearestPoint(p); + double offset = Geom::LineSegment(start, end).nearestTime(p); SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (rg, false); rg->ensureVector(); rg->vector.stops.at(point_i).offset = offset; @@ -1180,7 +1180,7 @@ void sp_item_gradient_set_coords(SPItem *item, GrPointType point_type, guint poi { Geom::Point start = Geom::Point (rg->cx.computed, rg->cy.computed); Geom::Point end = Geom::Point (rg->cx.computed, rg->cy.computed - rg->r.computed); - double offset = Geom::LineSegment(start, end).nearestPoint(p); + double offset = Geom::LineSegment(start, end).nearestTime(p); SPGradient *vector = sp_gradient_get_forked_vector_if_necessary(rg, false); rg->ensureVector(); rg->vector.stops.at(point_i).offset = offset; diff --git a/src/gradient-drag.cpp b/src/gradient-drag.cpp index b22714959..b5a988729 100644 --- a/src/gradient-drag.cpp +++ b/src/gradient-drag.cpp @@ -378,7 +378,7 @@ SPStop *GrDrag::addStopNearPoint(SPItem *item, Geom::Point mouse_p, double toler Geom::Point begin = getGradientCoords(item, POINT_LG_BEGIN, 0, fill_or_stroke); Geom::Point end = getGradientCoords(item, POINT_LG_END, 0, fill_or_stroke); Geom::LineSegment ls(begin, end); - double offset = ls.nearestPoint(mouse_p); + double offset = ls.nearestTime(mouse_p); Geom::Point nearest = ls.pointAt(offset); double dist_screen = Geom::distance(mouse_p, nearest); if ( dist_screen < tolerance ) { @@ -391,7 +391,7 @@ SPStop *GrDrag::addStopNearPoint(SPItem *item, Geom::Point mouse_p, double toler Geom::Point begin = getGradientCoords(item, POINT_RG_CENTER, 0, fill_or_stroke); Geom::Point end = getGradientCoords(item, POINT_RG_R1, 0, fill_or_stroke); Geom::LineSegment ls(begin, end); - double offset = ls.nearestPoint(mouse_p); + double offset = ls.nearestTime(mouse_p); Geom::Point nearest = ls.pointAt(offset); double dist_screen = Geom::distance(mouse_p, nearest); if ( dist_screen < tolerance ) { @@ -403,7 +403,7 @@ SPStop *GrDrag::addStopNearPoint(SPItem *item, Geom::Point mouse_p, double toler } else { end = getGradientCoords(item, POINT_RG_R2, 0, fill_or_stroke); ls = Geom::LineSegment(begin, end); - offset = ls.nearestPoint(mouse_p); + offset = ls.nearestTime(mouse_p); nearest = ls.pointAt(offset); dist_screen = Geom::distance(mouse_p, nearest); if ( dist_screen < tolerance ) { @@ -442,7 +442,7 @@ SPStop *GrDrag::addStopNearPoint(SPItem *item, Geom::Point mouse_p, double toler p[2] = patch.getPoint( 0, 2 ) * transform; p[3] = patch.getPoint( 0, 3 ) * transform; Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); - Geom::Coord coord = b.nearestPoint( mouse_p ); + Geom::Coord coord = b.nearestTime( mouse_p ); Geom::Point nearest = b( coord ); double dist_screen = Geom::L2 ( mouse_p - nearest ); if ( dist_screen < closest ) { @@ -460,7 +460,7 @@ SPStop *GrDrag::addStopNearPoint(SPItem *item, Geom::Point mouse_p, double toler p[2] = patch.getPoint( 1, 2 ) * transform; p[3] = patch.getPoint( 1, 3 ) * transform; Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); - Geom::Coord coord = b.nearestPoint( mouse_p ); + Geom::Coord coord = b.nearestTime( mouse_p ); Geom::Point nearest = b( coord ); double dist_screen = Geom::L2 ( mouse_p - nearest ); if ( dist_screen < closest ) { @@ -478,7 +478,7 @@ SPStop *GrDrag::addStopNearPoint(SPItem *item, Geom::Point mouse_p, double toler p[2] = patch.getPoint( 2, 2 ) * transform; p[3] = patch.getPoint( 2, 3 ) * transform; Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); - Geom::Coord coord = b.nearestPoint( mouse_p ); + Geom::Coord coord = b.nearestTime( mouse_p ); Geom::Point nearest = b( coord ); double dist_screen = Geom::L2 ( mouse_p - nearest ); if ( dist_screen < closest ) { @@ -496,7 +496,7 @@ SPStop *GrDrag::addStopNearPoint(SPItem *item, Geom::Point mouse_p, double toler p[2] = patch.getPoint( 3, 2 ) * transform; p[3] = patch.getPoint( 3, 3 ) * transform; Geom::BezierCurveN<3> b( p[0], p[1], p[2], p[3] ); - Geom::Coord coord = b.nearestPoint( mouse_p ); + Geom::Coord coord = b.nearestTime( mouse_p ); Geom::Point nearest = b( coord ); double dist_screen = Geom::L2 ( mouse_p - nearest ); if ( dist_screen < closest ) { @@ -603,7 +603,7 @@ bool GrDrag::dropColor(SPItem */*item*/, gchar const *c, Geom::Point p) for (GSList *l = lines; (l != NULL) && (!over_line); l = l->next) { SPCtrlLine *line = (SPCtrlLine*) l->data; Geom::LineSegment ls(line->s, line->e); - Geom::Point nearest = ls.pointAt(ls.nearestPoint(p)); + Geom::Point nearest = ls.pointAt(ls.nearestTime(p)); double dist_screen = Geom::L2(p - nearest) * desktop->current_zoom(); if (line->item && dist_screen < 5) { SPStop *stop = addStopNearPoint(line->item, p, 5/desktop->current_zoom()); @@ -1018,10 +1018,10 @@ static void gr_knot_moved_midpoint_handler(SPKnot */*knot*/, Geom::Point const & if (state & GDK_CONTROL_MASK) { Geom::LineSegment ls(low_lim, high_lim); - p = ls.pointAt(round(ls.nearestPoint(p) / snap_fraction) * snap_fraction); + p = ls.pointAt(round(ls.nearestTime(p) / snap_fraction) * snap_fraction); } else { Geom::LineSegment ls(low_lim, high_lim); - p = ls.pointAt(ls.nearestPoint(p)); + p = ls.pointAt(ls.nearestTime(p)); if (!(state & GDK_SHIFT_MASK)) { Inkscape::Snapper::SnapConstraint cl(low_lim, high_lim - low_lim); SPDesktop *desktop = dragger->parent->desktop; @@ -2394,7 +2394,7 @@ void GrDrag::selected_move(double x, double y, bool write_repr, bool scale_radia gr_midpoint_limits(dragger, server, &begin, &end, &low_lim, &high_lim, &moving); Geom::LineSegment ls(low_lim, high_lim); - Geom::Point p = ls.pointAt(ls.nearestPoint(dragger->point + Geom::Point(x,y))); + Geom::Point p = ls.pointAt(ls.nearestTime(dragger->point + Geom::Point(x,y))); Geom::Point displacement = p - dragger->point; for (GSList const* i = moving; i != NULL; i = i->next) { diff --git a/src/helper/geom-curves.h b/src/helper/geom-curves.h index 4586a346c..7357403f7 100644 --- a/src/helper/geom-curves.h +++ b/src/helper/geom-curves.h @@ -14,15 +14,13 @@ * Released under GNU GPL */ -#include <2geom/hvlinesegment.h> #include <2geom/line.h> #include <2geom/bezier-curve.h> /// \todo un-inline this function -inline bool is_straight_curve(Geom::Curve const & c) { - if( dynamic_cast<Geom::LineSegment const*>(&c) || - dynamic_cast<Geom::HLineSegment const*>(&c) || - dynamic_cast<Geom::VLineSegment const*>(&c) ) +inline bool is_straight_curve(Geom::Curve const & c) +{ + if( dynamic_cast<Geom::LineSegment const*>(&c) ) { return true; } @@ -31,7 +29,7 @@ inline bool is_straight_curve(Geom::Curve const & c) { Geom::BezierCurve const *curve = dynamic_cast<Geom::BezierCurve const *>(&c); if (curve) { Geom::Line line(curve->initialPoint(), curve->finalPoint()); - std::vector<Geom::Point> pts = curve->points(); + std::vector<Geom::Point> pts = curve->controlPoints(); for (unsigned i = 1; i < pts.size() - 1; ++i) { if (!are_near(pts[i], line)) return false; diff --git a/src/helper/geom-pathstroke.cpp b/src/helper/geom-pathstroke.cpp index 930737572..c73a9e9e7 100644 --- a/src/helper/geom-pathstroke.cpp +++ b/src/helper/geom-pathstroke.cpp @@ -1,7 +1,8 @@ -/* Author: +/* Authors: * Liam P. White + * Tavmjong Bah * - * Copyright (C) 2014-2015 Author + * Copyright (C) 2014-2015 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -10,89 +11,20 @@ #include <2geom/path-sink.h> #include <2geom/point.h> #include <2geom/bezier-curve.h> -#include <2geom/svg-elliptical-arc.h> +#include <2geom/elliptical-arc.h> #include <2geom/sbasis-to-bezier.h> // cubicbezierpath_from_sbasis #include <2geom/path-intersection.h> +#include <2geom/circle.h> #include "helper/geom-pathstroke.h" namespace Geom { -// 2geom/circle-circle.cpp, no header -int circle_circle_intersection(Point X0, double r0, Point X1, double r1, Point &p0, Point &p1); - -/** - * Determine the intersection points between a circle C0 and a line defined - * by two points, X0 and X1. - * - * Which intersection point is assigned to p0 or p1 is unspecified, and callers - * should not depend on any particular intersection always being assigned to p0. - * - * Returns: - * If the line and circle do not cross, 0 is returned. - * If solution(s) exist, 2 is returned, and the results are written to p0 and p1. - */ -static int circle_line_intersection(Circle C0, Point X0, Point X1, Point &p0, Point &p1) -{ - /* equation of a circle: (x - h)^2 + (y - k)^2 = r^2 */ - Coord r = C0.ray(); - Coord h = C0.center()[X]; - Coord k = C0.center()[Y]; - - Coord x0, y0; - Coord x1, y1; - - if (are_near(X1[X], X0[X])) { - /* slope is undefined (vertical line) */ - Coord c = X0[X]; - Coord det = r*r - (c-h)*(c-h); - - /* no intersection */ - if (det < 0) - return 0; - - /* solve for y */ - y0 = k + std::sqrt(det); - y1 = k - std::sqrt(det); - - // x == c (always) - x0 = c; - x1 = c; - } else { - /* equation of a line: y = mx + b */ - Coord m = (X1[Y] - X0[Y]) / (X1[X] - X0[X]); - Coord b = X0[Y] - m*X0[X]; - - /* obtain quadratic for x: */ - Coord A = m*m + 1; - Coord B = 2*h - 2*b*m + 2*k*m; - Coord C = b*b + h*h + k*k - r*r - 2*b*k; - - Coord det = B*B - 4*A*C; - - /* no intersection, circle and line do not cross */ - if (det < 0) - return 0; - - /* solve quadratic */ - x0 = (B + std::sqrt(det)) / (2*A); - x1 = (B - std::sqrt(det)) / (2*A); - - /* substitute the calculated x times to determine the y values */ - y0 = m*x0 + b; - y1 = m*x1 + b; - } - - p0 = Point(x0, y0); - p1 = Point(x1, y1); - - return 2; -} static Point intersection_point(Point origin_a, Point vector_a, Point origin_b, Point vector_b) { - Coord denom = cross(vector_b, vector_a); + Coord denom = cross(vector_a, vector_b); if (!are_near(denom,0.)) { - Coord t = (cross(origin_a,vector_b) + cross(vector_b,origin_b)) / denom; + Coord t = (cross(vector_b, origin_a) + cross(origin_b, vector_b)) / denom; return origin_a + vector_a*t; } return Point(infinity(), infinity()); @@ -129,14 +61,14 @@ namespace { // Internal data structure -struct join_data -{ +struct join_data { join_data(Geom::Path &_res, Geom::Path const&_outgoing, Geom::Point _in_tang, Geom::Point _out_tang, double _miter, double _width) - : res(_res), outgoing(_outgoing), in_tang(_in_tang) - , out_tang(_out_tang), miter(_miter), width(_width) {} + : res(_res), outgoing(_outgoing), in_tang(_in_tang), out_tang(_out_tang), miter(_miter), width(_width) {}; - // I/O + // contains the current path that is being built on Geom::Path &res; + + // contains the next curve to append Geom::Path const& outgoing; // input tangents @@ -160,7 +92,7 @@ void bevel_join(join_data jd) void round_join(join_data jd) { - jd.res.appendNew<Geom::SVGEllipticalArc>(jd.width, jd.width, 0, false, jd.width <= 0, jd.outgoing.initialPoint()); + jd.res.appendNew<Geom::EllipticalArc>(jd.width, jd.width, 0, false, jd.width <= 0, jd.outgoing.initialPoint()); jd.res.append(jd.outgoing); } @@ -226,18 +158,20 @@ void miter_join_internal(join_data jd, bool clip) void miter_join(join_data jd) { miter_join_internal(jd, false); } void miter_clip_join(join_data jd) { miter_join_internal(jd, true); } -Geom::Point pick_solution(Geom::Point points[2], Geom::Point tang2, Geom::Point endPt) +Geom::Point pick_solution(std::vector<Geom::ShapeIntersection> points, Geom::Point tang2, Geom::Point endPt) { + assert(points.size() == 2); Geom::Point sol; - if ( dot(tang2,points[0]-endPt) > 0 ) { + if ( dot(tang2, points[0].point() - endPt) > 0 ) { // points[0] is bad, choose points[1] sol = points[1]; - } else if ( dot(tang2,points[1]-endPt) > 0 ) { // points[0] could be good, now check points[1] + } else if ( dot(tang2, points[1].point() - endPt) > 0 ) { // points[0] could be good, now check points[1] // points[1] is bad, choose points[0] sol = points[0]; } else { // both points are good, choose nearest - sol = ( distanceSq(endPt, points[0]) < distanceSq(endPt, points[1]) ) ? points[0] : points[1]; + sol = ( distanceSq(endPt, points[0].point()) < distanceSq(endPt, points[1].point()) ) + ? points[0].point() : points[1].point(); } return sol; } @@ -261,9 +195,8 @@ void extrapolate_join(join_data jd) bool inc_ls = !circle1.center().isFinite(); bool out_ls = !circle2.center().isFinite(); - Geom::Point points[2]; + std::vector<Geom::ShapeIntersection> points; - int solutions = 0; Geom::EllipticalArc *arc1 = NULL; Geom::EllipticalArc *arc2 = NULL; Geom::Point sol; @@ -272,33 +205,29 @@ void extrapolate_join(join_data jd) if (!inc_ls && !out_ls) { // Two circles - solutions = Geom::circle_circle_intersection(circle1.center(), circle1.ray(), - circle2.center(), circle2.ray(), - points[0], points[1]); - if (solutions == 2) { + points = circle1.intersect(circle2); + if (points.size() == 2) { sol = pick_solution(points, tang2, endPt); - arc1 = circle1.arc(startPt, 0.5*(startPt+sol), sol, true); - arc2 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true); + arc1 = circle1.arc(startPt, 0.5*(startPt+sol), sol); + arc2 = circle2.arc(sol, 0.5*(sol+endPt), endPt); } } else if (inc_ls && !out_ls) { // Line and circle - solutions = Geom::circle_line_intersection(circle2, incoming.initialPoint(), incoming.finalPoint(), points[0], points[1]); - - if (solutions == 2) { + points = circle2.intersect(Line(incoming.initialPoint(), incoming.finalPoint())); + if (points.size() == 2) { sol = pick_solution(points, tang2, endPt); - arc2 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true); + arc2 = circle2.arc(sol, 0.5*(sol+endPt), endPt); } } else if (!inc_ls && out_ls) { // Circle and line - solutions = Geom::circle_line_intersection(circle1, outgoing.initialPoint(), outgoing.finalPoint(), points[0], points[1]); - - if (solutions == 2) { + points = circle1.intersect(Line(outgoing.initialPoint(), outgoing.finalPoint())); + if (points.size() == 2) { sol = pick_solution(points, tang2, endPt); - arc1 = circle1.arc(startPt, 0.5*(sol+startPt), sol, true); + arc1 = circle1.arc(startPt, 0.5*(sol+startPt), sol); } } - if (solutions != 2) + if (points.size() != 2) // no solutions available, fall back to miter return miter_clip_join(jd); @@ -342,25 +271,24 @@ void extrapolate_join(join_data jd) Geom::Ray end_ray(center, sol); Geom::Line limit_line(center, 0); // Angle set below - if (Geom::cross(start_ray.versor(), end_ray.versor()) > 0) { + if (Geom::cross(start_ray.versor(), end_ray.versor()) < 0) { limit_line.setAngle(start_ray.angle() - limit_angle); } else { limit_line.setAngle(start_ray.angle() + limit_angle); } - Geom::EllipticalArc *arc_center = circle_center.arc(point_on_path, 0.5*(point_on_path + sol), sol, true); + Geom::EllipticalArc *arc_center = circle_center.arc(point_on_path, 0.5*(point_on_path + sol), sol); if (arc_center && arc_center->sweepAngle() > limit_angle) { // We need to clip clipped = true; if (!inc_ls) { // Incoming circular - solutions = Geom::circle_line_intersection(circle1, limit_line.pointAt(0), limit_line.pointAt(1), points[0], points[1]); - - if (solutions == 2) { + points = circle1.intersect(limit_line); + if (points.size() == 2) { p1 = pick_solution(points, tang2, endPt); delete arc1; - arc1 = circle1.arc(startPt, 0.5*(p1+startPt), p1, true); + arc1 = circle1.arc(startPt, 0.5*(p1+startPt), p1); } } else { p1 = Geom::intersection_point(startPt, tang1, limit_line.pointAt(0), limit_line.versor()); @@ -368,12 +296,11 @@ void extrapolate_join(join_data jd) if (!out_ls) { // Outgoing circular - solutions = Geom::circle_line_intersection(circle2, limit_line.pointAt(0), limit_line.pointAt(1), points[0], points[1]); - - if (solutions == 2) { + points = circle2.intersect(limit_line); + if (points.size() == 2) { p2 = pick_solution(points, tang1, endPt); delete arc2; - arc2 = circle2.arc(p2, 0.5*(p2+endPt), endPt, true); + arc2 = circle2.arc(p2, 0.5*(p2+endPt), endPt); } } else { p2 = Geom::intersection_point(endPt, tang2, limit_line.pointAt(0), limit_line.versor()); @@ -456,47 +383,6 @@ void tangents(Geom::Point tang[2], Geom::Curve const& incoming, Geom::Curve cons tang[0] = tang1, tang[1] = tang2; } -void outline_helper(Geom::Path &res, Geom::Path const& temp, Geom::Point in_tang, Geom::Point out_tang, double width, double miter, Inkscape::LineJoinType join) -{ - if (res.size() == 0 || temp.size() == 0) - return; - - Geom::Curve const& outgoing = temp.front(); - if (Geom::are_near(res.finalPoint(), outgoing.initialPoint())) { - // if the points are /that/ close, just ignore this one - res.setFinal(temp.initialPoint()); - res.append(temp); - return; - } - - join_data jd(res, temp, in_tang, out_tang, miter, width); - - bool on_outside = (Geom::cross(in_tang, out_tang) < 0); - - if (on_outside) { - join_func *jf; - switch (join) { - case Inkscape::JOIN_BEVEL: - jf = &bevel_join; - break; - case Inkscape::JOIN_ROUND: - jf = &round_join; - break; - case Inkscape::JOIN_EXTRAPOLATE: - jf = &extrapolate_join; - break; - case Inkscape::JOIN_MITER_CLIP: - jf = &miter_clip_join; - break; - default: - jf = &miter_join; - } - jf(jd); - } else { - join_inside(jd); - } -} - // Offsetting a line segment is mathematically stable and quick to do Geom::LineSegment offset_line(Geom::LineSegment const& l, double width) { @@ -531,12 +417,12 @@ void get_cubic_data(Geom::CubicBezier const& bez, double time, double& len, doub if (Geom::are_near(l, 0)) { return; // this isn't a segment... } - rad = 1e8; + rad = 1e8; } else { - rad = -l * (Geom::dot(der2, der2) / Geom::cross(der3, der2)); + rad = -l * (Geom::dot(der2, der2) / Geom::cross(der2, der3)); } } else { - rad = -l * (Geom::dot(der1, der1) / Geom::cross(der2, der1)); + rad = -l * (Geom::dot(der1, der1) / Geom::cross(der1, der2)); } len = l; } @@ -583,7 +469,7 @@ void offset_cubic(Geom::Path& p, Geom::CubicBezier const& bez, double width, dou // reached maximum recursive depth // don't bother with any more correction if (levels == 0) { - p.append(c, Geom::Path::STITCH_DISCONTINUOUS); + p.append(c); return; } @@ -615,7 +501,7 @@ void offset_quadratic(Geom::Path& p, Geom::QuadraticBezier const& bez, double wi // cheat // it's faster // seriously - std::vector<Geom::Point> points = bez.points(); + std::vector<Geom::Point> points = bez.controlPoints(); Geom::Point b1 = points[0] + (2./3) * (points[1] - points[0]); Geom::Point b2 = b1 + (1./3) * (points[2] - points[0]); Geom::CubicBezier cub = Geom::CubicBezier(points[0], b1, b2, points[2]); @@ -703,7 +589,7 @@ Geom::PathVector outline(Geom::Path const& input, double width, double miter, Li Geom::PathBuilder res; Geom::Path with_dir = half_outline(input, width/2., miter, join); - Geom::Path against_dir = half_outline(input.reverse(), width/2., miter, join); + Geom::Path against_dir = half_outline(input.reversed(), width/2., miter, join); res.moveTo(with_dir[0].initialPoint()); res.append(with_dir); @@ -752,13 +638,16 @@ Geom::Path half_outline(Geom::Path const& input, double width, double miter, Lin Geom::Path temp; Geom::Point tang[2]; + res.setStitching(true); + temp.setStitching(true); + res.start(start); // Do two curves at a time for efficiency, since the join function needs to know the outgoing curve as well const size_t k = (input.back_closed().isDegenerate() && input.closed()) ?input.size_default()-1:input.size_default(); for (size_t u = 0; u < k; u += 2) { - temp = Geom::Path(); + temp.clear(); offset_curve(temp, &input[u], width); @@ -767,27 +656,27 @@ Geom::Path half_outline(Geom::Path const& input, double width, double miter, Lin res.append(temp); } else { tangents(tang, input[u-1], input[u]); - outline_helper(res, temp, tang[0], tang[1], width, miter, join); + outline_join(res, temp, tang[0], tang[1], width, miter, join); } // odd number of paths if (u < k - 1) { - temp = Geom::Path(); + temp.clear(); offset_curve(temp, &input[u+1], width); tangents(tang, input[u], input[u+1]); - outline_helper(res, temp, tang[0], tang[1], width, miter, join); + outline_join(res, temp, tang[0], tang[1], width, miter, join); } } if (input.closed()) { Geom::Curve const &c1 = res.back(); Geom::Curve const &c2 = res.front(); - temp = Geom::Path(); + temp.clear(); temp.append(c1); Geom::Path temp2; temp2.append(c2); tangents(tang, input.back(), input.front()); - outline_helper(temp, temp2, tang[0], tang[1], width, miter, join); + outline_join(temp, temp2, tang[0], tang[1], width, miter, join); res.erase(res.begin()); res.erase_last(); // @@ -798,6 +687,47 @@ Geom::Path half_outline(Geom::Path const& input, double width, double miter, Lin return res; } +void outline_join(Geom::Path &res, Geom::Path const& temp, Geom::Point in_tang, Geom::Point out_tang, double width, double miter, Inkscape::LineJoinType join) +{ + if (res.size() == 0 || temp.size() == 0) + return; + + Geom::Curve const& outgoing = temp.front(); + if (Geom::are_near(res.finalPoint(), outgoing.initialPoint())) { + // if the points are /that/ close, just ignore this one + res.setFinal(temp.initialPoint()); + res.append(temp); + return; + } + + join_data jd(res, temp, in_tang, out_tang, miter, width); + + bool on_outside = (Geom::cross(in_tang, out_tang) > 0); + + if (on_outside) { + join_func *jf; + switch (join) { + case Inkscape::JOIN_BEVEL: + jf = &bevel_join; + break; + case Inkscape::JOIN_ROUND: + jf = &round_join; + break; + case Inkscape::JOIN_EXTRAPOLATE: + jf = &extrapolate_join; + break; + case Inkscape::JOIN_MITER_CLIP: + jf = &miter_clip_join; + break; + default: + jf = &miter_join; + } + jf(jd); + } else { + join_inside(jd); + } +} + } // namespace Inkscape /* diff --git a/src/helper/geom-pathstroke.h b/src/helper/geom-pathstroke.h index 0cfb9f817..6697273cf 100644 --- a/src/helper/geom-pathstroke.h +++ b/src/helper/geom-pathstroke.h @@ -1,10 +1,11 @@ #ifndef INKSCAPE_HELPER_PATH_STROKE_H #define INKSCAPE_HELPER_PATH_STROKE_H -/* Author: +/* Authors: * Liam P. White + * Tavmjong Bah * - * Copyright (C) 2014-2015 Author + * Copyright (C) 2014-2015 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -26,21 +27,54 @@ enum LineCapType { BUTT_FLAT, BUTT_ROUND, BUTT_SQUARE, - BUTT_PEAK, // ? + BUTT_PEAK, // This is not a line ending supported by the SVG standard. }; /** + * Strokes the path given by @a input. + * Joins may behave oddly if the width is negative. + * + * @param[in] input Input path. + * @param[in] width Stroke width. + * @param[in] miter Miter limit. Only used when @a join is one of JOIN_MITER, JOIN_MITER_CLIP, and JOIN_EXTRAPOLATE. + * @param[in] join Line join type used during offset. Member of LineJoinType enum. + * @param[in] cap Line cap type used during stroking. Member of LineCapType enum. + * + * @return Stroked path. + * If the input path is closed, the resultant vector will contain two paths. + * Otherwise, there should be only one in the output. + */ +Geom::PathVector outline(Geom::Path const& input, double width, double miter, LineJoinType join = JOIN_BEVEL, LineCapType cap = BUTT_FLAT); + +/** * Offset the input path by @a width. * Joins may behave oddly if the width is negative. * - * @param input - * @param width Amount to offset. - * @param miter Miter limit. Only used with JOIN_MITER, JOIN_MITER_CLIP, and JOIN_EXTRAPOLATE. - * @param join + * @param[in] input Input path. + * @param[in] width Amount to offset. + * @param[in] miter Miter limit. Only used when @a join is one of JOIN_MITER, JOIN_MITER_CLIP, and JOIN_EXTRAPOLATE. + * @param[in] join Line join type used during offset. Member of LineJoinType enum. + * + * @return Offsetted output. */ Geom::Path half_outline(Geom::Path const& input, double width, double miter, LineJoinType join = JOIN_BEVEL); -Geom::PathVector outline(Geom::Path const& input, double width, double miter, LineJoinType join = JOIN_BEVEL, LineCapType cap = BUTT_FLAT); +/** + * Builds a join on the provided path. + * Joins may behave oddly if the width is negative. + * + * @param[inout] res The path to build the join on. + * The outgoing path (or a portion thereof) will be appended after the join is created. + * Previous segments may be modified as an optimization, beware! + * + * @param[in] outgoing The segment to append on the outgoing portion of the join. + * @param[in] in_tang The end tangent to consider on the input path. + * @param[in] out_tang The begin tangent to consider on the output path. + * @param[in] width + * @param[in] miter + * @param[in] join + */ +void outline_join(Geom::Path &res, Geom::Path const& outgoing, Geom::Point in_tang, Geom::Point out_tang, double width, double miter, LineJoinType join); } // namespace Inkscape diff --git a/src/helper/geom.cpp b/src/helper/geom.cpp index 77cba4736..ecb330b01 100644 --- a/src/helper/geom.cpp +++ b/src/helper/geom.cpp @@ -15,8 +15,7 @@ #include <typeinfo> #include <2geom/pathvector.h> #include <2geom/path.h> -#include <2geom/bezier-curve.h> -#include <2geom/hvlinesegment.h> +#include <2geom/curves.h> #include <2geom/transforms.h> #include <2geom/rect.h> #include <2geom/coord.h> @@ -468,8 +467,8 @@ pathv_to_linear_and_cubic_beziers( Geom::PathVector const &pathv ) for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) { output.push_back( Geom::Path() ); + output.back().setStitching(true); output.back().start( pit->initialPoint() ); - output.back().close( pit->closed() ); for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) { if (is_straight_curve(*cit)) { @@ -483,10 +482,13 @@ pathv_to_linear_and_cubic_beziers( Geom::PathVector const &pathv ) } else { // convert all other curve types to cubicbeziers Geom::Path cubicbezier_path = Geom::cubicbezierpath_from_sbasis(cit->toSBasis(), 0.1); + cubicbezier_path.close(false); output.back().append(cubicbezier_path); } } } + + output.back().close( pit->closed() ); } return output; @@ -520,8 +522,7 @@ pathv_to_linear( Geom::PathVector const &pathv, double /*maxdisp*/) } else { /* all others must be Bezier curves */ Geom::BezierCurve const *curve = dynamic_cast<Geom::BezierCurve const *>(&*cit); - Geom::CubicBezier b((*curve)[0], (*curve)[1], (*curve)[2], (*curve)[3]); - std::vector<Geom::Point> bzrpoints = b.points(); + std::vector<Geom::Point> bzrpoints = curve->controlPoints(); Geom::Point A = bzrpoints[0]; Geom::Point B = bzrpoints[1]; Geom::Point C = bzrpoints[2]; @@ -579,7 +580,7 @@ pathv_to_cubicbezier( Geom::PathVector const &pathv) pitCubic.appendNew<Geom::LineSegment>( pitCubic.initialPoint() ); pitCubic.close(true); } - for (Geom::Path::const_iterator cit = pitCubic.begin(); cit != pitCubic.end_open(); ++cit) { + for (Geom::Path::iterator cit = pitCubic.begin(); cit != pitCubic.end_open(); ++cit) { if (is_straight_curve(*cit)) { Geom::CubicBezier b(cit->initialPoint(), cit->pointAt(0.3334) + Geom::Point(cubicGap,cubicGap), cit->finalPoint(), cit->finalPoint()); output.back().append(b); @@ -849,41 +850,6 @@ recursive_bezier4(const double x1, const double y1, recursive_bezier4(x1234, y1234, x234, y234, x34, y34, x4, y4, m_points, level + 1); } - -/** - * rounds all corners of the rectangle 'outwards', i.e. x0 and y0 are floored, x1 and y1 are ceiled. - */ -void round_rectangle_outwards(Geom::Rect & rect) { - Geom::Interval ints[2]; - for (int i=0; i < 2; i++) { - ints[i] = Geom::Interval(std::floor(rect[i][0]), std::ceil(rect[i][1])); - } - rect = Geom::Rect(ints[0], ints[1]); -} - - -namespace Geom { - -bool transform_equalp(Geom::Affine const &m0, Geom::Affine const &m1, Geom::Coord const epsilon) { - return - Geom::are_near(m0[0], m1[0], epsilon) && - Geom::are_near(m0[1], m1[1], epsilon) && - Geom::are_near(m0[2], m1[2], epsilon) && - Geom::are_near(m0[3], m1[3], epsilon); -} - - -bool translate_equalp(Geom::Affine const &m0, Geom::Affine const &m1, Geom::Coord const epsilon) { - return Geom::are_near(m0[4], m1[4], epsilon) && Geom::are_near(m0[5], m1[5], epsilon); -} - - -bool matrix_equalp(Geom::Affine const &m0, Geom::Affine const &m1, Geom::Coord const epsilon) { - return transform_equalp(m0, m1, epsilon) && translate_equalp(m0, m1, epsilon); -} - -} //end namespace Geom - /* Local Variables: mode:c++ diff --git a/src/helper/geom.h b/src/helper/geom.h index 3232d9fd5..d49e2070c 100644 --- a/src/helper/geom.h +++ b/src/helper/geom.h @@ -33,13 +33,6 @@ void recursive_bezier4(const double x1, const double y1, const double x2, const std::vector<Geom::Point> &pointlist, int level); -void round_rectangle_outwards(Geom::Rect & rect); - -namespace Geom{ -bool transform_equalp(Geom::Affine const &m0, Geom::Affine const &m1, Geom::Coord const epsilon); -bool translate_equalp(Geom::Affine const &m0, Geom::Affine const &m1, Geom::Coord const epsilon); -bool matrix_equalp(Geom::Affine const &m0, Geom::Affine const &m1, Geom::Coord const epsilon); -} #endif // INKSCAPE_HELPER_GEOM_H /* diff --git a/src/libcroco/cr-fonts.c b/src/libcroco/cr-fonts.c index 3c6896ffd..10f26c99c 100644 --- a/src/libcroco/cr-fonts.c +++ b/src/libcroco/cr-fonts.c @@ -768,7 +768,7 @@ cr_font_weight_get_bolder (enum CRFontWeight a_weight) } else if (a_weight < FONT_WEIGHT_NORMAL) { return FONT_WEIGHT_NORMAL ; } else if (a_weight == FONT_WEIGHT_BOLDER - || a_weight == FONT_WEIGHT_BOLDER) { + || a_weight == FONT_WEIGHT_LIGHTER) { cr_utils_trace_info ("FONT_WEIGHT_BOLDER or FONT_WEIGHT_LIGHTER should not appear here") ; return FONT_WEIGHT_NORMAL ; } else { diff --git a/src/libdepixelize/priv/splines-kopf2011.h b/src/libdepixelize/priv/splines-kopf2011.h index c586f74b7..c3da58c05 100644 --- a/src/libdepixelize/priv/splines-kopf2011.h +++ b/src/libdepixelize/priv/splines-kopf2011.h @@ -93,7 +93,7 @@ template<class T> void worker(const typename HomogeneousSplines<T>::Polygon &source, Splines::Path &dest, bool optimize) { - dest.pathVector.reserve(source.holes.size() + 1); + //dest.pathVector.reserve(source.holes.size() + 1); for ( int i = 0 ; i != 4 ; ++i ) dest.rgba[i] = source.rgba[i]; diff --git a/src/libnrtype/FontFactory.cpp b/src/libnrtype/FontFactory.cpp index c8f5e1fef..65eb62dda 100644 --- a/src/libnrtype/FontFactory.cpp +++ b/src/libnrtype/FontFactory.cpp @@ -18,9 +18,11 @@ #include <glibmm/i18n.h> #include <pango/pangoft2.h> +#include <pango/pango-ot.h> #include "libnrtype/FontFactory.h" #include "libnrtype/font-instance.h" #include "util/unordered-containers.h" +#include <map> typedef INK_UNORDERED_MAP<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> FaceMapType; @@ -591,7 +593,23 @@ font_instance* font_factory::FaceFromFontSpecification(char const *fontSpecifica return font; } +void dump_tag( guint32 *tag, Glib::ustring prefix = "" ) { + std::cout << prefix + << ((char)((*tag & 0xff000000)>>24)) + << ((char)((*tag & 0x00ff0000)>>16)) + << ((char)((*tag & 0x0000ff00)>>8)) + << ((char)((*tag & 0x000000ff)>>0)) + << std::endl; +} +Glib::ustring extract_tag( guint32 *tag ) { + Glib::ustring tag_name; + tag_name += ((char)((*tag & 0xff000000)>>24)); + tag_name += ((char)((*tag & 0x00ff0000)>>16)); + tag_name += ((char)((*tag & 0x0000ff00)>>8)); + tag_name += ((char)((*tag & 0x000000ff)>>0)); + return tag_name; +} font_instance *font_factory::Face(PangoFontDescription *descr, bool canFail) { @@ -657,6 +675,76 @@ font_instance *font_factory::Face(PangoFontDescription *descr, bool canFail) pango_font_description_free(descr); } } + + // Extract which OpenType tables are in the font. We'll make a list of all tables + // regardless of which script and langauge they are in. These functions are deprecated but + // will eventually be replaced by newer functions (according to Behdad). + PangoOTInfo* info = pango_ot_info_get( res->theFace ); + + PangoOTTag* scripts = pango_ot_info_list_scripts( info, PANGO_OT_TABLE_GSUB ); + // std::cout << " scripts: " << std::endl; + for( unsigned i = 0; scripts[i] != 0; ++i ) { + // dump_tag( &scripts[i], " " ); + + guint script_index = -1; + if( pango_ot_info_find_script( info, PANGO_OT_TABLE_GSUB, scripts[i], &script_index )) { + + PangoOTTag* languages = + pango_ot_info_list_languages( info, PANGO_OT_TABLE_GSUB, script_index, NULL); + // if( languages[0] != 0 ) + // std::cout << " languages: " << std::endl; + + for( unsigned j = 0; languages[j] != 0; ++j ) { + // dump_tag( &languages[j], " lang: "); + + guint language_index = -1; + if( pango_ot_info_find_language(info, PANGO_OT_TABLE_GSUB, script_index, languages[j], &language_index, NULL)) { + + PangoOTTag* features = + pango_ot_info_list_features( info, PANGO_OT_TABLE_GSUB, 0, i, j ); + if( features[0] != 0 ) + // std::cout << " features: " << std::endl; + + for( unsigned k = 0; features[k] != 0; ++k ) { + // dump_tag( &features[k], " feature: "); + ++(res->openTypeTables[ extract_tag(&features[k])]); + } + g_free( features ); + } else { + // std::cout << " No languages defined" << std::endl; + PangoOTTag* features = + pango_ot_info_list_features( info, PANGO_OT_TABLE_GSUB, 0, i, PANGO_OT_DEFAULT_LANGUAGE ); + // if( features[0] != 0 ) + // std::cout << " default features: " << std::endl; + + for( unsigned k = 0; features[k] != 0; ++k ) { + // dump_tag( &features[k], " feature: " ); + ++(res->openTypeTables[ extract_tag(&features[k])]); + } + g_free( features ); + } + } + g_free( languages ); + } else { + // std::cout << " No scripts defined! " << std::endl; + } + } + g_free( scripts ); + + PangoOTTag* features = + pango_ot_info_list_features( info, PANGO_OT_TABLE_GSUB, 0, 0, PANGO_OT_DEFAULT_LANGUAGE ); + // if( features[0] != 0 ) + // std::cout << " DFTL DFTL features: " << std::endl; + for( unsigned i = 0; features[i] != 0; ++i ) { + // dump_tag( &features[i], " feature: " ); + ++(res->openTypeTables[ extract_tag(&features[i])]); + } + // std::map<Glib::ustring,int>::iterator it; + // for( it = res->openTypeTables.begin(); it != res->openTypeTables.end(); ++it) { + // std::cout << "Table: " << it->first << " Occurances: " << it->second << std::endl; + // } + g_free( features ); + } else { // already here res = loadedFaces[descr]; diff --git a/src/libnrtype/Layout-TNG-Compute.cpp b/src/libnrtype/Layout-TNG-Compute.cpp index beff3734b..d81e1b6b4 100644 --- a/src/libnrtype/Layout-TNG-Compute.cpp +++ b/src/libnrtype/Layout-TNG-Compute.cpp @@ -958,6 +958,10 @@ void Layout::Calculator::ParagraphInfo::free() */ void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) const { + TRACE(("pango version string: %s\n", pango_version_string() )); +#if PANGO_VERSION_CHECK(1,37,1) + TRACE((" ... compiled for font features\n")); +#endif Glib::ustring para_text; PangoAttrList *attributes_list; unsigned input_index; @@ -986,9 +990,21 @@ void Layout::Calculator::_buildPangoItemizationForPara(ParagraphInfo *para) con PangoAttribute *attribute_font_description = pango_attr_font_desc_new(font->descr); attribute_font_description->start_index = para_text.bytes(); + +#if PANGO_VERSION_CHECK(1,37,1) + PangoAttribute *attribute_font_features = + pango_attr_font_features_new( text_source->style->getFontFeatureString().c_str()); + attribute_font_features->start_index = para_text.bytes(); +#endif para_text.append(&*text_source->text_begin.base(), text_source->text_length); // build the combined text attribute_font_description->end_index = para_text.bytes(); pango_attr_list_insert(attributes_list, attribute_font_description); + +#if PANGO_VERSION_CHECK(1,37,1) + attribute_font_features->end_index = para_text.bytes(); + pango_attr_list_insert(attributes_list, attribute_font_features); +#endif + // ownership of attribute is assumed by the list font->Unref(); } diff --git a/src/libnrtype/font-instance.h b/src/libnrtype/font-instance.h index 2c7d1ce46..5a71e353b 100644 --- a/src/libnrtype/font-instance.h +++ b/src/libnrtype/font-instance.h @@ -36,6 +36,9 @@ public: int nbGlyph, maxGlyph; font_glyph* glyphs; + // Map of OpenType tables found in font (convert to std::set?) + std::map<Glib::ustring, int> openTypeTables; + font_instance(void); virtual ~font_instance(void); @@ -68,6 +71,8 @@ public: private: void FreeTheFace(); + // Temp: make public +public: #ifdef USE_PANGO_WIN32 HFONT theFace; #else diff --git a/src/libuemf/CMakeLists.txt b/src/libuemf/CMakeLists.txt index 922d404a6..9e6b68994 100644 --- a/src/libuemf/CMakeLists.txt +++ b/src/libuemf/CMakeLists.txt @@ -18,6 +18,7 @@ set(libuemf_SRC uemf.h uemf_endian.h uemf_print.h + uemf_safe.h uemf_utf.h uwmf.h uwmf_endian.h diff --git a/src/livarot/PathConversion.cpp b/src/livarot/PathConversion.cpp index 42df898e6..30e21d546 100644 --- a/src/livarot/PathConversion.cpp +++ b/src/livarot/PathConversion.cpp @@ -716,7 +716,7 @@ static void ArcAnglesAndCenter(Geom::Point const &iS, Geom::Point const &iE, { Geom::Point se = iE - iS; Geom::Point ca(cos(angle), sin(angle)); - Geom::Point cse(dot(se, ca), cross(se, ca)); + Geom::Point cse(dot(ca, se), cross(ca, se)); cse[0] /= rx; cse[1] /= ry; double const lensq = dot(cse,cse); @@ -753,8 +753,8 @@ static void ArcAnglesAndCenter(Geom::Point const &iS, Geom::Point const &iE, csd[1] *= ry; ca[1] = -ca[1]; // because it's the inverse rotation - dr[0] = dot(csd, ca); - dr[1] = cross(csd, ca); + dr[0] = dot(ca, csd); + dr[1] = cross(ca, csd); ca[1] = -ca[1]; diff --git a/src/livarot/PathCutting.cpp b/src/livarot/PathCutting.cpp index 49b2c5a78..086b30557 100644 --- a/src/livarot/PathCutting.cpp +++ b/src/livarot/PathCutting.cpp @@ -308,7 +308,7 @@ Path::MakePathVector() { /* TODO: add testcase for this descr_arcto case */ PathDescrArcTo *nData = dynamic_cast<PathDescrArcTo *>(descr_cmd[i]); - currentpath->appendNew<Geom::SVGEllipticalArc>( nData->rx, nData->ry, nData->angle*M_PI/180.0, nData->large, !nData->clockwise, nData->p ); + currentpath->appendNew<Geom::EllipticalArc>( nData->rx, nData->ry, nData->angle*M_PI/180.0, nData->large, !nData->clockwise, nData->p ); lastP = nData->p; } break; @@ -400,11 +400,11 @@ void Path::AddCurve(Geom::Curve const &c) Geom::Point tme = 3 * ((*cubic_bezier)[3] - (*cubic_bezier)[2]); CubicTo (tmp, tms, tme); } - else if(Geom::SVGEllipticalArc const *svg_elliptical_arc = dynamic_cast<Geom::SVGEllipticalArc const *>(&c)) { - ArcTo( svg_elliptical_arc->finalPoint(), - svg_elliptical_arc->ray(Geom::X), svg_elliptical_arc->ray(Geom::Y), - svg_elliptical_arc->rotationAngle()*180.0/M_PI, // convert from radians to degrees - svg_elliptical_arc->largeArc(), !svg_elliptical_arc->sweep() ); + else if(Geom::EllipticalArc const *elliptical_arc = dynamic_cast<Geom::EllipticalArc const *>(&c)) { + ArcTo( elliptical_arc->finalPoint(), + elliptical_arc->ray(Geom::X), elliptical_arc->ray(Geom::Y), + elliptical_arc->rotationAngle()*180.0/M_PI, // convert from radians to degrees + elliptical_arc->largeArc(), !elliptical_arc->sweep() ); } else { //this case handles sbasis as well as all other curve types Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1); @@ -434,17 +434,11 @@ void Path::LoadPath(Geom::Path const &path, Geom::Affine const &tr, bool doTran MoveTo( pathtr.initialPoint() ); - for(Geom::Path::const_iterator cit = pathtr.begin(); cit != pathtr.end_open(); ++cit) { + for(Geom::Path::const_iterator cit = pathtr.begin(); cit != pathtr.end(); ++cit) { AddCurve(*cit); } if (pathtr.closed()) { - // check if closing segment is empty before adding it - Geom::Curve const &crv = pathtr.back_closed(); - if ( !crv.isDegenerate() ) { - AddCurve(crv); - } - Close(); } } @@ -513,10 +507,10 @@ double Path::Surface() for (std::vector<path_lineto>::const_iterator i = pts.begin(); i != pts.end(); ++i) { if ( i->isMoveTo == polyline_moveto ) { - surf += Geom::cross(lastM - lastP, lastM); + surf += Geom::cross(lastM, lastM - lastP); lastP = lastM = i->p; } else { - surf += Geom::cross(i->p - lastP, i->p); + surf += Geom::cross(i->p, i->p - lastP); lastP = i->p; } diff --git a/src/livarot/PathOutline.cpp b/src/livarot/PathOutline.cpp index 211ee31e2..e146bb908 100644 --- a/src/livarot/PathOutline.cpp +++ b/src/livarot/PathOutline.cpp @@ -1108,7 +1108,7 @@ Path::TangentOnCubAt (double at, Geom::Point const &iS, PathDescrCubicTo const & } return; } - rad = -l * (dot(dder,dder)) / (cross(ddder,dder)); + rad = -l * (dot(dder,dder)) / (cross(dder, ddder)); tgt = dder / l; if (before) { tgt = -tgt; @@ -1117,7 +1117,7 @@ Path::TangentOnCubAt (double at, Geom::Point const &iS, PathDescrCubicTo const & } len = l; - rad = -l * (dot(der,der)) / (cross(dder,der)); + rad = -l * (dot(der,der)) / (cross(der, dder)); tgt = der / l; } @@ -1156,7 +1156,7 @@ Path::TangentOnBezAt (double at, Geom::Point const &iS, return; } len = l; - rad = -l * (dot(der,der)) / (cross(dder,der)); + rad = -l * (dot(der,der)) / (cross(der, dder)); tgt = der / l; } @@ -1180,7 +1180,7 @@ Path::OutlineJoin (Path * dest, Geom::Point pos, Geom::Point stNor, Geom::Point TurnInside ^= PrevPos == pos; PrevPos = pos; - const double angSi = cross (enNor,stNor); + const double angSi = cross (stNor, enNor); const double angCo = dot (stNor, enNor); if ((fabs(angSi) < .0000001) && angCo > 0) { // The join is straight -> nothing to do. diff --git a/src/livarot/PathSimplify.cpp b/src/livarot/PathSimplify.cpp index 7d9f3f57d..81ddcd049 100644 --- a/src/livarot/PathSimplify.cpp +++ b/src/livarot/PathSimplify.cpp @@ -128,7 +128,7 @@ static double DistanceToCubic(Geom::Point const &start, PathDescrCubicTo res, Ge } Geom::Point seg = res.p - start; - nnle = Geom::cross(seg, sp); + nnle = Geom::cross(sp, seg); nnle *= nnle; nnle /= Geom::dot(seg, seg); if ( nnle < nle ) { diff --git a/src/livarot/PathStroke.cpp b/src/livarot/PathStroke.cpp index 6ec7fa209..4cfeb887a 100644 --- a/src/livarot/PathStroke.cpp +++ b/src/livarot/PathStroke.cpp @@ -292,7 +292,7 @@ void Path::DoJoin (Shape *dest, double width, JoinType join, Geom::Point pos, Ge { Geom::Point pnor = prev.ccw(); Geom::Point nnor = next.ccw(); - double angSi = cross(next, prev); + double angSi = cross(prev, next); /* FIXED: this special case caused bug 1028953 */ if (angSi > -0.0001 && angSi < 0.0001) { @@ -416,7 +416,7 @@ Path::DoLeftJoin (Shape * dest, double width, JoinType join, Geom::Point pos, { Geom::Point pnor=prev.ccw(); Geom::Point nnor=next.ccw(); - double angSi = cross (next, prev); + double angSi = cross(prev, next); if (angSi > -0.0001 && angSi < 0.0001) { double angCo = dot (prev, next); @@ -444,7 +444,7 @@ Path::DoLeftJoin (Shape * dest, double width, JoinType join, Geom::Point pos, /* Geom::Point biss; biss.x=next.x-prev.x; biss.y=next.y-prev.y; - double c2=cross(biss,next); + double c2=cross(next, biss); double l=width/c2; double projn=l*(dot(biss,next)); double projp=-l*(dot(biss,prev)); @@ -503,7 +503,7 @@ Path::DoLeftJoin (Shape * dest, double width, JoinType join, Geom::Point pos, } else { - double s2 = cross (biss, nnor); + double s2 = cross(nnor, biss); double dec = (l - emiter) * c2 / s2; const Geom::Point tbiss=biss.ccw(); @@ -560,7 +560,7 @@ Path::DoRightJoin (Shape * dest, double width, JoinType join, Geom::Point pos, { const Geom::Point pnor=prev.ccw(); const Geom::Point nnor=next.ccw(); - double angSi = cross (next,prev); + double angSi = cross(prev, next); if (angSi > -0.0001 && angSi < 0.0001) { double angCo = dot (prev, next); @@ -614,7 +614,7 @@ Path::DoRightJoin (Shape * dest, double width, JoinType join, Geom::Point pos, } else { - double s2 = cross (biss, nnor); + double s2 = cross(nnor, biss); double dec = (l - emiter) * c2 / s2; const Geom::Point tbiss=biss.ccw(); @@ -667,7 +667,7 @@ Path::DoRightJoin (Shape * dest, double width, JoinType join, Geom::Point pos, /* Geom::Point biss; biss=next.x-prev.x; biss.y=next.y-prev.y; - double c2=cross(next,biss); + double c2=cross(biss, next); double l=width/c2; double projn=l*(dot(biss,next)); double projp=-l*(dot(biss,prev)); @@ -719,7 +719,7 @@ void Path::RecRound(Shape *dest, int sNo, int eNo, // start and end index sia = 1; } else { double coa = dot(nS, nE); - sia = cross(nS, nE); + sia = cross(nE, nS); ang = acos(coa); if ( coa >= 1 ) { ang = 0; diff --git a/src/livarot/Shape.cpp b/src/livarot/Shape.cpp index ac6c72342..0bb3390e8 100644 --- a/src/livarot/Shape.cpp +++ b/src/livarot/Shape.cpp @@ -1683,7 +1683,7 @@ Shape::CmpToVert (Geom::Point ax, Geom::Point bx,bool as,bool bs) Geom::Point av, bv; av = ax; bv = bx; - double si = cross (bv, av); + double si = cross(av, bv); int tstSi = 0; if (si > 0.000001) tstSi = 1; if (si < -0.000001) tstSi = -1; @@ -2104,7 +2104,7 @@ Shape::PtWinding (const Geom::Point px) const } Geom::Point const diff = px - ast; - double const cote = cross(diff, adir); + double const cote = cross(adir, diff); if (cote == 0) continue; if (cote < 0) { if (ast[0] > px[0]) lr += nWeight; diff --git a/src/livarot/ShapeSweep.cpp b/src/livarot/ShapeSweep.cpp index b04b36bfd..1e6273964 100644 --- a/src/livarot/ShapeSweep.cpp +++ b/src/livarot/ShapeSweep.cpp @@ -1738,7 +1738,7 @@ Shape::TesteIntersection (SweepTree * iL, SweepTree * iR, Geom::Point &atx, doub } } - double ang = cross (rdir, ldir); + double ang = cross (ldir, rdir); // ang*=iL->src->eData[iL->bord].isqlength; // ang*=iR->src->eData[iR->bord].isqlength; if (ang <= 0) return false; // edges in opposite directions: <-left ... right -> @@ -1776,12 +1776,12 @@ Shape::TesteIntersection (SweepTree * iL, SweepTree * iR, Geom::Point &atx, doub double srDot, erDot; sDiff = iL->src->pData[lSt].rx - iR->src->pData[rSt].rx; eDiff = iL->src->pData[lEn].rx - iR->src->pData[rSt].rx; - srDot = cross (sDiff,rdir); - erDot = cross (eDiff,rdir); + srDot = cross(rdir, sDiff); + erDot = cross(rdir, eDiff); sDiff = iR->src->pData[rSt].rx - iL->src->pData[lSt].rx; eDiff = iR->src->pData[rEn].rx - iL->src->pData[lSt].rx; - slDot = cross (sDiff,ldir); - elDot = cross (eDiff,ldir); + slDot = cross(ldir, sDiff); + elDot = cross(ldir, eDiff); if ((srDot >= 0 && erDot >= 0) || (srDot <= 0 && erDot <= 0)) { @@ -2089,7 +2089,7 @@ Shape::Winding (const Geom::Point px) const } diff = px - ast; - double cote = cross (diff,adir); + double cote = cross(adir, diff); if (cote == 0) continue; if (cote < 0) @@ -2563,15 +2563,15 @@ Shape::TesteIntersection (Shape * ils, Shape * irs, int ilb, int irb, double srDot, erDot; sDiff = ils->pData[lSt].rx - irs->pData[rSt].rx; eDiff = ils->pData[lEn].rx - irs->pData[rSt].rx; - srDot = cross (sDiff,rdir ); - erDot = cross (eDiff,rdir ); + srDot = cross(rdir, sDiff); + erDot = cross(rdir, eDiff); if ((srDot >= 0 && erDot >= 0) || (srDot <= 0 && erDot <= 0)) return false; sDiff = irs->pData[rSt].rx - ils->pData[lSt].rx; eDiff = irs->pData[rEn].rx - ils->pData[lSt].rx; - slDot = cross (sDiff,ldir ); - elDot = cross (eDiff,ldir); + slDot = cross(ldir, sDiff); + elDot = cross(ldir, eDiff); if ((slDot >= 0 && elDot >= 0) || (slDot <= 0 && elDot <= 0)) return false; @@ -2615,8 +2615,8 @@ Shape::TesteIntersection (Shape * ils, Shape * irs, int ilb, int irb, double sDot, eDot; sDiff = ils->pData[lSt].rx - irs->pData[rSt].rx; eDiff = ils->pData[lEn].rx - irs->pData[rSt].rx; - sDot = cross (sDiff,rdir ); - eDot = cross (eDiff,rdir); + sDot = cross(rdir, sDiff); + eDot = cross(rdir, eDiff); atx = (sDot * irs->pData[lEn].rx - eDot * irs->pData[lSt].rx) / (sDot - @@ -2625,8 +2625,8 @@ Shape::TesteIntersection (Shape * ils, Shape * irs, int ilb, int irb, sDiff = irs->pData[rSt].rx - ils->pData[lSt].rx; eDiff = irs->pData[rEn].rx - ils->pData[lSt].rx; - sDot = cross (sDiff,ldir ); - eDot = cross (eDiff,ldir ); + sDot = cross(ldir, sDiff); + eDot = cross(ldir, eDiff); atR = sDot / (sDot - eDot); @@ -2669,7 +2669,7 @@ Shape::TesteAdjacency (Shape * a, int no, const Geom::Point atx, int nPt, diff = atx - ast; - double e = IHalfRound ((cross (diff,adir)) * a->eData[no].isqlength); + double e = IHalfRound(cross(adir, diff) * a->eData[no].isqlength); if (-3 < e && e < 3) { double rad = HalfRound (0.501); // when using single precision, 0.505 is better (0.5 would be the correct value, @@ -2684,16 +2684,16 @@ Shape::TesteAdjacency (Shape * a, int no, const Geom::Point atx, int nPt, diff4[1] = diff[1] + rad; double di1, di2; bool adjacent = false; - di1 = cross (diff1,adir); - di2 = cross (diff3,adir); + di1 = cross(adir, diff1); + di2 = cross(adir, diff3); if ((di1 < 0 && di2 > 0) || (di1 > 0 && di2 < 0)) { adjacent = true; } else { - di1 = cross ( diff2,adir); - di2 = cross (diff4,adir); + di1 = cross(adir, diff2); + di2 = cross(adir, diff4); if ((di1 < 0 && di2 > 0) || (di1 > 0 && di2 < 0)) { adjacent = true; diff --git a/src/livarot/sweep-tree.cpp b/src/livarot/sweep-tree.cpp index 7a016a2ee..1b9868f2e 100644 --- a/src/livarot/sweep-tree.cpp +++ b/src/livarot/sweep-tree.cpp @@ -117,9 +117,9 @@ SweepTree::Find(Geom::Point const &px, SweepTree *newOne, SweepTree *&insertL, nNorm=nNorm.ccw(); if (sweepSens) { - y = cross(nNorm, bNorm); - } else { y = cross(bNorm, nNorm); + } else { + y = cross(nNorm, bNorm); } if (y == 0) { y = dot(bNorm, nNorm); @@ -345,7 +345,7 @@ SweepTree::InsertAt(SweepTreeList &list, SweepEventQueue &queue, SweepTree *insertL = NULL; SweepTree *insertR = NULL; - double ang = cross(nNorm, bNorm); + double ang = cross(bNorm, nNorm); if (ang == 0) { insertL = insNode; @@ -384,7 +384,7 @@ SweepTree::InsertAt(SweepTreeList &list, SweepEventQueue &queue, { bNorm = -bNorm; } - ang = cross(nNorm, bNorm); + ang = cross(bNorm, nNorm); if (ang <= 0) { break; @@ -426,7 +426,7 @@ SweepTree::InsertAt(SweepTreeList &list, SweepEventQueue &queue, { bNorm = -bNorm; } - ang = cross(nNorm, bNorm); + ang = cross(bNorm, nNorm); if (ang > 0) { break; diff --git a/src/live_effects/CMakeLists.txt b/src/live_effects/CMakeLists.txt index c4b92e579..8a097590a 100644 --- a/src/live_effects/CMakeLists.txt +++ b/src/live_effects/CMakeLists.txt @@ -3,7 +3,6 @@ set(live_effects_SRC lpe-angle_bisector.cpp lpe-attach-path.cpp lpe-bendpath.cpp - lpe-boolops.cpp lpe-bounding-box.cpp lpe-bspline.cpp lpe-circle_3pts.cpp @@ -81,7 +80,6 @@ set(live_effects_SRC lpe-angle_bisector.h lpe-attach-path.h lpe-bendpath.h - lpe-boolops.h lpe-bounding-box.h lpe-bspline.h lpe-circle_3pts.h diff --git a/src/live_effects/Makefile_insert b/src/live_effects/Makefile_insert index dace45739..c2c2ce93c 100644 --- a/src/live_effects/Makefile_insert +++ b/src/live_effects/Makefile_insert @@ -14,8 +14,6 @@ ink_common_sources += \ live_effects/lpe-patternalongpath.h \ live_effects/lpe-bendpath.cpp \ live_effects/lpe-bendpath.h \ - live_effects/lpe-boolops.cpp \ - live_effects/lpe-boolops.h \ live_effects/lpe-dynastroke.cpp \ live_effects/lpe-dynastroke.h \ live_effects/lpe-extrude.cpp \ diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index f65e8f289..b65678f0a 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -14,54 +14,53 @@ // include effects: #include "live_effects/lpe-patternalongpath.h" #include "live_effects/effect.h" +#include "live_effects/lpe-angle_bisector.h" +#include "live_effects/lpe-attach-path.h" #include "live_effects/lpe-bendpath.h" -#include "live_effects/lpe-sketch.h" -#include "live_effects/lpe-vonkoch.h" -#include "live_effects/lpe-knot.h" -#include "live_effects/lpe-rough-hatches.h" -#include "live_effects/lpe-dynastroke.h" -#include "live_effects/lpe-test-doEffect-stack.h" -#include "live_effects/lpe-gears.h" -#include "live_effects/lpe-curvestitch.h" +#include "live_effects/lpe-bounding-box.h" +#include "live_effects/lpe-bspline.h" +#include "live_effects/lpe-circle_3pts.h" #include "live_effects/lpe-circle_with_radius.h" -#include "live_effects/lpe-perspective_path.h" -#include "live_effects/lpe-perspective-envelope.h" -#include "live_effects/lpe-spiro.h" -#include "live_effects/lpe-lattice.h" -#include "live_effects/lpe-lattice2.h" -#include "live_effects/lpe-roughen.h" -#include "live_effects/lpe-show_handles.h" -#include "live_effects/lpe-simplify.h" -#include "live_effects/lpe-envelope.h" +#include "live_effects/lpe-clone-original.h" #include "live_effects/lpe-constructgrid.h" -#include "live_effects/lpe-perp_bisector.h" -#include "live_effects/lpe-tangent_to_curve.h" -#include "live_effects/lpe-mirror_symmetry.h" -#include "live_effects/lpe-circle_3pts.h" -#include "live_effects/lpe-angle_bisector.h" -#include "live_effects/lpe-parallel.h" #include "live_effects/lpe-copy_rotate.h" -#include "live_effects/lpe-offset.h" -#include "live_effects/lpe-ruler.h" -#include "live_effects/lpe-boolops.h" +#include "live_effects/lpe-curvestitch.h" +#include "live_effects/lpe-dynastroke.h" +#include "live_effects/lpe-ellipse_5pts.h" +#include "live_effects/lpe-envelope.h" +#include "live_effects/lpe-extrude.h" +#include "live_effects/lpe-fill-between-many.h" +#include "live_effects/lpe-fill-between-strokes.h" +#include "live_effects/lpe-fillet-chamfer.h" +#include "live_effects/lpe-gears.h" #include "live_effects/lpe-interpolate.h" #include "live_effects/lpe-interpolate_points.h" -#include "live_effects/lpe-text_label.h" -#include "live_effects/lpe-path_length.h" +#include "live_effects/lpe-jointype.h" +#include "live_effects/lpe-knot.h" +#include "live_effects/lpe-lattice2.h" +#include "live_effects/lpe-lattice.h" #include "live_effects/lpe-line_segment.h" -#include "live_effects/lpe-recursiveskeleton.h" -#include "live_effects/lpe-extrude.h" +#include "live_effects/lpe-mirror_symmetry.h" +#include "live_effects/lpe-offset.h" +#include "live_effects/lpe-parallel.h" +#include "live_effects/lpe-path_length.h" +#include "live_effects/lpe-perp_bisector.h" +#include "live_effects/lpe-perspective-envelope.h" +#include "live_effects/lpe-perspective_path.h" #include "live_effects/lpe-powerstroke.h" -#include "live_effects/lpe-clone-original.h" -#include "live_effects/lpe-bspline.h" -#include "live_effects/lpe-attach-path.h" -#include "live_effects/lpe-fill-between-strokes.h" -#include "live_effects/lpe-fill-between-many.h" -#include "live_effects/lpe-ellipse_5pts.h" -#include "live_effects/lpe-bounding-box.h" -#include "live_effects/lpe-jointype.h" +#include "live_effects/lpe-recursiveskeleton.h" +#include "live_effects/lpe-roughen.h" +#include "live_effects/lpe-rough-hatches.h" +#include "live_effects/lpe-ruler.h" +#include "live_effects/lpe-show_handles.h" +#include "live_effects/lpe-simplify.h" +#include "live_effects/lpe-sketch.h" +#include "live_effects/lpe-spiro.h" +#include "live_effects/lpe-tangent_to_curve.h" #include "live_effects/lpe-taperstroke.h" -#include "live_effects/lpe-fillet-chamfer.h" +#include "live_effects/lpe-test-doEffect-stack.h" +#include "live_effects/lpe-text_label.h" +#include "live_effects/lpe-vonkoch.h" #include "xml/node-event-vector.h" #include "sp-object.h" @@ -98,8 +97,6 @@ const Util::EnumData<EffectType> LPETypeData[] = { #ifdef LPE_ENABLE_TEST_EFFECTS {DOEFFECTSTACK_TEST, N_("doEffect stack test"), "doeffectstacktest"}, {ANGLE_BISECTOR, N_("Angle bisector"), "angle_bisector"}, - // TRANSLATORS: boolean operations - {BOOLOPS, N_("Boolops"), "boolops"}, {CIRCLE_WITH_RADIUS, N_("Circle (by center and radius)"), "circle_with_radius"}, {CIRCLE_3PTS, N_("Circle by 3 points"), "circle_3pts"}, {DYNASTROKE, N_("Dynamic stroke"), "dynastroke"}, @@ -244,9 +241,6 @@ Effect::New(EffectType lpenr, LivePathEffectObject *lpeobj) case RULER: neweffect = static_cast<Effect*> ( new LPERuler(lpeobj) ); break; - case BOOLOPS: - neweffect = static_cast<Effect*> ( new LPEBoolops(lpeobj) ); - break; case INTERPOLATE: neweffect = static_cast<Effect*> ( new LPEInterpolate(lpeobj) ); break; @@ -521,24 +515,24 @@ Effect::acceptParamPath (SPPath const*/*param_path*/) { void Effect::doEffect (SPCurve * curve) { - std::vector<Geom::Path> orig_pathv = curve->get_pathvector(); + Geom::PathVector orig_pathv = curve->get_pathvector(); - std::vector<Geom::Path> result_pathv = doEffect_path(orig_pathv); + Geom::PathVector result_pathv = doEffect_path(orig_pathv); curve->set_pathvector(result_pathv); } -std::vector<Geom::Path> -Effect::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +Effect::doEffect_path (Geom::PathVector const & path_in) { - std::vector<Geom::Path> path_out; + Geom::PathVector path_out; if ( !concatenate_before_pwd2 ) { // default behavior for (unsigned int i=0; i < path_in.size(); i++) { Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb(); Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out = doEffect_pwd2(pwd2_in); - std::vector<Geom::Path> path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE); + Geom::PathVector path = Geom::path_from_piecewise( pwd2_out, LPE_CONVERSION_TOLERANCE); // add the output path vector to the already accumulated vector: for (unsigned int j=0; j < path.size(); j++) { path_out.push_back(path[j]); diff --git a/src/live_effects/effect.h b/src/live_effects/effect.h index a6b5a13ea..ea57ff243 100644 --- a/src/live_effects/effect.h +++ b/src/live_effects/effect.h @@ -129,9 +129,9 @@ protected: // of what kind of input/output parameters he desires. // the order in which they appear is the order in which they are // called by this base class. (i.e. doEffect(SPCurve * curve) defaults to calling - // doEffect(std::vector<Geom::Path> ) - virtual std::vector<Geom::Path> - doEffect_path (std::vector<Geom::Path> const & path_in); + // doEffect(Geom::PathVector ) + virtual Geom::PathVector + doEffect_path (Geom::PathVector const & path_in); virtual Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in); @@ -162,7 +162,7 @@ protected: double current_zoom; std::vector<Geom::Point> selectedNodesPoints; SPCurve * sp_curve; - std::vector<Geom::Path> pathvector_before_effect; + Geom::PathVector pathvector_before_effect; private: bool provides_own_flash_paths; // if true, the standard flash path is suppressed diff --git a/src/live_effects/lpe-angle_bisector.cpp b/src/live_effects/lpe-angle_bisector.cpp index 1acf9605f..95a81c763 100644 --- a/src/live_effects/lpe-angle_bisector.cpp +++ b/src/live_effects/lpe-angle_bisector.cpp @@ -56,8 +56,8 @@ LPEAngleBisector::~LPEAngleBisector() { } -std::vector<Geom::Path> -LPEAngleBisector::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPEAngleBisector::doEffect_path (Geom::PathVector const & path_in) { using namespace Geom; @@ -73,7 +73,7 @@ LPEAngleBisector::doEffect_path (std::vector<Geom::Path> const & path_in) Geom::Point D = ptA - dir * length_left; Geom::Point E = ptA + dir * length_right; - Piecewise<D2<SBasis> > output = Piecewise<D2<SBasis> >(D2<SBasis>(Linear(D[X], E[X]), Linear(D[Y], E[Y]))); + Piecewise<D2<SBasis> > output = Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(D[X], E[X]), SBasis(D[Y], E[Y]))); return path_from_piecewise(output, LPE_CONVERSION_TOLERANCE); } @@ -103,7 +103,7 @@ KnotHolderEntityLeftEnd::knot_set(Geom::Point const &p, Geom::Point const &/*ori Geom::Point const s = snap_knot_position(p, state); - double lambda = Geom::nearest_point(s, lpe->ptA, lpe->dir); + double lambda = Geom::nearest_time(s, lpe->ptA, lpe->dir); lpe->length_left.param_set_value(-lambda); sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true); @@ -116,7 +116,7 @@ KnotHolderEntityRightEnd::knot_set(Geom::Point const &p, Geom::Point const &/*or Geom::Point const s = snap_knot_position(p, state); - double lambda = Geom::nearest_point(s, lpe->ptA, lpe->dir); + double lambda = Geom::nearest_time(s, lpe->ptA, lpe->dir); lpe->length_right.param_set_value(lambda); sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true); diff --git a/src/live_effects/lpe-angle_bisector.h b/src/live_effects/lpe-angle_bisector.h index 95048751e..400cbf6b6 100644 --- a/src/live_effects/lpe-angle_bisector.h +++ b/src/live_effects/lpe-angle_bisector.h @@ -28,7 +28,7 @@ public: LPEAngleBisector(LivePathEffectObject *lpeobject); virtual ~LPEAngleBisector(); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); friend class AB::KnotHolderEntityLeftEnd; friend class AB::KnotHolderEntityRightEnd; diff --git a/src/live_effects/lpe-attach-path.cpp b/src/live_effects/lpe-attach-path.cpp index 768c66ee2..21459f322 100644 --- a/src/live_effects/lpe-attach-path.cpp +++ b/src/live_effects/lpe-attach-path.cpp @@ -64,7 +64,7 @@ void LPEAttachPath::resetDefaults(SPItem const * /*item*/) void LPEAttachPath::doEffect (SPCurve * curve) { - std::vector<Geom::Path> this_pathv = curve->get_pathvector(); + Geom::PathVector this_pathv = curve->get_pathvector(); if (sp_lpe_item && !this_pathv.empty()) { Geom::Path p = Geom::Path(this_pathv.front().initialPoint()); @@ -73,7 +73,7 @@ void LPEAttachPath::doEffect (SPCurve * curve) if (start_path.linksToPath()) { - std::vector<Geom::Path> linked_pathv = start_path.get_pathvector(); + Geom::PathVector linked_pathv = start_path.get_pathvector(); Geom::Affine linkedtransform = start_path.getObject()->getRelativeTransform(sp_lpe_item); if ( !linked_pathv.empty() ) @@ -87,7 +87,7 @@ void LPEAttachPath::doEffect (SPCurve * curve) Geom::Coord length = derivs[deriv_n].length(); if ( ! Geom::are_near(length, 0) ) { if (set_start_end) { - start_path_position.param_set_value(transformedpath.nearestPoint(start_path_curve_end.getOrigin())); + start_path_position.param_set_value(transformedpath.nearestTime(start_path_curve_end.getOrigin()).asFlatTime()); } if (start_path_position > transformedpath.size()) { @@ -95,7 +95,8 @@ void LPEAttachPath::doEffect (SPCurve * curve) } else if (start_path_position < 0) { start_path_position.param_set_value(0); } - const Geom::Curve *c = start_path_position >= transformedpath.size() ? &transformedpath.back() : &transformedpath.at_index((int)start_path_position); + Geom::Curve const *c = start_path_position >= transformedpath.size() ? + &transformedpath.back() : &transformedpath.at((int)start_path_position); std::vector<Geom::Point> derivs_2 = c->pointAndDerivatives(start_path_position >= transformedpath.size() ? 1 : (start_path_position - (int)start_path_position), 3); for (unsigned deriv_n_2 = 1; deriv_n_2 < derivs_2.size(); deriv_n_2++) { @@ -126,7 +127,7 @@ void LPEAttachPath::doEffect (SPCurve * curve) if (end_path.linksToPath()) { - std::vector<Geom::Path> linked_pathv = end_path.get_pathvector(); + Geom::PathVector linked_pathv = end_path.get_pathvector(); Geom::Affine linkedtransform = end_path.getObject()->getRelativeTransform(sp_lpe_item); if ( !linked_pathv.empty() ) @@ -141,7 +142,7 @@ void LPEAttachPath::doEffect (SPCurve * curve) Geom::Coord length = derivs[deriv_n].length(); if ( ! Geom::are_near(length, 0) ) { if (set_end_end) { - end_path_position.param_set_value(transformedpath.nearestPoint(end_path_curve_end.getOrigin())); + end_path_position.param_set_value(transformedpath.nearestTime(end_path_curve_end.getOrigin()).asFlatTime()); } if (end_path_position > transformedpath.size()) { @@ -149,7 +150,8 @@ void LPEAttachPath::doEffect (SPCurve * curve) } else if (end_path_position < 0) { end_path_position.param_set_value(0); } - const Geom::Curve *c = end_path_position >= transformedpath.size() ? &transformedpath.back() : &transformedpath.at_index((int)end_path_position); + const Geom::Curve *c = end_path_position >= transformedpath.size() ? + &transformedpath.back() : &transformedpath.at((int)end_path_position); std::vector<Geom::Point> derivs_2 = c->pointAndDerivatives(end_path_position >= transformedpath.size() ? 1 : (end_path_position - (int)end_path_position), 3); for (unsigned deriv_n_2 = 1; deriv_n_2 < derivs_2.size(); deriv_n_2++) { diff --git a/src/live_effects/lpe-boolops.cpp b/src/live_effects/lpe-boolops.cpp deleted file mode 100644 index 641cf5d50..000000000 --- a/src/live_effects/lpe-boolops.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/** \file - * LPE boolops implementation - */ -/* - * Authors: - * Johan Engelen - * - * Copyright (C) Johan Engelen 2007-2008 <j.b.c.engelen@utwente.nl> - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "live_effects/lpe-boolops.h" - -#include <2geom/path.h> -#include <2geom/shape.h> - -namespace Inkscape { -namespace LivePathEffect { - -static const Util::EnumData<unsigned> BoolopTypeData[] = { - {Geom::BOOLOP_NULL , N_("Null"), "null"}, - {Geom::BOOLOP_INTERSECT , N_("Intersect"), "intersect"}, - {Geom::BOOLOP_SUBTRACT_A_B , N_("Subtract A-B"), "subtract_a_b"}, - {Geom::BOOLOP_IDENTITY_A , N_("Identity A"), "identity_a"}, - {Geom::BOOLOP_SUBTRACT_B_A , N_("Subtract B-A"), "subtract_b_a"}, - {Geom::BOOLOP_IDENTITY_B , N_("Identity B"), "identity_b"}, - {Geom::BOOLOP_EXCLUSION , N_("Exclusion"), "Exclusion"}, - {Geom::BOOLOP_UNION , N_("Union"), "Union"} -}; -static const Util::EnumDataConverter<unsigned> BoolopTypeConverter(BoolopTypeData, sizeof(BoolopTypeData)/sizeof(*BoolopTypeData)); - -LPEBoolops::LPEBoolops(LivePathEffectObject *lpeobject) : - Effect(lpeobject), - bool_path(_("2nd path:"), _("Path to which the original path will be boolop'ed."), "path_2nd", &wr, this, "M0,0 L1,0"), - boolop_type(_("Boolop type:"), _("Determines which kind of boolop will be performed."), "boolop_type", BoolopTypeConverter, &wr, this, Geom::BOOLOP_UNION) -{ - show_orig_path = true; - - registerParameter( dynamic_cast<Parameter *>(&boolop_type) ); - registerParameter( dynamic_cast<Parameter *>(&bool_path) ); -} - -LPEBoolops::~LPEBoolops() -{ - -} - - -Geom::PathVector -LPEBoolops::doEffect_path (Geom::PathVector const & path_in) -{ - std::vector<Geom::Path> path_out; - - Geom::Shape shape_in = Geom::sanitize(path_in); - - Geom::Shape shape_param = Geom::sanitize(bool_path.get_pathvector()); - - Geom::Shape shape_out = Geom::boolop(shape_in, shape_param, boolop_type.get_value()); - - path_out = Geom::desanitize(shape_out); - - return path_out; -} - - -} //namespace LivePathEffect -} /* namespace Inkscape */ - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/live_effects/lpe-boolops.h b/src/live_effects/lpe-boolops.h deleted file mode 100644 index 3c8dc85c4..000000000 --- a/src/live_effects/lpe-boolops.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef INKSCAPE_LPE_BOOLOPS_H -#define INKSCAPE_LPE_BOOLOPS_H - -/** \file - * LPE boolops implementation, see lpe-boolops.cpp. - */ - -/* - * Authors: - * Johan Engelen - * - * Copyright (C) Johan Engelen 2007-2008 <j.b.c.engelen@utwente.nl> - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "live_effects/parameter/enum.h" -#include "live_effects/effect.h" -#include "live_effects/parameter/path.h" - -namespace Inkscape { -namespace LivePathEffect { - -class LPEBoolops : public Effect { -public: - LPEBoolops(LivePathEffectObject *lpeobject); - virtual ~LPEBoolops(); - - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); - -private: - PathParam bool_path; - EnumParam<unsigned> boolop_type; - - LPEBoolops(const LPEBoolops&); - LPEBoolops& operator=(const LPEBoolops&); -}; - -} //namespace LivePathEffect -} //namespace Inkscape - -#endif // INKSCAPE_LPE_BOOLOPS_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 : diff --git a/src/live_effects/lpe-bounding-box.cpp b/src/live_effects/lpe-bounding-box.cpp index bafd5e70e..43a60d482 100644 --- a/src/live_effects/lpe-bounding-box.cpp +++ b/src/live_effects/lpe-bounding-box.cpp @@ -45,7 +45,7 @@ void LPEBoundingBox::doEffect (SPCurve * curve) p.appendNew<Geom::LineSegment>(Geom::Point(bbox->right(), bbox->bottom())); p.appendNew<Geom::LineSegment>(Geom::Point(bbox->left(), bbox->bottom())); p.appendNew<Geom::LineSegment>(Geom::Point(bbox->left(), bbox->top())); - std::vector<Geom::Path> out; + Geom::PathVector out; out.push_back(p); curve->set_pathvector(out); } diff --git a/src/live_effects/lpe-bspline.cpp b/src/live_effects/lpe-bspline.cpp index ecbfef76a..c2a2d080e 100644 --- a/src/live_effects/lpe-bspline.cpp +++ b/src/live_effects/lpe-bspline.cpp @@ -9,6 +9,7 @@ #include "sp-path.h" #include "svg/svg.h" #include "xml/repr.h" +#include "preferences.h" // TODO due to internal breakage in glibmm headers, this must be last: #include <glibmm/i18n.h> @@ -43,7 +44,7 @@ LPEBSpline::LPEBSpline(LivePathEffectObject *lpeobject) steps.param_set_digits(0); helper_size.param_set_range(0.0, 999.0); - helper_size.param_set_increments(5, 5); + helper_size.param_set_increments(1, 1); helper_size.param_set_digits(2); } @@ -76,13 +77,15 @@ void LPEBSpline::doEffect(SPCurve *curve) Geom::PathVector const original_pathv = curve->get_pathvector(); curve->reset(); - + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); for (Geom::PathVector::const_iterator path_it = original_pathv.begin(); path_it != original_pathv.end(); ++path_it) { if (path_it->empty()) { continue; } - hp.push_back(*path_it); + if (!prefs->getBool("/tools/nodes/show_outline", true)){ + hp.push_back(*path_it); + } Geom::Path::const_iterator curve_it1 = path_it->begin(); Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); Geom::Path::const_iterator curve_endit = path_it->end_default(); @@ -121,12 +124,12 @@ void LPEBSpline::doEffect(SPCurve *curve) if(are_near((*cubic)[1],(*cubic)[0]) && !are_near((*cubic)[2],(*cubic)[3])) { point_at1 = sbasis_in.valueAt(DEFAULT_START_POWER); } else { - point_at1 = sbasis_in.valueAt(Geom::nearest_point((*cubic)[1], *in->first_segment())); + point_at1 = sbasis_in.valueAt(Geom::nearest_time((*cubic)[1], *in->first_segment())); } if(are_near((*cubic)[2],(*cubic)[3]) && !are_near((*cubic)[1],(*cubic)[0])) { point_at2 = sbasis_in.valueAt(DEFAULT_END_POWER); } else { - point_at2 = sbasis_in.valueAt(Geom::nearest_point((*cubic)[2], *in->first_segment())); + point_at2 = sbasis_in.valueAt(Geom::nearest_time((*cubic)[2], *in->first_segment())); } } else { point_at1 = in->first_segment()->initialPoint(); @@ -144,7 +147,7 @@ void LPEBSpline::doEffect(SPCurve *curve) if(are_near((*cubic)[1],(*cubic)[0]) && !are_near((*cubic)[2],(*cubic)[3])) { next_point_at1 = sbasis_in.valueAt(DEFAULT_START_POWER); } else { - next_point_at1 = sbasis_out.valueAt(Geom::nearest_point((*cubic)[1], *out->first_segment())); + next_point_at1 = sbasis_out.valueAt(Geom::nearest_time((*cubic)[1], *out->first_segment())); } } else { next_point_at1 = out->first_segment()->initialPoint(); @@ -161,7 +164,7 @@ void LPEBSpline::doEffect(SPCurve *curve) cubic = dynamic_cast<Geom::CubicBezier const *>(&*path_it->begin()); if (cubic) { line_helper->moveto(sbasis_start.valueAt( - Geom::nearest_point((*cubic)[1], *start->first_segment()))); + Geom::nearest_time((*cubic)[1], *start->first_segment()))); } else { line_helper->moveto(start->first_segment()->initialPoint()); } @@ -175,7 +178,7 @@ void LPEBSpline::doEffect(SPCurve *curve) cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1); if (cubic) { line_helper->lineto(sbasis_end.valueAt( - Geom::nearest_point((*cubic)[2], *end->first_segment()))); + Geom::nearest_time((*cubic)[2], *end->first_segment()))); } else { line_helper->lineto(end->first_segment()->finalPoint()); } @@ -233,7 +236,7 @@ LPEBSpline::drawHandle(Geom::Point p, double helper_size) Geom::Affine aff = Geom::Affine(); aff *= Geom::Scale(helper_size); pathv *= aff; - pathv += p - Geom::Point(0.5*helper_size, 0.5*helper_size); + pathv *= Geom::Translate(p - Geom::Point(0.5*helper_size, 0.5*helper_size)); hp.push_back(pathv[0]); } diff --git a/src/live_effects/lpe-circle_3pts.cpp b/src/live_effects/lpe-circle_3pts.cpp index fb7496f58..dbb1f4b6b 100644 --- a/src/live_effects/lpe-circle_3pts.cpp +++ b/src/live_effects/lpe-circle_3pts.cpp @@ -17,6 +17,7 @@ // You might need to include other 2geom files. You can add them here: #include <2geom/path.h> #include <2geom/circle.h> +#include <2geom/path-sink.h> namespace Inkscape { namespace LivePathEffect { @@ -30,7 +31,7 @@ LPECircle3Pts::~LPECircle3Pts() { } -static void _circle3(Geom::Point const &A, Geom::Point const &B, Geom::Point const &C, std::vector<Geom::Path> &path_out) { +static void _circle3(Geom::Point const &A, Geom::Point const &B, Geom::Point const &C, Geom::PathVector &path_out) { using namespace Geom; Point D = (A + B)/2; @@ -47,13 +48,13 @@ static void _circle3(Geom::Point const &A, Geom::Point const &B, Geom::Point con double radius = L2(M - A); Geom::Circle c(M, radius); - c.getPath(path_out); + path_out = Geom::Path(c); } -std::vector<Geom::Path> -LPECircle3Pts::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPECircle3Pts::doEffect_path (Geom::PathVector const & path_in) { - std::vector<Geom::Path> path_out = std::vector<Geom::Path>(); + Geom::PathVector path_out = Geom::PathVector(); // we assume that the path has >= 3 nodes Geom::Point A = path_in[0].initialPoint(); diff --git a/src/live_effects/lpe-circle_3pts.h b/src/live_effects/lpe-circle_3pts.h index 2533fe251..07c2eddb0 100644 --- a/src/live_effects/lpe-circle_3pts.h +++ b/src/live_effects/lpe-circle_3pts.h @@ -27,7 +27,7 @@ public: LPECircle3Pts(LivePathEffectObject *lpeobject); virtual ~LPECircle3Pts(); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); private: LPECircle3Pts(const LPECircle3Pts&); diff --git a/src/live_effects/lpe-circle_with_radius.cpp b/src/live_effects/lpe-circle_with_radius.cpp index 8a32cd230..8f2156044 100644 --- a/src/live_effects/lpe-circle_with_radius.cpp +++ b/src/live_effects/lpe-circle_with_radius.cpp @@ -15,11 +15,9 @@ #include "display/curve.h" // You might need to include other 2geom files. You can add them here: -#include <2geom/path.h> -#include <2geom/sbasis.h> -#include <2geom/bezier-to-sbasis.h> -#include <2geom/d2.h> +#include <2geom/pathvector.h> #include <2geom/circle.h> +#include <2geom/path-sink.h> using namespace Geom; @@ -40,10 +38,10 @@ LPECircleWithRadius::~LPECircleWithRadius() } -std::vector<Geom::Path> -LPECircleWithRadius::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPECircleWithRadius::doEffect_path (Geom::PathVector const & path_in) { - std::vector<Geom::Path> path_out = std::vector<Geom::Path>(); + Geom::PathVector path_out = Geom::PathVector(); Geom::Point center = path_in[0].initialPoint(); Geom::Point pt = path_in[0].finalPoint(); @@ -51,9 +49,7 @@ LPECircleWithRadius::doEffect_path (std::vector<Geom::Path> const & path_in) double radius = Geom::L2(pt - center); Geom::Circle c(center, radius); - c.getPath(path_out); - - return path_out; + return Geom::Path(c); } /* diff --git a/src/live_effects/lpe-circle_with_radius.h b/src/live_effects/lpe-circle_with_radius.h index 10f652771..f5bf0c414 100644 --- a/src/live_effects/lpe-circle_with_radius.h +++ b/src/live_effects/lpe-circle_with_radius.h @@ -26,7 +26,7 @@ public: virtual ~LPECircleWithRadius(); // Choose to implement one of the doEffect functions. You can delete or comment out the others. - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); private: // add the parameters for your effect here: diff --git a/src/live_effects/lpe-clone-original.cpp b/src/live_effects/lpe-clone-original.cpp index d7cd6e19c..7541c0be2 100644 --- a/src/live_effects/lpe-clone-original.cpp +++ b/src/live_effects/lpe-clone-original.cpp @@ -28,7 +28,7 @@ LPECloneOriginal::~LPECloneOriginal() void LPECloneOriginal::doEffect (SPCurve * curve) { if ( linked_path.linksToPath() ) { - std::vector<Geom::Path> linked_pathv = linked_path.get_pathvector(); + Geom::PathVector linked_pathv = linked_path.get_pathvector(); if ( !linked_pathv.empty() ) { curve->set_pathvector(linked_pathv); } diff --git a/src/live_effects/lpe-constructgrid.cpp b/src/live_effects/lpe-constructgrid.cpp index fbf9faf56..b1e0edaac 100644 --- a/src/live_effects/lpe-constructgrid.cpp +++ b/src/live_effects/lpe-constructgrid.cpp @@ -41,8 +41,8 @@ LPEConstructGrid::~LPEConstructGrid() } -std::vector<Geom::Path> -LPEConstructGrid::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPEConstructGrid::doEffect_path (Geom::PathVector const & path_in) { // Check that the path has at least 3 nodes (i.e. 2 segments), more nodes are ignored if (path_in[0].size() >= 2) { @@ -64,7 +64,7 @@ LPEConstructGrid::doEffect_path (std::vector<Geom::Path> const & path_in) second_path.appendNew<LineSegment>( origin + second_p*nr_x ); // use the gridpaths and set them in the correct grid - std::vector<Geom::Path> path_out; + Geom::PathVector path_out; path_out.push_back(first_path); for (int ix = 0; ix < nr_x; ix++) { path_out.push_back(path_out.back() * second_translation ); diff --git a/src/live_effects/lpe-constructgrid.h b/src/live_effects/lpe-constructgrid.h index c7e695794..49c986742 100644 --- a/src/live_effects/lpe-constructgrid.h +++ b/src/live_effects/lpe-constructgrid.h @@ -25,7 +25,7 @@ public: LPEConstructGrid(LivePathEffectObject *lpeobject); virtual ~LPEConstructGrid(); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); private: ScalarParam nr_x; diff --git a/src/live_effects/lpe-curvestitch.cpp b/src/live_effects/lpe-curvestitch.cpp index 854cc0734..609447f26 100644 --- a/src/live_effects/lpe-curvestitch.cpp +++ b/src/live_effects/lpe-curvestitch.cpp @@ -68,8 +68,8 @@ LPECurveStitch::~LPECurveStitch() } -std::vector<Geom::Path> -LPECurveStitch::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPECurveStitch::doEffect_path (Geom::PathVector const & path_in) { if (path_in.size() >= 2) { startpoint_edge_variation.resetRandomizer(); @@ -86,7 +86,7 @@ LPECurveStitch::doEffect_path (std::vector<Geom::Path> const & path_in) gdouble scaling = bndsStroke->max() - bndsStroke->min(); Point stroke_origin(bndsStroke->min(), (bndsStrokeY->max()+bndsStrokeY->min())/2); - std::vector<Geom::Path> path_out; + Geom::PathVector path_out; // do this for all permutations (ii,jj) if there are more than 2 paths? realllly cool! for (unsigned ii = 0 ; ii < path_in.size() - 1; ii++) @@ -127,7 +127,7 @@ LPECurveStitch::doEffect_path (std::vector<Geom::Path> const & path_in) // add stuff to one big pw<d2<sbasis> > and then outside the loop convert to path? // No: this way, the separate result paths are kept separate which might come in handy some time! - std::vector<Geom::Path> result = Geom::path_from_piecewise(pwd2_out, LPE_CONVERSION_TOLERANCE); + Geom::PathVector result = Geom::path_from_piecewise(pwd2_out, LPE_CONVERSION_TOLERANCE); path_out.push_back(result[0]); } gdouble svA = startpoint_spacing_variation - startpoint_spacing_variation.get_value()/2; @@ -162,7 +162,7 @@ LPECurveStitch::resetDefaults(SPItem const* item) // calculate bounding box: (isn't there a simpler way?) Piecewise<D2<SBasis> > pwd2; - std::vector<Geom::Path> temppath = sp_svg_read_pathv( item->getRepr()->attribute("inkscape:original-d")); + Geom::PathVector temppath = sp_svg_read_pathv( item->getRepr()->attribute("inkscape:original-d")); for (unsigned int i=0; i < temppath.size(); i++) { pwd2.concat( temppath[i].toPwSb() ); } diff --git a/src/live_effects/lpe-curvestitch.h b/src/live_effects/lpe-curvestitch.h index 38a11b4af..c6ea66f6c 100644 --- a/src/live_effects/lpe-curvestitch.h +++ b/src/live_effects/lpe-curvestitch.h @@ -28,7 +28,7 @@ public: LPECurveStitch(LivePathEffectObject *lpeobject); virtual ~LPECurveStitch(); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); virtual void resetDefaults(SPItem const* item); diff --git a/src/live_effects/lpe-ellipse_5pts.cpp b/src/live_effects/lpe-ellipse_5pts.cpp index b0a5919fe..4c953bcda 100644 --- a/src/live_effects/lpe-ellipse_5pts.cpp +++ b/src/live_effects/lpe-ellipse_5pts.cpp @@ -67,10 +67,10 @@ static double _det5(double (*mat)[5]) return mat[4][4]; } -std::vector<Geom::Path> -LPEEllipse5Pts::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPEEllipse5Pts::doEffect_path (Geom::PathVector const & path_in) { - std::vector<Geom::Path> path_out = std::vector<Geom::Path>(); + Geom::PathVector path_out = Geom::PathVector(); if (path_in[0].size() < 4) { @@ -190,7 +190,7 @@ LPEEllipse5Pts::doEffect_path (std::vector<Geom::Path> const & path_in) p.appendNew<Geom::CubicBezier>(Geom::Point(x1,y1), Geom::Point(x2,y2), Geom::Point(x3,y3)); } - Geom::Affine aff = Geom::Scale(el.ray(Geom::X), el.ray(Geom::Y)) * Geom::Rotate(el.rot_angle()) * Geom::Translate(el.center()); + Geom::Affine aff = Geom::Scale(el.rays()) * Geom::Rotate(el.rotationAngle()) * Geom::Translate(el.center()); path_out.push_back(p * aff); diff --git a/src/live_effects/lpe-ellipse_5pts.h b/src/live_effects/lpe-ellipse_5pts.h index d3b1fccfa..691a693dc 100644 --- a/src/live_effects/lpe-ellipse_5pts.h +++ b/src/live_effects/lpe-ellipse_5pts.h @@ -26,7 +26,7 @@ public: LPEEllipse5Pts(LivePathEffectObject *lpeobject); virtual ~LPEEllipse5Pts(); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); private: LPEEllipse5Pts(const LPEEllipse5Pts&); diff --git a/src/live_effects/lpe-fill-between-many.cpp b/src/live_effects/lpe-fill-between-many.cpp index 7cf354044..3e0810cfc 100644 --- a/src/live_effects/lpe-fill-between-many.cpp +++ b/src/live_effects/lpe-fill-between-many.cpp @@ -35,14 +35,14 @@ LPEFillBetweenMany::~LPEFillBetweenMany() void LPEFillBetweenMany::doEffect (SPCurve * curve) { - std::vector<Geom::Path> res_pathv; + Geom::PathVector res_pathv; SPItem * firstObj = NULL; for (std::vector<PathAndDirection*>::iterator iter = linked_paths._vector.begin(); iter != linked_paths._vector.end(); iter++) { SPObject *obj; if ((*iter)->ref.isAttached() && (obj = (*iter)->ref.getObject()) && SP_IS_ITEM(obj) && !(*iter)->_pathvector.empty()) { Geom::Path linked_path; if ((*iter)->reversed) { - linked_path = (*iter)->_pathvector.front().reverse(); + linked_path = (*iter)->_pathvector.front().reversed(); } else { linked_path = (*iter)->_pathvector.front(); } diff --git a/src/live_effects/lpe-fill-between-strokes.cpp b/src/live_effects/lpe-fill-between-strokes.cpp index e72979ed0..89ea80545 100644 --- a/src/live_effects/lpe-fill-between-strokes.cpp +++ b/src/live_effects/lpe-fill-between-strokes.cpp @@ -39,17 +39,17 @@ void LPEFillBetweenStrokes::doEffect (SPCurve * curve) { if (curve) { if ( linked_path.linksToPath() && second_path.linksToPath() && linked_path.getObject() && second_path.getObject() ) { - std::vector<Geom::Path> linked_pathv = linked_path.get_pathvector(); - std::vector<Geom::Path> second_pathv = second_path.get_pathvector(); - std::vector<Geom::Path> result_linked_pathv; - std::vector<Geom::Path> result_second_pathv; + Geom::PathVector linked_pathv = linked_path.get_pathvector(); + Geom::PathVector second_pathv = second_path.get_pathvector(); + Geom::PathVector result_linked_pathv; + Geom::PathVector result_second_pathv; Geom::Affine second_transform = second_path.getObject()->getRelativeTransform(linked_path.getObject()); - for (std::vector<Geom::Path>::iterator iter = linked_pathv.begin(); iter != linked_pathv.end(); ++iter) + for (Geom::PathVector::iterator iter = linked_pathv.begin(); iter != linked_pathv.end(); ++iter) { result_linked_pathv.push_back((*iter)); } - for (std::vector<Geom::Path>::iterator iter = second_pathv.begin(); iter != second_pathv.end(); ++iter) + for (Geom::PathVector::iterator iter = second_pathv.begin(); iter != second_pathv.end(); ++iter) { result_second_pathv.push_back((*iter) * second_transform); } @@ -58,7 +58,7 @@ void LPEFillBetweenStrokes::doEffect (SPCurve * curve) if (reverse_second.get_value()) { result_linked_pathv.front().appendNew<Geom::LineSegment>(result_second_pathv.front().finalPoint()); - result_linked_pathv.front().append(result_second_pathv.front().reverse()); + result_linked_pathv.front().append(result_second_pathv.front().reversed()); } else { @@ -75,10 +75,10 @@ void LPEFillBetweenStrokes::doEffect (SPCurve * curve) } } else if ( linked_path.linksToPath() && linked_path.getObject() ) { - std::vector<Geom::Path> linked_pathv = linked_path.get_pathvector(); - std::vector<Geom::Path> result_pathv; + Geom::PathVector linked_pathv = linked_path.get_pathvector(); + Geom::PathVector result_pathv; - for (std::vector<Geom::Path>::iterator iter = linked_pathv.begin(); iter != linked_pathv.end(); ++iter) + for (Geom::PathVector::iterator iter = linked_pathv.begin(); iter != linked_pathv.end(); ++iter) { result_pathv.push_back((*iter)); } @@ -87,10 +87,10 @@ void LPEFillBetweenStrokes::doEffect (SPCurve * curve) } } else if ( second_path.linksToPath() && second_path.getObject() ) { - std::vector<Geom::Path> second_pathv = second_path.get_pathvector(); - std::vector<Geom::Path> result_pathv; + Geom::PathVector second_pathv = second_path.get_pathvector(); + Geom::PathVector result_pathv; - for (std::vector<Geom::Path>::iterator iter = second_pathv.begin(); iter != second_pathv.end(); ++iter) + for (Geom::PathVector::iterator iter = second_pathv.begin(); iter != second_pathv.end(); ++iter) { result_pathv.push_back((*iter)); } diff --git a/src/live_effects/lpe-fillet-chamfer.cpp b/src/live_effects/lpe-fillet-chamfer.cpp index ca87c7be1..a9a96864a 100644 --- a/src/live_effects/lpe-fillet-chamfer.cpp +++ b/src/live_effects/lpe-fillet-chamfer.cpp @@ -16,7 +16,7 @@ #include "live_effects/lpe-fillet-chamfer.h" #include <2geom/sbasis-to-bezier.h> -#include <2geom/svg-elliptical-arc.h> +#include <2geom/elliptical-arc.h> #include <2geom/line.h> #include "desktop.h" @@ -272,11 +272,11 @@ void LPEFilletChamfer::refreshKnots() } } -void LPEFilletChamfer::doUpdateFillet(std::vector<Geom::Path> const& original_pathv, double power) +void LPEFilletChamfer::doUpdateFillet(Geom::PathVector const &original_pathv, double power) { std::vector<Point> filletChamferData = fillet_chamfer_values.data(); std::vector<Geom::Point> result; - std::vector<Geom::Path> original_pathv_processed = pathv_to_linear_and_cubic_beziers(original_pathv); + Geom::PathVector original_pathv_processed = pathv_to_linear_and_cubic_beziers(original_pathv); int counter = 0; for (PathVector::const_iterator path_it = original_pathv_processed.begin(); path_it != original_pathv_processed.end(); ++path_it) { @@ -320,11 +320,11 @@ void LPEFilletChamfer::doUpdateFillet(std::vector<Geom::Path> const& original_pa fillet_chamfer_values.param_set_and_write_new_value(result); } -void LPEFilletChamfer::doChangeType(std::vector<Geom::Path> const& original_pathv, int type) +void LPEFilletChamfer::doChangeType(Geom::PathVector const &original_pathv, int type) { std::vector<Point> filletChamferData = fillet_chamfer_values.data(); std::vector<Geom::Point> result; - std::vector<Geom::Path> original_pathv_processed = pathv_to_linear_and_cubic_beziers(original_pathv); + Geom::PathVector original_pathv_processed = pathv_to_linear_and_cubic_beziers(original_pathv); int counter = 0; for (PathVector::const_iterator path_it = original_pathv_processed.begin(); path_it != original_pathv_processed.end(); ++path_it) { int pathCounter = 0; @@ -461,7 +461,7 @@ int LPEFilletChamfer::getKnotsNumber(SPCurve const *c) { int nKnots = c->nodes_in_path(); PathVector const pv = pathv_to_linear_and_cubic_beziers(c->get_pathvector()); - for (std::vector<Geom::Path>::const_iterator path_it = pv.begin(); + for (Geom::PathVector::const_iterator path_it = pv.begin(); path_it != pv.end(); ++path_it) { if (!(*path_it).closed()) { nKnots--; @@ -471,17 +471,17 @@ int LPEFilletChamfer::getKnotsNumber(SPCurve const *c) } void -LPEFilletChamfer::adjustForNewPath(std::vector<Geom::Path> const &path_in) +LPEFilletChamfer::adjustForNewPath(Geom::PathVector const &path_in) { if (!path_in.empty()) { fillet_chamfer_values.recalculate_controlpoints_for_new_pwd2(pathv_to_linear_and_cubic_beziers(path_in)[0].toPwSb()); } } -std::vector<Geom::Path> -LPEFilletChamfer::doEffect_path(std::vector<Geom::Path> const &path_in) +Geom::PathVector +LPEFilletChamfer::doEffect_path(Geom::PathVector const &path_in) { - std::vector<Geom::Path> pathvector_out; + Geom::PathVector pathvector_out; Piecewise<D2<SBasis> > pwd2_in = paths_to_pw(pathv_to_linear_and_cubic_beziers(path_in)); pwd2_in = remove_short_cuts(pwd2_in, .01); Piecewise<D2<SBasis> > der = derivative(pwd2_in); @@ -490,7 +490,7 @@ LPEFilletChamfer::doEffect_path(std::vector<Geom::Path> const &path_in) std::vector<Point> filletChamferData = fillet_chamfer_values.data(); unsigned int counter = 0; const double K = (4.0 / 3.0) * (sqrt(2.0) - 1.0); - std::vector<Geom::Path> path_in_processed = pathv_to_linear_and_cubic_beziers(path_in); + Geom::PathVector path_in_processed = pathv_to_linear_and_cubic_beziers(path_in); for (PathVector::const_iterator path_it = path_in_processed.begin(); path_it != path_in_processed.end(); ++path_it) { if (path_it->empty()) @@ -543,7 +543,7 @@ LPEFilletChamfer::doEffect_path(std::vector<Geom::Path> const &path_in) ray2.setPoints(endArcPoint, (*cubic2)[1]); } Point handle2 = endArcPoint - Point::polar(ray2.angle(),k2); - bool ccwToggle = cross(curve_it1->finalPoint() - startArcPoint, endArcPoint - startArcPoint) < 0; + bool ccwToggle = cross(curve_it1->finalPoint() - startArcPoint, endArcPoint - startArcPoint) > 0; double angle = angle_between(ray1, ray2, ccwToggle); double handleAngle = ray1.angle() - angle; if (ccwToggle) { @@ -582,7 +582,7 @@ LPEFilletChamfer::doEffect_path(std::vector<Geom::Path> const &path_in) Geom::Path path_chamfer; path_chamfer.start(path_out.finalPoint()); if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed) && method != FM_BEZIER )|| method == FM_ARC){ - path_chamfer.appendNew<SVGEllipticalArc>(rx, ry, angleArc, 0, ccwToggle, endArcPoint); + path_chamfer.appendNew<EllipticalArc>(rx, ry, angleArc, 0, ccwToggle, endArcPoint); } else { path_chamfer.appendNew<Geom::CubicBezier>(handle1, handle2, endArcPoint); } @@ -598,7 +598,7 @@ LPEFilletChamfer::doEffect_path(std::vector<Geom::Path> const &path_in) path_chamfer.start(path_out.finalPoint()); if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed) && method != FM_BEZIER )|| method == FM_ARC){ ccwToggle = ccwToggle?0:1; - path_chamfer.appendNew<SVGEllipticalArc>(rx, ry, angleArc, 0, ccwToggle, endArcPoint); + path_chamfer.appendNew<EllipticalArc>(rx, ry, angleArc, 0, ccwToggle, endArcPoint); }else{ path_chamfer.appendNew<Geom::CubicBezier>(inverseHandle1, inverseHandle2, endArcPoint); } @@ -611,13 +611,13 @@ LPEFilletChamfer::doEffect_path(std::vector<Geom::Path> const &path_in) } else if (type == 2) { if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed) && method != FM_BEZIER )|| method == FM_ARC){ ccwToggle = ccwToggle?0:1; - path_out.appendNew<SVGEllipticalArc>(rx, ry, angleArc, 0, ccwToggle, endArcPoint); + path_out.appendNew<EllipticalArc>(rx, ry, angleArc, 0, ccwToggle, endArcPoint); }else{ path_out.appendNew<Geom::CubicBezier>(inverseHandle1, inverseHandle2, endArcPoint); } } else if (type == 1){ if((is_straight_curve(*curve_it1) && is_straight_curve(*curve_it2Fixed) && method != FM_BEZIER )|| method == FM_ARC){ - path_out.appendNew<SVGEllipticalArc>(rx, ry, angleArc, 0, ccwToggle, endArcPoint); + path_out.appendNew<EllipticalArc>(rx, ry, angleArc, 0, ccwToggle, endArcPoint); } else { path_out.appendNew<Geom::CubicBezier>(handle1, handle2, endArcPoint); } diff --git a/src/live_effects/lpe-fillet-chamfer.h b/src/live_effects/lpe-fillet-chamfer.h index f8e03a399..290a37f92 100644 --- a/src/live_effects/lpe-fillet-chamfer.h +++ b/src/live_effects/lpe-fillet-chamfer.h @@ -37,11 +37,11 @@ public: LPEFilletChamfer(LivePathEffectObject *lpeobject); virtual ~LPEFilletChamfer(); - virtual std::vector<Geom::Path> doEffect_path(std::vector<Geom::Path> const &path_in); + virtual Geom::PathVector doEffect_path(Geom::PathVector const &path_in); virtual void doOnApply(SPLPEItem const *lpeItem); virtual void doBeforeEffect(SPLPEItem const *lpeItem); - virtual void adjustForNewPath(std::vector<Geom::Path> const &path_in); + virtual void adjustForNewPath(Geom::PathVector const &path_in); virtual Gtk::Widget* newWidget(); int getKnotsNumber(SPCurve const *c); @@ -53,8 +53,8 @@ public: void fillet(); void inverseFillet(); void updateFillet(); - void doUpdateFillet(std::vector<Geom::Path> const& original_pathv, double power); - void doChangeType(std::vector<Geom::Path> const& original_pathv, int type); + void doUpdateFillet(Geom::PathVector const& original_pathv, double power); + void doChangeType(Geom::PathVector const& original_pathv, int type); void refreshKnots(); FilletChamferPointArrayParam fillet_chamfer_values; diff --git a/src/live_effects/lpe-gears.cpp b/src/live_effects/lpe-gears.cpp index fafe143b5..d4d695542 100644 --- a/src/live_effects/lpe-gears.cpp +++ b/src/live_effects/lpe-gears.cpp @@ -232,10 +232,10 @@ LPEGears::~LPEGears() } -std::vector<Geom::Path> -LPEGears::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPEGears::doEffect_path (Geom::PathVector const &path_in) { - std::vector<Geom::Path> path_out; + Geom::PathVector path_out; Geom::Path gearpath = path_in[0]; Geom::Path::iterator it(gearpath.begin()); diff --git a/src/live_effects/lpe-gears.h b/src/live_effects/lpe-gears.h index bd5e4c4f9..5dd6dd239 100644 --- a/src/live_effects/lpe-gears.h +++ b/src/live_effects/lpe-gears.h @@ -22,7 +22,7 @@ public: LPEGears(LivePathEffectObject *lpeobject); virtual ~LPEGears(); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path(Geom::PathVector const &path_in); private: ScalarParam teeth; diff --git a/src/live_effects/lpe-interpolate.cpp b/src/live_effects/lpe-interpolate.cpp index e41bc6f0e..b1ad07d23 100644 --- a/src/live_effects/lpe-interpolate.cpp +++ b/src/live_effects/lpe-interpolate.cpp @@ -61,7 +61,7 @@ LPEInterpolate::doEffect_path (Geom::PathVector const & path_in) return path_in; } - std::vector<Geom::Path> path_out; + Geom::PathVector path_out; Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_A = path_in[0].toPwSb(); Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_B = path_in[1].toPwSb(); diff --git a/src/live_effects/lpe-interpolate.h b/src/live_effects/lpe-interpolate.h index 679001a81..ef8043e46 100644 --- a/src/live_effects/lpe-interpolate.h +++ b/src/live_effects/lpe-interpolate.h @@ -27,7 +27,7 @@ public: LPEInterpolate(LivePathEffectObject *lpeobject); virtual ~LPEInterpolate(); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); virtual void resetDefaults(SPItem const* item); private: diff --git a/src/live_effects/lpe-interpolate_points.h b/src/live_effects/lpe-interpolate_points.h index 7a3364747..eb706a320 100644 --- a/src/live_effects/lpe-interpolate_points.h +++ b/src/live_effects/lpe-interpolate_points.h @@ -25,7 +25,7 @@ public: LPEInterpolatePoints(LivePathEffectObject *lpeobject); virtual ~LPEInterpolatePoints(); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); private: EnumParam<unsigned> interpolator_type; diff --git a/src/live_effects/lpe-jointype.cpp b/src/live_effects/lpe-jointype.cpp index c6a08d336..28f99eb94 100644 --- a/src/live_effects/lpe-jointype.cpp +++ b/src/live_effects/lpe-jointype.cpp @@ -20,7 +20,7 @@ #include "display/curve.h" #include <2geom/path.h> -#include <2geom/svg-elliptical-arc.h> +#include <2geom/elliptical-arc.h> #include "lpe-jointype.h" @@ -151,7 +151,7 @@ void LPEJoinType::doOnRemove(SPLPEItem const* lpeitem) } } -std::vector<Geom::Path> LPEJoinType::doEffect_path(std::vector<Geom::Path> const & path_in) +Geom::PathVector LPEJoinType::doEffect_path(Geom::PathVector const & path_in) { Geom::PathVector ret; for (size_t i = 0; i < path_in.size(); ++i) { diff --git a/src/live_effects/lpe-jointype.h b/src/live_effects/lpe-jointype.h index bca0961c9..fddaeb619 100644 --- a/src/live_effects/lpe-jointype.h +++ b/src/live_effects/lpe-jointype.h @@ -24,7 +24,7 @@ public: virtual void doOnApply(SPLPEItem const* lpeitem);
virtual void doOnRemove(SPLPEItem const* lpeitem);
- virtual std::vector <Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in);
+ virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in);
private:
LPEJoinType(const LPEJoinType&);
diff --git a/src/live_effects/lpe-knot.cpp b/src/live_effects/lpe-knot.cpp index ec2f9c419..c3000fe0d 100644 --- a/src/live_effects/lpe-knot.cpp +++ b/src/live_effects/lpe-knot.cpp @@ -155,7 +155,7 @@ findShadowedTime(Geom::Path const &patha, std::vector<Geom::Point> const &pt_and // -for each component, the time at which this crossing occurs + the order of this crossing along the component (when starting from 0). namespace LPEKnotNS {//just in case... -CrossingPoints::CrossingPoints(std::vector<Geom::Path> const &paths) : std::vector<CrossingPoint>(){ +CrossingPoints::CrossingPoints(Geom::PathVector const &paths) : std::vector<CrossingPoint>(){ // std::cout<<"\nCrossingPoints creation from path vector\n"; for( unsigned i=0; i<paths.size(); i++){ for( unsigned ii=0; ii < size_nondegenerate(paths[i]); ii++){ @@ -391,11 +391,11 @@ LPEKnot::updateSwitcher(){ } } -std::vector<Geom::Path> -LPEKnot::doEffect_path (std::vector<Geom::Path> const &path_in) +Geom::PathVector +LPEKnot::doEffect_path (Geom::PathVector const &path_in) { using namespace Geom; - std::vector<Geom::Path> path_out; + Geom::PathVector path_out; if (gpaths.size()==0){ return path_in; @@ -433,7 +433,7 @@ LPEKnot::doEffect_path (std::vector<Geom::Path> const &path_in) std::vector<Point> flag_j = gpaths[j][curveidx].pointAndDerivatives(t,1); - int geom_sign = ( cross(flag_i[1],flag_j[1]) > 0 ? 1 : -1); + int geom_sign = ( cross(flag_i[1], flag_j[1]) < 0 ? 1 : -1); bool i0_is_under = false; if ( crossing_points[p].sign * geom_sign > 0 ){ @@ -486,8 +486,9 @@ LPEKnot::doEffect_path (std::vector<Geom::Path> const &path_in) ++beg_comp; --end_comp; Path first = gpaths[i0].portion(dom.back()); - //FIXME: STITCH_DISCONTINUOUS should not be necessary (?!?) - first.append(gpaths[i0].portion(dom.front()), Path::STITCH_DISCONTINUOUS); + //FIXME: stitching should not be necessary (?!?) + first.setStitching(true); + first.append(gpaths[i0].portion(dom.front())); path_out.push_back(first); } } @@ -503,7 +504,7 @@ LPEKnot::doEffect_path (std::vector<Geom::Path> const &path_in) //recursively collect gpaths and stroke widths (stolen from "sp-lpe_item.cpp"). static void -collectPathsAndWidths (SPLPEItem const *lpeitem, std::vector<Geom::Path> &paths, std::vector<double> &stroke_widths){ +collectPathsAndWidths (SPLPEItem const *lpeitem, Geom::PathVector &paths, std::vector<double> &stroke_widths){ if (SP_IS_GROUP(lpeitem)) { std::vector<SPItem*> item_list = sp_item_group_item_list(SP_GROUP(lpeitem)); for ( std::vector<SPItem*>::const_iterator iter = item_list.begin(); iter != item_list.end(); iter++) { @@ -615,8 +616,7 @@ LPEKnot::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::Pat svgd = "M 10,0 C 10,5.52 5.52,10 0,10 -5.52,10 -10,5.52 -10,0 c 0,-5.52 4.48,-10 10,-10 5.52,0 10,4.48 10,10 z"; } PathVector pathv = sp_svg_read_pathv(svgd); - pathv *= Affine(r,0,0,r,0,0); - pathv += switcher; + pathv *= Affine(r,0,0,r,0,0) * Translate(switcher); hp_vec.push_back(pathv); } diff --git a/src/live_effects/lpe-knot.h b/src/live_effects/lpe-knot.h index 080f32de2..95bfaf6e1 100644 --- a/src/live_effects/lpe-knot.h +++ b/src/live_effects/lpe-knot.h @@ -42,8 +42,8 @@ struct CrossingPoint { class CrossingPoints : public std::vector<CrossingPoint>{ public: CrossingPoints() : std::vector<CrossingPoint>() {} - CrossingPoints(Geom::CrossingSet const &cs, std::vector<Geom::Path> const &path);//for self crossings only! - CrossingPoints(std::vector<Geom::Path> const &paths); + CrossingPoints(Geom::CrossingSet const &cs, Geom::PathVector const &path);//for self crossings only! + CrossingPoints(Geom::PathVector const &paths); CrossingPoints(std::vector<double> const &input); std::vector<double> to_vector(); CrossingPoint get(unsigned const i, unsigned const ni); @@ -57,7 +57,7 @@ public: virtual ~LPEKnot(); virtual void doBeforeEffect (SPLPEItem const* lpeitem); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & input_path); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & input_path); /* the knotholder entity classes must be declared friends */ friend class KnotHolderEntityCrossingSwitcher; @@ -65,7 +65,7 @@ public: protected: virtual void addCanvasIndicators(SPLPEItem const *lpeitem, std::vector<Geom::PathVector> &hp_vec); - std::vector<Geom::Path> supplied_path; //for knotholder business + Geom::PathVector supplied_path; //for knotholder business private: void updateSwitcher(); @@ -79,7 +79,7 @@ private: LPEKnotNS::CrossingPoints crossing_points;//topology representation of the knot. - std::vector<Geom::Path> gpaths;//the collection of all the paths in the object or group. + Geom::PathVector gpaths;//the collection of all the paths in the object or group. std::vector<double> gstroke_widths;//the collection of all the stroke widths in the object or group. //UI: please, someone, help me to improve this!! diff --git a/src/live_effects/lpe-lattice2.cpp b/src/live_effects/lpe-lattice2.cpp index 02bda3af5..8c7f46cbd 100644 --- a/src/live_effects/lpe-lattice2.cpp +++ b/src/live_effects/lpe-lattice2.cpp @@ -291,7 +291,7 @@ LPELattice2::vertical(PointParam ¶m_one, PointParam ¶m_two, Geom::Line v double Y = (A[Geom::Y] + B[Geom::Y])/2; A[Geom::Y] = Y; B[Geom::Y] = Y; - Geom::Point nearest = vert.pointAt(vert.nearestPoint(A)); + Geom::Point nearest = vert.pointAt(vert.nearestTime(A)); double distance_one = Geom::distance(A,nearest); double distance_two = Geom::distance(B,nearest); double distance_middle = (distance_one + distance_two)/2; @@ -312,7 +312,7 @@ LPELattice2::horizontal(PointParam ¶m_one, PointParam ¶m_two, Geom::Line double X = (A[Geom::X] + B[Geom::X])/2; A[Geom::X] = X; B[Geom::X] = X; - Geom::Point nearest = horiz.pointAt(horiz.nearestPoint(A)); + Geom::Point nearest = horiz.pointAt(horiz.nearestTime(A)); double distance_one = Geom::distance(A,nearest); double distance_two = Geom::distance(B,nearest); double distance_middle = (distance_one + distance_two)/2; diff --git a/src/live_effects/lpe-line_segment.cpp b/src/live_effects/lpe-line_segment.cpp index 78d286143..dfd8aea8f 100644 --- a/src/live_effects/lpe-line_segment.cpp +++ b/src/live_effects/lpe-line_segment.cpp @@ -48,16 +48,16 @@ LPELineSegment::doBeforeEffect (SPLPEItem const* lpeitem) Inkscape::UI::Tools::lpetool_get_limiting_bbox_corners(lpeitem->document, bboxA, bboxB); } -std::vector<Geom::Path> -LPELineSegment::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPELineSegment::doEffect_path (Geom::PathVector const & path_in) { - std::vector<Geom::Path> output; + Geom::PathVector output; - A = initialPoint(path_in); - B = finalPoint(path_in); + A = path_in.initialPoint(); + B = path_in.finalPoint(); Geom::Rect dummyRect(bboxA, bboxB); - boost::optional<Geom::LineSegment> intersection_segment = Geom::rect_line_intersect(dummyRect, Geom::Line(A, B)); + boost::optional<Geom::LineSegment> intersection_segment = Geom::Line(A, B).clip(dummyRect); if (!intersection_segment) { g_print ("Possible error - no intersection with limiting bounding box.\n"); @@ -65,11 +65,11 @@ LPELineSegment::doEffect_path (std::vector<Geom::Path> const & path_in) } if (end_type == END_OPEN_INITIAL || end_type == END_OPEN_BOTH) { - A = (*intersection_segment).initialPoint(); + A = intersection_segment->initialPoint(); } if (end_type == END_OPEN_FINAL || end_type == END_OPEN_BOTH) { - B = (*intersection_segment).finalPoint(); + B = intersection_segment->finalPoint(); } Geom::Path path(A); diff --git a/src/live_effects/lpe-line_segment.h b/src/live_effects/lpe-line_segment.h index 1a57688b6..def828fe2 100644 --- a/src/live_effects/lpe-line_segment.h +++ b/src/live_effects/lpe-line_segment.h @@ -34,7 +34,7 @@ public: virtual void doBeforeEffect (SPLPEItem const* lpeitem); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); //private: EnumParam<EndType> end_type; diff --git a/src/live_effects/lpe-mirror_symmetry.cpp b/src/live_effects/lpe-mirror_symmetry.cpp index 0bb67a4a2..783053df2 100644 --- a/src/live_effects/lpe-mirror_symmetry.cpp +++ b/src/live_effects/lpe-mirror_symmetry.cpp @@ -64,24 +64,24 @@ LPEMirrorSymmetry::doOnApply (SPLPEItem const* lpeitem) Point B(bbox.left(), bbox.top()); A *= t; B *= t; - Piecewise<D2<SBasis> > rline = Piecewise<D2<SBasis> >(D2<SBasis>(Linear(A[X], B[X]), Linear(A[Y], B[Y]))); + Piecewise<D2<SBasis> > rline = Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(A[X], B[X]), SBasis(A[Y], B[Y]))); reflection_line.set_new_value(rline, true); } -std::vector<Geom::Path> -LPEMirrorSymmetry::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPEMirrorSymmetry::doEffect_path (Geom::PathVector const & path_in) { // Don't allow empty path parameter: if ( reflection_line.get_pathvector().empty() ) { return path_in; } - std::vector<Geom::Path> path_out; + Geom::PathVector path_out; if (!discard_orig_path) { path_out = path_in; } - std::vector<Geom::Path> mline(reflection_line.get_pathvector()); + Geom::PathVector mline(reflection_line.get_pathvector()); Geom::Point A(mline.front().initialPoint()); Geom::Point B(mline.back().finalPoint()); diff --git a/src/live_effects/lpe-mirror_symmetry.h b/src/live_effects/lpe-mirror_symmetry.h index a4a2b86c0..7a484a473 100644 --- a/src/live_effects/lpe-mirror_symmetry.h +++ b/src/live_effects/lpe-mirror_symmetry.h @@ -32,7 +32,7 @@ public: virtual void doBeforeEffect (SPLPEItem const* lpeitem); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); private: BoolParam discard_orig_path; diff --git a/src/live_effects/lpe-offset.cpp b/src/live_effects/lpe-offset.cpp index ba7179476..dd7af92a2 100644 --- a/src/live_effects/lpe-offset.cpp +++ b/src/live_effects/lpe-offset.cpp @@ -20,7 +20,7 @@ #include <2geom/path.h> #include <2geom/piecewise.h> #include <2geom/sbasis-geometric.h> -#include <2geom/svg-elliptical-arc.h> +#include <2geom/elliptical-arc.h> #include <2geom/transforms.h> namespace Inkscape { @@ -52,7 +52,7 @@ static void append_half_circle(Geom::Piecewise<Geom::D2<Geom::SBasis> > &pwd2, using namespace Geom; double r = L2(dir); - SVGEllipticalArc cap(center + dir, r, r, angle_between(Point(1,0), dir), false, false, center - dir); + EllipticalArc cap(center + dir, r, r, angle_between(Point(1,0), dir), false, false, center - dir); Piecewise<D2<SBasis> > cap_pwd2(cap.toSBasis()); pwd2.continuousConcat(cap_pwd2); } @@ -72,7 +72,7 @@ LPEOffset::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_ Piecewise<D2<SBasis> > output; - double t = nearest_point(offset_pt, pwd2_in); + double t = nearest_time(offset_pt, pwd2_in); Point A = pwd2_in.valueAt(t); double offset = L2(A - offset_pt); diff --git a/src/live_effects/lpe-parallel.cpp b/src/live_effects/lpe-parallel.cpp index aa7405607..23cd5e2e7 100644 --- a/src/live_effects/lpe-parallel.cpp +++ b/src/live_effects/lpe-parallel.cpp @@ -91,7 +91,7 @@ LPEParallel::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd C = offset_pt - dir * length_left; D = offset_pt + dir * length_right; - output = Piecewise<D2<SBasis> >(D2<SBasis>(Linear(C[X], D[X]), Linear(C[Y], D[Y]))); + output = Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(C[X], D[X]), SBasis(C[Y], D[Y]))); return output + dir; } diff --git a/src/live_effects/lpe-perp_bisector.cpp b/src/live_effects/lpe-perp_bisector.cpp index feed55b26..660318c57 100644 --- a/src/live_effects/lpe-perp_bisector.cpp +++ b/src/live_effects/lpe-perp_bisector.cpp @@ -67,7 +67,7 @@ KnotHolderEntityEnd::bisector_end_set(Geom::Point const &p, guint state, bool le Geom::Point const s = snap_knot_position(p, state); - double lambda = Geom::nearest_point(s, lpe->M, lpe->perp_dir); + double lambda = Geom::nearest_time(s, lpe->M, lpe->perp_dir); if (left) { lpe->C = lpe->M + lpe->perp_dir * lambda; lpe->length_left.param_set_value(lambda); @@ -146,7 +146,7 @@ LPEPerpBisector::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & C = M + perp_dir * length_left; D = M - perp_dir * length_right; - output = Piecewise<D2<SBasis> >(D2<SBasis>(Linear(C[X], D[X]), Linear(C[Y], D[Y]))); + output = Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(C[X], D[X]), SBasis(C[Y], D[Y]))); return output; } diff --git a/src/live_effects/lpe-perspective-envelope.cpp b/src/live_effects/lpe-perspective-envelope.cpp index 08100bb2d..72c1b0e99 100644 --- a/src/live_effects/lpe-perspective-envelope.cpp +++ b/src/live_effects/lpe-perspective-envelope.cpp @@ -323,7 +323,7 @@ LPEPerspectiveEnvelope::vertical(PointParam ¶m_one, PointParam ¶m_two, G double Y = (A[Geom::Y] + B[Geom::Y])/2; A[Geom::Y] = Y; B[Geom::Y] = Y; - Geom::Point nearest = vert.pointAt(vert.nearestPoint(A)); + Geom::Point nearest = vert.pointAt(vert.nearestTime(A)); double distance_one = Geom::distance(A,nearest); double distance_two = Geom::distance(B,nearest); double distance_middle = (distance_one + distance_two)/2; @@ -344,7 +344,7 @@ LPEPerspectiveEnvelope::horizontal(PointParam ¶m_one, PointParam ¶m_two, double X = (A[Geom::X] + B[Geom::X])/2; A[Geom::X] = X; B[Geom::X] = X; - Geom::Point nearest = horiz.pointAt(horiz.nearestPoint(A)); + Geom::Point nearest = horiz.pointAt(horiz.nearestTime(A)); double distance_one = Geom::distance(A,nearest); double distance_two = Geom::distance(B,nearest); double distance_middle = (distance_one + distance_two)/2; diff --git a/src/live_effects/lpe-powerstroke.cpp b/src/live_effects/lpe-powerstroke.cpp index 5d9d224e8..f90d67d4e 100644 --- a/src/live_effects/lpe-powerstroke.cpp +++ b/src/live_effects/lpe-powerstroke.cpp @@ -27,12 +27,13 @@ #include <2geom/sbasis-geometric.h> #include <2geom/transforms.h> #include <2geom/bezier-utils.h> -#include <2geom/svg-elliptical-arc.h> +#include <2geom/elliptical-arc.h> #include <2geom/sbasis-to-bezier.h> #include <2geom/path-sink.h> #include <2geom/path-intersection.h> #include <2geom/crossing.h> #include <2geom/ellipse.h> +#include <2geom/circle.h> #include <2geom/math-utils.h> #include <math.h> @@ -46,9 +47,9 @@ namespace Geom { static boost::optional<Point> intersection_point( Point const & origin_a, Point const & vector_a, Point const & origin_b, Point const & vector_b) { - Coord denom = cross(vector_b, vector_a); + Coord denom = cross(vector_a, vector_b); if (!are_near(denom,0.)){ - Coord t = (cross(origin_a,vector_b) + cross(vector_b,origin_b)) / denom; + Coord t = (cross(vector_b, origin_a) + cross(origin_b, vector_b)) / denom; return origin_a + t * vector_a; } return boost::none; @@ -94,71 +95,6 @@ static Ellipse find_ellipse(Point P, Point Q, Point O) } /** - * Refer to: Weisstein, Eric W. "Circle-Circle Intersection." - From MathWorld--A Wolfram Web Resource. - http://mathworld.wolfram.com/Circle-CircleIntersection.html - * - * @return 0 if no intersection - * @return 1 if one circle is contained in the other - * @return 2 if intersections are found (they are written to p0 and p1) - */ -static int circle_circle_intersection(Circle const &circle0, Circle const &circle1, - Point & p0, Point & p1) -{ - Point X0 = circle0.center(); - double r0 = circle0.ray(); - Point X1 = circle1.center(); - double r1 = circle1.ray(); - - /* dx and dy are the vertical and horizontal distances between - * the circle centers. - */ - Point D = X1 - X0; - - /* Determine the straight-line distance between the centers. */ - double d = L2(D); - - /* Check for solvability. */ - if (d > (r0 + r1)) - { - /* no solution. circles do not intersect. */ - return 0; - } - if (d <= fabs(r0 - r1)) - { - /* no solution. one circle is contained in the other */ - return 1; - } - - /* 'point 2' is the point where the line through the circle - * intersection points crosses the line between the circle - * centers. - */ - - /* Determine the distance from point 0 to point 2. */ - double a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ; - - /* Determine the coordinates of point 2. */ - Point p2 = X0 + D * (a/d); - - /* Determine the distance from point 2 to either of the - * intersection points. - */ - double h = std::sqrt((r0*r0) - (a*a)); - - /* Now determine the offsets of the intersection points from - * point 2. - */ - Point r = (h/d)*rot90(D); - - /* Determine the absolute intersection points. */ - p0 = p2 + r; - p1 = p2 - r; - - return 2; -} - -/** * Find circle that touches inside of the curve, with radius matching the curvature, at time value \c t. * Because this method internally uses unitTangentAt, t should be smaller than 1.0 (see unitTangentAt). */ @@ -313,7 +249,7 @@ LPEPowerStroke::doOnApply(SPLPEItem const* lpeitem) Geom::Path const &path = pathv.front(); Geom::Path::size_type const size = path.size_default(); if (!path.closed()) { - points.push_back( Geom::Point(0.2,width) ); + points.push_back( Geom::Point(0.2,width) ); } points.push_back( Geom::Point(0.5*size,width) ); if (!path.closed()) { @@ -362,7 +298,7 @@ void LPEPowerStroke::doOnRemove(SPLPEItem const* lpeitem) } void -LPEPowerStroke::adjustForNewPath(std::vector<Geom::Path> const & path_in) +LPEPowerStroke::adjustForNewPath(Geom::PathVector const & path_in) { if (!path_in.empty()) { offset_points.recalculate_controlpoints_for_new_pwd2(path_in[0].toPwSb()); @@ -387,6 +323,8 @@ static Geom::Path path_from_piecewise_fix_cusps( Geom::Piecewise<Geom::D2<Geom:: return pb.peek().front(); } + pb.setStitching(true); + Geom::Point start = B[0].at0(); pb.moveTo(start); build_from_sbasis(pb, B[0], tol, false); @@ -446,7 +384,7 @@ static Geom::Path path_from_piecewise_fix_cusps( Geom::Piecewise<Geom::D2<Geom:: break; } - pb.arcTo( ellipse.ray(Geom::X), ellipse.ray(Geom::Y), ellipse.rot_angle(), + pb.arcTo( ellipse.ray(Geom::X), ellipse.ray(Geom::Y), ellipse.rotationAngle(), false, width < 0, B[i].at0() ); break; @@ -482,24 +420,24 @@ static Geom::Path path_from_piecewise_fix_cusps( Geom::Piecewise<Geom::D2<Geom:: // Extrapolate using the curvature at the end of the path segments to join Geom::Circle circle1 = Geom::touching_circle(reverse(B[prev_i]), 0.0); Geom::Circle circle2 = Geom::touching_circle(B[i], 0.0); - Geom::Point points[2]; - int solutions = circle_circle_intersection(circle1, circle2, points[0], points[1]); - if (solutions == 2) { + std::vector<Geom::ShapeIntersection> solutions; + solutions = circle1.intersect(circle2); + if (solutions.size() == 2) { Geom::Point sol(0.,0.); - if ( dot(tang2,points[0]-B[i].at0()) > 0 ) { + if ( dot(tang2, solutions[0].point() - B[i].at0()) > 0 ) { // points[0] is bad, choose points[1] - sol = points[1]; - } else if ( dot(tang2,points[1]-B[i].at0()) > 0 ) { // points[0] could be good, now check points[1] + sol = solutions[1].point(); + } else if ( dot(tang2, solutions[1].point() - B[i].at0()) > 0 ) { // points[0] could be good, now check points[1] // points[1] is bad, choose points[0] - sol = points[0]; + sol = solutions[0].point(); } else { // both points are good, choose nearest - sol = ( distanceSq(B[i].at0(), points[0]) < distanceSq(B[i].at0(), points[1]) ) ? - points[0] : points[1]; + sol = ( distanceSq(B[i].at0(), solutions[0].point()) < distanceSq(B[i].at0(), solutions[1].point()) ) ? + solutions[0].point() : solutions[1].point(); } - Geom::EllipticalArc *arc0 = circle1.arc(B[prev_i].at1(), 0.5*(B[prev_i].at1()+sol), sol, true); - Geom::EllipticalArc *arc1 = circle2.arc(sol, 0.5*(sol+B[i].at0()), B[i].at0(), true); + Geom::EllipticalArc *arc0 = circle1.arc(B[prev_i].at1(), 0.5*(B[prev_i].at1()+sol), sol); + Geom::EllipticalArc *arc1 = circle2.arc(sol, 0.5*(sol+B[i].at0()), B[i].at0()); if (arc0) { build_from_sbasis(pb,arc0->toSBasis(), tol, false); @@ -574,7 +512,7 @@ static Geom::Path path_from_piecewise_fix_cusps( Geom::Piecewise<Geom::D2<Geom:: Geom::Path spiro; Spiro::spiro_run(controlpoints, 4, spiro); - pb.append(spiro.portion(1,spiro.size_open()-1), Geom::Path::STITCH_DISCONTINUOUS); + pb.append(spiro.portion(1, spiro.size_open() - 1)); break; } case LINEJOIN_BEVEL: @@ -593,15 +531,15 @@ static Geom::Path path_from_piecewise_fix_cusps( Geom::Piecewise<Geom::D2<Geom:: if (cross.size() != 1) { // empty crossing or too many crossings: default to bevel pb.lineTo(B[i].at0()); - pb.append(bzr2, Geom::Path::STITCH_DISCONTINUOUS); + pb.append(bzr2); } else { // :-) quick hack: for (unsigned i=0; i < bzr1.size_open(); ++i) { pb.backspace(); } - pb.append( bzr1.portion(0, cross[0].ta), Geom::Path::STITCH_DISCONTINUOUS ); - pb.append( bzr2.portion(cross[0].tb, bzr2.size_open()), Geom::Path::STITCH_DISCONTINUOUS ); + pb.append( bzr1.portion(0, cross[0].ta) ); + pb.append( bzr2.portion(cross[0].tb, bzr2.size_open()) ); } } } else { @@ -615,12 +553,12 @@ static Geom::Path path_from_piecewise_fix_cusps( Geom::Piecewise<Geom::D2<Geom:: } -std::vector<Geom::Path> -LPEPowerStroke::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPEPowerStroke::doEffect_path (Geom::PathVector const & path_in) { using namespace Geom; - std::vector<Geom::Path> path_out; + Geom::PathVector path_out; if (path_in.empty()) { return path_out; } @@ -737,12 +675,12 @@ LPEPowerStroke::doEffect_path (std::vector<Geom::Path> const & path_in) default: { double radius1 = 0.5 * distance(pwd2_out.lastValue(), mirrorpath.firstValue()); - fixed_path.appendNew<SVGEllipticalArc>( radius1, radius1, M_PI/2., false, y.lastValue() < 0, mirrorpath.firstValue() ); + fixed_path.appendNew<EllipticalArc>( radius1, radius1, M_PI/2., false, y.lastValue() < 0, mirrorpath.firstValue() ); break; } } - fixed_path.append(fixed_mirrorpath, Geom::Path::STITCH_DISCONTINUOUS); + fixed_path.append(fixed_mirrorpath); switch (start_linecap) { case LINECAP_ZERO_WIDTH: @@ -775,7 +713,7 @@ LPEPowerStroke::doEffect_path (std::vector<Geom::Path> const & path_in) default: { double radius2 = 0.5 * distance(pwd2_out.firstValue(), mirrorpath.lastValue()); - fixed_path.appendNew<SVGEllipticalArc>( radius2, radius2, M_PI/2., false, y.firstValue() < 0, pwd2_out.firstValue() ); + fixed_path.appendNew<EllipticalArc>( radius2, radius2, M_PI/2., false, y.firstValue() < 0, pwd2_out.firstValue() ); break; } } diff --git a/src/live_effects/lpe-powerstroke.h b/src/live_effects/lpe-powerstroke.h index a773434aa..135d39274 100644 --- a/src/live_effects/lpe-powerstroke.h +++ b/src/live_effects/lpe-powerstroke.h @@ -26,13 +26,13 @@ public: virtual ~LPEPowerStroke(); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); virtual void doOnApply(SPLPEItem const* lpeitem); virtual void doOnRemove(SPLPEItem const* lpeitem); // methods called by path-manipulator upon edits - void adjustForNewPath(std::vector<Geom::Path> const & path_in); + void adjustForNewPath(Geom::PathVector const & path_in); PowerStrokePointArrayParam offset_points; diff --git a/src/live_effects/lpe-rough-hatches.cpp b/src/live_effects/lpe-rough-hatches.cpp index 455653e46..76421e0f0 100644 --- a/src/live_effects/lpe-rough-hatches.cpp +++ b/src/live_effects/lpe-rough-hatches.cpp @@ -529,7 +529,8 @@ LPERoughHatches::smoothSnake(std::vector<std::vector<Point> > const &linearSnake } if ( fat_output.get_value() ){ res_comp = res_comp_bot; - res_comp.append(res_comp_top.reverse(),Geom::Path::STITCH_DISCONTINUOUS); + res_comp.setStitching(true); + res_comp.append(res_comp_top.reversed()); } result.concat(res_comp.toPwSb()); } diff --git a/src/live_effects/lpe-roughen.cpp b/src/live_effects/lpe-roughen.cpp index c5e002351..33ffd96d6 100644 --- a/src/live_effects/lpe-roughen.cpp +++ b/src/live_effects/lpe-roughen.cpp @@ -271,8 +271,8 @@ SPCurve *LPERoughen::addNodesAndJitter(const Geom::Curve *A, double t) } if (cubic) { std::pair<Geom::CubicBezier, Geom::CubicBezier> div = cubic->subdivide(t); - std::vector<Geom::Point> seg1 = div.first.points(), - seg2 = div.second.points(); + std::vector<Geom::Point> seg1 = div.first.controlPoints(), + seg2 = div.second.controlPoints(); out->moveto(seg1[0]); out->curveto(seg1[1] + point1, seg1[2] + point2, seg1[3] + point3); out->curveto(seg2[1] + point_b1, seg2[2], seg2[3]); diff --git a/src/live_effects/lpe-ruler.cpp b/src/live_effects/lpe-ruler.cpp index fd611c78d..49b5faa2e 100644 --- a/src/live_effects/lpe-ruler.cpp +++ b/src/live_effects/lpe-ruler.cpp @@ -111,7 +111,7 @@ LPERuler::ruler_mark(Geom::Point const &A, Geom::Point const &n, MarkType const break; } - Piecewise<D2<SBasis> > seg(D2<SBasis>(Linear(C[X], D[X]), Linear(C[Y], D[Y]))); + Piecewise<D2<SBasis> > seg(D2<SBasis>(SBasis(C[X], D[X]), SBasis(C[Y], D[Y]))); return seg; } diff --git a/src/live_effects/lpe-show_handles.cpp b/src/live_effects/lpe-show_handles.cpp index 2638f312e..0bc1c4f17 100644 --- a/src/live_effects/lpe-show_handles.cpp +++ b/src/live_effects/lpe-show_handles.cpp @@ -79,9 +79,9 @@ void LPEShowHandles::doBeforeEffect (SPLPEItem const* lpeitem) stroke_width = item->style->stroke_width.computed; } -std::vector<Geom::Path> LPEShowHandles::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector LPEShowHandles::doEffect_path (Geom::PathVector const & path_in) { - std::vector<Geom::Path> path_out; + Geom::PathVector path_out; Geom::PathVector const original_pathv = pathv_to_linear_and_cubic_beziers(path_in); if(original_path) { for (unsigned int i=0; i < path_in.size(); i++) { @@ -112,14 +112,14 @@ LPEShowHandles::generateHelperPath(Geom::PathVector result) continue; } //Itreadores - Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve - Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve - Geom::Path::const_iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop + Geom::Path::iterator curve_it1 = path_it->begin(); // incoming curve + Geom::Path::iterator curve_it2 = ++(path_it->begin()); // outgoing curve + Geom::Path::iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop if (path_it->closed()) { // if the path is closed, maybe we have to stop a bit earlier because the // closing line segment has zerolength. - const Geom::Curve &closingline = path_it->back_closed(); // the closing line segment is always of type + Geom::Curve const &closingline = path_it->back_closed(); // the closing line segment is always of type // Geom::LineSegment. if (are_near(closingline.initialPoint(), closingline.finalPoint())) { // closingline.isDegenerate() did not work, because it only checks for @@ -165,9 +165,7 @@ LPEShowHandles::drawNode(Geom::Point p) char const * svgd; svgd = "M 0.05,0 A 0.05,0.05 0 0 1 0,0.05 0.05,0.05 0 0 1 -0.05,0 0.05,0.05 0 0 1 0,-0.05 0.05,0.05 0 0 1 0.05,0 Z M -0.5,-0.5 0.5,-0.5 0.5,0.5 -0.5,0.5 Z"; Geom::PathVector pathv = sp_svg_read_pathv(svgd); - pathv *= Geom::Rotate::from_degrees(rotate_nodes); - pathv *= Geom::Scale (diameter); - pathv += p; + pathv *= Geom::Rotate::from_degrees(rotate_nodes) * Geom::Scale(diameter) * Geom::Translate(p); outline_path.push_back(pathv[0]); outline_path.push_back(pathv[1]); } @@ -181,8 +179,7 @@ LPEShowHandles::drawHandle(Geom::Point p) char const * svgd; svgd = "M 0.7,0.35 A 0.35,0.35 0 0 1 0.35,0.7 0.35,0.35 0 0 1 0,0.35 0.35,0.35 0 0 1 0.35,0 0.35,0.35 0 0 1 0.7,0.35 Z"; Geom::PathVector pathv = sp_svg_read_pathv(svgd); - pathv *= Geom::Scale (diameter); - pathv += p-Geom::Point(diameter * 0.35,diameter * 0.35); + pathv *= Geom::Scale (diameter) * Geom::Translate(p - Geom::Point(diameter * 0.35,diameter * 0.35)); outline_path.push_back(pathv[0]); } } diff --git a/src/live_effects/lpe-show_handles.h b/src/live_effects/lpe-show_handles.h index 77b28e77a..34390dd32 100644 --- a/src/live_effects/lpe-show_handles.h +++ b/src/live_effects/lpe-show_handles.h @@ -36,7 +36,7 @@ public: protected: - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); private: diff --git a/src/live_effects/lpe-simplify.cpp b/src/live_effects/lpe-simplify.cpp index 7fc20ede1..265192a17 100644 --- a/src/live_effects/lpe-simplify.cpp +++ b/src/live_effects/lpe-simplify.cpp @@ -164,14 +164,13 @@ LPESimplify::generateHelperPathAndSmooth(Geom::PathVector &result) Geom::PathVector tmp_path; Geom::CubicBezier const *cubic = NULL; for (Geom::PathVector::iterator path_it = result.begin(); path_it != result.end(); ++path_it) { - //Si está vacío... if (path_it->empty()) { continue; } - //Itreadores - Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve - Geom::Path::const_iterator curve_it2 = ++(path_it->begin());// outgoing curve - Geom::Path::const_iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop + + Geom::Path::iterator curve_it1 = path_it->begin(); // incoming curve + Geom::Path::iterator curve_it2 = ++(path_it->begin());// outgoing curve + Geom::Path::iterator curve_endit = path_it->end_default(); // this determines when the loop has to stop SPCurve *nCurve = new SPCurve(); if (path_it->closed()) { // if the path is closed, maybe we have to stop a bit earlier because the @@ -264,8 +263,7 @@ LPESimplify::drawNode(Geom::Point p) char const * svgd; svgd = "M 0.55,0.5 A 0.05,0.05 0 0 1 0.5,0.55 0.05,0.05 0 0 1 0.45,0.5 0.05,0.05 0 0 1 0.5,0.45 0.05,0.05 0 0 1 0.55,0.5 Z M 0,0 1,0 1,1 0,1 Z"; Geom::PathVector pathv = sp_svg_read_pathv(svgd); - pathv *= Geom::Affine(r,0,0,r,0,0); - pathv += p - Geom::Point(0.5*r,0.5*r); + pathv *= Geom::Scale(r) * Geom::Translate(p - Geom::Point(0.5*r,0.5*r)); hp.push_back(pathv[0]); hp.push_back(pathv[1]); } @@ -277,8 +275,7 @@ LPESimplify::drawHandle(Geom::Point p) char const * svgd; svgd = "M 0.7,0.35 A 0.35,0.35 0 0 1 0.35,0.7 0.35,0.35 0 0 1 0,0.35 0.35,0.35 0 0 1 0.35,0 0.35,0.35 0 0 1 0.7,0.35 Z"; Geom::PathVector pathv = sp_svg_read_pathv(svgd); - pathv *= Geom::Affine(r,0,0,r,0,0); - pathv += p - Geom::Point(0.35*r,0.35*r); + pathv *= Geom::Scale(r) * Geom::Translate(p - Geom::Point(0.35*r,0.35*r)); hp.push_back(pathv[0]); } diff --git a/src/live_effects/lpe-skeleton.cpp b/src/live_effects/lpe-skeleton.cpp index c61e9773f..6e4afbe9b 100644 --- a/src/live_effects/lpe-skeleton.cpp +++ b/src/live_effects/lpe-skeleton.cpp @@ -62,10 +62,10 @@ LPESkeleton::doEffect (SPCurve * curve) // spice this up to make the effect actually *do* something! } -std::vector<Geom::Path> -LPESkeleton::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPESkeleton::doEffect_path (Geom::PathVector const & path_in) { - std::vector<Geom::Path> path_out; + Geom::PathVector path_out; path_out = path_in; // spice this up to make the effect actually *do* something! diff --git a/src/live_effects/lpe-skeleton.h b/src/live_effects/lpe-skeleton.h index 124d1a4cb..3b45b6978 100644 --- a/src/live_effects/lpe-skeleton.h +++ b/src/live_effects/lpe-skeleton.h @@ -36,8 +36,8 @@ public: // Choose to implement one of the doEffect functions. You can delete or comment out the others. // virtual void doEffect (SPCurve * curve); -// virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); - virtual Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in); +// virtual Geom::PathVector doEffect_path (Geom::PathVector const &path_in); + virtual Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const &pwd2_in); /* the knotholder entity classes (if any) can be declared friends */ //friend class Skeleton::KnotHolderEntityMyHandle; diff --git a/src/live_effects/lpe-sketch.cpp b/src/live_effects/lpe-sketch.cpp index 551dbe16a..82d343f6e 100644 --- a/src/live_effects/lpe-sketch.cpp +++ b/src/live_effects/lpe-sketch.cpp @@ -346,7 +346,7 @@ LPESketch::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_ //TODO: put this 4 as a parameter in the UI... //TODO: what if with v=0? double l = tgtlength*(1-tgtlength_rdm)/v_t.length(); - double r = std::pow(v_t.length(),3)/cross(a_t,v_t); + double r = std::pow(v_t.length(), 3) / cross(v_t, a_t); r = sqrt((2*fabs(r)-tgtscale)*tgtscale)/v_t.length(); l=(r<l)?r:l; //collect the tgt segment into output. diff --git a/src/live_effects/lpe-spiro.cpp b/src/live_effects/lpe-spiro.cpp index 8b4274ab2..eefd25c7d 100644 --- a/src/live_effects/lpe-spiro.cpp +++ b/src/live_effects/lpe-spiro.cpp @@ -10,8 +10,7 @@ #include <typeinfo> #include <2geom/pathvector.h> #include <2geom/affine.h> -#include <2geom/bezier-curve.h> -#include <2geom/hvlinesegment.h> +#include <2geom/curves.h> #include "helper/geom-nodetype.h" #include "helper/geom-curves.h" diff --git a/src/live_effects/lpe-tangent_to_curve.cpp b/src/live_effects/lpe-tangent_to_curve.cpp index bce4876af..978ab57fb 100644 --- a/src/live_effects/lpe-tangent_to_curve.cpp +++ b/src/live_effects/lpe-tangent_to_curve.cpp @@ -90,7 +90,7 @@ LPETangentToCurve::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const C = ptA - derivA * length_left; D = ptA + derivA * length_right; - output = Piecewise<D2<SBasis> >(D2<SBasis>(Linear(C[X], D[X]), Linear(C[Y], D[Y]))); + output = Piecewise<D2<SBasis> >(D2<SBasis>(SBasis(C[X], D[X]), SBasis(C[Y], D[Y]))); return output; } @@ -135,7 +135,7 @@ KnotHolderEntityAttachPt::knot_set(Geom::Point const &p, Geom::Point const &/*or } Piecewise<D2<SBasis> > pwd2 = paths_to_pw( lpe->pathvector_before_effect ); - double t0 = nearest_point(s, pwd2); + double t0 = nearest_time(s, pwd2); lpe->t_attach.param_set_value(t0); // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating. @@ -149,7 +149,7 @@ KnotHolderEntityLeftEnd::knot_set(Geom::Point const &p, Geom::Point const &/*ori Geom::Point const s = snap_knot_position(p, state); - double lambda = Geom::nearest_point(s, lpe->ptA, lpe->derivA); + double lambda = Geom::nearest_time(s, lpe->ptA, lpe->derivA); lpe->length_left.param_set_value(-lambda); sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true); @@ -162,7 +162,7 @@ KnotHolderEntityRightEnd::knot_set(Geom::Point const &p, Geom::Point const &/*or Geom::Point const s = snap_knot_position(p, state); - double lambda = Geom::nearest_point(s, lpe->ptA, lpe->derivA); + double lambda = Geom::nearest_time(s, lpe->ptA, lpe->derivA); lpe->length_right.param_set_value(lambda); sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true); diff --git a/src/live_effects/lpe-taperstroke.cpp b/src/live_effects/lpe-taperstroke.cpp index 5765febbd..ef616f802 100644 --- a/src/live_effects/lpe-taperstroke.cpp +++ b/src/live_effects/lpe-taperstroke.cpp @@ -14,7 +14,6 @@ #include "live_effects/lpe-taperstroke.h" #include <2geom/path.h> -#include <2geom/shape.h> #include <2geom/path.h> #include <2geom/circle.h> #include <2geom/sbasis-to-bezier.h> @@ -194,7 +193,7 @@ Piecewise<D2<SBasis> > stretch_along(Piecewise<D2<SBasis> > pwd2_in, Geom::Path Geom::PathVector LPETaperStroke::doEffect_path(Geom::PathVector const& path_in) { Geom::Path first_cusp = return_at_first_cusp(path_in[0]); - Geom::Path last_cusp = return_at_first_cusp(path_in[0].reverse()); + Geom::Path last_cusp = return_at_first_cusp(path_in[0].reversed()); bool zeroStart = false; // [distance from start taper knot -> start of path] == 0 bool zeroEnd = false; // [distance from end taper knot -> end of path] == 0 @@ -318,7 +317,7 @@ Geom::PathVector LPETaperStroke::doEffect_path(Geom::PathVector const& path_in) if (!metInMiddle) { // append the inside outline of the path (against direction) - throwaway_path = half_outline(pathv_out[1].reverse(), fabs(line_width)/2., miter_limit, static_cast<LineJoinType>(join_type.get_value())); + throwaway_path = half_outline(pathv_out[1].reversed(), fabs(line_width)/2., miter_limit, static_cast<LineJoinType>(join_type.get_value())); if (!Geom::are_near(real_path.finalPoint(), throwaway_path.initialPoint()) && real_path.size() >= 1) { real_path.appendNew<Geom::LineSegment>(throwaway_path.initialPoint()); @@ -483,7 +482,7 @@ void KnotHolderEntityAttachBegin::knot_set(Geom::Point const &p, Geom::Point con Geom::Path p_in = return_at_first_cusp(pathv[0]); pwd2.concat(p_in.toPwSb()); - double t0 = nearest_point(s, pwd2); + double t0 = nearest_time(s, pwd2); lpe->attach_start.param_set_value(t0); // FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating. @@ -508,10 +507,10 @@ void KnotHolderEntityAttachEnd::knot_set(Geom::Point const &p, Geom::Point const return; } Geom::PathVector pathv = lpe->pathvector_before_effect; - Geom::Path p_in = return_at_first_cusp(pathv[0].reverse()); + Geom::Path p_in = return_at_first_cusp(pathv[0].reversed()); Piecewise<D2<SBasis> > pwd2 = p_in.toPwSb(); - double t0 = nearest_point(s, pwd2); + double t0 = nearest_time(s, pwd2); lpe->attach_end.param_set_value(t0); sp_lpe_item_update_patheffect (SP_LPE_ITEM(item), false, true); diff --git a/src/live_effects/lpe-test-doEffect-stack.cpp b/src/live_effects/lpe-test-doEffect-stack.cpp index c6787aae1..2bcd4c136 100644 --- a/src/live_effects/lpe-test-doEffect-stack.cpp +++ b/src/live_effects/lpe-test-doEffect-stack.cpp @@ -47,14 +47,14 @@ LPEdoEffectStackTest::doEffect (SPCurve * curve) } } -std::vector<Geom::Path> -LPEdoEffectStackTest::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPEdoEffectStackTest::doEffect_path (Geom::PathVector const &path_in) { if (step >= 2) { return Effect::doEffect_path(path_in); } else { // return here - std::vector<Geom::Path> path_out = path_in; + Geom::PathVector path_out = path_in; return path_out; } } diff --git a/src/live_effects/lpe-test-doEffect-stack.h b/src/live_effects/lpe-test-doEffect-stack.h index fa3ee09be..208a345c6 100644 --- a/src/live_effects/lpe-test-doEffect-stack.h +++ b/src/live_effects/lpe-test-doEffect-stack.h @@ -27,7 +27,7 @@ public: virtual ~LPEdoEffectStackTest(); virtual void doEffect (SPCurve * curve); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); virtual Geom::Piecewise<Geom::D2<Geom::SBasis> > doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in); private: diff --git a/src/live_effects/lpe-vonkoch.cpp b/src/live_effects/lpe-vonkoch.cpp index b52816c87..7b424e019 100644 --- a/src/live_effects/lpe-vonkoch.cpp +++ b/src/live_effects/lpe-vonkoch.cpp @@ -76,12 +76,12 @@ LPEVonKoch::~LPEVonKoch() } -std::vector<Geom::Path> -LPEVonKoch::doEffect_path (std::vector<Geom::Path> const & path_in) +Geom::PathVector +LPEVonKoch::doEffect_path (Geom::PathVector const & path_in) { using namespace Geom; - std::vector<Geom::Path> generating_path = generator.get_pathvector(); + Geom::PathVector generating_path = generator.get_pathvector(); if (generating_path.empty()) { return path_in; @@ -148,15 +148,15 @@ LPEVonKoch::doEffect_path (std::vector<Geom::Path> const & path_in) } //Generate path: - std::vector<Geom::Path> pathi = path_in; - std::vector<Geom::Path> path_out = path_in; + Geom::PathVector pathi = path_in; + Geom::PathVector path_out = path_in; for (unsigned i = 0; i<nbgenerations; i++){ if (drawall.get_value()){ path_out = path_in; complexity = path_in_complexity; }else{ - path_out = std::vector<Geom::Path>(); + path_out = Geom::PathVector(); complexity = 0; } for (unsigned j = 0; j<transforms.size(); j++){ @@ -203,7 +203,7 @@ LPEVonKoch::doEffect_path (std::vector<Geom::Path> const & path_in) hp_vec.push_back(refbox_as_vect); } //Draw the transformed boxes - std::vector<Geom::Path> generating_path = generator.get_pathvector(); + Geom::PathVector generating_path = generator.get_pathvector(); for (unsigned i=0;i<generating_path.size(); i++){ if (generating_path[i].size()==0){ //Ooops! this should not happen. @@ -243,7 +243,7 @@ LPEVonKoch::doBeforeEffect (SPLPEItem const* lpeitem) using namespace Geom; original_bbox(lpeitem); - std::vector<Geom::Path> paths = ref_path.get_pathvector(); + Geom::PathVector paths = ref_path.get_pathvector(); Geom::Point A,B; if (paths.empty()||paths.front().size()==0){ //FIXME: a path is used as ref instead of 2 points to work around path/point param incompatibility bug. @@ -258,7 +258,7 @@ LPEVonKoch::doBeforeEffect (SPLPEItem const* lpeitem) if (paths.size()!=1||paths.front().size()!=1){ Geom::Path tmp_path(A); tmp_path.appendNew<LineSegment>(B); - std::vector<Geom::Path> tmp_pathv; + Geom::PathVector tmp_pathv; tmp_pathv.push_back(tmp_path); ref_path.set_new_value(tmp_pathv,true); } @@ -282,7 +282,7 @@ LPEVonKoch::resetDefaults(SPItem const* item) B[Geom::X] = boundingbox_X.max(); B[Geom::Y] = boundingbox_Y.middle(); - std::vector<Geom::Path> paths,refpaths; + Geom::PathVector paths,refpaths; Geom::Path path = Geom::Path(A); path.appendNew<Geom::LineSegment>(B); @@ -298,7 +298,7 @@ LPEVonKoch::resetDefaults(SPItem const* item) //refA[Geom::Y] = boundingbox_Y.middle(); //refB[Geom::X] = boundingbox_X.max(); //refB[Geom::Y] = boundingbox_Y.middle(); - //std::vector<Geom::Path> paths; + //Geom::PathVector paths; //Geom::Path path = Geom::Path( (Point) refA); //path.appendNew<Geom::LineSegment>( (Point) refB ); //paths.push_back(path * Affine(1./3,0,0,1./3, refA[X]*2./3, refA[Y]*2./3 + boundingbox_Y.extent()/2)); diff --git a/src/live_effects/lpe-vonkoch.h b/src/live_effects/lpe-vonkoch.h index 7dff2be52..bffbebd54 100644 --- a/src/live_effects/lpe-vonkoch.h +++ b/src/live_effects/lpe-vonkoch.h @@ -49,7 +49,7 @@ public: LPEVonKoch(LivePathEffectObject *lpeobject); virtual ~LPEVonKoch(); - virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); + virtual Geom::PathVector doEffect_path (Geom::PathVector const & path_in); virtual void resetDefaults(SPItem const* item); diff --git a/src/live_effects/lpeobject-reference.cpp b/src/live_effects/lpeobject-reference.cpp index 573c8a2fd..d9de6e77f 100644 --- a/src/live_effects/lpeobject-reference.cpp +++ b/src/live_effects/lpeobject-reference.cpp @@ -43,14 +43,7 @@ LPEObjectReference::~LPEObjectReference(void) bool LPEObjectReference::_acceptObject(SPObject * const obj) const { if (IS_LIVEPATHEFFECT(obj)) { - SPObject * const owner = getOwner(); - /* Refuse references to us or to an ancestor. */ - for ( SPObject *iter = owner ; iter ; iter = iter->parent ) { - if ( iter == obj ) { - return false; - } - } - return true; + return URIReference::_acceptObject(obj); } else { return false; } diff --git a/src/live_effects/parameter/filletchamferpointarray.cpp b/src/live_effects/parameter/filletchamferpointarray.cpp index 10ded4d9f..b089213fd 100644 --- a/src/live_effects/parameter/filletchamferpointarray.cpp +++ b/src/live_effects/parameter/filletchamferpointarray.cpp @@ -11,7 +11,6 @@ #include <2geom/piecewise.h> #include <2geom/sbasis-to-bezier.h> #include <2geom/sbasis-geometric.h> -#include <2geom/svg-elliptical-arc.h> #include <2geom/line.h> #include <2geom/path-intersection.h> @@ -205,7 +204,7 @@ void FilletChamferPointArrayParam::recalculate_controlpoints_for_new_pwd2( //delete temp vector std::vector<Point>().swap(tmp); if (last_pathv.size() > counterPaths) { - last_pathv[counterPaths] = last_pathv[counterPaths].reverse(); + last_pathv[counterPaths] = last_pathv[counterPaths].reversed(); } } else { if (last_pathv.size() > counterPaths) { @@ -223,7 +222,7 @@ void FilletChamferPointArrayParam::recalculate_controlpoints_for_new_pwd2( } double xPos = 0; if (_vector[1][X] > 0) { - xPos = nearest_point(curve_it1->initialPoint(), pwd2_in); + xPos = nearest_time(curve_it1->initialPoint(), pwd2_in); } if (nodetype == NODE_CUSP) { result.push_back(Point(xPos, 1)); @@ -234,7 +233,7 @@ void FilletChamferPointArrayParam::recalculate_controlpoints_for_new_pwd2( double xPos = _vector[counter - offset][X]; if (_vector.size() <= (unsigned)(counter - offset)) { if (_vector[1][X] > 0) { - xPos = nearest_point(curve_it1->initialPoint(), pwd2_in); + xPos = nearest_time(curve_it1->initialPoint(), pwd2_in); } else { xPos = 0; } @@ -270,6 +269,9 @@ void FilletChamferPointArrayParam::recalculate_knots( Piecewise<D2<SBasis> > const &pwd2_in) { bool change = false; + if(_vector.size() == 0){ + return; + } PathVector pathv = path_from_piecewise(pwd2_in, 0.001); if (!pathv.empty()) { std::vector<Point> result; @@ -393,8 +395,8 @@ void FilletChamferPointArrayParam::updateCanvasIndicators() Geom::Affine aff = Geom::Affine(); aff *= Geom::Scale(helper_size); aff *= Geom::Rotate(ray1.angle() - deg_to_rad(270)); + aff *= Geom::Translate(last_pwd2[i].valueAt(Xvalue)); pathv *= aff; - pathv += last_pwd2[i].valueAt(Xvalue); hp.push_back(pathv[0]); hp.push_back(pathv[1]); i++; @@ -410,7 +412,7 @@ void FilletChamferPointArrayParam::addCanvasIndicators( double FilletChamferPointArrayParam::rad_to_len(int index, double rad) { double len = 0; - std::vector<Geom::Path> subpaths = path_from_piecewise(last_pwd2, 0.1); + Geom::PathVector subpaths = path_from_piecewise(last_pwd2, 0.1); std::pair<std::size_t, std::size_t> positions = get_positions(index, subpaths); D2<SBasis> A = last_pwd2[last_index(index, subpaths)]; if(positions.second != 0){ @@ -428,7 +430,7 @@ double FilletChamferPointArrayParam::rad_to_len(int index, double rad) Geom::Crossings cs = Geom::crossings(p0, p1); if(cs.size() > 0){ Point cp =p0(cs[0].ta); - double p0pt = nearest_point(cp, B); + double p0pt = nearest_time(cp, B); len = time_to_len(index,p0pt); } else { if(rad < 0){ @@ -443,7 +445,7 @@ double FilletChamferPointArrayParam::len_to_rad(int index, double len) double rad = 0; double tmp_len = _vector[index][X]; _vector[index] = Geom::Point(len,_vector[index][Y]); - std::vector<Geom::Path> subpaths = path_from_piecewise(last_pwd2, 0.1); + Geom::PathVector subpaths = path_from_piecewise(last_pwd2, 0.1); std::pair<std::size_t, std::size_t> positions = get_positions(index, subpaths); Piecewise<D2<SBasis> > u; u.push_cut(0); @@ -475,14 +477,14 @@ double FilletChamferPointArrayParam::len_to_rad(int index, double len) if (cubic2) { ray2.setPoints(endArcPoint, (*cubic2)[1]); } - bool ccwToggle = cross(A->finalPoint() - startArcPoint, endArcPoint - startArcPoint) < 0; + bool ccwToggle = cross(A->finalPoint() - startArcPoint, endArcPoint - startArcPoint) > 0; double distanceArc = Geom::distance(startArcPoint,middle_point(startArcPoint,endArcPoint)); double angleBetween = angle_between(ray1, ray2, ccwToggle); rad = distanceArc/sin(angleBetween/2.0); return rad * -1; } -std::vector<double> FilletChamferPointArrayParam::get_times(int index, std::vector<Geom::Path> subpaths, bool last) +std::vector<double> FilletChamferPointArrayParam::get_times(int index, Geom::PathVector subpaths, bool last) { const double tolerance = 0.001; const double gapHelper = 0.00001; @@ -491,6 +493,13 @@ std::vector<double> FilletChamferPointArrayParam::get_times(int index, std::vect curve_it1 = subpaths[positions.first][positions.second].duplicate(); Coord it1_length = (*curve_it1).length(tolerance); double time_it1, time_it2, time_it1_B, intpart; + if(_vector.size() <= index){ + std::vector<double> out; + out.push_back(0); + out.push_back(1); + out.push_back(0); + return out; + } time_it1 = modf(to_time(index, _vector[index][X]), &intpart); if (_vector[index][Y] == 0) { time_it1 = 0; @@ -530,7 +539,7 @@ std::vector<double> FilletChamferPointArrayParam::get_times(int index, std::vect return out; } -std::pair<std::size_t, std::size_t> FilletChamferPointArrayParam::get_positions(int index, std::vector<Geom::Path> subpaths) +std::pair<std::size_t, std::size_t> FilletChamferPointArrayParam::get_positions(int index, Geom::PathVector subpaths) { int counter = -1; std::size_t first = 0; @@ -572,7 +581,7 @@ std::pair<std::size_t, std::size_t> FilletChamferPointArrayParam::get_positions( return out; } -int FilletChamferPointArrayParam::last_index(int index, std::vector<Geom::Path> subpaths) +int FilletChamferPointArrayParam::last_index(int index, Geom::PathVector subpaths) { int counter = -1; bool inSubpath = false; @@ -698,9 +707,9 @@ void FilletChamferPointArrayParamKnotHolderEntity::knot_set(Point const &p, return; } Piecewise<D2<SBasis> > const &pwd2 = _pparam->get_pwd2(); - double t = nearest_point(p, pwd2[_index]); + double t = nearest_time(p, pwd2[_index]); Geom::Point const s = snap_knot_position(pwd2[_index].valueAt(t), state); - t = nearest_point(s, pwd2[_index]); + t = nearest_time(s, pwd2[_index]); if (t == 1) { t = 0.9999; } @@ -792,7 +801,7 @@ void FilletChamferPointArrayParamKnotHolderEntity::knot_click(guint state) if(xModified < 0 && !_pparam->use_distance){ xModified = _pparam->len_to_rad(_index, _pparam->_vector.at(_index).x()); } - std::vector<Geom::Path> subpaths = path_from_piecewise(_pparam->last_pwd2, 0.1); + Geom::PathVector subpaths = path_from_piecewise(_pparam->last_pwd2, 0.1); std::pair<std::size_t, std::size_t> positions = _pparam->get_positions(_index, subpaths); D2<SBasis> A = _pparam->last_pwd2[_pparam->last_index(_index, subpaths)]; if(positions.second != 0){ diff --git a/src/live_effects/parameter/filletchamferpointarray.h b/src/live_effects/parameter/filletchamferpointarray.h index 9bfd86b41..48cd26d2d 100644 --- a/src/live_effects/parameter/filletchamferpointarray.h +++ b/src/live_effects/parameter/filletchamferpointarray.h @@ -47,9 +47,9 @@ public: virtual double len_to_rad(int index, double len); virtual double len_to_time(int index, double len); virtual double time_to_len(int index, double time); - virtual std::pair<std::size_t, std::size_t> get_positions(int index, std::vector<Geom::Path> subpaths); - virtual int last_index(int index, std::vector<Geom::Path> subpaths); - std::vector<double> get_times(int index, std::vector<Geom::Path> subpaths, bool last); + virtual std::pair<std::size_t, std::size_t> get_positions(int index, Geom::PathVector subpaths); + virtual int last_index(int index, Geom::PathVector subpaths); + std::vector<double> get_times(int index, Geom::PathVector subpaths, bool last); virtual void set_helper_size(int hs); virtual void set_use_distance(bool use_knot_distance); virtual void set_chamfer_steps(int value_chamfer_steps); diff --git a/src/live_effects/parameter/originalpatharray.h b/src/live_effects/parameter/originalpatharray.h index 6c792613f..296c0f7f7 100644 --- a/src/live_effects/parameter/originalpatharray.h +++ b/src/live_effects/parameter/originalpatharray.h @@ -40,7 +40,7 @@ public: } gchar *href; URIReference ref; - std::vector<Geom::Path> _pathvector; + Geom::PathVector _pathvector; bool reversed; sigc::connection linked_changed_connection; diff --git a/src/live_effects/parameter/path-reference.cpp b/src/live_effects/parameter/path-reference.cpp index a76fb1b32..42589b050 100644 --- a/src/live_effects/parameter/path-reference.cpp +++ b/src/live_effects/parameter/path-reference.cpp @@ -22,7 +22,7 @@ bool PathReference::_acceptObject(SPObject * const obj) const return false; } // TODO: check whether the referred path has this LPE applied, if so: deny deny deny! - return true; + return URIReference::_acceptObject(obj); } else { return false; } diff --git a/src/live_effects/parameter/path.cpp b/src/live_effects/parameter/path.cpp index 32e82ff8c..e0369e662 100644 --- a/src/live_effects/parameter/path.cpp +++ b/src/live_effects/parameter/path.cpp @@ -77,7 +77,7 @@ PathParam::~PathParam() g_free(defvalue); } -std::vector<Geom::Path> const & +Geom::PathVector const & PathParam::get_pathvector() const { return _pathvector; @@ -255,7 +255,7 @@ PathParam::param_transform_multiply(Geom::Affine const& postmul, bool /*set*/) } /* - * See comments for set_new_value(std::vector<Geom::Path>). + * See comments for set_new_value(Geom::PathVector). */ void PathParam::set_new_value (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & newpath, bool write_to_svg) @@ -291,7 +291,7 @@ PathParam::set_new_value (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & newpa * The new path data is not written to SVG. This method will emit the signal_path_changed signal. */ void -PathParam::set_new_value (std::vector<Geom::Path> const &newpath, bool write_to_svg) +PathParam::set_new_value (Geom::PathVector const &newpath, bool write_to_svg) { remove_link(); _pathvector = newpath; diff --git a/src/live_effects/parameter/path.h b/src/live_effects/parameter/path.h index 112a1ea13..d2dddbe97 100644 --- a/src/live_effects/parameter/path.h +++ b/src/live_effects/parameter/path.h @@ -31,7 +31,7 @@ public: const gchar * default_value = "M0,0 L1,1"); virtual ~PathParam(); - std::vector<Geom::Path> const & get_pathvector() const; + Geom::PathVector const & get_pathvector() const; Geom::Piecewise<Geom::D2<Geom::SBasis> > const & get_pwd2(); virtual Gtk::Widget * param_newWidget(); @@ -41,7 +41,7 @@ public: virtual void param_set_default(); void param_set_and_write_default(); - void set_new_value (std::vector<Geom::Path> const &newpath, bool write_to_svg); + void set_new_value (Geom::PathVector const &newpath, bool write_to_svg); void set_new_value (Geom::Piecewise<Geom::D2<Geom::SBasis> > const &newpath, bool write_to_svg); virtual void param_editOncanvas(SPItem * item, SPDesktop * dt); @@ -59,7 +59,7 @@ public: void on_paste_button_click(); protected: - std::vector<Geom::Path> _pathvector; // this is primary data storage, since it is closest to SVG. + Geom::PathVector _pathvector; // this is primary data storage, since it is closest to SVG. Geom::Piecewise<Geom::D2<Geom::SBasis> > _pwd2; // secondary, hence the bool must_recalculate_pwd2 bool must_recalculate_pwd2; // set when _pathvector was updated, but _pwd2 not diff --git a/src/live_effects/parameter/powerstrokepointarray.cpp b/src/live_effects/parameter/powerstrokepointarray.cpp index e0c2f4c68..c61e8f9cb 100644 --- a/src/live_effects/parameter/powerstrokepointarray.cpp +++ b/src/live_effects/parameter/powerstrokepointarray.cpp @@ -79,7 +79,7 @@ PowerStrokePointArrayParam::recalculate_controlpoints_for_new_pwd2(Geom::Piecewi Geom::Point pt = _vector[i]; Geom::Point position = last_pwd2.valueAt(pt[Geom::X]) + pt[Geom::Y] * last_pwd2_normal.valueAt(pt[Geom::X]); - double t = nearest_point(position, pwd2_in); + double t = nearest_time(position, pwd2_in); double offset = dot(position - pwd2_in.valueAt(t), normal.valueAt(t)); _vector[i] = Geom::Point(t, offset); } @@ -161,7 +161,7 @@ PowerStrokePointArrayParamKnotHolderEntity::knot_set(Geom::Point const &p, Geom: Piecewise<D2<SBasis> > const & n = _pparam->get_pwd2_normal(); Geom::Point const s = snap_knot_position(p, state); - double t = nearest_point(s, pwd2); + double t = nearest_time(s, pwd2); double offset = dot(s - pwd2.valueAt(t), n.valueAt(t)); _pparam->_vector.at(_index) = Geom::Point(t, offset); sp_lpe_item_update_patheffect(SP_LPE_ITEM(item), false, false); diff --git a/src/live_effects/spiro-converters.cpp b/src/live_effects/spiro-converters.cpp index 3c7bdf99e..f116d5256 100644 --- a/src/live_effects/spiro-converters.cpp +++ b/src/live_effects/spiro-converters.cpp @@ -64,7 +64,11 @@ ConverterSPCurve::curveto(double x1, double y1, double x2, double y2, double x3, }
-
+ConverterPath::ConverterPath(Geom::Path &path)
+ : _path(path)
+{
+ _path.setStitching(true);
+}
void
ConverterPath::moveto(double x, double y, bool is_open)
diff --git a/src/live_effects/spiro-converters.h b/src/live_effects/spiro-converters.h index 83f6ebbc3..90855d2d6 100644 --- a/src/live_effects/spiro-converters.h +++ b/src/live_effects/spiro-converters.h @@ -25,7 +25,7 @@ class ConverterSPCurve : public ConverterBase { public: ConverterSPCurve(SPCurve &curve) : _curve(curve) - {} ; + {} virtual void moveto(double x, double y, bool is_open); virtual void lineto(double x, double y); @@ -45,9 +45,7 @@ private: */ class ConverterPath : public ConverterBase { public: - ConverterPath(Geom::Path &path) - : _path(path) - {} ; + ConverterPath(Geom::Path &path); virtual void moveto(double x, double y, bool is_open); virtual void lineto(double x, double y); diff --git a/src/live_effects/spiro.h b/src/live_effects/spiro.h index 0d85da74b..066b44ca8 100644 --- a/src/live_effects/spiro.h +++ b/src/live_effects/spiro.h @@ -25,11 +25,9 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA #define INKSCAPE_SPIRO_H #include "live_effects/spiro-converters.h" +#include <2geom/forward.h> class SPCurve; -namespace Geom { - class Path; -} namespace Spiro { @@ -53,4 +51,4 @@ double get_knot_th(const spiro_seg *s, int i); } // namespace Spiro -#endif // INKSCAPE_SPIRO_H
\ No newline at end of file +#endif // INKSCAPE_SPIRO_H diff --git a/src/main.cpp b/src/main.cpp index d1ebdc3bb..26c25af02 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1542,7 +1542,7 @@ static int sp_do_export_png(SPDocument *doc) } if (sp_export_area_snap) { - round_rectangle_outwards(area); + area = area.roundOutwards(); } // default dpi diff --git a/src/object-snapper.cpp b/src/object-snapper.cpp index f9c189c58..634d56aa6 100644 --- a/src/object-snapper.cpp +++ b/src/object-snapper.cpp @@ -19,6 +19,7 @@ #include <2geom/rect.h> #include <2geom/line.h> #include <2geom/circle.h> +#include <2geom/path-sink.h> #include "document.h" #include "sp-namedview.h" #include "sp-image.h" @@ -517,14 +518,14 @@ void Inkscape::ObjectSnapper::_snapPaths(IntermSnapResults &isr, for(Geom::PathVector::iterator it_pv = (it_p->path_vector)->begin(); it_pv != (it_p->path_vector)->end(); ++it_pv) { // Find a nearest point for each curve within this path // n curves will return n time values with 0 <= t <= 1 - std::vector<double> anp = (*it_pv).nearestPointPerCurve(p_doc); + std::vector<double> anp = (*it_pv).nearestTimePerCurve(p_doc); //std::cout << "#nearest points = " << anp.size() << " | p = " << p.getPoint() << std::endl; // Now we will examine each of the nearest points, and determine whether it's within snapping range and if we should snap to it std::vector<double>::const_iterator np = anp.begin(); unsigned int index = 0; for (; np != anp.end(); ++np, index++) { - Geom::Curve const *curve = &((*it_pv).at_index(index)); + Geom::Curve const *curve = &(it_pv->at(index)); Geom::Point const sp_doc = curve->pointAt(*np); //dt->snapindicator->set_new_debugging_point(sp_doc*dt->doc2dt()); bool c1 = true; @@ -623,10 +624,13 @@ void Inkscape::ObjectSnapper::_snapPathsConstrained(IntermSnapResults &isr, // PS: Because the paths we're about to snap to are all expressed relative to document coordinate system, we will have // to convert the snapper coordinates from the desktop coordinates to document coordinates - std::vector<Geom::Path> constraint_path; + Geom::PathVector constraint_path; if (c.isCircular()) { Geom::Circle constraint_circle(dt->dt2doc(c.getPoint()), c.getRadius()); - constraint_circle.getPath(constraint_path); + Geom::PathBuilder pb; + pb.feed(constraint_circle); + pb.flush(); + constraint_path = pb.peek(); } else { Geom::Path constraint_line; constraint_line.start(p_min_on_cl); diff --git a/src/persp3d-reference.cpp b/src/persp3d-reference.cpp index 895eac6f2..4526a8d8f 100644 --- a/src/persp3d-reference.cpp +++ b/src/persp3d-reference.cpp @@ -35,7 +35,8 @@ Persp3DReference::~Persp3DReference(void) bool Persp3DReference::_acceptObject(SPObject *obj) const { - return SP_IS_PERSP3D(obj); + return SP_IS_PERSP3D(obj) && URIReference::_acceptObject(obj); +; /* effic: Don't bother making this an inline function: _acceptObject is a virtual function, typically called from a context where the runtime type is not known at compile time. */ } diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h index 66a3e47d4..f4a08b928 100644 --- a/src/preferences-skeleton.h +++ b/src/preferences-skeleton.h @@ -95,7 +95,7 @@ static char const preferences_skeleton[] = " <eventcontext id=\"pencil\" tolerance=\"4.0\" selcue=\"1\" style=\"stroke-width:1px;\" usecurrent=\"0\" average_all_sketches=\"1\"/>\n" " <eventcontext id=\"pen\" mode=\"drag\" selcue=\"1\" style=\"stroke-width:1px;\" usecurrent=\"0\"/>\n" " </group>\n" -" <eventcontext id=\"calligraphic\" style=\"fill:black;fill-opacity:1;fill-rule:nonzero;stroke:none;\"\n" +" <eventcontext id=\"calligraphic\" style=\"fill:black;fill-opacity:1;stroke:none;\"\n" " mass=\"2\" angle=\"30\" width=\"15\" thinning=\"10\" flatness=\"90\" cap_rounding=\"0.0\" usecurrent=\"1\"\n" " tracebackground=\"0\" usepressure=\"1\" usetilt=\"0\" keep_selected=\"1\">\n" " <group id=\"preset\">\n" diff --git a/src/sp-clippath.h b/src/sp-clippath.h index 91dcfd625..c9a8c68df 100644 --- a/src/sp-clippath.h +++ b/src/sp-clippath.h @@ -88,7 +88,7 @@ protected: return false; } SPObject * const owner = this->getOwner(); - if (obj->isAncestorOf(owner)) { + if (!URIReference::_acceptObject(obj)) { //XML Tree being used directly here while it shouldn't be... Inkscape::XML::Node * const owner_repr = owner->getRepr(); //XML Tree being used directly here while it shouldn't be... diff --git a/src/sp-conn-end.cpp b/src/sp-conn-end.cpp index 1e478d1c9..fa5a57529 100644 --- a/src/sp-conn-end.cpp +++ b/src/sp-conn-end.cpp @@ -110,7 +110,7 @@ static bool try_get_intersect_point_with_item(SPPath* conn, SPItem* item, if (!at_start) { // connectors are actually a single path, so consider the first element from a Geom::PathVector - conn_pv[0] = conn_pv[0].reverse(); + conn_pv[0] = conn_pv[0].reversed(); } // We start with the intersection point at the beginning of the path diff --git a/src/sp-ellipse.cpp b/src/sp-ellipse.cpp index 932a3a1b7..0675b7781 100644 --- a/src/sp-ellipse.cpp +++ b/src/sp-ellipse.cpp @@ -18,6 +18,7 @@ #include <glibmm/i18n.h> #include <2geom/angle.h> +#include <2geom/circle.h> #include <2geom/ellipse.h> #include <2geom/path-sink.h> #include <2geom/pathvector.h> diff --git a/src/sp-factory.cpp b/src/sp-factory.cpp index e48646f8d..55e673c4a 100644 --- a/src/sp-factory.cpp +++ b/src/sp-factory.cpp @@ -27,6 +27,7 @@ #include "sp-font.h" #include "sp-font-face.h" #include "sp-glyph.h" +#include "sp-glyph-kerning.h" #include "sp-guide.h" #include "sp-hatch.h" #include "sp-hatch-path.h" @@ -150,6 +151,10 @@ SPObject *SPFactory::createObject(std::string const& id) ret = new SPFontFace; else if (id == "svg:glyph") ret = new SPGlyph; + else if (id == "svg:hkern") + ret = new SPHkern; + else if (id == "svg:vkern") + ret = new SPVkern; else if (id == "sodipodi:guide") ret = new SPGuide; else if (id == "svg:hatch") diff --git a/src/sp-filter-reference.cpp b/src/sp-filter-reference.cpp index 57600ad39..afb014820 100644 --- a/src/sp-filter-reference.cpp +++ b/src/sp-filter-reference.cpp @@ -4,7 +4,7 @@ bool SPFilterReference::_acceptObject(SPObject *obj) const { - return SP_IS_FILTER(obj); + return SP_IS_FILTER(obj) && URIReference::_acceptObject(obj); /* effic: Don't bother making this an inline function: _acceptObject is a virtual function, typically called from a context where the runtime type is not known at compile time. */ } diff --git a/src/sp-gradient-reference.cpp b/src/sp-gradient-reference.cpp index d2b8128fb..216ac73de 100644 --- a/src/sp-gradient-reference.cpp +++ b/src/sp-gradient-reference.cpp @@ -4,7 +4,7 @@ bool SPGradientReference::_acceptObject(SPObject *obj) const { - return SP_IS_GRADIENT(obj); + return SP_IS_GRADIENT(obj) && URIReference::_acceptObject(obj); /* effic: Don't bother making this an inline function: _acceptObject is a virtual function, typically called from a context where the runtime type is not known at compile time. */ } diff --git a/src/sp-gradient-test.h b/src/sp-gradient-test.h index 696072929..578d0c5c0 100644 --- a/src/sp-gradient-test.h +++ b/src/sp-gradient-test.h @@ -102,10 +102,10 @@ public: Geom::Affine const g2d(sp_gradient_get_g2d_matrix(gr, Geom::identity(), unit_rect)); Geom::Affine const gs2d(sp_gradient_get_gs2d_matrix(gr, Geom::identity(), unit_rect)); TS_ASSERT_EQUALS( g2d, Geom::identity() ); - TS_ASSERT( Geom::matrix_equalp(gs2d, gr->gradientTransform * g2d, 1e-12) ); + TS_ASSERT( Geom::are_near(gs2d, gr->gradientTransform * g2d, 1e-12) ); sp_gradient_set_gs2d_matrix(gr, Geom::identity(), unit_rect, gs2d); - TS_ASSERT( Geom::matrix_equalp(gr->gradientTransform, grXform, 1e-12) ); + TS_ASSERT( Geom::are_near(gr->gradientTransform, grXform, 1e-12) ); } gr->gradientTransform = grXform; @@ -116,10 +116,10 @@ public: Geom::Affine const g2d(sp_gradient_get_g2d_matrix(gr, funny, unit_rect)); Geom::Affine const gs2d(sp_gradient_get_gs2d_matrix(gr, funny, unit_rect)); TS_ASSERT_EQUALS( g2d, funny ); - TS_ASSERT( Geom::matrix_equalp(gs2d, gr->gradientTransform * g2d, 1e-12) ); + TS_ASSERT( Geom::are_near(gs2d, gr->gradientTransform * g2d, 1e-12) ); sp_gradient_set_gs2d_matrix(gr, funny, unit_rect, gs2d); - TS_ASSERT( Geom::matrix_equalp(gr->gradientTransform, grXform, 1e-12) ); + TS_ASSERT( Geom::are_near(gr->gradientTransform, grXform, 1e-12) ); } gr->gradientTransform = grXform; @@ -130,16 +130,16 @@ public: TS_ASSERT_EQUALS( g2d, Geom::Affine(3, 0, 0, 4, 5, 6) * funny ); - TS_ASSERT( Geom::matrix_equalp(gs2d, gr->gradientTransform * g2d, 1e-12) ); + TS_ASSERT( Geom::are_near(gs2d, gr->gradientTransform * g2d, 1e-12) ); sp_gradient_set_gs2d_matrix(gr, funny, larger_rect, gs2d); - TS_ASSERT( Geom::matrix_equalp(gr->gradientTransform, grXform, 1e-12) ); + TS_ASSERT( Geom::are_near(gr->gradientTransform, grXform, 1e-12) ); SP_OBJECT(gr)->setKeyValue( SP_ATTR_GRADIENTUNITS, "userSpaceOnUse"); Geom::Affine const user_g2d(sp_gradient_get_g2d_matrix(gr, funny, larger_rect)); Geom::Affine const user_gs2d(sp_gradient_get_gs2d_matrix(gr, funny, larger_rect)); TS_ASSERT_EQUALS( user_g2d, funny ); - TS_ASSERT( Geom::matrix_equalp(user_gs2d, gr->gradientTransform * user_g2d, 1e-12) ); + TS_ASSERT( Geom::are_near(user_gs2d, gr->gradientTransform * user_g2d, 1e-12) ); } g_object_unref(gr); } diff --git a/src/sp-hatch.h b/src/sp-hatch.h index 5004a611f..546f06a1e 100644 --- a/src/sp-hatch.h +++ b/src/sp-hatch.h @@ -168,7 +168,7 @@ public: protected: virtual bool _acceptObject(SPObject *obj) const { - return dynamic_cast<SPHatch *>(obj) != NULL; + return dynamic_cast<SPHatch *>(obj) != NULL && URIReference::_acceptObject(obj); } }; diff --git a/src/sp-marker.h b/src/sp-marker.h index 56cbaf94f..bae13243b 100644 --- a/src/sp-marker.h +++ b/src/sp-marker.h @@ -92,7 +92,7 @@ class SPMarkerReference : public Inkscape::URIReference { } protected: virtual bool _acceptObject(SPObject *obj) const { - return SP_IS_MARKER(obj); + return SP_IS_MARKER(obj) && URIReference::_acceptObject(obj); } }; diff --git a/src/sp-mask.h b/src/sp-mask.h index 3559483bb..74bd4d66e 100644 --- a/src/sp-mask.h +++ b/src/sp-mask.h @@ -81,7 +81,7 @@ protected: return false; } SPObject * const owner = this->getOwner(); - if (obj->isAncestorOf(owner)) { + if (!URIReference::_acceptObject(obj)) { //XML Tree being used directly here while it shouldn't be... Inkscape::XML::Node * const owner_repr = owner->getRepr(); //XML Tree being used directly here while it shouldn't be... diff --git a/src/sp-object.cpp b/src/sp-object.cpp index 0bb8c240f..db66eb3e6 100644 --- a/src/sp-object.cpp +++ b/src/sp-object.cpp @@ -721,6 +721,9 @@ void SPObject::invoke_build(SPDocument *document, Inkscape::XML::Node *repr, uns } this->cloned = cloned; + /* Invoke derived methods, if any */ + this->build(document, repr); + if ( !cloned ) { this->document->bindObjectToRepr(this->repr, this); @@ -754,8 +757,6 @@ void SPObject::invoke_build(SPDocument *document, Inkscape::XML::Node *repr, uns g_assert(this->getId() == NULL); } - /* Invoke derived methods, if any */ - this->build(document, repr); /* Signalling (should be connected AFTER processing derived methods */ sp_repr_add_listener(repr, &object_event_vector, this); diff --git a/src/sp-offset.cpp b/src/sp-offset.cpp index e8aa09952..7c3d0bd03 100644 --- a/src/sp-offset.cpp +++ b/src/sp-offset.cpp @@ -890,7 +890,7 @@ sp_offset_distance_to_original (SPOffset * offset, Geom::Point px) if (ab > 0 && ab < len * len) { // we're in the zone of influence of the segment - double ndist = (cross(pxsx,nx)) / len; + double ndist = (cross(nx, pxsx)) / len; if (arSet == false || fabs (ndist) < fabs (arDist)) { diff --git a/src/sp-paint-server.cpp b/src/sp-paint-server.cpp index ae40ad1aa..c3416c6c8 100644 --- a/src/sp-paint-server.cpp +++ b/src/sp-paint-server.cpp @@ -27,7 +27,7 @@ SPPaintServer *SPPaintServerReference::getObject() const bool SPPaintServerReference::_acceptObject(SPObject *obj) const { - return SP_IS_PAINT_SERVER(obj); + return SP_IS_PAINT_SERVER(obj) && URIReference::_acceptObject(obj); } SPPaintServer::SPPaintServer() : SPObject() { diff --git a/src/sp-path.cpp b/src/sp-path.cpp index 42883588b..cabed0467 100644 --- a/src/sp-path.cpp +++ b/src/sp-path.cpp @@ -28,8 +28,7 @@ #include "display/curve.h" #include <2geom/pathvector.h> -#include <2geom/bezier-curve.h> -#include <2geom/hvlinesegment.h> +#include <2geom/curves.h> #include "helper/geom-curves.h" #include "svg/svg.h" diff --git a/src/sp-pattern.h b/src/sp-pattern.h index 145bb934e..a5e7be1d4 100644 --- a/src/sp-pattern.h +++ b/src/sp-pattern.h @@ -129,9 +129,8 @@ public: } protected: - virtual bool _acceptObject(SPObject *obj) const - { - return SP_IS_PATTERN(obj); + virtual bool _acceptObject(SPObject *obj) const { + return SP_IS_PATTERN (obj)&& URIReference::_acceptObject(obj); } }; diff --git a/src/sp-polygon.cpp b/src/sp-polygon.cpp index af71280d5..ced485f12 100644 --- a/src/sp-polygon.cpp +++ b/src/sp-polygon.cpp @@ -16,8 +16,7 @@ #include "display/curve.h" #include <glibmm/i18n.h> #include <2geom/pathvector.h> -#include <2geom/bezier-curve.h> -#include <2geom/hvlinesegment.h> +#include <2geom/curves.h> #include "helper/geom-curves.h" #include "svg/stringstream.h" #include "xml/repr.h" diff --git a/src/sp-tag-use-reference.cpp b/src/sp-tag-use-reference.cpp index 220cd16d1..9fcb31fd1 100644 --- a/src/sp-tag-use-reference.cpp +++ b/src/sp-tag-use-reference.cpp @@ -24,14 +24,7 @@ bool SPTagUseReference::_acceptObject(SPObject * const obj) const { if (SP_IS_ITEM(obj)) { - SPObject * const owner = getOwner(); - // Refuse references to us or to an ancestor. - for ( SPObject *iter = owner ; iter ; iter = iter->parent ) { - if ( iter == obj ) { - return false; - } - } - return true; + return URIReference::_acceptObject(obj); } else { return false; } diff --git a/src/sp-tref-reference.cpp b/src/sp-tref-reference.cpp index e82f575e0..7c6ff00e7 100644 --- a/src/sp-tref-reference.cpp +++ b/src/sp-tref-reference.cpp @@ -21,7 +21,7 @@ bool SPTRefReference::_acceptObject(SPObject * const obj) const { SPObject *owner = getOwner(); if (SP_IS_TREF(owner)) - return sp_tref_reference_allowed(SP_TREF(getOwner()), obj); + return URIReference::_acceptObject(obj); else return false; } diff --git a/src/sp-use-reference.cpp b/src/sp-use-reference.cpp index 642cfede8..f0b2985d2 100644 --- a/src/sp-use-reference.cpp +++ b/src/sp-use-reference.cpp @@ -25,18 +25,7 @@ bool SPUseReference::_acceptObject(SPObject * const obj) const { - if (SP_IS_ITEM(obj)) { - SPObject * const owner = getOwner(); - // Refuse references to us or to an ancestor. - for ( SPObject *iter = owner ; iter ; iter = iter->parent ) { - if ( iter == obj ) { - return false; - } - } - return true; - } else { - return false; - } + return URIReference::_acceptObject(obj); } diff --git a/src/splivarot.cpp b/src/splivarot.cpp index bec300936..726df6e20 100644 --- a/src/splivarot.cpp +++ b/src/splivarot.cpp @@ -47,6 +47,7 @@ #include "xml/repr.h" #include "xml/repr-sorting.h" #include <2geom/pathvector.h> +#include <2geom/svg-path-writer.h> #include "helper/geom.h" #include "livarot/Path.h" @@ -305,8 +306,9 @@ sp_pathvector_boolop(Geom::PathVector const &pathva, Geom::PathVector const &pat delete originaux[0]; delete originaux[1]; - std::vector<Geom::Path> outres = Geom::parse_svg_path(res->svg_dump_path()); - + gchar *result_str = res->svg_dump_path(); + Geom::PathVector outres = Geom::parse_svg_path(result_str); + g_free(result_str); delete res; return outres; @@ -315,7 +317,7 @@ sp_pathvector_boolop(Geom::PathVector const &pathva, Geom::PathVector const &pat /* Convert from a livarot path to a 2geom PathVector */ Geom::PathVector pathliv_to_pathvector(Path *pathliv){ - std::vector<Geom::Path> outres = Geom::parse_svg_path(pathliv->svg_dump_path()); + Geom::PathVector outres = Geom::parse_svg_path(pathliv->svg_dump_path()); return outres; } @@ -445,8 +447,9 @@ sp_selected_path_boolop(Inkscape::Selection *selection, SPDesktop *desktop, bool // reverse if needed // note that the selection list keeps its order if ( reverseOrderForOp ) { - Path* swap=originaux[0];originaux[0]=originaux[1];originaux[1]=swap; - FillRule swai=origWind[0]; origWind[0]=origWind[1]; origWind[1]=swai; + using std::swap; + swap(originaux[0], originaux[1]); + swap(origWind[0], origWind[1]); } // and work @@ -2260,17 +2263,17 @@ Ancetre(Inkscape::XML::Node *a, Inkscape::XML::Node *who) } // derived from Path_for_item -// there must be some other way to load dest directly from epathv, without going through pathv... Path * Path_for_pathvector(Geom::PathVector const &epathv) { - Geom::PathVector *pathv = new Geom::PathVector; - std::copy(epathv.begin(), epathv.end(), std::back_inserter(*pathv)); - + /*std::cout << "converting to Livarot path" << std::endl; + + Geom::SVGPathWriter wr; + wr.feed(epathv); + std::cout << wr.str() << std::endl;*/ + Path *dest = new Path; - dest->LoadPathVector(*pathv); - delete pathv; - + dest->LoadPathVector(epathv); return dest; } @@ -2281,14 +2284,26 @@ Path_for_item(SPItem *item, bool doTransformation, bool transformFull) if (curve == NULL) return NULL; - + Geom::PathVector *pathv = pathvector_for_curve(item, curve, doTransformation, transformFull, Geom::identity(), Geom::identity()); curve->unref(); - + + /*std::cout << "converting to Livarot path" << std::endl; + + Geom::SVGPathWriter wr; + if (pathv) { + wr.feed(*pathv); + } + std::cout << wr.str() << std::endl;*/ + Path *dest = new Path; dest->LoadPathVector(*pathv); delete pathv; - + + /*gchar *str = dest->svg_dump_path(); + std::cout << "After conversion:\n" << str << std::endl; + g_free(str);*/ + return dest; } @@ -2335,7 +2350,7 @@ pathvector_for_curve(SPItem *item, SPCurve *curve, bool doTransformation, bool t } else { *dest *= extraPreAffine * extraPostAffine; } - + return dest; } diff --git a/src/style-enums.h b/src/style-enums.h index 29b8e2130..dfc99282c 100644 --- a/src/style-enums.h +++ b/src/style-enums.h @@ -115,6 +115,7 @@ enum SPCSSFontVariantNumeric { SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO = 128 }; +// Quite complicated... (see spec) enum SPCSSFontVariantAlternates { SP_CSS_FONT_VARIANT_ALTERNATES_NORMAL, SP_CSS_FONT_VARIANT_ALTERNATES_HISTORICAL_FORMS, @@ -126,17 +127,18 @@ enum SPCSSFontVariantAlternates { SP_CSS_FONT_VARIANT_ALTERNATES_ANNOTATION }; +// Can select more than one (see spec) enum SPCSSFontVariantEastAsian { - SP_CSS_FONT_VARIANT_EAST_ASIAN_NORMAL, - SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78, - SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83, - SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90, - SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04, - SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED, - SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL, - SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH, - SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH, - SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY + SP_CSS_FONT_VARIANT_EAST_ASIAN_NORMAL = 0, + SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78 = 1, + SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83 = 2, + SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90 = 4, + SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04 = 8, + SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED = 16, + SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL = 32, + SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH = 64, + SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH = 128, + SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY = 256 }; enum SPCSSTextAlign { diff --git a/src/style-internal.h b/src/style-internal.h index bd2a92c8c..1ddab30f1 100644 --- a/src/style-internal.h +++ b/src/style-internal.h @@ -501,12 +501,12 @@ public: public: SPStyleEnum const *enums; - unsigned value : 8; - unsigned computed: 8; + unsigned value : 16; // 9 bits required for 'font-variant-east-asian' + unsigned computed: 16; private: - unsigned value_default : 8; - unsigned computed_default: 8; // for font-weight + unsigned value_default : 16; + unsigned computed_default: 16; // for font-weight }; @@ -546,7 +546,7 @@ public: {} SPILigatures( Glib::ustring const &name, SPStyleEnum const *enums) : - SPIEnum( name, enums, SP_CSS_FONT_VARIANT_NORMAL ) + SPIEnum( name, enums, SP_CSS_FONT_VARIANT_LIGATURES_NORMAL ) {} virtual ~SPILigatures() diff --git a/src/style.cpp b/src/style.cpp index 1668646b6..d8402e08a 100644 --- a/src/style.cpp +++ b/src/style.cpp @@ -1190,6 +1190,92 @@ SPStyle::_mergeObjectStylesheet( SPObject const *const object ) { } } +std::string +SPStyle::getFontFeatureString() { + + std::string feature_string; + + if ( !(font_variant_ligatures.value & SP_CSS_FONT_VARIANT_LIGATURES_COMMON) ) + feature_string += "liga 0, clig 0, "; + if ( font_variant_ligatures.value & SP_CSS_FONT_VARIANT_LIGATURES_DISCRETIONARY ) + feature_string += "dlig, "; + if ( font_variant_ligatures.value & SP_CSS_FONT_VARIANT_LIGATURES_HISTORICAL ) + feature_string += "hlig, "; + if ( !(font_variant_ligatures.value & SP_CSS_FONT_VARIANT_LIGATURES_CONTEXTUAL) ) + feature_string += "calt 0, "; + + if ( font_variant_position.value & SP_CSS_FONT_VARIANT_POSITION_SUB ) + feature_string += "subs, "; + if ( font_variant_position.value & SP_CSS_FONT_VARIANT_POSITION_SUPER ) + feature_string += "sups, "; + + if ( font_variant_caps.value & SP_CSS_FONT_VARIANT_CAPS_SMALL ) + feature_string += "smcp, "; + if ( font_variant_caps.value & SP_CSS_FONT_VARIANT_CAPS_ALL_SMALL ) + feature_string += "smcp, c2sc, "; + if ( font_variant_caps.value & SP_CSS_FONT_VARIANT_CAPS_PETITE ) + feature_string += "pcap, "; + if ( font_variant_caps.value & SP_CSS_FONT_VARIANT_CAPS_ALL_PETITE ) + feature_string += "pcap, c2pc, "; + if ( font_variant_caps.value & SP_CSS_FONT_VARIANT_CAPS_UNICASE ) + feature_string += "unic, "; + if ( font_variant_caps.value & SP_CSS_FONT_VARIANT_CAPS_TITLING ) + feature_string += "titl, "; + + if ( font_variant_numeric.value & SP_CSS_FONT_VARIANT_NUMERIC_LINING_NUMS ) + feature_string += "lnum, "; + if ( font_variant_numeric.value & SP_CSS_FONT_VARIANT_NUMERIC_OLDSTYLE_NUMS ) + feature_string += "onum, "; + if ( font_variant_numeric.value & SP_CSS_FONT_VARIANT_NUMERIC_PROPORTIONAL_NUMS ) + feature_string += "pnum, "; + if ( font_variant_numeric.value & SP_CSS_FONT_VARIANT_NUMERIC_TABULAR_NUMS ) + feature_string += "tnum, "; + if ( font_variant_numeric.value & SP_CSS_FONT_VARIANT_NUMERIC_DIAGONAL_FRACTIONS ) + feature_string += "frac, "; + if ( font_variant_numeric.value & SP_CSS_FONT_VARIANT_NUMERIC_STACKED_FRACTIONS ) + feature_string += "afrc, "; + if ( font_variant_numeric.value & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL ) + feature_string += "ordn, "; + if ( font_variant_numeric.value & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO ) + feature_string += "zero, "; + + if( font_variant_east_asian.value & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS78 ) + feature_string += "jp78, "; + if( font_variant_east_asian.value & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS83 ) + feature_string += "jp83, "; + if( font_variant_east_asian.value & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS90 ) + feature_string += "jp90, "; + if( font_variant_east_asian.value & SP_CSS_FONT_VARIANT_EAST_ASIAN_JIS04 ) + feature_string += "jp04, "; + if( font_variant_east_asian.value & SP_CSS_FONT_VARIANT_EAST_ASIAN_SIMPLIFIED ) + feature_string += "smpl, "; + if( font_variant_east_asian.value & SP_CSS_FONT_VARIANT_EAST_ASIAN_TRADITIONAL ) + feature_string += "trad, "; + if( font_variant_east_asian.value & SP_CSS_FONT_VARIANT_EAST_ASIAN_FULL_WIDTH ) + feature_string += "fwid, "; + if( font_variant_east_asian.value & SP_CSS_FONT_VARIANT_EAST_ASIAN_PROPORTIONAL_WIDTH ) + feature_string += "pwid, "; + if( font_variant_east_asian.value & SP_CSS_FONT_VARIANT_EAST_ASIAN_RUBY ) + feature_string += "ruby, "; + + if ( strcmp( font_feature_settings.value, "normal") ) { + // We do no sanity checking... + feature_string += font_feature_settings.value; + feature_string += ", "; + } + + if (feature_string.empty()) { + feature_string = "normal"; + } else { + // Remove last ", " + feature_string.erase( feature_string.size() - 1 ); + feature_string.erase( feature_string.size() - 1 ); + } + + return feature_string; +} + + // Internal /** * Release callback. diff --git a/src/style.h b/src/style.h index 8e22b3121..02432d3e7 100644 --- a/src/style.h +++ b/src/style.h @@ -303,6 +303,12 @@ public: SPPaintServer *getStrokePaintServer() { return (stroke.value.href) ? stroke.value.href->getObject() : NULL; } SPPaintServer const *getStrokePaintServer() const { return (stroke.value.href) ? stroke.value.href->getObject() : NULL; } char const *getStrokeURI() const { return (stroke.value.href) ? stroke.value.href->getURI()->toString() : NULL; } + + /** + * Return a font feature string useful for Pango. + */ + std::string getFontFeatureString(); + }; SPStyle *sp_style_ref(SPStyle *style); // SPStyle::ref(); diff --git a/src/svg/svg-path-geom-test.h b/src/svg/svg-path-geom-test.h index 3558b4e55..c3972133d 100644 --- a/src/svg/svg-path-geom-test.h +++ b/src/svg/svg-path-geom-test.h @@ -453,38 +453,6 @@ private: return false; } } - else if(Geom::HLineSegment const *la = dynamic_cast<Geom::HLineSegment const*>(ca)) - { - Geom::HLineSegment const *lb = dynamic_cast<Geom::HLineSegment const*>(cb); - if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - } - else if(Geom::VLineSegment const *la = dynamic_cast<Geom::VLineSegment const*>(ca)) - { - Geom::VLineSegment const *lb = dynamic_cast<Geom::VLineSegment const*>(cb); - if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - } else if(Geom::CubicBezier const *la = dynamic_cast<Geom::CubicBezier const*>(ca)) { Geom::CubicBezier const *lb = dynamic_cast<Geom::CubicBezier const*>(cb); @@ -522,91 +490,10 @@ private: } else // not same type { - if(Geom::LineSegment const *la = dynamic_cast<Geom::LineSegment const*>(ca)) - { - if (Geom::HLineSegment const *lb = dynamic_cast<Geom::HLineSegment const*>(cb)) { - if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - char temp[200]; - sprintf(temp, "A LineSegment and an HLineSegment have been considered equal. Subpath: %u, segment: %u", static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_TRACE(temp); - } else if (Geom::VLineSegment const *lb = dynamic_cast<Geom::VLineSegment const*>(cb)) { - if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - char temp[200]; - sprintf(temp, "A LineSegment and a VLineSegment have been considered equal. Subpath: %u, segment: %u", static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_TRACE(temp); - } else { - char temp[200]; - sprintf(temp, "Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(), typeid(*cb).name(), static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - } - } - else if(Geom::LineSegment const *lb = dynamic_cast<Geom::LineSegment const*>(cb)) - { - if (Geom::HLineSegment const *la = dynamic_cast<Geom::HLineSegment const*>(ca)) { - if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - char temp[200]; - sprintf(temp, "An HLineSegment and a LineSegment have been considered equal. Subpath: %u, segment: %u", static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_TRACE(temp); - } else if (Geom::VLineSegment const *la = dynamic_cast<Geom::VLineSegment const*>(ca)) { - if (!Geom::are_near((*la).initialPoint(),(*lb).initialPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different start of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).initialPoint()[Geom::X], (*la).initialPoint()[Geom::Y], (*lb).initialPoint()[Geom::X], (*lb).initialPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - if (!Geom::are_near((*la).finalPoint(),(*lb).finalPoint(), eps)) { - char temp[200]; - sprintf(temp, "Different end of segment: (%g,%g) != (%g,%g), subpath: %u, segment: %u", (*la).finalPoint()[Geom::X], (*la).finalPoint()[Geom::Y], (*lb).finalPoint()[Geom::X], (*lb).finalPoint()[Geom::Y], static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - char temp[200]; - sprintf(temp, "A VLineSegment and a LineSegment have been considered equal. Subpath: %u, segment: %u", static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_TRACE(temp); - } else { - char temp[200]; - sprintf(temp, "Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(), typeid(*cb).name(), static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - return false; - } - } else { - char temp[200]; - sprintf(temp, "Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(), typeid(*cb).name(), static_cast<unsigned int>(i), static_cast<unsigned int>(j)); - TS_FAIL(temp); - } + char temp[200]; + sprintf(temp, "Different curve types: %s != %s, subpath: %u, segment: %u", typeid(*ca).name(), typeid(*cb).name(), static_cast<unsigned int>(i), static_cast<unsigned int>(j)); + TS_FAIL(temp); + return false; } } } diff --git a/src/svg/svg-path.cpp b/src/svg/svg-path.cpp index 9ba3c0ebd..ba9e11452 100644 --- a/src/svg/svg-path.cpp +++ b/src/svg/svg-path.cpp @@ -43,16 +43,15 @@ Geom::PathVector sp_svg_read_pathv(char const * str) if (!str) return pathv; // return empty pathvector when str == NULL - - typedef std::back_insert_iterator<Geom::PathVector> Inserter; - Inserter iter(pathv); - Geom::PathIteratorSink<Inserter> generator(iter); + Geom::PathBuilder builder(pathv); + Geom::SVGPathParser parser(builder); + parser.setZSnapThreshold(Geom::EPSILON); try { - Geom::parse_svg_path(str, generator); + parser.parse(str); } catch (Geom::SVGPathParseError &e) { - generator.flush(); + builder.flush(); // This warning is extremely annoying when testing //g_warning("Malformed SVG path, truncated path up to where error was found.\n Input path=\"%s\"\n Parsed path=\"%s\"", str, sp_svg_write_path(pathv)); } @@ -61,10 +60,17 @@ Geom::PathVector sp_svg_read_pathv(char const * str) } static void sp_svg_write_curve(Inkscape::SVG::PathString & str, Geom::Curve const * c) { + // TODO: this code needs to removed and replaced by appropriate path sink if(Geom::LineSegment const *line_segment = dynamic_cast<Geom::LineSegment const *>(c)) { // don't serialize stitch segments if (!dynamic_cast<Geom::Path::StitchSegment const *>(c)) { - str.lineTo( (*line_segment)[1][0], (*line_segment)[1][1] ); + if (line_segment->initialPoint()[Geom::X] == line_segment->finalPoint()[Geom::X]) { + str.verticalLineTo( line_segment->finalPoint()[Geom::Y] ); + } else if (line_segment->initialPoint()[Geom::Y] == line_segment->finalPoint()[Geom::Y]) { + str.horizontalLineTo( line_segment->finalPoint()[Geom::X] ); + } else { + str.lineTo( (*line_segment)[1][0], (*line_segment)[1][1] ); + } } } else if(Geom::QuadraticBezier const *quadratic_bezier = dynamic_cast<Geom::QuadraticBezier const *>(c)) { @@ -76,17 +82,11 @@ static void sp_svg_write_curve(Inkscape::SVG::PathString & str, Geom::Curve cons (*cubic_bezier)[2][0], (*cubic_bezier)[2][1], (*cubic_bezier)[3][0], (*cubic_bezier)[3][1] ); } - else if(Geom::SVGEllipticalArc const *svg_elliptical_arc = dynamic_cast<Geom::SVGEllipticalArc const *>(c)) { - str.arcTo( svg_elliptical_arc->ray(Geom::X), svg_elliptical_arc->ray(Geom::Y), - Geom::rad_to_deg(svg_elliptical_arc->rotationAngle()), - svg_elliptical_arc->largeArc(), svg_elliptical_arc->sweep(), - svg_elliptical_arc->finalPoint() ); - } - else if(Geom::HLineSegment const *hline_segment = dynamic_cast<Geom::HLineSegment const *>(c)) { - str.horizontalLineTo( hline_segment->finalPoint()[0] ); - } - else if(Geom::VLineSegment const *vline_segment = dynamic_cast<Geom::VLineSegment const *>(c)) { - str.verticalLineTo( vline_segment->finalPoint()[1] ); + else if(Geom::EllipticalArc const *elliptical_arc = dynamic_cast<Geom::EllipticalArc const *>(c)) { + str.arcTo( elliptical_arc->ray(Geom::X), elliptical_arc->ray(Geom::Y), + Geom::rad_to_deg(elliptical_arc->rotationAngle()), + elliptical_arc->largeArc(), elliptical_arc->sweep(), + elliptical_arc->finalPoint() ); } else { //this case handles sbasis as well as all other curve types Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c->toSBasis(), 0.1); diff --git a/src/trace/CMakeLists.txt b/src/trace/CMakeLists.txt index 958907df6..bf7cfa276 100644 --- a/src/trace/CMakeLists.txt +++ b/src/trace/CMakeLists.txt @@ -28,6 +28,7 @@ set(trace_SRC potrace/auxiliary.h potrace/bitmap.h + potrace/bitops.h potrace/curve.h potrace/decompose.h potrace/greymap.h diff --git a/src/trace/potrace/inkscape-potrace.cpp b/src/trace/potrace/inkscape-potrace.cpp index e9e708f52..8f3bb7bdf 100644 --- a/src/trace/potrace/inkscape-potrace.cpp +++ b/src/trace/potrace/inkscape-potrace.cpp @@ -497,7 +497,7 @@ std::vector<TracingEngineResult> PotraceTracingEngine::traceBrightnessMulti(GdkP if ( !d.empty() ) { //### get style info int grayVal = (int)(256.0 * brightnessThreshold); - ustring style = ustring::compose("fill-opacity:1.0;fill:%1%2%3", twohex(grayVal), twohex(grayVal), twohex(grayVal) ); + ustring style = ustring::compose("fill-opacity:1.0;fill:#%1%2%3", twohex(grayVal), twohex(grayVal), twohex(grayVal) ); //g_message("### GOT '%s' \n", style.c_str()); TracingEngineResult result(style, d, nodeCount); diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index fbf25d039..58af7d935 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -226,6 +226,7 @@ set(ui_SRC dialog/livepatheffect-add.h dialog/livepatheffect-editor.h dialog/lpe-fillet-chamfer-properties.h + dialog/lpe-powerstroke-properties.h dialog/memory.h dialog/messages.h dialog/new-from-template.h @@ -242,6 +243,7 @@ set(ui_SRC dialog/svg-fonts-dialog.h dialog/swatches.h dialog/symbols.h + dialog/tags.h dialog/template-load-tab.h dialog/template-widget.h dialog/text-edit.h @@ -293,9 +295,11 @@ set(ui_SRC tools/tweak-tool.h tools/zoom-tool.h + widget/addtoicon.h widget/anchor-selector.h widget/attr-widget.h widget/button.h + widget/clipmaskicon.h widget/color-entry.h widget/color-icc-selector.h widget/color-notebook.h @@ -314,10 +318,13 @@ set(ui_SRC widget/frame.h widget/gimpspinscale.h widget/gimpcolorwheel.h + widget/highlight-picker.h + widget/insertordericon.h widget/imageicon.h widget/imagetoggler.h widget/labelled.h widget/layer-selector.h + widget/layertypeicon.h widget/licensor.h widget/notebook-page.h widget/object-composite-settings.h diff --git a/src/ui/dialog/export.cpp b/src/ui/dialog/export.cpp index 384aec415..1edfdfe80 100644 --- a/src/ui/dialog/export.cpp +++ b/src/ui/dialog/export.cpp @@ -945,17 +945,19 @@ Gtk::Dialog * Export::create_progress_dialog (Glib::ustring progress_text) { Glib::ustring Export::filename_add_extension (Glib::ustring filename, Glib::ustring extension) { Glib::ustring::size_type dot; + Glib::ustring::size_type dot_ext; dot = filename.find_last_of("."); - if ( !dot ) + dot_ext = filename.lowercase().rfind("." + extension.lowercase()); + if ( dot == std::string::npos ) { return filename = filename + "." + extension; } else { - if (dot==filename.find_last_of(Glib::ustring::compose(".", extension))) + if (dot == dot_ext) { - return filename; + return filename = filename; } else { diff --git a/src/ui/dialog/grid-arrange-tab.cpp b/src/ui/dialog/grid-arrange-tab.cpp index c44f66a4d..ccd23a572 100644 --- a/src/ui/dialog/grid-arrange-tab.cpp +++ b/src/ui/dialog/grid-arrange-tab.cpp @@ -311,7 +311,7 @@ g_print("\n row = %f col = %f selection x= %f selection y = %f", total_row_h GSList *current_row = NULL; col_cnt = 0; - for(;it!=sorted.end()&&col<NoOfCols;it++) { + for(;it!=sorted.end()&&col_cnt<NoOfCols;it++) { current_row = g_slist_append (current_row, *it); col_cnt++; } diff --git a/src/ui/dialog/objects.cpp b/src/ui/dialog/objects.cpp index 662cce6e1..835ecf35b 100644 --- a/src/ui/dialog/objects.cpp +++ b/src/ui/dialog/objects.cpp @@ -1614,6 +1614,12 @@ ObjectsPanel::ObjectsPanel() : _pending(0), _toggleEvent(0), _defer_target(), + _visibleHeader(_("V")), + _lockHeader(_("L")), + _typeHeader(_("T")), + _clipmaskHeader(_("CM")), + _highlightHeader(_("HL")), + _nameHeader(_("Label")), _composite_vbox(false, 0), _opacity_vbox(false, 0), _opacity_label(_("Opacity:")), @@ -1641,7 +1647,7 @@ ObjectsPanel::ObjectsPanel() : //Set up the tree _tree.set_model( _store ); - _tree.set_headers_visible(false); + _tree.set_headers_visible(true); _tree.set_reorderable(true); _tree.enable_model_drag_dest (Gdk::ACTION_MOVE); @@ -1654,6 +1660,10 @@ ObjectsPanel::ObjectsPanel() : Gtk::TreeViewColumn* col = _tree.get_column(visibleColNum); if ( col ) { col->add_attribute( eyeRenderer->property_active(), _model->_colVisible ); + // In order to get tooltips on header, we must create our own label. + _visibleHeader.set_tooltip_text(_("Toggle visibility of Layer, Group, or Object.")); + _visibleHeader.show(); + col->set_widget( _visibleHeader ); } //Locked @@ -1664,6 +1674,9 @@ ObjectsPanel::ObjectsPanel() : col = _tree.get_column(lockedColNum); if ( col ) { col->add_attribute( renderer->property_active(), _model->_colLocked ); + _lockHeader.set_tooltip_text(_("Toggle lock of Layer, Group, or Object.")); + _lockHeader.show(); + col->set_widget( _lockHeader ); } //Type @@ -1673,6 +1686,9 @@ ObjectsPanel::ObjectsPanel() : col = _tree.get_column(typeColNum); if ( col ) { col->add_attribute( typeRenderer->property_active(), _model->_colType ); + _typeHeader.set_tooltip_text(_("Type: Layer, Group, or Object. Clicking on Layer or Group icon, toggles between the two types.")); + _typeHeader.show(); + col->set_widget( _typeHeader ); } //Insert order (LiamW: unused) @@ -1689,6 +1705,9 @@ ObjectsPanel::ObjectsPanel() : col = _tree.get_column(clipColNum); if ( col ) { col->add_attribute( clipRenderer->property_active(), _model->_colClipMask ); + _clipmaskHeader.set_tooltip_text(_("Is object clipped and/or masked?")); + _clipmaskHeader.show(); + col->set_widget( _clipmaskHeader ); } //Highlight @@ -1697,13 +1716,21 @@ ObjectsPanel::ObjectsPanel() : col = _tree.get_column(highlightColNum); if ( col ) { col->add_attribute( highlightRenderer->property_active(), _model->_colHighlight ); + _highlightHeader.set_tooltip_text(_("Highlight color of outline in Node tool. Click to set. If alpha is zero, use inherited color.")); + _highlightHeader.show(); + col->set_widget( _highlightHeader ); } //Label _text_renderer = Gtk::manage(new Gtk::CellRendererText()); int nameColNum = _tree.append_column("Name", *_text_renderer) - 1; _name_column = _tree.get_column(nameColNum); - _name_column->add_attribute(_text_renderer->property_text(), _model->_colLabel); + if( _name_column ) { + _name_column->add_attribute(_text_renderer->property_text(), _model->_colLabel); + _nameHeader.set_tooltip_text(_("Layer/Group/Object label (inkscape:label). Double-click to set. Default value is object 'id'.")); + _nameHeader.show(); + _name_column->set_widget( _nameHeader ); + } //Set the expander and search columns _tree.set_expander_column( *_tree.get_column(nameColNum) ); diff --git a/src/ui/dialog/objects.h b/src/ui/dialog/objects.h index 7a826d02e..9b9a6025a 100644 --- a/src/ui/dialog/objects.h +++ b/src/ui/dialog/objects.h @@ -147,7 +147,14 @@ private: Gtk::Menu _popupMenu; Inkscape::UI::Widget::SpinButton _spinBtn; Gtk::VBox _page; - + + Gtk::Label _visibleHeader; + Gtk::Label _lockHeader; + Gtk::Label _typeHeader; + Gtk::Label _clipmaskHeader; + Gtk::Label _highlightHeader; + Gtk::Label _nameHeader; + /* Composite Settings */ Gtk::VBox _composite_vbox; Gtk::VBox _opacity_vbox; diff --git a/src/ui/dialog/text-edit.cpp b/src/ui/dialog/text-edit.cpp index 1a696c820..7575cc854 100644 --- a/src/ui/dialog/text-edit.cpp +++ b/src/ui/dialog/text-edit.cpp @@ -391,7 +391,9 @@ void TextEdit::onReadSelection ( gboolean dostyle, gboolean /*docontent*/ ) // Update font variant widget //int result_variants = sp_desktop_query_style (SP_ACTIVE_DESKTOP, &query, QUERY_STYLE_PROPERTY_FONTVARIANTS); - vari_vbox.update( &query ); + int result_features = + sp_desktop_query_style (SP_ACTIVE_DESKTOP, &query, QUERY_STYLE_PROPERTY_FONTFEATURESETTINGS); + vari_vbox.update( &query, result_features == QUERY_STYLE_MULTIPLE_DIFFERENT, fontspec ); } blocked = false; diff --git a/src/ui/dialog/text-edit.h b/src/ui/dialog/text-edit.h index 41f89b3e7..cfe612268 100644 --- a/src/ui/dialog/text-edit.h +++ b/src/ui/dialog/text-edit.h @@ -36,7 +36,7 @@ class SPItem; struct SPFontSelector; -class FontVariants; +//class FontVariants; class font_instance; class SPCSSAttr; diff --git a/src/ui/tool/curve-drag-point.cpp b/src/ui/tool/curve-drag-point.cpp index d1756fa2c..e460b0fb7 100644 --- a/src/ui/tool/curve-drag-point.cpp +++ b/src/ui/tool/curve-drag-point.cpp @@ -187,6 +187,10 @@ Glib::ustring CurveDragPoint::_getTip(unsigned state) const if (_pm.empty()) return ""; if (!first || !first.next()) return ""; bool linear = first->front()->isDegenerate() && first.next()->back()->isDegenerate(); + if(state_held_shift(state) && _pm._isBSpline()){ + return C_("Path segment tip", + "<b>Shift</b>: drag to open or move BSpline handles"); + } if (state_held_shift(state)) { return C_("Path segment tip", "<b>Shift</b>: click to toggle segment selection"); @@ -195,6 +199,11 @@ Glib::ustring CurveDragPoint::_getTip(unsigned state) const return C_("Path segment tip", "<b>Ctrl+Alt</b>: click to insert a node"); } + if(_pm._isBSpline()){ + return C_("Path segment tip", + "<b>BSpline segment</b>: drag to shape the segment, doubleclick to insert node, " + "click to select (more: Shift, Ctrl+Alt)"); + } if (linear) { return C_("Path segment tip", "<b>Linear segment</b>: drag to convert to a Bezier segment, " diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp index aa5365265..ca6f5abb1 100644 --- a/src/ui/tool/node.cpp +++ b/src/ui/tool/node.cpp @@ -356,8 +356,8 @@ void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event) Geom::Line perp_line(parent_pos, parent_pos + Geom::rot90(origin - parent_pos)); Geom::Point snap_pos = parent_pos + Geom::constrain_angle( Geom::Point(0,0), new_pos - parent_pos, snaps, Geom::Point(1,0)); - Geom::Point orig_pos = original_line.pointAt(original_line.nearestPoint(new_pos)); - Geom::Point perp_pos = perp_line.pointAt(perp_line.nearestPoint(new_pos)); + Geom::Point orig_pos = original_line.pointAt(original_line.nearestTime(new_pos)); + Geom::Point perp_pos = perp_line.pointAt(perp_line.nearestTime(new_pos)); Geom::Point result = snap_pos; ctrl_constraint = Inkscape::Snapper::SnapConstraint(parent_pos, parent_pos - snap_pos); @@ -1464,13 +1464,14 @@ Glib::ustring Node::_getTip(unsigned state) const // No modifiers: assemble tip from node type char const *nodetype = node_type_to_localized_string(_type); + double power = _pm()._bsplineHandlePosition(h,h2); if (_selection.transformHandlesEnabled() && selected()) { if (_selection.size() == 1 && !isBSpline) { return format_tip(C_("Path node tip", "<b>%s</b>: drag to shape the path (more: Shift, Ctrl, Alt)"), nodetype); }else if(_selection.size() == 1){ return format_tip(C_("Path node tip", - "<b>BSpline node</b>: drag to shape the path (more: Shift, Ctrl, Alt). %g power"),_pm()._bsplineHandlePosition(h,h2)); + "<b>BSpline node</b>: drag to shape the path (more: Shift, Ctrl, Alt). %g power"), power); } return format_tip(C_("Path node tip", "<b>%s</b>: drag to shape the path, click to toggle scale/rotation handles (more: Shift, Ctrl, Alt)"), nodetype); @@ -1480,7 +1481,7 @@ Glib::ustring Node::_getTip(unsigned state) const "<b>%s</b>: drag to shape the path, click to select only this node (more: Shift, Ctrl, Alt)"), nodetype); }else{ return format_tip(C_("Path node tip", - "<b>BSpline node</b>: drag to shape the path, click to select only this node (more: Shift, Ctrl, Alt). %g power"),_pm()._bsplineHandlePosition(h,h2)); + "<b>BSpline node</b>: drag to shape the path, click to select only this node (more: Shift, Ctrl, Alt). %g power"), power); } } @@ -1567,6 +1568,13 @@ NodeList::iterator NodeList::before(double t, double *fracpart) return ret; } +NodeList::iterator NodeList::before(Geom::PathTime const &pvp) +{ + iterator ret = begin(); + std::advance(ret, pvp.curve_index); + return ret; +} + NodeList::iterator NodeList::insert(iterator pos, Node *x) { ListNode *ins = pos._node; diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h index 101af4817..025c460e2 100644 --- a/src/ui/tool/node.h +++ b/src/ui/tool/node.h @@ -406,9 +406,13 @@ public: void setClosed(bool c) { _closed = c; } iterator before(double t, double *fracpart = NULL); + iterator before(Geom::PathTime const &pvp); const_iterator before(double t, double *fracpart = NULL) const { return const_iterator(before(t, fracpart)._node); } + const_iterator before(Geom::PathTime const &pvp) const { + return const_iterator(before(pvp)._node); + } // list operations diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp index a772c07c2..848b10373 100644 --- a/src/ui/tool/path-manipulator.cpp +++ b/src/ui/tool/path-manipulator.cpp @@ -1018,7 +1018,7 @@ NodeList::iterator PathManipulator::subdivideSegment(NodeList::iterator first, d Geom::CubicBezier temp(first->position(), first->front()->position(), second->back()->position(), second->position()); std::pair<Geom::CubicBezier, Geom::CubicBezier> div = temp.subdivide(t); - std::vector<Geom::Point> seg1 = div.first.points(), seg2 = div.second.points(); + std::vector<Geom::Point> seg1 = div.first.controlPoints(), seg2 = div.second.controlPoints(); // set new handle positions Node *n = new Node(_multi_path_manipulator._path_data.node_data, seg2[0]); @@ -1163,22 +1163,22 @@ void PathManipulator::_createControlPointsFromGeometry() pathv *= (_edit_transform * _i2d_transform); // in this loop, we know that there are no zero-segment subpaths - for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) { + for (Geom::PathVector::iterator pit = pathv.begin(); pit != pathv.end(); ++pit) { // prepare new subpath SubpathPtr subpath(new NodeList(_subpaths)); _subpaths.push_back(subpath); Node *previous_node = new Node(_multi_path_manipulator._path_data.node_data, pit->initialPoint()); subpath->push_back(previous_node); - Geom::Curve const &cseg = pit->back_closed(); - bool fuse_ends = pit->closed() - && Geom::are_near(cseg.initialPoint(), cseg.finalPoint()); - for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) { + + bool closed = pit->closed(); + + for (Geom::Path::iterator cit = pit->begin(); cit != pit->end(); ++cit) { Geom::Point pos = cit->finalPoint(); Node *current_node; // if the closing segment is degenerate and the path is closed, we need to move // the handle of the first node instead of creating a new one - if (fuse_ends && cit == --(pit->end_open())) { + if (closed && cit == --(pit->end())) { current_node = subpath->begin().get_pointer(); } else { /* regardless of segment type, create a new node at the end @@ -1293,7 +1293,7 @@ double PathManipulator::_bsplineHandlePosition(Handle *h, Handle *h2) line_inside_nodes->moveto(n->position()); line_inside_nodes->lineto(next_node->position()); if(!are_near(h->position(), n->position())){ - pos = Geom::nearest_point(Geom::Point(h->position()[X] - HANDLE_CUBIC_GAP, h->position()[Y] - HANDLE_CUBIC_GAP), *line_inside_nodes->first_segment()); + pos = Geom::nearest_time(Geom::Point(h->position()[X] - HANDLE_CUBIC_GAP, h->position()[Y] - HANDLE_CUBIC_GAP), *line_inside_nodes->first_segment()); } } if (pos == NO_POWER && !h2){ @@ -1447,7 +1447,7 @@ void PathManipulator::_updateOutline() Geom::PathVector arrows; for (Geom::PathVector::iterator i = pv.begin(); i != pv.end(); ++i) { Geom::Path &path = *i; - for (Geom::Path::const_iterator j = path.begin(); j != path.end_default(); ++j) { + for (Geom::Path::iterator j = path.begin(); j != path.end_default(); ++j) { Geom::Point at = j->pointAt(0.5); Geom::Point ut = j->unitTangentAt(0.5); // rotate the point @@ -1667,21 +1667,22 @@ void PathManipulator::_commit(Glib::ustring const &annotation, gchar const *key) * point of the path. */ Geom::Coord PathManipulator::_updateDragPoint(Geom::Point const &evp) { - Geom::Coord dist = 1e23; + Geom::Coord dist = HUGE_VAL; Geom::Affine to_desktop = _edit_transform * _i2d_transform; Geom::PathVector pv = _spcurve->get_pathvector(); - boost::optional<Geom::PathVectorPosition> pvp - = Geom::nearestPoint(pv, _desktop->w2d(evp) * to_desktop.inverse()); + + boost::optional<Geom::PathVectorTime> pvp = + pv.nearestTime(_desktop->w2d(evp) * to_desktop.inverse()); if (!pvp) return dist; - Geom::Point nearest_point = _desktop->d2w(pv.at(pvp->path_nr).pointAt(pvp->t) * to_desktop); - - double fracpart; + Geom::Point nearest_pt = _desktop->d2w(pv.pointAt(*pvp) * to_desktop); + + double fracpart = pvp->t; std::list<SubpathPtr>::iterator spi = _subpaths.begin(); - for (unsigned i = 0; i < pvp->path_nr; ++i, ++spi) {} - NodeList::iterator first = (*spi)->before(pvp->t, &fracpart); + for (unsigned i = 0; i < pvp->path_index; ++i, ++spi) {} + NodeList::iterator first = (*spi)->before(pvp->asPathTime()); - dist = Geom::distance(evp, nearest_point); + dist = Geom::distance(evp, nearest_pt); double stroke_tolerance = _getStrokeTolerance(); if (first && first.next() && @@ -1689,7 +1690,7 @@ Geom::Coord PathManipulator::_updateDragPoint(Geom::Point const &evp) dist < stroke_tolerance) { _dragpoint->setVisible(true); - _dragpoint->setPosition(_desktop->w2d(nearest_point)); + _dragpoint->setPosition(_desktop->w2d(nearest_pt)); _dragpoint->setSize(2 * stroke_tolerance); _dragpoint->setTimeValue(fracpart); _dragpoint->setIterator(first); diff --git a/src/ui/tools/calligraphic-tool.cpp b/src/ui/tools/calligraphic-tool.cpp index 15e6527a3..28195eb75 100644 --- a/src/ui/tools/calligraphic-tool.cpp +++ b/src/ui/tools/calligraphic-tool.cpp @@ -140,8 +140,7 @@ void CalligraphicTool::setup() { { /* TODO: have a look at DropperTool::setup where the same is done.. generalize? */ - Geom::PathVector path; - Geom::Circle(0, 0, 1).getPath(path); + Geom::PathVector path = Geom::Path(Geom::Circle(0,0,1)); SPCurve *c = new SPCurve(path); diff --git a/src/ui/tools/dropper-tool.cpp b/src/ui/tools/dropper-tool.cpp index bda9d8e8a..c838c27d5 100644 --- a/src/ui/tools/dropper-tool.cpp +++ b/src/ui/tools/dropper-tool.cpp @@ -84,8 +84,7 @@ void DropperTool::setup() { ToolBase::setup(); /* TODO: have a look at CalligraphicTool::setup where the same is done.. generalize? */ - Geom::PathVector path; - Geom::Circle(0, 0, 1).getPath(path); + Geom::PathVector path = Geom::Path(Geom::Circle(0,0,1)); SPCurve *c = new SPCurve(path); diff --git a/src/ui/tools/eraser-tool.cpp b/src/ui/tools/eraser-tool.cpp index 10f8c8694..e416fd7ef 100644 --- a/src/ui/tools/eraser-tool.cpp +++ b/src/ui/tools/eraser-tool.cpp @@ -789,6 +789,8 @@ add_cap(SPCurve *curve, } void EraserTool::accumulate() { + // construct a crude outline of the eraser's path. + // this desperately needs to be rewritten to use the path outliner... if ( !this->cal1->is_empty() && !this->cal2->is_empty() ) { this->accumulated->reset(); /* Is this required ?? */ SPCurve *rev_cal2 = this->cal2->create_reverse(); @@ -798,10 +800,10 @@ void EraserTool::accumulate() { g_assert( ! this->cal1->first_path()->closed() ); g_assert( ! rev_cal2->first_path()->closed() ); - Geom::CubicBezier const * dc_cal1_firstseg = dynamic_cast<Geom::CubicBezier const *>( this->cal1->first_segment() ); - Geom::CubicBezier const * rev_cal2_firstseg = dynamic_cast<Geom::CubicBezier const *>( rev_cal2->first_segment() ); - Geom::CubicBezier const * dc_cal1_lastseg = dynamic_cast<Geom::CubicBezier const *>( this->cal1->last_segment() ); - Geom::CubicBezier const * rev_cal2_lastseg = dynamic_cast<Geom::CubicBezier const *>( rev_cal2->last_segment() ); + Geom::BezierCurve const * dc_cal1_firstseg = dynamic_cast<Geom::BezierCurve const *>( this->cal1->first_segment() ); + Geom::BezierCurve const * rev_cal2_firstseg = dynamic_cast<Geom::BezierCurve const *>( rev_cal2->first_segment() ); + Geom::BezierCurve const * dc_cal1_lastseg = dynamic_cast<Geom::BezierCurve const *>( this->cal1->last_segment() ); + Geom::BezierCurve const * rev_cal2_lastseg = dynamic_cast<Geom::BezierCurve const *>( rev_cal2->last_segment() ); g_assert( dc_cal1_firstseg ); g_assert( rev_cal2_firstseg ); @@ -810,11 +812,21 @@ void EraserTool::accumulate() { this->accumulated->append(this->cal1, FALSE); - add_cap(this->accumulated, (*dc_cal1_lastseg)[2], (*dc_cal1_lastseg)[3], (*rev_cal2_firstseg)[0], (*rev_cal2_firstseg)[1], this->cap_rounding); + add_cap(this->accumulated, + dc_cal1_lastseg->finalPoint() - dc_cal1_lastseg->unitTangentAt(1), + dc_cal1_lastseg->finalPoint(), + rev_cal2_firstseg->initialPoint(), + rev_cal2_firstseg->initialPoint() + rev_cal2_firstseg->unitTangentAt(0), + this->cap_rounding); this->accumulated->append(rev_cal2, TRUE); - add_cap(this->accumulated, (*rev_cal2_lastseg)[2], (*rev_cal2_lastseg)[3], (*dc_cal1_firstseg)[0], (*dc_cal1_firstseg)[1], this->cap_rounding); + add_cap(this->accumulated, + rev_cal2_lastseg->finalPoint() - rev_cal2_lastseg->unitTangentAt(1), + rev_cal2_lastseg->finalPoint(), + dc_cal1_firstseg->initialPoint(), + dc_cal1_firstseg->initialPoint() + dc_cal1_firstseg->unitTangentAt(0), + this->cap_rounding); this->accumulated->closepath(); diff --git a/src/ui/tools/gradient-tool.cpp b/src/ui/tools/gradient-tool.cpp index 526671515..603458983 100644 --- a/src/ui/tools/gradient-tool.cpp +++ b/src/ui/tools/gradient-tool.cpp @@ -207,7 +207,7 @@ sp_gradient_context_is_over_line (GradientTool *rc, SPItem *item, Geom::Point ev SPCtrlLine* line = SP_CTRLLINE(item); Geom::LineSegment ls(line->s, line->e); - Geom::Point nearest = ls.pointAt(ls.nearestPoint(rc->mousepoint_doc)); + Geom::Point nearest = ls.pointAt(ls.nearestTime(rc->mousepoint_doc)); double dist_screen = Geom::L2 (rc->mousepoint_doc - nearest) * desktop->current_zoom(); double tolerance = (double) SP_EVENT_CONTEXT(rc)->tolerance; diff --git a/src/ui/tools/mesh-tool.cpp b/src/ui/tools/mesh-tool.cpp index 0e68af601..813d6ae5b 100644 --- a/src/ui/tools/mesh-tool.cpp +++ b/src/ui/tools/mesh-tool.cpp @@ -275,7 +275,7 @@ sp_mesh_context_is_over_line (MeshTool *rc, SPItem *item, Geom::Point event_p) SPCtrlCurve *curve = SP_CTRLCURVE(item); Geom::BezierCurveN<3> b( curve->p0, curve->p1, curve->p2, curve->p3 ); - Geom::Coord coord = b.nearestPoint( rc->mousepoint_doc ); // Coord == double + Geom::Coord coord = b.nearestTime( rc->mousepoint_doc ); // Coord == double Geom::Point nearest = b( coord ); double dist_screen = Geom::L2 (rc->mousepoint_doc - nearest) * desktop->current_zoom(); diff --git a/src/ui/tools/node-tool.cpp b/src/ui/tools/node-tool.cpp index 761492f41..6a6ca0b26 100644 --- a/src/ui/tools/node-tool.cpp +++ b/src/ui/tools/node-tool.cpp @@ -665,9 +665,27 @@ void NodeTool::update_tip(GdkEvent *event) { unsigned total = this->_selected_nodes->allPoints().size(); if (sz != 0) { - char *nodestring = g_strdup_printf( - ngettext("<b>%u of %u</b> node selected.", "<b>%u of %u</b> nodes selected.", total), - sz, total); + char *nodestring; + if (sz == 2) { + // if there are only two nodes selected, display the angle + // of a line going through them relative to the X axis. + Inkscape::UI::ControlPointSelection::Set &selection_nodes = this->_selected_nodes->allPoints(); + std::vector<Geom::Point> positions; + for (Inkscape::UI::ControlPointSelection::Set::iterator i = selection_nodes.begin(); i != selection_nodes.end(); ++i) { + if ((*i)->selected()) { + Inkscape::UI::Node *n = dynamic_cast<Inkscape::UI::Node *>(*i); + positions.push_back(n->position()); + } + } + g_assert(positions.size() == 2); + const double angle = Geom::rad_to_deg(Geom::Line(positions[0], positions[1]).angle()); + nodestring = g_strdup_printf("<b>%u of %u</b> nodes selected, angle: %.2f°.", sz, total, angle); + } + else { + nodestring = g_strdup_printf( + ngettext("<b>%u of %u</b> node selected.", "<b>%u of %u</b> nodes selected.", total), + sz, total); + } if (this->_last_over) { // TRANSLATORS: The %s below is where the "%u of %u nodes selected" sentence gets put diff --git a/src/ui/tools/pen-tool.cpp b/src/ui/tools/pen-tool.cpp index be6156fa2..827dbf5c3 100644 --- a/src/ui/tools/pen-tool.cpp +++ b/src/ui/tools/pen-tool.cpp @@ -57,8 +57,7 @@ #include <typeinfo> #include <2geom/pathvector.h> #include <2geom/affine.h> -#include <2geom/bezier-curve.h> -#include <2geom/hvlinesegment.h> +#include <2geom/curves.h> #include "helper/geom-nodetype.h" #include "helper/geom-curves.h" @@ -71,7 +70,7 @@ #define INKSCAPE_LPE_BSPLINE_C #include "live_effects/lpe-bspline.h" -#include <2geom/nearest-point.h> +#include <2geom/nearest-time.h> #include "live_effects/effect.h" @@ -1352,8 +1351,9 @@ void PenTool::_bsplineSpiroColor() void PenTool::_bsplineSpiro(bool shift) { - if(!this->spiro && !this->bspline) + if(!this->spiro && !this->bspline){ return; + } shift?this->_bsplineSpiroOff():this->_bsplineSpiroOn(); this->_bsplineSpiroBuild(); @@ -1414,13 +1414,19 @@ void PenTool::_bsplineSpiroStartAnchor(bool shift) }else{ this->spiro = false; } - if(!this->spiro && !this->bspline) + if(!this->spiro && !this->bspline){ + SPCurve *tmp_curve = this->sa->curve->copy(); + if (this->sa->start) { + tmp_curve = tmp_curve ->create_reverse(); + } + this->overwrite_curve = tmp_curve ; return; - - if(shift) + } + if(shift){ this->_bsplineSpiroStartAnchorOff(); - else + } else { this->_bsplineSpiroStartAnchorOn(); + } } void PenTool::_bsplineSpiroStartAnchorOn() @@ -1483,9 +1489,9 @@ void PenTool::_bsplineSpiroStartAnchorOff() } void PenTool::_bsplineSpiroMotion(bool shift){ - if(!this->spiro && !this->bspline) + if(!this->spiro && !this->bspline){ return; - + } using Geom::X; using Geom::Y; if(this->red_curve->is_empty()) return; @@ -1515,7 +1521,7 @@ void PenTool::_bsplineSpiroMotion(bool shift){ Geom::D2< Geom::SBasis > SBasisweight_power; weight_power->moveto(tmp_curve ->last_segment()->finalPoint()); weight_power->lineto(tmp_curve ->last_segment()->initialPoint()); - float WP = Geom::nearest_point((*cubic)[2],*weight_power->first_segment()); + float WP = Geom::nearest_time((*cubic)[2],*weight_power->first_segment()); weight_power->reset(); weight_power->moveto(this->red_curve->last_segment()->initialPoint()); weight_power->lineto(this->red_curve->last_segment()->finalPoint()); @@ -1737,7 +1743,7 @@ void PenTool::_bsplineSpiroBuild() //from LPE BSPLINE: void PenTool::_bsplineDoEffect(SPCurve * curve) { - const double NO_POWER = 0.0; + //const double NO_POWER = 0.0; const double DEFAULT_START_POWER = 0.3334; const double DEFAULT_END_POWER = 0.6667; if (curve->get_segment_count() < 1) { @@ -1791,12 +1797,12 @@ void PenTool::_bsplineDoEffect(SPCurve * curve) if(are_near((*cubic)[1],(*cubic)[0]) && !are_near((*cubic)[2],(*cubic)[3])) { point_at1 = sbasis_in.valueAt(DEFAULT_START_POWER); } else { - point_at1 = sbasis_in.valueAt(Geom::nearest_point((*cubic)[1], *in->first_segment())); + point_at1 = sbasis_in.valueAt(Geom::nearest_time((*cubic)[1], *in->first_segment())); } if(are_near((*cubic)[2],(*cubic)[3]) && !are_near((*cubic)[1],(*cubic)[0])) { point_at2 = sbasis_in.valueAt(DEFAULT_END_POWER); } else { - point_at2 = sbasis_in.valueAt(Geom::nearest_point((*cubic)[2], *in->first_segment())); + point_at2 = sbasis_in.valueAt(Geom::nearest_time((*cubic)[2], *in->first_segment())); } } else { point_at1 = in->first_segment()->initialPoint(); @@ -1814,7 +1820,7 @@ void PenTool::_bsplineDoEffect(SPCurve * curve) if(are_near((*cubic)[1],(*cubic)[0]) && !are_near((*cubic)[2],(*cubic)[3])) { next_point_at1 = sbasis_in.valueAt(DEFAULT_START_POWER); } else { - next_point_at1 = sbasis_out.valueAt(Geom::nearest_point((*cubic)[1], *out->first_segment())); + next_point_at1 = sbasis_out.valueAt(Geom::nearest_time((*cubic)[1], *out->first_segment())); } } else { next_point_at1 = out->first_segment()->initialPoint(); @@ -1831,7 +1837,7 @@ void PenTool::_bsplineDoEffect(SPCurve * curve) cubic = dynamic_cast<Geom::CubicBezier const *>(&*path_it->begin()); if (cubic) { line_helper->moveto(sbasis_start.valueAt( - Geom::nearest_point((*cubic)[1], *start->first_segment()))); + Geom::nearest_time((*cubic)[1], *start->first_segment()))); } else { line_helper->moveto(start->first_segment()->initialPoint()); } @@ -1845,7 +1851,7 @@ void PenTool::_bsplineDoEffect(SPCurve * curve) cubic = dynamic_cast<Geom::CubicBezier const *>(&*curve_it1); if (cubic) { line_helper->lineto(sbasis_end.valueAt( - Geom::nearest_point((*cubic)[2], *end->first_segment()))); + Geom::nearest_time((*cubic)[2], *end->first_segment()))); } else { line_helper->lineto(end->first_segment()->finalPoint()); } diff --git a/src/ui/tools/spray-tool.cpp b/src/ui/tools/spray-tool.cpp index 26d74733a..e2be5ca4b 100644 --- a/src/ui/tools/spray-tool.cpp +++ b/src/ui/tools/spray-tool.cpp @@ -133,14 +133,13 @@ SprayTool::SprayTool() : ToolBase(cursor_spray_xpm, 4, 4, false) , pressure(TC_DEFAULT_PRESSURE) , dragging(false) - , usepressure(0) - , usetilt(0) + , usepressure(false) + , usetilt(false) , usetext(false) , width(0.2) , ratio(0) , tilt(0) , rotation_variation(0) - , force(0.2) , population(0) , scale_variation(1) , scale(1) @@ -165,14 +164,6 @@ SprayTool::~SprayTool() { } } -static bool is_transform_modes(gint mode) -{ - return (mode == SPRAY_MODE_COPY || - mode == SPRAY_MODE_CLONE || - mode == SPRAY_MODE_SINGLE_PATH || - mode == SPRAY_OPTION); -} - void SprayTool::update_cursor(bool /*with_shift*/) { guint num = 0; gchar *sel_message = NULL; @@ -207,8 +198,7 @@ void SprayTool::setup() { { /* TODO: have a look at sp_dyna_draw_context_setup where the same is done.. generalize? at least make it an arcto! */ - Geom::PathVector path; - Geom::Circle(0, 0, 1).getPath(path); + Geom::PathVector path = Geom::Path(Geom::Circle(0,0,1)); SPCurve *c = new SPCurve(path); @@ -229,7 +219,6 @@ void SprayTool::setup() { sp_event_context_read(this, "scale_variation"); sp_event_context_read(this, "mode"); sp_event_context_read(this, "population"); - sp_event_context_read(this, "force"); sp_event_context_read(this, "mean"); sp_event_context_read(this, "standard_deviation"); sp_event_context_read(this, "usepressure"); @@ -271,9 +260,7 @@ void SprayTool::set(const Inkscape::Preferences::Entry& val) { this->tilt = CLAMP(val.getDouble(0.1), 0, 1000.0); } else if (path == "ratio") { this->ratio = CLAMP(val.getDouble(), 0.0, 0.9); - } else if (path == "force") { - this->force = CLAMP(val.getDouble(1.0), 0, 1.0); - } + } } static void sp_spray_extinput(SprayTool *tc, GdkEvent *event) @@ -290,16 +277,6 @@ static double get_dilate_radius(SprayTool *tc) return 250 * tc->width/SP_EVENT_CONTEXT(tc)->desktop->current_zoom(); } -static double get_path_force(SprayTool *tc) -{ - double force = 8 * (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE) - /sqrt(SP_EVENT_CONTEXT(tc)->desktop->current_zoom()); - if (force > 3) { - force += 4 * (force - 3); - } - return force * tc->force; -} - static double get_path_mean(SprayTool *tc) { return tc->mean; @@ -310,10 +287,11 @@ static double get_path_standard_deviation(SprayTool *tc) return tc->standard_deviation; } -static double get_move_force(SprayTool *tc) +static double get_population(SprayTool *tc) { - double force = (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE); - return force * tc->force; + double pressure = (tc->usepressure? tc->pressure / TC_DEFAULT_PRESSURE : 1); + //g_warning("Pressure, population: %f, %f", pressure, pressure * tc->population); + return pressure * tc->population; } static double get_move_mean(SprayTool *tc) @@ -361,7 +339,6 @@ static bool sp_spray_recursive(SPDesktop *desktop, Geom::Point /*vector*/, gint mode, double radius, - double /*force*/, double population, double &scale, double scale_variation, @@ -525,8 +502,8 @@ static bool sp_spray_dilate(SprayTool *tc, Geom::Point /*event_p*/, Geom::Point bool did = false; double radius = get_dilate_radius(tc); - double path_force = get_path_force(tc); - if (radius == 0 || path_force == 0) { + double population = get_population(tc); + if (radius == 0 || population == 0) { return false; } double path_mean = get_path_mean(tc); @@ -537,12 +514,11 @@ static bool sp_spray_dilate(SprayTool *tc, Geom::Point /*event_p*/, Geom::Point if (radius == 0 || path_standard_deviation == 0) { return false; } - double move_force = get_move_force(tc); double move_mean = get_move_mean(tc); double move_standard_deviation = get_move_standard_deviation(tc); { - std::vector<SPItem*> const items(selection->itemList()); + std::vector<SPItem*> const items(selection->itemList()); for(std::vector<SPItem*>::const_iterator i=items.begin();i!=items.end();i++){ SPItem *item = *i; @@ -554,14 +530,8 @@ static bool sp_spray_dilate(SprayTool *tc, Geom::Point /*event_p*/, Geom::Point SPItem *item = *i; g_assert(item != NULL); - if (is_transform_modes(tc->mode)) { - if (sp_spray_recursive(desktop, selection, item, p, vector, tc->mode, radius, move_force, tc->population, tc->scale, tc->scale_variation, reverse, move_mean, move_standard_deviation, tc->ratio, tc->tilt, tc->rotation_variation, tc->distrib)) { - did = true; - } - } else { - if (sp_spray_recursive(desktop, selection, item, p, vector, tc->mode, radius, path_force, tc->population, tc->scale, tc->scale_variation, reverse, path_mean, path_standard_deviation, tc->ratio, tc->tilt, tc->rotation_variation, tc->distrib)) { - did = true; - } + if (sp_spray_recursive(desktop, selection, item, p, vector, tc->mode, radius, population, tc->scale, tc->scale_variation, reverse, move_mean, move_standard_deviation, tc->ratio, tc->tilt, tc->rotation_variation, tc->distrib)) { + did = true; } } diff --git a/src/ui/tools/spray-tool.h b/src/ui/tools/spray-tool.h index 1a8f98006..8df730201 100644 --- a/src/ui/tools/spray-tool.h +++ b/src/ui/tools/spray-tool.h @@ -52,8 +52,8 @@ enum { class SprayTool : public ToolBase { public: - SprayTool(); - virtual ~SprayTool(); + SprayTool(); + virtual ~SprayTool(); //ToolBase event_context; //Inkscape::UI::Dialog::Dialog *dialog_option;//Attribut de type SprayOptionClass, localisé dans scr/ui/dialog @@ -70,7 +70,6 @@ public: double ratio; double tilt; double rotation_variation; - double force; double population; double scale_variation; double scale; @@ -90,16 +89,16 @@ public: sigc::connection style_set_connection; - static const std::string prefsPath; + static const std::string prefsPath; - virtual void setup(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); + virtual void setup(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); - virtual const std::string& getPrefsPath(); + virtual const std::string& getPrefsPath(); - void update_cursor(bool /*with_shift*/); + void update_cursor(bool /*with_shift*/); }; } diff --git a/src/ui/tools/tweak-tool.cpp b/src/ui/tools/tweak-tool.cpp index 76b52f9be..94f7aa135 100644 --- a/src/ui/tools/tweak-tool.cpp +++ b/src/ui/tools/tweak-tool.cpp @@ -259,8 +259,7 @@ void TweakTool::setup() { { /* TODO: have a look at sp_dyna_draw_context_setup where the same is done.. generalize? at least make it an arcto! */ - Geom::PathVector path; - Geom::Circle(0, 0, 1).getPath(path); + Geom::PathVector path = Geom::Path(Geom::Circle(0,0,1)); SPCurve *c = new SPCurve(path); diff --git a/src/ui/widget/entity-entry.cpp b/src/ui/widget/entity-entry.cpp index 69173fa25..a8de2f384 100644 --- a/src/ui/widget/entity-entry.cpp +++ b/src/ui/widget/entity-entry.cpp @@ -188,8 +188,7 @@ EntityMultiLineEntry::on_changed() Gtk::TextView *tv = static_cast<Gtk::TextView*>(s->get_child()); Glib::ustring text = tv->get_buffer()->get_text(); if (rdf_set_work_entity (doc, _entity, text.c_str())) { - DocumentUndo::done(doc, SP_VERB_NONE, - /* TODO: annotate */ "entity-entry.cpp:146"); + DocumentUndo::done(doc, SP_VERB_NONE, "Document metadata updated"); } _wr->setUpdating (false); } diff --git a/src/ui/widget/font-variants.cpp b/src/ui/widget/font-variants.cpp index 637631fda..5d1e40971 100644 --- a/src/ui/widget/font-variants.cpp +++ b/src/ui/widget/font-variants.cpp @@ -13,7 +13,7 @@ #include <gtkmm.h> #include <glibmm/i18n.h> - +#include <libnrtype/font-instance.h> #include <iostream> #include "font-variants.h" @@ -68,6 +68,9 @@ namespace Widget { _numeric_ordinal ( Glib::ustring(_("Ordinal" )) ), _numeric_slashed_zero ( Glib::ustring(_("Slashed Zero" )) ), + _feature_frame ( Glib::ustring(_("Feature Settings")) ), + _feature_label ( Glib::ustring(_("Selection has different Feature Settings!")) ), + _ligatures_changed( false ), _position_changed( false ), _caps_changed( false ), @@ -231,6 +234,21 @@ namespace Widget { add( _numeric_frame ); + // Feature settings --------------------- + + // Add tooltips + _feature_entry.set_tooltip_text( _("Feature settings in CSS form. No sanity checking is performed.")); + + // Add to frame + _feature_vbox.add( _feature_entry ); + _feature_vbox.add( _feature_label ); + _feature_frame.add( _feature_vbox ); + add( _feature_frame ); + + // Add signals + //_feature_entry.signal_key_press_event().connect ( sigc::mem_fun(*this, &FontVariants::feature_callback) ); + _feature_entry.signal_changed().connect( sigc::mem_fun(*this, &FontVariants::feature_callback) ); + show_all_children(); } @@ -283,9 +301,21 @@ namespace Widget { _changed_signal.emit(); } + void + FontVariants::feature_init() { + // std::cout << "FontVariants::feature_init()" << std::endl; + } + + void + FontVariants::feature_callback() { + // std::cout << "FontVariants::feature_callback()" << std::endl; + _feature_changed = true; + _changed_signal.emit(); + } + // Update GUI based on query. void - FontVariants::update( SPStyle const *query ) { + FontVariants::update( SPStyle const *query, bool different_features, Glib::ustring& font_spec ) { _ligatures_all = query->font_variant_ligatures.computed; _ligatures_mix = query->font_variant_ligatures.value; @@ -370,10 +400,182 @@ namespace Widget { _numeric_ordinal.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_ORDINAL ); _numeric_slashed_zero.set_inconsistent( _numeric_mix & SP_CSS_FONT_VARIANT_NUMERIC_SLASHED_ZERO ); + if( query->font_feature_settings.value ) + _feature_entry.set_text( query->font_feature_settings.value ); + if( different_features ) { + _feature_label.show(); + } else { + _feature_label.hide(); + } + + + // Disable/Enable based on available OpenType tables. + font_instance* res = font_factory::Default()->FaceFromFontSpecification( font_spec.c_str() ); + if( res ) { + + std::map<Glib::ustring,int>::iterator it; + + if((it = res->openTypeTables.find("liga"))!= res->openTypeTables.end() || + (it = res->openTypeTables.find("clig"))!= res->openTypeTables.end()) { + _ligatures_common.set_sensitive(); + } else { + _ligatures_common.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("dlig"))!= res->openTypeTables.end()) { + _ligatures_discretionary.set_sensitive(); + } else { + _ligatures_discretionary.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("hlig"))!= res->openTypeTables.end()) { + _ligatures_historical.set_sensitive(); + } else { + _ligatures_historical.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("calt"))!= res->openTypeTables.end()) { + _ligatures_contextual.set_sensitive(); + } else { + _ligatures_contextual.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("subs"))!= res->openTypeTables.end()) { + _position_sub.set_sensitive(); + } else { + _position_sub.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("sups"))!= res->openTypeTables.end()) { + _position_super.set_sensitive(); + } else { + _position_super.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("smcp"))!= res->openTypeTables.end()) { + _caps_small.set_sensitive(); + } else { + _caps_small.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("c2sc"))!= res->openTypeTables.end() && + (it = res->openTypeTables.find("smcp"))!= res->openTypeTables.end()) { + _caps_all_small.set_sensitive(); + } else { + _caps_all_small.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("pcap"))!= res->openTypeTables.end()) { + _caps_petite.set_sensitive(); + } else { + _caps_petite.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("c2sc"))!= res->openTypeTables.end() && + (it = res->openTypeTables.find("pcap"))!= res->openTypeTables.end()) { + _caps_all_petite.set_sensitive(); + } else { + _caps_all_petite.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("unic"))!= res->openTypeTables.end()) { + _caps_unicase.set_sensitive(); + } else { + _caps_unicase.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("titl"))!= res->openTypeTables.end()) { + _caps_titling.set_sensitive(); + } else { + _caps_titling.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("lnum"))!= res->openTypeTables.end()) { + _numeric_lining.set_sensitive(); + } else { + _numeric_lining.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("onum"))!= res->openTypeTables.end()) { + _numeric_old_style.set_sensitive(); + } else { + _numeric_old_style.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("pnum"))!= res->openTypeTables.end()) { + _numeric_proportional.set_sensitive(); + } else { + _numeric_proportional.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("tnum"))!= res->openTypeTables.end()) { + _numeric_tabular.set_sensitive(); + } else { + _numeric_tabular.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("frac"))!= res->openTypeTables.end()) { + _numeric_diagonal.set_sensitive(); + } else { + _numeric_diagonal.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("afrac"))!= res->openTypeTables.end()) { + _numeric_stacked.set_sensitive(); + } else { + _numeric_stacked.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("ordn"))!= res->openTypeTables.end()) { + _numeric_ordinal.set_sensitive(); + } else { + _numeric_ordinal.set_sensitive( false ); + } + + if((it = res->openTypeTables.find("zero"))!= res->openTypeTables.end()) { + _numeric_slashed_zero.set_sensitive(); + } else { + _numeric_slashed_zero.set_sensitive( false ); + } + + // Make list of tables not handled above... eventually add Gtk::Label with + // this info. + // std::map<Glib::ustring,int> table_copy = res->openTypeTables; + // if( (it = table_copy.find("liga")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("clig")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("dlig")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("hlig")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("calt")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("subs")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("sups")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("smcp")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("c2sc")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("pcap")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("unic")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("titl")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("lnum")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("onum")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("pnum")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("tnum")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("frac")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("afrc")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("ordn")) != table_copy.end() ) table_copy.erase( it ); + // if( (it = table_copy.find("zero")) != table_copy.end() ) table_copy.erase( it ); + // for(it = table_copy.begin(); it != table_copy.end(); ++it) { + // std::cout << "Other: " << it->first << " Occurances: " << it->second << std::endl; + // } + + } else { + std::cerr << "FontVariants::update(): Couldn't find font_instance for: " + << font_spec << std::endl; + } + + _ligatures_changed = false; _position_changed = false; _caps_changed = false; _numeric_changed = false; + _feature_changed = false; } void @@ -494,6 +696,11 @@ namespace Widget { sp_repr_css_set_property(css, "font-variant-numeric", css_string.c_str() ); } + // Feature settings + Glib::ustring feature_string = _feature_entry.get_text(); + if( !feature_string.empty() || feature_string.compare( "normal" ) ) { + sp_repr_css_set_property(css, "font-feature-settings", feature_string.c_str()); + } } } // namespace Widget diff --git a/src/ui/widget/font-variants.h b/src/ui/widget/font-variants.h index e6a9dd68b..d4329feff 100644 --- a/src/ui/widget/font-variants.h +++ b/src/ui/widget/font-variants.h @@ -13,6 +13,7 @@ #include <gtkmm/expander.h> #include <gtkmm/checkbutton.h> #include <gtkmm/radiobutton.h> +#include <gtkmm/entry.h> class SPDesktop; class SPObject; @@ -81,6 +82,11 @@ protected: Gtk::CheckButton _numeric_ordinal; Gtk::CheckButton _numeric_slashed_zero; + Gtk::Expander _feature_frame; + Gtk::VBox _feature_vbox; + Gtk::Entry _feature_entry; + Gtk::Label _feature_label; + private: void ligatures_init(); void ligatures_callback(); @@ -94,6 +100,9 @@ private: void numeric_init(); void numeric_callback(); + void feature_init(); + void feature_callback(); + // To determine if we need to write out property (may not be necessary) unsigned _ligatures_all; unsigned _position_all; @@ -109,6 +118,7 @@ private: bool _position_changed; bool _caps_changed; bool _numeric_changed; + bool _feature_changed; sigc::signal<void> _changed_signal; @@ -117,7 +127,7 @@ public: /** * Update GUI based on query results. */ - void update( SPStyle const *query ); + void update( SPStyle const *query, bool different_features, Glib::ustring& font_spec ); /** * Fill SPCSSAttr based on settings of buttons. diff --git a/src/ui/widget/page-sizer.cpp b/src/ui/widget/page-sizer.cpp index 0a5697661..19ab1a280 100644 --- a/src/ui/widget/page-sizer.cpp +++ b/src/ui/widget/page-sizer.cpp @@ -266,12 +266,14 @@ PageSizer::PageSizer(Registry & _wr) _viewboxW.setDigits(2); _viewboxH.setDigits(2); + _dimensionWidth.setRange( 0.00001, 10000000 ); + _dimensionHeight.setRange( 0.00001, 10000000 ); _scaleX.setRange( 0.00001, 100000 ); _scaleY.setRange( 0.00001, 100000 ); _viewboxX.setRange( -100000, 100000 ); _viewboxY.setRange( -100000, 100000 ); - _viewboxW.setRange( 0, 200000 ); - _viewboxH.setRange( 0, 200000 ); + _viewboxW.setRange( 0.01, 200000 ); + _viewboxH.setRange( 0.01, 200000 ); _scaleY.set_sensitive (false); // We only want to display Y scale. diff --git a/src/uri-references.cpp b/src/uri-references.cpp index 2518c173e..04f904d39 100644 --- a/src/uri-references.cpp +++ b/src/uri-references.cpp @@ -43,6 +43,56 @@ URIReference::~URIReference() detach(); } +/* + * The main ideas here are: + * (1) "If we are inside a clone, then we can accept if and only if our "original thing" can accept the reference" + * (this caused problems when there are clones because a change in ids triggers signals for the object hrefing this id, but also its cloned reprs + * (descendants of <use> referencing an ancestor of the href'ing object)). The way it is done here is *atrocious*, but i could not find a better way. + * FIXME: find a better and safer way to find the "original object" of anyone with the flag ->cloned + * + * (2) Once we have an (potential owner) object, it can accept a href to obj, iff the graph of objects where directed edges are + * either parent->child relations , *** or href'ing to href'ed *** relations, stays acyclic. + * We can go either from owner and up in the tree, or from obj and down, in either case this will be in the worst case linear in the number of objects. + * There are no easy objects allowing to do the second proposition, while "hrefList" is a "list of objects href'ing us", so we'll take this. + * Then we keep a set of already visited elements, and do a DFS on this graph. if we find obj, then BOOM. + */ + +bool URIReference::_acceptObject(SPObject *obj) const { + //we go back following hrefList and parent to find if the object already references ourselves indirectly + std::set<SPObject*> done; + SPObject * owner = getOwner(); + if(!owner)return true; + while(owner->cloned){ + std::vector<int> positions; + while(owner->cloned){ + int position=0; + SPObject* c = owner->parent->firstChild(); + while(c != owner && dynamic_cast<SPObject*>(c) ){position++;c=c->next;} + positions.push_back(position); + owner=owner->parent; + } + owner = ((SPUse*)owner)->get_original(); + for(int i=positions.size()-2;i>=0;i--)owner=owner->childList(false)[positions[i]]; + } + //once we have the "original" object (hopefully) we look at who is referencing it + std::list<SPObject*> todo(owner->hrefList); + todo.push_front(owner->parent); + while(!todo.empty()){ + SPObject* e = todo.front(); + todo.pop_front(); + if(!dynamic_cast<SPObject*>(e))continue; + if(done.insert(e).second){ + if(e==obj){return false;} + todo.push_front(e->parent); + todo.insert(todo.begin(),e->hrefList.begin(),e->hrefList.end()); + } + } + return true; +} + + + + void URIReference::attach(const URI &uri) throw(BadURIException) { SPDocument *document = NULL; diff --git a/src/uri-references.h b/src/uri-references.h index 0c51481cc..e56ea0612 100644 --- a/src/uri-references.h +++ b/src/uri-references.h @@ -15,11 +15,15 @@ */ #include <cstddef> +#include <vector> +#include <set> #include <sigc++/connection.h> #include <sigc++/trackable.h> #include "bad-uri-exception.h" #include "sp-object.h" +#include "sp-item.h" +#include "sp-use.h" namespace Inkscape { @@ -122,11 +126,7 @@ public: SPObject *getOwnerObject() { return _owner; } protected: - virtual bool _acceptObject(SPObject *obj) const { - (void)obj; - return true; - } - + virtual bool _acceptObject(SPObject *obj) const; private: SPObject *_owner; SPDocument *_owner_document; diff --git a/src/widgets/stroke-style.cpp b/src/widgets/stroke-style.cpp index 482ca7af4..d05b3b994 100644 --- a/src/widgets/stroke-style.cpp +++ b/src/widgets/stroke-style.cpp @@ -450,7 +450,11 @@ StrokeStyle::makeRadioButton(Gtk::RadioButtonGroup &grp, */ void StrokeStyle::markerSelectCB(MarkerComboBox *marker_combo, StrokeStyle *spw, SPMarkerLoc const /*which*/) { - if (spw->update) { + bool markers_update = spw->startMarkerCombo->update() || + spw->midMarkerCombo->update() || + spw->endMarkerCombo->update(); + + if (spw->update || markers_update) { return; } |
