1
//! `post` table parsing and writing.
2

            
3
use std::str;
4

            
5
use crate::binary::read::{ReadArray, ReadBinary, ReadCtxt};
6
use crate::binary::write::{WriteBinary, WriteContext};
7
use crate::binary::{I16Be, I32Be, U16Be, U32Be, U8};
8
use crate::error::{ParseError, WriteError};
9

            
10
pub struct PostTable<'a> {
11
    pub header: Header,
12
    pub opt_sub_table: Option<SubTable<'a>>,
13
}
14

            
15
pub struct Header {
16
    pub version: i32,
17
    pub italic_angle: i32,
18
    pub underline_position: i16,
19
    pub underline_thickness: i16,
20
    pub is_fixed_pitch: u32,
21
    pub min_mem_type_42: u32,
22
    pub max_mem_type_42: u32,
23
    pub min_mem_type_1: u32,
24
    pub max_mem_type_1: u32,
25
}
26

            
27
pub struct SubTable<'a> {
28
    pub glyph_name_index: ReadArray<'a, U16Be>,
29
    pub names: Vec<PascalString<'a>>,
30
}
31

            
32
#[derive(Clone)]
33
pub struct PascalString<'a> {
34
    pub bytes: &'a [u8],
35
}
36

            
37
impl ReadBinary for Header {
38
    type HostType<'b> = Self;
39

            
40
    fn read(ctxt: &mut ReadCtxt<'_>) -> Result<Self, ParseError> {
41
        let version = ctxt.read_i32be()?;
42
        let italic_angle = ctxt.read_i32be()?;
43
        let underline_position = ctxt.read_i16be()?;
44
        let underline_thickness = ctxt.read_i16be()?;
45
        let is_fixed_pitch = ctxt.read_u32be()?;
46
        let min_mem_type_42 = ctxt.read_u32be()?;
47
        let max_mem_type_42 = ctxt.read_u32be()?;
48
        let min_mem_type_1 = ctxt.read_u32be()?;
49
        let max_mem_type_1 = ctxt.read_u32be()?;
50

            
51
        Ok(Header {
52
            version,
53
            italic_angle,
54
            underline_position,
55
            underline_thickness,
56
            is_fixed_pitch,
57
            min_mem_type_42,
58
            max_mem_type_42,
59
            min_mem_type_1,
60
            max_mem_type_1,
61
        })
62
    }
63
}
64

            
65
impl WriteBinary<&Self> for Header {
66
    type Output = ();
67

            
68
    fn write<C: WriteContext>(ctxt: &mut C, table: &Header) -> Result<(), WriteError> {
69
        I32Be::write(ctxt, table.version)?;
70
        I32Be::write(ctxt, table.italic_angle)?;
71
        I16Be::write(ctxt, table.underline_position)?;
72
        I16Be::write(ctxt, table.underline_thickness)?;
73
        U32Be::write(ctxt, table.is_fixed_pitch)?;
74
        U32Be::write(ctxt, table.min_mem_type_42)?;
75
        U32Be::write(ctxt, table.max_mem_type_42)?;
76
        U32Be::write(ctxt, table.min_mem_type_1)?;
77
        U32Be::write(ctxt, table.max_mem_type_1)?;
78

            
79
        Ok(())
80
    }
81
}
82

            
83
impl ReadBinary for PostTable<'_> {
84
    type HostType<'a> = PostTable<'a>;
85

            
86
    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
87
        let header = ctxt.read::<Header>()?;
88
        let opt_sub_table = match header.version {
89
            0x00020000 => {
90
                // May include some Format 1 glyphs
91
                let num_glyphs = ctxt.read_u16be()?;
92
                let glyph_name_index = ctxt.read_array(usize::from(num_glyphs))?;
93

            
94
                // Find the largest index used and use that to determine how many names to read
95
                let names_to_read = glyph_name_index.iter().max().map_or(0, |max| {
96
                    (usize::from(max) + 1).saturating_sub(FORMAT_1_NAMES.len())
97
                });
98

            
99
                // Read the names
100
                let mut names = Vec::with_capacity(names_to_read);
101
                for _ in 0..names_to_read {
102
                    let length = ctxt.read_u8()?;
103
                    let bytes = ctxt.read_slice(usize::from(length))?;
104
                    names.push(PascalString { bytes });
105
                }
106

            
107
                Some(SubTable {
108
                    glyph_name_index,
109
                    names,
110
                })
111
            }
112
            // TODO Handle post version 1.0, 2.5, 3.0
113
            0x00010000 | 0x00025000 | 0x00030000 => None,
114
            _ => return Err(ParseError::BadVersion),
115
        };
