1 /****************************************************************************
3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the QtDeclarative module of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
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>
48 #include <private/qsgcontext_p.h>
50 #include <QtCore/qpoint.h>
52 #include <qtextdocument.h>
53 #include <qtextlayout.h>
54 #include <qabstracttextdocumentlayout.h>
55 #include <qxmlstream.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>
66 Creates an empty QSGTextNode
68 QSGTextNode::QSGTextNode(QSGContext *context)
69 : m_context(context), m_cursorNode(0)
71 #if defined(QML_RUNTIME_TESTING)
72 description = QLatin1String("text");
76 QSGTextNode::~QSGTextNode()
81 void QSGTextNode::setColor(const QColor &color)
83 if (m_usePixmapCache) {
84 setUpdateFlag(UpdateNodes);
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);
101 void QSGTextNode::setStyleColor(const QColor &styleColor)
103 if (m_textStyle != QSGTextNode::NormalTextStyle) {
104 if (m_usePixmapCache) {
105 setUpdateFlag(UpdateNodes);
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);
120 m_styleColor = styleColor;
124 QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
125 QSGText::TextStyle style, const QColor &styleColor,
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);
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
141 node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
142 node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
146 parentNode->appendChildNode(node);
151 void QSGTextNode::setCursor(const QRectF &rect, const QColor &color)
153 if (m_cursorNode != 0)
156 m_cursorNode = new QSGSimpleRectNode(rect, color);
157 appendChildNode(m_cursorNode);
162 struct BinaryTreeNode {
163 enum SelectionState {
169 : selectionState(Unselected)
171 , decorations(QSGTextNode::NoDecoration)
173 , rightChildIndex(-1)
178 BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
179 const QSGTextNode::Decorations &decs, const QColor &c,
182 , boundingRect(brect)
183 , selectionState(selState)
189 , rightChildIndex(-1)
195 SelectionState selectionState;
196 QSGClipNode *clipNode;
197 QSGTextNode::Decorations decorations;
204 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
205 const QGlyphRun &glyphRun,
206 SelectionState selectionState,
207 const QColor &textColor,
208 const QPointF &position)
210 int newIndex = binaryTree->size();
211 QRectF searchRect = glyphRun.boundingRect();
213 if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
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);
221 binaryTree->append(BinaryTreeNode(glyphRun, selectionState, searchRect, decorations,
222 textColor, position));
228 BinaryTreeNode *node = binaryTree->data() + searchIndex;
229 if (searchRect.left() < node->boundingRect.left()) {
230 if (node->leftChildIndex < 0) {
231 node->leftChildIndex = newIndex;
234 searchIndex = node->leftChildIndex;
237 if (node->rightChildIndex < 0) {
238 node->rightChildIndex = newIndex;
241 searchIndex = node->rightChildIndex;
247 static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree,
248 QVarLengthArray<int> *sortedIndexes,
249 int currentIndex = 0)
251 Q_ASSERT(currentIndex < binaryTree.size());
253 const BinaryTreeNode *node = binaryTree.data() + currentIndex;
254 if (node->leftChildIndex >= 0)
255 inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
257 sortedIndexes->append(currentIndex);
259 if (node->rightChildIndex >= 0)
260 inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
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
270 SelectionEngine() : m_hasSelection(false) {}
272 QTextLine currentLine() const { return m_currentLine; }
274 void setCurrentLine(const QTextLine ¤tLine)
276 if (m_currentLine.isValid())
277 processCurrentLine();
279 m_currentLine = currentLine;
282 void addSelectedGlyphs(const QGlyphRun &glyphRun);
283 void addUnselectedGlyphs(const QGlyphRun &glyphRun);
285 void addToSceneGraph(QSGTextNode *parent,
286 QSGText::TextStyle style = QSGText::Normal,
287 const QColor &styleColor = QColor());
289 void setSelectionColor(const QColor &selectionColor)
291 m_selectionColor = selectionColor;
294 void setSelectedTextColor(const QColor &selectedTextColor)
296 m_selectedTextColor = selectedTextColor;
299 void setTextColor(const QColor &textColor)
301 m_textColor = textColor;
304 void setPosition(const QPointF &position)
306 m_position = position;
310 struct TextDecoration
312 TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
313 TextDecoration(const BinaryTreeNode::SelectionState &s,
322 BinaryTreeNode::SelectionState selectionState;
327 void processCurrentLine();
328 void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
329 qreal offset, qreal thickness);
331 QColor m_selectionColor;
333 QColor m_selectedTextColor;
336 QTextLine m_currentLine;
339 QList<QRectF> m_selectionRects;
340 QVarLengthArray<BinaryTreeNode> m_currentLineTree;
342 QList<TextDecoration> m_lines;
343 QVector<BinaryTreeNode> m_processedNodes;
346 void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
347 qreal offset, qreal thickness)
349 for (int i=0; i<textDecorations.size(); ++i) {
350 TextDecoration textDecoration = textDecorations.at(i);
353 QRectF &rect = textDecoration.rect;
354 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
355 rect.setHeight(thickness);
358 m_lines.append(textDecoration);
362 void SelectionEngine::processCurrentLine()
364 // No glyphs, do nothing
365 if (m_currentLineTree.isEmpty())
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
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);
377 Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
379 BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
382 QSGTextNode::Decorations currentDecorations = QSGTextNode::NoDecoration;
383 qreal underlineOffset = 0.0;
384 qreal underlineThickness = 0.0;
386 qreal overlineOffset = 0.0;
387 qreal overlineThickness = 0.0;
389 qreal strikeOutOffset = 0.0;
390 qreal strikeOutThickness = 0.0;
392 QRectF decorationRect = currentRect;
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());
408 node = m_currentLineTree.data() + sortedIndex;
412 currentSelectionState = node->selectionState;
414 // Update decorations
415 if (currentDecorations != QSGTextNode::NoDecoration) {
416 decorationRect.setY(m_position.y() + m_currentLine.y());
417 decorationRect.setHeight(m_currentLine.height());
420 decorationRect.setRight(node->boundingRect.left());
422 TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
423 if (currentDecorations & QSGTextNode::Underline)
424 pendingUnderlines.append(textDecoration);
426 if (currentDecorations & QSGTextNode::Overline)
427 pendingOverlines.append(textDecoration);
429 if (currentDecorations & QSGTextNode::StrikeOut)
430 pendingStrikeOuts.append(textDecoration);
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) {
438 currentRect.setRight(node->boundingRect.left());
439 currentRect.setY(m_position.y() + m_currentLine.y());
440 currentRect.setHeight(m_currentLine.height());
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);
446 if (currentClipNode != 0) {
447 if (!currentClipNodeUsed) {
448 delete currentClipNode;
450 currentClipNode->setIsRectangular(true);
451 currentClipNode->setClipRect(currentRect);
455 if (node != 0 && m_hasSelection)
456 currentClipNode = new QSGClipNode;
459 currentClipNodeUsed = false;
462 currentSelectionState = node->selectionState;
463 currentRect = node->boundingRect;
465 // Make sure currentRect is valid, otherwise the unite won't work
466 if (currentRect.isNull())
467 currentRect.setSize(QSizeF(1, 1));
470 if (currentRect.isNull())
471 currentRect = node->boundingRect;
473 currentRect = currentRect.united(node->boundingRect);
477 node->clipNode = currentClipNode;
478 currentClipNodeUsed = true;
480 decorationRect = node->boundingRect;
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);
488 pendingUnderlines.clear();
490 underlineOffset = 0.0;
491 underlineThickness = 0.0;
494 // ### Add pending when overlineOffset/thickness changes to minimize number of
496 if (!pendingOverlines.isEmpty()) {
497 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
499 pendingOverlines.clear();
501 overlineOffset = 0.0;
502 overlineThickness = 0.0;
505 // ### Add pending when overlineOffset/thickness changes to minimize number of
507 if (!pendingStrikeOuts.isEmpty()) {
508 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
510 pendingStrikeOuts.clear();
512 strikeOutOffset = 0.0;
513 strikeOutThickness = 0.0;
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();
525 if (node->decorations & QSGTextNode::Overline) {
526 overlineOffset = -rawFont.ascent();
527 overlineThickness = rawFont.lineThickness();
530 if (node->decorations & QSGTextNode::StrikeOut) {
531 strikeOutThickness = rawFont.lineThickness();
532 strikeOutOffset = rawFont.ascent() / -3.0;
535 currentDecorations = node->decorations;
536 lastColor = node->color;
537 m_processedNodes.append(*node);
541 // If there are pending decorations, we need to add them
542 if (!pendingUnderlines.isEmpty())
543 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
545 if (!pendingOverlines.isEmpty())
546 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
548 if (!pendingStrikeOuts.isEmpty())
549 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
552 m_currentLineTree.clear();
553 m_currentLine = QTextLine();
554 m_hasSelection = false;
557 void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
559 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
560 m_textColor, m_position);
563 void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
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;
571 void SelectionEngine::addToSceneGraph(QSGTextNode *parentNode,
572 QSGText::TextStyle style,
573 const QColor &styleColor)
575 if (m_currentLine.isValid())
576 processCurrentLine();
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);
582 parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
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;
592 QGlyphRun glyphRun = node->glyphRun;
593 QRawFont rawFont = glyphRun.rawFont();
594 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
596 QFontEngine *fontEngine = rawFontD->fontEngine;
598 KeyType key(qMakePair(fontEngine,
599 qMakePair(node->clipNode,
600 qMakePair(node->color.rgba(), int(node->selectionState)))));
602 BinaryTreeNode *otherNode = map.value(key, 0);
603 if (otherNode != 0) {
604 QGlyphRun &otherGlyphRun = otherNode->glyphRun;
606 QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
607 QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
609 otherGlyphIndexes += glyphRun.glyphIndexes();
611 QVector<QPointF> glyphPositions = glyphRun.positions();
612 for (int j=0; j<glyphPositions.size(); ++j) {
613 otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
616 otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
617 otherGlyphRun.setPositions(otherGlyphPositions);
620 map.insert(key, node);
624 // ...and add clip nodes and glyphs to tree.
625 QHash<KeyType, BinaryTreeNode *>::const_iterator it = map.constBegin();
626 while (it != map.constEnd()) {
628 BinaryTreeNode *node = it.value();
630 QSGClipNode *clipNode = node->clipNode;
631 if (clipNode != 0 && clipNode->parent() == 0 )
632 parentNode->appendChildNode(clipNode);
634 QColor color = node->selectionState == BinaryTreeNode::Selected
635 ? m_selectedTextColor
638 parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
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);
647 QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
648 ? m_selectedTextColor
649 : textDecoration.color;
651 parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
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)
662 QTextFrame *textFrame = textDocument->rootFrame();
663 QPointF position = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
665 SelectionEngine engine;
666 engine.setTextColor(textColor);
667 engine.setSelectedTextColor(selectedTextColor);
668 engine.setSelectionColor(selectionColor);
670 bool hasSelection = selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd;
672 QTextFrame::iterator it = textFrame->begin();
673 while (!it.atEnd()) {
674 Q_ASSERT(!engine.currentLine().isValid());
676 QTextBlock block = it.currentBlock();
678 QTextBlock::iterator blockIterator = block.begin();
679 while (!blockIterator.atEnd()) {
680 QTextFragment fragment = blockIterator.fragment();
681 if (fragment.text().isEmpty())
684 QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
685 engine.setPosition(position + blockPosition);
687 QTextCharFormat charFormat = fragment.charFormat();
688 if (!textColor.isValid())
689 engine.setTextColor(charFormat.foreground().color());
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);
700 int lineEnd = line.textStart() + block.position() + line.textLength();
702 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
705 int currentStepEnd = textPos + len;
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));
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));
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();
726 QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionRelativeBlockPosition,
727 currentSelectionLength);
728 for (int j=0; j<glyphRuns.size(); ++j)
729 engine.addSelectedGlyphs(glyphRuns.at(j));
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));
740 textPos = currentStepEnd;
746 engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
750 engine.addToSceneGraph(this, style, styleColor);
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)
758 SelectionEngine engine;
759 engine.setTextColor(color);
760 engine.setSelectedTextColor(selectedTextColor);
761 engine.setSelectionColor(selectionColor);
762 engine.setPosition(position);
764 for (int i=0; i<textLayout->lineCount(); ++i) {
765 QTextLine line = textLayout->lineAt(i);
767 engine.setCurrentLine(line);
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));
775 if (line.textStart() < selectionStart) {
776 QList<QGlyphRun> glyphRuns = line.glyphRuns(line.textStart(),
777 qMin(selectionStart - line.textStart(),
780 for (int j=0; j<glyphRuns.size(); ++j)
781 engine.addUnselectedGlyphs(glyphRuns.at(j));
784 if (lineEnd >= selectionStart && selectionStart >= line.textStart()) {
785 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionStart, selectionEnd - selectionStart + 1);
787 for (int j=0; j<glyphRuns.size(); ++j)
788 engine.addSelectedGlyphs(glyphRuns.at(j));
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));
799 engine.addToSceneGraph(this, style, styleColor);
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.
809 bool QSGTextNode::isComplexRichText(QTextDocument *doc)
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"));
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);
898 QXmlStreamReader reader(doc->toHtml("utf-8"));
899 while (!reader.atEnd()) {
902 if (reader.isStartElement()) {
903 if (!supportedTags.contains(reader.name().toString().toLower()))
906 QXmlStreamAttributes attributes = reader.attributes();
907 if (attributes.hasAttribute(QLatin1String("bgcolor")))
909 if (attributes.hasAttribute(QLatin1String("style"))) {
910 QCss::StyleSheet styleSheet;
911 QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet);
913 QVector<QCss::Declaration> decls;
914 for (int i=0; i<styleSheet.pageRules.size(); ++i)
915 decls += styleSheet.pageRules.at(i).declarations;
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;
924 for (int i=0; i<styleRules.size(); ++i)
925 decls += styleRules.at(i).declarations;
927 for (int i=0; i<decls.size(); ++i) {
928 if (!supportedCssProperties.contains(decls.at(i).d->propertyId))
936 return reader.hasError();
939 void QSGTextNode::deleteContent()
941 while (firstChild() != 0)
947 void QSGTextNode::updateNodes()
951 if (m_text.isEmpty())
954 if (m_usePixmapCache) {
955 // ### gunnar: port properly
956 // QPixmap pixmap = generatedPixmap();
957 // if (pixmap.isNull())
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);
967 // appendChildNode(pixmapNode);
969 if (m_text.isEmpty())
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);
984 case SunkenTextStyle:
985 shiftForStyle << QPointF(0, -1);
987 case RaisedTextStyle:
988 shiftForStyle << QPointF(0, 1);
994 shiftForStyle << QPointF(0, 0); // Regular position
995 while (!shiftForStyle.isEmpty()) {
996 QPointF shift = shiftForStyle.takeFirst();
998 // Use styleColor for all but last shift
1000 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
1002 QTextFrame *textFrame = m_textDocument->rootFrame();
1003 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
1005 QTextFrame::iterator it = textFrame->begin();
1006 while (!it.atEnd()) {
1007 addTextBlock(shift + p, it.currentBlock(), overrideColor);
1011 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()