Refactor shared code of font-based icon engines

Add private QFontIconEngine that renders the glyphs; the specialized
engines have to specify the font, and implement the mapping of icon name
to glyph sequence (i.e. a QString).

Use it for the Android and Windows icon engines.

Task-number: QTBUG-102346
Change-Id: I6b0f0d6bc0378ed6918ea7cfb9dcce12ac86e8ab
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
Volker Hilsheimer 2024-11-13 10:38:41 +01:00
parent 176816f213
commit 2af58490b3
7 changed files with 269 additions and 254 deletions

View File

@ -68,6 +68,7 @@ qt_internal_add_module(Gui
image/qabstractfileiconprovider.cpp image/qabstractfileiconprovider.h image/qabstractfileiconprovider_p.h
image/qbitmap.cpp image/qbitmap.h
image/qbmphandler.cpp image/qbmphandler_p.h
image/qfonticonengine.cpp image/qfonticonengine_p.h
image/qicon.cpp image/qicon.h image/qicon_p.h
image/qiconengine.cpp image/qiconengine.h image/qiconengine_p.h
image/qiconengineplugin.cpp image/qiconengineplugin.h

View File

@ -0,0 +1,118 @@
// Copyright (C) 2024 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 "qfonticonengine_p.h"
#ifndef QT_NO_ICON
#include <QtCore/qdebug.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;
QFontIconEngine::QFontIconEngine(const QString &iconName, const QFont &font)
: m_iconName(iconName)
, m_iconFont(font)
{
}
QFontIconEngine::~QFontIconEngine() = default;
QString QFontIconEngine::iconName()
{
return m_iconName;
}
bool QFontIconEngine::isNull()
{
const QString text = string();
if (text.isEmpty())
return true;
const QChar c0 = text.at(0);
const QFontMetrics fontMetrics(m_iconFont);
if (c0.category() == QChar::Other_Surrogate && text.size() > 1)
return !fontMetrics.inFontUcs4(QChar::surrogateToUcs4(c0, text.at(1)));
return !fontMetrics.inFont(c0);
}
QList<QSize> QFontIconEngine::availableSizes(QIcon::Mode, QIcon::State)
{
return {{16, 16}, {24, 24}, {48, 48}, {128, 128}};
}
QSize QFontIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
{
return QIconEngine::actualSize(size, mode, state);
}
QPixmap QFontIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
{
return scaledPixmap(size, mode, state, 1.0);
}
QPixmap QFontIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
{
const quint64 cacheKey = calculateCacheKey(mode, state);
if (cacheKey != m_pixmapCacheKey || m_pixmap.size() != size
|| m_pixmap.devicePixelRatio() != scale) {
m_pixmap = QPixmap(size * scale);
m_pixmap.fill(Qt::transparent);
m_pixmap.setDevicePixelRatio(scale);
if (!m_pixmap.isNull()) {
QPainter painter(&m_pixmap);
paint(&painter, QRect(QPoint(), size), mode, state);
}
m_pixmapCacheKey = cacheKey;
}
return m_pixmap;
}
void QFontIconEngine::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;
}
const QString text = string();
painter->drawText(rect, Qt::AlignCenter, text);
painter->restore();
}
QString QFontIconEngine::string() const
{
return {};
}
QT_END_NAMESPACE
#endif // QT_NO_ICON

View File

@ -0,0 +1,59 @@
// 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
#ifndef QFONTICONENGINE_H
#define QFONTICONENGINE_H
#include <QtGui/qiconengine.h>
#ifndef QT_NO_ICON
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtGui/qfont.h>
QT_BEGIN_NAMESPACE
class Q_GUI_EXPORT QFontIconEngine : public QIconEngine
{
public:
QFontIconEngine(const QString &iconName, const QFont &font);
~QFontIconEngine();
QString iconName() override;
bool isNull() override;
QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override;
QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
protected:
virtual QString string() const;
private:
static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state)
{
return (quint64(mode) << 32) | state;
}
const QString m_iconName;
const QFont m_iconFont;
mutable QPixmap m_pixmap;
mutable quint64 m_pixmapCacheKey = {};
};
QT_END_NAMESPACE
#endif // QT_NO_ICON
#endif // QFONTICONENGINE_H

