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