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