perf(ui): replace transliterateCodepoint switch with sorted-table binary search

Old 80+ case switch generated a jump table around ~600-1200 B of flash;
new layout is two parallel static-const arrays (uint16 codepoint + char
ascii, 144 entries each = 432 B) and a 5-line bsearch loop. Same input
range, same output, faster lookup (~log2(144) ≈ 7 compares vs jump-table
indirection + bounds check).

Adds a static_assert that the two arrays stay in sync.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-05-24 20:14:01 +02:00
parent 71e1cb7a23
commit f82d23b200

View File

@@ -70,87 +70,63 @@ public:
print(tmp);
}
// Sorted-codepoint → ASCII transliteration table.
// Binary search beats a ~110-case switch on flash (parallel arrays = 3 bytes/entry,
// no padding) and gives O(log n) lookup. Covers Latin Extended A/B + common
// Latin-1 accented diacritics needed by EU languages and Turkish.
static char transliterateCodepoint(uint32_t cp) {
switch (cp) {
// Polish
case 0x0105: return 'a'; case 0x0104: return 'A'; // ą Ą
case 0x0107: return 'c'; case 0x0106: return 'C'; // ć Ć
case 0x0119: return 'e'; case 0x0118: return 'E'; // ę Ę
case 0x0142: return 'l'; case 0x0141: return 'L'; // ł Ł
case 0x0144: return 'n'; case 0x0143: return 'N'; // ń Ń
case 0x00F3: return 'o'; case 0x00D3: return 'O'; // ó Ó
case 0x015B: return 's'; case 0x015A: return 'S'; // ś Ś
case 0x017A: return 'z'; case 0x0179: return 'Z'; // ź Ź
case 0x017C: return 'z'; case 0x017B: return 'Z'; // ż Ż
// Czech/Slovak
case 0x010D: return 'c'; case 0x010C: return 'C'; // č Č
case 0x0161: return 's'; case 0x0160: return 'S'; // š Š
case 0x017E: return 'z'; case 0x017D: return 'Z'; // ž Ž
case 0x0159: return 'r'; case 0x0158: return 'R'; // ř Ř
case 0x011B: return 'e'; case 0x011A: return 'E'; // ě Ě
case 0x016F: return 'u'; case 0x016E: return 'U'; // ů Ů
case 0x010F: return 'd'; case 0x010E: return 'D'; // ď Ď
case 0x0165: return 't'; case 0x0164: return 'T'; // ť Ť
case 0x0148: return 'n'; case 0x0147: return 'N'; // ň Ň
case 0x013E: return 'l'; case 0x013D: return 'L'; // ľ Ľ
case 0x013A: return 'l'; case 0x0139: return 'L'; // ĺ Ĺ
case 0x0155: return 'r'; case 0x0154: return 'R'; // ŕ Ŕ
// German
case 0x00E4: return 'a'; case 0x00C4: return 'A'; // ä Ä
case 0x00F6: return 'o'; case 0x00D6: return 'O'; // ö Ö
case 0x00FC: return 'u'; case 0x00DC: return 'U'; // ü Ü
case 0x00DF: return 's'; // ß
// Scandinavian
case 0x00E5: return 'a'; case 0x00C5: return 'A'; // å Å
case 0x00F8: return 'o'; case 0x00D8: return 'O'; // ø Ø
case 0x00E6: return 'a'; case 0x00C6: return 'A'; // æ Æ
// Hungarian
case 0x0151: return 'o'; case 0x0150: return 'O'; // ő Ő
case 0x0171: return 'u'; case 0x0170: return 'U'; // ű Ű
// Romanian
case 0x0103: return 'a'; case 0x0102: return 'A'; // ă Ă
case 0x0219: return 's'; case 0x0218: return 'S'; // ș Ș
case 0x021B: return 't'; case 0x021A: return 'T'; // ț Ț
// Croatian
case 0x0111: return 'd'; case 0x0110: return 'D'; // đ Đ
// Icelandic
case 0x00F0: return 'd'; case 0x00D0: return 'D'; // ð Ð (eth)
case 0x00FE: return 't'; case 0x00DE: return 'T'; // þ Þ (thorn)
// Romanian cedilla variants (legacy encoding)
case 0x0163: return 't'; case 0x0162: return 'T'; // ţ Ţ
// Turkish
case 0x011F: return 'g'; case 0x011E: return 'G'; // ğ Ğ
case 0x015F: return 's'; case 0x015E: return 'S'; // ş Ş
case 0x0131: return 'i'; // ı
// Baltic (Lithuanian/Latvian)
case 0x0101: return 'a'; case 0x0100: return 'A'; // ā Ā
case 0x0113: return 'e'; case 0x0112: return 'E'; // ē Ē
case 0x012B: return 'i'; case 0x012A: return 'I'; // ī Ī
case 0x016B: return 'u'; case 0x016A: return 'U'; // ū Ū
case 0x0123: return 'g'; case 0x0122: return 'G'; // ģ Ģ
case 0x0137: return 'k'; case 0x0136: return 'K'; // ķ Ķ
case 0x013C: return 'l'; case 0x013B: return 'L'; // ļ Ļ
case 0x0146: return 'n'; case 0x0145: return 'N'; // ņ Ņ
case 0x0157: return 'r'; case 0x0156: return 'R'; // ŗ Ŗ
case 0x0173: return 'u'; case 0x0172: return 'U'; // ų Ų
case 0x0117: return 'e'; case 0x0116: return 'E'; // ė Ė
case 0x012F: return 'i'; case 0x012E: return 'I'; // į Į
// French/Spanish/Portuguese common accents
case 0x00E0: case 0x00E1: case 0x00E2: case 0x00E3: return 'a';
case 0x00C0: case 0x00C1: case 0x00C2: case 0x00C3: return 'A';
case 0x00E8: case 0x00E9: case 0x00EA: case 0x00EB: return 'e';
case 0x00C8: case 0x00C9: case 0x00CA: case 0x00CB: return 'E';
case 0x00EC: case 0x00ED: case 0x00EE: case 0x00EF: return 'i';
case 0x00CC: case 0x00CD: case 0x00CE: case 0x00CF: return 'I';
case 0x00F2: case 0x00F4: case 0x00F5: return 'o';
case 0x00D2: case 0x00D4: case 0x00D5: return 'O';
case 0x00F9: case 0x00FA: case 0x00FB: return 'u';
case 0x00D9: case 0x00DA: case 0x00DB: return 'U';
case 0x00E7: return 'c'; case 0x00C7: return 'C'; // ç Ç
case 0x00F1: return 'n'; case 0x00D1: return 'N'; // ñ Ñ
case 0x00FD: return 'y'; case 0x00DD: return 'Y'; // ý Ý
default: return '\xDB';
// Codepoints must stay sorted ascending — binary search assumes it.
static const uint16_t CPS[] = {
0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7,
0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF,
0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D8,
0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF,
0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7,
0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF,
0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F8,
0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE,
0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107,
0x010C, 0x010D, 0x010E, 0x010F, 0x0110, 0x0111, 0x0112, 0x0113,
0x0116, 0x0117, 0x0118, 0x0119, 0x011A, 0x011B, 0x011E, 0x011F,
0x0122, 0x0123, 0x012A, 0x012B, 0x012E, 0x012F, 0x0131,
0x0136, 0x0137, 0x0139, 0x013A, 0x013B, 0x013C, 0x013D, 0x013E,
0x0141, 0x0142, 0x0143, 0x0144, 0x0145, 0x0146, 0x0147, 0x0148,
0x0150, 0x0151, 0x0154, 0x0155, 0x0156, 0x0157, 0x0158, 0x0159,
0x015A, 0x015B, 0x015E, 0x015F, 0x0160, 0x0161, 0x0162, 0x0163,
0x0164, 0x0165, 0x016A, 0x016B, 0x016E, 0x016F, 0x0170, 0x0171,
0x0172, 0x0173, 0x0179, 0x017A, 0x017B, 0x017C, 0x017D, 0x017E,
0x0218, 0x0219, 0x021A, 0x021B
};
static const char ASCII[] = {
'A','A','A','A','A','A','A','C',
'E','E','E','E','I','I','I','I',
'D','N','O','O','O','O','O','O',
'U','U','U','U','Y','T','s',
'a','a','a','a','a','a','a','c',
'e','e','e','e','i','i','i','i',
'd','n','o','o','o','o','o','o',
'u','u','u','u','y','t',
'A','a','A','a','A','a','C','c',
'C','c','D','d','D','d','E','e',
'E','e','E','e','E','e','G','g',
'G','g','I','i','I','i','i',
'K','k','L','l','L','l','L','l',
'L','l','N','n','N','n','N','n',
'O','o','R','r','R','r','R','r',
'S','s','S','s','S','s','T','t',
'T','t','U','u','U','u','U','u',
'U','u','Z','z','Z','z','Z','z',
'S','s','T','t'
};
static_assert(sizeof(CPS) / sizeof(CPS[0]) == sizeof(ASCII), "transliteration tables out of sync");
int lo = 0, hi = (int)(sizeof(ASCII)) - 1;
while (lo <= hi) {
int mid = (lo + hi) >> 1;
uint16_t v = CPS[mid];
if (v == cp) return ASCII[mid];
if (v < cp) lo = mid + 1; else hi = mid - 1;
}
return '\xDB';
}
// convert UTF-8 to ASCII, transliterating accented/diacritic characters (callable without a display instance)