base256u 1.1.5

Simple mapping between bytes and Unicode codepoints
Documentation
#![cfg_attr(not(test), no_std)]

/// Encoder iterator, converting bytes into unicode chars based on the contained encoding callable.
pub struct Encoder<I, F>
where
    I: Iterator<Item = u8>,
    F: Fn(u8) -> char,
{
    iterator: I,
    encode: F,
}

impl<I, F> Encoder<I, F>
where
    I: Iterator<Item = u8>,
    F: Fn(u8) -> char,
{
    pub fn new(iterator: I, encode: F) -> Self {
        Self { iterator, encode }
    }
}

impl<I, F> Iterator for Encoder<I, F>
where
    I: Iterator<Item = u8>,
    F: Fn(u8) -> char,
{
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        self.iterator.next().map(|byte| (self.encode)(byte))
    }
}

/// Encoder iterator, converting unicode chars into bytes based on the contained decoding callable.
/// Must be fallible because not all unicode codepoints are valid bytes.
pub struct Decoder<I, F>
where
    I: Iterator<Item = char>,
    F: Fn(char) -> Option<u8>,
{
    iterator: I,
    decode: F,
}

impl<I, F> Decoder<I, F>
where
    I: Iterator<Item = char>,
    F: Fn(char) -> Option<u8>,
{
    pub fn new(iterator: I, decode: F) -> Self {
        Self { iterator, decode }
    }
}

impl<I, F> Iterator for Decoder<I, F>
where
    I: Iterator<Item = char>,
    F: Fn(char) -> Option<u8>,
{
    type Item = Option<u8>;

    fn next(&mut self) -> Option<Self::Item> {
        self.iterator.next().map(|byte| (self.decode)(byte))
    }
}

/// Encode function for encoding to printable-ascii-preserving Unicode.
/// `0x00..=0x1F` is mapped to the range starting at `U+B0` to map into the Latin-1 Supplement block in the first range that is 8-byte aligned and fully printable, skipping `NBSP` and `SHY`.
/// `0x20..=0x7E` are mapped to the same bytes as printable ASCII.
/// `0x7F` is arbitrarily mapped from ASCII ESC to §.
/// `0x80..=0xFF`  mapped to the range starting at `U+100`, Latin Extended-A, with the exception of
/// `0xC9`, which is mapped arbitrarily to `¤` to avoid the deprecated character at that codepoint.
pub fn encode_papu(byte: u8) -> char {
    match byte {
        b @ 0x00..=0x1F => (b + 0xB0) as char,
        b @ 0x20..=0x7E => b as char,
        0x7F => '§',
        // Unsafe is fine here because these ranges are known to be safe char values.
        b @ (0x80..=0xC8 | 0xCA..) => unsafe { char::from_u32_unchecked(b as u32 + 0x80) },
        0xC9 => '¤',
    }
}

/// Decode function for printable-ascii-preserving Unicode.
/// All values are mapped as the inverse of encode_papu.  All other input chars map to None.
pub const fn decode_papu(c: char) -> Option<u8> {
    Some(match c as u32 {
        b @ 0xB0..=0xCF => (b - 0xB0) as u8,
        b @ 0x20..=0x7E => b as u8,
        0xA7 => 0x7F,
        b @ (0x100..=0x148 | 0x14A..=0x17F) => (b - 0x80) as u8,
        0xA4 => 0xC9,
        _ => return None,
    })
}

/// Encode function for encoding to emoji.
/// `0x00..=0x4F` is mapped to the range starting at `U+1F370` For some plants and foods.
/// `0x50..=0x8F` is mapped to the range starting at `U+1F400` for animals.
/// `0x90..=0xDF` is mapped to the range starting at `U+1F600` for expressions and hand signs.
/// `0xE0..=0xFF` is mapped to the range starting at `U+1F910` for more expressions and hand signs.
pub fn encode_emoji(byte: u8) -> char {
    match byte {
        b @ 0x00..=0x4F => unsafe { char::from_u32_unchecked(b as u32 + 0x1F330) },
        b @ 0x50..=0x8F => unsafe { char::from_u32_unchecked(b as u32 + (0x1F400 - 0x50)) },
        b @ 0x90..=0xDF => unsafe { char::from_u32_unchecked(b as u32 + (0x1F600 - 0x90)) },
        b @ 0xE0..=0xFF => unsafe { char::from_u32_unchecked(b as u32 + (0x1F910 - 0xE0)) },
    }
}

