Update copyright year in Nokia copyright headers.
[profile/ivi/qtdeclarative.git] / src / quick / items / qquicktextnode.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qquicktextnode_p.h"
43 #include <QtQuick/qsgsimplerectnode.h>
44 #include <private/qsgadaptationlayer_p.h>
45 #include <private/qsgdistancefieldglyphnode_p.h>
46
47 #include <QtQuick/private/qsgcontext_p.h>
48
49 #include <QtCore/qpoint.h>
50 #include <qmath.h>
51 #include <qtextdocument.h>
52 #include <qtextlayout.h>
53 #include <qabstracttextdocumentlayout.h>
54 #include <qxmlstream.h>
55 #include <qrawfont.h>
56 #include <qtexttable.h>
57 #include <qtextlist.h>
58 #include <private/qdeclarativestyledtext_p.h>
59 #include <private/qfont_p.h>
60 #include <private/qfontengine_p.h>
61 #include <private/qrawfont_p.h>
62 #include <private/qtextimagehandler_p.h>
63 #include <private/qtextdocumentlayout_p.h>
64 #include <qhash.h>
65
66 QT_BEGIN_NAMESPACE
67
68 /*!
69   Creates an empty QQuickTextNode
70 */
71 QQuickTextNode::QQuickTextNode(QSGContext *context)
72     : m_context(context), m_cursorNode(0)
73 {
74 #if defined(QML_RUNTIME_TESTING)
75     description = QLatin1String("text");
76 #endif
77 }
78
79 QQuickTextNode::~QQuickTextNode()
80 {
81     qDeleteAll(m_textures);
82 }
83
84 #if 0
85 void QQuickTextNode::setColor(const QColor &color)
86 {
87     if (m_usePixmapCache) {
88         setUpdateFlag(UpdateNodes);
89     } else {
90         for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
91             if (childNode->subType() == GlyphNodeSubType) {
92                 QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
93                 if (glyphNode->color() == m_color)
94                     glyphNode->setColor(color);
95             } else if (childNode->subType() == SolidRectNodeSubType) {
96                 QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
97                 if (solidRectNode->color() == m_color)
98                     solidRectNode->setColor(color);
99             }
100         }
101     }
102     m_color = color;
103 }
104
105 void QQuickTextNode::setStyleColor(const QColor &styleColor)
106 {
107     if (m_textStyle != QQuickTextNode::NormalTextStyle) {
108         if (m_usePixmapCache) {
109             setUpdateFlag(UpdateNodes);
110         } else {
111             for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
112                 if (childNode->subType() == GlyphNodeSubType) {
113                     QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
114                     if (glyphNode->color() == m_styleColor)
115                         glyphNode->setColor(styleColor);
116                 } else if (childNode->subType() == SolidRectNodeSubType) {
117                     QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
118                     if (solidRectNode->color() == m_styleColor)
119                         solidRectNode->setColor(styleColor);
120                 }
121             }
122         }
123     }
124     m_styleColor = styleColor;
125 }
126 #endif
127
128 QSGGlyphNode *QQuickTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
129                                      QQuickText::TextStyle style, const QColor &styleColor,
130                                      QSGNode *parentNode)
131 {
132     QSGGlyphNode *node = m_context->createGlyphNode();
133     node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
134     node->setStyle(style);
135     node->setStyleColor(styleColor);
136     node->setColor(color);
137     node->update();
138
139     /* We flag the geometry as static, but we never call markVertexDataDirty
140        or markIndexDataDirty on them. This is because all text nodes are
141        discarded when a change occurs. If we start appending/removing from
142        existing geometry, then we also need to start marking the geometry as
143        dirty.
144      */
145     node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
146     node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
147
148     if (parentNode == 0)
149         parentNode = this;
150     parentNode->appendChildNode(node);
151
152     return node;
153 }
154
155 void QQuickTextNode::setCursor(const QRectF &rect, const QColor &color)
156 {
157     if (m_cursorNode != 0)
158         delete m_cursorNode;
159
160     m_cursorNode = new QSGSimpleRectNode(rect, color);
161     appendChildNode(m_cursorNode);
162 }
163
164 namespace {
165
166     struct BinaryTreeNode {
167         enum SelectionState {
168             Unselected,
169             Selected
170         };
171
172         BinaryTreeNode()
173             : selectionState(Unselected)
174             , clipNode(0)
175             , decorations(QQuickTextNode::NoDecoration)
176             , ascent(0.0)
177             , leftChildIndex(-1)
178             , rightChildIndex(-1)
179         {
180
181         }
182
183         BinaryTreeNode(const QRectF &brect, const QImage &i, SelectionState selState, qreal a)
184             : boundingRect(brect)
185             , selectionState(selState)
186             , clipNode(0)
187             , decorations(QQuickTextNode::NoDecoration)
188             , image(i)
189             , ascent(a)
190             , leftChildIndex(-1)
191             , rightChildIndex(-1)
192         {
193         }
194
195         BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
196                        const QQuickTextNode::Decorations &decs, const QColor &c, const QColor &bc,
197                        const QPointF &pos, qreal a)
198             : glyphRun(g)
199             , boundingRect(brect)
200             , selectionState(selState)
201             , clipNode(0)
202             , decorations(decs)
203             , color(c)
204             , backgroundColor(bc)
205             , position(pos)
206             , ascent(a)
207             , leftChildIndex(-1)
208             , rightChildIndex(-1)
209         {
210         }
211
212         QGlyphRun glyphRun;
213         QRectF boundingRect;
214         SelectionState selectionState;
215         QSGClipNode *clipNode;
216         QQuickTextNode::Decorations decorations;
217         QColor color;
218         QColor backgroundColor;
219         QPointF position;
220         QImage image;
221         qreal ascent;
222
223         int leftChildIndex;
224         int rightChildIndex;
225
226         static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
227                            const QRectF &rect,
228                            const QImage &image,
229                            qreal ascent,
230                            SelectionState selectionState)
231         {
232             insert(binaryTree, BinaryTreeNode(rect, image, selectionState, ascent));
233         }
234
235         static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
236                            const QGlyphRun &glyphRun,
237                            SelectionState selectionState,
238                            const QColor &textColor,
239                            const QColor &backgroundColor,
240                            const QPointF &position)
241         {
242             QRectF searchRect = glyphRun.boundingRect();
243             searchRect.translate(position);
244
245             if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
246                 return;
247
248             QQuickTextNode::Decorations decorations = QQuickTextNode::NoDecoration;
249             decorations |= (glyphRun.underline() ? QQuickTextNode::Underline : QQuickTextNode::NoDecoration);
250             decorations |= (glyphRun.overline()  ? QQuickTextNode::Overline  : QQuickTextNode::NoDecoration);
251             decorations |= (glyphRun.strikeOut() ? QQuickTextNode::StrikeOut : QQuickTextNode::NoDecoration);
252             decorations |= (backgroundColor.isValid() ? QQuickTextNode::Background : QQuickTextNode::NoDecoration);
253
254             qreal ascent = glyphRun.rawFont().ascent();
255             // ### QTBUG-22919 The bounding rect returned by QGlyphRun appears to start on the
256             // baseline, move it by the ascent so all bounding rects are at baseline - ascent.
257             searchRect.translate(0, -ascent);
258             insert(binaryTree, BinaryTreeNode(glyphRun, selectionState, searchRect, decorations,
259                                               textColor, backgroundColor, position, ascent));
260         }
261
262         static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
263                            const BinaryTreeNode &binaryTreeNode)
264         {
265             int newIndex = binaryTree->size();
266             binaryTree->append(binaryTreeNode);
267             if (newIndex == 0)
268                 return;
269
270             int searchIndex = 0;
271             forever {
272                 BinaryTreeNode *node = binaryTree->data() + searchIndex;
273                 if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) {
274                     if (node->leftChildIndex < 0) {
275                         node->leftChildIndex = newIndex;
276                         break;
277                     } else {
278                         searchIndex = node->leftChildIndex;
279                     }
280                 } else {
281                     if (node->rightChildIndex < 0) {
282                         node->rightChildIndex = newIndex;
283                         break;
284                     } else {
285                         searchIndex = node->rightChildIndex;
286                     }
287                 }
288             }
289         }
290
291         static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree,
292                             QVarLengthArray<int> *sortedIndexes,
293                             int currentIndex = 0)
294         {
295             Q_ASSERT(currentIndex < binaryTree.size());
296
297             const BinaryTreeNode *node = binaryTree.data() + currentIndex;
298             if (node->leftChildIndex >= 0)
299                 inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
300
301             sortedIndexes->append(currentIndex);
302
303             if (node->rightChildIndex >= 0)
304                 inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
305         }
306     };
307
308     // Engine that takes glyph runs as input, and produces a set of glyph nodes, clip nodes,
309     // and rectangle nodes to represent the text, decorations and selection. Will try to minimize
310     // number of nodes, and join decorations in neighbouring items
311     class SelectionEngine
312     {
313     public:
314         SelectionEngine() : m_hasSelection(false) {}
315
316         QTextLine currentLine() const { return m_currentLine; }
317
318         void setCurrentLine(const QTextLine &currentLine)
319         {
320             if (m_currentLine.isValid())
321                 processCurrentLine();
322
323             m_currentLine = currentLine;
324         }
325
326         void addBorder(const QRectF &rect, qreal border, QTextFrameFormat::BorderStyle borderStyle,
327                        const QBrush &borderBrush);
328         void addFrameDecorations(QTextDocument *document, QTextFrame *frame);
329         void addImage(const QRectF &rect, const QImage &image, qreal ascent,
330                       BinaryTreeNode::SelectionState selectionState,
331                       QTextFrameFormat::Position layoutPosition);
332         int addText(const QTextBlock &block,
333                     const QTextCharFormat &charFormat,
334                     const QColor &textColor,
335                     const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
336                     int textPos, int fragmentEnd,
337                     int selectionStart, int selectionEnd);
338         void addTextObject(const QPointF &position, const QTextCharFormat &format,
339                            BinaryTreeNode::SelectionState selectionState,
340                            QTextDocument *textDocument, int pos,
341                            QTextFrameFormat::Position layoutPosition = QTextFrameFormat::InFlow);
342         void addSelectedGlyphs(const QGlyphRun &glyphRun);
343         void addUnselectedGlyphs(const QGlyphRun &glyphRun);
344         void addGlyphsInRange(int rangeStart, int rangeEnd,
345                               const QColor &color, const QColor &backgroundColor,
346                               int selectionStart, int selectionEnd);
347         void addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
348                                 int start, int end,
349                                 int selectionStart, int selectionEnd);
350
351         void addToSceneGraph(QQuickTextNode *parent,
352                              QQuickText::TextStyle style = QQuickText::Normal,
353                              const QColor &styleColor = QColor());
354
355         void setSelectionColor(const QColor &selectionColor)
356         {
357             m_selectionColor = selectionColor;
358         }
359
360         void setSelectedTextColor(const QColor &selectedTextColor)
361         {
362             m_selectedTextColor = selectedTextColor;
363         }
364
365         void setTextColor(const QColor &textColor)
366         {
367             m_textColor = textColor;
368         }
369
370         void setPosition(const QPointF &position)
371         {
372             m_position = position;
373         }
374
375     private:
376         struct TextDecoration
377         {
378             TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
379             TextDecoration(const BinaryTreeNode::SelectionState &s,
380                            const QRectF &r,
381                            const QColor &c)
382                 : selectionState(s)
383                 , rect(r)
384                 , color(c)
385             {
386             }
387
388             BinaryTreeNode::SelectionState selectionState;
389             QRectF rect;
390             QColor color;
391         };
392
393         void processCurrentLine();
394         void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
395                                 qreal offset, qreal thickness);
396
397         QColor m_selectionColor;
398         QColor m_textColor;
399         QColor m_backgroundColor;
400         QColor m_selectedTextColor;
401         QPointF m_position;
402
403         QTextLine m_currentLine;
404         bool m_hasSelection;
405
406         QList<QPair<QRectF, QColor> > m_backgrounds;
407         QList<QRectF> m_selectionRects;
408         QVarLengthArray<BinaryTreeNode> m_currentLineTree;
409
410         QList<TextDecoration> m_lines;
411         QVector<BinaryTreeNode> m_processedNodes;
412
413         QList<QPair<QRectF, QImage> > m_images;
414     };
415
416     int SelectionEngine::addText(const QTextBlock &block,
417                                  const QTextCharFormat &charFormat,
418                                  const QColor &textColor,
419                                  const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
420                                  int textPos, int fragmentEnd,
421                                  int selectionStart, int selectionEnd)
422     {
423         if (charFormat.foreground().style() != Qt::NoBrush)
424             setTextColor(charFormat.foreground().color());
425         else
426             setTextColor(textColor);
427
428         while (textPos < fragmentEnd) {
429             int blockRelativePosition = textPos - block.position();
430             QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
431             if (!currentLine().isValid()
432                 || line.lineNumber() != currentLine().lineNumber()) {
433                 setCurrentLine(line);
434             }
435
436             Q_ASSERT(line.textLength() > 0);
437             int lineEnd = line.textStart() + block.position() + line.textLength();
438
439             int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
440             Q_ASSERT(len > 0);
441
442             int currentStepEnd = textPos + len;
443
444             addGlyphsForRanges(colorChanges,
445                                textPos - block.position(),
446                                currentStepEnd - block.position(),
447                                selectionStart - block.position(),
448                                selectionEnd - block.position());
449
450             textPos = currentStepEnd;
451         }
452         return textPos;
453     }
454
455     void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
456                                              qreal offset, qreal thickness)
457     {
458         for (int i=0; i<textDecorations.size(); ++i) {
459             TextDecoration textDecoration = textDecorations.at(i);
460
461             {
462                 QRectF &rect = textDecoration.rect;
463                 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
464                 rect.setHeight(thickness);
465             }
466
467             m_lines.append(textDecoration);
468         }
469     }
470
471     void SelectionEngine::processCurrentLine()
472     {
473         // No glyphs, do nothing
474         if (m_currentLineTree.isEmpty())
475             return;
476
477         // 1. Go through current line and get correct decoration position for each node based on
478         // neighbouring decorations. Add decoration to global list
479         // 2. Create clip nodes for all selected text. Try to merge as many as possible within
480         // the line.
481         // 3. Add QRects to a list of selection rects.
482         // 4. Add all nodes to a global processed list
483         QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
484         BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
485
486         Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
487
488         BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
489         QRectF currentRect;
490
491         QQuickTextNode::Decorations currentDecorations = QQuickTextNode::NoDecoration;
492         qreal underlineOffset = 0.0;
493         qreal underlineThickness = 0.0;
494
495         qreal overlineOffset = 0.0;
496         qreal overlineThickness = 0.0;
497
498         qreal strikeOutOffset = 0.0;
499         qreal strikeOutThickness = 0.0;
500
501         QRectF decorationRect = currentRect;
502
503         QColor lastColor;
504         QColor lastBackgroundColor;
505
506         QVarLengthArray<TextDecoration> pendingUnderlines;
507         QVarLengthArray<TextDecoration> pendingOverlines;
508         QVarLengthArray<TextDecoration> pendingStrikeOuts;
509         if (!sortedIndexes.isEmpty()) {
510             QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0;
511             bool currentClipNodeUsed = false;
512             for (int i=0; i<=sortedIndexes.size(); ++i) {
513                 BinaryTreeNode *node = 0;
514                 if (i < sortedIndexes.size()) {
515                     int sortedIndex = sortedIndexes.at(i);
516                     Q_ASSERT(sortedIndex < m_currentLineTree.size());
517
518                     node = m_currentLineTree.data() + sortedIndex;
519                 }
520
521                 if (i == 0)
522                     currentSelectionState = node->selectionState;
523
524                 // Update decorations
525                 if (currentDecorations != QQuickTextNode::NoDecoration) {
526                     decorationRect.setY(m_position.y() + m_currentLine.y());
527                     decorationRect.setHeight(m_currentLine.height());
528
529                     if (node != 0)
530                         decorationRect.setRight(node->boundingRect.left());
531
532                     TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
533                     if (currentDecorations & QQuickTextNode::Underline)
534                         pendingUnderlines.append(textDecoration);
535
536                     if (currentDecorations & QQuickTextNode::Overline)
537                         pendingOverlines.append(textDecoration);
538
539                     if (currentDecorations & QQuickTextNode::StrikeOut)
540                         pendingStrikeOuts.append(textDecoration);
541
542                     if (currentDecorations & QQuickTextNode::Background)
543                         m_backgrounds.append(qMakePair(decorationRect, lastBackgroundColor));
544                 }
545
546                 // If we've reached an unselected node from a selected node, we add the
547                 // selection rect to the graph, and we add decoration every time the
548                 // selection state changes, because that means the text color changes
549                 if (node == 0 || node->selectionState != currentSelectionState) {
550                     if (node != 0)
551                         currentRect.setRight(node->boundingRect.left());
552                     currentRect.setY(m_position.y() + m_currentLine.y());
553                     currentRect.setHeight(m_currentLine.height());
554
555                     // Draw selection all the way up to the left edge of the unselected item
556                     if (currentSelectionState == BinaryTreeNode::Selected)
557                         m_selectionRects.append(currentRect);
558
559                     if (currentClipNode != 0) {
560                         if (!currentClipNodeUsed) {
561                             delete currentClipNode;
562                         } else {
563                             currentClipNode->setIsRectangular(true);
564                             currentClipNode->setClipRect(currentRect);
565                         }
566                     }
567
568                     if (node != 0 && m_hasSelection)
569                         currentClipNode = new QSGClipNode;
570                     else
571                         currentClipNode = 0;
572                     currentClipNodeUsed = false;
573
574                     if (node != 0) {
575                         currentSelectionState = node->selectionState;
576                         currentRect = node->boundingRect;
577
578                         // Make sure currentRect is valid, otherwise the unite won't work
579                         if (currentRect.isNull())
580                             currentRect.setSize(QSizeF(1, 1));
581                     }
582                 } else {
583                     if (currentRect.isNull())
584                         currentRect = node->boundingRect;
585                     else
586                         currentRect = currentRect.united(node->boundingRect);
587                 }
588
589                 if (node != 0) {
590                     node->clipNode = currentClipNode;
591                     currentClipNodeUsed = true;
592
593                     decorationRect = node->boundingRect;
594
595                     // If previous item(s) had underline and current does not, then we add the
596                     // pending lines to the lists and likewise for overlines and strikeouts
597                     if (!pendingUnderlines.isEmpty()
598                       && !(node->decorations & QQuickTextNode::Underline)) {
599                         addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
600
601                         pendingUnderlines.clear();
602
603                         underlineOffset = 0.0;
604                         underlineThickness = 0.0;
605                     }
606
607                     // ### Add pending when overlineOffset/thickness changes to minimize number of
608                     // nodes
609                     if (!pendingOverlines.isEmpty()) {
610                         addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
611
612                         pendingOverlines.clear();
613
614                         overlineOffset = 0.0;
615                         overlineThickness = 0.0;
616                     }
617
618                     // ### Add pending when overlineOffset/thickness changes to minimize number of
619                     // nodes
620                     if (!pendingStrikeOuts.isEmpty()) {
621                         addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
622
623                         pendingStrikeOuts.clear();
624
625                         strikeOutOffset = 0.0;
626                         strikeOutThickness = 0.0;
627                     }
628
629                     // Merge current values with previous. Prefer greatest thickness
630                     QRawFont rawFont = node->glyphRun.rawFont();
631                     if (node->decorations & QQuickTextNode::Underline) {
632                         if (rawFont.lineThickness() > underlineThickness) {
633                             underlineThickness = rawFont.lineThickness();
634                             underlineOffset = rawFont.underlinePosition();
635                         }
636                     }
637
638                     if (node->decorations & QQuickTextNode::Overline) {
639                         overlineOffset = -rawFont.ascent();
640                         overlineThickness = rawFont.lineThickness();
641                     }
642
643                     if (node->decorations & QQuickTextNode::StrikeOut) {
644                         strikeOutThickness = rawFont.lineThickness();
645                         strikeOutOffset = rawFont.ascent() / -3.0;
646                     }
647
648                     currentDecorations = node->decorations;
649                     lastColor = node->color;
650                     lastBackgroundColor = node->backgroundColor;
651                     m_processedNodes.append(*node);
652                 }
653             }
654
655             if (!pendingUnderlines.isEmpty())
656                 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
657
658             if (!pendingOverlines.isEmpty())
659                 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
660
661             if (!pendingStrikeOuts.isEmpty())
662                 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
663         }
664
665         m_currentLineTree.clear();
666         m_currentLine = QTextLine();
667         m_hasSelection = false;
668     }
669
670     void SelectionEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent,
671                                    BinaryTreeNode::SelectionState selectionState,
672                                    QTextFrameFormat::Position layoutPosition)
673     {
674         QRectF searchRect = rect;
675         if (layoutPosition == QTextFrameFormat::InFlow) {
676             if (m_currentLineTree.isEmpty()) {
677                 searchRect.moveTopLeft(m_position + m_currentLine.position());
678             } else {
679                 const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1;
680                 if (lastNode->glyphRun.isRightToLeft()) {
681                     QPointF lastPos = lastNode->boundingRect.topLeft();
682                     searchRect.moveTopRight(lastPos - QPointF(0, ascent - lastNode->ascent));
683                 } else {
684                     QPointF lastPos = lastNode->boundingRect.topRight();
685                     searchRect.moveTopLeft(lastPos - QPointF(0, ascent - lastNode->ascent));
686                 }
687             }
688         }
689
690         BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
691     }
692
693     void SelectionEngine::addTextObject(const QPointF &position, const QTextCharFormat &format,
694                                         BinaryTreeNode::SelectionState selectionState,
695                                         QTextDocument *textDocument, int pos,
696                                         QTextFrameFormat::Position layoutPosition)
697     {
698         QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType());
699         if (handler != 0) {
700             QImage image;
701             QSizeF size = handler->intrinsicSize(textDocument, pos, format);
702
703             if (format.objectType() == QTextFormat::ImageObject) {
704                 QTextImageFormat imageFormat = format.toImageFormat();
705                 QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
706                 image = imageHandler->image(textDocument, imageFormat);
707             }
708
709             if (image.isNull()) {
710                 image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied);
711                 image.fill(Qt::transparent);
712                 {
713                     QPainter painter(&image);
714                     handler->drawObject(&painter, image.rect(), textDocument, pos, format);
715                 }
716             }
717
718             qreal ascent;
719             QFontMetrics m(format.font());
720             switch (format.verticalAlignment())
721             {
722             case QTextCharFormat::AlignMiddle:
723                 ascent = size.height() / 2 - 1;
724                 break;
725             case QTextCharFormat::AlignBaseline:
726                 ascent = size.height() - m.descent() - 1;
727                 break;
728             default:
729                 ascent = size.height() - 1;
730             }
731
732             addImage(QRectF(position, size), image, ascent, selectionState, layoutPosition);
733         }
734     }
735
736     void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
737     {
738         BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
739                                m_textColor, m_backgroundColor, m_position);
740     }
741
742     void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
743     {
744         int currentSize = m_currentLineTree.size();
745         BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
746                                m_textColor, m_backgroundColor, m_position);
747         m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
748     }
749
750     void SelectionEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
751                                              int start, int end,
752                                              int selectionStart, int selectionEnd)
753     {
754         int currentPosition = start;
755         int remainingLength = end - start;
756         for (int j=0; j<ranges.size(); ++j) {
757             const QTextLayout::FormatRange &range = ranges.at(j);
758             if (range.start + range.length >= currentPosition
759                 && range.start < currentPosition + remainingLength) {
760
761                 if (range.start > currentPosition) {
762                     addGlyphsInRange(currentPosition, range.start - currentPosition,
763                                      QColor(), QColor(), selectionStart, selectionEnd);
764                 }
765
766                 int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
767                 QColor rangeColor = range.format.hasProperty(QTextFormat::ForegroundBrush)
768                         ? range.format.foreground().color()
769                         : QColor();
770                 QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush)
771                         ? range.format.background().color()
772                         : QColor();
773
774                 addGlyphsInRange(range.start, rangeEnd - range.start,
775                                  rangeColor, rangeBackgroundColor,
776                                  selectionStart, selectionEnd);
777
778                 currentPosition = range.start + range.length;
779                 remainingLength = end - currentPosition;
780
781             } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
782                 break;
783             }
784         }
785
786         if (remainingLength > 0) {
787             addGlyphsInRange(currentPosition, remainingLength, QColor(), QColor(),
788                              selectionStart, selectionEnd);
789         }
790
791     }
792
793     void SelectionEngine::addGlyphsInRange(int rangeStart, int rangeLength,
794                                            const QColor &color, const QColor &backgroundColor,
795                                            int selectionStart, int selectionEnd)
796     {
797         QColor oldColor;
798         if (color.isValid()) {
799             oldColor = m_textColor;
800             m_textColor = color;
801         }
802
803         QColor oldBackgroundColor = m_backgroundColor;
804         if (backgroundColor.isValid()) {
805             oldBackgroundColor = m_backgroundColor;
806             m_backgroundColor = backgroundColor;
807         }
808
809         bool hasSelection = selectionEnd >= 0
810                          && selectionStart <= selectionEnd;
811
812         QTextLine &line = m_currentLine;
813         int rangeEnd = rangeStart + rangeLength;
814         if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) {
815             QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
816             for (int j=0; j<glyphRuns.size(); ++j) {
817                 const QGlyphRun &glyphRun = glyphRuns.at(j);
818                 addUnselectedGlyphs(glyphRun);
819             }
820         } else {
821             if (rangeStart < selectionStart) {
822                 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart,
823                                                             qMin(selectionStart - rangeStart,
824                                                                  rangeLength));
825
826                 for (int j=0; j<glyphRuns.size(); ++j) {
827                     const QGlyphRun &glyphRun = glyphRuns.at(j);
828                     addUnselectedGlyphs(glyphRun);
829                 }
830             }
831
832             if (rangeEnd > selectionStart) {
833                 int start = qMax(selectionStart, rangeStart);
834                 int length = qMin(selectionEnd - start + 1, rangeEnd - start);
835                 QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
836
837                 for (int j=0; j<glyphRuns.size(); ++j) {
838                     const QGlyphRun &glyphRun = glyphRuns.at(j);
839                     addSelectedGlyphs(glyphRun);
840                 }
841             }
842
843             if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
844                 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd - 1);
845                 for (int j=0; j<glyphRuns.size(); ++j) {
846                     const QGlyphRun &glyphRun = glyphRuns.at(j);
847                     addUnselectedGlyphs(glyphRun);
848                 }
849             }
850         }
851
852         if (backgroundColor.isValid())
853             m_backgroundColor = oldBackgroundColor;
854
855         if (oldColor.isValid())
856             m_textColor = oldColor;
857     }
858
859     void SelectionEngine::addBorder(const QRectF &rect, qreal border,
860                                     QTextFrameFormat::BorderStyle borderStyle,
861                                     const QBrush &borderBrush)
862     {
863         QColor color = borderBrush.color();
864
865         // Currently we don't support other styles than solid
866         Q_UNUSED(borderStyle);
867
868         m_backgrounds.append(qMakePair(QRectF(rect.left(), rect.top(), border, rect.height() + border), color));
869         m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.top(), rect.width(), border), color));
870         m_backgrounds.append(qMakePair(QRectF(rect.right(), rect.top() + border, border, rect.height() - border), color));
871         m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.bottom(), rect.width(), border), color));
872     }
873
874     void SelectionEngine::addFrameDecorations(QTextDocument *document, QTextFrame *frame)
875     {
876         QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(document->documentLayout());
877         QTextFrameFormat frameFormat = frame->format().toFrameFormat();
878
879         QTextTable *table = qobject_cast<QTextTable *>(frame);
880         QRectF boundingRect = table == 0
881                 ? documentLayout->frameBoundingRect(frame)
882                 : documentLayout->tableBoundingRect(table);
883
884         QBrush bg = frame->frameFormat().background();
885         if (bg.style() != Qt::NoBrush)
886             m_backgrounds.append(qMakePair(boundingRect, bg.color()));
887
888         if (!frameFormat.hasProperty(QTextFormat::FrameBorder))
889             return;
890
891         qreal borderWidth = frameFormat.border();
892         if (qFuzzyIsNull(borderWidth))
893             return;
894
895         QBrush borderBrush = frameFormat.borderBrush();
896         QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle();
897         if (borderStyle == QTextFrameFormat::BorderStyle_None)
898             return;
899
900         addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
901                                         -frameFormat.rightMargin(), -frameFormat.bottomMargin()),
902                   borderWidth, borderStyle, borderBrush);
903         if (table != 0) {
904             int rows = table->rows();
905             int columns = table->columns();
906
907             for (int row=0; row<rows; ++row) {
908                 for (int column=0; column<columns; ++column) {
909                     QTextTableCell cell = table->cellAt(row, column);
910
911                     QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
912                     addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth,
913                               borderStyle, borderBrush);
914                 }
915             }
916         }
917     }
918
919     void SelectionEngine::addToSceneGraph(QQuickTextNode *parentNode,
920                                           QQuickText::TextStyle style,
921                                           const QColor &styleColor)
922     {
923         if (m_currentLine.isValid())
924             processCurrentLine();
925
926
927         for (int i=0; i<m_backgrounds.size(); ++i) {
928             const QRectF &rect = m_backgrounds.at(i).first;
929             const QColor &color = m_backgrounds.at(i).second;
930
931             parentNode->appendChildNode(new QSGSimpleRectNode(rect, color));
932         }
933
934         // First, prepend all selection rectangles to the tree
935         for (int i=0; i<m_selectionRects.size(); ++i) {
936             const QRectF &rect = m_selectionRects.at(i);
937
938             parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
939         }
940
941         // Finally, add decorations for each node to the tree.
942         for (int i=0; i<m_lines.size(); ++i) {
943             const TextDecoration &textDecoration = m_lines.at(i);
944
945             QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
946                     ? m_selectedTextColor
947                     : textDecoration.color;
948
949             parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
950         }
951
952         // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common
953         // font, selection state and clip node.
954         typedef QPair<QFontEngine *, QPair<QSGClipNode *, QPair<QRgb, int> > > KeyType;
955         QHash<KeyType, BinaryTreeNode *> map;
956         for (int i=0; i<m_processedNodes.size(); ++i) {
957             BinaryTreeNode *node = m_processedNodes.data() + i;
958
959             if (node->image.isNull()) {
960                 QGlyphRun glyphRun = node->glyphRun;
961                 QRawFont rawFont = glyphRun.rawFont();
962                 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
963
964                 QFontEngine *fontEngine = rawFontD->fontEngine;
965
966                 KeyType key(qMakePair(fontEngine,
967                                       qMakePair(node->clipNode,
968                                                 qMakePair(node->color.rgba(), int(node->selectionState)))));
969
970                 BinaryTreeNode *otherNode = map.value(key, 0);
971                 if (otherNode != 0) {
972                     QGlyphRun &otherGlyphRun = otherNode->glyphRun;
973
974                     QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
975                     QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
976
977                     otherGlyphIndexes += glyphRun.glyphIndexes();
978
979                     QVector<QPointF> glyphPositions = glyphRun.positions();
980                     for (int j=0; j<glyphPositions.size(); ++j) {
981                         otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
982                     }
983
984                     otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
985                     otherGlyphRun.setPositions(otherGlyphPositions);
986
987                 } else {
988                     map.insert(key, node);
989                 }
990             } else {
991                 parentNode->addImage(node->boundingRect, node->image);
992                 if (node->selectionState == BinaryTreeNode::Selected) {
993                     QColor color = m_selectionColor;
994                     color.setAlpha(128);
995                     parentNode->appendChildNode(new QSGSimpleRectNode(node->boundingRect, color));
996                 }
997             }
998         }
999
1000         // ...and add clip nodes and glyphs to tree.
1001         QHash<KeyType, BinaryTreeNode *>::const_iterator it = map.constBegin();
1002         while (it != map.constEnd()) {
1003
1004             BinaryTreeNode *node = it.value();
1005
1006             QSGClipNode *clipNode = node->clipNode;
1007             if (clipNode != 0 && clipNode->parent() == 0 )
1008                 parentNode->appendChildNode(clipNode);
1009
1010             QColor color = node->selectionState == BinaryTreeNode::Selected
1011                     ? m_selectedTextColor
1012                     : node->color;
1013
1014             parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
1015
1016             ++it;
1017         }
1018     }
1019 }
1020
1021 void QQuickTextNode::mergeFormats(QTextLayout *textLayout,
1022                                QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
1023 {
1024     Q_ASSERT(mergedFormats != 0);
1025     if (textLayout == 0)
1026         return;
1027
1028     QList<QTextLayout::FormatRange> additionalFormats = textLayout->additionalFormats();
1029     for (int i=0; i<additionalFormats.size(); ++i) {
1030         QTextLayout::FormatRange additionalFormat = additionalFormats.at(i);
1031         if (additionalFormat.format.hasProperty(QTextFormat::ForegroundBrush)
1032          || additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)) {
1033             // Merge overlapping formats
1034             if (!mergedFormats->isEmpty()) {
1035                 QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1;
1036
1037                 if (additionalFormat.start < lastFormat->start + lastFormat->length) {
1038                     QTextLayout::FormatRange *mergedRange = 0;
1039
1040                     int length = additionalFormat.length;
1041                     if (additionalFormat.start > lastFormat->start) {
1042                         lastFormat->length = additionalFormat.start - lastFormat->start;
1043                         length -= lastFormat->length;
1044
1045                         mergedFormats->append(QTextLayout::FormatRange());
1046                         mergedRange = mergedFormats->data() + mergedFormats->size() - 1;
1047                         lastFormat = mergedFormats->data() + mergedFormats->size() - 2;
1048                     } else {
1049                         mergedRange = lastFormat;
1050                     }
1051
1052                     mergedRange->format = lastFormat->format;
1053                     mergedRange->format.merge(additionalFormat.format);
1054                     mergedRange->start = additionalFormat.start;
1055
1056                     int end = qMin(additionalFormat.start + additionalFormat.length,
1057                                    lastFormat->start + lastFormat->length);
1058
1059                     mergedRange->length = end - mergedRange->start;
1060                     length -= mergedRange->length;
1061
1062                     additionalFormat.start = end;
1063                     additionalFormat.length = length;
1064                 }
1065             }
1066
1067             if (additionalFormat.length > 0)
1068                 mergedFormats->append(additionalFormat);
1069         }
1070     }
1071
1072 }
1073
1074 namespace {
1075
1076     class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
1077     {
1078     public:
1079         inline QTextCharFormat formatAccessor(int pos)
1080         {
1081             return format(pos);
1082         }
1083     };
1084
1085 }
1086
1087 void QQuickTextNode::addImage(const QRectF &rect, const QImage &image)
1088 {
1089     QSGImageNode *node = m_context->createImageNode();
1090     QSGTexture *texture = m_context->createTexture(image);
1091     m_textures.append(texture);
1092     node->setTargetRect(rect);
1093     node->setTexture(texture);
1094     appendChildNode(node);
1095     node->update();
1096 }
1097
1098 void QQuickTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument,
1099                                   const QColor &textColor,
1100                                   QQuickText::TextStyle style, const QColor &styleColor,
1101                                   const QColor &selectionColor, const QColor &selectedTextColor,
1102                                   int selectionStart, int selectionEnd)
1103 {
1104     SelectionEngine engine;
1105     engine.setTextColor(textColor);
1106     engine.setSelectedTextColor(selectedTextColor);
1107     engine.setSelectionColor(selectionColor);
1108
1109     QList<QTextFrame *> frames;
1110     frames.append(textDocument->rootFrame());
1111     while (!frames.isEmpty()) {
1112         QTextFrame *textFrame = frames.takeFirst();
1113         frames.append(textFrame->childFrames());
1114
1115         engine.addFrameDecorations(textDocument, textFrame);
1116
1117         if (textFrame->firstPosition() > textFrame->lastPosition()
1118          && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
1119             const int pos = textFrame->firstPosition() - 1;
1120             ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(textDocument->documentLayout());
1121             QTextCharFormat format = a->formatAccessor(pos);
1122             QRectF rect = a->frameBoundingRect(textFrame);
1123
1124             QTextBlock block = textFrame->firstCursorPosition().block();
1125             engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
1126             engine.addTextObject(rect.topLeft(), format, BinaryTreeNode::Unselected, textDocument,
1127                                  pos, textFrame->frameFormat().position());
1128         } else {
1129             QTextFrame::iterator it = textFrame->begin();
1130
1131             while (!it.atEnd()) {
1132                 Q_ASSERT(!engine.currentLine().isValid());
1133
1134                 QTextBlock block = it.currentBlock();
1135                 int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0;
1136                 int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
1137
1138                 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1139                 mergeFormats(block.layout(), &colorChanges);
1140
1141                 QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
1142                 if (QTextList *textList = block.textList()) {
1143                     QPointF pos = blockPosition;
1144                     QTextLayout *layout = block.layout();
1145                     if (layout->lineCount() > 0) {
1146                         QTextLine firstLine = layout->lineAt(0);
1147                         Q_ASSERT(firstLine.isValid());
1148
1149                         engine.setCurrentLine(firstLine);
1150
1151                         QRectF textRect = firstLine.naturalTextRect();
1152                         pos += textRect.topLeft();
1153                         if (block.textDirection() == Qt::RightToLeft)
1154                             pos.rx() += textRect.width();
1155
1156                         const QTextCharFormat charFormat = block.charFormat();
1157                         QFont font(charFormat.font());
1158                         QFontMetricsF fontMetrics(font);
1159                         QTextListFormat listFormat = textList->format();
1160
1161                         QString listItemBullet;
1162                         switch (listFormat.style()) {
1163                         case QTextListFormat::ListCircle:
1164                             listItemBullet = QChar(0x25E6); // White bullet
1165                             break;
1166                         case QTextListFormat::ListSquare:
1167                             listItemBullet = QChar(0x25AA); // Black small square
1168                             break;
1169                         case QTextListFormat::ListDecimal:
1170                         case QTextListFormat::ListLowerAlpha:
1171                         case QTextListFormat::ListUpperAlpha:
1172                         case QTextListFormat::ListLowerRoman:
1173                         case QTextListFormat::ListUpperRoman:
1174                             listItemBullet = textList->itemText(block);
1175                             break;
1176                         default:
1177                             listItemBullet = QChar(0x2022); // Black bullet
1178                             break;
1179                         };
1180
1181                         QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height());
1182                         qreal xoff = fontMetrics.width(QLatin1Char(' '));
1183                         if (block.textDirection() == Qt::LeftToRight)
1184                             xoff = -xoff - size.width();
1185                         engine.setPosition(pos + QPointF(xoff, 0));
1186
1187                         QTextLayout layout;
1188                         layout.setFont(font);
1189                         layout.setText(listItemBullet); // Bullet
1190                         layout.beginLayout();
1191                         QTextLine line = layout.createLine();
1192                         line.setPosition(QPointF(0, 0));
1193                         layout.endLayout();
1194
1195                         QList<QGlyphRun> glyphRuns = layout.glyphRuns();
1196                         for (int i=0; i<glyphRuns.size(); ++i)
1197                             engine.addUnselectedGlyphs(glyphRuns.at(i));
1198                     }
1199                 }
1200
1201                 int textPos = block.position();
1202                 QTextBlock::iterator blockIterator = block.begin();
1203                 if (blockIterator.atEnd() && preeditLength) {
1204                     engine.setPosition(blockPosition);
1205                     textPos = engine.addText(block, block.charFormat(), textColor, colorChanges,
1206                                              textPos, textPos + preeditLength,
1207                                              selectionStart, selectionEnd);
1208                 }
1209
1210                 while (!blockIterator.atEnd()) {
1211                     QTextFragment fragment = blockIterator.fragment();
1212                     QString text = fragment.text();
1213                     if (text.isEmpty())
1214                         continue;
1215
1216                     QTextCharFormat charFormat = fragment.charFormat();
1217                     engine.setPosition(blockPosition);
1218                     if (text.contains(QChar::ObjectReplacementCharacter)) {
1219                         QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
1220                         if (frame && frame->frameFormat().position() == QTextFrameFormat::InFlow) {
1221                             int blockRelativePosition = textPos - block.position();
1222                             QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
1223                             if (!engine.currentLine().isValid()
1224                                     || line.lineNumber() != engine.currentLine().lineNumber()) {
1225                                 engine.setCurrentLine(line);
1226                             }
1227
1228                             BinaryTreeNode::SelectionState selectionState =
1229                                     (selectionStart < textPos + text.length()
1230                                      && selectionEnd >= textPos)
1231                                     ? BinaryTreeNode::Selected
1232                                     : BinaryTreeNode::Unselected;
1233
1234                             engine.addTextObject(QPointF(), charFormat, selectionState, textDocument, textPos);
1235                         }
1236                         textPos += text.length();
1237                     } else {
1238                         int fragmentEnd = textPos + fragment.length();
1239                         if (preeditPosition >= 0
1240                          && preeditPosition >= textPos
1241                          && preeditPosition <= fragmentEnd) {
1242                             fragmentEnd += preeditLength;
1243                         }
1244
1245                         engine.addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd,
1246                                        selectionStart, selectionEnd);
1247                     }
1248
1249                     ++blockIterator;
1250                 }
1251
1252                 engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
1253                 ++it;
1254             }
1255         }
1256     }
1257
1258     engine.addToSceneGraph(this, style, styleColor);
1259 }
1260
1261 void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
1262                                 QQuickText::TextStyle style, const QColor &styleColor,
1263                                 const QColor &selectionColor, const QColor &selectedTextColor,
1264                                 int selectionStart, int selectionEnd)
1265 {
1266     SelectionEngine engine;
1267     engine.setTextColor(color);
1268     engine.setSelectedTextColor(selectedTextColor);
1269     engine.setSelectionColor(selectionColor);
1270     engine.setPosition(position);
1271
1272     int preeditLength = textLayout->preeditAreaText().length();
1273     int preeditPosition = textLayout->preeditAreaPosition();
1274
1275     QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1276     mergeFormats(textLayout, &colorChanges);
1277
1278     for (int i=0; i<textLayout->lineCount(); ++i) {
1279         QTextLine line = textLayout->lineAt(i);
1280
1281         int start = line.textStart();
1282         int length = line.textLength();
1283         int end = start + length;
1284
1285         if (preeditPosition >= 0
1286          && preeditPosition >= start
1287          && preeditPosition < end) {
1288             end += preeditLength;
1289         }
1290
1291         engine.setCurrentLine(line);
1292         engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
1293     }
1294
1295     engine.addToSceneGraph(this, style, styleColor);
1296 }
1297
1298 void QQuickTextNode::deleteContent()
1299 {
1300     while (firstChild() != 0)
1301         delete firstChild();
1302     m_cursorNode = 0;
1303 }
1304
1305 #if 0
1306 void QQuickTextNode::updateNodes()
1307 {
1308     return;
1309     deleteContent();
1310     if (m_text.isEmpty())
1311         return;
1312
1313     if (m_usePixmapCache) {
1314         // ### gunnar: port properly
1315 //        QPixmap pixmap = generatedPixmap();
1316 //        if (pixmap.isNull())
1317 //            return;
1318
1319 //        QSGImageNode *pixmapNode = m_context->createImageNode();
1320 //        pixmapNode->setRect(pixmap.rect());
1321 //        pixmapNode->setSourceRect(pixmap.rect());
1322 //        pixmapNode->setOpacity(m_opacity);
1323 //        pixmapNode->setClampToEdge(true);
1324 //        pixmapNode->setLinearFiltering(m_linearFiltering);
1325
1326 //        appendChildNode(pixmapNode);
1327     } else {
1328         if (m_text.isEmpty())
1329             return;
1330
1331         // Implement styling by drawing text several times at slight shifts. shiftForStyle
1332         // contains the sequence of shifted positions at which to draw the text. All except
1333         // the last will be drawn with styleColor.
1334         QList<QPointF> shiftForStyle;
1335         switch (m_textStyle) {
1336         case OutlineTextStyle:
1337             // ### Should be made faster by implementing outline material
1338             shiftForStyle << QPointF(-1, 0);
1339             shiftForStyle << QPointF(0, -1);
1340             shiftForStyle << QPointF(1, 0);
1341             shiftForStyle << QPointF(0, 1);
1342             break;
1343         case SunkenTextStyle:
1344             shiftForStyle << QPointF(0, -1);
1345             break;
1346         case RaisedTextStyle:
1347             shiftForStyle << QPointF(0, 1);
1348             break;
1349         default:
1350             break;
1351         }
1352
1353         shiftForStyle << QPointF(0, 0); // Regular position
1354         while (!shiftForStyle.isEmpty()) {
1355             QPointF shift = shiftForStyle.takeFirst();
1356
1357             // Use styleColor for all but last shift
1358             if (m_richText) {
1359                 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
1360
1361                 QTextFrame *textFrame = m_textDocument->rootFrame();
1362                 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
1363
1364                 QTextFrame::iterator it = textFrame->begin();
1365                 while (!it.atEnd()) {
1366                     addTextBlock(shift + p, it.currentBlock(), overrideColor);
1367                     ++it;
1368                 }
1369             } else {
1370                 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()
1371                                                    ? m_color
1372                                                    : m_styleColor);
1373             }
1374         }
1375     }
1376 }
1377 #endif
1378
1379 QT_END_NAMESPACE