1#![deny(missing_docs)]
2
3use std::{
10    error::Error,
11    fmt::{Debug, Display},
12};
13
14use ascii::{AsciiChar, AsciiStr, AsciiString};
15
16pub use ascii;
17
18const BRAILLE_ASCII_TO_UNICODE: [char; 96] = [
19    '⠀', '⠮', '⠐', '⠼', '⠫', '⠩', '⠯', '⠄', '⠷', '⠾', '⠡', '⠬', '⠠', '⠤', '⠨', '⠌', '⠴', '⠂', '⠆',
20    '⠒', '⠲', '⠢', '⠖', '⠶', '⠦', '⠔', '⠱', '⠰', '⠣', '⠿', '⠜', '⠹', '⠈', '⠁', '⠃', '⠉', '⠙', '⠑',
21    '⠋', '⠛', '⠓', '⠊', '⠚', '⠅', '⠇', '⠍', '⠝', '⠕', '⠏', '⠟', '⠗', '⠎', '⠞', '⠥', '⠧', '⠺', '⠭',
22    '⠽', '⠵', '⠪', '⠳', '⠻', '⠘', '⠸', '⠈', '⠁', '⠃', '⠉', '⠙', '⠑', '⠋', '⠛', '⠓', '⠊', '⠚', '⠅',
23    '⠇', '⠍', '⠝', '⠕', '⠏', '⠟', '⠗', '⠎', '⠞', '⠥', '⠧', '⠺', '⠭', '⠽', '⠵', '⠪', '⠳', '⠻', '⠘',
24    '⠸',
25];
26
27const BRAILLE_CELL_TO_BRAILLE_ASCII: &[u8; 64] =
28    b" A1B'K2L@CIF/MSP\"E3H9O6R^DJG>NTQ,*5<-U8V.%[$+X!&;:4\\0Z7(_?W]#Y)=";
29
30#[derive(Clone, Copy, PartialEq, Eq)]
36pub struct FromUnicodeError<T> {
37    index: usize,
38    source: T,
39}
40
41impl<T> Display for FromUnicodeError<T> {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        write!(
44            f,
45            "the char at index {} is not Braille or an ASCII control character",
46            self.index
47        )
48    }
49}
50
51impl<T> Debug for FromUnicodeError<T> {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        Display::fmt(self, f)
54    }
55}
56
57impl<T> Error for FromUnicodeError<T> {}
58
59#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
62pub struct BrailleAsciiString {
63    inner: AsciiString,
64}
65
66impl BrailleAsciiString {
67    pub fn new() -> Self {
69        Default::default()
70    }
71
72    pub fn from_ascii(s: AsciiString) -> Self {
74        BrailleAsciiString { inner: s }
75    }
76
77    pub fn from_unicode_braille<T: AsRef<str>>(t: T) -> Result<Self, FromUnicodeError<T>> {
82        let s = t.as_ref();
83        let mut bytes = Vec::with_capacity(s.chars().count());
84        for (i, c) in s.char_indices() {
85            if c <= ' ' {
86                bytes.push(c as u8);
87            } else if ('\u{2800}'..'\u{2840}').contains(&c) {
88                let j = (u32::from(c) - 0x2800) as u8;
89                bytes.push(BRAILLE_CELL_TO_BRAILLE_ASCII[usize::from(j)]);
90            } else {
91                return Err(FromUnicodeError {
92                    index: i,
93                    source: t,
94                });
95            }
96        }
97        Ok(BrailleAsciiString {
98            inner: unsafe { AsciiString::from_ascii_unchecked(bytes) },
99        })
100    }
101
102    pub fn as_ascii(&self) -> &AsciiStr {
104        &self.inner
105    }
106
107    pub fn as_ascii_mut(&mut self) -> &mut AsciiStr {
109        &mut self.inner
110    }
111
112    pub fn into_ascii(self) -> AsciiString {
114        self.inner
115    }
116
117    pub fn to_unicode_braille(&self) -> String {
119        self.inner
120            .chars()
121            .map(|ch| {
122                if ch < AsciiChar::Space {
123                    ch.as_char()
124                } else {
125                    let i = usize::from(ch.as_byte() - 0x20);
126                    BRAILLE_ASCII_TO_UNICODE[i]
127                }
128            })
129            .collect()
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    const TOM_SAWYER_ASCII: &str = r#"
138  ,,! ,,ADV5TURES ,,( ,,TOM ,,SAWY] 
139  ,,BY 
140  ,,M>K ,,TWA9 
141  7,SAMUEL ,LANGHORNE ,CLEM5S7   
142  ;,P ;,R ;,E ;,F ,A ;,C ;,E 
143  ,,MO/ (! ADV5TURES RECORD$ 9 ? BOOK
144RE,Y O3URR$2 "O OR TWO 7 EXP]I;ES ( MY
145[N1 ! RE/ ^? ( BOYS :O 7 S*OOLMATES (
146M9E4 ,HUCK ,F9N IS DRAWN F LIFE2 ,TOM
147,SAWY] AL1 B N F AN 9DIVIDUAL--HE IS A
148-B9A- TION (! "*I/ICS ( ?REE BOYS :OM ,I
149KNEW1 & "!=E 2L;GS 6! -POSITE ORD] (   
150>*I- TECTURE4  
151  ,! ODD SUP]/I;NS T\*$ ^U 7 ALL PREVA-
152L5T AM;G *N & SLAVES 9 ! ,WE/ AT ! P]IOD
153( ? /ORY--T IS 6SAY1 ?IRTY OR =TY YE>S
154AGO4   
155  ,AL? MY BOOK IS 9T5D$ MA9LY =! EN-
156T]TA9;T ( BOYS & GIRLS1 ,I HOPE X W N 2
157%UNN$ 0M5 & WOM5 ON T A3.T1 = "P ( MY
158PLAN HAS BE5 6TRY 6PL1SANTLY REM9D 
159ADULTS ( :AT !Y ONCE 7 !MVS1 &( H[ !Y
160FELT & ?"\ & TALK$1 & :AT QUE] 5T]PRISES
161!Y "S"TS 5GAG$ IN4    
162  ,,! ,,AU?OR4  
163  ,,H>T=D1 #AHGF4 
164  ;,T ,O ;,M ;,S ,A ;,W ;,Y ;,E ;,R    
165--------------------------------------#B
166"#;
167
168    const TOM_SAWYER_BRAILLE: &str = r#"
169⠀⠀⠠⠠⠮⠀⠠⠠⠁⠙⠧⠢⠞⠥⠗⠑⠎⠀⠠⠠⠷⠀⠠⠠⠞⠕⠍⠀⠠⠠⠎⠁⠺⠽⠻⠀
170⠀⠀⠠⠠⠃⠽⠀
171⠀⠀⠠⠠⠍⠜⠅⠀⠠⠠⠞⠺⠁⠔⠀
172⠀⠀⠶⠠⠎⠁⠍⠥⠑⠇⠀⠠⠇⠁⠝⠛⠓⠕⠗⠝⠑⠀⠠⠉⠇⠑⠍⠢⠎⠶⠀⠀⠀
173⠀⠀⠰⠠⠏⠀⠰⠠⠗⠀⠰⠠⠑⠀⠰⠠⠋⠀⠠⠁⠀⠰⠠⠉⠀⠰⠠⠑⠀
174⠀⠀⠠⠠⠍⠕⠌⠀⠷⠮⠀⠁⠙⠧⠢⠞⠥⠗⠑⠎⠀⠗⠑⠉⠕⠗⠙⠫⠀⠔⠀⠹⠀⠃⠕⠕⠅
175⠗⠑⠠⠽⠀⠕⠒⠥⠗⠗⠫⠆⠀⠐⠕⠀⠕⠗⠀⠞⠺⠕⠀⠶⠀⠑⠭⠏⠻⠊⠰⠑⠎⠀⠷⠀⠍⠽
176⠪⠝⠂⠀⠮⠀⠗⠑⠌⠀⠘⠹⠀⠷⠀⠃⠕⠽⠎⠀⠱⠕⠀⠶⠀⠎⠡⠕⠕⠇⠍⠁⠞⠑⠎⠀⠷
177⠍⠔⠑⠲⠀⠠⠓⠥⠉⠅⠀⠠⠋⠔⠝⠀⠊⠎⠀⠙⠗⠁⠺⠝⠀⠋⠀⠇⠊⠋⠑⠆⠀⠠⠞⠕⠍
178⠠⠎⠁⠺⠽⠻⠀⠁⠇⠂⠀⠃⠀⠝⠀⠋⠀⠁⠝⠀⠔⠙⠊⠧⠊⠙⠥⠁⠇⠤⠤⠓⠑⠀⠊⠎⠀⠁
179⠤⠃⠔⠁⠤⠀⠞⠊⠕⠝⠀⠷⠮⠀⠐⠡⠊⠌⠊⠉⠎⠀⠷⠀⠹⠗⠑⠑⠀⠃⠕⠽⠎⠀⠱⠕⠍⠀⠠⠊
180⠅⠝⠑⠺⠂⠀⠯⠀⠐⠮⠿⠑⠀⠆⠇⠰⠛⠎⠀⠖⠮⠀⠤⠏⠕⠎⠊⠞⠑⠀⠕⠗⠙⠻⠀⠷⠀⠀⠀
181⠜⠡⠊⠤⠀⠞⠑⠉⠞⠥⠗⠑⠲⠀⠀
182⠀⠀⠠⠮⠀⠕⠙⠙⠀⠎⠥⠏⠻⠌⠊⠰⠝⠎⠀⠞⠳⠡⠫⠀⠘⠥⠀⠶⠀⠁⠇⠇⠀⠏⠗⠑⠧⠁⠤
183⠇⠢⠞⠀⠁⠍⠰⠛⠀⠡⠝⠀⠯⠀⠎⠇⠁⠧⠑⠎⠀⠔⠀⠮⠀⠠⠺⠑⠌⠀⠁⠞⠀⠮⠀⠏⠻⠊⠕⠙
184⠷⠀⠹⠀⠌⠕⠗⠽⠤⠤⠞⠀⠊⠎⠀⠖⠎⠁⠽⠂⠀⠹⠊⠗⠞⠽⠀⠕⠗⠀⠿⠞⠽⠀⠽⠑⠜⠎
185⠁⠛⠕⠲⠀⠀⠀
186⠀⠀⠠⠁⠇⠹⠀⠍⠽⠀⠃⠕⠕⠅⠀⠊⠎⠀⠔⠞⠢⠙⠫⠀⠍⠁⠔⠇⠽⠀⠿⠮⠀⠑⠝⠤
187⠞⠻⠞⠁⠔⠰⠞⠀⠷⠀⠃⠕⠽⠎⠀⠯⠀⠛⠊⠗⠇⠎⠂⠀⠠⠊⠀⠓⠕⠏⠑⠀⠭⠀⠺⠀⠝⠀⠆
188⠩⠥⠝⠝⠫⠀⠴⠍⠢⠀⠯⠀⠺⠕⠍⠢⠀⠕⠝⠀⠞⠀⠁⠒⠨⠞⠂⠀⠿⠀⠐⠏⠀⠷⠀⠍⠽
189⠏⠇⠁⠝⠀⠓⠁⠎⠀⠃⠑⠢⠀⠖⠞⠗⠽⠀⠖⠏⠇⠂⠎⠁⠝⠞⠇⠽⠀⠗⠑⠍⠔⠙⠀
190⠁⠙⠥⠇⠞⠎⠀⠷⠀⠱⠁⠞⠀⠮⠽⠀⠕⠝⠉⠑⠀⠶⠀⠮⠍⠧⠎⠂⠀⠯⠷⠀⠓⠪⠀⠮⠽
191⠋⠑⠇⠞⠀⠯⠀⠹⠐⠳⠀⠯⠀⠞⠁⠇⠅⠫⠂⠀⠯⠀⠱⠁⠞⠀⠟⠥⠑⠻⠀⠢⠞⠻⠏⠗⠊⠎⠑⠎
192⠮⠽⠀⠐⠎⠐⠞⠎⠀⠢⠛⠁⠛⠫⠀⠊⠝⠲⠀⠀⠀⠀
193⠀⠀⠠⠠⠮⠀⠠⠠⠁⠥⠹⠕⠗⠲⠀⠀
194⠀⠀⠠⠠⠓⠜⠞⠿⠙⠂⠀⠼⠁⠓⠛⠋⠲⠀
195⠀⠀⠰⠠⠞⠀⠠⠕⠀⠰⠠⠍⠀⠰⠠⠎⠀⠠⠁⠀⠰⠠⠺⠀⠰⠠⠽⠀⠰⠠⠑⠀⠰⠠⠗⠀⠀⠀⠀
196⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠤⠼⠃
197"#;
198
199    #[test]
200    fn test_to_unicode_braille() {
201        let ascii = AsciiString::from_ascii(
202            " A1B'K2L@CIF/MSP\"E3H9O6R^DJG>NTQ,*5<-U8V.%[$+X!&;:4\\0Z7(_?W]#Y)=",
203        )
204        .unwrap();
205        let val = BrailleAsciiString::from_ascii(ascii);
206        let expected = "⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿";
207        assert_eq!(val.to_unicode_braille(), expected);
208
209        let ascii = AsciiString::from_ascii(TOM_SAWYER_ASCII).unwrap();
210        let tom_sawyer = BrailleAsciiString::from_ascii(ascii);
211
212        assert_eq!(tom_sawyer.to_unicode_braille(), TOM_SAWYER_BRAILLE);
213    }
214
215    #[test]
216    fn test_from_unicode_braille() {
217        let val = BrailleAsciiString::from_unicode_braille(
218            "⠀⠁⠂⠃⠄⠅⠆⠇⠈⠉⠊⠋⠌⠍⠎⠏⠐⠑⠒⠓⠔⠕⠖⠗⠘⠙⠚⠛⠜⠝⠞⠟⠠⠡⠢⠣⠤⠥⠦⠧⠨⠩⠪⠫⠬⠭⠮⠯⠰⠱⠲⠳⠴⠵⠶⠷⠸⠹⠺⠻⠼⠽⠾⠿",
219        )
220        .unwrap();
221        let expected = " A1B'K2L@CIF/MSP\"E3H9O6R^DJG>NTQ,*5<-U8V.%[$+X!&;:4\\0Z7(_?W]#Y)=";
222        assert_eq!(val.as_ascii().as_str(), expected);
223
224        let val = BrailleAsciiString::from_unicode_braille(TOM_SAWYER_BRAILLE).unwrap();
225        assert_eq!(val.as_ascii().as_str(), TOM_SAWYER_ASCII);
226    }
227}