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 glyph_indexes: LazyArray16<'a, u16>,
329 names_data: &'a [u8],
330}
331
332impl<'a> Table<'a> {
333 /// Parses a table from raw data.
334 pub fn parse(data: &'a [u8]) -> Option<Self> {
335 // Do not check the exact length, because some fonts include
336 // padding in table's length in table records, which is incorrect.
337 if data.len() < 32 {
338 return None;
339 }
340
341 let version = Stream::new(data).read::<u32>()?;
342 if !(version == 0x00010000
343 || version == 0x00020000
344 || version == 0x00025000
345 || version == 0x00030000
346 || version == 0x00040000)
347 {
348 return None;
349 }
350
351 let italic_angle = Stream::read_at::<Fixed>(data, ITALIC_ANGLE_OFFSET)?.0;
352
353 let underline_metrics = LineMetrics {
354 position: Stream::read_at::<i16>(data, UNDERLINE_POSITION_OFFSET)?,
355 thickness: Stream::read_at::<i16>(data, UNDERLINE_THICKNESS_OFFSET)?,
356 };
357
358 let is_monospaced = Stream::read_at::<u32>(data, IS_FIXED_PITCH_OFFSET)? != 0;
359
360 let mut names_data: &[u8] = &[];
361 let mut glyph_indexes = LazyArray16::default();
362 // Only version 2.0 of the table has data at the end.
363 if version == 0x00020000 {
364 let mut s = Stream::new_at(data, 32)?;
365 let indexes_count = s.read::<u16>()?;
366 glyph_indexes = s.read_array16::<u16>(indexes_count)?;
367 names_data = s.tail()?;
368 }
369
370 Some(Table {
371 italic_angle,
372 underline_metrics,
373 is_monospaced,
374 names_data,
375 glyph_indexes,
376 })
377 }
378
379 /// Returns a glyph name by ID.
380 #[cfg(feature = "glyph-names")]
381 pub fn glyph_name(&self, glyph_id: GlyphId) -> Option<&'a str> {
382 let mut index = self.glyph_indexes.get(glyph_id.0)?;
383
384 // 'If the name index is between 0 and 257, treat the name index
385 // as a glyph index in the Macintosh standard order.'
386 if usize::from(index) < MACINTOSH_NAMES.len() {
387 Some(MACINTOSH_NAMES[usize::from(index)])
388 } else {
389 // 'If the name index is between 258 and 65535, then subtract 258 and use that
390 // to index into the list of Pascal strings at the end of the table.'
391 index -= MACINTOSH_NAMES.len() as u16;
392 self.names().nth(usize::from(index))
393 }
394 }
395
396 /// Returns a glyph ID by a name.
397 #[cfg(feature = "glyph-names")]
398 pub fn glyph_index_by_name(&self, name: &str) -> Option<GlyphId> {
399 let id = if let Some(index) = MACINTOSH_NAMES.iter().position(|n| *n == name) {
400 self.glyph_indexes
401 .into_iter()
402 .position(|i| usize::from(i) == index)?
403 } else {
404 let mut index = self.names().position(|n| n == name)?;
405 index += MACINTOSH_NAMES.len();
406 self.glyph_indexes
407 .into_iter()
408 .position(|i| usize::from(i) == index)?
409 };
410
411 Some(GlyphId(id as u16))
412 }
413
414 /// Returns an iterator over glyph names.
415 ///
416 /// Default/predefined names are not included. Just the one in the font file.
417 pub fn names(&self) -> Names<'a> {
418 Names {
419 data: self.names_data,
420 offset: 0,
421 }
422 }
423}
424