1include!("../../generated/generated_post.rs");
4
5#[allow(clippy::needless_lifetimes)] impl<'a> Post<'a> {
7 pub fn num_names(&self) -> usize {
9 match self.version() {
10 Version16Dot16::VERSION_1_0 => DEFAULT_GLYPH_NAMES.len(),
11 Version16Dot16::VERSION_2_0 => self.num_glyphs().unwrap_or_default() as usize,
12 _ => 0,
13 }
14 }
15
16 pub fn glyph_name(&self, glyph_id: GlyphId16) -> Option<&str> {
17 let glyph_id = glyph_id.to_u16() as usize;
18 match self.version() {
19 Version16Dot16::VERSION_1_0 => DEFAULT_GLYPH_NAMES.get(glyph_id).copied(),
20 Version16Dot16::VERSION_2_0 => {
21 let idx = self.glyph_name_index()?.get(glyph_id)?.get() as usize;
22 if idx < DEFAULT_GLYPH_NAMES.len() {
23 return DEFAULT_GLYPH_NAMES.get(idx).copied();
24 }
25 let idx = idx - DEFAULT_GLYPH_NAMES.len();
26 self.string_data()?.get(idx)?.ok().map(|s| s.0)
27 }
28 _ => None,
29 }
30 }
31
32 #[cfg(feature = "experimental_traverse")]
35 fn traverse_string_data(&self) -> FieldType<'a> {
36 FieldType::I8(-42) }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct PString<'a>(&'a str);
46
47impl<'a> PString<'a> {
48 pub fn as_str(&self) -> &'a str {
49 self.0
50 }
51}
52
53impl std::ops::Deref for PString<'_> {
54 type Target = str;
55 fn deref(&self) -> &Self::Target {
56 self.0
57 }
58}
59
60impl PartialEq<&str> for PString<'_> {
61 fn eq(&self, other: &&str) -> bool {
62 self.0 == *other
63 }
64}
65
66impl<'a> FontRead<'a> for PString<'a> {
67 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
68 let len: u8 = data.read_at(0)?;
69 let pstring = data
70 .as_bytes()
71 .get(1..len as usize + 1)
72 .ok_or(ReadError::OutOfBounds)?;
73
74 if pstring.is_ascii() {
75 Ok(PString(std::str::from_utf8(pstring).unwrap()))
76 } else {
77 Err(ReadError::MalformedData("Must be valid ascii"))
79 }
80 }
81}
82
83impl VarSize for PString<'_> {
84 type Size = u8;
85}
86
87#[rustfmt::skip]
89pub static DEFAULT_GLYPH_NAMES: [&str; 258] = [
90 ".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl", "numbersign", "dollar",
91 "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma",
92 "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven",
93 "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B",
94 "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
95 "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
96 "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
97 "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright",
98 "asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis",
99 "aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute",
100 "egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde",
101 "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex",
102 "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph",
103 "germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE",
104 "Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff",
105 "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae",
106 "oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal",
107 "Delta", "guillemotleft", "guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde",
108 "Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft",
109 "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency",
110 "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase",
111 "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave",
112 "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve",
113 "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve",
114 "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash",
115 "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn",
116 "thorn", "minus", "multiply", "onesuperior", "twosuperior", "threesuperior", "onehalf",
117 "onequarter", "threequarters", "franc", "Gbreve", "gbreve", "Idotaccent", "Scedilla",
118 "scedilla", "Cacute", "cacute", "Ccaron", "ccaron", "dcroat",
119];
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use font_test_data::{bebuffer::BeBuffer, post as test_data};
125
126 #[test]
127 fn test_post() {
128 let table = Post::read(test_data::SIMPLE.into()).unwrap();
129 assert_eq!(table.version(), Version16Dot16::VERSION_2_0);
130 assert_eq!(table.underline_position(), FWord::new(-75));
131 assert_eq!(table.glyph_name(GlyphId16::new(1)), Some(".notdef"));
132 assert_eq!(table.glyph_name(GlyphId16::new(2)), Some("space"));
133 assert_eq!(table.glyph_name(GlyphId16::new(7)), Some("hello"));
134 assert_eq!(table.glyph_name(GlyphId16::new(8)), Some("hi"));
135 assert_eq!(table.glyph_name(GlyphId16::new(9)), Some("hola"));
136 }
137
138 fn make_basic_post(version: Version16Dot16, include_num_glyphs: bool) -> BeBuffer {
139 let buf = BeBuffer::new()
140 .push(version)
141 .push(Fixed::from_i32(5))
142 .extend([FWord::new(6), FWord::new(7)]) .push(0u32) .extend([7u32, 8, 9, 10]); if include_num_glyphs {
146 buf.push(0u16)
147 } else {
148 buf
149 }
150 }
151
152 #[test]
153 fn parse_versioned_fields_v1() {
154 let buf = make_basic_post(Version16Dot16::VERSION_1_0, true);
157 let postv1 = Post::read(buf.data().into()).unwrap();
158 assert!(postv1.num_glyphs().is_none());
159 }
160
161 #[test]
162 fn parse_versioned_fields_v2() {
163 let buf = make_basic_post(Version16Dot16::VERSION_2_0, false);
164 let postv2 = Post::read(buf.data().into()).unwrap();
165 assert!(postv2.num_glyphs().is_none());
167
168 let buf = make_basic_post(Version16Dot16::VERSION_2_0, true);
170 let postv2 = Post::read(buf.data().into()).unwrap();
171 assert_eq!(postv2.num_glyphs(), Some(0));
173 }
174
175 #[test]
176 fn parse_versioned_fields_v3() {
177 let buf = make_basic_post(Version16Dot16::VERSION_3_0, true);
179 let postv3 = Post::read(buf.data().into()).unwrap();
180 assert!(postv3.num_glyphs().is_none());
181 }
182
183 #[test]
184 fn num_names_defaults_to_zero_without_num_glyphs() {
185 let buf = make_basic_post(Version16Dot16::VERSION_2_0, false);
186 let post = Post::read(buf.data().into()).unwrap();
187 assert_eq!(post.num_names(), 0);
189 }
190
191 #[test]
192 fn glyph_name_missing_string_data_returns_none() {
193 let buf = BeBuffer::new()
194 .push(Version16Dot16::VERSION_2_0)
195 .push(Fixed::from_i32(5))
196 .extend([FWord::new(6), FWord::new(7)])
197 .push(0u32)
198 .extend([7u32, 8, 9, 10])
199 .push(1u16)
200 .push(258u16);
201 let post = Post::read(buf.data().into()).unwrap();
202 assert_eq!(post.glyph_name(GlyphId16::new(0)), None);
204 }
205}