7#include <QtCore/qpoint.h>
8#include <QtGui/qabstracttextdocumentlayout.h>
9#include <QtGui/qrawfont.h>
10#include <QtGui/qtextdocument.h>
11#include <QtGui/qtextlayout.h>
12#include <QtGui/qtextobject.h>
13#include <QtGui/qtexttable.h>
14#include <QtGui/qtextlist.h>
15#include <QtGui/qimageiohandler.h>
17#include <private/qquicktext_p.h>
18#include <private/qtextdocumentlayout_p.h>
19#include <private/qtextimagehandler_p.h>
20#include <private/qrawfont_p.h>
21#include <private/qglyphrun_p.h>
22#include <private/qquickitem_p.h>
23#include <private/qsgdistancefieldglyphnode_p.h>
25#include <QtGui/private/qguiapplication_p.h>
26#include <qpa/qplatformtheme.h>
34 if (style == QTextCharFormat::SpellCheckUnderline) {
35 QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme();
37 style = QTextCharFormat::UnderlineStyle(
38 theme->themeHint(QPlatformTheme::SpellCheckUnderlineStyle).toInt());
40 if (style == QTextCharFormat::SpellCheckUnderline)
41 style = QTextCharFormat::WaveUnderline;
45 case QTextCharFormat::NoUnderline:
46 case QTextCharFormat::SingleUnderline:
47 case QTextCharFormat::DashUnderline:
48 case QTextCharFormat::DotLine:
49 case QTextCharFormat::DashDotLine:
50 case QTextCharFormat::DashDotDotLine:
51 case QTextCharFormat::WaveUnderline:
54 qWarning(
"QQuickTextNodeEngine: unknown underline style %d, falling back to SingleUnderline",
56 return QTextCharFormat::SingleUnderline;
69 SelectionState selState,
71 const Decorations &decs,
72 QTextCharFormat::UnderlineStyle us,
74 const QColor &bc,
const QColor &dc,
75 const QPointF &pos, qreal a)
91 ranges.append(std::make_pair(d->textRangeStart, d->textRangeEnd));
95 Decorations decorations, QTextCharFormat::UnderlineStyle underlineStyle,
const QColor &textColor,
96 const QColor &backgroundColor,
const QColor &decorationColor,
const QPointF &position)
98 QRectF searchRect = glyphRun.boundingRect();
99 searchRect.translate(position);
101 if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
104 if (glyphRun.underline() || underlineStyle != QTextCharFormat::NoUnderline)
110 qreal ascent = glyphRun.rawFont().ascent();
111 insert(binaryTree, BinaryTreeNode(glyphRun,
125 int newIndex = binaryTree->size();
126 binaryTree->append(binaryTreeNode);
132 BinaryTreeNode *node = binaryTree->data() + searchIndex;
133 if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) {
134 if (node->leftChildIndex < 0) {
135 node->leftChildIndex = newIndex;
138 searchIndex = node->leftChildIndex;
141 if (node->rightChildIndex < 0) {
142 node->rightChildIndex = newIndex;
145 searchIndex = node->rightChildIndex;
152 QVarLengthArray<
int> *sortedIndexes,
int currentIndex)
154 Q_ASSERT(currentIndex < binaryTree.size());
157 if (node->leftChildIndex >= 0)
158 inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
160 sortedIndexes->append(currentIndex);
162 if (node->rightChildIndex >= 0)
163 inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
168 const QTextCharFormat &charFormat,
169 const QColor &textColor,
170 const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
171 int textPos,
int fragmentEnd,
172 int selectionStart,
int selectionEnd)
174 if (charFormat.foreground().style() != Qt::NoBrush)
179 while (textPos < fragmentEnd) {
180 int blockRelativePosition = textPos - block.position();
181 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
184 if (!currentLine().isValid()
185 || line.lineNumber() != currentLine().lineNumber()) {
189 Q_ASSERT(line.textLength() > 0);
190 int lineEnd = line.textStart() + block.position() + line.textLength();
192 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
195 int currentStepEnd = textPos + len;
197 addGlyphsForRanges(colorChanges,
198 textPos - block.position(),
199 currentStepEnd - block.position(),
200 selectionStart - block.position(),
201 selectionEnd - block.position());
203 textPos = currentStepEnd;
208void QQuickTextNodeEngine::addTextDecorations(
const QVarLengthArray<TextDecoration> &textDecorations,
209 qreal offset, qreal thickness)
211 for (
auto textDecoration : textDecorations) {
212 QRectF &rect = textDecoration.rect;
213 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
214 rect.setHeight(thickness);
216 m_lines.append(textDecoration);
223 if (m_currentLineTree.isEmpty())
232 QVarLengthArray<
int> sortedIndexes;
233 BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
235 Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
237 SelectionState currentSelectionState = Unselected;
241 qreal underlineOffset = 0.0;
242 qreal underlineThickness = 0.0;
244 qreal overlineOffset = 0.0;
245 qreal overlineThickness = 0.0;
247 qreal strikeOutOffset = 0.0;
248 qreal strikeOutThickness = 0.0;
250 QRectF decorationRect = currentRect;
253 QColor lastBackgroundColor;
254 QColor lastDecorationColor;
255 QTextCharFormat::UnderlineStyle lastUnderlineStyle = QTextCharFormat::NoUnderline;
257 QVarLengthArray<TextDecoration> pendingUnderlines;
258 QVarLengthArray<TextDecoration> pendingOverlines;
259 QVarLengthArray<TextDecoration> pendingStrikeOuts;
260 if (!sortedIndexes.isEmpty()) {
261 QQuickDefaultClipNode *currentClipNode = m_hasSelection ?
new QQuickDefaultClipNode(QRectF()) :
nullptr;
262 bool currentClipNodeUsed =
false;
263 for (
int i=0; i<=sortedIndexes.size(); ++i) {
265 if (i < sortedIndexes.size()) {
266 int sortedIndex = sortedIndexes.at(i);
267 Q_ASSERT(sortedIndex < m_currentLineTree.size());
269 node = m_currentLineTree.data() + sortedIndex;
271 currentSelectionState = node->selectionState;
276 decorationRect.setY(m_position.y() + m_currentLine.y());
277 decorationRect.setHeight(m_currentLine.height());
280 decorationRect.setRight(node->boundingRect.left());
282 TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
283 if (lastDecorationColor.isValid() &&
287 textDecoration.color = lastDecorationColor;
290 textDecoration.underlineStyle = lastUnderlineStyle;
292 if (!pendingUnderlines.isEmpty()) {
293 TextDecoration &last = pendingUnderlines.last();
295 constexpr qreal mergeThreshold = 0.5;
296 if (last.underlineStyle == lastUnderlineStyle
297 && last.color == textDecoration.color
298 && last.selectionState == textDecoration.selectionState
299 && qAbs(last.rect.right() - textDecoration.rect.left()) < mergeThreshold) {
301 last.rect.setRight(textDecoration.rect.right());
303 pendingUnderlines.append(textDecoration);
306 pendingUnderlines.append(textDecoration);
311 pendingOverlines.append(textDecoration);
314 pendingStrikeOuts.append(textDecoration);
316 if (currentDecorations & Decoration::Background)
317 m_backgrounds.append(std::make_pair(decorationRect, lastBackgroundColor));
323 if (node ==
nullptr || node->selectionState != currentSelectionState) {
324 currentRect.setY(m_position.y() + m_currentLine.y());
325 currentRect.setHeight(m_currentLine.height());
327 if (currentSelectionState == Selected)
328 m_selectionRects.append(currentRect);
330 if (currentClipNode !=
nullptr) {
331 if (!currentClipNodeUsed) {
332 delete currentClipNode;
334 currentClipNode->setIsRectangular(
true);
335 currentClipNode->setRect(currentRect);
336 currentClipNode->update();
340 if (node !=
nullptr && m_hasSelection)
341 currentClipNode =
new QQuickDefaultClipNode(QRectF());
343 currentClipNode =
nullptr;
344 currentClipNodeUsed =
false;
346 if (node !=
nullptr) {
347 currentSelectionState = node->selectionState;
348 currentRect = node->boundingRect;
351 if (currentRect.isNull())
352 currentRect.setSize(QSizeF(1, 1));
355 if (currentRect.isNull())
356 currentRect = node->boundingRect;
358 currentRect = currentRect.united(node->boundingRect);
361 if (node !=
nullptr) {
362 if (node->selectionState == Selected) {
363 node->clipNode = currentClipNode;
364 currentClipNodeUsed =
true;
367 decorationRect = node->boundingRect;
371 if (!pendingUnderlines.isEmpty()
373 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
375 pendingUnderlines.clear();
377 underlineOffset = 0.0;
378 underlineThickness = 0.0;
383 if (!pendingOverlines.isEmpty()) {
384 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
386 pendingOverlines.clear();
388 overlineOffset = 0.0;
389 overlineThickness = 0.0;
394 if (!pendingStrikeOuts.isEmpty()) {
395 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
397 pendingStrikeOuts.clear();
399 strikeOutOffset = 0.0;
400 strikeOutThickness = 0.0;
404 QRawFont rawFont = node->glyphRun.rawFont();
406 if (rawFont.lineThickness() > underlineThickness) {
407 underlineThickness = rawFont.lineThickness();
408 underlineOffset = rawFont.underlinePosition();
413 overlineOffset = -rawFont.ascent();
414 overlineThickness = rawFont.lineThickness();
418 strikeOutThickness = rawFont.lineThickness();
419 strikeOutOffset = rawFont.ascent() / -3.0;
422 currentDecorations = node->decorations;
423 lastColor = node->color;
424 lastBackgroundColor = node->backgroundColor;
425 lastDecorationColor = node->decorationColor;
426 lastUnderlineStyle = node->underlineStyle;
427 m_processedNodes.append(*node);
431 if (!pendingUnderlines.isEmpty())
432 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
434 if (!pendingOverlines.isEmpty())
435 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
437 if (!pendingStrikeOuts.isEmpty())
438 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
441 m_currentLineTree.clear();
442 m_currentLine = QTextLine();
443 m_hasSelection =
false;
447 SelectionState selectionState,
448 QTextFrameFormat::Position layoutPosition)
450 QRectF searchRect = rect;
451 if (layoutPosition == QTextFrameFormat::InFlow) {
452 if (m_currentLineTree.isEmpty()) {
453 qreal y = m_currentLine.ascent() - ascent;
454 if (m_currentTextDirection == Qt::RightToLeft)
455 searchRect.moveTopRight(m_position + m_currentLine.rect().topRight() + QPointF(0, y));
457 searchRect.moveTopLeft(m_position + m_currentLine.position() + QPointF(0, y));
459 const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1;
460 if (lastNode->glyphRun.isRightToLeft()) {
461 QPointF lastPos = lastNode->boundingRect.topLeft();
462 searchRect.moveTopRight(lastPos - QPointF(0, ascent - lastNode->ascent));
464 QPointF lastPos = lastNode->boundingRect.topRight();
465 searchRect.moveTopLeft(lastPos - QPointF(0, ascent - lastNode->ascent));
470 BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
471 m_hasContents =
true;
475 SelectionState selectionState,
476 QTextDocument *textDocument,
int pos,
477 QTextFrameFormat::Position layoutPosition)
479 QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType());
480 if (handler !=
nullptr) {
482 QSizeF size = handler->intrinsicSize(textDocument, pos, format);
484 if (format.objectType() == QTextFormat::ImageObject) {
485 QTextImageFormat imageFormat = format.toImageFormat();
486 QTextImageHandler *imageHandler =
static_cast<QTextImageHandler *>(handler);
487 image = imageHandler->image(textDocument, imageFormat);
490 if (image.isNull()) {
491 if (QImageIOHandler::allocateImage((size * m_devicePixelRatio).toSize(),
492 QImage::Format_ARGB32_Premultiplied,
494 image.setDevicePixelRatio(m_devicePixelRatio);
495 image.fill(Qt::transparent);
497 QPainter painter(&image);
498 handler->drawObject(&painter, QRectF({}, size), textDocument, pos, format);
507 QTextLine line = block.layout()->lineForTextPosition(pos - block.position());
508 switch (format.verticalAlignment())
510 case QTextCharFormat::AlignTop:
511 ascent = line.ascent();
513 case QTextCharFormat::AlignMiddle:
515 ascent = (line.ascent() + line.descent()) / 2 - line.descent() + size.height() / 2;
517 case QTextCharFormat::AlignBottom:
518 ascent = size.height() - line.descent();
520 case QTextCharFormat::AlignBaseline: {
521 QFontMetricsF m(format.font());
522 ascent = size.height() - m.descent();
526 ascent = size.height();
529 addImage(QRectF(position, size), image, ascent, selectionState, layoutPosition);
535 BinaryTreeNode::insert(&m_currentLineTree,
538 Decoration::NoDecoration,
548 int currentSize = m_currentLineTree.size();
549 BinaryTreeNode::insert(&m_currentLineTree,
552 Decoration::NoDecoration,
558 m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
563 int selectionStart,
int selectionEnd)
565 int currentPosition = start;
566 int remainingLength = end - start;
567 for (
const QTextLayout::FormatRange &range : ranges) {
568 if (range.start + range.length > currentPosition
569 && range.start < currentPosition + remainingLength) {
571 if (range.start > currentPosition) {
572 addGlyphsInRange(currentPosition, range.start - currentPosition,
573 QTextCharFormat::NoUnderline,
574 QColor(), QColor(), QColor(), selectionStart, selectionEnd);
576 int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
578 if (range.format.hasProperty(QTextFormat::ForegroundBrush))
579 rangeColor = range.format.foreground().color();
580 else if (range.format.isAnchor())
581 rangeColor = m_anchorColor;
582 QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush)
583 ? range.format.background().color()
586 QColor rangeDecorationColor = range.format.hasProperty(QTextFormat::TextUnderlineColor)
587 ? range.format.underlineColor()
590 QTextCharFormat::UnderlineStyle rangeUnderlineStyle = QTextCharFormat::NoUnderline;
591 if (range.format.hasProperty(QTextFormat::TextUnderlineStyle))
592 rangeUnderlineStyle = range.format.underlineStyle();
594 addGlyphsInRange(range.start, rangeEnd - range.start, rangeUnderlineStyle,
595 rangeColor, rangeBackgroundColor,
596 rangeDecorationColor, selectionStart, selectionEnd);
598 currentPosition = range.start + range.length;
599 remainingLength = end - currentPosition;
601 }
else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
606 if (remainingLength > 0) {
607 addGlyphsInRange(currentPosition, remainingLength, QTextCharFormat::NoUnderline,
608 QColor(), QColor(), QColor(),
609 selectionStart, selectionEnd);
615 QTextCharFormat::UnderlineStyle underlineStyle,
616 const QColor &color,
const QColor &backgroundColor,
const QColor &decorationColor,
617 int selectionStart,
int selectionEnd)
620 if (color.isValid()) {
621 oldColor = m_textColor;
625 QColor oldBackgroundColor = m_backgroundColor;
626 if (backgroundColor.isValid()) {
627 oldBackgroundColor = m_backgroundColor;
628 m_backgroundColor = backgroundColor;
631 QColor oldDecorationColor = m_decorationColor;
632 if (decorationColor.isValid()) {
633 oldDecorationColor = m_decorationColor;
634 m_decorationColor = decorationColor;
637 QTextCharFormat::UnderlineStyle oldUnderlineStyle = m_underlineStyle;
638 m_underlineStyle = underlineStyle;
640 bool hasSelection = selectionEnd >= 0
641 && selectionStart <= selectionEnd;
643 QTextLine &line = m_currentLine;
644 int rangeEnd = rangeStart + rangeLength;
645 if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) {
646 const QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
647 for (
const QGlyphRun &glyphRun : glyphRuns)
648 addUnselectedGlyphs(glyphRun);
650 if (rangeStart < selectionStart) {
651 int length = qMin(selectionStart - rangeStart, rangeLength);
652 const QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, length);
653 for (
const QGlyphRun &glyphRun : glyphRuns)
654 addUnselectedGlyphs(glyphRun);
657 if (rangeEnd > selectionStart) {
658 int start = qMax(selectionStart, rangeStart);
659 int length = qMin(selectionEnd - start + 1, rangeEnd - start);
660 const QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
661 for (
const QGlyphRun &glyphRun : glyphRuns)
662 addSelectedGlyphs(glyphRun);
665 if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
666 int start = selectionEnd + 1;
667 int length = rangeEnd - selectionEnd - 1;
668 const QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
669 for (
const QGlyphRun &glyphRun : glyphRuns)
670 addUnselectedGlyphs(glyphRun);
674 if (decorationColor.isValid())
675 m_decorationColor = oldDecorationColor;
677 if (backgroundColor.isValid())
678 m_backgroundColor = oldBackgroundColor;
680 if (oldColor.isValid())
681 m_textColor = oldColor;
683 m_underlineStyle = oldUnderlineStyle;
687 QTextFrameFormat::BorderStyle borderStyle,
688 const QBrush &borderBrush)
690 const QColor &color = borderBrush.color();
693 Q_UNUSED(borderStyle);
695 m_backgrounds.append(std::make_pair(QRectF(rect.left(), rect.top(), border, rect.height() + border), color));
696 m_backgrounds.append(std::make_pair(QRectF(rect.left() + border, rect.top(), rect.width(), border), color));
697 m_backgrounds.append(std::make_pair(QRectF(rect.right(), rect.top() + border, border, rect.height() - border), color));
698 m_backgrounds.append(std::make_pair(QRectF(rect.left() + border, rect.bottom(), rect.width(), border), color));
703 QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(document->documentLayout());
704 if (Q_UNLIKELY(!documentLayout))
707 QTextFrameFormat frameFormat = frame->format().toFrameFormat();
708 QTextTable *table = qobject_cast<QTextTable *>(frame);
710 QRectF boundingRect = table ==
nullptr
711 ? documentLayout->frameBoundingRect(frame)
712 : documentLayout->tableBoundingRect(table);
714 QBrush bg = frame->frameFormat().background();
715 if (bg.style() != Qt::NoBrush)
716 m_backgrounds.append(std::make_pair(boundingRect, bg.color()));
718 if (!frameFormat.hasProperty(QTextFormat::FrameBorder))
721 qreal borderWidth = frameFormat.border();
722 if (qFuzzyIsNull(borderWidth))
725 QBrush borderBrush = frameFormat.borderBrush();
726 QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle();
727 if (borderStyle == QTextFrameFormat::BorderStyle_None)
730 const auto collapsed = table->format().borderCollapse();
733 addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
734 -frameFormat.rightMargin() - borderWidth,
735 -frameFormat.bottomMargin() - borderWidth),
736 borderWidth, borderStyle, borderBrush);
738 if (table !=
nullptr) {
739 int rows = table->rows();
740 int columns = table->columns();
742 for (
int row=0; row<rows; ++row) {
743 for (
int column=0; column<columns; ++column) {
744 QTextTableCell cell = table->cellAt(row, column);
746 QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
747 addBorder(cellRect.adjusted(-borderWidth, -borderWidth, collapsed ? -borderWidth : 0, collapsed ? -borderWidth : 0), borderWidth,
748 borderStyle, borderBrush);
756 return qHashMulti(seed, key.fontEngine, key.clipNode, key.color, key
.selectionState);
760 QList<BinaryTreeNode *> *imageNodes)
762 QHash<BinaryTreeNodeKey, QList<BinaryTreeNode *> > map;
764 for (
int i = 0; i < m_processedNodes.size(); ++i) {
767 if (node->image.isNull()) {
768 if (node->glyphRun.isEmpty())
773 QList<BinaryTreeNode *> &nodes = map[key];
775 regularNodes->append(node);
779 imageNodes->append(node);
783 for (
int i = 0; i < regularNodes->size(); ++i) {
787 const QList<BinaryTreeNode *> &nodes = map.value(key);
788 Q_ASSERT(nodes.first() == primaryNode);
791 for (
int j = 0; j < nodes.size(); ++j)
792 count += nodes.at(j)->glyphRun.glyphIndexes().size();
794 if (count != primaryNode->glyphRun.glyphIndexes().size()) {
795 QGlyphRun &glyphRun = primaryNode->glyphRun;
796 QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
797 glyphIndexes.reserve(count);
799 QList<QPointF> glyphPositions = glyphRun.positions();
800 glyphPositions.reserve(count);
802 QRectF glyphBoundingRect = glyphRun.boundingRect();
804 for (
int j = 1; j < nodes.size(); ++j) {
806 glyphIndexes += otherNode->glyphRun.glyphIndexes();
807 primaryNode->ranges += otherNode->ranges;
808 glyphBoundingRect = glyphBoundingRect.united(otherNode->boundingRect);
810 QList<QPointF> otherPositions = otherNode->glyphRun.positions();
811 for (
int k = 0; k < otherPositions.size(); ++k)
812 glyphPositions += otherPositions.at(k) + (otherNode->position - primaryNode->position);
815 Q_ASSERT(glyphPositions.size() == count);
816 Q_ASSERT(glyphIndexes.size() == count);
818 glyphRun.setGlyphIndexes(glyphIndexes);
819 glyphRun.setPositions(glyphPositions);
820 glyphRun.setBoundingRect(glyphBoundingRect);
826 QQuickText::TextStyle style,
827 const QColor &styleColor)
829 if (m_currentLine.isValid())
830 processCurrentLine();
832 QList<BinaryTreeNode *> nodes;
833 QList<BinaryTreeNode *> imageNodes;
834 mergeProcessedNodes(&nodes, &imageNodes);
836 for (
int i = 0; i < m_backgrounds.size(); ++i) {
837 const QRectF &rect = m_backgrounds.at(i).first;
838 const QColor &color = m_backgrounds.at(i).second;
839 if (color.alpha() != 0)
840 parentNode->addRectangleNode(rect, color);
844 for (
int i = 0; i < nodes.size(); ++i) {
845 const BinaryTreeNode *node = nodes.at(i);
846 parentNode->addGlyphs(node->position, node->glyphRun, node->color, style, styleColor,
nullptr);
849 for (
int i = 0; i < imageNodes.size(); ++i) {
850 const BinaryTreeNode *node = imageNodes.at(i);
851 if (node->selectionState == Unselected)
852 parentNode->addImage(node->boundingRect, node->image);
856 for (
int i = 0; i < m_selectionRects.size(); ++i) {
857 const QRectF &rect = m_selectionRects.at(i);
858 if (m_selectionColor.alpha() != 0)
859 parentNode->addRectangleNode(rect, m_selectionColor);
863 for (
int i = 0; i < m_lines.size(); ++i) {
864 const TextDecoration &textDecoration = m_lines.at(i);
866 QColor color = textDecoration.selectionState == Selected
867 ? m_selectedTextColor
868 : textDecoration.color;
870 parentNode->addDecorationNode(textDecoration.rect, color,
871 resolveUnderlineStyle(textDecoration.underlineStyle));
875 for (
int i = 0; i < nodes.size(); ++i) {
876 const BinaryTreeNode *node = nodes.at(i);
877 QQuickDefaultClipNode *clipNode = node->clipNode;
878 if (clipNode !=
nullptr && clipNode->parent() ==
nullptr)
879 parentNode->appendChildNode(clipNode);
881 if (node->selectionState == Selected) {
882 QColor color = m_selectedTextColor;
883 int previousNodeIndex = i - 1;
884 int nextNodeIndex = i + 1;
885 const BinaryTreeNode *previousNode = previousNodeIndex < 0 ? 0 : nodes.at(previousNodeIndex);
886 while (previousNode !=
nullptr && qFuzzyCompare(previousNode->boundingRect.left(), node->boundingRect.left()))
887 previousNode = --previousNodeIndex < 0 ? 0 : nodes.at(previousNodeIndex);
889 const BinaryTreeNode *nextNode = nextNodeIndex == nodes.size() ? 0 : nodes.at(nextNodeIndex);
891 if (previousNode !=
nullptr && previousNode->selectionState == Unselected)
892 parentNode->addGlyphs(previousNode->position, previousNode->glyphRun, color, style, styleColor, clipNode);
894 if (nextNode !=
nullptr && nextNode->selectionState == Unselected)
895 parentNode->addGlyphs(nextNode->position, nextNode->glyphRun, color, style, styleColor, clipNode);
899 bool drawCurrent =
false;
900 if (previousNode !=
nullptr || nextNode !=
nullptr) {
906 auto subtractOverlap = [](
int rangeLength,
const std::pair<
int,
int> &range,
907 const BinaryTreeNode *otherNode,
const QPointF &nodePos) {
908 if (otherNode->position != nodePos)
910 for (
int j = 0; j < otherNode->ranges.size(); ++j) {
911 const std::pair<
int,
int> &otherRange = otherNode->ranges.at(j);
912 if (range.first < otherRange.second && range.second > otherRange.first) {
913 int start = qMax(range.first, otherRange.first);
914 int end = qMin(range.second, otherRange.second);
915 rangeLength -= end - start + 1;
916 if (rangeLength == 0)
923 for (
int i = 0; i < node->ranges.size(); ++i) {
924 const std::pair<
int,
int> &range = node->ranges.at(i);
926 int rangeLength = range.second - range.first + 1;
927 if (previousNode !=
nullptr)
928 rangeLength = subtractOverlap(rangeLength, range, previousNode, node->position);
929 if (nextNode !=
nullptr && rangeLength > 0)
930 rangeLength = subtractOverlap(rangeLength, range, nextNode, node->position);
932 if (rangeLength > 0) {
942 parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
946 for (
int i = 0; i < imageNodes.size(); ++i) {
947 const BinaryTreeNode *node = imageNodes.at(i);
948 if (node->selectionState == Selected) {
949 parentNode->addImage(node->boundingRect, node->image);
950 if (node->selectionState == Selected) {
951 QColor color = m_selectionColor;
953 parentNode->addRectangleNode(node->boundingRect, color);
959void QQuickTextNodeEngine::mergeFormats(QTextLayout *textLayout, QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
961 Q_ASSERT(mergedFormats !=
nullptr);
962 if (textLayout ==
nullptr)
965 const QList<QTextLayout::FormatRange> additionalFormats = textLayout->formats();
966 for (QTextLayout::FormatRange additionalFormat : additionalFormats) {
967 if (additionalFormat.format.hasProperty(QTextFormat::ForegroundBrush)
968 || additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)
969 || additionalFormat.format.hasProperty(QTextFormat::TextUnderlineStyle)
970 || additionalFormat.format.hasProperty(QTextFormat::TextUnderlineColor)
971 || additionalFormat.format.isAnchor()) {
973 if (!mergedFormats->isEmpty()) {
974 QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1;
976 if (additionalFormat.start < lastFormat->start + lastFormat->length) {
977 QTextLayout::FormatRange *mergedRange =
nullptr;
979 int length = additionalFormat.length;
980 if (additionalFormat.start > lastFormat->start) {
981 lastFormat->length = additionalFormat.start - lastFormat->start;
982 length -= lastFormat->length;
984 mergedFormats->append(QTextLayout::FormatRange());
985 mergedRange = mergedFormats->data() + mergedFormats->size() - 1;
986 lastFormat = mergedFormats->data() + mergedFormats->size() - 2;
988 mergedRange = lastFormat;
991 mergedRange->format = lastFormat->format;
992 mergedRange->format.merge(additionalFormat.format);
993 mergedRange->start = additionalFormat.start;
995 int end = qMin(additionalFormat.start + additionalFormat.length,
996 lastFormat->start + lastFormat->length);
998 mergedRange->length = end - mergedRange->start;
999 length -= mergedRange->length;
1001 additionalFormat.start = end;
1002 additionalFormat.length = length;
1006 if (additionalFormat.length > 0)
1007 mergedFormats->append(additionalFormat);
1013
1014
1015
1016
1017
1018
1019
1020
1022 const QColor &textColor,
const QColor &anchorColor,
int selectionStart,
int selectionEnd,
const QRectF &viewport)
1024 Q_ASSERT(textDocument);
1026 int preeditLength = block.isValid() ? block.layout()->preeditAreaText().size() : 0;
1027 int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
1030 setCurrentTextDirection(block.textDirection());
1032 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1033 mergeFormats(block.layout(), &colorChanges);
1035 const QTextCharFormat charFormat = block.charFormat();
1036 const QRectF blockBoundingRect = textDocument->documentLayout()->blockBoundingRect(block).translated(position);
1037 if (viewport.isValid()) {
1038 if (!blockBoundingRect.intersects(viewport))
1040 qCDebug(lcSgText) <<
"adding block with length" << block.length() <<
':' << blockBoundingRect <<
"in viewport" << viewport;
1043 if (charFormat.background().style() != Qt::NoBrush)
1044 m_backgrounds.append(std::make_pair(blockBoundingRect, charFormat.background().color()));
1046 if (QTextList *textList = block.textList()) {
1047 QPointF pos = blockBoundingRect.topLeft();
1048 QTextLayout *layout = block.layout();
1049 if (layout->lineCount() > 0) {
1050 QTextLine firstLine = layout->lineAt(0);
1051 Q_ASSERT(firstLine.isValid());
1055 QRectF textRect = firstLine.naturalTextRect();
1056 pos += textRect.topLeft();
1057 if (block.textDirection() == Qt::RightToLeft)
1058 pos.rx() += textRect.width();
1060 QFont font(charFormat.font());
1061 QFontMetricsF fontMetrics(font);
1062 QTextListFormat listFormat = textList->format();
1064 QString listItemBullet;
1065 switch (listFormat.style()) {
1066 case QTextListFormat::ListCircle:
1067 listItemBullet = QChar(0x25E6);
1069 case QTextListFormat::ListSquare:
1070 listItemBullet = QChar(0x25AA);
1072 case QTextListFormat::ListDecimal:
1073 case QTextListFormat::ListLowerAlpha:
1074 case QTextListFormat::ListUpperAlpha:
1075 case QTextListFormat::ListLowerRoman:
1076 case QTextListFormat::ListUpperRoman:
1077 listItemBullet = textList->itemText(block);
1080 listItemBullet = QChar(0x2022);
1084 switch (block.blockFormat().marker()) {
1085 case QTextBlockFormat::MarkerType::Checked:
1086 listItemBullet = QChar(0x2612);
1088 case QTextBlockFormat::MarkerType::Unchecked:
1089 listItemBullet = QChar(0x2610);
1091 case QTextBlockFormat::MarkerType::NoMarker:
1095 QSizeF size(fontMetrics.horizontalAdvance(listItemBullet), fontMetrics.height());
1096 qreal xoff = fontMetrics.horizontalAdvance(QLatin1Char(
' '));
1097 if (block.textDirection() == Qt::LeftToRight)
1098 xoff = -xoff - size.width();
1099 setPosition(pos + QPointF(xoff, 0));
1102 layout.setFont(font);
1103 layout.setText(listItemBullet);
1104 layout.beginLayout();
1105 QTextLine line = layout.createLine();
1106 line.setPosition(QPointF(0, 0));
1110 if (charFormat.foreground().style() == Qt::NoBrush)
1115 const QList<QGlyphRun> glyphRuns = layout.glyphRuns();
1116 for (
const auto &e : glyphRuns)
1117 addUnselectedGlyphs(e);
1121 int textPos = block.position();
1122 QTextBlock::iterator blockIterator = block.begin();
1124 while (!blockIterator.atEnd()) {
1125 QTextFragment fragment = blockIterator.fragment();
1126 QString text = fragment.text();
1130 QTextCharFormat charFormat = fragment.charFormat();
1131 QFont font(charFormat.font());
1132 QFontMetricsF fontMetrics(font);
1134 int fontHeight = fontMetrics.descent() + fontMetrics.ascent();
1135 int valign = charFormat.verticalAlignment();
1136 if (valign == QTextCharFormat::AlignSuperScript)
1137 setPosition(QPointF(blockBoundingRect.x(), blockBoundingRect.y() - fontHeight / 2));
1138 else if (valign == QTextCharFormat::AlignSubScript)
1139 setPosition(QPointF(blockBoundingRect.x(), blockBoundingRect.y() + fontHeight / 6));
1141 setPosition(blockBoundingRect.topLeft());
1143 if (text.contains(QChar::ObjectReplacementCharacter) && charFormat.objectType() != QTextFormat::NoObject) {
1144 QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
1145 if (!frame || frame->frameFormat().position() == QTextFrameFormat::InFlow) {
1146 int blockRelativePosition = textPos - block.position();
1147 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
1148 if (!currentLine().isValid()
1149 || line.lineNumber() != currentLine().lineNumber()) {
1153 QQuickTextNodeEngine::SelectionState selectionState =
1154 (selectionStart < textPos + text.size()
1155 && selectionEnd >= textPos)
1156 ? QQuickTextNodeEngine::Selected
1157 : QQuickTextNodeEngine::Unselected;
1159 addTextObject(block, QPointF(), charFormat, selectionState, textDocument, textPos);
1161 textPos += text.size();
1163 if (charFormat.foreground().style() != Qt::NoBrush)
1165 else if (charFormat.isAnchor())
1170 int fragmentEnd = textPos + fragment.length();
1172 if (preeditPosition >= 0
1173 && (preeditPosition + block.position()) >= textPos
1174 && (preeditPosition + block.position()) <= fragmentEnd) {
1175 fragmentEnd += preeditLength;
1178 if (charFormat.background().style() != Qt::NoBrush
1179 || charFormat.hasProperty(QTextFormat::TextUnderlineColor)
1180 || charFormat.hasProperty(QTextFormat::TextUnderlineStyle)) {
1181 QTextLayout::FormatRange additionalFormat;
1182 additionalFormat.start = textPos - block.position();
1183 additionalFormat.length = fragmentEnd - textPos;
1184 additionalFormat.format = charFormat;
1185 colorChanges << additionalFormat;
1188 textPos = addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd,
1189 selectionStart, selectionEnd);
1196 if (preeditLength >= 0 && textPos <= block.position() + preeditPosition) {
1197 setPosition(blockBoundingRect.topLeft());
1198 textPos = block.position() + preeditPosition;
1199 QTextLine line = block.layout()->lineForTextPosition(preeditPosition);
1200 if (!currentLine().isValid()
1201 || line.lineNumber() != currentLine().lineNumber()) {
1202 setCurrentLine(line);
1204 textPos = addText(block, block.charFormat(), textColor, colorChanges,
1205 textPos, textPos + preeditLength,
1206 selectionStart, selectionEnd);
1211 if (block.blockFormat().hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
1212 auto ruleLength = qvariant_cast<QTextLength>(block.blockFormat().property(QTextFormat::BlockTrailingHorizontalRulerWidth));
1213 QRectF ruleRect(0, 0, ruleLength.value(blockBoundingRect.width()), 1);
1214 ruleRect.moveCenter(blockBoundingRect.center());
1215 const QColor ruleColor = block.blockFormat().hasProperty(QTextFormat::BackgroundBrush)
1216 ? qvariant_cast<QBrush>(block.blockFormat().property(QTextFormat::BackgroundBrush)).color()
1218 m_lines.append(TextDecoration(QQuickTextNodeEngine::Unselected, ruleRect, ruleColor));
1222 m_hasContents =
true;
void addSelectedGlyphs(const QGlyphRun &glyphRun)
void addUnselectedGlyphs(const QGlyphRun &glyphRun)
void addTextObject(const QTextBlock &block, const QPointF &position, const QTextCharFormat &format, SelectionState selectionState, QTextDocument *textDocument, int pos, QTextFrameFormat::Position layoutPosition=QTextFrameFormat::InFlow)
void mergeProcessedNodes(QList< BinaryTreeNode * > *regularNodes, QList< BinaryTreeNode * > *imageNodes)
void addTextBlock(QTextDocument *, const QTextBlock &, const QPointF &position, const QColor &textColor, const QColor &anchorColor, int selectionStart, int selectionEnd, const QRectF &viewport=QRectF())
void setTextColor(const QColor &textColor)
void setCurrentLine(const QTextLine ¤tLine)
int addText(const QTextBlock &block, const QTextCharFormat &charFormat, const QColor &textColor, const QVarLengthArray< QTextLayout::FormatRange > &colorChanges, int textPos, int fragmentEnd, int selectionStart, int selectionEnd)
void addImage(const QRectF &rect, const QImage &image, qreal ascent, SelectionState selectionState, QTextFrameFormat::Position layoutPosition)
void addBorder(const QRectF &rect, qreal border, QTextFrameFormat::BorderStyle borderStyle, const QBrush &borderBrush)
void addGlyphsInRange(int rangeStart, int rangeEnd, QTextCharFormat::UnderlineStyle underlineStyle, const QColor &color, const QColor &backgroundColor, const QColor &underlineColor, int selectionStart, int selectionEnd)
void addFrameDecorations(QTextDocument *document, QTextFrame *frame)
void addGlyphsForRanges(const QVarLengthArray< QTextLayout::FormatRange > &ranges, int start, int end, int selectionStart, int selectionEnd)
Combined button and popup list for selecting options.
size_t qHash(const QQuickTextNodeEngine::BinaryTreeNodeKey &key, size_t seed=0)
static QT_BEGIN_NAMESPACE QTextCharFormat::UnderlineStyle resolveUnderlineStyle(QTextCharFormat::UnderlineStyle style)
BinaryTreeNodeKey(BinaryTreeNode *node)
BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect, const Decorations &decs, QTextCharFormat::UnderlineStyle us, const QColor &c, const QColor &bc, const QColor &dc, const QPointF &pos, qreal a)