Skip to main content

pdfboss_encoding/
lib.rs

1//! Shared PDF font-encoding tables (WinAnsi / MacRoman / Standard, from
2//! ISO 32000 Appendix D) and a bundled glyph-name-to-Unicode subset, consumed
3//! by the pdfboss text-extraction and rendering crates.
4
5mod afm;
6pub use afm::{is_standard_14, standard_14_width};
7
8/// WinAnsiEncoding codes `0x80..=0x9F` (the region that differs from
9/// Latin-1); `None` marks unassigned codes.
10const WIN_ANSI_80_9F: [Option<char>; 32] = [
11    Some('\u{20AC}'),
12    None,
13    Some('\u{201A}'),
14    Some('\u{0192}'),
15    Some('\u{201E}'),
16    Some('\u{2026}'),
17    Some('\u{2020}'),
18    Some('\u{2021}'),
19    Some('\u{02C6}'),
20    Some('\u{2030}'),
21    Some('\u{0160}'),
22    Some('\u{2039}'),
23    Some('\u{0152}'),
24    None,
25    Some('\u{017D}'),
26    None,
27    None,
28    Some('\u{2018}'),
29    Some('\u{2019}'),
30    Some('\u{201C}'),
31    Some('\u{201D}'),
32    Some('\u{2022}'),
33    Some('\u{2013}'),
34    Some('\u{2014}'),
35    Some('\u{02DC}'),
36    Some('\u{2122}'),
37    Some('\u{0161}'),
38    Some('\u{203A}'),
39    Some('\u{0153}'),
40    None,
41    Some('\u{017E}'),
42    Some('\u{0178}'),
43];
44
45/// Unicode value of `code` in `WinAnsiEncoding`.
46pub fn win_ansi(code: u8) -> Option<char> {
47    match code {
48        0x20..=0x7E => Some(code as char),
49        0x80..=0x9F => WIN_ANSI_80_9F[(code - 0x80) as usize],
50        0xA0..=0xFF => Some(code as char),
51        _ => None,
52    }
53}
54
55/// MacRomanEncoding codes `0x80..=0xFF` (codes below coincide with ASCII).
56const MAC_ROMAN_HIGH: [char; 128] = [
57    '\u{C4}', '\u{C5}', '\u{C7}', '\u{C9}', '\u{D1}', '\u{D6}', '\u{DC}', '\u{E1}', '\u{E0}',
58    '\u{E2}', '\u{E4}', '\u{E3}', '\u{E5}', '\u{E7}', '\u{E9}', '\u{E8}', '\u{EA}', '\u{EB}',
59    '\u{ED}', '\u{EC}', '\u{EE}', '\u{EF}', '\u{F1}', '\u{F3}', '\u{F2}', '\u{F4}', '\u{F6}',
60    '\u{F5}', '\u{FA}', '\u{F9}', '\u{FB}', '\u{FC}', '\u{2020}', '\u{B0}', '\u{A2}', '\u{A3}',
61    '\u{A7}', '\u{2022}', '\u{B6}', '\u{DF}', '\u{AE}', '\u{A9}', '\u{2122}', '\u{B4}', '\u{A8}',
62    '\u{2260}', '\u{C6}', '\u{D8}', '\u{221E}', '\u{B1}', '\u{2264}', '\u{2265}', '\u{A5}',
63    '\u{B5}', '\u{2202}', '\u{2211}', '\u{220F}', '\u{3C0}', '\u{222B}', '\u{AA}', '\u{BA}',
64    '\u{3A9}', '\u{E6}', '\u{F8}', '\u{BF}', '\u{A1}', '\u{AC}', '\u{221A}', '\u{192}', '\u{2248}',
65    '\u{2206}', '\u{AB}', '\u{BB}', '\u{2026}', '\u{A0}', '\u{C0}', '\u{C3}', '\u{D5}', '\u{152}',
66    '\u{153}', '\u{2013}', '\u{2014}', '\u{201C}', '\u{201D}', '\u{2018}', '\u{2019}', '\u{F7}',
67    '\u{25CA}', '\u{FF}', '\u{178}', '\u{2044}', '\u{20AC}', '\u{2039}', '\u{203A}', '\u{FB01}',
68    '\u{FB02}', '\u{2021}', '\u{B7}', '\u{201A}', '\u{201E}', '\u{2030}', '\u{C2}', '\u{CA}',
69    '\u{C1}', '\u{CB}', '\u{C8}', '\u{CD}', '\u{CE}', '\u{CF}', '\u{CC}', '\u{D3}', '\u{D4}',
70    '\u{F8FF}', '\u{D2}', '\u{DA}', '\u{DB}', '\u{D9}', '\u{131}', '\u{2C6}', '\u{2DC}', '\u{AF}',
71    '\u{2D8}', '\u{2D9}', '\u{2DA}', '\u{B8}', '\u{2DD}', '\u{2DB}', '\u{2C7}',
72];
73
74/// Unicode value of `code` in `MacRomanEncoding`.
75pub fn mac_roman(code: u8) -> Option<char> {
76    match code {
77        0x20..=0x7E => Some(code as char),
78        0x80..=0xFF => Some(MAC_ROMAN_HIGH[(code - 0x80) as usize]),
79        _ => None,
80    }
81}
82
83/// StandardEncoding codes above 0x7E that are assigned (sparse).
84const STANDARD_HIGH: &[(u8, char)] = &[
85    (0xA1, '\u{A1}'),
86    (0xA2, '\u{A2}'),
87    (0xA3, '\u{A3}'),
88    (0xA4, '\u{2044}'),
89    (0xA5, '\u{A5}'),
90    (0xA6, '\u{192}'),
91    (0xA7, '\u{A7}'),
92    (0xA8, '\u{A4}'),
93    (0xA9, '\u{27}'),
94    (0xAA, '\u{201C}'),
95    (0xAB, '\u{AB}'),
96    (0xAC, '\u{2039}'),
97    (0xAD, '\u{203A}'),
98    (0xAE, '\u{FB01}'),
99    (0xAF, '\u{FB02}'),
100    (0xB1, '\u{2013}'),
101    (0xB2, '\u{2020}'),
102    (0xB3, '\u{2021}'),
103    (0xB4, '\u{B7}'),
104    (0xB6, '\u{B6}'),
105    (0xB7, '\u{2022}'),
106    (0xB8, '\u{201A}'),
107    (0xB9, '\u{201E}'),
108    (0xBA, '\u{201D}'),
109    (0xBB, '\u{BB}'),
110    (0xBC, '\u{2026}'),
111    (0xBD, '\u{2030}'),
112    (0xBF, '\u{BF}'),
113    (0xC1, '\u{60}'),
114    (0xC2, '\u{B4}'),
115    (0xC3, '\u{2C6}'),
116    (0xC4, '\u{2DC}'),
117    (0xC5, '\u{AF}'),
118    (0xC6, '\u{2D8}'),
119    (0xC7, '\u{2D9}'),
120    (0xC8, '\u{A8}'),
121    (0xCA, '\u{2DA}'),
122    (0xCB, '\u{B8}'),
123    (0xCD, '\u{2DD}'),
124    (0xCE, '\u{2DB}'),
125    (0xCF, '\u{2C7}'),
126    (0xD0, '\u{2014}'),
127    (0xE1, '\u{C6}'),
128    (0xE3, '\u{AA}'),
129    (0xE8, '\u{141}'),
130    (0xE9, '\u{D8}'),
131    (0xEA, '\u{152}'),
132    (0xEB, '\u{BA}'),
133    (0xF1, '\u{E6}'),
134    (0xF5, '\u{131}'),
135    (0xF8, '\u{142}'),
136    (0xF9, '\u{F8}'),
137    (0xFA, '\u{153}'),
138    (0xFB, '\u{DF}'),
139];
140
141/// Unicode value of `code` in `StandardEncoding`.
142pub fn standard(code: u8) -> Option<char> {
143    match code {
144        0x27 => Some('\u{2019}'),
145        0x60 => Some('\u{2018}'),
146        0x20..=0x7E => Some(code as char),
147        0xA1..=0xFF => STANDARD_HIGH
148            .iter()
149            .find(|&&(c, _)| c == code)
150            .map(|&(_, u)| u),
151        _ => None,
152    }
153}
154
155/// StandardEncoding names for codes `0x20..=0x7E` (space..asciitilde), in
156/// code order (index `0` is code `0x20`). Two codes diverge from their plain
157/// ASCII name: `0x27` is `quoteright` (a curly right quote, not the straight
158/// `quotesingle` apostrophe) and `0x60` is `quoteleft` (a curly left quote,
159/// not `grave`) -- matching `standard`'s `0x27`/`0x60` special cases above.
160const STANDARD_ASCII_NAMES: [&str; 95] = [
161    "space",
162    "exclam",
163    "quotedbl",
164    "numbersign",
165    "dollar",
166    "percent",
167    "ampersand",
168    "quoteright",
169    "parenleft",
170    "parenright",
171    "asterisk",
172    "plus",
173    "comma",
174    "hyphen",
175    "period",
176    "slash",
177    "zero",
178    "one",
179    "two",
180    "three",
181    "four",
182    "five",
183    "six",
184    "seven",
185    "eight",
186    "nine",
187    "colon",
188    "semicolon",
189    "less",
190    "equal",
191    "greater",
192    "question",
193    "at",
194    "A",
195    "B",
196    "C",
197    "D",
198    "E",
199    "F",
200    "G",
201    "H",
202    "I",
203    "J",
204    "K",
205    "L",
206    "M",
207    "N",
208    "O",
209    "P",
210    "Q",
211    "R",
212    "S",
213    "T",
214    "U",
215    "V",
216    "W",
217    "X",
218    "Y",
219    "Z",
220    "bracketleft",
221    "backslash",
222    "bracketright",
223    "asciicircum",
224    "underscore",
225    "quoteleft",
226    "a",
227    "b",
228    "c",
229    "d",
230    "e",
231    "f",
232    "g",
233    "h",
234    "i",
235    "j",
236    "k",
237    "l",
238    "m",
239    "n",
240    "o",
241    "p",
242    "q",
243    "r",
244    "s",
245    "t",
246    "u",
247    "v",
248    "w",
249    "x",
250    "y",
251    "z",
252    "braceleft",
253    "bar",
254    "braceright",
255    "asciitilde",
256];
257
258/// StandardEncoding names for codes above `0x7E` (ISO 32000-1 Annex D.2
259/// "StandardEncoding" column), parallel to [`STANDARD_HIGH`]'s codes, in the
260/// same order.
261const STANDARD_HIGH_NAMES: &[(u8, &str)] = &[
262    (0xA1, "exclamdown"),
263    (0xA2, "cent"),
264    (0xA3, "sterling"),
265    (0xA4, "fraction"),
266    (0xA5, "yen"),
267    (0xA6, "florin"),
268    (0xA7, "section"),
269    (0xA8, "currency"),
270    (0xA9, "quotesingle"),
271    (0xAA, "quotedblleft"),
272    (0xAB, "guillemotleft"),
273    (0xAC, "guilsinglleft"),
274    (0xAD, "guilsinglright"),
275    (0xAE, "fi"),
276    (0xAF, "fl"),
277    (0xB1, "endash"),
278    (0xB2, "dagger"),
279    (0xB3, "daggerdbl"),
280    (0xB4, "periodcentered"),
281    (0xB6, "paragraph"),
282    (0xB7, "bullet"),
283    (0xB8, "quotesinglbase"),
284    (0xB9, "quotedblbase"),
285    (0xBA, "quotedblright"),
286    (0xBB, "guillemotright"),
287    (0xBC, "ellipsis"),
288    (0xBD, "perthousand"),
289    (0xBF, "questiondown"),
290    (0xC1, "grave"),
291    (0xC2, "acute"),
292    (0xC3, "circumflex"),
293    (0xC4, "tilde"),
294    (0xC5, "macron"),
295    (0xC6, "breve"),
296    (0xC7, "dotaccent"),
297    (0xC8, "dieresis"),
298    (0xCA, "ring"),
299    (0xCB, "cedilla"),
300    (0xCD, "hungarumlaut"),
301    (0xCE, "ogonek"),
302    (0xCF, "caron"),
303    (0xD0, "emdash"),
304    (0xE1, "AE"),
305    (0xE3, "ordfeminine"),
306    (0xE8, "Lslash"),
307    (0xE9, "Oslash"),
308    (0xEA, "OE"),
309    (0xEB, "ordmasculine"),
310    (0xF1, "ae"),
311    (0xF5, "dotlessi"),
312    (0xF8, "lslash"),
313    (0xF9, "oslash"),
314    (0xFA, "oe"),
315    (0xFB, "germandbls"),
316];
317
318/// Adobe StandardEncoding glyph name for `code` (ISO 32000-1 Annex D.2
319/// "StandardEncoding" column; equivalently Adobe Type 1 Font Format
320/// Appendix C). `None` for exactly the codes `standard` leaves unassigned
321/// (see the self-verifying `standard_encoding_name_matches_standard_table`
322/// test below, which ties this table to that one so an authoring mistake
323/// here fails a test rather than silently mis-encoding a glyph).
324pub fn standard_encoding_name(code: u8) -> Option<&'static str> {
325    match code {
326        0x20..=0x7E => Some(STANDARD_ASCII_NAMES[(code - 0x20) as usize]),
327        0xA1..=0xFF => STANDARD_HIGH_NAMES
328            .iter()
329            .find(|&&(c, _)| c == code)
330            .map(|&(_, n)| n),
331        _ => None,
332    }
333}
334
335/// Resolves a glyph name (as used in `/Differences`) to a Unicode scalar:
336/// `uniXXXX` and `uXXXX`–`uXXXXXX` hex forms, single ASCII letters, and a
337/// bundled subset of the standard glyph list.
338pub fn glyph_to_unicode(name: &str) -> Option<char> {
339    if let Some(hex) = name.strip_prefix("uni") {
340        if hex.len() == 4 && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
341            return char::from_u32(u32::from_str_radix(hex, 16).ok()?);
342        }
343    }
344    if let Some(hex) = name.strip_prefix('u') {
345        if (4..=6).contains(&hex.len()) && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
346            return char::from_u32(u32::from_str_radix(hex, 16).ok()?);
347        }
348    }
349    let mut chars = name.chars();
350    if let (Some(c), None) = (chars.next(), chars.next()) {
351        if c.is_ascii_alphabetic() {
352            return Some(c);
353        }
354    }
355    GLYPH_TABLES
356        .iter()
357        .flat_map(|t| t.iter())
358        .find(|&&(n, _)| n == name)
359        .map(|&(_, u)| u)
360}
361
362/// All bundled glyph-name tables, searched in order.
363const GLYPH_TABLES: [&[(&str, char)]; 5] = [
364    GLYPHS_ASCII,
365    GLYPHS_LATIN1,
366    GLYPHS_PUNCT,
367    GLYPHS_GREEK,
368    GLYPHS_MISC,
369];
370
371/// Names for the ASCII range (letters are handled separately).
372const GLYPHS_ASCII: &[(&str, char)] = &[
373    ("space", ' '),
374    ("exclam", '!'),
375    ("quotedbl", '"'),
376    ("numbersign", '#'),
377    ("dollar", '$'),
378    ("percent", '%'),
379    ("ampersand", '&'),
380    ("quotesingle", '\''),
381    ("parenleft", '('),
382    ("parenright", ')'),
383    ("asterisk", '*'),
384    ("plus", '+'),
385    ("comma", ','),
386    ("hyphen", '-'),
387    ("period", '.'),
388    ("slash", '/'),
389    ("zero", '0'),
390    ("one", '1'),
391    ("two", '2'),
392    ("three", '3'),
393    ("four", '4'),
394    ("five", '5'),
395    ("six", '6'),
396    ("seven", '7'),
397    ("eight", '8'),
398    ("nine", '9'),
399    ("colon", ':'),
400    ("semicolon", ';'),
401    ("less", '<'),
402    ("equal", '='),
403    ("greater", '>'),
404    ("question", '?'),
405    ("at", '@'),
406    ("bracketleft", '['),
407    ("backslash", '\\'),
408    ("bracketright", ']'),
409    ("asciicircum", '^'),
410    ("underscore", '_'),
411    ("grave", '`'),
412    ("braceleft", '{'),
413    ("bar", '|'),
414    ("braceright", '}'),
415    ("asciitilde", '~'),
416];
417
418/// Names for the Latin-1 supplement.
419const GLYPHS_LATIN1: &[(&str, char)] = &[
420    ("exclamdown", '\u{A1}'),
421    ("cent", '\u{A2}'),
422    ("sterling", '\u{A3}'),
423    ("currency", '\u{A4}'),
424    ("yen", '\u{A5}'),
425    ("brokenbar", '\u{A6}'),
426    ("section", '\u{A7}'),
427    ("dieresis", '\u{A8}'),
428    ("copyright", '\u{A9}'),
429    ("ordfeminine", '\u{AA}'),
430    ("guillemotleft", '\u{AB}'),
431    ("logicalnot", '\u{AC}'),
432    ("registered", '\u{AE}'),
433    ("macron", '\u{AF}'),
434    ("degree", '\u{B0}'),
435    ("plusminus", '\u{B1}'),
436    ("twosuperior", '\u{B2}'),
437    ("threesuperior", '\u{B3}'),
438    ("acute", '\u{B4}'),
439    ("mu", '\u{B5}'),
440    ("paragraph", '\u{B6}'),
441    ("periodcentered", '\u{B7}'),
442    ("cedilla", '\u{B8}'),
443    ("onesuperior", '\u{B9}'),
444    ("ordmasculine", '\u{BA}'),
445    ("guillemotright", '\u{BB}'),
446    ("onequarter", '\u{BC}'),
447    ("onehalf", '\u{BD}'),
448    ("threequarters", '\u{BE}'),
449    ("questiondown", '\u{BF}'),
450    ("Agrave", '\u{C0}'),
451    ("Aacute", '\u{C1}'),
452    ("Acircumflex", '\u{C2}'),
453    ("Atilde", '\u{C3}'),
454    ("Adieresis", '\u{C4}'),
455    ("Aring", '\u{C5}'),
456    ("AE", '\u{C6}'),
457    ("Ccedilla", '\u{C7}'),
458    ("Egrave", '\u{C8}'),
459    ("Eacute", '\u{C9}'),
460    ("Ecircumflex", '\u{CA}'),
461    ("Edieresis", '\u{CB}'),
462    ("Igrave", '\u{CC}'),
463    ("Iacute", '\u{CD}'),
464    ("Icircumflex", '\u{CE}'),
465    ("Idieresis", '\u{CF}'),
466    ("Eth", '\u{D0}'),
467    ("Ntilde", '\u{D1}'),
468    ("Ograve", '\u{D2}'),
469    ("Oacute", '\u{D3}'),
470    ("Ocircumflex", '\u{D4}'),
471    ("Otilde", '\u{D5}'),
472    ("Odieresis", '\u{D6}'),
473    ("multiply", '\u{D7}'),
474    ("Oslash", '\u{D8}'),
475    ("Ugrave", '\u{D9}'),
476    ("Uacute", '\u{DA}'),
477    ("Ucircumflex", '\u{DB}'),
478    ("Udieresis", '\u{DC}'),
479    ("Yacute", '\u{DD}'),
480    ("Thorn", '\u{DE}'),
481    ("germandbls", '\u{DF}'),
482    ("agrave", '\u{E0}'),
483    ("aacute", '\u{E1}'),
484    ("acircumflex", '\u{E2}'),
485    ("atilde", '\u{E3}'),
486    ("adieresis", '\u{E4}'),
487    ("aring", '\u{E5}'),
488    ("ae", '\u{E6}'),
489    ("ccedilla", '\u{E7}'),
490    ("egrave", '\u{E8}'),
491    ("eacute", '\u{E9}'),
492    ("ecircumflex", '\u{EA}'),
493    ("edieresis", '\u{EB}'),
494    ("igrave", '\u{EC}'),
495    ("iacute", '\u{ED}'),
496    ("icircumflex", '\u{EE}'),
497    ("idieresis", '\u{EF}'),
498    ("eth", '\u{F0}'),
499    ("ntilde", '\u{F1}'),
500    ("ograve", '\u{F2}'),
501    ("oacute", '\u{F3}'),
502    ("ocircumflex", '\u{F4}'),
503    ("otilde", '\u{F5}'),
504    ("odieresis", '\u{F6}'),
505    ("divide", '\u{F7}'),
506    ("oslash", '\u{F8}'),
507    ("ugrave", '\u{F9}'),
508    ("uacute", '\u{FA}'),
509    ("ucircumflex", '\u{FB}'),
510    ("udieresis", '\u{FC}'),
511    ("yacute", '\u{FD}'),
512    ("thorn", '\u{FE}'),
513    ("ydieresis", '\u{FF}'),
514];
515
516/// Typographic punctuation, ligatures, and accents.
517const GLYPHS_PUNCT: &[(&str, char)] = &[
518    ("quoteleft", '\u{2018}'),
519    ("quoteright", '\u{2019}'),
520    ("quotesinglbase", '\u{201A}'),
521    ("quotedblleft", '\u{201C}'),
522    ("quotedblright", '\u{201D}'),
523    ("quotedblbase", '\u{201E}'),
524    ("endash", '\u{2013}'),
525    ("emdash", '\u{2014}'),
526    ("bullet", '\u{2022}'),
527    ("ellipsis", '\u{2026}'),
528    ("dagger", '\u{2020}'),
529    ("daggerdbl", '\u{2021}'),
530    ("perthousand", '\u{2030}'),
531    ("guilsinglleft", '\u{2039}'),
532    ("guilsinglright", '\u{203A}'),
533    ("fraction", '\u{2044}'),
534    ("minus", '\u{2212}'),
535    ("florin", '\u{192}'),
536    ("Euro", '\u{20AC}'),
537    ("trademark", '\u{2122}'),
538    ("fi", '\u{FB01}'),
539    ("fl", '\u{FB02}'),
540    ("ff", '\u{FB00}'),
541    ("ffi", '\u{FB03}'),
542    ("ffl", '\u{FB04}'),
543    ("circumflex", '\u{2C6}'),
544    ("caron", '\u{2C7}'),
545    ("breve", '\u{2D8}'),
546    ("dotaccent", '\u{2D9}'),
547    ("ring", '\u{2DA}'),
548    ("ogonek", '\u{2DB}'),
549    ("tilde", '\u{2DC}'),
550    ("hungarumlaut", '\u{2DD}'),
551    ("OE", '\u{152}'),
552    ("oe", '\u{153}'),
553    ("Scaron", '\u{160}'),
554    ("scaron", '\u{161}'),
555    ("Zcaron", '\u{17D}'),
556    ("zcaron", '\u{17E}'),
557    ("Ydieresis", '\u{178}'),
558    ("Lslash", '\u{141}'),
559    ("lslash", '\u{142}'),
560    ("dotlessi", '\u{131}'),
561    ("nbspace", '\u{A0}'),
562    ("sfthyphen", '\u{AD}'),
563];
564
565/// Greek letters (per the glyph list, `Delta`/`Omega`/`mu` map to their
566/// technical-symbol codepoints; `mu` lives in the Latin-1 table).
567const GLYPHS_GREEK: &[(&str, char)] = &[
568    ("Alpha", '\u{391}'),
569    ("Beta", '\u{392}'),
570    ("Gamma", '\u{393}'),
571    ("Delta", '\u{2206}'),
572    ("Epsilon", '\u{395}'),
573    ("Zeta", '\u{396}'),
574    ("Eta", '\u{397}'),
575    ("Theta", '\u{398}'),
576    ("Iota", '\u{399}'),
577    ("Kappa", '\u{39A}'),
578    ("Lambda", '\u{39B}'),
579    ("Mu", '\u{39C}'),
580    ("Nu", '\u{39D}'),
581    ("Xi", '\u{39E}'),
582    ("Omicron", '\u{39F}'),
583    ("Pi", '\u{3A0}'),
584    ("Rho", '\u{3A1}'),
585    ("Sigma", '\u{3A3}'),
586    ("Tau", '\u{3A4}'),
587    ("Upsilon", '\u{3A5}'),
588    ("Phi", '\u{3A6}'),
589    ("Chi", '\u{3A7}'),
590    ("Psi", '\u{3A8}'),
591    ("Omega", '\u{2126}'),
592    ("alpha", '\u{3B1}'),
593    ("beta", '\u{3B2}'),
594    ("gamma", '\u{3B3}'),
595    ("delta", '\u{3B4}'),
596    ("epsilon", '\u{3B5}'),
597    ("zeta", '\u{3B6}'),
598    ("eta", '\u{3B7}'),
599    ("theta", '\u{3B8}'),
600    ("iota", '\u{3B9}'),
601    ("kappa", '\u{3BA}'),
602    ("lambda", '\u{3BB}'),
603    ("nu", '\u{3BD}'),
604    ("xi", '\u{3BE}'),
605    ("omicron", '\u{3BF}'),
606    ("pi", '\u{3C0}'),
607    ("rho", '\u{3C1}'),
608    ("sigma", '\u{3C3}'),
609    ("sigma1", '\u{3C2}'),
610    ("tau", '\u{3C4}'),
611    ("upsilon", '\u{3C5}'),
612    ("phi", '\u{3C6}'),
613    ("chi", '\u{3C7}'),
614    ("psi", '\u{3C8}'),
615    ("omega", '\u{3C9}'),
616];
617
618/// Mathematical and miscellaneous symbols.
619const GLYPHS_MISC: &[(&str, char)] = &[
620    ("infinity", '\u{221E}'),
621    ("notequal", '\u{2260}'),
622    ("lessequal", '\u{2264}'),
623    ("greaterequal", '\u{2265}'),
624    ("partialdiff", '\u{2202}'),
625    ("summation", '\u{2211}'),
626    ("product", '\u{220F}'),
627    ("integral", '\u{222B}'),
628    ("radical", '\u{221A}'),
629    ("approxequal", '\u{2248}'),
630    ("equivalence", '\u{2261}'),
631    ("element", '\u{2208}'),
632    ("intersection", '\u{2229}'),
633    ("union", '\u{222A}'),
634    ("arrowleft", '\u{2190}'),
635    ("arrowup", '\u{2191}'),
636    ("arrowright", '\u{2192}'),
637    ("arrowdown", '\u{2193}'),
638    ("arrowboth", '\u{2194}'),
639    ("lozenge", '\u{25CA}'),
640    ("apple", '\u{F8FF}'),
641];
642
643#[cfg(test)]
644mod tests {
645    use super::*;
646
647    #[test]
648    fn win_ansi_spot_checks() {
649        assert_eq!(win_ansi(b'A'), Some('A'));
650        assert_eq!(win_ansi(0x93), Some('\u{201C}')); // left double quote
651        assert_eq!(win_ansi(0x80), Some('\u{20AC}')); // euro sign
652        assert_eq!(win_ansi(0xE9), Some('\u{E9}')); // e acute (Latin-1)
653        assert_eq!(win_ansi(0x81), None); // unassigned
654        assert_eq!(win_ansi(0x0A), None); // control
655    }
656
657    #[test]
658    fn mac_roman_spot_checks() {
659        assert_eq!(mac_roman(b'A'), Some('A'));
660        assert_eq!(mac_roman(0xD0), Some('\u{2013}')); // en dash
661        assert_eq!(mac_roman(0x80), Some('\u{C4}')); // A dieresis
662        assert_eq!(mac_roman(0xA5), Some('\u{2022}')); // bullet
663        assert_eq!(mac_roman(0xFF), Some('\u{2C7}')); // caron
664        assert_eq!(mac_roman(0x00), None);
665    }
666
667    #[test]
668    fn standard_spot_checks() {
669        assert_eq!(standard(b'A'), Some('A'));
670        assert_eq!(standard(0xA9), Some('\u{27}')); // straight apostrophe
671        assert_eq!(standard(0x27), Some('\u{2019}')); // curly right quote
672        assert_eq!(standard(0x60), Some('\u{2018}')); // curly left quote
673        assert_eq!(standard(0xD0), Some('\u{2014}')); // em dash
674        assert_eq!(standard(0x7F), None);
675        assert_eq!(standard(0xA0), None); // unassigned in Standard
676    }
677
678    #[test]
679    fn glyph_names_hex_forms() {
680        assert_eq!(glyph_to_unicode("uni03B1"), Some('\u{3B1}'));
681        assert_eq!(glyph_to_unicode("uni20AC"), Some('\u{20AC}'));
682        assert_eq!(glyph_to_unicode("u1F600"), Some('\u{1F600}'));
683        assert_eq!(glyph_to_unicode("u00E9"), Some('\u{E9}'));
684        assert_eq!(glyph_to_unicode("uniD800"), None); // surrogate
685        assert_eq!(glyph_to_unicode("uniXYZW"), None);
686    }
687
688    /// Self-verifying anchor for `standard_encoding_name`: ties the new table
689    /// to the pre-existing, trusted `standard` (code -> Unicode) and
690    /// `glyph_to_unicode` (name -> Unicode) tables so an authoring typo in
691    /// the new table fails a test instead of silently mis-encoding a glyph.
692    /// Domain equality (StandardEncoding assigns a name to exactly the codes
693    /// `standard` maps to a char) must hold for every code; value agreement
694    /// only where `glyph_to_unicode` also resolves the name (some names
695    /// aren't in the bundled glyph-name subset).
696    #[test]
697    fn standard_encoding_name_matches_standard_table() {
698        for code in 0u16..=255 {
699            let code = code as u8;
700            assert_eq!(
701                standard_encoding_name(code).is_some(),
702                standard(code).is_some(),
703                "code {code:#04x}: standard_encoding_name/standard domain mismatch"
704            );
705            if let (Some(name), Some(expected)) = (standard_encoding_name(code), standard(code)) {
706                if let Some(resolved) = glyph_to_unicode(name) {
707                    assert_eq!(
708                        resolved, expected,
709                        "code {code:#04x} name {name:?}: glyph_to_unicode disagrees with standard"
710                    );
711                }
712            }
713        }
714    }
715
716    #[test]
717    fn standard_encoding_name_spot_checks() {
718        assert_eq!(standard_encoding_name(b'A'), Some("A"));
719        assert_eq!(standard_encoding_name(0x27), Some("quoteright"));
720        assert_eq!(standard_encoding_name(0x60), Some("quoteleft"));
721        assert_eq!(standard_encoding_name(0xA1), Some("exclamdown"));
722        assert_eq!(standard_encoding_name(0xA4), Some("fraction"));
723        assert_eq!(standard_encoding_name(0xA6), Some("florin"));
724        assert_eq!(standard_encoding_name(0xC1), Some("grave"));
725        assert_eq!(standard_encoding_name(0xC6), Some("breve"));
726        assert_eq!(standard_encoding_name(0xE1), Some("AE"));
727        assert_eq!(standard_encoding_name(0xF1), Some("ae"));
728        assert_eq!(standard_encoding_name(0xFB), Some("germandbls"));
729        assert_eq!(standard_encoding_name(0x7F), None);
730        assert_eq!(standard_encoding_name(0xA0), None);
731    }
732
733    #[test]
734    fn glyph_names_letters_and_tables() {
735        assert_eq!(glyph_to_unicode("A"), Some('A'));
736        assert_eq!(glyph_to_unicode("z"), Some('z'));
737        assert_eq!(glyph_to_unicode("alpha"), Some('\u{3B1}'));
738        assert_eq!(glyph_to_unicode("eacute"), Some('\u{E9}'));
739        assert_eq!(glyph_to_unicode("quotedblleft"), Some('\u{201C}'));
740        assert_eq!(glyph_to_unicode("seven"), Some('7'));
741        assert_eq!(glyph_to_unicode("union"), Some('\u{222A}'));
742        assert_eq!(glyph_to_unicode("nosuchglyphname"), None);
743    }
744}