base256b 1.0.1

encoder and decoder for base256 Braille
Documentation
#![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);
    }
}