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
qsginternaltextnode.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
8
9#include <private/qsgadaptationlayer_p.h>
10#include <private/qsgdistancefieldglyphnode_p.h>
11#include <private/qquickclipnode_p.h>
12#include <private/qquickitem_p.h>
13#include <private/qquicktextdocument_p.h>
14#include <private/qsgrendernode_p.h>
15
16#include <QtCore/qpoint.h>
17#include <qtextdocument.h>
18#include <qtextlayout.h>
19#include <qabstracttextdocumentlayout.h>
20#include <private/qquickstyledtext_p.h>
21#include <private/qquicktext_p_p.h>
22#include <private/qfont_p.h>
23#include <private/qfontengine_p.h>
24
25#include <private/qtextdocumentlayout_p.h>
26#include <qhash.h>
27
28// #define QSGINTERNALTEXTNODE_NO_RECYCLE
29
30Q_STATIC_LOGGING_CATEGORY(lcTextRecycle, "qt.quick.text.noderecycling")
31
32QT_BEGIN_NAMESPACE
33
34/*!
35 \class QSGInternalTextNode
36 \inmodule QtQuick
37 \internal
38
39 Creates an empty QSGInternalTextNode
40*/
41QSGInternalTextNode::QSGInternalTextNode(QSGRenderContext *renderContext)
42 : m_renderContext(renderContext)
43{
44#ifdef QSG_RUNTIME_DESCRIPTION
45 qsgnode_set_description(this, QLatin1String("text"));
46#endif
47
48 static_assert(int(QSGTextNode::Normal) == int(QQuickText::Normal));
49 static_assert(int(QSGTextNode::Outline) == int(QQuickText::Outline));
50 static_assert(int(QSGTextNode::Raised) == int(QQuickText::Raised));
51 static_assert(int(QSGTextNode::Sunken) == int(QQuickText::Sunken));
52
53 static_assert(int(QSGTextNode::QtRendering) == int(QQuickText::QtRendering));
54 static_assert(int(QSGTextNode::NativeRendering) == int(QQuickText::NativeRendering));
55 static_assert(int(QSGTextNode::CurveRendering) == int(QQuickText::CurveRendering));
56}
57
58QSGInternalTextNode::~QSGInternalTextNode()
59{
60 qDeleteAll(m_textures);
61}
62
63QSGGlyphNode *QSGInternalTextNode::addGlyphs(const QPointF &position,
64 const QGlyphRun &glyphs,
65 const QColor &color,
66 RecycleBin *recycleBin,
67 QQuickText::TextStyle style,
68 const QColor &styleColor,
69 QSGNode *parentNode)
70{
71 QRawFont font = glyphs.rawFont();
72
73 QSGTextNode::RenderType preferredRenderType = m_renderType;
74 if (m_renderType != NativeRendering) {
75 if (const QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine)
76 if (fe->hasUnreliableGlyphOutline() || !fe->isSmoothlyScalable)
77 preferredRenderType = QSGTextNode::NativeRendering;
78 }
79
80 if (preferredRenderType == NativeRendering)
81 m_containsUnscalableGlyphs = true;
82
83 QSGGlyphNode *node = findOrCreateGlyphNode(preferredRenderType, recycleBin);
84 node->setRenderTypeQuality(m_renderTypeQuality);
85 node->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
86 node->setStyle(style);
87 node->setStyleColor(styleColor);
88 node->setColor(color);
89 node->update();
90
91 /* We flag the geometry as static, but we never call markVertexDataDirty
92 or markIndexDataDirty on them. This is because all text nodes are
93 discarded when a change occurs. If we start appending/removing from
94 existing geometry, then we also need to start marking the geometry as
95 dirty.
96 */
97 node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
98 node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
99
100 if (parentNode == nullptr)
101 parentNode = this;
102
103 if (node->parent() == nullptr)
104 parentNode->appendChildNode(node);
105
106 if (style == QQuickText::Outline && color.alpha() > 0 && styleColor != color) {
107 QSGGlyphNode *fillNode = findOrCreateGlyphNode(preferredRenderType, recycleBin);
108 fillNode->setRenderTypeQuality(m_renderTypeQuality);
109 fillNode->setGlyphs(position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
110 fillNode->setStyle(QQuickText::Normal);
111 fillNode->setPreferredAntialiasingMode(QSGGlyphNode::GrayAntialiasing);
112 fillNode->setColor(color);
113 fillNode->update();
114
115 fillNode->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
116 fillNode->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
117
118 if (fillNode->parent() == nullptr)
119 parentNode->appendChildNode(fillNode);
120 fillNode->setRenderOrder(node->renderOrder() + 1);
121 }
122
123 return node;
124}
125
126void QSGInternalTextNode::setCursor(const QRectF &rect, const QColor &color, RecycleBin *recycleBin)
127{
128 if (m_cursorNode == nullptr)
129 m_cursorNode = findOrCreateRectangleNode(recycleBin);
130 m_cursorNode->setRect(rect);
131 m_cursorNode->setColor(color);
132 m_cursorNode->update();
133 if (m_cursorNode->parent() == nullptr)
134 appendChildNode(m_cursorNode);
135}
136
137void QSGInternalTextNode::clearCursor()
138{
139 if (m_cursorNode)
140 removeChildNode(m_cursorNode);
141 delete m_cursorNode;
142 m_cursorNode = nullptr;
143}
144
145void QSGInternalTextNode::addDecorationNode(const QRectF &rect,
146 const QColor &color,
147 QTextCharFormat::UnderlineStyle style,
148 RecycleBin *recycleBin)
149{
150 Q_UNUSED(style); // Software renderer does not support styled underlines
151 addRectangleNode(rect, color, recycleBin);
152}
153
154void QSGInternalTextNode::addRectangleNode(const QRectF &rect,
155 const QColor &color,
156 RecycleBin *recycleBin)
157{
158 QSGInternalRectangleNode *node = findOrCreateRectangleNode(recycleBin);
159 node->setRect(rect);
160 node->setColor(color);
161 node->update();
162
163 if (node->parent() == nullptr)
164 appendChildNode(node);
165}
166
167void QSGInternalTextNode::addImage(const QRectF &rect, const QImage &image, RecycleBin *recycleBin)
168{
169 QSGInternalImageNode *node = findOrCreateImageNode(recycleBin);
170 QSGTexture *texture = m_renderContext->createTexture(image);
171 texture->setFiltering(m_filtering);
172 m_textures.append(texture);
173 node->setTargetRect(rect);
174 node->setInnerTargetRect(rect);
175 node->setTexture(texture);
176 node->setFiltering(m_filtering);
177 if (node->parent() == nullptr)
178 appendChildNode(node);
179 node->update();
180}
181
182void QSGInternalTextNode::doAddTextDocument(QPointF position,
183 QTextDocument *textDocument,
184 int selectionStart,
185 int selectionEnd,
186 RecycleBin *recycleBin)
187{
188 QQuickTextNodeEngine engine;
189 engine.setTextColor(m_color);
190 engine.setSelectedTextColor(m_selectionTextColor);
191 engine.setSelectionColor(m_selectionColor);
192 engine.setAnchorColor(m_linkColor);
193 engine.setPosition(position);
194 engine.setDevicePixelRatio(m_devicePixelRatio);
195
196 QList<QTextFrame *> frames;
197 frames.append(textDocument->rootFrame());
198 while (!frames.isEmpty()) {
199 QTextFrame *textFrame = frames.takeFirst();
200 frames.append(textFrame->childFrames());
201
202 engine.addFrameDecorations(textDocument, textFrame);
203
204 if (textFrame->firstPosition() > textFrame->lastPosition()
205 && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
206 const int pos = textFrame->firstPosition() - 1;
207 auto *a = static_cast<QtPrivate::ProtectedLayoutAccessor *>(textDocument->documentLayout());
208 QTextCharFormat format = a->formatAccessor(pos);
209 QRectF rect = a->frameBoundingRect(textFrame);
210
211 QTextBlock block = textFrame->firstCursorPosition().block();
212 engine.setCurrentLine(block.layout()->lineForTextPosition(pos - block.position()));
213 engine.addTextObject(block, rect.topLeft(), format, QQuickTextNodeEngine::Unselected, textDocument,
214 pos, textFrame->frameFormat().position());
215 } else {
216 QTextFrame::iterator it = textFrame->begin();
217
218 while (!it.atEnd()) {
219 Q_ASSERT(!engine.currentLine().isValid());
220
221 QTextBlock block = it.currentBlock();
222 engine.addTextBlock(textDocument, block, position, m_color, m_linkColor, selectionStart, selectionEnd,
223 (textDocument->characterCount() > QQuickTextPrivate::largeTextSizeThreshold ?
224 m_viewport : QRectF()));
225 ++it;
226 }
227 }
228 }
229
230 engine.addToSceneGraph(this, recycleBin, QQuickText::TextStyle(m_textStyle), m_styleColor);
231}
232
233void QSGInternalTextNode::doAddTextLayout(QPointF position,
234 QTextLayout *textLayout,
235 int selectionStart,
236 int selectionEnd,
237 int lineStart,
238 int lineCount,
239 RecycleBin *recycleBin)
240{
241 QQuickTextNodeEngine engine;
242 engine.setTextColor(m_color);
243 engine.setSelectedTextColor(m_selectionTextColor);
244 engine.setSelectionColor(m_selectionColor);
245 engine.setAnchorColor(m_linkColor);
246 engine.setPosition(position);
247 engine.setDevicePixelRatio(m_devicePixelRatio);
248
249#if QT_CONFIG(im)
250 int preeditLength = textLayout->preeditAreaText().size();
251 int preeditPosition = textLayout->preeditAreaPosition();
252#endif
253
254 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
255 engine.mergeFormats(textLayout, &colorChanges);
256
257 lineCount = lineCount >= 0
258 ? qMin(lineStart + lineCount, textLayout->lineCount())
259 : textLayout->lineCount();
260
261 bool inViewport = false;
262 for (int i=lineStart; i<lineCount; ++i) {
263 QTextLine line = textLayout->lineAt(i);
264
265 int start = line.textStart();
266 int length = line.textLength();
267 int end = start + length;
268
269#if QT_CONFIG(im)
270 if (preeditPosition >= 0
271 && preeditPosition >= start
272 && preeditPosition < end) {
273 end += preeditLength;
274 }
275#endif
276 // If there's a lot of text, insert only the range of lines that can possibly be visible within the viewport.
277 if (m_viewport.isNull() || (line.y() + line.height() > m_viewport.top() && line.y() < m_viewport.bottom())) {
278 if (!inViewport && !m_viewport.isNull()) {
279 m_firstLineInViewport = i;
280 qCDebug(lcVP) << "first line in viewport" << i << "@" << line.y();
281 }
282 inViewport = true;
283 engine.setCurrentLine(line);
284 engine.addGlyphsForRanges(colorChanges, start, end, selectionStart, selectionEnd);
285 } else if (inViewport) {
286 Q_ASSERT(!m_viewport.isNull());
287 m_firstLinePastViewport = i;
288 qCDebug(lcVP) << "first omitted line past bottom of viewport" << i << "@" << line.y();
289 break; // went past the bottom of the viewport, so we're done
290 }
291 }
292
293 engine.addToSceneGraph(this, recycleBin, QQuickText::TextStyle(m_textStyle), m_styleColor);
294}
295
296namespace {
297
298 class ChildNodeCollector : public QSGNodeVisitorEx
299 {
300 public:
301 ChildNodeCollector(QSGInternalTextNode::RecycleBin &recycleBin,
302 std::vector<QSGNode *> &untrackedNodes)
303 : m_recycleBin(recycleBin)
304 , m_untrackedNodes(untrackedNodes)
305 {
306 }
307
308 bool visit(QSGTransformNode *node) override
309 {
310 Q_UNREACHABLE();
311 m_untrackedNodes.push_back(node);
312 return false;
313 }
314
315 void endVisit(QSGTransformNode *) override
316 {
317 }
318
319 bool visit(QSGClipNode *node) override
320 {
321 // Clip nodes are used for selections and are currently not recycled. Clip nodes
322 // will anyway be separate batch roots, so this text does not trigger reuploads.
323 m_untrackedNodes.push_back(node);
324 return false;
325 }
326
327 void endVisit(QSGClipNode *) override
328 {
329 }
330
331 bool visit(QSGGeometryNode *node) override
332 {
333 Q_UNREACHABLE();
334 m_untrackedNodes.push_back(node);
335 return false;
336 }
337
338 void endVisit(QSGGeometryNode *) override
339 {
340 }
341
342 bool visit(QSGOpacityNode *node) override
343 {
344 Q_UNREACHABLE();
345 m_untrackedNodes.push_back(node);
346 return false;
347 }
348
349 void endVisit(QSGOpacityNode *) override
350 {
351 }
352
353 bool visit(QSGInternalImageNode *node) override
354 {
355 m_recycleBin.unusedImageNodes.append(node);
356 return false;
357 }
358
359 void endVisit(QSGInternalImageNode *) override
360 {
361 }
362
363 bool visit(QSGPainterNode *node) override
364 {
365 Q_UNREACHABLE();
366 m_untrackedNodes.push_back(node);
367 return false;
368 }
369
370 void endVisit(QSGPainterNode *) override
371 {
372 }
373
374 bool visit(QSGInternalRectangleNode *node) override
375 {
376 m_recycleBin.unusedRectangleNodes.append(node);
377 return false;
378 }
379
380 void endVisit(QSGInternalRectangleNode *) override
381 {
382 }
383
384 bool visit(QSGGlyphNode *node) override
385 {
386 // Some glyph nodes with a lot of content have content split into child nodes.
387 // For now, we do not recycle these nodes as it is an uncommon case and adds
388 // complexity. This also affects curve-rendered glyphs, which will have children
389 // and thus be excluded from recycling.
390 if (node->childCount() > 0)
391 m_untrackedNodes.push_back(node);
392 else
393 m_recycleBin.unusedGlyphNodes.append(node);
394
395 return false;
396 }
397
398 void endVisit(QSGGlyphNode *) override
399 {
400 }
401
402 bool visit(QSGRootNode *node) override
403 {
404 Q_UNREACHABLE();
405 m_untrackedNodes.push_back(node);
406 return false;
407 }
408
409 void endVisit(QSGRootNode *) override
410 {
411 }
412
413#if QT_CONFIG(quick_sprite)
414 bool visit(QSGSpriteNode *node) override
415 {
416 Q_UNREACHABLE();
417 m_untrackedNodes.push_back(node);
418 return false;
419 }
420
421 void endVisit(QSGSpriteNode *) override
422 {
423 }
424#endif
425 bool visit(QSGRenderNode *node) override
426 {
427 Q_UNREACHABLE();
428 m_untrackedNodes.push_back(node);
429 return false;
430 }
431
432 void endVisit(QSGRenderNode *) override
433 {
434 }
435
436 private:
437 QSGInternalTextNode::RecycleBin &m_recycleBin;
438 std::vector<QSGNode *> &m_untrackedNodes;
439 };
440}
441
442QSGInternalRectangleNode *QSGInternalTextNode::findOrCreateRectangleNode(RecycleBin *recycleBin)
443{
444 qCDebug(lcTextRecycle) << "Searching for rectangle node";
445
446 if (recycleBin == nullptr || recycleBin->unusedRectangleNodes.isEmpty()) {
447 qCDebug(lcTextRecycle) << " Creating new rectangle node, no suitable node found";
448 return m_renderContext->sceneGraphContext()->createInternalRectangleNode();
449 }
450
451 qCDebug(lcTextRecycle) << " Found node, recycling";
452 auto *node = recycleBin->unusedRectangleNodes.last();
453 recycleBin->unusedRectangleNodes.removeLast();
454 return node;
455}
456
457
458QSGInternalImageNode *QSGInternalTextNode::findOrCreateImageNode(RecycleBin *recycleBin)
459{
460 qCDebug(lcTextRecycle) << "Searching for image node";
461
462 if (recycleBin == nullptr || recycleBin->unusedImageNodes.isEmpty()) {
463 qCDebug(lcTextRecycle) << " Creating new image node, no suitable node found";
464 return m_renderContext->sceneGraphContext()->createInternalImageNode(m_renderContext);
465 }
466
467 qCDebug(lcTextRecycle) << " Found node, recycling";
468 auto *node = recycleBin->unusedImageNodes.last();
469 recycleBin->unusedImageNodes.removeLast();
470 return node;
471}
472
473QSGGlyphNode *QSGInternalTextNode::findOrCreateGlyphNode(RenderType renderType,
474 RecycleBin *recycleBin)
475{
476 qCDebug(lcTextRecycle) << "Searching for glyph node with renderType" << renderType;
477 renderType = m_renderContext->sceneGraphContext()->processTextRenderType(renderType);
478
479 if (recycleBin != nullptr) {
480 for (int i = recycleBin->unusedGlyphNodes.size() - 1; i >= 0; --i) {
481 QSGGlyphNode *n = recycleBin->unusedGlyphNodes.at(i);
482 if (n->renderType() == renderType) {
483 qCDebug(lcTextRecycle) << " Found node, recycling";
484 recycleBin->unusedGlyphNodes.removeAt(i);
485 return n;
486 } else {
487 qCDebug(lcTextRecycle) << " Unsuitable node found:" << n->renderType();
488 }
489 }
490 }
491
492 qCDebug(lcTextRecycle) << " Creating new glyph node, no suitable node found";
493 return m_renderContext->sceneGraphContext()->createGlyphNode(m_renderContext,
494 renderType);
495}
496
497void QSGInternalTextNode::clear()
498{
499 while (firstChild() != nullptr)
500 delete firstChild();
501 m_cursorNode = nullptr;
502 qDeleteAll(m_textures);
503 m_textures.clear();
504}
505
506void QSGInternalTextNode::recycle(RecycleBin *recycleBin)
507{
508 Q_ASSERT(recycleBin != nullptr);
509 Q_ASSERT(recycleBin->unusedGlyphNodes.isEmpty());
510 Q_ASSERT(recycleBin->unusedImageNodes.isEmpty());
511 Q_ASSERT(recycleBin->unusedRectangleNodes.isEmpty());
512
513 recycleBin->unusedGlyphNodes.reserve(childCount());
514
515 std::vector<QSGNode *> untrackedNodes;
516 ChildNodeCollector collector(*recycleBin, untrackedNodes);
517
518 // We retain nodes for recycling and then discard the unused ones after adding a new layout.
519 // This is because removing and adding nodes to the root batch can cause expensive reuploads
520 // of all data. So we visit all immediate children of the text node and keep them around
521 // until they are either used or discardUnusedNodes() is called later. For rapid updates,
522 // you will typically just have a single glyph node and rapidly replace its text, so this
523 // is what we are optimzing for.
524 QSGNode *node = firstChild();
525 while (node != nullptr) {
526 if (node->type() == QSGNode::GeometryNodeType
527 && node->flags().testFlag(QSGNode::IsVisitableNode)) {
528 QSGVisitableNode *visitableNode = static_cast<QSGVisitableNode *>(node);
529 visitableNode->accept(&collector);
530 } else {
531 untrackedNodes.push_back(node);
532 }
533
534 node = node->nextSibling();
535 }
536
537 for (QSGGlyphNode *glyphNode : recycleBin->unusedGlyphNodes)
538 glyphNode->recycle();
539
540 qDeleteAll(untrackedNodes);
541
542#if defined(QSGINTERNALTEXTNODE_NO_RECYCLE)
543 qDeleteAll(recycleBin->unusedGlyphNodes);
544 recycleBin->unusedGlyphNodes = {};
545
546 qDeleteAll(recycleBin->unusedImageNodes);
547 recycleBin->unusedImageNodes = {};
548
549 qDeleteAll(recycleBin->unusedRectangleNodes);
550 recycleBin->unusedRectangleNodes = {};
551
552 qDeleteAll(m_textures);
553#else
554 recycleBin->unusedTextures = m_textures;
555#endif
556
557 qCDebug(lcTextRecycle) << "Recycle: "
558 << recycleBin->unusedGlyphNodes.size() << "glyph nodes"
559 << recycleBin->unusedImageNodes.size() << "image nodes"
560 << recycleBin->unusedRectangleNodes.size() << "rectangle nodes"
561 << recycleBin->unusedTextures.size() << "textures";
562
563 m_textures.clear();
564 m_cursorNode = nullptr;
565 recycleBin->unusedGlyphNodes.squeeze();
566}
567
568void QSGInternalTextNode::discardUnusedNodes(RecycleBin *recycleBin)
569{
570 qCDebug(lcTextRecycle) << "Discard: "
571 << recycleBin->unusedGlyphNodes.size() << "glyph nodes"
572 << recycleBin->unusedImageNodes.size() << "image nodes"
573 << recycleBin->unusedRectangleNodes.size() << "rectangle nodes"
574 << recycleBin->unusedTextures.size() << "textures";
575
576 qDeleteAll(recycleBin->unusedGlyphNodes);
577 recycleBin->unusedGlyphNodes = {};
578
579 qDeleteAll(recycleBin->unusedImageNodes);
580 recycleBin->unusedImageNodes = {};
581
582 qDeleteAll(recycleBin->unusedRectangleNodes);
583 recycleBin->unusedRectangleNodes = {};
584
585 qDeleteAll(recycleBin->unusedTextures);
586 recycleBin->unusedTextures = {};
587}
588
589QT_END_NAMESPACE
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)