1pub type TCodepoint = i32;
14
15pub mod char_handling_error {
18 use super::TCodepoint;
19 pub const INVALID_UTF8: TCodepoint = -10;
21 pub const INVALID_ESCAPE: TCodepoint = -11;
23 pub const INVALID_LATIN1: TCodepoint = -12;
25}
26
27pub fn char_to_utf8(codepoint: TCodepoint) -> Vec<u8> {
34 let cp = codepoint as u32;
35 let mut out = Vec::with_capacity(4);
36 if cp <= 0x7F {
37 out.push(cp as u8);
38 } else if cp <= 0x7FF {
39 out.push(0xC0 | ((cp >> 6) & 0x1F) as u8);
40 out.push(0x80 | (cp & 0x3F) as u8);
41 } else if cp <= 0xFFFF {
42 out.push(0xE0 | ((cp >> 12) & 0x0F) as u8);
43 out.push(0x80 | ((cp >> 6) & 0x3F) as u8);
44 out.push(0x80 | (cp & 0x3F) as u8);
45 } else {
46 out.push(0xF0 | ((cp >> 18) & 0x07) as u8);
47 out.push(0x80 | ((cp >> 12) & 0x3F) as u8);
48 out.push(0x80 | ((cp >> 6) & 0x3F) as u8);
49 out.push(0x80 | (cp & 0x3F) as u8);
50 }
51 out
52}
53
54const UTF8_BYTES: [i8; 256] = build_utf8_bytes();
57
58const fn build_utf8_bytes() -> [i8; 256] {
59 let mut t = [0i8; 256];
60 let mut i = 0;
61 while i < 256 {
62 t[i] = if i < 0x80 {
63 1
64 } else if i < 0xC0 {
65 -1
66 } else if i < 0xE0 {
67 2
68 } else if i < 0xF0 {
69 3
70 } else if i < 0xF8 {
71 4
72 } else {
73 -1
74 };
75 i += 1;
76 }
77 t
78}
79
80const FIRST_BYTE_MASK: [u8; 5] = [0x00, 0x7F, 0x1F, 0x0F, 0x07];
83
84pub fn handle_utf8_first_byte(byte: u8) -> (bool, i32, TCodepoint) {
89 let num_bytes = UTF8_BYTES[byte as usize];
90 if num_bytes == -1 {
91 return (false, 0, 0);
92 }
93 (
94 true,
95 num_bytes as i32,
96 (byte & FIRST_BYTE_MASK[num_bytes as usize]) as TCodepoint,
97 )
98}
99
100pub fn parse_next_utf8(utf8: &[u8]) -> (TCodepoint, i32) {
106 if utf8.is_empty() {
107 return (char_handling_error::INVALID_UTF8, 0);
108 }
109 let (mut accepted, num_bytes, mut res) = handle_utf8_first_byte(utf8[0]);
110 if accepted {
111 for i in 1..num_bytes as usize {
112 if i >= utf8.len() || utf8[i] == 0 || (utf8[i] & 0xC0) != 0x80 {
116 accepted = false;
117 break;
118 }
119 res = (res << 6) | (utf8[i] & 0x3F) as TCodepoint;
120 }
121 }
122 if !accepted {
123 return (char_handling_error::INVALID_UTF8, 0);
124 }
125 (res, num_bytes)
126}
127
128pub fn parse_utf8(utf8: &[u8], preserve_invalid_bytes: bool) -> Vec<TCodepoint> {
137 let mut codepoints = Vec::new();
138 let mut pos = 0;
139 while pos < utf8.len() && utf8[pos] != 0 {
140 let (codepoint, num_bytes) = parse_next_utf8(&utf8[pos..]);
141 if codepoint == char_handling_error::INVALID_UTF8 {
142 if preserve_invalid_bytes {
143 codepoints.push(utf8[pos] as TCodepoint);
144 pos += 1;
145 continue;
146 } else {
147 return vec![char_handling_error::INVALID_UTF8];
148 }
149 }
150 codepoints.push(codepoint);
151 pos += num_bytes as usize;
152 }
153 codepoints
154}
155
156pub fn hex_char_to_int(c: u8) -> i32 {
159 match c {
160 b'0'..=b'9' => (c - b'0') as i32,
161 b'a'..=b'f' => (c - b'a' + 10) as i32,
162 b'A'..=b'F' => (c - b'A' + 10) as i32,
163 _ => -1,
164 }
165}
166
167pub fn latin1_to_bytes(latin1: &[u8]) -> Result<Vec<u8>, TCodepoint> {
175 let mut result = Vec::with_capacity(latin1.len());
176 let mut i = 0;
177 while i < latin1.len() {
178 let c1 = latin1[i];
179 if c1 < 0x80 {
180 result.push(c1);
181 i += 1;
182 } else {
183 if i + 1 >= latin1.len() {
184 return Err(char_handling_error::INVALID_LATIN1);
185 }
186 let c2 = latin1[i + 1];
187 if (c2 & 0xC0) != 0x80 {
188 return Err(char_handling_error::INVALID_LATIN1);
189 }
190 let code = (((c1 & 0x1F) as i32) << 6) | (c2 & 0x3F) as i32;
191 if !(0x80..=0xFF).contains(&code) {
192 return Err(char_handling_error::INVALID_LATIN1);
193 }
194 result.push(code as u8);
195 i += 2;
196 }
197 }
198 Ok(result)
199}
200
201pub fn byte_to_latin1(bytes: &[u8]) -> Vec<u8> {
207 let mut result = Vec::new();
208 for &b in bytes {
209 if b == 0 {
210 break;
211 }
212 if b <= 0x7F {
213 result.push(b);
214 } else {
215 result.push(0xC0 | (b >> 6));
216 result.push(0x80 | (b & 0x3F));
217 }
218 }
219 result
220}
221
222#[cfg(test)]
223mod tests {
224 use super::char_handling_error::*;
225 use super::*;
226
227 #[test]
228 fn encode_one_byte() {
229 assert_eq!(char_to_utf8(0x41), vec![0x41]);
230 assert_eq!(char_to_utf8(0x00), vec![0x00]);
231 assert_eq!(char_to_utf8(0x7F), vec![0x7F]);
232 }
233
234 #[test]
235 fn encode_two_byte() {
236 assert_eq!(char_to_utf8(0xA9), vec![0xC2, 0xA9]);
238 assert_eq!(char_to_utf8(0x7FF), vec![0xDF, 0xBF]);
240 }
241
242 #[test]
243 fn encode_three_byte() {
244 assert_eq!(char_to_utf8(0x2603), vec![0xE2, 0x98, 0x83]);
246 assert_eq!(char_to_utf8(0xFFFF), vec![0xEF, 0xBF, 0xBF]);
248 }
249
250 #[test]
251 fn encode_four_byte() {
252 assert_eq!(char_to_utf8(0x1F600), vec![0xF0, 0x9F, 0x98, 0x80]);
254 assert_eq!(char_to_utf8(0x10FFFF), vec![0xF4, 0x8F, 0xBF, 0xBF]);
256 }
257
258 #[test]
259 fn encode_decode_round_trip() {
260 for cp in [0x41, 0xA9, 0x2603, 0x1F600, 0x6D4B, 0x8BD5] {
261 let bytes = char_to_utf8(cp);
262 let (decoded, n) = parse_next_utf8(&bytes);
263 assert_eq!(decoded, cp);
264 assert_eq!(n as usize, bytes.len());
265 }
266 }
267
268 #[test]
269 fn first_byte_classification() {
270 assert_eq!(handle_utf8_first_byte(0x41), (true, 1, 0x41));
271 assert!(handle_utf8_first_byte(0xC2).0);
272 assert_eq!(handle_utf8_first_byte(0xC2).1, 2);
273 assert_eq!(handle_utf8_first_byte(0xE2).1, 3);
274 assert_eq!(handle_utf8_first_byte(0xF0).1, 4);
275 assert_eq!(handle_utf8_first_byte(0x80), (false, 0, 0));
277 assert_eq!(handle_utf8_first_byte(0xFF), (false, 0, 0));
279 }
280
281 #[test]
282 fn decode_empty_is_invalid() {
283 assert_eq!(parse_next_utf8(&[]), (INVALID_UTF8, 0));
284 }
285
286 #[test]
287 fn decode_invalid_first_byte() {
288 assert_eq!(parse_next_utf8(&[0x80]), (INVALID_UTF8, 0));
289 }
290
291 #[test]
292 fn decode_bad_continuation() {
293 assert_eq!(parse_next_utf8(&[0xC2, 0x20]), (INVALID_UTF8, 0));
295 }
296
297 #[test]
298 fn decode_truncated_sequence() {
299 assert_eq!(parse_next_utf8(&[0xE2]), (INVALID_UTF8, 0));
301 }
302
303 #[test]
304 fn parse_utf8_string() {
305 let s = "UTF-8: © ☃ 😀".as_bytes();
306 let cps = parse_utf8(s, false);
307 assert!(cps.contains(&0xA9));
308 assert!(cps.contains(&0x2603));
309 assert!(cps.contains(&0x1F600));
310 }
311
312 #[test]
313 fn parse_utf8_invalid_aborts() {
314 let cps = parse_utf8(&[0x41, 0xC2, 0x20], false);
315 assert_eq!(cps, vec![INVALID_UTF8]);
316 }
317
318 #[test]
319 fn parse_utf8_preserve_invalid() {
320 let cps = parse_utf8(&[0x41, 0x80, 0x42], true);
321 assert_eq!(cps, vec![0x41, 0x80, 0x42]);
322 }
323
324 #[test]
325 fn parse_utf8_empty() {
326 assert_eq!(parse_utf8(&[], false), Vec::<TCodepoint>::new());
327 }
328
329 #[test]
330 fn parse_utf8_stops_at_nul() {
331 let cps = parse_utf8(&[0x41, 0x00, 0x42], false);
332 assert_eq!(cps, vec![0x41]);
333 }
334
335 #[test]
336 fn hex_digits() {
337 assert_eq!(hex_char_to_int(b'0'), 0);
338 assert_eq!(hex_char_to_int(b'9'), 9);
339 assert_eq!(hex_char_to_int(b'a'), 10);
340 assert_eq!(hex_char_to_int(b'f'), 15);
341 assert_eq!(hex_char_to_int(b'A'), 10);
342 assert_eq!(hex_char_to_int(b'F'), 15);
343 assert_eq!(hex_char_to_int(b'g'), -1);
344 assert_eq!(hex_char_to_int(b' '), -1);
345 }
346
347 #[test]
348 fn latin1_round_trip() {
349 let utf8 = byte_to_latin1(&[0xFF]);
351 assert_eq!(utf8, vec![0xC3, 0xBF]);
352 assert_eq!(latin1_to_bytes(&utf8), Ok(vec![0xFF]));
353 }
354
355 #[test]
356 fn latin1_ascii_passthrough() {
357 assert_eq!(byte_to_latin1(b"Abc"), b"Abc".to_vec());
358 assert_eq!(latin1_to_bytes(b"Abc"), Ok(b"Abc".to_vec()));
359 }
360
361 #[test]
362 fn latin1_invalid_truncated() {
363 assert_eq!(latin1_to_bytes(&[0xC3]), Err(INVALID_LATIN1));
364 }
365
366 #[test]
367 fn latin1_invalid_bad_continuation() {
368 assert_eq!(latin1_to_bytes(&[0xC3, 0x20]), Err(INVALID_LATIN1));
369 }
370
371 #[test]
372 fn latin1_invalid_out_of_range() {
373 assert_eq!(latin1_to_bytes(&[0xC4, 0x80]), Err(INVALID_LATIN1));
376 assert_eq!(latin1_to_bytes(&[0xC2, 0x40]), Err(INVALID_LATIN1));
378 }
379
380 #[test]
381 fn byte_to_latin1_stops_at_nul() {
382 assert_eq!(byte_to_latin1(&[0x41, 0x00, 0x42]), vec![0x41]);
383 }
384}