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

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