From e0c6872cf40896c7be36b11dcc744620f10adf1d Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Mon, 2 Sep 2019 13:46:59 +0900 Subject: Initial commit --- dviware/dvisvgm/src/PSInterpreter.cpp | 371 ++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 dviware/dvisvgm/src/PSInterpreter.cpp (limited to 'dviware/dvisvgm/src/PSInterpreter.cpp') diff --git a/dviware/dvisvgm/src/PSInterpreter.cpp b/dviware/dvisvgm/src/PSInterpreter.cpp new file mode 100644 index 0000000000..e9d33d7265 --- /dev/null +++ b/dviware/dvisvgm/src/PSInterpreter.cpp @@ -0,0 +1,371 @@ +/************************************************************************* +** PSInterpreter.cpp ** +** ** +** This file is part of dvisvgm -- a fast DVI to SVG converter ** +** Copyright (C) 2005-2019 Martin Gieseking ** +** ** +** 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 . ** +*************************************************************************/ + +#include +#include +#include +#include +#include +#include "FileFinder.hpp" +#include "InputReader.hpp" +#include "Message.hpp" +#include "PSFilter.hpp" +#include "PSInterpreter.hpp" +#include "SignalHandler.hpp" + +using namespace std; + + +/** Constructs a new PSInterpreter object. + * @param[in] actions template methods to be executed after recognizing the corresponding PS operator. */ +PSInterpreter::PSInterpreter (PSActions *actions) + : _mode(PS_NONE), _actions(actions) +{ +} + + +void PSInterpreter::init () { + if (!_initialized) { + vector gsargs { + "gs", // dummy name + "-q", // be quiet, suppress gs banner + "-dNODISPLAY", // we don't need a display device + "-dNOPAUSE", // keep going + "-dWRITESYSTEMDICT", // leave systemdict writable as some operators must be replaced + "-dNOPROMPT" + }; + if (int gsrev = _gs.revision()) + gsargs.emplace_back(gsrev == 922 ? "-dREALLYDELAYBIND" : "-dDELAYBIND"); + _gs.init(gsargs.size(), gsargs.data(), this); + _gs.set_stdio(input, output, error); + _initialized = true; + // Before executing any random PS code redefine some operators and run + // initializing PS code. This cannot be done in the constructor because we + // need the completely initialized PSInterpreter object here. + execute(PSDEFS); + } +} + + +PSActions* PSInterpreter::setActions (PSActions *actions) { + PSActions *old_actions = _actions; + _actions = actions; + return old_actions; +} + + +/** Checks if the given status value returned by Ghostscript indicates an error. + * @param[in] status status value returned by Ghostscript after the execution of a PS snippet + * @throw PSException if the status value indicates a PostScript error */ +void PSInterpreter::checkStatus (int status) { + if (status < 0) { + _mode = PS_QUIT; + if (status < -100) + throw PSException("fatal error"); + if (_errorMessage.empty()) + throw PSException(_gs.error_name(status)); + throw PSException(_errorMessage); + } +} + + +/** Executes a chunk of PostScript code. + * @param[in] str buffer containing the code + * @param[in] len number of characters in buffer + * @param[in] flush If true, a final 'flush' is sent which forces the output buffer to be written immediately. + * @return true if the assigned number of bytes have been read */ +bool PSInterpreter::execute (const char *str, size_t len, bool flush) { + init(); + if (_mode == PS_QUIT) + return false; + + int status=0; + if (_mode == PS_NONE) { + _gs.run_string_begin(0, &status); + _mode = PS_RUNNING; + } + checkStatus(status); + + bool complete=false; + if (_bytesToRead > 0 && len >= _bytesToRead) { + len = _bytesToRead; + complete = true; + } + + if (_filter && _filter->active()) { + PSFilter *filter = _filter; + _filter = nullptr; // prevent recursion when filter calls execute() + filter->execute(str, len); + if (filter->active()) // filter still active after execution? + _filter = filter; + return complete; + } + + // feed Ghostscript with code chunks that are not larger than 64KB + // => see documentation of gsapi_run_string_foo() + const char *p=str; + while (PS_RUNNING && len > 0) { + SignalHandler::instance().check(); + size_t chunksize = min(len, (size_t)0xffff); + _gs.run_string_continue(p, chunksize, 0, &status); + p += chunksize; + len -= chunksize; + if (_bytesToRead > 0) + _bytesToRead -= chunksize; + if (status == -101) // e_Quit + _mode = PS_QUIT; + else + checkStatus(status); + } + if (flush) { + // force writing contents of output buffer + _gs.run_string_continue("\nflush ", 7, 0, &status); + } + return complete; +} + + +/** Executes a chunk of PostScript code read from a stream. The method returns on EOF. + * @param[in] is the input stream + * @param[in] flush If true, a final 'flush' is sent which forces the output buffer to be written immediately. + * @return true if the assigned number of bytes have been read */ +bool PSInterpreter::execute (istream &is, bool flush) { + char buf[4096]; + bool finished = false; + while (is && !is.eof() && !finished) { + is.read(buf, 4096); + finished = execute(buf, is.gcount(), false); + } + execute("\n", 1, flush); + return finished; +} + + +bool PSInterpreter::executeRaw (const string &str, int n) { + _rawData.clear(); + ostringstream oss; + oss << str << ' ' << n << " (raw) prcmd\n"; + execute(oss.str()); + return !_rawData.empty(); +} + + +/** This callback function handles input from stdin to Ghostscript. Currently not needed. + * @param[in] inst pointer to calling instance of PSInterpreter + * @param[in] buf takes the read characters + * @param[in] len size of buffer buf + * @return number of characters read */ +int GSDLLCALL PSInterpreter::input (void *inst, char *buf, int len) { + return 0; +} + + +/** This callback function handles output from Ghostscript to stdout. It looks for + * emitted commands staring with "dvi." and executes them by calling method callActions. + * Ghostscript sends the text in chunks by several calls of this function. + * Unfortunately, the PostScript specification wants error messages also to be sent to stdout + * instead of stderr. Thus, we must collect and concatenate the chunks until an evaluable text + * snippet is completely received. Furthermore, error messages have to be recognized and to be + * filtered out. + * @param[in] inst pointer to calling instance of PSInterpreter + * @param[in] buf contains the characters to be output + * @param[in] len number of characters in buf + * @return number of processed characters (equals 'len') */ +int GSDLLCALL PSInterpreter::output (void *inst, const char *buf, int len) { + auto self = static_cast(inst); + if (self && self->_actions) { + const size_t MAXLEN = 512; // maximal line length (longer lines are of no interest) + const char *end = buf+len-1; // last position of buf + for (const char *first=buf, *last=buf; first <= end; last++, first=last) { + // move first and last to begin and end of the next line, respectively + while (last < end && *last != '\n') + last++; + size_t linelength = last-first+1; + if (linelength > MAXLEN) // skip long lines since they don't contain any relevant information + continue; + + vector &linebuf = self->_linebuf; // just a shorter name... + if ((*last == '\n' || !self->active()) || self->_inError) { + if (linelength + linebuf.size() > 1) { // prefix "dvi." plus final newline + SplittedCharInputBuffer ib(linebuf.empty() ? nullptr : &linebuf[0], linebuf.size(), first, linelength); + BufferInputReader in(ib); + if (self->_inError) + self->_errorMessage += string(first, linelength); + else { + in.skipSpace(); + if (in.check("Unrecoverable error: ")) { + self->_errorMessage.clear(); + while (!in.eof()) + self->_errorMessage += char(in.get()); + self->_inError = true; + } + else if (in.check("dvi.")) + self->callActions(in); + } + } + linebuf.clear(); + } + else { // no line end found => + // save remaining characters and prepend them to the next incoming chunk of characters + if (linebuf.size() + linelength > MAXLEN) + linebuf.clear(); // don't care for long lines + else { + size_t currsize = linebuf.size(); + linebuf.resize(currsize+linelength); + memcpy(&linebuf[currsize], first, linelength); + } + } + } + } + return len; +} + + +/** Evaluates a command emitted by Ghostscript and invokes the corresponding + * method of interface class PSActions. + * @param[in] in reader pointing to the next command */ +void PSInterpreter::callActions (InputReader &in) { + struct Operator { + int pcount; // number of parameters (< 0 : variable number of parameters) + void (PSActions::*handler)(vector &p); // operation handler + }; + static const unordered_map operators { + {"applyscalevals", { 3, &PSActions::applyscalevals}}, + {"clip", { 0, &PSActions::clip}}, + {"clippath", { 0, &PSActions::clippath}}, + {"closepath", { 0, &PSActions::closepath}}, + {"curveto", { 6, &PSActions::curveto}}, + {"eoclip", { 0, &PSActions::eoclip}}, + {"eofill", { 0, &PSActions::eofill}}, + {"fill", { 0, &PSActions::fill}}, + {"grestore", { 0, &PSActions::grestore}}, + {"grestoreall", { 0, &PSActions::grestoreall}}, + {"gsave", { 0, &PSActions::gsave}}, + {"initclip", { 0, &PSActions::initclip}}, + {"lineto", { 2, &PSActions::lineto}}, + {"makepattern", {-1, &PSActions::makepattern}}, + {"moveto", { 2, &PSActions::moveto}}, + {"newpath", { 1, &PSActions::newpath}}, + {"querypos", { 2, &PSActions::querypos}}, + {"raw", {-1, nullptr}}, + {"restore", { 1, &PSActions::restore}}, + {"rotate", { 1, &PSActions::rotate}}, + {"save", { 1, &PSActions::save}}, + {"scale", { 2, &PSActions::scale}}, + {"setblendmode", { 1, &PSActions::setblendmode}}, + {"setcmykcolor", { 4, &PSActions::setcmykcolor}}, + {"setdash", {-1, &PSActions::setdash}}, + {"setgray", { 1, &PSActions::setgray}}, + {"sethsbcolor", { 3, &PSActions::sethsbcolor}}, + {"setlinecap", { 1, &PSActions::setlinecap}}, + {"setlinejoin", { 1, &PSActions::setlinejoin}}, + {"setlinewidth", { 1, &PSActions::setlinewidth}}, + {"setmatrix", { 6, &PSActions::setmatrix}}, + {"setmiterlimit", { 1, &PSActions::setmiterlimit}}, + {"setopacityalpha",{ 1, &PSActions::setopacityalpha}}, + {"setshapealpha", { 1, &PSActions::setshapealpha}}, + {"setpagedevice", { 0, &PSActions::setpagedevice}}, + {"setpattern", {-1, &PSActions::setpattern}}, + {"setrgbcolor", { 3, &PSActions::setrgbcolor}}, + {"shfill", {-1, &PSActions::shfill}}, + {"stroke", { 0, &PSActions::stroke}}, + {"translate", { 2, &PSActions::translate}}, + }; + if (_actions) { + in.skipSpace(); + auto it = operators.find(in.getWord()); + if (it != operators.end()) { + if (!it->second.handler) { // raw string data received? + _rawData.clear(); + in.skipSpace(); + while (!in.eof()) { + _rawData.emplace_back(in.getString()); + in.skipSpace(); + } + } + else { + // collect parameters + vector params; + int pcount = it->second.pcount; + if (pcount < 0) { // variable number of parameters? + in.skipSpace(); + while (!in.eof()) { // read all available parameters + params.emplace_back(in.getString()); + in.skipSpace(); + } + } + else { // fix number of parameters + for (int i=0; i < pcount; i++) { + in.skipSpace(); + params.emplace_back(in.getString()); + } + } + // convert parameter strings to doubles + vector v(params.size()); + transform(params.begin(), params.end(), v.begin(), [](const string &str) { + return stod(str); + }); + // call operator handler + (_actions->*it->second.handler)(v); + _actions->executed(); + } + } + } +} + + +/** This callback function handles output from Ghostscript to stderr. + * @param[in] inst pointer to calling instance of PSInterpreter + * @param[in] buf contains the characters to be output + * @param[in] len number of chars in buf + * @return number of processed characters */ +int GSDLLCALL PSInterpreter::error (void *inst, const char *buf, int len) { + return len; +} + + +/** Returns the total number of pages of a PDF file. + * @param[in] fname name/path of the PDF file */ +int PSInterpreter::pdfPageCount (const string &fname) { + executeRaw("\n("+fname+")@pdfpagecount ", 1); + if (!_rawData.empty()) { + size_t index; + int ret = stoi(_rawData[0], &index, 10); + if (index > 0) + return ret; + } + return 0; +} + + +/** Returns the bounding box of a PDF page. If the selected page doesn't exist, + * the "invalid" flag of the returned bounding box is set. + * @param[in] fname name/path of the PDF file + * @param[in] pageno page number + * @return the bounding box of the given page */ +BoundingBox PSInterpreter::pdfPageBox (const string &fname, int pageno) { + BoundingBox pagebox; + executeRaw("\n"+to_string(pageno)+"("+fname+")@pdfpagebox ", 4); + if (_rawData.size() < 4) + pagebox.invalidate(); + else + pagebox = BoundingBox(stod(_rawData[0]), stod(_rawData[1]), stod(_rawData[2]), stod(_rawData[3])); + return pagebox; +} -- cgit v1.2.3