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 | |
13 | use core::num::NonZeroU16; |
14 | |
15 | use crate::parser::{FromData, LazyArray32, NumFrom, Offset, Offset32, Stream}; |
16 | use 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)] |
21 | pub 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 | |
32 | impl 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)] |
49 | pub struct ContextualEntryData { |
50 | /// A mark index. |
51 | pub mark_index: u16, |
52 | /// A current index. |
53 | pub current_index: u16, |
54 | } |
55 | |
56 | impl 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)] |
71 | pub 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 | |
79 | impl<'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 | |
110 | impl 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)] |
118 | pub 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 | |
129 | impl<'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)] |
156 | pub 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 | |
163 | impl 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)] |
178 | pub 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 | |
185 | impl<'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)] |
202 | pub 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)] |
212 | pub struct Coverage(u8); |
213 | |
214 | #[rustfmt::skip] |
215 | impl 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)] |
230 | pub 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)] |
244 | pub struct Subtables<'a> { |
245 | count: u32, |
246 | data: &'a [u8], |
247 | number_of_glyphs: NonZeroU16, |
248 | } |
249 | |
250 | impl<'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 | |
265 | impl 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)] |
274 | pub struct SubtablesIter<'a> { |
275 | index: u32, |
276 | count: u32, |
277 | stream: Stream<'a>, |
278 | number_of_glyphs: NonZeroU16, |
279 | } |
280 | |
281 | impl<'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)] |
340 | pub 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)] |
354 | pub struct Chains<'a> { |
355 | data: &'a [u8], |
356 | count: u32, |
357 | number_of_glyphs: NonZeroU16, |
358 | } |
359 | |
360 | impl<'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 | |
376 | impl<'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 | |
391 | impl 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)] |
400 | pub struct ChainsIter<'a> { |
401 | index: u32, |
402 | count: u32, |
403 | stream: Stream<'a>, |
404 | number_of_glyphs: NonZeroU16, |
405 | } |
406 | |
407 | impl<'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)] |
452 | pub struct Table<'a> { |
453 | /// A list of metamorphosis chains. |
454 | pub chains: Chains<'a>, |
455 | } |
456 | |
457 | impl core::fmt::Debug for Table<'_> { |
458 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
459 | write!(f, "Table {{ ... }}" ) |
460 | } |
461 | } |
462 | |
463 | impl<'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 | |