feat: add Lemon font with UTF-8 rendering for SH1106 display

- Add LemonFont.h: Adafruit GFX bitmap font converted from lemon.bdf,
  covering U+0020–U+017F (ASCII + Latin Extended A/B, 319/352 glyphs)
- SH1106Display: custom UTF-8 renderer for size 1 text using Lemon font;
  size >1 falls back to default built-in font
- translateUTF8ToBlocks() overridden to pass-through (no block substitution)
- No RAM impact: font bitmap stored in PROGMEM (Flash)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-05-16 11:52:14 +02:00
parent 2978b0ffc4
commit b8ac4245cf
3 changed files with 542 additions and 7 deletions

444
src/helpers/ui/LemonFont.h Normal file
View File

@@ -0,0 +1,444 @@
// Lemon font, converted from lemon.bdf
// Range: U+0020U+017F (352 glyphs)
// Generated by bdf2adafruit.py
#pragma once
#include <Adafruit_GFX.h>
static const uint8_t lemonBitmaps[] PROGMEM = {
0xF4, 0xB6, 0x80, 0x56, 0xD5, 0xB5, 0x00, 0x27, 0x86, 0x1E, 0x20, 0xCD, 0x24, 0xB3, 0x45, 0x11,
0x59, 0x34, 0xE0, 0x2A, 0x49, 0x11, 0x88, 0x92, 0x54, 0x96, 0xF6, 0x90, 0x21, 0x3E, 0x42, 0x00,
0x58, 0xF0, 0xC0, 0x11, 0x22, 0x44, 0x88, 0x69, 0xDB, 0x96, 0x75, 0x50, 0xE1, 0x16, 0x8F, 0xE1,
0x61, 0x1E, 0x99, 0x97, 0x11, 0xF8, 0xE1, 0x1E, 0x68, 0xE9, 0x96, 0xF1, 0x22, 0x44, 0x69, 0x69,
0x96, 0x69, 0x97, 0x16, 0xD8, 0x51, 0x60, 0x2A, 0x22, 0xE3, 0x80, 0x88, 0xA8, 0xF4, 0xA0, 0x80,
0x69, 0xBB, 0x87, 0x69, 0x9F, 0x99, 0xE9, 0xE9, 0x9E, 0x78, 0x88, 0x87, 0xE9, 0x99, 0x9E, 0xF8,
0xE8, 0x8F, 0xF8, 0xE8, 0x88, 0x78, 0x8B, 0x97, 0x99, 0xF9, 0x99, 0xE9, 0x25, 0xC0, 0x71, 0x11,
0x96, 0x9A, 0xCA, 0x99, 0x88, 0x88, 0x8F, 0x9F, 0xF9, 0x99, 0x9D, 0xDB, 0xB9, 0x69, 0x99, 0x96,
0xE9, 0x9E, 0x88, 0x69, 0x99, 0x96, 0x30, 0xE9, 0x9E, 0x99, 0x78, 0x61, 0x1E, 0xF9, 0x08, 0x42,
0x10, 0x99, 0x99, 0x97, 0x99, 0x96, 0x66, 0x99, 0x9F, 0xF9, 0x99, 0x69, 0x99, 0x8A, 0x88, 0x42,
0x10, 0xF1, 0x24, 0x8F, 0xF2, 0x49, 0x27, 0x88, 0x44, 0x22, 0x11, 0xE4, 0x92, 0x4F, 0x22, 0xA2,
0xF0, 0xA4, 0x79, 0x9B, 0x50, 0x88, 0xE9, 0x99, 0xE0, 0x78, 0x88, 0x70, 0x11, 0x79, 0x9B, 0x50,
0x69, 0xF8, 0x70, 0x73, 0xC9, 0x20, 0x79, 0x9B, 0x51, 0x60, 0x88, 0xE9, 0x99, 0x90, 0x4D, 0x54,
0x21, 0x92, 0x49, 0xC0, 0x88, 0x9A, 0xE9, 0x90, 0xD5, 0x54, 0x9F, 0xB9, 0x90, 0xE9, 0x99, 0x90,
0x69, 0x99, 0x60, 0xE9, 0x99, 0xE8, 0x80, 0x79, 0x99, 0x71, 0x10, 0xBC, 0x88, 0x80, 0x78, 0x61,
0xE0, 0x5D, 0x24, 0x40, 0x99, 0x99, 0x70, 0x99, 0x96, 0x60, 0x99, 0x9F, 0x90, 0x99, 0x69, 0x90,
0x99, 0x99, 0x71, 0x60, 0xF2, 0x48, 0xF0, 0x29, 0x6C, 0x91, 0xFF, 0x89, 0x36, 0x94, 0x45, 0x6A,
0x20, 0x6F, 0xF6, 0xBC, 0x17, 0xAA, 0xA7, 0x40, 0x32, 0x11, 0xC4, 0x27, 0xC0, 0x8B, 0x94, 0xA7,
0x44, 0x8A, 0x88, 0xE2, 0x38, 0x80, 0xEE, 0x34, 0x69, 0x96, 0x2C, 0xA0, 0x7A, 0x1B, 0x69, 0xB6,
0x17, 0x80, 0x79, 0x97, 0x5A, 0x50, 0xF8, 0x42, 0xE0, 0x7A, 0x1B, 0xED, 0xAE, 0x17, 0x80, 0xE0,
0x55, 0x00, 0x27, 0x20, 0x70, 0xC5, 0x4E, 0xC7, 0x1C, 0x60, 0x99, 0x99, 0xE8, 0x7D, 0x6A, 0xD2,
0x94, 0xF0, 0x47, 0x80, 0x75, 0x69, 0x96, 0xA2, 0xA8, 0x46, 0x10, 0xA3, 0x29, 0xE2, 0x46, 0x10,
0xE0, 0x88, 0xE0, 0xC0, 0x84, 0x0A, 0xD8, 0xA3, 0xC2, 0x41, 0x4B, 0xC0, 0x42, 0x69, 0x9F, 0x99,
0x24, 0x69, 0x9F, 0x99, 0x25, 0x06, 0x99, 0xF9, 0x90, 0x5A, 0x69, 0x9F, 0x99, 0x48, 0x0C, 0x94,
0xBD, 0x29, 0x69, 0x69, 0x9F, 0x99, 0x7E, 0x49, 0xBC, 0x92, 0x70, 0x78, 0x88, 0x87, 0x24, 0x42,
0xF8, 0xE8, 0x8F, 0x24, 0xF8, 0xE8, 0x8F, 0x4A, 0xF8, 0xE8, 0x8F, 0xA0, 0xF8, 0xE8, 0x8F, 0x88,
0x74, 0x92, 0xE0, 0x28, 0x74, 0x92, 0xE0, 0x54, 0x74, 0x92, 0xE0, 0xA3, 0xA4, 0x97, 0x72, 0x7A,
0x94, 0xB8, 0x2A, 0x80, 0xE4, 0xA5, 0x29, 0x84, 0x06, 0x99, 0x96, 0x24, 0x06, 0x99, 0x96, 0x4A,
0x06, 0x99, 0x96, 0x5A, 0x06, 0x99, 0x96, 0x48, 0x0C, 0x94, 0xA4, 0xC0, 0xAA, 0x80, 0x35, 0x65,
0x9A, 0x6A, 0xC0, 0x84, 0x99, 0x99, 0x97, 0x12, 0x99, 0x99, 0x97, 0x25, 0x09, 0x99, 0x99, 0x70,
0x50, 0x99, 0x99, 0x97, 0x11, 0x22, 0xA2, 0x10, 0x84, 0x88, 0xE9, 0x9E, 0x80, 0x69, 0xA9, 0x9A,
0x80, 0x84, 0x07, 0x99, 0xB5, 0x24, 0x07, 0x99, 0xB5, 0x4A, 0x07, 0x99, 0xB5, 0x5A, 0x07, 0x99,
0xB5, 0x90, 0x79, 0x9B, 0x50, 0x20, 0x79, 0x9B, 0x50, 0x75, 0x6F, 0x45, 0x80, 0x78, 0x88, 0x72,
0x40, 0x84, 0x06, 0x9F, 0x87, 0x24, 0x06, 0x9F, 0x87, 0x4A, 0x06, 0x9F, 0x87, 0xA0, 0x69, 0xF8,
0x70, 0x93, 0x55, 0x28, 0x64, 0x92, 0x54, 0x64, 0x92, 0xA3, 0x24, 0x90, 0xA6, 0x97, 0x99, 0x96,
0x5A, 0x0E, 0x99, 0x99, 0x41, 0x00, 0x64, 0xA5, 0x26, 0x12, 0x06, 0x99, 0x96, 0x4A, 0x06, 0x99,
0x96, 0x5A, 0x06, 0x99, 0x96, 0x90, 0x69, 0x99, 0x60, 0x20, 0x3E, 0x02, 0x00, 0x35, 0x67, 0x9A,
0xB0, 0x84, 0x09, 0x99, 0x97, 0x11, 0x00, 0x94, 0xA5, 0x27, 0x4A, 0x09, 0x99, 0x97, 0x50, 0x12,
0x94, 0xA4, 0xE0, 0x24, 0x09, 0x99, 0x97, 0x16, 0x88, 0xE9, 0x99, 0xE8, 0x80, 0x50, 0x99, 0x99,
0x71, 0x60, 0xF0, 0x69, 0x9F, 0x99, 0xF0, 0x79, 0x9B, 0x50, 0x96, 0x06, 0x99, 0xF9, 0x90, 0x96,
0x07, 0x99, 0xB5, 0x64, 0xA5, 0xE9, 0x48, 0x20, 0x74, 0xA5, 0x65, 0x04, 0x24, 0x07, 0x88, 0x88,
0x70, 0x24, 0x07, 0x88, 0x87, 0x25, 0x07, 0x88, 0x88, 0x70, 0x25, 0x07, 0x88, 0x87, 0x20, 0x78,
0x88, 0x87, 0x20, 0x78, 0x88, 0x70, 0x52, 0x07, 0x88, 0x88, 0x70, 0x52, 0x07, 0x88, 0x87, 0xA4,
0x0E, 0x99, 0x99, 0xE0, 0x04, 0x51, 0x9C, 0x92, 0x4B, 0x14, 0x72, 0x7A, 0x94, 0xB8, 0x38, 0x9D,
0x29, 0x59, 0x40, 0x60, 0xF8, 0xE8, 0x8F, 0xF0, 0x69, 0xF8, 0x70, 0x96, 0x0F, 0x8E, 0x88, 0xF0,
0x96, 0x06, 0x9F, 0x87, 0x40, 0xF8, 0xE8, 0x8F, 0x40, 0x69, 0xF8, 0x70, 0xF4, 0x39, 0x08, 0x78,
0x20, 0x69, 0xF8, 0x61, 0xA4, 0x0F, 0x8E, 0x88, 0xF0, 0xA4, 0x06, 0x9F, 0x87, 0x25, 0x07, 0x88,
0xB9, 0x70, 0x25, 0x07, 0x99, 0xB5, 0x16, 0x96, 0x07, 0x88, 0xB9, 0x70, 0x96, 0x07, 0x99, 0xB5,
0x16, 0x20, 0x78, 0x8B, 0x97, 0x20, 0x79, 0x9B, 0x51, 0x60, 0x78, 0x8B, 0x97, 0x24, 0x24, 0x07,
0x99, 0xB5, 0x16, 0x25, 0x09, 0x9F, 0x99, 0x90, 0x25, 0x88, 0xE9, 0x99, 0x90, 0xFD, 0x27, 0x92,
0x49, 0x20, 0xF2, 0x1C, 0x94, 0xA5, 0x20, 0x5A, 0x07, 0x22, 0x22, 0x70, 0x5A, 0x06, 0x22, 0x22,
0xE3, 0xA4, 0x97, 0xE3, 0x24, 0x90, 0xA8, 0x74, 0x92, 0xE0, 0xA8, 0x64, 0x92, 0xE9, 0x25, 0xE2,
0x4D, 0x5E, 0x40, 0x43, 0xA4, 0x97, 0xD5, 0x40, 0xFD, 0x14, 0x51, 0x57, 0xA0, 0x48, 0x36, 0x94,
0xA5, 0x26, 0x25, 0x07, 0x11, 0x19, 0x60, 0x25, 0x06, 0x22, 0x22, 0x2C, 0x9A, 0xCA, 0x99, 0x48,
0x88, 0x9A, 0xE9, 0x94, 0x80, 0x9A, 0xE9, 0x90, 0x12, 0x88, 0x88, 0x8F, 0x12, 0xC4, 0x44, 0x44,
0x40, 0x88, 0x88, 0x8F, 0x24, 0x64, 0x92, 0x4A, 0x80, 0x39, 0x88, 0x88, 0xF0, 0x31, 0xC4, 0x44,
0x44, 0x42, 0x88, 0x8A, 0x8F, 0xC4, 0x44, 0x54, 0x40, 0x8A, 0xC8, 0x8F, 0xC9, 0x3C, 0x90, 0x24,
0x09, 0xDD, 0xBB, 0x90, 0x24, 0x0E, 0x99, 0x99, 0x9D, 0xDB, 0xBD, 0x48, 0x24, 0x0E, 0x99, 0x9D,
0x48, 0x52, 0x9D, 0xDB, 0xB9, 0x52, 0x0E, 0x99, 0x99, 0x88, 0x0E, 0x99, 0x99, 0x9D, 0xDB, 0xB9,
0x16, 0x24, 0x0E, 0x99, 0x99, 0x16, 0xF0, 0x69, 0x99, 0x96, 0xF0, 0x69, 0x99, 0x60, 0x96, 0x06,
0x99, 0x99, 0x60, 0x96, 0x06, 0x99, 0x96, 0x5A, 0x06, 0x99, 0x99, 0x60, 0x5A, 0x06, 0x99, 0x96,
0x7E, 0x49, 0xA4, 0x91, 0xF0, 0x75, 0x6F, 0x47, 0x80, 0x24, 0xE9, 0x9E, 0x99, 0x24, 0x0B, 0xC8,
0x88, 0xE9, 0x9E, 0x99, 0x48, 0xBC, 0x88, 0x84, 0x80, 0xA4, 0xE9, 0x9E, 0x99, 0xA4, 0x0B, 0xC8,
0x88, 0x24, 0x07, 0x86, 0x11, 0xE0, 0x24, 0x07, 0x86, 0x1E, 0x25, 0x07, 0x86, 0x11, 0xE0, 0x25,
0x07, 0x86, 0x1E, 0x78, 0x61, 0x1E, 0x22, 0x78, 0x61, 0xE2, 0x20, 0x52, 0x07, 0x86, 0x11, 0xE0,
0x52, 0x07, 0x86, 0x1E, 0xF9, 0x08, 0x42, 0x11, 0x00, 0x5D, 0x24, 0x50, 0x51, 0x01, 0xF2, 0x10,
0x84, 0x20, 0x31, 0x4E, 0x44, 0x42, 0xF9, 0x08, 0xE2, 0x10, 0x5D, 0x74, 0x40, 0x5A, 0x09, 0x99,
0x99, 0x70, 0x5A, 0x09, 0x99, 0x97, 0xF0, 0x99, 0x99, 0x97, 0xF0, 0x99, 0x99, 0x70, 0x96, 0x09,
0x99, 0x99, 0x70, 0x96, 0x09, 0x99, 0x97, 0x25, 0x29, 0x99, 0x99, 0x70, 0x25, 0x29, 0x99, 0x97,
0x4C, 0x81, 0x29, 0x4A, 0x52, 0x70, 0x4C, 0x81, 0x29, 0x4A, 0x4E, 0x99, 0x99, 0x97, 0x42, 0x99,
0x99, 0x74, 0x20, 0x25, 0x09, 0x99, 0x9F, 0x90, 0x25, 0x09, 0x99, 0xF9, 0x22, 0x81, 0x15, 0x10,
0x84, 0x20, 0x25, 0x09, 0x99, 0x97, 0x16, 0x50, 0x22, 0xA2, 0x10, 0x84, 0x24, 0x0F, 0x12, 0x48,
0xF0, 0x24, 0x0F, 0x24, 0x8F, 0x20, 0xF1, 0x24, 0x8F, 0x20, 0xF2, 0x48, 0xF0, 0x52, 0x0F, 0x12,
0x48, 0xF0, 0x52, 0x0F, 0x24, 0x8F, 0x34, 0xC4, 0x44, 0x40,
};
static const GFXglyph lemonGlyphs[] PROGMEM = {
{ 0, 0, 0, 5, 0, 0 }, // U+0020
{ 0, 1, 6, 5, 2, -6 }, // U+0021
{ 1, 3, 3, 5, 1, -7 }, // U+0022
{ 3, 5, 5, 5, 0, -5 }, // U+0023
{ 7, 4, 7, 5, 1, -7 }, // U+0024
{ 11, 4, 6, 5, 1, -6 }, // U+0025
{ 14, 5, 6, 5, 0, -6 }, // U+0026
{ 18, 1, 3, 5, 2, -7 }, // U+0027
{ 19, 3, 8, 5, 2, -7 }, // U+0028
{ 22, 3, 8, 5, 2, -7 }, // U+0029
{ 25, 4, 5, 5, 1, -5 }, // U+002A
{ 28, 5, 5, 5, 0, -6 }, // U+002B
{ 32, 2, 3, 5, 1, -2 }, // U+002C
{ 33, 4, 1, 5, 1, -4 }, // U+002D
{ 34, 1, 2, 5, 2, -2 }, // U+002E
{ 35, 4, 8, 5, 1, -7 }, // U+002F
{ 39, 4, 6, 5, 1, -6 }, // U+0030
{ 42, 2, 6, 5, 1, -6 }, // U+0031
{ 44, 4, 6, 5, 1, -6 }, // U+0032
{ 47, 4, 6, 5, 1, -6 }, // U+0033
{ 50, 4, 6, 5, 1, -6 }, // U+0034
{ 53, 4, 6, 5, 1, -6 }, // U+0035
{ 56, 4, 6, 5, 1, -6 }, // U+0036
{ 59, 4, 6, 5, 1, -6 }, // U+0037
{ 62, 4, 6, 5, 1, -6 }, // U+0038
{ 65, 4, 6, 5, 1, -6 }, // U+0039
{ 68, 1, 5, 5, 2, -5 }, // U+003A
{ 69, 2, 6, 5, 1, -5 }, // U+003B
{ 71, 3, 5, 5, 1, -5 }, // U+003C
{ 73, 3, 3, 5, 1, -5 }, // U+003D
{ 75, 3, 5, 5, 2, -5 }, // U+003E
{ 77, 3, 6, 5, 1, -6 }, // U+003F
{ 80, 4, 6, 5, 1, -6 }, // U+0040
{ 83, 4, 6, 5, 1, -6 }, // U+0041
{ 86, 4, 6, 5, 1, -6 }, // U+0042
{ 89, 4, 6, 5, 1, -6 }, // U+0043
{ 92, 4, 6, 5, 1, -6 }, // U+0044
{ 95, 4, 6, 5, 1, -6 }, // U+0045
{ 98, 4, 6, 5, 1, -6 }, // U+0046
{ 101, 4, 6, 5, 1, -6 }, // U+0047
{ 104, 4, 6, 5, 1, -6 }, // U+0048
{ 107, 3, 6, 5, 1, -6 }, // U+0049
{ 110, 4, 6, 5, 1, -6 }, // U+004A
{ 113, 4, 6, 5, 1, -6 }, // U+004B
{ 116, 4, 6, 5, 1, -6 }, // U+004C
{ 119, 4, 6, 5, 1, -6 }, // U+004D
{ 122, 4, 6, 5, 1, -6 }, // U+004E
{ 125, 4, 6, 5, 1, -6 }, // U+004F
{ 128, 4, 6, 5, 1, -6 }, // U+0050
{ 131, 4, 7, 5, 1, -6 }, // U+0051
{ 135, 4, 6, 5, 1, -6 }, // U+0052
{ 138, 4, 6, 5, 1, -6 }, // U+0053
{ 141, 5, 6, 5, 0, -6 }, // U+0054
{ 145, 4, 6, 5, 1, -6 }, // U+0055
{ 148, 4, 6, 5, 1, -6 }, // U+0056
{ 151, 4, 6, 5, 1, -6 }, // U+0057
{ 154, 4, 6, 5, 1, -6 }, // U+0058
{ 157, 5, 6, 5, 0, -6 }, // U+0059
{ 161, 4, 6, 5, 1, -6 }, // U+005A
{ 164, 3, 8, 5, 1, -7 }, // U+005B
{ 167, 4, 8, 5, 1, -7 }, // U+005C
{ 171, 3, 8, 5, 1, -7 }, // U+005D
{ 174, 5, 3, 5, 0, -7 }, // U+005E
{ 176, 4, 1, 5, 1, 0 }, // U+005F
{ 177, 2, 3, 5, 2, -7 }, // U+0060
{ 178, 4, 5, 5, 1, -5 }, // U+0061
{ 181, 4, 7, 5, 1, -7 }, // U+0062
{ 185, 4, 5, 5, 1, -5 }, // U+0063
{ 188, 4, 7, 5, 1, -7 }, // U+0064
{ 192, 4, 5, 5, 1, -5 }, // U+0065
{ 195, 3, 7, 5, 2, -7 }, // U+0066
{ 198, 4, 7, 5, 1, -5 }, // U+0067
{ 202, 4, 7, 5, 1, -7 }, // U+0068
{ 206, 2, 7, 5, 2, -7 }, // U+0069
{ 208, 3, 9, 5, 1, -7 }, // U+006A
{ 212, 4, 7, 5, 1, -7 }, // U+006B
{ 216, 2, 7, 5, 2, -7 }, // U+006C
{ 218, 4, 5, 5, 1, -5 }, // U+006D
{ 221, 4, 5, 5, 1, -5 }, // U+006E
{ 224, 4, 5, 5, 1, -5 }, // U+006F
{ 227, 4, 7, 5, 1, -5 }, // U+0070
{ 231, 4, 7, 5, 1, -5 }, // U+0071
{ 235, 4, 5, 5, 1, -5 }, // U+0072
{ 238, 4, 5, 5, 1, -5 }, // U+0073
{ 241, 3, 6, 5, 1, -6 }, // U+0074
{ 244, 4, 5, 5, 1, -5 }, // U+0075
{ 247, 4, 5, 5, 1, -5 }, // U+0076
{ 250, 4, 5, 5, 1, -5 }, // U+0077
{ 253, 4, 5, 5, 1, -5 }, // U+0078
{ 256, 4, 7, 5, 1, -5 }, // U+0079
{ 260, 4, 5, 5, 1, -5 }, // U+007A
{ 263, 3, 8, 5, 1, -7 }, // U+007B
{ 266, 1, 8, 5, 2, -7 }, // U+007C
{ 267, 3, 8, 5, 2, -7 }, // U+007D
{ 270, 5, 4, 5, 0, -5 }, // U+007E
{ 273, 0, 0, 1, 0, 0 }, // U+007F
{ 273, 0, 0, 1, 0, 0 }, // U+0080
{ 273, 0, 0, 1, 0, 0 }, // U+0081
{ 273, 0, 0, 1, 0, 0 }, // U+0082
{ 273, 0, 0, 1, 0, 0 }, // U+0083
{ 273, 0, 0, 1, 0, 0 }, // U+0084
{ 273, 0, 0, 1, 0, 0 }, // U+0085
{ 273, 0, 0, 1, 0, 0 }, // U+0086
{ 273, 0, 0, 1, 0, 0 }, // U+0087
{ 273, 0, 0, 1, 0, 0 }, // U+0088
{ 273, 0, 0, 1, 0, 0 }, // U+0089
{ 273, 0, 0, 1, 0, 0 }, // U+008A
{ 273, 0, 0, 1, 0, 0 }, // U+008B
{ 273, 0, 0, 1, 0, 0 }, // U+008C
{ 273, 0, 0, 1, 0, 0 }, // U+008D
{ 273, 0, 0, 1, 0, 0 }, // U+008E
{ 273, 0, 0, 1, 0, 0 }, // U+008F
{ 273, 0, 0, 1, 0, 0 }, // U+0090
{ 273, 0, 0, 1, 0, 0 }, // U+0091
{ 273, 0, 0, 1, 0, 0 }, // U+0092
{ 273, 0, 0, 1, 0, 0 }, // U+0093
{ 273, 0, 0, 1, 0, 0 }, // U+0094
{ 273, 4, 4, 5, 1, -4 }, // U+0095
{ 275, 0, 0, 1, 0, 0 }, // U+0096
{ 275, 0, 0, 1, 0, 0 }, // U+0097
{ 275, 0, 0, 1, 0, 0 }, // U+0098
{ 275, 0, 0, 1, 0, 0 }, // U+0099
{ 275, 0, 0, 1, 0, 0 }, // U+009A
{ 275, 0, 0, 1, 0, 0 }, // U+009B
{ 275, 0, 0, 1, 0, 0 }, // U+009C
{ 275, 0, 0, 1, 0, 0 }, // U+009D
{ 275, 0, 0, 1, 0, 0 }, // U+009E
{ 275, 0, 0, 1, 0, 0 }, // U+009F
{ 275, 0, 0, 1, 0, 0 }, // U+00A0
{ 275, 1, 6, 5, 2, -6 }, // U+00A1
{ 276, 4, 7, 5, 0, -6 }, // U+00A2
{ 280, 5, 7, 5, 0, -7 }, // U+00A3
{ 285, 5, 6, 5, 0, -6 }, // U+00A4
{ 289, 5, 7, 5, 0, -7 }, // U+00A5
{ 294, 1, 7, 5, 2, -6 }, // U+00A6
{ 295, 4, 8, 5, 0, -7 }, // U+00A7
{ 299, 3, 1, 5, 1, -6 }, // U+00A8
{ 300, 6, 7, 5, 0, -7 }, // U+00A9
{ 306, 4, 4, 5, 1, -6 }, // U+00AA
{ 308, 4, 3, 5, 1, -5 }, // U+00AB
{ 310, 5, 3, 5, 0, -4 }, // U+00AC
{ 312, 3, 1, 5, 1, -3 }, // U+00AD
{ 313, 6, 7, 5, 0, -7 }, // U+00AE
{ 319, 4, 1, 5, 1, -7 }, // U+00AF
{ 320, 3, 3, 5, 1, -6 }, // U+00B0
{ 322, 4, 5, 5, 0, -5 }, // U+00B1
{ 325, 3, 5, 5, 1, -7 }, // U+00B2
{ 327, 3, 5, 5, 1, -7 }, // U+00B3
{ 329, 2, 2, 5, 1, -8 }, // U+00B4
{ 330, 4, 6, 5, 1, -5 }, // U+00B5
{ 333, 5, 6, 5, 0, -6 }, // U+00B6
{ 337, 2, 2, 5, 2, -4 }, // U+00B7
{ 338, 3, 3, 5, 1, -4 }, // U+00B8
{ 340, 2, 4, 5, 1, -6 }, // U+00B9
{ 341, 4, 4, 5, 0, -8 }, // U+00BA
{ 343, 5, 3, 5, 1, -5 }, // U+00BB
{ 345, 5, 8, 5, 1, -8 }, // U+00BC
{ 350, 5, 7, 5, 1, -8 }, // U+00BD
{ 355, 6, 8, 5, 0, -8 }, // U+00BE
{ 361, 3, 6, 5, 2, -6 }, // U+00BF
{ 364, 4, 8, 5, 1, -8 }, // U+00C0
{ 368, 4, 8, 5, 1, -8 }, // U+00C1
{ 372, 4, 9, 5, 1, -9 }, // U+00C2
{ 377, 4, 8, 5, 1, -8 }, // U+00C3
{ 381, 5, 8, 5, 0, -8 }, // U+00C4
{ 386, 4, 8, 5, 1, -8 }, // U+00C5
{ 390, 6, 6, 5, 0, -6 }, // U+00C6
{ 395, 4, 8, 5, 0, -6 }, // U+00C7
{ 399, 4, 8, 5, 1, -8 }, // U+00C8
{ 403, 4, 8, 5, 1, -8 }, // U+00C9
{ 407, 4, 8, 5, 1, -8 }, // U+00CA
{ 411, 4, 8, 5, 1, -8 }, // U+00CB
{ 415, 3, 9, 5, 1, -9 }, // U+00CC
{ 419, 3, 9, 5, 1, -9 }, // U+00CD
{ 423, 3, 9, 5, 1, -9 }, // U+00CE
{ 427, 3, 8, 5, 1, -8 }, // U+00CF
{ 430, 5, 6, 5, 0, -6 }, // U+00D0
{ 434, 5, 8, 5, 0, -8 }, // U+00D1
{ 439, 4, 8, 5, 1, -8 }, // U+00D2
{ 443, 4, 8, 5, 1, -8 }, // U+00D3
{ 447, 4, 8, 5, 1, -8 }, // U+00D4
{ 451, 4, 8, 5, 1, -8 }, // U+00D5
{ 455, 5, 7, 5, 0, -7 }, // U+00D6
{ 460, 3, 3, 5, 1, -5 }, // U+00D7
{ 462, 6, 6, 5, 0, -6 }, // U+00D8
{ 467, 4, 8, 5, 1, -8 }, // U+00D9
{ 471, 4, 8, 5, 1, -8 }, // U+00DA
{ 475, 4, 9, 5, 1, -9 }, // U+00DB
{ 480, 4, 8, 5, 1, -8 }, // U+00DC
{ 484, 5, 8, 5, 0, -8 }, // U+00DD
{ 489, 4, 7, 5, 1, -6 }, // U+00DE
{ 493, 4, 7, 5, 1, -6 }, // U+00DF
{ 497, 4, 8, 5, 1, -8 }, // U+00E0
{ 501, 4, 8, 5, 1, -8 }, // U+00E1
{ 505, 4, 8, 5, 1, -8 }, // U+00E2
{ 509, 4, 8, 5, 1, -8 }, // U+00E3
{ 513, 4, 7, 5, 1, -7 }, // U+00E4
{ 517, 4, 7, 5, 1, -7 }, // U+00E5
{ 521, 5, 5, 5, 1, -5 }, // U+00E6
{ 525, 4, 7, 5, 1, -5 }, // U+00E7
{ 529, 4, 8, 5, 1, -8 }, // U+00E8
{ 533, 4, 8, 5, 1, -8 }, // U+00E9
{ 537, 4, 8, 5, 1, -8 }, // U+00EA
{ 541, 4, 7, 5, 1, -7 }, // U+00EB
{ 545, 2, 8, 5, 2, -8 }, // U+00EC
{ 547, 3, 8, 5, 2, -8 }, // U+00ED
{ 550, 3, 8, 5, 2, -8 }, // U+00EE
{ 553, 3, 7, 5, 2, -7 }, // U+00EF
{ 556, 4, 8, 5, 1, -8 }, // U+00F0
{ 560, 4, 8, 5, 1, -8 }, // U+00F1
{ 564, 5, 8, 5, 0, -8 }, // U+00F2
{ 569, 4, 8, 5, 1, -8 }, // U+00F3
{ 573, 4, 8, 5, 1, -8 }, // U+00F4
{ 577, 4, 8, 5, 1, -8 }, // U+00F5
{ 581, 4, 7, 5, 1, -7 }, // U+00F6
{ 585, 5, 5, 5, 0, -6 }, // U+00F7
{ 589, 6, 5, 5, 0, -5 }, // U+00F8
{ 593, 4, 8, 5, 1, -8 }, // U+00F9
{ 597, 5, 8, 5, 0, -8 }, // U+00FA
{ 602, 4, 8, 5, 1, -8 }, // U+00FB
{ 606, 5, 7, 5, 0, -7 }, // U+00FC
{ 611, 4, 10, 5, 1, -8 }, // U+00FD
{ 616, 4, 9, 5, 1, -7 }, // U+00FE
{ 621, 4, 9, 5, 1, -7 }, // U+00FF
{ 626, 4, 8, 5, 1, -8 }, // U+0100
{ 630, 4, 7, 5, 1, -7 }, // U+0101
{ 634, 4, 9, 5, 1, -9 }, // U+0102
{ 639, 4, 8, 5, 1, -8 }, // U+0103
{ 643, 5, 7, 5, 1, -6 }, // U+0104
{ 648, 5, 6, 5, 1, -5 }, // U+0105
{ 652, 4, 9, 5, 1, -9 }, // U+0106
{ 657, 4, 8, 5, 1, -8 }, // U+0107
{ 661, 4, 9, 5, 1, -9 }, // U+0108
{ 666, 4, 8, 5, 1, -8 }, // U+0109
{ 670, 4, 8, 5, 1, -8 }, // U+010A
{ 674, 4, 7, 5, 1, -7 }, // U+010B
{ 678, 4, 9, 5, 1, -9 }, // U+010C
{ 683, 4, 8, 5, 1, -8 }, // U+010D
{ 687, 4, 9, 5, 1, -9 }, // U+010E
{ 692, 6, 8, 5, 1, -8 }, // U+010F
{ 698, 5, 6, 5, 0, -6 }, // U+0110
{ 702, 5, 7, 5, 1, -7 }, // U+0111
{ 707, 4, 8, 5, 1, -8 }, // U+0112
{ 711, 4, 7, 5, 1, -7 }, // U+0113
{ 715, 4, 9, 5, 1, -9 }, // U+0114
{ 720, 4, 8, 5, 1, -8 }, // U+0115
{ 724, 4, 8, 5, 1, -8 }, // U+0116
{ 728, 4, 7, 5, 1, -7 }, // U+0117
{ 732, 5, 7, 5, 1, -6 }, // U+0118
{ 737, 4, 6, 5, 1, -5 }, // U+0119
{ 740, 4, 9, 5, 1, -9 }, // U+011A
{ 745, 4, 8, 5, 1, -8 }, // U+011B
{ 749, 4, 9, 5, 1, -9 }, // U+011C
{ 754, 4, 10, 5, 1, -8 }, // U+011D
{ 759, 4, 9, 5, 1, -9 }, // U+011E
{ 764, 4, 10, 5, 1, -8 }, // U+011F
{ 769, 4, 8, 5, 1, -8 }, // U+0120
{ 773, 4, 9, 5, 1, -7 }, // U+0121
{ 778, 4, 8, 5, 1, -6 }, // U+0122
{ 782, 4, 10, 5, 1, -8 }, // U+0123
{ 787, 4, 9, 5, 1, -9 }, // U+0124
{ 792, 4, 9, 5, 1, -9 }, // U+0125
{ 797, 6, 6, 5, 0, -6 }, // U+0126
{ 802, 5, 7, 5, 0, -7 }, // U+0127
{ 807, 4, 9, 5, 0, -9 }, // U+0128
{ 812, 4, 8, 5, 1, -8 }, // U+0129
{ 816, 3, 8, 5, 1, -8 }, // U+012A
{ 819, 3, 7, 5, 2, -7 }, // U+012B
{ 822, 3, 9, 5, 1, -9 }, // U+012C
{ 826, 3, 8, 5, 2, -8 }, // U+012D
{ 829, 3, 8, 5, 1, -6 }, // U+012E
{ 832, 2, 9, 5, 2, -7 }, // U+012F
{ 835, 3, 8, 5, 1, -8 }, // U+0130
{ 838, 2, 5, 5, 2, -5 }, // U+0131
{ 840, 6, 6, 5, 1, -6 }, // U+0132
{ 845, 5, 8, 5, 1, -7 }, // U+0133
{ 850, 4, 9, 5, 1, -9 }, // U+0134
{ 855, 4, 10, 5, 1, -8 }, // U+0135
{ 860, 4, 8, 5, 1, -6 }, // U+0136
{ 864, 4, 9, 5, 1, -7 }, // U+0137
{ 869, 4, 5, 5, 1, -5 }, // U+0138
{ 872, 4, 8, 5, 1, -8 }, // U+0139
{ 876, 4, 9, 5, 2, -9 }, // U+013A
{ 881, 4, 8, 5, 1, -6 }, // U+013B
{ 885, 3, 9, 5, 1, -7 }, // U+013C
{ 889, 4, 7, 5, 1, -7 }, // U+013D
{ 893, 4, 10, 5, 2, -9 }, // U+013E
{ 898, 4, 6, 5, 1, -6 }, // U+013F
{ 901, 4, 7, 5, 2, -7 }, // U+0140
{ 905, 4, 6, 5, 1, -6 }, // U+0141
{ 908, 3, 7, 5, 2, -7 }, // U+0142
{ 911, 4, 9, 5, 1, -9 }, // U+0143
{ 916, 4, 8, 5, 1, -8 }, // U+0144
{ 920, 4, 8, 5, 1, -6 }, // U+0145
{ 924, 4, 10, 5, 1, -8 }, // U+0146
{ 929, 4, 8, 5, 1, -8 }, // U+0147
{ 933, 4, 8, 5, 1, -8 }, // U+0148
{ 937, 4, 8, 5, 1, -8 }, // U+0149
{ 941, 4, 8, 5, 1, -6 }, // U+014A
{ 945, 4, 10, 5, 1, -8 }, // U+014B
{ 950, 4, 8, 5, 1, -8 }, // U+014C
{ 954, 4, 7, 5, 1, -7 }, // U+014D
{ 958, 4, 9, 5, 1, -9 }, // U+014E
{ 963, 4, 8, 5, 1, -8 }, // U+014F
{ 967, 4, 9, 5, 1, -9 }, // U+0150
{ 972, 4, 8, 5, 1, -8 }, // U+0151
{ 976, 6, 6, 5, 0, -6 }, // U+0152
{ 981, 5, 5, 5, 1, -5 }, // U+0153
{ 985, 4, 8, 5, 1, -8 }, // U+0154
{ 989, 4, 8, 5, 1, -8 }, // U+0155
{ 993, 4, 8, 5, 1, -6 }, // U+0156
{ 997, 4, 7, 5, 1, -5 }, // U+0157
{ 1001, 4, 8, 5, 1, -8 }, // U+0158
{ 1005, 4, 8, 5, 1, -8 }, // U+0159
{ 1009, 4, 9, 5, 1, -9 }, // U+015A
{ 1014, 4, 8, 5, 1, -8 }, // U+015B
{ 1018, 4, 9, 5, 1, -9 }, // U+015C
{ 1023, 4, 8, 5, 1, -8 }, // U+015D
{ 1027, 4, 8, 5, 1, -6 }, // U+015E
{ 1031, 4, 7, 5, 1, -5 }, // U+015F
{ 1035, 4, 9, 5, 1, -9 }, // U+0160
{ 1040, 4, 8, 5, 1, -8 }, // U+0161
{ 1044, 5, 7, 5, 0, -6 }, // U+0162
{ 1049, 3, 7, 5, 1, -6 }, // U+0163
{ 1052, 5, 9, 5, 0, -9 }, // U+0164
{ 1058, 4, 8, 5, 1, -8 }, // U+0165
{ 1062, 5, 6, 5, 0, -6 }, // U+0166
{ 1066, 3, 6, 5, 1, -6 }, // U+0167
{ 1069, 4, 9, 5, 1, -9 }, // U+0168
{ 1074, 4, 8, 5, 1, -8 }, // U+0169
{ 1078, 4, 8, 5, 1, -8 }, // U+016A
{ 1082, 4, 7, 5, 1, -7 }, // U+016B
{ 1086, 4, 9, 5, 1, -9 }, // U+016C
{ 1091, 4, 8, 5, 1, -8 }, // U+016D
{ 1095, 4, 9, 5, 1, -9 }, // U+016E
{ 1100, 4, 8, 5, 1, -8 }, // U+016F
{ 1104, 5, 9, 5, 1, -9 }, // U+0170
{ 1110, 5, 8, 5, 1, -8 }, // U+0171
{ 1115, 4, 8, 5, 1, -6 }, // U+0172
{ 1119, 4, 7, 5, 1, -5 }, // U+0173
{ 1123, 4, 9, 5, 1, -9 }, // U+0174
{ 1128, 4, 8, 5, 1, -8 }, // U+0175
{ 1132, 5, 9, 5, 0, -9 }, // U+0176
{ 1138, 4, 10, 5, 1, -8 }, // U+0177
{ 1143, 5, 8, 5, 0, -8 }, // U+0178
{ 1148, 4, 9, 5, 1, -9 }, // U+0179
{ 1153, 4, 8, 5, 1, -8 }, // U+017A
{ 1157, 4, 8, 5, 1, -8 }, // U+017B
{ 1161, 4, 7, 5, 1, -7 }, // U+017C
{ 1165, 4, 9, 5, 1, -9 }, // U+017D
{ 1170, 4, 8, 5, 1, -8 }, // U+017E
{ 1174, 4, 7, 5, 1, -7 }, // U+017F
};
static const GFXfont Lemon PROGMEM = {
(uint8_t*)lemonBitmaps,
(GFXglyph*)lemonGlyphs,
0x0020, 0x017F,
10 // yAdvance
};

