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>
28 : fontEngine(QRawFontPrivate::get(node->glyphRun.rawFont())->fontEngine)
29 , clipNode(node->clipNode)
30 , color(node->color.rgba())
31 , selectionState(node->selectionState)
36 SelectionState selState,
38 const Decorations &decs,
40 const QColor &bc,
const QColor &dc,
41 const QPointF &pos, qreal a)
56 ranges.append(std::make_pair(d->textRangeStart, d->textRangeEnd));
61 Decorations decorations,
const QColor &textColor,
62 const QColor &backgroundColor,
const QColor &decorationColor,
const QPointF &position)
64 QRectF searchRect = glyphRun.boundingRect();
65 searchRect.translate(position);
67 if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
75 qreal ascent = glyphRun.rawFont().ascent();
76 insert(binaryTree, BinaryTreeNode(glyphRun,
89 int newIndex = binaryTree->size();
90 binaryTree->append(binaryTreeNode);
96 BinaryTreeNode *node = binaryTree->data() + searchIndex;
97 if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) {
98 if (node->leftChildIndex < 0) {
99 node->leftChildIndex = newIndex;
102 searchIndex = node->leftChildIndex;
105 if (node->rightChildIndex < 0) {
106 node->rightChildIndex = newIndex;
109 searchIndex = node->rightChildIndex;
116 QVarLengthArray<
int> *sortedIndexes,
int currentIndex)
118 Q_ASSERT(currentIndex < binaryTree.size());
121 if (node->leftChildIndex >= 0)
122 inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
124 sortedIndexes->append(currentIndex);
126 if (node->rightChildIndex >= 0)
127 inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
132 const QTextCharFormat &charFormat,
133 const QColor &textColor,
134 const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
135 int textPos,
int fragmentEnd,
136 int selectionStart,
int selectionEnd)
138 if (charFormat.foreground().style() != Qt::NoBrush)
143 while (textPos < fragmentEnd) {
144 int blockRelativePosition = textPos - block.position();
145 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
148 if (!currentLine().isValid()
149 || line.lineNumber() != currentLine().lineNumber()) {
153 Q_ASSERT(line.textLength() > 0);
154 int lineEnd = line.textStart() + block.position() + line.textLength();
156 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
159 int currentStepEnd = textPos + len;
161 addGlyphsForRanges(colorChanges,
162 textPos - block.position(),
163 currentStepEnd - block.position(),
164 selectionStart - block.position(),
165 selectionEnd - block.position());
167 textPos = currentStepEnd;
172void QQuickTextNodeEngine::addTextDecorations(
const QVarLengthArray<TextDecoration> &textDecorations,
173 qreal offset, qreal thickness)
175 for (
auto textDecoration : textDecorations) {
176 QRectF &rect = textDecoration.rect;
177 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
178 rect.setHeight(thickness);
180 m_lines.append(textDecoration);
187 if (m_currentLineTree.isEmpty())
196 QVarLengthArray<
int> sortedIndexes;
197 BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
199 Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
201 SelectionState currentSelectionState = Unselected;
205 qreal underlineOffset = 0.0;
206 qreal underlineThickness = 0.0;
208 qreal overlineOffset = 0.0;
209 qreal overlineThickness = 0.0;
211 qreal strikeOutOffset = 0.0;
212 qreal strikeOutThickness = 0.0;
214 QRectF decorationRect = currentRect;
217 QColor lastBackgroundColor;
218 QColor lastDecorationColor;
220 QVarLengthArray<TextDecoration> pendingUnderlines;
221 QVarLengthArray<TextDecoration> pendingOverlines;
222 QVarLengthArray<TextDecoration> pendingStrikeOuts;
223 if (!sortedIndexes.isEmpty()) {
224 QQuickDefaultClipNode *currentClipNode = m_hasSelection ?
new QQuickDefaultClipNode(QRectF()) :
nullptr;
225 bool currentClipNodeUsed =
false;
226 for (
int i=0; i<=sortedIndexes.size(); ++i) {
228 if (i < sortedIndexes.size()) {
229 int sortedIndex = sortedIndexes.at(i);
230 Q_ASSERT(sortedIndex < m_currentLineTree.size());
232 node = m_currentLineTree.data() + sortedIndex;
234 currentSelectionState = node->selectionState;
239 decorationRect.setY(m_position.y() + m_currentLine.y());
240 decorationRect.setHeight(m_currentLine.height());
243 decorationRect.setRight(node->boundingRect.left());
245 TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
246 if (lastDecorationColor.isValid() &&
250 textDecoration.color = lastDecorationColor;
253 pendingUnderlines.append(textDecoration);
256 pendingOverlines.append(textDecoration);
259 pendingStrikeOuts.append(textDecoration);
261 if (currentDecorations & Decoration::Background)
262 m_backgrounds.append(std::make_pair(decorationRect, lastBackgroundColor));
268 if (node ==
nullptr || node->selectionState != currentSelectionState) {
269 currentRect.setY(m_position.y() + m_currentLine.y());
270 currentRect.setHeight(m_currentLine.height());
272 if (currentSelectionState == Selected)
273 m_selectionRects.append(currentRect);
275 if (currentClipNode !=
nullptr) {
276 if (!currentClipNodeUsed) {
277 delete currentClipNode;
279 currentClipNode->setIsRectangular(
true);
280 currentClipNode->setRect(currentRect);
281 currentClipNode->update();
285 if (node !=
nullptr && m_hasSelection)
286 currentClipNode =
new QQuickDefaultClipNode(QRectF());
288 currentClipNode =
nullptr;
289 currentClipNodeUsed =
false;
291 if (node !=
nullptr) {
292 currentSelectionState = node->selectionState;
293 currentRect = node->boundingRect;
296 if (currentRect.isNull())
297 currentRect.setSize(QSizeF(1, 1));
300 if (currentRect.isNull())
301 currentRect = node->boundingRect;
303 currentRect = currentRect.united(node->boundingRect);
306 if (node !=
nullptr) {
307 if (node->selectionState == Selected) {
308 node->clipNode = currentClipNode;
309 currentClipNodeUsed =
true;
312 decorationRect = node->boundingRect;
316 if (!pendingUnderlines.isEmpty()
318 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
320 pendingUnderlines.clear();
322 underlineOffset = 0.0;
323 underlineThickness = 0.0;
328 if (!pendingOverlines.isEmpty()) {
329 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
331 pendingOverlines.clear();
333 overlineOffset = 0.0;
334 overlineThickness = 0.0;
339 if (!pendingStrikeOuts.isEmpty()) {
340 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
342 pendingStrikeOuts.clear();
344 strikeOutOffset = 0.0;
345 strikeOutThickness = 0.0;
349 QRawFont rawFont = node->glyphRun.rawFont();
351 if (rawFont.lineThickness() > underlineThickness) {
352 underlineThickness = rawFont.lineThickness();
353 underlineOffset = rawFont.underlinePosition();
358 overlineOffset = -rawFont.ascent();
359 overlineThickness = rawFont.lineThickness();
363 strikeOutThickness = rawFont.lineThickness();
364 strikeOutOffset = rawFont.ascent() / -3.0;
367 currentDecorations = node->decorations;
368 lastColor = node->color;
369 lastBackgroundColor = node->backgroundColor;
370 lastDecorationColor = node->decorationColor;
371 m_processedNodes.append(*node);
375 if (!pendingUnderlines.isEmpty())
376 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
378 if (!pendingOverlines.isEmpty())
379 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
381 if (!pendingStrikeOuts.isEmpty())
382 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
385 m_currentLineTree.clear();
386 m_currentLine = QTextLine();
387 m_hasSelection =
false;
391 SelectionState selectionState,
392 QTextFrameFormat::Position layoutPosition)
394 QRectF searchRect = rect;
395 if (layoutPosition == QTextFrameFormat::InFlow) {
396 if (m_currentLineTree.isEmpty()) {
397 qreal y = m_currentLine.ascent() - ascent;
398 if (m_currentTextDirection == Qt::RightToLeft)
399 searchRect.moveTopRight(m_position + m_currentLine.rect().topRight() + QPointF(0, y));
401 searchRect.moveTopLeft(m_position + m_currentLine.position() + QPointF(0, y));
403 const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1;
404 if (lastNode->glyphRun.isRightToLeft()) {
405 QPointF lastPos = lastNode->boundingRect.topLeft();
406 searchRect.moveTopRight(lastPos - QPointF(0, ascent - lastNode->ascent));
408 QPointF lastPos = lastNode->boundingRect.topRight();
409 searchRect.moveTopLeft(lastPos - QPointF(0, ascent - lastNode->ascent));
414 BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
415 m_hasContents =
true;
419 SelectionState selectionState,
420 QTextDocument *textDocument,
int pos,
421 QTextFrameFormat::Position layoutPosition)
423 QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType());
424 if (handler !=
nullptr) {
426 QSizeF size = handler->intrinsicSize(textDocument, pos, format);
428 if (format.objectType() == QTextFormat::ImageObject) {
429 QTextImageFormat imageFormat = format.toImageFormat();
430 QTextImageHandler *imageHandler =
static_cast<QTextImageHandler *>(handler);
431 image = imageHandler->image(textDocument, imageFormat);
434 if (image.isNull()) {
435 if (QImageIOHandler::allocateImage((size * m_devicePixelRatio).toSize(),
436 QImage::Format_ARGB32_Premultiplied,
438 image.setDevicePixelRatio(m_devicePixelRatio);
439 image.fill(Qt::transparent);
441 QPainter painter(&image);
442 handler->drawObject(&painter, QRectF({}, size), textDocument, pos, format);
451 QTextLine line = block.layout()->lineForTextPosition(pos - block.position());
452 switch (format.verticalAlignment())
454 case QTextCharFormat::AlignTop:
455 ascent = line.ascent();
457 case QTextCharFormat::AlignMiddle:
459 ascent = (line.ascent() + line.descent()) / 2 - line.descent() + size.height() / 2;
461 case QTextCharFormat::AlignBottom:
462 ascent = size.height() - line.descent();
464 case QTextCharFormat::AlignBaseline: {
465 QFontMetrics m(format.font());
466 ascent = size.height() - m.descent();
470 ascent = size.height();
473 addImage(QRectF(position, size), image, ascent, selectionState, layoutPosition);
479 BinaryTreeNode::insert(&m_currentLineTree,
482 Decoration::NoDecoration,
491 int currentSize = m_currentLineTree.size();
492 BinaryTreeNode::insert(&m_currentLineTree,
495 Decoration::NoDecoration,
500 m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
505 int selectionStart,
int selectionEnd)
507 int currentPosition = start;
508 int remainingLength = end - start;
509 for (
const QTextLayout::FormatRange &range : ranges) {
510 if (range.start + range.length > currentPosition
511 && range.start < currentPosition + remainingLength) {
513 if (range.start > currentPosition) {
514 addGlyphsInRange(currentPosition, range.start - currentPosition,
515 QColor(), QColor(), QColor(), selectionStart, selectionEnd);
517 int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
519 if (range.format.hasProperty(QTextFormat::ForegroundBrush))
520 rangeColor = range.format.foreground().color();
521 else if (range.format.isAnchor())
522 rangeColor = m_anchorColor;
523 QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush)
524 ? range.format.background().color()
527 QColor rangeDecorationColor = range.format.hasProperty(QTextFormat::TextUnderlineColor)
528 ? range.format.underlineColor()
531 addGlyphsInRange(range.start, rangeEnd - range.start,
532 rangeColor, rangeBackgroundColor, rangeDecorationColor,
533 selectionStart, selectionEnd);
535 currentPosition = range.start + range.length;
536 remainingLength = end - currentPosition;
538 }
else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
543 if (remainingLength > 0) {
545 selectionStart
, selectionEnd
);
551 const QColor &color,
const QColor &backgroundColor,
const QColor &decorationColor,
552 int selectionStart,
int selectionEnd)
555 if (color.isValid()) {
556 oldColor = m_textColor;
560 QColor oldBackgroundColor = m_backgroundColor;
561 if (backgroundColor.isValid()) {
562 oldBackgroundColor = m_backgroundColor;
563 m_backgroundColor = backgroundColor;
566 QColor oldDecorationColor = m_decorationColor;
567 if (decorationColor.isValid()) {
568 oldDecorationColor = m_decorationColor;
569 m_decorationColor = decorationColor;
572 bool hasSelection = selectionEnd >= 0
573 && selectionStart <= selectionEnd;
575 QTextLine &line = m_currentLine;
576 int rangeEnd = rangeStart + rangeLength;
577 if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) {
578 const QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
579 for (
const QGlyphRun &glyphRun : glyphRuns)
580 addUnselectedGlyphs(glyphRun);
582 if (rangeStart < selectionStart) {
583 int length = qMin(selectionStart - rangeStart, rangeLength);
584 const QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, length);
585 for (
const QGlyphRun &glyphRun : glyphRuns)
586 addUnselectedGlyphs(glyphRun);
589 if (rangeEnd > selectionStart) {
590 int start = qMax(selectionStart, rangeStart);
591 int length = qMin(selectionEnd - start + 1, rangeEnd - start);
592 const QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
593 for (
const QGlyphRun &glyphRun : glyphRuns)
594 addSelectedGlyphs(glyphRun);
597 if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
598 int start = selectionEnd + 1;
599 int length = rangeEnd - selectionEnd - 1;
600 const QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
601 for (
const QGlyphRun &glyphRun : glyphRuns)
602 addUnselectedGlyphs(glyphRun);
606 if (decorationColor.isValid())
607 m_decorationColor = oldDecorationColor;
609 if (backgroundColor.isValid())
610 m_backgroundColor = oldBackgroundColor;
612 if (oldColor.isValid())
613 m_textColor = oldColor;
617 QTextFrameFormat::BorderStyle borderStyle,
618 const QBrush &borderBrush)
620 const QColor &color = borderBrush.color();
623 Q_UNUSED(borderStyle);
625 m_backgrounds.append(std::make_pair(QRectF(rect.left(), rect.top(), border, rect.height() + border), color));
626 m_backgrounds.append(std::make_pair(QRectF(rect.left() + border, rect.top(), rect.width(), border), color));
627 m_backgrounds.append(std::make_pair(QRectF(rect.right(), rect.top() + border, border, rect.height() - border), color));
628 m_backgrounds.append(std::make_pair(QRectF(rect.left() + border, rect.bottom(), rect.width(), border), color));
633 QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(document->documentLayout());
634 if (Q_UNLIKELY(!documentLayout))
637 QTextFrameFormat frameFormat = frame->format().toFrameFormat();
638 QTextTable *table = qobject_cast<QTextTable *>(frame);
640 QRectF boundingRect = table ==
nullptr
641 ? documentLayout->frameBoundingRect(frame)
642 : documentLayout->tableBoundingRect(table);
644 QBrush bg = frame->frameFormat().background();
645 if (bg.style() != Qt::NoBrush)
646 m_backgrounds.append(std::make_pair(boundingRect, bg.color()));
648 if (!frameFormat.hasProperty(QTextFormat::FrameBorder))
651 qreal borderWidth = frameFormat.border();
652 if (qFuzzyIsNull(borderWidth))
655 QBrush borderBrush = frameFormat.borderBrush();
656 QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle();
657 if (borderStyle == QTextFrameFormat::BorderStyle_None)
660 const auto collapsed = table->format().borderCollapse();
663 addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
664 -frameFormat.rightMargin() - borderWidth,
665 -frameFormat.bottomMargin() - borderWidth),
666 borderWidth, borderStyle, borderBrush);
668 if (table !=
nullptr) {
669 int rows = table->rows();
670 int columns = table->columns();
672 for (
int row=0; row<rows; ++row) {
673 for (
int column=0; column<columns; ++column) {
674 QTextTableCell cell = table->cellAt(row, column);
676 QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
677 addBorder(cellRect.adjusted(-borderWidth, -borderWidth, collapsed ? -borderWidth : 0, collapsed ? -borderWidth : 0), borderWidth,
678 borderStyle, borderBrush);
686 return qHashMulti(seed, key.fontEngine, key.clipNode, key.color, key
.selectionState);
690 QList<BinaryTreeNode *> *imageNodes)
692 QHash<BinaryTreeNodeKey, QList<BinaryTreeNode *> > map;
694 for (
int i = 0; i < m_processedNodes.size(); ++i) {
697 if (node->image.isNull()) {
698 if (node->glyphRun.isEmpty())
703 QList<BinaryTreeNode *> &nodes = map[key];
705 regularNodes->append(node);
709 imageNodes->append(node);
713 for (
int i = 0; i < regularNodes->size(); ++i) {
717 const QList<BinaryTreeNode *> &nodes = map.value(key);
718 Q_ASSERT(nodes.first() == primaryNode);
721 for (
int j = 0; j < nodes.size(); ++j)
722 count += nodes.at(j)->glyphRun.glyphIndexes().size();
724 if (count != primaryNode->glyphRun.glyphIndexes().size()) {
725 QGlyphRun &glyphRun = primaryNode->glyphRun;
726 QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
727 glyphIndexes.reserve(count);
729 QList<QPointF> glyphPositions = glyphRun.positions();
730 glyphPositions.reserve(count);
732 QRectF glyphBoundingRect = glyphRun.boundingRect();
734 for (
int j = 1; j < nodes.size(); ++j) {
736 glyphIndexes += otherNode->glyphRun.glyphIndexes();
737 primaryNode->ranges += otherNode->ranges;
738 glyphBoundingRect = glyphBoundingRect.united(otherNode->boundingRect);
740 QList<QPointF> otherPositions = otherNode->glyphRun.positions();
741 for (
int k = 0; k < otherPositions.size(); ++k)
742 glyphPositions += otherPositions.at(k) + (otherNode->position - primaryNode->position);
745 Q_ASSERT(glyphPositions.size() == count);
746 Q_ASSERT(glyphIndexes.size() == count);
748 glyphRun.setGlyphIndexes(glyphIndexes);
749 glyphRun.setPositions(glyphPositions);
750 glyphRun.setBoundingRect(glyphBoundingRect);
756 QQuickText::TextStyle style,
757 const QColor &styleColor)
759 if (m_currentLine.isValid())
760 processCurrentLine();
762 QList<BinaryTreeNode *> nodes;
763 QList<BinaryTreeNode *> imageNodes;
764 mergeProcessedNodes(&nodes, &imageNodes);
766 for (
int i = 0; i < m_backgrounds.size(); ++i) {
767 const QRectF &rect = m_backgrounds.at(i).first;
768 const QColor &color = m_backgrounds.at(i).second;
769 if (color.alpha() != 0)
770 parentNode->addRectangleNode(rect, color);
774 for (
int i = 0; i < nodes.size(); ++i) {
775 const BinaryTreeNode *node = nodes.at(i);
776 parentNode->addGlyphs(node->position, node->glyphRun, node->color, style, styleColor,
nullptr);
779 for (
int i = 0; i < imageNodes.size(); ++i) {
780 const BinaryTreeNode *node = imageNodes.at(i);
781 if (node->selectionState == Unselected)
782 parentNode->addImage(node->boundingRect, node->image);
786 for (
int i = 0; i < m_selectionRects.size(); ++i) {
787 const QRectF &rect = m_selectionRects.at(i);
788 if (m_selectionColor.alpha() != 0)
789 parentNode->addRectangleNode(rect, m_selectionColor);
793 for (
int i = 0; i < m_lines.size(); ++i) {
794 const TextDecoration &textDecoration = m_lines.at(i);
796 QColor color = textDecoration.selectionState == Selected
797 ? m_selectedTextColor
798 : textDecoration.color;
800 parentNode->addDecorationNode(textDecoration.rect, color);
804 for (
int i = 0; i < nodes.size(); ++i) {
805 const BinaryTreeNode *node = nodes.at(i);
806 QQuickDefaultClipNode *clipNode = node->clipNode;
807 if (clipNode !=
nullptr && clipNode->parent() ==
nullptr)
808 parentNode->appendChildNode(clipNode);
810 if (node->selectionState == Selected) {
811 QColor color = m_selectedTextColor;
812 int previousNodeIndex = i - 1;
813 int nextNodeIndex = i + 1;
814 const BinaryTreeNode *previousNode = previousNodeIndex < 0 ? 0 : nodes.at(previousNodeIndex);
815 while (previousNode !=
nullptr && qFuzzyCompare(previousNode->boundingRect.left(), node->boundingRect.left()))
816 previousNode = --previousNodeIndex < 0 ? 0 : nodes.at(previousNodeIndex);
818 const BinaryTreeNode *nextNode = nextNodeIndex == nodes.size() ? 0 : nodes.at(nextNodeIndex);
820 if (previousNode !=
nullptr && previousNode->selectionState == Unselected)
821 parentNode->addGlyphs(previousNode->position, previousNode->glyphRun, color, style, styleColor, clipNode);
823 if (nextNode !=
nullptr && nextNode->selectionState == Unselected)
824 parentNode->addGlyphs(nextNode->position, nextNode->glyphRun, color, style, styleColor, clipNode);
828 bool drawCurrent =
false;
829 if (previousNode !=
nullptr || nextNode !=
nullptr) {
830 for (
int i = 0; i < node->ranges.size(); ++i) {
831 const std::pair<
int,
int> &range = node->ranges.at(i);
833 int rangeLength = range.second - range.first + 1;
834 if (previousNode !=
nullptr) {
835 for (
int j = 0; j < previousNode->ranges.size(); ++j) {
836 const std::pair<
int,
int> &otherRange = previousNode->ranges.at(j);
838 if (range.first < otherRange.second && range.second > otherRange.first) {
839 int start = qMax(range.first, otherRange.first);
840 int end = qMin(range.second, otherRange.second);
841 rangeLength -= end - start + 1;
842 if (rangeLength == 0)
848 if (nextNode !=
nullptr && rangeLength > 0) {
849 for (
int j = 0; j < nextNode->ranges.size(); ++j) {
850 const std::pair<
int,
int> &otherRange = nextNode->ranges.at(j);
852 if (range.first < otherRange.second && range.second > otherRange.first) {
853 int start = qMax(range.first, otherRange.first);
854 int end = qMin(range.second, otherRange.second);
855 rangeLength -= end - start + 1;
856 if (rangeLength == 0)
862 if (rangeLength > 0) {
872 parentNode->addGlyphs(node->position, node->glyphRun, color, style, styleColor, clipNode);
876 for (
int i = 0; i < imageNodes.size(); ++i) {
877 const BinaryTreeNode *node = imageNodes.at(i);
878 if (node->selectionState == Selected) {
879 parentNode->addImage(node->boundingRect, node->image);
880 if (node->selectionState == Selected) {
881 QColor color = m_selectionColor;
883 parentNode->addRectangleNode(node->boundingRect, color);
889void QQuickTextNodeEngine::mergeFormats(QTextLayout *textLayout, QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
891 Q_ASSERT(mergedFormats !=
nullptr);
892 if (textLayout ==
nullptr)
895 const QList<QTextLayout::FormatRange> additionalFormats = textLayout->formats();
896 for (QTextLayout::FormatRange additionalFormat : additionalFormats) {
897 if (additionalFormat.format.hasProperty(QTextFormat::ForegroundBrush)
898 || additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)
899 || additionalFormat.format.isAnchor()) {
901 if (!mergedFormats->isEmpty()) {
902 QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1;
904 if (additionalFormat.start < lastFormat->start + lastFormat->length) {
905 QTextLayout::FormatRange *mergedRange =
nullptr;
907 int length = additionalFormat.length;
908 if (additionalFormat.start > lastFormat->start) {
909 lastFormat->length = additionalFormat.start - lastFormat->start;
910 length -= lastFormat->length;
912 mergedFormats->append(QTextLayout::FormatRange());
913 mergedRange = mergedFormats->data() + mergedFormats->size() - 1;
914 lastFormat = mergedFormats->data() + mergedFormats->size() - 2;
916 mergedRange = lastFormat;
919 mergedRange->format = lastFormat->format;
920 mergedRange->format.merge(additionalFormat.format);
921 mergedRange->start = additionalFormat.start;
923 int end = qMin(additionalFormat.start + additionalFormat.length,
924 lastFormat->start + lastFormat->length);
926 mergedRange->length = end - mergedRange->start;
927 length -= mergedRange->length;
929 additionalFormat.start = end;
930 additionalFormat.length = length;
934 if (additionalFormat.length > 0)
935 mergedFormats->append(additionalFormat);
941
942
943
944
945
946
947
948
950 const QColor &textColor,
const QColor &anchorColor,
int selectionStart,
int selectionEnd,
const QRectF &viewport)
952 Q_ASSERT(textDocument);
954 int preeditLength = block.isValid() ? block.layout()->preeditAreaText().size() : 0;
955 int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
958 setCurrentTextDirection(block.textDirection());
960 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
961 mergeFormats(block.layout(), &colorChanges);
963 const QTextCharFormat charFormat = block.charFormat();
964 const QRectF blockBoundingRect = textDocument->documentLayout()->blockBoundingRect(block).translated(position);
965 if (viewport.isValid()) {
966 if (!blockBoundingRect.intersects(viewport))
968 qCDebug(lcSgText) <<
"adding block with length" << block.length() <<
':' << blockBoundingRect <<
"in viewport" << viewport;
971 if (charFormat.background().style() != Qt::NoBrush)
972 m_backgrounds.append(std::make_pair(blockBoundingRect, charFormat.background().color()));
974 if (QTextList *textList = block.textList()) {
975 QPointF pos = blockBoundingRect.topLeft();
976 QTextLayout *layout = block.layout();
977 if (layout->lineCount() > 0) {
978 QTextLine firstLine = layout->lineAt(0);
979 Q_ASSERT(firstLine.isValid());
983 QRectF textRect = firstLine.naturalTextRect();
984 pos += textRect.topLeft();
985 if (block.textDirection() == Qt::RightToLeft)
986 pos.rx() += textRect.width();
988 QFont font(charFormat.font());
989 QFontMetricsF fontMetrics(font);
990 QTextListFormat listFormat = textList->format();
992 QString listItemBullet;
993 switch (listFormat.style()) {
994 case QTextListFormat::ListCircle:
995 listItemBullet = QChar(0x25E6);
997 case QTextListFormat::ListSquare:
998 listItemBullet = QChar(0x25AA);
1000 case QTextListFormat::ListDecimal:
1001 case QTextListFormat::ListLowerAlpha:
1002 case QTextListFormat::ListUpperAlpha:
1003 case QTextListFormat::ListLowerRoman:
1004 case QTextListFormat::ListUpperRoman:
1005 listItemBullet = textList->itemText(block);
1008 listItemBullet = QChar(0x2022);
1012 switch (block.blockFormat().marker()) {
1013 case QTextBlockFormat::MarkerType::Checked:
1014 listItemBullet = QChar(0x2612);
1016 case QTextBlockFormat::MarkerType::Unchecked:
1017 listItemBullet = QChar(0x2610);
1019 case QTextBlockFormat::MarkerType::NoMarker:
1023 QSizeF size(fontMetrics.horizontalAdvance(listItemBullet), fontMetrics.height());
1024 qreal xoff = fontMetrics.horizontalAdvance(QLatin1Char(
' '));
1025 if (block.textDirection() == Qt::LeftToRight)
1026 xoff = -xoff - size.width();
1027 setPosition(pos + QPointF(xoff, 0));
1030 layout.setFont(font);
1031 layout.setText(listItemBullet);
1032 layout.beginLayout();
1033 QTextLine line = layout.createLine();
1034 line.setPosition(QPointF(0, 0));
1038 if (charFormat.foreground().style() == Qt::NoBrush)
1043 const QList<QGlyphRun> glyphRuns = layout.glyphRuns();
1044 for (
const auto &e : glyphRuns)
1045 addUnselectedGlyphs(e);
1049 int textPos = block.position();
1050 QTextBlock::iterator blockIterator = block.begin();
1052 while (!blockIterator.atEnd()) {
1053 QTextFragment fragment = blockIterator.fragment();
1054 QString text = fragment.text();
1058 QTextCharFormat charFormat = fragment.charFormat();
1059 QFont font(charFormat.font());
1060 QFontMetricsF fontMetrics(font);
1062 int fontHeight = fontMetrics.descent() + fontMetrics.ascent();
1063 int valign = charFormat.verticalAlignment();
1064 if (valign == QTextCharFormat::AlignSuperScript)
1065 setPosition(QPointF(blockBoundingRect.x(), blockBoundingRect.y() - fontHeight / 2));
1066 else if (valign == QTextCharFormat::AlignSubScript)
1067 setPosition(QPointF(blockBoundingRect.x(), blockBoundingRect.y() + fontHeight / 6));
1069 setPosition(blockBoundingRect.topLeft());
1071 if (text.contains(QChar::ObjectReplacementCharacter) && charFormat.objectType() != QTextFormat::NoObject) {
1072 QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
1073 if (!frame || frame->frameFormat().position() == QTextFrameFormat::InFlow) {
1074 int blockRelativePosition = textPos - block.position();
1075 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
1076 if (!currentLine().isValid()
1077 || line.lineNumber() != currentLine().lineNumber()) {
1081 QQuickTextNodeEngine::SelectionState selectionState =
1082 (selectionStart < textPos + text.size()
1083 && selectionEnd >= textPos)
1084 ? QQuickTextNodeEngine::Selected
1085 : QQuickTextNodeEngine::Unselected;
1087 addTextObject(block, QPointF(), charFormat, selectionState, textDocument, textPos);
1089 textPos += text.size();
1091 if (charFormat.foreground().style() != Qt::NoBrush)
1093 else if (charFormat.isAnchor())
1098 int fragmentEnd = textPos + fragment.length();
1100 if (preeditPosition >= 0
1101 && (preeditPosition + block.position()) >= textPos
1102 && (preeditPosition + block.position()) <= fragmentEnd) {
1103 fragmentEnd += preeditLength;
1106 if (charFormat.background().style() != Qt::NoBrush || charFormat.hasProperty(QTextFormat::TextUnderlineColor)) {
1107 QTextLayout::FormatRange additionalFormat;
1108 additionalFormat.start = textPos - block.position();
1109 additionalFormat.length = fragmentEnd - textPos;
1110 additionalFormat.format = charFormat;
1111 colorChanges << additionalFormat;
1114 textPos = addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd,
1115 selectionStart, selectionEnd);
1122 if (preeditLength >= 0 && textPos <= block.position() + preeditPosition) {
1123 setPosition(blockBoundingRect.topLeft());
1124 textPos = block.position() + preeditPosition;
1125 QTextLine line = block.layout()->lineForTextPosition(preeditPosition);
1126 if (!currentLine().isValid()
1127 || line.lineNumber() != currentLine().lineNumber()) {
1128 setCurrentLine(line);
1130 textPos = addText(block, block.charFormat(), textColor, colorChanges,
1131 textPos, textPos + preeditLength,
1132 selectionStart, selectionEnd);
1137 if (block.blockFormat().hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
1138 auto ruleLength = qvariant_cast<QTextLength>(block.blockFormat().property(QTextFormat::BlockTrailingHorizontalRulerWidth));
1139 QRectF ruleRect(0, 0, ruleLength.value(blockBoundingRect.width()), 1);
1140 ruleRect.moveCenter(blockBoundingRect.center());
1141 const QColor ruleColor = block.blockFormat().hasProperty(QTextFormat::BackgroundBrush)
1142 ? qvariant_cast<QBrush>(block.blockFormat().property(QTextFormat::BackgroundBrush)).color()
1144 m_lines.append(TextDecoration(QQuickTextNodeEngine::Unselected, ruleRect, ruleColor));
1148 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 addGlyphsInRange(int rangeStart, int rangeEnd, const QColor &color, const QColor &backgroundColor, const QColor &underlineColor, int selectionStart, int selectionEnd)
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 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)
BinaryTreeNodeKey(BinaryTreeNode *node)
BinaryTreeNode(const QGlyphRun &g, SelectionState selState, const QRectF &brect, const Decorations &decs, const QColor &c, const QColor &bc, const QColor &dc, const QPointF &pos, qreal a)