read-fonts 0.46.0

Reading OpenType font files.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
//! the [post (PostScript)](https://docs.microsoft.com/en-us/typography/opentype/spec/post#header) table

include!("../../generated/generated_post.rs");

impl<'a> Post<'a> {
    /// The number of glyph names covered by this table
    pub fn num_names(&self) -> usize {
        match self.version() {
            Version16Dot16::VERSION_1_0 => DEFAULT_GLYPH_NAMES.len(),
            Version16Dot16::VERSION_2_0 => self.num_glyphs().unwrap_or_default() as usize,
            _ => 0,
        }
    }

    /// Returns the name for the given glyph.
    ///
    /// Note that this is a relatively expensive operation, as it may require
    /// a linear scan through the string data to find the target name. If you
    /// need to iterate over all glyph names or collect them into a map for
    /// faster access, use [`Self::glyph_names`] instead.
    pub fn glyph_name(&self, glyph_id: GlyphId16) -> Option<&'a str> {
        let glyph_id = glyph_id.to_u16() as usize;
        match self.version() {
            Version16Dot16::VERSION_1_0 => DEFAULT_GLYPH_NAMES.get(glyph_id).copied(),
            Version16Dot16::VERSION_2_0 => {
                let idx = self.glyph_name_index()?.get(glyph_id)?.get() as usize;
                if idx < DEFAULT_GLYPH_NAMES.len() {
                    return DEFAULT_GLYPH_NAMES.get(idx).copied();
                }
                let idx = idx - DEFAULT_GLYPH_NAMES.len();
                self.string_data()?.get(idx)?.ok().map(|s| s.0)
            }
            _ => None,
        }
    }

    /// Return an iterator over the glyph names in this table.
    pub fn glyph_names(&self) -> GlyphNames<'a> {
        let num_names = self.num_names() as u32;
        let kind = match self.version() {
            Version16Dot16::VERSION_1_0 => GlyphNameIterKind::V1(self.clone(), 0),
            Version16Dot16::VERSION_2_0 => GlyphNameIterKind::V2 {
                post: self.clone(),
                idx: 0,
                checkpoint_stride: (num_names as usize).div_ceil(NUM_CHECKPOINTS + 1).max(1),
                checkpoints: [UNSET_CHECKPOINT; NUM_CHECKPOINTS],
                last_actual_idx: None,
                last_offset: 0,
                strings_end: usize::MAX,
            },
            _ => GlyphNameIterKind::None,
        };
        GlyphNames { num_names, kind }
    }

    //FIXME: how do we want to traverse this? I want to stop needing to
    // add special cases for things...
}

/// A string in the post table.
///
/// This is basically just a newtype that knows how to parse from a Pascal-style
/// string.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PString<'a>(&'a str);

impl<'a> PString<'a> {
    pub fn as_str(&self) -> &'a str {
        self.0
    }
}

impl std::ops::Deref for PString<'_> {
    type Target = str;
    fn deref(&self) -> &Self::Target {
        self.0
    }
}

impl PartialEq<&str> for PString<'_> {
    fn eq(&self, other: &&str) -> bool {
        self.0 == *other
    }
}

impl ReadArgs for PString<'_> {
    type Args = ();
}

impl<'a> FontRead<'a> for PString<'a> {
    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
        let len: u8 = data.read_at(0)?;
        let pstring = data
            .as_bytes()
            .get(1..len as usize + 1)
            .ok_or(ReadError::OutOfBounds)?;

        if pstring.is_ascii() {
            Ok(PString(std::str::from_utf8(pstring).unwrap()))
        } else {
            //FIXME not really sure how we want to handle this?
            Err(ReadError::MalformedData("Must be valid ascii"))
        }
    }
}

impl VarSize for PString<'_> {
    type Size = u8;
}

const NUM_CHECKPOINTS: usize = 16;
const UNSET_CHECKPOINT: u32 = u32::MAX;

/// Returns the array slot that holds the offset of `index`, if `index` is one
/// of the checkpoints we keep.
///
/// Slot `n` is read back as the start of index `(n + 1) * stride`, so an index
/// past the last checkpoint has to be dropped rather than clamped into it.
fn checkpoint_slot(index: usize, stride: usize) -> Option<usize> {
    (index % stride == 0)
        .then(|| index / stride)
        .filter(|slot| (1..=NUM_CHECKPOINTS).contains(slot))
        .map(|slot| slot - 1)
}

/// Iterator over the glyph names in a post table.
#[derive(Clone)]
pub struct GlyphNames<'a> {
    num_names: u32,
    kind: GlyphNameIterKind<'a>,
}

