#![cfg_attr(not(any(test, feature = "use-std")), no_std)]
#![cfg_attr(not(doctest), doc = include_str!("../README.md"))]
/// `Hello World` => `⡈⡥⡬⡬⡯⠠⡗⡯⡲⡬⡤⠡`
///
/// The output buffer needs to be at least 3 times bigger then the input buffer.
///
/// Returns how many bytes were written to the output buffer.
pub const fn encode_bytes(bytes: &[u8], output: &mut [u8]) -> Option<usize> {
let Some(needed_output) = bytes.len().checked_mul(3) else {
return None;
};
if needed_output > output.len() {
return None;
}
let mut i = 0usize;
while i < needed_output {
let byte = bytes[i / 3];
output[i] = 0xe2;
output[i + 1] = 0xa0 | (byte >> 6);
output[i + 2] = 0x80 | (byte & 0x3f);
i += 3;
}
Some(needed_output)
}
/// `⡈⡥⡬⡬⡯⠠⡗⡯⡲⡬⡤⠡` => `Hello World`
///
/// The output buffer needs to be at least a third of the input buffer.
///
/// Returns how many bytes were written to the output buffer.
pub const fn decode_bytes(bytes: &[u8], output: &mut [u8]) -> Option<usize> {
let Some(needed_output) = bytes.len().checked_div(3) else {
return None;
};
if needed_output > output.len() {
return None;
}
let mut i = 0usize;
while i < needed_output {
let s = i * 3;
if bytes[s] != 0xe2 {
return Some(i);
}
let b2 = bytes[s + 1];
let b1 = bytes[s + 2];
if b2 < 0xa0 || b2 > 0xa3 || b1 < 0x80 || b1 > 0xbf {
return Some(i);
}
output[i] = ((b2 ^ 0xa0) << 6) | (b1 ^ 0x80);
i += 1;
}
Some(i)
}
#[cfg(feature = "use-std")]
/// Panics: if the `bytes.len()` cannot be multiplied by 3!
///
/// Panics: if the needed memory cannot be allocated!
pub fn encode(bytes: &[u8]) -> String {
let mut output = vec![0u8; bytes.len() * 3];
assert_ne!(encode_bytes(bytes, output.as_mut_slice()), None);
match String::from_utf8(output) {
Ok(out) => out,
Err(_) => unreachable!(),
}
}
#[cfg(feature = "use-std")]
/// Panics: if the `text.as_bytes().len()` cannot be divided by 3!
///
/// Panics: if the needed memory cannot be allocated!
pub fn decode(text: &str) -> Vec<u8> {
let bytes = text.as_bytes();
let mut output = vec![0u8; bytes.len() / 3];
assert_ne!(decode_bytes(bytes, output.as_mut_slice()), None);
output
}
#[cfg(test)]
mod tests {
#[test]
fn encode_decode() {
const INPUT: [u8; 256] = const {
let mut out = [0u8; 256];
let mut i = 0;
loop {
out[i as usize] = i;
if i == 255 {
break;
}
i += 1;
}
out
};
const OUTPUT: &str = "⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿⡀⡁⡂⡃⡄⡅⡆⡇⡈⡉⡊⡋⡌⡍⡎⡏⡐⡑⡒⡓⡔⡕⡖⡗⡘⡙⡚⡛⡜⡝⡞⡟⡠⡡⡢⡣⡤⡥⡦⡧⡨⡩⡪⡫⡬⡭⡮⡯⡰⡱⡲⡳⡴⡵⡶⡷⡸⡹⡺⡻⡼⡽⡾⡿⢀⢁⢂⢃⢄⢅⢆⢇⢈⢉⢊⢋⢌⢍⢎⢏⢐⢑⢒⢓⢔⢕⢖⢗⢘⢙⢚⢛⢜⢝⢞⢟⢠⢡⢢⢣⢤⢥⢦⢧⢨⢩⢪⢫⢬⢭⢮⢯⢰⢱⢲⢳⢴⢵⢶⢷⢸⢹⢺⢻⢼⢽⢾⢿⣀⣁⣂⣃⣄⣅⣆⣇⣈⣉⣊⣋⣌⣍⣎⣏⣐⣑⣒⣓⣔⣕⣖⣗⣘⣙⣚⣛⣜⣝⣞⣟⣠⣡⣢⣣⣤⣥⣦⣧⣨⣩⣪⣫⣬⣭⣮⣯⣰⣱⣲⣳⣴⣵⣶⣷⣸⣹⣺⣻⣼⣽⣾⣿";
let mut output = [0u8; 256 * 3];
assert_eq!(
crate::encode_bytes(&INPUT, &mut output),
Some(INPUT.len() * 3)
);
assert_eq!(&output, OUTPUT.as_bytes());
let mut out = [0u8; 256];
assert_eq!(crate::decode_bytes(&output, &mut out), Some(INPUT.len()));
assert_eq!(&INPUT, &out);
}
#[test]
fn decode_partial() {
const INPUT: &str = "⣹⣺⣻⣼⣽⣾⣿testing";
const EXPECTED_OUTPUT: [u8; 7] = [249, 250, 251, 252, 253, 254, 255];
let mut output = [0u8; 9];
assert_eq!(crate::decode_bytes(INPUT.as_bytes(), &mut output), Some(7));
assert_eq!(&output[0..7], &EXPECTED_OUTPUT);
}
}