Coverage Report

Created: 2026-10-28 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtsvg/src/svg/qsvgfont.cpp
Line
Count
Source
1
// Copyright (C) 2016 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
// Qt-Security score:significant reason:default
4
5
6
#include "qsvgfont_p.h"
7
8
#include "qpainter.h"
9
#include "qpen.h"
10
#include "qdebug.h"
11
12
QT_BEGIN_NAMESPACE
13
14
QSvgGlyph::QSvgGlyph(const QString &unicode, const QPainterPath &path, qreal horizAdvX)
15
0
    : m_unicode(unicode), m_path(path), m_horizAdvX(horizAdvX)
16
0
{
17
18
0
}
19
20
21
QSvgFont::QSvgFont(qreal horizAdvX)
22
98
    : m_horizAdvX(horizAdvX)
23
98
{
24
98
}
25
26
98
QSvgFont::~QSvgFont()
27
    = default;
28
29
void QSvgFont::addGlyph(const QString &unicode, const QPainterPath &path, qreal horizAdvX)
30
0
{
31
0
    m_glyphs.emplaceBack(unicode, path, (horizAdvX == -1) ? m_horizAdvX : horizAdvX);
32
0
}
33
34
bool QSvgFont::addMissingGlyph(const QPainterPath &path, qreal horizAdvX)
35
0
{
36
0
    if (m_missingGlyph) {
37
0
        qWarning("The font already has a 'missing-glyph' element.");
38
0
        return false;
39
0
    }
40
0
    m_missingGlyph.reset(new QSvgGlyph(QChar(), path, (horizAdvX == -1) ? m_horizAdvX : horizAdvX));
41
0
    return true;
42
0
}
43
44
45
void QSvgFont::draw(QPainter *p, const QPointF &point, const QList<const QSvgGlyph *> &glyphs,
46
                    qreal pixelSize, Qt::Alignment alignment) const
47
0
{
48
0
    draw_helper(p, point, glyphs, pixelSize, alignment, nullptr);
49
0
}
50
51
QRectF QSvgFont::boundingRect(QPainter *p, const QPointF &point,
52
                              const QList<const QSvgGlyph *> &glyphs,
53
                              qreal pixelSize, Qt::Alignment alignment) const
54
0
{
55
0
    QRectF bounds;
56
0
    draw_helper(p, point, glyphs, pixelSize, alignment, &bounds);
57
0
    return bounds;
58
0
}
59
60
// text must not be empty
61
char32_t firstUcs4(QStringView text)
62
0
{
63
0
    if (text.size() > 1 && text.at(0).isHighSurrogate() && text.at(1).isLowSurrogate())
64
0
        return QChar::surrogateToUcs4(text.at(0), text.at(1));
65
66
0
    return text.first().unicode();
67
0
}
68
69
// returns a pointer to the first QSvgGlyph to be used in the text
70
// or nullptr if none can be found
71
const QSvgGlyph *QSvgFont::findFirstGlyphFor(QStringView text) const
72
0
{
73
0
    const auto possibleIndices = m_possibleGlyphIndicesForChar.value(firstUcs4(text));
74
0
    for (const qsizetype i : possibleIndices) {
75
0
        const QSvgGlyph &currentGlyph = m_glyphs.at(i);
76
0
        if (text.startsWith(currentGlyph.m_unicode)) {
77
0
            return &currentGlyph;
78
0
        }
79
0
    }
80
81
0
    for (; m_firstUnscannedGlyphIdx < m_glyphs.size(); ++m_firstUnscannedGlyphIdx) {
82
0
        const QSvgGlyph &currentGlyph = m_glyphs.at(m_firstUnscannedGlyphIdx);
83
0
        m_possibleGlyphIndicesForChar[firstUcs4(currentGlyph.m_unicode)].push_back(m_firstUnscannedGlyphIdx);
84
0
        if (text.startsWith(currentGlyph.m_unicode)) {
85
0
            ++m_firstUnscannedGlyphIdx;
86
0
            return &currentGlyph;
87
0
        }
88
0
    }
89
0
    return nullptr;
90
0
}
91
92
QList<const QSvgGlyph *> QSvgFont::toGlyphs(QStringView text) const
93
0
{
94
0
    QList<const QSvgGlyph *> glyphs;
95
0
    glyphs.reserve(text.length());
96
0
    while (text.length()) {
97
0
        const QSvgGlyph *foundGlyph = findFirstGlyphFor(text);
98
0
        if (foundGlyph) {
99
0
            glyphs.append(foundGlyph);
100
0
            text.slice(foundGlyph->m_unicode.length());
101
0
        } else {
102
0
            if (m_missingGlyph)
103
0
                glyphs.append(m_missingGlyph.get());
104
0
            if (text.size() > 1
105
0
                && text.at(0).isHighSurrogate() && text.at(1).isLowSurrogate()) {
106
0
                text.slice(2);
107
0
            } else {
108
0
                text.slice(1);
109
0
            }
110
0
        }
111
0
    }
112
0
    return glyphs;
113
0
}
114
115
void QSvgFont::draw_helper(QPainter *p, const QPointF &point,
116
                           const QList<const QSvgGlyph *> &glyphs, qreal pixelSize,
117
                           Qt::Alignment alignment, QRectF *boundingRect) const
118
0
{
119
0
    const bool isPainting = (boundingRect == nullptr);
120
121
0
    p->save();
122
0
    p->translate(point);
123
0
    p->scale(pixelSize / m_unitsPerEm, -pixelSize / m_unitsPerEm);
124
125
    // Calculate the text width to be used for alignment
126
0
    int textWidth = 0;
127
0
    for (const auto *glyph : glyphs)
128
0
        textWidth += static_cast<int>(glyph->m_horizAdvX);
129
130
0
    QPoint alignmentOffset(0, 0);
131
0
    if (alignment == Qt::AlignHCenter) {
132
0
        alignmentOffset.setX(-textWidth / 2);
133
0
    } else if (alignment == Qt::AlignRight) {
134
0
        alignmentOffset.setX(-textWidth);
135
0
    }
136
137
0
    p->translate(alignmentOffset);
138
139
    // since in SVG the embedded font ain't really a path
140
    // the outline has got to stay untransformed...
141
0
    qreal penWidth = p->pen().widthF();
142
0
    penWidth /= (pixelSize/m_unitsPerEm);
143
0
    QPen pen = p->pen();
144
0
    pen.setWidthF(penWidth);
145
0
    p->setPen(pen);
146
147
0
    for (const auto *glyph : glyphs) {
148
0
        if (isPainting)
149
0
            p->drawPath(glyph->m_path);
150
151
0
        if (boundingRect) {
152
0
            QPainterPathStroker stroker;
153
0
            stroker.setWidth(penWidth);
154
0
            stroker.setJoinStyle(p->pen().joinStyle());
155
0
            stroker.setMiterLimit(p->pen().miterLimit());
156
0
            QPainterPath stroke = stroker.createStroke(glyph->m_path);
157
0
            *boundingRect |= p->transform().map(stroke).boundingRect();
158
0
        }
159
160
0
        p->translate(glyph->m_horizAdvX, 0);
161
0
    }
162
163
0
    p->restore();
164
0
}
165
166
void QSvgFont::setUnitsPerEm(qreal upem)
167
0
{
168
0
    m_unitsPerEm = upem;
169
0
}
170
171
QT_END_NAMESPACE