aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items/qquicktextnode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/items/qquicktextnode.cpp')
-rw-r--r--src/quick/items/qquicktextnode.cpp1344
1 files changed, 1344 insertions, 0 deletions
diff --git a/src/quick/items/qquicktextnode.cpp b/src/quick/items/qquicktextnode.cpp
new file mode 100644
index 0000000000..bfb3591d04
--- /dev/null
+++ b/src/quick/items/qquicktextnode.cpp
@@ -0,0 +1,1344 @@
+/****************************************************************************
+**
+** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtDeclarative module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquicktextnode_p.h"
+#include <QtQuick/qsgsimplerectnode.h>
+#include <private/qsgadaptationlayer_p.h>
+#include <private/qsgdistancefieldglyphnode_p.h>
+
+#include <QtQuick/private/qsgcontext_p.h>
+
+#include <QtCore/qpoint.h>
+#include <qmath.h>
+#include <qtextdocument.h>
+#include <qtextlayout.h>
+#include <qabstracttextdocumentlayout.h>
+#include <qxmlstream.h>
+#include <qrawfont.h>
+#include <qtexttable.h>
+#include <qtextlist.h>
+#include <private/qdeclarativestyledtext_p.h>
+#include <private/qfont_p.h>
+#include <private/qfontengine_p.h>
+#include <private/qrawfont_p.h>
+#include <private/qtextimagehandler_p.h>
+#include <private/qtextdocumentlayout_p.h>
+#include <qhash.h>
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ Creates an empty QQuickTextNode
+*/
+QQuickTextNode::QQuickTextNode(QSGContext *context)
+ : m_context(context), m_cursorNode(0)
+{
+#if defined(QML_RUNTIME_TESTING)
+ description = QLatin1String("text");
+#endif
+}
+
+QQuickTextNode::~QQuickTextNode()
+{
+ qDeleteAll(m_textures);
+}
+
+#if 0
+void QQuickTextNode::setColor(const QColor &color)
+{
+ if (m_usePixmapCache) {
+ setUpdateFlag(UpdateNodes);
+ } else {
+ for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
+ if (childNode->subType() == GlyphNodeSubType) {
+ QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
+ if (glyphNode->color() == m_color)
+ glyphNode->setColor(color);
+ } else if (childNode->subType() == SolidRectNodeSubType) {
+ QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
+ if (solidRectNode->color() == m_color)
+ solidRectNode->setColor(color);
+ }
+ }
+ }
+ m_color = color;
+}
+
+void QQuickTextNode::setStyleColor(const QColor &styleColor)
+{
+ if (m_textStyle != QQuickTextNode::NormalTextStyle) {
+ if (m_usePixmapCache) {
+ setUpdateFlag(UpdateNodes);
+ } else {
+ for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
+ if (childNode->subType() == GlyphNodeSubType) {
+ QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
+ if (glyphNode->color() == m_styleColor)
+ glyphNode->setColor(styleColor);
+ } else if (childNode->subType() == SolidRectNodeSubType) {
+ QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
+ if (solidRectNode->color() == m_styleColor)
+ solidRectNode->setColor(styleColor);
+ }
+ }
+ }
+ }
+ m_styleColor = styleColor;
+}
+#endif
+
+QSGGlyphNode *QQuickTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
+ QQuickText::TextStyle style, const QColor &styleColor,
+ QSGNode *parentNode)
+{
+ QSGGlyphNode *node = m_context->createGlyphNode();
+ node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
+ node->setStyle(style);
+ node->setStyleColor(styleColor);
+ node->setColor(color);
+ node->update();
+
+ /* We flag the geometry as static, but we never call markVertexDataDirty
+ or markIndexDataDirty on them. This is because all text nodes are
+ discarded when a change occurs. If we start appending/removing from
+ existing geometry, then we also need to start marking the geometry as
+ dirty.
+ */
+ node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
+ node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
+
+ if (parentNode == 0)
+ parentNode = this;
+ parentNode->appendChildNode(node);
+
+ return node;
+}
+
+void QQuickTextNode::setCursor(const QRectF &rect, const QColor &color)
+{
+ if (m_cursorNode != 0)
+ delete m_cursorNode;
+
+ m_cursorNode = new QSGSimpleRectNode(rect, color);
+ appendChildNode(m_cursorNode);
+}
+
+namespace {
+
+ struct BinaryTreeNode {
+ enum SelectionState {
+ Unselected,
+ Selected
+ };
+
+ BinaryTreeNode()
+ : selectionState(Unselected)
+ , clipNode(0)
+ , decorations(QQuickTextNode::NoDecoration)
+ , ascent(0.0)
+ , leftChildIndex(-1)
+ , rightChildIndex(-1)
+ {
+
+ }
+
+ BinaryTreeNode(const QRectF &brect, const QImage &i, SelectionState selState, qreal a)
+ : boundingRect(brect)
+ , selectionState(selState)
+ , clipNode(0)
+ , decorations(QQuickTextNode::NoDecoration)
+ , image(i)
+ , ascent(a)
+ , leftChildIndex(-1)
+ , rightChildIndex(-1)
+ {
+ }
+
+ BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
+ const QQuickTextNode::Decorations &decs, const QColor &c, const QColor &bc,
+ const QPointF &pos, qreal a)
+ : glyphRun(g)
+ , boundingRect(brect)
+ , selectionState(selState)
+ , clipNode(0)
+ , decorations(decs)
+ , color(c)
+ , backgroundColor(bc)
+ , position(pos)
+ , ascent(a)
+ , leftChildIndex(-1)
+ , rightChildIndex(-1)
+ {
+ }
+
+ QGlyphRun glyphRun;
+ QRectF boundingRect;
+ SelectionState selectionState;
+ QSGClipNode *clipNode;
+ QQuickTextNode::Decorations decorations;
+ QColor color;
+ QColor backgroundColor;
+ QPointF position;
+ QImage image;
+ qreal ascent;
+
+ int leftChildIndex;
+ int rightChildIndex;
+
+ static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
+ const QRectF &rect,
+ const QImage &image,
+ qreal ascent,
+ SelectionState selectionState)
+ {
+ insert(binaryTree, BinaryTreeNode(rect, image, selectionState, ascent));
+ }
+
+ static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
+ const QGlyphRun &glyphRun,
+ SelectionState selectionState,
+ const QColor &textColor,
+ const QColor &backgroundColor,
+ const QPointF &position)
+ {
+ QRectF searchRect = glyphRun.boundingRect();
+ searchRect.translate(position);
+
+ if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
+ return;
+
+ QQuickTextNode::Decorations decorations = QQuickTextNode::NoDecoration;
+ decorations |= (glyphRun.underline() ? QQuickTextNode::Underline : QQuickTextNode::NoDecoration);
+ decorations |= (glyphRun.overline() ? QQuickTextNode::Overline : QQuickTextNode::NoDecoration);
+ decorations |= (glyphRun.strikeOut() ? QQuickTextNode::StrikeOut : QQuickTextNode::NoDecoration);
+ decorations |= (backgroundColor.isValid() ? QQuickTextNode::Background : QQuickTextNode::NoDecoration);
+
+ qreal ascent = glyphRun.rawFont().ascent();
+ insert(binaryTree, BinaryTreeNode(glyphRun, selectionState, searchRect, decorations,
+ textColor, backgroundColor, position, ascent));
+ }
+
+ static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
+ const BinaryTreeNode &binaryTreeNode)
+ {
+ int newIndex = binaryTree->size();
+ binaryTree->append(binaryTreeNode);
+ if (newIndex == 0)
+ return;
+
+ int searchIndex = 0;
+ forever {
+ BinaryTreeNode *node = binaryTree->data() + searchIndex;
+ if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) {
+ if (node->leftChildIndex < 0) {
+ node->leftChildIndex = newIndex;
+ break;
+ } else {
+ searchIndex = node->leftChildIndex;
+ }
+ } else {
+ if (node->rightChildIndex < 0) {
+ node->rightChildIndex = newIndex;
+ break;
+ } else {
+ searchIndex = node->rightChildIndex;
+ }
+ }
+ }
+ }
+
+ static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree,
+ QVarLengthArray<int> *sortedIndexes,
+ int currentIndex = 0)
+ {
+ Q_ASSERT(currentIndex < binaryTree.size());
+
+ const BinaryTreeNode *node = binaryTree.data() + currentIndex;
+ if (node->leftChildIndex >= 0)
+ inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
+
+ sortedIndexes->append(currentIndex);
+
+ if (node->rightChildIndex >= 0)
+ inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
+ }
+ };
+
+ // Engine that takes glyph runs as input, and produces a set of glyph nodes, clip nodes,
+ // and rectangle nodes to represent the text, decorations and selection. Will try to minimize
+ // number of nodes, and join decorations in neighbouring items
+ class SelectionEngine
+ {
+ public:
+ SelectionEngine() : m_hasSelection(false) {}
+
+ QTextLine currentLine() const { return m_currentLine; }
+
+ void setCurrentLine(const QTextLine &currentLine)
+ {
+ if (m_currentLine.isValid())
+ processCurrentLine();
+
+ m_currentLine = currentLine;
+ }
+
+ void addBorder(const QRectF &rect, qreal border, QTextFrameFormat::BorderStyle borderStyle,
+ const QBrush &borderBrush);
+ void addFrameDecorations(QTextDocument *document, QTextFrame *frame);
+ void addImage(const QRectF &rect, const QImage &image, qreal ascent,
+ BinaryTreeNode::SelectionState selectionState,
+ QTextFrameFormat::Position layoutPosition);
+ void addTextObject(const QPointF &position, const QTextCharFormat &format,
+ BinaryTreeNode::SelectionState selectionState,
+ QTextDocument *textDocument, int pos,
+ QTextFrameFormat::Position layoutPosition = QTextFrameFormat::InFlow);
+ void addSelectedGlyphs(const QGlyphRun &glyphRun);
+ void addUnselectedGlyphs(const QGlyphRun &glyphRun);
+ void addGlyphsInRange(int rangeStart, int rangeEnd,
+ const QColor &color, const QColor &backgroundColor,
+ int selectionStart, int selectionEnd);
+ void addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
+ int start, int end,
+ int selectionStart, int selectionEnd);
+
+ void addToSceneGraph(QQuickTextNode *parent,
+ QQuickText::TextStyle style = QQuickText::Normal,
+ const QColor &styleColor = QColor());
+
+ void setSelectionColor(const QColor &selectionColor)
+ {
+ m_selectionColor = selectionColor;
+ }
+
+ void setSelectedTextColor(const QColor &selectedTextColor)
+ {
+ m_selectedTextColor = selectedTextColor;
+ }
+
+ void setTextColor(const QColor &textColor)
+ {
+ m_textColor = textColor;
+ }
+
+ void setPosition(const QPointF &position)
+ {
+ m_position = position;
+ }
+
+ private:
+ struct TextDecoration
+ {
+ TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
+ TextDecoration(const BinaryTreeNode::SelectionState &s,
+ const QRectF &r,
+ const QColor &c)
+ : selectionState(s)
+ , rect(r)
+ , color(c)
+ {
+ }
+
+ BinaryTreeNode::SelectionState selectionState;
+ QRectF rect;
+ QColor color;
+ };
+
+ void processCurrentLine();
+ void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
+ qreal offset, qreal thickness);
+
+ QColor m_selectionColor;
+ QColor m_textColor;
+ QColor m_backgroundColor;
+ QColor m_selectedTextColor;
+ QPointF m_position;
+
+ QTextLine m_currentLine;
+ bool m_hasSelection;
+
+ QList<QPair<QRectF, QColor> > m_backgrounds;
+ QList<QRectF> m_selectionRects;
+ QVarLengthArray<BinaryTreeNode> m_currentLineTree;
+
+ QList<TextDecoration> m_lines;
+ QVector<BinaryTreeNode> m_processedNodes;
+
+ QList<QPair<QRectF, QImage> > m_images;
+ };
+
+ void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
+ qreal offset, qreal thickness)
+ {
+ for (int i=0; i<textDecorations.size(); ++i) {
+ TextDecoration textDecoration = textDecorations.at(i);
+
+ {
+ QRectF &rect = textDecoration.rect;
+ rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
+ rect.setHeight(thickness);
+ }
+
+ m_lines.append(textDecoration);
+ }
+ }
+
+ void SelectionEngine::processCurrentLine()
+ {
+ // No glyphs, do nothing
+ if (m_currentLineTree.isEmpty())
+ return;
+
+ // 1. Go through current line and get correct decoration position for each node based on
+ // neighbouring decorations. Add decoration to global list
+ // 2. Create clip nodes for all selected text. Try to merge as many as possible within
+ // the line.
+ // 3. Add QRects to a list of selection rects.
+ // 4. Add all nodes to a global processed list
+ QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
+ BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
+
+ Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
+
+ BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
+ QRectF currentRect;
+
+ QQuickTextNode::Decorations currentDecorations = QQuickTextNode::NoDecoration;
+ qreal underlineOffset = 0.0;
+ qreal underlineThickness = 0.0;
+
+ qreal overlineOffset = 0.0;
+ qreal overlineThickness = 0.0;
+
+ qreal strikeOutOffset = 0.0;
+ qreal strikeOutThickness = 0.0;
+
+ QRectF decorationRect = currentRect;
+
+ QColor lastColor;
+ QColor lastBackgroundColor;
+
+ QVarLengthArray<TextDecoration> pendingUnderlines;
+ QVarLengthArray<TextDecoration> pendingOverlines;
+ QVarLengthArray<TextDecoration> pendingStrikeOuts;
+ if (!sortedIndexes.isEmpty()) {
+ QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0;
+ bool currentClipNodeUsed = false;
+ for (int i=0; i<=sortedIndexes.size(); ++i) {
+ BinaryTreeNode *node = 0;
+ if (i < sortedIndexes.size()) {
+ int sortedIndex = sortedIndexes.at(i);
+ Q_ASSERT(sortedIndex < m_currentLineTree.size());
+
+ node = m_currentLineTree.data() + sortedIndex;
+ }
+
+ if (i == 0)
+ currentSelectionState = node->selectionState;
+
+ // Update decorations
+ if (currentDecorations != QQuickTextNode::NoDecoration) {
+ decorationRect.setY(m_position.y() + m_currentLine.y());
+ decorationRect.setHeight(m_currentLine.height());
+
+ if (node != 0)
+ decorationRect.setRight(node->boundingRect.left());
+
+ TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
+ if (currentDecorations & QQuickTextNode::Underline)
+ pendingUnderlines.append(textDecoration);
+
+ if (currentDecorations & QQuickTextNode::Overline)
+ pendingOverlines.append(textDecoration);
+
+ if (currentDecorations & QQuickTextNode::StrikeOut)
+ pendingStrikeOuts.append(textDecoration);
+
+ if (currentDecorations & QQuickTextNode::Background)
+ m_backgrounds.append(qMakePair(decorationRect, lastBackgroundColor));
+ }
+
+ // If we've reached an unselected node from a selected node, we add the
+ // selection rect to the graph, and we add decoration every time the
+ // selection state changes, because that means the text color changes
+ if (node == 0 || node->selectionState != currentSelectionState) {
+ if (node != 0)
+ currentRect.setRight(node->boundingRect.left());
+ currentRect.setY(m_position.y() + m_currentLine.y());
+ currentRect.setHeight(m_currentLine.height());
+
+ // Draw selection all the way up to the left edge of the unselected item
+ if (currentSelectionState == BinaryTreeNode::Selected)
+ m_selectionRects.append(currentRect);
+
+ if (currentClipNode != 0) {
+ if (!currentClipNodeUsed) {
+ delete currentClipNode;
+ } else {
+ currentClipNode->setIsRectangular(true);
+ currentClipNode->setClipRect(currentRect);
+ }
+ }
+
+ if (node != 0 && m_hasSelection)
+ currentClipNode = new QSGClipNode;
+ else
+ currentClipNode = 0;
+ currentClipNodeUsed = false;
+
+ if (node != 0) {
+ currentSelectionState = node->selectionState;
+ currentRect = node->boundingRect;
+
+ // Make sure currentRect is valid, otherwise the unite won't work
+ if (currentRect.isNull())
+ currentRect.setSize(QSizeF(1, 1));
+ }
+ } else {
+ if (currentRect.isNull())
+ currentRect = node->boundingRect;
+ else
+ currentRect = currentRect.united(node->boundingRect);
+ }
+
+ if (node != 0) {
+ node->clipNode = currentClipNode;
+ currentClipNodeUsed = true;
+
+ decorationRect = node->boundingRect;
+
+ // If previous item(s) had underline and current does not, then we add the
+ // pending lines to the lists and likewise for overlines and strikeouts
+ if (!pendingUnderlines.isEmpty()
+ && !(node->decorations & QQuickTextNode::Underline)) {
+ addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
+
+ pendingUnderlines.clear();
+
+ underlineOffset = 0.0;
+ underlineThickness = 0.0;
+ }
+
+ // ### Add pending when overlineOffset/thickness changes to minimize number of
+ // nodes
+ if (!pendingOverlines.isEmpty()) {
+ addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
+
+ pendingOverlines.clear();
+
+ overlineOffset = 0.0;
+ overlineThickness = 0.0;
+ }
+
+ // ### Add pending when overlineOffset/thickness changes to minimize number of
+ // nodes
+ if (!pendingStrikeOuts.isEmpty()) {
+ addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
+
+ pendingStrikeOuts.clear();
+
+ strikeOutOffset = 0.0;
+ strikeOutThickness = 0.0;
+ }
+
+ // Merge current values with previous. Prefer greatest thickness
+ QRawFont rawFont = node->glyphRun.rawFont();
+ if (node->decorations & QQuickTextNode::Underline) {
+ if (rawFont.lineThickness() > underlineThickness) {
+ underlineThickness = rawFont.lineThickness();
+ underlineOffset = rawFont.underlinePosition();
+ }
+ }
+
+ if (node->decorations & QQuickTextNode::Overline) {
+ overlineOffset = -rawFont.ascent();
+ overlineThickness = rawFont.lineThickness();
+ }
+
+ if (node->decorations & QQuickTextNode::StrikeOut) {
+ strikeOutThickness = rawFont.lineThickness();
+ strikeOutOffset = rawFont.ascent() / -3.0;
+ }
+
+ currentDecorations = node->decorations;
+ lastColor = node->color;
+ lastBackgroundColor = node->backgroundColor;
+ m_processedNodes.append(*node);
+ }
+ }
+
+ if (!pendingUnderlines.isEmpty())
+ addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
+
+ if (!pendingOverlines.isEmpty())
+ addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
+
+ if (!pendingStrikeOuts.isEmpty())
+ addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
+ }
+
+ m_currentLineTree.clear();
+ m_currentLine = QTextLine();
+ m_hasSelection = false;
+ }
+
+ void SelectionEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent,
+ BinaryTreeNode::SelectionState selectionState,
+ QTextFrameFormat::Position layoutPosition)
+ {
+ QRectF searchRect = rect;
+ if (layoutPosition == QTextFrameFormat::InFlow) {
+ if (m_currentLineTree.isEmpty()) {
+ searchRect.moveTopLeft(m_position);
+ } else {
+ const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1;
+ if (lastNode->glyphRun.isRightToLeft()) {
+ QPointF lastPos = lastNode->boundingRect.topLeft();
+ searchRect.moveTopRight(lastPos - QPointF(0, ascent));
+ } else {
+ QPointF lastPos = lastNode->boundingRect.topRight();
+ searchRect.moveTopLeft(lastPos - QPointF(0, ascent));
+ }
+ }
+ }
+
+ BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
+ }
+
+ void SelectionEngine::addTextObject(const QPointF &position, const QTextCharFormat &format,
+ BinaryTreeNode::SelectionState selectionState,
+ QTextDocument *textDocument, int pos,
+ QTextFrameFormat::Position layoutPosition)
+ {
+ QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType());
+ if (handler != 0) {
+ QImage image;
+ QSizeF size = handler->intrinsicSize(textDocument, pos, format);
+
+ if (format.objectType() == QTextFormat::ImageObject) {
+ QTextImageFormat imageFormat = format.toImageFormat();
+ QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
+ image = imageHandler->image(textDocument, imageFormat);
+ }
+
+ if (image.isNull()) {
+ image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied);
+ image.fill(Qt::transparent);
+ {
+ QPainter painter(&image);
+ handler->drawObject(&painter, image.rect(), textDocument, pos, format);
+ }
+ }
+
+ qreal ascent;
+ QFontMetrics m(format.font());
+ switch (format.verticalAlignment())
+ {
+ case QTextCharFormat::AlignMiddle:
+ ascent = size.height() / 2 - 1;
+ break;
+ case QTextCharFormat::AlignBaseline:
+ ascent = size.height() - m.descent() - 1;
+ break;
+ default:
+ ascent = size.height() - 1;
+ }
+
+ addImage(QRectF(position, size), image, ascent, selectionState, layoutPosition);
+ }
+ }
+
+ void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
+ {
+ BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
+ m_textColor, m_backgroundColor, m_position);
+ }
+
+ void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
+ {
+ int currentSize = m_currentLineTree.size();
+ BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
+ m_textColor, m_backgroundColor, m_position);
+ m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
+ }
+
+ void SelectionEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
+ int start, int end,
+ int selectionStart, int selectionEnd)
+ {
+ int currentPosition = start;
+ 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
+ && range.start < currentPosition + remainingLength) {
+
+ if (range.start > currentPosition) {
+ addGlyphsInRange(currentPosition, range.start - currentPosition,
+ QColor(), QColor(), selectionStart, selectionEnd);
+ }
+
+ int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
+ QColor rangeColor = range.format.hasProperty(QTextFormat::ForegroundBrush)
+ ? range.format.foreground().color()
+ : QColor();
+ QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush)
+ ? range.format.background().color()
+ : QColor();
+
+ addGlyphsInRange(range.start, rangeEnd - range.start,
+ rangeColor, rangeBackgroundColor,
+ selectionStart, selectionEnd);
+
+ currentPosition = range.start + range.length;
+ remainingLength = end - currentPosition;
+
+ } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
+ break;
+ }
+ }
+
+ if (remainingLength > 0) {
+ addGlyphsInRange(currentPosition, remainingLength, QColor(), QColor(),
+ selectionStart, selectionEnd);
+ }
+
+ }
+
+ void SelectionEngine::addGlyphsInRange(int rangeStart, int rangeLength,
+ const QColor &color, const QColor &backgroundColor,
+ int selectionStart, int selectionEnd)
+ {
+ QColor oldColor;
+ if (color.isValid()) {
+ oldColor = m_textColor;
+ m_textColor = color;
+ }
+
+ QColor oldBackgroundColor = m_backgroundColor;
+ if (backgroundColor.isValid()) {
+ oldBackgroundColor = m_backgroundColor;
+ m_backgroundColor = backgroundColor;
+ }
+
+ bool hasSelection = selectionEnd >= 0
+ && selectionStart <= selectionEnd;
+
+ QTextLine &line = m_currentLine;
+ int rangeEnd = rangeStart + rangeLength;
+ if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) {
+ QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
+ for (int j=0; j<glyphRuns.size(); ++j) {
+ const QGlyphRun &glyphRun = glyphRuns.at(j);
+ addUnselectedGlyphs(glyphRun);
+ }
+ } else {
+ if (rangeStart < selectionStart) {
+ QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart,
+ qMin(selectionStart - rangeStart,
+ rangeLength));
+
+ for (int j=0; j<glyphRuns.size(); ++j) {
+ const QGlyphRun &glyphRun = glyphRuns.at(j);
+ addUnselectedGlyphs(glyphRun);
+ }
+ }
+
+ if (rangeEnd > selectionStart) {
+ int start = qMax(selectionStart, rangeStart);
+ int length = qMin(selectionEnd - start + 1, rangeEnd - start);
+ QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
+
+ for (int j=0; j<glyphRuns.size(); ++j) {
+ const QGlyphRun &glyphRun = glyphRuns.at(j);
+ addSelectedGlyphs(glyphRun);
+ }
+ }
+
+ if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
+ QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd - 1);
+ for (int j=0; j<glyphRuns.size(); ++j) {
+ const QGlyphRun &glyphRun = glyphRuns.at(j);
+ addUnselectedGlyphs(glyphRun);
+ }
+ }
+ }
+
+ if (backgroundColor.isValid())
+ m_backgroundColor = oldBackgroundColor;
+
+ if (oldColor.isValid())
+ m_textColor = oldColor;
+ }
+
+ void SelectionEngine::addBorder(const QRectF &rect, qreal border,
+ QTextFrameFormat::BorderStyle borderStyle,
+ const QBrush &borderBrush)
+ {
+ QColor color = borderBrush.color();
+
+ // Currently we don't support other styles than solid
+ Q_UNUSED(borderStyle);
+
+ m_backgrounds.append(qMakePair(QRectF(rect.left(), rect.top(), border, rect.height() + border), color));
+ m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.top(), rect.width(), border), color));
+ m_backgrounds.append(qMakePair(QRectF(rect.right(), rect.top() + border, border, rect.height() - border), color));
+ m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.bottom(), rect.width(), border), color));
+ }
+
+ void SelectionEngine::addFrameDecorations(QTextDocument *document, QTextFrame *frame)
+ {
+ QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(document->documentLayout());
+ QTextFrameFormat frameFormat = frame->format().toFrameFormat();
+
+ QTextTable *table = qobject_cast<QTextTable *>(frame);
+ QRectF boundingRect = table == 0
+ ? documentLayout->frameBoundingRect(frame)
+ : documentLayout->tableBoundingRect(table);
+
+ QBrush bg = frame->frameFormat().background();
+ if (bg.style() != Qt::NoBrush)
+ m_backgrounds.append(qMakePair(boundingRect, bg.color()));
+
+ if (!frameFormat.hasProperty(QTextFormat::FrameBorder))
+ return;
+
+ qreal borderWidth = frameFormat.border();
+ if (qFuzzyIsNull(borderWidth))
+ return;
+
+ QBrush borderBrush = frameFormat.borderBrush();
+ QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle();
+ if (borderStyle == QTextFrameFormat::BorderStyle_None)
+ return;
+
+ addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
+ -frameFormat.rightMargin(), -frameFormat.bottomMargin()),
+ borderWidth, borderStyle, borderBrush);
+ if (table != 0) {
+ int rows = table->rows();
+ int columns = table->columns();
+
+ for (int row=0; row<rows; ++row) {
+ for (int column=0; column<columns; ++column) {
+ QTextTableCell cell = table->cellAt(row, column);
+
+ QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
+ addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth,
+ borderStyle, borderBrush);
+ }
+ }
+ }
+ }
+
+ void SelectionEngine::addToSceneGraph(QQuickTextNode *parentNode,
+ QQuickText::TextStyle style,
+ const QColor &styleColor)
+ {
+ if (m_currentLine.isValid())
+ processCurrentLine();
+
+
+ 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->appendChildNode(new QSGSimpleRectNode(rect, color));
+ }
+
+ // First, prepend all selection rectangles to the tree
+ for (int i=0; i<m_selectionRects.size(); ++i) {
+ const QRectF &rect = m_selectionRects.at(i);
+
+ parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
+ }
+
+ // Finally, add decorations for each node to the tree.
+ for (int i=0; i<m_lines.size(); ++i) {
+ const TextDecoration &textDecoration = m_lines.at(i);
+
+ QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
+ ? m_selectedTextColor
+ : textDecoration.color;
+
+ parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
+ }
+
+ // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common
+ // font, selection state and clip node.
+ typedef QPair<QFontEngine *, QPair<QSGClipNode *, QPair<QRgb, int> > > KeyType;
+ QHash<KeyType, BinaryTreeNode *> map;
+ for (int i=0; i<m_processedNodes.size(); ++i) {
+ BinaryTreeNode *node = m_processedNodes.data() + i;
+
+ if (node->image.isNull()) {
+ QGlyphRun glyphRun = node->glyphRun;
+ QRawFont rawFont = glyphRun.rawFont();
+ QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
+
+ QFontEngine *fontEngine = rawFontD->fontEngine;
+
+ KeyType key(qMakePair(fontEngine,
+ qMakePair(node->clipNode,
+ qMakePair(node->color.rgba(), int(node->selectionState)))));
+
+ BinaryTreeNode *otherNode = map.value(key, 0);
+ if (otherNode != 0) {
+ QGlyphRun &otherGlyphRun = otherNode->glyphRun;
+
+ QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
+ QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
+
+ otherGlyphIndexes += glyphRun.glyphIndexes();
+
+ QVector<QPointF> glyphPositions = glyphRun.positions();
+ for (int j=0; j<glyphPositions.size(); ++j) {
+ otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
+ }
+
+ otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
+ otherGlyphRun.setPositions(otherGlyphPositions);
+
+ } else {
+ map.insert(key, node);
+ }
+ } else {
+ parentNode->addImage(node->boundingRect, node->image);
+ if (node->selectionState == BinaryTreeNode::Selected) {
+ QColor color = m_selectionColor;
+ color.setAlpha(128);
+ parentNode->appendChildNode(new QSGSimpleRectNode(node->boundingRect, color));
+ }
+ }
+ }
+
+ // ...and add clip nodes and glyphs to tree.
+ QHash<KeyType, BinaryTreeNode *>::const_iterator it = map.constBegin();
+ while (it != map.constEnd()) {
+
+ BinaryTreeNode *node = it.value();
+
+ QSGClipNode *clipNode = node->clipNode;
+ if (clipNode != 0 && clipNode->parent() == 0 )
+ parentNode->appendChildNode(clipNode);
+
+ QColor color = node->selectionState == BinaryTreeNode::Selected
+ ? m_selectedTextColor
+ : node->color;
+
+ parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
+
+ ++it;
+ }
+ }
+}
+
+void QQuickTextNode::mergeFormats(QTextLayout *textLayout,
+ QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
+{
+ Q_ASSERT(mergedFormats != 0);
+ if (textLayout == 0)
+ return;
+
+ QList<QTextLayout::FormatRange> additionalFormats = textLayout->additionalFormats();
+ for (int i=0; i<additionalFormats.size(); ++i) {
+ QTextLayout::FormatRange additionalFormat = additionalFormats.at(i);
+ if (additionalFormat.format.hasProperty(QTextFormat::ForegroundBrush)
+ || additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)) {
+ // Merge overlapping formats
+ if (!mergedFormats->isEmpty()) {
+ QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1;
+
+ if (additionalFormat.start < lastFormat->start + lastFormat->length) {
+ QTextLayout::FormatRange *mergedRange = 0;
+
+ int length = additionalFormat.length;
+ if (additionalFormat.start > lastFormat->start) {
+ lastFormat->length = additionalFormat.start - lastFormat->start;
+ length -= lastFormat->length;
+
+ mergedFormats->append(QTextLayout::FormatRange());
+ mergedRange = mergedFormats->data() + mergedFormats->size() - 1;
+ lastFormat = mergedFormats->data() + mergedFormats->size() - 2;
+ } else {
+ mergedRange = lastFormat;
+ }
+
+ mergedRange->format = lastFormat->format;
+ mergedRange->format.merge(additionalFormat.format);
+ mergedRange->start = additionalFormat.start;
+
+ int end = qMin(additionalFormat.start + additionalFormat.length,
+ lastFormat->start + lastFormat->length);
+
+ mergedRange->length = end - mergedRange->start;
+ length -= mergedRange->length;
+
+ additionalFormat.start = end;
+ additionalFormat.length = length;
+ }
+ }
+
+ if (additionalFormat.length > 0)
+ mergedFormats->append(additionalFormat);
+ }
+ }
+
+}
+
+namespace {
+
+ class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
+ {
+ public:
+ inline QTextCharFormat formatAccessor(int pos)
+ {
+ return format(pos);
+ }
+ };
+
+}
+
+void QQuickTextNode::addImage(const QRectF &rect, const QImage &image)
+{
+ QSGImageNode *node = m_context->createImageNode();
+ QSGTexture *texture = m_context->createTexture(image);
+ m_textures.append(texture);
+ node->setTargetRect(rect);
+ node->setTexture(texture);
+ appendChildNode(node);
+ node->update();
+}
+
+void QQuickTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument,
+ const QColor &textColor,
+ QQuickText::TextStyle style, const QColor &styleColor,
+ const QColor &selectionColor, const QColor &selectedTextColor,
+ int selectionStart, int selectionEnd)
+{
+ SelectionEngine engine;
+ engine.setTextColor(textColor);
+ engine.setSelectedTextColor(selectedTextColor);
+ engine.setSelectionColor(selectionColor);
+
+ QList<QTextFrame *> frames;
+ frames.append(textDocument->rootFrame());
+ while (!frames.isEmpty()) {
+ QTextFrame *textFrame = frames.takeFirst();
+ frames.append(textFrame->childFrames());
+
+ engine.addFrameDecorations(textDocument, textFrame);
+
+ if (textFrame->firstPosition() > textFrame->lastPosition()
+ && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
+ const int pos = textFrame->firstPosition() - 1;
+ ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(textDocument->documentLayout());
+ QTextCharFormat format = a->formatAccessor(pos);
+ QRectF rect = a->frameBoundingRect(textFrame);
+
+ QTextBlock block = textFrame->firstCursorPosition().block();
+ engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
+ engine.addTextObject(rect.topLeft(), format, BinaryTreeNode::Unselected, textDocument,
+ pos, textFrame->frameFormat().position());
+ } else {
+ QTextFrame::iterator it = textFrame->begin();
+
+ while (!it.atEnd()) {
+ Q_ASSERT(!engine.currentLine().isValid());
+
+ QTextBlock block = it.currentBlock();
+ int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0;
+ int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
+
+ QVarLengthArray<QTextLayout::FormatRange> colorChanges;
+ mergeFormats(block.layout(), &colorChanges);
+
+ QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
+ if (QTextList *textList = block.textList()) {
+ QPointF pos = blockPosition;
+ QTextLayout *layout = block.layout();
+ if (layout->lineCount() > 0) {
+ QTextLine firstLine = layout->lineAt(0);
+ Q_ASSERT(firstLine.isValid());
+
+ engine.setCurrentLine(firstLine);
+
+ QRectF textRect = firstLine.naturalTextRect();
+ pos += textRect.topLeft();
+ if (block.textDirection() == Qt::RightToLeft)
+ pos.rx() += textRect.width();
+
+ const QTextCharFormat charFormat = block.charFormat();
+ QFont font(charFormat.font());
+ QFontMetricsF fontMetrics(font);
+ QTextListFormat listFormat = textList->format();
+
+ QString listItemBullet;
+ switch (listFormat.style()) {
+ case QTextListFormat::ListCircle:
+ listItemBullet = QChar(0x25E6); // White bullet
+ break;
+ case QTextListFormat::ListSquare:
+ listItemBullet = QChar(0x25AA); // Black small square
+ break;
+ case QTextListFormat::ListDecimal:
+ case QTextListFormat::ListLowerAlpha:
+ case QTextListFormat::ListUpperAlpha:
+ case QTextListFormat::ListLowerRoman:
+ case QTextListFormat::ListUpperRoman:
+ listItemBullet = textList->itemText(block);
+ break;
+ default:
+ listItemBullet = QChar(0x2022); // Black bullet
+ break;
+ };
+
+ QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height());
+ qreal xoff = fontMetrics.width(QLatin1Char(' '));
+ if (block.textDirection() == Qt::LeftToRight)
+ xoff = -xoff - size.width();
+ engine.setPosition(pos + QPointF(xoff, 0));
+
+ QTextLayout layout;
+ layout.setFont(font);
+ layout.setText(listItemBullet); // Bullet
+ layout.beginLayout();
+ QTextLine line = layout.createLine();
+ line.setPosition(QPointF(0, 0));
+ layout.endLayout();
+
+ QList<QGlyphRun> glyphRuns = layout.glyphRuns();
+ for (int i=0; i<glyphRuns.size(); ++i)
+ engine.addUnselectedGlyphs(glyphRuns.at(i));
+ }
+ }
+
+ int textPos = block.position();
+ QTextBlock::iterator blockIterator = block.begin();
+ while (!blockIterator.atEnd()) {
+ QTextFragment fragment = blockIterator.fragment();
+ QString text = fragment.text();
+ if (text.isEmpty())
+ continue;
+
+ QTextCharFormat charFormat = fragment.charFormat();
+ engine.setPosition(blockPosition);
+ if (text.contains(QChar::ObjectReplacementCharacter)) {
+ QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
+ if (frame && frame->frameFormat().position() == QTextFrameFormat::InFlow) {
+ BinaryTreeNode::SelectionState selectionState =
+ (selectionStart < textPos + text.length()
+ && selectionEnd >= textPos)
+ ? BinaryTreeNode::Selected
+ : BinaryTreeNode::Unselected;
+
+ engine.addTextObject(QPointF(), charFormat, selectionState, textDocument, textPos);
+ }
+ textPos += text.length();
+ } else {
+ if (charFormat.foreground().style() != Qt::NoBrush)
+ engine.setTextColor(charFormat.foreground().color());
+ else
+ engine.setTextColor(textColor);
+
+ int fragmentEnd = textPos + fragment.length();
+ if (preeditPosition >= 0
+ && preeditPosition >= textPos
+ && preeditPosition < fragmentEnd) {
+ fragmentEnd += preeditLength;
+ }
+
+ while (textPos < fragmentEnd) {
+ int blockRelativePosition = textPos - block.position();
+ QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
+ if (!engine.currentLine().isValid()
+ || line.lineNumber() != engine.currentLine().lineNumber()) {
+ engine.setCurrentLine(line);
+ }
+
+ Q_ASSERT(line.textLength() > 0);
+ int lineEnd = line.textStart() + block.position() + line.textLength();
+
+ int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
+ Q_ASSERT(len > 0);
+
+ int currentStepEnd = textPos + len;
+
+ engine.addGlyphsForRanges(colorChanges,
+ textPos - block.position(),
+ currentStepEnd - block.position(),
+ selectionStart - block.position(),
+ selectionEnd - block.position());
+
+ textPos = currentStepEnd;
+ }
+ }
+
+ ++blockIterator;
+ }
+
+ engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
+ ++it;
+ }
+ }
+ }
+
+ engine.addToSceneGraph(this, style, styleColor);
+}
+
+void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
+ QQuickText::TextStyle style, const QColor &styleColor,
+ const QColor &selectionColor, const QColor &selectedTextColor,
+ int selectionStart, int selectionEnd)
+{
+ SelectionEngine engine;
+ engine.setTextColor(color);
+ engine.setSelectedTextColor(selectedTextColor);
+ engine.setSelectionColor(selectionColor);
+ engine.setPosition(position);
+
+ int preeditLength = textLayout->preeditAreaText().length();
+ int preeditPosition = textLayout->preeditAreaPosition();
+
+ QVarLengthArray<QTextLayout::FormatRange> colorChanges;
+ mergeFormats(textLayout, &colorChanges);
+
+ for (int i=0; i<textLayout->lineCount(); ++i) {
+ QTextLine line = textLayout->lineAt(i);
+
+ int start = line.textStart();
+ int length = line.textLength();
+ int end = start + length;
+
+ if (preeditPosition >= 0
+ && preeditPosition >= start
+ && preeditPosition < end) {
+ end += preeditLength;
+ }
+
+ engine.setCurrentLine(line);
+ engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
+ }
+
+ engine.addToSceneGraph(this, style, styleColor);
+}
+
+void QQuickTextNode::deleteContent()
+{
+ while (firstChild() != 0)
+ delete firstChild();
+ m_cursorNode = 0;
+}
+
+#if 0
+void QQuickTextNode::updateNodes()
+{
+ return;
+ deleteContent();
+ if (m_text.isEmpty())
+ return;
+
+ if (m_usePixmapCache) {
+ // ### gunnar: port properly
+// QPixmap pixmap = generatedPixmap();
+// if (pixmap.isNull())
+// return;
+
+// QSGImageNode *pixmapNode = m_context->createImageNode();
+// pixmapNode->setRect(pixmap.rect());
+// pixmapNode->setSourceRect(pixmap.rect());
+// pixmapNode->setOpacity(m_opacity);
+// pixmapNode->setClampToEdge(true);
+// pixmapNode->setLinearFiltering(m_linearFiltering);
+
+// appendChildNode(pixmapNode);
+ } else {
+ if (m_text.isEmpty())
+ return;
+
+ // Implement styling by drawing text several times at slight shifts. shiftForStyle
+ // contains the sequence of shifted positions at which to draw the text. All except
+ // the last will be drawn with styleColor.
+ QList<QPointF> shiftForStyle;
+ switch (m_textStyle) {
+ case OutlineTextStyle:
+ // ### Should be made faster by implementing outline material
+ shiftForStyle << QPointF(-1, 0);
+ shiftForStyle << QPointF(0, -1);
+ shiftForStyle << QPointF(1, 0);
+ shiftForStyle << QPointF(0, 1);
+ break;
+ case SunkenTextStyle:
+ shiftForStyle << QPointF(0, -1);
+ break;
+ case RaisedTextStyle:
+ shiftForStyle << QPointF(0, 1);
+ break;
+ default:
+ break;
+ }
+
+ shiftForStyle << QPointF(0, 0); // Regular position
+ while (!shiftForStyle.isEmpty()) {
+ QPointF shift = shiftForStyle.takeFirst();
+
+ // Use styleColor for all but last shift
+ if (m_richText) {
+ QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
+
+ QTextFrame *textFrame = m_textDocument->rootFrame();
+ QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
+
+ QTextFrame::iterator it = textFrame->begin();
+ while (!it.atEnd()) {
+ addTextBlock(shift + p, it.currentBlock(), overrideColor);
+ ++it;
+ }
+ } else {
+ addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()
+ ? m_color
+ : m_styleColor);
+ }
+ }
+ }
+}
+#endif
+
+QT_END_NAMESPACE