use hermes_unicode::{
UNICODE_MAX_VALUE, UNICODE_REPLACEMENT_CHARACTER, UNICODE_SURROGATE_FIRST,
UNICODE_SURROGATE_LAST, UTF16_HIGH_SURROGATE, UTF16_LOW_SURROGATE,
};
#[inline]
pub fn is_utf8_start(ch: u8) -> bool {
(ch & 0x80) != 0
}
#[inline]
fn at(bytes: &[u8], i: usize) -> u32 {
bytes.get(i).copied().unwrap_or(0) as u32
}
#[allow(clippy::manual_range_contains)]
pub fn decode_utf8_slow_path<const ALLOW_SURROGATES: bool>(
bytes: &[u8],
i: &mut usize,
mut error: impl FnMut(&str),
) -> u32 {
let ch = at(bytes, *i);
let result: u32;
debug_assert!(is_utf8_start(ch as u8));
if (ch & 0xE0) == 0xC0 {
let ch1 = at(bytes, *i + 1);
if (ch1 & 0xC0) != 0x80 {
*i += 1;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
*i += 2;
result = ((ch & 0x1F) << 6) | (ch1 & 0x3F);
if result <= 0x7F {
error("Non-canonical UTF-8 encoding");
return UNICODE_REPLACEMENT_CHARACTER;
}
} else if (ch & 0xF0) == 0xE0 {
let ch1 = at(bytes, *i + 1);
if (ch1 & 0x40) != 0 || (ch1 & 0x80) == 0 {
*i += 1;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
let ch2 = at(bytes, *i + 2);
if (ch2 & 0x40) != 0 || (ch2 & 0x80) == 0 {
*i += 2;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
*i += 3;
result = ((ch & 0x0F) << 12) | ((ch1 & 0x3F) << 6) | (ch2 & 0x3F);
if result <= 0x7FF {
error("Non-canonical UTF-8 encoding");
return UNICODE_REPLACEMENT_CHARACTER;
}
if result >= UNICODE_SURROGATE_FIRST && result <= UNICODE_SURROGATE_LAST && !ALLOW_SURROGATES
{
error(&format!("Invalid UTF-8 code point 0x{:X}", result));
return UNICODE_REPLACEMENT_CHARACTER;
}
} else if (ch & 0xF8) == 0xF0 {
let ch1 = at(bytes, *i + 1);
if (ch1 & 0x40) != 0 || (ch1 & 0x80) == 0 {
*i += 1;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
let ch2 = at(bytes, *i + 2);
if (ch2 & 0x40) != 0 || (ch2 & 0x80) == 0 {
*i += 2;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
let ch3 = at(bytes, *i + 3);
if (ch3 & 0x40) != 0 || (ch3 & 0x80) == 0 {
*i += 3;
error("Invalid UTF-8 continuation byte");
return UNICODE_REPLACEMENT_CHARACTER;
}
*i += 4;
result =
((ch & 0x07) << 18) | ((ch1 & 0x3F) << 12) | ((ch2 & 0x3F) << 6) | (ch3 & 0x3F);
if result <= 0xFFFF {
error("Non-canonical UTF-8 encoding");
return UNICODE_REPLACEMENT_CHARACTER;
}
if result > UNICODE_MAX_VALUE {
error(&format!("Invalid UTF-8 code point 0x{:X}", result));
return UNICODE_REPLACEMENT_CHARACTER;
}
} else {
*i += 1;
error(&format!("Invalid UTF-8 lead byte 0x{:X}", ch & 0xFF));
return UNICODE_REPLACEMENT_CHARACTER;
}
result
}
#[inline]
pub fn decode_utf8<const ALLOW_SURROGATES: bool>(
bytes: &[u8],
i: &mut usize,
error: impl FnMut(&str),
) -> u32 {
if *i < bytes.len() && (bytes[*i] & 0x80) == 0 {
let c = bytes[*i] as u32;
*i += 1;
return c;
}
decode_utf8_slow_path::<ALLOW_SURROGATES>(bytes, i, error)
}
#[inline]
pub fn encode_utf16(out: &mut Vec<u16>, cp: u32) {
if cp < 0x10000 {
out.push(cp as u16);
} else {
debug_assert!(cp <= UNICODE_MAX_VALUE, "invalid Unicode value");
let cp = cp - 0x10000;
out.push((UTF16_HIGH_SURROGATE + ((cp >> 10) & 0x3FF)) as u16);
out.push((UTF16_LOW_SURROGATE + (cp & 0x3FF)) as u16);
}
}
pub fn convert_utf8_with_surrogates_to_utf16(bytes: &[u8]) -> Vec<u16> {
let mut out = Vec::with_capacity(bytes.len());
let mut i = 0usize;
while i < bytes.len() {
let cp = decode_utf8::<true>(bytes, &mut i, |_| {});
encode_utf16(&mut out, cp);
}
out
}
#[cfg(test)]
mod tests {
use super::convert_utf8_with_surrogates_to_utf16;
#[test]
fn ascii_passthrough() {
assert_eq!(convert_utf8_with_surrogates_to_utf16(b"abc"), vec![0x61, 0x62, 0x63]);
}
#[test]
fn bmp_non_ascii() {
assert_eq!(convert_utf8_with_surrogates_to_utf16(&[0xE5, 0x93, 0x88]), vec![0x54C8]);
}
#[test]
fn astral_4byte() {
assert_eq!(
convert_utf8_with_surrogates_to_utf16(&[0xF0, 0x9F, 0x91, 0x8B]),
vec![0xD83D, 0xDC4B]
);
}
#[test]
fn wtf8_lone_surrogate() {
assert_eq!(convert_utf8_with_surrogates_to_utf16(&[0xED, 0xA0, 0x80]), vec![0xD800]);
}
}