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