diff options
Diffstat (limited to 'dviware/dvisvgm/src')
76 files changed, 1663 insertions, 957 deletions
diff --git a/dviware/dvisvgm/src/BasicDVIReader.cpp b/dviware/dvisvgm/src/BasicDVIReader.cpp index 350e130166..272c88c3ad 100644 --- a/dviware/dvisvgm/src/BasicDVIReader.cpp +++ b/dviware/dvisvgm/src/BasicDVIReader.cpp @@ -19,7 +19,6 @@ *************************************************************************/ #include <algorithm> -#include <sstream> #include "BasicDVIReader.hpp" using namespace std; @@ -100,11 +99,8 @@ int BasicDVIReader::evalCommand (CommandHandler &handler, int ¶m) { handler = &BasicDVIReader::cmdDir; num_param_bytes = 1; } - else if (opcode > OP_POSTPOST) { - ostringstream oss; - oss << "undefined DVI command (opcode " << opcode << ')'; - throw DVIException(oss.str()); - } + else if (opcode > OP_POSTPOST) + throw DVIException("undefined DVI command (opcode " + to_string(opcode) + ")"); else { const int offset = opcode < OP_FNTNUM0 ? OP_SET1 : (OP_FNTNUM63+1)-(OP_FNTNUM0-OP_SET1); handler = commands[opcode-offset].handler; @@ -194,9 +190,7 @@ void BasicDVIReader::setDVIVersion (DVIVersion version) { case DVI_XDV7: break; default: - ostringstream oss; - oss << "DVI version " << _dviVersion << " not supported"; - throw DVIException(oss.str()); + throw DVIException("DVI version " + to_string(_dviVersion) + " not supported"); } } diff --git a/dviware/dvisvgm/src/Bezier.cpp b/dviware/dvisvgm/src/Bezier.cpp index 70656d14f9..d2077bcfeb 100644 --- a/dviware/dvisvgm/src/Bezier.cpp +++ b/dviware/dvisvgm/src/Bezier.cpp @@ -21,6 +21,7 @@ #include <algorithm> #include <utility> #include "Bezier.hpp" +#include "Matrix.hpp" using namespace std; @@ -230,8 +231,8 @@ static bool solve_quadratic_equation (double a, double b, double c, double &x1, /** Returns a tight bounding box parallel to the x- and y-axis. */ -void Bezier::getBBox (BoundingBox &bbox) const { - bbox.invalidate(); +BoundingBox Bezier::getBBox () const { + BoundingBox bbox; // coefficients of the derivative DPair pa = _points[3] - _points[2]*3.0 + _points[1]*3.0 - _points[0]; DPair pb = (_points[2]-_points[1]*2.0+_points[0])*2.0; @@ -253,4 +254,12 @@ void Bezier::getBBox (BoundingBox &bbox) const { } bbox.embed(_points[0]); bbox.embed(_points[3]); + return bbox; +} + + +Bezier& Bezier::transform (const Matrix &matrix) { + for (int i=0; i < 4; i++) + _points[i] = matrix*_points[i]; + return *this; } diff --git a/dviware/dvisvgm/src/Bezier.hpp b/dviware/dvisvgm/src/Bezier.hpp index f4f93dcf2f..525ef23435 100644 --- a/dviware/dvisvgm/src/Bezier.hpp +++ b/dviware/dvisvgm/src/Bezier.hpp @@ -25,8 +25,9 @@ #include "BoundingBox.hpp" #include "Pair.hpp" -class Bezier -{ +class Matrix; + +class Bezier { public: Bezier (); Bezier (const DPair &p0, const DPair &p1, const DPair &p2); @@ -37,10 +38,11 @@ class Bezier DPair valueAt (double t) const; DPair blossomValue (double u, double v, double w) const; void subdivide (double t, Bezier *bezier1, Bezier *bezier2) const; + Bezier& transform (const Matrix &matrix); int approximate (double delta, std::vector<DPair> &p, std::vector<double> *t=nullptr) const; const DPair& point (int i) const {return _points[i];} int reduceDegree (double delta, std::vector<DPair> &p) const; - void getBBox (BoundingBox &bbox) const; + BoundingBox getBBox () const; protected: int approximate (double delta, double t0, double t1, std::vector<DPair> &p, std::vector<double> *t) const; diff --git a/dviware/dvisvgm/src/Bitmap.cpp b/dviware/dvisvgm/src/Bitmap.cpp index e39ad79c47..3fa554eed4 100644 --- a/dviware/dvisvgm/src/Bitmap.cpp +++ b/dviware/dvisvgm/src/Bitmap.cpp @@ -25,10 +25,6 @@ using namespace std; -Bitmap::Bitmap () : _rows(0), _cols(0), _xshift(0), _yshift(0), _bpr(0), _bytes(0) -{ -} - /** Constructs a Bitmap */ Bitmap::Bitmap (int minx, int maxx, int miny , int maxy) { @@ -48,8 +44,7 @@ void Bitmap::resize (int minx, int maxx, int miny , int maxy) { _yshift = miny; _bpr = _cols/8 + (_cols % 8 ? 1 : 0); // bytes per row _bytes.resize(_rows*_bpr); - for (uint8_t &byte : _bytes) - byte = 0; + std::fill(_bytes.begin(), _bytes.end(), 0); } @@ -69,7 +64,7 @@ void Bitmap::setBits (int row, int col, int n) { int m = min(n, b+1); // number of bits to be set in current byte int bitseq = (1 << m)-1; // sequence of n set bits (bits 0..n-1 are set) bitseq <<= b-m+1; // move bit sequence so that bit b is the leftmost set bit - *byte |= uint8_t(bitseq); // apply bit sequence to current byte + *byte |= uint8_t(bitseq); // apply bit sequence to current byte byte++; n -= m; col += m; @@ -90,10 +85,8 @@ void Bitmap::forAllPixels (Callback &data) const { } -class BBoxCallback : public Bitmap::Callback -{ +class BBoxCallback : public Bitmap::Callback { public: - BBoxCallback () : _changed(false), _minx(numeric_limits<int>::max()), _miny(_minx), _maxx(0), _maxy(0) {} int minx () const {return _minx;} int miny () const {return _miny;} int maxx () const {return _maxx;} @@ -116,8 +109,9 @@ class BBoxCallback : public Bitmap::Callback } private: - bool _changed; - int _minx, _miny, _maxx, _maxy; + bool _changed = false; + int _minx = numeric_limits<int>::max(), _miny=_minx; + int _maxx = 0, _maxy = 0; }; diff --git a/dviware/dvisvgm/src/Bitmap.hpp b/dviware/dvisvgm/src/Bitmap.hpp index fe9f31f053..026edae9a2 100644 --- a/dviware/dvisvgm/src/Bitmap.hpp +++ b/dviware/dvisvgm/src/Bitmap.hpp @@ -25,8 +25,7 @@ #include <vector> -class Bitmap -{ +class Bitmap { public: struct Callback { virtual ~Callback() =default; @@ -36,7 +35,7 @@ class Bitmap }; public: - Bitmap (); + Bitmap () =default; Bitmap (int minx, int maxx, int miny , int maxy); void resize (int minx, int maxx, int miny , int maxy); void setBits(int row, int col, int n); @@ -60,9 +59,9 @@ class Bitmap // std::ostream& write (std::ostream &os) const; private: - int _rows, _cols; ///< number of rows, columns - int _xshift, _yshift; ///< horizontal/vertical shift - int _bpr; ///< number of bytes per row + int _rows=0, _cols=0; ///< number of rows, columns + int _xshift=0, _yshift=0; ///< horizontal/vertical shift + int _bpr=0; ///< number of bytes per row std::vector<uint8_t> _bytes; }; @@ -81,7 +80,7 @@ int Bitmap::copy (std::vector<T> &target, bool vflip) const { int targetrow = vflip ? _rows-r-1 : r; for (int b=0; b < _bpr; b++) { T &t = target[targetrow*tpr + b/s]; - T chunk = (T)_bytes[r*_bpr+b] << (8*(s-1-b%s)); + T chunk = static_cast<T>(_bytes[r*_bpr+b]) << (8*(s-1-b%s)); if (b % s == 0) t = chunk; else @@ -107,5 +106,4 @@ void Bitmap::write (std::ostream &os, const std::vector<T> &v) const { } }*/ - #endif diff --git a/dviware/dvisvgm/src/CMapManager.cpp b/dviware/dvisvgm/src/CMapManager.cpp index 24b5b69f24..779c6ee3a8 100644 --- a/dviware/dvisvgm/src/CMapManager.cpp +++ b/dviware/dvisvgm/src/CMapManager.cpp @@ -44,9 +44,7 @@ CMap* CMapManager::lookup (const string &name) { if (_includedCMaps.find(name) != _includedCMaps.end()) { _level = 0; - ostringstream oss; - oss << "circular reference of CMap " << name; - throw CMapReaderException(oss.str()); + throw CMapReaderException("circular reference of CMap " + name); } unique_ptr<CMap> cmap_ptr; diff --git a/dviware/dvisvgm/src/CMapReader.cpp b/dviware/dvisvgm/src/CMapReader.cpp index 08450cf17f..f873bded0a 100644 --- a/dviware/dvisvgm/src/CMapReader.cpp +++ b/dviware/dvisvgm/src/CMapReader.cpp @@ -89,9 +89,10 @@ void CMapReader::executeOperator (const string &opname, InputReader &ir) { const char *name; void (CMapReader::*handler)(InputReader&); }; - constexpr array<Operator, 6> operators {{ + constexpr array<Operator, 7> operators {{ {"beginbfchar", &CMapReader::op_beginbfchar}, {"beginbfrange", &CMapReader::op_beginbfrange}, + {"begincidchar", &CMapReader::op_begincidchar}, {"begincidrange", &CMapReader::op_begincidrange}, {"def", &CMapReader::op_def}, {"endcmap", &CMapReader::op_endcmap}, @@ -154,6 +155,22 @@ static uint32_t parse_hexentry (InputReader &ir) { } +void CMapReader::op_begincidchar (InputReader &ir) { + if (!_tokens.empty() && _tokens.back().type() == Token::Type::NUMBER) { + ir.skipSpace(); + int num_entries = static_cast<int>(popToken().numvalue()); + while (num_entries > 0 && ir.peek() == '<') { + uint32_t first = parse_hexentry(ir); + uint32_t cid; + ir.skipSpace(); + if (!ir.parseUInt(cid)) + throw CMapReaderException("invalid char entry (decimal value expected)"); + _cmap->addCIDRange(first, first, cid); + ir.skipSpace(); + } + } +} + void CMapReader::op_begincidrange (InputReader &ir) { if (!_tokens.empty() && _tokens.back().type() == Token::Type::NUMBER) { ir.skipSpace(); @@ -249,9 +266,7 @@ void CMapReader::Token::scan (InputReader &ir) { else if (isdigit(ir.peek())) { // number? double val; if (ir.parseDouble(val)) { - ostringstream oss; - oss << val; - _value = oss.str(); + _value = util::to_string(val); _type = Type::NUMBER; } } diff --git a/dviware/dvisvgm/src/CMapReader.hpp b/dviware/dvisvgm/src/CMapReader.hpp index 820882c04d..b39ca9874c 100644 --- a/dviware/dvisvgm/src/CMapReader.hpp +++ b/dviware/dvisvgm/src/CMapReader.hpp @@ -59,6 +59,7 @@ class CMapReader { void executeOperator (const std::string &op, InputReader &ir); void op_beginbfchar (InputReader &ir); void op_beginbfrange (InputReader &ir); + void op_begincidchar (InputReader &ir); void op_begincidrange (InputReader &ir); void op_def (InputReader &ir); void op_endcmap (InputReader &ir); diff --git a/dviware/dvisvgm/src/CRC32.cpp b/dviware/dvisvgm/src/CRC32.cpp deleted file mode 100644 index 583b019324..0000000000 --- a/dviware/dvisvgm/src/CRC32.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/************************************************************************* -** CRC32.cpp ** -** ** -** This file is part of dvisvgm -- a fast DVI to SVG converter ** -** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> ** -** ** -** This program is free software; you can redistribute it and/or ** -** modify it under the terms of the GNU General Public License as ** -** published by the Free Software Foundation; either version 3 of ** -** the License, or (at your option) any later version. ** -** ** -** This program is distributed in the hope that it will be useful, but ** -** WITHOUT ANY WARRANTY; without even the implied warranty of ** -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** -** GNU General Public License for more details. ** -** ** -** You should have received a copy of the GNU General Public License ** -** along with this program; if not, see <http://www.gnu.org/licenses/>. ** -*************************************************************************/ - -#include <cstring> -#include "CRC32.hpp" - -using namespace std; - - -CRC32::CRC32 () : _crc32(0xFFFFFFFF) -{ - const uint32_t poly = 0xEDB88320; - for (int i = 0; i < 256; i++) { - uint32_t crc=i; - for (int j=8; j > 0; j--) { - if (crc & 1) - crc = (crc >> 1) ^ poly; - else - crc >>= 1; - } - _tab[i] = crc; - } -} - - -/** Resets CRC32 sum to 0. */ -void CRC32::reset () { - _crc32 = 0xFFFFFFFF; -} - - -/** Appends string bytes to the previous data and computes the resulting checksum. - * @param[in] data string to update the checksum with */ -void CRC32::update (const char *data) { - update((const uint8_t*)data, strlen(data)); -} - - -/** Appends a single value to the previous data and computes the resulting checksum. - * @param[in] n value to update the checksum with - * @param[in] bytes number of bytes to consider (0-4) */ -void CRC32::update (uint32_t n, int bytes) { - for (int i=bytes-1; i >= 0; --i) { - uint8_t byte = uint8_t((n >> (8*i)) & 0xff); - update(&byte, 1); - } -} - - -/** Appends a sequence of bytes to the previous data and computes the resulting checksum. - * @param[in] bytes pointer to array of bytes - * @param[in] len number of bytes in array */ -void CRC32::update (const uint8_t *bytes, size_t len) { - for (size_t i=0; i < len; ++i) - _crc32 = ((_crc32 >> 8) & 0x00FFFFFF) ^ _tab[(_crc32 ^ *bytes++) & 0xFF]; -} - - -void CRC32::update (istream &is) { - char buf [4096]; - while (is) { - is.read(buf, 4096); - update((uint8_t*)buf, is.gcount()); - } -} - - -/** Returns the checksum computed from values added with the update functions. */ -uint32_t CRC32::get () const { - return _crc32 ^ 0xFFFFFFFF; -} - - -/** Computes the CRC32 checksum of a sequence of bytes. - * @param[in] bytes pointer to array of bytes - * @param[in] len number of bytes in array - * @return CRC32 checksum */ -uint32_t CRC32::compute (const uint8_t *bytes, size_t len) { - CRC32 crc32; - crc32.update(bytes, len); - return crc32.get(); -} - - -/** Computes the CRC32 checksum of a string. */ -uint32_t CRC32::compute (const char *str) { - return compute((const uint8_t*)str, strlen(str)); -} - - -uint32_t CRC32::compute (istream &is) { - CRC32 crc32; - crc32.update(is); - return crc32.get(); -} diff --git a/dviware/dvisvgm/src/CRC32.hpp b/dviware/dvisvgm/src/CRC32.hpp deleted file mode 100644 index 880f8b737e..0000000000 --- a/dviware/dvisvgm/src/CRC32.hpp +++ /dev/null @@ -1,47 +0,0 @@ -/************************************************************************* -** CRC32.hpp ** -** ** -** This file is part of dvisvgm -- a fast DVI to SVG converter ** -** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> ** -** ** -** This program is free software; you can redistribute it and/or ** -** modify it under the terms of the GNU General Public License as ** -** published by the Free Software Foundation; either version 3 of ** -** the License, or (at your option) any later version. ** -** ** -** This program is distributed in the hope that it will be useful, but ** -** WITHOUT ANY WARRANTY; without even the implied warranty of ** -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** -** GNU General Public License for more details. ** -** ** -** You should have received a copy of the GNU General Public License ** -** along with this program; if not, see <http://www.gnu.org/licenses/>. ** -*************************************************************************/ - -#ifndef CRC32_HPP -#define CRC32_HPP - -#include <cstdlib> -#include <istream> - -class CRC32 { - public: - CRC32 (); - CRC32 (const CRC32 &crc32) =delete; - void update (const uint8_t *bytes, size_t len); - void update (uint32_t n, int bytes=4); - void update (const char *str); - void update (std::istream &is); - uint32_t get () const; - void reset (); - static uint32_t compute (const uint8_t *bytes, size_t len); - static uint32_t compute (const char *str); - static uint32_t compute (std::istream &is); - - private: - uint32_t _crc32; - uint32_t _tab[256]; -}; - - -#endif diff --git a/dviware/dvisvgm/src/Color.cpp b/dviware/dvisvgm/src/Color.cpp index 949fcba56c..de7e9074ab 100644 --- a/dviware/dvisvgm/src/Color.cpp +++ b/dviware/dvisvgm/src/Color.cpp @@ -40,7 +40,7 @@ const Color Color::TRANSPARENT(uint32_t(0xff000000)); static inline uint8_t double_to_byte (double v) { v = max(0.0, min(1.0, v)); - return uint8_t(floor(255*v+0.5)); + return uint8_t(round(255.0*v)); } @@ -632,14 +632,14 @@ double Color::deltaE (const Color &c) const { c.getLab(l2, a2, b2); const double deltaL = l2-l1; const double lBar = (l1+l2)/2; - const double c1 = sqrt(a1*a1 + b1*b1); - const double c2 = sqrt(a2*a2 + b2*b2); + const double c1 = hypot(a1, b1); + const double c2 = hypot(a2, b2); const double cBar = (c1+c2)/2.0; const double g = (1.0-sqrt(pow(cBar, 7.0)/(pow(cBar, 7.0)+6103515625.0)))/2.0; const double aa1 = a1*(1.0+g); const double aa2 = a2*(1.0+g); - const double cc1 = sqrt(aa1*aa1 + b1*b1); - const double cc2 = sqrt(aa2*aa2 + b2*b2); + const double cc1 = hypot(aa1, b1); + const double cc2 = hypot(aa2, b2); const double ccBar = (cc1+cc2)/2.0; const double deltaCC = cc2-cc1; double hh1 = atan2(b1, aa1); diff --git a/dviware/dvisvgm/src/DVIReader.cpp b/dviware/dvisvgm/src/DVIReader.cpp index 38862839a0..b0bbabee94 100644 --- a/dviware/dvisvgm/src/DVIReader.cpp +++ b/dviware/dvisvgm/src/DVIReader.cpp @@ -457,11 +457,8 @@ void DVIReader::cmdDir (int) { uint8_t wmode = readUnsigned(1); if (wmode == 4) // yoko mode (4) equals default LR mode (0) wmode = 0; - if (wmode == 2 || wmode > 3) { - ostringstream oss; - oss << "invalid writing mode value " << wmode << " (0, 1, 3, or 4 expected)"; - throw DVIException(oss.str()); - } + if (wmode == 2 || wmode > 3) + throw DVIException("invalid writing mode value " + std::to_string(wmode) + " (0, 1, 3, or 4 expected)"); _dviState.d = (WritingMode)wmode; dviDir(_dviState.d); } @@ -485,11 +482,8 @@ void DVIReader::setFont (int fontnum, SetFontMode mode) { _currFontNum = fontnum; dviFontNum(uint32_t(fontnum), mode, font); } - else { - ostringstream oss; - oss << "undefined font number " << fontnum; - throw DVIException(oss.str()); - } + else + throw DVIException("undefined font number " + std::to_string(fontnum)); } diff --git a/dviware/dvisvgm/src/DVIToSVG.cpp b/dviware/dvisvgm/src/DVIToSVG.cpp index 4911dfe321..4801bd80a0 100644 --- a/dviware/dvisvgm/src/DVIToSVG.cpp +++ b/dviware/dvisvgm/src/DVIToSVG.cpp @@ -111,11 +111,11 @@ void DVIToSVG::convert (unsigned first, unsigned last, HashFunction *hashFunc) { combinedHash = hashFunc->digestString(); } const SVGOutput::HashTriple hashTriple(dviHash, shortenedOptHash, combinedHash); - string fname = _out.filename(i, numberOfPages(), hashTriple); - if (!dviHash.empty() && !PAGE_HASH_SETTINGS.isSet(HashSettings::P_REPLACE) && FileSystem::exists(fname)) { + FilePath path = _out.filepath(i, numberOfPages(), hashTriple); + if (!dviHash.empty() && !PAGE_HASH_SETTINGS.isSet(HashSettings::P_REPLACE) && path.exists()) { Message::mstream(false, Message::MC_PAGE_NUMBER) << "skipping page " << i; Message::mstream().indent(1); - Message::mstream(false, Message::MC_PAGE_WRITTEN) << "\nfile " << fname << " exists\n"; + Message::mstream(false, Message::MC_PAGE_WRITTEN) << "\nfile " << path.shorterAbsoluteOrRelative() << " exists\n"; Message::mstream().indent(0); } else { @@ -123,6 +123,7 @@ void DVIToSVG::convert (unsigned first, unsigned last, HashFunction *hashFunc) { SVGOptimizer(_svg).execute(); embedFonts(_svg.rootNode()); bool success = _svg.write(_out.getPageStream(currentPageNumber(), numberOfPages(), hashTriple)); + string fname = path.shorterAbsoluteOrRelative(); if (fname.empty()) fname = "<stdout>"; if (success) @@ -332,12 +333,16 @@ Matrix DVIToSVG::getPageTransformation () const { static void collect_chars (unordered_map<const Font*, set<int>> &fontmap) { + unordered_map<const Font*, set<int>> insertedChars; for (const auto &entry : fontmap) { - if (entry.first->uniqueFont() != entry.first) { + const Font *unique_font = entry.first->uniqueFont(); + if (unique_font != entry.first) { for (int c : entry.second) - fontmap[entry.first->uniqueFont()].insert(c); + insertedChars[unique_font].insert(c); } } + for (const auto &entry : insertedChars) + fontmap[entry.first].insert(entry.second.begin(), entry.second.end()); } @@ -421,8 +426,8 @@ void DVIToSVG::setProcessSpecials (const char *ignorelist, bool pswarning) { } -string DVIToSVG::getSVGFilename (unsigned pageno) const { - return _out.filename(pageno, numberOfPages()); +FilePath DVIToSVG::getSVGFilePath (unsigned pageno) const { + return _out.filepath(pageno, numberOfPages()); } @@ -568,6 +573,15 @@ void DVIToSVG::HashSettings::setParameters (const string ¶mstr) { _params.insert(it->second); else if (_algo.empty() && HashFunction::isSupportedAlgorithm(name)) _algo = name; + else if (!name.empty()) { + string msg = "invalid hash parameter '"+name+"' (supported algorithms: "; + for (string str : HashFunction::supportedAlgorithms()) + msg += str + ", "; + msg.pop_back(); + msg.pop_back(); + msg += ')'; + throw MessageException(msg); + } } // set default hash algorithm if none is given if (_algo.empty()) diff --git a/dviware/dvisvgm/src/DVIToSVG.hpp b/dviware/dvisvgm/src/DVIToSVG.hpp index 9463f78736..40dbdc884f 100644 --- a/dviware/dvisvgm/src/DVIToSVG.hpp +++ b/dviware/dvisvgm/src/DVIToSVG.hpp @@ -25,6 +25,7 @@ #include <string> #include <utility> #include "DVIReader.hpp" +#include "FilePath.hpp" #include "SVGTree.hpp" struct DVIActions; @@ -62,7 +63,7 @@ class DVIToSVG : public DVIReader { void finishLine () override {_prevYPos = std::numeric_limits<double>::min();} void listHashes (const std::string &rangestr, std::ostream &os); - std::string getSVGFilename (unsigned pageno) const; + FilePath getSVGFilePath (unsigned pageno) const; std::string getUserBBoxString () const {return _bboxFormatString;} static void setProcessSpecials (const char *ignorelist=nullptr, bool pswarning=false); diff --git a/dviware/dvisvgm/src/DVIToSVGActions.cpp b/dviware/dvisvgm/src/DVIToSVGActions.cpp index 6448fc2d42..3d53d1bd4d 100644 --- a/dviware/dvisvgm/src/DVIToSVGActions.cpp +++ b/dviware/dvisvgm/src/DVIToSVGActions.cpp @@ -69,17 +69,19 @@ void DVIToSVGActions::moveToY (double y, bool forceSVGMove) { } -string DVIToSVGActions::getSVGFilename (unsigned pageno) const { +FilePath DVIToSVGActions::getSVGFilePath (unsigned pageno) const { + FilePath path; if (auto dvi2svg = dynamic_cast<DVIToSVG*>(_dvireader)) - return dvi2svg->getSVGFilename(pageno); - return ""; + path = dvi2svg->getSVGFilePath(pageno); + return path; } string DVIToSVGActions::getBBoxFormatString () const { + string boxstr; if (auto dvi2svg = dynamic_cast<DVIToSVG*>(_dvireader)) - return dvi2svg->getUserBBoxString(); - return ""; + boxstr = dvi2svg->getUserBBoxString(); + return boxstr; } diff --git a/dviware/dvisvgm/src/DVIToSVGActions.hpp b/dviware/dvisvgm/src/DVIToSVGActions.hpp index e1292a5efb..88fa6028ee 100644 --- a/dviware/dvisvgm/src/DVIToSVGActions.hpp +++ b/dviware/dvisvgm/src/DVIToSVGActions.hpp @@ -73,7 +73,7 @@ class DVIToSVGActions : public DVIActions, public SpecialActions { BoundingBox& bbox (const std::string &name, bool reset=false) override; void embed (const BoundingBox &bbox) override; void embed (const DPair &p, double r=0) override; - std::string getSVGFilename (unsigned pageno) const override; + FilePath getSVGFilePath (unsigned pageno) const override; std::string getBBoxFormatString () const override; CharMap& getUsedChars () const {return _usedChars;} const FontSet& getUsedFonts () const {return _usedFonts;} diff --git a/dviware/dvisvgm/src/EllipticalArc.cpp b/dviware/dvisvgm/src/EllipticalArc.cpp new file mode 100644 index 0000000000..b820af0052 --- /dev/null +++ b/dviware/dvisvgm/src/EllipticalArc.cpp @@ -0,0 +1,260 @@ +/************************************************************************* +** EllipticalArc.cpp ** +** ** +** This file is part of dvisvgm -- a fast DVI to SVG converter ** +** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> ** +** ** +** This program is free software; you can redistribute it and/or ** +** modify it under the terms of the GNU General Public License as ** +** published by the Free Software Foundation; either version 3 of ** +** the License, or (at your option) any later version. ** +** ** +** This program is distributed in the hope that it will be useful, but ** +** WITHOUT ANY WARRANTY; without even the implied warranty of ** +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** +** GNU General Public License for more details. ** +** ** +** You should have received a copy of the GNU General Public License ** +** along with this program; if not, see <http://www.gnu.org/licenses/>. ** +*************************************************************************/ + +#include <cmath> +#include "EllipticalArc.hpp" +#include "utility.hpp" + +using namespace std; + + +/** Constructs an elliptical arc from end point parameterization. + * @param[in] start start point of arc + * @param[in] rx length of semi-major axis + * @param[in] ry length of semi-minor axis + * @param[in] angle rotation of ellipse around its center (in radians) + * @param[in] laf if true, choose the larger arc between start and end point + * @param[in] sweep if true, arc is drawn in the direction of increasing angles + * @param[in] end end point of the arc */ +EllipticalArc::EllipticalArc (const DPair &start, double rx, double ry, double angle, bool laf, bool sweep, const DPair &end) + : _rx(abs(rx)), _ry(abs(ry)), _rotationAngle(math::normalize_angle(angle, math::PI)), + _largeArc(laf), _sweepPositive(sweep), _startPoint(start), _endPoint(end) +{ + if (!isStraightLine()) { + // fix out-of-range radii according to section F.6.6.3 in + // https://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii + double c = cos(_rotationAngle); + double s = sin(_rotationAngle); + DPair p = (_startPoint-_endPoint)/2.0; + p = DPair(c*p.x()+s*p.y(), c*p.y()-s*p.x()); + double lambda = (p.x()*p.x())/(_rx*_rx) + (p.y()*p.y())/(_ry*_ry); + if (lambda > 1) { + lambda = sqrt(lambda); + _rx *= lambda; + _ry *= lambda; + } + } +} + + +/** Constructs an elliptical arc from center parameterization + * @param[in] center absolute coordinates of the center of the ellipse + * @param[in] rx length of semi-major axis + * @param[in] ry length of semi-minor axis + * @param[in] rot rotation of ellipse around its center (in radians) + * @param[in] startAngle angle between major axis and vector from center to start point + * @param[in] deltaAngle angle between the vectors from center to start and end point, respectively */ +EllipticalArc::EllipticalArc (const DPair ¢er, double rx, double ry, double rot, double startAngle, double deltaAngle) + : _rx(rx), _ry(ry), _rotationAngle(math::normalize_angle(rot, math::TWO_PI)), + _largeArc(abs(deltaAngle) > math::PI), _sweepPositive(deltaAngle > 0) +{ + // https://www.w3.org/TR/SVG/implnote.html#ArcConversionCenterToEndpoint + double c = cos(_rotationAngle); + double s = sin(_rotationAngle); + double c1 = cos(startAngle); + double s1 = sin(startAngle); + double c2 = cos(startAngle+deltaAngle); + double s2 = sin(startAngle+deltaAngle); + _startPoint = DPair(c*rx*c1*c - s*ry*s1, s*rx*c1 + c*ry*s1) + center; + _endPoint = DPair(c*rx*c2*c - s*ry*s2, s*rx*c2 + c*ry*s2) + center; +} + + +/** Returns the angle between (1, 0) and a given vector. + * The angle is normalized to the range [0, 2pi). */ +static inline double angle (const DPair &p) { + return math::normalize_0_2pi(atan2(p.y(), p.x())); +} + + +/** Computes the center parameterization of the arc. */ +EllipticalArc::CenterParams EllipticalArc::getCenterParams () const { + EllipticalArc::CenterParams params; + if (isStraightLine()) { + params.center = (_endPoint-_startPoint)/2.0; + params.startAngle = params.deltaAngle = 0; + } + else { + // https://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter + double c = cos(_rotationAngle); + double s = sin(_rotationAngle); + DPair p = (_startPoint-_endPoint)/2.0; + p = DPair(c*p.x()+s*p.y(), c*p.y()-s*p.x()); + double rx2 = _rx*_rx, ry2 = _ry*_ry; + double px2 = p.x()*p.x(), py2 = p.y()*p.y(); + double radicand = rx2*ry2 - rx2*py2 - ry2*px2; + if (radicand < 0) // should not happen if out-of-range radii were fixed correctly + radicand = 0; + else + radicand /= rx2*py2 + ry2*px2; + double root = sqrt(radicand) * (_largeArc == _sweepPositive ? -1 : 1); + DPair cp(root*p.y()*_rx/_ry, -root*p.x()*_ry/_rx); + DPair mid = (_startPoint+_endPoint)/2.0; + params.center = DPair(c*cp.x() - s*cp.y() + mid.x(), s*cp.x() + c*cp.y() + mid.y()); + DPair q1((p.x() - cp.x())/_rx, (p.y() - cp.y())/_ry); + DPair q2(-(p.x() + cp.x())/_rx, -(p.y() + cp.y())/_ry); + params.startAngle = angle(q1); + params.deltaAngle = angle(q2) - params.startAngle; + if (_sweepPositive && params.deltaAngle < 0) + params.deltaAngle += math::TWO_PI; + else if (!_sweepPositive && params.deltaAngle > 0) + params.deltaAngle -= math::TWO_PI; + } + return params; +} + + +/** Applies the affine transformation described by a given matrix to the arc. */ +void EllipticalArc::transform (const Matrix &matrix) { + double c = cos(_rotationAngle); + double s = sin(_rotationAngle); + Matrix ellipse({_rx*c, -_ry*s, 0, _rx*s, _ry*c}); // E := rotate(xrot)*scale(rx, ry) + ellipse.lmultiply(matrix); // E':= M*E + // Compute the singular value decomposition of the transformed ellipse shape: + // E' = rotate(phi)*scale(sx, sy)*rotate(theta) + // The initial, right-hand rotation can be ignored because it rotates the unit circle + // around the origin, i.e. rotate(theta) maps the circle to itself. + // The signs of sx and sy don't matter either. They just flip the yet unrotated + // ellipse on the x- and/or y-axis. Thus, |sx| and |sy| are the new radii, + // and phi the new rotation angle. + auto vec = math::svd({{ellipse.get(0,0), ellipse.get(0,1)}, {ellipse.get(1,0), ellipse.get(1,1)}}); + if (std::abs(vec[1]-vec[2]) < 1e-7) { // circle? + _rx = _ry = vec[1]; // always >= 0 + _rotationAngle = 0; + } + else { + _rx = vec[1]; // always >= 0 + _ry = abs(vec[2]); // ensure >= 0 + _rotationAngle = math::normalize_angle(vec[0], math::HALF_PI); + } + // change drawing direction (clockwise vs. counter-clockwise) if 'matrix' + // flipped the ellipse horizontally or vertically but not both + if ((matrix.get(0, 0) < 0) != (matrix.get(1, 1) < 0)) + _sweepPositive = !_sweepPositive; + _startPoint = matrix * _startPoint; + _endPoint = matrix * _endPoint; +} + + +/** Approximates an arc of the unit circle by a single cubic Bézier curve. + * @param[in] phi start angle of the arc in radians + * @param[in] delta length of the arc */ +static Bezier approx_unit_arc (double phi, double delta) { + double c = 0.551915024494; // see http://spencermortensen.com/articles/bezier-circle + if (abs(delta + math::HALF_PI) < 1e-7) + c = -c; + else + c = 4.0/3*tan(delta/4); + DPair p1(cos(phi), sin(phi)); + DPair p4(cos(phi+delta), sin(phi+delta)); + DPair p2(p1.x()-c*p1.y(), p1.y()+c*p1.x()); + DPair p3(p4.x()+c*p4.y(), p4.y()-c*p4.x()); + return Bezier(p1, p2, p3, p4); +} + + +/** Approximates the arc by a sequence of cubic Bézier curves. */ +vector<Bezier> EllipticalArc::approximate () const { + vector<Bezier> beziers; + if (_startPoint != _endPoint) { + if (isStraightLine()) { + DPair dir = (_endPoint - _startPoint); + dir /= dir.length()/3.0; + beziers.emplace_back(Bezier(_startPoint, _startPoint+dir, _endPoint-dir, _endPoint)); + } + else { + CenterParams cparams = getCenterParams(); + int numCurves = ceil(cparams.deltaAngle/math::HALF_PI); + double remainder = abs(fmod(cparams.deltaAngle, math::HALF_PI)); + if (remainder < 1e-7) + numCurves--; + else if (math::HALF_PI-remainder < 1e-7) + numCurves++; + if (numCurves > 0) { + double c = cos(_rotationAngle); + double s = sin(_rotationAngle); + Matrix ellipse = {_rx*c, -_ry*s, cparams.center.x(), _rx*s, _ry*c, cparams.center.y()}; + double angle = cparams.startAngle; + double diff = cparams.deltaAngle/numCurves; + while (numCurves-- > 0) { + beziers.emplace_back(approx_unit_arc(angle, diff).transform(ellipse)); + angle += diff; + } + } + } + } + return beziers; +} + + +static inline bool is_angle_between (double t, double angle1, double angle2) { + if (angle1 < angle2) + return angle1 < t && t < angle2; + return angle2 > t || t > angle1; +} + + +/** Returns the tight bounding box of the arc. */ +BoundingBox EllipticalArc::getBBox () const { + BoundingBox bbox; + bbox.embed(_startPoint); + bbox.embed(_endPoint); + if (!isStraightLine()) { + // compute extremes of ellipse centered at the origin + double c = cos(_rotationAngle); + double s = sin(_rotationAngle); + double tx1 = math::normalize_0_2pi(-atan2(_ry*s, _rx*c)); // position of vertical tangent, d/dt E(tx1)=(0, y) + double tx2 = math::normalize_0_2pi(math::PI+tx1); // position of second vertical tangent + double ct = cos(tx1); + double st = sin(tx1); + DPair pv1(_rx*c*ct - _ry*s*st, _rx*s*ct + _ry*c*st); // E(tx1), 1st point on ellipse with vertical tangent + DPair pv2 = -pv1; // E(tx2), 2nd point on ellipse with vertical tangent + + double ty1 = math::normalize_0_2pi(atan2(_ry*c, _rx*s)); // position of horizontal tangent, d/dt E(ty1)=(x, 0) + double ty2 = math::normalize_0_2pi(math::PI+ty1); // position of second horizontal tangent + ct = cos(ty1); + st = sin(ty1); + DPair ph1(_rx*c*ct - _ry*s*st, _rx*s*ct + _ry*c*st); // E(ty1), 1st point on ellipse with horizontal tangent + DPair ph2 = -ph1; // E(ty2), 2nd point on ellipse with horizontal tangent + + // translate extreme points to actual coordinates + CenterParams cparams = getCenterParams(); + pv1 += cparams.center; + pv2 += cparams.center; + ph1 += cparams.center; + ph2 += cparams.center; + + double angle1 = cparams.startAngle; + double angle2 = math::normalize_0_2pi(angle1+cparams.deltaAngle); + if (!_sweepPositive) + swap(angle1, angle2); + + // only consider extreme points located on the arc + if (is_angle_between(tx1, angle1, angle2)) + bbox.embed(pv1); + if (is_angle_between(tx2, angle1, angle2)) + bbox.embed(pv2); + if (is_angle_between(ty1, angle1, angle2)) + bbox.embed(ph1); + if (is_angle_between(ty2, angle1, angle2)) + bbox.embed(ph2); + } + return bbox; +} diff --git a/dviware/dvisvgm/src/EllipticalArc.hpp b/dviware/dvisvgm/src/EllipticalArc.hpp new file mode 100644 index 0000000000..5d20558196 --- /dev/null +++ b/dviware/dvisvgm/src/EllipticalArc.hpp @@ -0,0 +1,59 @@ +/************************************************************************* +** EllipticalArc.hpp ** +** ** +** This file is part of dvisvgm -- a fast DVI to SVG converter ** +** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> ** +** ** +** This program is free software; you can redistribute it and/or ** +** modify it under the terms of the GNU General Public License as ** +** published by the Free Software Foundation; either version 3 of ** +** the License, or (at your option) any later version. ** +** ** +** This program is distributed in the hope that it will be useful, but ** +** WITHOUT ANY WARRANTY; without even the implied warranty of ** +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** +** GNU General Public License for more details. ** +** ** +** You should have received a copy of the GNU General Public License ** +** along with this program; if not, see <http://www.gnu.org/licenses/>. ** +*************************************************************************/ + +#pragma once + +#include "Bezier.hpp" +#include "BoundingBox.hpp" +#include "Matrix.hpp" +#include "Pair.hpp" + +class EllipticalArc { + public: + struct CenterParams { + DPair center; ///< absolute coordinates of the center point + double startAngle; ///< angle of start point relative to semi-major axis (in radians) + double deltaAngle; ///< difference between angles of the start and end point (>0 if sweep flag is set) + }; + + public: + EllipticalArc (const DPair &start, double rx, double ry, double angle, bool laf, bool sweep, const DPair &end); + EllipticalArc (const DPair ¢er, double rx, double ry, double rot, double startAngle, double deltaAngle); + CenterParams getCenterParams () const; + double rx () const {return _rx;} + double ry () const {return _ry;} + bool largeArc () const {return _largeArc;} + bool sweepPositive () const {return _sweepPositive;} + double rotationAngle () const {return _rotationAngle;} + DPair startPoint () const {return _startPoint;} + DPair endPoint () const {return _endPoint;} + bool isStraightLine () const {return _rx < 1e-7 || _ry < 1e-7;} + BoundingBox getBBox () const; + void transform (const Matrix &matrix); + std::vector<Bezier> approximate () const; + + private: + double _rx, _ry; ///< length of semi-major and semi-minor axes + double _rotationAngle; ///< rotation angle around center (in radians) + bool _largeArc; ///< if true, the longer arc from start to end point is chosen, else the shorter one + bool _sweepPositive; ///< if true, arc is drawn in direction of positive angles, else the opposite direction + DPair _startPoint, _endPoint; ///< absolute coordinates of start and end point +}; + diff --git a/dviware/dvisvgm/src/FilePath.cpp b/dviware/dvisvgm/src/FilePath.cpp index 98449c101b..95fdab8c25 100644 --- a/dviware/dvisvgm/src/FilePath.cpp +++ b/dviware/dvisvgm/src/FilePath.cpp @@ -87,10 +87,28 @@ FilePath::FilePath (const string &path, bool isfile, const string ¤t_dir) } -/** Assigns a new path. Relative paths are relative to the current working directory. +/** Assigns a new path of a file or directory that already exists. + * Relative paths are relative to the current working directory. * @param[in] path absolute or relative path to a file or directory */ -void FilePath::set(const string &path) { - init(path, !FileSystem::isDirectory(path), FileSystem::getcwd()); +void FilePath::set (const string &path) { + set(path, !FileSystem::isDirectory(path)); +} + + +/** Assigns a new path. Relative paths are relative to the current working directory. + * @param[in] path absolute or relative path to a file or directory + * @param[in] isfile true if 'path' references a file, false if a directory is referenced */ +void FilePath::set (const string &path, bool isfile) { + init(path, isfile, FileSystem::getcwd()); +} + + +/** Assigns a new path. Relative paths are relative to the current working directory. + * @param[in] path absolute or relative path to a file or directory + * @param[in] isfile true if 'path' references a file, false if a directory is referenced + * @param[in] current_dir if 'path' is a relative path expression it will be related to 'current_dir' */ +void FilePath::set (const string &path, bool isfile, const string ¤t_dir) { + init(path, isfile, current_dir); } @@ -257,3 +275,18 @@ string FilePath::relative (string reldir, bool with_filename) const { path = "."; return single_slashes(path); } + + +/** Return the absolute or relative path whichever is shorter. +* @param[in] reldir absolute path to a directory +* @param[in] with_filename if false, the filename is omitted */ +string FilePath::shorterAbsoluteOrRelative (string reldir, bool with_filename) const { + string abs = absolute(with_filename); + string rel = relative(reldir, with_filename); + return abs.length() < rel.length() ? abs : rel; +} + + +bool FilePath::exists () const { + return empty() ? false : FileSystem::exists(absolute()); +}
\ No newline at end of file diff --git a/dviware/dvisvgm/src/FilePath.hpp b/dviware/dvisvgm/src/FilePath.hpp index d152c19f20..abcea4a9dc 100644 --- a/dviware/dvisvgm/src/FilePath.hpp +++ b/dviware/dvisvgm/src/FilePath.hpp @@ -39,12 +39,16 @@ class FilePath { }; public: + FilePath () =default; explicit FilePath (const std::string &path) {set(path);} FilePath (const std::string &path, bool isfile) : FilePath(path, isfile, "") {} FilePath (const std::string &path, bool isfile, const std::string ¤t_dir); void set (const std::string &path); + void set (const std::string &path, bool isfile); + void set (const std::string &path, bool isfile, const std::string ¤t_dir); std::string absolute (bool with_filename=true) const; std::string relative (std::string reldir="", bool with_filename=true) const; + std::string shorterAbsoluteOrRelative (std::string reldir="", bool with_filename=true) const; std::string basename () const; std::string suffix () const; void suffix (const std::string &s); @@ -53,6 +57,7 @@ class FilePath { bool empty () const {return _dirs.empty() && _fname.empty();} const std::string& filename () const {return _fname;} void filename (const std::string &fname) {_fname = fname;} + bool exists () const; protected: void init (std::string path, bool isfile, std::string current_dir); @@ -62,7 +67,7 @@ class FilePath { std::vector<Directory> _dirs; std::string _fname; #ifdef _WIN32 - char _drive; + char _drive=0; #endif }; diff --git a/dviware/dvisvgm/src/Font.cpp b/dviware/dvisvgm/src/Font.cpp index f5bdff6674..97520c5953 100644 --- a/dviware/dvisvgm/src/Font.cpp +++ b/dviware/dvisvgm/src/Font.cpp @@ -159,7 +159,7 @@ bool TFMFont::verifyChecksums () const { // static class variables bool PhysicalFont::EXACT_BBOX = false; bool PhysicalFont::KEEP_TEMP_FILES = false; -const char *PhysicalFont::CACHE_PATH = nullptr; +string PhysicalFont::CACHE_PATH; double PhysicalFont::METAFONT_MAG = 4; FontCache PhysicalFont::_cache; @@ -324,7 +324,7 @@ std::string PhysicalFont::styleName () const { bool PhysicalFont::getGlyph (int c, GraphicsPath<int32_t> &glyph, GFGlyphTracer::Callback *callback) const { if (type() == Type::MF) { const Glyph *cached_glyph=nullptr; - if (CACHE_PATH) { + if (!CACHE_PATH.empty()) { _cache.write(CACHE_PATH); _cache.read(name(), CACHE_PATH); cached_glyph = _cache.getGlyph(c); @@ -342,7 +342,7 @@ bool PhysicalFont::getGlyph (int c, GraphicsPath<int32_t> &glyph, GFGlyphTracer: tracer.setGlyph(glyph); tracer.executeChar(c); glyph.closeOpenSubPaths(); - if (CACHE_PATH) + if (!CACHE_PATH.empty()) _cache.setGlyph(c, glyph); return true; } @@ -392,7 +392,7 @@ bool PhysicalFont::createGF (string &gfname) const { * @return number of glyphs traced */ int PhysicalFont::traceAllGlyphs (bool includeCached, GFGlyphTracer::Callback *cb) const { int count = 0; - if (type() == Type::MF && CACHE_PATH) { + if (type() == Type::MF && !CACHE_PATH.empty()) { if (const FontMetrics *metrics = getMetrics()) { int fchar = metrics->firstChar(); int lchar = metrics->lastChar(); @@ -479,7 +479,7 @@ PhysicalFontImpl::PhysicalFontImpl (const string &name, int fontindex, uint32_t PhysicalFontImpl::~PhysicalFontImpl () { - if (CACHE_PATH) + if (!CACHE_PATH.empty()) _cache.write(CACHE_PATH); if (!KEEP_TEMP_FILES) tidy(); diff --git a/dviware/dvisvgm/src/Font.hpp b/dviware/dvisvgm/src/Font.hpp index bbc0d62b8a..3273a0c14b 100644 --- a/dviware/dvisvgm/src/Font.hpp +++ b/dviware/dvisvgm/src/Font.hpp @@ -142,7 +142,7 @@ class PhysicalFont : public virtual Font { public: static bool EXACT_BBOX; static bool KEEP_TEMP_FILES; - static const char *CACHE_PATH; ///< path to cache directory (0 if caching is disabled) + static std::string CACHE_PATH; ///< path to cache directory ("" if caching is disabled) static double METAFONT_MAG; ///< magnification factor for Metafont calls protected: diff --git a/dviware/dvisvgm/src/FontCache.cpp b/dviware/dvisvgm/src/FontCache.cpp index 5f61fc341c..34478542df 100644 --- a/dviware/dvisvgm/src/FontCache.cpp +++ b/dviware/dvisvgm/src/FontCache.cpp @@ -23,13 +23,12 @@ #include <fstream> #include <iomanip> #include <sstream> -#include "CRC32.hpp" #include "FileSystem.hpp" #include "FontCache.hpp" -#include "Glyph.hpp" #include "Pair.hpp" #include "StreamReader.hpp" #include "StreamWriter.hpp" +#include "XXHashFunction.hpp" using namespace std; @@ -78,10 +77,9 @@ bool FontCache::write (const string &fontname, const string &dir) const { return true; if (!fontname.empty()) { - string dirstr = dir.empty() ? FileSystem::getcwd() : dir; - ostringstream oss; - oss << dirstr << '/' << fontname << ".fgd"; - ofstream ofs(oss.str(), ios::binary); + string pathstr = dir.empty() ? FileSystem::getcwd() : dir; + pathstr += "/" + fontname + ".fgd"; + ofstream ofs(pathstr, ios::binary); return write(fontname, ofs); } return false; @@ -94,7 +92,7 @@ bool FontCache::write (const string &dir) const { /** Returns the minimal number of bytes needed to store the given value. */ -static int max_int_size (int32_t value) { +static int max_number_of_bytes (int32_t value) { int32_t limit = 0x7f; for (int i=1; i <= 4; i++) { if ((value < 0 && -value <= limit+1) || (value >= 0 && value <= limit)) @@ -104,19 +102,49 @@ static int max_int_size (int32_t value) { return 4; } +static int max_int_size () { + return 0; +} -/** Returns the minimal number of bytes needed to store the biggest - * pair component of the given vector. */ -static int max_int_size (const Pair<int32_t> *pairs, size_t n) { - int ret=0; - for (size_t i=0; i < n; i++) { - ret = max(ret, max_int_size(pairs[i].x())); - ret = max(ret, max_int_size(pairs[i].y())); - } - return ret; +template <typename ...Args> +static int max_int_size (const Glyph::Point &p1, const Args& ...args) { + int max1 = max(max_number_of_bytes(p1.x()), max_number_of_bytes(p1.y())); + return max(max1, max_int_size(args...)); } +struct WriteActions : Glyph::IterationActions { + WriteActions (StreamWriter &sw, HashFunction &hashfunc) : _sw(sw), _hashfunc(hashfunc) {} + + using Point = Glyph::Point; + void moveto (const Point &p) override {write('M', p);} + void lineto (const Point &p) override {write('L', p);} + void quadto (const Point &p1, const Point &p2) override {write('Q', p1, p2);} + void cubicto (const Point &p1, const Point &p2, const Point &p3) override {write('C', p1, p2, p3); } + void closepath () override {write('Z');} + + template <typename ...Args> + void write (char cmd, Args ...args) { + int bytesPerValue = max_int_size(args...); + int cmdchar = (bytesPerValue << 5) | (cmd - 'A'); + _sw.writeUnsigned(cmdchar, 1, _hashfunc); + writeParams(bytesPerValue, args...); + } + + static void writeParams (int bytesPerValue) {} + + template <typename ...Args> + void writeParams (int bytesPerValue, const Point &p, const Args& ...args) { + _sw.writeSigned(p.x(), bytesPerValue, _hashfunc); + _sw.writeSigned(p.y(), bytesPerValue, _hashfunc); + writeParams(bytesPerValue, args...); + } + + StreamWriter &_sw; + HashFunction &_hashfunc; +}; + + /** Writes the current cache data to a stream (only if anything changed after * the last call of read()). * @param[in] fontname name of current font @@ -129,36 +157,22 @@ bool FontCache::write (const string &fontname, ostream &os) const { return false; StreamWriter sw(os); - CRC32 crc32; - - struct WriteActions : Glyph::Actions { - WriteActions (StreamWriter &sw, CRC32 &crc32) : _sw(sw), _crc32(crc32) {} - - void draw (char cmd, const Glyph::Point *points, int n) override { - int bytes = max_int_size(points, n); - int cmdchar = (bytes << 5) | (cmd - 'A'); - _sw.writeUnsigned(cmdchar, 1, _crc32); - for (int i=0; i < n; i++) { - _sw.writeSigned(points[i].x(), bytes, _crc32); - _sw.writeSigned(points[i].y(), bytes, _crc32); - } - } - StreamWriter &_sw; - CRC32 &_crc32; - } actions(sw, crc32); - - sw.writeUnsigned(FORMAT_VERSION, 1, crc32); - sw.writeUnsigned(0, 4); // space for checksum - sw.writeString(fontname, crc32, true); - sw.writeUnsigned(_glyphs.size(), 4, crc32); + XXH32HashFunction hashfunc; + + sw.writeUnsigned(FORMAT_VERSION, 1, hashfunc); + sw.writeBytes(hashfunc.digestValue()); // space for checksum + sw.writeString(fontname, hashfunc, true); + sw.writeUnsigned(_glyphs.size(), 4, hashfunc); + WriteActions actions(sw, hashfunc); for (const auto &charglyphpair : _glyphs) { const Glyph &glyph = charglyphpair.second; - sw.writeUnsigned(charglyphpair.first, 4, crc32); - sw.writeUnsigned(glyph.size(), 2, crc32); + sw.writeUnsigned(charglyphpair.first, 4, hashfunc); + sw.writeUnsigned(glyph.size(), 2, hashfunc); glyph.iterate(actions, false); } os.seekp(1); - sw.writeUnsigned(crc32.get(), 4); // insert CRC32 checksum + auto digest = hashfunc.digestValue(); + sw.writeBytes(digest); // insert checksum os.seekp(0, ios::end); return true; } @@ -195,17 +209,17 @@ bool FontCache::read (const string &fontname, istream &is) { return false; StreamReader sr(is); - CRC32 crc32; - if (sr.readUnsigned(1, crc32) != FORMAT_VERSION) + XXH32HashFunction hashfunc; + if (sr.readUnsigned(1, hashfunc) != FORMAT_VERSION) return false; - uint32_t crc32_cmp = sr.readUnsigned(4); - crc32.update(is); - if (crc32.get() != crc32_cmp) + auto hashcmp = sr.readBytes(hashfunc.digestSize()); + hashfunc.update(is); + if (hashfunc.digestValue() != hashcmp) return false; is.clear(); - is.seekg(5); // continue reading after checksum + is.seekg(hashfunc.digestSize()+1); // continue reading after checksum string fname = sr.readString(); if (fname != fontname) @@ -237,7 +251,7 @@ bool FontCache::read (const string &fontname, istream &is) { case 'Q': { Pair32 p1 = read_pair(bytes, sr); Pair32 p2 = read_pair(bytes, sr); - glyph.conicto(p1, p2); + glyph.quadto(p1, p2); break; } case 'Z': @@ -289,17 +303,17 @@ bool FontCache::fontinfo (std::istream &is, FontInfo &info) { is.seekg(0); try { StreamReader sr(is); - CRC32 crc32; - if ((info.version = sr.readUnsigned(1, crc32)) != FORMAT_VERSION) + XXH32HashFunction hashfunc; + if ((info.version = sr.readUnsigned(1, hashfunc)) != FORMAT_VERSION) return false; - info.checksum = sr.readUnsigned(4); - crc32.update(is); - if (crc32.get() != info.checksum) + info.checksum = sr.readBytes(hashfunc.digestSize()); + hashfunc.update(is); + if (hashfunc.digestValue() != info.checksum) return false; is.clear(); - is.seekg(5); // continue reading after checksum + is.seekg(hashfunc.digestSize()+1); // continue reading after checksum info.name = sr.readString(); info.numchars = sr.readUnsigned(4); @@ -361,8 +375,10 @@ void FontCache::fontinfo (const string &dirname, ostream &os, bool purge) { << setw(5) << right << strinfopair.second->numchars << " glyph" << (strinfopair.second->numchars == 1 ? ' ':'s') << setw(10) << right << strinfopair.second->numcmds << " cmd" << (strinfopair.second->numcmds == 1 ? ' ':'s') << setw(12) << right << strinfopair.second->numbytes << " byte" << (strinfopair.second->numbytes == 1 ? ' ':'s') - << setw(6) << "crc:" << setw(8) << hex << right << setfill('0') << strinfopair.second->checksum - << endl; + << " hash:" << hex; + for (int byte : strinfopair.second->checksum) + os << setw(2) << setfill('0') << byte; + os << '\n'; } } if (purge) { diff --git a/dviware/dvisvgm/src/FontCache.hpp b/dviware/dvisvgm/src/FontCache.hpp index d242e54ad3..0068b6c78b 100644 --- a/dviware/dvisvgm/src/FontCache.hpp +++ b/dviware/dvisvgm/src/FontCache.hpp @@ -30,12 +30,12 @@ class FontCache { public: struct FontInfo { - std::string name; // fontname - uint16_t version; // file format version - uint32_t checksum; // CRC32 checksum of file data - uint32_t numchars; // number of characters - uint32_t numbytes; // number of bytes - uint32_t numcmds; // number of path commands + std::string name; // fontname + uint16_t version; // file format version + std::vector<uint8_t> checksum; // checksum of file data + uint32_t numchars; // number of characters + uint32_t numbytes; // number of bytes + uint32_t numcmds; // number of path commands }; public: diff --git a/dviware/dvisvgm/src/FontEngine.cpp b/dviware/dvisvgm/src/FontEngine.cpp index ef02f37335..61efd68916 100644 --- a/dviware/dvisvgm/src/FontEngine.cpp +++ b/dviware/dvisvgm/src/FontEngine.cpp @@ -351,9 +351,9 @@ static int lineto (FTVectorPtr to, void *user) { } -static int conicto (FTVectorPtr control, FTVectorPtr to, void *user) { +static int quadto (FTVectorPtr control, FTVectorPtr to, void *user) { auto glyph = static_cast<Glyph*>(user); - glyph->conicto(control->x, control->y, to->x, to->y); + glyph->quadto(control->x, control->y, to->x, to->y); return 0; } @@ -366,7 +366,7 @@ static int cubicto (FTVectorPtr control1, FTVectorPtr control2, FTVectorPtr to, /** Traces the outline of a glyph by calling the corresponding "drawing" functions. - * Each glyph is composed of straight lines, quadratic (conic) or cubic B�zier curves. + * Each glyph is composed of straight lines, quadratic or cubic Bézier curves. * This function creates a Glyph object representing these graphics segments. * @param[in] face FreeType object representing the font to scan * @param[in] font corresponding Font object providing additional data @@ -392,7 +392,7 @@ static bool trace_outline (FT_Face face, const Font *font, int index, Glyph &gly if (style->bold != 0) FT_Outline_Embolden(&outline, style->bold/font->scaledSize()*face->units_per_EM); } - const FT_Outline_Funcs funcs = {moveto, lineto, conicto, cubicto, 0, 0}; + const FT_Outline_Funcs funcs = {moveto, lineto, quadto, cubicto, 0, 0}; FT_Outline_Decompose(&outline, &funcs, &glyph); return true; } @@ -402,7 +402,7 @@ static bool trace_outline (FT_Face face, const Font *font, int index, Glyph &gly /** Traces the outline of a glyph by calling the corresponding "drawing" functions. - * Each glyph is composed of straight lines, quadratic (conic) or cubic B�zier curves. + * Each glyph is composed of straight lines, quadratic or cubic Bézier curves. * This function creates a Glyph object representing these graphics segments. * @param[in] c the glyph of this character will be traced * @param[out] glyph resulting Glyph object containing the graphics segments diff --git a/dviware/dvisvgm/src/FontWriter.cpp b/dviware/dvisvgm/src/FontWriter.cpp index 863b142c5c..d93402baf5 100644 --- a/dviware/dvisvgm/src/FontWriter.cpp +++ b/dviware/dvisvgm/src/FontWriter.cpp @@ -95,33 +95,38 @@ FontWriter::FontWriter (const PhysicalFont &font) : _font(font) { } -struct SFDActions : Glyph::Actions { +struct SFDActions : Glyph::IterationActions { explicit SFDActions (ostream &os) : _os(os) {} - void draw (char cmd, const Glyph::Point *points, int n) override { - if (cmd == 'Q') { - // convert quadratic Bézier curve to cubic one - DPair p0(_currentPoint.x(), _currentPoint.y()); - DPair p1(points[0].x(), points[0].y()); - DPair p2(points[1].x(), points[1].y()); - Bezier bezier(p0, p1, p2); - for (int i=1; i < 4; i++) - _os << lround(bezier.point(i).x()) << ' ' << lround(bezier.point(i).y()) << ' '; - _os << 'c'; - } - else { - for (int i=0; i < n; i++) - _os << points[i].x() << ' ' << points[i].y() << ' '; - switch (cmd) { - case 'M': _os << 'm'; _startPoint = points[0]; break; - case 'L': _os << 'l'; break; - case 'C': _os << 'c'; break; - case 'Z': _os << _startPoint.x() << ' ' << _startPoint.y() << " l"; _currentPoint = _startPoint; break; - } - } - if (n > 0) - _currentPoint = points[n-1]; - _os << " 0\n"; + + using Point = Glyph::Point; + void moveto (const Point &p) override {write('m', p);} + void lineto (const Point &p) override {write('l', p);} + void cubicto (const Point &p1, const Point &p2, const Point &p3) override {write('c', p1, p2, p3); } + void closepath () override {write('m', startPoint());} + + void quadto (const Point &p1, const Point &p2) override { + // convert quadratic Bézier curve to cubic one + DPair pt0(currentPoint().x(), currentPoint().y()); + DPair pt1(p1.x(), p1.y()); + DPair pt2(p2.x(), p2.y()); + Bezier b(pt0, pt1, pt2); + write('c', round(b.point(0)), round(b.point(1)), round(b.point(2)), round(b.point(3))); } + + template <typename ...Args> + void write (char cmd, const Args& ...args) { + writeParams(args...); + _os << cmd << " 0\n"; + } + + static void writeParams () {} + + template <typename Pt, typename ...Args> + void writeParams (const Pt &p, const Args& ...args) const { + _os << p.x() << ' ' << p.y() << ' '; + writeParams(args...); + } + ostream &_os; Glyph::Point _startPoint, _currentPoint; }; diff --git a/dviware/dvisvgm/src/GFReader.cpp b/dviware/dvisvgm/src/GFReader.cpp index 3cba8e5069..32ba354443 100644 --- a/dviware/dvisvgm/src/GFReader.cpp +++ b/dviware/dvisvgm/src/GFReader.cpp @@ -106,11 +106,8 @@ int GFReader::executeCommand () { cmdPaint0(opcode); else if (opcode >= 74 && opcode <= 238) cmdNewRow(opcode-74); - else if (opcode >= 250) { - ostringstream oss; - oss << "undefined GF command (opcode " << opcode << ")"; - throw GFException(oss.str()); - } + else if (opcode >= 250) + throw GFException("undefined GF command (opcode " + std::to_string(opcode) + ")"); else { int offset = opcode <= 73 ? 64 : 239-(73-64+1); const GFCommand &cmd = commands[opcode-offset]; 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> ¤tPoint) { + 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> ¤tPoint); + + 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> ¤tPoint) { + 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 +}; diff --git a/dviware/dvisvgm/src/HashFunction.cpp b/dviware/dvisvgm/src/HashFunction.cpp index 7479fca604..517cc8f3cc 100644 --- a/dviware/dvisvgm/src/HashFunction.cpp +++ b/dviware/dvisvgm/src/HashFunction.cpp @@ -31,7 +31,12 @@ using namespace std; /** Returns a vector containing the names of the currently supported hash algorithms. */ vector<string> HashFunction::supportedAlgorithms () { - return vector<string> {"md5", "xxh32", "xxh64"}; + return vector<string> { + "md5", "xxh32", "xxh64", +#ifdef ENABLE_XXH128 + "xxh128" +#endif + }; } @@ -52,6 +57,10 @@ unique_ptr<HashFunction> HashFunction::create (const string &name) { return util::make_unique<XXH32HashFunction>(); if (lowerName == "xxh64") return util::make_unique<XXH64HashFunction>(); +#ifdef ENABLE_XXH128 + if (lowerName == "xxh128") + return util::make_unique<XXH128HashFunction>(); +#endif return nullptr; } @@ -74,6 +83,15 @@ std::unique_ptr<HashFunction> HashFunction::create (const string &name, const ve } +void HashFunction::update (istream &is) { + char buf[4096]; + while (is) { + is.read(buf, 4096); + update(buf, is.gcount()); + } +} + + /** Returns the current digest as hexadecimal value. */ string HashFunction::digestString () const { ostringstream oss; diff --git a/dviware/dvisvgm/src/HashFunction.hpp b/dviware/dvisvgm/src/HashFunction.hpp index 1b1259393f..bba61918ed 100644 --- a/dviware/dvisvgm/src/HashFunction.hpp +++ b/dviware/dvisvgm/src/HashFunction.hpp @@ -21,6 +21,7 @@ #ifndef HASHFUNCTION_HPP #define HASHFUNCTION_HPP +#include <istream> #include <memory> #include <string> #include <vector> @@ -35,6 +36,7 @@ class HashFunction { virtual void update (const std::string &data) =0; virtual void update (const std::vector<uint8_t> &data) =0; virtual std::vector<uint8_t> digestValue () const =0; + void update (std::istream &is); std::string digestString () const; static std::vector<std::string> supportedAlgorithms (); static bool isSupportedAlgorithm (const std::string &algo); diff --git a/dviware/dvisvgm/src/HyperlinkManager.cpp b/dviware/dvisvgm/src/HyperlinkManager.cpp index ad5cabd93c..de412afcdc 100644 --- a/dviware/dvisvgm/src/HyperlinkManager.cpp +++ b/dviware/dvisvgm/src/HyperlinkManager.cpp @@ -96,7 +96,7 @@ void HyperlinkManager::createLink (string uri, SpecialActions &actions) { uri = "#loc"+XMLString(id); if (actions.getCurrentPageNumber() != it->second.pageno) { ostringstream oss; - oss << actions.getSVGFilename(it->second.pageno) << uri; + oss << actions.getSVGFilePath(it->second.pageno).relative() << uri; uri = oss.str(); } } diff --git a/dviware/dvisvgm/src/ImageToSVG.cpp b/dviware/dvisvgm/src/ImageToSVG.cpp index 92e5ece1dd..26edb3c753 100644 --- a/dviware/dvisvgm/src/ImageToSVG.cpp +++ b/dviware/dvisvgm/src/ImageToSVG.cpp @@ -85,7 +85,7 @@ void ImageToSVG::convert (int pageno) { _svg.setBBox(_bbox); _svg.appendToDoc(util::make_unique<XMLComment>(" This file was generated by dvisvgm " + string(PROGRAM_VERSION) + " ")); bool success = _svg.write(_out.getPageStream(pageno, totalPageCount())); - string svgfname = _out.filename(pageno, totalPageCount()); + string svgfname = _out.filepath(pageno, totalPageCount()).shorterAbsoluteOrRelative(); if (svgfname.empty()) svgfname = "<stdout>"; if (!success) @@ -145,10 +145,11 @@ void ImageToSVG::convert (const std::string &rangestr, pair<int,int> *pageinfo) } -string ImageToSVG::getSVGFilename (unsigned pageno) const { +FilePath ImageToSVG::getSVGFilePath (unsigned pageno) const { + FilePath path; if (pageno == 1) - return _out.filename(1, 1); - return ""; + path = _out.filepath(1, 1); + return path; } diff --git a/dviware/dvisvgm/src/ImageToSVG.hpp b/dviware/dvisvgm/src/ImageToSVG.hpp index 88564fd736..a4e4de223f 100644 --- a/dviware/dvisvgm/src/ImageToSVG.hpp +++ b/dviware/dvisvgm/src/ImageToSVG.hpp @@ -68,7 +68,7 @@ class ImageToSVG : protected SpecialActions { unsigned getCurrentPageNumber() const override {return 0;} BoundingBox& bbox () override {return _bbox;} BoundingBox& bbox (const std::string &name, bool reset=false) override {return _bbox;} - std::string getSVGFilename (unsigned pageno) const override; + FilePath getSVGFilePath (unsigned pageno) const override; std::string getBBoxFormatString () const override {return "";} private: diff --git a/dviware/dvisvgm/src/JFM.cpp b/dviware/dvisvgm/src/JFM.cpp index a1835f6732..a79fdf0c03 100644 --- a/dviware/dvisvgm/src/JFM.cpp +++ b/dviware/dvisvgm/src/JFM.cpp @@ -32,11 +32,9 @@ JFM::JFM (istream &is) { is.seekg(0); StreamReader reader(is); uint16_t id = uint16_t(reader.readUnsigned(2)); // JFM ID (9 or 11) - if (id != 9 && id != 11) { - ostringstream oss; - oss << "invalid JFM identifier " << id << " (9 or 11 expected)"; - throw FontMetricException(oss.str()); - } + if (id != 9 && id != 11) + throw FontMetricException("invalid JFM identifier " + std::to_string(id) + " (9 or 11 expected)"); + _vertical = (id == 9); uint16_t nt = uint16_t(reader.readUnsigned(2)); // length of character type table uint16_t lf = uint16_t(reader.readUnsigned(2)); // length of entire file in 4 byte words diff --git a/dviware/dvisvgm/src/Length.cpp b/dviware/dvisvgm/src/Length.cpp index ba7dd2a225..cbee103d49 100644 --- a/dviware/dvisvgm/src/Length.cpp +++ b/dviware/dvisvgm/src/Length.cpp @@ -60,9 +60,7 @@ void Length::set (double val, Unit unit) { case Unit::SP: _pt = val/pt2sp; break; default: // this isn't supposed to happen - ostringstream oss; - oss << "invalid length unit: (" << static_cast<int>(unit) << ")"; - throw UnitException(oss.str()); + throw UnitException("invalid length unit: (" + std::to_string(static_cast<int>(unit)) + ")"); } } @@ -80,9 +78,7 @@ double Length::get (Unit unit) const { case Unit::SP: return sp(); } // this isn't supposed to happen - ostringstream oss; - oss << "invalid length unit: (" << static_cast<int>(unit) << ")"; - throw UnitException(oss.str()); + throw UnitException("invalid length unit: (" + std::to_string(static_cast<int>(unit)) + ")"); } @@ -111,7 +107,7 @@ Length::Unit Length::stringToUnit (const std::string &unitstr) { case unit_id('s','p'): return Unit::SP; } } - throw UnitException(string("invalid length unit: ")+unitstr); + throw UnitException(string("invalid length unit: ") + unitstr); } @@ -136,7 +132,7 @@ void Length::set (double val, string unitstr) { if (unitstr.empty()) unitstr = "pt"; else if (unitstr.length() != 2) - throw UnitException(string("invalid length unit: ")+unitstr); + throw UnitException(string("invalid length unit: ") + unitstr); set(val, stringToUnit(unitstr)); } diff --git a/dviware/dvisvgm/src/MD5HashFunction.hpp b/dviware/dvisvgm/src/MD5HashFunction.hpp index 4ff18f3f3e..e233673d36 100644 --- a/dviware/dvisvgm/src/MD5HashFunction.hpp +++ b/dviware/dvisvgm/src/MD5HashFunction.hpp @@ -32,7 +32,7 @@ class MD5HashFunction : public HashFunction { public: - explicit MD5HashFunction () {MD5_Init(&_context);} + MD5HashFunction () {MD5_Init(&_context);} MD5HashFunction (const char *data, size_t length) : MD5HashFunction() {update(data, length);} explicit MD5HashFunction (const std::string &data) : MD5HashFunction() {update(data);} explicit MD5HashFunction (const std::vector<uint8_t> &data) : MD5HashFunction() {update(data);} diff --git a/dviware/dvisvgm/src/Makefile.am b/dviware/dvisvgm/src/Makefile.am index 6bdbea32e6..fcab2ba5cc 100644 --- a/dviware/dvisvgm/src/Makefile.am +++ b/dviware/dvisvgm/src/Makefile.am @@ -55,7 +55,6 @@ libdvisvgm_la_SOURCES = \ Color.hpp Color.cpp \ ColorSpecialHandler.hpp ColorSpecialHandler.cpp \ CommandLine.hpp \ - CRC32.hpp CRC32.cpp \ Directory.hpp Directory.cpp \ DVIActions.hpp \ DLLoader.hpp DLLoader.cpp \ @@ -63,6 +62,7 @@ libdvisvgm_la_SOURCES = \ DvisvgmSpecialHandler.hpp DvisvgmSpecialHandler.cpp \ DVIToSVG.hpp DVIToSVG.cpp \ DVIToSVGActions.hpp DVIToSVGActions.cpp \ + EllipticalArc.hpp EllipticalArc.cpp \ EmSpecialHandler.hpp EmSpecialHandler.cpp \ EncFile.hpp EncFile.cpp \ EPSFile.hpp EPSFile.cpp \ diff --git a/dviware/dvisvgm/src/Makefile.in b/dviware/dvisvgm/src/Makefile.in index 6369ce9995..7c6cf1ae15 100644 --- a/dviware/dvisvgm/src/Makefile.in +++ b/dviware/dvisvgm/src/Makefile.in @@ -139,12 +139,12 @@ am__libdvisvgm_la_SOURCES_DIST = AGLTable.hpp BasicDVIReader.hpp \ CLCommandLine.hpp CLCommandLine.cpp CMap.hpp CMap.cpp \ CMapManager.hpp CMapManager.cpp CMapReader.hpp CMapReader.cpp \ CLOption.hpp Color.hpp Color.cpp ColorSpecialHandler.hpp \ - ColorSpecialHandler.cpp CommandLine.hpp CRC32.hpp CRC32.cpp \ - Directory.hpp Directory.cpp DVIActions.hpp DLLoader.hpp \ - DLLoader.cpp DVIReader.hpp DVIReader.cpp \ - DvisvgmSpecialHandler.hpp DvisvgmSpecialHandler.cpp \ - DVIToSVG.hpp DVIToSVG.cpp DVIToSVGActions.hpp \ - DVIToSVGActions.cpp EmSpecialHandler.hpp EmSpecialHandler.cpp \ + ColorSpecialHandler.cpp CommandLine.hpp Directory.hpp \ + Directory.cpp DVIActions.hpp DLLoader.hpp DLLoader.cpp \ + DVIReader.hpp DVIReader.cpp DvisvgmSpecialHandler.hpp \ + DvisvgmSpecialHandler.cpp DVIToSVG.hpp DVIToSVG.cpp \ + DVIToSVGActions.hpp DVIToSVGActions.cpp EllipticalArc.hpp \ + EllipticalArc.cpp EmSpecialHandler.hpp EmSpecialHandler.cpp \ EncFile.hpp EncFile.cpp EPSFile.hpp EPSFile.cpp EPSToSVG.hpp \ FileFinder.hpp FileFinder.cpp FilePath.hpp FilePath.cpp \ FileSystem.hpp FileSystem.cpp FixWord.hpp Font.hpp Font.cpp \ @@ -199,8 +199,8 @@ am_libdvisvgm_la_OBJECTS = BasicDVIReader.lo Bezier.lo \ BgColorSpecialHandler.lo Bitmap.lo BoundingBox.lo \ Calculator.lo CharMapID.lo CLCommandLine.lo CMap.lo \ CMapManager.lo CMapReader.lo Color.lo ColorSpecialHandler.lo \ - CRC32.lo Directory.lo DLLoader.lo DVIReader.lo \ - DvisvgmSpecialHandler.lo DVIToSVG.lo DVIToSVGActions.lo \ + Directory.lo DLLoader.lo DVIReader.lo DvisvgmSpecialHandler.lo \ + DVIToSVG.lo DVIToSVGActions.lo EllipticalArc.lo \ EmSpecialHandler.lo EncFile.lo EPSFile.lo FileFinder.lo \ FilePath.lo FileSystem.lo Font.lo FontCache.lo FontEncoding.lo \ FontEngine.lo FontManager.lo FontMap.lo FontMetrics.lo \ @@ -255,21 +255,22 @@ am__depfiles_remade = ./$(DEPDIR)/BasicDVIReader.Plo \ ./$(DEPDIR)/Bitmap.Plo ./$(DEPDIR)/BoundingBox.Plo \ ./$(DEPDIR)/CLCommandLine.Plo ./$(DEPDIR)/CMap.Plo \ ./$(DEPDIR)/CMapManager.Plo ./$(DEPDIR)/CMapReader.Plo \ - ./$(DEPDIR)/CRC32.Plo ./$(DEPDIR)/Calculator.Plo \ - ./$(DEPDIR)/CharMapID.Plo ./$(DEPDIR)/Color.Plo \ - ./$(DEPDIR)/ColorSpecialHandler.Plo ./$(DEPDIR)/DLLoader.Plo \ - ./$(DEPDIR)/DVIReader.Plo ./$(DEPDIR)/DVIToSVG.Plo \ - ./$(DEPDIR)/DVIToSVGActions.Plo ./$(DEPDIR)/Directory.Plo \ + ./$(DEPDIR)/Calculator.Plo ./$(DEPDIR)/CharMapID.Plo \ + ./$(DEPDIR)/Color.Plo ./$(DEPDIR)/ColorSpecialHandler.Plo \ + ./$(DEPDIR)/DLLoader.Plo ./$(DEPDIR)/DVIReader.Plo \ + ./$(DEPDIR)/DVIToSVG.Plo ./$(DEPDIR)/DVIToSVGActions.Plo \ + ./$(DEPDIR)/Directory.Plo \ ./$(DEPDIR)/DvisvgmSpecialHandler.Plo ./$(DEPDIR)/EPSFile.Plo \ - ./$(DEPDIR)/EmSpecialHandler.Plo ./$(DEPDIR)/EncFile.Plo \ - ./$(DEPDIR)/FileFinder.Plo ./$(DEPDIR)/FilePath.Plo \ - ./$(DEPDIR)/FileSystem.Plo ./$(DEPDIR)/Font.Plo \ - ./$(DEPDIR)/FontCache.Plo ./$(DEPDIR)/FontEncoding.Plo \ - ./$(DEPDIR)/FontEngine.Plo ./$(DEPDIR)/FontManager.Plo \ - ./$(DEPDIR)/FontMap.Plo ./$(DEPDIR)/FontMetrics.Plo \ - ./$(DEPDIR)/FontWriter.Plo ./$(DEPDIR)/GFGlyphTracer.Plo \ - ./$(DEPDIR)/GFReader.Plo ./$(DEPDIR)/GFTracer.Plo \ - ./$(DEPDIR)/Ghostscript.Plo ./$(DEPDIR)/HashFunction.Plo \ + ./$(DEPDIR)/EllipticalArc.Plo ./$(DEPDIR)/EmSpecialHandler.Plo \ + ./$(DEPDIR)/EncFile.Plo ./$(DEPDIR)/FileFinder.Plo \ + ./$(DEPDIR)/FilePath.Plo ./$(DEPDIR)/FileSystem.Plo \ + ./$(DEPDIR)/Font.Plo ./$(DEPDIR)/FontCache.Plo \ + ./$(DEPDIR)/FontEncoding.Plo ./$(DEPDIR)/FontEngine.Plo \ + ./$(DEPDIR)/FontManager.Plo ./$(DEPDIR)/FontMap.Plo \ + ./$(DEPDIR)/FontMetrics.Plo ./$(DEPDIR)/FontWriter.Plo \ + ./$(DEPDIR)/GFGlyphTracer.Plo ./$(DEPDIR)/GFReader.Plo \ + ./$(DEPDIR)/GFTracer.Plo ./$(DEPDIR)/Ghostscript.Plo \ + ./$(DEPDIR)/HashFunction.Plo \ ./$(DEPDIR)/HtmlSpecialHandler.Plo \ ./$(DEPDIR)/HyperlinkManager.Plo ./$(DEPDIR)/ImageToSVG.Plo \ ./$(DEPDIR)/InputBuffer.Plo ./$(DEPDIR)/InputReader.Plo \ @@ -593,12 +594,12 @@ libdvisvgm_la_SOURCES = AGLTable.hpp BasicDVIReader.hpp \ CLCommandLine.hpp CLCommandLine.cpp CMap.hpp CMap.cpp \ CMapManager.hpp CMapManager.cpp CMapReader.hpp CMapReader.cpp \ CLOption.hpp Color.hpp Color.cpp ColorSpecialHandler.hpp \ - ColorSpecialHandler.cpp CommandLine.hpp CRC32.hpp CRC32.cpp \ - Directory.hpp Directory.cpp DVIActions.hpp DLLoader.hpp \ - DLLoader.cpp DVIReader.hpp DVIReader.cpp \ - DvisvgmSpecialHandler.hpp DvisvgmSpecialHandler.cpp \ - DVIToSVG.hpp DVIToSVG.cpp DVIToSVGActions.hpp \ - DVIToSVGActions.cpp EmSpecialHandler.hpp EmSpecialHandler.cpp \ + ColorSpecialHandler.cpp CommandLine.hpp Directory.hpp \ + Directory.cpp DVIActions.hpp DLLoader.hpp DLLoader.cpp \ + DVIReader.hpp DVIReader.cpp DvisvgmSpecialHandler.hpp \ + DvisvgmSpecialHandler.cpp DVIToSVG.hpp DVIToSVG.cpp \ + DVIToSVGActions.hpp DVIToSVGActions.cpp EllipticalArc.hpp \ + EllipticalArc.cpp EmSpecialHandler.hpp EmSpecialHandler.cpp \ EncFile.hpp EncFile.cpp EPSFile.hpp EPSFile.cpp EPSToSVG.hpp \ FileFinder.hpp FileFinder.cpp FilePath.hpp FilePath.cpp \ FileSystem.hpp FileSystem.cpp FixWord.hpp Font.hpp Font.cpp \ @@ -778,7 +779,6 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CMap.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CMapManager.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CMapReader.Plo@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CRC32.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Calculator.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/CharMapID.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Color.Plo@am__quote@ # am--include-marker @@ -790,6 +790,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Directory.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DvisvgmSpecialHandler.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/EPSFile.Plo@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/EllipticalArc.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/EmSpecialHandler.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/EncFile.Plo@am__quote@ # am--include-marker @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FileFinder.Plo@am__quote@ # am--include-marker @@ -1137,7 +1138,6 @@ distclean: distclean-recursive -rm -f ./$(DEPDIR)/CMap.Plo -rm -f ./$(DEPDIR)/CMapManager.Plo -rm -f ./$(DEPDIR)/CMapReader.Plo - -rm -f ./$(DEPDIR)/CRC32.Plo -rm -f ./$(DEPDIR)/Calculator.Plo -rm -f ./$(DEPDIR)/CharMapID.Plo -rm -f ./$(DEPDIR)/Color.Plo @@ -1149,6 +1149,7 @@ distclean: distclean-recursive -rm -f ./$(DEPDIR)/Directory.Plo -rm -f ./$(DEPDIR)/DvisvgmSpecialHandler.Plo -rm -f ./$(DEPDIR)/EPSFile.Plo + -rm -f ./$(DEPDIR)/EllipticalArc.Plo -rm -f ./$(DEPDIR)/EmSpecialHandler.Plo -rm -f ./$(DEPDIR)/EncFile.Plo -rm -f ./$(DEPDIR)/FileFinder.Plo @@ -1278,7 +1279,6 @@ maintainer-clean: maintainer-clean-recursive -rm -f ./$(DEPDIR)/CMap.Plo -rm -f ./$(DEPDIR)/CMapManager.Plo -rm -f ./$(DEPDIR)/CMapReader.Plo - -rm -f ./$(DEPDIR)/CRC32.Plo -rm -f ./$(DEPDIR)/Calculator.Plo -rm -f ./$(DEPDIR)/CharMapID.Plo -rm -f ./$(DEPDIR)/Color.Plo @@ -1290,6 +1290,7 @@ maintainer-clean: maintainer-clean-recursive -rm -f ./$(DEPDIR)/Directory.Plo -rm -f ./$(DEPDIR)/DvisvgmSpecialHandler.Plo -rm -f ./$(DEPDIR)/EPSFile.Plo + -rm -f ./$(DEPDIR)/EllipticalArc.Plo -rm -f ./$(DEPDIR)/EmSpecialHandler.Plo -rm -f ./$(DEPDIR)/EncFile.Plo -rm -f ./$(DEPDIR)/FileFinder.Plo diff --git a/dviware/dvisvgm/src/MapLine.cpp b/dviware/dvisvgm/src/MapLine.cpp index 483fec4b71..57aa116f02 100644 --- a/dviware/dvisvgm/src/MapLine.cpp +++ b/dviware/dvisvgm/src/MapLine.cpp @@ -221,9 +221,7 @@ void MapLine::parseDVIPDFMLine (InputReader &ir) { throw_number_expected('w', true); break; default: - ostringstream oss; - oss << "invalid option: -" << option; - throw MapLineException(oss.str()); + throw MapLineException("invalid option: -" + string(1, option)); } ir.skipSpace(); } diff --git a/dviware/dvisvgm/src/Matrix.cpp b/dviware/dvisvgm/src/Matrix.cpp index 7b425596e9..f94e84809c 100644 --- a/dviware/dvisvgm/src/Matrix.cpp +++ b/dviware/dvisvgm/src/Matrix.cpp @@ -396,11 +396,8 @@ Matrix Matrix::parse (istream &is, Calculator &calc) { if (c != 'X' && c != 'Y') throw ParserException("transformation command 'K' must be followed by 'X' or 'Y'"); double a = getArgument(is, calc, 0, false, false); - if (std::abs(cos(deg2rad(a))) < numeric_limits<double>::epsilon()) { - ostringstream oss; - oss << "illegal skewing angle: " << a << " degrees"; - throw ParserException(oss.str()); - } + if (std::abs(cos(deg2rad(a))) < numeric_limits<double>::epsilon()) + throw ParserException("illegal skewing angle: " + util::to_string(a) + " degrees"); if (c == 'X') ret.xskewByAngle(a); else @@ -419,9 +416,7 @@ Matrix Matrix::parse (istream &is, Calculator &calc) { break; } default: - ostringstream oss; - oss << "transformation command expected (found '" << char(cmd) << "' instead)"; - throw ParserException(oss.str()); + throw ParserException("transformation command expected (found '" + string(1, cmd) + "' instead)"); } } return ret; @@ -606,4 +601,4 @@ XSkewingMatrix::XSkewingMatrix (double deg) { YSkewingMatrix::YSkewingMatrix (double deg) { double xyratio = tan(deg2rad(deg)); lmultiply(Matrix({1, 0, 0, xyratio})); -}
\ No newline at end of file +} diff --git a/dviware/dvisvgm/src/MetafontWrapper.cpp b/dviware/dvisvgm/src/MetafontWrapper.cpp index dfb7c9b465..5807d7254e 100644 --- a/dviware/dvisvgm/src/MetafontWrapper.cpp +++ b/dviware/dvisvgm/src/MetafontWrapper.cpp @@ -18,7 +18,6 @@ ** along with this program; if not, see <http://www.gnu.org/licenses/>. ** *************************************************************************/ -#include <cstdlib> #include <cctype> #include <fstream> #include <sstream> @@ -38,7 +37,7 @@ MetafontWrapper::MetafontWrapper (string fname, string dir) // ensure that folder paths ends with slash if (_dir.empty()) _dir = "./"; - else if (_dir != "/" && dir[dir.length()-1] != '/') + else if (_dir != "/" && _dir.back() != '/') _dir += '/'; } @@ -88,7 +87,7 @@ bool MetafontWrapper::call (const string &mode, double mag) { iss.getline(buf, sizeof(buf)); string line = buf; if (line.substr(0, 3) == ">> ") { - resolution = atoi(line.substr(3).c_str()); + resolution = stoi(line.substr(3)); break; } } diff --git a/dviware/dvisvgm/src/MiKTeXCom.hpp b/dviware/dvisvgm/src/MiKTeXCom.hpp index c87d0e3d86..5bf1981167 100644 --- a/dviware/dvisvgm/src/MiKTeXCom.hpp +++ b/dviware/dvisvgm/src/MiKTeXCom.hpp @@ -25,10 +25,10 @@ #include "windows.hpp" #ifdef _MSC_VER -#import <MiKTeX209-session.tlb> +#import <miktex-session.tlb> using namespace MiKTeXSession2_9; #else -#include "miktex209-session.h" +#include "miktex-session.h" #endif class MiKTeXCom { diff --git a/dviware/dvisvgm/src/PDFParser.cpp b/dviware/dvisvgm/src/PDFParser.cpp index 12637a6368..1ecd40cba8 100644 --- a/dviware/dvisvgm/src/PDFParser.cpp +++ b/dviware/dvisvgm/src/PDFParser.cpp @@ -168,10 +168,8 @@ static string parse_literal_string (InputReader &ir) { static char get_hex_digit (InputReader &ir) { int c = ir.get(); if (isxdigit(c)) - return static_cast<char>(c); - ostringstream oss; - oss << "invalid hexadecimal digit '" << static_cast<char>(c) << "'"; - throw PDFException(oss.str()); + return char(c); + throw PDFException("invalid hexadecimal digit '" + string(1, char(c)) + "'"); } diff --git a/dviware/dvisvgm/src/PDFToSVG.hpp b/dviware/dvisvgm/src/PDFToSVG.hpp index f999de88e8..d1871d0bb1 100644 --- a/dviware/dvisvgm/src/PDFToSVG.hpp +++ b/dviware/dvisvgm/src/PDFToSVG.hpp @@ -60,4 +60,3 @@ class PDFToSVG : public ImageToSVG { }; #endif - diff --git a/dviware/dvisvgm/src/PSInterpreter.cpp b/dviware/dvisvgm/src/PSInterpreter.cpp index e9d33d7265..ba368f17ee 100644 --- a/dviware/dvisvgm/src/PSInterpreter.cpp +++ b/dviware/dvisvgm/src/PSInterpreter.cpp @@ -51,8 +51,15 @@ void PSInterpreter::init () { "-dWRITESYSTEMDICT", // leave systemdict writable as some operators must be replaced "-dNOPROMPT" }; - if (int gsrev = _gs.revision()) + if (int gsrev = _gs.revision()) { gsargs.emplace_back(gsrev == 922 ? "-dREALLYDELAYBIND" : "-dDELAYBIND"); + // As of GS 9.50, -dSAFER is active by default which leads to warnings + // in conjunction with -dDELAYBIND and -dWRITESYSTEMDICT. + // Thus, -dDELAYSAFER (or -dNOSAFER) must be added. + // https://www.ghostscript.com/doc/9.50/Use.htm#Safer + if (gsrev >= 950) + gsargs.emplace_back("-dDELAYSAFER"); + } _gs.init(gsargs.size(), gsargs.data(), this); _gs.set_stdio(input, output, error); _initialized = true; diff --git a/dviware/dvisvgm/src/Pair.hpp b/dviware/dvisvgm/src/Pair.hpp index 5804a2b467..5dd7c68a93 100644 --- a/dviware/dvisvgm/src/Pair.hpp +++ b/dviware/dvisvgm/src/Pair.hpp @@ -26,17 +26,21 @@ #include "macros.hpp" template <typename T> -class Pair -{ +class Pair { public: explicit Pair (T x=0, T y=0) : _x(x), _y(y) {} + template <typename U> Pair (const Pair<U> &p) : _x(U(p.x())), _y(U(p.y())) {} + Pair (const Pair &p) =default; + Pair (Pair &&p) =default; + Pair& operator = (const Pair &p) =default; + Pair& operator = (Pair &&p) =default; Pair operator += (const Pair &p) {_x += p._x; _y += p._y; return *this;} Pair operator -= (const Pair &p) {_x -= p._x; _y -= p._y; return *this;} Pair operator *= (T c) {_x *= c; _y *= c; return *this;} Pair operator /= (T c) {_x /= c; _y /= c; return *this;} Pair operator - () const {return Pair(-_x, -_y);} Pair ortho () const {return Pair(-_y, _x);} - double length () const {return std::sqrt(_x*_x + _y*_y);} + double length () const {return std::hypot(_x, _y);} bool operator == (const Pair &p) const {return _x == p._x && _y == p._y;} bool operator != (const Pair &p) const {return _x != p._x || _y != p._y;} T x () const {return _x;} @@ -54,6 +58,18 @@ inline Pair<T> abs (const Pair<T> &p) { return Pair<T>(std::abs(p.x()), std::abs(p.y())); } +/** Returns the dot product of two 2D vectors. */ +template <typename T> +inline T dot (const Pair<T> &p1, const Pair<T> &p2) { + return p1.x()*p1.y() + p1.y()*p2.y(); +} + +/** Returns the determinant of two 2D vectors. */ +template <typename T> +inline T det (const Pair<T> &p1, const Pair<T> &p2) { + return p1.x()*p2.y() - p1.y()*p2.x(); +} + struct Pair32 : public Pair<int32_t> { explicit Pair32 (int32_t x=0, int32_t y=0) : Pair<int32_t>(x, y) {} explicit Pair32 (double x, double y) : Pair<int32_t>(lround(x), lround(y)) {} @@ -62,6 +78,10 @@ struct Pair32 : public Pair<int32_t> { typedef Pair<double> DPair; +inline DPair round (const DPair &p) { + return DPair(std::lround(p.x()), std::lround(p.y())); +} + template <typename T> IMPLEMENT_ARITHMETIC_OPERATOR(Pair<T>, +) diff --git a/dviware/dvisvgm/src/PathClipper.cpp b/dviware/dvisvgm/src/PathClipper.cpp index 1663e7ae36..f646289d25 100644 --- a/dviware/dvisvgm/src/PathClipper.cpp +++ b/dviware/dvisvgm/src/PathClipper.cpp @@ -49,7 +49,7 @@ inline DPair to_DPair (const IntPoint &p) { /** In order to flatten a curved path, all path segements are processed sequentially. * Depending on the type of the segment, one of the methods provided by this class * is called. */ -class FlattenActions : public CurvedPath::Actions { +class FlattenActions : public CurvedPath::IterationActions { public: FlattenActions (vector<Bezier> &curves, Polygons &polygons, int &numLines) : _polygons(polygons), _curves(curves), _numLines(numLines) {} @@ -73,7 +73,7 @@ class FlattenActions : public CurvedPath::Actions { _currentPoint = p; } - void conicto (const CurvedPath::Point &p1, const CurvedPath::Point &p2) override { + void quadto (const CurvedPath::Point &p1, const CurvedPath::Point &p2) override { Bezier bezier(_currentPoint, p1, p2); addCurvePoints(bezier); } @@ -206,7 +206,7 @@ static double division_ratio (const IntPoint &p1, const IntPoint &p2, const IntP /** Returns the label of point q that lies on the line between points p1 and p2. */ inline ZLabel division_label (const IntPoint &p1, const IntPoint &p2, const IntPoint &q) { - double t1, t2; + double t1=0, t2=0; double s=0; int32_t id = segment_id(p1, p2, t1, t2); if (id > 0) diff --git a/dviware/dvisvgm/src/PsSpecialHandler.cpp b/dviware/dvisvgm/src/PsSpecialHandler.cpp index 6d5c906a8c..871a34b4f2 100644 --- a/dviware/dvisvgm/src/PsSpecialHandler.cpp +++ b/dviware/dvisvgm/src/PsSpecialHandler.cpp @@ -25,6 +25,7 @@ #include <sstream> #include "EPSFile.hpp" #include "FileFinder.hpp" +#include "FilePath.hpp" #include "FileSystem.hpp" #include "Message.hpp" #include "PathClipper.hpp" @@ -183,6 +184,15 @@ void PsSpecialHandler::preprocess (const string &prefix, istream &is, SpecialAct } +static string filename_suffix (const string &fname) { + string ret; + size_t pos = fname.rfind('.'); + if (pos != string::npos) + ret = util::tolower(fname.substr(pos+1)); + return ret; +} + + bool PsSpecialHandler::process (const string &prefix, istream &is, SpecialActions &actions) { // process PS headers only once (in prescan) if (prefix == "!" || prefix == "header=") @@ -204,9 +214,22 @@ bool PsSpecialHandler::process (const string &prefix, istream &is, SpecialAction if (_actions) { StreamInputReader in(is); const string fname = in.getQuotedString(in.peek() == '"' ? "\"" : nullptr); + FileType fileType = FileType::EPS; + if (prefix == "pdffile") + fileType = FileType::PDF; + else { + // accept selected non-PS files in psfile special + string ext = filename_suffix(fname); + if (ext == "pdf") + fileType = FileType::PDF; + else if (ext == "svg") + fileType = FileType::SVG; + else if (ext == "jpg" || ext == "jpeg" || ext == "png") + fileType = FileType::BITMAP; + } map<string,string> attr; in.parseAttributes(attr, false); - imgfile(prefix == "pdffile=" ? FileType::PDF : FileType::EPS, fname, attr); + imgfile(fileType, fname, attr); } } else if (prefix == "ps::") { @@ -290,7 +313,9 @@ void PsSpecialHandler::imgfile (FileType filetype, const string &fname, const ma double ury = (it = attr.find("ury")) != attr.end() ? stod(it->second) : 0; int pageno = (it = attr.find("page")) != attr.end() ? stoi(it->second, nullptr, 10) : 1; - if (filetype == FileType::PDF && llx == 0 && lly == 0 && urx == 0 && ury == 0) { + if (filetype == FileType::BITMAP || filetype == FileType::SVG) + swap(lly, ury); + else if (filetype == FileType::PDF && llx == 0 && lly == 0 && urx == 0 && ury == 0) { BoundingBox pagebox = _psi.pdfPageBox(fname, pageno); if (pagebox.valid()) { llx = pagebox.minX(); @@ -337,26 +362,12 @@ void PsSpecialHandler::imgfile (FileType filetype, const string &fname, const ma _actions->setY(0); moveToDVIPos(); - // clip image to its bounding box if flag 'clip' is given - string rectclip; - if (clipToBbox) - rectclip = to_string(llx)+" "+to_string(lly)+" "+to_string(urx-llx)+" "+to_string(ury-lly)+" rectclip"; - - auto groupNode = util::make_unique<XMLElement>("g"); // put SVG nodes created from the EPS/PDF file in this group - _xmlnode = groupNode.get(); - _psi.execute( - "\n@beginspecial @setspecial" // enter special environment - "/setpagedevice{@setpagedevice}def " // activate processing of operator "setpagedevice" - "matrix setmatrix" // don't apply outer PS transformations - "/FirstPage "+to_string(pageno)+" def" // set number of fisrt page to convert (PDF only) - "/LastPage "+to_string(pageno)+" def " // set number of last page to convert (PDF only) - +rectclip+ // clip to bounding box (if requexted by attribute 'clip') - "("+ filepath + ")run " // execute file content - "@endspecial " // leave special environment - ); - if (!groupNode->empty()) { // has anything been drawn? + auto imgNode = createImageNode(filetype, filepath, pageno, BoundingBox(llx, lly, urx, ury), clipToBbox); + if (imgNode) { // has anything been drawn? Matrix matrix(1); - matrix.scale(sx, -sy).rotate(-angle).scale(hscale/100, vscale/100); // apply transformation attributes + if (filetype == FileType::EPS || filetype == FileType::PDF) + sy = -sy; // adapt orientation of y-coordinates + matrix.scale(sx, sy).rotate(-angle).scale(hscale/100, vscale/100); // apply transformation attributes matrix.translate(x+hoffset, y-voffset); // move image to current DVI position matrix.lmultiply(_actions->getMatrix()); @@ -365,14 +376,12 @@ void PsSpecialHandler::imgfile (FileType filetype, const string &fname, const ma bbox.transform(matrix); _actions->embed(bbox); - // insert group containing SVG nodes created from image + // insert element containing the image data matrix.rmultiply(TranslationMatrix(-llx, -lly)); // move lower left corner of image to origin if (!matrix.isIdentity()) - groupNode->addAttribute("transform", matrix.toSVG()); - _actions->svgTree().appendToPage(std::move(groupNode)); + imgNode->addAttribute("transform", matrix.toSVG()); + _actions->svgTree().appendToPage(std::move(imgNode)); } - _xmlnode = nullptr; // append following elements to page group again - // restore DVI position _actions->setX(x); _actions->setY(y); @@ -380,6 +389,52 @@ void PsSpecialHandler::imgfile (FileType filetype, const string &fname, const ma } +/** Creates an XML element containing the image data depending on the file type. + * @param[in] type file type of the image + * @param[in] pathstr absolute path to image file + * @param[in] pageno number of page to process (PDF only) + * @param[in] bbox bounding box of the image + * @param[in] clip if true, the image is clipped to its bounding box + * @return pointer to the element or nullptr if there's no image data */ +unique_ptr<XMLElement> PsSpecialHandler::createImageNode (FileType type, const string &pathstr, int pageno, BoundingBox bbox, bool clip) { + unique_ptr<XMLElement> node; + if (type == FileType::BITMAP || type == FileType::SVG) { + node = util::make_unique<XMLElement>("image"); + node->addAttribute("x", 0); + node->addAttribute("y", 0); + node->addAttribute("width", bbox.width()); + node->addAttribute("height", bbox.height()); + // add path to image file relative to SVG file being generated + FilePath imgPath(pathstr); + string svgPathStr = _actions->getSVGFilePath(pageno).absolute(false); + node->addAttribute("xlink:href", imgPath.relative(svgPathStr)); + } + else { // PostScript or PDF + // clip image to its bounding box if flag 'clip' is given + string rectclip; + if (clip) + rectclip = to_string(bbox.minX())+" "+to_string(bbox.minY())+" "+to_string(bbox.width())+" "+to_string(bbox.height())+" rectclip"; + + node = util::make_unique<XMLElement>("g"); // put SVG nodes created from the EPS/PDF file in this group + _xmlnode = node.get(); + _psi.execute( + "\n@beginspecial @setspecial" // enter special environment + "/setpagedevice{@setpagedevice}def " // activate processing of operator "setpagedevice" + "matrix setmatrix" // don't apply outer PS transformations + "/FirstPage "+to_string(pageno)+" def" // set number of first page to convert (PDF only) + "/LastPage "+to_string(pageno)+" def " // set number of last page to convert (PDF only) + +rectclip+ // clip to bounding box (if requexted by attribute 'clip') + "(" + pathstr + ")run " // execute file content + "@endspecial " // leave special environment + ); + if (node->empty()) + node.reset(nullptr); + _xmlnode = nullptr; // append following elements to page group again + } + return node; +} + + /** Apply transformation to width, height, and depth set by preview package. * @param[in] matrix transformation matrix to apply * @param[out] w width @@ -394,15 +449,17 @@ static bool transform_box_extents (const Matrix &matrix, double &w, double &h, d return false; // => non-horizontal baseline, can't compute meaningful extents if (ex.y() == 0) // horizontal scaling or skewing? - w *= fabs(ex.x()); + w *= abs(ex.x()); if (ey.x()==0 || ex.y()==0) { // vertical scaling? - if (ey.y() < 0) swap(h, d); - if (double sy = fabs(ey.y())/ey.length()) { - h *= fabs(ey.y()/sy); - d *= fabs(ey.y()/sy); - } - else + if (ey.y() < 0) + swap(h, d); + double sy = abs(ey.y())/ey.length(); + if (sy < 1e-8) h = d = 0; + else { + h *= abs(ey.y()/sy); + d *= abs(ey.y()/sy); + } } return true; } @@ -600,13 +657,11 @@ void PsSpecialHandler::stroke (vector<double> &p) { if (_blendmode > 0 && _blendmode < 16) path->addAttribute("style", "mix-blend-mode:"+css_blendmode_name(_blendmode)); if (!_dashpattern.empty()) { - ostringstream oss; - for (size_t i=0; i < _dashpattern.size(); i++) { - if (i > 0) - oss << ','; - oss << XMLString(_dashpattern[i]); - } - path->addAttribute("stroke-dasharray", oss.str()); + string patternStr; + for (double dashValue : _dashpattern) + patternStr += XMLString(dashValue)+","; + patternStr.pop_back(); + path->addAttribute("stroke-dasharray", patternStr); if (_dashoffset != 0) path->addAttribute("stroke-dashoffset", _dashoffset); } @@ -690,7 +745,7 @@ static void create_matrix (vector<double> &v, int startindex, Matrix &matrix) { // using corresponding values of the identity matrix. if (v.size()-startindex < 6) { v.resize(6+startindex); - for (int i=v.size()-startindex; i < 6; ++i) + for (size_t i=v.size()-startindex; i < 6; ++i) v[i+startindex] = (i%3 ? 0 : 1); } // PS matrix [a b c d e f] equals ((a,b,0),(c,d,0),(e,f,1)). @@ -1007,16 +1062,14 @@ void PsSpecialHandler::processSequentialPatchMesh (int shadingTypeID, ColorSpace #if 0 if (bgcolorGiven) { // fill whole patch area with given background color - GraphicsPath<double> outline; - patch->getBoundaryPath(outline); + GraphicsPath<double> outline = patch->getBoundaryPath(); callback.patchSegment(outline, bgcolor); } #endif patch->approximate(SHADING_SEGMENT_SIZE, SHADING_SEGMENT_OVERLAP, SHADING_SIMPLIFY_DELTA, callback); if (!_xmlnode) { // update bounding box - BoundingBox bbox; - patch->getBBox(bbox); + BoundingBox bbox = patch->getBBox(); bbox.transform(_actions->getMatrix()); _actions->embed(bbox); } diff --git a/dviware/dvisvgm/src/PsSpecialHandler.hpp b/dviware/dvisvgm/src/PsSpecialHandler.hpp index 4c06e6a1b6..27da9c8471 100644 --- a/dviware/dvisvgm/src/PsSpecialHandler.hpp +++ b/dviware/dvisvgm/src/PsSpecialHandler.hpp @@ -77,7 +77,7 @@ class PsSpecialHandler : public SpecialHandler, protected PSActions { }; enum PsSection {PS_NONE, PS_HEADERS, PS_BODY}; - enum class FileType {EPS, PDF}; + enum class FileType {EPS, PDF, SVG, BITMAP}; public: PsSpecialHandler (); @@ -104,6 +104,7 @@ class PsSpecialHandler : public SpecialHandler, protected PSActions { void executeAndSync (std::istream &is, bool updatePos); void processHeaderFile (const char *fname); void imgfile (FileType type, const std::string &fname, const std::map<std::string,std::string> &attr); + std::unique_ptr<XMLElement> createImageNode (FileType type, const std::string &pathstr, int pageno, BoundingBox bbox, bool clip); void dviEndPage (unsigned pageno, SpecialActions &actions) override; void clip (Path path, bool evenodd); void processSequentialPatchMesh (int shadingTypeID, ColorSpace cspace, VectorIterator<double> &it); diff --git a/dviware/dvisvgm/src/SVGOutput.cpp b/dviware/dvisvgm/src/SVGOutput.cpp index 469e5f5af2..7c4108c876 100644 --- a/dviware/dvisvgm/src/SVGOutput.cpp +++ b/dviware/dvisvgm/src/SVGOutput.cpp @@ -46,8 +46,8 @@ SVGOutput::SVGOutput (const string &base, string pattern, int zipLevel) * @param[in] hash hash value of the current page * @return output stream for the given page */ ostream& SVGOutput::getPageStream (int page, int numPages, const HashTriple &hashes) const { - string fname = filename(page, numPages, hashes); - if (fname.empty()) { + FilePath path = filepath(page, numPages, hashes); + if (path.empty()) { if (_zipLevel == 0) { _osptr.reset(); return cout; @@ -63,52 +63,35 @@ ostream& SVGOutput::getPageStream (int page, int numPages, const HashTriple &has _page = page; if (_zipLevel > 0) - _osptr = util::make_unique<ZLibOutputFileStream>(fname, ZLIB_GZIP, _zipLevel); + _osptr = util::make_unique<ZLibOutputFileStream>(path.absolute(), ZLIB_GZIP, _zipLevel); else - _osptr = util::make_unique<ofstream>(fname); + _osptr = util::make_unique<ofstream>(path.absolute()); if (!_osptr) - throw MessageException("can't open file "+fname+" for writing"); + throw MessageException("can't open file "+path.shorterAbsoluteOrRelative()+" for writing"); return *_osptr; } -/** Returns the name of the SVG file containing the given page. +/** Returns the path of the SVG file containing the given page number. * @param[in] page number of current page * @param[in] numPages total number of pages * @param[in] hash hash value of current page */ -string SVGOutput::filename (int page, int numPages, const HashTriple &hashes) const { - if (_stdout) - return ""; - - string expanded_pattern = util::trim(expandFormatString(_pattern, page, numPages, hashes)); - // set and expand default pattern if necessary - if (expanded_pattern.empty()) { - string pattern = hashes.empty() ? (numPages > 1 ? "%f-%p" : "%f") : "%f-%hd"; - expanded_pattern = expandFormatString(pattern, page, numPages, hashes); +FilePath SVGOutput::filepath (int page, int numPages, const HashTriple &hashes) const { + FilePath outpath; + if (!_stdout) { + string expanded_pattern = util::trim(expandFormatString(_pattern, page, numPages, hashes)); + // set and expand default pattern if necessary + if (expanded_pattern.empty()) { + string pattern = hashes.empty() ? (numPages > 1 ? "%f-%p" : "%f") : "%f-%hd"; + expanded_pattern = expandFormatString(pattern, page, numPages, hashes); + } + // append suffix if necessary + outpath.set(expanded_pattern, true); + if (outpath.suffix().empty()) + outpath.suffix(_zipLevel > 0 ? "svgz" : "svg"); } - // append suffix if necessary - FilePath outpath(expanded_pattern, true); - if (outpath.suffix().empty()) - outpath.suffix(_zipLevel > 0 ? "svgz" : "svg"); - string abspath = outpath.absolute(); - string relpath = outpath.relative(); - return abspath.length() < relpath.length() ? abspath : relpath; -} - - -#if 0 -string SVGOutput::outpath (int page, int numPages) const { - string path = filename(page, numPages); - if (path.empty()) - return ""; - size_t pos = path.rfind('/'); - if (pos == string::npos) - return "."; - if (pos == 0) - return "/"; - return path.substr(0, pos); + return outpath; } -#endif /** Replaces expressions in a given string by the corresponding values and returns the result. @@ -174,9 +157,7 @@ string SVGOutput::expandFormatString (string str, int page, int numPages, const result += oss.str(); } catch (CalculatorException &e) { - oss.str(""); - oss << "error in filename pattern (" << e.what() << ")"; - throw MessageException(oss.str()); + throw MessageException("error in filename pattern (" + string(e.what()) + ")"); } pos = endpos; } diff --git a/dviware/dvisvgm/src/SVGOutput.hpp b/dviware/dvisvgm/src/SVGOutput.hpp index 38193dbdc8..3c382f0b7f 100644 --- a/dviware/dvisvgm/src/SVGOutput.hpp +++ b/dviware/dvisvgm/src/SVGOutput.hpp @@ -46,7 +46,7 @@ struct SVGOutputBase { virtual ~SVGOutputBase () =default; virtual std::ostream& getPageStream (int page, int numPages, const HashTriple &hashes=HashTriple()) const =0; - virtual std::string filename (int page, int numPages, const HashTriple &hashes=HashTriple()) const =0; + virtual FilePath filepath (int page, int numPages, const HashTriple &hashes= HashTriple()) const =0; virtual bool ignoresHashes () const {return true;} }; @@ -58,7 +58,7 @@ class SVGOutput : public SVGOutputBase { SVGOutput (const std::string &base, const std::string &pattern) : SVGOutput(base, pattern, 0) {} SVGOutput (const std::string &base, std::string pattern, int zipLevel); std::ostream& getPageStream (int page, int numPages, const HashTriple &hash=HashTriple()) const override; - std::string filename (int page, int numPages, const HashTriple &hash=HashTriple()) const override; + FilePath filepath (int page, int numPages, const HashTriple &hash=HashTriple()) const override; bool ignoresHashes () const override; protected: diff --git a/dviware/dvisvgm/src/ShadingPatch.hpp b/dviware/dvisvgm/src/ShadingPatch.hpp index a39edc5100..4346301e7b 100644 --- a/dviware/dvisvgm/src/ShadingPatch.hpp +++ b/dviware/dvisvgm/src/ShadingPatch.hpp @@ -42,8 +42,8 @@ class ShadingPatch { virtual ~ShadingPatch () =default; virtual int psShadingType () const =0; virtual void approximate (int gridsize, bool overlap, double delta, Callback &callback) const =0; - virtual void getBBox (BoundingBox &bbox) const =0; - virtual void getBoundaryPath (GraphicsPath<double> &path) const =0; + virtual BoundingBox getBBox () const =0; + virtual GraphicsPath<double> getBoundaryPath () const =0; virtual void setPoints (const PointVec &points, int edgeflag, ShadingPatch *patch) =0; virtual void setColors (const ColorVec &colors, int edgeflag, ShadingPatch *patch) =0; virtual int numPoints (int edgeflag) const =0; diff --git a/dviware/dvisvgm/src/SpecialActions.hpp b/dviware/dvisvgm/src/SpecialActions.hpp index 0f7a43074d..3a510e9ea3 100644 --- a/dviware/dvisvgm/src/SpecialActions.hpp +++ b/dviware/dvisvgm/src/SpecialActions.hpp @@ -25,6 +25,7 @@ #include <memory> #include "BoundingBox.hpp" #include "Color.hpp" +#include "FilePath.hpp" #include "Matrix.hpp" #include "SVGTree.hpp" @@ -52,7 +53,7 @@ class SpecialActions { virtual void embed (const BoundingBox &bbox) =0; virtual void embed (const DPair &p, double r=0) =0; virtual unsigned getCurrentPageNumber () const =0; - virtual std::string getSVGFilename (unsigned pageno) const =0; + virtual FilePath getSVGFilePath (unsigned pageno) const =0; virtual std::string getBBoxFormatString () const =0; virtual void progress (const char *id) {} virtual int getDVIStackDepth () const {return 0;} @@ -80,7 +81,7 @@ class EmptySpecialActions : public SpecialActions { void embed (const BoundingBox &bbox) override {} void embed (const DPair &p, double r=0) override {} unsigned getCurrentPageNumber() const override {return 0;} - std::string getSVGFilename (unsigned pageno) const override {return "";} + FilePath getSVGFilePath (unsigned pageno) const override {return FilePath();} std::string getBBoxFormatString () const override {return "";} private: diff --git a/dviware/dvisvgm/src/StreamReader.cpp b/dviware/dvisvgm/src/StreamReader.cpp index f06dcd42dd..ccfb176abe 100644 --- a/dviware/dvisvgm/src/StreamReader.cpp +++ b/dviware/dvisvgm/src/StreamReader.cpp @@ -20,8 +20,9 @@ #include <algorithm> #include <vector> -#include "CRC32.hpp" +#include "HashFunction.hpp" #include "StreamReader.hpp" +#include "utility.hpp" using namespace std; @@ -46,13 +47,13 @@ uint32_t StreamReader::readUnsigned (int bytes) { } -/** Reads an unsigned integer from assigned input stream and updates the CRC32 checksum. - * @param[in] bytes number of bytes to read (max. 4) - * @param[in,out] crc32 checksum to be updated +/** Reads an unsigned integer from assigned input stream and updates the hash value. + * @param[in] n number of bytes to read (max. 4) + * @param[in,out] hashfunc hash to update * @return read integer */ -uint32_t StreamReader::readUnsigned (int bytes, CRC32 &crc32) { - uint32_t ret = readUnsigned(bytes); - crc32.update(ret, bytes); +uint32_t StreamReader::readUnsigned (int n, HashFunction &hashfunc) { + uint32_t ret = readUnsigned(n); + hashfunc.update(util::bytes(ret, n)); return ret; } @@ -70,13 +71,13 @@ int32_t StreamReader::readSigned (int bytes) { } -/** Reads an signed integer from assigned input stream and updates the CRC32 checksum. +/** Reads an signed integer from assigned input stream and updates the hash value. * @param[in] bytes number of bytes to read (max. 4) - * @param[in,out] crc32 checksum to be updated + * @param[in,out] hashfunc hash to update * @return read integer */ -int32_t StreamReader::readSigned (int bytes, CRC32 &crc32) { - int32_t ret = readSigned(bytes); - crc32.update(uint32_t(ret), bytes); +int32_t StreamReader::readSigned (int n, HashFunction &hashfunc) { + int32_t ret = readSigned(n); + hashfunc.update(util::bytes(ret, n)); return ret; } @@ -93,15 +94,15 @@ string StreamReader::readString () { } -/** Reads a string terminated by a 0-byte and updates the CRC32 checksum. - * @param[in,out] crc32 checksum to be updated +/** Reads a string terminated by a 0-byte and updates the hash value. + * @param[in,out] hashfunc hash to update * @param[in] finalZero consider final 0-byte in checksum * @return the string read */ -string StreamReader::readString (CRC32 &crc32, bool finalZero) { +string StreamReader::readString (HashFunction &hashfunc, bool finalZero) { string ret = readString(); - crc32.update(reinterpret_cast<const uint8_t*>(ret.data()), ret.length()); + hashfunc.update(ret.data(), ret.length()); if (finalZero) - crc32.update(0, 1); + hashfunc.update(0, 1); return ret; } @@ -119,36 +120,37 @@ string StreamReader::readString (int length) { } -/** Reads a string of a given length and updates the CRC32 checksum. +/** Reads a string of a given length and updates the hash value. * @param[in] length number of characters to read - * @param[in,out] crc32 checksum to be updated + * @param[in,out] hashfunc hash to update * @return the string read */ -string StreamReader::readString (int length, CRC32 &crc32) { +string StreamReader::readString (int length, HashFunction &hashfunc) { string ret = readString(length); - crc32.update(reinterpret_cast<const uint8_t*>(ret.data()), length); + hashfunc.update(ret.data(), length); return ret; } -vector<uint8_t>& StreamReader::readBytes (int n, vector<uint8_t> &bytes) { +vector<uint8_t> StreamReader::readBytes (int n) { + vector<uint8_t> bytes(n); if (n > 0) - _is->read((char*)&bytes[0], n); + _is->read(reinterpret_cast<char*>(bytes.data()), n); return bytes; } -vector<uint8_t>& StreamReader::readBytes (int n, vector<uint8_t> &bytes, CRC32 &crc32) { - readBytes(n, bytes); - crc32.update(&bytes[0], bytes.size()); +vector<uint8_t> StreamReader::readBytes (int n, HashFunction &hashfunc) { + vector<uint8_t> bytes = readBytes(n); + hashfunc.update(bytes); return bytes; } -int StreamReader::readByte (CRC32 &crc32) { +int StreamReader::readByte (HashFunction &hashfunc) { int ret = readByte(); if (ret >= 0) { - const uint8_t c = uint8_t(ret & 0xff); - crc32.update(&c, 1); + char c = ret & 0xff; + hashfunc.update(&c, 1); } return ret; } diff --git a/dviware/dvisvgm/src/StreamReader.hpp b/dviware/dvisvgm/src/StreamReader.hpp index 2b95f653ca..05c4e1e701 100644 --- a/dviware/dvisvgm/src/StreamReader.hpp +++ b/dviware/dvisvgm/src/StreamReader.hpp @@ -26,7 +26,7 @@ #include <vector> #include "MessageException.hpp" -class CRC32; +class HashFunction; class StreamReader { public: @@ -37,17 +37,17 @@ class StreamReader { void clearStream () {_is->clear();} std::istream& replaceStream (std::istream &s); uint32_t readUnsigned (int n); - uint32_t readUnsigned (int n, CRC32 &crc32); + uint32_t readUnsigned (int n, HashFunction &hashfunc); int32_t readSigned (int n); - int32_t readSigned (int n, CRC32 &crc32); + int32_t readSigned (int n, HashFunction &hashfunc); std::string readString (); - std::string readString (CRC32 &crc32, bool finalZero=false); + std::string readString (HashFunction &hashfunc, bool finalZero=false); std::string readString (int length); - std::string readString (int length, CRC32 &crc32); - std::vector<uint8_t>& readBytes (int n, std::vector<uint8_t> &bytes); - std::vector<uint8_t>& readBytes (int n, std::vector<uint8_t> &bytes, CRC32 &crc32); + std::string readString (int length, HashFunction &hashfunc); + std::vector<uint8_t> readBytes (int n); + std::vector<uint8_t> readBytes (int n, HashFunction &hash); int readByte () {return _is->get();} - int readByte (CRC32 &crc32); + int readByte (HashFunction &hashfunc); void seek (std::streampos pos, std::ios::seekdir dir) {_is->seekg(pos, dir);} void seek (std::streampos pos) {_is->seekg(pos);} std::streampos tell () const {return _is->tellg();} diff --git a/dviware/dvisvgm/src/StreamWriter.cpp b/dviware/dvisvgm/src/StreamWriter.cpp index c5630f56ba..6216468695 100644 --- a/dviware/dvisvgm/src/StreamWriter.cpp +++ b/dviware/dvisvgm/src/StreamWriter.cpp @@ -18,8 +18,9 @@ ** along with this program; if not, see <http://www.gnu.org/licenses/>. ** *************************************************************************/ -#include "CRC32.hpp" +#include "HashFunction.hpp" #include "StreamWriter.hpp" +#include "utility.hpp" using namespace std; @@ -37,7 +38,7 @@ void StreamWriter::writeUnsigned (uint32_t val, int n) { * @param[in] val the value to write * @param[in] n number of bytes to be considered */ void StreamWriter::writeSigned (int32_t val, int n) { - writeUnsigned((uint32_t)val, n); + writeUnsigned(uint32_t(val), n); } @@ -71,27 +72,27 @@ void StreamWriter::writeString (const string &str, bool finalZero) { /** Writes an unsigned integer to the output stream. * @param[in] val the value to write * @param[in] n number of bytes to be considered - * @param[in,out] crc32 checksum to be updated */ -void StreamWriter::writeUnsigned (uint32_t val, int n, CRC32 &crc32) { + * @param[in,out] hashfunc hash to update */ +void StreamWriter::writeUnsigned (uint32_t val, int n, HashFunction &hashfunc) { writeUnsigned(val, n); - crc32.update(val, n); + hashfunc.update(util::bytes(val, n)); } -/** Writes a signed integer to the output stream and updates the CRC32 checksum. +/** Writes a signed integer to the output stream and updates the hash value. * @param[in] val the value to write * @param[in] n number of bytes to be considered - * @param[in,out] crc32 checksum to be updated */ -void StreamWriter::writeSigned (int32_t val, int n, CRC32 &crc32) { - writeUnsigned((uint32_t)val, n, crc32); + * @param[in,out] hashfunc hash to update */ +void StreamWriter::writeSigned (int32_t val, int n, HashFunction &hashfunc) { + writeUnsigned(uint32_t(val), n, hashfunc); } -/** Writes a string to the output stream and updates the CRC32 checksum. +/** Writes a string to the output stream and updates the hash value. * @param[in] str the string to write - * @param[in,out] crc32 checksum to be updated + * @param[in,out] hashfunc hash to update * @param[in] finalZero if true, a final 0-byte is appended */ -void StreamWriter::writeString (const std::string &str, CRC32 &crc32, bool finalZero) { +void StreamWriter::writeString (const std::string &str, HashFunction &hashfunc, bool finalZero) { writeString(str, finalZero); - crc32.update((const uint8_t*)str.c_str(), str.length() + (finalZero ? 1 : 0)); + hashfunc.update(str.data(), str.length() + (finalZero ? 1 : 0)); } diff --git a/dviware/dvisvgm/src/StreamWriter.hpp b/dviware/dvisvgm/src/StreamWriter.hpp index 49b3401589..441a9a7e7f 100644 --- a/dviware/dvisvgm/src/StreamWriter.hpp +++ b/dviware/dvisvgm/src/StreamWriter.hpp @@ -24,7 +24,7 @@ #include <ostream> #include <vector> -class CRC32; +class HashFunction; class StreamWriter { public: @@ -35,9 +35,9 @@ class StreamWriter { void writeBytes (const uint8_t *buf, size_t bufsize); void writeBytes (int byte, size_t count); void writeString (const std::string &str, bool finalZero=false); - void writeUnsigned (uint32_t val, int n, CRC32 &crc32); - void writeSigned (int32_t val, int n, CRC32 &crc32); - void writeString (const std::string &str, CRC32 &crc32, bool finalZero=false); + void writeUnsigned (uint32_t val, int n, HashFunction &hashfunc); + void writeSigned (int32_t val, int n, HashFunction &hashfunc); + void writeString (const std::string &str, HashFunction &hashfunc, bool finalZero=false); private: std::ostream &_os; diff --git a/dviware/dvisvgm/src/TensorProductPatch.cpp b/dviware/dvisvgm/src/TensorProductPatch.cpp index 13650d71ee..52acd63179 100644 --- a/dviware/dvisvgm/src/TensorProductPatch.cpp +++ b/dviware/dvisvgm/src/TensorProductPatch.cpp @@ -201,16 +201,18 @@ Color TensorProductPatch::averageColor (const Color &c1, const Color &c2, const } -void TensorProductPatch::getBoundaryPath (GraphicsPath<double> &path) const { +GraphicsPath<double> TensorProductPatch::getBoundaryPath () const { // Simple approach: Use the outer curves as boundary path. This doesn't always lead // to correct results since, depending on the control points, P(u,v) might exceed // the simple boundary. + GraphicsPath<double> path; path.moveto(_points[0][0]); path.cubicto(_points[0][1], _points[0][2], _points[0][3]); path.cubicto(_points[1][3], _points[2][3], _points[3][3]); path.cubicto(_points[3][2], _points[3][1], _points[3][0]); path.cubicto(_points[2][0], _points[1][0], _points[0][0]); path.closepath(); + return path; } @@ -354,8 +356,7 @@ void TensorProductPatch::approximateRow (double v1, double inc, bool overlap, do void TensorProductPatch::approximate (int gridsize, bool overlap, double delta, Callback &callback) const { if (_colors[0] == _colors[1] && _colors[1] == _colors[2] && _colors[2] == _colors[3]) { // simple case: monochromatic patch - GraphicsPath<double> path; - getBoundaryPath(path); + GraphicsPath<double> path = getBoundaryPath(); callback.patchSegment(path, _colors[0]); } else { @@ -377,17 +378,16 @@ void TensorProductPatch::approximate (int gridsize, bool overlap, double delta, } -void TensorProductPatch::getBBox (BoundingBox &bbox) const { +BoundingBox TensorProductPatch::getBBox () const { + BoundingBox bbox; Bezier bezier; - BoundingBox bezierBox; for (int i=0; i <= 1; i++) { horizontalCurve(i, bezier); - bezier.getBBox(bezierBox); - bbox.embed(bezierBox); + bbox.embed(bezier.getBBox()); verticalCurve(i, bezier); - bezier.getBBox(bezierBox); - bbox.embed(bezierBox); + bbox.embed(bezier.getBBox()); } + return bbox; } diff --git a/dviware/dvisvgm/src/TensorProductPatch.hpp b/dviware/dvisvgm/src/TensorProductPatch.hpp index 444b93fd39..711feefc2d 100644 --- a/dviware/dvisvgm/src/TensorProductPatch.hpp +++ b/dviware/dvisvgm/src/TensorProductPatch.hpp @@ -51,12 +51,12 @@ class TensorProductPatch : public ShadingPatch { Color averageColor () const override; void horizontalCurve (double v, Bezier &bezier) const; void verticalCurve (double u, Bezier &bezier) const; - void getBoundaryPath (GraphicsPath<double> &path) const override; + GraphicsPath<double> getBoundaryPath () const override; void subpatch (double u1, double u2, double v1, double v2, TensorProductPatch &patch) const; DPair blossomValue (double u1, double u2, double u3, double v1, double v2, double v3) const; DPair blossomValue (double u[3], double v[3]) const {return blossomValue(u[0], u[1], u[2], v[0], v[1], v[2]);} void approximate (int gridsize, bool overlap, double delta, Callback &callback) const override; - void getBBox (BoundingBox &bbox) const override; + BoundingBox getBBox () const override; int numPoints (int edgeflag) const override {return edgeflag == 0 ? 16 : 12;} int numColors (int edgeflag) const override {return edgeflag == 0 ? 4 : 2;} diff --git a/dviware/dvisvgm/src/TpicSpecialHandler.cpp b/dviware/dvisvgm/src/TpicSpecialHandler.cpp index ee539f6b02..7bfb7937eb 100644 --- a/dviware/dvisvgm/src/TpicSpecialHandler.cpp +++ b/dviware/dvisvgm/src/TpicSpecialHandler.cpp @@ -23,6 +23,7 @@ #include <sstream> #include <string> #include "Color.hpp" +#include "EllipticalArc.hpp" #include "InputBuffer.hpp" #include "InputReader.hpp" #include "GraphicsPath.hpp" @@ -164,7 +165,7 @@ void TpicSpecialHandler::drawSplines (double ddist, SpecialActions &actions) { const DPair p1 = p+_points[i]; const DPair p2 = p+_points[i+1]; mid = p1+(p2-p1)/2.0; - path.conicto(p1, mid); + path.quadto(p1, mid); actions.embed(mid); actions.embed((p0+p1*6.0+p2)/8.0, _penwidth); } @@ -187,14 +188,6 @@ void TpicSpecialHandler::drawSplines (double ddist, SpecialActions &actions) { } -static double normalized_angle (double rad) { - rad = fmod(rad, math::TWO_PI); - if (rad < 0) - rad += math::TWO_PI; - return rad; -} - - /** Draws an elliptical arc. * @param[in] cx x-coordinate of arc center relative to current DVI position * @param[in] cy y-coordinate of arc center relative to current DVI position @@ -205,8 +198,6 @@ static double normalized_angle (double rad) { * @param[in] actions object providing the actions that can be performed by the SpecialHandler */ void TpicSpecialHandler::drawArc (double cx, double cy, double rx, double ry, double angle1, double angle2, SpecialActions &actions) { if (_penwidth > 0 || _grayLevel >= 0) { - angle1 = -angle1; - angle2 = -angle2; cx += actions.getX(); cy += actions.getY(); unique_ptr<XMLElement> elem; @@ -214,21 +205,17 @@ void TpicSpecialHandler::drawArc (double cx, double cy, double rx, double ry, do if (abs(angle2-angle1) >= math::TWO_PI) // closed ellipse? elem = create_ellipse_element(cx, cy, rx, ry); else { - angle1 = normalized_angle(angle1); - angle2 = normalized_angle(angle2); - double delta = normalized_angle(angle2-angle1); - int large_arg = (delta < math::PI) ? 1 : 0; - ostringstream oss; - oss << 'M' << XMLString(cx+rx*cos(angle1)) << ' ' << XMLString(cy+ry*sin(-angle1)) - << 'A' << XMLString(rx) << ' ' << XMLString(ry) - << " 0 " // no rotation of x-axis - << large_arg << " 1 " // always draw arc clockwise (sweep flag == 1) - << XMLString(cx+rx*cos(angle2)) << ' ' << XMLString(cy-ry*sin(angle2)); + EllipticalArc arc(DPair(cx, cy), rx, ry, 0, -angle1, math::normalize_0_2pi(angle2-angle1)); + GraphicsPath<double> path; + path.moveto(arc.startPoint()); + path.arcto(rx, ry, 0, arc.largeArc(), arc.sweepPositive(), arc.endPoint()); if (_grayLevel >= 0) - oss << 'Z'; + path.closepath(); else closed = false; elem = util::make_unique<XMLElement>("path"); + ostringstream oss; + path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS); elem->addAttribute("d", oss.str()); } if (_penwidth > 0) { diff --git a/dviware/dvisvgm/src/TriangularPatch.cpp b/dviware/dvisvgm/src/TriangularPatch.cpp index 929fe1b885..01efe2e8f0 100644 --- a/dviware/dvisvgm/src/TriangularPatch.cpp +++ b/dviware/dvisvgm/src/TriangularPatch.cpp @@ -161,8 +161,7 @@ static inline double snap (double x) { * @param[in] callback object notified */ void TriangularPatch::approximate (int gridsize, bool overlap, double delta, Callback &callback) const { if (_colors[0] == _colors[1] && _colors[1] == _colors[2]) { - GraphicsPath<double> path; - getBoundaryPath(path); + GraphicsPath<double> path = getBoundaryPath(); callback.patchSegment(path, _colors[0]); } else { @@ -197,18 +196,21 @@ void TriangularPatch::approximate (int gridsize, bool overlap, double delta, Cal } -void TriangularPatch::getBoundaryPath(GraphicsPath<double> &path) const { +GraphicsPath<double> TriangularPatch::getBoundaryPath () const { + GraphicsPath<double> path; path.clear(); path.moveto(_points[0]); path.lineto(_points[1]); path.lineto(_points[2]); path.closepath(); + return path; } -void TriangularPatch::getBBox (BoundingBox &bbox) const { - bbox.invalidate(); +BoundingBox TriangularPatch::getBBox () const { + BoundingBox bbox; bbox.embed(_points[0]); bbox.embed(_points[1]); bbox.embed(_points[2]); + return bbox; } diff --git a/dviware/dvisvgm/src/TriangularPatch.hpp b/dviware/dvisvgm/src/TriangularPatch.hpp index 8c6405ef08..d329e43fa1 100644 --- a/dviware/dvisvgm/src/TriangularPatch.hpp +++ b/dviware/dvisvgm/src/TriangularPatch.hpp @@ -39,8 +39,8 @@ class TriangularPatch : public ShadingPatch { void setColors (const ColorVec &colors, int edgeflag, ShadingPatch *patch) override; void setColors (const Color &c1, const Color &c2, const Color &c3); void approximate (int gridsize, bool overlap, double delta, Callback &listener) const override; - void getBBox (BoundingBox &bbox) const override; - void getBoundaryPath(GraphicsPath<double> &path) const override; + BoundingBox getBBox () const override; + GraphicsPath<double> getBoundaryPath () const override; int numPoints (int edgeflag) const override {return edgeflag == 0 ? 3 : 1;} int numColors (int edgeflag) const override {return edgeflag == 0 ? 3 : 1;} diff --git a/dviware/dvisvgm/src/TrueTypeFont.cpp b/dviware/dvisvgm/src/TrueTypeFont.cpp index 51056c36cc..408a00eede 100644 --- a/dviware/dvisvgm/src/TrueTypeFont.cpp +++ b/dviware/dvisvgm/src/TrueTypeFont.cpp @@ -127,7 +127,7 @@ void TrueTypeFont::writeWOFF (ostream &os) const { void TrueTypeFont::writeWOFF (const string &fname) const { - ofstream ofs(fname.c_str(), ios::binary); + ofstream ofs(fname, ios::binary); writeWOFF(ofs); } @@ -151,7 +151,7 @@ bool TrueTypeFont::writeWOFF2 (ostream &os) const { bool TrueTypeFont::writeWOFF2 (const string &fname) const { - ofstream ofs(fname.c_str(), ios::binary); + ofstream ofs(fname, ios::binary); return writeWOFF2(ofs); } @@ -172,4 +172,4 @@ uint32_t TrueTypeFont::TTFTableRecord::computeChecksum () const { while (startptr < endptr) sum += *startptr++; return sum; -}
\ No newline at end of file +} diff --git a/dviware/dvisvgm/src/VFReader.cpp b/dviware/dvisvgm/src/VFReader.cpp index a618f96ef7..cff67c919c 100644 --- a/dviware/dvisvgm/src/VFReader.cpp +++ b/dviware/dvisvgm/src/VFReader.cpp @@ -66,9 +66,7 @@ int VFReader::executeCommand (ApproveAction approve) { case 248: cmdPost(); break; // postamble default : { // invalid opcode replaceActions(actions); // reenable actions - ostringstream oss; - oss << "undefined VF command (opcode " << opcode << ')'; - throw VFException(oss.str()); + throw VFException("undefined VF command (opcode " + std::to_string(opcode) + ")"); } } } @@ -139,8 +137,7 @@ void VFReader::cmdLongChar () { else { uint32_t cc = readUnsigned(4); // character code readUnsigned(4); // equals character width from corresponding TFM file - vector<uint8_t> dvi(pl); // DVI subroutine - readBytes(pl, dvi); + auto dvi = readBytes(pl); // DVI subroutine _actions->defineVFChar(cc, std::move(dvi)); // call template method for user actions } } @@ -154,8 +151,7 @@ void VFReader::cmdShortChar (int pl) { else { uint32_t cc = readUnsigned(1); // character code readUnsigned(3); // character width from corresponding TFM file - vector<uint8_t> dvi(pl); // DVI subroutine - readBytes(pl, dvi); + auto dvi = readBytes(pl); // DVI subroutine _actions->defineVFChar(cc, std::move(dvi)); // call template method for user actions } } diff --git a/dviware/dvisvgm/src/XMLString.cpp b/dviware/dvisvgm/src/XMLString.cpp index b07e0e287b..fcafb8d1ee 100644 --- a/dviware/dvisvgm/src/XMLString.cpp +++ b/dviware/dvisvgm/src/XMLString.cpp @@ -21,6 +21,7 @@ #include <cmath> #include <cstdlib> #include "Unicode.hpp" +#include "utility.hpp" #include "XMLString.hpp" using namespace std; @@ -89,17 +90,8 @@ XMLString::XMLString (double x) { } if (std::abs(x) < 1e-6) x = 0; - assign(to_string(x)); - size_t pos = find('.'); - if (pos != string::npos) { - pos = find_last_not_of('0'); - if (pos != string::npos) { - erase(pos+1); // remove trailing zeros - if (at(length()-1) == '.') - pop_back(); // remove trailing dot - } - } - pos = find("0."); + assign(util::to_string(x)); + size_t pos = find("0."); if (pos != string::npos && (pos == 0 || at(pos-1) == '-')) erase(pos, 1); } diff --git a/dviware/dvisvgm/src/XXHashFunction.hpp b/dviware/dvisvgm/src/XXHashFunction.hpp index c0ae14e20b..f81deddbd4 100644 --- a/dviware/dvisvgm/src/XXHashFunction.hpp +++ b/dviware/dvisvgm/src/XXHashFunction.hpp @@ -23,7 +23,11 @@ #include <xxhash.h> #include "HashFunction.hpp" +#include "utility.hpp" +#if (XXH_VERSION_NUMBER >= 701) && defined(XXH3_SECRET_SIZE_MIN) +#define ENABLE_XXH128 +#endif template <int HASH_SIZE> struct XXHInterface { @@ -49,6 +53,17 @@ struct XXHInterface<8> { static constexpr auto digest = &XXH64_digest; }; +#ifdef ENABLE_XXH128 +template<> +struct XXHInterface<16> { + using State = XXH3_state_t; + static constexpr auto createState = &XXH3_createState; + static constexpr auto freeState = &XXH3_freeState; + static constexpr auto reset = &XXH3_128bits_reset_withSeed; + static constexpr auto update = &XXH3_128bits_update; + static constexpr auto digest = &XXH3_128bits_digest; +}; +#endif /** Implements the HashFunction class for the xxHash algorithms. */ template <int HASH_BYTES> @@ -66,14 +81,10 @@ class XXHashFunction : public HashFunction { void update (const std::string &data) override {update(data.data(), data.length());} void update (const std::vector<uint8_t> &data) override {update(reinterpret_cast<const char*>(data.data()), data.size());} + using HashFunction::update; // unhide update(istream &is) defined in base class + std::vector<uint8_t> digestValue () const override { - std::vector<uint8_t> hash(HASH_BYTES); - auto digest = Interface::digest(_state); - for (int i=HASH_BYTES-1; i >= 0; i--) { - hash[i] = digest & 0xff; - digest >>= 8; - } - return hash; + return util::bytes(Interface::digest(_state), HASH_BYTES); } static unsigned version () {return XXH_versionNumber();} @@ -85,4 +96,19 @@ class XXHashFunction : public HashFunction { using XXH32HashFunction = XXHashFunction<4>; using XXH64HashFunction = XXHashFunction<8>; +#ifdef ENABLE_XXH128 +using XXH128HashFunction = XXHashFunction<16>; + +template<> +inline std::vector<uint8_t> XXHashFunction<16>::digestValue () const { + std::vector<uint8_t> hash; + auto digest = Interface::digest(_state); + for (auto chunk : {digest.high64, digest.low64}) { + auto bytes = util::bytes(chunk); + hash.insert(hash.end(), bytes.begin(), bytes.end()); + } + return hash; +} +#endif + #endif diff --git a/dviware/dvisvgm/src/dvisvgm.cpp b/dviware/dvisvgm/src/dvisvgm.cpp index a2c28f8a77..37584d5ddb 100644 --- a/dviware/dvisvgm/src/dvisvgm.cpp +++ b/dviware/dvisvgm/src/dvisvgm.cpp @@ -109,27 +109,36 @@ static void set_libgs (CommandLine &args) { static bool set_cache_dir (const CommandLine &args) { if (args.cacheOpt.given() && !args.cacheOpt.value().empty()) { if (args.cacheOpt.value() == "none") - PhysicalFont::CACHE_PATH = nullptr; + PhysicalFont::CACHE_PATH.clear(); else if (FileSystem::exists(args.cacheOpt.value())) - PhysicalFont::CACHE_PATH = args.cacheOpt.value().c_str(); + PhysicalFont::CACHE_PATH = args.cacheOpt.value(); else Message::wstream(true) << "cache directory '" << args.cacheOpt.value() << "' does not exist (caching disabled)\n"; } - else if (const char *userdir = FileSystem::userdir()) { + else { + string &cachepath = PhysicalFont::CACHE_PATH; + const char *cachehome = getenv("XDG_CACHE_HOME"); + if (!cachehome || util::trim(cachehome).empty()) { #ifdef _WIN32 - string cachedir = "\\.dvisvgm\\cache"; + cachehome = "~\\.cache"; #else - string cachedir = "/.dvisvgm/cache"; + cachehome = "~/.cache"; #endif - static string cachepath = userdir + cachedir; + } + cachepath = util::trim(cachehome) + FileSystem::PATHSEP + "dvisvgm"; + if (cachepath[0] == '~' && cachepath[1] == FileSystem::PATHSEP) { + if (FileSystem::userdir()) + cachepath.replace(0, 1, FileSystem::userdir()); + else + cachepath.erase(0, 2); // strip leading "~/" + } if (!FileSystem::exists(cachepath)) FileSystem::mkdir(cachepath); - PhysicalFont::CACHE_PATH = cachepath.c_str(); } if (args.cacheOpt.given() && args.cacheOpt.value().empty()) { - cout << "cache directory: " << (PhysicalFont::CACHE_PATH ? PhysicalFont::CACHE_PATH : "(none)") << '\n'; + cout << "cache directory: " << (PhysicalFont::CACHE_PATH.empty() ? "(none)" : PhysicalFont::CACHE_PATH) << '\n'; try { - if (PhysicalFont::CACHE_PATH) + if (!PhysicalFont::CACHE_PATH.empty()) FontCache::fontinfo(PhysicalFont::CACHE_PATH, cout, true); } catch (StreamReaderException &e) { diff --git a/dviware/dvisvgm/src/optimizer/AttributeExtractor.cpp b/dviware/dvisvgm/src/optimizer/AttributeExtractor.cpp index ae2a0e36d7..e26018253d 100644 --- a/dviware/dvisvgm/src/optimizer/AttributeExtractor.cpp +++ b/dviware/dvisvgm/src/optimizer/AttributeExtractor.cpp @@ -113,14 +113,14 @@ XMLNode* AttributeExtractor::extractAttribute (XMLElement *elem) { * @return true if the element is groupable */ bool AttributeExtractor::groupable (const XMLElement &elem) { // https://www.w3.org/TR/SVG/struct.html#GElement - static constexpr auto names = util::make_array( + static const char *names[] = { "a", "altGlyphDef", "animate", "animateColor", "animateMotion", "animateTransform", "circle", "clipPath", "color-profile", "cursor", "defs", "desc", "ellipse", "filter", "font", "font-face", "foreignObject", "g", "image", "line", "linearGradient", "marker", "mask", "path", "pattern", "polygon", "polyline", "radialGradient", "rect", "set", "style", "switch", "symbol", "text", "title", "use", "view" - ); - return binary_search(names.begin(), names.end(), elem.name(), [](const string &name1, const string &name2) { + }; + return binary_search(begin(names), end(names), elem.name(), [](const string &name1, const string &name2) { return name1 < name2; }); } @@ -136,15 +136,15 @@ bool AttributeExtractor::inheritable (const Attribute &attrib) { // clip-path is not inheritable but can be moved to the parent element as long as // no child gets an different clip-path attribute // https://www.w3.org/TR/SVG11/styling.html#Inheritance - static constexpr auto names = util::make_array( + static const char *names[] = { "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "direction", "fill", "fill-opacity", "fill-rule", "font", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "glyph-orientation-horizontal", "glyph-orientation-vertical", "letter-spacing", "paint-order", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "transform", "visibility", "word-spacing", "writing-mode" - ); - return binary_search(names.begin(), names.end(), attrib.name, [](const string &name1, const string &name2) { + }; + return binary_search(begin(names), end(names), attrib.name, [](const string &name1, const string &name2) { return name1 < name2; }); } @@ -159,13 +159,13 @@ bool AttributeExtractor::extractable (const Attribute &attrib, XMLElement &eleme // the 'fill' attribute of animation elements has different semantics than // that of graphics elements => don't extract it from animation nodes // https://www.w3.org/TR/SVG11/animate.html#TimingAttributes - static constexpr auto names = util::make_array( + static const char *names[] = { "animate", "animateColor", "animateMotion", "animateTransform", "set" - ); - auto it = find_if(names.begin(), names.end(), [&](const string &name) { + }; + auto it = find_if(begin(names), end(names), [&](const string &name) { return element.name() == name; }); - return it == names.end(); + return it == end(names); } diff --git a/dviware/dvisvgm/src/optimizer/GroupCollapser.cpp b/dviware/dvisvgm/src/optimizer/GroupCollapser.cpp index 5d580f5ba0..36bf3a8cd5 100644 --- a/dviware/dvisvgm/src/optimizer/GroupCollapser.cpp +++ b/dviware/dvisvgm/src/optimizer/GroupCollapser.cpp @@ -132,13 +132,13 @@ bool GroupCollapser::moveAttributes (XMLElement &source, XMLElement &dest) { bool GroupCollapser::collapsible (const XMLElement &element) { // the 'fill' attribute of animation elements has different semantics than // that of graphics elements => don't collapse them - static constexpr auto names = util::make_array( + static const char *names[] = { "animate", "animateColor", "animateMotion", "animateTransform", "set" - ); - auto it = find_if(names.begin(), names.end(), [&](const string &name) { + }; + auto it = find_if(begin(names), end(names), [&](const string &name) { return element.name() == name; }); - return it == names.end(); + return it == end(names); } @@ -155,11 +155,11 @@ bool GroupCollapser::unwrappable (const XMLElement &source, const XMLElement &de } } // these attributes prevent a group from being unwrapped - static constexpr auto attribs = util::make_array( + static const char *attribs[] = { "class", "id", "filter", "mask", "style" - ); - auto it = find_if(attribs.begin(), attribs.end(), [&](const string &name) { + }; + auto it = find_if(begin(attribs), end(attribs), [&](const string &name) { return source.hasAttribute(name) || dest.hasAttribute(name); }); - return it == attribs.end(); + return it == end(attribs); } diff --git a/dviware/dvisvgm/src/optimizer/Makefile.am b/dviware/dvisvgm/src/optimizer/Makefile.am index 41c0266009..fec405662a 100644 --- a/dviware/dvisvgm/src/optimizer/Makefile.am +++ b/dviware/dvisvgm/src/optimizer/Makefile.am @@ -10,3 +10,4 @@ liboptimizer_la_SOURCES = \ TransformSimplifier.hpp TransformSimplifier.cpp \ WSNodeRemover.hpp WSNodeRemover.cpp +AM_CXXFLAGS = -I$(dvisvgm_srcdir)/libs/variant/include diff --git a/dviware/dvisvgm/src/optimizer/Makefile.in b/dviware/dvisvgm/src/optimizer/Makefile.in index 64b288a723..45344c1933 100644 --- a/dviware/dvisvgm/src/optimizer/Makefile.in +++ b/dviware/dvisvgm/src/optimizer/Makefile.in @@ -369,6 +369,7 @@ liboptimizer_la_SOURCES = \ TransformSimplifier.hpp TransformSimplifier.cpp \ WSNodeRemover.hpp WSNodeRemover.cpp +AM_CXXFLAGS = -I$(dvisvgm_srcdir)/libs/variant/include all: all-am .SUFFIXES: diff --git a/dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp b/dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp index 06d28926d4..b9d4c3db59 100644 --- a/dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp +++ b/dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp @@ -19,6 +19,7 @@ *************************************************************************/ #include <cmath> +#include <cstdlib> #include "TransformSimplifier.hpp" #include "../Matrix.hpp" #include "../utility.hpp" @@ -38,14 +39,16 @@ void TransformSimplifier::execute (XMLElement *context) { return; if (const char *transform = context->getAttributeValue("transform")) { Matrix matrix = Matrix::parseSVGTransform(transform); - string decomp = decompose(matrix); - if (decomp.length() > matrix.toSVG().length()) - context->addAttribute("transform", matrix.toSVG()); - else { - if (decomp.empty()) - context->removeAttribute("transform"); - else - context->addAttribute("transform", decomp); + if (!incorporateTransform(context, matrix)) { + string decomp = decompose(matrix); + if (decomp.length() > matrix.toSVG().length()) + context->addAttribute("transform", matrix.toSVG()); + else { + if (decomp.empty()) + context->removeAttribute("transform"); + else + context->addAttribute("transform", decomp); + } } } // continue with child elements @@ -56,6 +59,36 @@ void TransformSimplifier::execute (XMLElement *context) { } +/** Tries to incorporate the translation and scaling components of the 'transform' attribute + * of a given element into the positional and/or size attributes of that element. If successful, + * the 'transform' attribute is removed. + * Currently, only 'image' and 'rect' elements are considered. + * @param[in] elem element to check + * @param[in] matrix matrix representing the 'transform' attribute of the element + * @return true on success */ +bool TransformSimplifier::incorporateTransform (XMLElement *elem, const Matrix &matrix) { + if ((elem->name() == "image" || elem->name() == "rect") && matrix.get(0, 1) == 0 && matrix.get(1, 0) == 0) { + double tx = matrix.get(0, 2); + double ty = matrix.get(1, 2); + double sx = matrix.get(0, 0); + double sy = matrix.get(1, 1); + if (const char *xstr = elem->getAttributeValue("x")) + tx += sx*strtod(xstr, nullptr); + if (const char *ystr = elem->getAttributeValue("y")) + ty += sy*strtod(ystr, nullptr); + if (const char *wstr = elem->getAttributeValue("width")) + elem->addAttribute("width", sx*strtod(wstr, nullptr)); + if (const char *hstr = elem->getAttributeValue("height")) + elem->addAttribute("height", sy*strtod(hstr, nullptr)); + elem->addAttribute("x", tx); // update x attribute + elem->addAttribute("y", ty); // update x attribute + elem->removeAttribute("transform"); + return true; + } + return false; +} + + static string translate_cmd (double dx, double dy) { string ret; XMLString dxstr(dx), dystr(dy); diff --git a/dviware/dvisvgm/src/optimizer/TransformSimplifier.hpp b/dviware/dvisvgm/src/optimizer/TransformSimplifier.hpp index 40ac21a465..c4b6d9ee16 100644 --- a/dviware/dvisvgm/src/optimizer/TransformSimplifier.hpp +++ b/dviware/dvisvgm/src/optimizer/TransformSimplifier.hpp @@ -32,5 +32,6 @@ class TransformSimplifier : public OptimizerModule { const char* info () const override; protected: + bool incorporateTransform (XMLElement *elem, const Matrix &matrix); std::string decompose (const Matrix &matrix); }; diff --git a/dviware/dvisvgm/src/psdefs.cpp b/dviware/dvisvgm/src/psdefs.cpp index d8a07177b7..39c938dac9 100644 --- a/dviware/dvisvgm/src/psdefs.cpp +++ b/dviware/dvisvgm/src/psdefs.cpp @@ -95,25 +95,24 @@ const char *PSInterpreter::PSDEFS = "rotate sysexec applyscalevals}def/scale{dup type/arraytype ne @dodraw and{2 co" "py 2(scale)prcmd}if/scale sysexec applyscalevals}def/translate{dup type/arrayt" "ype ne @dodraw and{2 copy 2(translate)prcmd}if/translate sysexec}def/setmatrix" -"{dup/setmatrix sysexec @dodraw{aload pop 6(setmatrix)prcmd applyscalevals}if}d" -"ef/initmatrix{matrix setmatrix}def/concat{matrix currentmatrix matrix concatma" -"trix setmatrix}def/makepattern{gsave<</mx 3 -1 roll>>begin dup/XUID[1000000 @p" -"atcnt]put mx/makepattern sysexec dup dup begin PatternType @patcnt BBox aload " -"pop XStep YStep PaintType mx aload pop 15(makepattern)prcmd :newpath matrix se" -"tmatrix PaintProc 0 1(makepattern)prcmd end/@patcnt @patcnt 1 add store end gr" -"estore}def/setpattern{begin PatternType 1 eq{PaintType 1 eq{XUID aload pop exc" -"h pop 1}{:gsave[currentcolorspace aload length -1 roll pop]setcolorspace/setco" -"lor sysexec XUID aload pop exch pop currentrgbcolor :grestore 4}ifelse(setpatt" -"ern)prcmd}{/setpattern sysexec}ifelse end}def/setcolor{dup type/dicttype eq{se" -"tpattern}{/setcolor sysexec/currentrgbcolor sysexec setrgbcolor}ifelse}def/set" -"gray 1 defpr/setcmykcolor 4 defpr/sethsbcolor 3 defpr/setrgbcolor 3 defpr/.set" -"opacityalpha{dup/.setopacityalpha sysexec 1(setopacityalpha)prcmd}def/.setshap" -"ealpha{dup/.setshapealpha sysexec 1(setshapealpha)prcmd}def/.setblendmode{dup/" -".setblendmode sysexec<</Normal 0/Compatible 0/Multiply 1/Screen 2/Overlay 3/So" -"ftLight 4/HardLight 5/ColorDodge 6/ColorBurn 7/Darken 8/Lighten 9/Difference 1" -"0/Exclusion 11/Hue 12/Saturation 13/Color 14/Luminosity 15/CompatibleOverprint" -" 16>>exch get 1(setblendmode)prcmd}def/@pdfpagecount{GS_PDF_ProcSet begin pdfd" -"ict begin(r)file pdfopen begin pdfpagecount currentdict pdfclose end end end}d" -"ef/@pdfpagebox{GS_PDF_ProcSet begin pdfdict begin(r)file pdfopen begin dup dup" -" 1 lt exch pdfpagecount gt or{pop}{pdfgetpage/MediaBox pget pop aload pop}ifel" -"se currentdict pdfclose end end end}def DELAYBIND{.bindnow}if "; +"{dup/setmatrix sysexec @dodraw{aload pop 6(setmatrix)prcmd applyscalevals}{pop" +"}ifelse}def/initmatrix{matrix setmatrix}def/concat{matrix currentmatrix matrix" +" concatmatrix setmatrix}def/makepattern{gsave<</mx 3 -1 roll>>begin dup/XUID[1" +"000000 @patcnt]put mx/makepattern sysexec dup dup begin PatternType @patcnt BB" +"ox aload pop XStep YStep PaintType mx aload pop 15(makepattern)prcmd :newpath " +"matrix setmatrix PaintProc 0 1(makepattern)prcmd end/@patcnt @patcnt 1 add sto" +"re end grestore}def/setpattern{begin PatternType 1 eq{PaintType 1 eq{XUID aloa" +"d pop exch pop 1}{:gsave[currentcolorspace aload length -1 roll pop]setcolorsp" +"ace/setcolor sysexec XUID aload pop exch pop currentrgbcolor :grestore 4}ifels" +"e(setpattern)prcmd}{/setpattern sysexec}ifelse end}def/setcolor{dup type/dictt" +"ype eq{setpattern}{/setcolor sysexec/currentrgbcolor sysexec setrgbcolor}ifels" +"e}def/setgray 1 defpr/setcmykcolor 4 defpr/sethsbcolor 3 defpr/setrgbcolor 3 d" +"efpr/.setopacityalpha{dup/.setopacityalpha sysexec 1(setopacityalpha)prcmd}def" +"/.setshapealpha{dup/.setshapealpha sysexec 1(setshapealpha)prcmd}def/.setblend" +"mode{dup/.setblendmode sysexec<</Normal 0/Compatible 0/Multiply 1/Screen 2/Ove" +"rlay 3/SoftLight 4/HardLight 5/ColorDodge 6/ColorBurn 7/Darken 8/Lighten 9/Dif" +"ference 10/Exclusion 11/Hue 12/Saturation 13/Color 14/Luminosity 15/Compatible" +"Overprint 16>>exch get 1(setblendmode)prcmd}def/@pdfpagecount{(r)file runpdfbe" +"gin pdfpagecount runpdfend}def/@pdfpagebox{(r)file runpdfbegin dup dup 1 lt ex" +"ch pdfpagecount gt or{pop}{pdfgetpage/MediaBox pget pop aload pop}ifelse runpd" +"fend}def DELAYBIND{.bindnow}if "; diff --git a/dviware/dvisvgm/src/utility.cpp b/dviware/dvisvgm/src/utility.cpp index 99e8e194a0..f1ae59ec08 100644 --- a/dviware/dvisvgm/src/utility.cpp +++ b/dviware/dvisvgm/src/utility.cpp @@ -20,6 +20,7 @@ #include <algorithm> #include <cctype> +#include <cmath> #include <fstream> #include <functional> #include <iterator> @@ -27,6 +28,56 @@ using namespace std; +/** Computes the singular value decomposition of a given 2x2 matrix M + * so that M = rotate(phi)*scale(sx,sy)*rotate(theta), where + * rotate(t):={{cos t, -sin t}, {sin t, cos t}} and scale(sx, sy):={{sx, 0}, {0, sy}}. + * The corresponding math can be found in Jim Blinn: "Consider the Lowly 2x2 Matrix" + * https://ieeexplore.ieee.org/document/486688 + * Also published in "Jim Blinn's Corner: Notation, Notation, Notation", pp. 69--95. + * @param[in] m matrix {{m00, m01}, {m10, m11}} where the inner pairs denote the rows + * @return vector {phi, sx, sy, theta} */ +vector<double> math::svd (const double (&m)[2][2]) { + double phi=0, theta=0, sx=0, sy=0; + if (m[0][0] != 0 || m[0][1] != 0 || m[1][0] != 0 || m[1][1] != 0) { + double e = (m[0][0] + m[1][1])/2; // = cos(phi+theta)*(sx+sy)/2 + double f = (m[0][0] - m[1][1])/2; // = cos(phi-theta)*(sx-sy)/2 + double g = (m[1][0] + m[0][1])/2; // = sin(phi-theta)*(sx-sy)/2 + double h = (m[1][0] - m[0][1])/2; // = sin(phi+theta)*(sx+sy)/2 + double hyp1 = hypot(e, h); // = (sx+sy)/2 + double hyp2 = hypot(f, g); // = (sx-sy)/2 + sx = hyp1+hyp2; + sy = hyp1-hyp2; + if (hyp2 == 0) // uniformly scaled rotation? + theta = atan2(h, e); + else if (hyp1 == 0) // uniformly scaled reflection? + theta = -atan2(g, f); + else { + double a1 = atan2(g, f); // = phi-theta (g/f = tan(phi-theta)) + double a2 = atan2(h, e); // = phi+theta (h/e = tan(phi+theta)) + phi = (a2+a1)/2; + theta = (a2-a1)/2; + } + } + return vector<double>{phi, sx, sy, theta}; +} + + +/** Normalizes an angle to the interval [-mod, mod). */ +double math::normalize_angle (double angle, double mod) { + angle = fmod(angle+mod, 2.0*mod); + if (angle < 0) + angle += 2.0*mod; + return angle-mod; +} + + +double math::normalize_0_2pi (double rad) { + rad = fmod(rad, TWO_PI); + if (rad < 0) + rad += TWO_PI; + return rad; +} + /** Returns a given string with leading and trailing whitespace removed. * @param[in] str the string to process @@ -109,6 +160,20 @@ string util::tolower (const string &str) { } +/** Converts a double to a string and strips redundant trailing digits/dots. */ +string util::to_string (double val) { + string str = std::to_string(val); + if (str.find('.') != string::npos) { // double value and not an integer? + size_t pos = str.find_last_not_of('0'); + if (pos != string::npos) // trailing zeros + str.erase(pos+1, string::npos); + if (str.back() == '.') // trailing dot? + str.pop_back(); + } + return str; +} + + /** Returns the integer part of log10 of a given integer \f$n>0\f$. * If \f$n<0\f$, the result is 0. */ int util::ilog10 (int n) { diff --git a/dviware/dvisvgm/src/utility.hpp b/dviware/dvisvgm/src/utility.hpp index ca0b0873dc..7127280284 100644 --- a/dviware/dvisvgm/src/utility.hpp +++ b/dviware/dvisvgm/src/utility.hpp @@ -36,6 +36,13 @@ constexpr const double SQRT2 = 1.414213562373095048801688724209698079; inline double deg2rad (double deg) {return PI*deg/180.0;} inline double rad2deg (double rad) {return 180.0*rad/PI;} +double normalize_angle (double angle, double mod); +double normalize_0_2pi (double rad); +std::vector<double> svd (const double (&m)[2][2]); + +/** Signum function (returns x/abs(x) if x != 0, and 0 otherwise). */ +template <typename T> +inline int sgn (T x) {return (x > T(0)) - (x < T(0));} } // namespace math @@ -52,12 +59,31 @@ std::string trim (const std::string &str, const char *ws=" \t\n\r\f"); std::string normalize_space (std::string str, const char *ws=" \t\n\r\f"); std::string tolower (const std::string &str); std::string replace (std::string str, const std::string &find, const std::string &repl); +std::string to_string (double val); std::vector<std::string> split (const std::string &str, const std::string &sep); int ilog10 (int n); std::string read_file_contents (const std::string &fname); void write_file_contents (const std::string &fname, std::string::iterator start, std::string::iterator end); + +/** Returns a sequence of bytes of a given integral value. + * @param[in] val get bytes of this value + * @param[in] n number of bytes to extract (from LSB to MSB), all if n == 0 + * @return vector of bytes in big-endian order */ +template <typename T> +std::vector<uint8_t> bytes (T val, int n=0) { + if (n <= 0) + n = sizeof(T); + std::vector<uint8_t> ret(n); + for (int i=0; i < n; i++) { + ret[n-i-1] = val & 0xff; + val >>= 8; + } + return ret; +} + + /** Encodes the bytes in the half-open range [first,last) to Base64 and writes * the result to the range starting at 'dest'. * @param[in] first initial position of the range to be encoded @@ -103,15 +129,6 @@ std::unique_ptr<T> static_unique_ptr_cast (std::unique_ptr<U> &&old){ return std::unique_ptr<T>{static_cast<T*>(old.release())}; } - -template <typename... T> -constexpr auto make_array (T&&... values) -> - std::array<typename std::decay<typename std::common_type<T...>::type>::type, sizeof...(T)> { - return std::array<typename std::decay<typename std::common_type<T...>::type>::type, sizeof...(T)>{ - std::forward<T>(values)... - }; - } - } // namespace util #endif |