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