mojibake/
encode.rs

1use std::io::{self, Read, Write};
2
3use crate::{EMOJI_MAP, TAIL_MAP};
4
5#[inline]
6fn bytes_to_emojis<'a>(stage: &mut u16, remaining: &mut u8, byte: u8) -> Option<&'a str> {
7    let byte = u16::from(byte);
8    let need = 11 - *remaining;
9    if need <= 8 {
10        *remaining = 8 - need;
11        let index = (*stage << need) | (byte >> *remaining);
12        let emoji = EMOJI_MAP
13            .get(&index)
14            .expect("Somehow Unicode got rid of some emoji characters");
15
16        *stage = byte & ((1 << *remaining) - 1);
17        Some(emoji)
18    } else {
19        *stage = (*stage << 8) | byte;
20        *remaining += 8;
21        None
22    }
23}
24
25#[inline]
26fn handle_remaining_bits<'a>(stage: u16, remaining: u8) -> Option<&'a str> {
27    if remaining == 0 {
28        return None;
29    }
30    let emoji = {
31        if remaining <= 3 {
32            TAIL_MAP
33                .get(&stage)
34                .expect("Somehow Unicode got rid of some emoji characters")
35        } else {
36            EMOJI_MAP
37                .get(&stage)
38                .expect("Somehow Unicode got rid of some emoji characters")
39        }
40    };
41    Some(emoji)
42}
43
44#[inline]
45fn push_str(source: Option<&str>, dst: &mut String) {
46    if let Some(str) = source {
47        dst.push_str(str);
48    }
49}
50
51#[inline]
52fn write_str(source: Option<&str>, dst: &mut impl Write) -> io::Result<()> {
53    if let Some(str) = source {
54        dst.write_all(str.as_bytes())?;
55    }
56    Ok(())
57}
58
59/// Encodes a byte array into a string representation using a defined emoji map.
60///
61/// This function is designed to convert bytes into a specific set of emoji
62/// characters, providing a fun and unique way of representing data.
63///
64/// It accepts any type that implements `AsRef<[u8]>` (like `&[u8]` and `Vec<u8>`)
65/// and returns the encoded data as a `String`. Each input byte is mapped
66/// to a specific emoji character, with the help of two maps: `EMOJI_MAP` and `TAIL_MAP`.
67///
68/// # Arguments
69///
70/// * `bytes` - A byte array to be encoded into emojis.
71///
72/// # Returns
73///
74/// A `String` containing the emoji representation of the input byte array.
75///
76/// # Panics
77///
78/// This function will panic if any emoji character needed for encoding is not
79/// present in `EMOJI_MAP` or `TAIL_MAP`. This is not expected to ever happen.
80///
81/// # Example
82/// ```rust
83/// use mojibake::encode;
84///
85/// let bytes = vec![0x31, 0x32, 0x33];
86/// let encoded = encode(bytes);
87/// println!("{}", encoded);  // Prints the emoji representation of the byte array.
88/// ```
89pub fn encode(bytes: impl AsRef<[u8]>) -> String {
90    let bytes = bytes.as_ref();
91    let mut output = String::new();
92    let mut stage = 0x0000u16;
93    let mut remaining = 0;
94
95    for byte in bytes {
96        push_str(
97            bytes_to_emojis(&mut stage, &mut remaining, *byte),
98            &mut output,
99        );
100    }
101    push_str(handle_remaining_bits(stage, remaining), &mut output);
102
103    output
104}
105
106/// Encodes a byte stream into a string representation using a defined emoji map.
107///
108/// This function is designed to convert bytes into a specific set of emoji
109/// characters, providing a fun and unique way of representing data.
110///
111/// It accepts any type that implements `Read` (for reading bytes)
112/// and `Write` (for writing the resulting emojis),
113/// and returns a `Result<(), io::Error>`. Each input byte is mapped
114/// to a specific emoji character, with the help of two maps: `EMOJI_MAP` and `TAIL_MAP`.
115///
116/// # Arguments
117///
118/// * `reader` - An object implementing `Read` to read bytes from.
119/// * `writer` - An object implementing `Write` to write emojis to.
120///
121/// # Returns
122///
123/// An `io::Result<()>`. If it is `Err`, an error occurred while reading or writing.
124///
125/// # Panics
126///
127/// This function will panic if any emoji character needed for encoding is not
128/// present in `EMOJI_MAP` or `TAIL_MAP`. This is not expected to ever happen.
129///
130/// # Example
131/// ```rust
132/// use mojibake::encode_stream;
133/// use std::io::Cursor;
134///
135/// let input = vec![0x31, 0x32, 0x33];
136/// let mut reader = Cursor::new(input);
137/// let mut writer = Cursor::new(Vec::new());
138///
139/// encode_stream(&mut reader, &mut writer).expect("encoding failed");
140/// println!("{}", String::from_utf8(writer.into_inner()).unwrap());
141/// ```
142#[allow(clippy::module_name_repetitions)]
143pub fn encode_stream<R: Read, W: Write>(reader: &mut R, mut writer: &mut W) -> io::Result<()> {
144    let mut buffer = [0; 2]; // read two bytes at a time
145    let mut stage = 0x0000u16;
146    let mut remaining = 0;
147
148    while let Ok(n) = reader.read(&mut buffer) {
149        if n == 0 {
150            break;
151        }
152        for byte in buffer.iter().take(n) {
153            write_str(
154                bytes_to_emojis(&mut stage, &mut remaining, *byte),
155                &mut writer,
156            )?;
157        }
158    }
159
160    write_str(handle_remaining_bits(stage, remaining), &mut writer)?;
161
162    Ok(())
163}