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 <qtexttable.h>
58 #include <qtextlist.h>
59 #include <private/qdeclarativestyledtext_p.h>
60 #include <private/qfont_p.h>
61 #include <private/qfontengine_p.h>
62 #include <private/qrawfont_p.h>
63 #include <private/qtextimagehandler_p.h>
64 #include <private/qtextdocumentlayout_p.h>
70 Creates an empty QSGTextNode
72 QSGTextNode::QSGTextNode(QSGContext *context)
73 : m_context(context), m_cursorNode(0)
75 #if defined(QML_RUNTIME_TESTING)
76 description = QLatin1String("text");
80 QSGTextNode::~QSGTextNode()
82 qDeleteAll(m_textures);
86 void QSGTextNode::setColor(const QColor &color)
88 if (m_usePixmapCache) {
89 setUpdateFlag(UpdateNodes);
91 for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
92 if (childNode->subType() == GlyphNodeSubType) {
93 QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
94 if (glyphNode->color() == m_color)
95 glyphNode->setColor(color);
96 } else if (childNode->subType() == SolidRectNodeSubType) {
97 QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
98 if (solidRectNode->color() == m_color)
99 solidRectNode->setColor(color);
106 void QSGTextNode::setStyleColor(const QColor &styleColor)
108 if (m_textStyle != QSGTextNode::NormalTextStyle) {
109 if (m_usePixmapCache) {
110 setUpdateFlag(UpdateNodes);
112 for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
113 if (childNode->subType() == GlyphNodeSubType) {
114 QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
115 if (glyphNode->color() == m_styleColor)
116 glyphNode->setColor(styleColor);
117 } else if (childNode->subType() == SolidRectNodeSubType) {
118 QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
119 if (solidRectNode->color() == m_styleColor)
120 solidRectNode->setColor(styleColor);
125 m_styleColor = styleColor;
129 QSGGlyphNode *QSGTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
130 QSGText::TextStyle style, const QColor &styleColor,
133 QSGGlyphNode *node = m_context->createGlyphNode();
134 node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
135 node->setStyle(style);
136 node->setStyleColor(styleColor);
137 node->setColor(color);
140 /* We flag the geometry as static, but we never call markVertexDataDirty
141 or markIndexDataDirty on them. This is because all text nodes are
142 discarded when a change occurs. If we start appending/removing from
143 existing geometry, then we also need to start marking the geometry as
146 node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
147 node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
151 parentNode->appendChildNode(node);
156 void QSGTextNode::setCursor(const QRectF &rect, const QColor &color)
158 if (m_cursorNode != 0)
161 m_cursorNode = new QSGSimpleRectNode(rect, color);
162 appendChildNode(m_cursorNode);
167 struct BinaryTreeNode {
168 enum SelectionState {
174 : selectionState(Unselected)
176 , decorations(QSGTextNode::NoDecoration)
179 , rightChildIndex(-1)
184 BinaryTreeNode(const QRectF &brect, const QImage &i, SelectionState selState, qreal a)
185 : boundingRect(brect)
186 , selectionState(selState)
188 , decorations(QSGTextNode::NoDecoration)
192 , rightChildIndex(-1)
196 BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
197 const QSGTextNode::Decorations &decs, const QColor &c, const QColor &bc,
198 const QPointF &pos, qreal a)
200 , boundingRect(brect)
201 , selectionState(selState)
205 , backgroundColor(bc)
209 , rightChildIndex(-1)
215 SelectionState selectionState;
216 QSGClipNode *clipNode;
217 QSGTextNode::Decorations decorations;
219 QColor backgroundColor;
227 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
231 SelectionState selectionState)
233 insert(binaryTree, BinaryTreeNode(rect, image, selectionState, ascent));
236 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
237 const QGlyphRun &glyphRun,
238 SelectionState selectionState,
239 const QColor &textColor,
240 const QColor &backgroundColor,
241 const QPointF &position)
243 QRectF searchRect = glyphRun.boundingRect();
244 searchRect.translate(position);
246 if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
249 QSGTextNode::Decorations decorations = QSGTextNode::NoDecoration;
250 decorations |= (glyphRun.underline() ? QSGTextNode::Underline : QSGTextNode::NoDecoration);
251 decorations |= (glyphRun.overline() ? QSGTextNode::Overline : QSGTextNode::NoDecoration);
252 decorations |= (glyphRun.strikeOut() ? QSGTextNode::StrikeOut : QSGTextNode::NoDecoration);
253 decorations |= (backgroundColor.isValid() ? QSGTextNode::Background : QSGTextNode::NoDecoration);
255 qreal ascent = glyphRun.rawFont().ascent();
256 insert(binaryTree, BinaryTreeNode(glyphRun, selectionState, searchRect, decorations,
257 textColor, backgroundColor, position, ascent));
260 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
261 const BinaryTreeNode &binaryTreeNode)
263 int newIndex = binaryTree->size();
264 binaryTree->append(binaryTreeNode);
270 BinaryTreeNode *node = binaryTree->data() + searchIndex;
271 if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) {
272 if (node->leftChildIndex < 0) {
273 node->leftChildIndex = newIndex;
276 searchIndex = node->leftChildIndex;
279 if (node->rightChildIndex < 0) {
280 node->rightChildIndex = newIndex;
283 searchIndex = node->rightChildIndex;
289 static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree,
290 QVarLengthArray<int> *sortedIndexes,
291 int currentIndex = 0)
293 Q_ASSERT(currentIndex < binaryTree.size());
295 const BinaryTreeNode *node = binaryTree.data() + currentIndex;
296 if (node->leftChildIndex >= 0)
297 inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
299 sortedIndexes->append(currentIndex);
301 if (node->rightChildIndex >= 0)
302 inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
306 // Engine that takes glyph runs as input, and produces a set of glyph nodes, clip nodes,
307 // and rectangle nodes to represent the text, decorations and selection. Will try to minimize
308 // number of nodes, and join decorations in neighbouring items
309 class SelectionEngine
312 SelectionEngine() : m_hasSelection(false) {}
314 QTextLine currentLine() const { return m_currentLine; }
316 void setCurrentLine(const QTextLine ¤tLine)
318 if (m_currentLine.isValid())
319 processCurrentLine();
321 m_currentLine = currentLine;
324 void addBorder(const QRectF &rect, qreal border, QTextFrameFormat::BorderStyle borderStyle,
325 const QBrush &borderBrush);
326 void addFrameDecorations(QTextDocument *document, QTextFrame *frame);
327 void addImage(const QRectF &rect, const QImage &image, qreal ascent,
328 BinaryTreeNode::SelectionState selectionState);
329 void addTextObject(const QPointF &position, const QTextCharFormat &format,
330 BinaryTreeNode::SelectionState selectionState,
331 QTextDocument *textDocument, int pos);
332 void addSelectedGlyphs(const QGlyphRun &glyphRun);
333 void addUnselectedGlyphs(const QGlyphRun &glyphRun);
334 void addGlyphsInRange(int rangeStart, int rangeEnd,
335 const QColor &color, const QColor &backgroundColor,
336 int selectionStart, int selectionEnd);
337 void addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
339 int selectionStart, int selectionEnd);
341 void addToSceneGraph(QSGTextNode *parent,
342 QSGText::TextStyle style = QSGText::Normal,
343 const QColor &styleColor = QColor());
345 void setSelectionColor(const QColor &selectionColor)
347 m_selectionColor = selectionColor;
350 void setSelectedTextColor(const QColor &selectedTextColor)
352 m_selectedTextColor = selectedTextColor;
355 void setTextColor(const QColor &textColor)
357 m_textColor = textColor;
360 void setPosition(const QPointF &position)
362 m_position = position;
366 struct TextDecoration
368 TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
369 TextDecoration(const BinaryTreeNode::SelectionState &s,
378 BinaryTreeNode::SelectionState selectionState;
383 void processCurrentLine();
384 void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
385 qreal offset, qreal thickness);
387 QColor m_selectionColor;
389 QColor m_backgroundColor;
390 QColor m_selectedTextColor;
393 QTextLine m_currentLine;
396 QList<QPair<QRectF, QColor> > m_backgrounds;
397 QList<QRectF> m_selectionRects;
398 QVarLengthArray<BinaryTreeNode> m_currentLineTree;
400 QList<TextDecoration> m_lines;
401 QVector<BinaryTreeNode> m_processedNodes;
403 QList<QPair<QRectF, QImage> > m_images;
406 void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
407 qreal offset, qreal thickness)
409 for (int i=0; i<textDecorations.size(); ++i) {
410 TextDecoration textDecoration = textDecorations.at(i);
413 QRectF &rect = textDecoration.rect;
414 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
415 rect.setHeight(thickness);
418 m_lines.append(textDecoration);
422 void SelectionEngine::processCurrentLine()
424 // No glyphs, do nothing
425 if (m_currentLineTree.isEmpty())
428 // 1. Go through current line and get correct decoration position for each node based on
429 // neighbouring decorations. Add decoration to global list
430 // 2. Create clip nodes for all selected text. Try to merge as many as possible within
432 // 3. Add QRects to a list of selection rects.
433 // 4. Add all nodes to a global processed list
434 QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
435 BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
437 Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
439 BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
442 QSGTextNode::Decorations currentDecorations = QSGTextNode::NoDecoration;
443 qreal underlineOffset = 0.0;
444 qreal underlineThickness = 0.0;
446 qreal overlineOffset = 0.0;
447 qreal overlineThickness = 0.0;
449 qreal strikeOutOffset = 0.0;
450 qreal strikeOutThickness = 0.0;
452 QRectF decorationRect = currentRect;
455 QColor lastBackgroundColor;
457 QVarLengthArray<TextDecoration> pendingUnderlines;
458 QVarLengthArray<TextDecoration> pendingOverlines;
459 QVarLengthArray<TextDecoration> pendingStrikeOuts;
460 if (!sortedIndexes.isEmpty()) {
461 QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0;
462 bool currentClipNodeUsed = false;
463 for (int i=0; i<=sortedIndexes.size(); ++i) {
464 BinaryTreeNode *node = 0;
465 if (i < sortedIndexes.size()) {
466 int sortedIndex = sortedIndexes.at(i);
467 Q_ASSERT(sortedIndex < m_currentLineTree.size());
469 node = m_currentLineTree.data() + sortedIndex;
473 currentSelectionState = node->selectionState;
475 // Update decorations
476 if (currentDecorations != QSGTextNode::NoDecoration) {
477 decorationRect.setY(m_position.y() + m_currentLine.y());
478 decorationRect.setHeight(m_currentLine.height());
481 decorationRect.setRight(node->boundingRect.left());
483 TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
484 if (currentDecorations & QSGTextNode::Underline)
485 pendingUnderlines.append(textDecoration);
487 if (currentDecorations & QSGTextNode::Overline)
488 pendingOverlines.append(textDecoration);
490 if (currentDecorations & QSGTextNode::StrikeOut)
491 pendingStrikeOuts.append(textDecoration);
493 if (currentDecorations & QSGTextNode::Background)
494 m_backgrounds.append(qMakePair(decorationRect, lastBackgroundColor));
497 // If we've reached an unselected node from a selected node, we add the
498 // selection rect to the graph, and we add decoration every time the
499 // selection state changes, because that means the text color changes
500 if (node == 0 || node->selectionState != currentSelectionState) {
502 currentRect.setRight(node->boundingRect.left());
503 currentRect.setY(m_position.y() + m_currentLine.y());
504 currentRect.setHeight(m_currentLine.height());
506 // Draw selection all the way up to the left edge of the unselected item
507 if (currentSelectionState == BinaryTreeNode::Selected)
508 m_selectionRects.append(currentRect);
510 if (currentClipNode != 0) {
511 if (!currentClipNodeUsed) {
512 delete currentClipNode;
514 currentClipNode->setIsRectangular(true);
515 currentClipNode->setClipRect(currentRect);
519 if (node != 0 && m_hasSelection)
520 currentClipNode = new QSGClipNode;
523 currentClipNodeUsed = false;
526 currentSelectionState = node->selectionState;
527 currentRect = node->boundingRect;
529 // Make sure currentRect is valid, otherwise the unite won't work
530 if (currentRect.isNull())
531 currentRect.setSize(QSizeF(1, 1));
534 if (currentRect.isNull())
535 currentRect = node->boundingRect;
537 currentRect = currentRect.united(node->boundingRect);
541 node->clipNode = currentClipNode;
542 currentClipNodeUsed = true;
544 decorationRect = node->boundingRect;
546 // If previous item(s) had underline and current does not, then we add the
547 // pending lines to the lists and likewise for overlines and strikeouts
548 if (!pendingUnderlines.isEmpty()
549 && !(node->decorations & QSGTextNode::Underline)) {
550 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
552 pendingUnderlines.clear();
554 underlineOffset = 0.0;
555 underlineThickness = 0.0;
558 // ### Add pending when overlineOffset/thickness changes to minimize number of
560 if (!pendingOverlines.isEmpty()) {
561 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
563 pendingOverlines.clear();
565 overlineOffset = 0.0;
566 overlineThickness = 0.0;
569 // ### Add pending when overlineOffset/thickness changes to minimize number of
571 if (!pendingStrikeOuts.isEmpty()) {
572 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
574 pendingStrikeOuts.clear();
576 strikeOutOffset = 0.0;
577 strikeOutThickness = 0.0;
580 // Merge current values with previous. Prefer greatest thickness
581 QRawFont rawFont = node->glyphRun.rawFont();
582 if (node->decorations & QSGTextNode::Underline) {
583 if (rawFont.lineThickness() > underlineThickness) {
584 underlineThickness = rawFont.lineThickness();
585 underlineOffset = rawFont.underlinePosition();
589 if (node->decorations & QSGTextNode::Overline) {
590 overlineOffset = -rawFont.ascent();
591 overlineThickness = rawFont.lineThickness();
594 if (node->decorations & QSGTextNode::StrikeOut) {
595 strikeOutThickness = rawFont.lineThickness();
596 strikeOutOffset = rawFont.ascent() / -3.0;
599 currentDecorations = node->decorations;
600 lastColor = node->color;
601 lastBackgroundColor = node->backgroundColor;
602 m_processedNodes.append(*node);
606 if (!pendingUnderlines.isEmpty())
607 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
609 if (!pendingOverlines.isEmpty())
610 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
612 if (!pendingStrikeOuts.isEmpty())
613 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
616 m_currentLineTree.clear();
617 m_currentLine = QTextLine();
618 m_hasSelection = false;
621 void SelectionEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent,
622 BinaryTreeNode::SelectionState selectionState)
624 QRectF searchRect = rect;
625 if (searchRect.topLeft().isNull()) {
626 if (m_currentLineTree.isEmpty()) {
627 searchRect.moveTopLeft(m_position);
629 const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1;
630 if (lastNode->glyphRun.isRightToLeft()) {
631 QPointF lastPos = lastNode->boundingRect.topLeft();
632 searchRect.moveTopRight(lastPos - QPointF(0, ascent));
634 QPointF lastPos = lastNode->boundingRect.topRight();
635 searchRect.moveTopLeft(lastPos - QPointF(0, ascent));
640 BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
643 void SelectionEngine::addTextObject(const QPointF &position, const QTextCharFormat &format,
644 BinaryTreeNode::SelectionState selectionState,
645 QTextDocument *textDocument, int pos)
647 QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType());
650 QSizeF size = handler->intrinsicSize(textDocument, pos, format);
652 if (format.objectType() == QTextFormat::ImageObject) {
653 QTextImageFormat imageFormat = format.toImageFormat();
654 QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
655 image = imageHandler->image(textDocument, imageFormat);
658 if (image.isNull()) {
659 image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied);
660 image.fill(Qt::transparent);
662 QPainter painter(&image);
663 handler->drawObject(&painter, image.rect(), textDocument, pos, format);
668 QFontMetrics m(format.font());
669 switch (format.verticalAlignment())
671 case QTextCharFormat::AlignMiddle:
672 ascent = size.height() / 2 - 1;
674 case QTextCharFormat::AlignBaseline:
675 ascent = size.height() - m.descent() - 1;
678 ascent = size.height() - 1;
681 addImage(QRectF(position, size), image, ascent, selectionState);
685 void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
687 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
688 m_textColor, m_backgroundColor, m_position);
691 void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
693 int currentSize = m_currentLineTree.size();
694 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
695 m_textColor, m_backgroundColor, m_position);
696 m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
699 void SelectionEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
701 int selectionStart, int selectionEnd)
703 int currentPosition = start;
704 int remainingLength = end - start;
705 for (int j=0; j<ranges.size(); ++j) {
706 const QTextLayout::FormatRange &range = ranges.at(j);
707 if (range.start + range.length >= currentPosition
708 && range.start < currentPosition + remainingLength) {
710 if (range.start > currentPosition) {
711 addGlyphsInRange(currentPosition, range.start - currentPosition,
712 QColor(), QColor(), selectionStart, selectionEnd);
715 int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
716 QColor rangeColor = range.format.hasProperty(QTextFormat::ForegroundBrush)
717 ? range.format.foreground().color()
719 QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush)
720 ? range.format.background().color()
723 addGlyphsInRange(range.start, rangeEnd - range.start,
724 rangeColor, rangeBackgroundColor,
725 selectionStart, selectionEnd);
727 currentPosition = range.start + range.length;
728 remainingLength = end - currentPosition;
730 } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
735 if (remainingLength > 0) {
736 addGlyphsInRange(currentPosition, remainingLength, QColor(), QColor(),
737 selectionStart, selectionEnd);
742 void SelectionEngine::addGlyphsInRange(int rangeStart, int rangeLength,
743 const QColor &color, const QColor &backgroundColor,
744 int selectionStart, int selectionEnd)
747 if (color.isValid()) {
748 oldColor = m_textColor;
752 QColor oldBackgroundColor = m_backgroundColor;
753 if (backgroundColor.isValid()) {
754 oldBackgroundColor = m_backgroundColor;
755 m_backgroundColor = backgroundColor;
758 bool hasSelection = selectionEnd >= 0
759 && selectionStart <= selectionEnd;
761 QTextLine &line = m_currentLine;
762 int rangeEnd = rangeStart + rangeLength;
763 if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) {
764 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
765 for (int j=0; j<glyphRuns.size(); ++j) {
766 const QGlyphRun &glyphRun = glyphRuns.at(j);
767 addUnselectedGlyphs(glyphRun);
770 if (rangeStart < selectionStart) {
771 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart,
772 qMin(selectionStart - rangeStart,
775 for (int j=0; j<glyphRuns.size(); ++j) {
776 const QGlyphRun &glyphRun = glyphRuns.at(j);
777 addUnselectedGlyphs(glyphRun);
781 if (rangeEnd > selectionStart) {
782 int start = qMax(selectionStart, rangeStart);
783 int length = qMin(selectionEnd - start + 1, rangeEnd - start);
784 QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
786 for (int j=0; j<glyphRuns.size(); ++j) {
787 const QGlyphRun &glyphRun = glyphRuns.at(j);
788 addSelectedGlyphs(glyphRun);
792 if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
793 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd - 1);
794 for (int j=0; j<glyphRuns.size(); ++j) {
795 const QGlyphRun &glyphRun = glyphRuns.at(j);
796 addUnselectedGlyphs(glyphRun);
801 if (backgroundColor.isValid())
802 m_backgroundColor = oldBackgroundColor;
804 if (oldColor.isValid())
805 m_textColor = oldColor;
808 void SelectionEngine::addBorder(const QRectF &rect, qreal border,
809 QTextFrameFormat::BorderStyle borderStyle,
810 const QBrush &borderBrush)
812 QColor color = borderBrush.color();
814 // Currently we don't support other styles than solid
815 Q_UNUSED(borderStyle);
817 m_backgrounds.append(qMakePair(QRectF(rect.left(), rect.top(), border, rect.height() + border), color));
818 m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.top(), rect.width(), border), color));
819 m_backgrounds.append(qMakePair(QRectF(rect.right(), rect.top() + border, border, rect.height() - border), color));
820 m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.bottom(), rect.width(), border), color));
823 void SelectionEngine::addFrameDecorations(QTextDocument *document, QTextFrame *frame)
825 QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(document->documentLayout());
826 QTextFrameFormat frameFormat = frame->format().toFrameFormat();
828 QTextTable *table = qobject_cast<QTextTable *>(frame);
829 QRectF boundingRect = table == 0
830 ? documentLayout->frameBoundingRect(frame)
831 : documentLayout->tableBoundingRect(table);
833 QBrush bg = frame->frameFormat().background();
834 if (bg.style() != Qt::NoBrush)
835 m_backgrounds.append(qMakePair(boundingRect, bg.color()));
837 if (!frameFormat.hasProperty(QTextFormat::FrameBorder))
840 qreal borderWidth = frameFormat.border();
841 if (qFuzzyIsNull(borderWidth))
844 QBrush borderBrush = frameFormat.borderBrush();
845 QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle();
846 if (borderStyle == QTextFrameFormat::BorderStyle_None)
849 addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
850 -frameFormat.rightMargin(), -frameFormat.bottomMargin()),
851 borderWidth, borderStyle, borderBrush);
853 int rows = table->rows();
854 int columns = table->columns();
856 for (int row=0; row<rows; ++row) {
857 for (int column=0; column<columns; ++column) {
858 QTextTableCell cell = table->cellAt(row, column);
860 QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
861 addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth,
862 borderStyle, borderBrush);
868 void SelectionEngine::addToSceneGraph(QSGTextNode *parentNode,
869 QSGText::TextStyle style,
870 const QColor &styleColor)
872 if (m_currentLine.isValid())
873 processCurrentLine();
876 for (int i=0; i<m_backgrounds.size(); ++i) {
877 const QRectF &rect = m_backgrounds.at(i).first;
878 const QColor &color = m_backgrounds.at(i).second;
880 parentNode->appendChildNode(new QSGSimpleRectNode(rect, color));
883 // First, prepend all selection rectangles to the tree
884 for (int i=0; i<m_selectionRects.size(); ++i) {
885 const QRectF &rect = m_selectionRects.at(i);
887 parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
890 // Finally, add decorations for each node to the tree.
891 for (int i=0; i<m_lines.size(); ++i) {
892 const TextDecoration &textDecoration = m_lines.at(i);
894 QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
895 ? m_selectedTextColor
896 : textDecoration.color;
898 parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
901 // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common
902 // font, selection state and clip node.
903 typedef QPair<QFontEngine *, QPair<QSGClipNode *, QPair<QRgb, int> > > KeyType;
904 QHash<KeyType, BinaryTreeNode *> map;
905 for (int i=0; i<m_processedNodes.size(); ++i) {
906 BinaryTreeNode *node = m_processedNodes.data() + i;
908 if (node->image.isNull()) {
909 QGlyphRun glyphRun = node->glyphRun;
910 QRawFont rawFont = glyphRun.rawFont();
911 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
913 QFontEngine *fontEngine = rawFontD->fontEngine;
915 KeyType key(qMakePair(fontEngine,
916 qMakePair(node->clipNode,
917 qMakePair(node->color.rgba(), int(node->selectionState)))));
919 BinaryTreeNode *otherNode = map.value(key, 0);
920 if (otherNode != 0) {
921 QGlyphRun &otherGlyphRun = otherNode->glyphRun;
923 QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
924 QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
926 otherGlyphIndexes += glyphRun.glyphIndexes();
928 QVector<QPointF> glyphPositions = glyphRun.positions();
929 for (int j=0; j<glyphPositions.size(); ++j) {
930 otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
933 otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
934 otherGlyphRun.setPositions(otherGlyphPositions);
937 map.insert(key, node);
940 parentNode->addImage(node->boundingRect, node->image);
941 if (node->selectionState == BinaryTreeNode::Selected) {
942 QColor color = m_selectionColor;
944 parentNode->appendChildNode(new QSGSimpleRectNode(node->boundingRect,
950 // ...and add clip nodes and glyphs to tree.
951 QHash<KeyType, BinaryTreeNode *>::const_iterator it = map.constBegin();
952 while (it != map.constEnd()) {
954 BinaryTreeNode *node = it.value();
956 QSGClipNode *clipNode = node->clipNode;
957 if (clipNode != 0 && clipNode->parent() == 0 )
958 parentNode->appendChildNode(clipNode);
960 QColor color = node->selectionState == BinaryTreeNode::Selected
961 ? m_selectedTextColor
964 parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
971 void QSGTextNode::mergeFormats(QTextLayout *textLayout,
972 QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
974 Q_ASSERT(mergedFormats != 0);
978 QList<QTextLayout::FormatRange> additionalFormats = textLayout->additionalFormats();
979 for (int i=0; i<additionalFormats.size(); ++i) {
980 QTextLayout::FormatRange additionalFormat = additionalFormats.at(i);
981 if (additionalFormat.format.hasProperty(QTextFormat::ForegroundBrush)
982 || additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)) {
983 // Merge overlapping formats
984 if (!mergedFormats->isEmpty()) {
985 QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1;
987 if (additionalFormat.start < lastFormat->start + lastFormat->length) {
988 QTextLayout::FormatRange *mergedRange = 0;
990 int length = additionalFormat.length;
991 if (additionalFormat.start > lastFormat->start) {
992 lastFormat->length = additionalFormat.start - lastFormat->start;
993 length -= lastFormat->length;
995 mergedFormats->append(QTextLayout::FormatRange());
996 mergedRange = mergedFormats->data() + mergedFormats->size() - 1;
997 lastFormat = mergedFormats->data() + mergedFormats->size() - 2;
999 mergedRange = lastFormat;
1002 mergedRange->format = lastFormat->format;
1003 mergedRange->format.merge(additionalFormat.format);
1004 mergedRange->start = additionalFormat.start;
1006 int end = qMin(additionalFormat.start + additionalFormat.length,
1007 lastFormat->start + lastFormat->length);
1009 mergedRange->length = end - mergedRange->start;
1010 length -= mergedRange->length;
1012 additionalFormat.start = end;
1013 additionalFormat.length = length;
1017 if (additionalFormat.length > 0)
1018 mergedFormats->append(additionalFormat);
1026 class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
1029 inline QTextCharFormat formatAccessor(int pos)
1037 void QSGTextNode::addImage(const QRectF &rect, const QImage &image)
1039 QSGImageNode *node = m_context->createImageNode();
1040 QSGTexture *texture = m_context->createTexture(image);
1041 m_textures.append(texture);
1042 node->setTargetRect(rect);
1043 node->setTexture(texture);
1044 appendChildNode(node);
1048 void QSGTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument,
1049 const QColor &textColor,
1050 QSGText::TextStyle style, const QColor &styleColor,
1051 const QColor &selectionColor, const QColor &selectedTextColor,
1052 int selectionStart, int selectionEnd)
1054 SelectionEngine engine;
1055 engine.setTextColor(textColor);
1056 engine.setSelectedTextColor(selectedTextColor);
1057 engine.setSelectionColor(selectionColor);
1059 QList<QTextFrame *> frames;
1060 frames.append(textDocument->rootFrame());
1061 while (!frames.isEmpty()) {
1062 QTextFrame *textFrame = frames.takeFirst();
1063 frames.append(textFrame->childFrames());
1065 engine.addFrameDecorations(textDocument, textFrame);
1067 if (textFrame->firstPosition() > textFrame->lastPosition()
1068 && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
1069 const int pos = textFrame->firstPosition() - 1;
1070 ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(textDocument->documentLayout());
1071 QTextCharFormat format = a->formatAccessor(pos);
1072 QRectF rect = a->frameBoundingRect(textFrame);
1074 engine.addTextObject(rect.topLeft(), format, BinaryTreeNode::Unselected, textDocument,
1077 QTextFrame::iterator it = textFrame->begin();
1079 while (!it.atEnd()) {
1080 Q_ASSERT(!engine.currentLine().isValid());
1082 QTextBlock block = it.currentBlock();
1083 int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0;
1084 int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
1086 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1087 mergeFormats(block.layout(), &colorChanges);
1089 QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
1090 if (QTextList *textList = block.textList()) {
1091 QPointF pos = blockPosition;
1092 QTextLayout *layout = block.layout();
1093 if (layout->lineCount() > 0) {
1094 QTextLine firstLine = layout->lineAt(0);
1095 Q_ASSERT(firstLine.isValid());
1097 engine.setCurrentLine(firstLine);
1099 QRectF textRect = firstLine.naturalTextRect();
1100 pos += textRect.topLeft();
1101 if (block.textDirection() == Qt::RightToLeft)
1102 pos.rx() += textRect.width();
1104 const QTextCharFormat charFormat = block.charFormat();
1105 QFont font(charFormat.font());
1106 QFontMetricsF fontMetrics(font);
1107 QTextListFormat listFormat = textList->format();
1109 QString listItemBullet;
1110 switch (listFormat.style()) {
1111 case QTextListFormat::ListCircle:
1112 listItemBullet = QChar(0x25E6); // White bullet
1114 case QTextListFormat::ListSquare:
1115 listItemBullet = QChar(0x25AA); // Black small square
1117 case QTextListFormat::ListDecimal:
1118 case QTextListFormat::ListLowerAlpha:
1119 case QTextListFormat::ListUpperAlpha:
1120 case QTextListFormat::ListLowerRoman:
1121 case QTextListFormat::ListUpperRoman:
1122 listItemBullet = textList->itemText(block);
1125 listItemBullet = QChar(0x2022); // Black bullet
1129 QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height());
1130 qreal xoff = fontMetrics.width(QLatin1Char(' '));
1131 if (block.textDirection() == Qt::LeftToRight)
1132 xoff = -xoff - size.width();
1133 engine.setPosition(pos + QPointF(xoff, 0));
1136 layout.setFont(font);
1137 layout.setText(listItemBullet); // Bullet
1138 layout.beginLayout();
1139 QTextLine line = layout.createLine();
1140 line.setPosition(QPointF(0, 0));
1143 QList<QGlyphRun> glyphRuns = layout.glyphRuns();
1144 for (int i=0; i<glyphRuns.size(); ++i)
1145 engine.addUnselectedGlyphs(glyphRuns.at(i));
1149 int textPos = block.position();
1150 QTextBlock::iterator blockIterator = block.begin();
1151 while (!blockIterator.atEnd()) {
1152 QTextFragment fragment = blockIterator.fragment();
1153 QString text = fragment.text();
1157 QTextCharFormat charFormat = fragment.charFormat();
1158 engine.setPosition(blockPosition);
1159 if (text.contains(QChar::ObjectReplacementCharacter)) {
1160 QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
1162 BinaryTreeNode::SelectionState selectionState =
1163 (selectionStart < textPos + text.length()
1164 && selectionEnd >= textPos)
1165 ? BinaryTreeNode::Selected
1166 : BinaryTreeNode::Unselected;
1168 engine.addTextObject(QPointF(), charFormat, selectionState, textDocument, textPos);
1170 textPos += text.length();
1172 if (!textColor.isValid())
1173 engine.setTextColor(charFormat.foreground().color());
1175 int fragmentEnd = textPos + fragment.length();
1176 if (preeditPosition >= 0
1177 && preeditPosition >= textPos
1178 && preeditPosition < fragmentEnd) {
1179 fragmentEnd += preeditLength;
1182 while (textPos < fragmentEnd) {
1183 int blockRelativePosition = textPos - block.position();
1184 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
1185 if (!engine.currentLine().isValid()
1186 || line.lineNumber() != engine.currentLine().lineNumber()) {
1187 engine.setCurrentLine(line);
1190 Q_ASSERT(line.textLength() > 0);
1191 int lineEnd = line.textStart() + block.position() + line.textLength();
1193 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
1196 int currentStepEnd = textPos + len;
1198 engine.addGlyphsForRanges(colorChanges,
1199 textPos - block.position(),
1200 currentStepEnd - block.position(),
1201 selectionStart - block.position(),
1202 selectionEnd - block.position());
1204 textPos = currentStepEnd;
1211 engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
1217 engine.addToSceneGraph(this, style, styleColor);
1220 void QSGTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
1221 QSGText::TextStyle style, const QColor &styleColor,
1222 const QColor &selectionColor, const QColor &selectedTextColor,
1223 int selectionStart, int selectionEnd)
1225 SelectionEngine engine;
1226 engine.setTextColor(color);
1227 engine.setSelectedTextColor(selectedTextColor);
1228 engine.setSelectionColor(selectionColor);
1229 engine.setPosition(position);
1231 int preeditLength = textLayout->preeditAreaText().length();
1232 int preeditPosition = textLayout->preeditAreaPosition();
1234 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1235 mergeFormats(textLayout, &colorChanges);
1237 for (int i=0; i<textLayout->lineCount(); ++i) {
1238 QTextLine line = textLayout->lineAt(i);
1240 int start = line.textStart();
1241 int length = line.textLength();
1242 int end = start + length;
1244 if (preeditPosition >= 0
1245 && preeditPosition >= start
1246 && preeditPosition < end) {
1247 end += preeditLength;
1250 engine.setCurrentLine(line);
1251 engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
1254 engine.addToSceneGraph(this, style, styleColor);
1257 void QSGTextNode::deleteContent()
1259 while (firstChild() != 0)
1260 delete firstChild();
1265 void QSGTextNode::updateNodes()
1269 if (m_text.isEmpty())
1272 if (m_usePixmapCache) {
1273 // ### gunnar: port properly
1274 // QPixmap pixmap = generatedPixmap();
1275 // if (pixmap.isNull())
1278 // QSGImageNode *pixmapNode = m_context->createImageNode();
1279 // pixmapNode->setRect(pixmap.rect());
1280 // pixmapNode->setSourceRect(pixmap.rect());
1281 // pixmapNode->setOpacity(m_opacity);
1282 // pixmapNode->setClampToEdge(true);
1283 // pixmapNode->setLinearFiltering(m_linearFiltering);
1285 // appendChildNode(pixmapNode);
1287 if (m_text.isEmpty())
1290 // Implement styling by drawing text several times at slight shifts. shiftForStyle
1291 // contains the sequence of shifted positions at which to draw the text. All except
1292 // the last will be drawn with styleColor.
1293 QList<QPointF> shiftForStyle;
1294 switch (m_textStyle) {
1295 case OutlineTextStyle:
1296 // ### Should be made faster by implementing outline material
1297 shiftForStyle << QPointF(-1, 0);
1298 shiftForStyle << QPointF(0, -1);
1299 shiftForStyle << QPointF(1, 0);
1300 shiftForStyle << QPointF(0, 1);
1302 case SunkenTextStyle:
1303 shiftForStyle << QPointF(0, -1);
1305 case RaisedTextStyle:
1306 shiftForStyle << QPointF(0, 1);
1312 shiftForStyle << QPointF(0, 0); // Regular position
1313 while (!shiftForStyle.isEmpty()) {
1314 QPointF shift = shiftForStyle.takeFirst();
1316 // Use styleColor for all but last shift
1318 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
1320 QTextFrame *textFrame = m_textDocument->rootFrame();
1321 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
1323 QTextFrame::iterator it = textFrame->begin();
1324 while (!it.atEnd()) {
1325 addTextBlock(shift + p, it.currentBlock(), overrideColor);
1329 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()