1 /****************************************************************************
3 ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
6 ** This file is part of the QtQml module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
40 ****************************************************************************/
42 #include "qquicktextnode_p.h"
44 #include <QtQuick/qsgsimplerectnode.h>
45 #include <private/qsgadaptationlayer_p.h>
46 #include <private/qsgdistancefieldglyphnode_p.h>
47 #include <private/qquickclipnode_p.h>
48 #include <QtQuick/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/qquickstyledtext_p.h>
60 #include <private/qquicktext_p_p.h>
61 #include <private/qfont_p.h>
62 #include <private/qfontengine_p.h>
63 #include <private/qrawfont_p.h>
64 #include <private/qtextimagehandler_p.h>
65 #include <private/qtextdocumentlayout_p.h>
71 Creates an empty QQuickTextNode
73 QQuickTextNode::QQuickTextNode(QSGContext *context, QQuickItem *ownerElement)
74 : m_context(context), m_cursorNode(0), m_ownerElement(ownerElement), m_useNativeRenderer(false)
76 #if defined(QML_RUNTIME_TESTING)
77 description = QLatin1String("text");
81 QQuickTextNode::~QQuickTextNode()
83 qDeleteAll(m_textures);
87 void QQuickTextNode::setColor(const QColor &color)
89 if (m_usePixmapCache) {
90 setUpdateFlag(UpdateNodes);
92 for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
93 if (childNode->subType() == GlyphNodeSubType) {
94 QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
95 if (glyphNode->color() == m_color)
96 glyphNode->setColor(color);
97 } else if (childNode->subType() == SolidRectNodeSubType) {
98 QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
99 if (solidRectNode->color() == m_color)
100 solidRectNode->setColor(color);
107 void QQuickTextNode::setStyleColor(const QColor &styleColor)
109 if (m_textStyle != QQuickTextNode::NormalTextStyle) {
110 if (m_usePixmapCache) {
111 setUpdateFlag(UpdateNodes);
113 for (QSGNode *childNode = firstChild(); childNode; childNode = childNode->nextSibling()) {
114 if (childNode->subType() == GlyphNodeSubType) {
115 QSGGlyphNode *glyphNode = static_cast<QSGGlyphNode *>(childNode);
116 if (glyphNode->color() == m_styleColor)
117 glyphNode->setColor(styleColor);
118 } else if (childNode->subType() == SolidRectNodeSubType) {
119 QSGSimpleRectNode *solidRectNode = static_cast<QSGSimpleRectNode *>(childNode);
120 if (solidRectNode->color() == m_styleColor)
121 solidRectNode->setColor(styleColor);
126 m_styleColor = styleColor;
130 QSGGlyphNode *QQuickTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
131 QQuickText::TextStyle style, const QColor &styleColor,
134 QSGGlyphNode *node = m_useNativeRenderer
135 ? m_context->createNativeGlyphNode()
136 : m_context->createGlyphNode();
137 node->setOwnerElement(m_ownerElement);
138 node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
139 node->setStyle(style);
140 node->setStyleColor(styleColor);
141 node->setColor(color);
144 /* We flag the geometry as static, but we never call markVertexDataDirty
145 or markIndexDataDirty on them. This is because all text nodes are
146 discarded when a change occurs. If we start appending/removing from
147 existing geometry, then we also need to start marking the geometry as
150 node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
151 node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
155 parentNode->appendChildNode(node);
160 void QQuickTextNode::setCursor(const QRectF &rect, const QColor &color)
162 if (m_cursorNode != 0)
165 m_cursorNode = new QSGSimpleRectNode(rect, color);
166 appendChildNode(m_cursorNode);
171 struct BinaryTreeNode {
172 enum SelectionState {
178 : selectionState(Unselected)
180 , decorations(QQuickTextNode::NoDecoration)
183 , rightChildIndex(-1)
188 BinaryTreeNode(const QRectF &brect, const QImage &i, SelectionState selState, qreal a)
189 : boundingRect(brect)
190 , selectionState(selState)
192 , decorations(QQuickTextNode::NoDecoration)
196 , rightChildIndex(-1)
200 BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect,
201 const QQuickTextNode::Decorations &decs, const QColor &c, const QColor &bc,
202 const QPointF &pos, qreal a)
204 , boundingRect(brect)
205 , selectionState(selState)
209 , backgroundColor(bc)
213 , rightChildIndex(-1)
219 SelectionState selectionState;
220 QQuickDefaultClipNode *clipNode;
221 QQuickTextNode::Decorations decorations;
223 QColor backgroundColor;
231 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
235 SelectionState selectionState)
237 insert(binaryTree, BinaryTreeNode(rect, image, selectionState, ascent));
240 static void insert(QVarLengthArray<BinaryTreeNode> *binaryTree,
241 const QGlyphRun &glyphRun,
242 SelectionState selectionState,
243 QQuickTextNode::Decorations decorations,
244 const QColor &textColor,
245 const QColor &backgroundColor,
246 const QPointF &position)
248 QRectF searchRect = glyphRun.boundingRect();
249 searchRect.translate(position);
251 if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
254 decorations |= (glyphRun.underline() ? QQuickTextNode::Underline : QQuickTextNode::NoDecoration);
255 decorations |= (glyphRun.overline() ? QQuickTextNode::Overline : QQuickTextNode::NoDecoration);
256 decorations |= (glyphRun.strikeOut() ? QQuickTextNode::StrikeOut : QQuickTextNode::NoDecoration);
257 decorations |= (backgroundColor.isValid() ? QQuickTextNode::Background : QQuickTextNode::NoDecoration);
259 qreal ascent = glyphRun.rawFont().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 setAnchorColor(const QColor &anchorColor)
374 m_anchorColor = anchorColor;
377 void setPosition(const QPointF &position)
379 m_position = position;
383 struct TextDecoration
385 TextDecoration() : selectionState(BinaryTreeNode::Unselected) {}
386 TextDecoration(const BinaryTreeNode::SelectionState &s,
395 BinaryTreeNode::SelectionState selectionState;
400 void processCurrentLine();
401 void addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
402 qreal offset, qreal thickness);
404 QColor m_selectionColor;
406 QColor m_backgroundColor;
407 QColor m_selectedTextColor;
408 QColor m_anchorColor;
411 QTextLine m_currentLine;
414 QList<QPair<QRectF, QColor> > m_backgrounds;
415 QList<QRectF> m_selectionRects;
416 QVarLengthArray<BinaryTreeNode> m_currentLineTree;
418 QList<TextDecoration> m_lines;
419 QVector<BinaryTreeNode> m_processedNodes;
421 QList<QPair<QRectF, QImage> > m_images;
424 int SelectionEngine::addText(const QTextBlock &block,
425 const QTextCharFormat &charFormat,
426 const QColor &textColor,
427 const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
428 int textPos, int fragmentEnd,
429 int selectionStart, int selectionEnd)
431 if (charFormat.foreground().style() != Qt::NoBrush)
432 setTextColor(charFormat.foreground().color());
434 setTextColor(textColor);
436 while (textPos < fragmentEnd) {
437 int blockRelativePosition = textPos - block.position();
438 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
439 if (!currentLine().isValid()
440 || line.lineNumber() != currentLine().lineNumber()) {
441 setCurrentLine(line);
444 Q_ASSERT(line.textLength() > 0);
445 int lineEnd = line.textStart() + block.position() + line.textLength();
447 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
450 int currentStepEnd = textPos + len;
452 addGlyphsForRanges(colorChanges,
453 textPos - block.position(),
454 currentStepEnd - block.position(),
455 selectionStart - block.position(),
456 selectionEnd - block.position());
458 textPos = currentStepEnd;
463 void SelectionEngine::addTextDecorations(const QVarLengthArray<TextDecoration> &textDecorations,
464 qreal offset, qreal thickness)
466 for (int i=0; i<textDecorations.size(); ++i) {
467 TextDecoration textDecoration = textDecorations.at(i);
470 QRectF &rect = textDecoration.rect;
471 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
472 rect.setHeight(thickness);
475 m_lines.append(textDecoration);
479 void SelectionEngine::processCurrentLine()
481 // No glyphs, do nothing
482 if (m_currentLineTree.isEmpty())
485 // 1. Go through current line and get correct decoration position for each node based on
486 // neighbouring decorations. Add decoration to global list
487 // 2. Create clip nodes for all selected text. Try to merge as many as possible within
489 // 3. Add QRects to a list of selection rects.
490 // 4. Add all nodes to a global processed list
491 QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
492 BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
494 Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
496 BinaryTreeNode::SelectionState currentSelectionState = BinaryTreeNode::Unselected;
499 QQuickTextNode::Decorations currentDecorations = QQuickTextNode::NoDecoration;
500 qreal underlineOffset = 0.0;
501 qreal underlineThickness = 0.0;
503 qreal overlineOffset = 0.0;
504 qreal overlineThickness = 0.0;
506 qreal strikeOutOffset = 0.0;
507 qreal strikeOutThickness = 0.0;
509 QRectF decorationRect = currentRect;
512 QColor lastBackgroundColor;
514 QVarLengthArray<TextDecoration> pendingUnderlines;
515 QVarLengthArray<TextDecoration> pendingOverlines;
516 QVarLengthArray<TextDecoration> pendingStrikeOuts;
517 if (!sortedIndexes.isEmpty()) {
518 QQuickDefaultClipNode *currentClipNode = m_hasSelection ? new QQuickDefaultClipNode(QRectF()) : 0;
519 bool currentClipNodeUsed = false;
520 for (int i=0; i<=sortedIndexes.size(); ++i) {
521 BinaryTreeNode *node = 0;
522 if (i < sortedIndexes.size()) {
523 int sortedIndex = sortedIndexes.at(i);
524 Q_ASSERT(sortedIndex < m_currentLineTree.size());
526 node = m_currentLineTree.data() + sortedIndex;
530 currentSelectionState = node->selectionState;
532 // Update decorations
533 if (currentDecorations != QQuickTextNode::NoDecoration) {
534 decorationRect.setY(m_position.y() + m_currentLine.y());
535 decorationRect.setHeight(m_currentLine.height());
538 decorationRect.setRight(node->boundingRect.left());
540 TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
541 if (currentDecorations & QQuickTextNode::Underline)
542 pendingUnderlines.append(textDecoration);
544 if (currentDecorations & QQuickTextNode::Overline)
545 pendingOverlines.append(textDecoration);
547 if (currentDecorations & QQuickTextNode::StrikeOut)
548 pendingStrikeOuts.append(textDecoration);
550 if (currentDecorations & QQuickTextNode::Background)
551 m_backgrounds.append(qMakePair(decorationRect, lastBackgroundColor));
554 // If we've reached an unselected node from a selected node, we add the
555 // selection rect to the graph, and we add decoration every time the
556 // selection state changes, because that means the text color changes
557 if (node == 0 || node->selectionState != currentSelectionState) {
559 currentRect.setRight(node->boundingRect.left());
560 currentRect.setY(m_position.y() + m_currentLine.y());
561 currentRect.setHeight(m_currentLine.height());
563 // Draw selection all the way up to the left edge of the unselected item
564 if (currentSelectionState == BinaryTreeNode::Selected)
565 m_selectionRects.append(currentRect);
567 if (currentClipNode != 0) {
568 if (!currentClipNodeUsed) {
569 delete currentClipNode;
571 currentClipNode->setIsRectangular(true);
572 currentClipNode->setRect(currentRect);
573 currentClipNode->update();
577 if (node != 0 && m_hasSelection)
578 currentClipNode = new QQuickDefaultClipNode(QRectF());
581 currentClipNodeUsed = false;
584 currentSelectionState = node->selectionState;
585 currentRect = node->boundingRect;
587 // Make sure currentRect is valid, otherwise the unite won't work
588 if (currentRect.isNull())
589 currentRect.setSize(QSizeF(1, 1));
592 if (currentRect.isNull())
593 currentRect = node->boundingRect;
595 currentRect = currentRect.united(node->boundingRect);
599 node->clipNode = currentClipNode;
600 currentClipNodeUsed = true;
602 decorationRect = node->boundingRect;
604 // If previous item(s) had underline and current does not, then we add the
605 // pending lines to the lists and likewise for overlines and strikeouts
606 if (!pendingUnderlines.isEmpty()
607 && !(node->decorations & QQuickTextNode::Underline)) {
608 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
610 pendingUnderlines.clear();
612 underlineOffset = 0.0;
613 underlineThickness = 0.0;
616 // ### Add pending when overlineOffset/thickness changes to minimize number of
618 if (!pendingOverlines.isEmpty()) {
619 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
621 pendingOverlines.clear();
623 overlineOffset = 0.0;
624 overlineThickness = 0.0;
627 // ### Add pending when overlineOffset/thickness changes to minimize number of
629 if (!pendingStrikeOuts.isEmpty()) {
630 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
632 pendingStrikeOuts.clear();
634 strikeOutOffset = 0.0;
635 strikeOutThickness = 0.0;
638 // Merge current values with previous. Prefer greatest thickness
639 QRawFont rawFont = node->glyphRun.rawFont();
640 if (node->decorations & QQuickTextNode::Underline) {
641 if (rawFont.lineThickness() > underlineThickness) {
642 underlineThickness = rawFont.lineThickness();
643 underlineOffset = rawFont.underlinePosition();
647 if (node->decorations & QQuickTextNode::Overline) {
648 overlineOffset = -rawFont.ascent();
649 overlineThickness = rawFont.lineThickness();
652 if (node->decorations & QQuickTextNode::StrikeOut) {
653 strikeOutThickness = rawFont.lineThickness();
654 strikeOutOffset = rawFont.ascent() / -3.0;
657 currentDecorations = node->decorations;
658 lastColor = node->color;
659 lastBackgroundColor = node->backgroundColor;
660 m_processedNodes.append(*node);
664 if (!pendingUnderlines.isEmpty())
665 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
667 if (!pendingOverlines.isEmpty())
668 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
670 if (!pendingStrikeOuts.isEmpty())
671 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
674 m_currentLineTree.clear();
675 m_currentLine = QTextLine();
676 m_hasSelection = false;
679 void SelectionEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent,
680 BinaryTreeNode::SelectionState selectionState,
681 QTextFrameFormat::Position layoutPosition)
683 QRectF searchRect = rect;
684 if (layoutPosition == QTextFrameFormat::InFlow) {
685 if (m_currentLineTree.isEmpty()) {
686 searchRect.moveTopLeft(m_position + m_currentLine.position());
688 const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1;
689 if (lastNode->glyphRun.isRightToLeft()) {
690 QPointF lastPos = lastNode->boundingRect.topLeft();
691 searchRect.moveTopRight(lastPos - QPointF(0, ascent - lastNode->ascent));
693 QPointF lastPos = lastNode->boundingRect.topRight();
694 searchRect.moveTopLeft(lastPos - QPointF(0, ascent - lastNode->ascent));
699 BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
702 void SelectionEngine::addTextObject(const QPointF &position, const QTextCharFormat &format,
703 BinaryTreeNode::SelectionState selectionState,
704 QTextDocument *textDocument, int pos,
705 QTextFrameFormat::Position layoutPosition)
707 QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType());
710 QSizeF size = handler->intrinsicSize(textDocument, pos, format);
712 if (format.objectType() == QTextFormat::ImageObject) {
713 QTextImageFormat imageFormat = format.toImageFormat();
714 if (QQuickTextDocumentWithImageResources *imageDoc = qobject_cast<QQuickTextDocumentWithImageResources *>(textDocument)) {
715 image = imageDoc->image(imageFormat);
720 QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
721 image = imageHandler->image(textDocument, imageFormat);
725 if (image.isNull()) {
726 image = QImage(size.toSize(), QImage::Format_ARGB32_Premultiplied);
727 image.fill(Qt::transparent);
729 QPainter painter(&image);
730 handler->drawObject(&painter, image.rect(), textDocument, pos, format);
735 QFontMetrics m(format.font());
736 switch (format.verticalAlignment())
738 case QTextCharFormat::AlignMiddle:
739 ascent = size.height() / 2 - 1;
741 case QTextCharFormat::AlignBaseline:
742 ascent = size.height() - m.descent() - 1;
745 ascent = size.height() - 1;
748 addImage(QRectF(position, size), image, ascent, selectionState, layoutPosition);
752 void SelectionEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
754 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Unselected,
755 QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position);
758 void SelectionEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
760 int currentSize = m_currentLineTree.size();
761 BinaryTreeNode::insert(&m_currentLineTree, glyphRun, BinaryTreeNode::Selected,
762 QQuickTextNode::NoDecoration, m_textColor, m_backgroundColor, m_position);
763 m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
766 void SelectionEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
768 int selectionStart, int selectionEnd)
770 int currentPosition = start;
771 int remainingLength = end - start;
772 for (int j=0; j<ranges.size(); ++j) {
773 const QTextLayout::FormatRange &range = ranges.at(j);
774 if (range.start + range.length >= currentPosition
775 && range.start < currentPosition + remainingLength) {
777 if (range.start > currentPosition) {
778 addGlyphsInRange(currentPosition, range.start - currentPosition,
779 QColor(), QColor(), selectionStart, selectionEnd);
781 int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
783 if (range.format.hasProperty(QTextFormat::ForegroundBrush))
784 rangeColor = range.format.foreground().color();
785 else if (range.format.isAnchor())
786 rangeColor = m_anchorColor;
787 QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush)
788 ? range.format.background().color()
791 addGlyphsInRange(range.start, rangeEnd - range.start,
792 rangeColor, rangeBackgroundColor,
793 selectionStart, selectionEnd);
795 currentPosition = range.start + range.length;
796 remainingLength = end - currentPosition;
798 } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
803 if (remainingLength > 0) {
804 addGlyphsInRange(currentPosition, remainingLength, QColor(), QColor(),
805 selectionStart, selectionEnd);
810 void SelectionEngine::addGlyphsInRange(int rangeStart, int rangeLength,
811 const QColor &color, const QColor &backgroundColor,
812 int selectionStart, int selectionEnd)
815 if (color.isValid()) {
816 oldColor = m_textColor;
820 QColor oldBackgroundColor = m_backgroundColor;
821 if (backgroundColor.isValid()) {
822 oldBackgroundColor = m_backgroundColor;
823 m_backgroundColor = backgroundColor;
826 bool hasSelection = selectionEnd >= 0
827 && selectionStart <= selectionEnd;
829 QTextLine &line = m_currentLine;
830 int rangeEnd = rangeStart + rangeLength;
831 if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) {
832 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
833 for (int j=0; j<glyphRuns.size(); ++j) {
834 const QGlyphRun &glyphRun = glyphRuns.at(j);
835 addUnselectedGlyphs(glyphRun);
838 if (rangeStart < selectionStart) {
839 QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart,
840 qMin(selectionStart - rangeStart,
843 for (int j=0; j<glyphRuns.size(); ++j) {
844 const QGlyphRun &glyphRun = glyphRuns.at(j);
845 addUnselectedGlyphs(glyphRun);
849 if (rangeEnd > selectionStart) {
850 int start = qMax(selectionStart, rangeStart);
851 int length = qMin(selectionEnd - start + 1, rangeEnd - start);
852 QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
854 for (int j=0; j<glyphRuns.size(); ++j) {
855 const QGlyphRun &glyphRun = glyphRuns.at(j);
856 addSelectedGlyphs(glyphRun);
860 if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
861 QList<QGlyphRun> glyphRuns = line.glyphRuns(selectionEnd + 1, rangeEnd - selectionEnd - 1);
862 for (int j=0; j<glyphRuns.size(); ++j) {
863 const QGlyphRun &glyphRun = glyphRuns.at(j);
864 addUnselectedGlyphs(glyphRun);
869 if (backgroundColor.isValid())
870 m_backgroundColor = oldBackgroundColor;
872 if (oldColor.isValid())
873 m_textColor = oldColor;
876 void SelectionEngine::addBorder(const QRectF &rect, qreal border,
877 QTextFrameFormat::BorderStyle borderStyle,
878 const QBrush &borderBrush)
880 QColor color = borderBrush.color();
882 // Currently we don't support other styles than solid
883 Q_UNUSED(borderStyle);
885 m_backgrounds.append(qMakePair(QRectF(rect.left(), rect.top(), border, rect.height() + border), color));
886 m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.top(), rect.width(), border), color));
887 m_backgrounds.append(qMakePair(QRectF(rect.right(), rect.top() + border, border, rect.height() - border), color));
888 m_backgrounds.append(qMakePair(QRectF(rect.left() + border, rect.bottom(), rect.width(), border), color));
891 void SelectionEngine::addFrameDecorations(QTextDocument *document, QTextFrame *frame)
893 QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(document->documentLayout());
894 QTextFrameFormat frameFormat = frame->format().toFrameFormat();
896 QTextTable *table = qobject_cast<QTextTable *>(frame);
897 QRectF boundingRect = table == 0
898 ? documentLayout->frameBoundingRect(frame)
899 : documentLayout->tableBoundingRect(table);
901 QBrush bg = frame->frameFormat().background();
902 if (bg.style() != Qt::NoBrush)
903 m_backgrounds.append(qMakePair(boundingRect, bg.color()));
905 if (!frameFormat.hasProperty(QTextFormat::FrameBorder))
908 qreal borderWidth = frameFormat.border();
909 if (qFuzzyIsNull(borderWidth))
912 QBrush borderBrush = frameFormat.borderBrush();
913 QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle();
914 if (borderStyle == QTextFrameFormat::BorderStyle_None)
917 addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
918 -frameFormat.rightMargin(), -frameFormat.bottomMargin()),
919 borderWidth, borderStyle, borderBrush);
921 int rows = table->rows();
922 int columns = table->columns();
924 for (int row=0; row<rows; ++row) {
925 for (int column=0; column<columns; ++column) {
926 QTextTableCell cell = table->cellAt(row, column);
928 QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
929 addBorder(cellRect.adjusted(-borderWidth, -borderWidth, 0, 0), borderWidth,
930 borderStyle, borderBrush);
936 void SelectionEngine::addToSceneGraph(QQuickTextNode *parentNode,
937 QQuickText::TextStyle style,
938 const QColor &styleColor)
940 if (m_currentLine.isValid())
941 processCurrentLine();
944 for (int i=0; i<m_backgrounds.size(); ++i) {
945 const QRectF &rect = m_backgrounds.at(i).first;
946 const QColor &color = m_backgrounds.at(i).second;
948 parentNode->appendChildNode(new QSGSimpleRectNode(rect, color));
951 // First, prepend all selection rectangles to the tree
952 for (int i=0; i<m_selectionRects.size(); ++i) {
953 const QRectF &rect = m_selectionRects.at(i);
955 parentNode->appendChildNode(new QSGSimpleRectNode(rect, m_selectionColor));
958 // Finally, add decorations for each node to the tree.
959 for (int i=0; i<m_lines.size(); ++i) {
960 const TextDecoration &textDecoration = m_lines.at(i);
962 QColor color = textDecoration.selectionState == BinaryTreeNode::Selected
963 ? m_selectedTextColor
964 : textDecoration.color;
966 parentNode->appendChildNode(new QSGSimpleRectNode(textDecoration.rect, color));
969 // Then, go through all the nodes for all lines and combine all QGlyphRuns with a common
970 // font, selection state and clip node.
971 typedef QPair<QFontEngine *, QPair<QQuickDefaultClipNode *, QPair<QRgb, int> > > KeyType;
972 QHash<KeyType, BinaryTreeNode *> map;
973 QList<BinaryTreeNode *> nodes;
974 for (int i=0; i<m_processedNodes.size(); ++i) {
975 BinaryTreeNode *node = m_processedNodes.data() + i;
977 if (node->image.isNull()) {
978 QGlyphRun glyphRun = node->glyphRun;
979 QRawFont rawFont = glyphRun.rawFont();
980 QRawFontPrivate *rawFontD = QRawFontPrivate::get(rawFont);
982 QFontEngine *fontEngine = rawFontD->fontEngine;
984 KeyType key(qMakePair(fontEngine,
985 qMakePair(node->clipNode,
986 qMakePair(node->color.rgba(), int(node->selectionState)))));
988 BinaryTreeNode *otherNode = map.value(key, 0);
989 if (otherNode != 0) {
990 QGlyphRun &otherGlyphRun = otherNode->glyphRun;
992 QVector<quint32> otherGlyphIndexes = otherGlyphRun.glyphIndexes();
993 QVector<QPointF> otherGlyphPositions = otherGlyphRun.positions();
995 otherGlyphIndexes += glyphRun.glyphIndexes();
997 QVector<QPointF> glyphPositions = glyphRun.positions();
998 for (int j=0; j<glyphPositions.size(); ++j) {
999 otherGlyphPositions += glyphPositions.at(j) + (node->position - otherNode->position);
1002 otherGlyphRun.setGlyphIndexes(otherGlyphIndexes);
1003 otherGlyphRun.setPositions(otherGlyphPositions);
1006 map.insert(key, node);
1010 parentNode->addImage(node->boundingRect, node->image);
1011 if (node->selectionState == BinaryTreeNode::Selected) {
1012 QColor color = m_selectionColor;
1013 color.setAlpha(128);
1014 parentNode->appendChildNode(new QSGSimpleRectNode(node->boundingRect, color));
1019 // ...and add clip nodes and glyphs to tree.
1020 foreach (const BinaryTreeNode *node, nodes) {
1022 QQuickDefaultClipNode *clipNode = node->clipNode;
1023 if (clipNode != 0 && clipNode->parent() == 0 )
1024 parentNode->appendChildNode(clipNode);
1026 QColor color = node->selectionState == BinaryTreeNode::Selected
1027 ? m_selectedTextColor
1030 parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
1035 void QQuickTextNode::mergeFormats(QTextLayout *textLayout,
1036 QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
1038 Q_ASSERT(mergedFormats != 0);
1039 if (textLayout == 0)
1042 QList<QTextLayout::FormatRange> additionalFormats = textLayout->additionalFormats();
1043 for (int i=0; i<additionalFormats.size(); ++i) {
1044 QTextLayout::FormatRange additionalFormat = additionalFormats.at(i);
1045 if (additionalFormat.format.hasProperty(QTextFormat::ForegroundBrush)
1046 || additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)
1047 || additionalFormat.format.isAnchor()) {
1048 // Merge overlapping formats
1049 if (!mergedFormats->isEmpty()) {
1050 QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1;
1052 if (additionalFormat.start < lastFormat->start + lastFormat->length) {
1053 QTextLayout::FormatRange *mergedRange = 0;
1055 int length = additionalFormat.length;
1056 if (additionalFormat.start > lastFormat->start) {
1057 lastFormat->length = additionalFormat.start - lastFormat->start;
1058 length -= lastFormat->length;
1060 mergedFormats->append(QTextLayout::FormatRange());
1061 mergedRange = mergedFormats->data() + mergedFormats->size() - 1;
1062 lastFormat = mergedFormats->data() + mergedFormats->size() - 2;
1064 mergedRange = lastFormat;
1067 mergedRange->format = lastFormat->format;
1068 mergedRange->format.merge(additionalFormat.format);
1069 mergedRange->start = additionalFormat.start;
1071 int end = qMin(additionalFormat.start + additionalFormat.length,
1072 lastFormat->start + lastFormat->length);
1074 mergedRange->length = end - mergedRange->start;
1075 length -= mergedRange->length;
1077 additionalFormat.start = end;
1078 additionalFormat.length = length;
1082 if (additionalFormat.length > 0)
1083 mergedFormats->append(additionalFormat);
1091 class ProtectedLayoutAccessor: public QAbstractTextDocumentLayout
1094 inline QTextCharFormat formatAccessor(int pos)
1102 void QQuickTextNode::addImage(const QRectF &rect, const QImage &image)
1104 QSGImageNode *node = m_context->createImageNode();
1105 QSGTexture *texture = m_context->createTexture(image);
1106 m_textures.append(texture);
1107 node->setTargetRect(rect);
1108 node->setTexture(texture);
1109 appendChildNode(node);
1113 void QQuickTextNode::addTextDocument(const QPointF &position, QTextDocument *textDocument,
1114 const QColor &textColor,
1115 QQuickText::TextStyle style, const QColor &styleColor,
1116 const QColor &anchorColor,
1117 const QColor &selectionColor, const QColor &selectedTextColor,
1118 int selectionStart, int selectionEnd)
1120 SelectionEngine engine;
1121 engine.setTextColor(textColor);
1122 engine.setSelectedTextColor(selectedTextColor);
1123 engine.setSelectionColor(selectionColor);
1124 engine.setAnchorColor(anchorColor);
1126 QList<QTextFrame *> frames;
1127 frames.append(textDocument->rootFrame());
1128 while (!frames.isEmpty()) {
1129 QTextFrame *textFrame = frames.takeFirst();
1130 frames.append(textFrame->childFrames());
1132 engine.addFrameDecorations(textDocument, textFrame);
1134 if (textFrame->firstPosition() > textFrame->lastPosition()
1135 && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
1136 const int pos = textFrame->firstPosition() - 1;
1137 ProtectedLayoutAccessor *a = static_cast<ProtectedLayoutAccessor *>(textDocument->documentLayout());
1138 QTextCharFormat format = a->formatAccessor(pos);
1139 QRectF rect = a->frameBoundingRect(textFrame);
1141 QTextBlock block = textFrame->firstCursorPosition().block();
1142 engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
1143 engine.addTextObject(rect.topLeft(), format, BinaryTreeNode::Unselected, textDocument,
1144 pos, textFrame->frameFormat().position());
1146 QTextFrame::iterator it = textFrame->begin();
1148 while (!it.atEnd()) {
1149 Q_ASSERT(!engine.currentLine().isValid());
1151 QTextBlock block = it.currentBlock();
1152 int preeditLength = block.isValid() ? block.layout()->preeditAreaText().length() : 0;
1153 int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
1155 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1156 mergeFormats(block.layout(), &colorChanges);
1158 QPointF blockPosition = textDocument->documentLayout()->blockBoundingRect(block).topLeft() + position;
1159 if (QTextList *textList = block.textList()) {
1160 QPointF pos = blockPosition;
1161 QTextLayout *layout = block.layout();
1162 if (layout->lineCount() > 0) {
1163 QTextLine firstLine = layout->lineAt(0);
1164 Q_ASSERT(firstLine.isValid());
1166 engine.setCurrentLine(firstLine);
1168 QRectF textRect = firstLine.naturalTextRect();
1169 pos += textRect.topLeft();
1170 if (block.textDirection() == Qt::RightToLeft)
1171 pos.rx() += textRect.width();
1173 const QTextCharFormat charFormat = block.charFormat();
1174 QFont font(charFormat.font());
1175 QFontMetricsF fontMetrics(font);
1176 QTextListFormat listFormat = textList->format();
1178 QString listItemBullet;
1179 switch (listFormat.style()) {
1180 case QTextListFormat::ListCircle:
1181 listItemBullet = QChar(0x25E6); // White bullet
1183 case QTextListFormat::ListSquare:
1184 listItemBullet = QChar(0x25AA); // Black small square
1186 case QTextListFormat::ListDecimal:
1187 case QTextListFormat::ListLowerAlpha:
1188 case QTextListFormat::ListUpperAlpha:
1189 case QTextListFormat::ListLowerRoman:
1190 case QTextListFormat::ListUpperRoman:
1191 listItemBullet = textList->itemText(block);
1194 listItemBullet = QChar(0x2022); // Black bullet
1198 QSizeF size(fontMetrics.width(listItemBullet), fontMetrics.height());
1199 qreal xoff = fontMetrics.width(QLatin1Char(' '));
1200 if (block.textDirection() == Qt::LeftToRight)
1201 xoff = -xoff - size.width();
1202 engine.setPosition(pos + QPointF(xoff, 0));
1205 layout.setFont(font);
1206 layout.setText(listItemBullet); // Bullet
1207 layout.beginLayout();
1208 QTextLine line = layout.createLine();
1209 line.setPosition(QPointF(0, 0));
1212 QList<QGlyphRun> glyphRuns = layout.glyphRuns();
1213 for (int i=0; i<glyphRuns.size(); ++i)
1214 engine.addUnselectedGlyphs(glyphRuns.at(i));
1218 int textPos = block.position();
1219 QTextBlock::iterator blockIterator = block.begin();
1221 while (!blockIterator.atEnd()) {
1222 QTextFragment fragment = blockIterator.fragment();
1223 QString text = fragment.text();
1227 QTextCharFormat charFormat = fragment.charFormat();
1228 engine.setPosition(blockPosition);
1229 if (text.contains(QChar::ObjectReplacementCharacter)) {
1230 QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
1231 if (frame && frame->frameFormat().position() == QTextFrameFormat::InFlow) {
1232 int blockRelativePosition = textPos - block.position();
1233 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
1234 if (!engine.currentLine().isValid()
1235 || line.lineNumber() != engine.currentLine().lineNumber()) {
1236 engine.setCurrentLine(line);
1239 BinaryTreeNode::SelectionState selectionState =
1240 (selectionStart < textPos + text.length()
1241 && selectionEnd >= textPos)
1242 ? BinaryTreeNode::Selected
1243 : BinaryTreeNode::Unselected;
1245 engine.addTextObject(QPointF(), charFormat, selectionState, textDocument, textPos);
1247 textPos += text.length();
1249 if (charFormat.foreground().style() != Qt::NoBrush)
1250 engine.setTextColor(charFormat.foreground().color());
1251 else if (charFormat.isAnchor())
1252 engine.setTextColor(anchorColor);
1254 engine.setTextColor(textColor);
1256 int fragmentEnd = textPos + fragment.length();
1257 if (preeditPosition >= 0
1258 && preeditPosition >= textPos
1259 && preeditPosition <= fragmentEnd) {
1260 fragmentEnd += preeditLength;
1263 textPos = engine.addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd,
1264 selectionStart, selectionEnd);
1270 if (preeditLength >= 0 && textPos <= block.position() + preeditPosition) {
1271 engine.setPosition(blockPosition);
1272 textPos = block.position() + preeditPosition;
1273 QTextLine line = block.layout()->lineForTextPosition(preeditPosition);
1274 if (!engine.currentLine().isValid()
1275 || line.lineNumber() != engine.currentLine().lineNumber()) {
1276 engine.setCurrentLine(line);
1278 textPos = engine.addText(block, block.charFormat(), textColor, colorChanges,
1279 textPos, textPos + preeditLength,
1280 selectionStart, selectionEnd);
1283 engine.setCurrentLine(QTextLine()); // Reset current line because the text layout changed
1289 engine.addToSceneGraph(this, style, styleColor);
1292 void QQuickTextNode::addTextLayout(const QPointF &position, QTextLayout *textLayout, const QColor &color,
1293 QQuickText::TextStyle style, const QColor &styleColor,
1294 const QColor &anchorColor,
1295 const QColor &selectionColor, const QColor &selectedTextColor,
1296 int selectionStart, int selectionEnd,
1297 int lineStart, int lineCount)
1299 SelectionEngine engine;
1300 engine.setTextColor(color);
1301 engine.setSelectedTextColor(selectedTextColor);
1302 engine.setSelectionColor(selectionColor);
1303 engine.setAnchorColor(anchorColor);
1304 engine.setPosition(position);
1306 int preeditLength = textLayout->preeditAreaText().length();
1307 int preeditPosition = textLayout->preeditAreaPosition();
1309 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1310 mergeFormats(textLayout, &colorChanges);
1312 lineCount = lineCount >= 0
1313 ? qMin(lineStart + lineCount, textLayout->lineCount())
1314 : textLayout->lineCount();
1316 for (int i=lineStart; i<lineCount; ++i) {
1317 QTextLine line = textLayout->lineAt(i);
1319 int start = line.textStart();
1320 int length = line.textLength();
1321 int end = start + length;
1323 if (preeditPosition >= 0
1324 && preeditPosition >= start
1325 && preeditPosition < end) {
1326 end += preeditLength;
1329 engine.setCurrentLine(line);
1330 engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
1333 engine.addToSceneGraph(this, style, styleColor);
1336 void QQuickTextNode::deleteContent()
1338 while (firstChild() != 0)
1339 delete firstChild();
1344 void QQuickTextNode::updateNodes()
1348 if (m_text.isEmpty())
1351 if (m_usePixmapCache) {
1352 // ### gunnar: port properly
1353 // QPixmap pixmap = generatedPixmap();
1354 // if (pixmap.isNull())
1357 // QSGImageNode *pixmapNode = m_context->createImageNode();
1358 // pixmapNode->setRect(pixmap.rect());
1359 // pixmapNode->setSourceRect(pixmap.rect());
1360 // pixmapNode->setOpacity(m_opacity);
1361 // pixmapNode->setClampToEdge(true);
1362 // pixmapNode->setLinearFiltering(m_linearFiltering);
1364 // appendChildNode(pixmapNode);
1366 if (m_text.isEmpty())
1369 // Implement styling by drawing text several times at slight shifts. shiftForStyle
1370 // contains the sequence of shifted positions at which to draw the text. All except
1371 // the last will be drawn with styleColor.
1372 QList<QPointF> shiftForStyle;
1373 switch (m_textStyle) {
1374 case OutlineTextStyle:
1375 // ### Should be made faster by implementing outline material
1376 shiftForStyle << QPointF(-1, 0);
1377 shiftForStyle << QPointF(0, -1);
1378 shiftForStyle << QPointF(1, 0);
1379 shiftForStyle << QPointF(0, 1);
1381 case SunkenTextStyle:
1382 shiftForStyle << QPointF(0, -1);
1384 case RaisedTextStyle:
1385 shiftForStyle << QPointF(0, 1);
1391 shiftForStyle << QPointF(0, 0); // Regular position
1392 while (!shiftForStyle.isEmpty()) {
1393 QPointF shift = shiftForStyle.takeFirst();
1395 // Use styleColor for all but last shift
1397 QColor overrideColor = shiftForStyle.isEmpty() ? QColor() : m_styleColor;
1399 QTextFrame *textFrame = m_textDocument->rootFrame();
1400 QPointF p = m_textDocument->documentLayout()->frameBoundingRect(textFrame).topLeft();
1402 QTextFrame::iterator it = textFrame->begin();
1403 while (!it.atEnd()) {
1404 addTextBlock(shift + p, it.currentBlock(), overrideColor);
1408 addTextLayout(shift, m_textLayout, shiftForStyle.isEmpty()