/************************************************************************* ** ImageToSVG.cpp ** ** ** ** This file is part of dvisvgm -- a fast DVI to SVG converter ** ** Copyright (C) 2005-2024 Martin Gieseking ** ** ** ** This program is free software; you can redistribute it and/or ** ** modify it under the terms of the GNU General Public License as ** ** published by the Free Software Foundation; either version 3 of ** ** the License, or (at your option) any later version. ** ** ** ** This program is distributed in the hope that it will be useful, but ** ** WITHOUT ANY WARRANTY; without even the implied warranty of ** ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** ** GNU General Public License for more details. ** ** ** ** You should have received a copy of the GNU General Public License ** ** along with this program; if not, see . ** *************************************************************************/ #include #include #include #include "Calculator.hpp" #include "DvisvgmSpecialHandler.hpp" #include "ImageToSVG.hpp" #include "Message.hpp" #include "MessageException.hpp" #include "PageRanges.hpp" #include "PsSpecialHandler.hpp" #include "optimizer/SVGOptimizer.hpp" #include "SVGOutput.hpp" #include "System.hpp" #include "utility.hpp" #include "version.hpp" using namespace std; ImageToSVG::ImageToSVG (std::string fname, SVGOutputBase &out) : _fname(std::move(fname)), _out(out), _gsVersion(Ghostscript().revision()) { } void ImageToSVG::checkGSAndFileFormat () { if (!_gsVersion) throw MessageException("Ghostscript is required to process "+imageFormat()+" files"); if (!imageIsValid()) throw MessageException("invalid "+imageFormat()+" file"); } void ImageToSVG::convert (int pageno) { checkGSAndFileFormat(); BoundingBox bbox = imageBBox(); if (bbox.valid() && (bbox.width() == 0 || bbox.height() == 0)) Message::wstream(true) << "bounding box of " << imageFormat() << " file is empty\n"; Message::mstream().indent(0); Message::mstream(false, Message::MC_PAGE_NUMBER) << "processing " << imageFormat() << " file\n"; Message::mstream().indent(1); _svg.newPage(pageno); // create a psfile special and forward it to the PsSpecialHandler stringstream ss; ss << "\"" << _fname << "\" " "llx=" << bbox.minX() << " " "lly=" << bbox.minY() << " " "urx=" << bbox.maxX() << " " "ury=" << bbox.maxY(); _currentPageNumber = pageno; if (!isSinglePageFormat()) ss << " page=" << pageno << " proc=gs"; try { _psHandler.process(psSpecialCmd(), ss, *this); } catch (...) { progress(nullptr); // remove progress message throw; } writeSVG(pageno); } void ImageToSVG::writeSVG (int pageno) { progress(nullptr); Matrix matrix = getUserMatrix(_bbox); // output SVG file SVGOptimizer(_svg).execute(); _svg.transformPage(matrix); _bbox.transform(matrix); _svg.setBBox(_bbox); _svg.appendToDoc(util::make_unique(" This file was generated by dvisvgm " + string(PROGRAM_VERSION) + " ")); bool success = _svg.write(_out.getPageStream(pageno, totalPageCount())); string svgfname = _out.filepath(pageno, totalPageCount()).shorterAbsoluteOrRelative(); _out.finish(); if (svgfname.empty()) svgfname = ""; if (!success) Message::wstream() << "failed to write output to " << svgfname << '\n'; else { const double bp2pt = 72.27/72; const double bp2mm = 25.4/72; Message::mstream(false,Message::MC_PAGE_SIZE) << "graphic size: " << XMLString(_bbox.width()*bp2pt) << "pt" << " x " << XMLString(_bbox.height()*bp2pt) << "pt" << " (" << XMLString(_bbox.width()*bp2mm) << "mm" << " x " << XMLString(_bbox.height()*bp2mm) << "mm)\n"; Message::mstream(false, Message::MC_PAGE_WRITTEN) << "output written to " << svgfname << '\n'; if (!_userMessage.empty()) { string msg = expandText(_userMessage); Message::ustream(true) << msg << "\n"; } } _bbox.invalidate(); _svg.reset(); } void ImageToSVG::convert (int firstPage, int lastPage, pair *pageinfo) { checkGSAndFileFormat(); int pageCount = 1; // number of pages converted if (isSinglePageFormat()) convert(1); else { if (firstPage > lastPage) swap(firstPage, lastPage); firstPage = max(1, firstPage); if (firstPage > totalPageCount()) pageCount = 0; else { lastPage = min(totalPageCount(), lastPage); pageCount = lastPage-firstPage+1; for (int i=firstPage; i <= lastPage; i++) convert(i); } } if (pageinfo) { pageinfo->first = pageCount; pageinfo->second = totalPageCount(); } } void ImageToSVG::convert (const std::string &rangestr, pair *pageinfo) { checkGSAndFileFormat(); PageRanges ranges; if (!ranges.parse(rangestr, totalPageCount())) throw MessageException("invalid page range format"); int pageCount=0; // number of pages converted for (const auto &range : ranges) { convert(range.first, range.second, pageinfo); if (pageinfo) pageCount += pageinfo->first; } if (pageinfo) pageinfo->first = pageCount; } FilePath ImageToSVG::getSVGFilePath (unsigned pageno) const { FilePath path; unsigned numPages = totalPageCount(); if (pageno >= 1 && pageno <= numPages) path = _out.filepath(pageno, numPages); return path; } void ImageToSVG::progress (const char *id) { static double time=System::time(); static bool draw=false; // show progress indicator? static size_t count=0; count++; // don't show the progress indicator before the given time has elapsed if (!draw && System::time()-time > PROGRESSBAR_DELAY) { draw = true; Terminal::cursor(false); Message::mstream(false) << "\n"; } if (draw && ((System::time() - time > 0.05) || id == nullptr)) { const size_t DIGITS=6; Message::mstream(false, Message::MC_PROGRESS) << string(DIGITS-min(DIGITS, static_cast(log10(count))), ' ') << count << " PostScript instructions processed\r"; // overprint indicator when finished if (id == nullptr) { Message::estream().clearline(); Terminal::cursor(true); } time = System::time(); } } /** Returns the matrix describing the graphics transformations * given by the user in terms of transformation commands. * @param[in] bbox bounding box of the graphics to transform */ Matrix ImageToSVG::getUserMatrix (const BoundingBox &bbox) const { Matrix matrix(1); if (!_transCmds.empty()) { const double bp2pt = (1_bp).pt(); Calculator calc; calc.setVariable("ux", bbox.minX()*bp2pt); calc.setVariable("uy", bbox.minY()*bp2pt); calc.setVariable("w", bbox.width()*bp2pt); calc.setVariable("h", bbox.height()*bp2pt); // add constants for length units to calculator for (const auto &unit : Length::getUnits()) calc.setVariable(unit.first, Length(1, unit.second).pt()); matrix.set(_transCmds, calc); } return matrix; }