read_fonts/tables/
morx.rs

1//! The [morx (Extended Glyph Metamorphosis)](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html) table.
2
3use 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        // Size in a chain is second field beyond 4 byte `defaultFlags`
12        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        // The default implementation assumes that the length field itself
23        // is not included in the total size which is not true of this
24        // table.
25        data.read_at::<u32>(pos).ok().map(|size| size as usize)
26    }
27}
28
29impl<'a> Subtable<'a> {
30    /// If true, this subtable will process glyphs in logical order (or reverse
31    /// logical order, depending on the value of bit 0x80000000).
32    #[inline]
33    pub fn is_logical(&self) -> bool {
34        self.coverage() & 0x10000000 != 0
35    }
36
37    /// If true, this subtable will be applied to both horizontal and vertical
38    /// text (i.e. the state of bit 0x80000000 is ignored).
39    #[inline]
40    pub fn is_all_directions(&self) -> bool {
41        self.coverage() & 0x20000000 != 0
42    }
43
44    /// If true, this subtable will process glyphs in descending order.
45    /// Otherwise, it will process the glyphs in ascending order.
46    #[inline]
47    pub fn is_backwards(&self) -> bool {
48        self.coverage() & 0x40000000 != 0
49    }
50
51    /// If true, this subtable will only be applied to vertical text.
52    /// Otherwise, this subtable will only be applied to horizontal
53    /// text.
54    #[inline]
55    pub fn is_vertical(&self) -> bool {
56        self.coverage() & 0x80000000 != 0
57    }
58
59    /// Returns an enum representing the actual subtable data.
60    pub fn kind(&self) -> Result<SubtableKind<'a>, ReadError> {
61        SubtableKind::read_with_args(FontData::new(self.data()), &self.coverage())
62    }
63}
64
65/// The various `morx` subtable formats.
66#[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        // Format is low byte of coverage
82        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            // 3 is reserved
88            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/// Contextual glyph substitution subtable.
96#[derive(Clone)]
97pub struct ContextualSubtable<'a> {
98    pub state_table: ExtendedStateTable<'a, ContextualEntryData>,
99    /// List of lookups specifying substitutions. The index into this array
100    /// is specified by the action in the state table.
101    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/// Ligature glyph substitution subtable.
122#[derive(Clone)]
123pub struct LigatureSubtable<'a> {
124    pub state_table: ExtendedStateTable<'a, BigEndian<u16>>,
125    /// Contains the set of ligature stack actions, one for each state.
126    pub ligature_actions: &'a [BigEndian<u32>],
127    /// Array of component indices which are summed to determine the index
128    /// of the final ligature glyph.
129    pub components: &'a [BigEndian<u16>],
130    /// Output ligature glyphs.
131    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        // None of these arrays have associated sizes, so we just read until
140        // the end of the data.
141        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/// Insertion glyph substitution subtable.
157#[derive(Clone)]
158pub struct InsertionSubtable<'a> {
159    pub state_table: ExtendedStateTable<'a, InsertionEntryData>,
160    /// Insertion glyph table. The index and count of glyphs to insert is
161    /// determined by the state machine.
162    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// Literal bytes are grouped according to layout in the spec
210// for readabiity
211#[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        // Rearrangement is just a state table
237        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        // Note, we limit the number of elements because the arrays do not
270        // have specified lengths in the table
271        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        // See above explanation for the limit
280        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}