summaryrefslogtreecommitdiff
path: root/src/sys/encoding.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sys/encoding.c')
-rw-r--r--src/sys/encoding.c208
1 files changed, 208 insertions, 0 deletions
diff --git a/src/sys/encoding.c b/src/sys/encoding.c
new file mode 100644
index 0000000..ccdcc0a
--- /dev/null
+++ b/src/sys/encoding.c
@@ -0,0 +1,208 @@
+#include "sys/encoding.h"
+
+#include "base/allocator.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+static bool
+utf8_codepoint(const unsigned char *input, size_t length, size_t *used, uint32_t *codepoint)
+{
+ const unsigned char first = input[0];
+ size_t width, index;
+ uint32_t value;
+ if (first < 0x80)
+ {
+ width = 1;
+ value = first;
+ }
+ else if (first >= 0xc2 && first <= 0xdf)
+ {
+ width = 2;
+ value = first & 0x1f;
+ }
+ else if (first >= 0xe0 && first <= 0xef)
+ {
+ width = 3;
+ value = first & 0x0f;
+ }
+ else if (first >= 0xf0 && first <= 0xf4)
+ {
+ width = 4;
+ value = first & 0x07;
+ }
+ else
+ {
+ return false;
+ }
+ if (width > length)
+ {
+ return false;
+ }
+ for (index = 1; index < width; ++index)
+ {
+ if ((input[index] & 0xc0) != 0x80)
+ {
+ return false;
+ }
+ value = (value << 6) | (input[index] & 0x3f);
+ }
+ if ((width == 3 && ((first == 0xe0 && input[1] < 0xa0) || (first == 0xed && input[1] >= 0xa0)))
+ || (width == 4
+ && ((first == 0xf0 && input[1] < 0x90) || (first == 0xf4 && input[1] >= 0x90))))
+ {
+ return false;
+ }
+ *used = width;
+ *codepoint = value;
+ return true;
+}
+
+tiny_encoding_status_t
+tiny_utf8_to_utf16(const char *input, size_t input_length, uint16_t **output, size_t *output_length)
+{
+ size_t offset = 0, units = 0;
+ uint16_t *result;
+ if (output == NULL || output_length == NULL)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ *output = NULL;
+ *output_length = 0;
+ if (input == NULL && input_length != 0)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ while (offset < input_length)
+ {
+ size_t used;
+ uint32_t point;
+ if (!utf8_codepoint(
+ (const unsigned char *)input + offset, input_length - offset, &used, &point))
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ if (units > SIZE_MAX - (point >= 0x10000 ? 2U : 1U))
+ {
+ return TINY_ENCODING_OVERFLOW;
+ }
+ units += point >= 0x10000 ? 2U : 1U;
+ offset += used;
+ }
+ if (units == SIZE_MAX || units + 1 > SIZE_MAX / sizeof(*result))
+ {
+ return TINY_ENCODING_OVERFLOW;
+ }
+ result = malloc((units + 1) * sizeof(*result));
+ if (result == NULL)
+ {
+ return TINY_ENCODING_NO_MEMORY;
+ }
+ offset = 0;
+ units = 0;
+ while (offset < input_length)
+ {
+ size_t used;
+ uint32_t point;
+ (void)utf8_codepoint(
+ (const unsigned char *)input + offset, input_length - offset, &used, &point);
+ if (point < 0x10000)
+ {
+ result[units++] = (uint16_t)point;
+ }
+ else
+ {
+ point -= 0x10000;
+ result[units++] = (uint16_t)(0xd800 + (point >> 10));
+ result[units++] = (uint16_t)(0xdc00 + (point & 0x3ff));
+ }
+ offset += used;
+ }
+ result[units] = 0;
+ *output = result;
+ *output_length = units;
+ return TINY_ENCODING_OK;
+}
+
+tiny_encoding_status_t
+tiny_utf16_to_utf8(const uint16_t *input, size_t input_length, char **output, size_t *output_length)
+{
+ size_t index = 0, bytes = 0;
+ char *result;
+ if (output == NULL || output_length == NULL)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ *output = NULL;
+ *output_length = 0;
+ if (input == NULL && input_length != 0)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ while (index < input_length)
+ {
+ uint32_t point = input[index++];
+ if (point >= 0xd800 && point <= 0xdbff)
+ {
+ if (index >= input_length || input[index] < 0xdc00 || input[index] > 0xdfff)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ point = 0x10000 + ((point - 0xd800) << 10) + (input[index++] - 0xdc00);
+ }
+ else if (point >= 0xdc00 && point <= 0xdfff)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ if (bytes > SIZE_MAX - (point < 0x80 ? 1U : point < 0x800 ? 2U : point < 0x10000 ? 3U : 4U))
+ {
+ return TINY_ENCODING_OVERFLOW;
+ }
+ bytes += point < 0x80 ? 1U : point < 0x800 ? 2U : point < 0x10000 ? 3U : 4U;
+ }
+ if (bytes == SIZE_MAX)
+ {
+ return TINY_ENCODING_OVERFLOW;
+ }
+ result = malloc(bytes + 1);
+ if (result == NULL)
+ {
+ return TINY_ENCODING_NO_MEMORY;
+ }
+ index = 0;
+ bytes = 0;
+ while (index < input_length)
+ {
+ uint32_t point = input[index++];
+ if (point >= 0xd800 && point <= 0xdbff)
+ {
+ point = 0x10000 + ((point - 0xd800) << 10) + (input[index++] - 0xdc00);
+ }
+ if (point < 0x80)
+ {
+ result[bytes++] = (char)point;
+ }
+ else if (point < 0x800)
+ {
+ result[bytes++] = (char)(0xc0 | (point >> 6));
+ result[bytes++] = (char)(0x80 | (point & 0x3f));
+ }
+ else if (point < 0x10000)
+ {
+ result[bytes++] = (char)(0xe0 | (point >> 12));
+ result[bytes++] = (char)(0x80 | ((point >> 6) & 0x3f));
+ result[bytes++] = (char)(0x80 | (point & 0x3f));
+ }
+ else
+ {
+ result[bytes++] = (char)(0xf0 | (point >> 18));
+ result[bytes++] = (char)(0x80 | ((point >> 12) & 0x3f));
+ result[bytes++] = (char)(0x80 | ((point >> 6) & 0x3f));
+ result[bytes++] = (char)(0x80 | (point & 0x3f));
+ }
+ }
+ result[bytes] = '\0';
+ *output = result;
+ *output_length = bytes;
+ return TINY_ENCODING_OK;
+}