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 <QtGui/qrawfont.h>
41#include <QtGui/qglyphrun.h>
42#include <QtGui/private/qrawfont_p.h>
43
44#include "qdistancefieldglyphcache_p.h"
45#include "qtextureatlas_p.h"
46
47#include <QtGui/qfont.h>
48#include <QtGui/qpainterpath.h>
49#include <QtGui/private/qdistancefield_p.h>
50#include <Qt3DCore/private/qnode_p.h>
51#include <Qt3DExtras/private/qtextureatlas_p.h>
52
53QT_BEGIN_NAMESPACE
54
55#define DEFAULT_IMAGE_PADDING 1
56
57using namespace Qt3DCore;
58
59namespace Qt3DExtras {
60
61// ref-count glyphs and keep track of where they are stored
62class StoredGlyph {
63public:
64 StoredGlyph() = default;
65 StoredGlyph(const StoredGlyph &) = default;
66 StoredGlyph(const QRawFont &font, quint32 glyph, bool doubleResolution);
67
68 int refCount() const { return m_ref; }
69 void ref() { ++m_ref; }
70 int deref() { return m_ref = std::max(a: m_ref - 1, b: (quint32) 0); }
71
72 bool addToTextureAtlas(QTextureAtlas *atlas);
73 void removeFromTextureAtlas();
74
75 QTextureAtlas *atlas() const { return m_atlas; }
76 QRectF glyphPathBoundingRect() const { return m_glyphPathBoundingRect; }
77 QRectF texCoords() const;
78
79private:
80 quint32 m_ref = 0;
81 QTextureAtlas *m_atlas = nullptr;
82 QTextureAtlas::TextureId m_atlasEntry = QTextureAtlas::InvalidTexture;
83 QRectF m_glyphPathBoundingRect;
84 QImage m_distanceFieldImage; // only used until added to texture atlas
85};
86
87// A DistanceFieldFont stores all glyphs for a given QRawFont.
88// it will use multiple QTextureAtlasess to store the distance
89// fields and uses ref-counting for each glyph to ensure that
90// unused glyphs are removed from the texture atlasses.
91class DistanceFieldFont
92{
93public:
94 DistanceFieldFont(const QRawFont &font, bool doubleRes, Qt3DCore::QNode *parent);
95 ~DistanceFieldFont();
96
97 StoredGlyph findGlyph(quint32 glyph) const;
98 StoredGlyph refGlyph(quint32 glyph);
99 void derefGlyph(quint32 glyph);
100
101 bool doubleGlyphResolution() const { return m_doubleGlyphResolution; }
102
103private:
104 QRawFont m_font;
105 bool m_doubleGlyphResolution;
106 Qt3DCore::QNode *m_parentNode; // parent node for the QTextureAtlasses
107
108 QHash<quint32, StoredGlyph> m_glyphs;
109
110 QVector<QTextureAtlas*> m_atlasses;
111};
112
113StoredGlyph::StoredGlyph(const QRawFont &font, quint32 glyph, bool doubleResolution)
114 : m_ref(1)
115 , m_atlas(nullptr)
116 , m_atlasEntry(QTextureAtlas::InvalidTexture)
117{
118 // create new single-channel distance field image for given glyph
119 const QPainterPath path = font.pathForGlyph(glyphIndex: glyph);
120 const QDistanceField dfield(font, glyph, doubleResolution);
121 m_distanceFieldImage = dfield.toImage(format: QImage::Format_Alpha8);
122
123 // scale bounding rect down (as in QSGDistanceFieldGlyphCache::glyphData())
124 const QRectF pathBound = path.boundingRect();
125 float f = 1.0f / QT_DISTANCEFIELD_SCALE(narrowOutlineFont: doubleResolution);
126 m_glyphPathBoundingRect = QRectF(pathBound.left() * f, -pathBound.top() * f, pathBound.width() * f, pathBound.height() * f);
127}
128
129bool StoredGlyph::addToTextureAtlas(QTextureAtlas *atlas)
130{
131 if (m_atlas || m_distanceFieldImage.isNull())
132 return false;
133
134 const auto texId = atlas->addImage(image: m_distanceFieldImage, DEFAULT_IMAGE_PADDING);
135 if (texId != QTextureAtlas::InvalidTexture) {
136 m_atlas = atlas;
137 m_atlasEntry = texId;
138 m_distanceFieldImage = QImage(); // free glyph image data
139 return true;
140 }
141
142 return false;
143}
144
145void StoredGlyph::removeFromTextureAtlas()
146{
147 if (m_atlas) {
148 m_atlas->removeImage(id: m_atlasEntry);
149 m_atlas = nullptr;
150 m_atlasEntry = QTextureAtlas::InvalidTexture;
151 }
152}
153
154QRectF StoredGlyph::texCoords() const
155{
156 return m_atlas ? m_atlas->imageTexCoords(id: m_atlasEntry) : QRectF();
157}
158
159DistanceFieldFont::DistanceFieldFont(const QRawFont &font, bool doubleRes, Qt3DCore::QNode *parent)
160 : m_font(font)
161 , m_doubleGlyphResolution(doubleRes)
162 , m_parentNode(parent)
163{
164}
165
166DistanceFieldFont::~DistanceFieldFont()
167{
168 qDeleteAll(c: m_atlasses);
169}
170
171StoredGlyph DistanceFieldFont::findGlyph(quint32 glyph) const
172{
173 const auto it = m_glyphs.find(akey: glyph);
174 return (it != m_glyphs.cend()) ? it.value() : StoredGlyph();
175}
176
177StoredGlyph DistanceFieldFont::refGlyph(quint32 glyph)
178{
179 // if glyph already exists, just increase ref-count
180 auto it = m_glyphs.find(akey: glyph);
181 if (it != m_glyphs.end()) {
182 it.value().ref();
183 return it.value();
184 }
185
186 // need to create new glyph
187 StoredGlyph storedGlyph(m_font, glyph, m_doubleGlyphResolution);
188
189 // see if one of the existing atlasses can hold the distance field image
190 for (int i = 0; i < m_atlasses.size(); i++)
191 if (storedGlyph.addToTextureAtlas(atlas: m_atlasses[i]))
192 break;
193
194 // if no texture atlas is big enough (or no exists yet), allocate a new one
195 if (!storedGlyph.atlas()) {
196 // this should be enough to store 40-60 glyphs, which should be sufficient for most
197 // scenarios
198 const int size = m_doubleGlyphResolution ? 512 : 256;
199
200 QTextureAtlas *atlas = new QTextureAtlas(m_parentNode);
201 atlas->setWidth(size);
202 atlas->setHeight(size);
203 atlas->setFormat(Qt3DRender::QAbstractTexture::R8_UNorm);
204 atlas->setPixelFormat(QOpenGLTexture::Red);
205 atlas->setMinificationFilter(Qt3DRender::QAbstractTexture::Linear);
206 atlas->setMagnificationFilter(Qt3DRender::QAbstractTexture::Linear);
207 m_atlasses << atlas;
208
209 if (!storedGlyph.addToTextureAtlas(atlas))
210 qWarning() << Q_FUNC_INFO << "Couldn't add glyph to newly allocated atlas. Glyph could be huge?";
211 }
212
213 m_glyphs.insert(akey: glyph, avalue: storedGlyph);
214 return storedGlyph;
215}
216
217void DistanceFieldFont::derefGlyph(quint32 glyph)
218{
219 auto it = m_glyphs.find(akey: glyph);
220 if (it == m_glyphs.end())
221 return;
222
223 // TODO
224 // possible optimization: keep unreferenced glyphs as the texture atlas
225 // still has space. only if a new glyph needs to be allocated, and there
226 // is no more space within the atlas, then we can actually remove the glyphs
227 // from the atlasses.
228
229 // remove glyph if no refs anymore
230 if (it.value().deref() <= 0) {
231 QTextureAtlas *atlas = it.value().atlas();
232 it.value().removeFromTextureAtlas();
233
234 // remove atlas, if it contains no glyphs anymore
235 if (atlas && atlas->imageCount() == 0) {
236 Q_ASSERT(m_atlasses.contains(atlas));
237
238 m_atlasses.removeAll(t: atlas);
239 delete atlas;
240 }
241
242 m_glyphs.erase(it);
243 }
244}
245
246// copied from QSGDistanceFieldGlyphCacheManager::fontKey
247// we use this function to compare QRawFonts, as QRawFont doesn't
248// implement a stable comparison function
249QString QDistanceFieldGlyphCache::fontKey(const QRawFont &font)
250{
251 QFontEngine *fe = QRawFontPrivate::get(font)->fontEngine;
252 if (!fe->faceId().filename.isEmpty()) {
253 QByteArray keyName = fe->faceId().filename;
254 if (font.style() != QFont::StyleNormal)
255 keyName += QByteArray(" I");
256 if (font.weight() != QFont::Normal)
257 keyName += ' ' + QByteArray::number(font.weight());
258 keyName += QByteArray(" DF");
259 return QString::fromUtf8(str: keyName);
260 } else {
261 return QString::fromLatin1(str: "%1_%2_%3_%4")
262 .arg(a: font.familyName())
263 .arg(a: font.styleName())
264 .arg(a: font.weight())
265 .arg(a: font.style());
266 }
267}
268
269DistanceFieldFont* QDistanceFieldGlyphCache::getOrCreateDistanceFieldFont(const QRawFont &font)
270{
271 // return, if font already exists (make sure to only create one DistanceFieldFont for
272 // each unique QRawFont, by building a hash on the QRawFont that ignores the font size)
273 const QString key = fontKey(font);
274 const auto it = m_fonts.constFind(akey: key);
275 if (it != m_fonts.cend())
276 return it.value();
277
278 // logic taken from QSGDistanceFieldGlyphCache::QSGDistanceFieldGlyphCache
279 QRawFontPrivate *fontD = QRawFontPrivate::get(font);
280 const int glyphCount = fontD->fontEngine->glyphCount();
281 const bool useDoubleRes = qt_fontHasNarrowOutlines(f: font) && glyphCount < QT_DISTANCEFIELD_HIGHGLYPHCOUNT();
282
283 // only keep one FontCache with a fixed pixel size for each distinct font type
284 QRawFont actualFont = font;
285 actualFont.setPixelSize(QT_DISTANCEFIELD_BASEFONTSIZE(narrowOutlineFont: useDoubleRes) * QT_DISTANCEFIELD_SCALE(narrowOutlineFont: useDoubleRes));
286
287 // create new font cache
288 // we set the parent node to nullptr, since the parent node of QTextureAtlasses
289 // will be set when we pass them to QText2DMaterial later
290 DistanceFieldFont *dff = new DistanceFieldFont(actualFont, useDoubleRes, nullptr);
291 m_fonts.insert(akey: key, avalue: dff);
292 return dff;
293}
294
295QDistanceFieldGlyphCache::QDistanceFieldGlyphCache()
296 : m_rootNode(nullptr)
297{
298}
299
300QDistanceFieldGlyphCache::~QDistanceFieldGlyphCache()
301{
302}
303
304void QDistanceFieldGlyphCache::setRootNode(QNode *rootNode)
305{
306 m_rootNode = rootNode;
307}
308
309QNode *QDistanceFieldGlyphCache::rootNode() const
310{
311 return m_rootNode;
312}
313
314bool QDistanceFieldGlyphCache::doubleGlyphResolution(const QRawFont &font)
315{
316 return getOrCreateDistanceFieldFont(font)->doubleGlyphResolution();
317}
318
319namespace {
320QDistanceFieldGlyphCache::Glyph refAndGetGlyph(DistanceFieldFont *dff, quint32 glyph)
321{
322 QDistanceFieldGlyphCache::Glyph ret;
323
324 if (dff) {
325 const auto entry = dff->refGlyph(glyph);
326
327 if (entry.atlas()) {
328 ret.glyphPathBoundingRect = entry.glyphPathBoundingRect();
329 ret.texCoords = entry.texCoords();
330 ret.texture = entry.atlas();
331 }
332 }
333
334 return ret;
335}
336} // anonymous
337
338QVector<QDistanceFieldGlyphCache::Glyph> QDistanceFieldGlyphCache::refGlyphs(const QGlyphRun &run)
339{
340 DistanceFieldFont *dff = getOrCreateDistanceFieldFont(font: run.rawFont());
341 QVector<QDistanceFieldGlyphCache::Glyph> ret;
342
343 const QVector<quint32> glyphs = run.glyphIndexes();
344 for (quint32 glyph : glyphs)
345 ret << refAndGetGlyph(dff, glyph);
346
347 return ret;
348}
349
350QDistanceFieldGlyphCache::Glyph QDistanceFieldGlyphCache::refGlyph(const QRawFont &font, quint32 glyph)
351{
352 return refAndGetGlyph(dff: getOrCreateDistanceFieldFont(font), glyph);
353}
354
355void QDistanceFieldGlyphCache::derefGlyphs(const QGlyphRun &run)
356{
357 DistanceFieldFont *dff = getOrCreateDistanceFieldFont(font: run.rawFont());
358
359 const QVector<quint32> glyphs = run.glyphIndexes();
360 for (quint32 glyph : glyphs)
361 dff->derefGlyph(glyph);
362}
363
364void QDistanceFieldGlyphCache::derefGlyph(const QRawFont &font, quint32 glyph)
365{
366 getOrCreateDistanceFieldFont(font)->derefGlyph(glyph);
367}
368
369} // namespace Qt3DExtras
370
371QT_END_NAMESPACE
372

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