diff --git a/src/gui/text/freetype/qfontengine_ft.cpp b/src/gui/text/freetype/qfontengine_ft.cpp index 3256c74f3b..b7eb9f412b 100644 --- a/src/gui/text/freetype/qfontengine_ft.cpp +++ b/src/gui/text/freetype/qfontengine_ft.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #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) @@ -243,6 +247,15 @@ 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->ref.storeRelaxed(1); @@ -747,6 +760,43 @@ bool QFontEngineFT::init(FaceId faceId, bool antialias, GlyphFormat format, static void dont_delete(void*) {} +static FT_UShort calculateActualWeight(FT_Face face, QFontEngine::FaceId faceId) +{ + if (faceId.instanceIndex >= 0) { + FT_MM_Var *var = nullptr; + FT_Get_MM_Var(face, &var); + if (var != nullptr && FT_UInt(faceId.instanceIndex) < var->num_namedstyles) { + for (FT_UInt axis = 0; axis < var->num_axis; ++axis) { + if (var->axis[axis].tag == MAKE_TAG('w', 'g', 'h', 't')) { + return var->namedstyle[faceId.instanceIndex].coords[axis] >> 16; + } + } + } + } + if (const TT_OS2 *os2 = reinterpret_cast(FT_Get_Sfnt_Table(face, ft_sfnt_os2))) { + return os2->usWeightClass; + } + + return 700; +} + +static bool calculateActualItalic(FT_Face face, QFontEngine::FaceId faceId) +{ + if (faceId.instanceIndex >= 0) { + FT_MM_Var *var = nullptr; + FT_Get_MM_Var(face, &var); + if (var != nullptr && FT_UInt(faceId.instanceIndex) < var->num_namedstyles) { + for (FT_UInt axis = 0; axis < var->num_axis; ++axis) { + if (var->axis[axis].tag == MAKE_TAG('i', 't', 'a', 'l')) { + 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) { @@ -778,18 +828,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(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(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(face, faceId); + if (actualWeight < 700 && + (fontDef.pixelSize < 64 || qEnvironmentVariableIsSet("QT_NO_SYNTHESIZED_BOLD_LIMIT"))) { + embolden = true; } } // underline metrics @@ -2191,6 +2241,15 @@ Qt::HANDLE QFontEngineFT::handle() const return non_locked_face(); } +bool QFontEngineFT::supportsVariableApplicationFonts() const +{ +#if (FREETYPE_MAJOR*10000 + FREETYPE_MINOR*100 + FREETYPE_PATCH) >= 20900 + return true; +#else + return false; +#endif +} + QT_END_NAMESPACE #endif // QT_NO_FREETYPE diff --git a/src/gui/text/freetype/qfontengine_ft_p.h b/src/gui/text/freetype/qfontengine_ft_p.h index e195174960..85f1d87a3c 100644 --- a/src/gui/text/freetype/qfontengine_ft_p.h +++ b/src/gui/text/freetype/qfontengine_ft_p.h @@ -159,6 +159,8 @@ private: return supportsHorizontalSubPixelPositions(); } + bool supportsVariableApplicationFonts() const override; + bool getSfntTableData(uint tag, uchar *buffer, uint *length) const override; int synthesized() const override; diff --git a/src/gui/text/freetype/qfreetypefontdatabase.cpp b/src/gui/text/freetype/qfreetypefontdatabase.cpp index cf1ca42ab4..2441deaa38 100644 --- a/src/gui/text/freetype/qfreetypefontdatabase.cpp +++ b/src/gui/text/freetype/qfreetypefontdatabase.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #undef QT_NO_FREETYPE #include "qfontengine_ft_p.h" @@ -18,8 +19,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() @@ -54,6 +61,7 @@ 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; return QFontEngineFT::create(fontDef, faceId, fontfile->data); } @@ -77,6 +85,110 @@ 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(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) { + 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 == MAKE_TAG('w', 'g', 'h', 't')) { + instanceWeight = QFont::Weight(var->namedstyle[i].coords[axis] >> 16); + } else if (var->axis[axis].tag == MAKE_TAG('w', 'd', 't', 'h')) { + instanceStretch = QFont::Stretch(var->namedstyle[i].coords[axis] >> 16); + } else if (var->axis[axis].tag == MAKE_TAG('i', 't', 'a', 'l')) { + 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(); @@ -194,6 +306,7 @@ QStringList QFreeTypeFontDatabase::addTTFile(const QByteArray &fontData, const Q FontFile *fontFile = new FontFile{ QFile::decodeName(file), index, + -1, fontData }; @@ -211,6 +324,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); diff --git a/src/gui/text/freetype/qfreetypefontdatabase_p.h b/src/gui/text/freetype/qfreetypefontdatabase_p.h index ffb10a2e5c..46ffa4353c 100644 --- a/src/gui/text/freetype/qfreetypefontdatabase_p.h +++ b/src/gui/text/freetype/qfreetypefontdatabase_p.h @@ -26,6 +26,7 @@ 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 @@ -42,6 +43,13 @@ public: QStringList addApplicationFont(const QByteArray &fontData, const QString &fileName, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr) override; void releaseHandle(void *handle) 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); }; diff --git a/src/gui/text/qfontengine.cpp b/src/gui/text/qfontengine.cpp index 8f2336f2ac..66b0e88598 100644 --- a/src/gui/text/qfontengine.cpp +++ b/src/gui/text/qfontengine.cpp @@ -247,6 +247,11 @@ bool QFontEngine::supportsTransformation(const QTransform &transform) const return transform.type() < QTransform::TxProject; } +bool QFontEngine::supportsVariableApplicationFonts() const +{ + return false; +} + bool QFontEngine::expectsGammaCorrectedBlending() const { return true; diff --git a/src/gui/text/qfontengine_p.h b/src/gui/text/qfontengine_p.h index dbad0d95f9..50e427e46e 100644 --- a/src/gui/text/qfontengine_p.h +++ b/src/gui/text/qfontengine_p.h @@ -127,10 +127,11 @@ public: virtual bool getSfntTableData(uint tag, uchar *buffer, uint *length) const; struct FaceId { - FaceId() : index(0), encoding(0) {} + FaceId() : index(0), instanceIndex(-1), encoding(0) {} QByteArray filename; QByteArray uuid; int index; + int instanceIndex; int encoding; }; virtual FaceId faceId() const { return FaceId(); } @@ -212,6 +213,7 @@ public: inline bool canRender(uint ucs4) const { return glyphIndex(ucs4) != 0; } virtual bool canRender(const QChar *str, int len) const; + virtual bool supportsVariableApplicationFonts() const; virtual bool supportsTransformation(const QTransform &transform) const; @@ -370,13 +372,17 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(QFontEngine::ShaperFlags) inline bool operator ==(const QFontEngine::FaceId &f1, const QFontEngine::FaceId &f2) { - return f1.index == f2.index && f1.encoding == f2.encoding && f1.filename == f2.filename && f1.uuid == f2.uuid; + return f1.index == f2.index + && f1.encoding == f2.encoding + && f1.filename == f2.filename + && f1.uuid == f2.uuid + && f1.instanceIndex == f2.instanceIndex; } inline size_t qHash(const QFontEngine::FaceId &f, size_t seed = 0) noexcept(noexcept(qHash(f.filename))) { - return qHashMulti(seed, f.filename, f.uuid, f.index, f.encoding); + return qHashMulti(seed, f.filename, f.uuid, f.index, f.instanceIndex, f.encoding); } diff --git a/src/gui/text/unix/qfontconfigdatabase.cpp b/src/gui/text/unix/qfontconfigdatabase.cpp index 474644b871..4a402cd764 100644 --- a/src/gui/text/unix/qfontconfigdatabase.cpp +++ b/src/gui/text/unix/qfontconfigdatabase.cpp @@ -29,6 +29,8 @@ QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(lcFontDb) + static inline int mapToQtWeightForRange(int fcweight, int fcLower, int fcUpper, int qtLower, int qtUpper) { return qtLower + ((fcweight - fcLower) * (qtUpper - qtLower)) / (fcUpper - fcLower); @@ -366,7 +368,10 @@ static inline bool requiresOpenType(int writingSystem) || writingSystem == QFontDatabase::Khmer || writingSystem == QFontDatabase::Nko); } -static void populateFromPattern(FcPattern *pattern, QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr) +static void populateFromPattern(FcPattern *pattern, + QFontDatabasePrivate::ApplicationFont *applicationFont = nullptr, + FT_Face face = nullptr, + QFontconfigDatabase *db = nullptr) { QString familyName; QString familyNameLang; @@ -489,6 +494,20 @@ static void populateFromPattern(FcPattern *pattern, QFontDatabasePrivate::Applic } QPlatformFontDatabase::registerFont(familyName,styleName,QLatin1StringView((const char *)foundry_value),weight,style,stretch,antialias,scalable,pixel_size,fixedPitch,writingSystems,fontFile); + if (applicationFont != nullptr && face != nullptr && db != nullptr) { + db->addNamedInstancesForFace(face, + indexValue, + familyName, + styleName, + weight, + stretch, + style, + fixedPitch, + writingSystems, + QByteArray((const char*)file_value), + applicationFont->data); + } + // qDebug() << familyName << (const char *)foundry_value << weight << style << &writingSystems << scalable << true << pixel_size; for (int k = 1; FcPatternGetString(pattern, FC_FAMILY, k, &value) == FcResultMatch; ++k) { @@ -702,6 +721,7 @@ QFontEngine *QFontconfigDatabase::fontEngine(const QFontDef &f, void *usrPtr) QFontEngine::FaceId fid; fid.filename = QFile::encodeName(fontfile->fileName); fid.index = fontfile->indexValue; + fid.instanceIndex = fontfile->instanceIndex; // FIXME: Unify with logic in QFontEngineFT::create() QFontEngineFT *engine = new QFontEngineFT(f); @@ -803,26 +823,28 @@ QStringList QFontconfigDatabase::fallbacksForFamily(const QString &family, QFont return fallbackFamilies; } -static FcPattern *queryFont(const FcChar8 *file, const QByteArray &data, int id, FcBlanks *blanks, int *count) +static FcPattern *queryFont(const FcChar8 *file, const QByteArray &data, int id, FcBlanks *blanks, int *count, FT_Face *face) { #if FC_VERSION < 20402 Q_UNUSED(data); + *face = nullptr; return FcFreeTypeQuery(file, id, blanks, count); #else - if (data.isEmpty()) + if (data.isEmpty()) { + *face = nullptr; return FcFreeTypeQuery(file, id, blanks, count); + } FT_Library lib = qt_getFreetype(); FcPattern *pattern = nullptr; - FT_Face face; - if (!FT_New_Memory_Face(lib, (const FT_Byte *)data.constData(), data.size(), id, &face)) { - *count = face->num_faces; + if (!FT_New_Memory_Face(lib, (const FT_Byte *)data.constData(), data.size(), id, face)) { + *count = (*face)->num_faces; - pattern = FcFreeTypeQueryFace(face, file, id, blanks); - - FT_Done_Face(face); + pattern = FcFreeTypeQueryFace(*face, file, id, blanks); + } else { + *face = nullptr; } return pattern; @@ -850,8 +872,9 @@ QStringList QFontconfigDatabase::addApplicationFont(const QByteArray &fontData, FcPattern *pattern; do { + FT_Face face; pattern = queryFont((const FcChar8 *)QFile::encodeName(fileName).constData(), - fontData, id, blanks, &count); + fontData, id, blanks, &count, &face); if (!pattern) return families; @@ -860,7 +883,10 @@ QStringList QFontconfigDatabase::addApplicationFont(const QByteArray &fontData, QString family = QString::fromUtf8(reinterpret_cast(fam)); families << family; } - populateFromPattern(pattern, applicationFont); + populateFromPattern(pattern, applicationFont, face, this); + + if (face) + FT_Done_Face(face); FcFontSetAdd(set, pattern); diff --git a/tests/auto/gui/text/qfontdatabase/CMakeLists.txt b/tests/auto/gui/text/qfontdatabase/CMakeLists.txt index 245fd6cf77..18b96ded5d 100644 --- a/tests/auto/gui/text/qfontdatabase/CMakeLists.txt +++ b/tests/auto/gui/text/qfontdatabase/CMakeLists.txt @@ -37,11 +37,15 @@ set_source_files_properties("../../../shared/resources/testfont_italic.ttf" set_source_files_properties("../../../shared/resources/testfont_open.otf" PROPERTIES QT_RESOURCE_ALIAS "testfont_open.otf" ) +set_source_files_properties("../../../shared/resources/testfont_variable.ttf" + PROPERTIES QT_RESOURCE_ALIAS "testfont_variable.ttf" +) set(testdata_resource_files "../../../shared/resources/testfont.ttf" "../../../shared/resources/testfont_condensed.ttf" "../../../shared/resources/testfont_italic.ttf" "../../../shared/resources/testfont_open.otf" + "../../../shared/resources/testfont_variable.ttf" "LED_REAL.TTF" ) diff --git a/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp b/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp index 172cb2480b..81c402cd89 100644 --- a/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp +++ b/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp @@ -61,6 +61,8 @@ private slots: void stretchRespected(); + void variableFont(); + #ifdef Q_OS_WIN void findCourier(); #endif @@ -70,6 +72,7 @@ private: QString m_testFont; QString m_testFontCondensed; QString m_testFontItalic; + QString m_testFontVariable; }; tst_QFontDatabase::tst_QFontDatabase() @@ -82,10 +85,12 @@ void tst_QFontDatabase::initTestCase() m_testFont = QFINDTESTDATA("testfont.ttf"); m_testFontCondensed = QFINDTESTDATA("testfont_condensed.ttf"); m_testFontItalic = QFINDTESTDATA("testfont_italic.ttf"); + m_testFontVariable = QFINDTESTDATA("testfont_variable.ttf"); QVERIFY(!m_ledFont.isEmpty()); QVERIFY(!m_testFont.isEmpty()); QVERIFY(!m_testFontCondensed.isEmpty()); QVERIFY(!m_testFontItalic.isEmpty()); + QVERIFY(!m_testFontVariable.isEmpty()); } void tst_QFontDatabase::styles_data() @@ -505,5 +510,47 @@ void tst_QFontDatabase::findCourier() } #endif +void tst_QFontDatabase::variableFont() +{ + { + QFont f; + f.setStyleStrategy(QFont::NoFontMerging); + QFontPrivate *font_d = QFontPrivate::get(f); + if (!font_d->engineForScript(QChar::Script_Common)->supportsVariableApplicationFonts()) + QSKIP("Variable application fonts only supported on Freetype currently"); + } + + int id = QFontDatabase::addApplicationFont(m_testFontVariable); + if (id == -1) + QSKIP("Skip the test since app fonts are not supported on this system"); + + QString family = QFontDatabase::applicationFontFamilies(id).first(); + { + QFont font(family); + QCOMPARE(QFontInfo(font).styleName(), u"Regular"_s); + QCOMPARE(QFontInfo(font).weight(), QFont::Normal); + } + + { + QFont font(family); + font.setWeight(QFont::ExtraBold); + QCOMPARE(QFontInfo(font).styleName(), u"QtExtraBold"_s); + QCOMPARE(QFontInfo(font).weight(), QFont::ExtraBold); + } + + { + QFont regularFont(family); + QFont extraBoldFont(family); + extraBoldFont.setStyleName(u"QtExtraBold"_s); + + QFontMetricsF regularFm(regularFont); + QFontMetricsF extraBoldFm(extraBoldFont); + + QVERIFY(regularFm.horizontalAdvance(QLatin1Char('1')) < extraBoldFm.horizontalAdvance(QLatin1Char('1'))); + } + + QFontDatabase::removeApplicationFont(id); +} + QTEST_MAIN(tst_QFontDatabase) #include "tst_qfontdatabase.moc" diff --git a/tests/auto/shared/resources/testfont_variable.ttf b/tests/auto/shared/resources/testfont_variable.ttf new file mode 100644 index 0000000000..f183b9333d Binary files /dev/null and b/tests/auto/shared/resources/testfont_variable.ttf differ