1include!("../../generated/generated_aat.rs");
6
7pub 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#[derive(Copy, Clone, bytemuck::AnyBitPattern)]
37#[repr(C, packed)]
38pub struct LookupSegment2<T>
39where
40 T: LookupValue,
41{
42 pub last_glyph: BigEndian<u16>,
44 pub first_glyph: BigEndian<u16>,
46 pub value: BigEndian<T>,
48}
49
50impl<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#[derive(Copy, Clone, bytemuck::AnyBitPattern)]
119#[repr(C, packed)]
120pub struct LookupSingle<T>
121where
122 T: LookupValue,
123{
124 pub glyph: BigEndian<u16>,
126 pub value: BigEndian<T>,
128}
129
130impl<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 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
243pub 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 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 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#[derive(Copy, Clone, bytemuck::AnyBitPattern, Debug)]
291pub struct NoPayload(());
292
293impl FixedSize for NoPayload {
294 const RAW_BYTE_LEN: usize = 0;
295}
296
297#[derive(Clone, Debug)]
299pub struct StateEntry<T = NoPayload> {
300 pub new_state: u16,
302 pub flags: u16,
304 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 const RAW_BYTE_LEN: usize = u16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + T::RAW_BYTE_LEN;
333}
334
335#[derive(Clone)]
345pub struct StateTable<'a> {
346 pub header: StateHeader<'a>,
347 n_classes: usize,
348 class_first_glyph: u16,
349 class_array: &'a [u8],
350 state_array: &'a [u8],
351 entry_table: &'a [u8],
352 n_classes_magic: u64,
355}
356
357impl StateTable<'_> {
358 pub const HEADER_LEN: usize = u16::RAW_BYTE_LEN * 4;
359
360 pub fn class(&self, glyph_id: GlyphId16) -> Result<u8, ReadError> {
362 let glyph_id = glyph_id.to_u16();
363 if glyph_id == 0xFFFF {
364 return Ok(class::DELETED_GLYPH);
365 }
366 glyph_id
367 .checked_sub(self.class_first_glyph)
368 .and_then(|ix| self.class_array.get(ix as usize).copied())
369 .ok_or(ReadError::OutOfBounds)
370 }
371
372 #[inline(always)]
374 pub fn entry(&self, state: u16, class: u8) -> Result<StateEntry, ReadError> {
375 let mut class = class as usize;
376 if class >= self.n_classes {
377 class = class::OUT_OF_BOUNDS as usize;
378 }
379 let entry_ix = self
380 .state_array
381 .get(state as usize * self.n_classes + class)
382 .copied()
383 .ok_or(ReadError::OutOfBounds)? as usize;
384 let entry_offset = entry_ix * 4;
385 let entry_data = self
386 .entry_table
387 .get(entry_offset..)
388 .ok_or(ReadError::OutOfBounds)?;
389 let mut entry = StateEntry::read(FontData::new(entry_data))?;
390 let offset = self.header.state_array_offset().to_u32() as i32;
393 let diff = entry.new_state as i32 - offset;
394 let new_state = if diff >= 0 {
395 ((diff as u64 * self.n_classes_magic) >> 32) as i32
399 } else {
400 diff / self.n_classes as i32
401 };
402 entry.new_state = new_state.try_into().map_err(|_| ReadError::OutOfBounds)?;
403 Ok(entry)
404 }
405
406 pub fn read_value<T: Scalar>(&self, offset: usize) -> Result<T, ReadError> {
408 self.header.offset_data().read_at::<T>(offset)
409 }
410}
411
412impl ReadArgs for StateTable<'_> {
413 type Args = ();
414}
415
416impl<'a> FontRead<'a> for StateTable<'a> {
417 fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
418 let header = StateHeader::read(data)?;
419 let n_classes = header.state_size() as usize;
421 if n_classes == 0 {
422 return Err(ReadError::MalformedData("empty AAT state table"));
424 }
425 let class_table = header.class_table()?;
426 let class_first_glyph = class_table.first_glyph();
427 let class_array = class_table.class_array();
428 let state_array = header.state_array()?.data();
429 let entry_table = header.entry_table()?.data();
430 Ok(Self {
431 header: StateHeader::read(data)?,
432 n_classes,
433 class_first_glyph,
434 class_array,
435 state_array,
436 entry_table,
437 n_classes_magic: (1u64 << 32) / n_classes as u64 + 1,
438 })
439 }
440}
441
442#[cfg(feature = "experimental_traverse")]
443impl<'a> SomeTable<'a> for StateTable<'a> {
444 fn type_name(&self) -> &str {
445 "StateTable"
446 }
447
448 fn get_field(&self, idx: usize) -> Option<Field<'a>> {
449 self.header.get_field(idx)
450 }
451}
452
453#[derive(Clone)]
454pub struct ExtendedStateTable<'a, T = NoPayload> {
455 pub n_classes: usize,
456 pub class_table: LookupU16<'a>,
457 state_array: &'a [BigEndian<u16>],
458 entry_table: &'a [u8],
459 _marker: std::marker::PhantomData<fn() -> T>,
460}
461
462impl<T> ExtendedStateTable<'_, T> {
463 pub const HEADER_LEN: usize = u32::RAW_BYTE_LEN * 4;
464}
465
466impl<T> ExtendedStateTable<'_, T>
476where
477 T: FixedSize + bytemuck::AnyBitPattern,
478{
479 #[inline]
481 pub fn class(&self, glyph_id: GlyphId) -> Result<u16, ReadError> {
482 let glyph_id: u16 = glyph_id
483 .to_u32()
484 .try_into()
485 .map_err(|_| ReadError::OutOfBounds)?;
486 if glyph_id == 0xFFFF {
487 return Ok(class::DELETED_GLYPH as u16);
488 }
489 self.class_table.value(glyph_id)
490 }
491
492 #[inline]
494 pub fn entry(&self, state: u16, class: u16) -> Result<StateEntry<T>, ReadError> {
495 let mut class = class as usize;
496 if class >= self.n_classes {
497 class = class::OUT_OF_BOUNDS as usize;
498 }
499 let state_ix = (state as usize)
500 .wrapping_mul(self.n_classes)
501 .wrapping_add(class);
502 let entry_ix = self
503 .state_array
504 .get(state_ix)
505 .copied()
506 .ok_or(ReadError::OutOfBounds)?
507 .get() as usize;
508 let entry_offset = entry_ix.wrapping_mul(StateEntry::<T>::RAW_BYTE_LEN);
509 let entry_data = self
510 .entry_table
511 .get(entry_offset..)
512 .ok_or(ReadError::OutOfBounds)?;
513 StateEntry::read(FontData::new(entry_data))
514 }
515}
516
517#[derive(Clone, Copy, Debug, Default)]
522pub struct StateTableParts {
523 pub n_classes: u32,
524 pub class_table_offset: u32,
525 pub state_array_offset: u32,
526 pub entry_table_offset: u32,
527}
528
529impl StateTableParts {
530 pub fn read(data: FontData) -> Result<Self, ReadError> {
532 let header = StxHeader::read(data)?;
533 Ok(StateTableParts {
534 n_classes: header.n_classes(),
535 class_table_offset: header.class_table_offset().to_u32(),
536 state_array_offset: header.state_array_offset().to_u32(),
537 entry_table_offset: header.entry_table_offset().to_u32(),
538 })
539 }
540}
541
542impl<'a, T> ExtendedStateTable<'a, T> {
543 #[inline]
546 pub fn from_parts(data: FontData<'a>, parts: &StateTableParts) -> Result<Self, ReadError> {
547 let class_table = LookupU16::read(
548 data.split_off(parts.class_table_offset as usize)
549 .ok_or(ReadError::OutOfBounds)?,
550 )?;
551 let state_array = safe_read_array_to_end(&data, parts.state_array_offset as usize)?;
552 let entry_table = data
553 .as_bytes()
554 .get(parts.entry_table_offset as usize..)
555 .ok_or(ReadError::OutOfBounds)?;
556 Ok(Self {
557 n_classes: parts.n_classes as usize,
558 class_table,
559 state_array,
560 entry_table,
561 _marker: std::marker::PhantomData,
562 })
563 }
564}
565
566impl<T> ReadArgs for ExtendedStateTable<'_, T> {
567 type Args = ();
568}
569
570impl<'a, T> FontRead<'a> for ExtendedStateTable<'a, T> {
571 fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
572 let header = StxHeader::read(data)?;
573 let n_classes = header.n_classes() as usize;
574 let class_table = header.class_table()?;
575 let state_array = header.state_array()?.data();
576 let entry_table = header.entry_table()?.data();
577 Ok(Self {
578 n_classes,
579 class_table,
580 state_array,
581 entry_table,
582 _marker: std::marker::PhantomData,
583 })
584 }
585}
586
587#[cfg(feature = "experimental_traverse")]
588impl<'a, T> SomeTable<'a> for ExtendedStateTable<'a, T> {
589 fn type_name(&self) -> &str {
590 "ExtendedStateTable"
591 }
592
593 fn get_field(&self, _idx: usize) -> Option<Field<'a>> {
594 None
595 }
596}
597
598pub(crate) fn safe_read_array_to_end<'a, T: bytemuck::AnyBitPattern + FixedSize>(
606 data: &FontData<'a>,
607 offset: usize,
608) -> Result<&'a [T], ReadError> {
609 let len = data
610 .len()
611 .checked_sub(offset)
612 .ok_or(ReadError::OutOfBounds)?;
613 let end = offset + len / T::RAW_BYTE_LEN * T::RAW_BYTE_LEN;
614 data.read_array(offset..end)
615}
616
617#[cfg(test)]
618mod tests {
619 use font_test_data::bebuffer::BeBuffer;
620
621 use super::*;
622
623 #[test]
624 fn lookup_format_0() {
625 #[rustfmt::skip]
626 let words = [
627 0_u16, 0, 2, 4, 6, 8, 10, 12, 14, 16, ];
630 let mut buf = BeBuffer::new();
631 buf = buf.extend(words);
632 let lookup = LookupU16::read(buf.data().into()).unwrap();
633 for gid in 0..=8 {
634 assert_eq!(lookup.value(gid).unwrap(), gid * 2);
635 }
636 assert!(lookup.value(9).is_err());
637 }
638
639 #[test]
641 fn lookup_format_2() {
642 #[rustfmt::skip]
643 let words = [
644 2_u16, 6, 3, 12, 1, 6, 22, 20, 4, 24, 23, 5, 28, 25, 6, ];
654 let mut buf = BeBuffer::new();
655 buf = buf.extend(words);
656 let lookup = LookupU16::read(buf.data().into()).unwrap();
657 let expected = [(20..=22, 4), (23..=24, 5), (25..=28, 6)];
658 for (range, class) in expected {
659 for gid in range {
660 assert_eq!(lookup.value(gid).unwrap(), class);
661 }
662 }
663 for fail in [0, 10, 19, 29, 0xFFFF] {
664 assert!(lookup.value(fail).is_err());
665 }
666 }
667
668 #[test]
669 fn lookup_format_4() {
670 #[rustfmt::skip]
671 let words = [
672 4_u16, 6, 3, 12, 1, 6, 22, 20, 30, 24, 23, 36, 28, 25, 40, 3, 2, 1,
683 100, 150,
684 8, 6, 7, 9
685 ];
686 let mut buf = BeBuffer::new();
687 buf = buf.extend(words);
688 let lookup = LookupU16::read(buf.data().into()).unwrap();
689 let expected = [
690 (20, 3),
691 (21, 2),
692 (22, 1),
693 (23, 100),
694 (24, 150),
695 (25, 8),
696 (26, 6),
697 (27, 7),
698 (28, 9),
699 ];
700 for (in_glyph, out_glyph) in expected {
701 assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
702 }
703 for fail in [0, 10, 19, 29, 0xFFFF] {
704 assert!(lookup.value(fail).is_err());
705 }
706 }
707
708 #[test]
710 fn lookup_format_6() {
711 #[rustfmt::skip]
712 let words = [
713 6_u16, 4, 4, 16, 2, 0, 50, 600, 51, 601, 201, 602, 202, 900, ];
724 let mut buf = BeBuffer::new();
725 buf = buf.extend(words);
726 let lookup = LookupU16::read(buf.data().into()).unwrap();
727 let expected = [(50, 600), (51, 601), (201, 602), (202, 900)];
728 for (in_glyph, out_glyph) in expected {
729 assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
730 }
731 for fail in [0, 10, 49, 52, 203, 0xFFFF] {
732 assert!(lookup.value(fail).is_err());
733 }
734 }
735
736 #[test]
737 fn lookup_format_8() {
738 #[rustfmt::skip]
739 let words = [
740 8_u16, 201, 7, 3, 8, 2, 9, 1, 200, 60, ];
745 let mut buf = BeBuffer::new();
746 buf = buf.extend(words);
747 let lookup = LookupU16::read(buf.data().into()).unwrap();
748 let expected = &words[3..];
749 for (gid, expected) in (201..209).zip(expected) {
750 assert_eq!(lookup.value(gid).unwrap(), *expected);
751 }
752 for fail in [0, 10, 200, 210, 0xFFFF] {
753 assert!(lookup.value(fail).is_err());
754 }
755 }
756
757 #[test]
758 fn lookup_format_10() {
759 #[rustfmt::skip]
760 let words = [
761 10_u16, 4, 201, 7, ];
766 let mapped = [3_u32, 8, 2902384, 9, 1, u32::MAX, 60];
768 let mut buf = BeBuffer::new();
769 buf = buf.extend(words).extend(mapped);
770 let lookup = LookupU32::read(buf.data().into()).unwrap();
771 for (gid, expected) in (201..209).zip(mapped) {
772 assert_eq!(lookup.value(gid).unwrap(), expected);
773 }
774 for fail in [0, 10, 200, 210, 0xFFFF] {
775 assert!(lookup.value(fail).is_err());
776 }
777 }
778
779 #[test]
780 fn extended_state_table() {
781 #[rustfmt::skip]
782 let header = [
783 6_u32, 20, 56, 92, 0, ];
789 #[rustfmt::skip]
790 let class_table = [
791 6_u16, 4, 5, 16, 2, 0, 50, 4, 51, 4, 80, 5, 201, 4, 202, 4, !0, !0
803 ];
804 #[rustfmt::skip]
805 let state_array: [u16; 18] = [
806 0, 0, 0, 0, 0, 1,
807 0, 0, 0, 0, 0, 1,
808 0, 0, 0, 0, 2, 1,
809 ];
810 #[rustfmt::skip]
811 let entry_table: [u16; 12] = [
812 0, 0, u16::MAX, u16::MAX,
813 2, 0, u16::MAX, u16::MAX,
814 0, 0, u16::MAX, 0,
815 ];
816 let buf = BeBuffer::new()
817 .extend(header)
818 .extend(class_table)
819 .extend(state_array)
820 .extend(entry_table);
821 let table = ExtendedStateTable::<ContextualData>::read(buf.data().into()).unwrap();
822 let [class_50, class_80, class_201] =
824 [50, 80, 201].map(|gid| table.class(GlyphId::new(gid)).unwrap());
825 assert_eq!(class_50, 4);
826 assert_eq!(class_80, 5);
827 assert_eq!(class_201, 4);
828 let entry = table.entry(0, 4).unwrap();
830 assert_eq!(entry.new_state, 0);
831 assert_eq!(entry.payload.current_index, !0);
832 let entry = table.entry(0, 5).unwrap();
834 assert_eq!(entry.new_state, 2);
835 let entry = table.entry(2, 4).unwrap();
838 assert_eq!(entry.new_state, 0);
839 assert_eq!(entry.payload.current_index, 0);
840 }
841
842 #[derive(Copy, Clone, Debug, bytemuck::AnyBitPattern)]
843 #[repr(C, packed)]
844 struct ContextualData {
845 _mark_index: BigEndian<u16>,
846 current_index: BigEndian<u16>,
847 }
848
849 impl FixedSize for ContextualData {
850 const RAW_BYTE_LEN: usize = 4;
851 }
852
853 #[test]
856 fn state_table() {
857 #[rustfmt::skip]
858 let header = [
859 7_u16, 10, 18, 40, 64, ];
865 #[rustfmt::skip]
866 let class_table = [
867 3_u16, 4, ];
870 let classes = [1u8, 2, 3, 4];
871 #[rustfmt::skip]
872 let state_array: [u8; 22] = [
873 2, 0, 0, 2, 1, 0, 0,
874 2, 0, 0, 2, 1, 0, 0,
875 2, 3, 3, 2, 3, 4, 5,
876 0, ];
878 #[rustfmt::skip]
879 let entry_table: [u16; 10] = [
880 18, 0x8112,
883 32, 0x8112,
884 18, 0x0000,
885 32, 0x8114,
886 18, 0x8116,
887 ];
888 let buf = BeBuffer::new()
889 .extend(header)
890 .extend(class_table)
891 .extend(classes)
892 .extend(state_array)
893 .extend(entry_table);
894 let table = StateTable::read(buf.data().into()).unwrap();
895 for i in 0..4u8 {
897 assert_eq!(table.class(GlyphId16::from(i as u16 + 3)).unwrap(), i + 1);
898 }
899 let cases = [
901 ((0, 4), (2, 0x8112)),
902 ((2, 1), (2, 0x8114)),
903 ((1, 3), (0, 0x0000)),
904 ((2, 5), (0, 0x8116)),
905 ];
906 for ((state, class), (new_state, flags)) in cases {
907 let entry = table.entry(state, class).unwrap();
908 assert_eq!(
909 entry.new_state, new_state,
910 "state {state}, class {class} should map to new state {new_state} (got {})",
911 entry.new_state
912 );
913 assert_eq!(
914 entry.flags, flags,
915 "state {state}, class {class} should map to flags 0x{flags:X} (got 0x{:X})",
916 entry.flags
917 );
918 }
919 }
920}