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/RazrFalcon/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<'_> = 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<'_> = 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<'_> = Stream::new(data);
132
133 let state: ExtendedStateTable<'_, u16> = aat::ExtendedStateTable::parse(number_of_glyphs, &mut s)?;
134
135 // Offset are from `ExtendedStateTable`/`data`, not from subtable start.
136 let ligature_action_offset: usize = s.read::<Offset32>()?.to_usize();
137 let component_offset: usize = s.read::<Offset32>()?.to_usize();
138 let ligature_offset: usize = 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> = LazyArray32::<u32>::new(data:data.get(index:ligature_action_offset..)?);
142 let components: LazyArray32<'_, u16> = LazyArray32::<u16>::new(data:data.get(index:component_offset..)?);
143 let ligatures: LazyArray32<'_, GlyphId> = LazyArray32::<GlyphId>::new(data:data.get(index: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<'_> = 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<'_> = Stream::new(data);
188 let state: ExtendedStateTable<'_, InsertionEntryData> = aat::ExtendedStateTable::parse(number_of_glyphs, &mut s)?;
189 let offset: usize = s.read::<Offset32>()?.to_usize();
190
191 // TODO: unsized array?
192 // The list is unsized.
193 let glyphs: LazyArray32<'_, GlyphId> = LazyArray32::<GlyphId>::new(data:data.get(index: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<'_> = Stream::new(data);
363
364 s.skip::<u16>(); // version
365 s.skip::<u16>(); // reserved
366 let count: u32 = 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: Chains<'_>| Self { chains })
469 }
470}
471