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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtdeclarative/src/quick/items/qquicktext.cpp