From e6eb3e34bc7904e2c21cdaadcd5e84286401d9a5 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Tue, 16 Mar 2021 20:34:47 +1100 Subject: [PATCH 1/8] Expand drawChar for larger offset --- source/Core/Drivers/OLED.cpp | 9 +++++---- source/Core/Drivers/OLED.hpp | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/source/Core/Drivers/OLED.cpp b/source/Core/Drivers/OLED.cpp index 18b9d0fe..df06b3b1 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); 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 From 3858ac4a162ab166c96580f413ec4e0a637b3994 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Tue, 16 Mar 2021 20:42:47 +1100 Subject: [PATCH 2/8] Rough pass expanding print --- source/Core/Drivers/OLED.cpp | 15 ++++++++++++--- source/build.sh | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/source/Core/Drivers/OLED.cpp b/source/Core/Drivers/OLED.cpp index df06b3b1..9bd49e13 100644 --- a/source/Core/Drivers/OLED.cpp +++ b/source/Core/Drivers/OLED.cpp @@ -122,11 +122,11 @@ void OLED::drawChar(const uint16_t charCode) { cursor_x = 0; cursor_y = 8; return; - } else if (charCode <=0x01) { + } else if (charCode <= 0x01) { return; } // First index is \x02 - uint16_t index = charCode-2; + uint16_t index = charCode - 2; uint8_t *charPointer; charPointer = ((uint8_t *)currentFont) + ((fontWidth * (fontHeight / 8)) * index); drawArea(cursor_x, cursor_y, fontWidth, fontHeight, charPointer); @@ -236,8 +236,17 @@ void OLED::setRotation(bool leftHanded) { // print a string to the current cursor location void OLED::print(const char *str) { + uint16_t cache = 0; while (str[0]) { - drawChar(str[0]); + if (str[0] >= 0xF0) { + cache = static_cast(str[0] & 0x0F) << 8; + } else if (cache != 0) { + cache |= str[0]; + drawChar(cache); + cache = 0; + } else { + drawChar(str[0]); + } str++; } } diff --git a/source/build.sh b/source/build.sh index e07e102f..eaa2a1c2 100644 --- a/source/build.sh +++ b/source/build.sh @@ -1,4 +1,5 @@ #!/bin/bash +set -x TRANSLATION_DIR="../Translations" TRANSLATION_SCRIPT="make_translation.py" From 483581b3f2f4e173ff38bfe1735c3a2ff503eaa7 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Tue, 16 Mar 2021 20:45:02 +1100 Subject: [PATCH 3/8] Make translations exit if symbol missing --- Translations/make_translation.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/Translations/make_translation.py b/Translations/make_translation.py index 38171344..cefc7ba2 100755 --- a/Translations/make_translation.py +++ b/Translations/make_translation.py @@ -23,6 +23,7 @@ except NameError: @functools.lru_cache(maxsize=None) def cjkFont(): from bdflib import reader as bdfreader + with open(os.path.join(HERE, "wqy-bitmapsong/wenquanyi_9pt.bdf"), "rb") as f: return bdfreader.read_bdf(f) @@ -43,7 +44,7 @@ def loadJson(fileName, skipFirstLine): def readTranslation(jsonDir, langCode): - fileName = 'translation_{}.json'.format(langCode) + fileName = "translation_{}.json".format(langCode) fileWithPath = os.path.join(jsonDir, fileName) @@ -184,8 +185,10 @@ def getLetterCounts(defs, lang): symbolCounts.reverse() return symbolCounts + def getCJKGlyph(sym): from bdflib.model import Glyph + try: glyph: Glyph = cjkFont()[ord(sym)] except: @@ -213,6 +216,7 @@ def getCJKGlyph(sym): return True else: return False + # A glyph in the font table is divided into upper and lower parts, each by # 8px high. Each byte represents half if a column, with the LSB being the # top-most pixel. The data goes from the left-most to the right-most column @@ -228,6 +232,7 @@ def getCJKGlyph(sym): s += f"0x{b:02X}," return s + def getFontMapAndTable(textList): # the text list is sorted # allocate out these in their order as number codes @@ -311,13 +316,14 @@ 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 def writeLanguage(lang, defs, f): - languageCode = lang['languageCode'] + languageCode = lang["languageCode"] log("Generating block for " + languageCode) # Iterate over all of the text to build up the symbols & counts textList = getLetterCounts(defs, lang) @@ -516,10 +522,11 @@ def writeLanguage(lang, defs, f): ) f.write(to_unicode("};\n\n")) - f.write("const bool HasFahrenheit = " + ( - "true" if lang.get('tempUnitFahrenheit', True) else "false") + - ";\n") - + f.write( + "const bool HasFahrenheit = " + + ("true" if lang.get("tempUnitFahrenheit", True) else "false") + + ";\n" + ) def readVersion(jsonDir): @@ -567,9 +574,9 @@ def orderOutput(langDict): def parseArgs(): parser = argparse.ArgumentParser() parser.add_argument( - '--output', '-o', - help='Target file', type=argparse.FileType('w'), required=True) - parser.add_argument('languageCode', help='Language to generate') + "--output", "-o", help="Target file", type=argparse.FileType("w"), required=True + ) + parser.add_argument("languageCode", help="Language to generate") return parser.parse_args() From c07b621daf6ca58c5eedb8dadff3f15d28b29660 Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Tue, 16 Mar 2021 21:24:22 +1100 Subject: [PATCH 4/8] Rough pass --- Translations/make_translation.py | 16 +++++++++++++--- source/Core/Drivers/OLED.cpp | 15 ++++++++------- source/build.sh | 2 +- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Translations/make_translation.py b/Translations/make_translation.py index cefc7ba2..fe6aaebc 100755 --- a/Translations/make_translation.py +++ b/Translations/make_translation.py @@ -247,14 +247,24 @@ def getFontMapAndTable(textList): 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): + if totalSymbolCount > ((0xEE * 0x0F) - 2): log(f"Error, too many used symbols for this version (total {totalSymbolCount})") - exit(1) + 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 + page = int(index / 0xEF) + if page == 0: + symbolMap[sym] = "\\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 % 0xEF) + 1 + symbolMap[sym] = "\\x%0.2X" % leader + "\\x%0.2X" % value + index = index + 1 # Get the font table fontTableStrings = [] diff --git a/source/Core/Drivers/OLED.cpp b/source/Core/Drivers/OLED.cpp index 9bd49e13..062e09cb 100644 --- a/source/Core/Drivers/OLED.cpp +++ b/source/Core/Drivers/OLED.cpp @@ -236,14 +236,15 @@ void OLED::setRotation(bool leftHanded) { // print a string to the current cursor location void OLED::print(const char *str) { - uint16_t cache = 0; + uint16_t page = 0; while (str[0]) { - if (str[0] >= 0xF0) { - cache = static_cast(str[0] & 0x0F) << 8; - } else if (cache != 0) { - cache |= str[0]; - drawChar(cache); - cache = 0; + if (static_cast(str[0]) >= 0xF0) { + page = static_cast(str[0]&0x0F); + } else if (page != 0) { + page*=0xEF; + page +=static_cast(str[0])-1; + drawChar(page); + page = 0; } else { drawChar(str[0]); } diff --git a/source/build.sh b/source/build.sh index eaa2a1c2..27c4f974 100644 --- a/source/build.sh +++ b/source/build.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -x +set -e TRANSLATION_DIR="../Translations" TRANSLATION_SCRIPT="make_translation.py" From 0c00247d2985fba34265eab35a673d6f0831fe7c Mon Sep 17 00:00:00 2001 From: "Ben V. Brown" Date: Thu, 18 Mar 2021 12:14:15 +0000 Subject: [PATCH 5/8] Format OLED --- source/Core/Drivers/OLED.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/Core/Drivers/OLED.cpp b/source/Core/Drivers/OLED.cpp index 062e09cb..b9f27171 100644 --- a/source/Core/Drivers/OLED.cpp +++ b/source/Core/Drivers/OLED.cpp @@ -239,10 +239,10 @@ void OLED::print(const char *str) { uint16_t page = 0; while (str[0]) { if (static_cast(str[0]) >= 0xF0) { - page = static_cast(str[0]&0x0F); + page = static_cast(str[0] & 0x0F); } else if (page != 0) { - page*=0xEF; - page +=static_cast(str[0])-1; + page *= 0xEF; + page += static_cast(str[0]) - 1; drawChar(page); page = 0; } else { From 55f3a8e0ed9000df07cea255568f3cdb1a6ab10b Mon Sep 17 00:00:00 2001 From: Alvin Wong Date: Sat, 20 Mar 2021 15:36:39 +0800 Subject: [PATCH 6/8] Make font encoding use all byte seq. possible --- Translations/make_translation.py | 68 +++++++++++++++++++++++++------- source/Core/Drivers/OLED.cpp | 25 ++++++------ 2 files changed, 67 insertions(+), 26 deletions(-) diff --git a/Translations/make_translation.py b/Translations/make_translation.py index 764d9934..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,29 +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 > ((0xEE * 0x0F) - 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: - page = int(index / 0xEF) - if page == 0: - symbolMap[sym] = "\\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 % 0xEF) + 1 - symbolMap[sym] = "\\x%0.2X" % leader + "\\x%0.2X" % value - + symbolMap[sym] = getCharsFromFontIndex(index) index = index + 1 # Get the font table fontTableStrings = [] diff --git a/source/Core/Drivers/OLED.cpp b/source/Core/Drivers/OLED.cpp index b9f27171..752f7cf4 100644 --- a/source/Core/Drivers/OLED.cpp +++ b/source/Core/Drivers/OLED.cpp @@ -235,20 +235,21 @@ void OLED::setRotation(bool leftHanded) { } // print a string to the current cursor location -void OLED::print(const char *str) { - uint16_t page = 0; - while (str[0]) { - if (static_cast(str[0]) >= 0xF0) { - page = static_cast(str[0] & 0x0F); - } else if (page != 0) { - page *= 0xEF; - page += static_cast(str[0]) - 1; - drawChar(page); - page = 0; +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 { - drawChar(str[0]); + if (!next[1]) { + return; + } + index = (next[0] - 0xF0) * 0xFF - 15 + next[1]; + next += 2; } - str++; + drawChar(index); } } From b7780f7bfb4e6bb922e25fd2334dcc6ba9cd71b9 Mon Sep 17 00:00:00 2001 From: Alvin Wong Date: Sat, 20 Mar 2021 15:37:19 +0800 Subject: [PATCH 7/8] Add font encoding test --- Translations/make_translation_test.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Translations/make_translation_test.py 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() From 17824fb376166ed44b97e008309d877d187ee3fe Mon Sep 17 00:00:00 2001 From: Alvin Wong Date: Sat, 20 Mar 2021 15:38:06 +0800 Subject: [PATCH 8/8] Fix scrolling text using incorrect length --- source/Core/Src/gui.cpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) 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) {