QFontEngine: add test coverage and benchmark for all engines

For some known fonts, confirm that we get the right glyph count and
glyph index for specific unicode code points.

Create QGuiApplication with a specific font engine, and test the fonts
with all engines. On Windows, that's DirectWrite, GDI, and Freetype;
on macOS CoreText and Freetype; and otherwise only Freetype.

Not all fonts will be available with all engines, so test in each test
function whether the font is a good enough match (family is enough, no
need to do a deep test).

Add a benchmark as well, using the same setup plumbing, but with
different test functions.

Change-Id: I2ed279965fc3f1dc3f283d0fe7b018fc3035c67d
Reviewed-by: Anton Kudryavtsev <antkudr@mail.ru>
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
Volker Hilsheimer 2024-11-19 16:24:02 +01:00
parent 51b584e606
commit 4db368d474
6 changed files with 350 additions and 0 deletions

View File

@ -4,6 +4,7 @@
add_subdirectory(qabstracttextdocumentlayout)
add_subdirectory(qfont)
add_subdirectory(qfontdatabase)
add_subdirectory(qfontengine)
add_subdirectory(qfontmetrics)
add_subdirectory(qglyphrun)
add_subdirectory(qrawfont)

View File

@ -0,0 +1,40 @@
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_qfontengine Test:
#####################################################################
if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
cmake_minimum_required(VERSION 3.16)
project(tst_qfontengine LANGUAGES CXX)
find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
endif()
# Resources:
set_source_files_properties("../../../shared/resources/test.ttf"
PROPERTIES QT_RESOURCE_ALIAS "test.ttf"
)
set_source_files_properties("../../../shared/resources/testfont.ttf"
PROPERTIES QT_RESOURCE_ALIAS "testfont.ttf"
)
set_source_files_properties("../../../shared/resources/testfont_variable.ttf"
PROPERTIES QT_RESOURCE_ALIAS "testfont_variable.ttf"
)
set(testfonts_resource_files
"../../../shared/resources/test.ttf"
"../../../shared/resources/testfont.ttf"
"../../../shared/resources/testfont_variable.ttf"
)
qt_internal_add_test(tst_qfontengine
SOURCES
tst_qfontengine.cpp
TESTDATA
${testfonts_resource_files}
LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::GuiPrivate
BUILTIN_TESTDATA
)

View File

