Optimize QQuickTextEdit for larger documents.
authorPierre Rossi <pierre.rossi@digia.com>
Mon, 18 Mar 2013 15:25:59 +0000 (16:25 +0100)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Wed, 20 Mar 2013 07:31:56 +0000 (08:31 +0100)
The rationale is to not end up re-processing the whole document on each update
Since we know where the editing takes place, we can break down the text edit's
contents in several text nodes and only re-create the affected text nodes upon
editing.

This requires ripping out the SelectionEngine helper class from QQuickTextNode
so that QQuickTextEdit can tap into its functionality directly.

A positive side-effect of this exercise is that it should be much harder to
to come across GlyphNodes packing more than 16300 glyphs or so.

Task-number: QTBUG-29596
Change-Id: Id29b0709baa43f5b29c44ab02398ba996be3e28a
Reviewed-by: Yoann Lopes <yoann.lopes@digia.com>
src/quick/items/items.pri
src/quick/items/qquicktextcontrol.cpp
src/quick/items/qquicktextcontrol_p.h
src/quick/items/qquicktextcontrol_p_p.h
src/quick/items/qquicktextedit.cpp
src/quick/items/qquicktextedit_p.h
src/quick/items/qquicktextedit_p_p.h
src/quick/items/qquicktextnode.cpp
src/quick/items/qquicktextnode_p.h
src/quick/items/qquicktextnodeengine.cpp [new file with mode: 0644]
src/quick/items/qquicktextnodeengine_p.h [new file with mode: 0644]

index fe406ec..5aaf7d3 100644 (file)
@@ -17,6 +17,7 @@ HEADERS += \
     $$PWD/qquicktext_p.h \
     $$PWD/qquicktext_p_p.h \
     $$PWD/qquicktextnode_p.h \
+    $$PWD/qquicktextnodeengine_p.h \
     $$PWD/qquicktextinput_p.h \
     $$PWD/qquicktextinput_p_p.h \
     $$PWD/qquicktextcontrol_p.h \
@@ -84,6 +85,7 @@ SOURCES += \
     $$PWD/qquickpainteditem.cpp \
     $$PWD/qquicktext.cpp \
     $$PWD/qquicktextnode.cpp \
+    $$PWD/qquicktextnodeengine.cpp \
     $$PWD/qquicktextinput.cpp \
     $$PWD/qquicktextcontrol.cpp \
     $$PWD/qquicktextdocument.cpp \
index 6cce3cc..9625aa5 100644 (file)
@@ -586,6 +586,7 @@ QQuickTextControl::QQuickTextControl(QTextDocument *doc, QObject *parent)
     qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), this, QQuickTextControl, SIGNAL(textChanged()));
     qmlobject_connect(doc, QTextDocument, SIGNAL(contentsChanged()), this, QQuickTextControl, SLOT(_q_updateCurrentCharFormatAndSelection()));
     qmlobject_connect(doc, QTextDocument, SIGNAL(cursorPositionChanged(QTextCursor)), this, QQuickTextControl, SLOT(_q_emitCursorPosChanged(QTextCursor)));
+    connect(doc, &QTextDocument::contentsChange, this, &QQuickTextControl::contentsChange);
 
     layout->setProperty("cursorWidth", textCursorWidth);
 
index 94eae81..7ec8a68 100644 (file)
@@ -138,6 +138,7 @@ public Q_SLOTS:
 
 Q_SIGNALS:
     void textChanged();
+    void contentsChange(int from, int charsRemoved, int charsAdded);
     void undoAvailable(bool b);
     void redoAvailable(bool b);
     void currentCharFormatChanged(const QTextCharFormat &format);
index a0727c7..fbb88bd 100644 (file)
@@ -39,8 +39,8 @@
 **
 ****************************************************************************/
 
-#ifndef QTEXTCONTROL_P_P_H
-#define QTEXTCONTROL_P_P_H
+#ifndef QQUICKTEXTCONTROL_P_P_H
+#define QQUICKTEXTCONTROL_P_P_H
 
 //
 //  W A R N I N G
index feabbba..cf20b1d 100644 (file)
@@ -46,6 +46,7 @@
 #include "qquickevents_p_p.h"
 #include "qquickwindow.h"
 #include "qquicktextnode_p.h"
+#include "qquicktextnodeengine_p.h"
 #include "qquicktextutil_p.h"
 #include <QtQuick/qsgsimplerectnode.h>
 
@@ -55,6 +56,7 @@
 #include <QtGui/qpainter.h>
 #include <QtGui/qtextobject.h>
 #include <QtCore/qmath.h>
+#include <QtCore/qalgorithms.h>
 
 #include <private/qqmlglobal_p.h>
 #include <private/qqmlproperty_p.h>
@@ -118,6 +120,11 @@ TextEdit {
     The link must be in rich text or HTML format and the
     \a link string provides access to the particular link.
 */
+
+// This is a pretty arbitrary figure. The idea is that we don't want to break down the document
+// into text nodes corresponding to a text block each so that the glyph node grouping doesn't become pointless.
+static const int nodeBreakingSize = 300;
+
 QQuickTextEdit::QQuickTextEdit(QQuickItem *parent)
 : QQuickImplicitSizeItem(*(new QQuickTextEditPrivate), parent)
 {
@@ -410,7 +417,7 @@ void QQuickTextEdit::setFont(const QFont &font)
             moveCursorDelegate();
         }
         updateSize();
-        updateDocument();
+        updateWholeDocument();
 #ifndef QT_NO_IM
         updateInputMethod(Qt::ImCursorRectangle | Qt::ImFont);
 #endif
@@ -446,7 +453,7 @@ void QQuickTextEdit::setColor(const QColor &color)
         return;
 
     d->color = color;
-    updateDocument();
+    updateWholeDocument();
     emit colorChanged(d->color);
 }
 
@@ -468,7 +475,7 @@ void QQuickTextEdit::setSelectionColor(const QColor &color)
         return;
 
     d->selectionColor = color;
-    updateDocument();
+    updateWholeDocument();
     emit selectionColorChanged(d->selectionColor);
 }
 
@@ -490,7 +497,7 @@ void QQuickTextEdit::setSelectedTextColor(const QColor &color)
         return;
 
     d->selectedTextColor = color;
-    updateDocument();
+    updateWholeDocument();
     emit selectedTextColorChanged(d->selectedTextColor);
 }
 
