|
| 1 | +#include "converter.h" |
| 2 | +#include <stdbool.h> |
| 3 | + |
| 4 | +// The type of a single Unicode codepoint |
| 5 | +typedef uint32_t codepoint_t; |
| 6 | + |
| 7 | +// The last codepoint of the Basic Multilingual Plane, which is the part of Unicode that |
| 8 | +// UTF-16 can encode without surrogates |
| 9 | +#define BMP_END 0xFFFF |
| 10 | + |
| 11 | +// The highest valid Unicode codepoint |
| 12 | +#define UNICODE_MAX 0x10FFFF |
| 13 | + |
| 14 | +// The codepoint that is used to replace invalid encodings |
| 15 | +#define INVALID_CODEPOINT 0xFFFD |
| 16 | + |
| 17 | +// If a character, masked with GENERIC_SURROGATE_MASK, matches this value, it is a surrogate. |
| 18 | +#define GENERIC_SURROGATE_VALUE 0xD800 |
| 19 | +// The mask to apply to a character before testing it against GENERIC_SURROGATE_VALUE |
| 20 | +#define GENERIC_SURROGATE_MASK 0xF800 |
| 21 | + |
| 22 | +// If a character, masked with SURROGATE_MASK, matches this value, it is a high surrogate. |
| 23 | +#define HIGH_SURROGATE_VALUE 0xD800 |
| 24 | +// If a character, masked with SURROGATE_MASK, matches this value, it is a low surrogate. |
| 25 | +#define LOW_SURROGATE_VALUE 0xDC00 |
| 26 | +// The mask to apply to a character before testing it against HIGH_SURROGATE_VALUE or LOW_SURROGATE_VALUE |
| 27 | +#define SURROGATE_MASK 0xFC00 |
| 28 | + |
| 29 | +// The value that is subtracted from a codepoint before encoding it in a surrogate pair |
| 30 | +#define SURROGATE_CODEPOINT_OFFSET 0x10000 |
| 31 | +// A mask that can be applied to a surrogate to extract the codepoint value contained in it |
| 32 | +#define SURROGATE_CODEPOINT_MASK 0x03FF |
| 33 | +// The number of bits of SURROGATE_CODEPOINT_MASK |
| 34 | +#define SURROGATE_CODEPOINT_BITS 10 |
| 35 | + |
| 36 | + |
| 37 | +// The highest codepoint that can be encoded with 1 byte in UTF-8 |
| 38 | +#define UTF8_1_MAX 0x7F |
| 39 | +// The highest codepoint that can be encoded with 2 bytes in UTF-8 |
| 40 | +#define UTF8_2_MAX 0x7FF |
| 41 | +// The highest codepoint that can be encoded with 3 bytes in UTF-8 |
| 42 | +#define UTF8_3_MAX 0xFFFF |
| 43 | +// The highest codepoint that can be encoded with 4 bytes in UTF-8 |
| 44 | +#define UTF8_4_MAX 0x10FFFF |
| 45 | + |
| 46 | +// If a character, masked with UTF8_CONTINUATION_MASK, matches this value, it is a UTF-8 continuation byte |
| 47 | +#define UTF8_CONTINUATION_VALUE 0x80 |
| 48 | +// The mask to a apply to a character before testing it against UTF8_CONTINUATION_VALUE |
| 49 | +#define UTF8_CONTINUATION_MASK 0xC0 |
| 50 | +// The number of bits of a codepoint that are contained in a UTF-8 continuation byte |
| 51 | +#define UTF8_CONTINUATION_CODEPOINT_BITS 6 |
| 52 | + |
| 53 | +// Represents a UTF-8 bit pattern that can be set or verified |
| 54 | +typedef struct |
| 55 | +{ |
| 56 | + // The mask that should be applied to the character before testing it |
| 57 | + utf8_t mask; |
| 58 | + // The value that the character should be tested against after applying the mask |
| 59 | + utf8_t value; |
| 60 | +} utf8_pattern; |
| 61 | + |
| 62 | +// The patterns for leading bytes of a UTF-8 codepoint encoding |
| 63 | +// Each pattern represents the leading byte for a character encoded with N UTF-8 bytes, |
| 64 | +// where N is the index + 1 |
| 65 | +static const utf8_pattern utf8_leading_bytes[] = |
| 66 | +{ |
| 67 | + { 0x80, 0x00 }, // 0xxxxxxx |
| 68 | + { 0xE0, 0xC0 }, // 110xxxxx |
| 69 | + { 0xF0, 0xE0 }, // 1110xxxx |
| 70 | + { 0xF8, 0xF0 } // 11110xxx |
| 71 | +}; |
| 72 | + |
| 73 | +// The number of elements in utf8_leading_bytes |
| 74 | +#define UTF8_LEADING_BYTES_LEN 4 |
| 75 | + |
| 76 | + |
| 77 | +// Gets a codepoint from a UTF-16 string |
| 78 | +// utf16: The UTF-16 string |
| 79 | +// len: The length of the UTF-16 string, in UTF-16 characters |
| 80 | +// index: |
| 81 | +// A pointer to the current index on the string. |
| 82 | +// When the function returns, this will be left at the index of the last character |
| 83 | +// that composes the returned codepoint. |
| 84 | +// For surrogate pairs, this means the index will be left at the low surrogate. |
| 85 | +static codepoint_t decode_utf16(utf16_t const* utf16, size_t len, size_t* index) |
| 86 | +{ |
| 87 | + utf16_t high = utf16[*index]; |
| 88 | + |
| 89 | + // BMP character |
| 90 | + if ((high & GENERIC_SURROGATE_MASK) != GENERIC_SURROGATE_VALUE) |
| 91 | + return high; |
| 92 | + |
| 93 | + // Unmatched low surrogate, invalid |
| 94 | + if ((high & SURROGATE_MASK) != HIGH_SURROGATE_VALUE) |
| 95 | + return INVALID_CODEPOINT; |
| 96 | + |
| 97 | + // String ended with an unmatched high surrogate, invalid |
| 98 | + if (*index == len - 1) |
| 99 | + return INVALID_CODEPOINT; |
| 100 | + |
| 101 | + utf16_t low = utf16[*index + 1]; |
| 102 | + |
| 103 | + // Unmatched high surrogate, invalid |
| 104 | + if ((low & SURROGATE_MASK) != LOW_SURROGATE_VALUE) |
| 105 | + return INVALID_CODEPOINT; |
| 106 | + |
| 107 | + // Two correctly matched surrogates, increase index to indicate we've consumed |
| 108 | + // two characters |
| 109 | + (*index)++; |
| 110 | + |
| 111 | + // The high bits of the codepoint are the value bits of the high surrogate |
| 112 | + // The low bits of the codepoint are the value bits of the low surrogate |
| 113 | + codepoint_t result = high & SURROGATE_CODEPOINT_MASK; |
| 114 | + result <<= SURROGATE_CODEPOINT_BITS; |
| 115 | + result |= low & SURROGATE_CODEPOINT_MASK; |
| 116 | + result += SURROGATE_CODEPOINT_OFFSET; |
| 117 | + |
| 118 | + // And if all else fails, it's valid |
| 119 | + return result; |
| 120 | +} |
| 121 | + |
| 122 | +// Calculates the number of UTF-8 characters it would take to encode a codepoint |
| 123 | +// The codepoint won't be checked for validity, that should be done beforehand. |
| 124 | +static int calculate_utf8_len(codepoint_t codepoint) |
| 125 | +{ |
| 126 | + // An array with the max values would be more elegant, but a bit too heavy |
| 127 | + // for this common function |
| 128 | + |
| 129 | + if (codepoint <= UTF8_1_MAX) |
| 130 | + return 1; |
| 131 | + |
| 132 | + if (codepoint <= UTF8_2_MAX) |
| 133 | + return 2; |
| 134 | + |
| 135 | + if (codepoint <= UTF8_3_MAX) |
| 136 | + return 3; |
| 137 | + |
| 138 | + return 4; |
| 139 | +} |
| 140 | + |
| 141 | +// Encodes a codepoint in a UTF-8 string. |
| 142 | +// The codepoint won't be checked for validity, that should be done beforehand. |
| 143 | +// |
| 144 | +// codepoint: The codepoint to be encoded. |
| 145 | +// utf8: The UTF-8 string |
| 146 | +// len: The length of the UTF-8 string, in UTF-8 characters |
| 147 | +// index: The first empty index on the string. |
| 148 | +// |
| 149 | +// return: The number of characters written to the string. |
| 150 | +static size_t encode_utf8(codepoint_t codepoint, utf8_t* utf8, size_t len, size_t index) |
| 151 | +{ |
| 152 | + int size = calculate_utf8_len(codepoint); |
| 153 | + |
| 154 | + // Not enough space left on the string |
| 155 | + if (index + size > len) |
| 156 | + return 0; |
| 157 | + |
| 158 | + // Write the continuation bytes in reverse order first |
| 159 | + for (int cont_index = size - 1; cont_index > 0; cont_index--) |
| 160 | + { |
| 161 | + utf8_t cont = codepoint & ~UTF8_CONTINUATION_MASK; |
| 162 | + cont |= UTF8_CONTINUATION_VALUE; |
| 163 | + |
| 164 | + utf8[index + cont_index] = cont; |
| 165 | + codepoint >>= UTF8_CONTINUATION_CODEPOINT_BITS; |
| 166 | + } |
| 167 | + |
| 168 | + // Write the leading byte |
| 169 | + utf8_pattern pattern = utf8_leading_bytes[size - 1]; |
| 170 | + |
| 171 | + utf8_t lead = codepoint & ~(pattern.mask); |
| 172 | + lead |= pattern.value; |
| 173 | + |
| 174 | + utf8[index] = lead; |
| 175 | + |
| 176 | + return size; |
| 177 | +} |
| 178 | + |
| 179 | +size_t utf16_to_utf8(utf16_t const* utf16, size_t utf16_len, utf8_t* utf8, size_t utf8_len) |
| 180 | +{ |
| 181 | + // The next codepoint that will be written in the UTF-8 string |
| 182 | + // or the size of the required buffer if utf8 is NULL |
| 183 | + size_t utf8_index = 0; |
| 184 | + |
| 185 | + for (size_t utf16_index = 0; utf16_index < utf16_len; utf16_index++) |
| 186 | + { |
| 187 | + codepoint_t codepoint = decode_utf16(utf16, utf16_len, &utf16_index); |
| 188 | + |
| 189 | + if (utf8 == NULL) |
| 190 | + utf8_index += calculate_utf8_len(codepoint); |
| 191 | + else |
| 192 | + utf8_index += encode_utf8(codepoint, utf8, utf8_len, utf8_index); |
| 193 | + } |
| 194 | + |
| 195 | + return utf8_index; |
| 196 | +} |
| 197 | + |
| 198 | +// Gets a codepoint from a UTF-8 string |
| 199 | +// utf8: The UTF-8 string |
| 200 | +// len: The length of the UTF-8 string, in UTF-8 characters |
| 201 | +// index: |
| 202 | +// A pointer to the current index on the string. |
| 203 | +// When the function returns, this will be left at the index of the last character |
| 204 | +// that composes the returned codepoint. |
| 205 | +// For example, for a 3-byte codepoint, the index will be left at the third character. |
| 206 | +static codepoint_t decode_utf8(utf8_t const* utf8, size_t len, size_t* index) |
| 207 | +{ |
| 208 | + utf8_t leading = utf8[*index]; |
| 209 | + |
| 210 | + // The number of bytes that are used to encode the codepoint |
| 211 | + int encoding_len = 0; |
| 212 | + // The pattern of the leading byte |
| 213 | + utf8_pattern leading_pattern; |
| 214 | + // If the leading byte matches the current leading pattern |
| 215 | + bool matches = false; |
| 216 | + |
| 217 | + do |
| 218 | + { |
| 219 | + encoding_len++; |
| 220 | + leading_pattern = utf8_leading_bytes[encoding_len - 1]; |
| 221 | + |
| 222 | + matches = (leading & leading_pattern.mask) == leading_pattern.value; |
| 223 | + |
| 224 | + } while (!matches && encoding_len < UTF8_LEADING_BYTES_LEN); |
| 225 | + |
| 226 | + // Leading byte doesn't match any known pattern, consider it invalid |
| 227 | + if (!matches) |
| 228 | + return INVALID_CODEPOINT; |
| 229 | + |
| 230 | + codepoint_t codepoint = leading & ~leading_pattern.mask; |
| 231 | + |
| 232 | + for (int i = 0; i < encoding_len - 1; i++) |
| 233 | + { |
| 234 | + // String ended before all continuation bytes were found |
| 235 | + // Invalid encoding |
| 236 | + if (*index + 1 >= len) |
| 237 | + return INVALID_CODEPOINT; |
| 238 | + |
| 239 | + utf8_t continuation = utf8[*index + 1]; |
| 240 | + |
| 241 | + // Number of continuation bytes not the same as advertised on the leading byte |
| 242 | + // Invalid encoding |
| 243 | + if ((continuation & UTF8_CONTINUATION_MASK) != UTF8_CONTINUATION_VALUE) |
| 244 | + return INVALID_CODEPOINT; |
| 245 | + |
| 246 | + codepoint <<= UTF8_CONTINUATION_CODEPOINT_BITS; |
| 247 | + codepoint |= continuation & ~UTF8_CONTINUATION_MASK; |
| 248 | + |
| 249 | + (*index)++; |
| 250 | + } |
| 251 | + |
| 252 | + int proper_len = calculate_utf8_len(codepoint); |
| 253 | + |
| 254 | + // Overlong encoding: too many bytes were used to encode a short codepoint |
| 255 | + // Invalid encoding |
| 256 | + if (proper_len != encoding_len) |
| 257 | + return INVALID_CODEPOINT; |
| 258 | + |
| 259 | + // Surrogates are invalid Unicode codepoints, and should only be used in UTF-16 |
| 260 | + // Invalid encoding |
| 261 | + if (codepoint < BMP_END && (codepoint & GENERIC_SURROGATE_MASK) == GENERIC_SURROGATE_VALUE) |
| 262 | + return INVALID_CODEPOINT; |
| 263 | + |
| 264 | + // UTF-8 can encode codepoints larger than the Unicode standard allows |
| 265 | + // Invalid encoding |
| 266 | + if (codepoint > UNICODE_MAX) |
| 267 | + return INVALID_CODEPOINT; |
| 268 | + |
| 269 | + return codepoint; |
| 270 | +} |
| 271 | + |
| 272 | +// Calculates the number of UTF-16 characters it would take to encode a codepoint |
| 273 | +// The codepoint won't be checked for validity, that should be done beforehand. |
| 274 | +static int calculate_utf16_len(codepoint_t codepoint) |
| 275 | +{ |
| 276 | + if (codepoint <= BMP_END) |
| 277 | + return 1; |
| 278 | + |
| 279 | + return 2; |
| 280 | +} |
| 281 | + |
| 282 | +// Encodes a codepoint in a UTF-16 string. |
| 283 | +// The codepoint won't be checked for validity, that should be done beforehand. |
| 284 | +// |
| 285 | +// codepoint: The codepoint to be encoded. |
| 286 | +// utf16: The UTF-16 string |
| 287 | +// len: The length of the UTF-16 string, in UTF-16 characters |
| 288 | +// index: The first empty index on the string. |
| 289 | +// |
| 290 | +// return: The number of characters written to the string. |
| 291 | +static size_t encode_utf16(codepoint_t codepoint, utf16_t* utf16, size_t len, size_t index) |
| 292 | +{ |
| 293 | + // Not enough space on the string |
| 294 | + if (index >= len) |
| 295 | + return 0; |
| 296 | + |
| 297 | + if (codepoint <= BMP_END) |
| 298 | + { |
| 299 | + utf16[index] = codepoint; |
| 300 | + return 1; |
| 301 | + } |
| 302 | + |
| 303 | + // Not enough space on the string for two surrogates |
| 304 | + if (index + 1 >= len) |
| 305 | + return 0; |
| 306 | + |
| 307 | + codepoint -= SURROGATE_CODEPOINT_OFFSET; |
| 308 | + |
| 309 | + utf16_t low = LOW_SURROGATE_VALUE; |
| 310 | + low |= codepoint & SURROGATE_CODEPOINT_MASK; |
| 311 | + |
| 312 | + codepoint >>= SURROGATE_CODEPOINT_BITS; |
| 313 | + |
| 314 | + utf16_t high = HIGH_SURROGATE_VALUE; |
| 315 | + high |= codepoint & SURROGATE_CODEPOINT_MASK; |
| 316 | + |
| 317 | + utf16[index] = high; |
| 318 | + utf16[index + 1] = low; |
| 319 | + |
| 320 | + return 2; |
| 321 | +} |
| 322 | + |
| 323 | + |
| 324 | +size_t utf8_to_utf16(utf8_t const* utf8, size_t utf8_len, utf16_t* utf16, size_t utf16_len) |
| 325 | +{ |
| 326 | + // The next codepoint that will be written in the UTF-16 string |
| 327 | + // or the size of the required buffer if utf16 is NULL |
| 328 | + size_t utf16_index = 0; |
| 329 | + |
| 330 | + for (size_t utf8_index = 0; utf8_index < utf8_len; utf8_index++) |
| 331 | + { |
| 332 | + codepoint_t codepoint = decode_utf8(utf8, utf8_len, &utf8_index); |
| 333 | + |
| 334 | + if (utf16 == NULL) |
| 335 | + utf16_index += calculate_utf16_len(codepoint); |
| 336 | + else |
| 337 | + utf16_index += encode_utf16(codepoint, utf16, utf16_len, utf16_index); |
| 338 | + } |
| 339 | + |
| 340 | + return utf16_index; |
| 341 | +} |
0 commit comments