1use super::aat::{safe_read_array_to_end, ExtendedStateTable, LookupU16};
4
5include!("../../generated/generated_morx.rs");
6
7impl VarSize for Chain<'_> {
8 type Size = u32;
9
10 fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
11 data.read_at::<u32>(pos.checked_add(u32::RAW_BYTE_LEN)?)
13 .ok()
14 .map(|size| size as usize)
15 }
16}
17
18impl VarSize for Subtable<'_> {
19 type Size = u32;
20
21 fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
22 data.read_at::<u32>(pos).ok().map(|size| size as usize)
26 }
27}
28
29impl<'a> Subtable<'a> {
30 #[inline]
33 pub fn is_logical(&self) -> bool {
34 self.coverage() & 0x10000000 != 0
35 }
36
37 #[inline]
40 pub fn is_all_directions(&self) -> bool {
41 self.coverage() & 0x20000000 != 0
42 }
43
44 #[inline]
47 pub fn is_backwards(&self) -> bool {
48 self.coverage() & 0x40000000 != 0
49 }
50
51 #[inline]
55 pub fn is_vertical(&self) -> bool {
56 self.coverage() & 0x80000000 != 0
57 }
58
59 pub fn kind(&self) -> Result<SubtableKind<'a>, ReadError> {
61 SubtableKind::read_with_args(FontData::new(self.data()), &self.coverage())
62 }
63}
64
65#[derive(Clone)]
67pub enum SubtableKind<'a> {
68 Rearrangement(ExtendedStateTable<'a>),
69 Contextual(ContextualSubtable<'a>),
70 Ligature(LigatureSubtable<'a>),
71 NonContextual(LookupU16<'a>),
72 Insertion(InsertionSubtable<'a>),
73}
74
75impl ReadArgs for SubtableKind<'_> {
76 type Args = u32;
77}
78
79impl<'a> FontReadWithArgs<'a> for SubtableKind<'a> {
80 fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
81 let format = *args & 0xFF;
83 match format {
84 0 => Ok(Self::Rearrangement(ExtendedStateTable::read(data)?)),
85 1 => Ok(Self::Contextual(ContextualSubtable::read(data)?)),
86 2 => Ok(Self::Ligature(LigatureSubtable::read(data)?)),
87 4 => Ok(Self::NonContextual(LookupU16::read(data)?)),
89 5 => Ok(Self::Insertion(InsertionSubtable::read(data)?)),
90 _ => Err(ReadError::InvalidFormat(format as _)),
91 }
92 }
93}
94
95#[derive(Clone)]
97pub struct ContextualSubtable<'a> {
98 pub state_table: ExtendedStateTable<'a, ContextualEntryData>,
99 pub lookups: ArrayOfOffsets<'a, LookupU16<'a>, Offset32>,
102}
103
104impl<'a> FontRead<'a> for ContextualSubtable<'a> {
105 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
106 let state_table = ExtendedStateTable::read(data)?;
107 let mut cursor = data.cursor();
108 cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
109 let offset = cursor.read::<u32>()? as usize;
110 let end = data.len();
111 let offsets_data = FontData::new(data.read_array(offset..end)?);
112 let raw_offsets: &[BigEndian<Offset32>] = safe_read_array_to_end(&offsets_data, 0)?;
113 let lookups = ArrayOfOffsets::new(raw_offsets, offsets_data, ());
114 Ok(Self {
115 state_table,
116 lookups,
117 })
118 }
119}
120
121#[derive(Clone)]
123pub struct LigatureSubtable<'a> {
124 pub state_table: ExtendedStateTable<'a, BigEndian<u16>>,
125 pub ligature_actions: &'a [BigEndian<u32>],
127 pub components: &'a [BigEndian<u16>],
130 pub ligatures: &'a [BigEndian<GlyphId16>],
132}
133
134impl<'a> FontRead<'a> for LigatureSubtable<'a> {
135 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
136 let state_table = ExtendedStateTable::read(data)?;
137 let mut cursor = data.cursor();
138 cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
139 let lig_action_offset = cursor.read::<u32>()? as usize;
142 let component_offset = cursor.read::<u32>()? as usize;
143 let ligature_offset = cursor.read::<u32>()? as usize;
144 let ligature_actions = safe_read_array_to_end(&data, lig_action_offset)?;
145 let components = safe_read_array_to_end(&data, component_offset)?;
146 let ligatures = safe_read_array_to_end(&data, ligature_offset)?;
147 Ok(Self {
148 state_table,
149 ligature_actions,
150 components,
151 ligatures,
152 })
153 }
154}
155
156#[derive(Clone)]
158pub struct InsertionSubtable<'a> {
159 pub state_table: ExtendedStateTable<'a, InsertionEntryData>,
160 pub glyphs: &'a [BigEndian<GlyphId16>],
163}
164
165impl<'a> FontRead<'a> for InsertionSubtable<'a> {
166 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
167 let state_table = ExtendedStateTable::read(data)?;
168 let mut cursor = data.cursor();
169 cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
170 let glyphs_offset = cursor.read::<u32>()? as usize;
171 let glyphs = safe_read_array_to_end(&data, glyphs_offset)?;
172 Ok(Self {
173 state_table,
174 glyphs,
175 })
176 }
177}
178
179#[cfg(feature = "experimental_traverse")]
180impl<'a> SomeRecord<'a> for Chain<'a> {
181 fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
182 RecordResolver {
183 name: "Chain",
184 get_field: Box::new(move |idx, _data| match idx {
185 0usize => Some(Field::new("default_flags", self.default_flags())),
186 _ => None,
187 }),
188 data,
189 }
190 }
191}
192
193#[cfg(feature = "experimental_traverse")]
194impl<'a> SomeRecord<'a> for Subtable<'a> {
195 fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
196 RecordResolver {
197 name: "Subtable",
198 get_field: Box::new(move |idx, _data| match idx {
199 0usize => Some(Field::new("coverage", self.coverage())),
200 1usize => Some(Field::new("sub_feature_flags", self.sub_feature_flags())),
201 _ => None,
202 }),
203 data,
204 }
205 }
206}
207
208#[cfg(test)]
209#[allow(clippy::unusual_byte_groupings)]
212mod tests {
213 use super::*;
214 use crate::{FontRef, TableProvider};
215
216 #[test]
217 fn parse_chain_flags_features() {
218 let font = FontRef::new(font_test_data::morx::FOUR).unwrap();
219 let morx = font.morx().unwrap();
220 let chain = morx.chains().iter().next().unwrap().unwrap();
221 assert_eq!(chain.default_flags(), 1);
222 let feature = chain.features()[0];
223 assert_eq!(feature.feature_type(), 4);
224 assert_eq!(feature.feature_settings(), 0);
225 assert_eq!(feature.enable_flags(), 1);
226 assert_eq!(feature.disable_flags(), 0xFFFFFFFF);
227 }
228
229 #[test]
230 fn parse_rearrangement() {
231 let font = FontRef::new(font_test_data::morx::FOUR).unwrap();
232 let morx = font.morx().unwrap();
233 let chain = morx.chains().iter().next().unwrap().unwrap();
234 let subtable = chain.subtables().iter().next().unwrap().unwrap();
235 assert_eq!(subtable.coverage(), 0x20_0000_00);
236 let SubtableKind::Rearrangement(_kind) = subtable.kind().unwrap() else {
238 panic!("expected rearrangement subtable!");
239 };
240 }
241
242 #[test]
243 fn parse_contextual() {
244 let font = FontRef::new(font_test_data::morx::EIGHTEEN).unwrap();
245 let morx = font.morx().unwrap();
246 let chain = morx.chains().iter().next().unwrap().unwrap();
247 let subtable = chain.subtables().iter().next().unwrap().unwrap();
248 assert_eq!(subtable.coverage(), 0x20_0000_01);
249 let SubtableKind::Contextual(kind) = subtable.kind().unwrap() else {
250 panic!("expected contextual subtable!");
251 };
252 let lookup = kind.lookups.get(0).unwrap();
253 let expected = [None, None, Some(7u16), Some(8), Some(9), Some(10), Some(11)];
254 let values = (0..7).map(|gid| lookup.value(gid).ok()).collect::<Vec<_>>();
255 assert_eq!(values, &expected);
256 }
257
258 #[test]
259 fn parse_ligature() {
260 let font = FontRef::new(font_test_data::morx::FORTY_ONE).unwrap();
261 let morx = font.morx().unwrap();
262 let chain = morx.chains().iter().next().unwrap().unwrap();
263 let subtable = chain.subtables().iter().next().unwrap().unwrap();
264 assert_eq!(subtable.coverage(), 0x20_0000_02);
265 let SubtableKind::Ligature(kind) = subtable.kind().unwrap() else {
266 panic!("expected ligature subtable!");
267 };
268 let expected_actions = [0x3FFFFFFE, 0xBFFFFFFE];
269 let actions = kind
272 .ligature_actions
273 .iter()
274 .take(2)
275 .map(|action| action.get())
276 .collect::<Vec<_>>();
277 assert_eq!(actions, &expected_actions);
278 let expected_components = [0u16, 1, 0, 0];
279 let components = kind
281 .components
282 .iter()
283 .take(4)
284 .map(|comp| comp.get())
285 .collect::<Vec<_>>();
286 assert_eq!(components, &expected_components);
287 let expected_ligatures = [GlyphId16::new(5), GlyphId16::new(6)];
288 let ligatures = kind
289 .ligatures
290 .iter()
291 .map(|gid| gid.get())
292 .collect::<Vec<_>>();
293 assert_eq!(ligatures, &expected_ligatures);
294 }
295
296 #[test]
297 fn parse_non_contextual() {
298 let font = FontRef::new(font_test_data::morx::ONE).unwrap();
299 let morx = font.morx().unwrap();
300 let chain = morx.chains().iter().next().unwrap().unwrap();
301 let subtable = chain.subtables().iter().next().unwrap().unwrap();
302 assert_eq!(subtable.coverage(), 0x20_0000_04);
303 let SubtableKind::NonContextual(kind) = subtable.kind().unwrap() else {
304 panic!("expected non-contextual subtable!");
305 };
306 let expected_values = [None, None, Some(5u16), None, Some(7)];
307 let values = (0..5).map(|gid| kind.value(gid).ok()).collect::<Vec<_>>();
308 assert_eq!(values, &expected_values);
309 }
310
311 #[test]
312 fn parse_insertion() {
313 let font = FontRef::new(font_test_data::morx::THIRTY_FOUR).unwrap();
314 let morx = font.morx().unwrap();
315 let chain = morx.chains().iter().next().unwrap().unwrap();
316 let subtable = chain.subtables().iter().next().unwrap().unwrap();
317 assert_eq!(subtable.coverage(), 0x20_0000_05);
318 let SubtableKind::Insertion(kind) = subtable.kind().unwrap() else {
319 panic!("expected insertion subtable!");
320 };
321 let mut expected_glyphs = vec![];
322 for _ in 0..9 {
323 for gid in [3, 2] {
324 expected_glyphs.push(GlyphId16::new(gid));
325 }
326 }
327 assert_eq!(kind.glyphs, &expected_glyphs);
328 }
329}