@@ -1205,6 +1212,7 @@ void QQuickTextEdit::geometryChanged(const QRectF &newGeometry,
     Q_D(QQuickTextEdit);
     if (newGeometry.width() != oldGeometry.width() && widthValid() && !d->inLayout) {
         updateSize();
+        updateWholeDocument();
         moveCursorDelegate();
     }
     QQuickImplicitSizeItem::geometryChanged(newGeometry, oldGeometry);
@@ -1492,7 +1500,7 @@ void QQuickTextEdit::select(int start, int end)
     d->control->setTextCursor(cursor);
 
     // QTBUG-11100
-    updateSelectionMarkers();
+    updateSelection();
 }
 
 /*!
@@ -1679,6 +1687,15 @@ void QQuickTextEdit::triggerPreprocess()
     update();
 }
 
+typedef QQuickTextEditPrivate::Node TextNode;
+typedef QList<TextNode*>::iterator TextNodeIterator;
+
+
+static bool comesBefore(TextNode* n1, TextNode* n2)
+{
+    return n1->startPos() < n2->startPos();
+}
+
 QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *updatePaintNodeData)
 {
     Q_UNUSED(updatePaintNodeData);
@@ -1692,45 +1709,144 @@ QSGNode *QQuickTextEdit::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *
 
     d->updateType = QQuickTextEditPrivate::UpdateNone;
 
-    QSGNode *currentNode = oldNode;
-    if (oldNode == 0 || d->documentDirty) {
-        d->documentDirty = false;
+    QSGTransformNode *rootNode = static_cast<QSGTransformNode *>(oldNode);
+    TextNodeIterator nodeIterator = d->textNodeMap.begin();
+    while (nodeIterator != d->textNodeMap.end() && !(*nodeIterator)->dirty())
+        ++nodeIterator;
 
-        QQuickTextNode *node = 0;
-        if (oldNode == 0) {
-            node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this);
-            currentNode = node;
-        } else {
-            node = static_cast<QQuickTextNode *>(oldNode);
+
+    if (!oldNode || nodeIterator < d->textNodeMap.end()) {
+
+        if (!oldNode)
+            rootNode = new QSGTransformNode;
+
+        int firstDirtyPos = 0;
+        if (nodeIterator != d->textNodeMap.end()) {
+            firstDirtyPos = (*nodeIterator)->startPos();
+            do {
+                rootNode->removeChildNode((*nodeIterator)->textNode());
+                delete (*nodeIterator)->textNode();
+                delete *nodeIterator;
+                nodeIterator = d->textNodeMap.erase(nodeIterator);
+            } while (nodeIterator != d->textNodeMap.end() && (*nodeIterator)->dirty());
         }
 
+        // FIXME: the text decorations could probably be handled separately (only updated for affected textFrames)
+        if (d->frameDecorationsNode) {
+            rootNode->removeChildNode(d->frameDecorationsNode);
+            delete d->frameDecorationsNode;
+        }
+        d->frameDecorationsNode = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this);
+        d->frameDecorationsNode->initEngine(QColor(), QColor(), QColor());
+
+
+        QQuickTextNode *node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this);
         node->setUseNativeRenderer(d->renderType == NativeRendering);
-        node->deleteContent();
-        node->setMatrix(QMatrix4x4());
+        node->initEngine(d->color, d->selectedTextColor, d->selectionColor);
+
+
+        int sizeCounter = 0;
+        int prevBlockStart = firstDirtyPos;
+        QPointF basePosition(d->xoff, d->yoff);
+        QPointF nodeOffset;
+        TextNode *firstCleanNode = (nodeIterator != d->textNodeMap.end()) ? *nodeIterator : 0;
+
+        QList<QTextFrame *> frames;
+        frames.append(d->document->rootFrame());
+
+        while (!frames.isEmpty()) {
+            QTextFrame *textFrame = frames.takeFirst();
+            frames.append(textFrame->childFrames());
+            d->frameDecorationsNode->m_engine->addFrameDecorations(d->document, textFrame);
+
+            if (textFrame->firstPosition() > textFrame->lastPosition()
+                    && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
+                QRectF rect = d->document->documentLayout()->frameBoundingRect(textFrame);
+
+                if (!node->m_engine->hasContents()) {
+                    nodeOffset = rect.topLeft();
+                    QMatrix4x4 transformMatrix;
+                    transformMatrix.translate(nodeOffset.x(), nodeOffset.y());
+                    node->setMatrix(transformMatrix);
+                }
+                const int pos = textFrame->firstPosition() - 1;
+                QTextBlock block = textFrame->firstCursorPosition().block();
+                node->m_engine->setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
+                node->m_engine->addTextObject(QPointF(0, 0), block.charFormat(), QQuickTextNodeEngine::Unselected, d->document,
+                                              pos, textFrame->frameFormat().position());
+            } else {
+
+                QTextFrame::iterator it = textFrame->begin();
+
+                while (!it.atEnd()) {
+                    QTextBlock block = it.currentBlock();
+                    ++it;
+                    if (block.position() < firstDirtyPos)
+                        continue;
+
+                    if (!node->m_engine->hasContents()) {
+                        nodeOffset = d->document->documentLayout()->blockBoundingRect(block).topLeft();
+                        QMatrix4x4 transformMatrix;
+                        transformMatrix.translate(nodeOffset.x(), nodeOffset.y());
+                        node->setMatrix(transformMatrix);
+                    }
+
+                    node->m_engine->addTextBlock(d->document, block, basePosition - nodeOffset, d->color, QColor(), selectionStart(), selectionEnd() - 1);
+                    sizeCounter += block.length();
+
+                    if ((it.atEnd() && frames.isEmpty()) || (firstCleanNode && block.next().position() >= firstCleanNode->startPos())) // last node that needed replacing or last block of the last frame
+                        break;
+
+                    if (sizeCounter > nodeBreakingSize) {
+                        sizeCounter = 0;
+                        node->m_engine->addToSceneGraph(node, QQuickText::Normal, QColor());
+                        nodeIterator = d->textNodeMap.insert(nodeIterator, new TextNode(prevBlockStart, node));
+                        ++nodeIterator;
+                        rootNode->appendChildNode(node);
+                        prevBlockStart = block.next().position();
+                        node = new QQuickTextNode(QQuickItemPrivate::get(this)->sceneGraphContext(), this);
+                        node->setUseNativeRenderer(d->renderType == NativeRendering);
+                        node->initEngine(d->color, d->selectedTextColor, d->selectionColor);
+                    }
+                }
+            }
+        }
+        node->m_engine->addToSceneGraph(node, QQuickText::Normal, QColor());
+        nodeIterator = d->textNodeMap.insert(nodeIterator, new TextNode(prevBlockStart, node));
+        ++nodeIterator;
+        rootNode->appendChildNode(node);
+        d->frameDecorationsNode->m_engine->addToSceneGraph(d->frameDecorationsNode, QQuickText::Normal, QColor());
+        // Now prepend the frame decorations since we want them rendered first, with the text nodes and cursor in front.
+        rootNode->prependChildNode(d->frameDecorationsNode);
+
+        Q_ASSERT(nodeIterator == d->textNodeMap.end() || (*nodeIterator) == firstCleanNode);
+        // Update the position of the subsequent text blocks.
+        if (firstCleanNode) {
+            QPointF oldOffset = firstCleanNode->textNode()->matrix().map(QPointF(0,0));
+            QPointF currentOffset = d->document->documentLayout()->blockBoundingRect(d->document->findBlock(firstCleanNode->startPos())).topLeft();
+            QPointF delta = currentOffset - oldOffset;
+            while (nodeIterator != d->textNodeMap.end()) {
+                QMatrix4x4 transformMatrix = (*nodeIterator)->textNode()->matrix();
+                transformMatrix.translate(delta.x(), delta.y());
+                (*nodeIterator)->textNode()->setMatrix(transformMatrix);
+                ++nodeIterator;
+            }
 
-        node->addTextDocument(QPointF(d->xoff, d->yoff), d->document, d->color, QQuickText::Normal, QColor(),
-                              QColor(), d->selectionColor, d->selectedTextColor, selectionStart(),
-                              selectionEnd() - 1);  // selectionEnd() returns first char after
-                                                    // selection
+        }
     }
 
     if (d->cursorComponent == 0 && !isReadOnly()) {
-        QQuickTextNode *node = static_cast<QQuickTextNode *>(currentNode);
-
         QColor color = (!d->cursorVisible || !d->control->cursorOn())
                 ? QColor(0, 0, 0, 0)
                 : d->color;
-
-        if (node->cursorNode() == 0) {
-            node->setCursor(cursorRectangle(), color);
-        } else {
-            node->cursorNode()->setRect(cursorRectangle());
-            node->cursorNode()->setColor(color);
-        }
-
+        if (d->cursorNode)
+            rootNode->removeChildNode(d->cursorNode);
+        delete d->cursorNode;
+        d->cursorNode = new QSGSimpleRectNode(cursorRectangle(), color);
+        rootNode->appendChildNode(d->cursorNode);
     }
 
-    return currentNode;
+    return rootNode;
 }
 
 /*!
@@ -1818,27 +1934,28 @@ void QQuickTextEditPrivate::init()
     control->setAcceptRichText(false);
     control->setCursorIsFocusIndicator(true);
 
-    qmlobject_connect(control, QQuickTextControl, SIGNAL(updateRequest()), q, QQuickTextEdit, SLOT(updateDocument()));
     qmlobject_connect(control, QQuickTextControl, SIGNAL(updateCursorRequest()), q, QQuickTextEdit, SLOT(updateCursor()));
-    qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged()));
     qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SIGNAL(selectedTextChanged()));
-    qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SLOT(updateSelectionMarkers()));
-    qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SLOT(updateSelectionMarkers()));
+    qmlobject_connect(control, QQuickTextControl, SIGNAL(selectionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
+    qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SLOT(updateSelection()));
     qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorPositionChanged()), q, QQuickTextEdit, SIGNAL(cursorPositionChanged()));
     qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorRectangleChanged()), q, QQuickTextEdit, SLOT(moveCursorDelegate()));
     qmlobject_connect(control, QQuickTextControl, SIGNAL(linkActivated(QString)), q, QQuickTextEdit, SIGNAL(linkActivated(QString)));
+    qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged()));
 #ifndef QT_NO_CLIPBOARD
     qmlobject_connect(QGuiApplication::clipboard(), QClipboard, SIGNAL(dataChanged()), q, QQuickTextEdit, SLOT(q_canPasteChanged()));
 #endif
     qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(undoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canUndoChanged()));
     qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(redoAvailable(bool)), q, QQuickTextEdit, SIGNAL(canRedoChanged()));
     qmlobject_connect(document, QQuickTextDocumentWithImageResources, SIGNAL(imagesLoaded()), q, QQuickTextEdit, SLOT(updateSize()));
+    QObject::connect(document, &QQuickTextDocumentWithImageResources::contentsChange, q, &QQuickTextEdit::q_contentsChange);
 
     document->setDefaultFont(font);
     document->setDocumentMargin(textMargin);
     document->setUndoRedoEnabled(false); // flush undo buffer.
     document->setUndoRedoEnabled(true);
     updateDefaultTextOption();
+    q->updateSize();
 }
 
 void QQuickTextEdit::q_textChanged()
@@ -1857,6 +1974,44 @@ void QQuickTextEdit::q_textChanged()
     emit textChanged();
 }
 
+void QQuickTextEdit::markDirtyNodesForRange(int start, int end, int charDelta)
+{
+    Q_D(QQuickTextEdit);
+    if (start == end)
+        return;
+    TextNode dummyNode(start, 0);
+    TextNodeIterator it = qLowerBound(d->textNodeMap.begin(), d->textNodeMap.end(), &dummyNode, &comesBefore);
+    // qLowerBound gives us the first node past the start of the affected portion, rewind by one if we can.
+    if (it != d->textNodeMap.begin())
+        --it;
+
+    // mark the affected nodes as dirty
+    while (it != d->textNodeMap.constEnd()) {
+        if ((*it)->startPos() <= end)
+            (*it)->setDirty();
+        else if (charDelta)
+            (*it)->moveStartPos(charDelta);
+        else
+            return;
+        ++it;
+    }
+}
+
+void QQuickTextEdit::q_contentsChange(int pos, int charsRemoved, int charsAdded)
+{
+    Q_D(QQuickTextEdit);
+
+    const int editRange = pos + qMax(charsAdded, charsRemoved);
+    const int delta = charsAdded - charsRemoved;
+
+    markDirtyNodesForRange(pos, editRange, delta);
+
+    if (isComponentComplete()) {
+        d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
+        update();
+    }
+}
+
 void QQuickTextEdit::moveCursorDelegate()
 {
     Q_D(QQuickTextEdit);
@@ -1871,9 +2026,21 @@ void QQuickTextEdit::moveCursorDelegate()
     d->cursorItem->setY(cursorRect.y());
 }
 
-void QQuickTextEdit::updateSelectionMarkers()
+void QQuickTextEdit::updateSelection()
 {
     Q_D(QQuickTextEdit);
+
+    // No need for node updates when we go from an empty selection to another empty selection
+    if (d->control->textCursor().hasSelection() || d->hadSelection) {
+        markDirtyNodesForRange(qMin(d->lastSelectionStart, d->control->textCursor().selectionStart()), qMax(d->control->textCursor().selectionEnd(), d->lastSelectionEnd), 0);
+        if (isComponentComplete()) {
+            d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
+            update();
+        }
+    }
+
+    d->hadSelection = d->control->textCursor().hasSelection();
+
     if (d->lastSelectionStart != d->control->textCursor().selectionStart()) {
         d->lastSelectionStart = d->control->textCursor().selectionStart();
         emit selectionStartChanged();
@@ -1938,70 +2105,70 @@ qreal QQuickTextEditPrivate::getImplicitWidth() const
 void QQuickTextEdit::updateSize()
 {
     Q_D(QQuickTextEdit);
-    if (isComponentComplete()) {
-        qreal naturalWidth = d->implicitWidth;
-        // ### assumes that if the width is set, the text will fill to edges
-        // ### (unless wrap is false, then clipping will occur)
-        if (widthValid()) {
-            if (!d->requireImplicitWidth) {
-                emit implicitWidthChanged();
-                // if the implicitWidth is used, then updateSize() has already been called (recursively)
-                if (d->requireImplicitWidth)
-                    return;
-            }
-            if (d->requireImplicitWidth) {
-                d->document->setTextWidth(-1);
-                naturalWidth = d->document->idealWidth();
-
-                const bool wasInLayout = d->inLayout;
-                d->inLayout = true;
-                setImplicitWidth(naturalWidth);
-                d->inLayout = wasInLayout;
-                if (d->inLayout)    // probably the result of a binding loop, but by letting it
-                    return;         // get this far we'll get a warning to that effect.
-            }
-            if (d->document->textWidth() != width())
-                d->document->setTextWidth(width());
-        } else {
+    if (!isComponentComplete()) {
+        d->dirty = true;
+        return;
+    }
+
+    qreal naturalWidth = d->implicitWidth;
+
+    qreal newWidth = d->document->idealWidth();
+    // ### assumes that if the width is set, the text will fill to edges
+    // ### (unless wrap is false, then clipping will occur)
+    if (widthValid()) {
+        if (!d->requireImplicitWidth) {
+            emit implicitWidthChanged();
+            // if the implicitWidth is used, then updateSize() has already been called (recursively)
+            if (d->requireImplicitWidth)
+                return;
+        }
+        if (d->requireImplicitWidth) {
             d->document->setTextWidth(-1);
+            naturalWidth = d->document->idealWidth();
+
+            const bool wasInLayout = d->inLayout;
+            d->inLayout = true;
+            setImplicitWidth(naturalWidth);
+            d->inLayout = wasInLayout;
+            if (d->inLayout)    // probably the result of a binding loop, but by letting it
+                return;         // get this far we'll get a warning to that effect.
         }
-
+        if (d->document->textWidth() != width())
+            d->document->setTextWidth(width());
         //### need to confirm cost of always setting these
-        qreal newWidth = d->document->idealWidth();
-        if ((!widthValid() || d->wrapMode == NoWrap) && d->document->textWidth() != newWidth)
-            d->document->setTextWidth(newWidth); // ### Text does not align if width is not set or the idealWidth exceeds the textWidth (QTextDoc bug)
-        // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
-        qreal iWidth = -1;
-        if (!widthValid() && !d->requireImplicitWidth)
-            iWidth = newWidth;
-
-        QFontMetricsF fm(d->font);
-        qreal newHeight = d->document->isEmpty() ? qCeil(fm.height()) : d->document->size().height();
-
-        if (iWidth > -1)
-            setImplicitSize(iWidth, newHeight);
-        else
-            setImplicitHeight(newHeight);
+    } else if (d->wrapMode == NoWrap && d->document->textWidth() != newWidth) {
+        d->document->setTextWidth(newWidth); // ### Text does not align if width is not set or the idealWidth exceeds the textWidth (QTextDoc bug)
+    } else {
+        d->document->setTextWidth(-1);
+    }
 
-        d->xoff = QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign());
-        d->yoff = QQuickTextUtil::alignedY(d->document->size().height(), height(), d->vAlign);
-        setBaselineOffset(fm.ascent() + d->yoff + d->textMargin);
+    QFontMetricsF fm(d->font);
+    qreal newHeight = d->document->isEmpty() ? qCeil(fm.height()) : d->document->size().height();
 
-        QSizeF size(newWidth, newHeight);
-        if (d->contentSize != size) {
-            d->contentSize = size;
-            emit contentSizeChanged();
-        }
-    } else {
-        d->dirty = true;
+    // ### Setting the implicitWidth triggers another updateSize(), and unless there are bindings nothing has changed.
+    if (!widthValid() && !d->requireImplicitWidth)
+        setImplicitSize(newWidth, newHeight);
+    else
+        setImplicitHeight(newHeight);
+
+    d->xoff = qMax(qreal(0), QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign()));
+    d->yoff = QQuickTextUtil::alignedY(d->document->size().height(), height(), d->vAlign);
+    setBaselineOffset(fm.ascent() + d->yoff + d->textMargin);
+
+    QSizeF size(newWidth, newHeight);
+    if (d->contentSize != size) {
+        d->contentSize = size;
+        emit contentSizeChanged();
     }
-    updateDocument();
 }
 
-void QQuickTextEdit::updateDocument()
+void QQuickTextEdit::updateWholeDocument()
 {
     Q_D(QQuickTextEdit);
-    d->documentDirty = true;
+    if (!d->textNodeMap.isEmpty()) {
+        Q_FOREACH (TextNode* node, d->textNodeMap)
+            node->setDirty();
+    }
 
     if (isComponentComplete()) {
         d->updateType = QQuickTextEditPrivate::UpdatePaintNode;
@@ -2023,7 +2190,7 @@ void QQuickTextEdit::q_updateAlignment()
     Q_D(QQuickTextEdit);
     if (d->determineHorizontalAlignment()) {
         d->updateDefaultTextOption();
-        d->xoff = QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign());
+        d->xoff = qMax(qreal(0), QQuickTextUtil::alignedX(d->document->size().width(), width(), effectiveHAlign()));
         moveCursorDelegate();
     }
 }
index 8a2d9b1..4e09eaf 100644 (file)
@@ -313,17 +313,19 @@ public Q_SLOTS:
 
 private Q_SLOTS:
     void q_textChanged();
-    void updateSelectionMarkers();
+    void q_contentsChange(int, int, int);
+    void updateSelection();
     void moveCursorDelegate();
     void createCursor();
     void q_canPasteChanged();
-    void updateDocument();
+    void updateWholeDocument();
     void updateCursor();
     void q_updateAlignment();
     void updateSize();
     void triggerPreprocess();
 
 private:
+    void markDirtyNodesForRange(int start, int end, int charDelta);
     void updateTotalLines();
 
 protected:
index dd0f76f..feb7e98 100644 (file)
 #include "qquicktextcontrol_p.h"
 
 #include <QtQml/qqml.h>
+#include <QtCore/qlist.h>
 
 QT_BEGIN_NAMESPACE
 class QTextLayout;
 class QQuickTextDocumentWithImageResources;
 class QQuickTextControl;
+class QQuickTextNode;
+class QSGSimpleRectNode;
 class QQuickTextEditPrivate : public QQuickImplicitSizeItemPrivate
 {
 public:
@@ -70,10 +73,26 @@ public:
 
     typedef QQuickTextEdit Public;
 
+    struct Node {
+        explicit Node(int startPos, QQuickTextNode* node)
+            : m_startPos(startPos), m_node(node), m_dirty(false) { }
+        QQuickTextNode* textNode() const { return m_node; }
+        void moveStartPos(int delta) { Q_ASSERT(m_startPos + delta > 0); m_startPos += delta; }
+        int startPos() const { return m_startPos; }
+        void setDirty() { m_dirty = true; }
+        bool dirty() const { return m_dirty; }
+
+    private:
+        int m_startPos;
+        QQuickTextNode* m_node;
+        bool m_dirty;
+    };
+
+
     QQuickTextEditPrivate()
         : color(QRgb(0xFF000000)), selectionColor(QRgb(0xFF000080)), selectedTextColor(QRgb(0xFFFFFFFF))
         , textMargin(0.0), xoff(0), yoff(0), font(sourceFont), cursorComponent(0), cursorItem(0), document(0), control(0)
-        , quickDocument(0)
+        , quickDocument(0), frameDecorationsNode(0), cursorNode(0)
         , lastSelectionStart(0), lastSelectionEnd(0), lineCount(0)
         , hAlign(QQuickTextEdit::AlignLeft), vAlign(QQuickTextEdit::AlignTop)
         , format(QQuickTextEdit::PlainText), wrapMode(QQuickTextEdit::NoWrap)
@@ -84,10 +103,11 @@ public:
         , inputMethodHints(Qt::ImhNone)
 #endif
         , updateType(UpdatePaintNode)
-        , documentDirty(true), dirty(false), richText(false), cursorVisible(false), cursorPending(false)
+        , dirty(false), richText(false), cursorVisible(false), cursorPending(false)
         , focusOnPress(true), persistentSelection(false), requireImplicitWidth(false)
         , selectByMouse(false), canPaste(false), canPasteValid(false), hAlignImplicit(true)
         , textCached(true), inLayout(false), selectByKeyboard(false), selectByKeyboardSet(false)
+        , hadSelection(false)
     {
     }
 
@@ -131,6 +151,9 @@ public:
     QQuickTextDocumentWithImageResources *document;
     QQuickTextControl *control;
     QQuickTextDocument *quickDocument;
+    QList<Node*> textNodeMap;
+    QQuickTextNode *frameDecorationsNode;
+    QSGSimpleRectNode *cursorNode;
 
     int lastSelectionStart;
     int lastSelectionEnd;
@@ -154,7 +177,6 @@ public:
 #endif
     UpdateType updateType;
 
-    bool documentDirty : 1;
     bool dirty : 1;
     bool richText : 1;
     bool cursorVisible : 1;
@@ -170,6 +192,7 @@ public:
     bool inLayout:1;
     bool selectByKeyboard:1;
     bool selectByKeyboardSet:1;
+    bool hadSelection : 1;
 };
 
 QT_END_NAMESPACE
index 5e1b20c..d469594 100644 (file)
@@ -41,6 +41,8 @@
 
 #include "qquicktextnode_p.h"
 
+#include "qquicktextnodeengine_p.h"
+
 #include <QtQuick/qsgsimplerectnode.h>
 #include <private/qsgadaptationlayer_p.h>
 #include <private/qsgdistancefieldglyphnode_p.h>
 #include <qtextlayout.h>
 #include <qabstracttextdocumentlayout.h>
 #include <qxmlstream.h>
-#include <qrawfont.h>
-#include <qtexttable.h>
-#include <qtextlist.h>
 #include <private/qquickstyledtext_p.h>
-#include <private/qquicktext_p_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
 
+namespace {
+
+    class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
+    {
+    public:
+        inline QTextCharFormat formatAccessor(int pos)
+        {
+            return format(pos);
+        }
+    };
+
+}
+
 /*!
   Creates an empty QQuickTextNode
 */
