ttf_parser/tables/
morx.rs

1//! An [Extended Glyph Metamorphosis Table](
2//! https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html) implementation.
3
4// Note: We do not have tests for this table because it has a very complicated structure.
5// Specifically, the State Machine Tables. I have no idea how to generate them.
6// And all fonts that use this table are mainly Apple one, so we cannot use them for legal reasons.
7//
8// On the other hand, this table is tested indirectly by https://github.com/harfbuzz/rustybuzz
9// And it has like 170 tests. Which is pretty good.
10// Therefore after applying any changes to this table,
11// you have to check that all rustybuzz tests are still passing.
12
13use core::num::NonZeroU16;
14
15use crate::parser::{FromData, LazyArray32, NumFrom, Offset, Offset32, Stream};
16use crate::{aat, GlyphId};
17
18/// The feature table is used to compute the sub-feature flags
19/// for a list of requested features and settings.
20#[derive(Clone, Copy, Debug)]
21pub struct Feature {
22    /// The type of feature.
23    pub kind: u16,
24    /// The feature's setting (aka selector).
25    pub setting: u16,
26    /// Flags for the settings that this feature and setting enables.
27    pub enable_flags: u32,
28    /// Complement of flags for the settings that this feature and setting disable.
29    pub disable_flags: u32,
30}
31
32impl FromData for Feature {
33    const SIZE: usize = 12;
34
35    #[inline]
36    fn parse(data: &[u8]) -> Option<Self> {
37        let mut s = Stream::new(data);
38        Some(Feature {
39            kind: s.read::<u16>()?,
40            setting: s.read::<u16>()?,
41            enable_flags: s.read::<u32>()?,
42            disable_flags: s.read::<u32>()?,
43        })
44    }
45}
46
47/// A contextual subtable state table trailing data.
48#[derive(Clone, Copy, Debug)]
49pub struct ContextualEntryData {
50    /// A mark index.
51    pub mark_index: u16,
52    /// A current index.
53    pub current_index: u16,
54}
55
56impl FromData for ContextualEntryData {
57    const SIZE: usize = 4;
58
59    #[inline]
60    fn parse(data: &[u8]) -> Option<Self> {
61        let mut s = Stream::new(data);
62        Some(ContextualEntryData {
63            mark_index: s.read::<u16>()?,
64            current_index: s.read::<u16>()?,
65        })
66    }
67}
68
69/// A contextual subtable.
70#[derive(Clone)]
71pub struct ContextualSubtable<'a> {
72    /// The contextual glyph substitution state table.
73    pub state: aat::ExtendedStateTable<'a, ContextualEntryData>,
74    offsets_data: &'a [u8],
75    offsets: LazyArray32<'a, Offset32>,
76    number_of_glyphs: NonZeroU16,
77}
78
79impl<'a> ContextualSubtable<'a> {
80    fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option<Self> {
81        let mut s = Stream::new(data);
82
83        let state = aat::ExtendedStateTable::parse(number_of_glyphs, &mut s)?;
84
85        // While the spec clearly states that this is an
86        // 'offset from the beginning of the state subtable',
87        // it's actually not. Subtable header should not be included.
88        let offset = s.read::<Offset32>()?.to_usize();
89
90        // The offsets list is unsized.
91        let offsets_data = data.get(offset..)?;
92        let offsets = LazyArray32::<Offset32>::new(offsets_data);
93
94        Some(ContextualSubtable {
95            state,
96            offsets_data,
97            offsets,
98            number_of_glyphs,
99        })
100    }
101
102    /// Returns a [Lookup](aat::Lookup) at index.
103    pub fn lookup(&self, index: u32) -> Option<aat::Lookup<'a>> {
104        let offset = self.offsets.get(index)?.to_usize();
105        let lookup_data = self.offsets_data.get(offset..)?;
106        aat::Lookup::parse(self.number_of_glyphs, lookup_data)
107    }
108}
109
110impl core::fmt::Debug for ContextualSubtable<'_> {
111    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
112        write!(f, "ContextualSubtable {{ ... }}")
113    }
114}
115
116/// A ligature subtable.
117#[derive(Clone, Debug)]
118pub struct LigatureSubtable<'a> {
119    /// A state table.
120    pub state: aat::ExtendedStateTable<'a, u16>,
121    /// Ligature actions.
122    pub ligature_actions: LazyArray32<'a, u32>,
123    /// Ligature components.
124    pub components: LazyArray32<'a, u16>,
125    /// Ligatures.
126    pub ligatures: LazyArray32<'a, GlyphId>,
127}
128
129impl<'a> LigatureSubtable<'a> {
130    fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option<Self> {
131        let mut s = Stream::new(data);
132
133        let state = aat::ExtendedStateTable::parse(number_of_glyphs, &mut s)?;
134
135        // Offset are from `ExtendedStateTable`/`data`, not from subtable start.
136        let ligature_action_offset = s.read::<Offset32>()?.to_usize();
137        let component_offset = s.read::<Offset32>()?.to_usize();
138        let ligature_offset = s.read::<Offset32>()?.to_usize();
139
140        // All three arrays are unsized, so we're simply reading/mapping all the data past offset.
141        let ligature_actions = LazyArray32::<u32>::new(data.get(ligature_action_offset..)?);
142        let components = LazyArray32::<u16>::new(data.get(component_offset..)?);
143        let ligatures = LazyArray32::<GlyphId>::new(data.get(ligature_offset..)?);
144
145        Some(LigatureSubtable {
146            state,
147            ligature_actions,
148            components,
149            ligatures,
150        })
151    }
152}
153
154/// A contextual subtable state table trailing data.
155#[derive(Clone, Copy, Debug)]
156pub struct InsertionEntryData {
157    /// A current insert index.
158    pub current_insert_index: u16,
159    /// A marked insert index.
160    pub marked_insert_index: u16,
161}
162
163impl FromData for InsertionEntryData {
164    const SIZE: usize = 4;
165
166    #[inline]
167    fn parse(data: &[u8]) -> Option<Self> {
168        let mut s = Stream::new(data);
169        Some(InsertionEntryData {
170            current_insert_index: s.read::<u16>()?,
171            marked_insert_index: s.read::<u16>()?,
172        })
173    }
174}
175
176/// An insertion subtable.
177#[derive(Clone, Debug)]
178pub struct InsertionSubtable<'a> {
179    /// A state table.
180    pub state: aat::ExtendedStateTable<'a, InsertionEntryData>,
181    /// Insertion glyphs.
182    pub glyphs: LazyArray32<'a, GlyphId>,
183}
184
185impl<'a> InsertionSubtable<'a> {
186    fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option<Self> {
187        let mut s = Stream::new(data);
188        let state = aat::ExtendedStateTable::parse(number_of_glyphs, &mut s)?;
189        let offset = s.read::<Offset32>()?.to_usize();
190
191        // TODO: unsized array?
192        // The list is unsized.
193        let glyphs = LazyArray32::<GlyphId>::new(data.get(offset..)?);
194
195        Some(InsertionSubtable { state, glyphs })
196    }
197}
198
199/// A subtable kind.
200#[allow(missing_docs)]
201#[derive(Clone, Debug)]
202pub enum SubtableKind<'a> {
203    Rearrangement(aat::ExtendedStateTable<'a, ()>),
204    Contextual(ContextualSubtable<'a>),
205    Ligature(LigatureSubtable<'a>),
206    NonContextual(aat::Lookup<'a>),
207    Insertion(InsertionSubtable<'a>),
208}
209
210/// A subtable coverage.
211#[derive(Clone, Copy, Debug)]
212pub struct Coverage(u8);
213
214#[rustfmt::skip]
215impl Coverage {
216    /// If true, this subtable will process glyphs in logical order
217    /// (or reverse logical order if [`is_vertical`](Self::is_vertical) is also true).
218    #[inline] pub fn is_logical(self) -> bool { self.0 & 0x10 != 0 }
219    /// If true, this subtable will be applied to both horizontal and vertical text
220    /// ([`is_vertical`](Self::is_vertical) should be ignored).
221    #[inline] pub fn is_all_directions(self) -> bool { self.0 & 0x20 != 0 }
222    /// If true, this subtable will process glyphs in descending order.
223    #[inline] pub fn is_backwards(self) -> bool { self.0 & 0x40 != 0 }
224    /// If true, this subtable will only be applied to vertical text.
225    #[inline] pub fn is_vertical(self) -> bool { self.0 & 0x80 != 0 }
226}
227
228/// A subtable in a metamorphosis chain.
229#[derive(Clone, Debug)]
230pub struct Subtable<'a> {
231    /// A subtable kind.
232    pub kind: SubtableKind<'a>,
233    /// A subtable coverage.
234    pub coverage: Coverage,
235    /// Subtable feature flags.
236    pub feature_flags: u32,
237}
238
239/// A list of subtables in a metamorphosis chain.
240///
241/// The internal data layout is not designed for random access,
242/// therefore we're not providing the `get()` method and only an iterator.
243#[derive(Clone, Copy)]
244pub struct Subtables<'a> {
245    count: u32,
246    data: &'a [u8],
247    number_of_glyphs: NonZeroU16,
248}
249
250impl<'a> IntoIterator for Subtables<'a> {
251    type Item = Subtable<'a>;
252    type IntoIter = SubtablesIter<'a>;
253
254    #[inline]
255    fn into_iter(self) -> Self::IntoIter {
256        SubtablesIter {
257            index: 0,
258            count: self.count,
259            stream: Stream::new(self.data),
260            number_of_glyphs: self.number_of_glyphs,
261        }
262    }
263}
264
265impl core::fmt::Debug for Subtables<'_> {
266    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
267        write!(f, "Subtables {{ ... }}")
268    }
269}
270
271/// An iterator over a metamorphosis chain subtables.
272#[allow(missing_debug_implementations)]
273#[derive(Clone)]
274pub struct SubtablesIter<'a> {
275    index: u32,
276    count: u32,
277    stream: Stream<'a>,
278    number_of_glyphs: NonZeroU16,
279}
280
281impl<'a> Iterator for SubtablesIter<'a> {
282    type Item = Subtable<'a>;
283
284    fn next(&mut self) -> Option<Self::Item> {
285        if self.index == self.count {
286            return None;
287        }
288
289        let s = &mut self.stream;
290        if s.at_end() {
291            return None;
292        }
293
294        let len = s.read::<u32>()?;
295        let coverage = Coverage(s.read::<u8>()?);
296        s.skip::<u16>(); // reserved
297        let kind = s.read::<u8>()?;
298        let feature_flags = s.read::<u32>()?;
299
300        const HEADER_LEN: usize = 12;
301        let len = usize::num_from(len).checked_sub(HEADER_LEN)?;
302        let subtables_data = s.read_bytes(len)?;
303
304        let kind = match kind {
305            0 => {
306                let mut s = Stream::new(subtables_data);
307                let table = aat::ExtendedStateTable::parse(self.number_of_glyphs, &mut s)?;
308                SubtableKind::Rearrangement(table)
309            }
310            1 => {
311                let table = ContextualSubtable::parse(self.number_of_glyphs, subtables_data)?;
312                SubtableKind::Contextual(table)
313            }
314            2 => {
315                let table = LigatureSubtable::parse(self.number_of_glyphs, subtables_data)?;
316                SubtableKind::Ligature(table)
317            }
318            // 3 - reserved
319            4 => SubtableKind::NonContextual(aat::Lookup::parse(
320                self.number_of_glyphs,
321                subtables_data,
322            )?),
323            5 => {
324                let table = InsertionSubtable::parse(self.number_of_glyphs, subtables_data)?;
325                SubtableKind::Insertion(table)
326            }
327            _ => return None,
328        };
329
330        Some(Subtable {
331            kind,
332            coverage,
333            feature_flags,
334        })
335    }
336}
337
338/// A metamorphosis chain.
339#[derive(Clone, Copy, Debug)]
340pub struct Chain<'a> {
341    /// Default chain features.
342    pub default_flags: u32,
343    /// A list of chain features.
344    pub features: LazyArray32<'a, Feature>,
345    /// A list of chain subtables.
346    pub subtables: Subtables<'a>,
347}
348
349/// A list of metamorphosis chains.
350///
351/// The internal data layout is not designed for random access,
352/// therefore we're not providing the `get()` method and only an iterator.
353#[derive(Clone, Copy)]
354pub struct Chains<'a> {
355    data: &'a [u8],
356    count: u32,
357    number_of_glyphs: NonZeroU16,
358}
359
360impl<'a> Chains<'a> {
361    fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option<Self> {
362        let mut s = Stream::new(data);
363
364        s.skip::<u16>(); // version
365        s.skip::<u16>(); // reserved
366        let count = s.read::<u32>()?;
367
368        Some(Chains {
369            count,
370            data: s.tail()?,
371            number_of_glyphs,
372        })
373    }
374}
375
376impl<'a> IntoIterator for Chains<'a> {
377    type Item = Chain<'a>;
378    type IntoIter = ChainsIter<'a>;
379
380    #[inline]
381    fn into_iter(self) -> Self::IntoIter {
382        ChainsIter {
383            index: 0,
384            count: self.count,
385            stream: Stream::new(self.data),
386            number_of_glyphs: self.number_of_glyphs,
387        }
388    }
389}
390
391impl core::fmt::Debug for Chains<'_> {
392    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
393        write!(f, "Chains {{ ... }}")
394    }
395}
396
397/// An iterator over metamorphosis chains.
398#[allow(missing_debug_implementations)]
399#[derive(Clone)]
400pub struct ChainsIter<'a> {
401    index: u32,
402    count: u32,
403    stream: Stream<'a>,
404    number_of_glyphs: NonZeroU16,
405}
406
407impl<'a> Iterator for ChainsIter<'a> {
408    type Item = Chain<'a>;
409
410    fn next(&mut self) -> Option<Self::Item> {
411        if self.index == self.count {
412            return None;
413        }
414
415        if self.stream.at_end() {
416            return None;
417        }
418
419        let default_flags = self.stream.read::<u32>()?;
420        let len = self.stream.read::<u32>()?;
421        let features_count = self.stream.read::<u32>()?;
422        let subtables_count = self.stream.read::<u32>()?;
423
424        let features = self.stream.read_array32::<Feature>(features_count)?;
425
426        const HEADER_LEN: usize = 16;
427        let len = usize::num_from(len)
428            .checked_sub(HEADER_LEN)?
429            .checked_sub(Feature::SIZE * usize::num_from(features_count))?;
430
431        let subtables_data = self.stream.read_bytes(len)?;
432
433        let subtables = Subtables {
434            data: subtables_data,
435            count: subtables_count,
436            number_of_glyphs: self.number_of_glyphs,
437        };
438
439        Some(Chain {
440            default_flags,
441            features,
442            subtables,
443        })
444    }
445}
446
447/// An [Extended Glyph Metamorphosis Table](
448/// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html).
449///
450/// Subtable Glyph Coverage used by morx v3 is not supported.
451#[derive(Clone)]
452pub struct Table<'a> {
453    /// A list of metamorphosis chains.
454    pub chains: Chains<'a>,
455}
456
457impl core::fmt::Debug for Table<'_> {
458    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
459        write!(f, "Table {{ ... }}")
460    }
461}
462
463impl<'a> Table<'a> {
464    /// Parses a table from raw data.
465    ///
466    /// `number_of_glyphs` is from the `maxp` table.
467    pub fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option<Self> {
468        Chains::parse(number_of_glyphs, data).map(|chains| Self { chains })
469    }
470}