summaryrefslogtreecommitdiff
path: root/dviware/dvisvgm/src/GraphicsPath.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'dviware/dvisvgm/src/GraphicsPath.hpp')
-rw-r--r--dviware/dvisvgm/src/GraphicsPath.hpp786
1 files changed, 527 insertions, 259 deletions
diff --git a/dviware/dvisvgm/src/GraphicsPath.hpp b/dviware/dvisvgm/src/GraphicsPath.hpp
index bf453d11d4..78b29d4630 100644
--- a/dviware/dvisvgm/src/GraphicsPath.hpp
+++ b/dviware/dvisvgm/src/GraphicsPath.hpp
@@ -18,17 +18,148 @@
** along with this program; if not, see <http://www.gnu.org/licenses/>. **
*************************************************************************/
-#ifndef GRAPHICSPATH_HPP
-#define GRAPHICSPATH_HPP
+#pragma once
+#include <array>
#include <cctype>
#include <deque>
#include <ostream>
+#include <type_traits>
+#include <mpark/variant.hpp>
#include "BoundingBox.hpp"
+#include "EllipticalArc.hpp"
#include "Matrix.hpp"
#include "Pair.hpp"
+#include "utility.hpp"
#include "XMLString.hpp"
+template <typename T>
+class GraphicsPath;
+
+namespace gp {
+
+/// Base class for all path data commands, like moveto, lineto, curveto, etc.
+struct CommandBase {};
+
+/** Base class for all path data commands with NUM_POINTS point parameters
+ * @tparam NUM_POINTS number of parameter pairs representing points, e.g. 1 for moveto and lineto */
+template <typename T, int NUM_POINTS>
+class Command : public CommandBase {
+ friend class GraphicsPath<T>;
+ public:
+ int numPoints () const {return NUM_POINTS;}
+ Pair<T>& point (int n) {return points[n];}
+ const Pair<T>& point (int n) const {return points[n];}
+
+ /** Transforms the command by a given transformation matrix.
+ * @params[in] matrix describes the affine transformation to apply
+ * @params[in] currentPoint the untransformed end point of the preceding command */
+ void transform (const Matrix &matrix, const Pair<T> &currentPoint) {
+ for (Pair<T> &p : points)
+ p = matrix * p;
+ }
+
+ /** Returns true if all points are identical to those of another command. */
+ bool pointsEqual (const Command &cmd) const {
+ for (int i=0; i < NUM_POINTS; i++)
+ if (points[i] != cmd.points[i])
+ return false;
+ return true;
+ }
+
+ protected:
+ explicit Command () =default;
+ explicit Command (std::array<Pair<T>, NUM_POINTS> &&pts) : points(std::move(pts)) {}
+
+ protected:
+ std::array<Pair<T>, NUM_POINTS> points;
+};
+
+template <typename T>
+struct MoveTo : public Command<T, 1> {
+ explicit MoveTo (const Pair<T> &p) : Command<T, 1>({p}) {}
+};
+
+template <typename T>
+struct LineTo : public Command<T, 1> {
+ explicit LineTo (const Pair<T> &p) : Command<T, 1>({p}) {}
+};
+
+template <typename T>
+struct CubicTo : public Command<T, 3> {
+ explicit CubicTo (const Pair<T> &p1, const Pair<T> &p2, const Pair<T> &p3) : Command<T, 3>({p1, p2, p3}) {}
+};
+
+template <typename T>
+struct QuadTo : public Command<T, 2> {
+ explicit QuadTo (const Pair<T> &p1, const Pair<T> &p2) : Command<T, 2>({p1, p2}) {}
+};
+
+template <typename T>
+struct ClosePath : public Command<T, 0> {
+ ClosePath () : Command<T, 0>() {}
+};
+
+template <typename T>
+struct ArcTo : Command<T, 1> {
+ ArcTo (T rxx, T ryy, double xrot, bool laf, bool sf, const Pair<T> &pp)
+ : Command<T, 1>({pp}), rx(rxx < 0 ? -rxx : rxx), ry(ryy < 0 ? -ryy : ryy),
+ xrotation(xrot), largeArcFlag(laf), sweepFlag(sf) {}
+
+ bool operator == (const ArcTo &arc) const {
+ return rx == arc.rx
+ && ry == arc.ry
+ && xrotation == arc.xrotation
+ && largeArcFlag == arc.largeArcFlag
+ && sweepFlag == arc.sweepFlag
+ && this->points[0] == arc.points[0];
+ }
+
+ void transform (const Matrix &matrix, const Pair<T> &currentPoint);
+
+ bool operator != (const ArcTo &arc) const {return !(*this == arc);}
+
+ T rx, ry; ///< length of the semi-major and semi-minor axes
+ double xrotation; ///< rotation of the semi-major axis in degrees
+ bool largeArcFlag; ///< if true, the longer arc from start to end point is chosen, else the shorter one
+ bool sweepFlag; ///< if true, arc is drawn in direction of positive angles, else the opposite direction
+};
+
+/** Applies an affine transformation described by a given matrix to the arc segment.
+ * @params[in] matrix describes the affine transformation to apply
+ * @params[in] currentPoint the untransformed end point of the preceding command */
+template <typename T>
+void ArcTo<T>::transform (const Matrix &matrix, const Pair<T> &currentPoint) {
+ EllipticalArc arc(currentPoint, rx, ry, math::deg2rad(xrotation), largeArcFlag, sweepFlag, this->points[0]);
+ arc.transform(matrix);
+ rx = arc.rx();
+ ry = arc.ry();
+ xrotation = math::rad2deg(arc.rotationAngle());
+ largeArcFlag = arc.largeArc();
+ sweepFlag = arc.sweepPositive();
+ this->points[0] = Pair<T>(arc.endPoint());
+}
+
+/** Returns true if two path command objects are identical (same command and same parameters). */
+template <typename Cmd1, typename Cmd2>
+inline typename std::enable_if<std::is_base_of<CommandBase, Cmd1>::value, bool>::type
+operator == (const Cmd1 &cmd1, const Cmd2 &cmd2) {
+ if (std::is_convertible<Cmd1, Cmd2>::value && std::is_convertible<Cmd2, Cmd1>::value)
+ return cmd1.pointsEqual(cmd2);
+ return false;
+}
+
+/** Returns true if two path command objects differ (different commands or different parameters). */
+template <typename Cmd1, typename Cmd2>
+inline typename std::enable_if<std::is_base_of<CommandBase, Cmd1>::value, bool>::type
+operator != (const Cmd1 &cmd1, const Cmd2 &cmd2) {
+ if (std::is_convertible<Cmd1, Cmd2>::value && std::is_convertible<Cmd2, Cmd1>::value)
+ return !cmd1.pointsEqual(cmd2);
+ return true;
+}
+
+} // namespace gp
+
template <typename T>
class GraphicsPath {
@@ -37,74 +168,268 @@ class GraphicsPath {
enum class WindingRule {EVEN_ODD, NON_ZERO};
using Point = Pair<T>;
- struct Command {
- enum class Type {MOVETO, LINETO, CONICTO, CUBICTO, CLOSEPATH};
+ protected:
- explicit Command (Type t) : type(t) {}
+ static XMLString to_param_str (double v, double s, double d, bool leadingSpace) {
+ XMLString str(v*s + d);
+ if (leadingSpace && (str[0] != '-'))
+ str.insert(0, " ");
+ return str;
+ }
- Command (Type t, const Point &p) : type(t) {
- params[0] = p;
- }
+ static XMLString to_param_str (double val, double prev, double s, double d, bool leadingSpace) {
+ XMLString str((val-prev)*s + d);
+ if (leadingSpace && (str[0] != '-'))
+ str.insert(0, " ");
+ return str;
+ }
- Command (Type t, const Point &p1, const Point &p2) : type(t) {
- params[0] = p1;
- params[1] = p2;
- }
+ static std::string to_param_str (const Point &p, double sx, double sy, double dx, double dy, bool leadingSpace) {
+ return to_param_str(p.x(), sx, dx, leadingSpace) + to_param_str(p.y(), sy, dy, true);
+ }
- Command (Type t, const Point &p1, const Point &p2, const Point &p3) : type(t) {
- params[0] = p1;
- params[1] = p2;
- params[2] = p3;
- }
+ static std::string to_param_str (const Point &p, const Point &prev, double sx, double sy, double dx, double dy, bool leadingSpace) {
+ return to_param_str(p.x()-prev.x(), sx, dx, leadingSpace) + to_param_str(p.y()-prev.y(), sy, dy, true);
+ }
+
+ using MoveTo = gp::MoveTo<T>;
+ using LineTo = gp::LineTo<T>;
+ using CubicTo = gp::CubicTo<T>;
+ using QuadTo = gp::QuadTo<T>;
+ using ArcTo = gp::ArcTo<T>;
+ using ClosePath = gp::ClosePath<T>;
+
+ /// Variant representing a single path command
+ using CommandVariant = mpark::variant<MoveTo, LineTo, CubicTo, QuadTo, ArcTo, ClosePath>;
- int numParams () const {
- switch (type) {
- case Type::CLOSEPATH : return 0;
- case Type::MOVETO :
- case Type::LINETO : return 1;
- case Type::CONICTO : return 2;
- case Type::CUBICTO : return 3;
+ class IterationVisitor;
+
+ public:
+ /** Base class providing several template methods being called when executing
+ * GraphicsPath::iterate(). */
+ class IterationActions {
+ friend class IterationVisitor;
+ public:
+ virtual ~IterationActions () =default;
+ virtual void moveto (const Point &p) {}
+ virtual void lineto (const Point &p) {}
+ virtual void hlineto (const T &x) {}
+ virtual void vlineto (const T &y) {}
+ virtual void quadto (const Point &p) {}
+ virtual void quadto (const Point &p1, const Point &p2) {}
+ virtual void cubicto (const Point &p1, const Point &p2) {}
+ virtual void cubicto (const Point &p1, const Point &p2, const Point &p3) {}
+ virtual void arcto (T rx, T ry, double angle, bool largeArcFlag, bool sweepFlag, const Point &p) {}
+ virtual void closepath () {}
+ virtual bool quit () {return false;}
+ virtual void finished () {}
+ const Point& startPoint () const {return _startPoint;}
+ const Point& currentPoint () const {return _currentPoint;}
+
+ private:
+ Point _startPoint; ///< first point of the current sub-path
+ Point _currentPoint; ///< point reached by preceding path command, or (0,0) otherwise
+ };
+
+ protected:
+ class ModificationActions : public IterationActions {
+ friend class GraphicsPath;
+ public:
+ explicit ModificationActions (GraphicsPath &path) : _path(path) {}
+
+ protected:
+ GraphicsPath& path () {return _path;}
+ int commandPos () const {return _commandPos;}
+
+ private:
+ GraphicsPath &_path;
+ int _commandPos=0; ///< number of command in path being processed
+ };
+
+ class WriteActions : public IterationActions {
+ public:
+ WriteActions (std::ostream &os, bool rel, double sx, double sy, double dx, double dy)
+ : _os(os), _relative(rel), _sx(sx), _sy(sy), _dx(dx), _dy(dy) {}
+
+ void moveto (const Point &p) override {write('M', {p});}
+ void lineto (const Point &p) override {write('L', {p});}
+ void hlineto (const T &x) override {write('H', x, this->currentPoint().x(), _sx, _dx);}
+ void vlineto (const T &y) override {write('V', y, this->currentPoint().y(), _sy, _dy);}
+ void quadto (const Point &p) override {write('T', {p});}
+ void quadto (const Point &p1, const Point &p2) override {write('Q', {p1, p2});}
+ void cubicto (const Point &p1, const Point &p2) override {write('S', {p1, p2});}
+ void cubicto (const Point &p1, const Point &p2, const Point &p3) override {write('C', {p1, p2, p3});}
+ void closepath () override {_os << (_relative ? 'z' : 'Z');}
+
+ void arcto (T rx, T ry, double angle, bool largeArcFlag, bool sweepFlag, const Point &p) override {
+ Point diff = p-this->currentPoint();
+ if (std::abs(diff.x()) < 1e-7 && std::abs(diff.y()) < 1e-7)
+ return;
+ if (std::abs(rx) < 1e-7 && std::abs(ry) < 1e-7)
+ lineto(p);
+ else {
+ if (std::abs(std::abs(_sx) - std::abs(_sy)) < 1e-7) { // symmetric scaling?
+ angle *= math::sgn(_sx) * math::sgn(_sy);
+ rx *= std::abs(_sx);
+ ry *= std::abs(_sx);
+ }
+ else { // asymmetric scaling => compute new shape parameters
+ EllipticalArc arc(this->currentPoint(), double(rx), double(ry), math::deg2rad(angle), largeArcFlag, sweepFlag, p);
+ arc.transform(ScalingMatrix(_sx, _sy));
+ angle = math::rad2deg(arc.rotationAngle());
+ rx = arc.rx();
+ ry = arc.ry();
+ }
+ _os << (_relative ? 'a' : 'A')
+ << to_param_str(rx, 1.0, 0, false)
+ << to_param_str(ry, 1.0, 0, true)
+ << to_param_str(angle, 1.0, 0, true)
+ << ' ' << (largeArcFlag ? 1 : 0)
+ << ' ' << (sweepFlag ? 1 : 0);
+ if (_relative)
+ _os << to_param_str(p, this->currentPoint(), _sx, _sy, _dx, _dy, true);
+ else
+ _os << to_param_str(p, _sx, _sy, _dx, _dy, true);
+ }
}
- return 0;
- }
- void transform (const Matrix &matrix) {
- for (int i=0; i < numParams(); i++)
- params[i] = matrix * params[i];
- }
+ protected:
+ void write (char cmdchar, std::initializer_list<Point> points) const {
+ int count=0;
+ if (_relative) {
+ _os << char(tolower(cmdchar));
+ for (const Point &p : points)
+ _os << to_param_str(p, this->currentPoint(), _sx, _sy, _dx, _dy, count++ > 0);
+ }
+ else {
+ _os << cmdchar;
+ for (const Point &p : points)
+ _os << to_param_str(p, _sx, _sy, _dx, _dy, count++ > 0);
+ }
+ }
- bool operator == (const Command &cmd) const {
- bool ret = (type == cmd.type);
- for (int i=0; ret && i < numParams(); i++)
- ret &= (params[i] == cmd.params[i]);
- return ret;
- }
+ void write (char cmdchar, T val, T relval, double s, double d) const {
+ if (_relative)
+ _os << char(tolower(cmdchar)) << to_param_str(val, relval, s, d, false);
+ else
+ _os << cmdchar << to_param_str(val, s, d, false);
+ }
- bool operator != (const Command &cmd) const {
- bool ret = (type != cmd.type);
- for (int i=0; !ret && i < numParams(); i++)
- ret |= (params[i] != cmd.params[i]);
- return ret;
- }
+ private:
+ std::ostream &_os; ///< write output to this stream
+ bool _relative; ///< if true, use relative coordinates in path commands
+ double _sx, _sy; ///< horizontal and vertical scaling factors
+ double _dx, _dy; ///< horizontal and vertical translation values
+ };
+
+ ///////////////////////////////////////////////////////////////////////////////
+
+ /** Calls the corresponding template method of an Action object for the current path command.
+ * If parameter 'useShortCmds' is true, the visitor operators check whether a command
+ * can be shortened due to special cases, e.g. horizontal or vertical lines, smooth
+ * curve connections etc. Otherwise, the full command templates are triggered. */
+ class IterationVisitor {
+ public:
+ IterationVisitor (IterationActions &actions, bool useShortCmds, double eps=1e-7)
+ : _actions(actions), _shortCommandsActive(useShortCmds), _eps(eps) {}
+
+ void setPrevCommand (const CommandVariant &prevCommand) {
+ _prevCommand = &prevCommand;
+ }
+
+ void operator () (const MoveTo &cmd) {
+ _actions.moveto(cmd.points[0]);
+ _actions._startPoint = _actions._currentPoint = cmd.points[0];
+ }
+
+ void operator () (const LineTo &cmd) {
+ Point diff = abs(_actions._currentPoint-cmd.points[0]);
+ if (diff.x() >= _eps || diff.y() >= _eps) {
+ if (!_shortCommandsActive)
+ _actions.lineto(cmd.points[0]);
+ else {
+ if (diff.x() < _eps)
+ _actions.vlineto(cmd.points[0].y());
+ else if (diff.y() < _eps)
+ _actions.hlineto(cmd.points[0].x());
+ else
+ _actions.lineto(cmd.points[0]);
+ }
+ }
+ _actions._currentPoint = cmd.points[0];
+ }
+
+ void operator () (const CubicTo &cmd) {
+ bool smooth=false;
+ if (_shortCommandsActive) {
+ if (auto *prevCubic = mpark::get_if<CubicTo>(_prevCommand)) {
+ Point diff = abs(cmd.points[0] - prevCubic->points[2]*T(2) + prevCubic->points[1]);
+ if ((smooth = (diff.x() < _eps && diff.y() < _eps)))
+ _actions.cubicto(cmd.points[1], cmd.points[2]);
+ }
+ }
+ if (!smooth)
+ _actions.cubicto(cmd.points[0], cmd.points[1], cmd.points[2]);
+ _actions._currentPoint = cmd.points[2];
+ }
+
+ void operator () (const QuadTo &cmd) {
+ bool smooth=false;
+ if (_shortCommandsActive) {
+ if (auto *prevQuad = mpark::get_if<QuadTo>(_prevCommand)) {
+ Point diff = abs(cmd.points[0] - prevQuad->points[1] * T(2) + prevQuad->points[0]);
+ if ((smooth = (diff.x() < _eps && diff.y() < _eps))) // is reflection?
+ _actions.quadto(cmd.points[1]);
+ }
+ }
+ if (!smooth)
+ _actions.quadto(cmd.points[0], cmd.points[1]);
+ _actions._currentPoint = cmd.points[1];
+ }
- Type type;
- Point params[3];
+ void operator () (const ClosePath &cmd) {
+ _actions.closepath();
+ _actions._currentPoint = _actions._startPoint;
+ }
+
+ void operator () (const ArcTo &cmd) {
+ _actions.arcto(cmd.rx, cmd.ry, cmd.xrotation, cmd.largeArcFlag, cmd.sweepFlag, cmd.points[0]);
+ _actions._currentPoint = cmd.points[0];
+ }
+
+ private:
+ IterationActions &_actions;
+ bool _shortCommandsActive=false;
+ double _eps=1e-7;
+ const CommandVariant *_prevCommand=nullptr;
};
- struct Actions {
- virtual ~Actions () =default;
- virtual void moveto (const Point &p) {}
- virtual void lineto (const Point &p) {}
- virtual void hlineto (const T &y) {}
- virtual void vlineto (const T &x) {}
- virtual void conicto (const Point &p) {}
- virtual void conicto (const Point &p1, const Point &p2) {}
- virtual void cubicto (const Point &p1, const Point &p2) {}
- virtual void cubicto (const Point &p1, const Point &p2, const Point &p3) {}
- virtual void closepath () {}
- virtual void draw (char cmd, const Point *points, int n) {}
- virtual bool quit () {return false;}
- virtual void finished () {}
+ ///////////////////////////////////////////////////////////////////////////////
+
+ /** Transforms all Point parameters of a path command. */
+ class TransformVisior {
+ public:
+ explicit TransformVisior (const Matrix &m) : matrix(m) {}
+
+ template <typename Cmd> void operator () (Cmd &cmd) {
+ Point cp = cmd.point(cmd.numPoints()-1);
+ cmd.transform(matrix, _currentPoint);
+ _currentPoint = cp;
+ }
+
+ void operator () (MoveTo &cmd) {
+ Point cp = cmd.point(0);
+ cmd.transform(matrix, _currentPoint);
+ _startPoint = _currentPoint = cp;
+ }
+
+ void operator () (ClosePath &cmd) {
+ _currentPoint = _startPoint;
+ }
+
+ private:
+ const Matrix &matrix;
+ Point _startPoint, _currentPoint; ///< untransformed start end current point
};
public:
@@ -127,6 +452,9 @@ class GraphicsPath {
return _commands.size();
}
+ const Point& startPoint () const {return _startPoint;}
+ const Point& finalPoint () const {return _finalPoint;}
+
/// Insert another path at the beginning of this one.
void prepend (const GraphicsPath &path) {
_commands.insert(_commands.begin(), path._commands.begin(), path._commands.end());
@@ -138,10 +466,11 @@ class GraphicsPath {
void moveto (const Point &p) {
// avoid sequences of several MOVETOs; always use latest
- if (_commands.empty() || _commands.back().type != Command::Type::MOVETO)
- _commands.emplace_back(Command(Command::Type::MOVETO, p));
+ if (_commands.empty() || !mpark::get_if<MoveTo>(&_commands.back()))
+ _commands.emplace_back(MoveTo{p});
else
- _commands.back().params[0] = p;
+ mpark::get<MoveTo>(_commands.back()).points[0] = p;
+ _startPoint = _finalPoint = p;
}
void lineto (const T &x, const T &y) {
@@ -149,57 +478,94 @@ class GraphicsPath {
}
void lineto (const Point &p) {
- _commands.emplace_back(Command(Command::Type::LINETO, p));
+ _commands.emplace_back(LineTo{p});
+ _finalPoint = p;
}
- void conicto (const T &x1, const T &y1, const T &x2, const T &y2) {
- conicto(Point(x1, y1), Point(x2, y2));
+ void quadto (const T &x1, const T &y1, const T &x2, const T &y2) {
+ quadto(Point(x1, y1), Point(x2, y2));
}
- void conicto (const Point &p1, const Point &p2) {
- _commands.emplace_back(Command(Command::Type::CONICTO, p1, p2));
+ /** Creates a quadratic Bézier segment. */
+ void quadto (const Point &p1, const Point &p2) {
+ _commands.emplace_back(QuadTo{p1, p2});
+ _finalPoint = p2;
+ }
+
+ /** Creates a quadratic Bézier segment smoothly extending a preceding one, i.e. the gradients
+ * of the two curves are identical at the connection point. The control point of the second
+ * curve is computed as the reflection of the preceding curve's control point at the connection
+ * point. */
+ void quadto (const Point &p2) {
+ Point p1;
+ if (!_commands.empty()) {
+ if (auto qto = mpark::get_if<QuadTo>(&_commands.back()))
+ p1 = _finalPoint*T(2) - qto->point(0); // reflect previous control point at current point
+ else // previous command isn't a quadto?
+ p1 = _finalPoint; // => use current point as control point
+ }
+ quadto(p1, p2);
}
void cubicto (const T &x1, const T &y1, const T &x2, const T &y2, const T &x3, const T &y3) {
cubicto(Point(x1, y1), Point(x2, y2), Point(x3, y3));
}
+ /** Creates a cubic Bézier segment. */
void cubicto (const Point &p1, const Point &p2, const Point &p3) {
- _commands.emplace_back(Command(Command::Type::CUBICTO, p1, p2, p3));
+ _commands.emplace_back(CubicTo{p1, p2, p3});
+ _finalPoint = p3;
}
- void closepath () {
- _commands.emplace_back(Command(Command::Type::CLOSEPATH));
+ /** Creates a cubic Bézier segment smoothly extending a preceding one, i.e. the gradients
+ * of the two curves are identical at the connection point. The first control point of
+ * the second curve is computed as the reflection of the preceding curve's second control
+ * point at the connection point. */
+ void cubicto (const Point &p2, const Point &p3) {
+ Point p1;
+ if (!_commands.empty()) {
+ if (auto cto = mpark::get_if<CubicTo>(&_commands.back()))
+ p1 = _finalPoint*T(2) - cto->point(1); // reflect previous control point at current point
+ else // previous command isn't a cubicto?
+ p1 = _finalPoint; // => use current point as control point
+ }
+ cubicto(p1, p2, p3);
}
- const std::vector<Command>& commands () const {
- return _commands;
+ void closepath () {
+ if (!_commands.empty() && !mpark::get_if<ClosePath>(&_commands.back())) {
+ _commands.emplace_back(ClosePath{});
+ _finalPoint = _startPoint;
+ }
}
+ void arcto (double rx, double ry, double angle, bool laf, bool sweep, const Point &p) {
+ _commands.emplace_back(ArcTo{rx, ry, angle, laf, sweep, p});
+ _finalPoint = p;
+ }
/** Detects all open subpaths and closes them by adding a closePath command.
* Most font formats only support closed outline paths so there are no explicit closePath statements
* in the glyph's outline description. All open paths are automatically closed by the renderer.
* This method detects all open paths and adds the missing closePath statement. */
void closeOpenSubPaths () {
- Command *prevCommand=0;
+ CommandVariant *prevCmd = nullptr;
for (auto it=_commands.begin(); it != _commands.end(); ++it) {
- if (it->type == Command::Type::MOVETO && prevCommand && prevCommand->type != Command::Type::CLOSEPATH) {
- prevCommand = &(*it);
- it = _commands.insert(it, Command(Command::Type::CLOSEPATH))+1;
+ if (mpark::get_if<MoveTo>(&*it) && prevCmd && !mpark::get_if<ClosePath>(prevCmd)) {
+ prevCmd = &*it;
+ it = _commands.insert(it, ClosePath{})+1;
}
else
- prevCommand = &(*it);
+ prevCmd = &*it;
}
- if (!_commands.empty() && _commands.back().type != Command::Type::CLOSEPATH)
+ if (!_commands.empty() && !mpark::get_if<ClosePath>(&_commands.back()))
closepath();
}
-
- /** Removes redundant path commands commands. Currently, it only removes movetos. */
+ /** Removes redundant path commands commands. Currently, only removes movetos. */
void removeRedundantCommands () {
// remove trailing moveto commands
- while (!_commands.empty() && _commands.back().type == Command::Type::MOVETO)
+ while (!_commands.empty() && mpark::get_if<MoveTo>(&_commands.back()))
_commands.pop_back();
// resolve intermediate sequences of moveto commands
auto it=_commands.begin();
@@ -207,7 +573,7 @@ class GraphicsPath {
return;
auto prev = it++;
while (it != _commands.end()) {
- if (prev->type != Command::Type::MOVETO || it->type != Command::Type::MOVETO)
+ if (!mpark::get_if<MoveTo>(&*prev) || !mpark::get_if<MoveTo>(&*it))
prev = it++;
else {
prev = _commands.erase(prev); // remove leading MOVETO and advance 'prev' to 'it'
@@ -216,7 +582,6 @@ class GraphicsPath {
}
}
-
/** Writes the path data as SVG path drawing command to a given output stream.
* @param[in] os output stream used to write the SVG commands to
* @param[in] relative if true, create relative rather than absolute coordinate values
@@ -225,102 +590,38 @@ class GraphicsPath {
* @param[in] dx horizontal translation in PS point units
* @param[in] dy vertical translation in PS point units */
void writeSVG (std::ostream &os, bool relative, double sx=1.0, double sy=1.0, double dx=0.0, double dy=0.0) const {
- struct WriteActions : Actions {
- WriteActions (std::ostream &os, bool relative, double sx, double sy, double dx, double dy)
- : _os(os), _relative(relative), _sx(sx), _sy(sy), _dx(dx), _dy(dy) {}
-
- void draw (char cmd, const Point *points, int n) override {
- if (_relative)
- cmd = tolower(cmd);
- _os << cmd;
- switch (cmd) {
- case 'h': _os << XMLString(_sx*(points->x()-_currentPoint.x())+_dx); break;
- case 'v': _os << XMLString(_sy*(points->y()-_currentPoint.y())+_dy); break;
- case 'z': _currentPoint = _startPoint; break;
- case 'H': _os << XMLString(_sx*points->x()+_dx); break;
- case 'V': _os << XMLString(_sy*points->y()+_dy); break;
- default : {
- for (int i=0; i < n; i++) {
- Point p = points[i];
- if (_relative)
- p -= _currentPoint;
- double x = _sx*p.x() + _dx;
- XMLString xstr(x);
- if (i > 0 && (xstr[0] != '-')) // space required to separate numbers?
- _os << ' ';
- _os << xstr;
- double y = _sy*p.y() + _dy;
- XMLString ystr(y);
- if (ystr[0] != '-') // space required to separate numbers?
- _os << ' ';
- _os << ystr;
- }
- }
- }
- if (cmd == 'm')
- _startPoint = points[0];
- if (islower(cmd) && n > 0)
- _currentPoint = points[n-1];
- }
- std::ostream &_os;
- bool _relative;
- double _sx, _sy, _dx, _dy;
- Point _startPoint, _currentPoint;
- } actions(os, relative, sx, sy, dx, dy);
+ WriteActions actions(os, relative, sx, sy, dx, dy);
iterate(actions, true);
}
-#if 0
- void writePS (std::ostream &os, double sx=1.0, double sy=1.0, double dx=0.0, double dy=0.0) const {
- struct WriteActions : Actions {
- WriteActions (std::ostream &os, double sx, double sy, double dx, double dy)
- : _os(os), _sx(sx), _sy(sy), _dx(dx), _dy(dy) {}
- void draw (char cmd, const Point *points, int n) {
- for (int i=0; i < n; i++)
- _os << _sx*points[i].x()+_dx << ' ' << _sy*points[i].y()+_dy << ' ';
- switch (cmd) {
- case 'M': _os << "moveto"; break;
- case 'L': _os << "lineto"; break;
- case 'C': _os << "curveto"; break;
- case 'Z': _os << "closepath"; break;
- default: ;
- }
- _os << '\n';
- }
- std::ostream &_os;
- bool _relative;
- double _sx, _sy, _dx, _dy;
- } actions(os, sx, sy, dx, dy);
- iterate(actions, false);
- }
-#endif
-
-
/** Computes the bounding box of the current path.
* @param[out] bbox the computed bounding box */
void computeBBox (BoundingBox &bbox) const {
- struct BBoxActions : Actions {
+ struct BBoxActions : IterationActions {
explicit BBoxActions (BoundingBox &bb) : bbox(bb) {}
void moveto (const Point &p) override {bbox.embed(p);}
void lineto (const Point &p) override {bbox.embed(p);}
- void conicto (const Point &p1, const Point &p2) override {bbox.embed(p1); bbox.embed(p2);}
+ void quadto (const Point &p1, const Point &p2) override {bbox.embed(p1); bbox.embed(p2);}
void cubicto (const Point &p1, const Point &p2, const Point &p3) override {bbox.embed(p1); bbox.embed(p2); bbox.embed(p3);}
+ void arcto (T rx, T ry, double angle, bool laf, bool sweep, const Point &p) override {
+ bbox.embed(EllipticalArc(this->currentPoint(), double(rx), double(ry), angle, laf, sweep, p).getBBox());
+ }
BoundingBox &bbox;
} actions(bbox);
iterate(actions, false);
}
-
/** Checks whether the current path describes a dot/point only (with no extent).
* @param[out] p coordinates of the point if path describes a dot
* @return true if path is a dot/point */
bool isDot (Point &p) const {
- struct DotActions : Actions {
+ struct DotActions : IterationActions {
DotActions () : differs(false) {}
void moveto (const Point &p) override {point = p;}
void lineto (const Point &p) override {differs = (p != point);}
- void conicto (const Point &p1, const Point &p2) override {differs = (point != p1 || point != p2);}
+ void quadto (const Point &p1, const Point &p2) override { differs = (point != p1 || point != p2);}
void cubicto (const Point &p1, const Point &p2, const Point &p3) override {differs = (point != p1 || point != p2 || point != p3);}
+ void arcto (T rx, T ry, double angle, bool largeArcFlag, bool sweepFlag, const Point &p) override { differs = (point != p);}
bool quit () override {return differs;}
Point point;
bool differs;
@@ -330,138 +631,105 @@ class GraphicsPath {
return !actions.differs;
}
+ /** Replaces all elliptic arcs with cubic Bézier curves. */
+ void approximateArcs () {
+ struct ArcActions : ModificationActions {
+ explicit ArcActions (GraphicsPath &path) : ModificationActions(path) {}
+ void arcto (T rx, T ry, double angle, bool largeArcFlag, bool sweepFlag, const Point &p) override {
+ EllipticalArc arc(this->currentPoint(), rx, ry, angle, largeArcFlag, sweepFlag, p);
+ std::vector<CommandVariant> cmds;
+ for (const Bezier &bezier : arc.approximate())
+ cmds.emplace_back(CubicTo{bezier.point(1), bezier.point(2), bezier.point(3)});
+ this->path().replace(this->commandPos(), cmds);
+ }
+ } actions(*this);
+ iterate(actions);
+ }
/** Transforms the path according to a given Matrix.
* @param[in] matrix Matrix describing the affine transformation */
void transform (const Matrix &matrix) {
- for (Command &command : _commands)
- command.transform(matrix);
+ TransformVisior visior(matrix);
+ for (CommandVariant &command : _commands)
+ mpark::visit(visior, command);
}
-
+ /** Returns true if this path equals another one, i.e. it consists the same sequence
+ * of commands and coordinates. */
bool operator == (const GraphicsPath &path) const {
if (size() != path.size())
return false;
auto it = _commands.begin();
- for (const Command &cmd : path._commands) {
+ for (const auto &cmd : path._commands) {
if (*it++ != cmd)
return false;
}
return true;
}
-
+ /** Returns true if this path differs from another one (command-wise). */
bool operator != (const GraphicsPath &path) const {
if (size() != path.size())
return true;
auto it = _commands.begin();
- for (const Command &cmd : path._commands) {
+ for (const auto &cmd : path._commands) {
if (*it++ != cmd)
return true;
}
return false;
}
-
- void iterate (Actions &actions, bool optimize) const;
-
- private:
- std::deque<Command> _commands;
- WindingRule _windingRule;
-};
-
-
-/** Iterates over all commands defining this path and calls the corresponding template methods.
- * In the case of successive bezier curve sequences, control points or tangent slopes are often
- * identical so that the path description contains redundant information. SVG provides shorthand
- * curve commands that require less parameters. If 'optimize' is true, this method detects such
- * command sequences.
- * @param[in] actions template methods called by each iteration step
- * @param[in] optimize if true, shorthand drawing commands (hlineto, vlineto,...) are considered */
-template <typename T>
-void GraphicsPath<T>::iterate (Actions &actions, bool optimize) const {
- auto prev = _commands.end(); // pointer to preceding command
- Point fp; // first point of current path
- Point cp; // current point
- Point pstore[2];
- const double eps = XMLString::DECIMAL_PLACES > 0 ? pow(10, -XMLString::DECIMAL_PLACES) : 1e-7;
- for (auto it=_commands.begin(); it != _commands.end() && !actions.quit(); ++it) {
- const Point *params = it->params;
- switch (it->type) {
- case Command::Type::MOVETO:
- actions.moveto(params[0]);
- actions.draw('M', params, 1);
- fp = params[0];
- break;
- case Command::Type::LINETO: {
- Point diff = abs(cp - params[0]);
- if (diff.x() < eps && diff.y() < eps)
+ /** Iterates over all commands defining this path and calls the corresponding template methods.
+ * In the case of successive bezier curve sequences, control points or tangent slopes are often
+ * identical so that the path description contains redundant information. SVG provides shorthand
+ * curve commands that require less parameters. If 'optimize' is true, this method detects such
+ * command sequences.
+ * @param[in] actions template methods called by each iteration step
+ * @param[in] optimize if true, shorthand drawing commands (hlineto, vlineto,...) are considered */
+ void iterate (IterationActions &actions, bool optimize) const {
+ double eps = XMLString::DECIMAL_PLACES > 0 ? pow(10, -XMLString::DECIMAL_PLACES) : 1e-7;
+ IterationVisitor visitor(actions, optimize, eps);
+ for (const CommandVariant &cmd : _commands) {
+ if (actions.quit())
break;
- if (optimize) {
- if (diff.x() < eps) {
- actions.vlineto(params[0].y());
- actions.draw('V', params, 1);
- }
- else if (diff.y() < eps) {
- actions.hlineto(params[0].x());
- actions.draw('H', params, 1);
- }
- else {
- actions.lineto(params[0]);
- actions.draw('L', params, 1);
- }
- }
- else {
- actions.lineto(params[0]);
- actions.draw('L', params, 1);
- }
- break;
+ mpark::visit(visitor, cmd);
+ visitor.setPrevCommand(cmd);
}
- case Command::Type::CONICTO: {
- // check if first control point is the reflection of the preceding second control point?
- Point diff = abs(params[0]-pstore[1]*T(2)+pstore[0]);
- bool isReflection = diff.x() < eps && diff.y() < eps;
- if (optimize && prev != _commands.end() && prev->type == Command::Type::CONICTO && isReflection) {
- actions.conicto(params[1]);
- actions.draw('T', params+1, 1);
- }
- else {
- actions.conicto(params[0], params[1]);
- actions.draw('Q', params, 2);
- }
- pstore[0] = params[0]; // store control point and
- pstore[1] = params[1]; // curve endpoint
- break;
+ actions.finished();
+ }
+
+ protected:
+ /** Replaces a command by a sequence of other ones.
+ * @param[in] pos position of command to replace (0-based)
+ * @param[in] cmds commands to insert */
+ void replace (int pos, const std::vector<CommandVariant> &cmds) {
+ auto it = _commands.end();
+ if (!_commands.empty()) {
+ it = _commands.begin()+pos;
+ it = _commands.erase(it);
}
- case Command::Type::CUBICTO: {
- // check if first control point is the reflection of the preceding second control point?
- Point diff = abs(params[0]-pstore[1]*T(2)+pstore[0]);
- bool isReflection = diff.x() < eps && diff.y() < eps;
- // is first control point reflection of preceding second control point?
- if (optimize && prev != _commands.end() && prev->type == Command::Type::CUBICTO && isReflection) {
- actions.cubicto(params[1], params[2]);
- actions.draw('S', params+1, 2);
- }
- else {
- actions.cubicto(params[0], params[1], params[2]);
- actions.draw('C', params, 3);
- }
- pstore[0] = params[1]; // store second control point and
- pstore[1] = params[2]; // curve endpoint
- break;
+ _commands.insert(it, cmds.begin(), cmds.end());
+ }
+
+ /** Iterates over all commands of the path and calls the corresponding template methods.
+ * In contrast to the public iterate() method, this one allows to modify the command sequence.
+ * @param[in] actions template methods called by each iteration step */
+ void iterate (ModificationActions &actions) {
+ IterationVisitor visitor(actions, false);
+ // no iterators here since they may be invalidated during path modifications
+ for (size_t i=0; i < _commands.size(); i++) {
+ if (actions.quit())
+ break;
+ actions._commandPos = i;
+ mpark::visit(visitor, _commands[i]);
+ visitor.setPrevCommand(_commands[i]);
}
- case Command::Type::CLOSEPATH:
- actions.closepath();
- actions.draw('Z', params, 0);
- cp = fp;
- }
- // update current point
- const int np = it->numParams();
- if (np > 0)
- cp = it->params[np-1];
- prev = it;
- }
- actions.finished();
-}
+ actions.finished();
+ }
-#endif
+ private:
+ std::deque<CommandVariant> _commands; ///< sequence of path commands
+ WindingRule _windingRule;
+ Point _startPoint; ///< start point of final sub-path
+ Point _finalPoint; ///< final point reached by last command in path
+};