diff --git a/Translations/make_translation.py b/Translations/make_translation.py index 81290746..acbd9c1c 100755 --- a/Translations/make_translation.py +++ b/Translations/make_translation.py @@ -233,6 +233,55 @@ def getCJKGlyph(sym): return s +def getCharsFromFontIndex(index: int) -> str: + ''' + Converts the font table index into its corresponding string escape + sequence(s). + ''' + + # We want to be able to use more than 254 symbols (excluding \x00 null + # terminator and \x01 new-line) in the font table but without making all + # the chars take 2 bytes. To do this, we use \xF1 to \xFF as lead bytes + # to designate double-byte chars, and leave the remaining as single-byte + # chars. + # + # For the sake of sanity, \x00 always means the end of string, so we skip + # \xF1\x00 and others in the mapping. + # + # Mapping example: + # + # 0x02 => 2 + # 0x03 => 3 + # ... + # 0xEF => 239 + # 0xF0 => 240 + # 0xF1 0x01 => 1 * 0xFF - 15 + 1 = 241 + # 0xF1 0x02 => 1 * 0xFF - 15 + 2 = 242 + # ... + # 0xF1 0xFF => 1 * 0xFF - 15 + 255 = 495 + # 0xF2 0x01 => 2 * 0xFF - 15 + 1 = 496 + # ... + # 0xF2 0xFF => 2 * 0xFF - 15 + 255 = 750 + # 0xF3 0x01 => 3 * 0xFF - 15 + 1 = 751 + # ... + # 0xFF 0xFF => 15 * 0xFF - 15 + 255 = 4065 + + assert index >= 0 + page = int((index + 14) / 0xFF) + assert page <= 0x0F + if page == 0: + return "\\x%0.2X" % index + else: + # Into extended range + # Leader is 0xFz where z is the page number + # Following char is the remainder + leader = page + 0xF0 + value = ((index + 14) % 0xFF) + 1 + assert leader <= 0xFF + assert value <= 0xFF + return "\\x%0.2X\\x%0.2X" % (leader, value) + + def getFontMapAndTable(textList): # the text list is sorted # allocate out these in their order as number codes @@ -242,19 +291,20 @@ def getFontMapAndTable(textList): forcedFirstSymbols = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] # enforce numbers are first for sym in forcedFirstSymbols: - symbolMap[sym] = "\\x%0.2X" % index + symbolMap[sym] = getCharsFromFontIndex(index) index = index + 1 totalSymbolCount = len(set(textList) | set(forcedFirstSymbols)) # \x00 is for NULL termination and \x01 is for newline, so the maximum - # number of symbols allowed with 8 bits is `256 - 2`. - if totalSymbolCount > (256 - 2): + # number of symbols allowed is as follow (see also the comments in + # `getCharsFromFontIndex`): + if totalSymbolCount > (0x10 * 0xFF - 15) - 2: log(f"Error, too many used symbols for this version (total {totalSymbolCount})") sys.exit(1) log("Generating fonts for {} symbols".format(totalSymbolCount)) for sym in textList: if sym not in symbolMap: - symbolMap[sym] = "\\x%0.2X" % index + symbolMap[sym] = getCharsFromFontIndex(index) index = index + 1 # Get the font table fontTableStrings = [] @@ -316,6 +366,7 @@ def convStr(symbolConversionTable, text): for c in text.replace("\\r", "").replace("\\n", "\n"): if c not in symbolConversionTable: log("Missing font definition for {}".format(c)) + sys.exit(1) else: outputString = outputString + symbolConversionTable[c] return outputString diff --git a/Translations/make_translation_test.py b/Translations/make_translation_test.py new file mode 100644 index 00000000..5cae449a --- /dev/null +++ b/Translations/make_translation_test.py @@ -0,0 +1,21 @@ +import unittest + + +class TestMakeTranslation(unittest.TestCase): + def test_getCharsFromFontIndex(self): + from make_translation import getCharsFromFontIndex + self.assertEqual(getCharsFromFontIndex(2), "\\x02") + self.assertEqual(getCharsFromFontIndex(239), "\\xEF") + self.assertEqual(getCharsFromFontIndex(240), "\\xF0") + self.assertEqual(getCharsFromFontIndex(241), "\\xF1\\x01") + self.assertEqual(getCharsFromFontIndex(495), "\\xF1\\xFF") + self.assertEqual(getCharsFromFontIndex(496), "\\xF2\\x01") + self.assertEqual(getCharsFromFontIndex(750), "\\xF2\\xFF") + self.assertEqual(getCharsFromFontIndex(751), "\\xF3\\x01") + self.assertEqual(getCharsFromFontIndex(0x10 * 0xFF - 15), "\\xFF\\xFF") + with self.assertRaises(AssertionError): + getCharsFromFontIndex(0x10 * 0xFF - 14) + + +if __name__ == '__main__': + unittest.main() diff --git a/source/Core/Drivers/OLED.cpp b/source/Core/Drivers/OLED.cpp index 18b9d0fe..752f7cf4 100644 --- a/source/Core/Drivers/OLED.cpp +++ b/source/Core/Drivers/OLED.cpp @@ -117,15 +117,16 @@ void OLED::setFramebuffer(uint8_t *buffer) { * UTF font handling is done using the two input chars. * Precursor is the command char that is used to select the table. */ -void OLED::drawChar(char c) { - if (c == '\x01' && cursor_y == 0) { // 0x01 is used as new line char +void OLED::drawChar(const uint16_t charCode) { + if (charCode == '\x01' && cursor_y == 0) { // 0x01 is used as new line char cursor_x = 0; cursor_y = 8; return; - } else if (c == 0) { + } else if (charCode <= 0x01) { return; } - uint16_t index = static_cast(c) - 2; // First index is \x02 + // First index is \x02 + uint16_t index = charCode - 2; uint8_t *charPointer; charPointer = ((uint8_t *)currentFont) + ((fontWidth * (fontHeight / 8)) * index); drawArea(cursor_x, cursor_y, fontWidth, fontHeight, charPointer); @@ -234,10 +235,21 @@ void OLED::setRotation(bool leftHanded) { } // print a string to the current cursor location -void OLED::print(const char *str) { - while (str[0]) { - drawChar(str[0]); - str++; +void OLED::print(const char *const str) { + const uint8_t *next = reinterpret_cast(str); + while (next[0]) { + uint16_t index; + if (next[0] <= 0xF0) { + index = next[0]; + next++; + } else { + if (!next[1]) { + return; + } + index = (next[0] - 0xF0) * 0xFF - 15 + next[1]; + next += 2; + } + drawChar(index); } } diff --git a/source/Core/Drivers/OLED.hpp b/source/Core/Drivers/OLED.hpp index 4e7af19f..cacf4230 100644 --- a/source/Core/Drivers/OLED.hpp +++ b/source/Core/Drivers/OLED.hpp @@ -83,7 +83,7 @@ public: static void useSecondaryFramebuffer(bool useSecondary); private: - static void drawChar(char c); // Draw a character to a specific location + static void drawChar(const uint16_t charCode); // Draw a character to the current cursor location static void setFramebuffer(uint8_t *buffer); static const uint8_t *currentFont; // Pointer to the current font used for rendering to the buffer static uint8_t * firstStripPtr; // Pointers to the strips to allow for buffer having extra content diff --git a/source/Core/Src/gui.cpp b/source/Core/Src/gui.cpp index ceb554e4..d1a88271 100644 --- a/source/Core/Src/gui.cpp +++ b/source/Core/Src/gui.cpp @@ -12,7 +12,6 @@ #include "Translation.h" #include "cmsis_os.h" #include "main.hpp" -#include "string.h" void gui_Menu(const menuitem *menu); @@ -274,8 +273,33 @@ static void printShortDescription(uint32_t shortDescIndex, uint16_t cursorCharPo OLED::setCursor(OLED::getCursorX() - 2, 0); } +/** + * Counts the number of chars in the string excluding the null terminator. + * This is a custom version of `strlen` which takes into account our custom + * double-byte char encoding. + * @param str The input string. + * @return The length of the string. + */ +static uint16_t str_display_len(const char *const str) { + const uint8_t *next = reinterpret_cast(str); + uint16_t count = 0; + while (next[0]) { + if (next[0] <= 0xF0) { + count++; + next++; + } else { + if (!next[1]) { + break; + } + count++; + next += 2; + } + } + return count; +} + static int userConfirmation(const char *message) { - uint16_t messageWidth = FONT_12_WIDTH * (strlen(message) + 7); + uint16_t messageWidth = FONT_12_WIDTH * (str_display_len(message) + 7); uint32_t messageStart = xTaskGetTickCount(); OLED::setFont(0); @@ -1144,7 +1168,7 @@ void gui_Menu(const menuitem *menu) { if (descriptionStart == 0) descriptionStart = xTaskGetTickCount(); // lower the value - higher the speed - int16_t descriptionWidth = FONT_12_WIDTH * (strlen(menu[currentScreen].description) + 7); + int16_t descriptionWidth = FONT_12_WIDTH * (str_display_len(menu[currentScreen].description) + 7); int16_t descriptionOffset = ((xTaskGetTickCount() - descriptionStart) / (systemSettings.descriptionScrollSpeed == 1 ? (TICKS_100MS / 10) : (TICKS_100MS / 5))); descriptionOffset %= descriptionWidth; // Roll around at the end if (lastOffset != descriptionOffset) { diff --git a/source/build.sh b/source/build.sh index f8d30751..27c4f974 100644 --- a/source/build.sh +++ b/source/build.sh @@ -1,5 +1,6 @@ #!/bin/bash set -e + TRANSLATION_DIR="../Translations" TRANSLATION_SCRIPT="make_translation.py"