diff options
Diffstat (limited to 'src/quick/items/qquicktextnodeengine.cpp')
-rw-r--r-- | src/quick/items/qquicktextnodeengine.cpp | 212 |
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; } |