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