Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquicktextnodeengine.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
6
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>
16
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>
24
25#include <QtGui/private/qguiapplication_p.h>
26#include <qpa/qplatformtheme.h>
27
29
30// Resolves SpellCheckUnderline to the platform-specific style and normalizes
31// unknown styles to SingleUnderline.
32static QTextCharFormat::UnderlineStyle resolveUnderlineStyle(QTextCharFormat::UnderlineStyle style)
33{
34 if (style == QTextCharFormat::SpellCheckUnderline) {
35 QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme();
36 if (theme) {
37 style = QTextCharFormat::UnderlineStyle(
38 theme->themeHint(QPlatformTheme::SpellCheckUnderlineStyle).toInt());
39 }
40 if (style == QTextCharFormat::SpellCheckUnderline)
41 style = QTextCharFormat::WaveUnderline;
42 }
43
44 switch (style) {
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:
52 return style;
53 default:
54 qWarning("QQuickTextNodeEngine: unknown underline style %d, falling back to SingleUnderline",
55 int(style));
56 return QTextCharFormat::SingleUnderline;
57 }
58}
59
67
69 SelectionState selState,
70 const QRectF &brect,
71 const Decorations &decs,
72 QTextCharFormat::UnderlineStyle us,
73 const QColor &c,
74 const QColor &bc, const QColor &dc,
75 const QPointF &pos, qreal a)
76 : glyphRun(g)
79 , clipNode(nullptr)
82 , color(c)
85 , position(pos)
86 , ascent(a)
87 , leftChildIndex(-1)
88 , rightChildIndex(-1)
89{
90 QGlyphRunPrivate *d = QGlyphRunPrivate::get(g);
91 ranges.append(std::make_pair(d->textRangeStart, d->textRangeEnd));
92}
93
94void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode, 16> *binaryTree, const QGlyphRun &glyphRun, SelectionState selectionState,
95 Decorations decorations, QTextCharFormat::UnderlineStyle underlineStyle, const QColor &textColor,
96 const QColor &backgroundColor, const QColor &decorationColor, const QPointF &position)
97{
98 QRectF searchRect = glyphRun.boundingRect();
99 searchRect.translate(position);
100
101 if (qFuzzyIsNull(searchRect.width()) || qFuzzyIsNull(searchRect.height()))
102 return;
103
104 if (glyphRun.underline() || underlineStyle != QTextCharFormat::NoUnderline)
105 decorations |= Decoration::Underline;
106 decorations |= (glyphRun.overline() ? Decoration::Overline : Decoration::NoDecoration);
107 decorations |= (glyphRun.strikeOut() ? Decoration::StrikeOut : Decoration::NoDecoration);
108 decorations |= (backgroundColor.isValid() ? Decoration::Background : Decoration::NoDecoration);
109
110 qreal ascent = glyphRun.rawFont().ascent();
111 insert(binaryTree, BinaryTreeNode(glyphRun,
112 selectionState,
113 searchRect,
114 decorations,
115 underlineStyle,
116 textColor,
117 backgroundColor,
118 decorationColor,
119 position,
120 ascent));
121}
122
123void QQuickTextNodeEngine::BinaryTreeNode::insert(QVarLengthArray<BinaryTreeNode, 16> *binaryTree, const BinaryTreeNode &binaryTreeNode)
124{
125 int newIndex = binaryTree->size();
126 binaryTree->append(binaryTreeNode);
127 if (newIndex == 0)
128 return;
129
130 int searchIndex = 0;
131 forever {
132 BinaryTreeNode *node = binaryTree->data() + searchIndex;
133 if (binaryTreeNode.boundingRect.left() < node->boundingRect.left()) {
134 if (node->leftChildIndex < 0) {
135 node->leftChildIndex = newIndex;
136 break;
137 } else {
138 searchIndex = node->leftChildIndex;
139 }
140 } else {
141 if (node->rightChildIndex < 0) {
142 node->rightChildIndex = newIndex;
143 break;
144 } else {
145 searchIndex = node->rightChildIndex;
146 }
147 }
148 }
149}
150
151void QQuickTextNodeEngine::BinaryTreeNode::inOrder(const QVarLengthArray<BinaryTreeNode, 16> &binaryTree,
152 QVarLengthArray<int> *sortedIndexes, int currentIndex)
153{
154 Q_ASSERT(currentIndex < binaryTree.size());
155
156 const BinaryTreeNode *node = binaryTree.data() + currentIndex;
157 if (node->leftChildIndex >= 0)
158 inOrder(binaryTree, sortedIndexes, node->leftChildIndex);
159
160 sortedIndexes->append(currentIndex);
161
162 if (node->rightChildIndex >= 0)
163 inOrder(binaryTree, sortedIndexes, node->rightChildIndex);
164}
165
166
167int QQuickTextNodeEngine::addText(const QTextBlock &block,
168 const QTextCharFormat &charFormat,
169 const QColor &textColor,
170 const QVarLengthArray<QTextLayout::FormatRange> &colorChanges,
171 int textPos, int fragmentEnd,
172 int selectionStart, int selectionEnd)
173{
174 if (charFormat.foreground().style() != Qt::NoBrush)
175 setTextColor(charFormat.foreground().color());
176 else
177 setTextColor(textColor);
178
179 while (textPos < fragmentEnd) {
180 int blockRelativePosition = textPos - block.position();
181 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
182 if (!line.isValid())
183 break;
184 if (!currentLine().isValid()
185 || line.lineNumber() != currentLine().lineNumber()) {
187 }
188
189 Q_ASSERT(line.textLength() > 0);
190 int lineEnd = line.textStart() + block.position() + line.textLength();
191
192 int len = qMin(lineEnd - textPos, fragmentEnd - textPos);
193 Q_ASSERT(len > 0);
194
195 int currentStepEnd = textPos + len;
196
197 addGlyphsForRanges(colorChanges,
198 textPos - block.position(),
199 currentStepEnd - block.position(),
200 selectionStart - block.position(),
201 selectionEnd - block.position());
202
203 textPos = currentStepEnd;
204 }
205 return textPos;
206}
207
208void QQuickTextNodeEngine::addTextDecorations(QSpan<const TextDecoration> textDecorations,
209 qreal offset, qreal thickness)
210{
211 for (auto textDecoration : textDecorations) {
212 QRectF &rect = textDecoration.rect;
213 rect.setY(qRound(rect.y() + m_currentLine.ascent() + offset));
214 rect.setHeight(thickness);
215
216 m_lines.append(textDecoration);
217 }
218}
219
220void QQuickTextNodeEngine::processCurrentLine()
221{
222 // No glyphs, do nothing
223 if (m_currentLineTree.isEmpty())
224 return;
225
226 // 1. Go through current line and get correct decoration position for each node based on
227 // neighbouring decorations. Add decoration to global list
228 // 2. Create clip nodes for all selected text. Try to merge as many as possible within
229 // the line.
230 // 3. Add QRects to a list of selection rects.
231 // 4. Add all nodes to a global processed list
232 QVarLengthArray<int> sortedIndexes; // Indexes in tree sorted by x position
233 BinaryTreeNode::inOrder(m_currentLineTree, &sortedIndexes);
234
235 Q_ASSERT(sortedIndexes.size() == m_currentLineTree.size());
236
237 SelectionState currentSelectionState = Unselected;
238 QRectF currentRect;
239
240 Decorations currentDecorations = Decoration::NoDecoration;
241 qreal underlineOffset = 0.0;
242 qreal underlineThickness = 0.0;
243
244 qreal overlineOffset = 0.0;
245 qreal overlineThickness = 0.0;
246
247 qreal strikeOutOffset = 0.0;
248 qreal strikeOutThickness = 0.0;
249
250 QRectF decorationRect = currentRect;
251
252 QColor lastColor;
253 QColor lastBackgroundColor;
254 QColor lastDecorationColor;
255 QTextCharFormat::UnderlineStyle lastUnderlineStyle = QTextCharFormat::NoUnderline;
256
257 QVarLengthArray<TextDecoration, 16> pendingUnderlines;
258 QVarLengthArray<TextDecoration, 16> pendingOverlines;
259 QVarLengthArray<TextDecoration, 16> 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) {
264 BinaryTreeNode *node = nullptr;
265 if (i < sortedIndexes.size()) {
266 int sortedIndex = sortedIndexes.at(i);
267 Q_ASSERT(sortedIndex < m_currentLineTree.size());
268
269 node = m_currentLineTree.data() + sortedIndex;
270 if (i == 0)
271 currentSelectionState = node->selectionState;
272 }
273
274 // Update decorations
275 if (currentDecorations != Decoration::NoDecoration) {
276 decorationRect.setY(m_position.y() + m_currentLine.y());
277 decorationRect.setHeight(m_currentLine.height());
278
279 // Extend decoration rect to the next node only if the next node continues the
280 // same decoration, or if it is directly adjacent. Don't extend across gaps
281 // (e.g. tab characters) to a node that does not share the same decoration, as
282 // that would draw the decoration through invisible content.
283 if (node != nullptr) {
284 const bool continuesDecoration = node->decorations & currentDecorations;
285 if (continuesDecoration || node->boundingRect.left() < decorationRect.right())
286 decorationRect.setRight(node->boundingRect.left());
287 }
288
289 TextDecoration textDecoration(currentSelectionState, decorationRect, lastColor);
290 if (lastDecorationColor.isValid() &&
291 (currentDecorations.testFlag(Decoration::Underline) ||
292 currentDecorations.testFlag(Decoration::Overline) ||
293 currentDecorations.testFlag(Decoration::StrikeOut)))
294 textDecoration.color = lastDecorationColor;
295
296 if (currentDecorations & Decoration::Underline) {
297 textDecoration.underlineStyle = lastUnderlineStyle;
298 // Try to merge with previous underline if style, color, and selection match
299 if (!pendingUnderlines.isEmpty()) {
300 TextDecoration &last = pendingUnderlines.last();
301 // Half-pixel threshold accounts for floating-point gaps between adjacent glyph runs
302 constexpr qreal mergeThreshold = 0.5;
303 if (last.underlineStyle == lastUnderlineStyle
304 && last.color == textDecoration.color
305 && last.selectionState == textDecoration.selectionState
306 && qAbs(last.rect.right() - textDecoration.rect.left()) < mergeThreshold) {
307 // Extend previous range instead of creating a new one
308 last.rect.setRight(textDecoration.rect.right());
309 } else {
310 pendingUnderlines.append(textDecoration);
311 }
312 } else {
313 pendingUnderlines.append(textDecoration);
314 }
315 }
316
317 if (currentDecorations & Decoration::Overline)
318 pendingOverlines.append(textDecoration);
319
320 if (currentDecorations & Decoration::StrikeOut)
321 pendingStrikeOuts.append(textDecoration);
322
323 if (currentDecorations & Decoration::Background)
324 m_backgrounds.append(std::make_pair(decorationRect, lastBackgroundColor));
325 }
326
327 // If we've reached an unselected node from a selected node, we add the
328 // selection rect to the graph, and we add decoration every time the
329 // selection state changes, because that means the text color changes
330 if (node == nullptr || node->selectionState != currentSelectionState) {
331 currentRect.setY(m_position.y() + m_currentLine.y());
332 currentRect.setHeight(m_currentLine.height());
333
334 if (currentSelectionState == Selected)
335 m_selectionRects.append(currentRect);
336
337 if (currentClipNode != nullptr) {
338 if (!currentClipNodeUsed) {
339 delete currentClipNode;
340 } else {
341 currentClipNode->setIsRectangular(true);
342 currentClipNode->setRect(currentRect);
343 currentClipNode->update();
344 }
345 }
346
347 if (node != nullptr && m_hasSelection)
348 currentClipNode = new QQuickDefaultClipNode(QRectF());
349 else
350 currentClipNode = nullptr;
351 currentClipNodeUsed = false;
352
353 if (node != nullptr) {
354 currentSelectionState = node->selectionState;
355 currentRect = node->boundingRect;
356
357 // Make sure currentRect is valid, otherwise the unite won't work
358 if (currentRect.isNull())
359 currentRect.setSize(QSizeF(1, 1));
360 }
361 } else {
362 if (currentRect.isNull())
363 currentRect = node->boundingRect;
364 else
365 currentRect = currentRect.united(node->boundingRect);
366 }
367
368 if (node != nullptr) {
369 if (node->selectionState == Selected) {
370 node->clipNode = currentClipNode;
371 currentClipNodeUsed = true;
372 }
373
374 decorationRect = node->boundingRect;
375
376 // If previous item(s) had underline and current does not, then we add the
377 // pending lines to the lists and likewise for overlines and strikeouts
378 if (!pendingUnderlines.isEmpty()
379 && !(node->decorations & Decoration::Underline)) {
380 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
381
382 pendingUnderlines.clear();
383
384 underlineOffset = 0.0;
385 underlineThickness = 0.0;
386 }
387
388 // ### Add pending when overlineOffset/thickness changes to minimize number of
389 // nodes
390 if (!pendingOverlines.isEmpty()) {
391 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
392
393 pendingOverlines.clear();
394
395 overlineOffset = 0.0;
396 overlineThickness = 0.0;
397 }
398
399 // ### Add pending when overlineOffset/thickness changes to minimize number of
400 // nodes
401 if (!pendingStrikeOuts.isEmpty()) {
402 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
403
404 pendingStrikeOuts.clear();
405
406 strikeOutOffset = 0.0;
407 strikeOutThickness = 0.0;
408 }
409
410 // Merge current values with previous. Prefer greatest thickness
411 QRawFont rawFont = node->glyphRun.rawFont();
412 if (node->decorations & Decoration::Underline) {
413 if (rawFont.lineThickness() > underlineThickness) {
414 underlineThickness = rawFont.lineThickness();
415 underlineOffset = rawFont.underlinePosition();
416 }
417 }
418
419 if (node->decorations & Decoration::Overline) {
420 overlineOffset = -rawFont.ascent();
421 overlineThickness = rawFont.lineThickness();
422 }
423
424 if (node->decorations & Decoration::StrikeOut) {
425 strikeOutThickness = rawFont.lineThickness();
426 strikeOutOffset = rawFont.ascent() / -3.0;
427 }
428
429 currentDecorations = node->decorations;
430 lastColor = node->color;
431 lastBackgroundColor = node->backgroundColor;
432 lastDecorationColor = node->decorationColor;
433 lastUnderlineStyle = node->underlineStyle;
434 m_processedNodes.append(*node);
435 }
436 }
437
438 if (!pendingUnderlines.isEmpty())
439 addTextDecorations(pendingUnderlines, underlineOffset, underlineThickness);
440
441 if (!pendingOverlines.isEmpty())
442 addTextDecorations(pendingOverlines, overlineOffset, overlineThickness);
443
444 if (!pendingStrikeOuts.isEmpty())
445 addTextDecorations(pendingStrikeOuts, strikeOutOffset, strikeOutThickness);
446 }
447
448 m_currentLineTree.clear();
449 m_currentLine = QTextLine();
450 m_hasSelection = false;
451}
452
453void QQuickTextNodeEngine::addImage(const QRectF &rect, const QImage &image, qreal ascent,
454 SelectionState selectionState,
455 QTextFrameFormat::Position layoutPosition)
456{
457 QRectF searchRect = rect;
458 if (layoutPosition == QTextFrameFormat::InFlow) {
459 if (m_currentLineTree.isEmpty()) {
460 qreal y = m_currentLine.ascent() - ascent;
461 if (m_currentTextDirection == Qt::RightToLeft)
462 searchRect.moveTopRight(m_position + m_currentLine.rect().topRight() + QPointF(0, y));
463 else
464 searchRect.moveTopLeft(m_position + m_currentLine.position() + QPointF(0, y));
465 } else {
466 const BinaryTreeNode *lastNode = m_currentLineTree.data() + m_currentLineTree.size() - 1;
467 if (lastNode->glyphRun.isRightToLeft()) {
468 QPointF lastPos = lastNode->boundingRect.topLeft();
469 searchRect.moveTopRight(lastPos - QPointF(0, ascent - lastNode->ascent));
470 } else {
471 QPointF lastPos = lastNode->boundingRect.topRight();
472 searchRect.moveTopLeft(lastPos - QPointF(0, ascent - lastNode->ascent));
473 }
474 }
475 }
476
477 BinaryTreeNode::insert(&m_currentLineTree, searchRect, image, ascent, selectionState);
478 m_hasContents = true;
479}
480
481void QQuickTextNodeEngine::addTextObject(const QTextBlock &block, const QPointF &position, const QTextCharFormat &format,
482 SelectionState selectionState,
483 QTextDocument *textDocument, int pos,
484 QTextFrameFormat::Position layoutPosition)
485{
486 QTextObjectInterface *handler = textDocument->documentLayout()->handlerForObject(format.objectType());
487 if (handler != nullptr) {
488 QImage image;
489 QSizeF size = handler->intrinsicSize(textDocument, pos, format);
490
491 if (format.objectType() == QTextFormat::ImageObject) {
492 QTextImageFormat imageFormat = format.toImageFormat();
493 QTextImageHandler *imageHandler = static_cast<QTextImageHandler *>(handler);
494 image = imageHandler->image(textDocument, imageFormat);
495 }
496
497 if (image.isNull()) {
498 if (QImageIOHandler::allocateImage((size * m_devicePixelRatio).toSize(),
499 QImage::Format_ARGB32_Premultiplied,
500 &image)) {
501 image.setDevicePixelRatio(m_devicePixelRatio);
502 image.fill(Qt::transparent);
503 {
504 QPainter painter(&image);
505 handler->drawObject(&painter, QRectF({}, size), textDocument, pos, format);
506 }
507 }
508 }
509
510 // Use https://developer.mozilla.org/de/docs/Web/CSS/vertical-align as a reference
511 // The top/bottom positions are supposed to be higher/lower than the text and reference
512 // the line height, not the text height (using QFontMetrics)
513 qreal ascent;
514 QTextLine line = block.layout()->lineForTextPosition(pos - block.position());
515 switch (format.verticalAlignment())
516 {
517 case QTextCharFormat::AlignTop:
518 ascent = line.ascent();
519 break;
520 case QTextCharFormat::AlignMiddle:
521 // Middlepoint of line (height - descent) + Half object height
522 ascent = (line.ascent() + line.descent()) / 2 - line.descent() + size.height() / 2;
523 break;
524 case QTextCharFormat::AlignBottom:
525 ascent = size.height() - line.descent();
526 break;
527 case QTextCharFormat::AlignBaseline: {
528 QFontMetricsF m(format.font());
529 ascent = size.height() - m.descent();
530 break;
531 }
532 default:
533 ascent = size.height();
534 }
535
536 addImage(QRectF(position, size), image, ascent, selectionState, layoutPosition);
537 }
538}
539
540void QQuickTextNodeEngine::addUnselectedGlyphs(const QGlyphRun &glyphRun)
541{
542 BinaryTreeNode::insert(&m_currentLineTree,
543 glyphRun,
544 Unselected,
545 Decoration::NoDecoration,
546 m_underlineStyle,
547 m_textColor,
548 m_backgroundColor,
549 m_decorationColor,
550 m_position);
551}
552
553void QQuickTextNodeEngine::addSelectedGlyphs(const QGlyphRun &glyphRun)
554{
555 int currentSize = m_currentLineTree.size();
556 BinaryTreeNode::insert(&m_currentLineTree,
557 glyphRun,
558 Selected,
559 Decoration::NoDecoration,
560 m_underlineStyle,
561 m_textColor,
562 m_backgroundColor,
563 m_decorationColor,
564 m_position);
565 m_hasSelection = m_hasSelection || m_currentLineTree.size() > currentSize;
566}
567
568void QQuickTextNodeEngine::addGlyphsForRanges(const QVarLengthArray<QTextLayout::FormatRange> &ranges,
569 int start, int end,
570 int selectionStart, int selectionEnd)
571{
572 int currentPosition = start;
573 int remainingLength = end - start;
574 for (const QTextLayout::FormatRange &range : ranges) {
575 if (range.start + range.length > currentPosition
576 && range.start < currentPosition + remainingLength) {
577
578 if (range.start > currentPosition) {
579 addGlyphsInRange(currentPosition, range.start - currentPosition,
580 QTextCharFormat::NoUnderline,
581 QColor(), QColor(), QColor(), selectionStart, selectionEnd);
582 }
583 int rangeEnd = qMin(range.start + range.length, currentPosition + remainingLength);
584 QColor rangeColor;
585 if (range.format.hasProperty(QTextFormat::ForegroundBrush))
586 rangeColor = range.format.foreground().color();
587 else if (range.format.isAnchor())
588 rangeColor = m_anchorColor;
589 QColor rangeBackgroundColor = range.format.hasProperty(QTextFormat::BackgroundBrush)
590 ? range.format.background().color()
591 : QColor();
592
593 QColor rangeDecorationColor = range.format.hasProperty(QTextFormat::TextUnderlineColor)
594 ? range.format.underlineColor()
595 : QColor();
596
597 QTextCharFormat::UnderlineStyle rangeUnderlineStyle = QTextCharFormat::NoUnderline;
598 if (range.format.hasProperty(QTextFormat::TextUnderlineStyle))
599 rangeUnderlineStyle = range.format.underlineStyle();
600
601 addGlyphsInRange(range.start, rangeEnd - range.start, rangeUnderlineStyle,
602 rangeColor, rangeBackgroundColor,
603 rangeDecorationColor, selectionStart, selectionEnd);
604
605 currentPosition = range.start + range.length;
606 remainingLength = end - currentPosition;
607
608 } else if (range.start > currentPosition + remainingLength || remainingLength <= 0) {
609 break;
610 }
611 }
612
613 if (remainingLength > 0) {
614 addGlyphsInRange(currentPosition, remainingLength, QTextCharFormat::NoUnderline,
615 QColor(), QColor(), QColor(),
616 selectionStart, selectionEnd);
617 }
618
619}
620
621void QQuickTextNodeEngine::addGlyphsInRange(int rangeStart, int rangeLength,
622 QTextCharFormat::UnderlineStyle underlineStyle,
623 const QColor &color, const QColor &backgroundColor, const QColor &decorationColor,
624 int selectionStart, int selectionEnd)
625{
626 QColor oldColor;
627 if (color.isValid()) {
628 oldColor = m_textColor;
629 m_textColor = color;
630 }
631
632 QColor oldBackgroundColor = m_backgroundColor;
633 if (backgroundColor.isValid()) {
634 oldBackgroundColor = m_backgroundColor;
635 m_backgroundColor = backgroundColor;
636 }
637
638 QColor oldDecorationColor = m_decorationColor;
639 if (decorationColor.isValid()) {
640 oldDecorationColor = m_decorationColor;
641 m_decorationColor = decorationColor;
642 }
643
644 QTextCharFormat::UnderlineStyle oldUnderlineStyle = m_underlineStyle;
645 m_underlineStyle = underlineStyle;
646
647 bool hasSelection = selectionEnd >= 0
648 && selectionStart <= selectionEnd;
649
650 QTextLine &line = m_currentLine;
651 int rangeEnd = rangeStart + rangeLength;
652 if (!hasSelection || (selectionStart > rangeEnd || selectionEnd < rangeStart)) {
653 const QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, rangeLength);
654 for (const QGlyphRun &glyphRun : glyphRuns)
655 addUnselectedGlyphs(glyphRun);
656 } else {
657 if (rangeStart < selectionStart) {
658 int length = qMin(selectionStart - rangeStart, rangeLength);
659 const QList<QGlyphRun> glyphRuns = line.glyphRuns(rangeStart, length);
660 for (const QGlyphRun &glyphRun : glyphRuns)
661 addUnselectedGlyphs(glyphRun);
662 }
663
664 if (rangeEnd > selectionStart) {
665 int start = qMax(selectionStart, rangeStart);
666 int length = qMin(selectionEnd - start + 1, rangeEnd - start);
667 const QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
668 for (const QGlyphRun &glyphRun : glyphRuns)
669 addSelectedGlyphs(glyphRun);
670 }
671
672 if (selectionEnd >= rangeStart && selectionEnd < rangeEnd) {
673 int start = selectionEnd + 1;
674 int length = rangeEnd - selectionEnd - 1;
675 const QList<QGlyphRun> glyphRuns = line.glyphRuns(start, length);
676 for (const QGlyphRun &glyphRun : glyphRuns)
677 addUnselectedGlyphs(glyphRun);
678 }
679 }
680
681 if (decorationColor.isValid())
682 m_decorationColor = oldDecorationColor;
683
684 if (backgroundColor.isValid())
685 m_backgroundColor = oldBackgroundColor;
686
687 if (oldColor.isValid())
688 m_textColor = oldColor;
689
690 m_underlineStyle = oldUnderlineStyle;
691}
692
693void QQuickTextNodeEngine::addBorder(const QRectF &rect, qreal border,
694 QTextFrameFormat::BorderStyle borderStyle,
695 const QBrush &borderBrush)
696{
697 const QColor &color = borderBrush.color();
698
699 // Currently we don't support other styles than solid
700 Q_UNUSED(borderStyle);
701
702 m_backgrounds.append(std::make_pair(QRectF(rect.left(), rect.top(), border, rect.height() + border), color));
703 m_backgrounds.append(std::make_pair(QRectF(rect.left() + border, rect.top(), rect.width(), border), color));
704 m_backgrounds.append(std::make_pair(QRectF(rect.right(), rect.top() + border, border, rect.height() - border), color));
705 m_backgrounds.append(std::make_pair(QRectF(rect.left() + border, rect.bottom(), rect.width(), border), color));
706}
707
708void QQuickTextNodeEngine::addFrameDecorations(QTextDocument *document, QTextFrame *frame)
709{
710 QTextDocumentLayout *documentLayout = qobject_cast<QTextDocumentLayout *>(document->documentLayout());
711 if (Q_UNLIKELY(!documentLayout))
712 return;
713
714 QTextFrameFormat frameFormat = frame->format().toFrameFormat();
715 QTextTable *table = qobject_cast<QTextTable *>(frame);
716
717 QRectF boundingRect = table == nullptr
718 ? documentLayout->frameBoundingRect(frame)
719 : documentLayout->tableBoundingRect(table);
720
721 QBrush bg = frame->frameFormat().background();
722 if (bg.style() != Qt::NoBrush)
723 m_backgrounds.append(std::make_pair(boundingRect, bg.color()));
724
725 if (!frameFormat.hasProperty(QTextFormat::FrameBorder))
726 return;
727
728 qreal borderWidth = frameFormat.border();
729 if (qFuzzyIsNull(borderWidth))
730 return;
731
732 QBrush borderBrush = frameFormat.borderBrush();
733 QTextFrameFormat::BorderStyle borderStyle = frameFormat.borderStyle();
734 if (borderStyle == QTextFrameFormat::BorderStyle_None)
735 return;
736
737 const auto collapsed = table->format().borderCollapse();
738
739 if (!collapsed) {
740 addBorder(boundingRect.adjusted(frameFormat.leftMargin(), frameFormat.topMargin(),
741 -frameFormat.rightMargin() - borderWidth,
742 -frameFormat.bottomMargin() - borderWidth),
743 borderWidth, borderStyle, borderBrush);
744 }
745 if (table != nullptr) {
746 int rows = table->rows();
747 int columns = table->columns();
748
749 for (int row=0; row<rows; ++row) {
750 for (int column=0; column<columns; ++column) {
751 QTextTableCell cell = table->cellAt(row, column);
752
753 QRectF cellRect = documentLayout->tableCellBoundingRect(table, cell);
754 addBorder(cellRect.adjusted(-borderWidth, -borderWidth, collapsed ? -borderWidth : 0, collapsed ? -borderWidth : 0), borderWidth,
755 borderStyle, borderBrush);
756 }
757 }
758 }
759}
760
762{
763 return qHashMulti(seed, key.fontEngine, key.clipNode, key.color, key.selectionState);
764}
765
766void QQuickTextNodeEngine::mergeProcessedNodes(QVarLengthArray<BinaryTreeNode *, 8> *regularNodes,
767 QVarLengthArray<BinaryTreeNode *, 8> *imageNodes)
768{
769 QHash<BinaryTreeNodeKey, QVarLengthArray<BinaryTreeNode *, 2> > map;
770
771 for (int i = 0; i < m_processedNodes.size(); ++i) {
772 BinaryTreeNode *node = m_processedNodes.data() + i;
773
774 if (node->image.isNull()) {
775 if (node->glyphRun.isEmpty())
776 continue;
777
778 BinaryTreeNodeKey key(node);
779
780 QVarLengthArray<BinaryTreeNode *, 2> &nodes = map[key];
781 if (nodes.isEmpty())
782 regularNodes->append(node);
783
784 nodes.append(node);
785 } else {
786 imageNodes->append(node);
787 }
788 }
789
790 for (int i = 0; i < regularNodes->size(); ++i) {
791 BinaryTreeNode *primaryNode = regularNodes->at(i);
792 BinaryTreeNodeKey key(primaryNode);
793
794 const auto it = map.constFind(key);
795 Q_ASSERT(it != map.constEnd());
796 const QVarLengthArray<BinaryTreeNode *, 2> &nodes = it.value();
797 Q_ASSERT(nodes.first() == primaryNode);
798
799 int count = 0;
800 for (int j = 0; j < nodes.size(); ++j)
801 count += nodes.at(j)->glyphRun.glyphIndexes().size();
802
803 if (count != primaryNode->glyphRun.glyphIndexes().size()) {
804 QGlyphRun &glyphRun = primaryNode->glyphRun;
805 QList<quint32> glyphIndexes = glyphRun.glyphIndexes();
806 glyphIndexes.reserve(count);
807
808 QList<QPointF> glyphPositions = glyphRun.positions();
809 glyphPositions.reserve(count);
810
811 QRectF glyphBoundingRect = glyphRun.boundingRect();
812
813 for (int j = 1; j < nodes.size(); ++j) {
814 BinaryTreeNode *otherNode = nodes.at(j);
815 glyphIndexes += otherNode->glyphRun.glyphIndexes();
816 primaryNode->ranges += otherNode->ranges;
817 glyphBoundingRect = glyphBoundingRect.united(otherNode->boundingRect);
818
819 QList<QPointF> otherPositions = otherNode->glyphRun.positions();
820 for (int k = 0; k < otherPositions.size(); ++k)
821 glyphPositions += otherPositions.at(k) + (otherNode->position - primaryNode->position);
822 }
823
824 Q_ASSERT(glyphPositions.size() == count);
825 Q_ASSERT(glyphIndexes.size() == count);
826
827 glyphRun.setGlyphIndexes(glyphIndexes);
828 glyphRun.setPositions(glyphPositions);
829 glyphRun.setBoundingRect(glyphBoundingRect);
830 }
831 }
832}
833
834void QQuickTextNodeEngine::addToSceneGraph(QSGInternalTextNode *parentNode,
835 QSGInternalTextNode::RecycleBin *recycleBin,
836 QQuickText::TextStyle style,
837 const QColor &styleColor)
838{
839 if (m_currentLine.isValid())
840 processCurrentLine();
841
842 QVarLengthArray<BinaryTreeNode *, 8> nodes;
843 QVarLengthArray<BinaryTreeNode *, 8> imageNodes;
844 mergeProcessedNodes(&nodes, &imageNodes);
845
846 for (int i = 0; i < m_backgrounds.size(); ++i) {
847 const QRectF &rect = m_backgrounds.at(i).first;
848 const QColor &color = m_backgrounds.at(i).second;
849 if (color.alpha() != 0)
850 parentNode->addRectangleNode(rect, color, recycleBin);
851 }
852
853 // Add all text with unselected color first
854 for (int i = 0; i < nodes.size(); ++i) {
855 const BinaryTreeNode *node = nodes.at(i);
856 parentNode->addGlyphs(node->position, node->glyphRun, node->color, recycleBin, style, styleColor, nullptr);
857 }
858
859 for (int i = 0; i < imageNodes.size(); ++i) {
860 const BinaryTreeNode *node = imageNodes.at(i);
861 if (node->selectionState == Unselected)
862 parentNode->addImage(node->boundingRect, node->image, recycleBin);
863 }
864
865 for (int i = 0; i < m_selectionRects.size(); ++i) {
866 const QRectF &rect = m_selectionRects.at(i);
867 if (m_selectionColor.alpha() != 0)
868 parentNode->addRectangleNode(rect, m_selectionColor, recycleBin);
869 }
870
871 // Add decorations for each node to the tree.
872 for (int i = 0; i < m_lines.size(); ++i) {
873 const TextDecoration &textDecoration = m_lines.at(i);
874
875 QColor color = textDecoration.selectionState == Selected
876 ? m_selectedTextColor
877 : textDecoration.color;
878
879 parentNode->addDecorationNode(textDecoration.rect,
880 color,
881 resolveUnderlineStyle(textDecoration.underlineStyle),
882 recycleBin);
883 }
884
885 // Finally add the selected text on top of everything
886 for (int i = 0; i < nodes.size(); ++i) {
887 const BinaryTreeNode *node = nodes.at(i);
888 QQuickDefaultClipNode *clipNode = node->clipNode;
889 if (clipNode != nullptr && clipNode->parent() == nullptr)
890 parentNode->appendChildNode(clipNode);
891
892 if (node->selectionState == Selected) {
893 QColor color = m_selectedTextColor;
894 int previousNodeIndex = i - 1;
895 int nextNodeIndex = i + 1;
896 const BinaryTreeNode *previousNode = previousNodeIndex < 0 ? 0 : nodes.at(previousNodeIndex);
897 while (previousNode != nullptr && qFuzzyCompare(previousNode->boundingRect.left(), node->boundingRect.left()))
898 previousNode = --previousNodeIndex < 0 ? 0 : nodes.at(previousNodeIndex);
899
900 const BinaryTreeNode *nextNode = nextNodeIndex == nodes.size() ? 0 : nodes.at(nextNodeIndex);
901
902 if (previousNode != nullptr && previousNode->selectionState == Unselected)
903 parentNode->addGlyphs(previousNode->position, previousNode->glyphRun, color, recycleBin, style, styleColor, clipNode);
904
905 if (nextNode != nullptr && nextNode->selectionState == Unselected)
906 parentNode->addGlyphs(nextNode->position, nextNode->glyphRun, color, recycleBin, style, styleColor, clipNode);
907
908 // If the previous or next node completely overlaps this one, then we have already drawn the glyphs of
909 // this node
910 bool drawCurrent = false;
911 if (previousNode != nullptr || nextNode != nullptr) {
912 // Return |rangeLength| minus the overlap between |range| and the
913 // ranges of |otherNode|. Only considers nodes from the same text position;
914 // nodes from different positions (e.g. list bullets from a separate
915 // QTextLayout) have coincidental range overlaps that don't represent
916 // actual glyph coverage.
917 auto subtractOverlap = [](int rangeLength, const std::pair<int, int> &range,
918 const BinaryTreeNode *otherNode, const QPointF &nodePos) {
919 if (otherNode->position != nodePos)
920 return rangeLength;
921 for (int j = 0; j < otherNode->ranges.size(); ++j) {
922 const std::pair<int, int> &otherRange = otherNode->ranges.at(j);
923 if (range.first < otherRange.second && range.second > otherRange.first) {
924 int start = qMax(range.first, otherRange.first);
925 int end = qMin(range.second, otherRange.second);
926 rangeLength -= end - start + 1;
927 if (rangeLength == 0)
928 break;
929 }
930 }
931 return rangeLength;
932 };
933
934 for (int i = 0; i < node->ranges.size(); ++i) {
935 const std::pair<int, int> &range = node->ranges.at(i);
936
937 int rangeLength = range.second - range.first + 1;
938 if (previousNode != nullptr)
939 rangeLength = subtractOverlap(rangeLength, range, previousNode, node->position);
940 if (nextNode != nullptr && rangeLength > 0)
941 rangeLength = subtractOverlap(rangeLength, range, nextNode, node->position);
942
943 if (rangeLength > 0) {
944 drawCurrent = true;
945 break;
946 }
947 }
948 } else {
949 drawCurrent = true;
950 }
951
952 if (drawCurrent)
953 parentNode->addGlyphs(node->position, node->glyphRun, color, recycleBin, style, styleColor, clipNode);
954 }
955 }
956
957 for (int i = 0; i < imageNodes.size(); ++i) {
958 const BinaryTreeNode *node = imageNodes.at(i);
959 if (node->selectionState == Selected) {
960 parentNode->addImage(node->boundingRect, node->image, recycleBin);
961 if (node->selectionState == Selected) {
962 QColor color = m_selectionColor;
963 color.setAlpha(128);
964 parentNode->addRectangleNode(node->boundingRect, color, recycleBin);
965 }
966 }
967 }
968}
969
970void QQuickTextNodeEngine::mergeFormats(QTextLayout *textLayout, QVarLengthArray<QTextLayout::FormatRange> *mergedFormats)
971{
972 Q_ASSERT(mergedFormats != nullptr);
973 if (textLayout == nullptr)
974 return;
975
976 const QList<QTextLayout::FormatRange> additionalFormats = textLayout->formats();
977 for (QTextLayout::FormatRange additionalFormat : additionalFormats) {
978 if (additionalFormat.format.hasProperty(QTextFormat::ForegroundBrush)
979 || additionalFormat.format.hasProperty(QTextFormat::BackgroundBrush)
980 || additionalFormat.format.hasProperty(QTextFormat::TextUnderlineStyle)
981 || additionalFormat.format.hasProperty(QTextFormat::TextUnderlineColor)
982 || additionalFormat.format.isAnchor()) {
983 // Merge overlapping formats
984 if (!mergedFormats->isEmpty()) {
985 QTextLayout::FormatRange *lastFormat = mergedFormats->data() + mergedFormats->size() - 1;
986
987 if (additionalFormat.start < lastFormat->start + lastFormat->length) {
988 QTextLayout::FormatRange *mergedRange = nullptr;
989
990 int length = additionalFormat.length;
991 if (additionalFormat.start > lastFormat->start) {
992 lastFormat->length = additionalFormat.start - lastFormat->start;
993 length -= lastFormat->length;
994
995 mergedFormats->append(QTextLayout::FormatRange());
996 mergedRange = mergedFormats->data() + mergedFormats->size() - 1;
997 lastFormat = mergedFormats->data() + mergedFormats->size() - 2;
998 } else {
999 mergedRange = lastFormat;
1000 }
1001
1002 mergedRange->format = lastFormat->format;
1003 mergedRange->format.merge(additionalFormat.format);
1004 mergedRange->start = additionalFormat.start;
1005
1006 int end = qMin(additionalFormat.start + additionalFormat.length,
1007 lastFormat->start + lastFormat->length);
1008
1009 mergedRange->length = end - mergedRange->start;
1010 length -= mergedRange->length;
1011
1012 additionalFormat.start = end;
1013 additionalFormat.length = length;
1014 }
1015 }
1016
1017 if (additionalFormat.length > 0)
1018 mergedFormats->append(additionalFormat);
1019 }
1020 }
1021}
1022
1023/*!
1024 \internal
1025 Adds the \a block from the \a textDocument at \a position if its
1026 \l {QAbstractTextDocumentLayout::blockBoundingRect()}{bounding rect}
1027 intersects the \a viewport, or if \c viewport is not valid
1028 (i.e. use a default-constructed QRectF to skip the viewport check).
1029
1030 \sa QQuickItem::clipRect()
1031 */
1032void QQuickTextNodeEngine::addTextBlock(QTextDocument *textDocument, const QTextBlock &block, const QPointF &position,
1033 const QColor &textColor, const QColor &anchorColor, int selectionStart, int selectionEnd, const QRectF &viewport)
1034{
1035 Q_ASSERT(textDocument);
1036#if QT_CONFIG(im)
1037 int preeditLength = block.isValid() ? block.layout()->preeditAreaText().size() : 0;
1038 int preeditPosition = block.isValid() ? block.layout()->preeditAreaPosition() : -1;
1039#endif
1040
1041 setCurrentTextDirection(block.textDirection());
1042
1043 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
1044 mergeFormats(block.layout(), &colorChanges);
1045
1046 const QTextCharFormat charFormat = block.charFormat();
1047 const QRectF blockBoundingRect = textDocument->documentLayout()->blockBoundingRect(block).translated(position);
1048 if (viewport.isValid()) {
1049 if (!blockBoundingRect.intersects(viewport))
1050 return;
1051 qCDebug(lcSgText) << "adding block with length" << block.length() << ':' << blockBoundingRect << "in viewport" << viewport;
1052 }
1053
1054 if (charFormat.background().style() != Qt::NoBrush)
1055 m_backgrounds.append(std::make_pair(blockBoundingRect, charFormat.background().color()));
1056
1057 if (QTextList *textList = block.textList()) {
1058 QPointF pos = blockBoundingRect.topLeft();
1059 QTextLayout *layout = block.layout();
1060 if (layout->lineCount() > 0) {
1061 QTextLine firstLine = layout->lineAt(0);
1062 Q_ASSERT(firstLine.isValid());
1063
1064 setCurrentLine(firstLine);
1065
1066 QRectF textRect = firstLine.naturalTextRect();
1067 pos += textRect.topLeft();
1068 if (block.textDirection() == Qt::RightToLeft)
1069 pos.rx() += textRect.width();
1070
1071 QFont font(charFormat.font());
1072 QFontMetricsF fontMetrics(font);
1073 QTextListFormat listFormat = textList->format();
1074
1075 QString listItemBullet;
1076 switch (listFormat.style()) {
1077 case QTextListFormat::ListCircle:
1078 listItemBullet = QChar(0x25E6); // White bullet
1079 break;
1080 case QTextListFormat::ListSquare:
1081 listItemBullet = QChar(0x25AA); // Black small square
1082 break;
1083 case QTextListFormat::ListDecimal:
1084 case QTextListFormat::ListLowerAlpha:
1085 case QTextListFormat::ListUpperAlpha:
1086 case QTextListFormat::ListLowerRoman:
1087 case QTextListFormat::ListUpperRoman:
1088 listItemBullet = textList->itemText(block);
1089 break;
1090 default:
1091 listItemBullet = QChar(0x2022); // Black bullet
1092 break;
1093 };
1094
1095 switch (block.blockFormat().marker()) {
1096 case QTextBlockFormat::MarkerType::Checked:
1097 listItemBullet = QChar(0x2612); // Checked checkbox
1098 break;
1099 case QTextBlockFormat::MarkerType::Unchecked:
1100 listItemBullet = QChar(0x2610); // Unchecked checkbox
1101 break;
1102 case QTextBlockFormat::MarkerType::NoMarker:
1103 break;
1104 }
1105
1106 QSizeF size(fontMetrics.horizontalAdvance(listItemBullet), fontMetrics.height());
1107 qreal xoff = fontMetrics.horizontalAdvance(QLatin1Char(' '));
1108 if (block.textDirection() == Qt::LeftToRight)
1109 xoff = -xoff - size.width();
1110
1111 QTextLayout layout;
1112 layout.setFont(font);
1113 layout.setText(listItemBullet); // Bullet
1114 layout.beginLayout();
1115 QTextLine line = layout.createLine();
1116 line.setPosition(QPointF(0, 0));
1117 layout.endLayout();
1118
1119 // Align the marker to the first line's baseline, so it does not float
1120 // above taller content such as an inline image or a larger font.
1121 const qreal baselineOffset = firstLine.ascent() - line.ascent();
1122 setPosition(pos + QPointF(xoff, baselineOffset));
1123
1124 // set the color for the bullets, instead of using the previous QTextBlock's color.
1125 if (charFormat.foreground().style() == Qt::NoBrush)
1126 setTextColor(textColor);
1127 else
1128 setTextColor(charFormat.foreground().color());
1129
1130 const QList<QGlyphRun> glyphRuns = layout.glyphRuns();
1131 for (const auto &e : glyphRuns)
1132 addUnselectedGlyphs(e);
1133 }
1134 }
1135
1136 int textPos = block.position();
1137 QTextBlock::iterator blockIterator = block.begin();
1138
1139 while (!blockIterator.atEnd()) {
1140 QTextFragment fragment = blockIterator.fragment();
1141 QString text = fragment.text();
1142 if (text.isEmpty())
1143 continue;
1144
1145 QTextCharFormat charFormat = fragment.charFormat();
1146 QFont font(charFormat.font());
1147 QFontMetricsF fontMetrics(font);
1148
1149 int fontHeight = fontMetrics.descent() + fontMetrics.ascent();
1150 int valign = charFormat.verticalAlignment();
1151 if (valign == QTextCharFormat::AlignSuperScript)
1152 setPosition(QPointF(blockBoundingRect.x(), blockBoundingRect.y() - fontHeight / 2));
1153 else if (valign == QTextCharFormat::AlignSubScript)
1154 setPosition(QPointF(blockBoundingRect.x(), blockBoundingRect.y() + fontHeight / 6));
1155 else
1156 setPosition(blockBoundingRect.topLeft());
1157
1158 if (text.contains(QChar::ObjectReplacementCharacter) && charFormat.objectType() != QTextFormat::NoObject) {
1159 QTextFrame *frame = qobject_cast<QTextFrame *>(textDocument->objectForFormat(charFormat));
1160 if (!frame || frame->frameFormat().position() == QTextFrameFormat::InFlow) {
1161 int blockRelativePosition = textPos - block.position();
1162 QTextLine line = block.layout()->lineForTextPosition(blockRelativePosition);
1163 if (!currentLine().isValid()
1164 || line.lineNumber() != currentLine().lineNumber()) {
1165 setCurrentLine(line);
1166 }
1167
1168 QQuickTextNodeEngine::SelectionState selectionState =
1169 (selectionStart < textPos + text.size()
1170 && selectionEnd >= textPos)
1171 ? QQuickTextNodeEngine::Selected
1172 : QQuickTextNodeEngine::Unselected;
1173
1174 addTextObject(block, QPointF(), charFormat, selectionState, textDocument, textPos);
1175 }
1176 textPos += text.size();
1177 } else {
1178 if (charFormat.foreground().style() != Qt::NoBrush)
1179 setTextColor(charFormat.foreground().color());
1180 else if (charFormat.isAnchor())
1181 setTextColor(anchorColor);
1182 else
1183 setTextColor(textColor);
1184
1185 int fragmentEnd = textPos + fragment.length();
1186#if QT_CONFIG(im)
1187 if (preeditPosition >= 0
1188 && (preeditPosition + block.position()) >= textPos
1189 && (preeditPosition + block.position()) <= fragmentEnd) {
1190 fragmentEnd += preeditLength;
1191 }
1192#endif
1193 if (charFormat.background().style() != Qt::NoBrush
1194 || charFormat.hasProperty(QTextFormat::TextUnderlineColor)
1195 || charFormat.hasProperty(QTextFormat::TextUnderlineStyle)) {
1196 QTextLayout::FormatRange additionalFormat;
1197 additionalFormat.start = textPos - block.position();
1198 additionalFormat.length = fragmentEnd - textPos;
1199 additionalFormat.format = charFormat;
1200 colorChanges << additionalFormat;
1201 }
1202
1203 textPos = addText(block, charFormat, textColor, colorChanges, textPos, fragmentEnd,
1204 selectionStart, selectionEnd);
1205 }
1206
1207 ++blockIterator;
1208 }
1209
1210#if QT_CONFIG(im)
1211 if (preeditLength >= 0 && textPos <= block.position() + preeditPosition) {
1212 setPosition(blockBoundingRect.topLeft());
1213 textPos = block.position() + preeditPosition;
1214 QTextLine line = block.layout()->lineForTextPosition(preeditPosition);
1215 if (!currentLine().isValid()
1216 || line.lineNumber() != currentLine().lineNumber()) {
1217 setCurrentLine(line);
1218 }
1219 textPos = addText(block, block.charFormat(), textColor, colorChanges,
1220 textPos, textPos + preeditLength,
1221 selectionStart, selectionEnd);
1222 }
1223#endif
1224
1225 // Add block decorations (so far only horizontal rules)
1226 if (block.blockFormat().hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) {
1227 auto ruleLength = qvariant_cast<QTextLength>(block.blockFormat().property(QTextFormat::BlockTrailingHorizontalRulerWidth));
1228 QRectF ruleRect(0, 0, ruleLength.value(blockBoundingRect.width()), 1);
1229 ruleRect.moveCenter(blockBoundingRect.center());
1230 const QColor ruleColor = block.blockFormat().hasProperty(QTextFormat::BackgroundBrush)
1231 ? qvariant_cast<QBrush>(block.blockFormat().property(QTextFormat::BackgroundBrush)).color()
1232 : m_textColor;
1233 m_lines.append(TextDecoration(QQuickTextNodeEngine::Unselected, ruleRect, ruleColor));
1234 }
1235
1236 setCurrentLine(QTextLine()); // Reset current line because the text layout changed
1237 m_hasContents = true;
1238}
1239
1240
1241QT_END_NAMESPACE
void addSelectedGlyphs(const QGlyphRun &glyphRun)
void addUnselectedGlyphs(const QGlyphRun &glyphRun)
void mergeProcessedNodes(QVarLengthArray< BinaryTreeNode *, 8 > *regularNodes, QVarLengthArray< BinaryTreeNode *, 8 > *imageNodes)
void addTextObject(const QTextBlock &block, const QPointF &position, const QTextCharFormat &format, SelectionState selectionState, QTextDocument *textDocument, int pos, QTextFrameFormat::Position layoutPosition=QTextFrameFormat::InFlow)
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 &currentLine)
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)
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)