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
179pub struct TypedLookup<'a, T> {
180 lookup: Lookup<'a>,
181 _marker: std::marker::PhantomData<fn() -> T>,
182}
183
184impl<T: LookupValue> TypedLookup<'_, T> {
185 pub fn value(&self, index: u16) -> Result<T, ReadError> {
187 self.lookup.value::<T>(index)
188 }
189}
190
191impl<'a, T> FontRead<'a> for TypedLookup<'a, T> {
192 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
193 Ok(Self {
194 lookup: Lookup::read(data)?,
195 _marker: std::marker::PhantomData,
196 })
197 }
198}
199
200#[cfg(feature = "experimental_traverse")]
201impl<'a, T> SomeTable<'a> for TypedLookup<'a, T> {
202 fn type_name(&self) -> &str {
203 "TypedLookup"
204 }
205
206 fn get_field(&self, idx: usize) -> Option<Field<'a>> {
207 self.lookup.get_field(idx)
208 }
209}
210
211pub trait LookupValue: Copy + Scalar + bytemuck::AnyBitPattern {
213 fn from_u16(v: u16) -> Self;
214 fn from_u32(v: u32) -> Self;
215}
216
217impl LookupValue for u16 {
218 fn from_u16(v: u16) -> Self {
219 v
220 }
221
222 fn from_u32(v: u32) -> Self {
223 v as _
225 }
226}
227
228impl LookupValue for u32 {
229 fn from_u16(v: u16) -> Self {
230 v as _
231 }
232
233 fn from_u32(v: u32) -> Self {
234 v
235 }
236}
237
238impl LookupValue for GlyphId16 {
239 fn from_u16(v: u16) -> Self {
240 GlyphId16::from(v)
241 }
242
243 fn from_u32(v: u32) -> Self {
244 GlyphId16::from(v as u16)
246 }
247}
248
249pub type LookupU16<'a> = TypedLookup<'a, u16>;
250pub type LookupU32<'a> = TypedLookup<'a, u32>;
251pub type LookupGlyphId<'a> = TypedLookup<'a, GlyphId16>;
252
253#[derive(Copy, Clone, bytemuck::AnyBitPattern)]
259pub struct NoPayload(());
260
261impl FixedSize for NoPayload {
262 const RAW_BYTE_LEN: usize = 0;
263}
264
265pub struct StateEntry<T = NoPayload> {
267 pub new_state: u16,
269 pub flags: u16,
271 pub payload: T,
273}
274
275impl<'a, T: bytemuck::AnyBitPattern + FixedSize> FontRead<'a> for StateEntry<T> {
276 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
277 let mut cursor = data.cursor();
278 let new_state = cursor.read()?;
279 let flags = cursor.read()?;
280 let remaining = cursor.remaining().ok_or(ReadError::OutOfBounds)?;
281 let payload = *remaining.read_ref_at(0)?;
282 Ok(Self {
283 new_state,
284 flags,
285 payload,
286 })
287 }
288}
289
290impl<T> FixedSize for StateEntry<T>
291where
292 T: FixedSize,
293{
294 const RAW_BYTE_LEN: usize = u16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + T::RAW_BYTE_LEN;
296}
297
298pub struct StateTable<'a> {
299 header: StateHeader<'a>,
300}
301
302impl StateTable<'_> {
303 pub fn class(&self, glyph_id: GlyphId16) -> Result<u8, ReadError> {
305 let glyph_id = glyph_id.to_u16();
306 if glyph_id == 0xFFFF {
307 return Ok(class::DELETED_GLYPH);
308 }
309 let class_table = self.header.class_table()?;
310 glyph_id
311 .checked_sub(class_table.first_glyph())
312 .and_then(|ix| class_table.class_array().get(ix as usize).copied())
313 .ok_or(ReadError::OutOfBounds)
314 }
315
316 pub fn entry(&self, state: u16, class: u8) -> Result<StateEntry, ReadError> {
318 let n_classes = self.header.state_size() as usize;
320 if n_classes == 0 {
321 return Err(ReadError::MalformedData("empty AAT state table"));
323 }
324 let mut class = class as usize;
325 if class >= n_classes {
326 class = class::OUT_OF_BOUNDS as usize;
327 }
328 let state_array = self.header.state_array()?.data();
329 let entry_ix = state_array
330 .get(
331 (state as usize)
332 .checked_mul(n_classes)
333 .ok_or(ReadError::OutOfBounds)?
334 + class,
335 )
336 .copied()
337 .ok_or(ReadError::OutOfBounds)? as usize;
338 let entry_offset = entry_ix * 4;
339 let entry_data = self
340 .header
341 .entry_table()?
342 .data()
343 .get(entry_offset..)
344 .ok_or(ReadError::OutOfBounds)?;
345 let mut entry = StateEntry::read(FontData::new(entry_data))?;
346 let new_state = (entry.new_state as i32)
349 .checked_sub(self.header.state_array_offset().to_u32() as i32)
350 .ok_or(ReadError::OutOfBounds)?
351 / n_classes as i32;
352 entry.new_state = new_state.try_into().map_err(|_| ReadError::OutOfBounds)?;
353 Ok(entry)
354 }
355}
356
357impl<'a> FontRead<'a> for StateTable<'a> {
358 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
359 Ok(Self {
360 header: StateHeader::read(data)?,
361 })
362 }
363}
364
365#[cfg(feature = "experimental_traverse")]
366impl<'a> SomeTable<'a> for StateTable<'a> {
367 fn type_name(&self) -> &str {
368 "StateTable"
369 }
370
371 fn get_field(&self, idx: usize) -> Option<Field<'a>> {
372 self.header.get_field(idx)
373 }
374}
375
376pub struct ExtendedStateTable<'a, T = ()> {
377 header: StxHeader<'a>,
378 _marker: std::marker::PhantomData<fn() -> T>,
379}
380
381impl<T: bytemuck::AnyBitPattern + FixedSize> ExtendedStateTable<'_, T> {
382 pub fn class(&self, glyph_id: GlyphId16) -> Result<u16, ReadError> {
384 let glyph_id = glyph_id.to_u16();
385 if glyph_id == 0xFFFF {
386 return Ok(class::DELETED_GLYPH as u16);
387 }
388 self.header.class_table()?.value(glyph_id)
389 }
390
391 pub fn entry(&self, state: u16, class: u16) -> Result<StateEntry<T>, ReadError> {
393 let n_classes = self.header.n_classes() as usize;
394 let mut class = class as usize;
395 if class >= n_classes {
396 class = class::OUT_OF_BOUNDS as usize;
397 }
398 let state_array = self.header.state_array()?.data();
399 let state_ix = state as usize * n_classes + class;
400 let entry_ix = state_array
401 .get(state_ix)
402 .copied()
403 .ok_or(ReadError::OutOfBounds)?
404 .get() as usize;
405 let entry_offset = entry_ix * StateEntry::<T>::RAW_BYTE_LEN;
406 let entry_data = self
407 .header
408 .entry_table()?
409 .data()
410 .get(entry_offset..)
411 .ok_or(ReadError::OutOfBounds)?;
412 StateEntry::read(FontData::new(entry_data))
413 }
414}
415
416impl<'a, T> FontRead<'a> for ExtendedStateTable<'a, T> {
417 fn read(data: FontData<'a>) -> Result<Self, ReadError> {
418 Ok(Self {
419 header: StxHeader::read(data)?,
420 _marker: std::marker::PhantomData,
421 })
422 }
423}
424
425#[cfg(feature = "experimental_traverse")]
426impl<'a, T> SomeTable<'a> for ExtendedStateTable<'a, T> {
427 fn type_name(&self) -> &str {
428 "ExtendedStateTable"
429 }
430
431 fn get_field(&self, idx: usize) -> Option<Field<'a>> {
432 self.header.get_field(idx)
433 }
434}
435
436pub type ExtendedStateTableU16<'a> = ExtendedStateTable<'a, u16>;
437
438#[cfg(test)]
439mod tests {
440 use font_test_data::bebuffer::BeBuffer;
441
442 use super::*;
443
444 #[test]
445 fn lookup_format_0() {
446 #[rustfmt::skip]
447 let words = [
448 0_u16, 0, 2, 4, 6, 8, 10, 12, 14, 16, ];
451 let mut buf = BeBuffer::new();
452 buf = buf.extend(words);
453 let lookup = LookupU16::read(buf.data().into()).unwrap();
454 for gid in 0..=8 {
455 assert_eq!(lookup.value(gid).unwrap(), gid * 2);
456 }
457 assert!(lookup.value(9).is_err());
458 }
459
460 #[test]
462 fn lookup_format_2() {
463 #[rustfmt::skip]
464 let words = [
465 2_u16, 6, 3, 12, 1, 6, 22, 20, 4, 24, 23, 5, 28, 25, 6, ];
475 let mut buf = BeBuffer::new();
476 buf = buf.extend(words);
477 let lookup = LookupU16::read(buf.data().into()).unwrap();
478 let expected = [(20..=22, 4), (23..=24, 5), (25..=28, 6)];
479 for (range, class) in expected {
480 for gid in range {
481 assert_eq!(lookup.value(gid).unwrap(), class);
482 }
483 }
484 for fail in [0, 10, 19, 29, 0xFFFF] {
485 assert!(lookup.value(fail).is_err());
486 }
487 }
488
489 #[test]
490 fn lookup_format_4() {
491 #[rustfmt::skip]
492 let words = [
493 4_u16, 6, 3, 12, 1, 6, 22, 20, 30, 24, 23, 36, 28, 25, 40, 3, 2, 1,
504 100, 150,
505 8, 6, 7, 9
506 ];
507 let mut buf = BeBuffer::new();
508 buf = buf.extend(words);
509 let lookup = LookupU16::read(buf.data().into()).unwrap();
510 let expected = [
511 (20, 3),
512 (21, 2),
513 (22, 1),
514 (23, 100),
515 (24, 150),
516 (25, 8),
517 (26, 6),
518 (27, 7),
519 (28, 9),
520 ];
521 for (in_glyph, out_glyph) in expected {
522 assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
523 }
524 for fail in [0, 10, 19, 29, 0xFFFF] {
525 assert!(lookup.value(fail).is_err());
526 }
527 }
528
529 #[test]
531 fn lookup_format_6() {
532 #[rustfmt::skip]
533 let words = [
534 6_u16, 4, 4, 16, 2, 0, 50, 600, 51, 601, 201, 602, 202, 900, ];
545 let mut buf = BeBuffer::new();
546 buf = buf.extend(words);
547 let lookup = LookupU16::read(buf.data().into()).unwrap();
548 let expected = [(50, 600), (51, 601), (201, 602), (202, 900)];
549 for (in_glyph, out_glyph) in expected {
550 assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph);
551 }
552 for fail in [0, 10, 49, 52, 203, 0xFFFF] {
553 assert!(lookup.value(fail).is_err());
554 }
555 }
556
557 #[test]
558 fn lookup_format_8() {
559 #[rustfmt::skip]
560 let words = [
561 8_u16, 201, 8, 3, 8, 2, 9, 1, 200, 60, ];
566 let mut buf = BeBuffer::new();
567 buf = buf.extend(words);
568 let lookup = LookupU16::read(buf.data().into()).unwrap();
569 let expected = &words[3..];
570 for (gid, expected) in (201..209).zip(expected) {
571 assert_eq!(lookup.value(gid).unwrap(), *expected);
572 }
573 for fail in [0, 10, 200, 210, 0xFFFF] {
574 assert!(lookup.value(fail).is_err());
575 }
576 }
577
578 #[test]
579 fn lookup_format_10() {
580 #[rustfmt::skip]
581 let words = [
582 10_u16, 4, 201, 8, ];
587 let mapped = [3_u32, 8, 2902384, 9, 1, u32::MAX, 60];
589 let mut buf = BeBuffer::new();
590 buf = buf.extend(words).extend(mapped);
591 let lookup = LookupU32::read(buf.data().into()).unwrap();
592 for (gid, expected) in (201..209).zip(mapped) {
593 assert_eq!(lookup.value(gid).unwrap(), expected);
594 }
595 for fail in [0, 10, 200, 210, 0xFFFF] {
596 assert!(lookup.value(fail).is_err());
597 }
598 }
599
600 #[test]
601 fn extended_state_table() {
602 #[rustfmt::skip]
603 let header = [
604 6_u32, 20, 56, 92, 0, ];
610 #[rustfmt::skip]
611 let class_table = [
612 6_u16, 4, 5, 16, 2, 0, 50, 4, 51, 4, 80, 5, 201, 4, 202, 4, !0, !0
624 ];
625 #[rustfmt::skip]
626 let state_array: [u16; 18] = [
627 0, 0, 0, 0, 0, 1,
628 0, 0, 0, 0, 0, 1,
629 0, 0, 0, 0, 2, 1,
630 ];
631 #[rustfmt::skip]
632 let entry_table: [u16; 12] = [
633 0, 0, u16::MAX, u16::MAX,
634 2, 0, u16::MAX, u16::MAX,
635 0, 0, u16::MAX, 0,
636 ];
637 let buf = BeBuffer::new()
638 .extend(header)
639 .extend(class_table)
640 .extend(state_array)
641 .extend(entry_table);
642 let table = ExtendedStateTable::<ContextualData>::read(buf.data().into()).unwrap();
643 let [class_50, class_80, class_201] =
645 [50, 80, 201].map(|gid| table.class(GlyphId16::from(gid)).unwrap());
646 assert_eq!(class_50, 4);
647 assert_eq!(class_80, 5);
648 assert_eq!(class_201, 4);
649 let entry = table.entry(0, 4).unwrap();
651 assert_eq!(entry.new_state, 0);
652 assert_eq!(entry.payload.current_index, !0);
653 let entry = table.entry(0, 5).unwrap();
655 assert_eq!(entry.new_state, 2);
656 let entry = table.entry(2, 4).unwrap();
659 assert_eq!(entry.new_state, 0);
660 assert_eq!(entry.payload.current_index, 0);
661 }
662
663 #[derive(Copy, Clone, Debug, bytemuck::AnyBitPattern)]
664 #[repr(C, packed)]
665 struct ContextualData {
666 _mark_index: BigEndian<u16>,
667 current_index: BigEndian<u16>,
668 }
669
670 impl FixedSize for ContextualData {
671 const RAW_BYTE_LEN: usize = 4;
672 }
673
674 #[test]
677 fn state_table() {
678 #[rustfmt::skip]
679 let header = [
680 7_u16, 10, 18, 40, 64, ];
686 #[rustfmt::skip]
687 let class_table = [
688 3_u16, 4, ];
691 let classes = [1u8, 2, 3, 4];
692 #[rustfmt::skip]
693 let state_array: [u8; 22] = [
694 2, 0, 0, 2, 1, 0, 0,
695 2, 0, 0, 2, 1, 0, 0,
696 2, 3, 3, 2, 3, 4, 5,
697 0, ];
699 #[rustfmt::skip]
700 let entry_table: [u16; 10] = [
701 18, 0x8112,
704 32, 0x8112,
705 18, 0x0000,
706 32, 0x8114,
707 18, 0x8116,
708 ];
709 let buf = BeBuffer::new()
710 .extend(header)
711 .extend(class_table)
712 .extend(classes)
713 .extend(state_array)
714 .extend(entry_table);
715 let table = StateTable::read(buf.data().into()).unwrap();
716 for i in 0..4u8 {
718 assert_eq!(table.class(GlyphId16::from(i as u16 + 3)).unwrap(), i + 1);
719 }
720 let cases = [
722 ((0, 4), (2, 0x8112)),
723 ((2, 1), (2, 0x8114)),
724 ((1, 3), (0, 0x0000)),
725 ((2, 5), (0, 0x8116)),
726 ];
727 for ((state, class), (new_state, flags)) in cases {
728 let entry = table.entry(state, class).unwrap();
729 assert_eq!(
730 entry.new_state, new_state,
731 "state {state}, class {class} should map to new state {new_state} (got {})",
732 entry.new_state
733 );
734 assert_eq!(
735 entry.flags, flags,
736 "state {state}, class {class} should map to flags 0x{flags:X} (got 0x{:X})",
737 entry.flags
738 );
739 }
740 }
741}