1mod afm;
7mod agl;
8pub use afm::{is_standard_14, standard_14_width};
9
10const WIN_ANSI_80_9F: [Option<char>; 32] = [
13 Some('\u{20AC}'),
14 None,
15 Some('\u{201A}'),
16 Some('\u{0192}'),
17 Some('\u{201E}'),
18 Some('\u{2026}'),
19 Some('\u{2020}'),
20 Some('\u{2021}'),
21 Some('\u{02C6}'),
22 Some('\u{2030}'),
23 Some('\u{0160}'),
24 Some('\u{2039}'),
25 Some('\u{0152}'),
26 None,
27 Some('\u{017D}'),
28 None,
29 None,
30 Some('\u{2018}'),
31 Some('\u{2019}'),
32 Some('\u{201C}'),
33 Some('\u{201D}'),
34 Some('\u{2022}'),
35 Some('\u{2013}'),
36 Some('\u{2014}'),
37 Some('\u{02DC}'),
38 Some('\u{2122}'),
39 Some('\u{0161}'),
40 Some('\u{203A}'),
41 Some('\u{0153}'),
42 None,
43 Some('\u{017E}'),
44 Some('\u{0178}'),
45];
46
47pub fn win_ansi(code: u8) -> Option<char> {
49 match code {
50 0x20..=0x7E => Some(code as char),
51 0x80..=0x9F => WIN_ANSI_80_9F[(code - 0x80) as usize],
52 0xA0..=0xFF => Some(code as char),
53 _ => None,
54 }
55}
56
57pub fn win_ansi_glyph_name(code: u8) -> Option<&'static str> {
67 match code {
68 0x27 => Some("quotesingle"),
69 0x60 => Some("grave"),
70 0x20..=0x7E => Some(STANDARD_ASCII_NAMES[(code - 0x20) as usize]),
71 0x80..=0x9F => WIN_ANSI_80_9F_NAMES[(code - 0x80) as usize],
72 0xA0..=0xFF => Some(WIN_ANSI_A0_FF_NAMES[(code - 0xA0) as usize]),
73 _ => None,
74 }
75}
76
77const WIN_ANSI_80_9F_NAMES: [Option<&str>; 32] = [
80 Some("Euro"),
81 None,
82 Some("quotesinglbase"),
83 Some("florin"),
84 Some("quotedblbase"),
85 Some("ellipsis"),
86 Some("dagger"),
87 Some("daggerdbl"),
88 Some("circumflex"),
89 Some("perthousand"),
90 Some("Scaron"),
91 Some("guilsinglleft"),
92 Some("OE"),
93 None,
94 Some("Zcaron"),
95 None,
96 None,
97 Some("quoteleft"),
98 Some("quoteright"),
99 Some("quotedblleft"),
100 Some("quotedblright"),
101 Some("bullet"),
102 Some("endash"),
103 Some("emdash"),
104 Some("tilde"),
105 Some("trademark"),
106 Some("scaron"),
107 Some("guilsinglright"),
108 Some("oe"),
109 None,
110 Some("zcaron"),
111 Some("Ydieresis"),
112];
113
114const WIN_ANSI_A0_FF_NAMES: [&str; 96] = [
118 "space",
119 "exclamdown",
120 "cent",
121 "sterling",
122 "currency",
123 "yen",
124 "brokenbar",
125 "section",
126 "dieresis",
127 "copyright",
128 "ordfeminine",
129 "guillemotleft",
130 "logicalnot",
131 "hyphen",
132 "registered",
133 "macron",
134 "degree",
135 "plusminus",
136 "twosuperior",
137 "threesuperior",
138 "acute",
139 "mu",
140 "paragraph",
141 "periodcentered",
142 "cedilla",
143 "onesuperior",
144 "ordmasculine",
145 "guillemotright",
146 "onequarter",
147 "onehalf",
148 "threequarters",
149 "questiondown",
150 "Agrave",
151 "Aacute",
152 "Acircumflex",
153 "Atilde",
154 "Adieresis",
155 "Aring",
156 "AE",
157 "Ccedilla",
158 "Egrave",
159 "Eacute",
160 "Ecircumflex",
161 "Edieresis",
162 "Igrave",
163 "Iacute",
164 "Icircumflex",
165 "Idieresis",
166 "Eth",
167 "Ntilde",
168 "Ograve",
169 "Oacute",
170 "Ocircumflex",
171 "Otilde",
172 "Odieresis",
173 "multiply",
174 "Oslash",
175 "Ugrave",
176 "Uacute",
177 "Ucircumflex",
178 "Udieresis",
179 "Yacute",
180 "Thorn",
181 "germandbls",
182 "agrave",
183 "aacute",
184 "acircumflex",
185 "atilde",
186 "adieresis",
187 "aring",
188 "ae",
189 "ccedilla",
190 "egrave",
191 "eacute",
192 "ecircumflex",
193 "edieresis",
194 "igrave",
195 "iacute",
196 "icircumflex",
197 "idieresis",
198 "eth",
199 "ntilde",
200 "ograve",
201 "oacute",
202 "ocircumflex",
203 "otilde",
204 "odieresis",
205 "divide",
206 "oslash",
207 "ugrave",
208 "uacute",
209 "ucircumflex",
210 "udieresis",
211 "yacute",
212 "thorn",
213 "ydieresis",
214];
215
216const MAC_ROMAN_HIGH: [char; 128] = [
218 '\u{C4}', '\u{C5}', '\u{C7}', '\u{C9}', '\u{D1}', '\u{D6}', '\u{DC}', '\u{E1}', '\u{E0}',
219 '\u{E2}', '\u{E4}', '\u{E3}', '\u{E5}', '\u{E7}', '\u{E9}', '\u{E8}', '\u{EA}', '\u{EB}',
220 '\u{ED}', '\u{EC}', '\u{EE}', '\u{EF}', '\u{F1}', '\u{F3}', '\u{F2}', '\u{F4}', '\u{F6}',
221 '\u{F5}', '\u{FA}', '\u{F9}', '\u{FB}', '\u{FC}', '\u{2020}', '\u{B0}', '\u{A2}', '\u{A3}',
222 '\u{A7}', '\u{2022}', '\u{B6}', '\u{DF}', '\u{AE}', '\u{A9}', '\u{2122}', '\u{B4}', '\u{A8}',
223 '\u{2260}', '\u{C6}', '\u{D8}', '\u{221E}', '\u{B1}', '\u{2264}', '\u{2265}', '\u{A5}',
224 '\u{B5}', '\u{2202}', '\u{2211}', '\u{220F}', '\u{3C0}', '\u{222B}', '\u{AA}', '\u{BA}',
225 '\u{3A9}', '\u{E6}', '\u{F8}', '\u{BF}', '\u{A1}', '\u{AC}', '\u{221A}', '\u{192}', '\u{2248}',
226 '\u{2206}', '\u{AB}', '\u{BB}', '\u{2026}', '\u{A0}', '\u{C0}', '\u{C3}', '\u{D5}', '\u{152}',
227 '\u{153}', '\u{2013}', '\u{2014}', '\u{201C}', '\u{201D}', '\u{2018}', '\u{2019}', '\u{F7}',
228 '\u{25CA}', '\u{FF}', '\u{178}', '\u{2044}', '\u{20AC}', '\u{2039}', '\u{203A}', '\u{FB01}',
229 '\u{FB02}', '\u{2021}', '\u{B7}', '\u{201A}', '\u{201E}', '\u{2030}', '\u{C2}', '\u{CA}',
230 '\u{C1}', '\u{CB}', '\u{C8}', '\u{CD}', '\u{CE}', '\u{CF}', '\u{CC}', '\u{D3}', '\u{D4}',
231 '\u{F8FF}', '\u{D2}', '\u{DA}', '\u{DB}', '\u{D9}', '\u{131}', '\u{2C6}', '\u{2DC}', '\u{AF}',
232 '\u{2D8}', '\u{2D9}', '\u{2DA}', '\u{B8}', '\u{2DD}', '\u{2DB}', '\u{2C7}',
233];
234
235pub fn mac_roman(code: u8) -> Option<char> {
237 match code {
238 0x20..=0x7E => Some(code as char),
239 0x80..=0xFF => Some(MAC_ROMAN_HIGH[(code - 0x80) as usize]),
240 _ => None,
241 }
242}
243
244const STANDARD_HIGH: &[(u8, char)] = &[
246 (0xA1, '\u{A1}'),
247 (0xA2, '\u{A2}'),
248 (0xA3, '\u{A3}'),
249 (0xA4, '\u{2044}'),
250 (0xA5, '\u{A5}'),
251 (0xA6, '\u{192}'),
252 (0xA7, '\u{A7}'),
253 (0xA8, '\u{A4}'),
254 (0xA9, '\u{27}'),
255 (0xAA, '\u{201C}'),
256 (0xAB, '\u{AB}'),
257 (0xAC, '\u{2039}'),
258 (0xAD, '\u{203A}'),
259 (0xAE, '\u{FB01}'),
260 (0xAF, '\u{FB02}'),
261 (0xB1, '\u{2013}'),
262 (0xB2, '\u{2020}'),
263 (0xB3, '\u{2021}'),
264 (0xB4, '\u{B7}'),
265 (0xB6, '\u{B6}'),
266 (0xB7, '\u{2022}'),
267 (0xB8, '\u{201A}'),
268 (0xB9, '\u{201E}'),
269 (0xBA, '\u{201D}'),
270 (0xBB, '\u{BB}'),
271 (0xBC, '\u{2026}'),
272 (0xBD, '\u{2030}'),
273 (0xBF, '\u{BF}'),
274 (0xC1, '\u{60}'),
275 (0xC2, '\u{B4}'),
276 (0xC3, '\u{2C6}'),
277 (0xC4, '\u{2DC}'),
278 (0xC5, '\u{AF}'),
279 (0xC6, '\u{2D8}'),
280 (0xC7, '\u{2D9}'),
281 (0xC8, '\u{A8}'),
282 (0xCA, '\u{2DA}'),
283 (0xCB, '\u{B8}'),
284 (0xCD, '\u{2DD}'),
285 (0xCE, '\u{2DB}'),
286 (0xCF, '\u{2C7}'),
287 (0xD0, '\u{2014}'),
288 (0xE1, '\u{C6}'),
289 (0xE3, '\u{AA}'),
290 (0xE8, '\u{141}'),
291 (0xE9, '\u{D8}'),
292 (0xEA, '\u{152}'),
293 (0xEB, '\u{BA}'),
294 (0xF1, '\u{E6}'),
295 (0xF5, '\u{131}'),
296 (0xF8, '\u{142}'),
297 (0xF9, '\u{F8}'),
298 (0xFA, '\u{153}'),
299 (0xFB, '\u{DF}'),
300];
301
302pub fn standard(code: u8) -> Option<char> {
304 match code {
305 0x27 => Some('\u{2019}'),
306 0x60 => Some('\u{2018}'),
307 0x20..=0x7E => Some(code as char),
308 0xA1..=0xFF => STANDARD_HIGH
309 .iter()
310 .find(|&&(c, _)| c == code)
311 .map(|&(_, u)| u),
312 _ => None,
313 }
314}
315
316const STANDARD_ASCII_NAMES: [&str; 95] = [
322 "space",
323 "exclam",
324 "quotedbl",
325 "numbersign",
326 "dollar",
327 "percent",
328 "ampersand",
329 "quoteright",
330 "parenleft",
331 "parenright",
332 "asterisk",
333 "plus",
334 "comma",
335 "hyphen",
336 "period",
337 "slash",
338 "zero",
339 "one",
340 "two",
341 "three",
342 "four",
343 "five",
344 "six",
345 "seven",
346 "eight",
347 "nine",
348 "colon",
349 "semicolon",
350 "less",
351 "equal",
352 "greater",
353 "question",
354 "at",
355 "A",
356 "B",
357 "C",
358 "D",
359 "E",
360 "F",
361 "G",
362 "H",
363 "I",
364 "J",
365 "K",
366 "L",
367 "M",
368 "N",
369 "O",
370 "P",
371 "Q",
372 "R",
373 "S",
374 "T",
375 "U",
376 "V",
377 "W",
378 "X",
379 "Y",
380 "Z",
381 "bracketleft",
382 "backslash",
383 "bracketright",
384 "asciicircum",
385 "underscore",
386 "quoteleft",
387 "a",
388 "b",
389 "c",
390 "d",
391 "e",
392 "f",
393 "g",
394 "h",
395 "i",
396 "j",
397 "k",
398 "l",
399 "m",
400 "n",
401 "o",
402 "p",
403 "q",
404 "r",
405 "s",
406 "t",
407 "u",
408 "v",
409 "w",
410 "x",
411 "y",
412 "z",
413 "braceleft",
414 "bar",
415 "braceright",
416 "asciitilde",
417];
418
419const STANDARD_HIGH_NAMES: &[(u8, &str)] = &[
423 (0xA1, "exclamdown"),
424 (0xA2, "cent"),
425 (0xA3, "sterling"),
426 (0xA4, "fraction"),
427 (0xA5, "yen"),
428 (0xA6, "florin"),
429 (0xA7, "section"),
430 (0xA8, "currency"),
431 (0xA9, "quotesingle"),
432 (0xAA, "quotedblleft"),
433 (0xAB, "guillemotleft"),
434 (0xAC, "guilsinglleft"),
435 (0xAD, "guilsinglright"),
436 (0xAE, "fi"),
437 (0xAF, "fl"),
438 (0xB1, "endash"),
439 (0xB2, "dagger"),
440 (0xB3, "daggerdbl"),
441 (0xB4, "periodcentered"),
442 (0xB6, "paragraph"),
443 (0xB7, "bullet"),
444 (0xB8, "quotesinglbase"),
445 (0xB9, "quotedblbase"),
446 (0xBA, "quotedblright"),
447 (0xBB, "guillemotright"),
448 (0xBC, "ellipsis"),
449 (0xBD, "perthousand"),
450 (0xBF, "questiondown"),
451 (0xC1, "grave"),
452 (0xC2, "acute"),
453 (0xC3, "circumflex"),
454 (0xC4, "tilde"),
455 (0xC5, "macron"),
456 (0xC6, "breve"),
457 (0xC7, "dotaccent"),
458 (0xC8, "dieresis"),
459 (0xCA, "ring"),
460 (0xCB, "cedilla"),
461 (0xCD, "hungarumlaut"),
462 (0xCE, "ogonek"),
463 (0xCF, "caron"),
464 (0xD0, "emdash"),
465 (0xE1, "AE"),
466 (0xE3, "ordfeminine"),
467 (0xE8, "Lslash"),
468 (0xE9, "Oslash"),
469 (0xEA, "OE"),
470 (0xEB, "ordmasculine"),
471 (0xF1, "ae"),
472 (0xF5, "dotlessi"),
473 (0xF8, "lslash"),
474 (0xF9, "oslash"),
475 (0xFA, "oe"),
476 (0xFB, "germandbls"),
477];
478
479pub fn standard_encoding_name(code: u8) -> Option<&'static str> {
486 match code {
487 0x20..=0x7E => Some(STANDARD_ASCII_NAMES[(code - 0x20) as usize]),
488 0xA1..=0xFF => STANDARD_HIGH_NAMES
489 .iter()
490 .find(|&&(c, _)| c == code)
491 .map(|&(_, n)| n),
492 _ => None,
493 }
494}
495
496pub fn glyph_to_unicode(name: &str) -> Option<char> {
502 if let Some(hex) = name.strip_prefix("uni") {
503 if hex.len() == 4 && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
504 return char::from_u32(u32::from_str_radix(hex, 16).ok()?);
505 }
506 }
507 if let Some(hex) = name.strip_prefix('u') {
508 if (4..=6).contains(&hex.len()) && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
509 return char::from_u32(u32::from_str_radix(hex, 16).ok()?);
510 }
511 }
512 let mut chars = name.chars();
513 if let (Some(c), None) = (chars.next(), chars.next()) {
514 if c.is_ascii_alphabetic() {
515 return Some(c);
516 }
517 }
518 if let Some(text) = agl_text(name) {
519 let mut chars = text.chars();
520 return match (chars.next(), chars.next()) {
521 (Some(c), None) => Some(c),
522 _ => None,
523 };
524 }
525 tex_glyph(name)
526}
527
528pub fn glyph_to_text(name: &str) -> Option<String> {
536 let base = name.split('.').next().unwrap_or_default();
537 if base.is_empty() {
538 return None;
539 }
540 let mut out = String::new();
541 for component in base.split('_') {
542 push_component(component, &mut out)?;
543 }
544 Some(out)
545}
546
547fn push_component(component: &str, out: &mut String) -> Option<()> {
550 let hex = component.strip_prefix("uni").unwrap_or_default();
551 if hex.len() >= 8 && hex.len().is_multiple_of(4) && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
552 for group in hex.as_bytes().chunks(4) {
555 let group = std::str::from_utf8(group).ok()?;
556 let scalar = u32::from_str_radix(group, 16).ok()?;
557 out.push(char::from_u32(scalar)?);
558 }
559 return Some(());
560 }
561 if let Some(text) = agl_text(component) {
562 out.push_str(text);
563 return Some(());
564 }
565 out.push(glyph_to_unicode(component)?);
566 Some(())
567}
568
569fn agl_text(name: &str) -> Option<&'static str> {
571 let index = agl::AGL.binary_search_by(|(n, _)| n.cmp(&name)).ok()?;
572 Some(agl::AGL[index].1)
573}
574
575fn tex_glyph(name: &str) -> Option<char> {
577 let index = GLYPHS_TEX.binary_search_by(|(n, _)| n.cmp(&name)).ok()?;
578 Some(GLYPHS_TEX[index].1)
579}
580
581const GLYPHS_TEX: &[(&str, char)] = &[
586 ("Circle", '\u{25CB}'),
587 ("Ifractur", '\u{2111}'),
588 ("Rfractur", '\u{211C}'),
589 ("angbracketleft", '\u{27E8}'),
590 ("angbracketleftBig", '\u{27E8}'),
591 ("angbracketleftBigg", '\u{27E8}'),
592 ("angbracketleftbig", '\u{27E8}'),
593 ("angbracketleftbigg", '\u{27E8}'),
594 ("angbracketright", '\u{27E9}'),
595 ("angbracketrightBig", '\u{27E9}'),
596 ("angbracketrightBigg", '\u{27E9}'),
597 ("angbracketrightbig", '\u{27E9}'),
598 ("angbracketrightbigg", '\u{27E9}'),
599 ("arrowbothv", '\u{2195}'),
600 ("arrowdblbothv", '\u{21D5}'),
601 ("arrowhookleft", '\u{21A9}'),
602 ("arrowhookright", '\u{21AA}'),
603 ("arrowleftbothalf", '\u{21BD}'),
604 ("arrowlefttophalf", '\u{21BC}'),
605 ("arrownortheast", '\u{2197}'),
606 ("arrownorthwest", '\u{2196}'),
607 ("arrowrightbothalf", '\u{21C1}'),
608 ("arrowrighttophalf", '\u{21C0}'),
609 ("arrowsoutheast", '\u{2198}'),
610 ("arrowsouthwest", '\u{2199}'),
611 ("asteriskmath", '\u{2217}'),
612 ("backslashBig", '\\'),
613 ("backslashBigg", '\\'),
614 ("backslashbig", '\\'),
615 ("backslashbigg", '\\'),
616 ("bardbl", '\u{2016}'),
617 ("braceleftBig", '{'),
618 ("braceleftBigg", '{'),
619 ("braceleftbig", '{'),
620 ("braceleftbigg", '{'),
621 ("bracerightBig", '}'),
622 ("bracerightBigg", '}'),
623 ("bracerightbig", '}'),
624 ("bracerightbigg", '}'),
625 ("bracketleftBig", '['),
626 ("bracketleftBigg", '['),
627 ("bracketleftbig", '['),
628 ("bracketleftbigg", '['),
629 ("bracketrightBig", ']'),
630 ("bracketrightBigg", ']'),
631 ("bracketrightbig", ']'),
632 ("bracketrightbigg", ']'),
633 ("ceilingleft", '\u{2308}'),
634 ("ceilingleftBig", '\u{2308}'),
635 ("ceilingleftBigg", '\u{2308}'),
636 ("ceilingleftbig", '\u{2308}'),
637 ("ceilingleftbigg", '\u{2308}'),
638 ("ceilingright", '\u{2309}'),
639 ("ceilingrightBig", '\u{2309}'),
640 ("ceilingrightBigg", '\u{2309}'),
641 ("ceilingrightbig", '\u{2309}'),
642 ("ceilingrightbigg", '\u{2309}'),
643 ("circlecopyrt", '\u{A9}'),
644 ("circledivide", '\u{2298}'),
645 ("circledot", '\u{2299}'),
646 ("circledotdisplay", '\u{2A00}'),
647 ("circledottext", '\u{2A00}'),
648 ("circleminus", '\u{2296}'),
649 ("circlemultiply", '\u{2297}'),
650 ("circlemultiplydisplay", '\u{2A02}'),
651 ("circlemultiplytext", '\u{2A02}'),
652 ("circleplus", '\u{2295}'),
653 ("circleplusdisplay", '\u{2A01}'),
654 ("circleplustext", '\u{2A01}'),
655 ("contintegraldisplay", '\u{222E}'),
656 ("contintegraltext", '\u{222E}'),
657 ("coproduct", '\u{2210}'),
658 ("coproductdisplay", '\u{2210}'),
659 ("coproducttext", '\u{2210}'),
660 ("diamondmath", '\u{22C4}'),
661 ("dotlessj", '\u{237}'),
662 ("epsilon1", '\u{3F5}'),
663 ("equivasymptotic", '\u{224D}'),
664 ("flat", '\u{266D}'),
665 ("floorleft", '\u{230A}'),
666 ("floorleftBig", '\u{230A}'),
667 ("floorleftBigg", '\u{230A}'),
668 ("floorleftbig", '\u{230A}'),
669 ("floorleftbigg", '\u{230A}'),
670 ("floorright", '\u{230B}'),
671 ("floorrightBig", '\u{230B}'),
672 ("floorrightBigg", '\u{230B}'),
673 ("floorrightbig", '\u{230B}'),
674 ("floorrightbigg", '\u{230B}'),
675 ("followsequal", '\u{227D}'),
676 ("greatermuch", '\u{226B}'),
677 ("greaterorsimilar", '\u{2273}'),
678 ("hatwide", '^'),
679 ("hatwider", '^'),
680 ("hatwidest", '^'),
681 ("integraldisplay", '\u{222B}'),
682 ("integraltext", '\u{222B}'),
683 ("intercal", '\u{22BA}'),
684 ("intersectiondisplay", '\u{22C2}'),
685 ("intersectionsq", '\u{2293}'),
686 ("intersectionsqdisplay", '\u{2A05}'),
687 ("intersectionsqtext", '\u{2A05}'),
688 ("intersectiontext", '\u{22C2}'),
689 ("latticetop", '\u{22A4}'),
690 ("lessmuch", '\u{226A}'),
691 ("lessorsimilar", '\u{2272}'),
692 ("logicalanddisplay", '\u{22C0}'),
693 ("logicalandtext", '\u{22C0}'),
694 ("logicalordisplay", '\u{22C1}'),
695 ("logicalortext", '\u{22C1}'),
696 ("lscript", '\u{2113}'),
697 ("mapsto", '\u{21A6}'),
698 ("measuredangle", '\u{2221}'),
699 ("minusplus", '\u{2213}'),
700 ("natural", '\u{266E}'),
701 ("negationslash", '\u{0338}'),
702 ("nequal", '\u{2260}'),
703 ("notexistential", '\u{2204}'),
704 ("openbullet", '\u{25E6}'),
705 ("owner", '\u{220B}'),
706 ("parenleftBig", '('),
707 ("parenleftBigg", '('),
708 ("parenleftbig", '('),
709 ("parenleftbigg", '('),
710 ("parenrightBig", ')'),
711 ("parenrightBigg", ')'),
712 ("parenrightbig", ')'),
713 ("parenrightbigg", ')'),
714 ("phi1", '\u{3D5}'),
715 ("pi1", '\u{3D6}'),
716 ("precedesequal", '\u{227C}'),
717 ("prime", '\u{2032}'),
718 ("productdisplay", '\u{220F}'),
719 ("producttext", '\u{220F}'),
720 ("radicalBig", '\u{221A}'),
721 ("radicalBigg", '\u{221A}'),
722 ("radicalbig", '\u{221A}'),
723 ("radicalbigg", '\u{221A}'),
724 ("rho1", '\u{3F1}'),
725 ("sharp", '\u{266F}'),
726 ("sigma1", '\u{3C2}'),
727 ("similarequal", '\u{2243}'),
728 ("slashBig", '/'),
729 ("slashBigg", '/'),
730 ("slashbig", '/'),
731 ("slashbigg", '/'),
732 ("slurabove", '\u{2322}'),
733 ("slurbelow", '\u{2323}'),
734 ("squaresolid", '\u{25A0}'),
735 ("star", '\u{22C6}'),
736 ("subsetnoteql", '\u{228A}'),
737 ("subsetsqequal", '\u{2291}'),
738 ("summationdisplay", '\u{2211}'),
739 ("summationtext", '\u{2211}'),
740 ("supersetsqequal", '\u{2292}'),
741 ("theta1", '\u{3D1}'),
742 ("tie", '\u{2040}'),
743 ("tildewide", '~'),
744 ("tildewider", '~'),
745 ("tildewidest", '~'),
746 ("triangle", '\u{25B3}'),
747 ("triangleinv", '\u{25BD}'),
748 ("triangleleft", '\u{25C1}'),
749 ("triangleright", '\u{25B7}'),
750 ("turnstileleft", '\u{22A2}'),
751 ("turnstileright", '\u{22A3}'),
752 ("uniondisplay", '\u{22C3}'),
753 ("unionmulti", '\u{228E}'),
754 ("unionmultidisplay", '\u{2A04}'),
755 ("unionmultitext", '\u{2A04}'),
756 ("unionsq", '\u{2294}'),
757 ("unionsqdisplay", '\u{2A06}'),
758 ("unionsqtext", '\u{2A06}'),
759 ("uniontext", '\u{22C3}'),
760 ("vector", '\u{20D7}'),
761 ("vextenddouble", '\u{2016}'),
762 ("vextendsingle", '|'),
763 ("weierstrass", '\u{2118}'),
764 ("wreathproduct", '\u{2240}'),
765];
766
767pub fn type1_builtin_encoding(program: &[u8]) -> Option<Box<[Option<String>; 256]>> {
774 let clear = type1_clear_text(program);
775 let mut tokens = PsTokens {
776 bytes: clear,
777 at: 0,
778 };
779 tokens.find(|t| *t == b"/Encoding")?;
780 let mut table: Box<[Option<String>; 256]> = Box::new(std::array::from_fn(|_| None));
781 let tokens: Vec<&[u8]> = tokens.collect();
782 if tokens.first() == Some(&b"StandardEncoding".as_slice()) {
783 for (code, slot) in table.iter_mut().enumerate() {
784 *slot = standard_encoding_name(code as u8).map(String::from);
785 }
786 }
787 for entry in tokens.windows(4) {
788 if entry[0] != b"dup" || entry[3] != b"put" {
789 continue;
790 }
791 let Some(code) = std::str::from_utf8(entry[1])
792 .ok()
793 .and_then(|t| t.parse::<usize>().ok())
794 else {
795 continue;
796 };
797 let Some(name) = entry[2]
798 .strip_prefix(b"/")
799 .and_then(|n| std::str::from_utf8(n).ok())
800 else {
801 continue;
802 };
803 if let Some(slot) = table.get_mut(code) {
804 *slot = Some(name.to_string());
805 }
806 }
807 Some(table)
808}
809
810fn type1_clear_text(program: &[u8]) -> &[u8] {
814 if program.first() == Some(&0x80) {
815 return pfb_clear_text(program);
816 }
817 const TOKEN: &[u8] = b"eexec";
818 let end = program
819 .windows(TOKEN.len())
820 .position(|w| w == TOKEN)
821 .unwrap_or(program.len());
822 &program[..end]
823}
824
825fn pfb_clear_text(program: &[u8]) -> &[u8] {
829 if program.get(1) != Some(&0x01) || program.len() < 6 {
830 return &[];
831 }
832 let len = u32::from_le_bytes([program[2], program[3], program[4], program[5]]) as usize;
833 let end = len.saturating_add(6).min(program.len());
834 &program[6..end]
835}
836
837struct PsTokens<'a> {
841 bytes: &'a [u8],
842 at: usize,
843}
844
845impl<'a> Iterator for PsTokens<'a> {
846 type Item = &'a [u8];
847
848 fn next(&mut self) -> Option<&'a [u8]> {
849 loop {
850 let &b = self.bytes.get(self.at)?;
851 if is_ps_whitespace(b) {
852 self.at += 1;
853 continue;
854 }
855 if b != b'%' {
856 break;
857 }
858 while self
859 .bytes
860 .get(self.at)
861 .is_some_and(|&b| b != b'\n' && b != b'\r')
862 {
863 self.at += 1;
864 }
865 }
866 let start = self.at;
867 if is_ps_delimiter(self.bytes[start]) {
868 self.at += 1;
869 return Some(&self.bytes[start..self.at]);
870 }
871 self.at += 1;
872 while self
873 .bytes
874 .get(self.at)
875 .is_some_and(|&b| !is_ps_whitespace(b) && !is_ps_delimiter(b) && b != b'/')
876 {
877 self.at += 1;
878 }
879 Some(&self.bytes[start..self.at])
880 }
881}
882
883fn is_ps_whitespace(b: u8) -> bool {
884 matches!(b, b' ' | b'\t' | b'\r' | b'\n' | b'\x0C' | b'\0')
885}
886
887fn is_ps_delimiter(b: u8) -> bool {
888 matches!(b, b'(' | b')' | b'<' | b'>' | b'[' | b']' | b'{' | b'}')
889}
890
891#[cfg(test)]
892mod tests {
893 use super::*;
894
895 #[test]
896 fn win_ansi_spot_checks() {
897 assert_eq!(win_ansi(b'A'), Some('A'));
898 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); }
904
905 #[test]
906 fn win_ansi_glyph_name_spot_checks() {
907 assert_eq!(win_ansi_glyph_name(0x41), Some("A"));
908 assert_eq!(win_ansi_glyph_name(0x20), Some("space"));
909 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"));
912 assert_eq!(win_ansi_glyph_name(0x93), Some("quotedblleft"));
913 assert_eq!(win_ansi_glyph_name(0xE9), Some("eacute"));
914 assert_eq!(win_ansi_glyph_name(0xFF), Some("ydieresis"));
915 assert_eq!(win_ansi_glyph_name(0x81), None); assert_eq!(win_ansi_glyph_name(0x0A), None); }
918
919 #[test]
927 fn win_ansi_glyph_name_matches_win_ansi_table() {
928 assert_eq!(win_ansi_glyph_name(0xA0), Some("space"));
929 assert_eq!(win_ansi_glyph_name(0xAD), Some("hyphen"));
930 for code in 0u16..=255 {
931 let code = code as u8;
932 assert_eq!(
933 win_ansi_glyph_name(code).is_some(),
934 win_ansi(code).is_some(),
935 "code {code:#04x}: win_ansi_glyph_name/win_ansi domain mismatch"
936 );
937 let Some(name) = win_ansi_glyph_name(code) else {
938 continue;
939 };
940 let expected = match code {
941 0xA0 => ' ',
942 0xAD => '-',
943 _ => win_ansi(code).unwrap(),
944 };
945 assert_eq!(
946 glyph_to_unicode(name),
947 Some(expected),
948 "code {code:#04x} name {name:?}: glyph_to_unicode disagrees with win_ansi"
949 );
950 }
951 }
952
953 #[test]
954 fn mac_roman_spot_checks() {
955 assert_eq!(mac_roman(b'A'), Some('A'));
956 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);
961 }
962
963 #[test]
964 fn standard_spot_checks() {
965 assert_eq!(standard(b'A'), Some('A'));
966 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);
971 assert_eq!(standard(0xA0), None); }
973
974 #[test]
975 fn glyph_names_hex_forms() {
976 assert_eq!(glyph_to_unicode("uni03B1"), Some('\u{3B1}'));
977 assert_eq!(glyph_to_unicode("uni20AC"), Some('\u{20AC}'));
978 assert_eq!(glyph_to_unicode("u1F600"), Some('\u{1F600}'));
979 assert_eq!(glyph_to_unicode("u00E9"), Some('\u{E9}'));
980 assert_eq!(glyph_to_unicode("uniD800"), None); assert_eq!(glyph_to_unicode("uniXYZW"), None);
982 }
983
984 #[test]
993 fn standard_encoding_name_matches_standard_table() {
994 for code in 0u16..=255 {
995 let code = code as u8;
996 assert_eq!(
997 standard_encoding_name(code).is_some(),
998 standard(code).is_some(),
999 "code {code:#04x}: standard_encoding_name/standard domain mismatch"
1000 );
1001 if let (Some(name), Some(expected)) = (standard_encoding_name(code), standard(code)) {
1002 if let Some(resolved) = glyph_to_unicode(name) {
1003 assert_eq!(
1004 resolved, expected,
1005 "code {code:#04x} name {name:?}: glyph_to_unicode disagrees with standard"
1006 );
1007 }
1008 }
1009 }
1010 }
1011
1012 #[test]
1013 fn standard_encoding_name_spot_checks() {
1014 assert_eq!(standard_encoding_name(b'A'), Some("A"));
1015 assert_eq!(standard_encoding_name(0x27), Some("quoteright"));
1016 assert_eq!(standard_encoding_name(0x60), Some("quoteleft"));
1017 assert_eq!(standard_encoding_name(0xA1), Some("exclamdown"));
1018 assert_eq!(standard_encoding_name(0xA4), Some("fraction"));
1019 assert_eq!(standard_encoding_name(0xA6), Some("florin"));
1020 assert_eq!(standard_encoding_name(0xC1), Some("grave"));
1021 assert_eq!(standard_encoding_name(0xC6), Some("breve"));
1022 assert_eq!(standard_encoding_name(0xE1), Some("AE"));
1023 assert_eq!(standard_encoding_name(0xF1), Some("ae"));
1024 assert_eq!(standard_encoding_name(0xFB), Some("germandbls"));
1025 assert_eq!(standard_encoding_name(0x7F), None);
1026 assert_eq!(standard_encoding_name(0xA0), None);
1027 }
1028
1029 #[test]
1030 fn glyph_names_letters_and_tables() {
1031 assert_eq!(glyph_to_unicode("A"), Some('A'));
1032 assert_eq!(glyph_to_unicode("z"), Some('z'));
1033 assert_eq!(glyph_to_unicode("alpha"), Some('\u{3B1}'));
1034 assert_eq!(glyph_to_unicode("eacute"), Some('\u{E9}'));
1035 assert_eq!(glyph_to_unicode("quotedblleft"), Some('\u{201C}'));
1036 assert_eq!(glyph_to_unicode("seven"), Some('7'));
1037 assert_eq!(glyph_to_unicode("union"), Some('\u{222A}'));
1038 assert_eq!(glyph_to_unicode("nosuchglyphname"), None);
1039 }
1040
1041 #[test]
1042 fn glyph_text_ligatures_and_variants() {
1043 assert_eq!(glyph_to_text("f_i").as_deref(), Some("fi"));
1044 assert_eq!(glyph_to_text("f_l").as_deref(), Some("fl"));
1045 assert_eq!(glyph_to_text("T_h").as_deref(), Some("Th"));
1046 assert_eq!(glyph_to_text("f_f_i").as_deref(), Some("ffi"));
1047 assert_eq!(glyph_to_text("eight.oldstyle").as_deref(), Some("8"));
1048 assert_eq!(glyph_to_text("x.sc").as_deref(), Some("x"));
1049 assert_eq!(glyph_to_text("C.a").as_deref(), Some("C"));
1050 assert_eq!(glyph_to_text("f_i.alt").as_deref(), Some("fi"));
1052 assert_eq!(glyph_to_text("uni00A0").as_deref(), Some("\u{A0}"));
1053 assert_eq!(glyph_to_text("eacute").as_deref(), Some("\u{E9}"));
1054 }
1055
1056 #[test]
1057 fn glyph_text_multi_group_uni() {
1058 assert_eq!(
1059 glyph_to_text("uni20AC0308").as_deref(),
1060 Some("\u{20AC}\u{0308}")
1061 );
1062 assert_eq!(glyph_to_text("uniD800DC00"), None); }
1064
1065 #[test]
1066 fn glyph_text_rejects_unknowns() {
1067 assert_eq!(glyph_to_text(".notdef"), None);
1068 assert_eq!(glyph_to_text(""), None);
1069 assert_eq!(glyph_to_text("glorp"), None);
1070 assert_eq!(glyph_to_text("f_glorp"), None);
1072 assert_eq!(glyph_to_text("f__i"), None);
1073 }
1074
1075 #[test]
1079 fn glyph_names_cover_the_full_adobe_glyph_list() {
1080 assert_eq!(glyph_to_unicode("universal"), Some('\u{2200}'));
1081 assert_eq!(glyph_to_unicode("existential"), Some('\u{2203}'));
1082 assert_eq!(glyph_to_unicode("emptyset"), Some('\u{2205}'));
1083 assert_eq!(glyph_to_unicode("copyright"), Some('\u{A9}'));
1084 assert_eq!(glyph_to_unicode("ff"), Some('\u{FB00}'));
1085 assert_eq!(glyph_to_unicode("afii57414"), Some('\u{0626}'));
1086 assert_eq!(
1087 glyph_to_text("dalethatafpatah").as_deref(),
1088 Some("\u{05D3}\u{05B2}")
1089 );
1090 assert_eq!(glyph_to_unicode("dalethatafpatah"), None);
1091 }
1092
1093 #[test]
1096 fn tex_math_glyph_names_resolve() {
1097 assert_eq!(glyph_to_unicode("owner"), Some('\u{220B}'));
1098 assert_eq!(glyph_to_unicode("arrowbothv"), Some('\u{2195}'));
1099 assert_eq!(glyph_to_unicode("angbracketleft"), Some('\u{27E8}'));
1100 assert_eq!(glyph_to_unicode("lessmuch"), Some('\u{226A}'));
1101 assert_eq!(glyph_to_unicode("prime"), Some('\u{2032}'));
1102 assert_eq!(glyph_to_unicode("negationslash"), Some('\u{0338}'));
1103 assert_eq!(glyph_to_unicode("lessorsimilar"), Some('\u{2272}'));
1104 assert_eq!(glyph_to_unicode("notexistential"), Some('\u{2204}'));
1105 assert_eq!(glyph_to_unicode("vextendsingle"), Some('|'));
1106 assert_eq!(glyph_to_unicode("Circle"), Some('\u{25CB}'));
1107 }
1108
1109 #[test]
1110 fn type1_program_encoding_reads_dup_put_entries() {
1111 let program: &[u8] = b"%!PS-AdobeFont-1.0: CMSY10\n/FontName /CMSY10 def\n\
1112 /Encoding 256 array\n0 1 255 {1 index exch /.notdef put} for\n\
1113 dup 56 /universal put\ndup 169 /copyright put\nreadonly def\n\
1114 currentdict end\ncurrentfile eexec\n\x80\x01dup 57 /existential put";
1115 let table = type1_builtin_encoding(program).expect("an /Encoding is present");
1116 assert_eq!(table[56].as_deref(), Some("universal"));
1117 assert_eq!(table[169].as_deref(), Some("copyright"));
1118 assert_eq!(table[57], None, "nothing past eexec is read");
1119 assert_eq!(table[0], None);
1120 }
1121
1122 #[test]
1123 fn type1_program_encoding_expands_the_standard_token() {
1124 let program: &[u8] = b"/Encoding StandardEncoding def\ncurrentfile eexec\n";
1125 let table = type1_builtin_encoding(program).unwrap();
1126 assert_eq!(table[0x41].as_deref(), Some("A"));
1127 assert_eq!(table[0x27].as_deref(), Some("quoteright"));
1128 }
1129
1130 #[test]
1131 fn type1_program_encoding_reads_pfb_segments() {
1132 let clear: &[u8] =
1133 b"/Encoding 256 array\ndup 65 /alpha put\nreadonly def\ncurrentfile eexec\n";
1134 let mut program = vec![0x80, 0x01];
1135 program.extend_from_slice(&(clear.len() as u32).to_le_bytes());
1136 program.extend_from_slice(clear);
1137 program.extend_from_slice(&[0x80, 0x02, 4, 0, 0, 0, 0xDE, 0xAD, 0xBE, 0xEF, 0x80, 0x03]);
1138 let table = type1_builtin_encoding(&program).unwrap();
1139 assert_eq!(table[65].as_deref(), Some("alpha"));
1140 }
1141
1142 #[test]
1143 fn type1_program_without_an_encoding_yields_none() {
1144 let program: &[u8] = b"/FontName /X def\ncurrentfile eexec\n";
1145 assert!(type1_builtin_encoding(program).is_none());
1146 }
1147}