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 value<T: LookupValue>(&self, index: u16) -> Result<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 .get(index as usize)
26 .map(|val| val.get())
27 .ok_or(ReadError::OutOfBounds)
28 }
29}
30
31#[derive(Copy, Clone, bytemuck::AnyBitPattern)]
33#[repr(C, packed)]
34pub struct LookupSegment2<T>
35where
36 T: LookupValue,
37{
38 pub last_glyph: BigEndian<u16>,
40 pub first_glyph: BigEndian<u16>,
42 pub value: BigEndian<T>,
44}
45
46impl<T: LookupValue> FixedSize for LookupSegment2<T> {
48 const RAW_BYTE_LEN: usize = std::mem::size_of::<Self>();
49}
50
51impl Lookup2<'_> {
52 pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
53 let segments = self.segments::<T>()?;
54 let ix = match segments.binary_search_by(|segment| segment.first_glyph.get().cmp(&index)) {
55 Ok(ix) => ix,
56 Err(ix) => ix.saturating_sub(1),
57 };
58 let segment = segments.get(ix).ok_or(ReadError::OutOfBounds)?;
59 if (segment.first_glyph.get()..=segment.last_glyph.get()).contains(&index) {
60 let value = segment.value;
61 return Ok(value.get());
62 }
63 Err(ReadError::OutOfBounds)
64 }
65
66 fn segments<T: LookupValue>(&self) -> Result<&[LookupSegment2<T>], ReadError> {
67 FontData::new(self.segments_data())
68 .cursor()
69 .read_array(self.n_units() as usize)
70 }
71}
72
73impl Lookup4<'_> {
74 pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
75 let segments = self.segments();
76 let ix = match segments.binary_search_by(|segment| segment.first_glyph.get().cmp(&index)) {
77 Ok(ix) => ix,
78 Err(ix) => ix.saturating_sub(1),
79 };
80 let segment = segments.get(ix).ok_or(ReadError::OutOfBounds)?;
81 if (segment.first_glyph.get()..=segment.last_glyph.get()).contains(&index) {
82 let base_offset = segment.value_offset() as usize;
83 let offset = base_offset
84 + index
85 .checked_sub(segment.first_glyph())
86 .ok_or(ReadError::OutOfBounds)? as usize
87 * T::RAW_BYTE_LEN;
88 return self.offset_data().read_at(offset);
89 }
90 Err(ReadError::OutOfBounds)
91 }
92}
93
94#[derive(Copy, Clone, bytemuck::AnyBitPattern)]
96#[repr(C, packed)]
97pub struct LookupSingle<T>
98where
99 T: LookupValue,
100{
101 pub glyph: BigEndian<u16>,
103 pub value: BigEndian<T>,
105}
106
107impl<T: LookupValue> FixedSize for LookupSingle<T> {
109 const RAW_BYTE_LEN: usize = std::mem::size_of::<Self>();
110}
111
112impl Lookup6<'_> {
113 pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
114 let entries = self.entries::<T>()?;
115 if let Ok(ix) = entries.binary_search_by_key(&index, |entry| entry.glyph.get()) {
116 let entry = &entries[ix];
117 let value = entry.value;
118 return Ok(value.get());
119 }
120 Err(ReadError::OutOfBounds)
121 }
122
123 fn entries<T: LookupValue>(&self) -> Result<&[LookupSingle<T>], ReadError> {
124 FontData::new(self.entries_data())
125 .cursor()
126 .read_array(self.n_units() as usize)
127 }
128}
129
130impl Lookup8<'_> {
131 pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
132 index
133 .checked_sub(self.first_glyph())
134 .and_then(|ix| {
135 self.value_array()
136 .get(ix as usize)
137 .map(|val| T::from_u16(val.get()))
138 })
139 .ok_or(ReadError::OutOfBounds)
140 }
141}
142
143impl Lookup10<'_> {
144 pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
145 let ix = index
146 .checked_sub(self.first_glyph())
147 .ok_or(ReadError::OutOfBounds)? as usize;
148 let unit_size = self.unit_size() as usize;
149 let offset = ix * unit_size;
150 let mut cursor = FontData::new(self.values_data()).cursor();
151 cursor.advance_by(offset);
152 let val = match unit_size {
153 1 => cursor.read::<u8>()? as u32,
154 2 => cursor.read::<u16>()? as u32,
155 4 => cursor.read::<u32>()?,
156 _ => {
157 return Err(ReadError::MalformedData(
158 "invalid unit_size in format 10 AAT lookup table",
159 ))
160 }
161 };
162 Ok(T::from_u32(val))
163 }
164}
165
166impl Lookup<'_> {
167 pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> {
168 match self {
169 Lookup::Format0(lookup) => lookup.value::<T>(index),
170 Lookup::Format2(lookup) => lookup.value::<T>(index),
171 Lookup::Format4(lookup) => lookup.value::<T>(index),
172 Lookup::Format6(lookup) => lookup.value::<T>(index),
173 Lookup::Format8(lookup) => lookup.value::<T>(index),
174 Lookup::Format10(lookup) => lookup.value::<T>(index),
175 }
176 }
177}
178
179#[derive(Clone)]
180pub struct TypedLookup<'a, T> {
181 lookup: Lookup<'a>,
182 _marker: std::marker::PhantomData<fn() -> T>,
183}
184
185impl<T: LookupValue> TypedLookup<'_, T> {
186 pub fn value(&self, index: u16) -> Result<T, ReadError> {
188 self.lookup.value::<T>(index)
189 }
190}
191
192impl<'a, T> FontRead<'a> for TypedLookup<'a, T> {
193 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
194 Ok(Self {
195 lookup: Lookup::read(data)?,
196 _marker: std::marker::PhantomData,
197 })
198 }
199}
200
201#[cfg(feature = "experimental_traverse")]
202impl<'a, T> SomeTable<'a> for TypedLookup<'a, T> {
203 fn type_name(&self) -> &str {
204 "TypedLookup"
205 }
206
207 fn get_field(&self, idx: usize) -> Option<Field<'a>> {
208 self.lookup.get_field(idx)
209 }
210}
211
212pub trait LookupValue: Copy + Scalar + bytemuck::AnyBitPattern {
214 fn from_u16(v: u16) -> Self;
215 fn from_u32(v: u32) -> Self;
216}
217
218impl LookupValue for u16 {
219 fn from_u16(v: u16) -> Self {
220 v
221 }
222
223 fn from_u32(v: u32) -> Self {
224 v as _
226 }
227}
228
229impl LookupValue for u32 {
230 fn from_u16(v: u16) -> Self {
231 v as _
232 }
233
234 fn from_u32(v: u32) -> Self {
235 v
236 }
237}
238
239impl LookupValue for GlyphId16 {
240 fn from_u16(v: u16) -> Self {
241 GlyphId16::from(v)
242 }
243
244 fn from_u32(v: u32) -> Self {
245 GlyphId16::from(v as u16)
247 }
248}
249
250pub type LookupU16<'a> = TypedLookup<'a, u16>;
251pub type LookupU32<'a> = TypedLookup<'a, u32>;
252pub type LookupGlyphId<'a> = TypedLookup<'a, GlyphId16>;
253
254#[derive(Copy, Clone, bytemuck::AnyBitPattern, Debug)]
260pub struct NoPayload(());
261
262impl FixedSize for NoPayload {
263 const RAW_BYTE_LEN: usize = 0;
264}
265
266#[derive(Clone, Debug)]
268pub struct StateEntry<T = NoPayload> {
269 pub new_state: u16,
271 pub flags: u16,
273 pub payload: T,
275}
276
277impl<'a, T: bytemuck::AnyBitPattern + FixedSize> FontRead<'a> for StateEntry<T> {
278 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
279 let mut cursor = data.cursor();
280 let new_state = cursor.read()?;
281 let flags = cursor.read()?;
282 let remaining = cursor.remaining().ok_or(ReadError::OutOfBounds)?;
283 let payload = *remaining.read_ref_at(0)?;
284 Ok(Self {
285 new_state,
286 flags,
287 payload,
288 })
289 }
290}
291
292impl<T> FixedSize for StateEntry<T>
293where
294 T: FixedSize,
295{
296 const RAW_BYTE_LEN: usize = u16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + T::RAW_BYTE_LEN;
298}
299
300#[derive(Clone)]
310pub struct StateTable<'a> {
311 header: StateHeader<'a>,
312}
313
314impl StateTable<'_> {
315 pub fn class(&self, glyph_id: GlyphId16) -> Result<u8, ReadError> {
317 let glyph_id = glyph_id.to_u16();
318 if glyph_id == 0xFFFF {
319 return Ok(class::DELETED_GLYPH);
320 }
321 let class_table = self.header.class_table()?;
322 glyph_id
323 .checked_sub(class_table.first_glyph())
324 .and_then(|ix| class_table.class_array().get(ix as usize).copied())
325 .ok_or(ReadError::OutOfBounds)
326 }
327
328 pub fn entry(&self, state: u16, class: u8) -> Result<StateEntry, ReadError> {
330 let n_classes = self.header.state_size() as usize;
332 if n_classes == 0 {
333 return Err(ReadError::MalformedData("empty AAT state table"));
335 }
336 let mut class = class as usize;
337 if class >= n_classes {
338 class = class::OUT_OF_BOUNDS as usize;
339 }
340 let state_array = self.header.state_array()?.data();
341 let entry_ix = state_array
342 .get(
343 (state as usize)
344 .checked_mul(n_classes)
345 .ok_or(ReadError::OutOfBounds)?
346 + class,
347 )
348 .copied()
349 .ok_or(ReadError::OutOfBounds)? as usize;
350 let entry_offset = entry_ix * 4;
351 let entry_data = self
352 .header
353 .entry_table()?
354 .data()
355 .get(entry_offset..)
356 .ok_or(ReadError::OutOfBounds)?;
357 let mut entry = StateEntry::read(FontData::new(entry_data))?;
358 let new_state = (entry.new_state as i32)
361 .checked_sub(self.header.state_array_offset().to_u32() as i32)
362 .ok_or(ReadError::OutOfBounds)?
363 / n_classes as i32;
364 entry.new_state = new_state.try_into().map_err(|_| ReadError::OutOfBounds)?;
365 Ok(entry)
366 }
367}
368
369impl<'a> FontRead<'a> for StateTable<'a> {
370 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
371 Ok(Self {
372 header: StateHeader::read(data)?,
373 })
374 }
375}
376
377#[cfg(feature = "experimental_traverse")]
378impl<'a> SomeTable<'a> for StateTable<'a> {
379 fn type_name(&self) -> &str {
380 "StateTable"
381 }
382
383 fn get_field(&self, idx: usize) -> Option<Field<'a>> {
384 self.header.get_field(idx)
385 }
386}
387
388#[derive(Clone)]
389pub struct ExtendedStateTable<'a, T = NoPayload> {
390 n_classes: usize,
391 class_table: LookupU16<'a>,
392 state_array: &'a [BigEndian<u16>],
393 entry_table: &'a [u8],
394 _marker: std::marker::PhantomData<fn() -> T>,
395}
396
397impl<T> ExtendedStateTable<'_, T> {
398 pub const HEADER_LEN: usize = u32::RAW_BYTE_LEN * 4;
399}
400
401impl<T> ExtendedStateTable<'_, T>
411where
412 T: FixedSize + bytemuck::AnyBitPattern,
413{
414 pub fn class(&self, glyph_id: GlyphId) -> Result<u16, ReadError> {
416 let glyph_id: u16 = glyph_id
417 .to_u32()
418 .try_into()
419 .map_err(|_| ReadError::OutOfBounds)?;
420 if glyph_id == 0xFFFF {
421 return Ok(class::DELETED_GLYPH as u16);
422 }
423 self.class_table.value(glyph_id)
424 }
425
426 pub fn entry(&self, state: u16, class: u16) -> Result<StateEntry<T>, ReadError> {
428 let mut class = class as usize;
429 if class >= self.n_classes {
430 class = class::OUT_OF_BOUNDS as usize;
431 }
432 let state_ix = state as usize * self.n_classes + class;
433 let entry_ix = self
434 .state_array
435 .get(state_ix)
436 .copied()
437 .ok_or(ReadError::OutOfBounds)?
438 .get() as usize;
439 let entry_offset = entry_ix * StateEntry::<T>::RAW_BYTE_LEN;
440 let entry_data = self
441 .entry_table
442 .get(entry_offset..)
443 .ok_or(ReadError::OutOfBounds)?;
444 StateEntry::read(FontData::new(entry_data))
445 }
446}
447
448impl<'a, T> FontRead<'a> for ExtendedStateTable<'a, T> {
449 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
450 let header = StxHeader::read(data)?;
451 let n_classes = header.n_classes() as usize;
452 let class_table = header.class_table()?;
453 let state_array = header.state_array()?.data();
454 let entry_table = header.entry_table()?.data();
455 Ok(Self {
456 n_classes,
457 class_table,
458 state_array,
459 entry_table,
460 _marker: std::marker::PhantomData,
461 })
462 }
463}
464
465#[cfg(feature = "experimental_traverse")]
466impl<'a, T> SomeTable<'a> for ExtendedStateTable<'a, T> {
467 fn type_name(&self) -> &str {
468 "ExtendedStateTable"
469 }
470
471 fn get_field(&self, _idx: usize) -> Option<Field<'a>> {
472 None
473 }
474}
475
476pub(crate) fn safe_read_array_to_end<'a, T: bytemuck::AnyBitPattern + FixedSize>(
484 data: &FontData<'a>,
485 offset: usize,
486) -> Result<&'a [T], ReadError> {
487 let len = data
488 .len()
489 .checked_sub(offset)
490 .ok_or(ReadError::OutOfBounds)?;
491 let end = offset + len / T::RAW_BYTE_LEN * T::RAW_BYTE_LEN;
492 data.read_array(offset..end)
493}
494
495#[cfg(test)]
496mod tests {
497 use font_test_data::bebuffer::BeBuffer;
498
499 use super::*;
500
501 #[test]
502 fn lookup_format_0() {
503 #[rustfmt::skip]
504 let words = [
505 0_u16, 0, 2, 4, 6, 8, 10, 12, 14, 16, ];
508 let mut buf = BeBuffer::new();
509 buf = buf.extend(words);
510 let lookup = LookupU16::read(buf.data().into()).unwrap();
511 for gid in 0..=8 {
512 assert_eq!(lookup.value(gid).unwrap(), gid * 2);
513 }
514 assert!(lookup.value(9).is_err());
515 }
516
517 #[test]
519 fn lookup_format_2() {
520 #[rustfmt::skip]
521 let words = [
522 2_u16, 6, 3, 12, 1, 6, 22, 20, 4, 24, 23, 5, 28, 25, 6, ];
532 let mut buf = BeBuffer::new();
533 buf = buf.extend(words);
534 let lookup = LookupU16::read(buf.data().into()).unwrap();
535 let expected = [(20..=22, 4), (23..=24, 5), (25..=28, 6)];
536 for (range, class) in expected {
537 for gid in range {
538 assert_eq!(lookup.value(gid).unwrap(), class);
539 }
540 }
541 for fail in [0, 10, 19, 29, 0xFFFF] {
542 assert!(lookup.value(fail).is_err());
543 }
544 }
545
546 #[test]
547 fn lookup_format_4() {
548 #[rustfmt::skip]
549 let words = [
550 4_u16, 6, 3, 12, 1, 6, 22, 20, 30, 24, 23, 36, 28, 25, 40, 3, 2, 1,
561 100, 150,
562 8, 6, 7, 9
563 ];
564 let mut buf = BeBuffer::new();
565 buf = buf.extend(words);
566 let lookup = LookupU16::read(buf.data().into()).unwrap();
567 let expected = [
568 (20, 3),
569 (21, 2),
570 (22, 1),
571 (23, 100),
572 (24, 150),
573 (25, 8),
574 (26, 6),
575 (27, 7),
576 (28, 9),
577 ];
578 for (in_glyph, out_glyph) in expected {
579 assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
580 }
581 for fail in [0, 10, 19, 29, 0xFFFF] {
582 assert!(lookup.value(fail).is_err());
583 }
584 }
585
586 #[test]
588 fn lookup_format_6() {
589 #[rustfmt::skip]
590 let words = [
591 6_u16, 4, 4, 16, 2, 0, 50, 600, 51, 601, 201, 602, 202, 900, ];
602 let mut buf = BeBuffer::new();
603 buf = buf.extend(words);
604 let lookup = LookupU16::read(buf.data().into()).unwrap();
605 let expected = [(50, 600), (51, 601), (201, 602), (202, 900)];
606 for (in_glyph, out_glyph) in expected {
607 assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
608 }
609 for fail in [0, 10, 49, 52, 203, 0xFFFF] {
610 assert!(lookup.value(fail).is_err());
611 }
612 }
613
614 #[test]
615 fn lookup_format_8() {
616 #[rustfmt::skip]
617 let words = [
618 8_u16, 201, 7, 3, 8, 2, 9, 1, 200, 60, ];
623 let mut buf = BeBuffer::new();
624 buf = buf.extend(words);
625 let lookup = LookupU16::read(buf.data().into()).unwrap();
626 let expected = &words[3..];
627 for (gid, expected) in (201..209).zip(expected) {
628 assert_eq!(lookup.value(gid).unwrap(), *expected);
629 }
630 for fail in [0, 10, 200, 210, 0xFFFF] {
631 assert!(lookup.value(fail).is_err());
632 }
633 }
634
635 #[test]
636 fn lookup_format_10() {
637 #[rustfmt::skip]
638 let words = [
639 10_u16, 4, 201, 7, ];
644 let mapped = [3_u32, 8, 2902384, 9, 1, u32::MAX, 60];
646 let mut buf = BeBuffer::new();
647 buf = buf.extend(words).extend(mapped);
648 let lookup = LookupU32::read(buf.data().into()).unwrap();
649 for (gid, expected) in (201..209).zip(mapped) {
650 assert_eq!(lookup.value(gid).unwrap(), expected);
651 }
652 for fail in [0, 10, 200, 210, 0xFFFF] {
653 assert!(lookup.value(fail).is_err());
654 }
655 }
656
657 #[test]
658 fn extended_state_table() {
659 #[rustfmt::skip]
660 let header = [
661 6_u32, 20, 56, 92, 0, ];
667 #[rustfmt::skip]
668 let class_table = [
669 6_u16, 4, 5, 16, 2, 0, 50, 4, 51, 4, 80, 5, 201, 4, 202, 4, !0, !0
681 ];
682 #[rustfmt::skip]
683 let state_array: [u16; 18] = [
684 0, 0, 0, 0, 0, 1,
685 0, 0, 0, 0, 0, 1,
686 0, 0, 0, 0, 2, 1,
687 ];
688 #[rustfmt::skip]
689 let entry_table: [u16; 12] = [
690 0, 0, u16::MAX, u16::MAX,
691 2, 0, u16::MAX, u16::MAX,
692 0, 0, u16::MAX, 0,
693 ];
694 let buf = BeBuffer::new()
695 .extend(header)
696 .extend(class_table)
697 .extend(state_array)
698 .extend(entry_table);
699 let table = ExtendedStateTable::<ContextualData>::read(buf.data().into()).unwrap();
700 let [class_50, class_80, class_201] =
702 [50, 80, 201].map(|gid| table.class(GlyphId::new(gid)).unwrap());
703 assert_eq!(class_50, 4);
704 assert_eq!(class_80, 5);
705 assert_eq!(class_201, 4);
706 let entry = table.entry(0, 4).unwrap();
708 assert_eq!(entry.new_state, 0);
709 assert_eq!(entry.payload.current_index, !0);
710 let entry = table.entry(0, 5).unwrap();
712 assert_eq!(entry.new_state, 2);
713 let entry = table.entry(2, 4).unwrap();
716 assert_eq!(entry.new_state, 0);
717 assert_eq!(entry.payload.current_index, 0);
718 }
719
720 #[derive(Copy, Clone, Debug, bytemuck::AnyBitPattern)]
721 #[repr(C, packed)]
722 struct ContextualData {
723 _mark_index: BigEndian<u16>,
724 current_index: BigEndian<u16>,
725 }
726
727 impl FixedSize for ContextualData {
728 const RAW_BYTE_LEN: usize = 4;
729 }
730
731 #[test]
734 fn state_table() {
735 #[rustfmt::skip]
736 let header = [
737 7_u16, 10, 18, 40, 64, ];
743 #[rustfmt::skip]
744 let class_table = [
745 3_u16, 4, ];
748 let classes = [1u8, 2, 3, 4];
749 #[rustfmt::skip]
750 let state_array: [u8; 22] = [
751 2, 0, 0, 2, 1, 0, 0,
752 2, 0, 0, 2, 1, 0, 0,
753 2, 3, 3, 2, 3, 4, 5,
754 0, ];
756 #[rustfmt::skip]
757 let entry_table: [u16; 10] = [
758 18, 0x8112,
761 32, 0x8112,
762 18, 0x0000,
763 32, 0x8114,
764 18, 0x8116,
765 ];
766 let buf = BeBuffer::new()
767 .extend(header)
768 .extend(class_table)
769 .extend(classes)
770 .extend(state_array)
771 .extend(entry_table);
772 let table = StateTable::read(buf.data().into()).unwrap();
773 for i in 0..4u8 {
775 assert_eq!(table.class(GlyphId16::from(i as u16 + 3)).unwrap(), i + 1);
776 }
777 let cases = [
779 ((0, 4), (2, 0x8112)),
780 ((2, 1), (2, 0x8114)),
781 ((1, 3), (0, 0x0000)),
782 ((2, 5), (0, 0x8116)),
783 ];
784 for ((state, class), (new_state, flags)) in cases {
785 let entry = table.entry(state, class).unwrap();
786 assert_eq!(
787 entry.new_state, new_state,
788 "state {state}, class {class} should map to new state {new_state} (got {})",
789 entry.new_state
790 );
791 assert_eq!(
792 entry.flags, flags,
793 "state {state}, class {class} should map to flags 0x{flags:X} (got 0x{:X})",
794 entry.flags
795 );
796 }
797 }
798}