1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qfonticonengine_p.h"
5
6#ifndef QT_NO_ICON
7
8#include <QtCore/qdebug.h>
9#include <QtCore/qfile.h>
10#include <QtCore/qset.h>
11
12#include <QtGui/qfontdatabase.h>
13#include <QtGui/qpainter.h>
14#include <QtGui/qpainterpath.h>
15#include <QtGui/qpalette.h>
16#include <QtGui/qtextlayout.h>
17
18#include <QtGui/private/qfont_p.h>
19#include <QtGui/private/qfontengine_p.h>
20
21QT_BEGIN_NAMESPACE
22
23using namespace Qt::StringLiterals;
24
25QFontIconEngine::QFontIconEngine(const QString &iconName, const QFont &font)
26 : m_iconName(iconName)
27 , m_iconFont(font)
28{
29 Q_ASSERT_X(font.styleStrategy() & QFont::NoFontMerging, "QFontIconEngine",
30 "Icon fonts must not use font merging");
31}
32
33QFontIconEngine::~QFontIconEngine() = default;
34
35QIconEngine *QFontIconEngine::clone() const
36{
37 return new QFontIconEngine(m_iconName, m_iconFont);
38}
39
40QString QFontIconEngine::key() const
41{
42 return u"QFontIconEngine("_s + m_iconFont.key() + u')';
43}
44
45QString QFontIconEngine::iconName()
46{
47 return m_iconName;
48}
49
50bool QFontIconEngine::isNull()
51{
52 const QString text = string();
53 if (!text.isEmpty()) {
54 const QChar c0 = text.at(i: 0);
55 const QFontMetrics fontMetrics(m_iconFont);
56 if (c0.isHighSurrogate() && text.size() > 1)
57 return !fontMetrics.inFontUcs4(ucs4: QChar::surrogateToUcs4(high: c0, low: text.at(i: 1)));
58 return !fontMetrics.inFont(c0);
59 }
60
61 return glyph() == 0;
62}
63
64QList<QSize> QFontIconEngine::availableSizes(QIcon::Mode, QIcon::State)
65{
66 return {{16, 16}, {24, 24}, {48, 48}, {128, 128}};
67}
68
69QSize QFontIconEngine::actualSize(const QSize &size, QIcon::Mode mode, QIcon::State state)
70{
71 if (isNull())
72 return QIconEngine::actualSize(size, mode, state);
73
74 QFont renderFont(m_iconFont);
75 renderFont.setPixelSize(size.height());
76 QSizeF result;
77 if (const glyph_t glyphIndex = glyph()) {
78 QFontEngine *engine = QFontPrivate::get(font: renderFont)->engineForScript(script: QChar::Script_Common);
79
80 const glyph_metrics_t gm = engine->boundingBox(glyph: glyphIndex);
81 const qreal glyph_x = gm.x.toReal();
82 const qreal glyph_y = gm.y.toReal();
83 const qreal glyph_width = (gm.x + gm.width).toReal() - glyph_x;
84 const qreal glyph_height = (gm.y + gm.height).toReal() - glyph_y;
85
86 if (glyph_width > .0 && glyph_height > .0)
87 result = {glyph_width, glyph_height};
88 } else if (const QString text = string(); !text.isEmpty()) {
89 const QFontMetricsF fm(renderFont);
90 result = {fm.horizontalAdvance(string: text), fm.tightBoundingRect(text).height()};
91 }
92 if (!result.isValid())
93 return QIconEngine::actualSize(size, mode, state);
94
95 return result.scaled(s: size, mode: Qt::KeepAspectRatio).toSize();
96}
97
98QPixmap QFontIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
99{
100 return scaledPixmap(size, mode, state, scale: 1.0);
101}
102
103QPixmap QFontIconEngine::scaledPixmap(const QSize &size, QIcon::Mode mode, QIcon::State state, qreal scale)
104{
105 const quint64 cacheKey = calculateCacheKey(mode, state);
106 const QSize fittingSize = actualSize(size, mode, state);
107 if (cacheKey != m_pixmapCacheKey || m_pixmap.deviceIndependentSize() != fittingSize
108 || m_pixmap.devicePixelRatio() != scale) {
109 m_pixmap = QPixmap(fittingSize * scale);
110 m_pixmap.fill(fillColor: Qt::transparent);
111 m_pixmap.setDevicePixelRatio(scale);
112
113 if (!m_pixmap.isNull()) {
114 QPainter painter(&m_pixmap);
115 paint(painter: &painter, rect: QRect(QPoint(), fittingSize), mode, state);
116 }
117
118 m_pixmapCacheKey = cacheKey;
119 }
120
121 return m_pixmap;
122}
123
124void QFontIconEngine::paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state)
125{
126 Q_UNUSED(state);
127
128 painter->save();
129 QFont renderFont(m_iconFont);
130 renderFont.setPixelSize(rect.height());
131
132 QColor color = Qt::black;
133 QPalette palette;
134 switch (mode) {
135 case QIcon::Active:
136 color = palette.color(cg: QPalette::Active, cr: QPalette::Text);
137 break;
138 case QIcon::Normal:
139 color = palette.color(cg: QPalette::Active, cr: QPalette::Text);
140 break;
141 case QIcon::Disabled:
142 color = palette.color(cg: QPalette::Disabled, cr: QPalette::Text);
143 break;
144 case QIcon::Selected:
145 color = palette.color(cg: QPalette::Active, cr: QPalette::HighlightedText);
146 break;
147 }
148
149 if (glyph_t glyphIndex = glyph()) {
150 QFontEngine *engine = QFontPrivate::get(font: renderFont)->engineForScript(script: QChar::Script_Common);
151
152 const glyph_metrics_t gm = engine->boundingBox(glyph: glyphIndex);
153 const int glyph_x = qFloor(v: gm.x.toReal());
154 const int glyph_y = qFloor(v: gm.y.toReal());
155 const int glyph_width = qCeil(v: (gm.x + gm.width).toReal()) - glyph_x;
156 const int glyph_height = qCeil(v: (gm.y + gm.height).toReal()) - glyph_y;
157
158 if (glyph_width > 0 && glyph_height > 0) {
159 QFixedPoint pt(QFixed(-glyph_x), QFixed(-glyph_y));
160 QPainterPath path;
161 path.setFillRule(Qt::WindingFill);
162 engine->addGlyphsToPath(glyphs: &glyphIndex, positions: &pt, nglyphs: 1, path: &path, flags: {});
163 // make the glyph fit tightly into rect
164 const QRectF pathBoundingRect = path.boundingRect();
165 // center the glyph inside the rect
166 const QPointF topLeft = rect.topLeft() - pathBoundingRect.topLeft()
167 + (QPointF(rect.width(), rect.height())
168 - QPointF(pathBoundingRect.width(), pathBoundingRect.height())) / 2;
169 painter->translate(offset: topLeft);
170
171 painter->setRenderHint(hint: QPainter::Antialiasing);
172 painter->setPen(Qt::NoPen);
173 painter->setBrush(color);
174 painter->drawPath(path);
175 }
176 } else if (const QString text = string(); !text.isEmpty()) {
177 painter->setFont(renderFont);
178 painter->setPen(color);
179 painter->drawText(r: rect, flags: Qt::AlignCenter, text);
180 }
181 painter->restore();
182}
183
184QString QFontIconEngine::string() const
185{
186 return {};
187}
188
189glyph_t QFontIconEngine::glyph() const
190{
191 if (m_glyph == uninitializedGlyph) {
192 QFontEngine *engine = QFontPrivate::get(font: m_iconFont)->engineForScript(script: QChar::Script_Common);
193 if (engine)
194 m_glyph = engine->findGlyph(name: QLatin1StringView(m_iconName.toLatin1()));
195 if (!m_glyph) {
196 // May not be a named glyph, but there might be a ligature for the
197 // icon name.
198 QTextLayout layout(m_iconName, m_iconFont);
199 layout.beginLayout();
200 layout.createLine();
201 layout.endLayout();
202 const auto glyphRuns = layout.glyphRuns();
203 if (glyphRuns.size() == 1) {
204 const auto glyphIndexes = glyphRuns.first().glyphIndexes();
205 if (glyphIndexes.size() == 1)
206 m_glyph = glyphIndexes.first();
207 }
208 }
209 }
210 return m_glyph;
211}
212
213QT_END_NAMESPACE
214
215#endif // QT_NO_ICON
216

source code of qtbase/src/gui/image/qfonticonengine.cpp