1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtDeclarative module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** 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/qquicktext_p_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 QQuickTextNode
72 QQuickTextNode::QQuickTextNode(QSGContext *context, QQuickItem *ownerElement)
73 : m_context(context), m_cursorNode(0), m_ownerElement(ownerElement)
75 #if defined(QML_RUNTIME_TESTING)
76 description = QLatin1String("text");
80 QQuickTextNode::~QQuickTextNode()
82 qDeleteAll(m_textures);
86 void QQuickTextNode::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 QQuickTextNode::setStyleColor(const QColor &styleColor)
108 if (m_textStyle != QQuickTextNode::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 *QQuickTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
130 QQuickText::TextStyle style, const QColor &styleColor,
133 QSGGlyphNode *node = m_context->createGlyphNode();
134 node->setOwnerElement(m_ownerElement);
135 node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
136 node->setStyle(style);
137 node->setStyleColor(styleColor);
138 node->setColor(color);
141 /* We flag the geometry as static, but we never call markVertexDataDirty
142 or markIndexDataDirty on them. This is because all text nodes are
143 discarded when a change occurs. If we start appending/removing from
144 existing geometry, then we also need to start marking the geometry as
147 node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
148 node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
152 parentNode->appendChildNode(node);
157 void QQuickTextNode::setCursor(const QRectF &rect, const QColor &color)
159 if (m_cursorNode != 0)
162 m_cursorNode = new QSGSimpleRectNode(rect, color);
163 appendChildNode(m_cursorNode);
168 struct BinaryTreeNode {
169 enum SelectionState {
175 : selectionState(Unselected)
177 , decorations(QQuickTextNode::NoDecoration)
180 , rightChildIndex(-1)
185 BinaryTreeNode(const QRectF &brect, const QImage &i, SelectionState selState, qreal a)
186 : boundingRect(brect)
187 , selectionState(selState)
189 , decorations(QQuickTextNode::NoDecoration)
193 , rightChildIndex(-1)
197 BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
198 const QQuickTextNode::Decorations &decs, const QColor &c, const QColor &bc,
199 const QPointF &pos, qreal a)
201 , boundingRect(brect)
202 , selectionState(selState)
206 , backgroundColor(bc)
210 , rightChildIndex(-1)
216 SelectionState selectionState;
217 QSGClipNode *clipNode;
218 QQuickTextNode::Decorations decorations;
220 QColor backgroundColor;
228 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
232 SelectionState selectionState)
234 insert(binaryTree, BinaryTreeNode(rect, image, selectionState, ascent));
237 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
238 const QGlyphRun &glyphRun,
239 SelectionState selectionState,
240 const QColor &textColor,
241 const QColor &backgroundColor,
242 const QPointF &position)
244 QRectF searchRect = glyphRun.boundingRect();
245 searchRect.translate(position);
247 if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
250 QQuickTextNode::Decorations decorations = QQuickTextNode::NoDecoration;
251 decorations |= (glyphRun.underline() ? QQuickTextNode::Underline : QQuickTextNode::NoDecoration);
252 decorations |= (glyphRun.overline() ? QQuickTextNode::Overline : QQuickTextNode::NoDecoration);
253 decorations |= (glyphRun.strikeOut() ? QQuickTextNode::StrikeOut : QQuickTextNode::NoDecoration);
254 decorations |= (backgroundColor.isValid() ? QQuickTextNode::Background : QQuickTextNode::NoDecoration);
256 qreal ascent = glyphRun.rawFont().ascent();
257 // ### QTBUG-22919 The bounding rect returned by QGlyphRun appears to start on the
258 // baseline, move it by the ascent so all bounding rects are at baseline - ascent.
259 searchRect.translate(0, -ascent);
260 insert(binaryTree, BinaryTreeNode(glyphRun, selectionState, searchRect, decorations,
261 textColor, backgroundColor, position, ascent));
264 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
265 const BinaryTreeNode &binaryTreeNode)
267 int newIndex = binaryTree->size();
268 binaryTree->append(binaryTreeNode);
274 BinaryTreeNode *node = binaryTree->data() + searchIndex;
275 if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) {
276 if (node->leftChildIndex < 0) {
277 node->leftChildIndex = newIndex;
280 searchIndex = node->leftChildIndex;
283 if (node->rightChildIndex < 0) {
284 node->rightChildIndex = newIndex;
287 searchIndex = node->rightChildIndex;
293 static void inOrder(const QVarLengthArray<BinaryTreeNode> &binaryTree,
294 QVarLengthArray<int> *sortedIndexes,
295 int currentIndex = 0)
297 Q_ASSERT(currentIndex < binaryTree.size());
299 const BinaryTreeNode *node = binaryTree.data() + currentIndex;
300 if (node->leftChildIndex >= 0)
301 inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
303 sortedIndexes->append(currentIndex);
305 if (node->rightChildIndex >= 0)
306 inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
310 // Engine that takes glyph runs as input, and produces a set of glyph nodes, clip nodes,
311 // and rectangle nodes to represent the text, decorations and selection. Will try to minimize
312 // number of nodes, and join decorations in neighbouring items
313 class SelectionEngine
316 SelectionEngine() : m_hasSelection(false) {}
318 QTextLine currentLine() const { return m_currentLine; }
320 void setCurrentLine(const QTextLine ¤tLine)
322 if (m_currentLine.isValid())
323 processCurrentLine();
325 m_currentLine = currentLine;
328 void addBorder(const QRectF &rect, qreal border, QTextFrameFormat::BorderStyle borderStyle,
329 const QBrush &borderBrush);
330 void addFrameDecorations(QTextDocument *document, QTextFrame *frame);
331 void addImage(const QRectF &rect, const QImage &image, qreal ascent,
332 BinaryTreeNode::SelectionState selectionState,
333 QTextFrameFormat::Position layoutPosition);
334 int addText(const QTextBlock &block,
335 const QTextCharFormat &charFormat,
336 const QColor &textColor,
337 const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
338 int textPos, int fragmentEnd,
339 int selectionStart, int selectionEnd);
340 void addTextObject(const QPointF &position, const QTextCharFormat &format,
341 BinaryTreeNode::SelectionState selectionState,
342 QTextDocument *textDocument, int pos,
343 QTextFrameFormat::Position layoutPosition = QTextFrameFormat::InFlow);
344 void addSelectedGlyphs(const QGlyphRun &glyphRun);
345 void addUnselectedGlyphs(const QGlyphRun &glyphRun);
346 void addGlyphsInRange(int rangeStart, int rangeEnd,
347 const QColor &color, const QColor &backgroundColor,
348 int selectionStart, int selectionEnd);
349 void addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
351 int selectionStart, int selectionEnd);
353 void addToSceneGraph(QQuickTextNode *parent,
354 QQuickText::TextStyle style = QQuickText::Normal,
355 const QColor &styleColor = QColor());
357 void setSelectionColor(const QColor &selectionColor)
359 m_selectionColor = selectionColor;
362 void setSelectedTextColor(const QColor &selectedTextColor)
364 m_selectedTextColor = selectedTextColor;
367 void setTextColor(const QColor &textColor)
369 m_textColor = textColor;
372 void setPosition(const QPointF &position)
374 m_position = position;
378 struct TextDecoration
380 TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
381 TextDecoration(const BinaryTreeNode::SelectionState &s,
390 BinaryTreeNode::SelectionState selectionState;
395 void processCurrentLine();
396 void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
397 qreal offset, qreal thickness);
399 QColor m_selectionColor;
401 QColor m_backgroundColor;
402 QColor m_selectedTextColor;
405 QTextLine m_currentLine;
408 QList<QPair<QRectF, QColor> > m_backgrounds;
409 QList<QRectF> m_selectionRects;
410 QVarLengthArray<BinaryTreeNode> m_currentLineTree;
412 QList<TextDecoration> m_lines;
413 QVector<BinaryTreeNode> m_processedNodes;
415 QList<QPair<QRectF, QImage> > m_images;
418 int SelectionEngine::addText(const QTextBlock &block,
419 const QTextCharFormat &charFormat,
420 const QColor &textColor,
421 const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
422 int textPos, int fragmentEnd,
423 int selectionStart, int selectionEnd)
425 if (charFormat.foreground().style() != Qt::NoBrush)
426 setTextColor(charFormat.foreground().color());
428 setTextColor(textColor);
430 while (textPos < fragmentEnd) {
431 int blockRelativePosition = textPos - block.position();
432 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
433 if (!currentLine().isValid()
434 || line.lineNumber() != currentLine().lineNumber()) {
435 setCurrentLine(line);
438 Q_ASSERT(line.textLength() > 0);
439 int lineEnd = line.textStart() + block.position() + line.textLength();
441 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
444 int currentStepEnd = textPos + len;
446 addGlyphsForRanges(colorChanges,
447 textPos - block.position(),
448 currentStepEnd - block.position(),
449 selectionStart - block.position(),
450 selectionEnd - block.position());
452 textPos = currentStepEnd;
457 void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
458 qreal offset, qreal thickness)
460 for (int i=0; i<textDecorations.size(); ++i) {
461 TextDecoration textDecoration = textDecorations.at(i);
464 QRectF &rect = textDecoration.rect;
465 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
466 rect.setHeight(thickness);
469 m_lines.append(textDecoration);
473 void SelectionEngine::processCurrentLine()
475 // No glyphs, do nothing
476 if (m_currentLineTree.isEmpty())
479 // 1. Go through current line and get correct decoration position for each node based on
480 // neighbouring decorations. Add decoration to global list
481 // 2. Create clip nodes for all selected text. Try to merge as many as possible within
483 // 3. Add QRects to a list of selection rects.
484 // 4. Add all nodes to a global processed list
485 QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
486 BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
488 Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
490 BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
493 QQuickTextNode::Decorations currentDecorations = QQuickTextNode::NoDecoration;
494 qreal underlineOffset = 0.0;
495 qreal underlineThickness = 0.0;
497 qreal overlineOffset = 0.0;
498 qreal overlineThickness = 0.0;
500 qreal strikeOutOffset = 0.0;
501 qreal strikeOutThickness = 0.0;
503 QRectF decorationRect = currentRect;
506 QColor lastBackgroundColor;
508 QVarLengthArray<TextDecoration> pendingUnderlines;
509 QVarLengthArray<TextDecoration> pendingOverlines;
510 QVarLengthArray<TextDecoration> pendingStrikeOuts;
511 if (!sortedIndexes.isEmpty()) {
512 QSGClipNode *currentClipNode = m_hasSelection ? new QSGClipNode : 0;
513 bool currentClipNodeUsed = false;
514 for (int i=0; i<=sortedIndexes.size(); ++i) {
515 BinaryTreeNode *node = 0;
516 if (i < sortedIndexes.size()) {
517 int sortedIndex = sortedIndexes.at(i);
518 Q_ASSERT(sortedIndex < m_currentLineTree.size());
520 node = m_currentLineTree.data() + sortedIndex;
524 currentSelectionState = node->selectionState;
526 // Update decorations
527 if (currentDecorations != QQuickTextNode::NoDecoration) {
528 decorationRect.setY(m_position.y() + m_currentLine.y());
529 decorationRect.setHeight(m_currentLine.height());
532 decorationRect.setRight(node->boundingRect.left());
534 TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
535 if (currentDecorations & QQuickTextNode::Underline)
536 pendingUnderlines.append(textDecoration);
538 if (currentDecorations & QQuickTextNode::Overline)
539 pendingOverlines.append(textDecoration);
541 if (currentDecorations & QQuickTextNode::StrikeOut)
542 pendingStrikeOuts.append(textDecoration);
544 if (currentDecorations & QQuickTextNode::Background)
545 m_backgrounds.append(qMakePair(decorationRect, lastBackgroundColor));
548 // If we've reached an unselected node from a selected node, we add the
549 // selection rect to the graph, and we add decoration every time the
550 // selection state changes, because that means the text color changes
551 if (node == 0 || node->selectionState != currentSelectionState) {
553 currentRect.setRight(node->boundingRect.left());
554 currentRect.setY(m_position.y() + m_currentLine.y());
555 currentRect.setHeight(m_currentLine.height());
557 // Draw selection all the way up to the left edge of the unselected item
558 if (currentSelectionState == BinaryTreeNode::Selected)
559 m_selectionRects.append(currentRect);
561 if (currentClipNode != 0) {
562 if (!currentClipNodeUsed) {
563 delete currentClipNode;
565 currentClipNode->setIsRectangular(true);
566 currentClipNode->setClipRect(currentRect);
570 if (node != 0 && m_hasSelection)
571 currentClipNode = new QSGClipNode;
574 currentClipNodeUsed = false;
577 currentSelectionState = node->selectionState;
578 currentRect = node->boundingRect;
580 // Make sure currentRect is valid, otherwise the unite won't work
581 if (currentRect.isNull())
582 currentRect.setSize(QSizeF(1, 1));
585 if (currentRect.isNull())
586 currentRect = node->boundingRect;
588 currentRect = currentRect.united(node->boundingRect);
592 node->clipNode = currentClipNode;
593 currentClipNodeUsed = true;
595 decorationRect = node->boundingRect;
597 // If previous item(s) had underline and current does not, then we add the
598 // pending lines to the lists and likewise for overlines and strikeouts
599 if (!pendingUnderlines.isEmpty()
600 && !(node->decorations & QQuickTextNode::Underline)) {
601 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
603 pendingUnderlines.clear();
605 underlineOffset = 0.0;
606 underlineThickness = 0.0;
609 // ### Add pending when overlineOffset/thickness changes to minimize number of
611 if (!pendingOverlines.isEmpty()) {
612 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
614 pendingOverlines.clear();
616 overlineOffset = 0.0;
617 overlineThickness = 0.0;
620 // ### Add pending when overlineOffset/thickness changes to minimize number of
622 if (!pendingStrikeOuts.isEmpty()) {
623 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
625 pendingStrikeOuts.clear();
627 strikeOutOffset = 0.0;
628 strikeOutThickness = 0.0;
631 // Merge current values with previous. Prefer greatest thickness
632 QRawFont rawFont = node->glyphRun.rawFont();
633 if (node->decorations & QQuickTextNode::Underline) {
634 if (rawFont.lineThickness() > underlineThickness) {
635 underlineThickness = rawFont.lineThickness();
636 underlineOffset = rawFont.underlinePosition();
640 if (node->decorations & QQuickTextNode::Overline) {
641 overlineOffset = -rawFont.ascent();
642 overlineThickness = rawFont.lineThickness();
645 if (node->decorations & QQuickTextNode::StrikeOut) {
646 strikeOutThickness = rawFont.lineThickness();
647 strikeOutOffset = rawFont.ascent() / -3.0;
650 currentDecorations = node->decorations;
651 lastColor = node->color;
652 lastBackgroundColor = node->backgroundColor;
653 m_processedNodes.append(*node);
657 if (!pendingUnderlines.isEmpty())
658 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
660 if (!pendingOverlines.isEmpty())
661 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
663 if (!pendingStrikeOuts.isEmpty())
664 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
667 m_currentLineTree.clear();
668 m_currentLine = QTextLine();
669 m_hasSelection = false;
672 void SelectionEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent,
673 BinaryTreeNode::SelectionState selectionState,
674 QTextFrameFormat::Position layoutPosition)
676 QRectF searchRect = rect;
677 if (layoutPosition == QTextFrameFormat::InFlow) {
678 if (m_currentLineTree.isEmpty()) {
679 searchRect.moveTopLeft(m_position + m_currentLine.position());
681 const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1;
682 if (lastNode->glyphRun.isRightToLeft()) {
683 QPointF lastPos = lastNode->boundingRect.topLeft();
684 searchRect.moveTopRight(lastPos - QPointF(0, ascent - lastNode->ascent));
686 QPointF lastPos = lastNode->boundingRect.topRight();
687 searchRect.moveTopLeft(lastPos - QPointF(0, ascent - lastNode->ascent));
692 BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
695 void SelectionEngine::addTextObject(const QPointF &position, const QTextCharFormat &format,
696 BinaryTreeNode::SelectionState selectionState,
697 QTextDocument *textDocument, int pos,
698 QTextFrameFormat::Position layoutPosition)
700 QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType());
703 QSizeF size = handler->intrinsicSize(textDocument, pos, format);
705 if (format.objectType() == QTextFormat::ImageObject) {
706 QTextImageFormat imageFormat = format.toImageFormat();
707 if (QQuickTextDocumentWithImageResources *imageDoc = qobject_cast<QQuickTextDocumentWithImageResources *>(textDocument)) {
708 image = imageDoc->image(imageFormat);
713 QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
714 image = imageHandler->image(textDocument, imageFormat);
718 if (image.isNull()) {
719 image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied);
720 image.fill(Qt::transparent);
722 QPainter painter(&image);
723 handler->drawObject(&painter, image.rect(), textDocument, pos, format);
728 QFontMetrics m(format.font());
729 switch (format.verticalAlignment())
731 case QTextCharFormat::AlignMiddle:
732 ascent = size.height() / 2 - 1;
734 case QTextCharFormat::AlignBaseline:
735 ascent = size.height() - m.descent() - 1;
738 ascent = size.height() - 1;
741 addImage(QRectF(position, size), image, ascent, selectionState, layoutPosition);
745 void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
747 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
748 m_textColor, m_backgroundColor, m_position);
751 void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
753 int currentSize = m_currentLineTree.size();
754 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
755 m_textColor, m_backgroundColor, m_position);
756 m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
759 void SelectionEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
761 int selectionStart, int selectionEnd)
763 int currentPosition = start;
764 int remainingLength = end - start;
765 for (int j=0; j<ranges.size(); ++j) {
766 const QTextLayout::FormatRange &range = ranges.at(j);
767 if (range.start + range.length >= currentPosition
768 && range.start < currentPosition + remainingLength) {
770 if (range.start > currentPosition) {
771 addGlyphsInRange(currentPosition, range.start - currentPosition,
772 QColor(), QColor(), selectionStart, selectionEnd);
775 int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
776 QColor rangeColor = range.format.hasProperty(QTextFormat::ForegroundBrush)
777 ? range.format.foreground().color()
779 QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush)
780 ? range.format.background().color()
783 addGlyphsInRange(range.start, rangeEnd - range.start,
784 rangeColor, rangeBackgroundColor,
785 selectionStart, selectionEnd);
787 currentPosition = range.start + range.length;
788 remainingLength = end - currentPosition;
790 } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
795 if (remainingLength > 0) {
796 addGlyphsInRange(currentPosition, remainingLength, QColor(), QColor(),
797 selectionStart, selectionEnd);
802 void SelectionEngine::addGlyphsInRange(int rangeStart, int rangeLength,
803 const QColor &color, const QColor &backgroundColor,
804 int selectionStart, int selectionEnd)
807 if (color.isValid()) {
808 oldColor = m_textColor;
812 QColor oldBackgroundColor = m_backgroundColor;
813 if (backgroundColor.isValid()) {
814 oldBackgroundColor = m_backgroundColor;
815 m_backgroundColor = backgroundColor;
818 bool hasSelection = selectionEnd >= 0
819 && selectionStart <= selectionEnd;
821 QTextLine &line = m_currentLine;
822 int rangeEnd = rangeStart + rangeLength;
823 if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) {
824 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
825 for (int j=0; j<glyphRuns.size(); ++j) {
826 const QGlyphRun &glyphRun = glyphRuns.at(j);
827 addUnselectedGlyphs(glyphRun);
830 if (rangeStart < selectionStart) {
831 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart,
832 qMin(selectionStart - rangeStart,
835 for (int j=0; j<glyphRuns.size(); ++j) {
836 const QGlyphRun &glyphRun = glyphRuns.at(j);
837 addUnselectedGlyphs(glyphRun);
841 if (rangeEnd > selectionStart) {
842 int start = qMax(selectionStart, rangeStart);
843 int length = qMin(selectionEnd - start + 1, rangeEnd - start);
844 QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
846 for (int j=0; j<glyphRuns.size(); ++j) {
847 const QGlyphRun &glyphRun = glyphRuns.at(j);
848 addSelectedGlyphs(glyphRun);
852 if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
853 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd - 1);
854 for (int j=0; j<glyphRuns.size(); ++j) {
855 const QGlyphRun &glyphRun = glyphRuns.at(j);
856 addUnselectedGlyphs(glyphRun);
861 if (backgroundColor.isValid())
862 m_backgroundColor = oldBackgroundColor;
864 if (oldColor.isValid())
865 m_textColor = oldColor;
868 void SelectionEngine::addBorder(const QRectF &rect, qreal border,
869 QTextFrameFormat::BorderStyle borderStyle,
870 const QBrush &borderBrush)
872 QColor color = borderBrush.color();
874 // Currently we don't support other styles than solid
875 Q_UNUSED(borderStyle);
877 m_backgrounds.append(qMakePair(QRectF(rect.left(), rect.top(), border, rect.height() + border), color));
878 m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.top(), rect.width(), border), color));
879 m_backgrounds.append(qMakePair(QRectF(rect.right(), rect.top() + border, border, rect.height() - border), color));
880 m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.bottom(), rect.width(), border), color));
883 void SelectionEngine::addFrameDecorations(QTextDocument *document, QTextFrame *frame)
885 QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(document->documentLayout());
886 QTextFrameFormat frameFormat = frame->format().toFrameFormat();
888 QTextTable *table = qobject_cast<QTextTable *>(frame);
889 QRectF boundingRect = table == 0
890 ? documentLayout->frameBoundingRect(frame)
891 : documentLayout->tableBoundingRect(table);
893 QBrush bg = frame->frameFormat().background();
894 if (bg.style() != Qt::NoBrush)
895 m_backgrounds.append(qMakePair(boundingRect, bg.color()));
897 if (!frameFormat.hasProperty(QTextFormat::FrameBorder))
900 qreal borderWidth = frameFormat.border();
901 if (qFuzzyIsNull(borderWidth))
904 QBrush borderBrush = frameFormat.borderBrush();
905 QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle();
906 if (borderStyle == QTextFrameFormat::BorderStyle_None)
909 addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
910 -frameFormat.rightMargin(), -frameFormat.bottomMargin()),
911 borderWidth, borderStyle, borderBrush);
913 int rows = table->rows();
914 int columns = table->columns();
916 for (int row=0; row<rows; ++row) {
917 for (int column=0; column<columns; ++column) {
918 QTextTableCell cell = table->cellAt(row, column);
920 QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
921 addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth,
922 borderStyle, borderBrush);
928 void SelectionEngine::addToSceneGraph(QQuickTextNode *parentNode,
929 QQuickText::TextStyle style,
930 const QColor &styleColor)
932 if (m_currentLine.isValid())
933 processCurrentLine();
936 for (int i=0; i<m_backgrounds.size(); ++i) {
937 const QRectF &rect = m_backgrounds.at(i).first;
938 const QColor &color = m_backgrounds.at(i).second;
940 parentNode->appendChildNode(new QSGSimpleRectNode(rect, color));
943 // First, prepend all selection rectangles to the tree
944 for (int i=0; i<m_selectionRects.size(); ++i) {
945 const QRectF &rect = m_selectionRects.at(i);
947 parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
950 // Finally, add decorations for each node to the tree.
951 for (int i=0; i<m_lines.size(); ++i) {
952 const TextDecoration &textDecoration = m_lines.at(i);
954 QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
955 ? m_selectedTextColor
956 : textDecoration.color;
958 parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
961 // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common
962 // font, selection state and clip node.
963 typedef QPair<QFontEngine *, QPair<QSGClipNode *, QPair<QRgb, int> > > KeyType;
964 QHash<KeyType, BinaryTreeNode *> map;
965 for (int i=0; i<m_processedNodes.size(); ++i) {
966 BinaryTreeNode *node = m_processedNodes.data() + i;
968 if (node->image.isNull()) {
969 QGlyphRun glyphRun = node->glyphRun;
970 QRawFont rawFont = glyphRun.rawFont();
971 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
973 QFontEngine *fontEngine = rawFontD->fontEngine;
975 KeyType key(qMakePair(fontEngine,
976 qMakePair(node->clipNode,
977 qMakePair(node->color.rgba(), int(node->selectionState)))));
979 BinaryTreeNode *otherNode = map.value(key, 0);
980 if (otherNode != 0) {
981 QGlyphRun &otherGlyphRun = otherNode->glyphRun;
983 QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
984 QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
986 otherGlyphIndexes += glyphRun.glyphIndexes();
988 QVector<QPointF> glyphPositions = glyphRun.positions();
989 for (int j=0; j<glyphPositions.size(); ++j) {
990 otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
993 otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
994 otherGlyphRun.setPositions(otherGlyphPositions);
997 map.insert(key, node);
1000 parentNode->addImage(node->boundingRect, node->image);
1001 if (node->selectionState == BinaryTreeNode::Selected) {
1002 QColor color = m_selectionColor;
1003 color.setAlpha(128);
1004 parentNode->appendChildNode(new QSGSimpleRectNode(node->boundingRect, color));
1009 // ...and add clip nodes and glyphs to tree.
1010 QHash<KeyType, BinaryTreeNode *>::const_iterator it = map.constBegin();
1011 while (it != map.constEnd()) {
1013 BinaryTreeNode *node = it.value();
1015 QSGClipNode *clipNode = node->clipNode;
1016 if (clipNode != 0 && clipNode->parent() == 0 )
1017 parentNode->appendChildNode(clipNode);
1019 QColor color = node->selectionState == BinaryTreeNode::Selected
1020 ? m_selectedTextColor
1023 parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
1030 void QQuickTextNode::mergeFormats(QTextLayout *textLayout,
1031 QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
1033 Q_ASSERT(mergedFormats != 0);
1034 if (textLayout == 0)
1037 QList<QTextLayout::FormatRange> additionalFormats = textLayout->additionalFormats();
1038 for (int i=0; i<additionalFormats.size(); ++i) {
1039 QTextLayout::FormatRange additionalFormat = additionalFormats.at(i);
1040 if (additionalFormat.format.hasProperty(QTextFormat::ForegroundBrush)
1041 || additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)) {
1042 // Merge overlapping formats
1043 if (!mergedFormats->isEmpty()) {
1044 QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1;
1046 if (additionalFormat.start < lastFormat->start + lastFormat->length) {
1047 QTextLayout::FormatRange *mergedRange = 0;
1049 int length = additionalFormat.length;
1050 if (additionalFormat.start > lastFormat->start) {
1051 lastFormat->length = additionalFormat.start - lastFormat->start;
1052 length -= lastFormat->length;
1054 mergedFormats->append(QTextLayout::FormatRange());
1055 mergedRange = mergedFormats->data() + mergedFormats->size() - 1;
1056 lastFormat = mergedFormats->data() + mergedFormats->size() - 2;
1058 mergedRange = lastFormat;
1061 mergedRange->format = lastFormat->format;
1062 mergedRange->format.merge(additionalFormat.format);
1063 mergedRange->start = additionalFormat.start;
1065 int end = qMin(additionalFormat.start + additionalFormat.length,
1066 lastFormat->start + lastFormat->length);
1068 mergedRange->length = end - mergedRange->start;
1069 length -= mergedRange->length;
1071 additionalFormat.start = end;
1072 additionalFormat.length = length;
1076 if (additionalFormat.length > 0)
1077 mergedFormats->append(additionalFormat);
1085 class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
1088 inline QTextCharFormat formatAccessor(int pos)
1096 void QQuickTextNode::addImage(const QRectF &rect, const QImage &image)
1098 QSGImageNode *node = m_context->createImageNode();
1099 QSGTexture *texture = m_context->createTexture(image);
1100 m_textures.append(texture);
1101 node->setTargetRect(rect);
1102 node->setTexture(texture);
1103 appendChildNode(node);
1107 void QQuickTextNode::addTextDocument(const QPointF &, QTextDocument *textDocument,
1108 const QColor &textColor,
1109 QQuickText::TextStyle style, const QColor &styleColor,
1110 const QColor &selectionColor, const QColor &selectedTextColor,
1111 int selectionStart, int selectionEnd)
1113 SelectionEngine engine;
1114 engine.setTextColor(textColor);
1115 engine.setSelectedTextColor(selectedTextColor);
1116 engine.setSelectionColor(selectionColor);
1118 QList<QTextFrame *> frames;
1119 frames.append(textDocument->rootFrame());
1120 while (!frames.isEmpty()) {
1121 QTextFrame *textFrame = frames.takeFirst();
1122 frames.append(textFrame->childFrames());
1124 engine.addFrameDecorations(textDocument, textFrame);
1126 if (textFrame->firstPosition() > textFrame->lastPosition()
1127 && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
1128 const int pos = textFrame->firstPosition() - 1;
1129 ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(textDocument->documentLayout());
1130 QTextCharFormat format = a->formatAccessor(pos);
1131 QRectF rect = a->frameBoundingRect(textFrame);
1133 QTextBlock block = textFrame->firstCursorPosition().block();
1134 engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
1135 engine.addTextObject(rect.topLeft(), format, BinaryTreeNode::Unselected, textDocument,
1136 pos, textFrame->frameFormat().position());
1138 QTextFrame::iterator it = textFrame->begin();
1140 while (!it.atEnd()) {
1141 Q_ASSERT(!engine.currentLine().isValid());
1143 QTextBlock block = it.currentBlock();
1144 int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0;
1145 int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
1147 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1148 mergeFormats(block.layout(), &colorChanges);
1150 QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft();
1151 if (QTextList *textList = block.textList()) {
1152 QPointF pos = blockPosition;
1153 QTextLayout *layout = block.layout();
1154 if (layout->lineCount() > 0) {
1155 QTextLine firstLine = layout->lineAt(0);
1156 Q_ASSERT(firstLine.isValid());
1158 engine.setCurrentLine(firstLine);
1160 QRectF textRect = firstLine.naturalTextRect();
1161 pos += textRect.topLeft();
1162 if (block.textDirection() == Qt::RightToLeft)
1163 pos.rx() += textRect.width();
1165 const QTextCharFormat charFormat = block.charFormat();
1166 QFont font(charFormat.font());
1167 QFontMetricsF fontMetrics(font);
1168 QTextListFormat listFormat = textList->format();
1170 QString listItemBullet;
1171 switch (listFormat.style()) {
1172 case QTextListFormat::ListCircle:
1173 listItemBullet = QChar(0x25E6); // White bullet
1175 case QTextListFormat::ListSquare:
1176 listItemBullet = QChar(0x25AA); // Black small square
1178 case QTextListFormat::ListDecimal:
1179 case QTextListFormat::ListLowerAlpha:
1180 case QTextListFormat::ListUpperAlpha:
1181 case QTextListFormat::ListLowerRoman:
1182 case QTextListFormat::ListUpperRoman:
1183 listItemBullet = textList->itemText(block);
1186 listItemBullet = QChar(0x2022); // Black bullet
1190 QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height());
1191 qreal xoff = fontMetrics.width(QLatin1Char(' '));
1192 if (block.textDirection() == Qt::LeftToRight)
1193 xoff = -xoff - size.width();
1194 engine.setPosition(pos + QPointF(xoff, 0));
1197 layout.setFont(font);
1198 layout.setText(listItemBullet); // Bullet
1199 layout.beginLayout();
1200 QTextLine line = layout.createLine();
1201 line.setPosition(QPointF(0, 0));
1204 QList<QGlyphRun> glyphRuns = layout.glyphRuns();
1205 for (int i=0; i<glyphRuns.size(); ++i)
1206 engine.addUnselectedGlyphs(glyphRuns.at(i));
1210 int textPos = block.position();
1211 QTextBlock::iterator blockIterator = block.begin();
1212 if (blockIterator.atEnd() && preeditLength) {
1213 engine.setPosition(blockPosition);
1214 textPos = engine.addText(block, block.charFormat(), textColor, colorChanges,
1215 textPos, textPos + preeditLength,
1216 selectionStart, selectionEnd);
1219 while (!blockIterator.atEnd()) {
1220 QTextFragment fragment = blockIterator.fragment();
1221 QString text = fragment.text();
1225 QTextCharFormat charFormat = fragment.charFormat();
1226 engine.setPosition(blockPosition);
1227 if (text.contains(QChar::ObjectReplacementCharacter)) {
1228 QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
1229 if (frame && frame->frameFormat().position() == QTextFrameFormat::InFlow) {
1230 int blockRelativePosition = textPos - block.position();
1231 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
1232 if (!engine.currentLine().isValid()
1233 || line.lineNumber() != engine.currentLine().lineNumber()) {
1234 engine.setCurrentLine(line);
1237 BinaryTreeNode::SelectionState selectionState =
1238 (selectionStart < textPos + text.length()
1239 && selectionEnd >= textPos)
1240 ? BinaryTreeNode::Selected
1241 : BinaryTreeNode::Unselected;
1243 engine.addTextObject(QPointF(), charFormat, selectionState, textDocument, textPos);
1245 textPos += text.length();
1247 int fragmentEnd = textPos + fragment.length();
1248 if (preeditPosition >= 0
1249 && preeditPosition >= textPos
1250 && preeditPosition <= fragmentEnd) {
1251 fragmentEnd += preeditLength;
1254 engine.addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd,
1255 selectionStart, selectionEnd);
1261 engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
1267 engine.addToSceneGraph(this, style, styleColor);
1270 void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
1271 QQuickText::TextStyle style, const QColor &styleColor,
1272 const QColor &selectionColor, const QColor &selectedTextColor,
1273 int selectionStart, int selectionEnd)
1275 SelectionEngine engine;
1276 engine.setTextColor(color);
1277 engine.setSelectedTextColor(selectedTextColor);
1278 engine.setSelectionColor(selectionColor);
1279 engine.setPosition(position);
1281 int preeditLength = textLayout->preeditAreaText().length();
1282 int preeditPosition = textLayout->preeditAreaPosition();
1284 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1285 mergeFormats(textLayout, &colorChanges);
1287 for (int i=0; i<textLayout->lineCount(); ++i) {
1288 QTextLine line = textLayout->lineAt(i);
1290 int start = line.textStart();
1291 int length = line.textLength();
1292 int end = start + length;
1294 if (preeditPosition >= 0
1295 && preeditPosition >= start
1296 && preeditPosition < end) {
1297 end += preeditLength;
1300 engine.setCurrentLine(line);
1301 engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
1304 engine.addToSceneGraph(this, style, styleColor);
1307 void QQuickTextNode::deleteContent()
1309 while (firstChild() != 0)
1310 delete firstChild();
1315 void QQuickTextNode::updateNodes()
1319 if (m_text.isEmpty())
1322 if (m_usePixmapCache) {
1323 // ### gunnar: port properly
1324 // QPixmap pixmap = generatedPixmap();
1325 // if (pixmap.isNull())
1328 // QSGImageNode *pixmapNode = m_context->createImageNode();
1329 // pixmapNode->setRect(pixmap.rect());
1330 // pixmapNode->setSourceRect(pixmap.rect());
1331 // pixmapNode->setOpacity(m_opacity);
1332 // pixmapNode->setClampToEdge(true);
1333 // pixmapNode->setLinearFiltering(m_linearFiltering);
1335 // appendChildNode(pixmapNode);
1337 if (m_text.isEmpty())
1340 // Implement styling by drawing text several times at slight shifts. shiftForStyle
1341 // contains the sequence of shifted positions at which to draw the text. All except
1342 // the last will be drawn with styleColor.
1343 QList<QPointF> shiftForStyle;
1344 switch (m_textStyle) {
1345 case OutlineTextStyle:
1346 // ### Should be made faster by implementing outline material
1347 shiftForStyle << QPointF(-1, 0);
1348 shiftForStyle << QPointF(0, -1);
1349 shiftForStyle << QPointF(1, 0);
1350 shiftForStyle << QPointF(0, 1);
1352 case SunkenTextStyle:
1353 shiftForStyle << QPointF(0, -1);
1355 case RaisedTextStyle:
1356 shiftForStyle << QPointF(0, 1);
1362 shiftForStyle << QPointF(0, 0); // Regular position
1363 while (!shiftForStyle.isEmpty()) {
1364 QPointF shift = shiftForStyle.takeFirst();
1366 // Use styleColor for all but last shift
1368 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
1370 QTextFrame *textFrame = m_textDocument->rootFrame();
1371 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
1373 QTextFrame::iterator it = textFrame->begin();
1374 while (!it.atEnd()) {
1375 addTextBlock(shift + p, it.currentBlock(), overrideColor);
1379 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()