summaryrefslogtreecommitdiff
path: root/Build/source/texk/dvisvgm/dvisvgm-1.0.10/src/PsSpecialHandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/dvisvgm/dvisvgm-1.0.10/src/PsSpecialHandler.cpp')
-rw-r--r--Build/source/texk/dvisvgm/dvisvgm-1.0.10/src/PsSpecialHandler.cpp718
1 files changed, 718 insertions, 0 deletions
diff --git a/Build/source/texk/dvisvgm/dvisvgm-1.0.10/src/PsSpecialHandler.cpp b/Build/source/texk/dvisvgm/dvisvgm-1.0.10/src/PsSpecialHandler.cpp
new file mode 100644
index 00000000000..98dab7909a1
--- /dev/null
+++ b/Build/source/texk/dvisvgm/dvisvgm-1.0.10/src/PsSpecialHandler.cpp
@@ -0,0 +1,718 @@
+/*************************************************************************
+** PsSpecialHandler.cpp **
+** **
+** This file is part of dvisvgm -- the DVI to SVG converter **
+** Copyright (C) 2005-2011 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 <fstream>
+#include <iostream>
+#include <sstream>
+#include "FileFinder.h"
+#include "Ghostscript.h"
+#include "Message.h"
+#include "PsSpecialHandler.h"
+#include "SpecialActions.h"
+#include "XMLNode.h"
+#include "XMLString.h"
+
+using namespace std;
+
+
+static inline double str2double (const string &str) {
+ double ret;
+ istringstream iss(str);
+ iss >> ret;
+ return ret;
+}
+
+
+PsSpecialHandler::PsSpecialHandler () : _psi(this), _actions(0), _initialized(false)
+{
+}
+
+
+PsSpecialHandler::~PsSpecialHandler () {
+ // ensure no further PS actions are performed
+ _psi.setActions(0);
+}
+
+
+/** Initializes the PostScript handler. It's called by the first use of process(). The
+ * deferred initialization speeds up the conversion of DVI files that doesn't contain
+ * PS specials. */
+void PsSpecialHandler::initialize (SpecialActions *actions) {
+ if (!_initialized) {
+ // initial values of graphics state
+ _linewidth = 1;
+ _linecap = _linejoin = 0;
+ _miterlimit = 4;
+ _xmlnode = 0;
+ _opacityalpha = 1; // fully opaque
+ _sx = _sy = _cos = 1.0;
+
+ // execute dvips prologue/header files
+ const char *headers[] = {"tex.pro", "texps.pro", "special.pro", /*"color.pro",*/ 0};
+ for (const char **p=headers; *p; ++p) {
+ if (const char *path = FileFinder::lookup(*p, false)) {
+ ifstream ifs(path);
+ _psi.execute(ifs);
+ }
+ else
+ Message::wstream(true) << "PostScript header file " << *p << " not found\n";
+ }
+ // push dictionary "TeXDict" with dvips definitions on dictionary stack
+ // and initialize basic dvips PostScript variables
+ ostringstream oss;
+ oss << " TeXDict begin 0 0 1000 72 72 () @start "
+ " 0 0 moveto ";
+ if (actions) {
+ float r, g, b;
+ actions->getColor().getRGB(r, g, b);
+ oss << r << ' ' << g << ' ' << b << " setrgbcolor ";
+ }
+ _psi.execute(oss.str());
+ _initialized = true;
+ }
+}
+
+
+const char* PsSpecialHandler::info () const {
+ static string str;
+ str = "dvips PostScript specials (using " + Ghostscript().revision() + ")";
+ return str.c_str();
+}
+
+
+/** Move PS graphic position to current DVI location. */
+void PsSpecialHandler::moveToDVIPos () {
+ if (_actions) {
+ const double bp=72.0/72.27; // pt -> bp
+ const double x = _actions->getX()*bp;
+ const double y = _actions->getY()*bp;
+ ostringstream oss;
+ oss << '\n' << x << ' ' << y << " moveto ";
+ _psi.execute(oss.str());
+ _currentpoint = DPair(x, y);
+ }
+}
+
+
+/** Executes a PS snippet and moves the DVI cursor to the current DVI position afterwards.
+ * It's just a shorthand function as this action sequence is required several times.
+ * @param[in] psi PS interpreter instance
+ * @param[in] is stream to read the PS code from
+ * @param[in] pos current PS graphic position
+ * @param[in] actions special actions */
+static void exec_and_syncpos (PSInterpreter &psi, istream &is, const DPair &pos, SpecialActions *actions) {
+ psi.execute(is);
+ psi.execute("\nquerypos "); // retrieve current PS position (stored in 'pos')
+ const double pt = 72.27/72.0; // bp -> pt
+ actions->setX(pos.x()*pt);
+ actions->setY(pos.y()*pt);
+}
+
+
+bool PsSpecialHandler::process (const char *prefix, istream &is, SpecialActions *actions) {
+ if (!_initialized)
+ initialize(actions);
+ _actions = actions;
+
+ if (*prefix == '"') {
+ // read and execute literal PostScript code (isolated by a wrapping save/restore pair)
+ moveToDVIPos();
+ _psi.execute("\n@beginspecial @setspecial ");
+ _psi.execute(is);
+ _psi.execute("\n@endspecial ");
+ }
+ else if (*prefix == '!') {
+ // execute literal PostScript header
+ _psi.execute("\nTeXDict begin @defspecial ");
+ _psi.execute(is);
+ _psi.execute("\n@fedspecial end ");
+ }
+ else if (strcmp(prefix, "header=") == 0) {
+ // read and execute PS header files
+ string fname;
+ is >> fname;
+ if (const char *path = FileFinder::lookup(fname, false)) {
+ ifstream ifs(path);
+ _psi.execute(ifs);
+ }
+ else
+ Message::estream(true) << "PS header file '" << fname << "' not found";
+ }
+ else if (strcmp(prefix, "psfile=") == 0 || strcmp(prefix, "PSfile=") == 0) {
+ if (_actions) {
+ StreamInputReader in(is);
+ string fname = in.getString(in.peek() == '"' ? '"' : 0);
+ map<string,string> attr;
+ in.parseAttributes(attr);
+ psfile(fname, attr);
+ }
+ }
+ else if (strcmp(prefix, "ps::") == 0) {
+ _actions->finishLine(); // reset DVI position on next DVI command
+ if (is.peek() == '[') {
+ // collect characters inside the brackets
+ string code;
+ for (int i=0; i < 9 && is.peek() != ']' && !is.eof(); ++i)
+ code += is.get();
+ if (is.peek() == ']')
+ code += is.get();
+
+ if (code == "[begin]" || code == "[nobreak]") {
+ moveToDVIPos();
+ exec_and_syncpos(_psi, is, _currentpoint, _actions);
+ }
+ else {
+ // no move to DVI position here
+ if (code != "[end]") // PS array?
+ _psi.execute(code);
+ exec_and_syncpos(_psi, is, _currentpoint, _actions);
+ }
+ }
+ else { // ps::<code> behaves like ps::[end]<code>
+ // no move to DVI position here
+ exec_and_syncpos(_psi, is, _currentpoint, _actions);
+ }
+ }
+ else { // ps: ...
+ _actions->finishLine();
+ moveToDVIPos();
+ StreamInputReader in(is);
+ if (in.check(" plotfile ")) { // ps: plotfile fname
+ string fname = in.getString();
+ ifstream ifs(fname.c_str());
+ if (ifs)
+ _psi.execute(ifs);
+ else
+ Message::wstream(true) << "file '" << fname << "' not found in ps: plotfile\n";
+ }
+ else {
+ // ps:<code> is almost identical to ps::[begin]<code> but does
+ // a final repositioning to the current DVI location
+ exec_and_syncpos(_psi, is, _currentpoint, _actions);
+ moveToDVIPos();
+ }
+ }
+ return true;
+}
+
+
+/** Handles psfile special.
+ * @param[in] fname EPS file to be included
+ * @param[in] attr attributes given with \special psfile */
+void PsSpecialHandler::psfile (const string &fname, const map<string,string> &attr) {
+ ifstream ifs(fname.c_str());
+ if (!ifs)
+ Message::wstream(true) << "file '" << fname << "' not found in special 'psfile'\n";
+ else {
+ map<string,string>::const_iterator it;
+ const double pt = 72.27/72.0; // bp -> pt
+
+ // bounding box of EPS figure
+ double llx = (it = attr.find("llx")) != attr.end() ? str2double(it->second)*pt : 0;
+ double lly = (it = attr.find("lly")) != attr.end() ? str2double(it->second)*pt : 0;
+ double urx = (it = attr.find("urx")) != attr.end() ? str2double(it->second)*pt : 0;
+ double ury = (it = attr.find("ury")) != attr.end() ? str2double(it->second)*pt : 0;
+
+ // desired width/height of resulting figure
+ double rwi = (it = attr.find("rwi")) != attr.end() ? str2double(it->second)/10.0*pt : -1;
+ double rhi = (it = attr.find("rhi")) != attr.end() ? str2double(it->second)/10.0*pt : -1;
+ if (rwi == 0 || rhi == 0 || urx-llx == 0 || ury-lly == 0)
+ return;
+
+ // user transformations (default values chosen according to dvips manual)
+ double hoffset = (it = attr.find("hoffset")) != attr.end() ? str2double(it->second)*pt : 0;
+ double voffset = (it = attr.find("voffset")) != attr.end() ? str2double(it->second)*pt : 0;
+// double hsize = (it = attr.find("hsize")) != attr.end() ? str2double(it->second) : 612;
+// double vsize = (it = attr.find("vsize")) != attr.end() ? str2double(it->second) : 792;
+ double hscale = (it = attr.find("hscale")) != attr.end() ? str2double(it->second) : 100;
+ double vscale = (it = attr.find("vscale")) != attr.end() ? str2double(it->second) : 100;
+ double angle = (it = attr.find("angle")) != attr.end() ? str2double(it->second) : 0;
+
+ Matrix m(1);
+ m.rotate(angle).scale(hscale/100, vscale/100).translate(hoffset, voffset);
+ BoundingBox bbox(llx, lly, urx, ury);
+ bbox.transform(m);
+
+ double sx = rwi/bbox.width();
+ double sy = rhi/bbox.height();
+ if (sx < 0) sx = sy;
+ if (sy < 0) sy = sx;
+ if (sx < 0) sx = sy = 1.0;
+
+ // save current DVI position (in pt units)
+ const double x = _actions->getX();
+ const double y = _actions->getY();
+
+ // all following drawings are relative to (0,0)
+ _actions->setX(0);
+ _actions->setY(0);
+ moveToDVIPos();
+
+ _xmlnode = new XMLElementNode("g");
+ _psi.execute("\n@beginspecial @setspecial "); // enter \special environment
+ _psi.execute(ifs); // process EPS file
+ _psi.execute("\n@endspecial "); // leave special environment
+ if (!_xmlnode->empty()) { // has anything been drawn?
+ Matrix m(1);
+ m.rotate(angle).scale(hscale/100, vscale/100).translate(hoffset, voffset);
+ m.translate(-llx, lly);
+ m.scale(sx, sy); // resize image to width "rwi" and height "rhi"
+ m.translate(x, y); // move image to current DVI position
+ _xmlnode->addAttribute("transform", m.getSVG());
+ _actions->appendToPage(_xmlnode);
+ }
+ else
+ delete _xmlnode;
+ _xmlnode = 0;
+
+ // restore DVI position
+ _actions->setX(x);
+ _actions->setY(y);
+ moveToDVIPos();
+
+ // update bounding box
+ m.scale(sx, -sy);
+ m.translate(x, y);
+ bbox = BoundingBox(0, 0, fabs(urx-llx), fabs(ury-lly));
+ bbox.transform(m);
+ _actions->embed(bbox);
+ }
+}
+
+///////////////////////////////////////////////////////
+
+void PsSpecialHandler::gsave (vector<double> &p) {
+ _clipStack.dup();
+}
+
+
+void PsSpecialHandler::grestore (vector<double> &p) {
+ _clipStack.pop();
+}
+
+
+void PsSpecialHandler::grestoreall (vector<double> &p) {
+ _clipStack.pop(-1, true);
+}
+
+
+void PsSpecialHandler::save (vector<double> &p) {
+ _clipStack.dup(static_cast<int>(p[0]));
+}
+
+
+void PsSpecialHandler::restore (vector<double> &p) {
+ _clipStack.pop(static_cast<int>(p[0]));
+}
+
+
+void PsSpecialHandler::moveto (vector<double> &p) {
+ _path.moveto(p[0], p[1]);
+}
+
+
+void PsSpecialHandler::lineto (vector<double> &p) {
+ _path.lineto(p[0], p[1]);
+}
+
+
+void PsSpecialHandler::curveto (vector<double> &p) {
+ _path.cubicto(p[0], p[1], p[2], p[3], p[4], p[5]);
+}
+
+
+void PsSpecialHandler::closepath (vector<double> &p) {
+ _path.closepath();
+}
+
+
+/** Draws the current path recorded by previously executed path commands (moveto, lineto,...).
+ * @param[in] p not used */
+void PsSpecialHandler::stroke (vector<double> &p) {
+ if (!_path.empty() && _actions) {
+ BoundingBox bbox;
+ if (!_actions->getMatrix().isIdentity()) {
+ _path.transform(_actions->getMatrix());
+ if (!_xmlnode)
+ bbox.transform(_actions->getMatrix());
+ }
+
+ const double pt = 72.27/72.0; // factor to convert bp -> pt
+ ScalingMatrix scale(pt, pt);
+ _path.transform(scale);
+ bbox.transform(scale);
+
+ XMLElementNode *path=0;
+ Pair<double> point;
+ if (_path.isDot(point)) { // zero-length path?
+ if (_linecap == 1) { // round line ends? => draw dot
+ double x = point.x();
+ double y = point.y();
+ double r = _linewidth/2.0;
+ path = new XMLElementNode("circle");
+ path->addAttribute("cx", XMLString(x));
+ path->addAttribute("cy", XMLString(y));
+ path->addAttribute("r", XMLString(r));
+ path->addAttribute("fill", _actions->getColor().rgbString());
+ bbox = BoundingBox(x-r, y-r, x+r, y+r);
+ }
+ }
+ else {
+ // compute bounding box
+ _path.computeBBox(bbox);
+ bbox.expand(_linewidth/2);
+
+ ostringstream oss;
+ _path.writeSVG(oss);
+ path = new XMLElementNode("path");
+ path->addAttribute("d", oss.str());
+ path->addAttribute("stroke", _actions->getColor().rgbString());
+ path->addAttribute("fill", "none");
+ if (_linewidth != 1)
+ path->addAttribute("stroke-width", XMLString(_linewidth));
+ if (_miterlimit != 4)
+ path->addAttribute("stroke-miterlimit", XMLString(_miterlimit));
+ if (_linecap > 0) // default value is "butt", no need to set it explicitely
+ path->addAttribute("stroke-linecap", XMLString(_linecap == 1 ? "round" : "square"));
+ if (_linejoin > 0) // default value is "miter", no need to set it explicitely
+ path->addAttribute("stroke-linejoin", XMLString(_linecap == 1 ? "round" : "bevel"));
+ if (_opacityalpha < 1)
+ path->addAttribute("stroke-opacity", XMLString(_opacityalpha));
+ if (_dashpattern.size() > 0) {
+ ostringstream oss;
+ for (size_t i=0; i < _dashpattern.size(); i++) {
+ if (i > 0)
+ oss << ',';
+ oss << _dashpattern[i];
+ }
+ path->addAttribute("stroke-dasharray", oss.str());
+ if (_dashoffset != 0)
+ path->addAttribute("stroke-dashoffset", _dashoffset);
+ }
+ }
+ if (path && _clipStack.top()) {
+ // assign clipping path and clip bounding box
+ path->addAttribute("clip-path", XMLString("url(#clip")+XMLString(_clipStack.topID())+XMLString(")"));
+ BoundingBox clipbox;
+ _clipStack.top()->computeBBox(clipbox);
+ bbox.intersect(clipbox);
+ }
+
+ if (_xmlnode)
+ _xmlnode->append(path);
+ else {
+ _actions->appendToPage(path);
+ _actions->embed(bbox);
+ }
+ _path.newpath();
+ }
+}
+
+
+/** Draws a closed path filled with the current color.
+ * @param[in] p not used
+ * @param[in] evenodd true: use even-odd fill algorithm, false: use nonzero fill algorithm */
+void PsSpecialHandler::fill (vector<double> &p, bool evenodd) {
+ if (!_path.empty() && _actions) {
+ // compute bounding box
+ BoundingBox bbox;
+ _path.computeBBox(bbox);
+ if (!_actions->getMatrix().isIdentity()) {
+ _path.transform(_actions->getMatrix());
+ if (!_xmlnode)
+ bbox.transform(_actions->getMatrix());
+ }
+
+ const double pt = 72.27/72.0; // factor to convert bp -> pt
+ ScalingMatrix scale(pt, pt);
+ _path.transform(scale);
+ bbox.transform(scale);
+
+ ostringstream oss;
+ _path.writeSVG(oss);
+ XMLElementNode *path = new XMLElementNode("path");
+ path->addAttribute("d", oss.str());
+ if (_actions->getColor() != Color::BLACK)
+ path->addAttribute("fill", _actions->getColor().rgbString());
+ if (_clipStack.top()) {
+ // assign clipping path and clip bounding box
+ path->addAttribute("clip-path", XMLString("url(#clip")+XMLString(_clipStack.topID())+XMLString(")"));
+ BoundingBox clipbox;
+ _clipStack.top()->computeBBox(clipbox);
+ bbox.intersect(clipbox);
+ }
+ if (evenodd) // SVG default fill rule is "nonzero" algorithm
+ path->addAttribute("fill-rule", "evenodd");
+ if (_opacityalpha < 1)
+ path->addAttribute("fill-opacity", XMLString(_opacityalpha));
+ if (_xmlnode)
+ _xmlnode->append(path);
+ else {
+ _actions->appendToPage(path);
+ _actions->embed(bbox);
+ }
+ _path.newpath();
+ }
+}
+
+
+/** Clears the current clipping path.
+ * @param[in] p not used */
+void PsSpecialHandler::initclip (vector<double> &p) {
+ _clipStack.push(); // push empty path
+}
+
+
+/** Assigns a new clipping path.
+ * @param[in] p not used
+ * @param[in] evenodd true: use even-odd fill algorithm, false: use nonzero fill algorithm */
+void PsSpecialHandler::clip (vector<double> &p, bool evenodd) {
+ // when this method is called, _path contains the clipping path
+ if (!_path.empty() && _actions) {
+ if (!_actions->getMatrix().isIdentity())
+ _path.transform(_actions->getMatrix());
+
+ const double pt = 72.27/72.0; // factor to convert bp -> pt
+ ScalingMatrix scale(pt, pt);
+ _path.transform(scale);
+
+
+ int oldID = _clipStack.topID();
+ _clipStack.replace(_path);
+ int newID = _clipStack.topID();
+
+ ostringstream oss;
+ _path.writeSVG(oss);
+ XMLElementNode *path = new XMLElementNode("path");
+ path->addAttribute("d", oss.str());
+ if (evenodd)
+ path->addAttribute("clip-rule", "evenodd");
+
+ XMLElementNode *clip = new XMLElementNode("clipPath");
+ clip->addAttribute("id", XMLString("clip")+XMLString(newID));
+ if (oldID)
+ clip->addAttribute("clip-path", XMLString("url(#clip")+XMLString(oldID)+XMLString(")"));
+
+ clip->append(path);
+ _actions->appendToDefs(clip);
+ }
+}
+
+
+/** Clears current path */
+void PsSpecialHandler::newpath (vector<double> &p) {
+ _path.newpath();
+}
+
+
+void PsSpecialHandler::setmatrix (vector<double> &p) {
+ if (_actions) {
+ // Ensure vector p has 6 elements. If necessary, add missing ones
+ // using corresponding values of the identity matrix.
+ if (p.size() < 6) {
+ p.resize(6);
+ for (int i=p.size(); i < 6; i++)
+ p[i] = (i%3 ? 0 : 1);
+ }
+ // PS matrix [a b c d e f] equals ((a,b,0),(c,d,0),(e,f,1)).
+ // Since PS uses left multiplications, we must transpose and reorder
+ // the matrix to ((a,c,e),(b,d,f),(0,0,1)). This is done by the
+ // following swaps.
+ swap(p[1], p[2]); // => (a, c, b, d, e, f)
+ swap(p[2], p[4]); // => (a, c, e, d, b, f)
+ swap(p[3], p[4]); // => (a, c, e, b, d, f)
+ Matrix m(p);
+ _actions->setMatrix(m);
+ }
+}
+
+
+// In contrast to SVG, PostScript transformations are applied in
+// reverse order (M' = T*M). Thus, the transformation matrices must be
+// left-multiplied in the following methods scale(), translate() and rotate().
+
+
+void PsSpecialHandler::scale (vector<double> &p) {
+ if (_actions) {
+ Matrix m = _actions->getMatrix();
+ ScalingMatrix s(p[0], p[1]);
+ m.lmultiply(s);
+ _actions->setMatrix(m);
+ }
+}
+
+
+void PsSpecialHandler::translate (vector<double> &p) {
+ if (_actions) {
+ Matrix m = _actions->getMatrix();
+ TranslationMatrix t(p[0], p[1]);
+ m.lmultiply(t);
+ _actions->setMatrix(m);
+ }
+}
+
+
+void PsSpecialHandler::rotate (vector<double> &p) {
+ if (_actions) {
+ Matrix m = _actions->getMatrix();
+ RotationMatrix r(p[0]);
+ m.lmultiply(r);
+ _actions->setMatrix(m);
+ }
+}
+
+void PsSpecialHandler::setgray (vector<double> &p) {
+ if (_actions) {
+ Color c;
+ c.setGray((float)p[0]);
+ _actions->setColor(c);
+ }
+}
+
+
+void PsSpecialHandler::setrgbcolor (vector<double> &p) {
+ if (_actions)
+ _actions->setColor(Color((float)p[0], (float)p[1], (float)p[2]));
+}
+
+
+void PsSpecialHandler::setcmykcolor (vector<double> &p) {
+ if (_actions) {
+ Color c;
+ c.setCMYK((float)p[0], (float)p[1], (float)p[2], (float)p[3]);
+ _actions->setColor(c);
+ }
+}
+
+
+void PsSpecialHandler::sethsbcolor (vector<double> &p) {
+ if (_actions) {
+ Color c;
+ c.setHSB((float)p[0], (float)p[1], (float)p[2]);
+ _actions->setColor(c);
+ }
+}
+
+
+/** Sets the dash parameters used for stroking.
+ * @param[in] p dash pattern array m1,...,mn plus trailing dash offset */
+void PsSpecialHandler::setdash (vector<double> &p) {
+ _dashpattern.clear();
+ for (size_t i=0; i < p.size()-1; i++)
+ _dashpattern.push_back(scale(p[i]));
+ _dashoffset = scale(p.back());
+}
+
+
+/** This method is called by the PSInterpreter if an PS operator has been executed. */
+void PsSpecialHandler::executed () {
+ if (_actions)
+ _actions->progress("ps");
+}
+
+////////////////////////////////////////////
+
+void PsSpecialHandler::ClippingStack::push () {
+ if (!_stack.empty())
+ _stack.push(Entry(0, -1));
+}
+
+
+void PsSpecialHandler::ClippingStack::push (const Path &path, int saveID) {
+ if (path.empty())
+ _stack.push(Entry(0, saveID));
+ else {
+ _paths.push_back(path);
+ _stack.push(Entry(_paths.size(), saveID));
+ }
+}
+
+
+/** Pops a single or several elements from the clipping stack.
+ * The method distingushes between the following cases:
+ * 1) saveID < 0 and grestoreall == false:
+ * pop top element if it was pushed by gsave (its saveID is < 0 as well)
+ * 2) saveID < 0 and grestoreall == true
+ * repeat popping until stack is empty or the top element was pushed
+ * by save (its saveID is >= 0)
+ * 3) saveID >= 0:
+ * pop all elements until the saveID of the top element equals parameter saveID */
+void PsSpecialHandler::ClippingStack::pop (int saveID, bool grestoreall) {
+ if (!_stack.empty()) {
+ if (saveID < 0) { // grestore?
+ if (_stack.top().saveID < 0) // pushed by 'gsave'?
+ _stack.pop();
+ // pop all further elements pushed by 'gsave' if grestoreall == true
+ while (grestoreall && !_stack.empty() && _stack.top().saveID < 0)
+ _stack.pop();
+ }
+ else {
+ // pop elements pushed by 'gsave'
+ while (!_stack.empty() && _stack.top().saveID != saveID)
+ _stack.pop();
+ // pop element pushed by 'save'
+ if (!_stack.empty())
+ _stack.pop();
+ }
+ }
+}
+
+
+/** Returns a pointer to the path on top of the stack, or 0 if the stack is empty. */
+PsSpecialHandler::Path* PsSpecialHandler::ClippingStack::top () {
+ return (!_stack.empty() && _stack.top().pathID)
+ ? &_paths[_stack.top().pathID-1]
+ : 0;
+}
+
+
+/** Pops all elements from the stack. */
+void PsSpecialHandler::ClippingStack::clear() {
+ _paths.clear();
+ while (!_stack.empty())
+ _stack.pop();
+}
+
+
+/** Replaces the top element by a new one.
+ * @param[in] path new path to be on top of the stack */
+void PsSpecialHandler::ClippingStack::replace (const Path &path) {
+ if (_stack.empty())
+ push(path, -1);
+ else {
+ _paths.push_back(path);
+ _stack.top().pathID = _paths.size();
+ }
+}
+
+
+/** Duplicates the top element, i.e. the top element is pushed again. */
+void PsSpecialHandler::ClippingStack::dup (int saveID) {
+ _stack.push(_stack.empty() ? Entry(0, -1) : _stack.top());
+ _stack.top().saveID = saveID;
+}
+
+
+const char** PsSpecialHandler::prefixes () const {
+ static const char *pfx[] = {"header=", "psfile=", "PSfile=", "ps:", "ps::", "!", "\"", 0};
+ return pfx;
+}
+