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

source code of qtdeclarative/src/quick/items/qquicktext.cpp