diff options
Diffstat (limited to 'tests/auto/qtextlayout/tst_qtextlayout.cpp')
-rw-r--r-- | tests/auto/qtextlayout/tst_qtextlayout.cpp | 1284 |
1 files changed, 1284 insertions, 0 deletions
diff --git a/tests/auto/qtextlayout/tst_qtextlayout.cpp b/tests/auto/qtextlayout/tst_qtextlayout.cpp new file mode 100644 index 0000000..534c7b0 --- /dev/null +++ b/tests/auto/qtextlayout/tst_qtextlayout.cpp @@ -0,0 +1,1284 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +/* + !!!!!! 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 <private/qtextengine_p.h> +#include <qtextlayout.h> + +#include <qdebug.h> + + +#define TESTFONT_SIZE 12 + + + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QTextLayout : public QObject +{ + Q_OBJECT + +public: + tst_QTextLayout(); + virtual ~tst_QTextLayout(); + + +public slots: + void init(); + void cleanup(); +private slots: + void getSetCheck(); + void lineBreaking(); + void simpleBoundingRect(); + void threeLineBoundingRect(); + void boundingRectWithLongLineAndNoWrap(); + void forcedBreaks(); + void breakAny(); + void noWrap(); + void cursorToXForInlineObjects(); + void cursorToXForSetColumns(); + void defaultWordSeparators_data(); + void defaultWordSeparators(); + void cursorMovementInsideSpaces(); + void charWordStopOnLineSeparator(); + void xToCursorAtEndOfLine(); + void boundingRectTopLeft(); + void charStopForSurrogatePairs(); + void tabStops(); + void integerOverflow(); + void testDefaultTabs(); + void testTabs(); + void testMultilineTab(); + void testRightTab(); + void testTabsInAlignedParag(); + void testCenteredTab(); + void testDelimiterTab(); + void testMultiTab(); + void testTabDPIScale(); + void tabsForRtl(); + void tabHeight(); + void capitalization_allUpperCase(); + void capitalization_allLowerCase(); + void capitalization_smallCaps(); + void capitalization_capitalize(); + void longText(); + void widthOfTabs(); + + // QTextLine stuff + void setNumColumnsWrapAtWordBoundaryOrAnywhere(); + void setNumColumnsWordWrap(); + void smallTextLengthNoWrap(); + void smallTextLengthWordWrap(); + void smallTextLengthWrapAtWordBoundaryOrAnywhere(); + void testLineBreakingAllSpaces(); + + +private: + QFont testFont; +}; + +// Testing get/set functions +void tst_QTextLayout::getSetCheck() +{ + QString str("Bogus text"); + QTextLayout layout(str, testFont); + layout.beginLayout(); + QTextEngine *engine = layout.engine(); + QTextInlineObject obj1(0, engine); + // qreal QTextInlineObject::width() + // void QTextInlineObject::setWidth(qreal) + obj1.setWidth(0.0); + QCOMPARE(0.0, obj1.width()); + obj1.setWidth(1.2); + QVERIFY(1.0 < obj1.width()); + + // qreal QTextInlineObject::ascent() + // void QTextInlineObject::setAscent(qreal) + obj1.setAscent(0.0); + QCOMPARE(0.0, obj1.ascent()); + obj1.setAscent(1.2); + QVERIFY(1.0 < obj1.ascent()); + + // qreal QTextInlineObject::descent() + // void QTextInlineObject::setDescent(qreal) + obj1.setDescent(0.0); + QCOMPARE(0.0, obj1.descent()); + obj1.setDescent(1.2); + QVERIFY(1.0 < obj1.descent()); + + QTextLayout obj2; + // bool QTextLayout::cacheEnabled() + // void QTextLayout::setCacheEnabled(bool) + obj2.setCacheEnabled(false); + QCOMPARE(false, obj2.cacheEnabled()); + obj2.setCacheEnabled(true); + QCOMPARE(true, obj2.cacheEnabled()); +} + +QT_BEGIN_NAMESPACE +extern Q_GUI_EXPORT bool qt_enable_test_font; +QT_END_NAMESPACE + +tst_QTextLayout::tst_QTextLayout() +{ + qt_enable_test_font = true; +} + +tst_QTextLayout::~tst_QTextLayout() +{ +} + +void tst_QTextLayout::init() +{ + testFont = QFont(); + testFont.setFamily("__Qt__Box__Engine__"); + testFont.setPixelSize(TESTFONT_SIZE); + testFont.setWeight(QFont::Normal); +#if defined(Q_WS_MAC) && QT_VERSION < 0x040200 + QSKIP("QTestFontEngine is not supported on the mac right now", SkipAll); +#endif + QCOMPARE(QFontMetrics(testFont).width('a'), testFont.pixelSize()); +} + +void tst_QTextLayout::cleanup() +{ + testFont = QFont(); +} + + +void tst_QTextLayout::lineBreaking() +{ +#if defined(Q_WS_X11) + struct Breaks { + const char *utf8; + uchar breaks[32]; + }; + Breaks brks[] = { + { "11", { false, 0xff } }, + { "aa", { false, 0xff } }, + { "++", { false, 0xff } }, + { "--", { false, 0xff } }, + { "((", { false, 0xff } }, + { "))", { false, 0xff } }, + { "..", { false, 0xff } }, + { "\"\"", { false, 0xff } }, + { "$$", { false, 0xff } }, + { "!!", { false, 0xff } }, + { "??", { false, 0xff } }, + { ",,", { false, 0xff } }, + + { ")()", { true, false, 0xff } }, + { "?!?", { false, false, 0xff } }, + { ".,.", { false, false, 0xff } }, + { "+-+", { false, false, 0xff } }, + { "+=+", { false, false, 0xff } }, + { "+(+", { false, false, 0xff } }, + { "+)+", { false, false, 0xff } }, + + { "a b", { false, true, 0xff } }, + { "a(b", { false, false, 0xff } }, + { "a)b", { false, false, 0xff } }, + { "a-b", { false, true, 0xff } }, + { "a.b", { false, false, 0xff } }, + { "a+b", { false, false, 0xff } }, + { "a?b", { false, false, 0xff } }, + { "a!b", { false, false, 0xff } }, + { "a$b", { false, false, 0xff } }, + { "a,b", { false, false, 0xff } }, + { "a/b", { false, false, 0xff } }, + { "1/2", { false, false, 0xff } }, + { "./.", { false, false, 0xff } }, + { ",/,", { false, false, 0xff } }, + { "!/!", { false, false, 0xff } }, + { "\\/\\", { false, false, 0xff } }, + { "1 2", { false, true, 0xff } }, + { "1(2", { false, false, 0xff } }, + { "1)2", { false, false, 0xff } }, + { "1-2", { false, false, 0xff } }, + { "1.2", { false, false, 0xff } }, + { "1+2", { false, false, 0xff } }, + { "1?2", { false, true, 0xff } }, + { "1!2", { false, true, 0xff } }, + { "1$2", { false, false, 0xff } }, + { "1,2", { false, false, 0xff } }, + { "1/2", { false, false, 0xff } }, + { "\330\260\331\216\331\204\331\220\331\203\331\216", { false, false, false, false, false, 0xff } }, + { "\330\247\331\204\331\205 \330\247\331\204\331\205", { false, false, false, true, false, false, 0xff } }, + { "1#2", { false, false, 0xff } }, + { "!#!", { false, false, 0xff } }, + { 0, {} } + }; + Breaks *b = brks; + while (b->utf8) { + QString str = QString::fromUtf8(b->utf8); + QTextEngine engine(str, QFont()); + const HB_CharAttributes *attrs = engine.attributes(); + int i; + for (i = 0; i < (int)str.length() - 1; ++i) { + QVERIFY(b->breaks[i] != 0xff); + if ( (attrs[i].lineBreakType != HB_NoBreak) != (bool)b->breaks[i] ) { + qDebug("test case \"%s\" failed at char %d; break type: %d", b->utf8, i, attrs[i].lineBreakType); + QCOMPARE( (attrs[i].lineBreakType != HB_NoBreak), (bool)b->breaks[i] ); + } + } + QVERIFY(attrs[i].lineBreakType == HB_ForcedBreak); + QCOMPARE(b->breaks[i], (uchar)0xff); + ++b; + } +#else + QSKIP("This test can not be run on non-X11 platforms", SkipAll); +#endif +} + +void tst_QTextLayout::simpleBoundingRect() +{ + /* just check if boundingRect() gives sane values. The text is not broken. */ + + QString hello("hello world"); + + const int width = hello.length() * testFont.pixelSize(); + + QTextLayout layout(hello, testFont); + layout.beginLayout(); + + QTextLine line = layout.createLine(); + line.setLineWidth(width); + QCOMPARE(line.textLength(), hello.length()); + QCOMPARE(layout.boundingRect(), QRectF(0, 0, width, QFontMetrics(testFont).height())); +} + +void tst_QTextLayout::threeLineBoundingRect() +{ +#if defined(Q_WS_MAC) + QSKIP("QTestFontEngine on the mac does not support logclusters at the moment", SkipAll); +#endif + /* stricter check. break text into three lines */ + + 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(); + + const int longestLine = qMax(firstLineWidth, qMax(secondLineWidth, thirdLineWidth)); + + QTextLayout layout(text, testFont); + layout.beginLayout(); + + int pos = 0; + int y = 0; + QTextLine line = layout.createLine(); + line.setLineWidth(firstLineWidth); + line.setPosition(QPoint(0, y)); + QCOMPARE(line.textStart(), pos); + // + 1 for trailing space + QCOMPARE(line.textLength(), firstWord.length() + 1); + QCOMPARE(qRound(line.naturalTextWidth()), firstLineWidth); + + pos += line.textLength(); + y += qRound(line.ascent() + line.descent()); + + 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(qRound(line.naturalTextWidth()), secondLineWidth); + + pos += line.textLength(); + y += qRound(line.ascent() + line.descent()); + + 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(qRound(line.naturalTextWidth()), thirdLineWidth); + y += qRound(line.ascent() + line.descent()); + + QCOMPARE(layout.boundingRect(), QRectF(0, 0, longestLine, y + 1)); +} + +void tst_QTextLayout::boundingRectWithLongLineAndNoWrap() +{ + QString longString("thisisaverylongstringthatcannotbewrappedatallitjustgoesonandonlikeonebigword"); + + const int width = longString.length() * testFont.pixelSize() / 20; // very small widthx + + QTextLayout layout(longString, testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(width); + + QVERIFY(layout.boundingRect().width() >= line.width()); + QCOMPARE(layout.boundingRect().width(), line.naturalTextWidth()); +} + +void tst_QTextLayout::forcedBreaks() +{ + QString text = "A\n\nB\nC"; + text.replace('\n', QChar::LineSeparator); + + QTextLayout layout(text, testFont); + + layout.beginLayout(); + + int pos = 0; + + QTextLine line = layout.createLine(); + line.setLineWidth(0x10000); + QCOMPARE(line.textStart(), pos); + QCOMPARE(line.textLength(),2); + QCOMPARE(qRound(line.naturalTextWidth()),testFont.pixelSize()); + QCOMPARE((int) line.height(), testFont.pixelSize() + 1); // + 1 baseline + QCOMPARE(line.xToCursor(0), line.textStart()); + pos += line.textLength(); + + line = layout.createLine(); + line.setLineWidth(0x10000); + QCOMPARE(line.textStart(),pos); + QCOMPARE(line.textLength(),1); + QCOMPARE(qRound(line.naturalTextWidth()), 0); + QCOMPARE((int) line.height(), testFont.pixelSize() + 1); // + 1 baseline + QCOMPARE(line.xToCursor(0), line.textStart()); + pos += line.textLength(); + + line = layout.createLine(); + line.setLineWidth(0x10000); + QCOMPARE(line.textStart(),pos); + QCOMPARE(line.textLength(),2); + QCOMPARE(qRound(line.naturalTextWidth()),testFont.pixelSize()); + QCOMPARE(qRound(line.height()), testFont.pixelSize() + 1); // + 1 baseline + QCOMPARE(line.xToCursor(0), line.textStart()); + pos += line.textLength(); + + line = layout.createLine(); + line.setLineWidth(0x10000); + QCOMPARE(line.textStart(),pos); + QCOMPARE(line.textLength(),1); + QCOMPARE(qRound(line.naturalTextWidth()), testFont.pixelSize()); + QCOMPARE((int) line.height(), testFont.pixelSize() + 1); // + 1 baseline + QCOMPARE(line.xToCursor(0), line.textStart()); +} + +void tst_QTextLayout::breakAny() +{ +#if defined(Q_WS_MAC) + QSKIP("QTestFontEngine on the mac does not support logclusters at the moment", SkipAll); +#endif + QString text = "ABCD"; + + QTextLayout layout(text, testFont); + QTextLine line; + + QTextOption opt; + opt.setWrapMode(QTextOption::WrapAnywhere); + layout.setTextOption(opt); + layout.beginLayout(); + + line = layout.createLine(); + line.setLineWidth(testFont.pixelSize() * 2); + QCOMPARE(line.textStart(), 0); + QCOMPARE(line.textLength(), 2); + + line = layout.createLine(); + line.setLineWidth(testFont.pixelSize() * 2); + QCOMPARE(line.textStart(), 2); + QCOMPARE(line.textLength(), 2); + + line = layout.createLine(); + QVERIFY(!line.isValid()); + + layout.endLayout(); + + text = "ABCD EFGH"; + layout.setText(text); + layout.beginLayout(); + + line = layout.createLine(); + line.setLineWidth(testFont.pixelSize() * 7); + QCOMPARE(line.textStart(), 0); + QCOMPARE(line.textLength(), 7); + + layout.endLayout(); +} + +void tst_QTextLayout::noWrap() +{ +#if defined(Q_WS_MAC) + QSKIP("QTestFontEngine on the mac does not support logclusters at the moment", SkipAll); +#endif + QString text = "AB CD"; + + QTextLayout layout(text, testFont); + QTextLine line; + + QTextOption opt; + opt.setWrapMode(QTextOption::NoWrap); + layout.setTextOption(opt); + layout.beginLayout(); + + line = layout.createLine(); + line.setLineWidth(testFont.pixelSize() * 2); + QCOMPARE(line.textStart(), 0); + QCOMPARE(line.textLength(), 5); + + line = layout.createLine(); + QVERIFY(!line.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::cursorToXForInlineObjects() +{ + QChar ch(QChar::ObjectReplacementCharacter); + QString text(ch); + QTextLayout layout(text, testFont); + layout.beginLayout(); + + QTextEngine *engine = layout.engine(); + const int item = engine->findItem(0); + engine->layoutData->items[item].width = 32; + + QTextLine line = layout.createLine(); + line.setLineWidth(0x10000); + + QCOMPARE(line.cursorToX(0), qreal(0)); + QCOMPARE(line.cursorToX(1), qreal(32)); +} + +void tst_QTextLayout::cursorToXForSetColumns() +{ + QTextLayout lay("abc", testFont); + QTextOption o = lay.textOption(); + o.setWrapMode(QTextOption::WrapAnywhere); + + // enable/disable this line for full effect ;) + o.setAlignment(Qt::AlignHCenter); + + lay.setTextOption(o); + lay.beginLayout(); + QTextLine line = lay.createLine(); + line.setNumColumns(1); + lay.endLayout(); + QCOMPARE(line.cursorToX(0), 0.); + QCOMPARE(line.cursorToX(1), (qreal) TESTFONT_SIZE); +} + +void tst_QTextLayout::defaultWordSeparators_data() +{ + QTest::addColumn<QString>("text"); + QTest::addColumn<int>("startPos"); + QTest::addColumn<int>("endPos"); + + QString separators(".,:;-<>[](){}=/+%&^*"); + separators += QLatin1String("!?"); + for (int i = 0; i < separators.count(); ++i) { + QTest::newRow(QString::number(i).toAscii().data()) + << QString::fromLatin1("abcd") + separators.at(i) + QString::fromLatin1("efgh") + << 0 << 4; + } + + QTest::newRow("nbsp") + << QString::fromLatin1("abcd") + QString(QChar::Nbsp) + QString::fromLatin1("efgh") + << 0 << 5; + + QTest::newRow("tab") + << QString::fromLatin1("abcd") + QString::fromLatin1("\t") + QString::fromLatin1("efgh") + << 0 << 5; + + QTest::newRow("lineseparator") + << QString::fromLatin1("abcd") + QString(QChar::LineSeparator) + QString::fromLatin1("efgh") + << 0 << 5; +} + +void tst_QTextLayout::defaultWordSeparators() +{ + QFETCH(QString, text); + QFETCH(int, startPos); + QFETCH(int, endPos); + QTextLayout layout(text, testFont); + + QCOMPARE(layout.nextCursorPosition(startPos, QTextLayout::SkipWords), endPos); + QCOMPARE(layout.previousCursorPosition(endPos, QTextLayout::SkipWords), startPos); +} + +void tst_QTextLayout::cursorMovementInsideSpaces() +{ + QTextLayout layout("ABC DEF", testFont); + + QCOMPARE(layout.previousCursorPosition(6, QTextLayout::SkipWords), 0); + QCOMPARE(layout.nextCursorPosition(6, QTextLayout::SkipWords), 15); +} + +void tst_QTextLayout::charWordStopOnLineSeparator() +{ + const QChar lineSeparator(QChar::LineSeparator); + QString txt; + txt.append(lineSeparator); + txt.append(lineSeparator); + QTextLayout layout(txt, testFont); + QTextEngine *engine = layout.engine(); + const HB_CharAttributes *attrs = engine->attributes(); + QVERIFY(attrs); + QVERIFY(attrs[1].charStop); +} + +void tst_QTextLayout::xToCursorAtEndOfLine() +{ +#if defined(Q_WS_MAC) + QSKIP("QTestFontEngine on the mac does not support logclusters at the moment", SkipAll); +#endif + QString text = "FirstLine SecondLine"; + text.replace('\n', QChar::LineSeparator); + + const qreal firstLineWidth = QString("FirstLine").length() * testFont.pixelSize(); + + QTextLayout layout(text, testFont); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + QVERIFY(line.isValid()); + line.setLineWidth(firstLineWidth); + QVERIFY(layout.createLine().isValid()); + QVERIFY(!layout.createLine().isValid()); + layout.endLayout(); + + line = layout.lineAt(0); + QCOMPARE(line.xToCursor(100000), 9); + line = layout.lineAt(1); + QCOMPARE(line.xToCursor(100000), 20); +} + +void tst_QTextLayout::boundingRectTopLeft() +{ + QString text = "FirstLine\nSecondLine"; + text.replace('\n', QChar::LineSeparator); + + QTextLayout layout(text, testFont); + + layout.beginLayout(); + QTextLine firstLine = layout.createLine(); + QVERIFY(firstLine.isValid()); + firstLine.setPosition(QPointF(10, 10)); + QTextLine secondLine = layout.createLine(); + QVERIFY(secondLine.isValid()); + secondLine.setPosition(QPointF(20, 20)); + layout.endLayout(); + + QCOMPARE(layout.boundingRect().topLeft(), firstLine.position()); +} + +void tst_QTextLayout::charStopForSurrogatePairs() +{ + QString txt; + txt.append("a"); + txt.append(0xd87e); + txt.append(0xdc25); + txt.append("b"); + QTextLayout layout(txt, testFont); + QTextEngine *engine = layout.engine(); + const HB_CharAttributes *attrs = engine->attributes(); + QVERIFY(attrs); + QVERIFY(attrs[0].charStop); + QVERIFY(attrs[1].charStop); + QVERIFY(!attrs[2].charStop); + QVERIFY(attrs[3].charStop); +} + +void tst_QTextLayout::tabStops() +{ +#if defined(Q_WS_MAC) + QSKIP("QTestFontEngine on the mac does not support logclusters at the moment", SkipAll); +#endif + QString txt("Hello there\tworld"); + QTextLayout layout(txt, testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + + QVERIFY(line.isValid()); + line.setNumColumns(11); + QCOMPARE(line.textLength(), TESTFONT_SIZE); + + line = layout.createLine(); + QVERIFY(line.isValid()); + line.setNumColumns(5); + QCOMPARE(line.textLength(), 5); + + layout.endLayout(); +} + +void tst_QTextLayout::integerOverflow() +{ + QString txt("Hello world... "); + + for (int i = 0; i < 8; ++i) + txt += txt; + + QTextLayout layout(txt, testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + + QVERIFY(line.isValid()); + line.setLineWidth(INT_MAX); + QCOMPARE(line.textLength(), txt.length()); + + QVERIFY(!layout.createLine().isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::setNumColumnsWrapAtWordBoundaryOrAnywhere() +{ + QString txt("This is a small test text"); + QTextLayout layout(txt, testFont); + QTextOption option = layout.textOption(); + option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + QVERIFY(line1.isValid()); + line1.setNumColumns(1); + + // qDebug() << line1.naturalTextWidth(); + QCOMPARE(line1.textLength(), 1); + QVERIFY(line1.naturalTextWidth() == testFont.pixelSize()); // contains only one character + + QTextLine line2 = layout.createLine(); + QVERIFY(line2.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::setNumColumnsWordWrap() +{ + QString txt("This is a small test text"); + QTextLayout layout(txt, testFont); + QTextOption option = layout.textOption(); + option.setWrapMode(QTextOption::WordWrap); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + QVERIFY(line1.isValid()); + line1.setNumColumns(1); + + // qDebug() << line1.naturalTextWidth(); + QCOMPARE(line1.textLength(), 5); + QVERIFY(line1.naturalTextWidth() > 20.0); // contains the whole first word. + + QTextLine line2 = layout.createLine(); + QVERIFY(line2.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::smallTextLengthNoWrap() +{ + QString txt("This is a small test text"); + QTextLayout layout(txt, testFont); + QTextOption option = layout.textOption(); + option.setWrapMode(QTextOption::NoWrap); + layout.setTextOption(option); + + /// NoWrap + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + QVERIFY(line1.isValid()); + line1.setLineWidth(5); // most certainly too short for the word 'This' to fit. + + QCOMPARE(line1.width(), 5.0); + QVERIFY(line1.naturalTextWidth() > 70); // contains all the text. + + QTextLine line2 = layout.createLine(); + QVERIFY(! line2.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::smallTextLengthWordWrap() +{ + QString txt("This is a small test text"); + QTextLayout layout(txt, testFont); + QTextOption option = layout.textOption(); + option.setWrapMode(QTextOption::WordWrap); + layout.setTextOption(option); + + /// WordWrap + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + QVERIFY(line1.isValid()); + line1.setLineWidth(5); // most certainly too short for the word 'This' to fit. + + QCOMPARE(line1.width(), 5.0); + QVERIFY(line1.naturalTextWidth() > 20.0); // contains the whole first word. + QCOMPARE(line1.textLength(), 5); + + QTextLine line2 = layout.createLine(); + QVERIFY(line2.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::smallTextLengthWrapAtWordBoundaryOrAnywhere() +{ + QString txt("This is a small test text"); + QTextLayout layout(txt, testFont); + QTextOption option = layout.textOption(); + option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + QVERIFY(line1.isValid()); + line1.setLineWidth(5); // most certainly too short for the word 'This' to fit. + + QCOMPARE(line1.width(), 5.0); + // qDebug() << line1.naturalTextWidth(); + QCOMPARE(line1.textLength(), 1); + QVERIFY(line1.naturalTextWidth() == testFont.pixelSize()); // contains just the characters that fit. + + QTextLine line2 = layout.createLine(); + QVERIFY(line2.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::testDefaultTabs() +{ + QTextLayout layout("Foo\tBar\ta slightly longer text\tend.", testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(1000); + layout.endLayout(); + + //qDebug() << "After the tab: " << line.cursorToX(4); + QCOMPARE(line.cursorToX(4), 80.); // default tab is 80 + QCOMPARE(line.cursorToX(8), 160.); + QCOMPARE(line.cursorToX(31), 480.); + + QTextOption option = layout.textOption(); + option.setTabStop(90); + layout.setTextOption(option); + layout.beginLayout(); + line = layout.createLine(); + line.setLineWidth(1000); + layout.endLayout(); + + QCOMPARE(line.cursorToX(4), 90.); + QCOMPARE(line.cursorToX(8), 180.); + QCOMPARE(line.cursorToX(31), 450.); + + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab; + tab.position = 110; // set one tab to 110, but since the rest is unset they will be at the normal interval again. + tabs.append(tab); + option.setTabs(tabs); + layout.setTextOption(option); + layout.beginLayout(); + line = layout.createLine(); + line.setLineWidth(1000); + layout.endLayout(); + + QCOMPARE(line.cursorToX(4), 110.); + QCOMPARE(line.cursorToX(8), 180.); + QCOMPARE(line.cursorToX(31), 450.); +} + +void tst_QTextLayout::testTabs() +{ + QTextLayout layout("Foo\tBar.", testFont); + QTextOption option = layout.textOption(); + option.setTabStop(150); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(200.); + layout.endLayout(); + + QVERIFY(line.naturalTextWidth() > 150); + QCOMPARE(line.cursorToX(4), 150.); +} + +void tst_QTextLayout::testMultilineTab() +{ + QTextLayout layout("Lorem ipsum dolor sit\tBar.", testFont); + // test if this works on the second line. + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(220.); // moves 'sit' to next line. + line = layout.createLine(); + line.setLineWidth(220.); + layout.endLayout(); + + QCOMPARE(line.cursorToX(22), 80.); +} + +void tst_QTextLayout::testMultiTab() +{ + QTextLayout layout("Foo\t\t\tBar.", testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(1000.); + layout.endLayout(); + + QCOMPARE(line.cursorToX(6), 80. * 3); +} + +void tst_QTextLayout::testTabsInAlignedParag() +{ + QTextLayout layout("Foo\tsome more words", testFont); + QTextOption option = layout.textOption(); + // right + option.setAlignment(Qt::AlignRight); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(300.); + layout.endLayout(); + + const qreal textWidth = 80 + 15 * TESTFONT_SIZE; // 15 chars right of the tab + QCOMPARE(line.naturalTextWidth(), textWidth); + QCOMPARE(line.cursorToX(0), 300. - textWidth); + + // centered + option.setAlignment(Qt::AlignCenter); + layout.setTextOption(option); + + layout.beginLayout(); + line = layout.createLine(); + line.setLineWidth(300.); + layout.endLayout(); + + QCOMPARE(line.naturalTextWidth(), textWidth); + QCOMPARE(line.cursorToX(0), (300. - textWidth) / 2.); + + // justified + option.setAlignment(Qt::AlignJustify); + layout.setTextOption(option); + + layout.beginLayout(); + line = layout.createLine(); + line.setLineWidth(textWidth - 10); // make the last word slip to the next line so justification actually happens. + layout.endLayout(); + + QCOMPARE(line.cursorToX(0), 0.); + QCOMPARE(line.cursorToX(4), 80.); + + //QTextLayout layout2("Foo\tUt wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis", testFont); // means it will be more then one line long. +} + +void tst_QTextLayout::testRightTab() +{ + QTextLayout layout("Foo\tLorem ipsum te sit\tBar baz\tText\tEnd", testFont); + /* ^ a ^ b ^ c ^ d + a = 4, b = 22, c = 30, d = 35 (position) + + I expect the output to be: + Foo Lorem ipsum te + sit Bar Baz + Text End + + a) tab replaced with a single space due to the text not fitting before the tab. + b) tab takes space so the text until the 3th tab fits to the tab pos. + c) tab is after last tab (both auto and defined) and thus moves text to start of next line. + d) tab takes space so text until enter fits to tab pos. + */ + + QTextOption option = layout.textOption(); + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab; + tab.type = QTextOption::RightTab; + tab.position = 190; // which means only 15(.8) chars of our test font fit left of it. + tabs.append(tab); + option.setTabs(tabs); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + line1.setLineWidth(220.); + // qDebug() << "=====line 2"; + QTextLine line2 = layout.createLine(); + QVERIFY(line2.isValid()); + line2.setLineWidth(220.); + // qDebug() << "=====line 3"; + QTextLine line3 = layout.createLine(); + QVERIFY(line3.isValid()); + line3.setLineWidth(220.); + // qDebug() << "=====line 4"; + QTextLine line4 = layout.createLine(); + QVERIFY(! line4.isValid()); + layout.endLayout(); + // qDebug() << "--------"; + + QCOMPARE(line1.cursorToX(4), 3. * TESTFONT_SIZE ); // a + QCOMPARE(line1.textLength(), 19); + QCOMPARE(line2.cursorToX(23), 190. - 7. * TESTFONT_SIZE); // b + QCOMPARE(line2.textLength(), 12); + QCOMPARE(line3.cursorToX(31), 0.); // c + QCOMPARE(line3.cursorToX(36), 190 - 3. * TESTFONT_SIZE); // d +} + +void tst_QTextLayout::testCenteredTab() +{ + QTextLayout layout("Foo\tBar", testFont); + // test if centering the tab works. We expect the center of 'Bar.' to be at the tab point. + QTextOption option = layout.textOption(); + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab; + tab.type = QTextOption::CenterTab; + tab.position = 150; + tabs.append(tab); + option.setTabs(tabs); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(200.); + layout.endLayout(); + + const qreal wordLength = 3 * TESTFONT_SIZE; // the length of 'Bar' + QCOMPARE(line.cursorToX(4), 150 - wordLength / 2.); +} + +void tst_QTextLayout::testDelimiterTab() +{ + QTextLayout layout("Foo\tBar. Barrabas", testFont); + // try the different delimiter characters to see if the alignment works there. + QTextOption option = layout.textOption(); + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab; + tab.type = QTextOption::DelimiterTab; + tab.delimiter = QChar('.'); + tab.position = 100; + tabs.append(tab); + option.setTabs(tabs); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(200.); + layout.endLayout(); + + const qreal distanceBeforeTab = 3.5 * TESTFONT_SIZE; // the length of 'bar' and half the width of the dot. + QCOMPARE(line.cursorToX(4), 100 - distanceBeforeTab); +} + +void tst_QTextLayout::testLineBreakingAllSpaces() +{ + QTextLayout layout(" 123", testFont); // thats 20 spaces + const qreal firstLineWidth = 17 * TESTFONT_SIZE; + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + line1.setLineWidth(firstLineWidth); + QTextLine line2 = layout.createLine(); + line2.setLineWidth(1000); // the rest + layout.endLayout(); + QCOMPARE(line1.width(), firstLineWidth); + QCOMPARE(line1.naturalTextWidth(), 0.); // spaces don't take space + QCOMPARE(line1.textLength(), 20); + QCOMPARE(line2.textLength(), 3); + QCOMPARE(line2.naturalTextWidth(), 3. * TESTFONT_SIZE); +} + +void tst_QTextLayout::tabsForRtl() +{ + QString word(QChar(0x5e9)); // a hebrew character + word = word + word + word; // 3 hebrew characters ;) + + QTextLayout layout(word +'\t'+ word +'\t'+ word +'\t'+ word, testFont); +//QTextLayout layout(word +' '+ word +' '+ word +' '+ word, testFont);// tester ;) + /* ^ a ^ b ^ c + a = 4, b = 8, c = 12, d = 16 (position) + + a) Left tab in RTL is a righ tab; so a is at width - 80 + b) Like a + c) right tab on RTL is a left tab; so its at width - 240 + d) center tab is still a centered tab. + */ + + QTextOption option = layout.textOption(); + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab; + tab.position = 80; + tabs.append(tab); + tab.position = 160; + tabs.append(tab); + tab.position = 240; + tab.type = QTextOption::RightTab; + tabs.append(tab); + option.setTabs(tabs); + option.setTextDirection(Qt::RightToLeft); + option.setAlignment(Qt::AlignRight); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + const qreal WIDTH = 400.; + line.setLineWidth(WIDTH); + layout.endLayout(); + +//qDebug() << "layout ended --------------"; + + QCOMPARE(line.cursorToX(0), WIDTH); + QCOMPARE(line.cursorToX(1), WIDTH - TESTFONT_SIZE); // check its right-aligned + QCOMPARE(line.cursorToX(4), WIDTH - 80 + 3 * TESTFONT_SIZE); + QCOMPARE(line.cursorToX(8), WIDTH - 160 + 3 * TESTFONT_SIZE); + QCOMPARE(line.cursorToX(12), WIDTH - 240); +} + +QT_BEGIN_NAMESPACE +extern int qt_defaultDpiY(); +QT_END_NAMESPACE + +void tst_QTextLayout::testTabDPIScale() +{ + #ifdef Q_OS_WINCE + QSKIP("This test fails for large DPIs.", SkipAll); + #endif + + class MyPaintDevice : public QPaintDevice { + QPaintEngine *paintEngine () const { return 0; } + int metric (QPaintDevice::PaintDeviceMetric metric) const { + switch(metric) { + case QPaintDevice::PdmWidth: + case QPaintDevice::PdmHeight: + case QPaintDevice::PdmWidthMM: + case QPaintDevice::PdmHeightMM: + case QPaintDevice::PdmNumColors: + return INT_MAX; + case QPaintDevice::PdmDepth: + return 32; + case QPaintDevice::PdmDpiX: + case QPaintDevice::PdmDpiY: + case QPaintDevice::PdmPhysicalDpiX: + case QPaintDevice::PdmPhysicalDpiY: + return 72; + } + return 0; + } + }; + + MyPaintDevice pd; + + QTextLayout layout("text1\ttext2\ttext3\tend", testFont, &pd); + + QTextOption option = layout.textOption(); + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab; + tab.position = 100; + tabs.append(tab); + + tab.position = 200; + tab.type = QTextOption::RightTab; + tabs.append(tab); + + tab.position = 300; + tab.type = QTextOption::CenterTab; + tabs.append(tab); + option.setTabs(tabs); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(500.); + layout.endLayout(); + QCOMPARE(line.cursorToX(0), 0.); + QCOMPARE(line.cursorToX(1), (double) TESTFONT_SIZE); // check that the font does not resize + qreal scale = 72 / (qreal) qt_defaultDpiY(); + // lets do the transformation of deminishing resolution that QFixed has as effect. + int fixedScale = (int)( scale * qreal(64)); // into a QFixed + scale = ((qreal)fixedScale)/(qreal)64; // and out of a QFixed + + QCOMPARE(line.cursorToX(6), 100 * scale); + QCOMPARE(line.cursorToX(12), 200 * scale - TESTFONT_SIZE * 5); + QCOMPARE(line.cursorToX(18), 300 * scale - TESTFONT_SIZE * 3 / 2.0); +} + +void tst_QTextLayout::tabHeight() +{ + QTextLayout layout("\t", testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QCOMPARE(qRound(line.ascent()), QFontMetrics(testFont).ascent()); + QCOMPARE(qRound(line.descent()), QFontMetrics(testFont).descent()); +} + +void tst_QTextLayout::capitalization_allUpperCase() +{ + QFont font(testFont); + font.setCapitalization(QFont::AllUppercase); + QTextLayout layout("Test", font); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QTextEngine *engine = layout.engine(); + engine->itemize(); + QCOMPARE(engine->layoutData->items.count(), 1); + QVERIFY(engine->layoutData->items.at(0).analysis.flags == QScriptAnalysis::Uppercase); +} + +void tst_QTextLayout::capitalization_allLowerCase() +{ + QFont font(testFont); + font.setCapitalization(QFont::AllLowercase); + QTextLayout layout("Test", font); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QTextEngine *engine = layout.engine(); + engine->itemize(); + QCOMPARE(engine->layoutData->items.count(), 1); + QVERIFY(engine->layoutData->items.at(0).analysis.flags == QScriptAnalysis::Lowercase); +} + +void tst_QTextLayout::capitalization_smallCaps() +{ + QFont font(testFont); + font.setCapitalization(QFont::SmallCaps); + QTextLayout layout("Test", font); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QTextEngine *engine = layout.engine(); + engine->itemize(); + QCOMPARE(engine->layoutData->items.count(), 2); + QVERIFY(engine->layoutData->items.at(0).analysis.flags == QScriptAnalysis::None); + QVERIFY(engine->layoutData->items.at(1).analysis.flags == QScriptAnalysis::SmallCaps); +} + +void tst_QTextLayout::capitalization_capitalize() +{ + QFont font(testFont); + font.setCapitalization(QFont::Capitalize); + QTextLayout layout("hello\tworld", font); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QTextEngine *engine = layout.engine(); + engine->itemize(); + QCOMPARE(engine->layoutData->items.count(), 5); + QVERIFY(engine->layoutData->items.at(0).analysis.flags == QScriptAnalysis::Uppercase); + QVERIFY(engine->layoutData->items.at(1).analysis.flags == QScriptAnalysis::None); + QVERIFY(engine->layoutData->items.at(2).analysis.flags == QScriptAnalysis::Tab); + QVERIFY(engine->layoutData->items.at(3).analysis.flags == QScriptAnalysis::Uppercase); + QVERIFY(engine->layoutData->items.at(4).analysis.flags == QScriptAnalysis::None); +} + +void tst_QTextLayout::longText() +{ + QString longText(128000, 'a'); + + { + QTextLayout layout(longText, testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + QVERIFY(line.isValid()); + QVERIFY(line.cursorToX(line.textLength() - 1) > 0); + } + + for (int cap = QFont::MixedCase; cap < QFont::Capitalize + 1; ++cap) { + QFont f(testFont); + f.setCapitalization(QFont::Capitalization(cap)); + QTextLayout layout(longText, f); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + QVERIFY(line.isValid()); + QVERIFY(line.cursorToX(line.textLength() - 1) > 0); + } + + { + QTextLayout layout(longText, testFont); + layout.setFlags(Qt::TextForceLeftToRight); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + QVERIFY(line.isValid()); + QVERIFY(line.cursorToX(line.textLength() - 1) > 0); + } + + { + QTextLayout layout(longText, testFont); + layout.setFlags(Qt::TextForceRightToLeft); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + QVERIFY(line.isValid()); + QVERIFY(line.cursorToX(line.textLength() - 1) > 0); + } +} + +void tst_QTextLayout::widthOfTabs() +{ + QTextEngine engine("ddd\t\t", testFont); + engine.ignoreBidi = true; + engine.itemize(); + QCOMPARE(qRound(engine.width(0, 5)), qRound(engine.boundingBox(0, 5).width)); +} + +QTEST_MAIN(tst_QTextLayout) +#include "tst_qtextlayout.moc" |