@@ -166,937 +176,14 @@ void QQuickTextNode::setCursor(const QRectF &rect, const QColor &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;
-        QQuickDefaultClipNode *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,
-                           QQuickTextNode::Decorations decorations,
-                           const QColor &textColor,
-                           const QColor &backgroundColor,
-                           const QPointF &position)
-        {
-            QRectF searchRect = glyphRun.boundingRect();
-            searchRect.translate(position);
-
-            if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
-                return;
-
-            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);
-        int addText(const QTextBlock &block,
-                    const QTextCharFormat &charFormat,
-                    const QColor &textColor,
-                    const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
-                    int textPos, int fragmentEnd,
-                    int selectionStart, int selectionEnd);
-        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 setAnchorColor(const QColor &anchorColor)
-        {
-            m_anchorColor = anchorColor;
-        }
-
-        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;
-        QColor m_anchorColor;
-        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;
-    };
-
-    int SelectionEngine::addText(const QTextBlock &block,
-                                 const QTextCharFormat &charFormat,
-                                 const QColor &textColor,
-                                 const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
-                                 int textPos, int fragmentEnd,
-                                 int selectionStart, int selectionEnd)
-    {
-        if (charFormat.foreground().style() != Qt::NoBrush)
-            setTextColor(charFormat.foreground().color());
-        else
-            setTextColor(textColor);
-
-        while (textPos < fragmentEnd) {
-            int blockRelativePosition = textPos - block.position();
-            QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
-            if (!currentLine().isValid()
-                || line.lineNumber() != currentLine().lineNumber()) {
-                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;
-
-            addGlyphsForRanges(colorChanges,
-                               textPos - block.position(),
-                               currentStepEnd - block.position(),
-                               selectionStart - block.position(),
-                               selectionEnd - block.position());
-
-            textPos = currentStepEnd;
-        }
-        return textPos;
-    }
-
-    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()) {
-            QQuickDefaultClipNode *currentClipNode = m_hasSelection ? new QQuickDefaultClipNode(QRectF()) : 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->setRect(currentRect);
-                            currentClipNode->update();
-                        }
-                    }
-
-                    if (node != 0 && m_hasSelection)
-                        currentClipNode = new QQuickDefaultClipNode(QRectF());
-                    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 + m_currentLine.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 - lastNode->ascent));
-                } else {
-                    QPointF lastPos = lastNode->boundingRect.topRight();
-                    searchRect.moveTopLeft(lastPos - QPointF(0, ascent - lastNode->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();
-                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);
-                }
-            }
-
-            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,
-                               QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position);
-    }
-
-    void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
-    {
-        int currentSize = m_currentLineTree.size();
-        BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
-                               QQuickTextNode::NoDecoration, 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;
-                if (range.format.hasProperty(QTextFormat::ForegroundBrush))
-                    rangeColor = range.format.foreground().color();
-                else if (range.format.isAnchor())
-                    rangeColor = m_anchorColor;
-                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<QQuickDefaultClipNode *, QPair<QRgb, int> > > KeyType;
-        QHash<KeyType, BinaryTreeNode *> map;
-        QList<BinaryTreeNode *> nodes;
-        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);
-                    nodes.append(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.
-        foreach (const BinaryTreeNode *node, nodes) {
-
-            QQuickDefaultClipNode *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);
-        }
-    }
-}
-
-void QQuickTextNode::mergeFormats(QTextLayout *textLayout,
-                               QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
+void QQuickTextNode::initEngine(const QColor& textColor, const QColor& selectedTextColor, const QColor& selectionColor, const QColor& anchorColor)
 {
-    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)
-         || additionalFormat.format.isAnchor()) {
-            // 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);
-        }
-    };
-
+    m_engine.reset(new QQuickTextNodeEngine);
+    m_engine->m_hasContents = false;
+    m_engine->setTextColor(textColor);
+    m_engine->setSelectedTextColor(selectedTextColor);
+    m_engine->setSelectionColor(selectionColor);
+    m_engine->setAnchorColor(anchorColor);
 }
 
 void QQuickTextNode::addImage(const QRectF &rect, const QImage &image)
