diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index a843e9e5..081a9b12 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -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)