diff options
Diffstat (limited to 'src/gui/text/freetype')
-rw-r--r-- | src/gui/text/freetype/qfontengine_ft.cpp | 223 | ||||
-rw-r--r-- | src/gui/text/freetype/qfontengine_ft_p.h | 9 | ||||
-rw-r--r-- | src/gui/text/freetype/qfreetypefontdatabase.cpp | 165 | ||||
-rw-r--r-- | src/gui/text/freetype/qfreetypefontdatabase_p.h | 14 |
4 files changed, 349 insertions, 62 deletions
diff --git a/src/gui/text/freetype/qfontengine_ft.cpp b/src/gui/text/freetype/qfontengine_ft.cpp index a7892f95b6..d3791f1f6e 100644 --- a/src/gui/text/freetype/qfontengine_ft.cpp +++ b/src/gui/text/freetype/qfontengine_ft.cpp @@ -12,6 +12,7 @@ #include <qscreen.h> #include <qpa/qplatformscreen.h> #include <QtCore/QUuid> +#include <QtCore/QLoggingCategory> #include <QtGui/QPainterPath> #ifndef QT_NO_FREETYPE @@ -34,6 +35,7 @@ #include FT_GLYPH_H #include FT_MODULE_H #include FT_LCD_FILTER_H +#include FT_MULTIPLE_MASTERS_H #if defined(FT_CONFIG_OPTIONS_H) #include FT_CONFIG_OPTIONS_H @@ -53,6 +55,8 @@ QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcFontMatch) + using namespace Qt::StringLiterals; #define FLOOR(x) ((x) & -64) @@ -111,8 +115,11 @@ public: QtFreetypeData::~QtFreetypeData() { - for (QHash<QFontEngine::FaceId, QFreetypeFace *>::ConstIterator iter = faces.cbegin(); iter != faces.cend(); ++iter) + for (auto iter = faces.cbegin(); iter != faces.cend(); ++iter) { iter.value()->cleanup(); + if (!iter.value()->ref.deref()) + delete iter.value(); + } faces.clear(); FT_Done_FreeType(library); library = nullptr; @@ -183,10 +190,15 @@ int QFreetypeFace::getPointInOutline(glyph_t glyph, int flags, quint32 point, QF return Err_Ok; } +bool QFreetypeFace::isScalable() const +{ + return FT_IS_SCALABLE(face); +} + bool QFreetypeFace::isScalableBitmap() const { #ifdef FT_HAS_COLOR - return !FT_IS_SCALABLE(face) && FT_HAS_COLOR(face); + return !isScalable() && FT_HAS_COLOR(face); #else return false; #endif @@ -209,19 +221,37 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id, QtFreetypeData *freetypeData = qt_getFreetypeData(); - QFreetypeFace *freetype = freetypeData->faces.value(face_id, nullptr); - if (freetype) { - freetype->ref.ref(); - } else { + QFreetypeFace *freetype = nullptr; + auto it = freetypeData->faces.find(face_id); + if (it != freetypeData->faces.end()) { + freetype = *it; + + Q_ASSERT(freetype->ref.loadRelaxed() > 0); + if (freetype->ref.loadRelaxed() == 1) { + // If there is only one reference left to the face, it means it is only referenced by + // the cache itself, and thus it is in cleanup state (but the final outside reference + // was removed on a different thread so it could not be deleted right away). We then + // complete the cleanup and pretend we didn't find it, so that it can be re-created with + // the present state. + freetype->cleanup(); + freetypeData->faces.erase(it); + delete freetype; + freetype = nullptr; + } else { + freetype->ref.ref(); + } + } + + if (!freetype) { const auto deleter = [](QFreetypeFace *f) { delete f; }; std::unique_ptr<QFreetypeFace, decltype(deleter)> newFreetype(new QFreetypeFace, deleter); FT_Face face; if (!face_id.filename.isEmpty()) { QString fileName = QFile::decodeName(face_id.filename); - if (face_id.filename.startsWith(":qmemoryfonts/")) { + if (const char *prefix = ":qmemoryfonts/"; face_id.filename.startsWith(prefix)) { // from qfontdatabase.cpp QByteArray idx = face_id.filename; - idx.remove(0, 14); // remove ':qmemoryfonts/' + idx.remove(0, strlen(prefix)); // remove ':qmemoryfonts/' bool ok = false; newFreetype->fontData = qt_fontdata_from_index(idx.toInt(&ok)); if (!ok) @@ -243,7 +273,23 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id, } else if (FT_New_Face(freetypeData->library, face_id.filename, face_id.index, &face)) { return nullptr; } + +#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900 + if (face_id.instanceIndex >= 0) { + qCDebug(lcFontMatch) + << "Selecting named instance" << (face_id.instanceIndex) + << "in" << face_id.filename; + FT_Set_Named_Instance(face, face_id.instanceIndex + 1); + } +#endif newFreetype->face = face; + newFreetype->mm_var = nullptr; + if (FT_IS_NAMED_INSTANCE(newFreetype->face)) { + FT_Error ftresult; + ftresult = FT_Get_MM_Var(face, &newFreetype->mm_var); + if (ftresult != FT_Err_Ok) + newFreetype->mm_var = nullptr; + } newFreetype->ref.storeRelaxed(1); newFreetype->xsize = 0; @@ -282,6 +328,25 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id, FT_Set_Char_Size(face, newFreetype->face->available_sizes[0].x_ppem, newFreetype->face->available_sizes[0].y_ppem, 0, 0); FT_Set_Charmap(newFreetype->face, newFreetype->unicode_map); + + if (!face_id.variableAxes.isEmpty()) { + FT_MM_Var *var = nullptr; + FT_Get_MM_Var(newFreetype->face, &var); + if (var != nullptr) { + QVarLengthArray<FT_Fixed, 16> coords(var->num_axis); + FT_Get_Var_Design_Coordinates(face, var->num_axis, coords.data()); + for (FT_UInt i = 0; i < var->num_axis; ++i) { + if (const auto tag = QFont::Tag::fromValue(var->axis[i].tag)) { + const auto it = face_id.variableAxes.constFind(*tag); + if (it != face_id.variableAxes.constEnd()) + coords[i] = FT_Fixed(*it * 65536); + } + } + FT_Set_Var_Design_Coordinates(face, var->num_axis, coords.data()); + FT_Done_MM_Var(qt_getFreetype(), var); + } + } + QT_TRY { freetypeData->faces.insert(face_id, newFreetype.get()); } QT_CATCH(...) { @@ -290,6 +355,7 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id, QT_RETHROW; } freetype = newFreetype.release(); + freetype->ref.ref(); } return freetype; } @@ -297,30 +363,45 @@ QFreetypeFace *QFreetypeFace::getFace(const QFontEngine::FaceId &face_id, void QFreetypeFace::cleanup() { hbFace.reset(); + if (mm_var && face && face->glyph) + FT_Done_MM_Var(face->glyph->library, mm_var); + mm_var = nullptr; FT_Done_Face(face); face = nullptr; } void QFreetypeFace::release(const QFontEngine::FaceId &face_id) { - if (!ref.deref()) { - if (face) { - QtFreetypeData *freetypeData = qt_getFreetypeData(); - - cleanup(); - - auto it = freetypeData->faces.constFind(face_id); - if (it != freetypeData->faces.constEnd()) - freetypeData->faces.erase(it); - - if (freetypeData->faces.isEmpty()) { - FT_Done_FreeType(freetypeData->library); - freetypeData->library = nullptr; + Q_UNUSED(face_id); + bool deleteThis = !ref.deref(); + + // If the only reference left over is the cache's reference, we remove it from the cache, + // granted that we are on the correct thread. If not, we leave it there to be cleaned out + // later. While we are at it, we also purge all left over faces which are only referenced + // from the cache. + if (face && ref.loadRelaxed() == 1) { + QtFreetypeData *freetypeData = qt_getFreetypeData(); + for (auto it = freetypeData->faces.constBegin(); it != freetypeData->faces.constEnd(); ) { + if (it.value()->ref.loadRelaxed() == 1) { + it.value()->cleanup(); + if (it.value() == this) + deleteThis = true; // This face, delete at end of function for safety + else + delete it.value(); + it = freetypeData->faces.erase(it); + } else { + ++it; } } - delete this; + if (freetypeData->faces.isEmpty()) { + FT_Done_FreeType(freetypeData->library); + freetypeData->library = nullptr; + } } + + if (deleteThis) + delete this; } static int computeFaceIndex(const QString &faceFileName, const QString &styleName) @@ -339,12 +420,12 @@ static int computeFaceIndex(const QString &faceFileName, const QString &styleNam break; } - QString faceStyleName = QString::fromLatin1(face->style_name); + const bool found = QLatin1StringView(face->style_name) == styleName; numFaces = face->num_faces; FT_Done_Face(face); - if (faceStyleName == styleName) + if (found) return faceIndex; } while (++faceIndex < numFaces); @@ -674,27 +755,32 @@ namespace { fontDef.weight = QFont::Bold; } - bool initFromData(const QByteArray &fontData) + bool initFromData(const QByteArray &fontData, const QMap<QFont::Tag, float> &variableAxisValues) { FaceId faceId; faceId.filename = ""; faceId.index = 0; faceId.uuid = QUuid::createUuid().toByteArray(); + faceId.variableAxes = variableAxisValues; return init(faceId, true, Format_None, fontData); } }; } -QFontEngineFT *QFontEngineFT::create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) +QFontEngineFT *QFontEngineFT::create(const QByteArray &fontData, + qreal pixelSize, + QFont::HintingPreference hintingPreference, + const QMap<QFont::Tag, float> &variableAxisValues) { QFontDef fontDef; fontDef.pixelSize = pixelSize; fontDef.stretch = QFont::Unstretched; fontDef.hintingPreference = hintingPreference; + fontDef.variableAxisValues = variableAxisValues; QFontEngineFTRawData *fe = new QFontEngineFTRawData(fontDef); - if (!fe->initFromData(fontData)) { + if (!fe->initFromData(fontData, variableAxisValues)) { delete fe; return nullptr; } @@ -747,6 +833,37 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, static void dont_delete(void*) {} +static FT_UShort calculateActualWeight(QFreetypeFace *freetypeFace, FT_Face face, QFontEngine::FaceId faceId) +{ + FT_MM_Var *var = freetypeFace->mm_var; + if (var != nullptr && faceId.instanceIndex >= 0 && FT_UInt(faceId.instanceIndex) < var->num_namedstyles) { + for (FT_UInt axis = 0; axis < var->num_axis; ++axis) { + if (var->axis[axis].tag == QFont::Tag("wght").value()) { + return var->namedstyle[faceId.instanceIndex].coords[axis] >> 16; + } + } + } + if (const TT_OS2 *os2 = reinterpret_cast<const TT_OS2 *>(FT_Get_Sfnt_Table(face, ft_sfnt_os2))) { + return os2->usWeightClass; + } + + return 700; +} + +static bool calculateActualItalic(QFreetypeFace *freetypeFace, FT_Face face, QFontEngine::FaceId faceId) +{ + FT_MM_Var *var = freetypeFace->mm_var; + if (var != nullptr && faceId.instanceIndex >= 0 && FT_UInt(faceId.instanceIndex) < var->num_namedstyles) { + for (FT_UInt axis = 0; axis < var->num_axis; ++axis) { + if (var->axis[axis].tag == QFont::Tag("ital").value()) { + return (var->namedstyle[faceId.instanceIndex].coords[axis] >> 16) == 1; + } + } + } + + return (face->style_flags & FT_STYLE_FLAG_ITALIC); +} + bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, QFreetypeFace *freetypeFace) { @@ -770,7 +887,7 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, PS_FontInfoRec psrec; // don't assume that type1 fonts are symbol fonts by default if (FT_Get_PS_Font_Info(freetype->face, &psrec) == FT_Err_Ok) { - symbol = !fontDef.families.isEmpty() && bool(fontDef.families.first().contains("symbol"_L1, Qt::CaseInsensitive)); + symbol = !fontDef.families.isEmpty() && bool(fontDef.families.constFirst().contains("symbol"_L1, Qt::CaseInsensitive)); } freetype->computeSize(fontDef, &xsize, &ysize, &defaultGlyphSet.outline_drawing, &scalableBitmapScaleFactor); @@ -778,18 +895,18 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, FT_Face face = lockFace(); if (FT_IS_SCALABLE(face)) { - bool fake_oblique = (fontDef.style != QFont::StyleNormal) && !(face->style_flags & FT_STYLE_FLAG_ITALIC) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_ITALIC"); + bool isItalic = calculateActualItalic(freetype, face, faceId); + bool fake_oblique = (fontDef.style != QFont::StyleNormal) && !isItalic && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_ITALIC"); if (fake_oblique) obliquen = true; FT_Set_Transform(face, &matrix, nullptr); freetype->matrix = matrix; // fake bold if ((fontDef.weight >= QFont::Bold) && !(face->style_flags & FT_STYLE_FLAG_BOLD) && !FT_IS_FIXED_WIDTH(face) && !qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD")) { - if (const TT_OS2 *os2 = reinterpret_cast<const TT_OS2 *>(FT_Get_Sfnt_Table(face, ft_sfnt_os2))) { - if (os2->usWeightClass < 700 && - (fontDef.pixelSize < 64 || qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD_LIMIT"))) { - embolden = true; - } + FT_UShort actualWeight = calculateActualWeight(freetype, face, faceId); + if (actualWeight < 700 && + (fontDef.pixelSize < 64 || qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD_LIMIT"))) { + embolden = true; } } // underline metrics @@ -799,16 +916,15 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, } else { // ad hoc algorithm int score = fontDef.weight * fontDef.pixelSize; - line_thickness = score / 700; + line_thickness = score / 7000; // looks better with thicker line for small pointsizes if (line_thickness < 2 && score >= 1050) line_thickness = 2; underline_position = ((line_thickness * 2) + 3) / 6; - if (isScalableBitmap()) { + cacheEnabled = false; + if (isScalableBitmap()) glyphFormat = defaultFormat = GlyphFormat::Format_ARGB; - cacheEnabled = false; - } } if (line_thickness < 1) line_thickness = 1; @@ -1254,7 +1370,7 @@ QFontEngine::Properties QFontEngineFT::properties() const { Properties p = freetype->properties(); if (p.postscriptName.isEmpty()) { - p.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(fontDef.families.first().toUtf8()); + p.postscriptName = QFontEngine::convertToPostscriptFontFamilyName(fontDef.families.constFirst().toUtf8()); } return freetype->properties(); @@ -1481,7 +1597,7 @@ void QFontEngineFT::getUnscaledGlyph(glyph_t glyph, QPainterPath *path, glyph_me bool QFontEngineFT::supportsTransformation(const QTransform &transform) const { - return transform.type() <= QTransform::TxRotate; + return transform.type() <= QTransform::TxRotate && (freetype->isScalable() || freetype->isScalableBitmap()); } void QFontEngineFT::addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) @@ -1562,15 +1678,16 @@ glyph_t QFontEngineFT::glyphIndex(uint ucs4) const return glyph; } -bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, +int QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, QFontEngine::ShaperFlags flags) const { Q_ASSERT(glyphs->numGlyphs >= *nglyphs); if (*nglyphs < len) { *nglyphs = len; - return false; + return -1; } + int mappedGlyphs = 0; int glyph_pos = 0; if (freetype->symbol_map) { FT_Face face = freetype->face; @@ -1603,6 +1720,8 @@ bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs if (uc < QFreetypeFace::cmapCacheSize) freetype->cmapCache[uc] = glyph; } + if (glyphs->glyphs[glyph_pos] || isIgnorableChar(uc)) + mappedGlyphs++; ++glyph_pos; } } else { @@ -1624,6 +1743,8 @@ bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs freetype->cmapCache[uc] = glyph; } } + if (glyphs->glyphs[glyph_pos] || isIgnorableChar(uc)) + mappedGlyphs++; ++glyph_pos; } } @@ -1634,7 +1755,7 @@ bool QFontEngineFT::stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs if (!(flags & GlyphIndicesOnly)) recalcAdvances(glyphs, flags); - return true; + return mappedGlyphs; } bool QFontEngineFT::shouldUseDesignMetrics(QFontEngine::ShaperFlags flags) const @@ -1828,6 +1949,11 @@ glyph_metrics_t QFontEngineFT::alphaMapBoundingBox(glyph_t glyph, // When rendering glyphs into a cache via the alphaMap* functions, we disable // outline drawing. To ensure the bounding box matches the rendered glyph, we // need to do the same here. + + const bool needsImageTransform = !FT_IS_SCALABLE(freetype->face) + && matrix.type() > QTransform::TxTranslate; + if (needsImageTransform && format == QFontEngine::Format_Mono) + format = QFontEngine::Format_A8; Glyph *g = loadGlyphFor(glyph, subPixelPosition, format, matrix, true, true); glyph_metrics_t overall; @@ -1854,7 +1980,7 @@ glyph_metrics_t QFontEngineFT::alphaMapBoundingBox(glyph_t glyph, unlockFace(); } - if (isScalableBitmap()) + if (isScalableBitmap() || needsImageTransform) overall = scaledBitmapMetrics(overall, matrix); return overall; } @@ -1954,12 +2080,17 @@ QImage QFontEngineFT::alphaMapForGlyph(glyph_t g, const QFixedPoint &subPixelPosition, const QTransform &t) { - const GlyphFormat neededFormat = antialias ? Format_A8 : Format_Mono; + const bool needsImageTransform = !FT_IS_SCALABLE(freetype->face) + && t.type() > QTransform::TxTranslate; + const GlyphFormat neededFormat = antialias || needsImageTransform ? Format_A8 : Format_Mono; Glyph *glyph = loadGlyphFor(g, subPixelPosition, neededFormat, t, false, true); QImage img = alphaMapFromGlyphData(glyph, neededFormat); - img = img.copy(); + if (needsImageTransform) + img = img.transformed(t, Qt::SmoothTransformation); + else + img = img.copy(); if (!cacheEnabled && glyph != &emptyGlyph) delete glyph; diff --git a/src/gui/text/freetype/qfontengine_ft_p.h b/src/gui/text/freetype/qfontengine_ft_p.h index 3cc7635838..bdd4549827 100644 --- a/src/gui/text/freetype/qfontengine_ft_p.h +++ b/src/gui/text/freetype/qfontengine_ft_p.h @@ -19,6 +19,7 @@ #include <ft2build.h> #include FT_FREETYPE_H +#include FT_MULTIPLE_MASTERS_H #ifndef Q_OS_WIN @@ -62,6 +63,7 @@ public: } FT_Face face; + FT_MM_Var *mm_var; int xsize; // 26.6 int ysize; // 26.6 FT_Matrix matrix; @@ -75,6 +77,7 @@ public: int getPointInOutline(glyph_t glyph, int flags, quint32 point, QFixed *xpos, QFixed *ypos, quint32 *nPoints); + bool isScalable() const; bool isScalableBitmap() const; static void addGlyphToPath(FT_Face face, FT_GlyphSlot g, const QFixedPoint &point, QPainterPath *path, FT_Fixed x_scale, FT_Fixed y_scale); @@ -183,7 +186,7 @@ private: void addOutlineToPath(qreal x, qreal y, const QGlyphLayout &glyphs, QPainterPath *path, QTextItem::RenderFlags flags) override; - bool stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override; + int stringToCMap(const QChar *str, int len, QGlyphLayout *glyphs, int *nglyphs, ShaperFlags flags) const override; glyph_metrics_t boundingBox(const QGlyphLayout &glyphs) override; glyph_metrics_t boundingBox(glyph_t glyph) override; @@ -266,7 +269,7 @@ private: HintStyle defaultHintStyle() const { return default_hint_style; } static QFontEngineFT *create(const QFontDef &fontDef, FaceId faceId, const QByteArray &fontData = QByteArray()); - static QFontEngineFT *create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference); + static QFontEngineFT *create(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference, const QMap<QFont::Tag, float> &variableAxisValue); protected: @@ -325,8 +328,6 @@ private: QFixed scalableBitmapScaleFactor; }; -Q_DECLARE_TYPEINFO(QFontEngineFT::QGlyphSet, Q_RELOCATABLE_TYPE); - inline size_t qHash(const QFontEngineFT::GlyphAndSubPixelPosition &g, size_t seed = 0) { diff --git a/src/gui/text/freetype/qfreetypefontdatabase.cpp b/src/gui/text/freetype/qfreetypefontdatabase.cpp index fd02b80619..018e590ac2 100644 --- a/src/gui/text/freetype/qfreetypefontdatabase.cpp +++ b/src/gui/text/freetype/qfreetypefontdatabase.cpp @@ -10,6 +10,8 @@ #include <QtCore/QLibraryInfo> #include <QtCore/QDir> #include <QtCore/QtEndian> +#include <QtCore/QLoggingCategory> +#include <QtCore/QUuid> #undef QT_NO_FREETYPE #include "qfontengine_ft_p.h" @@ -18,8 +20,14 @@ #include FT_TRUETYPE_TABLES_H #include FT_ERRORS_H +#include FT_MULTIPLE_MASTERS_H +#include FT_SFNT_NAMES_H +#include FT_TRUETYPE_IDS_H + QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcFontDb) + using namespace Qt::StringLiterals; void QFreeTypeFontDatabase::populateFontDatabase() @@ -34,14 +42,14 @@ void QFreeTypeFontDatabase::populateFontDatabase() return; } - QStringList nameFilters; - nameFilters << "*.ttf"_L1 - << "*.ttc"_L1 - << "*.pfa"_L1 - << "*.pfb"_L1 - << "*.otf"_L1; + static const QString nameFilters[] = { + u"*.ttf"_s, + u"*.pfa"_s, + u"*.pfb"_s, + u"*.otf"_s, + }; - const auto fis = dir.entryInfoList(nameFilters, QDir::Files); + const auto fis = dir.entryInfoList(QStringList::fromReadOnlyData(nameFilters), QDir::Files); for (const QFileInfo &fi : fis) { const QByteArray file = QFile::encodeName(fi.absoluteFilePath()); QFreeTypeFontDatabase::addTTFile(QByteArray(), file); @@ -54,14 +62,24 @@ QFontEngine *QFreeTypeFontDatabase::fontEngine(const QFontDef &fontDef, void *us QFontEngine::FaceId faceId; faceId.filename = QFile::encodeName(fontfile->fileName); faceId.index = fontfile->indexValue; + faceId.instanceIndex = fontfile->instanceIndex; + faceId.variableAxes = fontDef.variableAxisValues; + + // Make sure the FaceId compares uniquely in cases where a + // file name is not provided. + if (faceId.filename.isEmpty()) { + QUuid::Id128Bytes id{}; + memcpy(&id, &usrPtr, sizeof(usrPtr)); + faceId.uuid = QUuid(id).toByteArray(); + } - return QFontEngineFT::create(fontDef, faceId); + return QFontEngineFT::create(fontDef, faceId, fontfile->data); } QFontEngine *QFreeTypeFontDatabase::fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) { - return QFontEngineFT::create(fontData, pixelSize, hintingPreference); + return QFontEngineFT::create(fontData, pixelSize, hintingPreference, {}); } QStringList QFreeTypeFontDatabase::addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont) @@ -77,6 +95,114 @@ void QFreeTypeFontDatabase::releaseHandle(void *handle) extern FT_Library qt_getFreetype(); +void QFreeTypeFontDatabase::addNamedInstancesForFace(void *face_, + int faceIndex, + const QString &family, + const QString &styleName, + QFont::Weight weight, + QFont::Stretch stretch, + QFont::Style style, + bool fixedPitch, + const QSupportedWritingSystems &writingSystems, + const QByteArray &fileName, + const QByteArray &fontData) +{ + FT_Face face = reinterpret_cast<FT_Face>(face_); + + // Note: The following does not actually depend on API from 2.9, but the + // FT_Set_Named_Instance() was added in 2.9, so to avoid populating the database with + // named instances that cannot be selected, we disable the feature on older Freetype + // versions. +#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900 + FT_MM_Var *var = nullptr; + FT_Get_MM_Var(face, &var); + if (var != nullptr) { + std::unique_ptr<FT_MM_Var, void(*)(FT_MM_Var*)> varGuard(var, [](FT_MM_Var *res) { + FT_Done_MM_Var(qt_getFreetype(), res); + }); + + for (FT_UInt i = 0; i < var->num_namedstyles; ++i) { + FT_UInt id = var->namedstyle[i].strid; + + QFont::Weight instanceWeight = weight; + QFont::Stretch instanceStretch = stretch; + QFont::Style instanceStyle = style; + for (FT_UInt axis = 0; axis < var->num_axis; ++axis) { + if (var->axis[axis].tag == QFont::Tag("wght").value()) { + instanceWeight = QFont::Weight(var->namedstyle[i].coords[axis] >> 16); + } else if (var->axis[axis].tag == QFont::Tag("wdth").value()) { + instanceStretch = QFont::Stretch(var->namedstyle[i].coords[axis] >> 16); + } else if (var->axis[axis].tag == QFont::Tag("ital").value()) { + FT_UInt ital = var->namedstyle[i].coords[axis] >> 16; + if (ital == 1) + instanceStyle = QFont::StyleItalic; + else + instanceStyle = QFont::StyleNormal; + } + } + + FT_UInt count = FT_Get_Sfnt_Name_Count(face); + for (FT_UInt j = 0; j < count; ++j) { + FT_SfntName name; + if (FT_Get_Sfnt_Name(face, j, &name)) + continue; + + if (name.name_id != id) + continue; + + // Only support Unicode for now + if (name.encoding_id != TT_MS_ID_UNICODE_CS) + continue; + + // Sfnt names stored as UTF-16BE + QString instanceName; + for (FT_UInt k = 0; k < name.string_len; k += 2) + instanceName += QChar((name.string[k] << 8) + name.string[k + 1]); + if (instanceName != styleName) { + FontFile *variantFontFile = new FontFile{ + QFile::decodeName(fileName), + faceIndex, + int(i), + fontData + }; + + qCDebug(lcFontDb) << "Registering named instance" << i + << ":" << instanceName + << "for font family" << family + << "with weight" << instanceWeight + << ", style" << instanceStyle + << ", stretch" << instanceStretch; + + registerFont(family, + instanceName, + QString(), + instanceWeight, + instanceStyle, + instanceStretch, + true, + true, + 0, + fixedPitch, + writingSystems, + variantFontFile); + } + } + } + } +#else + Q_UNUSED(face); + Q_UNUSED(family); + Q_UNUSED(styleName); + Q_UNUSED(weight); + Q_UNUSED(stretch); + Q_UNUSED(style); + Q_UNUSED(fixedPitch); + Q_UNUSED(writingSystems); + Q_UNUSED(fontData); +#endif + +} + QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const QByteArray &file, QFontDatabasePrivate::ApplicationFont *applicationFont) { FT_Library library = qt_getFreetype(); @@ -191,9 +317,12 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q } QString family = QString::fromLatin1(face->family_name); - FontFile *fontFile = new FontFile; - fontFile->fileName = QFile::decodeName(file); - fontFile->indexValue = index; + FontFile *fontFile = new FontFile{ + QFile::decodeName(file), + index, + -1, + fontData + }; QString styleName = QString::fromLatin1(face->style_name); @@ -209,6 +338,9 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q } registerFont(family, styleName, QString(), weight, style, stretch, true, true, 0, fixedPitch, writingSystems, fontFile); + + addNamedInstancesForFace(face, index, family, styleName, weight, stretch, style, fixedPitch, writingSystems, file, fontData); + families.append(family); FT_Done_Face(face); @@ -217,4 +349,13 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q return families; } +bool QFreeTypeFontDatabase::supportsVariableApplicationFonts() const +{ +#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900 + return true; +#else + return false; +#endif +} + QT_END_NAMESPACE diff --git a/src/gui/text/freetype/qfreetypefontdatabase_p.h b/src/gui/text/freetype/qfreetypefontdatabase_p.h index f9ad0824b9..5fcec585d2 100644 --- a/src/gui/text/freetype/qfreetypefontdatabase_p.h +++ b/src/gui/text/freetype/qfreetypefontdatabase_p.h @@ -26,6 +26,12 @@ struct FontFile { QString fileName; int indexValue; + int instanceIndex = -1; + + // Note: The data may be implicitly shared throughout the + // font database and platform font database, so be careful + // to never detach when accessing this member! + const QByteArray data; }; class Q_GUI_EXPORT QFreeTypeFontDatabase : public QPlatformFontDatabase @@ -36,6 +42,14 @@ public: QFontEngine *fontEngine(const QByteArray &fontData, qreal pixelSize, QFont::HintingPreference hintingPreference) override; QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr) override; void releaseHandle(void *handle) override; + bool supportsVariableApplicationFonts() const override; + + static void addNamedInstancesForFace(void *face, int faceIndex, + const QString &family, const QString &styleName, + QFont::Weight weight, QFont::Stretch stretch, + QFont::Style style, bool fixedPitch, + const QSupportedWritingSystems &writingSystems, + const QByteArray &fileName, const QByteArray &fontData); static QStringList addTTFile(const QByteArray &fontData, const QByteArray &file, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr); }; |