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