/************************************************************************* ** FontMap.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 #include #include "CMap.hpp" #include "Directory.hpp" #include "FileFinder.hpp" #include "FontManager.hpp" #include "FontMap.hpp" #include "MapLine.hpp" #include "Message.hpp" #include "Subfont.hpp" #include "utility.hpp" using namespace std; /** Returns the singleton instance. */ FontMap& FontMap::instance() { static FontMap fontmap; return fontmap; } /** Reads and evaluates a single font map file. * @param[in] fname name of map file to read * @param[in] mode selects how to integrate the map file entries into the global map tree * @return true if file could be opened */ bool FontMap::read (const string &fname, FontMap::Mode mode) { ifstream ifs(fname); if (!ifs) return false; int line_number = 1; while (ifs) { int c = ifs.peek(); if (c < 0 || strchr("\n&#%;*", c)) // comment or empty line? ifs.ignore(numeric_limits::max(), '\n'); else { try { MapLine mapline(ifs); apply(mapline, mode); } catch (const MapLineException &e) { Message::wstream(true) << fname << ", line " << line_number << ": " << e.what() << '\n'; } catch (const SubfontException &e) { Message::wstream(true) << e.filename(); if (e.lineno() > 0) Message::wstream(false) << ", line " << e.lineno(); Message::wstream(false) << e.what() << '\n'; } } line_number++; } return true; } bool FontMap::read (const string &fname, char modechar) { Mode mode; switch (modechar) { case '=': mode = Mode::REPLACE; break; case '-': mode = Mode::REMOVE; break; default : mode = Mode::APPEND; } return read(fname, mode); } /** Applies a mapline according to the given mode (append, remove, replace). * @param[in] mapline the mapline to be applied * @param[in] mode mode to use * @return true in case of success */ bool FontMap::apply (const MapLine& mapline, FontMap::Mode mode) { switch (mode) { case Mode::APPEND: return append(mapline); case Mode::REMOVE: return remove(mapline); default: return replace(mapline); } } /** Applies a mapline according to the given mode (append, remove, replace). * @param[in] mapline the mapline to be applied * @param[in] modechar character that denotes the mode (+, -, or =) * @return true in case of success */ bool FontMap::apply (const MapLine& mapline, char modechar) { Mode mode; switch (modechar) { case '=': mode = Mode::REPLACE; break; case '-': mode = Mode::REMOVE; break; default : mode = Mode::APPEND; } return apply(mapline, mode); } /** Reads and evaluates a sequence of map files. Each map file is looked up in the local * directory and the TeX file tree. * @param[in] fname_seq comma-separated list of map file names * @return true if at least one of the given map files was found */ bool FontMap::read (const string &fname_seq) { bool found = false; string::size_type left=0; while (left < fname_seq.length()) { const char modechar = fname_seq[left]; if (strchr("+-=", modechar)) left++; string fname; auto right = fname_seq.find(',', left); if (right != string::npos) fname = fname_seq.substr(left, right-left); else { fname = fname_seq.substr(left); right = fname_seq.length(); } if (!fname.empty()) { if (!read(fname, modechar)) { if (const char *path = FileFinder::instance().lookup(fname, false)) found = found || read(path, modechar); else Message::wstream(true) << "map file " << fname << " not found\n"; } } left = right+1; } return found; } /** Appends given map line data to the font map if there is no entry for the corresponding * font in the map yet. * @param[in] mapline parsed font data * @return true if data has been appended */ bool FontMap::append (const MapLine &mapline) { bool appended = false; if (!mapline.texname().empty()) { if (!mapline.fontfname().empty() || !mapline.encname().empty()) { vector subfonts; if (mapline.sfd()) subfonts = mapline.sfd()->subfonts(); else subfonts.push_back(nullptr); for (Subfont *subfont : subfonts) { string fontname = mapline.texname()+(subfont ? subfont->id() : ""); auto it = _entries.find(fontname); if (it == _entries.end()) { _entries.emplace(fontname, util::make_unique(mapline, subfont)); appended = true; } } } } return appended; } /** Replaces the map data of the given font. * If the font is locked (because it's already in use) nothing happens. * @param[in] mapline parsed font data * @return true if data has been replaced */ bool FontMap::replace (const MapLine &mapline) { if (mapline.texname().empty()) return false; if (mapline.fontfname().empty() && mapline.encname().empty()) return remove(mapline); vector subfonts; if (mapline.sfd()) subfonts = mapline.sfd()->subfonts(); else subfonts.push_back(nullptr); for (Subfont *subfont : subfonts) { string fontname = mapline.texname()+(subfont ? subfont->id() : ""); auto it = _entries.find(fontname); if (it == _entries.end()) _entries.emplace(fontname, util::make_unique(mapline, subfont)); else if (!it->second->locked) *it->second = Entry(mapline, subfont); } return true; } /** Removes the map entry of the given font. * If the font is locked (because it's already in use) nothing happens. * @param[in] mapline parsed font data * @return true if entry has been removed */ bool FontMap::remove (const MapLine &mapline) { bool removed = false; if (!mapline.texname().empty()) { vector subfonts; if (mapline.sfd()) subfonts = mapline.sfd()->subfonts(); else subfonts.push_back(nullptr); for (const Subfont *subfont : subfonts) { string fontname = mapline.texname()+(subfont ? subfont->id() : ""); auto it = _entries.find(fontname); if (it != _entries.end() && !it->second->locked) { _entries.erase(it); removed = true; } } } return removed; } ostream& FontMap::write (ostream &os) const { for (const auto &entry : _entries) os << entry.first << " -> " << entry.second->fontname << " [" << entry.second->encname << "]\n"; return os; } /** Reads and evaluates all map files in the given directory. * @param[in] dirname path to directory containing the map files to be read */ void FontMap::readdir (const string &dirname) { Directory dir(dirname); while (const char *fname = dir.read(Directory::ET_FILE)) { if (strlen(fname) >= 4 && strcmp(fname+strlen(fname)-4, ".map") == 0) { string path = dirname + "/" + fname; read(path); } } } /** Returns name of font that is mapped to a given font. * @param[in] fontname name of font whose mapped name is retrieved * @returns name of mapped font */ const FontMap::Entry* FontMap::lookup (const string &fontname) const { auto it = _entries.find(fontname); if (it == _entries.end()) return nullptr; return it->second.get(); } /** Sets the lock flag for the given font in order to avoid changing the map data of this font. * @param[in] fontname name of font to be locked */ void FontMap::lockFont (const string& fontname) { auto it = _entries.find(fontname); if (it != _entries.end()) it->second->locked = true; } /** Removes all (unlocked) entries from the font map. * @param[in] unlocked_only if true, only unlocked entries are removed */ void FontMap::clear (bool unlocked_only) { if (!unlocked_only) _entries.clear(); else { auto it=_entries.begin(); while (it != _entries.end()) { if (it->second->locked) ++it; else it = _entries.erase(it); } } } ///////////////////////////////////////////////// FontMap::Entry::Entry (const MapLine &mapline, Subfont *sf) : fontname(mapline.fontfname()), encname(mapline.encname()), subfont(sf), fontindex(mapline.fontindex()), locked(false), style(mapline.bold(), mapline.extend(), mapline.slant()) { }