Coverage Report

Created: 2026-08-25 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/poppler/poppler/CIDFontsWidthsBuilder.h
Line
Count
Source
1
//========================================================================
2
//
3
// CIDFontsWidthsBuilder.h
4
//
5
// This file is licensed under the GPLv2 or later
6
//
7
// Copyright 2023 g10 Code GmbH, Author: Sune Stolborg Vuorela <sune@vuorela.dk>
8
// Copyright 2024, 2025 Albert Astals Cid <aacid@kde.org>
9
//========================================================================
10
11
#ifndef CIDFontsWidthsBuilder_H
12
#define CIDFontsWidthsBuilder_H
13
14
#include <optional>
15
#include <vector>
16
#include <variant>
17
#include <algorithm>
18
#include <ranges>
19
#include <cassert>
20
21
/** Class to help build the widths array as defined in
22
    pdf standard 9.7.4.3 Glyph Metrcis in CIDFonts in
23
    ISO 32000-2:2020
24
25
    The way to use this is to create a builder, then add all the widths
26
    and their attached code in order using \ref addWidth and finally call \ref takeSegments
27
28
    The resulting value is a list of segments of either \ref ListSegment or
29
    \ref RangeSegment
30
    */
31
class CIDFontsWidthsBuilder
32
{
33
public:
34
    /// Segment that should be encoded as a first index and a list of n number specifying the next n widths
35
    class ListSegment
36
    {
37
    public:
38
        int first;
39
        std::vector<int> widths;
40
    };
41
    /// Segment that should be encoded as 3 integers, first, last (included) and the width for that group.
42
    class RangeSegment
43
    {
44
    public:
45
        int first;
46
        int last;
47
        int width;
48
    };
49
    using Segment = std::variant<RangeSegment, ListSegment>;
50
51
    /**
52
     * Adds a width for a given index.
53
     *
54
     * Must be called with ever increasing indices until \ref takeSegments
55
     * has been called
56
     */
57
    void addWidth(int index, int width)
58
0
    {
59
0
        if (m_currentSegment.m_lastIndex.has_value() && index <= m_currentSegment.m_lastIndex) {
60
0
            assert(false); // this is likely a error originating from the user of this code that this function gets called twice with the same or decreasing value.
61
0
            return;
62
0
        }
63
0
        while (!m_currentSegment.accept(index, width)) {
64
0
            segmentDone();
65
0
        }
66
0
    }
67
68
    /**
69
     * \return the resulting segments and resets this font builder
70
     */
71
    [[nodiscard]] std::vector<Segment> takeSegments()
72
0
    {
73
0
        finish();
74
0
        auto rv = std::move(m_segments);
75
0
        m_segments = {};
76
0
        return rv;
77
0
    }
78
79
private:
80
    void finish()
81
0
    {
82
0
        while (!m_currentSegment.m_values.empty()) {
83
0
            segmentDone();
84
0
        }
85
0
        m_currentSegment = {};
86
0
    }
87
    class SegmentBuilder
88
    {
89
        // How many elements at the end has this
90
        int uniqueElementsFromEnd(int value)
91
0
        {
92
0
            auto lastDifferent = std::ranges::find_if(std::ranges::reverse_view(m_values), [value](auto &&element) { return element != value; });
93
0
            return std::distance(m_values.rbegin(), lastDifferent);
94
0
        }
95
96
    public:
97
        /** Tries to add a index/width combo.
98
         * If a value is not accepted, caller should
99
         * build a segment and repeat the accept call.
100
         *
101
         * \return if accepted or not
102
         */
103
        bool accept(int index, int value)
104
0
        {
105
0
            if (m_lastIndex.has_value() && m_lastIndex != index - 1) {
106
                // we have gaps. That's okay. We just need to ensure to finish the segment
107
0
                return false;
108
0
            }
109
0
            if (!m_firstIndex) {
110
0
                m_firstIndex = index;
111
0
            }
112
0
            if (m_values.size() < 4) {
113
0
                m_values.push_back(value);
114
0
                if (m_values.front() != value) {
115
0
                    differentValues = true;
116
0
                }
117
0
                m_lastIndex = index;
118
0
                return true;
119
0
            }
120
0
            if (!differentValues) {
121
0
                if (m_values.back() == value) {
122
0
                    m_values.push_back(value);
123
0
                    m_lastIndex = index;
124
0
                    return true;
125
0
                }
126
                // We need to end a range segment
127
                // to start a new segment with different value
128
0
                return false;
129
0
            }
130
0
            if (uniqueElementsFromEnd(value) >= 3) {
131
                // We now have at least 3 unique elements
132
                // at the end, so we should finish the previous
133
                // list segment and then start a range segment
134
0
                return false;
135
0
            }
136
0
            m_values.push_back(value);
137
0
            m_lastIndex = index;
138
0
            return true;
139
0
        }
140
        /**
141
         * Builds the segment of the values so far.
142
         */
143
        Segment build()
144
0
        {
145
0
            if (differentValues || m_values.size() < 4) {
146
0
                std::vector<int> savedValues;
147
0
                if (m_values.size() >= 4) {
148
0
                    auto lastDifferent = std::ranges::find_if(std::ranges::reverse_view(m_values), [value = m_values.back()](auto &&element) { return element != value; });
149
0
                    if (std::distance(m_values.rbegin(), lastDifferent) >= 3) {
150
0
                        savedValues.push_back(m_values.back());
151
0
                        m_values.pop_back();
152
0
                        while (!m_values.empty() && m_values.back() == savedValues.back()) {
153
0
                            savedValues.push_back(m_values.back());
154
0
                            m_values.pop_back();
155
0
                        }
156
0
                    }
157
0
                }
158
159
0
                assert(m_firstIndex.has_value());
160
0
                ListSegment segment { .first = m_firstIndex.value(), .widths = std::move(m_values) };
161
0
                if (!savedValues.empty()) {
162
0
                    assert(m_lastIndex.has_value());
163
0
                    m_firstIndex = m_lastIndex.value() - savedValues.size() + 1;
164
0
                } else {
165
0
                    m_firstIndex = {};
166
0
                    m_lastIndex = {};
167
0
                }
168
0
                m_values = std::move(savedValues);
169
0
                differentValues = false;
170
0
                return segment;
171
0
            }
172
0
            assert(m_firstIndex.has_value());
173
0
            assert(m_lastIndex.has_value());
174
0
            auto segment = RangeSegment { .first = m_firstIndex.value(), .last = m_lastIndex.value(), .width = m_values.back() };
175
0
            m_values.clear();
176
0
            m_firstIndex = {};
177
0
            m_lastIndex = {};
178
0
            differentValues = false;
179
0
            return segment;
180
0
        }
181
        std::vector<int> m_values;
182
        std::optional<int> m_lastIndex;
183
        std::optional<int> m_firstIndex;
184
        bool differentValues = false;
185
    };
186
    std::vector<Segment> m_segments;
187
    SegmentBuilder m_currentSegment;
188
189
0
    void segmentDone() { m_segments.push_back(m_currentSegment.build()); }
190
};
191
192
#endif // CIDFontsWidthsBuilder_H