/* This is a port of to C++ from Rust.
* It is not complete but implements the most important parts.
*/
#ifndef MONOFORMAT_FONTREADER_HPP
#define MONOFORMAT_FONTREADER_HPP
#include "monoformat_parsehelpers.hpp"
#include "monoformat_gfx.hpp"
#include "monoformat_utf8.hpp"
#include
#include
#include
#include
#include
#include
#include
namespace monoformat {
class GlyphReader;
enum class LookupError {
GlyphNotFound = 0,
InvalidUnicodeSequence = 1,
};
enum class RenderError {
Unknown = -1,
};
struct UnicodeJumptableEntry {
std::uint16_t jumpDistance{};
std::uint16_t characterUpperLimit{};
};
class UnicodeJumptableReader {
public:
explicit UnicodeJumptableReader(std::span data)
: m_data{data}
{
}
std::optional calculateJumpOffset(std::uint16_t encoding) {
std::size_t jumpOffset = 0;
std::optional entry;
do {
entry = nextEntry();
if (!entry) {
return std::nullopt;
}
jumpOffset += entry->jumpDistance;
} while (entry->characterUpperLimit < encoding);
return jumpOffset;
}
std::optional nextEntry() {
auto jumpDistance_ = readU16BE(m_data);
auto characterUpperLimit_ = readU16BE(m_data);
if (!jumpDistance_ || !characterUpperLimit_) {
return std::nullopt;
}
return UnicodeJumptableEntry{*jumpDistance_, *characterUpperLimit_};
}
private:
std::span m_data;
};
template
class GlyphSearcher;
class FontReader {
public:
explicit FontReader(std::span data);
bool supportsBackgroundColor() const noexcept { return m_supportsBackgroundColor; }
std::uint8_t glyphCount() const noexcept { return m_glyphCount; }
std::uint8_t m0() const noexcept { return m_m0; }
std::uint8_t m1() const noexcept { return m_m1; }
std::uint8_t bitCountW() const noexcept { return m_bitCountW; }
std::uint8_t bitCountH() const noexcept { return m_bitCountH; }
std::uint8_t bitCountX() const noexcept { return m_bitCountX; }
std::uint8_t bitCountY() const noexcept { return m_bitCountY; }
std::uint8_t bitCountD() const noexcept { return m_bitCountD; }
std::uint8_t fontBoundingBoxWidth() const noexcept { return m_fontBoundingBoxWidth; }
std::int8_t fontBoundingBoxHeight() const noexcept { return m_fontBoundingBoxHeight; }
std::int8_t fontBoundingBoxXOffset() const noexcept { return m_fontBoundingBoxXOffset; }
std::int8_t fontBoundingBoxYOffset() const noexcept { return m_fontBoundingBoxYOffset; }
std::int8_t ascent() const noexcept { return m_ascent; }
std::int8_t descent() const noexcept { return m_descent; }
std::int8_t ascentOfParentheses() const noexcept { return m_ascentOfParentheses; }
std::int8_t descentOfParentheses() const noexcept { return m_descentOfParentheses; }
std::uint16_t arrayOffsetLowerA() const noexcept { return m_arrayOffsetLowerA; }
std::uint16_t arrayOffsetUpperA() const noexcept { return m_arrayOffsetUpperA; }
std::uint16_t arrayOffset0x0100() const noexcept { return m_arrayOffset0x0100; }
bool doesIgnoreUnknownGlyphs() const noexcept { return m_ignoreUnknownGlyphs; }
std::uint32_t lineHeight() const noexcept { return m_lineHeight; }
std::uint8_t getDefaultLineHeight() const;
FontReader withIgnoreUnknownGlyphs(bool ignore) && {
m_ignoreUnknownGlyphs = ignore;
return *this;
}
FontReader withLineHeight(std::uint32_t lineHeight) && {
m_lineHeight = lineHeight;
return *this;
}
std::expected, LookupError> tryRetrieveGlyphData(std::uint32_t ch) const;
std::expected retrieveGlyphData(std::uint32_t ch) const;
private:
friend class GlyphReader;
template
friend class GlyphSearcher;
std::span m_data;
std::uint8_t m_glyphCount{};
bool m_supportsBackgroundColor{};
std::uint8_t m_m0{};
std::uint8_t m_m1{};
std::uint8_t m_bitCountW{};
std::uint8_t m_bitCountH{};
std::uint8_t m_bitCountX{};
std::uint8_t m_bitCountY{};
std::uint8_t m_bitCountD{};
std::int8_t m_fontBoundingBoxWidth{};
std::int8_t m_fontBoundingBoxHeight{};
std::int8_t m_fontBoundingBoxXOffset{};
std::int8_t m_fontBoundingBoxYOffset{};
std::int8_t m_ascent{};
std::int8_t m_descent{};
std::int8_t m_ascentOfParentheses{};
std::int8_t m_descentOfParentheses{};
std::uint16_t m_arrayOffsetUpperA{};
std::uint16_t m_arrayOffsetLowerA{};
std::uint16_t m_arrayOffset0x0100{};
bool m_ignoreUnknownGlyphs{};
std::uint32_t m_lineHeight{};
};
class GlyphReader {
public:
explicit GlyphReader(std::span data, FontReader const& font)
: m_data{data}
, m_bitCount0{font.m_m0}
, m_bitCount1{font.m_m1}
{
m_glyphWidth = readUnsigned(font.m_bitCountW);
m_glyphHeight = readUnsigned(font.m_bitCountH);
m_offsetX = readSigned(font.m_bitCountX);
m_offsetY = readSigned(font.m_bitCountY);
m_advance = readSigned(font.m_bitCountD);
}
std::uint8_t readUnsigned(std::uint8_t bits) {
std::uint8_t const bitStart = m_bitPos;
std::uint8_t bitEnd = bitStart + bits;
auto value = overflowingShr(m_currentByte, bitStart).first;
if (bitEnd >= 8) {
auto value2 = static_cast(m_data[0]);
m_data = m_data.subspan(1);
bitEnd -= 8;
m_currentByte = value2;
value |= overflowingShl(value2, 8 - bitStart).first;
}
m_bitPos = bitEnd;
return value & static_cast((std::uint16_t{1} << bits) - 1);
}
std::int8_t readSigned(std::uint8_t bits) {
return static_cast(readUnsigned(bits) - static_cast(1 << (bits - 1)));
}
Point topLeft(Point pos) const {
return {
pos.x + m_offsetX,
pos.y - (m_glyphHeight + m_offsetY),
};
}
std::int32_t left(std::int32_t posX) const {
return posX + m_offsetX;
}
Size size() const {
return {m_glyphWidth, m_glyphHeight};
}
std::uint32_t width() const {
return m_glyphWidth;
}
std::uint32_t advance() const {
return m_advance;
}
std::uint8_t readRunLength0() {
return readUnsigned(m_bitCount0);
}
std::uint8_t readRunLength1() {
return readUnsigned(m_bitCount1);
}
private:
std::span m_data;
// Start at 8 to mark the current byte as invalid
std::uint8_t m_bitPos{8};
std::uint8_t m_currentByte{0};
std::uint8_t m_glyphWidth{};
std::uint8_t m_glyphHeight{};
std::int8_t m_offsetX{};
std::int8_t m_offsetY{};
std::int8_t m_advance{};
std::uint8_t m_bitCount0{};
std::uint8_t m_bitCount1{};
};
template
class GlyphSearcherBase {
public:
void jumpBy(std::size_t offset) {
m_data = m_data.subspan(offset);
}
std::uint8_t getOffset() const {
return static_cast(m_data[CharWidth]);
}
bool jumpToNext() {
auto offset = getOffset();
if (offset == 0) {
return false;
} else {
jumpBy(offset);
return true;
}
}
GlyphReader intoGlyphReader() && {
return GlyphReader{m_data.subspan(CharWidth + 1), m_font};
}
protected:
GlyphSearcherBase(std::span data, FontReader const& font)
: m_data{data}
, m_font{font}
{
}
std::span m_data;
FontReader const& m_font;
};
template<>
class GlyphSearcher<2> : public GlyphSearcherBase<2> {
public:
std::uint16_t getCh() const {
return peekU16BE(m_data).value();
}
private:
explicit GlyphSearcher(std::span data, FontReader const& font)
: GlyphSearcherBase<2>{data, font}
{
}
template
friend class GlyphSearcher;
};
template<>
class GlyphSearcher<1> : public GlyphSearcherBase<1> {
constexpr static std::size_t const U8G2FontDataStructSize{23};
public:
explicit GlyphSearcher(FontReader const& font)
: GlyphSearcherBase<1>{font.m_data.subspan(U8G2FontDataStructSize), font}
{
}
std::uint8_t getCh() const {
return static_cast(m_data[0]);
}
std::pair, UnicodeJumptableReader> intoUnicodeMode(std::uint16_t offset) && {
jumpBy(offset);
return {GlyphSearcher<2>{m_data, m_font}, UnicodeJumptableReader{m_data}};
}
};
inline FontReader::FontReader(std::span data)
: m_data{data}
, m_glyphCount{static_cast(data[0])}
, m_supportsBackgroundColor{data[1] != std::byte{}}
, m_m0{static_cast(data[2])}
, m_m1{static_cast(data[3])}
, m_bitCountW{static_cast(data[4])}
, m_bitCountH{static_cast(data[5])}
, m_bitCountX{static_cast(data[6])}
, m_bitCountY{static_cast(data[7])}
, m_bitCountD{static_cast(data[8])}
, m_fontBoundingBoxWidth{static_cast(data[9])}
, m_fontBoundingBoxHeight{static_cast(data[10])}
, m_fontBoundingBoxXOffset{static_cast(data[11])}
, m_fontBoundingBoxYOffset{static_cast(data[12])}
, m_ascent{static_cast(data[13])}
, m_descent{static_cast(data[14])}
, m_ascentOfParentheses{static_cast(data[15])}
, m_descentOfParentheses{static_cast(data[16])}
, m_arrayOffsetUpperA{*peekU16BE(data.subspan(17, 2))}
, m_arrayOffsetLowerA{*peekU16BE(data.subspan(19, 2))}
, m_arrayOffset0x0100{*peekU16BE(data.subspan(21, 2))}
, m_ignoreUnknownGlyphs{false}
{
m_lineHeight = getDefaultLineHeight();
}
inline std::uint8_t FontReader::getDefaultLineHeight() const {
assert(m_fontBoundingBoxHeight >= 0);
return static_cast(m_fontBoundingBoxHeight) + 1;
}
inline std::expected, LookupError> FontReader::tryRetrieveGlyphData(std::uint32_t ch) const {
auto ret = retrieveGlyphData(ch);
if (!ret) {
if (m_ignoreUnknownGlyphs && ret.error() == LookupError::GlyphNotFound) {
return std::nullopt;
}
return std::unexpected(ret.error());
}
return *ret;
}
inline std::expected FontReader::retrieveGlyphData(std::uint32_t ch) const {
if (ch > std::numeric_limits::max()) {
return std::unexpected(LookupError::GlyphNotFound);
}
auto glyph = GlyphSearcher<1>(*this);
if (ch <= 255) {
if (ch >= 'a') {
glyph.jumpBy(m_arrayOffsetLowerA);
} else if (ch >= 'A') {
glyph.jumpBy(m_arrayOffsetUpperA);
}
while (glyph.getCh() != ch) {
bool ok = glyph.jumpToNext();
if (!ok) {
return std::unexpected(LookupError::GlyphNotFound);
}
}
return std::move(glyph).intoGlyphReader();
} else {
auto [gl, unicodeJumpTable] = std::move(glyph).intoUnicodeMode(m_arrayOffset0x0100);
auto jumpOffset_ = unicodeJumpTable.calculateJumpOffset(ch);
if (!jumpOffset_) {
return std::unexpected(LookupError::GlyphNotFound);
}
gl.jumpBy(*jumpOffset_);
while (true) {
auto glCh = gl.getCh();
if (glCh == 0) {
return std::unexpected(LookupError::GlyphNotFound);
}
if (glCh == ch) {
break;
}
if (!gl.jumpToNext()) {
return std::unexpected(LookupError::GlyphNotFound);
}
}
return std::move(gl).intoGlyphReader();
}
}
class GlyphRenderer {
public:
explicit GlyphRenderer(GlyphReader const& glyph)
: m_glyph{glyph}
{
}
Rectangle getBoundingBox(Point position) const {
return Rectangle{m_glyph.topLeft(position), m_glyph.size()};
}
template
std::expected renderAsBoxFill(Point position, DrawTarget& display, typename DrawTarget::Color fgColor, typename DrawTarget::Color bgColor) {
auto boundingBox = getBoundingBox(position);
int numZeros = m_glyph.readRunLength0();
int numOnes = m_glyph.readRunLength1();
int numZerosLeftOver = numZeros;
int numOnesLeftOver = numOnes;
auto colorIter = [&] () -> std::optional {
if (numZerosLeftOver == 0 && numOnesLeftOver == 0) {
bool repeat = m_glyph.readUnsigned(1) != 0;
if (!repeat) {
numZeros = m_glyph.readRunLength0();
numOnes = m_glyph.readRunLength1();
}
numZerosLeftOver = numZeros;
numOnesLeftOver = numOnes;
}
if (numZerosLeftOver > 0) {
--numZerosLeftOver;
return bgColor;
} else {
--numOnesLeftOver;
return fgColor;
}
};
for (std::uint32_t dy = 0; dy < boundingBox.size.height; ++dy) {
std::int32_t y = dy + boundingBox.topLeft.y;
for (std::uint32_t dx = 0; dx < boundingBox.size.width; ++dx) {
std::int32_t x = dx + boundingBox.topLeft.x;
auto color = colorIter().value_or(bgColor);
display.setPixel(x, y, color);
}
}
return boundingBox;
}
template
std::expected renderTransparent(Point position, DrawTarget& display, typename DrawTarget::Color fgColor) {
auto boundingBox = getBoundingBox(position);
auto colorIter = [&] () {
auto numZeros = m_glyph.readRunLength0();
auto numOnes = m_glyph.readRunLength1();
auto numZerosLeftOver = numZeros;
auto numOnesLeftOver = numOnes;
return [&] () -> std::optional {
if (numZerosLeftOver == 0 || numOnesLeftOver == 0) {
bool repeat = m_glyph.readUnsigned(1) != 0;
if (!repeat) {
numZeros = m_glyph.readRunLength0();
numOnes = m_glyph.readRunLength1();
}
numZerosLeftOver = numZeros;
numOnesLeftOver = numOnes;
}
if (numZerosLeftOver > 0) {
--numZerosLeftOver;
return std::nullopt;
} else {
--numOnesLeftOver;
return fgColor;
}
};
}();
for (std::uint32_t dy = 0; dy < boundingBox.size.height; ++dy) {
std::int32_t y = dy + boundingBox.topLeft.y;
for (std::uint32_t dx = 0; dx < boundingBox.size.width; ++dx) {
std::int32_t x = dx + boundingBox.topLeft.x;
if (auto color = colorIter()) {
display.setPixel(x, y, *color);
}
}
}
return boundingBox;
}
private:
GlyphReader m_glyph;
};
struct RenderedDimensions {
Point advance;
std::optional boundingBox;
};
class FontRenderer {
public:
explicit FontRenderer(std::span fontData);
FontRenderer withIgnoreUnknownChars(bool ignore) && {
FontRenderer result{std::move(m_font).withIgnoreUnknownGlyphs(ignore)};
return result;
}
FontRenderer withLineHeight(std::uint32_t lineHeight) && {
FontRenderer result{std::move(m_font).withLineHeight(lineHeight)};
return result;
}
std::uint32_t lineHeight() const noexcept {
return m_font.lineHeight();
}
template
std::expected render(std::string_view content, Point position, Display& display, typename Display::Color fontColor, std::optional bgColor) const {
Point advance{0, 0};
std::optional boundingBox;
position.y += 0; // Top alignment
for (std::uint32_t cp : UTF8Iterator{content}) {
if (cp == ~std::uint32_t{0}) {
if (!m_font.doesIgnoreUnknownGlyphs()) {
return std::unexpected(LookupError::InvalidUnicodeSequence);
}
continue;
}
// FIXME: newline support
if (cp == '\n') {
cp = ' ';
}
auto dims = renderGlyph(cp, position + advance, m_font, display, fontColor, bgColor);
if (!dims) {
return std::unexpected(dims.error());
}
advance += dims->advance;
boundingBox = combineBoundingBoxes(boundingBox, dims->boundingBox);
}
return RenderedDimensions{advance, boundingBox};
}
std::expected getRenderedDimensions(std::string_view content, Point position) const {
Point advance{0, 0};
std::optional boundingBox;
position.y += 0; // Top alignment
for (std::uint32_t cp : UTF8Iterator{content}) {
if (cp == ~std::uint32_t{0}) {
if (!m_font.doesIgnoreUnknownGlyphs()) {
return std::unexpected(LookupError::InvalidUnicodeSequence);
}
continue;
}
// FIXME: newline support
// also FIXME: call ensureAdvance when handling newline
if (cp == '\n') {
cp = ' ';
}
auto dims = computeGlyphDimensions(cp, position + advance, m_font);
if (!dims) {
return std::unexpected(dims.error());
}
advance += dims->advance;
boundingBox = combineBoundingBoxes(boundingBox, dims->boundingBox);
}
boundingBox = ensureAdvance(boundingBox, advance);
return RenderedDimensions{advance, boundingBox};
}
private:
explicit FontRenderer(FontReader&& font)
: m_font{std::move(font)}
{
}
static std::expected computeGlyphDimensions(std::uint32_t cp, Point position, FontReader const& font) {
auto glyph_ = font.tryRetrieveGlyphData(cp);
if (!glyph_) {
return std::unexpected(glyph_.error());
}
if (!*glyph_) {
return RenderedDimensions{};
}
auto glyph = **glyph_;
auto advance = glyph.advance();
auto size = glyph.size();
std::optional boundingBox;
if (size.width > 0 && size.height > 0) {
auto renderer = GlyphRenderer{glyph};
boundingBox = renderer.getBoundingBox(position);
}
return RenderedDimensions{Point{static_cast(advance), 0}, boundingBox};
}
static std::optional combineBoundingBoxes(std::optional a, std::optional b) {
if (!a && !b) {
return std::nullopt;
} else if (a && !b) {
return a;
} else if (b && !a) {
return b;
} else {
auto topLeftA = a->topLeft;
auto topLeftB = b->topLeft;
auto bottomRightA = a->getBottomRight();
auto bottomRightB = b->getBottomRight();
Point topLeft{std::min(topLeftA.x, topLeftB.x), std::min(topLeftA.y, topLeftB.y)};
Point bottomRight{std::max(bottomRightA.x, bottomRightB.x), std::max(bottomRightA.y, bottomRightB.y)};
return Rectangle::fromCorners(topLeft, bottomRight);
}
}
static std::optional ensureAdvance(std::optional a, Point advance) {
if (!a) {
return std::nullopt;
}
// FIXME: once we support multi-line also handle y component
auto bottomRight = a->getBottomRight();
if ((bottomRight.x + 1) < advance.x) {
bottomRight.x = advance.x - 1;
return Rectangle::fromCorners(a->topLeft, bottomRight);
} else {
return a;
}
}
template
static std::expected renderGlyph(std::uint32_t cp, Point position, FontReader const& font, Display& display, typename Display::Color fontColor, std::optional bgColor) {
auto glyph_ = font.tryRetrieveGlyphData(cp);
if (!glyph_) {
return std::unexpected(glyph_.error());
}
if (!*glyph_) {
return RenderedDimensions{};
}
auto glyph = **glyph_;
auto advance = glyph.advance();
auto size = glyph.size();
std::optional boundingBox;
if (size.width > 0 && size.height > 0) {
auto renderer = GlyphRenderer{glyph};
auto bbox = bgColor
? renderer.renderAsBoxFill(position, display, fontColor, *bgColor)
: renderer.renderTransparent(position, display, fontColor);
boundingBox = renderer.getBoundingBox(position);
}
return RenderedDimensions{Point{static_cast(advance), 0}, boundingBox};
}
FontReader m_font;
};
inline FontRenderer::FontRenderer(std::span fontData)
: m_font{fontData}
{
}
extern std::span findEmbeddedFont(std::size_t fontIndex);
} // namespace monoformat
#endif // MONOFORMAT_FONTREADER_HPP