diff options
-rw-r--r-- | src/basic/utf8.c | 72 | ||||
-rw-r--r-- | src/basic/utf8.h | 5 | ||||
-rw-r--r-- | src/test/test-utf8.c | 32 |
3 files changed, 95 insertions, 14 deletions
diff --git a/src/basic/utf8.c b/src/basic/utf8.c index 090c69d140..e12876962d 100644 --- a/src/basic/utf8.c +++ b/src/basic/utf8.c @@ -196,42 +196,88 @@ char *utf8_escape_invalid(const char *str) { return p; } -char *utf8_escape_non_printable(const char *str) { - char *p, *s; +static int utf8_char_console_width(const char *str) { + char32_t c; + int r; + + r = utf8_encoded_to_unichar(str, &c); + if (r < 0) + return r; + + /* TODO: we should detect combining characters */ + + return unichar_iswide(c) ? 2 : 1; +} + +char *utf8_escape_non_printable_full(const char *str, size_t console_width) { + char *p, *s, *prev_s; + size_t n = 0; /* estimated print width */ assert(str); - p = s = malloc(strlen(str) * 4 + 1); + if (console_width == 0) + return strdup(""); + + p = s = prev_s = malloc(strlen(str) * 4 + 1); if (!p) return NULL; - while (*str) { + for (;;) { int len; + char *saved_s = s; + + if (!*str) /* done! */ + goto finish; len = utf8_encoded_valid_unichar(str, (size_t) -1); if (len > 0) { if (utf8_is_printable(str, len)) { + int w; + + w = utf8_char_console_width(str); + assert(w >= 0); + if (n + w > console_width) + goto truncation; + s = mempcpy(s, str, len); str += len; + n += w; + } else { - while (len > 0) { + for (; len > 0; len--) { + if (n + 4 > console_width) + goto truncation; + *(s++) = '\\'; *(s++) = 'x'; *(s++) = hexchar((int) *str >> 4); *(s++) = hexchar((int) *str); str += 1; - len--; + n += 4; } } } else { - s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER); + if (n + 1 > console_width) + goto truncation; + + s = mempcpy(s, UTF8_REPLACEMENT_CHARACTER, strlen(UTF8_REPLACEMENT_CHARACTER)); str += 1; + n += 1; } + + prev_s = saved_s; } - *s = '\0'; + truncation: + /* Try to go back one if we don't have enough space for the ellipsis */ + if (n + 1 >= console_width) + s = prev_s; + + s = mempcpy(s, "…", strlen("…")); + finish: + *s = '\0'; return p; } @@ -519,15 +565,15 @@ size_t utf8_console_width(const char *str) { /* Returns the approximate width a string will take on screen when printed on a character cell * terminal/console. */ - while (*str != 0) { - char32_t c; + while (*str) { + int w; - if (utf8_encoded_to_unichar(str, &c) < 0) + w = utf8_char_console_width(str); + if (w < 0) return (size_t) -1; + n += w; str = utf8_next_char(str); - - n += unichar_iswide(c) ? 2 : 1; } return n; diff --git a/src/basic/utf8.h b/src/basic/utf8.h index 6df70921db..62e99b7280 100644 --- a/src/basic/utf8.h +++ b/src/basic/utf8.h @@ -22,7 +22,10 @@ bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pu #define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true) char *utf8_escape_invalid(const char *s); -char *utf8_escape_non_printable(const char *str); +char *utf8_escape_non_printable_full(const char *str, size_t console_width); +static inline char *utf8_escape_non_printable(const char *str) { + return utf8_escape_non_printable_full(str, (size_t) -1); +} size_t utf8_encode_unichar(char *out_utf8, char32_t g); size_t utf16_encode_unichar(char16_t *out, char32_t c); diff --git a/src/test/test-utf8.c b/src/test/test-utf8.c index 343cbf4ced..b5c4e3dc34 100644 --- a/src/test/test-utf8.c +++ b/src/test/test-utf8.c @@ -111,6 +111,37 @@ static void test_utf8_escape_non_printable(void) { assert_se(utf8_is_valid(p6)); } +static void test_utf8_escape_non_printable_full(void) { + log_info("/* %s */", __func__); + + for (size_t i = 0; i < 20; i++) { + _cleanup_free_ char *p; + + p = utf8_escape_non_printable_full("goo goo goo", i); + puts(p); + assert_se(utf8_is_valid(p)); + assert_se(utf8_console_width(p) <= i); + } + + for (size_t i = 0; i < 20; i++) { + _cleanup_free_ char *p; + + p = utf8_escape_non_printable_full("\001 \019\20\a", i); + puts(p); + assert_se(utf8_is_valid(p)); + assert_se(utf8_console_width(p) <= i); + } + + for (size_t i = 0; i < 20; i++) { + _cleanup_free_ char *p; + + p = utf8_escape_non_printable_full("\xef\xbf\x30\x13", i); + puts(p); + assert_se(utf8_is_valid(p)); + assert_se(utf8_console_width(p) <= i); + } +} + static void test_utf16_to_utf8(void) { const char16_t utf16[] = { htole16('a'), htole16(0xd800), htole16('b'), htole16(0xdc00), htole16('c'), htole16(0xd801), htole16(0xdc37) }; static const char utf8[] = { 'a', 'b', 'c', 0xf0, 0x90, 0x90, 0xb7 }; @@ -189,6 +220,7 @@ int main(int argc, char *argv[]) { test_utf8_encoded_valid_unichar(); test_utf8_escape_invalid(); test_utf8_escape_non_printable(); + test_utf8_escape_non_printable_full(); test_utf16_to_utf8(); test_utf8_n_codepoints(); test_utf8_console_width(); |