1 | //! A [PostScript Table]( |
2 | //! https://docs.microsoft.com/en-us/typography/opentype/spec/post) implementation. |
3 | |
4 | use crate::parser::{Fixed, LazyArray16, Stream}; |
5 | #[cfg (feature = "glyph-names" )] |
6 | use crate::GlyphId; |
7 | use crate::LineMetrics; |
8 | |
9 | const ITALIC_ANGLE_OFFSET: usize = 4; |
10 | const UNDERLINE_POSITION_OFFSET: usize = 8; |
11 | const UNDERLINE_THICKNESS_OFFSET: usize = 10; |
12 | const IS_FIXED_PITCH_OFFSET: usize = 12; |
13 | |
14 | // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6post.html |
15 | /// A list of Macintosh glyph names. |
16 | #[cfg (feature = "glyph-names" )] |
17 | const MACINTOSH_NAMES: &[&str] = &[ |
18 | ".notdef" , |
19 | ".null" , |
20 | "nonmarkingreturn" , |
21 | "space" , |
22 | "exclam" , |
23 | "quotedbl" , |
24 | "numbersign" , |
25 | "dollar" , |
26 | "percent" , |
27 | "ampersand" , |
28 | "quotesingle" , |
29 | "parenleft" , |
30 | "parenright" , |
31 | "asterisk" , |
32 | "plus" , |
33 | "comma" , |
34 | "hyphen" , |
35 | "period" , |
36 | "slash" , |
37 | "zero" , |
38 | "one" , |
39 | "two" , |
40 | "three" , |
41 | "four" , |
42 | "five" , |
43 | "six" , |
44 | "seven" , |
45 | "eight" , |
46 | "nine" , |
47 | "colon" , |
48 | "semicolon" , |
49 | "less" , |
50 | "equal" , |
51 | "greater" , |
52 | "question" , |
53 | "at" , |
54 | "A" , |
55 | "B" , |
56 | "C" , |
57 | "D" , |
58 | "E" , |
59 | "F" , |
60 | "G" , |
61 | "H" , |
62 | "I" , |
63 | "J" , |
64 | "K" , |
65 | "L" , |
66 | "M" , |
67 | "N" , |
68 | "O" , |
69 | "P" , |
70 | "Q" , |
71 | "R" , |
72 | "S" , |
73 | "T" , |
74 | "U" , |
75 | "V" , |
76 | "W" , |
77 | "X" , |
78 | "Y" , |
79 | "Z" , |
80 | "bracketleft" , |
81 | "backslash" , |
82 | "bracketright" , |
83 | "asciicircum" , |
84 | "underscore" , |
85 | "grave" , |
86 | "a" , |
87 | "b" , |
88 | "c" , |
89 | "d" , |
90 | "e" , |
91 | "f" , |
92 | "g" , |
93 | "h" , |
94 | "i" , |
95 | "j" , |
96 | "k" , |
97 | "l" , |
98 | "m" , |
99 | "n" , |
100 | "o" , |
101 | "p" , |
102 | "q" , |
103 | "r" , |
104 | "s" , |
105 | "t" , |
106 | "u" , |
107 | "v" , |
108 | "w" , |
109 | "x" , |
110 | "y" , |
111 | "z" , |
112 | "braceleft" , |
113 | "bar" , |
114 | "braceright" , |
115 | "asciitilde" , |
116 | "Adieresis" , |
117 | "Aring" , |
118 | "Ccedilla" , |
119 | "Eacute" , |
120 | "Ntilde" , |
121 | "Odieresis" , |
122 | "Udieresis" , |
123 | "aacute" , |
124 | "agrave" , |
125 | "acircumflex" , |
126 | "adieresis" , |
127 | "atilde" , |
128 | "aring" , |
129 | "ccedilla" , |
130 | "eacute" , |
131 | "egrave" , |
132 | "ecircumflex" , |
133 | "edieresis" , |
134 | "iacute" , |
135 | "igrave" , |
136 | "icircumflex" , |
137 | "idieresis" , |
138 | "ntilde" , |
139 | "oacute" , |
140 | "ograve" , |
141 | "ocircumflex" , |
142 | "odieresis" , |
143 | "otilde" , |
144 | "uacute" , |
145 | "ugrave" , |
146 | "ucircumflex" , |
147 | "udieresis" , |
148 | "dagger" , |
149 | "degree" , |
150 | "cent" , |
151 | "sterling" , |
152 | "section" , |
153 | "bullet" , |
154 | "paragraph" , |
155 | "germandbls" , |
156 | "registered" , |
157 | "copyright" , |
158 | "trademark" , |
159 | "acute" , |
160 | "dieresis" , |
161 | "notequal" , |
162 | "AE" , |
163 | "Oslash" , |
164 | "infinity" , |
165 | "plusminus" , |
166 | "lessequal" , |
167 | "greaterequal" , |
168 | "yen" , |
169 | "mu" , |
170 | "partialdiff" , |
171 | "summation" , |
172 | "product" , |
173 | "pi" , |
174 | "integral" , |
175 | "ordfeminine" , |
176 | "ordmasculine" , |
177 | "Omega" , |
178 | "ae" , |
179 | "oslash" , |
180 | "questiondown" , |
181 | "exclamdown" , |
182 | "logicalnot" , |
183 | "radical" , |
184 | "florin" , |
185 | "approxequal" , |
186 | "Delta" , |
187 | "guillemotleft" , |
188 | "guillemotright" , |
189 | "ellipsis" , |
190 | "nonbreakingspace" , |
191 | "Agrave" , |
192 | "Atilde" , |
193 | "Otilde" , |
194 | "OE" , |
195 | "oe" , |
196 | "endash" , |
197 | "emdash" , |
198 | "quotedblleft" , |
199 | "quotedblright" , |
200 | "quoteleft" , |
201 | "quoteright" , |
202 | "divide" , |
203 | "lozenge" , |
204 | "ydieresis" , |
205 | "Ydieresis" , |
206 | "fraction" , |
207 | "currency" , |
208 | "guilsinglleft" , |
209 | "guilsinglright" , |
210 | "fi" , |
211 | "fl" , |
212 | "daggerdbl" , |
213 | "periodcentered" , |
214 | "quotesinglbase" , |
215 | "quotedblbase" , |
216 | "perthousand" , |
217 | "Acircumflex" , |
218 | "Ecircumflex" , |
219 | "Aacute" , |
220 | "Edieresis" , |
221 | "Egrave" , |
222 | "Iacute" , |
223 | "Icircumflex" , |
224 | "Idieresis" , |
225 | "Igrave" , |
226 | "Oacute" , |
227 | "Ocircumflex" , |
228 | "apple" , |
229 | "Ograve" , |
230 | "Uacute" , |
231 | "Ucircumflex" , |
232 | "Ugrave" , |
233 | "dotlessi" , |
234 | "circumflex" , |
235 | "tilde" , |
236 | "macron" , |
237 | "breve" , |
238 | "dotaccent" , |
239 | "ring" , |
240 | "cedilla" , |
241 | "hungarumlaut" , |
242 | "ogonek" , |
243 | "caron" , |
244 | "Lslash" , |
245 | "lslash" , |
246 | "Scaron" , |
247 | "scaron" , |
248 | "Zcaron" , |
249 | "zcaron" , |
250 | "brokenbar" , |
251 | "Eth" , |
252 | "eth" , |
253 | "Yacute" , |
254 | "yacute" , |
255 | "Thorn" , |
256 | "thorn" , |
257 | "minus" , |
258 | "multiply" , |
259 | "onesuperior" , |
260 | "twosuperior" , |
261 | "threesuperior" , |
262 | "onehalf" , |
263 | "onequarter" , |
264 | "threequarters" , |
265 | "franc" , |
266 | "Gbreve" , |
267 | "gbreve" , |
268 | "Idotaccent" , |
269 | "Scedilla" , |
270 | "scedilla" , |
271 | "Cacute" , |
272 | "cacute" , |
273 | "Ccaron" , |
274 | "ccaron" , |
275 | "dcroat" , |
276 | ]; |
277 | |
278 | /// An iterator over glyph names. |
279 | /// |
280 | /// The `post` table doesn't provide the glyph names count, |
281 | /// so we have to simply iterate over all of them to find it out. |
282 | #[derive (Clone, Copy, Default)] |
283 | pub struct Names<'a> { |
284 | data: &'a [u8], |
285 | offset: usize, |
286 | } |
287 | |
288 | impl core::fmt::Debug for Names<'_> { |
289 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
290 | write!(f, "Names {{ ... }}" ) |
291 | } |
292 | } |
293 | |
294 | impl<'a> Iterator for Names<'a> { |
295 | type Item = &'a str; |
296 | |
297 | fn next(&mut self) -> Option<Self::Item> { |
298 | // Glyph names are stored as Pascal Strings. |
299 | // Meaning u8 (len) + [u8] (data). |
300 | |
301 | if self.offset >= self.data.len() { |
302 | return None; |
303 | } |
304 | |
305 | let len: u8 = self.data[self.offset]; |
306 | self.offset += 1; |
307 | |
308 | // An empty name is an error. |
309 | if len == 0 { |
310 | return None; |
311 | } |
312 | |
313 | let name: &[u8] = self.data.get(self.offset..self.offset + usize::from(len))?; |
314 | self.offset += usize::from(len); |
315 | core::str::from_utf8(name).ok() |
316 | } |
317 | } |
318 | |
319 | /// A [PostScript Table](https://docs.microsoft.com/en-us/typography/opentype/spec/post). |
320 | #[derive (Clone, Copy, Debug)] |
321 | pub struct Table<'a> { |
322 | /// Italic angle in counter-clockwise degrees from the vertical. |
323 | pub italic_angle: f32, |
324 | /// Underline metrics. |
325 | pub underline_metrics: LineMetrics, |
326 | /// Flag that indicates that the font is monospaced. |
327 | pub is_monospaced: bool, |
328 | |
329 | glyph_indexes: LazyArray16<'a, u16>, |
330 | names_data: &'a [u8], |
331 | } |
332 | |
333 | impl<'a> Table<'a> { |
334 | /// Parses a table from raw data. |
335 | pub fn parse(data: &'a [u8]) -> Option<Self> { |
336 | // Do not check the exact length, because some fonts include |
337 | // padding in table's length in table records, which is incorrect. |
338 | if data.len() < 32 { |
339 | return None; |
340 | } |
341 | |
342 | let version = Stream::new(data).read::<u32>()?; |
343 | if !(version == 0x00010000 |
344 | || version == 0x00020000 |
345 | || version == 0x00025000 |
346 | || version == 0x00030000 |
347 | || version == 0x00040000) |
348 | { |
349 | return None; |
350 | } |
351 | |
352 | let italic_angle = Stream::read_at::<Fixed>(data, ITALIC_ANGLE_OFFSET)?.0; |
353 | |
354 | let underline_metrics = LineMetrics { |
355 | position: Stream::read_at::<i16>(data, UNDERLINE_POSITION_OFFSET)?, |
356 | thickness: Stream::read_at::<i16>(data, UNDERLINE_THICKNESS_OFFSET)?, |
357 | }; |
358 | |
359 | let is_monospaced = Stream::read_at::<u32>(data, IS_FIXED_PITCH_OFFSET)? != 0; |
360 | |
361 | let mut names_data: &[u8] = &[]; |
362 | let mut glyph_indexes = LazyArray16::default(); |
363 | // Only version 2.0 of the table has data at the end. |
364 | if version == 0x00020000 { |
365 | let mut s = Stream::new_at(data, 32)?; |
366 | let indexes_count = s.read::<u16>()?; |
367 | glyph_indexes = s.read_array16::<u16>(indexes_count)?; |
368 | names_data = s.tail()?; |
369 | } |
370 | |
371 | Some(Table { |
372 | italic_angle, |
373 | underline_metrics, |
374 | is_monospaced, |
375 | names_data, |
376 | glyph_indexes, |
377 | }) |
378 | } |
379 | |
380 | /// Returns a glyph name by ID. |
381 | #[cfg (feature = "glyph-names" )] |
382 | pub fn glyph_name(&self, glyph_id: GlyphId) -> Option<&'a str> { |
383 | let mut index = self.glyph_indexes.get(glyph_id.0)?; |
384 | |
385 | // 'If the name index is between 0 and 257, treat the name index |
386 | // as a glyph index in the Macintosh standard order.' |
387 | if usize::from(index) < MACINTOSH_NAMES.len() { |
388 | Some(MACINTOSH_NAMES[usize::from(index)]) |
389 | } else { |
390 | // 'If the name index is between 258 and 65535, then subtract 258 and use that |
391 | // to index into the list of Pascal strings at the end of the table.' |
392 | index -= MACINTOSH_NAMES.len() as u16; |
393 | self.names().nth(usize::from(index)) |
394 | } |
395 | } |
396 | |
397 | /// Returns a glyph ID by a name. |
398 | #[cfg (feature = "glyph-names" )] |
399 | pub fn glyph_index_by_name(&self, name: &str) -> Option<GlyphId> { |
400 | let id = if let Some(index) = MACINTOSH_NAMES.iter().position(|n| *n == name) { |
401 | self.glyph_indexes |
402 | .into_iter() |
403 | .position(|i| usize::from(i) == index)? |
404 | } else { |
405 | let mut index = self.names().position(|n| n == name)?; |
406 | index += MACINTOSH_NAMES.len(); |
407 | self.glyph_indexes |
408 | .into_iter() |
409 | .position(|i| usize::from(i) == index)? |
410 | }; |
411 | |
412 | Some(GlyphId(id as u16)) |
413 | } |
414 | |
415 | /// Returns an iterator over glyph names. |
416 | /// |
417 | /// Default/predefined names are not included. Just the one in the font file. |
418 | pub fn names(&self) -> Names<'a> { |
419 | Names { |
420 | data: self.names_data, |
421 | offset: 0, |
422 | } |
423 | } |
424 | } |
425 | |