116

            
117
        Ok(PostTable {
118
            header,
119
            opt_sub_table,
120
        })
121
    }
122
}
123

            
124
impl<'a> WriteBinary<&Self> for PostTable<'a> {
125
    type Output = ();
126

            
127
    fn write<C: WriteContext>(ctxt: &mut C, table: &PostTable<'a>) -> Result<(), WriteError> {
128
        Header::write(ctxt, &table.header)?;
129
        if let Some(sub_table) = &table.opt_sub_table {
130
            SubTable::write(ctxt, sub_table)?;
131
        }
132

            
133
        Ok(())
134
    }
135
}
136

            
137
impl<'a> WriteBinary<&Self> for SubTable<'a> {
138
    type Output = ();
139

            
140
    fn write<C: WriteContext>(ctxt: &mut C, table: &SubTable<'a>) -> Result<(), WriteError> {
141
        let num_glyphs = u16::try_from(table.glyph_name_index.len())?;
142
        U16Be::write(ctxt, num_glyphs)?;
143
        <&ReadArray<'_, _>>::write(ctxt, &table.glyph_name_index)?;
144
        for name in &table.names {
145
            PascalString::write(ctxt, name)?;
146
        }
147

            
148
        Ok(())
149
    }
150
}
151

            
152
impl<'a> WriteBinary<&Self> for PascalString<'a> {
153
    type Output = ();
154

            
155
    fn write<C: WriteContext>(ctxt: &mut C, string: &PascalString<'a>) -> Result<(), WriteError> {
156
        if string.bytes.len() <= usize::from(u8::MAX) {
157
            // cast is safe due to check above
158
            U8::write(ctxt, string.bytes.len() as u8)?;
159
            ctxt.write_bytes(string.bytes)?;
160
            Ok(())
161
        } else {
162
            Err(WriteError::BadValue)
163
        }
164
    }
165
}
166

            
167
impl<'a> PostTable<'a> {
168
    /// Retrieve the glyph name for the supplied `glyph_index`.
169
    ///
170
    /// **Note:** Some fonts map more than one glyph to the same name so don't assume names are
171
    /// unique.
172
    pub fn glyph_name(&self, glyph_index: u16) -> Result<Option<&'a str>, ParseError> {
173
        match &self.header.version {
174
            0x00010000 => Ok(FORMAT_1_NAMES.get(usize::from(glyph_index)).copied()),
175
            0x00020000 => match &self.opt_sub_table {
176
                Some(sub_table) => {
177
                    let Some(name_index) = sub_table
178
                        .glyph_name_index
179
                        .get_item(usize::from(glyph_index))
180
                        .map(usize::from)
181
                    else {
182
                        return Ok(None);
183
                    };
184

            
185
                    match FORMAT_1_NAMES.get(name_index) {
186
                        Some(name) => Ok(Some(*name)),
187
                        None => {
188
                            let index = name_index - FORMAT_1_NAMES.len();
189
                            // NOTE: indexing is safe as we read enough names to satisfy the max index
190
                            // in glyph_name_index.
191
                            let pascal_string = &sub_table.names[index];
192
                            pascal_string.to_str().map(Some).ok_or(ParseError::BadValue)
193
                        }
194
                    }
195
                }
196
                // If the table is version 2, the sub-table should exist
197
                None => Err(ParseError::BadValue),
198
            },
199
            _ => Ok(None),
200
        }
201
    }
202
}
203

            
204
impl<'a> PascalString<'a> {
205
    /// Returns a `&str` slice if the `PascalString` is valid UTF-8.
206
    pub fn to_str(&self) -> Option<&'a str> {
207
        str::from_utf8(self.bytes).ok()
208
    }