@ -0,0 +1,257 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QTest>
#include <QtGui/qfont.h>
#include <QtGui/qfontdatabase.h>
#include <QtGui/qfontinfo.h>
#include <QtGui/private/qfont_p.h>
#include <QtGui/private/qfontengine_p.h>
using namespace Qt::StringLiterals;
class tst_QFontEngine : public QObject
{
Q_OBJECT
struct Candidate {
QLatin1StringView rowName;
int expectedGlyphCount;
int ucs4;
glyph_t expectedGlyphIndex;
QString familyName() const
{
const auto split = rowName.indexOf('@');
Q_ASSERT(split >= 0);
return QString::fromUtf8(rowName.mid(split + 1));
}
QFont font() const
{
QFont font(familyName());
font.setStyleStrategy(QFont::NoFontMerging);
return font;
}
bool isFontAvailable() const
{
QFont theFont(font());
return theFont.family() == QFontInfo(theFont).family();
}
QFontEngine *fontEngine() const
{
return QFontPrivate::get(font())->engineForScript(QChar::Script_Common);
}
};
public:
tst_QFontEngine() = default;
private slots:
void initTestCase_data();
void init();
void cleanup();
void glyphCount_data() { data(); }
void glyphCount();
void glyphIndex_data() { data(); }
void glyphIndex();
private:
void setupApplication();
void data();
// member variables to keep the arguments data alive
QByteArray platformArgument;
QList<const char *> theArguments;
std::unique_ptr<QGuiApplication> theApp;
int QtsSpecialTestFont = -1;
int QtBidiTestFont = -1;
int QtTestVariableFont = -1;
};
// The tst_bench_QFontEngine benchmark project shares the test class declaration
// and the common setup code here. The file is both compiled as part of the
// project (moc needs to see it), and included (so that the benchmark code sees
// the class declaration).
#if !defined(QFONTENGINE_BENCHMARK) || defined(QFONTENGINE_COMMON)
void tst_QFontEngine::initTestCase_data()
{
QTest::addColumn<QByteArray>("platform");
QTest::addColumn<QByteArray>("engine");
QTest::addColumn<QFontEngine::Type>("engineType");
#if defined(Q_OS_WIN)
QTest::addRow("DirectWrite") << ""_ba << ""_ba << QFontEngine::DirectWrite;
QTest::addRow("GDI") << "windows"_ba << "gdi"_ba << QFontEngine::Win;
QTest::addRow("Freetype") << "windows"_ba << "freetype"_ba << QFontEngine::Freetype;
#elif defined(Q_OS_DARWIN)
QTest::addRow("CoreText") << ""_ba << ""_ba << QFontEngine::Mac;
QTest::addRow("Freetype") << "cocoa"_ba << "freetype"_ba << QFontEngine::Freetype;
#else
QTest::addRow("freetype") << ""_ba << ""_ba << QFontEngine::Freetype;
#endif
}
void tst_QFontEngine::init()
{
setupApplication();
#if !defined(QFONTENGINE_BENCHMARK)
QtsSpecialTestFont = QFontDatabase::addApplicationFont(QFINDTESTDATA("test.ttf"));
QVERIFY(QtsSpecialTestFont >= 0);
QCOMPARE(QFontDatabase::applicationFontFamilies(QtsSpecialTestFont),
QStringList{u"QtsSpecialTestFont"_s});
QtBidiTestFont = QFontDatabase::addApplicationFont(QFINDTESTDATA("testfont.ttf"));
QVERIFY(QtBidiTestFont >= 0);
QCOMPARE(QFontDatabase::applicationFontFamilies(QtBidiTestFont),
QStringList{u"QtBidiTestFont"_s});
// This font comes with two font faces, so we get multiple entries (which
// might be a bug, esp since on macOS we get two, on Windows we get three
// identical families).
QtTestVariableFont = QFontDatabase::addApplicationFont(QFINDTESTDATA("testfont_variable.ttf"));
QVERIFY(QtTestVariableFont >= 0);
QVERIFY(QFontDatabase::applicationFontFamilies(QtTestVariableFont)
.contains(u"QtTestVariableFont"_s));
#endif
}
void tst_QFontEngine::cleanup()
{
#if !defined(QFONTENGINE_BENCHMARK)
QFontDatabase::removeApplicationFont(QtTestVariableFont);
QFontDatabase::removeApplicationFont(QtBidiTestFont);
QFontDatabase::removeApplicationFont(QtsSpecialTestFont);
#endif
theApp.reset();
theArguments = {};
}
void tst_QFontEngine::setupApplication()
{
if (theApp)
return;
QFETCH_GLOBAL(const QByteArray, platform);
QFETCH_GLOBAL(const QByteArray, engine);
QList<const char *> arguments = {"tst_qfontengine"};
if (!platform.isEmpty()) {
arguments += "-platform";
platformArgument = platform + ":fontengine=" + engine;
arguments += platformArgument.data();
}
theArguments = arguments;
int argc = arguments.size();
theApp = std::make_unique<QGuiApplication>(argc, const_cast<char **>(theArguments.data()));
}
// only called once per test function, even if the global data (i.e. font engine) changed!
void tst_QFontEngine::data()
{
QTest::addColumn<Candidate>("candidate");
const std::initializer_list<Candidate> candidates = {
#if !defined(QFONTENGINE_BENCHMARK)
// our own testfonts
{
".notdef@QtsSpecialTestFont"_L1,
6, 0x0000, 0,
},
{
"Dotty@QtsSpecialTestFont"_L1,
6, 0xe000, 1,
},
{
"A@QtsSpecialTestFont"_L1,
6, 0x0041, 2,
},
{
"one@QtsSpecialTestFont"_L1,
6, 0x0031, 3,
},
{
"uni200D@QtsSpecialTestFont"_L1,
6, 0x200d, 4,
},
{
"uniFFFC@QtsSpecialTestFont"_L1,
6, 0xfffc, 5,
},
{
"percent@QtBidiTestFont"_L1,
150, 0x0025, 13,
},
{ // up arrow, beyond 0xFFFF
"u1034A@QtBidiTestFont"_L1,
150, 0x1034A, 149,
},
{
"peseta@QtTestVariableFont"_L1,
235, 0x20a7, 218,
},
#else // some typical icon fonts that might be present on the system, but also change frequently
{ // early hit
"faucet@Font Awesome 6 Free"_L1,
1399, 0xe005, 51,
},
{ // last glyph in the font
"caravan@Font Awesome 6 Free"_L1,
1399, 0xf8ff, 1398,
},
{
"delete@Material Symbols Outlined"_L1,
5032, 0xe872, 1,
},
{ // the Segoe Fluent Icons font that comes with Windows 11
"gid1486@Segoe Fluent Icons"_L1, // voicemail
1529, 0xf47f, 1486,
},
#endif // !defined(QFONTENGINE_BENCHMARK)
};
for (auto candidate : candidates)
QTest::addRow("%s", candidate.rowName.constData()) << candidate;
}
#include "tst_qfontengine.moc"
QTEST_APPLESS_MAIN(tst_QFontEngine)
#endif // !defined(QFONTENGINE_BENCHMARK) || defined(QFONTENGINE_COMMON)
// The benchmark project implements the test functions differently.
#if !defined(QFONTENGINE_BENCHMARK)
void tst_QFontEngine::glyphCount()
{
QFETCH_GLOBAL(const QFontEngine::Type, engineType);
QFETCH(const Candidate, candidate);
if (!candidate.isFontAvailable())
QSKIP("Font is not available");
const auto fontEngine = candidate.fontEngine();
QCOMPARE(fontEngine->type(), engineType);
QCOMPARE(fontEngine->glyphCount(), candidate.expectedGlyphCount);
}
void tst_QFontEngine::glyphIndex()
{
QFETCH_GLOBAL(const QFontEngine::Type, engineType);
QFETCH(const Candidate, candidate);
if (!candidate.isFontAvailable())
QSKIP("Font is not available");
const auto fontEngine = candidate.fontEngine();
QCOMPARE(fontEngine->type(), engineType);
QCOMPARE(fontEngine->glyphIndex(candidate.ucs4), candidate.expectedGlyphIndex);
}
#endif // !define(QFONTENGINE_BENCHMARK)

