1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28#include <qtest.h>
29#include <QtTest/QSignalSpy>
30#include <QTextDocument>
31#include <QtQml/qqmlengine.h>
32#include <QtQml/qqmlcomponent.h>
33#include <QtQml/qjsvalue.h>
34#include <QtQuick/private/qquicktext_p.h>
35#include <QtQuick/private/qquickmousearea_p.h>
36#include <QtQuickTest/QtQuickTest>
37#include <private/qquicktext_p_p.h>
38#include <private/qquicktextdocument_p.h>
39#include <private/qquickvaluetypes_p.h>
40#include <QFontMetrics>
41#include <qmath.h>
42#include <QtQuick/QQuickView>
43#include <QtQuick/qquickitemgrabresult.h>
44#include <private/qguiapplication_p.h>
45#include <limits.h>
46#include <QtGui/QMouseEvent>
47#include "../../shared/util.h"
48#include "testhttpserver.h"
49
50DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
51
52Q_DECLARE_METATYPE(QQuickText::TextFormat)
53
54QT_BEGIN_NAMESPACE
55extern void qt_setQtEnableTestFont(bool value);
56QT_END_NAMESPACE
57
58class tst_qquicktext : public QQmlDataTest
59{
60 Q_OBJECT
61public:
62 tst_qquicktext();
63
64private slots:
65 void cleanup();
66 void text();
67 void width();
68 void wrap();
69 void elide();
70 void elideParentChanged();
71 void elideRelayoutAfterZeroWidth();
72 void multilineElide_data();
73 void multilineElide();
74 void implicitElide_data();
75 void implicitElide();
76 void textFormat();
77
78 void baseUrl();
79 void embeddedImages_data();
80 void embeddedImages();
81
82 void lineCount();
83 void lineHeight();
84
85 // ### these tests may be trivial
86 void horizontalAlignment();
87 void horizontalAlignment_RightToLeft();
88 void verticalAlignment();
89 void hAlignImplicitWidth();
90 void font();
91 void style();
92 void color();
93 void smooth();
94 void renderType();
95 void antialiasing();
96
97 // QQuickFontValueType
98 void weight();
99 void underline();
100 void overline();
101 void strikeout();
102 void capitalization();
103 void letterSpacing();
104 void wordSpacing();
105
106 void linkInteraction_data();
107 void linkInteraction();
108
109 void implicitSize_data();
110 void implicitSize();
111 void implicitSizeChangeRewrap();
112 void dependentImplicitSizes();
113 void contentSize();
114 void implicitSizeBinding_data();
115 void implicitSizeBinding();
116 void geometryChanged();
117
118 void boundingRect_data();
119 void boundingRect();
120 void clipRect();
121 void lineLaidOut();
122 void lineLaidOutRelayout();
123 void lineLaidOutHAlign();
124 void lineLaidOutImplicitWidth();
125
126 void imgTagsBaseUrl_data();
127 void imgTagsBaseUrl();
128 void imgTagsAlign_data();
129 void imgTagsAlign();
130 void imgTagsMultipleImages();
131 void imgTagsElide();
132 void imgTagsUpdates();
133 void imgTagsError();
134 void fontSizeMode_data();
135 void fontSizeMode();
136 void fontSizeModeMultiline_data();
137 void fontSizeModeMultiline();
138 void multilengthStrings_data();
139 void multilengthStrings();
140 void fontFormatSizes_data();
141 void fontFormatSizes();
142
143 void baselineOffset_data();
144 void baselineOffset();
145
146 void htmlLists();
147 void htmlLists_data();
148
149 void elideBeforeMaximumLineCount();
150
151 void hover();
152
153 void growFromZeroWidth();
154
155 void padding();
156
157 void hintingPreference();
158
159 void zeroWidthAndElidedDoesntRender();
160
161 void hAlignWidthDependsOnImplicitWidth_data();
162 void hAlignWidthDependsOnImplicitWidth();
163
164 void fontInfo();
165
166 void initialContentHeight();
167
168 void verticallyAlignedImageInTable();
169
170 void transparentBackground();
171
172 void displaySuperscriptedTag();
173
174private:
175 QStringList standard;
176 QStringList richText;
177
178 QStringList horizontalAlignmentmentStrings;
179 QStringList verticalAlignmentmentStrings;
180
181 QList<Qt::Alignment> verticalAlignmentments;
182 QList<Qt::Alignment> horizontalAlignmentments;
183
184 QStringList styleStrings;
185 QList<QQuickText::TextStyle> styles;
186
187 QStringList colorStrings;
188
189 QQmlEngine engine;
190
191 QQuickView *createView(const QString &filename);
192 int numberOfNonWhitePixels(int fromX, int toX, const QImage &image);
193};
194
195void tst_qquicktext::cleanup()
196{
197 QVERIFY(QGuiApplication::topLevelWindows().isEmpty());
198}
199
200tst_qquicktext::tst_qquicktext()
201{
202 standard << "the quick brown fox jumped over the lazy dog"
203 << "the quick brown fox\n jumped over the lazy dog";
204
205 richText << "<i>the <b>quick</b> brown <a href=\\\"http://www.google.com\\\">fox</a> jumped over the <b>lazy</b> dog</i>"
206 << "<i>the <b>quick</b> brown <a href=\\\"http://www.google.com\\\">fox</a><br>jumped over the <b>lazy</b> dog</i>";
207
208 horizontalAlignmentmentStrings << "AlignLeft"
209 << "AlignRight"
210 << "AlignHCenter";
211
212 verticalAlignmentmentStrings << "AlignTop"
213 << "AlignBottom"
214 << "AlignVCenter";
215
216 horizontalAlignmentments << Qt::AlignLeft
217 << Qt::AlignRight
218 << Qt::AlignHCenter;
219
220 verticalAlignmentments << Qt::AlignTop
221 << Qt::AlignBottom
222 << Qt::AlignVCenter;
223
224 styleStrings << "Normal"
225 << "Outline"
226 << "Raised"
227 << "Sunken";
228
229 styles << QQuickText::Normal
230 << QQuickText::Outline
231 << QQuickText::Raised
232 << QQuickText::Sunken;
233
234 colorStrings << "aliceblue"
235 << "antiquewhite"
236 << "aqua"
237 << "darkkhaki"
238 << "darkolivegreen"
239 << "dimgray"
240 << "palevioletred"
241 << "lightsteelblue"
242 << "#000000"
243 << "#AAAAAA"
244 << "#FFFFFF"
245 << "#2AC05F";
246 //
247 // need a different test to do alpha channel test
248 // << "#AA0011DD"
249 // << "#00F16B11";
250 //
251 qt_setQtEnableTestFont(value: true);
252}
253
254QQuickView *tst_qquicktext::createView(const QString &filename)
255{
256 QQuickView *window = new QQuickView(nullptr);
257
258 window->setSource(QUrl::fromLocalFile(localfile: filename));
259 return window;
260}
261
262void tst_qquicktext::text()
263{
264 {
265 QQmlComponent textComponent(&engine);
266 textComponent.setData("import QtQuick 2.0\nText { text: \"\" }", baseUrl: QUrl::fromLocalFile(localfile: ""));
267 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
268
269 QVERIFY(textObject != nullptr);
270 QCOMPARE(textObject->text(), QString(""));
271 QCOMPARE(textObject->width(), qreal(0));
272
273 delete textObject;
274 }
275
276 for (int i = 0; i < standard.size(); i++)
277 {
278 QString componentStr = "import QtQuick 2.0\nText { text: \"" + standard.at(i) + "\" }";
279 QQmlComponent textComponent(&engine);
280 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
281
282 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
283
284 QVERIFY(textObject != nullptr);
285 QCOMPARE(textObject->text(), standard.at(i));
286 QVERIFY(textObject->width() > 0);
287
288 delete textObject;
289 }
290
291 for (int i = 0; i < richText.size(); i++)
292 {
293 QString componentStr = "import QtQuick 2.0\nText { text: \"" + richText.at(i) + "\" }";
294 QQmlComponent textComponent(&engine);
295 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
296 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
297
298 QVERIFY(textObject != nullptr);
299 QString expected = richText.at(i);
300 QCOMPARE(textObject->text(), expected.replace("\\\"", "\""));
301 QVERIFY(textObject->width() > 0);
302
303 delete textObject;
304 }
305}
306
307void tst_qquicktext::width()
308{
309 // uses Font metrics to find the width for standard and document to find the width for rich
310 {
311 QQmlComponent textComponent(&engine);
312 textComponent.setData("import QtQuick 2.0\nText { text: \"\" }", baseUrl: QUrl::fromLocalFile(localfile: ""));
313 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
314
315 QVERIFY(textObject != nullptr);
316 QCOMPARE(textObject->width(), 0.);
317
318 delete textObject;
319 }
320
321 bool requiresUnhintedMetrics = !qmlDisableDistanceField();
322
323 for (int i = 0; i < standard.size(); i++)
324 {
325 QVERIFY(!Qt::mightBeRichText(standard.at(i))); // self-test
326
327 QFont f;
328 qreal metricWidth = 0.0;
329
330 if (requiresUnhintedMetrics) {
331 QString s = standard.at(i);
332 s.replace(before: QLatin1Char('\n'), after: QChar::LineSeparator);
333
334 QTextLayout layout(s);
335 layout.setFlags(Qt::TextExpandTabs | Qt::TextShowMnemonic);
336 {
337 QTextOption option;
338 option.setUseDesignMetrics(true);
339 layout.setTextOption(option);
340 }
341
342 layout.beginLayout();
343 forever {
344 QTextLine line = layout.createLine();
345 if (!line.isValid())
346 break;
347 }
348
349 layout.endLayout();
350
351 metricWidth = layout.boundingRect().width();
352 } else {
353 QFontMetricsF fm(f);
354 metricWidth = fm.size(flags: Qt::TextExpandTabs | Qt::TextShowMnemonic, str: standard.at(i)).width();
355 }
356
357 QString componentStr = "import QtQuick 2.0\nText { text: \"" + standard.at(i) + "\" }";
358 QQmlComponent textComponent(&engine);
359 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
360 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
361
362 QVERIFY(textObject != nullptr);
363 QVERIFY(textObject->boundingRect().width() > 0);
364 QCOMPARE(textObject->width(), qreal(metricWidth));
365 QVERIFY(textObject->textFormat() == QQuickText::AutoText); // setting text doesn't change format
366
367 delete textObject;
368 }
369
370 for (int i = 0; i < richText.size(); i++)
371 {
372 QVERIFY(Qt::mightBeRichText(richText.at(i))); // self-test
373
374 QString componentStr = "import QtQuick 2.0\nText { text: \"" + richText.at(i) + "\"; textFormat: Text.RichText }";
375 QQmlComponent textComponent(&engine);
376 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
377 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
378 QVERIFY(textObject != nullptr);
379
380 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: textObject);
381 QVERIFY(textPrivate != nullptr);
382 QVERIFY(textPrivate->extra.isAllocated());
383
384 QTextDocument *doc = textPrivate->extra->doc;
385 QVERIFY(doc != nullptr);
386
387 QCOMPARE(int(textObject->width()), int(doc->idealWidth()));
388 QCOMPARE(textObject->textFormat(), QQuickText::RichText);
389
390 delete textObject;
391 }
392}
393
394void tst_qquicktext::wrap()
395{
396 int textHeight = 0;
397 // for specified width and wrap set true
398 {
399 QQmlComponent textComponent(&engine);
400 textComponent.setData("import QtQuick 2.0\nText { text: \"Hello\"; wrapMode: Text.WordWrap; width: 300 }", baseUrl: QUrl::fromLocalFile(localfile: ""));
401 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
402 textHeight = textObject->height();
403
404 QVERIFY(textObject != nullptr);
405 QCOMPARE(textObject->wrapMode(), QQuickText::WordWrap);
406 QCOMPARE(textObject->width(), 300.);
407
408 delete textObject;
409 }
410
411 for (int i = 0; i < standard.size(); i++)
412 {
413 QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; width: 30; text: \"" + standard.at(i) + "\" }";
414 QQmlComponent textComponent(&engine);
415 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
416 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
417
418 QVERIFY(textObject != nullptr);
419 QCOMPARE(textObject->width(), 30.);
420 QVERIFY(textObject->height() > textHeight);
421
422 int oldHeight = textObject->height();
423 textObject->setWidth(100);
424 QVERIFY(textObject->height() < oldHeight);
425
426 delete textObject;
427 }
428
429 for (int i = 0; i < richText.size(); i++)
430 {
431 QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; width: 30; text: \"" + richText.at(i) + "\" }";
432 QQmlComponent textComponent(&engine);
433 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
434 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
435
436 QVERIFY(textObject != nullptr);
437 QCOMPARE(textObject->width(), 30.);
438 QVERIFY(textObject->height() > textHeight);
439
440 qreal oldHeight = textObject->height();
441 textObject->setWidth(100);
442 QVERIFY(textObject->height() < oldHeight);
443
444 delete textObject;
445 }
446
447 // Check that increasing width from idealWidth will cause a relayout
448 for (int i = 0; i < richText.size(); i++)
449 {
450 QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; textFormat: Text.RichText; width: 30; text: \"" + richText.at(i) + "\" }";
451 QQmlComponent textComponent(&engine);
452 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
453 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
454
455 QVERIFY(textObject != nullptr);
456 QCOMPARE(textObject->width(), 30.);
457 QVERIFY(textObject->height() > textHeight);
458
459 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: textObject);
460 QVERIFY(textPrivate != nullptr);
461 QVERIFY(textPrivate->extra.isAllocated());
462
463 QTextDocument *doc = textPrivate->extra->doc;
464 QVERIFY(doc != nullptr);
465 textObject->setWidth(doc->idealWidth());
466 QCOMPARE(textObject->width(), doc->idealWidth());
467 QVERIFY(textObject->height() > textHeight);
468
469 qreal oldHeight = textObject->height();
470 textObject->setWidth(100);
471 QVERIFY(textObject->height() < oldHeight);
472
473 delete textObject;
474 }
475
476 // richtext again with a fixed height
477 for (int i = 0; i < richText.size(); i++)
478 {
479 QString componentStr = "import QtQuick 2.0\nText { wrapMode: Text.WordWrap; width: 30; height: 50; text: \"" + richText.at(i) + "\" }";
480 QQmlComponent textComponent(&engine);
481 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
482 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
483
484 QVERIFY(textObject != nullptr);
485 QCOMPARE(textObject->width(), 30.);
486 QVERIFY(textObject->implicitHeight() > textHeight);
487
488 qreal oldHeight = textObject->implicitHeight();
489 textObject->setWidth(100);
490 QVERIFY(textObject->implicitHeight() < oldHeight);
491
492 delete textObject;
493 }
494
495 {
496 QQmlComponent component(&engine);
497 component.setData("import QtQuick 2.0\n Text {}", baseUrl: QUrl());
498 QScopedPointer<QObject> object(component.create());
499 QQuickText *textObject = qobject_cast<QQuickText *>(object: object.data());
500 QVERIFY(textObject);
501
502 QSignalSpy spy(textObject, SIGNAL(wrapModeChanged()));
503
504 QCOMPARE(textObject->wrapMode(), QQuickText::NoWrap);
505
506 textObject->setWrapMode(QQuickText::Wrap);
507 QCOMPARE(textObject->wrapMode(), QQuickText::Wrap);
508 QCOMPARE(spy.count(), 1);
509
510 textObject->setWrapMode(QQuickText::Wrap);
511 QCOMPARE(spy.count(), 1);
512
513 textObject->setWrapMode(QQuickText::NoWrap);
514 QCOMPARE(textObject->wrapMode(), QQuickText::NoWrap);
515 QCOMPARE(spy.count(), 2);
516 }
517}
518
519void tst_qquicktext::elide()
520{
521 for (QQuickText::TextElideMode m = QQuickText::ElideLeft; m<=QQuickText::ElideNone; m=QQuickText::TextElideMode(int(m)+1)) {
522 const char* elidename[]={"ElideLeft", "ElideRight", "ElideMiddle", "ElideNone"};
523 QString elide = "elide: Text." + QString(elidename[int(m)]) + ";";
524
525 // XXX Poor coverage.
526
527 {
528 QQmlComponent textComponent(&engine);
529 textComponent.setData(("import QtQuick 2.0\nText { text: \"\"; "+elide+" width: 100 }").toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
530 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
531
532 QCOMPARE(textObject->elideMode(), m);
533 QCOMPARE(textObject->width(), 100.);
534
535 delete textObject;
536 }
537
538 for (int i = 0; i < standard.size(); i++)
539 {
540 QString componentStr = "import QtQuick 2.0\nText { "+elide+" width: 100; text: \"" + standard.at(i) + "\" }";
541 QQmlComponent textComponent(&engine);
542 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
543 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
544
545 QCOMPARE(textObject->elideMode(), m);
546 QCOMPARE(textObject->width(), 100.);
547
548 if (m != QQuickText::ElideNone && !standard.at(i).contains(c: '\n'))
549 QVERIFY(textObject->contentWidth() <= textObject->width());
550
551 delete textObject;
552 }
553
554 for (int i = 0; i < richText.size(); i++)
555 {
556 QString componentStr = "import QtQuick 2.0\nText { "+elide+" width: 100; text: \"" + richText.at(i) + "\" }";
557 QQmlComponent textComponent(&engine);
558 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
559 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
560
561 QCOMPARE(textObject->elideMode(), m);
562 QCOMPARE(textObject->width(), 100.);
563
564 if (m != QQuickText::ElideNone && standard.at(i).contains(s: "<br>"))
565 QVERIFY(textObject->contentWidth() <= textObject->width());
566
567 delete textObject;
568 }
569 }
570}
571
572// QTBUG-60328
573// Tests that text with elide set is rendered after
574// having its parent cleared and then set again.
575void tst_qquicktext::elideParentChanged()
576{
577 QQuickView window;
578 window.setSource(testFileUrl(fileName: "elideParentChanged.qml"));
579 QTRY_COMPARE(window.status(), QQuickView::Ready);
580
581 window.show();
582 QVERIFY(QTest::qWaitForWindowExposed(&window));
583
584 QQuickItem *root = window.rootObject();
585 QVERIFY(root);
586 QCOMPARE(root->childItems().size(), 1);
587
588 // Store a snapshot of the scene so that we can compare it later.
589 QSharedPointer<QQuickItemGrabResult> grabResult = root->grabToImage();
590 QTRY_VERIFY(!grabResult->image().isNull());
591 const QImage expectedItemImageGrab(grabResult->image());
592
593 // Clear the text's parent. It shouldn't render anything.
594 QQuickItem *text = root->childItems().first();
595 text->setParentItem(nullptr);
596 QCOMPARE(text->width(), 0.0);
597 QCOMPARE(text->height(), 0.0);
598
599 // Set the parent back to what it was. The text should
600 // be rendered identically to how it was before.
601 text->setParentItem(root);
602 QCOMPARE(text->width(), 100.0);
603 QCOMPARE(text->height(), 30.0);
604
605 grabResult = root->grabToImage();
606 QTRY_VERIFY(!grabResult->image().isNull());
607 const QImage actualItemImageGrab(grabResult->image());
608 QCOMPARE(actualItemImageGrab, expectedItemImageGrab);
609}
610
611void tst_qquicktext::elideRelayoutAfterZeroWidth()
612{
613 QQmlEngine engine;
614 QQmlComponent component(&engine, testFileUrl(fileName: "elideZeroWidth.qml"));
615 QScopedPointer<QObject> root(component.create());
616 QVERIFY2(root, qPrintable(component.errorString()));
617 QVERIFY(root->property("ok").toBool());
618}
619
620void tst_qquicktext::multilineElide_data()
621{
622 QTest::addColumn<QQuickText::TextFormat>(name: "format");
623 QTest::newRow(dataTag: "plain") << QQuickText::PlainText;
624 QTest::newRow(dataTag: "styled") << QQuickText::StyledText;
625}
626
627void tst_qquicktext::multilineElide()
628{
629 QFETCH(QQuickText::TextFormat, format);
630 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "multilineelide.qml")));
631
632 QQuickText *myText = qobject_cast<QQuickText*>(object: window->rootObject());
633 QVERIFY(myText != nullptr);
634 myText->setTextFormat(format);
635
636 QCOMPARE(myText->lineCount(), 3);
637 QCOMPARE(myText->truncated(), true);
638
639 qreal lineHeight = myText->contentHeight() / 3.;
640
641 // Set a valid height greater than the truncated content height and ensure the line count is
642 // unchanged.
643 myText->setHeight(200);
644 QCOMPARE(myText->lineCount(), 3);
645 QCOMPARE(myText->truncated(), true);
646
647 // reduce size and ensure fewer lines are drawn
648 myText->setHeight(lineHeight * 2);
649 QCOMPARE(myText->lineCount(), 2);
650
651 myText->setHeight(lineHeight);
652 QCOMPARE(myText->lineCount(), 1);
653
654 myText->setHeight(5);
655 QCOMPARE(myText->lineCount(), 1);
656
657 myText->setHeight(lineHeight * 3);
658 QCOMPARE(myText->lineCount(), 3);
659
660 // remove max count and show all lines.
661 myText->setHeight(1000);
662 myText->resetMaximumLineCount();
663
664 QCOMPARE(myText->truncated(), false);
665
666 // reduce size again
667 myText->setHeight(lineHeight * 2);
668 QCOMPARE(myText->lineCount(), 2);
669 QCOMPARE(myText->truncated(), true);
670
671 // change line height
672 myText->setLineHeight(1.1);
673 QCOMPARE(myText->lineCount(), 1);
674}
675
676void tst_qquicktext::implicitElide_data()
677{
678 QTest::addColumn<QString>(name: "width");
679 QTest::addColumn<QString>(name: "initialText");
680 QTest::addColumn<QString>(name: "text");
681
682 QTest::newRow(dataTag: "maximum width, empty")
683 << "Math.min(implicitWidth, 100)"
684 << "";
685 QTest::newRow(dataTag: "maximum width, short")
686 << "Math.min(implicitWidth, 100)"
687 << "the";
688 QTest::newRow(dataTag: "maximum width, long")
689 << "Math.min(implicitWidth, 100)"
690 << "the quick brown fox jumped over the lazy dog";
691 QTest::newRow(dataTag: "reset width, empty")
692 << "implicitWidth > 100 ? 100 : undefined"
693 << "";
694 QTest::newRow(dataTag: "reset width, short")
695 << "implicitWidth > 100 ? 100 : undefined"
696 << "the";
697 QTest::newRow(dataTag: "reset width, long")
698 << "implicitWidth > 100 ? 100 : undefined"
699 << "the quick brown fox jumped over the lazy dog";
700}
701
702void tst_qquicktext::implicitElide()
703{
704 QFETCH(QString, width);
705 QFETCH(QString, initialText);
706
707 QString componentStr =
708 "import QtQuick 2.0\n"
709 "Text {\n"
710 "width: " + width + "\n"
711 "text: \"" + initialText + "\"\n"
712 "elide: Text.ElideRight\n"
713 "}";
714 QQmlComponent textComponent(&engine);
715 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
716 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
717
718 QVERIFY(textObject->contentWidth() <= textObject->width());
719
720 textObject->setText("the quick brown fox jumped over");
721
722 QVERIFY(textObject->contentWidth() > 0);
723 QVERIFY(textObject->contentWidth() <= textObject->width());
724}
725
726void tst_qquicktext::textFormat()
727{
728 {
729 QQmlComponent textComponent(&engine);
730 textComponent.setData("import QtQuick 2.0\nText { text: \"Hello\"; textFormat: Text.RichText }", baseUrl: QUrl::fromLocalFile(localfile: ""));
731 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
732
733 QVERIFY(textObject != nullptr);
734 QCOMPARE(textObject->textFormat(), QQuickText::RichText);
735
736 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: textObject);
737 QVERIFY(textPrivate != nullptr);
738 QVERIFY(textPrivate->richText);
739
740 delete textObject;
741 }
742 {
743 QQmlComponent textComponent(&engine);
744 textComponent.setData("import QtQuick 2.0\nText { text: \"<b>Hello</b>\" }", baseUrl: QUrl::fromLocalFile(localfile: ""));
745 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
746
747 QVERIFY(textObject != nullptr);
748 QCOMPARE(textObject->textFormat(), QQuickText::AutoText);
749
750 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: textObject);
751 QVERIFY(textPrivate != nullptr);
752 QVERIFY(textPrivate->styledText);
753
754 delete textObject;
755 }
756 {
757 QQmlComponent textComponent(&engine);
758 textComponent.setData("import QtQuick 2.0\nText { text: \"<b>Hello</b>\"; textFormat: Text.PlainText }", baseUrl: QUrl::fromLocalFile(localfile: ""));
759 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
760
761 QVERIFY(textObject != nullptr);
762 QCOMPARE(textObject->textFormat(), QQuickText::PlainText);
763
764 delete textObject;
765 }
766
767 {
768 QQmlComponent component(&engine);
769 component.setData("import QtQuick 2.0\n Text {}", baseUrl: QUrl());
770 QScopedPointer<QObject> object(component.create());
771 QQuickText *text = qobject_cast<QQuickText *>(object: object.data());
772 QVERIFY(text);
773
774 QSignalSpy spy(text, &QQuickText::textFormatChanged);
775
776 QCOMPARE(text->textFormat(), QQuickText::AutoText);
777
778 text->setTextFormat(QQuickText::StyledText);
779 QCOMPARE(text->textFormat(), QQuickText::StyledText);
780 QCOMPARE(spy.count(), 1);
781
782 text->setTextFormat(QQuickText::StyledText);
783 QCOMPARE(spy.count(), 1);
784
785 text->setTextFormat(QQuickText::AutoText);
786 QCOMPARE(text->textFormat(), QQuickText::AutoText);
787 QCOMPARE(spy.count(), 2);
788 }
789
790 {
791 QQmlComponent component(&engine);
792 component.setData("import QtQuick 2.0\n Text { text: \"<b>Hello</b>\" }", baseUrl: QUrl());
793 QScopedPointer<QObject> object(component.create());
794 QQuickText *text = qobject_cast<QQuickText *>(object: object.data());
795 QVERIFY(text);
796 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: text);
797 QVERIFY(textPrivate);
798
799 QCOMPARE(text->textFormat(), QQuickText::AutoText);
800 QVERIFY(!textPrivate->layout.formats().isEmpty());
801
802 text->setTextFormat(QQuickText::StyledText);
803 QVERIFY(!textPrivate->layout.formats().isEmpty());
804
805 text->setTextFormat(QQuickText::PlainText);
806 QVERIFY(textPrivate->layout.formats().isEmpty());
807
808 text->setTextFormat(QQuickText::AutoText);
809 QVERIFY(!textPrivate->layout.formats().isEmpty());
810 }
811
812 {
813 QQmlComponent component(&engine);
814 component.setData("import QtQuick 2.0\nText { text: \"Hello\"; elide: Text.ElideRight }", baseUrl: QUrl::fromLocalFile(localfile: ""));
815 QScopedPointer<QObject> object(component.create());
816 QQuickText *text = qobject_cast<QQuickText *>(object: object.data());
817 QVERIFY(text);
818 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: text);
819 QVERIFY(textPrivate);
820
821 // underline a mnemonic
822 QVector<QTextLayout::FormatRange> formats;
823 QTextLayout::FormatRange range;
824 range.start = 0;
825 range.length = 1;
826 range.format.setFontUnderline(true);
827 formats << range;
828
829 // the mnemonic format should be retained
830 textPrivate->layout.setFormats(formats);
831 text->forceLayout();
832 QCOMPARE(textPrivate->layout.formats(), formats);
833
834 // and carried over to the elide layout
835 text->setWidth(text->implicitWidth() - 1);
836 QVERIFY(textPrivate->elideLayout);
837 QCOMPARE(textPrivate->elideLayout->formats(), formats);
838
839 // but cleared when the text changes
840 text->setText("Changed");
841 QVERIFY(textPrivate->elideLayout);
842 QVERIFY(textPrivate->layout.formats().isEmpty());
843 }
844}
845
846//the alignment tests may be trivial o.oa
847void tst_qquicktext::horizontalAlignment()
848{
849 //test one align each, and then test if two align fails.
850
851 for (int i = 0; i < standard.size(); i++)
852 {
853 for (int j=0; j < horizontalAlignmentmentStrings.size(); j++)
854 {
855 QString componentStr = "import QtQuick 2.0\nText { horizontalAlignment: \"" + horizontalAlignmentmentStrings.at(i: j) + "\"; text: \"" + standard.at(i) + "\" }";
856 QQmlComponent textComponent(&engine);
857 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
858 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
859
860 QCOMPARE((int)textObject->hAlign(), (int)horizontalAlignmentments.at(j));
861
862 delete textObject;
863 }
864 }
865
866 for (int i = 0; i < richText.size(); i++)
867 {
868 for (int j=0; j < horizontalAlignmentmentStrings.size(); j++)
869 {
870 QString componentStr = "import QtQuick 2.0\nText { horizontalAlignment: \"" + horizontalAlignmentmentStrings.at(i: j) + "\"; text: \"" + richText.at(i) + "\" }";
871 QQmlComponent textComponent(&engine);
872 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
873 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
874
875 QCOMPARE((int)textObject->hAlign(), (int)horizontalAlignmentments.at(j));
876
877 delete textObject;
878 }
879 }
880
881}
882
883void tst_qquicktext::horizontalAlignment_RightToLeft()
884{
885 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "horizontalAlignment_RightToLeft.qml")));
886 QQuickText *text = window->rootObject()->findChild<QQuickText*>(aName: "text");
887 QVERIFY(text != nullptr);
888 window->showNormal();
889
890 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: text);
891 QVERIFY(textPrivate != nullptr);
892
893 QTRY_VERIFY(textPrivate->layout.lineCount());
894
895 // implicit alignment should follow the reading direction of RTL text
896 QCOMPARE(text->hAlign(), QQuickText::AlignRight);
897 QCOMPARE(text->effectiveHAlign(), text->hAlign());
898 QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2);
899
900 // explicitly left aligned text
901 text->setHAlign(QQuickText::AlignLeft);
902 QCOMPARE(text->hAlign(), QQuickText::AlignLeft);
903 QCOMPARE(text->effectiveHAlign(), text->hAlign());
904 QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2);
905
906 // explicitly right aligned text
907 text->setHAlign(QQuickText::AlignRight);
908 QCOMPARE(text->hAlign(), QQuickText::AlignRight);
909 QCOMPARE(text->effectiveHAlign(), text->hAlign());
910 QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2);
911
912 // change to rich text
913 QString textString = text->text();
914 text->setText(QString("<i>") + textString + QString("</i>"));
915 text->setTextFormat(QQuickText::RichText);
916 text->resetHAlign();
917
918 // implicitly aligned rich text should follow the reading direction of text
919 QCOMPARE(text->hAlign(), QQuickText::AlignRight);
920 QCOMPARE(text->effectiveHAlign(), text->hAlign());
921 QVERIFY(textPrivate->extra.isAllocated());
922 QVERIFY(textPrivate->extra->doc->defaultTextOption().alignment() & Qt::AlignLeft);
923
924 // explicitly left aligned rich text
925 text->setHAlign(QQuickText::AlignLeft);
926 QCOMPARE(text->hAlign(), QQuickText::AlignLeft);
927 QCOMPARE(text->effectiveHAlign(), text->hAlign());
928 QVERIFY(textPrivate->extra->doc->defaultTextOption().alignment() & Qt::AlignRight);
929
930 // explicitly right aligned rich text
931 text->setHAlign(QQuickText::AlignRight);
932 QCOMPARE(text->hAlign(), QQuickText::AlignRight);
933 QCOMPARE(text->effectiveHAlign(), text->hAlign());
934 QVERIFY(textPrivate->extra->doc->defaultTextOption().alignment() & Qt::AlignLeft);
935
936 text->setText(textString);
937 text->setTextFormat(QQuickText::PlainText);
938
939 // explicitly center aligned
940 text->setHAlign(QQuickText::AlignHCenter);
941 QCOMPARE(text->hAlign(), QQuickText::AlignHCenter);
942 QCOMPARE(text->effectiveHAlign(), text->hAlign());
943 QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2);
944 QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().right() > window->width()/2);
945
946 // reseted alignment should go back to following the text reading direction
947 text->resetHAlign();
948 QCOMPARE(text->hAlign(), QQuickText::AlignRight);
949 QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2);
950
951 // mirror the text item
952 QQuickItemPrivate::get(item: text)->setLayoutMirror(true);
953
954 // mirrored implicit alignment should continue to follow the reading direction of the text
955 QCOMPARE(text->hAlign(), QQuickText::AlignRight);
956 QCOMPARE(text->effectiveHAlign(), QQuickText::AlignRight);
957 QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2);
958
959 // mirrored explicitly right aligned behaves as left aligned
960 text->setHAlign(QQuickText::AlignRight);
961 QCOMPARE(text->hAlign(), QQuickText::AlignRight);
962 QCOMPARE(text->effectiveHAlign(), QQuickText::AlignLeft);
963 QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2);
964
965 // mirrored explicitly left aligned behaves as right aligned
966 text->setHAlign(QQuickText::AlignLeft);
967 QCOMPARE(text->hAlign(), QQuickText::AlignLeft);
968 QCOMPARE(text->effectiveHAlign(), QQuickText::AlignRight);
969 QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() > window->width()/2);
970
971 // disable mirroring
972 QQuickItemPrivate::get(item: text)->setLayoutMirror(false);
973 text->resetHAlign();
974
975 // English text should be implicitly left aligned
976 text->setText("Hello world!");
977 QCOMPARE(text->hAlign(), QQuickText::AlignLeft);
978 QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().left() < window->width()/2);
979
980 // empty text with implicit alignment follows the system locale-based
981 // keyboard input direction from QInputMethod::inputDirection()
982 text->setText("");
983 QCOMPARE(text->hAlign(), qApp->inputMethod()->inputDirection() == Qt::LeftToRight ?
984 QQuickText::AlignLeft : QQuickText::AlignRight);
985 text->setHAlign(QQuickText::AlignRight);
986 QCOMPARE(text->hAlign(), QQuickText::AlignRight);
987
988 window.reset();
989
990 // alignment of Text with no text set to it
991 QString componentStr = "import QtQuick 2.0\nText {}";
992 QQmlComponent textComponent(&engine);
993 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
994 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
995 QCOMPARE(textObject->hAlign(), qApp->inputMethod()->inputDirection() == Qt::LeftToRight ?
996 QQuickText::AlignLeft : QQuickText::AlignRight);
997 delete textObject;
998}
999
1000int tst_qquicktext::numberOfNonWhitePixels(int fromX, int toX, const QImage &image)
1001{
1002 int pixels = 0;
1003 for (int x = fromX; x < toX; ++x) {
1004 for (int y = 0; y < image.height(); ++y) {
1005 if (image.pixel(x, y) != qRgb(r: 255, g: 255, b: 255))
1006 pixels++;
1007 }
1008 }
1009 return pixels;
1010}
1011
1012static inline QByteArray msgNotGreaterThan(int n1, int n2)
1013{
1014 return QByteArray::number(n1) + QByteArrayLiteral(" is not greater than ") + QByteArray::number(n2);
1015}
1016
1017static inline QByteArray msgNotLessThan(int n1, int n2)
1018{
1019 return QByteArray::number(n1) + QByteArrayLiteral(" is not less than ") + QByteArray::number(n2);
1020}
1021
1022void tst_qquicktext::hAlignImplicitWidth()
1023{
1024#ifdef Q_OS_MACOS
1025 QSKIP("this test currently crashes on MacOS. See QTBUG-68047");
1026#endif
1027 QQuickView view(testFileUrl(fileName: "hAlignImplicitWidth.qml"));
1028 view.setFlags(view.flags() | Qt::WindowStaysOnTopHint); // Prevent being obscured by other windows.
1029 view.show();
1030 view.requestActivate();
1031 QVERIFY(QTest::qWaitForWindowExposed(&view));
1032
1033 QQuickText *text = view.rootObject()->findChild<QQuickText*>(aName: "textItem");
1034 QVERIFY(text != nullptr);
1035
1036 // Try to check whether alignment works by checking the number of black
1037 // pixels in the thirds of the grabbed image.
1038 // QQuickWindow::grabWindow() scales the returned image by the devicePixelRatio of the screen.
1039 const qreal devicePixelRatio = view.screen()->devicePixelRatio();
1040 const int windowWidth = 220 * devicePixelRatio;
1041 const int textWidth = qCeil(v: text->implicitWidth()) * devicePixelRatio;
1042 QVERIFY2(textWidth < windowWidth, "System font too large.");
1043 const int sectionWidth = textWidth / 3;
1044 const int centeredSection1 = (windowWidth - textWidth) / 2;
1045 const int centeredSection2 = centeredSection1 + sectionWidth;
1046 const int centeredSection3 = centeredSection2 + sectionWidth;
1047 const int centeredSection3End = centeredSection3 + sectionWidth;
1048
1049 {
1050 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
1051 || (QGuiApplication::platformName() == QLatin1String("minimal")))
1052 QEXPECT_FAIL("", "Failure due to grabWindow not functional on offscreen/minimal platforms", Abort);
1053
1054 // Left Align
1055 QImage image = view.grabWindow();
1056 const int left = numberOfNonWhitePixels(fromX: centeredSection1, toX: centeredSection2, image);
1057 const int mid = numberOfNonWhitePixels(fromX: centeredSection2, toX: centeredSection3, image);
1058 const int right = numberOfNonWhitePixels(fromX: centeredSection3, toX: centeredSection3End, image);
1059 QVERIFY2(left > mid, msgNotGreaterThan(left, mid).constData());
1060 QVERIFY2(mid > right, msgNotGreaterThan(mid, right).constData());
1061 }
1062 {
1063 // HCenter Align
1064 text->setHAlign(QQuickText::AlignHCenter);
1065 text->setText("Reset"); // set dummy string to force relayout once original text is set again
1066 text->setText("AA\nBBBBBBB\nCCCCCCCCCCCCCCCC");
1067 QImage image = view.grabWindow();
1068 const int left = numberOfNonWhitePixels(fromX: centeredSection1, toX: centeredSection2, image);
1069 const int mid = numberOfNonWhitePixels(fromX: centeredSection2, toX: centeredSection3, image);
1070 const int right = numberOfNonWhitePixels(fromX: centeredSection3, toX: centeredSection3End, image);
1071 QVERIFY2(left < mid, msgNotLessThan(left, mid).constData());
1072 QVERIFY2(mid > right, msgNotGreaterThan(mid, right).constData());
1073 }
1074 {
1075 // Right Align
1076 text->setHAlign(QQuickText::AlignRight);
1077 text->setText("Reset"); // set dummy string to force relayout once original text is set again
1078 text->setText("AA\nBBBBBBB\nCCCCCCCCCCCCCCCC");
1079 QImage image = view.grabWindow();
1080 const int left = numberOfNonWhitePixels(fromX: centeredSection1, toX: centeredSection2, image);
1081 const int mid = numberOfNonWhitePixels(fromX: centeredSection2, toX: centeredSection3, image);
1082 const int right = numberOfNonWhitePixels(fromX: centeredSection3, toX: centeredSection3End, image);
1083 QVERIFY2(left < mid, msgNotLessThan(left, mid).constData());
1084 QVERIFY2(mid < right, msgNotLessThan(mid, right).constData());
1085 }
1086}
1087
1088void tst_qquicktext::verticalAlignment()
1089{
1090 //test one align each, and then test if two align fails.
1091
1092 for (int i = 0; i < standard.size(); i++)
1093 {
1094 for (int j=0; j < verticalAlignmentmentStrings.size(); j++)
1095 {
1096 QString componentStr = "import QtQuick 2.0\nText { verticalAlignment: \"" + verticalAlignmentmentStrings.at(i: j) + "\"; text: \"" + standard.at(i) + "\" }";
1097 QQmlComponent textComponent(&engine);
1098 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1099 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1100
1101 QVERIFY(textObject != nullptr);
1102 QCOMPARE((int)textObject->vAlign(), (int)verticalAlignmentments.at(j));
1103
1104 delete textObject;
1105 }
1106 }
1107
1108 for (int i = 0; i < richText.size(); i++)
1109 {
1110 for (int j=0; j < verticalAlignmentmentStrings.size(); j++)
1111 {
1112 QString componentStr = "import QtQuick 2.0\nText { verticalAlignment: \"" + verticalAlignmentmentStrings.at(i: j) + "\"; text: \"" + richText.at(i) + "\" }";
1113 QQmlComponent textComponent(&engine);
1114 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1115 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1116
1117 QVERIFY(textObject != nullptr);
1118 QCOMPARE((int)textObject->vAlign(), (int)verticalAlignmentments.at(j));
1119
1120 delete textObject;
1121 }
1122 }
1123
1124}
1125
1126void tst_qquicktext::font()
1127{
1128 //test size, then bold, then italic, then family
1129 {
1130 QString componentStr = "import QtQuick 2.0\nText { font.pointSize: 40; text: \"Hello World\" }";
1131 QQmlComponent textComponent(&engine);
1132 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1133 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1134
1135 QCOMPARE(textObject->font().pointSize(), 40);
1136 QCOMPARE(textObject->font().bold(), false);
1137 QCOMPARE(textObject->font().italic(), false);
1138
1139 delete textObject;
1140 }
1141
1142 {
1143 QString componentStr = "import QtQuick 2.0\nText { font.pixelSize: 40; text: \"Hello World\" }";
1144 QQmlComponent textComponent(&engine);
1145 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1146 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1147
1148 QCOMPARE(textObject->font().pixelSize(), 40);
1149 QCOMPARE(textObject->font().bold(), false);
1150 QCOMPARE(textObject->font().italic(), false);
1151
1152 delete textObject;
1153 }
1154
1155 {
1156 QString componentStr = "import QtQuick 2.0\nText { font.bold: true; text: \"Hello World\" }";
1157 QQmlComponent textComponent(&engine);
1158 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1159 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1160
1161 QCOMPARE(textObject->font().bold(), true);
1162 QCOMPARE(textObject->font().italic(), false);
1163
1164 delete textObject;
1165 }
1166
1167 {
1168 QString componentStr = "import QtQuick 2.0\nText { font.italic: true; text: \"Hello World\" }";
1169 QQmlComponent textComponent(&engine);
1170 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1171 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1172
1173 QCOMPARE(textObject->font().italic(), true);
1174 QCOMPARE(textObject->font().bold(), false);
1175
1176 delete textObject;
1177 }
1178
1179 {
1180 QString componentStr = "import QtQuick 2.0\nText { font.family: \"Helvetica\"; text: \"Hello World\" }";
1181 QQmlComponent textComponent(&engine);
1182 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1183 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1184
1185 QCOMPARE(textObject->font().family(), QString("Helvetica"));
1186 QCOMPARE(textObject->font().bold(), false);
1187 QCOMPARE(textObject->font().italic(), false);
1188
1189 delete textObject;
1190 }
1191
1192 {
1193 QString componentStr = "import QtQuick 2.0\nText { font.family: \"\"; text: \"Hello World\" }";
1194 QQmlComponent textComponent(&engine);
1195 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1196 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1197
1198 QCOMPARE(textObject->font().family(), QString(""));
1199
1200 delete textObject;
1201 }
1202}
1203
1204void tst_qquicktext::style()
1205{
1206 //test style
1207 for (int i = 0; i < styles.size(); i++)
1208 {
1209 QString componentStr = "import QtQuick 2.0\nText { style: \"" + styleStrings.at(i) + "\"; styleColor: \"white\"; text: \"Hello World\" }";
1210 QQmlComponent textComponent(&engine);
1211 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1212 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1213
1214 QCOMPARE((int)textObject->style(), (int)styles.at(i));
1215 QCOMPARE(textObject->styleColor(), QColor("white"));
1216
1217 delete textObject;
1218 }
1219 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello World\" }";
1220 QQmlComponent textComponent(&engine);
1221 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1222 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1223
1224 QRectF brPre = textObject->boundingRect();
1225 textObject->setStyle(QQuickText::Outline);
1226 QRectF brPost = textObject->boundingRect();
1227
1228 QVERIFY(brPre.width() < brPost.width());
1229 QVERIFY(brPre.height() < brPost.height());
1230
1231 delete textObject;
1232}
1233
1234void tst_qquicktext::color()
1235{
1236 //test style
1237 for (int i = 0; i < colorStrings.size(); i++)
1238 {
1239 QString componentStr = "import QtQuick 2.0\nText { color: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
1240 QQmlComponent textComponent(&engine);
1241 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1242 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1243
1244 QCOMPARE(textObject->color(), QColor(colorStrings.at(i)));
1245 QCOMPARE(textObject->styleColor(), QColor("black"));
1246 QCOMPARE(textObject->linkColor(), QColor("blue"));
1247
1248 delete textObject;
1249 }
1250
1251 for (int i = 0; i < colorStrings.size(); i++)
1252 {
1253 QString componentStr = "import QtQuick 2.0\nText { styleColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
1254 QQmlComponent textComponent(&engine);
1255 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1256 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1257
1258 QCOMPARE(textObject->styleColor(), QColor(colorStrings.at(i)));
1259 // default color to black?
1260 QCOMPARE(textObject->color(), QColor("black"));
1261 QCOMPARE(textObject->linkColor(), QColor("blue"));
1262
1263 QSignalSpy colorSpy(textObject, SIGNAL(colorChanged()));
1264 QSignalSpy linkColorSpy(textObject, SIGNAL(linkColorChanged()));
1265
1266 textObject->setColor(QColor("white"));
1267 QCOMPARE(textObject->color(), QColor("white"));
1268 QCOMPARE(colorSpy.count(), 1);
1269
1270 textObject->setLinkColor(QColor("black"));
1271 QCOMPARE(textObject->linkColor(), QColor("black"));
1272 QCOMPARE(linkColorSpy.count(), 1);
1273
1274 textObject->setColor(QColor("white"));
1275 QCOMPARE(colorSpy.count(), 1);
1276
1277 textObject->setLinkColor(QColor("black"));
1278 QCOMPARE(linkColorSpy.count(), 1);
1279
1280 textObject->setColor(QColor("black"));
1281 QCOMPARE(textObject->color(), QColor("black"));
1282 QCOMPARE(colorSpy.count(), 2);
1283
1284 textObject->setLinkColor(QColor("blue"));
1285 QCOMPARE(textObject->linkColor(), QColor("blue"));
1286 QCOMPARE(linkColorSpy.count(), 2);
1287
1288 delete textObject;
1289 }
1290
1291 for (int i = 0; i < colorStrings.size(); i++)
1292 {
1293 QString componentStr = "import QtQuick 2.0\nText { linkColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
1294 QQmlComponent textComponent(&engine);
1295 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1296 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1297
1298 QCOMPARE(textObject->styleColor(), QColor("black"));
1299 QCOMPARE(textObject->color(), QColor("black"));
1300 QCOMPARE(textObject->linkColor(), QColor(colorStrings.at(i)));
1301
1302 delete textObject;
1303 }
1304
1305 for (int i = 0; i < colorStrings.size(); i++)
1306 {
1307 for (int j = 0; j < colorStrings.size(); j++)
1308 {
1309 QString componentStr = "import QtQuick 2.0\nText { "
1310 "color: \"" + colorStrings.at(i) + "\"; "
1311 "styleColor: \"" + colorStrings.at(i: j) + "\"; "
1312 "linkColor: \"" + colorStrings.at(i: j) + "\"; "
1313 "text: \"Hello World\" }";
1314 QQmlComponent textComponent(&engine);
1315 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1316 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1317
1318 QCOMPARE(textObject->color(), QColor(colorStrings.at(i)));
1319 QCOMPARE(textObject->styleColor(), QColor(colorStrings.at(j)));
1320 QCOMPARE(textObject->linkColor(), QColor(colorStrings.at(j)));
1321
1322 delete textObject;
1323 }
1324 }
1325 {
1326 QString colorStr = "#AA001234";
1327 QColor testColor("#001234");
1328 testColor.setAlpha(170);
1329
1330 QString componentStr = "import QtQuick 2.0\nText { color: \"" + colorStr + "\"; text: \"Hello World\" }";
1331 QQmlComponent textComponent(&engine);
1332 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1333 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1334
1335 QCOMPARE(textObject->color(), testColor);
1336
1337 delete textObject;
1338 } {
1339 QString colorStr = "#001234";
1340 QColor testColor(colorStr);
1341
1342 QString componentStr = "import QtQuick 2.0\nText { color: \"" + colorStr + "\"; text: \"Hello World\" }";
1343 QQmlComponent textComponent(&engine);
1344 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1345 QScopedPointer<QObject> object(textComponent.create());
1346 QQuickText *textObject = qobject_cast<QQuickText*>(object: object.data());
1347
1348 QSignalSpy spy(textObject, SIGNAL(colorChanged()));
1349
1350 QCOMPARE(textObject->color(), testColor);
1351 textObject->setColor(testColor);
1352 QCOMPARE(textObject->color(), testColor);
1353 QCOMPARE(spy.count(), 0);
1354
1355 testColor = QColor("black");
1356 textObject->setColor(testColor);
1357 QCOMPARE(textObject->color(), testColor);
1358 QCOMPARE(spy.count(), 1);
1359 } {
1360 QString colorStr = "#001234";
1361 QColor testColor(colorStr);
1362
1363 QString componentStr = "import QtQuick 2.0\nText { styleColor: \"" + colorStr + "\"; text: \"Hello World\" }";
1364 QQmlComponent textComponent(&engine);
1365 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1366 QScopedPointer<QObject> object(textComponent.create());
1367 QQuickText *textObject = qobject_cast<QQuickText*>(object: object.data());
1368
1369 QSignalSpy spy(textObject, SIGNAL(styleColorChanged()));
1370
1371 QCOMPARE(textObject->styleColor(), testColor);
1372 textObject->setStyleColor(testColor);
1373 QCOMPARE(textObject->styleColor(), testColor);
1374 QCOMPARE(spy.count(), 0);
1375
1376 testColor = QColor("black");
1377 textObject->setStyleColor(testColor);
1378 QCOMPARE(textObject->styleColor(), testColor);
1379 QCOMPARE(spy.count(), 1);
1380 } {
1381 QString colorStr = "#001234";
1382 QColor testColor(colorStr);
1383
1384 QString componentStr = "import QtQuick 2.0\nText { linkColor: \"" + colorStr + "\"; text: \"Hello World\" }";
1385 QQmlComponent textComponent(&engine);
1386 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1387 QScopedPointer<QObject> object(textComponent.create());
1388 QQuickText *textObject = qobject_cast<QQuickText*>(object: object.data());
1389
1390 QSignalSpy spy(textObject, SIGNAL(linkColorChanged()));
1391
1392 QCOMPARE(textObject->linkColor(), testColor);
1393 textObject->setLinkColor(testColor);
1394 QCOMPARE(textObject->linkColor(), testColor);
1395 QCOMPARE(spy.count(), 0);
1396
1397 testColor = QColor("black");
1398 textObject->setLinkColor(testColor);
1399 QCOMPARE(textObject->linkColor(), testColor);
1400 QCOMPARE(spy.count(), 1);
1401 }
1402}
1403
1404void tst_qquicktext::smooth()
1405{
1406 for (int i = 0; i < standard.size(); i++)
1407 {
1408 {
1409 QString componentStr = "import QtQuick 2.0\nText { smooth: false; text: \"" + standard.at(i) + "\" }";
1410 QQmlComponent textComponent(&engine);
1411 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1412 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1413 QCOMPARE(textObject->smooth(), false);
1414
1415 delete textObject;
1416 }
1417 {
1418 QString componentStr = "import QtQuick 2.0\nText { text: \"" + standard.at(i) + "\" }";
1419 QQmlComponent textComponent(&engine);
1420 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1421 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1422 QCOMPARE(textObject->smooth(), true);
1423
1424 delete textObject;
1425 }
1426 }
1427 for (int i = 0; i < richText.size(); i++)
1428 {
1429 {
1430 QString componentStr = "import QtQuick 2.0\nText { smooth: false; text: \"" + richText.at(i) + "\" }";
1431 QQmlComponent textComponent(&engine);
1432 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1433 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1434 QCOMPARE(textObject->smooth(), false);
1435
1436 delete textObject;
1437 }
1438 {
1439 QString componentStr = "import QtQuick 2.0\nText { text: \"" + richText.at(i) + "\" }";
1440 QQmlComponent textComponent(&engine);
1441 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1442 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1443 QCOMPARE(textObject->smooth(), true);
1444
1445 delete textObject;
1446 }
1447 }
1448}
1449
1450void tst_qquicktext::renderType()
1451{
1452 QQmlComponent component(&engine);
1453 component.setData("import QtQuick 2.0\n Text {}", baseUrl: QUrl());
1454 QScopedPointer<QObject> object(component.create());
1455 QQuickText *text = qobject_cast<QQuickText *>(object: object.data());
1456 QVERIFY(text);
1457
1458 QSignalSpy spy(text, SIGNAL(renderTypeChanged()));
1459
1460 QCOMPARE(text->renderType(), QQuickText::QtRendering);
1461
1462 text->setRenderType(QQuickText::NativeRendering);
1463 QCOMPARE(text->renderType(), QQuickText::NativeRendering);
1464 QCOMPARE(spy.count(), 1);
1465
1466 text->setRenderType(QQuickText::NativeRendering);
1467 QCOMPARE(spy.count(), 1);
1468
1469 text->setRenderType(QQuickText::QtRendering);
1470 QCOMPARE(text->renderType(), QQuickText::QtRendering);
1471 QCOMPARE(spy.count(), 2);
1472}
1473
1474void tst_qquicktext::antialiasing()
1475{
1476 QQmlComponent component(&engine);
1477 component.setData("import QtQuick 2.0\n Text {}", baseUrl: QUrl());
1478 QScopedPointer<QObject> object(component.create());
1479 QQuickText *text = qobject_cast<QQuickText *>(object: object.data());
1480 QVERIFY(text);
1481
1482 QSignalSpy spy(text, SIGNAL(antialiasingChanged(bool)));
1483
1484 QCOMPARE(text->antialiasing(), true);
1485
1486 text->setAntialiasing(false);
1487 QCOMPARE(text->antialiasing(), false);
1488 QCOMPARE(spy.count(), 1);
1489
1490 text->setAntialiasing(false);
1491 QCOMPARE(spy.count(), 1);
1492
1493 text->resetAntialiasing();
1494 QCOMPARE(text->antialiasing(), true);
1495 QCOMPARE(spy.count(), 2);
1496
1497 // QTBUG-39047
1498 component.setData("import QtQuick 2.0\n Text { antialiasing: true }", baseUrl: QUrl());
1499 object.reset(other: component.create());
1500 text = qobject_cast<QQuickText *>(object: object.data());
1501 QVERIFY(text);
1502 QCOMPARE(text->antialiasing(), true);
1503}
1504
1505void tst_qquicktext::weight()
1506{
1507 {
1508 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }";
1509 QQmlComponent textComponent(&engine);
1510 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1511 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1512
1513 QVERIFY(textObject != nullptr);
1514 QCOMPARE((int)textObject->font().weight(), (int)QQuickFontValueType::Normal);
1515
1516 delete textObject;
1517 }
1518 {
1519 QString componentStr = "import QtQuick 2.0\nText { font.weight: \"Bold\"; text: \"Hello world!\" }";
1520 QQmlComponent textComponent(&engine);
1521 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1522 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1523
1524 QVERIFY(textObject != nullptr);
1525 QCOMPARE((int)textObject->font().weight(), (int)QQuickFontValueType::Bold);
1526
1527 delete textObject;
1528 }
1529}
1530
1531void tst_qquicktext::underline()
1532{
1533 QQuickView view(testFileUrl(fileName: "underline.qml"));
1534 view.show();
1535 view.requestActivate();
1536 QVERIFY(QTest::qWaitForWindowActive(&view));
1537 QQuickText *textObject = view.rootObject()->findChild<QQuickText*>(aName: "myText");
1538 QVERIFY(textObject != nullptr);
1539 QCOMPARE(textObject->font().overline(), false);
1540 QCOMPARE(textObject->font().underline(), true);
1541 QCOMPARE(textObject->font().strikeOut(), false);
1542}
1543
1544void tst_qquicktext::overline()
1545{
1546 QQuickView view(testFileUrl(fileName: "overline.qml"));
1547 view.show();
1548 view.requestActivate();
1549 QVERIFY(QTest::qWaitForWindowActive(&view));
1550 QQuickText *textObject = view.rootObject()->findChild<QQuickText*>(aName: "myText");
1551 QVERIFY(textObject != nullptr);
1552 QCOMPARE(textObject->font().overline(), true);
1553 QCOMPARE(textObject->font().underline(), false);
1554 QCOMPARE(textObject->font().strikeOut(), false);
1555}
1556
1557void tst_qquicktext::strikeout()
1558{
1559 QQuickView view(testFileUrl(fileName: "strikeout.qml"));
1560 view.show();
1561 view.requestActivate();
1562 QVERIFY(QTest::qWaitForWindowActive(&view));
1563 QQuickText *textObject = view.rootObject()->findChild<QQuickText*>(aName: "myText");
1564 QVERIFY(textObject != nullptr);
1565 QCOMPARE(textObject->font().overline(), false);
1566 QCOMPARE(textObject->font().underline(), false);
1567 QCOMPARE(textObject->font().strikeOut(), true);
1568}
1569
1570void tst_qquicktext::capitalization()
1571{
1572 {
1573 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }";
1574 QQmlComponent textComponent(&engine);
1575 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1576 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1577
1578 QVERIFY(textObject != nullptr);
1579 QCOMPARE((int)textObject->font().capitalization(), (int)QQuickFontValueType::MixedCase);
1580
1581 delete textObject;
1582 }
1583 {
1584 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"AllUppercase\" }";
1585 QQmlComponent textComponent(&engine);
1586 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1587 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1588
1589 QVERIFY(textObject != nullptr);
1590 QCOMPARE((int)textObject->font().capitalization(), (int)QQuickFontValueType::AllUppercase);
1591
1592 delete textObject;
1593 }
1594 {
1595 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"AllLowercase\" }";
1596 QQmlComponent textComponent(&engine);
1597 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1598 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1599
1600 QVERIFY(textObject != nullptr);
1601 QCOMPARE((int)textObject->font().capitalization(), (int)QQuickFontValueType::AllLowercase);
1602
1603 delete textObject;
1604 }
1605 {
1606 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"SmallCaps\" }";
1607 QQmlComponent textComponent(&engine);
1608 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1609 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1610
1611 QVERIFY(textObject != nullptr);
1612 QCOMPARE((int)textObject->font().capitalization(), (int)QQuickFontValueType::SmallCaps);
1613
1614 delete textObject;
1615 }
1616 {
1617 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.capitalization: \"Capitalize\" }";
1618 QQmlComponent textComponent(&engine);
1619 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1620 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1621
1622 QVERIFY(textObject != nullptr);
1623 QCOMPARE((int)textObject->font().capitalization(), (int)QQuickFontValueType::Capitalize);
1624
1625 delete textObject;
1626 }
1627}
1628
1629void tst_qquicktext::letterSpacing()
1630{
1631 {
1632 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }";
1633 QQmlComponent textComponent(&engine);
1634 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1635 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1636
1637 QVERIFY(textObject != nullptr);
1638 QCOMPARE(textObject->font().letterSpacing(), 0.0);
1639
1640 delete textObject;
1641 }
1642 {
1643 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.letterSpacing: -2 }";
1644 QQmlComponent textComponent(&engine);
1645 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1646 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1647
1648 QVERIFY(textObject != nullptr);
1649 QCOMPARE(textObject->font().letterSpacing(), -2.);
1650
1651 delete textObject;
1652 }
1653 {
1654 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.letterSpacing: 3 }";
1655 QQmlComponent textComponent(&engine);
1656 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1657 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1658
1659 QVERIFY(textObject != nullptr);
1660 QCOMPARE(textObject->font().letterSpacing(), 3.);
1661
1662 delete textObject;
1663 }
1664}
1665
1666void tst_qquicktext::wordSpacing()
1667{
1668 {
1669 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }";
1670 QQmlComponent textComponent(&engine);
1671 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1672 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1673
1674 QVERIFY(textObject != nullptr);
1675 QCOMPARE(textObject->font().wordSpacing(), 0.0);
1676
1677 delete textObject;
1678 }
1679 {
1680 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.wordSpacing: -50 }";
1681 QQmlComponent textComponent(&engine);
1682 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1683 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1684
1685 QVERIFY(textObject != nullptr);
1686 QCOMPARE(textObject->font().wordSpacing(), -50.);
1687
1688 delete textObject;
1689 }
1690 {
1691 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.wordSpacing: 200 }";
1692 QQmlComponent textComponent(&engine);
1693 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
1694 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
1695
1696 QVERIFY(textObject != nullptr);
1697 QCOMPARE(textObject->font().wordSpacing(), 200.);
1698
1699 delete textObject;
1700 }
1701}
1702
1703class EventSender : public QQuickItem
1704{
1705public:
1706 void sendEvent(QEvent *event) {
1707 switch (event->type()) {
1708 case QEvent::MouseButtonPress:
1709 mousePressEvent(event: static_cast<QMouseEvent *>(event));
1710 break;
1711 case QEvent::MouseButtonRelease:
1712 mouseReleaseEvent(event: static_cast<QMouseEvent *>(event));
1713 break;
1714 case QEvent::MouseMove:
1715 mouseMoveEvent(event: static_cast<QMouseEvent *>(event));
1716 break;
1717 case QEvent::HoverEnter:
1718 hoverEnterEvent(event: static_cast<QHoverEvent *>(event));
1719 break;
1720 case QEvent::HoverLeave:
1721 hoverLeaveEvent(event: static_cast<QHoverEvent *>(event));
1722 break;
1723 case QEvent::HoverMove:
1724 hoverMoveEvent(event: static_cast<QHoverEvent *>(event));
1725 break;
1726 default:
1727 qWarning() << "Trying to send unsupported event type";
1728 break;
1729 }
1730 }
1731};
1732
1733class LinkTest : public QObject
1734{
1735 Q_OBJECT
1736public:
1737 LinkTest() {}
1738
1739 QString clickedLink;
1740 QString hoveredLink;
1741
1742public slots:
1743 void linkClicked(QString l) { clickedLink = l; }
1744 void linkHovered(QString l) { hoveredLink = l; }
1745};
1746
1747class TextMetrics
1748{
1749public:
1750 TextMetrics(const QString &text, Qt::TextElideMode elideMode = Qt::ElideNone)
1751 {
1752 QString adjustedText = text;
1753 adjustedText.replace(before: QLatin1Char('\n'), after: QChar(QChar::LineSeparator));
1754 if (elideMode == Qt::ElideLeft)
1755 adjustedText = QChar(0x2026) + adjustedText;
1756 else if (elideMode == Qt::ElideRight)
1757 adjustedText = adjustedText + QChar(0x2026);
1758
1759 layout.setText(adjustedText);
1760 QTextOption option;
1761 option.setUseDesignMetrics(true);
1762 layout.setTextOption(option);
1763
1764 layout.beginLayout();
1765 qreal height = 0;
1766 QTextLine line = layout.createLine();
1767 while (line.isValid()) {
1768 line.setLineWidth(FLT_MAX);
1769 line.setPosition(QPointF(0, height));
1770 height += line.height();
1771 line = layout.createLine();
1772 }
1773 layout.endLayout();
1774 }
1775
1776 qreal width() const { return layout.maximumWidth(); }
1777
1778 QRectF characterRectangle(
1779 int position,
1780 int hAlign = Qt::AlignLeft,
1781 int vAlign = Qt::AlignTop,
1782 const QSizeF &bounds = QSizeF(240, 320)) const
1783 {
1784 qreal dy = 0;
1785 switch (vAlign) {
1786 case Qt::AlignBottom:
1787 dy = bounds.height() - layout.boundingRect().height();
1788 break;
1789 case Qt::AlignVCenter:
1790 dy = (bounds.height() - layout.boundingRect().height()) / 2;
1791 break;
1792 default:
1793 break;
1794 }
1795
1796 for (int i = 0; i < layout.lineCount(); ++i) {
1797 QTextLine line = layout.lineAt(i);
1798 if (position >= line.textStart() + line.textLength())
1799 continue;
1800 qreal dx = 0;
1801 switch (hAlign) {
1802 case Qt::AlignRight:
1803 dx = bounds.width() - line.naturalTextWidth();
1804 break;
1805 case Qt::AlignHCenter:
1806 dx = (bounds.width() - line.naturalTextWidth()) / 2;
1807 break;
1808 default:
1809 break;
1810 }
1811
1812 QRectF rect;
1813 rect.setLeft(dx + line.cursorToX(cursorPos: position, edge: QTextLine::Leading));
1814 rect.setRight(dx + line.cursorToX(cursorPos: position, edge: QTextLine::Trailing));
1815 rect.setTop(dy + line.y());
1816 rect.setBottom(dy + line.y() + line.height());
1817
1818 return rect;
1819 }
1820 return QRectF();
1821 }
1822
1823 QTextLayout layout;
1824};
1825
1826
1827typedef QVector<QPointF> PointVector;
1828Q_DECLARE_METATYPE(PointVector);
1829
1830void tst_qquicktext::linkInteraction_data()
1831{
1832 QTest::addColumn<QString>(name: "text");
1833 QTest::addColumn<qreal>(name: "width");
1834 QTest::addColumn<QString>(name: "bindings");
1835 QTest::addColumn<PointVector>(name: "mousePositions");
1836 QTest::addColumn<QString>(name: "clickedLink");
1837 QTest::addColumn<QString>(name: "hoverEnterLink");
1838 QTest::addColumn<QString>(name: "hoverMoveLink");
1839
1840 const QString singleLineText = "this text has a <a href=\\\"http://qt-project.org/single\\\">link</a> in it";
1841 const QString singleLineLink = "http://qt-project.org/single";
1842 const QString multipleLineText = "this text<br/>has <a href=\\\"http://qt-project.org/multiple\\\">multiple<br/>lines</a> in it";
1843 const QString multipleLineLink = "http://qt-project.org/multiple";
1844 const QString nestedText = "this text has a <a href=\\\"http://qt-project.org/outer\\\">nested <a href=\\\"http://qt-project.org/inner\\\">link</a> in it</a>";
1845 const QString outerLink = "http://qt-project.org/outer";
1846 const QString innerLink = "http://qt-project.org/inner";
1847
1848 {
1849 const TextMetrics metrics("this text has a link in it");
1850
1851 QTest::newRow(dataTag: "click on link")
1852 << singleLineText << 240.
1853 << ""
1854 << (PointVector() << metrics.characterRectangle(position: 18).center())
1855 << singleLineLink
1856 << singleLineLink << singleLineLink;
1857 QTest::newRow(dataTag: "click on text")
1858 << singleLineText << 240.
1859 << ""
1860 << (PointVector() << metrics.characterRectangle(position: 13).center())
1861 << QString()
1862 << QString() << QString();
1863 QTest::newRow(dataTag: "drag within link")
1864 << singleLineText << 240.
1865 << ""
1866 << (PointVector()
1867 << metrics.characterRectangle(position: 17).center()
1868 << metrics.characterRectangle(position: 19).center())
1869 << singleLineLink
1870 << singleLineLink << singleLineLink;
1871 QTest::newRow(dataTag: "drag away from link")
1872 << singleLineText << 240.
1873 << ""
1874 << (PointVector()
1875 << metrics.characterRectangle(position: 18).center()
1876 << metrics.characterRectangle(position: 13).center())
1877 << QString()
1878 << singleLineLink << QString();
1879 QTest::newRow(dataTag: "drag on to link")
1880 << singleLineText << 240.
1881 << ""
1882 << (PointVector()
1883 << metrics.characterRectangle(position: 13).center()
1884 << metrics.characterRectangle(position: 18).center())
1885 << QString()
1886 << QString() << singleLineLink;
1887 QTest::newRow(dataTag: "click on bottom right aligned link")
1888 << singleLineText << 240.
1889 << "horizontalAlignment: Text.AlignRight; verticalAlignment: Text.AlignBottom"
1890 << (PointVector() << metrics.characterRectangle(position: 18, hAlign: Qt::AlignRight, vAlign: Qt::AlignBottom).center())
1891 << singleLineLink
1892 << singleLineLink << singleLineLink;
1893 QTest::newRow(dataTag: "click on mirrored link")
1894 << singleLineText << 240.
1895 << "horizontalAlignment: Text.AlignLeft; LayoutMirroring.enabled: true"
1896 << (PointVector() << metrics.characterRectangle(position: 18, hAlign: Qt::AlignRight, vAlign: Qt::AlignTop).center())
1897 << singleLineLink
1898 << singleLineLink << singleLineLink;
1899 QTest::newRow(dataTag: "click on center aligned link")
1900 << singleLineText << 240.
1901 << "horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter"
1902 << (PointVector() << metrics.characterRectangle(position: 18, hAlign: Qt::AlignHCenter, vAlign: Qt::AlignVCenter).center())
1903 << singleLineLink
1904 << singleLineLink << singleLineLink;
1905 QTest::newRow(dataTag: "click on rich text link")
1906 << singleLineText << 240.
1907 << "textFormat: Text.RichText"
1908 << (PointVector() << metrics.characterRectangle(position: 18).center())
1909 << singleLineLink
1910 << singleLineLink << singleLineLink;
1911 QTest::newRow(dataTag: "click on rich text")
1912 << singleLineText << 240.
1913 << "textFormat: Text.RichText"
1914 << (PointVector() << metrics.characterRectangle(position: 13).center())
1915 << QString()
1916 << QString() << QString();
1917 QTest::newRow(dataTag: "click on bottom right aligned rich text link")
1918 << singleLineText << 240.
1919 << "textFormat: Text.RichText; horizontalAlignment: Text.AlignRight; verticalAlignment: Text.AlignBottom"
1920 << (PointVector() << metrics.characterRectangle(position: 18, hAlign: Qt::AlignRight, vAlign: Qt::AlignBottom).center())
1921 << singleLineLink
1922 << singleLineLink << singleLineLink;
1923 QTest::newRow(dataTag: "click on center aligned rich text link")
1924 << singleLineText << 240.
1925 << "textFormat: Text.RichText; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter"
1926 << (PointVector() << metrics.characterRectangle(position: 18, hAlign: Qt::AlignHCenter, vAlign: Qt::AlignVCenter).center())
1927 << singleLineLink
1928 << singleLineLink << singleLineLink;
1929 } {
1930 const TextMetrics metrics("this text has a li", Qt::ElideRight);
1931 QTest::newRow(dataTag: "click on right elided link")
1932 << singleLineText << metrics.width() + 2
1933 << "elide: Text.ElideRight"
1934 << (PointVector() << metrics.characterRectangle(position: 17).center())
1935 << singleLineLink
1936 << singleLineLink << singleLineLink;
1937 } {
1938 const TextMetrics metrics("ink in it", Qt::ElideLeft);
1939 QTest::newRow(dataTag: "click on left elided link")
1940 << singleLineText << metrics.width() + 2
1941 << "elide: Text.ElideLeft"
1942 << (PointVector() << metrics.characterRectangle(position: 2).center())
1943 << singleLineLink
1944 << singleLineLink << singleLineLink;
1945 } {
1946 const TextMetrics metrics("this text\nhas multiple\nlines in it");
1947 QTest::newRow(dataTag: "click on second line")
1948 << multipleLineText << 240.
1949 << ""
1950 << (PointVector() << metrics.characterRectangle(position: 18).center())
1951 << multipleLineLink
1952 << multipleLineLink << multipleLineLink;
1953 QTest::newRow(dataTag: "click on third line")
1954 << multipleLineText << 240.
1955 << ""
1956 << (PointVector() << metrics.characterRectangle(position: 25).center())
1957 << multipleLineLink
1958 << multipleLineLink << multipleLineLink;
1959 QTest::newRow(dataTag: "drag from second line to third")
1960 << multipleLineText << 240.
1961 << ""
1962 << (PointVector()
1963 << metrics.characterRectangle(position: 18).center()
1964 << metrics.characterRectangle(position: 25).center())
1965 << multipleLineLink
1966 << multipleLineLink << multipleLineLink;
1967 QTest::newRow(dataTag: "click on rich text second line")
1968 << multipleLineText << 240.
1969 << "textFormat: Text.RichText"
1970 << (PointVector() << metrics.characterRectangle(position: 18).center())
1971 << multipleLineLink
1972 << multipleLineLink << multipleLineLink;
1973 QTest::newRow(dataTag: "click on rich text third line")
1974 << multipleLineText << 240.
1975 << "textFormat: Text.RichText"
1976 << (PointVector() << metrics.characterRectangle(position: 25).center())
1977 << multipleLineLink
1978 << multipleLineLink << multipleLineLink;
1979 QTest::newRow(dataTag: "drag rich text from second line to third")
1980 << multipleLineText << 240.
1981 << "textFormat: Text.RichText"
1982 << (PointVector()
1983 << metrics.characterRectangle(position: 18).center()
1984 << metrics.characterRectangle(position: 25).center())
1985 << multipleLineLink
1986 << multipleLineLink << multipleLineLink;
1987 } {
1988 const TextMetrics metrics("this text has a nested link in it");
1989 QTest::newRow(dataTag: "click on left outer link")
1990 << nestedText << 240.
1991 << ""
1992 << (PointVector() << metrics.characterRectangle(position: 22).center())
1993 << outerLink
1994 << outerLink << outerLink;
1995 QTest::newRow(dataTag: "click on right outer link")
1996 << nestedText << 240.
1997 << ""
1998 << (PointVector() << metrics.characterRectangle(position: 27).center())
1999 << outerLink
2000 << outerLink << outerLink;
2001 QTest::newRow(dataTag: "click on inner link left")
2002 << nestedText << 240.
2003 << ""
2004 << (PointVector() << metrics.characterRectangle(position: 23).center())
2005 << innerLink
2006 << innerLink << innerLink;
2007 QTest::newRow(dataTag: "click on inner link right")
2008 << nestedText << 240.
2009 << ""
2010 << (PointVector() << metrics.characterRectangle(position: 26).center())
2011 << innerLink
2012 << innerLink << innerLink;
2013 QTest::newRow(dataTag: "drag from inner to outer link")
2014 << nestedText << 240.
2015 << ""
2016 << (PointVector()
2017 << metrics.characterRectangle(position: 25).center()
2018 << metrics.characterRectangle(position: 30).center())
2019 << QString()
2020 << innerLink << outerLink;
2021 QTest::newRow(dataTag: "drag from outer to inner link")
2022 << nestedText << 240.
2023 << ""
2024 << (PointVector()
2025 << metrics.characterRectangle(position: 30).center()
2026 << metrics.characterRectangle(position: 25).center())
2027 << QString()
2028 << outerLink << innerLink;
2029 QTest::newRow(dataTag: "click on left outer rich text link")
2030 << nestedText << 240.
2031 << "textFormat: Text.RichText"
2032 << (PointVector() << metrics.characterRectangle(position: 22).center())
2033 << outerLink
2034 << outerLink << outerLink;
2035 QTest::newRow(dataTag: "click on right outer rich text link")
2036 << nestedText << 240.
2037 << "textFormat: Text.RichText"
2038 << (PointVector() << metrics.characterRectangle(position: 27).center())
2039 << outerLink
2040 << outerLink << outerLink;
2041 QTest::newRow(dataTag: "click on inner rich text link left")
2042 << nestedText << 240.
2043 << "textFormat: Text.RichText"
2044 << (PointVector() << metrics.characterRectangle(position: 23).center())
2045 << innerLink
2046 << innerLink << innerLink;
2047 QTest::newRow(dataTag: "click on inner rich text link right")
2048 << nestedText << 240.
2049 << "textFormat: Text.RichText"
2050 << (PointVector() << metrics.characterRectangle(position: 26).center())
2051 << innerLink
2052 << innerLink << innerLink;
2053 QTest::newRow(dataTag: "drag from inner to outer rich text link")
2054 << nestedText << 240.
2055 << "textFormat: Text.RichText"
2056 << (PointVector()
2057 << metrics.characterRectangle(position: 25).center()
2058 << metrics.characterRectangle(position: 30).center())
2059 << QString()
2060 << innerLink << outerLink;
2061 QTest::newRow(dataTag: "drag from outer to inner rich text link")
2062 << nestedText << 240.
2063 << "textFormat: Text.RichText"
2064 << (PointVector()
2065 << metrics.characterRectangle(position: 30).center()
2066 << metrics.characterRectangle(position: 25).center())
2067 << QString()
2068 << outerLink << innerLink;
2069 }
2070}
2071
2072void tst_qquicktext::linkInteraction()
2073{
2074 QFETCH(QString, text);
2075 QFETCH(qreal, width);
2076 QFETCH(QString, bindings);
2077 QFETCH(PointVector, mousePositions);
2078 QFETCH(QString, clickedLink);
2079 QFETCH(QString, hoverEnterLink);
2080 QFETCH(QString, hoverMoveLink);
2081
2082 QString componentStr =
2083 "import QtQuick 2.2\nText {\n"
2084 "width: " + QString::number(width) + "\n"
2085 "height: 320\n"
2086 "text: \"" + text + "\"\n"
2087 "" + bindings + "\n"
2088 "}";
2089 QQmlComponent textComponent(&engine);
2090 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
2091 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
2092
2093 QVERIFY(textObject != nullptr);
2094
2095 LinkTest test;
2096 QObject::connect(sender: textObject, SIGNAL(linkActivated(QString)), receiver: &test, SLOT(linkClicked(QString)));
2097 QObject::connect(sender: textObject, SIGNAL(linkHovered(QString)), receiver: &test, SLOT(linkHovered(QString)));
2098
2099 QVERIFY(mousePositions.count() > 0);
2100
2101 QPointF mousePosition = mousePositions.first();
2102 {
2103 QHoverEvent he(QEvent::HoverEnter, mousePosition, QPointF());
2104 static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(event: &he);
2105
2106 QMouseEvent me(QEvent::MouseButtonPress, mousePosition, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
2107 static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(event: &me);
2108 }
2109
2110 QCOMPARE(test.hoveredLink, hoverEnterLink);
2111 QCOMPARE(textObject->hoveredLink(), hoverEnterLink);
2112 QCOMPARE(textObject->linkAt(mousePosition.x(), mousePosition.y()), hoverEnterLink);
2113
2114 for (int i = 1; i < mousePositions.count(); ++i) {
2115 mousePosition = mousePositions.at(i);
2116
2117 QHoverEvent he(QEvent::HoverMove, mousePosition, QPointF());
2118 static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(event: &he);
2119
2120 QMouseEvent me(QEvent::MouseMove, mousePosition, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
2121 static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(event: &me);
2122 }
2123
2124 QCOMPARE(test.hoveredLink, hoverMoveLink);
2125 QCOMPARE(textObject->hoveredLink(), hoverMoveLink);
2126 QCOMPARE(textObject->linkAt(mousePosition.x(), mousePosition.y()), hoverMoveLink);
2127
2128 {
2129 QHoverEvent he(QEvent::HoverLeave, mousePosition, QPointF());
2130 static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(event: &he);
2131
2132 QMouseEvent me(QEvent::MouseButtonRelease, mousePosition, Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
2133 static_cast<EventSender*>(static_cast<QQuickItem*>(textObject))->sendEvent(event: &me);
2134 }
2135
2136 QCOMPARE(test.clickedLink, clickedLink);
2137 QCOMPARE(test.hoveredLink, QString());
2138 QCOMPARE(textObject->hoveredLink(), QString());
2139 QCOMPARE(textObject->linkAt(-1, -1), QString());
2140
2141 delete textObject;
2142}
2143
2144void tst_qquicktext::baseUrl()
2145{
2146 QUrl localUrl("file:///tests/text.qml");
2147 QUrl remoteUrl("http://www.qt-project.org/test.qml");
2148
2149 QQmlComponent textComponent(&engine);
2150 textComponent.setData("import QtQuick 2.0\n Text {}", baseUrl: localUrl);
2151 QQuickText *textObject = qobject_cast<QQuickText *>(object: textComponent.create());
2152
2153 QCOMPARE(textObject->baseUrl(), localUrl);
2154
2155 QSignalSpy spy(textObject, SIGNAL(baseUrlChanged()));
2156
2157 textObject->setBaseUrl(localUrl);
2158 QCOMPARE(textObject->baseUrl(), localUrl);
2159 QCOMPARE(spy.count(), 0);
2160
2161 textObject->setBaseUrl(remoteUrl);
2162 QCOMPARE(textObject->baseUrl(), remoteUrl);
2163 QCOMPARE(spy.count(), 1);
2164
2165 textObject->resetBaseUrl();
2166 QCOMPARE(textObject->baseUrl(), localUrl);
2167 QCOMPARE(spy.count(), 2);
2168}
2169
2170void tst_qquicktext::embeddedImages_data()
2171{
2172 QTest::addColumn<QUrl>(name: "qmlfile");
2173 QTest::addColumn<QString>(name: "error");
2174 QTest::newRow(dataTag: "local") << testFileUrl(fileName: "embeddedImagesLocal.qml") << "";
2175 QTest::newRow(dataTag: "local-error") << testFileUrl(fileName: "embeddedImagesLocalError.qml")
2176 << testFileUrl(fileName: "embeddedImagesLocalError.qml").toString()+":3:1: QML Text: Cannot open: " + testFileUrl(fileName: "http/notexists.png").toString();
2177 QTest::newRow(dataTag: "local") << testFileUrl(fileName: "embeddedImagesLocalRelative.qml") << "";
2178 QTest::newRow(dataTag: "remote") << testFileUrl(fileName: "embeddedImagesRemote.qml") << "";
2179 QTest::newRow(dataTag: "remote-error") << testFileUrl(fileName: "embeddedImagesRemoteError.qml")
2180 << testFileUrl(fileName: "embeddedImagesRemoteError.qml").toString()+":3:1: QML Text: Error transferring {{ServerBaseUrl}}/notexists.png - server replied: Not found";
2181 QTest::newRow(dataTag: "remote-relative") << testFileUrl(fileName: "embeddedImagesRemoteRelative.qml") << "";
2182}
2183
2184void tst_qquicktext::embeddedImages()
2185{
2186 // Tests QTBUG-9900
2187
2188 QFETCH(QUrl, qmlfile);
2189 QFETCH(QString, error);
2190
2191#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
2192 if (qstrcmp(str1: QTest::currentDataTag(), str2: "remote") == 0
2193 || qstrcmp(str1: QTest::currentDataTag(), str2: "remote-error") == 0
2194 || qstrcmp(str1: QTest::currentDataTag(), str2: "remote-relative") == 0) {
2195 QSKIP("Remote tests cause occasional hangs in the CI system -- QTBUG-45655");
2196 }
2197#endif
2198
2199 TestHTTPServer server;
2200 QVERIFY2(server.listen(), qPrintable(server.errorString()));
2201 server.serveDirectory(testFile(fileName: "http"));
2202 error.replace(QStringLiteral("{{ServerBaseUrl}}"), after: server.baseUrl().toString());
2203
2204 if (!error.isEmpty())
2205 QTest::ignoreMessage(type: QtWarningMsg, message: error.toLatin1());
2206
2207 QQuickView *view = new QQuickView;
2208 view->rootContext()->setContextProperty(QStringLiteral("serverBaseUrl"), server.baseUrl());
2209 view->setSource(qmlfile);
2210 view->show();
2211 view->requestActivate();
2212 QVERIFY(QTest::qWaitForWindowActive(view));
2213 QQuickText *textObject = qobject_cast<QQuickText*>(object: view->rootObject());
2214
2215 QVERIFY(textObject != nullptr);
2216 QTRY_COMPARE(textObject->resourcesLoading(), 0);
2217
2218 QPixmap pm(testFile(fileName: "http/exists.png"));
2219 if (error.isEmpty()) {
2220 QCOMPARE(textObject->width(), double(pm.width()));
2221 QCOMPARE(textObject->height(), double(pm.height()));
2222 } else {
2223 QVERIFY(16 != pm.width()); // check test is effective
2224 QCOMPARE(textObject->width(), 16.0); // default size of QTextDocument broken image icon
2225 QCOMPARE(textObject->height(), 16.0);
2226 }
2227
2228 delete view;
2229}
2230
2231void tst_qquicktext::lineCount()
2232{
2233 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "lineCount.qml")));
2234
2235 QQuickText *myText = window->rootObject()->findChild<QQuickText*>(aName: "myText");
2236 QVERIFY(myText != nullptr);
2237
2238 QVERIFY(myText->lineCount() > 1);
2239 QVERIFY(!myText->truncated());
2240 QCOMPARE(myText->maximumLineCount(), INT_MAX);
2241
2242 myText->setMaximumLineCount(2);
2243 QCOMPARE(myText->lineCount(), 2);
2244 QCOMPARE(myText->truncated(), true);
2245 QCOMPARE(myText->maximumLineCount(), 2);
2246
2247 myText->resetMaximumLineCount();
2248 QCOMPARE(myText->maximumLineCount(), INT_MAX);
2249 QCOMPARE(myText->truncated(), false);
2250
2251 myText->setElideMode(QQuickText::ElideRight);
2252 myText->setMaximumLineCount(2);
2253 QCOMPARE(myText->lineCount(), 2);
2254 QCOMPARE(myText->truncated(), true);
2255 QCOMPARE(myText->maximumLineCount(), 2);
2256
2257 // QTBUG-84458
2258 myText->resetMaximumLineCount();
2259 myText->setText("qqqqq\nqqqqq");
2260 QCOMPARE(myText->lineCount(), 2);
2261 myText->setText("qqqqq\nqqqqq\nqqqqq");
2262 QCOMPARE(myText->lineCount(), 3);
2263 myText->setText("");
2264 QCOMPARE(myText->lineCount(), 1);
2265
2266 myText->setText("qqqqq\nqqqqq\nqqqqq");
2267 QCOMPARE(myText->lineCount(), 3);
2268 myText->setFontSizeMode(QQuickText::HorizontalFit);
2269 myText->setText("");
2270 QCOMPARE(myText->lineCount(), 1);
2271
2272 myText->setText("qqqqq\nqqqqq\nqqqqq");
2273 QCOMPARE(myText->lineCount(), 3);
2274 myText->setFontSizeMode(QQuickText::VerticalFit);
2275 myText->setText("");
2276 QCOMPARE(myText->lineCount(), 1);
2277
2278 myText->setText("qqqqq\nqqqqq\nqqqqq");
2279 QCOMPARE(myText->lineCount(), 3);
2280 myText->setFontSizeMode(QQuickText::Fit);
2281 myText->setText("");
2282 QCOMPARE(myText->lineCount(), 1);
2283
2284 QScopedPointer<QQuickView> layoutWindow(createView(filename: testFile(fileName: "lineLayoutHAlign.qml")));
2285 QQuickText *lineLaidOut = layoutWindow->rootObject()->findChild<QQuickText*>(aName: "myText");
2286 QVERIFY(lineLaidOut != nullptr);
2287
2288 lineLaidOut->setText("qqqqq\nqqqqq\nqqqqq");
2289 QCOMPARE(lineLaidOut->lineCount(), 3);
2290 lineLaidOut->setFontSizeMode(QQuickText::FixedSize);
2291 lineLaidOut->setText("");
2292 QCOMPARE(lineLaidOut->lineCount(), 1);
2293}
2294
2295void tst_qquicktext::lineHeight()
2296{
2297 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "lineHeight.qml")));
2298
2299 QQuickText *myText = window->rootObject()->findChild<QQuickText*>(aName: "myText");
2300 QVERIFY(myText != nullptr);
2301
2302 QCOMPARE(myText->lineHeight(), qreal(1));
2303 QCOMPARE(myText->lineHeightMode(), QQuickText::ProportionalHeight);
2304
2305 qreal h = myText->height();
2306 myText->setLineHeight(1.5);
2307 QCOMPARE(myText->height(), qreal(qCeil(h)) * 1.5);
2308
2309 myText->setLineHeightMode(QQuickText::FixedHeight);
2310 myText->setLineHeight(20);
2311 QCOMPARE(myText->height(), myText->lineCount() * 20.0);
2312
2313 myText->setText("Lorem ipsum sit <b>amet</b>, consectetur adipiscing elit. Integer felis nisl, varius in pretium nec, venenatis non erat. Proin lobortis interdum dictum.");
2314 myText->setLineHeightMode(QQuickText::ProportionalHeight);
2315 myText->setLineHeight(1.0);
2316
2317 qreal h2 = myText->height();
2318 myText->setLineHeight(2.0);
2319 QVERIFY(myText->height() == h2 * 2.0);
2320
2321 myText->setLineHeightMode(QQuickText::FixedHeight);
2322 myText->setLineHeight(10);
2323 QCOMPARE(myText->height(), myText->lineCount() * 10.0);
2324}
2325
2326void tst_qquicktext::implicitSize_data()
2327{
2328 QTest::addColumn<QString>(name: "text");
2329 QTest::addColumn<QString>(name: "width");
2330 QTest::addColumn<QString>(name: "wrap");
2331 QTest::addColumn<QString>(name: "elide");
2332 QTest::addColumn<QString>(name: "format");
2333 QTest::newRow(dataTag: "plain") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.NoWrap" << "Text.ElideNone" << "Text.PlainText";
2334 QTest::newRow(dataTag: "richtext") << "<b>The quick red fox jumped over the lazy brown dog</b>" <<" 50" << "Text.NoWrap" << "Text.ElideNone" << "Text.RichText";
2335 QTest::newRow(dataTag: "styledtext") << "<b>The quick red fox jumped over the lazy brown dog</b>" <<" 50" << "Text.NoWrap" << "Text.ElideNone" << "Text.StyledText";
2336 QTest::newRow(dataTag: "plain, 0 width") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.NoWrap" << "Text.ElideNone" << "Text.PlainText";
2337 QTest::newRow(dataTag: "plain, elide") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.NoWrap" << "Text.ElideRight" << "Text.PlainText";
2338 QTest::newRow(dataTag: "plain, 0 width, elide") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.NoWrap" << "Text.ElideRight" << "Text.PlainText";
2339 QTest::newRow(dataTag: "richtext, 0 width") << "<b>The quick red fox jumped over the lazy brown dog</b>" <<" 0" << "Text.NoWrap" << "Text.ElideNone" << "Text.RichText";
2340 QTest::newRow(dataTag: "styledtext, 0 width") << "<b>The quick red fox jumped over the lazy brown dog</b>" <<" 0" << "Text.NoWrap" << "Text.ElideNone" << "Text.StyledText";
2341 QTest::newRow(dataTag: "plain_wrap") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.Wrap" << "Text.ElideNone" << "Text.PlainText";
2342 QTest::newRow(dataTag: "richtext_wrap") << "<b>The quick red fox jumped over the lazy brown dog</b>" << "50" << "Text.Wrap" << "Text.ElideNone" << "Text.RichText";
2343 QTest::newRow(dataTag: "styledtext_wrap") << "<b>The quick red fox jumped over the lazy brown dog</b>" << "50" << "Text.Wrap" << "Text.ElideNone" << "Text.StyledText";
2344 QTest::newRow(dataTag: "plain_wrap, 0 width") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.Wrap" << "Text.ElideNone" << "Text.PlainText";
2345 QTest::newRow(dataTag: "plain_wrap, elide") << "The quick red fox jumped over the lazy brown dog" << "50" << "Text.Wrap" << "Text.ElideRight" << "Text.PlainText";
2346 QTest::newRow(dataTag: "plain_wrap, 0 width, elide") << "The quick red fox jumped over the lazy brown dog" << "0" << "Text.Wrap" << "Text.ElideRight" << "Text.PlainText";
2347 QTest::newRow(dataTag: "richtext_wrap, 0 width") << "<b>The quick red fox jumped over the lazy brown dog</b>" << "0" << "Text.Wrap" << "Text.ElideNone" << "Text.RichText";
2348 QTest::newRow(dataTag: "styledtext_wrap, 0 width") << "<b>The quick red fox jumped over the lazy brown dog</b>" << "0" << "Text.Wrap" << "Text.ElideNone" << "Text.StyledText";
2349}
2350
2351void tst_qquicktext::implicitSize()
2352{
2353 QFETCH(QString, text);
2354 QFETCH(QString, width);
2355 QFETCH(QString, format);
2356 QFETCH(QString, wrap);
2357 QFETCH(QString, elide);
2358 QString componentStr = "import QtQuick 2.0\nText { "
2359 "property real iWidth: implicitWidth; "
2360 "text: \"" + text + "\"; "
2361 "width: " + width + "; "
2362 "textFormat: " + format + "; "
2363 "wrapMode: " + wrap + "; "
2364 "elide: " + elide + "; "
2365 "maximumLineCount: 2 }";
2366 QQmlComponent textComponent(&engine);
2367 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
2368 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
2369
2370 QVERIFY(textObject->width() < textObject->implicitWidth());
2371 QCOMPARE(textObject->height(), textObject->implicitHeight());
2372 QCOMPARE(textObject->property("iWidth").toReal(), textObject->implicitWidth());
2373
2374 textObject->resetWidth();
2375 QCOMPARE(textObject->width(), textObject->implicitWidth());
2376 QCOMPARE(textObject->height(), textObject->implicitHeight());
2377
2378 delete textObject;
2379}
2380
2381void tst_qquicktext::dependentImplicitSizes()
2382{
2383 QQmlComponent component(&engine, testFile(fileName: "implicitSizes.qml"));
2384 QScopedPointer<QObject> object(component.create());
2385 QVERIFY(object.data());
2386
2387 QQuickText *reference = object->findChild<QQuickText *>(aName: "reference");
2388 QQuickText *fixedWidthAndHeight = object->findChild<QQuickText *>(aName: "fixedWidthAndHeight");
2389 QQuickText *implicitWidthFixedHeight = object->findChild<QQuickText *>(aName: "implicitWidthFixedHeight");
2390 QQuickText *fixedWidthImplicitHeight = object->findChild<QQuickText *>(aName: "fixedWidthImplicitHeight");
2391 QQuickText *cappedWidthAndHeight = object->findChild<QQuickText *>(aName: "cappedWidthAndHeight");
2392 QQuickText *cappedWidthFixedHeight = object->findChild<QQuickText *>(aName: "cappedWidthFixedHeight");
2393 QQuickText *fixedWidthCappedHeight = object->findChild<QQuickText *>(aName: "fixedWidthCappedHeight");
2394
2395 QVERIFY(reference);
2396 QVERIFY(fixedWidthAndHeight);
2397 QVERIFY(implicitWidthFixedHeight);
2398 QVERIFY(fixedWidthImplicitHeight);
2399 QVERIFY(cappedWidthAndHeight);
2400 QVERIFY(cappedWidthFixedHeight);
2401 QVERIFY(fixedWidthCappedHeight);
2402
2403 QCOMPARE(reference->width(), reference->implicitWidth());
2404 QCOMPARE(reference->height(), reference->implicitHeight());
2405
2406 QVERIFY(fixedWidthAndHeight->width() < fixedWidthAndHeight->implicitWidth());
2407 QVERIFY(fixedWidthAndHeight->height() < fixedWidthAndHeight->implicitHeight());
2408 QCOMPARE(fixedWidthAndHeight->implicitWidth(), reference->implicitWidth());
2409 QVERIFY(fixedWidthAndHeight->implicitHeight() > reference->implicitHeight());
2410
2411 QCOMPARE(implicitWidthFixedHeight->width(), implicitWidthFixedHeight->implicitWidth());
2412 QVERIFY(implicitWidthFixedHeight->height() < implicitWidthFixedHeight->implicitHeight());
2413 QCOMPARE(implicitWidthFixedHeight->implicitWidth(), reference->implicitWidth());
2414 QCOMPARE(implicitWidthFixedHeight->implicitHeight(), reference->implicitHeight());
2415
2416 QVERIFY(fixedWidthImplicitHeight->width() < fixedWidthImplicitHeight->implicitWidth());
2417 QCOMPARE(fixedWidthImplicitHeight->height(), fixedWidthImplicitHeight->implicitHeight());
2418 QCOMPARE(fixedWidthImplicitHeight->implicitWidth(), reference->implicitWidth());
2419 QCOMPARE(fixedWidthImplicitHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight());
2420
2421 QVERIFY(cappedWidthAndHeight->width() < cappedWidthAndHeight->implicitWidth());
2422 QVERIFY(cappedWidthAndHeight->height() < cappedWidthAndHeight->implicitHeight());
2423 QCOMPARE(cappedWidthAndHeight->implicitWidth(), reference->implicitWidth());
2424 QCOMPARE(cappedWidthAndHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight());
2425
2426 QVERIFY(cappedWidthFixedHeight->width() < cappedWidthAndHeight->implicitWidth());
2427 QVERIFY(cappedWidthFixedHeight->height() < cappedWidthFixedHeight->implicitHeight());
2428 QCOMPARE(cappedWidthFixedHeight->implicitWidth(), reference->implicitWidth());
2429 QCOMPARE(cappedWidthFixedHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight());
2430
2431 QVERIFY(fixedWidthCappedHeight->width() < fixedWidthCappedHeight->implicitWidth());
2432 QVERIFY(fixedWidthCappedHeight->height() < fixedWidthCappedHeight->implicitHeight());
2433 QCOMPARE(fixedWidthCappedHeight->implicitWidth(), reference->implicitWidth());
2434 QCOMPARE(fixedWidthCappedHeight->implicitHeight(), fixedWidthAndHeight->implicitHeight());
2435}
2436
2437void tst_qquicktext::contentSize()
2438{
2439 QString componentStr = "import QtQuick 2.0\nText { width: 75; height: 16; font.pixelSize: 10 }";
2440 QQmlComponent textComponent(&engine);
2441 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
2442 QScopedPointer<QObject> object(textComponent.create());
2443 QQuickText *textObject = qobject_cast<QQuickText *>(object: object.data());
2444
2445 QSignalSpy spySize(textObject, SIGNAL(contentSizeChanged()));
2446 QSignalSpy spyWidth(textObject, SIGNAL(contentWidthChanged(qreal)));
2447 QSignalSpy spyHeight(textObject, SIGNAL(contentHeightChanged(qreal)));
2448
2449 textObject->setText("The quick red fox jumped over the lazy brown dog");
2450
2451 QVERIFY(textObject->contentWidth() > textObject->width());
2452 QVERIFY(textObject->contentHeight() < textObject->height());
2453 QCOMPARE(spySize.count(), 1);
2454 QCOMPARE(spyWidth.count(), 1);
2455 QCOMPARE(spyHeight.count(), 0);
2456
2457 textObject->setWrapMode(QQuickText::WordWrap);
2458 QVERIFY(textObject->contentWidth() <= textObject->width());
2459 QVERIFY(textObject->contentHeight() > textObject->height());
2460 QCOMPARE(spySize.count(), 2);
2461 QCOMPARE(spyWidth.count(), 2);
2462 QCOMPARE(spyHeight.count(), 1);
2463
2464 textObject->setElideMode(QQuickText::ElideRight);
2465 QVERIFY(textObject->contentWidth() <= textObject->width());
2466 QVERIFY(textObject->contentHeight() < textObject->height());
2467 QCOMPARE(spySize.count(), 3);
2468 QCOMPARE(spyWidth.count(), 3);
2469 QCOMPARE(spyHeight.count(), 2);
2470 int spyCount = 3;
2471 qreal elidedWidth = textObject->contentWidth();
2472
2473 textObject->setText("The quickredfoxjumpedoverthe lazy brown dog");
2474 QVERIFY(textObject->contentWidth() <= textObject->width());
2475 QVERIFY(textObject->contentHeight() < textObject->height());
2476 // this text probably won't have the same elided width, but it's not guaranteed.
2477 if (textObject->contentWidth() != elidedWidth)
2478 QCOMPARE(spySize.count(), ++spyCount);
2479 else
2480 QCOMPARE(spySize.count(), spyCount);
2481
2482 textObject->setElideMode(QQuickText::ElideNone);
2483 QVERIFY(textObject->contentWidth() > textObject->width());
2484 QVERIFY(textObject->contentHeight() > textObject->height());
2485 QCOMPARE(spySize.count(), ++spyCount);
2486 QCOMPARE(spyWidth.count(), spyCount);
2487 QCOMPARE(spyHeight.count(), 3);
2488}
2489
2490void tst_qquicktext::geometryChanged()
2491{
2492 // Test that text is re-laid out when the geometry of the item by verifying changes in content
2493 // size. Implicit width is also tested as that in combination with item geometry provides a
2494 // reference for expected content sizes.
2495
2496 QString componentStr = "import QtQuick 2.0\nText { font.family: \"__Qt__Box__Engine__\"; font.pixelSize: 10 }";
2497 QQmlComponent textComponent(&engine);
2498 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
2499 QScopedPointer<QObject> object(textComponent.create());
2500 QQuickText *textObject = qobject_cast<QQuickText *>(object: object.data());
2501
2502 const qreal implicitHeight = textObject->implicitHeight();
2503
2504 const qreal widths[] = { 100, 2000, 3000, -100, 100 };
2505 const qreal heights[] = { implicitHeight, 2000, 3000, -implicitHeight, implicitHeight };
2506
2507 QCOMPARE(textObject->implicitWidth(), 0.);
2508 QVERIFY(implicitHeight > 0.);
2509 QCOMPARE(textObject->width(), textObject->implicitWidth());
2510 QCOMPARE(textObject->height(), implicitHeight);
2511 QCOMPARE(textObject->contentWidth(), textObject->implicitWidth());
2512 QCOMPARE(textObject->contentHeight(), implicitHeight);
2513
2514 textObject->setText("The quick red fox jumped over the lazy brown dog");
2515
2516 const qreal implicitWidth = textObject->implicitWidth();
2517
2518 QVERIFY(implicitWidth > 0.);
2519 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2520 QCOMPARE(textObject->width(), textObject->implicitWidth());
2521 QCOMPARE(textObject->height(), textObject->implicitHeight());
2522 QCOMPARE(textObject->contentWidth(), textObject->implicitWidth());
2523 QCOMPARE(textObject->contentHeight(), textObject->implicitHeight());
2524
2525 // Changing the geometry with no eliding, or wrapping doesn't change the content size.
2526 for (int i = 0; i < 5; ++i) {
2527 textObject->setWidth(widths[i]);
2528 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2529 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2530 QCOMPARE(textObject->width(), widths[i]);
2531 QCOMPARE(textObject->height(), implicitHeight);
2532 QCOMPARE(textObject->contentWidth(), implicitWidth);
2533 QCOMPARE(textObject->contentHeight(), implicitHeight);
2534 }
2535
2536 // With eliding enabled the content width is bounded to the item width, but is never
2537 // larger than the implicit width.
2538 textObject->setElideMode(QQuickText::ElideRight);
2539 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2540 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2541 QCOMPARE(textObject->width(), 100.);
2542 QCOMPARE(textObject->height(), implicitHeight);
2543 QVERIFY(textObject->contentWidth() <= 100.);
2544 QCOMPARE(textObject->contentHeight(), implicitHeight);
2545
2546 textObject->setWidth(2000.);
2547 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2548 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2549 QCOMPARE(textObject->width(), 2000.);
2550 QCOMPARE(textObject->height(), implicitHeight);
2551 QCOMPARE(textObject->contentWidth(), implicitWidth);
2552 QCOMPARE(textObject->contentHeight(), implicitHeight);
2553
2554 textObject->setWidth(3000.);
2555 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2556 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2557 QCOMPARE(textObject->width(), 3000.);
2558 QCOMPARE(textObject->height(), implicitHeight);
2559 QCOMPARE(textObject->contentWidth(), implicitWidth);
2560 QCOMPARE(textObject->contentHeight(), implicitHeight);
2561
2562 textObject->setWidth(-100);
2563 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2564 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2565 QCOMPARE(textObject->width(), -100.);
2566 QCOMPARE(textObject->height(), implicitHeight);
2567 QCOMPARE(textObject->contentWidth(), 0.);
2568 QCOMPARE(textObject->contentHeight(), implicitHeight);
2569
2570 textObject->setWidth(100.);
2571 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2572 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2573 QCOMPARE(textObject->width(), 100.);
2574 QCOMPARE(textObject->height(), implicitHeight);
2575 QVERIFY(textObject->contentWidth() <= 100.);
2576 QCOMPARE(textObject->contentHeight(), implicitHeight);
2577
2578 // With wrapping enabled the implicit height changes with the width.
2579 textObject->setElideMode(QQuickText::ElideNone);
2580 textObject->setWrapMode(QQuickText::Wrap);
2581 const qreal wrappedImplicitHeight = textObject->implicitHeight();
2582
2583 QVERIFY(wrappedImplicitHeight > implicitHeight);
2584
2585 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2586 QCOMPARE(textObject->width(), 100.);
2587 QCOMPARE(textObject->height(), wrappedImplicitHeight);
2588 QVERIFY(textObject->contentWidth() <= 100.);
2589 QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight);
2590
2591 textObject->setWidth(2000.);
2592 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2593 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2594 QCOMPARE(textObject->width(), 2000.);
2595 QCOMPARE(textObject->height(), implicitHeight);
2596 QCOMPARE(textObject->contentWidth(), implicitWidth);
2597 QCOMPARE(textObject->contentHeight(), implicitHeight);
2598
2599 textObject->setWidth(3000.);
2600 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2601 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2602 QCOMPARE(textObject->width(), 3000.);
2603 QCOMPARE(textObject->height(), implicitHeight);
2604 QCOMPARE(textObject->contentWidth(), implicitWidth);
2605 QCOMPARE(textObject->contentHeight(), implicitHeight);
2606
2607 textObject->setWidth(-100);
2608 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2609 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2610 QCOMPARE(textObject->width(), -100.);
2611 QCOMPARE(textObject->height(), implicitHeight);
2612 QCOMPARE(textObject->contentWidth(), implicitWidth); // 0 or negative width item won't wrap.
2613 QCOMPARE(textObject->contentHeight(), implicitHeight);
2614
2615 textObject->setWidth(100.);
2616 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2617 QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
2618 QCOMPARE(textObject->width(), 100.);
2619 QCOMPARE(textObject->height(), wrappedImplicitHeight);
2620 QVERIFY(textObject->contentWidth() <= 100.);
2621 QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight);
2622
2623 // With no eliding or maximum line count the content height is the same as the implicit height.
2624 for (int i = 0; i < 5; ++i) {
2625 textObject->setHeight(heights[i]);
2626 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2627 QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
2628 QCOMPARE(textObject->width(), 100.);
2629 QCOMPARE(textObject->height(), heights[i]);
2630 QVERIFY(textObject->contentWidth() <= 100.);
2631 QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight);
2632 }
2633
2634 // The implicit height is unaffected by eliding but the content height will change.
2635 textObject->setElideMode(QQuickText::ElideRight);
2636
2637 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2638 QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
2639 QCOMPARE(textObject->width(), 100.);
2640 QCOMPARE(textObject->height(), implicitHeight);
2641 QVERIFY(textObject->contentWidth() <= 100.);
2642 QCOMPARE(textObject->contentHeight(), implicitHeight);
2643
2644 textObject->setHeight(2000);
2645 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2646 QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
2647 QCOMPARE(textObject->width(), 100.);
2648 QCOMPARE(textObject->height(), 2000.);
2649 QVERIFY(textObject->contentWidth() <= 100.);
2650 QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight);
2651
2652 textObject->setHeight(3000);
2653 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2654 QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
2655 QCOMPARE(textObject->width(), 100.);
2656 QCOMPARE(textObject->height(), 3000.);
2657 QVERIFY(textObject->contentWidth() <= 100.);
2658 QCOMPARE(textObject->contentHeight(), wrappedImplicitHeight);
2659
2660 textObject->setHeight(-implicitHeight);
2661 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2662 QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
2663 QCOMPARE(textObject->width(), 100.);
2664 QCOMPARE(textObject->height(), -implicitHeight);
2665 QVERIFY(textObject->contentWidth() <= 0.);
2666 QCOMPARE(textObject->contentHeight(), implicitHeight); // content height is never less than font height. seems a little odd in this instance.
2667
2668 textObject->setHeight(implicitHeight);
2669 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2670 QCOMPARE(textObject->implicitHeight(), wrappedImplicitHeight);
2671 QCOMPARE(textObject->width(), 100.);
2672 QCOMPARE(textObject->height(), implicitHeight);
2673 QVERIFY(textObject->contentWidth() <= 100.);
2674 QCOMPARE(textObject->contentHeight(), implicitHeight);
2675
2676 // Varying the height with a maximum line count but no eliding won't affect the content height.
2677 textObject->setElideMode(QQuickText::ElideNone);
2678 textObject->setMaximumLineCount(2);
2679 textObject->resetHeight();
2680
2681 const qreal maxLineCountImplicitHeight = textObject->implicitHeight();
2682 QVERIFY(maxLineCountImplicitHeight > implicitHeight);
2683 QVERIFY(maxLineCountImplicitHeight < wrappedImplicitHeight);
2684
2685 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2686 QCOMPARE(textObject->width(), 100.);
2687 QCOMPARE(textObject->height(), maxLineCountImplicitHeight);
2688 QVERIFY(textObject->contentWidth() <= 100.);
2689 QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight);
2690
2691 for (int i = 0; i < 5; ++i) {
2692 textObject->setHeight(heights[i]);
2693 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2694 QCOMPARE(textObject->implicitHeight(), maxLineCountImplicitHeight);
2695 QCOMPARE(textObject->width(), 100.);
2696 QCOMPARE(textObject->height(), heights[i]);
2697 QVERIFY(textObject->contentWidth() <= 100.);
2698 QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight);
2699 }
2700
2701 // Varying the width with a maximum line count won't increase the implicit height beyond the
2702 // height of the maximum number of lines.
2703 textObject->setWidth(2000.);
2704 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2705 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2706 QCOMPARE(textObject->width(), 2000.);
2707 QCOMPARE(textObject->height(), implicitHeight);
2708 QCOMPARE(textObject->contentWidth(), implicitWidth);
2709 QCOMPARE(textObject->contentHeight(), implicitHeight);
2710
2711 textObject->setWidth(3000.);
2712 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2713 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2714 QCOMPARE(textObject->width(), 3000.);
2715 QCOMPARE(textObject->height(), implicitHeight);
2716 QCOMPARE(textObject->contentWidth(), implicitWidth);
2717 QCOMPARE(textObject->contentHeight(), implicitHeight);
2718
2719 textObject->setWidth(-100);
2720 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2721 QCOMPARE(textObject->implicitHeight(), implicitHeight);
2722 QCOMPARE(textObject->width(), -100.);
2723 QCOMPARE(textObject->height(), implicitHeight);
2724 QCOMPARE(textObject->contentWidth(), implicitWidth); // 0 or negative width item won't wrap.
2725 QCOMPARE(textObject->contentHeight(), implicitHeight);
2726
2727 textObject->setWidth(50.);
2728 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2729 QCOMPARE(textObject->implicitHeight(), maxLineCountImplicitHeight);
2730 QCOMPARE(textObject->width(), 50.);
2731 QCOMPARE(textObject->height(), implicitHeight);
2732 QVERIFY(textObject->contentWidth() <= 50.);
2733 QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight);
2734
2735 textObject->setWidth(100.);
2736 QCOMPARE(textObject->implicitWidth(), implicitWidth);
2737 QCOMPARE(textObject->implicitHeight(), maxLineCountImplicitHeight);
2738 QCOMPARE(textObject->width(), 100.);
2739 QCOMPARE(textObject->height(), implicitHeight);
2740 QVERIFY(textObject->contentWidth() <= 100.);
2741 QCOMPARE(textObject->contentHeight(), maxLineCountImplicitHeight);
2742}
2743
2744void tst_qquicktext::implicitSizeBinding_data()
2745{
2746 implicitSize_data();
2747}
2748
2749void tst_qquicktext::implicitSizeBinding()
2750{
2751 QFETCH(QString, text);
2752 QFETCH(QString, wrap);
2753 QFETCH(QString, format);
2754 QString componentStr = "import QtQuick 2.0\nText { text: \"" + text + "\"; width: implicitWidth; height: implicitHeight; wrapMode: " + wrap + "; textFormat: " + format + " }";
2755
2756 QQmlComponent textComponent(&engine);
2757 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
2758 QScopedPointer<QObject> object(textComponent.create());
2759 QQuickText *textObject = qobject_cast<QQuickText *>(object: object.data());
2760
2761 QCOMPARE(textObject->width(), textObject->implicitWidth());
2762 QCOMPARE(textObject->height(), textObject->implicitHeight());
2763
2764 textObject->resetWidth();
2765 QCOMPARE(textObject->width(), textObject->implicitWidth());
2766 QCOMPARE(textObject->height(), textObject->implicitHeight());
2767
2768 textObject->resetHeight();
2769 QCOMPARE(textObject->width(), textObject->implicitWidth());
2770 QCOMPARE(textObject->height(), textObject->implicitHeight());
2771}
2772
2773void tst_qquicktext::boundingRect_data()
2774{
2775 QTest::addColumn<QString>(name: "format");
2776 QTest::newRow(dataTag: "PlainText") << "Text.PlainText";
2777 QTest::newRow(dataTag: "StyledText") << "Text.StyledText";
2778 QTest::newRow(dataTag: "RichText") << "Text.RichText";
2779}
2780
2781void tst_qquicktext::boundingRect()
2782{
2783 QFETCH(QString, format);
2784
2785 QQmlComponent component(&engine);
2786 component.setData("import QtQuick 2.0\n Text { textFormat:" + format.toUtf8() + "}", baseUrl: QUrl());
2787 QScopedPointer<QObject> object(component.create());
2788 QQuickText *text = qobject_cast<QQuickText *>(object: object.data());
2789 QVERIFY(text);
2790
2791 QCOMPARE(text->boundingRect().x(), qreal(0));
2792 QCOMPARE(text->boundingRect().y(), qreal(0));
2793 QCOMPARE(text->boundingRect().width(), qreal(0));
2794 QCOMPARE(text->boundingRect().height(), qreal(qCeil(QFontMetricsF(text->font()).height())));
2795
2796 text->setText("Hello World");
2797
2798 QTextLayout layout(text->text());
2799 layout.setFont(text->font());
2800
2801 if (!qmlDisableDistanceField()) {
2802 QTextOption option;
2803 option.setUseDesignMetrics(true);
2804 layout.setTextOption(option);
2805 }
2806 layout.beginLayout();
2807 QTextLine line = layout.createLine();
2808 layout.endLayout();
2809
2810 QCOMPARE(text->boundingRect().x(), qreal(0));
2811 QCOMPARE(text->boundingRect().y(), qreal(0));
2812 QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
2813 QCOMPARE(text->boundingRect().height(), line.height());
2814
2815 // the size of the bounding rect shouldn't be bounded by the size of item.
2816 text->setWidth(text->width() / 2);
2817 QCOMPARE(text->boundingRect().x(), qreal(0));
2818 QCOMPARE(text->boundingRect().y(), qreal(0));
2819 QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
2820 QCOMPARE(text->boundingRect().height(), line.height());
2821
2822 text->setHeight(text->height() * 2);
2823 QCOMPARE(text->boundingRect().x(), qreal(0));
2824 QCOMPARE(text->boundingRect().y(), qreal(0));
2825 QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
2826 QCOMPARE(text->boundingRect().height(), line.height());
2827
2828 text->setHAlign(QQuickText::AlignRight);
2829 QCOMPARE(text->boundingRect().x(), text->width() - line.naturalTextWidth());
2830 QCOMPARE(text->boundingRect().y(), qreal(0));
2831 QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
2832 QCOMPARE(text->boundingRect().height(), line.height());
2833
2834 QQuickItemPrivate::get(item: text)->setLayoutMirror(true);
2835 QCOMPARE(text->boundingRect().x(), qreal(0));
2836 QCOMPARE(text->boundingRect().y(), qreal(0));
2837 QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
2838 QCOMPARE(text->boundingRect().height(), line.height());
2839
2840 text->setHAlign(QQuickText::AlignLeft);
2841 QCOMPARE(text->boundingRect().x(), text->width() - line.naturalTextWidth());
2842 QCOMPARE(text->boundingRect().y(), qreal(0));
2843 QCOMPARE(text->boundingRect().width(), line.naturalTextWidth());
2844 QCOMPARE(text->boundingRect().height(), line.height());
2845
2846 text->setWrapMode(QQuickText::Wrap);
2847 QCOMPARE(text->boundingRect().right(), text->width());
2848 QCOMPARE(text->boundingRect().y(), qreal(0));
2849 QVERIFY(text->boundingRect().width() < line.naturalTextWidth());
2850 QVERIFY(text->boundingRect().height() > line.height());
2851
2852 text->setVAlign(QQuickText::AlignBottom);
2853 QCOMPARE(text->boundingRect().right(), text->width());
2854 QCOMPARE(text->boundingRect().bottom(), text->height());
2855 QVERIFY(text->boundingRect().width() < line.naturalTextWidth());
2856 QVERIFY(text->boundingRect().height() > line.height());
2857}
2858
2859void tst_qquicktext::clipRect()
2860{
2861 QQmlComponent component(&engine);
2862 component.setData("import QtQuick 2.0\n Text {}", baseUrl: QUrl());
2863 QScopedPointer<QObject> object(component.create());
2864 QQuickText *text = qobject_cast<QQuickText *>(object: object.data());
2865 QVERIFY(text);
2866
2867 QTextLayout layout;
2868 layout.setFont(text->font());
2869
2870 QCOMPARE(text->clipRect().x(), qreal(0));
2871 QCOMPARE(text->clipRect().y(), qreal(0));
2872 QCOMPARE(text->clipRect().width(), text->width());
2873 QCOMPARE(text->clipRect().height(), text->height());
2874
2875 text->setText("Hello World");
2876
2877 QCOMPARE(text->clipRect().x(), qreal(0));
2878 QCOMPARE(text->clipRect().y(), qreal(0));
2879 QCOMPARE(text->clipRect().width(), text->width());
2880 QCOMPARE(text->clipRect().height(), text->height());
2881
2882 // Clip rect follows the item not content dimensions.
2883 text->setWidth(text->width() / 2);
2884 QCOMPARE(text->clipRect().x(), qreal(0));
2885 QCOMPARE(text->clipRect().y(), qreal(0));
2886 QCOMPARE(text->clipRect().width(), text->width());
2887 QCOMPARE(text->clipRect().height(), text->height());
2888
2889 text->setHeight(text->height() * 2);
2890 QCOMPARE(text->clipRect().x(), qreal(0));
2891 QCOMPARE(text->clipRect().y(), qreal(0));
2892 QCOMPARE(text->clipRect().width(), text->width());
2893 QCOMPARE(text->clipRect().height(), text->height());
2894
2895 // Setting a style adds a small amount of padding to the clip rect.
2896 text->setStyle(QQuickText::Outline);
2897 QCOMPARE(text->clipRect().x(), qreal(-1));
2898 QCOMPARE(text->clipRect().y(), qreal(0));
2899 QCOMPARE(text->clipRect().width(), text->width() + 2);
2900 QCOMPARE(text->clipRect().height(), text->height() + 2);
2901}
2902
2903void tst_qquicktext::lineLaidOut()
2904{
2905 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "lineLayout.qml")));
2906
2907 QQuickText *myText = window->rootObject()->findChild<QQuickText*>(aName: "myText");
2908 QVERIFY(myText != nullptr);
2909
2910 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: myText);
2911 QVERIFY(textPrivate != nullptr);
2912
2913 QVERIFY(!textPrivate->extra.isAllocated());
2914
2915 for (int i = 0; i < textPrivate->layout.lineCount(); ++i) {
2916 QRectF r = textPrivate->layout.lineAt(i).rect();
2917 QVERIFY(r.width() == i * 15);
2918 if (i >= 30)
2919 QVERIFY(r.x() == r.width() + 30);
2920 if (i >= 60) {
2921 QVERIFY(r.x() == r.width() * 2 + 60);
2922 QCOMPARE(r.height(), qreal(20));
2923 }
2924 }
2925
2926 // Ensure that isLast was correctly emitted
2927 int lastLineNumber = myText->property(name: "lastLineNumber").toInt();
2928 QCOMPARE(lastLineNumber, myText->lineCount() - 1);
2929 // Ensure that only one line was considered last (after changing its width)
2930 bool receivedMultipleLastLines = myText->property(name: "receivedMultipleLastLines").toBool();
2931 QVERIFY(!receivedMultipleLastLines);
2932}
2933
2934void tst_qquicktext::lineLaidOutRelayout()
2935{
2936 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "lineLayoutRelayout.qml")));
2937
2938 window->show();
2939 window->requestActivate();
2940 QVERIFY(QTest::qWaitForWindowActive(window.data()));
2941
2942 QQuickText *myText = window->rootObject()->findChild<QQuickText*>(aName: "myText");
2943 QVERIFY(myText != nullptr);
2944
2945 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: myText);
2946 QVERIFY(textPrivate != nullptr);
2947
2948 QVERIFY(!textPrivate->extra.isAllocated());
2949
2950 qreal y = 0.0;
2951 for (int i = 0; i < textPrivate->layout.lineCount(); ++i) {
2952 QTextLine line = textPrivate->layout.lineAt(i);
2953 const QRectF r = line.rect();
2954 if (r.x() == 0) {
2955 QCOMPARE(r.y(), y);
2956 } else {
2957 if (qFuzzyIsNull(d: r.y()))
2958 y = 0.0;
2959 QCOMPARE(r.x(), myText->width() / 2);
2960 QCOMPARE(r.y(), y);
2961 }
2962 y += line.height();
2963 }
2964}
2965
2966void tst_qquicktext::lineLaidOutHAlign()
2967{
2968 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "lineLayoutHAlign.qml")));
2969
2970 QQuickText *myText = window->rootObject()->findChild<QQuickText*>(aName: "myText");
2971 QVERIFY(myText != nullptr);
2972
2973 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: myText);
2974 QVERIFY(textPrivate != nullptr);
2975
2976 QCOMPARE(textPrivate->layout.lineCount(), 1);
2977
2978 QVERIFY(textPrivate->layout.lineAt(0).naturalTextRect().x() < 0.0);
2979}
2980
2981void tst_qquicktext::imgTagsBaseUrl_data()
2982{
2983 QTest::addColumn<QUrl>(name: "src");
2984 QTest::addColumn<QUrl>(name: "baseUrl");
2985 QTest::addColumn<QUrl>(name: "contextUrl");
2986 QTest::addColumn<qreal>(name: "imgHeight");
2987
2988 QTest::newRow(dataTag: "absolute local")
2989 << testFileUrl(fileName: "images/heart200.png")
2990 << QUrl()
2991 << QUrl()
2992 << 181.;
2993 QTest::newRow(dataTag: "relative local context 1")
2994 << QUrl("images/heart200.png")
2995 << QUrl()
2996 << testFileUrl(fileName: "/app.qml")
2997 << 181.;
2998 QTest::newRow(dataTag: "relative local context 2")
2999 << QUrl("heart200.png")
3000 << QUrl()
3001 << testFileUrl(fileName: "images/app.qml")
3002 << 181.;
3003 QTest::newRow(dataTag: "relative local base 1")
3004 << QUrl("images/heart200.png")
3005 << testFileUrl(fileName: "")
3006 << testFileUrl(fileName: "nonexistant/app.qml")
3007 << 181.;
3008 QTest::newRow(dataTag: "relative local base 2")
3009 << QUrl("heart200.png")
3010 << testFileUrl(fileName: "images/")
3011 << testFileUrl(fileName: "nonexistant/app.qml")
3012 << 181.;
3013 QTest::newRow(dataTag: "base relative to local context")
3014 << QUrl("heart200.png")
3015 << testFileUrl(fileName: "images/")
3016 << testFileUrl(fileName: "/app.qml")
3017 << 181.;
3018
3019 QTest::newRow(dataTag: "absolute remote")
3020 << QUrl("http://testserver/images/heart200.png")
3021 << QUrl()
3022 << QUrl()
3023 << 181.;
3024 QTest::newRow(dataTag: "relative remote base 1")
3025 << QUrl("images/heart200.png")
3026 << QUrl("http://testserver/")
3027 << testFileUrl(fileName: "nonexistant/app.qml")
3028 << 181.;
3029 QTest::newRow(dataTag: "relative remote base 2")
3030 << QUrl("heart200.png")
3031 << QUrl("http://testserver/images/")
3032 << testFileUrl(fileName: "nonexistant/app.qml")
3033 << 181.;
3034}
3035
3036void tst_qquicktext::lineLaidOutImplicitWidth()
3037{
3038 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "lineLayoutImplicitWidth.qml")));
3039
3040 QQuickText *myText = window->rootObject()->findChild<QQuickText*>(aName: "myText");
3041 QVERIFY(myText != nullptr);
3042
3043 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: myText);
3044 QVERIFY(textPrivate != nullptr);
3045
3046 // Retrieve the saved implicitWidth values of each rendered line
3047 QVariant widthsProperty = myText->property(name: "lineImplicitWidths");
3048 QVERIFY(!widthsProperty.isNull());
3049 QVERIFY(widthsProperty.isValid());
3050 QVERIFY(widthsProperty.canConvert<QJSValue>());
3051 QJSValue widthsValue = widthsProperty.value<QJSValue>();
3052 QVERIFY(widthsValue.isArray());
3053 int lineCount = widthsValue.property(name: "length").toInt();
3054 QVERIFY(lineCount > 0);
3055
3056 // Create the same text layout by hand
3057 // Note that this approach needs additional processing for styled text,
3058 // so we only use it for plain text here.
3059 QTextLayout layout;
3060 layout.setCacheEnabled(true);
3061 layout.setText(myText->text());
3062 layout.setTextOption(textPrivate->layout.textOption());
3063 layout.setFont(myText->font());
3064 layout.beginLayout();
3065 for (QTextLine line = layout.createLine(); line.isValid(); line = layout.createLine()) {
3066 line.setLineWidth(myText->width());
3067 }
3068 layout.endLayout();
3069
3070 // Line count of the just created layout should match the rendered text
3071 QCOMPARE(lineCount, layout.lineCount());
3072
3073 // Go through each line and verify that the values emitted by lineLaidOut are correct
3074 for (int i = 0; i < layout.lineCount(); ++i) {
3075 qreal implicitWidth = widthsValue.property(arrayIndex: i).toNumber();
3076 QVERIFY(implicitWidth > 0);
3077
3078 QTextLine line = layout.lineAt(i);
3079 QCOMPARE(implicitWidth, line.naturalTextWidth());
3080 }
3081}
3082
3083static QUrl substituteTestServerUrl(const QUrl &serverUrl, const QUrl &testUrl)
3084{
3085 QUrl result = testUrl;
3086 if (result.host() == QStringLiteral("testserver")) {
3087 result.setScheme(serverUrl.scheme());
3088 result.setHost(host: serverUrl.host());
3089 result.setPort(serverUrl.port());
3090 }
3091 return result;
3092}
3093
3094void tst_qquicktext::imgTagsBaseUrl()
3095{
3096 QFETCH(QUrl, src);
3097 QFETCH(QUrl, baseUrl);
3098 QFETCH(QUrl, contextUrl);
3099 QFETCH(qreal, imgHeight);
3100
3101 TestHTTPServer server;
3102 QVERIFY2(server.listen(), qPrintable(server.errorString()));
3103 server.serveDirectory(testFile(fileName: ""));
3104
3105 src = substituteTestServerUrl(serverUrl: server.baseUrl(), testUrl: src);
3106 baseUrl = substituteTestServerUrl(serverUrl: server.baseUrl(), testUrl: baseUrl);
3107 contextUrl = substituteTestServerUrl(serverUrl: server.baseUrl(), testUrl: contextUrl);
3108
3109 QByteArray baseUrlFragment;
3110 if (!baseUrl.isEmpty())
3111 baseUrlFragment = "; baseUrl: \"" + baseUrl.toEncoded() + "\"";
3112 QByteArray componentStr = "import QtQuick 2.0\nText { text: \"This is a test <img src=\\\"" + src.toEncoded() + "\\\">\"" + baseUrlFragment + " }";
3113
3114 QQmlComponent component(&engine);
3115 component.setData(componentStr, baseUrl: contextUrl);
3116 QScopedPointer<QObject> object(component.create());
3117 QQuickText *textObject = qobject_cast<QQuickText *>(object: object.data());
3118 QVERIFY(textObject);
3119
3120 QCoreApplication::processEvents();
3121
3122 QTRY_COMPARE(textObject->height(), imgHeight);
3123}
3124
3125void tst_qquicktext::imgTagsAlign_data()
3126{
3127 QTest::addColumn<QString>(name: "src");
3128 QTest::addColumn<int>(name: "imgHeight");
3129 QTest::addColumn<QString>(name: "align");
3130 QTest::newRow(dataTag: "heart-bottom") << "data/images/heart200.png" << 181 << "bottom";
3131 QTest::newRow(dataTag: "heart-middle") << "data/images/heart200.png" << 181 << "middle";
3132 QTest::newRow(dataTag: "heart-top") << "data/images/heart200.png" << 181 << "top";
3133 QTest::newRow(dataTag: "starfish-bottom") << "data/images/starfish_2.png" << 217 << "bottom";
3134 QTest::newRow(dataTag: "starfish-middle") << "data/images/starfish_2.png" << 217 << "middle";
3135 QTest::newRow(dataTag: "starfish-top") << "data/images/starfish_2.png" << 217 << "top";
3136}
3137
3138void tst_qquicktext::imgTagsAlign()
3139{
3140 QFETCH(QString, src);
3141 QFETCH(int, imgHeight);
3142 QFETCH(QString, align);
3143 QString componentStr = "import QtQuick 2.0\nText { text: \"This is a test <img src=\\\"" + src + "\\\" align=\\\"" + align + "\\\"> of image.\" }";
3144 QQmlComponent textComponent(&engine);
3145 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: "."));
3146 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
3147
3148 QVERIFY(textObject != nullptr);
3149 QCOMPARE(textObject->height(), qreal(imgHeight));
3150
3151 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: textObject);
3152 QVERIFY(textPrivate != nullptr);
3153
3154 QRectF br = textPrivate->layout.boundingRect();
3155 if (align == "bottom")
3156 QVERIFY(br.y() == imgHeight - br.height());
3157 else if (align == "middle")
3158 QVERIFY(br.y() == imgHeight / 2.0 - br.height() / 2.0);
3159 else if (align == "top")
3160 QCOMPARE(br.y(), qreal(0));
3161
3162 delete textObject;
3163}
3164
3165void tst_qquicktext::imgTagsMultipleImages()
3166{
3167 QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"data/images/starfish_2.png\\\" width=\\\"60\\\" height=\\\"60\\\" > and another one<img src=\\\"data/images/heart200.png\\\" width=\\\"85\\\" height=\\\"85\\\">.\" }";
3168
3169 QQmlComponent textComponent(&engine);
3170 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: "."));
3171 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
3172
3173 QVERIFY(textObject != nullptr);
3174 QCOMPARE(textObject->height(), qreal(85));
3175
3176 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: textObject);
3177 QVERIFY(textPrivate != nullptr);
3178 QCOMPARE(textPrivate->extra->visibleImgTags.count(), 2);
3179
3180 delete textObject;
3181}
3182
3183void tst_qquicktext::imgTagsElide()
3184{
3185 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "imgTagsElide.qml")));
3186 QQuickText *myText = window->rootObject()->findChild<QQuickText*>(aName: "myText");
3187 QVERIFY(myText != nullptr);
3188
3189 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: myText);
3190 QVERIFY(textPrivate != nullptr);
3191 QCOMPARE(textPrivate->extra->visibleImgTags.count(), 0);
3192 myText->setMaximumLineCount(20);
3193 QTRY_COMPARE(textPrivate->extra->visibleImgTags.count(), 1);
3194
3195 delete myText;
3196}
3197
3198void tst_qquicktext::imgTagsUpdates()
3199{
3200 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "imgTagsUpdates.qml")));
3201 QQuickText *myText = window->rootObject()->findChild<QQuickText*>(aName: "myText");
3202 QVERIFY(myText != nullptr);
3203
3204 QSignalSpy spy(myText, SIGNAL(contentSizeChanged()));
3205
3206 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: myText);
3207 QVERIFY(textPrivate != nullptr);
3208
3209 myText->setText("This is a heart<img src=\"images/heart200.png\">.");
3210 QCOMPARE(textPrivate->extra->visibleImgTags.count(), 1);
3211 QCOMPARE(spy.count(), 1);
3212
3213 myText->setMaximumLineCount(2);
3214 myText->setText("This is another heart<img src=\"images/heart200.png\">.");
3215 QTRY_COMPARE(textPrivate->extra->visibleImgTags.count(), 1);
3216
3217 // if maximumLineCount is set and the img tag doesn't have an explicit size
3218 // we relayout twice.
3219 QCOMPARE(spy.count(), 3);
3220
3221 delete myText;
3222}
3223
3224void tst_qquicktext::imgTagsError()
3225{
3226 QString componentStr = "import QtQuick 2.0\nText { text: \"This is a starfish<img src=\\\"data/images/starfish_2.pn\\\" width=\\\"60\\\" height=\\\"60\\\">.\" }";
3227
3228 QQmlComponent textComponent(&engine);
3229 QTest::ignoreMessage(type: QtWarningMsg, message: "<Unknown File>:2:1: QML Text: Cannot open: file:data/images/starfish_2.pn");
3230 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl("file:"));
3231 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
3232
3233 QVERIFY(textObject != nullptr);
3234 delete textObject;
3235}
3236
3237void tst_qquicktext::fontSizeMode_data()
3238{
3239 QTest::addColumn<QString>(name: "text");
3240 QTest::newRow(dataTag: "plain") << "The quick red fox jumped over the lazy brown dog";
3241 QTest::newRow(dataTag: "styled") << "<b>The quick red fox jumped over the lazy brown dog</b>";
3242}
3243
3244void tst_qquicktext::fontSizeMode()
3245{
3246 QFETCH(QString, text);
3247
3248 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "fontSizeMode.qml")));
3249 window->show();
3250
3251 QQuickText *myText = window->rootObject()->findChild<QQuickText*>(aName: "myText");
3252 QVERIFY(myText != nullptr);
3253
3254 myText->setText(text);
3255 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3256
3257 qreal originalWidth = myText->contentWidth();
3258 qreal originalHeight = myText->contentHeight();
3259
3260 // The original text unwrapped should exceed the width of the item.
3261 QVERIFY(originalWidth > myText->width());
3262 QVERIFY(originalHeight < myText->height());
3263
3264 QFont font = myText->font();
3265 font.setPixelSize(64);
3266
3267 myText->setFont(font);
3268 myText->setFontSizeMode(QQuickText::HorizontalFit);
3269 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3270 // Font size reduced to fit within the width of the item.
3271 qreal horizontalFitWidth = myText->contentWidth();
3272 qreal horizontalFitHeight = myText->contentHeight();
3273 QVERIFY(horizontalFitWidth <= myText->width() + 2); // rounding
3274 QVERIFY(horizontalFitHeight <= myText->height() + 2);
3275
3276 // Elide won't affect the size with HorizontalFit.
3277 myText->setElideMode(QQuickText::ElideRight);
3278 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3279 QVERIFY(!myText->truncated());
3280 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3281 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3282
3283 myText->setElideMode(QQuickText::ElideLeft);
3284 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3285 QVERIFY(!myText->truncated());
3286 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3287 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3288
3289 myText->setElideMode(QQuickText::ElideMiddle);
3290 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3291 QVERIFY(!myText->truncated());
3292 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3293 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3294
3295 myText->setElideMode(QQuickText::ElideNone);
3296 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3297
3298 myText->setFontSizeMode(QQuickText::VerticalFit);
3299 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3300 // Font size increased to fill the height of the item.
3301 qreal verticalFitHeight = myText->contentHeight();
3302 QVERIFY(myText->contentWidth() > myText->width());
3303 QVERIFY(verticalFitHeight <= myText->height() + 2);
3304 QVERIFY(verticalFitHeight > originalHeight);
3305
3306 // Elide won't affect the height of a single line with VerticalFit but will crop the width.
3307 myText->setElideMode(QQuickText::ElideRight);
3308 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3309 QVERIFY(myText->truncated());
3310 QVERIFY(myText->contentWidth() <= myText->width() + 2);
3311 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3312
3313 myText->setElideMode(QQuickText::ElideLeft);
3314 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3315 QVERIFY(myText->truncated());
3316 QVERIFY(myText->contentWidth() <= myText->width() + 2);
3317 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3318
3319 myText->setElideMode(QQuickText::ElideMiddle);
3320 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3321 QVERIFY(myText->truncated());
3322 QVERIFY(myText->contentWidth() <= myText->width() + 2);
3323 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3324
3325 myText->setElideMode(QQuickText::ElideNone);
3326 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3327
3328 myText->setFontSizeMode(QQuickText::Fit);
3329 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3330 // Should be the same as HorizontalFit with no wrapping.
3331 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3332 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3333
3334 // Elide won't affect the size with Fit.
3335 myText->setElideMode(QQuickText::ElideRight);
3336 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3337 QVERIFY(!myText->truncated());
3338 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3339 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3340
3341 myText->setElideMode(QQuickText::ElideLeft);
3342 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3343 QVERIFY(!myText->truncated());
3344 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3345 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3346
3347 myText->setElideMode(QQuickText::ElideMiddle);
3348 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3349 QVERIFY(!myText->truncated());
3350 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3351 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3352
3353 myText->setElideMode(QQuickText::ElideNone);
3354 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3355
3356 myText->setFontSizeMode(QQuickText::FixedSize);
3357 myText->setWrapMode(QQuickText::Wrap);
3358 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3359
3360 originalWidth = myText->contentWidth();
3361 originalHeight = myText->contentHeight();
3362
3363 // The original text wrapped should exceed the height of the item.
3364 QVERIFY(originalWidth <= myText->width() + 2);
3365 QVERIFY(originalHeight > myText->height());
3366
3367 myText->setFontSizeMode(QQuickText::HorizontalFit);
3368 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3369 // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the
3370 // same size as without text wrapping.
3371 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3372 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3373
3374 // Elide won't affect the size with HorizontalFit.
3375 myText->setElideMode(QQuickText::ElideRight);
3376 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3377 QVERIFY(!myText->truncated());
3378 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3379 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3380
3381 myText->setElideMode(QQuickText::ElideNone);
3382 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3383
3384 myText->setFontSizeMode(QQuickText::VerticalFit);
3385 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3386 // VerticalFit should reduce the size to the wrapped text within the vertical height.
3387 verticalFitHeight = myText->contentHeight();
3388 qreal verticalFitWidth = myText->contentWidth();
3389 QVERIFY(myText->contentWidth() <= myText->width() + 2);
3390 QVERIFY(verticalFitHeight <= myText->height() + 2);
3391 QVERIFY(verticalFitHeight < originalHeight);
3392
3393 // Elide won't affect the height or width of a wrapped text with VerticalFit.
3394 myText->setElideMode(QQuickText::ElideRight);
3395 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3396 QVERIFY(!myText->truncated());
3397 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3398 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3399
3400 myText->setElideMode(QQuickText::ElideNone);
3401 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3402
3403 myText->setFontSizeMode(QQuickText::Fit);
3404 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3405 // Should be the same as VerticalFit with wrapping.
3406 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3407 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3408
3409 // Elide won't affect the size with Fit.
3410 myText->setElideMode(QQuickText::ElideRight);
3411 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3412 QVERIFY(!myText->truncated());
3413 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3414 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3415
3416 myText->setElideMode(QQuickText::ElideNone);
3417 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3418
3419 myText->setFontSizeMode(QQuickText::FixedSize);
3420 myText->setMaximumLineCount(2);
3421 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3422
3423 // The original text wrapped should exceed the height of the item.
3424 QVERIFY(originalWidth <= myText->width() + 2);
3425 QVERIFY(originalHeight > myText->height());
3426
3427 myText->setFontSizeMode(QQuickText::HorizontalFit);
3428 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3429 // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the
3430 // same size as without text wrapping.
3431 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3432 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3433
3434 // Elide won't affect the size with HorizontalFit.
3435 myText->setElideMode(QQuickText::ElideRight);
3436 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3437 QVERIFY(!myText->truncated());
3438 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3439 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3440
3441 myText->setElideMode(QQuickText::ElideNone);
3442 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3443
3444 myText->setFontSizeMode(QQuickText::VerticalFit);
3445 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3446 // VerticalFit should reduce the size to the wrapped text within the vertical height.
3447 verticalFitHeight = myText->contentHeight();
3448 verticalFitWidth = myText->contentWidth();
3449 QVERIFY(myText->contentWidth() <= myText->width() + 2);
3450 QVERIFY(verticalFitHeight <= myText->height() + 2);
3451 QVERIFY(verticalFitHeight < originalHeight);
3452
3453 // Elide won't affect the height or width of a wrapped text with VerticalFit.
3454 myText->setElideMode(QQuickText::ElideRight);
3455 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3456 QVERIFY(!myText->truncated());
3457 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3458 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3459
3460 myText->setElideMode(QQuickText::ElideNone);
3461 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3462
3463 myText->setFontSizeMode(QQuickText::Fit);
3464 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3465 // Should be the same as VerticalFit with wrapping.
3466 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3467 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3468
3469 // Elide won't affect the size with Fit.
3470 myText->setElideMode(QQuickText::ElideRight);
3471 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3472 QVERIFY(!myText->truncated());
3473 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3474 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3475
3476 myText->setElideMode(QQuickText::ElideNone);
3477 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3478}
3479
3480void tst_qquicktext::fontSizeModeMultiline_data()
3481{
3482 QTest::addColumn<QString>(name: "text");
3483 QTest::newRow(dataTag: "plain") << "The quick red fox jumped\n over the lazy brown dog";
3484 QTest::newRow(dataTag: "styledtext") << "<b>The quick red fox jumped<br/> over the lazy brown dog</b>";
3485}
3486
3487void tst_qquicktext::fontSizeModeMultiline()
3488{
3489 QFETCH(QString, text);
3490
3491 QScopedPointer<QQuickView> window(createView(filename: testFile(fileName: "fontSizeMode.qml")));
3492 window->show();
3493
3494 QQuickText *myText = window->rootObject()->findChild<QQuickText*>(aName: "myText");
3495 QVERIFY(myText != nullptr);
3496
3497 myText->setText(text);
3498 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3499
3500 qreal originalWidth = myText->contentWidth();
3501 qreal originalHeight = myText->contentHeight();
3502 QCOMPARE(myText->lineCount(), 2);
3503
3504 // The original text unwrapped should exceed the width and height of the item.
3505 QVERIFY(originalWidth > myText->width());
3506 QVERIFY(originalHeight > myText->height());
3507
3508 QFont font = myText->font();
3509 font.setPixelSize(64);
3510
3511 myText->setFont(font);
3512 myText->setFontSizeMode(QQuickText::HorizontalFit);
3513 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3514 // Font size reduced to fit within the width of the item.
3515 QCOMPARE(myText->lineCount(), 2);
3516 qreal horizontalFitWidth = myText->contentWidth();
3517 qreal horizontalFitHeight = myText->contentHeight();
3518 QVERIFY(horizontalFitWidth <= myText->width() + 2); // rounding
3519 QVERIFY(horizontalFitHeight > myText->height());
3520
3521 // Right eliding will remove the last line
3522 myText->setElideMode(QQuickText::ElideRight);
3523 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3524 QVERIFY(myText->truncated());
3525 QCOMPARE(myText->lineCount(), 1);
3526 QVERIFY(myText->contentWidth() <= myText->width() + 2);
3527 QVERIFY(myText->contentHeight() <= myText->height() + 2);
3528
3529 // Left or middle eliding wont have any effect.
3530 myText->setElideMode(QQuickText::ElideLeft);
3531 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3532 QVERIFY(!myText->truncated());
3533 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3534 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3535
3536 myText->setElideMode(QQuickText::ElideMiddle);
3537 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3538 QVERIFY(!myText->truncated());
3539 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3540 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3541
3542 myText->setElideMode(QQuickText::ElideNone);
3543 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3544
3545 myText->setFontSizeMode(QQuickText::VerticalFit);
3546 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3547 // Font size reduced to fit within the height of the item.
3548 qreal verticalFitWidth = myText->contentWidth();
3549 qreal verticalFitHeight = myText->contentHeight();
3550 QVERIFY(verticalFitWidth <= myText->width() + 2);
3551 QVERIFY(verticalFitHeight <= myText->height() + 2);
3552
3553 // Elide will have no effect.
3554 myText->setElideMode(QQuickText::ElideRight);
3555 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3556 QVERIFY(!myText->truncated());
3557 QVERIFY(myText->contentWidth() <= myText->width() + 2);
3558 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3559 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3560
3561 myText->setElideMode(QQuickText::ElideLeft);
3562 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3563 QVERIFY(!myText->truncated());
3564 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3565 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3566
3567 myText->setElideMode(QQuickText::ElideMiddle);
3568 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3569 QVERIFY(!myText->truncated());
3570 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3571 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3572
3573 myText->setElideMode(QQuickText::ElideNone);
3574 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3575
3576 myText->setFontSizeMode(QQuickText::Fit);
3577 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3578 // Should be the same as VerticalFit with no wrapping.
3579 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3580 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3581
3582 // Elide won't affect the size with Fit.
3583 myText->setElideMode(QQuickText::ElideRight);
3584 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3585 QVERIFY(!myText->truncated());
3586 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3587 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3588
3589 myText->setElideMode(QQuickText::ElideLeft);
3590 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3591 QVERIFY(!myText->truncated());
3592 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3593 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3594
3595 myText->setElideMode(QQuickText::ElideMiddle);
3596 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3597 QVERIFY(!myText->truncated());
3598 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3599 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3600
3601 myText->setElideMode(QQuickText::ElideNone);
3602 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3603
3604 myText->setFontSizeMode(QQuickText::FixedSize);
3605 myText->setWrapMode(QQuickText::Wrap);
3606 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3607
3608 originalWidth = myText->contentWidth();
3609 originalHeight = myText->contentHeight();
3610
3611 // The original text wrapped should exceed the height of the item.
3612 QVERIFY(originalWidth <= myText->width() + 2);
3613 QVERIFY(originalHeight > myText->height());
3614
3615 myText->setFontSizeMode(QQuickText::HorizontalFit);
3616 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3617 // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the
3618 // same size as without text wrapping.
3619 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3620 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3621
3622 // Text will be elided vertically with HorizontalFit
3623 myText->setElideMode(QQuickText::ElideRight);
3624 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3625 QVERIFY(myText->truncated());
3626 QVERIFY(myText->contentWidth() <= myText->width() + 2);
3627 QVERIFY(myText->contentHeight() <= myText->height() + 2);
3628
3629 myText->setElideMode(QQuickText::ElideNone);
3630 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3631
3632 myText->setFontSizeMode(QQuickText::VerticalFit);
3633 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3634 // VerticalFit should reduce the size to the wrapped text within the vertical height.
3635 verticalFitHeight = myText->contentHeight();
3636 verticalFitWidth = myText->contentWidth();
3637 QVERIFY(myText->contentWidth() <= myText->width() + 2);
3638 QVERIFY(verticalFitHeight <= myText->height() + 2);
3639 QVERIFY(verticalFitHeight < originalHeight);
3640
3641 // Elide won't affect the height or width of a wrapped text with VerticalFit.
3642 myText->setElideMode(QQuickText::ElideRight);
3643 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3644 QVERIFY(!myText->truncated());
3645 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3646 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3647
3648 myText->setElideMode(QQuickText::ElideNone);
3649 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3650
3651 myText->setFontSizeMode(QQuickText::Fit);
3652 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3653 // Should be the same as VerticalFit with wrapping.
3654 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3655 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3656
3657 // Elide won't affect the size with Fit.
3658 myText->setElideMode(QQuickText::ElideRight);
3659 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3660 QVERIFY(!myText->truncated());
3661 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3662 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3663
3664 myText->setElideMode(QQuickText::ElideNone);
3665 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3666
3667 myText->setFontSizeMode(QQuickText::FixedSize);
3668 myText->setMaximumLineCount(2);
3669 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3670
3671 // The original text wrapped should exceed the height of the item.
3672 QVERIFY(originalWidth <= myText->width() + 2);
3673 QVERIFY(originalHeight > myText->height());
3674
3675 myText->setFontSizeMode(QQuickText::HorizontalFit);
3676 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3677 // HorizontalFit should reduce the font size to minimize wrapping, which brings it back to the
3678 // same size as without text wrapping.
3679 QCOMPARE(myText->contentWidth(), horizontalFitWidth);
3680 QCOMPARE(myText->contentHeight(), horizontalFitHeight);
3681
3682 // Elide won't affect the size with HorizontalFit.
3683 myText->setElideMode(QQuickText::ElideRight);
3684 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3685 QVERIFY(myText->truncated());
3686 QVERIFY(myText->contentWidth() <= myText->width() + 2);
3687 QVERIFY(myText->contentHeight() <= myText->height() + 2);
3688
3689 myText->setElideMode(QQuickText::ElideNone);
3690 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3691
3692 myText->setFontSizeMode(QQuickText::VerticalFit);
3693 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3694 // VerticalFit should reduce the size to the wrapped text within the vertical height.
3695 verticalFitHeight = myText->contentHeight();
3696 verticalFitWidth = myText->contentWidth();
3697 QVERIFY(myText->contentWidth() <= myText->width() + 2);
3698 QVERIFY(verticalFitHeight <= myText->height() + 2);
3699 QVERIFY(verticalFitHeight < originalHeight);
3700
3701 // Elide won't affect the height or width of a wrapped text with VerticalFit.
3702 myText->setElideMode(QQuickText::ElideRight);
3703 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3704 QVERIFY(!myText->truncated());
3705 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3706 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3707
3708 myText->setElideMode(QQuickText::ElideNone);
3709 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3710
3711 myText->setFontSizeMode(QQuickText::Fit);
3712 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3713 // Should be the same as VerticalFit with wrapping.
3714 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3715 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3716
3717 // Elide won't affect the size with Fit.
3718 myText->setElideMode(QQuickText::ElideRight);
3719 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3720 QVERIFY(!myText->truncated());
3721 QCOMPARE(myText->contentWidth(), verticalFitWidth);
3722 QCOMPARE(myText->contentHeight(), verticalFitHeight);
3723
3724 myText->setElideMode(QQuickText::ElideNone);
3725 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3726}
3727
3728void tst_qquicktext::multilengthStrings_data()
3729{
3730 QTest::addColumn<QString>(name: "source");
3731 QTest::newRow(dataTag: "No Wrap") << testFile(fileName: "multilengthStrings.qml");
3732 QTest::newRow(dataTag: "Wrap") << testFile(fileName: "multilengthStringsWrapped.qml");
3733}
3734
3735void tst_qquicktext::multilengthStrings()
3736{
3737 QFETCH(QString, source);
3738
3739 QScopedPointer<QQuickView> window(createView(filename: source));
3740 window->show();
3741
3742 QQuickText *myText = window->rootObject()->findChild<QQuickText*>(aName: "myText");
3743 QVERIFY(myText != nullptr);
3744
3745 const QString longText = "the quick brown fox jumped over the lazy dog";
3746 const QString mediumText = "the brown fox jumped over the dog";
3747 const QString shortText = "fox jumped dog";
3748
3749 myText->setText(longText);
3750 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3751 const qreal longWidth = myText->contentWidth();
3752 const qreal longHeight = myText->contentHeight();
3753
3754 myText->setText(mediumText);
3755 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3756 const qreal mediumWidth = myText->contentWidth();
3757 const qreal mediumHeight = myText->contentHeight();
3758
3759 myText->setText(shortText);
3760 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3761 const qreal shortWidth = myText->contentWidth();
3762 const qreal shortHeight = myText->contentHeight();
3763
3764 myText->setElideMode(QQuickText::ElideRight);
3765 myText->setText(longText + QLatin1Char('\x9c') + mediumText + QLatin1Char('\x9c') + shortText);
3766
3767 myText->setSize(QSizeF(longWidth, longHeight));
3768 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3769
3770 QCOMPARE(myText->contentWidth(), longWidth);
3771 QCOMPARE(myText->contentHeight(), longHeight);
3772 QCOMPARE(myText->truncated(), false);
3773
3774 myText->setSize(QSizeF(mediumWidth, mediumHeight));
3775 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3776
3777 QCOMPARE(myText->contentWidth(), mediumWidth);
3778 QCOMPARE(myText->contentHeight(), mediumHeight);
3779 QCOMPARE(myText->truncated(), true);
3780
3781 myText->setSize(QSizeF(shortWidth, shortHeight));
3782 QVERIFY(QQuickTest::qWaitForItemPolished(myText));
3783
3784 QCOMPARE(myText->contentWidth(), shortWidth);
3785 QCOMPARE(myText->contentHeight(), shortHeight);
3786 QCOMPARE(myText->truncated(), true);
3787}
3788
3789void tst_qquicktext::fontFormatSizes_data()
3790{
3791 QTest::addColumn<QString>(name: "text");
3792 QTest::addColumn<QString>(name: "textWithTag");
3793 QTest::addColumn<bool>(name: "fontIsBigger");
3794
3795 QTest::newRow(dataTag: "fs1") << "Hello world!" << "Hello <font size=\"1\">world</font>!" << false;
3796 QTest::newRow(dataTag: "fs2") << "Hello world!" << "Hello <font size=\"2\">world</font>!" << false;
3797 QTest::newRow(dataTag: "fs3") << "Hello world!" << "Hello <font size=\"3\">world</font>!" << false;
3798 QTest::newRow(dataTag: "fs4") << "Hello world!" << "Hello <font size=\"4\">world</font>!" << true;
3799 QTest::newRow(dataTag: "fs5") << "Hello world!" << "Hello <font size=\"5\">world</font>!" << true;
3800 QTest::newRow(dataTag: "fs6") << "Hello world!" << "Hello <font size=\"6\">world</font>!" << true;
3801 QTest::newRow(dataTag: "fs7") << "Hello world!" << "Hello <font size=\"7\">world</font>!" << true;
3802 QTest::newRow(dataTag: "h1") << "This is<br/>a font<br/> size test." << "This is <h1>a font</h1> size test." << true;
3803 QTest::newRow(dataTag: "h2") << "This is<br/>a font<br/> size test." << "This is <h2>a font</h2> size test." << true;
3804 QTest::newRow(dataTag: "h3") << "This is<br/>a font<br/> size test." << "This is <h3>a font</h3> size test." << true;
3805 QTest::newRow(dataTag: "h4") << "This is<br/>a font<br/> size test." << "This is <h4>a font</h4> size test." << true;
3806 QTest::newRow(dataTag: "h5") << "This is<br/>a font<br/> size test." << "This is <h5>a font</h5> size test." << false;
3807 QTest::newRow(dataTag: "h6") << "This is<br/>a font<br/> size test." << "This is <h6>a font</h6> size test." << false;
3808}
3809
3810void tst_qquicktext::fontFormatSizes()
3811{
3812 QFETCH(QString, text);
3813 QFETCH(QString, textWithTag);
3814 QFETCH(bool, fontIsBigger);
3815
3816 QQuickView *view = new QQuickView;
3817 {
3818 view->setSource(testFileUrl(fileName: "pointFontSizes.qml"));
3819 view->show();
3820
3821 QQuickText *qtext = view->rootObject()->findChild<QQuickText*>(aName: "text");
3822 QQuickText *qtextWithTag = view->rootObject()->findChild<QQuickText*>(aName: "textWithTag");
3823 QVERIFY(qtext != nullptr);
3824 QVERIFY(qtextWithTag != nullptr);
3825
3826 qtext->setText(text);
3827 qtextWithTag->setText(textWithTag);
3828
3829 for (int size = 6; size < 100; size += 4) {
3830 view->rootObject()->setProperty(name: "pointSize", value: size);
3831 if (fontIsBigger)
3832 QVERIFY(qtext->height() <= qtextWithTag->height());
3833 else
3834 QVERIFY(qtext->height() >= qtextWithTag->height());
3835 }
3836 }
3837
3838 {
3839 view->setSource(testFileUrl(fileName: "pixelFontSizes.qml"));
3840 QQuickText *qtext = view->rootObject()->findChild<QQuickText*>(aName: "text");
3841 QQuickText *qtextWithTag = view->rootObject()->findChild<QQuickText*>(aName: "textWithTag");
3842 QVERIFY(qtext != nullptr);
3843 QVERIFY(qtextWithTag != nullptr);
3844
3845 qtext->setText(text);
3846 qtextWithTag->setText(textWithTag);
3847
3848 for (int size = 6; size < 100; size += 4) {
3849 view->rootObject()->setProperty(name: "pixelSize", value: size);
3850 if (fontIsBigger)
3851 QVERIFY(qtext->height() <= qtextWithTag->height());
3852 else
3853 QVERIFY(qtext->height() >= qtextWithTag->height());
3854 }
3855 }
3856 delete view;
3857}
3858
3859typedef qreal (*ExpectedBaseline)(QQuickText *item);
3860Q_DECLARE_METATYPE(ExpectedBaseline)
3861
3862static qreal expectedBaselineTop(QQuickText *item)
3863{
3864 QFontMetricsF fm(item->font());
3865 return fm.ascent() + item->topPadding();
3866}
3867
3868static qreal expectedBaselineBottom(QQuickText *item)
3869{
3870 QFontMetricsF fm(item->font());
3871 return item->height() - item->contentHeight() - item->bottomPadding() + fm.ascent();
3872}
3873
3874static qreal expectedBaselineCenter(QQuickText *item)
3875{
3876 QFontMetricsF fm(item->font());
3877 return ((item->height() - item->contentHeight() - item->topPadding() - item->bottomPadding()) / 2) + fm.ascent() + item->topPadding();
3878}
3879
3880static qreal expectedBaselineBold(QQuickText *item)
3881{
3882 QFont font = item->font();
3883 font.setBold(true);
3884 QFontMetricsF fm(font);
3885 return fm.ascent() + item->topPadding();
3886}
3887
3888static qreal expectedBaselineImage(QQuickText *item)
3889{
3890 QFontMetricsF fm(item->font());
3891 // The line is positioned so the bottom of the line is aligned with the bottom of the image,
3892 // or image height - line height and the baseline is line position + ascent. Because
3893 // QTextLine's height is rounded up this can give slightly different results to image height
3894 // - descent.
3895 return 181 - qCeil(v: fm.height()) + fm.ascent() + item->topPadding();
3896}
3897
3898static qreal expectedBaselineCustom(QQuickText *item)
3899{
3900 QFontMetricsF fm(item->font());
3901 return 16 + fm.ascent() + item->topPadding();
3902}
3903
3904static qreal expectedBaselineScaled(QQuickText *item)
3905{
3906 QFont font = item->font();
3907 QTextLayout layout(item->text().replace(before: QLatin1Char('\n'), after: QChar::LineSeparator));
3908 do {
3909 layout.setFont(font);
3910 qreal width = 0;
3911 layout.beginLayout();
3912 for (QTextLine line = layout.createLine(); line.isValid(); line = layout.createLine()) {
3913 line.setLineWidth(FLT_MAX);
3914 width = qMax(a: line.naturalTextWidth(), b: width);
3915 }
3916 layout.endLayout();
3917
3918 if (width < item->width()) {
3919 QFontMetricsF fm(layout.font());
3920 return fm.ascent() + item->topPadding();
3921 }
3922 font.setPointSize(font.pointSize() - 1);
3923 } while (font.pointSize() > 0);
3924 return item->topPadding();
3925}
3926
3927static qreal expectedBaselineFixedBottom(QQuickText *item)
3928{
3929 QFontMetricsF fm(item->font());
3930 qreal dy = item->text().contains(c: QLatin1Char('\n'))
3931 ? 160
3932 : 180;
3933 return dy + fm.ascent() - item->bottomPadding();
3934}
3935
3936static qreal expectedBaselineProportionalBottom(QQuickText *item)
3937{
3938 QFontMetricsF fm(item->font());
3939 qreal dy = item->text().contains(c: QLatin1Char('\n'))
3940 ? 200 - (qCeil(v: fm.height()) * 3)
3941 : 200 - (qCeil(v: fm.height()) * 1.5);
3942 return dy + fm.ascent() - item->bottomPadding();
3943}
3944
3945void tst_qquicktext::baselineOffset_data()
3946{
3947 qRegisterMetaType<ExpectedBaseline>();
3948 QTest::addColumn<QString>(name: "text");
3949 QTest::addColumn<QString>(name: "wrappedText");
3950 QTest::addColumn<QByteArray>(name: "bindings");
3951 QTest::addColumn<ExpectedBaseline>(name: "expectedBaseline");
3952 QTest::addColumn<ExpectedBaseline>(name: "expectedBaselineEmpty");
3953
3954 QTest::newRow(dataTag: "top align")
3955 << "hello world"
3956 << "hello\nworld"
3957 << QByteArray("height: 200; verticalAlignment: Text.AlignTop")
3958 << &expectedBaselineTop
3959 << &expectedBaselineTop;
3960 QTest::newRow(dataTag: "bottom align")
3961 << "hello world"
3962 << "hello\nworld"
3963 << QByteArray("height: 200; verticalAlignment: Text.AlignBottom")
3964 << &expectedBaselineBottom
3965 << &expectedBaselineBottom;
3966 QTest::newRow(dataTag: "center align")
3967 << "hello world"
3968 << "hello\nworld"
3969 << QByteArray("height: 200; verticalAlignment: Text.AlignVCenter")
3970 << &expectedBaselineCenter
3971 << &expectedBaselineCenter;
3972
3973 QTest::newRow(dataTag: "bold")
3974 << "<b>hello world</b>"
3975 << "<b>hello<br/>world</b>"
3976 << QByteArray("height: 200")
3977 << &expectedBaselineBold
3978 << &expectedBaselineTop;
3979
3980 QTest::newRow(dataTag: "richText")
3981 << "<b>hello world</b>"
3982 << "<b>hello<br/>world</b>"
3983 << QByteArray("height: 200; textFormat: Text.RichText")
3984 << &expectedBaselineTop
3985 << &expectedBaselineTop;
3986
3987 QTest::newRow(dataTag: "elided")
3988 << "hello world"
3989 << "hello\nworld"
3990 << QByteArray("width: 20; height: 8; elide: Text.ElideRight")
3991 << &expectedBaselineTop
3992 << &expectedBaselineTop;
3993
3994 QTest::newRow(dataTag: "elided bottom align")
3995 << "hello world"
3996 << "hello\nworld!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
3997 << QByteArray("width: 200; height: 200; elide: Text.ElideRight; verticalAlignment: Text.AlignBottom")
3998 << &expectedBaselineBottom
3999 << &expectedBaselineBottom;
4000
4001 QTest::newRow(dataTag: "image")
4002 << "hello <img src=\"images/heart200.png\" /> world"
4003 << "hello <img src=\"images/heart200.png\" /><br/>world"
4004 << QByteArray("height: 200\n; baseUrl: \"") + testFileUrl(fileName: "reference").toEncoded() + QByteArray("\"")
4005 << &expectedBaselineImage
4006 << &expectedBaselineTop;
4007
4008 QTest::newRow(dataTag: "customLine")
4009 << "hello world"
4010 << "hello\nworld"
4011 << QByteArray("height: 200; onLineLaidOut: line.y += 16")
4012 << &expectedBaselineCustom
4013 << &expectedBaselineCustom;
4014
4015 QTest::newRow(dataTag: "scaled font")
4016 << "hello world"
4017 << "hello\nworld"
4018 << QByteArray("width: 200; minimumPointSize: 1; font.pointSize: 64; fontSizeMode: Text.HorizontalFit")
4019 << &expectedBaselineScaled
4020 << &expectedBaselineTop;
4021
4022 QTest::newRow(dataTag: "fixed line height top align")
4023 << "hello world"
4024 << "hello\nworld"
4025 << QByteArray("height: 200; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignTop")
4026 << &expectedBaselineTop
4027 << &expectedBaselineTop;
4028
4029 QTest::newRow(dataTag: "fixed line height bottom align")
4030 << "hello world"
4031 << "hello\nworld"
4032 << QByteArray("height: 200; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignBottom")
4033 << &expectedBaselineFixedBottom
4034 << &expectedBaselineFixedBottom;
4035
4036 QTest::newRow(dataTag: "proportional line height top align")
4037 << "hello world"
4038 << "hello\nworld"
4039 << QByteArray("height: 200; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignTop")
4040 << &expectedBaselineTop
4041 << &expectedBaselineTop;
4042
4043 QTest::newRow(dataTag: "proportional line height bottom align")
4044 << "hello world"
4045 << "hello\nworld"
4046 << QByteArray("height: 200; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignBottom")
4047 << &expectedBaselineProportionalBottom
4048 << &expectedBaselineProportionalBottom;
4049
4050 QTest::newRow(dataTag: "top align with padding")
4051 << "hello world"
4052 << "hello\nworld"
4053 << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; verticalAlignment: Text.AlignTop")
4054 << &expectedBaselineTop
4055 << &expectedBaselineTop;
4056 QTest::newRow(dataTag: "bottom align with padding")
4057 << "hello world"
4058 << "hello\nworld"
4059 << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; verticalAlignment: Text.AlignBottom")
4060 << &expectedBaselineBottom
4061 << &expectedBaselineBottom;
4062 QTest::newRow(dataTag: "center align with padding")
4063 << "hello world"
4064 << "hello\nworld"
4065 << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; verticalAlignment: Text.AlignVCenter")
4066 << &expectedBaselineCenter
4067 << &expectedBaselineCenter;
4068
4069 QTest::newRow(dataTag: "bold width padding")
4070 << "<b>hello world</b>"
4071 << "<b>hello<br/>world</b>"
4072 << QByteArray("height: 200; topPadding: 10; bottomPadding: 20")
4073 << &expectedBaselineBold
4074 << &expectedBaselineTop;
4075
4076 QTest::newRow(dataTag: "richText with padding")
4077 << "<b>hello world</b>"
4078 << "<b>hello<br/>world</b>"
4079 << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; textFormat: Text.RichText")
4080 << &expectedBaselineTop
4081 << &expectedBaselineTop;
4082
4083 QTest::newRow(dataTag: "elided with padding")
4084 << "hello world"
4085 << "hello\nworld"
4086 << QByteArray("width: 20; height: 8; topPadding: 10; bottomPadding: 20; elide: Text.ElideRight")
4087 << &expectedBaselineTop
4088 << &expectedBaselineTop;
4089
4090 QTest::newRow(dataTag: "elided bottom align with padding")
4091 << "hello world"
4092 << "hello\nworld!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
4093 << QByteArray("width: 200; height: 200; topPadding: 10; bottomPadding: 20; elide: Text.ElideRight; verticalAlignment: Text.AlignBottom")
4094 << &expectedBaselineBottom
4095 << &expectedBaselineBottom;
4096
4097 QTest::newRow(dataTag: "image with padding")
4098 << "hello <img src=\"images/heart200.png\" /> world"
4099 << "hello <img src=\"images/heart200.png\" /><br/>world"
4100 << QByteArray("height: 200\n; topPadding: 10; bottomPadding: 20; baseUrl: \"") + testFileUrl(fileName: "reference").toEncoded() + QByteArray("\"")
4101 << &expectedBaselineImage
4102 << &expectedBaselineTop;
4103
4104 QTest::newRow(dataTag: "customLine with padding")
4105 << "hello world"
4106 << "hello\nworld"
4107 << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; onLineLaidOut: line.y += 16")
4108 << &expectedBaselineCustom
4109 << &expectedBaselineCustom;
4110
4111 QTest::newRow(dataTag: "scaled font with padding")
4112 << "hello world"
4113 << "hello\nworld"
4114 << QByteArray("width: 200; topPadding: 10; bottomPadding: 20; minimumPointSize: 1; font.pointSize: 64; fontSizeMode: Text.HorizontalFit")
4115 << &expectedBaselineScaled
4116 << &expectedBaselineTop;
4117
4118 QTest::newRow(dataTag: "fixed line height top align with padding")
4119 << "hello world"
4120 << "hello\nworld"
4121 << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignTop")
4122 << &expectedBaselineTop
4123 << &expectedBaselineTop;
4124
4125 QTest::newRow(dataTag: "fixed line height bottom align with padding")
4126 << "hello world"
4127 << "hello\nworld"
4128 << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.FixedHeight; lineHeight: 20; verticalAlignment: Text.AlignBottom")
4129 << &expectedBaselineFixedBottom
4130 << &expectedBaselineFixedBottom;
4131
4132 QTest::newRow(dataTag: "proportional line height top align with padding")
4133 << "hello world"
4134 << "hello\nworld"
4135 << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignTop")
4136 << &expectedBaselineTop
4137 << &expectedBaselineTop;
4138
4139 QTest::newRow(dataTag: "proportional line height bottom align with padding")
4140 << "hello world"
4141 << "hello\nworld"
4142 << QByteArray("height: 200; topPadding: 10; bottomPadding: 20; lineHeightMode: Text.ProportionalHeight; lineHeight: 1.5; verticalAlignment: Text.AlignBottom")
4143 << &expectedBaselineProportionalBottom
4144 << &expectedBaselineProportionalBottom;
4145}
4146
4147void tst_qquicktext::baselineOffset()
4148{
4149 QFETCH(QString, text);
4150 QFETCH(QString, wrappedText);
4151 QFETCH(QByteArray, bindings);
4152 QFETCH(ExpectedBaseline, expectedBaseline);
4153 QFETCH(ExpectedBaseline, expectedBaselineEmpty);
4154
4155 QQmlComponent component(&engine);
4156 component.setData(
4157 "import QtQuick 2.6\n"
4158 "Text {\n"
4159 + bindings + "\n"
4160 "}", baseUrl: QUrl());
4161
4162 QScopedPointer<QObject> object(component.create());
4163
4164 QQuickText *item = qobject_cast<QQuickText *>(object: object.data());
4165 QVERIFY(item);
4166
4167 {
4168 qreal baseline = expectedBaselineEmpty(item);
4169
4170 QCOMPARE(item->baselineOffset(), baseline);
4171
4172 item->setText(text);
4173 if (expectedBaseline != expectedBaselineEmpty)
4174 baseline = expectedBaseline(item);
4175
4176 QCOMPARE(item->baselineOffset(), baseline);
4177
4178 item->setText(wrappedText);
4179 QCOMPARE(item->baselineOffset(), expectedBaseline(item));
4180 }
4181
4182 QFont font = item->font();
4183 font.setPointSize(font.pointSize() + 8);
4184
4185 {
4186 QCOMPARE(item->baselineOffset(), expectedBaseline(item));
4187
4188 item->setText(text);
4189 qreal baseline = expectedBaseline(item);
4190 QCOMPARE(item->baselineOffset(), baseline);
4191
4192 item->setText(QString());
4193 if (expectedBaselineEmpty != expectedBaseline)
4194 baseline = expectedBaselineEmpty(item);
4195
4196 QCOMPARE(item->baselineOffset(), baseline);
4197 }
4198}
4199
4200void tst_qquicktext::htmlLists()
4201{
4202 QFETCH(QString, text);
4203 QFETCH(int, nbLines);
4204
4205 QQuickView *view = createView(filename: testFile(fileName: "htmlLists.qml"));
4206 QQuickText *textObject = view->rootObject()->findChild<QQuickText*>(aName: "myText");
4207
4208 QQuickTextPrivate *textPrivate = QQuickTextPrivate::get(t: textObject);
4209 QVERIFY(textPrivate != nullptr);
4210 QVERIFY(textPrivate->extra.isAllocated());
4211
4212 QVERIFY(textObject != nullptr);
4213 textObject->setText(text);
4214
4215 view->show();
4216 view->requestActivate();
4217 QVERIFY(QTest::qWaitForWindowActive(view));
4218
4219 QCOMPARE(textPrivate->extra->doc->lineCount(), nbLines);
4220
4221 delete view;
4222}
4223
4224void tst_qquicktext::htmlLists_data()
4225{
4226 QTest::addColumn<QString>(name: "text");
4227 QTest::addColumn<int>(name: "nbLines");
4228
4229 QTest::newRow(dataTag: "ordered list") << "<ol><li>one<li>two<li>three" << 3;
4230 QTest::newRow(dataTag: "ordered list closed") << "<ol><li>one</li></ol>" << 1;
4231 QTest::newRow(dataTag: "ordered list alpha") << "<ol type=\"a\"><li>one</li><li>two</li></ol>" << 2;
4232 QTest::newRow(dataTag: "ordered list upper alpha") << "<ol type=\"A\"><li>one</li><li>two</li></ol>" << 2;
4233 QTest::newRow(dataTag: "ordered list roman") << "<ol type=\"i\"><li>one</li><li>two</li></ol>" << 2;
4234 QTest::newRow(dataTag: "ordered list upper roman") << "<ol type=\"I\"><li>one</li><li>two</li></ol>" << 2;
4235 QTest::newRow(dataTag: "ordered list bad") << "<ol type=\"z\"><li>one</li><li>two</li></ol>" << 2;
4236 QTest::newRow(dataTag: "unordered list") << "<ul><li>one<li>two" << 2;
4237 QTest::newRow(dataTag: "unordered list closed") << "<ul><li>one</li><li>two</li></ul>" << 2;
4238 QTest::newRow(dataTag: "unordered list disc") << "<ul type=\"disc\"><li>one</li><li>two</li></ul>" << 2;
4239 QTest::newRow(dataTag: "unordered list square") << "<ul type=\"square\"><li>one</li><li>two</li></ul>" << 2;
4240 QTest::newRow(dataTag: "unordered list bad") << "<ul type=\"bad\"><li>one</li><li>two</li></ul>" << 2;
4241}
4242
4243void tst_qquicktext::elideBeforeMaximumLineCount()
4244{ // QTBUG-31471
4245 QQmlComponent component(&engine, testFile(fileName: "elideBeforeMaximumLineCount.qml"));
4246
4247 QScopedPointer<QObject> object(component.create());
4248
4249 QQuickText *item = qobject_cast<QQuickText *>(object: object.data());
4250 QVERIFY(item);
4251
4252 QCOMPARE(item->lineCount(), 2);
4253}
4254
4255void tst_qquicktext::hover()
4256{ // QTBUG-33842
4257 QQmlComponent component(&engine, testFile(fileName: "hover.qml"));
4258
4259 QScopedPointer<QObject> object(component.create());
4260
4261 QQuickWindow *window = qobject_cast<QQuickWindow *>(object: object.data());
4262 QVERIFY(window);
4263 window->show();
4264 QVERIFY(QTest::qWaitForWindowExposed(window));
4265
4266 QQuickMouseArea *mouseArea = window->property(name: "mouseArea").value<QQuickMouseArea *>();
4267 QVERIFY(mouseArea);
4268 QQuickText *textItem = window->property(name: "textItem").value<QQuickText *>();
4269 QVERIFY(textItem);
4270
4271 QVERIFY(!mouseArea->property("wasHovered").toBool());
4272
4273 QPoint center(window->width() / 2, window->height() / 2);
4274 QPoint delta(window->width() / 10, window->height() / 10);
4275
4276 QTest::mouseMove(window, pos: center - delta);
4277 QTest::mouseMove(window, pos: center + delta);
4278
4279 QVERIFY(mouseArea->property("wasHovered").toBool());
4280}
4281
4282void tst_qquicktext::growFromZeroWidth()
4283{
4284 QQmlComponent component(&engine, testFile(fileName: "growFromZeroWidth.qml"));
4285
4286 QScopedPointer<QObject> object(component.create());
4287
4288 QQuickText *text = qobject_cast<QQuickText *>(object: object.data());
4289 QVERIFY(text);
4290
4291 QCOMPARE(text->lineCount(), 3);
4292
4293 text->setWidth(80);
4294
4295 // the new width should force our contents to wrap
4296 QVERIFY(text->lineCount() > 3);
4297}
4298
4299void tst_qquicktext::padding()
4300{
4301 QScopedPointer<QQuickView> window(new QQuickView);
4302 window->setSource(testFileUrl(fileName: "padding.qml"));
4303 QTRY_COMPARE(window->status(), QQuickView::Ready);
4304 window->show();
4305 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4306 QQuickItem *root = window->rootObject();
4307 QVERIFY(root);
4308 QQuickText *obj = qobject_cast<QQuickText*>(object: root);
4309 QVERIFY(obj != nullptr);
4310
4311 qreal cw = obj->contentWidth();
4312 qreal ch = obj->contentHeight();
4313
4314 QVERIFY(cw > 0);
4315 QVERIFY(ch > 0);
4316
4317 QCOMPARE(obj->topPadding(), 20.0);
4318 QCOMPARE(obj->leftPadding(), 30.0);
4319 QCOMPARE(obj->rightPadding(), 40.0);
4320 QCOMPARE(obj->bottomPadding(), 50.0);
4321
4322 QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
4323 QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
4324
4325 obj->setTopPadding(2.25);
4326 QCOMPARE(obj->topPadding(), 2.25);
4327 QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
4328
4329 obj->setLeftPadding(3.75);
4330 QCOMPARE(obj->leftPadding(), 3.75);
4331 QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
4332
4333 obj->setRightPadding(4.4);
4334 QCOMPARE(obj->rightPadding(), 4.4);
4335 QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
4336
4337 obj->setBottomPadding(1.11);
4338 QCOMPARE(obj->bottomPadding(), 1.11);
4339 QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
4340
4341 obj->setWidth(cw / 2);
4342 obj->setElideMode(QQuickText::ElideRight);
4343 QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
4344 QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
4345 obj->setElideMode(QQuickText::ElideNone);
4346 obj->resetWidth();
4347
4348 obj->setWrapMode(QQuickText::WordWrap);
4349 QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
4350 QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
4351 obj->setWrapMode(QQuickText::NoWrap);
4352
4353 obj->setText("Qt");
4354 QVERIFY(obj->contentWidth() < cw);
4355 QCOMPARE(obj->contentHeight(), ch);
4356 cw = obj->contentWidth();
4357
4358 QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
4359 QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
4360
4361 obj->setFont(QFont("Courier", 96));
4362 QVERIFY(obj->contentWidth() > cw);
4363 QVERIFY(obj->contentHeight() > ch);
4364 cw = obj->contentWidth();
4365 ch = obj->contentHeight();
4366
4367 QCOMPARE(obj->implicitWidth(), cw + obj->leftPadding() + obj->rightPadding());
4368 QCOMPARE(obj->implicitHeight(), ch + obj->topPadding() + obj->bottomPadding());
4369
4370 obj->resetTopPadding();
4371 QCOMPARE(obj->topPadding(), 10.0);
4372 obj->resetLeftPadding();
4373 QCOMPARE(obj->leftPadding(), 10.0);
4374 obj->resetRightPadding();
4375 QCOMPARE(obj->rightPadding(), 10.0);
4376 obj->resetBottomPadding();
4377 QCOMPARE(obj->bottomPadding(), 10.0);
4378
4379 obj->resetPadding();
4380 QCOMPARE(obj->padding(), 0.0);
4381 QCOMPARE(obj->topPadding(), 0.0);
4382 QCOMPARE(obj->leftPadding(), 0.0);
4383 QCOMPARE(obj->rightPadding(), 0.0);
4384 QCOMPARE(obj->bottomPadding(), 0.0);
4385
4386 delete root;
4387}
4388
4389void tst_qquicktext::hintingPreference()
4390{
4391 {
4392 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\" }";
4393 QQmlComponent textComponent(&engine);
4394 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
4395 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
4396
4397 QVERIFY(textObject != nullptr);
4398 QCOMPARE((int)textObject->font().hintingPreference(), (int)QFont::PreferDefaultHinting);
4399
4400 delete textObject;
4401 }
4402 {
4403 QString componentStr = "import QtQuick 2.0\nText { text: \"Hello world!\"; font.hintingPreference: Font.PreferNoHinting }";
4404 QQmlComponent textComponent(&engine);
4405 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
4406 QQuickText *textObject = qobject_cast<QQuickText*>(object: textComponent.create());
4407
4408 QVERIFY(textObject != nullptr);
4409 QCOMPARE((int)textObject->font().hintingPreference(), (int)QFont::PreferNoHinting);
4410
4411 delete textObject;
4412 }
4413}
4414
4415
4416void tst_qquicktext::zeroWidthAndElidedDoesntRender()
4417{
4418 // Tests QTBUG-34990
4419
4420 QQmlComponent component(&engine, testFile(fileName: "ellipsisText.qml"));
4421
4422 QScopedPointer<QObject> object(component.create());
4423
4424 QQuickText *text = qobject_cast<QQuickText *>(object: object.data());
4425 QVERIFY(text);
4426
4427 QCOMPARE(text->contentWidth(), 0.0);
4428
4429 QQuickText *reference = text->findChild<QQuickText *>(aName: "elidedRef");
4430 QVERIFY(reference);
4431
4432 text->setWidth(10);
4433 QCOMPARE(text->contentWidth(), reference->contentWidth());
4434}
4435
4436void tst_qquicktext::hAlignWidthDependsOnImplicitWidth_data()
4437{
4438 QTest::addColumn<QQuickText::HAlignment>(name: "horizontalAlignment");
4439 QTest::addColumn<QQuickText::TextElideMode>(name: "elide");
4440 QTest::addColumn<int>(name: "extraWidth");
4441
4442 QTest::newRow(dataTag: "AlignHCenter, ElideNone, 0 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideNone << 0;
4443 QTest::newRow(dataTag: "AlignRight, ElideNone, 0 extraWidth") << QQuickText::AlignRight << QQuickText::ElideNone << 0;
4444 QTest::newRow(dataTag: "AlignHCenter, ElideRight, 0 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideRight << 0;
4445 QTest::newRow(dataTag: "AlignRight, ElideRight, 0 extraWidth") << QQuickText::AlignRight << QQuickText::ElideRight << 0;
4446 QTest::newRow(dataTag: "AlignHCenter, ElideNone, 20 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideNone << 20;
4447 QTest::newRow(dataTag: "AlignRight, ElideNone, 20 extraWidth") << QQuickText::AlignRight << QQuickText::ElideNone << 20;
4448 QTest::newRow(dataTag: "AlignHCenter, ElideRight, 20 extraWidth") << QQuickText::AlignHCenter << QQuickText::ElideRight << 20;
4449 QTest::newRow(dataTag: "AlignRight, ElideRight, 20 extraWidth") << QQuickText::AlignRight << QQuickText::ElideRight << 20;
4450}
4451
4452void tst_qquicktext::hAlignWidthDependsOnImplicitWidth()
4453{
4454 QFETCH(QQuickText::HAlignment, horizontalAlignment);
4455 QFETCH(QQuickText::TextElideMode, elide);
4456 QFETCH(int, extraWidth);
4457
4458 QScopedPointer<QQuickView> window(new QQuickView);
4459 window->setSource(testFileUrl(fileName: "hAlignWidthDependsOnImplicitWidth.qml"));
4460 QTRY_COMPARE(window->status(), QQuickView::Ready);
4461
4462 window->show();
4463 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4464
4465 QQuickItem *rect = window->rootObject();
4466 QVERIFY(rect);
4467
4468 QVERIFY(rect->setProperty("horizontalAlignment", horizontalAlignment));
4469 QVERIFY(rect->setProperty("elide", elide));
4470 QVERIFY(rect->setProperty("extraWidth", extraWidth));
4471
4472 QImage image = window->grabWindow();
4473 const int rectX = 100 * window->screen()->devicePixelRatio();
4474 QCOMPARE(numberOfNonWhitePixels(0, rectX - 1, image), 0);
4475
4476 QVERIFY(rect->setProperty("text", "this is mis-aligned"));
4477 image = window->grabWindow();
4478 QCOMPARE(numberOfNonWhitePixels(0, rectX - 1, image), 0);
4479}
4480
4481void tst_qquicktext::fontInfo()
4482{
4483 QQmlComponent component(&engine, testFile(fileName: "fontInfo.qml"));
4484
4485 QScopedPointer<QObject> object(component.create());
4486 QObject *root = object.data();
4487
4488 QQuickText *main = root->findChild<QQuickText *>(aName: "main");
4489 QVERIFY(main);
4490 QCOMPARE(main->font().pixelSize(), 1000);
4491
4492 QQuickText *copy = root->findChild<QQuickText *>(aName: "copy");
4493 QVERIFY(copy);
4494 QCOMPARE(copy->font().family(), QFontInfo(QFont()).family());
4495 QVERIFY(copy->font().pixelSize() < 1000);
4496}
4497
4498void tst_qquicktext::initialContentHeight()
4499{
4500 QQmlComponent component(&engine, testFile(fileName: "contentHeight.qml"));
4501 QVERIFY(component.isReady());
4502 QScopedPointer<QObject> object(component.create());
4503 QObject *root = object.data();
4504 QVERIFY(root);
4505 QQuickText *text = qobject_cast<QQuickText *>(object: root);
4506 QVERIFY(text);
4507 QCOMPARE(text->height(), text->contentHeight());
4508}
4509
4510void tst_qquicktext::implicitSizeChangeRewrap()
4511{
4512 QScopedPointer<QQuickView> window(new QQuickView);
4513 window->setSource(testFileUrl(fileName: "implicitSizeChangeRewrap.qml"));
4514 QTRY_COMPARE(window->status(), QQuickView::Ready);
4515
4516 window->show();
4517 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4518
4519 QObject *root = window->rootObject();
4520
4521 QQuickText *text = root->findChild<QQuickText *>(aName: "text");
4522 QVERIFY(text != nullptr);
4523
4524 QVERIFY(text->contentWidth() < window->width());
4525}
4526
4527void tst_qquicktext::verticallyAlignedImageInTable()
4528{
4529 QScopedPointer<QQuickView> window(new QQuickView);
4530 window->setSource(testFileUrl(fileName: "verticallyAlignedImageInTable.qml"));
4531 QTRY_COMPARE(window->status(), QQuickView::Ready);
4532
4533 window->show();
4534 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4535
4536 // Don't crash
4537}
4538
4539void tst_qquicktext::transparentBackground()
4540{
4541 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
4542 || (QGuiApplication::platformName() == QLatin1String("minimal")))
4543 QSKIP("Skipping due to grabToImage not functional on offscreen/minimal platforms");
4544
4545 QScopedPointer<QQuickView> window(new QQuickView);
4546 window->setSource(testFileUrl(fileName: "transparentBackground.qml"));
4547 QTRY_COMPARE(window->status(), QQuickView::Ready);
4548
4549 window->show();
4550 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4551 QImage img = window->grabWindow();
4552 QCOMPARE(img.isNull(), false);
4553
4554 QColor color = img.pixelColor(x: 0, y: 0);
4555 QCOMPARE(color.red(), 255);
4556 QCOMPARE(color.blue(), 255);
4557 QCOMPARE(color.green(), 255);
4558}
4559
4560void tst_qquicktext::displaySuperscriptedTag()
4561{
4562 if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
4563 || (QGuiApplication::platformName() == QLatin1String("minimal")))
4564 QSKIP("Skipping due to grabToImage not functional on offscreen/minimal platforms");
4565
4566 QScopedPointer<QQuickView> window(new QQuickView);
4567 window->setSource(testFileUrl(fileName: "displaySuperscriptedTag.qml"));
4568 QTRY_COMPARE(window->status(), QQuickView::Ready);
4569
4570 window->show();
4571 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
4572
4573 QQuickText *text = window->findChild<QQuickText *>(aName: "text");
4574 QVERIFY(text);
4575
4576 QImage img = window->grabWindow();
4577 QCOMPARE(img.isNull(), false);
4578
4579 QColor color = img.pixelColor(x: 1, y: static_cast<int>(text->contentHeight()) / 4 * 3);
4580 QCOMPARE(color.red(), 255);
4581 QCOMPARE(color.blue(), 255);
4582 QCOMPARE(color.green(), 255);
4583}
4584
4585QTEST_MAIN(tst_qquicktext)
4586
4587#include "tst_qquicktext.moc"
4588

source code of qtdeclarative/tests/auto/quick/qquicktext/tst_qquicktext.cpp