diff --git a/src/gui/text/qfontdatabase.cpp b/src/gui/text/qfontdatabase.cpp index d3a13d801b..3d6d3b7886 100644 --- a/src/gui/text/qfontdatabase.cpp +++ b/src/gui/text/qfontdatabase.cpp @@ -670,7 +670,7 @@ static QStringList fallbacksForFamily(const QString &family, QFont::Style style, return *fallbacks; // make sure that the db has all fallback families - QStringList userFallbacks = db->applicationFallbackFontFamilies.value(script == QChar::Script_Common ? QChar::Script_Latin : script); + QStringList userFallbacks = db->applicationFallbackFontFamilies.value(script == QChar::Script_Latin ? QChar::Script_Common : script); QStringList retList = userFallbacks + QGuiApplicationPrivate::platformIntegration()->fontDatabase()->fallbacksForFamily(family,style,styleHint,script); QStringList::iterator i; @@ -2187,6 +2187,8 @@ int QFontDatabasePrivate::addAppFont(const QByteArray &fontData, const QString & // loaded, so it has to be flushed. QFontCache::instance()->clear(); + fallbacksCache.clear(); + emit qApp->fontDatabaseChanged(); return i; @@ -2386,23 +2388,32 @@ bool QFontDatabase::removeAllApplicationFonts() be prioritized in reverse order, so that the last family added will be checked first and so on. + \note Qt's font matching algorithm considers \c{QChar::Script_Common} (undetermined script) + and \c{QChar::Script_Latin} the same. Adding a fallback for either of these will also apply + to the other. + \sa setApplicationFallbackFontFamilies(), removeApplicationFallbackFontFamily(), applicationFallbackFontFamilies() */ void QFontDatabase::addApplicationFallbackFontFamily(QChar::Script script, const QString &familyName) { QMutexLocker locker(fontDatabaseMutex()); - if (script < QChar::Script_Latin) { + if (script < QChar::Script_Common) { qCWarning(lcFontDb) << "Invalid script passed to addApplicationFallbackFontFamily:" << script; return; } + if (script == QChar::Script_Latin) + script = QChar::Script_Common; + auto *db = QFontDatabasePrivate::instance(); auto it = db->applicationFallbackFontFamilies.find(script); if (it == db->applicationFallbackFontFamilies.end()) it = db->applicationFallbackFontFamilies.insert(script, QStringList{}); it->prepend(familyName); + + QFontCache::instance()->clear(); db->fallbacksCache.clear(); } @@ -2420,6 +2431,14 @@ bool QFontDatabase::removeApplicationFallbackFontFamily(QChar::Script script, co { QMutexLocker locker(fontDatabaseMutex()); + if (script < QChar::Script_Common) { + qCWarning(lcFontDb) << "Invalid script passed to removeApplicationFallbackFontFamily:" << script; + return false; + } + + if (script == QChar::Script_Latin) + script = QChar::Script_Common; + auto *db = QFontDatabasePrivate::instance(); auto it = db->applicationFallbackFontFamilies.find(script); if (it != db->applicationFallbackFontFamilies.end()) { @@ -2452,11 +2471,14 @@ void QFontDatabase::setApplicationFallbackFontFamilies(QChar::Script script, con { QMutexLocker locker(fontDatabaseMutex()); - if (script < QChar::Script_Latin) { + if (script < QChar::Script_Common) { qCWarning(lcFontDb) << "Invalid script passed to setApplicationFallbackFontFamilies:" << script; return; } + if (script == QChar::Script_Latin) + script = QChar::Script_Common; + auto *db = QFontDatabasePrivate::instance(); db->applicationFallbackFontFamilies[script] = familyNames; @@ -2476,6 +2498,9 @@ QStringList QFontDatabase::applicationFallbackFontFamilies(QChar::Script script) { QMutexLocker locker(fontDatabaseMutex()); + if (script == QChar::Script_Latin) + script = QChar::Script_Common; + auto *db = QFontDatabasePrivate::instance(); return db->applicationFallbackFontFamilies.value(script); } diff --git a/tests/auto/gui/text/qfontdatabase/CMakeLists.txt b/tests/auto/gui/text/qfontdatabase/CMakeLists.txt index 18b96ded5d..0cb6e8d7c8 100644 --- a/tests/auto/gui/text/qfontdatabase/CMakeLists.txt +++ b/tests/auto/gui/text/qfontdatabase/CMakeLists.txt @@ -47,6 +47,8 @@ set(testdata_resource_files "../../../shared/resources/testfont_open.otf" "../../../shared/resources/testfont_variable.ttf" "LED_REAL.TTF" + "QtTestLimitedFont-Regular.ttf" + "QtTestFallbackFont-Regular.ttf" ) qt_internal_add_resource(tst_qfontdatabase "testdata" diff --git a/tests/auto/gui/text/qfontdatabase/QtTestFallbackFont-Regular.ttf b/tests/auto/gui/text/qfontdatabase/QtTestFallbackFont-Regular.ttf new file mode 100644 index 0000000000..ae21fec9a5 Binary files /dev/null and b/tests/auto/gui/text/qfontdatabase/QtTestFallbackFont-Regular.ttf differ diff --git a/tests/auto/gui/text/qfontdatabase/QtTestLimitedFont-Regular.ttf b/tests/auto/gui/text/qfontdatabase/QtTestLimitedFont-Regular.ttf new file mode 100644 index 0000000000..2891f8aeff Binary files /dev/null and b/tests/auto/gui/text/qfontdatabase/QtTestLimitedFont-Regular.ttf differ diff --git a/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp b/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp index 849e7432d1..8733f64d97 100644 --- a/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp +++ b/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp @@ -78,6 +78,8 @@ private: QString m_testFontCondensed; QString m_testFontItalic; QString m_testFontVariable; + QString m_limitedFont; + QString m_fallbackFont; }; tst_QFontDatabase::tst_QFontDatabase() @@ -91,11 +93,15 @@ void tst_QFontDatabase::initTestCase() m_testFontCondensed = QFINDTESTDATA("testfont_condensed.ttf"); m_testFontItalic = QFINDTESTDATA("testfont_italic.ttf"); m_testFontVariable = QFINDTESTDATA("testfont_variable.ttf"); + m_limitedFont = QFINDTESTDATA("QtTestLimitedFont-Regular.ttf"); + m_fallbackFont = QFINDTESTDATA("QtTestFallbackFont-Regular.ttf"); QVERIFY(!m_ledFont.isEmpty()); QVERIFY(!m_testFont.isEmpty()); QVERIFY(!m_testFontCondensed.isEmpty()); QVERIFY(!m_testFontItalic.isEmpty()); QVERIFY(!m_testFontVariable.isEmpty()); + QVERIFY(!m_limitedFont.isEmpty()); + QVERIFY(!m_fallbackFont.isEmpty()); } void tst_QFontDatabase::styles_data() @@ -391,8 +397,14 @@ void tst_QFontDatabase::condensedFontWidthNoFontMerging() void tst_QFontDatabase::condensedFontWidth() { - QFontDatabase::addApplicationFont(m_testFont); - QFontDatabase::addApplicationFont(m_testFontCondensed); + int testFontId = QFontDatabase::addApplicationFont(m_testFont); + int testFontCondensedId = QFontDatabase::addApplicationFont(m_testFontCondensed); + auto cleanup = qScopeGuard([&testFontId, &testFontCondensedId] { + if (testFontId >= 0) + QFontDatabase::removeApplicationFont(testFontId); + if (testFontCondensedId >= 0) + QFontDatabase::removeApplicationFont(testFontCondensedId); + }); QVERIFY(QFontDatabase::hasFamily("QtBidiTestFont")); if (!QFontDatabase::hasFamily("QtBidiTestFontCondensed")) @@ -410,10 +422,16 @@ void tst_QFontDatabase::condensedFontWidth() void tst_QFontDatabase::condensedFontMatching() { QFontDatabase::removeAllApplicationFonts(); - QFontDatabase::addApplicationFont(m_testFontCondensed); + int testFontCondensedId = QFontDatabase::addApplicationFont(m_testFontCondensed); if (!QFontDatabase::hasFamily("QtBidiTestFont")) QSKIP("This platform doesn't support preferred font family names (QTBUG-53478)"); - QFontDatabase::addApplicationFont(m_testFont); + int testFontId = QFontDatabase::addApplicationFont(m_testFont); + auto cleanup = qScopeGuard([&testFontId, &testFontCondensedId] { + if (testFontId >= 0) + QFontDatabase::removeApplicationFont(testFontId); + if (testFontCondensedId >= 0) + QFontDatabase::removeApplicationFont(testFontCondensedId); + }); // Test we correctly get the condensed font using different font matching methods: QFont tfcByStretch("QtBidiTestFont"); @@ -561,11 +579,17 @@ void tst_QFontDatabase::addApplicationFontFallback() { int ledId = -1; int id = -1; - auto cleanup = qScopeGuard([&id, &ledId] { + int limitedId = -1; + int fallbackId = -1; + auto cleanup = qScopeGuard([&id, &ledId, &limitedId, &fallbackId] { if (id >= 0) QFontDatabase::removeApplicationFont(id); if (ledId >= 0) QFontDatabase::removeApplicationFont(ledId); + if (limitedId >= 0) + QFontDatabase::removeApplicationFont(limitedId); + if (fallbackId >= 0) + QFontDatabase::removeApplicationFont(fallbackId); }); const QChar hebrewChar(0x05D0); // Hebrew 'aleph' @@ -633,6 +657,107 @@ void tst_QFontDatabase::addApplicationFontFallback() QCOMPARE(hebrewFontNow, defaultHebrewFont); } + limitedId = QFontDatabase::addApplicationFont(m_limitedFont); + QVERIFY(limitedId >= 0); + + fallbackId = QFontDatabase::addApplicationFont(m_fallbackFont); + QVERIFY(fallbackId >= 0); + + QFontDatabase::addApplicationFallbackFontFamily(QChar::Script_Common, u"QtTestFallbackFont"_s); + + // The fallback for Common will be used also for Latin, because Latin and Common are + // considered the same script by the font matching engine. + { + QTextLayout layout; + layout.setText(u"A'B,"_s); + layout.setFont(QFont(u"QtTestLimitedFont"_s)); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + QList glyphRuns = layout.glyphRuns(); + QVERIFY(glyphRuns.size() > 1); + for (int i = 0; i < glyphRuns.size(); ++i) { + QVERIFY(glyphRuns.at(i).rawFont().familyName() == u"QtTestFallbackFont"_s + || glyphRuns.at(i).rawFont().familyName() == u"QtTestLimitedFont"_s); + } + } + + // When the text only consists of common script characters, the fallback font will also be used. + { + QTextLayout layout; + layout.setText(u"',"_s); + layout.setFont(QFont(u"QtTestLimitedFont"_s)); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + QList glyphRuns = layout.glyphRuns(); + QCOMPARE(glyphRuns.size(), 2); + for (int i = 0; i < glyphRuns.size(); ++i) { + QVERIFY(glyphRuns.at(i).rawFont().familyName() == u"QtTestFallbackFont"_s + || glyphRuns.at(i).rawFont().familyName() == u"QtTestLimitedFont"_s); + } + } + + QVERIFY(QFontDatabase::removeApplicationFallbackFontFamily(QChar::Script_Common, u"QtTestFallbackFont"_s)); + QFontDatabase::addApplicationFallbackFontFamily(QChar::Script_Latin, u"QtTestFallbackFont"_s); + + // Latin fallback works just the same as Common fallback + { + QTextLayout layout; + layout.setText(u"A'B,"_s); + layout.setFont(QFont(u"QtTestLimitedFont"_s)); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + QList glyphRuns = layout.glyphRuns(); + QCOMPARE(glyphRuns.size(), 2); + for (int i = 0; i < glyphRuns.size(); ++i) { + QVERIFY(glyphRuns.at(i).rawFont().familyName() == u"QtTestFallbackFont"_s + || glyphRuns.at(i).rawFont().familyName() == u"QtTestLimitedFont"_s); + } + } + + // When the common character is placed next to a Cyrillic characters, it gets adapted to this, + // so the fallback font will not be selected, even if it supports the character in question + { + QTextLayout layout; + layout.setText(u"A'Б,"_s); + layout.setFont(QFont(u"QtTestLimitedFont"_s)); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + QList glyphRuns = layout.glyphRuns(); + QCOMPARE(glyphRuns.size(), 2); + for (int i = 0; i < glyphRuns.size(); ++i) { + QVERIFY(glyphRuns.at(i).rawFont().familyName() != u"QtTestFallbackFont"_s); + } + } + + QFontDatabase::addApplicationFallbackFontFamily(QChar::Script_Cyrillic, u"QtTestFallbackFont"_s); + + // When we set the fallback font for Cyrillic as well, it gets selected + { + QTextLayout layout; + layout.setText(u"A'Б,"_s); + layout.setFont(QFont(u"QtTestLimitedFont"_s)); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + QList glyphRuns = layout.glyphRuns(); + QCOMPARE(glyphRuns.size(), 2); + for (int i = 0; i < glyphRuns.size(); ++i) { + QVERIFY(glyphRuns.at(i).rawFont().familyName() == u"QtTestFallbackFont"_s + || glyphRuns.at(i).rawFont().familyName() == u"QtTestLimitedFont"_s); + } + } + + QVERIFY(QFontDatabase::removeApplicationFallbackFontFamily(QChar::Script_Cyrillic, u"QtTestFallbackFont"_s)); + QVERIFY(QFontDatabase::removeApplicationFallbackFontFamily(QChar::Script_Latin, u"QtTestFallbackFont"_s)); } QTEST_MAIN(tst_QFontDatabase)