1 /****************************************************************************
3 ** Copyright (C) 2012 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 "qquicktextnode_p.h"
43 #include <QtQuick/qsgsimplerectnode.h>
44 #include <private/qsgadaptationlayer_p.h>
45 #include <private/qsgdistancefieldglyphnode_p.h>
47 #include <QtQuick/private/qsgcontext_p.h>
49 #include <QtCore/qpoint.h>
51 #include <qtextdocument.h>
52 #include <qtextlayout.h>
53 #include <qabstracttextdocumentlayout.h>
54 #include <qxmlstream.h>
56 #include <qtexttable.h>
57 #include <qtextlist.h>
58 #include <private/qdeclarativestyledtext_p.h>
59 #include <private/qfont_p.h>
60 #include <private/qfontengine_p.h>
61 #include <private/qrawfont_p.h>
62 #include <private/qtextimagehandler_p.h>
63 #include <private/qtextdocumentlayout_p.h>
69 Creates an empty QQuickTextNode
71 QQuickTextNode::QQuickTextNode(QSGContext *context)
72 : m_context(context), m_cursorNode(0)
74 #if defined(QML_RUNTIME_TESTING)
75 description = QLatin1String("text");
79 QQuickTextNode::~QQuickTextNode()
81 qDeleteAll(m_textures);
85 void QQuickTextNode::setColor(const QColor &color)
87 if (m_usePixmapCache) {
88 setUpdateFlag(UpdateNodes);
90 for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
91 if (childNode->subType() == GlyphNodeSubType) {
92 QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
93 if (glyphNode->color() == m_color)
94 glyphNode->setColor(color);
95 } else if (childNode->subType() == SolidRectNodeSubType) {
96 QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
97 if (solidRectNode->color() == m_color)
98 solidRectNode->setColor(color);
105 void QQuickTextNode::setStyleColor(const QColor &styleColor)
107 if (m_textStyle != QQuickTextNode::NormalTextStyle) {
108 if (m_usePixmapCache) {
109 setUpdateFlag(UpdateNodes);
111 for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
112 if (childNode->subType() == GlyphNodeSubType) {
113 QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
114 if (glyphNode->color() == m_styleColor)
115 glyphNode->setColor(styleColor);
116 } else if (childNode->subType() == SolidRectNodeSubType) {
117 QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
118 if (solidRectNode->color() == m_styleColor)
119 solidRectNode->setColor(styleColor);
124 m_styleColor = styleColor;
128 QSGGlyphNode *QQuickTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
129 QQuickText::TextStyle style, const QColor &styleColor,
132 QSGGlyphNode *node = m_context->createGlyphNode();
133 node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
134 node->setStyle(style);
135 node->setStyleColor(styleColor);
136 node->setColor(color);
139 /* We flag the geometry as static, but we never call markVertexDataDirty
140 or markIndexDataDirty on them. This is because all text nodes are
141 discarded when a change occurs. If we start appending/removing from
142 existing geometry, then we also need to start marking the geometry as
145 node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
146 node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
150 parentNode->appendChildNode(node);
155 void QQuickTextNode::setCursor(const QRectF &rect, const QColor &color)
157 if (m_cursorNode != 0)
160 m_cursorNode = new QSGSimpleRectNode(rect, color);
161 appendChildNode(m_cursorNode);
166 struct BinaryTreeNode {
167 enum SelectionState {
173 : selectionState(Unselected)
175 , decorations(QQuickTextNode::NoDecoration)
178 , rightChildIndex(-1)
183 BinaryTreeNode(const QRectF &brect, const QImage &i, SelectionState selState, qreal a)
184 : boundingRect(brect)
185 , selectionState(selState)
187 , decorations(QQuickTextNode::NoDecoration)
191 , rightChildIndex(-1)
195 BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
196 const QQuickTextNode::Decorations &decs, const QColor &c, const QColor &bc,
197 const QPointF &pos, qreal a)
199 , boundingRect(brect)
200 , selectionState(selState)
204 , backgroundColor(bc)
208 , rightChildIndex(-1)
214 SelectionState selectionState;
215 QSGClipNode *clipNode;
216 QQuickTextNode::Decorations decorations;
218 QColor backgroundColor;
226 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
230 SelectionState selectionState)
232 insert(binaryTree, BinaryTreeNode(rect, image, selectionState, ascent));
235 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
236 const QGlyphRun &glyphRun,
237 SelectionState selectionState,
238 const QColor &textColor,
239 const QColor &backgroundColor,
240 const QPointF &position)
242 QRectF searchRect = glyphRun.boundingRect();
243 searchRect.translate(position);
245 if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
248 QQuickTextNode::Decorations decorations = QQuickTextNode::NoDecoration;
249 decorations |= (glyphRun.underline() ? QQuickTextNode::Underline : QQuickTextNode::NoDecoration);
250 decorations |= (glyphRun.overline() ? QQuickTextNode::Overline : QQuickTextNode::NoDecoration);
251 decorations |= (glyphRun.strikeOut() ? QQuickTextNode::StrikeOut : QQuickTextNode::NoDecoration);
252 decorations |= (backgroundColor.isValid() ? QQuickTextNode::Background : QQuickTextNode::NoDecoration);
254 qreal ascent = glyphRun.rawFont().ascent();
255 // ### QTBUG-22919 The bounding rect returned by QGlyphRun appears to start on the
256 // baseline, move it by the ascent so all bounding rects are at baseline - ascent.
257 searchRect.translate(0, -ascent);
258 insert(binaryTree, BinaryTreeNode(glyphRun, selectionState, searchRect, decorations,
259 textColor, backgroundColor, position, ascent));
262 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
263 const BinaryTreeNode &binaryTreeNode)
265 int newIndex = binaryTree->size();
266 binaryTree->append(binaryTreeNode);
272 BinaryTreeNode *node = binaryTree->data() + searchIndex;
273 if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) {
274 if (node->leftChildIndex < 0) {
275 node->leftChildIndex = newIndex;
278 searchIndex = node->leftChildIndex;
281 if (node->rightChildIndex < 0) {
282 node->rightChildIndex = newIndex;
285 searchIndex = node->rightChildIndex;
291 static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree,
292 QVarLengthArray<int> *sortedIndexes,
293 int currentIndex = 0)
295 Q_ASSERT(currentIndex < binaryTree.size());
297 const BinaryTreeNode *node = binaryTree.data() + currentIndex;
298 if (node->leftChildIndex >= 0)
299 inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
301 sortedIndexes->append(currentIndex);
303 if (node->rightChildIndex >= 0)
304 inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
308 // Engine that takes glyph runs as input, and produces a set of glyph nodes, clip nodes,
309 // and rectangle nodes to represent the text, decorations and selection. Will try to minimize
310 // number of nodes, and join decorations in neighbouring items
311 class SelectionEngine
314 SelectionEngine() : m_hasSelection(false) {}
316 QTextLine currentLine() const { return m_currentLine; }
318 void setCurrentLine(const QTextLine ¤tLine)
320 if (m_currentLine.isValid())
321 processCurrentLine();
323 m_currentLine = currentLine;
326 void addBorder(const QRectF &rect, qreal border, QTextFrameFormat::BorderStyle borderStyle,
327 const QBrush &borderBrush);
328 void addFrameDecorations(QTextDocument *document, QTextFrame *frame);
329 void addImage(const QRectF &rect, const QImage &image, qreal ascent,
330 BinaryTreeNode::SelectionState selectionState,
331 QTextFrameFormat::Position layoutPosition);
332 int addText(const QTextBlock &block,
333 const QTextCharFormat &charFormat,
334 const QColor &textColor,
335 const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
336 int textPos, int fragmentEnd,
337 int selectionStart, int selectionEnd);
338 void addTextObject(const QPointF &position, const QTextCharFormat &format,
339 BinaryTreeNode::SelectionState selectionState,
340 QTextDocument *textDocument, int pos,
341 QTextFrameFormat::Position layoutPosition = QTextFrameFormat::InFlow);
342 void addSelectedGlyphs(const QGlyphRun &glyphRun);
343 void addUnselectedGlyphs(const QGlyphRun &glyphRun);
344 void addGlyphsInRange(int rangeStart, int rangeEnd,
345 const QColor &color, const QColor &backgroundColor,
346 int selectionStart, int selectionEnd);
347 void addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
349 int selectionStart, int selectionEnd);
351 void addToSceneGraph(QQuickTextNode *parent,
352 QQuickText::TextStyle style = QQuickText::Normal,
353 const QColor &styleColor = QColor());
355 void setSelectionColor(const QColor &selectionColor)
357 m_selectionColor = selectionColor;
360 void setSelectedTextColor(const QColor &selectedTextColor)
362 m_selectedTextColor = selectedTextColor;
365 void setTextColor(const QColor &textColor)
367 m_textColor = textColor;
370 void setPosition(const QPointF &position)
372 m_position = position;
376 struct TextDecoration
378 TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
379 TextDecoration(const BinaryTreeNode::SelectionState &s,
388 BinaryTreeNode::SelectionState selectionState;
393 void processCurrentLine();
394 void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
395 qreal offset, qreal thickness);
397 QColor m_selectionColor;
399 QColor m_backgroundColor;
400 QColor m_selectedTextColor;
403 QTextLine m_currentLine;
406 QList<QPair<QRectF, QColor> > m_backgrounds;
407 QList<QRectF> m_selectionRects;
408 QVarLengthArray<BinaryTreeNode> m_currentLineTree;
410 QList<TextDecoration> m_lines;
411 QVector<BinaryTreeNode> m_processedNodes;
413 QList<QPair<QRectF, QImage> > m_images;
416 int SelectionEngine::addText(const QTextBlock &block,
417 const QTextCharFormat &charFormat,
418 const QColor &textColor,
419 const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
420 int textPos, int fragmentEnd,
421 int selectionStart, int selectionEnd)
423 if (charFormat.foreground().style() != Qt::NoBrush)
424 setTextColor(charFormat.foreground().color());
426 setTextColor(textColor);
428 while (textPos < fragmentEnd) {
429 int blockRelativePosition = textPos - block.position();
430 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
431 if (!currentLine().isValid()
432 || line.lineNumber() != currentLine().lineNumber()) {
433 setCurrentLine(line);
436 Q_ASSERT(line.textLength() > 0);
437 int lineEnd = line.textStart() + block.position() + line.textLength();
439 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
442 int currentStepEnd = textPos + len;
444 addGlyphsForRanges(colorChanges,
445 textPos - block.position(),
446 currentStepEnd - block.position(),
447 selectionStart - block.position(),
448 selectionEnd - block.position());
450 textPos = currentStepEnd;
455 void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
456 qreal offset, qreal thickness)
458 for (int i=0; i<textDecorations.size(); ++i) {
459 TextDecoration textDecoration = textDecorations.at(i);
462 QRectF &rect = textDecoration.rect;
463 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
464 rect.setHeight(thickness);
467 m_lines.append(textDecoration);
471 void SelectionEngine::processCurrentLine()
473 // No glyphs, do nothing
474 if (m_currentLineTree.isEmpty())
477 // 1. Go through current line and get correct decoration position for each node based on
478 // neighbouring decorations. Add decoration to global list
479 // 2. Create clip nodes for all selected text. Try to merge as many as possible within
481 // 3. Add QRects to a list of selection rects.
482 // 4. Add all nodes to a global processed list
483 QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
484 BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
486 Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
488 BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
491 QQuickTextNode::Decorations currentDecorations = QQuickTextNode::NoDecoration;
492 qreal underlineOffset = 0.0;
493 qreal underlineThickness = 0.0;
495 qreal overlineOffset = 0.0;
496 qreal overlineThickness = 0.0;
498 qreal strikeOutOffset = 0.0;
499 qreal strikeOutThickness = 0.0;
501 QRectF decorationRect = currentRect;
504 QColor lastBackgroundColor;
506 QVarLengthArray<TextDecoration> pendingUnderlines;
507 QVarLengthArray<TextDecoration> pendingOverlines;
508 QVarLengthArray<TextDecoration> pendingStrikeOuts;
509 if (!sortedIndexes.isEmpty()) {
510 QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0;
511 bool currentClipNodeUsed = false;
512 for (int i=0; i<=sortedIndexes.size(); ++i) {
513 BinaryTreeNode *node = 0;
514 if (i < sortedIndexes.size()) {
515 int sortedIndex = sortedIndexes.at(i);
516 Q_ASSERT(sortedIndex < m_currentLineTree.size());
518 node = m_currentLineTree.data() + sortedIndex;
522 currentSelectionState = node->selectionState;
524 // Update decorations
525 if (currentDecorations != QQuickTextNode::NoDecoration) {
526 decorationRect.setY(m_position.y() + m_currentLine.y());
527 decorationRect.setHeight(m_currentLine.height());
530 decorationRect.setRight(node->boundingRect.left());
532 TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
533 if (currentDecorations & QQuickTextNode::Underline)
534 pendingUnderlines.append(textDecoration);
536 if (currentDecorations & QQuickTextNode::Overline)
537 pendingOverlines.append(textDecoration);
539 if (currentDecorations & QQuickTextNode::StrikeOut)
540 pendingStrikeOuts.append(textDecoration);
542 if (currentDecorations & QQuickTextNode::Background)
543 m_backgrounds.append(qMakePair(decorationRect, lastBackgroundColor));
546 // If we've reached an unselected node from a selected node, we add the
547 // selection rect to the graph, and we add decoration every time the
548 // selection state changes, because that means the text color changes
549 if (node == 0 || node->selectionState != currentSelectionState) {
551 currentRect.setRight(node->boundingRect.left());
552 currentRect.setY(m_position.y() + m_currentLine.y());
553 currentRect.setHeight(m_currentLine.height());
555 // Draw selection all the way up to the left edge of the unselected item
556 if (currentSelectionState == BinaryTreeNode::Selected)
557 m_selectionRects.append(currentRect);
559 if (currentClipNode != 0) {
560 if (!currentClipNodeUsed) {
561 delete currentClipNode;
563 currentClipNode->setIsRectangular(true);
564 currentClipNode->setClipRect(currentRect);
568 if (node != 0 && m_hasSelection)
569 currentClipNode = new QSGClipNode;
572 currentClipNodeUsed = false;
575 currentSelectionState = node->selectionState;
576 currentRect = node->boundingRect;
578 // Make sure currentRect is valid, otherwise the unite won't work
579 if (currentRect.isNull())
580 currentRect.setSize(QSizeF(1, 1));
583 if (currentRect.isNull())
584 currentRect = node->boundingRect;
586 currentRect = currentRect.united(node->boundingRect);
590 node->clipNode = currentClipNode;
591 currentClipNodeUsed = true;
593 decorationRect = node->boundingRect;
595 // If previous item(s) had underline and current does not, then we add the
596 // pending lines to the lists and likewise for overlines and strikeouts
597 if (!pendingUnderlines.isEmpty()
598 && !(node->decorations & QQuickTextNode::Underline)) {
599 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
601 pendingUnderlines.clear();
603 underlineOffset = 0.0;
604 underlineThickness = 0.0;
607 // ### Add pending when overlineOffset/thickness changes to minimize number of
609 if (!pendingOverlines.isEmpty()) {
610 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
612 pendingOverlines.clear();
614 overlineOffset = 0.0;
615 overlineThickness = 0.0;
618 // ### Add pending when overlineOffset/thickness changes to minimize number of
620 if (!pendingStrikeOuts.isEmpty()) {
621 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
623 pendingStrikeOuts.clear();
625 strikeOutOffset = 0.0;
626 strikeOutThickness = 0.0;
629 // Merge current values with previous. Prefer greatest thickness
630 QRawFont rawFont = node->glyphRun.rawFont();
631 if (node->decorations & QQuickTextNode::Underline) {
632 if (rawFont.lineThickness() > underlineThickness) {
633 underlineThickness = rawFont.lineThickness();
634 underlineOffset = rawFont.underlinePosition();
638 if (node->decorations & QQuickTextNode::Overline) {
639 overlineOffset = -rawFont.ascent();
640 overlineThickness = rawFont.lineThickness();
643 if (node->decorations & QQuickTextNode::StrikeOut) {
644 strikeOutThickness = rawFont.lineThickness();
645 strikeOutOffset = rawFont.ascent() / -3.0;
648 currentDecorations = node->decorations;
649 lastColor = node->color;
650 lastBackgroundColor = node->backgroundColor;
651 m_processedNodes.append(*node);
655 if (!pendingUnderlines.isEmpty())
656 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
658 if (!pendingOverlines.isEmpty())
659 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
661 if (!pendingStrikeOuts.isEmpty())
662 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
665 m_currentLineTree.clear();
666 m_currentLine = QTextLine();
667 m_hasSelection = false;
670 void SelectionEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent,
671 BinaryTreeNode::SelectionState selectionState,
672 QTextFrameFormat::Position layoutPosition)
674 QRectF searchRect = rect;
675 if (layoutPosition == QTextFrameFormat::InFlow) {
676 if (m_currentLineTree.isEmpty()) {
677 searchRect.moveTopLeft(m_position + m_currentLine.position());
679 const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1;
680 if (lastNode->glyphRun.isRightToLeft()) {
681 QPointF lastPos = lastNode->boundingRect.topLeft();
682 searchRect.moveTopRight(lastPos - QPointF(0, ascent - lastNode->ascent));
684 QPointF lastPos = lastNode->boundingRect.topRight();
685 searchRect.moveTopLeft(lastPos - QPointF(0, ascent - lastNode->ascent));
690 BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
693 void SelectionEngine::addTextObject(const QPointF &position, const QTextCharFormat &format,
694 BinaryTreeNode::SelectionState selectionState,
695 QTextDocument *textDocument, int pos,
696 QTextFrameFormat::Position layoutPosition)
698 QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType());
701 QSizeF size = handler->intrinsicSize(textDocument, pos, format);
703 if (format.objectType() == QTextFormat::ImageObject) {
704 QTextImageFormat imageFormat = format.toImageFormat();
705 QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
706 image = imageHandler->image(textDocument, imageFormat);
709 if (image.isNull()) {
710 image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied);
711 image.fill(Qt::transparent);
713 QPainter painter(&image);
714 handler->drawObject(&painter, image.rect(), textDocument, pos, format);
719 QFontMetrics m(format.font());
720 switch (format.verticalAlignment())
722 case QTextCharFormat::AlignMiddle:
723 ascent = size.height() / 2 - 1;
725 case QTextCharFormat::AlignBaseline:
726 ascent = size.height() - m.descent() - 1;
729 ascent = size.height() - 1;
732 addImage(QRectF(position, size), image, ascent, selectionState, layoutPosition);
736 void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
738 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
739 m_textColor, m_backgroundColor, m_position);
742 void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
744 int currentSize = m_currentLineTree.size();
745 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
746 m_textColor, m_backgroundColor, m_position);
747 m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
750 void SelectionEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
752 int selectionStart, int selectionEnd)
754 int currentPosition = start;
755 int remainingLength = end - start;
756 for (int j=0; j<ranges.size(); ++j) {
757 const QTextLayout::FormatRange &range = ranges.at(j);
758 if (range.start + range.length >= currentPosition
759 && range.start < currentPosition + remainingLength) {
761 if (range.start > currentPosition) {
762 addGlyphsInRange(currentPosition, range.start - currentPosition,
763 QColor(), QColor(), selectionStart, selectionEnd);
766 int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
767 QColor rangeColor = range.format.hasProperty(QTextFormat::ForegroundBrush)
768 ? range.format.foreground().color()
770 QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush)
771 ? range.format.background().color()
774 addGlyphsInRange(range.start, rangeEnd - range.start,
775 rangeColor, rangeBackgroundColor,
776 selectionStart, selectionEnd);
778 currentPosition = range.start + range.length;
779 remainingLength = end - currentPosition;
781 } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
786 if (remainingLength > 0) {
787 addGlyphsInRange(currentPosition, remainingLength, QColor(), QColor(),
788 selectionStart, selectionEnd);
793 void SelectionEngine::addGlyphsInRange(int rangeStart, int rangeLength,
794 const QColor &color, const QColor &backgroundColor,
795 int selectionStart, int selectionEnd)
798 if (color.isValid()) {
799 oldColor = m_textColor;
803 QColor oldBackgroundColor = m_backgroundColor;
804 if (backgroundColor.isValid()) {
805 oldBackgroundColor = m_backgroundColor;
806 m_backgroundColor = backgroundColor;
809 bool hasSelection = selectionEnd >= 0
810 && selectionStart <= selectionEnd;
812 QTextLine &line = m_currentLine;
813 int rangeEnd = rangeStart + rangeLength;
814 if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) {
815 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
816 for (int j=0; j<glyphRuns.size(); ++j) {
817 const QGlyphRun &glyphRun = glyphRuns.at(j);
818 addUnselectedGlyphs(glyphRun);
821 if (rangeStart < selectionStart) {
822 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart,
823 qMin(selectionStart - rangeStart,
826 for (int j=0; j<glyphRuns.size(); ++j) {
827 const QGlyphRun &glyphRun = glyphRuns.at(j);
828 addUnselectedGlyphs(glyphRun);
832 if (rangeEnd > selectionStart) {
833 int start = qMax(selectionStart, rangeStart);
834 int length = qMin(selectionEnd - start + 1, rangeEnd - start);
835 QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
837 for (int j=0; j<glyphRuns.size(); ++j) {
838 const QGlyphRun &glyphRun = glyphRuns.at(j);
839 addSelectedGlyphs(glyphRun);
843 if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
844 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd - 1);
845 for (int j=0; j<glyphRuns.size(); ++j) {
846 const QGlyphRun &glyphRun = glyphRuns.at(j);
847 addUnselectedGlyphs(glyphRun);
852 if (backgroundColor.isValid())
853 m_backgroundColor = oldBackgroundColor;
855 if (oldColor.isValid())
856 m_textColor = oldColor;
859 void SelectionEngine::addBorder(const QRectF &rect, qreal border,
860 QTextFrameFormat::BorderStyle borderStyle,
861 const QBrush &borderBrush)
863 QColor color = borderBrush.color();
865 // Currently we don't support other styles than solid
866 Q_UNUSED(borderStyle);
868 m_backgrounds.append(qMakePair(QRectF(rect.left(), rect.top(), border, rect.height() + border), color));
869 m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.top(), rect.width(), border), color));
870 m_backgrounds.append(qMakePair(QRectF(rect.right(), rect.top() + border, border, rect.height() - border), color));
871 m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.bottom(), rect.width(), border), color));
874 void SelectionEngine::addFrameDecorations(QTextDocument *document, QTextFrame *frame)
876 QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(document->documentLayout());
877 QTextFrameFormat frameFormat = frame->format().toFrameFormat();
879 QTextTable *table = qobject_cast<QTextTable *>(frame);
880 QRectF boundingRect = table == 0
881 ? documentLayout->frameBoundingRect(frame)
882 : documentLayout->tableBoundingRect(table);
884 QBrush bg = frame->frameFormat().background();
885 if (bg.style() != Qt::NoBrush)
886 m_backgrounds.append(qMakePair(boundingRect, bg.color()));
888 if (!frameFormat.hasProperty(QTextFormat::FrameBorder))
891 qreal borderWidth = frameFormat.border();
892 if (qFuzzyIsNull(borderWidth))
895 QBrush borderBrush = frameFormat.borderBrush();
896 QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle();
897 if (borderStyle == QTextFrameFormat::BorderStyle_None)
900 addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
901 -frameFormat.rightMargin(), -frameFormat.bottomMargin()),
902 borderWidth, borderStyle, borderBrush);
904 int rows = table->rows();
905 int columns = table->columns();
907 for (int row=0; row<rows; ++row) {
908 for (int column=0; column<columns; ++column) {
909 QTextTableCell cell = table->cellAt(row, column);
911 QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
912 addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth,
913 borderStyle, borderBrush);
919 void SelectionEngine::addToSceneGraph(QQuickTextNode *parentNode,
920 QQuickText::TextStyle style,
921 const QColor &styleColor)
923 if (m_currentLine.isValid())
924 processCurrentLine();
927 for (int i=0; i<m_backgrounds.size(); ++i) {
928 const QRectF &rect = m_backgrounds.at(i).first;
929 const QColor &color = m_backgrounds.at(i).second;
931 parentNode->appendChildNode(new QSGSimpleRectNode(rect, color));
934 // First, prepend all selection rectangles to the tree
935 for (int i=0; i<m_selectionRects.size(); ++i) {
936 const QRectF &rect = m_selectionRects.at(i);
938 parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
941 // Finally, add decorations for each node to the tree.
942 for (int i=0; i<m_lines.size(); ++i) {
943 const TextDecoration &textDecoration = m_lines.at(i);
945 QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
946 ? m_selectedTextColor
947 : textDecoration.color;
949 parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
952 // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common
953 // font, selection state and clip node.
954 typedef QPair<QFontEngine *, QPair<QSGClipNode *, QPair<QRgb, int> > > KeyType;
955 QHash<KeyType, BinaryTreeNode *> map;
956 for (int i=0; i<m_processedNodes.size(); ++i) {
957 BinaryTreeNode *node = m_processedNodes.data() + i;
959 if (node->image.isNull()) {
960 QGlyphRun glyphRun = node->glyphRun;
961 QRawFont rawFont = glyphRun.rawFont();
962 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
964 QFontEngine *fontEngine = rawFontD->fontEngine;
966 KeyType key(qMakePair(fontEngine,
967 qMakePair(node->clipNode,
968 qMakePair(node->color.rgba(), int(node->selectionState)))));
970 BinaryTreeNode *otherNode = map.value(key, 0);
971 if (otherNode != 0) {
972 QGlyphRun &otherGlyphRun = otherNode->glyphRun;
974 QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
975 QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
977 otherGlyphIndexes += glyphRun.glyphIndexes();
979 QVector<QPointF> glyphPositions = glyphRun.positions();
980 for (int j=0; j<glyphPositions.size(); ++j) {
981 otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
984 otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
985 otherGlyphRun.setPositions(otherGlyphPositions);
988 map.insert(key, node);
991 parentNode->addImage(node->boundingRect, node->image);
992 if (node->selectionState == BinaryTreeNode::Selected) {
993 QColor color = m_selectionColor;
995 parentNode->appendChildNode(new QSGSimpleRectNode(node->boundingRect, color));
1000 // ...and add clip nodes and glyphs to tree.
1001 QHash<KeyType, BinaryTreeNode *>::const_iterator it = map.constBegin();
1002 while (it != map.constEnd()) {
1004 BinaryTreeNode *node = it.value();
1006 QSGClipNode *clipNode = node->clipNode;
1007 if (clipNode != 0 && clipNode->parent() == 0 )
1008 parentNode->appendChildNode(clipNode);
1010 QColor color = node->selectionState == BinaryTreeNode::Selected
1011 ? m_selectedTextColor
1014 parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
1021 void QQuickTextNode::mergeFormats(QTextLayout *textLayout,
1022 QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
1024 Q_ASSERT(mergedFormats != 0);
1025 if (textLayout == 0)
1028 QList<QTextLayout::FormatRange> additionalFormats = textLayout->additionalFormats();
1029 for (int i=0; i<additionalFormats.size(); ++i) {
1030 QTextLayout::FormatRange additionalFormat = additionalFormats.at(i);
1031 if (additionalFormat.format.hasProperty(QTextFormat::ForegroundBrush)
1032 || additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)) {
1033 // Merge overlapping formats
1034 if (!mergedFormats->isEmpty()) {
1035 QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1;
1037 if (additionalFormat.start < lastFormat->start + lastFormat->length) {
1038 QTextLayout::FormatRange *mergedRange = 0;
1040 int length = additionalFormat.length;
1041 if (additionalFormat.start > lastFormat->start) {
1042 lastFormat->length = additionalFormat.start - lastFormat->start;
1043 length -= lastFormat->length;
1045 mergedFormats->append(QTextLayout::FormatRange());
1046 mergedRange = mergedFormats->data() + mergedFormats->size() - 1;
1047 lastFormat = mergedFormats->data() + mergedFormats->size() - 2;
1049 mergedRange = lastFormat;
1052 mergedRange->format = lastFormat->format;
1053 mergedRange->format.merge(additionalFormat.format);
1054 mergedRange->start = additionalFormat.start;
1056 int end = qMin(additionalFormat.start + additionalFormat.length,
1057 lastFormat->start + lastFormat->length);
1059 mergedRange->length = end - mergedRange->start;
1060 length -= mergedRange->length;
1062 additionalFormat.start = end;
1063 additionalFormat.length = length;
1067 if (additionalFormat.length > 0)
1068 mergedFormats->append(additionalFormat);
1076 class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
1079 inline QTextCharFormat formatAccessor(int pos)
1087 void QQuickTextNode::addImage(const QRectF &rect, const QImage &image)
1089 QSGImageNode *node = m_context->createImageNode();
1090 QSGTexture *texture = m_context->createTexture(image);
1091 m_textures.append(texture);
1092 node->setTargetRect(rect);
1093 node->setTexture(texture);
1094 appendChildNode(node);
1098 void QQuickTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument,
1099 const QColor &textColor,
1100 QQuickText::TextStyle style, const QColor &styleColor,
1101 const QColor &selectionColor, const QColor &selectedTextColor,
1102 int selectionStart, int selectionEnd)
1104 SelectionEngine engine;
1105 engine.setTextColor(textColor);
1106 engine.setSelectedTextColor(selectedTextColor);
1107 engine.setSelectionColor(selectionColor);
1109 QList<QTextFrame *> frames;
1110 frames.append(textDocument->rootFrame());
1111 while (!frames.isEmpty()) {
1112 QTextFrame *textFrame = frames.takeFirst();
1113 frames.append(textFrame->childFrames());
1115 engine.addFrameDecorations(textDocument, textFrame);
1117 if (textFrame->firstPosition() > textFrame->lastPosition()
1118 && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
1119 const int pos = textFrame->firstPosition() - 1;
1120 ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(textDocument->documentLayout());
1121 QTextCharFormat format = a->formatAccessor(pos);
1122 QRectF rect = a->frameBoundingRect(textFrame);
1124 QTextBlock block = textFrame->firstCursorPosition().block();
1125 engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
1126 engine.addTextObject(rect.topLeft(), format, BinaryTreeNode::Unselected, textDocument,
1127 pos, textFrame->frameFormat().position());
1129 QTextFrame::iterator it = textFrame->begin();
1131 while (!it.atEnd()) {
1132 Q_ASSERT(!engine.currentLine().isValid());
1134 QTextBlock block = it.currentBlock();
1135 int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0;
1136 int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
1138 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1139 mergeFormats(block.layout(), &colorChanges);
1141 QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
1142 if (QTextList *textList = block.textList()) {
1143 QPointF pos = blockPosition;
1144 QTextLayout *layout = block.layout();
1145 if (layout->lineCount() > 0) {
1146 QTextLine firstLine = layout->lineAt(0);
1147 Q_ASSERT(firstLine.isValid());
1149 engine.setCurrentLine(firstLine);
1151 QRectF textRect = firstLine.naturalTextRect();
1152 pos += textRect.topLeft();
1153 if (block.textDirection() == Qt::RightToLeft)
1154 pos.rx() += textRect.width();
1156 const QTextCharFormat charFormat = block.charFormat();
1157 QFont font(charFormat.font());
1158 QFontMetricsF fontMetrics(font);
1159 QTextListFormat listFormat = textList->format();
1161 QString listItemBullet;
1162 switch (listFormat.style()) {
1163 case QTextListFormat::ListCircle:
1164 listItemBullet = QChar(0x25E6); // White bullet
1166 case QTextListFormat::ListSquare:
1167 listItemBullet = QChar(0x25AA); // Black small square
1169 case QTextListFormat::ListDecimal:
1170 case QTextListFormat::ListLowerAlpha:
1171 case QTextListFormat::ListUpperAlpha:
1172 case QTextListFormat::ListLowerRoman:
1173 case QTextListFormat::ListUpperRoman:
1174 listItemBullet = textList->itemText(block);
1177 listItemBullet = QChar(0x2022); // Black bullet
1181 QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height());
1182 qreal xoff = fontMetrics.width(QLatin1Char(' '));
1183 if (block.textDirection() == Qt::LeftToRight)
1184 xoff = -xoff - size.width();
1185 engine.setPosition(pos + QPointF(xoff, 0));
1188 layout.setFont(font);
1189 layout.setText(listItemBullet); // Bullet
1190 layout.beginLayout();
1191 QTextLine line = layout.createLine();
1192 line.setPosition(QPointF(0, 0));
1195 QList<QGlyphRun> glyphRuns = layout.glyphRuns();
1196 for (int i=0; i<glyphRuns.size(); ++i)
1197 engine.addUnselectedGlyphs(glyphRuns.at(i));
1201 int textPos = block.position();
1202 QTextBlock::iterator blockIterator = block.begin();
1203 if (blockIterator.atEnd() && preeditLength) {
1204 engine.setPosition(blockPosition);
1205 textPos = engine.addText(block, block.charFormat(), textColor, colorChanges,
1206 textPos, textPos + preeditLength,
1207 selectionStart, selectionEnd);
1210 while (!blockIterator.atEnd()) {
1211 QTextFragment fragment = blockIterator.fragment();
1212 QString text = fragment.text();
1216 QTextCharFormat charFormat = fragment.charFormat();
1217 engine.setPosition(blockPosition);
1218 if (text.contains(QChar::ObjectReplacementCharacter)) {
1219 QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
1220 if (frame && frame->frameFormat().position() == QTextFrameFormat::InFlow) {
1221 int blockRelativePosition = textPos - block.position();
1222 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
1223 if (!engine.currentLine().isValid()
1224 || line.lineNumber() != engine.currentLine().lineNumber()) {
1225 engine.setCurrentLine(line);
1228 BinaryTreeNode::SelectionState selectionState =
1229 (selectionStart < textPos + text.length()
1230 && selectionEnd >= textPos)
1231 ? BinaryTreeNode::Selected
1232 : BinaryTreeNode::Unselected;
1234 engine.addTextObject(QPointF(), charFormat, selectionState, textDocument, textPos);
1236 textPos += text.length();
1238 int fragmentEnd = textPos + fragment.length();
1239 if (preeditPosition >= 0
1240 && preeditPosition >= textPos
1241 && preeditPosition <= fragmentEnd) {
1242 fragmentEnd += preeditLength;
1245 engine.addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd,
1246 selectionStart, selectionEnd);
1252 engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
1258 engine.addToSceneGraph(this, style, styleColor);
1261 void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
1262 QQuickText::TextStyle style, const QColor &styleColor,
1263 const QColor &selectionColor, const QColor &selectedTextColor,
1264 int selectionStart, int selectionEnd)
1266 SelectionEngine engine;
1267 engine.setTextColor(color);
1268 engine.setSelectedTextColor(selectedTextColor);
1269 engine.setSelectionColor(selectionColor);
1270 engine.setPosition(position);
1272 int preeditLength = textLayout->preeditAreaText().length();
1273 int preeditPosition = textLayout->preeditAreaPosition();
1275 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1276 mergeFormats(textLayout, &colorChanges);
1278 for (int i=0; i<textLayout->lineCount(); ++i) {
1279 QTextLine line = textLayout->lineAt(i);
1281 int start = line.textStart();
1282 int length = line.textLength();
1283 int end = start + length;
1285 if (preeditPosition >= 0
1286 && preeditPosition >= start
1287 && preeditPosition < end) {
1288 end += preeditLength;
1291 engine.setCurrentLine(line);
1292 engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
1295 engine.addToSceneGraph(this, style, styleColor);
1298 void QQuickTextNode::deleteContent()
1300 while (firstChild() != 0)
1301 delete firstChild();
1306 void QQuickTextNode::updateNodes()
1310 if (m_text.isEmpty())
1313 if (m_usePixmapCache) {
1314 // ### gunnar: port properly
1315 // QPixmap pixmap = generatedPixmap();
1316 // if (pixmap.isNull())
1319 // QSGImageNode *pixmapNode = m_context->createImageNode();
1320 // pixmapNode->setRect(pixmap.rect());
1321 // pixmapNode->setSourceRect(pixmap.rect());
1322 // pixmapNode->setOpacity(m_opacity);
1323 // pixmapNode->setClampToEdge(true);
1324 // pixmapNode->setLinearFiltering(m_linearFiltering);
1326 // appendChildNode(pixmapNode);
1328 if (m_text.isEmpty())
1331 // Implement styling by drawing text several times at slight shifts. shiftForStyle
1332 // contains the sequence of shifted positions at which to draw the text. All except
1333 // the last will be drawn with styleColor.
1334 QList<QPointF> shiftForStyle;
1335 switch (m_textStyle) {
1336 case OutlineTextStyle:
1337 // ### Should be made faster by implementing outline material
1338 shiftForStyle << QPointF(-1, 0);
1339 shiftForStyle << QPointF(0, -1);
1340 shiftForStyle << QPointF(1, 0);
1341 shiftForStyle << QPointF(0, 1);
1343 case SunkenTextStyle:
1344 shiftForStyle << QPointF(0, -1);
1346 case RaisedTextStyle:
1347 shiftForStyle << QPointF(0, 1);
1353 shiftForStyle << QPointF(0, 0); // Regular position
1354 while (!shiftForStyle.isEmpty()) {
1355 QPointF shift = shiftForStyle.takeFirst();
1357 // Use styleColor for all but last shift
1359 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
1361 QTextFrame *textFrame = m_textDocument->rootFrame();
1362 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
1364 QTextFrame::iterator it = textFrame->begin();
1365 while (!it.atEnd()) {
1366 addTextBlock(shift + p, it.currentBlock(), overrideColor);
1370 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()