diff options
Diffstat (limited to 'src/plugins/platforms/wasm/qwasmfontdatabase.cpp')
-rw-r--r-- | src/plugins/platforms/wasm/qwasmfontdatabase.cpp | 431 |
1 files changed, 322 insertions, 109 deletions
diff --git a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp index 37f80279e0..3f3dc10f71 100644 --- a/src/plugins/platforms/wasm/qwasmfontdatabase.cpp +++ b/src/plugins/platforms/wasm/qwasmfontdatabase.cpp @@ -6,152 +6,315 @@ #include <QtCore/qfile.h> #include <QtCore/private/qstdweb_p.h> +#include <QtCore/private/qeventdispatcher_wasm_p.h> #include <QtGui/private/qguiapplication_p.h> #include <emscripten.h> #include <emscripten/val.h> #include <emscripten/bind.h> -// FIXME: replace with shared implementation from qstdweb -QByteArray fromUint8Array(emscripten::val uint8array) -{ - qstdweb::ArrayBuffer arrayBuffer(uint8array); - - using qstdweb::Uint8Array; - Uint8Array sourceArray(arrayBuffer); - if (sourceArray.length() > std::numeric_limits<qsizetype>::max()) - return QByteArray(); - QByteArray destinationArray; - destinationArray.resize(sourceArray.length()); - sourceArray.copyTo(destinationArray.data()); - return destinationArray; -} +#include <map> +#include <array> QT_BEGIN_NAMESPACE using namespace emscripten; using namespace Qt::StringLiterals; -void QWasmFontDatabase::populateFontDatabase() + +namespace { + +class FontData { - // Load font file from resources. Currently - // all fonts needs to be bundled with the nexe - // as Qt resources. +public: + FontData(val fontData) + :m_fontData(fontData) {} - 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; + QString family() const + { + return QString::fromStdString(m_fontData["family"].as<std::string>()); + } - QFreeTypeFontDatabase::addTTFile(theFont.readAll(), fontFileName.toLatin1()); + QString fullName() const + { + return QString::fromStdString(m_fontData["fullName"].as<std::string>()); } - // check if local-fonts API is available in the browser - val window = val::global("window"); - val fonts = window["queryLocalFonts"]; + QString postscriptName() const + { + return QString::fromStdString(m_fontData["postscriptName"].as<std::string>()); + } - if (fonts.isUndefined()) - return; + QString style() const + { + return QString::fromStdString(m_fontData["style"].as<std::string>()); + } - val permissions = val::global("navigator")["permissions"]; - if (permissions["request"].isUndefined()) + val value() const + { + return m_fontData; + } + +private: + val m_fontData; +}; + +val makeObject(const char *key, const char *value) +{ + val obj = val::object(); + obj.set(key, std::string(value)); + return obj; +} + +void printError(val err) { + qCWarning(lcQpaFonts) + << QString::fromStdString(err["name"].as<std::string>()) + << QString::fromStdString(err["message"].as<std::string>()); + QWasmFontDatabase::endAllFontFileLoading(); +} + +void checkFontAccessPermitted(std::function<void(bool)> callback) +{ + const val permissions = val::global("navigator")["permissions"]; + if (permissions.isUndefined()) { + callback(false); 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"; - } - }); + qstdweb::Promise::make(permissions, "query", { + .thenFunc = [callback](val status) { + callback(status["state"].as<std::string>() == "granted"); }, - .catchFunc = [](val error) { - qCWarning(lcQpaFonts) - << "Error while requesting local-fonts API permission: " - << QString::fromStdString(error["name"].as<std::string>()); - } - }; + }, makeObject("name", "local-fonts")); +} - // request local fonts permission (currently supported only by Chrome 103+) - qstdweb::Promise::make(permissions, "request", std::move(permissionRequestCallbacks), std::move(requestLocalFontsPermission)); +void queryLocalFonts(std::function<void(const QList<FontData> &)> callback) +{ + emscripten::val window = emscripten::val::global("window"); + qstdweb::Promise::make(window, "queryLocalFonts", { + .thenFunc = [callback](emscripten::val fontArray) { + QList<FontData> fonts; + const int count = fontArray["length"].as<int>(); + fonts.reserve(count); + for (int i = 0; i < count; ++i) + fonts.append(FontData(fontArray.call<emscripten::val>("at", i))); + callback(fonts); + }, + .catchFunc = printError + }); } -void QWasmFontDatabase::populateFamily(const QString &familyName) +void readBlob(val blob, std::function<void(const QByteArray &)> callback) { - val window = val::global("window"); + qstdweb::Promise::make(blob, "arrayBuffer", { + .thenFunc = [callback](emscripten::val fontArrayBuffer) { + QByteArray fontData = qstdweb::Uint8Array(qstdweb::ArrayBuffer(fontArrayBuffer)).copyToQByteArray(); + callback(fontData); + }, + .catchFunc = printError + }); +} - auto queryFontsArgument = val::array(std::vector<val>({ val(familyName.toStdString()) })); - val queryFont = val::object(); - queryFont.set("postscriptNames", std::move(queryFontsArgument)); +void readFont(FontData font, std::function<void(const QByteArray &)> callback) +{ + qstdweb::Promise::make(font.value(), "blob", { + .thenFunc = [callback](val blob) { + readBlob(blob, [callback](const QByteArray &data) { + callback(data); + }); + }, + .catchFunc = printError + }); +} - qstdweb::PromiseCallbacks localFontsQueryCallback { - .thenFunc = [](val status) { - val font = status.call<val>("at", 0); +emscripten::val getLocalFontsConfigProperty(const char *name) { + emscripten::val qt = val::module_property("qt"); + if (qt.isUndefined()) + return emscripten::val(); + emscripten::val localFonts = qt["localFonts"]; + if (localFonts.isUndefined()) + return emscripten::val(); + return localFonts[name]; +}; + +bool getLocalFontsBoolConfigPropertyWithDefault(const char *name, bool defaultValue) { + emscripten::val prop = getLocalFontsConfigProperty(name); + if (prop.isUndefined()) + return defaultValue; + return prop.as<bool>(); +}; + +QString getLocalFontsStringConfigPropertyWithDefault(const char *name, QString defaultValue) { + emscripten::val prop = getLocalFontsConfigProperty(name); + if (prop.isUndefined()) + return defaultValue; + return QString::fromStdString(prop.as<std::string>()); +}; + +QStringList getLocalFontsStringListConfigPropertyWithDefault(const char *name, QStringList defaultValue) { + emscripten::val array = getLocalFontsConfigProperty(name); + if (array.isUndefined()) + return defaultValue; + + QStringList list; + int size = array["length"].as<int>(); + for (int i = 0; i < size; ++i) { + emscripten::val element = array.call<emscripten::val>("at", i); + QString string = QString::fromStdString(element.as<std::string>()); + if (!string.isEmpty()) + list.append(string); + } + return list; +}; - if (font.isUndefined()) - return; +} // namespace - qstdweb::PromiseCallbacks blobQueryCallback { - .thenFunc = [](val status) { - qCDebug(lcQpaFonts) << "onBlobQuerySuccess"; +QWasmFontDatabase::QWasmFontDatabase() +:QFreeTypeFontDatabase() +{ + m_localFontsApiSupported = val::global("window")["queryLocalFonts"].isUndefined() == false; + if (m_localFontsApiSupported) + beginFontDatabaseStartupTask(); +} - qstdweb::PromiseCallbacks arrayBufferCallback { - .thenFunc = [](val status) { - qCDebug(lcQpaFonts) << "onArrayBuffer" ; +QWasmFontDatabase *QWasmFontDatabase::get() +{ + return static_cast<QWasmFontDatabase *>(QWasmIntegration::get()->fontDatabase()); +} - QByteArray fontByteArray = fromUint8Array(status); +// Populates the font database with local fonts. Will make the browser ask +// the user for permission if needed. Does nothing if the Local Font Access API +// is not supported. +void QWasmFontDatabase::populateLocalfonts() +{ + // Decide which font families to populate based on user preferences + QStringList selectedLocalFontFamilies; + bool allFamilies = false; + + switch (m_localFontFamilyLoadSet) { + case NoFontFamilies: + default: + // keep empty selectedLocalFontFamilies + break; + case DefaultFontFamilies: { + const QStringList webSafeFontFamilies = + {"Arial", "Verdana", "Tahoma", "Trebuchet", "Times New Roman", + "Georgia", "Garamond", "Courier New"}; + selectedLocalFontFamilies = webSafeFontFamilies; + } break; + case AllFontFamilies: + allFamilies = true; + break; + } - QFreeTypeFontDatabase::addTTFile(fontByteArray, QByteArray()); + selectedLocalFontFamilies += m_extraLocalFontFamilies; - QWasmFontDatabase::notifyFontsChanged(); - }, - .catchFunc = [](val) { - qCWarning(lcQpaFonts) << "onArrayBufferError"; - } - }; + if (selectedLocalFontFamilies.isEmpty() && !allFamilies) { + endAllFontFileLoading(); + return; + } - qstdweb::Promise::make(status, "arrayBuffer", std::move(arrayBufferCallback)); - }, - .catchFunc = [](val) { - qCWarning(lcQpaFonts) << "onBlobQueryError"; - } - }; + populateLocalFontFamilies(selectedLocalFontFamilies, allFamilies); +} - qstdweb::Promise::make(font, "blob", std::move(blobQueryCallback)); - }, - .catchFunc = [](val) { - qCWarning(lcQpaFonts) << "onLocalFontsQueryError"; +namespace { + QStringList toStringList(emscripten::val array) + { + QStringList list; + int size = array["length"].as<int>(); + for (int i = 0; i < size; ++i) { + emscripten::val element = array.call<emscripten::val>("at", i); + QString string = QString::fromStdString(element.as<std::string>()); + if (!string.isEmpty()) + list.append(string); } + return list; + } +} + +void QWasmFontDatabase::populateLocalFontFamilies(emscripten::val families) +{ + if (!m_localFontsApiSupported) + return; + populateLocalFontFamilies(toStringList(families), false); +} + +void QWasmFontDatabase::populateLocalFontFamilies(const QStringList &fontFamilies, bool allFamilies) +{ + queryLocalFonts([fontFamilies, allFamilies](const QList<FontData> &fonts) { + refFontFileLoading(); + QList<FontData> filteredFonts; + std::copy_if(fonts.begin(), fonts.end(), std::back_inserter(filteredFonts), + [fontFamilies, allFamilies](FontData fontData) { + return allFamilies || fontFamilies.contains(fontData.family()); + }); + + for (const FontData &font: filteredFonts) { + refFontFileLoading(); + readFont(font, [font](const QByteArray &fontData){ + QFreeTypeFontDatabase::registerFontFamily(font.family()); + QFreeTypeFontDatabase::addTTFile(fontData, QByteArray()); + derefFontFileLoading(); + }); + } + derefFontFileLoading(); + }); + +} + +void QWasmFontDatabase::populateFontDatabase() +{ + // Load bundled font file from resources. + const QString fontFileNames[] = { + QStringLiteral(":/fonts/DejaVuSansMono.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()); + } + + // Get config options for controlling local fonts usage + m_queryLocalFontsPermission = getLocalFontsBoolConfigPropertyWithDefault("requestPermission", false); + QString fontFamilyLoadSet = getLocalFontsStringConfigPropertyWithDefault("familiesCollection", "DefaultFontFamilies"); + m_extraLocalFontFamilies = getLocalFontsStringListConfigPropertyWithDefault("extraFamilies", QStringList()); + + if (fontFamilyLoadSet == "NoFontFamilies") { + m_localFontFamilyLoadSet = NoFontFamilies; + } else if (fontFamilyLoadSet == "DefaultFontFamilies") { + m_localFontFamilyLoadSet = DefaultFontFamilies; + } else if (fontFamilyLoadSet == "AllFontFamilies") { + m_localFontFamilyLoadSet = AllFontFamilies; + } else { + m_localFontFamilyLoadSet = NoFontFamilies; + qWarning() << "Unknown fontFamilyLoadSet value" << fontFamilyLoadSet; + } - qstdweb::Promise::make(window, "queryLocalFonts", std::move(localFontsQueryCallback), std::move(queryFont)); + if (!m_localFontsApiSupported) + return; + + // Populate the font database with local fonts. Either try unconditianlly + // if displyaing a fonts permissions dialog at startup is allowed, or else + // only if we already have permission. + if (m_queryLocalFontsPermission) { + populateLocalfonts(); + } else { + checkFontAccessPermitted([this](bool granted) { + if (granted) + populateLocalfonts(); + else + endAllFontFileLoading(); + }); + } } QFontEngine *QWasmFontDatabase::fontEngine(const QFontDef &fontDef, void *handle) { - return QFreeTypeFontDatabase::fontEngine(fontDef, handle); + QFontEngine *fontEngine = QFreeTypeFontDatabase::fontEngine(fontDef, handle); + return fontEngine; } QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont::Style style, @@ -161,9 +324,9 @@ QStringList QWasmFontDatabase::fallbacksForFamily(const QString &family, QFont:: QStringList fallbacks = QFreeTypeFontDatabase::fallbacksForFamily(family, style, styleHint, script); - // Add the vera.ttf and DejaVuSans.ttf fonts (loaded in populateFontDatabase above) as falback fonts + // Add the DejaVuSans.ttf font (loaded in populateFontDatabase above) as a falback font // to all other fonts (except itself). - static const QString wasmFallbackFonts[] = { "Bitstream Vera Sans", "DejaVu Sans" }; + static const QString wasmFallbackFonts[] = { "DejaVu Sans" }; for (auto wasmFallbackFont : wasmFallbackFonts) { if (family != wasmFallbackFont && !fallbacks.contains(wasmFallbackFont)) fallbacks.append(wasmFallbackFont); @@ -179,13 +342,63 @@ void QWasmFontDatabase::releaseHandle(void *handle) QFont QWasmFontDatabase::defaultFont() const { - return QFont("Bitstream Vera Sans"_L1); + return QFont("DejaVu Sans"_L1); } -void QWasmFontDatabase::notifyFontsChanged() +namespace { + int g_pendingFonts = 0; + bool g_fontStartupTaskCompleted = false; +} + +// Registers font loading as a startup task, which makes Qt delay +// sending onLoaded event until font loading has completed. +void QWasmFontDatabase::beginFontDatabaseStartupTask() +{ + g_fontStartupTaskCompleted = false; + QEventDispatcherWasm::registerStartupTask(); +} + +// Ends the font loading startup task. +void QWasmFontDatabase::endFontDatabaseStartupTask() { - QFontCache::instance()->clear(); - emit qGuiApp->fontDatabaseChanged(); + if (!g_fontStartupTaskCompleted) { + g_fontStartupTaskCompleted = true; + QEventDispatcherWasm::completeStarupTask(); + } } +// Registers that a font file will be loaded. +void QWasmFontDatabase::refFontFileLoading() +{ + g_pendingFonts += 1; +} + +// Registers that one font file has been loaded, and sends notifactions +// when all pending font files have been loaded. +void QWasmFontDatabase::derefFontFileLoading() +{ + if (--g_pendingFonts <= 0) { + QFontCache::instance()->clear(); + emit qGuiApp->fontDatabaseChanged(); + endFontDatabaseStartupTask(); + } +} + +// Unconditionally ends local font loading, for instance if there +// are no fonts to load or if there was an unexpected error. +void QWasmFontDatabase::endAllFontFileLoading() +{ + bool hadPandingfonts = g_pendingFonts > 0; + if (hadPandingfonts) { + // The hadPandingfonts counter might no longer be correct; disable counting + // and send notifications unconditionally. + g_pendingFonts = 0; + QFontCache::instance()->clear(); + emit qGuiApp->fontDatabaseChanged(); + } + + endFontDatabaseStartupTask(); +} + + QT_END_NAMESPACE |