1include!("../../generated/generated_name.rs");
4use read_fonts::tables::name::{Encoding, MacRomanMapping};
5
6impl Name {
7 fn compute_storage_offset(&self) -> u16 {
16 let v0 = 6 + self.name_record.len() * 12;
18 if let Some(lang_tag_records) = self.lang_tag_record.as_ref() {
19 v0 + 4 * lang_tag_records.len()
20 } else {
21 v0
22 }
23 .try_into()
24 .unwrap()
25 }
26
27 fn compute_version(&self) -> u16 {
28 self.lang_tag_record.is_some().into()
29 }
30
31 fn check_sorted_and_unique_name_records(&self, ctx: &mut ValidationCtx) {
32 if self
34 .name_record
35 .windows(2)
36 .any(|window| window[0] > window[1])
37 {
38 ctx.report("name_record array must be sorted");
39 }
40 for (left, right) in self.name_record.iter().zip(self.name_record.iter().skip(1)) {
41 let left = (
42 left.platform_id,
43 left.encoding_id,
44 left.language_id,
45 left.name_id,
46 );
47 let right = (
48 right.platform_id,
49 right.encoding_id,
50 right.language_id,
51 right.name_id,
52 );
53 if left == right {
54 ctx.report(format!("duplicate entry in name_record: '{}'", left.3))
55 }
56 }
57 }
58}
59
60impl NameRecord {
61 fn string(&self) -> &str {
62 self.string.as_str()
63 }
64
65 fn compile_name_string(&self) -> NameStringAndLenWriter<'_> {
66 NameStringAndLenWriter(NameStringWriter {
67 encoding: Encoding::new(self.platform_id, self.encoding_id),
68 string: self.string(),
69 })
70 }
71
72 fn validate_string_data(&self, ctx: &mut ValidationCtx) {
73 let encoding = Encoding::new(self.platform_id, self.encoding_id);
74 match encoding {
75 Encoding::Unknown => ctx.report(format!(
76 "Unhandled platform/encoding id pair: ({}, {})",
77 self.platform_id, self.encoding_id
78 )),
79 Encoding::Utf16Be => (), Encoding::MacRoman => {
81 for c in self.string().chars() {
82 if MacRomanMapping.encode(c).is_none() {
83 ctx.report(format!(
84 "char {c} {} not representable in MacRoman encoding",
85 c.escape_unicode()
86 ))
87 }
88 }
89 }
90 }
91 }
92}
93
94impl LangTagRecord {
95 fn lang_tag(&self) -> &str {
96 self.lang_tag.as_str()
97 }
98
99 fn compile_name_string(&self) -> NameStringAndLenWriter<'_> {
100 NameStringAndLenWriter(NameStringWriter {
101 encoding: Encoding::Utf16Be,
102 string: self.lang_tag(),
103 })
104 }
105}
106
107struct NameStringAndLenWriter<'a>(NameStringWriter<'a>);
109
110struct NameStringWriter<'a> {
111 encoding: Encoding,
112 string: &'a str,
113}
114
115impl NameStringWriter<'_> {
116 fn compute_length(&self) -> u16 {
117 match self.encoding {
118 Encoding::Utf16Be => self.string.chars().map(|c| c.len_utf16() as u16 * 2).sum(),
119 Encoding::MacRoman => self.string.chars().count().try_into().unwrap(),
121 Encoding::Unknown => 0,
122 }
123 }
124}
125
126impl FontWrite for NameStringAndLenWriter<'_> {
127 fn write_into(&self, writer: &mut TableWriter) {
128 self.0.compute_length().write_into(writer);
129 writer.write_offset(&self.0, 2)
130 }
131}
132
133impl FontWrite for NameStringWriter<'_> {
134 fn write_into(&self, writer: &mut TableWriter) {
135 for c in self.string.chars() {
136 match self.encoding {
137 Encoding::Utf16Be => {
138 let mut buf = [0, 0];
139 let enc = c.encode_utf16(&mut buf);
140 enc.iter()
141 .for_each(|unit| writer.write_slice(&unit.to_be_bytes()))
142 }
143 Encoding::MacRoman => {
144 MacRomanMapping
145 .encode(c)
146 .expect("invalid char for MacRoman")
147 .write_into(writer);
148 }
149 Encoding::Unknown => panic!("unknown encoding"),
150 }
151 }
152 }
153}
154
155impl FromObjRef<read_fonts::tables::name::NameString<'_>> for String {
156 fn from_obj_ref(obj: &read_fonts::tables::name::NameString<'_>, _: FontData) -> Self {
157 obj.chars().collect()
158 }
159}
160
161impl FromTableRef<read_fonts::tables::name::NameString<'_>> for String {}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166 use env_logger;
167 use log::debug;
168
169 fn init() {
170 let _ = env_logger::builder().is_test(true).try_init();
171 }
172
173 fn make_name_record(
174 platform_id: u16,
175 encoding_id: u16,
176 language_id: u16,
177 name_id: u16,
178 name: &str,
179 ) -> NameRecord {
180 NameRecord {
181 platform_id,
182 encoding_id,
183 language_id,
184 name_id: NameId::new(name_id),
185 string: name.to_string().into(),
186 }
187 }
188
189 #[test]
190 fn encoding() {
191 let stringthing = NameStringWriter {
192 encoding: Encoding::Utf16Be,
193 string: "hello",
194 };
195 assert_eq!(stringthing.compute_length(), 10);
196 }
197
198 #[test]
199 fn compute_version() {
200 let mut table = Name::default();
201 assert_eq!(table.compute_version(), 0);
202 table.lang_tag_record = Some(Vec::new());
203 assert_eq!(table.compute_version(), 1);
204 }
205
206 #[test]
207 fn sorting() {
208 let mut table = Name::default();
209 table
210 .name_record
211 .push(make_name_record(3, 1, 0, 1030, "Ordinær"));
212 table.name_record.push(make_name_record(0, 4, 0, 4, "oh"));
213 table
214 .name_record
215 .push(make_name_record(3, 1, 0, 1029, "Regular"));
216
217 assert!(crate::dump_table(&table).is_err());
219
220 table.name_record.sort();
222
223 let _dumped = crate::dump_table(&table).unwrap();
224 let loaded = read_fonts::tables::name::Name::read(FontData::new(&_dumped)).unwrap();
225 assert_eq!(loaded.name_record()[0].encoding_id, 4);
226 assert_eq!(loaded.name_record()[1].name_id, NameId::new(1029));
227 assert_eq!(loaded.name_record()[2].name_id, NameId::new(1030));
228 }
229
230 #[test]
232 fn mac_str_length() {
233 let name = NameRecord::new(1, 0, 0, NameId::new(9), String::from("cé").into());
234 let mut table = Name::default();
235 table.name_record.push(name);
236 let bytes = crate::dump_table(&table).unwrap();
237 let load = read_fonts::tables::name::Name::read(FontData::new(&bytes)).unwrap();
238
239 let data = load.name_record()[0].string(load.string_data()).unwrap();
240 assert_eq!(data.chars().collect::<String>(), "cé");
241 }
242
243 #[test]
244 fn roundtrip() {
245 init();
246
247 #[rustfmt::skip]
248 static COLINS_BESPOKE_DATA: &[u8] = &[
249 0x0, 0x0, 0x0, 0x03, 0x0, 42, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x01, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x02, 0x00, 0x10, 0x00, 0x0a, 0x00, 0x03, 0x00, 0x01, 0x04, 0x09, 0x00, 0x03, 0x00, 0x18, 0x00, 0x1a, 0x0, 0x63, 0x0, 0x6F, 0x0, 0x6C, 0x0, 0x69,
276 0x0, 0x6E,
277 0x0, 0x6E, 0x0, 0x69, 0x0, 0x63, 0x0, 0x65,
279 0x0, 0x6C, 0x0, 0x69, 0x0, 0x66, 0x0, 0x65,
280 0x0, 0x69, 0x0, 0x20, 0x0, 0x68, 0x0, 0x61,
282 0x0, 0x74, 0x0, 0x65, 0x0, 0x20, 0x0, 0x66,
283 0x0, 0x6F, 0x0, 0x6E, 0x0, 0x74, 0x0, 0x73,
284 ];
285
286 let raw_table =
287 read_fonts::tables::name::Name::read(FontData::new(COLINS_BESPOKE_DATA)).unwrap();
288 let owned: Name = raw_table.to_owned_table();
289 let dumped = crate::dump_table(&owned).unwrap();
290 let reloaded = read_fonts::tables::name::Name::read(FontData::new(&dumped)).unwrap();
291
292 for rec in raw_table.name_record() {
293 let raw_str = rec.string(raw_table.string_data()).unwrap();
294 debug!("{raw_str}");
295 }
296
297 assert_eq!(raw_table.version(), reloaded.version());
298 assert_eq!(raw_table.count(), reloaded.count());
299 assert_eq!(raw_table.storage_offset(), reloaded.storage_offset());
300
301 let mut fail = false;
302 for (old, new) in raw_table
303 .name_record()
304 .iter()
305 .zip(reloaded.name_record().iter())
306 {
307 assert_eq!(old.platform_id(), new.platform_id());
308 assert_eq!(old.encoding_id(), new.encoding_id());
309 assert_eq!(old.language_id(), new.language_id());
310 assert_eq!(old.name_id(), new.name_id());
311 assert_eq!(old.length(), new.length());
312 debug!("{:?} {:?}", old.string_offset(), new.string_offset());
313 let old_str = old.string(raw_table.string_data()).unwrap();
314 let new_str = new.string(reloaded.string_data()).unwrap();
315 if old_str != new_str {
316 debug!("'{old_str}' != '{new_str}'");
317 fail = true;
318 }
319 }
320 if fail {
321 panic!("some comparisons failed");
322 }
323 }
324}