Skip to main content

katex_parser/
unicode_scripts.rs

1use std::collections::HashMap;
2use std::sync::OnceLock;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5/// Whether a Unicode script is a subscript or superscript.
6pub enum UnicodeScriptKind {
7    UnicodeSubscript,
8    UnicodeSuperscript,
9}
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct UnicodeScript {
13    pub replacement: String,
14    pub kind: UnicodeScriptKind,
15}
16
17fn unicode_scripts() -> &'static HashMap<String, UnicodeScript> {
18    static TABLE: OnceLock<HashMap<String, UnicodeScript>> = OnceLock::new();
19    TABLE.get_or_init(|| {
20        let mut map = HashMap::new();
21        fn sub(map: &mut HashMap<String, UnicodeScript>, c: &str, r: &str) {
22            map.insert(
23                c.to_string(),
24                UnicodeScript {
25                    replacement: r.to_string(),
26                    kind: UnicodeScriptKind::UnicodeSubscript,
27                },
28            );
29        }
30        fn sup(map: &mut HashMap<String, UnicodeScript>, c: &str, r: &str) {
31            map.insert(
32                c.to_string(),
33                UnicodeScript {
34                    replacement: r.to_string(),
35                    kind: UnicodeScriptKind::UnicodeSuperscript,
36                },
37            );
38        }
39        sub(&mut map, "₊", "+");
40        sub(&mut map, "₋", "-");
41        sub(&mut map, "₌", "=");
42        sub(&mut map, "₍", "(");
43        sub(&mut map, "₎", ")");
44        sub(&mut map, "₀", "0");
45        sub(&mut map, "₁", "1");
46        sub(&mut map, "₂", "2");
47        sub(&mut map, "₃", "3");
48        sub(&mut map, "₄", "4");
49        sub(&mut map, "₅", "5");
50        sub(&mut map, "₆", "6");
51        sub(&mut map, "₇", "7");
52        sub(&mut map, "₈", "8");
53        sub(&mut map, "₉", "9");
54        sub(&mut map, "ₐ", "a");
55        sub(&mut map, "ₑ", "e");
56        sub(&mut map, "ₕ", "h");
57        sub(&mut map, "ᵢ", "i");
58        sub(&mut map, "ⱼ", "j");
59        sub(&mut map, "ₖ", "k");
60        sub(&mut map, "ₗ", "l");
61        sub(&mut map, "ₘ", "m");
62        sub(&mut map, "ₙ", "n");
63        sub(&mut map, "ₒ", "o");
64        sub(&mut map, "ₚ", "p");
65        sub(&mut map, "ᵣ", "r");
66        sub(&mut map, "ₛ", "s");
67        sub(&mut map, "ₜ", "t");
68        sub(&mut map, "ᵤ", "u");
69        sub(&mut map, "ᵥ", "v");
70        sub(&mut map, "ₓ", "x");
71        sub(&mut map, "ᵦ", "β");
72        sub(&mut map, "ᵧ", "γ");
73        sub(&mut map, "ᵨ", "ρ");
74        sub(&mut map, "ᵩ", "ϕ");
75        sub(&mut map, "ᵪ", "χ");
76        sup(&mut map, "⁺", "+");
77        sup(&mut map, "⁻", "-");
78        sup(&mut map, "⁼", "=");
79        sup(&mut map, "⁽", "(");
80        sup(&mut map, "⁾", ")");
81        sup(&mut map, "⁰", "0");
82        sup(&mut map, "¹", "1");
83        sup(&mut map, "²", "2");
84        sup(&mut map, "³", "3");
85        sup(&mut map, "⁴", "4");
86        sup(&mut map, "⁵", "5");
87        sup(&mut map, "⁶", "6");
88        sup(&mut map, "⁷", "7");
89        sup(&mut map, "⁸", "8");
90        sup(&mut map, "⁹", "9");
91        sup(&mut map, "ᴬ", "A");
92        sup(&mut map, "ᴮ", "B");
93        sup(&mut map, "ᴰ", "D");
94        sup(&mut map, "ᴱ", "E");
95        sup(&mut map, "ᴳ", "G");
96        sup(&mut map, "ᴴ", "H");
97        sup(&mut map, "ᴵ", "I");
98        sup(&mut map, "ᴶ", "J");
99        sup(&mut map, "ᴷ", "K");
100        sup(&mut map, "ᴸ", "L");
101        sup(&mut map, "ᴹ", "M");
102        sup(&mut map, "ᴺ", "N");
103        sup(&mut map, "ᴼ", "O");
104        sup(&mut map, "ᴾ", "P");
105        sup(&mut map, "ᴿ", "R");
106        sup(&mut map, "ᵀ", "T");
107        sup(&mut map, "ᵁ", "U");
108        sup(&mut map, "ⱽ", "V");
109        sup(&mut map, "ᵂ", "W");
110        sup(&mut map, "ᵃ", "a");
111        sup(&mut map, "ᵇ", "b");
112        sup(&mut map, "ᶜ", "c");
113        sup(&mut map, "ᵈ", "d");
114        sup(&mut map, "ᵉ", "e");
115        sup(&mut map, "ᶠ", "f");
116        sup(&mut map, "ᵍ", "g");
117        sup(&mut map, "ʰ", "h");
118        sup(&mut map, "ⁱ", "i");
119        sup(&mut map, "ʲ", "j");
120        sup(&mut map, "ᵏ", "k");
121        sup(&mut map, "ˡ", "l");
122        sup(&mut map, "ᵐ", "m");
123        sup(&mut map, "ⁿ", "n");
124        sup(&mut map, "ᵒ", "o");
125        sup(&mut map, "ᵖ", "p");
126        sup(&mut map, "ʳ", "r");
127        sup(&mut map, "ˢ", "s");
128        sup(&mut map, "ᵗ", "t");
129        sup(&mut map, "ᵘ", "u");
130        sup(&mut map, "ᵛ", "v");
131        sup(&mut map, "ʷ", "w");
132        sup(&mut map, "ˣ", "x");
133        sup(&mut map, "ʸ", "y");
134        sup(&mut map, "ᶻ", "z");
135        sup(&mut map, "ᵝ", "β");
136        sup(&mut map, "ᵞ", "γ");
137        sup(&mut map, "ᵟ", "δ");
138        sup(&mut map, "ᵠ", "ϕ");
139        sup(&mut map, "ᵡ", "χ");
140        sup(&mut map, "ᶿ", "θ");
141        map
142    })
143}
144
145pub(crate) fn lookup_unicode_script(text: &str) -> Option<&'static UnicodeScript> {
146    unicode_scripts().get(text)
147}
148
149/// Returns the Unicode superscript or subscript character for `replacement`.
150pub fn unicode_script_character(kind: UnicodeScriptKind, replacement: &str) -> Option<String> {
151    unicode_scripts()
152        .iter()
153        .find(|(_, script)| script.kind == kind && script.replacement == replacement)
154        .map(|(character, _)| character.clone())
155}
156
157/// True when the codepoint falls within one of the supported Unicode script
158/// blocks. Mirrors KaTeX's `supportedCodepoint` (unicodeScripts.ts).
159pub fn supported_codepoint(code: u32) -> bool {
160    (0x0100..=0x024F).contains(&code) || // Latin Extended-A/B
161    (0x0300..=0x036F).contains(&code) || // Combining Diacritical marks
162    (0x0400..=0x04FF).contains(&code) || // Cyrillic
163    (0x0530..=0x058F).contains(&code) || // Armenian
164    (0x0900..=0x109F).contains(&code) || // Brahmic
165    (0x10A0..=0x10FF).contains(&code) || // Georgian
166    (0x3000..=0x30FF).contains(&code) || // CJK symbols, Hiragana, Katakana
167    (0x4E00..=0x9FAF).contains(&code) || // CJK ideograms
168    (0xFF00..=0xFF60).contains(&code) || // Fullwidth punctuation
169    (0xAC00..=0xD7AF).contains(&code) // Hangul
170}