1use crate::parser::{read_i16, read_i32, read_u16, read_u32, read_u8};
54use crate::Error;
55
56pub const POST_TABLE_TAG: [u8; 4] = *b"post";
58
59#[doc(hidden)]
62pub const POST_HEADER_LEN: usize = 32;
63
64pub const POST_VERSION_10: u32 = 0x0001_0000;
67
68pub const POST_VERSION_20: u32 = 0x0002_0000;
71
72pub const POST_VERSION_25: u32 = 0x0002_5000;
75
76pub const POST_VERSION_30: u32 = 0x0003_0000;
79
80pub const STANDARD_MAC_GLYPH_COUNT: u16 = 258;
85
86pub const RECOMMENDED_GLYPH_NAME_MAX_LEN: usize = 63;
95
96pub static STANDARD_MAC_GLYPH_NAMES: [&str; STANDARD_MAC_GLYPH_COUNT as usize] = [
108 ".notdef",
109 ".null",
110 "nonmarkingreturn",
111 "space",
112 "exclam",
113 "quotedbl",
114 "numbersign",
115 "dollar",
116 "percent",
117 "ampersand",
118 "quotesingle",
119 "parenleft",
120 "parenright",
121 "asterisk",
122 "plus",
123 "comma",
124 "hyphen",
125 "period",
126 "slash",
127 "zero",
128 "one",
129 "two",
130 "three",
131 "four",
132 "five",
133 "six",
134 "seven",
135 "eight",
136 "nine",
137 "colon",
138 "semicolon",
139 "less",
140 "equal",
141 "greater",
142 "question",
143 "at",
144 "A",
145 "B",
146 "C",
147 "D",
148 "E",
149 "F",
150 "G",
151 "H",
152 "I",
153 "J",
154 "K",
155 "L",
156 "M",
157 "N",
158 "O",
159 "P",
160 "Q",
161 "R",
162 "S",
163 "T",
164 "U",
165 "V",
166 "W",
167 "X",
168 "Y",
169 "Z",
170 "bracketleft",
171 "backslash",
172 "bracketright",
173 "asciicircum",
174 "underscore",
175 "grave",
176 "a",
177 "b",
178 "c",
179 "d",
180 "e",
181 "f",
182 "g",
183 "h",
184 "i",
185 "j",
186 "k",
187 "l",
188 "m",
189 "n",
190 "o",
191 "p",
192 "q",
193 "r",
194 "s",
195 "t",
196 "u",
197 "v",
198 "w",
199 "x",
200 "y",
201 "z",
202 "braceleft",
203 "bar",
204 "braceright",
205 "asciitilde",
206 "Adieresis",
207 "Aring",
208 "Ccedilla",
209 "Eacute",
210 "Ntilde",
211 "Odieresis",
212 "Udieresis",
213 "aacute",
214 "agrave",
215 "acircumflex",
216 "adieresis",
217 "atilde",
218 "aring",
219 "ccedilla",
220 "eacute",
221 "egrave",
222 "ecircumflex",
223 "edieresis",
224 "iacute",
225 "igrave",
226 "icircumflex",
227 "idieresis",
228 "ntilde",
229 "oacute",
230 "ograve",
231 "ocircumflex",
232 "odieresis",
233 "otilde",
234 "uacute",
235 "ugrave",
236 "ucircumflex",
237 "udieresis",
238 "dagger",
239 "degree",
240 "cent",
241 "sterling",
242 "section",
243 "bullet",
244 "paragraph",
245 "germandbls",
246 "registered",
247 "copyright",
248 "trademark",
249 "acute",
250 "dieresis",
251 "notequal",
252 "AE",
253 "Oslash",
254 "infinity",
255 "plusminus",
256 "lessequal",
257 "greaterequal",
258 "yen",
259 "mu",
260 "partialdiff",
261 "summation",
262 "product",
263 "pi",
264 "integral",
265 "ordfeminine",
266 "ordmasculine",
267 "Omega",
268 "ae",
269 "oslash",
270 "questiondown",
271 "exclamdown",
272 "logicalnot",
273 "radical",
274 "florin",
275 "approxequal",
276 "Delta",
277 "guillemotleft",
278 "guillemotright",
279 "ellipsis",
280 "nonbreakingspace",
281 "Agrave",
282 "Atilde",
283 "Otilde",
284 "OE",
285 "oe",
286 "endash",
287 "emdash",
288 "quotedblleft",
289 "quotedblright",
290 "quoteleft",
291 "quoteright",
292 "divide",
293 "lozenge",
294 "ydieresis",
295 "Ydieresis",
296 "fraction",
297 "currency",
298 "guilsinglleft",
299 "guilsinglright",
300 "fi",
301 "fl",
302 "daggerdbl",
303 "periodcentered",
304 "quotesinglbase",
305 "quotedblbase",
306 "perthousand",
307 "Acircumflex",
308 "Ecircumflex",
309 "Aacute",
310 "Edieresis",
311 "Egrave",
312 "Iacute",
313 "Icircumflex",
314 "Idieresis",
315 "Igrave",
316 "Oacute",
317 "Ocircumflex",
318 "apple",
319 "Ograve",
320 "Uacute",
321 "Ucircumflex",
322 "Ugrave",
323 "dotlessi",
324 "circumflex",
325 "tilde",
326 "macron",
327 "breve",
328 "dotaccent",
329 "ring",
330 "cedilla",
331 "hungarumlaut",
332 "ogonek",
333 "caron",
334 "Lslash",
335 "lslash",
336 "Scaron",
337 "scaron",
338 "Zcaron",
339 "zcaron",
340 "brokenbar",
341 "Eth",
342 "eth",
343 "Yacute",
344 "yacute",
345 "Thorn",
346 "thorn",
347 "minus",
348 "multiply",
349 "onesuperior",
350 "twosuperior",
351 "threesuperior",
352 "onehalf",
353 "onequarter",
354 "threequarters",
355 "franc",
356 "Gbreve",
357 "gbreve",
358 "Idotaccent",
359 "Scedilla",
360 "scedilla",
361 "Cacute",
362 "cacute",
363 "Ccaron",
364 "ccaron",
365 "dcroat",
366];
367
368pub fn standard_mac_glyph_name(index: u16) -> Option<&'static str> {
374 STANDARD_MAC_GLYPH_NAMES.get(index as usize).copied()
375}
376
377#[derive(Debug, Clone)]
380#[doc(hidden)]
382pub struct PostTable {
383 pub version_raw: u32,
388 pub italic_angle: f32,
392 pub underline_position: i16,
394 pub underline_thickness: i16,
396 pub is_fixed_pitch: bool,
399 pub min_mem_type42: u32,
402 pub max_mem_type42: u32,
404 pub min_mem_type1: u32,
406 pub max_mem_type1: u32,
408 pub format: PostFormat,
410}
411
412#[derive(Debug, Clone)]
414pub enum PostFormat {
415 Version10,
418 Version20(PostV20),
420 Version25(PostV25),
423 Version30,
425}
426
427#[derive(Debug, Clone)]
429pub struct PostV20 {
430 pub num_glyphs: u16,
433 pub glyph_name_indices: Vec<u16>,
436 pub pascal_strings: Vec<String>,
442 pub has_oversize_glyph_name: bool,
447 pub has_non_conformant_glyph_name: bool,
453}
454
455#[derive(Debug, Clone)]
458pub struct PostV25 {
459 pub num_glyphs: u16,
461 pub offsets: Vec<i8>,
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq)]
476pub enum GlyphNameRef<'a> {
477 StandardMac { index: u16 },
480 Custom(&'a str),
483}
484
485impl PostTable {
486 pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
490 if bytes.len() < POST_HEADER_LEN {
491 return Err(Error::UnexpectedEof);
492 }
493 let version_raw = read_u32(bytes, 0)?;
494 let italic_raw = read_i32(bytes, 4)?;
495 let italic_angle = italic_raw as f32 / 65536.0;
496 let underline_position = read_i16(bytes, 8)?;
497 let underline_thickness = read_i16(bytes, 10)?;
498 let is_fixed_pitch = read_u32(bytes, 12)? != 0;
499 let min_mem_type42 = read_u32(bytes, 16)?;
500 let max_mem_type42 = read_u32(bytes, 20)?;
501 let min_mem_type1 = read_u32(bytes, 24)?;
502 let max_mem_type1 = read_u32(bytes, 28)?;
503
504 let tail = &bytes[POST_HEADER_LEN..];
505 let format = match version_raw {
506 POST_VERSION_10 => PostFormat::Version10,
507 POST_VERSION_20 => PostFormat::Version20(parse_v20(tail)?),
508 POST_VERSION_25 => PostFormat::Version25(parse_v25(tail)?),
509 POST_VERSION_30 => PostFormat::Version30,
510 _ => return Err(Error::BadStructure("post: unsupported version")),
511 };
512
513 Ok(Self {
514 version_raw,
515 italic_angle,
516 underline_position,
517 underline_thickness,
518 is_fixed_pitch,
519 min_mem_type42,
520 max_mem_type42,
521 min_mem_type1,
522 max_mem_type1,
523 format,
524 })
525 }
526
527 pub fn has_glyph_names(&self) -> bool {
530 !matches!(self.format, PostFormat::Version30)
531 }
532
533 pub fn has_oversize_glyph_name(&self) -> bool {
537 match &self.format {
538 PostFormat::Version20(v) => v.has_oversize_glyph_name,
539 _ => false,
540 }
541 }
542
543 pub fn has_non_conformant_glyph_name(&self) -> bool {
547 match &self.format {
548 PostFormat::Version20(v) => v.has_non_conformant_glyph_name,
549 _ => false,
550 }
551 }
552
553 pub fn pascal_string_count(&self) -> usize {
556 match &self.format {
557 PostFormat::Version20(v) => v.pascal_strings.len(),
558 _ => 0,
559 }
560 }
561
562 pub fn glyph_name_ref(&self, gid: u16) -> Option<GlyphNameRef<'_>> {
578 match &self.format {
579 PostFormat::Version10 => {
580 if gid < STANDARD_MAC_GLYPH_COUNT {
581 Some(GlyphNameRef::StandardMac { index: gid })
582 } else {
583 None
584 }
585 }
586 PostFormat::Version20(v) => {
587 let idx = *v.glyph_name_indices.get(gid as usize)?;
588 if idx < STANDARD_MAC_GLYPH_COUNT {
589 Some(GlyphNameRef::StandardMac { index: idx })
590 } else {
591 let pi = (idx - STANDARD_MAC_GLYPH_COUNT) as usize;
592 v.pascal_strings
593 .get(pi)
594 .map(|s| GlyphNameRef::Custom(s.as_str()))
595 }
596 }
597 PostFormat::Version25(v) => {
598 let off = *v.offsets.get(gid as usize)?;
599 let std = i32::from(gid) + i32::from(off);
602 if (0..i32::from(STANDARD_MAC_GLYPH_COUNT)).contains(&std) {
603 Some(GlyphNameRef::StandardMac { index: std as u16 })
604 } else {
605 None
606 }
607 }
608 PostFormat::Version30 => None,
609 }
610 }
611
612 pub fn custom_glyph_name(&self, gid: u16) -> Option<&str> {
617 match self.glyph_name_ref(gid)? {
618 GlyphNameRef::Custom(s) => Some(s),
619 GlyphNameRef::StandardMac { .. } => None,
620 }
621 }
622
623 pub fn resolved_glyph_name(&self, gid: u16) -> Option<&str> {
631 match self.glyph_name_ref(gid)? {
632 GlyphNameRef::Custom(s) => Some(s),
633 GlyphNameRef::StandardMac { index } => standard_mac_glyph_name(index),
634 }
635 }
636
637 pub fn named_glyph_count(&self) -> u16 {
646 match &self.format {
647 PostFormat::Version10 => STANDARD_MAC_GLYPH_COUNT,
648 PostFormat::Version20(v) => v.num_glyphs,
649 PostFormat::Version25(v) => v.num_glyphs,
650 PostFormat::Version30 => 0,
651 }
652 }
653
654 pub fn gid_for_name(&self, name: &str) -> Option<u16> {
672 if matches!(self.format, PostFormat::Version30) {
673 return None;
674 }
675 let count = self.named_glyph_count();
676 let std_target = STANDARD_MAC_GLYPH_NAMES
681 .iter()
682 .position(|n| *n == name)
683 .map(|i| i as u16);
684 for gid in 0..count {
685 match self.glyph_name_ref(gid) {
686 Some(GlyphNameRef::StandardMac { index }) if Some(index) == std_target => {
687 return Some(gid);
688 }
689 Some(GlyphNameRef::Custom(s)) if s == name => {
690 return Some(gid);
691 }
692 _ => {}
693 }
694 }
695 None
696 }
697
698 pub fn iter_glyph_names(&self) -> impl Iterator<Item = (u16, &str)> + '_ {
706 let count = self.named_glyph_count();
707 (0..count).filter_map(move |gid| self.resolved_glyph_name(gid).map(|n| (gid, n)))
708 }
709}
710
711fn parse_v20(tail: &[u8]) -> Result<PostV20, Error> {
712 if tail.len() < 2 {
713 return Err(Error::UnexpectedEof);
714 }
715 let num_glyphs = read_u16(tail, 0)?;
716 let idx_bytes_len = 2usize
717 .checked_mul(num_glyphs as usize)
718 .ok_or(Error::BadStructure("post v2.0: numGlyphs overflow"))?;
719 let idx_end = 2usize
720 .checked_add(idx_bytes_len)
721 .ok_or(Error::BadStructure("post v2.0: numGlyphs overflow"))?;
722 if tail.len() < idx_end {
723 return Err(Error::UnexpectedEof);
724 }
725 let mut glyph_name_indices = Vec::with_capacity(num_glyphs as usize);
726 let mut max_pascal_referenced: i32 = -1;
727 for i in 0..num_glyphs as usize {
728 let v = read_u16(tail, 2 + i * 2)?;
729 if v >= STANDARD_MAC_GLYPH_COUNT {
730 let pi = (v - STANDARD_MAC_GLYPH_COUNT) as i32;
731 if pi > max_pascal_referenced {
732 max_pascal_referenced = pi;
733 }
734 }
735 glyph_name_indices.push(v);
736 }
737
738 let pool = &tail[idx_end..];
740 let mut pascal_strings: Vec<String> = Vec::new();
741 let mut has_oversize_glyph_name = false;
742 let mut has_non_conformant_glyph_name = false;
743 let mut p = 0usize;
744 while p < pool.len() {
745 let len = read_u8(pool, p)? as usize;
746 p += 1;
747 if p + len > pool.len() {
748 return Err(Error::UnexpectedEof);
749 }
750 let raw = &pool[p..p + len];
751 if len > RECOMMENDED_GLYPH_NAME_MAX_LEN {
752 has_oversize_glyph_name = true;
753 }
754 if !raw.iter().all(|b| is_conformant_glyph_name_byte(*b)) {
755 has_non_conformant_glyph_name = true;
756 }
757 let s = match std::str::from_utf8(raw) {
761 Ok(s) => s.to_string(),
762 Err(_) => String::from_utf8_lossy(raw).into_owned(),
763 };
764 pascal_strings.push(s);
765 p += len;
766 }
767
768 let _ = max_pascal_referenced;
774
775 Ok(PostV20 {
776 num_glyphs,
777 glyph_name_indices,
778 pascal_strings,
779 has_oversize_glyph_name,
780 has_non_conformant_glyph_name,
781 })
782}
783
784fn parse_v25(tail: &[u8]) -> Result<PostV25, Error> {
785 if tail.len() < 2 {
786 return Err(Error::UnexpectedEof);
787 }
788 let num_glyphs = read_u16(tail, 0)?;
789 let needed = 2usize
790 .checked_add(num_glyphs as usize)
791 .ok_or(Error::BadStructure("post v2.5: numGlyphs overflow"))?;
792 if tail.len() < needed {
793 return Err(Error::UnexpectedEof);
794 }
795 let mut offsets = Vec::with_capacity(num_glyphs as usize);
796 for i in 0..num_glyphs as usize {
797 offsets.push(tail[2 + i] as i8);
798 }
799 Ok(PostV25 {
800 num_glyphs,
801 offsets,
802 })
803}
804
805fn is_conformant_glyph_name_byte(b: u8) -> bool {
806 b.is_ascii_uppercase() || b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'.' || b == b'_'
809}
810
811#[cfg(test)]
812mod tests {
813 use super::*;
814
815 fn header(version: u32) -> Vec<u8> {
816 let mut b = vec![0u8; POST_HEADER_LEN];
817 b[0..4].copy_from_slice(&version.to_be_bytes());
818 b[4..8].copy_from_slice(&((-10i32) << 16).to_be_bytes());
820 b[8..10].copy_from_slice(&(-100i16).to_be_bytes());
821 b[10..12].copy_from_slice(&50i16.to_be_bytes());
822 b[12..16].copy_from_slice(&1u32.to_be_bytes());
823 b[16..20].copy_from_slice(&0u32.to_be_bytes());
824 b[20..24].copy_from_slice(&0u32.to_be_bytes());
825 b[24..28].copy_from_slice(&0u32.to_be_bytes());
826 b[28..32].copy_from_slice(&0u32.to_be_bytes());
827 b
828 }
829
830 #[test]
831 fn parses_minimal_v3_header() {
832 let b = header(POST_VERSION_30);
833 let p = PostTable::parse(&b).unwrap();
834 assert_eq!(p.version_raw, POST_VERSION_30);
835 assert!((p.italic_angle - (-10.0)).abs() < 0.001);
836 assert_eq!(p.underline_position, -100);
837 assert_eq!(p.underline_thickness, 50);
838 assert!(p.is_fixed_pitch);
839 assert!(matches!(p.format, PostFormat::Version30));
840 assert!(!p.has_glyph_names());
841 assert!(p.glyph_name_ref(0).is_none());
842 }
843
844 #[test]
845 fn parses_v10_returns_standard_mac_indices() {
846 let b = header(POST_VERSION_10);
847 let p = PostTable::parse(&b).unwrap();
848 assert!(matches!(p.format, PostFormat::Version10));
849 assert!(p.has_glyph_names());
850 assert_eq!(
851 p.glyph_name_ref(0),
852 Some(GlyphNameRef::StandardMac { index: 0 })
853 );
854 assert_eq!(
855 p.glyph_name_ref(217),
856 Some(GlyphNameRef::StandardMac { index: 217 })
857 );
858 assert!(p.glyph_name_ref(258).is_none());
860 assert_eq!(p.resolved_glyph_name(0), Some(".notdef"));
862 assert_eq!(p.resolved_glyph_name(217), Some("tilde"));
863 assert!(p.resolved_glyph_name(258).is_none());
864 }
865
866 #[test]
870 fn v20_resolves_spec_worked_example() {
871 let num_glyphs: u16 = 409;
872 let mut tail = Vec::new();
873 tail.extend_from_slice(&num_glyphs.to_be_bytes());
874 for gid in 0..num_glyphs {
875 let idx: u16 = match gid {
876 302 => 217,
877 408 => 262, _ => 0, };
880 tail.extend_from_slice(&idx.to_be_bytes());
881 }
882 for name in ["one", "two", "three", "four", "weird"] {
884 tail.push(name.len() as u8);
885 tail.extend_from_slice(name.as_bytes());
886 }
887
888 let mut bytes = header(POST_VERSION_20);
889 bytes.extend_from_slice(&tail);
890 let p = PostTable::parse(&bytes).unwrap();
891 assert!(p.has_glyph_names());
892 assert_eq!(p.pascal_string_count(), 5);
893 assert_eq!(
894 p.glyph_name_ref(302),
895 Some(GlyphNameRef::StandardMac { index: 217 })
896 );
897 assert_eq!(p.glyph_name_ref(408), Some(GlyphNameRef::Custom("weird")));
898 assert_eq!(p.custom_glyph_name(408), Some("weird"));
899 assert!(p.custom_glyph_name(302).is_none());
900 assert_eq!(p.resolved_glyph_name(302), Some("tilde"));
903 assert_eq!(p.resolved_glyph_name(408), Some("weird"));
904 }
905
906 #[test]
907 fn v20_pascal_pool_indices_are_zero_based() {
908 let num_glyphs: u16 = 2;
910 let mut tail = Vec::new();
911 tail.extend_from_slice(&num_glyphs.to_be_bytes());
912 tail.extend_from_slice(&258u16.to_be_bytes()); tail.extend_from_slice(&259u16.to_be_bytes()); for name in ["Alpha", "Beta"] {
915 tail.push(name.len() as u8);
916 tail.extend_from_slice(name.as_bytes());
917 }
918 let mut bytes = header(POST_VERSION_20);
919 bytes.extend_from_slice(&tail);
920 let p = PostTable::parse(&bytes).unwrap();
921 assert_eq!(p.glyph_name_ref(0), Some(GlyphNameRef::Custom("Alpha")));
922 assert_eq!(p.glyph_name_ref(1), Some(GlyphNameRef::Custom("Beta")));
923 }
924
925 #[test]
926 fn v20_rejects_truncated_pascal_string() {
927 let num_glyphs: u16 = 1;
929 let mut tail = Vec::new();
930 tail.extend_from_slice(&num_glyphs.to_be_bytes());
931 tail.extend_from_slice(&258u16.to_be_bytes());
932 tail.push(5); tail.extend_from_slice(b"abc");
934 let mut bytes = header(POST_VERSION_20);
935 bytes.extend_from_slice(&tail);
936 assert!(matches!(
937 PostTable::parse(&bytes),
938 Err(Error::UnexpectedEof)
939 ));
940 }
941
942 #[test]
943 fn v20_flags_oversize_and_non_conformant_names() {
944 let num_glyphs: u16 = 2;
947 let mut tail = Vec::new();
948 tail.extend_from_slice(&num_glyphs.to_be_bytes());
949 tail.extend_from_slice(&258u16.to_be_bytes()); tail.extend_from_slice(&259u16.to_be_bytes()); let oversize = "a".repeat(64);
952 tail.push(oversize.len() as u8);
953 tail.extend_from_slice(oversize.as_bytes());
954 let bad = "weird/name";
955 tail.push(bad.len() as u8);
956 tail.extend_from_slice(bad.as_bytes());
957 let mut bytes = header(POST_VERSION_20);
958 bytes.extend_from_slice(&tail);
959 let p = PostTable::parse(&bytes).unwrap();
960 assert!(p.has_oversize_glyph_name());
961 assert!(p.has_non_conformant_glyph_name());
962 }
963
964 #[test]
965 fn v25_resolves_signed_offset_into_standard_set() {
966 let num_glyphs: u16 = 3;
970 let mut tail = Vec::new();
971 tail.extend_from_slice(&num_glyphs.to_be_bytes());
972 tail.push(36i8 as u8);
973 tail.push(36i8 as u8);
974 tail.push(36i8 as u8);
975 let mut bytes = header(POST_VERSION_25);
976 bytes.extend_from_slice(&tail);
977 let p = PostTable::parse(&bytes).unwrap();
978 assert!(p.has_glyph_names());
979 assert_eq!(
980 p.glyph_name_ref(0),
981 Some(GlyphNameRef::StandardMac { index: 36 })
982 );
983 assert_eq!(
984 p.glyph_name_ref(1),
985 Some(GlyphNameRef::StandardMac { index: 37 })
986 );
987 assert_eq!(
988 p.glyph_name_ref(2),
989 Some(GlyphNameRef::StandardMac { index: 38 })
990 );
991 assert_eq!(p.resolved_glyph_name(0), Some("A"));
993 assert_eq!(p.resolved_glyph_name(1), Some("B"));
994 assert_eq!(p.resolved_glyph_name(2), Some("C"));
995 }
996
997 #[test]
998 fn v25_negative_offset_below_zero_yields_none() {
999 let num_glyphs: u16 = 1;
1001 let mut tail = Vec::new();
1002 tail.extend_from_slice(&num_glyphs.to_be_bytes());
1003 tail.push((-1i8) as u8);
1004 let mut bytes = header(POST_VERSION_25);
1005 bytes.extend_from_slice(&tail);
1006 let p = PostTable::parse(&bytes).unwrap();
1007 assert!(p.glyph_name_ref(0).is_none());
1008 }
1009
1010 #[test]
1011 fn v25_offset_past_standard_set_yields_none() {
1012 let num_glyphs: u16 = 251;
1016 let mut tail = Vec::new();
1017 tail.extend_from_slice(&num_glyphs.to_be_bytes());
1018 for _ in 0..num_glyphs {
1019 tail.push(127i8 as u8);
1020 }
1021 let mut bytes = header(POST_VERSION_25);
1022 bytes.extend_from_slice(&tail);
1023 let p = PostTable::parse(&bytes).unwrap();
1024 assert_eq!(
1026 p.glyph_name_ref(0),
1027 Some(GlyphNameRef::StandardMac { index: 127 })
1028 );
1029 assert!(p.glyph_name_ref(250).is_none());
1031 }
1032
1033 #[test]
1034 fn standard_mac_glyph_names_table_is_well_formed() {
1035 assert_eq!(STANDARD_MAC_GLYPH_NAMES.len(), 258);
1038 assert_eq!(
1039 STANDARD_MAC_GLYPH_NAMES.len(),
1040 STANDARD_MAC_GLYPH_COUNT as usize
1041 );
1042 let mut sorted: Vec<&str> = STANDARD_MAC_GLYPH_NAMES.to_vec();
1043 sorted.sort_unstable();
1044 sorted.dedup();
1045 assert_eq!(sorted.len(), 258, "all 258 names must be distinct");
1046 }
1047
1048 #[test]
1049 fn standard_mac_glyph_name_spot_checks() {
1050 assert_eq!(standard_mac_glyph_name(0), Some(".notdef"));
1052 assert_eq!(standard_mac_glyph_name(1), Some(".null"));
1053 assert_eq!(standard_mac_glyph_name(2), Some("nonmarkingreturn"));
1054 assert_eq!(standard_mac_glyph_name(3), Some("space"));
1055 assert_eq!(standard_mac_glyph_name(36), Some("A"));
1056 assert_eq!(standard_mac_glyph_name(192), Some("fi"));
1057 assert_eq!(standard_mac_glyph_name(193), Some("fl"));
1058 assert_eq!(standard_mac_glyph_name(217), Some("tilde"));
1059 assert_eq!(standard_mac_glyph_name(257), Some("dcroat"));
1060 assert_eq!(standard_mac_glyph_name(258), None);
1062 assert_eq!(standard_mac_glyph_name(u16::MAX), None);
1063 }
1064
1065 #[test]
1066 fn rejects_unknown_version() {
1067 let b = header(0x0004_0000);
1069 assert!(matches!(PostTable::parse(&b), Err(Error::BadStructure(_))));
1070 }
1071
1072 #[test]
1073 fn rejects_short_header() {
1074 let b = vec![0u8; 31];
1075 assert!(matches!(PostTable::parse(&b), Err(Error::UnexpectedEof)));
1076 }
1077
1078 #[test]
1079 fn v20_truncated_index_array_rejected() {
1080 let mut tail = Vec::new();
1082 tail.extend_from_slice(&2u16.to_be_bytes());
1083 tail.extend_from_slice(&0u16.to_be_bytes()); let mut bytes = header(POST_VERSION_20);
1085 bytes.extend_from_slice(&tail);
1086 assert!(matches!(
1087 PostTable::parse(&bytes),
1088 Err(Error::UnexpectedEof)
1089 ));
1090 }
1091
1092 #[test]
1093 fn v20_pascal_index_out_of_pool_decodes_glyph_as_none() {
1094 let num_glyphs: u16 = 1;
1096 let mut tail = Vec::new();
1097 tail.extend_from_slice(&num_glyphs.to_be_bytes());
1098 tail.extend_from_slice(&258u16.to_be_bytes());
1099 let mut bytes = header(POST_VERSION_20);
1101 bytes.extend_from_slice(&tail);
1102 let p = PostTable::parse(&bytes).unwrap();
1103 assert!(p.glyph_name_ref(0).is_none());
1104 }
1105
1106 #[test]
1107 fn v10_reverse_lookup_spans_full_standard_set() {
1108 let b = header(POST_VERSION_10);
1109 let p = PostTable::parse(&b).unwrap();
1110 assert_eq!(p.named_glyph_count(), 258);
1111 assert_eq!(p.gid_for_name(".notdef"), Some(0));
1113 assert_eq!(p.gid_for_name("A"), Some(36));
1114 assert_eq!(p.gid_for_name("tilde"), Some(217));
1115 assert_eq!(p.gid_for_name("dcroat"), Some(257));
1116 assert_eq!(p.gid_for_name("Alpha"), None);
1118 assert_eq!(p.gid_for_name(""), None);
1119 }
1120
1121 #[test]
1122 fn v20_reverse_lookup_covers_custom_and_standard() {
1123 let num_glyphs: u16 = 4;
1124 let mut tail = Vec::new();
1125 tail.extend_from_slice(&num_glyphs.to_be_bytes());
1126 for idx in [0u16, 36, 258, 259] {
1129 tail.extend_from_slice(&idx.to_be_bytes());
1130 }
1131 for name in ["Alpha", "Beta"] {
1132 tail.push(name.len() as u8);
1133 tail.extend_from_slice(name.as_bytes());
1134 }
1135 let mut bytes = header(POST_VERSION_20);
1136 bytes.extend_from_slice(&tail);
1137 let p = PostTable::parse(&bytes).unwrap();
1138 assert_eq!(p.named_glyph_count(), 4);
1139 assert_eq!(p.gid_for_name(".notdef"), Some(0));
1140 assert_eq!(p.gid_for_name("A"), Some(1));
1141 assert_eq!(p.gid_for_name("Alpha"), Some(2));
1142 assert_eq!(p.gid_for_name("Beta"), Some(3));
1143 assert_eq!(p.gid_for_name("missing"), None);
1144 }
1145
1146 #[test]
1147 fn reverse_lookup_returns_lowest_gid_on_duplicate() {
1148 let num_glyphs: u16 = 3;
1150 let mut tail = Vec::new();
1151 tail.extend_from_slice(&num_glyphs.to_be_bytes());
1152 for idx in [0u16, 36, 36] {
1153 tail.extend_from_slice(&idx.to_be_bytes());
1154 }
1155 let mut bytes = header(POST_VERSION_20);
1156 bytes.extend_from_slice(&tail);
1157 let p = PostTable::parse(&bytes).unwrap();
1158 assert_eq!(p.gid_for_name("A"), Some(1));
1160 }
1161
1162 #[test]
1163 fn v25_reverse_lookup_inverts_offset() {
1164 let num_glyphs: u16 = 3;
1166 let mut tail = Vec::new();
1167 tail.extend_from_slice(&num_glyphs.to_be_bytes());
1168 for _ in 0..3 {
1169 tail.push(36i8 as u8);
1170 }
1171 let mut bytes = header(POST_VERSION_25);
1172 bytes.extend_from_slice(&tail);
1173 let p = PostTable::parse(&bytes).unwrap();
1174 assert_eq!(p.named_glyph_count(), 3);
1175 assert_eq!(p.gid_for_name("A"), Some(0));
1176 assert_eq!(p.gid_for_name("B"), Some(1));
1177 assert_eq!(p.gid_for_name("C"), Some(2));
1178 assert_eq!(p.gid_for_name("D"), None);
1180 }
1181
1182 #[test]
1183 fn v30_reverse_lookup_and_iter_are_empty() {
1184 let b = header(POST_VERSION_30);
1185 let p = PostTable::parse(&b).unwrap();
1186 assert_eq!(p.named_glyph_count(), 0);
1187 assert_eq!(p.gid_for_name(".notdef"), None);
1188 assert_eq!(p.iter_glyph_names().count(), 0);
1189 }
1190
1191 #[test]
1192 fn iter_glyph_names_round_trips_through_reverse_lookup() {
1193 let num_glyphs: u16 = 4;
1194 let mut tail = Vec::new();
1195 tail.extend_from_slice(&num_glyphs.to_be_bytes());
1196 for idx in [0u16, 36, 258, 259] {
1197 tail.extend_from_slice(&idx.to_be_bytes());
1198 }
1199 for name in ["Alpha", "Beta"] {
1200 tail.push(name.len() as u8);
1201 tail.extend_from_slice(name.as_bytes());
1202 }
1203 let mut bytes = header(POST_VERSION_20);
1204 bytes.extend_from_slice(&tail);
1205 let p = PostTable::parse(&bytes).unwrap();
1206 let pairs: Vec<(u16, &str)> = p.iter_glyph_names().collect();
1207 assert_eq!(
1208 pairs,
1209 vec![(0, ".notdef"), (1, "A"), (2, "Alpha"), (3, "Beta")]
1210 );
1211 for (gid, name) in pairs {
1214 assert_eq!(p.gid_for_name(name), Some(gid));
1215 }
1216 }
1217
1218 #[test]
1219 fn v20_iter_skips_unsatisfiable_pascal_reference() {
1220 let num_glyphs: u16 = 2;
1223 let mut tail = Vec::new();
1224 tail.extend_from_slice(&num_glyphs.to_be_bytes());
1225 tail.extend_from_slice(&0u16.to_be_bytes());
1226 tail.extend_from_slice(&258u16.to_be_bytes());
1227 let mut bytes = header(POST_VERSION_20);
1228 bytes.extend_from_slice(&tail);
1229 let p = PostTable::parse(&bytes).unwrap();
1230 let pairs: Vec<(u16, &str)> = p.iter_glyph_names().collect();
1231 assert_eq!(pairs, vec![(0, ".notdef")]);
1232 }
1233
1234 #[test]
1235 fn version_constants_sanity() {
1236 assert_eq!(POST_VERSION_10, 0x0001_0000);
1237 assert_eq!(POST_VERSION_20, 0x0002_0000);
1238 assert_eq!(POST_VERSION_25, 0x0002_5000);
1239 assert_eq!(POST_VERSION_30, 0x0003_0000);
1240 assert_eq!(STANDARD_MAC_GLYPH_COUNT, 258);
1241 assert_eq!(POST_HEADER_LEN, 32);
1242 }
1243}