//! An [Extended Kerning Table]( //! https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kerx.html) implementation. // TODO: find a way to test this table // This table is basically untested because it uses Apple's State Tables // and I have no idea how to generate them. use core::num::NonZeroU16; use crate::kern::KerningPair; use crate::parser::{FromData, LazyArray32, NumFrom, Offset, Offset32, Stream}; use crate::{aat, GlyphId}; const HEADER_SIZE: usize = 12; /// A format 0 subtable. /// /// Ordered List of Kerning Pairs. /// /// The same as in `kern`, but uses `LazyArray32` instead of `LazyArray16`. #[derive(Clone, Copy, Debug)] pub struct Subtable0<'a> { /// A list of kerning pairs. pub pairs: LazyArray32<'a, KerningPair>, } impl<'a> Subtable0<'a> { /// Parses a subtable from raw data. fn parse(data: &'a [u8]) -> Option { let mut s = Stream::new(data); let number_of_pairs = s.read::()?; s.advance(12); // search_range (u32) + entry_selector (u32) + range_shift (u32) let pairs = s.read_array32::(number_of_pairs)?; Some(Self { pairs }) } /// Returns kerning for a pair of glyphs. #[inline] pub fn glyphs_kerning(&self, left: GlyphId, right: GlyphId) -> Option { let needle = u32::from(left.0) << 16 | u32::from(right.0); self.pairs .binary_search_by(|v| v.pair.cmp(&needle)) .map(|(_, v)| v.value) } } /// A state machine entry. #[derive(Clone, Copy, Debug)] pub struct EntryData { /// An action index. pub action_index: u16, } impl FromData for EntryData { const SIZE: usize = 2; #[inline] fn parse(data: &[u8]) -> Option { let mut s = Stream::new(data); Some(EntryData { action_index: s.read::()?, }) } } /// A format 1 subtable. /// /// State Table for Contextual Kerning. #[derive(Clone)] pub struct Subtable1<'a> { /// A state table. pub state_table: aat::ExtendedStateTable<'a, EntryData>, actions_data: &'a [u8], } impl<'a> Subtable1<'a> { fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option { let mut s = Stream::new(data); let state_table = aat::ExtendedStateTable::parse(number_of_glyphs, &mut s)?; // Actions offset is right after the state table. let actions_offset = s.read::()?; // Actions offset is from the start of the state table and not from the start of subtable. // And since we don't know the length of the actions data, // simply store all the data after the offset. let actions_data = data.get(actions_offset.to_usize()..)?; Some(Subtable1 { state_table, actions_data, }) } /// Returns kerning at action index. #[inline] pub fn glyphs_kerning(&self, action_index: u16) -> Option { Stream::read_at(self.actions_data, usize::from(action_index) * i16::SIZE) } } impl<'a> core::ops::Deref for Subtable1<'a> { type Target = aat::ExtendedStateTable<'a, EntryData>; fn deref(&self) -> &Self::Target { &self.state_table } } impl core::fmt::Debug for Subtable1<'_> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "Subtable1 {{ ... }}") } } /// A format 2 subtable. /// /// Simple n x m Array of Kerning Values. /// /// The same as in `kern`, but uses 32bit offsets instead of 16bit one. #[derive(Clone, Copy)] pub struct Subtable2<'a>(&'a [u8]); // TODO: parse actual structure impl<'a> Subtable2<'a> { /// Returns kerning for a pair of glyphs. pub fn glyphs_kerning(&self, left: GlyphId, right: GlyphId) -> Option { let mut s = Stream::new(self.0); s.skip::(); // row_width // Offsets are from beginning of the subtable and not from the `data` start, // so we have to subtract the header. let left_hand_table_offset = s.read::()?.to_usize().checked_sub(HEADER_SIZE)?; let right_hand_table_offset = s.read::()?.to_usize().checked_sub(HEADER_SIZE)?; let array_offset = s.read::()?.to_usize().checked_sub(HEADER_SIZE)?; // 'The array can be indexed by completing the left-hand and right-hand class mappings, // adding the class values to the address of the subtable, // and fetching the kerning value to which the new address points.' let left_class = crate::kern::get_format2_class(left.0, left_hand_table_offset, self.0).unwrap_or(0); let right_class = crate::kern::get_format2_class(right.0, right_hand_table_offset, self.0).unwrap_or(0); // 'Values within the left-hand offset table should not be less than the kerning array offset.' if usize::from(left_class) < array_offset { return None; } // Classes are already premultiplied, so we only need to sum them. let index = usize::from(left_class) + usize::from(right_class); let value_offset = index.checked_sub(HEADER_SIZE)?; Stream::read_at::(self.0, value_offset) } } impl core::fmt::Debug for Subtable2<'_> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "Subtable2 {{ ... }}") } } /// A container of Anchor Points used by [`Subtable4`]. #[derive(Clone, Copy)] pub struct AnchorPoints<'a>(&'a [u8]); impl AnchorPoints<'_> { /// Returns a mark and current anchor points at action index. pub fn get(&self, action_index: u16) -> Option<(u16, u16)> { let offset = usize::from(action_index) * u16::SIZE; let mut s = Stream::new_at(self.0, offset)?; Some((s.read::()?, s.read::()?)) } } impl core::fmt::Debug for AnchorPoints<'_> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "AnchorPoints {{ ... }}") } } /// A format 4 subtable. /// /// State Table for Control Point/Anchor Point Positioning. /// /// Note: I wasn't able to find any fonts that actually use /// `ControlPointActions` and/or `ControlPointCoordinateActions`, /// therefore only `AnchorPointActions` is supported. #[derive(Clone)] pub struct Subtable4<'a> { /// A state table. pub state_table: aat::ExtendedStateTable<'a, EntryData>, /// Anchor points. pub anchor_points: AnchorPoints<'a>, } impl<'a> Subtable4<'a> { fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option { let mut s = Stream::new(data); let state_table = aat::ExtendedStateTable::parse(number_of_glyphs, &mut s)?; let flags = s.read::()?; let action_type = ((flags & 0xC0000000) >> 30) as u8; let points_offset = usize::num_from(flags & 0x00FFFFFF); // We support only Anchor Point Actions. if action_type != 1 { return None; } Some(Self { state_table, anchor_points: AnchorPoints(data.get(points_offset..)?), }) } } impl<'a> core::ops::Deref for Subtable4<'a> { type Target = aat::ExtendedStateTable<'a, EntryData>; fn deref(&self) -> &Self::Target { &self.state_table } } impl core::fmt::Debug for Subtable4<'_> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "Subtable4 {{ ... }}") } } /// A format 6 subtable. /// /// Simple Index-based n x m Array of Kerning Values. #[derive(Clone, Copy)] pub struct Subtable6<'a> { data: &'a [u8], number_of_glyphs: NonZeroU16, } impl<'a> Subtable6<'a> { // TODO: parse actual structure fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Self { Subtable6 { number_of_glyphs, data, } } /// Returns kerning for a pair of glyphs. pub fn glyphs_kerning(&self, left: GlyphId, right: GlyphId) -> Option { use core::convert::TryFrom; let mut s = Stream::new(self.data); let flags = s.read::()?; s.skip::(); // row_count s.skip::(); // col_count // All offsets are from the start of the subtable. let row_index_table_offset = s.read::()?.to_usize().checked_sub(HEADER_SIZE)?; let column_index_table_offset = s.read::()?.to_usize().checked_sub(HEADER_SIZE)?; let kerning_array_offset = s.read::()?.to_usize().checked_sub(HEADER_SIZE)?; let kerning_vector_offset = s.read::()?.to_usize().checked_sub(HEADER_SIZE)?; let row_index_table_data = self.data.get(row_index_table_offset..)?; let column_index_table_data = self.data.get(column_index_table_offset..)?; let kerning_array_data = self.data.get(kerning_array_offset..)?; let kerning_vector_data = self.data.get(kerning_vector_offset..)?; let has_long_values = flags & 0x00000001 != 0; if has_long_values { let l: u32 = aat::Lookup::parse(self.number_of_glyphs, row_index_table_data)? .value(left) .unwrap_or(0) as u32; let r: u32 = aat::Lookup::parse(self.number_of_glyphs, column_index_table_data)? .value(right) .unwrap_or(0) as u32; let array_offset = usize::try_from(l + r).ok()?.checked_mul(i32::SIZE)?; let vector_offset: u32 = Stream::read_at(kerning_array_data, array_offset)?; Stream::read_at(kerning_vector_data, usize::num_from(vector_offset)) } else { let l: u16 = aat::Lookup::parse(self.number_of_glyphs, row_index_table_data)? .value(left) .unwrap_or(0); let r: u16 = aat::Lookup::parse(self.number_of_glyphs, column_index_table_data)? .value(right) .unwrap_or(0); let array_offset = usize::try_from(l + r).ok()?.checked_mul(i16::SIZE)?; let vector_offset: u16 = Stream::read_at(kerning_array_data, array_offset)?; Stream::read_at(kerning_vector_data, usize::from(vector_offset)) } } } impl core::fmt::Debug for Subtable6<'_> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "Subtable6 {{ ... }}") } } /// An extended kerning subtable format. #[allow(missing_docs)] #[derive(Clone, Debug)] pub enum Format<'a> { Format0(Subtable0<'a>), Format1(Subtable1<'a>), Format2(Subtable2<'a>), Format4(Subtable4<'a>), Format6(Subtable6<'a>), } /// A kerning subtable. #[derive(Clone, Debug)] pub struct Subtable<'a> { /// Indicates that subtable is for horizontal text. pub horizontal: bool, /// Indicates that subtable is variable. pub variable: bool, /// Indicates that subtable has a cross-stream values. pub has_cross_stream: bool, /// Indicates that subtable uses a state machine. /// /// In this case `glyphs_kerning()` will return `None`. pub has_state_machine: bool, /// The tuple count. /// /// This value is only used with variation fonts and should be 0 for all other fonts. pub tuple_count: u32, /// Subtable format. pub format: Format<'a>, } impl<'a> Subtable<'a> { /// Returns kerning for a pair of glyphs. /// /// Returns `None` in case of state machine based subtable. #[inline] pub fn glyphs_kerning(&self, left: GlyphId, right: GlyphId) -> Option { match self.format { Format::Format0(ref subtable) => subtable.glyphs_kerning(left, right), Format::Format1(_) => None, Format::Format2(ref subtable) => subtable.glyphs_kerning(left, right), Format::Format4(_) => None, Format::Format6(ref subtable) => subtable.glyphs_kerning(left, right), } } } #[derive(Clone, Copy, Debug)] struct Coverage(u8); #[rustfmt::skip] impl Coverage { // TODO: use hex #[inline] pub fn is_horizontal(self) -> bool { self.0 & (1 << 7) == 0 } #[inline] pub fn has_cross_stream(self) -> bool { self.0 & (1 << 6) != 0 } #[inline] pub fn is_variable(self) -> bool { self.0 & (1 << 5) != 0 } } /// A list of extended kerning subtables. /// /// The internal data layout is not designed for random access, /// therefore we're not providing the `get()` method and only an iterator. #[derive(Clone, Copy)] pub struct Subtables<'a> { /// The number of glyphs from the `maxp` table. number_of_glyphs: NonZeroU16, /// The total number of tables. number_of_tables: u32, /// Actual data. Starts right after the `kerx` header. data: &'a [u8], } impl core::fmt::Debug for Subtables<'_> { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "Subtables {{ ... }}") } } impl<'a> IntoIterator for Subtables<'a> { type Item = Subtable<'a>; type IntoIter = SubtablesIter<'a>; #[inline] fn into_iter(self) -> Self::IntoIter { SubtablesIter { number_of_glyphs: self.number_of_glyphs, table_index: 0, number_of_tables: self.number_of_tables, stream: Stream::new(self.data), } } } /// An iterator over extended kerning subtables. #[allow(missing_debug_implementations)] #[derive(Clone)] pub struct SubtablesIter<'a> { /// The number of glyphs from the `maxp` table. number_of_glyphs: NonZeroU16, /// The current table index. table_index: u32, /// The total number of tables. number_of_tables: u32, /// Actual data. Starts right after the `kerx` header. stream: Stream<'a>, } impl<'a> Iterator for SubtablesIter<'a> { type Item = Subtable<'a>; fn next(&mut self) -> Option { if self.table_index == self.number_of_tables { return None; } if self.stream.at_end() { return None; } let s = &mut self.stream; let table_len = s.read::()?; let coverage = Coverage(s.read::()?); s.skip::(); // unused let raw_format = s.read::()?; let tuple_count = s.read::()?; // Subtract the header size. let data_len = usize::num_from(table_len).checked_sub(HEADER_SIZE)?; let data = s.read_bytes(data_len)?; let format = match raw_format { 0 => Subtable0::parse(data).map(Format::Format0)?, 1 => Subtable1::parse(self.number_of_glyphs, data).map(Format::Format1)?, 2 => Format::Format2(Subtable2(data)), 4 => Subtable4::parse(self.number_of_glyphs, data).map(Format::Format4)?, 6 => Format::Format6(Subtable6::parse(self.number_of_glyphs, data)), _ => { // Unknown format. return None; } }; Some(Subtable { horizontal: coverage.is_horizontal(), variable: coverage.is_variable(), has_cross_stream: coverage.has_cross_stream(), has_state_machine: raw_format == 1 || raw_format == 4, tuple_count, format, }) } } /// An [Extended Kerning Table]( /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6kerx.html). #[derive(Clone, Copy, Debug)] pub struct Table<'a> { /// A list of subtables. pub subtables: Subtables<'a>, } impl<'a> Table<'a> { /// Parses a table from raw data. /// /// `number_of_glyphs` is from the `maxp` table. pub fn parse(number_of_glyphs: NonZeroU16, data: &'a [u8]) -> Option { let mut s = Stream::new(data); s.skip::(); // version s.skip::(); // padding let number_of_tables = s.read::()?; let subtables = Subtables { number_of_glyphs, number_of_tables, data: s.tail()?, }; Some(Table { subtables }) } }