View File

@ -2,6 +2,9 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qandroidplatformiconengine.h"
#ifndef QT_NO_ICON
#include "androidjnimain.h"
#include <QtCore/qdebug.h>
@ -214,13 +217,45 @@ static QString fetchFont(const QString &query)
triedFonts[query] = fontFamily;
return fontFamily;
}
static QFont selectFont()
{
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;
return QFont(fontFamily);
}
QString QAndroidPlatformIconEngine::glyphs() const
{
if (!QFontInfo(m_iconFont).exactMatch())
return {};
} // namespace FontProvider
static QString getGlyphs(QStringView iconName)
{
static constexpr std::pair<QLatin1StringView, QStringView> glyphMap[] = {
{"address-book-new"_L1, u"\ue0e0"},
{"application-exit"_L1, u"\ue5cd"},
@ -477,139 +512,38 @@ QString QAndroidPlatformIconEngine::glyphs() const
{"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;
const auto it = std::find_if(std::begin(glyphMap),
std::end(glyphMap), [iconName](const auto &c){
return c.first == iconName;
});
return it != std::end(glyphMap) ? it->second.toString()
: (m_iconName.length() == 1 ? m_iconName : QString());
: (iconName.length() == 1 ? iconName.toString() : QString());
}
QAndroidPlatformIconEngine::QAndroidPlatformIconEngine(const QString &iconName)
: m_iconName(iconName)
, m_glyphs(glyphs())
: QFontIconEngine(iconName, FontProvider::selectFont())
, m_glyphs(getGlyphs(iconName))
{
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);
}
QAndroidPlatformIconEngine::~QAndroidPlatformIconEngine() = default;
QString QAndroidPlatformIconEngine::key() const
{
return u"QAndroidPlatformIconEngine"_s;
}
QString QAndroidPlatformIconEngine::iconName()
QIconEngine *QAndroidPlatformIconEngine::clone() const
{
return m_iconName;
QAndroidPlatformIconEngine *that = const_cast<QAndroidPlatformIconEngine *>(this);
return new QAndroidPlatformIconEngine(that->iconName());
}
bool QAndroidPlatformIconEngine::isNull()
QString QAndroidPlatformIconEngine::string() const
{
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);
if (!m_pixmap.isNull()) {
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();
return m_glyphs;
}
QT_END_NAMESPACE
#endif // QT_NO_ICON

View File

@ -4,41 +4,30 @@
#ifndef QANDROIDPLATFORMICONENGINE_H
#define QANDROIDPLATFORMICONENGINE_H
#include <QtGui/qiconengine.h>
#include <QtGui/qfont.h>
#include <QtGui/private/qfonticonengine_p.h>
#ifndef QT_NO_ICON
QT_BEGIN_NAMESPACE
class QAndroidPlatformIconEngine : public QIconEngine
class QAndroidPlatformIconEngine : public QFontIconEngine
{
public:
QAndroidPlatformIconEngine(const QString &iconName);
~QAndroidPlatformIconEngine();
QIconEngine *clone() const override;
QString key() const override;
QString iconName() override;
bool isNull() override;
QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override;
QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
QString key() const override;
QIconEngine *clone() const override;
protected:
QString string() const override;
private:
static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state)
{
return (quint64(mode) << 32) | state;
}
QString glyphs() const;
const QString m_iconName;
QFont m_iconFont;
const QString m_glyphs;
mutable QPixmap m_pixmap;
mutable quint64 m_cacheKey = {};
};
QT_END_NAMESPACE
#endif // QT_NO_ICON
#endif // QANDROIDPLATFORMICONENGINE_H

View File

