/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 |