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

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