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, 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 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 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 pub fn class_mappings(&self) -> (u16, &'_ [u8]) {
380 (self.class_first_glyph, self.class_array)
381 }
382
383 #[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 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 ((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 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#[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 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 #[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 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 let n_classes = header.state_size() as usize;
509 if n_classes == 0 {
510 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
555impl<T> ExtendedStateTable<'_, T>
565where
566 T: FixedSize + bytemuck::AnyBitPattern,
567{
568 #[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 #[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#[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 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 #[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
687pub(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, 0, 2, 4, 6, 8, 10, 12, 14, 16, ];
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 #[test]
730 fn lookup_format_2() {
731 #[rustfmt::skip]
732 let words = [
733 2_u16, 6, 3, 12, 1, 6, 22, 20, 4, 24, 23, 5, 28, 25, 6, ];
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, 6, 3, 12, 1, 6, 22, 20, 30, 24, 23, 36, 28, 25, 40, 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 #[test]
799 fn lookup_format_6() {
800 #[rustfmt::skip]
801 let words = [
802 6_u16, 4, 4, 16, 2, 0, 50, 600, 51, 601, 201, 602, 202, 900, ];
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, 201, 7, 3, 8, 2, 9, 1, 200, 60, ];
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, 4, 201, 7, ];
855 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, 20, 56, 92, 0, ];
878 #[rustfmt::skip]
879 let class_table = [
880 6_u16, 4, 5, 16, 2, 0, 50, 4, 51, 4, 80, 5, 201, 4, 202, 4, !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 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 let entry = table.entry(0, 4).unwrap();
919 assert_eq!(entry.new_state, 0);
920 assert_eq!(entry.payload.current_index, !0);
921 let entry = table.entry(0, 5).unwrap();
923 assert_eq!(entry.new_state, 2);
924 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 #[test]
945 fn state_table() {
946 #[rustfmt::skip]
947 let header = [
948 7_u16, 10, 18, 40, 64, ];
954 #[rustfmt::skip]
955 let class_table = [
956 3_u16, 4, ];
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, ];
967 #[rustfmt::skip]
968 let entry_table: [u16; 10] = [
969 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 for i in 0..4u8 {
986 assert_eq!(table.class(GlyphId16::from(i as u16 + 3)).unwrap(), i + 1);
987 }
988 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}