feat(oled): misc-fixed 6x9 font on SSD1306 too, for GAT562 30S solo

The misc-fixed font (full Latin/Greek/Cyrillic) had only ever reached
SH1106Display and the e-ink driver. GAT562 30S -- like 24 other variants
-- uses SSD1306Display, which stayed on the built-in 5x7 font, so every
keyboard alphabet beyond ASCII and every accented contact name was
transliterated (Łódź -> Lodz) or drawn as a filled block.

Extract the renderer into MiscFixedRenderer.h rather than copying it:
Adafruit_SH110X and Adafruit_SSD1306 both derive from Adafruit_GFX and
glyph drawing is pure pixel plotting, so one implementation serves both
drivers (-79 lines from SH1106Display.cpp, same flash usage as before).
The header is .cpp-only by contract -- the font tables are static const,
so including it from a driver header would land a copy in every
translation unit.

On SSD1306 the path is opt-in behind OLED_MISC_FIXED_FONT, set only in
the two GAT562 30S solo envs: the font costs ~14 KB of flash and the
other 24 variants' repeater/companion builds have no keyboard to type
those alphabets on. Without the flag that driver is unchanged, verified
by building GAT562_30S_Mesh_Kit_repeater.

Drive-by consistency fix from the extraction: a newline in print() now
advances the cursor by yAdvance * text_size instead of a single yAdvance,
which was half a row short at setTextSize(2). The e-ink driver always
scaled it. No effect at size 1, which is everywhere the UI prints '\n'.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-07-29 22:48:06 +02:00
co-authored by Claude Opus 5
parent de16dbe32a
commit f8b9e1acb8
7 changed files with 160 additions and 76 deletions
+4 -75
View File
@@ -1,8 +1,7 @@
#include "SH1106Display.h"
#include <Adafruit_GrayOLED.h>
#include "Adafruit_SH110X.h"
#include "MiscFixedFont.h"
#include "LemonIcons.h"
#include "MiscFixedRenderer.h"
bool SH1106Display::i2c_probe(TwoWire &wire, uint8_t addr)
{
@@ -67,64 +66,8 @@ void SH1106Display::setCursor(int x, int y)
display.setCursor(x, y);
}
int16_t SH1106Display::drawGlyph(int16_t x, int16_t y, uint32_t cp) {
int sz = _text_sz;
for (uint8_t i = 0; i < lemonIconCount; i++) {
if (pgm_read_dword(&lemonIconCPs[i]) == cp) {
const GFXglyph* g = &lemonIconGlyphs[i];
uint8_t w = pgm_read_byte(&g->width), h = pgm_read_byte(&g->height);
int8_t xo = (int8_t)pgm_read_byte(&g->xOffset), yo = (int8_t)pgm_read_byte(&g->yOffset);
uint8_t xa = pgm_read_byte(&g->xAdvance);
uint16_t bo = pgm_read_word(&g->bitmapOffset);
uint8_t bits = 0, bit = 0;
for (uint8_t row = 0; row < h; row++)
for (uint8_t col = 0; col < w; col++) {
if (!bit) { bits = pgm_read_byte(&lemonIconBitmaps[bo++]); bit = 0x80; }
if (bits & bit) {
if (sz == 1) display.drawPixel(x + xo + col, y + 6 + yo + row, _color);
else display.fillRect(x + xo*sz + col*sz, y + 6*sz + yo*sz + row*sz, sz, sz, _color);
}
bit >>= 1;
}
return x + xa * sz;
}
}
// Font glyphs come from misc-fixed 6x9 (full Latin/Greek/Cyrillic, ascent 7) —
// baseline +7. The custom UI icons above keep +6, so they sit 1px higher.
if (cp < MiscFixed.first || cp > MiscFixed.last) {
// y here is the top of the current text row, i.e. already baseline minus
// the font's 7px ascent (see real-glyph rendering just below: y + 7 + yo +
// row) -- unlike GxEPDDisplay's drawGlyph, where y IS the baseline and
// the box sits at y - 7*sc for the same reason. Drawing this box at plain
// `y` (not y - 7*sz) lands it in the same ascent-top position, within its
// own row instead of bleeding into the row above.
if (cp >= 0x20) display.fillRect(x + sz, y, 4*sz, 6*sz, _color);
return x + 6 * sz;
}
const GFXglyph* g = &MiscFixedGlyphs[cp - MiscFixed.first];
uint8_t w = pgm_read_byte(&g->width), h = pgm_read_byte(&g->height);
int8_t xo = (int8_t)pgm_read_byte(&g->xOffset), yo = (int8_t)pgm_read_byte(&g->yOffset);
uint8_t xa = pgm_read_byte(&g->xAdvance);
uint16_t bo = pgm_read_word(&g->bitmapOffset);
uint8_t bits = 0, bit = 0;
for (uint8_t row = 0; row < h; row++)
for (uint8_t col = 0; col < w; col++) {
if (!bit) { bits = pgm_read_byte(&MiscFixedBitmaps[bo++]); bit = 0x80; }
if (bits & bit) {
if (sz == 1) display.drawPixel(x + xo + col, y + 7 + yo + row, _color);
else display.fillRect(x + xo*sz + col*sz, y + 7*sz + yo*sz + row*sz, sz, sz, _color);
}
bit >>= 1;
}
return x + xa * sz;
}
uint8_t SH1106Display::glyphXAdvance(uint32_t cp) {
uint8_t xa;
if (cp < MiscFixed.first || cp > MiscFixed.last) xa = 6;
else xa = pgm_read_byte(&MiscFixedGlyphs[cp - MiscFixed.first].xAdvance);
return xa * _text_sz;
return miscFixedXAdvance(cp, _text_sz);
}
void SH1106Display::translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) {
@@ -141,16 +84,7 @@ void SH1106Display::translateUTF8ToBlocks(char* dest, const char* src, size_t de
void SH1106Display::print(const char *str)
{
if (!_single_font) { display.print(str); return; }
int16_t cx = display.getCursorX();
int16_t cy = display.getCursorY();
const uint8_t* p = (const uint8_t*)str;
while (*p) {
uint32_t cp = decodeCodepoint(p);
if (cp == '\n') { cy += MiscFixed.yAdvance; cx = 0; }
else { cx = drawGlyph(cx, cy, cp); }
}
display.setCursor(cx, cy);
miscFixedPrint(display, str, _text_sz, _color);
}
void SH1106Display::fillRect(int x, int y, int w, int h)
@@ -170,12 +104,7 @@ void SH1106Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h)
uint16_t SH1106Display::getTextWidth(const char *str)
{
if (_single_font) {
uint16_t width = 0;
const uint8_t* p = (const uint8_t*)str;
while (*p) width += glyphXAdvance(decodeCodepoint(p));
return width;
}
if (_single_font) return miscFixedTextWidth(str, _text_sz);
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);