1// Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
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 "qtext2dentity.h"
5#include "qtext2dentity_p.h"
6#include "qtext2dmaterial_p.h"
7
8#include <QtGui/qtextlayout.h>
9#include <QtGui/qglyphrun.h>
10#include <QtGui/private/qdistancefield_p.h>
11#include <QtGui/private/qtextureglyphcache_p.h>
12#include <QtGui/private/qfont_p.h>
13#include <QtGui/private/qdistancefield_p.h>
14
15#include <Qt3DCore/qbuffer.h>
16#include <Qt3DCore/qattribute.h>
17#include <Qt3DCore/qgeometry.h>
18#include <Qt3DRender/qmaterial.h>
19#include <Qt3DRender/qgeometryrenderer.h>
20
21#include <Qt3DCore/private/qscene_p.h>
22
23QT_BEGIN_NAMESPACE
24
25namespace {
26
27inline constexpr QRectF scaleRectF(const QRectF &rect, qreal scale)
28{
29 return QRectF(rect.left() * scale, rect.top() * scale, rect.width() * scale, rect.height() * scale);
30}
31
32} // anonymous
33
34namespace Qt3DExtras {
35
36/*!
37 * \qmltype Text2DEntity
38 * \nativetype Qt3DExtras::QText2DEntity
39 * \inqmlmodule Qt3D.Extras
40 * \brief Text2DEntity allows creation of a 2D text in 3D space.
41 *
42 * The Text2DEntity renders text as triangles in the XY plane. The geometry will be fitted
43 * in the rectangle of specified width and height. If the resulting geometry is wider than
44 * the specified width, the remainder will be rendered on the new line.
45 *
46 * The entity can be positionned in the scene by adding a transform component.
47 *
48 * Text2DEntity will create geometry based on the shape of the glyphs and a solid
49 * material using the specified color.
50 *
51 */
52
53/*!
54 * \qmlproperty QString Text2DEntity::text
55 *
56 * Holds the text used for the mesh.
57 */
58
59/*!
60 * \qmlproperty QFont Text2DEntity::font
61 *
62 * Holds the font of the text.
63 */
64
65/*!
66 * \qmlproperty QColor Text2DEntity::color
67 *
68 * Holds the color of the text.
69 */
70
71/*!
72 * \qmlproperty float Text2DEntity::width
73 *
74 * Holds the width of the text's bounding rectangle.
75 */
76
77/*!
78 * \qmlproperty float Text2DEntity::height
79 *
80 * Holds the height of the text's bounding rectangle.
81 */
82
83 /*!
84 * \qmlproperty Qt::Alignment Text2DEntity::alignment
85 *
86 * Holds the alignment of the text.
87 */
88
89
90/*!
91 * \class Qt3DExtras::QText2DEntity
92 * \inheaderfile Qt3DExtras/QText2DEntity
93 * \inmodule Qt3DExtras
94 *
95 * \brief QText2DEntity allows creation of a 2D text in 3D space.
96 *
97 * The QText2DEntity renders text as triangles in the XY plane. The geometry will be fitted
98 * in the rectangle of specified width and height. If the resulting geometry is wider than
99 * the specified width, the remainder will be rendered on the new line.
100 *
101 * The entity can be positionned in the scene by adding a transform component.
102 *
103 * QText2DEntity will create geometry based on the shape of the glyphs and a solid
104 * material using the specified color.
105 *
106 */
107
108QHash<Qt3DCore::QScene *, QText2DEntityPrivate::CacheEntry> QText2DEntityPrivate::m_glyphCacheInstances;
109
110QText2DEntityPrivate::QText2DEntityPrivate()
111 : m_glyphCache(nullptr)
112 , m_font(QLatin1String("Times"), 10)
113 , m_scaledFont(QLatin1String("Times"), 10)
114 , m_color(QColor(255, 255, 255, 255))
115 , m_width(0.0f)
116 , m_height(0.0f)
117 , m_alignment(Qt::AlignTop | Qt::AlignLeft)
118{
119}
120
121QText2DEntityPrivate::~QText2DEntityPrivate()
122{
123}
124
125void QText2DEntityPrivate::setScene(Qt3DCore::QScene *scene)
126{
127 if (scene == m_scene)
128 return;
129
130 // Unref old glyph cache if it exists
131 if (m_scene != nullptr) {
132 // Ensure we don't keep reference to glyphs
133 // if we are changing the cache
134 if (m_glyphCache != nullptr)
135 clearCurrentGlyphRuns();
136
137 m_glyphCache = nullptr;
138
139 QText2DEntityPrivate::CacheEntry &entry = QText2DEntityPrivate::m_glyphCacheInstances[m_scene];
140 --entry.count;
141 if (entry.count == 0 && entry.glyphCache != nullptr) {
142
143 delete entry.glyphCache;
144 entry.glyphCache = nullptr;
145 }
146 }
147
148 QEntityPrivate::setScene(scene);
149
150 // Ref new glyph cache is scene is valid
151 if (scene != nullptr) {
152 QText2DEntityPrivate::CacheEntry &entry = QText2DEntityPrivate::m_glyphCacheInstances[scene];
153 if (entry.glyphCache == nullptr) {
154 entry.glyphCache = new QDistanceFieldGlyphCache();
155 entry.glyphCache->setRootNode(scene->rootNode());
156 }
157 m_glyphCache = entry.glyphCache;
158 ++entry.count;
159 // Update to populate glyphCache if needed
160 updateGlyphs();
161 }
162}
163
164QText2DEntity::QText2DEntity(QNode *parent)
165 : Qt3DCore::QEntity(*new QText2DEntityPrivate(), parent)
166{
167}
168
169/*! \internal */
170QText2DEntity::~QText2DEntity()
171{
172}
173
174qreal QText2DEntityPrivate::computeActualScale() const
175{
176 // scale font based on fontScale property and given QFont
177 float scale = 1.0f;
178 if (m_font.pointSizeF() > 0)
179 scale *= m_font.pointSizeF() / m_scaledFont.pointSizeF();
180 return scale;
181}
182
183struct RenderData {
184 int vertexCount = 0;
185 std::vector<float> vertex;
186 std::vector<quint16> index;
187};
188
189void QText2DEntityPrivate::setCurrentGlyphRuns(const QList<QGlyphRun> &runs)
190{
191 // For each distinct texture, we need a separate DistanceFieldTextRenderer,
192 // for which we need vertex and index data
193 QHash<Qt3DRender::QAbstractTexture*, RenderData> renderData;
194 const qreal scale = computeActualScale();
195
196 // process glyph runs
197 for (const QGlyphRun &run : runs) {
198 const auto glyphs = run.glyphIndexes();
199 const auto pos = run.positions();
200
201 Q_ASSERT(glyphs.size() == pos.size());
202
203 const bool doubleGlyphResolution = m_glyphCache->doubleGlyphResolution(font: run.rawFont());
204
205 // faithfully copied from QSGDistanceFieldGlyphNode::updateGeometry()
206 const qreal pixelSize = run.rawFont().pixelSize();
207 const qreal fontScale = pixelSize / QT_DISTANCEFIELD_BASEFONTSIZE(narrowOutlineFont: doubleGlyphResolution);
208 const qreal margin = QT_DISTANCEFIELD_RADIUS(narrowOutlineFont: doubleGlyphResolution) / QT_DISTANCEFIELD_SCALE(narrowOutlineFont: doubleGlyphResolution) * fontScale;
209
210 for (int i = 0; i < glyphs.size(); i++) {
211 const QDistanceFieldGlyphCache::Glyph &dfield = m_glyphCache->refGlyph(font: run.rawFont(), glyph: glyphs[i]);
212
213 if (!dfield.texture)
214 continue;
215
216 RenderData &data = renderData[dfield.texture];
217
218 // faithfully copied from QSGDistanceFieldGlyphNode::updateGeometry()
219 QRectF metrics = scaleRectF(rect: dfield.glyphPathBoundingRect, scale: fontScale);
220 metrics.adjust(xp1: -margin, yp1: margin, xp2: margin, yp2: 3*margin);
221
222 const qreal top = 0.0;
223 const qreal left = 0.0;
224 const qreal right = qreal(m_width);
225 const qreal bottom = qreal(m_height);
226
227 qreal x1 = left + scale * (pos[i].x() + metrics.left());
228 qreal y2 = bottom - scale * (pos[i].y() - metrics.top());
229 qreal x2 = x1 + scale * metrics.width();
230 qreal y1 = y2 - scale * metrics.height();
231
232 // only draw glyphs that are at least partly visible
233 if (y2 < top || x1 > right)
234 continue;
235
236 QRectF texCoords = dfield.texCoords;
237
238 // if a glyph is only partly visible within the given rectangle,
239 // cut it in half and adjust tex coords
240 if (y1 < top) {
241 const auto insideRatio = (top - y2) / (y1 - y2);
242 y1 = top;
243 texCoords.setHeight(texCoords.height() * insideRatio);
244 }
245
246 // do the same thing horizontally
247 if (x2 > right) {
248 const auto insideRatio = (right - x1) / (x2 - x1);
249 x2 = right;
250 texCoords.setWidth(texCoords.width() * insideRatio);
251 }
252
253 for (auto v: std::vector<qreal>{x1, y1, qreal(i), texCoords.left(), texCoords.bottom()})
254 data.vertex.push_back(x: float(v));
255 for (auto v: std::vector<qreal>{x1, y2, qreal(i), texCoords.left(), texCoords.top()})
256 data.vertex.push_back(x: float(v));
257 for (auto v: std::vector<qreal>{x2, y1, qreal(i), texCoords.right(), texCoords.bottom()})
258 data.vertex.push_back(x: float(v));
259 for (auto v: std::vector<qreal>{x2, y2, qreal(i), texCoords.right(), texCoords.top()})
260 data.vertex.push_back(x: float(v));
261
262 for (int i: std::vector<int>{data.vertexCount, data.vertexCount + 3, data.vertexCount + 1})
263 data.index.push_back(x: quint16(i));
264 for (int i: std::vector<int>{data.vertexCount, data.vertexCount + 2, data.vertexCount + 3})
265 data.index.push_back(x: quint16(i));
266
267 data.vertexCount += 4;
268 }
269 }
270
271 // de-ref all glyphs for previous QGlyphRuns
272 for (int i = 0; i < m_currentGlyphRuns.size(); i++)
273 m_glyphCache->derefGlyphs(run: m_currentGlyphRuns[i]);
274 m_currentGlyphRuns = runs;
275
276 // make sure we have the correct number of DistanceFieldTextRenderers
277 // TODO: we might keep one renderer at all times, so we won't delete and
278 // re-allocate one every time the text changes from an empty to a non-empty string
279 // and vice-versa
280 while (m_renderers.size() > renderData.size())
281 delete m_renderers.takeLast();
282
283 while (m_renderers.size() < renderData.size()) {
284 DistanceFieldTextRenderer *renderer = new DistanceFieldTextRenderer();
285 renderer->setColor(m_color);
286 renderer->setParent(q_func());
287 m_renderers << renderer;
288 }
289
290 Q_ASSERT(m_renderers.size() == renderData.size());
291
292 // assign vertex data for all textures to the renderers
293 int rendererIdx = 0;
294 for (auto it = renderData.begin(); it != renderData.end(); ++it)
295 m_renderers[rendererIdx++]->setGlyphData(glyphTexture: it.key(), vertexData: it.value().vertex, indexData: it.value().index);
296}
297
298void QText2DEntityPrivate::clearCurrentGlyphRuns()
299{
300 for (int i = 0; i < m_currentGlyphRuns.size(); i++)
301 m_glyphCache->derefGlyphs(run: m_currentGlyphRuns[i]);
302 m_currentGlyphRuns.clear();
303}
304
305void QText2DEntityPrivate::updateGlyphs()
306{
307 if (m_glyphCache == nullptr)
308 return;
309
310 QList<QGlyphRun> glyphRuns;
311
312 // collect all GlyphRuns generated by the QTextLayout
313 if ((m_width > 0.0f || m_height > 0.0f) && !m_text.isEmpty()) {
314 QTextLayout layout(m_text, m_scaledFont);
315
316 QTextOption textOption = layout.textOption();
317 textOption.setAlignment(m_alignment);
318 layout.setTextOption(textOption);
319
320 const float lineWidth = m_width / computeActualScale();
321 float height = 0;
322 layout.beginLayout();
323
324 while (true) {
325 QTextLine line = layout.createLine();
326 if (!line.isValid())
327 break;
328
329 // position current line
330 line.setLineWidth(lineWidth);
331 line.setPosition(QPointF(0, height));
332 height += line.height();
333
334 // add glyph runs created by line
335 const QList<QGlyphRun> runs = line.glyphRuns();
336 for (const QGlyphRun &run : runs)
337 glyphRuns << run;
338 }
339
340 layout.endLayout();
341 }
342
343 setCurrentGlyphRuns(glyphRuns);
344}
345
346/*!
347 \property Qt3DExtras::QText2DEntity::font
348
349 Holds the font for the text item that is displayed
350 in the Qt Quick scene.
351*/
352QFont QText2DEntity::font() const
353{
354 Q_D(const QText2DEntity);
355 return d->m_font;
356}
357
358void QText2DEntity::setFont(const QFont &font)
359{
360 Q_D(QText2DEntity);
361 if (d->m_font != font) {
362 // ignore the point size of the font, just make it a default value.
363 // still we want to make sure that font() returns the same value
364 // that was passed to setFont(), so we store it nevertheless
365 d->m_font = font;
366 d->m_scaledFont = font;
367 d->m_scaledFont.setPointSize(10);
368
369 emit fontChanged(font);
370
371 if (!d->m_text.isEmpty())
372 d->updateGlyphs();
373 }
374}
375
376/*!
377 \property Qt3DExtras::QText2DEntity::color
378
379 Holds the color for the text item that is displayed in the Qt
380 Quick scene.
381*/
382QColor QText2DEntity::color() const
383{
384 Q_D(const QText2DEntity);
385 return d->m_color;
386}
387
388void QText2DEntity::setColor(const QColor &color)
389{
390 Q_D(QText2DEntity);
391 if (d->m_color != color) {
392 d->m_color = color;
393
394 emit colorChanged(color);
395
396 for (DistanceFieldTextRenderer *renderer : std::as_const(t&: d->m_renderers))
397 renderer->setColor(color);
398 }
399}
400
401/*!
402 \property Qt3DExtras::QText2DEntity::text
403
404 Holds the text that is displayed in the Qt Quick scene.
405*/
406QString QText2DEntity::text() const
407{
408 Q_D(const QText2DEntity);
409 return d->m_text;
410}
411
412void QText2DEntity::setText(const QString &text)
413{
414 Q_D(QText2DEntity);
415 if (d->m_text != text) {
416 d->m_text = text;
417 emit textChanged(text);
418
419 d->updateGlyphs();
420 }
421}
422
423/*!
424 \property Qt3DExtras::QText2DEntity::width
425
426 Returns the width of the text item that is displayed in the
427 Qt Quick scene.
428*/
429float QText2DEntity::width() const
430{
431 Q_D(const QText2DEntity);
432 return d->m_width;
433}
434
435/*!
436 \property Qt3DExtras::QText2DEntity::height
437
438 Returns the height of the text item that is displayed in the
439 Qt Quick scene.
440*/
441float QText2DEntity::height() const
442{
443 Q_D(const QText2DEntity);
444 return d->m_height;
445}
446
447void QText2DEntity::setWidth(float width)
448{
449 Q_D(QText2DEntity);
450 if (width != d->m_width) {
451 d->m_width = width;
452 emit widthChanged(width);
453 d->updateGlyphs();
454 }
455}
456
457void QText2DEntity::setHeight(float height)
458{
459 Q_D(QText2DEntity);
460 if (height != d->m_height) {
461 d->m_height = height;
462 emit heightChanged(height);
463 d->updateGlyphs();
464 }
465}
466
467/*!
468 \property Qt3DExtras::QText2DEntity::alignment
469
470 Returns the alignment of the text item that is displayed in the
471 Qt Quick scene.
472
473 The default alignment is top-left-aligned.
474*/
475Qt::Alignment QText2DEntity::alignment() const
476{
477 Q_D(const QText2DEntity);
478 return d->m_alignment;
479}
480
481void QText2DEntity::setAlignment(Qt::Alignment alignment)
482{
483 Q_D(QText2DEntity);
484 if (alignment != d->m_alignment) {
485 d->m_alignment = alignment;
486 d->updateGlyphs();
487 }
488}
489
490} // namespace Qt3DExtras
491
492QT_END_NAMESPACE
493
494#include "moc_qtext2dentity.cpp"
495

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qt3d/src/extras/text/qtext2dentity.cpp