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