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