const VS_START: u32 = 0xFE00;
const VS_END: u32 = 0xFE0F;
const VS_SUP_START: u32 = 0xE0100;
const VS_SUP_END: u32 = 0xE01EF;
pub fn byte_to_vs(b: u8) -> char {
let cp = if b <= 0x0F {
VS_START + b as u32
} else {
VS_SUP_START + (b as u32 - 0x10)
};
char::from_u32(cp).expect("variation-selector code points are valid scalars")
}
pub fn vs_to_byte(c: char) -> Option<u8> {
match c as u32 {
cp @ VS_START..=VS_END => Some((cp - VS_START) as u8),
cp @ VS_SUP_START..=VS_SUP_END => Some((cp - VS_SUP_START + 0x10) as u8),
_ => None,
}
}
pub fn is_vs(c: char) -> bool {
vs_to_byte(c).is_some()
}
pub fn encode_run(bytes: &[u8]) -> String {
bytes.iter().map(|&b| byte_to_vs(b)).collect()
}
pub fn decode_run(s: &str) -> (Vec<u8>, usize) {
let mut out = Vec::new();
for (offset, c) in s.char_indices() {
match vs_to_byte(c) {
Some(b) => out.push(b),
None => return (out, offset),
}
}
(out, s.len())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spec_boundary_vectors() {
assert_eq!(byte_to_vs(0x00), '\u{FE00}');
assert_eq!(byte_to_vs(0x0F), '\u{FE0F}');
assert_eq!(byte_to_vs(0x10), '\u{E0100}');
assert_eq!(byte_to_vs(0xFF), '\u{E01EF}');
}
#[test]
fn codec_is_a_bijection() {
let mut seen = std::collections::HashSet::new();
for b in 0u8..=0xFF {
let c = byte_to_vs(b);
assert_eq!(vs_to_byte(c), Some(b), "byte {b:#04x} did not round-trip");
assert!(seen.insert(c), "byte {b:#04x} collided on {c:?}");
}
assert_eq!(seen.len(), 256);
}
#[test]
fn non_selectors_are_rejected() {
for c in ['a', 'Z', '0', ' ', '\n', '\u{FEFF}', '\u{FDFF}', '\u{FE10}'] {
assert_eq!(vs_to_byte(c), None, "{c:?} was accepted");
assert!(!is_vs(c));
}
assert_eq!(vs_to_byte('\u{E01F0}'), None);
}
#[test]
fn run_round_trips_and_reports_its_end() {
let payload = [0xDEu8, 0xAD, 0xBE, 0xEF, 0x00, 0x0F, 0x10];
let run = encode_run(&payload);
let (decoded, end) = decode_run(&run);
assert_eq!(decoded, payload);
assert_eq!(end, run.len());
}
#[test]
fn run_stops_at_visible_text() {
let mut s = encode_run(&[0x01, 0x02]);
let run_len = s.len();
s.push_str("tail");
let (decoded, end) = decode_run(&s);
assert_eq!(decoded, vec![0x01, 0x02]);
assert_eq!(
end, run_len,
"end offset must be the first non-selector byte"
);
assert_eq!(&s[end..], "tail");
}
#[test]
fn empty_input_decodes_to_nothing() {
assert_eq!(decode_run(""), (Vec::new(), 0));
assert_eq!(encode_run(&[]), "");
}
}