1use super::aat::{safe_read_array_to_end, ExtendedStateTable, LookupU16, StateTableParts};
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> FontRead<'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, Copy, Debug, Default)]
100pub struct SubtableParts {
101 pub format: u8,
103 pub state: StateTableParts,
104 pub extra: [u32; 3],
108}
109
110impl<'a> SubtableKind<'a> {
111 pub fn parts(data: FontData<'a>, coverage: u32) -> Result<SubtableParts, ReadError> {
114 let format = (coverage & 0xFF) as u8;
115 let mut parts = SubtableParts {
116 format,
117 ..Default::default()
118 };
119 if format == 4 {
120 return Ok(parts);
122 }
123 parts.state = StateTableParts::read(data)?;
124 let mut cursor = data.cursor();
125 cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
126 match format {
127 1 | 5 => {
128 parts.extra[0] = cursor.read::<u32>()?;
129 }
130 2 => {
131 parts.extra[0] = cursor.read::<u32>()?;
132 parts.extra[1] = cursor.read::<u32>()?;
133 parts.extra[2] = cursor.read::<u32>()?;
134 }
135 _ => {}
136 }
137 Ok(parts)
138 }
139
140 #[inline]
143 pub fn from_parts(data: FontData<'a>, parts: &SubtableParts) -> Result<Self, ReadError> {
144 match parts.format {
145 0 => Ok(Self::Rearrangement(ExtendedStateTable::from_parts(
146 data,
147 &parts.state,
148 )?)),
149 1 => {
150 let state_table = ExtendedStateTable::from_parts(data, &parts.state)?;
151 let offset = parts.extra[0] as usize;
152 let end = data.len();
153 let offsets_data = FontData::new(data.read_array(offset..end)?);
154 let raw_offsets: &[BigEndian<Offset32>] = safe_read_array_to_end(&offsets_data, 0)?;
155 let lookups = ArrayOfOffsets::new(raw_offsets, offsets_data, ());
156 Ok(Self::Contextual(ContextualSubtable {
157 state_table,
158 lookups,
159 }))
160 }
161 2 => Ok(Self::Ligature(LigatureSubtable {
162 state_table: ExtendedStateTable::from_parts(data, &parts.state)?,
163 ligature_actions: safe_read_array_to_end(&data, parts.extra[0] as usize)?,
164 components: safe_read_array_to_end(&data, parts.extra[1] as usize)?,
165 ligatures: safe_read_array_to_end(&data, parts.extra[2] as usize)?,
166 })),
167 4 => Ok(Self::NonContextual(LookupU16::read(data)?)),
168 5 => Ok(Self::Insertion(InsertionSubtable {
169 state_table: ExtendedStateTable::from_parts(data, &parts.state)?,
170 glyphs: safe_read_array_to_end(&data, parts.extra[0] as usize)?,
171 })),
172 _ => Err(ReadError::InvalidFormat(parts.format as _)),
173 }
174 }
175}
176
177#[derive(Clone)]
179pub struct ContextualSubtable<'a> {
180 pub state_table: ExtendedStateTable<'a, ContextualEntryData>,
181 pub lookups: ArrayOfOffsets<'a, LookupU16<'a>, Offset32>,
184}
185
186impl ReadArgs for ContextualSubtable<'_> {
187 type Args = ();
188}
189
190impl<'a> FontRead<'a> for ContextualSubtable<'a> {
191 fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
192 let state_table = ExtendedStateTable::read(data)?;
193 let mut cursor = data.cursor();
194 cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
195 let offset = cursor.read::<u32>()? as usize;
196 let end = data.len();
197 let offsets_data = FontData::new(data.read_array(offset..end)?);
198 let raw_offsets: &[BigEndian<Offset32>] = safe_read_array_to_end(&offsets_data, 0)?;
199 let lookups = ArrayOfOffsets::new(raw_offsets, offsets_data, ());
200 Ok(Self {
201 state_table,
202 lookups,
203 })
204 }
205}
206
207#[derive(Clone)]
209pub struct LigatureSubtable<'a> {
210 pub state_table: ExtendedStateTable<'a, BigEndian<u16>>,
211 pub ligature_actions: &'a [BigEndian<u32>],
213 pub components: &'a [BigEndian<u16>],
216 pub ligatures: &'a [BigEndian<GlyphId16>],
218}
219
220impl ReadArgs for LigatureSubtable<'_> {
221 type Args = ();
222}
223
224impl<'a> FontRead<'a> for LigatureSubtable<'a> {
225 fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
226 let state_table = ExtendedStateTable::read(data)?;
227 let mut cursor = data.cursor();
228 cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
229 let lig_action_offset = cursor.read::<u32>()? as usize;
232 let component_offset = cursor.read::<u32>()? as usize;
233 let ligature_offset = cursor.read::<u32>()? as usize;
234 let ligature_actions = safe_read_array_to_end(&data, lig_action_offset)?;
235 let components = safe_read_array_to_end(&data, component_offset)?;
236 let ligatures = safe_read_array_to_end(&data, ligature_offset)?;
237 Ok(Self {
238 state_table,
239 ligature_actions,
240 components,
241 ligatures,
242 })
243 }
244}
245
246#[derive(Clone)]
248pub struct InsertionSubtable<'a> {
249 pub state_table: ExtendedStateTable<'a, InsertionEntryData>,
250 pub glyphs: &'a [BigEndian<GlyphId16>],
253}
254
255impl ReadArgs for InsertionSubtable<'_> {
256 type Args = ();
257}
258
259impl<'a> FontRead<'a> for InsertionSubtable<'a> {
260 fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
261 let state_table = ExtendedStateTable::read(data)?;
262 let mut cursor = data.cursor();
263 cursor.advance_by(ExtendedStateTable::<()>::HEADER_LEN);
264 let glyphs_offset = cursor.read::<u32>()? as usize;
265 let glyphs = safe_read_array_to_end(&data, glyphs_offset)?;
266 Ok(Self {
267 state_table,
268 glyphs,
269 })
270 }
271}
272
273#[cfg(feature = "experimental_traverse")]
274impl<'a> SomeRecord<'a> for Chain<'a> {
275 fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
276 RecordResolver {
277 name: "Chain",
278 get_field: Box::new(move |idx, _data| match idx {
279 0usize => Some(Field::new("default_flags", self.default_flags())),
280 _ => None,
281 }),
282 data,
283 }
284 }
285}
286
287#[cfg(feature = "experimental_traverse")]
288impl<'a> SomeRecord<'a> for Subtable<'a> {
289 fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
290 RecordResolver {
291 name: "Subtable",
292 get_field: Box::new(move |idx, _data| match idx {
293 0usize => Some(Field::new("coverage", self.coverage())),
294 1usize => Some(Field::new("sub_feature_flags", self.sub_feature_flags())),
295 _ => None,
296 }),
297 data,
298 }
299 }
300}
301
302#[cfg(test)]
303#[allow(clippy::unusual_byte_groupings)]
306mod tests {
307 use super::*;
308 use crate::{FontRef, TableProvider};
309
310 #[test]
311 fn parse_chain_flags_features() {
312 let font = FontRef::new(font_test_data::morx::FOUR).unwrap();
313 let morx = font.morx().unwrap();
314 let chain = morx.chains().iter().next().unwrap().unwrap();
315 assert_eq!(chain.default_flags(), 1);
316 let feature = chain.features()[0];
317 assert_eq!(feature.feature_type(), 4);
318 assert_eq!(feature.feature_settings(), 0);
319 assert_eq!(feature.enable_flags(), 1);
320 assert_eq!(feature.disable_flags(), 0xFFFFFFFF);
321 }
322
323 #[test]
324 fn parse_rearrangement() {
325 let font = FontRef::new(font_test_data::morx::FOUR).unwrap();
326 let morx = font.morx().unwrap();
327 let chain = morx.chains().iter().next().unwrap().unwrap();
328 let subtable = chain.subtables().iter().next().unwrap().unwrap();
329 assert_eq!(subtable.coverage(), 0x20_0000_00);
330 let SubtableKind::Rearrangement(_kind) = subtable.kind().unwrap() else {
332 panic!("expected rearrangement subtable!");
333 };
334 }
335
336 #[test]
337 fn parse_contextual() {
338 let font = FontRef::new(font_test_data::morx::EIGHTEEN).unwrap();
339 let morx = font.morx().unwrap();
340 let chain = morx.chains().iter().next().unwrap().unwrap();
341 let subtable = chain.subtables().iter().next().unwrap().unwrap();
342 assert_eq!(subtable.coverage(), 0x20_0000_01);
343 let SubtableKind::Contextual(kind) = subtable.kind().unwrap() else {
344 panic!("expected contextual subtable!");
345 };
346 let lookup = kind.lookups.get(0).unwrap();
347 let expected = [None, None, Some(7u16), Some(8), Some(9), Some(10), Some(11)];
348 let values = (0..7).map(|gid| lookup.value(gid).ok()).collect::<Vec<_>>();
349 assert_eq!(values, &expected);
350 }
351
352 #[test]
353 fn parse_ligature() {
354 let font = FontRef::new(font_test_data::morx::FORTY_ONE).unwrap();
355 let morx = font.morx().unwrap();
356 let chain = morx.chains().iter().next().unwrap().unwrap();
357 let subtable = chain.subtables().iter().next().unwrap().unwrap();
358 assert_eq!(subtable.coverage(), 0x20_0000_02);
359 let SubtableKind::Ligature(kind) = subtable.kind().unwrap() else {
360 panic!("expected ligature subtable!");
361 };
362 let expected_actions = [0x3FFFFFFE, 0xBFFFFFFE];
363 let actions = kind
366 .ligature_actions
367 .iter()
368 .take(2)
369 .map(|action| action.get())
370 .collect::<Vec<_>>();
371 assert_eq!(actions, &expected_actions);
372 let expected_components = [0u16, 1, 0, 0];
373 let components = kind
375 .components
376 .iter()
377 .take(4)
378 .map(|comp| comp.get())
379 .collect::<Vec<_>>();
380 assert_eq!(components, &expected_components);
381 let expected_ligatures = [GlyphId16::new(5), GlyphId16::new(6)];
382 let ligatures = kind
383 .ligatures
384 .iter()
385 .map(|gid| gid.get())
386 .collect::<Vec<_>>();
387 assert_eq!(ligatures, &expected_ligatures);
388 }
389
390 #[test]
391 fn parse_non_contextual() {
392 let font = FontRef::new(font_test_data::morx::ONE).unwrap();
393 let morx = font.morx().unwrap();
394 let chain = morx.chains().iter().next().unwrap().unwrap();
395 let subtable = chain.subtables().iter().next().unwrap().unwrap();
396 assert_eq!(subtable.coverage(), 0x20_0000_04);
397 let SubtableKind::NonContextual(kind) = subtable.kind().unwrap() else {
398 panic!("expected non-contextual subtable!");
399 };
400 let expected_values = [None, None, Some(5u16), None, Some(7)];
401 let values = (0..5).map(|gid| kind.value(gid).ok()).collect::<Vec<_>>();
402 assert_eq!(values, &expected_values);
403 }
404
405 #[test]
406 fn parse_insertion() {
407 let font = FontRef::new(font_test_data::morx::THIRTY_FOUR).unwrap();
408 let morx = font.morx().unwrap();
409 let chain = morx.chains().iter().next().unwrap().unwrap();
410 let subtable = chain.subtables().iter().next().unwrap().unwrap();
411 assert_eq!(subtable.coverage(), 0x20_0000_05);
412 let SubtableKind::Insertion(kind) = subtable.kind().unwrap() else {
413 panic!("expected insertion subtable!");
414 };
415 let mut expected_glyphs = vec![];
416 for _ in 0..9 {
417 for gid in [3, 2] {
418 expected_glyphs.push(GlyphId16::new(gid));
419 }
420 }
421 assert_eq!(kind.glyphs, &expected_glyphs);
422 }
423}