Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qwasmfontdatabase.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7
8#include <QtCore/qfile.h>
9#include <QtCore/private/qstdweb_p.h>
10#include <QtCore/private/qeventdispatcher_wasm_p.h>
11#include <QtGui/private/qguiapplication_p.h>
12
13#include <emscripten.h>
14#include <emscripten/val.h>
15#include <emscripten/bind.h>
16
17#include <map>
18#include <array>
19
20QT_BEGIN_NAMESPACE
21
22using namespace emscripten;
23using namespace Qt::StringLiterals;
24
25
26namespace {
27
28class FontData
29{
30public:
31 FontData(val fontData)
32 :m_fontData(fontData) {}
33
34 QString family() const
35 {
36 return QString::fromStdString(m_fontData["family"].as<std::string>());
37 }
38
39 QString fullName() const
40 {
41 return QString::fromStdString(m_fontData["fullName"].as<std::string>());
42 }
43
44 QString postscriptName() const
45 {
46 return QString::fromStdString(m_fontData["postscriptName"].as<std::string>());
47 }
48
49 QString style() const
50 {
51 return QString::fromStdString(m_fontData["style"].as<std::string>());
52 }
53
54 val value() const
55 {
56 return m_fontData;
57 }
58
59private:
60 val m_fontData;
61};
62
63val makeObject(const char *key, const char *value)
64{
65 val obj = val::object();
66 obj.set(key, std::string(value));
67 return obj;
68}
69
70void printError(val err) {
71 qCWarning(lcQpaFonts)
72 << QString::fromStdString(err["name"].as<std::string>())
73 << QString::fromStdString(err["message"].as<std::string>());
74 QWasmFontDatabase::endAllFontFileLoading();
75}
76
77void checkFontAccessPermitted(std::function<void(bool)> callback)
78{
79 const val permissions = val::global("navigator")["permissions"];
80 if (permissions.isUndefined()) {
81 callback(false);
82 return;
83 }
84
85 qstdweb::Promise::make(permissions, "query", {
86 .thenFunc = [callback](val status) {
87 callback(status["state"].as<std::string>() == "granted");
88 },
89 }, makeObject("name", "local-fonts"));
90}
91
92void queryLocalFonts(std::function<void(const QList<FontData> &)> callback)
93{
94 emscripten::val window = emscripten::val::global("window");
95 qstdweb::Promise::make(window, "queryLocalFonts", {
96 .thenFunc = [callback](emscripten::val fontArray) {
97 QList<FontData> fonts;
98 const int count = fontArray["length"].as<int>();
99 fonts.reserve(count);
100 for (int i = 0; i < count; ++i)
101 fonts.append(FontData(fontArray.call<emscripten::val>("at", i)));
102 callback(fonts);
103 },
104 .catchFunc = printError
105 });
106}
107
108void readBlob(val blob, std::function<void(const QByteArray &)> callback)
109{
110 qstdweb::Promise::make(blob, "arrayBuffer", {
111 .thenFunc = [callback](emscripten::val fontArrayBuffer) {
112 QByteArray fontData = qstdweb::Uint8Array(qstdweb::ArrayBuffer(fontArrayBuffer)).copyToQByteArray();
113 callback(fontData);
114 },
115 .catchFunc = printError
116 });
117}
118
119void readFont(FontData font, std::function<void(const QByteArray &)> callback)
120{
121 qstdweb::Promise::make(font.value(), "blob", {
122 .thenFunc = [callback](val blob) {
123 readBlob(blob, [callback](const QByteArray &data) {
124 callback(data);
125 });
126 },
127 .catchFunc = printError
128 });
129}
130
131emscripten::val getLocalFontsConfigProperty(const char *name) {
132 emscripten::val qt = val::module_property("qt");
133 if (qt.isUndefined())
134 return emscripten::val();
135 emscripten::val localFonts = qt["localFonts"];
136 if (localFonts.isUndefined())
137 return emscripten::val();
138 return localFonts[name];
139};
140
141bool getLocalFontsBoolConfigPropertyWithDefault(const char *name, bool defaultValue) {
142 emscripten::val prop = getLocalFontsConfigProperty(name);
143 if (prop.isUndefined())
144 return defaultValue;
145 return prop.as<bool>();
146};
147
148QString getLocalFontsStringConfigPropertyWithDefault(const char *name, QString defaultValue) {
149 emscripten::val prop = getLocalFontsConfigProperty(name);
150 if (prop.isUndefined())
151 return defaultValue;
152 return QString::fromStdString(prop.as<std::string>());
153};
154
155QStringList getLocalFontsStringListConfigPropertyWithDefault(const char *name, QStringList defaultValue) {
156 emscripten::val array = getLocalFontsConfigProperty(name);
157 if (array.isUndefined())
158 return defaultValue;
159
160 QStringList list;
161 int size = array["length"].as<int>();
162 for (int i = 0; i < size; ++i) {
163 emscripten::val element = array.call<emscripten::val>("at", i);
164 QString string = QString::fromStdString(element.as<std::string>());
165 if (!string.isEmpty())
166 list.append(string);
167 }
168 return list;
169};
170
171} // namespace
172
173QWasmFontDatabase::QWasmFontDatabase()
174:QFreeTypeFontDatabase()
175{
176 m_localFontsApiSupported = val::global("window")["queryLocalFonts"].isUndefined() == false;
177}
178
179QWasmFontDatabase *QWasmFontDatabase::get()
180{
181 return static_cast<QWasmFontDatabase *>(QWasmIntegration::get()->fontDatabase());
182}
183
184// Populates the font database with local fonts. Will make the browser ask
185// the user for permission if needed. Does nothing if the Local Font Access API
186// is not supported.
187void QWasmFontDatabase::populateLocalfonts()
188{
189 // Decide which font families to populate based on user preferences
190 QStringList selectedLocalFontFamilies;
191 bool allFamilies = false;
192
193 switch (m_localFontFamilyLoadSet) {
194 case NoFontFamilies:
195 default:
196 // keep empty selectedLocalFontFamilies
197 break;
198 case DefaultFontFamilies: {
199 const QStringList webSafeFontFamilies =
200 {"Arial", "Verdana", "Tahoma", "Trebuchet", "Times New Roman",
201 "Georgia", "Garamond", "Courier New"};
202 selectedLocalFontFamilies = webSafeFontFamilies;
203 } break;
204 case AllFontFamilies:
205 allFamilies = true;
206 break;
207 }
208
209 selectedLocalFontFamilies += m_extraLocalFontFamilies;
210
211 if (selectedLocalFontFamilies.isEmpty() && !allFamilies) {
212 endAllFontFileLoading();
213 return;
214 }
215
216 populateLocalFontFamilies(selectedLocalFontFamilies, allFamilies);
217}
218
219namespace {
220 QStringList toStringList(emscripten::val array)
221 {
222 QStringList list;
223 int size = array["length"].as<int>();
224 for (int i = 0; i < size; ++i) {
225 emscripten::val element = array.call<emscripten::val>("at", i);
226 QString string = QString::fromStdString(element.as<std::string>());
227 if (!string.isEmpty())
228 list.append(string);
229 }
230 return list;
231 }
232}
233
234void QWasmFontDatabase::populateLocalFontFamilies(emscripten::val families)
235{
236 if (!m_localFontsApiSupported)
237 return;
238 populateLocalFontFamilies(toStringList(families), false);
239}
240
241void QWasmFontDatabase::populateLocalFontFamilies(const QStringList &fontFamilies, bool allFamilies)
242{
243 queryLocalFonts([fontFamilies, allFamilies](const QList<FontData> &fonts) {
244 refFontFileLoading();
245 QList<FontData> filteredFonts;
246 std::copy_if(fonts.begin(), fonts.end(), std::back_inserter(filteredFonts),
247 [fontFamilies, allFamilies](FontData fontData) {
248 return allFamilies || fontFamilies.contains(fontData.family());
249 });
250
251 for (const FontData &font: filteredFonts) {
252 refFontFileLoading();
253 readFont(font, [font](const QByteArray &fontData){
254 QFreeTypeFontDatabase::registerFontFamily(font.family());
255 QFreeTypeFontDatabase::addTTFile(fontData, QByteArray());
256 derefFontFileLoading();
257 });
258 }
259 derefFontFileLoading();
260 });
261
262}
263
264void QWasmFontDatabase::populateFontDatabase()
265{
266 // Load bundled font file from resources.
267 const QString fontFileNames[] = {
268 QStringLiteral(":/fonts/DejaVuSansMono.ttf"),
269 QStringLiteral(":/fonts/DejaVuSans.ttf"),
270 };
271 for (const QString &fontFileName : fontFileNames) {
272 QFile theFont(fontFileName);
273 if (!theFont.open(QIODevice::ReadOnly))
274 break;
275
276 QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1());
277 }
278
279 // Get config options for controlling local fonts usage
280 m_queryLocalFontsPermission = getLocalFontsBoolConfigPropertyWithDefault("requestPermission", false);
281 QString fontFamilyLoadSet = getLocalFontsStringConfigPropertyWithDefault("familiesCollection", "DefaultFontFamilies");
282 m_extraLocalFontFamilies = getLocalFontsStringListConfigPropertyWithDefault("extraFamilies", QStringList());
283
284 if (fontFamilyLoadSet == "NoFontFamilies") {
285 m_localFontFamilyLoadSet = NoFontFamilies;
286 } else if (fontFamilyLoadSet == "DefaultFontFamilies") {
287 m_localFontFamilyLoadSet = DefaultFontFamilies;
288 } else if (fontFamilyLoadSet == "AllFontFamilies") {
289 m_localFontFamilyLoadSet = AllFontFamilies;
290 } else {
291 m_localFontFamilyLoadSet = NoFontFamilies;
292 qWarning() << "Unknown fontFamilyLoadSet value" << fontFamilyLoadSet;
293 }
294
295 if (!m_localFontsApiSupported)
296 return;
297
298 // Populate the font database with local fonts. Either try unconditianlly
299 // if displyaing a fonts permissions dialog at startup is allowed, or else
300 // only if we already have permission.
301 if (m_queryLocalFontsPermission) {
302 populateLocalfonts();
303 } else {
304 checkFontAccessPermitted([this](bool granted) {
305 if (granted)
306 populateLocalfonts();
307 else
308 endAllFontFileLoading();
309 });
310 }
311}
312
313QFontEngine *QWasmFontDatabase::fontEngine(const QFontDef &fontDef, void *handle)
314{
315 QFontEngine *fontEngine = QFreeTypeFontDatabase::fontEngine(fontDef, handle);
316 return fontEngine;
317}
318
319QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style,
320 QFont::StyleHint styleHint,
321 QFontDatabasePrivate::ExtendedScript script) const
322{
323 QStringList fallbacks
324 = QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script);
325
326 // Add the DejaVuSans.ttf font (loaded in populateFontDatabase above) as a falback font
327 // to all other fonts (except itself).
328 static const QString wasmFallbackFonts[] = { "DejaVu Sans" };
329 for (auto wasmFallbackFont : wasmFallbackFonts) {
330 if (family != wasmFallbackFont && !fallbacks.contains(wasmFallbackFont))
331 fallbacks.append(wasmFallbackFont);
332 }
333
334 return fallbacks;
335}
336
337void QWasmFontDatabase::releaseHandle(void *handle)
338{
339 QFreeTypeFontDatabase::releaseHandle(handle);
340}
341
342QFont QWasmFontDatabase::defaultFont() const
343{
344 return QFont("DejaVu Sans"_L1);
345}
346
347namespace {
348 int g_pendingFonts = 0;
349}
350
351// Registers that a font file will be loaded.
352void QWasmFontDatabase::refFontFileLoading()
353{
354 g_pendingFonts += 1;
355}
356
357// Registers that one font file has been loaded, and sends notifactions
358// when all pending font files have been loaded.
359void QWasmFontDatabase::derefFontFileLoading()
360{
361 if (--g_pendingFonts <= 0) {
362 QFontCache::instance()->clear();
363 emit qGuiApp->fontDatabaseChanged();
364 }
365}
366
367// Unconditionally ends local font loading, for instance if there
368// are no fonts to load or if there was an unexpected error.
369void QWasmFontDatabase::endAllFontFileLoading()
370{
371 bool hadPandingfonts = g_pendingFonts > 0;
372 if (hadPandingfonts) {
373 // The hadPandingfonts counter might no longer be correct; disable counting
374 // and send notifications unconditionally.
375 g_pendingFonts = 0;
376 QFontCache::instance()->clear();
377 emit qGuiApp->fontDatabaseChanged();
378 }
379}
380
381
382QT_END_NAMESPACE
static QWasmIntegration * get()