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);
284 void addGlyphsInRange(int rangeStart, int rangeEnd, const QColor &color,
285 int selectionStart, int selectionEnd);
287 void addToSceneGraph(QSGTextNode *parent,
288 QSGText::TextStyle style = QSGText::Normal,
289 const QColor &styleColor = QColor());
291 void setSelectionColor(const QColor &selectionColor)
293 m_selectionColor = selectionColor;
296 void setSelectedTextColor(const QColor &selectedTextColor)
298 m_selectedTextColor = selectedTextColor;
301 void setTextColor(const QColor &textColor)
303 m_textColor = textColor;
306 void setPosition(const QPointF &position)
308 m_position = position;
312 struct TextDecoration
314 TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
315 TextDecoration(const BinaryTreeNode::SelectionState &s,
324 BinaryTreeNode::SelectionState selectionState;
329 void processCurrentLine();
330 void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
331 qreal offset, qreal thickness);
333 QColor m_selectionColor;
335 QColor m_selectedTextColor;
338 QTextLine m_currentLine;
341 QList<QRectF> m_selectionRects;
342 QVarLengthArray<BinaryTreeNode> m_currentLineTree;
344 QList<TextDecoration> m_lines;
345 QVector<BinaryTreeNode> m_processedNodes;
348 void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
349 qreal offset, qreal thickness)
351 for (int i=0; i<textDecorations.size(); ++i) {
352 TextDecoration textDecoration = textDecorations.at(i);
355 QRectF &rect = textDecoration.rect;
356 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
357 rect.setHeight(thickness);
360 m_lines.append(textDecoration);
364 void SelectionEngine::processCurrentLine()
366 // No glyphs, do nothing
367 if (m_currentLineTree.isEmpty())
370 // 1. Go through current line and get correct decoration position for each node based on
371 // neighbouring decorations. Add decoration to global list
372 // 2. Create clip nodes for all selected text. Try to merge as many as possible within
374 // 3. Add QRects to a list of selection rects.
375 // 4. Add all nodes to a global processed list
376 QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
377 BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
379 Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
381 BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
384 QSGTextNode::Decorations currentDecorations = QSGTextNode::NoDecoration;
385 qreal underlineOffset = 0.0;
386 qreal underlineThickness = 0.0;
388 qreal overlineOffset = 0.0;
389 qreal overlineThickness = 0.0;
391 qreal strikeOutOffset = 0.0;
392 qreal strikeOutThickness = 0.0;
394 QRectF decorationRect = currentRect;
398 QVarLengthArray<TextDecoration> pendingUnderlines;
399 QVarLengthArray<TextDecoration> pendingOverlines;
400 QVarLengthArray<TextDecoration> pendingStrikeOuts;
401 if (!sortedIndexes.isEmpty()) {
402 QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0;
403 bool currentClipNodeUsed = false;
404 for (int i=0; i<=sortedIndexes.size(); ++i) {
405 BinaryTreeNode *node = 0;
406 if (i < sortedIndexes.size()) {
407 int sortedIndex = sortedIndexes.at(i);
408 Q_ASSERT(sortedIndex < m_currentLineTree.size());
410 node = m_currentLineTree.data() + sortedIndex;
414 currentSelectionState = node->selectionState;
416 // Update decorations
417 if (currentDecorations != QSGTextNode::NoDecoration) {
418 decorationRect.setY(m_position.y() + m_currentLine.y());
419 decorationRect.setHeight(m_currentLine.height());
422 decorationRect.setRight(node->boundingRect.left());
424 TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
425 if (currentDecorations & QSGTextNode::Underline)
426 pendingUnderlines.append(textDecoration);
428 if (currentDecorations & QSGTextNode::Overline)
429 pendingOverlines.append(textDecoration);
431 if (currentDecorations & QSGTextNode::StrikeOut)
432 pendingStrikeOuts.append(textDecoration);
435 // If we've reached an unselected node from a selected node, we add the
436 // selection rect to the graph, and we add decoration every time the
437 // selection state changes, because that means the text color changes
438 if (node == 0 || node->selectionState != currentSelectionState) {
440 currentRect.setRight(node->boundingRect.left());
441 currentRect.setY(m_position.y() + m_currentLine.y());
442 currentRect.setHeight(m_currentLine.height());
444 // Draw selection all the way up to the left edge of the unselected item
445 if (currentSelectionState == BinaryTreeNode::Selected)
446 m_selectionRects.append(currentRect);
448 if (currentClipNode != 0) {
449 if (!currentClipNodeUsed) {
450 delete currentClipNode;
452 currentClipNode->setIsRectangular(true);
453 currentClipNode->setClipRect(currentRect);
457 if (node != 0 && m_hasSelection)
458 currentClipNode = new QSGClipNode;
461 currentClipNodeUsed = false;
464 currentSelectionState = node->selectionState;
465 currentRect = node->boundingRect;
467 // Make sure currentRect is valid, otherwise the unite won't work
468 if (currentRect.isNull())
469 currentRect.setSize(QSizeF(1, 1));
472 if (currentRect.isNull())
473 currentRect = node->boundingRect;
475 currentRect = currentRect.united(node->boundingRect);
479 node->clipNode = currentClipNode;
480 currentClipNodeUsed = true;
482 decorationRect = node->boundingRect;
484 // If previous item(s) had underline and current does not, then we add the
485 // pending lines to the lists and likewise for overlines and strikeouts
486 if (!pendingUnderlines.isEmpty()
487 && !(node->decorations & QSGTextNode::Underline)) {
488 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
490 pendingUnderlines.clear();
492 underlineOffset = 0.0;
493 underlineThickness = 0.0;
496 // ### Add pending when overlineOffset/thickness changes to minimize number of
498 if (!pendingOverlines.isEmpty()) {
499 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
501 pendingOverlines.clear();
503 overlineOffset = 0.0;
504 overlineThickness = 0.0;
507 // ### Add pending when overlineOffset/thickness changes to minimize number of
509 if (!pendingStrikeOuts.isEmpty()) {
510 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
512 pendingStrikeOuts.clear();
514 strikeOutOffset = 0.0;
515 strikeOutThickness = 0.0;
518 // Merge current values with previous. Prefer greatest thickness
519 QRawFont rawFont = node->glyphRun.rawFont();
520 if (node->decorations & QSGTextNode::Underline) {
521 if (rawFont.lineThickness() > underlineThickness) {
522 underlineThickness = rawFont.lineThickness();
523 underlineOffset = rawFont.underlinePosition();
527 if (node->decorations & QSGTextNode::Overline) {
528 overlineOffset = -rawFont.ascent();
529 overlineThickness = rawFont.lineThickness();
532 if (node->decorations & QSGTextNode::StrikeOut) {
533 strikeOutThickness = rawFont.lineThickness();
534 strikeOutOffset = rawFont.ascent() / -3.0;
537 currentDecorations = node->decorations;
538 lastColor = node->color;
539 m_processedNodes.append(*node);
543 // If there are pending decorations, we need to add them
544 if (!pendingUnderlines.isEmpty())
545 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
547 if (!pendingOverlines.isEmpty())
548 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
550 if (!pendingStrikeOuts.isEmpty())
551 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
554 m_currentLineTree.clear();
555 m_currentLine = QTextLine();
556 m_hasSelection = false;
559 void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
561 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
562 m_textColor, m_position);
565 void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
567 int currentSize = m_currentLineTree.size();
568 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
569 m_textColor, m_position);
570 m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
573 void SelectionEngine::addGlyphsInRange(int rangeStart, int rangeLength,
575 int selectionStart, int selectionEnd)
577 QColor oldColor = m_textColor;
580 QTextLine &line = m_currentLine;
581 int rangeEnd = rangeStart + rangeLength;
582 if (selectionStart > rangeEnd || selectionEnd < rangeStart) {
583 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
584 for (int j=0; j<glyphRuns.size(); ++j)
585 addUnselectedGlyphs(glyphRuns.at(j));
587 if (rangeStart < selectionStart) {
588 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart,
589 qMin(selectionStart - rangeStart,
592 for (int j=0; j<glyphRuns.size(); ++j)
593 addUnselectedGlyphs(glyphRuns.at(j));
596 if (rangeEnd >= selectionStart && selectionStart >= rangeStart) {
597 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionStart, selectionEnd - selectionStart + 1);
599 for (int j=0; j<glyphRuns.size(); ++j)
600 addSelectedGlyphs(glyphRuns.at(j));
603 if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
604 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd);
605 for (int j=0; j<glyphRuns.size(); ++j)
606 addUnselectedGlyphs(glyphRuns.at(j));
609 m_textColor = oldColor;
612 void SelectionEngine::addToSceneGraph(QSGTextNode *parentNode,
613 QSGText::TextStyle style,
614 const QColor &styleColor)
616 if (m_currentLine.isValid())
617 processCurrentLine();
619 // First, prepend all selection rectangles to the tree
620 for (int i=0; i<m_selectionRects.size(); ++i) {
621 const QRectF &rect = m_selectionRects.at(i);
623 parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
626 // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common
627 // font, selection state and clip node.
628 typedef QPair<QFontEngine *, QPair<QSGClipNode *, QPair<QRgb, int> > > KeyType;
629 QHash<KeyType, BinaryTreeNode *> map;
630 for (int i=0; i<m_processedNodes.size(); ++i) {
631 BinaryTreeNode *node = m_processedNodes.data() + i;
633 QGlyphRun glyphRun = node->glyphRun;
634 QRawFont rawFont = glyphRun.rawFont();
635 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
637 QFontEngine *fontEngine = rawFontD->fontEngine;
639 KeyType key(qMakePair(fontEngine,
640 qMakePair(node->clipNode,
641 qMakePair(node->color.rgba(), int(node->selectionState)))));
643 BinaryTreeNode *otherNode = map.value(key, 0);
644 if (otherNode != 0) {
645 QGlyphRun &otherGlyphRun = otherNode->glyphRun;
647 QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
648 QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
650 otherGlyphIndexes += glyphRun.glyphIndexes();
652 QVector<QPointF> glyphPositions = glyphRun.positions();
653 for (int j=0; j<glyphPositions.size(); ++j) {
654 otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
657 otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
658 otherGlyphRun.setPositions(otherGlyphPositions);
661 map.insert(key, node);
665 // ...and add clip nodes and glyphs to tree.
666 QHash<KeyType, BinaryTreeNode *>::const_iterator it = map.constBegin();
667 while (it != map.constEnd()) {
669 BinaryTreeNode *node = it.value();
671 QSGClipNode *clipNode = node->clipNode;
672 if (clipNode != 0 && clipNode->parent() == 0 )
673 parentNode->appendChildNode(clipNode);
675 QColor color = node->selectionState == BinaryTreeNode::Selected
676 ? m_selectedTextColor
679 parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
684 // Finally, add decorations for each node to the tree.
685 for (int i=0; i<m_lines.size(); ++i) {
686 const TextDecoration &textDecoration = m_lines.at(i);
688 QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
689 ? m_selectedTextColor
690 : textDecoration.color;
692 parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
697 void QSGTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument,
698 const QColor &textColor,
699 QSGText::TextStyle style, const QColor &styleColor,
700 const QColor &selectionColor, const QColor &selectedTextColor,
701 int selectionStart, int selectionEnd)
703 QTextFrame *textFrame = textDocument->rootFrame();
704 QPointF position = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
706 SelectionEngine engine;
707 engine.setTextColor(textColor);
708 engine.setSelectedTextColor(selectedTextColor);
709 engine.setSelectionColor(selectionColor);
711 bool hasSelection = selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd;
713 QTextFrame::iterator it = textFrame->begin();
714 while (!it.atEnd()) {
715 Q_ASSERT(!engine.currentLine().isValid());
717 QTextBlock block = it.currentBlock();
719 QTextBlock::iterator blockIterator = block.begin();
720 while (!blockIterator.atEnd()) {
721 QTextFragment fragment = blockIterator.fragment();
722 if (fragment.text().isEmpty())
725 QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
726 engine.setPosition(position + blockPosition);
728 QTextCharFormat charFormat = fragment.charFormat();
729 if (!textColor.isValid())
730 engine.setTextColor(charFormat.foreground().color());
732 int fragmentEnd = fragment.position() + fragment.length();
733 int textPos = fragment.position();
734 while (textPos < fragmentEnd) {
735 int blockRelativePosition = textPos - block.position();
736 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
737 Q_ASSERT(line.textLength() > 0);
738 if (!engine.currentLine().isValid() || line.lineNumber() != engine.currentLine().lineNumber())
739 engine.setCurrentLine(line);
741 int lineEnd = line.textStart() + block.position() + line.textLength();
743 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
746 int currentStepEnd = textPos + len;
748 if (!hasSelection || selectionStart > currentStepEnd || selectionEnd < textPos) {
749 QList<QGlyphRun> glyphRuns = fragment.glyphRuns(blockRelativePosition, len);
750 for (int j=0; j<glyphRuns.size(); ++j)
751 engine.addUnselectedGlyphs(glyphRuns.at(j));
753 if (textPos < selectionStart) {
754 int unselectedPartLength = qMin(selectionStart - textPos, len);
755 QList<QGlyphRun> glyphRuns = fragment.glyphRuns(blockRelativePosition,
756 unselectedPartLength);
757 for (int j=0; j<glyphRuns.size(); ++j)
758 engine.addUnselectedGlyphs(glyphRuns.at(j));
761 if (selectionStart < currentStepEnd && selectionEnd > textPos) {
762 int currentSelectionStart = qMax(selectionStart, textPos);
763 int currentSelectionLength = qMin(selectionEnd - currentSelectionStart,
764 currentStepEnd - currentSelectionStart);
765 int selectionRelativeBlockPosition = currentSelectionStart - block.position();
767 QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionRelativeBlockPosition,
768 currentSelectionLength);
769 for (int j=0; j<glyphRuns.size(); ++j)
770 engine.addSelectedGlyphs(glyphRuns.at(j));
773 if (selectionEnd >= textPos && selectionEnd < currentStepEnd) {
774 QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionEnd - block.position(),
775 currentStepEnd - selectionEnd);
776 for (int j=0; j<glyphRuns.size(); ++j)
777 engine.addUnselectedGlyphs(glyphRuns.at(j));
781 textPos = currentStepEnd;
787 engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
791 engine.addToSceneGraph(this, style, styleColor);
794 void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
795 QSGText::TextStyle style, const QColor &styleColor,
796 const QColor &selectionColor, const QColor &selectedTextColor,
797 int selectionStart, int selectionEnd)
799 SelectionEngine engine;
800 engine.setTextColor(color);
801 engine.setSelectedTextColor(selectedTextColor);
802 engine.setSelectionColor(selectionColor);
803 engine.setPosition(position);
805 QList<QTextLayout::FormatRange> additionalFormats = textLayout->additionalFormats();
806 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
807 for (int i=0; i<additionalFormats.size(); ++i) {
808 if (additionalFormats.at(i).format.hasProperty(QTextFormat::ForegroundBrush))
809 colorChanges.append(additionalFormats.at(i));
812 for (int i=0; i<textLayout->lineCount(); ++i) {
813 QTextLine line = textLayout->lineAt(i);
815 engine.setCurrentLine(line);
817 int currentPosition = line.textStart();
818 int remainingLength = line.textLength();
819 for (int j=0; j<colorChanges.size(); ++j) {
820 const QTextLayout::FormatRange &range = colorChanges.at(j);
821 if (range.start + range.length >= currentPosition
822 && range.start < currentPosition + remainingLength) {
824 if (range.start > currentPosition) {
825 engine.addGlyphsInRange(currentPosition, range.start - currentPosition,
826 color, selectionStart, selectionEnd);
829 int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
830 QColor rangeColor = range.format.foreground().color();
832 engine.addGlyphsInRange(range.start, rangeEnd - range.start, rangeColor,
833 selectionStart, selectionEnd);
835 currentPosition = range.start + range.length;
836 remainingLength = line.textStart() + line.textLength() - currentPosition;
838 } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
843 if (remainingLength > 0) {
844 engine.addGlyphsInRange(currentPosition, remainingLength, color,
845 selectionStart, selectionEnd);
849 engine.addToSceneGraph(this, style, styleColor);
854 Returns true if \a text contains any HTML tags, attributes or CSS properties which are unrelated
855 to text, fonts or text layout. Otherwise the function returns false. If the return value is
856 false, \a text is considered to be easily representable in the scenegraph. If it returns true,
857 then the text should be prerendered into a pixmap before it's displayed on screen.
859 bool QSGTextNode::isComplexRichText(QTextDocument *doc)
864 static QSet<QString> supportedTags;
865 if (supportedTags.isEmpty()) {
866 supportedTags.insert(QLatin1String("i"));
867 supportedTags.insert(QLatin1String("b"));
868 supportedTags.insert(QLatin1String("u"));
869 supportedTags.insert(QLatin1String("div"));
870 supportedTags.insert(QLatin1String("big"));
871 supportedTags.insert(QLatin1String("blockquote"));
872 supportedTags.insert(QLatin1String("body"));
873 supportedTags.insert(QLatin1String("br"));
874 supportedTags.insert(QLatin1String("center"));
875 supportedTags.insert(QLatin1String("cite"));
876 supportedTags.insert(QLatin1String("code"));
877 supportedTags.insert(QLatin1String("tt"));
878 supportedTags.insert(QLatin1String("dd"));
879 supportedTags.insert(QLatin1String("dfn"));
880 supportedTags.insert(QLatin1String("em"));
881 supportedTags.insert(QLatin1String("font"));
882 supportedTags.insert(QLatin1String("h1"));
883 supportedTags.insert(QLatin1String("h2"));
884 supportedTags.insert(QLatin1String("h3"));
885 supportedTags.insert(QLatin1String("h4"));
886 supportedTags.insert(QLatin1String("h5"));
887 supportedTags.insert(QLatin1String("h6"));
888 supportedTags.insert(QLatin1String("head"));
889 supportedTags.insert(QLatin1String("html"));
890 supportedTags.insert(QLatin1String("meta"));
891 supportedTags.insert(QLatin1String("nobr"));
892 supportedTags.insert(QLatin1String("p"));
893 supportedTags.insert(QLatin1String("pre"));
894 supportedTags.insert(QLatin1String("qt"));
895 supportedTags.insert(QLatin1String("s"));
896 supportedTags.insert(QLatin1String("samp"));
897 supportedTags.insert(QLatin1String("small"));
898 supportedTags.insert(QLatin1String("span"));
899 supportedTags.insert(QLatin1String("strong"));
900 supportedTags.insert(QLatin1String("sub"));
901 supportedTags.insert(QLatin1String("sup"));
902 supportedTags.insert(QLatin1String("title"));
903 supportedTags.insert(QLatin1String("var"));
904 supportedTags.insert(QLatin1String("style"));
907 static QSet<QCss::Property> supportedCssProperties;
908 if (supportedCssProperties.isEmpty()) {
909 supportedCssProperties.insert(QCss::Color);
910 supportedCssProperties.insert(QCss::Float);
911 supportedCssProperties.insert(QCss::Font);
912 supportedCssProperties.insert(QCss::FontFamily);
913 supportedCssProperties.insert(QCss::FontSize);
914 supportedCssProperties.insert(QCss::FontStyle);
915 supportedCssProperties.insert(QCss::FontWeight);
916 supportedCssProperties.insert(QCss::Margin);
917 supportedCssProperties.insert(QCss::MarginBottom);
918 supportedCssProperties.insert(QCss::MarginLeft);
919 supportedCssProperties.insert(QCss::MarginRight);
920 supportedCssProperties.insert(QCss::MarginTop);
921 supportedCssProperties.insert(QCss::TextDecoration);
922 supportedCssProperties.insert(QCss::TextIndent);
923 supportedCssProperties.insert(QCss::TextUnderlineStyle);
924 supportedCssProperties.insert(QCss::VerticalAlignment);
925 supportedCssProperties.insert(QCss::Whitespace);
926 supportedCssProperties.insert(QCss::Padding);
927 supportedCssProperties.insert(QCss::PaddingLeft);
928 supportedCssProperties.insert(QCss::PaddingRight);
929 supportedCssProperties.insert(QCss::PaddingTop);
930 supportedCssProperties.insert(QCss::PaddingBottom);
931 supportedCssProperties.insert(QCss::PageBreakBefore);
932 supportedCssProperties.insert(QCss::PageBreakAfter);
933 supportedCssProperties.insert(QCss::Width);
934 supportedCssProperties.insert(QCss::Height);
935 supportedCssProperties.insert(QCss::MinimumWidth);
936 supportedCssProperties.insert(QCss::MinimumHeight);
937 supportedCssProperties.insert(QCss::MaximumWidth);
938 supportedCssProperties.insert(QCss::MaximumHeight);
939 supportedCssProperties.insert(QCss::Left);
940 supportedCssProperties.insert(QCss::Right);
941 supportedCssProperties.insert(QCss::Top);
942 supportedCssProperties.insert(QCss::Bottom);
943 supportedCssProperties.insert(QCss::Position);
944 supportedCssProperties.insert(QCss::TextAlignment);
945 supportedCssProperties.insert(QCss::FontVariant);
948 QXmlStreamReader reader(doc->toHtml("utf-8"));
949 while (!reader.atEnd()) {
952 if (reader.isStartElement()) {
953 if (!supportedTags.contains(reader.name().toString().toLower()))
956 QXmlStreamAttributes attributes = reader.attributes();
957 if (attributes.hasAttribute(QLatin1String("bgcolor")))
959 if (attributes.hasAttribute(QLatin1String("style"))) {
960 QCss::StyleSheet styleSheet;
961 QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet);
963 QVector<QCss::Declaration> decls;
964 for (int i=0; i<styleSheet.pageRules.size(); ++i)
965 decls += styleSheet.pageRules.at(i).declarations;
967 QVector<QCss::StyleRule> styleRules =
968 styleSheet.styleRules
969 + styleSheet.idIndex.values().toVector()
970 + styleSheet.nameIndex.values().toVector();
971 for (int i=0; i<styleSheet.mediaRules.size(); ++i)
972 styleRules += styleSheet.mediaRules.at(i).styleRules;
974 for (int i=0; i<styleRules.size(); ++i)
975 decls += styleRules.at(i).declarations;
977 for (int i=0; i<decls.size(); ++i) {
978 if (!supportedCssProperties.contains(decls.at(i).d->propertyId))
986 return reader.hasError();
989 void QSGTextNode::deleteContent()
991 while (firstChild() != 0)
997 void QSGTextNode::updateNodes()
1001 if (m_text.isEmpty())
1004 if (m_usePixmapCache) {
1005 // ### gunnar: port properly
1006 // QPixmap pixmap = generatedPixmap();
1007 // if (pixmap.isNull())
1010 // QSGImageNode *pixmapNode = m_context->createImageNode();
1011 // pixmapNode->setRect(pixmap.rect());
1012 // pixmapNode->setSourceRect(pixmap.rect());
1013 // pixmapNode->setOpacity(m_opacity);
1014 // pixmapNode->setClampToEdge(true);
1015 // pixmapNode->setLinearFiltering(m_linearFiltering);
1017 // appendChildNode(pixmapNode);
1019 if (m_text.isEmpty())
1022 // Implement styling by drawing text several times at slight shifts. shiftForStyle
1023 // contains the sequence of shifted positions at which to draw the text. All except
1024 // the last will be drawn with styleColor.
1025 QList<QPointF> shiftForStyle;
1026 switch (m_textStyle) {
1027 case OutlineTextStyle:
1028 // ### Should be made faster by implementing outline material
1029 shiftForStyle << QPointF(-1, 0);
1030 shiftForStyle << QPointF(0, -1);
1031 shiftForStyle << QPointF(1, 0);
1032 shiftForStyle << QPointF(0, 1);
1034 case SunkenTextStyle:
1035 shiftForStyle << QPointF(0, -1);
1037 case RaisedTextStyle:
1038 shiftForStyle << QPointF(0, 1);
1044 shiftForStyle << QPointF(0, 0); // Regular position
1045 while (!shiftForStyle.isEmpty()) {
1046 QPointF shift = shiftForStyle.takeFirst();
1048 // Use styleColor for all but last shift
1050 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
1052 QTextFrame *textFrame = m_textDocument->rootFrame();
1053 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
1055 QTextFrame::iterator it = textFrame->begin();
1056 while (!it.atEnd()) {
1057 addTextBlock(shift + p, it.currentBlock(), overrideColor);
1061 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()