1 | /*! |
2 | A collection of [Apple Advanced Typography]( |
3 | https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6AATIntro.html) |
4 | related types. |
5 | */ |
6 | |
7 | use core::num::NonZeroU16; |
8 | |
9 | use crate::parser::{FromData, LazyArray16, NumFrom, Offset, Offset16, Offset32, Stream}; |
10 | use crate::GlyphId; |
11 | |
12 | /// Predefined states. |
13 | pub mod state { |
14 | #![allow (missing_docs)] |
15 | pub const START_OF_TEXT: u16 = 0; |
16 | } |
17 | |
18 | /// Predefined classes. |
19 | /// |
20 | /// Search for _Class Code_ in [Apple Advanced Typography Font Tables]( |
21 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html). |
22 | pub mod class { |
23 | #![allow (missing_docs)] |
24 | pub const END_OF_TEXT: u8 = 0; |
25 | pub const OUT_OF_BOUNDS: u8 = 1; |
26 | pub const DELETED_GLYPH: u8 = 2; |
27 | } |
28 | |
29 | /// A State Table entry. |
30 | /// |
31 | /// Used by legacy and extended tables. |
32 | #[derive (Clone, Copy, Debug)] |
33 | pub struct GenericStateEntry<T: FromData> { |
34 | /// A new state. |
35 | pub new_state: u16, |
36 | /// Entry flags. |
37 | pub flags: u16, |
38 | /// Additional data. |
39 | /// |
40 | /// Use `()` if no data expected. |
41 | pub extra: T, |
42 | } |
43 | |
44 | impl<T: FromData> FromData for GenericStateEntry<T> { |
45 | const SIZE: usize = 4 + T::SIZE; |
46 | |
47 | #[inline ] |
48 | fn parse(data: &[u8]) -> Option<Self> { |
49 | let mut s: Stream<'_> = Stream::new(data); |
50 | Some(GenericStateEntry { |
51 | new_state: s.read::<u16>()?, |
52 | flags: s.read::<u16>()?, |
53 | extra: s.read::<T>()?, |
54 | }) |
55 | } |
56 | } |
57 | |
58 | impl<T: FromData> GenericStateEntry<T> { |
59 | /// Checks that entry has an offset. |
60 | #[inline ] |
61 | pub fn has_offset(&self) -> bool { |
62 | self.flags & 0x3FFF != 0 |
63 | } |
64 | |
65 | /// Returns a value offset. |
66 | /// |
67 | /// Used by kern::format1 subtable. |
68 | #[inline ] |
69 | pub fn value_offset(&self) -> ValueOffset { |
70 | ValueOffset(self.flags & 0x3FFF) |
71 | } |
72 | |
73 | /// If set, reset the kerning data (clear the stack). |
74 | #[inline ] |
75 | pub fn has_reset(&self) -> bool { |
76 | self.flags & 0x2000 != 0 |
77 | } |
78 | |
79 | /// If set, advance to the next glyph before going to the new state. |
80 | #[inline ] |
81 | pub fn has_advance(&self) -> bool { |
82 | self.flags & 0x4000 == 0 |
83 | } |
84 | |
85 | /// If set, push this glyph on the kerning stack. |
86 | #[inline ] |
87 | pub fn has_push(&self) -> bool { |
88 | self.flags & 0x8000 != 0 |
89 | } |
90 | |
91 | /// If set, remember this glyph as the marked glyph. |
92 | /// |
93 | /// Used by kerx::format4 subtable. |
94 | /// |
95 | /// Yes, the same as [`has_push`](Self::has_push). |
96 | #[inline ] |
97 | pub fn has_mark(&self) -> bool { |
98 | self.flags & 0x8000 != 0 |
99 | } |
100 | } |
101 | |
102 | /// A legacy state entry used by [StateTable]. |
103 | pub type StateEntry = GenericStateEntry<()>; |
104 | |
105 | /// A type-safe wrapper for a kerning value offset. |
106 | #[derive (Clone, Copy, Debug)] |
107 | pub struct ValueOffset(u16); |
108 | |
109 | impl ValueOffset { |
110 | /// Returns the next offset. |
111 | /// |
112 | /// After reaching u16::MAX will start from 0. |
113 | #[inline ] |
114 | pub fn next(self) -> Self { |
115 | ValueOffset(self.0.wrapping_add(u16::SIZE as u16)) |
116 | } |
117 | } |
118 | |
119 | /// A [State Table]( |
120 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html). |
121 | /// |
122 | /// Also called `STHeader`. |
123 | /// |
124 | /// Currently used by `kern` table. |
125 | #[derive (Clone)] |
126 | pub struct StateTable<'a> { |
127 | number_of_classes: u16, |
128 | first_glyph: GlyphId, |
129 | class_table: &'a [u8], |
130 | state_array_offset: u16, |
131 | state_array: &'a [u8], |
132 | entry_table: &'a [u8], |
133 | actions: &'a [u8], |
134 | } |
135 | |
136 | impl<'a> StateTable<'a> { |
137 | pub(crate) fn parse(data: &'a [u8]) -> Option<Self> { |
138 | let mut s = Stream::new(data); |
139 | |
140 | let number_of_classes: u16 = s.read()?; |
141 | // Note that in format1 subtable, offsets are not from the subtable start, |
142 | // but from subtable start + `header_size`. |
143 | // So there is not need to subtract the `header_size`. |
144 | let class_table_offset = s.read::<Offset16>()?.to_usize(); |
145 | let state_array_offset = s.read::<Offset16>()?.to_usize(); |
146 | let entry_table_offset = s.read::<Offset16>()?.to_usize(); |
147 | // Ignore `values_offset` since we don't use it. |
148 | |
149 | // Parse class subtable. |
150 | let mut s = Stream::new_at(data, class_table_offset)?; |
151 | let first_glyph: GlyphId = s.read()?; |
152 | let number_of_glyphs: u16 = s.read()?; |
153 | // The class table contains u8, so it's easier to use just a slice |
154 | // instead of a LazyArray. |
155 | let class_table = s.read_bytes(usize::from(number_of_glyphs))?; |
156 | |
157 | Some(StateTable { |
158 | number_of_classes, |
159 | first_glyph, |
160 | class_table, |
161 | state_array_offset: state_array_offset as u16, |
162 | // We don't know the actual data size and it's kinda expensive to calculate. |
163 | // So we are simply storing all the data past the offset. |
164 | // Despite the fact that they may overlap. |
165 | state_array: data.get(state_array_offset..)?, |
166 | entry_table: data.get(entry_table_offset..)?, |
167 | // `ValueOffset` defines an offset from the start of the subtable data. |
168 | // We do not check that the provided offset is actually after `values_offset`. |
169 | actions: data, |
170 | }) |
171 | } |
172 | |
173 | /// Returns a glyph class. |
174 | #[inline ] |
175 | pub fn class(&self, glyph_id: GlyphId) -> Option<u8> { |
176 | if glyph_id.0 == 0xFFFF { |
177 | return Some(class::DELETED_GLYPH as u8); |
178 | } |
179 | |
180 | let idx = glyph_id.0.checked_sub(self.first_glyph.0)?; |
181 | self.class_table.get(usize::from(idx)).copied() |
182 | } |
183 | |
184 | /// Returns a class entry. |
185 | #[inline ] |
186 | pub fn entry(&self, state: u16, mut class: u8) -> Option<StateEntry> { |
187 | if u16::from(class) >= self.number_of_classes { |
188 | class = class::OUT_OF_BOUNDS as u8; |
189 | } |
190 | |
191 | let entry_idx = self |
192 | .state_array |
193 | .get(usize::from(state) * usize::from(self.number_of_classes) + usize::from(class))?; |
194 | |
195 | Stream::read_at(self.entry_table, usize::from(*entry_idx) * StateEntry::SIZE) |
196 | } |
197 | |
198 | /// Returns kerning at offset. |
199 | #[inline ] |
200 | pub fn kerning(&self, offset: ValueOffset) -> Option<i16> { |
201 | Stream::read_at(self.actions, usize::from(offset.0)) |
202 | } |
203 | |
204 | /// Produces a new state. |
205 | #[inline ] |
206 | pub fn new_state(&self, state: u16) -> u16 { |
207 | let n = (i32::from(state) - i32::from(self.state_array_offset)) |
208 | / i32::from(self.number_of_classes); |
209 | |
210 | use core::convert::TryFrom; |
211 | u16::try_from(n).unwrap_or(0) |
212 | } |
213 | } |
214 | |
215 | impl core::fmt::Debug for StateTable<'_> { |
216 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
217 | write!(f, "StateTable {{ ... }}" ) |
218 | } |
219 | } |
220 | |
221 | /// An [Extended State Table]( |
222 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html). |
223 | /// |
224 | /// Also called `STXHeader`. |
225 | /// |
226 | /// Currently used by `kerx` and `morx` tables. |
227 | #[derive (Clone)] |
228 | pub struct ExtendedStateTable<'a, T> { |
229 | number_of_classes: u32, |
230 | lookup: Lookup<'a>, |
231 | state_array: &'a [u8], |
232 | entry_table: &'a [u8], |
233 | entry_type: core::marker::PhantomData<T>, |
234 | } |
235 | |
236 | impl<'a, T: FromData> ExtendedStateTable<'a, T> { |
237 | // TODO: make private |
238 | /// Parses an Extended State Table from a stream. |
239 | /// |
240 | /// `number_of_glyphs` is from the `maxp` table. |
241 | pub fn parse(number_of_glyphs: NonZeroU16, s: &mut Stream<'a>) -> Option<Self> { |
242 | let data = s.tail()?; |
243 | |
244 | let number_of_classes = s.read::<u32>()?; |
245 | // Note that offsets are not from the subtable start, |
246 | // but from subtable start + `header_size`. |
247 | // So there is not need to subtract the `header_size`. |
248 | let lookup_table_offset = s.read::<Offset32>()?.to_usize(); |
249 | let state_array_offset = s.read::<Offset32>()?.to_usize(); |
250 | let entry_table_offset = s.read::<Offset32>()?.to_usize(); |
251 | |
252 | Some(ExtendedStateTable { |
253 | number_of_classes, |
254 | lookup: Lookup::parse(number_of_glyphs, data.get(lookup_table_offset..)?)?, |
255 | // We don't know the actual data size and it's kinda expensive to calculate. |
256 | // So we are simply storing all the data past the offset. |
257 | // Despite the fact that they may overlap. |
258 | state_array: data.get(state_array_offset..)?, |
259 | entry_table: data.get(entry_table_offset..)?, |
260 | entry_type: core::marker::PhantomData, |
261 | }) |
262 | } |
263 | |
264 | /// Returns a glyph class. |
265 | #[inline ] |
266 | pub fn class(&self, glyph_id: GlyphId) -> Option<u16> { |
267 | if glyph_id.0 == 0xFFFF { |
268 | return Some(u16::from(class::DELETED_GLYPH)); |
269 | } |
270 | |
271 | self.lookup.value(glyph_id) |
272 | } |
273 | |
274 | /// Returns a class entry. |
275 | #[inline ] |
276 | pub fn entry(&self, state: u16, mut class: u16) -> Option<GenericStateEntry<T>> { |
277 | if u32::from(class) >= self.number_of_classes { |
278 | class = u16::from(class::OUT_OF_BOUNDS); |
279 | } |
280 | |
281 | let state_idx = |
282 | usize::from(state) * usize::num_from(self.number_of_classes) + usize::from(class); |
283 | |
284 | let entry_idx: u16 = Stream::read_at(self.state_array, state_idx * u16::SIZE)?; |
285 | Stream::read_at( |
286 | self.entry_table, |
287 | usize::from(entry_idx) * GenericStateEntry::<T>::SIZE, |
288 | ) |
289 | } |
290 | } |
291 | |
292 | impl<T> core::fmt::Debug for ExtendedStateTable<'_, T> { |
293 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
294 | write!(f, "ExtendedStateTable {{ ... }}" ) |
295 | } |
296 | } |
297 | |
298 | /// A [lookup table]( |
299 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html). |
300 | /// |
301 | /// u32 values in Format10 tables will be truncated to u16. |
302 | /// u64 values in Format10 tables are not supported. |
303 | #[derive (Clone)] |
304 | pub struct Lookup<'a> { |
305 | data: LookupInner<'a>, |
306 | } |
307 | |
308 | impl<'a> Lookup<'a> { |
309 | /// Parses a lookup table from raw data. |
310 | /// |
311 | /// `number_of_glyphs` is from the `maxp` table. |
312 | #[inline ] |
313 | pub fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option<Self> { |
314 | LookupInner::parse(number_of_glyphs, data).map(|data: LookupInner<'_>| Self { data }) |
315 | } |
316 | |
317 | /// Returns a value associated with the specified glyph. |
318 | #[inline ] |
319 | pub fn value(&self, glyph_id: GlyphId) -> Option<u16> { |
320 | self.data.value(glyph_id) |
321 | } |
322 | } |
323 | |
324 | impl core::fmt::Debug for Lookup<'_> { |
325 | fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { |
326 | write!(f, "Lookup {{ ... }}" ) |
327 | } |
328 | } |
329 | |
330 | #[derive (Clone)] |
331 | enum LookupInner<'a> { |
332 | Format1(LazyArray16<'a, u16>), |
333 | Format2(BinarySearchTable<'a, LookupSegment>), |
334 | Format4(BinarySearchTable<'a, LookupSegment>, &'a [u8]), |
335 | Format6(BinarySearchTable<'a, LookupSingle>), |
336 | Format8 { |
337 | first_glyph: u16, |
338 | values: LazyArray16<'a, u16>, |
339 | }, |
340 | Format10 { |
341 | value_size: u16, |
342 | first_glyph: u16, |
343 | glyph_count: u16, |
344 | data: &'a [u8], |
345 | }, |
346 | } |
347 | |
348 | impl<'a> LookupInner<'a> { |
349 | fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option<Self> { |
350 | let mut s = Stream::new(data); |
351 | let format = s.read::<u16>()?; |
352 | match format { |
353 | 0 => { |
354 | let values = s.read_array16::<u16>(number_of_glyphs.get())?; |
355 | Some(Self::Format1(values)) |
356 | } |
357 | 2 => { |
358 | let bsearch = BinarySearchTable::<LookupSegment>::parse(s.tail()?)?; |
359 | Some(Self::Format2(bsearch)) |
360 | } |
361 | 4 => { |
362 | let bsearch = BinarySearchTable::<LookupSegment>::parse(s.tail()?)?; |
363 | Some(Self::Format4(bsearch, data)) |
364 | } |
365 | 6 => { |
366 | let bsearch = BinarySearchTable::<LookupSingle>::parse(s.tail()?)?; |
367 | Some(Self::Format6(bsearch)) |
368 | } |
369 | 8 => { |
370 | let first_glyph = s.read::<u16>()?; |
371 | let glyph_count = s.read::<u16>()?; |
372 | let values = s.read_array16::<u16>(glyph_count)?; |
373 | Some(Self::Format8 { |
374 | first_glyph, |
375 | values, |
376 | }) |
377 | } |
378 | 10 => { |
379 | let value_size = s.read::<u16>()?; |
380 | let first_glyph = s.read::<u16>()?; |
381 | let glyph_count = s.read::<u16>()?; |
382 | Some(Self::Format10 { |
383 | value_size, |
384 | first_glyph, |
385 | glyph_count, |
386 | data: s.tail()?, |
387 | }) |
388 | } |
389 | _ => None, |
390 | } |
391 | } |
392 | |
393 | fn value(&self, glyph_id: GlyphId) -> Option<u16> { |
394 | match self { |
395 | Self::Format1(values) => values.get(glyph_id.0), |
396 | Self::Format2(ref bsearch) => bsearch.get(glyph_id).map(|v| v.value), |
397 | Self::Format4(ref bsearch, data) => { |
398 | // In format 4, LookupSegment contains an offset to a list of u16 values. |
399 | // One value for each glyph in the LookupSegment range. |
400 | let segment = bsearch.get(glyph_id)?; |
401 | let index = glyph_id.0.checked_sub(segment.first_glyph)?; |
402 | let offset = usize::from(segment.value) + u16::SIZE * usize::from(index); |
403 | Stream::read_at::<u16>(data, offset) |
404 | } |
405 | Self::Format6(ref bsearch) => bsearch.get(glyph_id).map(|v| v.value), |
406 | Self::Format8 { |
407 | first_glyph, |
408 | values, |
409 | } => { |
410 | let idx = glyph_id.0.checked_sub(*first_glyph)?; |
411 | values.get(idx) |
412 | } |
413 | Self::Format10 { |
414 | value_size, |
415 | first_glyph, |
416 | glyph_count, |
417 | data, |
418 | } => { |
419 | let idx = glyph_id.0.checked_sub(*first_glyph)?; |
420 | let mut s = Stream::new(data); |
421 | match value_size { |
422 | 1 => s.read_array16::<u8>(*glyph_count)?.get(idx).map(u16::from), |
423 | 2 => s.read_array16::<u16>(*glyph_count)?.get(idx), |
424 | // TODO: we should return u32 here, but this is not supported yet |
425 | 4 => s |
426 | .read_array16::<u32>(*glyph_count)? |
427 | .get(idx) |
428 | .map(|n| n as u16), |
429 | _ => None, // 8 is also supported |
430 | } |
431 | } |
432 | } |
433 | } |
434 | } |
435 | |
436 | /// A binary searching table as defined at |
437 | /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6Tables.html |
438 | #[derive (Clone)] |
439 | struct BinarySearchTable<'a, T: BinarySearchValue> { |
440 | values: LazyArray16<'a, T>, |
441 | len: NonZeroU16, // values length excluding termination segment |
442 | } |
443 | |
444 | impl<'a, T: BinarySearchValue + core::fmt::Debug> BinarySearchTable<'a, T> { |
445 | #[inline (never)] |
446 | fn parse(data: &'a [u8]) -> Option<Self> { |
447 | let mut s = Stream::new(data); |
448 | let segment_size = s.read::<u16>()?; |
449 | let number_of_segments = s.read::<u16>()?; |
450 | s.advance(6); // search_range + entry_selector + range_shift |
451 | |
452 | if usize::from(segment_size) != T::SIZE { |
453 | return None; |
454 | } |
455 | |
456 | if number_of_segments == 0 { |
457 | return None; |
458 | } |
459 | |
460 | let values = s.read_array16::<T>(number_of_segments)?; |
461 | |
462 | // 'The number of termination values that need to be included is table-specific. |
463 | // The value that indicates binary search termination is 0xFFFF.' |
464 | let mut len = number_of_segments; |
465 | if values.last()?.is_termination() { |
466 | len = len.checked_sub(1)?; |
467 | } |
468 | |
469 | Some(BinarySearchTable { |
470 | len: NonZeroU16::new(len)?, |
471 | values, |
472 | }) |
473 | } |
474 | |
475 | fn get(&self, key: GlyphId) -> Option<T> { |
476 | let mut min = 0; |
477 | let mut max = (self.len.get() as isize) - 1; |
478 | while min <= max { |
479 | let mid = (min + max) / 2; |
480 | let v = self.values.get(mid as u16)?; |
481 | match v.contains(key) { |
482 | core::cmp::Ordering::Less => max = mid - 1, |
483 | core::cmp::Ordering::Greater => min = mid + 1, |
484 | core::cmp::Ordering::Equal => return Some(v), |
485 | } |
486 | } |
487 | |
488 | None |
489 | } |
490 | } |
491 | |
492 | trait BinarySearchValue: FromData { |
493 | fn is_termination(&self) -> bool; |
494 | fn contains(&self, glyph_id: GlyphId) -> core::cmp::Ordering; |
495 | } |
496 | |
497 | #[derive (Clone, Copy, Debug)] |
498 | struct LookupSegment { |
499 | last_glyph: u16, |
500 | first_glyph: u16, |
501 | value: u16, |
502 | } |
503 | |
504 | impl FromData for LookupSegment { |
505 | const SIZE: usize = 6; |
506 | |
507 | #[inline ] |
508 | fn parse(data: &[u8]) -> Option<Self> { |
509 | let mut s: Stream<'_> = Stream::new(data); |
510 | Some(LookupSegment { |
511 | last_glyph: s.read::<u16>()?, |
512 | first_glyph: s.read::<u16>()?, |
513 | value: s.read::<u16>()?, |
514 | }) |
515 | } |
516 | } |
517 | |
518 | impl BinarySearchValue for LookupSegment { |
519 | #[inline ] |
520 | fn is_termination(&self) -> bool { |
521 | self.last_glyph == 0xFFFF && self.first_glyph == 0xFFFF |
522 | } |
523 | |
524 | #[inline ] |
525 | fn contains(&self, id: GlyphId) -> core::cmp::Ordering { |
526 | if id.0 < self.first_glyph { |
527 | core::cmp::Ordering::Less |
528 | } else if id.0 <= self.last_glyph { |
529 | core::cmp::Ordering::Equal |
530 | } else { |
531 | core::cmp::Ordering::Greater |
532 | } |
533 | } |
534 | } |
535 | |
536 | #[derive (Clone, Copy, Debug)] |
537 | struct LookupSingle { |
538 | glyph: u16, |
539 | value: u16, |
540 | } |
541 | |
542 | impl FromData for LookupSingle { |
543 | const SIZE: usize = 4; |
544 | |
545 | #[inline ] |
546 | fn parse(data: &[u8]) -> Option<Self> { |
547 | let mut s: Stream<'_> = Stream::new(data); |
548 | Some(LookupSingle { |
549 | glyph: s.read::<u16>()?, |
550 | value: s.read::<u16>()?, |
551 | }) |
552 | } |
553 | } |
554 | |
555 | impl BinarySearchValue for LookupSingle { |
556 | #[inline ] |
557 | fn is_termination(&self) -> bool { |
558 | self.glyph == 0xFFFF |
559 | } |
560 | |
561 | #[inline ] |
562 | fn contains(&self, id: GlyphId) -> core::cmp::Ordering { |
563 | id.0.cmp(&self.glyph) |
564 | } |
565 | } |
566 | |