@@ -1118,11 +205,7 @@ void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *tex
                                   const QColor &selectionColor, const QColor &selectedTextColor,
                                   int selectionStart, int selectionEnd)
 {
-    SelectionEngine engine;
-    engine.setTextColor(textColor);
-    engine.setSelectedTextColor(selectedTextColor);
-    engine.setSelectionColor(selectionColor);
-    engine.setAnchorColor(anchorColor);
+    initEngine(textColor, selectedTextColor, selectionColor, anchorColor);
 
     QList<QTextFrame *> frames;
     frames.append(textDocument->rootFrame());
@@ -1130,7 +213,7 @@ void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *tex
         QTextFrame *textFrame = frames.takeFirst();
         frames.append(textFrame->childFrames());
 
-        engine.addFrameDecorations(textDocument, textFrame);
+        m_engine->addFrameDecorations(textDocument, textFrame);
 
         if (textFrame->firstPosition() > textFrame->lastPosition()
          && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
@@ -1140,160 +223,23 @@ void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *tex
             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,
+            m_engine->setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
+            m_engine->addTextObject(rect.topLeft(), format, QQuickTextNodeEngine::Unselected, textDocument,
                                  pos, textFrame->frameFormat().position());
         } else {
             QTextFrame::iterator it = textFrame->begin();
 
             while (!it.atEnd()) {
-                Q_ASSERT(!engine.currentLine().isValid());
+                Q_ASSERT(!m_engine->currentLine().isValid());
 
                 QTextBlock block = it.currentBlock();
-#ifndef QT_NO_IM
-                int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0;
-                int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
-#endif
-
-                QVarLengthArray<QTextLayout::FormatRange> colorChanges;
-                mergeFormats(block.layout(), &colorChanges);
-
-                QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft() + position;
-                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) {
-                            int blockRelativePosition = textPos - block.position();
-                            QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
-                            if (!engine.currentLine().isValid()
-                                    || line.lineNumber() != engine.currentLine().lineNumber()) {
-                                engine.setCurrentLine(line);
-                            }
-
-                            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 if (charFormat.isAnchor())
-                            engine.setTextColor(anchorColor);
-                        else
-                            engine.setTextColor(textColor);
-
-                        int fragmentEnd = textPos + fragment.length();
-#ifndef QT_NO_IM
-                        if (preeditPosition >= 0
-                         && preeditPosition >= textPos
-                         && preeditPosition <= fragmentEnd) {
-                            fragmentEnd += preeditLength;
-                        }
-#endif
-
-                        textPos = engine.addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd,
-                                       selectionStart, selectionEnd);
-                    }
-
-                    ++blockIterator;
-                }
-
-#ifndef QT_NO_IM
-                if (preeditLength >= 0 && textPos <= block.position() + preeditPosition) {
-                    engine.setPosition(blockPosition);
-                    textPos = block.position() + preeditPosition;
-                    QTextLine line = block.layout()->lineForTextPosition(preeditPosition);
-                    if (!engine.currentLine().isValid()
-                            || line.lineNumber() != engine.currentLine().lineNumber()) {
-                        engine.setCurrentLine(line);
-                    }
-                    textPos = engine.addText(block, block.charFormat(), textColor, colorChanges,
-                                             textPos, textPos + preeditLength,
-                                             selectionStart, selectionEnd);
-                }
-#endif
-
-                engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
+                m_engine->addTextBlock(textDocument, block, position, textColor, anchorColor, selectionStart, selectionEnd);
                 ++it;
             }
         }
     }
 
