summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorten Sørvig <morten.sorvig@qt.io>2023-03-24 15:39:08 +0100
committerMorten Johan Sørvig <morten.sorvig@qt.io>2023-04-25 21:38:08 +0000
commit15dab565d071455ef08d6bf4ad4980f726df1cfa (patch)
tree57da86701ef91d78a6935b68af55d97e8d1fc8e1
parentc10c66e552e3ab19758709e84bac187cda27962c (diff)
wasm: rework local font support
Populate a subset of the font families at startup if the local fonts access API is supported, and the access permission has been given. Since this code runs at app startup there is no opportunity to request font access. That should be done in response to user action, for example by having a "load local fonts" button in the application. Pick-to: 6.5 Change-Id: Ib6826deeec06ee3def0e793dd1462977710462be Reviewed-by: Mikołaj Boc <Mikolaj.Boc@qt.io> Reviewed-by: Aleksandr Reviakin <aleksandr.reviakin@qt.io>
-rw-r--r--src/plugins/platforms/wasm/qwasmfontdatabase.cpp218
-rw-r--r--src/plugins/platforms/wasm/qwasmfontdatabase.h3
2 files changed, 127 insertions, 94 deletions
diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
index 5fdaae8f84..c0833a65ca 100644
--- a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
+++ b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp
@@ -12,126 +12,158 @@
#include <emscripten/val.h>
#include <emscripten/bind.h>
+#include <map>
+#include <array>
+
QT_BEGIN_NAMESPACE
using namespace emscripten;
using namespace Qt::StringLiterals;
-void QWasmFontDatabase::populateFontDatabase()
+
+namespace {
+
+bool isLocalFontsAPISupported()
{
- // Load font file from resources. Currently
- // all fonts needs to be bundled with the nexe
- // as Qt resources.
+ return val::global("window")["queryLocalFonts"].isUndefined() == false;
+}
- const QString fontFileNames[] = {
- QStringLiteral(":/fonts/DejaVuSansMono.ttf"),
- QStringLiteral(":/fonts/Vera.ttf"),
- QStringLiteral(":/fonts/DejaVuSans.ttf"),
- };
- for (const QString &fontFileName : fontFileNames) {
- QFile theFont(fontFileName);
- if (!theFont.open(QIODevice::ReadOnly))
- break;
+val makeObject(const char *key, const char *value)
+{
+ val obj = val::object();
+ obj.set(key, std::string(value));
+ return obj;
+}
- QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1());
+std::multimap<QString, emscripten::val> makeFontFamilyMap(const QList<val> &fonts)
+{
+ std::multimap<QString, emscripten::val> fontFamilies;
+ for (auto font : fonts) {
+ QString family = QString::fromStdString(font["family"].as<std::string>());
+ fontFamilies.insert(std::make_pair(family, font));
}
+ return fontFamilies;
+}
- // check if local-fonts API is available in the browser
- val window = val::global("window");
- val fonts = window["queryLocalFonts"];
+void printError(val err) {
+ qCWarning(lcQpaFonts)
+ << QString::fromStdString(err["name"].as<std::string>())
+ << QString::fromStdString(err["message"].as<std::string>());
+}
- if (fonts.isUndefined())
- return;
+std::array<const char *, 8> webSafeFontFamilies()
+{
+ return {"Arial", "Verdana", "Tahoma", "Trebuchet", "Times New Roman",
+ "Georgia", "Garamond", "Courier New"};
+}
- val permissions = val::global("navigator")["permissions"];
- if (permissions["request"].isUndefined())
+void checkFontAccessPermitted(std::function<void()> callback)
+{
+ const val permissions = val::global("navigator")["permissions"];
+ if (permissions.isUndefined())
return;
- val requestLocalFontsPermission = val::object();
- requestLocalFontsPermission.set("name", std::string("local-fonts"));
-
- qstdweb::PromiseCallbacks permissionRequestCallbacks {
- .thenFunc = [window](val status) {
- qCDebug(lcQpaFonts) << "onFontPermissionSuccess:"
- << QString::fromStdString(status["state"].as<std::string>());
-
- // query all available local fonts and call registerFontFamily for each of them
- qstdweb::Promise::make(window, "queryLocalFonts", {
- .thenFunc = [](val status) {
- const int count = status["length"].as<int>();
- for (int i = 0; i < count; ++i) {
- val font = status.call<val>("at", i);
- const std::string family = font["family"].as<std::string>();
- QFreeTypeFontDatabase::registerFontFamily(QString::fromStdString(family));
- }
- QWasmFontDatabase::notifyFontsChanged();
- },
- .catchFunc = [](val) {
- qCWarning(lcQpaFonts)
- << "Error while trying to query local-fonts API";
- }
- });
- },
- .catchFunc = [](val error) {
- qCWarning(lcQpaFonts)
- << "Error while requesting local-fonts API permission: "
- << QString::fromStdString(error["name"].as<std::string>());
+ qstdweb::Promise::make(permissions, "query", {
+ .thenFunc = [callback](val status) {
+ if (status["state"].as<std::string>() == "granted")
+ callback();
}
- };
-
- // request local fonts permission (currently supported only by Chrome 103+)
- qstdweb::Promise::make(permissions, "request", std::move(permissionRequestCallbacks), std::move(requestLocalFontsPermission));
+ }, makeObject("name", "local-fonts"));
}
-void QWasmFontDatabase::populateFamily(const QString &familyName)
+void queryLocalFonts(std::function<void(const QList<val> &)> callback)
{
- val window = val::global("window");
-
- auto queryFontsArgument = val::array(std::vector<val>({ val(familyName.toStdString()) }));
- val queryFont = val::object();
- queryFont.set("postscriptNames", std::move(queryFontsArgument));
-
- qstdweb::PromiseCallbacks localFontsQueryCallback {
- .thenFunc = [](val status) {
- val font = status.call<val>("at", 0);
-
- if (font.isUndefined())
- return;
-
- qstdweb::PromiseCallbacks blobQueryCallback {
- .thenFunc = [](val status) {
- qCDebug(lcQpaFonts) << "onBlobQuerySuccess";
+ emscripten::val window = emscripten::val::global("window");
+ qstdweb::Promise::make(window, "queryLocalFonts", {
+ .thenFunc = [callback](emscripten::val fontArray) {
+ QList<val> fonts;
+ const int count = fontArray["length"].as<int>();
+ fonts.reserve(count);
+ for (int i = 0; i < count; ++i)
+ fonts.append(fontArray.call<emscripten::val>("at", i));
+ callback(fonts);
+ },
+ .catchFunc = printError
+ });
+}
- qstdweb::PromiseCallbacks arrayBufferCallback {
- .thenFunc = [](val status) {
- qCDebug(lcQpaFonts) << "onArrayBuffer" ;
+void readBlob(val blob, std::function<void(const QByteArray &)> callback)
+{
+ qstdweb::Promise::make(blob, "arrayBuffer", {
+ .thenFunc = [callback](emscripten::val fontArrayBuffer) {
+ QByteArray fontData = qstdweb::Uint8Array(qstdweb::ArrayBuffer(fontArrayBuffer)).copyToQByteArray();
+ callback(fontData);
+ },
+ .catchFunc = printError
+ });
+}
- QByteArray fontByteArray = QByteArray::fromEcmaUint8Array(status);
+void readFont(val font, std::function<void(const QByteArray &)> callback)
+{
+ qstdweb::Promise::make(font, "blob", {
+ .thenFunc = [callback](val blob) {
+ readBlob(blob, [callback](const QByteArray &data) {
+ callback(data);
+ });
+ },
+ .catchFunc = printError
+ });
+}
- QFreeTypeFontDatabase::addTTFile(fontByteArray, QByteArray());
+} // namespace
- QWasmFontDatabase::notifyFontsChanged();
- },
- .catchFunc = [](val) {
- qCWarning(lcQpaFonts) << "onArrayBufferError";
- }
- };
+void QWasmFontDatabase::populateLocalfonts()
+{
+ if (!isLocalFontsAPISupported())
+ return;
- qstdweb::Promise::make(status, "arrayBuffer", std::move(arrayBufferCallback));
- },
- .catchFunc = [](val) {
- qCWarning(lcQpaFonts) << "onBlobQueryError";
+ // Run the font population code if local font access has been
+ // permitted. This does not request permission, since we are currently
+ // starting up and should not display a permission request dialog at
+ // this point.
+ checkFontAccessPermitted([](){
+ queryLocalFonts([](const QList<val> &fonts){
+ auto fontFamilies = makeFontFamilyMap(fonts);
+ // Populate some font families. We can't populate _all_ fonts as in-memory fonts,
+ // since that would require several gigabytes of memory. Instead, populate
+ // a subset of the available fonts.
+ for (const QString &family: webSafeFontFamilies()) {
+ auto fontsRange = fontFamilies.equal_range(family);
+ if (fontsRange.first != fontsRange.second)
+ QFreeTypeFontDatabase::registerFontFamily(family);
+
+ for (auto it = fontsRange.first; it != fontsRange.second; ++it) {
+ const val font = it->second;
+ readFont(font, [](const QByteArray &fontData){
+ QFreeTypeFontDatabase::addTTFile(fontData, QByteArray());
+ QWasmFontDatabase::notifyFontsChanged();
+ });
}
- };
+ }
+ });
+ });
+}
- qstdweb::Promise::make(font, "blob", std::move(blobQueryCallback));
- },
- .catchFunc = [](val) {
- qCWarning(lcQpaFonts) << "onLocalFontsQueryError";
- }
+void QWasmFontDatabase::populateFontDatabase()
+{
+ // Load font file from resources. Currently
+ // all fonts needs to be bundled with the nexe
+ // as Qt resources.
+
+ const QString fontFileNames[] = {
+ QStringLiteral(":/fonts/DejaVuSansMono.ttf"),
+ QStringLiteral(":/fonts/Vera.ttf"),
+ QStringLiteral(":/fonts/DejaVuSans.ttf"),
};
+ for (const QString &fontFileName : fontFileNames) {
+ QFile theFont(fontFileName);
+ if (!theFont.open(QIODevice::ReadOnly))
+ break;
+
+ QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1());
+ }
- qstdweb::Promise::make(window, "queryLocalFonts", std::move(localFontsQueryCallback), std::move(queryFont));
+ populateLocalfonts();
}
QFontEngine *QWasmFontDatabase::fontEngine(const QFontDef &fontDef, void *handle)
diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.h b/src/plugins/platforms/wasm/qwasmfontdatabase.h
index 22c550f244..8a2936cb1d 100644
--- a/src/plugins/platforms/wasm/qwasmfontdatabase.h
+++ b/src/plugins/platforms/wasm/qwasmfontdatabase.h
@@ -12,7 +12,6 @@ class QWasmFontDatabase : public QFreeTypeFontDatabase
{
public:
void populateFontDatabase() override;
- void populateFamily(const QString &familyName) override;
QFontEngine *fontEngine(const QFontDef &fontDef, void *handle) override;
QStringList fallbacksForFamily(const QString &family, QFont::Style style,
QFont::StyleHint styleHint,
@@ -20,6 +19,8 @@ public:
void releaseHandle(void *handle) override;
QFont defaultFont() const override;
+ void populateLocalfonts();
+
static void notifyFontsChanged();
};
QT_END_NAMESPACE