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 const HEADER_LEN: usize = u16::RAW_BYTE_LEN * 4;
316
317 pub fn class(&self, glyph_id: GlyphId16) -> Result<u8, ReadError> {
319 let glyph_id = glyph_id.to_u16();
320 if glyph_id == 0xFFFF {
321 return Ok(class::DELETED_GLYPH);
322 }
323 let class_table = self.header.class_table()?;
324 glyph_id
325 .checked_sub(class_table.first_glyph())
326 .and_then(|ix| class_table.class_array().get(ix as usize).copied())
327 .ok_or(ReadError::OutOfBounds)
328 }
329
330 pub fn entry(&self, state: u16, class: u8) -> Result<StateEntry, ReadError> {
332 let n_classes = self.header.state_size() as usize;
334 if n_classes == 0 {
335 return Err(ReadError::MalformedData("empty AAT state table"));
337 }
338 let mut class = class as usize;
339 if class >= n_classes {
340 class = class::OUT_OF_BOUNDS as usize;
341 }
342 let state_array = self.header.state_array()?.data();
343 let entry_ix = state_array
344 .get(
345 (state as usize)
346 .checked_mul(n_classes)
347 .ok_or(ReadError::OutOfBounds)?
348 + class,
349 )
350 .copied()
351 .ok_or(ReadError::OutOfBounds)? as usize;
352 let entry_offset = entry_ix * 4;
353 let entry_data = self
354 .header
355 .entry_table()?
356 .data()
357 .get(entry_offset..)
358 .ok_or(ReadError::OutOfBounds)?;
359 let mut entry = StateEntry::read(FontData::new(entry_data))?;
360 let new_state = (entry.new_state as i32)
363 .checked_sub(self.header.state_array_offset().to_u32() as i32)
364 .ok_or(ReadError::OutOfBounds)?
365 / n_classes as i32;
366 entry.new_state = new_state.try_into().map_err(|_| ReadError::OutOfBounds)?;
367 Ok(entry)
368 }
369
370 pub fn read_value<T: Scalar>(&self, offset: usize) -> Result<T, ReadError> {
372 self.header.offset_data().read_at::<T>(offset)
373 }
374}
375
376impl<'a> FontRead<'a> for StateTable<'a> {
377 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
378 Ok(Self {
379 header: StateHeader::read(data)?,
380 })
381 }
382}
383
384#[cfg(feature = "experimental_traverse")]
385impl<'a> SomeTable<'a> for StateTable<'a> {
386 fn type_name(&self) -> &str {
387 "StateTable"
388 }
389
390 fn get_field(&self, idx: usize) -> Option<Field<'a>> {
391 self.header.get_field(idx)
392 }
393}
394
395#[derive(Clone)]
396pub struct ExtendedStateTable<'a, T = NoPayload> {
397 n_classes: usize,
398 class_table: LookupU16<'a>,
399 state_array: &'a [BigEndian<u16>],
400 entry_table: &'a [u8],
401 _marker: std::marker::PhantomData<fn() -> T>,
402}
403
404impl<T> ExtendedStateTable<'_, T> {
405 pub const HEADER_LEN: usize = u32::RAW_BYTE_LEN * 4;
406}
407
408impl<T> ExtendedStateTable<'_, T>
418where
419 T: FixedSize + bytemuck::AnyBitPattern,
420{
421 pub fn class(&self, glyph_id: GlyphId) -> Result<u16, ReadError> {
423 let glyph_id: u16 = glyph_id
424 .to_u32()
425 .try_into()
426 .map_err(|_| ReadError::OutOfBounds)?;
427 if glyph_id == 0xFFFF {
428 return Ok(class::DELETED_GLYPH as u16);
429 }
430 self.class_table.value(glyph_id)
431 }
432
433 pub fn entry(&self, state: u16, class: u16) -> Result<StateEntry<T>, ReadError> {
435 let mut class = class as usize;
436 if class >= self.n_classes {
437 class = class::OUT_OF_BOUNDS as usize;
438 }
439 let state_ix = state as usize * self.n_classes + class;
440 let entry_ix = self
441 .state_array
442 .get(state_ix)
443 .copied()
444 .ok_or(ReadError::OutOfBounds)?
445 .get() as usize;
446 let entry_offset = entry_ix * StateEntry::<T>::RAW_BYTE_LEN;
447 let entry_data = self
448 .entry_table
449 .get(entry_offset..)
450 .ok_or(ReadError::OutOfBounds)?;
451 StateEntry::read(FontData::new(entry_data))
452 }
453}
454
455impl<'a, T> FontRead<'a> for ExtendedStateTable<'a, T> {
456 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
457 let header = StxHeader::read(data)?;
458 let n_classes = header.n_classes() as usize;
459 let class_table = header.class_table()?;
460 let state_array = header.state_array()?.data();
461 let entry_table = header.entry_table()?.data();
462 Ok(Self {
463 n_classes,
464 class_table,
465 state_array,
466 entry_table,
467 _marker: std::marker::PhantomData,
468 })
469 }
470}
471
472#[cfg(feature = "experimental_traverse")]
473impl<'a, T> SomeTable<'a> for ExtendedStateTable<'a, T> {
474 fn type_name(&self) -> &str {
475 "ExtendedStateTable"
476 }
477
478 fn get_field(&self, _idx: usize) -> Option<Field<'a>> {
479 None
480 }
481}
482
483pub(crate) fn safe_read_array_to_end<'a, T: bytemuck::AnyBitPattern + FixedSize>(
491 data: &FontData<'a>,
492 offset: usize,
493) -> Result<&'a [T], ReadError> {
494 let len = data
495 .len()
496 .checked_sub(offset)
497 .ok_or(ReadError::OutOfBounds)?;
498 let end = offset + len / T::RAW_BYTE_LEN * T::RAW_BYTE_LEN;
499 data.read_array(offset..end)
500}
501
502#[cfg(test)]
503mod tests {
504 use font_test_data::bebuffer::BeBuffer;
505
506 use super::*;
507
508 #[test]
509 fn lookup_format_0() {
510 #[rustfmt::skip]
511 let words = [
512 0_u16, 0, 2, 4, 6, 8, 10, 12, 14, 16, ];
515 let mut buf = BeBuffer::new();
516 buf = buf.extend(words);
517 let lookup = LookupU16::read(buf.data().into()).unwrap();
518 for gid in 0..=8 {
519 assert_eq!(lookup.value(gid).unwrap(), gid * 2);
520 }
521 assert!(lookup.value(9).is_err());
522 }
523
524 #[test]
526 fn lookup_format_2() {
527 #[rustfmt::skip]
528 let words = [
529 2_u16, 6, 3, 12, 1, 6, 22, 20, 4, 24, 23, 5, 28, 25, 6, ];
539 let mut buf = BeBuffer::new();
540 buf = buf.extend(words);
541 let lookup = LookupU16::read(buf.data().into()).unwrap();
542 let expected = [(20..=22, 4), (23..=24, 5), (25..=28, 6)];
543 for (range, class) in expected {
544 for gid in range {
545 assert_eq!(lookup.value(gid).unwrap(), class);
546 }
547 }
548 for fail in [0, 10, 19, 29, 0xFFFF] {
549 assert!(lookup.value(fail).is_err());
550 }
551 }
552
553 #[test]
554 fn lookup_format_4() {
555 #[rustfmt::skip]
556 let words = [
557 4_u16, 6, 3, 12, 1, 6, 22, 20, 30, 24, 23, 36, 28, 25, 40, 3, 2, 1,
568 100, 150,
569 8, 6, 7, 9
570 ];
571 let mut buf = BeBuffer::new();
572 buf = buf.extend(words);
573 let lookup = LookupU16::read(buf.data().into()).unwrap();
574 let expected = [
575 (20, 3),
576 (21, 2),
577 (22, 1),
578 (23, 100),
579 (24, 150),
580 (25, 8),
581 (26, 6),
582 (27, 7),
583 (28, 9),
584 ];
585 for (in_glyph, out_glyph) in expected {
586 assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
587 }
588 for fail in [0, 10, 19, 29, 0xFFFF] {
589 assert!(lookup.value(fail).is_err());
590 }
591 }
592
593 #[test]
595 fn lookup_format_6() {
596 #[rustfmt::skip]
597 let words = [
598 6_u16, 4, 4, 16, 2, 0, 50, 600, 51, 601, 201, 602, 202, 900, ];
609 let mut buf = BeBuffer::new();
610 buf = buf.extend(words);
611 let lookup = LookupU16::read(buf.data().into()).unwrap();
612 let expected = [(50, 600), (51, 601), (201, 602), (202, 900)];
613 for (in_glyph, out_glyph) in expected {
614 assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
615 }
616 for fail in [0, 10, 49, 52, 203, 0xFFFF] {
617 assert!(lookup.value(fail).is_err());
618 }
619 }
620
621 #[test]
622 fn lookup_format_8() {
623 #[rustfmt::skip]
624 let words = [
625 8_u16, 201, 7, 3, 8, 2, 9, 1, 200, 60, ];
630 let mut buf = BeBuffer::new();
631 buf = buf.extend(words);
632 let lookup = LookupU16::read(buf.data().into()).unwrap();
633 let expected = &words[3..];
634 for (gid, expected) in (201..209).zip(expected) {
635 assert_eq!(lookup.value(gid).unwrap(), *expected);
636 }
637 for fail in [0, 10, 200, 210, 0xFFFF] {
638 assert!(lookup.value(fail).is_err());
639 }
640 }
641
642 #[test]
643 fn lookup_format_10() {
644 #[rustfmt::skip]
645 let words = [
646 10_u16, 4, 201, 7, ];
651 let mapped = [3_u32, 8, 2902384, 9, 1, u32::MAX, 60];
653 let mut buf = BeBuffer::new();
654 buf = buf.extend(words).extend(mapped);
655 let lookup = LookupU32::read(buf.data().into()).unwrap();
656 for (gid, expected) in (201..209).zip(mapped) {
657 assert_eq!(lookup.value(gid).unwrap(), expected);
658 }
659 for fail in [0, 10, 200, 210, 0xFFFF] {
660 assert!(lookup.value(fail).is_err());
661 }
662 }
663
664 #[test]
665 fn extended_state_table() {
666 #[rustfmt::skip]
667 let header = [
668 6_u32, 20, 56, 92, 0, ];
674 #[rustfmt::skip]
675 let class_table = [
676 6_u16, 4, 5, 16, 2, 0, 50, 4, 51, 4, 80, 5, 201, 4, 202, 4, !0, !0
688 ];
689 #[rustfmt::skip]
690 let state_array: [u16; 18] = [
691 0, 0, 0, 0, 0, 1,
692 0, 0, 0, 0, 0, 1,
693 0, 0, 0, 0, 2, 1,
694 ];
695 #[rustfmt::skip]
696 let entry_table: [u16; 12] = [
697 0, 0, u16::MAX, u16::MAX,
698 2, 0, u16::MAX, u16::MAX,
699 0, 0, u16::MAX, 0,
700 ];
701 let buf = BeBuffer::new()
702 .extend(header)
703 .extend(class_table)
704 .extend(state_array)
705 .extend(entry_table);
706 let table = ExtendedStateTable::<ContextualData>::read(buf.data().into()).unwrap();
707 let [class_50, class_80, class_201] =
709 [50, 80, 201].map(|gid| table.class(GlyphId::new(gid)).unwrap());
710 assert_eq!(class_50, 4);
711 assert_eq!(class_80, 5);
712 assert_eq!(class_201, 4);
713 let entry = table.entry(0, 4).unwrap();
715 assert_eq!(entry.new_state, 0);
716 assert_eq!(entry.payload.current_index, !0);
717 let entry = table.entry(0, 5).unwrap();
719 assert_eq!(entry.new_state, 2);
720 let entry = table.entry(2, 4).unwrap();
723 assert_eq!(entry.new_state, 0);
724 assert_eq!(entry.payload.current_index, 0);
725 }
726
727 #[derive(Copy, Clone, Debug, bytemuck::AnyBitPattern)]
728 #[repr(C, packed)]
729 struct ContextualData {
730 _mark_index: BigEndian<u16>,
731 current_index: BigEndian<u16>,
732 }
733
734 impl FixedSize for ContextualData {
735 const RAW_BYTE_LEN: usize = 4;
736 }
737
738 #[test]
741 fn state_table() {
742 #[rustfmt::skip]
743 let header = [
744 7_u16, 10, 18, 40, 64, ];
750 #[rustfmt::skip]
751 let class_table = [
752 3_u16, 4, ];
755 let classes = [1u8, 2, 3, 4];
756 #[rustfmt::skip]
757 let state_array: [u8; 22] = [
758 2, 0, 0, 2, 1, 0, 0,
759 2, 0, 0, 2, 1, 0, 0,
760 2, 3, 3, 2, 3, 4, 5,
761 0, ];
763 #[rustfmt::skip]
764 let entry_table: [u16; 10] = [
765 18, 0x8112,
768 32, 0x8112,
769 18, 0x0000,
770 32, 0x8114,
771 18, 0x8116,
772 ];
773 let buf = BeBuffer::new()
774 .extend(header)
775 .extend(class_table)
776 .extend(classes)
777 .extend(state_array)
778 .extend(entry_table);
779 let table = StateTable::read(buf.data().into()).unwrap();
780 for i in 0..4u8 {
782 assert_eq!(table.class(GlyphId16::from(i as u16 + 3)).unwrap(), i + 1);
783 }
784 let cases = [
786 ((0, 4), (2, 0x8112)),
787 ((2, 1), (2, 0x8114)),
788 ((1, 3), (0, 0x0000)),
789 ((2, 5), (0, 0x8116)),
790 ];
791 for ((state, class), (new_state, flags)) in cases {
792 let entry = table.entry(state, class).unwrap();
793 assert_eq!(
794 entry.new_state, new_state,
795 "state {state}, class {class} should map to new state {new_state} (got {})",
796 entry.new_state
797 );
798 assert_eq!(
799 entry.flags, flags,
800 "state {state}, class {class} should map to flags 0x{flags:X} (got 0x{:X})",
801 entry.flags
802 );
803 }
804 }
805}