#[derive(Clone)]
enum GlyphNameIterKind<'a> {
    None,
    V1(Post<'a>, u32),
    V2 {
        post: Post<'a>,
        idx: u32,
        // The number of indices between checkpoints
        checkpoint_stride: usize,
        // The offset of each checkpoint; checkpoint 0 is implicit
        checkpoints: [u32; NUM_CHECKPOINTS],
        // The last actual index and that was scanned, for monotonic fast path
        last_actual_idx: Option<usize>,
        // The offset associated with the last scanned index
        last_offset: usize,
        // The first string index the data cannot reach. Strings are read in
        // order, so no later one can be read either.
        strings_end: usize,
    },
}

impl<'a> Iterator for GlyphNames<'a> {
    type Item = (GlyphId, &'a str);

    /// Returns the next glyph that has a readable name.
    ///
    /// Glyphs whose names cannot be read are skipped, so the glyph ids can
    /// have gaps.
    fn next(&mut self) -> Option<Self::Item> {
        loop {
            let (gid, name) = self.next_entry()?;
            if let Some(name) = name {
                return Some((gid.into(), name));
            }
        }
    }
}

impl<'a> GlyphNames<'a> {
    /// Returns the next glyph and its name, or `None` if there are no more
    /// glyphs.
    fn next_entry(&mut self) -> Option<(GlyphId16, Option<&'a str>)> {
        match &mut self.kind {
            GlyphNameIterKind::None => None,
            GlyphNameIterKind::V1(post, idx) => {
                if *idx >= self.num_names {
                    return None;
                }
                let gid = GlyphId16::new(*idx as u16);
                *idx += 1;
                Some((gid, post.glyph_name(gid)))
            }
            GlyphNameIterKind::V2 {
                post,
                idx,
                checkpoint_stride,
                checkpoints,
                last_actual_idx,
                last_offset,
                strings_end,
            } => {
                if *idx >= self.num_names {
                    return None;
                }
                let stride = *checkpoint_stride;
                let gid = GlyphId16::new(*idx as u16);
                let index = *idx as usize;
                *idx += 1;
                let mut name = || -> Option<&'a str> {
                    let mut actual_idx = post.glyph_name_index()?.get(index)?.get() as usize;
                    Some(if actual_idx < DEFAULT_GLYPH_NAMES.len() {
                        DEFAULT_GLYPH_NAMES.get(actual_idx).copied()?
                    } else {
                        actual_idx -= DEFAULT_GLYPH_NAMES.len();
                        if actual_idx >= *strings_end {
                            return None;
                        }
                        let string_data =
                            post.offset_data().slice(post.string_data_byte_range())?;
                        // Checkpoint 0 is implicit and always at offset 0; the
                        // array stores logical checkpoints 1..=NUM_CHECKPOINTS.
                        let target_slot = (actual_idx / stride).min(NUM_CHECKPOINTS);
                        // Find the the starting location for our scan
                        let (mut scan_idx, mut offset) = {
                            // Search backward from the target slot to find the
                            // nearest checkpoint that has been set
                            let mut slot = target_slot;
                            while slot > 0 && checkpoints[slot - 1] == UNSET_CHECKPOINT {
                                slot -= 1;
                            }
                            if slot == 0 {
                                // Fallback to implicit checkpoint 0
                                (0, 0)
                            } else {
                                // Otherwise, start scanning from the nearest
                                // checkpoint
                                (slot * stride, checkpoints[slot - 1] as usize)
                            }
                        };
                        // See if we can use the monotonic fast path.
                        if let Some(last_idx) = *last_actual_idx {
                            // Start scanning from the last index if that provides
                            // a smaller search space than the nearest checkpoint
                            if last_idx <= actual_idx && last_idx > scan_idx {
                                scan_idx = last_idx;
                                offset = *last_offset;
                            }
                        }
                        // Now do the linear scan over the string data
                        while scan_idx < actual_idx {
                            let Some(next) = PString::read_len_at(string_data, offset)
                                .and_then(|len| offset.checked_add(len))
                            else {
                                *strings_end = scan_idx;
                                return None;
                            };
                            offset = next;
                            scan_idx += 1;
                            // If this index is a checkpoint, record the offset
                            // for future scans
                            if let Some(slot) = checkpoint_slot(scan_idx, stride) {
                                checkpoints[slot] = u32::try_from(offset).ok()?;
                            }
                        }
                        if let Some(slot) = checkpoint_slot(actual_idx, stride) {
                            checkpoints[slot] = u32::try_from(offset).ok()?;
                        }
                        // Record the last index and offset for future scans
                        *last_actual_idx = Some(actual_idx);
                        *last_offset = offset;
                        PString::read(string_data.split_off(offset)?).ok()?.0
                    })
                };
                Some((gid, name()))
            }
        }
    }
}

