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