/// Decode function for emoji.
/// All values are mapped as the inverse of encode_emoji.  All other input chars map to None.
pub const fn decode_emoji(c: char) -> Option<u8> {
    Some(match c as u32 {
        b @ 0x1F330..=0x1F37F => (b - 0x1F330) as u8,
        b @ 0x1F400..=0x1F43F => (b - (0x1F400 - 0x50)) as u8,
        b @ 0x1F600..=0x1F64F => (b - (0x1F600 - 0x90)) as u8,
        b @ 0x1F910..=0x1F92F => (b - (0x1F910 - 0xE0)) as u8,
        _ => return None,
    })
}

pub trait Encode: Iterator<Item = u8>
where
    Self: Sized,
{
    fn base256u<F>(self, function: F) -> Encoder<Self, F>
    where
        F: Fn(u8) -> char,
    {
        Encoder::new(self, function)
    }

    fn base256u_papu(self) -> Encoder<Self, fn(u8) -> char> {
        self.base256u(encode_papu)
    }
    fn base256u_emoji(self) -> Encoder<Self, fn(u8) -> char> {
        self.base256u(encode_emoji)
    }
}

impl<T> Encode for T where T: Iterator<Item = u8> {}

pub trait Decode: Iterator<Item = char>
where
    Self: Sized,
{
    fn base256u<F>(self, function: F) -> Decoder<Self, F>
    where
        F: Fn(char) -> Option<u8>,
    {
        Decoder::new(self, function)
    }

    fn base256u_papu(self) -> Decoder<Self, fn(char) -> Option<u8>> {
        self.base256u(decode_papu)
    }
    fn base256u_emoji(self) -> Decoder<Self, fn(char) -> Option<u8>> {
        self.base256u(decode_emoji)
    }
}

impl<T> Decode for T where T: Iterator<Item = char> {}

#[cfg(test)]
mod tests {
    use crate::{Decode, Encode};

    #[test]
    fn encoding_papu() {
        let encoded: String = (u8::MIN..=u8::MAX).base256u_papu().collect();
        assert_eq!(encoded, "°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~§ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇň¤ŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ");
        let encoded: String = b"Pack my box with five dozen liquor jugs."
            .into_iter()
            .copied()
            .base256u_papu()
            .collect();
        assert_eq!(encoded, "Pack my box with five dozen liquor jugs.");
    }

