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