From 80b0a5972999caacab538f2f46fa8287a30caacd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 25 Jun 2018 19:17:42 +0200 Subject: [PATCH] utf8: add utf8_to_utf16() helper --- src/basic/utf8.c | 73 ++++++++++++++++++++++++++++++++++++++++++++ src/basic/utf8.h | 5 +++ src/test/test-utf8.c | 44 +++++++++++++++++++++++--- 3 files changed, 117 insertions(+), 5 deletions(-) diff --git a/src/basic/utf8.c b/src/basic/utf8.c index 7dc84c1157e..e0d1949dc7f 100644 --- a/src/basic/utf8.c +++ b/src/basic/utf8.c @@ -366,6 +366,79 @@ char *utf16_to_utf8(const char16_t *s, size_t length /* bytes! */) { return r; } +size_t utf16_encode_unichar(char16_t *out, char32_t c) { + + /* Note that this encodes as little-endian. */ + + switch (c) { + + case 0 ... 0xd7ffU: + case 0xe000U ... 0xffffU: + out[0] = htole16(c); + return 1; + + case 0x10000U ... 0x10ffffU: + c -= 0x10000U; + out[0] = htole16((c >> 10) + 0xd800U); + out[1] = htole16((c & 0x3ffU) + 0xdc00U); + return 2; + + default: /* A surrogate (invalid) */ + return 0; + } +} + +char16_t *utf8_to_utf16(const char *s, size_t length) { + char16_t *n, *p; + size_t i; + int r; + + assert(s); + + n = new(char16_t, length + 1); + if (!n) + return NULL; + + p = n; + + for (i = 0; i < length;) { + char32_t unichar; + size_t e; + + e = utf8_encoded_expected_len(s + i); + if (e <= 1) /* Invalid and single byte characters are copied as they are */ + goto copy; + + if (i + e > length) /* sequence longer than input buffer, then copy as-is */ + goto copy; + + r = utf8_encoded_to_unichar(s + i, &unichar); + if (r < 0) /* sequence invalid, then copy as-is */ + goto copy; + + p += utf16_encode_unichar(p, unichar); + i += e; + continue; + + copy: + *(p++) = htole16(s[i++]); + } + + *p = 0; + return n; +} + +size_t char16_strlen(const char16_t *s) { + size_t n = 0; + + assert(s); + + while (*s != 0) + n++, s++; + + return n; +} + /* expected size used to encode one unicode char */ static int utf8_unichar_to_encoded_len(char32_t unichar) { diff --git a/src/basic/utf8.h b/src/basic/utf8.h index 13c48b0978c..69a816e125f 100644 --- a/src/basic/utf8.h +++ b/src/basic/utf8.h @@ -25,7 +25,12 @@ char *utf8_escape_invalid(const char *s); char *utf8_escape_non_printable(const char *str); size_t utf8_encode_unichar(char *out_utf8, char32_t g); +size_t utf16_encode_unichar(char16_t *out, char32_t c); + char *utf16_to_utf8(const char16_t *s, size_t length /* bytes! */); +char16_t *utf8_to_utf16(const char *s, size_t length); + +size_t char16_strlen(const char16_t *s); int utf8_encoded_valid_unichar(const char *str); int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar); diff --git a/src/test/test-utf8.c b/src/test/test-utf8.c index d35daf53dca..9849530ac88 100644 --- a/src/test/test-utf8.c +++ b/src/test/test-utf8.c @@ -2,6 +2,7 @@ #include "alloc-util.h" #include "string-util.h" +#include "strv.h" #include "utf8.h" #include "util.h" @@ -87,15 +88,25 @@ static void test_utf8_escaping_printable(void) { } static void test_utf16_to_utf8(void) { - char *a = NULL; - const uint16_t utf16[] = { htole16('a'), htole16(0xd800), htole16('b'), htole16(0xdc00), htole16('c'), htole16(0xd801), htole16(0xdc37) }; - const char utf8[] = { 'a', 'b', 'c', 0xf0, 0x90, 0x90, 0xb7, 0 }; + 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 }; + _cleanup_free_ char16_t *b = NULL; + _cleanup_free_ char *a = NULL; - a = utf16_to_utf8(utf16, 14); + /* Convert UTF-16 to UTF-8, filtering embedded bad chars */ + a = utf16_to_utf8(utf16, sizeof(utf16)); assert_se(a); - assert_se(streq(a, utf8)); + assert_se(memcmp(a, utf8, sizeof(utf8)) == 0); + + /* Convert UTF-8 to UTF-16, and back */ + b = utf8_to_utf16(utf8, sizeof(utf8)); + assert_se(b); free(a); + a = utf16_to_utf8(b, char16_strlen(b) * 2); + assert_se(a); + assert_se(strlen(a) == sizeof(utf8)); + assert_se(memcmp(a, utf8, sizeof(utf8)) == 0); } static void test_utf8_n_codepoints(void) { @@ -116,6 +127,28 @@ static void test_utf8_console_width(void) { assert_se(utf8_console_width("\xF1") == (size_t) -1); } +static void test_utf8_to_utf16(void) { + const char *p; + + FOREACH_STRING(p, + "abc", + "zażółcić gęślą jaźń", + "串", + "", + "…👊🔪💐…") { + + _cleanup_free_ char16_t *a = NULL; + _cleanup_free_ char *b = NULL; + + a = utf8_to_utf16(p, strlen(p)); + assert_se(a); + + b = utf16_to_utf8(a, char16_strlen(a) * 2); + assert_se(b); + assert_se(streq(p, b)); + } +} + int main(int argc, char *argv[]) { test_utf8_is_valid(); test_utf8_is_printable(); @@ -127,6 +160,7 @@ int main(int argc, char *argv[]) { test_utf16_to_utf8(); test_utf8_n_codepoints(); test_utf8_console_width(); + test_utf8_to_utf16(); return 0; } -- 2.47.3