1mod afm;
7mod agl;
8mod symbol;
9pub use afm::{is_standard_14, standard_14_width};
10
11const WIN_ANSI_80_9F: [Option<char>; 32] = [
14 Some('\u{20AC}'),
15 None,
16 Some('\u{201A}'),
17 Some('\u{0192}'),
18 Some('\u{201E}'),
19 Some('\u{2026}'),
20 Some('\u{2020}'),
21 Some('\u{2021}'),
22 Some('\u{02C6}'),
23 Some('\u{2030}'),
24 Some('\u{0160}'),
25 Some('\u{2039}'),
26 Some('\u{0152}'),
27 None,
28 Some('\u{017D}'),
29 None,
30 None,
31 Some('\u{2018}'),
32 Some('\u{2019}'),
33 Some('\u{201C}'),
34 Some('\u{201D}'),
35 Some('\u{2022}'),
36 Some('\u{2013}'),
37 Some('\u{2014}'),
38 Some('\u{02DC}'),
39 Some('\u{2122}'),
40 Some('\u{0161}'),
41 Some('\u{203A}'),
42 Some('\u{0153}'),
43 None,
44 Some('\u{017E}'),
45 Some('\u{0178}'),
46];
47
48pub fn win_ansi(code: u8) -> Option<char> {
52 match code {
53 0x20..=0x7E => Some(code as char),
54 0x80..=0x9F => WIN_ANSI_80_9F[(code - 0x80) as usize],
55 0xA0..=0xFF => Some(code as char),
56 _ => None,
57 }
58}
59
60pub fn win_ansi_glyph_name(code: u8) -> Option<&'static str> {
72 match code {
73 0x27 => Some("quotesingle"),
74 0x60 => Some("grave"),
75 0x20..=0x7E => Some(STANDARD_ASCII_NAMES[(code - 0x20) as usize]),
76 0x80..=0x9F => WIN_ANSI_80_9F_NAMES[(code - 0x80) as usize],
77 0xA0..=0xFF => Some(WIN_ANSI_A0_FF_NAMES[(code - 0xA0) as usize]),
78 _ => None,
79 }
80}
81
82const WIN_ANSI_80_9F_NAMES: [Option<&str>; 32] = [
85 Some("Euro"),
86 None,
87 Some("quotesinglbase"),
88 Some("florin"),
89 Some("quotedblbase"),
90 Some("ellipsis"),
91 Some("dagger"),
92 Some("daggerdbl"),
93 Some("circumflex"),
94 Some("perthousand"),
95 Some("Scaron"),
96 Some("guilsinglleft"),
97 Some("OE"),
98 None,
99 Some("Zcaron"),
100 None,
101 None,
102 Some("quoteleft"),
103 Some("quoteright"),
104 Some("quotedblleft"),
105 Some("quotedblright"),
106 Some("bullet"),
107 Some("endash"),
108 Some("emdash"),
109 Some("tilde"),
110 Some("trademark"),
111 Some("scaron"),
112 Some("guilsinglright"),
113 Some("oe"),
114 None,
115 Some("zcaron"),
116 Some("Ydieresis"),
117];
118
119const WIN_ANSI_A0_FF_NAMES: [&str; 96] = [
123 "space",
124 "exclamdown",
125 "cent",
126 "sterling",
127 "currency",
128 "yen",
129 "brokenbar",
130 "section",
131 "dieresis",
132 "copyright",
133 "ordfeminine",
134 "guillemotleft",
135 "logicalnot",
136 "hyphen",
137 "registered",
138 "macron",
139 "degree",
140 "plusminus",
141 "twosuperior",
142 "threesuperior",
143 "acute",
144 "mu",
145 "paragraph",
146 "periodcentered",
147 "cedilla",
148 "onesuperior",
149 "ordmasculine",
150 "guillemotright",
151 "onequarter",
152 "onehalf",
153 "threequarters",
154 "questiondown",
155 "Agrave",
156 "Aacute",
157 "Acircumflex",
158 "Atilde",
159 "Adieresis",
160 "Aring",
161 "AE",
162 "Ccedilla",
163 "Egrave",
164 "Eacute",
165 "Ecircumflex",
166 "Edieresis",
167 "Igrave",
168 "Iacute",
169 "Icircumflex",
170 "Idieresis",
171 "Eth",
172 "Ntilde",
173 "Ograve",
174 "Oacute",
175 "Ocircumflex",
176 "Otilde",
177 "Odieresis",
178 "multiply",
179 "Oslash",
180 "Ugrave",
181 "Uacute",
182 "Ucircumflex",
183 "Udieresis",
184 "Yacute",
185 "Thorn",
186 "germandbls",
187 "agrave",
188 "aacute",
189 "acircumflex",
190 "atilde",
191 "adieresis",
192 "aring",
193 "ae",
194 "ccedilla",
195 "egrave",
196 "eacute",
197 "ecircumflex",
198 "edieresis",
199 "igrave",
200 "iacute",
201 "icircumflex",
202 "idieresis",
203 "eth",
204 "ntilde",
205 "ograve",
206 "oacute",
207 "ocircumflex",
208 "otilde",
209 "odieresis",
210 "divide",
211 "oslash",
212 "ugrave",
213 "uacute",
214 "ucircumflex",
215 "udieresis",
216 "yacute",
217 "thorn",
218 "ydieresis",
219];
220
221const MAC_ROMAN_HIGH: [char; 128] = [
223 '\u{C4}', '\u{C5}', '\u{C7}', '\u{C9}', '\u{D1}', '\u{D6}', '\u{DC}', '\u{E1}', '\u{E0}',
224 '\u{E2}', '\u{E4}', '\u{E3}', '\u{E5}', '\u{E7}', '\u{E9}', '\u{E8}', '\u{EA}', '\u{EB}',
225 '\u{ED}', '\u{EC}', '\u{EE}', '\u{EF}', '\u{F1}', '\u{F3}', '\u{F2}', '\u{F4}', '\u{F6}',
226 '\u{F5}', '\u{FA}', '\u{F9}', '\u{FB}', '\u{FC}', '\u{2020}', '\u{B0}', '\u{A2}', '\u{A3}',
227 '\u{A7}', '\u{2022}', '\u{B6}', '\u{DF}', '\u{AE}', '\u{A9}', '\u{2122}', '\u{B4}', '\u{A8}',
228 '\u{2260}', '\u{C6}', '\u{D8}', '\u{221E}', '\u{B1}', '\u{2264}', '\u{2265}', '\u{A5}',
229 '\u{B5}', '\u{2202}', '\u{2211}', '\u{220F}', '\u{3C0}', '\u{222B}', '\u{AA}', '\u{BA}',
230 '\u{3A9}', '\u{E6}', '\u{F8}', '\u{BF}', '\u{A1}', '\u{AC}', '\u{221A}', '\u{192}', '\u{2248}',
231 '\u{2206}', '\u{AB}', '\u{BB}', '\u{2026}', '\u{A0}', '\u{C0}', '\u{C3}', '\u{D5}', '\u{152}',
232 '\u{153}', '\u{2013}', '\u{2014}', '\u{201C}', '\u{201D}', '\u{2018}', '\u{2019}', '\u{F7}',
233 '\u{25CA}', '\u{FF}', '\u{178}', '\u{2044}', '\u{20AC}', '\u{2039}', '\u{203A}', '\u{FB01}',
234 '\u{FB02}', '\u{2021}', '\u{B7}', '\u{201A}', '\u{201E}', '\u{2030}', '\u{C2}', '\u{CA}',
235 '\u{C1}', '\u{CB}', '\u{C8}', '\u{CD}', '\u{CE}', '\u{CF}', '\u{CC}', '\u{D3}', '\u{D4}',
236 '\u{F8FF}', '\u{D2}', '\u{DA}', '\u{DB}', '\u{D9}', '\u{131}', '\u{2C6}', '\u{2DC}', '\u{AF}',
237 '\u{2D8}', '\u{2D9}', '\u{2DA}', '\u{B8}', '\u{2DD}', '\u{2DB}', '\u{2C7}',
238];
239
240pub fn mac_roman(code: u8) -> Option<char> {
242 match code {
243 0x20..=0x7E => Some(code as char),
244 0x80..=0xFF => Some(MAC_ROMAN_HIGH[(code - 0x80) as usize]),
245 _ => None,
246 }
247}
248
249#[rustfmt::skip]
255const MAC_ROMAN_HIGH_NAMES: [&str; 128] = [
256 "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute",
257 "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute", "egrave",
258 "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute",
259 "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex", "udieresis",
260 "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph", "germandbls",
261 "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE", "Oslash",
262 "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff", "summation",
263 "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash",
264 "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal", "Delta", "guillemotleft",
265 "guillemotright", "ellipsis", "space", "Agrave", "Atilde", "Otilde", "OE", "oe",
266 "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft", "quoteright", "divide", "lozenge",
267 "ydieresis", "Ydieresis", "fraction", "Euro", "guilsinglleft", "guilsinglright", "fi", "fl",
268 "daggerdbl", "periodcentered", "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute",
269 "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex",
270 "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde",
271 "macron", "breve", "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron",
272];
273
274pub fn mac_roman_glyph_name(code: u8) -> Option<&'static str> {
283 match code {
284 0x27 => Some("quotesingle"),
285 0x60 => Some("grave"),
286 0x20..=0x7E => Some(STANDARD_ASCII_NAMES[(code - 0x20) as usize]),
287 0x80..=0xFF => Some(MAC_ROMAN_HIGH_NAMES[(code - 0x80) as usize]),
288 _ => None,
289 }
290}
291
292const STANDARD_HIGH: &[(u8, char)] = &[
294 (0xA1, '\u{A1}'),
295 (0xA2, '\u{A2}'),
296 (0xA3, '\u{A3}'),
297 (0xA4, '\u{2044}'),
298 (0xA5, '\u{A5}'),
299 (0xA6, '\u{192}'),
300 (0xA7, '\u{A7}'),
301 (0xA8, '\u{A4}'),
302 (0xA9, '\u{27}'),
303 (0xAA, '\u{201C}'),
304 (0xAB, '\u{AB}'),
305 (0xAC, '\u{2039}'),
306 (0xAD, '\u{203A}'),
307 (0xAE, '\u{FB01}'),
308 (0xAF, '\u{FB02}'),
309 (0xB1, '\u{2013}'),
310 (0xB2, '\u{2020}'),
311 (0xB3, '\u{2021}'),
312 (0xB4, '\u{B7}'),
313 (0xB6, '\u{B6}'),
314 (0xB7, '\u{2022}'),
315 (0xB8, '\u{201A}'),
316 (0xB9, '\u{201E}'),
317 (0xBA, '\u{201D}'),
318 (0xBB, '\u{BB}'),
319 (0xBC, '\u{2026}'),
320 (0xBD, '\u{2030}'),
321 (0xBF, '\u{BF}'),
322 (0xC1, '\u{60}'),
323 (0xC2, '\u{B4}'),
324 (0xC3, '\u{2C6}'),
325 (0xC4, '\u{2DC}'),
326 (0xC5, '\u{AF}'),
327 (0xC6, '\u{2D8}'),
328 (0xC7, '\u{2D9}'),
329 (0xC8, '\u{A8}'),
330 (0xCA, '\u{2DA}'),
331 (0xCB, '\u{B8}'),
332 (0xCD, '\u{2DD}'),
333 (0xCE, '\u{2DB}'),
334 (0xCF, '\u{2C7}'),
335 (0xD0, '\u{2014}'),
336 (0xE1, '\u{C6}'),
337 (0xE3, '\u{AA}'),
338 (0xE8, '\u{141}'),
339 (0xE9, '\u{D8}'),
340 (0xEA, '\u{152}'),
341 (0xEB, '\u{BA}'),
342 (0xF1, '\u{E6}'),
343 (0xF5, '\u{131}'),
344 (0xF8, '\u{142}'),
345 (0xF9, '\u{F8}'),
346 (0xFA, '\u{153}'),
347 (0xFB, '\u{DF}'),
348];
349
350pub fn standard(code: u8) -> Option<char> {
352 match code {
353 0x27 => Some('\u{2019}'),
354 0x60 => Some('\u{2018}'),
355 0x20..=0x7E => Some(code as char),
356 0xA1..=0xFF => STANDARD_HIGH
357 .iter()
358 .find(|&&(c, _)| c == code)
359 .map(|&(_, u)| u),
360 _ => None,
361 }
362}
363
364const STANDARD_ASCII_NAMES: [&str; 95] = [
370 "space",
371 "exclam",
372 "quotedbl",
373 "numbersign",
374 "dollar",
375 "percent",
376 "ampersand",
377 "quoteright",
378 "parenleft",
379 "parenright",
380 "asterisk",
381 "plus",
382 "comma",
383 "hyphen",
384 "period",
385 "slash",
386 "zero",
387 "one",
388 "two",
389 "three",
390 "four",
391 "five",
392 "six",
393 "seven",
394 "eight",
395 "nine",
396 "colon",
397 "semicolon",
398 "less",
399 "equal",
400 "greater",
401 "question",
402 "at",
403 "A",
404 "B",
405 "C",
406 "D",
407 "E",
408 "F",
409 "G",
410 "H",
411 "I",
412 "J",
413 "K",
414 "L",
415 "M",
416 "N",
417 "O",
418 "P",
419 "Q",
420 "R",
421 "S",
422 "T",
423 "U",
424 "V",
425 "W",
426 "X",
427 "Y",
428 "Z",
429 "bracketleft",
430 "backslash",
431 "bracketright",
432 "asciicircum",
433 "underscore",
434 "quoteleft",
435 "a",
436 "b",
437 "c",
438 "d",
439 "e",
440 "f",
441 "g",
442 "h",
443 "i",
444 "j",
445 "k",
446 "l",
447 "m",
448 "n",
449 "o",
450 "p",
451 "q",
452 "r",
453 "s",
454 "t",
455 "u",
456 "v",
457 "w",
458 "x",
459 "y",
460 "z",
461 "braceleft",
462 "bar",
463 "braceright",
464 "asciitilde",
465];
466
467const STANDARD_HIGH_NAMES: &[(u8, &str)] = &[
471 (0xA1, "exclamdown"),
472 (0xA2, "cent"),
473 (0xA3, "sterling"),
474 (0xA4, "fraction"),
475 (0xA5, "yen"),
476 (0xA6, "florin"),
477 (0xA7, "section"),
478 (0xA8, "currency"),
479 (0xA9, "quotesingle"),
480 (0xAA, "quotedblleft"),
481 (0xAB, "guillemotleft"),
482 (0xAC, "guilsinglleft"),
483 (0xAD, "guilsinglright"),
484 (0xAE, "fi"),
485 (0xAF, "fl"),
486 (0xB1, "endash"),
487 (0xB2, "dagger"),
488 (0xB3, "daggerdbl"),
489 (0xB4, "periodcentered"),
490 (0xB6, "paragraph"),
491 (0xB7, "bullet"),
492 (0xB8, "quotesinglbase"),
493 (0xB9, "quotedblbase"),
494 (0xBA, "quotedblright"),
495 (0xBB, "guillemotright"),
496 (0xBC, "ellipsis"),
497 (0xBD, "perthousand"),
498 (0xBF, "questiondown"),
499 (0xC1, "grave"),
500 (0xC2, "acute"),
501 (0xC3, "circumflex"),
502 (0xC4, "tilde"),
503 (0xC5, "macron"),
504 (0xC6, "breve"),
505 (0xC7, "dotaccent"),
506 (0xC8, "dieresis"),
507 (0xCA, "ring"),
508 (0xCB, "cedilla"),
509 (0xCD, "hungarumlaut"),
510 (0xCE, "ogonek"),
511 (0xCF, "caron"),
512 (0xD0, "emdash"),
513 (0xE1, "AE"),
514 (0xE3, "ordfeminine"),
515 (0xE8, "Lslash"),
516 (0xE9, "Oslash"),
517 (0xEA, "OE"),
518 (0xEB, "ordmasculine"),
519 (0xF1, "ae"),
520 (0xF5, "dotlessi"),
521 (0xF8, "lslash"),
522 (0xF9, "oslash"),
523 (0xFA, "oe"),
524 (0xFB, "germandbls"),
525];
526
527pub fn standard_encoding_name(code: u8) -> Option<&'static str> {
534 match code {
535 0x20..=0x7E => Some(STANDARD_ASCII_NAMES[(code - 0x20) as usize]),
536 0xA1..=0xFF => STANDARD_HIGH_NAMES
537 .iter()
538 .find(|&&(c, _)| c == code)
539 .map(|&(_, n)| n),
540 _ => None,
541 }
542}
543
544pub fn mac_os_roman_code(ch: char) -> Option<u8> {
551 if ch == '\u{2126}' {
555 return Some(0xBD);
556 }
557 if ('\u{20}'..='\u{7E}').contains(&ch) {
558 return Some(ch as u8);
559 }
560 MAC_ROMAN_HIGH
561 .iter()
562 .position(|&c| c == ch)
563 .map(|i| 0x80 + i as u8)
564}
565
566pub fn symbol_glyph_name(code: u8) -> Option<&'static str> {
571 let i = symbol::SYMBOL
572 .binary_search_by_key(&code, |&(c, _)| c)
573 .ok()?;
574 Some(symbol::SYMBOL[i].1)
575}
576
577pub fn symbol(code: u8) -> Option<char> {
583 glyph_to_unicode(symbol_glyph_name(code)?)
584}
585
586pub fn zapf_dingbats_glyph_name(code: u8) -> Option<&'static str> {
591 let i = symbol::ZAPF_DINGBATS
592 .binary_search_by_key(&code, |&(c, _, _)| c)
593 .ok()?;
594 Some(symbol::ZAPF_DINGBATS[i].1)
595}
596
597pub fn zapf_dingbats(code: u8) -> Option<char> {
603 let i = symbol::ZAPF_DINGBATS
604 .binary_search_by_key(&code, |&(c, _, _)| c)
605 .ok()?;
606 Some(symbol::ZAPF_DINGBATS[i].2)
607}
608
609pub fn glyph_to_unicode(name: &str) -> Option<char> {
615 if let Some(hex) = name.strip_prefix("uni") {
616 if hex.len() == 4 && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
617 return char::from_u32(u32::from_str_radix(hex, 16).ok()?);
618 }
619 }
620 if let Some(hex) = name.strip_prefix('u') {
621 if (4..=6).contains(&hex.len()) && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
622 return char::from_u32(u32::from_str_radix(hex, 16).ok()?);
623 }
624 }
625 let mut chars = name.chars();
626 if let (Some(c), None) = (chars.next(), chars.next()) {
627 if c.is_ascii_alphabetic() {
628 return Some(c);
629 }
630 }
631 if let Some(text) = agl_text(name) {
632 let mut chars = text.chars();
633 return match (chars.next(), chars.next()) {
634 (Some(c), None) => Some(c),
635 _ => None,
636 };
637 }
638 tex_glyph(name)
639}
640
641pub fn glyph_to_text(name: &str) -> Option<String> {
649 let base = name.split('.').next().unwrap_or_default();
650 if base.is_empty() {
651 return None;
652 }
653 let mut out = String::new();
654 for component in base.split('_') {
655 push_component(component, &mut out)?;
656 }
657 Some(out)
658}
659
660fn push_component(component: &str, out: &mut String) -> Option<()> {
663 let hex = component.strip_prefix("uni").unwrap_or_default();
664 if hex.len() >= 8 && hex.len().is_multiple_of(4) && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
665 for group in hex.as_bytes().chunks(4) {
668 let group = std::str::from_utf8(group).ok()?;
669 let scalar = u32::from_str_radix(group, 16).ok()?;
670 out.push(char::from_u32(scalar)?);
671 }
672 return Some(());
673 }
674 if let Some(text) = agl_text(component) {
675 out.push_str(text);
676 return Some(());
677 }
678 out.push(glyph_to_unicode(component)?);
679 Some(())
680}
681
682fn agl_text(name: &str) -> Option<&'static str> {
684 let index = agl::AGL.binary_search_by(|(n, _)| n.cmp(&name)).ok()?;
685 Some(agl::AGL[index].1)
686}
687
688fn tex_glyph(name: &str) -> Option<char> {
690 let index = GLYPHS_TEX.binary_search_by(|(n, _)| n.cmp(&name)).ok()?;
691 Some(GLYPHS_TEX[index].1)
692}
693
694const GLYPHS_TEX: &[(&str, char)] = &[
699 ("Circle", '\u{25CB}'),
700 ("Ifractur", '\u{2111}'),
701 ("Rfractur", '\u{211C}'),
702 ("angbracketleft", '\u{27E8}'),
703 ("angbracketleftBig", '\u{27E8}'),
704 ("angbracketleftBigg", '\u{27E8}'),
705 ("angbracketleftbig", '\u{27E8}'),
706 ("angbracketleftbigg", '\u{27E8}'),
707 ("angbracketright", '\u{27E9}'),
708 ("angbracketrightBig", '\u{27E9}'),
709 ("angbracketrightBigg", '\u{27E9}'),
710 ("angbracketrightbig", '\u{27E9}'),
711 ("angbracketrightbigg", '\u{27E9}'),
712 ("arrowbothv", '\u{2195}'),
713 ("arrowdblbothv", '\u{21D5}'),
714 ("arrowhookleft", '\u{21A9}'),
715 ("arrowhookright", '\u{21AA}'),
716 ("arrowleftbothalf", '\u{21BD}'),
717 ("arrowlefttophalf", '\u{21BC}'),
718 ("arrownortheast", '\u{2197}'),
719 ("arrownorthwest", '\u{2196}'),
720 ("arrowrightbothalf", '\u{21C1}'),
721 ("arrowrighttophalf", '\u{21C0}'),
722 ("arrowsoutheast", '\u{2198}'),
723 ("arrowsouthwest", '\u{2199}'),
724 ("asteriskmath", '\u{2217}'),
725 ("backslashBig", '\\'),
726 ("backslashBigg", '\\'),
727 ("backslashbig", '\\'),
728 ("backslashbigg", '\\'),
729 ("bardbl", '\u{2016}'),
730 ("braceleftBig", '{'),
731 ("braceleftBigg", '{'),
732 ("braceleftbig", '{'),
733 ("braceleftbigg", '{'),
734 ("bracerightBig", '}'),
735 ("bracerightBigg", '}'),
736 ("bracerightbig", '}'),
737 ("bracerightbigg", '}'),
738 ("bracketleftBig", '['),
739 ("bracketleftBigg", '['),
740 ("bracketleftbig", '['),
741 ("bracketleftbigg", '['),
742 ("bracketrightBig", ']'),
743 ("bracketrightBigg", ']'),
744 ("bracketrightbig", ']'),
745 ("bracketrightbigg", ']'),
746 ("ceilingleft", '\u{2308}'),
747 ("ceilingleftBig", '\u{2308}'),
748 ("ceilingleftBigg", '\u{2308}'),
749 ("ceilingleftbig", '\u{2308}'),
750 ("ceilingleftbigg", '\u{2308}'),
751 ("ceilingright", '\u{2309}'),
752 ("ceilingrightBig", '\u{2309}'),
753 ("ceilingrightBigg", '\u{2309}'),
754 ("ceilingrightbig", '\u{2309}'),
755 ("ceilingrightbigg", '\u{2309}'),
756 ("circlecopyrt", '\u{A9}'),
757 ("circledivide", '\u{2298}'),
758 ("circledot", '\u{2299}'),
759 ("circledotdisplay", '\u{2A00}'),
760 ("circledottext", '\u{2A00}'),
761 ("circleminus", '\u{2296}'),
762 ("circlemultiply", '\u{2297}'),
763 ("circlemultiplydisplay", '\u{2A02}'),
764 ("circlemultiplytext", '\u{2A02}'),
765 ("circleplus", '\u{2295}'),
766 ("circleplusdisplay", '\u{2A01}'),
767 ("circleplustext", '\u{2A01}'),
768 ("contintegraldisplay", '\u{222E}'),
769 ("contintegraltext", '\u{222E}'),
770 ("coproduct", '\u{2210}'),
771 ("coproductdisplay", '\u{2210}'),
772 ("coproducttext", '\u{2210}'),
773 ("diamondmath", '\u{22C4}'),
774 ("dotlessj", '\u{237}'),
775 ("epsilon1", '\u{3F5}'),
776 ("equivasymptotic", '\u{224D}'),
777 ("flat", '\u{266D}'),
778 ("floorleft", '\u{230A}'),
779 ("floorleftBig", '\u{230A}'),
780 ("floorleftBigg", '\u{230A}'),
781 ("floorleftbig", '\u{230A}'),
782 ("floorleftbigg", '\u{230A}'),
783 ("floorright", '\u{230B}'),
784 ("floorrightBig", '\u{230B}'),
785 ("floorrightBigg", '\u{230B}'),
786 ("floorrightbig", '\u{230B}'),
787 ("floorrightbigg", '\u{230B}'),
788 ("followsequal", '\u{227D}'),
789 ("greatermuch", '\u{226B}'),
790 ("greaterorsimilar", '\u{2273}'),
791 ("hatwide", '^'),
792 ("hatwider", '^'),
793 ("hatwidest", '^'),
794 ("integraldisplay", '\u{222B}'),
795 ("integraltext", '\u{222B}'),
796 ("intercal", '\u{22BA}'),
797 ("intersectiondisplay", '\u{22C2}'),
798 ("intersectionsq", '\u{2293}'),
799 ("intersectionsqdisplay", '\u{2A05}'),
800 ("intersectionsqtext", '\u{2A05}'),
801 ("intersectiontext", '\u{22C2}'),
802 ("latticetop", '\u{22A4}'),
803 ("lessmuch", '\u{226A}'),
804 ("lessorsimilar", '\u{2272}'),
805 ("logicalanddisplay", '\u{22C0}'),
806 ("logicalandtext", '\u{22C0}'),
807 ("logicalordisplay", '\u{22C1}'),
808 ("logicalortext", '\u{22C1}'),
809 ("lscript", '\u{2113}'),
810 ("mapsto", '\u{21A6}'),
811 ("measuredangle", '\u{2221}'),
812 ("minusplus", '\u{2213}'),
813 ("natural", '\u{266E}'),
814 ("negationslash", '\u{0338}'),
815 ("nequal", '\u{2260}'),
816 ("notexistential", '\u{2204}'),
817 ("openbullet", '\u{25E6}'),
818 ("owner", '\u{220B}'),
819 ("parenleftBig", '('),
820 ("parenleftBigg", '('),
821 ("parenleftbig", '('),
822 ("parenleftbigg", '('),
823 ("parenrightBig", ')'),
824 ("parenrightBigg", ')'),
825 ("parenrightbig", ')'),
826 ("parenrightbigg", ')'),
827 ("phi1", '\u{3D5}'),
828 ("pi1", '\u{3D6}'),
829 ("precedesequal", '\u{227C}'),
830 ("prime", '\u{2032}'),
831 ("productdisplay", '\u{220F}'),
832 ("producttext", '\u{220F}'),
833 ("radicalBig", '\u{221A}'),
834 ("radicalBigg", '\u{221A}'),
835 ("radicalbig", '\u{221A}'),
836 ("radicalbigg", '\u{221A}'),
837 ("rho1", '\u{3F1}'),
838 ("sharp", '\u{266F}'),
839 ("sigma1", '\u{3C2}'),
840 ("similarequal", '\u{2243}'),
841 ("slashBig", '/'),
842 ("slashBigg", '/'),
843 ("slashbig", '/'),
844 ("slashbigg", '/'),
845 ("slurabove", '\u{2322}'),
846 ("slurbelow", '\u{2323}'),
847 ("squaresolid", '\u{25A0}'),
848 ("star", '\u{22C6}'),
849 ("subsetnoteql", '\u{228A}'),
850 ("subsetsqequal", '\u{2291}'),
851 ("summationdisplay", '\u{2211}'),
852 ("summationtext", '\u{2211}'),
853 ("supersetsqequal", '\u{2292}'),
854 ("theta1", '\u{3D1}'),
855 ("tie", '\u{2040}'),
856 ("tildewide", '~'),
857 ("tildewider", '~'),
858 ("tildewidest", '~'),
859 ("triangle", '\u{25B3}'),
860 ("triangleinv", '\u{25BD}'),
861 ("triangleleft", '\u{25C1}'),
862 ("triangleright", '\u{25B7}'),
863 ("turnstileleft", '\u{22A2}'),
864 ("turnstileright", '\u{22A3}'),
865 ("uniondisplay", '\u{22C3}'),
866 ("unionmulti", '\u{228E}'),
867 ("unionmultidisplay", '\u{2A04}'),
868 ("unionmultitext", '\u{2A04}'),
869 ("unionsq", '\u{2294}'),
870 ("unionsqdisplay", '\u{2A06}'),
871 ("unionsqtext", '\u{2A06}'),
872 ("uniontext", '\u{22C3}'),
873 ("vector", '\u{20D7}'),
874 ("vextenddouble", '\u{2016}'),
875 ("vextendsingle", '|'),
876 ("weierstrass", '\u{2118}'),
877 ("wreathproduct", '\u{2240}'),
878];
879
880pub fn type1_builtin_encoding(program: &[u8]) -> Option<Box<[Option<String>; 256]>> {
889 let clear = type1_clear_text(program);
890 let mut tokens = PsTokens {
891 bytes: clear,
892 at: 0,
893 };
894 tokens.find(|t| *t == b"/Encoding")?;
895 let mut table: Box<[Option<String>; 256]> = Box::new(std::array::from_fn(|_| None));
896 let tokens: Vec<&[u8]> = tokens.collect();
897 if tokens.first() == Some(&b"StandardEncoding".as_slice()) {
898 for (code, slot) in table.iter_mut().enumerate() {
899 *slot = standard_encoding_name(code as u8).map(String::from);
900 }
901 }
902 for entry in tokens.windows(4) {
903 if entry[0] != b"dup" || entry[3] != b"put" {
904 continue;
905 }
906 let Some(code) = std::str::from_utf8(entry[1])
907 .ok()
908 .and_then(|t| t.parse::<usize>().ok())
909 else {
910 continue;
911 };
912 let Some(name) = entry[2]
913 .strip_prefix(b"/")
914 .and_then(|n| std::str::from_utf8(n).ok())
915 else {
916 continue;
917 };
918 if let Some(slot) = table.get_mut(code) {
919 *slot = Some(name.to_string());
920 }
921 }
922 Some(table)
923}
924
925fn type1_clear_text(program: &[u8]) -> &[u8] {
929 if program.first() == Some(&0x80) {
930 return pfb_clear_text(program);
931 }
932 const TOKEN: &[u8] = b"eexec";
933 let end = program
934 .windows(TOKEN.len())
935 .position(|w| w == TOKEN)
936 .unwrap_or(program.len());
937 &program[..end]
938}
939
940fn pfb_clear_text(program: &[u8]) -> &[u8] {
944 if program.get(1) != Some(&0x01) || program.len() < 6 {
945 return &[];
946 }
947 let len = u32::from_le_bytes([program[2], program[3], program[4], program[5]]) as usize;
948 let end = len.saturating_add(6).min(program.len());
949 &program[6..end]
950}
951
952struct PsTokens<'a> {
956 bytes: &'a [u8],
957 at: usize,
958}
959
960impl<'a> Iterator for PsTokens<'a> {
961 type Item = &'a [u8];
962
963 fn next(&mut self) -> Option<&'a [u8]> {
964 loop {
965 let &b = self.bytes.get(self.at)?;
966 if is_ps_whitespace(b) {
967 self.at += 1;
968 continue;
969 }
970 if b != b'%' {
971 break;
972 }
973 while self
974 .bytes
975 .get(self.at)
976 .is_some_and(|&b| b != b'\n' && b != b'\r')
977 {
978 self.at += 1;
979 }
980 }
981 let start = self.at;
982 if is_ps_delimiter(self.bytes[start]) {
983 self.at += 1;
984 return Some(&self.bytes[start..self.at]);
985 }
986 self.at += 1;
987 while self
988 .bytes
989 .get(self.at)
990 .is_some_and(|&b| !is_ps_whitespace(b) && !is_ps_delimiter(b) && b != b'/')
991 {
992 self.at += 1;
993 }
994 Some(&self.bytes[start..self.at])
995 }
996}
997
998fn is_ps_whitespace(b: u8) -> bool {
999 matches!(b, b' ' | b'\t' | b'\r' | b'\n' | b'\x0C' | b'\0')
1000}
1001
1002fn is_ps_delimiter(b: u8) -> bool {
1003 matches!(b, b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}')
1004}
1005
1006#[cfg(test)]
1007mod tests {
1008 use super::*;
1009
1010 #[test]
1012 fn win_ansi_spot_checks() {
1013 assert_eq!(win_ansi(b'A'), Some('A'));
1014 assert_eq!(win_ansi(0x93), Some('\u{201C}')); assert_eq!(win_ansi(0x80), Some('\u{20AC}')); assert_eq!(win_ansi(0xE9), Some('\u{E9}')); assert_eq!(win_ansi(0x81), None); assert_eq!(win_ansi(0x0A), None); }
1020
1021 #[test]
1022 fn win_ansi_glyph_name_spot_checks() {
1023 assert_eq!(win_ansi_glyph_name(0x41), Some("A"));
1024 assert_eq!(win_ansi_glyph_name(0x20), Some("space"));
1025 assert_eq!(win_ansi_glyph_name(0x27), Some("quotesingle")); assert_eq!(win_ansi_glyph_name(0x60), Some("grave")); assert_eq!(win_ansi_glyph_name(0x80), Some("Euro"));
1028 assert_eq!(win_ansi_glyph_name(0x93), Some("quotedblleft"));
1029 assert_eq!(win_ansi_glyph_name(0xE9), Some("eacute"));
1030 assert_eq!(win_ansi_glyph_name(0xFF), Some("ydieresis"));
1031 assert_eq!(win_ansi_glyph_name(0x81), None); assert_eq!(win_ansi_glyph_name(0x0A), None); }
1034
1035 #[test]
1049 fn mac_roman_glyph_name_matches_mac_roman_table() {
1050 assert_eq!(mac_roman_glyph_name(0xCA), Some("space"));
1051 assert_eq!(mac_roman_glyph_name(0xBD), Some("Omega"));
1052 assert_eq!(mac_roman_glyph_name(0xA5), Some("bullet"));
1053 assert_eq!(mac_roman_glyph_name(0xDE), Some("fi"));
1054 for code in 0u16..=255 {
1055 let code = code as u8;
1056 assert_eq!(
1057 mac_roman_glyph_name(code).is_some(),
1058 mac_roman(code).is_some(),
1059 "code {code:#04x}: mac_roman_glyph_name/mac_roman domain mismatch"
1060 );
1061 let Some(name) = mac_roman_glyph_name(code) else {
1062 continue;
1063 };
1064 let expected = match code {
1065 0xCA => ' ',
1066 0xBD => '\u{2126}',
1067 _ => mac_roman(code).unwrap(),
1068 };
1069 assert_eq!(
1070 glyph_to_unicode(name),
1071 Some(expected),
1072 "code {code:#04x} name {name:?}: glyph_to_unicode disagrees with mac_roman"
1073 );
1074 }
1075 }
1076
1077 #[test]
1079 fn win_ansi_glyph_name_matches_win_ansi_table() {
1080 assert_eq!(win_ansi_glyph_name(0xA0), Some("space"));
1081 assert_eq!(win_ansi_glyph_name(0xAD), Some("hyphen"));
1082 for code in 0u16..=255 {
1083 let code = code as u8;
1084 assert_eq!(
1085 win_ansi_glyph_name(code).is_some(),
1086 win_ansi(code).is_some(),
1087 "code {code:#04x}: win_ansi_glyph_name/win_ansi domain mismatch"
1088 );
1089 let Some(name) = win_ansi_glyph_name(code) else {
1090 continue;
1091 };
1092 let expected = match code {
1093 0xA0 => ' ',
1094 0xAD => '-',
1095 _ => win_ansi(code).unwrap(),
1096 };
1097 assert_eq!(
1098 glyph_to_unicode(name),
1099 Some(expected),
1100 "code {code:#04x} name {name:?}: glyph_to_unicode disagrees with win_ansi"
1101 );
1102 }
1103 }
1104
1105 #[test]
1107 fn mac_roman_spot_checks() {
1108 assert_eq!(mac_roman(b'A'), Some('A'));
1109 assert_eq!(mac_roman(0xD0), Some('\u{2013}')); assert_eq!(mac_roman(0x80), Some('\u{C4}')); assert_eq!(mac_roman(0xA5), Some('\u{2022}')); assert_eq!(mac_roman(0xFF), Some('\u{2C7}')); assert_eq!(mac_roman(0x00), None);
1114 }
1115
1116 #[test]
1118 fn standard_spot_checks() {
1119 assert_eq!(standard(b'A'), Some('A'));
1120 assert_eq!(standard(0xA9), Some('\u{27}')); assert_eq!(standard(0x27), Some('\u{2019}')); assert_eq!(standard(0x60), Some('\u{2018}')); assert_eq!(standard(0xD0), Some('\u{2014}')); assert_eq!(standard(0x7F), None);
1125 assert_eq!(standard(0xA0), None); }
1127
1128 #[test]
1130 fn glyph_names_hex_forms() {
1131 assert_eq!(glyph_to_unicode("uni03B1"), Some('\u{3B1}'));
1132 assert_eq!(glyph_to_unicode("uni20AC"), Some('\u{20AC}'));
1133 assert_eq!(glyph_to_unicode("u1F600"), Some('\u{1F600}'));
1134 assert_eq!(glyph_to_unicode("u00E9"), Some('\u{E9}'));
1135 assert_eq!(glyph_to_unicode("uniD800"), None); assert_eq!(glyph_to_unicode("uniXYZW"), None);
1137 }
1138
1139 #[test]
1150 fn mac_os_roman_codes_follow_table_115() {
1151 assert_eq!(mac_os_roman_code('A'), Some(0x41));
1152 assert_eq!(mac_os_roman_code('\u{E9}'), Some(0x8E), "eacute");
1153 assert_eq!(mac_os_roman_code('\u{2022}'), Some(0xA5), "bullet");
1154 assert_eq!(mac_os_roman_code('\u{2260}'), Some(173), "notequal");
1156 assert_eq!(mac_os_roman_code('\u{221E}'), Some(176), "infinity");
1157 assert_eq!(mac_os_roman_code('\u{03C0}'), Some(185), "pi");
1158 assert_eq!(
1159 mac_os_roman_code('\u{2126}'),
1160 Some(189),
1161 "Omega, the glyph list's ohm"
1162 );
1163 assert_eq!(
1164 mac_os_roman_code('\u{2206}'),
1165 Some(198),
1166 "Delta, the glyph list's increment"
1167 );
1168 assert_eq!(mac_os_roman_code('\u{25CA}'), Some(215), "lozenge");
1169 assert_eq!(
1170 mac_os_roman_code('\u{20AC}'),
1171 Some(219),
1172 "Euro replaces currency"
1173 );
1174 assert_eq!(mac_os_roman_code('\u{A4}'), None, "currency is gone");
1175 assert_eq!(mac_os_roman_code('\u{F8FF}'), Some(240), "apple");
1176 assert_eq!(
1177 mac_os_roman_code('\u{0394}'),
1178 None,
1179 "Greek Delta is not the glyph list's Delta"
1180 );
1181 assert_eq!(mac_os_roman_code('\u{4E00}'), None);
1182 }
1183
1184 #[test]
1186 fn symbol_encoding_follows_annex_d5() {
1187 assert_eq!(symbol_glyph_name(0o141), Some("alpha"));
1191 assert_eq!(symbol(0o141), Some('\u{03B1}'));
1192 assert_eq!(symbol(0o127), Some('\u{2126}'), "Omega");
1195 assert_eq!(symbol(0o104), Some('\u{2206}'), "Delta");
1196 assert_eq!(symbol(0o155), Some('\u{00B5}'), "mu");
1197 assert_eq!(symbol(0o240), Some('\u{20AC}'), "Euro");
1198 assert_eq!(symbol(0o300), Some('\u{2135}'), "aleph");
1199 assert_eq!(symbol(0o301), Some('\u{2111}'), "Ifraktur");
1200 assert_eq!(symbol(0o354), Some('\u{F8F1}'), "bracelefttp");
1201 assert_eq!(symbol(0o040), Some(' '));
1202 assert_eq!(symbol(0o060), Some('0'), "digits keep their ASCII places");
1203 for code in [0o000, 0o037, 0o177, 0o200, 0o237, 0o360, 0o377] {
1207 assert_eq!(symbol_glyph_name(code), None, "{code:o}");
1208 assert_eq!(symbol(code), None, "{code:o}");
1209 }
1210 let assigned = (0..=255u8)
1211 .filter(|c| symbol_glyph_name(*c).is_some())
1212 .count();
1213 assert_eq!(assigned, 189, "Annex D.5 assigns 189 codes");
1214 for code in 0..=255u8 {
1215 assert_eq!(
1216 symbol(code).is_some(),
1217 symbol_glyph_name(code).is_some(),
1218 "every named code has a character: {code:o}"
1219 );
1220 }
1221 }
1222
1223 #[test]
1225 fn zapf_dingbats_encoding_follows_annex_d6() {
1226 assert_eq!(zapf_dingbats_glyph_name(0o041), Some("a1"));
1227 assert_eq!(zapf_dingbats(0o041), Some('\u{2701}'));
1228 assert_eq!(zapf_dingbats_glyph_name(0o043), Some("a202"));
1229 assert_eq!(zapf_dingbats(0o043), Some('\u{2703}'));
1230 assert_eq!(zapf_dingbats_glyph_name(0o315), Some("a153"));
1231 assert_eq!(zapf_dingbats(0o315), Some('\u{278D}'));
1232 assert_eq!(zapf_dingbats_glyph_name(0o376), Some("a191"));
1233 assert_eq!(zapf_dingbats(0o376), Some('\u{27BE}'));
1234 assert_eq!(zapf_dingbats(0o040), Some(' '));
1235 for code in [0o000, 0o177, 0o200, 0o240, 0o360, 0o377] {
1236 assert_eq!(zapf_dingbats_glyph_name(code), None, "{code:o}");
1237 assert_eq!(zapf_dingbats(code), None, "{code:o}");
1238 }
1239 assert_eq!(zapf_dingbats_glyph_name(0o200), None);
1241 let assigned = (0..=255u8)
1242 .filter(|c| zapf_dingbats_glyph_name(*c).is_some())
1243 .count();
1244 assert_eq!(assigned, 188, "Annex D.6 assigns space and 187 dingbats");
1245 for code in 0..=255u8 {
1246 assert_eq!(
1247 zapf_dingbats(code).is_some(),
1248 zapf_dingbats_glyph_name(code).is_some(),
1249 "{code:o}"
1250 );
1251 }
1252 }
1253
1254 #[test]
1256 fn standard_encoding_name_matches_standard_table() {
1257 for code in 0u16..=255 {
1258 let code = code as u8;
1259 assert_eq!(
1260 standard_encoding_name(code).is_some(),
1261 standard(code).is_some(),
1262 "code {code:#04x}: standard_encoding_name/standard domain mismatch"
1263 );
1264 if let (Some(name), Some(expected)) = (standard_encoding_name(code), standard(code)) {
1265 if let Some(resolved) = glyph_to_unicode(name) {
1266 assert_eq!(
1267 resolved, expected,
1268 "code {code:#04x} name {name:?}: glyph_to_unicode disagrees with standard"
1269 );
1270 }
1271 }
1272 }
1273 }
1274
1275 #[test]
1276 fn standard_encoding_name_spot_checks() {
1277 assert_eq!(standard_encoding_name(b'A'), Some("A"));
1278 assert_eq!(standard_encoding_name(0x27), Some("quoteright"));
1279 assert_eq!(standard_encoding_name(0x60), Some("quoteleft"));
1280 assert_eq!(standard_encoding_name(0xA1), Some("exclamdown"));
1281 assert_eq!(standard_encoding_name(0xA4), Some("fraction"));
1282 assert_eq!(standard_encoding_name(0xA6), Some("florin"));
1283 assert_eq!(standard_encoding_name(0xC1), Some("grave"));
1284 assert_eq!(standard_encoding_name(0xC6), Some("breve"));
1285 assert_eq!(standard_encoding_name(0xE1), Some("AE"));
1286 assert_eq!(standard_encoding_name(0xF1), Some("ae"));
1287 assert_eq!(standard_encoding_name(0xFB), Some("germandbls"));
1288 assert_eq!(standard_encoding_name(0x7F), None);
1289 assert_eq!(standard_encoding_name(0xA0), None);
1290 }
1291
1292 #[test]
1293 fn glyph_names_letters_and_tables() {
1294 assert_eq!(glyph_to_unicode("A"), Some('A'));
1295 assert_eq!(glyph_to_unicode("z"), Some('z'));
1296 assert_eq!(glyph_to_unicode("alpha"), Some('\u{3B1}'));
1297 assert_eq!(glyph_to_unicode("eacute"), Some('\u{E9}'));
1298 assert_eq!(glyph_to_unicode("quotedblleft"), Some('\u{201C}'));
1299 assert_eq!(glyph_to_unicode("seven"), Some('7'));
1300 assert_eq!(glyph_to_unicode("union"), Some('\u{222A}'));
1301 assert_eq!(glyph_to_unicode("nosuchglyphname"), None);
1302 }
1303
1304 #[test]
1305 fn glyph_text_ligatures_and_variants() {
1306 assert_eq!(glyph_to_text("f_i").as_deref(), Some("fi"));
1307 assert_eq!(glyph_to_text("f_l").as_deref(), Some("fl"));
1308 assert_eq!(glyph_to_text("T_h").as_deref(), Some("Th"));
1309 assert_eq!(glyph_to_text("f_f_i").as_deref(), Some("ffi"));
1310 assert_eq!(glyph_to_text("eight.oldstyle").as_deref(), Some("8"));
1311 assert_eq!(glyph_to_text("x.sc").as_deref(), Some("x"));
1312 assert_eq!(glyph_to_text("C.a").as_deref(), Some("C"));
1313 assert_eq!(glyph_to_text("f_i.alt").as_deref(), Some("fi"));
1315 assert_eq!(glyph_to_text("uni00A0").as_deref(), Some("\u{A0}"));
1316 assert_eq!(glyph_to_text("eacute").as_deref(), Some("\u{E9}"));
1317 }
1318
1319 #[test]
1320 fn glyph_text_multi_group_uni() {
1321 assert_eq!(
1322 glyph_to_text("uni20AC0308").as_deref(),
1323 Some("\u{20AC}\u{0308}")
1324 );
1325 assert_eq!(glyph_to_text("uniD800DC00"), None); }
1327
1328 #[test]
1329 fn glyph_text_rejects_unknowns() {
1330 assert_eq!(glyph_to_text(".notdef"), None);
1331 assert_eq!(glyph_to_text(""), None);
1332 assert_eq!(glyph_to_text("glorp"), None);
1333 assert_eq!(glyph_to_text("f_glorp"), None);
1335 assert_eq!(glyph_to_text("f__i"), None);
1336 }
1337
1338 #[test]
1343 fn glyph_names_cover_the_full_adobe_glyph_list() {
1344 assert_eq!(glyph_to_unicode("universal"), Some('\u{2200}'));
1345 assert_eq!(glyph_to_unicode("existential"), Some('\u{2203}'));
1346 assert_eq!(glyph_to_unicode("emptyset"), Some('\u{2205}'));
1347 assert_eq!(glyph_to_unicode("copyright"), Some('\u{A9}'));
1348 assert_eq!(glyph_to_unicode("ff"), Some('\u{FB00}'));
1349 assert_eq!(glyph_to_unicode("afii57414"), Some('\u{0626}'));
1350 assert_eq!(
1351 glyph_to_text("dalethatafpatah").as_deref(),
1352 Some("\u{05D3}\u{05B2}")
1353 );
1354 assert_eq!(glyph_to_unicode("dalethatafpatah"), None);
1355 }
1356
1357 #[test]
1360 fn tex_math_glyph_names_resolve() {
1361 assert_eq!(glyph_to_unicode("owner"), Some('\u{220B}'));
1362 assert_eq!(glyph_to_unicode("arrowbothv"), Some('\u{2195}'));
1363 assert_eq!(glyph_to_unicode("angbracketleft"), Some('\u{27E8}'));
1364 assert_eq!(glyph_to_unicode("lessmuch"), Some('\u{226A}'));
1365 assert_eq!(glyph_to_unicode("prime"), Some('\u{2032}'));
1366 assert_eq!(glyph_to_unicode("negationslash"), Some('\u{0338}'));
1367 assert_eq!(glyph_to_unicode("lessorsimilar"), Some('\u{2272}'));
1368 assert_eq!(glyph_to_unicode("notexistential"), Some('\u{2204}'));
1369 assert_eq!(glyph_to_unicode("vextendsingle"), Some('|'));
1370 assert_eq!(glyph_to_unicode("Circle"), Some('\u{25CB}'));
1371 }
1372
1373 #[test]
1375 fn type1_program_encoding_reads_dup_put_entries() {
1376 let program: &[u8] = b"%!PS-AdobeFont-1.0: CMSY10\n/FontName /CMSY10 def\n\
1377 /Encoding 256 array\n0 1 255 {1 index exch /.notdef put} for\n\
1378 dup 56 /universal put\ndup 169 /copyright put\nreadonly def\n\
1379 currentdict end\ncurrentfile eexec\n\x80\x01dup 57 /existential put";
1380 let table = type1_builtin_encoding(program).expect("an /Encoding is present");
1381 assert_eq!(table[56].as_deref(), Some("universal"));
1382 assert_eq!(table[169].as_deref(), Some("copyright"));
1383 assert_eq!(table[57], None, "nothing past eexec is read");
1384 assert_eq!(table[0], None);
1385 }
1386
1387 #[test]
1388 fn type1_program_encoding_expands_the_standard_token() {
1389 let program: &[u8] = b"/Encoding StandardEncoding def\ncurrentfile eexec\n";
1390 let table = type1_builtin_encoding(program).unwrap();
1391 assert_eq!(table[0x41].as_deref(), Some("A"));
1392 assert_eq!(table[0x27].as_deref(), Some("quoteright"));
1393 }
1394
1395 #[test]
1396 fn type1_program_encoding_reads_pfb_segments() {
1397 let clear: &[u8] =
1398 b"/Encoding 256 array\ndup 65 /alpha put\nreadonly def\ncurrentfile eexec\n";
1399 let mut program = vec![0x80, 0x01];
1400 program.extend_from_slice(&(clear.len() as u32).to_le_bytes());
1401 program.extend_from_slice(clear);
1402 program.extend_from_slice(&[0x80, 0x02, 4, 0, 0, 0, 0xDE, 0xAD, 0xBE, 0xEF, 0x80, 0x03]);
1403 let table = type1_builtin_encoding(&program).unwrap();
1404 assert_eq!(table[65].as_deref(), Some("alpha"));
1405 }
1406
1407 #[test]
1408 fn type1_program_without_an_encoding_yields_none() {
1409 let program: &[u8] = b"/FontName /X def\ncurrentfile eexec\n";
1410 assert!(type1_builtin_encoding(program).is_none());
1411 }
1412}