Skip to main content

write_fonts/tables/
post.rs

1//! The post table
2
3use std::collections::HashMap;
4
5include!("../../generated/generated_post.rs");
6
7//TODO: I imagine we're going to need a builder for this
8
9/// The number of standard Macintosh glyph names.
10const NUM_STANDARD: usize = 258;
11
12/// The maximum number of custom glyph names in a version 2.0 table.
13///
14/// Custom names are referenced by `u16` indices starting after the standard
15/// names.
16const MAX_CUSTOM_NAMES: usize = u16::MAX as usize + 1 - NUM_STANDARD;
17
18/// The maximum length in bytes of a custom glyph name.
19///
20/// Each name is a Pascal string, prefixed by a `u8` length.
21const MAX_NAME_LEN: usize = u8::MAX as usize;
22
23/// A string in the post table.
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
25#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
26pub struct PString(String);
27
28impl Post {
29    /// Construct a new version 2.0 table from a glyph order.
30    ///
31    /// A glyph order with more than 65278 distinct custom (non-standard) names
32    /// cannot be encoded; the resulting table will fail validation.
33    pub fn new_v2<'a>(order: impl IntoIterator<Item = &'a str>) -> Self {
34        let standard_glyphs = read_fonts::tables::post::DEFAULT_GLYPH_NAMES
35            .iter()
36            .enumerate()
37            .map(|(i, name)| (*name, i as u16))
38            .collect::<HashMap<_, _>>();
39        let mut name_index = Vec::new();
40        let mut storage = Vec::new();
41        let mut visited_names = HashMap::new();
42
43        for name in order {
44            match standard_glyphs.get(name) {
45                Some(i) => name_index.push(*i),
46                None => {
47                    let idx = match visited_names.get(name) {
48                        Some(i) => *i,
49                        None => {
50                            // Indices past u16::MAX can't be encoded. Saturate rather
51                            // than panic: the name is still stored, so validation sees
52                            // the real count in `string_data` and rejects the table.
53                            let idx = (NUM_STANDARD + storage.len())
54                                .try_into()
55                                .unwrap_or(u16::MAX);
56                            visited_names.insert(name, idx);
57                            storage.push(PString(name.into()));
58                            idx
59                        }
60                    };
61                    name_index.push(idx);
62                }
63            }
64        }
65
66        Post {
67            version: Version16Dot16::VERSION_2_0,
68            // too many glyphs is reported when validating `glyph_name_index`
69            num_glyphs: Some(name_index.len().try_into().unwrap_or(u16::MAX)),
70            glyph_name_index: Some(name_index),
71            string_data: Some(storage),
72            ..Default::default()
73        }
74    }
75
76    fn validate_string_data(&self, ctx: &mut ValidationCtx) {
77        let Some(names) = &self.string_data else {
78            return;
79        };
80        if !self.version.compatible(Version16Dot16::VERSION_2_0) {
81            return;
82        }
83        if names.len() > MAX_CUSTOM_NAMES {
84            ctx.report(format!(
85                "post table version 2.0 can hold at most {MAX_CUSTOM_NAMES} custom glyph names, found {}",
86                names.len()
87            ));
88        }
89        ctx.with_array_items(names.iter(), |ctx, name| {
90            if name.len() > MAX_NAME_LEN {
91                ctx.report(format!(
92                    "glyph name '{}' is {} bytes long, but the maximum is {MAX_NAME_LEN}",
93                    name.0,
94                    name.len()
95                ));
96            }
97        });
98    }
99}
100
101impl std::ops::Deref for PString {
102    type Target = str;
103    fn deref(&self) -> &Self::Target {
104        self.0.as_ref()
105    }
106}
107
108impl AsRef<str> for PString {
109    fn as_ref(&self) -> &str {
110        self.0.as_ref()
111    }
112}
113
114impl<'a> FromObjRef<read_fonts::tables::post::PString<'a>> for PString {
115    fn from_obj_ref(from: &read_fonts::tables::post::PString<'a>, _: FontData) -> Self {
116        PString(from.as_str().to_owned())
117    }
118}
119
120impl FontWrite for PString {
121    fn write_into(&self, writer: &mut TableWriter) {
122        let len = self.0.len() as u8;
123        len.write_into(writer);
124        self.0.as_bytes().write_into(writer);
125    }
126}
127
128impl PartialEq<&str> for PString {
129    fn eq(&self, other: &&str) -> bool {
130        self.0 == *other
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn roundtrip() {
140        use font_test_data::post as test_data;
141
142        let table = Post::read(test_data::SIMPLE.into()).unwrap();
143        let dumped = crate::dump_table(&table).unwrap();
144        assert_eq!(test_data::SIMPLE, &dumped);
145    }
146
147    #[test]
148    fn compilev2() {
149        let post = Post::new_v2([".dotdef", "A", "B", "one", "flarb", "C"]);
150        let dumped = crate::dump_table(&post).unwrap();
151        let loaded = read_fonts::tables::post::Post::read(FontData::new(&dumped)).unwrap();
152        assert_eq!(loaded.version(), Version16Dot16::VERSION_2_0);
153        assert_eq!(loaded.glyph_name(GlyphId16::new(1)), Some("A"));
154        assert_eq!(loaded.glyph_name(GlyphId16::new(4)), Some("flarb"));
155        assert_eq!(loaded.glyph_name(GlyphId16::new(5)), Some("C"));
156    }
157
158    #[test]
159    fn compilev2_with_duplicates() {
160        let post = Post::new_v2([".dotdef", "A", "flarb", "C", "A", "flarb"]);
161        let dumped = crate::dump_table(&post).unwrap();
162        let loaded = read_fonts::tables::post::Post::read(FontData::new(&dumped)).unwrap();
163
164        assert_eq!(post.num_glyphs, Some(6));
165        assert_eq!(post.glyph_name_index.as_ref().unwrap().len(), 6);
166        assert_eq!(post.glyph_name_index.as_ref().unwrap().first(), Some(&258));
167        assert_eq!(post.glyph_name_index.as_ref().unwrap().get(1), Some(&36));
168        assert_eq!(post.glyph_name_index.as_ref().unwrap().get(2), Some(&259));
169        assert_eq!(post.glyph_name_index.as_ref().unwrap().get(3), Some(&38));
170        assert_eq!(post.glyph_name_index.as_ref().unwrap().get(4), Some(&36));
171        assert_eq!(post.glyph_name_index.as_ref().unwrap().get(5), Some(&259));
172        assert_eq!(post.string_data.unwrap().len(), 2);
173
174        assert_eq!(loaded.version(), Version16Dot16::VERSION_2_0);
175        assert_eq!(loaded.num_glyphs(), Some(6));
176        assert_eq!(loaded.glyph_name(GlyphId16::new(1)), Some("A"));
177        assert_eq!(loaded.glyph_name(GlyphId16::new(2)), Some("flarb"));
178        assert_eq!(loaded.glyph_name(GlyphId16::new(3)), Some("C"));
179        assert_eq!(loaded.glyph_name(GlyphId16::new(4)), Some("A"));
180        assert_eq!(loaded.glyph_name(GlyphId16::new(5)), Some("flarb"));
181    }
182
183    #[test]
184    fn compilev2_too_many_custom_names() {
185        // one more than fits in the u16 indices that follow the 258 standard names
186        let names = (0..65_279)
187            .map(|i| format!("custom{i}"))
188            .collect::<Vec<_>>();
189        let post = Post::new_v2(names.iter().map(String::as_str));
190        let err = crate::dump_table(&post).unwrap_err().to_string();
191        assert!(
192            err.contains("can hold at most 65278 custom glyph names, found 65279"),
193            "{err}"
194        );
195    }
196
197    #[test]
198    fn compilev2_long_glyph_name() {
199        let too_long = "a".repeat(256);
200        let post = Post::new_v2([".notdef", too_long.as_str()]);
201        let err = crate::dump_table(&post).unwrap_err().to_string();
202        assert!(err.contains("is 256 bytes long"), "{err}");
203
204        let max_len = "a".repeat(255);
205        let post = Post::new_v2([".notdef", max_len.as_str()]);
206        let dumped = crate::dump_table(&post).unwrap();
207        let loaded = read_fonts::tables::post::Post::read(FontData::new(&dumped)).unwrap();
208        assert_eq!(loaded.glyph_name(GlyphId16::new(1)), Some(max_len.as_str()));
209    }
210
211    #[test]
212    fn compilev2_max_custom_names() {
213        let names = (0..65_278)
214            .map(|i| format!("custom{i}"))
215            .collect::<Vec<_>>();
216        let order = [".notdef", "space", "A"]
217            .into_iter()
218            .chain(names.iter().map(String::as_str));
219        let post = Post::new_v2(order);
220        assert_eq!(
221            post.glyph_name_index.as_ref().unwrap().last(),
222            Some(&u16::MAX)
223        );
224        let dumped = crate::dump_table(&post).unwrap();
225        let loaded = read_fonts::tables::post::Post::read(FontData::new(&dumped)).unwrap();
226
227        assert_eq!(loaded.num_glyphs(), Some(65_281));
228        assert_eq!(loaded.glyph_name(GlyphId16::new(2)), Some("A"));
229        assert_eq!(loaded.glyph_name(GlyphId16::new(3)), Some("custom0"));
230        assert_eq!(
231            loaded.glyph_name(GlyphId16::new(65_280)),
232            Some("custom65277")
233        );
234    }
235}