Skip to content

Commit 1e73f7b

Browse files
committed
Forgot to add converter
1 parent 9324081 commit 1e73f7b

2 files changed

Lines changed: 406 additions & 0 deletions

File tree

source/converter/converter.c

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
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

Comments
 (0)