aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items/qquicktext.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/items/qquicktext.cpp')
-rw-r--r--src/quick/items/qquicktext.cpp808
1 files changed, 552 insertions, 256 deletions
diff --git a/src/quick/items/qquicktext.cpp b/src/quick/items/qquicktext.cpp
index 10399b4e27..b30084474d 100644
--- a/src/quick/items/qquicktext.cpp
+++ b/src/quick/items/qquicktext.cpp
@@ -1,41 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the QtQuick module of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:LGPL$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU Lesser General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU Lesser
-** General Public License version 3 as published by the Free Software
-** Foundation and appearing in the file LICENSE.LGPL3 included in the
-** packaging of this file. Please review the following information to
-** ensure the GNU Lesser General Public License version 3 requirements
-** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 2.0 or (at your option) the GNU General
-** Public license version 3 or any later version approved by the KDE Free
-** Qt Foundation. The licenses are as published by the Free Software
-** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-2.0.html and
-** https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qquicktext_p.h"
#include "qquicktext_p_p.h"
@@ -46,10 +10,9 @@
#include <QtQuick/private/qsgcontext_p.h>
#include <private/qqmlglobal_p.h>
#include <private/qsgadaptationlayer_p.h>
-#include "qquicktextnode_p.h"
+#include "qsginternaltextnode_p.h"
#include "qquickimage_p_p.h"
#include "qquicktextutil_p.h"
-#include "qquicktextdocument_p.h"
#include <QtQuick/private/qsgtexture_p.h>
@@ -65,7 +28,7 @@
#include <private/qtextengine_p.h>
#include <private/qquickstyledtext_p.h>
-#include <QtQuick/private/qquickpixmapcache_p.h>
+#include <QtQuick/private/qquickpixmap_p.h>
#include <qmath.h>
#include <limits.h>
@@ -73,9 +36,18 @@
QT_BEGIN_NAMESPACE
Q_DECLARE_LOGGING_CATEGORY(lcHoverTrace)
+Q_LOGGING_CATEGORY(lcText, "qt.quick.text")
+
+using namespace Qt::StringLiterals;
const QChar QQuickTextPrivate::elideChar = QChar(0x2026);
+#if !defined(QQUICKTEXT_LARGETEXT_THRESHOLD)
+ #define QQUICKTEXT_LARGETEXT_THRESHOLD 10000
+#endif
+// if QString::size() > largeTextSizeThreshold, we render more often, but only visible lines
+const int QQuickTextPrivate::largeTextSizeThreshold = QQUICKTEXT_LARGETEXT_THRESHOLD;
+
QQuickTextPrivate::QQuickTextPrivate()
: fontInfo(font), elideLayout(nullptr), textLine(nullptr), lineWidth(0)
, color(0xFF000000), linkColor(0xFF0000FF), styleColor(0xFF000000)
@@ -124,6 +96,7 @@ void QQuickTextPrivate::init()
Q_Q(QQuickText);
q->setAcceptedMouseButtons(Qt::LeftButton);
q->setFlag(QQuickItem::ItemHasContents);
+ q->setFlag(QQuickItem::ItemObservesViewport); // default until size is known
}
QQuickTextPrivate::~QQuickTextPrivate()
@@ -233,7 +206,7 @@ void QQuickTextPrivate::setBottomPadding(qreal value, bool reset)
Used to decide if the Text should use antialiasing or not. Only Text
with renderType of Text.NativeRendering can disable antialiasing.
- The default is true.
+ The default is \c true.
*/
void QQuickText::q_updateLayout()
@@ -302,6 +275,85 @@ void QQuickTextPrivate::updateLayout()
q->polish();
}
+/*! \internal
+ QTextDocument::loadResource() calls this to load inline images etc.
+ But if it's a local file, don't do it: let QTextDocument::loadResource()
+ load it in the default way. QQuickPixmap is for QtQuick-specific uses.
+*/
+QVariant QQuickText::loadResource(int type, const QUrl &source)
+{
+ Q_D(QQuickText);
+ const QUrl url = d->extra->doc->baseUrl().resolved(source);
+ if (url.isLocalFile()) {
+ // qmlWarning if the file doesn't exist (because QTextDocument::loadResource() can't do that)
+ const QFileInfo fi(QQmlFile::urlToLocalFileOrQrc(url));
+ if (!fi.exists())
+ qmlWarning(this) << "Cannot open: " << url.toString();
+ // let QTextDocument::loadResource() handle local file loading
+ return {};
+ }
+ // see if we already started a load job
+ for (auto it = d->extra->pixmapsInProgress.cbegin(); it != d->extra->pixmapsInProgress.cend();) {
+ auto *job = *it;
+ if (job->url() == url) {
+ if (job->isError()) {
+ qmlWarning(this) << job->error();
+ delete *it;
+ it = d->extra->pixmapsInProgress.erase(it);
+ return QImage();
+ }
+ qCDebug(lcText) << "already downloading" << url;
+ // existing job: return a null variant if it's not done yet
+ return job->isReady() ? job->image() : QVariant();
+ }
+ ++it;
+ }
+ qCDebug(lcText) << "loading" << source << "resolved" << url
+ << "type" << static_cast<QTextDocument::ResourceType>(type);
+ QQmlContext *context = qmlContext(this);
+ Q_ASSERT(context);
+ // don't cache it in QQuickPixmapCache, because it's cached in QTextDocumentPrivate::cachedResources
+ QQuickPixmap *p = new QQuickPixmap(context->engine(), url, QQuickPixmap::Options{});
+ p->connectFinished(this, SLOT(resourceRequestFinished()));
+ d->extra->pixmapsInProgress.append(p);
+ // the new job is probably not done; return a null variant if the caller should poll again
+ return p->isReady() ? p->image() : QVariant();
+}
+
+/*! \internal
+ Handle completion of a download that QQuickText::loadResource() started.
+*/
+void QQuickText::resourceRequestFinished()
+{
+ Q_D(QQuickText);
+ bool allDone = true;
+ for (auto it = d->extra->pixmapsInProgress.cbegin(); it != d->extra->pixmapsInProgress.cend();) {
+ auto *job = *it;
+ if (job->isError()) {
+ // get QTextDocument::loadResource() to call QQuickText::loadResource() again, to return the placeholder
+ qCDebug(lcText) << "failed to load" << job->url();
+ d->extra->doc->resource(QTextDocument::ImageResource, job->url());
+ } else if (job->isReady()) {
+ // get QTextDocument::loadResource() to call QQuickText::loadResource() again, and cache the result
+ auto res = d->extra->doc->resource(QTextDocument::ImageResource, job->url());
+ // If QTextDocument::resource() returned a valid variant, it's been cached too. Either way, the job is done.
+ qCDebug(lcText) << (res.isValid() ? "done downloading" : "failed to load") << job->url();
+ delete *it;
+ it = d->extra->pixmapsInProgress.erase(it);
+ } else {
+ allDone = false;
+ ++it;
+ }
+ }
+ if (allDone) {
+ Q_ASSERT(d->extra->pixmapsInProgress.isEmpty());
+ d->updateLayout();
+ }
+}
+
+/*! \internal
+ Handle completion of StyledText image downloads (there's no QTextDocument instance in that case).
+*/
void QQuickText::imageDownloadFinished()
{
Q_D(QQuickText);
@@ -314,7 +366,7 @@ void QQuickText::imageDownloadFinished()
if (d->extra.isAllocated() && d->extra->nbActiveDownloads == 0) {
bool needToUpdateLayout = false;
- for (QQuickStyledTextImgTag *img : qAsConst(d->extra->visibleImgTags)) {
+ for (QQuickStyledTextImgTag *img : std::as_const(d->extra->visibleImgTags)) {
if (!img->size.isValid()) {
img->size = img->pix->implicitSize();
needToUpdateLayout = true;
@@ -459,8 +511,13 @@ void QQuickTextPrivate::updateSize()
layedOutTextRect = QRectF(QPointF(0,0), dsize);
size = QSizeF(extra->doc->idealWidth(),dsize.height());
- QFontMetricsF fm(font);
- updateBaseline(fm.ascent(), q->height() - size.height() - vPadding);
+
+ qreal baseline = QFontMetricsF(font).ascent();
+ QTextBlock firstBlock = extra->doc->firstBlock();
+ if (firstBlock.isValid() && firstBlock.layout() != nullptr && firstBlock.lineCount() > 0)
+ baseline = firstBlock.layout()->lineAt(0).ascent();
+
+ updateBaseline(baseline, q->height() - size.height() - vPadding);
//### need to confirm cost of always setting these for richText
internalWidthUpdate = true;
@@ -497,7 +554,7 @@ void QQuickTextPrivate::updateSize()
QTextLine firstLine = firstBlock.layout()->lineAt(0);
QTextLine lastLine = lastBlock.layout()->lineAt(lastBlock.layout()->lineCount() - 1);
advance = QSizeF(lastLine.horizontalAdvance(),
- (lastLine.y() + lastBlock.layout()->position().y()) - (firstLine.y() + firstBlock.layout()->position().y()));
+ (lastLine.y() + lastBlock.layout()->position().y() + lastLine.ascent()) - (firstLine.y() + firstBlock.layout()->position().y() + firstLine.ascent()));
} else {
advance = QSizeF();
}
@@ -646,7 +703,7 @@ void QQuickTextPrivate::elideFormats(
{
const int end = start + length;
const QVector<QTextLayout::FormatRange> formats = layout.formats();
- for (int i = 0; i < formats.count(); ++i) {
+ for (int i = 0; i < formats.size(); ++i) {
QTextLayout::FormatRange format = formats.at(i);
const int formatLength = qMin(format.start + format.length, end) - qMax(format.start, start);
if (formatLength > 0) {
@@ -657,7 +714,7 @@ void QQuickTextPrivate::elideFormats(
}
}
-QString QQuickTextPrivate::elidedText(qreal lineWidth, const QTextLine &line, QTextLine *nextLine) const
+QString QQuickTextPrivate::elidedText(qreal lineWidth, const QTextLine &line, const QTextLine *nextLine) const
{
if (nextLine) {
return layout.engine()->elidedText(
@@ -670,7 +727,7 @@ QString QQuickTextPrivate::elidedText(qreal lineWidth, const QTextLine &line, QT
QString elideText = layout.text().mid(line.textStart(), line.textLength());
if (!styledText) {
// QFontMetrics won't help eliding styled text.
- elideText[elideText.length() - 1] = elideChar;
+ elideText[elideText.size() - 1] = elideChar;
// Appending the elide character may push the line over the maximum width
// in which case the elided text will need to be elided.
QFontMetricsF metrics(layout.font());
@@ -717,6 +774,7 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
}
if (lineCount) {
lineCount = 0;
+ q->setFlag(QQuickItem::ItemObservesViewport, false);
emit q->lineCountChanged();
}
@@ -764,11 +822,15 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
const bool pixelSize = font.pixelSize() != -1;
QString layoutText = layout.text();
- int largeFont = pixelSize ? font.pixelSize() : font.pointSize();
- int smallFont = fontSizeMode() != QQuickText::FixedSize
- ? qMin(pixelSize ? minimumPixelSize() : minimumPointSize(), largeFont)
- : largeFont;
- int scaledFontSize = largeFont;
+ const qreal minimumSize = pixelSize
+ ? static_cast<qreal>(minimumPixelSize())
+ : minimumPointSize();
+ qreal largeFont = pixelSize ? font.pixelSize() : font.pointSizeF();
+ qreal smallFont = fontSizeMode() != QQuickText::FixedSize
+ ? qMin<qreal>(minimumSize, largeFont)
+ : largeFont;
+ qreal scaledFontSize = largeFont;
+ const qreal sizeFittingThreshold(0.01);
bool widthChanged = false;
widthExceeded = availableWidth() <= 0 && (singlelineElide || canWrap || horizontalFit);
@@ -797,7 +859,7 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
if (pixelSize)
scaledFont.setPixelSize(scaledFontSize);
else
- scaledFont.setPointSize(scaledFontSize);
+ scaledFont.setPointSizeF(scaledFontSize);
if (layout.font() != scaledFont)
layout.setFont(scaledFont);
}
@@ -809,19 +871,21 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
truncated = false;
elide = false;
int unwrappedLineCount = 1;
- int maxLineCount = maximumLineCount();
+ const int maxLineCount = maximumLineCount();
height = 0;
qreal naturalHeight = 0;
qreal previousHeight = 0;
br = QRectF();
QRectF unelidedRect;
- QTextLine line = layout.createLine();
+ QTextLine line;
for (visibleCount = 1; ; ++visibleCount) {
+ line = layout.createLine();
+
if (noBreakLastLine && visibleCount == maxLineCount)
layout.engine()->option.setWrapMode(QTextOption::WrapAnywhere);
if (customLayout) {
- setupCustomLineGeometry(line, naturalHeight, layoutText.length());
+ setupCustomLineGeometry(line, naturalHeight, layoutText.size());
} else {
setLineGeometry(line, lineWidth, naturalHeight);
}
@@ -843,7 +907,7 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
visibleCount -= 1;
- QTextLine previousLine = layout.lineAt(visibleCount - 1);
+ const QTextLine previousLine = layout.lineAt(visibleCount - 1);
elideText = layoutText.at(line.textStart() - 1) != QChar::LineSeparator
? elidedText(line.width(), previousLine, &line)
: elidedText(line.width(), previousLine);
@@ -854,11 +918,10 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
break;
}
- const QTextLine previousLine = line;
- line = layout.createLine();
- if (!line.isValid()) {
- if (singlelineElide && visibleCount == 1 && previousLine.naturalTextWidth() > previousLine.width()) {
- // Elide a single previousLine of text if its width exceeds the element width.
+ const bool isLastLine = line.textStart() + line.textLength() >= layoutText.size();
+ if (isLastLine) {
+ if (singlelineElide && visibleCount == 1 && line.naturalTextWidth() > line.width()) {
+ // Elide a single previousLine of text if its width exceeds the element width.
elide = true;
widthExceeded = true;
if (eos != -1) // There's an abbreviated string available.
@@ -867,26 +930,25 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
truncated = true;
elideText = layout.engine()->elidedText(
Qt::TextElideMode(elideMode),
- QFixed::fromReal(previousLine.width()),
+ QFixed::fromReal(line.width()),
0,
- previousLine.textStart(),
- previousLine.textLength());
- elideStart = previousLine.textStart();
- elideEnd = elideStart + previousLine.textLength();
+ line.textStart(),
+ line.textLength());
+ elideStart = line.textStart();
+ elideEnd = elideStart + line.textLength();
} else {
br = unelidedRect;
height = naturalHeight;
}
break;
} else {
- const bool wrappedLine = layoutText.at(line.textStart() - 1) != QChar::LineSeparator;
+ const bool wrappedLine = layoutText.at(line.textStart() + line.textLength() - 1) != QChar::LineSeparator;
wrapped |= wrappedLine;
if (!wrappedLine)
++unwrappedLineCount;
- // Stop if the maximum number of lines has been reached and elide the last line
- // if enabled.
+ // Stop if the maximum number of lines has been reached
if (visibleCount == maxLineCount) {
truncated = true;
heightExceeded |= wrapped;
@@ -895,10 +957,12 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
elide = true;
if (eos != -1) // There's an abbreviated string available
break;
+
+ const QTextLine nextLine = layout.createLine();
elideText = wrappedLine
- ? elidedText(previousLine.width(), previousLine, &line)
- : elidedText(previousLine.width(), previousLine);
- elideStart = previousLine.textStart();
+ ? elidedText(line.width(), line, &nextLine)
+ : elidedText(line.width(), line);
+ elideStart = line.textStart();
// elideEnd isn't required for right eliding.
} else {
br = unelidedRect;
@@ -935,8 +999,8 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
// implicit width.
const int eol = line.isValid()
? line.textStart() + line.textLength()
- : layoutText.length();
- if (eol < layoutText.length() && layoutText.at(eol) != QChar::LineSeparator)
+ : layoutText.size();
+ if (eol < layoutText.size() && layoutText.at(eol) != QChar::LineSeparator)
line = layout.createLine();
for (; line.isValid() && unwrappedLineCount <= maxLineCount; ++unwrappedLineCount)
line = layout.createLine();
@@ -967,7 +1031,7 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
const qreal availWidth = availableWidth();
const qreal availHeight = availableHeight();
- lineWidth = q->widthValid() && availWidth > 0 ? availWidth : naturalWidth;
+ lineWidth = q->widthValid() && q->width() > 0 ? availWidth : naturalWidth;
maxHeight = q->heightValid() ? availHeight : FLT_MAX;
// If the width of the item has changed and it's possible the result of wrapping,
@@ -1061,40 +1125,45 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
if (!horizontalFit && !verticalFit)
break;
+ // Can't find a better fit
+ if (qFuzzyCompare(smallFont, largeFont))
+ break;
+
// Try and find a font size that better fits the dimensions of the element.
if (horizontalFit) {
if (unelidedRect.width() > lineWidth || (!verticalFit && wrapped)) {
widthExceeded = true;
- largeFont = scaledFontSize - 1;
- if (smallFont > largeFont)
- break;
+ largeFont = scaledFontSize;
+
scaledFontSize = (smallFont + largeFont) / 2;
- if (pixelSize)
- scaledFont.setPixelSize(scaledFontSize);
- else
- scaledFont.setPointSize(scaledFontSize);
+
continue;
} else if (!verticalFit) {
smallFont = scaledFontSize;
- if (smallFont == largeFont)
+
+ // Check to see if the current scaledFontSize is acceptable
+ if ((largeFont - smallFont) < sizeFittingThreshold)
break;
- scaledFontSize = (smallFont + largeFont + 1) / 2;
+
+ scaledFontSize = (smallFont + largeFont) / 2;
}
}
if (verticalFit) {
if (truncateHeight || unelidedRect.height() > maxHeight) {
heightExceeded = true;
- largeFont = scaledFontSize - 1;
- if (smallFont > largeFont)
- break;
+ largeFont = scaledFontSize;
+
scaledFontSize = (smallFont + largeFont) / 2;
} else {
smallFont = scaledFontSize;
- if (smallFont == largeFont)
+
+ // Check to see if the current scaledFontSize is acceptable
+ if ((largeFont - smallFont) < sizeFittingThreshold)
break;
- scaledFontSize = (smallFont + largeFont + 1) / 2;
+
+ scaledFontSize = (smallFont + largeFont) / 2;
}
}
}
@@ -1128,18 +1197,18 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
QVector<QTextLayout::FormatRange> formats;
switch (elideMode) {
case QQuickText::ElideRight:
- elideFormats(elideStart, elideText.length() - 1, 0, &formats);
+ elideFormats(elideStart, elideText.size() - 1, 0, &formats);
break;
case QQuickText::ElideLeft:
- elideFormats(elideEnd - elideText.length() + 1, elideText.length() - 1, 1, &formats);
+ elideFormats(elideEnd - elideText.size() + 1, elideText.size() - 1, 1, &formats);
break;
case QQuickText::ElideMiddle: {
const int index = elideText.indexOf(elideChar);
if (index != -1) {
elideFormats(elideStart, index, 0, &formats);
elideFormats(
- elideEnd - elideText.length() + index + 1,
- elideText.length() - index - 1,
+ elideEnd - elideText.size() + index + 1,
+ elideText.size() - index - 1,
index + 1,
&formats);
}
@@ -1153,17 +1222,13 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
elideLayout->setFont(layout.font());
elideLayout->setTextOption(layout.textOption());
- if (QQmlDebugTranslationService *service
- = QQmlDebugConnector::service<QQmlDebugTranslationService>()) {
- elideText = service->foundElidedText(q, layoutText, elideText);
- }
elideLayout->setText(elideText);
elideLayout->beginLayout();
QTextLine elidedLine = elideLayout->createLine();
elidedLine.setPosition(QPointF(0, height));
if (customLayout) {
- setupCustomLineGeometry(elidedLine, height, elideText.length(), visibleCount - 1);
+ setupCustomLineGeometry(elidedLine, height, elideText.size(), visibleCount - 1);
} else {
setLineGeometry(elidedLine, lineWidth, height);
}
@@ -1181,8 +1246,8 @@ QRectF QQuickTextPrivate::setupTextLayout(qreal *const baseline)
QTextLine firstLine = visibleCount == 1 && elideLayout
? elideLayout->lineAt(0)
: layout.lineAt(0);
- Q_ASSERT(firstLine.isValid());
- *baseline = firstLine.y() + firstLine.ascent();
+ if (firstLine.isValid())
+ *baseline = firstLine.y() + firstLine.ascent();
if (!customLayout)
br.setHeight(height);
@@ -1217,7 +1282,7 @@ void QQuickTextPrivate::setLineGeometry(QTextLine &line, qreal lineWidth, qreal
QList<QQuickStyledTextImgTag *> imagesInLine;
if (extra.isAllocated()) {
- for (QQuickStyledTextImgTag *image : qAsConst(extra->imgTags)) {
+ for (QQuickStyledTextImgTag *image : std::as_const(extra->imgTags)) {
if (image->position >= line.textStart() &&
image->position < line.textStart() + line.textLength()) {
@@ -1255,7 +1320,7 @@ void QQuickTextPrivate::setLineGeometry(QTextLine &line, qreal lineWidth, qreal
}
}
- for (QQuickStyledTextImgTag *image : qAsConst(imagesInLine)) {
+ for (QQuickStyledTextImgTag *image : std::as_const(imagesInLine)) {
totalLineHeight = qMax(totalLineHeight, textTop + image->pos.y() + image->size.height());
const int leadX = line.cursorToX(image->position);
const int trailX = line.cursorToX(image->position, QTextLine::Trailing);
@@ -1287,16 +1352,33 @@ void QQuickTextPrivate::ensureDoc()
{
if (!extra.isAllocated() || !extra->doc) {
Q_Q(QQuickText);
- extra.value().doc = new QQuickTextDocumentWithImageResources(q);
- extra->doc->setPageSize(QSizeF(0, 0));
- extra->doc->setDocumentMargin(0);
+ extra.value().doc = new QTextDocument(q);
+ auto *doc = extra->doc;
+ extra->imageHandler = new QQuickTextImageHandler(doc);
+ doc->documentLayout()->registerHandler(QTextFormat::ImageObject, extra->imageHandler);
+ doc->setPageSize(QSizeF(0, 0));
+ doc->setDocumentMargin(0);
const QQmlContext *context = qmlContext(q);
- extra->doc->setBaseUrl(context ? context->resolvedUrl(q->baseUrl()) : q->baseUrl());
- qmlobject_connect(extra->doc, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()),
- q, QQuickText, SLOT(q_updateLayout()));
+ doc->setBaseUrl(context ? context->resolvedUrl(q->baseUrl()) : q->baseUrl());
}
}
+void QQuickTextPrivate::updateDocumentText()
+{
+ ensureDoc();
+#if QT_CONFIG(textmarkdownreader)
+ if (markdownText)
+ extra->doc->setMarkdown(text);
+ else
+#endif
+#if QT_CONFIG(texthtmlparser)
+ extra->doc->setHtml(text);
+#else
+ extra->doc->setPlainText(text);
+#endif
+ rightToLeftText = extra->doc->toPlainText().isRightToLeft();
+}
+
/*!
\qmltype Text
\instantiates QQuickText
@@ -1305,8 +1387,8 @@ void QQuickTextPrivate::ensureDoc()
\inherits Item
\brief Specifies how to add formatted text to a scene.
- Text items can display both plain and rich text. For example, red text with
- a specific font and size can be defined like this:
+ Text items can display both plain and rich text. For example, you can define
+ red text with a specific font and size like this:
\qml
Text {
@@ -1317,25 +1399,47 @@ void QQuickTextPrivate::ensureDoc()
}
\endqml
- Rich text is defined using HTML-style markup:
+ Use HTML-style markup or Markdown to define rich text:
+ \if defined(onlinedocs)
+ \tab {build-qt-app}{tab-html}{HTML-style}{checked}
+ \tab {build-qt-app}{tab-md}{Markdown}{}
+ \tabcontent {tab-html}
+ \else
+ \section1 Using HTML-style
+ \endif
\qml
Text {
text: "<b>Hello</b> <i>World!</i>"
}
\endqml
+ \if defined(onlinedocs)
+ \endtabcontent
+ \tabcontent {tab-md}
+ \else
+ \section1 Using Markdown
+ \endif
+ \qml
+ Text {
+ text: "**Hello** *World!*"
+ }
+ \endqml
+ \if defined(onlinedocs)
+ \endtabcontent
+ \endif
\image declarative-text.png
- If height and width are not explicitly set, Text will attempt to determine how
- much room is needed and set it accordingly. Unless \l wrapMode is set, it will always
- prefer width to height (all text will be placed on a single line).
+ If height and width are not explicitly set, Text will try to determine how
+ much room is needed and set it accordingly. Unless \l wrapMode is set, it
+ will always prefer width to height (all text will be placed on a single
+ line).
- The \l elide property can alternatively be used to fit a single line of
- plain text to a set width.
+ To fit a single line of plain text to a set width, you can use the \l elide
+ property.
- Note that the \l{Supported HTML Subset} is limited. Also, if the text contains
- HTML img tags that load remote images, the text is reloaded.
+ Note that the \l{Supported HTML Subset} is limited. Also, if the text
+ contains HTML img tags that load remote images, the text is reloaded.
Text provides read-only text. For editable text, see \l TextEdit.
@@ -1357,13 +1461,18 @@ QQuickText::QQuickText(QQuickTextPrivate &dd, QQuickItem *parent)
QQuickText::~QQuickText()
{
+ Q_D(QQuickText);
+ if (d->extra.isAllocated()) {
+ qDeleteAll(d->extra->pixmapsInProgress);
+ d->extra->pixmapsInProgress.clear();
+ }
}
/*!
\qmlproperty bool QtQuick::Text::clip
This property holds whether the text is clipped.
- Note that if the text does not fit in the bounding rectangle it will be abruptly chopped.
+ Note that if the text does not fit in the bounding rectangle, it will be abruptly chopped.
If you want to display potentially long text in a limited space, you probably want to use \c elide instead.
*/
@@ -1452,7 +1561,8 @@ QQuickText::~QQuickText()
Sets the family name of the font.
- The family name is case insensitive and may optionally include a foundry name, e.g. "Helvetica [Cronyx]".
+ The family name is case insensitive and may optionally include a foundry
+ name, for example "Helvetica [Cronyx]".
If the family is available from more than one foundry and the foundry isn't specified, an arbitrary foundry is chosen.
If the family isn't available a family will be set using the font matching algorithm.
*/
@@ -1478,17 +1588,16 @@ QQuickText::~QQuickText()
The requested weight of the font. The weight requested must be an integer
between 1 and 1000, or one of the predefined values:
- \list
- \li Font.Thin
- \li Font.Light
- \li Font.ExtraLight
- \li Font.Normal - the default
- \li Font.Medium
- \li Font.DemiBold
- \li Font.Bold
- \li Font.ExtraBold
- \li Font.Black
- \endlist
+
+ \value Font.Thin 100
+ \value Font.ExtraLight 200
+ \value Font.Light 300
+ \value Font.Normal 400 (default)
+ \value Font.Medium 500
+ \value Font.DemiBold 600
+ \value Font.Bold 700
+ \value Font.ExtraBold 800
+ \value Font.Black 900
\qml
Text { text: "Hello"; font.weight: Font.DemiBold }
@@ -1552,13 +1661,12 @@ QQuickText::~QQuickText()
Sets the capitalization for the text.
- \list
- \li Font.MixedCase - This is the normal text rendering option where no capitalization change is applied.
- \li Font.AllUppercase - This alters the text to be rendered in all uppercase type.
- \li Font.AllLowercase - This alters the text to be rendered in all lowercase type.
- \li Font.SmallCaps - This alters the text to be rendered in small-caps type.
- \li Font.Capitalize - This alters the text to be rendered with the first character of each word as an uppercase character.
- \endlist
+ \value Font.MixedCase the normal case: no capitalization change is applied
+ \value Font.AllUppercase alters the text to be rendered in all uppercase type
+ \value Font.AllLowercase alters the text to be rendered in all lowercase type
+ \value Font.SmallCaps alters the text to be rendered in small-caps type
+ \value Font.Capitalize alters the text to be rendered with the first character of
+ each word as an uppercase character
\qml
Text { text: "Hello"; font.capitalization: Font.AllLowercase }
@@ -1575,23 +1683,21 @@ QQuickText::~QQuickText()
\note This property only has an effect when used together with render type Text.NativeRendering.
- \list
- \value Font.PreferDefaultHinting - Use the default hinting level for the target platform.
- \value Font.PreferNoHinting - If possible, render text without hinting the outlines
+ \value Font.PreferDefaultHinting Use the default hinting level for the target platform.
+ \value Font.PreferNoHinting If possible, render text without hinting the outlines
of the glyphs. The text layout will be typographically accurate, using the same metrics
- as are used e.g. when printing.
- \value Font.PreferVerticalHinting - If possible, render text with no horizontal hinting,
+ as are used, for example, when printing.
+ \value Font.PreferVerticalHinting If possible, render text with no horizontal hinting,
but align glyphs to the pixel grid in the vertical direction. The text will appear
crisper on displays where the density is too low to give an accurate rendering
of the glyphs. But since the horizontal metrics of the glyphs are unhinted, the text's
layout will be scalable to higher density devices (such as printers) without impacting
details such as line breaks.
- \value Font.PreferFullHinting - If possible, render text with hinting in both horizontal and
+ \value Font.PreferFullHinting If possible, render text with hinting in both horizontal and
vertical directions. The text will be altered to optimize legibility on the target
device, but since the metrics will depend on the target size of the text, the positions
of glyphs, line breaks, and other typographical detail will not scale, meaning that a
text layout may look different on devices with different pixel densities.
- \endlist
\qml
Text { text: "Hello"; renderType: Text.NativeRendering; font.hintingPreference: Font.PreferVerticalHinting }
@@ -1617,7 +1723,7 @@ QQuickText::~QQuickText()
Sometimes, a font will apply complex rules to a set of characters in order to
display them correctly. In some writing systems, such as Brahmic scripts, this is
- required in order for the text to be legible, but in e.g. Latin script, it is merely
+ required in order for the text to be legible, but in for example Latin script, it is merely
a cosmetic feature. Setting the \c preferShaping property to false will disable all
such features when they are not required, which will improve performance in most cases.
@@ -1627,6 +1733,122 @@ QQuickText::~QQuickText()
Text { text: "Some text"; font.preferShaping: false }
\endqml
*/
+
+/*!
+ \qmlproperty object QtQuick::Text::font.variableAxes
+ \since 6.7
+
+//! [qml-font-variable-axes]
+ Applies floating point values to variable axes in variable fonts.
+
+ Variable fonts provide a way to store multiple variations (with different weights, widths
+ or styles) in the same font file. The variations are given as floating point values for
+ a pre-defined set of parameters, called "variable axes". Specific instances are typically
+ given names by the font designer, and, in Qt, these can be selected using setStyleName()
+ just like traditional sub-families.
+
+ In some cases, it is also useful to provide arbitrary values for the different axes. For
+ instance, if a font has a Regular and Bold sub-family, you may want a weight in-between these.
+ You could then manually request this by supplying a custom value for the "wght" axis in the
+ font.
+
+ \qml
+ Text {
+ text: "Foobar"
+ font.family: "MyVariableFont"
+ font.variableAxes: { "wght": (Font.Normal + Font.Bold) / 2.0 }
+ }
+ \endqml
+
+ If the "wght" axis is supported by the font and the given value is within its defined range,
+ a font corresponding to the weight 550.0 will be provided.
+
+ There are a few standard axes than many fonts provide, such as "wght" (weight), "wdth" (width),
+ "ital" (italic) and "opsz" (optical size). They each have indivdual ranges defined in the font
+ itself. For instance, "wght" may span from 100 to 900 (QFont::Thin to QFont::Black) whereas
+ "ital" can span from 0 to 1 (from not italic to fully italic).
+
+ A font may also choose to define custom axes; the only limitation is that the name has to
+ meet the requirements for a QFont::Tag (sequence of four latin-1 characters.)
+
+ By default, no variable axes are set.
+
+ \note In order to use variable axes on Windows, the application has to run with either the
+ FreeType or DirectWrite font databases. See the documentation for
+ QGuiApplication::QGuiApplication() for more information on how to select these technologies.
+
+ \sa QFont::setVariableAxis()
+//! [qml-font-variable-axes]
+*/
+
+
+/*!
+ \qmlproperty object QtQuick::Text::font.features
+ \since 6.6
+
+//! [qml-font-features]
+ Applies integer values to specific OpenType features when shaping the text based on the contents
+ in \a features. This provides advanced access to the font shaping process, and can be used
+ to support font features that are otherwise not covered in the API.
+
+ The font features are represented by a map from four-letter tags to integer values. This integer
+ value passed along with the tag in most cases represents a boolean value: A zero value means the
+ feature is disabled, and a non-zero value means it is enabled. For certain font features,
+ however, it may have other interpretations. For example, when applied to the \c salt feature, the
+ value is an index that specifies the stylistic alternative to use.
+
+ For example, the \c frac font feature will convert diagonal fractions separated with a slash
+ (such as \c 1/2) with a different representation. Typically this will involve baking the full
+ fraction into a single character width (such as \c ½).
+
+ If a font supports the \c frac feature, then it can be enabled in the shaper as in the following
+ code:
+
+ \qml
+ Text {
+ text: "One divided by two is 1/2"
+ font.family: "MyFractionFont"
+ font.features: { "frac": 1 }
+ }
+ \endqml
+
+ Multiple features can be assigned values in the same mapping. For instance,
+ if you would like to also disable kerning for the font, you can explicitly
+ disable this as follows:
+
+ \qml
+ Text {
+ text: "One divided by two is 1/2"
+ font.family: "MyFractionFont"
+ font.features: { "frac": 1, "kern": 0 }
+ }
+ \endqml
+
+ You can also collect the font properties in an object:
+
+ \qml
+ Text {
+ text: "One divided by two is 1/2"
+ font: {
+ family: "MyFractionFont"
+ features: { "frac": 1, "kern": 0 }
+ }
+ }
+ \endqml
+
+ \note By default, Qt will enable and disable certain font features based on other font
+ properties. In particular, the \c kern feature will be enabled/disabled depending on the
+ \l font.kerning property of the QFont. In addition, all ligature features (\c liga, \c clig,
+ \c dlig, \c hlig) will be disabled if a \l font.letterSpacing is set, but only for writing
+ systems where the use of ligature is cosmetic. For writing systems where ligatures are required,
+ the features will remain in their default state. The values set using \c font.features will
+ override the default behavior. If, for instance, \c{"kern"} is set to 1, then kerning will
+ always be enabled, regardless of whether the \l font.kerning property is set to false. Similarly,
+ if it is set to \c 0, it will always be disabled.
+
+ \sa QFont::setFeature()
+//! [qml-font-features]
+*/
QFont QQuickText::font() const
{
Q_D(const QQuickText);
@@ -1727,12 +1949,7 @@ void QQuickText::setText(const QString &n)
d->text = n;
if (isComponentComplete()) {
if (d->richText) {
- d->ensureDoc();
- if (d->markdownText)
- d->extra->doc->setMarkdownText(n);
- else
- d->extra->doc->setText(n);
- d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft();
+ d->updateDocumentText();
} else {
d->clearFormats();
d->rightToLeftText = d->text.isRightToLeft();
@@ -1747,6 +1964,7 @@ void QQuickText::setText(const QString &n)
qDeleteAll(d->extra->imgTags);
d->extra->imgTags.clear();
}
+ setFlag(QQuickItem::ItemObservesViewport, n.size() > QQuickTextPrivate::largeTextSizeThreshold);
d->updateLayout();
setAcceptHoverEvents(d->richText || d->styledText);
emit textChanged(d->text);
@@ -1831,12 +2049,11 @@ void QQuickText::setLinkColor(const QColor &color)
Set an additional text style.
Supported text styles are:
- \list
- \li Text.Normal - the default
- \li Text.Outline
- \li Text.Raised
- \li Text.Sunken
- \endlist
+
+ \value Text.Normal - the default
+ \value Text.Outline
+ \value Text.Raised
+ \value Text.Sunken
\qml
Row {
@@ -2036,12 +2253,17 @@ void QQuickText::setVAlign(VAlignment align)
Set this property to wrap the text to the Text item's width. The text will only
wrap if an explicit width has been set. wrapMode can be one of:
- \list
- \li Text.NoWrap (default) - no wrapping will be performed. If the text contains insufficient newlines, then \l contentWidth will exceed a set width.
- \li Text.WordWrap - wrapping is done on word boundaries only. If a word is too long, \l contentWidth will exceed a set width.
- \li Text.WrapAnywhere - wrapping is done at any point on a line, even if it occurs in the middle of a word.
- \li Text.Wrap - if possible, wrapping occurs at a word boundary; otherwise it will occur at the appropriate point on the line, even in the middle of a word.
- \endlist
+ \value Text.NoWrap
+ (default) no wrapping will be performed. If the text contains
+ insufficient newlines, then \l contentWidth will exceed a set width.
+ \value Text.WordWrap
+ wrapping is done on word boundaries only. If a word is too long,
+ \l contentWidth will exceed a set width.
+ \value Text.WrapAnywhere
+ wrapping is done at any point on a line, even if it occurs in the middle of a word.
+ \value Text.Wrap
+ if possible, wrapping occurs at a word boundary; otherwise it will occur
+ at the appropriate point on the line, even in the middle of a word.
*/
QQuickText::WrapMode QQuickText::wrapMode() const
{
@@ -2147,7 +2369,7 @@ void QQuickText::resetMaximumLineCount()
\l {https://guides.github.com/features/mastering-markdown/}{GitHub}
extensions for tables and task lists (since 5.14)
- If the text format is \c Text.AutoText the Text item
+ If the text format is \c Text.AutoText, the Text item
will automatically determine whether the text should be treated as
styled text. This determination is made using Qt::mightBeRichText(),
which can detect the presence of an HTML tag on the first line of text,
@@ -2171,7 +2393,7 @@ void QQuickText::resetMaximumLineCount()
<img src="" align="top,middle,bottom" width="" height=""> - inline images
<ol type="">, <ul type=""> and <li> - ordered and unordered lists
<pre></pre> - preformatted
- &gt; &lt; &amp;
+ All entities
\endcode
\c Text.StyledText parser is strict, requiring tags to be correctly nested.
@@ -2192,7 +2414,6 @@ void QQuickText::resetMaximumLineCount()
\list
\li code blocks use the \l {QFontDatabase::FixedFont}{default monospace font} but without a surrounding highlight box
\li block quotes are indented, but there is no vertical line alongside the quote
- \li horizontal rules are not rendered
\endlist
*/
QQuickText::TextFormat QQuickText::textFormat() const
@@ -2214,9 +2435,7 @@ void QQuickText::setTextFormat(TextFormat format)
if (isComponentComplete()) {
if (!wasRich && d->richText) {
- d->ensureDoc();
- d->extra->doc->setText(d->text);
- d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft();
+ d->updateDocumentText();
} else {
d->clearFormats();
d->rightToLeftText = d->text.isRightToLeft();
@@ -2240,12 +2459,11 @@ void QQuickText::setTextFormat(TextFormat format)
This property cannot be used with rich text.
Eliding can be:
- \list
- \li Text.ElideNone - the default
- \li Text.ElideLeft
- \li Text.ElideMiddle
- \li Text.ElideRight
- \endlist
+
+ \value Text.ElideNone - the default
+ \value Text.ElideLeft
+ \value Text.ElideMiddle
+ \value Text.ElideRight
If this property is set to Text.ElideRight, it can be used with \l {wrapMode}{wrapped}
text. The text will only elide if \c maximumLineCount, or \c height has been set.
@@ -2279,7 +2497,7 @@ void QQuickText::setElideMode(QQuickText::TextElideMode mode)
/*!
\qmlproperty url QtQuick::Text::baseUrl
- This property specifies a base URL which is used to resolve relative URLs
+ This property specifies a base URL that is used to resolve relative URLs
within the text.
Urls are resolved to be within the same directory as the target of the base
@@ -2342,7 +2560,13 @@ void QQuickText::resetBaseUrl()
setBaseUrl(QUrl());
}
-/*! \internal */
+/*!
+ Returns the extents of the text after layout.
+ If the \l style() is not \c Text.Normal, a margin is added to ensure
+ that the rendering effect will fit within this rectangle.
+
+ \sa contentWidth(), contentHeight(), clipRect()
+*/
QRectF QQuickText::boundingRect() const
{
Q_D(const QQuickText);
@@ -2358,6 +2582,17 @@ QRectF QQuickText::boundingRect() const
return rect;
}
+/*!
+ Returns a rectangular area slightly larger than what is currently visible
+ in \l viewportItem(); otherwise, the rectangle \c (0, 0, width, height).
+ The text will be clipped to fit if \l clip is \c true.
+
+ \note If the \l style is not \c Text.Normal, the clip rectangle is adjusted
+ to be slightly larger, to limit clipping of the outline effect at the edges.
+ But it still looks better to set \l clip to \c false in that case.
+
+ \sa contentWidth(), contentHeight(), boundingRect()
+*/
QRectF QQuickText::clipRect() const
{
Q_D(const QQuickText);
@@ -2412,26 +2647,30 @@ void QQuickText::geometryChange(const QRectF &newGeometry, const QRectF &oldGeom
goto geomChangeDone;
if (!(widthChanged || widthMaximum) && !d->isLineLaidOutConnected()) { // only height has changed
- if (newGeometry.height() > oldGeometry.height()) {
- if (!d->heightExceeded && !qFuzzyIsNull(oldGeometry.height())) {
- // Height is adequate and growing, and it wasn't 0 previously.
- goto geomChangeDone;
- }
- if (d->lineCount == d->maximumLineCount()) // Reached maximum line and height is growing.
- goto geomChangeDone;
- } else if (newGeometry.height() < oldGeometry.height()) {
- if (d->lineCount < 2 && !verticalScale && newGeometry.height() > 0) // A single line won't be truncated until the text is 0 height.
- goto geomChangeDone;
-
- if (!verticalScale // no scaling, no eliding, and either unwrapped, or no maximum line count.
- && d->elideMode != QQuickText::ElideRight
- && !(d->maximumLineCountValid && d->widthExceeded)) {
- goto geomChangeDone;
+ if (!verticalPositionChanged) {
+ if (newGeometry.height() > oldGeometry.height()) {
+ if (!d->heightExceeded && !qFuzzyIsNull(oldGeometry.height())) {
+ // Height is adequate and growing, and it wasn't 0 previously.
+ goto geomChangeDone;
+ }
+ if (d->lineCount == d->maximumLineCount()) // Reached maximum line and height is growing.
+ goto geomChangeDone;
+ } else if (newGeometry.height() < oldGeometry.height()) {
+ if (d->lineCount < 2 && !verticalScale && newGeometry.height() > 0) // A single line won't be truncated until the text is 0 height.
+ goto geomChangeDone;
+
+ if (!verticalScale // no scaling, no eliding, and either unwrapped, or no maximum line count.
+ && d->elideMode != QQuickText::ElideRight
+ && !(d->maximumLineCountValid && d->widthExceeded)) {
+ goto geomChangeDone;
+ }
}
}
} else if (!heightChanged && widthMaximum) {
- if (!qFuzzyIsNull(oldGeometry.width())) {
+ if (oldGeometry.width() > 0) {
// no change to height, width is adequate and wasn't 0 before
+ // (old width could also be negative if it was 0 and the margins
+ // were set)
goto geomChangeDone;
}
}
@@ -2476,43 +2715,46 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data
const qreal dy = QQuickTextUtil::alignedY(d->layedOutTextRect.height() + d->lineHeightOffset(), d->availableHeight(), d->vAlign) + topPadding();
- QQuickTextNode *node = nullptr;
+ QSGInternalTextNode *node = nullptr;
if (!oldNode)
- node = new QQuickTextNode(this);
+ node = d->sceneGraphContext()->createInternalTextNode(d->sceneGraphRenderContext());
else
- node = static_cast<QQuickTextNode *>(oldNode);
+ node = static_cast<QSGInternalTextNode *>(oldNode);
- node->setUseNativeRenderer(d->renderType == NativeRendering);
+ node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);
+
+ node->setTextStyle(QSGTextNode::TextStyle(d->style));
+ node->setRenderType(QSGTextNode::RenderType(d->renderType));
node->setRenderTypeQuality(d->renderTypeQuality());
- node->deleteContent();
+ node->clear();
node->setMatrix(QMatrix4x4());
- const QColor color = QColor::fromRgba(d->color);
- const QColor styleColor = QColor::fromRgba(d->styleColor);
- const QColor linkColor = QColor::fromRgba(d->linkColor);
+ node->setColor(QColor::fromRgba(d->color));
+ node->setStyleColor(QColor::fromRgba(d->styleColor));
+ node->setLinkColor(QColor::fromRgba(d->linkColor));
if (d->richText) {
+ node->setViewport(clipRect());
const qreal dx = QQuickTextUtil::alignedX(d->layedOutTextRect.width(), d->availableWidth(), effectiveHAlign()) + leftPadding();
d->ensureDoc();
- node->addTextDocument(QPointF(dx, dy), d->extra->doc, color, d->style, styleColor, linkColor);
+ node->addTextDocument(QPointF(dx, dy), d->extra->doc);
} else if (d->layedOutTextRect.width() > 0) {
+ if (flags().testFlag(ItemObservesViewport))
+ node->setViewport(clipRect());
+ else
+ node->setViewport(QRectF{});
const qreal dx = QQuickTextUtil::alignedX(d->lineWidth, d->availableWidth(), effectiveHAlign()) + leftPadding();
int unelidedLineCount = d->lineCount;
if (d->elideLayout)
unelidedLineCount -= 1;
- if (unelidedLineCount > 0) {
- node->addTextLayout(
- QPointF(dx, dy),
- &d->layout,
- color, d->style, styleColor, linkColor,
- QColor(), QColor(), -1, -1,
- 0, unelidedLineCount);
- }
+ if (unelidedLineCount > 0)
+ node->addTextLayout(QPointF(dx, dy), &d->layout, -1, -1,0, unelidedLineCount);
+
if (d->elideLayout)
- node->addTextLayout(QPointF(dx, dy), d->elideLayout, color, d->style, styleColor, linkColor);
+ node->addTextLayout(QPointF(dx, dy), d->elideLayout);
if (d->extra.isAllocated()) {
- for (QQuickStyledTextImgTag *img : qAsConst(d->extra->visibleImgTags)) {
+ for (QQuickStyledTextImgTag *img : std::as_const(d->extra->visibleImgTags)) {
QQuickPixmap *pix = img->pix;
if (pix && pix->isReady())
node->addImage(QRectF(img->pos.x() + dx, img->pos.y() + dy, pix->width(), pix->height()), pix->image());
@@ -2530,6 +2772,11 @@ QSGNode *QQuickText::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data
void QQuickText::updatePolish()
{
Q_D(QQuickText);
+ const bool clipNodeChanged =
+ d->componentComplete && d->clipNode() && d->clipNode()->rect() != clipRect();
+ if (clipNodeChanged)
+ d->dirty(QQuickItemPrivate::Clip);
+
// If the fonts used for rendering are different from the ones used in the GUI thread,
// it means we will get warnings and corrupted text. If this case is detected, we need
// to update the text layout before creating the scenegraph nodes.
@@ -2547,7 +2794,7 @@ void QQuickText::updatePolish()
\qmlproperty real QtQuick::Text::contentWidth
Returns the width of the text, including width past the width
- which is covered due to insufficient wrapping if WrapMode is set.
+ that is covered due to insufficient wrapping if WrapMode is set.
*/
qreal QQuickText::contentWidth() const
{
@@ -2559,7 +2806,7 @@ qreal QQuickText::contentWidth() const
\qmlproperty real QtQuick::Text::contentHeight
Returns the height of the text, including height past the height
- which is covered due to there being more text than fits in the set height.
+ that is covered due to there being more text than fits in the set height.
*/
qreal QQuickText::contentHeight() const
{
@@ -2602,11 +2849,9 @@ void QQuickText::setLineHeight(qreal lineHeight)
This property determines how the line height is specified.
The possible values are:
- \list
- \li Text.ProportionalHeight (default) - this sets the spacing proportional to the
- line (as a multiplier). For example, set to 2 for double spacing.
- \li Text.FixedHeight - this sets the line height to a fixed line height (in pixels).
- \endlist
+ \value Text.ProportionalHeight (default) sets the spacing proportional to the line
+ (as a multiplier). For example, set to 2 for double spacing.
+ \value Text.FixedHeight sets the line height to a fixed line height (in pixels).
*/
QQuickText::LineHeightMode QQuickText::lineHeightMode() const
{
@@ -2634,16 +2879,16 @@ void QQuickText::setLineHeightMode(LineHeightMode mode)
This property specifies how the font size of the displayed text is determined.
The possible values are:
- \list
- \li Text.FixedSize (default) - The size specified by \l font.pixelSize
- or \l font.pointSize is used.
- \li Text.HorizontalFit - The largest size up to the size specified that fits
- within the width of the item without wrapping is used.
- \li Text.VerticalFit - The largest size up to the size specified that fits
- the height of the item is used.
- \li Text.Fit - The largest size up to the size specified that fits within the
- width and height of the item is used.
- \endlist
+ \value Text.FixedSize
+ (default) The size specified by \l font.pixelSize or \l font.pointSize is used.
+ \value Text.HorizontalFit
+ The largest size up to the size specified that fits within the width of the item
+ without wrapping is used.
+ \value Text.VerticalFit
+ The largest size up to the size specified that fits the height of the item is used.
+ \value Text.Fit
+ The largest size up to the size specified that fits within the width and height
+ of the item is used.
The font size of fitted text has a minimum bound specified by the
minimumPointSize or minimumPixelSize property and maximum bound specified
@@ -2748,8 +2993,8 @@ void QQuickText::setMinimumPointSize(int size)
int QQuickText::resourcesLoading() const
{
Q_D(const QQuickText);
- if (d->richText && d->extra.isAllocated() && d->extra->doc)
- return d->extra->doc->resourcesLoading();
+ if (d->richText && d->extra.isAllocated())
+ return d->extra->pixmapsInProgress.size();
return 0;
}
@@ -2759,12 +3004,7 @@ void QQuickText::componentComplete()
Q_D(QQuickText);
if (d->updateOnComponentComplete) {
if (d->richText) {
- d->ensureDoc();
- if (d->markdownText)
- d->extra->doc->setMarkdownText(d->text);
- else
- d->extra->doc->setText(d->text);
- d->rightToLeftText = d->extra->doc->toPlainText().isRightToLeft();
+ d->updateDocumentText();
} else {
d->rightToLeftText = d->text.isRightToLeft();
}
@@ -2867,6 +3107,38 @@ bool QQuickTextPrivate::isLinkHoveredConnected()
IS_SIGNAL_CONNECTED(q, QQuickText, linkHovered, (const QString &));
}
+static void getLinks_helper(const QTextLayout *layout, QVector<QQuickTextPrivate::LinkDesc> *links)
+{
+ for (const QTextLayout::FormatRange &formatRange : layout->formats()) {
+ if (formatRange.format.isAnchor()) {
+ const int start = formatRange.start;
+ const int len = formatRange.length;
+ QTextLine line = layout->lineForTextPosition(start);
+ QRectF r;
+ r.setTop(line.y());
+ r.setLeft(line.cursorToX(start, QTextLine::Leading));
+ r.setHeight(line.height());
+ r.setRight(line.cursorToX(start + len, QTextLine::Trailing));
+ // ### anchorNames() is empty?! Not sure why this doesn't work
+ // QString anchorName = formatRange.format.anchorNames().value(0); //### pick the first?
+ // Therefore, we resort to QString::mid()
+ QString anchorName = layout->text().mid(start, len);
+ const QString anchorHref = formatRange.format.anchorHref();
+ if (anchorName.isEmpty())
+ anchorName = anchorHref;
+ links->append( { anchorName, anchorHref, start, start + len, r.toRect()} );
+ }
+ }
+}
+
+QVector<QQuickTextPrivate::LinkDesc> QQuickTextPrivate::getLinks() const
+{
+ QVector<QQuickTextPrivate::LinkDesc> links;
+ getLinks_helper(&layout, &links);
+ return links;
+}
+
+
/*!
\qmlsignal QtQuick::Text::linkHovered(string link)
\since 5.2
@@ -2920,7 +3192,7 @@ void QQuickTextPrivate::processHoverEvent(QHoverEvent *event)
emit q->linkHovered(extra->hoveredLink);
}
}
- event->setAccepted(!link.isEmpty());
+ event->ignore();
}
void QQuickText::hoverEnterEvent(QHoverEvent *event)
@@ -2941,8 +3213,27 @@ void QQuickText::hoverLeaveEvent(QHoverEvent *event)
d->processHoverEvent(event);
}
+void QQuickText::invalidate()
+{
+ Q_D(QQuickText);
+ d->textHasChanged = true;
+ QMetaObject::invokeMethod(this,[&]{q_updateLayout();});
+}
+
+bool QQuickTextPrivate::transformChanged(QQuickItem *transformedItem)
+{
+ // If there's a lot of text, we may need QQuickText::updatePaintNode() to call
+ // QSGInternalTextNode::addTextLayout() again to populate a different range of lines
+ if (flags & QQuickItem::ItemObservesViewport) {
+ updateType = UpdatePaintNode;
+ dirty(QQuickItemPrivate::Content);
+ }
+ return QQuickImplicitSizeItemPrivate::transformChanged(transformedItem);
+}
+
/*!
\qmlproperty int QtQuick::Text::renderTypeQuality
+ \since 6.0
Override the default rendering type quality for this component. This is a low-level
customization which can be ignored in most cases. It currently only has an effect
@@ -2956,13 +3247,11 @@ void QQuickText::hoverLeaveEvent(QHoverEvent *event)
The \c renderTypeQuality may be any integer over 0, or one of the following
predefined values
- \list
- \li Text.DefaultRenderTypeQuality (default) = -1
- \li Text.LowRenderTypeQuality = 26
- \li Text.NormalRenderTypeQuality = 52
- \li Text.HighRenderTypeQuality = 104
- \li Text.VeryHighRenderTypeQuality = 208
- \endlist
+ \value Text.DefaultRenderTypeQuality -1 (default)
+ \value Text.LowRenderTypeQuality 26
+ \value Text.NormalRenderTypeQuality 52
+ \value Text.HighRenderTypeQuality 104
+ \value Text.VeryHighRenderTypeQuality 208
*/
int QQuickText::renderTypeQuality() const
{
@@ -2991,16 +3280,23 @@ void QQuickText::setRenderTypeQuality(int renderTypeQuality)
Override the default rendering type for this component.
Supported render types are:
- \list
- \li Text.QtRendering
- \li Text.NativeRendering
- \endlist
- Select Text.NativeRendering if you prefer text to look native on the target platform and do
+ \value Text.QtRendering Text is rendered using a scalable distance field for each glyph.
+ \value Text.NativeRendering Text is rendered using a platform-specific technique.
+ \value Text.CurveRendering Text is rendered using a curve rasterizer running directly on the
+ graphics hardware. (Introduced in Qt 6.7.0.)
+
+ Select \c Text.NativeRendering if you prefer text to look native on the target platform and do
not require advanced features such as transformation of the text. Using such features in
combination with the NativeRendering render type will lend poor and sometimes pixelated
results.
+ Both \c Text.QtRendering and \c Text.CurveRendering are hardware-accelerated techniques.
+ \c QtRendering is the faster of the two, but uses more memory and will exhibit rendering
+ artifacts at large sizes. \c CurveRendering should be considered as an alternative in cases
+ where \c QtRendering does not give good visual results or where reducing graphics memory
+ consumption is a priority.
+
The default rendering type is determined by \l QQuickWindow::textRenderType().
*/
QQuickText::RenderType QQuickText::renderType() const
@@ -3256,7 +3552,7 @@ void QQuickText::resetBottomPadding()
*/
/*!
- \qmlproperty string QtQuick::Text::fontInfo.pixelSize
+ \qmlproperty int QtQuick::Text::fontInfo.pixelSize
\since 5.9
The pixel size of the font info that has been resolved for the current font
@@ -3293,7 +3589,7 @@ QJSValue QQuickText::fontInfo() const
in a text flow.
Note that the advance can be negative if the text flows from
- the right to the left.
+ right to left.
*/
QSizeF QQuickText::advance() const
{