Spaces:
Sleeping
Sleeping
//======================================================================== | |
// | |
// CIDFontsWidthsBuilder.h | |
// | |
// This file is licensed under the GPLv2 or later | |
// | |
// Copyright 2023 g10 Code GmbH, Author: Sune Stolborg Vuorela <[email protected]> | |
//======================================================================== | |
/** Class to help build the widths array as defined in | |
pdf standard 9.7.4.3 Glyph Metrcis in CIDFonts in | |
ISO 32000-2:2020 | |
The way to use this is to create a builder, then add all the widths | |
and their attached code in order using \ref addWidth and finally call \ref takeSegments | |
The resulting value is a list of segments of either \ref ListSegment or | |
\ref RangeSegment | |
*/ | |
class CIDFontsWidthsBuilder | |
{ | |
public: | |
/// Segment that should be encoded as a first index and a list of n number specifying the next n widths | |
class ListSegment | |
{ | |
public: | |
int first; | |
std::vector<int> widths; | |
}; | |
/// Segment that should be encoded as 3 integers, first, last (included) and the width for that group. | |
class RangeSegment | |
{ | |
public: | |
int first; | |
int last; | |
int width; | |
}; | |
using Segment = std::variant<RangeSegment, ListSegment>; | |
/** | |
* Adds a width for a given index. | |
* | |
* Must be called with ever increasing indices until \ref takeSegments | |
* has been called | |
*/ | |
void addWidth(int index, int width) | |
{ | |
if (m_currentSegment.m_lastIndex.has_value() && index <= m_currentSegment.m_lastIndex) { | |
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. | |
return; | |
} | |
while (!m_currentSegment.accept(index, width)) { | |
segmentDone(); | |
} | |
} | |
/** | |
* \return the resulting segments and resets this font builder | |
*/ | |
[[nodiscard]] std::vector<Segment> takeSegments() | |
{ | |
finish(); | |
auto rv = std::move(m_segments); | |
m_segments = {}; | |
return rv; | |
} | |
private: | |
void finish() | |
{ | |
while (m_currentSegment.m_values.size()) { | |
segmentDone(); | |
} | |
m_currentSegment = {}; | |
} | |
class SegmentBuilder | |
{ | |
// How many elements at the end has this | |
int uniqueElementsFromEnd(int value) | |
{ | |
auto lastDifferent = std::find_if(m_values.rbegin(), m_values.rend(), [value](auto &&element) { return element != value; }); | |
return std::distance(m_values.rbegin(), lastDifferent); | |
} | |
public: | |
/** Tries to add a index/width combo. | |
* If a value is not accepted, caller should | |
* build a segment and repeat the accept call. | |
* | |
* \return if accepted or not | |
*/ | |
bool accept(int index, int value) | |
{ | |
if (m_lastIndex.has_value() && m_lastIndex != index - 1) { | |
// we have gaps. That's okay. We just need to ensure to finish the segment | |
return false; | |
} | |
if (!m_firstIndex) { | |
m_firstIndex = index; | |
} | |
if (m_values.size() < 4) { | |
m_values.push_back(value); | |
if (m_values.front() != value) { | |
differentValues = true; | |
} | |
m_lastIndex = index; | |
return true; | |
} | |
if (!differentValues) { | |
if (m_values.back() == value) { | |
m_values.push_back(value); | |
m_lastIndex = index; | |
return true; | |
} else { | |
// We need to end a range segment | |
// to start a new segment with different value | |
return false; | |
} | |
} else { | |
if (uniqueElementsFromEnd(value) >= 3) { | |
// We now have at least 3 unique elements | |
// at the end, so we should finish the previous | |
// list segment and then start a range segment | |
return false; | |
} else { | |
m_values.push_back(value); | |
m_lastIndex = index; | |
return true; | |
} | |
} | |
} | |
/** | |
* Builds the segment of the values so far. | |
*/ | |
Segment build() | |
{ | |
if (differentValues || m_values.size() < 4) { | |
std::vector<int> savedValues; | |
if (m_values.size() >= 4) { | |
auto lastDifferent = std::find_if(m_values.rbegin(), m_values.rend(), [value = m_values.back()](auto &&element) { return element != value; }); | |
if (std::distance(m_values.rbegin(), lastDifferent) >= 3) { | |
savedValues.push_back(m_values.back()); | |
m_values.pop_back(); | |
while (m_values.size() && m_values.back() == savedValues.back()) { | |
savedValues.push_back(m_values.back()); | |
m_values.pop_back(); | |
} | |
} | |
} | |
ListSegment segment { m_firstIndex.value(), std::move(m_values) }; | |
if (!savedValues.empty()) { | |
m_firstIndex = m_lastIndex.value() - savedValues.size() + 1; | |
} else { | |
m_firstIndex = {}; | |
m_lastIndex = {}; | |
} | |
m_values = std::move(savedValues); | |
differentValues = false; | |
return segment; | |
} else { | |
auto segment = RangeSegment { m_firstIndex.value(), m_lastIndex.value(), m_values.back() }; | |
m_values.clear(); | |
m_firstIndex = {}; | |
m_lastIndex = {}; | |
differentValues = false; | |
return segment; | |
} | |
} | |
std::vector<int> m_values; | |
std::optional<int> m_lastIndex; | |
std::optional<int> m_firstIndex; | |
bool differentValues = false; | |
}; | |
std::vector<Segment> m_segments; | |
SegmentBuilder m_currentSegment; | |
void segmentDone() { m_segments.push_back(m_currentSegment.build()); } | |
}; | |