Skip to main content

read_fonts/tables/
aat.rs

1//! Apple Advanced Typography common tables.
2//!
3//! See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html>
4
5include!("../../generated/generated_aat.rs");
6
7/// Predefined classes.
8///
9/// See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html>
10pub mod class {
11    pub const END_OF_TEXT: u8 = 0;
12    pub const OUT_OF_BOUNDS: u8 = 1;
13    pub const DELETED_GLYPH: u8 = 2;
14}
15
16impl Lookup0<'_> {
17    pub fn values<T: LookupValue>(&self) -> Result<&[BigEndian<T>], ReadError> {
18        let data = self.values_data();
19        let data_len = data.len();
20        let n_elems = data_len / T::RAW_BYTE_LEN;
21        let len_in_bytes = n_elems * T::RAW_BYTE_LEN;
22        FontData::new(&data[..len_in_bytes])
23            .cursor()
24            .read_array::<BigEndian<T>>(n_elems)
25    }
26    #[inline]
27    pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
28        self.values::<T>()?
29            .get(index as usize)
30            .map(|val| val.get())
31            .ok_or(ReadError::OutOfBounds)
32    }
33}
34
35/// Lookup segment for format 2.
36#[derive(Copy, Clone, bytemuck::AnyBitPattern)]
37#[repr(C, packed)]
38pub struct LookupSegment2<T>
39where
40    T: LookupValue,
41{
42    /// Last glyph index in this segment.
43    pub last_glyph: BigEndian<u16>,
44    /// First glyph index in this segment.
45    pub first_glyph: BigEndian<u16>,
46    /// The lookup value.
47    pub value: BigEndian<T>,
48}
49
50/// Note: this requires `LookupSegment2` to be `repr(packed)`.
51impl<T: LookupValue> FixedSize for LookupSegment2<T> {
52    const RAW_BYTE_LEN: usize = std::mem::size_of::<Self>();
53}
54
55impl Lookup2<'_> {
56    #[inline]
57    pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
58        let segments = self.segments::<T>()?;
59        let ix = match segments.binary_search_by(|segment| segment.first_glyph.get().cmp(&index)) {
60            Ok(ix) => ix,
61            Err(ix) => ix.saturating_sub(1),
62        };
63        let segment = segments.get(ix).ok_or(ReadError::OutOfBounds)?;
64        if (segment.first_glyph.get()..=segment.last_glyph.get()).contains(&index) {
65            let value = segment.value;
66            return Ok(value.get());
67        }
68        Err(ReadError::OutOfBounds)
69    }
70
71    pub fn segments<T: LookupValue>(&self) -> Result<&[LookupSegment2<T>], ReadError> {
72        FontData::new(self.segments_data())
73            .cursor()
74            .read_array(self.n_units() as usize)
75    }
76}
77
78impl Lookup4<'_> {
79    #[inline]
80    pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
81        let segments = self.segments();
82        let ix = match segments.binary_search_by(|segment| segment.first_glyph.get().cmp(&index)) {
83            Ok(ix) => ix,
84            Err(ix) => ix.saturating_sub(1),
85        };
86        let segment = segments.get(ix).ok_or(ReadError::OutOfBounds)?;
87        if (segment.first_glyph.get()..=segment.last_glyph.get()).contains(&index) {
88            let base_offset = segment.value_offset() as usize;
89            let offset = base_offset
90                + index
91                    .checked_sub(segment.first_glyph())
92                    .ok_or(ReadError::OutOfBounds)? as usize
93                    * T::RAW_BYTE_LEN;
94            return self.offset_data().read_at(offset);
95        }
96        Err(ReadError::OutOfBounds)
97    }
98    pub fn segment_values<T: LookupValue>(
99        &self,
100        segment: usize,
101    ) -> Result<&[BigEndian<T>], ReadError> {
102        let segment = self.segments().get(segment).ok_or(ReadError::OutOfBounds)?;
103        let base_offset = segment.value_offset() as usize;
104        let n_elems = segment
105            .last_glyph
106            .get()
107            .checked_sub(segment.first_glyph.get())
108            .ok_or(ReadError::MalformedData(
109                "invalid segment in format 4 AAT lookup table",
110            ))? as usize
111            + 1;
112        self.offset_data()
113            .read_array::<BigEndian<T>>(base_offset..base_offset + n_elems * T::RAW_BYTE_LEN)
114    }
115}
116
117/// Lookup single record for format 6.
118#[derive(Copy, Clone, bytemuck::AnyBitPattern)]
119#[repr(C, packed)]
120pub struct LookupSingle<T>
121where
122    T: LookupValue,
123{
124    /// The glyph index.
125    pub glyph: BigEndian<u16>,
126    /// The lookup value.
127    pub value: BigEndian<T>,
128}
129
130/// Note: this requires `LookupSingle` to be `repr(packed)`.
131impl<T: LookupValue> FixedSize for LookupSingle<T> {
132    const RAW_BYTE_LEN: usize = std::mem::size_of::<Self>();
133}
134
135impl Lookup6<'_> {
136    #[inline]
137    pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
138        let entries = self.entries::<T>()?;
139        if let Ok(ix) = entries.binary_search_by_key(&index, |entry| entry.glyph.get()) {
140            let entry = &entries[ix];
141            let value = entry.value;
142            return Ok(value.get());
143        }
144        Err(ReadError::OutOfBounds)
145    }
146
147    pub fn entries<T: LookupValue>(&self) -> Result<&[LookupSingle<T>], ReadError> {
148        FontData::new(self.entries_data())
149            .cursor()
150            .read_array(self.n_units() as usize)
151    }
152}
153
154impl Lookup8<'_> {
155    #[inline]
156    pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
157        index
158            .checked_sub(self.first_glyph())
159            .and_then(|ix| {
160                self.value_array()
161                    .get(ix as usize)
162                    .map(|val| T::from_u16(val.get()))
163            })
164            .ok_or(ReadError::OutOfBounds)
165    }
166}
167
168impl Lookup10<'_> {
169    #[inline]
170    pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
171        let ix = index
172            .checked_sub(self.first_glyph())
173            .ok_or(ReadError::OutOfBounds)? as usize;
174        let unit_size = self.unit_size() as usize;
175        let offset = ix.wrapping_mul(unit_size);
176        let mut cursor = FontData::new(self.values_data()).cursor();
177        cursor.advance_by(offset);
178        let val = match unit_size {
179            1 => cursor.read::<u8>()? as u32,
180            2 => cursor.read::<u16>()? as u32,
181            4 => cursor.read::<u32>()?,
182            _ => {
183                return Err(ReadError::MalformedData(
184                    "invalid unit_size in format 10 AAT lookup table",
185                ))
186            }
187        };
188        Ok(T::from_u32(val))
189    }
190}
191
192impl Lookup<'_> {
193    #[inline]
194    pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
195        match self {
196            Lookup::Format0(lookup) => lookup.value::<T>(index),
197            Lookup::Format2(lookup) => lookup.value::<T>(index),
198            Lookup::Format4(lookup) => lookup.value::<T>(index),
199            Lookup::Format6(lookup) => lookup.value::<T>(index),
200            Lookup::Format8(lookup) => lookup.value::<T>(index),
201            Lookup::Format10(lookup) => lookup.value::<T>(index),
202        }
203    }
204}
205
206#[derive(Clone)]
207pub struct TypedLookup<'a, T> {
208    pub lookup: Lookup<'a>,
209    _marker: std::marker::PhantomData<fn() -> T>,
210}
211
212impl<T: LookupValue> TypedLookup<'_, T> {
213    /// Returns the value associated with the given index.
214    pub fn value(&self, index: u16) -> Result<T, ReadError> {
215        self.lookup.value::<T>(index)
216    }
217}
218
219impl<T> ReadArgs for TypedLookup<'_, T> {
220    type Args = ();
221}
222
223impl<'a, T> FontRead<'a> for TypedLookup<'a, T> {
224    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
225        Ok(Self {
226            lookup: Lookup::read(data)?,
227            _marker: std::marker::PhantomData,
228        })
229    }
230}
231
232#[cfg(feature = "experimental_traverse")]
233impl<'a, T> SomeTable<'a> for TypedLookup<'a, T> {
234    fn type_name(&self) -> &str {
235        "TypedLookup"
236    }
237
238    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
239        self.lookup.get_field(idx)
240    }
241}
242
243/// Trait for values that can be read from lookup tables.
244pub trait LookupValue: Copy + Scalar + bytemuck::AnyBitPattern {
245    fn from_u16(v: u16) -> Self;
246    fn from_u32(v: u32) -> Self;
247}
248
249impl LookupValue for u16 {
250    fn from_u16(v: u16) -> Self {
251        v
252    }
253
254    fn from_u32(v: u32) -> Self {
255        // intentionally truncates
256        v as _
257    }
258}
259
260impl LookupValue for u32 {
261    fn from_u16(v: u16) -> Self {
262        v as _
263    }
264
265    fn from_u32(v: u32) -> Self {
266        v
267    }
268}
269
270impl LookupValue for GlyphId16 {
271    fn from_u16(v: u16) -> Self {
272        GlyphId16::from(v)
273    }
274
275    fn from_u32(v: u32) -> Self {
276        // intentionally truncates
277        GlyphId16::from(v as u16)
278    }
279}
280
281pub type LookupU16<'a> = TypedLookup<'a, u16>;
282pub type LookupU32<'a> = TypedLookup<'a, u32>;
283pub type LookupGlyphId<'a> = TypedLookup<'a, GlyphId16>;
284
285/// Empty data type for a state table entry with no payload.
286///
287/// Note: this type is only intended for use as the type parameter for
288/// `StateEntry`. The inner field is private and this type cannot be
289/// constructed outside of this module.
290#[derive(Copy, Clone, bytemuck::AnyBitPattern, Debug)]
291pub struct NoPayload(());
292
293impl FixedSize for NoPayload {
294    const RAW_BYTE_LEN: usize = 0;
295}
296
297/// Entry in an (extended) state table.
298#[derive(Clone, Debug)]
299pub struct StateEntry<T = NoPayload> {
300    /// Index of the next state.
301    pub new_state: u16,
302    /// Flag values are table specific.
303    pub flags: u16,
304    /// Payload is table specific.
305    pub payload: T,
306}
307
308impl<T: bytemuck::AnyBitPattern + FixedSize> ReadArgs for StateEntry<T> {
309    type Args = ();
310}
311
312impl<'a, T: bytemuck::AnyBitPattern + FixedSize> FontRead<'a> for StateEntry<T> {
313    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
314        let mut cursor = data.cursor();
315        let new_state = cursor.read()?;
316        let flags = cursor.read()?;
317        let remaining = cursor.remaining().ok_or(ReadError::OutOfBounds)?;
318        let payload = *remaining.read_ref_at(0)?;
319        Ok(Self {
320            new_state,
321            flags,
322            payload,
323        })
324    }
325}
326
327impl<T> FixedSize for StateEntry<T>
328where
329    T: FixedSize,
330{
331    // Two u16 fields + payload
332    const RAW_BYTE_LEN: usize = u16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + T::RAW_BYTE_LEN;
333}
334
335/// Table for driving a finite state machine for layout.
336///
337/// The input to the state machine consists of the current state
338/// and a glyph class. The output is an [entry](StateEntry) containing
339/// the next state and a payload that is dependent on the type of
340/// layout action being performed.
341///
342/// See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#StateHeader>
343/// for more detail.
344#[derive(Clone)]
345pub struct StateTable<'a, T = NoPayload> {
346    pub header: StateHeader<'a>,
347    pub n_classes: usize,
348    class_first_glyph: u16,
349    class_array: &'a [u8],
350    state_array: &'a [u8],
351    entry_table: &'a [u8],
352    /// floor(2^32 / n_classes) + 1: exact reciprocal for dividends < 2^16,
353    /// so the per-transition new-state conversion avoids a hardware divide.
354    n_classes_magic: u64,
355    _marker: std::marker::PhantomData<fn() -> T>,
356}
357
358impl<T> StateTable<'_, T> {
359    pub const HEADER_LEN: usize = u16::RAW_BYTE_LEN * 4;
360}
361
362impl<T> StateTable<'_, T>
363where
364    T: FixedSize + bytemuck::AnyBitPattern,
365{
366    /// Returns the class table entry for the given glyph identifier.
367    pub fn class(&self, glyph_id: GlyphId16) -> Result<u8, ReadError> {
368        let glyph_id = glyph_id.to_u16();
369        if glyph_id == 0xFFFF {
370            return Ok(class::DELETED_GLYPH);
371        }
372        glyph_id
373            .checked_sub(self.class_first_glyph)
374            .and_then(|ix| self.class_array.get(ix as usize).copied())
375            .ok_or(ReadError::OutOfBounds)
376    }
377
378    /// Returns the first covered glyph and its consecutive class values.
379    pub fn class_mappings(&self) -> (u16, &'_ [u8]) {
380        (self.class_first_glyph, self.class_array)
381    }
382
383    /// Returns the entry for the given state and class.
384    #[inline(always)]
385    pub fn entry(&self, state: u16, class: u8) -> Result<StateEntry<T>, ReadError> {
386        let mut class = class as usize;
387        if class >= self.n_classes {
388            class = class::OUT_OF_BOUNDS as usize;
389        }
390        let entry_ix = self
391            .state_array
392            .get(state as usize * self.n_classes + class)
393            .copied()
394            .ok_or(ReadError::OutOfBounds)? as usize;
395        let entry_offset = entry_ix.wrapping_mul(StateEntry::<T>::RAW_BYTE_LEN);
396        let entry_data = self
397            .entry_table
398            .get(entry_offset..)
399            .ok_or(ReadError::OutOfBounds)?;
400        let mut entry = StateEntry::read(FontData::new(entry_data))?;
401        // For legacy state tables, the newState is a byte offset into
402        // the state array. Convert this to an index for consistency.
403        let offset = self.header.state_array_offset().to_u32() as i32;
404        let diff = entry.new_state as i32 - offset;
405        let new_state = if diff >= 0 {
406            // Multiply by the precomputed reciprocal instead of dividing;
407            // exact for all dividends below 2^16, and this is the per-
408            // transition hot path of legacy state machines.
409            ((diff as u64 * self.n_classes_magic) >> 32) as i32
410        } else {
411            diff / self.n_classes as i32
412        };
413        entry.new_state = new_state.try_into().map_err(|_| ReadError::OutOfBounds)?;
414        Ok(entry)
415    }
416
417    /// Reads scalar values that are referenced from state table entries.
418    pub fn read_value<S: Scalar>(&self, offset: usize) -> Result<S, ReadError> {
419        self.header.offset_data().read_at::<S>(offset)
420    }
421}
422
423/// Pre-resolved byte offsets of a legacy [StateTable]'s components, relative
424/// to the table start.
425#[derive(Clone, Copy, Debug, Default)]
426pub struct LegacyStateTableParts {
427    pub n_classes: u16,
428    pub class_table_offset: u16,
429    pub state_array_offset: u16,
430    pub entry_table_offset: u16,
431}
432
433impl LegacyStateTableParts {
434    /// Reads the header of a legacy state table at the start of `data`.
435    pub fn read(data: FontData) -> Result<Self, ReadError> {
436        let header = StateHeader::read(data)?;
437        Ok(Self {
438            n_classes: header.state_size(),
439            class_table_offset: header.class_table_offset().to_u32() as u16,
440            state_array_offset: header.state_array_offset().to_u32() as u16,
441            entry_table_offset: header.entry_table_offset().to_u32() as u16,
442        })
443    }
444}
445
446impl<'a, T> StateTable<'a, T> {
447    /// Builds the state table from `data` and offsets previously captured
448    /// with [LegacyStateTableParts::read] on the same data.
449    #[inline]
450    pub fn from_parts(
451        data: FontData<'a>,
452        parts: &LegacyStateTableParts,
453    ) -> Result<Self, ReadError> {
454        let n_classes = parts.n_classes as usize;
455        if n_classes == 0 {
456            return Err(ReadError::MalformedData("empty AAT state table"));
457        }
458        let class_table = ClassSubtable::read(
459            data.split_off(parts.class_table_offset as usize)
460                .ok_or(ReadError::OutOfBounds)?,
461        )?;
462        let class_first_glyph = class_table.first_glyph();
463        let class_array = class_table.class_array();
464        let state_array = data
465            .as_bytes()
466            .get(parts.state_array_offset as usize..)
467            .ok_or(ReadError::OutOfBounds)?;
468        let entry_table = data
469            .as_bytes()
470            .get(parts.entry_table_offset as usize..)
471            .ok_or(ReadError::OutOfBounds)?;
472        Ok(Self {
473            header: StateHeader::read(data)?,
474            n_classes,
475            class_first_glyph,
476            class_array,
477            state_array,
478            entry_table,
479            n_classes_magic: (1u64 << 32) / n_classes as u64 + 1,
480            _marker: std::marker::PhantomData,
481        })
482    }
483}
484
485impl<'a> StateTable<'a, NoPayload> {
486    /// Reads a state table whose entries carry no payload.
487    ///
488    /// This exists so that `StateTable::read(data)` still resolves without
489    /// naming the payload type: a type parameter's default does not apply in a
490    /// path expression, only in type position. Without it, adding the payload
491    /// parameter would have broken every existing caller.
492    ///
493    /// Remove this at the next breaking release, so that callers name the
494    /// payload as they do for `ExtendedStateTable`.
495    pub fn read(data: FontData<'a>) -> Result<Self, ReadError> {
496        <Self as FontRead<'a>>::read(data)
497    }
498}
499
500impl<T> ReadArgs for StateTable<'_, T> {
501    type Args = ();
502}
503
504impl<'a, T> FontRead<'a> for StateTable<'a, T> {
505    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
506        let header = StateHeader::read(data)?;
507        // Each state has a 1-byte entry per class so state_size == n_classes
508        let n_classes = header.state_size() as usize;
509        if n_classes == 0 {
510            // This will result in a divide by 0 in all cases
511            return Err(ReadError::MalformedData("empty AAT state table"));
512        }
513        let class_table = header.class_table()?;
514        let class_first_glyph = class_table.first_glyph();
515        let class_array = class_table.class_array();
516        let state_array = header.state_array()?.data();
517        let entry_table = header.entry_table()?.data();
518        Ok(Self {
519            header: StateHeader::read(data)?,
520            n_classes,
521            class_first_glyph,
522            class_array,
523            state_array,
524            entry_table,
525            n_classes_magic: (1u64 << 32) / n_classes as u64 + 1,
526            _marker: std::marker::PhantomData,
527        })
528    }
529}
530
531#[cfg(feature = "experimental_traverse")]
532impl<'a, T> SomeTable<'a> for StateTable<'a, T> {
533    fn type_name(&self) -> &str {
534        "StateTable"
535    }
536
537    fn get_field(&self, idx: usize) -> Option<Field<'a>> {
538        self.header.get_field(idx)
539    }
540}
541
542#[derive(Clone)]
543pub struct ExtendedStateTable<'a, T = NoPayload> {
544    pub n_classes: usize,
545    pub class_table: LookupU16<'a>,
546    state_array: &'a [BigEndian<u16>],
547    entry_table: &'a [u8],
548    _marker: std::marker::PhantomData<fn() -> T>,
549}
550
551impl<T> ExtendedStateTable<'_, T> {
552    pub const HEADER_LEN: usize = u32::RAW_BYTE_LEN * 4;
553}
554
555/// Table for driving a finite state machine for layout.
556///
557/// The input to the state machine consists of the current state
558/// and a glyph class. The output is an [entry](StateEntry) containing
559/// the next state and a payload that is dependent on the type of
560/// layout action being performed.
561///
562/// See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#StateHeader>
563/// for more detail.
564impl<T> ExtendedStateTable<'_, T>
565where
566    T: FixedSize + bytemuck::AnyBitPattern,
567{
568    /// Returns the class table entry for the given glyph identifier.
569    #[inline]
570    pub fn class(&self, glyph_id: GlyphId) -> Result<u16, ReadError> {
571        let glyph_id: u16 = glyph_id
572            .to_u32()
573            .try_into()
574            .map_err(|_| ReadError::OutOfBounds)?;
575        if glyph_id == 0xFFFF {
576            return Ok(class::DELETED_GLYPH as u16);
577        }
578        self.class_table.value(glyph_id)
579    }
580
581    /// Returns the entry for the given state and class.
582    #[inline]
583    pub fn entry(&self, state: u16, class: u16) -> Result<StateEntry<T>, ReadError> {
584        let mut class = class as usize;
585        if class >= self.n_classes {
586            class = class::OUT_OF_BOUNDS as usize;
587        }
588        let state_ix = (state as usize)
589            .wrapping_mul(self.n_classes)
590            .wrapping_add(class);
591        let entry_ix = self
592            .state_array
593            .get(state_ix)
594            .copied()
595            .ok_or(ReadError::OutOfBounds)?
596            .get() as usize;
597        let entry_offset = entry_ix.wrapping_mul(StateEntry::<T>::RAW_BYTE_LEN);
598        let entry_data = self
599            .entry_table
600            .get(entry_offset..)
601            .ok_or(ReadError::OutOfBounds)?;
602        StateEntry::read(FontData::new(entry_data))
603    }
604}
605
606/// Pre-resolved byte offsets of an [ExtendedStateTable]'s components,
607/// relative to the table start. Lifetime-free, so callers can cache it and
608/// rebuild the table with [ExtendedStateTable::from_parts] without re-reading
609/// and re-validating the header.
610#[derive(Clone, Copy, Debug, Default)]
611pub struct StateTableParts {
612    pub n_classes: u32,
613    pub class_table_offset: u32,
614    pub state_array_offset: u32,
615    pub entry_table_offset: u32,
616}
617
618impl StateTableParts {
619    /// Reads the header of an extended state table at the start of `data`.
620    pub fn read(data: FontData) -> Result<Self, ReadError> {
621        let header = StxHeader::read(data)?;
622        Ok(StateTableParts {
623            n_classes: header.n_classes(),
624            class_table_offset: header.class_table_offset().to_u32(),
625            state_array_offset: header.state_array_offset().to_u32(),
626            entry_table_offset: header.entry_table_offset().to_u32(),
627        })
628    }
629}
630
631impl<'a, T> ExtendedStateTable<'a, T> {
632    /// Builds the state table from `data` and offsets previously captured
633    /// with [StateTableParts::read] on the same data.
634    #[inline]
635    pub fn from_parts(data: FontData<'a>, parts: &StateTableParts) -> Result<Self, ReadError> {
636        let class_table = LookupU16::read(
637            data.split_off(parts.class_table_offset as usize)
638                .ok_or(ReadError::OutOfBounds)?,
639        )?;
640        let state_array = safe_read_array_to_end(&data, parts.state_array_offset as usize)?;
641        let entry_table = data
642            .as_bytes()
643            .get(parts.entry_table_offset as usize..)
644            .ok_or(ReadError::OutOfBounds)?;
645        Ok(Self {
646            n_classes: parts.n_classes as usize,
647            class_table,
648            state_array,
649            entry_table,
650            _marker: std::marker::PhantomData,
651        })
652    }
653}
654
655impl<T> ReadArgs for ExtendedStateTable<'_, T> {
656    type Args = ();
657}
658
659impl<'a, T> FontRead<'a> for ExtendedStateTable<'a, T> {
660    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
661        let header = StxHeader::read(data)?;
662        let n_classes = header.n_classes() as usize;
663        let class_table = header.class_table()?;
664        let state_array = header.state_array()?.data();
665        let entry_table = header.entry_table()?.data();
666        Ok(Self {
667            n_classes,
668            class_table,
669            state_array,
670            entry_table,
671            _marker: std::marker::PhantomData,
672        })
673    }
674}
675
676#[cfg(feature = "experimental_traverse")]
677impl<'a, T> SomeTable<'a> for ExtendedStateTable<'a, T> {
678    fn type_name(&self) -> &str {
679        "ExtendedStateTable"
680    }
681
682    fn get_field(&self, _idx: usize) -> Option<Field<'a>> {
683        None
684    }
685}
686
687/// Reads an array of T from the given FontData, ensuring that the byte length
688/// is a multiple of the size of T.
689///
690/// Many of the `morx` subtables have arrays without associated lengths so we
691/// simply read to the end of the available data. The `FontData::read_array`
692/// method will fail if the byte range provided is not exact so this helper
693/// allows us to force the lengths to an acceptable value.
694pub(crate) fn safe_read_array_to_end<'a, T: bytemuck::AnyBitPattern + FixedSize>(
695    data: &FontData<'a>,
696    offset: usize,
697) -> Result<&'a [T], ReadError> {
698    let len = data
699        .len()
700        .checked_sub(offset)
701        .ok_or(ReadError::OutOfBounds)?;
702    let end = offset + len / T::RAW_BYTE_LEN * T::RAW_BYTE_LEN;
703    data.read_array(offset..end)
704}
705
706#[cfg(test)]
707mod tests {
708    use font_test_data::bebuffer::BeBuffer;
709
710    use super::*;
711
712    #[test]
713    fn lookup_format_0() {
714        #[rustfmt::skip]
715        let words = [
716            0_u16, // format
717            0, 2, 4, 6, 8, 10, 12, 14, 16, // maps all glyphs to gid * 2
718        ];
719        let mut buf = BeBuffer::new();
720        buf = buf.extend(words);
721        let lookup = LookupU16::read(buf.data().into()).unwrap();
722        for gid in 0..=8 {
723            assert_eq!(lookup.value(gid).unwrap(), gid * 2);
724        }
725        assert!(lookup.value(9).is_err());
726    }
727
728    // Taken from example 2 at https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html
729    #[test]
730    fn lookup_format_2() {
731        #[rustfmt::skip]
732        let words = [
733            2_u16, // format
734            6,     // unit size (6 bytes)
735            3,     // number of units
736            12,    // search range
737            1,     // entry selector
738            6,     // range shift
739            22, 20, 4, // First segment, mapping glyphs 20 through 22 to class 4
740            24, 23, 5, // Second segment, mapping glyph 23 and 24 to class 5
741            28, 25, 6, // Third segment, mapping glyphs 25 through 28 to class 6
742        ];
743        let mut buf = BeBuffer::new();
744        buf = buf.extend(words);
745        let lookup = LookupU16::read(buf.data().into()).unwrap();
746        let expected = [(20..=22, 4), (23..=24, 5), (25..=28, 6)];
747        for (range, class) in expected {
748            for gid in range {
749                assert_eq!(lookup.value(gid).unwrap(), class);
750            }
751        }
752        for fail in [0, 10, 19, 29, 0xFFFF] {
753            assert!(lookup.value(fail).is_err());
754        }
755    }
756
757    #[test]
758    fn lookup_format_4() {
759        #[rustfmt::skip]
760        let words = [
761            4_u16, // format
762            6,     // unit size (6 bytes)
763            3,     // number of units
764            12,    // search range
765            1,     // entry selector
766            6,     // range shift
767            22, 20, 30, // First segment, mapping glyphs 20 through 22 to mapped data at offset 30
768            24, 23, 36, // Second segment, mapping glyph 23 and 24 to mapped data at offset 36
769            28, 25, 40, // Third segment, mapping glyphs 25 through 28 to mapped data at offset 40
770            // mapped data
771            3, 2, 1,
772            100, 150,
773            8, 6, 7, 9
774        ];
775        let mut buf = BeBuffer::new();
776        buf = buf.extend(words);
777        let lookup = LookupU16::read(buf.data().into()).unwrap();
778        let expected = [
779            (20, 3),
780            (21, 2),
781            (22, 1),
782            (23, 100),
783            (24, 150),
784            (25, 8),
785            (26, 6),
786            (27, 7),
787            (28, 9),
788        ];
789        for (in_glyph, out_glyph) in expected {
790            assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
791        }
792        for fail in [0, 10, 19, 29, 0xFFFF] {
793            assert!(lookup.value(fail).is_err());
794        }
795    }
796
797    // Taken from example 1 at https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html
798    #[test]
799    fn lookup_format_6() {
800        #[rustfmt::skip]
801        let words = [
802            6_u16, // format
803            4,     // unit size (4 bytes)
804            4,     // number of units
805            16,    // search range
806            2,     // entry selector
807            0,     // range shift
808            50, 600, // Input glyph 50 maps to glyph 600
809            51, 601, // Input glyph 51 maps to glyph 601
810            201, 602, // Input glyph 201 maps to glyph 602
811            202, 900, // Input glyph 202 maps to glyph 900
812        ];
813        let mut buf = BeBuffer::new();
814        buf = buf.extend(words);
815        let lookup = LookupU16::read(buf.data().into()).unwrap();
816        let expected = [(50, 600), (51, 601), (201, 602), (202, 900)];
817        for (in_glyph, out_glyph) in expected {
818            assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
819        }
820        for fail in [0, 10, 49, 52, 203, 0xFFFF] {
821            assert!(lookup.value(fail).is_err());
822        }
823    }
824
825    #[test]
826    fn lookup_format_8() {
827        #[rustfmt::skip]
828        let words = [
829            8_u16, // format
830            201,   // first glyph
831            7,     // glyph count
832            3, 8, 2, 9, 1, 200, 60, // glyphs 201..209 mapped to these values
833        ];
834        let mut buf = BeBuffer::new();
835        buf = buf.extend(words);
836        let lookup = LookupU16::read(buf.data().into()).unwrap();
837        let expected = &words[3..];
838        for (gid, expected) in (201..209).zip(expected) {
839            assert_eq!(lookup.value(gid).unwrap(), *expected);
840        }
841        for fail in [0, 10, 200, 210, 0xFFFF] {
842            assert!(lookup.value(fail).is_err());
843        }
844    }
845
846    #[test]
847    fn lookup_format_10() {
848        #[rustfmt::skip]
849        let words = [
850            10_u16, // format
851            4,      // unit size, use 4 byte values
852            201,   // first glyph
853            7,     // glyph count
854        ];
855        // glyphs 201..209 mapped to these values
856        let mapped = [3_u32, 8, 2902384, 9, 1, u32::MAX, 60];
857        let mut buf = BeBuffer::new();
858        buf = buf.extend(words).extend(mapped);
859        let lookup = LookupU32::read(buf.data().into()).unwrap();
860        for (gid, expected) in (201..209).zip(mapped) {
861            assert_eq!(lookup.value(gid).unwrap(), expected);
862        }
863        for fail in [0, 10, 200, 210, 0xFFFF] {
864            assert!(lookup.value(fail).is_err());
865        }
866    }
867
868    #[test]
869    fn extended_state_table() {
870        #[rustfmt::skip]
871        let header = [
872            6_u32, // number of classes
873            20, // byte offset to class table
874            56, // byte offset to state array
875            92, // byte offset to entry array
876            0, // padding
877        ];
878        #[rustfmt::skip]
879        let class_table = [
880            6_u16, // format
881            4,     // unit size (4 bytes)
882            5,     // number of units
883            16,    // search range
884            2,     // entry selector
885            0,     // range shift
886            50, 4, // Input glyph 50 maps to class 4
887            51, 4, // Input glyph 51 maps to class 4
888            80, 5, // Input glyph 80 maps to class 5
889            201, 4, // Input glyph 201 maps to class 4
890            202, 4, // Input glyph 202 maps to class 4
891            !0, !0
892        ];
893        #[rustfmt::skip]
894        let state_array: [u16; 18] = [
895            0, 0, 0, 0, 0, 1,
896            0, 0, 0, 0, 0, 1,
897            0, 0, 0, 0, 2, 1,
898        ];
899        #[rustfmt::skip]
900        let entry_table: [u16; 12] = [
901            0, 0, u16::MAX, u16::MAX,
902            2, 0, u16::MAX, u16::MAX,
903            0, 0, u16::MAX, 0,
904        ];
905        let buf = BeBuffer::new()
906            .extend(header)
907            .extend(class_table)
908            .extend(state_array)
909            .extend(entry_table);
910        let table = ExtendedStateTable::<ContextualData>::read(buf.data().into()).unwrap();
911        // check class lookups
912        let [class_50, class_80, class_201] =
913            [50, 80, 201].map(|gid| table.class(GlyphId::new(gid)).unwrap());
914        assert_eq!(class_50, 4);
915        assert_eq!(class_80, 5);
916        assert_eq!(class_201, 4);
917        // initial state
918        let entry = table.entry(0, 4).unwrap();
919        assert_eq!(entry.new_state, 0);
920        assert_eq!(entry.payload.current_index, !0);
921        // entry (state 0, class 5) should transition to state 2
922        let entry = table.entry(0, 5).unwrap();
923        assert_eq!(entry.new_state, 2);
924        // from state 2, we transition back to state 0 when class is not 5
925        // this also enables an action (payload.current_index != -1)
926        let entry = table.entry(2, 4).unwrap();
927        assert_eq!(entry.new_state, 0);
928        assert_eq!(entry.payload.current_index, 0);
929    }
930
931    #[derive(Copy, Clone, Debug, bytemuck::AnyBitPattern)]
932    #[repr(C, packed)]
933    struct ContextualData {
934        _mark_index: BigEndian<u16>,
935        current_index: BigEndian<u16>,
936    }
937
938    impl FixedSize for ContextualData {
939        const RAW_BYTE_LEN: usize = 4;
940    }
941
942    // Take from example at <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html>
943    // with class table trimmed to 4 glyphs
944    #[test]
945    fn state_table() {
946        #[rustfmt::skip]
947        let header = [
948            7_u16, // number of classes
949            10, // byte offset to class table
950            18, // byte offset to state array
951            40, // byte offset to entry array
952            64, // byte offset to value array (unused here)
953        ];
954        #[rustfmt::skip]
955        let class_table = [
956            3_u16, // first glyph
957            4, // number of glyphs
958        ];
959        let classes = [1u8, 2, 3, 4];
960        #[rustfmt::skip]
961        let state_array: [u8; 22] = [
962            2, 0, 0, 2, 1, 0, 0,
963            2, 0, 0, 2, 1, 0, 0,
964            2, 3, 3, 2, 3, 4, 5,
965            0, // padding
966        ];
967        #[rustfmt::skip]
968        let entry_table: [u16; 10] = [
969            // The first column are offsets from the beginning of the state
970            // table to some position in the state array
971            18, 0x8112,
972            32, 0x8112,
973            18, 0x0000,
974            32, 0x8114,
975            18, 0x8116,
976        ];
977        let buf = BeBuffer::new()
978            .extend(header)
979            .extend(class_table)
980            .extend(classes)
981            .extend(state_array)
982            .extend(entry_table);
983        let table = StateTable::<NoPayload>::read(buf.data().into()).unwrap();
984        // check class lookups
985        for i in 0..4u8 {
986            assert_eq!(table.class(GlyphId16::from(i as u16 + 3)).unwrap(), i + 1);
987        }
988        // (state, class) -> (new_state, flags)
989        let cases = [
990            ((0, 4), (2, 0x8112)),
991            ((2, 1), (2, 0x8114)),
992            ((1, 3), (0, 0x0000)),
993            ((2, 5), (0, 0x8116)),
994        ];
995        for ((state, class), (new_state, flags)) in cases {
996            let entry = table.entry(state, class).unwrap();
997            assert_eq!(
998                entry.new_state, new_state,
999                "state {state}, class {class} should map to new state {new_state} (got {})",
1000                entry.new_state
1001            );
1002            assert_eq!(
1003                entry.flags, flags,
1004                "state {state}, class {class} should map to flags 0x{flags:X} (got 0x{:X})",
1005                entry.flags
1006            );
1007        }
1008    }
1009}