Coverage Report

Created: 2026-08-11 07:19

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