1pub struct FontTable {
5 names: Vec<String>,
6}
7
8impl FontTable {
9 pub fn empty() -> FontTable {
10 FontTable { names: Vec::new() }
11 }
12
13 pub fn parse(sttbf_ffn: &[u8]) -> FontTable {
14 let mut names = Vec::new();
15 let mut pos;
16
17 let read_u16 = |bytes: &[u8], offset: usize| -> Option<u16> {
18 bytes
19 .get(offset..offset + 2)
20 .map(|pair| u16::from_le_bytes([pair[0], pair[1]]))
21 };
22
23 let Some(first) = read_u16(sttbf_ffn, 0) else {
24 return FontTable { names };
25 };
26 let count;
27 if first == 0xFFFF {
28 count = read_u16(sttbf_ffn, 2).unwrap_or(0) as usize;
29 pos = 6; } else {
31 count = first as usize;
32 pos = 4; }
34
35 for _ in 0..count {
36 let Some(&cb_ffn_m1) = sttbf_ffn.get(pos) else {
37 break;
38 };
39 let entry_end = pos + 1 + cb_ffn_m1 as usize;
40 let Some(entry) = sttbf_ffn.get(pos + 1..entry_end) else {
41 break;
42 };
43 const NAME_OFFSET: usize = 39;
47 let mut name = String::new();
48 let mut offset = NAME_OFFSET;
49 while offset + 2 <= entry.len() {
50 let unit = u16::from_le_bytes([entry[offset], entry[offset + 1]]);
51 if unit == 0 {
52 break;
53 }
54 name.push_str(&String::from_utf16_lossy(&[unit]));
55 offset += 2;
56 }
57 names.push(name);
58 pos = entry_end;
59 }
60
61 FontTable { names }
62 }
63
64 pub fn name(&self, ftc: u16) -> Option<String> {
65 self.names
66 .get(ftc as usize)
67 .filter(|name| !name.is_empty())
68 .cloned()
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 fn ffn_entry(name: &str) -> Vec<u8> {
77 let mut body = vec![0u8; 39];
78 for unit in name.encode_utf16() {
79 body.extend_from_slice(&unit.to_le_bytes());
80 }
81 body.extend_from_slice(&0u16.to_le_bytes());
82 let mut entry = vec![body.len() as u8];
83 entry.extend_from_slice(&body);
84 entry
85 }
86
87 #[test]
88 fn parses_font_names() {
89 let mut sttbf = Vec::new();
90 sttbf.extend_from_slice(&0xFFFFu16.to_le_bytes());
91 sttbf.extend_from_slice(&2u16.to_le_bytes());
92 sttbf.extend_from_slice(&0u16.to_le_bytes());
93 sttbf.extend_from_slice(&ffn_entry("Times New Roman"));
94 sttbf.extend_from_slice(&ffn_entry("Arial"));
95
96 let table = FontTable::parse(&sttbf);
97 assert_eq!(table.name(0).as_deref(), Some("Times New Roman"));
98 assert_eq!(table.name(1).as_deref(), Some("Arial"));
99 assert_eq!(table.name(9), None);
100 }
101}