ttf_parser/tables/
post.rs

1//! A [PostScript Table](
2//! https://docs.microsoft.com/en-us/typography/opentype/spec/post) implementation.
3
4use crate::LineMetrics;
5use crate::parser::{Stream, Fixed, LazyArray16};
6#[cfg(feature = "glyph-names")] use crate::GlyphId;
7
8const TABLE_SIZE: usize = 32;
9const ITALIC_ANGLE_OFFSET: usize = 4;
10const UNDERLINE_POSITION_OFFSET: usize = 8;
11const UNDERLINE_THICKNESS_OFFSET: usize = 10;
12const 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")]
17const 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
279/// A list of glyph names.
280#[derive(Clone, Copy, Default)]
281pub struct Names<'a> {
282    indexes: LazyArray16<'a, u16>,
283    data: &'a [u8],
284}
285
286// TODO: add low-level iterator
287impl<'a> Names<'a> {
288    /// Returns a glyph name by ID.
289    #[cfg(feature = "glyph-names")]
290    pub fn get(&self, glyph_id: GlyphId) -> Option<&'a str> {
291        let mut index = self.indexes.get(glyph_id.0)?;
292
293        // 'If the name index is between 0 and 257, treat the name index
294        // as a glyph index in the Macintosh standard order.'
295        if usize::from(index) < MACINTOSH_NAMES.len() {
296            Some(MACINTOSH_NAMES[usize::from(index)])
297        } else {
298            // 'If the name index is between 258 and 65535, then subtract 258 and use that
299            // to index into the list of Pascal strings at the end of the table.'
300            index -= MACINTOSH_NAMES.len() as u16;
301
302            let mut s = Stream::new(self.data);
303            let mut i = 0;
304            while !s.at_end() && i < core::u16::MAX {
305                let len = s.read::<u8>()?;
306
307                if i == index {
308                    if len == 0 {
309                        // Empty name is an error.
310                        break;
311                    } else {
312                        let name = s.read_bytes(usize::from(len))?;
313                        return core::str::from_utf8(name).ok();
314                    }
315                } else {
316                    s.advance(usize::from(len));
317                }
318
319                i += 1;
320            }
321
322            None
323        }
324    }
325
326    /// Returns names count.
327    #[inline]
328    pub fn len(&self) -> u16 {
329        self.indexes.len()
330    }
331}
332
333impl core::fmt::Debug for Names<'_> {
334    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
335        write!(f, "Names {{ ... }}")
336    }
337}
338
339
340/// A [PostScript Table](https://docs.microsoft.com/en-us/typography/opentype/spec/post).
341#[derive(Clone, Copy, Debug)]
342pub struct Table<'a> {
343    /// Italic angle in counter-clockwise degrees from the vertical.
344    pub italic_angle: f32,
345    /// Underline metrics.
346    pub underline_metrics: LineMetrics,
347    /// Flag that indicates that the font is monospaced.
348    pub is_monospaced: bool,
349    /// A list of glyph names.
350    pub names: Names<'a>,
351}
352
353
354impl<'a> Table<'a> {
355    /// Parses a table from raw data.
356    pub fn parse(data: &'a [u8]) -> Option<Self> {
357        if data.len() < TABLE_SIZE {
358            return None;
359        }
360
361        let version = Stream::new(data).read::<u32>()?;
362        if !(version == 0x00010000 || version == 0x00020000 ||
363             version == 0x00025000 || version == 0x00030000 ||
364             version == 0x00040000)
365        {
366            return None;
367        }
368
369        let italic_angle = Stream::read_at::<Fixed>(data, ITALIC_ANGLE_OFFSET)?.0;
370
371        let underline_metrics = LineMetrics {
372            position: Stream::read_at::<i16>(data, UNDERLINE_POSITION_OFFSET)?,
373            thickness: Stream::read_at::<i16>(data, UNDERLINE_THICKNESS_OFFSET)?,
374        };
375
376        let is_monospaced = Stream::read_at::<u32>(data, IS_FIXED_PITCH_OFFSET)? != 0;
377
378        let mut names = Names::default();
379        // Only version 2.0 of the table has data at the end.
380        if version == 0x00020000 {
381            let mut s = Stream::new_at(data, TABLE_SIZE)?;
382            let count = s.read::<u16>()?;
383            names.indexes = s.read_array16::<u16>(count)?;
384            names.data = s.tail()?;
385        }
386
387        Some(Table {
388            italic_angle,
389            underline_metrics,
390            is_monospaced,
391            names,
392        })
393    }
394}