feat: add Char Test screen and extend Lemon font to Greek/Cyrillic

- Add CharTestScreen showing sample characters from 11 scripts
  (PL, DE, CS, FR, NO, HU, HR, TR, LT, Cyrillic, Greek) as raw
  UTF-8 to verify native font rendering without transliteration
- Add "Char Test" entry to ToolsScreen menu
- Regenerate LemonFont.h from lemon.bdf with full U+0020–U+04FF
  range: 1074 BDF glyphs + 174 placeholders, 4111 bitmap bytes;
  adds Greek (137 glyphs) and Cyrillic (256 glyphs) coverage;
  also fixes space advance (DWIDTH=5, was BBX+1=1)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-05-17 23:34:44 +02:00
parent dbda484f2b
commit 50eb58c276
5 changed files with 1516 additions and 358 deletions

View File

@@ -0,0 +1,69 @@
#pragma once
// Test screen that displays sample characters from various alphabets/scripts.
// Useful for verifying transliteration and font coverage on experimental fonts.
class CharTestScreen : public UIScreen {
UITask* _task;
int _scroll;
int _visible; // updated each render, used by handleInput
struct TestLine { const char* label; const char* sample; };
static const int LINE_COUNT = 11;
static const TestLine LINES[LINE_COUNT];
public:
CharTestScreen(UITask* task) : _task(task), _scroll(0), _visible(6) {}
void enter() { _scroll = 0; }
int render(DisplayDriver& display) override {
display.setTextSize(1);
display.setColor(DisplayDriver::LIGHT);
display.drawTextCentered(display.width() / 2, 0, "CHAR TEST");
display.fillRect(0, 10, display.width(), 1);
const int lineH = display.getLineHeight();
const int startY = 12;
_visible = (display.height() - startY) / lineH;
for (int i = 0; i < _visible && (_scroll + i) < LINE_COUNT; i++) {
char buf[80];
snprintf(buf, sizeof(buf), "%s:%s", LINES[_scroll + i].label, LINES[_scroll + i].sample);
display.setCursor(2, startY + i * lineH);
display.print(buf);
}
if (_scroll > 0) {
display.setCursor(display.width() - 6, startY);
display.print("^");
}
if (_scroll + _visible < LINE_COUNT) {
display.setCursor(display.width() - 6, display.height() - lineH);
display.print("v");
}
return 500;
}
bool handleInput(char c) override {
if (c == KEY_UP && _scroll > 0) { _scroll--; return true; }
if (c == KEY_DOWN && _scroll + _visible < LINE_COUNT) { _scroll++; return true; }
if (c == KEY_CANCEL || c == KEY_CONTEXT_MENU) { _task->gotoToolsScreen(); return true; }
return false;
}
};
// Sample strings are printed as raw UTF-8 (no transliteration) to test native font coverage.
// Lemon font covers U+0020U+04FF: Latin, Greek and Cyrillic all render natively.
const CharTestScreen::TestLine CharTestScreen::LINES[CharTestScreen::LINE_COUNT] = {
{ "PL", "ąćęłńóśźżĄĆĘŁŃ" }, // Polish
{ "DE", "äöüÄÖÜß" }, // German
{ "CS", "čďěňřšťžůýČŘŠŤŽ" }, // Czech/Slovak
{ "FR", "àâçèêëîïôùûÿ" }, // French
{ "NO", "åæøÅÆØðþÐÞ" }, // Nordic/Icelandic
{ "HU", "áéíőúűÁÉÍŐÚŰ" }, // Hungarian
{ "HR", "čćđšžČĆĐŠŽ" }, // Croatian
{ "TR", "çğışöüİŞĞ" }, // Turkish
{ "LT", "āēīūģķļņŗėįų" }, // Baltic (Latvian/Lithuanian)
{ "RU", "АБВГДЕЖЗабвгдеж" }, // Cyrillic (→ blocks █)
{ "GR", "\xCE\xB1\xCE\xB2\xCE\xB3\xCE\xB4\xCE\xB5\xCE\xB6\xCE\xB7\xCE\xB8\xCE\xB9\xCE\xBA" }, // Greek αβγδεζηθικ (→ blocks █)
};

View File

@@ -6,7 +6,7 @@ class ToolsScreen : public UIScreen {
UITask* _task;
int _sel;
static const int ITEM_COUNT = 4;
static const int ITEM_COUNT = 5;
static const char* ITEMS[ITEM_COUNT];
public:
@@ -46,8 +46,9 @@ public:
if (_sel == 1) { _task->gotoBotScreen(); return true; }
if (_sel == 2) { _task->gotoNearbyScreen(); return true; }
if (_sel == 3) { _task->gotoAutoAdvertScreen(); return true; }
if (_sel == 4) { _task->gotoCharTestScreen(); return true; }
}
return false;
}
};
const char* ToolsScreen::ITEMS[4] = { "Ringtone Editor", "Auto-Reply Bot", "Nearby Nodes", "Auto-Advert" };
const char* ToolsScreen::ITEMS[5] = { "Ringtone Editor", "Auto-Reply Bot", "Nearby Nodes", "Auto-Advert", "Char Test" };

View File

@@ -127,6 +127,7 @@ static const uint16_t HP_ALL = 0x01FF;
#include "NearbyScreen.h"
#include "DashboardConfigScreen.h"
#include "AutoAdvertScreen.h"
#include "CharTestScreen.h"
#include "ToolsScreen.h"
// ── HomeScreen ────────────────────────────────────────────────────────────────
@@ -808,6 +809,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
nearby_screen = new NearbyScreen(this);
dashboard_config = new DashboardConfigScreen(this, node_prefs);
auto_advert_screen = new AutoAdvertScreen(this, node_prefs);
char_test_screen = new CharTestScreen(this);
setCurrScreen(splash);
applyBrightness();
@@ -847,6 +849,11 @@ void UITask::gotoAutoAdvertScreen() {
setCurrScreen(auto_advert_screen);
}
void UITask::gotoCharTestScreen() {
((CharTestScreen*)char_test_screen)->enter();
setCurrScreen(char_test_screen);
}
void UITask::playMelody(const char* melody) {
#ifdef PIN_BUZZER
buzzer.playForced(melody);

View File

@@ -72,6 +72,7 @@ class UITask : public AbstractUITask {
UIScreen* nearby_screen;
UIScreen* dashboard_config;
UIScreen* auto_advert_screen;
UIScreen* char_test_screen;
UIScreen* curr;
void userLedHandler();
@@ -113,6 +114,7 @@ public:
void gotoNearbyScreen();
void gotoDashboardConfig();
void gotoAutoAdvertScreen();
void gotoCharTestScreen();
void playMelody(const char* melody);
void stopMelody();
bool isMelodyPlaying();

File diff suppressed because it is too large Load Diff