diff options
Diffstat (limited to 'dviware/dvisvgm/src')
28 files changed, 421 insertions, 304 deletions
diff --git a/dviware/dvisvgm/src/DVIReader.cpp b/dviware/dvisvgm/src/DVIReader.cpp index 417efe9304..0581ad25c3 100644 --- a/dviware/dvisvgm/src/DVIReader.cpp +++ b/dviware/dvisvgm/src/DVIReader.cpp @@ -19,8 +19,6 @@ *************************************************************************/ #include <algorithm> -#include <cstdarg> -#include <fstream> #include <sstream> #include "Color.hpp" #include "DVIActions.hpp" @@ -28,6 +26,7 @@ #include "Font.hpp" #include "FontManager.hpp" #include "HashFunction.hpp" +#include "utility.hpp" #include "VectorStream.hpp" using namespace std; @@ -504,26 +503,64 @@ void DVIReader::cmdFontNum (int len) { } +/** Parses a sequence of font attributes given as key1=val1;key2=val2;...;keyn=valn */ +static map<string,string> parse_font_attribs (const string &str) { + map<string,string> attribs; + if (!str.empty()) { + for (const string &attr : util::split(str, ";")) { + vector<string> keyval = util::split(attr, "="); + if (keyval.size() == 2) + attribs[keyval[0]] = keyval[1]; + } + } + return attribs; +} + + /** Helper function to handle a font definition. * @param[in] fontnum local font number - * @param[in] name font name + * @param[in] name font name (or file path if enclosed in square brackets) * @param[in] cs checksum to be compared with TFM checksum - * @param[in] ds design size in PS point units - * @param[in] ss scaled size in PS point units */ -const Font* DVIReader::defineFont (uint32_t fontnum, const string &name, uint32_t cs, double ds, double ss) { + * @param[in] dsize design size of font in PS point units + * @param[in] ssize scaled size of font in PS point units */ +const Font* DVIReader::defineFont (uint32_t fontnum, const string &name, uint32_t cs, double dsize, double ssize) { FontManager &fm = FontManager::instance(); Font *font = fm.getFont(fontnum); - if (!font) { - int id = fm.registerFont(fontnum, name, cs, ds, ss); - font = fm.getFontById(id); - if (auto vf = dynamic_cast<VirtualFont*>(font)) { - // read vf file, register its font and character definitions - fm.enterVF(vf); - ifstream ifs(vf->path(), ios::binary); - VFReader vfReader(ifs); - vfReader.replaceActions(this); - vfReader.executeAll(); - fm.leaveVF(); + if (!font && !name.empty()) { // font not registered yet? + if (name[0] == '[') { // LuaTeX native font reference? + size_t last = name.rfind(']'); + if (last != string::npos) { + string path = name.substr(1, last-1); + FontStyle style; + int fontIndex=0; + if (name.size() > last && name[last+1] == ':') { // look for font attributes? + auto attribs = parse_font_attribs(name.substr(last+2)); + auto it = attribs.begin(); + if ((it = attribs.find("index")) != attribs.end()) + fontIndex = stoi(it->second); + if ((it = attribs.find("embolden")) != attribs.end()) + style.bold = stoi(it->second)/65536.0; + if ((it = attribs.find("extend")) != attribs.end()) + style.extend = stoi(it->second)/65536.0; + if ((it = attribs.find("slant")) != attribs.end()) + style.slant = stoi(it->second)/65536.0; + } + int id = fm.registerFont(fontnum, path, fontIndex, ssize, style, Color::BLACK); + font = fm.getFontById(id); + } + } + else { // TFM-based font specified by name + int id = fm.registerFont(fontnum, name, cs, dsize, ssize); + font = fm.getFontById(id); + if (auto vf = dynamic_cast<VirtualFont*>(font)) { + // read vf file, register its font and character definitions + fm.enterVF(vf); + ifstream ifs(vf->path(), ios::binary); + VFReader vfReader(ifs); + vfReader.replaceActions(this); + vfReader.executeAll(); + fm.leaveVF(); + } } } return font; diff --git a/dviware/dvisvgm/src/DVIReader.hpp b/dviware/dvisvgm/src/DVIReader.hpp index fc1424aca3..61c4866714 100644 --- a/dviware/dvisvgm/src/DVIReader.hpp +++ b/dviware/dvisvgm/src/DVIReader.hpp @@ -74,7 +74,7 @@ class DVIReader : public BasicDVIReader, public VFActions { virtual void moveDown (double dy, MoveMode mode); void putVFChar (Font *font, uint32_t c); double putGlyphArray (bool xonly, std::vector<double> &dx, std::vector<double> &dy, std::vector<uint16_t> &glyphs); - const Font* defineFont (uint32_t fontnum, const std::string &name, uint32_t cs, double ds, double ss); + const Font* defineFont (uint32_t fontnum, const std::string &name, uint32_t cs, double dsize, double ssize); void setFont (int num, SetFontMode mode); const DVIState& dviState() const {return _dviState;} double dvi2bp () const {return _dvi2bp;} diff --git a/dviware/dvisvgm/src/DVIToSVGActions.cpp b/dviware/dvisvgm/src/DVIToSVGActions.cpp index 6239be3b01..2464aec517 100644 --- a/dviware/dvisvgm/src/DVIToSVGActions.cpp +++ b/dviware/dvisvgm/src/DVIToSVGActions.cpp @@ -122,7 +122,7 @@ void DVIToSVGActions::setChar (double x, double y, unsigned c, bool vertical, co bbox.transform(getMatrix()); embed(bbox); #if 0 - XMLElement *rect = new XMLElement("rect"); + auto rect = util::make_unique<XMLElement>("rect"); rect->addAttribute("x", x-metrics.wl); rect->addAttribute("y", y-metrics.h); rect->addAttribute("width", metrics.wl+metrics.wr); @@ -130,26 +130,26 @@ void DVIToSVGActions::setChar (double x, double y, unsigned c, bool vertical, co rect->addAttribute("fill", "none"); rect->addAttribute("stroke", "red"); rect->addAttribute("stroke-width", "0.5"); - _svg.appendToPage(rect); + _svg.appendToPage(std::move(rect)); if (metrics.d > 0) { - XMLElement *line = new XMLElement("line"); + auto line = util::make_unique<XMLElement>("line"); line->addAttribute("x1", x-metrics.wl); line->addAttribute("y1", y); line->addAttribute("x2", x+metrics.wr); line->addAttribute("y2", y); line->addAttribute("stroke", "blue"); line->addAttribute("stroke-width", "0.5"); - _svg.appendToPage(line); + _svg.appendToPage(std::move(line)); } if (metrics.wl > 0) { - XMLElement *line = new XMLElement("line"); + auto line = util::make_unique<XMLElement>("line"); line->addAttribute("x1", x); line->addAttribute("y1", y-metrics.h); line->addAttribute("x2", x); line->addAttribute("y2", y+metrics.d); line->addAttribute("stroke", "blue"); line->addAttribute("stroke-width", "0.5"); - _svg.appendToPage(line); + _svg.appendToPage(std::move(line)); } #endif } @@ -227,7 +227,12 @@ void DVIToSVGActions::beginPage (unsigned pageno, const vector<int32_t>&) { /** This method is called when an "end of page (eop)" command was found in the DVI file. */ void DVIToSVGActions::endPage (unsigned pageno) { - SpecialManager::instance().notifyEndPage(pageno, *this); + try { + SpecialManager::instance().notifyEndPage(pageno, *this); + } + catch (const SpecialException &e) { + Message::estream(true) << "error in special: " << e.what() << '\n'; + } Matrix matrix = _dvireader->getPageTransformation(); _svg.transformPage(matrix); if (_bgcolor != Color::TRANSPARENT) { diff --git a/dviware/dvisvgm/src/DvisvgmSpecialHandler.cpp b/dviware/dvisvgm/src/DvisvgmSpecialHandler.cpp index 1f331659f0..97285db1a9 100644 --- a/dviware/dvisvgm/src/DvisvgmSpecialHandler.cpp +++ b/dviware/dvisvgm/src/DvisvgmSpecialHandler.cpp @@ -209,7 +209,9 @@ static void evaluate_expressions (string &str, SpecialActions &actions) { else { try { double val = calc.eval(expr); - str.replace(left, right-left+2, XMLString(val)); + XMLString valstr(val); + str.replace(left, right-left+2, valstr); + right = left+valstr.length()-1; } catch (CalculatorException &e) { throw SpecialException(string(e.what())+" in '{?("+expr+")}'"); @@ -413,8 +415,8 @@ void DvisvgmSpecialHandler::dviPreprocessingFinished () { void DvisvgmSpecialHandler::dviEndPage (unsigned, SpecialActions &actions) { - _defsParser.flush(actions); - _pageParser.flush(actions); + _defsParser.finish(actions); + _pageParser.finish(actions); actions.bbox().unlock(); for (auto &strvecpair : _macros) { StringVector &vec = strvecpair.second; @@ -437,8 +439,8 @@ vector<const char*> DvisvgmSpecialHandler::prefixes() const { /** Parses a fragment of XML code, creates corresponding XML nodes and adds them * to the SVG tree. The code may be split and processed by several calls of this * function. Incomplete chunks that can't be processed yet are stored and picked - * up again together with the next incoming XML fragment. If no further code should - * be appended, parameter 'finish' must be set. + * up again together with the next incoming XML fragment. If a call of this function + * is supposed to finish the parsing of an XML subtree, parameter 'finish' must be set. * @param[in] xml XML fragment to parse * @param[in] actions object providing the SVG tree functions * @param[in] finish if true, no more XML is expected and parsing is finished */ @@ -447,59 +449,65 @@ void DvisvgmSpecialHandler::XMLParser::parse (const string &xml, SpecialActions // incomplete tags are held back _xmlbuf += xml; size_t left=0, right; - while (left != string::npos) { - right = _xmlbuf.find('<', left); - if (left < right && left < _xmlbuf.length()) // plain text found? - (actions.svgTree().*_append)(util::make_unique<XMLText>(_xmlbuf.substr(left, right-left))); - if (right != string::npos) { - left = right; - if (_xmlbuf.compare(left, 9, "<![CDATA[") == 0) { - right = _xmlbuf.find("]]>", left+9); - if (right == string::npos) { - if (finish) throw SpecialException("expected ']]>' at end of CDATA block"); - break; + try { + while (left != string::npos) { + right = _xmlbuf.find('<', left); + if (left < right && left < _xmlbuf.length()) // plain text found? + (actions.svgTree().*_append)(util::make_unique<XMLText>(_xmlbuf.substr(left, right-left))); + if (right != string::npos) { + left = right; + if (_xmlbuf.compare(left, 9, "<![CDATA[") == 0) { + right = _xmlbuf.find("]]>", left+9); + if (right == string::npos) { + if (finish) throw SpecialException("expected ']]>' at end of CDATA block"); + break; + } + (actions.svgTree().*_append)(util::make_unique<XMLCData>(_xmlbuf.substr(left+9, right-left-9))); + right += 2; } - (actions.svgTree().*_append)(util::make_unique<XMLCData>(_xmlbuf.substr(left+9, right-left-9))); - right += 2; - } - else if (_xmlbuf.compare(left, 4, "<!--") == 0) { - right = _xmlbuf.find("-->", left+4); - if (right == string::npos) { - if (finish) throw SpecialException("expected '-->' at end of comment"); - break; + else if (_xmlbuf.compare(left, 4, "<!--") == 0) { + right = _xmlbuf.find("-->", left+4); + if (right == string::npos) { + if (finish) throw SpecialException("expected '-->' at end of comment"); + break; + } + (actions.svgTree().*_append)(util::make_unique<XMLComment>(_xmlbuf.substr(left+4, right-left-4))); + right += 2; } - (actions.svgTree().*_append)(util::make_unique<XMLComment>(_xmlbuf.substr(left+4, right-left-4))); - right += 2; - } - else if (_xmlbuf.compare(left, 2, "<?") == 0) { - right = _xmlbuf.find("?>", left+2); - if (right == string::npos) { - if (finish) throw SpecialException("expected '?>' at end of processing instruction"); - break; + else if (_xmlbuf.compare(left, 2, "<?") == 0) { + right = _xmlbuf.find("?>", left+2); + if (right == string::npos) { + if (finish) throw SpecialException("expected '?>' at end of processing instruction"); + break; + } + (actions.svgTree().*_append)(util::make_unique<XMLText>(_xmlbuf.substr(left, right-left+2))); + right++; } - (actions.svgTree().*_append)(util::make_unique<XMLText>(_xmlbuf.substr(left, right-left+2))); - right++; - } - else if (_xmlbuf.compare(left, 2, "</") == 0) { - right = _xmlbuf.find('>', left+2); - if (right == string::npos) { - if (finish) throw SpecialException("missing '>' at end of closing XML tag"); - break; + else if (_xmlbuf.compare(left, 2, "</") == 0) { + right = _xmlbuf.find('>', left+2); + if (right == string::npos) { + if (finish) throw SpecialException("missing '>' at end of closing XML tag"); + break; + } + closeElement(_xmlbuf.substr(left+2, right-left-2), actions); } - closeElement(_xmlbuf.substr(left+2, right-left-2), actions); - } - else { - right = _xmlbuf.find('>', left+1); - if (right == string::npos) { - if (finish) throw SpecialException("missing '>' or '/>' at end of opening XML tag"); - break; + else { + right = _xmlbuf.find('>', left+1); + if (right == string::npos) { + if (finish) throw SpecialException("missing '>' or '/>' at end of opening XML tag"); + break; + } + openElement(_xmlbuf.substr(left+1, right-left-1), actions); } - openElement(_xmlbuf.substr(left+1, right-left-1), actions); } + left = right; + if (right != string::npos) + left++; } - left = right; - if (right != string::npos) - left++; + } + catch (const SpecialException &e) { + _error = true; + throw; } if (left == string::npos) _xmlbuf.clear(); @@ -545,7 +553,7 @@ void DvisvgmSpecialHandler::XMLParser::closeElement (const string &tag, SpecialA if (_nameStack.empty()) throw SpecialException("spurious closing tag </" + name + ">"); if (_nameStack.back() != name) - throw SpecialException("expected </" + name + "> but found </" + _nameStack.back() + ">"); + throw SpecialException("expected </" + _nameStack.back() + "> but found </" + name + ">"); (actions.svgTree().*_popContext)(); _nameStack.pop_back(); } @@ -553,9 +561,10 @@ void DvisvgmSpecialHandler::XMLParser::closeElement (const string &tag, SpecialA /** Processes any remaining XML fragments, checks for missing closing tags, * and resets the parser state. */ -void DvisvgmSpecialHandler::XMLParser::flush (SpecialActions &actions) { +void DvisvgmSpecialHandler::XMLParser::finish (SpecialActions &actions) { if (!_xmlbuf.empty()) { - parse("", actions, true); + if (!_error) + parse("", actions, true); _xmlbuf.clear(); } string tags; @@ -563,8 +572,8 @@ void DvisvgmSpecialHandler::XMLParser::flush (SpecialActions &actions) { tags += "</"+_nameStack.back()+">, "; _nameStack.pop_back(); } - if (!tags.empty()) { + if (!tags.empty() && !_error) { tags.resize(tags.length()-2); - throw SpecialException("missing closing tags: "+tags); + throw SpecialException("missing closing tag(s): "+tags); } } diff --git a/dviware/dvisvgm/src/DvisvgmSpecialHandler.hpp b/dviware/dvisvgm/src/DvisvgmSpecialHandler.hpp index 0f73c73ecd..d6aa32c9ee 100644 --- a/dviware/dvisvgm/src/DvisvgmSpecialHandler.hpp +++ b/dviware/dvisvgm/src/DvisvgmSpecialHandler.hpp @@ -53,7 +53,7 @@ class DvisvgmSpecialHandler : public SpecialHandler { : _append(append), _pushContext(push), _popContext(pop) {} void parse (const std::string &xml, SpecialActions &actions, bool finish=false); - void flush (SpecialActions &actions); + void finish (SpecialActions &actions); protected: void openElement (const std::string &tag, SpecialActions &actions); @@ -65,6 +65,7 @@ class DvisvgmSpecialHandler : public SpecialHandler { PopFunc _popContext; std::string _xmlbuf; NameStack _nameStack; ///< names of nested elements still missing a closing tag + bool _error=false; }; using StringVector = std::vector<std::string>; diff --git a/dviware/dvisvgm/src/FileSystem.cpp b/dviware/dvisvgm/src/FileSystem.cpp index a1fafbdece..e1db59fa03 100644 --- a/dviware/dvisvgm/src/FileSystem.cpp +++ b/dviware/dvisvgm/src/FileSystem.cpp @@ -20,12 +20,14 @@ #include <config.h> #include <algorithm> +#include <chrono> #include <cstdlib> #include <cstring> #include <fstream> #include "FileSystem.hpp" #include "utility.hpp" #include "version.hpp" +#include "XXHashFunction.hpp" #ifdef HAVE_UNISTD_H #include <unistd.h> @@ -51,7 +53,7 @@ using namespace std; string FileSystem::TMPDIR; -const char *FileSystem::TMPSUBDIR = nullptr; +FileSystem::TemporaryDirectory FileSystem::_tmpdir; /** Private wrapper function for mkdir: creates a single folder. @@ -79,13 +81,6 @@ static bool inline s_rmdir (const string &dirname) { } -FileSystem::~FileSystem () { - // remove the subdirectory from the system's temp folder (if empty) - if (TMPSUBDIR) - s_rmdir(tmpdir()); -} - - bool FileSystem::remove (const string &fname) { return unlink(fname.c_str()) == 0; } @@ -192,36 +187,29 @@ const char* FileSystem::userdir () { /** Returns the path of the temporary folder. */ string FileSystem::tmpdir () { - string ret; - if (!TMPDIR.empty()) - ret = TMPDIR; - else { + if (_tmpdir.path().empty()) { + string basedir; + if (!TMPDIR.empty()) + basedir = TMPDIR; + else { #ifdef _WIN32 - char buf[MAX_PATH]; - if (GetTempPath(MAX_PATH, buf)) - ret = ensureForwardSlashes(buf); - else - ret = "."; + char buf[MAX_PATH]; + if (GetTempPath(MAX_PATH, buf)) + basedir = ensureForwardSlashes(buf); + else + basedir = "."; #else - if (const char *path = getenv("TMPDIR")) - ret = path; - else - ret = "/tmp"; + if (const char *path = getenv("TMPDIR")) + basedir = path; + else + basedir = "/tmp"; #endif - if (ret.back() == '/') - ret.pop_back(); - static bool initialized=false; - if (!initialized && ret != ".") { - TMPSUBDIR = PROGRAM_NAME; - s_mkdir(ret + "/" + TMPSUBDIR); - initialized = true; + if (basedir.back() == '/') + basedir.pop_back(); } - if (TMPSUBDIR) - ret += string("/") + TMPSUBDIR; + _tmpdir = TemporaryDirectory(basedir, PROGRAM_NAME); } - if (!ret.empty() && ret.back() != '/') - ret += '/'; - return ret; + return _tmpdir.path(); } @@ -368,3 +356,33 @@ int FileSystem::collect (const std::string &dirname, vector<string> &entries) { #endif return entries.size(); } + + +/** Creates a temporary directory in a given folder. + * @param[in] folder folder path in which the directory is to be created + * @param[in] prefix initial string of the directory name */ +FileSystem::TemporaryDirectory::TemporaryDirectory (const std::string &folder, string prefix) { + using namespace std::chrono; + auto now = system_clock::now().time_since_epoch(); + auto now_ms = duration_cast<milliseconds>(now).count(); + auto hash = XXH64HashFunction(to_string(now_ms)).digestValue(); + if (!prefix.empty() && prefix.back() != '-') + prefix += "-"; + for (int i=0; i < 10 && _path.empty(); i++) { + hash++; + stringstream oss; + oss << folder << '/' << prefix << hex << hash; + if (exists(oss.str())) + continue; + if (s_mkdir(oss.str())) + _path = oss.str() + "/"; + else + break; + } +} + + +FileSystem::TemporaryDirectory::~TemporaryDirectory () { + if (!_path.empty()) + s_rmdir(_path); +} diff --git a/dviware/dvisvgm/src/FileSystem.hpp b/dviware/dvisvgm/src/FileSystem.hpp index f82ff1e870..9a45c4ac19 100644 --- a/dviware/dvisvgm/src/FileSystem.hpp +++ b/dviware/dvisvgm/src/FileSystem.hpp @@ -25,8 +25,23 @@ #include <vector> class FileSystem { + class TemporaryDirectory { + friend class FileSystem; + public: + TemporaryDirectory (const std::string &folder, std::string prefix); + TemporaryDirectory (TemporaryDirectory &&tmpdir) =default; + ~TemporaryDirectory (); + TemporaryDirectory& operator = (TemporaryDirectory &&tmpdir) =default; + const std::string& path () const {return _path;} + + protected: + TemporaryDirectory () =default; + + private: + std::string _path; + }; + public: - ~FileSystem (); static bool remove (const std::string &fname); static bool rename (const std::string &oldname, const std::string &newname); static bool copy (const std::string &src, const std::string &dest, bool remove_src=false); @@ -48,8 +63,9 @@ class FileSystem { protected: FileSystem () =default; - bool system_tmpdir_available (); - static const char* TMPSUBDIR; ///< subdirectory of the system's temporary folder + + private: + static TemporaryDirectory _tmpdir; }; #endif diff --git a/dviware/dvisvgm/src/Font.cpp b/dviware/dvisvgm/src/Font.cpp index e40ad8a392..8cbcb1db47 100644 --- a/dviware/dvisvgm/src/Font.cpp +++ b/dviware/dvisvgm/src/Font.cpp @@ -607,8 +607,13 @@ PhysicalFont::Type NativeFont::type () const { double NativeFont::charWidth (int c) const { FontEngine::instance().setFont(*this); int upem = FontEngine::instance().getUnitsPerEM(); - double w = upem ? (scaledSize()*FontEngine::instance().getAdvance(c)/upem*_style.extend) : 0; - w += abs(_style.slant*charHeight(c)); + return upem ? (scaledSize()*FontEngine::instance().getAdvance(c)/upem*_style.extend) : 0; +} + + +double NativeFont::italicCorr(int c) const { + double w = abs(_style.slant*charHeight(c)); // slant := tan(phi) = dx/height + w *= _style.extend; return w; } diff --git a/dviware/dvisvgm/src/Font.hpp b/dviware/dvisvgm/src/Font.hpp index 59556e7a81..0a92cbeff5 100644 --- a/dviware/dvisvgm/src/Font.hpp +++ b/dviware/dvisvgm/src/Font.hpp @@ -266,7 +266,7 @@ class NativeFont : public PhysicalFont { double charWidth (int c) const override; double charDepth (int c) const override; double charHeight (int c) const override; - double italicCorr (int c) const override {return 0;} + double italicCorr (int c) const override; const FontMetrics* getMetrics () const override {return nullptr;} const FontStyle* style () const override {return &_style;} Color color () const override {return _color;} diff --git a/dviware/dvisvgm/src/FontCache.cpp b/dviware/dvisvgm/src/FontCache.cpp index e024558913..1fd13c9c11 100644 --- a/dviware/dvisvgm/src/FontCache.cpp +++ b/dviware/dvisvgm/src/FontCache.cpp @@ -160,7 +160,7 @@ bool FontCache::write (const string &fontname, ostream &os) const { XXH32HashFunction hashfunc; sw.writeUnsigned(FORMAT_VERSION, 1, hashfunc); - sw.writeBytes(hashfunc.digestValue()); // space for checksum + sw.writeBytes(hashfunc.digestBytes()); // space for checksum sw.writeString(fontname, hashfunc, true); sw.writeUnsigned(_glyphs.size(), 4, hashfunc); WriteActions actions(sw, hashfunc); @@ -171,7 +171,7 @@ bool FontCache::write (const string &fontname, ostream &os) const { glyph.iterate(actions, false); } os.seekp(1); - auto digest = hashfunc.digestValue(); + auto digest = hashfunc.digestBytes(); sw.writeBytes(digest); // insert checksum os.seekp(0, ios::end); return true; @@ -215,7 +215,7 @@ bool FontCache::read (const string &fontname, istream &is) { auto hashcmp = sr.readBytes(hashfunc.digestSize()); hashfunc.update(is); - if (hashfunc.digestValue() != hashcmp) + if (hashfunc.digestBytes() != hashcmp) return false; is.clear(); @@ -309,7 +309,7 @@ bool FontCache::fontinfo (std::istream &is, FontInfo &info) { info.checksum = sr.readBytes(hashfunc.digestSize()); hashfunc.update(is); - if (hashfunc.digestValue() != info.checksum) + if (hashfunc.digestBytes() != info.checksum) return false; is.clear(); diff --git a/dviware/dvisvgm/src/FontManager.cpp b/dviware/dvisvgm/src/FontManager.cpp index 58d2dfc261..eabb4a3d30 100644 --- a/dviware/dvisvgm/src/FontManager.cpp +++ b/dviware/dvisvgm/src/FontManager.cpp @@ -255,8 +255,6 @@ int FontManager::registerFont (uint32_t fontnum, string filename, int fontIndex, if (id >= 0) return id; - if (!filename.empty() && filename[0] == '[' && filename[filename.size()-1] == ']') - filename = filename.substr(1, filename.size()-2); string fontname = NativeFont::uniqueName(filename, style); const char *path = filename.c_str(); unique_ptr<Font> newfont; diff --git a/dviware/dvisvgm/src/GraphicsPath.hpp b/dviware/dvisvgm/src/GraphicsPath.hpp index 565251c0d0..64c112ad0b 100644 --- a/dviware/dvisvgm/src/GraphicsPath.hpp +++ b/dviware/dvisvgm/src/GraphicsPath.hpp @@ -732,7 +732,7 @@ class GraphicsPath { private: std::deque<CommandVariant> _commands; ///< sequence of path commands - WindingRule _windingRule; + WindingRule _windingRule = WindingRule::NON_ZERO; 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 7f99dacaf0..16661fb3d6 100644 --- a/dviware/dvisvgm/src/HashFunction.cpp +++ b/dviware/dvisvgm/src/HashFunction.cpp @@ -96,7 +96,7 @@ void HashFunction::update (istream &is) { string HashFunction::digestString () const { ostringstream oss; oss << hex << setfill('0'); - for (int byte : digestValue()) + for (int byte : digestBytes()) oss << setw(2) << byte; return oss.str(); } diff --git a/dviware/dvisvgm/src/HashFunction.hpp b/dviware/dvisvgm/src/HashFunction.hpp index b13ee39a50..e31040075e 100644 --- a/dviware/dvisvgm/src/HashFunction.hpp +++ b/dviware/dvisvgm/src/HashFunction.hpp @@ -35,7 +35,7 @@ class HashFunction { virtual void update (const char *data, size_t length) =0; 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; + virtual std::vector<uint8_t> digestBytes () const =0; void update (std::istream &is); std::string digestString () const; static std::vector<std::string> supportedAlgorithms (); diff --git a/dviware/dvisvgm/src/MD5HashFunction.hpp b/dviware/dvisvgm/src/MD5HashFunction.hpp index f0eda874f2..27539576ed 100644 --- a/dviware/dvisvgm/src/MD5HashFunction.hpp +++ b/dviware/dvisvgm/src/MD5HashFunction.hpp @@ -42,7 +42,7 @@ class MD5HashFunction : 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());} - std::vector<uint8_t> digestValue () const override { + std::vector<uint8_t> digestBytes () const override { std::vector<uint8_t> hash(16); MD5_CTX savedContext = _context; MD5_Final(hash.data(), &_context); // also erases the context structure diff --git a/dviware/dvisvgm/src/PathClipper.cpp b/dviware/dvisvgm/src/PathClipper.cpp index 4aa4373f46..f1cca09fd6 100644 --- a/dviware/dvisvgm/src/PathClipper.cpp +++ b/dviware/dvisvgm/src/PathClipper.cpp @@ -310,22 +310,37 @@ inline PolyFillType polyFillType (CurvedPath::WindingRule wr) { } -/** Computes the intersection of to curved paths. +/** Combines two curved paths by applying a boolean operation on them. + * @param[in] op operation to perform * @param[in] p1 first curved path * @param[in] p2 second curved path - * @param[out] result intersection of p1 and p2 */ -void PathClipper::intersect (const CurvedPath &p1, const CurvedPath &p2, CurvedPath &result) { - if (p1.size() < 2 || p2.size() < 2) - return; - Clipper clipper; - Polygons polygons; - flatten(p1, polygons); - clipper.AddPaths(polygons, ptSubject, true); - polygons.clear(); - flatten(p2, polygons); - clipper.AddPaths(polygons, ptClip, true); - clipper.ZFillFunction(callback); - Polygons flattenedPath; - clipper.Execute(ctIntersection, flattenedPath, polyFillType(p1.windingRule()), polyFillType(p2.windingRule())); - reconstruct(flattenedPath, result); + * @return intersection of p1 and p2 */ +CurvedPath PathClipper::combine (ClipType op, const CurvedPath &p1, const CurvedPath &p2) { + CurvedPath result; + if (p1.size() > 1 && p2.size() > 1) { + Clipper clipper; + Polygons polygons; + flatten(p1, polygons); + clipper.AddPaths(polygons, ptSubject, true); + polygons.clear(); + flatten(p2, polygons); + clipper.AddPaths(polygons, ptClip, true); + clipper.ZFillFunction(callback); + Polygons flattenedPath; + clipper.Execute(op, flattenedPath, polyFillType(p1.windingRule()), polyFillType(p2.windingRule())); + reconstruct(flattenedPath, result); + } + return result; +} + + +/** Returns the intersection of two curved paths. */ +CurvedPath PathClipper::intersect (const CurvedPath &p1, const CurvedPath &p2) { + return combine(ctIntersection, p1, p2); +} + + +/** Returns the union of two curved paths. */ +CurvedPath PathClipper::unite (const CurvedPath &p1, const CurvedPath &p2) { + return combine(ctUnion, p1, p2); } diff --git a/dviware/dvisvgm/src/PathClipper.hpp b/dviware/dvisvgm/src/PathClipper.hpp index e3e6bd1681..7c42f5de6d 100644 --- a/dviware/dvisvgm/src/PathClipper.hpp +++ b/dviware/dvisvgm/src/PathClipper.hpp @@ -36,9 +36,11 @@ class PathClipper { using CurvedPath = GraphicsPath<double>; public: - void intersect (const CurvedPath &p1, const CurvedPath &p2, CurvedPath &result); + CurvedPath intersect (const CurvedPath &p1, const CurvedPath &p2); + CurvedPath unite (const CurvedPath &p1, const CurvedPath &p2); protected: + CurvedPath combine (ClipperLib::ClipType op, const CurvedPath &p1, const CurvedPath &p2); void flatten (const CurvedPath &gp, ClipperLib::Paths &polygons); void reconstruct (const ClipperLib::Path &polygon, CurvedPath &path); void reconstruct (const ClipperLib::Paths &polygons, CurvedPath &path); diff --git a/dviware/dvisvgm/src/PsSpecialHandler.cpp b/dviware/dvisvgm/src/PsSpecialHandler.cpp index 2301eeb571..9641556dd5 100644 --- a/dviware/dvisvgm/src/PsSpecialHandler.cpp +++ b/dviware/dvisvgm/src/PsSpecialHandler.cpp @@ -941,7 +941,7 @@ void PsSpecialHandler::clip (Path path, bool evenodd) { if (!_actions->getMatrix().isIdentity()) path.transform(_actions->getMatrix()); if (_clipStack.prependedPath()) - path.prepend(*_clipStack.prependedPath()); + path = PathClipper().unite(*_clipStack.prependedPath(), path); int oldID = _clipStack.topID(); @@ -954,9 +954,7 @@ void PsSpecialHandler::clip (Path path, bool evenodd) { else { // compute the intersection of the current clipping path with the current graphics path const Path *oldPath = _clipStack.path(); - Path intersectedPath(windingRule); - PathClipper clipper; - clipper.intersect(*oldPath, path, intersectedPath); + Path intersectedPath = PathClipper().intersect(*oldPath, path); pathReplaced = _clipStack.replace(intersectedPath); intersectedPath.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS); } diff --git a/dviware/dvisvgm/src/XMLNode.cpp b/dviware/dvisvgm/src/XMLNode.cpp index db07d95dd2..363115fbca 100644 --- a/dviware/dvisvgm/src/XMLNode.cpp +++ b/dviware/dvisvgm/src/XMLNode.cpp @@ -259,7 +259,7 @@ XMLElement* XMLElement::wrap (XMLNode *first, XMLNode *last, const string &name) XMLNode *child = first; while (child && child != last) { XMLNode *next = child->next(); - wrapper->insertLast(remove(child)); + wrapper->insertLast(detach(child)); child = next; } XMLElement *ret = wrapper.get(); @@ -277,44 +277,41 @@ XMLElement* XMLElement::wrap (XMLNode *first, XMLNode *last, const string &name) * Example: unwrap a child element b of a: * <a>text1<b><c/>text2<d/></b></a> => <a>text1<c/>text2<d/></a> * @param[in] child child element to unwrap - * @return raw pointer to the first node C1 of the unwrapped sequence */ -XMLNode* XMLElement::unwrap (XMLElement *child) { - if (!child || !child->parent()) + * @return raw pointer to the first node C1 of the unwrapped sequence or nullptr if element was empty */ +XMLNode* XMLElement::unwrap (XMLElement *element) { + if (!element || !element->parent()) return nullptr; - XMLElement *parent = child->parent()->toElement(); - auto removedChild = remove(child); - if (child->empty()) - return child->next(); - XMLNode *firstGrandchild = child->firstChild(); - XMLNode *prev = child->prev(); - unique_ptr<XMLNode> grandchild = std::move(child->_firstChild); - while (grandchild) { - prev = parent->insertAfter(std::move(grandchild), prev); - grandchild = std::move(prev->_next); - } - return firstGrandchild; -} - - -/** Removes a child node from the element. - * @param[in] child pointer to child to remove - * @return pointer to removed child or nullptr if given child doesn't belong to this element */ -unique_ptr<XMLNode> XMLElement::remove (XMLNode *child) { - unique_ptr<XMLNode> node; - if (child && child->parent()) { - XMLElement *parent = child->parent()->toElement(); - if (child == parent->_lastChild) - parent->_lastChild = child->prev(); - if (child != parent->firstChild()) - node = child->prev()->removeNext(); + XMLElement *parent = element->parent()->toElement(); + XMLNode *prev = element->prev(); + auto unlinkedElement = util::static_unique_ptr_cast<XMLElement>(detach(element)); + if (unlinkedElement->empty()) + return nullptr; + XMLNode *firstChild = unlinkedElement->firstChild(); + while (auto child = detach(unlinkedElement->firstChild())) + prev = parent->insertAfter(std::move(child), prev); + return firstChild; +} + + +/** Isolates a node and its descendants from a subtree. + * @param[in] node raw pointer to node to be detached + * @return unique pointer to the detached node. */ +unique_ptr<XMLNode> XMLElement::detach (XMLNode *node) { + unique_ptr<XMLNode> uniqueNode; + if (node && node->parent()) { + XMLElement *parent = node->parent()->toElement(); + if (node == parent->_lastChild) + parent->_lastChild = node->prev(); + if (node != parent->firstChild()) + uniqueNode = node->prev()->removeNext(); else { - node = std::move(parent->_firstChild); - if ((parent->_firstChild = std::move(node->_next))) + uniqueNode = std::move(parent->_firstChild); + if ((parent->_firstChild = std::move(uniqueNode->_next))) parent->_firstChild->prev(nullptr); } - child->parent(nullptr); + node->parent(nullptr); } - return node; + return uniqueNode; } diff --git a/dviware/dvisvgm/src/XMLNode.hpp b/dviware/dvisvgm/src/XMLNode.hpp index 534ae87bd7..65bd69fab8 100644 --- a/dviware/dvisvgm/src/XMLNode.hpp +++ b/dviware/dvisvgm/src/XMLNode.hpp @@ -157,7 +157,7 @@ class XMLElement : public XMLNode { const XMLElement* toElement () const override {return this;} const Attribute* getAttribute (const std::string &name) const; - static std::unique_ptr<XMLNode> remove (XMLNode *child); + static std::unique_ptr<XMLNode> detach (XMLNode *node); static XMLElement* wrap (XMLNode *first, XMLNode *last, const std::string &name); static XMLNode* unwrap (XMLElement *child); diff --git a/dviware/dvisvgm/src/XXHashFunction.hpp b/dviware/dvisvgm/src/XXHashFunction.hpp index e44083f182..d26a4e4984 100644 --- a/dviware/dvisvgm/src/XXHashFunction.hpp +++ b/dviware/dvisvgm/src/XXHashFunction.hpp @@ -36,6 +36,7 @@ struct XXHInterface { template<> struct XXHInterface<4> { using State = XXH32_state_t; + using Digest = XXH32_hash_t; static constexpr auto createState = &XXH32_createState; static constexpr auto freeState = &XXH32_freeState; static constexpr auto reset = &XXH32_reset; @@ -46,6 +47,7 @@ struct XXHInterface<4> { template<> struct XXHInterface<8> { using State = XXH64_state_t; + using Digest = XXH64_hash_t; static constexpr auto createState = &XXH64_createState; static constexpr auto freeState = &XXH64_freeState; static constexpr auto reset = &XXH64_reset; @@ -57,6 +59,7 @@ struct XXHInterface<8> { template<> struct XXHInterface<16> { using State = XXH3_state_t; + using Digest = XXH128_hash_t; static constexpr auto createState = &XXH3_createState; static constexpr auto freeState = &XXH3_freeState; static constexpr auto reset = &XXH3_128bits_reset_withSeed; @@ -71,7 +74,7 @@ class XXHashFunction : public HashFunction { using Interface = XXHInterface<HASH_BYTES>; public: XXHashFunction () : _state(Interface::createState()) {Interface::reset(_state, 0);} - XXHashFunction(const char *data, size_t length) : XXHashFunction() {update(data, length);} + XXHashFunction (const char *data, size_t length) : XXHashFunction() {update(data, length);} explicit XXHashFunction(const std::string &data) : XXHashFunction() {update(data);} explicit XXHashFunction(const std::vector<uint8_t> &data) : XXHashFunction() {update(data);} ~XXHashFunction () override {Interface::freeState(_state);} @@ -83,10 +86,11 @@ class XXHashFunction : public HashFunction { using HashFunction::update; // unhide update(istream &is) defined in base class - std::vector<uint8_t> digestValue () const override { + std::vector<uint8_t> digestBytes () const override { return util::bytes(Interface::digest(_state), HASH_BYTES); } + typename Interface::Digest digestValue () const {return Interface::digest(_state);} static unsigned version () {return XXH_versionNumber();} private: @@ -100,7 +104,7 @@ using XXH64HashFunction = XXHashFunction<8>; using XXH128HashFunction = XXHashFunction<16>; template<> -inline std::vector<uint8_t> XXHashFunction<16>::digestValue () const { +inline std::vector<uint8_t> XXHashFunction<16>::digestBytes () const { std::vector<uint8_t> hash; auto digest = Interface::digest(_state); for (auto chunk : {digest.high64, digest.low64}) { diff --git a/dviware/dvisvgm/src/dvisvgm.cpp b/dviware/dvisvgm/src/dvisvgm.cpp index 74a8e83abc..803e34e717 100644 --- a/dviware/dvisvgm/src/dvisvgm.cpp +++ b/dviware/dvisvgm/src/dvisvgm.cpp @@ -480,16 +480,20 @@ int main (int argc, char *argv[]) { } catch (DVIException &e) { Message::estream() << "\nDVI error: " << e.what() << '\n'; + return -1; } catch (PSException &e) { Message::estream() << "\nPostScript error: " << e.what() << '\n'; + return -2; } catch (SignalException &e) { Message::wstream().clearline(); Message::wstream(true) << "execution interrupted by user\n"; + return -3; } catch (exception &e) { Message::estream(true) << e.what() << '\n'; + return -4; } return 0; } diff --git a/dviware/dvisvgm/src/optimizer/GroupCollapser.cpp b/dviware/dvisvgm/src/optimizer/GroupCollapser.cpp index b8a32786c1..c3060be788 100644 --- a/dviware/dvisvgm/src/optimizer/GroupCollapser.cpp +++ b/dviware/dvisvgm/src/optimizer/GroupCollapser.cpp @@ -61,7 +61,7 @@ static void remove_ws_nodes (XMLElement *elem) { node = node->next(); else { XMLNode *next = node->next(); - XMLElement::remove(node); + XMLElement::detach(node); node = next; } } @@ -69,29 +69,31 @@ static void remove_ws_nodes (XMLElement *elem) { /** Recursively removes all redundant group elements from the given context element - * and moves their attributes to the corresponding parent element. + * and moves their attributes to the corresponding parent elements. * @param[in] context root of the subtree to process */ void GroupCollapser::execute (XMLElement *context) { if (!context) return; - XMLNode *node=context->firstChild(); - while (node) { - XMLNode *next = node->next(); // keep safe pointer to next node - if (XMLElement *elem = node->toElement()) - execute(elem); - node = next; - } - if (context->name() == "g" && context->attributes().empty()) { - // unwrap group without attributes - remove_ws_nodes(context); - XMLElement::unwrap(context); + + XMLNode *child=context->firstChild(); + while (child) { + XMLNode *next=child->next(); + if (XMLElement *childElement = child->toElement()) { + execute(childElement); + // check for groups without attributes and remove them + if (childElement->name() == "g" && childElement->attributes().empty()) { + remove_ws_nodes(childElement); + if (XMLNode *firstUnwrappedNode = XMLElement::unwrap(childElement)) + next = firstUnwrappedNode; + } + } + child = next; } - else { - XMLElement *child = only_child_element(context); - if (child && collapsible(*context)) { - if (child->name() == "g" && unwrappable(*child, *context) && moveAttributes(*child, *context)) { + if (XMLElement *childElement = only_child_element(context)) { + if (collapsible(*context)) { + if (childElement->name() == "g" && unwrappable(*childElement, *context) && moveAttributes(*childElement, *context)) { remove_ws_nodes(context); - XMLElement::unwrap(child); + XMLElement::unwrap(childElement); } } } @@ -147,12 +149,15 @@ bool GroupCollapser::collapsible (const XMLElement &element) { * @param[in] source element whose children and attributes should be moved * @param[in] dest element that should receive the children and attributes */ bool GroupCollapser::unwrappable (const XMLElement &source, const XMLElement &dest) { - // check for colliding clip-path attributes - if (const char *cp1 = source.getAttributeValue("clip-path")) { - if (const char *cp2 = dest.getAttributeValue("clip-path")) { - if (string(cp1) != cp2) - return false; - } + const char *cp1 = source.getAttributeValue("clip-path"); + const char *cp2 = dest.getAttributeValue("clip-path"); + if (cp2) { + // check for colliding clip-path attributes + if (cp1 && string(cp1) != string(cp2)) + return false; + // don't apply inner transformations to outer clipping paths + if (source.hasAttribute("transform")) + return false; } // these attributes prevent a group from being unwrapped static const char *attribs[] = { diff --git a/dviware/dvisvgm/src/optimizer/RedundantElementRemover.cpp b/dviware/dvisvgm/src/optimizer/RedundantElementRemover.cpp index 906ccc2d14..a8aca1a993 100644 --- a/dviware/dvisvgm/src/optimizer/RedundantElementRemover.cpp +++ b/dviware/dvisvgm/src/optimizer/RedundantElementRemover.cpp @@ -64,6 +64,6 @@ void RedundantElementRemover::execute (XMLElement *defs, XMLElement *context) { descendants.clear(); for (const string &str : idTree.getKeys()) { XMLElement *node = defs->getFirstDescendant("clipPath", "id", str.c_str()); - XMLElement::remove(node); + XMLElement::detach(node); } } diff --git a/dviware/dvisvgm/src/optimizer/SVGOptimizer.cpp b/dviware/dvisvgm/src/optimizer/SVGOptimizer.cpp index b2d45176e7..ac4a78fe20 100644 --- a/dviware/dvisvgm/src/optimizer/SVGOptimizer.cpp +++ b/dviware/dvisvgm/src/optimizer/SVGOptimizer.cpp @@ -39,9 +39,9 @@ SVGOptimizer::SVGOptimizer (SVGTree *svg) : _svg(svg) { // optimizer modules available to the user; must be listed in default order // _moduleEntries.emplace_back(ModuleEntry("remove-ws", util::make_unique<WSNodeRemover>())); _moduleEntries.emplace_back(ModuleEntry("simplify-text", util::make_unique<TextSimplifier>())); + _moduleEntries.emplace_back(ModuleEntry("simplify-transform", util::make_unique<TransformSimplifier>())); _moduleEntries.emplace_back(ModuleEntry("group-attributes", util::make_unique<AttributeExtractor>())); _moduleEntries.emplace_back(ModuleEntry("collapse-groups", util::make_unique<GroupCollapser>())); - _moduleEntries.emplace_back(ModuleEntry("simplify-transform", util::make_unique<TransformSimplifier>())); _moduleEntries.emplace_back(ModuleEntry("remove-clippath", util::make_unique<RedundantElementRemover>())); } diff --git a/dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp b/dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp index 461698b62a..2541631b0e 100644 --- a/dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp +++ b/dviware/dvisvgm/src/optimizer/TransformSimplifier.cpp @@ -79,8 +79,9 @@ bool TransformSimplifier::incorporateTransform (XMLElement *elem, const Matrix & if (const char *ystr = elem->getAttributeValue("y")) y = strtod(ystr, nullptr); // width and height attributes must not become negative. Hence, only apply the scaling - // values if they are non-negative. Otherwise, keep a scaling matrix - if (sx < 0 || sy < 0) { + // values if they are non-negative. Otherwise, keep a scaling matrix. Also retain scaling + // transformations in image elements to avoid the need of attribute 'preseveAspectRatio'. + if (sx < 0 || sy < 0 || elem->name() == "image") { x += (sx == 0 ? 0 : tx/sx); y += (sy == 0 ? 0 : ty/sy); elem->addAttribute("transform", "scale("+XMLString(sx)+","+XMLString(sy)+")"); @@ -120,7 +121,7 @@ static string scale_cmd (double sx, double sy) { XMLString sxstr(sx), systr(sy); if (sxstr != "1" || systr != "1") { ret = "scale("+sxstr; - if (systr != "1") + if (systr != sxstr) ret += " "+systr; ret += ')'; } diff --git a/dviware/dvisvgm/src/optimizer/WSNodeRemover.cpp b/dviware/dvisvgm/src/optimizer/WSNodeRemover.cpp index be58bf70fd..3f83011f5a 100644 --- a/dviware/dvisvgm/src/optimizer/WSNodeRemover.cpp +++ b/dviware/dvisvgm/src/optimizer/WSNodeRemover.cpp @@ -34,7 +34,7 @@ void WSNodeRemover::execute (XMLElement *context) { while (child) { if (removeWS && child->toWSNode()) { XMLNode *next = child->next(); - XMLElement::remove(child); + XMLElement::detach(child); child = next; continue; } diff --git a/dviware/dvisvgm/src/psdefs.cpp b/dviware/dvisvgm/src/psdefs.cpp index 9c5991217d..9c8380816d 100644 --- a/dviware/dvisvgm/src/psdefs.cpp +++ b/dviware/dvisvgm/src/psdefs.cpp @@ -66,88 +66,90 @@ const char *PSInterpreter::PSDEFS = "felse}put @SD/fill{@dodraw @GD/@nulldev get not and{prcolor 0 1(newpath)prcmd " "prpath 0(fill)prcmd :newpath}{:fill}ifelse}put @SD/eofill{@dodraw @GD/@nulldev" " get not and{prcolor 0 1(newpath)prcmd prpath 0(eofill)prcmd :newpath}{:eofill" -"}ifelse}put @SD/clip{:clip @GD/@nulldev get not{0 1(newpath)prcmd prpath 0(cli" -"p)prcmd}if}put @SD/eoclip{:eoclip @GD/@nulldev get not{0 1(newpath)prcmd prpat" -"h 0(eoclip)prcmd}}put @SD/shfill{begin currentdict/ShadingType known currentdi" -"ct/ColorSpace known and currentdict/DataSource known and currentdict/Function " -"known not and ShadingType 4 ge{DataSource type/arraytype eq{<</DeviceGray 1/De" -"viceRGB 3/DeviceCMYK 4/bgknown currentdict/Background known/bbknown currentdic" -"t/BBox known>>begin currentdict ColorSpace known{ShadingType ColorSpace load b" -"gknown{1 Background aload pop}{0}ifelse bbknown{1 BBox aload pop}{0}ifelse Sha" -"dingType 5 eq{VerticesPerRow}if DataSource aload length 4 add bgknown{ColorSpa" -"ce load add}if bbknown{4 add}if ShadingType 5 eq{1 add}if(shfill)prcmd}if end}" -"if}if end}put @SD/image{dup type/dicttype eq{dup}{<</Width 6 index/Height 7 in" -"dex/colorimg false>>}ifelse @execimg}put @SD/colorimage{<<2 index{/Width 2 ind" -"ex 8 add index/Height 4 index 9 add index}{/Width 8 index/Height 9 index}ifels" -"e/colorimg true>>@execimg}put/@imgbase(./)def/@imgdevice(jpeg)def/@execimg{@GD" -"/@imgcnt 2 copy .knownget{1 add}{1}ifelse put begin<</imgdev null/imgid @GD/@i" -"mgcnt get/ispng @imgdevice 0 3 getinterval(png)eq dup/suffix exch{(.png)}{(.jp" -"g)}ifelse/colorimg currentdict/colorimg .knownget dup{pop}if/colordev 1 index " -"currentcolorspace dup length 1 ne exch 0 get/DeviceGray ne or or>>begin @imgde" -"vice(png)ne @imgdevice(jpeg)ne and{@imgdevice cvn}{colordev{ispng{/png16m}{/jp" -"eg}ifelse}{ispng{/pnggray}{/jpeggray}ifelse}ifelse}ifelse dup devicedict exch " -"known{:gsave/imgdev exch finddevice def mark/OutputFile @imgbase imgid 20 stri" -"ng cvs strconcat suffix strconcat/PageSize[Width Height]/UseFastColor true isp" -"ng{@imgdevice(pngmonod)eq{/MinFeatureSize where{pop/MinFeatureSize MinFeatureS" -"ize}if}if}{/JPEGQ where{pop/JPEGQ JPEGQ}if}ifelse imgdev putdeviceprops setdev" -"ice[Width 0 0 Height neg 0 Height]/setmatrix sysexec colorimg{:colorimage}{:im" -"age}ifelse/copypage sysexec mark/OutputFile()imgdev putdeviceprops pop :gresto" -"re imgid Width Height 3(image)prcmd}{pop colorimg{:colorimage}{:image}ifelse}i" -"felse end end}def/@rect{4 -2 roll moveto exch dup 0 rlineto exch 0 exch rlinet" -"o neg 0 rlineto closepath}bind def/@rectcc{4 -2 roll moveto 2 copy 0 lt exch 0" -" lt xor{dup 0 exch rlineto exch 0 rlineto neg 0 exch rlineto}{exch dup 0 rline" -"to exch 0 exch rlineto neg 0 rlineto}ifelse closepath}bind def @SD/rectclip{:n" -"ewpath dup type/arraytype eq{aload length 4 idiv{@rectcc}repeat}{@rectcc}ifels" -"e clip :newpath}put @SD/rectfill{:gsave :newpath dup type/arraytype eq{aload l" -"ength 4 idiv{@rectcc}repeat}{@rectcc}ifelse fill :grestore}put @SD/rectstroke{" -"gsave :newpath dup type/arraytype eq{aload length 4 idiv{@rect}repeat}{@rect}i" -"felse stroke grestore}put false setglobal @SD readonly pop/initclip 0 defpr/cl" -"ippath 0 defpr/sysexec{@SD exch get exec}def/adddot{dup length 1 add string du" -"p 0 46 put dup 3 -1 roll 1 exch putinterval}def/setlinewidth{dup/setlinewidth " -"sysexec 1(setlinewidth)prcmd}def/setlinecap 1 defpr/setlinejoin 1 defpr/setmit" -"erlimit 1 defpr/setdash{mark 3 1 roll 2 copy/setdash sysexec exch aload length" -" 1 add -1 roll counttomark(setdash)prcmd pop}def/@setpagedevice{pop<<>>/setpag" -"edevice sysexec matrix setmatrix newpath 0(setpagedevice)prcmd}def/@checknulld" -"ev{@GD/@nulldev get{currentpagedevice maxlength 0 ne{@GD/@nulldev false put 0 " -"1(setnulldevice)prcmd}if}if}def/prcolor{currentcolorspace @setcolorspace curre" -"ntrgbcolor 3(setrgbcolor)prcmd}def/printgstate{@dodraw @GD/@nulldev get not an" -"d{matrix currentmatrix aload pop 6(setmatrix)prcmd applyscalevals currentlinew" -"idth 1(setlinewidth)prcmd currentlinecap 1(setlinecap)prcmd currentlinejoin 1(" -"setlinejoin)prcmd currentmiterlimit 1(setmiterlimit)prcmd prcolor currentdash " -"mark 3 1 roll exch aload length 1 add -1 roll counttomark(setdash)prcmd pop}if" -"}def/strconcat{exch dup length 2 index length add string dup dup 4 2 roll copy" -" length 4 -1 roll putinterval}def/setgstate{/setgstate sysexec printgstate}def" -"/save{@UD begin/@saveID vmstatus pop pop def end :save @saveID 1(save)prcmd}de" -"f/restore{:restore @checknulldev printgstate @UD/@saveID known{@UD begin @save" -"ID end}{0}ifelse 1(restore)prcmd}def/gsave 0 defpr/grestore{:grestore @checknu" -"lldev printgstate 0(grestore)prcmd}def/grestoreall{:grestoreall @checknulldev " -"setstate 0(grestoreall)prcmd}def/rotate{dup type/arraytype ne @dodraw and{dup " -"1(rotate)prcmd}if/rotate sysexec applyscalevals}def/scale{dup type/arraytype n" -"e @dodraw and{2 copy 2(scale)prcmd}if/scale sysexec applyscalevals}def/transla" -"te{dup type/arraytype ne @dodraw and{2 copy 2(translate)prcmd}if/translate sys" -"exec}def/setmatrix{dup/setmatrix sysexec @dodraw{aload pop 6(setmatrix)prcmd a" -"pplyscalevals}{pop}ifelse}def/initmatrix{matrix setmatrix}def/concat{matrix cu" -"rrentmatrix matrix concatmatrix setmatrix}def/makepattern{gsave<</mx 3 -1 roll" -">>begin<</XUID[1000000 @patcnt]>>copy mx/makepattern sysexec dup begin Pattern" -"Type 2 lt{PatternType @patcnt BBox aload pop XStep YStep PaintType mx aload po" -"p 15(makepattern)prcmd :newpath matrix setmatrix dup PaintProc 0 1(makepattern" -")prcmd @GD/@patcnt @patcnt 1 add put}if end end grestore}def/setpattern{begin " -"PatternType 1 eq{PaintType 1 eq{XUID aload pop exch pop 1}{:gsave[currentcolor" -"space aload length -1 roll pop]/setcolorspace sysexec/setcolor sysexec XUID al" -"oad pop exch pop currentrgbcolor :grestore 4}ifelse(setpattern)prcmd currentco" -"lorspace 0 get/Pattern ne{[/Pattern currentcolorspace]/setcolorspace sysexec}i" -"f currentcolorspace @setcolorspace}{/setpattern sysexec}ifelse end}def/setcolo" -"r{dup type/dicttype eq{setpattern}{/setcolor sysexec/currentrgbcolor sysexec s" -"etrgbcolor}ifelse}def/setcolorspace{dup/setcolorspace sysexec @setcolorspace}d" -"ef/@setcolorspace{dup type/arraytype eq{0 get}if/Pattern eq{1}{0}ifelse 1(setc" -"olorspace)prcmd}def/setgray 1 defpr/setcmykcolor 4 defpr/sethsbcolor 3 defpr/s" -"etrgbcolor 3 defpr/.setopacityalpha{dup/.setopacityalpha sysexec 1(setopacitya" -"lpha)prcmd}def/.setshapealpha{dup/.setshapealpha sysexec 1(setshapealpha)prcmd" -"}def/.setblendmode{dup/.setblendmode sysexec<</Normal 0/Compatible 0/Multiply " -"1/Screen 2/Overlay 3/SoftLight 4/HardLight 5/ColorDodge 6/ColorBurn 7/Darken 8" -"/Lighten 9/Difference 10/Exclusion 11/Hue 12/Saturation 13/Color 14/Luminosity" -" 15/CompatibleOverprint 16>>exch get 1(setblendmode)prcmd}def/@pdfpagecount{(r" -")file runpdfbegin pdfpagecount runpdfend}def/@pdfpagebox{(r)file runpdfbegin d" -"up dup 1 lt exch pdfpagecount gt or{pop}{pdfgetpage/MediaBox pget pop aload po" -"p}ifelse runpdfend}def DELAYBIND{.bindnow}if "; +"}ifelse}put/.fillstroke{:gsave fill :grestore .swapcolors stroke .swapcolors}b" +"ind def/.eofillstroke{:gsave eofill :grestore .swapcolors stroke .swapcolors}b" +"ind def @SD/clip{:clip @GD/@nulldev get not{0 1(newpath)prcmd prpath 0(clip)pr" +"cmd}if}put @SD/eoclip{:eoclip @GD/@nulldev get not{0 1(newpath)prcmd prpath 0(" +"eoclip)prcmd}if}put @SD/shfill{begin currentdict/ShadingType known currentdict" +"/ColorSpace known and currentdict/DataSource known and currentdict/Function kn" +"own not and ShadingType 4 ge{DataSource type/arraytype eq{<</DeviceGray 1/Devi" +"ceRGB 3/DeviceCMYK 4/bgknown currentdict/Background known/bbknown currentdict/" +"BBox known>>begin currentdict ColorSpace known{ShadingType ColorSpace load bgk" +"nown{1 Background aload pop}{0}ifelse bbknown{1 BBox aload pop}{0}ifelse Shadi" +"ngType 5 eq{VerticesPerRow}if DataSource aload length 4 add bgknown{ColorSpace" +" load add}if bbknown{4 add}if ShadingType 5 eq{1 add}if(shfill)prcmd}if end}if" +"}if end}put @SD/image{dup type/dicttype eq{dup}{<</Width 6 index/Height 7 inde" +"x/colorimg false>>}ifelse @execimg}put @SD/colorimage{<<2 index{/Width 2 index" +" 8 add index/Height 4 index 9 add index}{/Width 8 index/Height 9 index}ifelse/" +"colorimg true>>@execimg}put/@imgbase(./)def/@imgdevice(jpeg)def/@execimg{@GD/@" +"imgcnt 2 copy .knownget{1 add}{1}ifelse put begin<</imgdev null/imgid @GD/@img" +"cnt get/ispng @imgdevice 0 3 getinterval(png)eq dup/suffix exch{(.png)}{(.jpg)" +"}ifelse/colorimg currentdict/colorimg .knownget dup{pop}if/colordev 1 index cu" +"rrentcolorspace dup length 1 ne exch 0 get/DeviceGray ne or or>>begin @imgdevi" +"ce(png)ne @imgdevice(jpeg)ne and{@imgdevice cvn}{colordev{ispng{/png16m}{/jpeg" +"}ifelse}{ispng{/pnggray}{/jpeggray}ifelse}ifelse}ifelse dup devicedict exch kn" +"own{:gsave/imgdev exch finddevice def mark/OutputFile @imgbase imgid 20 string" +" cvs strconcat suffix strconcat/PageSize[Width Height]/UseFastColor true ispng" +"{@imgdevice(pngmonod)eq{/MinFeatureSize where{pop/MinFeatureSize MinFeatureSiz" +"e}if}if}{/JPEGQ where{pop/JPEGQ JPEGQ}if}ifelse imgdev putdeviceprops setdevic" +"e[Width 0 0 Height neg 0 Height]/setmatrix sysexec colorimg{:colorimage}{:imag" +"e}ifelse/copypage sysexec mark/OutputFile()imgdev putdeviceprops pop :grestore" +" imgid Width Height 3(image)prcmd}{pop colorimg{:colorimage}{:image}ifelse}ife" +"lse end end}def/@rect{4 -2 roll moveto exch dup 0 rlineto exch 0 exch rlineto " +"neg 0 rlineto closepath}bind def/@rectcc{4 -2 roll moveto 2 copy 0 lt exch 0 l" +"t xor{dup 0 exch rlineto exch 0 rlineto neg 0 exch rlineto}{exch dup 0 rlineto" +" exch 0 exch rlineto neg 0 rlineto}ifelse closepath}bind def @SD/rectclip{:new" +"path dup type/arraytype eq{aload length 4 idiv{@rectcc}repeat}{@rectcc}ifelse " +"clip :newpath}put @SD/rectfill{:gsave :newpath dup type/arraytype eq{aload len" +"gth 4 idiv{@rectcc}repeat}{@rectcc}ifelse fill :grestore}put @SD/rectstroke{gs" +"ave :newpath dup type/arraytype eq{aload length 4 idiv{@rect}repeat}{@rect}ife" +"lse stroke grestore}put false setglobal @SD readonly pop/initclip 0 defpr/clip" +"path 0 defpr/sysexec{@SD exch get exec}def/adddot{dup length 1 add string dup " +"0 46 put dup 3 -1 roll 1 exch putinterval}def/setlinewidth{dup/setlinewidth sy" +"sexec 1(setlinewidth)prcmd}def/setlinecap 1 defpr/setlinejoin 1 defpr/setmiter" +"limit 1 defpr/setdash{mark 3 1 roll 2 copy/setdash sysexec exch aload length 1" +" add -1 roll counttomark(setdash)prcmd pop}def/@setpagedevice{pop<<>>/setpaged" +"evice sysexec matrix setmatrix newpath 0(setpagedevice)prcmd}def/@checknulldev" +"{@GD/@nulldev get{currentpagedevice maxlength 0 ne{@GD/@nulldev false put 0 1(" +"setnulldevice)prcmd}if}if}def/prcolor{currentcolorspace @setcolorspace current" +"rgbcolor 3(setrgbcolor)prcmd}def/printgstate{@dodraw @GD/@nulldev get not and{" +"matrix currentmatrix aload pop 6(setmatrix)prcmd applyscalevals currentlinewid" +"th 1(setlinewidth)prcmd currentlinecap 1(setlinecap)prcmd currentlinejoin 1(se" +"tlinejoin)prcmd currentmiterlimit 1(setmiterlimit)prcmd prcolor currentdash ma" +"rk 3 1 roll exch aload length 1 add -1 roll counttomark(setdash)prcmd pop}if}d" +"ef/strconcat{exch dup length 2 index length add string dup dup 4 2 roll copy l" +"ength 4 -1 roll putinterval}def/setgstate{/setgstate sysexec printgstate}def/s" +"ave{@UD begin/@saveID vmstatus pop pop def end :save @saveID 1(save)prcmd}def/" +"restore{:restore @checknulldev printgstate @UD/@saveID known{@UD begin @saveID" +" end}{0}ifelse 1(restore)prcmd}def/gsave 0 defpr/grestore{:grestore @checknull" +"dev printgstate 0(grestore)prcmd}def/grestoreall{:grestoreall @checknulldev se" +"tstate 0(grestoreall)prcmd}def/rotate{dup type/arraytype ne @dodraw and{dup 1(" +"rotate)prcmd}if/rotate sysexec applyscalevals}def/scale{dup type/arraytype ne " +"@dodraw and{2 copy 2(scale)prcmd}if/scale sysexec applyscalevals}def/translate" +"{dup type/arraytype ne @dodraw and{2 copy 2(translate)prcmd}if/translate sysex" +"ec}def/setmatrix{dup/setmatrix sysexec @dodraw{aload pop 6(setmatrix)prcmd app" +"lyscalevals}{pop}ifelse}def/initmatrix{matrix setmatrix}def/concat{matrix curr" +"entmatrix matrix concatmatrix setmatrix}def/makepattern{gsave<</mx 3 -1 roll>>" +"begin<</XUID[1000000 @patcnt]>>copy mx/makepattern sysexec dup begin PatternTy" +"pe 2 lt{PatternType @patcnt BBox aload pop XStep YStep PaintType mx aload pop " +"15(makepattern)prcmd :newpath matrix setmatrix dup PaintProc 0 1(makepattern)p" +"rcmd @GD/@patcnt @patcnt 1 add put}if end end grestore}def/setpattern{begin Pa" +"tternType 1 eq{PaintType 1 eq{XUID aload pop exch pop 1}{:gsave[currentcolorsp" +"ace aload length -1 roll pop]/setcolorspace sysexec/setcolor sysexec XUID aloa" +"d pop exch pop currentrgbcolor :grestore 4}ifelse(setpattern)prcmd currentcolo" +"rspace 0 get/Pattern ne{[/Pattern currentcolorspace]/setcolorspace sysexec}if " +"currentcolorspace @setcolorspace}{/setpattern sysexec}ifelse end}def/setcolor{" +"dup type/dicttype eq{setpattern}{/setcolor sysexec/currentrgbcolor sysexec set" +"rgbcolor}ifelse}def/setcolorspace{dup/setcolorspace sysexec @setcolorspace}def" +"/@setcolorspace{dup type/arraytype eq{0 get}if/Pattern eq{1}{0}ifelse 1(setcol" +"orspace)prcmd}def/setgray 1 defpr/setcmykcolor 4 defpr/sethsbcolor 3 defpr/set" +"rgbcolor 3 defpr/.setopacityalpha{dup/.setopacityalpha sysexec 1(setopacityalp" +"ha)prcmd}def/.setshapealpha{dup/.setshapealpha sysexec 1(setshapealpha)prcmd}d" +"ef/.setblendmode{dup/.setblendmode sysexec<</Normal 0/Compatible 0/Multiply 1/" +"Screen 2/Overlay 3/SoftLight 4/HardLight 5/ColorDodge 6/ColorBurn 7/Darken 8/L" +"ighten 9/Difference 10/Exclusion 11/Hue 12/Saturation 13/Color 14/Luminosity 1" +"5/CompatibleOverprint 16>>exch get 1(setblendmode)prcmd}def/@pdfpagecount{(r)f" +"ile runpdfbegin pdfpagecount runpdfend}def/@pdfpagebox{(r)file runpdfbegin dup" +" dup 1 lt exch pdfpagecount gt or{pop}{pdfgetpage/MediaBox pget pop aload pop}" +"ifelse runpdfend}def DELAYBIND{.bindnow}if "; |