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 if (m_localFontsApiSupported)
178 beginFontDatabaseStartupTask();
179}
180
181QWasmFontDatabase *QWasmFontDatabase::get()
182{
183 return static_cast<QWasmFontDatabase *>(QWasmIntegration::get()->fontDatabase());
184}
185
186// Populates the font database with local fonts. Will make the browser ask
187// the user for permission if needed. Does nothing if the Local Font Access API
188// is not supported.
189void QWasmFontDatabase::populateLocalfonts()
190{
191 // Decide which font families to populate based on user preferences
192 QStringList selectedLocalFontFamilies;
193 bool allFamilies = false;
194
195 switch (m_localFontFamilyLoadSet) {
196 case NoFontFamilies:
197 default:
198 // keep empty selectedLocalFontFamilies
199 break;
200 case DefaultFontFamilies: {
201 const QStringList webSafeFontFamilies =
202 {"Arial", "Verdana", "Tahoma", "Trebuchet", "Times New Roman",
203 "Georgia", "Garamond", "Courier New"};
204 selectedLocalFontFamilies = webSafeFontFamilies;
205 } break;
206 case AllFontFamilies:
207 allFamilies = true;
208 break;
209 }
210
211 selectedLocalFontFamilies += m_extraLocalFontFamilies;
212
213 if (selectedLocalFontFamilies.isEmpty() && !allFamilies) {
214 endAllFontFileLoading();
215 return;
216 }
217
218 populateLocalFontFamilies(selectedLocalFontFamilies, allFamilies);
219}
220
221namespace {
222 QStringList toStringList(emscripten::val array)
223 {
224 QStringList list;
225 int size = array["length"].as<int>();
226 for (int i = 0; i < size; ++i) {
227 emscripten::val element = array.call<emscripten::val>("at", i);
228 QString string = QString::fromStdString(element.as<std::string>());
229 if (!string.isEmpty())
230 list.append(string);
231 }
232 return list;
233 }
234}
235
236void QWasmFontDatabase::populateLocalFontFamilies(emscripten::val families)
237{
238 if (!m_localFontsApiSupported)
239 return;
240 populateLocalFontFamilies(toStringList(families), false);
241}
242
243void QWasmFontDatabase::populateLocalFontFamilies(const QStringList &fontFamilies, bool allFamilies)
244{
245 queryLocalFonts([fontFamilies, allFamilies](const QList<FontData> &fonts) {
246 refFontFileLoading();
247 QList<FontData> filteredFonts;
248 std::copy_if(fonts.begin(), fonts.end(), std::back_inserter(filteredFonts),
249 [fontFamilies, allFamilies](FontData fontData) {
250 return allFamilies || fontFamilies.contains(fontData.family());
251 });
252
253 for (const FontData &font: filteredFonts) {
254 refFontFileLoading();
255 readFont(font, [font](const QByteArray &fontData){
256 QFreeTypeFontDatabase::registerFontFamily(font.family());
257 QFreeTypeFontDatabase::addTTFile(fontData, QByteArray());
258 derefFontFileLoading();
259 });
260 }
261 derefFontFileLoading();
262 });
263
264}
265
266void QWasmFontDatabase::populateFontDatabase()
267{
268 // Load bundled font file from resources.
269 const QString fontFileNames[] = {
270 QStringLiteral(":/fonts/DejaVuSansMono.ttf"),
271 QStringLiteral(":/fonts/DejaVuSans.ttf"),
272 };
273 for (const QString &fontFileName : fontFileNames) {
274 QFile theFont(fontFileName);
275 if (!theFont.open(QIODevice::ReadOnly))
276 break;
277
278 QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1());
279 }
280
281 // Get config options for controlling local fonts usage
282 m_queryLocalFontsPermission = getLocalFontsBoolConfigPropertyWithDefault("requestPermission", false);
283 QString fontFamilyLoadSet = getLocalFontsStringConfigPropertyWithDefault("familiesCollection", "DefaultFontFamilies");
284 m_extraLocalFontFamilies = getLocalFontsStringListConfigPropertyWithDefault("extraFamilies", QStringList());
285
286 if (fontFamilyLoadSet == "NoFontFamilies") {
287 m_localFontFamilyLoadSet = NoFontFamilies;
288 } else if (fontFamilyLoadSet == "DefaultFontFamilies") {
289 m_localFontFamilyLoadSet = DefaultFontFamilies;
290 } else if (fontFamilyLoadSet == "AllFontFamilies") {
291 m_localFontFamilyLoadSet = AllFontFamilies;
292 } else {
293 m_localFontFamilyLoadSet = NoFontFamilies;
294 qWarning() << "Unknown fontFamilyLoadSet value" << fontFamilyLoadSet;
295 }
296
297 if (!m_localFontsApiSupported)
298 return;
299
300 // Populate the font database with local fonts. Either try unconditianlly
301 // if displyaing a fonts permissions dialog at startup is allowed, or else
302 // only if we already have permission.
303 if (m_queryLocalFontsPermission) {
304 populateLocalfonts();
305 } else {
306 checkFontAccessPermitted([this](bool granted) {
307 if (granted)
308 populateLocalfonts();
309 else
310 endAllFontFileLoading();
311 });
312 }
313}
314
315QFontEngine *QWasmFontDatabase::fontEngine(const QFontDef &fontDef, void *handle)
316{
317 QFontEngine *fontEngine = QFreeTypeFontDatabase::fontEngine(fontDef, handle);
318 return fontEngine;
319}
320
321QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style,
322 QFont::StyleHint styleHint,
323 QFontDatabasePrivate::ExtendedScript script) const
324{
325 QStringList fallbacks
326 = QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script);
327
328 // Add the DejaVuSans.ttf font (loaded in populateFontDatabase above) as a falback font
329 // to all other fonts (except itself).
330 static const QString wasmFallbackFonts[] = { "DejaVu Sans" };
331 for (auto wasmFallbackFont : wasmFallbackFonts) {
332 if (family != wasmFallbackFont && !fallbacks.contains(wasmFallbackFont))
333 fallbacks.append(wasmFallbackFont);
334 }
335
336 return fallbacks;
337}
338
339void QWasmFontDatabase::releaseHandle(void *handle)
340{
341 QFreeTypeFontDatabase::releaseHandle(handle);
342}
343
344QFont QWasmFontDatabase::defaultFont() const
345{
346 return QFont("DejaVu Sans"_L1);
347}
348
349namespace {
350 int g_pendingFonts = 0;
351 bool g_fontStartupTaskCompleted = false;
352}
353
354// Registers font loading as a startup task, which makes Qt delay
355// sending onLoaded event until font loading has completed.
356void QWasmFontDatabase::beginFontDatabaseStartupTask()
357{
358 g_fontStartupTaskCompleted = false;
359 QEventDispatcherWasm::registerStartupTask();
360}
361
362// Ends the font loading startup task.
363void QWasmFontDatabase::endFontDatabaseStartupTask()
364{
365 if (!g_fontStartupTaskCompleted) {
366 g_fontStartupTaskCompleted = true;
367 QEventDispatcherWasm::completeStarupTask();
368 }
369}
370
371// Registers that a font file will be loaded.
372void QWasmFontDatabase::refFontFileLoading()
373{
374 g_pendingFonts += 1;
375}
376
377// Registers that one font file has been loaded, and sends notifactions
378// when all pending font files have been loaded.
379void QWasmFontDatabase::derefFontFileLoading()
380{
381 if (--g_pendingFonts <= 0) {
382 QFontCache::instance()->clear();
383 emit qGuiApp->fontDatabaseChanged();
384 endFontDatabaseStartupTask();
385 }
386}
387
388// Unconditionally ends local font loading, for instance if there
389// are no fonts to load or if there was an unexpected error.
390void QWasmFontDatabase::endAllFontFileLoading()
391{
392 bool hadPandingfonts = g_pendingFonts > 0;
393 if (hadPandingfonts) {
394 // The hadPandingfonts counter might no longer be correct; disable counting
395 // and send notifications unconditionally.
396 g_pendingFonts = 0;
397 QFontCache::instance()->clear();
398 emit qGuiApp->fontDatabaseChanged();
399 }
400
401 endFontDatabaseStartupTask();
402}
403
404
405QT_END_NAMESPACE
static QWasmIntegration * get()