-    engine.addToSceneGraph(this, style, styleColor);
+    m_engine->addToSceneGraph(this, style, styleColor);
 }
 
 void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
@@ -1303,12 +249,7 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
                                 int selectionStart, int selectionEnd,
                                 int lineStart, int lineCount)
 {
-    SelectionEngine engine;
-    engine.setTextColor(color);
-    engine.setSelectedTextColor(selectedTextColor);
-    engine.setSelectionColor(selectionColor);
-    engine.setAnchorColor(anchorColor);
-    engine.setPosition(position);
+    initEngine(color, selectedTextColor, selectionColor, anchorColor);
 
 #ifndef QT_NO_IM
     int preeditLength = textLayout->preeditAreaText().length();
@@ -1316,7 +257,7 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
 #endif
 
     QVarLengthArray<QTextLayout::FormatRange> colorChanges;
-    mergeFormats(textLayout, &colorChanges);
+    m_engine->mergeFormats(textLayout, &colorChanges);
 
     lineCount = lineCount >= 0
             ? qMin(lineStart + lineCount, textLayout->lineCount())
@@ -1337,11 +278,11 @@ void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLay
         }
 #endif
 
-        engine.setCurrentLine(line);
-        engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
+        m_engine->setCurrentLine(line);
+        m_engine->addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
     }
 
