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