feat(eink): scale size-1 font with display orientation

Portrait mode (122px wide) matches the OLED's ~21 chars/line with a 1× font.
Landscape mode (250px wide) was using the same small 6×8 font, leaving text
physically tiny and the wide display underutilised.

Size 1 now scales by orientation: 1× (6×8 / Lemon 5×10) in portrait,
2× (12×16 / Lemon 10×20) in landscape. Size 2 stays at 12×16 and size 3
at FreeSans18pt in both orientations — their layout Y-positions are hardcoded
so they cannot be scaled without a full layout refactor.

fontAscender() updated to accept a scale parameter; Lemon ascender becomes
8×scale (8px portrait, 16px landscape) to keep top-of-cell coordinate
semantics consistent with the rest of the UI code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-05-22 17:26:03 +02:00
co-authored by Claude Sonnet 4.6
parent 4c37986737
commit 18867a85c0
2 changed files with 27 additions and 12 deletions
+12 -6
View File
@@ -16,9 +16,9 @@
// GFX fonts use the baseline as the cursor origin. UI code assumes top-of-cell // GFX fonts use the baseline as the cursor origin. UI code assumes top-of-cell
// coordinates (same convention as the OLED driver). Add the font ascender so // coordinates (same convention as the OLED driver). Add the font ascender so
// the two conventions match. // the two conventions match.
static int fontAscender(int sz, bool use_lemon) { static int fontAscender(int sz, bool use_lemon, int scale) {
if (sz == 3) return 26; // FreeSans18pt7b: proportional font, baseline origin if (sz == 3) return 26; // FreeSans18pt7b: proportional, baseline origin
if (use_lemon) return 8; // Lemon GFX font: baseline origin, typical ascender 8px if (use_lemon) return 8 * scale; // Lemon GFX font: baseline origin, ascender 8px×scale
return 0; // GFX built-in font: cursor is top-left of cell return 0; // GFX built-in font: cursor is top-left of cell
} }
@@ -72,14 +72,19 @@ void GxEPDDisplay::startFrame(Color bkg) {
display.fillScreen(GxEPD_WHITE); display.fillScreen(GxEPD_WHITE);
display.setTextColor(_curr_color = GxEPD_BLACK); display.setTextColor(_curr_color = GxEPD_BLACK);
_text_sz = 1; _text_sz = 1;
int sc = (width() >= height()) ? 2 : 1;
display.setFont(_use_lemon ? &Lemon : NULL); display.setFont(_use_lemon ? &Lemon : NULL);
display.setTextSize(1); display.setTextSize(sc);
display_crc.reset(); display_crc.reset();
} }
void GxEPDDisplay::setTextSize(int sz) { void GxEPDDisplay::setTextSize(int sz) {
_text_sz = sz; _text_sz = sz;
display_crc.update<int>(sz); display_crc.update<int>(sz);
// Size 1 scales with orientation: 1× in portrait (≈OLED width), 2× in landscape.
// Size 2 always uses 2× built-in (12×16) — fixed because layout Y-positions are hardcoded.
// Size 3 always uses FreeSans18pt for large headings.
int sc = (width() >= height()) ? 2 : 1;
switch (sz) { switch (sz) {
case 3: case 3:
display.setFont(&FreeSans18pt7b); display.setFont(&FreeSans18pt7b);
@@ -91,7 +96,7 @@ void GxEPDDisplay::setTextSize(int sz) {
break; break;
default: default:
display.setFont(_use_lemon ? &Lemon : NULL); display.setFont(_use_lemon ? &Lemon : NULL);
display.setTextSize(1); display.setTextSize(sc);
break; break;
} }
} }
@@ -111,7 +116,8 @@ void GxEPDDisplay::setCursor(int x, int y) {
display_crc.update<int>(y); display_crc.update<int>(y);
// Offset y by the font ascender: callers pass top-of-cell y, GFX fonts // Offset y by the font ascender: callers pass top-of-cell y, GFX fonts
// expect baseline y. Without this, text would be clipped at the top. // expect baseline y. Without this, text would be clipped at the top.
display.setCursor(x, y + fontAscender(_text_sz, _use_lemon)); int sc = (width() >= height()) ? 2 : 1;
display.setCursor(x, y + fontAscender(_text_sz, _use_lemon, sc));
} }
void GxEPDDisplay::print(const char* str) { void GxEPDDisplay::print(const char* str) {
+14 -5
View File
@@ -68,11 +68,20 @@ public:
// 1 = FreeSans9pt (lineH=16, charW≈9) // 1 = FreeSans9pt (lineH=16, charW≈9)
// 2 = FreeSansBold12pt (lineH=20, charW≈12) // 2 = FreeSansBold12pt (lineH=20, charW≈12)
// 3 = FreeSans18pt (lineH=28, charW≈17) // 3 = FreeSans18pt (lineH=28, charW≈17)
// Size 1: GFX built-in 6×8 (default) or Lemon 5×10 bitmap font — matches OLED metrics. // Size 1 scales with orientation (portrait 1×, landscape 2×); size 2 is always 12×16;
// Size 2: GFX built-in scaled 12×16 bitmap font. // size 3 is FreeSans18pt (~17×28). Landscape = width >= height.
// Size 3: FreeSans18pt proportional font for large headings. int getCharWidth() const override {
int getCharWidth() const override { return _text_sz == 3 ? 17 : _text_sz == 2 ? 12 : (_use_lemon ? 5 : 6); } int sc = (width() >= height()) ? 2 : 1;
int getLineHeight() const override { return _text_sz == 3 ? 28 : _text_sz == 2 ? 16 : (_use_lemon ? 10 : 8); } if (_text_sz == 3) return 17;
if (_text_sz == 2) return 12;
return (_use_lemon ? 5 : 6) * sc;
}
int getLineHeight() const override {
int sc = (width() >= height()) ? 2 : 1;
if (_text_sz == 3) return 28;
if (_text_sz == 2) return 16;
return (_use_lemon ? 10 : 8) * sc;
}
void setLemonFont(bool enabled) override { _use_lemon = enabled; } void setLemonFont(bool enabled) override { _use_lemon = enabled; }
bool begin(); bool begin();