diff --git a/core/embed/extmod/modtrezorui/display.c b/core/embed/extmod/modtrezorui/display.c index 642f6061f..aebf4739e 100644 --- a/core/embed/extmod/modtrezorui/display.c +++ b/core/embed/extmod/modtrezorui/display.c @@ -594,7 +594,7 @@ static uint8_t convert_char(const uint8_t c) { return c; } - // UTF-8 handling: https://en.wikipedia.org/wiki/UTF-8#Description + // UTF-8 handling: https://en.wikipedia.org/wiki/UTF-8#Encoding // bytes 11xxxxxx are first bytes of UTF-8 characters if (c >= 0xC0) { @@ -867,3 +867,38 @@ void display_fade(int start, int end, int delay) { } display_backlight(end); } + +#define UTF8_IS_CONT(ch) (((ch)&0xC0) == 0x80) + +void display_utf8_substr(const char *buf_start, size_t buf_len, int char_off, + int char_len, const char **out_start, int *out_len) { + size_t i = 0; + + for (; i < buf_len; i++) { + if (char_off == 0) { + break; + } + if (!UTF8_IS_CONT(buf_start[i])) { + char_off--; + } + } + size_t i_start = i; + + for (; i < buf_len; i++) { + if (char_len == 0) { + break; + } + if (!UTF8_IS_CONT(buf_start[i])) { + char_len--; + } + } + + for (; i < buf_len; i++) { + if (!UTF8_IS_CONT(buf_start[i])) { + break; + } + } + + *out_start = buf_start + i_start; + *out_len = i - i_start; +} diff --git a/core/embed/extmod/modtrezorui/display.h b/core/embed/extmod/modtrezorui/display.h index c897833c6..6bb601543 100644 --- a/core/embed/extmod/modtrezorui/display.h +++ b/core/embed/extmod/modtrezorui/display.h @@ -113,4 +113,8 @@ int display_orientation(int degrees); int display_backlight(int val); void display_fade(int start, int end, int delay); +// helper for locating a substring in buffer with utf-8 string +void display_utf8_substr(const char *buf_start, size_t buf_len, int char_off, + int char_len, const char **out_start, int *out_len); + #endif diff --git a/core/embed/extmod/modtrezorui/modtrezorui-display.h b/core/embed/extmod/modtrezorui/modtrezorui-display.h index 24f404ad8..d24100005 100644 --- a/core/embed/extmod/modtrezorui/modtrezorui-display.h +++ b/core/embed/extmod/modtrezorui/modtrezorui-display.h @@ -317,11 +317,16 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(mod_trezorui_Display_print_obj, /// font: int, /// fgcolor: int, /// bgcolor: int, +/// text_offset: int = None, +/// text_len: int = None, /// ) -> None: /// """ /// Renders left-aligned text at position (x,y) where x is left position and /// y is baseline. Font font is used for rendering, fgcolor is used as /// foreground color, bgcolor as background. +/// +/// Arguments text_offset and text_len can be used to render a substring of +/// the text. /// """ STATIC mp_obj_t mod_trezorui_Display_text(size_t n_args, const mp_obj_t *args) { mp_int_t x = mp_obj_get_int(args[1]); @@ -331,10 +336,25 @@ STATIC mp_obj_t mod_trezorui_Display_text(size_t n_args, const mp_obj_t *args) { mp_int_t font = mp_obj_get_int(args[4]); mp_int_t fgcolor = mp_obj_get_int(args[5]); mp_int_t bgcolor = mp_obj_get_int(args[6]); - display_text(x, y, text.buf, text.len, font, fgcolor, bgcolor); + + const char *buf_start = text.buf; + int buf_len = text.len; + if (n_args > 7) { + mp_int_t off = mp_obj_get_int(args[7]); + mp_int_t len = n_args > 8 ? mp_obj_get_int(args[8]) : text.len - off; + if (off < 0 || off > text.len) { + mp_raise_ValueError("Invalid text_offset"); + } + if (len < 0 || len + off > text.len) { + mp_raise_ValueError("Invalid text_len"); + } + display_utf8_substr(text.buf, text.len, off, len, &buf_start, &buf_len); + } + + display_text(x, y, buf_start, buf_len, font, fgcolor, bgcolor); return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_text_obj, 7, 7, +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_text_obj, 7, 9, mod_trezorui_Display_text); /// def text_center( @@ -397,20 +417,45 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_text_right_obj, 7, 7, mod_trezorui_Display_text_right); -/// def text_width(self, text: str, font: int) -> int: +/// def text_width( +/// self, +/// text: str, +/// font: int, +/// text_offset: int = None, +/// text_len: int = None, +/// ) -> int: /// """ /// Returns a width of text in pixels. Font font is used for rendering. +/// +/// Arguments text_offset and text_len can be used to render a substring of +/// the text. /// """ -STATIC mp_obj_t mod_trezorui_Display_text_width(mp_obj_t self, mp_obj_t text, - mp_obj_t font) { +STATIC mp_obj_t mod_trezorui_Display_text_width(size_t n_args, + const mp_obj_t *args) { mp_buffer_info_t txt = {0}; - mp_get_buffer_raise(text, &txt, MP_BUFFER_READ); - mp_int_t f = mp_obj_get_int(font); - int w = display_text_width(txt.buf, txt.len, f); + mp_get_buffer_raise(args[1], &txt, MP_BUFFER_READ); + mp_int_t f = mp_obj_get_int(args[2]); + + const char *buf_start = txt.buf; + int buf_len = txt.len; + if (n_args > 3) { + mp_int_t off = mp_obj_get_int(args[3]); + mp_int_t len = n_args > 4 ? mp_obj_get_int(args[4]) : txt.len - off; + if (off < 0 || off > txt.len) { + mp_raise_ValueError("Invalid text_offset"); + } + if (len < 0 || len + off > txt.len) { + mp_raise_ValueError("Invalid text_len"); + } + display_utf8_substr(txt.buf, txt.len, off, len, &buf_start, &buf_len); + } + + int w = display_text_width(buf_start, buf_len, f); return mp_obj_new_int(w); } -STATIC MP_DEFINE_CONST_FUN_OBJ_3(mod_trezorui_Display_text_width_obj, - mod_trezorui_Display_text_width); +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_trezorui_Display_text_width_obj, + 3, 5, + mod_trezorui_Display_text_width); /// def text_split(self, text: str, font: int, requested_width: int) -> int: /// """ diff --git a/core/mocks/generated/trezorui.pyi b/core/mocks/generated/trezorui.pyi index 6e964f562..c0752f309 100644 --- a/core/mocks/generated/trezorui.pyi +++ b/core/mocks/generated/trezorui.pyi @@ -114,11 +114,15 @@ class Display: font: int, fgcolor: int, bgcolor: int, + text_offset: int = None, + text_len: int = None, ) -> None: """ Renders left-aligned text at position (x,y) where x is left position and y is baseline. Font font is used for rendering, fgcolor is used as foreground color, bgcolor as background. + Arguments text_offset and text_len can be used to render a substring of + the text. """ def text_center( @@ -151,9 +155,17 @@ class Display: foreground color, bgcolor as background. """ - def text_width(self, text: str, font: int) -> int: + def text_width( + self, + text: str, + font: int, + text_offset: int = None, + text_len: int = None, + ) -> int: """ Returns a width of text in pixels. Font font is used for rendering. + Arguments text_offset and text_len can be used to render a substring of + the text. """ def text_split(self, text: str, font: int, requested_width: int) -> int: diff --git a/core/tests/test_trezor.ui.display.py b/core/tests/test_trezor.ui.display.py index d326d735f..1efad4aa6 100644 --- a/core/tests/test_trezor.ui.display.py +++ b/core/tests/test_trezor.ui.display.py @@ -25,6 +25,17 @@ class TestDisplay(unittest.TestCase): def test_text(self): display.text(120, 120, 'Test', 0, 0xFFFF, 0x0000) + display.text(120, 120, 'Test', 0, 0xFFFF, 0x0000, 2) + display.text(120, 120, 'Test', 0, 0xFFFF, 0x0000, 2, 1) + display.text(120, 120, 'Těst', 0, 0xFFFF, 0x0000, 2, 2) + + display.text(120, 120, "ǑǑǑǑǑǑǑǑ", 0, 0xFFFF, 0x0000) + for off in (0, 2, 3, 8, 16): + display.text(120, 120, "ǑǑǑǑǑǑǑǑ", 0, 0xFFFF, 0x0000, off) + display.text(120, 120, "ǑǑǑǑǑǑǑǑ", 0, 0xFFFF, 0x0000, off, 0) + + for off, tlen in ((2, 5), (2, 14), (3, 5), (3, 13), (8, 1), (8, 8)): + display.text(120, 120, "ǑǑǑǑǑǑǑǑ", 0, 0xFFFF, 0x0000, off, tlen) def test_text_center(self): display.text_center(120, 120, 'Test', 0, 0xFFFF, 0x0000) @@ -34,6 +45,24 @@ class TestDisplay(unittest.TestCase): def test_text_width(self): display.text_width('Test', 0) + display.text_width('Test', 0, 2) + display.text_width('Test', 0, 2, 1) + display.text_width('Těst', 0, 2, 2) + + display.text_width("ǑǑǑǑǑǑǑǑ", 0) + for off in (0, 2, 3, 8, 16): + display.text_width("ǑǑǑǑǑǑǑǑ", 0, off) + self.assertEqual(display.text_width("ǑǑǑǑǑǑǑǑ", 0, off, 0), 0) + + for off, tlen in ((0, 8), (0, 16), (2, 5), (2, 14), (3, 5), (3, 13)): + display.text_width("ǑǑǑǑǑǑǑǑ", 0, off, tlen) + + self.assertEqual(display.text_width("ǑǑǑǑǑǑǑǑ", 8), 0) + self.assertEqual(display.text_width("ǑǑǑǑǑǑǑǑ", 8, 1), 0) + self.assertEqual(display.text_width("ǑǑǑǑǑǑǑǑ", 8, 8), 0) + self.assertEqual(display.text_width("ǑǑǑǑǑǑǑǑ", 9), 0) + self.assertEqual(display.text_width("ǑǑǑǑǑǑǑǑ", 9, 1), 0) + self.assertEqual(display.text_width("ǑǑǑǑǑǑǑǑ", 15, 1), 0) def test_qrcode(self): display.qrcode(0, 0, 'Test', 4)