209
}
210

            
211
static FORMAT_1_NAMES: &[&str; 258] = &[
212
    ".notdef",
213
    ".null",
214
    "nonmarkingreturn",
215
    "space",
216
    "exclam",
217
    "quotedbl",
218
    "numbersign",
219
    "dollar",
220
    "percent",
221
    "ampersand",
222
    "quotesingle",
223
    "parenleft",
224
    "parenright",
225
    "asterisk",
226
    "plus",
227
    "comma",
228
    "hyphen",
229
    "period",
230
    "slash",
231
    "zero",
232
    "one",
233
    "two",
234
    "three",
235
    "four",
236
    "five",
237
    "six",
238
    "seven",
239
    "eight",
240
    "nine",
241
    "colon",
242
    "semicolon",
243
    "less",
244
    "equal",
245
    "greater",
246
    "question",
247
    "at",
248
    "A",
249
    "B",
250
    "C",
251
    "D",
252
    "E",
253
    "F",
254
    "G",
255
    "H",
256
    "I",
257
    "J",
258
    "K",
259
    "L",
260
    "M",
261
    "N",
262
    "O",
263
    "P",
264
    "Q",
265
    "R",
266
    "S",
267
    "T",
268
    "U",
269
    "V",
270
    "W",
271
    "X",
272
    "Y",
273
    "Z",
274
    "bracketleft",
275
    "backslash",
276
    "bracketright",
277
    "asciicircum",
278
    "underscore",
279
    "grave",
280
    "a",
281
    "b",
282
    "c",
283
    "d",
284
    "e",
285
    "f",
286
    "g",
287
    "h",
288
    "i",
289
    "j",
290
    "k",
291
    "l",
292
    "m",
293
    "n",
294
    "o",
295
    "p",
296
    "q",
297
    "r",
298
    "s",
299
    "t",
300
    "u",
301
    "v",
302
    "w",
303
    "x",
304
    "y",
305
    "z",
306
    "braceleft",
307
    "bar",
308
    "braceright",
309
    "asciitilde",
310
    "Adieresis",
311
    "Aring",
312
    "Ccedilla",
313
    "Eacute",
314
    "Ntilde",
315
    "Odieresis",
316
    "Udieresis",
317
    "aacute",
318
    "agrave",
319
    "acircumflex",
320
    "adieresis",
321
    "atilde",
322
    "aring",
323
    "ccedilla",
324
    "eacute",
325
    "egrave",
326
    "ecircumflex",
327
    "edieresis",
328
    "iacute",
329
    "igrave",
330
    "icircumflex",
331
    "idieresis",
332
    "ntilde",
333
    "oacute",
334
    "ograve",
335
    "ocircumflex",
336
    "odieresis",
337
    "otilde",
338
    "uacute",
339
    "ugrave",
340
    "ucircumflex",
341
    "udieresis",
342
    "dagger",
343
    "degree",
344
    "cent",
345
    "sterling",
346
    "section",
347
    "bullet",
348
    "paragraph",
349
    "germandbls",
350
    "registered",
351
    "copyright",
352
    "trademark",
353
    "acute",
354
    "dieresis",
355
    "notequal",
356
    "AE",
357
    "Oslash",
358
    "infinity",
359
    "plusminus",
360
    "lessequal",
361
    "greaterequal",
362
    "yen",
363
    "mu",
364
    "partialdiff",
365
    "summation",
366
    "product",
367
    "pi",
368
    "integral",
369
    "ordfeminine",
370
    "ordmasculine",
371
    "Omega",
372
    "ae",
373
    "oslash",
374
    "questiondown",
375
    "exclamdown",
376
    "logicalnot",
377
    "radical",
378
    "florin",
379
    "approxequal",
380
    "Delta",
381
    "guillemotleft",
382
    "guillemotright",
383
    "ellipsis",
384
    "nonbreakingspace",
385
    "Agrave",
386
    "Atilde",
387
    "Otilde",
388
    "OE",
389
    "oe",
390
    "endash",
391
    "emdash",
392
    "quotedblleft",
393
    "quotedblright",
394
    "quoteleft",
395
    "quoteright",
396
    "divide",
397
    "lozenge",
398
    "ydieresis",
399
    "Ydieresis",
400
    "fraction",
401
    "currency",
402
    "guilsinglleft",
403
    "guilsinglright",
404
    "fi",
405
    "fl",
406
    "daggerdbl",
407
    "periodcentered",
408
    "quotesinglbase",
409
    "quotedblbase",
410
    "perthousand",
411
    "Acircumflex",
412
    "Ecircumflex",
413
    "Aacute",
414
    "Edieresis",
415
    "Egrave",
416
    "Iacute",
417
    "Icircumflex",
418
    "Idieresis",
419
    "Igrave",
420
    "Oacute",
421
    "Ocircumflex",
422
    "apple",
423
    "Ograve",
424
    "Uacute",
425
    "Ucircumflex",
426
    "Ugrave",
427
    "dotlessi",
428
    "circumflex",
429
    "tilde",
430
    "macron",
431
    "breve",
432
    "dotaccent",
433
    "ring",
434
    "cedilla",
435
    "hungarumlaut",
436
    "ogonek",
437
    "caron",
438
    "Lslash",
439
    "lslash",
440
    "Scaron",
441
    "scaron",
442
    "Zcaron",
443
    "zcaron",
444
    "brokenbar",
445
    "Eth",
446
    "eth",
447
    "Yacute",
448
    "yacute",
449
    "Thorn",
450
    "thorn",
451
    "minus",
452
    "multiply",
453
    "onesuperior",
454
    "twosuperior",
455
    "threesuperior",
456
    "onehalf",
457
    "onequarter",
458
    "threequarters",
459
    "franc",
460
    "Gbreve",
461
    "gbreve",
462
    "Idotaccent",
463
    "Scedilla",
464
    "scedilla",
465
    "Cacute",
466
    "cacute",
467
    "Ccaron",
468
    "ccaron",
469
    "dcroat",
470
];
471

            
472
#[cfg(test)]
473
mod tests {
474
    use super::*;
475
    use crate::binary::read::ReadScope;
476
    use crate::binary::write::WriteBuffer;
477

            
478
    #[test]
479
    fn duplicate_glyph_names() {
480
        // Test for post table that maps multiple glyphs to the same name index. Before a fix was
481
        // implemented this table failed to parse.
482
        let post_data = include_bytes!("../tests/fonts/opentype/post.bin");
483
        let post = ReadScope::new(post_data)
484
            .read::<PostTable<'_>>()
485
            .expect("unable to parse post table");
486
        match post.opt_sub_table {
487
            Some(ref sub_table) => assert_eq!(sub_table.names.len(), 1872),
488
            None => panic!("expected post table to have a sub-table"),
489
        }
490

            
491
        // These map to the same index (397)
492
        assert_eq!(post.glyph_name(257).unwrap().unwrap(), "Ldot");
493
        assert_eq!(post.glyph_name(1442).unwrap().unwrap(), "Ldot");
494
    }
495

            
496
    fn build_post_with_unused_names() -> Result<Vec<u8>, WriteError> {
497
        // Build a post table with unused name entries
498
        let mut w = WriteBuffer::new();
499
        let header = Header {
500
            version: 0x00020000,
501
            italic_angle: 0,
502
            underline_position: 0,
503
            underline_thickness: 0,
504
            is_fixed_pitch: 0,
505
            min_mem_type_42: 0,
506
            max_mem_type_42: 0,
507
            min_mem_type_1: 0,
508
            max_mem_type_1: 0,
509
        };
510

            
511
        Header::write(&mut w, &header)?;
512

            
513
        let num_glyphs = 10u16;
514
        U16Be::write(&mut w, num_glyphs)?;
515

            
516
        // Write name indexes that have unused names between each used entry
517
        U16Be::write(&mut w, 0u16)?; // .notdef
518
        for i in 0..(num_glyphs - 1) {
519
            U16Be::write(&mut w, i * 2 + 258)?;
520
        }
521
        // Write the names
522
        for i in 1..num_glyphs {
523
            // Write a real entry
524
            let name = format!("gid{}", i);
525
            let string = PascalString {
526
                bytes: name.as_bytes(),
527
            };
528
            PascalString::write(&mut w, &string)?;
529

            
530
            // Then the unused one in between
531
            let name = format!("unused{}", i);
532
            let string = PascalString {
533
                bytes: name.as_bytes(),
534
            };
535
            PascalString::write(&mut w, &string)?;
536
        }
537

            
538
        Ok(w.into_inner())
539
    }
540

            
541
    #[test]
542
    fn unused_glyph_names() {
543
        let post_data = build_post_with_unused_names().expect("unable to build post table");
544
        let post = ReadScope::new(&post_data)
545
            .read::<PostTable<'_>>()
546
            .expect("unable to parse post table");
547
        let num_glyphs = post.opt_sub_table.as_ref().unwrap().glyph_name_index.len() as u16;
548
        for i in 0..num_glyphs {
549
            let expected = if i == 0 {
550
                String::from(".notdef")
551
            } else {
552
                format!("gid{}", i)
553
            };
554
            assert_eq!(post.glyph_name(i).unwrap().unwrap(), &expected);
555
        }
556
    }
557
}