1ae457b6fab27b940790e36f7670be00b36e6388
[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
285         void addToSceneGraph(QSGTextNode *parent,
286                              QSGText::TextStyle style = QSGText::Normal,
287                              const QColor &styleColor = QColor());
288
289         void setSelectionColor(const QColor &selectionColor)
290         {
291             m_selectionColor = selectionColor;
292         }
293
294         void setSelectedTextColor(const QColor &selectedTextColor)
295         {
296             m_selectedTextColor = selectedTextColor;
297         }
298
299         void setTextColor(const QColor &textColor)
300         {
301             m_textColor = textColor;
302         }
303
304         void setPosition(const QPointF &position)
305         {
306             m_position = position;
307         }
308
309     private:
310         struct TextDecoration
311         {
312             TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
313             TextDecoration(const BinaryTreeNode::SelectionState &s,
314                            const QRectF &r,
315                            const QColor &c)
316                 : selectionState(s)
317                 , rect(r)
318                 , color(c)
319             {
320             }
321
322             BinaryTreeNode::SelectionState selectionState;
323             QRectF rect;
324             QColor color;
325         };
326
327         void processCurrentLine();
328         void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
329                                 qreal offset, qreal thickness);
330
331         QColor m_selectionColor;
332         QColor m_textColor;
333         QColor m_selectedTextColor;
334         QPointF m_position;
335
336         QTextLine m_currentLine;
337         bool m_hasSelection;
338
339         QList<QRectF> m_selectionRects;
340         QVarLengthArray<BinaryTreeNode> m_currentLineTree;
341
342         QList<TextDecoration> m_lines;
343         QVector<BinaryTreeNode> m_processedNodes;
344     };
345
346     void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
347                                              qreal offset, qreal thickness)
348     {
349         for (int i=0; i<textDecorations.size(); ++i) {
350             TextDecoration textDecoration = textDecorations.at(i);
351
352             {
353                 QRectF &rect = textDecoration.rect;
354                 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
355                 rect.setHeight(thickness);
356             }
357
358             m_lines.append(textDecoration);
359         }
360     }
361
362     void SelectionEngine::processCurrentLine()
363     {
364         // No glyphs, do nothing
365         if (m_currentLineTree.isEmpty())
366             return;
367
368         // 1. Go through current line and get correct decoration position for each node based on
369         // neighbouring decorations. Add decoration to global list
370         // 2. Create clip nodes for all selected text. Try to merge as many as possible within
371         // the line.
372         // 3. Add QRects to a list of selection rects.
373         // 4. Add all nodes to a global processed list
374         QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
375         BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
376
377         Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
378
379         BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
380         QRectF currentRect;
381
382         QSGTextNode::Decorations currentDecorations = QSGTextNode::NoDecoration;
383         qreal underlineOffset = 0.0;
384         qreal underlineThickness = 0.0;
385
386         qreal overlineOffset = 0.0;
387         qreal overlineThickness = 0.0;
388
389         qreal strikeOutOffset = 0.0;
390         qreal strikeOutThickness = 0.0;
391
392         QRectF decorationRect = currentRect;
393
394         QColor lastColor;
395
396         QVarLengthArray<TextDecoration> pendingUnderlines;
397         QVarLengthArray<TextDecoration> pendingOverlines;
398         QVarLengthArray<TextDecoration> pendingStrikeOuts;
399         if (!sortedIndexes.isEmpty()) {
400             QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0;
401             bool currentClipNodeUsed = false;
402             for (int i=0; i<=sortedIndexes.size(); ++i) {
403                 BinaryTreeNode *node = 0;
404                 if (i < sortedIndexes.size()) {
405                     int sortedIndex = sortedIndexes.at(i);
406                     Q_ASSERT(sortedIndex < m_currentLineTree.size());
407
408                     node = m_currentLineTree.data() + sortedIndex;
409                 }
410
411                 if (i == 0)
412                     currentSelectionState = node->selectionState;
413
414                 // Update decorations
415                 if (currentDecorations != QSGTextNode::NoDecoration) {
416                     decorationRect.setY(m_position.y() + m_currentLine.y());
417                     decorationRect.setHeight(m_currentLine.height());
418
419                     if (node != 0)
420                         decorationRect.setRight(node->boundingRect.left());
421
422                     TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
423                     if (currentDecorations & QSGTextNode::Underline)
424                         pendingUnderlines.append(textDecoration);
425
426                     if (currentDecorations & QSGTextNode::Overline)
427                         pendingOverlines.append(textDecoration);
428
429                     if (currentDecorations & QSGTextNode::StrikeOut)
430                         pendingStrikeOuts.append(textDecoration);
431                 }
432
433                 // If we've reached an unselected node from a selected node, we add the
434                 // selection rect to the graph, and we add decoration every time the
435                 // selection state changes, because that means the text color changes
436                 if (node == 0 || node->selectionState != currentSelectionState) {
437                     if (node != 0)
438                         currentRect.setRight(node->boundingRect.left());
439                     currentRect.setY(m_position.y() + m_currentLine.y());
440                     currentRect.setHeight(m_currentLine.height());
441
442                     // Draw selection all the way up to the left edge of the unselected item
443                     if (currentSelectionState == BinaryTreeNode::Selected)
444                         m_selectionRects.append(currentRect);
445
446                     if (currentClipNode != 0) {
447                         if (!currentClipNodeUsed) {
448                             delete currentClipNode;
449                         } else {
450                             currentClipNode->setIsRectangular(true);
451                             currentClipNode->setClipRect(currentRect);
452                         }
453                     }
454
455                     if (node != 0 && m_hasSelection)
456                         currentClipNode = new QSGClipNode;
457                     else
458                         currentClipNode = 0;
459                     currentClipNodeUsed = false;
460
461                     if (node != 0) {
462                         currentSelectionState = node->selectionState;
463                         currentRect = node->boundingRect;
464
465                         // Make sure currentRect is valid, otherwise the unite won't work
466                         if (currentRect.isNull())
467                             currentRect.setSize(QSizeF(1, 1));
468                     }
469                 } else {
470                     if (currentRect.isNull())
471                         currentRect = node->boundingRect;
472                     else
473                         currentRect = currentRect.united(node->boundingRect);
474                 }
475
476                 if (node != 0) {
477                     node->clipNode = currentClipNode;
478                     currentClipNodeUsed = true;
479
480                     decorationRect = node->boundingRect;
481
482                     // If previous item(s) had underline and current does not, then we add the
483                     // pending lines to the lists and likewise for overlines and strikeouts
484                     if (!pendingUnderlines.isEmpty()
485                       && !(node->decorations & QSGTextNode::Underline)) {
486                         addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
487
488                         pendingUnderlines.clear();
489
490                         underlineOffset = 0.0;
491                         underlineThickness = 0.0;
492                     }
493
494                     // ### Add pending when overlineOffset/thickness changes to minimize number of
495                     // nodes
496                     if (!pendingOverlines.isEmpty()) {
497                         addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
498
499                         pendingOverlines.clear();
500
501                         overlineOffset = 0.0;
502                         overlineThickness = 0.0;
503                     }
504
505                     // ### Add pending when overlineOffset/thickness changes to minimize number of
506                     // nodes
507                     if (!pendingStrikeOuts.isEmpty()) {
508                         addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
509
510                         pendingStrikeOuts.clear();
511
512                         strikeOutOffset = 0.0;
513                         strikeOutThickness = 0.0;
514                     }
515
516                     // Merge current values with previous. Prefer greatest thickness
517                     QRawFont rawFont = node->glyphRun.rawFont();
518                     if (node->decorations & QSGTextNode::Underline) {
519                         if (rawFont.lineThickness() > underlineThickness) {
520                             underlineThickness = rawFont.lineThickness();
521                             underlineOffset = rawFont.underlinePosition();
522                         }
523                     }
524
525                     if (node->decorations & QSGTextNode::Overline) {
526                         overlineOffset = -rawFont.ascent();
527                         overlineThickness = rawFont.lineThickness();
528                     }
529
530                     if (node->decorations & QSGTextNode::StrikeOut) {
531                         strikeOutThickness = rawFont.lineThickness();
532                         strikeOutOffset = rawFont.ascent() / -3.0;
533                     }
534
535                     currentDecorations = node->decorations;
536                     lastColor = node->color;
537                     m_processedNodes.append(*node);
538                 }
539             }
540
541             // If there are pending decorations, we need to add them
542             if (!pendingUnderlines.isEmpty())
543                 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
544
545             if (!pendingOverlines.isEmpty())
546                 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
547
548             if (!pendingStrikeOuts.isEmpty())
549                 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
550         }
551
552         m_currentLineTree.clear();
553         m_currentLine = QTextLine();
554         m_hasSelection = false;
555     }
556
557     void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
558     {
559         BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
560                                m_textColor, m_position);
561     }
562
563     void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
564     {
565         int currentSize = m_currentLineTree.size();
566         BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
567                                m_textColor, m_position);
568         m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
569     }
570
571     void SelectionEngine::addToSceneGraph(QSGTextNode *parentNode,
572                                           QSGText::TextStyle style,
573                                           const QColor &styleColor)
574     {
575         if (m_currentLine.isValid())
576             processCurrentLine();
577
578         // First, prepend all selection rectangles to the tree
579         for (int i=0; i<m_selectionRects.size(); ++i) {
580             const QRectF &rect = m_selectionRects.at(i);
581
582             parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
583         }
584
585         // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common
586         // font, selection state and clip node.
587         typedef QPair<QFontEngine *, QPair<QSGClipNode *, QPair<QRgb, int> > > KeyType;
588         QHash<KeyType, BinaryTreeNode *> map;
589         for (int i=0; i<m_processedNodes.size(); ++i) {
590             BinaryTreeNode *node = m_processedNodes.data() + i;
591
592             QGlyphRun glyphRun = node->glyphRun;
593             QRawFont rawFont = glyphRun.rawFont();
594             QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
595
596             QFontEngine *fontEngine = rawFontD->fontEngine;
597
598             KeyType key(qMakePair(fontEngine,
599                                   qMakePair(node->clipNode,
600                                             qMakePair(node->color.rgba(), int(node->selectionState)))));
601
602             BinaryTreeNode *otherNode = map.value(key, 0);
603             if (otherNode != 0) {
604                 QGlyphRun &otherGlyphRun = otherNode->glyphRun;
605
606                 QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
607                 QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
608
609                 otherGlyphIndexes += glyphRun.glyphIndexes();
610
611                 QVector<QPointF> glyphPositions = glyphRun.positions();
612                 for (int j=0; j<glyphPositions.size(); ++j) {
613                     otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
614                 }
615
616                 otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
617                 otherGlyphRun.setPositions(otherGlyphPositions);
618
619             } else {
620                 map.insert(key, node);
621             }
622         }
623
624         // ...and add clip nodes and glyphs to tree.
625         QHash<KeyType, BinaryTreeNode *>::const_iterator it = map.constBegin();
626         while (it != map.constEnd()) {
627
628             BinaryTreeNode *node = it.value();
629
630             QSGClipNode *clipNode = node->clipNode;
631             if (clipNode != 0 && clipNode->parent() == 0 )
632                 parentNode->appendChildNode(clipNode);
633
634             QColor color = node->selectionState == BinaryTreeNode::Selected
635                     ? m_selectedTextColor
636                     : node->color;
637
638             parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
639
640             ++it;
641         }
642
643         // Finally, add decorations for each node to the tree.
644         for (int i=0; i<m_lines.size(); ++i) {
645             const TextDecoration &textDecoration = m_lines.at(i);
646
647             QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
648                     ? m_selectedTextColor
649                     : textDecoration.color;
650
651             parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
652         }
653     }
654 }
655
656 void QSGTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument,
657                                   const QColor &textColor,
658                                   QSGText::TextStyle style, const QColor &styleColor,
659                                   const QColor &selectionColor, const QColor &selectedTextColor,
660                                   int selectionStart, int selectionEnd)
661 {
662     QTextFrame *textFrame = textDocument->rootFrame();
663     QPointF position = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
664
665     SelectionEngine engine;
666     engine.setTextColor(textColor);
667     engine.setSelectedTextColor(selectedTextColor);
668     engine.setSelectionColor(selectionColor);
669
670     bool hasSelection = selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd;
671
672     QTextFrame::iterator it = textFrame->begin();
673     while (!it.atEnd()) {
674         Q_ASSERT(!engine.currentLine().isValid());
675
676         QTextBlock block = it.currentBlock();
677
678         QTextBlock::iterator blockIterator = block.begin();
679         while (!blockIterator.atEnd()) {
680             QTextFragment fragment = blockIterator.fragment();
681             if (fragment.text().isEmpty())
682                 continue;
683
684             QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
685             engine.setPosition(position + blockPosition);
686
687             QTextCharFormat charFormat = fragment.charFormat();
688             if (!textColor.isValid())
689                 engine.setTextColor(charFormat.foreground().color());
690
691             int fragmentEnd = fragment.position() + fragment.length();
692             int textPos = fragment.position();
693             while (textPos < fragmentEnd) {
694                 int blockRelativePosition = textPos - block.position();
695                 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
696                 Q_ASSERT(line.textLength() > 0);
697                 if (!engine.currentLine().isValid() || line.lineNumber() != engine.currentLine().lineNumber())
698                     engine.setCurrentLine(line);
699
700                 int lineEnd = line.textStart() + block.position() + line.textLength();
701
702                 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
703                 Q_ASSERT(len > 0);
704
705                 int currentStepEnd = textPos + len;
706
707                 if (!hasSelection || selectionStart > currentStepEnd || selectionEnd < textPos) {
708                     QList<QGlyphRun> glyphRuns = fragment.glyphRuns(blockRelativePosition, len);
709                     for (int j=0; j<glyphRuns.size(); ++j)
710                         engine.addUnselectedGlyphs(glyphRuns.at(j));
711                 } else {
712                     if (textPos < selectionStart) {
713                         int unselectedPartLength = qMin(selectionStart - textPos, len);
714                         QList<QGlyphRun> glyphRuns = fragment.glyphRuns(blockRelativePosition,
715                                                                         unselectedPartLength);
716                         for (int j=0; j<glyphRuns.size(); ++j)
717                             engine.addUnselectedGlyphs(glyphRuns.at(j));
718                     }
719
720                     if (selectionStart < currentStepEnd && selectionEnd > textPos) {
721                         int currentSelectionStart = qMax(selectionStart, textPos);
722                         int currentSelectionLength = qMin(selectionEnd - currentSelectionStart,
723                                                           currentStepEnd - currentSelectionStart);
724                         int selectionRelativeBlockPosition = currentSelectionStart - block.position();
725
726                         QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionRelativeBlockPosition,
727                                                                         currentSelectionLength);
728                         for (int j=0; j<glyphRuns.size(); ++j)
729                             engine.addSelectedGlyphs(glyphRuns.at(j));
730                     }
731
732                     if (selectionEnd >= textPos && selectionEnd < currentStepEnd) {
733                         QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionEnd - block.position(),
734                                                                         currentStepEnd - selectionEnd);
735                         for (int j=0; j<glyphRuns.size(); ++j)
736                             engine.addUnselectedGlyphs(glyphRuns.at(j));
737                     }
738                 }
739
740                 textPos = currentStepEnd;
741             }
742
743             ++blockIterator;
744         }
745
746         engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
747         ++it;
748     }
749
750     engine.addToSceneGraph(this, style, styleColor);
751 }
752
753 void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
754                                 QSGText::TextStyle style, const QColor &styleColor,
755                                 const QColor &selectionColor, const QColor &selectedTextColor,
756                                 int selectionStart, int selectionEnd)
757 {
758     SelectionEngine engine;
759     engine.setTextColor(color);
760     engine.setSelectedTextColor(selectedTextColor);
761     engine.setSelectionColor(selectionColor);
762     engine.setPosition(position);
763
764     for (int i=0; i<textLayout->lineCount(); ++i) {
765         QTextLine line = textLayout->lineAt(i);
766
767         engine.setCurrentLine(line);
768
769         int lineEnd = line.textStart() + line.textLength();
770         if (selectionStart > lineEnd || selectionEnd < line.textStart()) {
771             QList<QGlyphRun> glyphRuns = line.glyphRuns();
772             for (int j=0; j<glyphRuns.size(); ++j)
773                 engine.addUnselectedGlyphs(glyphRuns.at(j));
774         } else {
775             if (line.textStart() < selectionStart) {
776                 QList<QGlyphRun> glyphRuns = line.glyphRuns(line.textStart(),
777                                                             qMin(selectionStart - line.textStart(),
778                                                                  line.textLength()));
779
780                 for (int j=0; j<glyphRuns.size(); ++j)
781                     engine.addUnselectedGlyphs(glyphRuns.at(j));
782             }
783
784             if (lineEnd >= selectionStart && selectionStart >= line.textStart()) {
785                 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionStart, selectionEnd - selectionStart + 1);
786
787                 for (int j=0; j<glyphRuns.size(); ++j)
788                     engine.addSelectedGlyphs(glyphRuns.at(j));
789             }
790
791             if (selectionEnd >= line.textStart() && selectionEnd < lineEnd) {
792                 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, lineEnd - selectionEnd);
793                 for (int j=0; j<glyphRuns.size(); ++j)
794                     engine.addUnselectedGlyphs(glyphRuns.at(j));
795             }
796         }
797     }
798
799     engine.addToSceneGraph(this, style, styleColor);
800 }
801
802
803 /*!
804   Returns true if \a text contains any HTML tags, attributes or CSS properties which are unrelated
805    to text, fonts or text layout. Otherwise the function returns false. If the return value is
806   false, \a text is considered to be easily representable in the scenegraph. If it returns true,
807   then the text should be prerendered into a pixmap before it's displayed on screen.
808 */
809 bool QSGTextNode::isComplexRichText(QTextDocument *doc)
810 {
811     if (doc == 0)
812         return false;
813
814     static QSet<QString> supportedTags;
815     if (supportedTags.isEmpty()) {
816         supportedTags.insert(QLatin1String("i"));
817         supportedTags.insert(QLatin1String("b"));
818         supportedTags.insert(QLatin1String("u"));
819         supportedTags.insert(QLatin1String("div"));
820         supportedTags.insert(QLatin1String("big"));
821         supportedTags.insert(QLatin1String("blockquote"));
822         supportedTags.insert(QLatin1String("body"));
823         supportedTags.insert(QLatin1String("br"));
824         supportedTags.insert(QLatin1String("center"));
825         supportedTags.insert(QLatin1String("cite"));
826         supportedTags.insert(QLatin1String("code"));
827         supportedTags.insert(QLatin1String("tt"));
828         supportedTags.insert(QLatin1String("dd"));
829         supportedTags.insert(QLatin1String("dfn"));
830         supportedTags.insert(QLatin1String("em"));
831         supportedTags.insert(QLatin1String("font"));
832         supportedTags.insert(QLatin1String("h1"));
833         supportedTags.insert(QLatin1String("h2"));
834         supportedTags.insert(QLatin1String("h3"));
835         supportedTags.insert(QLatin1String("h4"));
836         supportedTags.insert(QLatin1String("h5"));
837         supportedTags.insert(QLatin1String("h6"));
838         supportedTags.insert(QLatin1String("head"));
839         supportedTags.insert(QLatin1String("html"));
840         supportedTags.insert(QLatin1String("meta"));
841         supportedTags.insert(QLatin1String("nobr"));
842         supportedTags.insert(QLatin1String("p"));
843         supportedTags.insert(QLatin1String("pre"));
844         supportedTags.insert(QLatin1String("qt"));
845         supportedTags.insert(QLatin1String("s"));
846         supportedTags.insert(QLatin1String("samp"));
847         supportedTags.insert(QLatin1String("small"));
848         supportedTags.insert(QLatin1String("span"));
849         supportedTags.insert(QLatin1String("strong"));
850         supportedTags.insert(QLatin1String("sub"));
851         supportedTags.insert(QLatin1String("sup"));
852         supportedTags.insert(QLatin1String("title"));
853         supportedTags.insert(QLatin1String("var"));
854         supportedTags.insert(QLatin1String("style"));
855     }
856
857     static QSet<QCss::Property> supportedCssProperties;
858     if (supportedCssProperties.isEmpty()) {
859         supportedCssProperties.insert(QCss::Color);
860         supportedCssProperties.insert(QCss::Float);
861         supportedCssProperties.insert(QCss::Font);
862         supportedCssProperties.insert(QCss::FontFamily);
863         supportedCssProperties.insert(QCss::FontSize);
864         supportedCssProperties.insert(QCss::FontStyle);
865         supportedCssProperties.insert(QCss::FontWeight);
866         supportedCssProperties.insert(QCss::Margin);
867         supportedCssProperties.insert(QCss::MarginBottom);
868         supportedCssProperties.insert(QCss::MarginLeft);
869         supportedCssProperties.insert(QCss::MarginRight);
870         supportedCssProperties.insert(QCss::MarginTop);
871         supportedCssProperties.insert(QCss::TextDecoration);
872         supportedCssProperties.insert(QCss::TextIndent);
873         supportedCssProperties.insert(QCss::TextUnderlineStyle);
874         supportedCssProperties.insert(QCss::VerticalAlignment);
875         supportedCssProperties.insert(QCss::Whitespace);
876         supportedCssProperties.insert(QCss::Padding);
877         supportedCssProperties.insert(QCss::PaddingLeft);
878         supportedCssProperties.insert(QCss::PaddingRight);
879         supportedCssProperties.insert(QCss::PaddingTop);
880         supportedCssProperties.insert(QCss::PaddingBottom);
881         supportedCssProperties.insert(QCss::PageBreakBefore);
882         supportedCssProperties.insert(QCss::PageBreakAfter);
883         supportedCssProperties.insert(QCss::Width);
884         supportedCssProperties.insert(QCss::Height);
885         supportedCssProperties.insert(QCss::MinimumWidth);
886         supportedCssProperties.insert(QCss::MinimumHeight);
887         supportedCssProperties.insert(QCss::MaximumWidth);
888         supportedCssProperties.insert(QCss::MaximumHeight);
889         supportedCssProperties.insert(QCss::Left);
890         supportedCssProperties.insert(QCss::Right);
891         supportedCssProperties.insert(QCss::Top);
892         supportedCssProperties.insert(QCss::Bottom);
893         supportedCssProperties.insert(QCss::Position);
894         supportedCssProperties.insert(QCss::TextAlignment);
895         supportedCssProperties.insert(QCss::FontVariant);
896     }
897
898     QXmlStreamReader reader(doc->toHtml("utf-8"));
899     while (!reader.atEnd()) {
900         reader.readNext();
901
902         if (reader.isStartElement()) {
903             if (!supportedTags.contains(reader.name().toString().toLower()))
904                 return true;
905
906             QXmlStreamAttributes attributes = reader.attributes();
907             if (attributes.hasAttribute(QLatin1String("bgcolor")))
908                 return true;
909             if (attributes.hasAttribute(QLatin1String("style"))) {
910                 QCss::StyleSheet styleSheet;
911                 QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet);
912
913                 QVector<QCss::Declaration> decls;
914                 for (int i=0; i<styleSheet.pageRules.size(); ++i)
915                     decls += styleSheet.pageRules.at(i).declarations;
916
917                 QVector<QCss::StyleRule> styleRules =
918                         styleSheet.styleRules
919                         + styleSheet.idIndex.values().toVector()
920                         + styleSheet.nameIndex.values().toVector();
921                 for (int i=0; i<styleSheet.mediaRules.size(); ++i)
922                     styleRules += styleSheet.mediaRules.at(i).styleRules;
923
924                 for (int i=0; i<styleRules.size(); ++i)
925                     decls += styleRules.at(i).declarations;
926
927                 for (int i=0; i<decls.size(); ++i) {
928                     if (!supportedCssProperties.contains(decls.at(i).d->propertyId))
929                         return true;
930                 }
931
932             }
933         }
934     }
935
936     return reader.hasError();
937 }
938
939 void QSGTextNode::deleteContent()
940 {
941     while (firstChild() != 0)
942         delete firstChild();
943     m_cursorNode = 0;
944 }
945
946 #if 0
947 void QSGTextNode::updateNodes()
948 {
949     return;
950     deleteContent();
951     if (m_text.isEmpty())
952         return;
953
954     if (m_usePixmapCache) {
955         // ### gunnar: port properly
956 //        QPixmap pixmap = generatedPixmap();
957 //        if (pixmap.isNull())
958 //            return;
959
960 //        QSGImageNode *pixmapNode = m_context->createImageNode();
961 //        pixmapNode->setRect(pixmap.rect());
962 //        pixmapNode->setSourceRect(pixmap.rect());
963 //        pixmapNode->setOpacity(m_opacity);
964 //        pixmapNode->setClampToEdge(true);
965 //        pixmapNode->setLinearFiltering(m_linearFiltering);
966
967 //        appendChildNode(pixmapNode);
968     } else {
969         if (m_text.isEmpty())
970             return;
971
972         // Implement styling by drawing text several times at slight shifts. shiftForStyle
973         // contains the sequence of shifted positions at which to draw the text. All except
974         // the last will be drawn with styleColor.
975         QList<QPointF> shiftForStyle;
976         switch (m_textStyle) {
977         case OutlineTextStyle:
978             // ### Should be made faster by implementing outline material
979             shiftForStyle << QPointF(-1, 0);
980             shiftForStyle << QPointF(0, -1);
981             shiftForStyle << QPointF(1, 0);
982             shiftForStyle << QPointF(0, 1);
983             break;
984         case SunkenTextStyle:
985             shiftForStyle << QPointF(0, -1);
986             break;
987         case RaisedTextStyle:
988             shiftForStyle << QPointF(0, 1);
989             break;
990         default:
991             break;
992         }
993
994         shiftForStyle << QPointF(0, 0); // Regular position
995         while (!shiftForStyle.isEmpty()) {
996             QPointF shift = shiftForStyle.takeFirst();
997
998             // Use styleColor for all but last shift
999             if (m_richText) {
1000                 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
1001
1002                 QTextFrame *textFrame = m_textDocument->rootFrame();
1003                 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
1004
1005                 QTextFrame::iterator it = textFrame->begin();
1006                 while (!it.atEnd()) {
1007                     addTextBlock(shift + p, it.currentBlock(), overrideColor);
1008                     ++it;
1009                 }
1010             } else {
1011                 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()
1012                                                    ? m_color
1013                                                    : m_styleColor);
1014             }
1015         }
1016     }
1017 }
1018 #endif
1019
1020 QT_END_NAMESPACE