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);
137 parentNode->appendChildNode(node);
142 void QSGTextNode::setCursor(const QRectF &rect, const QColor &color)
144 if (m_cursorNode != 0)
147 m_cursorNode = new QSGSimpleRectNode(rect, color);
148 appendChildNode(m_cursorNode);
153 struct BinaryTreeNode {
154 enum SelectionState {
160 : selectionState(Unselected)
162 , decorations(QSGTextNode::NoDecoration)
164 , rightChildIndex(-1)
169 BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
170 const QSGTextNode::Decorations &decs, const QColor &c,
173 , boundingRect(brect)
174 , selectionState(selState)
180 , rightChildIndex(-1)
186 SelectionState selectionState;
187 QSGClipNode *clipNode;
188 QSGTextNode::Decorations decorations;
195 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
196 const QGlyphRun &glyphRun,
197 SelectionState selectionState,
198 const QColor &textColor,
199 const QPointF &position)
201 int newIndex = binaryTree->size();
202 QRectF searchRect = glyphRun.boundingRect();
204 if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
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);
212 binaryTree->append(BinaryTreeNode(glyphRun, selectionState, searchRect, decorations,
213 textColor, position));
219 BinaryTreeNode *node = binaryTree->data() + searchIndex;
220 if (searchRect.left() < node->boundingRect.left()) {
221 if (node->leftChildIndex < 0) {
222 node->leftChildIndex = newIndex;
225 searchIndex = node->leftChildIndex;
228 if (node->rightChildIndex < 0) {
229 node->rightChildIndex = newIndex;
232 searchIndex = node->rightChildIndex;
238 static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree,
239 QVarLengthArray<int> *sortedIndexes,
240 int currentIndex = 0)
242 Q_ASSERT(currentIndex < binaryTree.size());
244 const BinaryTreeNode *node = binaryTree.data() + currentIndex;
245 if (node->leftChildIndex >= 0)
246 inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
248 sortedIndexes->append(currentIndex);
250 if (node->rightChildIndex >= 0)
251 inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
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
261 SelectionEngine() : m_hasSelection(false) {}
263 QTextLine currentLine() const { return m_currentLine; }
265 void setCurrentLine(const QTextLine ¤tLine)
267 if (m_currentLine.isValid())
268 processCurrentLine();
270 m_currentLine = currentLine;
273 void addSelectedGlyphs(const QGlyphRun &glyphRun);
274 void addUnselectedGlyphs(const QGlyphRun &glyphRun);
276 void addToSceneGraph(QSGTextNode *parent,
277 QSGText::TextStyle style = QSGText::Normal,
278 const QColor &styleColor = QColor());
280 void setSelectionColor(const QColor &selectionColor)
282 m_selectionColor = selectionColor;
285 void setSelectedTextColor(const QColor &selectedTextColor)
287 m_selectedTextColor = selectedTextColor;
290 void setTextColor(const QColor &textColor)
292 m_textColor = textColor;
295 void setPosition(const QPointF &position)
297 m_position = position;
301 struct TextDecoration
303 TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
304 TextDecoration(const BinaryTreeNode::SelectionState &s,
313 BinaryTreeNode::SelectionState selectionState;
318 void processCurrentLine();
319 void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
320 qreal offset, qreal thickness);
322 QColor m_selectionColor;
324 QColor m_selectedTextColor;
327 QTextLine m_currentLine;
330 QList<QRectF> m_selectionRects;
331 QVarLengthArray<BinaryTreeNode> m_currentLineTree;
333 QList<TextDecoration> m_lines;
334 QVector<BinaryTreeNode> m_processedNodes;
337 void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
338 qreal offset, qreal thickness)
340 for (int i=0; i<textDecorations.size(); ++i) {
341 TextDecoration textDecoration = textDecorations.at(i);
344 QRectF &rect = textDecoration.rect;
345 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
346 rect.setHeight(thickness);
349 m_lines.append(textDecoration);
353 void SelectionEngine::processCurrentLine()
355 // No glyphs, do nothing
356 if (m_currentLineTree.isEmpty())
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
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);
368 Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
370 BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
373 QSGTextNode::Decorations currentDecorations = QSGTextNode::NoDecoration;
374 qreal underlineOffset = 0.0;
375 qreal underlineThickness = 0.0;
377 qreal overlineOffset = 0.0;
378 qreal overlineThickness = 0.0;
380 qreal strikeOutOffset = 0.0;
381 qreal strikeOutThickness = 0.0;
383 QRectF decorationRect = currentRect;
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());
399 node = m_currentLineTree.data() + sortedIndex;
403 currentSelectionState = node->selectionState;
405 // Update decorations
406 if (currentDecorations != QSGTextNode::NoDecoration) {
407 decorationRect.setY(m_position.y() + m_currentLine.y());
408 decorationRect.setHeight(m_currentLine.height());
411 decorationRect.setRight(node->boundingRect.left());
413 TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
414 if (currentDecorations & QSGTextNode::Underline)
415 pendingUnderlines.append(textDecoration);
417 if (currentDecorations & QSGTextNode::Overline)
418 pendingOverlines.append(textDecoration);
420 if (currentDecorations & QSGTextNode::StrikeOut)
421 pendingStrikeOuts.append(textDecoration);
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) {
429 currentRect.setRight(node->boundingRect.left());
430 currentRect.setY(m_position.y() + m_currentLine.y());
431 currentRect.setHeight(m_currentLine.height());
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);
437 if (currentClipNode != 0) {
438 if (!currentClipNodeUsed) {
439 delete currentClipNode;
441 currentClipNode->setIsRectangular(true);
442 currentClipNode->setClipRect(currentRect);
446 if (node != 0 && m_hasSelection)
447 currentClipNode = new QSGClipNode;
450 currentClipNodeUsed = false;
453 currentSelectionState = node->selectionState;
454 currentRect = node->boundingRect;
456 // Make sure currentRect is valid, otherwise the unite won't work
457 if (currentRect.isNull())
458 currentRect.setSize(QSizeF(1, 1));
461 if (currentRect.isNull())
462 currentRect = node->boundingRect;
464 currentRect = currentRect.united(node->boundingRect);
468 node->clipNode = currentClipNode;
469 currentClipNodeUsed = true;
471 decorationRect = node->boundingRect;
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);
479 pendingUnderlines.clear();
481 underlineOffset = 0.0;
482 underlineThickness = 0.0;
485 // ### Add pending when overlineOffset/thickness changes to minimize number of
487 if (!pendingOverlines.isEmpty()) {
488 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
490 pendingOverlines.clear();
492 overlineOffset = 0.0;
493 overlineThickness = 0.0;
496 // ### Add pending when overlineOffset/thickness changes to minimize number of
498 if (!pendingStrikeOuts.isEmpty()) {
499 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
501 pendingStrikeOuts.clear();
503 strikeOutOffset = 0.0;
504 strikeOutThickness = 0.0;
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();
516 if (node->decorations & QSGTextNode::Overline) {
517 overlineOffset = -rawFont.ascent();
518 overlineThickness = rawFont.lineThickness();
521 if (node->decorations & QSGTextNode::StrikeOut) {
522 strikeOutThickness = rawFont.lineThickness();
523 strikeOutOffset = rawFont.ascent() / -3.0;
526 currentDecorations = node->decorations;
527 lastColor = node->color;
528 m_processedNodes.append(*node);
532 // If there are pending decorations, we need to add them
533 if (!pendingUnderlines.isEmpty())
534 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
536 if (!pendingOverlines.isEmpty())
537 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
539 if (!pendingStrikeOuts.isEmpty())
540 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
543 m_currentLineTree.clear();
544 m_currentLine = QTextLine();
545 m_hasSelection = false;
548 void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
550 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
551 m_textColor, m_position);
554 void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
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;
562 void SelectionEngine::addToSceneGraph(QSGTextNode *parentNode,
563 QSGText::TextStyle style,
564 const QColor &styleColor)
566 if (m_currentLine.isValid())
567 processCurrentLine();
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);
573 parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
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;
583 QGlyphRun glyphRun = node->glyphRun;
584 QRawFont rawFont = glyphRun.rawFont();
585 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
587 QFontEngine *fontEngine = rawFontD->fontEngine;
589 KeyType key(qMakePair(fontEngine,
590 qMakePair(node->clipNode,
591 qMakePair(node->color.rgba(), int(node->selectionState)))));
593 BinaryTreeNode *otherNode = map.value(key, 0);
594 if (otherNode != 0) {
595 QGlyphRun &otherGlyphRun = otherNode->glyphRun;
597 QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
598 QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
600 otherGlyphIndexes += glyphRun.glyphIndexes();
602 QVector<QPointF> glyphPositions = glyphRun.positions();
603 for (int j=0; j<glyphPositions.size(); ++j) {
604 otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
607 otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
608 otherGlyphRun.setPositions(otherGlyphPositions);
611 map.insert(key, node);
615 // ...and add clip nodes and glyphs to tree.
616 QHash<KeyType, BinaryTreeNode *>::const_iterator it = map.constBegin();
617 while (it != map.constEnd()) {
619 BinaryTreeNode *node = it.value();
621 QSGClipNode *clipNode = node->clipNode;
622 if (clipNode != 0 && clipNode->parent() == 0 )
623 parentNode->appendChildNode(clipNode);
625 QColor color = node->selectionState == BinaryTreeNode::Selected
626 ? m_selectedTextColor
629 parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
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);
638 QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
639 ? m_selectedTextColor
640 : textDecoration.color;
642 parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
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)
653 QTextFrame *textFrame = textDocument->rootFrame();
654 QPointF position = textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
656 SelectionEngine engine;
657 engine.setTextColor(textColor);
658 engine.setSelectedTextColor(selectedTextColor);
659 engine.setSelectionColor(selectionColor);
661 bool hasSelection = selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd;
663 QTextFrame::iterator it = textFrame->begin();
664 while (!it.atEnd()) {
665 Q_ASSERT(!engine.currentLine().isValid());
667 QTextBlock block = it.currentBlock();
669 QTextBlock::iterator blockIterator = block.begin();
670 while (!blockIterator.atEnd()) {
671 QTextFragment fragment = blockIterator.fragment();
672 if (fragment.text().isEmpty())
675 QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
676 engine.setPosition(position + blockPosition);
678 QTextCharFormat charFormat = fragment.charFormat();
679 if (!textColor.isValid())
680 engine.setTextColor(charFormat.foreground().color());
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);
691 int lineEnd = line.textStart() + block.position() + line.textLength();
693 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
696 int currentStepEnd = textPos + len;
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));
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));
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();
717 QList<QGlyphRun> glyphRuns = fragment.glyphRuns(selectionRelativeBlockPosition,
718 currentSelectionLength);
719 for (int j=0; j<glyphRuns.size(); ++j)
720 engine.addSelectedGlyphs(glyphRuns.at(j));
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));
731 textPos = currentStepEnd;
737 engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
741 engine.addToSceneGraph(this, style, styleColor);
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)
749 SelectionEngine engine;
750 engine.setTextColor(color);
751 engine.setSelectedTextColor(selectedTextColor);
752 engine.setSelectionColor(selectionColor);
753 engine.setPosition(position);
755 for (int i=0; i<textLayout->lineCount(); ++i) {
756 QTextLine line = textLayout->lineAt(i);
758 engine.setCurrentLine(line);
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));
766 if (line.textStart() < selectionStart) {
767 QList<QGlyphRun> glyphRuns = line.glyphRuns(line.textStart(),
768 qMin(selectionStart - line.textStart(),
771 for (int j=0; j<glyphRuns.size(); ++j)
772 engine.addUnselectedGlyphs(glyphRuns.at(j));
775 if (lineEnd >= selectionStart && selectionStart >= line.textStart()) {
776 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionStart, selectionEnd - selectionStart + 1);
778 for (int j=0; j<glyphRuns.size(); ++j)
779 engine.addSelectedGlyphs(glyphRuns.at(j));
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));
790 engine.addToSceneGraph(this, style, styleColor);
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.
800 bool QSGTextNode::isComplexRichText(QTextDocument *doc)
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"));
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);
889 QXmlStreamReader reader(doc->toHtml("utf-8"));
890 while (!reader.atEnd()) {
893 if (reader.isStartElement()) {
894 if (!supportedTags.contains(reader.name().toString().toLower()))
897 QXmlStreamAttributes attributes = reader.attributes();
898 if (attributes.hasAttribute(QLatin1String("bgcolor")))
900 if (attributes.hasAttribute(QLatin1String("style"))) {
901 QCss::StyleSheet styleSheet;
902 QCss::Parser(attributes.value(QLatin1String("style")).toString()).parse(&styleSheet);
904 QVector<QCss::Declaration> decls;
905 for (int i=0; i<styleSheet.pageRules.size(); ++i)
906 decls += styleSheet.pageRules.at(i).declarations;
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;
915 for (int i=0; i<styleRules.size(); ++i)
916 decls += styleRules.at(i).declarations;
918 for (int i=0; i<decls.size(); ++i) {
919 if (!supportedCssProperties.contains(decls.at(i).d->propertyId))
927 return reader.hasError();
930 void QSGTextNode::deleteContent()
932 while (firstChild() != 0)
938 void QSGTextNode::updateNodes()
942 if (m_text.isEmpty())
945 if (m_usePixmapCache) {
946 // ### gunnar: port properly
947 // QPixmap pixmap = generatedPixmap();
948 // if (pixmap.isNull())
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);
958 // appendChildNode(pixmapNode);
960 if (m_text.isEmpty())
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);
975 case SunkenTextStyle:
976 shiftForStyle << QPointF(0, -1);
978 case RaisedTextStyle:
979 shiftForStyle << QPointF(0, 1);
985 shiftForStyle << QPointF(0, 0); // Regular position
986 while (!shiftForStyle.isEmpty()) {
987 QPointF shift = shiftForStyle.takeFirst();
989 // Use styleColor for all but last shift
991 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
993 QTextFrame *textFrame = m_textDocument->rootFrame();
994 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
996 QTextFrame::iterator it = textFrame->begin();
997 while (!it.atEnd()) {
998 addTextBlock(shift + p, it.currentBlock(), overrideColor);
1002 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()