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/qdir.h>
10#include <QtCore/private/qstdweb_p.h>
11#include <QtCore/private/qeventdispatcher_wasm_p.h>
12#include <QtGui/private/qguiapplication_p.h>
13
14#include <emscripten.h>
15#include <emscripten/val.h>
16#include <emscripten/bind.h>
17
18#include <map>
19#include <array>
20
21QT_BEGIN_NAMESPACE
22
23using namespace emscripten;
24using namespace Qt::StringLiterals;
25
26
27namespace {
28
29class FontData
30{
31public:
32 FontData(val fontData)
33 :m_fontData(fontData) {}
34
35 QString family() const
36 {
37 return QString::fromStdString(m_fontData["family"].as<std::string>());
38 }
39
40 QString fullName() const
41 {
42 return QString::fromStdString(m_fontData["fullName"].as<std::string>());
43 }
44
45 QString postscriptName() const
46 {
47 return QString::fromStdString(m_fontData["postscriptName"].as<std::string>());
48 }
49
50 QString style() const
51 {
52 return QString::fromStdString(m_fontData["style"].as<std::string>());
53 }
54
55 val value() const
56 {
57 return m_fontData;
58 }
59
60private:
61 val m_fontData;
62};
63
64val makeObject(const char *key, const char *value)
65{
66 val obj = val::object();
67 obj.set(key, std::string(value));
68 return obj;
69}
70
71void printError(val err) {
72 qCWarning(lcQpaFonts)
73 << QString::fromStdString(err["name"].as<std::string>())
74 << QString::fromStdString(err["message"].as<std::string>());
75 QWasmFontDatabase::endAllFontFileLoading();
76}
77
78void checkFontAccessPermitted(std::function<void(bool)> callback)
79{
80 const val permissions = val::global("navigator")["permissions"];
81 if (permissions.isUndefined()) {
82 callback(false);
83 return;
84 }
85
86 qstdweb::Promise::make(permissions, "query", {
87 .thenFunc = [callback](val status) {
88 callback(status["state"].as<std::string>() == "granted");
89 },
90 }, makeObject("name", "local-fonts"));
91}
92
93void queryLocalFonts(std::function<void(const QList<FontData> &)> callback)
94{
95 emscripten::val window = emscripten::val::global("window");
96 qstdweb::Promise::make(window, "queryLocalFonts", {
97 .thenFunc = [callback](emscripten::val fontArray) {
98 QList<FontData> fonts;
99 const int count = fontArray["length"].as<int>();
100 fonts.reserve(count);
101 for (int i = 0; i < count; ++i)
102 fonts.append(FontData(fontArray.call<emscripten::val>("at", i)));
103 callback(fonts);
104 },
105 .catchFunc = printError
106 });
107}
108
109void readBlob(val blob, std::function<void(const QByteArray &)> callback)
110{
111 qstdweb::Promise::make(blob, "arrayBuffer", {
112 .thenFunc = [callback](emscripten::val fontArrayBuffer) {
113 QByteArray fontData = qstdweb::Uint8Array(qstdweb::ArrayBuffer(fontArrayBuffer)).copyToQByteArray();
114 callback(fontData);
115 },
116 .catchFunc = printError
117 });
118}
119
120void readFont(FontData font, std::function<void(const QByteArray &)> callback)
121{
122 qstdweb::Promise::make(font.value(), "blob", {
123 .thenFunc = [callback](val blob) {
124 readBlob(blob, [callback](const QByteArray &data) {
125 callback(data);
126 });
127 },
128 .catchFunc = printError
129 });
130}
131
132emscripten::val getLocalFontsConfigProperty(const char *name) {
133 emscripten::val qt = val::module_property("qt");
134 if (qt.isUndefined())
135 return emscripten::val();
136 emscripten::val localFonts = qt["localFonts"];
137 if (localFonts.isUndefined())
138 return emscripten::val();
139 return localFonts[name];
140};
141
142bool getLocalFontsBoolConfigPropertyWithDefault(const char *name, bool defaultValue) {
143 emscripten::val prop = getLocalFontsConfigProperty(name);
144 if (prop.isUndefined())
145 return defaultValue;
146 return prop.as<bool>();
147};
148
149QString getLocalFontsStringConfigPropertyWithDefault(const char *name, QString defaultValue) {
150 emscripten::val prop = getLocalFontsConfigProperty(name);
151 if (prop.isUndefined())
152 return defaultValue;
153 return QString::fromStdString(prop.as<std::string>());
154};
155
156QStringList getLocalFontsStringListConfigPropertyWithDefault(const char *name, QStringList defaultValue) {
157 emscripten::val array = getLocalFontsConfigProperty(name);
158 if (array.isUndefined())
159 return defaultValue;
160
161 QStringList list;
162 int size = array["length"].as<int>();
163 for (int i = 0; i < size; ++i) {
164 emscripten::val element = array.call<emscripten::val>("at", i);
165 QString string = QString::fromStdString(element.as<std::string>());
166 if (!string.isEmpty())
167 list.append(string);
168 }
169 return list;
170};
171
172} // namespace
173
174QWasmFontDatabase::QWasmFontDatabase()
175:QFreeTypeFontDatabase()
176{
177 m_localFontsApiSupported = val::global("window")["queryLocalFonts"].isUndefined() == false;
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 // Load font files from the platform font directory. On wasm this is a
281 // location in the in-memory file system, which applications can populate
282 // using the wasm preload mechanism without writing any code.
283 const QDir fontDirectory(fontDir());
284 if (fontDirectory.exists()) {
285 static const QString nameFilters[] = { u"*.ttf"_s, u"*.otf"_s, u"*.ttc"_s };
286 const auto entries = fontDirectory.entryInfoList(
287 QStringList::fromReadOnlyData(nameFilters), QDir::Files);
288 qCDebug(lcQpaFonts) << "Loading" << entries.count() << "font files from" << fontDir();
289 for (const QFileInfo &entry : entries) {
290 QFreeTypeFontDatabase::addTTFile(QByteArray(),
291 QFile::encodeName(entry.absoluteFilePath()));
292 }
293 } else {
294 qCDebug(lcQpaFonts) << "Font directory" << fontDir() << "does not exist";
295 }
296
297 // Get config options for controlling local fonts usage
298 m_queryLocalFontsPermission = getLocalFontsBoolConfigPropertyWithDefault("requestPermission", false);
299 QString fontFamilyLoadSet = getLocalFontsStringConfigPropertyWithDefault("familiesCollection", "DefaultFontFamilies");
300 m_extraLocalFontFamilies = getLocalFontsStringListConfigPropertyWithDefault("extraFamilies", QStringList());
301
302 if (fontFamilyLoadSet == "NoFontFamilies") {
303 m_localFontFamilyLoadSet = NoFontFamilies;
304 } else if (fontFamilyLoadSet == "DefaultFontFamilies") {
305 m_localFontFamilyLoadSet = DefaultFontFamilies;
306 } else if (fontFamilyLoadSet == "AllFontFamilies") {
307 m_localFontFamilyLoadSet = AllFontFamilies;
308 } else {
309 m_localFontFamilyLoadSet = NoFontFamilies;
310 qWarning() << "Unknown fontFamilyLoadSet value" << fontFamilyLoadSet;
311 }
312
313 if (!m_localFontsApiSupported)
314 return;
315
316 // Populate the font database with local fonts. Either try unconditianlly
317 // if displyaing a fonts permissions dialog at startup is allowed, or else
318 // only if we already have permission.
319 if (m_queryLocalFontsPermission) {
320 populateLocalfonts();
321 } else {
322 checkFontAccessPermitted([this](bool granted) {
323 if (granted)
324 populateLocalfonts();
325 else
326 endAllFontFileLoading();
327 });
328 }
329}
330
331QFontEngine *QWasmFontDatabase::fontEngine(const QFontDef &fontDef, void *handle)
332{
333 QFontEngine *fontEngine = QFreeTypeFontDatabase::fontEngine(fontDef, handle);
334 return fontEngine;
335}
336
337QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style,
338 QFont::StyleHint styleHint,
339 QFontDatabasePrivate::ExtendedScript script) const
340{
341 QStringList fallbacks
342 = QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script);
343
344 // Add the DejaVuSans.ttf font (loaded in populateFontDatabase above) as a falback font
345 // to all other fonts (except itself).
346 static const QString wasmFallbackFonts[] = { "DejaVu Sans" };
347 for (auto wasmFallbackFont : wasmFallbackFonts) {
348 if (family != wasmFallbackFont && !fallbacks.contains(wasmFallbackFont))
349 fallbacks.append(wasmFallbackFont);
350 }
351
352 return fallbacks;
353}
354
355void QWasmFontDatabase::releaseHandle(void *handle)
356{
357 QFreeTypeFontDatabase::releaseHandle(handle);
358}
359
360QFont QWasmFontDatabase::defaultFont() const
361{
362 return QFont("DejaVu Sans"_L1);
363}
364
365namespace {
366 int g_pendingFonts = 0;
367}
368
369// Registers that a font file will be loaded.
370void QWasmFontDatabase::refFontFileLoading()
371{
372 g_pendingFonts += 1;
373}
374
375// Registers that one font file has been loaded, and sends notifactions
376// when all pending font files have been loaded.
377void QWasmFontDatabase::derefFontFileLoading()
378{
379 if (--g_pendingFonts <= 0) {
380 QFontCache::instance()->clear();
381 emit qGuiApp->fontDatabaseChanged();
382 }
383}
384
385// Unconditionally ends local font loading, for instance if there
386// are no fonts to load or if there was an unexpected error.
387void QWasmFontDatabase::endAllFontFileLoading()
388{
389 bool hadPandingfonts = g_pendingFonts > 0;
390 if (hadPandingfonts) {
391 // The hadPandingfonts counter might no longer be correct; disable counting
392 // and send notifications unconditionally.
393 g_pendingFonts = 0;
394 QFontCache::instance()->clear();
395 emit qGuiApp->fontDatabaseChanged();
396 }
397}
398
399
400QT_END_NAMESPACE
static QWasmIntegration * get()