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

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