View File

@@ -1,6 +1,7 @@
#include "SH1106Display.h"
#include <Adafruit_GrayOLED.h>
#include "Adafruit_SH110X.h"
#include "LemonFont.h"
bool SH1106Display::i2c_probe(TwoWire &wire, uint8_t addr)
{
@@ -46,7 +47,14 @@ void SH1106Display::startFrame(Color bkg)
void SH1106Display::setTextSize(int sz)
{
display.setTextSize(sz);
_text_size = (uint8_t)sz;
if (sz == 1) {
display.setFont(nullptr); // use default font path; we override print() ourselves
display.setTextSize(1);
} else {
display.setFont(nullptr);
display.setTextSize(sz);
}
}
void SH1106Display::setColor(Color c)
@@ -60,9 +68,75 @@ void SH1106Display::setCursor(int x, int y)
display.setCursor(x, y);
}
// Decode one UTF-8 codepoint from *p, advancing p past it.
uint32_t SH1106Display::decodeUtf8(const uint8_t*& p) {
uint8_t c = *p++;
if (c < 0x80) return c;
if ((c & 0xE0) == 0xC0) {
uint32_t cp = c & 0x1F;
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
return cp;
}
if ((c & 0xF0) == 0xE0) {
uint32_t cp = c & 0x0F;
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
return cp;
}
// 4-byte and invalid — skip continuation bytes
while (*p && (*p & 0xC0) == 0x80) p++;
return '?';
}
// Draw one Lemon glyph at (x, y), return new cursor x.
int16_t SH1106Display::drawLemonChar(int16_t x, int16_t y, uint32_t cp) {
if (cp < Lemon.first || cp > Lemon.last) {
// outside font range — use default advance
return x + 6;
}
const GFXglyph* glyph = &lemonGlyphs[cp - Lemon.first];
uint8_t w = pgm_read_byte(&glyph->width);
uint8_t h = pgm_read_byte(&glyph->height);
int8_t xo = (int8_t)pgm_read_byte(&glyph->xOffset);
int8_t yo = (int8_t)pgm_read_byte(&glyph->yOffset);
uint8_t xa = pgm_read_byte(&glyph->xAdvance);
uint16_t bo = pgm_read_word(&glyph->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(&lemonBitmaps[bo++]); bit = 0x80; }
if (bits & bit)
display.drawPixel(x + xo + col, y + yo + row, _color);
bit >>= 1;
}
}
return x + xa;
}
uint8_t SH1106Display::lemonXAdvance(uint32_t cp) {
if (cp < Lemon.first || cp > Lemon.last) return 6;
const GFXglyph* glyph = &lemonGlyphs[cp - Lemon.first];
return pgm_read_byte(&glyph->xAdvance);
}
void SH1106Display::print(const char *str)
{
display.print(str);
if (_text_size != 1) { 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 = decodeUtf8(p);
if (cp == '\n') {
cy += Lemon.yAdvance;
cx = 0;
} else {
cx = drawLemonChar(cx, cy, cp);
}
}
display.setCursor(cx, cy);
}
void SH1106Display::fillRect(int x, int y, int w, int h)
@@ -82,10 +156,15 @@ void SH1106Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h)
uint16_t SH1106Display::getTextWidth(const char *str)
{
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w;
if (_text_size != 1) {
int16_t x1, y1; uint16_t w, h;
display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h);
return w;
}
uint16_t width = 0;
const uint8_t* p = (const uint8_t*)str;
while (*p) width += lemonXAdvance(decodeUtf8(p));
return width;
}
void SH1106Display::setBrightness(uint8_t level)

View File

@@ -21,11 +21,15 @@ class SH1106Display : public DisplayDriver
uint8_t _color;
uint8_t _contrast;
uint8_t _precharge;
uint8_t _text_size;
bool i2c_probe(TwoWire &wire, uint8_t addr);
static uint32_t decodeUtf8(const uint8_t*& p);
int16_t drawLemonChar(int16_t x, int16_t y, uint32_t cp);
uint8_t lemonXAdvance(uint32_t cp);
public:
SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; _contrast = 255; _precharge = 0x1F; }
SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; _contrast = 255; _precharge = 0x1F; _text_size = 1; }
bool begin();
bool isOn() override { return _isOn; }
@@ -43,4 +47,12 @@ public:
uint16_t getTextWidth(const char *str) override;
void setBrightness(uint8_t level) override;
void endFrame() override;
// UTF-8 is handled natively in print() — no substitution needed
void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) override {
size_t len = strlen(src);
if (len >= dest_size) len = dest_size - 1;
memcpy(dest, src, len);
dest[len] = '\0';
}
};