1use super::aat::{safe_read_array_to_end, LegacyStateTableParts, LookupU16, NoPayload, StateTable};
4
5include!("../../generated/generated_mort.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)?)
12 .ok()
13 .map(|size| size as usize)
14 }
15}
16
17impl VarSize for Subtable<'_> {
18 type Size = u16;
19
20 fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
21 data.read_at::<u16>(pos).ok().map(usize::from)
22 }
23}
24
25impl<'a> Subtable<'a> {
26 #[inline]
28 pub fn is_logical(&self) -> bool {
29 self.coverage() & 0x1000 != 0
30 }
31
32 #[inline]
34 pub fn is_all_directions(&self) -> bool {
35 self.coverage() & 0x2000 != 0
36 }
37
38 #[inline]
40 pub fn is_backwards(&self) -> bool {
41 self.coverage() & 0x4000 != 0
42 }
43
44 #[inline]
46 pub fn is_vertical(&self) -> bool {
47 self.coverage() & 0x8000 != 0
48 }
49
50 pub fn kind(&self) -> Result<SubtableKind<'a>, ReadError> {
52 SubtableKind::read_with_args(FontData::new(self.data()), self.coverage())
53 }
54}
55
56#[derive(Clone)]
58pub enum SubtableKind<'a> {
59 Rearrangement(StateTable<'a>),
60 Contextual(ContextualSubtable<'a>),
61 Ligature(LigatureSubtable<'a>),
62 NonContextual(LookupU16<'a>),
63 Insertion(InsertionSubtable<'a>),
64}
65
66impl ReadArgs for SubtableKind<'_> {
67 type Args = u16;
68}
69
70impl<'a> FontRead<'a> for SubtableKind<'a> {
71 fn read_with_args(data: FontData<'a>, coverage: Self::Args) -> Result<Self, ReadError> {
72 match coverage & 0xFF {
73 0 => Ok(Self::Rearrangement(StateTable::read(data)?)),
74 1 => Ok(Self::Contextual(ContextualSubtable::read(data)?)),
75 2 => Ok(Self::Ligature(LigatureSubtable::read(data)?)),
76 4 => Ok(Self::NonContextual(LookupU16::read(data)?)),
77 5 => Ok(Self::Insertion(InsertionSubtable::read(data)?)),
78 format => Err(ReadError::InvalidFormat(format as _)),
79 }
80 }
81}
82
83#[derive(Clone, Copy, Debug, Default)]
85pub struct SubtableParts {
86 pub format: u8,
88 pub state: LegacyStateTableParts,
89 pub extra: [u16; 3],
91}
92
93impl SubtableKind<'_> {
94 pub fn parts(data: FontData, coverage: u16) -> Result<SubtableParts, ReadError> {
96 let format = (coverage & 0xFF) as u8;
97 let mut parts = SubtableParts {
98 format,
99 ..Default::default()
100 };
101 if format == 4 {
102 return Ok(parts);
103 }
104 parts.state = LegacyStateTableParts::read(data)?;
105 let mut cursor = data.cursor();
106 cursor.advance_by(StateTable::<NoPayload>::HEADER_LEN);
107 match format {
108 1 | 5 => parts.extra[0] = cursor.read::<u16>()?,
109 2 => {
110 parts.extra[0] = cursor.read::<u16>()?;
111 parts.extra[1] = cursor.read::<u16>()?;
112 parts.extra[2] = cursor.read::<u16>()?;
113 }
114 _ => {}
115 }
116 Ok(parts)
117 }
118
119 #[inline]
121 pub fn from_parts<'a>(
122 data: FontData<'a>,
123 parts: &SubtableParts,
124 ) -> Result<SubtableKind<'a>, ReadError> {
125 match parts.format {
126 0 => Ok(SubtableKind::Rearrangement(StateTable::from_parts(
127 data,
128 &parts.state,
129 )?)),
130 1 => Ok(SubtableKind::Contextual(ContextualSubtable {
131 state_table: StateTable::from_parts(data, &parts.state)?,
132 data,
133 })),
134 2 => Ok(SubtableKind::Ligature(LigatureSubtable {
135 state_table: StateTable::from_parts(data, &parts.state)?,
136 data,
137 })),
138 4 => Ok(SubtableKind::NonContextual(LookupU16::read(data)?)),
139 5 => Ok(SubtableKind::Insertion(InsertionSubtable {
140 state_table: StateTable::from_parts(data, &parts.state)?,
141 glyphs: safe_read_array_to_end(&data, parts.extra[0] as usize)?,
142 })),
143 format => Err(ReadError::InvalidFormat(format as _)),
144 }
145 }
146}
147
148#[derive(Clone)]
150pub struct ContextualSubtable<'a> {
151 pub state_table: StateTable<'a, ContextualEntryData>,
152 data: FontData<'a>,
153}
154
155impl ContextualSubtable<'_> {
156 pub fn substitution(&self, offset: i16, glyph: GlyphId16) -> Result<GlyphId16, ReadError> {
158 let word = i32::from(offset)
159 .checked_add(i32::from(glyph.to_u16()))
160 .ok_or(ReadError::OutOfBounds)?;
161 let byte = usize::try_from(word)
162 .ok()
163 .and_then(|word| word.checked_mul(u16::RAW_BYTE_LEN))
164 .ok_or(ReadError::OutOfBounds)?;
165 self.data.read_at(byte)
166 }
167}
168
169impl ReadArgs for ContextualSubtable<'_> {
170 type Args = ();
171}
172
173impl<'a> FontRead<'a> for ContextualSubtable<'a> {
174 fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
175 let state_table = StateTable::<ContextualEntryData>::read_with_args(data, ())?;
176 let mut cursor = data.cursor();
177 cursor.advance_by(StateTable::<NoPayload>::HEADER_LEN);
178 cursor.read::<u16>()?;
179 Ok(Self { state_table, data })
180 }
181}
182
183#[derive(Clone)]
185pub struct LigatureSubtable<'a> {
186 pub state_table: StateTable<'a>,
187 data: FontData<'a>,
188}
189
190impl LigatureSubtable<'_> {
191 pub fn ligature_action(&self, offset: usize) -> Result<u32, ReadError> {
193 self.data.read_at(offset)
194 }
195
196 pub fn component(&self, offset: i32) -> Result<u16, ReadError> {
198 let byte = usize::try_from(offset)
199 .ok()
200 .and_then(|offset| offset.checked_mul(u16::RAW_BYTE_LEN))
201 .ok_or(ReadError::OutOfBounds)?;
202 self.data.read_at(byte)
203 }
204
205 pub fn ligature(&self, offset: usize) -> Result<GlyphId16, ReadError> {
207 self.data.read_at(offset)
208 }
209}
210
211impl ReadArgs for LigatureSubtable<'_> {
212 type Args = ();
213}
214
215impl<'a> FontRead<'a> for LigatureSubtable<'a> {
216 fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
217 let state_table = StateTable::read(data)?;
218 let mut cursor = data.cursor();
219 cursor.advance_by(StateTable::<NoPayload>::HEADER_LEN);
220 cursor.read::<u16>()?;
221 cursor.read::<u16>()?;
222 cursor.read::<u16>()?;
223 Ok(Self { state_table, data })
224 }
225}
226
227#[derive(Clone)]
229pub struct InsertionSubtable<'a> {
230 pub state_table: StateTable<'a, InsertionEntryData>,
231 pub glyphs: &'a [BigEndian<GlyphId16>],
232}
233
234impl ReadArgs for InsertionSubtable<'_> {
235 type Args = ();
236}
237
238impl<'a> FontRead<'a> for InsertionSubtable<'a> {
239 fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
240 let state_table = StateTable::<InsertionEntryData>::read_with_args(data, ())?;
241 let mut cursor = data.cursor();
242 cursor.advance_by(StateTable::<NoPayload>::HEADER_LEN);
243 let glyphs_offset = cursor.read::<u16>()? as usize;
244 let glyphs = safe_read_array_to_end(&data, glyphs_offset)?;
245 Ok(Self {
246 state_table,
247 glyphs,
248 })
249 }
250}
251
252#[cfg(feature = "experimental_traverse")]
253impl<'a> SomeRecord<'a> for Chain<'a> {
254 fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
255 RecordResolver {
256 name: "Chain",
257 get_field: Box::new(move |idx, _data| match idx {
258 0usize => Some(Field::new("default_flags", self.default_flags())),
259 _ => None,
260 }),
261 data,
262 }
263 }
264}
265
266#[cfg(feature = "experimental_traverse")]
267impl<'a> SomeRecord<'a> for Subtable<'a> {
268 fn traverse(self, data: FontData<'a>) -> RecordResolver<'a> {
269 RecordResolver {
270 name: "Subtable",
271 get_field: Box::new(move |idx, _data| match idx {
272 0usize => Some(Field::new("coverage", self.coverage())),
273 1usize => Some(Field::new("sub_feature_flags", self.sub_feature_flags())),
274 _ => None,
275 }),
276 data,
277 }
278 }
279}