From 15dab565d071455ef08d6bf4ad4980f726df1cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Morten=20S=C3=B8rvig?= Date: Fri, 24 Mar 2023 15:39:08 +0100 Subject: [PATCH] wasm: rework local font support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Aleksandr Reviakin --- .../platforms/wasm/qwasmfontdatabase.cpp | 224 ++++++++++-------- .../platforms/wasm/qwasmfontdatabase.h | 3 +- 2 files changed, 130 insertions(+), 97 deletions(-) diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp index 5fdaae8f84a..c0833a65ca2 100644 --- a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp +++ b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp @@ -12,11 +12,138 @@ #include #include +#include +#include + QT_BEGIN_NAMESPACE using namespace emscripten; using namespace Qt::StringLiterals; + +namespace { + +bool isLocalFontsAPISupported() +{ + return val::global("window")["queryLocalFonts"].isUndefined() == false; +} + +val makeObject(const char *key, const char *value) +{ + val obj = val::object(); + obj.set(key, std::string(value)); + return obj; +} + +std::multimap makeFontFamilyMap(const QList &fonts) +{ + std::multimap fontFamilies; + for (auto font : fonts) { + QString family = QString::fromStdString(font["family"].as()); + fontFamilies.insert(std::make_pair(family, font)); + } + return fontFamilies; +} + +void printError(val err) { + qCWarning(lcQpaFonts) + << QString::fromStdString(err["name"].as()) + << QString::fromStdString(err["message"].as()); +} + +std::array webSafeFontFamilies() +{ + return {"Arial", "Verdana", "Tahoma", "Trebuchet", "Times New Roman", + "Georgia", "Garamond", "Courier New"}; +} + +void checkFontAccessPermitted(std::function callback) +{ + const val permissions = val::global("navigator")["permissions"]; + if (permissions.isUndefined()) + return; + + qstdweb::Promise::make(permissions, "query", { + .thenFunc = [callback](val status) { + if (status["state"].as() == "granted") + callback(); + } + }, makeObject("name", "local-fonts")); +} + +void queryLocalFonts(std::function &)> callback) +{ + emscripten::val window = emscripten::val::global("window"); + qstdweb::Promise::make(window, "queryLocalFonts", { + .thenFunc = [callback](emscripten::val fontArray) { + QList fonts; + const int count = fontArray["length"].as(); + fonts.reserve(count); + for (int i = 0; i < count; ++i) + fonts.append(fontArray.call("at", i)); + callback(fonts); + }, + .catchFunc = printError + }); +} + +void readBlob(val blob, std::function callback) +{ + qstdweb::Promise::make(blob, "arrayBuffer", { + .thenFunc = [callback](emscripten::val fontArrayBuffer) { + QByteArray fontData = qstdweb::Uint8Array(qstdweb::ArrayBuffer(fontArrayBuffer)).copyToQByteArray(); + callback(fontData); + }, + .catchFunc = printError + }); +} + +void readFont(val font, std::function callback) +{ + qstdweb::Promise::make(font, "blob", { + .thenFunc = [callback](val blob) { + readBlob(blob, [callback](const QByteArray &data) { + callback(data); + }); + }, + .catchFunc = printError + }); +} + +} // namespace + +void QWasmFontDatabase::populateLocalfonts() +{ + if (!isLocalFontsAPISupported()) + return; + + // 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 &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(); + }); + } + } + }); + }); +} + void QWasmFontDatabase::populateFontDatabase() { // Load font file from resources. Currently @@ -36,102 +163,7 @@ void QWasmFontDatabase::populateFontDatabase() QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1()); } - // check if local-fonts API is available in the browser - val window = val::global("window"); - val fonts = window["queryLocalFonts"]; - - if (fonts.isUndefined()) - return; - - val permissions = val::global("navigator")["permissions"]; - if (permissions["request"].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()); - - // 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(); - for (int i = 0; i < count; ++i) { - val font = status.call("at", i); - const std::string family = font["family"].as(); - 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()); - } - }; - - // request local fonts permission (currently supported only by Chrome 103+) - qstdweb::Promise::make(permissions, "request", std::move(permissionRequestCallbacks), std::move(requestLocalFontsPermission)); -} - -void QWasmFontDatabase::populateFamily(const QString &familyName) -{ - val window = val::global("window"); - - auto queryFontsArgument = val::array(std::vector({ val(familyName.toStdString()) })); - val queryFont = val::object(); - queryFont.set("postscriptNames", std::move(queryFontsArgument)); - - qstdweb::PromiseCallbacks localFontsQueryCallback { - .thenFunc = [](val status) { - val font = status.call("at", 0); - - if (font.isUndefined()) - return; - - qstdweb::PromiseCallbacks blobQueryCallback { - .thenFunc = [](val status) { - qCDebug(lcQpaFonts) << "onBlobQuerySuccess"; - - qstdweb::PromiseCallbacks arrayBufferCallback { - .thenFunc = [](val status) { - qCDebug(lcQpaFonts) << "onArrayBuffer" ; - - QByteArray fontByteArray = QByteArray::fromEcmaUint8Array(status); - - QFreeTypeFontDatabase::addTTFile(fontByteArray, QByteArray()); - - QWasmFontDatabase::notifyFontsChanged(); - }, - .catchFunc = [](val) { - qCWarning(lcQpaFonts) << "onArrayBufferError"; - } - }; - - qstdweb::Promise::make(status, "arrayBuffer", std::move(arrayBufferCallback)); - }, - .catchFunc = [](val) { - qCWarning(lcQpaFonts) << "onBlobQueryError"; - } - }; - - qstdweb::Promise::make(font, "blob", std::move(blobQueryCallback)); - }, - .catchFunc = [](val) { - qCWarning(lcQpaFonts) << "onLocalFontsQueryError"; - } - }; - - 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 22c550f2445..8a2936cb1d7 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