-    engine.addToSceneGraph(this, style, styleColor);
+    m_engine->addToSceneGraph(this, style, styleColor);
 }
 
 void QQuickTextNode::deleteContent()
index dcc4ebe..16da3ce 100644 (file)
@@ -49,6 +49,7 @@
 #include <QtGui/qcolor.h>
 #include <QtGui/qtextlayout.h>
 #include <QtCore/qvarlengtharray.h>
+#include <QtCore/qscopedpointer.h>
 
 QT_BEGIN_NAMESPACE
 
@@ -62,6 +63,8 @@ class QSGSimpleRectNode;
 class QSGClipNode;
 class QSGTexture;
 
+class QQuickTextNodeEngine;
+
 class QQuickTextNode : public QSGTransformNode
 {
 public:
@@ -104,13 +107,17 @@ public:
     void setUseNativeRenderer(bool on) { m_useNativeRenderer = on; }
 
 private:
-    void mergeFormats(QTextLayout *textLayout, QVarLengthArray<QTextLayout::FormatRange> *mergedFormats);
+    void initEngine(const QColor &textColor, const QColor &selectedTextColor, const QColor &selectionColor, const QColor& anchorColor = QColor());
+
 
     QSGContext *m_context;
     QSGSimpleRectNode *m_cursorNode;
     QList<QSGTexture *> m_textures;
     QQuickItem *m_ownerElement;
     bool m_useNativeRenderer;
+    QScopedPointer<QQuickTextNodeEngine> m_engine;
+
+    friend class QQuickTextEdit;
 };
 
 QT_END_NAMESPACE
