1use hermes_unicode::{
18 UNICODE_MAX_VALUE, UNICODE_REPLACEMENT_CHARACTER, UNICODE_SURROGATE_FIRST,
19 UNICODE_SURROGATE_LAST, UTF16_HIGH_SURROGATE, UTF16_LOW_SURROGATE,
20};
21
22#[inline]
25pub fn is_utf8_start(ch: u8) -> bool {
26 (ch & 0x80) != 0
27}
28
29#[inline]
34fn at(bytes: &[u8], i: usize) -> u32 {
35 bytes.get(i).copied().unwrap_or(0) as u32
36}
37
38#[allow(clippy::manual_range_contains)]
48pub fn decode_utf8_slow_path<const ALLOW_SURROGATES: bool>(
49 bytes: &[u8],
50 i: &mut usize,
51 mut error: impl FnMut(&str),
52) -> u32 {
53 let ch = at(bytes, *i);
54 let result: u32;
55
56 debug_assert!(is_utf8_start(ch as u8));
57
58 if (ch & 0xE0) == 0xC0 {
59 let ch1 = at(bytes, *i + 1);
60 if (ch1 & 0xC0) != 0x80 {
61 *i += 1;
62 error("Invalid UTF-8 continuation byte");
63 return UNICODE_REPLACEMENT_CHARACTER;
64 }
65
66 *i += 2;
67 result = ((ch & 0x1F) << 6) | (ch1 & 0x3F);
68 if result <= 0x7F {
69 error("Non-canonical UTF-8 encoding");
70 return UNICODE_REPLACEMENT_CHARACTER;
71 }
72 } else if (ch & 0xF0) == 0xE0 {
73 let ch1 = at(bytes, *i + 1);
74 if (ch1 & 0x40) != 0 || (ch1 & 0x80) == 0 {
75 *i += 1;
76 error("Invalid UTF-8 continuation byte");
77 return UNICODE_REPLACEMENT_CHARACTER;
78 }
79 let ch2 = at(bytes, *i + 2);
80 if (ch2 & 0x40) != 0 || (ch2 & 0x80) == 0 {
81 *i += 2;
82 error("Invalid UTF-8 continuation byte");
83 return UNICODE_REPLACEMENT_CHARACTER;
84 }
85 *i += 3;
86 result = ((ch & 0x0F) << 12) | ((ch1 & 0x3F) << 6) | (ch2 & 0x3F);
87 if result <= 0x7FF {
88 error("Non-canonical UTF-8 encoding");
89 return UNICODE_REPLACEMENT_CHARACTER;
90 }
91 if result >= UNICODE_SURROGATE_FIRST && result <= UNICODE_SURROGATE_LAST && !ALLOW_SURROGATES
92 {
93 error(&format!("Invalid UTF-8 code point 0x{:X}", result));
94 return UNICODE_REPLACEMENT_CHARACTER;
95 }
96 } else if (ch & 0xF8) == 0xF0 {
97 let ch1 = at(bytes, *i + 1);
98 if (ch1 & 0x40) != 0 || (ch1 & 0x80) == 0 {
99 *i += 1;
100 error("Invalid UTF-8 continuation byte");
101 return UNICODE_REPLACEMENT_CHARACTER;
102 }
103 let ch2 = at(bytes, *i + 2);
104 if (ch2 & 0x40) != 0 || (ch2 & 0x80) == 0 {
105 *i += 2;
106 error("Invalid UTF-8 continuation byte");
107 return UNICODE_REPLACEMENT_CHARACTER;
108 }
109 let ch3 = at(bytes, *i + 3);
110 if (ch3 & 0x40) != 0 || (ch3 & 0x80) == 0 {
111 *i += 3;
112 error("Invalid UTF-8 continuation byte");
113 return UNICODE_REPLACEMENT_CHARACTER;
114 }
115 *i += 4;
116 result =
117 ((ch & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) | (ch3 & 0x3F);
118 if result <= 0xFFFF {
119 error("Non-canonical UTF-8 encoding");
120 return UNICODE_REPLACEMENT_CHARACTER;
121 }
122 if result > UNICODE_MAX_VALUE {
123 error(&format!("Invalid UTF-8 code point 0x{:X}", result));
124 return UNICODE_REPLACEMENT_CHARACTER;
125 }
126 } else {
127 *i += 1;
128 error(&format!("Invalid UTF-8 lead byte 0x{:X}", ch & 0xFF));
129 return UNICODE_REPLACEMENT_CHARACTER;
130 }
131
132 result
133}
134
135#[inline]
143pub fn decode_utf8<const ALLOW_SURROGATES: bool>(
144 bytes: &[u8],
145 i: &mut usize,
146 error: impl FnMut(&str),
147) -> u32 {
148 if *i < bytes.len() && (bytes[*i] & 0x80) == 0 {
149 let c = bytes[*i] as u32;
151 *i += 1;
152 return c;
153 }
154 decode_utf8_slow_path::<ALLOW_SURROGATES>(bytes, i, error)
155}
156
157#[inline]
161pub fn encode_utf16(out: &mut Vec<u16>, cp: u32) {
162 if cp < 0x10000 {
163 out.push(cp as u16);
164 } else {
165 debug_assert!(cp <= UNICODE_MAX_VALUE, "invalid Unicode value");
166 let cp = cp - 0x10000;
167 out.push((UTF16_HIGH_SURROGATE + ((cp >> 10) & 0x3FF)) as u16);
168 out.push((UTF16_LOW_SURROGATE + (cp & 0x3FF)) as u16);
169 }
170}
171
172pub fn convert_utf8_with_surrogates_to_utf16(bytes: &[u8]) -> Vec<u16> {
176 let mut out = Vec::with_capacity(bytes.len());
177 let mut i = 0usize;
178 while i < bytes.len() {
179 let cp = decode_utf8::<true>(bytes, &mut i, |_| {});
182 encode_utf16(&mut out, cp);
183 }
184 out
185}
186
187#[cfg(test)]
188mod tests {
189 use super::convert_utf8_with_surrogates_to_utf16;
190
191 #[test]
192 fn ascii_passthrough() {
193 assert_eq!(convert_utf8_with_surrogates_to_utf16(b"abc"), vec![0x61, 0x62, 0x63]);
194 }
195
196 #[test]
197 fn bmp_non_ascii() {
198 assert_eq!(convert_utf8_with_surrogates_to_utf16(&[0xE5, 0x93, 0x88]), vec![0x54C8]);
200 }
201
202 #[test]
203 fn astral_4byte() {
204 assert_eq!(
206 convert_utf8_with_surrogates_to_utf16(&[0xF0, 0x9F, 0x91, 0x8B]),
207 vec![0xD83D, 0xDC4B]
208 );
209 }
210
211 #[test]
212 fn wtf8_lone_surrogate() {
213 assert_eq!(convert_utf8_with_surrogates_to_utf16(&[0xED, 0xA0, 0x80]), vec![0xD800]);
215 }
216}