summaryrefslogtreecommitdiff
path: root/dviware/dvisvgm/src/DVIToSVG.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dviware/dvisvgm/src/DVIToSVG.cpp')
-rw-r--r--dviware/dvisvgm/src/DVIToSVG.cpp575
1 files changed, 575 insertions, 0 deletions
diff --git a/dviware/dvisvgm/src/DVIToSVG.cpp b/dviware/dvisvgm/src/DVIToSVG.cpp
new file mode 100644
index 0000000000..4911dfe321
--- /dev/null
+++ b/dviware/dvisvgm/src/DVIToSVG.cpp
@@ -0,0 +1,575 @@
+/*************************************************************************
+** DVIToSVG.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 <cstdlib>
+#include <ctime>
+#include <fstream>
+#include <iomanip>
+#include <set>
+#include <sstream>
+#include "Calculator.hpp"
+#include "DVIToSVG.hpp"
+#include "DVIToSVGActions.hpp"
+#include "FileSystem.hpp"
+#include "Font.hpp"
+#include "FontManager.hpp"
+#include "GlyphTracerMessages.hpp"
+#include "InputBuffer.hpp"
+#include "InputReader.hpp"
+#include "PageRanges.hpp"
+#include "PageSize.hpp"
+#include "PreScanDVIReader.hpp"
+#include "SignalHandler.hpp"
+#include "optimizer/SVGOptimizer.hpp"
+#include "SVGOutput.hpp"
+#include "utility.hpp"
+#include "version.hpp"
+#include "XXHashFunction.hpp"
+
+///////////////////////////////////
+// special handlers
+
+#include "BgColorSpecialHandler.hpp"
+#include "ColorSpecialHandler.hpp"
+#include "DvisvgmSpecialHandler.hpp"
+#include "EmSpecialHandler.hpp"
+#include "HtmlSpecialHandler.hpp"
+#include "PapersizeSpecialHandler.hpp"
+#include "PdfSpecialHandler.hpp"
+#include "TpicSpecialHandler.hpp"
+#ifndef HAVE_LIBGS
+ #include "NoPsSpecialHandler.hpp"
+#endif
+#ifndef DISABLE_GS
+ #include "PsSpecialHandler.hpp"
+#endif
+
+///////////////////////////////////
+
+using namespace std;
+
+/** 'a': trace all glyphs even if some of them are already cached
+ * 'm': trace missing glyphs, i.e. glyphs not yet cached
+ * 0 : only trace actually required glyphs */
+char DVIToSVG::TRACE_MODE = 0;
+bool DVIToSVG::COMPUTE_PROGRESS = false;
+DVIToSVG::HashSettings DVIToSVG::PAGE_HASH_SETTINGS;
+
+
+DVIToSVG::DVIToSVG (istream &is, SVGOutputBase &out) : DVIReader(is), _out(out)
+{
+ _pageHeight = _pageWidth = 0;
+ _tx = _ty = 0; // no cursor translation
+ _pageByte = 0;
+ _prevXPos = _prevYPos = numeric_limits<double>::min();
+ _prevWritingMode = WritingMode::LR;
+ _actions = util::make_unique<DVIToSVGActions>(*this, _svg);
+}
+
+
+/** Starts the conversion process.
+ * @param[in] first number of first page to convert
+ * @param[in] last number of last page to convert
+ * @param[in] hashFunc pointer to function to be used to compute page hashes */
+void DVIToSVG::convert (unsigned first, unsigned last, HashFunction *hashFunc) {
+ if (first > last)
+ swap(first, last);
+ if (first > numberOfPages()) {
+ ostringstream oss;
+ oss << "file contains only " << numberOfPages() << " page";
+ if (numberOfPages() > 1)
+ oss << 's';
+ throw DVIException(oss.str());
+ }
+ last = min(last, numberOfPages());
+ bool computeHashes = (hashFunc && !_out.ignoresHashes());
+ string shortenedOptHash = XXH32HashFunction(PAGE_HASH_SETTINGS.optionsHash()).digestString();
+ for (unsigned i=first; i <= last; ++i) {
+ string dviHash, combinedHash;
+ if (computeHashes) {
+ computePageHash(i, *hashFunc);
+ dviHash = hashFunc->digestString();
+ hashFunc->update(PAGE_HASH_SETTINGS.optionsHash());
+ combinedHash = hashFunc->digestString();
+ }
+ const SVGOutput::HashTriple hashTriple(dviHash, shortenedOptHash, combinedHash);
+ string fname = _out.filename(i, numberOfPages(), hashTriple);
+ if (!dviHash.empty() && !PAGE_HASH_SETTINGS.isSet(HashSettings::P_REPLACE) && FileSystem::exists(fname)) {
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << "skipping page " << i;
+ Message::mstream().indent(1);
+ Message::mstream(false, Message::MC_PAGE_WRITTEN) << "\nfile " << fname << " exists\n";
+ Message::mstream().indent(0);
+ }
+ else {
+ executePage(i);
+ SVGOptimizer(_svg).execute();
+ embedFonts(_svg.rootNode());
+ bool success = _svg.write(_out.getPageStream(currentPageNumber(), numberOfPages(), hashTriple));
+ if (fname.empty())
+ fname = "<stdout>";
+ if (success)
+ Message::mstream(false, Message::MC_PAGE_WRITTEN) << "\noutput written to " << fname << '\n';
+ else
+ Message::wstream(true) << "failed to write output to " << fname << '\n';
+ _svg.reset();
+ _actions->reset();
+ }
+ }
+}
+
+
+/** Creates a HashFunction object for a given algorithm name.
+ * @param[in] algo name of hash algorithm
+ * @return pointer to hash function
+ * @throw MessageException if algorithm name is invalid or not supported */
+static unique_ptr<HashFunction> create_hash_function (const string &algo) {
+ if (auto hashFunc = HashFunction::create(algo))
+ return hashFunc;
+
+ string msg = "unknown hash algorithm '"+algo+"' (supported algorithms: ";
+ for (const string &name : HashFunction::supportedAlgorithms())
+ msg += name + ", ";
+ msg.pop_back();
+ msg.back() = ')';
+ throw MessageException(msg);
+}
+
+
+/** Convert DVI pages specified by a page range string.
+ * @param[in] rangestr string describing the pages to convert
+ * @param[out] pageinfo (number of converted pages, number of total pages) */
+void DVIToSVG::convert (const string &rangestr, pair<int,int> *pageinfo) {
+ PageRanges ranges;
+ if (!ranges.parse(rangestr, numberOfPages()))
+ throw MessageException("invalid page range format");
+
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << "pre-processing DVI file (format version " << getDVIVersion() << ")\n";
+ if (auto actions = dynamic_cast<DVIToSVGActions*>(_actions.get())) {
+ PreScanDVIReader prescan(getInputStream(), actions);
+ actions->setDVIReader(prescan);
+ prescan.executeAllPages();
+ actions->setDVIReader(*this);
+ SpecialManager::instance().notifyPreprocessingFinished();
+ }
+
+ unique_ptr<HashFunction> hashFunc;
+ if (!PAGE_HASH_SETTINGS.algorithm().empty()) // name of hash algorithm present?
+ hashFunc = create_hash_function(PAGE_HASH_SETTINGS.algorithm());
+
+ for (const auto &range : ranges)
+ convert(range.first, range.second, hashFunc.get());
+ if (pageinfo) {
+ pageinfo->first = ranges.numberOfPages();
+ pageinfo->second = numberOfPages();
+ }
+}
+
+
+/** Writes the hash values of a selected set of pages to an output stream.
+ * @param[in] rangestr string describing the pages to convert
+ * @param[in,out] os stream the output is written to */
+void DVIToSVG::listHashes (const string &rangestr, std::ostream &os) {
+ PageRanges ranges;
+ if (!ranges.parse(rangestr, numberOfPages()))
+ throw MessageException("invalid page range format");
+
+ XXH32HashFunction xxh32;
+ auto hashFunc = create_hash_function(PAGE_HASH_SETTINGS.algorithm());
+ int width1 = util::ilog10(numberOfPages())+1;
+ int width2 = hashFunc->digestSize()*2;
+ int spaces1 = width1+2+(width2-3)/2;
+ int spaces2 = width1+2+width2+2-spaces1-3+(width2-7)/2;
+ string shortenedOptHash = XXH32HashFunction(PAGE_HASH_SETTINGS.optionsHash()).digestString();
+ os << string(spaces1, ' ') << "DVI"
+ << string(spaces2, ' ') << "DVI+opt\n";
+ for (const auto &range : ranges) {
+ for (int i=range.first; i <= range.second; i++) {
+ computePageHash(i, *hashFunc);
+ os << setw(width1) << i;
+ os << ": " << hashFunc->digestString();
+ hashFunc->update(PAGE_HASH_SETTINGS.optionsHash());
+ os << ", " << hashFunc->digestString() << '\n';
+ }
+ }
+ os << "hash algorithm: " << PAGE_HASH_SETTINGS.algorithm()
+ << ", options hash: " << shortenedOptHash << '\n';
+}
+
+
+int DVIToSVG::executeCommand () {
+ SignalHandler::instance().check();
+ const streampos cmdpos = tell();
+ int opcode = DVIReader::executeCommand();
+ if (dviState().v+_ty != _prevYPos) {
+ _tx = _ty = 0;
+ _prevYPos = dviState().v;
+ }
+ _prevXPos = dviState().h+_tx;
+ _prevWritingMode = dviState().d;
+ if (COMPUTE_PROGRESS && inPage() && _actions) {
+ size_t pagelen = numberOfPageBytes(currentPageNumber()-1);
+ _pageByte += tell()-cmdpos;
+ _actions->progress(_pageByte, pagelen);
+ }
+ return opcode;
+}
+
+
+/** This template method is called by parent class DVIReader before
+ * executing the BOP actions.
+ * @param[in] pageno physical page number (1 = first page)
+ * @param[in] c contains information about the page (page number etc.) */
+void DVIToSVG::enterBeginPage (unsigned pageno, const vector<int32_t> &c) {
+ if (dynamic_cast<DVIToSVGActions*>(_actions.get())) {
+ Message::mstream().indent(0);
+ Message::mstream(false, Message::MC_PAGE_NUMBER) << "processing page " << pageno;
+ if (pageno != (unsigned)c[0]) // Does page number shown on page differ from physical page number?
+ Message::mstream(false) << " [" << c[0] << ']';
+ Message::mstream().indent(1);
+ _svg.appendToDoc(util::make_unique<XMLComment>(" This file was generated by dvisvgm " + string(PROGRAM_VERSION) + " "));
+ }
+}
+
+
+/** This template method is called by DVIReader::cmdEop() after
+ * executing the EOP actions. */
+void DVIToSVG::leaveEndPage (unsigned) {
+ if (!dynamic_cast<DVIToSVGActions*>(_actions.get()))
+ return;
+
+ // set bounding box and apply page transformations
+ BoundingBox bbox = _actions->bbox(); // bounding box derived from the DVI commands executed
+ if (_bboxFormatString == "min" || _bboxFormatString == "preview" || _bboxFormatString == "papersize") {
+ bbox.unlock();
+ bbox.transform(getPageTransformation());
+ }
+ else if (_bboxFormatString == "dvi") {
+ // center page content
+ double dx = (_pageWidth-bbox.width())/2;
+ double dy = (_pageHeight-bbox.height())/2;
+ bbox += BoundingBox(-dx, -dy, dx, dy);
+ }
+ else if (_bboxFormatString != "none") {
+ istringstream iss(_bboxFormatString);
+ StreamInputReader ir(iss);
+ ir.skipSpace();
+ if (isalpha(ir.peek())) {
+ // set explicitly given page format
+ PageSize size(_bboxFormatString);
+ if (size.valid()) {
+ // convention: DVI position (0,0) equals (1in, 1in) relative
+ // to the upper left vertex of the page (see DVI specification)
+ const double border = -72;
+ bbox = BoundingBox(border, border, size.width().bp()+border, size.height().bp()+border);
+ }
+ }
+ else { // set/modify bounding box by explicitly given values
+ try {
+ vector<Length> lengths = BoundingBox::extractLengths(_bboxFormatString);
+ if (lengths.size() == 1 || lengths.size() == 2) { // relative box size?
+ // apply the page transformation and adjust the bbox afterwards
+ bbox.transform(getPageTransformation());
+ }
+ bbox.set(lengths);
+ }
+ catch (const MessageException &e) {
+ }
+ }
+ }
+ if (bbox.width() == 0)
+ Message::wstream(false) << "\npage is empty\n";
+ if (_bboxFormatString != "none") {
+ _svg.setBBox(bbox);
+ const double bp2pt = 72.27/72;
+ const double bp2mm = 25.4/72;
+ Message::mstream(false) << '\n';
+ 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)";
+ Message::mstream(false) << '\n';
+ }
+}
+
+
+Matrix DVIToSVG::getPageTransformation () const {
+ Matrix matrix(1); // unity matrix
+ if (!_transCmds.empty()) {
+ Calculator calc;
+ if (_actions) {
+ const double bp2pt = (1_bp).pt();
+ BoundingBox &bbox = _actions->bbox();
+ 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;
+}
+
+
+static void collect_chars (unordered_map<const Font*, set<int>> &fontmap) {
+ for (const auto &entry : fontmap) {
+ if (entry.first->uniqueFont() != entry.first) {
+ for (int c : entry.second)
+ fontmap[entry.first->uniqueFont()].insert(c);
+ }
+ }
+}
+
+
+/** Adds the font information to the SVG tree.
+ * @param[in] svgElement the font nodes are added to this node */
+void DVIToSVG::embedFonts (XMLElement *svgElement) {
+ if (!svgElement || !_actions) // no dvi actions => no chars written => no fonts to embed
+ return;
+
+ const DVIToSVGActions *svgActions = static_cast<DVIToSVGActions*>(_actions.get());
+ auto &usedCharsMap = svgActions->getUsedChars();
+
+ collect_chars(usedCharsMap);
+
+ GlyphTracerMessages messages;
+ unordered_set<const Font*> tracedFonts; // collect unique fonts already traced
+ for (const auto &fontchar : usedCharsMap) {
+ const Font *font = fontchar.first;
+ if (auto ph_font = dynamic_cast<const PhysicalFont*>(font)) {
+ // Check if glyphs should be traced. Only trace the glyphs of unique fonts, i.e.
+ // avoid retracing the same glyphs again if they are referenced in various sizes.
+ if (TRACE_MODE != 0 && tracedFonts.find(ph_font->uniqueFont()) == tracedFonts.end()) {
+ ph_font->traceAllGlyphs(TRACE_MODE == 'a', &messages);
+ tracedFonts.insert(ph_font->uniqueFont());
+ }
+ if (font->path()) // does font file exist?
+ _svg.append(*ph_font, fontchar.second, &messages);
+ else
+ Message::wstream(true) << "can't embed font '" << font->name() << "'\n";
+ }
+ else
+ Message::wstream(true) << "can't embed font '" << font->name() << "'\n";
+ }
+ _svg.appendFontStyles(svgActions->getUsedFonts());
+}
+
+
+/** Enables or disables processing of specials. If ignorelist == 0, all
+ * supported special handlers are loaded. To disable selected sets of specials,
+ * the corresponding prefixes can be given separated by non alpha-numeric characters,
+ * e.g. "color, ps, em" or "color: ps em" etc.
+ * A single "*" in the ignore list disables all specials.
+ * @param[in] ignorelist list of special prefixes to ignore
+ * @param[in] pswarning if true, shows warning about disabled PS support
+ * @return the SpecialManager that handles special statements */
+void DVIToSVG::setProcessSpecials (const char *ignorelist, bool pswarning) {
+ if (ignorelist && strcmp(ignorelist, "*") == 0) // ignore all specials?
+ SpecialManager::instance().unregisterHandlers();
+ else {
+ // add special handlers
+ vector<unique_ptr<SpecialHandler>> handlers;
+ handlers.emplace_back(util::make_unique<BgColorSpecialHandler>()); // handles background color special
+ handlers.emplace_back(util::make_unique<ColorSpecialHandler>()); // handles color specials
+ handlers.emplace_back(util::make_unique<DvisvgmSpecialHandler>()); // handles raw SVG embeddings
+ handlers.emplace_back(util::make_unique<EmSpecialHandler>()); // handles emTeX specials
+ handlers.emplace_back(util::make_unique<HtmlSpecialHandler>()); // handles hyperref specials
+ handlers.emplace_back(util::make_unique<PapersizeSpecialHandler>()); // handles papersize special
+ handlers.emplace_back(util::make_unique<PdfSpecialHandler>()); // handles pdf specials
+ handlers.emplace_back(util::make_unique<TpicSpecialHandler>()); // handles tpic specials
+#ifndef DISABLE_GS
+ if (Ghostscript().available())
+ handlers.emplace_back(util::make_unique<PsSpecialHandler>()); // handles PostScript specials
+ else
+#endif
+ {
+#ifndef HAVE_LIBGS
+ // dummy PS special handler that only prints warning messages
+ handlers.emplace_back(util::make_unique<NoPsSpecialHandler>());
+ if (pswarning) {
+#ifdef DISABLE_GS
+ Message::wstream() << "processing of PostScript specials has been disabled permanently\n";
+#else
+ Message::wstream() << "processing of PostScript specials is disabled (Ghostscript not found)\n";
+#endif
+ }
+#endif
+ }
+ SpecialManager::instance().unregisterHandlers();
+ SpecialManager::instance().registerHandlers(handlers, ignorelist);
+ }
+}
+
+
+string DVIToSVG::getSVGFilename (unsigned pageno) const {
+ return _out.filename(pageno, numberOfPages());
+}
+
+
+void DVIToSVG::moveRight (double dx, MoveMode mode) {
+ DVIReader::moveRight(dx, mode);
+ if (_actions) {
+ if (dviState().d == WritingMode::LR)
+ _actions->moveToX(dviState().h+_tx, mode == MoveMode::CHANGEPOS);
+ else
+ _actions->moveToY(dviState().v+_ty, mode == MoveMode::CHANGEPOS);
+ }
+}
+
+
+void DVIToSVG::moveDown (double dy, MoveMode mode) {
+ DVIReader::moveDown(dy, mode);
+ if (_actions) {
+ if (dviState().d == WritingMode::LR)
+ _actions->moveToY(dviState().v+_ty, mode == MoveMode::CHANGEPOS);
+ else
+ _actions->moveToX(dviState().h+_tx, mode == MoveMode::CHANGEPOS);
+ }
+}
+
+
+void DVIToSVG::dviPost (uint16_t, uint16_t pages, uint32_t pw, uint32_t ph, uint32_t, uint32_t, uint32_t, uint32_t) {
+ _pageHeight = ph; // height of tallest page in dvi units
+ _pageWidth = pw; // width of widest page in dvi units
+}
+
+
+void DVIToSVG::dviBop (const std::vector<int32_t> &c, int32_t) {
+ _pageByte = 0;
+ enterBeginPage(currentPageNumber(), c);
+ if (_actions) {
+ _actions->progress(0, 1); // ensure that progress is called at 0%
+ _actions->beginPage(currentPageNumber(), c);
+ }
+}
+
+
+void DVIToSVG::dviEop () {
+ if (_actions) {
+ _actions->endPage(currentPageNumber());
+ _pageByte = numberOfPageBytes(currentPageNumber()-1);
+ _actions->progress(_pageByte, _pageByte); // ensure that progress is called at 100%
+ }
+ leaveEndPage(currentPageNumber());
+}
+
+
+void DVIToSVG::dviSetChar0 (uint32_t c, const Font *font) {
+ if (_actions && !dynamic_cast<const VirtualFont*>(font))
+ _actions->setChar(dviState().h+_tx, dviState().v+_ty, c, dviState().d != WritingMode::LR, *font);
+}
+
+
+void DVIToSVG::dviSetChar (uint32_t c, const Font *font) {
+ dviSetChar0(c, font);
+}
+
+
+void DVIToSVG::dviPutChar (uint32_t c, const Font *font) {
+ dviSetChar0(c, font);
+}
+
+
+void DVIToSVG::dviSetRule (double height, double width) {
+ if (_actions && height > 0 && width > 0)
+ _actions->setRule(dviState().h+_tx, dviState().v+_ty, height, width);
+}
+
+
+void DVIToSVG::dviPutRule (double height, double width) {
+ dviSetRule(height, width);
+}
+
+
+void DVIToSVG::dviPop () {
+ if (_actions) {
+ if (_prevXPos != dviState().h+_tx)
+ _actions->moveToX(dviState().h + _tx, true); // force setting the SVG position
+ if (_prevYPos != dviState().v+_ty)
+ _actions->moveToY(dviState().v + _ty, true); // force setting the SVG position
+ if (_prevWritingMode != dviState().d)
+ _actions->setTextOrientation(dviState().d != WritingMode::LR);
+ }
+}
+
+
+void DVIToSVG::dviFontNum (uint32_t fontnum, SetFontMode, const Font *font) {
+ if (_actions && font && !dynamic_cast<const VirtualFont*>(font))
+ _actions->setFont(FontManager::instance().fontID(fontnum), *font); // all fonts get a recomputed ID
+}
+
+
+void DVIToSVG::dviDir (WritingMode dir) {
+ if (_actions)
+ _actions->setTextOrientation(dir != WritingMode::LR);
+}
+
+
+void DVIToSVG::dviXXX (const std::string &str) {
+ if (_actions)
+ _actions->special(str, dvi2bp());
+}
+
+
+void DVIToSVG::dviXGlyphArray (std::vector<double> &dx, vector<double> &dy, vector<uint16_t> &glyphs, const Font &font) {
+ if (_actions) {
+ for (size_t i=0; i < glyphs.size(); i++)
+ _actions->setChar(dviState().h+dx[i]+_tx, dviState().v+dy[i]+_ty, glyphs[i], false, font);
+ }
+}
+
+
+void DVIToSVG::dviXGlyphString (vector<double> &dx, vector<uint16_t> &glyphs, const Font &font) {
+ if (_actions) {
+ for (size_t i=0; i < glyphs.size(); i++)
+ _actions->setChar(dviState().h+dx[i]+_tx, dviState().v+_ty, glyphs[i], false, font);
+ }
+}
+
+
+void DVIToSVG::dviXTextAndGlyphs (vector<double> &dx, vector<double> &dy, vector<uint16_t>&, vector<uint16_t> &glyphs, const Font &font) {
+ dviXGlyphArray(dx, dy, glyphs, font);
+}
+
+///////////////////////////////////////////////////////////////
+
+/** Parses a string consisting of comma-separated words, and assigns
+ * the values to the hash settings. */
+void DVIToSVG::HashSettings::setParameters (const string &paramstr) {
+ auto paramnames = util::split(paramstr, ",");
+ map<string, Parameter> paramMap = {
+ {"list", P_LIST},
+ {"replace", P_REPLACE}
+ };
+ for (string &name : paramnames) {
+ name = util::trim(name);
+ auto it = paramMap.find(name);
+ if (it != paramMap.end())
+ _params.insert(it->second);
+ else if (_algo.empty() && HashFunction::isSupportedAlgorithm(name))
+ _algo = name;
+ }
+ // set default hash algorithm if none is given
+ if (_algo.empty())
+ _algo = "xxh64";
+}