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(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, QVector<QTextLayout::FormatRange> *elidedFormats)
721{
722 const int end = start + length;
723 const QVector<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 QVector<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 that the \l{Supported HTML Subset} is limited. Also, if the text
1457 contains HTML img tags that load remote images, the text is reloaded.
1458
1459 Text provides read-only text. For editable text, see \l TextEdit.
1460
1461 \sa {Qt Quick Examples - Text#Fonts}{Fonts example}
1462*/
1463QQuickText::QQuickText(QQuickItem *parent)
1464: QQuickImplicitSizeItem(*(new QQuickTextPrivate), parent)
1465{
1466 Q_D(QQuickText);
1467 d->init();
1468}
1469
1470QQuickText::QQuickText(QQuickTextPrivate &dd, QQuickItem *parent)
1471: QQuickImplicitSizeItem(dd, parent)
1472{
1473 Q_D(QQuickText);
1474 d->init();
1475}
1476
1477QQuickText::~QQuickText()
1478{
1479 Q_D(QQuickText);
1480 if (d->extra.isAllocated()) {
1481 qDeleteAll(d->extra->pixmapsInProgress);
1482 d->extra->pixmapsInProgress.clear();
1483 }
1484}
1485
1486/*!
1487 \qmlproperty bool QtQuick::Text::clip
1488 This property holds whether the text is clipped.
1489
1490 Note that if the text does not fit in the bounding rectangle, it will be abruptly chopped.
1491
1492 If you want to display potentially long text in a limited space, you probably want to use \c elide instead.
1493*/
1494
1495/*!
1496 \qmlsignal QtQuick::Text::lineLaidOut(object line)
1497
1498 This signal is emitted for each line of text that is laid out during the layout
1499 process in plain text or styled text mode. It is not emitted in rich text mode.
1500 The specified \a line object provides more details about the line that
1501 is currently being laid out.
1502
1503 This gives the opportunity to position and resize a line as it is being laid out.
1504 It can for example be used to create columns or lay out text around objects.
1505
1506 The properties of the specified \a line object are:
1507
1508 \table
1509 \header
1510 \li Property name
1511 \li Description
1512 \row
1513 \li number (read-only)
1514 \li Line number, starts with zero.
1515 \row
1516 \li x
1517 \li Specifies the line's x position inside the \c Text element.
1518 \row
1519 \li y
1520 \li Specifies the line's y position inside the \c Text element.
1521 \row
1522 \li width
1523 \li Specifies the width of the line.
1524 \row
1525 \li height
1526 \li Specifies the height of the line.
1527 \row
1528 \li implicitWidth (read-only)
1529 \li The width that the line would naturally occupy based on its contents,
1530 not taking into account any modifications made to \e width.
1531 \row
1532 \li isLast (read-only)
1533 \li Whether the line is the last. This property can change if you
1534 set the \e width property to a different value.
1535 \endtable
1536
1537 For example, this will move the first 5 lines of a Text item by 100 pixels to the right:
1538 \code
1539 onLineLaidOut: (line)=> {
1540 if (line.number < 5) {
1541 line.x = line.x + 100
1542 line.width = line.width - 100
1543 }
1544 }
1545 \endcode
1546
1547 The following example will allow you to position an item at the end of the last line:
1548 \code
1549 onLineLaidOut: (line)=> {
1550 if (line.isLast) {
1551 lastLineMarker.x = line.x + line.implicitWidth
1552 lastLineMarker.y = line.y + (line.height - lastLineMarker.height) / 2
1553 }
1554 }
1555 \endcode
1556*/
1557
1558/*!
1559 \qmlsignal QtQuick::Text::linkActivated(string link)
1560
1561 This signal is emitted when the user clicks on a link embedded in the text.
1562 The link must be in rich text or HTML format and the
1563 \a link string provides access to the particular link.
1564
1565 \snippet qml/text/onLinkActivated.qml 0
1566
1567 The example code will display the text
1568 "See the \l{http://qt-project.org}{Qt Project website}."
1569
1570 Clicking on the highlighted link will output
1571 \tt{http://qt-project.org link activated} to the console.
1572*/
1573
1574/*!
1575 \qmlproperty string QtQuick::Text::font.family
1576
1577 Sets the family name of the font.
1578
1579 The family name is case insensitive and may optionally include a foundry
1580 name, for example "Helvetica [Cronyx]".
1581 If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen.
1582 If the family isn't available a family will be set using the font matching algorithm.
1583*/
1584
1585/*!
1586 \qmlproperty string QtQuick::Text::font.styleName
1587 \since 5.6
1588
1589 Sets the style name of the font.
1590
1591 The style name is case insensitive. If set, the font will be matched against style name instead
1592 of the font properties \l font.weight, \l font.bold and \l font.italic.
1593*/
1594
1595/*!
1596 \qmlproperty bool QtQuick::Text::font.bold
1597
1598 Sets whether the font weight is bold.
1599*/
1600
1601/*!
1602 \qmlproperty int QtQuick::Text::font.weight
1603
1604 The requested weight of the font. The weight requested must be an integer
1605 between 1 and 1000, or one of the predefined values:
1606
1607 \value Font.Thin 100
1608 \value Font.ExtraLight 200
1609 \value Font.Light 300
1610 \value Font.Normal 400 (default)
1611 \value Font.Medium 500
1612 \value Font.DemiBold 600
1613 \value Font.Bold 700
1614 \value Font.ExtraBold 800
1615 \value Font.Black 900
1616
1617 \qml
1618 Text { text: "Hello"; font.weight: Font.DemiBold }
1619 \endqml
1620*/
1621
1622/*!
1623 \qmlproperty bool QtQuick::Text::font.italic
1624
1625 Sets whether the font has an italic style.
1626*/
1627
1628/*!
1629 \qmlproperty bool QtQuick::Text::font.underline
1630
1631 Sets whether the text is underlined.
1632*/
1633
1634/*!
1635 \qmlproperty bool QtQuick::Text::font.strikeout
1636
1637 Sets whether the font has a strikeout style.
1638*/
1639
1640/*!
1641 \qmlproperty real QtQuick::Text::font.pointSize
1642
1643 Sets the font size in points. The point size must be greater than zero.
1644*/
1645
1646/*!
1647 \qmlproperty int QtQuick::Text::font.pixelSize
1648
1649 Sets the font size in pixels.
1650
1651 Using this function makes the font device dependent.
1652 Use \c pointSize to set the size of the font in a device independent manner.
1653*/
1654
1655/*!
1656 \qmlproperty real QtQuick::Text::font.letterSpacing
1657
1658 Sets the letter spacing for the font.
1659
1660 Letter spacing changes the default spacing between individual letters in the font.
1661 A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing.
1662*/
1663
1664/*!
1665 \qmlproperty real QtQuick::Text::font.wordSpacing
1666
1667 Sets the word spacing for the font.
1668
1669 Word spacing changes the default spacing between individual words.
1670 A positive value increases the word spacing by a corresponding amount of pixels,
1671 while a negative value decreases the inter-word spacing accordingly.
1672*/
1673
1674/*!
1675 \qmlproperty enumeration QtQuick::Text::font.capitalization
1676
1677 Sets the capitalization for the text.
1678
1679 \value Font.MixedCase the normal case: no capitalization change is applied
1680 \value Font.AllUppercase alters the text to be rendered in all uppercase type
1681 \value Font.AllLowercase alters the text to be rendered in all lowercase type
1682 \value Font.SmallCaps alters the text to be rendered in small-caps type
1683 \value Font.Capitalize alters the text to be rendered with the first character of
1684 each word as an uppercase character
1685
1686 \qml
1687 Text { text: "Hello"; font.capitalization: Font.AllLowercase }
1688 \endqml
1689*/
1690
1691/*!
1692 \qmlproperty enumeration QtQuick::Text::font.hintingPreference
1693 \since 5.8
1694
1695 Sets the preferred hinting on the text. This is a hint to the underlying text rendering system
1696 to use a certain level of hinting, and has varying support across platforms. See the table in
1697 the documentation for QFont::HintingPreference for more details.
1698
1699 \note This property only has an effect when used together with render type Text.NativeRendering.
1700
1701 \value Font.PreferDefaultHinting Use the default hinting level for the target platform.
1702 \value Font.PreferNoHinting If possible, render text without hinting the outlines
1703 of the glyphs. The text layout will be typographically accurate, using the same metrics
1704 as are used, for example, when printing.
1705 \value Font.PreferVerticalHinting If possible, render text with no horizontal hinting,
1706 but align glyphs to the pixel grid in the vertical direction. The text will appear
1707 crisper on displays where the density is too low to give an accurate rendering
1708 of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's
1709 layout will be scalable to higher density devices (such as printers) without impacting
1710 details such as line breaks.
1711 \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and
1712 vertical directions. The text will be altered to optimize legibility on the target
1713 device, but since the metrics will depend on the target size of the text, the positions
1714 of glyphs, line breaks, and other typographical detail will not scale, meaning that a
1715 text layout may look different on devices with different pixel densities.
1716
1717 \qml
1718 Text { text: "Hello"; renderType: Text.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting }
1719 \endqml
1720*/
1721
1722/*!
1723 \qmlproperty bool QtQuick::Text::font.kerning
1724 \since 5.10
1725
1726 Enables or disables the kerning OpenType feature when shaping the text. Disabling this may
1727 improve performance when creating or changing the text, at the expense of some cosmetic
1728 features. The default value is true.
1729
1730 \qml
1731 Text { text: "OATS FLAVOUR WAY"; font.kerning: false }
1732 \endqml
1733*/
1734
1735/*!
1736 \qmlproperty bool QtQuick::Text::font.preferShaping
1737 \since 5.10
1738
1739 Sometimes, a font will apply complex rules to a set of characters in order to
1740 display them correctly. In some writing systems, such as Brahmic scripts, this is
1741 required in order for the text to be legible, but in for example Latin script, it is merely
1742 a cosmetic feature. Setting the \c preferShaping property to false will disable all
1743 such features when they are not required, which will improve performance in most cases.
1744
1745 The default value is true.
1746
1747 \qml
1748 Text { text: "Some text"; font.preferShaping: false }
1749 \endqml
1750*/
1751
1752/*!
1753 \qmlproperty object QtQuick::Text::font.variableAxes
1754 \since 6.7
1755
1756//! [qml-font-variable-axes]
1757 Applies floating point values to variable axes in variable fonts.
1758
1759 Variable fonts provide a way to store multiple variations (with different weights, widths
1760 or styles) in the same font file. The variations are given as floating point values for
1761 a pre-defined set of parameters, called "variable axes". Specific instances are typically
1762 given names by the font designer, and, in Qt, these can be selected using setStyleName()
1763 just like traditional sub-families.
1764
1765 In some cases, it is also useful to provide arbitrary values for the different axes. For
1766 instance, if a font has a Regular and Bold sub-family, you may want a weight in-between these.
1767 You could then manually request this by supplying a custom value for the "wght" axis in the
1768 font.
1769
1770 \qml
1771 Text {
1772 text: "Foobar"
1773 font.family: "MyVariableFont"
1774 font.variableAxes: { "wght": (Font.Normal + Font.Bold) / 2.0 }
1775 }
1776 \endqml
1777
1778 If the "wght" axis is supported by the font and the given value is within its defined range,
1779 a font corresponding to the weight 550.0 will be provided.
1780
1781 There are a few standard axes than many fonts provide, such as "wght" (weight), "wdth" (width),
1782 "ital" (italic) and "opsz" (optical size). They each have indivdual ranges defined in the font
1783 itself. For instance, "wght" may span from 100 to 900 (QFont::Thin to QFont::Black) whereas
1784 "ital" can span from 0 to 1 (from not italic to fully italic).
1785
1786 A font may also choose to define custom axes; the only limitation is that the name has to
1787 meet the requirements for a QFont::Tag (sequence of four latin-1 characters.)
1788
1789 By default, no variable axes are set.
1790
1791 \note On Windows, variable axes are not supported if the optional GDI font backend is in use.
1792
1793 \sa QFont::setVariableAxis()
1794//! [qml-font-variable-axes]
1795*/
1796
1797
1798/*!
1799 \qmlproperty object QtQuick::Text::font.features
1800 \since 6.6
1801
1802//! [qml-font-features]
1803 Applies integer values to specific OpenType features when shaping the text based on the contents
1804 in \a features. This provides advanced access to the font shaping process, and can be used
1805 to support font features that are otherwise not covered in the API.
1806
1807 The font features are represented by a map from four-letter tags to integer values. This integer
1808 value passed along with the tag in most cases represents a boolean value: A zero value means the
1809 feature is disabled, and a non-zero value means it is enabled. For certain font features,
1810 however, it may have other interpretations. For example, when applied to the \c salt feature, the
1811 value is an index that specifies the stylistic alternative to use.
1812
1813 For example, the \c frac font feature will convert diagonal fractions separated with a slash
1814 (such as \c 1/2) with a different representation. Typically this will involve baking the full
1815 fraction into a single character width (such as \c ½).
1816
1817 If a font supports the \c frac feature, then it can be enabled in the shaper as in the following
1818 code:
1819
1820 \qml
1821 Text {
1822 text: "One divided by two is 1/2"
1823 font.family: "MyFractionFont"
1824 font.features: { "frac": 1 }
1825 }
1826 \endqml
1827
1828 Multiple features can be assigned values in the same mapping. For instance,
1829 if you would like to also disable kerning for the font, you can explicitly
1830 disable this as follows:
1831
1832 \qml
1833 Text {
1834 text: "One divided by two is 1/2"
1835 font.family: "MyFractionFont"
1836 font.features: { "frac": 1, "kern": 0 }
1837 }
1838 \endqml
1839
1840 You can also collect the font properties in an object:
1841
1842 \qml
1843 Text {
1844 text: "One divided by two is 1/2"
1845 font: {
1846 family: "MyFractionFont"
1847 features: { "frac": 1, "kern": 0 }
1848 }
1849 }
1850 \endqml
1851
1852 \note By default, Qt will enable and disable certain font features based on other font
1853 properties. In particular, the \c kern feature will be enabled/disabled depending on the
1854 \l font.kerning property of the QFont. In addition, all ligature features (\c liga, \c clig,
1855 \c dlig, \c hlig) will be disabled if a \l font.letterSpacing is set, but only for writing
1856 systems where the use of ligature is cosmetic. For writing systems where ligatures are required,
1857 the features will remain in their default state. The values set using \c font.features will
1858 override the default behavior. If, for instance, \c{"kern"} is set to 1, then kerning will
1859 always be enabled, regardless of whether the \l font.kerning property is set to false. Similarly,
1860 if it is set to \c 0, it will always be disabled.
1861
1862 \sa QFont::setFeature()
1863//! [qml-font-features]
1864*/
1865
1866/*!
1867 \qmlproperty bool QtQuick::Text::font.contextFontMerging
1868 \since 6.8
1869
1870//! [qml-font-context-font-merging]
1871 If the selected font does not contain a certain character, Qt automatically chooses a
1872 similar-looking fallback font that contains the character. By default this is done on a
1873 character-by-character basis.
1874
1875 This means that in certain uncommon cases, many different fonts may be used to represent one
1876 string of text even if it's in the same script. Setting \c contextFontMerging to true will try
1877 finding the fallback font that matches the largest subset of the input string instead. This
1878 will be more expensive for strings where missing glyphs occur, but may give more consistent
1879 results. By default, \c contextFontMerging is \c{false}.
1880
1881 \sa QFont::StyleStrategy
1882//! [qml-font-context-font-merging]
1883*/
1884
1885/*!
1886 \qmlproperty bool QtQuick::Text::font.preferTypoLineMetrics
1887 \since 6.8
1888
1889//! [qml-font-prefer-typo-line-metrics] For compatibility reasons, OpenType fonts contain two
1890 competing sets of the vertical line metrics that provide the \l{QFontMetricsF::ascent()}{ascent},
1891 \l{QFontMetricsF::descent()}{descent} and \l{QFontMetricsF::leading()}{leading} of the font. These
1892 are often referred to as the
1893 \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#uswinascent}{win} (Windows)
1894 metrics and the \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#sta}{typo}
1895 (typographical) metrics. While the specification recommends using the \c typo metrics for line
1896 spacing, many applications prefer the \c win metrics unless the \c{USE_TYPO_METRICS} flag is set in
1897 the \l{https://learn.microsoft.com/en-us/typography/opentype/spec/os2#fsselection}{fsSelection}
1898 field of the font. For backwards-compatibility reasons, this is also the case for Qt applications.
1899 This is not an issue for fonts that set the \c{USE_TYPO_METRICS} flag to indicate that the \c{typo}
1900 metrics are valid, nor for fonts where the \c{win} metrics and \c{typo} metrics match up. However,
1901 for certain fonts the \c{win} metrics may be larger than the preferable line spacing and the
1902 \c{USE_TYPO_METRICS} flag may be unset by mistake. For such fonts, setting
1903 \c{font.preferTypoLineMetrics} may give superior results.
1904
1905 By default, \c preferTypoLineMetrics is \c{false}.
1906
1907 \sa QFont::StyleStrategy
1908//! [qml-font-prefer-typo-line-metrics]
1909*/
1910
1911
1912QFont QQuickText::font() const
1913{
1914 Q_D(const QQuickText);
1915 return d->sourceFont;
1916}
1917
1918void QQuickText::setFont(const QFont &font)
1919{
1920 Q_D(QQuickText);
1921 if (d->sourceFont == font)
1922 return;
1923
1924 d->sourceFont = font;
1925 QFont oldFont = d->font;
1926 d->font = font;
1927
1928 if (!antialiasing())
1929 d->font.setStyleStrategy(QFont::NoAntialias);
1930
1931 if (d->font.pointSizeF() != -1) {
1932 // 0.5pt resolution
1933 qreal size = qRound(d->font.pointSizeF()*2.0);
1934 d->font.setPointSizeF(size/2.0);
1935 }
1936
1937 if (oldFont != d->font) {
1938 // if the format changes the size of the text
1939 // with headings or <font> tag, we need to re-parse
1940 if (d->formatModifiesFontSize)
1941 d->textHasChanged = true;
1942 d->implicitWidthValid = false;
1943 d->implicitHeightValid = false;
1944 d->updateLayout();
1945 }
1946
1947 emit fontChanged(d->sourceFont);
1948}
1949
1950void QQuickText::itemChange(ItemChange change, const ItemChangeData &value)
1951{
1952 Q_D(QQuickText);
1953 Q_UNUSED(value);
1954 switch (change) {
1955 case ItemAntialiasingHasChanged:
1956 if (!antialiasing())
1957 d->font.setStyleStrategy(QFont::NoAntialias);
1958 else
1959 d->font.setStyleStrategy(QFont::PreferAntialias);
1960 d->implicitWidthValid = false;
1961 d->implicitHeightValid = false;
1962 d->updateLayout();
1963 break;
1964
1965 case ItemDevicePixelRatioHasChanged:
1966 {
1967 bool needUpdateLayout = false;
1968 if (d->containsUnscalableGlyphs) {
1969 // Native rendering optimizes for a given pixel grid, so its results must not be scaled.
1970 // Text layout code respects the current device pixel ratio automatically, we only need
1971 // to rerun layout after the ratio changed.
1972 // Changes of implicit size should be minimal; they are hard to avoid.
1973 d->implicitWidthValid = false;
1974 d->implicitHeightValid = false;
1975 needUpdateLayout = true;
1976 }
1977
1978 if (d->extra.isAllocated()) {
1979 // check if we have scalable inline images with explicit size set, which should be reloaded
1980 for (QQuickStyledTextImgTag *image : std::as_const(d->extra->visibleImgTags)) {
1981 if (image->size.isValid() && QQuickPixmap::isScalableImageFormat(image->url)) {
1982 image->pix.reset();
1983 needUpdateLayout = true;
1984 }
1985 }
1986 }
1987
1988 if (needUpdateLayout)
1989 d->updateLayout();
1990 }
1991 break;
1992
1993 default:
1994 break;
1995 }
1996 QQuickItem::itemChange(change, value);
1997}
1998
1999/*!
2000 \qmlproperty string QtQuick::Text::text
2001
2002 The text to display. Text supports both plain and rich text strings.
2003
2004 The item will try to automatically determine whether the text should
2005 be treated as styled text. This determination is made using Qt::mightBeRichText().
2006 However, detection of Markdown is not automatic.
2007
2008 \sa textFormat
2009*/
2010QString QQuickText::text() const
2011{
2012 Q_D(const QQuickText);
2013 return d->text;
2014}
2015
2016void QQuickText::setText(const QString &n)
2017{
2018 Q_D(QQuickText);
2019 if (d->text == n)
2020 return;
2021
2022 d->markdownText = d->format == MarkdownText;
2023 d->richText = d->format == RichText || d->markdownText;
2024 d->styledText = d->format == StyledText || (d->format == AutoText && Qt::mightBeRichText(n));
2025 d->text = n;
2026 if (isComponentComplete()) {
2027 if (d->richText) {
2028 d->updateDocumentText();
2029 } else {
2030 d->clearFormats();
2031 d->rightToLeftText = d->text.isRightToLeft();
2032 }
2033 d->determineHorizontalAlignment();
2034 }
2035 d->textHasChanged = true;
2036 d->implicitWidthValid = false;
2037 d->implicitHeightValid = false;
2038
2039 if (d->extra.isAllocated()) {
2040 qDeleteAll(d->extra->imgTags);
2041 d->extra->imgTags.clear();
2042 }
2043 setFlag(QQuickItem::ItemObservesViewport, n.size() > QQuickTextPrivate::largeTextSizeThreshold);
2044 d->updateLayout();
2045 setAcceptHoverEvents(d->richText || d->styledText);
2046 emit textChanged(d->text);
2047}
2048
2049/*!
2050 \qmlproperty color QtQuick::Text::color
2051
2052 The text color.
2053
2054 An example of green text defined using hexadecimal notation:
2055 \qml
2056 Text {
2057 color: "#00FF00"
2058 text: "green text"
2059 }
2060 \endqml
2061
2062 An example of steel blue text defined using an SVG color name:
2063 \qml
2064 Text {
2065 color: "steelblue"
2066 text: "blue text"
2067 }
2068 \endqml
2069*/
2070QColor QQuickText::color() const
2071{
2072 Q_D(const QQuickText);
2073 return QColor::fromRgba(d->color);
2074}
2075
2076void QQuickText::setColor(const QColor &color)
2077{
2078 Q_D(QQuickText);
2079 QRgb rgb = color.rgba();
2080 if (d->color == rgb)
2081 return;
2082
2083 d->color = rgb;
2084 if (isComponentComplete()) {
2085 d->updateType = QQuickTextPrivate::UpdatePaintNode;
2086 update();
2087 }
2088 emit colorChanged();
2089}
2090
2091/*!
2092 \qmlproperty color QtQuick::Text::linkColor
2093
2094 The color of links in the text.
2095
2096 This property works with the StyledText \l textFormat, but not with RichText.
2097 Link color in RichText can be specified by including CSS style tags in the
2098 text.
2099*/
2100
2101QColor QQuickText::linkColor() const
2102{
2103 Q_D(const QQuickText);
2104 return QColor::fromRgba(d->linkColor);
2105}
2106
2107void QQuickText::setLinkColor(const QColor &color)
2108{
2109 Q_D(QQuickText);
2110 QRgb rgb = color.rgba();
2111 if (d->linkColor == rgb)
2112 return;
2113
2114 d->linkColor = rgb;
2115 if (isComponentComplete()) {
2116 d->updateType = QQuickTextPrivate::UpdatePaintNode;
2117 update();
2118 }
2119 emit linkColorChanged();
2120}
2121
2122/*!
2123 \qmlproperty enumeration QtQuick::Text::style
2124
2125 Set an additional text style.
2126
2127 Supported text styles are:
2128
2129 \value Text.Normal - the default
2130 \value Text.Outline
2131 \value Text.Raised
2132 \value Text.Sunken
2133
2134 \qml
2135 Row {
2136 Text { font.pointSize: 24; text: "Normal" }
2137 Text { font.pointSize: 24; text: "Raised"; style: Text.Raised; styleColor: "#AAAAAA" }
2138 Text { font.pointSize: 24; text: "Outline";style: Text.Outline; styleColor: "red" }
2139 Text { font.pointSize: 24; text: "Sunken"; style: Text.Sunken; styleColor: "#AAAAAA" }
2140 }
2141 \endqml
2142
2143 \image declarative-textstyle.png
2144*/
2145QQuickText::TextStyle QQuickText::style() const
2146{
2147 Q_D(const QQuickText);
2148 return d->style;
2149}
2150
2151void QQuickText::setStyle(QQuickText::TextStyle style)
2152{
2153 Q_D(QQuickText);
2154 if (d->style == style)
2155 return;
2156
2157 d->style = style;
2158 if (isComponentComplete()) {
2159 d->updateType = QQuickTextPrivate::UpdatePaintNode;
2160 update();
2161 }
2162 emit styleChanged(d->style);
2163}
2164
2165/*!
2166 \qmlproperty color QtQuick::Text::styleColor
2167
2168 Defines the secondary color used by text styles.
2169
2170 \c styleColor is used as the outline color for outlined text, and as the
2171 shadow color for raised or sunken text. If no style has been set, it is not
2172 used at all.
2173
2174 \qml
2175 Text { font.pointSize: 18; text: "hello"; style: Text.Raised; styleColor: "gray" }
2176 \endqml
2177
2178 \sa style
2179 */
2180QColor QQuickText::styleColor() const
2181{
2182 Q_D(const QQuickText);
2183 return QColor::fromRgba(d->styleColor);
2184}
2185
2186void QQuickText::setStyleColor(const QColor &color)
2187{
2188 Q_D(QQuickText);
2189 QRgb rgb = color.rgba();
2190 if (d->styleColor == rgb)
2191 return;
2192
2193 d->styleColor = rgb;
2194 if (isComponentComplete()) {
2195 d->updateType = QQuickTextPrivate::UpdatePaintNode;
2196 update();
2197 }
2198 emit styleColorChanged();
2199}
2200
2201/*!
2202 \qmlproperty enumeration QtQuick::Text::horizontalAlignment
2203 \qmlproperty enumeration QtQuick::Text::verticalAlignment
2204 \qmlproperty enumeration QtQuick::Text::effectiveHorizontalAlignment
2205
2206 Sets the horizontal and vertical alignment of the text within the Text items
2207 width and height. By default, the text is vertically aligned to the top. Horizontal
2208 alignment follows the natural alignment of the text, for example text that is read
2209 from left to right will be aligned to the left.
2210
2211 The valid values for \c horizontalAlignment are \c Text.AlignLeft, \c Text.AlignRight, \c Text.AlignHCenter and
2212 \c Text.AlignJustify. The valid values for \c verticalAlignment are \c Text.AlignTop, \c Text.AlignBottom
2213 and \c Text.AlignVCenter.
2214
2215 Note that for a single line of text, the size of the text is the area of the text. In this common case,
2216 all alignments are equivalent. If you want the text to be, say, centered in its parent, then you will
2217 need to either modify the Item::anchors, or set horizontalAlignment to Text.AlignHCenter and bind the width to
2218 that of the parent.
2219
2220 When using the attached property LayoutMirroring::enabled to mirror application
2221 layouts, the horizontal alignment of text will also be mirrored. However, the property
2222 \c horizontalAlignment will remain unchanged. To query the effective horizontal alignment
2223 of Text, use the read-only property \c effectiveHorizontalAlignment.
2224*/
2225QQuickText::HAlignment QQuickText::hAlign() const
2226{
2227 Q_D(const QQuickText);
2228 return d->hAlign;
2229}
2230
2231void QQuickText::setHAlign(HAlignment align)
2232{
2233 Q_D(QQuickText);
2234 bool forceAlign = d->hAlignImplicit && d->effectiveLayoutMirror;
2235 d->hAlignImplicit = false;
2236 if (d->setHAlign(align, forceAlign) && isComponentComplete())
2237 d->updateLayout();
2238}
2239
2240void QQuickText::resetHAlign()
2241{
2242 Q_D(QQuickText);
2243 d->hAlignImplicit = true;
2244 if (isComponentComplete() && d->determineHorizontalAlignment())
2245 d->updateLayout();
2246}
2247
2248QQuickText::HAlignment QQuickText::effectiveHAlign() const
2249{
2250 Q_D(const QQuickText);
2251 QQuickText::HAlignment effectiveAlignment = d->hAlign;
2252 if (!d->hAlignImplicit && d->effectiveLayoutMirror) {
2253 switch (d->hAlign) {
2254 case QQuickText::AlignLeft:
2255 effectiveAlignment = QQuickText::AlignRight;
2256 break;
2257 case QQuickText::AlignRight:
2258 effectiveAlignment = QQuickText::AlignLeft;
2259 break;
2260 default:
2261 break;
2262 }
2263 }
2264 return effectiveAlignment;
2265}
2266
2267bool QQuickTextPrivate::setHAlign(QQuickText::HAlignment alignment, bool forceAlign)
2268{
2269 Q_Q(QQuickText);
2270 if (hAlign != alignment || forceAlign) {
2271 QQuickText::HAlignment oldEffectiveHAlign = q->effectiveHAlign();
2272 hAlign = alignment;
2273
2274 emit q->horizontalAlignmentChanged(hAlign);
2275 if (oldEffectiveHAlign != q->effectiveHAlign())
2276 emit q->effectiveHorizontalAlignmentChanged();
2277 return true;
2278 }
2279 return false;
2280}
2281
2282bool QQuickTextPrivate::determineHorizontalAlignment()
2283{
2284 if (hAlignImplicit) {
2285#if QT_CONFIG(im)
2286 bool alignToRight = text.isEmpty() ? QGuiApplication::inputMethod()->inputDirection() == Qt::RightToLeft : rightToLeftText;
2287#else
2288 bool alignToRight = rightToLeftText;
2289#endif
2290 return setHAlign(alignToRight ? QQuickText::AlignRight : QQuickText::AlignLeft);
2291 }
2292 return false;
2293}
2294
2295void QQuickTextPrivate::mirrorChange()
2296{
2297 Q_Q(QQuickText);
2298 if (q->isComponentComplete()) {
2299 if (!hAlignImplicit && (hAlign == QQuickText::AlignRight || hAlign == QQuickText::AlignLeft)) {
2300 updateLayout();
2301 emit q->effectiveHorizontalAlignmentChanged();
2302 }
2303 }
2304}
2305
2306QQuickText::VAlignment QQuickText::vAlign() const
2307{
2308 Q_D(const QQuickText);
2309 return d->vAlign;
2310}
2311
2312void QQuickText::setVAlign(VAlignment align)
2313{
2314 Q_D(QQuickText);
2315 if (d->vAlign == align)
2316 return;
2317
2318 d->vAlign = align;
2319
2320 if (isComponentComplete())
2321 d->updateLayout();
2322
2323 emit verticalAlignmentChanged(align);
2324}
2325
2326/*!
2327 \qmlproperty enumeration QtQuick::Text::wrapMode
2328
2329 Set this property to wrap the text to the Text item's width. The text will only
2330 wrap if an explicit width has been set. wrapMode can be one of:
2331
2332 \value Text.NoWrap
2333 (default) no wrapping will be performed. If the text contains
2334 insufficient newlines, then \l contentWidth will exceed a set width.
2335 \value Text.WordWrap
2336 wrapping is done on word boundaries only. If a word is too long,
2337 \l contentWidth will exceed a set width.
2338 \value Text.WrapAnywhere
2339 wrapping is done at any point on a line, even if it occurs in the middle of a word.
2340 \value Text.Wrap
2341 if possible, wrapping occurs at a word boundary; otherwise it will occur
2342 at the appropriate point on the line, even in the middle of a word.
2343*/
2344QQuickText::WrapMode QQuickText::wrapMode() const
2345{
2346 Q_D(const QQuickText);
2347 return d->wrapMode;
2348}
2349
2350void QQuickText::setWrapMode(WrapMode mode)
2351{
2352 Q_D(QQuickText);
2353 if (mode == d->wrapMode)
2354 return;
2355
2356 d->wrapMode = mode;
2357 d->updateLayout();
2358
2359 emit wrapModeChanged();
2360}
2361
2362/*!
2363 \qmlproperty int QtQuick::Text::lineCount
2364
2365 Returns the number of lines visible in the text item.
2366
2367 This property is not supported for rich text.
2368
2369 \sa maximumLineCount
2370*/
2371int QQuickText::lineCount() const
2372{
2373 Q_D(const QQuickText);
2374 return d->lineCount;
2375}
2376
2377/*!
2378 \qmlproperty bool QtQuick::Text::truncated
2379
2380 Returns true if the text has been truncated due to \l maximumLineCount
2381 or \l elide.
2382
2383 This property is not supported for rich text.
2384
2385 \sa maximumLineCount, elide
2386*/
2387bool QQuickText::truncated() const
2388{
2389 Q_D(const QQuickText);
2390 return d->truncated;
2391}
2392
2393/*!
2394 \qmlproperty int QtQuick::Text::maximumLineCount
2395
2396 Set this property to limit the number of lines that the text item will show.
2397 If elide is set to Text.ElideRight, the text will be elided appropriately.
2398 By default, this is the value of the largest possible integer.
2399
2400 This property is not supported for rich text.
2401
2402 \sa lineCount, elide
2403*/
2404int QQuickText::maximumLineCount() const
2405{
2406 Q_D(const QQuickText);
2407 return d->maximumLineCount();
2408}
2409
2410void QQuickText::setMaximumLineCount(int lines)
2411{
2412 Q_D(QQuickText);
2413
2414 d->maximumLineCountValid = lines==INT_MAX ? false : true;
2415 if (d->maximumLineCount() != lines) {
2416 d->extra.value().maximumLineCount = lines;
2417 d->implicitHeightValid = false;
2418 d->updateLayout();
2419 emit maximumLineCountChanged();
2420 }
2421}
2422
2423void QQuickText::resetMaximumLineCount()
2424{
2425 Q_D(QQuickText);
2426 setMaximumLineCount(INT_MAX);
2427 if (d->truncated != false) {
2428 d->truncated = false;
2429 emit truncatedChanged();
2430 }
2431}
2432
2433/*!
2434 \qmlproperty enumeration QtQuick::Text::textFormat
2435
2436 The way the \l text property should be displayed.
2437
2438 Supported text formats are:
2439
2440 \value Text.AutoText (default) detected via the Qt::mightBeRichText() heuristic
2441 \value Text.PlainText all styling tags are treated as plain text
2442 \value Text.StyledText optimized basic rich text as in HTML 3.2
2443 \value Text.RichText \l {Supported HTML Subset} {a subset of HTML 4}
2444 \value Text.MarkdownText \l {https://commonmark.org/help/}{CommonMark} plus the
2445 \l {https://guides.github.com/features/mastering-markdown/}{GitHub}
2446 extensions for tables and task lists (since 5.14)
2447
2448 If the text format is \c Text.AutoText, the Text item
2449 will automatically determine whether the text should be treated as
2450 styled text. This determination is made using Qt::mightBeRichText(),
2451 which can detect the presence of an HTML tag on the first line of text,
2452 but cannot distinguish Markdown from plain text.
2453
2454 \c Text.StyledText is an optimized format supporting some basic text
2455 styling markup, in the style of HTML 3.2:
2456
2457 \code
2458 <b></b> - bold
2459 <del></del> - strike out (removed content)
2460 <s></s> - strike out (no longer accurate or no longer relevant content)
2461 <strong></strong> - bold
2462 <i></i> - italic
2463 <br> - new line
2464 <p> - paragraph
2465 <u> - underlined text
2466 <font color="color_name" size="1-7"></font>
2467 <h1> to <h6> - headers
2468 <a href=""> - anchor
2469 <img src="" align="top,middle,bottom" width="" height=""> - inline images
2470 <ol type="">, <ul type=""> and <li> - ordered and unordered lists
2471 <pre></pre> - preformatted
2472 All entities
2473 \endcode
2474
2475 \c Text.StyledText parser is strict, requiring tags to be correctly nested.
2476
2477 \table
2478 \row
2479 \li
2480 \snippet qml/text/textFormats.qml 0
2481 \li \image declarative-textformat.png
2482 \endtable
2483
2484 \c Text.RichText supports a larger subset of HTML 4, as described on the
2485 \l {Supported HTML Subset} page. You should prefer using \c Text.PlainText,
2486 \c Text.StyledText or \c Text.MarkdownText instead, as they offer better performance.
2487
2488 \note With \c Text.MarkdownText, and with the supported subset of HTML,
2489 some decorative elements are not rendered as they would be in a web browser:
2490 \list
2491 \li code blocks use the \l {QFontDatabase::FixedFont}{default monospace font} but without a surrounding highlight box
2492 \li block quotes are indented, but there is no vertical line alongside the quote
2493 \endlist
2494*/
2495QQuickText::TextFormat QQuickText::textFormat() const
2496{
2497 Q_D(const QQuickText);
2498 return d->format;
2499}
2500
2501void QQuickText::setTextFormat(TextFormat format)
2502{
2503 Q_D(QQuickText);
2504 if (format == d->format)
2505 return;
2506 d->format = format;
2507 bool wasRich = d->richText;
2508 d->markdownText = format == MarkdownText;
2509 d->richText = format == RichText || d->markdownText;
2510 d->styledText = format == StyledText || (format == AutoText && Qt::mightBeRichText(d->text));
2511
2512 if (isComponentComplete()) {
2513 if (!wasRich && d->richText) {
2514 d->updateDocumentText();
2515 } else {
2516 d->clearFormats();
2517 d->rightToLeftText = d->text.isRightToLeft();
2518 d->textHasChanged = true;
2519 }
2520 d->determineHorizontalAlignment();
2521 }
2522 d->updateLayout();
2523 setAcceptHoverEvents(d->richText || d->styledText);
2524 setAcceptedMouseButtons(d->richText || d->styledText ? Qt::LeftButton : Qt::NoButton);
2525
2526 emit textFormatChanged(d->format);
2527}
2528
2529/*!
2530 \qmlproperty enumeration QtQuick::Text::elide
2531
2532 Set this property to elide parts of the text fit to the Text item's width.
2533 The text will only elide if an explicit width has been set.
2534
2535 This property cannot be used with rich text.
2536
2537 Eliding can be:
2538
2539 \value Text.ElideNone - the default
2540 \value Text.ElideLeft
2541 \value Text.ElideMiddle
2542 \value Text.ElideRight
2543
2544 If this property is set to Text.ElideRight, it can be used with \l {wrapMode}{wrapped}
2545 text. The text will only elide if \c maximumLineCount, or \c height has been set.
2546 If both \c maximumLineCount and \c height are set, \c maximumLineCount will
2547 apply unless the lines do not fit in the height allowed.
2548
2549 If the text is a multi-length string, and the mode is not \c Text.ElideNone,
2550 the first string that fits will be used, otherwise the last will be elided.
2551
2552 Multi-length strings are ordered from longest to shortest, separated by the
2553 Unicode "String Terminator" character \c U009C (write this in QML with \c{"\u009C"} or \c{"\x9C"}).
2554*/
2555QQuickText::TextElideMode QQuickText::elideMode() const
2556{
2557 Q_D(const QQuickText);
2558 return d->elideMode;
2559}
2560
2561void QQuickText::setElideMode(QQuickText::TextElideMode mode)
2562{
2563 Q_D(QQuickText);
2564 if (mode == d->elideMode)
2565 return;
2566
2567 d->elideMode = mode;
2568 d->updateLayout();
2569
2570 emit elideModeChanged(mode);
2571}
2572
2573/*!
2574 \qmlproperty url QtQuick::Text::baseUrl
2575
2576 This property specifies a base URL that is used to resolve relative URLs
2577 within the text.
2578
2579 Urls are resolved to be within the same directory as the target of the base
2580 URL meaning any portion of the path after the last '/' will be ignored.
2581
2582 \table
2583 \header \li Base URL \li Relative URL \li Resolved URL
2584 \row \li http://qt-project.org/ \li images/logo.png \li http://qt-project.org/images/logo.png
2585 \row \li http://qt-project.org/index.html \li images/logo.png \li http://qt-project.org/images/logo.png
2586 \row \li http://qt-project.org/content \li images/logo.png \li http://qt-project.org/content/images/logo.png
2587 \row \li http://qt-project.org/content/ \li images/logo.png \li http://qt-project.org/content/images/logo.png
2588 \row \li http://qt-project.org/content/index.html \li images/logo.png \li http://qt-project.org/content/images/logo.png
2589 \row \li http://qt-project.org/content/index.html \li ../images/logo.png \li http://qt-project.org/images/logo.png
2590 \row \li http://qt-project.org/content/index.html \li /images/logo.png \li http://qt-project.org/images/logo.png
2591 \endtable
2592
2593 The default value is the url of the QML file instantiating the Text item.
2594*/
2595
2596QUrl QQuickText::baseUrl() const
2597{
2598 Q_D(const QQuickText);
2599 if (!d->extra.isAllocated() || d->extra->baseUrl.isEmpty()) {
2600 if (QQmlContext *context = qmlContext(this))
2601 return context->baseUrl();
2602 else
2603 return QUrl();
2604 } else {
2605 return d->extra->baseUrl;
2606 }
2607}
2608
2609void QQuickText::setBaseUrl(const QUrl &url)
2610{
2611 Q_D(QQuickText);
2612 if (baseUrl() != url) {
2613 d->extra.value().baseUrl = url;
2614
2615 if (d->richText) {
2616 d->ensureDoc();
2617 d->extra->doc->setBaseUrl(url);
2618 }
2619 if (d->styledText) {
2620 d->textHasChanged = true;
2621 if (d->extra.isAllocated()) {
2622 qDeleteAll(d->extra->imgTags);
2623 d->extra->imgTags.clear();
2624 }
2625 d->updateLayout();
2626 }
2627 emit baseUrlChanged();
2628 }
2629}
2630
2631void QQuickText::resetBaseUrl()
2632{
2633 if (QQmlContext *context = qmlContext(this))
2634 setBaseUrl(context->baseUrl());
2635 else
2636 setBaseUrl(QUrl());
2637}
2638
2639/*!
2640 Returns the extents of the text after layout.
2641 If the \l style() is not \c Text.Normal, a margin is added to ensure
2642 that the rendering effect will fit within this rectangle.
2643
2644 \sa contentWidth(), contentHeight(), clipRect()
2645*/
2646QRectF QQuickText::boundingRect() const
2647{
2648 Q_D(const QQuickText);
2649
2650 QRectF rect = d->layedOutTextRect;
2651 rect.moveLeft(QQuickTextUtil::alignedX(rect.width(), width(), effectiveHAlign()));
2652 rect.moveTop(QQuickTextUtil::alignedY(rect.height() + d->lineHeightOffset(), height(), d->vAlign));
2653
2654 if (d->style != Normal)
2655 rect.adjust(-1, 0, 1, 2);
2656 // Could include font max left/right bearings to either side of rectangle.
2657
2658 return rect;
2659}
2660
2661/*!
2662 Returns a rectangular area slightly larger than what is currently visible
2663 in \l viewportItem(); otherwise, the rectangle \c (0, 0, width, height).
2664 The text will be clipped to fit if \l clip is \c true.
2665
2666 \note If the \l style is not \c Text.Normal, the clip rectangle is adjusted
2667 to be slightly larger, to limit clipping of the outline effect at the edges.
2668 But it still looks better to set \l clip to \c false in that case.
2669
2670 \sa contentWidth(), contentHeight(), boundingRect()
2671*/
2672QRectF QQuickText::clipRect() const
2673{
2674 Q_D(const QQuickText);
2675
2676 QRectF rect = QQuickImplicitSizeItem::clipRect();
2677 if (d->style != Normal)
2678 rect.adjust(-1, 0, 1, 2);
2679 return rect;
2680}
2681
2682/*! \internal */
2683void QQuickText::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
2684{
2685 Q_D(QQuickText);
2686 if (d->text.isEmpty()) {
2687 QQuickItem::geometryChange(newGeometry, oldGeometry);
2688 return;
2689 }
2690
2691 bool widthChanged = newGeometry.width() != oldGeometry.width();
2692 bool heightChanged = newGeometry.height() != oldGeometry.height();
2693 bool wrapped = d->wrapMode != QQuickText::NoWrap;
2694 bool elide = d->elideMode != QQuickText::ElideNone;
2695 bool scaleFont = d->fontSizeMode() != QQuickText::FixedSize && (widthValid() || heightValid());
2696 bool verticalScale = (d->fontSizeMode() & QQuickText::VerticalFit) && heightValid();
2697
2698 bool widthMaximum = newGeometry.width() >= oldGeometry.width() && !d->widthExceeded;
2699 bool heightMaximum = newGeometry.height() >= oldGeometry.height() && !d->heightExceeded;
2700
2701 bool verticalPositionChanged = heightChanged && d->vAlign != AlignTop;
2702
2703 if ((!widthChanged && !heightChanged) || d->internalWidthUpdate)
2704 goto geomChangeDone;
2705
2706 if ((effectiveHAlign() != QQuickText::AlignLeft && widthChanged) || verticalPositionChanged) {
2707 // If the width has changed and we're not left aligned do an update so the text is
2708 // repositioned even if a full layout isn't required. And the same for vertical.
2709 d->updateType = QQuickTextPrivate::UpdatePaintNode;
2710 update();
2711 }
2712
2713 if (!wrapped && !elide && !scaleFont && !verticalPositionChanged)
2714 goto geomChangeDone; // left aligned unwrapped text without eliding never needs relayout
2715
2716 if (elide // eliding and dimensions were and remain invalid;
2717 && ((widthValid() && oldGeometry.width() <= 0 && newGeometry.width() <= 0)
2718 || (heightValid() && oldGeometry.height() <= 0 && newGeometry.height() <= 0))) {
2719 goto geomChangeDone;
2720 }
2721
2722 if (widthMaximum && heightMaximum && !d->isLineLaidOutConnected() && !verticalPositionChanged) // Size is sufficient and growing.
2723 goto geomChangeDone;
2724
2725 if (!(widthChanged || widthMaximum) && !d->isLineLaidOutConnected()) { // only height has changed
2726 if (!verticalPositionChanged) {
2727 if (newGeometry.height() > oldGeometry.height()) {
2728 if (!d->heightExceeded && !qFuzzyIsNull(oldGeometry.height())) {
2729 // Height is adequate and growing, and it wasn't 0 previously.
2730 goto geomChangeDone;
2731 }
2732 if (d->lineCount == d->maximumLineCount()) // Reached maximum line and height is growing.
2733 goto geomChangeDone;
2734 } else if (newGeometry.height() < oldGeometry.height()) {
2735 if (d->lineCount < 2 && !verticalScale && newGeometry.height() > 0) // A single line won't be truncated until the text is 0 height.
2736 goto geomChangeDone;
2737
2738 if (!verticalScale // no scaling, no eliding, and either unwrapped, or no maximum line count.
2739 && d->elideMode != QQuickText::ElideRight
2740 && !(d->maximumLineCountValid && d->widthExceeded)) {
2741 goto geomChangeDone;
2742 }
2743 }
2744 }
2745 } else if (!heightChanged && widthMaximum) {
2746 if (oldGeometry.width() > 0) {
2747 // no change to height, width is adequate and wasn't 0 before
2748 // (old width could also be negative if it was 0 and the margins
2749 // were set)
2750 goto geomChangeDone;
2751 }
2752 }
2753
2754 if (d->updateOnComponentComplete || d->textHasChanged) {
2755 // We need to re-elide
2756 d->updateLayout();
2757 } else {
2758 // We just need to re-layout
2759 d->updateSize();
2760 }
2761
2762geomChangeDone:
2763 QQuickItem::geometryChange(newGeometry, oldGeometry);
2764}
2765
2766void QQuickText::triggerPreprocess()
2767{
2768 Q_D(QQuickText);
2769 if (d->updateType == QQuickTextPrivate::UpdateNone)
2770 d->updateType = QQuickTextPrivate::UpdatePreprocess;
2771 update();
2772}
2773
2774QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
2775{
2776 Q_UNUSED(data);
2777 Q_D(QQuickText);
2778
2779 if (d->text.isEmpty()) {
2780 d->containsUnscalableGlyphs = false;
2781 delete oldNode;
2782 return nullptr;
2783 }
2784
2785 if (d->updateType != QQuickTextPrivate::UpdatePaintNode && oldNode != nullptr) {
2786 // Update done in preprocess() in the nodes
2787 d->updateType = QQuickTextPrivate::UpdateNone;
2788 return oldNode;
2789 }
2790
2791 d->updateType = QQuickTextPrivate::UpdateNone;
2792
2793 const qreal dy = QQuickTextUtil::alignedY(d->layedOutTextRect.height() + d->lineHeightOffset(), d->availableHeight(), d->vAlign) + topPadding();
2794
2795 QSGInternalTextNode *node = nullptr;
2796 if (!oldNode)
2797 node = d->sceneGraphContext()->createInternalTextNode(d->sceneGraphRenderContext());
2798 else
2799 node = static_cast<QSGInternalTextNode *>(oldNode);
2800
2801 node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
2802
2803 node->setTextStyle(QSGTextNode::TextStyle(d->style));
2804 node->setRenderType(QSGTextNode::RenderType(d->renderType));
2805 node->setRenderTypeQuality(d->renderTypeQuality());
2806 node->clear();
2807 node->setMatrix(QMatrix4x4());
2808
2809 node->setColor(QColor::fromRgba(d->color));
2810 node->setStyleColor(QColor::fromRgba(d->styleColor));
2811 node->setLinkColor(QColor::fromRgba(d->linkColor));
2812
2813 node->setDevicePixelRatio(d->effectiveDevicePixelRatio());
2814
2815 if (d->richText) {
2816 node->setViewport(clipRect());
2817 const qreal dx = QQuickTextUtil::alignedX(d->layedOutTextRect.width(), d->availableWidth(), effectiveHAlign()) + leftPadding();
2818 d->ensureDoc();
2819 node->addTextDocument(QPointF(dx, dy), d->extra->doc);
2820 } else if (d->layedOutTextRect.width() > 0) {
2821 if (flags().testFlag(ItemObservesViewport))
2822 node->setViewport(clipRect());
2823 else
2824 node->setViewport(QRectF{});
2825 const qreal dx = QQuickTextUtil::alignedX(d->lineWidth, d->availableWidth(), effectiveHAlign()) + leftPadding();
2826 int unelidedLineCount = d->lineCount;
2827 if (d->elideLayout)
2828 unelidedLineCount -= 1;
2829 if (unelidedLineCount > 0)
2830 node->addTextLayout(QPointF(dx, dy), &d->layout, -1, -1,0, unelidedLineCount);
2831
2832 if (d->elideLayout)
2833 node->addTextLayout(QPointF(dx, dy), d->elideLayout.get());
2834
2835 if (d->extra.isAllocated()) {
2836 for (QQuickStyledTextImgTag *img : std::as_const(d->extra->visibleImgTags)) {
2837 if (img->pix && img->pix->isReady())
2838 node->addImage(QRectF(img->pos.x() + dx, img->pos.y() + dy, img->size.width(), img->size.height()), img->pix->image());
2839 }
2840 }
2841 }
2842
2843 d->containsUnscalableGlyphs = node->containsUnscalableGlyphs();
2844
2845 // The font caches have now been initialized on the render thread, so they have to be
2846 // invalidated before we can use them from the main thread again.
2847 invalidateFontCaches();
2848
2849 return node;
2850}
2851
2852void QQuickText::updatePolish()
2853{
2854 Q_D(QQuickText);
2855 const bool clipNodeChanged =
2856 d->componentComplete && d->clipNode() && d->clipNode()->rect() != clipRect();
2857 if (clipNodeChanged)
2858 d->dirty(QQuickItemPrivate::Clip);
2859
2860 // If the fonts used for rendering are different from the ones used in the GUI thread,
2861 // it means we will get warnings and corrupted text. If this case is detected, we need
2862 // to update the text layout before creating the scenegraph nodes.
2863 if (!d->assignedFont.isEmpty() && QFontInfo(d->font).family() != d->assignedFont)
2864 d->polishSize = true;
2865
2866 if (d->polishSize) {
2867 d->updateSize();
2868 d->polishSize = false;
2869 }
2870 invalidateFontCaches();
2871}
2872
2873/*!
2874 \qmlproperty real QtQuick::Text::contentWidth
2875
2876 Returns the width of the text, including width past the width
2877 that is covered due to insufficient wrapping if WrapMode is set.
2878*/
2879qreal QQuickText::contentWidth() const
2880{
2881 Q_D(const QQuickText);
2882 return d->layedOutTextRect.width();
2883}
2884
2885/*!
2886 \qmlproperty real QtQuick::Text::contentHeight
2887
2888 Returns the height of the text, including height past the height
2889 that is covered due to there being more text than fits in the set height.
2890*/
2891qreal QQuickText::contentHeight() const
2892{
2893 Q_D(const QQuickText);
2894 return d->layedOutTextRect.height() + qMax(d->lineHeightOffset(), 0);
2895}
2896
2897/*!
2898 \qmlproperty real QtQuick::Text::lineHeight
2899
2900 Sets the line height for the text.
2901 The value can be in pixels or a multiplier depending on lineHeightMode.
2902
2903 The default value is a multiplier of 1.0.
2904 The line height must be a positive value.
2905*/
2906qreal QQuickText::lineHeight() const
2907{
2908 Q_D(const QQuickText);
2909 return d->lineHeight();
2910}
2911
2912void QQuickText::setLineHeight(qreal lineHeight)
2913{
2914 Q_D(QQuickText);
2915
2916 if ((d->lineHeight() == lineHeight) || (lineHeight < 0.0))
2917 return;
2918
2919 d->extra.value().lineHeightValid = true;
2920 d->extra.value().lineHeight = lineHeight;
2921 d->implicitHeightValid = false;
2922 d->updateLayout();
2923 emit lineHeightChanged(lineHeight);
2924}
2925
2926/*!
2927 \qmlproperty enumeration QtQuick::Text::lineHeightMode
2928
2929 This property determines how the line height is specified.
2930 The possible values are:
2931
2932 \value Text.ProportionalHeight (default) sets the spacing proportional to the line
2933 (as a multiplier). For example, set to 2 for double spacing.
2934 \value Text.FixedHeight sets the line height to a fixed line height (in pixels).
2935*/
2936QQuickText::LineHeightMode QQuickText::lineHeightMode() const
2937{
2938 Q_D(const QQuickText);
2939 return d->lineHeightMode();
2940}
2941
2942void QQuickText::setLineHeightMode(LineHeightMode mode)
2943{
2944 Q_D(QQuickText);
2945 if (mode == d->lineHeightMode())
2946 return;
2947
2948 d->implicitHeightValid = false;
2949 d->extra.value().lineHeightValid = true;
2950 d->extra.value().lineHeightMode = mode;
2951 d->updateLayout();
2952
2953 emit lineHeightModeChanged(mode);
2954}
2955
2956/*!
2957 \qmlproperty enumeration QtQuick::Text::fontSizeMode
2958
2959 This property specifies how the font size of the displayed text is determined.
2960 The possible values are:
2961
2962 \value Text.FixedSize
2963 (default) The size specified by \l font.pixelSize or \l font.pointSize is used.
2964 \value Text.HorizontalFit
2965 The largest size up to the size specified that fits within the width of the item
2966 without wrapping is used.
2967 \value Text.VerticalFit
2968 The largest size up to the size specified that fits the height of the item is used.
2969 \value Text.Fit
2970 The largest size up to the size specified that fits within the width and height
2971 of the item is used.
2972
2973 The font size of fitted text has a minimum bound specified by the
2974 minimumPointSize or minimumPixelSize property and maximum bound specified
2975 by either the \l font.pointSize or \l font.pixelSize properties.
2976
2977 \qml
2978 Text { text: "Hello"; fontSizeMode: Text.Fit; minimumPixelSize: 10; font.pixelSize: 72 }
2979 \endqml
2980
2981 If the text does not fit within the item bounds with the minimum font size
2982 the text will be elided as per the \l elide property.
2983
2984 If the \l textFormat property is set to \c Text.RichText, this will have no effect at all as the
2985 property will be ignored completely. If \l textFormat is set to \c Text.StyledText, then the
2986 property will be respected provided there is no font size tags inside the text. If there are
2987 font size tags, the property will still respect those. This can cause it to not fully comply with
2988 the fontSizeMode setting.
2989*/
2990
2991QQuickText::FontSizeMode QQuickText::fontSizeMode() const
2992{
2993 Q_D(const QQuickText);
2994 return d->fontSizeMode();
2995}
2996
2997void QQuickText::setFontSizeMode(FontSizeMode mode)
2998{
2999 Q_D(QQuickText);
3000 if (d->fontSizeMode() == mode)
3001 return;
3002
3003 d->polishSize = true;
3004 polish();
3005
3006 d->extra.value().fontSizeMode = mode;
3007 emit fontSizeModeChanged();
3008}
3009
3010/*!
3011 \qmlproperty int QtQuick::Text::minimumPixelSize
3012
3013 This property specifies the minimum font pixel size of text scaled by the
3014 fontSizeMode property.
3015
3016 If the fontSizeMode is Text.FixedSize or the \l font.pixelSize is -1 this
3017 property is ignored.
3018*/
3019
3020int QQuickText::minimumPixelSize() const
3021{
3022 Q_D(const QQuickText);
3023 return d->minimumPixelSize();
3024}
3025
3026void QQuickText::setMinimumPixelSize(int size)
3027{
3028 Q_D(QQuickText);
3029 if (d->minimumPixelSize() == size)
3030 return;
3031
3032 if (d->fontSizeMode() != FixedSize && (widthValid() || heightValid())) {
3033 d->polishSize = true;
3034 polish();
3035 }
3036 d->extra.value().minimumPixelSize = size;
3037 emit minimumPixelSizeChanged();
3038}
3039
3040/*!
3041 \qmlproperty int QtQuick::Text::minimumPointSize
3042
3043 This property specifies the minimum font point \l size of text scaled by
3044 the fontSizeMode property.
3045
3046 If the fontSizeMode is Text.FixedSize or the \l font.pointSize is -1 this
3047 property is ignored.
3048*/
3049
3050int QQuickText::minimumPointSize() const
3051{
3052 Q_D(const QQuickText);
3053 return d->minimumPointSize();
3054}
3055
3056void QQuickText::setMinimumPointSize(int size)
3057{
3058 Q_D(QQuickText);
3059 if (d->minimumPointSize() == size)
3060 return;
3061
3062 if (d->fontSizeMode() != FixedSize && (widthValid() || heightValid())) {
3063 d->polishSize = true;
3064 polish();
3065 }
3066 d->extra.value().minimumPointSize = size;
3067 emit minimumPointSizeChanged();
3068}
3069
3070/*!
3071 Returns the number of resources (images) that are being loaded asynchronously.
3072*/
3073int QQuickText::resourcesLoading() const
3074{
3075 Q_D(const QQuickText);
3076 if (d->richText && d->extra.isAllocated())
3077 return d->extra->pixmapsInProgress.size();
3078 return 0;
3079}
3080
3081/*! \internal */
3082void QQuickText::componentComplete()
3083{
3084 Q_D(QQuickText);
3085 if (d->updateOnComponentComplete) {
3086 if (d->richText) {
3087 d->updateDocumentText();
3088 } else {
3089 d->rightToLeftText = d->text.isRightToLeft();
3090 }
3091 d->determineHorizontalAlignment();
3092 }
3093 QQuickItem::componentComplete();
3094 if (d->updateOnComponentComplete)
3095 d->updateLayout();
3096}
3097
3098QString QQuickTextPrivate::anchorAt(const QTextLayout *layout, const QPointF &mousePos)
3099{
3100 for (int i = 0; i < layout->lineCount(); ++i) {
3101 QTextLine line = layout->lineAt(i);
3102 if (line.naturalTextRect().contains(mousePos)) {
3103 int charPos = line.xToCursor(mousePos.x(), QTextLine::CursorOnCharacter);
3104 const auto formats = layout->formats();
3105 for (const QTextLayout::FormatRange &formatRange : formats) {
3106 if (formatRange.format.isAnchor()
3107 && charPos >= formatRange.start
3108 && charPos < formatRange.start + formatRange.length) {
3109 return formatRange.format.anchorHref();
3110 }
3111 }
3112 break;
3113 }
3114 }
3115 return QString();
3116}
3117
3118QString QQuickTextPrivate::anchorAt(const QPointF &mousePos) const
3119{
3120 Q_Q(const QQuickText);
3121 QPointF translatedMousePos = mousePos;
3122 translatedMousePos.rx() -= q->leftPadding();
3123 translatedMousePos.ry() -= q->topPadding() + QQuickTextUtil::alignedY(layedOutTextRect.height() + lineHeightOffset(), availableHeight(), vAlign);
3124 if (styledText) {
3125 QString link = anchorAt(&layout, translatedMousePos);
3126 if (link.isEmpty() && elideLayout)
3127 link = anchorAt(elideLayout.get(), translatedMousePos);
3128 return link;
3129 } else if (richText && extra.isAllocated() && extra->doc) {
3130 translatedMousePos.rx() -= QQuickTextUtil::alignedX(layedOutTextRect.width(), availableWidth(), q->effectiveHAlign());
3131 return extra->doc->documentLayout()->anchorAt(translatedMousePos);
3132 }
3133 return QString();
3134}
3135
3136bool QQuickTextPrivate::isLinkActivatedConnected()
3137{
3138 Q_Q(QQuickText);
3139 IS_SIGNAL_CONNECTED(q, QQuickText, linkActivated, (const QString &));
3140}
3141
3142/*! \internal */
3143void QQuickText::mousePressEvent(QMouseEvent *event)
3144{
3145 Q_D(QQuickText);
3146
3147 QString link;
3148 if (d->isLinkActivatedConnected())
3149 link = d->anchorAt(event->position());
3150
3151 if (link.isEmpty()) {
3152 event->setAccepted(false);
3153 } else {
3154 d->extra.value().activeLink = link;
3155 }
3156
3157 // ### may malfunction if two of the same links are clicked & dragged onto each other)
3158
3159 if (!event->isAccepted())
3160 QQuickItem::mousePressEvent(event);
3161}
3162
3163
3164/*! \internal */
3165void QQuickText::mouseReleaseEvent(QMouseEvent *event)
3166{
3167 Q_D(QQuickText);
3168
3169 // ### confirm the link, and send a signal out
3170
3171 QString link;
3172 if (d->isLinkActivatedConnected())
3173 link = d->anchorAt(event->position());
3174
3175 if (!link.isEmpty() && d->extra.isAllocated() && d->extra->activeLink == link)
3176 emit linkActivated(d->extra->activeLink);
3177 else
3178 event->setAccepted(false);
3179
3180 if (!event->isAccepted())
3181 QQuickItem::mouseReleaseEvent(event);
3182}
3183
3184bool QQuickTextPrivate::isLinkHoveredConnected()
3185{
3186 Q_Q(QQuickText);
3187 IS_SIGNAL_CONNECTED(q, QQuickText, linkHovered, (const QString &));
3188}
3189
3190static void getLinks_helper(const QTextLayout *layout, QVector<QQuickTextPrivate::LinkDesc> *links)
3191{
3192 for (const QTextLayout::FormatRange &formatRange : layout->formats()) {
3193 if (formatRange.format.isAnchor()) {
3194 const int start = formatRange.start;
3195 const int len = formatRange.length;
3196 QTextLine line = layout->lineForTextPosition(start);
3197 QRectF r;
3198 r.setTop(line.y());
3199 r.setLeft(line.cursorToX(start, QTextLine::Leading));
3200 r.setHeight(line.height());
3201 r.setRight(line.cursorToX(start + len, QTextLine::Trailing));
3202 // ### anchorNames() is empty?! Not sure why this doesn't work
3203 // QString anchorName = formatRange.format.anchorNames().value(0); //### pick the first?
3204 // Therefore, we resort to QString::mid()
3205 QString anchorName = layout->text().mid(start, len);
3206 const QString anchorHref = formatRange.format.anchorHref();
3207 if (anchorName.isEmpty())
3208 anchorName = anchorHref;
3209 links->append( { anchorName, anchorHref, start, start + len, r.toRect()} );
3210 }
3211 }
3212}
3213
3214QVector<QQuickTextPrivate::LinkDesc> QQuickTextPrivate::getLinks() const
3215{
3216 QVector<QQuickTextPrivate::LinkDesc> links;
3217 getLinks_helper(&layout, &links);
3218 return links;
3219}
3220
3221
3222/*!
3223 \qmlsignal QtQuick::Text::linkHovered(string link)
3224 \since 5.2
3225
3226 This signal is emitted when the user hovers a link embedded in the
3227 text. The link must be in rich text or HTML format and the \a link
3228 string provides access to the particular link.
3229
3230 \sa hoveredLink, linkAt()
3231*/
3232
3233/*!
3234 \qmlproperty string QtQuick::Text::hoveredLink
3235 \since 5.2
3236
3237 This property contains the link string when the user hovers a link
3238 embedded in the text. The link must be in rich text or HTML format
3239 and the \a hoveredLink string provides access to the particular link.
3240
3241 \sa linkHovered, linkAt()
3242*/
3243
3244QString QQuickText::hoveredLink() const
3245{
3246 Q_D(const QQuickText);
3247 if (const_cast<QQuickTextPrivate *>(d)->isLinkHoveredConnected()) {
3248 if (d->extra.isAllocated())
3249 return d->extra->hoveredLink;
3250 } else {
3251#if QT_CONFIG(cursor)
3252 if (QQuickWindow *wnd = window()) {
3253 QPointF pos = QCursor::pos(wnd->screen()) - wnd->position() - mapToScene(QPointF(0, 0));
3254 return d->anchorAt(pos);
3255 }
3256#endif // cursor
3257 }
3258 return QString();
3259}
3260
3261void QQuickTextPrivate::processHoverEvent(QHoverEvent *event)
3262{
3263 Q_Q(QQuickText);
3264 qCDebug(lcHoverTrace) << q;
3265 QString link;
3266 if (isLinkHoveredConnected()) {
3267 if (event->type() != QEvent::HoverLeave)
3268 link = anchorAt(event->position());
3269
3270 if ((!extra.isAllocated() && !link.isEmpty()) || (extra.isAllocated() && extra->hoveredLink != link)) {
3271 extra.value().hoveredLink = link;
3272 emit q->linkHovered(extra->hoveredLink);
3273 }
3274 }
3275 event->ignore();
3276}
3277
3278void QQuickText::hoverEnterEvent(QHoverEvent *event)
3279{
3280 Q_D(QQuickText);
3281 d->processHoverEvent(event);
3282}
3283
3284void QQuickText::hoverMoveEvent(QHoverEvent *event)
3285{
3286 Q_D(QQuickText);
3287 d->processHoverEvent(event);
3288}
3289
3290void QQuickText::hoverLeaveEvent(QHoverEvent *event)
3291{
3292 Q_D(QQuickText);
3293 d->processHoverEvent(event);
3294}
3295
3296void QQuickText::invalidate()
3297{
3298 Q_D(QQuickText);
3299 d->textHasChanged = true;
3300 QMetaObject::invokeMethod(this,[&]{q_updateLayout();});
3301}
3302
3303bool QQuickTextPrivate::transformChanged(QQuickItem *transformedItem)
3304{
3305 // If there's a lot of text, we may need QQuickText::updatePaintNode() to call
3306 // QSGInternalTextNode::addTextLayout() again to populate a different range of lines
3307 if (flags & QQuickItem::ItemObservesViewport) {
3308 updateType = UpdatePaintNode;
3309 dirty(QQuickItemPrivate::Content);
3310 }
3311 return QQuickImplicitSizeItemPrivate::transformChanged(transformedItem);
3312}
3313
3314/*!
3315 \qmlproperty int QtQuick::Text::renderTypeQuality
3316 \since 6.0
3317
3318 Override the default rendering type quality for this component. This is a low-level
3319 customization which can be ignored in most cases. It currently only has an effect
3320 when \l renderType is \c Text.QtRendering.
3321
3322 The rasterization algorithm used by Text.QtRendering may give artifacts at
3323 large text sizes, such as sharp corners looking rounder than they should. If
3324 this is an issue for specific text items, increase the \c renderTypeQuality to
3325 improve rendering quality, at the expense of memory consumption.
3326
3327 The \c renderTypeQuality may be any integer over 0, or one of the following
3328 predefined values
3329
3330 \value Text.DefaultRenderTypeQuality -1 (default)
3331 \value Text.LowRenderTypeQuality 26
3332 \value Text.NormalRenderTypeQuality 52
3333 \value Text.HighRenderTypeQuality 104
3334 \value Text.VeryHighRenderTypeQuality 208
3335*/
3336int QQuickText::renderTypeQuality() const
3337{
3338 Q_D(const QQuickText);
3339 return d->renderTypeQuality();
3340}
3341
3342void QQuickText::setRenderTypeQuality(int renderTypeQuality)
3343{
3344 Q_D(QQuickText);
3345 if (renderTypeQuality == d->renderTypeQuality())
3346 return;
3347 d->extra.value().renderTypeQuality = renderTypeQuality;
3348
3349 if (isComponentComplete()) {
3350 d->updateType = QQuickTextPrivate::UpdatePaintNode;
3351 update();
3352 }
3353
3354 emit renderTypeQualityChanged();
3355}
3356
3357/*!
3358 \qmlproperty enumeration QtQuick::Text::renderType
3359
3360 Override the default rendering type for this component.
3361
3362 Supported render types are:
3363
3364 \value Text.QtRendering Text is rendered using a scalable distance field for each glyph.
3365 \value Text.NativeRendering Text is rendered using a platform-specific technique.
3366 \value Text.CurveRendering Text is rendered using a curve rasterizer running directly on the
3367 graphics hardware. (Introduced in Qt 6.7.0.)
3368
3369 Select \c Text.NativeRendering if you prefer text to look native on the target platform and do
3370 not require advanced features such as transformation of the text. Using such features in
3371 combination with the NativeRendering render type will lend poor and sometimes pixelated
3372 results.
3373
3374 Both \c Text.QtRendering and \c Text.CurveRendering are hardware-accelerated techniques.
3375 \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
3376 artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
3377 where \c QtRendering does not give good visual results or where reducing graphics memory
3378 consumption is a priority.
3379
3380 The default rendering type is determined by \l QQuickWindow::textRenderType().
3381*/
3382QQuickText::RenderType QQuickText::renderType() const
3383{
3384 Q_D(const QQuickText);
3385 return d->renderType;
3386}
3387
3388void QQuickText::setRenderType(QQuickText::RenderType renderType)
3389{
3390 Q_D(QQuickText);
3391 if (d->renderType == renderType)
3392 return;
3393
3394 d->renderType = renderType;
3395 emit renderTypeChanged();
3396
3397 if (isComponentComplete())
3398 d->updateLayout();
3399}
3400
3401#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
3402#if QT_DEPRECATED_SINCE(5, 15)
3403/*!
3404 \qmlmethod QtQuick::Text::doLayout()
3405 \deprecated
3406
3407 Use \l forceLayout() instead.
3408*/
3409void QQuickText::doLayout()
3410{
3411 forceLayout();
3412}
3413
3414#endif
3415#endif
3416/*!
3417 \qmlmethod QtQuick::Text::forceLayout()
3418 \since 5.9
3419
3420 Triggers a re-layout of the displayed text.
3421*/
3422void QQuickText::forceLayout()
3423{
3424 Q_D(QQuickText);
3425 d->updateSize();
3426}
3427
3428/*!
3429 \qmlmethod QtQuick::Text::linkAt(real x, real y)
3430 \since 5.3
3431
3432 Returns the link string at point \a x, \a y in content coordinates,
3433 or an empty string if no link exists at that point.
3434
3435 \sa hoveredLink
3436*/
3437QString QQuickText::linkAt(qreal x, qreal y) const
3438{
3439 Q_D(const QQuickText);
3440 return d->anchorAt(QPointF(x, y));
3441}
3442
3443/*!
3444 * \internal
3445 *
3446 * Invalidates font caches owned by the text objects owned by the element
3447 * to work around the fact that text objects cannot be used from multiple threads.
3448 */
3449void QQuickText::invalidateFontCaches()
3450{
3451 Q_D(QQuickText);
3452
3453 if (d->richText && d->extra.isAllocated() && d->extra->doc != nullptr) {
3454 QTextBlock block;
3455 for (block = d->extra->doc->firstBlock(); block.isValid(); block = block.next()) {
3456 if (block.layout() != nullptr && block.layout()->engine() != nullptr)
3457 block.layout()->engine()->resetFontEngineCache();
3458 }
3459 } else {
3460 if (d->layout.engine() != nullptr)
3461 d->layout.engine()->resetFontEngineCache();
3462 }
3463}
3464
3465/*!
3466 \since 5.6
3467 \qmlproperty real QtQuick::Text::padding
3468 \qmlproperty real QtQuick::Text::topPadding
3469 \qmlproperty real QtQuick::Text::leftPadding
3470 \qmlproperty real QtQuick::Text::bottomPadding
3471 \qmlproperty real QtQuick::Text::rightPadding
3472
3473 These properties hold the padding around the content. This space is reserved
3474 in addition to the contentWidth and contentHeight.
3475*/
3476qreal QQuickText::padding() const
3477{
3478 Q_D(const QQuickText);
3479 return d->padding();
3480}
3481
3482void QQuickText::setPadding(qreal padding)
3483{
3484 Q_D(QQuickText);
3485 if (qFuzzyCompare(d->padding(), padding))
3486 return;
3487
3488 d->extra.value().padding = padding;
3489 d->updateSize();
3490 emit paddingChanged();
3491 if (!d->extra.isAllocated() || !d->extra->explicitTopPadding)
3492 emit topPaddingChanged();
3493 if (!d->extra.isAllocated() || !d->extra->explicitLeftPadding)
3494 emit leftPaddingChanged();
3495 if (!d->extra.isAllocated() || !d->extra->explicitRightPadding)
3496 emit rightPaddingChanged();
3497 if (!d->extra.isAllocated() || !d->extra->explicitBottomPadding)
3498 emit bottomPaddingChanged();
3499}
3500
3501void QQuickText::resetPadding()
3502{
3503 setPadding(0);
3504}
3505
3506qreal QQuickText::topPadding() const
3507{
3508 Q_D(const QQuickText);
3509 if (d->extra.isAllocated() && d->extra->explicitTopPadding)
3510 return d->extra->topPadding;
3511 return d->padding();
3512}
3513
3514void QQuickText::setTopPadding(qreal padding)
3515{
3516 Q_D(QQuickText);
3517 d->setTopPadding(padding);
3518}
3519
3520void QQuickText::resetTopPadding()
3521{
3522 Q_D(QQuickText);
3523 d->setTopPadding(0, true);
3524}
3525
3526qreal QQuickText::leftPadding() const
3527{
3528 Q_D(const QQuickText);
3529 if (d->extra.isAllocated() && d->extra->explicitLeftPadding)
3530 return d->extra->leftPadding;
3531 return d->padding();
3532}
3533
3534void QQuickText::setLeftPadding(qreal padding)
3535{
3536 Q_D(QQuickText);
3537 d->setLeftPadding(padding);
3538}
3539
3540void QQuickText::resetLeftPadding()
3541{
3542 Q_D(QQuickText);
3543 d->setLeftPadding(0, true);
3544}
3545
3546qreal QQuickText::rightPadding() const
3547{
3548 Q_D(const QQuickText);
3549 if (d->extra.isAllocated() && d->extra->explicitRightPadding)
3550 return d->extra->rightPadding;
3551 return d->padding();
3552}
3553
3554void QQuickText::setRightPadding(qreal padding)
3555{
3556 Q_D(QQuickText);
3557 d->setRightPadding(padding);
3558}
3559
3560void QQuickText::resetRightPadding()
3561{
3562 Q_D(QQuickText);
3563 d->setRightPadding(0, true);
3564}
3565
3566qreal QQuickText::bottomPadding() const
3567{
3568 Q_D(const QQuickText);
3569 if (d->extra.isAllocated() && d->extra->explicitBottomPadding)
3570 return d->extra->bottomPadding;
3571 return d->padding();
3572}
3573
3574void QQuickText::setBottomPadding(qreal padding)
3575{
3576 Q_D(QQuickText);
3577 d->setBottomPadding(padding);
3578}
3579
3580void QQuickText::resetBottomPadding()
3581{
3582 Q_D(QQuickText);
3583 d->setBottomPadding(0, true);
3584}
3585
3586/*!
3587 \qmlproperty string QtQuick::Text::fontInfo.family
3588 \since 5.9
3589
3590 The family name of the font that has been resolved for the current font
3591 and fontSizeMode.
3592*/
3593
3594/*!
3595 \qmlproperty string QtQuick::Text::fontInfo.styleName
3596 \since 5.9
3597
3598 The style name of the font info that has been resolved for the current font
3599 and fontSizeMode.
3600*/
3601
3602/*!
3603 \qmlproperty bool QtQuick::Text::fontInfo.bold
3604 \since 5.9
3605
3606 The bold state of the font info that has been resolved for the current font
3607 and fontSizeMode. This is true if the weight of the resolved font is bold or higher.
3608*/
3609
3610/*!
3611 \qmlproperty int QtQuick::Text::fontInfo.weight
3612 \since 5.9
3613
3614 The weight of the font info that has been resolved for the current font
3615 and fontSizeMode.
3616*/
3617
3618/*!
3619 \qmlproperty bool QtQuick::Text::fontInfo.italic
3620 \since 5.9
3621
3622 The italic state of the font info that has been resolved for the current font
3623 and fontSizeMode.
3624*/
3625
3626/*!
3627 \qmlproperty real QtQuick::Text::fontInfo.pointSize
3628 \since 5.9
3629
3630 The pointSize of the font info that has been resolved for the current font
3631 and fontSizeMode.
3632*/
3633
3634/*!
3635 \qmlproperty int QtQuick::Text::fontInfo.pixelSize
3636 \since 5.9
3637
3638 The pixel size of the font info that has been resolved for the current font
3639 and fontSizeMode.
3640*/
3641QJSValue QQuickText::fontInfo() const
3642{
3643 Q_D(const QQuickText);
3644
3645 QJSEngine *engine = qjsEngine(this);
3646 if (!engine) {
3647 qmlWarning(this) << "fontInfo: item has no JS engine";
3648 return QJSValue();
3649 }
3650
3651 QJSValue value = engine->newObject();
3652 value.setProperty(QStringLiteral("family"), d->fontInfo.family());
3653 value.setProperty(QStringLiteral("styleName"), d->fontInfo.styleName());
3654 value.setProperty(QStringLiteral("bold"), d->fontInfo.bold());
3655 value.setProperty(QStringLiteral("weight"), d->fontInfo.weight());
3656 value.setProperty(QStringLiteral("italic"), d->fontInfo.italic());
3657 value.setProperty(QStringLiteral("pointSize"), d->fontInfo.pointSizeF());
3658 value.setProperty(QStringLiteral("pixelSize"), d->fontInfo.pixelSize());
3659 return value;
3660}
3661
3662/*!
3663 \qmlproperty size QtQuick::Text::advance
3664 \since 5.10
3665
3666 The distance, in pixels, from the baseline origin of the first
3667 character of the text item, to the baseline origin of the first
3668 character in a text item occurring directly after this one
3669 in a text flow.
3670
3671 Note that the advance can be negative if the text flows from
3672 right to left.
3673*/
3674QSizeF QQuickText::advance() const
3675{
3676 Q_D(const QQuickText);
3677 return d->advance;
3678}
3679
3680QT_END_NAMESPACE
3681
3682#include "moc_qquicktext_p.cpp"
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")
#define QQUICKTEXT_LARGETEXT_THRESHOLD
static void getLinks_helper(const QTextLayout *layout, QVector< QQuickTextPrivate::LinkDesc > *links)