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 cannot be used with rich text.
2361
2362 Eliding can be:
2363
2364 \value Text.ElideNone - the default
2365 \value Text.ElideLeft
2366 \value Text.ElideMiddle
2367 \value Text.ElideRight
2368
2369 If this property is set to Text.ElideRight, it can be used with \l {wrapMode}{wrapped}
2370 text. The text will only elide if \c maximumLineCount, or \c height has been set.
2371 If both \c maximumLineCount and \c height are set, \c maximumLineCount will
2372 apply unless the lines do not fit in the height allowed.
2373
2374 If the text is a multi-length string, and the mode is not \c Text.ElideNone,
2375 the first string that fits will be used, otherwise the last will be elided.
2376
2377 Multi-length strings are ordered from longest to shortest, separated by the
2378 Unicode "String Terminator" character \c U009C (write this in QML with \c{"\u009C"} or \c{"\x9C"}).
2379*/
2380QQuickText::TextElideMode QQuickText::elideMode() const
2381{
2382 Q_D(const QQuickText);
2383 return d->elideMode;
2384}
2385
2386void QQuickText::setElideMode(QQuickText::TextElideMode mode)
2387{
2388 Q_D(QQuickText);
2389 if (mode == d->elideMode)
2390 return;
2391
2392 d->elideMode = mode;
2393 d->updateLayout();
2394
2395 emit elideModeChanged(mode);
2396}
2397
2398/*!
2399 \qmlproperty url QtQuick::Text::baseUrl
2400
2401 This property specifies a base URL that is used to resolve relative URLs
2402 within the text.
2403
2404 Urls are resolved to be within the same directory as the target of the base
2405 URL meaning any portion of the path after the last '/' will be ignored.
2406
2407 \table
2408 \header \li Base URL \li Relative URL \li Resolved URL
2409 \row \li http://qt-project.org/ \li images/logo.png \li http://qt-project.org/images/logo.png
2410 \row \li http://qt-project.org/index.html \li images/logo.png \li http://qt-project.org/images/logo.png
2411 \row \li http://qt-project.org/content \li images/logo.png \li http://qt-project.org/content/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/index.html \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/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 \endtable
2417
2418 The default value is the url of the QML file instantiating the Text item.
2419*/
2420
2421QUrl QQuickText::baseUrl() const
2422{
2423 Q_D(const QQuickText);
2424 if (!d->extra.isAllocated() || d->extra->baseUrl.isEmpty()) {
2425 if (QQmlContext *context = qmlContext(this))
2426 return context->baseUrl();
2427 else
2428 return QUrl();
2429 } else {
2430 return d->extra->baseUrl;
2431 }
2432}
2433
2434void QQuickText::setBaseUrl(const QUrl &url)
2435{
2436 Q_D(QQuickText);
2437 if (baseUrl() != url) {
2438 d->extra.value().baseUrl = url;
2439
2440 if (d->richText) {
2441 d->ensureDoc();
2442 d->extra->doc->setBaseUrl(url);
2443 }
2444 if (d->styledText) {
2445 d->textHasChanged = true;
2446 if (d->extra.isAllocated()) {
2447 qDeleteAll(d->extra->imgTags);
2448 d->extra->imgTags.clear();
2449 }
2450 d->updateLayout();
2451 }
2452 emit baseUrlChanged();
2453 }
2454}
2455
2456void QQuickText::resetBaseUrl()
2457{
2458 if (QQmlContext *context = qmlContext(this))
2459 setBaseUrl(context->baseUrl());
2460 else
2461 setBaseUrl(QUrl());
2462}
2463
2464/*!
2465 Returns the extents of the text after layout.
2466 If the \l style() is not \c Text.Normal, a margin is added to ensure
2467 that the rendering effect will fit within this rectangle.
2468
2469 \sa contentWidth(), contentHeight(), clipRect()
2470*/
2471QRectF QQuickText::boundingRect() const
2472{
2473 Q_D(const QQuickText);
2474
2475 QRectF rect = d->layedOutTextRect;
2476 rect.moveLeft(QQuickTextUtil::alignedX(rect.width(), width(), effectiveHAlign()));
2477 rect.moveTop(QQuickTextUtil::alignedY(rect.height() + d->lineHeightOffset(), height(), d->vAlign));
2478
2479 if (d->style != Normal)
2480 rect.adjust(-1, 0, 1, 2);
2481 // Could include font max left/right bearings to either side of rectangle.
2482
2483 return rect;
2484}
2485
2486/*!
2487 Returns a rectangular area slightly larger than what is currently visible
2488 in \l viewportItem(); otherwise, the rectangle \c (0, 0, width, height).
2489 The text will be clipped to fit if \l clip is \c true.
2490
2491 \note If the \l style is not \c Text.Normal, the clip rectangle is adjusted
2492 to be slightly larger, to limit clipping of the outline effect at the edges.
2493 But it still looks better to set \l clip to \c false in that case.
2494
2495 \sa contentWidth(), contentHeight(), boundingRect()
2496*/
2497QRectF QQuickText::clipRect() const
2498{
2499 Q_D(const QQuickText);
2500
2501 QRectF rect = QQuickImplicitSizeItem::clipRect();
2502 if (d->style != Normal)
2503 rect.adjust(-1, 0, 1, 2);
2504 return rect;
2505}
2506
2507/*! \internal */
2508void QQuickText::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
2509{
2510 Q_D(QQuickText);
2511 if (d->text.isEmpty()) {
2512 QQuickItem::geometryChange(newGeometry, oldGeometry);
2513 return;
2514 }
2515
2516 bool widthChanged = newGeometry.width() != oldGeometry.width();
2517 bool heightChanged = newGeometry.height() != oldGeometry.height();
2518 bool wrapped = d->wrapMode != QQuickText::NoWrap;
2519 bool elide = d->elideMode != QQuickText::ElideNone;
2520 bool scaleFont = d->fontSizeMode() != QQuickText::FixedSize && (widthValid() || heightValid());
2521 bool verticalScale = (d->fontSizeMode() & QQuickText::VerticalFit) && heightValid();
2522
2523 bool widthMaximum = newGeometry.width() >= oldGeometry.width() && !d->widthExceeded;
2524 bool heightMaximum = newGeometry.height() >= oldGeometry.height() && !d->heightExceeded;
2525
2526 bool verticalPositionChanged = heightChanged && d->vAlign != AlignTop;
2527
2528 if ((!widthChanged && !heightChanged) || d->internalWidthUpdate)
2529 goto geomChangeDone;
2530
2531 if ((effectiveHAlign() != QQuickText::AlignLeft && widthChanged) || verticalPositionChanged) {
2532 // If the width has changed and we're not left aligned do an update so the text is
2533 // repositioned even if a full layout isn't required. And the same for vertical.
2534 d->updateType = QQuickTextPrivate::UpdatePaintNode;
2535 update();
2536 }
2537
2538 if (!wrapped && !elide && !scaleFont && !verticalPositionChanged)
2539 goto geomChangeDone; // left aligned unwrapped text without eliding never needs relayout
2540
2541 if (elide // eliding and dimensions were and remain invalid;
2542 && ((widthValid() && oldGeometry.width() <= 0 && newGeometry.width() <= 0)
2543 || (heightValid() && oldGeometry.height() <= 0 && newGeometry.height() <= 0))) {
2544 goto geomChangeDone;
2545 }
2546
2547 if (widthMaximum && heightMaximum && !d->isLineLaidOutConnected() && !verticalPositionChanged && !elide) // Size is sufficient and growing.
2548 goto geomChangeDone;
2549
2550 if (!(widthChanged || widthMaximum) && !d->isLineLaidOutConnected()) { // only height has changed
2551 if (!verticalPositionChanged) {
2552 if (newGeometry.height() > oldGeometry.height()) {
2553 if (!d->heightExceeded && !qFuzzyIsNull(oldGeometry.height())) {
2554 // Height is adequate and growing, and it wasn't 0 previously.
2555 goto geomChangeDone;
2556 }
2557 if (d->lineCount == d->maximumLineCount()) // Reached maximum line and height is growing.
2558 goto geomChangeDone;
2559 } else if (newGeometry.height() < oldGeometry.height()) {
2560 if (d->lineCount < 2 && !verticalScale && newGeometry.height() > 0) // A single line won't be truncated until the text is 0 height.
2561 goto geomChangeDone;
2562
2563 if (!verticalScale // no scaling, no eliding, and either unwrapped, or no maximum line count.
2564 && d->elideMode != QQuickText::ElideRight
2565 && !(d->maximumLineCountValid && d->widthExceeded)) {
2566 goto geomChangeDone;
2567 }
2568 }
2569 }
2570 } else if (!heightChanged && widthMaximum && !elide) {
2571 if (oldGeometry.width() > 0) {
2572 // no change to height, width is adequate and wasn't 0 before
2573 // (old width could also be negative if it was 0 and the margins
2574 // were set)
2575 goto geomChangeDone;
2576 }
2577 }
2578
2579 if (d->updateOnComponentComplete || d->textHasChanged) {
2580 // We need to re-elide
2581 d->updateLayout();
2582 } else {
2583 // We just need to re-layout
2584 d->updateSize();
2585 }
2586
2587geomChangeDone:
2588 QQuickItem::geometryChange(newGeometry, oldGeometry);
2589}
2590
2591void QQuickText::triggerPreprocess()
2592{
2593 Q_D(QQuickText);
2594 if (d->updateType == QQuickTextPrivate::UpdateNone)
2595 d->updateType = QQuickTextPrivate::UpdatePreprocess;
2596 update();
2597}
2598
2599QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
2600{
2601 Q_UNUSED(data);
2602 Q_D(QQuickText);
2603
2604 if (d->text.isEmpty()) {
2605 d->containsUnscalableGlyphs = false;
2606 delete oldNode;
2607 return nullptr;
2608 }
2609
2610 if (d->updateType != QQuickTextPrivate::UpdatePaintNode && oldNode != nullptr) {
2611 // Update done in preprocess() in the nodes
2612 d->updateType = QQuickTextPrivate::UpdateNone;
2613 return oldNode;
2614 }
2615
2616 d->updateType = QQuickTextPrivate::UpdateNone;
2617
2618 const qreal dy = QQuickTextUtil::alignedY(d->layedOutTextRect.height() + d->lineHeightOffset(), d->availableHeight(), d->vAlign) + topPadding();
2619
2620 QSGInternalTextNode *node = nullptr;
2621 if (!oldNode)
2622 node = d->sceneGraphContext()->createInternalTextNode(d->sceneGraphRenderContext());
2623 else
2624 node = static_cast<QSGInternalTextNode *>(oldNode);
2625
2626 node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
2627
2628 node->setTextStyle(QSGTextNode::TextStyle(d->style));
2629 node->setRenderType(QSGTextNode::RenderType(d->renderType));
2630 node->setRenderTypeQuality(d->renderTypeQuality());
2631
2632 QSGInternalTextNode::RecycleBin recycleBin;
2633 node->recycle(&recycleBin);
2634 node->setMatrix(QMatrix4x4());
2635
2636 node->setColor(QColor::fromRgba(d->color));
2637 node->setStyleColor(QColor::fromRgba(d->styleColor));
2638 node->setLinkColor(QColor::fromRgba(d->linkColor));
2639
2640 node->setDevicePixelRatio(d->effectiveDevicePixelRatio());
2641
2642 if (d->richText) {
2643 node->setViewport(clipRect());
2644 const qreal dx = QQuickTextUtil::alignedX(d->layedOutTextRect.width(), d->availableWidth(), effectiveHAlign()) + leftPadding();
2645 d->ensureDoc();
2646 node->addTextDocument(QPointF(dx, dy), d->extra->doc, &recycleBin);
2647 } else if (d->layedOutTextRect.width() > 0) {
2648 if (flags().testFlag(ItemObservesViewport))
2649 node->setViewport(clipRect());
2650 else
2651 node->setViewport(QRectF{});
2652 const qreal dx = QQuickTextUtil::alignedX(d->lineWidth, d->availableWidth(), effectiveHAlign()) + leftPadding();
2653 int unelidedLineCount = d->lineCount;
2654 if (d->elideLayout)
2655 unelidedLineCount -= 1;
2656 if (unelidedLineCount > 0)
2657 node->addTextLayout(QPointF(dx, dy), &d->layout, &recycleBin, -1, -1,0, unelidedLineCount);
2658
2659 if (d->elideLayout)
2660 node->addTextLayout(QPointF(dx, dy), d->elideLayout.get(), &recycleBin);
2661
2662 if (d->extra.isAllocated()) {
2663 for (QQuickStyledTextImgTag *img : std::as_const(d->extra->visibleImgTags)) {
2664 if (img->pix && img->pix->isReady()) {
2665 node->addImage(QRectF(img->pos.x() + dx,
2666 img->pos.y() + dy,
2667 img->size.width(),
2668 img->size.height()),
2669 img->pix->image(),
2670 &recycleBin);
2671 }
2672 }
2673 }
2674 }
2675
2676 d->containsUnscalableGlyphs = node->containsUnscalableGlyphs();
2677
2678 // The font caches have now been initialized on the render thread, so they have to be
2679 // invalidated before we can use them from the main thread again.
2680 invalidateFontCaches();
2681
2682 node->discardUnusedNodes(&recycleBin);
2683
2684 return node;
2685}
2686
2687void QQuickText::updatePolish()
2688{
2689 Q_D(QQuickText);
2690 const bool clipNodeChanged =
2691 d->componentComplete && d->clipNode() && d->clipNode()->rect() != clipRect();
2692 if (clipNodeChanged)
2693 d->dirty(QQuickItemPrivate::Clip);
2694
2695 // If the fonts used for rendering are different from the ones used in the GUI thread,
2696 // it means we will get warnings and corrupted text. If this case is detected, we need
2697 // to update the text layout before creating the scenegraph nodes.
2698 if (!d->assignedFont.isEmpty() && QFontInfo(d->font).family() != d->assignedFont)
2699 d->polishSize = true;
2700
2701 if (d->polishSize) {
2702 d->updateSize();
2703 d->polishSize = false;
2704 }
2705 invalidateFontCaches();
2706}
2707
2708/*!
2709 \qmlproperty real QtQuick::Text::contentWidth
2710
2711 Returns the width of the text, including width past the width
2712 that is covered due to insufficient wrapping if WrapMode is set.
2713*/
2714qreal QQuickText::contentWidth() const
2715{
2716 Q_D(const QQuickText);
2717 return d->layedOutTextRect.width();
2718}
2719
2720/*!
2721 \qmlproperty real QtQuick::Text::contentHeight
2722
2723 Returns the height of the text, including height past the height
2724 that is covered due to there being more text than fits in the set height.
2725*/
2726qreal QQuickText::contentHeight() const
2727{
2728 Q_D(const QQuickText);
2729 return d->layedOutTextRect.height() + qMax(d->lineHeightOffset(), 0);
2730}
2731
2732/*!
2733 \qmlproperty real QtQuick::Text::lineHeight
2734
2735 Sets the line height for the text.
2736 The value can be in pixels or a multiplier depending on lineHeightMode.
2737
2738 The default value is a multiplier of 1.0.
2739 The line height must be a positive value.
2740*/
2741qreal QQuickText::lineHeight() const
2742{
2743 Q_D(const QQuickText);
2744 return d->lineHeight();
2745}
2746
2747void QQuickText::setLineHeight(qreal lineHeight)
2748{
2749 Q_D(QQuickText);
2750
2751 if ((d->lineHeight() == lineHeight) || (lineHeight < 0.0))
2752 return;
2753
2754 d->extra.value().lineHeightValid = true;
2755 d->extra.value().lineHeight = lineHeight;
2756 d->implicitHeightValid = false;
2757 d->updateLayout();
2758 emit lineHeightChanged(lineHeight);
2759}
2760
2761/*!
2762 \qmlproperty enumeration QtQuick::Text::lineHeightMode
2763
2764 This property determines how the line height is specified.
2765 The possible values are:
2766
2767 \value Text.ProportionalHeight (default) sets the spacing proportional to the line
2768 (as a multiplier). For example, set to 2 for double spacing.
2769 \value Text.FixedHeight sets the line height to a fixed line height (in pixels).
2770*/
2771QQuickText::LineHeightMode QQuickText::lineHeightMode() const
2772{
2773 Q_D(const QQuickText);
2774 return d->lineHeightMode();
2775}
2776
2777void QQuickText::setLineHeightMode(LineHeightMode mode)
2778{
2779 Q_D(QQuickText);
2780 if (mode == d->lineHeightMode())
2781 return;
2782
2783 d->implicitHeightValid = false;
2784 d->extra.value().lineHeightValid = true;
2785 d->extra.value().lineHeightMode = mode;
2786 d->updateLayout();
2787
2788 emit lineHeightModeChanged(mode);
2789}
2790
2791/*!
2792 \qmlproperty enumeration QtQuick::Text::fontSizeMode
2793
2794 This property specifies how the font size of the displayed text is determined.
2795 The possible values are:
2796
2797 \value Text.FixedSize
2798 (default) The size specified by \l font.pixelSize or \l font.pointSize is used.
2799 \value Text.HorizontalFit
2800 The largest size up to the size specified that fits within the width of the item
2801 without wrapping is used.
2802 \value Text.VerticalFit
2803 The largest size up to the size specified that fits the height of the item is used.
2804 \value Text.Fit
2805 The largest size up to the size specified that fits within the width and height
2806 of the item is used.
2807
2808 The font size of fitted text has a minimum bound specified by the
2809 minimumPointSize or minimumPixelSize property and maximum bound specified
2810 by either the \l font.pointSize or \l font.pixelSize properties.
2811
2812 \qml
2813 Text { text: "Hello"; fontSizeMode: Text.Fit; minimumPixelSize: 10; font.pixelSize: 72 }
2814 \endqml
2815
2816 If the text does not fit within the item bounds with the minimum font size
2817 the text will be elided as per the \l elide property.
2818
2819 If the \l textFormat property is set to \c Text.RichText, this will have no effect at all as the
2820 property will be ignored completely. If \l textFormat is set to \c Text.StyledText, then the
2821 property will be respected provided there is no font size tags inside the text. If there are
2822 font size tags, the property will still respect those. This can cause it to not fully comply with
2823 the fontSizeMode setting.
2824*/
2825
2826QQuickText::FontSizeMode QQuickText::fontSizeMode() const
2827{
2828 Q_D(const QQuickText);
2829 return d->fontSizeMode();
2830}
2831
2832void QQuickText::setFontSizeMode(FontSizeMode mode)
2833{
2834 Q_D(QQuickText);
2835 if (d->fontSizeMode() == mode)
2836 return;
2837
2838 d->polishSize = true;
2839 polish();
2840
2841 d->extra.value().fontSizeMode = mode;
2842 emit fontSizeModeChanged();
2843}
2844
2845/*!
2846 \qmlproperty int QtQuick::Text::minimumPixelSize
2847
2848 This property specifies the minimum font pixel size of text scaled by the
2849 fontSizeMode property.
2850
2851 If the fontSizeMode is Text.FixedSize or the \l font.pixelSize is -1 this
2852 property is ignored.
2853*/
2854
2855int QQuickText::minimumPixelSize() const
2856{
2857 Q_D(const QQuickText);
2858 return d->minimumPixelSize();
2859}
2860
2861void QQuickText::setMinimumPixelSize(int size)
2862{
2863 Q_D(QQuickText);
2864 if (d->minimumPixelSize() == size)
2865 return;
2866
2867 if (d->fontSizeMode() != FixedSize && (widthValid() || heightValid())) {
2868 d->polishSize = true;
2869 polish();
2870 }
2871 d->extra.value().minimumPixelSize = size;
2872 emit minimumPixelSizeChanged();
2873}
2874
2875/*!
2876 \qmlproperty int QtQuick::Text::minimumPointSize
2877
2878 This property specifies the minimum font point \l size of text scaled by
2879 the fontSizeMode property.
2880
2881 If the fontSizeMode is Text.FixedSize or the \l font.pointSize is -1 this
2882 property is ignored.
2883*/
2884
2885int QQuickText::minimumPointSize() const
2886{
2887 Q_D(const QQuickText);
2888 return d->minimumPointSize();
2889}
2890
2891void QQuickText::setMinimumPointSize(int size)
2892{
2893 Q_D(QQuickText);
2894 if (d->minimumPointSize() == size)
2895 return;
2896
2897 if (d->fontSizeMode() != FixedSize && (widthValid() || heightValid())) {
2898 d->polishSize = true;
2899 polish();
2900 }
2901 d->extra.value().minimumPointSize = size;
2902 emit minimumPointSizeChanged();
2903}
2904
2905/*!
2906 Returns the number of resources (images) that are being loaded asynchronously.
2907*/
2908int QQuickText::resourcesLoading() const
2909{
2910 Q_D(const QQuickText);
2911 if (d->richText && d->extra.isAllocated())
2912 return d->extra->pixmapsInProgress.size();
2913 return 0;
2914}
2915
2916/*! \internal */
2917void QQuickText::componentComplete()
2918{
2919 Q_D(QQuickText);
2920 if (d->updateOnComponentComplete) {
2921 if (d->richText)
2922 d->updateDocumentText();
2923 }
2924 QQuickItem::componentComplete();
2925 if (d->updateOnComponentComplete)
2926 d->updateLayout();
2927}
2928
2929QString QQuickTextPrivate::anchorAt(const QTextLayout *layout, const QPointF &mousePos)
2930{
2931 for (int i = 0; i < layout->lineCount(); ++i) {
2932 QTextLine line = layout->lineAt(i);
2933 if (line.naturalTextRect().contains(mousePos)) {
2934 int charPos = line.xToCursor(mousePos.x(), QTextLine::CursorOnCharacter);
2935 const auto formats = layout->formats();
2936 for (const QTextLayout::FormatRange &formatRange : formats) {
2937 if (formatRange.format.isAnchor()
2938 && charPos >= formatRange.start
2939 && charPos < formatRange.start + formatRange.length) {
2940 return formatRange.format.anchorHref();
2941 }
2942 }
2943 break;
2944 }
2945 }
2946 return QString();
2947}
2948
2949QString QQuickTextPrivate::anchorAt(const QPointF &mousePos) const
2950{
2951 Q_Q(const QQuickText);
2952 QPointF translatedMousePos = mousePos;
2953 translatedMousePos.rx() -= q->leftPadding();
2954 translatedMousePos.ry() -= q->topPadding() + QQuickTextUtil::alignedY(layedOutTextRect.height() + lineHeightOffset(), availableHeight(), vAlign);
2955 if (styledText) {
2956 translatedMousePos.rx() -= QQuickTextUtil::alignedX(lineWidth, availableWidth(), q->effectiveHAlign());
2957 QString link = anchorAt(&layout, translatedMousePos);
2958 if (link.isEmpty() && elideLayout)
2959 link = anchorAt(elideLayout.get(), translatedMousePos);
2960 return link;
2961 } else if (richText && extra.isAllocated() && extra->doc) {
2962 translatedMousePos.rx() -= QQuickTextUtil::alignedX(layedOutTextRect.width(), availableWidth(), q->effectiveHAlign());
2963 return extra->doc->documentLayout()->anchorAt(translatedMousePos);
2964 }
2965 return QString();
2966}
2967
2968bool QQuickTextPrivate::isLinkActivatedConnected()
2969{
2970 Q_Q(QQuickText);
2971 IS_SIGNAL_CONNECTED(q, QQuickText, linkActivated, (const QString &));
2972}
2973
2974/*! \internal */
2975void QQuickText::mousePressEvent(QMouseEvent *event)
2976{
2977 Q_D(QQuickText);
2978
2979 QString link;
2980 if (d->isLinkActivatedConnected())
2981 link = d->anchorAt(event->position());
2982
2983 if (link.isEmpty()) {
2984 event->setAccepted(false);
2985 } else {
2986 d->extra.value().activeLink = link;
2987 }
2988
2989 // ### may malfunction if two of the same links are clicked & dragged onto each other)
2990
2991 if (!event->isAccepted())
2992 QQuickItem::mousePressEvent(event);
2993}
2994
2995
2996/*! \internal */
2997void QQuickText::mouseReleaseEvent(QMouseEvent *event)
2998{
2999 Q_D(QQuickText);
3000
3001 // ### confirm the link, and send a signal out
3002
3003 QString link;
3004 if (d->isLinkActivatedConnected())
3005 link = d->anchorAt(event->position());
3006
3007 if (!link.isEmpty() && d->extra.isAllocated() && d->extra->activeLink == link)
3008 emit linkActivated(d->extra->activeLink);
3009 else
3010 event->setAccepted(false);
3011
3012 if (!event->isAccepted())
3013 QQuickItem::mouseReleaseEvent(event);
3014}
3015
3016bool QQuickTextPrivate::isLinkHoveredConnected()
3017{
3018 Q_Q(QQuickText);
3019 IS_SIGNAL_CONNECTED(q, QQuickText, linkHovered, (const QString &));
3020}
3021
3022static void getLinks_helper(const QTextLayout *layout, QList<QQuickTextPrivate::LinkDesc> *links)
3023{
3024 const auto formats = layout->formats();
3025 for (const QTextLayout::FormatRange &formatRange : formats) {
3026 if (formatRange.format.isAnchor()) {
3027 const int start = formatRange.start;
3028 const int len = formatRange.length;
3029 QTextLine line = layout->lineForTextPosition(start);
3030 QRectF r;
3031 r.setTop(line.y());
3032 r.setLeft(line.cursorToX(start, QTextLine::Leading));
3033 r.setHeight(line.height());
3034 r.setRight(line.cursorToX(start + len, QTextLine::Trailing));
3035 // ### anchorNames() is empty?! Not sure why this doesn't work
3036 // QString anchorName = formatRange.format.anchorNames().value(0); //### pick the first?
3037 // Therefore, we resort to QString::mid()
3038 QString anchorName = layout->text().mid(start, len);
3039 const QString anchorHref = formatRange.format.anchorHref();
3040 if (anchorName.isEmpty())
3041 anchorName = anchorHref;
3042 links->append( { anchorName, anchorHref, start, start + len, r.toRect()} );
3043 }
3044 }
3045}
3046
3047QList<QQuickTextPrivate::LinkDesc> QQuickTextPrivate::getLinks() const
3048{
3049 QList<QQuickTextPrivate::LinkDesc> links;
3050 getLinks_helper(&layout, &links);
3051 return links;
3052}
3053
3054
3055/*!
3056 \qmlsignal QtQuick::Text::linkHovered(string link)
3057 \since 5.2
3058
3059 This signal is emitted when the user hovers a link embedded in the
3060 text. The link must be in rich text or HTML format and the \a link
3061 string provides access to the particular link.
3062
3063 \sa hoveredLink, linkAt()
3064*/
3065
3066/*!
3067 \qmlproperty string QtQuick::Text::hoveredLink
3068 \since 5.2
3069
3070 This property contains the link string when the user hovers a link
3071 embedded in the text. The link must be in rich text or HTML format
3072 and the \a hoveredLink string provides access to the particular link.
3073
3074 \sa linkHovered, linkAt()
3075*/
3076
3077QString QQuickText::hoveredLink() const
3078{
3079 Q_D(const QQuickText);
3080 if (const_cast<QQuickTextPrivate *>(d)->isLinkHoveredConnected()) {
3081 if (d->extra.isAllocated())
3082 return d->extra->hoveredLink;
3083 } else {
3084#if QT_CONFIG(cursor)
3085 if (QQuickWindow *wnd = window()) {
3086 QPointF pos = QCursor::pos(wnd->screen()) - wnd->position() - mapToScene(QPointF(0, 0));
3087 return d->anchorAt(pos);
3088 }
3089#endif // cursor
3090 }
3091 return QString();
3092}
3093
3094void QQuickTextPrivate::processHoverEvent(QHoverEvent *event)
3095{
3096 Q_Q(QQuickText);
3097 qCDebug(lcHoverTrace) << q;
3098 QString link;
3099 if (isLinkHoveredConnected()) {
3100 if (event->type() != QEvent::HoverLeave)
3101 link = anchorAt(event->position());
3102
3103 if ((!extra.isAllocated() && !link.isEmpty()) || (extra.isAllocated() && extra->hoveredLink != link)) {
3104 extra.value().hoveredLink = link;
3105 emit q->linkHovered(extra->hoveredLink);
3106 }
3107 }
3108 event->ignore();
3109}
3110
3111void QQuickText::hoverEnterEvent(QHoverEvent *event)
3112{
3113 Q_D(QQuickText);
3114 d->processHoverEvent(event);
3115}
3116
3117void QQuickText::hoverMoveEvent(QHoverEvent *event)
3118{
3119 Q_D(QQuickText);
3120 d->processHoverEvent(event);
3121}
3122
3123void QQuickText::hoverLeaveEvent(QHoverEvent *event)
3124{
3125 Q_D(QQuickText);
3126 d->processHoverEvent(event);
3127}
3128
3129void QQuickText::invalidate()
3130{
3131 Q_D(QQuickText);
3132 d->textHasChanged = true;
3133 QMetaObject::invokeMethod(this,[&]{q_updateLayout();});
3134}
3135
3136bool QQuickTextPrivate::transformChanged(QQuickItem *transformedItem)
3137{
3138 // If there's a lot of text, we may need QQuickText::updatePaintNode() to call
3139 // QSGInternalTextNode::addTextLayout() again to populate a different range of lines
3140 if (flags & QQuickItem::ItemObservesViewport) {
3141 updateType = UpdatePaintNode;
3142 dirty(QQuickItemPrivate::Content);
3143 }
3144 return QQuickImplicitSizeItemPrivate::transformChanged(transformedItem);
3145}
3146
3147/*!
3148 \qmlproperty int QtQuick::Text::renderTypeQuality
3149 \since 6.0
3150
3151 Override the default rendering type quality for this component. This is a low-level
3152 customization which can be ignored in most cases. It currently only has an effect
3153 when \l renderType is \c Text.QtRendering.
3154
3155 The rasterization algorithm used by Text.QtRendering may give artifacts at
3156 large text sizes, such as sharp corners looking rounder than they should. If
3157 this is an issue for specific text items, increase the \c renderTypeQuality to
3158 improve rendering quality, at the expense of memory consumption.
3159
3160 The \c renderTypeQuality may be any integer over 0, or one of the following
3161 predefined values
3162
3163 \value Text.DefaultRenderTypeQuality -1 (default)
3164 \value Text.LowRenderTypeQuality 26
3165 \value Text.NormalRenderTypeQuality 52
3166 \value Text.HighRenderTypeQuality 104
3167 \value Text.VeryHighRenderTypeQuality 208
3168*/
3169int QQuickText::renderTypeQuality() const
3170{
3171 Q_D(const QQuickText);
3172 return d->renderTypeQuality();
3173}
3174
3175void QQuickText::setRenderTypeQuality(int renderTypeQuality)
3176{
3177 Q_D(QQuickText);
3178 if (renderTypeQuality == d->renderTypeQuality())
3179 return;
3180 d->extra.value().renderTypeQuality = renderTypeQuality;
3181
3182 if (isComponentComplete()) {
3183 d->updateType = QQuickTextPrivate::UpdatePaintNode;
3184 update();
3185 }
3186
3187 emit renderTypeQualityChanged();
3188}
3189
3190/*!
3191 \qmlproperty enumeration QtQuick::Text::renderType
3192
3193 Override the default rendering type for this component.
3194
3195 Supported render types are:
3196
3197 \value Text.QtRendering Text is rendered using a scalable distance field for each glyph.
3198 \value Text.NativeRendering Text is rendered using a platform-specific technique.
3199 \value Text.CurveRendering Text is rendered using a curve rasterizer running directly on the
3200 graphics hardware. (Introduced in Qt 6.7.0.)
3201
3202 Select \c Text.NativeRendering if you prefer text to look native on the target platform and do
3203 not require advanced features such as transformation of the text. Using such features in
3204 combination with the NativeRendering render type will lend poor and sometimes pixelated
3205 results.
3206
3207 Both \c Text.QtRendering and \c Text.CurveRendering are hardware-accelerated techniques.
3208 \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
3209 artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
3210 where \c QtRendering does not give good visual results or where reducing graphics memory
3211 consumption is a priority.
3212
3213 The default rendering type is determined by \l QQuickWindow::textRenderType().
3214*/
3215QQuickText::RenderType QQuickText::renderType() const
3216{
3217 Q_D(const QQuickText);
3218 return d->renderType;
3219}
3220
3221void QQuickText::setRenderType(QQuickText::RenderType renderType)
3222{
3223 Q_D(QQuickText);
3224 if (d->renderType == renderType)
3225 return;
3226
3227 d->renderType = renderType;
3228 emit renderTypeChanged();
3229
3230 if (isComponentComplete())
3231 d->updateLayout();
3232}
3233
3234#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
3235#if QT_DEPRECATED_SINCE(5, 15)
3236/*!
3237 \qmlmethod void QtQuick::Text::doLayout()
3238 \deprecated
3239
3240 Use \l forceLayout() instead.
3241*/
3242void QQuickText::doLayout()
3243{
3244 forceLayout();
3245}
3246
3247#endif
3248#endif
3249/*!
3250 \qmlmethod void QtQuick::Text::forceLayout()
3251 \since 5.9
3252
3253 Triggers a re-layout of the displayed text.
3254*/
3255void QQuickText::forceLayout()
3256{
3257 Q_D(QQuickText);
3258 d->updateSize();
3259}
3260
3261/*!
3262 \qmlmethod string QtQuick::Text::linkAt(real x, real y)
3263 \since 5.3
3264
3265 Returns the link string at point \a x, \a y in content coordinates,
3266 or an empty string if no link exists at that point.
3267
3268 \sa hoveredLink
3269*/
3270QString QQuickText::linkAt(qreal x, qreal y) const
3271{
3272 Q_D(const QQuickText);
3273 return d->anchorAt(QPointF(x, y));
3274}
3275
3276/*!
3277 * \internal
3278 *
3279 * Invalidates font caches owned by the text objects owned by the element
3280 * to work around the fact that text objects cannot be used from multiple threads.
3281 */
3282void QQuickText::invalidateFontCaches()
3283{
3284 Q_D(QQuickText);
3285
3286 if (d->richText && d->extra.isAllocated() && d->extra->doc != nullptr) {
3287 QTextBlock block;
3288 for (block = d->extra->doc->firstBlock(); block.isValid(); block = block.next()) {
3289 if (block.layout() != nullptr && block.layout()->engine() != nullptr)
3290 block.layout()->engine()->resetFontEngineCache();
3291 }
3292 } else {
3293 if (d->layout.engine() != nullptr)
3294 d->layout.engine()->resetFontEngineCache();
3295 }
3296}
3297
3298/*!
3299 \since 5.6
3300 \qmlproperty real QtQuick::Text::padding
3301 \qmlproperty real QtQuick::Text::topPadding
3302 \qmlproperty real QtQuick::Text::leftPadding
3303 \qmlproperty real QtQuick::Text::bottomPadding
3304 \qmlproperty real QtQuick::Text::rightPadding
3305
3306 These properties hold the padding around the content. This space is reserved
3307 in addition to the contentWidth and contentHeight.
3308*/
3309qreal QQuickText::padding() const
3310{
3311 Q_D(const QQuickText);
3312 return d->padding();
3313}
3314
3315void QQuickText::setPadding(qreal padding)
3316{
3317 Q_D(QQuickText);
3318 if (qFuzzyCompare(d->padding(), padding))
3319 return;
3320
3321 d->extra.value().padding = padding;
3322 d->updateSize();
3323 emit paddingChanged();
3324 if (!d->extra.isAllocated() || !d->extra->explicitTopPadding)
3325 emit topPaddingChanged();
3326 if (!d->extra.isAllocated() || !d->extra->explicitLeftPadding)
3327 emit leftPaddingChanged();
3328 if (!d->extra.isAllocated() || !d->extra->explicitRightPadding)
3329 emit rightPaddingChanged();
3330 if (!d->extra.isAllocated() || !d->extra->explicitBottomPadding)
3331 emit bottomPaddingChanged();
3332}
3333
3334void QQuickText::resetPadding()
3335{
3336 setPadding(0);
3337}
3338
3339qreal QQuickText::topPadding() const
3340{
3341 Q_D(const QQuickText);
3342 if (d->extra.isAllocated() && d->extra->explicitTopPadding)
3343 return d->extra->topPadding;
3344 return d->padding();
3345}
3346
3347void QQuickText::setTopPadding(qreal padding)
3348{
3349 Q_D(QQuickText);
3350 d->setTopPadding(padding);
3351}
3352
3353void QQuickText::resetTopPadding()
3354{
3355 Q_D(QQuickText);
3356 d->setTopPadding(0, true);
3357}
3358
3359qreal QQuickText::leftPadding() const
3360{
3361 Q_D(const QQuickText);
3362 if (d->extra.isAllocated() && d->extra->explicitLeftPadding)
3363 return d->extra->leftPadding;
3364 return d->padding();
3365}
3366
3367void QQuickText::setLeftPadding(qreal padding)
3368{
3369 Q_D(QQuickText);
3370 d->setLeftPadding(padding);
3371}
3372
3373void QQuickText::resetLeftPadding()
3374{
3375 Q_D(QQuickText);
3376 d->setLeftPadding(0, true);
3377}
3378
3379qreal QQuickText::rightPadding() const
3380{
3381 Q_D(const QQuickText);
3382 if (d->extra.isAllocated() && d->extra->explicitRightPadding)
3383 return d->extra->rightPadding;
3384 return d->padding();
3385}
3386
3387void QQuickText::setRightPadding(qreal padding)
3388{
3389 Q_D(QQuickText);
3390 d->setRightPadding(padding);
3391}
3392
3393void QQuickText::resetRightPadding()
3394{
3395 Q_D(QQuickText);
3396 d->setRightPadding(0, true);
3397}
3398
3399qreal QQuickText::bottomPadding() const
3400{
3401 Q_D(const QQuickText);
3402 if (d->extra.isAllocated() && d->extra->explicitBottomPadding)
3403 return d->extra->bottomPadding;
3404 return d->padding();
3405}
3406
3407void QQuickText::setBottomPadding(qreal padding)
3408{
3409 Q_D(QQuickText);
3410 d->setBottomPadding(padding);
3411}
3412
3413void QQuickText::resetBottomPadding()
3414{
3415 Q_D(QQuickText);
3416 d->setBottomPadding(0, true);
3417}
3418
3419/*!
3420 \qmlproperty string QtQuick::Text::fontInfo.family
3421 \since 5.9
3422
3423 The family name of the font that has been resolved for the current font
3424 and fontSizeMode.
3425*/
3426
3427/*!
3428 \qmlproperty string QtQuick::Text::fontInfo.styleName
3429 \since 5.9
3430
3431 The style name of the font info that has been resolved for the current font
3432 and fontSizeMode.
3433*/
3434
3435/*!
3436 \qmlproperty bool QtQuick::Text::fontInfo.bold
3437 \since 5.9
3438
3439 The bold state of the font info that has been resolved for the current font
3440 and fontSizeMode. This is true if the weight of the resolved font is bold or higher.
3441*/
3442
3443/*!
3444 \qmlproperty int QtQuick::Text::fontInfo.weight
3445 \since 5.9
3446
3447 The weight of the font info that has been resolved for the current font
3448 and fontSizeMode.
3449*/
3450
3451/*!
3452 \qmlproperty bool QtQuick::Text::fontInfo.italic
3453 \since 5.9
3454
3455 The italic state of the font info that has been resolved for the current font
3456 and fontSizeMode.
3457*/
3458
3459/*!
3460 \qmlproperty real QtQuick::Text::fontInfo.pointSize
3461 \since 5.9
3462
3463 The pointSize of the font info that has been resolved for the current font
3464 and fontSizeMode.
3465*/
3466
3467/*!
3468 \qmlproperty int QtQuick::Text::fontInfo.pixelSize
3469 \since 5.9
3470
3471 The pixel size of the font info that has been resolved for the current font
3472 and fontSizeMode.
3473*/
3474QJSValue QQuickText::fontInfo() const
3475{
3476 Q_D(const QQuickText);
3477
3478 QJSEngine *engine = qjsEngine(this);
3479 if (!engine) {
3480 qmlWarning(this) << "fontInfo: item has no JS engine";
3481 return QJSValue();
3482 }
3483
3484 QJSValue value = engine->newObject();
3485 value.setProperty(QStringLiteral("family"), d->fontInfo.family());
3486 value.setProperty(QStringLiteral("styleName"), d->fontInfo.styleName());
3487 value.setProperty(QStringLiteral("bold"), d->fontInfo.bold());
3488 value.setProperty(QStringLiteral("weight"), d->fontInfo.weight());
3489 value.setProperty(QStringLiteral("italic"), d->fontInfo.italic());
3490 value.setProperty(QStringLiteral("pointSize"), d->fontInfo.pointSizeF());
3491 value.setProperty(QStringLiteral("pixelSize"), d->fontInfo.pixelSize());
3492 return value;
3493}
3494
3495/*!
3496 \qmlproperty size QtQuick::Text::advance
3497 \since 5.10
3498
3499 The distance, in pixels, from the baseline origin of the first
3500 character of the text item, to the baseline origin of the first
3501 character in a text item occurring directly after this one
3502 in a text flow.
3503
3504 Note that the advance can be negative if the text flows from
3505 right to left.
3506*/
3507QSizeF QQuickText::advance() const
3508{
3509 Q_D(const QQuickText);
3510 return d->advance;
3511}
3512
3513QT_END_NAMESPACE
3514
3515#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)