You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
678 lines
22 KiB
678 lines
22 KiB
/* This is a port of <https://github.com/Finomnis/u8g2-fonts/blob/main/src/font_reader/> 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 <cassert>
|
|
#include <cstdint>
|
|
#include <cstddef>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <string_view>
|
|
#include <limits>
|
|
|
|
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<std::byte const> data)
|
|
: m_data{data}
|
|
{
|
|
}
|
|
|
|
std::optional<std::size_t> calculateJumpOffset(std::uint16_t encoding) {
|
|
std::size_t jumpOffset = 0;
|
|
|
|
std::optional<UnicodeJumptableEntry> entry;
|
|
do {
|
|
entry = nextEntry();
|
|
if (!entry) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
jumpOffset += entry->jumpDistance;
|
|
} while (entry->characterUpperLimit < encoding);
|
|
|
|
return jumpOffset;
|
|
}
|
|
|
|
std::optional<UnicodeJumptableEntry> nextEntry() {
|
|
auto jumpDistance_ = readU16BE(m_data);
|
|
auto characterUpperLimit_ = readU16BE(m_data);
|
|
if (!jumpDistance_ || !characterUpperLimit_) {
|
|
return std::nullopt;
|
|
}
|
|
return UnicodeJumptableEntry{*jumpDistance_, *characterUpperLimit_};
|
|
}
|
|
|
|
private:
|
|
std::span<std::byte const> m_data;
|
|
};
|
|
|
|
template<std::size_t CharWidth>
|
|
class GlyphSearcher;
|
|
|
|
class FontReader {
|
|
public:
|
|
explicit FontReader(std::span<std::byte const> 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<std::optional<GlyphReader>, LookupError> tryRetrieveGlyphData(std::uint32_t ch) const;
|
|
std::expected<GlyphReader, LookupError> retrieveGlyphData(std::uint32_t ch) const;
|
|
|
|
private:
|
|
friend class GlyphReader;
|
|
template<std::size_t CharWidth>
|
|
friend class GlyphSearcher;
|
|
|
|
std::span<std::byte const> 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<std::byte const> 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<std::uint8_t>(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::uint8_t>((std::uint16_t{1} << bits) - 1);
|
|
}
|
|
|
|
std::int8_t readSigned(std::uint8_t bits) {
|
|
return static_cast<std::int8_t>(readUnsigned(bits) - static_cast<std::uint8_t>(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<std::byte const> 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<std::size_t CharWidth>
|
|
class GlyphSearcherBase {
|
|
public:
|
|
void jumpBy(std::size_t offset) {
|
|
m_data = m_data.subspan(offset);
|
|
}
|
|
|
|
std::uint8_t getOffset() const {
|
|
return static_cast<std::uint8_t>(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<std::byte const> data, FontReader const& font)
|
|
: m_data{data}
|
|
, m_font{font}
|
|
{
|
|
}
|
|
|
|
std::span<std::byte const> 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<std::byte const> data, FontReader const& font)
|
|
: GlyphSearcherBase<2>{data, font}
|
|
{
|
|
}
|
|
|
|
template<std::size_t CharWidth>
|
|
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<std::uint8_t>(m_data[0]);
|
|
}
|
|
|
|
std::pair<GlyphSearcher<2>, UnicodeJumptableReader> intoUnicodeMode(std::uint16_t offset) && {
|
|
jumpBy(offset);
|
|
return {GlyphSearcher<2>{m_data, m_font}, UnicodeJumptableReader{m_data}};
|
|
}
|
|
};
|
|
|
|
inline FontReader::FontReader(std::span<std::byte const> data)
|
|
: m_data{data}
|
|
, m_glyphCount{static_cast<std::uint8_t>(data[0])}
|
|
, m_supportsBackgroundColor{data[1] != std::byte{}}
|
|
, m_m0{static_cast<std::uint8_t>(data[2])}
|
|
, m_m1{static_cast<std::uint8_t>(data[3])}
|
|
, m_bitCountW{static_cast<std::uint8_t>(data[4])}
|
|
, m_bitCountH{static_cast<std::uint8_t>(data[5])}
|
|
, m_bitCountX{static_cast<std::uint8_t>(data[6])}
|
|
, m_bitCountY{static_cast<std::uint8_t>(data[7])}
|
|
, m_bitCountD{static_cast<std::uint8_t>(data[8])}
|
|
, m_fontBoundingBoxWidth{static_cast<std::int8_t>(data[9])}
|
|
, m_fontBoundingBoxHeight{static_cast<std::int8_t>(data[10])}
|
|
, m_fontBoundingBoxXOffset{static_cast<std::int8_t>(data[11])}
|
|
, m_fontBoundingBoxYOffset{static_cast<std::int8_t>(data[12])}
|
|
, m_ascent{static_cast<std::int8_t>(data[13])}
|
|
, m_descent{static_cast<std::int8_t>(data[14])}
|
|
, m_ascentOfParentheses{static_cast<std::int8_t>(data[15])}
|
|
, m_descentOfParentheses{static_cast<std::int8_t>(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<std::uint8_t>(m_fontBoundingBoxHeight) + 1;
|
|
}
|
|
|
|
inline std::expected<std::optional<GlyphReader>, 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<GlyphReader, LookupError> FontReader::retrieveGlyphData(std::uint32_t ch) const {
|
|
if (ch > std::numeric_limits<std::uint16_t>::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<typename DrawTarget>
|
|
std::expected<Rectangle, RenderError> 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<typename DrawTarget::Color> {
|
|
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<typename DrawTarget>
|
|
std::expected<Rectangle, RenderError> 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<typename DrawTarget::Color> {
|
|
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<Rectangle> boundingBox;
|
|
};
|
|
|
|
class FontRenderer {
|
|
public:
|
|
explicit FontRenderer(std::span<std::byte const> 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<typename Display>
|
|
std::expected<RenderedDimensions, LookupError> render(std::string_view content, Point position, Display& display, typename Display::Color fontColor, std::optional<typename Display::Color> bgColor) const {
|
|
Point advance{0, 0};
|
|
std::optional<Rectangle> 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<RenderedDimensions, LookupError> getRenderedDimensions(std::string_view content, Point position) const {
|
|
Point advance{0, 0};
|
|
std::optional<Rectangle> 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<RenderedDimensions, LookupError> 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<Rectangle> boundingBox;
|
|
if (size.width > 0 && size.height > 0) {
|
|
auto renderer = GlyphRenderer{glyph};
|
|
boundingBox = renderer.getBoundingBox(position);
|
|
}
|
|
|
|
return RenderedDimensions{Point{static_cast<std::int32_t>(advance), 0}, boundingBox};
|
|
}
|
|
|
|
static std::optional<Rectangle> combineBoundingBoxes(std::optional<Rectangle> a, std::optional<Rectangle> 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<Rectangle> ensureAdvance(std::optional<Rectangle> 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<typename Display>
|
|
static std::expected<RenderedDimensions, LookupError> renderGlyph(std::uint32_t cp, Point position, FontReader const& font, Display& display, typename Display::Color fontColor, std::optional<typename Display::Color> 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<Rectangle> 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<std::int32_t>(advance), 0}, boundingBox};
|
|
}
|
|
|
|
FontReader m_font;
|
|
};
|
|
|
|
inline FontRenderer::FontRenderer(std::span<std::byte const> fontData)
|
|
: m_font{fontData}
|
|
{
|
|
}
|
|
|
|
extern std::span<std::byte const> findEmbeddedFont(std::size_t fontIndex);
|
|
|
|
} // namespace monoformat
|
|
|
|
#endif // MONOFORMAT_FONTREADER_HPP
|
|
|