Files
trail-mate/modules/ui_mono/src/text_renderer.cpp
T
liu weikai aab37e7436 Add T-Echo Lite nRF52 target
Integrate the T-Echo Lite board, e-paper driver path, physical keypad input, mono UI refactor, and initial 192x176 layout adaptations.
2026-06-12 19:05:22 +08:00

430 lines
10 KiB
C++

#include "ui/mono/text_renderer.h"
#include "ui/mono/runtime.h"
#include <algorithm>
#include <cstring>
namespace ui::mono
{
namespace
{
constexpr uint32_t kReplacementCodepoint = 0xFFFD;
struct GlyphView
{
constexpr GlyphView(const uint8_t* bitmap_in = nullptr,
uint8_t width_in = 0,
uint8_t height_in = 0,
int8_t x_offset_in = 0,
int8_t y_offset_in = 0,
uint8_t advance_in = 0)
: bitmap(bitmap_in),
width(width_in),
height(height_in),
x_offset(x_offset_in),
y_offset(y_offset_in),
advance(advance_in)
{
}
const uint8_t* bitmap;
uint8_t width;
uint8_t height;
int8_t x_offset;
int8_t y_offset;
uint8_t advance;
};
uint8_t glyphRowBytes(uint8_t width)
{
return static_cast<uint8_t>((width + 7U) / 8U);
}
size_t utf8SequenceLength(unsigned char lead)
{
if ((lead & 0x80U) == 0)
{
return 1;
}
if ((lead & 0xE0U) == 0xC0U)
{
return 2;
}
if ((lead & 0xF0U) == 0xE0U)
{
return 3;
}
if ((lead & 0xF8U) == 0xF0U)
{
return 4;
}
return 1;
}
bool isContinuation(unsigned char ch)
{
return (ch & 0xC0U) == 0x80U;
}
uint32_t decodeUtf8(const char* text, size_t len, size_t& consumed)
{
consumed = 0;
if (!text || len == 0)
{
return 0;
}
const unsigned char lead = static_cast<unsigned char>(text[0]);
if ((lead & 0x80U) == 0)
{
consumed = 1;
return lead;
}
const size_t seq_len = utf8SequenceLength(lead);
if (seq_len == 1 || seq_len > len)
{
consumed = 1;
return kReplacementCodepoint;
}
uint32_t codepoint = 0;
if (seq_len == 2)
{
if (!isContinuation(static_cast<unsigned char>(text[1])))
{
consumed = 1;
return kReplacementCodepoint;
}
codepoint = static_cast<uint32_t>(lead & 0x1FU) << 6;
codepoint |= static_cast<uint32_t>(text[1] & 0x3FU);
}
else if (seq_len == 3)
{
if (!isContinuation(static_cast<unsigned char>(text[1])) || !isContinuation(static_cast<unsigned char>(text[2])))
{
consumed = 1;
return kReplacementCodepoint;
}
codepoint = static_cast<uint32_t>(lead & 0x0FU) << 12;
codepoint |= static_cast<uint32_t>(text[1] & 0x3FU) << 6;
codepoint |= static_cast<uint32_t>(text[2] & 0x3FU);
}
else
{
if (!isContinuation(static_cast<unsigned char>(text[1])) ||
!isContinuation(static_cast<unsigned char>(text[2])) ||
!isContinuation(static_cast<unsigned char>(text[3])))
{
consumed = 1;
return kReplacementCodepoint;
}
codepoint = static_cast<uint32_t>(lead & 0x07U) << 18;
codepoint |= static_cast<uint32_t>(text[1] & 0x3FU) << 12;
codepoint |= static_cast<uint32_t>(text[2] & 0x3FU) << 6;
codepoint |= static_cast<uint32_t>(text[3] & 0x3FU);
}
consumed = seq_len;
return codepoint;
}
uint16_t lookupGlyphIndex(const MonoFont& font, uint32_t codepoint)
{
if (!font.bitmap || font.glyph_count == 0)
{
return font.glyph_count;
}
const bool is_compact16 = (font.codepoints16 != nullptr) && (font.glyphs == nullptr);
if (is_compact16)
{
if (!font.codepoints16 || codepoint > 0xFFFFU)
{
return font.fallback_glyph_index < font.glyph_count ? font.fallback_glyph_index : font.glyph_count;
}
const uint16_t wanted = static_cast<uint16_t>(codepoint);
const uint16_t* begin = font.codepoints16;
const uint16_t* end = font.codepoints16 + font.glyph_count;
const uint16_t* it = std::lower_bound(begin, end, wanted);
if (it != end && *it == wanted)
{
return static_cast<uint16_t>(it - begin);
}
}
else
{
if (!font.glyphs || !font.codepoints32)
{
return font.glyph_count;
}
const uint32_t* begin = font.codepoints32;
const uint32_t* end = font.codepoints32 + font.glyph_count;
const uint32_t* it = std::lower_bound(begin, end, codepoint);
if (it != end && *it == codepoint)
{
return static_cast<uint16_t>(it - begin);
}
}
return font.fallback_glyph_index < font.glyph_count ? font.fallback_glyph_index : font.glyph_count;
}
GlyphView resolveGlyph(const MonoFont& font, uint16_t glyph_index)
{
if (!font.bitmap || glyph_index >= font.glyph_count)
{
return {};
}
const bool is_compact16 = (font.codepoints16 != nullptr) && (font.glyphs == nullptr);
if (is_compact16)
{
const uint32_t offset = static_cast<uint32_t>(glyph_index) *
static_cast<uint32_t>(font.glyph_height) *
static_cast<uint32_t>(glyphRowBytes(font.glyph_width));
const uint8_t advance = font.advances ? font.advances[glyph_index] : font.fixed_advance;
return GlyphView{
font.bitmap + offset,
font.glyph_width,
font.glyph_height,
0,
0,
advance,
};
}
if (!font.glyphs)
{
return {};
}
const MonoGlyph& glyph = font.glyphs[glyph_index];
return GlyphView{
font.bitmap + glyph.bitmap_offset,
glyph.width,
glyph.height,
glyph.x_offset,
glyph.y_offset,
glyph.advance,
};
}
void drawGlyphBitmap(MonoDisplay& display, const MonoFont& font, int x, int y, const GlyphView& glyph, bool inverse)
{
if (!glyph.bitmap)
{
return;
}
if (inverse)
{
display.fillRect(x, y, glyph.advance, font.line_height, true);
}
for (int row = 0; row < glyph.height; ++row)
{
const uint8_t row_bytes = glyphRowBytes(glyph.width);
const uint8_t* row_ptr = glyph.bitmap + static_cast<size_t>(row) * row_bytes;
for (int col = 0; col < glyph.width; ++col)
{
const uint8_t byte_value = row_ptr[col / 8];
const bool on = ((byte_value >> (7 - (col % 8))) & 0x01U) != 0;
if (!on)
{
continue;
}
display.drawPixel(x + glyph.x_offset + col, y + glyph.y_offset + row, !inverse);
}
}
}
} // namespace
const MonoFont& builtin_ui_font()
{
static const uint8_t kBitmap[] = {
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x40,
0x40,
0x40,
0x40,
0x00,
0x40,
0x00,
0x00,
0xA0,
0xA0,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0xA0,
0xE0,
0xA0,
0xA0,
0xE0,
0xA0,
0x00,
0x00,
0x40,
0xE0,
0xC0,
0x60,
0x60,
0xC0,
0x40,
0x00,
0x00,
0xA0,
0x20,
0x40,
0x80,
0xA0,
0x00,
0x00,
0x40,
0xA0,
0x40,
0xA0,
0xA0,
0x60,
0x00,
0x00,
0x40,
0x40,
0x00,
0x00,
0x00,
0x00,
0x00,
};
static const MonoGlyph kGlyphs[] = {
{0, 8, 8, 0, -1, 4},
{8, 8, 8, 0, -1, 4},
{16, 8, 8, 0, -1, 4},
{24, 8, 8, 0, -1, 4},
{32, 8, 8, 0, -1, 4},
{40, 8, 8, 0, -1, 4},
{48, 8, 8, 0, -1, 4},
{56, 8, 8, 0, -1, 4},
};
static const uint32_t kCodepoints[] = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27};
static const MonoFont kFont = MonoFont::makeLegacy(
kBitmap,
kGlyphs,
kCodepoints,
static_cast<uint16_t>(sizeof(kCodepoints) / sizeof(kCodepoints[0])),
8,
7,
4,
0);
return kFont;
}
TextRenderer::TextRenderer(const MonoFont& font)
: font_(font)
{
}
int TextRenderer::lineHeight() const
{
return font_.line_height;
}
int TextRenderer::measureTextWidth(const char* utf8) const
{
if (!utf8)
{
return 0;
}
int width = 0;
const size_t len = std::strlen(utf8);
for (size_t i = 0; i < len;)
{
size_t consumed = 0;
const uint32_t cp = decodeUtf8(utf8 + i, len - i, consumed);
const uint16_t glyph_index = lookupGlyphIndex(font_, cp);
const GlyphView glyph = resolveGlyph(font_, glyph_index);
width += glyph.bitmap ? glyph.advance : font_.default_advance;
i += consumed ? consumed : 1;
}
return width;
}
size_t TextRenderer::clipTextToWidth(const char* utf8, int max_width) const
{
if (!utf8 || max_width <= 0)
{
return 0;
}
int width = 0;
size_t bytes = 0;
const size_t len = std::strlen(utf8);
for (size_t i = 0; i < len;)
{
size_t consumed = 0;
const uint32_t cp = decodeUtf8(utf8 + i, len - i, consumed);
const uint16_t glyph_index = lookupGlyphIndex(font_, cp);
const GlyphView glyph = resolveGlyph(font_, glyph_index);
const int advance = glyph.bitmap ? glyph.advance : font_.default_advance;
if (width + advance > max_width)
{
break;
}
width += advance;
bytes += consumed ? consumed : 1;
i += consumed ? consumed : 1;
}
return bytes;
}
int TextRenderer::ellipsisWidth() const
{
return measureTextWidth("...");
}
void TextRenderer::drawText(MonoDisplay& display, int x, int y, const char* utf8, bool inverse) const
{
if (!utf8)
{
return;
}
const size_t len = std::strlen(utf8);
int cursor_x = x;
for (size_t i = 0; i < len;)
{
size_t consumed = 0;
const uint32_t cp = decodeUtf8(utf8 + i, len - i, consumed);
const uint16_t glyph_index = lookupGlyphIndex(font_, cp);
const GlyphView glyph = resolveGlyph(font_, glyph_index);
if (glyph.bitmap)
{
drawGlyphBitmap(display, font_, cursor_x, y, glyph, inverse);
cursor_x += glyph.advance;
}
else
{
cursor_x += font_.default_advance;
}
i += consumed ? consumed : 1;
}
}
} // namespace ui::mono