1pub fn symbol_code(c: char) -> Option<u8> {
17 let u = c as u32;
18 if matches!(
20 c,
21 ' ' | '!' | '#' | '%' | '&' | '(' | ')' | '+' | ',' | '.' | '/' | '0'
22 ..='9' | ':' | ';' | '<' | '=' | '>' | '?' | '[' | ']' | '_' | '{' | '|' | '}'
23 ) {
24 return Some(u as u8);
25 }
26 Some(match c {
27 '\u{2200}' => 0x22, '\u{2203}' => 0x24, '\u{220B}' => 0x27, '\u{2217}' | '*' => 0x2A,
31 '\u{2212}' | '-' => 0x2D, '\u{2245}' => 0x40, '\u{0391}' => 0x41,
34 '\u{0392}' => 0x42,
35 '\u{03A7}' => 0x43,
36 '\u{0394}' | '\u{2206}' => 0x44,
37 '\u{0395}' => 0x45,
38 '\u{03A6}' => 0x46,
39 '\u{0393}' => 0x47,
40 '\u{0397}' => 0x48,
41 '\u{0399}' => 0x49,
42 '\u{03D1}' => 0x4A, '\u{039A}' => 0x4B,
44 '\u{039B}' => 0x4C,
45 '\u{039C}' => 0x4D,
46 '\u{039D}' => 0x4E,
47 '\u{039F}' => 0x4F,
48 '\u{03A0}' => 0x50,
49 '\u{0398}' => 0x51,
50 '\u{03A1}' => 0x52,
51 '\u{03A3}' => 0x53,
52 '\u{03A4}' => 0x54,
53 '\u{03A5}' => 0x55,
54 '\u{03C2}' => 0x56, '\u{03A9}' | '\u{2126}' => 0x57,
56 '\u{039E}' => 0x58,
57 '\u{03A8}' => 0x59,
58 '\u{0396}' => 0x5A,
59 '\u{2234}' => 0x5C, '\u{22A5}' => 0x5E, '\u{03B1}' => 0x61,
62 '\u{03B2}' => 0x62,
63 '\u{03C7}' => 0x63,
64 '\u{03B4}' => 0x64,
65 '\u{03B5}' | '\u{03F5}' => 0x65,
66 '\u{03C6}' => 0x66,
67 '\u{03B3}' => 0x67,
68 '\u{03B7}' => 0x68,
69 '\u{03B9}' => 0x69,
70 '\u{03D5}' => 0x6A, '\u{03BA}' => 0x6B,
72 '\u{03BB}' => 0x6C,
73 '\u{03BC}' | '\u{00B5}' => 0x6D,
74 '\u{03BD}' => 0x6E,
75 '\u{03BF}' => 0x6F,
76 '\u{03C0}' => 0x70,
77 '\u{03B8}' => 0x71,
78 '\u{03C1}' => 0x72,
79 '\u{03C3}' => 0x73,
80 '\u{03C4}' => 0x74,
81 '\u{03C5}' => 0x75,
82 '\u{03D6}' => 0x76, '\u{03C9}' => 0x77,
84 '\u{03BE}' => 0x78,
85 '\u{03C8}' => 0x79,
86 '\u{03B6}' => 0x7A,
87 '\u{223C}' | '~' => 0x7E, '\u{20AC}' => 0xA0, '\u{03D2}' => 0xA1, '\u{2032}' => 0xA2, '\u{2264}' => 0xA3,
92 '\u{2044}' => 0xA4, '\u{221E}' => 0xA5,
94 '\u{0192}' => 0xA6,
95 '\u{2663}' => 0xA7,
96 '\u{2666}' => 0xA8,
97 '\u{2665}' => 0xA9,
98 '\u{2660}' => 0xAA,
99 '\u{2194}' => 0xAB,
100 '\u{2190}' => 0xAC,
101 '\u{2191}' => 0xAD,
102 '\u{2192}' => 0xAE,
103 '\u{2193}' => 0xAF,
104 '\u{00B0}' => 0xB0,
105 '\u{00B1}' => 0xB1,
106 '\u{2033}' => 0xB2, '\u{2265}' => 0xB3,
108 '\u{00D7}' => 0xB4,
109 '\u{221D}' => 0xB5,
110 '\u{2202}' => 0xB6,
111 '\u{2022}' | '\u{2219}' => 0xB7,
112 '\u{00F7}' => 0xB8,
113 '\u{2260}' => 0xB9,
114 '\u{2261}' => 0xBA,
115 '\u{2248}' => 0xBB,
116 '\u{2026}' => 0xBC,
117 '\u{21B5}' => 0xBF,
118 '\u{2135}' => 0xC0,
119 '\u{2111}' => 0xC1,
120 '\u{211C}' => 0xC2,
121 '\u{2118}' => 0xC3,
122 '\u{2297}' => 0xC4,
123 '\u{2295}' => 0xC5,
124 '\u{2205}' => 0xC6,
125 '\u{2229}' => 0xC7,
126 '\u{222A}' => 0xC8,
127 '\u{2283}' => 0xC9,
128 '\u{2287}' => 0xCA,
129 '\u{2284}' => 0xCB,
130 '\u{2282}' => 0xCC,
131 '\u{2286}' => 0xCD,
132 '\u{2208}' => 0xCE,
133 '\u{2209}' => 0xCF,
134 '\u{2220}' => 0xD0,
135 '\u{2207}' => 0xD1,
136 '\u{00AE}' => 0xD2,
137 '\u{00A9}' => 0xD3,
138 '\u{2122}' => 0xD4,
139 '\u{220F}' => 0xD5,
140 '\u{221A}' => 0xD6,
141 '\u{22C5}' | '\u{00B7}' => 0xD7, '\u{00AC}' => 0xD8,
143 '\u{2227}' => 0xD9,
144 '\u{2228}' => 0xDA,
145 '\u{21D4}' => 0xDB,
146 '\u{21D0}' => 0xDC,
147 '\u{21D1}' => 0xDD,
148 '\u{21D2}' => 0xDE,
149 '\u{21D3}' => 0xDF,
150 '\u{25CA}' => 0xE0,
151 '\u{2329}' | '\u{27E8}' => 0xE1, '\u{2211}' => 0xE5,
153 '\u{232A}' | '\u{27E9}' => 0xF1, '\u{222B}' => 0xF2,
155 _ => return None,
156 })
157}
158
159const WIDTHS: [u16; 224] = [
161 250, 333, 713, 500, 549, 833, 778, 439, 333, 333, 500, 549, 250, 549, 250, 278, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 549, 549, 549, 444, 549, 722, 667, 722, 612, 611, 763, 603, 722, 333, 631, 722, 686, 889, 722, 722, 768, 741, 556, 592, 611, 690, 439, 768, 645, 795, 611, 333, 863, 333, 658, 500, 500, 631, 549, 549, 494, 439, 521, 411, 603, 329, 603, 549, 549, 576, 521, 549, 549, 521, 549, 603, 439, 576, 713, 686, 493, 686, 494, 480, 200, 480, 549, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 750, 620, 247, 549, 167, 713, 500, 753, 753, 753, 753, 1042, 987, 603, 987, 603, 400, 549, 411, 549, 549, 713, 494, 460, 549, 549, 549, 549, 1000, 603, 1000, 658, 823, 686, 795, 987, 768, 768, 823, 768, 768, 713, 713, 713, 713, 713, 713, 713, 768, 713, 790, 790, 890, 823, 549, 250, 713, 603, 603, 1042, 987, 603, 987, 603, 494, 329, 790, 790, 786, 713, 384, 384, 384, 384, 384, 384, 494, 494, 494, 494, 0, 329, 274, 686, 686, 686, 384, 384, 384, 384, 384, 384, 494, 494, 494, 0, ];
190
191pub fn symbol_width(code: u8) -> Option<u16> {
193 let w = *WIDTHS.get(usize::from(code).checked_sub(0x20)?)?;
194 (w > 0).then_some(w)
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn maths_symbols_are_encoded_with_their_widths() {
203 for (c, code, w) in [
204 ('α', 0x61, 631),
205 ('π', 0x70, 549),
206 ('Σ', 0x53, 592),
207 ('∑', 0xE5, 713),
208 ('∫', 0xF2, 274),
209 ('√', 0xD6, 549),
210 ('≤', 0xA3, 549),
211 ('≥', 0xB3, 549),
212 ('±', 0xB1, 549),
213 ('×', 0xB4, 549),
214 ('÷', 0xB8, 549),
215 ('∞', 0xA5, 713),
216 ('∂', 0xB6, 494),
217 ('−', 0x2D, 549),
218 ('=', 0x3D, 549),
219 ('2', 0x32, 500),
220 ] {
221 assert_eq!(symbol_code(c), Some(code), "{c}");
222 assert_eq!(symbol_width(code), Some(w), "{c}");
223 }
224 assert_eq!(symbol_code('A'), None, "Latin letters belong to the text faces");
225 assert_eq!(symbol_code('ℝ'), None);
226 assert_eq!(symbol_width(0x7F), None);
227 assert_eq!(symbol_width(0x10), None);
228 assert_eq!(symbol_width(0xFF), None);
229 }
230
231 #[test]
232 fn every_encoded_character_has_a_width() {
233 for u in 0..0x3000u32 {
234 if let Some(code) = char::from_u32(u).and_then(symbol_code) {
235 assert!(symbol_width(code).is_some(), "U+{u:04X} -> {code:#x}");
236 }
237 }
238 }
239}