mod tables;
use tables::{FOUR_BYTE_RANGES, TWO_BYTE_VALUES};
pub(crate) fn decode(bytes: &[u8]) -> Result<String, usize> {
let mut decoded = String::with_capacity(bytes.len());
let mut cursor = 0;
while cursor < bytes.len() {
let lead = bytes[cursor];
if lead <= 0x7f {
decoded.push(char::from(lead));
cursor += 1;
continue;
}
if !(0x81..=0xfe).contains(&lead) {
return Err(cursor);
}
let second = *bytes.get(cursor + 1).ok_or(cursor)?;
let (codepoint, width) = if (0x30..=0x39).contains(&second) {
let third = *bytes.get(cursor + 2).ok_or(cursor)?;
if !(0x81..=0xfe).contains(&third) {
return Err(cursor);
}
let fourth = *bytes.get(cursor + 3).ok_or(cursor)?;
if !(0x30..=0x39).contains(&fourth) {
return Err(cursor);
}
let pointer = (((u32::from(lead - 0x81) * 10 + u32::from(second - 0x30)) * 126
+ u32::from(third - 0x81))
* 10)
+ u32::from(fourth - 0x30);
let index = FOUR_BYTE_RANGES.partition_point(|&(start, _, _)| start <= pointer);
let (start, end, initial) = FOUR_BYTE_RANGES[index - 1];
if pointer > end {
return Err(cursor);
}
(initial + pointer - start, 4)
} else {
let character = decode_two_byte(lead, second).ok_or(cursor)?;
(u32::from(character), 2)
};
let character = char::from_u32(codepoint).ok_or(cursor)?;
decoded.push(character);
cursor += width;
}
Ok(decoded)
}
pub(crate) fn decode_two_byte(lead: u8, second: u8) -> Option<char> {
if !(0x81..=0xfe).contains(&lead)
|| !((0x40..=0x7e).contains(&second) || (0x80..=0xfe).contains(&second))
{
return None;
}
let trail = if second <= 0x7e {
second - 0x40
} else {
second - 0x41
};
let index = usize::from(lead - 0x81) * 190 + usize::from(trail);
char::from_u32(u32::from(TWO_BYTE_VALUES[index]))
}
#[cfg(test)]
mod tests {
use super::decode;
#[test]
fn decodes_ascii_gbk_and_four_byte_characters_together() {
let input = b"Chapter 1: \xc4\xe3 \xa2\xe3 \x81\x39\xee\x39 \x95\x34\xb2\x35";
assert_eq!(decode(input).unwrap(), "Chapter 1: 你 € 㐀 𠮷");
}
#[test]
fn decodes_four_byte_boundaries() {
assert_eq!(decode(b"\x81\x30\x81\x30").unwrap(), "\u{80}");
assert_eq!(decode(b"\x84\x31\xa4\x39").unwrap(), "\u{ffff}");
assert_eq!(decode(b"\x90\x30\x81\x30").unwrap(), "\u{10000}");
assert_eq!(decode(b"\xe3\x32\x9a\x35").unwrap(), "\u{10ffff}");
}
#[test]
fn decodes_two_byte_trail_boundaries() {
assert_eq!(decode(b"\x81\x40").unwrap(), "\u{4e02}");
assert_eq!(decode(b"\x81\x7e").unwrap(), "\u{4e8a}");
assert_eq!(decode(b"\x81\x80").unwrap(), "\u{4e90}");
assert_eq!(decode(b"\xfe\xfe").unwrap(), "\u{e4c5}");
}
#[test]
fn rejects_gaps_and_bad_trails_without_replacement() {
for bytes in [
b"\x81\x7f".as_slice(),
b"\xff\x40",
b"\x81\x30\x80\x30",
b"\x81\x30\x81\x40",
b"\x84\x31\xa5\x30",
b"\xe3\x32\x9a\x36",
] {
assert_eq!(decode(bytes).unwrap_err(), 0);
}
}
#[test]
fn reports_the_start_of_a_truncated_sequence() {
for bytes in [b"A\x81".as_slice(), b"A\x81\x30", b"A\x81\x30\x81"] {
assert_eq!(decode(bytes).unwrap_err(), 1);
}
}
#[test]
fn reports_the_title_byte_offset() {
assert_eq!(decode(b"ok\xff").unwrap_err(), 2);
assert_eq!(decode(b"abc\x95\x32\x82").unwrap_err(), 3);
}
#[test]
fn preserves_duplicate_text_and_does_not_trim_title_bytes() {
assert_eq!(decode(b"\xd5\xaa\xd2\xaa ").unwrap(), "摘要 ");
assert_eq!(decode(b"\0").unwrap(), "\0");
}
}