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 */
29class CIDFontsWidthsBuilder
30{
31public:
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
77private:
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

source code of poppler/poppler/CIDFontsWidthsBuilder.h