summaryrefslogtreecommitdiff
path: root/dviware/dvisvgm/src/dvisvgm.cpp
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /dviware/dvisvgm/src/dvisvgm.cpp
Initial commit
Diffstat (limited to 'dviware/dvisvgm/src/dvisvgm.cpp')
-rw-r--r--dviware/dvisvgm/src/dvisvgm.cpp478
1 files changed, 478 insertions, 0 deletions
diff --git a/dviware/dvisvgm/src/dvisvgm.cpp b/dviware/dvisvgm/src/dvisvgm.cpp
new file mode 100644
index 0000000000..a2c28f8a77
--- /dev/null
+++ b/dviware/dvisvgm/src/dvisvgm.cpp
@@ -0,0 +1,478 @@
+/*************************************************************************
+** dvisvgm.cpp **
+** **
+** This file is part of dvisvgm -- a fast DVI to SVG converter **
+** Copyright (C) 2005-2019 Martin Gieseking <martin.gieseking@uos.de> **
+** **
+** This program is free software; you can redistribute it and/or **
+** modify it under the terms of the GNU General Public License as **
+** published by the Free Software Foundation; either version 3 of **
+** the License, or (at your option) any later version. **
+** **
+** This program is distributed in the hope that it will be useful, but **
+** WITHOUT ANY WARRANTY; without even the implied warranty of **
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
+** GNU General Public License for more details. **
+** **
+** You should have received a copy of the GNU General Public License **
+** along with this program; if not, see <http://www.gnu.org/licenses/>. **
+*************************************************************************/
+
+#include <config.h>
+#include <algorithm>
+#include <clipper.hpp>
+#include <fstream>
+#include <iostream>
+#include <potracelib.h>
+#include <sstream>
+#include <vector>
+#include <zlib.h>
+#include "CommandLine.hpp"
+#include "DVIToSVG.hpp"
+#include "DVIToSVGActions.hpp"
+#include "EPSToSVG.hpp"
+#include "FileFinder.hpp"
+#include "FileSystem.hpp"
+#include "Font.hpp"
+#include "FontEngine.hpp"
+#include "Ghostscript.hpp"
+#include "HashFunction.hpp"
+#include "HyperlinkManager.hpp"
+#include "Message.hpp"
+#include "PageSize.hpp"
+#include "PDFToSVG.hpp"
+#include "PSInterpreter.hpp"
+#include "PsSpecialHandler.hpp"
+#include "SignalHandler.hpp"
+#include "SourceInput.hpp"
+#include "optimizer/SVGOptimizer.hpp"
+#include "SVGOutput.hpp"
+#include "System.hpp"
+#include "XXHashFunction.hpp"
+#include "utility.hpp"
+#include "version.hpp"
+
+#ifndef DISABLE_WOFF
+#include <brotli/encode.h>
+//#include <woff2/version.h>
+#include "ffwrapper.h"
+#include "TTFAutohint.hpp"
+#endif
+
+using namespace std;
+
+////////////////////////////////////////////////////////////////////////////////
+
+static string remove_path (string fname) {
+ fname = FileSystem::adaptPathSeperators(fname);
+ size_t slashpos = fname.rfind('/');
+ if (slashpos == string::npos)
+ return fname;
+ return fname.substr(slashpos+1);
+}
+
+
+static string ensure_suffix (string fname, const string &suffix) {
+ if (!fname.empty()) {
+ size_t dotpos = remove_path(fname).rfind('.');
+ if (dotpos == string::npos)
+ fname += "." + suffix;
+ }
+ return fname;
+}
+
+
+static string get_transformation_string (const CommandLine &args) {
+ ostringstream oss;
+ if (args.rotateOpt.given())
+ oss << 'R' << args.rotateOpt.value() << ",w/2,h/2";
+ if (args.translateOpt.given())
+ oss << 'T' << args.translateOpt.value();
+ if (args.scaleOpt.given())
+ oss << 'S' << args.scaleOpt.value();
+ if (args.transformOpt.given())
+ oss << args.transformOpt.value();
+ return oss.str();
+}
+
+
+static void set_libgs (CommandLine &args) {
+#if !defined(DISABLE_GS) && !defined(HAVE_LIBGS)
+ if (args.libgsOpt.given())
+ Ghostscript::LIBGS_NAME = args.libgsOpt.value();
+ else if (getenv("LIBGS"))
+ Ghostscript::LIBGS_NAME = getenv("LIBGS");
+#endif
+}
+
+
+static bool set_cache_dir (const CommandLine &args) {
+ if (args.cacheOpt.given() && !args.cacheOpt.value().empty()) {
+ if (args.cacheOpt.value() == "none")
+ PhysicalFont::CACHE_PATH = nullptr;
+ else if (FileSystem::exists(args.cacheOpt.value()))
+ PhysicalFont::CACHE_PATH = args.cacheOpt.value().c_str();
+ else
+ Message::wstream(true) << "cache directory '" << args.cacheOpt.value() << "' does not exist (caching disabled)\n";
+ }
+ else if (const char *userdir = FileSystem::userdir()) {
+#ifdef _WIN32
+ string cachedir = "\\.dvisvgm\\cache";
+#else
+ string cachedir = "/.dvisvgm/cache";
+#endif
+ static string cachepath = userdir + cachedir;
+ if (!FileSystem::exists(cachepath))
+ FileSystem::mkdir(cachepath);
+ PhysicalFont::CACHE_PATH = cachepath.c_str();
+ }
+ if (args.cacheOpt.given() && args.cacheOpt.value().empty()) {
+ cout << "cache directory: " << (PhysicalFont::CACHE_PATH ? PhysicalFont::CACHE_PATH : "(none)") << '\n';
+ try {
+ if (PhysicalFont::CACHE_PATH)
+ FontCache::fontinfo(PhysicalFont::CACHE_PATH, cout, true);
+ }
+ catch (StreamReaderException &e) {
+ Message::wstream(true) << "failed reading cache data\n";
+ }
+ return false;
+ }
+ return true;
+}
+
+
+static bool set_temp_dir (const CommandLine &args) {
+ if (args.tmpdirOpt.given()) {
+ if (!args.tmpdirOpt.value().empty())
+ FileSystem::TMPDIR = args.tmpdirOpt.value();
+ else {
+ cout << "temporary folder: " << FileSystem::tmpdir() << '\n';
+ return false;
+ }
+ }
+ return true;
+}
+
+
+static void check_bbox (const string &bboxstr) {
+ for (const char *fmt : {"none", "min", "preview", "papersize", "dvi"})
+ if (bboxstr == fmt)
+ return;
+ if (isalpha(bboxstr[0])) {
+ try {
+ PageSize size(bboxstr);
+ }
+ catch (const PageSizeException &e) {
+ throw MessageException("invalid bounding box format '" + bboxstr + "'");
+ }
+ }
+ else {
+ // check if given bbox argument is valid, i.e. doesn't throw an exception
+ BoundingBox bbox;
+ bbox.set(bboxstr);
+ }
+}
+
+
+// Helper class to generate a list of version information of the used libraries.
+class VersionInfo {
+ public:
+ void add (const string &name, const string &version, bool ignoreEmpty=false) {
+ if (!version.empty() || !ignoreEmpty)
+ append(name, util::trim(version));
+ }
+
+ void add (const string &name, const char *version, bool ignoreEmpty=false) {
+ if (version && *version)
+ append(name, util::trim(version));
+ else if (!ignoreEmpty)
+ append(name, "");
+ }
+
+ void add (const string &name, const vector<int> &versionComponents) {
+ string version;
+ for (auto it=versionComponents.begin(); it != versionComponents.end(); ++it) {
+ if (it != versionComponents.begin())
+ version += '.';
+ version += to_string(*it);
+ }
+ append(name, version);
+ }
+
+ /** Adds a version number given as a single unsigned integer, and optionally
+ * extracts its components, e.g. 0x00010203 => "1.2.3" (3 components separated
+ * by multiples of 256).
+ * @param[in] name library name
+ * @param[in] version version number
+ * @param[in] compcount number of components the version consists of
+ * @param[in] factor factor used to separate the components */
+ void add (const string &name, uint32_t version, int compcount=1, uint32_t factor=0xffffffff) {
+ string str;
+ while (compcount-- > 0) {
+ if (!str.empty())
+ str.insert(0, ".");
+ str.insert(0, to_string(version % factor));
+ version /= factor;
+ }
+ append(name, str);
+ }
+
+ /** Writes the version information to the given output stream. */
+ void write (ostream &os) {
+ using Entry = pair<string,string>;
+ sort(_versionPairs.begin(), _versionPairs.end(), [](const Entry &e1, const Entry &e2) {
+ return util::tolower(e1.first) < util::tolower(e2.first);
+ });
+ size_t maxNameLength=0;
+ for (const Entry &versionPair : _versionPairs)
+ maxNameLength = max(maxNameLength, versionPair.first.length());
+ for (const Entry &versionPair : _versionPairs) {
+ string name = versionPair.first+":";
+ os << left << setw(maxNameLength+2) << name;
+ os << (versionPair.second.empty() ? "unknown" : versionPair.second) << '\n';
+ }
+ }
+
+ protected:
+ void append (const string &name, const string &version) {
+ _versionPairs.emplace_back(pair<string,string>(name, version));
+ }
+
+ private:
+ vector<pair<string,string>> _versionPairs;
+};
+
+
+static void print_version (bool extended) {
+ string versionstr = string(PROGRAM_NAME)+" "+PROGRAM_VERSION;
+#ifdef TARGET_SYSTEM
+ if (extended && strlen(TARGET_SYSTEM) > 0)
+ versionstr += " (" TARGET_SYSTEM ")";
+#endif
+ cout << versionstr << '\n';
+ if (extended) {
+ cout << string(versionstr.length(), '-') << '\n';
+ VersionInfo versionInfo;
+ versionInfo.add("clipper", CLIPPER_VERSION);
+ versionInfo.add("freetype", FontEngine::version());
+ versionInfo.add("potrace", strchr(potrace_version(), ' '));
+ versionInfo.add("xxhash", XXH64HashFunction::version(), 3, 100);
+ versionInfo.add("zlib", zlibVersion());
+ versionInfo.add("Ghostscript", Ghostscript().revisionstr(), true);
+#ifndef DISABLE_WOFF
+ versionInfo.add("brotli", BrotliEncoderVersion(), 3, 0x1000);
+// versionInfo.add("woff2", woff2::version, 3, 0x100);
+ versionInfo.add("fontforge", ff_version());
+ versionInfo.add("ttfautohint", TTFAutohint().version(), true);
+#endif
+#ifdef MIKTEX
+ versionInfo.add("MiKTeX", FileFinder::instance().version());
+#else
+ versionInfo.add("kpathsea", FileFinder::instance().version());
+#endif
+ versionInfo.write(cout);
+ }
+}
+
+
+static void init_fontmap (const CommandLine &cmdline) {
+ string mapseq;
+ if (cmdline.fontmapOpt.given())
+ mapseq = cmdline.fontmapOpt.value();
+ bool additional = !mapseq.empty() && strchr("+-=", mapseq[0]);
+ if (mapseq.empty() || additional) {
+ bool found = false;
+ for (string mapfile : {"ps2pk", "pdftex", "dvipdfm", "psfonts"}) {
+ if ((found = FontMap::instance().read(mapfile+".map")))
+ break;
+ }
+ if (!found)
+ Message::wstream(true) << "none of the default map files could be found\n";
+ }
+ if (!mapseq.empty())
+ FontMap::instance().read(mapseq);
+}
+
+
+/** Returns a unique string for the current state of the command-line
+ * options affecting the SVG output. */
+static string svg_options_hash (const CommandLine &cmdline) {
+ // options affecting the SVG output
+ vector<const CL::Option*> svg_options = {
+ &cmdline.bboxOpt, &cmdline.clipjoinOpt, &cmdline.colornamesOpt, &cmdline.commentsOpt,
+ &cmdline.exactBboxOpt, &cmdline.fontFormatOpt, &cmdline.fontmapOpt, &cmdline.gradOverlapOpt,
+ &cmdline.gradSegmentsOpt, &cmdline.gradSimplifyOpt, &cmdline.linkmarkOpt, &cmdline.magOpt,
+ &cmdline.noFontsOpt, &cmdline.noMergeOpt, &cmdline.noSpecialsOpt, &cmdline.noStylesOpt,
+ &cmdline.optimizeOpt, &cmdline.precisionOpt, &cmdline.relativeOpt, &cmdline.zoomOpt
+ };
+ string idString = get_transformation_string(cmdline);
+ for (const CL::Option *opt : svg_options) {
+ idString += char(opt->given());
+ idString += opt->valueString();
+ }
+ return XXH64HashFunction(idString).digestString();
+}
+
+
+static bool list_page_hashes (const CommandLine &cmdline, DVIToSVG &dvisvg) {
+ if (cmdline.pageHashesOpt.given()) {
+ DVIToSVG::PAGE_HASH_SETTINGS.setParameters(cmdline.pageHashesOpt.value());
+ DVIToSVG::PAGE_HASH_SETTINGS.setOptionHash(svg_options_hash(cmdline));
+ if (DVIToSVG::PAGE_HASH_SETTINGS.isSet(DVIToSVG::HashSettings::P_LIST)) {
+ dvisvg.listHashes(cmdline.pageOpt.value(), cout);
+ return true;
+ }
+ }
+ return false;
+}
+
+
+static void set_variables (const CommandLine &cmdline) {
+ Message::COLORIZE = cmdline.colorOpt.given();
+ if (cmdline.progressOpt.given()) {
+ DVIToSVG::COMPUTE_PROGRESS = true;
+ SpecialActions::PROGRESSBAR_DELAY = cmdline.progressOpt.value();
+ }
+ Color::SUPPRESS_COLOR_NAMES = !cmdline.colornamesOpt.given();
+ SVGTree::CREATE_CSS = !cmdline.noStylesOpt.given();
+ SVGTree::USE_FONTS = !cmdline.noFontsOpt.given();
+ if (!SVGTree::setFontFormat(cmdline.fontFormatOpt.value())) {
+ string msg = "unknown font format '"+cmdline.fontFormatOpt.value()+"' (supported formats: ";
+ for (const string &format : FontWriter::supportedFormats())
+ msg += format + ", ";
+ msg.erase(msg.end()-2);
+ throw CL::CommandLineException(msg);
+ }
+ SVGTree::CREATE_USE_ELEMENTS = cmdline.noFontsOpt.value() < 1;
+ SVGTree::ZOOM_FACTOR = cmdline.zoomOpt.value();
+ SVGTree::RELATIVE_PATH_CMDS = cmdline.relativeOpt.given();
+ SVGTree::MERGE_CHARS = !cmdline.noMergeOpt.given();
+ SVGTree::ADD_COMMENTS = cmdline.commentsOpt.given();
+ DVIToSVG::TRACE_MODE = cmdline.traceAllOpt.given() ? (cmdline.traceAllOpt.value() ? 'a' : 'm') : 0;
+ Message::LEVEL = cmdline.verbosityOpt.value();
+ PhysicalFont::EXACT_BBOX = cmdline.exactBboxOpt.given();
+ PhysicalFont::KEEP_TEMP_FILES = cmdline.keepOpt.given();
+ PhysicalFont::METAFONT_MAG = max(1.0, cmdline.magOpt.value());
+ XMLString::DECIMAL_PLACES = max(0, min(6, cmdline.precisionOpt.value()));
+ PsSpecialHandler::COMPUTE_CLIPPATHS_INTERSECTIONS = cmdline.clipjoinOpt.given();
+ PsSpecialHandler::SHADING_SEGMENT_OVERLAP = cmdline.gradOverlapOpt.given();
+ PsSpecialHandler::SHADING_SEGMENT_SIZE = max(1, cmdline.gradSegmentsOpt.value());
+ PsSpecialHandler::SHADING_SIMPLIFY_DELTA = cmdline.gradSimplifyOpt.value();
+ if (cmdline.optimizeOpt.given()) {
+ SVGOptimizer::MODULE_SEQUENCE = cmdline.optimizeOpt.value();
+ vector<string> modnames;
+ if (!SVGOptimizer().checkModuleString(SVGOptimizer::MODULE_SEQUENCE, modnames)) {
+ string msg = "invalid optimizer module";
+ if (modnames.size() > 1) msg += 's';
+ msg += ": ";
+ for (const string &modname : modnames)
+ msg += modname + ", ";
+ msg.erase(msg.end()-2);
+ throw CL::CommandLineException(msg);
+ }
+ }
+}
+
+
+static void timer_message (double start_time, const pair<int,int> *pageinfo) {
+ Message::mstream().indent(0);
+ if (!pageinfo)
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << "\n" << "file";
+ else {
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << "\n" << pageinfo->first << " of " << pageinfo->second << " page";
+ if (pageinfo->second > 1)
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << 's';
+ }
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << " converted in " << (System::time()-start_time) << " seconds\n";
+}
+
+
+int main (int argc, char *argv[]) {
+ try {
+ CommandLine cmdline;
+ cmdline.parse(argc, argv);
+ if (argc == 1 || cmdline.helpOpt.given()) {
+ cmdline.help(cout, cmdline.helpOpt.value());
+ return 0;
+ }
+ FileFinder::init(argv[0], "dvisvgm", !cmdline.noMktexmfOpt.given());
+ set_libgs(cmdline);
+ if (cmdline.versionOpt.given()) {
+ print_version(cmdline.versionOpt.value());
+ return 0;
+ }
+ if (cmdline.listSpecialsOpt.given()) {
+ DVIToSVG::setProcessSpecials();
+ SpecialManager::instance().writeHandlerInfo(cout);
+ return 0;
+ }
+ if (cmdline.optimizeOpt.value() == "list") {
+ SVGOptimizer().listModules(cout);
+ return 0;
+ }
+ if (!set_cache_dir(cmdline) || !set_temp_dir(cmdline))
+ return 0;
+ check_bbox(cmdline.bboxOpt.value());
+ if (!HyperlinkManager::setLinkMarker(cmdline.linkmarkOpt.value()))
+ Message::wstream(true) << "invalid argument '"+cmdline.linkmarkOpt.value()+"' supplied for option --linkmark\n";
+ if (cmdline.stdinOpt.given() || cmdline.singleDashGiven()) {
+ if (!cmdline.filenames().empty())
+ throw MessageException("option - or --stdin can't be used together with a filename");
+ cmdline.addFilename(""); // empty filename => read from stdin
+ }
+ if (argc > 1 && cmdline.filenames().empty())
+ throw MessageException("no input file given");
+
+ SignalHandler::instance().start();
+ string inputfile = ensure_suffix(cmdline.filenames()[0],
+ cmdline.epsOpt.given() ? "eps" : cmdline.pdfOpt.given() ? "pdf" : "dvi");
+ SourceInput srcin(inputfile);
+ if (!srcin.getInputStream(true))
+ throw MessageException("can't open file '" + srcin.getMessageFileName() + "' for reading");
+
+ double start_time = System::time();
+ set_variables(cmdline);
+ SVGOutput out(cmdline.stdoutOpt.given() ? "" : srcin.getFileName(),
+ cmdline.outputOpt.value(),
+ cmdline.zipOpt.given() ? cmdline.zipOpt.value() : 0);
+ pair<int,int> pageinfo;
+ if (cmdline.epsOpt.given() || cmdline.pdfOpt.given()) {
+ auto img2svg = unique_ptr<ImageToSVG>(
+ cmdline.epsOpt.given()
+ ? static_cast<ImageToSVG*>(new EPSToSVG(srcin.getFilePath(), out))
+ : static_cast<ImageToSVG*>(new PDFToSVG(srcin.getFilePath(), out)));
+ img2svg->setPageTransformation(get_transformation_string(cmdline));
+ img2svg->convert(cmdline.pageOpt.value(), &pageinfo);
+ timer_message(start_time, img2svg->isSinglePageFormat() ? nullptr : &pageinfo);
+ }
+ else {
+ init_fontmap(cmdline);
+ DVIToSVG dvi2svg(srcin.getInputStream(), out);
+ if (list_page_hashes(cmdline, dvi2svg))
+ return 0;
+ const char *ignore_specials=nullptr;
+ if (cmdline.noSpecialsOpt.given())
+ ignore_specials = cmdline.noSpecialsOpt.value().empty() ? "*" : cmdline.noSpecialsOpt.value().c_str();
+ dvi2svg.setProcessSpecials(ignore_specials, true);
+ dvi2svg.setPageTransformation(get_transformation_string(cmdline));
+ dvi2svg.setPageSize(cmdline.bboxOpt.value());
+
+ dvi2svg.convert(cmdline.pageOpt.value(), &pageinfo);
+ timer_message(start_time, &pageinfo);
+ }
+ }
+ catch (DVIException &e) {
+ Message::estream() << "\nDVI error: " << e.what() << '\n';
+ }
+ catch (PSException &e) {
+ Message::estream() << "\nPostScript error: " << e.what() << '\n';
+ }
+ catch (SignalException &e) {
+ Message::wstream().clearline();
+ Message::wstream(true) << "execution interrupted by user\n";
+ }
+ catch (exception &e) {
+ Message::estream(true) << e.what() << '\n';
+ }
+ return 0;
+}