1mod afm;
6pub use afm::{is_standard_14, standard_14_width};
7
8const WIN_ANSI_80_9F: [Option<char>; 32] = [
11 Some('\u{20AC}'),
12 None,
13 Some('\u{201A}'),
14 Some('\u{0192}'),
15 Some('\u{201E}'),
16 Some('\u{2026}'),
17 Some('\u{2020}'),
18 Some('\u{2021}'),
19 Some('\u{02C6}'),
20 Some('\u{2030}'),
21 Some('\u{0160}'),
22 Some('\u{2039}'),
23 Some('\u{0152}'),
24 None,
25 Some('\u{017D}'),
26 None,
27 None,
28 Some('\u{2018}'),
29 Some('\u{2019}'),
30 Some('\u{201C}'),
31 Some('\u{201D}'),
32 Some('\u{2022}'),
33 Some('\u{2013}'),
34 Some('\u{2014}'),
35 Some('\u{02DC}'),
36 Some('\u{2122}'),
37 Some('\u{0161}'),
38 Some('\u{203A}'),
39 Some('\u{0153}'),
40 None,
41 Some('\u{017E}'),
42 Some('\u{0178}'),
43];
44
45pub fn win_ansi(code: u8) -> Option<char> {
47 match code {
48 0x20..=0x7E => Some(code as char),
49 0x80..=0x9F => WIN_ANSI_80_9F[(code - 0x80) as usize],
50 0xA0..=0xFF => Some(code as char),
51 _ => None,
52 }
53}
54
55pub fn win_ansi_glyph_name(code: u8) -> Option<&'static str> {
65 match code {
66 0x27 => Some("quotesingle"),
67 0x60 => Some("grave"),
68 0x20..=0x7E => Some(STANDARD_ASCII_NAMES[(code - 0x20) as usize]),
69 0x80..=0x9F => WIN_ANSI_80_9F_NAMES[(code - 0x80) as usize],
70 0xA0..=0xFF => Some(WIN_ANSI_A0_FF_NAMES[(code - 0xA0) as usize]),
71 _ => None,
72 }
73}
74
75const WIN_ANSI_80_9F_NAMES: [Option<&str>; 32] = [
78 Some("Euro"),
79 None,
80 Some("quotesinglbase"),
81 Some("florin"),
82 Some("quotedblbase"),
83 Some("ellipsis"),
84 Some("dagger"),
85 Some("daggerdbl"),
86 Some("circumflex"),
87 Some("perthousand"),
88 Some("Scaron"),
89 Some("guilsinglleft"),
90 Some("OE"),
91 None,
92 Some("Zcaron"),
93 None,
94 None,
95 Some("quoteleft"),
96 Some("quoteright"),
97 Some("quotedblleft"),
98 Some("quotedblright"),
99 Some("bullet"),
100 Some("endash"),
101 Some("emdash"),
102 Some("tilde"),
103 Some("trademark"),
104 Some("scaron"),
105 Some("guilsinglright"),
106 Some("oe"),
107 None,
108 Some("zcaron"),
109 Some("Ydieresis"),
110];
111
112const WIN_ANSI_A0_FF_NAMES: [&str; 96] = [
116 "space",
117 "exclamdown",
118 "cent",
119 "sterling",
120 "currency",
121 "yen",
122 "brokenbar",
123 "section",
124 "dieresis",
125 "copyright",
126 "ordfeminine",
127 "guillemotleft",
128 "logicalnot",
129 "hyphen",
130 "registered",
131 "macron",
132 "degree",
133 "plusminus",
134 "twosuperior",
135 "threesuperior",
136 "acute",
137 "mu",
138 "paragraph",
139 "periodcentered",
140 "cedilla",
141 "onesuperior",
142 "ordmasculine",
143 "guillemotright",
144 "onequarter",
145 "onehalf",
146 "threequarters",
147 "questiondown",
148 "Agrave",
149 "Aacute",
150 "Acircumflex",
151 "Atilde",
152 "Adieresis",
153 "Aring",
154 "AE",
155 "Ccedilla",
156 "Egrave",
157 "Eacute",
158 "Ecircumflex",
159 "Edieresis",
160 "Igrave",
161 "Iacute",
162 "Icircumflex",
163 "Idieresis",
164 "Eth",
165 "Ntilde",
166 "Ograve",
167 "Oacute",
168 "Ocircumflex",
169 "Otilde",
170 "Odieresis",
171 "multiply",
172 "Oslash",
173 "Ugrave",
174 "Uacute",
175 "Ucircumflex",
176 "Udieresis",
177 "Yacute",
178 "Thorn",
179 "germandbls",
180 "agrave",
181 "aacute",
182 "acircumflex",
183 "atilde",
184 "adieresis",
185 "aring",
186 "ae",
187 "ccedilla",
188 "egrave",
189 "eacute",
190 "ecircumflex",
191 "edieresis",
192 "igrave",
193 "iacute",
194 "icircumflex",
195 "idieresis",
196 "eth",
197 "ntilde",
198 "ograve",
199 "oacute",
200 "ocircumflex",
201 "otilde",
202 "odieresis",
203 "divide",
204 "oslash",
205 "ugrave",
206 "uacute",
207 "ucircumflex",
208 "udieresis",
209 "yacute",
210 "thorn",
211 "ydieresis",
212];
213
214const MAC_ROMAN_HIGH: [char; 128] = [
216 '\u{C4}', '\u{C5}', '\u{C7}', '\u{C9}', '\u{D1}', '\u{D6}', '\u{DC}', '\u{E1}', '\u{E0}',
217 '\u{E2}', '\u{E4}', '\u{E3}', '\u{E5}', '\u{E7}', '\u{E9}', '\u{E8}', '\u{EA}', '\u{EB}',
218 '\u{ED}', '\u{EC}', '\u{EE}', '\u{EF}', '\u{F1}', '\u{F3}', '\u{F2}', '\u{F4}', '\u{F6}',
219 '\u{F5}', '\u{FA}', '\u{F9}', '\u{FB}', '\u{FC}', '\u{2020}', '\u{B0}', '\u{A2}', '\u{A3}',
220 '\u{A7}', '\u{2022}', '\u{B6}', '\u{DF}', '\u{AE}', '\u{A9}', '\u{2122}', '\u{B4}', '\u{A8}',
221 '\u{2260}', '\u{C6}', '\u{D8}', '\u{221E}', '\u{B1}', '\u{2264}', '\u{2265}', '\u{A5}',
222 '\u{B5}', '\u{2202}', '\u{2211}', '\u{220F}', '\u{3C0}', '\u{222B}', '\u{AA}', '\u{BA}',
223 '\u{3A9}', '\u{E6}', '\u{F8}', '\u{BF}', '\u{A1}', '\u{AC}', '\u{221A}', '\u{192}', '\u{2248}',
224 '\u{2206}', '\u{AB}', '\u{BB}', '\u{2026}', '\u{A0}', '\u{C0}', '\u{C3}', '\u{D5}', '\u{152}',
225 '\u{153}', '\u{2013}', '\u{2014}', '\u{201C}', '\u{201D}', '\u{2018}', '\u{2019}', '\u{F7}',
226 '\u{25CA}', '\u{FF}', '\u{178}', '\u{2044}', '\u{20AC}', '\u{2039}', '\u{203A}', '\u{FB01}',
227 '\u{FB02}', '\u{2021}', '\u{B7}', '\u{201A}', '\u{201E}', '\u{2030}', '\u{C2}', '\u{CA}',
228 '\u{C1}', '\u{CB}', '\u{C8}', '\u{CD}', '\u{CE}', '\u{CF}', '\u{CC}', '\u{D3}', '\u{D4}',
229 '\u{F8FF}', '\u{D2}', '\u{DA}', '\u{DB}', '\u{D9}', '\u{131}', '\u{2C6}', '\u{2DC}', '\u{AF}',
230 '\u{2D8}', '\u{2D9}', '\u{2DA}', '\u{B8}', '\u{2DD}', '\u{2DB}', '\u{2C7}',
231];
232
233pub fn mac_roman(code: u8) -> Option<char> {
235 match code {
236 0x20..=0x7E => Some(code as char),
237 0x80..=0xFF => Some(MAC_ROMAN_HIGH[(code - 0x80) as usize]),
238 _ => None,
239 }
240}
241
242const STANDARD_HIGH: &[(u8, char)] = &[
244 (0xA1, '\u{A1}'),
245 (0xA2, '\u{A2}'),
246 (0xA3, '\u{A3}'),
247 (0xA4, '\u{2044}'),
248 (0xA5, '\u{A5}'),
249 (0xA6, '\u{192}'),
250 (0xA7, '\u{A7}'),
251 (0xA8, '\u{A4}'),
252 (0xA9, '\u{27}'),
253 (0xAA, '\u{201C}'),
254 (0xAB, '\u{AB}'),
255 (0xAC, '\u{2039}'),
256 (0xAD, '\u{203A}'),
257 (0xAE, '\u{FB01}'),
258 (0xAF, '\u{FB02}'),
259 (0xB1, '\u{2013}'),
260 (0xB2, '\u{2020}'),
261 (0xB3, '\u{2021}'),
262 (0xB4, '\u{B7}'),
263 (0xB6, '\u{B6}'),
264 (0xB7, '\u{2022}'),
265 (0xB8, '\u{201A}'),
266 (0xB9, '\u{201E}'),
267 (0xBA, '\u{201D}'),
268 (0xBB, '\u{BB}'),
269 (0xBC, '\u{2026}'),
270 (0xBD, '\u{2030}'),
271 (0xBF, '\u{BF}'),
272 (0xC1, '\u{60}'),
273 (0xC2, '\u{B4}'),
274 (0xC3, '\u{2C6}'),
275 (0xC4, '\u{2DC}'),
276 (0xC5, '\u{AF}'),
277 (0xC6, '\u{2D8}'),
278 (0xC7, '\u{2D9}'),
279 (0xC8, '\u{A8}'),
280 (0xCA, '\u{2DA}'),
281 (0xCB, '\u{B8}'),
282 (0xCD, '\u{2DD}'),
283 (0xCE, '\u{2DB}'),
284 (0xCF, '\u{2C7}'),
285 (0xD0, '\u{2014}'),
286 (0xE1, '\u{C6}'),
287 (0xE3, '\u{AA}'),
288 (0xE8, '\u{141}'),
289 (0xE9, '\u{D8}'),
290 (0xEA, '\u{152}'),
291 (0xEB, '\u{BA}'),
292 (0xF1, '\u{E6}'),
293 (0xF5, '\u{131}'),
294 (0xF8, '\u{142}'),
295 (0xF9, '\u{F8}'),
296 (0xFA, '\u{153}'),
297 (0xFB, '\u{DF}'),
298];
299
300pub fn standard(code: u8) -> Option<char> {
302 match code {
303 0x27 => Some('\u{2019}'),
304 0x60 => Some('\u{2018}'),
305 0x20..=0x7E => Some(code as char),
306 0xA1..=0xFF => STANDARD_HIGH
307 .iter()
308 .find(|&&(c, _)| c == code)
309 .map(|&(_, u)| u),
310 _ => None,
311 }
312}
313
314const STANDARD_ASCII_NAMES: [&str; 95] = [
320 "space",
321 "exclam",
322 "quotedbl",
323 "numbersign",
324 "dollar",
325 "percent",
326 "ampersand",
327 "quoteright",
328 "parenleft",
329 "parenright",
330 "asterisk",
331 "plus",
332 "comma",
333 "hyphen",
334 "period",
335 "slash",
336 "zero",
337 "one",
338 "two",
339 "three",
340 "four",
341 "five",
342 "six",
343 "seven",
344 "eight",
345 "nine",
346 "colon",
347 "semicolon",
348 "less",
349 "equal",
350 "greater",
351 "question",
352 "at",
353 "A",
354 "B",
355 "C",
356 "D",
357 "E",
358 "F",
359 "G",
360 "H",
361 "I",
362 "J",
363 "K",
364 "L",
365 "M",
366 "N",
367 "O",
368 "P",
369 "Q",
370 "R",
371 "S",
372 "T",
373 "U",
374 "V",
375 "W",
376 "X",
377 "Y",
378 "Z",
379 "bracketleft",
380 "backslash",
381 "bracketright",
382 "asciicircum",
383 "underscore",
384 "quoteleft",
385 "a",
386 "b",
387 "c",
388 "d",
389 "e",
390 "f",
391 "g",
392 "h",
393 "i",
394 "j",
395 "k",
396 "l",
397 "m",
398 "n",
399 "o",
400 "p",
401 "q",
402 "r",
403 "s",
404 "t",
405 "u",
406 "v",
407 "w",
408 "x",
409 "y",
410 "z",
411 "braceleft",
412 "bar",
413 "braceright",
414 "asciitilde",
415];
416
417const STANDARD_HIGH_NAMES: &[(u8, &str)] = &[
421 (0xA1, "exclamdown"),
422 (0xA2, "cent"),
423 (0xA3, "sterling"),
424 (0xA4, "fraction"),
425 (0xA5, "yen"),
426 (0xA6, "florin"),
427 (0xA7, "section"),
428 (0xA8, "currency"),
429 (0xA9, "quotesingle"),
430 (0xAA, "quotedblleft"),
431 (0xAB, "guillemotleft"),
432 (0xAC, "guilsinglleft"),
433 (0xAD, "guilsinglright"),
434 (0xAE, "fi"),
435 (0xAF, "fl"),
436 (0xB1, "endash"),
437 (0xB2, "dagger"),
438 (0xB3, "daggerdbl"),
439 (0xB4, "periodcentered"),
440 (0xB6, "paragraph"),
441 (0xB7, "bullet"),
442 (0xB8, "quotesinglbase"),
443 (0xB9, "quotedblbase"),
444 (0xBA, "quotedblright"),
445 (0xBB, "guillemotright"),
446 (0xBC, "ellipsis"),
447 (0xBD, "perthousand"),
448 (0xBF, "questiondown"),
449 (0xC1, "grave"),
450 (0xC2, "acute"),
451 (0xC3, "circumflex"),
452 (0xC4, "tilde"),
453 (0xC5, "macron"),
454 (0xC6, "breve"),
455 (0xC7, "dotaccent"),
456 (0xC8, "dieresis"),
457 (0xCA, "ring"),
458 (0xCB, "cedilla"),
459 (0xCD, "hungarumlaut"),
460 (0xCE, "ogonek"),
461 (0xCF, "caron"),
462 (0xD0, "emdash"),
463 (0xE1, "AE"),
464 (0xE3, "ordfeminine"),
465 (0xE8, "Lslash"),
466 (0xE9, "Oslash"),
467 (0xEA, "OE"),
468 (0xEB, "ordmasculine"),
469 (0xF1, "ae"),
470 (0xF5, "dotlessi"),
471 (0xF8, "lslash"),
472 (0xF9, "oslash"),
473 (0xFA, "oe"),
474 (0xFB, "germandbls"),
475];
476
477pub fn standard_encoding_name(code: u8) -> Option<&'static str> {
484 match code {
485 0x20..=0x7E => Some(STANDARD_ASCII_NAMES[(code - 0x20) as usize]),
486 0xA1..=0xFF => STANDARD_HIGH_NAMES
487 .iter()
488 .find(|&&(c, _)| c == code)
489 .map(|&(_, n)| n),
490 _ => None,
491 }
492}
493
494pub fn glyph_to_unicode(name: &str) -> Option<char> {
498 if let Some(hex) = name.strip_prefix("uni") {
499 if hex.len() == 4 && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
500 return char::from_u32(u32::from_str_radix(hex, 16).ok()?);
501 }
502 }
503 if let Some(hex) = name.strip_prefix('u') {
504 if (4..=6).contains(&hex.len()) && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
505 return char::from_u32(u32::from_str_radix(hex, 16).ok()?);
506 }
507 }
508 let mut chars = name.chars();
509 if let (Some(c), None) = (chars.next(), chars.next()) {
510 if c.is_ascii_alphabetic() {
511 return Some(c);
512 }
513 }
514 GLYPH_TABLES
515 .iter()
516 .flat_map(|t| t.iter())
517 .find(|&&(n, _)| n == name)
518 .map(|&(_, u)| u)
519}
520
521pub fn glyph_to_text(name: &str) -> Option<String> {
529 let base = name.split('.').next().unwrap_or_default();
530 if base.is_empty() {
531 return None;
532 }
533 let mut out = String::new();
534 for component in base.split('_') {
535 push_component(component, &mut out)?;
536 }
537 Some(out)
538}
539
540fn push_component(component: &str, out: &mut String) -> Option<()> {
543 let hex = component.strip_prefix("uni").unwrap_or_default();
544 if hex.len() >= 8 && hex.len().is_multiple_of(4) && hex.bytes().all(|b| b.is_ascii_hexdigit()) {
545 for group in hex.as_bytes().chunks(4) {
548 let group = std::str::from_utf8(group).ok()?;
549 let scalar = u32::from_str_radix(group, 16).ok()?;
550 out.push(char::from_u32(scalar)?);
551 }
552 return Some(());
553 }
554 out.push(glyph_to_unicode(component)?);
555 Some(())
556}
557
558const GLYPH_TABLES: [&[(&str, char)]; 5] = [
560 GLYPHS_ASCII,
561 GLYPHS_LATIN1,
562 GLYPHS_PUNCT,
563 GLYPHS_GREEK,
564 GLYPHS_MISC,
565];
566
567const GLYPHS_ASCII: &[(&str, char)] = &[
569 ("space", ' '),
570 ("exclam", '!'),
571 ("quotedbl", '"'),
572 ("numbersign", '#'),
573 ("dollar", '$'),
574 ("percent", '%'),
575 ("ampersand", '&'),
576 ("quotesingle", '\''),
577 ("parenleft", '('),
578 ("parenright", ')'),
579 ("asterisk", '*'),
580 ("plus", '+'),
581 ("comma", ','),
582 ("hyphen", '-'),
583 ("period", '.'),
584 ("slash", '/'),
585 ("zero", '0'),
586 ("one", '1'),
587 ("two", '2'),
588 ("three", '3'),
589 ("four", '4'),
590 ("five", '5'),
591 ("six", '6'),
592 ("seven", '7'),
593 ("eight", '8'),
594 ("nine", '9'),
595 ("colon", ':'),
596 ("semicolon", ';'),
597 ("less", '<'),
598 ("equal", '='),
599 ("greater", '>'),
600 ("question", '?'),
601 ("at", '@'),
602 ("bracketleft", '['),
603 ("backslash", '\\'),
604 ("bracketright", ']'),
605 ("asciicircum", '^'),
606 ("underscore", '_'),
607 ("grave", '`'),
608 ("braceleft", '{'),
609 ("bar", '|'),
610 ("braceright", '}'),
611 ("asciitilde", '~'),
612];
613
614const GLYPHS_LATIN1: &[(&str, char)] = &[
616 ("exclamdown", '\u{A1}'),
617 ("cent", '\u{A2}'),
618 ("sterling", '\u{A3}'),
619 ("currency", '\u{A4}'),
620 ("yen", '\u{A5}'),
621 ("brokenbar", '\u{A6}'),
622 ("section", '\u{A7}'),
623 ("dieresis", '\u{A8}'),
624 ("copyright", '\u{A9}'),
625 ("ordfeminine", '\u{AA}'),
626 ("guillemotleft", '\u{AB}'),
627 ("logicalnot", '\u{AC}'),
628 ("registered", '\u{AE}'),
629 ("macron", '\u{AF}'),
630 ("degree", '\u{B0}'),
631 ("plusminus", '\u{B1}'),
632 ("twosuperior", '\u{B2}'),
633 ("threesuperior", '\u{B3}'),
634 ("acute", '\u{B4}'),
635 ("mu", '\u{B5}'),
636 ("paragraph", '\u{B6}'),
637 ("periodcentered", '\u{B7}'),
638 ("cedilla", '\u{B8}'),
639 ("onesuperior", '\u{B9}'),
640 ("ordmasculine", '\u{BA}'),
641 ("guillemotright", '\u{BB}'),
642 ("onequarter", '\u{BC}'),
643 ("onehalf", '\u{BD}'),
644 ("threequarters", '\u{BE}'),
645 ("questiondown", '\u{BF}'),
646 ("Agrave", '\u{C0}'),
647 ("Aacute", '\u{C1}'),
648 ("Acircumflex", '\u{C2}'),
649 ("Atilde", '\u{C3}'),
650 ("Adieresis", '\u{C4}'),
651 ("Aring", '\u{C5}'),
652 ("AE", '\u{C6}'),
653 ("Ccedilla", '\u{C7}'),
654 ("Egrave", '\u{C8}'),
655 ("Eacute", '\u{C9}'),
656 ("Ecircumflex", '\u{CA}'),
657 ("Edieresis", '\u{CB}'),
658 ("Igrave", '\u{CC}'),
659 ("Iacute", '\u{CD}'),
660 ("Icircumflex", '\u{CE}'),
661 ("Idieresis", '\u{CF}'),
662 ("Eth", '\u{D0}'),
663 ("Ntilde", '\u{D1}'),
664 ("Ograve", '\u{D2}'),
665 ("Oacute", '\u{D3}'),
666 ("Ocircumflex", '\u{D4}'),
667 ("Otilde", '\u{D5}'),
668 ("Odieresis", '\u{D6}'),
669 ("multiply", '\u{D7}'),
670 ("Oslash", '\u{D8}'),
671 ("Ugrave", '\u{D9}'),
672 ("Uacute", '\u{DA}'),
673 ("Ucircumflex", '\u{DB}'),
674 ("Udieresis", '\u{DC}'),
675 ("Yacute", '\u{DD}'),
676 ("Thorn", '\u{DE}'),
677 ("germandbls", '\u{DF}'),
678 ("agrave", '\u{E0}'),
679 ("aacute", '\u{E1}'),
680 ("acircumflex", '\u{E2}'),
681 ("atilde", '\u{E3}'),
682 ("adieresis", '\u{E4}'),
683 ("aring", '\u{E5}'),
684 ("ae", '\u{E6}'),
685 ("ccedilla", '\u{E7}'),
686 ("egrave", '\u{E8}'),
687 ("eacute", '\u{E9}'),
688 ("ecircumflex", '\u{EA}'),
689 ("edieresis", '\u{EB}'),
690 ("igrave", '\u{EC}'),
691 ("iacute", '\u{ED}'),
692 ("icircumflex", '\u{EE}'),
693 ("idieresis", '\u{EF}'),
694 ("eth", '\u{F0}'),
695 ("ntilde", '\u{F1}'),
696 ("ograve", '\u{F2}'),
697 ("oacute", '\u{F3}'),
698 ("ocircumflex", '\u{F4}'),
699 ("otilde", '\u{F5}'),
700 ("odieresis", '\u{F6}'),
701 ("divide", '\u{F7}'),
702 ("oslash", '\u{F8}'),
703 ("ugrave", '\u{F9}'),
704 ("uacute", '\u{FA}'),
705 ("ucircumflex", '\u{FB}'),
706 ("udieresis", '\u{FC}'),
707 ("yacute", '\u{FD}'),
708 ("thorn", '\u{FE}'),
709 ("ydieresis", '\u{FF}'),
710];
711
712const GLYPHS_PUNCT: &[(&str, char)] = &[
714 ("quoteleft", '\u{2018}'),
715 ("quoteright", '\u{2019}'),
716 ("quotesinglbase", '\u{201A}'),
717 ("quotedblleft", '\u{201C}'),
718 ("quotedblright", '\u{201D}'),
719 ("quotedblbase", '\u{201E}'),
720 ("endash", '\u{2013}'),
721 ("emdash", '\u{2014}'),
722 ("bullet", '\u{2022}'),
723 ("ellipsis", '\u{2026}'),
724 ("dagger", '\u{2020}'),
725 ("daggerdbl", '\u{2021}'),
726 ("perthousand", '\u{2030}'),
727 ("guilsinglleft", '\u{2039}'),
728 ("guilsinglright", '\u{203A}'),
729 ("fraction", '\u{2044}'),
730 ("minus", '\u{2212}'),
731 ("florin", '\u{192}'),
732 ("Euro", '\u{20AC}'),
733 ("trademark", '\u{2122}'),
734 ("fi", '\u{FB01}'),
735 ("fl", '\u{FB02}'),
736 ("ff", '\u{FB00}'),
737 ("ffi", '\u{FB03}'),
738 ("ffl", '\u{FB04}'),
739 ("circumflex", '\u{2C6}'),
740 ("caron", '\u{2C7}'),
741 ("breve", '\u{2D8}'),
742 ("dotaccent", '\u{2D9}'),
743 ("ring", '\u{2DA}'),
744 ("ogonek", '\u{2DB}'),
745 ("tilde", '\u{2DC}'),
746 ("hungarumlaut", '\u{2DD}'),
747 ("OE", '\u{152}'),
748 ("oe", '\u{153}'),
749 ("Scaron", '\u{160}'),
750 ("scaron", '\u{161}'),
751 ("Zcaron", '\u{17D}'),
752 ("zcaron", '\u{17E}'),
753 ("Ydieresis", '\u{178}'),
754 ("Lslash", '\u{141}'),
755 ("lslash", '\u{142}'),
756 ("dotlessi", '\u{131}'),
757 ("nbspace", '\u{A0}'),
758 ("sfthyphen", '\u{AD}'),
759];
760
761const GLYPHS_GREEK: &[(&str, char)] = &[
764 ("Alpha", '\u{391}'),
765 ("Beta", '\u{392}'),
766 ("Gamma", '\u{393}'),
767 ("Delta", '\u{2206}'),
768 ("Epsilon", '\u{395}'),
769 ("Zeta", '\u{396}'),
770 ("Eta", '\u{397}'),
771 ("Theta", '\u{398}'),
772 ("Iota", '\u{399}'),
773 ("Kappa", '\u{39A}'),
774 ("Lambda", '\u{39B}'),
775 ("Mu", '\u{39C}'),
776 ("Nu", '\u{39D}'),
777 ("Xi", '\u{39E}'),
778 ("Omicron", '\u{39F}'),
779 ("Pi", '\u{3A0}'),
780 ("Rho", '\u{3A1}'),
781 ("Sigma", '\u{3A3}'),
782 ("Tau", '\u{3A4}'),
783 ("Upsilon", '\u{3A5}'),
784 ("Phi", '\u{3A6}'),
785 ("Chi", '\u{3A7}'),
786 ("Psi", '\u{3A8}'),
787 ("Omega", '\u{2126}'),
788 ("alpha", '\u{3B1}'),
789 ("beta", '\u{3B2}'),
790 ("gamma", '\u{3B3}'),
791 ("delta", '\u{3B4}'),
792 ("epsilon", '\u{3B5}'),
793 ("zeta", '\u{3B6}'),
794 ("eta", '\u{3B7}'),
795 ("theta", '\u{3B8}'),
796 ("iota", '\u{3B9}'),
797 ("kappa", '\u{3BA}'),
798 ("lambda", '\u{3BB}'),
799 ("nu", '\u{3BD}'),
800 ("xi", '\u{3BE}'),
801 ("omicron", '\u{3BF}'),
802 ("pi", '\u{3C0}'),
803 ("rho", '\u{3C1}'),
804 ("sigma", '\u{3C3}'),
805 ("sigma1", '\u{3C2}'),
806 ("tau", '\u{3C4}'),
807 ("upsilon", '\u{3C5}'),
808 ("phi", '\u{3C6}'),
809 ("chi", '\u{3C7}'),
810 ("psi", '\u{3C8}'),
811 ("omega", '\u{3C9}'),
812];
813
814const GLYPHS_MISC: &[(&str, char)] = &[
816 ("infinity", '\u{221E}'),
817 ("notequal", '\u{2260}'),
818 ("lessequal", '\u{2264}'),
819 ("greaterequal", '\u{2265}'),
820 ("partialdiff", '\u{2202}'),
821 ("summation", '\u{2211}'),
822 ("product", '\u{220F}'),
823 ("integral", '\u{222B}'),
824 ("radical", '\u{221A}'),
825 ("approxequal", '\u{2248}'),
826 ("equivalence", '\u{2261}'),
827 ("element", '\u{2208}'),
828 ("intersection", '\u{2229}'),
829 ("union", '\u{222A}'),
830 ("arrowleft", '\u{2190}'),
831 ("arrowup", '\u{2191}'),
832 ("arrowright", '\u{2192}'),
833 ("arrowdown", '\u{2193}'),
834 ("arrowboth", '\u{2194}'),
835 ("lozenge", '\u{25CA}'),
836 ("apple", '\u{F8FF}'),
837];
838
839#[cfg(test)]
840mod tests {
841 use super::*;
842
843 #[test]
844 fn win_ansi_spot_checks() {
845 assert_eq!(win_ansi(b'A'), Some('A'));
846 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); }
852
853 #[test]
854 fn win_ansi_glyph_name_spot_checks() {
855 assert_eq!(win_ansi_glyph_name(0x41), Some("A"));
856 assert_eq!(win_ansi_glyph_name(0x20), Some("space"));
857 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"));
860 assert_eq!(win_ansi_glyph_name(0x93), Some("quotedblleft"));
861 assert_eq!(win_ansi_glyph_name(0xE9), Some("eacute"));
862 assert_eq!(win_ansi_glyph_name(0xFF), Some("ydieresis"));
863 assert_eq!(win_ansi_glyph_name(0x81), None); assert_eq!(win_ansi_glyph_name(0x0A), None); }
866
867 #[test]
875 fn win_ansi_glyph_name_matches_win_ansi_table() {
876 assert_eq!(win_ansi_glyph_name(0xA0), Some("space"));
877 assert_eq!(win_ansi_glyph_name(0xAD), Some("hyphen"));
878 for code in 0u16..=255 {
879 let code = code as u8;
880 assert_eq!(
881 win_ansi_glyph_name(code).is_some(),
882 win_ansi(code).is_some(),
883 "code {code:#04x}: win_ansi_glyph_name/win_ansi domain mismatch"
884 );
885 let Some(name) = win_ansi_glyph_name(code) else {
886 continue;
887 };
888 let expected = match code {
889 0xA0 => ' ',
890 0xAD => '-',
891 _ => win_ansi(code).unwrap(),
892 };
893 assert_eq!(
894 glyph_to_unicode(name),
895 Some(expected),
896 "code {code:#04x} name {name:?}: glyph_to_unicode disagrees with win_ansi"
897 );
898 }
899 }
900
901 #[test]
902 fn mac_roman_spot_checks() {
903 assert_eq!(mac_roman(b'A'), Some('A'));
904 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);
909 }
910
911 #[test]
912 fn standard_spot_checks() {
913 assert_eq!(standard(b'A'), Some('A'));
914 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);
919 assert_eq!(standard(0xA0), None); }
921
922 #[test]
923 fn glyph_names_hex_forms() {
924 assert_eq!(glyph_to_unicode("uni03B1"), Some('\u{3B1}'));
925 assert_eq!(glyph_to_unicode("uni20AC"), Some('\u{20AC}'));
926 assert_eq!(glyph_to_unicode("u1F600"), Some('\u{1F600}'));
927 assert_eq!(glyph_to_unicode("u00E9"), Some('\u{E9}'));
928 assert_eq!(glyph_to_unicode("uniD800"), None); assert_eq!(glyph_to_unicode("uniXYZW"), None);
930 }
931
932 #[test]
941 fn standard_encoding_name_matches_standard_table() {
942 for code in 0u16..=255 {
943 let code = code as u8;
944 assert_eq!(
945 standard_encoding_name(code).is_some(),
946 standard(code).is_some(),
947 "code {code:#04x}: standard_encoding_name/standard domain mismatch"
948 );
949 if let (Some(name), Some(expected)) = (standard_encoding_name(code), standard(code)) {
950 if let Some(resolved) = glyph_to_unicode(name) {
951 assert_eq!(
952 resolved, expected,
953 "code {code:#04x} name {name:?}: glyph_to_unicode disagrees with standard"
954 );
955 }
956 }
957 }
958 }
959
960 #[test]
961 fn standard_encoding_name_spot_checks() {
962 assert_eq!(standard_encoding_name(b'A'), Some("A"));
963 assert_eq!(standard_encoding_name(0x27), Some("quoteright"));
964 assert_eq!(standard_encoding_name(0x60), Some("quoteleft"));
965 assert_eq!(standard_encoding_name(0xA1), Some("exclamdown"));
966 assert_eq!(standard_encoding_name(0xA4), Some("fraction"));
967 assert_eq!(standard_encoding_name(0xA6), Some("florin"));
968 assert_eq!(standard_encoding_name(0xC1), Some("grave"));
969 assert_eq!(standard_encoding_name(0xC6), Some("breve"));
970 assert_eq!(standard_encoding_name(0xE1), Some("AE"));
971 assert_eq!(standard_encoding_name(0xF1), Some("ae"));
972 assert_eq!(standard_encoding_name(0xFB), Some("germandbls"));
973 assert_eq!(standard_encoding_name(0x7F), None);
974 assert_eq!(standard_encoding_name(0xA0), None);
975 }
976
977 #[test]
978 fn glyph_names_letters_and_tables() {
979 assert_eq!(glyph_to_unicode("A"), Some('A'));
980 assert_eq!(glyph_to_unicode("z"), Some('z'));
981 assert_eq!(glyph_to_unicode("alpha"), Some('\u{3B1}'));
982 assert_eq!(glyph_to_unicode("eacute"), Some('\u{E9}'));
983 assert_eq!(glyph_to_unicode("quotedblleft"), Some('\u{201C}'));
984 assert_eq!(glyph_to_unicode("seven"), Some('7'));
985 assert_eq!(glyph_to_unicode("union"), Some('\u{222A}'));
986 assert_eq!(glyph_to_unicode("nosuchglyphname"), None);
987 }
988
989 #[test]
990 fn glyph_text_ligatures_and_variants() {
991 assert_eq!(glyph_to_text("f_i").as_deref(), Some("fi"));
992 assert_eq!(glyph_to_text("f_l").as_deref(), Some("fl"));
993 assert_eq!(glyph_to_text("T_h").as_deref(), Some("Th"));
994 assert_eq!(glyph_to_text("f_f_i").as_deref(), Some("ffi"));
995 assert_eq!(glyph_to_text("eight.oldstyle").as_deref(), Some("8"));
996 assert_eq!(glyph_to_text("x.sc").as_deref(), Some("x"));
997 assert_eq!(glyph_to_text("C.a").as_deref(), Some("C"));
998 assert_eq!(glyph_to_text("f_i.alt").as_deref(), Some("fi"));
1000 assert_eq!(glyph_to_text("uni00A0").as_deref(), Some("\u{A0}"));
1001 assert_eq!(glyph_to_text("eacute").as_deref(), Some("\u{E9}"));
1002 }
1003
1004 #[test]
1005 fn glyph_text_multi_group_uni() {
1006 assert_eq!(
1007 glyph_to_text("uni20AC0308").as_deref(),
1008 Some("\u{20AC}\u{0308}")
1009 );
1010 assert_eq!(glyph_to_text("uniD800DC00"), None); }
1012
1013 #[test]
1014 fn glyph_text_rejects_unknowns() {
1015 assert_eq!(glyph_to_text(".notdef"), None);
1016 assert_eq!(glyph_to_text(""), None);
1017 assert_eq!(glyph_to_text("glorp"), None);
1018 assert_eq!(glyph_to_text("f_glorp"), None);
1020 assert_eq!(glyph_to_text("f__i"), None);
1021 }
1022}