/// The 258 glyph names defined for Macintosh TrueType fonts
#[rustfmt::skip]
pub static DEFAULT_GLYPH_NAMES: [&str; 258] = [
    ".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl", "numbersign", "dollar",
    "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma",
    "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven",
    "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B",
    "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
    "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
    "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
    "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright",
    "asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis",
    "aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute",
    "egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde",
    "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex",
    "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph",
    "germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE",
    "Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff",
    "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae",
    "oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal",
    "Delta", "guillemotleft", "guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde",
    "Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft",
    "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency",
    "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase",
    "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave",
    "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve",
    "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve",
    "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash",
    "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn",
    "thorn", "minus", "multiply", "onesuperior", "twosuperior", "threesuperior", "onehalf",
    "onequarter", "threequarters", "franc", "Gbreve", "gbreve", "Idotaccent", "Scedilla",
    "scedilla", "Cacute", "cacute", "Ccaron", "ccaron", "dcroat",
];

#[cfg(test)]
mod tests {
    use super::*;
    use font_test_data::{bebuffer::BeBuffer, post as test_data};

    #[test]
    fn test_post() {
        let table = Post::read(test_data::SIMPLE.into()).unwrap();
        assert_eq!(table.version(), Version16Dot16::VERSION_2_0);
        assert_eq!(table.underline_position(), FWord::new(-75));
        assert_eq!(table.glyph_name(GlyphId16::new(1)), Some(".notdef"));
        assert_eq!(table.glyph_name(GlyphId16::new(2)), Some("space"));
        assert_eq!(table.glyph_name(GlyphId16::new(7)), Some("hello"));
        assert_eq!(table.glyph_name(GlyphId16::new(8)), Some("hi"));
        assert_eq!(table.glyph_name(GlyphId16::new(9)), Some("hola"));
    }

    fn make_basic_post(version: Version16Dot16, include_num_glyphs: bool) -> BeBuffer {
        let buf = BeBuffer::new()
            .push(version)
            .push(Fixed::from_i32(5))
            .extend([FWord::new(6), FWord::new(7)]) //underline pos/thickness
            .push(0u32) // isFixedPitch
            .extend([7u32, 8, 9, 10]); // min/max mem x
        if include_num_glyphs {
            buf.push(0u16)
        } else {
            buf
        }
    }

    #[test]
    fn parse_versioned_fields_v1() {
        // v1, even if it has the extra field will not read it:

        let buf = make_basic_post(Version16Dot16::VERSION_1_0, true);
        let postv1 = Post::read(buf.data().into()).unwrap();
        assert!(postv1.num_glyphs().is_none());
    }

    #[test]
    fn parse_versioned_fields_v2() {
        let buf = make_basic_post(Version16Dot16::VERSION_2_0, false);
        let postv2 = Post::read(buf.data().into()).unwrap();
        // v2 will fail to read if data is missing
        assert!(postv2.num_glyphs().is_none());

        // but read if data is present
        let buf = make_basic_post(Version16Dot16::VERSION_2_0, true);
        let postv2 = Post::read(buf.data().into()).unwrap();
        // v2 will fail to read if data is missing
        assert_eq!(postv2.num_glyphs(), Some(0));
    }

    #[test]
    fn parse_versioned_fields_v3() {
        // v3 will again not read since this field is not compatible
        let buf = make_basic_post(Version16Dot16::VERSION_3_0, true);
        let postv3 = Post::read(buf.data().into()).unwrap();
        assert!(postv3.num_glyphs().is_none());
    }

    #[test]
    fn num_names_defaults_to_zero_without_num_glyphs() {
        let buf = make_basic_post(Version16Dot16::VERSION_2_0, false);
        let post = Post::read(buf.data().into()).unwrap();
        // Just don't panic
        assert_eq!(post.num_names(), 0);
    }

    #[test]
    fn glyph_name_missing_string_data_returns_none() {
        let buf = BeBuffer::new()
            .push(Version16Dot16::VERSION_2_0)
            .push(Fixed::from_i32(5))
            .extend([FWord::new(6), FWord::new(7)])
            .push(0u32)
            .extend([7u32, 8, 9, 10])
            .push(1u16)
            .push(258u16);
        let post = Post::read(buf.data().into()).unwrap();
        // Just don't panic
        assert_eq!(post.glyph_name(GlyphId16::new(0)), None);
    }

