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 | |
59 | QT_BEGIN_NAMESPACE |
60 | |
61 | namespace { |
62 | |
63 | inline 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 | |
70 | namespace 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 | |
138 | QHash<Qt3DCore::QScene *, QText2DEntityPrivate::CacheEntry> QText2DEntityPrivate::; |
139 | |
140 | 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 | |
150 | QText2DEntityPrivate::() |
151 | { |
152 | } |
153 | |
154 | void QText2DEntityPrivate::(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 | |
193 | QText2DEntity::(QNode *parent) |
194 | : Qt3DCore::QEntity(*new QText2DEntityPrivate(), parent) |
195 | { |
196 | } |
197 | |
198 | /*! \internal */ |
199 | QText2DEntity::() |
200 | { |
201 | } |
202 | |
203 | float QText2DEntityPrivate::() 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 | |
212 | struct { |
213 | int = 0; |
214 | QVector<float> ; |
215 | QVector<quint16> ; |
216 | }; |
217 | |
218 | void QText2DEntityPrivate::(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 | |
321 | void QText2DEntityPrivate::() |
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 | |
328 | void QText2DEntityPrivate::() |
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 | */ |
370 | QFont QText2DEntity::() const |
371 | { |
372 | Q_D(const QText2DEntity); |
373 | return d->m_font; |
374 | } |
375 | |
376 | void QText2DEntity::(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 | */ |
400 | QColor QText2DEntity::() const |
401 | { |
402 | Q_D(const QText2DEntity); |
403 | return d->m_color; |
404 | } |
405 | |
406 | void QText2DEntity::(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 | */ |
424 | QString QText2DEntity::() const |
425 | { |
426 | Q_D(const QText2DEntity); |
427 | return d->m_text; |
428 | } |
429 | |
430 | void QText2DEntity::(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 | */ |
447 | float QText2DEntity::() 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 | */ |
459 | float QText2DEntity::() const |
460 | { |
461 | Q_D(const QText2DEntity); |
462 | return d->m_height; |
463 | } |
464 | |
465 | void QText2DEntity::(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 | |
475 | void QText2DEntity::(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 | |
487 | QT_END_NAMESPACE |
488 | |