    #[test]
    fn decoding_papu() {
        let decoded: Vec<Option<u8>> = "°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~§ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿŀŁłŃńŅņŇň¤ŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞşŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſƝʼn".chars().base256u_papu().collect();
        let mut matcher: Vec<Option<u8>> = (u8::MIN..=u8::MAX).map(|b| Some(b)).collect();
        matcher.push(None);
        matcher.push(None);
        assert_eq!(decoded, matcher);
        let decoded: Vec<u8> = "Pack my box with five dozen liquor jugs."
            .chars()
            .base256u_papu()
            .map(|c| c.unwrap())
            .collect();
        assert_eq!(
            decoded.as_slice(),
            b"Pack my box with five dozen liquor jugs."
        );
    }
    #[test]
    fn encoding_emoji() {
        let encoded: String = (u8::MIN..=u8::MAX).base256u_emoji().collect();
        assert_eq!(encoded, "🌰🌱🌲🌳🌴🌵🌶🌷🌸🌹🌺🌻🌼🌽🌾🌿🍀🍁🍂🍃🍄🍅🍆🍇🍈🍉🍊🍋🍌🍍🍎🍏🍐🍑🍒🍓🍔🍕🍖🍗🍘🍙🍚🍛🍜🍝🍞🍟🍠🍡🍢🍣🍤🍥🍦🍧🍨🍩🍪🍫🍬🍭🍮🍯🍰🍱🍲🍳🍴🍵🍶🍷🍸🍹🍺🍻🍼🍽🍾🍿🐀🐁🐂🐃🐄🐅🐆🐇🐈🐉🐊🐋🐌🐍🐎🐏🐐🐑🐒🐓🐔🐕🐖🐗🐘🐙🐚🐛🐜🐝🐞🐟🐠🐡🐢🐣🐤🐥🐦🐧🐨🐩🐪🐫🐬🐭🐮🐯🐰🐱🐲🐳🐴🐵🐶🐷🐸🐹🐺🐻🐼🐽🐾🐿😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮😯😰😱😲😳😴😵😶😷😸😹😺😻😼😽😾😿🙀🙁🙂🙃🙄🙅🙆🙇🙈🙉🙊🙋🙌🙍🙎🙏🤐🤑🤒🤓🤔🤕🤖🤗🤘🤙🤚🤛🤜🤝🤞🤟🤠🤡🤢🤣🤤🤥🤦🤧🤨🤩🤪🤫🤬🤭🤮🤯");
        let encoded: String = b"Pack my box with five dozen liquor jugs."
            .into_iter()
            .copied()
            .base256u_emoji()
            .collect();
        assert_eq!(
            encoded,
            "🐀🐑🐓🐛🍐🐝🐩🍐🐒🐟🐨🍐🐧🐙🐤🐘🍐🐖🐙🐦🐕🍐🐔🐟🐪🐕🐞🍐🐜🐙🐡🐥🐟🐢🍐🐚🐥🐗🐣🍞"
        );
    }
    #[test]
    fn decoding_emoji() {
        let decoded: Vec<Option<u8>> = "🌰🌱🌲🌳🌴🌵🌶🌷🌸🌹🌺🌻🌼🌽🌾🌿🍀🍁🍂🍃🍄🍅🍆🍇🍈🍉🍊🍋🍌🍍🍎🍏🍐🍑🍒🍓🍔🍕🍖🍗🍘🍙🍚🍛🍜🍝🍞🍟🍠🍡🍢🍣🍤🍥🍦🍧🍨🍩🍪🍫🍬🍭🍮🍯🍰🍱🍲🍳🍴🍵🍶🍷🍸🍹🍺🍻🍼🍽🍾🍿🐀🐁🐂🐃🐄🐅🐆🐇🐈🐉🐊🐋🐌🐍🐎🐏🐐🐑🐒🐓🐔🐕🐖🐗🐘🐙🐚🐛🐜🐝🐞🐟🐠🐡🐢🐣🐤🐥🐦🐧🐨🐩🐪🐫🐬🐭🐮🐯🐰🐱🐲🐳🐴🐵🐶🐷🐸🐹🐺🐻🐼🐽🐾🐿😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮😯😰😱😲😳😴😵😶😷😸😹😺😻😼😽😾😿🙀🙁🙂🙃🙄🙅🙆🙇🙈🙉🙊🙋🙌🙍🙎🙏🤐🤑🤒🤓🤔🤕🤖🤗🤘🤙🤚🤛🤜🤝🤞🤟🤠🤡🤢🤣🤤🤥🤦🤧🤨🤩🤪🤫🤬🤭🤮🤯Ɲʼn".chars().base256u_emoji().collect();
        let mut matcher: Vec<Option<u8>> = (u8::MIN..=u8::MAX).map(|b| Some(b)).collect();
        matcher.push(None);
        matcher.push(None);
        assert_eq!(decoded, matcher);
        let decoded: Vec<u8> =
            "🐀🐑🐓🐛🍐🐝🐩🍐🐒🐟🐨🍐🐧🐙🐤🐘🍐🐖🐙🐦🐕🍐🐔🐟🐪🐕🐞🍐🐜🐙🐡🐥🐟🐢🍐🐚🐥🐗🐣🍞"
                .chars()
                .base256u_emoji()
                .map(|c| c.unwrap())
                .collect();
        assert_eq!(
            decoded.as_slice(),
            b"Pack my box with five dozen liquor jugs."
        );
    }
}