read_fonts/tables/
post.rs

1//! the [post (PostScript)](https://docs.microsoft.com/en-us/typography/opentype/spec/post#header) table
2
3include!("../../generated/generated_post.rs");
4
5impl<'a> Post<'a> {
6    /// The number of glyph names covered by this table
7    pub fn num_names(&self) -> usize {
8        match self.version() {
9            Version16Dot16::VERSION_1_0 => DEFAULT_GLYPH_NAMES.len(),
10            Version16Dot16::VERSION_2_0 => self.num_glyphs().unwrap() as usize,
11            _ => 0,
12        }
13    }
14
15    pub fn glyph_name(&self, glyph_id: GlyphId16) -> Option<&str> {
16        let glyph_id = glyph_id.to_u16() as usize;
17        match self.version() {
18            Version16Dot16::VERSION_1_0 => DEFAULT_GLYPH_NAMES.get(glyph_id).copied(),
19            Version16Dot16::VERSION_2_0 => {
20                let idx = self.glyph_name_index()?.get(glyph_id)?.get() as usize;
21                if idx < DEFAULT_GLYPH_NAMES.len() {
22                    return DEFAULT_GLYPH_NAMES.get(idx).copied();
23                }
24                let idx = idx - DEFAULT_GLYPH_NAMES.len();
25                match self.string_data().unwrap().get(idx) {
26                    Some(Ok(s)) => Some(s.0),
27                    _ => None,
28                }
29            }
30            _ => None,
31        }
32    }
33
34    //FIXME: how do we want to traverse this? I want to stop needing to
35    // add special cases for things...
36    #[cfg(feature = "experimental_traverse")]
37    fn traverse_string_data(&self) -> FieldType<'a> {
38        FieldType::I8(-42) // meaningless value
39    }
40}
41
42/// A string in the post table.
43///
44/// This is basically just a newtype that knows how to parse from a Pascal-style
45/// string.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub struct PString<'a>(&'a str);
48
49impl<'a> PString<'a> {
50    pub fn as_str(&self) -> &'a str {
51        self.0
52    }
53}
54
55impl std::ops::Deref for PString<'_> {
56    type Target = str;
57    fn deref(&self) -> &Self::Target {
58        self.0
59    }
60}
61
62impl PartialEq<&str> for PString<'_> {
63    fn eq(&self, other: &&str) -> bool {
64        self.0 == *other
65    }
66}
67
68impl<'a> FontRead<'a> for PString<'a> {
69    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
70        let len: u8 = data.read_at(0)?;
71        let pstring = data
72            .as_bytes()
73            .get(1..len as usize + 1)
74            .ok_or(ReadError::OutOfBounds)?;
75
76        if pstring.is_ascii() {
77            Ok(PString(std::str::from_utf8(pstring).unwrap()))
78        } else {
79            //FIXME not really sure how we want to handle this?
80            Err(ReadError::MalformedData("Must be valid ascii"))
81        }
82    }
83}
84
85impl VarSize for PString<'_> {
86    type Size = u8;
87}
88
89/// The 258 glyph names defined for Macintosh TrueType fonts
90#[rustfmt::skip]
91pub static DEFAULT_GLYPH_NAMES: [&str; 258] = [
92    ".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl", "numbersign", "dollar",
93    "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma",
94    "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven",
95    "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B",
96    "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U",
97    "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum",
98    "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n",
99    "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright",
100    "asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis",
101    "aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute",
102    "egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde",
103    "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex",
104    "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph",
105    "germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE",
106    "Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff",
107    "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae",
108    "oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal",
109    "Delta", "guillemotleft", "guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde",
110    "Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft",
111    "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency",
112    "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase",
113    "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave",
114    "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve",
115    "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve",
116    "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash",
117    "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn",
118    "thorn", "minus", "multiply", "onesuperior", "twosuperior", "threesuperior", "onehalf",
119    "onequarter", "threequarters", "franc", "Gbreve", "gbreve", "Idotaccent", "Scedilla",
120    "scedilla", "Cacute", "cacute", "Ccaron", "ccaron", "dcroat",
121];
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use font_test_data::{bebuffer::BeBuffer, post as test_data};
127
128    #[test]
129    fn test_post() {
130        let table = Post::read(test_data::SIMPLE.into()).unwrap();
131        assert_eq!(table.version(), Version16Dot16::VERSION_2_0);
132        assert_eq!(table.underline_position(), FWord::new(-75));
133        assert_eq!(table.glyph_name(GlyphId16::new(1)), Some(".notdef"));
134        assert_eq!(table.glyph_name(GlyphId16::new(2)), Some("space"));
135        assert_eq!(table.glyph_name(GlyphId16::new(7)), Some("hello"));
136        assert_eq!(table.glyph_name(GlyphId16::new(8)), Some("hi"));
137        assert_eq!(table.glyph_name(GlyphId16::new(9)), Some("hola"));
138    }
139
140    #[test]
141    fn parse_versioned_fields() {
142        fn make_basic_post(version: Version16Dot16) -> BeBuffer {
143            BeBuffer::new()
144                .push(version)
145                .push(Fixed::from_i32(5))
146                .extend([FWord::new(6), FWord::new(7)]) //underline pos/thickness
147                .push(0u32) // isFixedPitch
148                .extend([7u32, 8, 9, 10]) // min/max mem x
149        }
150
151        // basic table should not parse in v2.0, because that adds another field:
152        let buf = make_basic_post(Version16Dot16::VERSION_2_0);
153        assert!(Post::read(buf.data().into()).is_err());
154
155        // but it should be fine on version 3.0, which does not require any extra fields:
156        let buf = make_basic_post(Version16Dot16::VERSION_3_0);
157        assert!(Post::read(buf.data().into()).is_ok());
158    }
159}