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