/**************************************************************************** ** ** 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$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD) Q_DECLARE_METATYPE(QQuickText::TextFormat) QT_BEGIN_NAMESPACE extern void qt_setQtEnableTestFont(bool value); QT_END_NAMESPACE Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") class tst_qquicktext : public QQmlDataTest { Q_OBJECT public: tst_qquicktext(); private slots: void cleanup(); void text(); void width(); void wrap(); void elide(); void elideParentChanged(); void elideRelayoutAfterZeroWidth(); void multilineElide_data(); void multilineElide(); void implicitElide_data(); void implicitElide(); void textFormat(); void baseUrl(); void embeddedImages_data(); void embeddedImages(); void lineCount(); void lineHeight(); // ### these tests may be trivial void horizontalAlignment(); void horizontalAlignment_RightToLeft(); void verticalAlignment(); void hAlignImplicitWidth(); void font(); void style(); void color(); void smooth(); void renderType(); void antialiasing(); // QQuickFontValueType void weight(); void underline(); void overline(); void strikeout(); void capitalization(); void letterSpacing(); void wordSpacing(); void linkInteraction_data(); void linkInteraction(); void implicitSize_data(); void implicitSize(); void implicitSizeChangeRewrap(); void dependentImplicitSizes(); void contentSize(); void implicitSizeBinding_data(); void implicitSizeBinding(); void geometryChanged(); void boundingRect_data(); void boundingRect(); void clipRect(); void largeTextObservesViewport_data(); void largeTextObservesViewport(); void lineLaidOut(); void lineLaidOutRelayout(); void lineLaidOutHAlign(); void lineLaidOutImplicitWidth(); void imgTagsBaseUrl_data(); void imgTagsBaseUrl(); void imgTagsAlign_data(); void imgTagsAlign(); void imgTagsMultipleImages(); void imgTagsElide(); void imgTagsUpdates(); void imgTagsError(); void fontSizeMode_data(); void fontSizeMode(); void fontSizeModeMultiline_data(); void fontSizeModeMultiline(); void multilengthStrings_data(); void multilengthStrings(); void fontFormatSizes_data(); void fontFormatSizes(); void baselineOffset_data(); void baselineOffset(); void htmlLists(); void htmlLists_data(); void elideBeforeMaximumLineCount(); void hover(); void growFromZeroWidth(); void padding(); void paddingInLoader(); void hintingPreference(); void zeroWidthAndElidedDoesntRender(); void hAlignWidthDependsOnImplicitWidth_data(); void hAlignWidthDependsOnImplicitWidth(); void fontInfo(); void initialContentHeight(); void verticallyAlignedImageInTable(); void transparentBackground(); void displaySuperscriptedTag(); private: QStringList standard; QStringList richText; QStringList horizontalAlignmentmentStrings; QStringList verticalAlignmentmentStrings; QList verticalAlignmentments; QList horizontalAlignmentments; QStringList styleStrings; QList styles; QStringList colorStrings; QQmlEngine engine; QQuickView *createView(const QString &filename); int numberOfNonWhitePixels(int fromX, int toX, const QImage &image); }; void tst_qquicktext::cleanup() { QVERIFY(QGuiApplication::topLevelWindows().isEmpty()); } tst_qquicktext::tst_qquicktext() : QQmlDataTest(QT_QMLTEST_DATADIR) { standard << "the quick brown fox jumped over the lazy dog" << "the quick brown fox\n jumped over the lazy dog"; richText << "the quick brown fox jumped over the lazy dog" << "the quick brown fox
jumped over the lazy dog
"; horizontalAlignmentmentStrings << "AlignLeft" << "AlignRight" << "AlignHCenter"; verticalAlignmentmentStrings << "AlignTop" << "AlignBottom" << "AlignVCenter"; horizontalAlignmentments << Qt::AlignLeft << Qt::AlignRight << Qt::AlignHCenter; verticalAlignmentments << Qt::AlignTop << Qt::AlignBottom << Qt::AlignVCenter; styleStrings << "Normal" << "Outline" << "Raised" << "Sunken"; styles << QQuickText::Normal << QQuickText::Outline << QQuickText::Raised << QQuickText::Sunken; colorStrings << "aliceblue" << "antiquewhite" << "aqua" << "darkkhaki" << "darkolivegreen" << "dimgray" << "palevioletred" << "lightsteelblue" << "#000000" << "#AAAAAA" << "#FFFFFF" << "#2AC05F"; // // need a different test to do alpha channel test // << "#AA0011DD" // << "#00F16B11"; // qt_setQtEnableTestFont(true); } QQuickView *tst_qquicktext::createView(const QString &filename) { QQuickView *window = new QQuickView(nullptr); window->setSource(QUrl::fromLocalFile(filename)); return window; } void tst_qquicktext::text() { { QQmlComponent textComponent(&engine); textComponent.setData("import QtQuick 2.0\nText { text: \"\" }", QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->text(), QString("")); QCOMPARE(textObject->width(), qreal(0)); delete textObject; } for (int i = 0; i < standard.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { text: \"" + standard.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->text(), standard.at(i)); QVERIFY(textObject->width() > 0); delete textObject; } for (int i = 0; i < richText.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { text: \"" + richText.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QString expected = richText.at(i); QCOMPARE(textObject->text(), expected.replace("\\\"", "\"")); QVERIFY(textObject->width() > 0); delete textObject; } } void tst_qquicktext::width() { // uses Font metrics to find the width for standard and document to find the width for rich { QQmlComponent textComponent(&engine); textComponent.setData("import QtQuick 2.0\nText { text: \"\" }", QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->width(), 0.); delete textObject; } bool requiresUnhintedMetrics = !qmlDisableDistanceField(); for (int i = 0; i < standard.size(); i++) { QVERIFY(!Qt::mightBeRichText(standard.at(i))); // self-test QFont f; qreal metricWidth = 0.0; if (requiresUnhintedMetrics) { QString s = standard.at(i); s.replace(QLatin1Char('\n'), QChar::LineSeparator); QTextLayout layout(s); layout.setFlags(Qt::TextExpandTabs | Qt::TextShowMnemonic); { QTextOption option; option.setUseDesignMetrics(true); layout.setTextOption(option); } layout.beginLayout(); forever { QTextLine line = layout.createLine(); if (!line.isValid()) break; } layout.endLayout(); metricWidth = layout.boundingRect().width(); } else { QFontMetricsF fm(f); metricWidth = fm.size(Qt::TextExpandTabs | Qt::TextShowMnemonic, standard.at(i)).width(); } QString componentStr = "import QtQuick 2.0\nText { text: \"" + standard.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QVERIFY(textObject->boundingRect().width() > 0); QCOMPARE(textObject->width(), qreal(metricWidth)); QVERIFY(textObject->textFormat() == QQuickText::AutoText); // setting text doesn't change format delete textObject; } for (int i = 0; i < richText.size(); i++) { QVERIFY(Qt::mightBeRichText(richText.at(i))); // self-test QString componentStr = "import QtQuick 2.0\nText { text: \"" + richText.at(i) + "\"; textFormat: Text.RichText }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject); QVERIFY(textPrivate != nullptr); QVERIFY(textPrivate->extra.isAllocated()); QTextDocument *doc = textPrivate->extra->doc; QVERIFY(doc != nullptr); QCOMPARE(int(textObject->width()), int(doc->idealWidth())); QCOMPARE(textObject->textFormat(), QQuickText::RichText); delete textObject; } } void tst_qquicktext::wrap() { int textHeight = 0; // for specified width and wrap set true { QQmlComponent textComponent(&engine); textComponent.setData("import QtQuick 2.0\nText { text: \"Hello\"; wrapMode: Text.WordWrap; width: 300 }", QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); textHeight = textObject->height(); QVERIFY(textObject != nullptr); QCOMPARE(textObject->wrapMode(), QQuickText::WordWrap); QCOMPARE(textObject->width(), 300.); delete textObject; } for (int i = 0; i < standard.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; width: 30; text: \"" + standard.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->width(), 30.); QVERIFY(textObject->height() > textHeight); int oldHeight = textObject->height(); textObject->setWidth(100); QVERIFY(textObject->height() < oldHeight); delete textObject; } for (int i = 0; i < richText.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; width: 30; text: \"" + richText.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->width(), 30.); QVERIFY(textObject->height() > textHeight); qreal oldHeight = textObject->height(); textObject->setWidth(100); QVERIFY(textObject->height() < oldHeight); delete textObject; } // Check that increasing width from idealWidth will cause a relayout for (int i = 0; i < richText.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; textFormat: Text.RichText; width: 30; text: \"" + richText.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->width(), 30.); QVERIFY(textObject->height() > textHeight); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject); QVERIFY(textPrivate != nullptr); QVERIFY(textPrivate->extra.isAllocated()); QTextDocument *doc = textPrivate->extra->doc; QVERIFY(doc != nullptr); textObject->setWidth(doc->idealWidth()); QCOMPARE(textObject->width(), doc->idealWidth()); QVERIFY(textObject->height() > textHeight); qreal oldHeight = textObject->height(); textObject->setWidth(100); QVERIFY(textObject->height() < oldHeight); delete textObject; } // richtext again with a fixed height for (int i = 0; i < richText.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; width: 30; height: 50; text: \"" + richText.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->width(), 30.); QVERIFY(textObject->implicitHeight() > textHeight); qreal oldHeight = textObject->implicitHeight(); textObject->setWidth(100); QVERIFY(textObject->implicitHeight() < oldHeight); delete textObject; } { QQmlComponent component(&engine); component.setData("import QtQuick 2.0\n Text {}", QUrl()); QScopedPointer object(component.create()); QQuickText *textObject = qobject_cast(object.data()); QVERIFY(textObject); QSignalSpy spy(textObject, SIGNAL(wrapModeChanged())); QCOMPARE(textObject->wrapMode(), QQuickText::NoWrap); textObject->setWrapMode(QQuickText::Wrap); QCOMPARE(textObject->wrapMode(), QQuickText::Wrap); QCOMPARE(spy.count(), 1); textObject->setWrapMode(QQuickText::Wrap); QCOMPARE(spy.count(), 1); textObject->setWrapMode(QQuickText::NoWrap); QCOMPARE(textObject->wrapMode(), QQuickText::NoWrap); QCOMPARE(spy.count(), 2); } } void tst_qquicktext::elide() { for (QQuickText::TextElideMode m = QQuickText::ElideLeft; m<=QQuickText::ElideNone; m=QQuickText::TextElideMode(int(m)+1)) { const char* elidename[]={"ElideLeft", "ElideRight", "ElideMiddle", "ElideNone"}; QString elide = "elide: Text." + QString(elidename[int(m)]) + ";"; // XXX Poor coverage. { QQmlComponent textComponent(&engine); textComponent.setData(("import QtQuick 2.0\nText { text: \"\"; "+elide+" width: 100 }").toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->elideMode(), m); QCOMPARE(textObject->width(), 100.); delete textObject; } for (int i = 0; i < standard.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { "+elide+" width: 100; text: \"" + standard.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->elideMode(), m); QCOMPARE(textObject->width(), 100.); if (m != QQuickText::ElideNone && !standard.at(i).contains('\n')) QVERIFY(textObject->contentWidth() <= textObject->width()); delete textObject; } for (int i = 0; i < richText.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { "+elide+" width: 100; text: \"" + richText.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->elideMode(), m); QCOMPARE(textObject->width(), 100.); if (m != QQuickText::ElideNone && standard.at(i).contains("
")) QVERIFY(textObject->contentWidth() <= textObject->width()); delete textObject; } } } // QTBUG-60328 // Tests that text with elide set is rendered after // having its parent cleared and then set again. void tst_qquicktext::elideParentChanged() { QQuickView window; window.setSource(testFileUrl("elideParentChanged.qml")); QTRY_COMPARE(window.status(), QQuickView::Ready); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QQuickItem *root = window.rootObject(); QVERIFY(root); QCOMPARE(root->childItems().size(), 1); // Store a snapshot of the scene so that we can compare it later. QSharedPointer grabResult = root->grabToImage(); QTRY_VERIFY(!grabResult->image().isNull()); const QImage expectedItemImageGrab(grabResult->image()); // Clear the text's parent. It shouldn't render anything. QQuickItem *text = root->childItems().first(); text->setParentItem(nullptr); QCOMPARE(text->width(), 0.0); QCOMPARE(text->height(), 0.0); // Set the parent back to what it was. The text should // be rendered identically to how it was before. text->setParentItem(root); QCOMPARE(text->width(), 100.0); QCOMPARE(text->height(), 30.0); grabResult = root->grabToImage(); QTRY_VERIFY(!grabResult->image().isNull()); const QImage actualItemImageGrab(grabResult->image()); QCOMPARE(actualItemImageGrab, expectedItemImageGrab); } void tst_qquicktext::elideRelayoutAfterZeroWidth() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("elideZeroWidth.qml")); QScopedPointer root(component.create()); QVERIFY2(root, qPrintable(component.errorString())); QVERIFY(root->property("ok").toBool()); } void tst_qquicktext::multilineElide_data() { QTest::addColumn("format"); QTest::newRow("plain") << QQuickText::PlainText; QTest::newRow("styled") << QQuickText::StyledText; } void tst_qquicktext::multilineElide() { QFETCH(QQuickText::TextFormat, format); QScopedPointer window(createView(testFile("multilineelide.qml"))); QQuickText *myText = qobject_cast(window->rootObject()); QVERIFY(myText != nullptr); myText->setTextFormat(format); QCOMPARE(myText->lineCount(), 3); QCOMPARE(myText->truncated(), true); qreal lineHeight = myText->contentHeight() / 3.; // Set a valid height greater than the truncated content height and ensure the line count is // unchanged. myText->setHeight(200); QCOMPARE(myText->lineCount(), 3); QCOMPARE(myText->truncated(), true); // reduce size and ensure fewer lines are drawn myText->setHeight(lineHeight * 2); QCOMPARE(myText->lineCount(), 2); myText->setHeight(lineHeight); QCOMPARE(myText->lineCount(), 1); myText->setHeight(5); QCOMPARE(myText->lineCount(), 1); myText->setHeight(lineHeight * 3); QCOMPARE(myText->lineCount(), 3); // remove max count and show all lines. myText->setHeight(1000); myText->resetMaximumLineCount(); QCOMPARE(myText->truncated(), false); // reduce size again myText->setHeight(lineHeight * 2); QCOMPARE(myText->lineCount(), 2); QCOMPARE(myText->truncated(), true); // change line height myText->setLineHeight(1.1); QCOMPARE(myText->lineCount(), 1); } void tst_qquicktext::implicitElide_data() { QTest::addColumn("width"); QTest::addColumn("initialText"); QTest::addColumn("text"); QTest::newRow("maximum width, empty") << "Math.min(implicitWidth, 100)" << ""; QTest::newRow("maximum width, short") << "Math.min(implicitWidth, 100)" << "the"; QTest::newRow("maximum width, long") << "Math.min(implicitWidth, 100)" << "the quick brown fox jumped over the lazy dog"; QTest::newRow("reset width, empty") << "implicitWidth > 100 ? 100 : undefined" << ""; QTest::newRow("reset width, short") << "implicitWidth > 100 ? 100 : undefined" << "the"; QTest::newRow("reset width, long") << "implicitWidth > 100 ? 100 : undefined" << "the quick brown fox jumped over the lazy dog"; } void tst_qquicktext::implicitElide() { QFETCH(QString, width); QFETCH(QString, initialText); QString componentStr = "import QtQuick 2.0\n" "Text {\n" "width: " + width + "\n" "text: \"" + initialText + "\"\n" "elide: Text.ElideRight\n" "}"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject->contentWidth() <= textObject->width()); textObject->setText("the quick brown fox jumped over"); QVERIFY(textObject->contentWidth() > 0); QVERIFY(textObject->contentWidth() <= textObject->width()); } void tst_qquicktext::textFormat() { { QQmlComponent textComponent(&engine); textComponent.setData("import QtQuick 2.0\nText { text: \"Hello\"; textFormat: Text.RichText }", QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->textFormat(), QQuickText::RichText); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject); QVERIFY(textPrivate != nullptr); QVERIFY(textPrivate->richText); delete textObject; } { QQmlComponent textComponent(&engine); textComponent.setData("import QtQuick 2.0\nText { text: \"Hello\" }", QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->textFormat(), QQuickText::AutoText); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject); QVERIFY(textPrivate != nullptr); QVERIFY(textPrivate->styledText); delete textObject; } { QQmlComponent textComponent(&engine); textComponent.setData("import QtQuick 2.0\nText { text: \"Hello\"; textFormat: Text.PlainText }", QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->textFormat(), QQuickText::PlainText); delete textObject; } { QQmlComponent component(&engine); component.setData("import QtQuick 2.0\n Text {}", QUrl()); QScopedPointer object(component.create()); QQuickText *text = qobject_cast(object.data()); QVERIFY(text); QSignalSpy spy(text, &QQuickText::textFormatChanged); QCOMPARE(text->textFormat(), QQuickText::AutoText); text->setTextFormat(QQuickText::StyledText); QCOMPARE(text->textFormat(), QQuickText::StyledText); QCOMPARE(spy.count(), 1); text->setTextFormat(QQuickText::StyledText); QCOMPARE(spy.count(), 1); text->setTextFormat(QQuickText::AutoText); QCOMPARE(text->textFormat(), QQuickText::AutoText); QCOMPARE(spy.count(), 2); } { QQmlComponent component(&engine); component.setData("import QtQuick 2.0\n Text { text: \"Hello\" }", QUrl()); QScopedPointer object(component.create()); QQuickText *text = qobject_cast(object.data()); QVERIFY(text); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(text); QVERIFY(textPrivate); QCOMPARE(text->textFormat(), QQuickText::AutoText); QVERIFY(!textPrivate->layout.formats().isEmpty()); text->setTextFormat(QQuickText::StyledText); QVERIFY(!textPrivate->layout.formats().isEmpty()); text->setTextFormat(QQuickText::PlainText); QVERIFY(textPrivate->layout.formats().isEmpty()); text->setTextFormat(QQuickText::AutoText); QVERIFY(!textPrivate->layout.formats().isEmpty()); } { QQmlComponent component(&engine); component.setData("import QtQuick 2.0\nText { text: \"Hello\"; elide: Text.ElideRight }", QUrl::fromLocalFile("")); QScopedPointer object(component.create()); QQuickText *text = qobject_cast(object.data()); QVERIFY(text); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(text); QVERIFY(textPrivate); // underline a mnemonic QVector formats; QTextLayout::FormatRange range; range.start = 0; range.length = 1; range.format.setFontUnderline(true); formats << range; // the mnemonic format should be retained textPrivate->layout.setFormats(formats); text->forceLayout(); QCOMPARE(textPrivate->layout.formats(), formats); // and carried over to the elide layout text->setWidth(text->implicitWidth() - 1); QVERIFY(textPrivate->elideLayout); QCOMPARE(textPrivate->elideLayout->formats(), formats); // but cleared when the text changes text->setText("Changed"); QVERIFY(textPrivate->elideLayout); QVERIFY(textPrivate->layout.formats().isEmpty()); } } //the alignment tests may be trivial o.oa void tst_qquicktext::horizontalAlignment() { //test one align each, and then test if two align fails. for (int i = 0; i < standard.size(); i++) { for (int j=0; j < horizontalAlignmentmentStrings.size(); j++) { QString componentStr = "import QtQuick 2.0\nText { horizontalAlignment: \"" + horizontalAlignmentmentStrings.at(j) + "\"; text: \"" + standard.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE((int)textObject->hAlign(), (int)horizontalAlignmentments.at(j)); delete textObject; } } for (int i = 0; i < richText.size(); i++) { for (int j=0; j < horizontalAlignmentmentStrings.size(); j++) { QString componentStr = "import QtQuick 2.0\nText { horizontalAlignment: \"" + horizontalAlignmentmentStrings.at(j) + "\"; text: \"" + richText.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE((int)textObject->hAlign(), (int)horizontalAlignmentments.at(j)); delete textObject; } } } void tst_qquicktext::horizontalAlignment_RightToLeft() { QScopedPointer window(createView(testFile("horizontalAlignment_RightToLeft.qml"))); QQuickText *text = window->rootObject()->findChild("text"); QVERIFY(text != nullptr); window->showNormal(); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(text); QVERIFY(textPrivate != nullptr); QTRY_VERIFY(textPrivate->layout.lineCount()); // implicit alignment should follow the reading direction of RTL text QCOMPARE(text->hAlign(), QQuickText::AlignRight); QCOMPARE(text->effectiveHAlign(), text->hAlign()); QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2); // explicitly left aligned text text->setHAlign(QQuickText::AlignLeft); QCOMPARE(text->hAlign(), QQuickText::AlignLeft); QCOMPARE(text->effectiveHAlign(), text->hAlign()); QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2); // explicitly right aligned text text->setHAlign(QQuickText::AlignRight); QCOMPARE(text->hAlign(), QQuickText::AlignRight); QCOMPARE(text->effectiveHAlign(), text->hAlign()); QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2); // change to rich text QString textString = text->text(); text->setText(QString("") + textString + QString("")); text->setTextFormat(QQuickText::RichText); text->resetHAlign(); // implicitly aligned rich text should follow the reading direction of text QCOMPARE(text->hAlign(), QQuickText::AlignRight); QCOMPARE(text->effectiveHAlign(), text->hAlign()); QVERIFY(textPrivate->extra.isAllocated()); QVERIFY(textPrivate->extra->doc->defaultTextOption().alignment() & Qt::AlignLeft); // explicitly left aligned rich text text->setHAlign(QQuickText::AlignLeft); QCOMPARE(text->hAlign(), QQuickText::AlignLeft); QCOMPARE(text->effectiveHAlign(), text->hAlign()); QVERIFY(textPrivate->extra->doc->defaultTextOption().alignment() & Qt::AlignRight); // explicitly right aligned rich text text->setHAlign(QQuickText::AlignRight); QCOMPARE(text->hAlign(), QQuickText::AlignRight); QCOMPARE(text->effectiveHAlign(), text->hAlign()); QVERIFY(textPrivate->extra->doc->defaultTextOption().alignment() & Qt::AlignLeft); text->setText(textString); text->setTextFormat(QQuickText::PlainText); // explicitly center aligned text->setHAlign(QQuickText::AlignHCenter); QCOMPARE(text->hAlign(), QQuickText::AlignHCenter); QCOMPARE(text->effectiveHAlign(), text->hAlign()); QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2); QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().right() > window->width()/2); // reseted alignment should go back to following the text reading direction text->resetHAlign(); QCOMPARE(text->hAlign(), QQuickText::AlignRight); QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2); // mirror the text item QQuickItemPrivate::get(text)->setLayoutMirror(true); // mirrored implicit alignment should continue to follow the reading direction of the text QCOMPARE(text->hAlign(), QQuickText::AlignRight); QCOMPARE(text->effectiveHAlign(), QQuickText::AlignRight); QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2); // mirrored explicitly right aligned behaves as left aligned text->setHAlign(QQuickText::AlignRight); QCOMPARE(text->hAlign(), QQuickText::AlignRight); QCOMPARE(text->effectiveHAlign(), QQuickText::AlignLeft); QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2); // mirrored explicitly left aligned behaves as right aligned text->setHAlign(QQuickText::AlignLeft); QCOMPARE(text->hAlign(), QQuickText::AlignLeft); QCOMPARE(text->effectiveHAlign(), QQuickText::AlignRight); QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2); // disable mirroring QQuickItemPrivate::get(text)->setLayoutMirror(false); text->resetHAlign(); // English text should be implicitly left aligned text->setText("Hello world!"); QCOMPARE(text->hAlign(), QQuickText::AlignLeft); QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2); // empty text with implicit alignment follows the system locale-based // keyboard input direction from QInputMethod::inputDirection() text->setText(""); QCOMPARE(text->hAlign(), qApp->inputMethod()->inputDirection() == Qt::LeftToRight ? QQuickText::AlignLeft : QQuickText::AlignRight); text->setHAlign(QQuickText::AlignRight); QCOMPARE(text->hAlign(), QQuickText::AlignRight); window.reset(); // alignment of Text with no text set to it QString componentStr = "import QtQuick 2.0\nText {}"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->hAlign(), qApp->inputMethod()->inputDirection() == Qt::LeftToRight ? QQuickText::AlignLeft : QQuickText::AlignRight); delete textObject; } int tst_qquicktext::numberOfNonWhitePixels(int fromX, int toX, const QImage &image) { int pixels = 0; for (int x = fromX; x < toX; ++x) { for (int y = 0; y < image.height(); ++y) { if (image.pixel(x, y) != qRgb(255, 255, 255)) pixels++; } } return pixels; } static inline QByteArray msgNotGreaterThan(int n1, int n2) { return QByteArray::number(n1) + QByteArrayLiteral(" is not greater than ") + QByteArray::number(n2); } static inline QByteArray msgNotLessThan(int n1, int n2) { return QByteArray::number(n1) + QByteArrayLiteral(" is not less than ") + QByteArray::number(n2); } void tst_qquicktext::hAlignImplicitWidth() { #ifdef Q_OS_MACOS QSKIP("this test currently crashes on MacOS. See QTBUG-68047"); #endif QQuickView view(testFileUrl("hAlignImplicitWidth.qml")); view.setFlags(view.flags() | Qt::WindowStaysOnTopHint); // Prevent being obscured by other windows. view.show(); view.requestActivate(); QVERIFY(QTest::qWaitForWindowExposed(&view)); QQuickText *text = view.rootObject()->findChild("textItem"); QVERIFY(text != nullptr); // Try to check whether alignment works by checking the number of black // pixels in the thirds of the grabbed image. // QQuickWindow::grabWindow() scales the returned image by the devicePixelRatio of the screen. const qreal devicePixelRatio = view.screen()->devicePixelRatio(); const int windowWidth = 220 * devicePixelRatio; const int textWidth = qCeil(text->implicitWidth()) * devicePixelRatio; QVERIFY2(textWidth < windowWidth, "System font too large."); const int sectionWidth = textWidth / 3; const int centeredSection1 = (windowWidth - textWidth) / 2; const int centeredSection2 = centeredSection1 + sectionWidth; const int centeredSection3 = centeredSection2 + sectionWidth; const int centeredSection3End = centeredSection3 + sectionWidth; { if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Skipping due to grabWindow not functional on offscreen/minimal platforms"); // Left Align QImage image = view.grabWindow(); const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image); const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image); const int right = numberOfNonWhitePixels(centeredSection3, centeredSection3End, image); QVERIFY2(left > mid, msgNotGreaterThan(left, mid).constData()); QVERIFY2(mid > right, msgNotGreaterThan(mid, right).constData()); } { // HCenter Align text->setHAlign(QQuickText::AlignHCenter); text->setText("Reset"); // set dummy string to force relayout once original text is set again text->setText("AA\nBBBBBBB\nCCCCCCCCCCCCCCCC"); QImage image = view.grabWindow(); const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image); const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image); const int right = numberOfNonWhitePixels(centeredSection3, centeredSection3End, image); QVERIFY2(left < mid, msgNotLessThan(left, mid).constData()); QVERIFY2(mid > right, msgNotGreaterThan(mid, right).constData()); } { // Right Align text->setHAlign(QQuickText::AlignRight); text->setText("Reset"); // set dummy string to force relayout once original text is set again text->setText("AA\nBBBBBBB\nCCCCCCCCCCCCCCCC"); QImage image = view.grabWindow(); const int left = numberOfNonWhitePixels(centeredSection1, centeredSection2, image); const int mid = numberOfNonWhitePixels(centeredSection2, centeredSection3, image); const int right = numberOfNonWhitePixels(centeredSection3, centeredSection3End, image); QVERIFY2(left < mid, msgNotLessThan(left, mid).constData()); QVERIFY2(mid < right, msgNotLessThan(mid, right).constData()); } } void tst_qquicktext::verticalAlignment() { //test one align each, and then test if two align fails. for (int i = 0; i < standard.size(); i++) { for (int j=0; j < verticalAlignmentmentStrings.size(); j++) { QString componentStr = "import QtQuick 2.0\nText { verticalAlignment: \"" + verticalAlignmentmentStrings.at(j) + "\"; text: \"" + standard.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE((int)textObject->vAlign(), (int)verticalAlignmentments.at(j)); delete textObject; } } for (int i = 0; i < richText.size(); i++) { for (int j=0; j < verticalAlignmentmentStrings.size(); j++) { QString componentStr = "import QtQuick 2.0\nText { verticalAlignment: \"" + verticalAlignmentmentStrings.at(j) + "\"; text: \"" + richText.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE((int)textObject->vAlign(), (int)verticalAlignmentments.at(j)); delete textObject; } } } void tst_qquicktext::font() { //test size, then bold, then italic, then family { QString componentStr = "import QtQuick 2.0\nText { font.pointSize: 40; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->font().pointSize(), 40); QCOMPARE(textObject->font().bold(), false); QCOMPARE(textObject->font().italic(), false); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { font.pixelSize: 40; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->font().pixelSize(), 40); QCOMPARE(textObject->font().bold(), false); QCOMPARE(textObject->font().italic(), false); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { font.bold: true; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->font().bold(), true); QCOMPARE(textObject->font().italic(), false); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { font.italic: true; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->font().italic(), true); QCOMPARE(textObject->font().bold(), false); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { font.family: \"Helvetica\"; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->font().family(), QString("Helvetica")); QCOMPARE(textObject->font().bold(), false); QCOMPARE(textObject->font().italic(), false); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { font.family: \"\"; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->font().family(), QString("")); delete textObject; } } void tst_qquicktext::style() { //test style for (int i = 0; i < styles.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { style: \"" + styleStrings.at(i) + "\"; styleColor: \"white\"; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE((int)textObject->style(), (int)styles.at(i)); QCOMPARE(textObject->styleColor(), QColor("white")); delete textObject; } QString componentStr = "import QtQuick 2.0\nText { text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QRectF brPre = textObject->boundingRect(); textObject->setStyle(QQuickText::Outline); QRectF brPost = textObject->boundingRect(); QVERIFY(brPre.width() < brPost.width()); QVERIFY(brPre.height() < brPost.height()); delete textObject; } void tst_qquicktext::color() { //test style for (int i = 0; i < colorStrings.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { color: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->color(), QColor(colorStrings.at(i))); QCOMPARE(textObject->styleColor(), QColor("black")); QCOMPARE(textObject->linkColor(), QColor("blue")); delete textObject; } for (int i = 0; i < colorStrings.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { styleColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->styleColor(), QColor(colorStrings.at(i))); // default color to black? QCOMPARE(textObject->color(), QColor("black")); QCOMPARE(textObject->linkColor(), QColor("blue")); QSignalSpy colorSpy(textObject, SIGNAL(colorChanged())); QSignalSpy linkColorSpy(textObject, SIGNAL(linkColorChanged())); textObject->setColor(QColor("white")); QCOMPARE(textObject->color(), QColor("white")); QCOMPARE(colorSpy.count(), 1); textObject->setLinkColor(QColor("black")); QCOMPARE(textObject->linkColor(), QColor("black")); QCOMPARE(linkColorSpy.count(), 1); textObject->setColor(QColor("white")); QCOMPARE(colorSpy.count(), 1); textObject->setLinkColor(QColor("black")); QCOMPARE(linkColorSpy.count(), 1); textObject->setColor(QColor("black")); QCOMPARE(textObject->color(), QColor("black")); QCOMPARE(colorSpy.count(), 2); textObject->setLinkColor(QColor("blue")); QCOMPARE(textObject->linkColor(), QColor("blue")); QCOMPARE(linkColorSpy.count(), 2); delete textObject; } for (int i = 0; i < colorStrings.size(); i++) { QString componentStr = "import QtQuick 2.0\nText { linkColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->styleColor(), QColor("black")); QCOMPARE(textObject->color(), QColor("black")); QCOMPARE(textObject->linkColor(), QColor(colorStrings.at(i))); delete textObject; } for (int i = 0; i < colorStrings.size(); i++) { for (int j = 0; j < colorStrings.size(); j++) { QString componentStr = "import QtQuick 2.0\nText { " "color: \"" + colorStrings.at(i) + "\"; " "styleColor: \"" + colorStrings.at(j) + "\"; " "linkColor: \"" + colorStrings.at(j) + "\"; " "text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->color(), QColor(colorStrings.at(i))); QCOMPARE(textObject->styleColor(), QColor(colorStrings.at(j))); QCOMPARE(textObject->linkColor(), QColor(colorStrings.at(j))); delete textObject; } } { QString colorStr = "#AA001234"; QColor testColor("#001234"); testColor.setAlpha(170); QString componentStr = "import QtQuick 2.0\nText { color: \"" + colorStr + "\"; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->color(), testColor); delete textObject; } { QString colorStr = "#001234"; QColor testColor(colorStr); QString componentStr = "import QtQuick 2.0\nText { color: \"" + colorStr + "\"; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QScopedPointer object(textComponent.create()); QQuickText *textObject = qobject_cast(object.data()); QSignalSpy spy(textObject, SIGNAL(colorChanged())); QCOMPARE(textObject->color(), testColor); textObject->setColor(testColor); QCOMPARE(textObject->color(), testColor); QCOMPARE(spy.count(), 0); testColor = QColor("black"); textObject->setColor(testColor); QCOMPARE(textObject->color(), testColor); QCOMPARE(spy.count(), 1); } { QString colorStr = "#001234"; QColor testColor(colorStr); QString componentStr = "import QtQuick 2.0\nText { styleColor: \"" + colorStr + "\"; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QScopedPointer object(textComponent.create()); QQuickText *textObject = qobject_cast(object.data()); QSignalSpy spy(textObject, SIGNAL(styleColorChanged())); QCOMPARE(textObject->styleColor(), testColor); textObject->setStyleColor(testColor); QCOMPARE(textObject->styleColor(), testColor); QCOMPARE(spy.count(), 0); testColor = QColor("black"); textObject->setStyleColor(testColor); QCOMPARE(textObject->styleColor(), testColor); QCOMPARE(spy.count(), 1); } { QString colorStr = "#001234"; QColor testColor(colorStr); QString componentStr = "import QtQuick 2.0\nText { linkColor: \"" + colorStr + "\"; text: \"Hello World\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QScopedPointer object(textComponent.create()); QQuickText *textObject = qobject_cast(object.data()); QSignalSpy spy(textObject, SIGNAL(linkColorChanged())); QCOMPARE(textObject->linkColor(), testColor); textObject->setLinkColor(testColor); QCOMPARE(textObject->linkColor(), testColor); QCOMPARE(spy.count(), 0); testColor = QColor("black"); textObject->setLinkColor(testColor); QCOMPARE(textObject->linkColor(), testColor); QCOMPARE(spy.count(), 1); } } void tst_qquicktext::smooth() { for (int i = 0; i < standard.size(); i++) { { QString componentStr = "import QtQuick 2.0\nText { smooth: false; text: \"" + standard.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->smooth(), false); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { text: \"" + standard.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->smooth(), true); delete textObject; } } for (int i = 0; i < richText.size(); i++) { { QString componentStr = "import QtQuick 2.0\nText { smooth: false; text: \"" + richText.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->smooth(), false); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { text: \"" + richText.at(i) + "\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->smooth(), true); delete textObject; } } } void tst_qquicktext::renderType() { QQmlComponent component(&engine); component.setData("import QtQuick 2.0\n Text {}", QUrl()); QScopedPointer object(component.create()); QQuickText *text = qobject_cast(object.data()); QVERIFY(text); QSignalSpy spy(text, SIGNAL(renderTypeChanged())); QCOMPARE(text->renderType(), QQuickText::QtRendering); text->setRenderType(QQuickText::NativeRendering); QCOMPARE(text->renderType(), QQuickText::NativeRendering); QCOMPARE(spy.count(), 1); text->setRenderType(QQuickText::NativeRendering); QCOMPARE(spy.count(), 1); text->setRenderType(QQuickText::QtRendering); QCOMPARE(text->renderType(), QQuickText::QtRendering); QCOMPARE(spy.count(), 2); } void tst_qquicktext::antialiasing() { QQmlComponent component(&engine); component.setData("import QtQuick 2.0\n Text {}", QUrl()); QScopedPointer object(component.create()); QQuickText *text = qobject_cast(object.data()); QVERIFY(text); QSignalSpy spy(text, SIGNAL(antialiasingChanged(bool))); QCOMPARE(text->antialiasing(), true); text->setAntialiasing(false); QCOMPARE(text->antialiasing(), false); QCOMPARE(spy.count(), 1); text->setAntialiasing(false); QCOMPARE(spy.count(), 1); text->resetAntialiasing(); QCOMPARE(text->antialiasing(), true); QCOMPARE(spy.count(), 2); // QTBUG-39047 component.setData("import QtQuick 2.0\n Text { antialiasing: true }", QUrl()); object.reset(component.create()); text = qobject_cast(object.data()); QVERIFY(text); QCOMPARE(text->antialiasing(), true); } void tst_qquicktext::weight() { { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE((int)textObject->font().weight(), int(QQuickFontEnums::Normal)); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { font.weight: Font.Bold; text: \"Hello world!\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE((int)textObject->font().weight(), int(QQuickFontEnums::Bold)); delete textObject; } } void tst_qquicktext::underline() { QQuickView view(testFileUrl("underline.qml")); view.show(); view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickText *textObject = view.rootObject()->findChild("myText"); QVERIFY(textObject != nullptr); QCOMPARE(textObject->font().overline(), false); QCOMPARE(textObject->font().underline(), true); QCOMPARE(textObject->font().strikeOut(), false); } void tst_qquicktext::overline() { QQuickView view(testFileUrl("overline.qml")); view.show(); view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickText *textObject = view.rootObject()->findChild("myText"); QVERIFY(textObject != nullptr); QCOMPARE(textObject->font().overline(), true); QCOMPARE(textObject->font().underline(), false); QCOMPARE(textObject->font().strikeOut(), false); } void tst_qquicktext::strikeout() { QQuickView view(testFileUrl("strikeout.qml")); view.show(); view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickText *textObject = view.rootObject()->findChild("myText"); QVERIFY(textObject != nullptr); QCOMPARE(textObject->font().overline(), false); QCOMPARE(textObject->font().underline(), false); QCOMPARE(textObject->font().strikeOut(), true); } void tst_qquicktext::capitalization() { { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE((int)textObject->font().capitalization(), int(QQuickFontEnums::MixedCase)); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"AllUppercase\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE((int)textObject->font().capitalization(), int(QQuickFontEnums::AllUppercase)); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"AllLowercase\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE((int)textObject->font().capitalization(), int(QQuickFontEnums::AllLowercase)); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"SmallCaps\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE((int)textObject->font().capitalization(), int(QQuickFontEnums::SmallCaps)); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"Capitalize\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE((int)textObject->font().capitalization(), int(QQuickFontEnums::Capitalize)); delete textObject; } } void tst_qquicktext::letterSpacing() { { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->font().letterSpacing(), 0.0); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.letterSpacing: -2 }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->font().letterSpacing(), -2.); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.letterSpacing: 3 }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->font().letterSpacing(), 3.); delete textObject; } } void tst_qquicktext::wordSpacing() { { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->font().wordSpacing(), 0.0); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.wordSpacing: -50 }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->font().wordSpacing(), -50.); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.wordSpacing: 200 }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->font().wordSpacing(), 200.); delete textObject; } } class EventSender : public QQuickItem { public: void sendEvent(QEvent *event) { switch (event->type()) { case QEvent::MouseButtonPress: mousePressEvent(static_cast(event)); break; case QEvent::MouseButtonRelease: mouseReleaseEvent(static_cast(event)); break; case QEvent::MouseMove: mouseMoveEvent(static_cast(event)); break; case QEvent::HoverEnter: hoverEnterEvent(static_cast(event)); break; case QEvent::HoverLeave: hoverLeaveEvent(static_cast(event)); break; case QEvent::HoverMove: hoverMoveEvent(static_cast(event)); break; default: qWarning() << "Trying to send unsupported event type"; break; } } }; class LinkTest : public QObject { Q_OBJECT public: LinkTest() {} QString clickedLink; QString hoveredLink; public slots: void linkClicked(QString l) { clickedLink = l; } void linkHovered(QString l) { hoveredLink = l; } }; class TextMetrics { public: TextMetrics(const QString &text, Qt::TextElideMode elideMode = Qt::ElideNone) { QString adjustedText = text; adjustedText.replace(QLatin1Char('\n'), QChar(QChar::LineSeparator)); if (elideMode == Qt::ElideLeft) adjustedText = QChar(0x2026) + adjustedText; else if (elideMode == Qt::ElideRight) adjustedText = adjustedText + QChar(0x2026); layout.setText(adjustedText); QTextOption option; option.setUseDesignMetrics(true); layout.setTextOption(option); layout.beginLayout(); qreal height = 0; QTextLine line = layout.createLine(); while (line.isValid()) { line.setLineWidth(FLT_MAX); line.setPosition(QPointF(0, height)); height += line.height(); line = layout.createLine(); } layout.endLayout(); } qreal width() const { return layout.maximumWidth(); } QRectF characterRectangle( int position, int hAlign = Qt::AlignLeft, int vAlign = Qt::AlignTop, const QSizeF &bounds = QSizeF(240, 320)) const { qreal dy = 0; switch (vAlign) { case Qt::AlignBottom: dy = bounds.height() - layout.boundingRect().height(); break; case Qt::AlignVCenter: dy = (bounds.height() - layout.boundingRect().height()) / 2; break; default: break; } for (int i = 0; i < layout.lineCount(); ++i) { QTextLine line = layout.lineAt(i); if (position >= line.textStart() + line.textLength()) continue; qreal dx = 0; switch (hAlign) { case Qt::AlignRight: dx = bounds.width() - line.naturalTextWidth(); break; case Qt::AlignHCenter: dx = (bounds.width() - line.naturalTextWidth()) / 2; break; default: break; } QRectF rect; rect.setLeft(dx + line.cursorToX(position, QTextLine::Leading)); rect.setRight(dx + line.cursorToX(position, QTextLine::Trailing)); rect.setTop(dy + line.y()); rect.setBottom(dy + line.y() + line.height()); return rect; } return QRectF(); } QTextLayout layout; }; typedef QVector PointVector; Q_DECLARE_METATYPE(PointVector); void tst_qquicktext::linkInteraction_data() { QTest::addColumn("text"); QTest::addColumn("width"); QTest::addColumn("bindings"); QTest::addColumn("mousePositions"); QTest::addColumn("clickedLink"); QTest::addColumn("hoverEnterLink"); QTest::addColumn("hoverMoveLink"); const QString singleLineText = "this text has a link in it"; const QString singleLineLink = "http://qt-project.org/single"; const QString multipleLineText = "this text
has multiple
lines
in it"; const QString multipleLineLink = "http://qt-project.org/multiple"; const QString nestedText = "this text has a nested link in it"; const QString outerLink = "http://qt-project.org/outer"; const QString innerLink = "http://qt-project.org/inner"; { const TextMetrics metrics("this text has a link in it"); QTest::newRow("click on link") << singleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(18).center()) << singleLineLink << singleLineLink << singleLineLink; QTest::newRow("click on text") << singleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(13).center()) << QString() << QString() << QString(); QTest::newRow("drag within link") << singleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(17).center() << metrics.characterRectangle(19).center()) << singleLineLink << singleLineLink << singleLineLink; QTest::newRow("drag away from link") << singleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(18).center() << metrics.characterRectangle(13).center()) << QString() << singleLineLink << QString(); QTest::newRow("drag on to link") << singleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(13).center() << metrics.characterRectangle(18).center()) << QString() << QString() << singleLineLink; QTest::newRow("click on bottom right aligned link") << singleLineText << 240. << "horizontalAlignment: Text.AlignRight; verticalAlignment: Text.AlignBottom" << (PointVector() << metrics.characterRectangle(18, Qt::AlignRight, Qt::AlignBottom).center()) << singleLineLink << singleLineLink << singleLineLink; QTest::newRow("click on mirrored link") << singleLineText << 240. << "horizontalAlignment: Text.AlignLeft; LayoutMirroring.enabled: true" << (PointVector() << metrics.characterRectangle(18, Qt::AlignRight, Qt::AlignTop).center()) << singleLineLink << singleLineLink << singleLineLink; QTest::newRow("click on center aligned link") << singleLineText << 240. << "horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter" << (PointVector() << metrics.characterRectangle(18, Qt::AlignHCenter, Qt::AlignVCenter).center()) << singleLineLink << singleLineLink << singleLineLink; QTest::newRow("click on rich text link") << singleLineText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(18).center()) << singleLineLink << singleLineLink << singleLineLink; QTest::newRow("click on rich text") << singleLineText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(13).center()) << QString() << QString() << QString(); QTest::newRow("click on bottom right aligned rich text link") << singleLineText << 240. << "textFormat: Text.RichText; horizontalAlignment: Text.AlignRight; verticalAlignment: Text.AlignBottom" << (PointVector() << metrics.characterRectangle(18, Qt::AlignRight, Qt::AlignBottom).center()) << singleLineLink << singleLineLink << singleLineLink; QTest::newRow("click on center aligned rich text link") << singleLineText << 240. << "textFormat: Text.RichText; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter" << (PointVector() << metrics.characterRectangle(18, Qt::AlignHCenter, Qt::AlignVCenter).center()) << singleLineLink << singleLineLink << singleLineLink; } { const TextMetrics metrics("this text has a li", Qt::ElideRight); QTest::newRow("click on right elided link") << singleLineText << metrics.width() + 2 << "elide: Text.ElideRight" << (PointVector() << metrics.characterRectangle(17).center()) << singleLineLink << singleLineLink << singleLineLink; } { const TextMetrics metrics("ink in it", Qt::ElideLeft); QTest::newRow("click on left elided link") << singleLineText << metrics.width() + 2 << "elide: Text.ElideLeft" << (PointVector() << metrics.characterRectangle(2).center()) << singleLineLink << singleLineLink << singleLineLink; } { const TextMetrics metrics("this text\nhas multiple\nlines in it"); QTest::newRow("click on second line") << multipleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(18).center()) << multipleLineLink << multipleLineLink << multipleLineLink; QTest::newRow("click on third line") << multipleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(25).center()) << multipleLineLink << multipleLineLink << multipleLineLink; QTest::newRow("drag from second line to third") << multipleLineText << 240. << "" << (PointVector() << metrics.characterRectangle(18).center() << metrics.characterRectangle(25).center()) << multipleLineLink << multipleLineLink << multipleLineLink; QTest::newRow("click on rich text second line") << multipleLineText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(18).center()) << multipleLineLink << multipleLineLink << multipleLineLink; QTest::newRow("click on rich text third line") << multipleLineText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(25).center()) << multipleLineLink << multipleLineLink << multipleLineLink; QTest::newRow("drag rich text from second line to third") << multipleLineText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(18).center() << metrics.characterRectangle(25).center()) << multipleLineLink << multipleLineLink << multipleLineLink; } { const TextMetrics metrics("this text has a nested link in it"); QTest::newRow("click on left outer link") << nestedText << 240. << "" << (PointVector() << metrics.characterRectangle(22).center()) << outerLink << outerLink << outerLink; QTest::newRow("click on right outer link") << nestedText << 240. << "" << (PointVector() << metrics.characterRectangle(27).center()) << outerLink << outerLink << outerLink; QTest::newRow("click on inner link left") << nestedText << 240. << "" << (PointVector() << metrics.characterRectangle(23).center()) << innerLink << innerLink << innerLink; QTest::newRow("click on inner link right") << nestedText << 240. << "" << (PointVector() << metrics.characterRectangle(26).center()) << innerLink << innerLink << innerLink; QTest::newRow("drag from inner to outer link") << nestedText << 240. << "" << (PointVector() << metrics.characterRectangle(25).center() << metrics.characterRectangle(30).center()) << QString() << innerLink << outerLink; QTest::newRow("drag from outer to inner link") << nestedText << 240. << "" << (PointVector() << metrics.characterRectangle(30).center() << metrics.characterRectangle(25).center()) << QString() << outerLink << innerLink; QTest::newRow("click on left outer rich text link") << nestedText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(22).center()) << outerLink << outerLink << outerLink; QTest::newRow("click on right outer rich text link") << nestedText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(27).center()) << outerLink << outerLink << outerLink; QTest::newRow("click on inner rich text link left") << nestedText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(23).center()) << innerLink << innerLink << innerLink; QTest::newRow("click on inner rich text link right") << nestedText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(26).center()) << innerLink << innerLink << innerLink; QTest::newRow("drag from inner to outer rich text link") << nestedText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(25).center() << metrics.characterRectangle(30).center()) << QString() << innerLink << outerLink; QTest::newRow("drag from outer to inner rich text link") << nestedText << 240. << "textFormat: Text.RichText" << (PointVector() << metrics.characterRectangle(30).center() << metrics.characterRectangle(25).center()) << QString() << outerLink << innerLink; } } void tst_qquicktext::linkInteraction() { QFETCH(QString, text); QFETCH(qreal, width); QFETCH(QString, bindings); QFETCH(PointVector, mousePositions); QFETCH(QString, clickedLink); QFETCH(QString, hoverEnterLink); QFETCH(QString, hoverMoveLink); QString componentStr = "import QtQuick 2.2\nText {\n" "width: " + QString::number(width) + "\n" "height: 320\n" "text: \"" + text + "\"\n" "" + bindings + "\n" "}"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); LinkTest test; QObject::connect(textObject, SIGNAL(linkActivated(QString)), &test, SLOT(linkClicked(QString))); QObject::connect(textObject, SIGNAL(linkHovered(QString)), &test, SLOT(linkHovered(QString))); QVERIFY(mousePositions.count() > 0); QPointF mousePosition = mousePositions.first(); auto globalPos = textObject->mapToGlobal(mousePosition); { QHoverEvent he(QEvent::HoverEnter, mousePosition, globalPos, QPointF()); static_cast(static_cast(textObject))->sendEvent(&he); QMouseEvent me(QEvent::MouseButtonPress, mousePosition, globalPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); static_cast(static_cast(textObject))->sendEvent(&me); } QCOMPARE(test.hoveredLink, hoverEnterLink); QCOMPARE(textObject->hoveredLink(), hoverEnterLink); QCOMPARE(textObject->linkAt(mousePosition.x(), mousePosition.y()), hoverEnterLink); for (int i = 1; i < mousePositions.count(); ++i) { mousePosition = mousePositions.at(i); auto globalPos = textObject->mapToGlobal(mousePosition); QHoverEvent he(QEvent::HoverMove, mousePosition, globalPos, QPointF()); static_cast(static_cast(textObject))->sendEvent(&he); QMouseEvent me(QEvent::MouseMove, mousePosition, globalPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); static_cast(static_cast(textObject))->sendEvent(&me); } QCOMPARE(test.hoveredLink, hoverMoveLink); QCOMPARE(textObject->hoveredLink(), hoverMoveLink); QCOMPARE(textObject->linkAt(mousePosition.x(), mousePosition.y()), hoverMoveLink); { QHoverEvent he(QEvent::HoverLeave, mousePosition, globalPos, QPointF()); static_cast(static_cast(textObject))->sendEvent(&he); QMouseEvent me(QEvent::MouseButtonRelease, mousePosition, globalPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); static_cast(static_cast(textObject))->sendEvent(&me); } QCOMPARE(test.clickedLink, clickedLink); QCOMPARE(test.hoveredLink, QString()); QCOMPARE(textObject->hoveredLink(), QString()); QCOMPARE(textObject->linkAt(-1, -1), QString()); delete textObject; } void tst_qquicktext::baseUrl() { QUrl localUrl("file:///tests/text.qml"); QUrl remoteUrl("http://www.qt-project.org/test.qml"); QQmlComponent textComponent(&engine); textComponent.setData("import QtQuick 2.0\n Text {}", localUrl); QQuickText *textObject = qobject_cast(textComponent.create()); QCOMPARE(textObject->baseUrl(), localUrl); QSignalSpy spy(textObject, SIGNAL(baseUrlChanged())); textObject->setBaseUrl(localUrl); QCOMPARE(textObject->baseUrl(), localUrl); QCOMPARE(spy.count(), 0); textObject->setBaseUrl(remoteUrl); QCOMPARE(textObject->baseUrl(), remoteUrl); QCOMPARE(spy.count(), 1); textObject->resetBaseUrl(); QCOMPARE(textObject->baseUrl(), localUrl); QCOMPARE(spy.count(), 2); } void tst_qquicktext::embeddedImages_data() { QTest::addColumn("qmlfile"); QTest::addColumn("error"); QTest::newRow("local") << testFileUrl("embeddedImagesLocal.qml") << ""; QTest::newRow("local-error") << testFileUrl("embeddedImagesLocalError.qml") << testFileUrl("embeddedImagesLocalError.qml").toString()+":3:1: QML Text: Cannot open: " + testFileUrl("http/notexists.png").toString(); QTest::newRow("local") << testFileUrl("embeddedImagesLocalRelative.qml") << ""; QTest::newRow("remote") << testFileUrl("embeddedImagesRemote.qml") << ""; QTest::newRow("remote-error") << testFileUrl("embeddedImagesRemoteError.qml") << testFileUrl("embeddedImagesRemoteError.qml").toString()+":3:1: QML Text: Error transferring {{ServerBaseUrl}}/notexists.png - server replied: Not found"; QTest::newRow("remote-relative") << testFileUrl("embeddedImagesRemoteRelative.qml") << ""; } void tst_qquicktext::embeddedImages() { // Tests QTBUG-9900 QFETCH(QUrl, qmlfile); QFETCH(QString, error); #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) if (qstrcmp(QTest::currentDataTag(), "remote") == 0 || qstrcmp(QTest::currentDataTag(), "remote-error") == 0 || qstrcmp(QTest::currentDataTag(), "remote-relative") == 0) { QSKIP("Remote tests cause occasional hangs in the CI system -- QTBUG-45655"); } #endif TestHTTPServer server; QVERIFY2(server.listen(), qPrintable(server.errorString())); server.serveDirectory(testFile("http")); error.replace(QStringLiteral("{{ServerBaseUrl}}"), server.baseUrl().toString()); if (!error.isEmpty()) QTest::ignoreMessage(QtWarningMsg, error.toLatin1()); QQuickView *view = new QQuickView; view->rootContext()->setContextProperty(QStringLiteral("serverBaseUrl"), server.baseUrl()); view->setSource(qmlfile); view->show(); view->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(view)); QQuickText *textObject = qobject_cast(view->rootObject()); QVERIFY(textObject != nullptr); QTRY_COMPARE(textObject->resourcesLoading(), 0); QPixmap pm(testFile("http/exists.png")); if (error.isEmpty()) { QCOMPARE(textObject->width(), double(pm.width())); QCOMPARE(textObject->height(), double(pm.height())); } else { QVERIFY(16 != pm.width()); // check test is effective QCOMPARE(textObject->width(), 16.0); // default size of QTextDocument broken image icon QCOMPARE(textObject->height(), 16.0); } delete view; } void tst_qquicktext::lineCount() { QScopedPointer window(createView(testFile("lineCount.qml"))); QQuickText *myText = window->rootObject()->findChild("myText"); QVERIFY(myText != nullptr); QVERIFY(myText->lineCount() > 1); QVERIFY(!myText->truncated()); QCOMPARE(myText->maximumLineCount(), INT_MAX); myText->setMaximumLineCount(2); QCOMPARE(myText->lineCount(), 2); QCOMPARE(myText->truncated(), true); QCOMPARE(myText->maximumLineCount(), 2); myText->resetMaximumLineCount(); QCOMPARE(myText->maximumLineCount(), INT_MAX); QCOMPARE(myText->truncated(), false); myText->setElideMode(QQuickText::ElideRight); myText->setMaximumLineCount(2); QCOMPARE(myText->lineCount(), 2); QCOMPARE(myText->truncated(), true); QCOMPARE(myText->maximumLineCount(), 2); // QTBUG-84458 myText->resetMaximumLineCount(); myText->setText("qqqqq\nqqqqq"); QCOMPARE(myText->lineCount(), 2); myText->setText("qqqqq\nqqqqq\nqqqqq"); QCOMPARE(myText->lineCount(), 3); myText->setText(""); QCOMPARE(myText->lineCount(), 1); myText->setText("qqqqq\nqqqqq\nqqqqq"); QCOMPARE(myText->lineCount(), 3); myText->setFontSizeMode(QQuickText::HorizontalFit); myText->setText(""); QCOMPARE(myText->lineCount(), 1); myText->setText("qqqqq\nqqqqq\nqqqqq"); QCOMPARE(myText->lineCount(), 3); myText->setFontSizeMode(QQuickText::VerticalFit); myText->setText(""); QCOMPARE(myText->lineCount(), 1); myText->setText("qqqqq\nqqqqq\nqqqqq"); QCOMPARE(myText->lineCount(), 3); myText->setFontSizeMode(QQuickText::Fit); myText->setText(""); QCOMPARE(myText->lineCount(), 1); QScopedPointer layoutWindow(createView(testFile("lineLayoutHAlign.qml"))); QQuickText *lineLaidOut = layoutWindow->rootObject()->findChild("myText"); QVERIFY(lineLaidOut != nullptr); lineLaidOut->setText("qqqqq\nqqqqq\nqqqqq"); QCOMPARE(lineLaidOut->lineCount(), 3); lineLaidOut->setFontSizeMode(QQuickText::FixedSize); lineLaidOut->setText(""); QCOMPARE(lineLaidOut->lineCount(), 1); } void tst_qquicktext::lineHeight() { QScopedPointer window(createView(testFile("lineHeight.qml"))); QQuickText *myText = window->rootObject()->findChild("myText"); QVERIFY(myText != nullptr); QCOMPARE(myText->lineHeight(), qreal(1)); QCOMPARE(myText->lineHeightMode(), QQuickText::ProportionalHeight); qreal h = myText->height(); QVERIFY(myText->lineCount() != 0); const qreal h1stLine = h / myText->lineCount(); myText->setLineHeight(1.5); QCOMPARE(myText->height(), qreal(qCeil(h)) * 1.5); QCOMPARE(myText->contentHeight(), qreal(qCeil(h)) * 1.5); myText->setLineHeightMode(QQuickText::FixedHeight); myText->setLineHeight(20); QCOMPARE(myText->height(), myText->lineCount() * 20.0); myText->setText("Lorem ipsum sit amet, consectetur adipiscing elit. Integer felis nisl, varius in pretium nec, venenatis non erat. Proin lobortis interdum dictum."); myText->setLineHeightMode(QQuickText::ProportionalHeight); myText->setLineHeight(1.0); qreal h2 = myText->height(); myText->setLineHeight(2.0); QCOMPARE(myText->height(), h2 * 2.0); myText->setLineHeightMode(QQuickText::FixedHeight); myText->setLineHeight(10); QVERIFY(myText->lineCount() > 1); QCOMPARE(myText->height(), h1stLine + (myText->lineCount() - 1) * 10.0); QCOMPARE(myText->contentHeight(), h1stLine + (myText->lineCount() - 1) * 10.0); QCOMPARE(myText->implicitHeight(), h1stLine + (myText->lineCount() - 1) * 10.0); myText->setLineHeightMode(QQuickText::ProportionalHeight); myText->setLineHeight(0.5); const qreal reducedHeight = h1stLine + (myText->lineCount() - 1) * h1stLine * 0.5; QVERIFY(qAbs(myText->height() - reducedHeight) < 1.0); // allow a deviation of one pixel because the exact height depends on the font. QVERIFY(qAbs(myText->contentHeight() - reducedHeight) < 1.0); QVERIFY(qAbs(myText->implicitHeight() - reducedHeight) < 1.0); } void tst_qquicktext::implicitSize_data() { QTest::addColumn("text"); QTest::addColumn("width"); QTest::addColumn("wrap"); QTest::addColumn("elide"); QTest::addColumn("format"); QTest::newRow("plain") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.NoWrap" << "Text.ElideNone" << "Text.PlainText"; QTest::newRow("richtext") << "The quick red fox jumped over the lazy brown dog" <<" 50" << "Text.NoWrap" << "Text.ElideNone" << "Text.RichText"; QTest::newRow("styledtext") << "The quick red fox jumped over the lazy brown dog" <<" 50" << "Text.NoWrap" << "Text.ElideNone" << "Text.StyledText"; QTest::newRow("plain, 0 width") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.NoWrap" << "Text.ElideNone" << "Text.PlainText"; QTest::newRow("plain, elide") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.NoWrap" << "Text.ElideRight" << "Text.PlainText"; QTest::newRow("plain, 0 width, elide") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.NoWrap" << "Text.ElideRight" << "Text.PlainText"; QTest::newRow("richtext, 0 width") << "The quick red fox jumped over the lazy brown dog" <<" 0" << "Text.NoWrap" << "Text.ElideNone" << "Text.RichText"; QTest::newRow("styledtext, 0 width") << "The quick red fox jumped over the lazy brown dog" <<" 0" << "Text.NoWrap" << "Text.ElideNone" << "Text.StyledText"; QTest::newRow("plain_wrap") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.Wrap" << "Text.ElideNone" << "Text.PlainText"; QTest::newRow("richtext_wrap") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.Wrap" << "Text.ElideNone" << "Text.RichText"; QTest::newRow("styledtext_wrap") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.Wrap" << "Text.ElideNone" << "Text.StyledText"; QTest::newRow("plain_wrap, 0 width") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.Wrap" << "Text.ElideNone" << "Text.PlainText"; QTest::newRow("plain_wrap, elide") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.Wrap" << "Text.ElideRight" << "Text.PlainText"; QTest::newRow("plain_wrap, 0 width, elide") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.Wrap" << "Text.ElideRight" << "Text.PlainText"; QTest::newRow("richtext_wrap, 0 width") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.Wrap" << "Text.ElideNone" << "Text.RichText"; QTest::newRow("styledtext_wrap, 0 width") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.Wrap" << "Text.ElideNone" << "Text.StyledText"; } void tst_qquicktext::implicitSize() { #ifdef Q_OS_ANDROID QSKIP("This segfaults on Android, QTBUG-103096"); #endif QFETCH(QString, text); QFETCH(QString, width); QFETCH(QString, format); QFETCH(QString, wrap); QFETCH(QString, elide); QString componentStr = "import QtQuick 2.0\nText { " "property real iWidth: implicitWidth; " "text: \"" + text + "\"; " "width: " + width + "; " "textFormat: " + format + "; " "wrapMode: " + wrap + "; " "elide: " + elide + "; " "maximumLineCount: 2 }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject->width() < textObject->implicitWidth()); QCOMPARE(textObject->height(), textObject->implicitHeight()); QCOMPARE(textObject->property("iWidth").toReal(), textObject->implicitWidth()); textObject->resetWidth(); QCOMPARE(textObject->width(), textObject->implicitWidth()); QCOMPARE(textObject->height(), textObject->implicitHeight()); delete textObject; } void tst_qquicktext::dependentImplicitSizes() { QQmlComponent component(&engine, testFile("implicitSizes.qml")); QScopedPointer object(component.create()); QVERIFY(object.data()); QQuickText *reference = object->findChild("reference"); QQuickText *fixedWidthAndHeight = object->findChild("fixedWidthAndHeight"); QQuickText *implicitWidthFixedHeight = object->findChild("implicitWidthFixedHeight"); QQuickText *fixedWidthImplicitHeight = object->findChild("fixedWidthImplicitHeight"); QQuickText *cappedWidthAndHeight = object->findChild("cappedWidthAndHeight"); QQuickText *cappedWidthFixedHeight = object->findChild("cappedWidthFixedHeight"); QQuickText *fixedWidthCappedHeight = object->findChild("fixedWidthCappedHeight"); QVERIFY(reference); QVERIFY(fixedWidthAndHeight); QVERIFY(implicitWidthFixedHeight); QVERIFY(fixedWidthImplicitHeight); QVERIFY(cappedWidthAndHeight); QVERIFY(cappedWidthFixedHeight); QVERIFY(fixedWidthCappedHeight); QCOMPARE(reference->width(), reference->implicitWidth()); QCOMPARE(reference->height(), reference->implicitHeight()); QVERIFY(fixedWidthAndHeight->width() < fixedWidthAndHeight->implicitWidth()); QVERIFY(fixedWidthAndHeight->height() < fixedWidthAndHeight->implicitHeight()); QCOMPARE(fixedWidthAndHeight->implicitWidth(), reference->implicitWidth()); QVERIFY(fixedWidthAndHeight->implicitHeight() > reference->implicitHeight()); QCOMPARE(implicitWidthFixedHeight->width(), implicitWidthFixedHeight->implicitWidth()); QVERIFY(implicitWidthFixedHeight->height() < implicitWidthFixedHeight->implicitHeight()); QCOMPARE(implicitWidthFixedHeight->implicitWidth(), reference->implicitWidth()); QCOMPARE(implicitWidthFixedHeight->implicitHeight(), reference->implicitHeight()); QVERIFY(fixedWidthImplicitHeight->width() < fixedWidthImplicitHeight->implicitWidth()); QCOMPARE(fixedWidthImplicitHeight->height(), fixedWidthImplicitHeight->implicitHeight()); QCOMPARE(fixedWidthImplicitHeight->implicitWidth(), reference->implicitWidth()); QCOMPARE(fixedWidthImplicitHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight()); QVERIFY(cappedWidthAndHeight->width() < cappedWidthAndHeight->implicitWidth()); QVERIFY(cappedWidthAndHeight->height() < cappedWidthAndHeight->implicitHeight()); QCOMPARE(cappedWidthAndHeight->implicitWidth(), reference->implicitWidth()); QCOMPARE(cappedWidthAndHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight()); QVERIFY(cappedWidthFixedHeight->width() < cappedWidthAndHeight->implicitWidth()); QVERIFY(cappedWidthFixedHeight->height() < cappedWidthFixedHeight->implicitHeight()); QCOMPARE(cappedWidthFixedHeight->implicitWidth(), reference->implicitWidth()); QCOMPARE(cappedWidthFixedHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight()); QVERIFY(fixedWidthCappedHeight->width() < fixedWidthCappedHeight->implicitWidth()); QVERIFY(fixedWidthCappedHeight->height() < fixedWidthCappedHeight->implicitHeight()); QCOMPARE(fixedWidthCappedHeight->implicitWidth(), reference->implicitWidth()); QCOMPARE(fixedWidthCappedHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight()); } void tst_qquicktext::contentSize() { QString componentStr = "import QtQuick 2.0\nText { width: 75; height: 16; font.pixelSize: 10 }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QScopedPointer object(textComponent.create()); QQuickText *textObject = qobject_cast(object.data()); QSignalSpy spySize(textObject, SIGNAL(contentSizeChanged())); QSignalSpy spyWidth(textObject, SIGNAL(contentWidthChanged(qreal))); QSignalSpy spyHeight(textObject, SIGNAL(contentHeightChanged(qreal))); textObject->setText("The quick red fox jumped over the lazy brown dog"); QVERIFY(textObject->contentWidth() > textObject->width()); QVERIFY(textObject->contentHeight() < textObject->height()); QCOMPARE(spySize.count(), 1); QCOMPARE(spyWidth.count(), 1); QCOMPARE(spyHeight.count(), 0); textObject->setWrapMode(QQuickText::WordWrap); QVERIFY(textObject->contentWidth() <= textObject->width()); QVERIFY(textObject->contentHeight() > textObject->height()); QCOMPARE(spySize.count(), 2); QCOMPARE(spyWidth.count(), 2); QCOMPARE(spyHeight.count(), 1); textObject->setElideMode(QQuickText::ElideRight); QVERIFY(textObject->contentWidth() <= textObject->width()); QVERIFY(textObject->contentHeight() < textObject->height()); QCOMPARE(spySize.count(), 3); QCOMPARE(spyWidth.count(), 3); QCOMPARE(spyHeight.count(), 2); int spyCount = 3; qreal elidedWidth = textObject->contentWidth(); textObject->setText("The quickredfoxjumpedoverthe lazy brown dog"); QVERIFY(textObject->contentWidth() <= textObject->width()); QVERIFY(textObject->contentHeight() < textObject->height()); // this text probably won't have the same elided width, but it's not guaranteed. if (textObject->contentWidth() != elidedWidth) QCOMPARE(spySize.count(), ++spyCount); else QCOMPARE(spySize.count(), spyCount); textObject->setElideMode(QQuickText::ElideNone); QVERIFY(textObject->contentWidth() > textObject->width()); QVERIFY(textObject->contentHeight() > textObject->height()); QCOMPARE(spySize.count(), ++spyCount); QCOMPARE(spyWidth.count(), spyCount); QCOMPARE(spyHeight.count(), 3); } void tst_qquicktext::geometryChanged() { // Test that text is re-laid out when the geometry of the item by verifying changes in content // size. Implicit width is also tested as that in combination with item geometry provides a // reference for expected content sizes. QString componentStr = "import QtQuick 2.0\nText { font.family: \"__Qt__Box__Engine__\"; font.pixelSize: 10 }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QScopedPointer object(textComponent.create()); QQuickText *textObject = qobject_cast(object.data()); const qreal implicitHeight = textObject->implicitHeight(); const qreal widths[] = { 100, 2000, 3000, -100, 100 }; const qreal heights[] = { implicitHeight, 2000, 3000, -implicitHeight, implicitHeight }; QCOMPARE(textObject->implicitWidth(), 0.); QVERIFY(implicitHeight > 0.); QCOMPARE(textObject->width(), textObject->implicitWidth()); QCOMPARE(textObject->height(), implicitHeight); QCOMPARE(textObject->contentWidth(), textObject->implicitWidth()); QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setText("The quick red fox jumped over the lazy brown dog"); const qreal implicitWidth = textObject->implicitWidth(); QVERIFY(implicitWidth > 0.); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), textObject->implicitWidth()); QCOMPARE(textObject->height(), textObject->implicitHeight()); QCOMPARE(textObject->contentWidth(), textObject->implicitWidth()); QCOMPARE(textObject->contentHeight(), textObject->implicitHeight()); // Changing the geometry with no eliding, or wrapping doesn't change the content size. for (int i = 0; i < 5; ++i) { textObject->setWidth(widths[i]); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), widths[i]); QCOMPARE(textObject->height(), implicitHeight); QCOMPARE(textObject->contentWidth(), implicitWidth); QCOMPARE(textObject->contentHeight(), implicitHeight); } // With eliding enabled the content width is bounded to the item width, but is never // larger than the implicit width. textObject->setElideMode(QQuickText::ElideRight); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), implicitHeight); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setWidth(2000.); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), 2000.); QCOMPARE(textObject->height(), implicitHeight); QCOMPARE(textObject->contentWidth(), implicitWidth); QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setWidth(3000.); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), 3000.); QCOMPARE(textObject->height(), implicitHeight); QCOMPARE(textObject->contentWidth(), implicitWidth); QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setWidth(-100); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), -100.); QCOMPARE(textObject->height(), implicitHeight); QCOMPARE(textObject->contentWidth(), 0.); QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setWidth(100.); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), implicitHeight); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), implicitHeight); // With wrapping enabled the implicit height changes with the width. textObject->setElideMode(QQuickText::ElideNone); textObject->setWrapMode(QQuickText::Wrap); const qreal wrappedImplicitHeight = textObject->implicitHeight(); QVERIFY(wrappedImplicitHeight > implicitHeight); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), wrappedImplicitHeight); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight); textObject->setWidth(2000.); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), 2000.); QCOMPARE(textObject->height(), implicitHeight); QCOMPARE(textObject->contentWidth(), implicitWidth); QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setWidth(3000.); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), 3000.); QCOMPARE(textObject->height(), implicitHeight); QCOMPARE(textObject->contentWidth(), implicitWidth); QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setWidth(-100); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), -100.); QCOMPARE(textObject->height(), implicitHeight); QCOMPARE(textObject->contentWidth(), implicitWidth); // 0 or negative width item won't wrap. QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setWidth(100.); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), wrappedImplicitHeight); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight); // With no eliding or maximum line count the content height is the same as the implicit height. for (int i = 0; i < 5; ++i) { textObject->setHeight(heights[i]); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), heights[i]); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight); } // The implicit height is unaffected by eliding but the content height will change. textObject->setElideMode(QQuickText::ElideRight); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), implicitHeight); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setHeight(2000); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), 2000.); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight); textObject->setHeight(3000); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), 3000.); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight); textObject->setHeight(-implicitHeight); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), -implicitHeight); QVERIFY(textObject->contentWidth() <= 0.); QCOMPARE(textObject->contentHeight(), implicitHeight); // content height is never less than font height. seems a little odd in this instance. textObject->setHeight(implicitHeight); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), implicitHeight); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), implicitHeight); // Varying the height with a maximum line count but no eliding won't affect the content height. textObject->setElideMode(QQuickText::ElideNone); textObject->setMaximumLineCount(2); textObject->resetHeight(); const qreal maxLineCountImplicitHeight = textObject->implicitHeight(); QVERIFY(maxLineCountImplicitHeight > implicitHeight); QVERIFY(maxLineCountImplicitHeight < wrappedImplicitHeight); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), maxLineCountImplicitHeight); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight); for (int i = 0; i < 5; ++i) { textObject->setHeight(heights[i]); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), maxLineCountImplicitHeight); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), heights[i]); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight); } // Varying the width with a maximum line count won't increase the implicit height beyond the // height of the maximum number of lines. textObject->setWidth(2000.); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), 2000.); QCOMPARE(textObject->height(), implicitHeight); QCOMPARE(textObject->contentWidth(), implicitWidth); QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setWidth(3000.); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), 3000.); QCOMPARE(textObject->height(), implicitHeight); QCOMPARE(textObject->contentWidth(), implicitWidth); QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setWidth(-100); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), implicitHeight); QCOMPARE(textObject->width(), -100.); QCOMPARE(textObject->height(), implicitHeight); QCOMPARE(textObject->contentWidth(), implicitWidth); // 0 or negative width item won't wrap. QCOMPARE(textObject->contentHeight(), implicitHeight); textObject->setWidth(50.); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), maxLineCountImplicitHeight); QCOMPARE(textObject->width(), 50.); QCOMPARE(textObject->height(), implicitHeight); QVERIFY(textObject->contentWidth() <= 50.); QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight); textObject->setWidth(100.); QCOMPARE(textObject->implicitWidth(), implicitWidth); QCOMPARE(textObject->implicitHeight(), maxLineCountImplicitHeight); QCOMPARE(textObject->width(), 100.); QCOMPARE(textObject->height(), implicitHeight); QVERIFY(textObject->contentWidth() <= 100.); QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight); } void tst_qquicktext::implicitSizeBinding_data() { implicitSize_data(); } void tst_qquicktext::implicitSizeBinding() { QFETCH(QString, text); QFETCH(QString, wrap); QFETCH(QString, format); QString componentStr = "import QtQuick 2.0\nText { text: \"" + text + "\"; width: implicitWidth; height: implicitHeight; wrapMode: " + wrap + "; textFormat: " + format + " }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QScopedPointer object(textComponent.create()); QQuickText *textObject = qobject_cast(object.data()); QCOMPARE(textObject->width(), textObject->implicitWidth()); QCOMPARE(textObject->height(), textObject->implicitHeight()); textObject->resetWidth(); QCOMPARE(textObject->width(), textObject->implicitWidth()); QCOMPARE(textObject->height(), textObject->implicitHeight()); textObject->resetHeight(); QCOMPARE(textObject->width(), textObject->implicitWidth()); QCOMPARE(textObject->height(), textObject->implicitHeight()); } void tst_qquicktext::boundingRect_data() { QTest::addColumn("format"); QTest::newRow("PlainText") << "Text.PlainText"; QTest::newRow("StyledText") << "Text.StyledText"; QTest::newRow("RichText") << "Text.RichText"; } void tst_qquicktext::boundingRect() { QFETCH(QString, format); QQmlComponent component(&engine); component.setData("import QtQuick 2.0\n Text { textFormat:" + format.toUtf8() + "}", QUrl()); QScopedPointer object(component.create()); QQuickText *text = qobject_cast(object.data()); QVERIFY(text); QCOMPARE(text->boundingRect().x(), qreal(0)); QCOMPARE(text->boundingRect().y(), qreal(0)); QCOMPARE(text->boundingRect().width(), qreal(0)); QCOMPARE(text->boundingRect().height(), qreal(qCeil(QFontMetricsF(text->font()).height()))); text->setText("Hello World"); QTextLayout layout(text->text()); layout.setFont(text->font()); if (!qmlDisableDistanceField()) { QTextOption option; option.setUseDesignMetrics(true); layout.setTextOption(option); } layout.beginLayout(); QTextLine line = layout.createLine(); layout.endLayout(); QCOMPARE(text->boundingRect().x(), qreal(0)); QCOMPARE(text->boundingRect().y(), qreal(0)); QCOMPARE(text->boundingRect().width(), line.naturalTextWidth()); QFontMetricsF fontMetrics(QGuiApplication::font()); qreal leading = fontMetrics.leading(); qreal ascent = fontMetrics.ascent(); qreal descent = fontMetrics.descent(); bool leadingOverflow = text->textFormat() == QQuickText::RichText && qCeil(ascent + descent) < qCeil(ascent + descent + leading); if (leadingOverflow) QEXPECT_FAIL("", "See QTBUG-82954", Continue); QCOMPARE(text->boundingRect().height(), line.height()); // the size of the bounding rect shouldn't be bounded by the size of item. text->setWidth(text->width() / 2); QCOMPARE(text->boundingRect().x(), qreal(0)); QCOMPARE(text->boundingRect().y(), qreal(0)); QCOMPARE(text->boundingRect().width(), line.naturalTextWidth()); if (leadingOverflow) QEXPECT_FAIL("", "See QTBUG-82954", Continue); QCOMPARE(text->boundingRect().height(), line.height()); text->setHeight(text->height() * 2); QCOMPARE(text->boundingRect().x(), qreal(0)); QCOMPARE(text->boundingRect().y(), qreal(0)); QCOMPARE(text->boundingRect().width(), line.naturalTextWidth()); if (leadingOverflow) QEXPECT_FAIL("", "See QTBUG-82954", Continue); QCOMPARE(text->boundingRect().height(), line.height()); text->setHAlign(QQuickText::AlignRight); QCOMPARE(text->boundingRect().x(), text->width() - line.naturalTextWidth()); QCOMPARE(text->boundingRect().y(), qreal(0)); QCOMPARE(text->boundingRect().width(), line.naturalTextWidth()); if (leadingOverflow) QEXPECT_FAIL("", "See QTBUG-82954", Continue); QCOMPARE(text->boundingRect().height(), line.height()); QQuickItemPrivate::get(text)->setLayoutMirror(true); QCOMPARE(text->boundingRect().x(), qreal(0)); QCOMPARE(text->boundingRect().y(), qreal(0)); QCOMPARE(text->boundingRect().width(), line.naturalTextWidth()); if (leadingOverflow) QEXPECT_FAIL("", "See QTBUG-82954", Continue); QCOMPARE(text->boundingRect().height(), line.height()); text->setHAlign(QQuickText::AlignLeft); QCOMPARE(text->boundingRect().x(), text->width() - line.naturalTextWidth()); QCOMPARE(text->boundingRect().y(), qreal(0)); QCOMPARE(text->boundingRect().width(), line.naturalTextWidth()); if (leadingOverflow) QEXPECT_FAIL("", "See QTBUG-82954", Continue); QCOMPARE(text->boundingRect().height(), line.height()); text->setWrapMode(QQuickText::Wrap); QCOMPARE(text->boundingRect().right(), text->width()); QCOMPARE(text->boundingRect().y(), qreal(0)); QVERIFY(text->boundingRect().width() < line.naturalTextWidth()); QVERIFY(text->boundingRect().height() > line.height()); text->setVAlign(QQuickText::AlignBottom); QCOMPARE(text->boundingRect().right(), text->width()); QCOMPARE(text->boundingRect().bottom(), text->height()); QVERIFY(text->boundingRect().width() < line.naturalTextWidth()); QVERIFY(text->boundingRect().height() > line.height()); } void tst_qquicktext::clipRect() { QQmlComponent component(&engine); component.setData("import QtQuick 2.0\n Text {}", QUrl()); QScopedPointer object(component.create()); QQuickText *text = qobject_cast(object.data()); QVERIFY(text); QTextLayout layout; layout.setFont(text->font()); QCOMPARE(text->clipRect().x(), qreal(0)); QCOMPARE(text->clipRect().y(), qreal(0)); QCOMPARE(text->clipRect().width(), text->width()); QCOMPARE(text->clipRect().height(), text->height()); text->setText("Hello World"); QCOMPARE(text->clipRect().x(), qreal(0)); QCOMPARE(text->clipRect().y(), qreal(0)); QCOMPARE(text->clipRect().width(), text->width()); QCOMPARE(text->clipRect().height(), text->height()); // Clip rect follows the item not content dimensions. text->setWidth(text->width() / 2); QCOMPARE(text->clipRect().x(), qreal(0)); QCOMPARE(text->clipRect().y(), qreal(0)); QCOMPARE(text->clipRect().width(), text->width()); QCOMPARE(text->clipRect().height(), text->height()); text->setHeight(text->height() * 2); QCOMPARE(text->clipRect().x(), qreal(0)); QCOMPARE(text->clipRect().y(), qreal(0)); QCOMPARE(text->clipRect().width(), text->width()); QCOMPARE(text->clipRect().height(), text->height()); // Setting a style adds a small amount of padding to the clip rect. text->setStyle(QQuickText::Outline); QCOMPARE(text->clipRect().x(), qreal(-1)); QCOMPARE(text->clipRect().y(), qreal(0)); QCOMPARE(text->clipRect().width(), text->width() + 2); QCOMPARE(text->clipRect().height(), text->height() + 2); } void tst_qquicktext::largeTextObservesViewport_data() { QTest::addColumn("text"); QTest::addColumn("textFormat"); QTest::addColumn("linesAboveViewport"); QTest::addColumn("parentIsViewport"); QString text; { QStringList lines; // "line 100" is 8 characters; many lines are longer, some are shorter // so we populate 1250 lines, 11389 characters const int lineCount = QQuickTextPrivate::largeTextSizeThreshold / 8; lines.reserve(lineCount); for (int i = 0; i < lineCount; ++i) lines << QLatin1String("line ") + QString::number(i); text = lines.join('\n'); } Q_ASSERT(text.size() > QQuickTextPrivate::largeTextSizeThreshold); // by default, the root item acts as the viewport: // QQuickTextNode doesn't populate lines of text beyond the bottom of the window QTest::newRow("default plain text") << text << QQuickText::PlainText << 0 << false; // make the rectangle into a viewport item, and move the text upwards: // QQuickTextNode doesn't populate lines of text beyond the bottom of the viewport rectangle QTest::newRow("clipped plain text") << text << QQuickText::PlainText << 10 << true; { QStringList lines; // "line 100" is 8 characters; many lines are longer, some are shorter // so we populate 1250 lines, 11389 characters const int lineCount = QQuickTextPrivate::largeTextSizeThreshold / 8; lines.reserve(lineCount); for (int i = 0; i < lineCount; ++i) { if (i > 0 && i % 50 == 0) lines << QLatin1String("

chapter ") + QString::number(i / 50) + QLatin1String("

"); lines << QLatin1String("

line ") + QString::number(i) + QLatin1String("

"); } text = lines.join('\n'); } Q_ASSERT(text.size() > QQuickTextPrivate::largeTextSizeThreshold); // by default, the root item acts as the viewport: // QQuickTextNodeEngine doesn't populate blocks beyond the bottom of the window QTest::newRow("default styled text") << text << QQuickText::StyledText << 0 << false; // make the rectangle into a viewport item, and move the text upwards: // QQuickTextNodeEngine doesn't populate blocks that don't intersect the viewport rectangle QTest::newRow("clipped styled text") << text << QQuickText::StyledText << 10 << true; // get a chapter heading into the viewport QTest::newRow("heading visible") << text << QQuickText::StyledText << 90 << true; } void tst_qquicktext::largeTextObservesViewport() { QFETCH(QString, text); QFETCH(QQuickText::TextFormat, textFormat); QFETCH(int, linesAboveViewport); QFETCH(bool, parentIsViewport); QQuickView window; QByteArray errorMessage; QVERIFY2(QQuickTest::initView(window, testFileUrl("viewport.qml"), true, &errorMessage), errorMessage.constData()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QQuickText *textItem = window.rootObject()->findChild(); QVERIFY(textItem); QQuickItem *viewportItem = textItem->parentItem(); QQuickTextPrivate *textPriv = QQuickTextPrivate::get(textItem); QQuickTextNode *node = static_cast(textPriv->paintNode); QFontMetricsF fm(textItem->font()); const qreal expectedTextHeight = (parentIsViewport ? viewportItem->height() : window.height() - viewportItem->y()); const qreal lineSpacing = qCeil(fm.height()); // A paragraph break is the same as an extra line break; so since our "lines" are paragraphs in StyledText, // visually, with StyledText we skip down 10 "lines", but the first paragraph you see says "line 5". // It's OK anyway for the test, because QQuickTextNode::addTextLayout() treats the paragraph breaks like lines of text. const int expectedLastLine = linesAboveViewport + int(expectedTextHeight / lineSpacing); viewportItem->setFlag(QQuickItem::ItemIsViewport, parentIsViewport); textItem->setY(lineSpacing * -linesAboveViewport); textItem->setTextFormat(textFormat); textItem->setText(text); qCDebug(lcTests) << "text size" << textItem->text().size() << "lines" << textItem->lineCount() << "font" << textItem->font(); Q_ASSERT(textItem->text().size() > QQuickTextPrivate::largeTextSizeThreshold); QVERIFY(textItem->flags().testFlag(QQuickItem::ItemObservesViewport)); // large text sets this flag automatically QCOMPARE(textItem->viewportItem(), parentIsViewport ? viewportItem : viewportItem->parentItem()); QTRY_VERIFY(node->renderedLineRange().first >= 0); // wait for rendering auto renderedLineRange = node->renderedLineRange(); qCDebug(lcTests) << "first line rendered" << renderedLineRange.first << "expected" << linesAboveViewport << "first line past viewport" << renderedLineRange.second << "expected last line" << expectedLastLine << "based on available height" << expectedTextHeight << "and line height" << lineSpacing << "rather than spacing" << fm.lineSpacing(); // QTest::qWait(2000); // uncomment for visual check QCOMPARE(renderedLineRange.first, linesAboveViewport); // if linesAboveViewport == 90, a chapter heading is visible, and those take more space QVERIFY(qAbs(renderedLineRange.second - (expectedLastLine + 1)) < (linesAboveViewport > 80 ? 4 : 2)); } void tst_qquicktext::lineLaidOut() { QScopedPointer window(createView(testFile("lineLayout.qml"))); QQuickText *myText = window->rootObject()->findChild("myText"); QVERIFY(myText != nullptr); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText); QVERIFY(textPrivate != nullptr); QVERIFY(!textPrivate->extra.isAllocated()); for (int i = 0; i < textPrivate->layout.lineCount(); ++i) { QRectF r = textPrivate->layout.lineAt(i).rect(); QVERIFY(r.width() == i * 15); if (i >= 30) QVERIFY(r.x() == r.width() + 30); if (i >= 60) { QVERIFY(r.x() == r.width() * 2 + 60); QCOMPARE(r.height(), qreal(20)); } } // Ensure that isLast was correctly emitted int lastLineNumber = myText->property("lastLineNumber").toInt(); QCOMPARE(lastLineNumber, myText->lineCount() - 1); // Ensure that only one line was considered last (after changing its width) bool receivedMultipleLastLines = myText->property("receivedMultipleLastLines").toBool(); QVERIFY(!receivedMultipleLastLines); } void tst_qquicktext::lineLaidOutRelayout() { QScopedPointer window(createView(testFile("lineLayoutRelayout.qml"))); window->show(); window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window.data())); QQuickText *myText = window->rootObject()->findChild("myText"); QVERIFY(myText != nullptr); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText); QVERIFY(textPrivate != nullptr); QVERIFY(!textPrivate->extra.isAllocated()); qreal y = 0.0; for (int i = 0; i < textPrivate->layout.lineCount(); ++i) { QTextLine line = textPrivate->layout.lineAt(i); const QRectF r = line.rect(); if (r.x() == 0) { QCOMPARE(r.y(), y); } else { if (qFuzzyIsNull(r.y())) y = 0.0; QCOMPARE(r.x(), myText->width() / 2); QCOMPARE(r.y(), y); } y += line.height(); } } void tst_qquicktext::lineLaidOutHAlign() { QScopedPointer window(createView(testFile("lineLayoutHAlign.qml"))); QQuickText *myText = window->rootObject()->findChild("myText"); QVERIFY(myText != nullptr); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText); QVERIFY(textPrivate != nullptr); QCOMPARE(textPrivate->layout.lineCount(), 1); QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().x() < 0.0); } void tst_qquicktext::imgTagsBaseUrl_data() { QTest::addColumn("src"); QTest::addColumn("baseUrl"); QTest::addColumn("contextUrl"); QTest::addColumn("imgHeight"); QTest::newRow("absolute local") << testFileUrl("images/heart200.png") << QUrl() << QUrl() << 181.; QTest::newRow("relative local context 1") << QUrl("images/heart200.png") << QUrl() << testFileUrl("/app.qml") << 181.; QTest::newRow("relative local context 2") << QUrl("heart200.png") << QUrl() << testFileUrl("images/app.qml") << 181.; QTest::newRow("relative local base 1") << QUrl("images/heart200.png") << testFileUrl("") << testFileUrl("nonexistant/app.qml") << 181.; QTest::newRow("relative local base 2") << QUrl("heart200.png") << testFileUrl("images/") << testFileUrl("nonexistant/app.qml") << 181.; QTest::newRow("base relative to local context") << QUrl("heart200.png") << testFileUrl("images/") << testFileUrl("/app.qml") << 181.; QTest::newRow("absolute remote") << QUrl("http://testserver/images/heart200.png") << QUrl() << QUrl() << 181.; QTest::newRow("relative remote base 1") << QUrl("images/heart200.png") << QUrl("http://testserver/") << testFileUrl("nonexistant/app.qml") << 181.; QTest::newRow("relative remote base 2") << QUrl("heart200.png") << QUrl("http://testserver/images/") << testFileUrl("nonexistant/app.qml") << 181.; } void tst_qquicktext::lineLaidOutImplicitWidth() { QScopedPointer window(createView(testFile("lineLayoutImplicitWidth.qml"))); QQuickText *myText = window->rootObject()->findChild("myText"); QVERIFY(myText != nullptr); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText); QVERIFY(textPrivate != nullptr); // Retrieve the saved implicitWidth values of each rendered line QVariant widthsProperty = myText->property("lineImplicitWidths"); QVERIFY(!widthsProperty.isNull()); QVERIFY(widthsProperty.isValid()); QVERIFY(widthsProperty.canConvert()); QJSValue widthsValue = widthsProperty.value(); QVERIFY(widthsValue.isArray()); int lineCount = widthsValue.property("length").toInt(); QVERIFY(lineCount > 0); // Create the same text layout by hand // Note that this approach needs additional processing for styled text, // so we only use it for plain text here. QTextLayout layout; layout.setCacheEnabled(true); layout.setText(myText->text()); layout.setTextOption(textPrivate->layout.textOption()); layout.setFont(myText->font()); layout.beginLayout(); for (QTextLine line = layout.createLine(); line.isValid(); line = layout.createLine()) { line.setLineWidth(myText->width()); } layout.endLayout(); // Line count of the just created layout should match the rendered text QCOMPARE(lineCount, layout.lineCount()); // Go through each line and verify that the values emitted by lineLaidOut are correct for (int i = 0; i < layout.lineCount(); ++i) { qreal implicitWidth = widthsValue.property(i).toNumber(); QVERIFY(implicitWidth > 0); QTextLine line = layout.lineAt(i); QCOMPARE(implicitWidth, line.naturalTextWidth()); } } static QUrl substituteTestServerUrl(const QUrl &serverUrl, const QUrl &testUrl) { QUrl result = testUrl; if (result.host() == QStringLiteral("testserver")) { result.setScheme(serverUrl.scheme()); result.setHost(serverUrl.host()); result.setPort(serverUrl.port()); } return result; } void tst_qquicktext::imgTagsBaseUrl() { QFETCH(QUrl, src); QFETCH(QUrl, baseUrl); QFETCH(QUrl, contextUrl); QFETCH(qreal, imgHeight); TestHTTPServer server; QVERIFY2(server.listen(), qPrintable(server.errorString())); server.serveDirectory(testFile("")); src = substituteTestServerUrl(server.baseUrl(), src); baseUrl = substituteTestServerUrl(server.baseUrl(), baseUrl); contextUrl = substituteTestServerUrl(server.baseUrl(), contextUrl); QByteArray baseUrlFragment; if (!baseUrl.isEmpty()) baseUrlFragment = "; baseUrl: \"" + baseUrl.toEncoded() + "\""; QByteArray componentStr = "import QtQuick 2.0\nText { text: \"This is a test \"" + baseUrlFragment + " }"; QQmlComponent component(&engine); component.setData(componentStr, contextUrl); QScopedPointer object(component.create()); QQuickText *textObject = qobject_cast(object.data()); QVERIFY(textObject); QCoreApplication::processEvents(); QTRY_COMPARE(textObject->height(), imgHeight); } void tst_qquicktext::imgTagsAlign_data() { QTest::addColumn("src"); QTest::addColumn("imgHeight"); QTest::addColumn("align"); QTest::newRow("heart-bottom") << "data/images/heart200.png" << 181 << "bottom"; QTest::newRow("heart-middle") << "data/images/heart200.png" << 181 << "middle"; QTest::newRow("heart-top") << "data/images/heart200.png" << 181 << "top"; QTest::newRow("starfish-bottom") << "data/images/starfish_2.png" << 217 << "bottom"; QTest::newRow("starfish-middle") << "data/images/starfish_2.png" << 217 << "middle"; QTest::newRow("starfish-top") << "data/images/starfish_2.png" << 217 << "top"; } void tst_qquicktext::imgTagsAlign() { QFETCH(QString, src); QFETCH(int, imgHeight); QFETCH(QString, align); QString componentStr = "import QtQuick 2.0\nText { text: \"This is a test of image.\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(".")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->height(), qreal(imgHeight)); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject); QVERIFY(textPrivate != nullptr); QRectF br = textPrivate->layout.boundingRect(); if (align == "bottom") QVERIFY(br.y() == imgHeight - br.height()); else if (align == "middle") QVERIFY(br.y() == imgHeight / 2.0 - br.height() / 2.0); else if (align == "top") QCOMPARE(br.y(), qreal(0)); delete textObject; } void tst_qquicktext::imgTagsMultipleImages() { QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish and another one.\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(".")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE(textObject->height(), qreal(85)); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject); QVERIFY(textPrivate != nullptr); QCOMPARE(textPrivate->extra->visibleImgTags.count(), 2); delete textObject; } void tst_qquicktext::imgTagsElide() { QScopedPointer window(createView(testFile("imgTagsElide.qml"))); QQuickText *myText = window->rootObject()->findChild("myText"); QVERIFY(myText != nullptr); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText); QVERIFY(textPrivate != nullptr); QCOMPARE(textPrivate->extra->visibleImgTags.count(), 0); myText->setMaximumLineCount(20); QTRY_COMPARE(textPrivate->extra->visibleImgTags.count(), 1); delete myText; } void tst_qquicktext::imgTagsUpdates() { QScopedPointer window(createView(testFile("imgTagsUpdates.qml"))); QQuickText *myText = window->rootObject()->findChild("myText"); QVERIFY(myText != nullptr); QSignalSpy spy(myText, SIGNAL(contentSizeChanged())); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(myText); QVERIFY(textPrivate != nullptr); myText->setText("This is a heart."); QCOMPARE(textPrivate->extra->visibleImgTags.count(), 1); QCOMPARE(spy.count(), 1); myText->setMaximumLineCount(2); myText->setText("This is another heart."); QTRY_COMPARE(textPrivate->extra->visibleImgTags.count(), 1); // if maximumLineCount is set and the img tag doesn't have an explicit size // we relayout twice. QCOMPARE(spy.count(), 3); delete myText; } void tst_qquicktext::imgTagsError() { QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish.\" }"; QQmlComponent textComponent(&engine); QTest::ignoreMessage(QtWarningMsg, ":2:1: QML Text: Cannot open: file:data/images/starfish_2.pn"); textComponent.setData(componentStr.toLatin1(), QUrl("file:")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); delete textObject; } void tst_qquicktext::fontSizeMode_data() { QTest::addColumn("text"); QTest::newRow("plain") << "The quick red fox jumped over the lazy brown dog"; QTest::newRow("styled") << "The quick red fox jumped over the lazy brown dog"; } void tst_qquicktext::fontSizeMode() { QFETCH(QString, text); QScopedPointer window(createView(testFile("fontSizeMode.qml"))); window->show(); QQuickText *myText = window->rootObject()->findChild("myText"); QVERIFY(myText != nullptr); myText->setText(text); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); qreal originalWidth = myText->contentWidth(); qreal originalHeight = myText->contentHeight(); // The original text unwrapped should exceed the width of the item. QVERIFY(originalWidth > myText->width()); QVERIFY(originalHeight < myText->height()); QFont font = myText->font(); font.setPixelSize(64); myText->setFont(font); myText->setFontSizeMode(QQuickText::HorizontalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // Font size reduced to fit within the width of the item. qreal horizontalFitWidth = myText->contentWidth(); qreal horizontalFitHeight = myText->contentHeight(); QVERIFY(horizontalFitWidth <= myText->width() + 2); // rounding QVERIFY(horizontalFitHeight <= myText->height() + 2); // Elide won't affect the size with HorizontalFit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); myText->setElideMode(QQuickText::ElideLeft); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); myText->setElideMode(QQuickText::ElideMiddle); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::VerticalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // Font size increased to fill the height of the item. qreal verticalFitHeight = myText->contentHeight(); QVERIFY(myText->contentWidth() > myText->width()); QVERIFY(verticalFitHeight <= myText->height() + 2); QVERIFY(verticalFitHeight > originalHeight); // Elide won't affect the height of a single line with VerticalFit but will crop the width. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(myText->truncated()); QVERIFY(myText->contentWidth() <= myText->width() + 2); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideLeft); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(myText->truncated()); QVERIFY(myText->contentWidth() <= myText->width() + 2); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideMiddle); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(myText->truncated()); QVERIFY(myText->contentWidth() <= myText->width() + 2); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::Fit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // Should be the same as HorizontalFit with no wrapping. QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); // Elide won't affect the size with Fit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); myText->setElideMode(QQuickText::ElideLeft); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); myText->setElideMode(QQuickText::ElideMiddle); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::FixedSize); myText->setWrapMode(QQuickText::Wrap); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); originalWidth = myText->contentWidth(); originalHeight = myText->contentHeight(); // The original text wrapped should exceed the height of the item. QVERIFY(originalWidth <= myText->width() + 2); QVERIFY(originalHeight > myText->height()); myText->setFontSizeMode(QQuickText::HorizontalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the // same size as without text wrapping. QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); // Elide won't affect the size with HorizontalFit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::VerticalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // VerticalFit should reduce the size to the wrapped text within the vertical height. verticalFitHeight = myText->contentHeight(); qreal verticalFitWidth = myText->contentWidth(); QVERIFY(myText->contentWidth() <= myText->width() + 2); QVERIFY(verticalFitHeight <= myText->height() + 2); QVERIFY(verticalFitHeight < originalHeight); // Elide won't affect the height or width of a wrapped text with VerticalFit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::Fit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // Should be the same as VerticalFit with wrapping. QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); // Elide won't affect the size with Fit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::FixedSize); myText->setMaximumLineCount(2); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // The original text wrapped should exceed the height of the item. QVERIFY(originalWidth <= myText->width() + 2); QVERIFY(originalHeight > myText->height()); myText->setFontSizeMode(QQuickText::HorizontalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the // same size as without text wrapping. QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); // Elide won't affect the size with HorizontalFit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::VerticalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // VerticalFit should reduce the size to the wrapped text within the vertical height. verticalFitHeight = myText->contentHeight(); verticalFitWidth = myText->contentWidth(); QVERIFY(myText->contentWidth() <= myText->width() + 2); QVERIFY(verticalFitHeight <= myText->height() + 2); QVERIFY(verticalFitHeight < originalHeight); // Elide won't affect the height or width of a wrapped text with VerticalFit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::Fit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // Should be the same as VerticalFit with wrapping. QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); // Elide won't affect the size with Fit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); } void tst_qquicktext::fontSizeModeMultiline_data() { QTest::addColumn("text"); QTest::newRow("plain") << "The quick red fox jumped\n over the lazy brown dog"; QTest::newRow("styledtext") << "The quick red fox jumped
over the lazy brown dog
"; } void tst_qquicktext::fontSizeModeMultiline() { QFETCH(QString, text); QScopedPointer window(createView(testFile("fontSizeMode.qml"))); window->show(); QQuickText *myText = window->rootObject()->findChild("myText"); QVERIFY(myText != nullptr); myText->setText(text); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); qreal originalWidth = myText->contentWidth(); qreal originalHeight = myText->contentHeight(); QCOMPARE(myText->lineCount(), 2); // The original text unwrapped should exceed the width and height of the item. QVERIFY(originalWidth > myText->width()); QVERIFY(originalHeight > myText->height()); QFont font = myText->font(); font.setPixelSize(64); myText->setFont(font); myText->setFontSizeMode(QQuickText::HorizontalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // Font size reduced to fit within the width of the item. QCOMPARE(myText->lineCount(), 2); qreal horizontalFitWidth = myText->contentWidth(); qreal horizontalFitHeight = myText->contentHeight(); QVERIFY(horizontalFitWidth <= myText->width() + 2); // rounding QVERIFY(horizontalFitHeight > myText->height()); // Right eliding will remove the last line myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(myText->truncated()); QCOMPARE(myText->lineCount(), 1); QVERIFY(myText->contentWidth() <= myText->width() + 2); QVERIFY(myText->contentHeight() <= myText->height() + 2); // Left or middle eliding wont have any effect. myText->setElideMode(QQuickText::ElideLeft); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); myText->setElideMode(QQuickText::ElideMiddle); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::VerticalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // Font size reduced to fit within the height of the item. qreal verticalFitWidth = myText->contentWidth(); qreal verticalFitHeight = myText->contentHeight(); QVERIFY(verticalFitWidth <= myText->width() + 2); QVERIFY(verticalFitHeight <= myText->height() + 2); // Elide will have no effect. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QVERIFY(myText->contentWidth() <= myText->width() + 2); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideLeft); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideMiddle); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::Fit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // Should be the same as VerticalFit with no wrapping. QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); // Elide won't affect the size with Fit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideLeft); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideMiddle); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::FixedSize); myText->setWrapMode(QQuickText::Wrap); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); originalWidth = myText->contentWidth(); originalHeight = myText->contentHeight(); // The original text wrapped should exceed the height of the item. QVERIFY(originalWidth <= myText->width() + 2); QVERIFY(originalHeight > myText->height()); myText->setFontSizeMode(QQuickText::HorizontalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the // same size as without text wrapping. QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); // Text will be elided vertically with HorizontalFit myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(myText->truncated()); QVERIFY(myText->contentWidth() <= myText->width() + 2); QVERIFY(myText->contentHeight() <= myText->height() + 2); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::VerticalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // VerticalFit should reduce the size to the wrapped text within the vertical height. verticalFitHeight = myText->contentHeight(); verticalFitWidth = myText->contentWidth(); QVERIFY(myText->contentWidth() <= myText->width() + 2); QVERIFY(verticalFitHeight <= myText->height() + 2); QVERIFY(verticalFitHeight < originalHeight); // Elide won't affect the height or width of a wrapped text with VerticalFit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::Fit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // Should be the same as VerticalFit with wrapping. QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); // Elide won't affect the size with Fit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::FixedSize); myText->setMaximumLineCount(2); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // The original text wrapped should exceed the height of the item. QVERIFY(originalWidth <= myText->width() + 2); QVERIFY(originalHeight > myText->height()); myText->setFontSizeMode(QQuickText::HorizontalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the // same size as without text wrapping. QCOMPARE(myText->contentWidth(), horizontalFitWidth); QCOMPARE(myText->contentHeight(), horizontalFitHeight); // Elide won't affect the size with HorizontalFit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(myText->truncated()); QVERIFY(myText->contentWidth() <= myText->width() + 2); QVERIFY(myText->contentHeight() <= myText->height() + 2); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::VerticalFit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // VerticalFit should reduce the size to the wrapped text within the vertical height. verticalFitHeight = myText->contentHeight(); verticalFitWidth = myText->contentWidth(); QVERIFY(myText->contentWidth() <= myText->width() + 2); QVERIFY(verticalFitHeight <= myText->height() + 2); QVERIFY(verticalFitHeight < originalHeight); // Elide won't affect the height or width of a wrapped text with VerticalFit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); myText->setFontSizeMode(QQuickText::Fit); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); // Should be the same as VerticalFit with wrapping. QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); // Elide won't affect the size with Fit. myText->setElideMode(QQuickText::ElideRight); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QVERIFY(!myText->truncated()); QCOMPARE(myText->contentWidth(), verticalFitWidth); QCOMPARE(myText->contentHeight(), verticalFitHeight); myText->setElideMode(QQuickText::ElideNone); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); } void tst_qquicktext::multilengthStrings_data() { QTest::addColumn("source"); QTest::newRow("No Wrap") << testFile("multilengthStrings.qml"); QTest::newRow("Wrap") << testFile("multilengthStringsWrapped.qml"); } void tst_qquicktext::multilengthStrings() { QFETCH(QString, source); QScopedPointer window(createView(source)); window->show(); QQuickText *myText = window->rootObject()->findChild("myText"); QVERIFY(myText != nullptr); const QString longText = "the quick brown fox jumped over the lazy dog"; const QString mediumText = "the brown fox jumped over the dog"; const QString shortText = "fox jumped dog"; myText->setText(longText); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); const qreal longWidth = myText->contentWidth(); const qreal longHeight = myText->contentHeight(); myText->setText(mediumText); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); const qreal mediumWidth = myText->contentWidth(); const qreal mediumHeight = myText->contentHeight(); myText->setText(shortText); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); const qreal shortWidth = myText->contentWidth(); const qreal shortHeight = myText->contentHeight(); myText->setElideMode(QQuickText::ElideRight); myText->setText(longText + QLatin1Char('\x9c') + mediumText + QLatin1Char('\x9c') + shortText); myText->setSize(QSizeF(longWidth, longHeight)); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QCOMPARE(myText->contentWidth(), longWidth); QCOMPARE(myText->contentHeight(), longHeight); QCOMPARE(myText->truncated(), false); myText->setSize(QSizeF(mediumWidth, mediumHeight)); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QCOMPARE(myText->contentWidth(), mediumWidth); QCOMPARE(myText->contentHeight(), mediumHeight); QCOMPARE(myText->truncated(), true); myText->setSize(QSizeF(shortWidth, shortHeight)); QVERIFY(QQuickTest::qWaitForItemPolished(myText)); QCOMPARE(myText->contentWidth(), shortWidth); QCOMPARE(myText->contentHeight(), shortHeight); QCOMPARE(myText->truncated(), true); } void tst_qquicktext::fontFormatSizes_data() { QTest::addColumn("text"); QTest::addColumn("textWithTag"); QTest::addColumn("fontIsBigger"); QTest::newRow("fs1") << "Hello world!" << "Hello world!" << false; QTest::newRow("fs2") << "Hello world!" << "Hello world!" << false; QTest::newRow("fs3") << "Hello world!" << "Hello world!" << false; QTest::newRow("fs4") << "Hello world!" << "Hello world!" << true; QTest::newRow("fs5") << "Hello world!" << "Hello world!" << true; QTest::newRow("fs6") << "Hello world!" << "Hello world!" << true; QTest::newRow("fs7") << "Hello world!" << "Hello world!" << true; QTest::newRow("h1") << "This is
a font
size test." << "This is

a font

size test." << true; QTest::newRow("h2") << "This is
a font
size test." << "This is

a font

size test." << true; QTest::newRow("h3") << "This is
a font
size test." << "This is

a font

size test." << true; QTest::newRow("h4") << "This is
a font
size test." << "This is

a font

size test." << true; QTest::newRow("h5") << "This is
a font
size test." << "This is
a font
size test." << false; QTest::newRow("h6") << "This is
a font
size test." << "This is
a font
size test." << false; } void tst_qquicktext::fontFormatSizes() { QFETCH(QString, text); QFETCH(QString, textWithTag); QFETCH(bool, fontIsBigger); { QQuickView view; QVERIFY(QQuickTest::showView(view, testFileUrl("pointFontSizes.qml"))); QQuickText *qtext = view.rootObject()->findChild("text"); QQuickText *qtextWithTag = view.rootObject()->findChild("textWithTag"); QVERIFY(qtext != nullptr); QVERIFY(qtextWithTag != nullptr); qtext->setText(text); qtextWithTag->setText(textWithTag); for (int size = 6; size < 100; size += 4) { view.rootObject()->setProperty("pointSize", size); if (fontIsBigger) QVERIFY(qtext->height() <= qtextWithTag->height()); else QVERIFY(qtext->height() >= qtextWithTag->height()); } } { QQuickView view; QVERIFY(QQuickTest::showView(view, testFileUrl("pixelFontSizes.qml"))); QQuickText *qtext = view.rootObject()->findChild("text"); QQuickText *qtextWithTag = view.rootObject()->findChild("textWithTag"); QVERIFY(qtext != nullptr); QVERIFY(qtextWithTag != nullptr); qtext->setText(text); qtextWithTag->setText(textWithTag); for (int size = 6; size < 100; size += 4) { view.rootObject()->setProperty("pixelSize", size); if (fontIsBigger) QVERIFY(qtext->height() <= qtextWithTag->height()); else QVERIFY(qtext->height() >= qtextWithTag->height()); } } } typedef qreal (*ExpectedBaseline)(QQuickText *item); Q_DECLARE_METATYPE(ExpectedBaseline) static qreal expectedBaselineTop(QQuickText *item) { QFontMetricsF fm(item->font()); return fm.ascent() + item->topPadding(); } static qreal expectedBaselineBottom(QQuickText *item) { QFontMetricsF fm(item->font()); return item->height() - item->contentHeight() - item->bottomPadding() + fm.ascent(); } static qreal expectedBaselineCenter(QQuickText *item) { QFontMetricsF fm(item->font()); return ((item->height() - item->contentHeight() - item->topPadding() - item->bottomPadding()) / 2) + fm.ascent() + item->topPadding(); } static qreal expectedBaselineBold(QQuickText *item) { QFont font = item->font(); font.setBold(true); QFontMetricsF fm(font); return fm.ascent() + item->topPadding(); } static qreal expectedBaselineImage(QQuickText *item) { QFontMetricsF fm(item->font()); // The line is positioned so the bottom of the line is aligned with the bottom of the image, // or image height - line height and the baseline is line position + ascent. Because // QTextLine's height is rounded up this can give slightly different results to image height // - descent. return 181 - qCeil(fm.height()) + fm.ascent() + item->topPadding(); } static qreal expectedBaselineCustom(QQuickText *item) { QFontMetricsF fm(item->font()); return 16 + fm.ascent() + item->topPadding(); } static qreal expectedBaselineScaled(QQuickText *item) { QFont font = item->font(); QTextLayout layout(item->text().replace(QLatin1Char('\n'), QChar::LineSeparator)); qreal low = 0; qreal high = 10000; while (low < high) { layout.setFont(font); qreal width = 0; layout.beginLayout(); for (QTextLine line = layout.createLine(); line.isValid(); line = layout.createLine()) { line.setLineWidth(FLT_MAX); width = qMax(line.naturalTextWidth(), width); } layout.endLayout(); 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); } } return item->topPadding(); } static qreal expectedBaselineFixedBottom(QQuickText *item) { QFontMetricsF fm(item->font()); qreal dy = item->text().contains(QLatin1Char('\n')) ? 160 : 180; return dy + fm.ascent() - item->bottomPadding(); } static qreal expectedBaselineProportionalBottom(QQuickText *item) { QFontMetricsF fm(item->font()); qreal dy = item->text().contains(QLatin1Char('\n')) ? 200 - (qCeil(fm.height()) * 3) : 200 - (qCeil(fm.height()) * 1.5); return dy + fm.ascent() - item->bottomPadding(); } void tst_qquicktext::baselineOffset_data() { qRegisterMetaType(); QTest::addColumn("text"); QTest::addColumn("wrappedText"); QTest::addColumn("bindings"); QTest::addColumn("expectedBaseline"); QTest::addColumn("expectedBaselineEmpty"); QTest::newRow("top align") << "hello world" << "hello\nworld" << QByteArray("height: 200; verticalAlignment: Text.AlignTop") << &expectedBaselineTop << &expectedBaselineTop; QTest::newRow("bottom align") << "hello world" << "hello\nworld" << QByteArray("height: 200; verticalAlignment: Text.AlignBottom") << &expectedBaselineBottom << &expectedBaselineBottom; QTest::newRow("center align") << "hello world" << "hello\nworld" << QByteArray("height: 200; verticalAlignment: Text.AlignVCenter") << &expectedBaselineCenter << &expectedBaselineCenter; QTest::newRow("bold") << "hello world" << "hello
world
" << QByteArray("height: 200") << &expectedBaselineBold << &expectedBaselineTop; QTest::newRow("richText") << "hello world" << "hello
world
" << QByteArray("height: 200; textFormat: Text.RichText") << &expectedBaselineTop << &expectedBaselineTop; QTest::newRow("elided") << "hello world" << "hello\nworld" << QByteArray("width: 20; height: 8; elide: Text.ElideRight") << &expectedBaselineTop << &expectedBaselineTop; QTest::newRow("elided bottom align") << "hello world" << "hello\nworld!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << QByteArray("width: 200; height: 200; elide: Text.ElideRight; verticalAlignment: Text.AlignBottom") << &expectedBaselineBottom << &expectedBaselineBottom; QTest::newRow("image") << "hello world" << "hello
world" << QByteArray("height: 200\n; baseUrl: \"") + testFileUrl("reference").toEncoded() + QByteArray("\"") << &expectedBaselineImage << &expectedBaselineTop; QTest::newRow("customLine") << "hello world" << "hello\nworld" << QByteArray("height: 200; onLineLaidOut: line.y += 16") << &expectedBaselineCustom << &expectedBaselineCustom; QTest::newRow("scaled font") << "hello world" << "hello\nworld" << QByteArray("width: 200; minimumPointSize: 1; font.pointSize: 64; fontSizeMode: Text.HorizontalFit") << &expectedBaselineScaled << &expectedBaselineTop; QTest::newRow("fixed line height top align") << "hello world" << "hello\nworld" << QByteArray("height: 200; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignTop") << &expectedBaselineTop << &expectedBaselineTop; QTest::newRow("fixed line height bottom align") << "hello world" << "hello\nworld" << QByteArray("height: 200; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignBottom") << &expectedBaselineFixedBottom << &expectedBaselineFixedBottom; QTest::newRow("proportional line height top align") << "hello world" << "hello\nworld" << QByteArray("height: 200; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignTop") << &expectedBaselineTop << &expectedBaselineTop; QTest::newRow("proportional line height bottom align") << "hello world" << "hello\nworld" << QByteArray("height: 200; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignBottom") << &expectedBaselineProportionalBottom << &expectedBaselineProportionalBottom; QTest::newRow("top align with padding") << "hello world" << "hello\nworld" << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; verticalAlignment: Text.AlignTop") << &expectedBaselineTop << &expectedBaselineTop; QTest::newRow("bottom align with padding") << "hello world" << "hello\nworld" << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; verticalAlignment: Text.AlignBottom") << &expectedBaselineBottom << &expectedBaselineBottom; QTest::newRow("center align with padding") << "hello world" << "hello\nworld" << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; verticalAlignment: Text.AlignVCenter") << &expectedBaselineCenter << &expectedBaselineCenter; QTest::newRow("bold width padding") << "hello world" << "hello
world
" << QByteArray("height: 200; topPadding: 10; bottomPadding: 20") << &expectedBaselineBold << &expectedBaselineTop; QTest::newRow("richText with padding") << "hello world" << "hello
world
" << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; textFormat: Text.RichText") << &expectedBaselineTop << &expectedBaselineTop; QTest::newRow("elided with padding") << "hello world" << "hello\nworld" << QByteArray("width: 20; height: 8; topPadding: 10; bottomPadding: 20; elide: Text.ElideRight") << &expectedBaselineTop << &expectedBaselineTop; QTest::newRow("elided bottom align with padding") << "hello world" << "hello\nworld!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << QByteArray("width: 200; height: 200; topPadding: 10; bottomPadding: 20; elide: Text.ElideRight; verticalAlignment: Text.AlignBottom") << &expectedBaselineBottom << &expectedBaselineBottom; QTest::newRow("image with padding") << "hello world" << "hello
world" << QByteArray("height: 200\n; topPadding: 10; bottomPadding: 20; baseUrl: \"") + testFileUrl("reference").toEncoded() + QByteArray("\"") << &expectedBaselineImage << &expectedBaselineTop; QTest::newRow("customLine with padding") << "hello world" << "hello\nworld" << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; onLineLaidOut: line.y += 16") << &expectedBaselineCustom << &expectedBaselineCustom; QTest::newRow("scaled font with padding") << "hello world" << "hello\nworld" << QByteArray("width: 200; topPadding: 10; bottomPadding: 20; minimumPointSize: 1; font.pointSize: 64; fontSizeMode: Text.HorizontalFit") << &expectedBaselineScaled << &expectedBaselineTop; QTest::newRow("fixed line height top align with padding") << "hello world" << "hello\nworld" << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignTop") << &expectedBaselineTop << &expectedBaselineTop; QTest::newRow("fixed line height bottom align with padding") << "hello world" << "hello\nworld" << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignBottom") << &expectedBaselineFixedBottom << &expectedBaselineFixedBottom; QTest::newRow("proportional line height top align with padding") << "hello world" << "hello\nworld" << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignTop") << &expectedBaselineTop << &expectedBaselineTop; QTest::newRow("proportional line height bottom align with padding") << "hello world" << "hello\nworld" << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignBottom") << &expectedBaselineProportionalBottom << &expectedBaselineProportionalBottom; } void tst_qquicktext::baselineOffset() { QFETCH(QString, text); QFETCH(QString, wrappedText); QFETCH(QByteArray, bindings); QFETCH(ExpectedBaseline, expectedBaseline); QFETCH(ExpectedBaseline, expectedBaselineEmpty); QQmlComponent component(&engine); component.setData( "import QtQuick 2.6\n" "Text {\n" + bindings + "\n" "}", QUrl()); QScopedPointer object(component.create()); QQuickText *item = qobject_cast(object.data()); QVERIFY(item); { qreal baseline = expectedBaselineEmpty(item); QCOMPARE(item->baselineOffset(), baseline); item->setText(text); if (expectedBaseline != expectedBaselineEmpty) baseline = expectedBaseline(item); QCOMPARE(item->baselineOffset(), baseline); item->setText(wrappedText); QCOMPARE(item->baselineOffset(), expectedBaseline(item)); } QFont font = item->font(); font.setPointSize(font.pointSize() + 8); { QCOMPARE(item->baselineOffset(), expectedBaseline(item)); item->setText(text); qreal baseline = expectedBaseline(item); QCOMPARE(item->baselineOffset(), baseline); item->setText(QString()); if (expectedBaselineEmpty != expectedBaseline) baseline = expectedBaselineEmpty(item); QCOMPARE(item->baselineOffset(), baseline); } } void tst_qquicktext::htmlLists() { QFETCH(QString, text); QFETCH(int, nbLines); QQuickView *view = createView(testFile("htmlLists.qml")); QQuickText *textObject = view->rootObject()->findChild("myText"); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(textObject); QVERIFY(textPrivate != nullptr); QVERIFY(textPrivate->extra.isAllocated()); QVERIFY(textObject != nullptr); textObject->setText(text); view->show(); view->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(view)); QCOMPARE(textPrivate->extra->doc->lineCount(), nbLines); delete view; } void tst_qquicktext::htmlLists_data() { QTest::addColumn("text"); QTest::addColumn("nbLines"); QTest::newRow("ordered list") << "
  1. one
  2. two
  3. three" << 3; QTest::newRow("ordered list closed") << "
    1. one
    " << 1; QTest::newRow("ordered list alpha") << "
    1. one
    2. two
    " << 2; QTest::newRow("ordered list upper alpha") << "
    1. one
    2. two
    " << 2; QTest::newRow("ordered list roman") << "
    1. one
    2. two
    " << 2; QTest::newRow("ordered list upper roman") << "
    1. one
    2. two
    " << 2; QTest::newRow("ordered list bad") << "
    1. one
    2. two
    " << 2; QTest::newRow("unordered list") << "
    • one
    • two" << 2; QTest::newRow("unordered list closed") << "
      • one
      • two
      " << 2; QTest::newRow("unordered list disc") << "
      • one
      • two
      " << 2; QTest::newRow("unordered list square") << "
      • one
      • two
      " << 2; QTest::newRow("unordered list bad") << "
      • one
      • two
      " << 2; } void tst_qquicktext::elideBeforeMaximumLineCount() { // QTBUG-31471 QQmlComponent component(&engine, testFile("elideBeforeMaximumLineCount.qml")); QScopedPointer object(component.create()); QQuickText *item = qobject_cast(object.data()); QVERIFY(item); QCOMPARE(item->lineCount(), 2); } void tst_qquicktext::hover() { // QTBUG-33842 QQmlComponent component(&engine, testFile("hover.qml")); QScopedPointer object(component.create()); QQuickWindow *window = qobject_cast(object.data()); QVERIFY(window); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickMouseArea *mouseArea = window->property("mouseArea").value(); QVERIFY(mouseArea); QQuickText *textItem = window->property("textItem").value(); QVERIFY(textItem); QVERIFY(!mouseArea->property("wasHovered").toBool()); QPoint center(window->width() / 2, window->height() / 2); QPoint delta(window->width() / 10, window->height() / 10); QTest::mouseMove(window, center - delta); QTest::mouseMove(window, center + delta); QVERIFY(mouseArea->property("wasHovered").toBool()); } void tst_qquicktext::growFromZeroWidth() { QQmlComponent component(&engine, testFile("growFromZeroWidth.qml")); QScopedPointer object(component.create()); QQuickText *text = qobject_cast(object.data()); QVERIFY(text); QCOMPARE(text->lineCount(), 3); text->setWidth(80); // the new width should force our contents to wrap QVERIFY(text->lineCount() > 3); } void tst_qquicktext::padding() { QScopedPointer window(new QQuickView); window->setSource(testFileUrl("padding.qml")); QTRY_COMPARE(window->status(), QQuickView::Ready); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickItem *root = window->rootObject(); QVERIFY(root); QQuickText *obj = qobject_cast(root); QVERIFY(obj != nullptr); qreal cw = obj->contentWidth(); qreal ch = obj->contentHeight(); QVERIFY(cw > 0); QVERIFY(ch > 0); QCOMPARE(obj->topPadding(), 20.0); QCOMPARE(obj->leftPadding(), 30.0); QCOMPARE(obj->rightPadding(), 40.0); QCOMPARE(obj->bottomPadding(), 50.0); QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding()); QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding()); obj->setTopPadding(2.25); QCOMPARE(obj->topPadding(), 2.25); QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding()); obj->setLeftPadding(3.75); QCOMPARE(obj->leftPadding(), 3.75); QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding()); obj->setRightPadding(4.4); QCOMPARE(obj->rightPadding(), 4.4); QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding()); obj->setBottomPadding(1.11); QCOMPARE(obj->bottomPadding(), 1.11); QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding()); obj->setWidth(cw / 2); obj->setElideMode(QQuickText::ElideRight); QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding()); QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding()); obj->setLeftPadding(0); QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding()); obj->setWidth(cw); obj->setRightPadding(cw); QCOMPARE(obj->contentWidth(), 0); for (int incr = 1; incr < 50 && qFuzzyIsNull(obj->contentWidth()); ++incr) obj->setWidth(cw + incr); QVERIFY(obj->contentWidth() > 0); qCDebug(lcTests) << "increasing Text width from" << cw << "to" << obj->width() << "rendered a character: contentWidth now" << obj->contentWidth(); obj->setElideMode(QQuickText::ElideNone); obj->resetWidth(); obj->setWrapMode(QQuickText::WordWrap); QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding()); QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding()); obj->setWrapMode(QQuickText::NoWrap); obj->setText("Qt"); QVERIFY(obj->contentWidth() < cw); QCOMPARE(obj->contentHeight(), ch); cw = obj->contentWidth(); QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding()); QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding()); obj->setFont(QFont("Courier", 96)); QVERIFY(obj->contentWidth() > cw); QVERIFY(obj->contentHeight() > ch); cw = obj->contentWidth(); ch = obj->contentHeight(); QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding()); QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding()); obj->resetTopPadding(); QCOMPARE(obj->topPadding(), 10.0); obj->resetLeftPadding(); QCOMPARE(obj->leftPadding(), 10.0); obj->resetRightPadding(); QCOMPARE(obj->rightPadding(), 10.0); obj->resetBottomPadding(); QCOMPARE(obj->bottomPadding(), 10.0); obj->resetPadding(); QCOMPARE(obj->padding(), 0.0); QCOMPARE(obj->topPadding(), 0.0); QCOMPARE(obj->leftPadding(), 0.0); QCOMPARE(obj->rightPadding(), 0.0); QCOMPARE(obj->bottomPadding(), 0.0); delete root; } void tst_qquicktext::paddingInLoader() // QTBUG-83413 { QQuickView view; QVERIFY(QQuickTest::showView(view, testFileUrl("paddingInLoader.qml"))); QQuickText *qtext = view.rootObject()->findChild(); QVERIFY(qtext); QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(qtext); QVERIFY(textPrivate); QCOMPARE(qtext->contentWidth(), 0); // does not render text, because width == rightPadding QCOMPARE(textPrivate->availableWidth(), 0); qtext->setLeftPadding(qtext->width()); qtext->setRightPadding(0); QCOMPARE(qtext->contentWidth(), 0); // does not render text, because width == leftPadding QCOMPARE(textPrivate->availableWidth(), 0); qtext->setRightPadding(qtext->width()); QCOMPARE(qtext->contentWidth(), 0); // does not render text: available space is negative QCOMPARE(textPrivate->availableWidth(), -qtext->width()); qtext->setLeftPadding(2); qtext->setRightPadding(2); QVERIFY(qtext->contentWidth() > 0); // finally space is available to render text QCOMPARE(textPrivate->availableWidth(), qtext->width() - 4); } void tst_qquicktext::hintingPreference() { { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE((int)textObject->font().hintingPreference(), (int)QFont::PreferDefaultHinting); delete textObject; } { QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.hintingPreference: Font.PreferNoHinting }"; QQmlComponent textComponent(&engine); textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile("")); QQuickText *textObject = qobject_cast(textComponent.create()); QVERIFY(textObject != nullptr); QCOMPARE((int)textObject->font().hintingPreference(), (int)QFont::PreferNoHinting); delete textObject; } } void tst_qquicktext::zeroWidthAndElidedDoesntRender() { // Tests QTBUG-34990 QQmlComponent component(&engine, testFile("ellipsisText.qml")); QScopedPointer object(component.create()); QQuickText *text = qobject_cast(object.data()); QVERIFY(text); QCOMPARE(text->contentWidth(), 0.0); QQuickText *reference = text->findChild("elidedRef"); QVERIFY(reference); text->setWidth(10); QCOMPARE(text->contentWidth(), reference->contentWidth()); } void tst_qquicktext::hAlignWidthDependsOnImplicitWidth_data() { QTest::addColumn("horizontalAlignment"); QTest::addColumn("elide"); QTest::addColumn("extraWidth"); QTest::newRow("AlignHCenter, ElideNone, 0 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideNone << 0; QTest::newRow("AlignRight, ElideNone, 0 extraWidth") << QQuickText::AlignRight << QQuickText::ElideNone << 0; QTest::newRow("AlignHCenter, ElideRight, 0 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideRight << 0; QTest::newRow("AlignRight, ElideRight, 0 extraWidth") << QQuickText::AlignRight << QQuickText::ElideRight << 0; QTest::newRow("AlignHCenter, ElideNone, 20 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideNone << 20; QTest::newRow("AlignRight, ElideNone, 20 extraWidth") << QQuickText::AlignRight << QQuickText::ElideNone << 20; QTest::newRow("AlignHCenter, ElideRight, 20 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideRight << 20; QTest::newRow("AlignRight, ElideRight, 20 extraWidth") << QQuickText::AlignRight << QQuickText::ElideRight << 20; } void tst_qquicktext::hAlignWidthDependsOnImplicitWidth() { QFETCH(QQuickText::HAlignment, horizontalAlignment); QFETCH(QQuickText::TextElideMode, elide); QFETCH(int, extraWidth); QScopedPointer window(new QQuickView); window->setSource(testFileUrl("hAlignWidthDependsOnImplicitWidth.qml")); QTRY_COMPARE(window->status(), QQuickView::Ready); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickItem *rect = window->rootObject(); QVERIFY(rect); QVERIFY(rect->setProperty("horizontalAlignment", horizontalAlignment)); QVERIFY(rect->setProperty("elide", elide)); QVERIFY(rect->setProperty("extraWidth", extraWidth)); QImage image = window->grabWindow(); const int rectX = 100 * window->screen()->devicePixelRatio(); QCOMPARE(numberOfNonWhitePixels(0, rectX - 1, image), 0); QVERIFY(rect->setProperty("text", "this is mis-aligned")); image = window->grabWindow(); QCOMPARE(numberOfNonWhitePixels(0, rectX - 1, image), 0); } void tst_qquicktext::fontInfo() { QQmlComponent component(&engine, testFile("fontInfo.qml")); QScopedPointer object(component.create()); QObject *root = object.data(); QQuickText *main = root->findChild("main"); QVERIFY(main); QCOMPARE(main->font().pixelSize(), 1000); QQuickText *copy = root->findChild("copy"); QVERIFY(copy); QCOMPARE(copy->font().family(), QFontInfo(QFont()).family()); QVERIFY(copy->font().pixelSize() < 1000); } void tst_qquicktext::initialContentHeight() { QQmlComponent component(&engine, testFile("contentHeight.qml")); QVERIFY(component.isReady()); QScopedPointer object(component.create()); QObject *root = object.data(); QVERIFY(root); QQuickText *text = qobject_cast(root); QVERIFY(text); QCOMPARE(text->height(), text->contentHeight()); } void tst_qquicktext::implicitSizeChangeRewrap() { QScopedPointer window(new QQuickView); window->setSource(testFileUrl("implicitSizeChangeRewrap.qml")); QTRY_COMPARE(window->status(), QQuickView::Ready); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QObject *root = window->rootObject(); QQuickText *text = root->findChild("text"); QVERIFY(text != nullptr); QVERIFY(text->contentWidth() < window->width()); } void tst_qquicktext::verticallyAlignedImageInTable() { QScopedPointer window(new QQuickView); window->setSource(testFileUrl("verticallyAlignedImageInTable.qml")); QTRY_COMPARE(window->status(), QQuickView::Ready); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); // Don't crash } void tst_qquicktext::transparentBackground() { if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Skipping due to grabToImage not functional on offscreen/minimal platforms"); QScopedPointer window(new QQuickView); window->setSource(testFileUrl("transparentBackground.qml")); QTRY_COMPARE(window->status(), QQuickView::Ready); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QImage img = window->grabWindow(); QCOMPARE(img.isNull(), false); QColor color = img.pixelColor(0, 0); QCOMPARE(color.red(), 255); QCOMPARE(color.blue(), 255); QCOMPARE(color.green(), 255); } void tst_qquicktext::displaySuperscriptedTag() { if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Skipping due to grabToImage not functional on offscreen/minimal platforms"); QScopedPointer window(new QQuickView); window->setSource(testFileUrl("displaySuperscriptedTag.qml")); QTRY_COMPARE(window->status(), QQuickView::Ready); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QQuickText *text = window->findChild("text"); QVERIFY(text); QImage img = window->grabWindow(); QCOMPARE(img.isNull(), false); QColor color = img.pixelColor(1, static_cast(text->contentHeight()) / 4 * 3); QCOMPARE(color.red(), 255); QCOMPARE(color.blue(), 255); QCOMPARE(color.green(), 255); } QTEST_MAIN(tst_qquicktext) #include "tst_qquicktext.moc"