Support font color with Text.StyledText in SceneGraph
[profile/ivi/qtdeclarative.git] / src / declarative / items / qsgtextnode.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2010 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 "qsgtextnode_p.h"
43 #include "qsgsimplerectnode.h"
44 #include <private/qsgadaptationlayer_p.h>
45 #include <private/qsgdistancefieldglyphcache_p.h>
46 #include <private/qsgdistancefieldglyphnode_p.h>
47
48 #include <private/qsgcontext_p.h>
49
50 #include <QtCore/qpoint.h>
51 #include <qmath.h>
52 #include <qtextdocument.h>
53 #include <qtextlayout.h>
54 #include <qabstracttextdocumentlayout.h>
55 #include <qxmlstream.h>
56 #include <qrawfont.h>
57 #include <private/qdeclarativestyledtext_p.h>
58 #include <private/qfont_p.h>
59 #include <private/qfontengine_p.h>
60 #include <private/qrawfont_p.h>
61 #include <qhash.h>
62
63 QT_BEGIN_NAMESPACE
64
65 /*!
66   Creates an empty QSGTextNode
67 */
68 QSGTextNode::QSGTextNode(QSGContext *context)
69     : m_context(context), m_cursorNode(0)
70 {
71 #if defined(QML_RUNTIME_TESTING)
72     description = QLatin1String("text");
73 #endif
74 }
75
76 QSGTextNode::~QSGTextNode()
77 {
78 }
79
80 #if 0
81 void QSGTextNode::setColor(const QColor &color)
82 {
83     if (m_usePixmapCache) {
84         setUpdateFlag(UpdateNodes);
85     } else {
86         for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
87             if (childNode->subType() == GlyphNodeSubType) {
88                 QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
89                 if (glyphNode->color() == m_color)
90                     glyphNode->setColor(color);
91             } else if (childNode->subType() == SolidRectNodeSubType) {
92                 QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
93                 if (solidRectNode->color() == m_color)
94                     solidRectNode->setColor(color);
95             }
96         }
97     }
98     m_color = color;
99 }
100
101 void QSGTextNode::setStyleColor(const QColor &styleColor)
102 {
103     if (m_textStyle != QSGTextNode::NormalTextStyle) {
104         if (m_usePixmapCache) {
105             setUpdateFlag(UpdateNodes);
106         } else {
107             for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
108                 if (childNode->subType() == GlyphNodeSubType) {
109                     QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
110                     if (glyphNode->color() == m_styleColor)
111                         glyphNode->setColor(styleColor);
112                 } else if (childNode->subType() == SolidRectNodeSubType) {
113                     QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
114                     if (solidRectNode->color() == m_styleColor)
115                         solidRectNode->setColor(styleColor);
116                 }
117             }
118         }
119     }
120     m_styleColor = styleColor;
121 }
122 #endif
123
124 QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
125                                      QSGText::TextStyle style, const QColor &styleColor,
126                                      QSGNode *parentNode)
127 {
128     QSGGlyphNode *node = m_context->createGlyphNode();
129     node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
130     node->setStyle(style);
131     node->setStyleColor(styleColor);
132     node->setColor(color);
133     node->update();
134
135     /* We flag the geometry as static, but we never call markVertexDataDirty
136        or markIndexDataDirty on them. This is because all text nodes are
137        discarded when a change occurs. If we start appending/removing from
138        existing geometry, then we also need to start marking the geometry as
139        dirty.
140      */
141     node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
142     node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
143
144     if (parentNode == 0)
145         parentNode = this;
146     parentNode->appendChildNode(node);
147
148     return node;
149 }
150
151 void QSGTextNode::setCursor(const QRectF &rect, const QColor &color)
152 {
153     if (m_cursorNode != 0)
154         delete m_cursorNode;
155
156     m_cursorNode = new QSGSimpleRectNode(rect, color);
157     appendChildNode(m_cursorNode);
158 }
159
160 namespace {
161
162     struct BinaryTreeNode {
163         enum SelectionState {
164             Unselected,
165             Selected
166         };
167
168         BinaryTreeNode()
169             : selectionState(Unselected)
170             , clipNode(0)
171             , decorations(QSGTextNode::NoDecoration)
172             , leftChildIndex(-1)
173             , rightChildIndex(-1)
174         {
175
176         }
177
178         BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
179                        const QSGTextNode::Decorations &decs, const QColor &c,
180                        const QPointF &pos)
181             : glyphRun(g)
182             , boundingRect(brect)
183             , selectionState(selState)
184             , clipNode(0)
185             , decorations(decs)
186             , color(c)
187             , position(pos)
188             , leftChildIndex(-1)
189             , rightChildIndex(-1)
190         {
191         }
192
193         QGlyphRun glyphRun;
194         QRectF boundingRect;
195         SelectionState selectionState;
196         QSGClipNode *clipNode;
197         QSGTextNode::Decorations decorations;
198         QColor color;
199         QPointF position;
200
201         int leftChildIndex;
202         int rightChildIndex;
203
204         static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
205                            const QGlyphRun &glyphRun,
206                            SelectionState selectionState,
207                            const QColor &textColor,
208                            const QPointF &position)
209         {
210             int newIndex = binaryTree->size();
211             QRectF searchRect = glyphRun.boundingRect();
212
213             if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
214                 return;
215
216             QSGTextNode::Decorations decorations = QSGTextNode::NoDecoration;
217             decorations |= (glyphRun.underline() ? QSGTextNode::Underline : QSGTextNode::NoDecoration);
218             decorations |= (glyphRun.overline()  ? QSGTextNode::Overline  : QSGTextNode::NoDecoration);
219             decorations |= (glyphRun.strikeOut() ? QSGTextNode::StrikeOut : QSGTextNode::NoDecoration);
220
221             binaryTree->append(BinaryTreeNode(glyphRun, selectionState, searchRect, decorations,
222                                               textColor, position));
223             if (newIndex == 0)
224                 return;
225
226             int searchIndex = 0;
227             forever {
228                 BinaryTreeNode *node = binaryTree->data() + searchIndex;
229                 if (searchRect.left() < node->boundingRect.left()) {
230                     if (node->leftChildIndex < 0) {
231                         node->leftChildIndex = newIndex;
232                         break;
233                     } else {
234                         searchIndex = node->leftChildIndex;
235                     }
236                 } else {
237                     if (node->rightChildIndex < 0) {
238                         node->rightChildIndex = newIndex;
239                         break;
240                     } else {
241                         searchIndex = node->rightChildIndex;
242                     }
243                 }
244             }
245         }
246
247         static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree,
248                             QVarLengthArray<int> *sortedIndexes,
249                             int currentIndex = 0)
250         {
251             Q_ASSERT(currentIndex < binaryTree.size());
252
253             const BinaryTreeNode *node = binaryTree.data() + currentIndex;
254             if (node->leftChildIndex >= 0)
255                 inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
256
257             sortedIndexes->append(currentIndex);
258
259             if (node->rightChildIndex >= 0)
260                 inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
261         }
262     };
263
264     // Engine that takes glyph runs as input, and produces a set of glyph nodes, clip nodes,
265     // and rectangle nodes to represent the text, decorations and selection. Will try to minimize
266     // number of nodes, and join decorations in neighbouring items
267     class SelectionEngine
268     {
269     public:
270         SelectionEngine() : m_hasSelection(false) {}
271
272         QTextLine currentLine() const { return m_currentLine; }
273
274         void setCurrentLine(const QTextLine &currentLine)
275         {
276             if (m_currentLine.isValid())
277                 processCurrentLine();
278
279             m_currentLine = currentLine;
280         }
281
282         void addSelectedGlyphs(const QGlyphRun &glyphRun);
283         void addUnselectedGlyphs(const QGlyphRun &glyphRun);
284         void addGlyphsInRange(int rangeStart, int rangeEnd, const QColor &color,
285                               int selectionStart, int selectionEnd);
286
287         void addToSceneGraph(QSGTextNode *parent,
288                              QSGText::TextStyle style = QSGText::Normal,
289                              const QColor &styleColor = QColor());
290
291         void setSelectionColor(const QColor &selectionColor)
292         {
293             m_selectionColor = selectionColor;
294         }
295
296         void setSelectedTextColor(const QColor &selectedTextColor)
297         {
298             m_selectedTextColor = selectedTextColor;
299         }
300
301         void setTextColor(const QColor &textColor)
302         {
303             m_textColor = textColor;
304         }
305
306         void setPosition(const QPointF &position)
307         {
308             m_position = position;
309         }
310
311     private:
312         struct TextDecoration
313         {
314             TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
315             TextDecoration(const BinaryTreeNode::SelectionState &s,
316                            const QRectF &r,
317                            const QColor &c)
318                 : selectionState(s)
319                 , rect(r)
320                 , color(c)
321             {
322             }
323
324             BinaryTreeNode::SelectionState selectionState;
325             QRectF rect;
326             QColor color;
327         };
328
329         void processCurrentLine();
330         void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
331                                 qreal offset, qreal thickness);
332
333         QColor m_selectionColor;
334         QColor m_textColor;
335         QColor m_selectedTextColor;
336         QPointF m_position;
337
338         QTextLine m_currentLine;
339         bool m_hasSelection;
340
341         QList<QRectF> m_selectionRects;
342         QVarLengthArray<BinaryTreeNode> m_currentLineTree;
343
344         QList<TextDecoration> m_lines;
345         QVector<BinaryTreeNode> m_processedNodes;
346     };
347
348     void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
349                                              qreal offset, qreal thickness)
350     {
351         for (int i=0; i<textDecorations.size(); ++i) {
352             TextDecoration textDecoration = textDecorations.at(i);
353
354             {
355                 QRectF &rect = textDecoration.rect;
356                 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
357                 rect.setHeight(thickness);
358             }
359
360             m_lines.append(textDecoration);
361         }
362     }
363
364     void SelectionEngine::processCurrentLine()
365     {
366         // No glyphs, do nothing
367         if (m_currentLineTree.isEmpty())
368             return;
369
370         // 1. Go through current line and get correct decoration position for each node based on
371         // neighbouring decorations. Add decoration to global list
372         // 2. Create clip nodes for all selected text. Try to merge as many as possible within
373         // the line.
374         // 3. Add QRects to a list of selection rects.
375         // 4. Add all nodes to a global processed list
376         QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
377         BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
378
379         Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
380
381         BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
382         QRectF currentRect;
383
384         QSGTextNode::Decorations currentDecorations = QSGTextNode::NoDecoration;
385         qreal underlineOffset = 0.0;
386         qreal underlineThickness = 0.0;
387
388         qreal overlineOffset = 0.0;
389         qreal overlineThickness = 0.0;
390
391         qreal strikeOutOffset = 0.0;
392         qreal strikeOutThickness = 0.0;
393
394         QRectF decorationRect = currentRect;
395
396         QColor lastColor;
397
398         QVarLengthArray<TextDecoration> pendingUnderlines;
399         QVarLengthArray<TextDecoration> pendingOverlines;
400         QVarLengthArray<TextDecoration> pendingStrikeOuts;
401         if (!sortedIndexes.isEmpty()) {
402             QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0;
403             bool currentClipNodeUsed = false;
404             for (int i=0; i<=sortedIndexes.size(); ++i) {
405                 BinaryTreeNode *node = 0;
406                 if (i < sortedIndexes.size()) {
407                     int sortedIndex = sortedIndexes.at(i);
408                     Q_ASSERT(sortedIndex < m_currentLineTree.size());
409
410                     node = m_currentLineTree.data() + sortedIndex;
411                 }
412
413                 if (i == 0)
414                     currentSelectionState = node->selectionState;
415
416                 // Update decorations
417                 if (currentDecorations != QSGTextNode::NoDecoration) {
418                     decorationRect.setY(m_position.y() + m_currentLine.y());
419                     decorationRect.setHeight(m_currentLine.height());
420
421                     if (node != 0)
422                         decorationRect.setRight(node->boundingRect.left());
423
424                     TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
425                     if (currentDecorations & QSGTextNode::Underline)
426                         pendingUnderlines.append(textDecoration);
427
428                     if (currentDecorations & QSGTextNode::Overline)
429                         pendingOverlines.append(textDecoration);
430
431                     if (currentDecorations & QSGTextNode::StrikeOut)
432                         pendingStrikeOuts.append(textDecoration);
433                 }
434
435                 // If we've reached an unselected node from a selected node, we add the
436                 // selection rect to the graph, and we add decoration every time the
437                 // selection state changes, because that means the text color changes
438                 if (node == 0 || node->selectionState != currentSelectionState) {
439                     if (node != 0)
440                         currentRect.setRight(node->boundingRect.left());
441                     currentRect.setY(m_position.y() + m_currentLine.y());
442                     currentRect.setHeight(m_currentLine.height());
443
444                     // Draw selection all the way up to the left edge of the unselected item
445                     if (currentSelectionState == BinaryTreeNode::Selected)
446                         m_selectionRects.append(currentRect);
447
448                     if (currentClipNode != 0) {
449                         if (!currentClipNodeUsed) {
450                             delete currentClipNode;
451                         } else {
452                             currentClipNode->setIsRectangular(true);
453                             currentClipNode->setClipRect(currentRect);
454                         }
455                     }
456
457                     if (node != 0 && m_hasSelection)
458                         currentClipNode = new QSGClipNode;
459                     else
460                         currentClipNode = 0;
461                     currentClipNodeUsed = false;
462
463                     if (node != 0) {
464                         currentSelectionState = node->selectionState;
465                         currentRect = node->boundingRect;
466
467                         // Make sure currentRect is valid, otherwise the unite won't work
468                         if (currentRect.isNull())
469                             currentRect.setSize(QSizeF(1, 1));
470                     }
471                 } else {
472                     if (currentRect.isNull())
473                         currentRect = node->boundingRect;
474                     else
475                         currentRect = currentRect.united(node->boundingRect);
476                 }
477
478                 if (node != 0) {
479                     node->clipNode = currentClipNode;
480                     currentClipNodeUsed = true;
481
482                     decorationRect = node->boundingRect;
483
484                     // If previous item(s) had underline and current does not, then we add the
485                     // pending lines to the lists and likewise for overlines and strikeouts
486                     if (!pendingUnderlines.isEmpty()
487                       && !(node->decorations & QSGTextNode::Underline)) {
488                         addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
489
490                         pendingUnderlines.clear();
491
492                         underlineOffset = 0.0;
493                         underlineThickness = 0.0;
494                     }
495
496                     // ### Add pending when overlineOffset/thickness changes to minimize number of
497                     // nodes
498                     if (!pendingOverlines.isEmpty()) {
499                         addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
500
501                         pendingOverlines.clear();
502
503                         overlineOffset = 0.0;
504                         overlineThickness = 0.0;
505                     }
506
507                     // ### Add pending when overlineOffset/thickness changes to minimize number of
508                     // nodes
509                     if (!pendingStrikeOuts.isEmpty()) {
510                         addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
511
512                         pendingStrikeOuts.clear();
513
514                         strikeOutOffset = 0.0;
515                         strikeOutThickness = 0.0;
516                     }
517
518                     // Merge current values with previous. Prefer greatest thickness
519                     QRawFont rawFont = node->glyphRun.rawFont();
520                     if (node->decorations & QSGTextNode::Underline) {
521                         if (rawFont.lineThickness() > underlineThickness) {
522                             underlineThickness = rawFont.lineThickness();
523                             underlineOffset = rawFont.underlinePosition();
524                         }
525                     }
526
527                     if (node->decorations & QSGTextNode::Overline) {
528                         overlineOffset = -rawFont.ascent();
529                         overlineThickness = rawFont.lineThickness();
530                     }
531
532                     if (node->decorations & QSGTextNode::StrikeOut) {
533                         strikeOutThickness = rawFont.lineThickness();
534                         strikeOutOffset = rawFont.ascent() / -3.0;
535                     }
536
537                     currentDecorations = node->decorations;
538                     lastColor = node->color;
539                     m_processedNodes.append(*node);
540                 }
541             }
542
543             // If there are pending decorations, we need to add them
544             if (!pendingUnderlines.isEmpty())
545                 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
546
547             if (!pendingOverlines.isEmpty())
548                 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
549
550             if (!pendingStrikeOuts.isEmpty())
551                 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
552         }
553
554         m_currentLineTree.clear();
555         m_currentLine = QTextLine();
556         m_hasSelection = false;
557     }
558
559     void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
560     {
561         BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
562                                m_textColor, m_position);
563     }
564
565     void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
566     {
567         int currentSize = m_currentLineTree.size();
568         BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
569                                m_textColor, m_position);
570         m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
571     }
572
573     void SelectionEngine::addGlyphsInRange(int rangeStart, int rangeLength,
574                                            const QColor &color,
575                                            int selectionStart, int selectionEnd)
576     {
577         QColor oldColor = m_textColor;
578         m_textColor = color;
579
580         QTextLine &line = m_currentLine;
581         int rangeEnd = rangeStart + rangeLength;
582         if (selectionStart > rangeEnd || selectionEnd < rangeStart) {
583             QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
584             for (int j=0; j<glyphRuns.size(); ++j)
585                 addUnselectedGlyphs(glyphRuns.at(j));
586         } else {
587             if (rangeStart < selectionStart) {
588                 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart,
589                                                             qMin(selectionStart - rangeStart,
590                                                                  rangeLength));
591
592                 for (int j=0; j<glyphRuns.size(); ++j)
593                     addUnselectedGlyphs(glyphRuns.at(j));
594             }
595
596             if (rangeEnd >= selectionStart && selectionStart >= rangeStart) {
597                 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionStart, selectionEnd - selectionStart + 1);
598
599                 for (int j=0; j<glyphRuns.size(); ++j)
600                     addSelectedGlyphs(glyphRuns.at(j));
601             }
602
603             if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
604                 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd);
605                 for (int j=0; j<glyphRuns.size(); ++j)
606                     addUnselectedGlyphs(glyphRuns.at(j));
607             }
608         }
609         m_textColor = oldColor;
610     }
611
612     void SelectionEngine::addToSceneGraph(QSGTextNode *parentNode,
613                                           QSGText::TextStyle style,
614                                           const QColor &styleColor)
615     {
616         if (m_currentLine.isValid())
617             processCurrentLine();
618
619         // First, prepend all selection rectangles to the tree
620         for (int i=0; i<m_selectionRects.size(); ++i) {
621             const QRectF &rect = m_selectionRects.at(i);
622
623             parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
624         }
625
626         // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common
627         // font, selection state and clip node.
628         typedef QPair<QFontEngine *, QPair<QSGClipNode *, QPair<QRgb, int> > > KeyType;
629         QHash<KeyType, BinaryTreeNode *> map;
630         for (int i=0; i<m_processedNodes.size(); ++i) {
631             BinaryTreeNode *node = m_processedNodes.data() + i;
632
633             QGlyphRun glyphRun = node->glyphRun;
634             QRawFont rawFont = glyphRun.rawFont();
635             QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
636
637             QFontEngine *fontEngine = rawFontD->fontEngine;
638
639             KeyType key(qMakePair(fontEngine,
640                                   qMakePair(node->clipNode,
641                                             qMakePair(node->color.rgba(), int(node->selectionState)))));
642
643             BinaryTreeNode *otherNode = map.value(key, 0);
644             if (otherNode != 0) {
645                 QGlyphRun &otherGlyphRun = otherNode->glyphRun;
646
647                 QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
648                 QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
649
650                 otherGlyphIndexes += glyphRun.glyphIndexes();
651
652                 QVector<QPointF> glyphPositions = glyphRun.positions();
653                 for (int j=0; j<glyphPositions.size(); ++j) {
654                     otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
655                 }
656
657                 otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
658                 otherGlyphRun.setPositions(otherGlyphPositions);
659
660             } else {
661                 map.insert(key, node);
662             }
663         }
664
665         // ...and add clip nodes and glyphs to tree.
666         QHash<KeyType, BinaryTreeNode *>::const_iterator it = map.constBegin();
667         while (it != map.constEnd()) {
668
669             BinaryTreeNode *node = it.value();
670
671             QSGClipNode *clipNode = node->clipNode;
672             if (clipNode != 0 && clipNode->parent() == 0 )
673                 parentNode->appendChildNode(clipNode);
674
675             QColor color = node->selectionState == BinaryTreeNode::Selected
676                     ? m_selectedTextColor
677                     : node->color;
678
679             parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
680
681             ++it;
682         }
683
684         // Finally, add decorations for each node to the tree.
685         for (int i=0; i<m_lines.size(); ++i) {
686             const TextDecoration &textDecoration = m_lines.at(i);
687
688             QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
689                     ? m_selectedTextColor
690                     : textDecoration.color;
691
692             parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
693         }
694     }
695 }
696
697 void QSGTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument,
698                                   const QColor &textColor,
699                                   QSGText::TextStyle style, const QColor &styleColor,
700                                   const QColor &selectionColor, const QColor &selectedTextColor,
701                                   int selectionStart, int selectionEnd)
702 {
703     QTextFrame *textFrame = textDocument->rootFrame();
704     QPointF position = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
705
706     SelectionEngine engine;
707     engine.setTextColor(textColor);
708     engine.setSelectedTextColor(selectedTextColor);
709     engine.setSelectionColor(selectionColor);
710
711     bool hasSelection = selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd;
712
713     QTextFrame::iterator it = textFrame->begin();
714     while (!it.atEnd()) {
715         Q_ASSERT(!engine.currentLine().isValid());
716
717         QTextBlock block = it.currentBlock();
718
719         QTextBlock::iterator blockIterator = block.begin();
720         while (!blockIterator.atEnd()) {
721             QTextFragment fragment = blockIterator.fragment();
722             if (fragment.text().isEmpty())
723                 continue;
724
725             QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
726             engine.setPosition(position + blockPosition);
727
728             QTextCharFormat charFormat = fragment.charFormat();
729             if (!textColor.isValid())
730                 engine.setTextColor(charFormat.foreground().color());
731
732             int fragmentEnd = fragment.position() + fragment.length();
733             int textPos = fragment.position();
734             while (textPos < fragmentEnd) {
735                 int blockRelativePosition = textPos - block.position();
736                 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
737                 Q_ASSERT(line.textLength() > 0);
738                 if (!engine.currentLine().isValid() || line.lineNumber() != engine.currentLine().lineNumber())
739                     engine.setCurrentLine(line);
740
741                 int lineEnd = line.textStart() + block.position() + line.textLength();
742
743                 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
744                 Q_ASSERT(len > 0);
745
746                 int currentStepEnd = textPos + len;
747
748                 if (!hasSelection || selectionStart > currentStepEnd || selectionEnd < textPos) {
749                     QList<QGlyphRun> glyphRuns = fragment.glyphRuns(blockRelativePosition, len);
750                     for (int j=0; j<glyphRuns.size(); ++j)
751                         engine.addUnselectedGlyphs(glyphRuns.at(j));
752                 } else {
753                     if (textPos < selectionStart) {
754                         int unselectedPartLength = qMin(selectionStart - textPos, len);
755                         QList<QGlyphRun> glyphRuns = fragment.glyphRuns(blockRelativePosition,
756                                                                         unselectedPartLength);
757                         for (int j=0; j<glyphRuns.size(); ++j)
758                             engine.addUnselectedGlyphs(glyphRuns.at(j));
759                     }
760
761                     if (selectionStart < currentStepEnd && selectionEnd > textPos) {
762                         int currentSelectionStart = qMax(selectionStart, textPos);
763                         int currentSelectionLength = qMin(selectionEnd - currentSelectionStart,
764                                                           currentStepEnd - currentSelectionStart);
765                         int selectionRelativeBlockPosition = currentSelectionStart - block.position();
766
767                         QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionRelativeBlockPosition,
768                                                                         currentSelectionLength);
769                         for (int j=0; j<glyphRuns.size(); ++j)
770                             engine.addSelectedGlyphs(glyphRuns.at(j));
771                     }
772
773                     if (selectionEnd >= textPos && selectionEnd < currentStepEnd) {
774                         QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionEnd - block.position(),
775                                                                         currentStepEnd - selectionEnd);
776                         for (int j=0; j<glyphRuns.size(); ++j)
777                             engine.addUnselectedGlyphs(glyphRuns.at(j));
778                     }
779                 }
780
781                 textPos = currentStepEnd;
782             }
783
784             ++blockIterator;
785         }
786
787         engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
788         ++it;
789     }
790
791     engine.addToSceneGraph(this, style, styleColor);
792 }
793
794 void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
795                                 QSGText::TextStyle style, const QColor &styleColor,
796                                 const QColor &selectionColor, const QColor &selectedTextColor,
797                                 int selectionStart, int selectionEnd)
798 {
799     SelectionEngine engine;
800     engine.setTextColor(color);
801     engine.setSelectedTextColor(selectedTextColor);
802     engine.setSelectionColor(selectionColor);
803     engine.setPosition(position);
804
805     QList<QTextLayout::FormatRange> additionalFormats = textLayout->additionalFormats();
806     QVarLengthArray<QTextLayout::FormatRange> colorChanges;
807     for (int i=0; i<additionalFormats.size(); ++i) {
808         if (additionalFormats.at(i).format.hasProperty(QTextFormat::ForegroundBrush))
809             colorChanges.append(additionalFormats.at(i));
810     }
811
812     for (int i=0; i<textLayout->lineCount(); ++i) {
813         QTextLine line = textLayout->lineAt(i);
814
815         engine.setCurrentLine(line);
816
817         int currentPosition = line.textStart();
818         int remainingLength = line.textLength();
819         for (int j=0; j<colorChanges.size(); ++j) {
820             const QTextLayout::FormatRange &range = colorChanges.at(j);
821             if (range.start + range.length >= currentPosition
822                 && range.start < currentPosition + remainingLength) {
823
824                 if (range.start > currentPosition) {
825                     engine.addGlyphsInRange(currentPosition, range.start - currentPosition,
826                                             color, selectionStart, selectionEnd);
827                 }
828
829                 int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
830                 QColor rangeColor = range.format.foreground().color();
831
832                 engine.addGlyphsInRange(range.start, rangeEnd - range.start, rangeColor,
833                                         selectionStart, selectionEnd);
834
835                 currentPosition = range.start + range.length;
836                 remainingLength = line.textStart() + line.textLength() - currentPosition;
837
838             } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
839                 break;
840             }
841         }
842
843         if (remainingLength > 0) {
844             engine.addGlyphsInRange(currentPosition, remainingLength, color,
845                                     selectionStart, selectionEnd);
846         }
847     }
848
849     engine.addToSceneGraph(this, style, styleColor);
850 }
851
852
853 /*!
854   Returns true if \a text contains any HTML tags, attributes or CSS properties which are unrelated
855    to text, fonts or text layout. Otherwise the function returns false. If the return value is
856   false, \a text is considered to be easily representable in the scenegraph. If it returns true,
857   then the text should be prerendered into a pixmap before it's displayed on screen.
858 */
859 bool QSGTextNode::isComplexRichText(QTextDocument *doc)
860 {
861     if (doc == 0)
862         return false;
863
864     static QSet<QString> supportedTags;
865     if (supportedTags.isEmpty()) {
866         supportedTags.insert(QLatin1String("i"));
867         supportedTags.insert(QLatin1String("b"));
868         supportedTags.insert(QLatin1String("u"));
869         supportedTags.insert(QLatin1String("div"));
870         supportedTags.insert(QLatin1String("big"));
871         supportedTags.insert(QLatin1String("blockquote"));
872         supportedTags.insert(QLatin1String("body"));
873         supportedTags.insert(QLatin1String("br"));
874         supportedTags.insert(QLatin1String("center"));
875         supportedTags.insert(QLatin1String("cite"));
876         supportedTags.insert(QLatin1String("code"));
877         supportedTags.insert(QLatin1String("tt"));
878         supportedTags.insert(QLatin1String("dd"));
879         supportedTags.insert(QLatin1String("dfn"));
880         supportedTags.insert(QLatin1String("em"));
881         supportedTags.insert(QLatin1String("font"));
882         supportedTags.insert(QLatin1String("h1"));
883         supportedTags.insert(QLatin1String("h2"));
884         supportedTags.insert(QLatin1String("h3"));
885         supportedTags.insert(QLatin1String("h4"));
886         supportedTags.insert(QLatin1String("h5"));
887         supportedTags.insert(QLatin1String("h6"));
888         supportedTags.insert(QLatin1String("head"));
889         supportedTags.insert(QLatin1String("html"));
890         supportedTags.insert(QLatin1String("meta"));
891         supportedTags.insert(QLatin1String("nobr"));
892         supportedTags.insert(QLatin1String("p"));
893         supportedTags.insert(QLatin1String("pre"));
894         supportedTags.insert(QLatin1String("qt"));
895         supportedTags.insert(QLatin1String("s"));
896         supportedTags.insert(QLatin1String("samp"));
897         supportedTags.insert(QLatin1String("small"));
898         supportedTags.insert(QLatin1String("span"));
899         supportedTags.insert(QLatin1String("strong"));
900         supportedTags.insert(QLatin1String("sub"));
901         supportedTags.insert(QLatin1String("sup"));
902         supportedTags.insert(QLatin1String("title"));
903         supportedTags.insert(QLatin1String("var"));
904         supportedTags.insert(QLatin1String("style"));
905     }
906
907     static QSet<QCss::Property> supportedCssProperties;
908     if (supportedCssProperties.isEmpty()) {
909         supportedCssProperties.insert(QCss::Color);
910         supportedCssProperties.insert(QCss::Float);
911         supportedCssProperties.insert(QCss::Font);
912         supportedCssProperties.insert(QCss::FontFamily);
913         supportedCssProperties.insert(QCss::FontSize);
914         supportedCssProperties.insert(QCss::FontStyle);
915         supportedCssProperties.insert(QCss::FontWeight);
916         supportedCssProperties.insert(QCss::Margin);
917         supportedCssProperties.insert(QCss::MarginBottom);
918         supportedCssProperties.insert(QCss::MarginLeft);
919         supportedCssProperties.insert(QCss::MarginRight);
920         supportedCssProperties.insert(QCss::MarginTop);
921         supportedCssProperties.insert(QCss::TextDecoration);
922         supportedCssProperties.insert(QCss::TextIndent);
923         supportedCssProperties.insert(QCss::TextUnderlineStyle);
924         supportedCssProperties.insert(QCss::VerticalAlignment);
925         supportedCssProperties.insert(QCss::Whitespace);
926         supportedCssProperties.insert(QCss::Padding);
927         supportedCssProperties.insert(QCss::PaddingLeft);
928         supportedCssProperties.insert(QCss::PaddingRight);
929         supportedCssProperties.insert(QCss::PaddingTop);
930         supportedCssProperties.insert(QCss::PaddingBottom);
931         supportedCssProperties.insert(QCss::PageBreakBefore);
932         supportedCssProperties.insert(QCss::PageBreakAfter);
933         supportedCssProperties.insert(QCss::Width);
934         supportedCssProperties.insert(QCss::Height);
935         supportedCssProperties.insert(QCss::MinimumWidth);
936         supportedCssProperties.insert(QCss::MinimumHeight);
937         supportedCssProperties.insert(QCss::MaximumWidth);
938         supportedCssProperties.insert(QCss::MaximumHeight);
939         supportedCssProperties.insert(QCss::Left);
940         supportedCssProperties.insert(QCss::Right);
941         supportedCssProperties.insert(QCss::Top);
942         supportedCssProperties.insert(QCss::Bottom);
943         supportedCssProperties.insert(QCss::Position);
944         supportedCssProperties.insert(QCss::TextAlignment);
945         supportedCssProperties.insert(QCss::FontVariant);
946     }
947
948     QXmlStreamReader reader(doc->toHtml("utf-8"));
949     while (!reader.atEnd()) {
950         reader.readNext();
951
952         if (reader.isStartElement()) {
953             if (!supportedTags.contains(reader.name().toString().toLower()))
954                 return true;
955
956             QXmlStreamAttributes attributes = reader.attributes();
957             if (attributes.hasAttribute(QLatin1String("bgcolor")))
958                 return true;
959             if (attributes.hasAttribute(QLatin1String("style"))) {
960                 QCss::StyleSheet styleSheet;
961                 QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet);
962
963                 QVector<QCss::Declaration> decls;
964                 for (int i=0; i<styleSheet.pageRules.size(); ++i)
965                     decls += styleSheet.pageRules.at(i).declarations;
966
967                 QVector<QCss::StyleRule> styleRules =
968                         styleSheet.styleRules
969                         + styleSheet.idIndex.values().toVector()
970                         + styleSheet.nameIndex.values().toVector();
971                 for (int i=0; i<styleSheet.mediaRules.size(); ++i)
972                     styleRules += styleSheet.mediaRules.at(i).styleRules;
973
974                 for (int i=0; i<styleRules.size(); ++i)
975                     decls += styleRules.at(i).declarations;
976
977                 for (int i=0; i<decls.size(); ++i) {
978                     if (!supportedCssProperties.contains(decls.at(i).d->propertyId))
979                         return true;
980                 }
981
982             }
983         }
984     }
985
986     return reader.hasError();
987 }
988
989 void QSGTextNode::deleteContent()
990 {
991     while (firstChild() != 0)
992         delete firstChild();
993     m_cursorNode = 0;
994 }
995
996 #if 0
997 void QSGTextNode::updateNodes()
998 {
999     return;
1000     deleteContent();
1001     if (m_text.isEmpty())
1002         return;
1003
1004     if (m_usePixmapCache) {
1005         // ### gunnar: port properly
1006 //        QPixmap pixmap = generatedPixmap();
1007 //        if (pixmap.isNull())
1008 //            return;
1009
1010 //        QSGImageNode *pixmapNode = m_context->createImageNode();
1011 //        pixmapNode->setRect(pixmap.rect());
1012 //        pixmapNode->setSourceRect(pixmap.rect());
1013 //        pixmapNode->setOpacity(m_opacity);
1014 //        pixmapNode->setClampToEdge(true);
1015 //        pixmapNode->setLinearFiltering(m_linearFiltering);
1016
1017 //        appendChildNode(pixmapNode);
1018     } else {
1019         if (m_text.isEmpty())
1020             return;
1021
1022         // Implement styling by drawing text several times at slight shifts. shiftForStyle
1023         // contains the sequence of shifted positions at which to draw the text. All except
1024         // the last will be drawn with styleColor.
1025         QList<QPointF> shiftForStyle;
1026         switch (m_textStyle) {
1027         case OutlineTextStyle:
1028             // ### Should be made faster by implementing outline material
1029             shiftForStyle << QPointF(-1, 0);
1030             shiftForStyle << QPointF(0, -1);
1031             shiftForStyle << QPointF(1, 0);
1032             shiftForStyle << QPointF(0, 1);
1033             break;
1034         case SunkenTextStyle:
1035             shiftForStyle << QPointF(0, -1);
1036             break;
1037         case RaisedTextStyle:
1038             shiftForStyle << QPointF(0, 1);
1039             break;
1040         default:
1041             break;
1042         }
1043
1044         shiftForStyle << QPointF(0, 0); // Regular position
1045         while (!shiftForStyle.isEmpty()) {
1046             QPointF shift = shiftForStyle.takeFirst();
1047
1048             // Use styleColor for all but last shift
1049             if (m_richText) {
1050                 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
1051
1052                 QTextFrame *textFrame = m_textDocument->rootFrame();
1053                 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
1054
1055                 QTextFrame::iterator it = textFrame->begin();
1056                 while (!it.atEnd()) {
1057                     addTextBlock(shift + p, it.currentBlock(), overrideColor);
1058                     ++it;
1059                 }
1060             } else {
1061                 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()
1062                                                    ? m_color
1063                                                    : m_styleColor);
1064             }
1065         }
1066     }
1067 }
1068 #endif
1069
1070 QT_END_NAMESPACE