summaryrefslogtreecommitdiff
path: root/Build/source/texk/dvisvgm/dvisvgm-src/src/PsSpecialHandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/dvisvgm/dvisvgm-src/src/PsSpecialHandler.cpp')
-rw-r--r--Build/source/texk/dvisvgm/dvisvgm-src/src/PsSpecialHandler.cpp1436
1 files changed, 0 insertions, 1436 deletions
diff --git a/Build/source/texk/dvisvgm/dvisvgm-src/src/PsSpecialHandler.cpp b/Build/source/texk/dvisvgm/dvisvgm-src/src/PsSpecialHandler.cpp
deleted file mode 100644
index fbb43f38fbe..00000000000
--- a/Build/source/texk/dvisvgm/dvisvgm-src/src/PsSpecialHandler.cpp
+++ /dev/null
@@ -1,1436 +0,0 @@
-/*************************************************************************
-** PsSpecialHandler.cpp **
-** **
-** This file is part of dvisvgm -- a fast DVI to SVG converter **
-** Copyright (C) 2005-2021 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 <array>
-#include <cmath>
-#include <fstream>
-#include <memory>
-#include <sstream>
-#include "FileFinder.hpp"
-#include "FilePath.hpp"
-#include "FileSystem.hpp"
-#include "Message.hpp"
-#include "PathClipper.hpp"
-#include "PSPattern.hpp"
-#include "PSPreviewFilter.hpp"
-#include "PsSpecialHandler.hpp"
-#include "SpecialActions.hpp"
-#include "SVGTree.hpp"
-#include "TensorProductPatch.hpp"
-#include "TriangularPatch.hpp"
-#include "utility.hpp"
-
-using namespace std;
-
-
-bool PsSpecialHandler::COMPUTE_CLIPPATHS_INTERSECTIONS = false;
-bool PsSpecialHandler::SHADING_SEGMENT_OVERLAP = false;
-int PsSpecialHandler::SHADING_SEGMENT_SIZE = 20;
-double PsSpecialHandler::SHADING_SIMPLIFY_DELTA = 0.01;
-string PsSpecialHandler::BITMAP_FORMAT;
-
-
-PsSpecialHandler::PsSpecialHandler () : _psi(this), _previewFilter(_psi)
-{
- _psi.setImageDevice(BITMAP_FORMAT);
-}
-
-
-PsSpecialHandler::~PsSpecialHandler () {
- _psi.setActions(nullptr); // ensure no further PS actions are performed
-}
-
-
-/** 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 () {
- if (_psSection == PS_NONE) {
- initgraphics();
- // execute dvips prologue/header files
- for (const char *fname : {"tex.pro", "texps.pro", "special.pro", "color.pro"})
- processHeaderFile(fname);
- // disable bop/eop operators to prevent side-effects by
- // unexpected bops/eops present in PS specials
- _psi.execute("\nTeXDict begin /bop{pop pop}def /eop{}def end ");
- _psSection = PS_HEADERS; // allow to process header specials now
- }
-}
-
-
-/** Set initial values of PS graphics state (see PS language reference (Red Book)), p. 612). */
-void PsSpecialHandler::initgraphics () {
- _linewidth = 1;
- _linecap = _linejoin = 0; // butt end caps and miter joins
- _miterlimit = 4;
- _xmlnode = _savenode = nullptr;
- _isshapealpha = false; // opacity operators change constant component by default
- _fillalpha = _strokealpha = {1, 1}; // set constant and shape opacity to non-transparent
- _blendmode = 0; // "normal" mode (no blending)
- _sx = _sy = _cos = 1.0;
- _pattern = nullptr;
- _patternEnabled = false;
- _currentcolor = Color::BLACK;
- _dashoffset = 0;
- _dashpattern.clear();
- _path.clear();
- _clipStack.clear();
-}
-
-
-void PsSpecialHandler::processHeaderFile (const char *name) {
- if (const char *path = FileFinder::instance().lookup(name, false)) {
- ifstream ifs(path);
- _psi.execute(string("%%BeginProcSet: ")+name+" 0 0\n", false);
- _psi.execute(ifs, false);
- _psi.execute("%%EndProcSet\n", false);
- }
- else
- Message::wstream(true) << "PostScript header file " << name << " not found\n";
-}
-
-
-void PsSpecialHandler::enterBodySection () {
- if (_psSection == PS_HEADERS) {
- _psSection = PS_BODY; // don't process any PS header code
- ostringstream oss;
- // process collected header code
- if (!_headerCode.empty()) {
- oss << "\nTeXDict begin @defspecial " << _headerCode << "\n@fedspecial end";
- _headerCode.clear();
- }
- // push dictionary "TeXDict" with dvips definitions on dictionary stack
- // and initialize basic dvips PostScript variables
- oss << "\nTeXDict begin 0 0 1000 72 72 () @start 0 0 moveto ";
- _psi.execute(oss.str(), false);
- // Check for information generated by preview.sty. If the tightpage options
- // was set, don't execute the bop-hook but allow the PS filter to read
- // the bbox data present at the beginning of the page.
- _psi.setFilter(&_previewFilter);
- _previewFilter.activate();
- if (!_previewFilter.tightpage())
- _psi.execute("userdict/bop-hook known{bop-hook}if\n", false);
- }
-}
-
-
-/** Move PS graphic position to current DVI location. */
-void PsSpecialHandler::moveToDVIPos () {
- if (_actions) {
- const double x = _actions->getX();
- const double y = _actions->getY();
- ostringstream oss;
- oss << '\n' << x << ' ' << y << " moveto ";
- _psi.execute(oss.str());
- _currentpoint = DPair(x, y);
- }
-}
-
-
-/** Executes a PS snippet and optionally synchronizes the DVI cursor position
- * with the current PS point.
- * @param[in] is stream to read the PS code from
- * @param[in] updatePos if true, move the DVI drawing position to the current PS point */
-void PsSpecialHandler::executeAndSync (istream &is, bool updatePos) {
- if (_actions && _actions->getColor() != _currentcolor) {
- // update the PS graphics state if the color has been changed by a color special
- double r, g, b;
- _actions->getColor().getRGB(r, g, b);
- ostringstream oss;
- oss << '\n' << r << ' ' << g << ' ' << b << " setrgbcolor ";
- _psi.execute(oss.str(), false);
- }
- _psi.execute(is);
- if (updatePos) {
- // retrieve current PS position (stored in _currentpoint)
- _psi.execute("\nquerypos ");
- if (_actions) {
- _actions->setX(_currentpoint.x());
- _actions->setY(_currentpoint.y());
- }
- }
-}
-
-
-void PsSpecialHandler::preprocess (const string &prefix, istream &is, SpecialActions &actions) {
- initialize();
- if (_psSection != PS_HEADERS)
- return;
-
- _actions = &actions;
- if (prefix == "!") {
- _headerCode += "\n";
- _headerCode += string(istreambuf_iterator<char>(is), istreambuf_iterator<char>());
- }
- else if (prefix == "header=") {
- // read and execute PS header file
- string fname;
- is >> fname;
- processHeaderFile(fname.c_str());
- }
-}
-
-
-static string filename_suffix (const string &fname) {
- string ret;
- size_t pos = fname.rfind('.');
- if (pos != string::npos)
- ret = util::tolower(fname.substr(pos+1));
- return ret;
-}
-
-
-bool PsSpecialHandler::process (const string &prefix, istream &is, SpecialActions &actions) {
- // process PS headers only once (in prescan)
- if (prefix == "!" || prefix == "header=")
- return true;
-
- _actions = &actions;
- initialize();
- if (_psSection != PS_BODY)
- enterBodySection();
-
- if (prefix == "\"" || prefix == "pst:") {
- // read and execute literal PostScript code (isolated by a wrapping save/restore pair)
- moveToDVIPos();
- _psi.execute("\n@beginspecial @setspecial ");
- executeAndSync(is, false);
- _psi.execute("\n@endspecial ");
- }
- else if (prefix == "psfile=" || prefix == "PSfile=" || prefix == "pdffile=") {
- if (_actions) {
- StreamInputReader in(is);
- string fname = in.getQuotedString(in.peek() == '"' ? "\"" : nullptr);
- fname = FileSystem::ensureForwardSlashes(fname);
- FileType fileType = FileType::EPS;
- if (prefix == "pdffile")
- fileType = FileType::PDF;
- else {
- // accept selected non-PS files in psfile special
- string ext = filename_suffix(fname);
- if (ext == "pdf")
- fileType = FileType::PDF;
- else if (ext == "svg")
- fileType = FileType::SVG;
- else if (ext == "jpg" || ext == "jpeg" || ext == "png")
- fileType = FileType::BITMAP;
- }
- map<string,string> attr;
- in.parseAttributes(attr, false);
- imgfile(fileType, fname, attr);
- }
- }
- else if (prefix == "ps::") {
- if (_actions)
- _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 += char(is.get());
- if (is.peek() == ']')
- code += char(is.get());
-
- if (code == "[begin]" || code == "[nobreak]") {
- moveToDVIPos();
- executeAndSync(is, true);
- }
- else {
- // no move to DVI position here
- if (code != "[end]") // PS array?
- _psi.execute(code);
- executeAndSync(is, true);
- }
- }
- else { // ps::<code> behaves like ps::[end]<code>
- // no move to DVI position here
- executeAndSync(is, true);
- }
- }
- else { // ps: ... or PST: ...
- if (_actions)
- _actions->finishLine();
- moveToDVIPos();
- StreamInputReader in(is);
- if (in.check(" plotfile ")) { // ps: plotfile fname
- string fname = in.getString();
- ifstream ifs(fname);
- 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
- executeAndSync(is, true);
- moveToDVIPos();
- }
- }
- return true;
-}
-
-
-/** Handles a psfile/pdffile special which places an external EPS/PDF graphic
- * at the current DVI position. The lower left corner (llx,lly) of the
- * given bounding box is placed at the DVI position.
- * @param[in] filetype type of file to process (EPS or PDF)
- * @param[in] fname EPS/PDF file to be included
- * @param[in] attr attributes given with psfile/pdffile special */
-void PsSpecialHandler::imgfile (FileType filetype, const string &fname, const map<string,string> &attr) {
- // prevent warning about missing image file "/dev/null" which is
- // added by option "psfixbb" of the preview package
- if (fname == "/dev/null")
- return;
-
- map<string,string>::const_iterator it;
-
- // bounding box of EPS figure in PS point units (lower left and upper right corner)
- double llx = (it = attr.find("llx")) != attr.end() ? stod(it->second) : 0;
- double lly = (it = attr.find("lly")) != attr.end() ? stod(it->second) : 0;
- double urx = (it = attr.find("urx")) != attr.end() ? stod(it->second) : 0;
- double ury = (it = attr.find("ury")) != attr.end() ? stod(it->second) : 0;
- int pageno = (it = attr.find("page")) != attr.end() ? stoi(it->second, nullptr, 10) : 1;
-
- if (filetype == FileType::BITMAP || filetype == FileType::SVG)
- swap(lly, ury);
- else if (filetype == FileType::PDF && llx == 0 && lly == 0 && urx == 0 && ury == 0) {
- BoundingBox pagebox = _psi.pdfPageBox(fname, pageno);
- if (pagebox.valid()) {
- llx = pagebox.minX();
- lly = pagebox.minY();
- urx = pagebox.maxX();
- ury = pagebox.maxY();
- }
- }
-
- // desired width and height of the untransformed figure in PS point units
- double rwi = (it = attr.find("rwi")) != attr.end() ? stod(it->second)/10.0 : -1;
- double rhi = (it = attr.find("rhi")) != attr.end() ? stod(it->second)/10.0 : -1;
- if (rwi == 0 || rhi == 0 || urx-llx == 0 || ury-lly == 0)
- return;
-
- // user transformations (default values chosen according to dvips manual)
- // order of transformations: rotate, scale, translate/offset
- double hoffset = (it = attr.find("hoffset")) != attr.end() ? stod(it->second) : 0;
- double voffset = (it = attr.find("voffset")) != attr.end() ? stod(it->second) : 0;
-// double hsize = (it = attr.find("hsize")) != attr.end() ? stod(it->second) : 612;
-// double vsize = (it = attr.find("vsize")) != attr.end() ? stod(it->second) : 792;
- double hscale = (it = attr.find("hscale")) != attr.end() ? stod(it->second) : 100;
- double vscale = (it = attr.find("vscale")) != attr.end() ? stod(it->second) : 100;
- double angle = (it = attr.find("angle")) != attr.end() ? stod(it->second) : 0;
-
- bool clipToBbox = (attr.find("clip") != attr.end());
-
- // compute factors to scale the bounding box to width rwi and height rhi
- double sx = rwi/abs(llx-urx);
- double sy = rhi/abs(lly-ury);
- if (sx == 0 || sy == 0)
- return;
-
- if (sx < 0) sx = sy; // rwi attribute not set
- if (sy < 0) sy = sx; // rhi attribute not set
- if (sx < 0) sx = sy = 1.0; // neither rwi nor rhi set
-
- // save current DVI position
- double x = _actions->getX();
- double y = _actions->getY();
-
- // all following drawings are relative to (0,0)
- _actions->setX(0);
- _actions->setY(0);
- moveToDVIPos();
-
- auto imgNode = createImageNode(filetype, fname, pageno, BoundingBox(llx, lly, urx, ury), clipToBbox);
- if (imgNode) { // has anything been drawn?
- Matrix matrix(1);
- if (filetype == FileType::EPS || filetype == FileType::PDF)
- sy = -sy; // adapt orientation of y-coordinates
- matrix.scale(sx, sy).rotate(-angle).scale(hscale/100, vscale/100); // apply transformation attributes
- matrix.translate(x+hoffset, y-voffset); // move image to current DVI position
- matrix.lmultiply(_actions->getMatrix());
-
- // update bounding box
- BoundingBox bbox(0, 0, urx-llx, ury-lly);
- bbox.transform(matrix);
- _actions->embed(bbox);
-
- // insert element containing the image data
- matrix.rmultiply(TranslationMatrix(-llx, -lly)); // move lower left corner of image to origin
- if (!matrix.isIdentity())
- imgNode->addAttribute("transform", matrix.toSVG());
- _actions->svgTree().appendToPage(std::move(imgNode));
- }
- // restore DVI position
- _actions->setX(x);
- _actions->setY(y);
- moveToDVIPos();
-}
-
-
-/** Returns path + basename of temporary bitmap images. */
-static string image_base_path (SpecialActions &actions) {
- FilePath imgpath = actions.getSVGFilePath(actions.getCurrentPageNumber());
- return FileSystem::tmpdir() + "/" + imgpath.basename() + "-tmp-";
-}
-
-
-/** Creates an XML element containing the image data depending on the file type.
- * @param[in] type file type of the image
- * @param[in] fname file name/path of image file
- * @param[in] pageno number of page to process (PDF only)
- * @param[in] bbox bounding box of the image
- * @param[in] clip if true, the image is clipped to its bounding box
- * @return pointer to the element or nullptr if there's no image data */
-unique_ptr<XMLElement> PsSpecialHandler::createImageNode (FileType type, const string &fname, int pageno, BoundingBox bbox, bool clip) {
- unique_ptr<XMLElement> node;
- string pathstr;
- if (const char *path = FileFinder::instance().lookup(fname, false))
- pathstr = FileSystem::ensureForwardSlashes(path);
- if ((pathstr.empty() || !FileSystem::exists(pathstr)) && FileSystem::exists(fname))
- pathstr = fname;
- if (pathstr.empty())
- Message::wstream(true) << "file '" << fname << "' not found\n";
- else if (type == FileType::BITMAP || type == FileType::SVG) {
- node = util::make_unique<XMLElement>("image");
- node->addAttribute("x", 0);
- node->addAttribute("y", 0);
- node->addAttribute("width", bbox.width());
- node->addAttribute("height", bbox.height());
-
- // Only reference the image with an absolute path if either an absolute path was given by the user
- // or a given plain filename is not present in the current working directory but was found through
- // the FileFinder, i.e. it's usually located somewhere in the texmf tree.
- string href = pathstr;
- if (!FilePath::isAbsolute(fname) && (fname.find('/') != string::npos || FilePath(fname).exists()))
- href = FilePath(pathstr).relative(FilePath(_actions->getSVGFilePath(pageno)));
- node->addAttribute("xlink:href", href);
- }
- else { // PostScript or PDF
- // clip image to its bounding box if flag 'clip' is given
- string rectclip;
- if (clip)
- rectclip = to_string(bbox.minX())+" "+to_string(bbox.minY())+" "+to_string(bbox.width())+" "+to_string(bbox.height())+" rectclip";
-
- node = util::make_unique<XMLElement>("g"); // put SVG nodes created from the EPS/PDF file in this group
- _xmlnode = node.get();
- _psi.execute(
- "\n@beginspecial @setspecial" // enter special environment
- "/setpagedevice{@setpagedevice}def " // activate processing of operator "setpagedevice"
- "/@imgbase("+image_base_path(*_actions)+")store " // path and basename of image files
- "matrix setmatrix" // don't apply outer PS transformations
- "/FirstPage "+to_string(pageno)+" def" // set number of first page to convert (PDF only)
- "/LastPage "+to_string(pageno)+" def " // set number of last page to convert (PDF only)
- +rectclip+ // clip to bounding box (if requexted by attribute 'clip')
- "(" + pathstr + ")run " // execute file content
- "@endspecial\n" // leave special environment
- );
- if (node->empty())
- node.reset(nullptr);
- _xmlnode = nullptr; // append following elements to page group again
- }
- return node;
-}
-
-
-/** Apply transformation to width, height, and depth set by preview package.
- * @param[in] matrix transformation matrix to apply
- * @param[out] w width
- * @param[out] h height
- * @param[out] d depth
- * @return true if the baseline is still horizontal after the transformation */
-static bool transform_box_extents (const Matrix &matrix, double &w, double &h, double &d) {
- DPair shift = matrix*DPair(0,0); // the translation component of the matrix
- DPair ex = matrix*DPair(1,0)-shift;
- DPair ey = matrix*DPair(0,1)-shift;
- if (ex.y() != 0 && ey.x() != 0) // rotation != mod 90 degrees?
- return false; // => non-horizontal baseline, can't compute meaningful extents
-
- if (ex.y() == 0) // horizontal scaling or skewing?
- w *= abs(ex.x());
- if (ey.x()==0 || ex.y()==0) { // vertical scaling?
- if (ey.y() < 0)
- swap(h, d);
- double sy = abs(ey.y())/ey.length();
- if (sy < 1e-8)
- h = d = 0;
- else {
- h *= abs(ey.y()/sy);
- d *= abs(ey.y()/sy);
- }
- }
- return true;
-}
-
-
-void PsSpecialHandler::dviBeginPage (unsigned int pageno, SpecialActions &actions) {
- _psi.execute("/@imgbase("+image_base_path(actions)+")store\n"); // path and basename of image files
-}
-
-
-void PsSpecialHandler::dviEndPage (unsigned, SpecialActions &actions) {
- BoundingBox bbox;
- if (_previewFilter.getBoundingBox(bbox)) { // is there any data written by preview package?
- double w=0, h=0, d=0;
- if (actions.getBBoxFormatString() == "preview" || actions.getBBoxFormatString() == "min") {
- if (actions.getBBoxFormatString() == "preview") {
- w = max(0.0, _previewFilter.width());
- h = max(0.0, _previewFilter.height());
- d = max(0.0, _previewFilter.depth());
- actions.bbox() = bbox;
- Message::mstream() << "\napplying bounding box set by";
- }
- else {
- w = actions.bbox().width();
- h = max(0.0, -actions.bbox().minY());
- d = max(0.0, actions.bbox().maxY());
- Message::mstream() << "\ncomputing extents based on data set by";
- }
- Message::mstream() << " preview package (version " << _previewFilter.version() << ")\n";
-
- // apply page transformations to box extents
- Matrix pagetrans = actions.getPageTransformation();
- bool isBaselineHorizontal = transform_box_extents(pagetrans, w, h, d);
- actions.bbox().lock();
-
- if (!isBaselineHorizontal)
- Message::mstream() << "can't determine height, width, and depth due to non-horizontal baseline\n";
- else {
- const double bp2pt = 72.27/72.0;
- Message::mstream() <<
- "width=" << XMLString(w*bp2pt) << "pt, "
- "height=" << XMLString(h*bp2pt) << "pt, "
- "depth=" << XMLString(d*bp2pt) << "pt\n";
- }
-#if 0
- auto rect = util::make_unique<XMLElement>("rect");
- rect->addAttribute("x", actions.bbox().minX());
- rect->addAttribute("y", actions.bbox().minY());
- rect->addAttribute("width", w);
- rect->addAttribute("height", h+d);
- rect->addAttribute("fill", "none");
- rect->addAttribute("stroke", "red");
- rect->addAttribute("stroke-width", "0.1");
- actions.appendToPage(std::move(rect));
- if (d > 0) {
- auto line = util::make_unique<XMLElement>("line");
- line->addAttribute("x1", actions.bbox().minX());
- line->addAttribute("y1", actions.bbox().minY()+h);
- line->addAttribute("x2", actions.bbox().maxX());
- line->addAttribute("y2", actions.bbox().minY()+h);
- line->addAttribute("stroke", "blue");
- line->addAttribute("stroke-width", "0.1");
- actions.appendToPage(std::move(line));
- }
-#endif
- }
- }
- // close dictionary TeXDict and execute end-hook if defined
- if (_psSection == PS_BODY) {
- _psi.execute("\nend userdict/end-hook known{end-hook}if initgraphics ");
- initgraphics(); // reset graphics state to default values
- _psSection = PS_HEADERS;
- }
-}
-
-///////////////////////////////////////////////////////
-
-void PsSpecialHandler::setpagedevice (std::vector<double> &p) {
- _linewidth = 1;
- _linecap = _linejoin = 0; // butt end caps and miter joins
- _miterlimit = 4;
- _isshapealpha = false; // opacity operators change constant component by default
- _fillalpha = _strokealpha = {1, 1}; // set constant and shape opacity to non-transparent
- _blendmode = 0; // "normal" mode (no blending)
- _sx = _sy = _cos = 1.0;
- _pattern = nullptr;
- _currentcolor = Color::BLACK;
- _dashoffset = 0;
- _dashpattern.clear();
- _path.clear();
-}
-
-
-void PsSpecialHandler::gsave (vector<double>&) {
- _clipStack.dup();
-}
-
-
-void PsSpecialHandler::grestore (vector<double>&) {
- _clipStack.pop();
-}
-
-
-void PsSpecialHandler::grestoreall (vector<double>&) {
- _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>&) {
- _path.closepath();
-}
-
-
-static string css_blendmode_name (int mode) {
- static const array<const char*,16> modenames = {{
- "normal", "multiply", "screen", "overlay", "soft-light", "hard-light", "color-dodge", "color-burn",
- "darken", "lighten", "difference", "exclusion", "hue", "saturation", "color", "luminosity"
- }};
- if (mode < 0 || mode > 15)
- return "";
- return modenames[mode];
-}
-
-
-/** Draws the current path recorded by previously executed path commands (moveto, lineto,...).
- * @param[in] p not used */
-void PsSpecialHandler::stroke (vector<double> &p) {
- _path.removeRedundantCommands();
- if ((_path.empty() && !_clipStack.prependedPath()) || !_actions)
- return;
-
- BoundingBox bbox;
- if (!_actions->getMatrix().isIdentity()) {
- _path.transform(_actions->getMatrix());
- if (!_xmlnode)
- bbox.transform(_actions->getMatrix());
- }
- if (_clipStack.prependedPath())
- _path.prepend(*_clipStack.prependedPath());
- unique_ptr<XMLElement> path;
- 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 = util::make_unique<XMLElement>("circle");
- path->addAttribute("cx", x);
- path->addAttribute("cy", y);
- path->addAttribute("r", r);
- path->addAttribute("fill", _actions->getColor().svgColorString());
- bbox = BoundingBox(x-r, y-r, x+r, y+r);
- }
- }
- else {
- // compute bounding box
- bbox = _path.computeBBox();
- bbox.expand(_linewidth/2);
-
- ostringstream oss;
- _path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS);
- path = util::make_unique<XMLElement>("path");
- path->addAttribute("d", oss.str());
- path->addAttribute("stroke", _actions->getColor().svgColorString());
- path->addAttribute("fill", "none");
- if (_linewidth != 1)
- path->addAttribute("stroke-width", _linewidth);
- if (_miterlimit != 4)
- path->addAttribute("stroke-miterlimit", _miterlimit);
- if (_linecap > 0) // default value is "butt", no need to set it explicitly
- path->addAttribute("stroke-linecap", _linecap == 1 ? "round" : "square");
- if (_linejoin > 0) // default value is "miter", no need to set it explicitly
- path->addAttribute("stroke-linejoin", _linecap == 1 ? "round" : "bevel");
- if (_strokealpha[0] < 1 || _strokealpha[1] < 1)
- path->addAttribute("stroke-opacity", _strokealpha[0] * _strokealpha[1]);
- if (_blendmode > 0 && _blendmode < 16)
- path->addAttribute("style", "mix-blend-mode:"+css_blendmode_name(_blendmode));
- if (!_dashpattern.empty()) {
- string patternStr;
- for (double dashValue : _dashpattern)
- patternStr += XMLString(dashValue)+",";
- patternStr.pop_back();
- path->addAttribute("stroke-dasharray", patternStr);
- if (_dashoffset != 0)
- path->addAttribute("stroke-dashoffset", _dashoffset);
- }
- }
- if (path && _clipStack.path() && !_savenode) {
- // assign clipping path and clip bounding box
- path->addAttribute("clip-path", XMLString("url(#clip")+XMLString(_clipStack.topID())+")");
- bbox.intersect(_clipStack.path()->computeBBox());
- _clipStack.removePrependedPath();
- }
- if (_xmlnode)
- _xmlnode->append(std::move(path));
- else {
- _actions->svgTree().appendToPage(std::move(path));
- _actions->embed(bbox);
- }
- _path.clear();
-}
-
-
-/** 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) {
- _path.removeRedundantCommands();
- if ((_path.empty() && !_clipStack.prependedPath()) || !_actions)
- return;
-
- // compute bounding box
- BoundingBox bbox = _path.computeBBox();
- if (!_actions->getMatrix().isIdentity()) {
- _path.transform(_actions->getMatrix());
- if (!_xmlnode)
- bbox.transform(_actions->getMatrix());
- }
- if (_clipStack.prependedPath())
- _path.prepend(*_clipStack.prependedPath());
-
- ostringstream oss;
- _path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS);
- unique_ptr<XMLElement> path = util::make_unique<XMLElement>("path");
- path->addAttribute("d", oss.str());
- if (_pattern)
- path->addAttribute("fill", XMLString("url(#")+_pattern->svgID()+")");
- else if (_actions->getColor() != Color::BLACK || _savenode)
- path->addAttribute("fill", _actions->getColor().svgColorString());
- if (_clipStack.path() && !_savenode) { // clip path active and not inside pattern definition?
- // assign clipping path and clip bounding box
- path->addAttribute("clip-path", XMLString("url(#clip")+XMLString(_clipStack.topID())+")");
- bbox.intersect(_clipStack.path()->computeBBox());
- _clipStack.removePrependedPath();
- }
- if (evenodd) // SVG default fill rule is "nonzero" algorithm
- path->addAttribute("fill-rule", "evenodd");
- if (_fillalpha[0] < 1 || _fillalpha[1] < 1)
- path->addAttribute("fill-opacity", _fillalpha[0] * _fillalpha[1]);
- if (_blendmode > 0 && _blendmode < 16)
- path->addAttribute("style", "mix-blend-mode:"+css_blendmode_name(_blendmode));
- if (_xmlnode)
- _xmlnode->append(std::move(path));
- else {
- _actions->svgTree().appendToPage(std::move(path));
- _actions->embed(bbox);
- }
- _path.clear();
-}
-
-
-/** Postprocesses the 'image' operation performed by the PS interpreter. If
- * the PS image operator succeeded, there's now a PNG file that must be embedded
- * into the SVG file. */
-void PsSpecialHandler::image (std::vector<double> &p) {
- int imgID = static_cast<int>(p[0]); // ID of PNG file written
- if (imgID < 0) // no bitmap file written?
- return;
-
- double width = p[1];
- double height = p[2];
- string suffix = (BITMAP_FORMAT.substr(0, 3) == "png" ? ".png" : ".jpg");
- string fname = image_base_path(*_actions)+to_string(imgID)+suffix;
- ifstream ifs(fname, ios::binary);
- if (ifs) {
- ifs.close();
- auto image = util::make_unique<XMLElement>("image");
- double x = _actions->getX();
- double y = _actions->getY();
- image->addAttribute("x", x);
- image->addAttribute("y", y);
- image->addAttribute("width", util::to_string(width));
- image->addAttribute("height", util::to_string(height));
-
- // The current transformation matrix (CTM) maps the unit square to the rectangular region
- // of the target canvas showing the bitmap (see PS Reference Manual, 4.10.3). Therefore,
- // the local pixel coordinates of the original bitmap must be transformed by CTM*inv(M) to
- // get the target coordinates. M is the matrix that maps the unit square to the bitmap rectangle.
- Matrix matrix{width, 0, 0, 0, -height, height}; // maps unit square to bitmap rectangle
- matrix = matrix.invert().lmultiply(_actions->getMatrix());
- image->addAttribute("transform", matrix.toSVG());
-
- // To prevent memory issues, only add the filename to the href attribute and tag it by '@'
- // for later base64 encoding.
- image->addAttribute("@xlink:href", string("data:image/")+(suffix == ".png" ? "png" : "jpeg")+";base64,"+fname);
-
- // if set, assign clipping path to image
- if (_clipStack.path()) {
- auto group = util::make_unique<XMLElement>("g");
- group->addAttribute("clip-path", XMLString("url(#clip")+XMLString(_clipStack.topID())+")");
- group->append(std::move(image));
- image = std::move(group); // handle the entire group as image to add
- }
- if (_xmlnode)
- _xmlnode->append(std::move(image));
- else {
- _actions->svgTree().appendToPage(std::move(image));
- BoundingBox bbox(x, y, x+width, y+height);
- bbox.transform(matrix);
- if (_clipStack.path())
- bbox.intersect(_clipStack.path()->computeBBox());
- _actions->embed(bbox);
- }
- }
-}
-
-
-/** Creates a Matrix object from a given sequence of 6 double values.
- * The given values must be arranged in PostScript matrix order.
- * @param[in] v vector containing the matrix values
- * @param[in] startindex vector index of first component
- * @param[out] matrix the generated matrix */
-static void create_matrix (vector<double> &v, int startindex, Matrix &matrix) {
- // Ensure vector p has 6 elements. If necessary, add missing ones
- // using corresponding values of the identity matrix.
- if (v.size()-startindex < 6) {
- v.resize(6+startindex);
- for (size_t i=v.size()-startindex; i < 6; ++i)
- v[i+startindex] = (i%3 ? 0 : 1);
- }
- // PS matrix [a b c d e f] equals ((a,b,0),(c,d,0),(e,f,1)).
- // 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(v[startindex+1], v[startindex+2]); // => (a, c, b, d, e, f)
- swap(v[startindex+2], v[startindex+4]); // => (a, c, e, d, b, f)
- swap(v[startindex+3], v[startindex+4]); // => (a, c, e, b, d, f)
- matrix.set(v, startindex);
-}
-
-
-/** Starts the definition of a new fill pattern. This operator
- * expects 9 parameters for tiling patterns (see PS reference 4.9.2):
- * @param[in] p the 9 values defining a tiling pattern (see PS reference 4.9.2):
- * 0: pattern type (0:none, 1:tiling, 2:shading)
- * 1: pattern ID
- * 2-5: lower left and upper right coordinates of pattern box
- * 6: horizontal distance of adjacent tiles
- * 7: vertical distance of adjacent tiles
- * 8: paint type (1: colored pattern, 2: uncolored pattern)
- * 9-14: pattern matrix */
-void PsSpecialHandler::makepattern (vector<double> &p) {
- int pattern_type = static_cast<int>(p[0]);
- switch (pattern_type) {
- case 0:
- // pattern definition completed
- if (_savenode) {
- _xmlnode = _savenode;
- _savenode = nullptr;
- }
- break;
- case 1: { // tiling pattern
- int id = static_cast<int>(p[1]);
- BoundingBox bbox(p[2], p[3], p[4], p[5]);
- const double &xstep=p[6], &ystep=p[7]; // horizontal and vertical distance of adjacent tiles
- int paint_type = static_cast<int>(p[8]);
-
- Matrix matrix; // transformation matrix given together with pattern definition
- create_matrix(p, 9, matrix);
- matrix.lmultiply(_actions->getMatrix());
-
- unique_ptr<PSTilingPattern> pattern;
- if (paint_type == 1)
- pattern = util::make_unique<PSColoredTilingPattern>(id, bbox, matrix, xstep, ystep);
- else
- pattern = util::make_unique<PSUncoloredTilingPattern>(id, bbox, matrix, xstep, ystep);
- _savenode = _xmlnode;
- _xmlnode = pattern->getContainerNode(); // insert the following SVG elements into this node
- _patterns[id] = std::move(pattern);
- break;
- }
- case 2: {
- // define a shading pattern
- }
- }
-}
-
-
-/** Selects a previously defined fill pattern.
- * 0: pattern ID
- * 1-3: (optional) RGB values for uncolored tiling patterns
- * further parameters depend on the pattern type */
-void PsSpecialHandler::setpattern (vector<double> &p) {
- int patternID = static_cast<int>(p[0]);
- Color color;
- if (p.size() == 4)
- color.setRGB(p[1], p[2], p[3]);
- auto it = _patterns.find(patternID);
- if (it == _patterns.end())
- _pattern = nullptr;
- else {
- if (auto pattern = dynamic_cast<PSUncoloredTilingPattern*>(it->second.get()))
- pattern->setColor(color);
- it->second->apply(*_actions);
- if (auto pattern = dynamic_cast<PSTilingPattern*>(it->second.get()))
- _pattern = pattern;
- else
- _pattern = nullptr;
- }
-}
-
-
-/** Clears the current clipping path.
- * @param[in] p not used */
-void PsSpecialHandler::initclip (vector<double> &) {
- _clipStack.pushEmptyPath();
-}
-
-
-/** Assigns the current clipping path to the graphics path. */
-void PsSpecialHandler::clippath (std::vector<double>&) {
- if (!_clipStack.empty())
- _clipStack.setPrependedPath();
-}
-
-
-/** Assigns a new clipping path to the graphics state using the current path.
- * If the graphics state already contains a clipping path, the new one is
- * computed by intersecting the current clipping path with the current graphics
- * path (see PS language reference, 3rd edition, pp. 193, 542)
- * @param[in] p not used
- * @param[in] evenodd true: use even-odd fill algorithm, false: use nonzero fill algorithm */
-void PsSpecialHandler::clip (vector<double>&, bool evenodd) {
- clip(_path, evenodd);
-}
-
-
-/** Assigns a new clipping path to the graphics state using the current path.
- * If the graphics state already contains a clipping path, the new one is
- * computed by intersecting the current one with the given path.
- * @param[in] path path used to restrict the clipping region
- * @param[in] evenodd true: use even-odd fill algorithm, false: use nonzero fill algorithm */
-void PsSpecialHandler::clip (Path path, bool evenodd) {
- // when this method is called, _path contains the clipping path
- if (path.empty() || !_actions)
- return;
-
- Path::WindingRule windingRule = evenodd ? Path::WindingRule::EVEN_ODD : Path::WindingRule::NON_ZERO;
- path.setWindingRule(windingRule);
-
- if (!_actions->getMatrix().isIdentity())
- path.transform(_actions->getMatrix());
- if (_clipStack.prependedPath())
- path = PathClipper().unite(*_clipStack.prependedPath(), path);
-
- int oldID = _clipStack.topID();
-
- ostringstream oss;
- bool pathReplaced;
- if (!COMPUTE_CLIPPATHS_INTERSECTIONS || oldID < 1) {
- pathReplaced = _clipStack.replace(path);
- path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS);
- }
- else {
- // compute the intersection of the current clipping path with the current graphics path
- const Path *oldPath = _clipStack.path();
- Path intersectedPath = PathClipper().intersect(*oldPath, path);
- pathReplaced = _clipStack.replace(intersectedPath);
- intersectedPath.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS);
- }
- if (pathReplaced) {
- auto pathElem = util::make_unique<XMLElement>("path");
- pathElem->addAttribute("d", oss.str());
- if (evenodd)
- pathElem->addAttribute("clip-rule", "evenodd");
-
- int newID = _clipStack.topID();
- auto clipElem = util::make_unique<XMLElement>("clipPath");
- clipElem->addAttribute("id", XMLString("clip")+XMLString(newID));
- if (!COMPUTE_CLIPPATHS_INTERSECTIONS && oldID)
- clipElem->addAttribute("clip-path", XMLString("url(#clip")+XMLString(oldID)+")");
-
- clipElem->append(std::move(pathElem));
- _actions->svgTree().appendToDefs(std::move(clipElem));
- }
-}
-
-
-/** Applies a gradient fill to the current graphics path. Vector p contains the shading parameters
- * in the following order:
- * - shading type (6=Coons, 7=tensor product)
- * - color space (1=gray, 3=rgb, 4=cmyk)
- * - 1.0 followed by the background color components based on the declared color space, or 0.0
- * - 1.0 followed by the bounding box coordinates, or 0.0
- * - geometry and color parameters depending on the shading type */
-void PsSpecialHandler::shfill (vector<double> &params) {
- if (params.size() < 9)
- return;
-
- // collect common data relevant for all shading types
- int shadingTypeID = static_cast<int>(params[0]);
- ColorSpace colorSpace = Color::ColorSpace::RGB;
- switch (static_cast<int>(params[1])) {
- case 1: colorSpace = Color::ColorSpace::GRAY; break;
- case 3: colorSpace = Color::ColorSpace::RGB; break;
- case 4: colorSpace = Color::ColorSpace::CMYK; break;
- }
- VectorIterator<double> it(params);
- it += 2; // skip shading type and color space
- // Get color to fill the whole mesh area before drawing the gradient colors on top of that background.
- // This is an optional parameter to shfill.
- bool bgcolorGiven = static_cast<bool>(*it++);
- Color bgcolor;
- if (bgcolorGiven)
- bgcolor.set(colorSpace, it);
- // Get clipping rectangle to limit the drawing area of the gradient mesh.
- // This is an optional parameter to shfill too.
- bool bboxGiven = static_cast<bool>(*it++);
- if (bboxGiven) { // bounding box given
- Path bboxPath;
- const double &x1 = *it++;
- const double &y1 = *it++;
- const double &x2 = *it++;
- const double &y2 = *it++;
- bboxPath.moveto(x1, y1);
- bboxPath.lineto(x2, y1);
- bboxPath.lineto(x2, y2);
- bboxPath.lineto(x1, y2);
- bboxPath.closepath();
- clip(bboxPath, false);
- }
- try {
- if (shadingTypeID == 5)
- processLatticeTriangularPatchMesh(colorSpace, it);
- else
- processSequentialPatchMesh(shadingTypeID, colorSpace, it);
- }
- catch (ShadingException &e) {
- Message::estream(false) << "PostScript error: " << e.what() << '\n';
- it.invalidate(); // stop processing the remaining patch data
- }
- catch (IteratorException &e) {
- Message::estream(false) << "PostScript error: incomplete shading data\n";
- }
- if (bboxGiven)
- _clipStack.pop();
-}
-
-
-/** Reads position and color data of a single shading patch from the data vector.
- * @param[in] shadingTypeID PS shading type ID identifying the format of the subsequent patch data
- * @param[in] edgeflag edge flag specifying how to connect the current patch to the preceding one
- * @param[in] cspace color space used to compute the color gradient
- * @param[in,out] it iterator used to sequentially access the patch data
- * @param[out] points the points defining the geometry of the patch
- * @param[out] colors the colors assigned to the vertices of the patch */
-static void read_patch_data (ShadingPatch &patch, int edgeflag,
- VectorIterator<double> &it, vector<DPair> &points, vector<Color> &colors)
-{
- // number of control points and colors required to define a single patch
- int numPoints = patch.numPoints(edgeflag);
- int numColors = patch.numColors(edgeflag);
- points.resize(numPoints);
- colors.resize(numColors);
- if (patch.psShadingType() == 4) {
- // format of a free-form triangular patch definition, where eN denotes
- // the edge of the corresponding vertex:
- // edge flag = 0, x1, y1, {color1}, e2, x2, y2, {color2}, e3, x3, y3, {color3}
- // edge flag > 0, x1, y1, {color1}
- for (int i=0; i < numPoints; i++) {
- if (i > 0) ++it; // skip redundant edge flag from free-form triangular patch
- double x = *it++;
- double y = *it++;
- points[i] = DPair(x, y);
- colors[i].set(patch.colorSpace(), it);
- }
- }
- else if (patch.psShadingType() == 6 || patch.psShadingType() == 7) {
- // format of each Coons/tensor product patch definition:
- // edge flag = 0, x1, y1, ... , xn, yn, {color1}, {color2}, {color3}, {color4}
- // edge flag > 0, x5, y5, ... , xn, yn, {color3}, {color4}
- for (int i=0; i < numPoints; i++) {
- double x = *it++;
- double y = *it++;
- points[i] = DPair(x, y);
- }
- for (int i=0; i < numColors; i++)
- colors[i].set(patch.colorSpace(), it);
- }
-}
-
-
-class ShadingCallback : public ShadingPatch::Callback {
- public:
- ShadingCallback (SpecialActions &actions, XMLElement *parent, int clippathID)
- : _actions(actions)
- {
- auto group = util::make_unique<XMLElement>("g");
- _group = group.get();
- if (parent)
- parent->append(std::move(group));
- else
- actions.svgTree().appendToPage(std::move(group));
- if (clippathID > 0)
- _group->addAttribute("clip-path", XMLString("url(#clip")+XMLString(clippathID)+")");
- }
-
- void patchSegment (GraphicsPath<double> &path, const Color &color) override {
- if (!_actions.getMatrix().isIdentity())
- path.transform(_actions.getMatrix());
-
- // draw a single patch segment
- ostringstream oss;
- path.writeSVG(oss, SVGTree::RELATIVE_PATH_CMDS);
- auto pathElem = util::make_unique<XMLElement>("path");
- pathElem->addAttribute("d", oss.str());
- pathElem->addAttribute("fill", color.svgColorString());
- _group->append(std::move(pathElem));
- }
-
- private:
- SpecialActions &_actions;
- XMLElement *_group;
-};
-
-
-/** Handle all patch meshes whose patches and their connections can be processed sequentially.
- * This comprises free-form triangular, Coons, and tensor-product patch meshes. */
-void PsSpecialHandler::processSequentialPatchMesh (int shadingTypeID, ColorSpace colorSpace, VectorIterator<double> &it) {
- unique_ptr<ShadingPatch> previousPatch;
- while (it.valid()) {
- int edgeflag = static_cast<int>(*it++);
- vector<DPair> points;
- vector<Color> colors;
- unique_ptr<ShadingPatch> patch = ShadingPatch::create(shadingTypeID, colorSpace);
- read_patch_data(*patch, edgeflag, it, points, colors);
- patch->setPoints(points, edgeflag, previousPatch.get());
- patch->setColors(colors, edgeflag, previousPatch.get());
- ShadingCallback callback(*_actions, _xmlnode, _clipStack.topID());
-#if 0
- if (bgcolorGiven) {
- // fill whole patch area with given background color
- GraphicsPath<double> outline = patch->getBoundaryPath();
- callback.patchSegment(outline, bgcolor);
- }
-#endif
- patch->approximate(SHADING_SEGMENT_SIZE, SHADING_SEGMENT_OVERLAP, SHADING_SIMPLIFY_DELTA, callback);
- if (!_xmlnode) {
- // update bounding box
- BoundingBox bbox = patch->getBBox();
- bbox.transform(_actions->getMatrix());
- _actions->embed(bbox);
- }
- previousPatch = std::move(patch);
- }
-}
-
-
-void PsSpecialHandler::processLatticeTriangularPatchMesh (ColorSpace colorSpace, VectorIterator<double> &it) {
- int verticesPerRow = static_cast<int>(*it++);
- if (verticesPerRow < 2)
- return;
-
- struct PatchVertex {
- DPair point;
- Color color;
- };
-
- // hold two adjacent rows of vertices and colors
- vector<PatchVertex> row1(verticesPerRow);
- vector<PatchVertex> row2(verticesPerRow);
- vector<PatchVertex> *rowptr1 = &row1;
- vector<PatchVertex> *rowptr2 = &row2;
- // read data of first row
- for (int i=0; i < verticesPerRow; i++) {
- PatchVertex &vertex = (*rowptr1)[i];
- vertex.point.x(*it++);
- vertex.point.y(*it++);
- vertex.color.set(colorSpace, it);
- }
- LatticeTriangularPatch patch(colorSpace);
- ShadingCallback callback(*_actions, _xmlnode, _clipStack.topID());
- while (it.valid()) {
- // read next row
- for (int i=0; i < verticesPerRow; i++) {
- PatchVertex &vertex = (*rowptr2)[i];
- vertex.point.x(*it++);
- vertex.point.y(*it++);
- vertex.color.set(colorSpace, it);
- }
- // create triangular patches for the vertices of the two rows
- for (int i=0; i < verticesPerRow-1; i++) {
- const PatchVertex &v1 = (*rowptr1)[i], &v2 = (*rowptr1)[i+1];
- const PatchVertex &v3 = (*rowptr2)[i], &v4 = (*rowptr2)[i+1];
- patch.setPoints(v1.point, v2.point, v3.point);
- patch.setColors(v1.color, v2.color, v3.color);
- patch.approximate(SHADING_SEGMENT_SIZE, SHADING_SEGMENT_OVERLAP, SHADING_SIMPLIFY_DELTA, callback);
-
- patch.setPoints(v2.point, v3.point, v4.point);
- patch.setColors(v2.color, v3.color, v4.color);
- patch.approximate(SHADING_SEGMENT_SIZE, SHADING_SEGMENT_OVERLAP, SHADING_SIMPLIFY_DELTA, callback);
- }
- swap(rowptr1, rowptr2);
- }
-}
-
-
-/** Clears current path. */
-void PsSpecialHandler::newpath (vector<double> &p) {
- bool calledByNewpathOp = (p[0] > 0);
- if (calledByNewpathOp) // function triggered by PS operator 'newpath'?
- _clipStack.removePrependedPath();
- _path.clear();
-}
-
-
-void PsSpecialHandler::setmatrix (vector<double> &p) {
- if (_actions) {
- Matrix m;
- create_matrix(p, 0, m);
- _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.rmultiply(s);
- _actions->setMatrix(m);
- }
-}
-
-
-void PsSpecialHandler::translate (vector<double> &p) {
- if (_actions) {
- Matrix m = _actions->getMatrix();
- TranslationMatrix t(p[0], p[1]);
- m.rmultiply(t);
- _actions->setMatrix(m);
- }
-}
-
-
-void PsSpecialHandler::rotate (vector<double> &p) {
- if (_actions) {
- Matrix m = _actions->getMatrix();
- RotationMatrix r(p[0]);
- m.rmultiply(r);
- _actions->setMatrix(m);
- }
-}
-
-
-void PsSpecialHandler::setgray (vector<double> &p) {
- if (!_patternEnabled)
- _pattern = nullptr;
- _currentcolor.setGray(p[0]);
- if (_actions)
- _actions->setColor(_currentcolor);
-}
-
-
-void PsSpecialHandler::setrgbcolor (vector<double> &p) {
- if (!_patternEnabled)
- _pattern= nullptr;
- _currentcolor.setRGB(p[0], p[1], p[2]);
- if (_actions)
- _actions->setColor(_currentcolor);
-}
-
-
-void PsSpecialHandler::setcmykcolor (vector<double> &p) {
- if (!_patternEnabled)
- _pattern = nullptr;
- _currentcolor.setCMYK(p[0], p[1], p[2], p[3]);
- if (_actions)
- _actions->setColor(_currentcolor);
-}
-
-
-void PsSpecialHandler::sethsbcolor (vector<double> &p) {
- if (!_patternEnabled)
- _pattern = nullptr;
- _currentcolor.setHSB(p[0], p[1], p[2]);
- if (_actions)
- _actions->setColor(_currentcolor);
-}
-
-
-/** 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");
-}
-
-
-/** This method is called by PSInterpreter if the status of the output devices has changed.
- * @param[in] p 1 if output device is the nulldevice, 1 otherwise */
-void PsSpecialHandler::setnulldevice (vector<double> &p) {
- if (_actions) {
- if (p[0] != 0)
- _actions->lockOutput(); // prevent further SVG output
- else
- _actions->unlockOutput(); // enable SVG output again
- }
-}
-
-////////////////////////////////////////////
-
-void PsSpecialHandler::ClippingStack::pushEmptyPath () {
- if (!_stack.empty())
- _stack.emplace(Entry());
-}
-
-
-void PsSpecialHandler::ClippingStack::push (const Path &path, int saveID) {
- shared_ptr<Path> prependedPath;
- if (!_stack.empty())
- prependedPath = _stack.top().prependedPath;
- if (path.empty())
- _stack.emplace(Entry(saveID));
- else
- _stack.emplace(Entry(path, ++_maxID, saveID));
- _stack.top().prependedPath = prependedPath;
-}
-
-
-/** Pops a single or several elements from the clipping stack.
- * The method distinguishes 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())
- return;
-
- 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. */
-const PsSpecialHandler::Path* PsSpecialHandler::ClippingStack::path () const {
- return _stack.empty() ? nullptr : _stack.top().path.get();
-}
-
-
-/** Returns a pointer to the path on top of the stack, or 0 if the stack is empty. */
-const PsSpecialHandler::Path* PsSpecialHandler::ClippingStack::prependedPath () const {
- return _stack.empty() ? nullptr : _stack.top().prependedPath.get();
-}
-
-
-void PsSpecialHandler::ClippingStack::removePrependedPath () {
- if (!_stack.empty())
- _stack.top().prependedPath = nullptr;
-}
-
-
-/** Pops all elements from the stack. */
-void PsSpecialHandler::ClippingStack::clear() {
- while (!_stack.empty())
- _stack.pop();
-}
-
-
-/** Replaces the top path by a new one.
- * @param[in] path new path to put on the stack
- * @return true if the new path differs from the previous one */
-bool PsSpecialHandler::ClippingStack::replace (const Path &path) {
- if (_stack.empty())
- push(path, -1);
- else if (_stack.top().path && path == *_stack.top().path)
- return false;
- else {
- _stack.top().path = make_shared<Path>(path);
- _stack.top().pathID = ++_maxID;
- }
- return true;
-}
-
-
-/** Duplicates the top element, i.e. the top element is pushed again. */
-void PsSpecialHandler::ClippingStack::dup (int saveID) {
- _stack.emplace(_stack.empty() ? Entry() : _stack.top());
- _stack.top().saveID = saveID;
-}
-
-
-void PsSpecialHandler::ClippingStack::setPrependedPath () {
- if (!_stack.empty())
- _stack.top().prependedPath = _stack.top().path;
-}
-
-
-vector<const char*> PsSpecialHandler::prefixes() const {
- vector<const char*> pfx {
- "header=", // read and execute PS header file prior to the following PS statements
- "pdffile=", // process PDF file
- "psfile=", // read and execute PS file
- "PSfile=", // dito
- "ps:", // execute literal PS code wrapped by @beginspecial and @endspecial
- "ps::", // execute literal PS code without additional adaption of the drawing position
- "!", // execute literal PS header code following this prefix
- "\"", // execute literal PS code following this prefix
- "pst:", // dito
- "PST:", // same as "ps:"
- };
- return pfx;
-}