diff --git a/qtwebutils.cpp b/qtwebutils.cpp index 5177fc597f90accecad1745fecfeaf10a6b57d1e..f0271d4787c04be536d77fcc9204c9f9a0576365 100644 --- a/qtwebutils.cpp +++ b/qtwebutils.cpp @@ -207,3 +207,5 @@ Q_INVOKABLE void QtWebUtils::closeBrowserWindow(const QString &name) emscripten::val window = g_windows->take(name); qtwebutils::closeBrowserWindow(window); } + +#include "qtwebutils_localfonts.cpp" diff --git a/qtwebutils.h b/qtwebutils.h index 45a1064efb219cdc85c19b5de2bab7beb193c96d..45b82c3cc59a8e3c4ebefa38af265a3acd1ac8bc 100644 --- a/qtwebutils.h +++ b/qtwebutils.h @@ -25,6 +25,12 @@ namespace qtwebutils { emscripten::val openBrowserWindow(const QString& source, const QString &name, QSize size, OpenWindowMode openMode); void closeBrowserWindow(emscripten::val window); + + void getFontFamilies(std::function<void(const QStringList &)> families = std::function<void(const QStringList &)>()); + void populateFontFamily(const QString &family, std::function<void(const QList<int> &)> populated = std::function<void(const QList<int> &)>()); + void populateFontFamilies(const QStringList &families, std::function<void(const QList<int> &)> populated = std::function<void(const QList<int> &)>()); + QStringList webSafeFontFamilies(); + void populateWebSafeFamilies(std::function<void(const QList<int> &)> populated = std::function<void(const QList<int> &)>()); } class QtWebUtils : public QObject diff --git a/qtwebutils_localfonts.cpp b/qtwebutils_localfonts.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c58e8ce345ab4f04732c8a21c8653edff6a17322 --- /dev/null +++ b/qtwebutils_localfonts.cpp @@ -0,0 +1,199 @@ + +#include "qtwebutils.h" + +#include <QtCore/private/qstdweb_p.h> +#include <QtGui/qfontdatabase.h> + +typedef std::multimap<QString, emscripten::val> FontFamiliesMap; +Q_GLOBAL_STATIC(FontFamiliesMap, g_fontFamiliesFonts); +Q_GLOBAL_STATIC(QStringList, g_fontFamilies); + +namespace { + +bool verifyLocalFontsSupport() +{ + emscripten::val window = emscripten::val::global("window"); + emscripten::val queryFn = window["queryLocalFonts"]; + static bool didwarn = false; + if (queryFn.isUndefined() && !didwarn) { + didwarn = true; + qWarning() << "This browser does not support local font access (window.queryLocalFonts is undefined)"; + return false; + } + return true; +} + +void queryLocalFonts(std::function<void(const QStringList &)> familiesCallback) +{ + emscripten::val window = emscripten::val::global("window"); + qstdweb::Promise::make(window, "queryLocalFonts", { + .thenFunc = [familiesCallback](emscripten::val fontArray) { + const int count = fontArray["length"].as<int>(); + for (int i = 0; i < count; ++i) { + const emscripten::val font = fontArray.call<emscripten::val>("at", i); + const QString family = QString::fromStdString(font["family"].as<std::string>()); + g_fontFamiliesFonts->insert(std::make_pair(family, font)); + if (!g_fontFamilies->contains(family)) + g_fontFamilies->append(family); + if (i == count - 1 && familiesCallback) + familiesCallback(*g_fontFamilies); + } + }, + .catchFunc = [familiesCallback](emscripten::val err) { + qWarning() << "Error while trying to query local-fonts API" + << QString::fromStdString(err["name"].as<std::string>()) + << QString::fromStdString(err["message"].as<std::string>()); + if (familiesCallback) + familiesCallback(*g_fontFamilies); + } + }); +} + +void populateFontBlob(emscripten::val fontBlob, std::function<void(int)> populatedCallback) +{ + qstdweb::Promise::make(fontBlob, "arrayBuffer", { + .thenFunc = [populatedCallback](emscripten::val fontArrayBuffer) { + QByteArray fontData = qstdweb::Uint8Array(qstdweb::ArrayBuffer(fontArrayBuffer)).copyToQByteArray(); + static quint64 totalSize = 0; + totalSize += fontData.length(); + // qDebug() << "total font data size" << totalSize; + int id = QFontDatabase::addApplicationFontFromData(fontData); + if (populatedCallback) + populatedCallback(id); + }, + .catchFunc = [](emscripten::val err) { + qWarning() << "Error while loading font ArrayBuffer" + << QString::fromStdString(err["name"].as<std::string>()) + << QString::fromStdString(err["message"].as<std::string>()); + } + }); +} + +void populateFontFamily(const QString &familiy, std::function<void(const QList<int> &)> populatedCallback) +{ + auto fontsRange = g_fontFamiliesFonts->equal_range(familiy); + struct State { + QList<int> fontIds; + int fontCounter; + }; + State *state = new State(); + state->fontCounter = std::distance(fontsRange.first, fontsRange.second); + for (auto it = fontsRange.first; it != fontsRange.second; ++it) { + emscripten::val font = it->second; + qstdweb::Promise::make(font, "blob", { + .thenFunc = [state, populatedCallback](emscripten::val blob) { + populateFontBlob(blob, [state, populatedCallback](int id){ + state->fontIds.append(id); + state->fontCounter--; + if (state->fontCounter == 0) { + if (populatedCallback) + populatedCallback(state->fontIds); + delete state; + } + }); + }, + .catchFunc = [state, familiy](emscripten::val err) { + qWarning() << "Error while loading font Blob for family" << familiy + << QString::fromStdString(err["name"].as<std::string>()) + << QString::fromStdString(err["message"].as<std::string>()); + delete state; + } + }); + } +} + +} // namespace + +/* + Local font support. + + The user must grant permission to access local fonts. The browser will prompt + the user to grant permission on the first call to window.queryLocalFonts(), or + in this case on the first call of any of the functions below. The first call + must happen in response to user input, for example a button click. + + Fonts are loaded fully into memory as freetype memory fonts. The full installed + local font set will often contain several gigabytes of font data, and loading + all fonts is not recomended. +*/ + +/*! + Queries for available local font families. +*/ +void qtwebutils::getFontFamilies(std::function<void(const QStringList &)> familiesCallback) +{ + if (!verifyLocalFontsSupport()) + return; + + if (g_fontFamiliesFonts->empty()) { + if (familiesCallback) + familiesCallback(*g_fontFamilies); + } else { + queryLocalFonts(familiesCallback); + } +} + +/*! + Populates the font database with local fonts for the given font family. + + The fonts are installed as application fonts, e.g. added with + QFontDatabase::addApplicationFontFromData(). +*/ +void qtwebutils::populateFontFamily(const QString &familiy, std::function<void(const QList<int> &)> populatedCallback) +{ + if (!verifyLocalFontsSupport()) + return; + + if (g_fontFamiliesFonts->empty()) { + queryLocalFonts([familiy, populatedCallback](const QStringList &){ + // g_fontFamiliesFonts should now be populated + ::populateFontFamily(familiy, populatedCallback); + }); + } else { + ::populateFontFamily(familiy, populatedCallback); + } +} + +/*! + Populates the font database with local fonts for the given font families. + + The fonts are installed as application fonts, e.g. added with + QFontDatabase::addApplicationFontFromData(). +*/ +void qtwebutils::populateFontFamilies(const QStringList &families, std::function<void(const QList<int> &)> populatedCallback) +{ + if (!verifyLocalFontsSupport()) + return; + + if (g_fontFamiliesFonts->empty()) { + queryLocalFonts([families, populatedCallback](const QStringList &){ + // TODO: combine callbacks into one update for all families + for (const QString &family : families) + populateFontFamily(family, populatedCallback); + }); + } else { + for (const QString &family : families) + populateFontFamily(family, populatedCallback); + } +} + +/*! + Returns a list of "web safe" font families. + +*/ +QStringList qtwebutils::webSafeFontFamilies() +{ + return {"Arial", "Verdana", "Tahoma", "Trebuchet", "Times New Roman", + "Georgia", "Garamond", "Courier New"}; +} + +/*! + Populates the font database with local fonts for the "web safe" font families. + + The fonts are installed as application fonts, e.g. added with + QFontDatabase::addApplicationFontFromData(). +*/ +void qtwebutils::populateWebSafeFamilies(std::function<void(const QList<int> &)> populatedCallback) +{ + populateFontFamilies(webSafeFontFamilies(), populatedCallback); +}