1include!("../../generated/generated_name.rs");
4use fontcull_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<fontcull_read_fonts::tables::name::NameString<'_>> for String {
156 fn from_obj_ref(obj: &fontcull_read_fonts::tables::name::NameString<'_>, _: FontData) -> Self {
157 obj.chars().collect()
158 }
159}
160
161impl FromTableRef<fontcull_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 =
225 fontcull_read_fonts::tables::name::Name::read(FontData::new(&_dumped)).unwrap();
226 assert_eq!(loaded.name_record()[0].encoding_id, 4);
227 assert_eq!(loaded.name_record()[1].name_id, NameId::new(1029));
228 assert_eq!(loaded.name_record()[2].name_id, NameId::new(1030));
229 }
230
231 #[test]
233 fn mac_str_length() {
234 let name = NameRecord::new(1, 0, 0, NameId::new(9), String::from("cé").into());
235 let mut table = Name::default();
236 table.name_record.push(name);
237 let bytes = crate::dump_table(&table).unwrap();
238 let load = fontcull_read_fonts::tables::name::Name::read(FontData::new(&bytes)).unwrap();
239
240 let data = load.name_record()[0].string(load.string_data()).unwrap();
241 assert_eq!(data.chars().collect::<String>(), "cé");
242 }
243
244 #[test]
245 fn roundtrip() {
246 init();
247
248 #[rustfmt::skip]
249 static COLINS_BESPOKE_DATA: &[u8] = &[
250 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,
277 0x0, 0x6E,
278 0x0, 0x6E, 0x0, 0x69, 0x0, 0x63, 0x0, 0x65,
280 0x0, 0x6C, 0x0, 0x69, 0x0, 0x66, 0x0, 0x65,
281 0x0, 0x69, 0x0, 0x20, 0x0, 0x68, 0x0, 0x61,
283 0x0, 0x74, 0x0, 0x65, 0x0, 0x20, 0x0, 0x66,
284 0x0, 0x6F, 0x0, 0x6E, 0x0, 0x74, 0x0, 0x73,
285 ];
286
287 let raw_table =
288 fontcull_read_fonts::tables::name::Name::read(FontData::new(COLINS_BESPOKE_DATA))
289 .unwrap();
290 let owned: Name = raw_table.to_owned_table();
291 let dumped = crate::dump_table(&owned).unwrap();
292 let reloaded =
293 fontcull_read_fonts::tables::name::Name::read(FontData::new(&dumped)).unwrap();
294
295 for rec in raw_table.name_record() {
296 let raw_str = rec.string(raw_table.string_data()).unwrap();
297 debug!("{raw_str}");
298 }
299
300 assert_eq!(raw_table.version(), reloaded.version());
301 assert_eq!(raw_table.count(), reloaded.count());
302 assert_eq!(raw_table.storage_offset(), reloaded.storage_offset());
303
304 let mut fail = false;
305 for (old, new) in raw_table
306 .name_record()
307 .iter()
308 .zip(reloaded.name_record().iter())
309 {
310 assert_eq!(old.platform_id(), new.platform_id());
311 assert_eq!(old.encoding_id(), new.encoding_id());
312 assert_eq!(old.language_id(), new.language_id());
313 assert_eq!(old.name_id(), new.name_id());
314 assert_eq!(old.length(), new.length());
315 debug!("{:?} {:?}", old.string_offset(), new.string_offset());
316 let old_str = old.string(raw_table.string_data()).unwrap();
317 let new_str = new.string(reloaded.string_data()).unwrap();
318 if old_str != new_str {
319 debug!("'{old_str}' != '{new_str}'");
320 fail = true;
321 }
322 }
323 if fail {
324 panic!("some comparisons failed");
325 }
326 }
327}