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}