diff options
Diffstat (limited to 'src/plugins/platforms/android/qandroidplatformiconengine.cpp')
-rw-r--r-- | src/plugins/platforms/android/qandroidplatformiconengine.cpp | 613 |
1 files changed, 613 insertions, 0 deletions
diff --git a/src/plugins/platforms/android/qandroidplatformiconengine.cpp b/src/plugins/platforms/android/qandroidplatformiconengine.cpp new file mode 100644 index 0000000000..a88696886d --- /dev/null +++ b/src/plugins/platforms/android/qandroidplatformiconengine.cpp @@ -0,0 +1,613 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qandroidplatformiconengine.h" +#include "androidjnimain.h" + +#include <QtCore/qdebug.h> +#include <QtCore/qjniarray.h> +#include <QtCore/qjniobject.h> +#include <QtCore/qloggingcategory.h> +#include <QtCore/qfile.h> +#include <QtCore/qset.h> + +#include <QtGui/qfontdatabase.h> +#include <QtGui/qpainter.h> +#include <QtGui/qpalette.h> + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; +Q_LOGGING_CATEGORY(lcIconEngineFontDownload, "qt.qpa.iconengine.fontdownload") + +// the primary types to work with the FontRequest API +Q_DECLARE_JNI_CLASS(FontRequest, "androidx/core/provider/FontRequest") +Q_DECLARE_JNI_CLASS(FontsContractCompat, "androidx/core/provider/FontsContractCompat") +Q_DECLARE_JNI_CLASS(FontFamilyResult, "androidx/core/provider/FontsContractCompat$FontFamilyResult") +Q_DECLARE_JNI_CLASS(FontInfo, "androidx/core/provider/FontsContractCompat$FontInfo") + +// various utility types +Q_DECLARE_JNI_CLASS(List, "java/util/List"); // List is just an Interface +Q_DECLARE_JNI_CLASS(HashSet, "java/util/HashSet"); +Q_DECLARE_JNI_CLASS(CancellationSignal, "android/os/CancellationSignal") +Q_DECLARE_JNI_CLASS(ParcelFileDescriptor, "android/os/ParcelFileDescriptor") +Q_DECLARE_JNI_CLASS(PackageManager, "android/content/pm/PackageManager") +Q_DECLARE_JNI_CLASS(ProviderInfo, "android/content/pm/ProviderInfo") +Q_DECLARE_JNI_CLASS(PackageInfo, "android/content/pm/PackageInfo") +Q_DECLARE_JNI_CLASS(Signature, "android/content/pm/Signature") + +namespace FontProvider { + +static QString fetchFont(const QString &query) +{ + using namespace QtJniTypes; + + static QMap<QString, QString> triedFonts; + const auto it = triedFonts.find(query); + if (it != triedFonts.constEnd()) + return it.value(); + + QString fontFamily; + triedFonts[query] = fontFamily; // mark as tried + + QStringList loadedFamilies; + if (QFile file(query); file.open(QIODevice::ReadOnly)) { + qCDebug(lcIconEngineFontDownload) << "Loading font from resource" << query; + const QByteArray fontData = file.readAll(); + int fontId = QFontDatabase::addApplicationFontFromData(fontData); + loadedFamilies << QFontDatabase::applicationFontFamilies(fontId); + } else if (!query.startsWith(u":/"_s)) { + const QString package = u"com.google.android.gms"_s; + const QString authority = u"com.google.android.gms.fonts"_s; + + // First we access the content provider to get the signatures of the authority for the package + const auto context = QtAndroidPrivate::context(); + + auto packageManager = context.callMethod<PackageManager>("getPackageManager"); + if (!packageManager.isValid()) { + qCWarning(lcIconEngineFontDownload, "Failed to instantiate PackageManager"); + return fontFamily; + } + const int signaturesField = PackageManager::getStaticField<int>("GET_SIGNATURES"); + auto providerInfo = packageManager.callMethod<ProviderInfo>("resolveContentProvider", + authority, 0); + if (!providerInfo.isValid()) { + qCWarning(lcIconEngineFontDownload, "Failed to resolve content provider"); + return fontFamily; + } + const QString packageName = providerInfo.getField<QString>("packageName"); + if (packageName != package) { + qCWarning(lcIconEngineFontDownload, "Mismatched provider package - expected '%s', got '%s'", + package.toUtf8().constData(), packageName.toUtf8().constData()); + return fontFamily; + } + auto packageInfo = packageManager.callMethod<PackageInfo>("getPackageInfo", + package, signaturesField); + if (!packageInfo.isValid()) { + qCWarning(lcIconEngineFontDownload, "Failed to get package info with signature field %d", + signaturesField); + return fontFamily; + } + const auto signatures = packageInfo.getField<Signature[]>("signatures"); + if (!signatures.isValid()) { + qCWarning(lcIconEngineFontDownload, "Failed to get signature array from package info"); + return fontFamily; + } + + // FontRequest wants a list of sets for the certificates + ArrayList outerList; + HashSet innerSet; + Q_ASSERT(outerList.isValid() && innerSet.isValid()); + + for (const auto &signature : signatures) { + const QJniArray<jbyte> byteArray = signature.callMethod<jbyte[]>("toByteArray"); + + // add takes an Object, not an Array + if (!innerSet.callMethod<jboolean>("add", byteArray.object<jobject>())) + qCWarning(lcIconEngineFontDownload, "Failed to add signature to set"); + } + // Add the set to the list + if (!outerList.callMethod<jboolean>("add", innerSet.object())) + qCWarning(lcIconEngineFontDownload, "Failed to add set to certificate list"); + + // FontRequest constructor wants a List interface, not an ArrayList + FontRequest fontRequest(authority, package, query, outerList.object<List>()); + if (!fontRequest.isValid()) { + qCWarning(lcIconEngineFontDownload, "Failed to create font request for '%s'", + query.toUtf8().constData()); + return fontFamily; + } + + // Call FontsContractCompat::fetchFonts with the FontRequest object + auto fontFamilyResult = FontsContractCompat::callStaticMethod<FontFamilyResult>( + "fetchFonts", + context, + CancellationSignal(nullptr), + fontRequest); + if (!fontFamilyResult.isValid()) { + qCWarning(lcIconEngineFontDownload, "Failed to fetch fonts for query '%s'", + query.toUtf8().constData()); + return fontFamily; + } + + enum class StatusCode { + OK = 0, + UNEXPECTED_DATA_PROVIDED = 1, + WRONG_CERTIFICATES = 2, + }; + + const StatusCode statusCode = fontFamilyResult.callMethod<StatusCode>("getStatusCode"); + switch (statusCode) { + case StatusCode::OK: + break; + case StatusCode::UNEXPECTED_DATA_PROVIDED: + qCWarning(lcIconEngineFontDownload, "Provider returned unexpected data for query '%s'", + query.toUtf8().constData()); + return fontFamily; + case StatusCode::WRONG_CERTIFICATES: + qCWarning(lcIconEngineFontDownload, "Wrong Certificates provided in query '%s'", + query.toUtf8().constData()); + return fontFamily; + } + + const auto fontInfos = fontFamilyResult.callMethod<FontInfo[]>("getFonts"); + if (!fontInfos.isValid()) { + qCWarning(lcIconEngineFontDownload, "FontFamilyResult::getFonts returned null object for '%s'", + query.toUtf8().constData()); + return fontFamily; + } + + auto contentResolver = context.callMethod<ContentResolver>("getContentResolver"); + + for (QJniObject fontInfo : fontInfos) { + if (!fontInfo.isValid()) { + qCDebug(lcIconEngineFontDownload, "Received null-fontInfo object, skipping"); + continue; + } + enum class ResultCode { + OK = 0, + FONT_NOT_FOUND = 1, + FONT_UNAVAILABLE = 2, + MALFORMED_QUERY = 3, + }; + const ResultCode resultCode = fontInfo.callMethod<ResultCode>("getResultCode"); + switch (resultCode) { + case ResultCode::OK: + break; + case ResultCode::FONT_NOT_FOUND: + qCWarning(lcIconEngineFontDownload, "Font '%s' could not be found", + query.toUtf8().constData()); + return fontFamily; + case ResultCode::FONT_UNAVAILABLE: + qCWarning(lcIconEngineFontDownload, "Font '%s' is unavailable at", + query.toUtf8().constData()); + return fontFamily; + case ResultCode::MALFORMED_QUERY: + qCWarning(lcIconEngineFontDownload, "Query string '%s' is malformed", + query.toUtf8().constData()); + return fontFamily; + } + auto fontUri = fontInfo.callMethod<Uri>("getUri"); + // in this case the Font URI is always a content scheme file, made + // so the app requesting it has permissions to open + auto fileDescriptor = contentResolver.callMethod<ParcelFileDescriptor>("openFileDescriptor", + fontUri, u"r"_s); + if (!fileDescriptor.isValid()) { + qCWarning(lcIconEngineFontDownload, "Font file '%s' not accessible", + fontUri.toString().toUtf8().constData()); + continue; + } + + int fd = fileDescriptor.callMethod<int>("detachFd"); + QFile file; + file.open(fd, QFile::OpenModeFlag::ReadOnly, QFile::FileHandleFlag::AutoCloseHandle); + const QByteArray fontData = file.readAll(); + qCDebug(lcIconEngineFontDownload) << "Font file read:" << fontData.size() << "bytes"; + int fontId = QFontDatabase::addApplicationFontFromData(fontData); + loadedFamilies << QFontDatabase::applicationFontFamilies(fontId); + } + } + + qCDebug(lcIconEngineFontDownload) << "Query '" << query << "' added families" << loadedFamilies; + if (!loadedFamilies.isEmpty()) + fontFamily = loadedFamilies.first(); + triedFonts[query] = fontFamily; + return fontFamily; +} +} + +QString QAndroidPlatformIconEngine::glyphs() const +{ + if (!QFontInfo(m_iconFont).exactMatch()) + return {}; + + static constexpr std::pair<QLatin1StringView, QStringView> glyphMap[] = { + {"address-book-new"_L1, u"\ue0e0"}, + {"application-exit"_L1, u"\ue5cd"}, + {"appointment-new"_L1, u"\ue878"}, + {"call-start"_L1, u"\ue0b0"}, + {"call-stop"_L1, u"\ue0b1"}, + {"contact-new"_L1, u"\uf22e"}, + {"document-new"_L1, u"\ue89c"}, + {"document-open"_L1, u"\ue2c8"}, + {"document-open-recent"_L1, u"\ue4a7"}, + {"document-page-setup"_L1, u"\uf88c"}, + {"document-print"_L1, u"\ue8ad"}, + {"document-print-preview"_L1, u"\uefb2"}, + {"document-properties"_L1, u"\uf775"}, + {"document-revert"_L1, u"\ue929"}, + {"document-save"_L1, u"\ue161"}, + {"document-save-as"_L1, u"\ueb60"}, + {"document-send"_L1, u"\uf09b"}, + {"edit-clear"_L1, u"\ue872"}, + {"edit-copy"_L1, u"\ue14d"}, + {"edit-cut"_L1, u"\ue14e"}, + {"edit-delete"_L1, u"\ue14a"}, + {"edit-find"_L1, u"\ue8b6"}, + {"edit-find-replace"_L1, u"\ue881"}, + {"edit-paste"_L1, u"\ue14f"}, + {"edit-redo"_L1, u"\ue15a"}, + {"edit-select-all"_L1, u"\ue162"}, + {"edit-undo"_L1, u"\ue166"}, + {"folder-new"_L1, u"\ue2cc"}, + {"format-indent-less"_L1, u"\ue23d"}, + {"format-indent-more"_L1, u"\ue23e"}, + {"format-justify-center"_L1, u"\ue234"}, + {"format-justify-fill"_L1, u"\ue235"}, + {"format-justify-left"_L1, u"\ue236"}, + {"format-justify-right"_L1, u"\ue237"}, + {"format-text-direction-ltr"_L1, u"\ue247"}, + {"format-text-direction-rtl"_L1, u"\ue248"}, + {"format-text-bold"_L1, u"\ue238"}, + {"format-text-italic"_L1, u"\ue23f"}, + {"format-text-underline"_L1, u"\ue249"}, + {"format-text-strikethrough"_L1, u"\ue246"}, + {"go-bottom"_L1,u"\ue258"}, + {"go-down"_L1,u"\uf1e3"}, + {"go-first"_L1, u"\ue5dc"}, + {"go-home"_L1, u"\ue88a"}, + {"go-jump"_L1, u"\uf719"}, + {"go-last"_L1, u"\ue5dd"}, + {"go-next"_L1, u"\ue5c8"}, + {"go-previous"_L1, u"\ue5c4"}, + {"go-top"_L1, u"\ue25a"}, + {"go-up"_L1, u"\uf1e0"}, + {"help-about"_L1, u"\ue88e"}, + {"help-contents"_L1, u"\ue8de"}, + {"help-faq"_L1, u"\uf04c"}, + {"insert-image"_L1, u"\ue43e"}, + {"insert-link"_L1, u"\ue178"}, + //{"insert-object"_L1, u"\u"}, + {"insert-text"_L1, u"\uf827"}, + {"list-add"_L1, u"\ue145"}, + {"list-remove"_L1, u"\ue15b"}, + {"mail-forward"_L1, u"\ue154"}, + {"mail-mark-important"_L1, u"\ue937"}, + //{"mail-mark-junk"_L1, u"\u"}, + //{"mail-mark-notjunk"_L1, u"\u"}, + {"mail-mark-read"_L1, u"\uf18c"}, + {"mail-mark-unread"_L1, u"\ue9bc"}, + {"mail-message-new"_L1, u"\ue3c9"}, + {"mail-reply-all"_L1, u"\ue15f"}, + {"mail-reply-sender"_L1, u"\ue15e"}, + {"mail-send"_L1, u"\ue163"}, + //{"mail-send-receive"_L1, u"\u"}, + {"media-eject"_L1, u"\ue8fb"}, + {"media-playback-pause"_L1, u"\ue034"}, + {"media-playback-start"_L1, u"\ue037"}, + {"media-playback-stop"_L1, u"\ue047"}, + {"media-record"_L1, u"\uf679"}, + {"media-seek-backward"_L1, u"\ue020"}, + {"media-seek-forward"_L1, u"\ue01f"}, + {"media-skip-backward"_L1, u"\ue045"}, + {"media-skip-forward"_L1, u"\ue044"}, + //{"object-flip-horizontal"_L1, u"\u"}, + //{"object-flip-vertical"_L1, u"\u"}, + {"object-rotate-left"_L1, u"\ue419"}, + {"object-rotate-right"_L1, u"\ue41a"}, + {"process-stop"_L1, u"\ue5c9"}, + {"system-lock-screen"_L1, u"\ue897"}, + {"system-log-out"_L1, u"\ue9ba"}, + //{"system-run"_L1, u"\u"}, + {"system-search"_L1, u"\uef70"}, + {"system-reboot"_L1, u"\uf053"}, + {"system-shutdown"_L1, u"\ue8ac"}, + {"tools-check-spelling"_L1, u"\ue8ce"}, + {"view-fullscreen"_L1, u"\ue5d0"}, + {"view-refresh"_L1, u"\ue5d5"}, + {"view-restore"_L1, u"\uf1cf"}, + {"view-sort-ascending"_L1, u"\ue25a"}, + {"view-sort-descending"_L1, u"\ue258"}, + {"window-close"_L1, u"\ue5cd"}, + {"window-new"_L1, u"\uf710"}, + {"zoom-fit-best"_L1, u"\uea10"}, + {"zoom-in"_L1, u"\ue8ff"}, + {"zoom-original"_L1, u"\ue5d1"}, + {"zoom-out"_L1, u"\ue900"}, + {"process-working"_L1, u"\uef64"}, + {"accessories-calculator"_L1, u"\uea5f"}, + {"accessories-character-map"_L1, u"\uf8a3"}, + {"accessories-dictionary"_L1, u"\uf539"}, + {"accessories-text-editor"_L1, u"\ue262"}, + {"help-browser"_L1, u"\ue887"}, + {"multimedia-volume-control"_L1, u"\ue050"}, + {"preferences-desktop-accessibility"_L1, u"\uf05d"}, + {"preferences-desktop-font"_L1, u"\ue165"}, + {"preferences-desktop-keyboard"_L1, u"\ue312"}, + //{"preferences-desktop-locale"_L1, u"\u"}, + {"preferences-desktop-multimedia"_L1, u"\uea75"}, + //{"preferences-desktop-screensaver"_L1, u"\u"}, + {"preferences-desktop-theme"_L1, u"\uf560"}, + {"preferences-desktop-wallpaper"_L1, u"\ue1bc"}, + {"system-file-manager"_L1, u"\ue2c7"}, + {"system-software-install"_L1, u"\ueb71"}, + {"system-software-update"_L1, u"\ue8d7"}, + {"utilities-system-monitor"_L1, u"\uef5b"}, + {"utilities-terminal"_L1, u"\ueb8e"}, + //{"applications-accessories"_L1, u"\u"}, + {"applications-development"_L1, u"\ue720"}, + {"applications-engineering"_L1, u"\uea3d"}, + {"applications-games"_L1, u"\uf135"}, + //{"applications-graphics"_L1, u"\u"}, + {"applications-internet"_L1, u"\ue80b"}, + {"applications-multimedia"_L1, u"\uf06a"}, + //{"applications-office"_L1, u"\u"}, + //{"applications-other"_L1, u"\u"}, + {"applications-science"_L1, u"\uea4b"}, + //{"applications-system"_L1, u"\u"}, + //{"applications-utilities"_L1, u"\u"}, + {"preferences-desktop"_L1, u"\ueb97"}, + //{"preferences-desktop-peripherals"_L1, u"\u"}, + {"preferences-desktop-personal"_L1, u"\uf835"}, + //{"preferences-other"_L1, u"\u"}, + {"preferences-system"_L1, u"\ue8b8"}, + {"preferences-system-network"_L1, u"\ue894"}, + {"system-help"_L1, u"\ue887"}, + //{"audio-card"_L1, u"\u"}, + {"audio-input-microphone"_L1, u"\ue029"}, + {"battery"_L1, u"\ue1a4"}, + {"camera-photo"_L1, u"\ue412"}, + {"camera-video"_L1, u"\ue04b"}, + {"camera-web"_L1, u"\uf7a6"}, + {"computer"_L1, u"\ue30a"}, + {"drive-harddisk"_L1, u"\uf80e"}, + {"drive-optical"_L1, u"\ue019"}, // same as media-optical + //{"drive-removable-media"_L1, u"\u"}, + {"input-gaming"_L1, u"\uf5ee"}, + {"input-keyboard"_L1, u"\ue312"}, + {"input-mouse"_L1, u"\ue323"}, + //{"input-tablet"_L1, u"\u"}, + //{"media-flash"_L1, u"\u"}, + //{"media-floppy"_L1, u"\u"}, + {"media-optical"_L1, u"\ue019"}, + //{"media-tape"_L1, u"\u"}, + //{"modem"_L1, u"\u"}, + //{"multimedia-player"_L1, u"\u"}, + //{"network-wired"_L1, u"\u"}, + {"network-wireless"_L1, u"\ue63e"}, + //{"pda"_L1, u"\u"}, + {"phone"_L1, u"\ue32c"}, + {"printer"_L1, u"\ue8ad"}, + {"scanner"_L1, u"\ue329"}, + {"video-display"_L1, u"\uf06a"}, + //{"emblem-default"_L1, u"\u"}, + {"emblem-documents"_L1, u"\ue873"}, + {"emblem-downloads"_L1, u"\uf090"}, + {"emblem-favorite"_L1, u"\uf090"}, + {"emblem-important"_L1, u"\ue645"}, + {"emblem-mail"_L1, u"\ue158"}, + {"emblem-photos"_L1, u"\ue413"}, + //{"emblem-readonly"_L1, u"\u"}, + {"emblem-shared"_L1, u"\ue413"}, + //{"emblem-symbolic-link"_L1, u"\u"}, + //{"emblem-synchronized"_L1, u"\u"}, + {"emblem-system"_L1, u"\ue8b8"}, + //{"emblem-unreadable"_L1, u"\u"}, + {"folder"_L1, u"\ue2c7"}, + //{"folder-remote"_L1, u"\u"}, + {"network-server"_L1, u"\ue875"}, + {"network-workgroup"_L1, u"\ue1a0"}, + {"start-here"_L1, u"\ue089"}, + {"user-bookmarks"_L1, u"\ue98b"}, + {"user-desktop"_L1, u"\ue30a"}, + {"user-home"_L1, u"\ue88a"}, + {"user-trash"_L1, u"\ue872"}, + {"appointment-missed"_L1, u"\ue615"}, + {"appointment-soon"_L1, u"\uf540"}, + {"audio-volume-high"_L1, u"\ue050"}, + {"audio-volume-low"_L1, u"\ue04d"}, + //{"audio-volume-medium"_L1, u"\u"}, + {"audio-volume-muted"_L1, u"\ue04e"}, + {"battery-caution"_L1, u"\ue19c"}, + {"battery-low"_L1, u"\uf147"}, + {"dialog-error"_L1, u"\ue000"}, + {"dialog-information"_L1, u"\ue88e"}, + {"dialog-password"_L1, u"\uf042"}, + {"dialog-question"_L1, u"\ueb8b"}, + {"dialog-warning"_L1, u"\ue002"}, + {"folder-drag-accept"_L1, u"\ue9a3"}, + {"folder-open"_L1, u"\ue2c8"}, + {"folder-visiting"_L1, u"\ue8a7"}, + {"image-loading"_L1, u"\ue41a"}, + {"image-missing"_L1, u"\ue3ad"}, + {"mail-attachment"_L1, u"\ue2bc"}, + {"mail-unread"_L1, u"\uf18a"}, + {"mail-read"_L1, u"\uf18c"}, + //{"mail-replied"_L1, u"\u"}, + //{"mail-signed"_L1, u"\u"}, + //{"mail-signed-verified"_L1, u"\u"}, + {"media-playlist-repeat"_L1, u"\ue040"}, + {"media-playlist-shuffle"_L1, u"\ue043"}, + {"network-error"_L1, u"\uead9"}, + {"network-idle"_L1, u"\ue51f"}, + {"network-offline"_L1, u"\uf239"}, + {"network-receive"_L1, u"\ue2c0"}, + {"network-transmit"_L1, u"\ue2c3"}, + {"network-transmit-receive"_L1, u"\uea18"}, + {"printer-error"_L1, u"\uf7a0"}, + {"printer-printing"_L1, u"\uf7a1"}, + {"security-high"_L1, u"\ue32a"}, + {"security-medium"_L1, u"\ue9e0"}, + {"security-low"_L1, u"\uf012"}, + {"software-update-available"_L1, u"\ue923"}, + {"software-update-urgent"_L1, u"\uf05a"}, + {"sync-error"_L1, u"\ue629"}, + {"sync-synchronizing"_L1, u"\ue627"}, + //{"task-due"_L1, u"\u"}, + //{"task-past-due"_L1, u"\u"}, + {"user-available"_L1, u"\uf565"}, + {"user-away"_L1, u"\ue510"}, + //{"user-idle"_L1, u"\u"}, + {"user-offline"_L1, u"\uf7b3"}, + {"user-trash-full"_L1, u"\ue872"}, //delete + //{"user-trash-full"_L1, u"\ue92b"}, //delete_forever + {"weather-clear"_L1, u"\uf157"}, + {"weather-clear-night"_L1, u"\uf159"}, + {"weather-few-clouds"_L1, u"\uf172"}, + {"weather-few-clouds-night"_L1, u"\uf174"}, + {"weather-fog"_L1, u"\ue818"}, + //{"weather-overcast"_L1, u"\u"}, + {"weather-severe-alert"_L1, u"\ue002"}, //warning + //{"weather-severe-alert"_L1, u"\uebd3"},//severe_cold + {"weather-showers"_L1, u"\uf176"}, + //{"weather-showers-scattered"_L1, u"\u"}, + {"weather-snow"_L1, u"\ue80f"}, //snowing + //{"weather-snow"_L1, u"\ue2cd"}, //weather_snowy + //{"weather-snow"_L1, u"\ue810"},//cloudy_snowing + {"weather-storm"_L1, u"\uf070"}, + }; + + const auto it = std::find_if(std::begin(glyphMap), std::end(glyphMap), [this](const auto &c){ + return c.first == m_iconName; + }); + return it != std::end(glyphMap) ? it->second.toString() + : (m_iconName.length() == 1 ? m_iconName : QString()); +} + +QAndroidPlatformIconEngine::QAndroidPlatformIconEngine(const QString &iconName) + : m_iconName(iconName) + , m_glyphs(glyphs()) +{ + QString fontFamily; + // The MaterialIcons-*.ttf and MaterialSymbols* font files are available from + // https://github.com/google/material-design-icons/tree/master. If one of them is + // packaged as a resource with the application, then we use it. We prioritize + // a variable font. + const QStringList fontCandidates = { + "MaterialSymbolsOutlined[FILL,GRAD,opsz,wght].ttf", + "MaterialSymbolsRounded[FILL,GRAD,opsz,wght].ttf", + "MaterialSymbolsSharp[FILL,GRAD,opsz,wght].ttf", + "MaterialIcons-Regular.ttf", + "MaterialIconsOutlined-Regular.otf", + "MaterialIconsRound-Regular.otf", + "MaterialIconsSharp-Regular.otf", + "MaterialIconsTwoTone-Regular.otf", + }; + for (const auto &fontCandidate : fontCandidates) { + fontFamily = FontProvider::fetchFont(u":/qt-project.org/icons/%1"_s.arg(fontCandidate)); + if (!fontFamily.isEmpty()) + break; + } + + // Otherwise we try to download the Outlined version of Material Symbols + const QString key = qEnvironmentVariable("QT_GOOGLE_FONTS_KEY"); + if (fontFamily.isEmpty() && !key.isEmpty()) + fontFamily = FontProvider::fetchFont(u"key=%1&name=Material+Symbols+Outlined"_s.arg(key)); + + // last resort - use any Material Icons + if (fontFamily.isEmpty()) + fontFamily = u"Material Icons"_s; + m_iconFont = QFont(fontFamily); +} + +QAndroidPlatformIconEngine::~QAndroidPlatformIconEngine() +{} + +QIconEngine *QAndroidPlatformIconEngine::clone() const +{ + return new QAndroidPlatformIconEngine(m_iconName); +} + +QString QAndroidPlatformIconEngine::key() const +{ + return u"QAndroidPlatformIconEngine"_s; +} + +QString QAndroidPlatformIconEngine::iconName() +{ + return m_iconName; +} + +bool QAndroidPlatformIconEngine::isNull() +{ + if (m_glyphs.isEmpty()) + return true; + const QChar c0 = m_glyphs.at(0); + const QFontMetrics fontMetrics(m_iconFont); + if (c0.category() == QChar::Other_Surrogate && m_glyphs.size() > 1) + return !fontMetrics.inFontUcs4(QChar::surrogateToUcs4(c0, m_glyphs.at(1))); + return !fontMetrics.inFont(c0); +} + +QList<QSize> QAndroidPlatformIconEngine::availableSizes(QIcon::Mode, QIcon::State) +{ + return {{16, 16}, {24, 24}, {48, 48}, {128, 128}}; +} + +QSize QAndroidPlatformIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + return QIconEngine::actualSize(size, mode, state); +} + +QPixmap QAndroidPlatformIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) +{ + return scaledPixmap(size, mode, state, 1.0); +} + +QPixmap QAndroidPlatformIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) +{ + const quint64 cacheKey = calculateCacheKey(mode, state); + if (cacheKey != m_cacheKey || m_pixmap.size() != size || m_pixmap.devicePixelRatio() != scale) { + m_pixmap = QPixmap(size * scale); + m_pixmap.fill(Qt::transparent); + m_pixmap.setDevicePixelRatio(scale); + + QPainter painter(&m_pixmap); + paint(&painter, QRect(QPoint(), size), mode, state); + + m_cacheKey = cacheKey; + } + + return m_pixmap; +} + +void QAndroidPlatformIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) +{ + Q_UNUSED(state); + + painter->save(); + QFont renderFont(m_iconFont); + renderFont.setPixelSize(rect.height()); + painter->setFont(renderFont); + + QPalette palette; + switch (mode) { + case QIcon::Active: + painter->setPen(palette.color(QPalette::Active, QPalette::Text)); + break; + case QIcon::Normal: + painter->setPen(palette.color(QPalette::Active, QPalette::Text)); + break; + case QIcon::Disabled: + painter->setPen(palette.color(QPalette::Disabled, QPalette::Text)); + break; + case QIcon::Selected: + painter->setPen(palette.color(QPalette::Active, QPalette::HighlightedText)); + break; + } + + painter->drawText(rect, Qt::AlignCenter, m_glyphs); + painter->restore(); +} + +QT_END_NAMESPACE |