@ -3,6 +3,8 @@
#include "qwindowsiconengine.h"
#ifndef QT_NO_ICON
#include <QtCore/qoperatingsystemversion.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qpainter.h>
@ -12,11 +14,8 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
QString QWindowsIconEngine::glyphs() const
static QString getGlyphs(QStringView iconName)
{
if (!QFontInfo(m_iconFont).exactMatch())
return {};
static constexpr std::pair<QLatin1StringView, QStringView> glyphMap[] = {
{"address-book-new"_L1, u"\ue780"},
{"application-exit"_L1, u"\ue8bb"},
@ -277,16 +276,17 @@ QString QWindowsIconEngine::glyphs() const
//{"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;
const auto it = std::find_if(std::begin(glyphMap),
std::end(glyphMap), [iconName](const auto &c){
return c.first == iconName;
});
return it != std::end(glyphMap) ? it->second.toString()
: (m_iconName.length() == 1 ? m_iconName : QString());
: (iconName.length() == 1 ? iconName.toString() : QString());
}
namespace {
auto iconFontFamily()
static auto iconFontFamily()
{
static const bool isWindows11 = QOperatingSystemVersion::current() >= QOperatingSystemVersion::Windows11;
return isWindows11 ? u"Segoe Fluent Icons"_s
@ -295,102 +295,30 @@ auto iconFontFamily()
}
QWindowsIconEngine::QWindowsIconEngine(const QString &iconName)
: m_iconName(iconName), m_iconFont(iconFontFamily())
, m_glyphs(glyphs())
: QFontIconEngine(iconName, iconFontFamily())
, m_glyphs(getGlyphs(iconName))
{
}
QWindowsIconEngine::~QWindowsIconEngine()
{}
QIconEngine *QWindowsIconEngine::clone() const
{
return new QWindowsIconEngine(m_iconName);
}
QString QWindowsIconEngine::key() const
{
return u"QWindowsIconEngine"_s;
}
QString QWindowsIconEngine::iconName()
QIconEngine *QWindowsIconEngine::clone() const
{
return m_iconName;
QWindowsIconEngine *that = const_cast<QWindowsIconEngine *>(this);
return new QWindowsIconEngine(that->iconName());
}
bool QWindowsIconEngine::isNull()
QString QWindowsIconEngine::string() const
{
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> QWindowsIconEngine::availableSizes(QIcon::Mode, QIcon::State)
{
return {{16, 16}, {24, 24}, {48, 48}, {128, 128}};
}
QSize QWindowsIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
{
return QIconEngine::actualSize(size, mode, state);
}
QPixmap QWindowsIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
{
return scaledPixmap(size, mode, state, 1.0);
}
QPixmap QWindowsIconEngine::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);
if (!m_pixmap.isNull()) {
QPainter painter(&m_pixmap);
paint(&painter, QRect(QPoint(), size), mode, state);
}
m_cacheKey = cacheKey;
}
return m_pixmap;
}
void QWindowsIconEngine::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();
return m_glyphs;
}
QT_END_NAMESPACE
#endif //QT_NO_ICON

View File

@ -4,44 +4,30 @@
#ifndef QWINDOWSICONENGINE_H
#define QWINDOWSICONENGINE_H
#include <QtCore/qt_windows.h>
#include <QtGui/private/qfonticonengine_p.h>
#include <QtGui/qfont.h>
#include <QtGui/qiconengine.h>
#ifndef QT_NO_ICON
QT_BEGIN_NAMESPACE
class QWindowsIconEngine : public QIconEngine
class QWindowsIconEngine : public QFontIconEngine
{
public:
QWindowsIconEngine(const QString &iconName);
~QWindowsIconEngine();
QIconEngine *clone() const override;
QString key() const override;
QString iconName() override;
bool isNull() override;
QList<QSize> availableSizes(QIcon::Mode, QIcon::State) override;
QSize actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
QPixmap scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale) override;
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
QString key() const override;
QIconEngine *clone() const override;
protected:
QString string() const override;
private:
static constexpr quint64 calculateCacheKey(QIcon::Mode mode, QIcon::State state)
{
return (quint64(mode) << 32) | state;
}
QString glyphs() const;
const QString m_iconName;
const QFont m_iconFont;
const QString m_glyphs;
mutable QPixmap m_pixmap;
mutable quint64 m_cacheKey = {};
};
QT_END_NAMESPACE
#endif // QT_NO_ICON
#endif // QWINDOWSICONENGINE_H