ferroui_base/media/fonts/tables/
script_list_table.rs1use std::collections::HashSet;
2
3use crate::media::fonts::OpenTypeTag;
4use crate::media::IFontMemory;
5
6use super::big_endian_binary_reader::{BigEndianBinaryReader, FontTableError};
7
8const GSUB: OpenTypeTag = OpenTypeTag::from_chars('G', 'S', 'U', 'B');
9const GPOS: OpenTypeTag = OpenTypeTag::from_chars('G', 'P', 'O', 'S');
10
11pub struct ScriptListTable;
13
14impl ScriptListTable {
15 pub fn try_read_script_tags(font: &dyn IFontMemory, script_tags: &mut HashSet<OpenTypeTag>) -> bool {
20 Self::try_read_script_list(font, GSUB, script_tags) & Self::try_read_script_list(font, GPOS, script_tags)
22 }
23
24 fn try_read_script_list(
25 font: &dyn IFontMemory,
26 table_tag: OpenTypeTag,
27 script_tags: &mut HashSet<OpenTypeTag>,
28 ) -> bool {
29 let Some(table) = font.try_get_table(table_tag) else {
30 return true;
32 };
33
34 Self::read_script_list(table.span(), script_tags).is_ok()
36 }
37
38 fn read_script_list(span: &[u8], script_tags: &mut HashSet<OpenTypeTag>) -> Result<(), FontTableError> {
39 let mut reader = BigEndianBinaryReader::new(span);
40
41 reader.read_uint16()?;
44 reader.read_uint16()?;
45 let script_list_offset = reader.read_offset16()?;
46
47 reader.seek(script_list_offset as i32)?;
49
50 let script_count = reader.read_uint16()?;
51
52 for _ in 0..script_count {
53 script_tags.insert(OpenTypeTag::new(reader.read_uint32()?));
54 reader.read_offset16()?; }
56
57 Ok(())
58 }
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use crate::media::fonts::tables::testing::{BigEndianBuffer, SyntheticFont};
65
66 fn build_layout_table(scripts: &[&str]) -> Vec<u8> {
67 let mut buffer = BigEndianBuffer::new();
68
69 buffer.uint16(1).uint16(0).uint16(10).uint16(0).uint16(0);
70 buffer.uint16(scripts.len() as i32);
71
72 for script in scripts {
73 buffer.tag(script).uint16(0);
74 }
75
76 buffer.to_array()
77 }
78
79 #[test]
80 fn absent_tables_contribute_no_scripts() {
81 let mut tags = HashSet::new();
82
83 assert!(ScriptListTable::try_read_script_tags(&SyntheticFont::new(), &mut tags));
84 assert!(tags.is_empty());
85 }
86
87 #[test]
88 fn reads_the_scripts_of_both_tables() {
89 let mut font = SyntheticFont::new();
90 font.replace("GSUB", build_layout_table(&["DFLT", "latn"]));
91 font.replace("GPOS", build_layout_table(&["latn", "cyrl"]));
92
93 let mut tags = HashSet::new();
94
95 assert!(ScriptListTable::try_read_script_tags(&font, &mut tags));
96 assert_eq!(tags.len(), 3);
97 assert!(tags.contains(&OpenTypeTag::parse("DFLT")));
98 assert!(tags.contains(&OpenTypeTag::parse("latn")));
99 assert!(tags.contains(&OpenTypeTag::parse("cyrl")));
100 }
101
102 #[test]
103 fn malformed_table_reports_unknown_but_still_reads_the_other_table() {
104 let mut font = SyntheticFont::new();
105 let gsub = build_layout_table(&["arab", "latn"]);
106
107 font.replace("GSUB", gsub[..gsub.len() - 3].to_vec());
109 font.replace("GPOS", build_layout_table(&["cyrl"]));
110
111 let mut tags = HashSet::new();
112
113 assert!(!ScriptListTable::try_read_script_tags(&font, &mut tags));
114 assert!(tags.contains(&OpenTypeTag::parse("arab")));
115 assert!(tags.contains(&OpenTypeTag::parse("cyrl")));
116 assert!(!tags.contains(&OpenTypeTag::parse("latn")));
117 }
118}