Skip to main content

read_fonts/tables/
mort.rs

1//! The [mort (Glyph Metamorphosis)](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6mort.html) table.
2
3use 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    /// If true, this subtable will process glyphs in logical order.
27    #[inline]
28    pub fn is_logical(&self) -> bool {
29        self.coverage() & 0x1000 != 0
30    }
31
32    /// If true, this subtable applies to horizontal and vertical text.
33    #[inline]
34    pub fn is_all_directions(&self) -> bool {
35        self.coverage() & 0x2000 != 0
36    }
37
38    /// If true, this subtable processes glyphs in descending order.
39    #[inline]
40    pub fn is_backwards(&self) -> bool {
41        self.coverage() & 0x4000 != 0
42    }
43
44    /// If true, this subtable applies only to vertical text.
45    #[inline]
46    pub fn is_vertical(&self) -> bool {
47        self.coverage() & 0x8000 != 0
48    }
49
50    /// Returns the format-specific subtable data.
51    pub fn kind(&self) -> Result<SubtableKind<'a>, ReadError> {
52        SubtableKind::read_with_args(FontData::new(self.data()), self.coverage())
53    }
54}
55
56/// The various `mort` subtable formats.
57#[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/// Pre-resolved, lifetime-free description of a `mort` subtable's layout.
84#[derive(Clone, Copy, Debug, Default)]
85pub struct SubtableParts {
86    /// Low byte of coverage: the subtable format (0/1/2/4/5).
87    pub format: u8,
88    pub state: LegacyStateTableParts,
89    /// Format-dependent offsets following the state-table header.
90    pub extra: [u16; 3],
91}
92
93impl SubtableKind<'_> {
94    /// Captures the offsets needed to rebuild this subtable kind from the same data.
95    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    /// Rebuilds a subtable kind from data and previously captured offsets.
120    #[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/// Contextual glyph substitution subtable.
149#[derive(Clone)]
150pub struct ContextualSubtable<'a> {
151    pub state_table: StateTable<'a, ContextualEntryData>,
152    data: FontData<'a>,
153}
154
155impl ContextualSubtable<'_> {
156    /// Resolves a legacy signed word offset for the specified glyph.
157    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/// Ligature glyph substitution subtable.
184#[derive(Clone)]
185pub struct LigatureSubtable<'a> {
186    pub state_table: StateTable<'a>,
187    data: FontData<'a>,
188}
189
190impl LigatureSubtable<'_> {
191    /// Reads an action at an absolute byte offset from the subtable start.
192    pub fn ligature_action(&self, offset: usize) -> Result<u32, ReadError> {
193        self.data.read_at(offset)
194    }
195
196    /// Reads a component at an absolute word offset from the subtable start.
197    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    /// Reads a ligature glyph at an absolute byte offset from the subtable start.
206    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/// Insertion glyph substitution subtable.
228#[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}