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 QtQuick module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
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 Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qquicktext_p.h"
41#include "qquicktext_p_p.h"
42
43#include <private/qqmldebugserviceinterfaces_p.h>
44#include <private/qqmldebugconnector_p.h>
45
46#include <QtQuick/private/qsgcontext_p.h>
47#include <private/qqmlglobal_p.h>
48#include <private/qsgadaptationlayer_p.h>
49#include "qquicktextnode_p.h"
50#include "qquickimage_p_p.h"
51#include "qquicktextutil_p.h"
52#include "qquicktextdocument_p.h"
53
54#include <QtQuick/private/qsgtexture_p.h>
55
56#include <QtQml/qqmlinfo.h>
57#include <QtGui/qevent.h>
58#include <QtGui/qabstracttextdocumentlayout.h>
59#include <QtGui/qpainter.h>
60#include <QtGui/qtextdocument.h>
61#include <QtGui/qtextobject.h>
62#include <QtGui/qtextcursor.h>
63#include <QtGui/qguiapplication.h>
64#include <QtGui/qinputmethod.h>
65
66#include <private/qtextengine_p.h>
67#include <private/qquickstyledtext_p.h>
68#include <QtQuick/private/qquickpixmapcache_p.h>
69
70#include <qmath.h>
71#include <limits.h>
72
73QT_BEGIN_NAMESPACE
74
75Q_DECLARE_LOGGING_CATEGORY(DBG_HOVER_TRACE)
76
77const QChar QQuickTextPrivate::elideChar = QChar(0x2026);
78
79QQuickTextPrivate::QQuickTextPrivate()
80 : fontInfo(font), elideLayout(nullptr), textLine(nullptr), lineWidth(0)
81 , color(0xFF000000), linkColor(0xFF0000FF), styleColor(0xFF000000)
82 , lineCount(1), multilengthEos(-1)
83 , elideMode(QQuickText::ElideNone), hAlign(QQuickText::AlignLeft), vAlign(QQuickText::AlignTop)
84 , format(QQuickText::AutoText), wrapMode(QQuickText::NoWrap)
85 , style(QQuickText::Normal)
86 , renderType(QQuickTextUtil::textRenderType<QQuickText>())
87 , updateType(UpdatePaintNode)
88 , maximumLineCountValid(false), updateOnComponentComplete(true), richText(false)
89 , styledText(false), widthExceeded(false), heightExceeded(false), internalWidthUpdate(false)
90 , requireImplicitSize(false), implicitWidthValid(false), implicitHeightValid(false)
91 , truncated(false), hAlignImplicit(true), rightToLeftText(false)
92 , layoutTextElided(false), textHasChanged(true), needToUpdateLayout(false), formatModifiesFontSize(false)
93 , polishSize(false)
94 , updateSizeRecursionGuard(false)
95{
96 implicitAntialiasing = true;
97}
98
99QQuickTextPrivate::ExtraData::ExtraData()
100 : padding(0)
101 , topPadding(0)
102 , leftPadding(0)
103 , rightPadding(0)
104 , bottomPadding(0)
105 , explicitTopPadding(false)
106 , explicitLeftPadding(false)
107 , explicitRightPadding(false)
108 , explicitBottomPadding(false)
109 , lineHeight(1.0)
110 , doc(nullptr)
111 , minimumPixelSize(12)
112 , minimumPointSize(12)
113 , nbActiveDownloads(0)
114 , maximumLineCount(INT_MAX)
115 , lineHeightValid(false)
116 , lineHeightMode(QQuickText::ProportionalHeight)
117 , fontSizeMode(QQuickText::FixedSize)
118{
119}
120
121void QQuickTextPrivate::init()
122{
123 Q_Q(QQuickText);
124 q->setAcceptedMouseButtons(Qt::LeftButton);
125 q->setFlag(QQuickItem::ItemHasContents);
126}
127
128QQuickTextPrivate::~QQuickTextPrivate()
129{
130 delete elideLayout;
131 delete textLine; textLine = nullptr;
132
133 if (extra.isAllocated()) {
134 qDeleteAll(extra->imgTags);
135 extra->imgTags.clear();
136 }
137}
138
139qreal QQuickTextPrivate::getImplicitWidth() const
140{
141 if (!requireImplicitSize) {
142 // We don't calculate implicitWidth unless it is required.
143 // We need to force a size update now to ensure implicitWidth is calculated
144 QQuickTextPrivate *me = const_cast<QQuickTextPrivate*>(this);
145 me->requireImplicitSize = true;
146 me->updateSize();
147 }
148 return implicitWidth;
149}
150
151qreal QQuickTextPrivate::getImplicitHeight() const
152{
153 if (!requireImplicitSize) {
154 QQuickTextPrivate *me = const_cast<QQuickTextPrivate*>(this);
155 me->requireImplicitSize = true;
156 me->updateSize();
157 }
158 return implicitHeight;
159}
160
161qreal QQuickTextPrivate::availableWidth() const
162{
163 Q_Q(const QQuickText);
164 return q->width() - q->leftPadding() - q->rightPadding();
165}
166
167qreal QQuickTextPrivate::availableHeight() const
168{
169 Q_Q(const QQuickText);
170 return q->height() - q->topPadding() - q->bottomPadding();
171}
172
173void QQuickTextPrivate::setTopPadding(qreal value, bool reset)
174{
175 Q_Q(QQuickText);
176 qreal oldPadding = q->topPadding();
177 if (!reset || extra.isAllocated()) {
178 extra.value().topPadding = value;
179 extra.value().explicitTopPadding = !reset;
180 }
181 if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
182 updateSize();
183 emit q->topPaddingChanged();
184 }
185}
186
187void QQuickTextPrivate::setLeftPadding(qreal value, bool reset)
188{
189 Q_Q(QQuickText);
190 qreal oldPadding = q->leftPadding();
191 if (!reset || extra.isAllocated()) {
192 extra.value().leftPadding = value;
193 extra.value().explicitLeftPadding = !reset;
194 }
195 if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
196 updateSize();
197 emit q->leftPaddingChanged();
198 }
199}
200
201void QQuickTextPrivate::setRightPadding(qreal value, bool reset)
202{
203 Q_Q(QQuickText);
204 qreal oldPadding = q->rightPadding();
205 if (!reset || extra.isAllocated()) {
206 extra.value().rightPadding = value;
207 extra.value().explicitRightPadding = !reset;
208 }
209 if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
210 updateSize();
211 emit q->rightPaddingChanged();
212 }
213}
214
215void QQuickTextPrivate::setBottomPadding(qreal value, bool reset)
216{
217 Q_Q(QQuickText);
218 qreal oldPadding = q->bottomPadding();
219 if (!reset || extra.isAllocated()) {
220 extra.value().bottomPadding = value;
221 extra.value().explicitBottomPadding = !reset;
222 }
223 if ((!reset && !qFuzzyCompare(oldPadding, value)) || (reset && !qFuzzyCompare(oldPadding, padding()))) {
224 updateSize();
225 emit q->bottomPaddingChanged();
226 }
227}
228
229/*!
230 \qmlproperty bool QtQuick::Text::antialiasing
231
232 Used to decide if the Text should use antialiasing or not. Only Text
233 with renderType of Text.NativeRendering can disable antialiasing.
234
235 The default is true.
236*/
237
238void QQuickText::q_updateLayout()
239{
240 Q_D(QQuickText);
241 d->updateLayout();
242}
243
244void QQuickTextPrivate::updateLayout()
245{
246 Q_Q(QQuickText);
247 if (!q->isComponentComplete()) {
248 updateOnComponentComplete = true;
249 return;
250 }
251 updateOnComponentComplete = false;
252 layoutTextElided = false;
253
254 if (extra.isAllocated())
255 extra->visibleImgTags.clear();
256 needToUpdateLayout = false;
257
258 // Setup instance of QTextLayout for all cases other than richtext
259 if (!richText) {
260 if (textHasChanged) {
261 if (styledText && !text.isEmpty()) {
262 layout.setFont(font);
263 // needs temporary bool because formatModifiesFontSize is in a bit-field
264 bool fontSizeModified = false;
265 QList<QQuickStyledTextImgTag*> someImgTags = extra.isAllocated() ? extra->imgTags : QList<QQuickStyledTextImgTag*>();
266 QQuickStyledText::parse(text, layout, someImgTags, q->baseUrl(), qmlContext(q), !maximumLineCountValid, &fontSizeModified);
267 if (someImgTags.size() || extra.isAllocated())
268 extra.value().imgTags = someImgTags;
269 formatModifiesFontSize = fontSizeModified;
270 multilengthEos = -1;
271 } else {
272 QString tmp = text;
273 multilengthEos = tmp.indexOf(QLatin1Char('\x9c'));
274 if (multilengthEos != -1)
275 tmp = tmp.mid(0, multilengthEos);
276 tmp.replace(QLatin1Char('\n'), QChar::LineSeparator);
277 layout.setText(tmp);
278 }
279 textHasChanged = false;
280 }
281 } else if (extra.isAllocated() && extra->lineHeightValid) {
282 ensureDoc();
283 QTextBlockFormat::LineHeightTypes type;
284 type = lineHeightMode() == QQuickText::FixedHeight ? QTextBlockFormat::FixedHeight : QTextBlockFormat::ProportionalHeight;
285 QTextBlockFormat blockFormat;
286 blockFormat.setLineHeight((lineHeightMode() == QQuickText::FixedHeight ? lineHeight() : lineHeight() * 100), type);
287 for (QTextBlock it = extra->doc->begin(); it != extra->doc->end(); it = it.next()) {
288 QTextCursor cursor(it);
289 cursor.mergeBlockFormat(blockFormat);
290 }
291 }
292
293 updateSize();
294
295 if (needToUpdateLayout) {
296 needToUpdateLayout = false;
297 textHasChanged = true;
298 updateLayout();
299 }
300
301 q->polish();
302}
303
304void QQuickText::imageDownloadFinished()
305{
306 Q_D(QQuickText);
307
308 (d->extra->nbActiveDownloads)--;
309
310 // when all the remote images have been downloaded,
311 // if one of the sizes was not specified at parsing time
312 // we use the implicit size from pixmapcache and re-layout.
313
314 if (d->extra.isAllocated() && d->extra->nbActiveDownloads == 0) {
315 bool needToUpdateLayout = false;
316 for (QQuickStyledTextImgTag *img : qAsConst(d->extra->visibleImgTags)) {
317 if (!img->size.isValid()) {
318 img->size = img->pix->implicitSize();
319 needToUpdateLayout = true;
320 }
321 }
322
323 if (needToUpdateLayout) {
324 d->textHasChanged = true;
325 d->updateLayout();
326 } else {
327 d->updateType = QQuickTextPrivate::UpdatePaintNode;
328 update();
329 }
330 }
331}
332
333void QQuickTextPrivate::updateBaseline(qreal baseline, qreal dy)
334{
335 Q_Q(QQuickText);
336
337 qreal yoff = 0;
338
339 if (q->heightValid()) {
340 if (vAlign == QQuickText::AlignBottom)
341 yoff = dy;
342 else if (vAlign == QQuickText::AlignVCenter)
343 yoff = dy/2;
344 }
345
346 q->setBaselineOffset(baseline + yoff + q->topPadding());
347}
348
349void QQuickTextPrivate::signalSizeChange(const QSizeF &previousSize)
350{
351 Q_Q(QQuickText);
352
353 if (layedOutTextRect.size() != previousSize) {
354 emit q->contentSizeChanged();
355 if (layedOutTextRect.width() != previousSize.width())
356 emit q->contentWidthChanged(layedOutTextRect.width());
357 if (layedOutTextRect.height() != previousSize.height())
358 emit q->contentHeightChanged(layedOutTextRect.height());
359 }
360}
361
362void QQuickTextPrivate::updateSize()
363{
364 Q_Q(QQuickText);
365
366 if (!q->isComponentComplete()) {
367 updateOnComponentComplete = true;
368 return;
369 }
370
371 if (!requireImplicitSize) {
372 implicitWidthChanged();
373 implicitHeightChanged();
374 // if the implicitWidth is used, then updateSize() has already been called (recursively)
375 if (requireImplicitSize)
376 return;
377 }
378
379 qreal hPadding = q->leftPadding() + q->rightPadding();
380 qreal vPadding = q->topPadding() + q->bottomPadding();
381
382 const QSizeF previousSize = layedOutTextRect.size();
383
384 if (text.isEmpty() && !isLineLaidOutConnected() && fontSizeMode() == QQuickText::FixedSize) {
385 // How much more expensive is it to just do a full layout on an empty string here?
386 // There may be subtle differences in the height and baseline calculations between
387 // QTextLayout and QFontMetrics and the number of variables that can affect the size
388 // and position of a line is increasing.
389 QFontMetricsF fm(font);
390 qreal fontHeight = qCeil(fm.height()); // QScriptLine and therefore QTextLine rounds up
391 if (!richText) { // line height, so we will as well.
392 fontHeight = lineHeightMode() == QQuickText::FixedHeight
393 ? lineHeight()
394 : fontHeight * lineHeight();
395 }
396 updateBaseline(fm.ascent(), q->height() - fontHeight - vPadding);
397 q->setImplicitSize(hPadding, fontHeight + vPadding);
398 layedOutTextRect = QRectF(0, 0, 0, fontHeight);
399 advance = QSizeF();
400 signalSizeChange(previousSize);
401 updateType = UpdatePaintNode;
402 q->update();
403 return;
404 }
405
406 QSizeF size(0, 0);
407
408 //setup instance of QTextLayout for all cases other than richtext
409 if (!richText) {
410 qreal baseline = 0;
411 QRectF textRect = setupTextLayout(&baseline);
412
413 if (internalWidthUpdate) // probably the result of a binding loop, but by letting it
414 return; // get this far we'll get a warning to that effect if it is.
415
416 layedOutTextRect = textRect;
417 size = textRect.size();
418 updateBaseline(baseline, q->height() - size.height() - vPadding);
419 } else {
420 widthExceeded = true; // always relayout rich text on width changes..
421 heightExceeded = false; // rich text layout isn't affected by height changes.
422 ensureDoc();
423 extra->doc->setDefaultFont(font);
424 QQuickText::HAlignment horizontalAlignment = q->effectiveHAlign();
425 if (rightToLeftText) {
426 if (horizontalAlignment == QQuickText::AlignLeft)
427 horizontalAlignment = QQuickText::AlignRight;
428 else if (horizontalAlignment == QQuickText::AlignRight)
429 horizontalAlignment = QQuickText::AlignLeft;
430 }
431 QTextOption option;
432 option.setAlignment((Qt::Alignment)int(horizontalAlignment | vAlign));
433 option.setWrapMode(QTextOption::WrapMode(wrapMode));
434 option.setUseDesignMetrics(renderType != QQuickText::NativeRendering);
435 extra->doc->setDefaultTextOption(option);
436 qreal naturalWidth = 0;
437 if (requireImplicitSize && q->widthValid()) {
438 extra->doc->setTextWidth(-1);
439 naturalWidth = extra->doc->idealWidth();
440 const bool wasInLayout = internalWidthUpdate;
441 internalWidthUpdate = true;
442 q->setImplicitWidth(naturalWidth + hPadding);
443 internalWidthUpdate = wasInLayout;
444 }
445 if (internalWidthUpdate)
446 return;
447
448 extra->doc->setPageSize(QSizeF(q->width(), -1));
449 if (q->widthValid() && (wrapMode != QQuickText::NoWrap || extra->doc->idealWidth() < availableWidth()))
450 extra->doc->setTextWidth(availableWidth());
451 else
452 extra->doc->setTextWidth(extra->doc->idealWidth()); // ### Text does not align if width is not set (QTextDoc bug)
453
454 QSizeF dsize = extra->doc->size();
455 layedOutTextRect = QRectF(QPointF(0,0), dsize);
456 size = QSizeF(extra->doc->idealWidth(),dsize.height());
457
458 QFontMetricsF fm(font);
459 updateBaseline(fm.ascent(), q->height() - size.height() - vPadding);
460
461 //### need to confirm cost of always setting these for richText
462 internalWidthUpdate = true;
463 qreal oldWidth = q->width();
464 qreal iWidth = -1;
465 if (!q->widthValid())
466 iWidth = size.width();
467 if (iWidth > -1)
468 q->setImplicitSize(iWidth + hPadding, size.height() + vPadding);
469 internalWidthUpdate = false;
470
471 // If the implicit width update caused a recursive change of the width,
472 // we will have skipped integral parts of the layout due to the
473 // internalWidthUpdate recursion guard. To make sure everything is up
474 // to date, we need to run a second pass over the layout when updateSize()
475 // is done.
476 if (!qFuzzyCompare(q->width(), oldWidth) && !updateSizeRecursionGuard) {
477 updateSizeRecursionGuard = true;
478 updateSize();
479 updateSizeRecursionGuard = false;
480 } else {
481 if (iWidth == -1)
482 q->setImplicitHeight(size.height() + vPadding);
483
484 QTextBlock firstBlock = extra->doc->firstBlock();
485 while (firstBlock.layout()->lineCount() == 0)
486 firstBlock = firstBlock.next();
487
488 QTextBlock lastBlock = extra->doc->lastBlock();
489 while (lastBlock.layout()->lineCount() == 0)
490 lastBlock = lastBlock.previous();
491
492 if (firstBlock.lineCount() > 0 && lastBlock.lineCount() > 0) {
493 QTextLine firstLine = firstBlock.layout()->lineAt(0);
494 QTextLine lastLine = lastBlock.layout()->lineAt(lastBlock.layout()->lineCount() - 1);
495 advance = QSizeF(lastLine.horizontalAdvance(),
496 (lastLine.y() + lastBlock.layout()->position().y()) - (firstLine.y() + firstBlock.layout()->position().y()));
497 } else {
498 advance = QSizeF();
499 }
500 }
501 }
502
503 signalSizeChange(previousSize);
504 updateType = UpdatePaintNode;
505 q->update();
506}
507
508QQuickTextLine::QQuickTextLine()
509 : QObject(), m_line(nullptr), m_height(0), m_lineOffset(0)
510{
511}
512
513void QQuickTextLine::setLine(QTextLine *line)
514{
515 m_line = line;
516}
517
518void QQuickTextLine::setLineOffset(int offset)
519{
520 m_lineOffset = offset;
521}
522
523void QQuickTextLine::setFullLayoutTextLength(int length)
524{
525 m_fullLayoutTextLength = length;
526}
527
528int QQuickTextLine::number() const
529{
530 if (m_line)
531 return m_line->lineNumber() + m_lineOffset;
532 return 0;
533}
534
535qreal QQuickTextLine::implicitWidth() const
536{
537 if (m_line)
538 return m_line->naturalTextWidth();
539 return 0;
540}
541
542bool QQuickTextLine::isLast() const
543{
544 if (m_line && (m_line->textStart() + m_line->textLength()) == m_fullLayoutTextLength) {
545 // Ensure that isLast will change if the user reduced the width of the line
546 // so that the text no longer fits.
547 return m_line->width() >= m_line->naturalTextWidth();
548 }
549
550 return false;
551}
552
553qreal QQuickTextLine::width() const
554{
555 if (m_line)
556 return m_line->width();
557 return 0;
558}
559
560void QQuickTextLine::setWidth(qreal width)
561{
562 if (m_line)
563 m_line->setLineWidth(width);
564}
565
566qreal QQuickTextLine::height() const
567{
568 if (m_height)
569 return m_height;
570 if (m_line)
571 return m_line->height();
572 return 0;
573}
574
575void QQuickTextLine::setHeight(qreal height)
576{
577 if (m_line)
578 m_line->setPosition(QPointF(m_line->x(), m_line->y() - m_line->height() + height));
579 m_height = height;
580}
581
582qreal QQuickTextLine::x() const
583{
584 if (m_line)
585 return m_line->x();
586 return 0;
587}
588
589void QQuickTextLine::setX(qreal x)
590{
591 if (m_line)
592 m_line->setPosition(QPointF(x, m_line->y()));
593}
594
595qreal QQuickTextLine::y() const
596{
597 if (m_line)
598 return m_line->y();
599 return 0;
600}
601
602void QQuickTextLine::setY(qreal y)
603{
604 if (m_line)
605 m_line->setPosition(QPointF(m_line->x(), y));
606}
607
608bool QQuickTextPrivate::isLineLaidOutConnected()
609{
610 Q_Q(QQuickText);
611 IS_SIGNAL_CONNECTED(q, QQuickText, lineLaidOut, (QQuickTextLine *));
612}
613
614void QQuickTextPrivate::setupCustomLineGeometry(QTextLine &line, qreal &height, int fullLayoutTextLength, int lineOffset)
615{
616 Q_Q(QQuickText);
617
618 if (!textLine)
619 textLine = new QQuickTextLine;
620 textLine->setFullLayoutTextLength(fullLayoutTextLength);
621 textLine->setLine(&line);
622 textLine->setY(height);
623 textLine->setHeight(0);
624 textLine->setLineOffset(lineOffset);
625
626 // use the text item's width by default if it has one and wrap is on or text must be aligned
627 if (q->widthValid() && (q->wrapMode() != QQuickText::NoWrap ||
628 q->effectiveHAlign() != QQuickText::AlignLeft))
629 textLine->setWidth(availableWidth());
630 else
631 textLine->setWidth(INT_MAX);
632 if (lineHeight() != 1.0)
633 textLine->setHeight((lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : line.height() * lineHeight());
634
635 emit q->lineLaidOut(textLine);
636
637 height += textLine->height();
638}
639
640void QQuickTextPrivate::elideFormats(
641 const int start, const int length, int offset, QVector<QTextLayout::FormatRange> *elidedFormats)
642{
643 const int end = start + length;
644 const QVector<QTextLayout::FormatRange> formats = layout.formats();
645 for (int i = 0; i < formats.count(); ++i) {
646 QTextLayout::FormatRange format = formats.at(i);
647 const int formatLength = qMin(format.start + format.length, end) - qMax(format.start, start);
648 if (formatLength > 0) {
649 format.start = qMax(offset, format.start - start + offset);
650 format.length = formatLength;
651 elidedFormats->append(format);
652 }
653 }
654}
655
656QString QQuickTextPrivate::elidedText(qreal lineWidth, const QTextLine &line, QTextLine *nextLine) const
657{
658 if (nextLine) {
659 return layout.engine()->elidedText(
660 Qt::TextElideMode(elideMode),
661 QFixed::fromReal(lineWidth),
662 0,
663 line.textStart(),
664 line.textLength() + nextLine->textLength());
665 } else {
666 QString elideText = layout.text().mid(line.textStart(), line.textLength());
667 if (!styledText) {
668 // QFontMetrics won't help eliding styled text.
669 elideText[elideText.length() - 1] = elideChar;
670 // Appending the elide character may push the line over the maximum width
671 // in which case the elided text will need to be elided.
672 QFontMetricsF metrics(layout.font());
673 if (metrics.horizontalAdvance(elideChar) + line.naturalTextWidth() >= lineWidth)
674 elideText = metrics.elidedText(elideText, Qt::TextElideMode(elideMode), lineWidth);
675 }
676 return elideText;
677 }
678}
679
680void QQuickTextPrivate::clearFormats()
681{
682 layout.clearFormats();
683 if (elideLayout)
684 elideLayout->clearFormats();
685}
686
687/*!
688 Lays out the QQuickTextPrivate::layout QTextLayout in the constraints of the QQuickText.
689
690 Returns the size of the final text. This can be used to position the text vertically (the text is
691 already absolutely positioned horizontally).
692*/
693
694QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
695{
696 Q_Q(QQuickText);
697
698 bool singlelineElide = elideMode != QQuickText::ElideNone && q->widthValid();
699 bool multilineElide = elideMode == QQuickText::ElideRight
700 && q->widthValid()
701 && (q->heightValid() || maximumLineCountValid);
702
703 if ((!requireImplicitSize || (implicitWidthValid && implicitHeightValid))
704 && ((singlelineElide && availableWidth() <= 0.)
705 || (multilineElide && q->heightValid() && availableHeight() <= 0.))) {
706 // we are elided and we have a zero width or height
707 widthExceeded = q->widthValid() && availableWidth() <= 0.;
708 heightExceeded = q->heightValid() && availableHeight() <= 0.;
709
710 if (!truncated) {
711 truncated = true;
712 emit q->truncatedChanged();
713 }
714 if (lineCount) {
715 lineCount = 0;
716 emit q->lineCountChanged();
717 }
718
719 if (qFuzzyIsNull(q->width())) {
720 layout.setText(QString());
721 textHasChanged = true;
722 }
723
724 QFontMetricsF fm(font);
725 qreal height = (lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : qCeil(fm.height()) * lineHeight();
726 *baseline = fm.ascent();
727 return QRectF(0, 0, 0, height);
728 }
729
730 bool shouldUseDesignMetrics = renderType != QQuickText::NativeRendering;
731 if (extra.isAllocated())
732 extra->visibleImgTags.clear();
733 layout.setCacheEnabled(true);
734 QTextOption textOption = layout.textOption();
735 if (textOption.alignment() != q->effectiveHAlign()
736 || textOption.wrapMode() != QTextOption::WrapMode(wrapMode)
737 || textOption.useDesignMetrics() != shouldUseDesignMetrics) {
738 textOption.setAlignment(Qt::Alignment(q->effectiveHAlign()));
739 textOption.setWrapMode(QTextOption::WrapMode(wrapMode));
740 textOption.setUseDesignMetrics(shouldUseDesignMetrics);
741 layout.setTextOption(textOption);
742 }
743 if (layout.font() != font)
744 layout.setFont(font);
745
746 lineWidth = (q->widthValid() || implicitWidthValid) && q->width() > 0
747 ? q->width()
748 : FLT_MAX;
749 qreal maxHeight = q->heightValid() ? availableHeight() : FLT_MAX;
750
751 const bool customLayout = isLineLaidOutConnected();
752 const bool wasTruncated = truncated;
753
754 bool canWrap = wrapMode != QQuickText::NoWrap && q->widthValid();
755
756 bool horizontalFit = fontSizeMode() & QQuickText::HorizontalFit && q->widthValid();
757 bool verticalFit = fontSizeMode() & QQuickText::VerticalFit
758 && (q->heightValid() || (maximumLineCountValid && canWrap));
759
760 const bool pixelSize = font.pixelSize() != -1;
761 QString layoutText = layout.text();
762
763 int largeFont = pixelSize ? font.pixelSize() : font.pointSize();
764 int smallFont = fontSizeMode() != QQuickText::FixedSize
765 ? qMin(pixelSize ? minimumPixelSize() : minimumPointSize(), largeFont)
766 : largeFont;
767 int scaledFontSize = largeFont;
768
769 bool widthChanged = false;
770 widthExceeded = availableWidth() <= 0 && (singlelineElide || canWrap || horizontalFit);
771 heightExceeded = availableHeight() <= 0 && (multilineElide || verticalFit);
772
773 QRectF br;
774
775 QFont scaledFont = font;
776
777 int visibleCount = 0;
778 bool elide;
779 qreal height = 0;
780 QString elideText;
781 bool once = true;
782 int elideStart = 0;
783 int elideEnd = 0;
784 bool noBreakLastLine = multilineElide && (wrapMode == QQuickText::Wrap || wrapMode == QQuickText::WordWrap);
785
786 int eos = multilengthEos;
787
788 // Repeated layouts with reduced font sizes or abbreviated strings may be required if the text
789 // doesn't fit within the item dimensions, or a binding to implicitWidth/Height changes
790 // the item dimensions.
791 for (;;) {
792 if (!once) {
793 if (pixelSize)
794 scaledFont.setPixelSize(scaledFontSize);
795 else
796 scaledFont.setPointSize(scaledFontSize);
797 if (layout.font() != scaledFont)
798 layout.setFont(scaledFont);
799 }
800
801 layout.beginLayout();
802
803 bool wrapped = false;
804 bool truncateHeight = false;
805 truncated = false;
806 elide = false;
807 int unwrappedLineCount = 1;
808 int maxLineCount = maximumLineCount();
809 height = 0;
810 qreal naturalHeight = 0;
811 qreal previousHeight = 0;
812 br = QRectF();
813
814 QRectF unelidedRect;
815 QTextLine line = layout.createLine();
816 for (visibleCount = 1; ; ++visibleCount) {
817 if (noBreakLastLine && visibleCount == maxLineCount)
818 layout.engine()->option.setWrapMode(QTextOption::WrapAnywhere);
819 if (customLayout) {
820 setupCustomLineGeometry(line, naturalHeight, layoutText.length());
821 } else {
822 setLineGeometry(line, lineWidth, naturalHeight);
823 }
824 if (noBreakLastLine && visibleCount == maxLineCount)
825 layout.engine()->option.setWrapMode(QTextOption::WrapMode(wrapMode));
826
827 unelidedRect = br.united(line.naturalTextRect());
828
829 // Elide the previous line if the accumulated height of the text exceeds the height
830 // of the element.
831 if (multilineElide && naturalHeight > maxHeight && visibleCount > 1) {
832 elide = true;
833 heightExceeded = true;
834 if (eos != -1) // There's an abbreviated string available, skip the rest as it's
835 break; // all going to be discarded.
836
837 truncated = true;
838 truncateHeight = true;
839
840 visibleCount -= 1;
841
842 QTextLine previousLine = layout.lineAt(visibleCount - 1);
843 elideText = layoutText.at(line.textStart() - 1) != QChar::LineSeparator
844 ? elidedText(line.width(), previousLine, &line)
845 : elidedText(line.width(), previousLine);
846 elideStart = previousLine.textStart();
847 // elideEnd isn't required for right eliding.
848
849 height = previousHeight;
850 break;
851 }
852
853 const QTextLine previousLine = line;
854 line = layout.createLine();
855 if (!line.isValid()) {
856 if (singlelineElide && visibleCount == 1 && previousLine.naturalTextWidth() > previousLine.width()) {
857 // Elide a single previousLine of text if its width exceeds the element width.
858 elide = true;
859 widthExceeded = true;
860 if (eos != -1) // There's an abbreviated string available.
861 break;
862
863 truncated = true;
864 elideText = layout.engine()->elidedText(
865 Qt::TextElideMode(elideMode),
866 QFixed::fromReal(previousLine.width()),
867 0,
868 previousLine.textStart(),
869 previousLine.textLength());
870 elideStart = previousLine.textStart();
871 elideEnd = elideStart + previousLine.textLength();
872 } else {
873 br = unelidedRect;
874 height = naturalHeight;
875 }
876 break;
877 } else {
878 const bool wrappedLine = layoutText.at(line.textStart() - 1) != QChar::LineSeparator;
879 wrapped |= wrappedLine;
880
881 if (!wrappedLine)
882 ++unwrappedLineCount;
883
884 // Stop if the maximum number of lines has been reached and elide the last line
885 // if enabled.
886 if (visibleCount == maxLineCount) {
887 truncated = true;
888 heightExceeded |= wrapped;
889
890 if (multilineElide) {
891 elide = true;
892 if (eos != -1) // There's an abbreviated string available
893 break;
894 elideText = wrappedLine
895 ? elidedText(previousLine.width(), previousLine, &line)
896 : elidedText(previousLine.width(), previousLine);
897 elideStart = previousLine.textStart();
898 // elideEnd isn't required for right eliding.
899 } else {
900 br = unelidedRect;
901 height = naturalHeight;
902 }
903 break;
904 }
905 }
906 br = unelidedRect;
907 previousHeight = height;
908 height = naturalHeight;
909 }
910 widthExceeded |= wrapped;
911
912 // Save the implicit size of the text on the first layout only.
913 if (once) {
914 once = false;
915
916 // If implicit sizes are required layout any additional lines up to the maximum line
917 // count.
918 if ((requireImplicitSize) && line.isValid() && unwrappedLineCount < maxLineCount) {
919 // Layout the remainder of the wrapped lines up to maxLineCount to get the implicit
920 // height.
921 for (int lineCount = layout.lineCount(); lineCount < maxLineCount; ++lineCount) {
922 line = layout.createLine();
923 if (!line.isValid())
924 break;
925 if (layoutText.at(line.textStart() - 1) == QChar::LineSeparator)
926 ++unwrappedLineCount;
927 setLineGeometry(line, lineWidth, naturalHeight);
928 }
929
930 // Create the remainder of the unwrapped lines up to maxLineCount to get the
931 // implicit width.
932 const int eol = line.isValid()
933 ? line.textStart() + line.textLength()
934 : layoutText.length();
935 if (eol < layoutText.length() && layoutText.at(eol) != QChar::LineSeparator)
936 line = layout.createLine();
937 for (; line.isValid() && unwrappedLineCount <= maxLineCount; ++unwrappedLineCount)
938 line = layout.createLine();
939 }
940 layout.endLayout();
941
942 const qreal naturalWidth = layout.maximumWidth();
943
944 bool wasInLayout = internalWidthUpdate;
945 internalWidthUpdate = true;
946 q->setImplicitSize(naturalWidth + q->leftPadding() + q->rightPadding(), naturalHeight + q->topPadding() + q->bottomPadding());
947 internalWidthUpdate = wasInLayout;
948
949 // Update any variables that are dependent on the validity of the width or height.
950 singlelineElide = elideMode != QQuickText::ElideNone && q->widthValid();
951 multilineElide = elideMode == QQuickText::ElideRight
952 && q->widthValid()
953 && (q->heightValid() || maximumLineCountValid);
954 canWrap = wrapMode != QQuickText::NoWrap && q->widthValid();
955
956 horizontalFit = fontSizeMode() & QQuickText::HorizontalFit && q->widthValid();
957 verticalFit = fontSizeMode() & QQuickText::VerticalFit
958 && (q->heightValid() || (maximumLineCountValid && canWrap));
959
960 const qreal oldWidth = lineWidth;
961 const qreal oldHeight = maxHeight;
962
963 const qreal availWidth = availableWidth();
964 const qreal availHeight = availableHeight();
965
966 lineWidth = q->widthValid() && availWidth > 0 ? availWidth : naturalWidth;
967 maxHeight = q->heightValid() ? availHeight : FLT_MAX;
968
969 // If the width of the item has changed and it's possible the result of wrapping,
970 // eliding, scaling has changed, or the text is not left aligned do another layout.
971 if ((!qFuzzyCompare(lineWidth, oldWidth) || (widthExceeded && lineWidth > oldWidth))
972 && (singlelineElide || multilineElide || canWrap || horizontalFit
973 || q->effectiveHAlign() != QQuickText::AlignLeft)) {
974 widthChanged = true;
975 widthExceeded = lineWidth >= qMin(oldWidth, naturalWidth);
976 heightExceeded = false;
977 continue;
978 }
979
980 // If the height of the item has changed and it's possible the result of eliding,
981 // line count truncation or scaling has changed, do another layout.
982 if ((maxHeight < qMin(oldHeight, naturalHeight) || (heightExceeded && maxHeight > oldHeight))
983 && (multilineElide || (canWrap && maximumLineCountValid))) {
984 widthExceeded = false;
985 heightExceeded = false;
986 continue;
987 }
988
989 // If the horizontal alignment is not left and the width was not valid we need to relayout
990 // now that we know the maximum line width.
991 if (!q->widthValid() && !implicitWidthValid && unwrappedLineCount > 1 && q->effectiveHAlign() != QQuickText::AlignLeft) {
992 widthExceeded = false;
993 heightExceeded = false;
994 continue;
995 }
996 } else if (widthChanged) {
997 widthChanged = false;
998 if (line.isValid()) {
999 for (int lineCount = layout.lineCount(); lineCount < maxLineCount; ++lineCount) {
1000 line = layout.createLine();
1001 if (!line.isValid())
1002 break;
1003 setLineGeometry(line, lineWidth, naturalHeight);
1004 }
1005 }
1006 layout.endLayout();
1007
1008 bool wasInLayout = internalWidthUpdate;
1009 internalWidthUpdate = true;
1010 q->setImplicitHeight(naturalHeight + q->topPadding() + q->bottomPadding());
1011 internalWidthUpdate = wasInLayout;
1012
1013 multilineElide = elideMode == QQuickText::ElideRight
1014 && q->widthValid()
1015 && (q->heightValid() || maximumLineCountValid);
1016 verticalFit = fontSizeMode() & QQuickText::VerticalFit
1017 && (q->heightValid() || (maximumLineCountValid && canWrap));
1018
1019 const qreal oldHeight = maxHeight;
1020 maxHeight = q->heightValid() ? availableHeight() : FLT_MAX;
1021 // If the height of the item has changed and it's possible the result of eliding,
1022 // line count truncation or scaling has changed, do another layout.
1023 if ((maxHeight < qMin(oldHeight, naturalHeight) || (heightExceeded && maxHeight > oldHeight))
1024 && (multilineElide || (canWrap && maximumLineCountValid))) {
1025 widthExceeded = false;
1026 heightExceeded = false;
1027 continue;
1028 }
1029 } else {
1030 layout.endLayout();
1031 }
1032
1033 // If the next needs to be elided and there's an abbreviated string available
1034 // go back and do another layout with the abbreviated string.
1035 if (eos != -1 && elide) {
1036 int start = eos + 1;
1037 eos = text.indexOf(QLatin1Char('\x9c'), start);
1038 layoutText = text.mid(start, eos != -1 ? eos - start : -1);
1039 layoutText.replace(QLatin1Char('\n'), QChar::LineSeparator);
1040 layout.setText(layoutText);
1041 textHasChanged = true;
1042 continue;
1043 }
1044
1045 br.moveTop(0);
1046
1047 // Find the advance of the text layout
1048 if (layout.lineCount() > 0) {
1049 QTextLine firstLine = layout.lineAt(0);
1050 QTextLine lastLine = layout.lineAt(layout.lineCount() - 1);
1051 advance = QSizeF(lastLine.horizontalAdvance(),
1052 lastLine.y() - firstLine.y());
1053 } else {
1054 advance = QSizeF();
1055 }
1056
1057 if (!horizontalFit && !verticalFit)
1058 break;
1059
1060 // Try and find a font size that better fits the dimensions of the element.
1061 if (horizontalFit) {
1062 if (unelidedRect.width() > lineWidth || (!verticalFit && wrapped)) {
1063 widthExceeded = true;
1064 largeFont = scaledFontSize - 1;
1065 if (smallFont > largeFont)
1066 break;
1067 scaledFontSize = (smallFont + largeFont) / 2;
1068 if (pixelSize)
1069 scaledFont.setPixelSize(scaledFontSize);
1070 else
1071 scaledFont.setPointSize(scaledFontSize);
1072 continue;
1073 } else if (!verticalFit) {
1074 smallFont = scaledFontSize;
1075 if (smallFont == largeFont)
1076 break;
1077 scaledFontSize = (smallFont + largeFont + 1) / 2;
1078 }
1079 }
1080
1081 if (verticalFit) {
1082 if (truncateHeight || unelidedRect.height() > maxHeight) {
1083 heightExceeded = true;
1084 largeFont = scaledFontSize - 1;
1085 if (smallFont > largeFont)
1086 break;
1087 scaledFontSize = (smallFont + largeFont) / 2;
1088
1089 } else {
1090 smallFont = scaledFontSize;
1091 if (smallFont == largeFont)
1092 break;
1093 scaledFontSize = (smallFont + largeFont + 1) / 2;
1094 }
1095 }
1096 }
1097
1098 implicitWidthValid = true;
1099 implicitHeightValid = true;
1100
1101 QFontInfo scaledFontInfo(scaledFont);
1102 if (fontInfo.weight() != scaledFontInfo.weight()
1103 || fontInfo.pixelSize() != scaledFontInfo.pixelSize()
1104 || fontInfo.italic() != scaledFontInfo.italic()
1105 || !qFuzzyCompare(fontInfo.pointSizeF(), scaledFontInfo.pointSizeF())
1106 || fontInfo.family() != scaledFontInfo.family()
1107 || fontInfo.styleName() != scaledFontInfo.styleName()) {
1108 fontInfo = scaledFontInfo;
1109 emit q->fontInfoChanged();
1110 }
1111
1112 if (eos != multilengthEos)
1113 truncated = true;
1114
1115 assignedFont = QFontInfo(font).family();
1116
1117 if (elide) {
1118 if (!elideLayout) {
1119 elideLayout = new QTextLayout;
1120 elideLayout->setCacheEnabled(true);
1121 }
1122 QTextEngine *engine = layout.engine();
1123 if (engine && engine->hasFormats()) {
1124 QVector<QTextLayout::FormatRange> formats;
1125 switch (elideMode) {
1126 case QQuickText::ElideRight:
1127 elideFormats(elideStart, elideText.length() - 1, 0, &formats);
1128 break;
1129 case QQuickText::ElideLeft:
1130 elideFormats(elideEnd - elideText.length() + 1, elideText.length() - 1, 1, &formats);
1131 break;
1132 case QQuickText::ElideMiddle: {
1133 const int index = elideText.indexOf(elideChar);
1134 if (index != -1) {
1135 elideFormats(elideStart, index, 0, &formats);
1136 elideFormats(
1137 elideEnd - elideText.length() + index + 1,
1138 elideText.length() - index - 1,
1139 index + 1,
1140 &formats);
1141 }
1142 break;
1143 }
1144 default:
1145 break;
1146 }
1147 elideLayout->setFormats(formats);
1148 }
1149
1150 elideLayout->setFont(layout.font());
1151 elideLayout->setTextOption(layout.textOption());
1152 if (QQmlDebugTranslationService *service
1153 = QQmlDebugConnector::service<QQmlDebugTranslationService>()) {
1154 elideText = service->foundElidedText(q, layoutText, elideText);
1155 }
1156 elideLayout->setText(elideText);
1157 elideLayout->beginLayout();
1158
1159 QTextLine elidedLine = elideLayout->createLine();
1160 elidedLine.setPosition(QPointF(0, height));
1161 if (customLayout) {
1162 setupCustomLineGeometry(elidedLine, height, elideText.length(), visibleCount - 1);
1163 } else {
1164 setLineGeometry(elidedLine, lineWidth, height);
1165 }
1166 elideLayout->endLayout();
1167
1168 br = br.united(elidedLine.naturalTextRect());
1169
1170 if (visibleCount == 1)
1171 layout.clearLayout();
1172 } else {
1173 delete elideLayout;
1174 elideLayout = nullptr;
1175 }
1176
1177 QTextLine firstLine = visibleCount == 1 && elideLayout
1178 ? elideLayout->lineAt(0)
1179 : layout.lineAt(0);
1180 Q_ASSERT(firstLine.isValid());
1181 *baseline = firstLine.y() + firstLine.ascent();
1182
1183 if (!customLayout)
1184 br.setHeight(height);
1185
1186 //Update the number of visible lines
1187 if (lineCount != visibleCount) {
1188 lineCount = visibleCount;
1189 emit q->lineCountChanged();
1190 }
1191
1192 if (truncated != wasTruncated)
1193 emit q->truncatedChanged();
1194
1195 return br;
1196}
1197
1198void QQuickTextPrivate::setLineGeometry(QTextLine &line, qreal lineWidth, qreal &height)
1199{
1200 Q_Q(QQuickText);
1201 line.setLineWidth(lineWidth);
1202
1203 if (extra.isAllocated() && extra->imgTags.isEmpty()) {
1204 line.setPosition(QPointF(line.position().x(), height));
1205 height += (lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : line.height() * lineHeight();
1206 return;
1207 }
1208
1209 qreal textTop = 0;
1210 qreal textHeight = line.height();
1211 qreal totalLineHeight = textHeight;
1212
1213 QList<QQuickStyledTextImgTag *> imagesInLine;
1214
1215 if (extra.isAllocated()) {
1216 for (QQuickStyledTextImgTag *image : qAsConst(extra->imgTags)) {
1217 if (image->position >= line.textStart() &&
1218 image->position < line.textStart() + line.textLength()) {
1219
1220 if (!image->pix) {
1221 QUrl url = q->baseUrl().resolved(image->url);
1222 image->pix = new QQuickPixmap(qmlEngine(q), url, QRect(), image->size);
1223 if (image->pix->isLoading()) {
1224 image->pix->connectFinished(q, SLOT(imageDownloadFinished()));
1225 if (!extra.isAllocated() || !extra->nbActiveDownloads)
1226 extra.value().nbActiveDownloads = 0;
1227 extra->nbActiveDownloads++;
1228 } else if (image->pix->isReady()) {
1229 if (!image->size.isValid()) {
1230 image->size = image->pix->implicitSize();
1231 // if the size of the image was not explicitly set, we need to
1232 // call updateLayout() once again.
1233 needToUpdateLayout = true;
1234 }
1235 } else if (image->pix->isError()) {
1236 qmlWarning(q) << image->pix->error();
1237 }
1238 }
1239
1240 qreal ih = qreal(image->size.height());
1241 if (image->align == QQuickStyledTextImgTag::Top)
1242 image->pos.setY(0);
1243 else if (image->align == QQuickStyledTextImgTag::Middle)
1244 image->pos.setY((textHeight / 2.0) - (ih / 2.0));
1245 else
1246 image->pos.setY(textHeight - ih);
1247 imagesInLine << image;
1248 textTop = qMax(textTop, qAbs(image->pos.y()));
1249 }
1250 }
1251 }
1252
1253 for (QQuickStyledTextImgTag *image : qAsConst(imagesInLine)) {
1254 totalLineHeight = qMax(totalLineHeight, textTop + image->pos.y() + image->size.height());
1255 const int leadX = line.cursorToX(image->position);
1256 const int trailX = line.cursorToX(image->position, QTextLine::Trailing);
1257 const bool rtl = trailX < leadX;
1258 image->pos.setX(leadX + (rtl ? (-image->offset - image->size.width()) : image->offset));
1259 image->pos.setY(image->pos.y() + height + textTop);
1260 extra->visibleImgTags << image;
1261 }
1262
1263 line.setPosition(QPointF(line.position().x(), height + textTop));
1264 height += (lineHeightMode() == QQuickText::FixedHeight) ? lineHeight() : totalLineHeight * lineHeight();
1265}
1266
1267/*!
1268 Returns the y offset when aligning text with a non-1.0 lineHeight
1269*/
1270int QQuickTextPrivate::lineHeightOffset() const
1271{
1272 QFontMetricsF fm(font);
1273 qreal fontHeight = qCeil(fm.height()); // QScriptLine and therefore QTextLine rounds up
1274 return lineHeightMode() == QQuickText::FixedHeight ? fontHeight - lineHeight()
1275 : (1.0 - lineHeight()) * fontHeight;
1276}
1277
1278/*!
1279 Ensures the QQuickTextPrivate::doc variable is set to a valid text document
1280*/
1281void QQuickTextPrivate::ensureDoc()
1282{
1283 if (!extra.isAllocated() || !extra->doc) {
1284 Q_Q(QQuickText);
1285 extra.value().doc = new QQuickTextDocumentWithImageResources(q);
1286 extra->doc->setPageSize(QSizeF(0, 0));
1287 extra->doc->setDocumentMargin(0);
1288 extra->doc->setBaseUrl(q->baseUrl());
1289 qmlobject_connect(extra->doc, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()),
1290 q, QQuickText, SLOT(q_updateLayout()));
1291 }
1292}
1293
1294/*!
1295 \qmltype Text
1296 \instantiates QQuickText
1297 \inqmlmodule QtQuick
1298 \ingroup qtquick-visual
1299 \inherits Item
1300 \brief Specifies how to add formatted text to a scene.
1301
1302 Text items can display both plain and rich text. For example, red text with
1303 a specific font and size can be defined like this:
1304
1305 \qml
1306 Text {
1307 text: "Hello World!"
1308 font.family: "Helvetica"
1309 font.pointSize: 24
1310 color: "red"
1311 }
1312 \endqml
1313
1314 Rich text is defined using HTML-style markup:
1315
1316 \qml
1317 Text {
1318 text: "<b>Hello</b> <i>World!</i>"
1319 }
1320 \endqml
1321
1322 \image declarative-text.png
1323
1324 If height and width are not explicitly set, Text will attempt to determine how
1325 much room is needed and set it accordingly. Unless \l wrapMode is set, it will always
1326 prefer width to height (all text will be placed on a single line).
1327
1328 The \l elide property can alternatively be used to fit a single line of
1329 plain text to a set width.
1330
1331 Note that the \l{Supported HTML Subset} is limited. Also, if the text contains
1332 HTML img tags that load remote images, the text is reloaded.
1333
1334 Text provides read-only text. For editable text, see \l TextEdit.
1335
1336 \sa {Qt Quick Examples - Text#Fonts}{Fonts example}
1337*/
1338QQuickText::QQuickText(QQuickItem *parent)
1339: QQuickImplicitSizeItem(*(new QQuickTextPrivate), parent)
1340{
1341 Q_D(QQuickText);
1342 d->init();
1343}
1344
1345QQuickText::QQuickText(QQuickTextPrivate &dd, QQuickItem *parent)
1346: QQuickImplicitSizeItem(dd, parent)
1347{
1348 Q_D(QQuickText);
1349 d->init();
1350}
1351
1352QQuickText::~QQuickText()
1353{
1354}
1355
1356/*!
1357 \qmlproperty bool QtQuick::Text::clip
1358 This property holds whether the text is clipped.
1359
1360 Note that if the text does not fit in the bounding rectangle it will be abruptly chopped.
1361
1362 If you want to display potentially long text in a limited space, you probably want to use \c elide instead.
1363*/
1364
1365/*!
1366 \qmlsignal QtQuick::Text::lineLaidOut(object line)
1367
1368 This signal is emitted for each line of text that is laid out during the layout
1369 process in plain text or styled text mode. It is not emitted in rich text mode.
1370 The specified \a line object provides more details about the line that
1371 is currently being laid out.
1372
1373 This gives the opportunity to position and resize a line as it is being laid out.
1374 It can for example be used to create columns or lay out text around objects.
1375
1376 The properties of the specified \a line object are:
1377
1378 \table
1379 \header
1380 \li Property name
1381 \li Description
1382 \row
1383 \li number (read-only)
1384 \li Line number, starts with zero.
1385 \row
1386 \li x
1387 \li Specifies the line's x position inside the \c Text element.
1388 \row
1389 \li y
1390 \li Specifies the line's y position inside the \c Text element.
1391 \row
1392 \li width
1393 \li Specifies the width of the line.
1394 \row
1395 \li height
1396 \li Specifies the height of the line.
1397 \row
1398 \li implicitWidth (read-only)
1399 \li The width that the line would naturally occupy based on its contents,
1400 not taking into account any modifications made to \a width.
1401 \row
1402 \li isLast (read-only)
1403 \li Whether the line is the last. This property can change if you
1404 set the \a width property to a different value.
1405 \endtable
1406
1407 For example, this will move the first 5 lines of a Text item by 100 pixels to the right:
1408 \code
1409 onLineLaidOut: {
1410 if (line.number < 5) {
1411 line.x = line.x + 100
1412 line.width = line.width - 100
1413 }
1414 }
1415 \endcode
1416
1417 The following example will allow you to position an item at the end of the last line:
1418 \code
1419 onLineLaidOut: {
1420 if (line.isLast) {
1421 lastLineMarker.x = line.x + line.implicitWidth
1422 lastLineMarker.y = line.y + (line.height - lastLineMarker.height) / 2
1423 }
1424 }
1425 \endcode
1426*/
1427
1428/*!
1429 \qmlsignal QtQuick::Text::linkActivated(string link)
1430
1431 This signal is emitted when the user clicks on a link embedded in the text.
1432 The link must be in rich text or HTML format and the
1433 \a link string provides access to the particular link.
1434
1435 \snippet qml/text/onLinkActivated.qml 0
1436
1437 The example code will display the text
1438 "See the \l{http://qt-project.org}{Qt Project website}."
1439
1440 Clicking on the highlighted link will output
1441 \tt{http://qt-project.org link activated} to the console.
1442*/
1443
1444/*!
1445 \qmlproperty string QtQuick::Text::font.family
1446
1447 Sets the family name of the font.
1448
1449 The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]".
1450 If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen.
1451 If the family isn't available a family will be set using the font matching algorithm.
1452*/
1453
1454/*!
1455 \qmlproperty string QtQuick::Text::font.styleName
1456 \since 5.6
1457
1458 Sets the style name of the font.
1459
1460 The style name is case insensitive. If set, the font will be matched against style name instead
1461 of the font properties \l font.weight, \l font.bold and \l font.italic.
1462*/
1463
1464/*!
1465 \qmlproperty bool QtQuick::Text::font.bold
1466
1467 Sets whether the font weight is bold.
1468*/
1469
1470/*!
1471 \qmlproperty enumeration QtQuick::Text::font.weight
1472
1473 Sets the font's weight.
1474
1475 The weight can be one of:
1476 \list
1477 \li Font.Thin
1478 \li Font.Light
1479 \li Font.ExtraLight
1480 \li Font.Normal - the default
1481 \li Font.Medium
1482 \li Font.DemiBold
1483 \li Font.Bold
1484 \li Font.ExtraBold
1485 \li Font.Black
1486 \endlist
1487
1488 \qml
1489 Text { text: "Hello"; font.weight: Font.DemiBold }
1490 \endqml
1491*/
1492
1493/*!
1494 \qmlproperty bool QtQuick::Text::font.italic
1495
1496 Sets whether the font has an italic style.
1497*/
1498
1499/*!
1500 \qmlproperty bool QtQuick::Text::font.underline
1501
1502 Sets whether the text is underlined.
1503*/
1504
1505/*!
1506 \qmlproperty bool QtQuick::Text::font.strikeout
1507
1508 Sets whether the font has a strikeout style.
1509*/
1510
1511/*!
1512 \qmlproperty real QtQuick::Text::font.pointSize
1513
1514 Sets the font size in points. The point size must be greater than zero.
1515*/
1516
1517/*!
1518 \qmlproperty int QtQuick::Text::font.pixelSize
1519
1520 Sets the font size in pixels.
1521
1522 Using this function makes the font device dependent.
1523 Use \c pointSize to set the size of the font in a device independent manner.
1524*/
1525
1526/*!
1527 \qmlproperty real QtQuick::Text::font.letterSpacing
1528
1529 Sets the letter spacing for the font.
1530
1531 Letter spacing changes the default spacing between individual letters in the font.
1532 A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing.
1533*/
1534
1535/*!
1536 \qmlproperty real QtQuick::Text::font.wordSpacing
1537
1538 Sets the word spacing for the font.
1539
1540 Word spacing changes the default spacing between individual words.
1541 A positive value increases the word spacing by a corresponding amount of pixels,
1542 while a negative value decreases the inter-word spacing accordingly.
1543*/
1544
1545/*!
1546 \qmlproperty enumeration QtQuick::Text::font.capitalization
1547
1548 Sets the capitalization for the text.
1549
1550 \list
1551 \li Font.MixedCase - This is the normal text rendering option where no capitalization change is applied.
1552 \li Font.AllUppercase - This alters the text to be rendered in all uppercase type.
1553 \li Font.AllLowercase - This alters the text to be rendered in all lowercase type.
1554 \li Font.SmallCaps - This alters the text to be rendered in small-caps type.
1555 \li Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character.
1556 \endlist
1557
1558 \qml
1559 Text { text: "Hello"; font.capitalization: Font.AllLowercase }
1560 \endqml
1561*/
1562
1563/*!
1564 \qmlproperty enumeration QtQuick::Text::font.hintingPreference
1565 \since 5.8
1566
1567 Sets the preferred hinting on the text. This is a hint to the underlying text rendering system
1568 to use a certain level of hinting, and has varying support across platforms. See the table in
1569 the documentation for QFont::HintingPreference for more details.
1570
1571 \note This property only has an effect when used together with render type Text.NativeRendering.
1572
1573 \list
1574 \value Font.PreferDefaultHinting - Use the default hinting level for the target platform.
1575 \value Font.PreferNoHinting - If possible, render text without hinting the outlines
1576 of the glyphs. The text layout will be typographically accurate, using the same metrics
1577 as are used e.g. when printing.
1578 \value Font.PreferVerticalHinting - If possible, render text with no horizontal hinting,
1579 but align glyphs to the pixel grid in the vertical direction. The text will appear
1580 crisper on displays where the density is too low to give an accurate rendering
1581 of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's
1582 layout will be scalable to higher density devices (such as printers) without impacting
1583 details such as line breaks.
1584 \value Font.PreferFullHinting - If possible, render text with hinting in both horizontal and
1585 vertical directions. The text will be altered to optimize legibility on the target
1586 device, but since the metrics will depend on the target size of the text, the positions
1587 of glyphs, line breaks, and other typographical detail will not scale, meaning that a
1588 text layout may look different on devices with different pixel densities.
1589 \endlist
1590
1591 \qml
1592 Text { text: "Hello"; renderType: Text.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting }
1593 \endqml
1594*/
1595
1596/*!
1597 \qmlproperty bool QtQuick::Text::font.kerning
1598 \since 5.10
1599
1600 Enables or disables the kerning OpenType feature when shaping the text. Disabling this may
1601 improve performance when creating or changing the text, at the expense of some cosmetic
1602 features. The default value is true.
1603
1604 \qml
1605 Text { text: "OATS FLAVOUR WAY"; font.kerning: false }
1606 \endqml
1607*/
1608
1609/*!
1610 \qmlproperty bool QtQuick::Text::font.preferShaping
1611 \since 5.10
1612
1613 Sometimes, a font will apply complex rules to a set of characters in order to
1614 display them correctly. In some writing systems, such as Brahmic scripts, this is
1615 required in order for the text to be legible, but in e.g. Latin script, it is merely
1616 a cosmetic feature. Setting the \c preferShaping property to false will disable all
1617 such features when they are not required, which will improve performance in most cases.
1618
1619 The default value is true.
1620
1621 \qml
1622 Text { text: "Some text"; font.preferShaping: false }
1623 \endqml
1624*/
1625QFont QQuickText::font() const
1626{
1627 Q_D(const QQuickText);
1628 return d->sourceFont;
1629}
1630
1631void QQuickText::setFont(const QFont &font)
1632{
1633 Q_D(QQuickText);
1634 if (d->sourceFont == font)
1635 return;
1636
1637 d->sourceFont = font;
1638 QFont oldFont = d->font;
1639 d->font = font;
1640
1641 if (!antialiasing())
1642 d->font.setStyleStrategy(QFont::NoAntialias);
1643
1644 if (d->font.pointSizeF() != -1) {
1645 // 0.5pt resolution
1646 qreal size = qRound(d->font.pointSizeF()*2.0);
1647 d->font.setPointSizeF(size/2.0);
1648 }
1649
1650 if (oldFont != d->font) {
1651 // if the format changes the size of the text
1652 // with headings or <font> tag, we need to re-parse
1653 if (d->formatModifiesFontSize)
1654 d->textHasChanged = true;
1655 d->implicitWidthValid = false;
1656 d->implicitHeightValid = false;
1657 d->updateLayout();
1658 }
1659
1660 emit fontChanged(d->sourceFont);
1661}
1662
1663void QQuickText::itemChange(ItemChange change, const ItemChangeData &value)
1664{
1665 Q_D(QQuickText);
1666 Q_UNUSED(value);
1667 switch (change) {
1668 case ItemAntialiasingHasChanged:
1669 if (!antialiasing())
1670 d->font.setStyleStrategy(QFont::NoAntialias);
1671 else
1672 d->font.setStyleStrategy(QFont::PreferAntialias);
1673 d->implicitWidthValid = false;
1674 d->implicitHeightValid = false;
1675 d->updateLayout();
1676 break;
1677
1678 case ItemDevicePixelRatioHasChanged:
1679 if (d->renderType == NativeRendering) {
1680 // Native rendering optimizes for a given pixel grid, so its results must not be scaled.
1681 // Text layout code respects the current device pixel ratio automatically, we only need
1682 // to rerun layout after the ratio changed.
1683 // Changes of implicit size should be minimal; they are hard to avoid.
1684 d->implicitWidthValid = false;
1685 d->implicitHeightValid = false;
1686 d->updateLayout();
1687 }
1688 break;
1689
1690 default:
1691 break;
1692 }
1693 QQuickItem::itemChange(change, value);
1694}
1695
1696/*!
1697 \qmlproperty string QtQuick::Text::text
1698
1699 The text to display. Text supports both plain and rich text strings.
1700
1701 The item will try to automatically determine whether the text should
1702 be treated as styled text. This determination is made using Qt::mightBeRichText().
1703 However, detection of Markdown is not automatic.
1704
1705 \sa textFormat
1706*/
1707QString QQuickText::text() const
1708{
1709 Q_D(const QQuickText);
1710 return d->text;
1711}
1712
1713void QQuickText::setText(const QString &n)
1714{
1715 Q_D(QQuickText);
1716 if (d->text == n)
1717 return;
1718
1719 d->markdownText = d->format == MarkdownText;
1720 d->richText = d->format == RichText || d->markdownText;
1721 d->styledText = d->format == StyledText || (d->format == AutoText && Qt::mightBeRichText(n));
1722 d->text = n;
1723 if (isComponentComplete()) {
1724 if (d->richText) {
1725 d->ensureDoc();
1726 if (d->markdownText)
1727 d->extra->doc->setMarkdownText(n);
1728 else
1729 d->extra->doc->setText(n);
1730 d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft();
1731 } else {
1732 d->clearFormats();
1733 d->rightToLeftText = d->text.isRightToLeft();
1734 }
1735 d->determineHorizontalAlignment();
1736 }
1737 d->textHasChanged = true;
1738 d->implicitWidthValid = false;
1739 d->implicitHeightValid = false;
1740
1741 if (d->extra.isAllocated()) {
1742 qDeleteAll(d->extra->imgTags);
1743 d->extra->imgTags.clear();
1744 }
1745 d->updateLayout();
1746 setAcceptHoverEvents(d->richText || d->styledText);
1747 emit textChanged(d->text);
1748}
1749
1750/*!
1751 \qmlproperty color QtQuick::Text::color
1752
1753 The text color.
1754
1755 An example of green text defined using hexadecimal notation:
1756 \qml
1757 Text {
1758 color: "#00FF00"
1759 text: "green text"
1760 }
1761 \endqml
1762
1763 An example of steel blue text defined using an SVG color name:
1764 \qml
1765 Text {
1766 color: "steelblue"
1767 text: "blue text"
1768 }
1769 \endqml
1770*/
1771QColor QQuickText::color() const
1772{
1773 Q_D(const QQuickText);
1774 return QColor::fromRgba(d->color);
1775}
1776
1777void QQuickText::setColor(const QColor &color)
1778{
1779 Q_D(QQuickText);
1780 QRgb rgb = color.rgba();
1781 if (d->color == rgb)
1782 return;
1783
1784 d->color = rgb;
1785 if (isComponentComplete()) {
1786 d->updateType = QQuickTextPrivate::UpdatePaintNode;
1787 update();
1788 }
1789 emit colorChanged();
1790}
1791
1792/*!
1793 \qmlproperty color QtQuick::Text::linkColor
1794
1795 The color of links in the text.
1796
1797 This property works with the StyledText \l textFormat, but not with RichText.
1798 Link color in RichText can be specified by including CSS style tags in the
1799 text.
1800*/
1801
1802QColor QQuickText::linkColor() const
1803{
1804 Q_D(const QQuickText);
1805 return QColor::fromRgba(d->linkColor);
1806}
1807
1808void QQuickText::setLinkColor(const QColor &color)
1809{
1810 Q_D(QQuickText);
1811 QRgb rgb = color.rgba();
1812 if (d->linkColor == rgb)
1813 return;
1814
1815 d->linkColor = rgb;
1816 if (isComponentComplete()) {
1817 d->updateType = QQuickTextPrivate::UpdatePaintNode;
1818 update();
1819 }
1820 emit linkColorChanged();
1821}
1822
1823/*!
1824 \qmlproperty enumeration QtQuick::Text::style
1825
1826 Set an additional text style.
1827
1828 Supported text styles are:
1829 \list
1830 \li Text.Normal - the default
1831 \li Text.Outline
1832 \li Text.Raised
1833 \li Text.Sunken
1834 \endlist
1835
1836 \qml
1837 Row {
1838 Text { font.pointSize: 24; text: "Normal" }
1839 Text { font.pointSize: 24; text: "Raised"; style: Text.Raised; styleColor: "#AAAAAA" }
1840 Text { font.pointSize: 24; text: "Outline";style: Text.Outline; styleColor: "red" }
1841 Text { font.pointSize: 24; text: "Sunken"; style: Text.Sunken; styleColor: "#AAAAAA" }
1842 }
1843 \endqml
1844
1845 \image declarative-textstyle.png
1846*/
1847QQuickText::TextStyle QQuickText::style() const
1848{
1849 Q_D(const QQuickText);
1850 return d->style;
1851}
1852
1853void QQuickText::setStyle(QQuickText::TextStyle style)
1854{
1855 Q_D(QQuickText);
1856 if (d->style == style)
1857 return;
1858
1859 d->style = style;
1860 if (isComponentComplete()) {
1861 d->updateType = QQuickTextPrivate::UpdatePaintNode;
1862 update();
1863 }
1864 emit styleChanged(d->style);
1865}
1866
1867/*!
1868 \qmlproperty color QtQuick::Text::styleColor
1869
1870 Defines the secondary color used by text styles.
1871
1872 \c styleColor is used as the outline color for outlined text, and as the
1873 shadow color for raised or sunken text. If no style has been set, it is not
1874 used at all.
1875
1876 \qml
1877 Text { font.pointSize: 18; text: "hello"; style: Text.Raised; styleColor: "gray" }
1878 \endqml
1879
1880 \sa style
1881 */
1882QColor QQuickText::styleColor() const
1883{
1884 Q_D(const QQuickText);
1885 return QColor::fromRgba(d->styleColor);
1886}
1887
1888void QQuickText::setStyleColor(const QColor &color)
1889{
1890 Q_D(QQuickText);
1891 QRgb rgb = color.rgba();
1892 if (d->styleColor == rgb)
1893 return;
1894
1895 d->styleColor = rgb;
1896 if (isComponentComplete()) {
1897 d->updateType = QQuickTextPrivate::UpdatePaintNode;
1898 update();
1899 }
1900 emit styleColorChanged();
1901}
1902
1903/*!
1904 \qmlproperty enumeration QtQuick::Text::horizontalAlignment
1905 \qmlproperty enumeration QtQuick::Text::verticalAlignment
1906 \qmlproperty enumeration QtQuick::Text::effectiveHorizontalAlignment
1907
1908 Sets the horizontal and vertical alignment of the text within the Text items
1909 width and height. By default, the text is vertically aligned to the top. Horizontal
1910 alignment follows the natural alignment of the text, for example text that is read
1911 from left to right will be aligned to the left.
1912
1913 The valid values for \c horizontalAlignment are \c Text.AlignLeft, \c Text.AlignRight, \c Text.AlignHCenter and
1914 \c Text.AlignJustify. The valid values for \c verticalAlignment are \c Text.AlignTop, \c Text.AlignBottom
1915 and \c Text.AlignVCenter.
1916
1917 Note that for a single line of text, the size of the text is the area of the text. In this common case,
1918 all alignments are equivalent. If you want the text to be, say, centered in its parent, then you will
1919 need to either modify the Item::anchors, or set horizontalAlignment to Text.AlignHCenter and bind the width to
1920 that of the parent.
1921
1922 When using the attached property LayoutMirroring::enabled to mirror application
1923 layouts, the horizontal alignment of text will also be mirrored. However, the property
1924 \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment
1925 of Text, use the read-only property \c effectiveHorizontalAlignment.
1926*/
1927QQuickText::HAlignment QQuickText::hAlign() const
1928{
1929 Q_D(const QQuickText);
1930 return d->hAlign;
1931}
1932
1933void QQuickText::setHAlign(HAlignment align)
1934{
1935 Q_D(QQuickText);
1936 bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror;
1937 d->hAlignImplicit = false;
1938 if (d->setHAlign(align, forceAlign) && isComponentComplete())
1939 d->updateLayout();
1940}
1941
1942void QQuickText::resetHAlign()
1943{
1944 Q_D(QQuickText);
1945 d->hAlignImplicit = true;
1946 if (isComponentComplete() && d->determineHorizontalAlignment())
1947 d->updateLayout();
1948}
1949
1950QQuickText::HAlignment QQuickText::effectiveHAlign() const
1951{
1952 Q_D(const QQuickText);
1953 QQuickText::HAlignment effectiveAlignment = d->hAlign;
1954 if (!d->hAlignImplicit && d->effectiveLayoutMirror) {
1955 switch (d->hAlign) {
1956 case QQuickText::AlignLeft:
1957 effectiveAlignment = QQuickText::AlignRight;
1958 break;
1959 case QQuickText::AlignRight:
1960 effectiveAlignment = QQuickText::AlignLeft;
1961 break;
1962 default:
1963 break;
1964 }
1965 }
1966 return effectiveAlignment;
1967}
1968
1969bool QQuickTextPrivate::setHAlign(QQuickText::HAlignment alignment, bool forceAlign)
1970{
1971 Q_Q(QQuickText);
1972 if (hAlign != alignment || forceAlign) {
1973 QQuickText::HAlignment oldEffectiveHAlign = q->effectiveHAlign();
1974 hAlign = alignment;
1975
1976 emit q->horizontalAlignmentChanged(hAlign);
1977 if (oldEffectiveHAlign != q->effectiveHAlign())
1978 emit q->effectiveHorizontalAlignmentChanged();
1979 return true;
1980 }
1981 return false;
1982}
1983
1984bool QQuickTextPrivate::determineHorizontalAlignment()
1985{
1986 if (hAlignImplicit) {
1987#if QT_CONFIG(im)
1988 bool alignToRight = text.isEmpty() ? QGuiApplication::inputMethod()->inputDirection() == Qt::RightToLeft : rightToLeftText;
1989#else
1990 bool alignToRight = rightToLeftText;
1991#endif
1992 return setHAlign(alignToRight ? QQuickText::AlignRight : QQuickText::AlignLeft);
1993 }
1994 return false;
1995}
1996
1997void QQuickTextPrivate::mirrorChange()
1998{
1999 Q_Q(QQuickText);
2000 if (q->isComponentComplete()) {
2001 if (!hAlignImplicit && (hAlign == QQuickText::AlignRight || hAlign == QQuickText::AlignLeft)) {
2002 updateLayout();
2003 emit q->effectiveHorizontalAlignmentChanged();
2004 }
2005 }
2006}
2007
2008QQuickText::VAlignment QQuickText::vAlign() const
2009{
2010 Q_D(const QQuickText);
2011 return d->vAlign;
2012}
2013
2014void QQuickText::setVAlign(VAlignment align)
2015{
2016 Q_D(QQuickText);
2017 if (d->vAlign == align)
2018 return;
2019
2020 d->vAlign = align;
2021
2022 if (isComponentComplete())
2023 d->updateLayout();
2024
2025 emit verticalAlignmentChanged(align);
2026}
2027
2028/*!
2029 \qmlproperty enumeration QtQuick::Text::wrapMode
2030
2031 Set this property to wrap the text to the Text item's width. The text will only
2032 wrap if an explicit width has been set. wrapMode can be one of:
2033
2034 \list
2035 \li Text.NoWrap (default) - no wrapping will be performed. If the text contains insufficient newlines, then \l contentWidth will exceed a set width.
2036 \li Text.WordWrap - wrapping is done on word boundaries only. If a word is too long, \l contentWidth will exceed a set width.
2037 \li Text.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word.
2038 \li Text.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word.
2039 \endlist
2040*/
2041QQuickText::WrapMode QQuickText::wrapMode() const
2042{
2043 Q_D(const QQuickText);
2044 return d->wrapMode;
2045}
2046
2047void QQuickText::setWrapMode(WrapMode mode)
2048{
2049 Q_D(QQuickText);
2050 if (mode == d->wrapMode)
2051 return;
2052
2053 d->wrapMode = mode;
2054 d->updateLayout();
2055
2056 emit wrapModeChanged();
2057}
2058
2059/*!
2060 \qmlproperty int QtQuick::Text::lineCount
2061
2062 Returns the number of lines visible in the text item.
2063
2064 This property is not supported for rich text.
2065
2066 \sa maximumLineCount
2067*/
2068int QQuickText::lineCount() const
2069{
2070 Q_D(const QQuickText);
2071 return d->lineCount;
2072}
2073
2074/*!
2075 \qmlproperty bool QtQuick::Text::truncated
2076
2077 Returns true if the text has been truncated due to \l maximumLineCount
2078 or \l elide.
2079
2080 This property is not supported for rich text.
2081
2082 \sa maximumLineCount, elide
2083*/
2084bool QQuickText::truncated() const
2085{
2086 Q_D(const QQuickText);
2087 return d->truncated;
2088}
2089
2090/*!
2091 \qmlproperty int QtQuick::Text::maximumLineCount
2092
2093 Set this property to limit the number of lines that the text item will show.
2094 If elide is set to Text.ElideRight, the text will be elided appropriately.
2095 By default, this is the value of the largest possible integer.
2096
2097 This property is not supported for rich text.
2098
2099 \sa lineCount, elide
2100*/
2101int QQuickText::maximumLineCount() const
2102{
2103 Q_D(const QQuickText);
2104 return d->maximumLineCount();
2105}
2106
2107void QQuickText::setMaximumLineCount(int lines)
2108{
2109 Q_D(QQuickText);
2110
2111 d->maximumLineCountValid = lines==INT_MAX ? false : true;
2112 if (d->maximumLineCount() != lines) {
2113 d->extra.value().maximumLineCount = lines;
2114 d->implicitHeightValid = false;
2115 d->updateLayout();
2116 emit maximumLineCountChanged();
2117 }
2118}
2119
2120void QQuickText::resetMaximumLineCount()
2121{
2122 Q_D(QQuickText);
2123 setMaximumLineCount(INT_MAX);
2124 if (d->truncated != false) {
2125 d->truncated = false;
2126 emit truncatedChanged();
2127 }
2128}
2129
2130/*!
2131 \qmlproperty enumeration QtQuick::Text::textFormat
2132
2133 The way the \l text property should be displayed.
2134
2135 Supported text formats are:
2136
2137 \value Text.AutoText (default) detected via the Qt::mightBeRichText() heuristic
2138 \value Text.PlainText all styling tags are treated as plain text
2139 \value Text.StyledText optimized basic rich text as in HTML 3.2
2140 \value Text.RichText \l {Supported HTML Subset} {a subset of HTML 4}
2141 \value Text.MarkdownText \l {https://commonmark.org/help/}{CommonMark} plus the
2142 \l {https://guides.github.com/features/mastering-markdown/}{GitHub}
2143 extensions for tables and task lists (since 5.14)
2144
2145 If the text format is \c Text.AutoText the Text item
2146 will automatically determine whether the text should be treated as
2147 styled text. This determination is made using Qt::mightBeRichText(),
2148 which can detect the presence of an HTML tag on the first line of text,
2149 but cannot distinguish Markdown from plain text.
2150
2151 \c Text.StyledText is an optimized format supporting some basic text
2152 styling markup, in the style of HTML 3.2:
2153
2154 \code
2155 <b></b> - bold
2156 <del></del> - strike out (removed content)
2157 <s></s> - strike out (no longer accurate or no longer relevant content)
2158 <strong></strong> - bold
2159 <i></i> - italic
2160 <br> - new line
2161 <p> - paragraph
2162 <u> - underlined text
2163 <font color="color_name" size="1-7"></font>
2164 <h1> to <h6> - headers
2165 <a href=""> - anchor
2166 <img src="" align="top,middle,bottom" width="" height=""> - inline images
2167 <ol type="">, <ul type=""> and <li> - ordered and unordered lists
2168 <pre></pre> - preformatted
2169 &gt; &lt; &amp;
2170 \endcode
2171
2172 \c Text.StyledText parser is strict, requiring tags to be correctly nested.
2173
2174 \table
2175 \row
2176 \li
2177 \snippet qml/text/textFormats.qml 0
2178 \li \image declarative-textformat.png
2179 \endtable
2180
2181 \c Text.RichText supports a larger subset of HTML 4, as described on the
2182 \l {Supported HTML Subset} page. You should prefer using \c Text.PlainText,
2183 \c Text.StyledText or \c Text.MarkdownText instead, as they offer better performance.
2184
2185 \note With \c Text.MarkdownText, and with the supported subset of HTML,
2186 some decorative elements are not rendered as they would be in a web browser:
2187 \list
2188 \li code blocks use the \l {QFontDatabase::FixedFont}{default monospace font} but without a surrounding highlight box
2189 \li block quotes are indented, but there is no vertical line alongside the quote
2190 \li horizontal rules are not rendered
2191 \endlist
2192*/
2193QQuickText::TextFormat QQuickText::textFormat() const
2194{
2195 Q_D(const QQuickText);
2196 return d->format;
2197}
2198
2199void QQuickText::setTextFormat(TextFormat format)
2200{
2201 Q_D(QQuickText);
2202 if (format == d->format)
2203 return;
2204 d->format = format;
2205 bool wasRich = d->richText;
2206 d->markdownText = format == MarkdownText;
2207 d->richText = format == RichText || d->markdownText;
2208 d->styledText = format == StyledText || (format == AutoText && Qt::mightBeRichText(d->text));
2209
2210 if (isComponentComplete()) {
2211 if (!wasRich && d->richText) {
2212 d->ensureDoc();
2213 d->extra->doc->setText(d->text);
2214 d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft();
2215 } else {
2216 d->clearFormats();
2217 d->rightToLeftText = d->text.isRightToLeft();
2218 d->textHasChanged = true;
2219 }
2220 d->determineHorizontalAlignment();
2221 }
2222 d->updateLayout();
2223 setAcceptHoverEvents(d->richText || d->styledText);
2224 setAcceptedMouseButtons(d->richText || d->styledText ? Qt::LeftButton : Qt::NoButton);
2225
2226 emit textFormatChanged(d->format);
2227}
2228
2229/*!
2230 \qmlproperty enumeration QtQuick::Text::elide
2231
2232 Set this property to elide parts of the text fit to the Text item's width.
2233 The text will only elide if an explicit width has been set.
2234
2235 This property cannot be used with rich text.
2236
2237 Eliding can be:
2238 \list
2239 \li Text.ElideNone - the default
2240 \li Text.ElideLeft
2241 \li Text.ElideMiddle
2242 \li Text.ElideRight
2243 \endlist
2244
2245 If this property is set to Text.ElideRight, it can be used with \l {wrapMode}{wrapped}
2246 text. The text will only elide if \c maximumLineCount, or \c height has been set.
2247 If both \c maximumLineCount and \c height are set, \c maximumLineCount will
2248 apply unless the lines do not fit in the height allowed.
2249
2250 If the text is a multi-length string, and the mode is not \c Text.ElideNone,
2251 the first string that fits will be used, otherwise the last will be elided.
2252
2253 Multi-length strings are ordered from longest to shortest, separated by the
2254 Unicode "String Terminator" character \c U009C (write this in QML with \c{"\u009C"} or \c{"\x9C"}).
2255*/
2256QQuickText::TextElideMode QQuickText::elideMode() const
2257{
2258 Q_D(const QQuickText);
2259 return d->elideMode;
2260}
2261
2262void QQuickText::setElideMode(QQuickText::TextElideMode mode)
2263{
2264 Q_D(QQuickText);
2265 if (mode == d->elideMode)
2266 return;
2267
2268 d->elideMode = mode;
2269 d->updateLayout();
2270
2271 emit elideModeChanged(mode);
2272}
2273
2274/*!
2275 \qmlproperty url QtQuick::Text::baseUrl
2276
2277 This property specifies a base URL which is used to resolve relative URLs
2278 within the text.
2279
2280 Urls are resolved to be within the same directory as the target of the base
2281 URL meaning any portion of the path after the last '/' will be ignored.
2282
2283 \table
2284 \header \li Base URL \li Relative URL \li Resolved URL
2285 \row \li http://qt-project.org/ \li images/logo.png \li http://qt-project.org/images/logo.png
2286 \row \li http://qt-project.org/index.html \li images/logo.png \li http://qt-project.org/images/logo.png
2287 \row \li http://qt-project.org/content \li images/logo.png \li http://qt-project.org/content/images/logo.png
2288 \row \li http://qt-project.org/content/ \li images/logo.png \li http://qt-project.org/content/images/logo.png
2289 \row \li http://qt-project.org/content/index.html \li images/logo.png \li http://qt-project.org/content/images/logo.png
2290 \row \li http://qt-project.org/content/index.html \li ../images/logo.png \li http://qt-project.org/images/logo.png
2291 \row \li http://qt-project.org/content/index.html \li /images/logo.png \li http://qt-project.org/images/logo.png
2292 \endtable
2293
2294 The default value is the url of the QML file instantiating the Text item.
2295*/
2296
2297QUrl QQuickText::baseUrl() const
2298{
2299 Q_D(const QQuickText);
2300 if (!d->extra.isAllocated() || d->extra->baseUrl.isEmpty()) {
2301 if (QQmlContext *context = qmlContext(this))
2302 return context->baseUrl();
2303 else
2304 return QUrl();
2305 } else {
2306 return d->extra->baseUrl;
2307 }
2308}
2309
2310void QQuickText::setBaseUrl(const QUrl &url)
2311{
2312 Q_D(QQuickText);
2313 if (baseUrl() != url) {
2314 d->extra.value().baseUrl = url;
2315
2316 if (d->richText) {
2317 d->ensureDoc();
2318 d->extra->doc->setBaseUrl(url);
2319 }
2320 if (d->styledText) {
2321 d->textHasChanged = true;
2322 if (d->extra.isAllocated()) {
2323 qDeleteAll(d->extra->imgTags);
2324 d->extra->imgTags.clear();
2325 }
2326 d->updateLayout();
2327 }
2328 emit baseUrlChanged();
2329 }
2330}
2331
2332void QQuickText::resetBaseUrl()
2333{
2334 if (QQmlContext *context = qmlContext(this))
2335 setBaseUrl(context->baseUrl());
2336 else
2337 setBaseUrl(QUrl());
2338}
2339
2340/*! \internal */
2341QRectF QQuickText::boundingRect() const
2342{
2343 Q_D(const QQuickText);
2344
2345 QRectF rect = d->layedOutTextRect;
2346 rect.moveLeft(QQuickTextUtil::alignedX(rect.width(), width(), effectiveHAlign()));
2347 rect.moveTop(QQuickTextUtil::alignedY(rect.height() + d->lineHeightOffset(), height(), d->vAlign));
2348
2349 if (d->style != Normal)
2350 rect.adjust(-1, 0, 1, 2);
2351 // Could include font max left/right bearings to either side of rectangle.
2352
2353 return rect;
2354}
2355
2356QRectF QQuickText::clipRect() const
2357{
2358 Q_D(const QQuickText);
2359
2360 QRectF rect = QQuickImplicitSizeItem::clipRect();
2361 if (d->style != Normal)
2362 rect.adjust(-1, 0, 1, 2);
2363 return rect;
2364}
2365
2366/*! \internal */
2367void QQuickText::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
2368{
2369 Q_D(QQuickText);
2370 if (d->text.isEmpty()) {
2371 QQuickItem::geometryChanged(newGeometry, oldGeometry);
2372 return;
2373 }
2374
2375 bool widthChanged = newGeometry.width() != oldGeometry.width();
2376 bool heightChanged = newGeometry.height() != oldGeometry.height();
2377 bool wrapped = d->wrapMode != QQuickText::NoWrap;
2378 bool elide = d->elideMode != QQuickText::ElideNone;
2379 bool scaleFont = d->fontSizeMode() != QQuickText::FixedSize && (widthValid() || heightValid());
2380 bool verticalScale = (d->fontSizeMode() & QQuickText::VerticalFit) && heightValid();
2381
2382 bool widthMaximum = newGeometry.width() >= oldGeometry.width() && !d->widthExceeded;
2383 bool heightMaximum = newGeometry.height() >= oldGeometry.height() && !d->heightExceeded;
2384
2385 bool verticalPositionChanged = heightChanged && d->vAlign != AlignTop;
2386
2387 if ((!widthChanged && !heightChanged) || d->internalWidthUpdate)
2388 goto geomChangeDone;
2389
2390 if ((effectiveHAlign() != QQuickText::AlignLeft && widthChanged) || verticalPositionChanged) {
2391 // If the width has changed and we're not left aligned do an update so the text is
2392 // repositioned even if a full layout isn't required. And the same for vertical.
2393 d->updateType = QQuickTextPrivate::UpdatePaintNode;
2394 update();
2395 }
2396
2397 if (!wrapped && !elide && !scaleFont && !verticalPositionChanged)
2398 goto geomChangeDone; // left aligned unwrapped text without eliding never needs relayout
2399
2400 if (elide // eliding and dimensions were and remain invalid;
2401 && ((widthValid() && oldGeometry.width() <= 0 && newGeometry.width() <= 0)
2402 || (heightValid() && oldGeometry.height() <= 0 && newGeometry.height() <= 0))) {
2403 goto geomChangeDone;
2404 }
2405
2406 if (widthMaximum && heightMaximum && !d->isLineLaidOutConnected() && !verticalPositionChanged) // Size is sufficient and growing.
2407 goto geomChangeDone;
2408
2409 if (!(widthChanged || widthMaximum) && !d->isLineLaidOutConnected()) { // only height has changed
2410 if (newGeometry.height() > oldGeometry.height()) {
2411 if (!d->heightExceeded && !qFuzzyIsNull(oldGeometry.height())) {
2412 // Height is adequate and growing, and it wasn't 0 previously.
2413 goto geomChangeDone;
2414 }
2415 if (d->lineCount == d->maximumLineCount()) // Reached maximum line and height is growing.
2416 goto geomChangeDone;
2417 } else if (newGeometry.height() < oldGeometry.height()) {
2418 if (d->lineCount < 2 && !verticalScale && newGeometry.height() > 0) // A single line won't be truncated until the text is 0 height.
2419 goto geomChangeDone;
2420
2421 if (!verticalScale // no scaling, no eliding, and either unwrapped, or no maximum line count.
2422 && d->elideMode != QQuickText::ElideRight
2423 && !(d->maximumLineCountValid && d->widthExceeded)) {
2424 goto geomChangeDone;
2425 }
2426 }
2427 } else if (!heightChanged && widthMaximum) {
2428 goto geomChangeDone;
2429 }
2430
2431 if (d->updateOnComponentComplete || d->textHasChanged) {
2432 // We need to re-elide
2433 d->updateLayout();
2434 } else {
2435 // We just need to re-layout
2436 d->updateSize();
2437 }
2438
2439geomChangeDone:
2440 QQuickItem::geometryChanged(newGeometry, oldGeometry);
2441}
2442
2443void QQuickText::triggerPreprocess()
2444{
2445 Q_D(QQuickText);
2446 if (d->updateType == QQuickTextPrivate::UpdateNone)
2447 d->updateType = QQuickTextPrivate::UpdatePreprocess;
2448 update();
2449}
2450
2451QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
2452{
2453 Q_UNUSED(data);
2454 Q_D(QQuickText);
2455
2456 if (d->text.isEmpty()) {
2457 delete oldNode;
2458 return nullptr;
2459 }
2460
2461 if (d->updateType != QQuickTextPrivate::UpdatePaintNode && oldNode != nullptr) {
2462 // Update done in preprocess() in the nodes
2463 d->updateType = QQuickTextPrivate::UpdateNone;
2464 return oldNode;
2465 }
2466
2467 d->updateType = QQuickTextPrivate::UpdateNone;
2468
2469 const qreal dy = QQuickTextUtil::alignedY(d->layedOutTextRect.height() + d->lineHeightOffset(), d->availableHeight(), d->vAlign) + topPadding();
2470
2471 QQuickTextNode *node = nullptr;
2472 if (!oldNode)
2473 node = new QQuickTextNode(this);
2474 else
2475 node = static_cast<QQuickTextNode *>(oldNode);
2476
2477 node->setUseNativeRenderer(d->renderType == NativeRendering);
2478 node->deleteContent();
2479 node->setMatrix(QMatrix4x4());
2480
2481 const QColor color = QColor::fromRgba(d->color);
2482 const QColor styleColor = QColor::fromRgba(d->styleColor);
2483 const QColor linkColor = QColor::fromRgba(d->linkColor);
2484
2485 if (d->richText) {
2486 const qreal dx = QQuickTextUtil::alignedX(d->layedOutTextRect.width(), d->availableWidth(), effectiveHAlign()) + leftPadding();
2487 d->ensureDoc();
2488 node->addTextDocument(QPointF(dx, dy), d->extra->doc, color, d->style, styleColor, linkColor);
2489 } else if (d->layedOutTextRect.width() > 0) {
2490 const qreal dx = QQuickTextUtil::alignedX(d->lineWidth, d->availableWidth(), effectiveHAlign()) + leftPadding();
2491 int unelidedLineCount = d->lineCount;
2492 if (d->elideLayout)
2493 unelidedLineCount -= 1;
2494 if (unelidedLineCount > 0) {
2495 node->addTextLayout(
2496 QPointF(dx, dy),
2497 &d->layout,
2498 color, d->style, styleColor, linkColor,
2499 QColor(), QColor(), -1, -1,
2500 0, unelidedLineCount);
2501 }
2502 if (d->elideLayout)
2503 node->addTextLayout(QPointF(dx, dy), d->elideLayout, color, d->style, styleColor, linkColor);
2504
2505 if (d->extra.isAllocated()) {
2506 for (QQuickStyledTextImgTag *img : qAsConst(d->extra->visibleImgTags)) {
2507 QQuickPixmap *pix = img->pix;
2508 if (pix && pix->isReady())
2509 node->addImage(QRectF(img->pos.x() + dx, img->pos.y() + dy, pix->width(), pix->height()), pix->image());
2510 }
2511 }
2512 }
2513
2514 // The font caches have now been initialized on the render thread, so they have to be
2515 // invalidated before we can use them from the main thread again.
2516 invalidateFontCaches();
2517
2518 return node;
2519}
2520
2521void QQuickText::updatePolish()
2522{
2523 Q_D(QQuickText);
2524 // If the fonts used for rendering are different from the ones used in the GUI thread,
2525 // it means we will get warnings and corrupted text. If this case is detected, we need
2526 // to update the text layout before creating the scenegraph nodes.
2527 if (!d->assignedFont.isEmpty() && QFontInfo(d->font).family() != d->assignedFont)
2528 d->polishSize = true;
2529
2530 if (d->polishSize) {
2531 d->updateSize();
2532 d->polishSize = false;
2533 }
2534 invalidateFontCaches();
2535}
2536
2537/*!
2538 \qmlproperty real QtQuick::Text::contentWidth
2539
2540 Returns the width of the text, including width past the width
2541 which is covered due to insufficient wrapping if WrapMode is set.
2542*/
2543qreal QQuickText::contentWidth() const
2544{
2545 Q_D(const QQuickText);
2546 return d->layedOutTextRect.width();
2547}
2548
2549/*!
2550 \qmlproperty real QtQuick::Text::contentHeight
2551
2552 Returns the height of the text, including height past the height
2553 which is covered due to there being more text than fits in the set height.
2554*/
2555qreal QQuickText::contentHeight() const
2556{
2557 Q_D(const QQuickText);
2558 return d->layedOutTextRect.height();
2559}
2560
2561/*!
2562 \qmlproperty real QtQuick::Text::lineHeight
2563
2564 Sets the line height for the text.
2565 The value can be in pixels or a multiplier depending on lineHeightMode.
2566
2567 The default value is a multiplier of 1.0.
2568 The line height must be a positive value.
2569*/
2570qreal QQuickText::lineHeight() const
2571{
2572 Q_D(const QQuickText);
2573 return d->lineHeight();
2574}
2575
2576void QQuickText::setLineHeight(qreal lineHeight)
2577{
2578 Q_D(QQuickText);
2579
2580 if ((d->lineHeight() == lineHeight) || (lineHeight < 0.0))
2581 return;
2582
2583 d->extra.value().lineHeightValid = true;
2584 d->extra.value().lineHeight = lineHeight;
2585 d->implicitHeightValid = false;
2586 d->updateLayout();
2587 emit lineHeightChanged(lineHeight);
2588}
2589
2590/*!
2591 \qmlproperty enumeration QtQuick::Text::lineHeightMode
2592
2593 This property determines how the line height is specified.
2594 The possible values are:
2595
2596 \list
2597 \li Text.ProportionalHeight (default) - this sets the spacing proportional to the
2598 line (as a multiplier). For example, set to 2 for double spacing.
2599 \li Text.FixedHeight - this sets the line height to a fixed line height (in pixels).
2600 \endlist
2601*/
2602QQuickText::LineHeightMode QQuickText::lineHeightMode() const
2603{
2604 Q_D(const QQuickText);
2605 return d->lineHeightMode();
2606}
2607
2608void QQuickText::setLineHeightMode(LineHeightMode mode)
2609{
2610 Q_D(QQuickText);
2611 if (mode == d->lineHeightMode())
2612 return;
2613
2614 d->implicitHeightValid = false;
2615 d->extra.value().lineHeightValid = true;
2616 d->extra.value().lineHeightMode = mode;
2617 d->updateLayout();
2618
2619 emit lineHeightModeChanged(mode);
2620}
2621
2622/*!
2623 \qmlproperty enumeration QtQuick::Text::fontSizeMode
2624
2625 This property specifies how the font size of the displayed text is determined.
2626 The possible values are:
2627
2628 \list
2629 \li Text.FixedSize (default) - The size specified by \l font.pixelSize
2630 or \l font.pointSize is used.
2631 \li Text.HorizontalFit - The largest size up to the size specified that fits
2632 within the width of the item without wrapping is used.
2633 \li Text.VerticalFit - The largest size up to the size specified that fits
2634 the height of the item is used.
2635 \li Text.Fit - The largest size up to the size specified that fits within the
2636 width and height of the item is used.
2637 \endlist
2638
2639 The font size of fitted text has a minimum bound specified by the
2640 minimumPointSize or minimumPixelSize property and maximum bound specified
2641 by either the \l font.pointSize or \l font.pixelSize properties.
2642
2643 \qml
2644 Text { text: "Hello"; fontSizeMode: Text.Fit; minimumPixelSize: 10; font.pixelSize: 72 }
2645 \endqml
2646
2647 If the text does not fit within the item bounds with the minimum font size
2648 the text will be elided as per the \l elide property.
2649
2650 If the \l textFormat property is set to \l Text.RichText, this will have no effect at all as the
2651 property will be ignored completely. If \l textFormat is set to \l Text.StyledText, then the
2652 property will be respected provided there is no font size tags inside the text. If there are
2653 font size tags, the property will still respect those. This can cause it to not fully comply with
2654 the fontSizeMode setting.
2655*/
2656
2657QQuickText::FontSizeMode QQuickText::fontSizeMode() const
2658{
2659 Q_D(const QQuickText);
2660 return d->fontSizeMode();
2661}
2662
2663void QQuickText::setFontSizeMode(FontSizeMode mode)
2664{
2665 Q_D(QQuickText);
2666 if (d->fontSizeMode() == mode)
2667 return;
2668
2669 d->polishSize = true;
2670 polish();
2671
2672 d->extra.value().fontSizeMode = mode;
2673 emit fontSizeModeChanged();
2674}
2675
2676/*!
2677 \qmlproperty int QtQuick::Text::minimumPixelSize
2678
2679 This property specifies the minimum font pixel size of text scaled by the
2680 fontSizeMode property.
2681
2682 If the fontSizeMode is Text.FixedSize or the \l font.pixelSize is -1 this
2683 property is ignored.
2684*/
2685
2686int QQuickText::minimumPixelSize() const
2687{
2688 Q_D(const QQuickText);
2689 return d->minimumPixelSize();
2690}
2691
2692void QQuickText::setMinimumPixelSize(int size)
2693{
2694 Q_D(QQuickText);
2695 if (d->minimumPixelSize() == size)
2696 return;
2697
2698 if (d->fontSizeMode() != FixedSize && (widthValid() || heightValid())) {
2699 d->polishSize = true;
2700 polish();
2701 }
2702 d->extra.value().minimumPixelSize = size;
2703 emit minimumPixelSizeChanged();
2704}
2705
2706/*!
2707 \qmlproperty int QtQuick::Text::minimumPointSize
2708
2709 This property specifies the minimum font point \l size of text scaled by
2710 the fontSizeMode property.
2711
2712 If the fontSizeMode is Text.FixedSize or the \l font.pointSize is -1 this
2713 property is ignored.
2714*/
2715
2716int QQuickText::minimumPointSize() const
2717{
2718 Q_D(const QQuickText);
2719 return d->minimumPointSize();
2720}
2721
2722void QQuickText::setMinimumPointSize(int size)
2723{
2724 Q_D(QQuickText);
2725 if (d->minimumPointSize() == size)
2726 return;
2727
2728 if (d->fontSizeMode() != FixedSize && (widthValid() || heightValid())) {
2729 d->polishSize = true;
2730 polish();
2731 }
2732 d->extra.value().minimumPointSize = size;
2733 emit minimumPointSizeChanged();
2734}
2735
2736/*!
2737 Returns the number of resources (images) that are being loaded asynchronously.
2738*/
2739int QQuickText::resourcesLoading() const
2740{
2741 Q_D(const QQuickText);
2742 if (d->richText && d->extra.isAllocated() && d->extra->doc)
2743 return d->extra->doc->resourcesLoading();
2744 return 0;
2745}
2746
2747/*! \internal */
2748void QQuickText::componentComplete()
2749{
2750 Q_D(QQuickText);
2751 if (d->updateOnComponentComplete) {
2752 if (d->richText) {
2753 d->ensureDoc();
2754 if (d->markdownText)
2755 d->extra->doc->setMarkdownText(d->text);
2756 else
2757 d->extra->doc->setText(d->text);
2758 d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft();
2759 } else {
2760 d->rightToLeftText = d->text.isRightToLeft();
2761 }
2762 d->determineHorizontalAlignment();
2763 }
2764 QQuickItem::componentComplete();
2765 if (d->updateOnComponentComplete)
2766 d->updateLayout();
2767}
2768
2769QString QQuickTextPrivate::anchorAt(const QTextLayout *layout, const QPointF &mousePos)
2770{
2771 for (int i = 0; i < layout->lineCount(); ++i) {
2772 QTextLine line = layout->lineAt(i);
2773 if (line.naturalTextRect().contains(mousePos)) {
2774 int charPos = line.xToCursor(mousePos.x(), QTextLine::CursorOnCharacter);
2775 const auto formats = layout->formats();
2776 for (const QTextLayout::FormatRange &formatRange : formats) {
2777 if (formatRange.format.isAnchor()
2778 && charPos >= formatRange.start
2779 && charPos < formatRange.start + formatRange.length) {
2780 return formatRange.format.anchorHref();
2781 }
2782 }
2783 break;
2784 }
2785 }
2786 return QString();
2787}
2788
2789QString QQuickTextPrivate::anchorAt(const QPointF &mousePos) const
2790{
2791 Q_Q(const QQuickText);
2792 QPointF translatedMousePos = mousePos;
2793 translatedMousePos.rx() -= q->leftPadding();
2794 translatedMousePos.ry() -= q->topPadding() + QQuickTextUtil::alignedY(layedOutTextRect.height() + lineHeightOffset(), availableHeight(), vAlign);
2795 if (styledText) {
2796 QString link = anchorAt(&layout, translatedMousePos);
2797 if (link.isEmpty() && elideLayout)
2798 link = anchorAt(elideLayout, translatedMousePos);
2799 return link;
2800 } else if (richText && extra.isAllocated() && extra->doc) {
2801 translatedMousePos.rx() -= QQuickTextUtil::alignedX(layedOutTextRect.width(), availableWidth(), q->effectiveHAlign());
2802 return extra->doc->documentLayout()->anchorAt(translatedMousePos);
2803 }
2804 return QString();
2805}
2806
2807bool QQuickTextPrivate::isLinkActivatedConnected()
2808{
2809 Q_Q(QQuickText);
2810 IS_SIGNAL_CONNECTED(q, QQuickText, linkActivated, (const QString &));
2811}
2812
2813/*! \internal */
2814void QQuickText::mousePressEvent(QMouseEvent *event)
2815{
2816 Q_D(QQuickText);
2817
2818 QString link;
2819 if (d->isLinkActivatedConnected())
2820 link = d->anchorAt(event->localPos());
2821
2822 if (link.isEmpty()) {
2823 event->setAccepted(false);
2824 } else {
2825 d->extra.value().activeLink = link;
2826 }
2827
2828 // ### may malfunction if two of the same links are clicked & dragged onto each other)
2829
2830 if (!event->isAccepted())
2831 QQuickItem::mousePressEvent(event);
2832}
2833
2834
2835/*! \internal */
2836void QQuickText::mouseReleaseEvent(QMouseEvent *event)
2837{
2838 Q_D(QQuickText);
2839
2840 // ### confirm the link, and send a signal out
2841
2842 QString link;
2843 if (d->isLinkActivatedConnected())
2844 link = d->anchorAt(event->localPos());
2845
2846 if (!link.isEmpty() && d->extra.isAllocated() && d->extra->activeLink == link)
2847 emit linkActivated(d->extra->activeLink);
2848 else
2849 event->setAccepted(false);
2850
2851 if (!event->isAccepted())
2852 QQuickItem::mouseReleaseEvent(event);
2853}
2854
2855bool QQuickTextPrivate::isLinkHoveredConnected()
2856{
2857 Q_Q(QQuickText);
2858 IS_SIGNAL_CONNECTED(q, QQuickText, linkHovered, (const QString &));
2859}
2860
2861/*!
2862 \qmlsignal QtQuick::Text::linkHovered(string link)
2863 \since 5.2
2864
2865 This signal is emitted when the user hovers a link embedded in the
2866 text. The link must be in rich text or HTML format and the \a link
2867 string provides access to the particular link.
2868
2869 \sa hoveredLink, linkAt()
2870*/
2871
2872/*!
2873 \qmlproperty string QtQuick::Text::hoveredLink
2874 \since 5.2
2875
2876 This property contains the link string when the user hovers a link
2877 embedded in the text. The link must be in rich text or HTML format
2878 and the \a hoveredLink string provides access to the particular link.
2879
2880 \sa linkHovered, linkAt()
2881*/
2882
2883QString QQuickText::hoveredLink() const
2884{
2885 Q_D(const QQuickText);
2886 if (const_cast<QQuickTextPrivate *>(d)->isLinkHoveredConnected()) {
2887 if (d->extra.isAllocated())
2888 return d->extra->hoveredLink;
2889 } else {
2890#if QT_CONFIG(cursor)
2891 if (QQuickWindow *wnd = window()) {
2892 QPointF pos = QCursor::pos(wnd->screen()) - wnd->position() - mapToScene(QPointF(0, 0));
2893 return d->anchorAt(pos);
2894 }
2895#endif // cursor
2896 }
2897 return QString();
2898}
2899
2900void QQuickTextPrivate::processHoverEvent(QHoverEvent *event)
2901{
2902 Q_Q(QQuickText);
2903 qCDebug(DBG_HOVER_TRACE) << q;
2904 QString link;
2905 if (isLinkHoveredConnected()) {
2906 if (event->type() != QEvent::HoverLeave)
2907 link = anchorAt(event->posF());
2908
2909 if ((!extra.isAllocated() && !link.isEmpty()) || (extra.isAllocated() && extra->hoveredLink != link)) {
2910 extra.value().hoveredLink = link;
2911 emit q->linkHovered(extra->hoveredLink);
2912 }
2913 }
2914 event->setAccepted(!link.isEmpty());
2915}
2916
2917void QQuickText::hoverEnterEvent(QHoverEvent *event)
2918{
2919 Q_D(QQuickText);
2920 d->processHoverEvent(event);
2921}
2922
2923void QQuickText::hoverMoveEvent(QHoverEvent *event)
2924{
2925 Q_D(QQuickText);
2926 d->processHoverEvent(event);
2927}
2928
2929void QQuickText::hoverLeaveEvent(QHoverEvent *event)
2930{
2931 Q_D(QQuickText);
2932 d->processHoverEvent(event);
2933}
2934
2935/*!
2936 \qmlproperty enumeration QtQuick::Text::renderType
2937
2938 Override the default rendering type for this component.
2939
2940 Supported render types are:
2941 \list
2942 \li Text.QtRendering
2943 \li Text.NativeRendering
2944 \endlist
2945
2946 Select Text.NativeRendering if you prefer text to look native on the target platform and do
2947 not require advanced features such as transformation of the text. Using such features in
2948 combination with the NativeRendering render type will lend poor and sometimes pixelated
2949 results.
2950
2951 The default rendering type is determined by \l QQuickWindow::textRenderType().
2952*/
2953QQuickText::RenderType QQuickText::renderType() const
2954{
2955 Q_D(const QQuickText);
2956 return d->renderType;
2957}
2958
2959void QQuickText::setRenderType(QQuickText::RenderType renderType)
2960{
2961 Q_D(QQuickText);
2962 if (d->renderType == renderType)
2963 return;
2964
2965 d->renderType = renderType;
2966 emit renderTypeChanged();
2967
2968 if (isComponentComplete())
2969 d->updateLayout();
2970}
2971
2972#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2973#if QT_DEPRECATED_SINCE(5, 15)
2974/*!
2975 \qmlmethod QtQuick::Text::doLayout()
2976 \deprecated
2977
2978 Use \l forceLayout() instead.
2979*/
2980void QQuickText::doLayout()
2981{
2982 forceLayout();
2983}
2984
2985#endif
2986#endif
2987/*!
2988 \qmlmethod QtQuick::Text::forceLayout()
2989 \since 5.9
2990
2991 Triggers a re-layout of the displayed text.
2992*/
2993void QQuickText::forceLayout()
2994{
2995 Q_D(QQuickText);
2996 d->updateSize();
2997}
2998
2999/*!
3000 \qmlmethod QtQuick::Text::linkAt(real x, real y)
3001 \since 5.3
3002
3003 Returns the link string at point \a x, \a y in content coordinates,
3004 or an empty string if no link exists at that point.
3005
3006 \sa hoveredLink
3007*/
3008QString QQuickText::linkAt(qreal x, qreal y) const
3009{
3010 Q_D(const QQuickText);
3011 return d->anchorAt(QPointF(x, y));
3012}
3013
3014/*!
3015 * \internal
3016 *
3017 * Invalidates font caches owned by the text objects owned by the element
3018 * to work around the fact that text objects cannot be used from multiple threads.
3019 */
3020void QQuickText::invalidateFontCaches()
3021{
3022 Q_D(QQuickText);
3023
3024 if (d->richText && d->extra.isAllocated() && d->extra->doc != nullptr) {
3025 QTextBlock block;
3026 for (block = d->extra->doc->firstBlock(); block.isValid(); block = block.next()) {
3027 if (block.layout() != nullptr && block.layout()->engine() != nullptr)
3028 block.layout()->engine()->resetFontEngineCache();
3029 }
3030 } else {
3031 if (d->layout.engine() != nullptr)
3032 d->layout.engine()->resetFontEngineCache();
3033 }
3034}
3035
3036/*!
3037 \since 5.6
3038 \qmlproperty real QtQuick::Text::padding
3039 \qmlproperty real QtQuick::Text::topPadding
3040 \qmlproperty real QtQuick::Text::leftPadding
3041 \qmlproperty real QtQuick::Text::bottomPadding
3042 \qmlproperty real QtQuick::Text::rightPadding
3043
3044 These properties hold the padding around the content. This space is reserved
3045 in addition to the contentWidth and contentHeight.
3046*/
3047qreal QQuickText::padding() const
3048{
3049 Q_D(const QQuickText);
3050 return d->padding();
3051}
3052
3053void QQuickText::setPadding(qreal padding)
3054{
3055 Q_D(QQuickText);
3056 if (qFuzzyCompare(d->padding(), padding))
3057 return;
3058
3059 d->extra.value().padding = padding;
3060 d->updateSize();
3061 emit paddingChanged();
3062 if (!d->extra.isAllocated() || !d->extra->explicitTopPadding)
3063 emit topPaddingChanged();
3064 if (!d->extra.isAllocated() || !d->extra->explicitLeftPadding)
3065 emit leftPaddingChanged();
3066 if (!d->extra.isAllocated() || !d->extra->explicitRightPadding)
3067 emit rightPaddingChanged();
3068 if (!d->extra.isAllocated() || !d->extra->explicitBottomPadding)
3069 emit bottomPaddingChanged();
3070}
3071
3072void QQuickText::resetPadding()
3073{
3074 setPadding(0);
3075}
3076
3077qreal QQuickText::topPadding() const
3078{
3079 Q_D(const QQuickText);
3080 if (d->extra.isAllocated() && d->extra->explicitTopPadding)
3081 return d->extra->topPadding;
3082 return d->padding();
3083}
3084
3085void QQuickText::setTopPadding(qreal padding)
3086{
3087 Q_D(QQuickText);
3088 d->setTopPadding(padding);
3089}
3090
3091void QQuickText::resetTopPadding()
3092{
3093 Q_D(QQuickText