1/*!
2A collection of [Apple Advanced Typography](
3https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6AATIntro.html)
4related types.
5*/
6
7use core::num::NonZeroU16;
8
9use crate::parser::{FromData, LazyArray16, NumFrom, Offset, Offset16, Offset32, Stream};
10use crate::GlyphId;
11
12/// Predefined states.
13pub 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).
22pub 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)]
33pub 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
44impl<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
58impl<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].
103pub type StateEntry = GenericStateEntry<()>;
104
105/// A type-safe wrapper for a kerning value offset.
106#[derive(Clone, Copy, Debug)]
107pub struct ValueOffset(u16);
108
109impl 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)]
126pub 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
136impl<'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
215impl 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)]
228pub 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
236impl<'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
292impl<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)]
304pub struct Lookup<'a> {
305 data: LookupInner<'a>,
306}
307
308impl<'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
324impl 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)]
331enum 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
348impl<'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)]
439struct BinarySearchTable<'a, T: BinarySearchValue> {
440 values: LazyArray16<'a, T>,
441 len: NonZeroU16, // values length excluding termination segment
442}
443
444impl<'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
492trait 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)]
498struct LookupSegment {
499 last_glyph: u16,
500 first_glyph: u16,
501 value: u16,
502}
503
504impl 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
518impl 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)]
537struct LookupSingle {
538 glyph: u16,
539 value: u16,
540}
541
542impl 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
555impl 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