diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /dviware/dvisvgm/src/dvisvgm.cpp |
Initial commit
Diffstat (limited to 'dviware/dvisvgm/src/dvisvgm.cpp')
-rw-r--r-- | dviware/dvisvgm/src/dvisvgm.cpp | 478 |
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; +} |