Add icon rendering and list scroll gesture support

Implement orientation-aware icon rendering using freeink::Icon format that applies rotation transforms per-pixel. Add wasListScroll helper for touch-based list navigation with swipe gestures. Switch vertical centering from x-height to cap-height for better visual alignment with capital-led UI labels.
This commit is contained in:
Justin Mitchell
2026-06-15 23:37:49 -04:00
parent e103cdfd11
commit 4a5b45409f
20 changed files with 339 additions and 96 deletions
+25 -7
View File
@@ -3,6 +3,7 @@
#include <BidiUtils.h>
#include <FontDecompressor.h>
#include <HalGPIO.h>
#include <Icon.h>
#include <Logging.h>
#include <SdCardFont.h>
#include <Utf8.h>
@@ -905,6 +906,22 @@ void GfxRenderer::drawIcon(const uint8_t bitmap[], const int x, const int y, con
display.drawImageTransparent(bitmap, y, getScreenWidth() - width - x, height, width);
}
void GfxRenderer::drawIcon(const freeink::Icon& icon, const int x, const int y, const bool black) const {
// Orientation-correct blit: the bits are stored un-rotated, so each black pixel
// goes through drawPixel (which applies the current orientation transform). The
// legacy uint8_t* overload above pre-rotates and is locked to one orientation;
// freeink::Icon assets are correct in every orientation.
const int rowBytes = (icon.w + 7) / 8;
for (int row = 0; row < icon.h; ++row) {
const uint8_t* r = icon.bits + static_cast<int>(row) * rowBytes;
for (int col = 0; col < icon.w; ++col) {
if (((r[col >> 3] >> (7 - (col & 7))) & 1) == 0) { // 0 = drawn
drawPixel(x + col, y + row, black);
}
}
}
}
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight,
const float cropX, const float cropY) const {
if (fontCacheManager_ && fontCacheManager_->isScanning()) return;
@@ -1572,14 +1589,15 @@ int GfxRenderer::getFontXHeight(const int fontId) const { return glyphTop(fontId
int GfxRenderer::getTextVisualCenterOffset(const int fontId) const {
// Distance from drawText's y (text top) down to the text's optical middle. The
// baseline is at y + ascender; the lowercase mass is centered at half the
// x-height above it, so the optical center is ascender - xHeight/2 below the top.
// x-height-based (UI text is lowercase-heavy); falls back to ascender*0.7 if the
// 'x' glyph is missing. Scales with the font, so no per-size tweaking.
// baseline is at y + ascender; the capitals reach capHeight above it, so the cap
// middle is ascender - capHeight/2 below the top. Cap-height (not x-height): UI
// labels are capital-led, so the eye centers on the caps — x-height sits a few px
// low. Falls back to ascender*0.65 if the 'H' glyph is missing. Scales with the
// font, so no per-size tweaking.
const int ascender = getFontAscenderSize(fontId);
const int xHeight = getFontXHeight(fontId);
if (xHeight <= 0) return (ascender * 7) / 10;
return ascender - xHeight / 2;
const int capHeight = getFontCapHeight(fontId);
if (capHeight <= 0) return (ascender * 65) / 100;
return ascender - capHeight / 2;
}
void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y, const char* text, const bool black,
+6
View File
@@ -13,6 +13,9 @@ enum class BidiBaseDir : signed char { AUTO = -1, LTR = 0, RTL = 1 };
class FontCacheManager;
class SdCardFont;
namespace freeink {
struct Icon;
}
#include <cstring>
#include <map>
@@ -102,6 +105,9 @@ class GfxRenderer {
// Setup
void begin(); // must be called right after display.begin()
void insertFont(int fontId, EpdFontFamily font);
// Orientation-correct icon blit for the freeink::Icon format (un-rotated bits +
// optical-center metadata). Prefer this over the legacy raw-bitmap drawIcon.
void drawIcon(const freeink::Icon& icon, int x, int y, bool black = true) const;
// UI chrome font scaling: firmware supplies a small remap table (from the board
// uiScale) that substitutes a larger font for each scaled UI font id at lookup
// time, so layout and drawing stay consistent with no call-site changes. Reader