aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp7
-rw-r--r--tests/auto/quick/scenegraph/data/neg_double.ttfbin0 -> 32896 bytes
-rw-r--r--tests/auto/quick/scenegraph/data/neg_single.ttfbin0 -> 32876 bytes
-rw-r--r--tests/auto/quick/scenegraph/data/ok_double.ttfbin0 -> 32896 bytes
-rw-r--r--tests/auto/quick/scenegraph/data/ok_single.ttfbin0 -> 28780 bytes
-rw-r--r--tests/auto/quick/scenegraph/data/pregeneratedDistanceFieldCache.qml21
-rw-r--r--tests/auto/quick/scenegraph/data/qtdf_invalidgeometry_5_12.ttfbin0 -> 32492 bytes
-rw-r--r--tests/auto/quick/scenegraph/data/qtdf_multitexture_5_12.ttfbin0 -> 32476 bytes
-rw-r--r--tests/auto/quick/scenegraph/data/wild.ttfbin0 -> 32896 bytes
-rw-r--r--tests/auto/quick/scenegraph/tst_scenegraph.cpp73
10 files changed, 100 insertions, 1 deletions
diff --git a/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp b/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp
index ad0551433a..22fd972d53 100644
--- a/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp
+++ b/src/quick/scenegraph/qsgrhidistancefieldglyphcache.cpp
@@ -441,8 +441,13 @@ bool QSGRhiDistanceFieldGlyphCache::loadPregeneratedCache(const QRawFont &font)
const quint32 allocWidth = Qtdf::fetch<quint32>(textureRecord, Qtdf::allocatedWidth);
const quint32 allocHeight = Qtdf::fetch<quint32>(textureRecord, Qtdf::allocatedHeight);
const quint32 maxSize = quint32(m_maxTextureSize);
+ // maxSize is the slice height of the area allocator, and a glyph that crosses a slice
+ // boundary expands the texture it is assigned to past the slice, by up to one glyph
+ // height - and a glyph can be no taller than the slice itself. The
+ // tallest valid texture is therefore 2 * maxSize - 1.
+ const quint32 maxTexHeight = maxSize * 2 - 1;
if (allocX > maxSize || allocY > maxSize
- || allocWidth > maxSize || allocHeight > maxSize) {
+ || allocWidth > maxSize || allocHeight > maxTexHeight) {
qWarning("Invalid texture geometry in qtdf table in font '%s'",
qPrintable(font.familyName()));
return false;
diff --git a/tests/auto/quick/scenegraph/data/neg_double.ttf b/tests/auto/quick/scenegraph/data/neg_double.ttf
new file mode 100644
index 0000000000..88e80a10c5
--- /dev/null
+++ b/tests/auto/quick/scenegraph/data/neg_double.ttf
Binary files differ
diff --git a/tests/auto/quick/scenegraph/data/neg_single.ttf b/tests/auto/quick/scenegraph/data/neg_single.ttf
new file mode 100644
index 0000000000..96d1861ad5
--- /dev/null
+++ b/tests/auto/quick/scenegraph/data/neg_single.ttf
Binary files differ
diff --git a/tests/auto/quick/scenegraph/data/ok_double.ttf b/tests/auto/quick/scenegraph/data/ok_double.ttf
new file mode 100644
index 0000000000..d95da04c1c
--- /dev/null
+++ b/tests/auto/quick/scenegraph/data/ok_double.ttf
Binary files differ
diff --git a/tests/auto/quick/scenegraph/data/ok_single.ttf b/tests/auto/quick/scenegraph/data/ok_single.ttf
new file mode 100644
index 0000000000..7f16fcdb64
--- /dev/null
+++ b/tests/auto/quick/scenegraph/data/ok_single.ttf
Binary files differ
diff --git a/tests/auto/quick/scenegraph/data/pregeneratedDistanceFieldCache.qml b/tests/auto/quick/scenegraph/data/pregeneratedDistanceFieldCache.qml
new file mode 100644
index 0000000000..d7b72a2849
--- /dev/null
+++ b/tests/auto/quick/scenegraph/data/pregeneratedDistanceFieldCache.qml
@@ -0,0 +1,21 @@
+// Copyright (C) 2026 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import QtQuick
+
+Rectangle {
+ width: 320
+ height: 200
+ color: "white"
+
+ property alias fontFamily: text.font.family
+
+ Text {
+ id: text
+ anchors.centerIn: parent
+ renderType: Text.QtRendering
+ text: "ABC"
+ color: "black"
+ font.pixelSize: 64
+ }
+}
diff --git a/tests/auto/quick/scenegraph/data/qtdf_invalidgeometry_5_12.ttf b/tests/auto/quick/scenegraph/data/qtdf_invalidgeometry_5_12.ttf
new file mode 100644
index 0000000000..57159c0c72
--- /dev/null
+++ b/tests/auto/quick/scenegraph/data/qtdf_invalidgeometry_5_12.ttf
Binary files differ
diff --git a/tests/auto/quick/scenegraph/data/qtdf_multitexture_5_12.ttf b/tests/auto/quick/scenegraph/data/qtdf_multitexture_5_12.ttf
new file mode 100644
index 0000000000..e9f336b5a5
--- /dev/null
+++ b/tests/auto/quick/scenegraph/data/qtdf_multitexture_5_12.ttf
Binary files differ
diff --git a/tests/auto/quick/scenegraph/data/wild.ttf b/tests/auto/quick/scenegraph/data/wild.ttf
new file mode 100644
index 0000000000..b2c92fbffa
--- /dev/null
+++ b/tests/auto/quick/scenegraph/data/wild.ttf
Binary files differ
diff --git a/tests/auto/quick/scenegraph/tst_scenegraph.cpp b/tests/auto/quick/scenegraph/tst_scenegraph.cpp
index 4e0ebc2877..e32733fd4a 100644
--- a/tests/auto/quick/scenegraph/tst_scenegraph.cpp
+++ b/tests/auto/quick/scenegraph/tst_scenegraph.cpp
@@ -108,6 +108,8 @@ private slots:
void resizeTextureFromImage();
void textureNativeInterface();
void distanceFieldCacheInvalidation();
+ void pregeneratedDistanceFieldCache_data();
+ void pregeneratedDistanceFieldCache();
void unexposeDuringPolish();
#ifdef QT_BUILD_INTERNAL
@@ -909,6 +911,77 @@ void tst_SceneGraph::distanceFieldCacheInvalidation()
}
}
+void tst_SceneGraph::pregeneratedDistanceFieldCache_data()
+{
+ QTest::addColumn<QString>("fontFileName");
+ QTest::addColumn<bool>("expectLoaded");
+
+ // Various tests for
+ QTest::newRow("control, one 64x64 texture") << QStringLiteral("ok_single.ttf") << true;
+ QTest::newRow("control, two 64x64 textures") << QStringLiteral("ok_double.ttf") << true;
+ QTest::newRow("control, two 64x64 textures") << QStringLiteral("ok_double.ttf") << true;
+ QTest::newRow("allocatedWidth -1, height 256 -> size -256") << QStringLiteral("neg_single.ttf") << true;
+ QTest::newRow("allocatedWidth -1, height 1024 -> size -1024, pointer slides back 1 KB") << QStringLiteral("neg_double.ttf") << true;
+ QTest::newRow("allocatedWidth INT_MIN -> size -2147483648") << QStringLiteral("wild.ttf") << true;
+
+ // In the 5.12 version of the qtdf format, the texture size in the header
+ // is the slice height of the stacked area allocator, and the texture
+ // holding a glyph that crosses a slice boundary is taller than this.
+ QTest::newRow("multiple textures, version 5.12")
+ << QStringLiteral("qtdf_multitexture_5_12.ttf") << true;
+
+ // QTBUG-149508: an allocated height with the high bit set becomes
+ // negative when narrowed to int, defeating the payload bounds check.
+ // Such geometry must be rejected.
+ QTest::newRow("invalid geometry, version 5.12")
+ << QStringLiteral("qtdf_invalidgeometry_5_12.ttf") << false;
+}
+
+void tst_SceneGraph::pregeneratedDistanceFieldCache()
+{
+ if (!isRunningOnRhi())
+ QSKIP("Skipping complex rendering tests due to not running with QRhi");
+
+ QFETCH(QString, fontFileName);
+ QFETCH(bool, expectLoaded);
+
+ const int fontId = QFontDatabase::addApplicationFont(testFile(fontFileName));
+ QVERIFY(fontId >= 0);
+ const auto cleanup = qScopeGuard([fontId] { QFontDatabase::removeApplicationFont(fontId); });
+
+ const QStringList families = QFontDatabase::applicationFontFamilies(fontId);
+ QVERIFY(!families.isEmpty());
+
+ // Verify that the font resolves and carries a pregenerated cache, so that
+ // the text below cannot accidentally pass by using another font or by
+ // generating distance fields dynamically.
+ QCOMPARE(QFontInfo(QFont(families.first())).family(), families.first());
+ {
+ const QRawFont rawFont(testFile(fontFileName), 32.0);
+ QVERIFY(rawFont.isValid());
+ QVERIFY(!rawFont.fontTable("qtdf").isEmpty());
+ }
+
+ // The scene renders "ABC" with the given font. The pregenerated cache
+ // contains the glyphs for "A" and "B" in the first texture, where "B"
+ // crosses the boundary into the next slice, and "C" in the second
+ // texture. The distance field data marks the glyphs' texture rects as
+ // fully inside, so if (and only if) the pregenerated cache is loaded, the
+ // glyphs are rendered as solid boxes.
+ QQuickView view;
+ view.setInitialProperties({{ QStringLiteral("fontFamily"), families.first() }});
+ view.setSource(testFileUrl(QLatin1String("pregeneratedDistanceFieldCache.qml")));
+ view.show();
+ QVERIFY(QTest::qWaitForWindowExposed(&view));
+
+ const QImage content = view.grabWindow();
+ QVERIFY(!content.isNull());
+ if (expectLoaded)
+ QVERIFY(containsSomethingOtherThanWhite(content));
+ else
+ QVERIFY(!containsSomethingOtherThanWhite(content));
+}
+
class NotificationItem : public QQuickItem
{
Q_OBJECT