1//! A [PostScript Table](
2//! https://docs.microsoft.com/en-us/typography/opentype/spec/post) implementation.
3
4use crate::parser::{Fixed, LazyArray16, Stream};
5#[cfg(feature = "glyph-names")]
6use crate::GlyphId;
7use crate::LineMetrics;
8
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/// 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)]
283pub struct Names<'a> {
284 data: &'a [u8],
285 offset: usize,
286}
287
288impl core::fmt::Debug for Names<'_> {
289 fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
290 write!(f, "Names {{ ... }}")
291 }
292}
293
294impl<'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)]
321pub 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
333impl<'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