1use crate::{string::StringId, FontRef, MetadataProvider, Tag};
5
6pub(super) fn require_interpreter(font: &FontRef) -> bool {
7 is_hint_reliant_by_name(font) || matches_hint_reliant_id_list(FontId::from_font(font))
8}
9
10fn is_hint_reliant_by_name(font: &FontRef) -> bool {
11 font.localized_strings(StringId::FAMILY_NAME)
12 .english_or_first()
13 .map(|name| {
14 let mut buf = [0u8; MAX_HINT_RELIANT_NAME_LEN];
15 let mut len = 0;
16 let mut chars = name.chars();
17 for ch in chars.by_ref().take(MAX_HINT_RELIANT_NAME_LEN) {
18 buf[len] = ch as u8;
19 len += 1;
20 }
21 if chars.next().is_some() {
22 return false;
23 }
24 matches_hint_reliant_name_list(core::str::from_utf8(&buf[..len]).unwrap_or_default())
25 })
26 .unwrap_or_default()
27}
28
29fn matches_hint_reliant_name_list(name: &str) -> bool {
33 let name = skip_pdf_random_tag(name);
34 HINT_RELIANT_NAMES
35 .iter()
36 .any(|tricky_name| name.contains(*tricky_name))
39}
40
41fn skip_pdf_random_tag(name: &str) -> &str {
46 let bytes = name.as_bytes();
47 if bytes.len() < 8 || bytes[6] != b'+' || !bytes.iter().take(6).all(|b| b.is_ascii_uppercase())
49 {
50 return name;
51 }
52 core::str::from_utf8(&bytes[7..]).unwrap_or(name)
53}
54
55#[rustfmt::skip]
57const HINT_RELIANT_NAMES: &[&str] = &[
58 "cpop", "DFGirl-W6-WIN-BF", "DFGothic-EB", "DFGyoSho-Lt", "DFHei", "DFHSGothic-W5", "DFHSMincho-W3", "DFHSMincho-W7", "DFKaiSho-SB", "DFKaiShu", "DFKai-SB", "DFMing", "DLC", "HuaTianKaiTi?", "HuaTianSongTi?", "Ming(for ISO10646)", "MingLiU", "MingMedium", "PMingLiU", "MingLi43", ];
94
95const MAX_HINT_RELIANT_NAME_LEN: usize = 18;
96
97#[derive(Copy, Clone, PartialEq, Default, Debug)]
98struct TableId {
99 checksum: u32,
100 len: u32,
101}
102
103impl TableId {
104 fn from_font_and_tag(font: &FontRef, tag: Tag) -> Option<Self> {
105 let data = font.table_data(tag)?;
106 Some(Self {
107 checksum: raw::tables::compute_checksum(data.as_bytes()),
110 len: data.len() as u32,
111 })
112 }
113}
114
115#[derive(Copy, Clone, PartialEq, Default, Debug)]
116struct FontId {
117 cvt: TableId,
118 fpgm: TableId,
119 prep: TableId,
120}
121
122impl FontId {
123 fn from_font(font: &FontRef) -> Self {
124 Self {
125 cvt: TableId::from_font_and_tag(font, Tag::new(b"cvt ")).unwrap_or_default(),
126 fpgm: TableId::from_font_and_tag(font, Tag::new(b"fpgm")).unwrap_or_default(),
127 prep: TableId::from_font_and_tag(font, Tag::new(b"prep")).unwrap_or_default(),
128 }
129 }
130}
131
132fn matches_hint_reliant_id_list(font_id: FontId) -> bool {
137 HINT_RELIANT_IDS.contains(&font_id)
138}
139
140#[rustfmt::skip]
142const HINT_RELIANT_IDS: &[FontId] = &[
143 FontId {
145 cvt: TableId { checksum: 0x05BCF058, len: 0x000002E4 },
146 fpgm: TableId { checksum: 0x28233BF1, len: 0x000087C4 },
147 prep: TableId { checksum: 0xA344A1EA, len: 0x000001E1 },
148 },
149 FontId {
151 cvt: TableId { checksum: 0x05BCF058, len: 0x000002E4 },
152 fpgm: TableId { checksum: 0x28233BF1, len: 0x000087C4 },
153 prep: TableId { checksum: 0xA344A1EB, len: 0x000001E1 },
154 },
155 FontId {
157 cvt: TableId { checksum: 0x12C3EBB2, len: 0x00000350 },
158 fpgm: TableId { checksum: 0xB680EE64, len: 0x000087A7 },
159 prep: TableId { checksum: 0xCE939563, len: 0x00000758 },
160 },
161 FontId {
163 cvt: TableId { checksum: 0x11E5EAD4, len: 0x00000350 },
164 fpgm: TableId { checksum: 0xCE5956E9, len: 0x0000BC85 },
165 prep: TableId { checksum: 0x8272F416, len: 0x00000045 },
166 },
167 FontId {
169 cvt: TableId { checksum: 0x1257EB46, len: 0x00000350 },
170 fpgm: TableId { checksum: 0xF699D160, len: 0x0000715F },
171 prep: TableId { checksum: 0xD222F568, len: 0x000003BC },
172 },
173 FontId {
175 cvt: TableId { checksum: 0x1262EB4E, len: 0x00000350 },
176 fpgm: TableId { checksum: 0xE86A5D64, len: 0x00007940 },
177 prep: TableId { checksum: 0x7850F729, len: 0x000005FF },
178 },
179 FontId {
181 cvt: TableId { checksum: 0x122DEB0A, len: 0x00000350 },
182 fpgm: TableId { checksum: 0x3D16328A, len: 0x0000859B },
183 prep: TableId { checksum: 0xA93FC33B, len: 0x000002CB },
184 },
185 FontId {
187 cvt: TableId { checksum: 0x125FEB26, len: 0x00000350 },
188 fpgm: TableId { checksum: 0xA5ACC982, len: 0x00007EE1 },
189 prep: TableId { checksum: 0x90999196, len: 0x0000041F },
190 },
191 FontId {
193 cvt: TableId { checksum: 0x11E5EAD4, len: 0x00000350 },
194 fpgm: TableId { checksum: 0x5A30CA3B, len: 0x00009063 },
195 prep: TableId { checksum: 0x13A42602, len: 0x0000007E },
196 },
197 FontId {
199 cvt: TableId { checksum: 0x11E5EAD4, len: 0x00000350 },
200 fpgm: TableId { checksum: 0xA6E78C01, len: 0x00008998 },
201 prep: TableId { checksum: 0x13A42602, len: 0x0000007E },
202 },
203 FontId {
205 cvt: TableId { checksum: 0x11E5EAD4, len: 0x00000360 },
206 fpgm: TableId { checksum: 0x9DB282B2, len: 0x0000C06E },
207 prep: TableId { checksum: 0x53E6D7CA, len: 0x00000082 },
208 },
209 FontId {
211 cvt: TableId { checksum: 0x1243EB18, len: 0x00000350 },
212 fpgm: TableId { checksum: 0xBA0A8C30, len: 0x000074AD },
213 prep: TableId { checksum: 0xF3D83409, len: 0x0000037B },
214 },
215 FontId {
217 cvt: TableId { checksum: 0x07DCF546, len: 0x00000308 },
218 fpgm: TableId { checksum: 0x40FE7C90, len: 0x00008E2A },
219 prep: TableId { checksum: 0x608174B5, len: 0x0000007A },
220 },
221 FontId {
223 cvt: TableId { checksum: 0xEB891238, len: 0x00000308 },
224 fpgm: TableId { checksum: 0xD2E4DCD4, len: 0x0000676F },
225 prep: TableId { checksum: 0x8EA5F293, len: 0x000003B8 },
226 },
227 FontId {
229 cvt: TableId { checksum: 0xFFFBFFFC, len: 0x00000008 },
230 fpgm: TableId { checksum: 0x9C9E48B8, len: 0x0000BEA2 },
231 prep: TableId { checksum: 0x70020112, len: 0x00000008 },
232 },
233 FontId {
235 cvt: TableId { checksum: 0xFFFBFFFC, len: 0x00000008 },
236 fpgm: TableId { checksum: 0x0A5A0483, len: 0x00017C39 },
237 prep: TableId { checksum: 0x70020112, len: 0x00000008 },
238 },
239 FontId {
241 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
242 fpgm: TableId { checksum: 0x40C92555, len: 0x000000E5 },
243 prep: TableId { checksum: 0xA39B58E3, len: 0x0000117C },
244 },
245 FontId {
247 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
248 fpgm: TableId { checksum: 0x33C41652, len: 0x000000E5 },
249 prep: TableId { checksum: 0x26D6C52A, len: 0x00000F6A },
250 },
251 FontId {
253 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
254 fpgm: TableId { checksum: 0x6DB1651D, len: 0x0000019D },
255 prep: TableId { checksum: 0x6C6E4B03, len: 0x00002492 },
256 },
257 FontId {
259 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
260 fpgm: TableId { checksum: 0x40C92555, len: 0x000000E5 },
261 prep: TableId { checksum: 0xDE51FAD0, len: 0x0000117C },
262 },
263 FontId {
265 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
266 fpgm: TableId { checksum: 0x85E47664, len: 0x000000E5 },
267 prep: TableId { checksum: 0xA6C62831, len: 0x00001CAA },
268 },
269 FontId {
271 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
272 fpgm: TableId { checksum: 0x2D891CFD, len: 0x0000019D },
273 prep: TableId { checksum: 0xA0604633, len: 0x00001DE8 },
274 },
275 FontId {
277 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
278 fpgm: TableId { checksum: 0x40AA774C, len: 0x000001CB },
279 prep: TableId { checksum: 0x9B5CAA96, len: 0x00001F9A },
280 },
281 FontId {
283 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
284 fpgm: TableId { checksum: 0x0D3DE9CB, len: 0x00000141 },
285 prep: TableId { checksum: 0xD4127766, len: 0x00002280 },
286 },
287 FontId {
289 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
290 fpgm: TableId { checksum: 0x4A692698, len: 0x000001F0 },
291 prep: TableId { checksum: 0x340D4346, len: 0x00001FCA },
292 },
293 FontId {
295 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
296 fpgm: TableId { checksum: 0xCD34C604, len: 0x00000166 },
297 prep: TableId { checksum: 0x6CF31046, len: 0x000022B0 },
298 },
299 FontId {
301 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
302 fpgm: TableId { checksum: 0x5DA75315, len: 0x0000019D },
303 prep: TableId { checksum: 0x40745A5F, len: 0x000022E0 },
304 },
305 FontId {
307 cvt: TableId { checksum: 0x00000000, len: 0x00000000 },
308 fpgm: TableId { checksum: 0xF055FC48, len: 0x000001C2 },
309 prep: TableId { checksum: 0x3900DED3, len: 0x00001E18 },
310 },
311 FontId {
313 cvt: TableId { checksum: 0x00170003, len: 0x00000060 },
314 fpgm: TableId { checksum: 0xDBB4306E, len: 0x000058AA },
315 prep: TableId { checksum: 0xD643482A, len: 0x00000035 },
316 },
317 FontId {
319 cvt: TableId { checksum: 0x1269EB58, len: 0x00000350 },
320 fpgm: TableId { checksum: 0x5CD5957A, len: 0x00006A4E },
321 prep: TableId { checksum: 0xF758323A, len: 0x00000380 },
322 },
323 FontId {
325 cvt: TableId { checksum: 0x122FEB0B, len: 0x00000350 },
326 fpgm: TableId { checksum: 0x7F10919A, len: 0x000070A9 },
327 prep: TableId { checksum: 0x7CD7E7B7, len: 0x0000025C },
328 },
329];
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn ensure_max_name_len() {
337 let max_len = HINT_RELIANT_NAMES
338 .iter()
339 .fold(0, |acc, name| acc.max(name.len()));
340 assert_eq!(max_len, MAX_HINT_RELIANT_NAME_LEN);
341 }
342
343 #[test]
344 fn skip_pdf_tags() {
345 assert_eq!(skip_pdf_random_tag("ABCDEF+"), "ABCDEF+");
347 assert_eq!(skip_pdf_random_tag("AbCdEF+Arial"), "AbCdEF+Arial");
349 assert_eq!(skip_pdf_random_tag("Ab12EF+Arial"), "Ab12EF+Arial");
351 assert_eq!(skip_pdf_random_tag("ABCDEFArial"), "ABCDEFArial");
353 assert_eq!(skip_pdf_random_tag("ABCDEFG+Arial"), "ABCDEFG+Arial");
355 assert_eq!(skip_pdf_random_tag("ABCDE+Arial"), "ABCDE+Arial");
357 assert_eq!(skip_pdf_random_tag("ABCDEF+Arial"), "Arial");
359 }
360
361 #[test]
362 fn all_hint_reliant_names() {
363 for name in HINT_RELIANT_NAMES {
364 assert!(matches_hint_reliant_name_list(name));
365 }
366 }
367
368 #[test]
369 fn non_hint_reliant_names() {
370 for not_tricky in ["Roboto", "Arial", "Helvetica", "Blah", ""] {
371 assert!(!matches_hint_reliant_name_list(not_tricky));
372 }
373 }
374}