Coverage Report

Created: 2026-02-10 07:39

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