/************************************************************************* ** Font.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 "CMap.hpp" #include "FileFinder.hpp" #include "FileSystem.hpp" #include "Font.hpp" #include "FontEngine.hpp" #include "Message.hpp" #include "MetafontWrapper.hpp" #include "SignalHandler.hpp" #include "Subfont.hpp" #include "Unicode.hpp" #include "utility.hpp" using namespace std; uint32_t Font::unicode (uint32_t c) const { return Unicode::charToCodepoint(c); } /** Returns the encoding object of this font which is asigned in a map file. * If there's no encoding assigned, the function returns 0. */ const FontEncoding* Font::encoding () const { if (const FontMap::Entry *entry = fontMapEntry()) return FontEncoding::encoding(entry->encname); return nullptr; } const FontMap::Entry* Font::fontMapEntry () const { string fontname = name(); auto pos = fontname.rfind('.'); if (pos != string::npos) fontname = fontname.substr(0, pos); // strip extension return FontMap::instance().lookup(fontname); } /** Compute the extents of a given glyph. * @param[in] c character code of glyph * @param[in] vertical true if is glyph is part of vertical aligned text * @param[out] metrics the resulting extents */ void Font::getGlyphMetrics (int c, bool vertical, GlyphMetrics &metrics) const { double s = scaleFactor(); if (vertical) { // get metrics for vertical text flow? if (verticalLayout()) { // is the font designed for vertical texts? metrics.wl = s*charDepth(c); metrics.wr = s*charHeight(c); metrics.h = 0; metrics.d = s*charWidth(c); } else { // rotate box by 90 degrees for alphabetic text metrics.wl = s*charDepth(c); metrics.wr = s*charHeight(c); metrics.h = 0; metrics.d = s*(charWidth(c)+italicCorr(c)); } } else { metrics.wl = 0; metrics.wr = s*(charWidth(c)+italicCorr(c)); metrics.h = s*charHeight(c); metrics.d = s*charDepth(c); } } const char* Font::filename () const { const char *fname = strrchr(path(), '/'); if (fname) return fname+1; return path(); } /////////////////////////////////////////////////////////////////////////////////////// TFMFont::TFMFont (string name, uint32_t cs, double ds, double ss) : _fontname(std::move(name)), _checksum(cs), _dsize(ds), _ssize(ss) { } /** Returns a font metrics object for the current font. * @throw FontException if TFM file can't be found */ const FontMetrics* TFMFont::getMetrics () const { if (!_metrics) { try { _metrics = FontMetrics::read(_fontname); if (!_metrics) { _metrics = util::make_unique(); Message::wstream(true) << "can't find "+_fontname+".tfm\n"; } } catch (FontMetricException &e) { const char *ext = (_metrics && _metrics->isOFM()) ? ".ofm" : ".tfm"; _metrics = util::make_unique(); Message::wstream(true) << e.what() << " in " << _fontname << ext << "\n"; } } return _metrics.get(); } double TFMFont::charWidth (int c) const { double w = getMetrics() ? getMetrics()->getCharWidth(c) : 0; if (style()) w *= style()->extend; return w; } double TFMFont::italicCorr (int c) const { double w = getMetrics() ? getMetrics()->getItalicCorr(c) : 0; if (style()) { w += abs(style()->slant*charHeight(c)); // slant := tan(phi) = dx/height w *= style()->extend; } return w; } double TFMFont::charDepth (int c) const {return getMetrics() ? getMetrics()->getCharDepth(c) : 0;} double TFMFont::charHeight (int c) const {return getMetrics() ? getMetrics()->getCharHeight(c) : 0;} /** Tests if the checksum of the font matches that of the corresponding TFM file. */ bool TFMFont::verifyChecksums () const { if (_checksum != 0 && getMetrics() && getMetrics()->getChecksum() != 0) return _checksum == getMetrics()->getChecksum(); return true; } ////////////////////////////////////////////////////////////////////////////// // static class variables bool PhysicalFont::EXACT_BBOX = false; bool PhysicalFont::KEEP_TEMP_FILES = false; string PhysicalFont::CACHE_PATH; double PhysicalFont::METAFONT_MAG = 4; FontCache PhysicalFont::_cache; unique_ptr PhysicalFont::create (const string &name, uint32_t checksum, double dsize, double ssize, PhysicalFont::Type type) { return unique_ptr(new PhysicalFontImpl(name, 0, checksum, dsize, ssize, type)); } unique_ptr PhysicalFont::create (const string &name, int fontindex, uint32_t checksum, double dsize, double ssize) { return unique_ptr(new PhysicalFontImpl(name, fontindex, checksum, dsize, ssize, PhysicalFont::Type::TTC)); } const char* PhysicalFont::path () const { const char *ext=nullptr; switch (type()) { case Type::OTF: ext = "otf"; break; case Type::PFB: ext = "pfb"; break; case Type::TTC: ext = "ttc"; break; case Type::TTF: ext = "ttf"; break; case Type::MF : ext = "mf"; break; default : ext = nullptr; } if (ext) return FileFinder::instance().lookup(name()+"."+ext); return FileFinder::instance().lookup(name()); } /** Returns true if this font is CID-based. */ bool PhysicalFont::isCIDFont () const { if (type() == Type::MF) return false; FontEngine::instance().setFont(*this); return FontEngine::instance().isCIDFont(); } /** Retrieve the IDs of all charachter maps available in the font file. * @param[out] charMapIDs IDs of the found character maps * @return number of found character maps */ int PhysicalFont::collectCharMapIDs (std::vector &charMapIDs) const { if (type() == Type::MF) return 0; FontEngine::instance().setFont(*this); return FontEngine::instance().getCharMapIDs(charMapIDs); } /** Decodes a character code used in the DVI file to the code required to * address the correct character in the font. * @param[in] c DVI character to decode * @return target character code or name */ Character PhysicalFont::decodeChar (uint32_t c) const { if (const FontEncoding *enc = encoding()) return enc->decode(c); return Character(Character::CHRCODE, c); } /** Returns the number of units per EM. The EM square is the virtual area a glyph is designed on. * All coordinates used to specify portions of the glyph are relative to the origin (0,0) at the * lower left corner of this square, while the upper right corner is located at (m,m), where m * is an integer value defined with the font, and returned by this function. */ int PhysicalFont::unitsPerEm() const { if (type() == Type::MF) return 1000; FontEngine::instance().setFont(*this); return FontEngine::instance().getUnitsPerEM(); } /** Returns the average horizontal advance width in font units. */ int PhysicalFont::hAverageAdvance () const { if (type() == Type::MF) return 0; FontEngine::instance().setFont(*this); return FontEngine::instance().getHAdvance(); } /** Returns the horizontal advance width of a character in font units. */ double PhysicalFont::hAdvance (int c) const { if (type() == Type::MF) return unitsPerEm()*charWidth(c)/designSize(); FontEngine::instance().setFont(*this); if (const FontMap::Entry *entry = fontMapEntry()) if (Subfont *sf = entry->subfont) c = sf->decode(c); return FontEngine::instance().getHAdvance(decodeChar(c)); } /** Returns the vertical advance width of a character in font units. */ double PhysicalFont::vAdvance (int c) const { if (type() == Type::MF) return unitsPerEm()*charWidth(c)/designSize(); FontEngine::instance().setFont(*this); if (const FontMap::Entry *entry = fontMapEntry()) if (Subfont *sf = entry->subfont) c = sf->decode(c); return FontEngine::instance().getVAdvance(decodeChar(c)); } string PhysicalFont::glyphName (int c) const { if (type() == Type::MF) return ""; FontEngine::instance().setFont(*this); if (const FontMap::Entry *entry = fontMapEntry()) if (Subfont *sf = entry->subfont) c = sf->decode(c); return FontEngine::instance().getGlyphName(decodeChar(c)); } double PhysicalFont::scaledAscent() const { return ascent()*scaledSize()/unitsPerEm(); } /** Returns the unscaled ascender of the font in design units. * Positive values denote an extension above the baseline.'*/ int PhysicalFont::ascent () const { if (type() == Type::MF) return getMetrics() ? getMetrics()->getAscent()*unitsPerEm()/getMetrics()->getQuad() : 0; FontEngine::instance().setFont(*this); return FontEngine::instance().getAscender(); } /** Returns the unscaled descender of the font in design units. * Positive values denote an extension below the baseline. */ int PhysicalFont::descent () const { if (type() == Type::MF) return getMetrics() ? getMetrics()->getDescent()*unitsPerEm()/getMetrics()->getQuad() : 0; FontEngine::instance().setFont(*this); return FontEngine::instance().getDescender(); } std::string PhysicalFont::familyName () const { if (type() == Type::MF) return ""; FontEngine::instance().setFont(*this); const char *family = FontEngine::instance().getFamilyName(); return family ? family : ""; } std::string PhysicalFont::styleName () const { if (type() == Type::MF) return ""; FontEngine::instance().setFont(*this); const char *style = FontEngine::instance().getStyleName(); return style ? style : ""; } int PhysicalFont::charIndexByName(const string &charname) const { if (charname.empty()) return 0; FontEngine::instance().setFont(*this); return FontEngine::instance().getCharIndexByGlyphName(charname.c_str()); } /** Extracts the glyph outlines of a given character. * @param[in] c character code of requested glyph * @param[out] glyph path segments of the glyph outline * @param[in] callback optional callback object for tracer class * @return true if outline could be computed */ bool PhysicalFont::getGlyph (int c, GraphicsPath &glyph, GFGlyphTracer::Callback *callback) const { if (type() == Type::MF) { const Glyph *cached_glyph=nullptr; if (!CACHE_PATH.empty()) { _cache.write(CACHE_PATH); _cache.read(name(), CACHE_PATH); cached_glyph = _cache.getGlyph(c); } if (cached_glyph) { glyph = *cached_glyph; return true; } else { string gfname; if (createGF(gfname)) { try { double ds = getMetrics() ? getMetrics()->getDesignSize() : 1; GFGlyphTracer tracer(gfname, unitsPerEm()/ds, callback); tracer.setGlyph(glyph); tracer.executeChar(c); glyph.closeOpenSubPaths(); if (!CACHE_PATH.empty()) _cache.setGlyph(c, glyph); return true; } catch (GFException &e) { // @@ print error message } } } } else { // vector fonts (OTF, PFB, TTF, TTC) bool ok=true; FontEngine::instance().setFont(*this); if (const FontMap::Entry *entry = fontMapEntry()) if (Subfont *sf = entry->subfont) c = sf->decode(c); ok = FontEngine::instance().traceOutline(decodeChar(c), glyph, false); glyph.closeOpenSubPaths(); return ok; } return false; } /** Creates a GF file for this font object. * @param[out] gfname name of the generated GF font file * @return true on success */ bool PhysicalFont::createGF (string &gfname) const { static set failed_fonts; if (failed_fonts.find(name()) == failed_fonts.end()) { SignalHandler::instance().check(); gfname = FileSystem::tmpdir()+name()+".gf"; MetafontWrapper mf(name(), FileSystem::tmpdir()); bool ok = mf.make("ljfour", METAFONT_MAG); // call Metafont if necessary if (ok && mf.success() && getMetrics()) return true; // report failure only once failed_fonts.insert(name()); Message::wstream(true) << "failed to create " << name() << ".gf\n"; } return false; } /** Traces all glyphs of the current font and stores them in the cache. If caching is disabled, nothing happens. * @param[in] includeCached if true, glyphs already cached are traced again * @param[in] cb optional callback methods called by the tracer * @return number of glyphs traced */ int PhysicalFont::traceAllGlyphs (bool includeCached, GFGlyphTracer::Callback *cb) const { int count = 0; if (type() == Type::MF && !CACHE_PATH.empty()) { if (const FontMetrics *metrics = getMetrics()) { int fchar = metrics->firstChar(); int lchar = metrics->lastChar(); string gfname; Glyph glyph; if (createGF(gfname)) { _cache.read(name(), CACHE_PATH); double ds = getMetrics() ? getMetrics()->getDesignSize() : 1; GFGlyphTracer tracer(gfname, unitsPerEm()/ds, cb); tracer.setGlyph(glyph); for (int i=fchar; i <= lchar; i++) { if (includeCached || !_cache.getGlyph(i)) { glyph.clear(); tracer.executeChar(i); glyph.closeOpenSubPaths(); _cache.setGlyph(i, glyph); ++count; } } _cache.write(CACHE_PATH); } } } return count; } /** Computes the exact bounding box of a glyph. * @param[in] c character code of the glyph * @param[out] bbox the computed bounding box * @param[in] cb optional calback object forwarded to the tracer * @return true if the box could be computed successfully */ bool PhysicalFont::getExactGlyphBox (int c, BoundingBox& bbox, GFGlyphTracer::Callback* cb) const { Glyph glyph; if (getGlyph(c, glyph, cb)) { bbox = glyph.computeBBox(); double s = scaledSize()/unitsPerEm(); bbox.scale(s, s); return true; } return false; } bool PhysicalFont::getExactGlyphBox (int c, GlyphMetrics &metrics, bool vertical, GFGlyphTracer::Callback *cb) const { BoundingBox charbox; if (!getExactGlyphBox(c, charbox, cb)) return false; metrics.wl = -charbox.minX(); metrics.wr = charbox.maxX(); metrics.h = charbox.maxY(); metrics.d = -charbox.minY(); if (vertical) { // vertical text orientation if (verticalLayout()) { // font designed for vertical layout? double wl = max(0.0, metrics.wl); double wr = max(0.0, metrics.wr); double h = max(0.0, metrics.h); metrics.wl = metrics.wr = (wl+wr)/2; metrics.d += h; metrics.h = 0; } else { double depth = metrics.d; metrics.d = metrics.wr; metrics.wr = metrics.h; metrics.h = metrics.wl; metrics.wl = depth; } } return true; } unique_ptr VirtualFont::create (const string &name, uint32_t checksum, double dsize, double ssize) { return unique_ptr(new VirtualFontImpl(name, checksum, dsize, ssize)); } ////////////////////////////////////////////////////////////////////////////// PhysicalFontImpl::PhysicalFontImpl (const string &name, int fontindex, uint32_t cs, double ds, double ss, PhysicalFont::Type type) : TFMFont(name, cs, ds, ss), _filetype(type), _fontIndex(fontindex), _encodingPair(Font::encoding()) { } PhysicalFontImpl::~PhysicalFontImpl () { if (!CACHE_PATH.empty()) _cache.write(CACHE_PATH); if (!KEEP_TEMP_FILES) tidy(); } const FontEncoding* PhysicalFontImpl::encoding () const { if (!_encodingPair.enc1()) return nullptr; return &_encodingPair; } bool PhysicalFontImpl::findAndAssignBaseFontMap () { const FontEncoding *enc = encoding(); if (enc && !enc->mapsToUnicode() && enc->mapsToCharIndex()) { // try to find a base font map that maps from character indexes to a suitable // target encoding supported by the font file if (const FontEncoding *bfmap = enc->findCompatibleBaseFontMap(this, _charmapID)) _encodingPair.assign(bfmap); else return false; } else if (type() != Type::MF) { FontEngine::instance().setFont(*this); _localCharMap = FontEngine::instance().createCustomToUnicodeMap(); if (_localCharMap) _charmapID = FontEngine::instance().setCustomCharMap(); else _charmapID = FontEngine::instance().setUnicodeCharMap(); } return true; } /** Returns the Unicode point for a given DVI character. */ uint32_t PhysicalFontImpl::unicode (uint32_t c) const { if (type() == Type::MF) return Font::unicode(c); Character chr = decodeChar(c); if (type() == Type::PFB) { // try to get the Unicode point from the character name string glyphname = glyphName(c); uint32_t codepoint; if (!glyphname.empty() && (codepoint = Unicode::aglNameToCodepoint(glyphname)) != 0) return codepoint; if (c <= 0x1900) // does character code c fit into Private Use Zone U+E000? return 0xe000+c; // Message::wstream() << "can't properly map PS character '" << glyphname << "' (0x" << hex << c << ") to Unicode\n"; // If we get here, there is no easy mapping. As for now, we use the character code as Unicode point. // Although quite unlikely, it might collide with properly mapped characters. return Unicode::charToCodepoint(c); } if (chr.type() == Character::NAME || chr.number() == 0) return Unicode::charToCodepoint(chr.number()); if (_localCharMap) { if (uint32_t mapped_char = _localCharMap->valueAt(chr.number())) return mapped_char; } // No Unicode equivalent found in the font file. // Now we should look for a smart alternative but at the moment // it's sufficient to simply choose a valid unused codepoint. return Unicode::charToCodepoint(chr.number()); } const FontStyle* PhysicalFontImpl::style () const { if (auto entry = fontMapEntry()) return &entry->style; return nullptr; } /** Delete all temporary font files created by Metafont. */ void PhysicalFontImpl::tidy () const { if (type() == Type::MF) { string fname = FileSystem::tmpdir()+name(); for (const char *ext : {"gf", "tfm", "log"}) { if (FileSystem::exists(fname+"."+ext)) FileSystem::remove(fname+"."+ext); } } } ////////////////////////////////////////////////////////////////////////////// string NativeFont::uniqueName (const string &path, const FontStyle &style) { static unordered_map ids; ostringstream oss; oss << path << "b" << style.bold << "e" << style.extend << "s" << style.slant; auto it = ids.find(oss.str()); int id = ids.size(); if (it == ids.end()) ids[oss.str()] = id; else id = it->second; oss.str(""); oss << "nf" << id; return oss.str(); } string NativeFont::name () const { return uniqueName(path(), _style); } PhysicalFont::Type NativeFont::type () const { if (const char *filepath = path()) { if (const char *p =strrchr(filepath, '.')) { string ext = p+1; if (ext == "otf") return PhysicalFont::Type::OTF; if (ext == "ttf") return PhysicalFont::Type::TTF; if (ext == "pfb" || ext == "pfa") return PhysicalFont::Type::PFB; } } return PhysicalFont::Type::UNKNOWN; } double NativeFont::charWidth (int c) const { FontEngine::instance().setFont(*this); int upem = FontEngine::instance().getUnitsPerEM(); return upem ? (scaledSize()*FontEngine::instance().getHAdvance(decodeChar(c))/upem*_style.extend) : 0; } double NativeFont::italicCorr(int c) const { double w = abs(_style.slant*charHeight(c)); // slant := tan(phi) = dx/height w *= _style.extend; return w; } double NativeFont::charHeight (int c) const { FontEngine::instance().setFont(*this); int upem = FontEngine::instance().getUnitsPerEM(); return upem ? (scaledSize()*FontEngine::instance().getHeight(decodeChar(c))/upem) : 0; } double NativeFont::charDepth (int c) const { FontEngine::instance().setFont(*this); int upem = FontEngine::instance().getUnitsPerEM(); return upem ? (scaledSize()*FontEngine::instance().getDepth(decodeChar(c))/upem) : 0; } double NativeFont::hAdvance (Character c) const { FontEngine::instance().setFont(*this); int upem = FontEngine::instance().getUnitsPerEM(); return upem ? (scaledSize()*FontEngine::instance().getHAdvance(c)/upem) : 0; } double NativeFont::vAdvance (Character c) const { FontEngine::instance().setFont(*this); int upem = FontEngine::instance().getUnitsPerEM(); return upem ? (scaledSize()*FontEngine::instance().getVAdvance(c)/upem) : 0; } ////////////////////////////////////////////////////////////////////////////// NativeFontImpl::NativeFontImpl (string fname, string fontname, double ptsize) : NativeFont(ptsize, FontStyle(), Color::BLACK), _path(std::move(fname)), _name(std::move(fontname)) { // plus characters (as appended to subset prefixes) are not allowed in font/family names std::replace(_name.begin(), _name.end(), '+', '-'); } bool NativeFontImpl::findAndAssignBaseFontMap () { FontEngine &fe = FontEngine::instance(); fe.setFont(*this); fe.setUnicodeCharMap(); fe.buildGidToCharCodeMap(_toUnicodeMap); if (!_toUnicodeMap.addMissingMappings(fe.getNumGlyphs())) Message::wstream(true) << "incomplete Unicode mapping for native font " << name() << " (" << filename() << ")\n"; return true; } Character NativeFontImpl::decodeChar (uint32_t c) const { return {Character::INDEX, c}; } uint32_t NativeFontImpl::unicode (uint32_t c) const { uint32_t ucode = _toUnicodeMap.valueAt(c); return Unicode::charToCodepoint(ucode, true); } void NativeFontImpl::mapCharToUnicode (uint32_t c, uint32_t codepoint) { _toUnicodeMap.addRange(c, c, codepoint); } bool NativeFontImpl::verticalLayout() const { FontEngine &fe = FontEngine::instance(); fe.setFont(*this); return fe.hasVerticalMetrics(); } ////////////////////////////////////////////////////////////////////////////// VirtualFontImpl::VirtualFontImpl (const string &name, uint32_t cs, double ds, double ss) : TFMFont(name, cs, ds, ss) { } const char* VirtualFontImpl::path () const { return FileFinder::instance().lookup(name()+".vf"); } void VirtualFontImpl::assignChar (uint32_t c, DVIVector &&dvi) { _charDefs.emplace(c, std::move(dvi)); } /** Returns the DVI sippet that describes a given character of the virtual font. * @param[in] c character code * @return pointer to vector of DVI commands, or 0 if character doesn't exist */ const VirtualFont::DVIVector* VirtualFontImpl::getDVI (int c) const { auto it = _charDefs.find(c); return (it == _charDefs.end() ? nullptr : &it->second); } ////////////////////////////////////////////////////////////////////////////// void PhysicalFont::visit (FontVisitor &visitor) {visitor.visited(this);} void VirtualFont::visit (FontVisitor &visitor) {visitor.visited(this);} void NativeFont::visit (FontVisitor &visitor) {visitor.visited(this);} void PhysicalFont::visit (FontVisitor &visitor) const {visitor.visited(this);} void VirtualFont::visit (FontVisitor &visitor) const {visitor.visited(this);} void NativeFont::visit (FontVisitor &visitor) const {visitor.visited(this);}