/src/fontations/read-fonts/src/tables/aat.rs
Line | Count | Source |
1 | | //! Apple Advanced Typography common tables. |
2 | | //! |
3 | | //! See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html> |
4 | | |
5 | | include!("../../generated/generated_aat.rs"); |
6 | | |
7 | | /// Predefined classes. |
8 | | /// |
9 | | /// See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html> |
10 | | pub 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 | | |
16 | | impl Lookup0<'_> { |
17 | 0 | pub fn values<T: LookupValue>(&self) -> Result<&[BigEndian<T>], ReadError> { |
18 | 0 | let data = self.values_data(); |
19 | 0 | let data_len = data.len(); |
20 | 0 | let n_elems = data_len / T::RAW_BYTE_LEN; |
21 | 0 | let len_in_bytes = n_elems * T::RAW_BYTE_LEN; |
22 | 0 | FontData::new(&data[..len_in_bytes]) |
23 | 0 | .cursor() |
24 | 0 | .read_array::<BigEndian<T>>(n_elems) |
25 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::Lookup0>::values::<u32> Unexecuted instantiation: <read_fonts::tables::aat::Lookup0>::values::<u16> |
26 | 0 | pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> { |
27 | 0 | self.values::<T>()? |
28 | 0 | .get(index as usize) |
29 | 0 | .map(|val| val.get()) Unexecuted instantiation: <read_fonts::tables::aat::Lookup0>::value::<u32>::{closure#0}Unexecuted instantiation: <read_fonts::tables::aat::Lookup0>::value::<u16>::{closure#0} |
30 | 0 | .ok_or(ReadError::OutOfBounds) |
31 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::Lookup0>::value::<u32> Unexecuted instantiation: <read_fonts::tables::aat::Lookup0>::value::<u16> |
32 | | } |
33 | | |
34 | | /// Lookup segment for format 2. |
35 | | #[derive(Copy, Clone, bytemuck::AnyBitPattern)] |
36 | | #[repr(C, packed)] |
37 | | pub struct LookupSegment2<T> |
38 | | where |
39 | | T: LookupValue, |
40 | | { |
41 | | /// Last glyph index in this segment. |
42 | | pub last_glyph: BigEndian<u16>, |
43 | | /// First glyph index in this segment. |
44 | | pub first_glyph: BigEndian<u16>, |
45 | | /// The lookup value. |
46 | | pub value: BigEndian<T>, |
47 | | } |
48 | | |
49 | | /// Note: this requires `LookupSegment2` to be `repr(packed)`. |
50 | | impl<T: LookupValue> FixedSize for LookupSegment2<T> { |
51 | | const RAW_BYTE_LEN: usize = std::mem::size_of::<Self>(); |
52 | | } |
53 | | |
54 | | impl Lookup2<'_> { |
55 | 0 | pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> { |
56 | 0 | let segments = self.segments::<T>()?; |
57 | 0 | let ix = match segments.binary_search_by(|segment| segment.first_glyph.get().cmp(&index)) {Unexecuted instantiation: <read_fonts::tables::aat::Lookup2>::value::<u32>::{closure#0}Unexecuted instantiation: <read_fonts::tables::aat::Lookup2>::value::<u16>::{closure#0} |
58 | 0 | Ok(ix) => ix, |
59 | 0 | Err(ix) => ix.saturating_sub(1), |
60 | | }; |
61 | 0 | let segment = segments.get(ix).ok_or(ReadError::OutOfBounds)?; |
62 | 0 | if (segment.first_glyph.get()..=segment.last_glyph.get()).contains(&index) { |
63 | 0 | let value = segment.value; |
64 | 0 | return Ok(value.get()); |
65 | 0 | } |
66 | 0 | Err(ReadError::OutOfBounds) |
67 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::Lookup2>::value::<u32> Unexecuted instantiation: <read_fonts::tables::aat::Lookup2>::value::<u16> |
68 | | |
69 | 0 | pub fn segments<T: LookupValue>(&self) -> Result<&[LookupSegment2<T>], ReadError> { |
70 | 0 | FontData::new(self.segments_data()) |
71 | 0 | .cursor() |
72 | 0 | .read_array(self.n_units() as usize) |
73 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::Lookup2>::segments::<u32> Unexecuted instantiation: <read_fonts::tables::aat::Lookup2>::segments::<u16> |
74 | | } |
75 | | |
76 | | impl Lookup4<'_> { |
77 | 0 | pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> { |
78 | 0 | let segments = self.segments(); |
79 | 0 | let ix = match segments.binary_search_by(|segment| segment.first_glyph.get().cmp(&index)) {Unexecuted instantiation: <read_fonts::tables::aat::Lookup4>::value::<u32>::{closure#0}Unexecuted instantiation: <read_fonts::tables::aat::Lookup4>::value::<u16>::{closure#0} |
80 | 0 | Ok(ix) => ix, |
81 | 0 | Err(ix) => ix.saturating_sub(1), |
82 | | }; |
83 | 0 | let segment = segments.get(ix).ok_or(ReadError::OutOfBounds)?; |
84 | 0 | if (segment.first_glyph.get()..=segment.last_glyph.get()).contains(&index) { |
85 | 0 | let base_offset = segment.value_offset() as usize; |
86 | 0 | let offset = base_offset |
87 | 0 | + index |
88 | 0 | .checked_sub(segment.first_glyph()) |
89 | 0 | .ok_or(ReadError::OutOfBounds)? as usize |
90 | | * T::RAW_BYTE_LEN; |
91 | 0 | return self.offset_data().read_at(offset); |
92 | 0 | } |
93 | 0 | Err(ReadError::OutOfBounds) |
94 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::Lookup4>::value::<u32> Unexecuted instantiation: <read_fonts::tables::aat::Lookup4>::value::<u16> |
95 | 0 | pub fn segment_values<T: LookupValue>( |
96 | 0 | &self, |
97 | 0 | segment: usize, |
98 | 0 | ) -> Result<&[BigEndian<T>], ReadError> { |
99 | 0 | let segment = self.segments().get(segment).ok_or(ReadError::OutOfBounds)?; |
100 | 0 | let base_offset = segment.value_offset() as usize; |
101 | 0 | let n_elems = segment |
102 | 0 | .last_glyph |
103 | 0 | .get() |
104 | 0 | .checked_sub(segment.first_glyph.get()) |
105 | 0 | .ok_or(ReadError::MalformedData( |
106 | 0 | "invalid segment in format 4 AAT lookup table", |
107 | 0 | ))? as usize |
108 | | + 1; |
109 | 0 | self.offset_data() |
110 | 0 | .read_array::<BigEndian<T>>(base_offset..base_offset + n_elems * T::RAW_BYTE_LEN) |
111 | 0 | } |
112 | | } |
113 | | |
114 | | /// Lookup single record for format 6. |
115 | | #[derive(Copy, Clone, bytemuck::AnyBitPattern)] |
116 | | #[repr(C, packed)] |
117 | | pub struct LookupSingle<T> |
118 | | where |
119 | | T: LookupValue, |
120 | | { |
121 | | /// The glyph index. |
122 | | pub glyph: BigEndian<u16>, |
123 | | /// The lookup value. |
124 | | pub value: BigEndian<T>, |
125 | | } |
126 | | |
127 | | /// Note: this requires `LookupSingle` to be `repr(packed)`. |
128 | | impl<T: LookupValue> FixedSize for LookupSingle<T> { |
129 | | const RAW_BYTE_LEN: usize = std::mem::size_of::<Self>(); |
130 | | } |
131 | | |
132 | | impl Lookup6<'_> { |
133 | 0 | pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> { |
134 | 0 | let entries = self.entries::<T>()?; |
135 | 0 | if let Ok(ix) = entries.binary_search_by_key(&index, |entry| entry.glyph.get()) {Unexecuted instantiation: <read_fonts::tables::aat::Lookup6>::value::<u32>::{closure#0}Unexecuted instantiation: <read_fonts::tables::aat::Lookup6>::value::<u16>::{closure#0} |
136 | 0 | let entry = &entries[ix]; |
137 | 0 | let value = entry.value; |
138 | 0 | return Ok(value.get()); |
139 | 0 | } |
140 | 0 | Err(ReadError::OutOfBounds) |
141 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::Lookup6>::value::<u32> Unexecuted instantiation: <read_fonts::tables::aat::Lookup6>::value::<u16> |
142 | | |
143 | 0 | pub fn entries<T: LookupValue>(&self) -> Result<&[LookupSingle<T>], ReadError> { |
144 | 0 | FontData::new(self.entries_data()) |
145 | 0 | .cursor() |
146 | 0 | .read_array(self.n_units() as usize) |
147 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::Lookup6>::entries::<u32> Unexecuted instantiation: <read_fonts::tables::aat::Lookup6>::entries::<u16> |
148 | | } |
149 | | |
150 | | impl Lookup8<'_> { |
151 | 0 | pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> { |
152 | 0 | index |
153 | 0 | .checked_sub(self.first_glyph()) |
154 | 0 | .and_then(|ix| { |
155 | 0 | self.value_array() |
156 | 0 | .get(ix as usize) |
157 | 0 | .map(|val| T::from_u16(val.get())) Unexecuted instantiation: <read_fonts::tables::aat::Lookup8>::value::<u32>::{closure#0}::{closure#0}Unexecuted instantiation: <read_fonts::tables::aat::Lookup8>::value::<u16>::{closure#0}::{closure#0} |
158 | 0 | }) Unexecuted instantiation: <read_fonts::tables::aat::Lookup8>::value::<u32>::{closure#0}Unexecuted instantiation: <read_fonts::tables::aat::Lookup8>::value::<u16>::{closure#0} |
159 | 0 | .ok_or(ReadError::OutOfBounds) |
160 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::Lookup8>::value::<u32> Unexecuted instantiation: <read_fonts::tables::aat::Lookup8>::value::<u16> |
161 | | } |
162 | | |
163 | | impl Lookup10<'_> { |
164 | 0 | pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> { |
165 | 0 | let ix = index |
166 | 0 | .checked_sub(self.first_glyph()) |
167 | 0 | .ok_or(ReadError::OutOfBounds)? as usize; |
168 | 0 | let unit_size = self.unit_size() as usize; |
169 | 0 | let offset = ix.wrapping_mul(unit_size); |
170 | 0 | let mut cursor = FontData::new(self.values_data()).cursor(); |
171 | 0 | cursor.advance_by(offset); |
172 | 0 | let val = match unit_size { |
173 | 0 | 1 => cursor.read::<u8>()? as u32, |
174 | 0 | 2 => cursor.read::<u16>()? as u32, |
175 | 0 | 4 => cursor.read::<u32>()?, |
176 | | _ => { |
177 | 0 | return Err(ReadError::MalformedData( |
178 | 0 | "invalid unit_size in format 10 AAT lookup table", |
179 | 0 | )) |
180 | | } |
181 | | }; |
182 | 0 | Ok(T::from_u32(val)) |
183 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::Lookup10>::value::<u32> Unexecuted instantiation: <read_fonts::tables::aat::Lookup10>::value::<u16> |
184 | | } |
185 | | |
186 | | impl Lookup<'_> { |
187 | 0 | pub fn value<T: LookupValue>(&self, index: u16) -> Result<T, ReadError> { |
188 | 0 | match self { |
189 | 0 | Lookup::Format0(lookup) => lookup.value::<T>(index), |
190 | 0 | Lookup::Format2(lookup) => lookup.value::<T>(index), |
191 | 0 | Lookup::Format4(lookup) => lookup.value::<T>(index), |
192 | 0 | Lookup::Format6(lookup) => lookup.value::<T>(index), |
193 | 0 | Lookup::Format8(lookup) => lookup.value::<T>(index), |
194 | 0 | Lookup::Format10(lookup) => lookup.value::<T>(index), |
195 | | } |
196 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::Lookup>::value::<u32> Unexecuted instantiation: <read_fonts::tables::aat::Lookup>::value::<u16> |
197 | | } |
198 | | |
199 | | #[derive(Clone)] |
200 | | pub struct TypedLookup<'a, T> { |
201 | | pub lookup: Lookup<'a>, |
202 | | _marker: std::marker::PhantomData<fn() -> T>, |
203 | | } |
204 | | |
205 | | impl<T: LookupValue> TypedLookup<'_, T> { |
206 | | /// Returns the value associated with the given index. |
207 | 0 | pub fn value(&self, index: u16) -> Result<T, ReadError> { |
208 | 0 | self.lookup.value::<T>(index) |
209 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::TypedLookup<u32>>::value Unexecuted instantiation: <read_fonts::tables::aat::TypedLookup<u16>>::value |
210 | | } |
211 | | |
212 | | impl<T> ReadArgs for TypedLookup<'_, T> { |
213 | | type Args = (); |
214 | | } |
215 | | |
216 | | impl<'a, T> FontRead<'a> for TypedLookup<'a, T> { |
217 | 0 | fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> { |
218 | | Ok(Self { |
219 | 0 | lookup: Lookup::read(data)?, |
220 | 0 | _marker: std::marker::PhantomData, |
221 | | }) |
222 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::TypedLookup<u32> as read_fonts::read::FontRead>::read_with_args Unexecuted instantiation: <read_fonts::tables::aat::TypedLookup<u16> as read_fonts::read::FontRead>::read_with_args |
223 | | } |
224 | | |
225 | | #[cfg(feature = "experimental_traverse")] |
226 | | impl<'a, T> SomeTable<'a> for TypedLookup<'a, T> { |
227 | | fn type_name(&self) -> &str { |
228 | | "TypedLookup" |
229 | | } |
230 | | |
231 | | fn get_field(&self, idx: usize) -> Option<Field<'a>> { |
232 | | self.lookup.get_field(idx) |
233 | | } |
234 | | } |
235 | | |
236 | | /// Trait for values that can be read from lookup tables. |
237 | | pub trait LookupValue: Copy + Scalar + bytemuck::AnyBitPattern { |
238 | | fn from_u16(v: u16) -> Self; |
239 | | fn from_u32(v: u32) -> Self; |
240 | | } |
241 | | |
242 | | impl LookupValue for u16 { |
243 | 0 | fn from_u16(v: u16) -> Self { |
244 | 0 | v |
245 | 0 | } |
246 | | |
247 | 0 | fn from_u32(v: u32) -> Self { |
248 | | // intentionally truncates |
249 | 0 | v as _ |
250 | 0 | } |
251 | | } |
252 | | |
253 | | impl LookupValue for u32 { |
254 | 0 | fn from_u16(v: u16) -> Self { |
255 | 0 | v as _ |
256 | 0 | } |
257 | | |
258 | 0 | fn from_u32(v: u32) -> Self { |
259 | 0 | v |
260 | 0 | } |
261 | | } |
262 | | |
263 | | impl LookupValue for GlyphId16 { |
264 | 0 | fn from_u16(v: u16) -> Self { |
265 | 0 | GlyphId16::from(v) |
266 | 0 | } |
267 | | |
268 | 0 | fn from_u32(v: u32) -> Self { |
269 | | // intentionally truncates |
270 | 0 | GlyphId16::from(v as u16) |
271 | 0 | } |
272 | | } |
273 | | |
274 | | pub type LookupU16<'a> = TypedLookup<'a, u16>; |
275 | | pub type LookupU32<'a> = TypedLookup<'a, u32>; |
276 | | pub type LookupGlyphId<'a> = TypedLookup<'a, GlyphId16>; |
277 | | |
278 | | /// Empty data type for a state table entry with no payload. |
279 | | /// |
280 | | /// Note: this type is only intended for use as the type parameter for |
281 | | /// `StateEntry`. The inner field is private and this type cannot be |
282 | | /// constructed outside of this module. |
283 | | #[derive(Copy, Clone, bytemuck::AnyBitPattern, Debug)] |
284 | | pub struct NoPayload(()); |
285 | | |
286 | | impl FixedSize for NoPayload { |
287 | | const RAW_BYTE_LEN: usize = 0; |
288 | | } |
289 | | |
290 | | /// Entry in an (extended) state table. |
291 | | #[derive(Clone, Debug)] |
292 | | pub struct StateEntry<T = NoPayload> { |
293 | | /// Index of the next state. |
294 | | pub new_state: u16, |
295 | | /// Flag values are table specific. |
296 | | pub flags: u16, |
297 | | /// Payload is table specific. |
298 | | pub payload: T, |
299 | | } |
300 | | |
301 | | impl<T: bytemuck::AnyBitPattern + FixedSize> ReadArgs for StateEntry<T> { |
302 | | type Args = (); |
303 | | } |
304 | | |
305 | | impl<'a, T: bytemuck::AnyBitPattern + FixedSize> FontRead<'a> for StateEntry<T> { |
306 | 0 | fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> { |
307 | 0 | let mut cursor = data.cursor(); |
308 | 0 | let new_state = cursor.read()?; |
309 | 0 | let flags = cursor.read()?; |
310 | 0 | let remaining = cursor.remaining().ok_or(ReadError::OutOfBounds)?; |
311 | 0 | let payload = *remaining.read_ref_at(0)?; |
312 | 0 | Ok(Self { |
313 | 0 | new_state, |
314 | 0 | flags, |
315 | 0 | payload, |
316 | 0 | }) |
317 | 0 | } |
318 | | } |
319 | | |
320 | | impl<T> FixedSize for StateEntry<T> |
321 | | where |
322 | | T: FixedSize, |
323 | | { |
324 | | // Two u16 fields + payload |
325 | | const RAW_BYTE_LEN: usize = u16::RAW_BYTE_LEN + u16::RAW_BYTE_LEN + T::RAW_BYTE_LEN; |
326 | | } |
327 | | |
328 | | /// Table for driving a finite state machine for layout. |
329 | | /// |
330 | | /// The input to the state machine consists of the current state |
331 | | /// and a glyph class. The output is an [entry](StateEntry) containing |
332 | | /// the next state and a payload that is dependent on the type of |
333 | | /// layout action being performed. |
334 | | /// |
335 | | /// See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#StateHeader> |
336 | | /// for more detail. |
337 | | #[derive(Clone)] |
338 | | pub struct StateTable<'a> { |
339 | | pub header: StateHeader<'a>, |
340 | | n_classes: usize, |
341 | | class_first_glyph: u16, |
342 | | class_array: &'a [u8], |
343 | | state_array: &'a [u8], |
344 | | entry_table: &'a [u8], |
345 | | } |
346 | | |
347 | | impl StateTable<'_> { |
348 | | pub const HEADER_LEN: usize = u16::RAW_BYTE_LEN * 4; |
349 | | |
350 | | /// Returns the class table entry for the given glyph identifier. |
351 | 0 | pub fn class(&self, glyph_id: GlyphId16) -> Result<u8, ReadError> { |
352 | 0 | let glyph_id = glyph_id.to_u16(); |
353 | 0 | if glyph_id == 0xFFFF { |
354 | 0 | return Ok(class::DELETED_GLYPH); |
355 | 0 | } |
356 | 0 | glyph_id |
357 | 0 | .checked_sub(self.class_first_glyph) |
358 | 0 | .and_then(|ix| self.class_array.get(ix as usize).copied()) |
359 | 0 | .ok_or(ReadError::OutOfBounds) |
360 | 0 | } |
361 | | |
362 | | /// Returns the entry for the given state and class. |
363 | | #[inline(always)] |
364 | 0 | pub fn entry(&self, state: u16, class: u8) -> Result<StateEntry, ReadError> { |
365 | 0 | let mut class = class as usize; |
366 | 0 | if class >= self.n_classes { |
367 | 0 | class = class::OUT_OF_BOUNDS as usize; |
368 | 0 | } |
369 | 0 | let entry_ix = self |
370 | 0 | .state_array |
371 | 0 | .get(state as usize * self.n_classes + class) |
372 | 0 | .copied() |
373 | 0 | .ok_or(ReadError::OutOfBounds)? as usize; |
374 | 0 | let entry_offset = entry_ix * 4; |
375 | 0 | let entry_data = self |
376 | 0 | .entry_table |
377 | 0 | .get(entry_offset..) |
378 | 0 | .ok_or(ReadError::OutOfBounds)?; |
379 | 0 | let mut entry = StateEntry::read(FontData::new(entry_data))?; |
380 | | // For legacy state tables, the newState is a byte offset into |
381 | | // the state array. Convert this to an index for consistency. |
382 | 0 | let new_state = (entry.new_state as i32) |
383 | 0 | .checked_sub(self.header.state_array_offset().to_u32() as i32) |
384 | 0 | .ok_or(ReadError::OutOfBounds)? |
385 | 0 | / self.n_classes as i32; |
386 | 0 | entry.new_state = new_state.try_into().map_err(|_| ReadError::OutOfBounds)?; |
387 | 0 | Ok(entry) |
388 | 0 | } |
389 | | |
390 | | /// Reads scalar values that are referenced from state table entries. |
391 | 0 | pub fn read_value<T: Scalar>(&self, offset: usize) -> Result<T, ReadError> { |
392 | 0 | self.header.offset_data().read_at::<T>(offset) |
393 | 0 | } |
394 | | } |
395 | | |
396 | | impl ReadArgs for StateTable<'_> { |
397 | | type Args = (); |
398 | | } |
399 | | |
400 | | impl<'a> FontRead<'a> for StateTable<'a> { |
401 | 0 | fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> { |
402 | 0 | let header = StateHeader::read(data)?; |
403 | | // Each state has a 1-byte entry per class so state_size == n_classes |
404 | 0 | let n_classes = header.state_size() as usize; |
405 | 0 | if n_classes == 0 { |
406 | | // This will result in a divide by 0 in all cases |
407 | 0 | return Err(ReadError::MalformedData("empty AAT state table")); |
408 | 0 | } |
409 | 0 | let class_table = header.class_table()?; |
410 | 0 | let class_first_glyph = class_table.first_glyph(); |
411 | 0 | let class_array = class_table.class_array(); |
412 | 0 | let state_array = header.state_array()?.data(); |
413 | 0 | let entry_table = header.entry_table()?.data(); |
414 | | Ok(Self { |
415 | 0 | header: StateHeader::read(data)?, |
416 | 0 | n_classes, |
417 | 0 | class_first_glyph, |
418 | 0 | class_array, |
419 | 0 | state_array, |
420 | 0 | entry_table, |
421 | | }) |
422 | 0 | } |
423 | | } |
424 | | |
425 | | #[cfg(feature = "experimental_traverse")] |
426 | | impl<'a> SomeTable<'a> for StateTable<'a> { |
427 | | fn type_name(&self) -> &str { |
428 | | "StateTable" |
429 | | } |
430 | | |
431 | | fn get_field(&self, idx: usize) -> Option<Field<'a>> { |
432 | | self.header.get_field(idx) |
433 | | } |
434 | | } |
435 | | |
436 | | #[derive(Clone)] |
437 | | pub struct ExtendedStateTable<'a, T = NoPayload> { |
438 | | pub n_classes: usize, |
439 | | pub class_table: LookupU16<'a>, |
440 | | state_array: &'a [BigEndian<u16>], |
441 | | entry_table: &'a [u8], |
442 | | _marker: std::marker::PhantomData<fn() -> T>, |
443 | | } |
444 | | |
445 | | impl<T> ExtendedStateTable<'_, T> { |
446 | | pub const HEADER_LEN: usize = u32::RAW_BYTE_LEN * 4; |
447 | | } |
448 | | |
449 | | /// Table for driving a finite state machine for layout. |
450 | | /// |
451 | | /// The input to the state machine consists of the current state |
452 | | /// and a glyph class. The output is an [entry](StateEntry) containing |
453 | | /// the next state and a payload that is dependent on the type of |
454 | | /// layout action being performed. |
455 | | /// |
456 | | /// See <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html#StateHeader> |
457 | | /// for more detail. |
458 | | impl<T> ExtendedStateTable<'_, T> |
459 | | where |
460 | | T: FixedSize + bytemuck::AnyBitPattern, |
461 | | { |
462 | | /// Returns the class table entry for the given glyph identifier. |
463 | 0 | pub fn class(&self, glyph_id: GlyphId) -> Result<u16, ReadError> { |
464 | 0 | let glyph_id: u16 = glyph_id |
465 | 0 | .to_u32() |
466 | 0 | .try_into() |
467 | 0 | .map_err(|_| ReadError::OutOfBounds)?; |
468 | 0 | if glyph_id == 0xFFFF { |
469 | 0 | return Ok(class::DELETED_GLYPH as u16); |
470 | 0 | } |
471 | 0 | self.class_table.value(glyph_id) |
472 | 0 | } |
473 | | |
474 | | /// Returns the entry for the given state and class. |
475 | 0 | pub fn entry(&self, state: u16, class: u16) -> Result<StateEntry<T>, ReadError> { |
476 | 0 | let mut class = class as usize; |
477 | 0 | if class >= self.n_classes { |
478 | 0 | class = class::OUT_OF_BOUNDS as usize; |
479 | 0 | } |
480 | 0 | let state_ix = (state as usize) |
481 | 0 | .wrapping_mul(self.n_classes) |
482 | 0 | .wrapping_add(class); |
483 | 0 | let entry_ix = self |
484 | 0 | .state_array |
485 | 0 | .get(state_ix) |
486 | 0 | .copied() |
487 | 0 | .ok_or(ReadError::OutOfBounds)? |
488 | 0 | .get() as usize; |
489 | 0 | let entry_offset = entry_ix.wrapping_mul(StateEntry::<T>::RAW_BYTE_LEN); |
490 | 0 | let entry_data = self |
491 | 0 | .entry_table |
492 | 0 | .get(entry_offset..) |
493 | 0 | .ok_or(ReadError::OutOfBounds)?; |
494 | 0 | StateEntry::read(FontData::new(entry_data)) |
495 | 0 | } |
496 | | } |
497 | | |
498 | | impl<T> ReadArgs for ExtendedStateTable<'_, T> { |
499 | | type Args = (); |
500 | | } |
501 | | |
502 | | impl<'a, T> FontRead<'a> for ExtendedStateTable<'a, T> { |
503 | 0 | fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> { |
504 | 0 | let header = StxHeader::read(data)?; |
505 | 0 | let n_classes = header.n_classes() as usize; |
506 | 0 | let class_table = header.class_table()?; |
507 | 0 | let state_array = header.state_array()?.data(); |
508 | 0 | let entry_table = header.entry_table()?.data(); |
509 | 0 | Ok(Self { |
510 | 0 | n_classes, |
511 | 0 | class_table, |
512 | 0 | state_array, |
513 | 0 | entry_table, |
514 | 0 | _marker: std::marker::PhantomData, |
515 | 0 | }) |
516 | 0 | } Unexecuted instantiation: <read_fonts::tables::aat::ExtendedStateTable<font_types::raw::BigEndian<u16>> as read_fonts::read::FontRead>::read_with_args Unexecuted instantiation: <read_fonts::tables::aat::ExtendedStateTable<read_fonts::tables::morx::InsertionEntryData> as read_fonts::read::FontRead>::read_with_args Unexecuted instantiation: <read_fonts::tables::aat::ExtendedStateTable<read_fonts::tables::morx::ContextualEntryData> as read_fonts::read::FontRead>::read_with_args Unexecuted instantiation: <read_fonts::tables::aat::ExtendedStateTable as read_fonts::read::FontRead>::read_with_args |
517 | | } |
518 | | |
519 | | #[cfg(feature = "experimental_traverse")] |
520 | | impl<'a, T> SomeTable<'a> for ExtendedStateTable<'a, T> { |
521 | | fn type_name(&self) -> &str { |
522 | | "ExtendedStateTable" |
523 | | } |
524 | | |
525 | | fn get_field(&self, _idx: usize) -> Option<Field<'a>> { |
526 | | None |
527 | | } |
528 | | } |
529 | | |
530 | | /// Reads an array of T from the given FontData, ensuring that the byte length |
531 | | /// is a multiple of the size of T. |
532 | | /// |
533 | | /// Many of the `morx` subtables have arrays without associated lengths so we |
534 | | /// simply read to the end of the available data. The `FontData::read_array` |
535 | | /// method will fail if the byte range provided is not exact so this helper |
536 | | /// allows us to force the lengths to an acceptable value. |
537 | 0 | pub(crate) fn safe_read_array_to_end<'a, T: bytemuck::AnyBitPattern + FixedSize>( |
538 | 0 | data: &FontData<'a>, |
539 | 0 | offset: usize, |
540 | 0 | ) -> Result<&'a [T], ReadError> { |
541 | 0 | let len = data |
542 | 0 | .len() |
543 | 0 | .checked_sub(offset) |
544 | 0 | .ok_or(ReadError::OutOfBounds)?; |
545 | 0 | let end = offset + len / T::RAW_BYTE_LEN * T::RAW_BYTE_LEN; |
546 | 0 | data.read_array(offset..end) |
547 | 0 | } Unexecuted instantiation: read_fonts::tables::aat::safe_read_array_to_end::<font_types::raw::BigEndian<font_types::offset::Offset32>> Unexecuted instantiation: read_fonts::tables::aat::safe_read_array_to_end::<font_types::raw::BigEndian<font_types::glyph_id::GlyphId16>> Unexecuted instantiation: read_fonts::tables::aat::safe_read_array_to_end::<font_types::raw::BigEndian<i32>> Unexecuted instantiation: read_fonts::tables::aat::safe_read_array_to_end::<font_types::raw::BigEndian<u32>> Unexecuted instantiation: read_fonts::tables::aat::safe_read_array_to_end::<font_types::raw::BigEndian<i16>> Unexecuted instantiation: read_fonts::tables::aat::safe_read_array_to_end::<font_types::raw::BigEndian<u16>> |
548 | | |
549 | | #[cfg(test)] |
550 | | mod tests { |
551 | | use font_test_data::bebuffer::BeBuffer; |
552 | | |
553 | | use super::*; |
554 | | |
555 | | #[test] |
556 | | fn lookup_format_0() { |
557 | | #[rustfmt::skip] |
558 | | let words = [ |
559 | | 0_u16, // format |
560 | | 0, 2, 4, 6, 8, 10, 12, 14, 16, // maps all glyphs to gid * 2 |
561 | | ]; |
562 | | let mut buf = BeBuffer::new(); |
563 | | buf = buf.extend(words); |
564 | | let lookup = LookupU16::read(buf.data().into()).unwrap(); |
565 | | for gid in 0..=8 { |
566 | | assert_eq!(lookup.value(gid).unwrap(), gid * 2); |
567 | | } |
568 | | assert!(lookup.value(9).is_err()); |
569 | | } |
570 | | |
571 | | // Taken from example 2 at https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html |
572 | | #[test] |
573 | | fn lookup_format_2() { |
574 | | #[rustfmt::skip] |
575 | | let words = [ |
576 | | 2_u16, // format |
577 | | 6, // unit size (6 bytes) |
578 | | 3, // number of units |
579 | | 12, // search range |
580 | | 1, // entry selector |
581 | | 6, // range shift |
582 | | 22, 20, 4, // First segment, mapping glyphs 20 through 22 to class 4 |
583 | | 24, 23, 5, // Second segment, mapping glyph 23 and 24 to class 5 |
584 | | 28, 25, 6, // Third segment, mapping glyphs 25 through 28 to class 6 |
585 | | ]; |
586 | | let mut buf = BeBuffer::new(); |
587 | | buf = buf.extend(words); |
588 | | let lookup = LookupU16::read(buf.data().into()).unwrap(); |
589 | | let expected = [(20..=22, 4), (23..=24, 5), (25..=28, 6)]; |
590 | | for (range, class) in expected { |
591 | | for gid in range { |
592 | | assert_eq!(lookup.value(gid).unwrap(), class); |
593 | | } |
594 | | } |
595 | | for fail in [0, 10, 19, 29, 0xFFFF] { |
596 | | assert!(lookup.value(fail).is_err()); |
597 | | } |
598 | | } |
599 | | |
600 | | #[test] |
601 | | fn lookup_format_4() { |
602 | | #[rustfmt::skip] |
603 | | let words = [ |
604 | | 4_u16, // format |
605 | | 6, // unit size (6 bytes) |
606 | | 3, // number of units |
607 | | 12, // search range |
608 | | 1, // entry selector |
609 | | 6, // range shift |
610 | | 22, 20, 30, // First segment, mapping glyphs 20 through 22 to mapped data at offset 30 |
611 | | 24, 23, 36, // Second segment, mapping glyph 23 and 24 to mapped data at offset 36 |
612 | | 28, 25, 40, // Third segment, mapping glyphs 25 through 28 to mapped data at offset 40 |
613 | | // mapped data |
614 | | 3, 2, 1, |
615 | | 100, 150, |
616 | | 8, 6, 7, 9 |
617 | | ]; |
618 | | let mut buf = BeBuffer::new(); |
619 | | buf = buf.extend(words); |
620 | | let lookup = LookupU16::read(buf.data().into()).unwrap(); |
621 | | let expected = [ |
622 | | (20, 3), |
623 | | (21, 2), |
624 | | (22, 1), |
625 | | (23, 100), |
626 | | (24, 150), |
627 | | (25, 8), |
628 | | (26, 6), |
629 | | (27, 7), |
630 | | (28, 9), |
631 | | ]; |
632 | | for (in_glyph, out_glyph) in expected { |
633 | | assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph); |
634 | | } |
635 | | for fail in [0, 10, 19, 29, 0xFFFF] { |
636 | | assert!(lookup.value(fail).is_err()); |
637 | | } |
638 | | } |
639 | | |
640 | | // Taken from example 1 at https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6morx.html |
641 | | #[test] |
642 | | fn lookup_format_6() { |
643 | | #[rustfmt::skip] |
644 | | let words = [ |
645 | | 6_u16, // format |
646 | | 4, // unit size (4 bytes) |
647 | | 4, // number of units |
648 | | 16, // search range |
649 | | 2, // entry selector |
650 | | 0, // range shift |
651 | | 50, 600, // Input glyph 50 maps to glyph 600 |
652 | | 51, 601, // Input glyph 51 maps to glyph 601 |
653 | | 201, 602, // Input glyph 201 maps to glyph 602 |
654 | | 202, 900, // Input glyph 202 maps to glyph 900 |
655 | | ]; |
656 | | let mut buf = BeBuffer::new(); |
657 | | buf = buf.extend(words); |
658 | | let lookup = LookupU16::read(buf.data().into()).unwrap(); |
659 | | let expected = [(50, 600), (51, 601), (201, 602), (202, 900)]; |
660 | | for (in_glyph, out_glyph) in expected { |
661 | | assert_eq!(lookup.value(in_glyph).unwrap(), out_glyph); |
662 | | } |
663 | | for fail in [0, 10, 49, 52, 203, 0xFFFF] { |
664 | | assert!(lookup.value(fail).is_err()); |
665 | | } |
666 | | } |
667 | | |
668 | | #[test] |
669 | | fn lookup_format_8() { |
670 | | #[rustfmt::skip] |
671 | | let words = [ |
672 | | 8_u16, // format |
673 | | 201, // first glyph |
674 | | 7, // glyph count |
675 | | 3, 8, 2, 9, 1, 200, 60, // glyphs 201..209 mapped to these values |
676 | | ]; |
677 | | let mut buf = BeBuffer::new(); |
678 | | buf = buf.extend(words); |
679 | | let lookup = LookupU16::read(buf.data().into()).unwrap(); |
680 | | let expected = &words[3..]; |
681 | | for (gid, expected) in (201..209).zip(expected) { |
682 | | assert_eq!(lookup.value(gid).unwrap(), *expected); |
683 | | } |
684 | | for fail in [0, 10, 200, 210, 0xFFFF] { |
685 | | assert!(lookup.value(fail).is_err()); |
686 | | } |
687 | | } |
688 | | |
689 | | #[test] |
690 | | fn lookup_format_10() { |
691 | | #[rustfmt::skip] |
692 | | let words = [ |
693 | | 10_u16, // format |
694 | | 4, // unit size, use 4 byte values |
695 | | 201, // first glyph |
696 | | 7, // glyph count |
697 | | ]; |
698 | | // glyphs 201..209 mapped to these values |
699 | | let mapped = [3_u32, 8, 2902384, 9, 1, u32::MAX, 60]; |
700 | | let mut buf = BeBuffer::new(); |
701 | | buf = buf.extend(words).extend(mapped); |
702 | | let lookup = LookupU32::read(buf.data().into()).unwrap(); |
703 | | for (gid, expected) in (201..209).zip(mapped) { |
704 | | assert_eq!(lookup.value(gid).unwrap(), expected); |
705 | | } |
706 | | for fail in [0, 10, 200, 210, 0xFFFF] { |
707 | | assert!(lookup.value(fail).is_err()); |
708 | | } |
709 | | } |
710 | | |
711 | | #[test] |
712 | | fn extended_state_table() { |
713 | | #[rustfmt::skip] |
714 | | let header = [ |
715 | | 6_u32, // number of classes |
716 | | 20, // byte offset to class table |
717 | | 56, // byte offset to state array |
718 | | 92, // byte offset to entry array |
719 | | 0, // padding |
720 | | ]; |
721 | | #[rustfmt::skip] |
722 | | let class_table = [ |
723 | | 6_u16, // format |
724 | | 4, // unit size (4 bytes) |
725 | | 5, // number of units |
726 | | 16, // search range |
727 | | 2, // entry selector |
728 | | 0, // range shift |
729 | | 50, 4, // Input glyph 50 maps to class 4 |
730 | | 51, 4, // Input glyph 51 maps to class 4 |
731 | | 80, 5, // Input glyph 80 maps to class 5 |
732 | | 201, 4, // Input glyph 201 maps to class 4 |
733 | | 202, 4, // Input glyph 202 maps to class 4 |
734 | | !0, !0 |
735 | | ]; |
736 | | #[rustfmt::skip] |
737 | | let state_array: [u16; 18] = [ |
738 | | 0, 0, 0, 0, 0, 1, |
739 | | 0, 0, 0, 0, 0, 1, |
740 | | 0, 0, 0, 0, 2, 1, |
741 | | ]; |
742 | | #[rustfmt::skip] |
743 | | let entry_table: [u16; 12] = [ |
744 | | 0, 0, u16::MAX, u16::MAX, |
745 | | 2, 0, u16::MAX, u16::MAX, |
746 | | 0, 0, u16::MAX, 0, |
747 | | ]; |
748 | | let buf = BeBuffer::new() |
749 | | .extend(header) |
750 | | .extend(class_table) |
751 | | .extend(state_array) |
752 | | .extend(entry_table); |
753 | | let table = ExtendedStateTable::<ContextualData>::read(buf.data().into()).unwrap(); |
754 | | // check class lookups |
755 | | let [class_50, class_80, class_201] = |
756 | | [50, 80, 201].map(|gid| table.class(GlyphId::new(gid)).unwrap()); |
757 | | assert_eq!(class_50, 4); |
758 | | assert_eq!(class_80, 5); |
759 | | assert_eq!(class_201, 4); |
760 | | // initial state |
761 | | let entry = table.entry(0, 4).unwrap(); |
762 | | assert_eq!(entry.new_state, 0); |
763 | | assert_eq!(entry.payload.current_index, !0); |
764 | | // entry (state 0, class 5) should transition to state 2 |
765 | | let entry = table.entry(0, 5).unwrap(); |
766 | | assert_eq!(entry.new_state, 2); |
767 | | // from state 2, we transition back to state 0 when class is not 5 |
768 | | // this also enables an action (payload.current_index != -1) |
769 | | let entry = table.entry(2, 4).unwrap(); |
770 | | assert_eq!(entry.new_state, 0); |
771 | | assert_eq!(entry.payload.current_index, 0); |
772 | | } |
773 | | |
774 | | #[derive(Copy, Clone, Debug, bytemuck::AnyBitPattern)] |
775 | | #[repr(C, packed)] |
776 | | struct ContextualData { |
777 | | _mark_index: BigEndian<u16>, |
778 | | current_index: BigEndian<u16>, |
779 | | } |
780 | | |
781 | | impl FixedSize for ContextualData { |
782 | | const RAW_BYTE_LEN: usize = 4; |
783 | | } |
784 | | |
785 | | // Take from example at <https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kern.html> |
786 | | // with class table trimmed to 4 glyphs |
787 | | #[test] |
788 | | fn state_table() { |
789 | | #[rustfmt::skip] |
790 | | let header = [ |
791 | | 7_u16, // number of classes |
792 | | 10, // byte offset to class table |
793 | | 18, // byte offset to state array |
794 | | 40, // byte offset to entry array |
795 | | 64, // byte offset to value array (unused here) |
796 | | ]; |
797 | | #[rustfmt::skip] |
798 | | let class_table = [ |
799 | | 3_u16, // first glyph |
800 | | 4, // number of glyphs |
801 | | ]; |
802 | | let classes = [1u8, 2, 3, 4]; |
803 | | #[rustfmt::skip] |
804 | | let state_array: [u8; 22] = [ |
805 | | 2, 0, 0, 2, 1, 0, 0, |
806 | | 2, 0, 0, 2, 1, 0, 0, |
807 | | 2, 3, 3, 2, 3, 4, 5, |
808 | | 0, // padding |
809 | | ]; |
810 | | #[rustfmt::skip] |
811 | | let entry_table: [u16; 10] = [ |
812 | | // The first column are offsets from the beginning of the state |
813 | | // table to some position in the state array |
814 | | 18, 0x8112, |
815 | | 32, 0x8112, |
816 | | 18, 0x0000, |
817 | | 32, 0x8114, |
818 | | 18, 0x8116, |
819 | | ]; |
820 | | let buf = BeBuffer::new() |
821 | | .extend(header) |
822 | | .extend(class_table) |
823 | | .extend(classes) |
824 | | .extend(state_array) |
825 | | .extend(entry_table); |
826 | | let table = StateTable::read(buf.data().into()).unwrap(); |
827 | | // check class lookups |
828 | | for i in 0..4u8 { |
829 | | assert_eq!(table.class(GlyphId16::from(i as u16 + 3)).unwrap(), i + 1); |
830 | | } |
831 | | // (state, class) -> (new_state, flags) |
832 | | let cases = [ |
833 | | ((0, 4), (2, 0x8112)), |
834 | | ((2, 1), (2, 0x8114)), |
835 | | ((1, 3), (0, 0x0000)), |
836 | | ((2, 5), (0, 0x8116)), |
837 | | ]; |
838 | | for ((state, class), (new_state, flags)) in cases { |
839 | | let entry = table.entry(state, class).unwrap(); |
840 | | assert_eq!( |
841 | | entry.new_state, new_state, |
842 | | "state {state}, class {class} should map to new state {new_state} (got {})", |
843 | | entry.new_state |
844 | | ); |
845 | | assert_eq!( |
846 | | entry.flags, flags, |
847 | | "state {state}, class {class} should map to flags 0x{flags:X} (got 0x{:X})", |
848 | | entry.flags |
849 | | ); |
850 | | } |
851 | | } |
852 | | } |