aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items/qquicktextnodeengine.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/items/qquicktextnodeengine.cpp')
-rw-r--r--src/quick/items/qquicktextnodeengine.cpp212
1 files changed, 121 insertions, 91 deletions
diff --git a/src/quick/items/qquicktextnodeengine.cpp b/src/quick/items/qquicktextnodeengine.cpp
index 36fc168ec2..d6d31b4621 100644
--- a/src/quick/items/qquicktextnodeengine.cpp
+++ b/src/quick/items/qquicktextnodeengine.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 "qquicktextnodeengine_p.h"
@@ -49,11 +13,12 @@
#include <QtGui/qtextlist.h>
#include <private/qquicktext_p.h>
-#include <private/qquicktextdocument_p.h>
#include <private/qtextdocumentlayout_p.h>
#include <private/qtextimagehandler_p.h>
#include <private/qrawfont_p.h>
#include <private/qglyphrun_p.h>
+#include <private/qquickitem_p.h>
+#include <private/qsgdistancefieldglyphnode_p.h>
QT_BEGIN_NAMESPACE
@@ -70,7 +35,7 @@ QQuickTextNodeEngine::BinaryTreeNode::BinaryTreeNode(const QGlyphRun &g,
const QRectF &brect,
const Decorations &decs,
const QColor &c,
- const QColor &bc,
+ const QColor &bc, const QColor &dc,
const QPointF &pos, qreal a)
: glyphRun(g)
, boundingRect(brect)
@@ -79,6 +44,7 @@ QQuickTextNodeEngine::BinaryTreeNode::BinaryTreeNode(const QGlyphRun &g,
, decorations(decs)
, color(c)
, backgroundColor(bc)
+ , decorationColor(dc)
, position(pos)
, ascent(a)
, leftChildIndex(-1)
@@ -91,7 +57,7 @@ QQuickTextNodeEngine::BinaryTreeNode::BinaryTreeNode(const QGlyphRun &g,
void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode, 16> *binaryTree, const QGlyphRun &glyphRun, SelectionState selectionState,
Decorations decorations, const QColor &textColor,
- const QColor &backgroundColor, const QPointF &position)
+ const QColor &backgroundColor, const QColor &decorationColor, const QPointF &position)
{
QRectF searchRect = glyphRun.boundingRect();
searchRect.translate(position);
@@ -111,6 +77,7 @@ void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode
decorations,
textColor,
backgroundColor,
+ decorationColor,
position,
ascent));
}
@@ -206,10 +173,7 @@ void QQuickTextNodeEngine::addTextDecorations(const QVarLengthArray<TextDecorati
{
QRectF &rect = textDecoration.rect;
- rect.setY(qRound(rect.y()
- + m_currentLine.ascent()
- + (m_currentLine.leadingIncluded() ? m_currentLine.leading() : qreal(0.0f))
- + offset));
+ rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
rect.setHeight(thickness);
}
@@ -251,6 +215,7 @@ void QQuickTextNodeEngine::processCurrentLine()
QColor lastColor;
QColor lastBackgroundColor;
+ QColor lastDecorationColor;
QVarLengthArray<TextDecoration> pendingUnderlines;
QVarLengthArray<TextDecoration> pendingOverlines;
@@ -265,11 +230,10 @@ void QQuickTextNodeEngine::processCurrentLine()
Q_ASSERT(sortedIndex < m_currentLineTree.size());
node = m_currentLineTree.data() + sortedIndex;
+ if (i == 0)
+ currentSelectionState = node->selectionState;
}
- if (i == 0)
- currentSelectionState = node->selectionState;
-
// Update decorations
if (currentDecorations != Decoration::NoDecoration) {
decorationRect.setY(m_position.y() + m_currentLine.y());
@@ -279,6 +243,12 @@ void QQuickTextNodeEngine::processCurrentLine()
decorationRect.setRight(node->boundingRect.left());
TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
+ if (lastDecorationColor.isValid() &&
+ (currentDecorations.testFlag(Decoration::Underline) ||
+ currentDecorations.testFlag(Decoration::Overline) ||
+ currentDecorations.testFlag(Decoration::StrikeOut)))
+ textDecoration.color = lastDecorationColor;
+
if (currentDecorations & Decoration::Underline)
pendingUnderlines.append(textDecoration);
@@ -397,6 +367,7 @@ void QQuickTextNodeEngine::processCurrentLine()
currentDecorations = node->decorations;
lastColor = node->color;
lastBackgroundColor = node->backgroundColor;
+ lastDecorationColor = node->decorationColor;
m_processedNodes.append(*node);
}
}
@@ -456,15 +427,8 @@ void QQuickTextNodeEngine::addTextObject(const QTextBlock &block, const QPointF
if (format.objectType() == QTextFormat::ImageObject) {
QTextImageFormat imageFormat = format.toImageFormat();
- if (QQuickTextDocumentWithImageResources *imageDoc = qobject_cast<QQuickTextDocumentWithImageResources *>(textDocument)) {
- image = imageDoc->image(imageFormat);
-
- if (image.isNull())
- return;
- } else {
- QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
- image = imageHandler->image(textDocument, imageFormat);
- }
+ QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
+ image = imageHandler->image(textDocument, imageFormat);
}
if (image.isNull()) {
@@ -476,18 +440,20 @@ void QQuickTextNodeEngine::addTextObject(const QTextBlock &block, const QPointF
}
}
+ // Use https://developer.mozilla.org/de/docs/Web/CSS/vertical-align as a reference
+ // The top/bottom positions are supposed to be higher/lower than the text and reference
+ // the line height, not the text height (using QFontMetrics)
qreal ascent;
- QTextLine line = block.layout()->lineForTextPosition(pos);
+ QTextLine line = block.layout()->lineForTextPosition(pos - block.position());
switch (format.verticalAlignment())
{
case QTextCharFormat::AlignTop:
ascent = line.ascent();
break;
- case QTextCharFormat::AlignMiddle: {
- QFontMetrics m(format.font());
- ascent = (size.height() - m.xHeight()) / 2;
+ case QTextCharFormat::AlignMiddle:
+ // Middlepoint of line (height - descent) + Half object height
+ ascent = (line.ascent() + line.descent()) / 2 - line.descent() + size.height() / 2;
break;
- }
case QTextCharFormat::AlignBottom:
ascent = size.height() - line.descent();
break;
@@ -508,6 +474,7 @@ void QQuickTextNodeEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
Decoration::NoDecoration,
m_textColor,
m_backgroundColor,
+ m_decorationColor,
m_position);
}
@@ -520,6 +487,7 @@ void QQuickTextNodeEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
Decoration::NoDecoration,
m_textColor,
m_backgroundColor,
+ m_decorationColor,
m_position);
m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
}
@@ -532,12 +500,12 @@ void QQuickTextNodeEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout:
int remainingLength = end - start;
for (int j=0; j<ranges.size(); ++j) {
const QTextLayout::FormatRange &range = ranges.at(j);
- if (range.start + range.length >= currentPosition
+ if (range.start + range.length > currentPosition
&& range.start < currentPosition + remainingLength) {
if (range.start > currentPosition) {
addGlyphsInRange(currentPosition, range.start - currentPosition,
- QColor(), QColor(), selectionStart, selectionEnd);
+ QColor(), QColor(), QColor(), selectionStart, selectionEnd);
}
int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
QColor rangeColor;
@@ -549,8 +517,12 @@ void QQuickTextNodeEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout:
? range.format.background().color()
: QColor();
+ QColor rangeDecorationColor = range.format.hasProperty(QTextFormat::TextUnderlineColor)
+ ? range.format.underlineColor()
+ : QColor();
+
addGlyphsInRange(range.start, rangeEnd - range.start,
- rangeColor, rangeBackgroundColor,
+ rangeColor, rangeBackgroundColor, rangeDecorationColor,
selectionStart, selectionEnd);
currentPosition = range.start + range.length;
@@ -562,14 +534,14 @@ void QQuickTextNodeEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout:
}
if (remainingLength > 0) {
- addGlyphsInRange(currentPosition, remainingLength, QColor(), QColor(),
+ addGlyphsInRange(currentPosition, remainingLength, QColor(), QColor(), QColor(),
selectionStart, selectionEnd);
}
}
void QQuickTextNodeEngine::addGlyphsInRange(int rangeStart, int rangeLength,
- const QColor &color, const QColor &backgroundColor,
+ const QColor &color, const QColor &backgroundColor, const QColor &decorationColor,
int selectionStart, int selectionEnd)
{
QColor oldColor;
@@ -584,6 +556,12 @@ void QQuickTextNodeEngine::addGlyphsInRange(int rangeStart, int rangeLength,
m_backgroundColor = backgroundColor;
}
+ QColor oldDecorationColor = m_decorationColor;
+ if (decorationColor.isValid()) {
+ oldDecorationColor = m_decorationColor;
+ m_decorationColor = decorationColor;
+ }
+
bool hasSelection = selectionEnd >= 0
&& selectionStart <= selectionEnd;
@@ -627,6 +605,9 @@ void QQuickTextNodeEngine::addGlyphsInRange(int rangeStart, int rangeLength,
}
}
+ if (decorationColor.isValid())
+ m_decorationColor = oldDecorationColor;
+
if (backgroundColor.isValid())
m_backgroundColor = oldBackgroundColor;
@@ -638,7 +619,7 @@ void QQuickTextNodeEngine::addBorder(const QRectF &rect, qreal border,
QTextFrameFormat::BorderStyle borderStyle,
const QBrush &borderBrush)
{
- QColor color = borderBrush.color();
+ const QColor &color = borderBrush.color();
// Currently we don't support other styles than solid
Q_UNUSED(borderStyle);
@@ -678,9 +659,14 @@ void QQuickTextNodeEngine::addFrameDecorations(QTextDocument *document, QTextFra
if (borderStyle == QTextFrameFormat::BorderStyle_None)
return;
- addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
- -frameFormat.rightMargin(), -frameFormat.bottomMargin()),
- borderWidth, borderStyle, borderBrush);
+ const auto collapsed = table->format().borderCollapse();
+
+ if (!collapsed) {
+ addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
+ -frameFormat.rightMargin() - borderWidth,
+ -frameFormat.bottomMargin() - borderWidth),
+ borderWidth, borderStyle, borderBrush);
+ }
if (table != nullptr) {
int rows = table->rows();
int columns = table->columns();
@@ -690,18 +676,16 @@ void QQuickTextNodeEngine::addFrameDecorations(QTextDocument *document, QTextFra
QTextTableCell cell = table->cellAt(row, column);
QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
- addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth,
+ addBorder(cellRect.adjusted(-borderWidth, -borderWidth, collapsed ? -borderWidth : 0, collapsed ? -borderWidth : 0), borderWidth,
borderStyle, borderBrush);
}
}
}
}
-uint qHash(const QQuickTextNodeEngine::BinaryTreeNodeKey &key)
+size_t qHash(const QQuickTextNodeEngine::BinaryTreeNodeKey &key, size_t seed = 0)
{
- // Just use the default hash for pairs
- return qHash(qMakePair(key.fontEngine, qMakePair(key.clipNode,
- qMakePair(key.color, key.selectionState))));
+ return qHashMulti(seed, key.fontEngine, key.clipNode, key.color, key.selectionState);
}
void QQuickTextNodeEngine::mergeProcessedNodes(QList<BinaryTreeNode *> *regularNodes,
@@ -713,6 +697,9 @@ void QQuickTextNodeEngine::mergeProcessedNodes(QList<BinaryTreeNode *> *regularN
BinaryTreeNode *node = m_processedNodes.data() + i;
if (node->image.isNull()) {
+ if (node->glyphRun.isEmpty())
+ continue;
+
BinaryTreeNodeKey key(node);
QList<BinaryTreeNode *> &nodes = map[key];
@@ -767,7 +754,7 @@ void QQuickTextNodeEngine::mergeProcessedNodes(QList<BinaryTreeNode *> *regularN
}
}
-void QQuickTextNodeEngine::addToSceneGraph(QQuickTextNode *parentNode,
+void QQuickTextNodeEngine::addToSceneGraph(QSGInternalTextNode *parentNode,
QQuickText::TextStyle style,
const QColor &styleColor)
{
@@ -781,8 +768,8 @@ void QQuickTextNodeEngine::addToSceneGraph(QQuickTextNode *parentNode,
for (int i = 0; i < m_backgrounds.size(); ++i) {
const QRectF &rect = m_backgrounds.at(i).first;
const QColor &color = m_backgrounds.at(i).second;
-
- parentNode->addRectangleNode(rect, color);
+ if (color.alpha() != 0)
+ parentNode->addRectangleNode(rect, color);
}
// Add all text with unselected color first
@@ -800,8 +787,8 @@ void QQuickTextNodeEngine::addToSceneGraph(QQuickTextNode *parentNode,
// Then, prepend all selection rectangles to the tree
for (int i = 0; i < m_selectionRects.size(); ++i) {
const QRectF &rect = m_selectionRects.at(i);
-
- parentNode->addRectangleNode(rect, m_selectionColor);
+ if (m_selectionColor.alpha() != 0)
+ parentNode->addRectangleNode(rect, m_selectionColor);
}
// Add decorations for each node to the tree.
@@ -812,7 +799,7 @@ void QQuickTextNodeEngine::addToSceneGraph(QQuickTextNode *parentNode,
? m_selectedTextColor
: textDecoration.color;
- parentNode->addRectangleNode(textDecoration.rect, color);
+ parentNode->addDecorationNode(textDecoration.rect, color);
}
// Finally add the selected text on top of everything
@@ -954,11 +941,21 @@ void QQuickTextNodeEngine::mergeFormats(QTextLayout *textLayout, QVarLengthArray
}
-void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QTextBlock &block, const QPointF &position, const QColor &textColor, const QColor &anchorColor, int selectionStart, int selectionEnd)
+/*!
+ \internal
+ Adds the \a block from the \a textDocument at \a position if its
+ \l {QAbstractTextDocumentLayout::blockBoundingRect()}{bounding rect}
+ intersects the \a viewport, or if \c viewport is not valid
+ (i.e. use a default-constructed QRectF to skip the viewport check).
+
+ \sa QQuickItem::clipRect()
+ */
+void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QTextBlock &block, const QPointF &position,
+ const QColor &textColor, const QColor &anchorColor, int selectionStart, int selectionEnd, const QRectF &viewport)
{
Q_ASSERT(textDocument);
#if QT_CONFIG(im)
- int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0;
+ int preeditLength = block.isValid() ? block.layout()->preeditAreaText().size() : 0;
int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
#endif
@@ -969,6 +966,11 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText
const QTextCharFormat charFormat = block.charFormat();
const QRectF blockBoundingRect = textDocument->documentLayout()->blockBoundingRect(block).translated(position);
+ if (viewport.isValid()) {
+ if (!blockBoundingRect.intersects(viewport))
+ return;
+ qCDebug(lcSgText) << "adding block with length" << block.length() << ':' << blockBoundingRect << "in viewport" << viewport;
+ }
if (charFormat.background().style() != Qt::NoBrush)
m_backgrounds.append(qMakePair(blockBoundingRect, charFormat.background().color()));
@@ -1011,8 +1013,19 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText
break;
};
- QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height());
- qreal xoff = fontMetrics.width(QLatin1Char(' '));
+ switch (block.blockFormat().marker()) {
+ case QTextBlockFormat::MarkerType::Checked:
+ listItemBullet = QChar(0x2612); // Checked checkbox
+ break;
+ case QTextBlockFormat::MarkerType::Unchecked:
+ listItemBullet = QChar(0x2610); // Unchecked checkbox
+ break;
+ case QTextBlockFormat::MarkerType::NoMarker:
+ break;
+ }
+
+ QSizeF size(fontMetrics.horizontalAdvance(listItemBullet), fontMetrics.height());
+ qreal xoff = fontMetrics.horizontalAdvance(QLatin1Char(' '));
if (block.textDirection() == Qt::LeftToRight)
xoff = -xoff - size.width();
setPosition(pos + QPointF(xoff, 0));
@@ -1025,6 +1038,12 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText
line.setPosition(QPointF(0, 0));
layout.endLayout();
+ // set the color for the bullets, instead of using the previous QTextBlock's color.
+ if (charFormat.foreground().style() == Qt::NoBrush)
+ setTextColor(textColor);
+ else
+ setTextColor(charFormat.foreground().color());
+
QList<QGlyphRun> glyphRuns = layout.glyphRuns();
for (int i=0; i<glyphRuns.size(); ++i)
addUnselectedGlyphs(glyphRuns.at(i));
@@ -1053,9 +1072,9 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText
else
setPosition(blockBoundingRect.topLeft());
- if (text.contains(QChar::ObjectReplacementCharacter)) {
+ if (text.contains(QChar::ObjectReplacementCharacter) && charFormat.objectType() != QTextFormat::NoObject) {
QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
- if (frame && frame->frameFormat().position() == QTextFrameFormat::InFlow) {
+ if (!frame || frame->frameFormat().position() == QTextFrameFormat::InFlow) {
int blockRelativePosition = textPos - block.position();
QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
if (!currentLine().isValid()
@@ -1064,14 +1083,14 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText
}
QQuickTextNodeEngine::SelectionState selectionState =
- (selectionStart < textPos + text.length()
+ (selectionStart < textPos + text.size()
&& selectionEnd >= textPos)
? QQuickTextNodeEngine::Selected
: QQuickTextNodeEngine::Unselected;
addTextObject(block, QPointF(), charFormat, selectionState, textDocument, textPos);
}
- textPos += text.length();
+ textPos += text.size();
} else {
if (charFormat.foreground().style() != Qt::NoBrush)
setTextColor(charFormat.foreground().color());
@@ -1088,7 +1107,7 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText
fragmentEnd += preeditLength;
}
#endif
- if (charFormat.background().style() != Qt::NoBrush) {
+ if (charFormat.background().style() != Qt::NoBrush || charFormat.hasProperty(QTextFormat::TextUnderlineColor)) {
QTextLayout::FormatRange additionalFormat;
additionalFormat.start = textPos - block.position();
additionalFormat.length = fragmentEnd - textPos;
@@ -1118,6 +1137,17 @@ void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QText
}
#endif
+ // Add block decorations (so far only horizontal rules)
+ if (block.blockFormat().hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
+ auto ruleLength = qvariant_cast<QTextLength>(block.blockFormat().property(QTextFormat::BlockTrailingHorizontalRulerWidth));
+ QRectF ruleRect(0, 0, ruleLength.value(blockBoundingRect.width()), 1);
+ ruleRect.moveCenter(blockBoundingRect.center());
+ const QColor ruleColor = block.blockFormat().hasProperty(QTextFormat::BackgroundBrush)
+ ? qvariant_cast<QBrush>(block.blockFormat().property(QTextFormat::BackgroundBrush)).color()
+ : m_textColor;
+ m_lines.append(TextDecoration(QQuickTextNodeEngine::Unselected, ruleRect, ruleColor));
+ }
+
setCurrentLine(QTextLine()); // Reset current line because the text layout changed
m_hasContents = true;
}