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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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