feat(ui-lvgl): European fonts and phone-style keyboard

Fonts: Noto Sans generated with lv_font_conv (ui-lvgl/fonts/generate.sh,
OFL) replaces ASCII-only Montserrat: European Latin (Latin-1, Extended-A/B),
Greek, Cyrillic + supplement, typographic punctuation, currency, LVGL
symbols + globe; 12/14/16/20 px, clock 40 px digits only (RLE, so
LV_USE_FONT_COMPRESSED is on).

Keyboard (ui-lvgl/Keyboard.h): one layout per script (Latin QWERTY,
Cyrillic, Greek) picked by the same keyboard_main/alt_alphabet prefs as
ui-new, globe key to switch, hold a key for its variants in a popup above
it, one-shot shift, symbols page; compose limit counted in UTF-8 bytes.
First Settings screen (Keyboard: Main / Additional).

Long-press variants and UTF-8 case mapping move to ui-core/KeyboardData.h
and are shared with ui-new, whose Hold-Enter accents gain Romanian,
Turkish, Hungarian, Baltic and Icelandic letters (and ș/ț, Ukrainian /
Serbian letters now capitalise).

Verified on L2 hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-09-25 00:08:15 +02:00
co-authored by Claude Opus 5.5
parent cd926b7e45
commit bf1e6fcaa0
16 changed files with 47899 additions and 75 deletions
+1 -1
View File
@@ -145,7 +145,7 @@ consumes keys only.
- LVGL 9 (pinned), memory in a PSRAM pool; partial-render buffers in internal RAM, flush over QSPI via LovyanGFX. - LVGL 9 (pinned), memory in a PSRAM pool; partial-render buffers in internal RAM, flush over QSPI via LovyanGFX.
- Theme tokens (colours, spacing, typography) in one place — the first chance to realise the "Amber Trace" direction in colour. - Theme tokens (colours, spacing, typography) in one place — the first chance to realise the "Amber Trace" direction in colour.
- Fonts with full Latin/Polish/Cyrillic coverage. - Fonts ✅ Noto Sans generated by `ui-lvgl/fonts/generate.sh` (lv_font_conv): European Latin (Latin-1, Extended-A/B), Greek, Cyrillic + supplement, typographic punctuation, currency, LVGL symbols; 12/14/16/20 px, clock 40 px digits only. On-screen keyboard (`ui-lvgl/Keyboard.h`), phone-style: one layout per script (Latin QWERTY / Cyrillic ЙЦУКЕН / Greek) chosen by the same `keyboard_main_alphabet` / `keyboard_alt_alphabet` prefs as `ui-new` (globe key switches), hold a key for its variants; compose limit counted in UTF-8 bytes. Long-press variants and UTF-8 case mapping are shared with `ui-new` in `ui-core/KeyboardData.h`.
- Runs in the WASM sim at 320×240 with mouse as touch, so most UI work needs no flashing. - Runs in the WASM sim at 320×240 with mouse as touch, so most UI work needs no flashing.
- PR #3381's LVGL UI is a reference for platform glue (flush, GT911, PSRAM pool) only; its screens are not adopted. - PR #3381's LVGL UI is a reference for platform glue (flush, GT911, PSRAM pool) only; its screens are not adopted.
@@ -0,0 +1,161 @@
#pragma once
// Keyboard data shared by every frontend: the long-press variants offered for
// a base key (phone-style: hold a letter to get its accented forms) and UTF-8
// case mapping for everything the keyboards can produce. Scripts are picked by
// NodePrefs::keyboard_main_alphabet / keyboard_alt_alphabet (Latin, Cyrillic,
// Greek); Latin diacritics never need a page of their own -- they're all
// reachable by holding the base letter.
//
// Standalone header (no MeshCore / display dependencies).
#include <stdint.h>
#include <string.h>
namespace kbd {
// ── Long-press variants ──────────────────────────────────────────────────────
// One UTF-8 string of concatenated variants per base key, lowercase. Latin
// covers the union of the European languages written in it (Polish, Czech,
// Slovak, German, French, Spanish, Portuguese, Italian, Nordic, Icelandic,
// Hungarian, Romanian, Turkish, Baltic, …). Ligature / non-diacritic letters
// are filed under their conventional key, as on a phone: ß → s, œ → o,
// æ → a, ð → d, þ → t.
static const char LATIN_BASES[] = "acdegiklnorstuyz";
static const char* const LATIN_VARIANTS[] = {
"áàâãäåąăāæ", // a
"çćč", // c
"ďð", // d
"éèêëěęēė", // e
"ğģ", // g
"íîïīį", // i
"ķ", // k
"łĺľļ", // l
"ñńňņ", // n
"óòôõöøœő", // o
"řŕ", // r
"śšşșß", // s
"ťțþ", // t
"úùûüůűūų", // u
"ýÿ", // y
"źżž", // z
};
static const int LATIN_COUNT = sizeof(LATIN_BASES) - 1;
// Cyrillic, on a Russian ЙЦУКЕН base: Ukrainian / Belarusian / Serbian /
// Macedonian letters under the key they sit on (or sound like).
struct Variant { const char* base; const char* variants; };
static const Variant CYRILLIC_VARIANTS[] = {
{ "е", "ёє" }, { "и", "ії" }, { "г", "ґѓ" }, { "у", "ў" }, { "д", "ђ" },
{ "й", "ј" }, { "л", "љ" }, { "н", "њ" }, { "ч", "ћ" }, { "ц", "џ" },
{ "к", "ќ" }, { "з", "ѕ" }, { "ь", "ъ" },
};
static const Variant GREEK_VARIANTS[] = {
{ "α", "ά" }, { "ε", "έ" }, { "η", "ή" }, { "ι", "ίϊΐ" }, { "ο", "ό" },
{ "υ", "ύϋΰ" }, { "ω", "ώ" }, { "σ", "ς" },
};
// Punctuation behind the period key (any script).
static const char PERIOD_VARIANTS[] = ",?!'\"-:;()";
// Latin index by ASCII base letter (for callers that key on a char), -1 if none.
static inline int latinGroup(char base) {
for (int i = 0; i < LATIN_COUNT; i++) if (LATIN_BASES[i] == base) return i;
return -1;
}
// Variants for a base key given as UTF-8 (lowercase), or nullptr.
static inline const char* variantsFor(const char* base) {
if (!base || !base[0]) return nullptr;
if (strcmp(base, ".") == 0) return PERIOD_VARIANTS;
if (base[1] == '\0') {
int g = latinGroup(base[0]);
return g >= 0 ? LATIN_VARIANTS[g] : nullptr;
}
for (const Variant& v : CYRILLIC_VARIANTS) if (strcmp(v.base, base) == 0) return v.variants;
for (const Variant& v : GREEK_VARIANTS) if (strcmp(v.base, base) == 0) return v.variants;
return nullptr;
}
// ── UTF-8 ────────────────────────────────────────────────────────────────────
static inline uint32_t decode(const uint8_t*& p) {
uint32_t c = *p++;
if (c < 0x80) return c;
if ((c & 0xE0) == 0xC0) { uint32_t r = (c & 0x1F) << 6; if (*p) r |= (*p++ & 0x3F); return r; }
if ((c & 0xF0) == 0xE0) {
uint32_t r = (c & 0x0F) << 12;
if (*p) r |= (*p++ & 0x3F) << 6;
if (*p) r |= (*p++ & 0x3F);
return r;
}
return '?';
}
static inline int encode(uint32_t cp, char* out) {
if (cp < 0x80) { out[0] = (char)cp; return 1; }
if (cp < 0x800) { out[0] = (char)(0xC0 | (cp >> 6)); out[1] = (char)(0x80 | (cp & 0x3F)); return 2; }
out[0] = (char)(0xE0 | (cp >> 12));
out[1] = (char)(0x80 | ((cp >> 6) & 0x3F));
out[2] = (char)(0x80 | (cp & 0x3F));
return 3;
}
// Uppercase of one codepoint, for every letter the keyboards produce. Each
// block pairs its cases differently:
// - ASCII a-z, Cyrillic а-я, Greek α-ω: flat -0x20. Exceptions: ё → Ё, the
// Cyrillic ѐ-џ extras (Ukrainian / Serbian …, U+0450-045F) are -0x50, ς has
// no uppercase of its own and maps to Σ, and the tonos vowels ά έ ή ί ό ύ ώ
// sit outside α-ω.
// - Latin-1 à-þ: flat -0x20 (÷ excluded); ÿ → Ÿ (U+0178) is special.
// - Latin Extended-A: adjacent pairs, lowercase = uppercase + 1, but the
// parity flips around the unpaired ĸ (U+0138), ʼn (U+0149) and Ÿ (U+0178).
// - ș ț (U+0219 / U+021B): same pair rule. Cyrillic ґ (U+0491): pair rule.
// - ß → ẞ (U+1E9E).
static inline uint32_t toUpper(uint32_t cp) {
if (cp == 0x0451) return 0x0401; // ё
if (cp >= 0x0450 && cp <= 0x045F) return cp - 0x50; // ѐ-џ
if (cp == 0x0491) return 0x0490; // ґ
if (cp == 0x03C2) return 0x03A3; // ς → Σ
if (cp == 0x03AC) return 0x0386; // ά
if (cp >= 0x03AD && cp <= 0x03AF) return cp - 0x25; // έ ή ί
if (cp == 0x03CC) return 0x038C; // ό
if (cp == 0x03CD || cp == 0x03CE) return cp - 0x3F; // ύ ώ
if (cp == 0x03CA || cp == 0x03CB) return cp - 0x20; // ϊ ϋ
if (cp == 0x00FF) return 0x0178; // ÿ
if (cp == 0x00DF) return 0x1E9E; // ß
if (cp >= 0x0430 && cp <= 0x044F) return cp - 0x20;
if (cp >= 0x03B1 && cp <= 0x03C9) return cp - 0x20;
if (cp >= 0x00E0 && cp <= 0x00FE && cp != 0x00F7) return cp - 0x20;
if (cp >= 0x0100 && cp <= 0x0137 && (cp & 1) == 1) return cp - 1;
if (cp >= 0x0139 && cp <= 0x0148 && (cp & 1) == 0) return cp - 1;
if (cp >= 0x014A && cp <= 0x0177 && (cp & 1) == 1) return cp - 1;
if (cp >= 0x0179 && cp <= 0x017E && (cp & 1) == 0) return cp - 1;
if (cp == 0x0219 || cp == 0x021B) return cp - 1;
if (cp >= 'a' && cp <= 'z') return cp - 0x20;
return cp;
}
// Uppercase a UTF-8 string into `out` (codepoint by codepoint).
static inline void toUpperUtf8(const char* in, char* out, size_t out_size) {
size_t o = 0;
const uint8_t* p = (const uint8_t*)in;
while (*p && o + 4 < out_size) o += encode(toUpper(decode(p)), out + o);
out[o] = '\0';
}
// Number of codepoints / the idx-th codepoint (as its own UTF-8 string).
static inline int utf8Len(const char* s) {
int n = 0;
const uint8_t* p = (const uint8_t*)s;
while (*p) { decode(p); n++; }
return n;
}
static inline void utf8At(const char* s, int idx, char* out /* >= 5 bytes */) {
const uint8_t* p = (const uint8_t*)s;
for (int i = 0; *p; i++) {
const uint8_t* start = p;
decode(p);
if (i == idx) { memcpy(out, start, p - start); out[p - start] = '\0'; return; }
}
out[0] = '\0';
}
} // namespace kbd
+265
View File
@@ -0,0 +1,265 @@
#pragma once
// Phone-style on-screen keyboard on top of lv_keyboard (USER_1 mode):
// - one letter layout per script (Latin QWERTY, Cyrillic ЙЦУКЕН, Greek);
// which scripts are offered comes from the same prefs ui-new uses
// (NodePrefs::keyboard_main_alphabet / keyboard_alt_alphabet). The globe
// key switches between the two when they differ.
// - hold a key for its variants (ą ć ę … / ё є ї … / ά έ … / punctuation
// behind '.'), from the shared table in ui-core/KeyboardData.h. The popup
// opens above the key; tap a variant to type it, tap elsewhere to close.
// - one-shot shift; digits / symbols page behind "1#".
// Letters type on release (popover keys), so a long press never types the
// base letter first.
//
// Single-TU fragment: included by ui-lvgl/UITask.cpp only.
namespace kb {
#define KB_BS LV_SYMBOL_BACKSPACE
#define KB_OK LV_SYMBOL_OK
#define KB_SH LV_SYMBOL_UP
static const char* const K_SYM = "1#";
static const char* const K_BACK = "abc";
static const char* const K_GLOBE = "\xEF\x82\xAC"; // FontAwesome globe (U+F0AC) -- see fonts/generate.sh
static const char* const LAT[] = {
"q","w","e","r","t","y","u","i","o","p","\n",
"a","s","d","f","g","h","j","k","l","\n",
KB_SH,"z","x","c","v","b","n","m",KB_BS,"\n",
nullptr };
static const char* const LAT_UP[] = {
"Q","W","E","R","T","Y","U","I","O","P","\n",
"A","S","D","F","G","H","J","K","L","\n",
KB_SH,"Z","X","C","V","B","N","M",KB_BS,"\n",
nullptr };
static const char* const CYR[] = {
"й","ц","у","к","е","н","г","ш","щ","з","х","\n",
"ф","ы","в","а","п","р","о","л","д","ж","э","\n",
KB_SH,"я","ч","с","м","и","т","ь","б","ю",KB_BS,"\n",
nullptr };
static const char* const CYR_UP[] = {
"Й","Ц","У","К","Е","Н","Г","Ш","Щ","З","Х","\n",
"Ф","Ы","В","А","П","Р","О","Л","Д","Ж","Э","\n",
KB_SH,"Я","Ч","С","М","И","Т","Ь","Б","Ю",KB_BS,"\n",
nullptr };
static const char* const GRK[] = {
"ς","ε","ρ","τ","υ","θ","ι","ο","π","\n",
"α","σ","δ","φ","γ","η","ξ","κ","λ","\n",
KB_SH,"ζ","χ","ψ","ω","β","ν","μ",KB_BS,"\n",
nullptr };
static const char* const GRK_UP[] = {
"Σ","Ε","Ρ","Τ","Υ","Θ","Ι","Ο","Π","\n",
"Α","Σ","Δ","Φ","Γ","Η","Ξ","Κ","Λ","\n",
KB_SH,"Ζ","Χ","Ψ","Ω","Β","Ν","Μ",KB_BS,"\n",
nullptr };
static const char* const SYM[] = {
"1","2","3","4","5","6","7","8","9","0","\n",
"-","/",":",";","(",")","€","&","@","\"","\n",
".",",","?","!","'","#","%","+","=",KB_BS,"\n",
nullptr };
// Layout ids; letter layouts in (lower, upper) pairs, in NodePrefs
// KB_ALPHABET_* order (Latin, Cyrillic, Greek) so script * 2 = its lower layout.
enum : uint8_t { L_LAT, L_LAT_UP, L_CYR, L_CYR_UP, L_GRK, L_GRK_UP, L_PAIRED_END = L_GRK_UP,
L_SYM, L_COUNT };
static const char* const* const ROWS[L_COUNT] = { LAT, LAT_UP, CYR, CYR_UP, GRK, GRK_UP, SYM };
// Full lv_keyboard maps: the rows above + a bottom row assembled in
// buildMaps() (globe only when a second script is enabled).
static const int MAX_KEYS = 40;
static const char* s_maps[L_COUNT][MAX_KEYS + 8];
static lv_buttonmatrix_ctrl_t s_ctrl[L_COUNT][MAX_KEYS];
static uint8_t s_layout = L_LAT;
static uint8_t s_script = 0; // current script (NodePrefs KB_ALPHABET_*)
static NodePrefs* s_prefs = nullptr;
static bool s_suppress_release = false; // a long press opened the popup: don't type the base
static lv_obj_t* s_popup = nullptr; // overlay (on lv_layer_top) holding the variants row
static lv_obj_t* s_popup_kbd = nullptr;
static uint8_t mainScript() {
uint8_t s = s_prefs ? s_prefs->keyboard_main_alphabet : 0;
return s < NodePrefs::KB_ALPHABET_COUNT ? s : 0;
}
static uint8_t altScript() {
uint8_t s = s_prefs ? s_prefs->keyboard_alt_alphabet : 0;
return s < NodePrefs::KB_ALPHABET_COUNT ? s : 0;
}
static bool hasAlt() { return altScript() != mainScript(); }
static bool isSpecial(const char* t) {
static const char* const SP[] = { KB_SH, KB_BS, KB_OK, K_SYM, K_BACK, K_GLOBE };
for (const char* s : SP) if (strcmp(t, s) == 0) return true;
return false;
}
// Assemble the maps and derive key widths / styles from the labels: letters
// narrow, typed on release with a magnified popover and no auto-repeat;
// special keys wider and highlighted; space widest.
static void buildMaps() {
bool alt = hasAlt();
for (int l = 0; l < L_COUNT; l++) {
int m = 0;
for (const char* const* p = ROWS[l]; *p; p++) s_maps[l][m++] = *p;
if (l == L_SYM) {
s_maps[l][m++] = K_BACK; s_maps[l][m++] = "_"; s_maps[l][m++] = " ";
s_maps[l][m++] = "*"; s_maps[l][m++] = KB_OK;
} else {
s_maps[l][m++] = K_SYM;
if (alt) s_maps[l][m++] = K_GLOBE;
s_maps[l][m++] = " "; s_maps[l][m++] = "."; s_maps[l][m++] = KB_OK;
}
s_maps[l][m] = "";
int k = 0;
for (const char* const* p = s_maps[l]; **p; p++) {
if (strcmp(*p, "\n") == 0) continue;
uint32_t c;
if (strcmp(*p, " ") == 0) c = 6;
else if (strcmp(*p, KB_BS) == 0) c = 3 | LV_BUTTONMATRIX_CTRL_CHECKED; // keeps auto-repeat
else if (isSpecial(*p)) c = 3 | LV_BUTTONMATRIX_CTRL_CHECKED | LV_BUTTONMATRIX_CTRL_NO_REPEAT;
else c = 2 | LV_BUTTONMATRIX_CTRL_POPOVER | LV_BUTTONMATRIX_CTRL_NO_REPEAT;
if (k < MAX_KEYS) s_ctrl[l][k++] = (lv_buttonmatrix_ctrl_t)c;
}
}
}
// Label of button `id` (buttons numbered without the "\n" row breaks).
static const char* keyText(uint8_t layout, uint32_t id) {
uint32_t k = 0;
for (const char* const* p = s_maps[layout]; **p; p++) {
if (strcmp(*p, "\n") == 0) continue;
if (k++ == id) return *p;
}
return "";
}
static void apply(lv_obj_t* kbd, uint8_t layout) {
s_layout = layout;
lv_keyboard_set_map(kbd, LV_KEYBOARD_MODE_USER_1, s_maps[layout], s_ctrl[layout]);
lv_keyboard_set_mode(kbd, LV_KEYBOARD_MODE_USER_1);
}
static void releaseShift(lv_obj_t* kbd) {
if (s_layout <= L_PAIRED_END && (s_layout & 1)) apply(kbd, s_layout ^ 1);
}
// ── Variants popup ───────────────────────────────────────────────────────────
static char s_var_txt[12][8];
static const char* s_var_map[13];
static void closePopup() {
if (s_popup) { lv_obj_delete_async(s_popup); s_popup = nullptr; }
}
static void onPopupPick(lv_event_t* e) {
lv_obj_t* m = (lv_obj_t*)lv_event_get_target(e);
uint32_t id = lv_buttonmatrix_get_selected_button(m);
if (id == LV_BUTTONMATRIX_BUTTON_NONE || !s_popup_kbd) return;
lv_obj_t* ta = lv_keyboard_get_textarea(s_popup_kbd);
if (ta) lv_textarea_add_text(ta, lv_buttonmatrix_get_button_text(m, id));
releaseShift(s_popup_kbd);
closePopup();
}
static void onPopupOutside(lv_event_t* e) { (void)e; closePopup(); }
static void openPopup(lv_obj_t* kbd, const char* variants, bool upper) {
closePopup();
int n = ::kbd::utf8Len(variants);
if (n > 12) n = 12;
for (int i = 0; i < n; i++) {
char one[8];
::kbd::utf8At(variants, i, one);
if (upper) ::kbd::toUpperUtf8(one, s_var_txt[i], sizeof(s_var_txt[i]));
else memcpy(s_var_txt[i], one, sizeof(one));
s_var_map[i] = s_var_txt[i];
}
s_var_map[n] = "";
// Full-screen transparent overlay: a tap outside the row closes the popup.
s_popup = lv_obj_create(lv_layer_top());
lv_obj_remove_style_all(s_popup);
lv_obj_set_size(s_popup, LV_PCT(100), LV_PCT(100));
lv_obj_add_flag(s_popup, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(s_popup, onPopupOutside, LV_EVENT_CLICKED, NULL);
s_popup_kbd = kbd;
lv_obj_t* m = lv_buttonmatrix_create(s_popup);
lv_buttonmatrix_set_map(m, s_var_map);
const int key_w = 30;
int w = n * key_w + 8;
lv_obj_set_size(m, w, 44);
lv_obj_set_style_pad_all(m, 3, 0);
lv_obj_set_style_pad_gap(m, 2, 0);
lv_obj_set_style_bg_color(m, lv_color_hex(theme::SURFACE_2), 0);
lv_obj_set_style_border_color(m, lv_color_hex(theme::ACCENT), 0);
lv_obj_set_style_border_width(m, 1, 0);
lv_obj_set_style_radius(m, theme::RADIUS, 0);
lv_obj_set_style_text_font(m, THEME_FONT_LARGE, LV_PART_ITEMS);
// Above the pressed key, clamped to the screen.
lv_point_t pt = { 0, 0 };
lv_indev_t* indev = lv_indev_active();
if (indev) lv_indev_get_point(indev, &pt);
int scr_w = lv_display_get_horizontal_resolution(NULL);
int x = pt.x - w / 2;
if (x < 2) x = 2;
if (x + w > scr_w - 2) x = scr_w - 2 - w;
int y = pt.y - 70;
if (y < theme::STATUS_H) y = theme::STATUS_H;
lv_obj_set_pos(m, x, y);
lv_obj_add_event_cb(m, onPopupPick, LV_EVENT_VALUE_CHANGED, NULL);
}
// ── Events ───────────────────────────────────────────────────────────────────
static void onLongPress(lv_event_t* e) {
lv_obj_t* kbd = (lv_obj_t*)lv_event_get_target(e);
uint32_t id = lv_keyboard_get_selected_button(kbd);
if (id == LV_BUTTONMATRIX_BUTTON_NONE) return;
bool upper = s_layout <= L_PAIRED_END && (s_layout & 1);
uint8_t lower = upper ? (uint8_t)(s_layout ^ 1) : s_layout;
const char* variants = ::kbd::variantsFor(keyText(lower, id));
if (!variants) return; // plain key: the release types it as usual
s_suppress_release = true;
openPopup(kbd, variants, upper);
}
static void onEvent(lv_event_t* e) {
lv_obj_t* kbd = (lv_obj_t*)lv_event_get_target(e);
if (s_suppress_release) { s_suppress_release = false; return; }
uint32_t id = lv_keyboard_get_selected_button(kbd);
if (id == LV_BUTTONMATRIX_BUTTON_NONE) return;
const char* t = lv_keyboard_get_button_text(kbd, id);
if (!t) return;
if (strcmp(t, KB_SH) == 0) { if (s_layout <= L_PAIRED_END) apply(kbd, s_layout ^ 1); return; }
if (strcmp(t, K_SYM) == 0) { apply(kbd, L_SYM); return; }
if (strcmp(t, K_BACK) == 0) { apply(kbd, s_script * 2); return; }
if (strcmp(t, K_GLOBE) == 0) {
s_script = (s_script == mainScript()) ? altScript() : mainScript();
apply(kbd, s_script * 2);
return;
}
lv_keyboard_def_event_cb(e); // type the character / backspace / OK
if (strcmp(t, KB_BS) != 0) releaseShift(kbd);
}
static void onDelete(lv_event_t* e) { (void)e; closePopup(); s_popup_kbd = nullptr; }
// Create a keyboard on `parent`, opening on the main script.
static lv_obj_t* create(lv_obj_t* parent, NodePrefs* prefs) {
s_prefs = prefs;
buildMaps(); // cheap; picks up a changed alphabet setting
s_script = mainScript();
lv_obj_t* kbd = lv_keyboard_create(parent);
lv_obj_remove_event_cb(kbd, lv_keyboard_def_event_cb);
lv_obj_add_event_cb(kbd, onEvent, LV_EVENT_VALUE_CHANGED, NULL);
lv_obj_add_event_cb(kbd, onLongPress, LV_EVENT_LONG_PRESSED, NULL);
lv_obj_add_event_cb(kbd, onDelete, LV_EVENT_DELETE, NULL);
lv_keyboard_set_popovers(kbd, true);
apply(kbd, s_script * 2);
return kbd;
}
} // namespace kb
+7 -6
View File
@@ -21,10 +21,11 @@ namespace theme {
static const int STATUS_H = 22; // top status bar static const int STATUS_H = 22; // top status bar
static const int ROW_H = 44; // list row (touch target) static const int ROW_H = 44; // list row (touch target)
// Type // Type -- Noto Sans, generated by fonts/generate.sh (declared in lv_conf.h).
#define THEME_FONT_SMALL (&lv_font_montserrat_12) // ui_font_40 holds digits / ':' / '-' only (clock).
#define THEME_FONT_BODY (&lv_font_montserrat_14) #define THEME_FONT_SMALL (&ui_font_12)
#define THEME_FONT_TITLE (&lv_font_montserrat_16) #define THEME_FONT_BODY (&ui_font_14)
#define THEME_FONT_LARGE (&lv_font_montserrat_20) #define THEME_FONT_TITLE (&ui_font_16)
#define THEME_FONT_CLOCK (&lv_font_montserrat_40) #define THEME_FONT_LARGE (&ui_font_20)
#define THEME_FONT_CLOCK (&ui_font_40)
} }
+93 -14
View File
@@ -9,6 +9,8 @@
#include "../ui-core/UiCore.h" // shared UI Core (header-only, this TU) #include "../ui-core/UiCore.h" // shared UI Core (header-only, this TU)
#include "Theme.h" #include "Theme.h"
#include "LvglPort.h" #include "LvglPort.h"
#include "../ui-core/KeyboardData.h"
#include "Keyboard.h"
// ui-lvgl skeleton (docs/development/ui-core.md, step 5): status bar, home, // ui-lvgl skeleton (docs/development/ui-core.md, step 5): status bar, home,
// conversation list, contact picker, conversation view with compose. Every // conversation list, contact picker, conversation view with compose. Every
@@ -353,6 +355,7 @@ void UITask::back() {
switch (_screen) { switch (_screen) {
case SCR_THREAD: showChats(); break; case SCR_THREAD: showChats(); break;
case SCR_CONTACTS: showChats(); break; case SCR_CONTACTS: showChats(); break;
case SCR_SETTINGS: showHome(); break;
case SCR_CHATS: showHome(); break; case SCR_CHATS: showHome(); break;
default: break; default: break;
} }
@@ -361,6 +364,24 @@ void UITask::back() {
// ── Home ────────────────────────────────────────────────────────────────────── // ── Home ──────────────────────────────────────────────────────────────────────
static void onOpenChats(lv_event_t* e) { (void)e; s_ui->showChats(); } static void onOpenChats(lv_event_t* e) { (void)e; s_ui->showChats(); }
static void onOpenSettings(lv_event_t* e) { (void)e; s_ui->showSettings(); }
// Wide home tile: icon + label left, optional value label right.
static lv_obj_t* homeTile(lv_obj_t* parent, const char* text, lv_event_cb_t cb, lv_obj_t** value_out) {
lv_obj_t* tile = lv_button_create(parent);
lv_obj_set_size(tile, LV_PCT(100), 42);
lv_obj_set_style_bg_color(tile, lv_color_hex(theme::SURFACE), 0);
lv_obj_set_style_radius(tile, theme::RADIUS, 0);
lv_obj_set_style_shadow_width(tile, 0, 0);
lv_obj_add_event_cb(tile, cb, LV_EVENT_CLICKED, NULL);
lv_obj_t* tl = label(tile, text, THEME_FONT_TITLE, theme::TEXT);
lv_obj_align(tl, LV_ALIGN_LEFT_MID, theme::PAD, 0);
if (value_out) {
*value_out = label(tile, "", THEME_FONT_TITLE, theme::ACCENT);
lv_obj_align(*value_out, LV_ALIGN_RIGHT_MID, -theme::PAD, 0);
}
return tile;
}
void UITask::showHome() { void UITask::showHome() {
_screen = SCR_HOME; _screen = SCR_HOME;
@@ -373,21 +394,12 @@ void UITask::buildHome() {
lv_obj_set_flex_align(body, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_flex_align(body, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
_home_clock = label(body, "--:--", THEME_FONT_CLOCK, theme::TEXT); _home_clock = label(body, "--:--", THEME_FONT_CLOCK, theme::TEXT);
lv_obj_set_style_pad_top(_home_clock, 10, 0);
_home_date = label(body, "", THEME_FONT_BODY, theme::TEXT_MUTED); _home_date = label(body, "", THEME_FONT_BODY, theme::TEXT_MUTED);
lv_obj_t* name = label(body, the_mesh.getNodeName(), THEME_FONT_BODY, theme::ACCENT); lv_obj_t* name = label(body, the_mesh.getNodeName(), THEME_FONT_BODY, theme::ACCENT);
lv_obj_set_style_pad_bottom(name, 8, 0); lv_obj_set_style_pad_bottom(name, 4, 0);
lv_obj_t* tile = lv_button_create(body); homeTile(body, LV_SYMBOL_ENVELOPE " Messages", onOpenChats, &_home_unread);
lv_obj_set_size(tile, LV_PCT(100), 56); homeTile(body, LV_SYMBOL_SETTINGS " Settings", onOpenSettings, NULL);
lv_obj_set_style_bg_color(tile, lv_color_hex(theme::SURFACE), 0);
lv_obj_set_style_radius(tile, theme::RADIUS, 0);
lv_obj_set_style_shadow_width(tile, 0, 0);
lv_obj_add_event_cb(tile, onOpenChats, LV_EVENT_CLICKED, NULL);
lv_obj_t* tl = label(tile, LV_SYMBOL_ENVELOPE " Messages", THEME_FONT_TITLE, theme::TEXT);
lv_obj_align(tl, LV_ALIGN_LEFT_MID, theme::PAD, 0);
_home_unread = label(tile, "", THEME_FONT_TITLE, theme::ACCENT);
lv_obj_align(_home_unread, LV_ALIGN_RIGHT_MID, -theme::PAD, 0);
} }
void UITask::refreshHome() { void UITask::refreshHome() {
@@ -547,6 +559,18 @@ static void onKeyboard(lv_event_t* e) {
else if (code == LV_EVENT_CANCEL) lv_obj_add_flag((lv_obj_t*)lv_event_get_user_data(e), LV_OBJ_FLAG_HIDDEN); else if (code == LV_EVENT_CANCEL) lv_obj_add_flag((lv_obj_t*)lv_event_get_user_data(e), LV_OBJ_FLAG_HIDDEN);
} }
// Compose limit is in UTF-8 bytes (the over-the-air limit), not characters:
// Cyrillic / Greek / accented letters take two bytes each. Leaves room for the
// "Name: " prefix a channel send adds.
static const size_t COMPOSE_MAX_BYTES = MAX_TEXT_LEN - 40;
static void onComposeInsert(lv_event_t* e) {
lv_obj_t* ta = (lv_obj_t*)lv_event_get_target(e);
const char* ins = (const char*)lv_event_get_param(e);
if (ins && strlen(lv_textarea_get_text(ta)) + strlen(ins) > COMPOSE_MAX_BYTES)
lv_textarea_set_insert_replace(ta, ""); // reject: would exceed the byte limit
}
static void onComposeClicked(lv_event_t* e) { static void onComposeClicked(lv_event_t* e) {
lv_obj_t* kb = (lv_obj_t*)lv_event_get_user_data(e); lv_obj_t* kb = (lv_obj_t*)lv_event_get_user_data(e);
lv_obj_remove_flag(kb, LV_OBJ_FLAG_HIDDEN); lv_obj_remove_flag(kb, LV_OBJ_FLAG_HIDDEN);
@@ -606,13 +630,13 @@ void UITask::buildThread() {
_compose_ta = lv_textarea_create(bar); _compose_ta = lv_textarea_create(bar);
lv_textarea_set_one_line(_compose_ta, true); lv_textarea_set_one_line(_compose_ta, true);
lv_textarea_set_placeholder_text(_compose_ta, "Message"); lv_textarea_set_placeholder_text(_compose_ta, "Message");
lv_textarea_set_max_length(_compose_ta, MAX_TEXT_LEN - 40); lv_obj_add_event_cb(_compose_ta, onComposeInsert, LV_EVENT_INSERT, NULL);
lv_obj_set_size(_compose_ta, LV_PCT(100), 36); lv_obj_set_size(_compose_ta, LV_PCT(100), 36);
lv_obj_align(_compose_ta, LV_ALIGN_LEFT_MID, 0, 0); lv_obj_align(_compose_ta, LV_ALIGN_LEFT_MID, 0, 0);
// In the body's flex column below the compose bar: showing it shrinks the // In the body's flex column below the compose bar: showing it shrinks the
// message list, so the text field stays visible just above the keys. // message list, so the text field stays visible just above the keys.
_keyboard = lv_keyboard_create(body); _keyboard = kb::create(body, _prefs); // phone-style, scripts from prefs, hold for accents (Keyboard.h)
lv_obj_set_size(_keyboard, LV_PCT(100), 130); lv_obj_set_size(_keyboard, LV_PCT(100), 130);
lv_keyboard_set_textarea(_keyboard, _compose_ta); lv_keyboard_set_textarea(_keyboard, _compose_ta);
lv_obj_add_event_cb(_keyboard, onKeyboard, LV_EVENT_READY, _keyboard); lv_obj_add_event_cb(_keyboard, onKeyboard, LV_EVENT_READY, _keyboard);
@@ -756,3 +780,58 @@ void UITask::sendFromCompose() {
showToast("Send failed"); showToast("Send failed");
} }
} }
// ── Settings (first rows; the declarative schema replaces this later) ────────
static lv_obj_t* s_kb_main_dd = nullptr;
static lv_obj_t* s_kb_alt_dd = nullptr;
static void onKeyboardAlphabet(lv_event_t* e) {
(void)e;
s_ui->setKeyboardAlphabets(lv_dropdown_get_selected(s_kb_main_dd), lv_dropdown_get_selected(s_kb_alt_dd));
}
// One settings row: label left, dropdown right.
static lv_obj_t* dropdownRow(lv_obj_t* parent, const char* text, const char* options, int sel) {
lv_obj_t* row = lv_obj_create(parent);
styleSurface(row, theme::SURFACE);
lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_size(row, LV_PCT(100), theme::ROW_H);
lv_obj_set_style_radius(row, theme::RADIUS, 0);
lv_obj_t* l = label(row, text, THEME_FONT_BODY, theme::TEXT);
lv_obj_align(l, LV_ALIGN_LEFT_MID, theme::PAD, 0);
lv_obj_t* dd = lv_dropdown_create(row);
lv_dropdown_set_options_static(dd, options);
lv_dropdown_set_selected(dd, sel);
lv_obj_set_width(dd, 130);
lv_obj_align(dd, LV_ALIGN_RIGHT_MID, -4, 0);
lv_obj_add_event_cb(dd, onKeyboardAlphabet, LV_EVENT_VALUE_CHANGED, NULL);
return dd;
}
void UITask::showSettings() {
_screen = SCR_SETTINGS;
buildSettings();
}
void UITask::buildSettings() {
lv_obj_t* body = newScreen("Settings", true);
sectionTitle(body, "KEYBOARD");
uint8_t main_a = _prefs ? _prefs->keyboard_main_alphabet : 0;
uint8_t alt_a = _prefs ? _prefs->keyboard_alt_alphabet : 0;
if (main_a >= NodePrefs::KB_ALPHABET_COUNT) main_a = 0;
if (alt_a >= NodePrefs::KB_ALPHABET_COUNT) alt_a = main_a;
// Order matches NodePrefs::KB_ALPHABET_* (Latin, Cyrillic, Greek).
s_kb_main_dd = dropdownRow(body, "Main", "Latin\nCyrillic\nGreek", main_a);
// "None" = no second script (stored as alt == main, as ui-new does).
s_kb_alt_dd = dropdownRow(body, "Additional", "None\nLatin\nCyrillic\nGreek",
alt_a == main_a ? 0 : alt_a + 1);
label(body, "Hold a letter for accents and other variants.", THEME_FONT_SMALL, theme::TEXT_MUTED);
}
void UITask::setKeyboardAlphabets(int main_idx, int alt_sel) {
if (!_prefs) return;
_prefs->keyboard_main_alphabet = (uint8_t)main_idx;
_prefs->keyboard_alt_alphabet = (uint8_t)(alt_sel == 0 ? main_idx : alt_sel - 1);
the_mesh.savePrefs();
}
+4 -1
View File
@@ -36,6 +36,8 @@ public:
void showHome(); void showHome();
void showChats(); void showChats();
void showContacts(); void showContacts();
void showSettings();
void setKeyboardAlphabets(int main_idx, int alt_sel);
void openChannel(uint8_t channel_idx); void openChannel(uint8_t channel_idx);
void openDM(const uint8_t* pub_key); void openDM(const uint8_t* pub_key);
void back(); void back();
@@ -47,7 +49,7 @@ public:
void openAdminFor(const ContactInfo& ci, bool from_picker) { (void)ci; (void)from_picker; } void openAdminFor(const ContactInfo& ci, bool from_picker) { (void)ci; (void)from_picker; }
private: private:
enum Screen : uint8_t { SCR_HOME, SCR_CHATS, SCR_CONTACTS, SCR_THREAD }; enum Screen : uint8_t { SCR_HOME, SCR_CHATS, SCR_CONTACTS, SCR_THREAD, SCR_SETTINGS };
void buildStatusBar(); void buildStatusBar();
void refreshStatusBar(); void refreshStatusBar();
@@ -56,6 +58,7 @@ private:
void refreshHome(); void refreshHome();
void buildChats(); void buildChats();
void buildContacts(); void buildContacts();
void buildSettings();
void buildThread(); void buildThread();
void refreshThread(); void refreshThread();
uint32_t threadSignature() const; uint32_t threadSignature() const;
@@ -0,0 +1,93 @@
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/latin-greek-cyrillic)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
# Regenerates the ui-lvgl fonts (Noto Sans + LVGL's built-in symbols).
#
# Coverage: full European Latin (Latin-1, Latin Extended-A/B -- incl. Polish,
# Czech, Slovak, Hungarian, Romanian ș/ț, Baltic, Turkish, Nordic, Icelandic,
# Maltese, Welsh ŵ/ŷ/ẁ…, German ẞ), Greek, Cyrillic (+ supplement: Ukrainian,
# Belarusian, Serbian, Macedonian, Bulgarian…), typographic punctuation,
# currency (€ ₴ ₽ …) and №, plus LVGL's LV_SYMBOL_* icons.
#
# Needs node (npx fetches lv_font_conv) and curl. Run from anywhere:
# examples/companion_radio/ui-lvgl/fonts/generate.sh
# Output: ui_font_<size>.c next to this script (commit them).
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
CACHE="${FONT_CACHE:-${TMPDIR:-/tmp}/meshcore-ui-fonts}"
mkdir -p "$CACHE"
NOTO_BASE=https://github.com/notofonts/notofonts.github.io/raw/main/fonts/NotoSans/hinted/ttf
for w in Medium SemiBold; do
[ -f "$CACHE/NotoSans-$w.ttf" ] || curl -sSL -o "$CACHE/NotoSans-$w.ttf" "$NOTO_BASE/NotoSans-$w.ttf"
done
FA_URL=https://raw.githubusercontent.com/lvgl/lvgl/v9.2.2/scripts/built_in_font/FontAwesome5-Solid+Brands+Regular.woff
[ -f "$CACHE/fa.woff" ] || curl -sSL -o "$CACHE/fa.woff" "$FA_URL"
# Text ranges
TEXT="0x20-0x7E,0xA0-0x24F,0x370-0x3FF,0x400-0x52F,0x1E80-0x1E85,0x1E9E,0x1EF2-0x1EF3"
TEXT="$TEXT,0x2010-0x2027,0x2030,0x2039-0x203A,0x20A4-0x20BF,0x2116,0x2122"
# LVGL built-in symbols (same list as lvgl/scripts/built_in_font/built_in_font_gen.py)
# plus 61612 = globe (keyboard script key)
SYMS="61612,61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650"
CONV="npx -y lv_font_conv@1.5.3"
gen() { # size weight ranges name
$CONV --bpp 4 --size "$1" --force-fast-kern-format \
--font "$CACHE/NotoSans-$2.ttf" -r "$3" \
--font "$CACHE/fa.woff" -r "$SYMS" \
--format lvgl --lv-include lvgl.h --lv-font-name "$4" -o "$HERE/$4.c"
}
for s in 12 14 16 20; do gen "$s" Medium "$TEXT" "ui_font_$s"; done
# Clock: digits, colon, dash, space only.
$CONV --bpp 4 --size 40 --no-compress --font "$CACHE/NotoSans-SemiBold.ttf" -r "0x20,0x2D,0x30-0x3A" \
--format lvgl --lv-include lvgl.h --lv-font-name ui_font_40 -o "$HERE/ui_font_40.c"
echo "done: $(ls "$HERE"/ui_font_*.c | wc -l) fonts"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,531 @@
/*******************************************************************************
* Size: 40 px
* Bpp: 4
* Opts: --bpp 4 --size 40 --no-compress --font /private/tmp/claude-501/-Users-jakub-Documents-MeshCore-MeshCore/3d84fa48-e031-42a4-abec-01b33a2b06ef/scratchpad/fonts/NotoSans-SemiBold.ttf -r 0x20,0x2D,0x30-0x3A --format lvgl --lv-include lvgl.h --lv-font-name ui_font_40 -o /Users/jakub/Documents/MeshCore/MeshCore/examples/companion_radio/ui-lvgl/fonts/ui_font_40.c
******************************************************************************/
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl.h"
#endif
#ifndef UI_FONT_40
#define UI_FONT_40 1
#endif
#if UI_FONT_40
/*-----------------
* BITMAPS
*----------------*/
/*Store the image of the glyphs*/
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
/* U+0020 " " */
/* U+002D "-" */
0x23, 0x33, 0x33, 0x33, 0x33, 0x1b, 0xff, 0xff,
0xff, 0xff, 0xf8, 0xbf, 0xff, 0xff, 0xff, 0xff,
0x8b, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xbf, 0xff,
0xff, 0xff, 0xff, 0x80,
/* U+0030 "0" */
0x0, 0x0, 0x0, 0x4a, 0xdf, 0xfe, 0xc8, 0x20,
0x0, 0x0, 0x0, 0x0, 0x2, 0xdf, 0xff, 0xff,
0xff, 0xff, 0x90, 0x0, 0x0, 0x0, 0x3, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x0, 0x0,
0x1, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x90, 0x0, 0x0, 0x9f, 0xff, 0xff, 0x94, 0x24,
0xbf, 0xff, 0xff, 0x30, 0x0, 0x1f, 0xff, 0xff,
0x60, 0x0, 0x0, 0xaf, 0xff, 0xfb, 0x0, 0x6,
0xff, 0xff, 0xc0, 0x0, 0x0, 0x1, 0xff, 0xff,
0xf1, 0x0, 0xaf, 0xff, 0xf7, 0x0, 0x0, 0x0,
0xa, 0xff, 0xff, 0x60, 0xe, 0xff, 0xff, 0x30,
0x0, 0x0, 0x0, 0x6f, 0xff, 0xfa, 0x0, 0xff,
0xff, 0xf0, 0x0, 0x0, 0x0, 0x2, 0xff, 0xff,
0xd0, 0x2f, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xff, 0xff, 0x4, 0xff, 0xff, 0xc0, 0x0,
0x0, 0x0, 0x0, 0xff, 0xff, 0xf1, 0x5f, 0xff,
0xfc, 0x0, 0x0, 0x0, 0x0, 0xe, 0xff, 0xff,
0x25, 0xff, 0xff, 0xb0, 0x0, 0x0, 0x0, 0x0,
0xef, 0xff, 0xf2, 0x5f, 0xff, 0xfb, 0x0, 0x0,
0x0, 0x0, 0xe, 0xff, 0xff, 0x24, 0xff, 0xff,
0xc0, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff, 0xf2,
0x3f, 0xff, 0xfc, 0x0, 0x0, 0x0, 0x0, 0xf,
0xff, 0xff, 0x12, 0xff, 0xff, 0xe0, 0x0, 0x0,
0x0, 0x0, 0xff, 0xff, 0xf0, 0xf, 0xff, 0xff,
0x0, 0x0, 0x0, 0x0, 0x2f, 0xff, 0xfe, 0x0,
0xdf, 0xff, 0xf3, 0x0, 0x0, 0x0, 0x5, 0xff,
0xff, 0xb0, 0x9, 0xff, 0xff, 0x70, 0x0, 0x0,
0x0, 0xaf, 0xff, 0xf7, 0x0, 0x4f, 0xff, 0xfd,
0x0, 0x0, 0x0, 0x1f, 0xff, 0xff, 0x30, 0x0,
0xef, 0xff, 0xf7, 0x0, 0x0, 0xa, 0xff, 0xff,
0xd0, 0x0, 0x6, 0xff, 0xff, 0xfa, 0x43, 0x5b,
0xff, 0xff, 0xf6, 0x0, 0x0, 0xc, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfc, 0x0, 0x0, 0x0,
0x1e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x10,
0x0, 0x0, 0x0, 0x1b, 0xff, 0xff, 0xff, 0xff,
0xfb, 0x10, 0x0, 0x0, 0x0, 0x0, 0x3, 0x9d,
0xef, 0xed, 0x93, 0x0, 0x0, 0x0,
/* U+0031 "1" */
0x0, 0x0, 0x0, 0x0, 0x4e, 0xff, 0xfa, 0x0,
0x0, 0x0, 0x7, 0xff, 0xff, 0xfa, 0x0, 0x0,
0x1, 0xbf, 0xff, 0xff, 0xfa, 0x0, 0x0, 0x3d,
0xff, 0xff, 0xff, 0xfa, 0x0, 0x6, 0xff, 0xff,
0xff, 0xff, 0xfa, 0x0, 0x9f, 0xff, 0xff, 0xbf,
0xff, 0xfa, 0xc, 0xff, 0xff, 0xf6, 0x4f, 0xff,
0xfa, 0x8, 0xff, 0xfe, 0x40, 0x5f, 0xff, 0xfa,
0x0, 0xbf, 0xc2, 0x0, 0x5f, 0xff, 0xfa, 0x0,
0x8, 0x0, 0x0, 0x6f, 0xff, 0xfa, 0x0, 0x0,
0x0, 0x0, 0x6f, 0xff, 0xfa, 0x0, 0x0, 0x0,
0x0, 0x6f, 0xff, 0xfa, 0x0, 0x0, 0x0, 0x0,
0x6f, 0xff, 0xfa, 0x0, 0x0, 0x0, 0x0, 0x6f,
0xff, 0xfa, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff,
0xfa, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, 0xfa,
0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, 0xfa, 0x0,
0x0, 0x0, 0x0, 0x6f, 0xff, 0xfa, 0x0, 0x0,
0x0, 0x0, 0x6f, 0xff, 0xfa, 0x0, 0x0, 0x0,
0x0, 0x6f, 0xff, 0xfa, 0x0, 0x0, 0x0, 0x0,
0x6f, 0xff, 0xfa, 0x0, 0x0, 0x0, 0x0, 0x6f,
0xff, 0xfa, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff,
0xfa, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, 0xfa,
0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, 0xfa, 0x0,
0x0, 0x0, 0x0, 0x6f, 0xff, 0xfa, 0x0, 0x0,
0x0, 0x0, 0x6f, 0xff, 0xfa, 0x0, 0x0, 0x0,
0x0, 0x6f, 0xff, 0xfa,
/* U+0032 "2" */
0x0, 0x0, 0x2, 0x7b, 0xef, 0xfe, 0xc9, 0x40,
0x0, 0x0, 0x0, 0x0, 0x2a, 0xff, 0xff, 0xff,
0xff, 0xff, 0xd3, 0x0, 0x0, 0x0, 0x7f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x0, 0x0,
0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0x0, 0x9, 0xff, 0xff, 0xfb, 0x75, 0x57,
0xdf, 0xff, 0xff, 0xb0, 0x0, 0xb, 0xff, 0xa2,
0x0, 0x0, 0x0, 0xaf, 0xff, 0xff, 0x10, 0x0,
0xb, 0x50, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff,
0xf3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xc, 0xff, 0xff, 0x40, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xdf, 0xff, 0xf3, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xff,
0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4,
0xff, 0xff, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xcf, 0xff, 0xf5, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5f, 0xff,
0xff, 0x30, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x4f, 0xff, 0xff, 0x60, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x4f, 0xff, 0xff, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x4f, 0xff, 0xff, 0x80,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f, 0xff,
0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x5f, 0xff, 0xff, 0x70, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x5f, 0xff, 0xff, 0x60, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x5f, 0xff, 0xff, 0x50,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6f, 0xff,
0xff, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x6f, 0xff, 0xff, 0x30, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0xff, 0xff, 0xa7, 0x77, 0x77,
0x77, 0x77, 0x77, 0x72, 0x3f, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x44, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf4, 0x4f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x44, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf4,
/* U+0033 "3" */
0x0, 0x0, 0x26, 0xad, 0xef, 0xfe, 0xca, 0x50,
0x0, 0x0, 0x0, 0x3, 0xbf, 0xff, 0xff, 0xff,
0xff, 0xff, 0xe6, 0x0, 0x0, 0xa, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x0, 0x0,
0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf6, 0x0, 0x1, 0xef, 0xfe, 0x95, 0x32, 0x35,
0xbf, 0xff, 0xff, 0xd0, 0x0, 0x4, 0xc5, 0x0,
0x0, 0x0, 0x0, 0x7f, 0xff, 0xff, 0x10, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff,
0xf2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xe, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x2, 0xff, 0xff, 0xc0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1, 0xcf, 0xff, 0xf4,
0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x48, 0xef,
0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff,
0xff, 0xff, 0xff, 0xd4, 0x0, 0x0, 0x0, 0x0,
0xf, 0xff, 0xff, 0xff, 0xfb, 0x40, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff,
0xa4, 0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfa, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x13, 0x7c, 0xff, 0xff, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xff,
0xff, 0xf6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xb, 0xff, 0xff, 0xc0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, 0xff, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0xff,
0xff, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xd, 0xff, 0xff, 0xc0, 0x59,
0x20, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xff,
0xf7, 0x5, 0xff, 0xd9, 0x63, 0x21, 0x24, 0x8d,
0xff, 0xff, 0xff, 0x10, 0x5f, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0x50, 0x5, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x60,
0x0, 0x2c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xfb, 0x20, 0x0, 0x0, 0x2, 0x6a, 0xce, 0xff,
0xfe, 0xda, 0x72, 0x0, 0x0, 0x0,
/* U+0034 "4" */
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0xff,
0xff, 0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7, 0xff, 0xff, 0xf7, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x3, 0xff, 0xff, 0xff,
0x70, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xdf, 0xff, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x9f, 0xff, 0xff, 0xff, 0x70,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4f, 0xff,
0xef, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1e, 0xff, 0xf7, 0xff, 0xff, 0x70, 0x0,
0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xf8, 0x6f,
0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6,
0xff, 0xfd, 0x7, 0xff, 0xff, 0x70, 0x0, 0x0,
0x0, 0x0, 0x2, 0xff, 0xff, 0x30, 0x7f, 0xff,
0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0xcf, 0xff,
0x80, 0x8, 0xff, 0xff, 0x70, 0x0, 0x0, 0x0,
0x0, 0x7f, 0xff, 0xc0, 0x0, 0x8f, 0xff, 0xf7,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xff, 0xf2, 0x0,
0x8, 0xff, 0xff, 0x70, 0x0, 0x0, 0x0, 0xd,
0xff, 0xf6, 0x0, 0x0, 0x8f, 0xff, 0xf7, 0x0,
0x0, 0x0, 0x9, 0xff, 0xfb, 0x0, 0x0, 0x8,
0xff, 0xff, 0x70, 0x0, 0x0, 0x4, 0xff, 0xfe,
0x10, 0x0, 0x0, 0x8f, 0xff, 0xf7, 0x0, 0x0,
0x1, 0xef, 0xff, 0x50, 0x0, 0x0, 0x8, 0xff,
0xff, 0x70, 0x0, 0x0, 0xbf, 0xff, 0xb1, 0x11,
0x11, 0x11, 0x9f, 0xff, 0xf7, 0x11, 0x10, 0x3f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x23, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xf2, 0x3f, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x23, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xf2, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x8, 0xff, 0xff, 0x70, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f,
0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x8, 0xff, 0xff, 0x70, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff,
0xf7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x8, 0xff, 0xff, 0x70, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x8f, 0xff, 0xf7,
0x0, 0x0,
/* U+0035 "5" */
0x3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xe0, 0x0, 0x4f, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xfe, 0x0, 0x5, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xe0, 0x0, 0x7f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x0, 0x8, 0xff,
0xff, 0x86, 0x66, 0x66, 0x66, 0x66, 0x50, 0x0,
0x9f, 0xff, 0xf2, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xa, 0xff, 0xff, 0x10, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xcf, 0xff, 0xf0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xd, 0xff, 0xfd, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff,
0xc0, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, 0xf,
0xff, 0xfe, 0xef, 0xff, 0xff, 0xc7, 0x20, 0x0,
0x1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x70, 0x0, 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xa0, 0x1, 0xcf, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x80, 0x0, 0x57, 0x42,
0x10, 0x2, 0x6c, 0xff, 0xff, 0xff, 0x10, 0x0,
0x0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xff, 0xf7,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0xff,
0xff, 0xb0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x4, 0xff, 0xff, 0xd0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x5f, 0xff, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xff, 0xa0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff,
0xf6, 0xa6, 0x10, 0x0, 0x0, 0x0, 0x1, 0xcf,
0xff, 0xff, 0x1d, 0xff, 0xc8, 0x54, 0x33, 0x59,
0xff, 0xff, 0xff, 0x80, 0xdf, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xc0, 0xd, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc1, 0x0, 0x8f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x70, 0x0,
0x0, 0x5, 0x9b, 0xef, 0xff, 0xec, 0x95, 0x0,
0x0, 0x0,
/* U+0036 "6" */
0x0, 0x0, 0x0, 0x0, 0x5, 0x9c, 0xef, 0xff,
0xec, 0x30, 0x0, 0x0, 0x0, 0x1, 0x8f, 0xff,
0xff, 0xff, 0xff, 0xf4, 0x0, 0x0, 0x0, 0x4,
0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0x40, 0x0,
0x0, 0x5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf4, 0x0, 0x0, 0x3, 0xff, 0xff, 0xff, 0x95,
0x21, 0x0, 0x13, 0x10, 0x0, 0x0, 0xef, 0xff,
0xf9, 0x10, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0xff, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xe, 0xff, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x3, 0xff, 0xff, 0x50,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8f,
0xff, 0xf0, 0x1, 0x8c, 0xde, 0xda, 0x50, 0x0,
0x0, 0xc, 0xff, 0xfc, 0x7, 0xff, 0xff, 0xff,
0xff, 0xc2, 0x0, 0x0, 0xff, 0xff, 0x98, 0xff,
0xff, 0xff, 0xff, 0xff, 0xe3, 0x0, 0x1f, 0xff,
0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd0,
0x2, 0xff, 0xff, 0xff, 0xf8, 0x30, 0x3, 0xbf,
0xff, 0xff, 0x70, 0x3f, 0xff, 0xff, 0xf3, 0x0,
0x0, 0x0, 0xaf, 0xff, 0xfd, 0x4, 0xff, 0xff,
0xf6, 0x0, 0x0, 0x0, 0x1, 0xff, 0xff, 0xf1,
0x4f, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0xc,
0xff, 0xff, 0x33, 0xff, 0xff, 0xd0, 0x0, 0x0,
0x0, 0x0, 0xaf, 0xff, 0xf5, 0x1f, 0xff, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xff, 0x50,
0xff, 0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0xbf,
0xff, 0xf4, 0xb, 0xff, 0xff, 0x60, 0x0, 0x0,
0x0, 0xe, 0xff, 0xff, 0x20, 0x7f, 0xff, 0xfd,
0x0, 0x0, 0x0, 0x4, 0xff, 0xff, 0xe0, 0x1,
0xff, 0xff, 0xfa, 0x0, 0x0, 0x1, 0xdf, 0xff,
0xf9, 0x0, 0x7, 0xff, 0xff, 0xfc, 0x52, 0x26,
0xef, 0xff, 0xff, 0x20, 0x0, 0xc, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0x70, 0x0, 0x0,
0x1c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x90,
0x0, 0x0, 0x0, 0x8, 0xff, 0xff, 0xff, 0xff,
0xfe, 0x60, 0x0, 0x0, 0x0, 0x0, 0x1, 0x7b,
0xef, 0xfd, 0xb6, 0x0, 0x0, 0x0,
/* U+0037 "7" */
0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x5b, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xf5, 0xbf, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x5b,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xf3, 0x35, 0x55, 0x55, 0x55, 0x55, 0x55,
0x55, 0x7f, 0xff, 0xfc, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xa, 0xff, 0xff, 0x50, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff, 0xff,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x9f, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xff, 0xff, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xff, 0x80,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xef,
0xff, 0xf1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0xff, 0xfa, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xe, 0xff, 0xff, 0x30, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0xff, 0xff,
0xb0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xdf, 0xff, 0xf4, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x5f, 0xff, 0xfd, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xc, 0xff, 0xff, 0x60,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0xff,
0xff, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xbf, 0xff, 0xf7, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, 0x10, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xa, 0xff, 0xff,
0x90, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
0xff, 0xff, 0xf2, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x9f, 0xff, 0xfa, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1f, 0xff, 0xff, 0x30,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0xff,
0xff, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1, 0xff, 0xff, 0xf5, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x8f, 0xff, 0xfd, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xe, 0xff, 0xff,
0x60, 0x0, 0x0, 0x0, 0x0, 0x0,
/* U+0038 "8" */
0x0, 0x0, 0x1, 0x6a, 0xde, 0xfe, 0xda, 0x50,
0x0, 0x0, 0x0, 0x0, 0x8, 0xff, 0xff, 0xff,
0xff, 0xff, 0xe7, 0x0, 0x0, 0x0, 0xb, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x0, 0x0,
0xa, 0xff, 0xff, 0xff, 0xed, 0xef, 0xff, 0xff,
0xf8, 0x0, 0x2, 0xff, 0xff, 0xfa, 0x20, 0x0,
0x3c, 0xff, 0xff, 0xf0, 0x0, 0x6f, 0xff, 0xfb,
0x0, 0x0, 0x0, 0xd, 0xff, 0xff, 0x30, 0x7,
0xff, 0xff, 0x60, 0x0, 0x0, 0x0, 0x9f, 0xff,
0xf4, 0x0, 0x6f, 0xff, 0xf6, 0x0, 0x0, 0x0,
0x9, 0xff, 0xff, 0x30, 0x3, 0xff, 0xff, 0xb0,
0x0, 0x0, 0x0, 0xdf, 0xff, 0xf0, 0x0, 0xc,
0xff, 0xff, 0x80, 0x0, 0x0, 0x9f, 0xff, 0xf9,
0x0, 0x0, 0x3f, 0xff, 0xff, 0xc3, 0x3, 0xcf,
0xff, 0xfc, 0x0, 0x0, 0x0, 0x3f, 0xff, 0xff,
0xfd, 0xff, 0xff, 0xfb, 0x0, 0x0, 0x0, 0x0,
0x2d, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x0, 0x0,
0x0, 0x0, 0x0, 0x4f, 0xff, 0xff, 0xff, 0xf9,
0x0, 0x0, 0x0, 0x0, 0x1, 0xaf, 0xff, 0xff,
0xff, 0xff, 0xfe, 0x50, 0x0, 0x0, 0x2, 0xef,
0xff, 0xff, 0x98, 0xff, 0xff, 0xff, 0xa0, 0x0,
0x1, 0xef, 0xff, 0xfc, 0x20, 0x1, 0x9f, 0xff,
0xff, 0xa0, 0x0, 0x9f, 0xff, 0xf9, 0x0, 0x0,
0x0, 0x4e, 0xff, 0xff, 0x60, 0x1f, 0xff, 0xfd,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xff, 0xfd, 0x4,
0xff, 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0xbf,
0xff, 0xf1, 0x6f, 0xff, 0xf6, 0x0, 0x0, 0x0,
0x0, 0x9, 0xff, 0xff, 0x35, 0xff, 0xff, 0x90,
0x0, 0x0, 0x0, 0x0, 0xcf, 0xff, 0xf2, 0x2f,
0xff, 0xff, 0x20, 0x0, 0x0, 0x0, 0x4f, 0xff,
0xff, 0x0, 0xdf, 0xff, 0xfe, 0x60, 0x0, 0x1,
0x7f, 0xff, 0xff, 0x90, 0x4, 0xff, 0xff, 0xff,
0xfe, 0xde, 0xff, 0xff, 0xff, 0xe1, 0x0, 0x6,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe1,
0x0, 0x0, 0x4, 0xdf, 0xff, 0xff, 0xff, 0xff,
0xff, 0x81, 0x0, 0x0, 0x0, 0x0, 0x38, 0xbe,
0xff, 0xed, 0xa6, 0x10, 0x0, 0x0,
/* U+0039 "9" */
0x0, 0x0, 0x2, 0x7b, 0xef, 0xfe, 0xb6, 0x10,
0x0, 0x0, 0x0, 0x0, 0x9, 0xff, 0xff, 0xff,
0xff, 0xff, 0x70, 0x0, 0x0, 0x0, 0x1d, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0x0, 0x0,
0xc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xb0, 0x0, 0x6, 0xff, 0xff, 0xfc, 0x63, 0x35,
0xbf, 0xff, 0xff, 0x60, 0x0, 0xdf, 0xff, 0xf9,
0x0, 0x0, 0x0, 0x7f, 0xff, 0xfe, 0x0, 0x2f,
0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0xbf, 0xff,
0xf5, 0x5, 0xff, 0xff, 0xa0, 0x0, 0x0, 0x0,
0x3, 0xff, 0xff, 0xa0, 0x6f, 0xff, 0xf8, 0x0,
0x0, 0x0, 0x0, 0xf, 0xff, 0xfe, 0x6, 0xff,
0xff, 0x90, 0x0, 0x0, 0x0, 0x0, 0xef, 0xff,
0xf0, 0x5f, 0xff, 0xfc, 0x0, 0x0, 0x0, 0x0,
0x2f, 0xff, 0xff, 0x21, 0xff, 0xff, 0xf4, 0x0,
0x0, 0x0, 0x1d, 0xff, 0xff, 0xf2, 0xc, 0xff,
0xff, 0xf6, 0x10, 0x1, 0x6e, 0xff, 0xff, 0xff,
0x20, 0x4f, 0xff, 0xff, 0xff, 0xef, 0xff, 0xff,
0xef, 0xff, 0xf2, 0x0, 0x8f, 0xff, 0xff, 0xff,
0xff, 0xff, 0x8a, 0xff, 0xff, 0x10, 0x0, 0x7f,
0xff, 0xff, 0xff, 0xff, 0x70, 0xcf, 0xff, 0xf0,
0x0, 0x0, 0x17, 0xce, 0xff, 0xd9, 0x30, 0xe,
0xff, 0xfd, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x2, 0xff, 0xff, 0xa0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x6f, 0xff, 0xf6, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xc, 0xff,
0xff, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x5, 0xff, 0xff, 0xc0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x2, 0xef, 0xff, 0xf5, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x4, 0xef, 0xff, 0xfc,
0x0, 0x0, 0x2, 0x42, 0x10, 0x13, 0x7c, 0xff,
0xff, 0xfe, 0x20, 0x0, 0x0, 0x6f, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x40, 0x0, 0x0, 0x6,
0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0x20, 0x0,
0x0, 0x0, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xe7,
0x0, 0x0, 0x0, 0x0, 0x4, 0xde, 0xff, 0xfe,
0xc9, 0x50, 0x0, 0x0, 0x0, 0x0,
/* U+003A ":" */
0x5, 0xdf, 0xd5, 0x3, 0xff, 0xff, 0xf4, 0x8f,
0xff, 0xff, 0x87, 0xff, 0xff, 0xf8, 0x4f, 0xff,
0xff, 0x40, 0x8f, 0xff, 0x80, 0x0, 0x2, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0,
0x8, 0xff, 0xf8, 0x5, 0xff, 0xff, 0xf5, 0x8f,
0xff, 0xff, 0x87, 0xff, 0xff, 0xf8, 0x3f, 0xff,
0xff, 0x30, 0x5d, 0xfd, 0x50
};
/*---------------------
* GLYPH DESCRIPTION
*--------------------*/
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
{.bitmap_index = 0, .adv_w = 166, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 0, .adv_w = 205, .box_w = 11, .box_h = 5, .ofs_x = 1, .ofs_y = 9},
{.bitmap_index = 28, .adv_w = 366, .box_w = 21, .box_h = 28, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 322, .adv_w = 366, .box_w = 14, .box_h = 28, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 518, .adv_w = 366, .box_w = 21, .box_h = 28, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 812, .adv_w = 366, .box_w = 21, .box_h = 28, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 1106, .adv_w = 366, .box_w = 23, .box_h = 28, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 1428, .adv_w = 366, .box_w = 19, .box_h = 28, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 1694, .adv_w = 366, .box_w = 21, .box_h = 28, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 1988, .adv_w = 366, .box_w = 21, .box_h = 28, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 2282, .adv_w = 366, .box_w = 21, .box_h = 28, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 2576, .adv_w = 366, .box_w = 21, .box_h = 28, .ofs_x = 1, .ofs_y = 0},
{.bitmap_index = 2870, .adv_w = 177, .box_w = 7, .box_h = 22, .ofs_x = 2, .ofs_y = 0}
};
/*---------------------
* CHARACTER MAPPING
*--------------------*/
static const uint16_t unicode_list_0[] = {
0x0, 0xd
};
/*Collect the unicode lists and glyph_id offsets*/
static const lv_font_fmt_txt_cmap_t cmaps[] =
{
{
.range_start = 32, .range_length = 14, .glyph_id_start = 1,
.unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 2, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY
},
{
.range_start = 48, .range_length = 11, .glyph_id_start = 3,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
}
};
/*--------------------
* ALL CUSTOM DATA
*--------------------*/
#if LVGL_VERSION_MAJOR == 8
/*Store all the custom data of the font*/
static lv_font_fmt_txt_glyph_cache_t cache;
#endif
#if LVGL_VERSION_MAJOR >= 8
static const lv_font_fmt_txt_dsc_t font_dsc = {
#else
static lv_font_fmt_txt_dsc_t font_dsc = {
#endif
.glyph_bitmap = glyph_bitmap,
.glyph_dsc = glyph_dsc,
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 2,
.bpp = 4,
.kern_classes = 0,
.bitmap_format = 0,
#if LVGL_VERSION_MAJOR == 8
.cache = &cache
#endif
};
/*-----------------
* PUBLIC FONT
*----------------*/
/*Initialize a public general font descriptor*/
#if LVGL_VERSION_MAJOR >= 8
const lv_font_t ui_font_40 = {
#else
lv_font_t ui_font_40 = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
.line_height = 28, /*The maximum line height required by the font*/
.base_line = 0, /*Baseline measured from the bottom of the line*/
#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
.subpx = LV_FONT_SUBPX_NONE,
#endif
#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
.underline_position = -4,
.underline_thickness = 2,
#endif
.dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
.fallback = NULL,
#endif
.user_data = NULL,
};
#endif /*#if UI_FONT_40*/
+8 -8
View File
@@ -26,14 +26,14 @@
#define LV_USE_ASSERT_NULL 1 #define LV_USE_ASSERT_NULL 1
#define LV_USE_ASSERT_MALLOC 1 #define LV_USE_ASSERT_MALLOC 1
// Fonts. Montserrat is ASCII + a few symbols only -- Polish / Cyrillic // Fonts: generated Noto Sans (European Latin, Greek, Cyrillic + LV_SYMBOL_*),
// coverage needs a generated font (lv_font_conv), planned with the theme work. // see fonts/generate.sh. LVGL's built-in Montserrat (ASCII only) is off.
#define LV_FONT_MONTSERRAT_12 1 #define LV_FONT_MONTSERRAT_14 0
#define LV_FONT_MONTSERRAT_14 1 #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(ui_font_12) LV_FONT_DECLARE(ui_font_14) \
#define LV_FONT_MONTSERRAT_16 1 LV_FONT_DECLARE(ui_font_16) LV_FONT_DECLARE(ui_font_20) \
#define LV_FONT_MONTSERRAT_20 1 LV_FONT_DECLARE(ui_font_40)
#define LV_FONT_MONTSERRAT_40 1 #define LV_FONT_DEFAULT &ui_font_14
#define LV_FONT_DEFAULT &lv_font_montserrat_14 #define LV_USE_FONT_COMPRESSED 1 // generated fonts are RLE-compressed (without this every glyph draws as a box)
#define LV_USE_THEME_DEFAULT 1 #define LV_USE_THEME_DEFAULT 1
#define LV_THEME_DEFAULT_DARK 1 #define LV_THEME_DEFAULT_DARK 1
@@ -5,6 +5,7 @@
#include "PopupMenu.h" #include "PopupMenu.h"
#include "icons.h" // mini-icons for the special-key row (⇧ ⌫ ⎵ ✓) #include "icons.h" // mini-icons for the special-key row (⇧ ⌫ ⎵ ✓)
#include "../NodePrefs.h" #include "../NodePrefs.h"
#include "../ui-core/KeyboardData.h" // shared long-press variants + case table
// Layout constants shared by all keyboard users. // Layout constants shared by all keyboard users.
// Two pages: letters (page 0) and symbols (page 1), toggled by the "#@"/"abc" // Two pages: letters (page 0) and symbols (page 1), toggled by the "#@"/"abc"
@@ -147,30 +148,12 @@ static const int KB_PREVIEW_BYTES = KB_PREVIEW_CAP * 2;
static void kbApplyCapsUtf8(const char* in, bool caps, char* out, size_t out_size) { static void kbApplyCapsUtf8(const char* in, bool caps, char* out, size_t out_size) {
size_t o = 0; size_t o = 0;
const uint8_t* p = (const uint8_t*)in; const uint8_t* p = (const uint8_t*)in;
while (*p && o + 2 < out_size) { while (*p && o + 4 < out_size) {
uint32_t cp = DisplayDriver::decodeCodepoint(p); uint32_t cp = DisplayDriver::decodeCodepoint(p);
if (caps) { // Shared case table (ui-core/KeyboardData.h). ß stays ß here: its
if (cp == 0x0451) cp = 0x0401; // ё -> Ё // uppercase ẞ (U+1E9E) is outside the misc-fixed font's U+0020-04FF.
else if (cp == 0x03C2) cp = 0x03A3; // ς -> Σ if (caps && cp != 0x00DF) cp = kbd::toUpper(cp);
else if (cp == 0x00FF) cp = 0x0178; // ÿ -> Ÿ (French; breaks the à-þ flat -0x20 rule below — Ÿ sits outside Latin-1 Supplement entirely) o += kbd::encode(cp, out + o);
else if (cp >= 0x0430 && cp <= 0x044F) cp -= 0x20; // а-я -> А-Я
else if (cp >= 0x03B1 && cp <= 0x03C9) cp -= 0x20; // α-ω -> Α-Ω
else if (cp >= 0x00E0 && cp <= 0x00FE && cp != 0x00F7) cp -= 0x20; // à-þ -> À-Þ
// ą-ż (Latin Extended-A): pairing parity flips around the unpaired
// codepoints ĸ (U+0138), ʼn (U+0149), Ÿ (U+0178) -- verified exhaustively
// over the whole U+0100-U+017F block, resolves ł ń ź ż + ĺ ľ ň ž too.
else if (cp >= 0x0100 && cp <= 0x0137 && (cp & 1) == 1) cp -= 1; // odd=lower
else if (cp >= 0x0139 && cp <= 0x0148 && (cp & 1) == 0) cp -= 1; // even=lower
else if (cp >= 0x014A && cp <= 0x0177 && (cp & 1) == 1) cp -= 1; // odd=lower
else if (cp >= 0x0179 && cp <= 0x017E && (cp & 1) == 0) cp -= 1; // even=lower
else if (cp >= 'a' && cp <= 'z') cp -= 0x20; // a-z -> A-Z
}
if (cp < 0x80) {
out[o++] = (char)cp;
} else {
out[o++] = (char)(0xC0 | (cp >> 6));
out[o++] = (char)(0x80 | (cp & 0x3F));
}
} }
out[o] = '\0'; out[o] = '\0';
} }
@@ -239,28 +222,9 @@ static int kbUtf8CharBytesAt(const char* buf, int pos, int len) {
// Ligature/non-diacritic letters are filed under their conventional key, same // Ligature/non-diacritic letters are filed under their conventional key, same
// as a phone keyboard's long-press: ß (German) -> s, œ (French) -> o. ĺ/ŕ // as a phone keyboard's long-press: ß (German) -> s, œ (French) -> o. ĺ/ŕ
// (Slovak) are l/r with an acute, not i/e variants, so they're filed there. // (Slovak) are l/r with an acute, not i/e variants, so they're filed there.
static const char KB_ACCENT_BASES[] = "acdeilnorstuyz"; // The table itself is shared with every frontend (ui-core/KeyboardData.h).
static const char* const KB_ACCENT_VARIANTS[] = { static const char* const* const KB_ACCENT_VARIANTS = kbd::LATIN_VARIANTS;
"áàâãäåą", // a static int findAccentGroup(char base) { return kbd::latinGroup(base); }
"çćč", // c
"ď", // d
"éèêëěę", // e
"íîï", // i
"łĺľ", // l
"ñńň", // n
"óòôõöøœ", // o
"řŕ", // r
"śšß", // s
"ť", // t
"úùûüů", // u
"ýÿ", // y
"źżž", // z
};
static const int KB_ACCENT_COUNT = sizeof(KB_ACCENT_BASES) - 1; // exclude the trailing NUL
static int findAccentGroup(char base) {
for (int i = 0; i < KB_ACCENT_COUNT; i++) if (KB_ACCENT_BASES[i] == base) return i;
return -1;
}
static const int KB_PH_MAX = 20; // max placeholders in list (PopupMenu::PM_MAX_ITEMS=24 is the hard ceiling) static const int KB_PH_MAX = 20; // max placeholders in list (PopupMenu::PM_MAX_ITEMS=24 is the hard ceiling)
static const int KB_PH_LEN = 30; // max placeholder string length incl. null -- sized for the longest static const int KB_PH_LEN = 30; // max placeholder string length incl. null -- sized for the longest
+1
View File
@@ -60,6 +60,7 @@ build_src_filter = ${Wio_Tracker_L2.build_src_filter}
+<helpers/ui/MomentaryButton.cpp> +<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp> +<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-lvgl/*.cpp> +<../examples/companion_radio/ui-lvgl/*.cpp>
+<../examples/companion_radio/ui-lvgl/fonts/*.c>
lib_deps = lib_deps =
${Wio_Tracker_L2.lib_deps} ${Wio_Tracker_L2.lib_deps}
densaugeo/base64 @ ~1.4.0 densaugeo/base64 @ ~1.4.0