Skip to main content

ravenrs_sheet/pdf/
symbol.rs

1//! The standard Symbol font: its built-in encoding and advance widths.
2//!
3//! Source: the character codes and glyph advance widths ("C" and "WX" values) of Adobe's Core 14
4//! AFM metrics file for Symbol (`Symbol.afm`, version 001.008), and the Unicode values of its glyph
5//! names from Unicode, Inc.'s `symbol.txt` ("Adobe Symbol Encoding to Unicode", table version
6//! 1.0). Adobe distributes the Core 14 metrics with permission to use, copy and distribute them
7//! for any purpose (the notice is quoted in `metrics.rs`); Unicode, Inc. grants the right to use
8//! the information in its file in products supporting the Unicode Standard (its notice is
9//! recorded in `ATTRIBUTION.md`). Only the numbers are reproduced here, typed in from the public
10//! tables; neither file is included. The stretchable
11//! bracket, brace and integral pieces (codes 0xE6 to 0xEF and 0xF3 to 0xFE) and the sans-serif
12//! duplicates of the registered, copyright and trademark signs are left out of the Unicode map:
13//! they have no Unicode counterpart of their own.
14
15/// The Symbol-encoding code for `c`, or `None` when Symbol has no such glyph.
16pub fn symbol_code(c: char) -> Option<u8> {
17    let u = c as u32;
18    // Characters Symbol encodes at their ASCII code.
19    if matches!(
20        c,
21        ' ' | '!' | '#' | '%' | '&' | '(' | ')' | '+' | ',' | '.' | '/' | '0'
22            ..='9' | ':' | ';' | '<' | '=' | '>' | '?' | '[' | ']' | '_' | '{' | '|' | '}'
23    ) {
24        return Some(u as u8);
25    }
26    Some(match c {
27        '\u{2200}' => 0x22, // for all
28        '\u{2203}' => 0x24, // there exists
29        '\u{220B}' => 0x27, // such that (contains as member)
30        '\u{2217}' | '*' => 0x2A,
31        '\u{2212}' | '-' => 0x2D, // minus
32        '\u{2245}' => 0x40,       // approximately equal (congruent)
33        '\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, // theta1
43        '\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, // final sigma (sigma1)
55        '\u{03A9}' | '\u{2126}' => 0x57,
56        '\u{039E}' => 0x58,
57        '\u{03A8}' => 0x59,
58        '\u{0396}' => 0x5A,
59        '\u{2234}' => 0x5C, // therefore
60        '\u{22A5}' => 0x5E, // perpendicular
61        '\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, // phi1
71        '\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, // omega1 (pi symbol)
83        '\u{03C9}' => 0x77,
84        '\u{03BE}' => 0x78,
85        '\u{03C8}' => 0x79,
86        '\u{03B6}' => 0x7A,
87        '\u{223C}' | '~' => 0x7E, // similar
88        '\u{20AC}' => 0xA0,       // euro
89        '\u{03D2}' => 0xA1,       // Upsilon1
90        '\u{2032}' => 0xA2,       // minute (prime)
91        '\u{2264}' => 0xA3,
92        '\u{2044}' => 0xA4, // fraction slash
93        '\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, // second (double prime)
107        '\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, // dot operator
142        '\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, // angle bracket left
152        '\u{2211}' => 0xE5,
153        '\u{232A}' | '\u{27E9}' => 0xF1, // angle bracket right
154        '\u{222B}' => 0xF2,
155        _ => return None,
156    })
157}
158
159/// Advance widths in 1/1000 em for codes 0x20..=0xFF (0 where Symbol has no glyph).
160const WIDTHS: [u16; 224] = [
161    // 0x20
162    250, 333, 713, 500, 549, 833, 778, 439, 333, 333, 500, 549, 250, 549, 250, 278, //
163    // 0x30
164    500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 278, 278, 549, 549, 549, 444, //
165    // 0x40
166    549, 722, 667, 722, 612, 611, 763, 603, 722, 333, 631, 722, 686, 889, 722, 722, //
167    // 0x50
168    768, 741, 556, 592, 611, 690, 439, 768, 645, 795, 611, 333, 863, 333, 658, 500, //
169    // 0x60
170    500, 631, 549, 549, 494, 439, 521, 411, 603, 329, 603, 549, 549, 576, 521, 549, //
171    // 0x70
172    549, 521, 549, 603, 439, 576, 713, 686, 493, 686, 494, 480, 200, 480, 549, 0, //
173    // 0x80
174    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //
175    // 0x90
176    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, //
177    // 0xA0
178    750, 620, 247, 549, 167, 713, 500, 753, 753, 753, 753, 1042, 987, 603, 987, 603, //
179    // 0xB0
180    400, 549, 411, 549, 549, 713, 494, 460, 549, 549, 549, 549, 1000, 603, 1000, 658, //
181    // 0xC0
182    823, 686, 795, 987, 768, 768, 823, 768, 768, 713, 713, 713, 713, 713, 713, 713, //
183    // 0xD0
184    768, 713, 790, 790, 890, 823, 549, 250, 713, 603, 603, 1042, 987, 603, 987, 603, //
185    // 0xE0
186    494, 329, 790, 790, 786, 713, 384, 384, 384, 384, 384, 384, 494, 494, 494, 494, //
187    // 0xF0
188    0, 329, 274, 686, 686, 686, 384, 384, 384, 384, 384, 384, 494, 494, 494, 0, //
189];
190
191/// The advance width of code `code` in 1/1000 em, or `None` for an empty slot.
192pub 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}