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
249const STANDARD_HIGH: &[(u8, char)] = &[
251 (0xA1, '\u{A1}'),
252 (0xA2, '\u{A2}'),
253 (0xA3, '\u{A3}'),
254 (0xA4, '\u{2044}'),
255 (0xA5, '\u{A5}'),
256 (0xA6, '\u{192}'),
257 (0xA7, '\u{A7}'),
258 (0xA8, '\u{A4}'),
259 (0xA9, '\u{27}'),
260 (0xAA, '\u{201C}'),
261 (0xAB, '\u{AB}'),
262 (0xAC, '\u{2039}'),
263 (0xAD, '\u{203A}'),
264 (0xAE, '\u{FB01}'),
265 (0xAF, '\u{FB02}'),
266 (0xB1, '\u{2013}'),
267 (0xB2, '\u{2020}'),
268 (0xB3, '\u{2021}'),
269 (0xB4, '\u{B7}'),
270 (0xB6, '\u{B6}'),
271 (0xB7, '\u{2022}'),
272 (0xB8, '\u{201A}'),
273 (0xB9, '\u{201E}'),
274 (0xBA, '\u{201D}'),
275 (0xBB, '\u{BB}'),
276 (0xBC, '\u{2026}'),
277 (0xBD, '\u{2030}'),
278 (0xBF, '\u{BF}'),
279 (0xC1, '\u{60}'),
280 (0xC2, '\u{B4}'),
281 (0xC3, '\u{2C6}'),
282 (0xC4, '\u{2DC}'),
283 (0xC5, '\u{AF}'),
284 (0xC6, '\u{2D8}'),
285 (0xC7, '\u{2D9}'),
286 (0xC8, '\u{A8}'),
287 (0xCA, '\u{2DA}'),
288 (0xCB, '\u{B8}'),
289 (0xCD, '\u{2DD}'),
290 (0xCE, '\u{2DB}'),
291 (0xCF, '\u{2C7}'),
292 (0xD0, '\u{2014}'),
293 (0xE1, '\u{C6}'),
294 (0xE3, '\u{AA}'),
295 (0xE8, '\u{141}'),
296 (0xE9, '\u{D8}'),
297 (0xEA, '\u{152}'),
298 (0xEB, '\u{BA}'),
299 (0xF1, '\u{E6}'),
300 (0xF5, '\u{131}'),
301 (0xF8, '\u{142}'),
302 (0xF9, '\u{F8}'),
303 (0xFA, '\u{153}'),
304 (0xFB, '\u{DF}'),
305];
306
307pub fn standard(code: u8) -> Option<char> {
309 match code {
310 0x27 => Some('\u{2019}'),
311 0x60 => Some('\u{2018}'),
312 0x20..=0x7E => Some(code as char),
313 0xA1..=0xFF => STANDARD_HIGH
314 .iter()
315 .find(|&&(c, _)| c == code)
316 .map(|&(_, u)| u),
317 _ => None,
318 }
319}
320
321const STANDARD_ASCII_NAMES: [&str; 95] = [
327 "space",
328 "exclam",
329 "quotedbl",
330 "numbersign",
331 "dollar",
332 "percent",
333 "ampersand",
334 "quoteright",
335 "parenleft",
336 "parenright",
337 "asterisk",
338 "plus",
339 "comma",
340 "hyphen",
341 "period",
342 "slash",
343 "zero",
344 "one",
345 "two",
346 "three",
347 "four",
348 "five",
349 "six",
350 "seven",
351 "eight",
352 "nine",
353 "colon",
354 "semicolon",
355 "less",
356 "equal",
357 "greater",
358 "question",
359 "at",
360 "A",
361 "B",
362 "C",
363 "D",
364 "E",
365 "F",
366 "G",
367 "H",
368 "I",
369 "J",
370 "K",
371 "L",
372 "M",
373 "N",
374 "O",
375 "P",
376 "Q",
377 "R",
378 "S",
379 "T",
380 "U",
381 "V",
382 "W",
383 "X",
384 "Y",
385 "Z",
386 "bracketleft",
387 "backslash",
388 "bracketright",
389 "asciicircum",
390 "underscore",
391 "quoteleft",
392 "a",
393 "b",
394 "c",
395 "d",
396 "e",
397 "f",
398 "g",
399 "h",
400 "i",
401 "j",
402 "k",
403 "l",
404 "m",
405 "n",
406 "o",
407 "p",
408 "q",
409 "r",
410 "s",
411 "t",
412 "u",
413 "v",
414 "w",
415 "x",
416 "y",
417 "z",
418 "braceleft",
419 "bar",
420 "braceright",
421 "asciitilde",
422];
423
424const STANDARD_HIGH_NAMES: &[(u8, &str)] = &[
428 (0xA1, "exclamdown"),
429 (0xA2, "cent"),
430 (0xA3, "sterling"),
431 (0xA4, "fraction"),
432 (0xA5, "yen"),
433 (0xA6, "florin"),
434 (0xA7, "section"),
435 (0xA8, "currency"),
436 (0xA9, "quotesingle"),
437 (0xAA, "quotedblleft"),
438 (0xAB, "guillemotleft"),
439 (0xAC, "guilsinglleft"),
440 (0xAD, "guilsinglright"),
441 (0xAE, "fi"),
442 (0xAF, "fl"),
443 (0xB1, "endash"),
444 (0xB2, "dagger"),
445 (0xB3, "daggerdbl"),
446 (0xB4, "periodcentered"),
447 (0xB6, "paragraph"),
448 (0xB7, "bullet"),
449 (0xB8, "quotesinglbase"),
450 (0xB9, "quotedblbase"),
451 (0xBA, "quotedblright"),
452 (0xBB, "guillemotright"),
453 (0xBC, "ellipsis"),
454 (0xBD, "perthousand"),
455 (0xBF, "questiondown"),
456 (0xC1, "grave"),
457 (0xC2, "acute"),
458 (0xC3, "circumflex"),
459 (0xC4, "tilde"),
460 (0xC5, "macron"),
461 (0xC6, "breve"),
462 (0xC7, "dotaccent"),
463 (0xC8, "dieresis"),
464 (0xCA, "ring"),
465 (0xCB, "cedilla"),
466 (0xCD, "hungarumlaut"),
467 (0xCE, "ogonek"),
468 (0xCF, "caron"),
469 (0xD0, "emdash"),
470 (0xE1, "AE"),
471 (0xE3, "ordfeminine"),
472 (0xE8, "Lslash"),
473 (0xE9, "Oslash"),
474 (0xEA, "OE"),
475 (0xEB, "ordmasculine"),
476 (0xF1, "ae"),
477 (0xF5, "dotlessi"),
478 (0xF8, "lslash"),
479 (0xF9, "oslash"),
480 (0xFA, "oe"),
481 (0xFB, "germandbls"),
482];
483
484pub fn standard_encoding_name(code: u8) -> Option<&'static str> {
491 match code {
492 0x20..=0x7E => Some(STANDARD_ASCII_NAMES[(code - 0x20) as usize]),
493 0xA1..=0xFF => STANDARD_HIGH_NAMES
494 .iter()
495 .find(|&&(c, _)| c == code)
496 .map(|&(_, n)| n),
497 _ => None,
498 }
499}
500
501pub fn mac_os_roman_code(ch: char) -> Option<u8> {
508 if ch == '\u{2126}' {
512 return Some(0xBD);
513 }
514 if ('\u{20}'..='\u{7E}').contains(&ch) {
515 return Some(ch as u8);
516 }
517 MAC_ROMAN_HIGH
518 .iter()
519 .position(|&c| c == ch)
520 .map(|i| 0x80 + i as u8)
521}
522
523pub fn symbol_glyph_name(code: u8) -> Option<&'static str> {
528 let i = symbol::SYMBOL
529 .binary_search_by_key(&code, |&(c, _)| c)
530 .ok()?;
531 Some(symbol::SYMBOL[i].1)
532}
533
534pub fn symbol(code: u8) -> Option<char> {
540 glyph_to_unicode(symbol_glyph_name(code)?)
541}
542
543pub fn zapf_dingbats_glyph_name(code: u8) -> Option<&'static str> {
548 let i = symbol::ZAPF_DINGBATS
549 .binary_search_by_key(&code, |&(c, _, _)| c)
550 .ok()?;
551 Some(symbol::ZAPF_DINGBATS[i].1)
552}
553
554pub fn zapf_dingbats(code: u8) -> Option<char> {
560 let i = symbol::ZAPF_DINGBATS
561 .binary_search_by_key(&code, |&(c, _, _)| c)
562 .ok()?;
563 Some(symbol::ZAPF_DINGBATS[i].2)
564}
565
566pub fn glyph_to_unicode(name: &str) -> Option<char> {
572 if let Some(hex) = name.strip_prefix("uni") {
573 if hex.len() == 4 && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
574 return char::from_u32(u32::from_str_radix(hex, 16).ok()?);
575 }
576 }
577 if let Some(hex) = name.strip_prefix('u') {
578 if (4..=6).contains(&hex.len()) && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
579 return char::from_u32(u32::from_str_radix(hex, 16).ok()?);
580 }
581 }
582 let mut chars = name.chars();
583 if let (Some(c), None) = (chars.next(), chars.next()) {
584 if c.is_ascii_alphabetic() {
585 return Some(c);
586 }
587 }
588 if let Some(text) = agl_text(name) {
589 let mut chars = text.chars();
590 return match (chars.next(), chars.next()) {
591 (Some(c), None) => Some(c),
592 _ => None,
593 };
594 }
595 tex_glyph(name)
596}
597
598pub fn glyph_to_text(name: &str) -> Option<String> {
606 let base = name.split('.').next().unwrap_or_default();
607 if base.is_empty() {
608 return None;
609 }
610 let mut out = String::new();
611 for component in base.split('_') {
612 push_component(component, &mut out)?;
613 }
614 Some(out)
615}
616
617fn push_component(component: &str, out: &mut String) -> Option<()> {
620 let hex = component.strip_prefix("uni").unwrap_or_default();
621 if hex.len() >= 8 && hex.len().is_multiple_of(4) && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
622 for group in hex.as_bytes().chunks(4) {
625 let group = std::str::from_utf8(group).ok()?;
626 let scalar = u32::from_str_radix(group, 16).ok()?;
627 out.push(char::from_u32(scalar)?);
628 }
629 return Some(());
630 }
631 if let Some(text) = agl_text(component) {
632 out.push_str(text);
633 return Some(());
634 }
635 out.push(glyph_to_unicode(component)?);
636 Some(())
637}
638
639fn agl_text(name: &str) -> Option<&'static str> {
641 let index = agl::AGL.binary_search_by(|(n, _)| n.cmp(&name)).ok()?;
642 Some(agl::AGL[index].1)
643}
644
645fn tex_glyph(name: &str) -> Option<char> {
647 let index = GLYPHS_TEX.binary_search_by(|(n, _)| n.cmp(&name)).ok()?;
648 Some(GLYPHS_TEX[index].1)
649}
650
651const GLYPHS_TEX: &[(&str, char)] = &[
656 ("Circle", '\u{25CB}'),
657 ("Ifractur", '\u{2111}'),
658 ("Rfractur", '\u{211C}'),
659 ("angbracketleft", '\u{27E8}'),
660 ("angbracketleftBig", '\u{27E8}'),
661 ("angbracketleftBigg", '\u{27E8}'),
662 ("angbracketleftbig", '\u{27E8}'),
663 ("angbracketleftbigg", '\u{27E8}'),
664 ("angbracketright", '\u{27E9}'),
665 ("angbracketrightBig", '\u{27E9}'),
666 ("angbracketrightBigg", '\u{27E9}'),
667 ("angbracketrightbig", '\u{27E9}'),
668 ("angbracketrightbigg", '\u{27E9}'),
669 ("arrowbothv", '\u{2195}'),
670 ("arrowdblbothv", '\u{21D5}'),
671 ("arrowhookleft", '\u{21A9}'),
672 ("arrowhookright", '\u{21AA}'),
673 ("arrowleftbothalf", '\u{21BD}'),
674 ("arrowlefttophalf", '\u{21BC}'),
675 ("arrownortheast", '\u{2197}'),
676 ("arrownorthwest", '\u{2196}'),
677 ("arrowrightbothalf", '\u{21C1}'),
678 ("arrowrighttophalf", '\u{21C0}'),
679 ("arrowsoutheast", '\u{2198}'),
680 ("arrowsouthwest", '\u{2199}'),
681 ("asteriskmath", '\u{2217}'),
682 ("backslashBig", '\\'),
683 ("backslashBigg", '\\'),
684 ("backslashbig", '\\'),
685 ("backslashbigg", '\\'),
686 ("bardbl", '\u{2016}'),
687 ("braceleftBig", '{'),
688 ("braceleftBigg", '{'),
689 ("braceleftbig", '{'),
690 ("braceleftbigg", '{'),
691 ("bracerightBig", '}'),
692 ("bracerightBigg", '}'),
693 ("bracerightbig", '}'),
694 ("bracerightbigg", '}'),
695 ("bracketleftBig", '['),
696 ("bracketleftBigg", '['),
697 ("bracketleftbig", '['),
698 ("bracketleftbigg", '['),
699 ("bracketrightBig", ']'),
700 ("bracketrightBigg", ']'),
701 ("bracketrightbig", ']'),
702 ("bracketrightbigg", ']'),
703 ("ceilingleft", '\u{2308}'),
704 ("ceilingleftBig", '\u{2308}'),
705 ("ceilingleftBigg", '\u{2308}'),
706 ("ceilingleftbig", '\u{2308}'),
707 ("ceilingleftbigg", '\u{2308}'),
708 ("ceilingright", '\u{2309}'),
709 ("ceilingrightBig", '\u{2309}'),
710 ("ceilingrightBigg", '\u{2309}'),
711 ("ceilingrightbig", '\u{2309}'),
712 ("ceilingrightbigg", '\u{2309}'),
713 ("circlecopyrt", '\u{A9}'),
714 ("circledivide", '\u{2298}'),
715 ("circledot", '\u{2299}'),
716 ("circledotdisplay", '\u{2A00}'),
717 ("circledottext", '\u{2A00}'),
718 ("circleminus", '\u{2296}'),
719 ("circlemultiply", '\u{2297}'),
720 ("circlemultiplydisplay", '\u{2A02}'),
721 ("circlemultiplytext", '\u{2A02}'),
722 ("circleplus", '\u{2295}'),
723 ("circleplusdisplay", '\u{2A01}'),
724 ("circleplustext", '\u{2A01}'),
725 ("contintegraldisplay", '\u{222E}'),
726 ("contintegraltext", '\u{222E}'),
727 ("coproduct", '\u{2210}'),
728 ("coproductdisplay", '\u{2210}'),
729 ("coproducttext", '\u{2210}'),
730 ("diamondmath", '\u{22C4}'),
731 ("dotlessj", '\u{237}'),
732 ("epsilon1", '\u{3F5}'),
733 ("equivasymptotic", '\u{224D}'),
734 ("flat", '\u{266D}'),
735 ("floorleft", '\u{230A}'),
736 ("floorleftBig", '\u{230A}'),
737 ("floorleftBigg", '\u{230A}'),
738 ("floorleftbig", '\u{230A}'),
739 ("floorleftbigg", '\u{230A}'),
740 ("floorright", '\u{230B}'),
741 ("floorrightBig", '\u{230B}'),
742 ("floorrightBigg", '\u{230B}'),
743 ("floorrightbig", '\u{230B}'),
744 ("floorrightbigg", '\u{230B}'),
745 ("followsequal", '\u{227D}'),
746 ("greatermuch", '\u{226B}'),
747 ("greaterorsimilar", '\u{2273}'),
748 ("hatwide", '^'),
749 ("hatwider", '^'),
750 ("hatwidest", '^'),
751 ("integraldisplay", '\u{222B}'),
752 ("integraltext", '\u{222B}'),
753 ("intercal", '\u{22BA}'),
754 ("intersectiondisplay", '\u{22C2}'),
755 ("intersectionsq", '\u{2293}'),
756 ("intersectionsqdisplay", '\u{2A05}'),
757 ("intersectionsqtext", '\u{2A05}'),
758 ("intersectiontext", '\u{22C2}'),
759 ("latticetop", '\u{22A4}'),
760 ("lessmuch", '\u{226A}'),
761 ("lessorsimilar", '\u{2272}'),
762 ("logicalanddisplay", '\u{22C0}'),
763 ("logicalandtext", '\u{22C0}'),
764 ("logicalordisplay", '\u{22C1}'),
765 ("logicalortext", '\u{22C1}'),
766 ("lscript", '\u{2113}'),
767 ("mapsto", '\u{21A6}'),
768 ("measuredangle", '\u{2221}'),
769 ("minusplus", '\u{2213}'),
770 ("natural", '\u{266E}'),
771 ("negationslash", '\u{0338}'),
772 ("nequal", '\u{2260}'),
773 ("notexistential", '\u{2204}'),
774 ("openbullet", '\u{25E6}'),
775 ("owner", '\u{220B}'),
776 ("parenleftBig", '('),
777 ("parenleftBigg", '('),
778 ("parenleftbig", '('),
779 ("parenleftbigg", '('),
780 ("parenrightBig", ')'),
781 ("parenrightBigg", ')'),
782 ("parenrightbig", ')'),
783 ("parenrightbigg", ')'),
784 ("phi1", '\u{3D5}'),
785 ("pi1", '\u{3D6}'),
786 ("precedesequal", '\u{227C}'),
787 ("prime", '\u{2032}'),
788 ("productdisplay", '\u{220F}'),
789 ("producttext", '\u{220F}'),
790 ("radicalBig", '\u{221A}'),
791 ("radicalBigg", '\u{221A}'),
792 ("radicalbig", '\u{221A}'),
793 ("radicalbigg", '\u{221A}'),
794 ("rho1", '\u{3F1}'),
795 ("sharp", '\u{266F}'),
796 ("sigma1", '\u{3C2}'),
797 ("similarequal", '\u{2243}'),
798 ("slashBig", '/'),
799 ("slashBigg", '/'),
800 ("slashbig", '/'),
801 ("slashbigg", '/'),
802 ("slurabove", '\u{2322}'),
803 ("slurbelow", '\u{2323}'),
804 ("squaresolid", '\u{25A0}'),
805 ("star", '\u{22C6}'),
806 ("subsetnoteql", '\u{228A}'),
807 ("subsetsqequal", '\u{2291}'),
808 ("summationdisplay", '\u{2211}'),
809 ("summationtext", '\u{2211}'),
810 ("supersetsqequal", '\u{2292}'),
811 ("theta1", '\u{3D1}'),
812 ("tie", '\u{2040}'),
813 ("tildewide", '~'),
814 ("tildewider", '~'),
815 ("tildewidest", '~'),
816 ("triangle", '\u{25B3}'),
817 ("triangleinv", '\u{25BD}'),
818 ("triangleleft", '\u{25C1}'),
819 ("triangleright", '\u{25B7}'),
820 ("turnstileleft", '\u{22A2}'),
821 ("turnstileright", '\u{22A3}'),
822 ("uniondisplay", '\u{22C3}'),
823 ("unionmulti", '\u{228E}'),
824 ("unionmultidisplay", '\u{2A04}'),
825 ("unionmultitext", '\u{2A04}'),
826 ("unionsq", '\u{2294}'),
827 ("unionsqdisplay", '\u{2A06}'),
828 ("unionsqtext", '\u{2A06}'),
829 ("uniontext", '\u{22C3}'),
830 ("vector", '\u{20D7}'),
831 ("vextenddouble", '\u{2016}'),
832 ("vextendsingle", '|'),
833 ("weierstrass", '\u{2118}'),
834 ("wreathproduct", '\u{2240}'),
835];
836
837pub fn type1_builtin_encoding(program: &[u8]) -> Option<Box<[Option<String>; 256]>> {
846 let clear = type1_clear_text(program);
847 let mut tokens = PsTokens {
848 bytes: clear,
849 at: 0,
850 };
851 tokens.find(|t| *t == b"/Encoding")?;
852 let mut table: Box<[Option<String>; 256]> = Box::new(std::array::from_fn(|_| None));
853 let tokens: Vec<&[u8]> = tokens.collect();
854 if tokens.first() == Some(&b"StandardEncoding".as_slice()) {
855 for (code, slot) in table.iter_mut().enumerate() {
856 *slot = standard_encoding_name(code as u8).map(String::from);
857 }
858 }
859 for entry in tokens.windows(4) {
860 if entry[0] != b"dup" || entry[3] != b"put" {
861 continue;
862 }
863 let Some(code) = std::str::from_utf8(entry[1])
864 .ok()
865 .and_then(|t| t.parse::<usize>().ok())
866 else {
867 continue;
868 };
869 let Some(name) = entry[2]
870 .strip_prefix(b"/")
871 .and_then(|n| std::str::from_utf8(n).ok())
872 else {
873 continue;
874 };
875 if let Some(slot) = table.get_mut(code) {
876 *slot = Some(name.to_string());
877 }
878 }
879 Some(table)
880}
881
882fn type1_clear_text(program: &[u8]) -> &[u8] {
886 if program.first() == Some(&0x80) {
887 return pfb_clear_text(program);
888 }
889 const TOKEN: &[u8] = b"eexec";
890 let end = program
891 .windows(TOKEN.len())
892 .position(|w| w == TOKEN)
893 .unwrap_or(program.len());
894 &program[..end]
895}
896
897fn pfb_clear_text(program: &[u8]) -> &[u8] {
901 if program.get(1) != Some(&0x01) || program.len() < 6 {
902 return &[];
903 }
904 let len = u32::from_le_bytes([program[2], program[3], program[4], program[5]]) as usize;
905 let end = len.saturating_add(6).min(program.len());
906 &program[6..end]
907}
908
909struct PsTokens<'a> {
913 bytes: &'a [u8],
914 at: usize,
915}
916
917impl<'a> Iterator for PsTokens<'a> {
918 type Item = &'a [u8];
919
920 fn next(&mut self) -> Option<&'a [u8]> {
921 loop {
922 let &b = self.bytes.get(self.at)?;
923 if is_ps_whitespace(b) {
924 self.at += 1;
925 continue;
926 }
927 if b != b'%' {
928 break;
929 }
930 while self
931 .bytes
932 .get(self.at)
933 .is_some_and(|&b| b != b'\n' && b != b'\r')
934 {
935 self.at += 1;
936 }
937 }
938 let start = self.at;
939 if is_ps_delimiter(self.bytes[start]) {
940 self.at += 1;
941 return Some(&self.bytes[start..self.at]);
942 }
943 self.at += 1;
944 while self
945 .bytes
946 .get(self.at)
947 .is_some_and(|&b| !is_ps_whitespace(b) && !is_ps_delimiter(b) && b != b'/')
948 {
949 self.at += 1;
950 }
951 Some(&self.bytes[start..self.at])
952 }
953}
954
955fn is_ps_whitespace(b: u8) -> bool {
956 matches!(b, b' ' | b'\t' | b'\r' | b'\n' | b'\x0C' | b'\0')
957}
958
959fn is_ps_delimiter(b: u8) -> bool {
960 matches!(b, b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}')
961}
962
963#[cfg(test)]
964mod tests {
965 use super::*;
966
967 #[test]
969 fn win_ansi_spot_checks() {
970 assert_eq!(win_ansi(b'A'), Some('A'));
971 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); }
977
978 #[test]
979 fn win_ansi_glyph_name_spot_checks() {
980 assert_eq!(win_ansi_glyph_name(0x41), Some("A"));
981 assert_eq!(win_ansi_glyph_name(0x20), Some("space"));
982 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"));
985 assert_eq!(win_ansi_glyph_name(0x93), Some("quotedblleft"));
986 assert_eq!(win_ansi_glyph_name(0xE9), Some("eacute"));
987 assert_eq!(win_ansi_glyph_name(0xFF), Some("ydieresis"));
988 assert_eq!(win_ansi_glyph_name(0x81), None); assert_eq!(win_ansi_glyph_name(0x0A), None); }
991
992 #[test]
1001 fn win_ansi_glyph_name_matches_win_ansi_table() {
1002 assert_eq!(win_ansi_glyph_name(0xA0), Some("space"));
1003 assert_eq!(win_ansi_glyph_name(0xAD), Some("hyphen"));
1004 for code in 0u16..=255 {
1005 let code = code as u8;
1006 assert_eq!(
1007 win_ansi_glyph_name(code).is_some(),
1008 win_ansi(code).is_some(),
1009 "code {code:#04x}: win_ansi_glyph_name/win_ansi domain mismatch"
1010 );
1011 let Some(name) = win_ansi_glyph_name(code) else {
1012 continue;
1013 };
1014 let expected = match code {
1015 0xA0 => ' ',
1016 0xAD => '-',
1017 _ => win_ansi(code).unwrap(),
1018 };
1019 assert_eq!(
1020 glyph_to_unicode(name),
1021 Some(expected),
1022 "code {code:#04x} name {name:?}: glyph_to_unicode disagrees with win_ansi"
1023 );
1024 }
1025 }
1026
1027 #[test]
1029 fn mac_roman_spot_checks() {
1030 assert_eq!(mac_roman(b'A'), Some('A'));
1031 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);
1036 }
1037
1038 #[test]
1040 fn standard_spot_checks() {
1041 assert_eq!(standard(b'A'), Some('A'));
1042 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);
1047 assert_eq!(standard(0xA0), None); }
1049
1050 #[test]
1052 fn glyph_names_hex_forms() {
1053 assert_eq!(glyph_to_unicode("uni03B1"), Some('\u{3B1}'));
1054 assert_eq!(glyph_to_unicode("uni20AC"), Some('\u{20AC}'));
1055 assert_eq!(glyph_to_unicode("u1F600"), Some('\u{1F600}'));
1056 assert_eq!(glyph_to_unicode("u00E9"), Some('\u{E9}'));
1057 assert_eq!(glyph_to_unicode("uniD800"), None); assert_eq!(glyph_to_unicode("uniXYZW"), None);
1059 }
1060
1061 #[test]
1072 fn mac_os_roman_codes_follow_table_115() {
1073 assert_eq!(mac_os_roman_code('A'), Some(0x41));
1074 assert_eq!(mac_os_roman_code('\u{E9}'), Some(0x8E), "eacute");
1075 assert_eq!(mac_os_roman_code('\u{2022}'), Some(0xA5), "bullet");
1076 assert_eq!(mac_os_roman_code('\u{2260}'), Some(173), "notequal");
1078 assert_eq!(mac_os_roman_code('\u{221E}'), Some(176), "infinity");
1079 assert_eq!(mac_os_roman_code('\u{03C0}'), Some(185), "pi");
1080 assert_eq!(
1081 mac_os_roman_code('\u{2126}'),
1082 Some(189),
1083 "Omega, the glyph list's ohm"
1084 );
1085 assert_eq!(
1086 mac_os_roman_code('\u{2206}'),
1087 Some(198),
1088 "Delta, the glyph list's increment"
1089 );
1090 assert_eq!(mac_os_roman_code('\u{25CA}'), Some(215), "lozenge");
1091 assert_eq!(
1092 mac_os_roman_code('\u{20AC}'),
1093 Some(219),
1094 "Euro replaces currency"
1095 );
1096 assert_eq!(mac_os_roman_code('\u{A4}'), None, "currency is gone");
1097 assert_eq!(mac_os_roman_code('\u{F8FF}'), Some(240), "apple");
1098 assert_eq!(
1099 mac_os_roman_code('\u{0394}'),
1100 None,
1101 "Greek Delta is not the glyph list's Delta"
1102 );
1103 assert_eq!(mac_os_roman_code('\u{4E00}'), None);
1104 }
1105
1106 #[test]
1108 fn symbol_encoding_follows_annex_d5() {
1109 assert_eq!(symbol_glyph_name(0o141), Some("alpha"));
1113 assert_eq!(symbol(0o141), Some('\u{03B1}'));
1114 assert_eq!(symbol(0o127), Some('\u{2126}'), "Omega");
1117 assert_eq!(symbol(0o104), Some('\u{2206}'), "Delta");
1118 assert_eq!(symbol(0o155), Some('\u{00B5}'), "mu");
1119 assert_eq!(symbol(0o240), Some('\u{20AC}'), "Euro");
1120 assert_eq!(symbol(0o300), Some('\u{2135}'), "aleph");
1121 assert_eq!(symbol(0o301), Some('\u{2111}'), "Ifraktur");
1122 assert_eq!(symbol(0o354), Some('\u{F8F1}'), "bracelefttp");
1123 assert_eq!(symbol(0o040), Some(' '));
1124 assert_eq!(symbol(0o060), Some('0'), "digits keep their ASCII places");
1125 for code in [0o000, 0o037, 0o177, 0o200, 0o237, 0o360, 0o377] {
1129 assert_eq!(symbol_glyph_name(code), None, "{code:o}");
1130 assert_eq!(symbol(code), None, "{code:o}");
1131 }
1132 let assigned = (0..=255u8)
1133 .filter(|c| symbol_glyph_name(*c).is_some())
1134 .count();
1135 assert_eq!(assigned, 189, "Annex D.5 assigns 189 codes");
1136 for code in 0..=255u8 {
1137 assert_eq!(
1138 symbol(code).is_some(),
1139 symbol_glyph_name(code).is_some(),
1140 "every named code has a character: {code:o}"
1141 );
1142 }
1143 }
1144
1145 #[test]
1147 fn zapf_dingbats_encoding_follows_annex_d6() {
1148 assert_eq!(zapf_dingbats_glyph_name(0o041), Some("a1"));
1149 assert_eq!(zapf_dingbats(0o041), Some('\u{2701}'));
1150 assert_eq!(zapf_dingbats_glyph_name(0o043), Some("a202"));
1151 assert_eq!(zapf_dingbats(0o043), Some('\u{2703}'));
1152 assert_eq!(zapf_dingbats_glyph_name(0o315), Some("a153"));
1153 assert_eq!(zapf_dingbats(0o315), Some('\u{278D}'));
1154 assert_eq!(zapf_dingbats_glyph_name(0o376), Some("a191"));
1155 assert_eq!(zapf_dingbats(0o376), Some('\u{27BE}'));
1156 assert_eq!(zapf_dingbats(0o040), Some(' '));
1157 for code in [0o000, 0o177, 0o200, 0o240, 0o360, 0o377] {
1158 assert_eq!(zapf_dingbats_glyph_name(code), None, "{code:o}");
1159 assert_eq!(zapf_dingbats(code), None, "{code:o}");
1160 }
1161 assert_eq!(zapf_dingbats_glyph_name(0o200), None);
1163 let assigned = (0..=255u8)
1164 .filter(|c| zapf_dingbats_glyph_name(*c).is_some())
1165 .count();
1166 assert_eq!(assigned, 188, "Annex D.6 assigns space and 187 dingbats");
1167 for code in 0..=255u8 {
1168 assert_eq!(
1169 zapf_dingbats(code).is_some(),
1170 zapf_dingbats_glyph_name(code).is_some(),
1171 "{code:o}"
1172 );
1173 }
1174 }
1175
1176 #[test]
1178 fn standard_encoding_name_matches_standard_table() {
1179 for code in 0u16..=255 {
1180 let code = code as u8;
1181 assert_eq!(
1182 standard_encoding_name(code).is_some(),
1183 standard(code).is_some(),
1184 "code {code:#04x}: standard_encoding_name/standard domain mismatch"
1185 );
1186 if let (Some(name), Some(expected)) = (standard_encoding_name(code), standard(code)) {
1187 if let Some(resolved) = glyph_to_unicode(name) {
1188 assert_eq!(
1189 resolved, expected,
1190 "code {code:#04x} name {name:?}: glyph_to_unicode disagrees with standard"
1191 );
1192 }
1193 }
1194 }
1195 }
1196
1197 #[test]
1198 fn standard_encoding_name_spot_checks() {
1199 assert_eq!(standard_encoding_name(b'A'), Some("A"));
1200 assert_eq!(standard_encoding_name(0x27), Some("quoteright"));
1201 assert_eq!(standard_encoding_name(0x60), Some("quoteleft"));
1202 assert_eq!(standard_encoding_name(0xA1), Some("exclamdown"));
1203 assert_eq!(standard_encoding_name(0xA4), Some("fraction"));
1204 assert_eq!(standard_encoding_name(0xA6), Some("florin"));
1205 assert_eq!(standard_encoding_name(0xC1), Some("grave"));
1206 assert_eq!(standard_encoding_name(0xC6), Some("breve"));
1207 assert_eq!(standard_encoding_name(0xE1), Some("AE"));
1208 assert_eq!(standard_encoding_name(0xF1), Some("ae"));
1209 assert_eq!(standard_encoding_name(0xFB), Some("germandbls"));
1210 assert_eq!(standard_encoding_name(0x7F), None);
1211 assert_eq!(standard_encoding_name(0xA0), None);
1212 }
1213
1214 #[test]
1215 fn glyph_names_letters_and_tables() {
1216 assert_eq!(glyph_to_unicode("A"), Some('A'));
1217 assert_eq!(glyph_to_unicode("z"), Some('z'));
1218 assert_eq!(glyph_to_unicode("alpha"), Some('\u{3B1}'));
1219 assert_eq!(glyph_to_unicode("eacute"), Some('\u{E9}'));
1220 assert_eq!(glyph_to_unicode("quotedblleft"), Some('\u{201C}'));
1221 assert_eq!(glyph_to_unicode("seven"), Some('7'));
1222 assert_eq!(glyph_to_unicode("union"), Some('\u{222A}'));
1223 assert_eq!(glyph_to_unicode("nosuchglyphname"), None);
1224 }
1225
1226 #[test]
1227 fn glyph_text_ligatures_and_variants() {
1228 assert_eq!(glyph_to_text("f_i").as_deref(), Some("fi"));
1229 assert_eq!(glyph_to_text("f_l").as_deref(), Some("fl"));
1230 assert_eq!(glyph_to_text("T_h").as_deref(), Some("Th"));
1231 assert_eq!(glyph_to_text("f_f_i").as_deref(), Some("ffi"));
1232 assert_eq!(glyph_to_text("eight.oldstyle").as_deref(), Some("8"));
1233 assert_eq!(glyph_to_text("x.sc").as_deref(), Some("x"));
1234 assert_eq!(glyph_to_text("C.a").as_deref(), Some("C"));
1235 assert_eq!(glyph_to_text("f_i.alt").as_deref(), Some("fi"));
1237 assert_eq!(glyph_to_text("uni00A0").as_deref(), Some("\u{A0}"));
1238 assert_eq!(glyph_to_text("eacute").as_deref(), Some("\u{E9}"));
1239 }
1240
1241 #[test]
1242 fn glyph_text_multi_group_uni() {
1243 assert_eq!(
1244 glyph_to_text("uni20AC0308").as_deref(),
1245 Some("\u{20AC}\u{0308}")
1246 );
1247 assert_eq!(glyph_to_text("uniD800DC00"), None); }
1249
1250 #[test]
1251 fn glyph_text_rejects_unknowns() {
1252 assert_eq!(glyph_to_text(".notdef"), None);
1253 assert_eq!(glyph_to_text(""), None);
1254 assert_eq!(glyph_to_text("glorp"), None);
1255 assert_eq!(glyph_to_text("f_glorp"), None);
1257 assert_eq!(glyph_to_text("f__i"), None);
1258 }
1259
1260 #[test]
1265 fn glyph_names_cover_the_full_adobe_glyph_list() {
1266 assert_eq!(glyph_to_unicode("universal"), Some('\u{2200}'));
1267 assert_eq!(glyph_to_unicode("existential"), Some('\u{2203}'));
1268 assert_eq!(glyph_to_unicode("emptyset"), Some('\u{2205}'));
1269 assert_eq!(glyph_to_unicode("copyright"), Some('\u{A9}'));
1270 assert_eq!(glyph_to_unicode("ff"), Some('\u{FB00}'));
1271 assert_eq!(glyph_to_unicode("afii57414"), Some('\u{0626}'));
1272 assert_eq!(
1273 glyph_to_text("dalethatafpatah").as_deref(),
1274 Some("\u{05D3}\u{05B2}")
1275 );
1276 assert_eq!(glyph_to_unicode("dalethatafpatah"), None);
1277 }
1278
1279 #[test]
1282 fn tex_math_glyph_names_resolve() {
1283 assert_eq!(glyph_to_unicode("owner"), Some('\u{220B}'));
1284 assert_eq!(glyph_to_unicode("arrowbothv"), Some('\u{2195}'));
1285 assert_eq!(glyph_to_unicode("angbracketleft"), Some('\u{27E8}'));
1286 assert_eq!(glyph_to_unicode("lessmuch"), Some('\u{226A}'));
1287 assert_eq!(glyph_to_unicode("prime"), Some('\u{2032}'));
1288 assert_eq!(glyph_to_unicode("negationslash"), Some('\u{0338}'));
1289 assert_eq!(glyph_to_unicode("lessorsimilar"), Some('\u{2272}'));
1290 assert_eq!(glyph_to_unicode("notexistential"), Some('\u{2204}'));
1291 assert_eq!(glyph_to_unicode("vextendsingle"), Some('|'));
1292 assert_eq!(glyph_to_unicode("Circle"), Some('\u{25CB}'));
1293 }
1294
1295 #[test]
1297 fn type1_program_encoding_reads_dup_put_entries() {
1298 let program: &[u8] = b"%!PS-AdobeFont-1.0: CMSY10\n/FontName /CMSY10 def\n\
1299 /Encoding 256 array\n0 1 255 {1 index exch /.notdef put} for\n\
1300 dup 56 /universal put\ndup 169 /copyright put\nreadonly def\n\
1301 currentdict end\ncurrentfile eexec\n\x80\x01dup 57 /existential put";
1302 let table = type1_builtin_encoding(program).expect("an /Encoding is present");
1303 assert_eq!(table[56].as_deref(), Some("universal"));
1304 assert_eq!(table[169].as_deref(), Some("copyright"));
1305 assert_eq!(table[57], None, "nothing past eexec is read");
1306 assert_eq!(table[0], None);
1307 }
1308
1309 #[test]
1310 fn type1_program_encoding_expands_the_standard_token() {
1311 let program: &[u8] = b"/Encoding StandardEncoding def\ncurrentfile eexec\n";
1312 let table = type1_builtin_encoding(program).unwrap();
1313 assert_eq!(table[0x41].as_deref(), Some("A"));
1314 assert_eq!(table[0x27].as_deref(), Some("quoteright"));
1315 }
1316
1317 #[test]
1318 fn type1_program_encoding_reads_pfb_segments() {
1319 let clear: &[u8] =
1320 b"/Encoding 256 array\ndup 65 /alpha put\nreadonly def\ncurrentfile eexec\n";
1321 let mut program = vec![0x80, 0x01];
1322 program.extend_from_slice(&(clear.len() as u32).to_le_bytes());
1323 program.extend_from_slice(clear);
1324 program.extend_from_slice(&[0x80, 0x02, 4, 0, 0, 0, 0xDE, 0xAD, 0xBE, 0xEF, 0x80, 0x03]);
1325 let table = type1_builtin_encoding(&program).unwrap();
1326 assert_eq!(table[65].as_deref(), Some("alpha"));
1327 }
1328
1329 #[test]
1330 fn type1_program_without_an_encoding_yields_none() {
1331 let program: &[u8] = b"/FontName /X def\ncurrentfile eexec\n";
1332 assert!(type1_builtin_encoding(program).is_none());
1333 }
1334}