View File

@ -1,6 +1,7 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(qfontengine)
add_subdirectory(qfontmetrics)
add_subdirectory(qtext)
add_subdirectory(qtextdocument)

View File

@ -0,0 +1,18 @@
# Copyright (C) 2024 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_bench_QFontEngine Binary:
#####################################################################
qt_internal_add_benchmark(tst_bench_QFontEngine
SOURCES
../../../../auto/gui/text/qfontengine/tst_qfontengine.cpp
tst_qfontengine.cpp
DEFINES
QFONTENGINE_BENCHMARK
LIBRARIES
Qt::Gui
Qt::GuiPrivate
Qt::Test
)

View File

@ -0,0 +1,33 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#define QFONTENGINE_COMMON
#include "../../../../auto/gui/text/qfontengine/tst_qfontengine.cpp"
using namespace Qt::StringLiterals;
void tst_QFontEngine::glyphCount()
{
QFETCH(const Candidate, candidate);
if (!candidate.isFontAvailable())
QSKIP("Font is not available");
const auto fontEngine = candidate.fontEngine();
QBENCHMARK {
fontEngine->glyphCount();
}
}
void tst_QFontEngine::glyphIndex()
{
QFETCH(const Candidate, candidate);
if (!candidate.isFontAvailable())
QSKIP("Font is not available");
const auto fontEngine = candidate.fontEngine();
QBENCHMARK {
fontEngine->glyphIndex(candidate.ucs4);
}
}