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