diff --git a/src/quick/items/qquicktextnodeengine.cpp b/src/quick/items/qquicktextnodeengine.cpp
new file mode 100644 (file)
index 0000000..7bbfd1b
--- /dev/null
@@ -0,0 +1,935 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml 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 Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional
+** rights.  These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qquicktextnodeengine_p.h"
+
+#include <QtCore/qpoint.h>
+#include <QtGui/qabstracttextdocumentlayout.h>
+#include <QtGui/qrawfont.h>
+#include <QtGui/qtextdocument.h>
+#include <QtGui/qtextlayout.h>
+#include <QtGui/qtextobject.h>
+#include <QtGui/qtexttable.h>
+#include <QtGui/qtextlist.h>
+#include <QtQuick/qsgsimplerectnode.h>
+
+#include <private/qquicktext_p_p.h>
+#include <private/qtextdocumentlayout_p.h>
+#include <private/qtextimagehandler_p.h>
+#include <private/qrawfont_p.h>
+
+QT_BEGIN_NAMESPACE
+
+void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode> *binaryTree, const QGlyphRun &glyphRun, SelectionState selectionState,
+                                             QQuickTextNode::Decorations decorations, const QColor &textColor,
+                                             const QColor &backgroundColor, const QPointF &position)
+{
+    QRectF searchRect = glyphRun.boundingRect();
+    searchRect.translate(position);
+
+    if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
+        return;
+
+    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));
+}
+
+void QQuickTextNodeEngine::BinaryTreeNode::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;
+            }
+        }
+    }
+}
+
+void QQuickTextNodeEngine::BinaryTreeNode::inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree,
+                                              QVarLengthArray<int> *sortedIndexes, int currentIndex)
+{
+    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);
+}
+
+
+int QQuickTextNodeEngine::addText(const QTextBlock &block,
+                                  const QTextCharFormat &charFormat,
+                                  const QColor &textColor,
+                                  const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
+                                  int textPos, int fragmentEnd,
+                                  int selectionStart, int selectionEnd)
+{
+    if (charFormat.foreground().style() != Qt::NoBrush)
+        setTextColor(charFormat.foreground().color());
+    else
+        setTextColor(textColor);
+
+    while (textPos < fragmentEnd) {
+        int blockRelativePosition = textPos - block.position();
+        QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
+        if (!currentLine().isValid()
+                || line.lineNumber() != currentLine().lineNumber()) {
+            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;
+
+        addGlyphsForRanges(colorChanges,
+                           textPos - block.position(),
+                           currentStepEnd - block.position(),
+                           selectionStart - block.position(),
+                           selectionEnd - block.position());
+
+        textPos = currentStepEnd;
+    }
+    return textPos;
+}
+
+void QQuickTextNodeEngine::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 QQuickTextNodeEngine::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());
+
+    SelectionState currentSelectionState = 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()) {
+        QQuickDefaultClipNode *currentClipNode = m_hasSelection ? new QQuickDefaultClipNode(QRectF()) : 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 == Selected)
+                    m_selectionRects.append(currentRect);
+
+                if (currentClipNode != 0) {
+                    if (!currentClipNodeUsed) {
+                        delete currentClipNode;
+                    } else {
+                        currentClipNode->setIsRectangular(true);
+                        currentClipNode->setRect(currentRect);
+                        currentClipNode->update();
+                    }
+                }
+
+                if (node != 0 && m_hasSelection)
+                    currentClipNode = new QQuickDefaultClipNode(QRectF());
+                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 QQuickTextNodeEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent,
+                                    SelectionState selectionState,
+                                    QTextFrameFormat::Position layoutPosition)
+{
+    QRectF searchRect = rect;
+    if (layoutPosition == QTextFrameFormat::InFlow) {
+        if (m_currentLineTree.isEmpty()) {
+            searchRect.moveTopLeft(m_position + m_currentLine.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 - lastNode->ascent));
+            } else {
+                QPointF lastPos = lastNode->boundingRect.topRight();
+                searchRect.moveTopLeft(lastPos - QPointF(0, ascent - lastNode->ascent));
+            }
+        }
+    }
+
+    BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
+    m_hasContents = true;
+}
+
+void QQuickTextNodeEngine::addTextObject(const QPointF &position, const QTextCharFormat &format,
+                                         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();
+            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);
+            }
+        }
+
+        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 QQuickTextNodeEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
+{
+    BinaryTreeNode::insert(&m_currentLineTree, glyphRun, Unselected,
+                           QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position);
+}
+
+void QQuickTextNodeEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
+{
+    int currentSize = m_currentLineTree.size();
+    BinaryTreeNode::insert(&m_currentLineTree, glyphRun, Selected,
+                           QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position);
+    m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
+}
+
+void QQuickTextNodeEngine::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;
+            if (range.format.hasProperty(QTextFormat::ForegroundBrush))
+                rangeColor = range.format.foreground().color();
+            else if (range.format.isAnchor())
+                rangeColor = m_anchorColor;
+            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 QQuickTextNodeEngine::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 QQuickTextNodeEngine::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 QQuickTextNodeEngine::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  QQuickTextNodeEngine::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 == 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<QQuickDefaultClipNode *, QPair<QRgb, int> > > KeyType;
+    QHash<KeyType, BinaryTreeNode *> map;
+    QList<BinaryTreeNode *> nodes;
+    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();
+                otherGlyphPositions.reserve(otherGlyphPositions.size() + glyphPositions.size());
+                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);
+                nodes.append(node);
+            }
+        } else {
+            parentNode->addImage(node->boundingRect, node->image);
+            if (node->selectionState == Selected) {
+                QColor color = m_selectionColor;
+                color.setAlpha(128);
+                parentNode->appendChildNode(new QSGSimpleRectNode(node->boundingRect, color));
+            }
+        }
+    }
+
+    foreach (const BinaryTreeNode *node, nodes) {
+
+        QQuickDefaultClipNode *clipNode = node->clipNode;
+        if (clipNode != 0 && clipNode->parent() == 0 )
+            parentNode->appendChildNode(clipNode);
+
+        QColor color = node->selectionState == Selected
+                ? m_selectedTextColor
+                : node->color;
+
+        parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
+    }
+}
+
+void QQuickTextNodeEngine::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)
+         || additionalFormat.format.isAnchor()) {
+            // 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);
+        }
+    }
+
+}
+
+void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QTextBlock &block, const QPointF &position, const QColor &textColor, const QColor &anchorColor, int selectionStart, int selectionEnd)
+{
+    Q_ASSERT(textDocument);
+#ifndef QT_NO_IM
+    int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0;
+    int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
+#endif
+
+    QVarLengthArray<QTextLayout::FormatRange> colorChanges;
+    mergeFormats(block.layout(), &colorChanges);
+
+    QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft() + position;
+    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());
+
+            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();
+            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)
+                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();
+        setPosition(blockPosition);
+        if (text.contains(QChar::ObjectReplacementCharacter)) {
+            QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
+            if (frame && frame->frameFormat().position() == QTextFrameFormat::InFlow) {
+                int blockRelativePosition = textPos - block.position();
+                QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
+                if (!currentLine().isValid()
+                        || line.lineNumber() != currentLine().lineNumber()) {
+                    setCurrentLine(line);
+                }
+
+                QQuickTextNodeEngine::SelectionState selectionState =
+                        (selectionStart < textPos + text.length()
+                         && selectionEnd >= textPos)
+                        ? QQuickTextNodeEngine::Selected
+                        : QQuickTextNodeEngine::Unselected;
+
+                addTextObject(QPointF(), charFormat, selectionState, textDocument, textPos);
+            }
+            textPos += text.length();
+        } else {
+            if (charFormat.foreground().style() != Qt::NoBrush)
+                setTextColor(charFormat.foreground().color());
+            else if (charFormat.isAnchor())
+                setTextColor(anchorColor);
+            else
+                setTextColor(textColor);
+
+            int fragmentEnd = textPos + fragment.length();
+#ifndef QT_NO_IM
+            if (preeditPosition >= 0
+                    && preeditPosition >= textPos
+                    && preeditPosition <= fragmentEnd) {
+                fragmentEnd += preeditLength;
+            }
+#endif
+
+            textPos = addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd,
+                                                 selectionStart, selectionEnd);
+        }
+
+        ++blockIterator;
+    }
+
+#ifndef QT_NO_IM
+    if (preeditLength >= 0 && textPos <= block.position() + preeditPosition) {
+        setPosition(blockPosition);
+        textPos = block.position() + preeditPosition;
+        QTextLine line = block.layout()->lineForTextPosition(preeditPosition);
+        if (!currentLine().isValid()
+                || line.lineNumber() != currentLine().lineNumber()) {
+            setCurrentLine(line);
+        }
+        textPos = addText(block, block.charFormat(), textColor, colorChanges,
+                                             textPos, textPos + preeditLength,
+                                             selectionStart, selectionEnd);
+    }
+#endif
+
+    setCurrentLine(QTextLine()); // Reset current line because the text layout changed
+    m_hasContents = true;
+}
+
+
+QT_END_NAMESPACE
+
diff --git a/src/quick/items/qquicktextnodeengine_p.h b/src/quick/items/qquicktextnodeengine_p.h
new file mode 100644 (file)
index 0000000..6a98d6b
--- /dev/null
@@ -0,0 +1,234 @@
+/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtQml 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 Digia.  For licensing terms and
+** conditions see http://qt.digia.com/licensing.  For further information
+** use the contact form at http://qt.digia.com/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 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, Digia gives you certain additional
+** rights.  These rights are described in the Digia 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.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtCore/qlist.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtGui/qcolor.h>
+#include <QtGui/qglyphrun.h>
+#include <QtGui/qimage.h>
+#include <QtGui/qtextdocument.h>
+#include <QtGui/qtextlayout.h>
+#include "qquickclipnode_p.h"
+#include "qquicktextnode_p.h"
+
+#ifndef QQUICKTEXTNODEENGINE_P_H
+#define QQUICKTEXTNODEENGINE_P_H
+
+QT_BEGIN_NAMESPACE
+
+// 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 QQuickTextNodeEngine {
+
+public:
+
+    enum SelectionState {
+        Unselected,
+        Selected
+    };
+
+    struct BinaryTreeNode {
+
+        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;
+        QQuickDefaultClipNode *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,
+                           QQuickTextNode::Decorations decorations, const QColor &textColor, const QColor &backgroundColor, const QPointF &position);
+        static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree, const BinaryTreeNode &binaryTreeNode);
+        static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree, QVarLengthArray<int> *sortedIndexes, int currentIndex = 0);
+    };
+
+    QQuickTextNodeEngine() : m_hasSelection(false), m_hasContents(false) {}
+
+    bool hasContents() const { return m_hasContents; }
+    void addTextBlock(QTextDocument *, const QTextBlock &, const QPointF &position, const QColor &textColor, const QColor& anchorColor, int selectionStart, int selectionEnd);
+    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,
+                  SelectionState selectionState,
+                  QTextFrameFormat::Position layoutPosition);
+    int addText(const QTextBlock &block,
+                const QTextCharFormat &charFormat,
+                const QColor &textColor,
+                const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
+                int textPos, int fragmentEnd,
+                int selectionStart, int selectionEnd);
+    void addTextObject(const QPointF &position, const QTextCharFormat &format,
+                       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 setAnchorColor(const QColor &anchorColor)
+    {
+        m_anchorColor = anchorColor;
+    }
+
+    void setPosition(const QPointF &position)
+    {
+        m_position = position;
+    }
+
+
+
+
+private:
+    struct TextDecoration
+    {
+        TextDecoration() : selectionState(Unselected) {}
+        TextDecoration(const SelectionState &s,
+                       const QRectF &r,
+                       const QColor &c)
+            : selectionState(s)
+            , rect(r)
+            , color(c)
+        {
+        }
+
+        SelectionState selectionState;
+        QRectF rect;
+        QColor color;
+    };
+
+    void processCurrentLine();
+    void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations, qreal offset, qreal thickness);
+    void mergeFormats(QTextLayout *textLayout, QVarLengthArray<QTextLayout::FormatRange> *mergedFormats);
+
+    QColor m_selectionColor;
+    QColor m_textColor;
+    QColor m_backgroundColor;
+    QColor m_selectedTextColor;
+    QColor m_anchorColor;
+    QPointF m_position;
+
+    QTextLine m_currentLine;
+
+    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;
+
+    bool m_hasSelection : 1;
+    bool m_hasContents : 1;
+    friend class QQuickTextNode;
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QQUICKTEXTNODEENGINE_P_H