fluent_pseudo/
lib.rs

1use regex::Captures;
2use regex::Regex;
3use std::borrow::Cow;
4
5static TRANSFORM_SMALL_MAP: &[char] = &[
6    'a', 'ƀ', 'ƈ', 'ḓ', 'e', 'ƒ', 'ɠ', 'ħ', 'i', 'ĵ', 'ķ', 'ŀ', 'ḿ', 'ƞ', 'o', 'ƥ', 'ɋ', 'ř', 'ş',
7    'ŧ', 'u', 'ṽ', 'ẇ', 'ẋ', 'ẏ', 'ẑ',
8];
9static TRANSFORM_CAPS_MAP: &[char] = &[
10    'A', 'Ɓ', 'Ƈ', 'Ḓ', 'E', 'Ƒ', 'Ɠ', 'Ħ', 'I', 'Ĵ', 'Ķ', 'Ŀ', 'Ḿ', 'Ƞ', 'O', 'Ƥ', 'Ɋ', 'Ř', 'Ş',
11    'Ŧ', 'U', 'Ṽ', 'Ẇ', 'Ẋ', 'Ẏ', 'Ẑ',
12];
13
14static FLIPPED_SMALL_MAP: &[char] = &[
15    'ɐ', 'q', 'ɔ', 'p', 'ǝ', 'ɟ', 'ƃ', 'ɥ', 'ı', 'ɾ', 'ʞ', 'ʅ', 'ɯ', 'u', 'o', 'd', 'b', 'ɹ', 's',
16    'ʇ', 'n', 'ʌ', 'ʍ', 'x', 'ʎ', 'z',
17];
18static FLIPPED_CAPS_MAP: &[char] = &[
19    '∀', 'Ԑ', 'Ↄ', 'ᗡ', 'Ǝ', 'Ⅎ', '⅁', 'H', 'I', 'ſ', 'Ӽ', '⅂', 'W', 'N', 'O', 'Ԁ', 'Ò', 'ᴚ', 'S',
20    '⊥', '∩', 'Ʌ', 'M', 'X', '⅄', 'Z',
21];
22
23static mut RE_EXCLUDED: Option<Regex> = None;
24static mut RE_AZ: Option<Regex> = None;
25
26pub fn transform_dom(s: &str, flipped: bool, elongate: bool, with_markers: bool) -> Cow<str> {
27    // Exclude access-keys and other single-char messages
28    if s.len() == 1 {
29        return s.into();
30    }
31
32    // XML entities (&#x202a;) and XML tags.
33    let re_excluded =
34        unsafe { RE_EXCLUDED.get_or_insert_with(|| Regex::new(r"&[#\w]+;|<\s*.+?\s*>").unwrap()) };
35
36    let mut result = Cow::from(s);
37
38    let mut pos = 0;
39    let mut diff = 0;
40
41    for cap in re_excluded.captures_iter(s) {
42        let capture = cap.get(0).unwrap();
43
44        let sub_len = capture.start() - pos;
45        let range = pos..capture.start();
46        let result_range = pos + diff..capture.start() + diff;
47        let sub = &s[range.clone()];
48        let transform_sub = transform(sub, false, true);
49        diff += transform_sub.len() - sub_len;
50        result
51            .to_mut()
52            .replace_range(result_range.clone(), &transform_sub);
53        pos = capture.end();
54    }
55    let range = pos..s.len();
56    let result_range = pos + diff..result.len();
57    let transform_sub = transform(&s[range], flipped, elongate);
58    result.to_mut().replace_range(result_range, &transform_sub);
59
60    if with_markers {
61        return Cow::from("[") + result + "]";
62    }
63
64    result
65}
66
67pub fn transform(s: &str, flipped: bool, elongate: bool) -> Cow<str> {
68    let re_az = unsafe { RE_AZ.get_or_insert_with(|| Regex::new(r"[a-zA-Z]").unwrap()) };
69
70    let (small_map, caps_map) = if flipped {
71        (FLIPPED_SMALL_MAP, FLIPPED_CAPS_MAP)
72    } else {
73        (TRANSFORM_SMALL_MAP, TRANSFORM_CAPS_MAP)
74    };
75
76    re_az.replace_all(s, |caps: &Captures| {
77        let ch = caps[0].chars().next().unwrap();
78        let cc = ch as u8;
79        if (97..=122).contains(&cc) {
80            let pos = cc - 97;
81            let new_char = small_map[pos as usize];
82            // duplicate "a", "e", "o" and "u" to emulate ~30% longer text
83            if elongate && (cc == 97 || cc == 101 || cc == 111 || cc == 117) {
84                let mut s = new_char.to_string();
85                s.push(new_char);
86                s
87            } else {
88                new_char.to_string()
89            }
90        } else if (65..=90).contains(&cc) {
91            let pos = cc - 65;
92            let new_char = caps_map[pos as usize];
93            new_char.to_string()
94        } else {
95            ch.to_string()
96        }
97    })
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn it_works() {
106        let x = transform("Hello World", false, true);
107        assert_eq!(x, "Ħeeŀŀoo Ẇoořŀḓ");
108
109        let x = transform("Hello World", false, false);
110        assert_eq!(x, "Ħeŀŀo Ẇořŀḓ");
111
112        let x = transform("Hello World", true, false);
113        assert_eq!(x, "Hǝʅʅo Moɹʅp");
114
115        let x = transform("f", false, true);
116        assert_eq!(x, "ƒ");
117    }
118
119    #[test]
120    fn dom_test() {
121        let x = transform_dom("Hello <a>World</a>", false, true, false);
122        assert_eq!(x, "Ħeeŀŀoo <a>Ẇoořŀḓ</a>");
123
124        let x = transform_dom("Hello <a>World</a> in <b>my</b> House.", false, true, false);
125        assert_eq!(x, "Ħeeŀŀoo <a>Ẇoořŀḓ</a> iƞ <b>ḿẏ</b> Ħoouuşee.");
126
127        // Use markers.
128        let x = transform_dom("Hello World within markers", false, false, true);
129        assert_eq!(x, "[Ħeŀŀo Ẇořŀḓ ẇiŧħiƞ ḿařķeřş]");
130
131        // Don't touch single character values.
132        let x = transform_dom("f", false, true, false);
133        assert_eq!(x, "f");
134    }
135}