    #[test]
    fn glyph_names_matches_naive_on_varied_synthetic_v2_data() {
        let num_glyphs = 2_000u16;
        let orders = [
            test_data::GlyphNameOrder::Monotonic,
            test_data::GlyphNameOrder::MostlyMonotonicWithBackrefs,
            test_data::GlyphNameOrder::AllPointToLast,
        ];
        for order in orders {
            let (bytes, expected_custom_names) =
                test_data::v2_with_varied_glyph_names(num_glyphs, 63, order);
            let post = Post::read(bytes.as_slice().into()).unwrap();
            let from_naive: Vec<_> = (0..num_glyphs)
                .map(|gid| post.glyph_name(GlyphId16::new(gid)).unwrap())
                .collect();
            let from_iter: Vec<_> = post.glyph_names().map(|(_, name)| name).collect();
            assert_eq!(from_iter, from_naive);
            for (name, expected) in from_iter.iter().zip(expected_custom_names.iter()) {
                assert_eq!(*name, expected.as_str());
            }
        }
    }

    #[test]
    fn glyph_names_skip_a_name_that_cannot_be_read() {
        let mut buf = BeBuffer::new()
            .push(Version16Dot16::VERSION_2_0)
            .push(Fixed::from_i32(0))
            .extend([FWord::new(0), FWord::new(0)])
            .push(0u32)
            .extend([0u32, 0, 0, 0])
            .push(3u16);
        // The second glyph names a string the table does not have.
        for index in [258u16, 258 + 9, 3] {
            buf = buf.push(index);
        }
        buf = buf.push(2u8).extend(*b"ok");
        let post = Post::read(buf.data().into()).unwrap();
        assert_eq!(post.glyph_name(GlyphId16::new(1)), None);
        let names: Vec<_> = post.glyph_names().collect();
        assert_eq!(names, [(GlyphId::new(0), "ok"), (GlyphId::new(2), "space")]);
    }

    // Glyphs whose names lie past the end of the string data fail at once,
    // rather than each rescanning the strings that are there.
    #[test]
    fn glyph_names_past_the_string_data_stay_linear() {
        let num_glyphs = u16::MAX;
        let mut buf = BeBuffer::new()
            .push(Version16Dot16::VERSION_2_0)
            .push(Fixed::from_i32(0))
            .extend([FWord::new(0), FWord::new(0)])
            .push(0u32)
            .extend([0u32, 0, 0, 0])
            .push(num_glyphs);
        // The first two glyphs have names; every other glyph names a string
        // far past the end of the data.
        for gid in 0..num_glyphs {
            let index = if gid < 2 { 258 + gid } else { 258 + 60_000 };
            buf = buf.push(index);
        }
        for _ in 0..3854 {
            buf = buf.push(2u8).extend(*b"ab");
        }
        let post = Post::read(buf.data().into()).unwrap();
        let start = std::time::Instant::now();
        let names: Vec<_> = post.glyph_names().collect();
        let elapsed = start.elapsed();
        assert_eq!(names, [(GlyphId::new(0), "ab"), (GlyphId::new(1), "ab")]);
        // Linear work is a few milliseconds even in a debug build.
        assert!(elapsed.as_millis() < 2000, "{elapsed:?}");
    }

    /// A post table may hold more strings than it has glyphs, which puts a
    /// name index past the last checkpoint the iterator keeps. Clamping such
    /// an index into the final slot makes a later scan resume from the wrong
    /// offset.
    #[test]
    fn glyph_names_matches_naive_past_the_last_checkpoint() {
        // Two glyphs give a stride of one, so only the first NUM_CHECKPOINTS
        // string indices get a checkpoint of their own. The higher index is
        // read first, so the clamped slot is written before it is read back.
        let indices = [258u16 + 50, 258 + 30];
        let mut buf = BeBuffer::new()
            .push(Version16Dot16::VERSION_2_0)
            .push(Fixed::from_i32(0))
            .extend([FWord::new(0), FWord::new(0)])
            .push(0u32)
            .extend([0u32, 0, 0, 0])
            .push(indices.len() as u16);
        for index in indices {
            buf = buf.push(index);
        }
        for i in 0..70u8 {
            buf = buf.push(2u8).extend([b'a' + i / 26, b'a' + i % 26]);
        }
        let post = Post::read(buf.data().into()).unwrap();
        let from_naive: Vec<_> = (0..indices.len() as u16)
            .map(|gid| post.glyph_name(GlyphId16::new(gid)).unwrap())
            .collect();
        assert_eq!(from_naive, ["by", "be"]);
        let from_iter: Vec<_> = post.glyph_names().map(|(_, name)| name).collect();
        assert_eq!(from_iter, from_naive);
    }
}