/************************************************************************* ** FontEngine.cpp ** ** ** ** This file is part of dvisvgm -- a fast DVI to SVG converter ** ** Copyright (C) 2005-2025 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 FT_ADVANCES_H #include FT_GLYPH_H #include FT_OUTLINE_H #include FT_TRUETYPE_TABLES_H #include FT_TYPES_H #include "Font.hpp" #include "FontEngine.hpp" #include "FontStyle.hpp" #include "fonts/Base14Fonts.hpp" #include "Message.hpp" #include "Unicode.hpp" #include "utility.hpp" using namespace std; /** Converts a floating point value to a 16.16 fixed point value. */ static inline FT_Fixed to_16dot16 (double val) { return static_cast(lround(val*65536.0)); } /** Converts an integer to a 16.16 fixed point value. */ static inline FT_Fixed to_16dot16 (int val) { return static_cast(val) << 16; } /////////////////////////////////////////////////////////////////////////// FontEngine::FontEngine () { if (FT_Init_FreeType(&_library)) Message::estream(true) << "failed to initialize FreeType library\n"; } FontEngine::~FontEngine () { if (_currentFace && FT_Done_Face(_currentFace)) Message::estream(true) << "failed to release font\n"; if (FT_Done_FreeType(_library)) Message::estream(true) << "failed to release FreeType library\n"; } /** Returns the singleton instance of this class. */ FontEngine& FontEngine::instance () { static FontEngine engine; return engine; } string FontEngine::version () { FT_Int major, minor, patch; FT_Library &lib = instance()._library; FT_Library_Version(lib, &major, &minor, &patch); ostringstream oss; oss << major << '.' << minor << '.' << patch; return oss.str(); } /** Sets the font to be used. * @param[in] fname path to font file * @param[in] fontindex index of font in font collection (multi-font files, like TTC) * @param[in] charMapID character map ID to assign * @return true on success */ bool FontEngine::setFont (const string &fname, int fontindex, const CharMapID &charMapID) { if (_currentFace && FT_Done_Face(_currentFace)) Message::estream(true) << "failed to release font\n"; if (fname.size() <= 6 || fname.substr(0, 6) == "sys://") { if (const MemoryFontData *data = find_base14_font(fname.substr(6))) { FT_Open_Args args; args.flags = FT_OPEN_MEMORY; args.memory_base = reinterpret_cast(data->data); args.memory_size = FT_Long(data->size); if (FT_Open_Face(_library, &args, fontindex, &_currentFace)) { Message::estream(true) << "can't read memory font " << fname << '\n'; return false; } } } else if (FT_New_Face(_library, fname.c_str(), fontindex, &_currentFace)) { Message::estream(true) << "can't read font file " << fname << '\n'; return false; } if (charMapID.valid()) setCharMap(charMapID); return true; } bool FontEngine::setFont (const Font &font) { if (_currentFont && _currentFont->name() == font.name()) return true; if (const char *path=font.path()) { auto pf = font_cast(&font); if (setFont(path, font.fontIndex(), pf ? pf->getCharMapID() : CharMapID())) { _currentFont = &font; return true; } } return false; } bool FontEngine::isCIDFont() const { FT_Bool cid_keyed; return _currentFace && FT_Get_CID_Is_Internally_CID_Keyed(_currentFace, &cid_keyed) == 0 && cid_keyed; } /** Returns true if the current font contains vertical layout data. */ bool FontEngine::hasVerticalMetrics () const { return _currentFace && FT_HAS_VERTICAL(_currentFace); } bool FontEngine::setCharMap (const CharMapID &charMapID) const { if (_currentFace) { for (int i = 0; i < _currentFace->num_charmaps; i++) { FT_CharMap ft_cmap = _currentFace->charmaps[i]; if (ft_cmap->platform_id == charMapID.platform_id && ft_cmap->encoding_id == charMapID.encoding_id) { FT_Set_Charmap(_currentFace, ft_cmap); return true; } } } return false; } /** Returns a character map that maps from glyph indexes to Unicode code points */ RangeMap FontEngine::buildGidToUnicodeMap () const { RangeMap charmap; if (!_currentFace) return charmap; FT_CharMap ft_cmap = _currentFace->charmap; if (FT_Select_Charmap(_currentFace, FT_ENCODING_UNICODE) == 0) { FT_UInt gid; // index of current glyph uint32_t charcode = FT_Get_First_Char(_currentFace, &gid); while (gid) { if (!charmap.valueExists(gid)) charmap.addRange(gid, gid, charcode); charcode = FT_Get_Next_Char(_currentFace, charcode, &gid); } FT_Set_Charmap(_currentFace, ft_cmap); } // In case the Unicode map of the font doesn't cover all characters, some // of them could still be identified by their names if present in the font. if (FT_HAS_GLYPH_NAMES(_currentFace)) { NumericRanges usedCodepoints; for (size_t i=0; i < charmap.numRanges(); i++) usedCodepoints.addRange(charmap.getRange(i).minval(), charmap.getRange(i).maxval()); if (charmap.empty()) addCharsByGlyphNames(1, getNumGlyphs(), charmap, usedCodepoints); else { addCharsByGlyphNames(1, charmap.minKey()-1, charmap, usedCodepoints); for (size_t i=1; i < charmap.numRanges(); i++) addCharsByGlyphNames(charmap.getRange(i-1).max()+1, charmap.getRange(i).min()-1, charmap, usedCodepoints); addCharsByGlyphNames(charmap.maxKey()+1, getNumGlyphs(), charmap, usedCodepoints); } } return charmap; } /** Looks for known glyph names in a given GID range and adds the corresponding * GID->code point mapping to a character map if the code point is not already present. * @param[in] minGID minimum GID of range to iterate * @param[in] maxGID maximum GID of range to iterate * @param[in,out] charmap character map taking the new mappings * @param[in,out] ucp collection of code points already present in the character map */ void FontEngine::addCharsByGlyphNames (uint32_t minGID, uint32_t maxGID, RangeMap &charmap, CodepointRanges &ucp) const { for (uint32_t gid=minGID; gid <= maxGID; gid++) { char glyphName[64]; if (FT_Get_Glyph_Name(_currentFace, gid, glyphName, 64) == 0) { const int32_t cp = Unicode::aglNameToCodepoint(glyphName); if (cp && !ucp.valueExists(cp)) { charmap.addRange(gid, gid, cp); ucp.addRange(cp); } } } } /** Returns a map that maps the character codes of the currently selected char map to their glyph IDs. * @param[in] face font face to build the map for * @param[out] gids all GIDs mapped to a char code * @return char code to GID map based on the data present in the font */ static RangeMap build_charcode_to_gid_map (FT_Face face, set &gids) { RangeMap charmap; FT_UInt gid; // index of current glyph uint32_t charcode = FT_Get_First_Char(face, &gid); while (gid) { charmap.addRange(charcode, charcode, gid); gids.insert(gid); charcode = FT_Get_Next_Char(face, charcode, &gid); } return charmap; } /** Returns a map that maps specified glyph IDs to Unicode code points based on the data in the font. * GIDs covered by the font's Unicode map are removed from set 'codePoints', the unmapped GIDs stay * in the set. * @param[in] face font face to build the map for * @param[in,out] gids glyph IDs to process; GIDs present in the resulting map are removed from this set * @param[out] codePoints code points successfully assigned to a GID * @return GID to code point map based on the data present in the font */ static RangeMap build_gid_to_unicode_map (FT_Face face, set &gids, set &codePoints) { RangeMap gidToUnicodeMap; if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) == 0) { FT_UInt gid; // index of current glyph uint32_t cp = FT_Get_First_Char(face, &gid); // Unicode code point while (gid) { if (gids.find(gid) != gids.end()) { gidToUnicodeMap.addRange(gid, gid, cp); gids.erase(gid); codePoints.insert(cp); } cp = FT_Get_Next_Char(face, cp, &gid); } if (FT_HAS_GLYPH_NAMES(face)) { for (auto gidIt = gids.begin(); gidIt != gids.end();) { gid = *gidIt; cp = gidToUnicodeMap.valueAt(gid); if (!cp) { char glyphName[64]; if (FT_Get_Glyph_Name(face, gid, glyphName, 64) == 0) { cp = Unicode::aglNameToCodepoint(glyphName); if (cp && codePoints.find(cp) == codePoints.end()) gidToUnicodeMap.addRange(gid, gid, cp); else cp = 0; } } if (cp) { codePoints.insert(cp); gidIt = gids.erase(gidIt); } else ++gidIt; } } } return gidToUnicodeMap; } /** Creates a character map that maps from the custom character encoding to Unicode. * @return pointer to character map if it could be created, 0 otherwise */ unique_ptr FontEngine::createCustomToUnicodeMap () const { if (!_currentFace) return nullptr; auto charmap = util::make_unique(); FT_CharMap ftcharmap = _currentFace->charmap; if (FT_Select_Charmap(_currentFace, FT_ENCODING_ADOBE_CUSTOM) != 0) return nullptr; set assignedGIDs; // all GIDs assigned to a char code const RangeMap charCodeToGIDMap = build_charcode_to_gid_map(_currentFace, assignedGIDs); // get the code points for all GIDs assigned to custom character codes set assignedCodePoints; RangeMap gidToUnicodeMap = build_gid_to_unicode_map(_currentFace, assignedGIDs, assignedCodePoints); FT_Set_Charmap(_currentFace, ftcharmap); // reassign initially active character map // assign remaining GIDs to code points from a Private Use Area (PUA) const NumericRanges pua({{0xE000, 0xF8FF}, {0xF0000, 0xFFFFD}, {0x100000, 0x10FFFD}}); auto puaIt = pua.valueIterator(); for (const uint32_t gid : assignedGIDs) { for (; puaIt.valid(); ++puaIt) { if (assignedCodePoints.find(*puaIt) == assignedCodePoints.end()) { gidToUnicodeMap.addRange(gid, gid, *puaIt); assignedCodePoints.insert(*puaIt++); break; } } } assignedCodePoints.clear(); // build the resulting map via char code -> GID -> code point for (const auto entry : charCodeToGIDMap) { const uint32_t charCode = entry.first; const uint32_t gid = entry.second; const uint32_t cp = gidToUnicodeMap.valueAt(gid); charmap->addRange(charCode, charCode, cp); } return charmap->empty() ? nullptr : std::move(charmap); } const char* FontEngine::getFamilyName () const { return _currentFace ? _currentFace->family_name : nullptr; } const char* FontEngine::getStyleName () const { return _currentFace ? _currentFace->style_name : nullptr; } /** Returns the PS name of the current font. */ const char* FontEngine::getPSName () const { return _currentFace ? FT_Get_Postscript_Name(_currentFace) : nullptr; } /** Returns the PS name of a font given by a file. * @param[in] fname name/path of the font file * @return the PS name */ string FontEngine::getPSName (const string &fname) const { string psname; FT_Face face; if (FT_New_Face(_library, fname.c_str(), 0, &face) == 0) { if (const char *ptr = FT_Get_Postscript_Name(face)) psname = ptr; FT_Done_Face(face); } return psname; } int FontEngine::getUnitsPerEM () const { return _currentFace ? _currentFace->units_per_EM : 0; } /** Returns the ascender of the current font in font units. * The (usually) positive value denotes the maximum height * (extent above the baseline) of the font. */ int FontEngine::getAscender () const { return _currentFace ? _currentFace->ascender : 0; } /** Returns the descender of the current font in font units. * The (usually) positive value denotes the maximum depth * (extent below the baseline) of the font. */ int FontEngine::getDescender () const { return _currentFace ? -_currentFace->descender : 0; } int FontEngine::getHAdvance () const { if (_currentFace) { auto table = static_cast(FT_Get_Sfnt_Table(_currentFace, ft_sfnt_os2)); return table ? table->xAvgCharWidth : 0; } return 0; } /** Returns the horizontal advance width of a given character in font units. */ int FontEngine::getHAdvance (const Character &c) const { if (_currentFace) { FT_Fixed adv=0; FT_Get_Advance(_currentFace, charIndex(c), FT_LOAD_NO_SCALE, &adv); return adv; } return 0; } /** Returns the vertical advance width of a given character in font units. */ int FontEngine::getVAdvance (const Character &c) const { if (_currentFace) { FT_Fixed adv=0; auto flags = FT_LOAD_NO_SCALE; if (FT_HAS_VERTICAL(_currentFace)) flags |= FT_LOAD_VERTICAL_LAYOUT; FT_Get_Advance(_currentFace, charIndex(c), flags, &adv); return adv; } return 0; } int FontEngine::getWidth (const Character &c) const { if (_currentFace) { if (FT_Load_Glyph(_currentFace, charIndex(c), FT_LOAD_NO_SCALE) == 0) return _currentFace->glyph->metrics.width; } return 0; } int FontEngine::getHeight (const Character &c) const { if (_currentFace) { if (FT_Load_Glyph(_currentFace, charIndex(c), FT_LOAD_NO_SCALE) == 0) return _currentFace->glyph->metrics.horiBearingY; } return 0; } int FontEngine::getDepth (const Character &c) const { if (_currentFace) { if (FT_Load_Glyph(_currentFace, charIndex(c), FT_LOAD_NO_SCALE) == 0) return _currentFace->glyph->metrics.height - _currentFace->glyph->metrics.horiBearingY; } return 0; } int FontEngine::getCharIndexByGlyphName(const char *name) const { return _currentFace ? int(FT_Get_Name_Index(_currentFace, name)) : 0; } int FontEngine::charIndex (const Character &c) const { if (!_currentFace || !_currentFace->charmap) return c.type() == Character::NAME ? 0 : c.number(); switch (c.type()) { case Character::CHRCODE: return FT_Get_Char_Index(_currentFace, (FT_ULong)c.number()); case Character::NAME: return FT_Get_Name_Index(_currentFace, const_cast(c.name())); default: return c.number(); } } /** Returns the number of glyphs present in the current font face. */ int FontEngine::getNumGlyphs () const { return _currentFace ? _currentFace->num_glyphs : 0; } /** Returns the glyph name for a given character code. * @param[in] c char code * @return glyph name */ string FontEngine::getGlyphName (const Character &c) const { string ret; if (c.type() == Character::NAME) ret = c.name(); else if (_currentFace && FT_HAS_GLYPH_NAMES(_currentFace)) { char buf[256]; FT_Get_Glyph_Name(_currentFace, charIndex(c), buf, 256); ret = string(buf); } return ret; } vector FontEngine::getPanose () const { vector panose(10); if (_currentFace) { auto table = static_cast(FT_Get_Sfnt_Table(_currentFace, ft_sfnt_os2)); if (table) for (int i=0; i < 10; i++) panose[i] = table->panose[i]; } return panose; } int FontEngine::getCharMapIDs (vector &charmapIDs) const { charmapIDs.clear(); if (_currentFace) { for (int i=0; i < _currentFace->num_charmaps; i++) { FT_CharMap charmap = _currentFace->charmaps[i]; charmapIDs.emplace_back(uint8_t(charmap->platform_id), uint8_t(charmap->encoding_id)); } } return charmapIDs.size(); } CharMapID FontEngine::setUnicodeCharMap () const { if (_currentFace && FT_Select_Charmap(_currentFace, FT_ENCODING_UNICODE) == 0) return {uint8_t(_currentFace->charmap->platform_id), uint8_t(_currentFace->charmap->encoding_id)}; return {}; } CharMapID FontEngine::setCustomCharMap () const { if (_currentFace && FT_Select_Charmap(_currentFace, FT_ENCODING_ADOBE_CUSTOM) == 0) return {uint8_t(_currentFace->charmap->platform_id), uint8_t(_currentFace->charmap->encoding_id)}; return {}; } // handle API change in freetype version 2.2.1 #if FREETYPE_MAJOR > 2 || (FREETYPE_MAJOR == 2 && (FREETYPE_MINOR > 2 || (FREETYPE_MINOR == 2 && FREETYPE_PATCH >= 1))) using FTVectorPtr = const FT_Vector*; #else using FTVectorPtr = FT_Vector*; #endif // Callback functions used by trace_outline/FT_Outline_Decompose static int moveto (FTVectorPtr to, void *user) { auto glyph = static_cast(user); glyph->moveto(to->x, to->y); return 0; } static int lineto (FTVectorPtr to, void *user) { auto glyph = static_cast(user); glyph->lineto(to->x, to->y); return 0; } static int quadto (FTVectorPtr control, FTVectorPtr to, void *user) { auto glyph = static_cast(user); glyph->quadto(control->x, control->y, to->x, to->y); return 0; } static int cubicto (FTVectorPtr control1, FTVectorPtr control2, FTVectorPtr to, void *user) { auto glyph = static_cast(user); glyph->cubicto(control1->x, control1->y, control2->x, control2->y, to->x, to->y); return 0; } /** Traces the outline of a glyph by calling the corresponding "drawing" functions. * Each glyph is composed of straight lines, quadratic or cubic Bézier curves. * This function creates a Glyph object representing these graphics segments. * @param[in] face FreeType object representing the font to scan * @param[in] font corresponding Font object providing additional data * @param[in] index index of the glyph to be traced * @param[out] glyph resulting Glyph object containing the graphics segments * @param[in] scale if true the current pt size will be considered otherwise the plain TrueType units are used. * @return false on errors */ static bool trace_outline (FT_Face face, const Font *font, int index, Glyph &glyph, bool scale) { if (face) { if (FT_Load_Glyph(face, index, scale ? FT_LOAD_DEFAULT : FT_LOAD_NO_SCALE)) { Message::estream(true) << "can't load glyph " << int(index) << '\n'; return false; } if (face->glyph->format != FT_GLYPH_FORMAT_OUTLINE) { Message::estream(true) << "no outlines found in glyph " << int(index) << '\n'; return false; } FT_Outline outline = face->glyph->outline; // apply style parameters if set if (font) { if (const FontStyle *style = font->style()) { FT_Matrix matrix = {to_16dot16(style->extend), to_16dot16(style->slant), 0, to_16dot16(1)}; FT_Outline_Transform(&outline, &matrix); if (style->bold != 0) FT_Outline_Embolden(&outline, style->bold/font->scaledSize()*face->units_per_EM); } } constexpr FT_Outline_Funcs funcs = {moveto, lineto, quadto, cubicto, 0, 0}; FT_Outline_Decompose(&outline, &funcs, &glyph); return true; } Message::wstream(true) << "can't trace outline (no font selected)\n"; return false; } /** Traces the outline of a glyph by calling the corresponding "drawing" functions. * Each glyph is composed of straight lines, quadratic or cubic Bézier curves. * This function creates a Glyph object representing these graphics segments. * @param[in] c the glyph of this character will be traced * @param[out] glyph resulting Glyph object containing the graphics segments * @param[in] scale if true the current pt size will be considered otherwise the plain TrueType units are used. * @return false on errors */ bool FontEngine::traceOutline (const Character &c, Glyph &glyph, bool scale) const { return trace_outline(_currentFace, _currentFont, charIndex(c), glyph, scale); }