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
4#include "qsginternaltextnode_p.h"
5
6#include "qquicktextnodeengine_p.h"
7
8#include <private/qsgadaptationlayer_p.h>
9#include <private/qsgdistancefieldglyphnode_p.h>
10#include <private/qquickclipnode_p.h>
11#include <private/qquickitem_p.h>
12#include <private/qquicktextdocument_p.h>
13
14#include <QtCore/qpoint.h>
15#include <qtextdocument.h>
16#include <qtextlayout.h>
17#include <qabstracttextdocumentlayout.h>
18#include <private/qquickstyledtext_p.h>
19#include <private/qquicktext_p_p.h>
20#include <private/qfont_p.h>
21#include <private/qfontengine_p.h>
22
23#include <private/qtextdocumentlayout_p.h>
24#include <qhash.h>
25
26QT_BEGIN_NAMESPACE
27
28/*!
29 Creates an empty QSGInternalTextNode
30*/
31QSGInternalTextNode::QSGInternalTextNode(QSGRenderContext *renderContext)
32 : m_renderContext(renderContext)
33{
34#ifdef QSG_RUNTIME_DESCRIPTION
35 qsgnode_set_description(node: this, description: QLatin1String("text"));
36#endif
37
38 static_assert(int(QSGTextNode::Normal) == int(QQuickText::Normal));
39 static_assert(int(QSGTextNode::Outline) == int(QQuickText::Outline));
40 static_assert(int(QSGTextNode::Raised) == int(QQuickText::Raised));
41 static_assert(int(QSGTextNode::Sunken) == int(QQuickText::Sunken));
42
43 static_assert(int(QSGTextNode::QtRendering) == int(QQuickText::QtRendering));
44 static_assert(int(QSGTextNode::NativeRendering) == int(QQuickText::NativeRendering));
45 static_assert(int(QSGTextNode::CurveRendering) == int(QQuickText::CurveRendering));
46}
47
48QSGInternalTextNode::~QSGInternalTextNode()
49{
50 qDeleteAll(c: m_textures);
51}
52
53QSGGlyphNode *QSGInternalTextNode::addGlyphs(const QPointF &position, const QGlyphRun &glyphs, const QColor &color,
54 QQuickText::TextStyle style, const QColor &styleColor,
55 QSGNode *parentNode)
56{
57 QRawFont font = glyphs.rawFont();
58
59 QSGTextNode::RenderType preferredRenderType = m_renderType;
60 if (m_renderType != NativeRendering) {
61 if (const QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine)
62 if (fe->hasUnreliableGlyphOutline() || !fe->isSmoothlyScalable)
63 preferredRenderType = QSGTextNode::NativeRendering;
64 }
65
66 if (preferredRenderType == NativeRendering)
67 m_containsUnscalableGlyphs = true;
68
69 QSGGlyphNode *node = m_renderContext->sceneGraphContext()->createGlyphNode(rc: m_renderContext,
70 renderType: preferredRenderType,
71 renderTypeQuality: m_renderTypeQuality);
72 node->setGlyphs(position: position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
73 node->setStyle(style);
74 node->setStyleColor(styleColor);
75 node->setColor(color);
76 node->update();
77
78 /* We flag the geometry as static, but we never call markVertexDataDirty
79 or markIndexDataDirty on them. This is because all text nodes are
80 discarded when a change occurs. If we start appending/removing from
81 existing geometry, then we also need to start marking the geometry as
82 dirty.
83 */
84 node->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
85 node->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
86
87 if (parentNode == nullptr)
88 parentNode = this;
89 parentNode->appendChildNode(node);
90
91 if (style == QQuickText::Outline && color.alpha() > 0 && styleColor != color) {
92 QSGGlyphNode *fillNode = m_renderContext->sceneGraphContext()->createGlyphNode(rc: m_renderContext,
93 renderType: preferredRenderType,
94 renderTypeQuality: m_renderTypeQuality);
95 fillNode->setGlyphs(position: position + QPointF(0, glyphs.rawFont().ascent()), glyphs);
96 fillNode->setStyle(QQuickText::Normal);
97 fillNode->setPreferredAntialiasingMode(QSGGlyphNode::GrayAntialiasing);
98 fillNode->setColor(color);
99 fillNode->update();
100
101 fillNode->geometry()->setIndexDataPattern(QSGGeometry::StaticPattern);
102 fillNode->geometry()->setVertexDataPattern(QSGGeometry::StaticPattern);
103
104 parentNode->appendChildNode(node: fillNode);
105 fillNode->setRenderOrder(node->renderOrder() + 1);
106 }
107
108 return node;
109}
110
111void QSGInternalTextNode::setCursor(const QRectF &rect, const QColor &color)
112{
113 if (m_cursorNode != nullptr)
114 delete m_cursorNode;
115
116 m_cursorNode = m_renderContext->sceneGraphContext()->createInternalRectangleNode(rect, c: color);
117 appendChildNode(node: m_cursorNode);
118}
119
120void QSGInternalTextNode::clearCursor()
121{
122 if (m_cursorNode)
123 removeChildNode(node: m_cursorNode);
124 delete m_cursorNode;
125 m_cursorNode = nullptr;
126}
127
128void QSGInternalTextNode::addDecorationNode(const QRectF &rect, const QColor &color)
129{
130 addRectangleNode(rect, color);
131}
132
133void QSGInternalTextNode::addRectangleNode(const QRectF &rect, const QColor &color)
134{
135 appendChildNode(node: m_renderContext->sceneGraphContext()->createInternalRectangleNode(rect, c: color));
136}
137
138void QSGInternalTextNode::addImage(const QRectF &rect, const QImage &image)
139{
140 QSGInternalImageNode *node = m_renderContext->sceneGraphContext()->createInternalImageNode(renderContext: m_renderContext);
141 QSGTexture *texture = m_renderContext->createTexture(image);
142 texture->setFiltering(m_filtering);
143 m_textures.append(t: texture);
144 node->setTargetRect(rect);
145 node->setInnerTargetRect(rect);
146 node->setTexture(texture);
147 node->setFiltering(m_filtering);
148 appendChildNode(node);
149 node->update();
150}
151
152void QSGInternalTextNode::doAddTextDocument(QPointF position, QTextDocument *textDocument,
153 int selectionStart, int selectionEnd)
154{
155 QQuickTextNodeEngine engine;
156 engine.setTextColor(m_color);
157 engine.setSelectedTextColor(m_selectionTextColor);
158 engine.setSelectionColor(m_selectionColor);
159 engine.setAnchorColor(m_linkColor);
160 engine.setPosition(position);
161 engine.setDevicePixelRatio(m_devicePixelRatio);
162
163 QList<QTextFrame *> frames;
164 frames.append(t: textDocument->rootFrame());
165 while (!frames.isEmpty()) {
166 QTextFrame *textFrame = frames.takeFirst();
167 frames.append(other: textFrame->childFrames());
168
169 engine.addFrameDecorations(document: textDocument, frame: textFrame);
170
171 if (textFrame->firstPosition() > textFrame->lastPosition()
172 && textFrame->frameFormat().position() != QTextFrameFormat::InFlow) {
173 const int pos = textFrame->firstPosition() - 1;
174 auto *a = static_cast<QtPrivate::ProtectedLayoutAccessor *>(textDocument->documentLayout());
175 QTextCharFormat format = a->formatAccessor(pos);
176 QRectF rect = a->frameBoundingRect(frame: textFrame);
177
178 QTextBlock block = textFrame->firstCursorPosition().block();
179 engine.setCurrentLine(block.layout()->lineForTextPosition(pos: pos - block.position()));
180 engine.addTextObject(block, position: rect.topLeft(), format, selectionState: QQuickTextNodeEngine::Unselected, textDocument,
181 pos, layoutPosition: textFrame->frameFormat().position());
182 } else {
183 QTextFrame::iterator it = textFrame->begin();
184
185 while (!it.atEnd()) {
186 Q_ASSERT(!engine.currentLine().isValid());
187
188 QTextBlock block = it.currentBlock();
189 engine.addTextBlock(textDocument, block, position, textColor: m_color, anchorColor: m_linkColor, selectionStart, selectionEnd,
190 viewport: (textDocument->characterCount() > QQuickTextPrivate::largeTextSizeThreshold ?
191 m_viewport : QRectF()));
192 ++it;
193 }
194 }
195 }
196
197 engine.addToSceneGraph(parent: this, style: QQuickText::TextStyle(m_textStyle), styleColor: m_styleColor);
198}
199
200void QSGInternalTextNode::doAddTextLayout(QPointF position, QTextLayout *textLayout,
201 int selectionStart, int selectionEnd,
202 int lineStart, int lineCount)
203{
204 QQuickTextNodeEngine engine;
205 engine.setTextColor(m_color);
206 engine.setSelectedTextColor(m_selectionTextColor);
207 engine.setSelectionColor(m_selectionColor);
208 engine.setAnchorColor(m_linkColor);
209 engine.setPosition(position);
210 engine.setDevicePixelRatio(m_devicePixelRatio);
211
212#if QT_CONFIG(im)
213 int preeditLength = textLayout->preeditAreaText().size();
214 int preeditPosition = textLayout->preeditAreaPosition();
215#endif
216
217 QVarLengthArray<QTextLayout::FormatRange> colorChanges;
218 engine.mergeFormats(textLayout, mergedFormats: &colorChanges);
219
220 lineCount = lineCount >= 0
221 ? qMin(a: lineStart + lineCount, b: textLayout->lineCount())
222 : textLayout->lineCount();
223
224 bool inViewport = false;
225 for (int i=lineStart; i<lineCount; ++i) {
226 QTextLine line = textLayout->lineAt(i);
227
228 int start = line.textStart();
229 int length = line.textLength();
230 int end = start + length;
231
232#if QT_CONFIG(im)
233 if (preeditPosition >= 0
234 && preeditPosition >= start
235 && preeditPosition < end) {
236 end += preeditLength;
237 }
238#endif
239 // If there's a lot of text, insert only the range of lines that can possibly be visible within the viewport.
240 if (m_viewport.isNull() || (line.y() + line.height() > m_viewport.top() && line.y() < m_viewport.bottom())) {
241 if (!inViewport && !m_viewport.isNull()) {
242 m_firstLineInViewport = i;
243 qCDebug(lcVP) << "first line in viewport" << i << "@" << line.y();
244 }
245 inViewport = true;
246 engine.setCurrentLine(line);
247 engine.addGlyphsForRanges(ranges: colorChanges, start, end, selectionStart, selectionEnd);
248 } else if (inViewport) {
249 Q_ASSERT(!m_viewport.isNull());
250 m_firstLinePastViewport = i;
251 qCDebug(lcVP) << "first omitted line past bottom of viewport" << i << "@" << line.y();
252 break; // went past the bottom of the viewport, so we're done
253 }
254 }
255
256 engine.addToSceneGraph(parent: this, style: QQuickText::TextStyle(m_textStyle), styleColor: m_styleColor);
257}
258
259void QSGInternalTextNode::clear()
260{
261 while (firstChild() != nullptr)
262 delete firstChild();
263 m_cursorNode = nullptr;
264 qDeleteAll(c: m_textures);
265 m_textures.clear();
266}
267
268QT_END_NAMESPACE
269

source code of qtdeclarative/src/quick/items/qsginternaltextnode.cpp