diff options
author | Oliver Eftevaag <oliver.eftevaag@qt.io> | 2021-10-08 16:20:35 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2022-03-02 17:14:40 +0000 |
commit | b8c5634911444c2168b8f2019e7f083fe0248f5a (patch) | |
tree | 3a763c296f2b0e9601a62f360fd239720fa1a5e2 | |
parent | 593b42df141bde3725444f62434995ed5530dd7f (diff) |
QQuickText: fix fractional pointSize rounding error bug
Problem: The layout is initialized with a copy of the current font
before the laying out begins.
In cases when setupTextLayout() iterates through the main loop multiple
times, it will update the layout font, if it differs. (See code below)
if (!once) {
if (pixelSize)
scaledFont.setPixelSize(scaledFontSize);
else
scaledFont.setPointSizeF(scaledFontSize);
if (layout.font() != scaledFont)
layout.setFont(scaledFont);
}
The whole reason why we might update the font in the first place, is
because the QQuickText has a fontSizeMode property which can be set to
e.g. HorizontalFit, which will cause the layouting to downscale the font
in order to fit (assuming the text doesn't fit the space available),
instead of eliding.
The problem here is that QFont internally uses a float to store the point
size, and we would convert that value to an int when temporarily storing
it on the stack. This would modify the pointSize value in cases where
the pointSize is fractional, and cause a mismatch between the QQuickText
and QTextLayout.
The lineWidth is set only during the first loop iteration. During
successive iterations of the main loop, the layout's font would be updated
to one that has either a floored or ceiled pointSize.
If the pointSize is a fractional value, and is ceiled when being updated
during the second loop iteration, the layout would use a larger font
value, which would cause the QTextLayout::naturalTextWidth() to return a
larger value than that of the lineWidth, triggering eliding.
Solution: Keep the font precision, by storing the values as floats
instead of ints.
Fixes: QTBUG-92006
Change-Id: Ibf64ac2dfbf262c6aae05b8eb8251d2f5a869b69
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
(cherry picked from commit ae62122a8f57b1de654e891125f19d2e18d6f5df)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r-- | src/quick/items/qquicktext.cpp | 49 | ||||
-rw-r--r-- | tests/auto/quick/qquicktext/tst_qquicktext.cpp | 27 | ||||
-rw-r--r-- | tests/auto/quickcontrols2/qquickcontrol/data/fractionalFontSize.qml | 18 | ||||
-rw-r--r-- | tests/auto/quickcontrols2/qquickcontrol/tst_qquickcontrol.cpp | 20 |
4 files changed, 88 insertions, 26 deletions
diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp index f5dca6e0f5..66baa7ed7c 100644 --- a/src/quick/items/qquicktext.cpp +++ b/src/quick/items/qquicktext.cpp @@ -772,11 +772,15 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline) const bool pixelSize = font.pixelSize() != -1; QString layoutText = layout.text(); - int largeFont = pixelSize ? font.pixelSize() : font.pointSize(); - int smallFont = fontSizeMode() != QQuickText::FixedSize - ? qMin(pixelSize ? minimumPixelSize() : minimumPointSize(), largeFont) - : largeFont; - int scaledFontSize = largeFont; + const qreal minimumSize = pixelSize + ? static_cast<qreal>(minimumPixelSize()) + : minimumPointSize(); + qreal largeFont = pixelSize ? font.pixelSize() : font.pointSizeF(); + qreal smallFont = fontSizeMode() != QQuickText::FixedSize + ? qMin<qreal>(minimumSize, largeFont) + : largeFont; + qreal scaledFontSize = largeFont; + const qreal sizeFittingThreshold(0.01); bool widthChanged = false; widthExceeded = availableWidth() <= 0 && (singlelineElide || canWrap || horizontalFit); @@ -805,7 +809,7 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline) if (pixelSize) scaledFont.setPixelSize(scaledFontSize); else - scaledFont.setPointSize(scaledFontSize); + scaledFont.setPointSizeF(scaledFontSize); if (layout.font() != scaledFont) layout.setFont(scaledFont); } @@ -1069,40 +1073,45 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline) if (!horizontalFit && !verticalFit) break; + // Can't find a better fit + if (qFuzzyCompare(smallFont, largeFont)) + break; + // Try and find a font size that better fits the dimensions of the element. if (horizontalFit) { if (unelidedRect.width() > lineWidth || (!verticalFit && wrapped)) { widthExceeded = true; - largeFont = scaledFontSize - 1; - if (smallFont > largeFont) - break; + largeFont = scaledFontSize; + scaledFontSize = (smallFont + largeFont) / 2; - if (pixelSize) - scaledFont.setPixelSize(scaledFontSize); - else - scaledFont.setPointSize(scaledFontSize); + continue; } else if (!verticalFit) { smallFont = scaledFontSize; - if (smallFont == largeFont) + + // Check to see if the current scaledFontSize is acceptable + if ((largeFont - smallFont) < sizeFittingThreshold) break; - scaledFontSize = (smallFont + largeFont + 1) / 2; + + scaledFontSize = (smallFont + largeFont) / 2; } } if (verticalFit) { if (truncateHeight || unelidedRect.height() > maxHeight) { heightExceeded = true; - largeFont = scaledFontSize - 1; - if (smallFont > largeFont) - break; + largeFont = scaledFontSize; + scaledFontSize = (smallFont + largeFont) / 2; } else { smallFont = scaledFontSize; - if (smallFont == largeFont) + + // Check to see if the current scaledFontSize is acceptable + if ((largeFont - smallFont) < sizeFittingThreshold) break; - scaledFontSize = (smallFont + largeFont + 1) / 2; + + scaledFontSize = (smallFont + largeFont) / 2; } } } diff --git a/tests/auto/quick/qquicktext/tst_qquicktext.cpp b/tests/auto/quick/qquicktext/tst_qquicktext.cpp index bf90a8efd4..5f3679ca00 100644 --- a/tests/auto/quick/qquicktext/tst_qquicktext.cpp +++ b/tests/auto/quick/qquicktext/tst_qquicktext.cpp @@ -4049,7 +4049,11 @@ static qreal expectedBaselineScaled(QQuickText *item) { QFont font = item->font(); QTextLayout layout(item->text().replace(QLatin1Char('\n'), QChar::LineSeparator)); - do { + + qreal low = 0; + qreal high = 10000; + + while (low < high) { layout.setFont(font); qreal width = 0; layout.beginLayout(); @@ -4059,12 +4063,23 @@ static qreal expectedBaselineScaled(QQuickText *item) } layout.endLayout(); - if (width < item->width()) { - QFontMetricsF fm(layout.font()); - return fm.ascent() + item->topPadding(); + if (width > item->width()) { + high = font.pointSizeF(); + font.setPointSizeF((high + low) / 2); + } else { + low = font.pointSizeF(); + + // When fontSizeMode != FixedSize, the font size will be scaled to a value + // The goal is to find a pointSize that uses as much space as possible while + // still fitting inside the available space. 0.01 is chosen as the threshold. + if ((high - low) < qreal(0.01)) { + QFontMetricsF fm(layout.font()); + return fm.ascent() + item->topPadding(); + } + + font.setPointSizeF((high + low) / 2); } - font.setPointSize(font.pointSize() - 1); - } while (font.pointSize() > 0); + } return item->topPadding(); } diff --git a/tests/auto/quickcontrols2/qquickcontrol/data/fractionalFontSize.qml b/tests/auto/quickcontrols2/qquickcontrol/data/fractionalFontSize.qml new file mode 100644 index 0000000000..a4574e460e --- /dev/null +++ b/tests/auto/quickcontrols2/qquickcontrol/data/fractionalFontSize.qml @@ -0,0 +1,18 @@ +import QtQuick +import QtQuick.Controls + +ApplicationWindow { + width: 400 + height: 400 + + property alias control: ctrl + + Control { + id: ctrl + contentItem: Text { + font.pointSize: 10.5 + elide: Text.ElideRight + text: "This is some sample text" + } + } +} diff --git a/tests/auto/quickcontrols2/qquickcontrol/tst_qquickcontrol.cpp b/tests/auto/quickcontrols2/qquickcontrol/tst_qquickcontrol.cpp index 94dccf6115..3ee9cc2328 100644 --- a/tests/auto/quickcontrols2/qquickcontrol/tst_qquickcontrol.cpp +++ b/tests/auto/quickcontrols2/qquickcontrol/tst_qquickcontrol.cpp @@ -33,6 +33,7 @@ #include <QtQuickTestUtils/private/visualtestutils_p.h> #include <QtQuickTemplates2/private/qquickbutton_p.h> #include <QtQuickControlsTestUtils/private/qtest_quickcontrols_p.h> +#include <QtQuick/private/qquicktext_p_p.h> using namespace QQuickVisualTestUtils; @@ -46,6 +47,7 @@ public: private slots: void initTestCase() override; void flickable(); + void fractionalFontSize(); private: QScopedPointer<QPointingDevice> touchDevice; @@ -93,6 +95,24 @@ void tst_QQuickControl::flickable() QTRY_COMPARE(buttonClickedSpy.count(), 1); } +void tst_QQuickControl::fractionalFontSize() +{ + QQuickApplicationHelper helper(this, QStringLiteral("fractionalFontSize.qml")); + QQuickWindow *window = helper.window; + window->show(); + QVERIFY(QTest::qWaitForWindowExposed(window)); + const QQuickControl *control = window->property("control").value<QQuickControl *>(); + QVERIFY(control); + QQuickText *contentItem = qobject_cast<QQuickText *>(control->contentItem()); + QVERIFY(contentItem); + + QVERIFY(!contentItem->truncated()); + + QVERIFY2(qFuzzyCompare(contentItem->contentWidth(), + QQuickTextPrivate::get(contentItem)->layout.boundingRect().width()), + "The QQuickText::contentWidth() doesn't match the layout's preferred text width"); +} + QTEST_QUICKCONTROLS_MAIN(tst_QQuickControl) #include "tst_qquickcontrol.moc" |