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