summaryrefslogtreecommitdiffstats
path: root/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp')
-rw-r--r--tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp605
1 files changed, 527 insertions, 78 deletions
diff --git a/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp b/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp
index 9c477589f9..209f5a56e2 100644
--- a/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp
+++ b/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp
@@ -1,38 +1,14 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
/*
!!!!!! Warning !!!!!
Please don't save this file in emacs. It contains utf8 text sequences emacs will
silently convert to a series of question marks.
*/
-#include <QtTest/QtTest>
+#include <QTest>
@@ -62,16 +38,21 @@ private slots:
void lineBreaking();
#ifdef QT_BUILD_INTERNAL
void simpleBoundingRect();
+ void threeLineBoundingRect_data();
void threeLineBoundingRect();
void boundingRectWithLongLineAndNoWrap();
void forcedBreaks();
void breakAny();
void noWrap();
+
void cursorToXForInlineObjects();
void cursorToXForSetColumns();
void cursorToXForTrailingSpaces_data();
void cursorToXForTrailingSpaces();
void cursorToXInvalidInput();
+ void cursorToXForBidiBoundaries_data();
+ void cursorToXForBidiBoundaries();
+
void horizontalAlignment_data();
void horizontalAlignment();
void horizontalAlignmentMultiline_data();
@@ -85,6 +66,8 @@ private slots:
#ifdef QT_BUILD_INTERNAL
void xToCursorAtEndOfLine();
#endif
+ void xToCursorForBidiEnds_data();
+ void xToCursorForBidiEnds();
void boundingRectTopLeft();
void graphemeBoundaryForSurrogatePairs();
void tabStops();
@@ -138,6 +121,13 @@ private slots:
void noModificationOfInputString();
void superscriptCrash_qtbug53911();
void showLineAndParagraphSeparatorsCrash();
+ void koreanWordWrap();
+ void tooManyDirectionalCharctersCrash_qtbug77819();
+ void softHyphens_data();
+ void softHyphens();
+ void min_maximumWidth_data();
+ void min_maximumWidth();
+ void negativeLineWidth();
private:
QFont testFont;
@@ -302,29 +292,60 @@ void tst_QTextLayout::simpleBoundingRect()
QString hello("hello world");
- const int width = hello.length() * testFont.pixelSize();
+ const int width = hello.size() * testFont.pixelSize();
QTextLayout layout(hello, testFont);
layout.beginLayout();
QTextLine line = layout.createLine();
line.setLineWidth(width);
- QCOMPARE(line.textLength(), hello.length());
+ QCOMPARE(line.textLength(), hello.size());
QCOMPARE(layout.boundingRect(), QRectF(0, 0, width, QFontMetrics(testFont).height()));
}
+void tst_QTextLayout::threeLineBoundingRect_data()
+{
+ QTest::addColumn<QChar>("wordBoundary1");
+ QTest::addColumn<QChar>("wordBoundary2");
+ QTest::newRow("2x' '") << QChar(' ') << QChar(' ');
+ QTest::newRow("2x'\\n'") << QChar('\n') << QChar('\n');
+ QTest::newRow("' ' + '\\n'") << QChar(' ') << QChar('\n');
+ QTest::newRow("'\\n' + ' '") << QChar('\n') << QChar(' ');
+ QTest::newRow("2x'\\t'") << QChar('\t') << QChar('\t');
+ QTest::newRow("2xsoft hyphen") << QChar(0xad) << QChar(0xad);
+ QTest::newRow("2x'-'") << QChar('-') << QChar('-');
+ QTest::newRow("2x'/'") << QChar('/') << QChar('/');
+ QTest::newRow("soft hyphen + ' '") << QChar(0xad) << QChar(' ');
+ QTest::newRow("soft hyphen + '\\n'") << QChar(0xad) << QChar('\n');
+ QTest::newRow("soft hyphen + '-'") << QChar(0xad) << QChar('-');
+ QTest::newRow("' ' + soft hyphen") << QChar(' ') << QChar(0xad);
+ QTest::newRow("'\\n' + soft hyphen") << QChar('\n') << QChar(0xad);
+ QTest::newRow("'-' + soft hyphen") << QChar('-') << QChar(0xad);
+}
+
void tst_QTextLayout::threeLineBoundingRect()
{
/* stricter check. break text into three lines */
+ QFETCH(QChar, wordBoundary1);
+ QFETCH(QChar, wordBoundary2);
QString firstWord("hello");
- QString secondWord("world");
- QString thirdWord("test");
- QString text(firstWord + ' ' + secondWord + ' ' + thirdWord);
-
- const int firstLineWidth = firstWord.length() * testFont.pixelSize();
- const int secondLineWidth = secondWord.length() * testFont.pixelSize();
- const int thirdLineWidth = thirdWord.length() * testFont.pixelSize();
+ QString secondWord("test");
+ QString thirdWord("world");
+ QString text(firstWord + wordBoundary1 + secondWord + wordBoundary2 + thirdWord);
+
+ int firstLineWidth = firstWord.size() * testFont.pixelSize();
+ int secondLineWidth = secondWord.size() * testFont.pixelSize();
+ int thirdLineWidth = thirdWord.size() * testFont.pixelSize();
+ // Trailing spaces do not count to line width:
+ if (!wordBoundary1.isSpace())
+ firstLineWidth += testFont.pixelSize();
+ if (!wordBoundary2.isSpace())
+ secondLineWidth += testFont.pixelSize();
+ // But trailing spaces do count to line length:
+ const int firstLineLength = firstWord.size() + 1;
+ const int secondLineLength = secondWord.size() + 1;
+ const int thirdLineLength = thirdWord.size();
const int longestLine = qMax(firstLineWidth, qMax(secondLineWidth, thirdLineWidth));
@@ -337,8 +358,7 @@ void tst_QTextLayout::threeLineBoundingRect()
line.setLineWidth(firstLineWidth);
line.setPosition(QPoint(0, y));
QCOMPARE(line.textStart(), pos);
- // + 1 for trailing space
- QCOMPARE(line.textLength(), firstWord.length() + 1);
+ QCOMPARE(line.textLength(), firstLineLength);
QCOMPARE(qRound(line.naturalTextWidth()), firstLineWidth);
pos += line.textLength();
@@ -347,9 +367,8 @@ void tst_QTextLayout::threeLineBoundingRect()
line = layout.createLine();
line.setLineWidth(secondLineWidth);
line.setPosition(QPoint(0, y));
- // + 1 for trailing space
QCOMPARE(line.textStart(), pos);
- QCOMPARE(line.textLength(), secondWord.length() + 1);
+ QCOMPARE(line.textLength(), secondLineLength);
QCOMPARE(qRound(line.naturalTextWidth()), secondLineWidth);
pos += line.textLength();
@@ -358,9 +377,8 @@ void tst_QTextLayout::threeLineBoundingRect()
line = layout.createLine();
line.setLineWidth(secondLineWidth);
line.setPosition(QPoint(0, y));
- // no trailing space here!
QCOMPARE(line.textStart(), pos);
- QCOMPARE(line.textLength(), thirdWord.length());
+ QCOMPARE(line.textLength(), thirdLineLength);
QCOMPARE(qRound(line.naturalTextWidth()), thirdLineWidth);
y += qRound(line.ascent() + line.descent());
@@ -371,7 +389,7 @@ void tst_QTextLayout::boundingRectWithLongLineAndNoWrap()
{
QString longString("thisisaverylongstringthatcannotbewrappedatallitjustgoesonandonlikeonebigword");
- const int width = longString.length() * testFont.pixelSize() / 20; // very small widthx
+ const int width = longString.size() * testFont.pixelSize() / 20; // very small widthx
QTextLayout layout(longString, testFont);
layout.beginLayout();
@@ -496,18 +514,24 @@ void tst_QTextLayout::noWrap()
void tst_QTextLayout::cursorToXForInlineObjects()
{
- QChar ch(QChar::ObjectReplacementCharacter);
- QString text(ch);
- QTextLayout layout(text, testFont);
- layout.beginLayout();
+ QString text = QStringLiteral("<html><body><img src=\"\" width=\"32\" height=\"32\" /></body></html>");
- QTextEngine *engine = layout.engine();
- const int item = engine->findItem(0);
- engine->layoutData->items[item].width = 32;
+ QTextDocument document;
+ document.setHtml(text);
+ QCOMPARE(document.blockCount(), 1);
- QTextLine line = layout.createLine();
- line.setLineWidth(0x10000);
+ // Trigger layout
+ {
+ QImage img(1, 1, QImage::Format_ARGB32_Premultiplied);
+ QPainter p(&img);
+ document.drawContents(&p);
+ }
+
+ QTextLayout *layout = document.firstBlock().layout();
+ QVERIFY(layout != nullptr);
+ QCOMPARE(layout->lineCount(), 1);
+ QTextLine line = layout->lineAt(0);
QCOMPARE(line.cursorToX(0), qreal(0));
QCOMPARE(line.cursorToX(1), qreal(32));
}
@@ -700,6 +724,76 @@ void tst_QTextLayout::cursorToXInvalidInput()
QCOMPARE(cursorPos, 3);
}
+void tst_QTextLayout::cursorToXForBidiBoundaries_data()
+{
+ QTest::addColumn<Qt::LayoutDirection>("textDirection");
+ QTest::addColumn<QString>("text");
+ QTest::addColumn<int>("cursorPosition");
+ QTest::addColumn<int>("runsToInclude");
+
+ QTest::addRow("LTR, abcشزذabc, 0") << Qt::LeftToRight << "abcشزذabc"
+ << 0 << 0;
+ QTest::addRow("RTL, abcشزذabc, 9") << Qt::RightToLeft << "abcشزذabc"
+ << 9 << 1;
+ QTest::addRow("LTR, abcشزذabc, 3") << Qt::LeftToRight << "abcشزذabc"
+ << 0 << 0;
+ QTest::addRow("RTL, abcشزذabc, 6") << Qt::RightToLeft << "abcشزذabc"
+ << 9 << 1;
+
+ QTest::addRow("LTR, شزذabcشزذ, 0") << Qt::LeftToRight << "شزذabcشزذ"
+ << 0 << 1;
+ QTest::addRow("RTL, شزذabcشزذ, 9") << Qt::RightToLeft << "شزذabcشزذ"
+ << 9 << 0;
+ QTest::addRow("LTR, شزذabcشزذ, 3") << Qt::LeftToRight << "شزذabcشزذ"
+ << 3 << 1;
+ QTest::addRow("RTL, شزذabcشزذ, 3") << Qt::RightToLeft << "شزذabcشزذ"
+ << 3 << 2;
+ QTest::addRow("LTR, شزذabcشزذ, 6") << Qt::LeftToRight << "شزذabcشزذ"
+ << 6 << 2;
+ QTest::addRow("RTL, شزذabcشزذ, 6") << Qt::RightToLeft << "شزذabcشزذ"
+ << 6 << 1;
+}
+
+void tst_QTextLayout::cursorToXForBidiBoundaries()
+{
+ QFETCH(Qt::LayoutDirection, textDirection);
+ QFETCH(QString, text);
+ QFETCH(int, cursorPosition);
+ QFETCH(int, runsToInclude);
+
+ QTextOption option;
+ option.setTextDirection(textDirection);
+
+ QTextLayout layout(text, testFont);
+ layout.setTextOption(option);
+ layout.beginLayout();
+
+ {
+ QTextLine line = layout.createLine();
+ line.setLineWidth(0x10000);
+ }
+ layout.endLayout();
+
+ QTextLine line = layout.lineAt(0);
+ QList<QGlyphRun> glyphRuns = line.glyphRuns(-1,
+ -1,
+ QTextLayout::RetrieveStringIndexes
+ | QTextLayout::RetrieveGlyphIndexes);
+ QVERIFY(runsToInclude <= glyphRuns.size());
+
+ std::sort(glyphRuns.begin(), glyphRuns.end(),
+ [](const QGlyphRun &first, const QGlyphRun &second) {
+ return first.stringIndexes().first() < second.stringIndexes().first();
+ });
+
+ qreal expectedX = 0.0;
+ for (int i = 0; i < runsToInclude; ++i) {
+ expectedX += glyphRuns.at(i).boundingRect().width();
+ }
+
+ QCOMPARE(line.cursorToX(cursorPosition), expectedX);
+}
+
void tst_QTextLayout::horizontalAlignment_data()
{
qreal width = TESTFONT_SIZE * 4;
@@ -992,7 +1086,7 @@ void tst_QTextLayout::defaultWordSeparators_data()
QString separators(".,:;-<>[](){}=/+%&^*");
separators += QLatin1String("!?");
- for (int i = 0; i < separators.count(); ++i) {
+ for (int i = 0; i < separators.size(); ++i) {
QTest::newRow(QString::number(i).toLatin1().data())
<< QString::fromLatin1("abcd") + separators.at(i) + QString::fromLatin1("efgh")
<< 0 << 4;
@@ -1072,7 +1166,7 @@ void tst_QTextLayout::xToCursorAtEndOfLine()
QString text = "FirstLine SecondLine";
text.replace('\n', QChar::LineSeparator);
- const qreal firstLineWidth = QString("FirstLine").length() * testFont.pixelSize();
+ const qreal firstLineWidth = QString("FirstLine").size() * testFont.pixelSize();
QTextLayout layout(text, testFont);
layout.setCacheEnabled(true);
@@ -1092,6 +1186,60 @@ void tst_QTextLayout::xToCursorAtEndOfLine()
}
#endif
+
+void tst_QTextLayout::xToCursorForBidiEnds_data()
+{
+ QTest::addColumn<Qt::LayoutDirection>("textDirection");
+ QTest::addColumn<QString>("text");
+ QTest::addColumn<int>("leftPosition");
+ QTest::addColumn<int>("rightPosition");
+
+ QTest::addRow("LTR, abcشزذ") << Qt::LeftToRight << "abcشزذ"
+ << 0 << 6;
+ QTest::addRow("RTL, abcشزذ") << Qt::RightToLeft << "abcشزذ"
+ << 6 << 0;
+ QTest::addRow("LTR, شزذabc") << Qt::LeftToRight << "شزذabc"
+ << 0 << 6;
+ QTest::addRow("RTL, شزذabc") << Qt::RightToLeft << "شزذabc"
+ << 6 << 0;
+ QTest::addRow("LTR, شزذ123") << Qt::LeftToRight << "شزذ123"
+ << 0 << 6;
+ QTest::addRow("RTL, شزذ123") << Qt::RightToLeft << "شزذ123"
+ << 6 << 0;
+
+ QTest::addRow("LTR, abcشزذabc") << Qt::LeftToRight << "abcشزذabc"
+ << 0 << 9;
+ QTest::addRow("RTL, abcشزذabc") << Qt::RightToLeft << "abcشزذabc"
+ << 9 << 0;
+ QTest::addRow("LTR, شزذabcشزذ") << Qt::LeftToRight << "شزذabcشزذ"
+ << 0 << 9;
+ QTest::addRow("RTL, شزذabcشزذ") << Qt::RightToLeft << "شزذabcشزذ"
+ << 9 << 0;
+}
+
+void tst_QTextLayout::xToCursorForBidiEnds()
+{
+ QFETCH(Qt::LayoutDirection, textDirection);
+ QFETCH(QString, text);
+ QFETCH(int, leftPosition);
+ QFETCH(int, rightPosition);
+
+ QTextOption option;
+ option.setTextDirection(textDirection);
+
+ QTextLayout layout(text, testFont);
+ layout.setTextOption(option);
+ layout.beginLayout();
+
+ QTextLine line = layout.createLine();
+ line.setLineWidth(0x10000);
+
+ QCOMPARE(line.xToCursor(0), leftPosition);
+ QCOMPARE(line.xToCursor(line.width()), rightPosition);
+
+ layout.endLayout();
+}
+
void tst_QTextLayout::boundingRectTopLeft()
{
QString text = "FirstLine\nSecondLine";
@@ -1116,8 +1264,8 @@ void tst_QTextLayout::graphemeBoundaryForSurrogatePairs()
{
QString txt;
txt.append(QLatin1Char('a'));
- txt.append(0xd87e);
- txt.append(0xdc25);
+ txt.append(QChar(0xd87e));
+ txt.append(QChar(0xdc25));
txt.append(QLatin1Char('b'));
QTextLayout layout(txt);
QTextEngine *engine = layout.engine();
@@ -1161,7 +1309,7 @@ void tst_QTextLayout::integerOverflow()
QVERIFY(line.isValid());
line.setLineWidth(INT_MAX);
- QCOMPARE(line.textLength(), txt.length());
+ QCOMPARE(line.textLength(), txt.size());
QVERIFY(!layout.createLine().isValid());
@@ -1650,8 +1798,8 @@ QT_END_NAMESPACE
void tst_QTextLayout::testTabDPIScale()
{
class MyPaintDevice : public QPaintDevice {
- QPaintEngine *paintEngine () const { return 0; }
- int metric (QPaintDevice::PaintDeviceMetric metric) const {
+ QPaintEngine *paintEngine () const override { return 0; }
+ int metric (QPaintDevice::PaintDeviceMetric metric) const override {
switch(metric) {
case QPaintDevice::PdmWidth:
case QPaintDevice::PdmHeight:
@@ -1736,7 +1884,7 @@ void tst_QTextLayout::capitalization_allUpperCase()
QTextEngine *engine = layout.engine();
engine->itemize();
- QCOMPARE(engine->layoutData->items.count(), 1);
+ QCOMPARE(engine->layoutData->items.size(), 1);
QCOMPARE(engine->layoutData->items.at(0).analysis.flags, ushort(QScriptAnalysis::Uppercase));
}
@@ -1756,7 +1904,7 @@ void tst_QTextLayout::capitalization_allUpperCase_newline()
QTextEngine *engine = layout.engine();
engine->itemize();
- QCOMPARE(engine->layoutData->items.count(), 3);
+ QCOMPARE(engine->layoutData->items.size(), 3);
QCOMPARE(engine->layoutData->items.at(0).analysis.flags, ushort(QScriptAnalysis::Uppercase));
QCOMPARE(engine->layoutData->items.at(1).analysis.flags, ushort(QScriptAnalysis::LineOrParagraphSeparator));
QCOMPARE(engine->layoutData->items.at(2).analysis.flags, ushort(QScriptAnalysis::Uppercase));
@@ -1774,7 +1922,7 @@ void tst_QTextLayout::capitalization_allLowerCase()
QTextEngine *engine = layout.engine();
engine->itemize();
- QCOMPARE(engine->layoutData->items.count(), 1);
+ QCOMPARE(engine->layoutData->items.size(), 1);
QCOMPARE(engine->layoutData->items.at(0).analysis.flags, ushort(QScriptAnalysis::Lowercase));
}
@@ -1790,7 +1938,7 @@ void tst_QTextLayout::capitalization_smallCaps()
QTextEngine *engine = layout.engine();
engine->itemize();
- QCOMPARE(engine->layoutData->items.count(), 2);
+ QCOMPARE(engine->layoutData->items.size(), 2);
QCOMPARE(engine->layoutData->items.at(0).analysis.flags, ushort(QScriptAnalysis::None));
QCOMPARE(engine->layoutData->items.at(1).analysis.flags, ushort(QScriptAnalysis::SmallCaps));
}
@@ -1807,7 +1955,7 @@ void tst_QTextLayout::capitalization_capitalize()
QTextEngine *engine = layout.engine();
engine->itemize();
- QCOMPARE(engine->layoutData->items.count(), 5);
+ QCOMPARE(engine->layoutData->items.size(), 5);
QCOMPARE(engine->layoutData->items.at(0).analysis.flags, ushort(QScriptAnalysis::Uppercase));
QCOMPARE(engine->layoutData->items.at(1).analysis.flags, ushort(QScriptAnalysis::None));
QCOMPARE(engine->layoutData->items.at(2).analysis.flags, ushort(QScriptAnalysis::Tab));
@@ -1862,6 +2010,34 @@ void tst_QTextLayout::longText()
QVERIFY(line.isValid());
QVERIFY(line.cursorToX(line.textLength() - 1) > 0);
}
+
+ {
+ QTextLayout layout(QString("Qt rocks! ").repeated(200000));
+ layout.setCacheEnabled(true);
+ layout.beginLayout();
+ forever {
+ QTextLine line = layout.createLine();
+ if (!line.isValid())
+ break;
+ }
+ layout.endLayout();
+ QFontMetricsF fm(layout.font());
+ QVERIFY(layout.maximumWidth() - fm.horizontalAdvance(' ') <= QFIXED_MAX);
+ }
+
+ {
+ QTextLayout layout(QString("AAAAAAAA").repeated(200000));
+ layout.setCacheEnabled(true);
+ layout.beginLayout();
+ forever {
+ QTextLine line = layout.createLine();
+ if (!line.isValid())
+ break;
+ }
+ layout.endLayout();
+ QFontMetricsF fm(layout.font());
+ QVERIFY(layout.maximumWidth() - fm.horizontalAdvance('A') <= QFIXED_MAX);
+ }
}
void tst_QTextLayout::widthOfTabs()
@@ -1890,7 +2066,7 @@ void tst_QTextLayout::columnWrapWithTabs()
textLayout.beginLayout();
QTextLine line = textLayout.createLine();
line.setNumColumns(30);
- QCOMPARE(line.textLength(), text.length());
+ QCOMPARE(line.textLength(), text.size());
textLayout.endLayout();
}
@@ -1901,7 +2077,7 @@ void tst_QTextLayout::columnWrapWithTabs()
textLayout.beginLayout();
QTextLine line = textLayout.createLine();
line.setNumColumns(30);
- QVERIFY(line.textLength() < text.length());
+ QVERIFY(line.textLength() < text.size());
textLayout.endLayout();
}
@@ -1967,6 +2143,9 @@ void tst_QTextLayout::textWidthVsWIdth()
layout.setCacheEnabled(true);
QTextOption opt;
opt.setWrapMode(QTextOption::WrapAnywhere);
+#if defined(Q_OS_WIN)
+ layout.setFont(QFont(QString::fromLatin1("Arial")));
+#endif
layout.setTextOption(opt);
layout.setText(QString::fromLatin1(
"g++ -c -m64 -pipe -g -fvisibility=hidden -fvisibility-inlines-hidden -Wall -W -D_REENTRANT -fPIC -DCORE_LIBRARY -DIDE_LIBRARY_BASENAME=\"lib\" -DWITH_TESTS "
@@ -1983,6 +2162,21 @@ void tst_QTextLayout::textWidthVsWIdth()
// minimum right bearing reported by the font engine doesn't cover all the glyphs in the font.
// The result is that this test may fail in some cases. We should fix this by running the test
// with a font that we know have no suprising right bearings. See qtextlayout.cpp for details.
+ QFontMetricsF fontMetrics(layout.font());
+ QSet<char16_t> checked;
+ qreal minimumRightBearing = 0.0;
+ for (int i = 0; i < layout.text().size(); ++i) {
+ QChar c = layout.text().at(i);
+ if (!checked.contains(c.unicode())) {
+ qreal rightBearing = fontMetrics.rightBearing(c);
+ if (rightBearing < minimumRightBearing)
+ minimumRightBearing = rightBearing;
+ checked.insert(c.unicode());
+ }
+ }
+ if (minimumRightBearing < fontMetrics.minRightBearing())
+ QSKIP("Font reports invalid minimum right bearing, and can't be used for this test.");
+
for (int width = 100; width < 1000; ++width) {
layout.beginLayout();
QTextLine line = layout.createLine();
@@ -2066,7 +2260,12 @@ void tst_QTextLayout::cursorInLigatureWithMultipleLines()
void tst_QTextLayout::xToCursorForLigatures()
{
+#if defined(Q_OS_WIN32)
+ QTextLayout layout("fi", QFont("Calibri", 20));
+#else
QTextLayout layout("fi", QFont("Times", 20));
+#endif
+
layout.setCacheEnabled(true);
layout.beginLayout();
QTextLine line = layout.createLine();
@@ -2227,7 +2426,6 @@ void tst_QTextLayout::superscriptCrash_qtbug53911()
for (int j = 0; j < 4; ++j) {
QTextLayout* newTextLayout = new QTextLayout();
newTextLayout->setText(layoutText);
- QList<QTextLayout::FormatRange> formatRanges;
QTextLayout::FormatRange formatRange;
formatRange.format.setFont(QFont());
@@ -2256,8 +2454,7 @@ void tst_QTextLayout::superscriptCrash_qtbug53911()
formatRange.start = 0;
formatRange.length = layoutText.size();
- formatRanges << formatRange;
- newTextLayout->setAdditionalFormats(formatRanges);
+ newTextLayout->setFormats({formatRange});
textLayouts.push_front(newTextLayout);
}
@@ -2284,14 +2481,11 @@ void tst_QTextLayout::nbspWithFormat()
layout.setText(s1 + s2 + nbsp + s3);
QTextLayout::FormatRange formatRange;
- formatRange.start = s1.length() + s2.length();
+ formatRange.start = s1.size() + s2.size();
formatRange.length = 1;
formatRange.format.setFontUnderline(true);
- QList<QTextLayout::FormatRange> overrides;
- overrides.append(formatRange);
-
- layout.setAdditionalFormats(overrides);
+ layout.setFormats({formatRange});
layout.beginLayout();
forever {
@@ -2304,9 +2498,264 @@ void tst_QTextLayout::nbspWithFormat()
QCOMPARE(layout.lineCount(), 2);
QCOMPARE(layout.lineAt(0).textStart(), 0);
- QCOMPARE(layout.lineAt(0).textLength(), s1.length());
- QCOMPARE(layout.lineAt(1).textStart(), s1.length());
- QCOMPARE(layout.lineAt(1).textLength(), s2.length() + 1 + s3.length());
+ QCOMPARE(layout.lineAt(0).textLength(), s1.size());
+ QCOMPARE(layout.lineAt(1).textStart(), s1.size());
+ QCOMPARE(layout.lineAt(1).textLength(), s2.size() + 1 + s3.size());
+}
+
+void tst_QTextLayout::koreanWordWrap()
+{
+ QString s = QString::fromUtf8("안녕하세요 여러분!");
+ QTextLayout layout;
+ QTextOption option = layout.textOption();
+ option.setWrapMode(QTextOption::WordWrap);
+ option.setFlags(QTextOption::Flag(QTextOption::IncludeTrailingSpaces));
+ layout.setTextOption(option);
+ layout.setText(s);
+
+ QFontMetrics metrics(layout.font());
+
+ layout.beginLayout();
+ forever {
+ QTextLine line = layout.createLine();
+ if (!line.isValid())
+ break;
+ line.setLineWidth(metrics.horizontalAdvance(s) * 0.8);
+ }
+ layout.endLayout();
+ QCOMPARE(layout.lineCount(), 2);
+ QCOMPARE(layout.lineAt(0).textLength(), 6);
+ QCOMPARE(layout.lineAt(1).textLength(), 4);
+}
+
+void tst_QTextLayout::tooManyDirectionalCharctersCrash_qtbug77819()
+{
+ QString data;
+ data += QString::fromUtf8("\xe2\x81\xa8"); // U+2068 FSI character
+ data += QString::fromUtf8("\xe2\x81\xa7"); // U+2067 RLI character
+
+ // duplicating the text
+ for (int i = 0; i < 10; i++)
+ data += data;
+
+ // Nothing to test. It must not crash in beginLayout().
+ QTextLayout tl(data);
+ tl.beginLayout();
+ tl.endLayout();
+}
+
+void tst_QTextLayout::softHyphens_data()
+{
+ QTest::addColumn<int>("fontSize");
+
+ QTest::newRow("12") << 12;
+ QTest::newRow("14") << 14;
+ QTest::newRow("16") << 16;
+}
+
+void tst_QTextLayout::softHyphens()
+{
+ QFETCH(int, fontSize);
+ QString text = QStringLiteral("xxxx\u00ad") + QStringLiteral("xxxx\u00ad");
+
+ QFont font;
+ font.setPixelSize(fontSize);
+ font.setHintingPreference(QFont::PreferNoHinting);
+ const float xAdvance = QFontMetricsF(font).horizontalAdvance(QChar::fromLatin1('x'));
+ float shyWidth = 0.0f;
+ QTextLayout layout(text, font);
+ QTextOption option;
+ option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
+ layout.setTextOption(option);
+ {
+ // Calculate the effective width of a line-ending hyphen
+ // This calculation is currently done to work-around odditities on
+ // macOS 11 (see QTBUG-90698).
+ QTextLayout test(QStringLiteral("x\u00ad"), font);
+ // Note: This only works because Qt show the soft-hyphen when ending a text.
+ // This _could_ be considered a bug and the test would need to be changed
+ // if we stop doing that.
+ test.beginLayout();
+ QTextLine line = test.createLine();
+ line.setLineWidth(10 * xAdvance);
+ line.setPosition(QPoint(0, 0));
+ shyWidth = line.naturalTextWidth() - xAdvance;
+ test.endLayout();
+ }
+ qreal linefit;
+ // Loose fit
+ // xxxx- |
+ // xxxx- |
+ {
+ int pos = 0;
+ int y = 0;
+ layout.beginLayout();
+ QTextLine line = layout.createLine();
+ line.setLineWidth(qCeil(5 * xAdvance + shyWidth) + 1);
+ line.setPosition(QPoint(0, y));
+ QCOMPARE(line.textStart(), pos);
+ QCOMPARE(line.textLength(), 5);
+ linefit = line.naturalTextWidth();
+ QVERIFY(qAbs(linefit - qCeil(4 * xAdvance + shyWidth)) <= 1.0);
+
+ pos += line.textLength();
+ y += qRound(line.ascent() + line.descent());
+
+ line = layout.createLine();
+ line.setLineWidth(qCeil(5 * xAdvance + shyWidth) + 1);
+ line.setPosition(QPoint(0, y));
+ QCOMPARE(line.textStart(), pos);
+ QCOMPARE(line.textLength(), 5);
+ QVERIFY(qAbs(line.naturalTextWidth() - linefit) <= 1.0);
+ layout.endLayout();
+ }
+
+ // Tight fit
+ // xxxx-|
+ // xxxx-|
+ {
+ int pos = 0;
+ int y = 0;
+ layout.beginLayout();
+ QTextLine line = layout.createLine();
+ line.setLineWidth(qCeil(linefit) + 1);
+ line.setPosition(QPoint(0, y));
+ QCOMPARE(line.textStart(), pos);
+ QCOMPARE(line.textLength(), 5);
+ QVERIFY(qAbs(line.naturalTextWidth() - linefit) <= 1.0);
+
+ pos += line.textLength();
+ y += qRound(line.ascent() + line.descent());
+
+ line = layout.createLine();
+ line.setLineWidth(qCeil(linefit) + 1);
+ line.setPosition(QPoint(0, y));
+ QCOMPARE(line.textStart(), pos);
+ QCOMPARE(line.textLength(), 5);
+ QVERIFY(qAbs(line.naturalTextWidth() - linefit) <= 1.0);
+ layout.endLayout();
+ }
+
+ // Very tight fit
+ // xxxx|
+ // xxxx|
+ // - |
+ {
+ int pos = 0;
+ int y = 0;
+ layout.beginLayout();
+ QTextLine line = layout.createLine();
+ line.setLineWidth(qCeil(4 * xAdvance) + 2);
+ line.setPosition(QPoint(0, y));
+ QCOMPARE(line.textStart(), pos);
+ QCOMPARE(line.textLength(), 4);
+ QVERIFY(qAbs(line.naturalTextWidth() - qCeil(4 * xAdvance)) <= 1.0);
+
+ pos += line.textLength();
+ y += qRound(line.ascent() + line.descent());
+
+ line = layout.createLine();
+ line.setLineWidth(qCeil(4 * xAdvance) + 2);
+ line.setPosition(QPoint(0, y));
+ QCOMPARE(line.textStart(), pos);
+ QCOMPARE(line.textLength(), 5);
+ QVERIFY(qAbs(line.naturalTextWidth() - qCeil(4 * xAdvance)) <= 1.0);
+
+ pos += line.textLength();
+ y += qRound(line.ascent() + line.descent());
+
+ line = layout.createLine();
+ line.setLineWidth(qCeil(4 * xAdvance) + 2);
+ line.setPosition(QPoint(0, y));
+ QCOMPARE(line.textStart(), pos);
+ QCOMPARE(line.textLength(), 1);
+ QVERIFY(qAbs(line.naturalTextWidth() - shyWidth) <= 1.0);
+ layout.endLayout();
+ }
+}
+
+void tst_QTextLayout::min_maximumWidth_data()
+{
+ QTest::addColumn<QString>("text");
+
+ QTest::newRow("long string") << QStringLiteral("lmong_long_crazy_87235982735_23857239682376923876923876-fuwhfhfw-names-AAAA-deeaois2019-03-03.and.more");
+ QTest::newRow("QTBUG-106947") << QStringLiteral("text text");
+ QTest::newRow("spaces") << QStringLiteral(" text text ");
+ QTest::newRow("QTBUG-104986") << QStringLiteral("text\ntext\ntext");
+ QTest::newRow("spaces + line breaks") << QStringLiteral(" \n text\n \ntext \n ");
+}
+
+void tst_QTextLayout::min_maximumWidth()
+{
+ QFETCH(QString, text);
+ text.replace('\n', QChar::LineSeparator);
+
+ QTextLayout layout(text, testFont);
+ layout.setCacheEnabled(true);
+
+ QTextOption opt;
+ opt.setWrapMode(QTextOption::NoWrap);
+ layout.setTextOption(opt);
+ layout.beginLayout();
+ while (layout.createLine().isValid()) { }
+ layout.endLayout();
+
+ const qreal nonWrappedMaxWidth = layout.maximumWidth();
+
+ for (int wrapMode = QTextOption::NoWrap; wrapMode <= QTextOption::WrapAtWordBoundaryOrAnywhere; ++wrapMode) {
+ opt.setWrapMode((QTextOption::WrapMode)wrapMode);
+ layout.setTextOption(opt);
+ layout.beginLayout();
+ while (layout.createLine().isValid()) { }
+ layout.endLayout();
+ const qreal minWidth = layout.minimumWidth();
+ const qreal maxWidth = layout.maximumWidth();
+
+ QCOMPARE_LE(minWidth, maxWidth);
+ QCOMPARE_LE(maxWidth, nonWrappedMaxWidth); // maxWidth for wrapped text shouldn't exceed maxWidth for the text without wrapping.
+
+ // Try the layout from slightly wider than the widest (maxWidth)
+ // and narrow it down to slighly narrower than minWidth
+ // layout.maximumWidth() should return the same regardless
+ qreal width = qCeil(maxWidth/10)*10 + 10; // begin a bit wider
+ const qreal stepSize = 20;
+ while (width >= minWidth - stepSize) {
+ layout.beginLayout();
+ for (;;) {
+ QTextLine line = layout.createLine();
+ if (!line.isValid())
+ break;
+ line.setLineWidth(width);
+ }
+ layout.endLayout();
+ QCOMPARE(layout.minimumWidth(), minWidth);
+ QCOMPARE(layout.maximumWidth(), maxWidth);
+ width -= stepSize;
+ }
+ }
+}
+
+void tst_QTextLayout::negativeLineWidth()
+{
+ {
+ QTextLayout layout;
+ layout.setText("Foo bar");
+ layout.beginLayout();
+ QTextLine line = layout.createLine();
+ line.setLineWidth(-1);
+ QVERIFY(line.textLength() > 0);
+ layout.endLayout();
+ }
+
+ {
+ QTextLayout layout;
+ layout.setText("Foo bar");
+ layout.beginLayout();
+ QTextLine line = layout.createLine();
+ line.setNumColumns(2, -1);
+ QVERIFY(line.textLength() > 0);
+ layout.endLayout();
+ }
}
QTEST_MAIN(tst_QTextLayout)