write_fonts/tables/
post.rs1use std::collections::HashMap;
4
5include!("../../generated/generated_post.rs");
6
7const NUM_STANDARD: usize = 258;
11
12const MAX_CUSTOM_NAMES: usize = u16::MAX as usize + 1 - NUM_STANDARD;
17
18const MAX_NAME_LEN: usize = u8::MAX as usize;
22
23#[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 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 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 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 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}