use crate::GlyphId;
use crate::parser::{Stream, FromData, NumFrom, Offset16, Offset};
#[derive(Clone, Copy, Debug)]
struct OTCoverage(u8);
impl OTCoverage {
#[inline]
fn is_horizontal(self) -> bool {
self.0 & (1 << 0) != 0
}
#[inline]
fn has_cross_stream(self) -> bool {
self.0 & (1 << 2) != 0
}
}
impl FromData for OTCoverage {
const SIZE: usize = 1;
#[inline]
fn parse(data: &[u8]) -> Option<Self> {
data.get(0).copied().map(OTCoverage)
}
}
#[derive(Clone, Copy, Debug)]
struct AATCoverage(u8);
impl AATCoverage {
#[inline]
fn is_horizontal(self) -> bool {
self.0 & (1 << 7) == 0
}
#[inline]
fn has_cross_stream(self) -> bool {
self.0 & (1 << 6) != 0
}
#[inline]
fn is_variable(self) -> bool {
self.0 & (1 << 5) != 0
}
}
impl FromData for AATCoverage {
const SIZE: usize = 1;
#[inline]
fn parse(data: &[u8]) -> Option<Self> {
data.get(0).copied().map(AATCoverage)
}
}
#[derive(Clone, Copy)]
struct KerningRecord {
pair: u32,
value: i16,
}
impl FromData for KerningRecord {
const SIZE: usize = 6;
#[inline]
fn parse(data: &[u8]) -> Option<Self> {
let mut s = Stream::new(data);
Some(KerningRecord {
pair: s.read()?,
value: s.read()?,
})
}
}
#[derive(Clone, Copy, Default)]
pub struct Subtable<'a> {
is_horizontal: bool,
is_variable: bool,
has_cross_stream: bool,
format: u8,
header_size: u8,
data: &'a [u8],
}
impl<'a> Subtable<'a> {
#[inline]
pub fn is_horizontal(&self) -> bool {
self.is_horizontal
}
#[inline]
pub fn is_variable(&self) -> bool {
self.is_variable
}
#[inline]
pub fn has_cross_stream(&self) -> bool {
self.has_cross_stream
}
#[inline]
pub fn has_state_machine(&self) -> bool {
self.format == 1
}
#[inline]
pub fn glyphs_kerning(&self, left: GlyphId, right: GlyphId) -> Option<i16> {
match self.format {
0 => parse_format0(self.data, left, right),
2 => parse_format2(left, right, self.header_size, self.data),
3 => parse_format3(self.data, left, right),
_ => None,
}
}
#[inline]
pub fn state_machine(&self) -> Option<state_machine::Machine> {
if !self.has_state_machine() {
return None;
}
state_machine::Machine::parse(self.data)
}
}
impl core::fmt::Debug for Subtable<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.debug_struct("Subtable")
.field("is_horizontal", &self.is_horizontal())
.field("has_state_machine", &self.has_state_machine())
.field("has_cross_stream", &self.has_cross_stream())
.field("format", &self.format)
.finish()
}
}
#[allow(missing_debug_implementations)]
#[derive(Clone, Copy, Default)]
pub struct Subtables<'a> {
is_aat: bool,
table_index: u32,
number_of_tables: u32,
stream: Stream<'a>,
}
impl<'a> Iterator for Subtables<'a> {
type Item = Subtable<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.table_index == self.number_of_tables {
return None;
}
if self.stream.at_end() {
return None;
}
if self.is_aat {
const HEADER_SIZE: u8 = 8;
let table_len: u32 = self.stream.read()?;
let coverage: AATCoverage = self.stream.read()?;
let format: u8 = self.stream.read()?;
self.stream.skip::<u16>();
if format > 3 {
return None;
}
let data_len = usize::num_from(table_len).checked_sub(usize::from(HEADER_SIZE))?;
Some(Subtable {
is_horizontal: coverage.is_horizontal(),
is_variable: coverage.is_variable(),
has_cross_stream: coverage.has_cross_stream(),
format,
header_size: HEADER_SIZE,
data: self.stream.read_bytes(data_len)?,
})
} else {
const HEADER_SIZE: u8 = 6;
self.stream.skip::<u16>();
let table_len: u16 = self.stream.read()?;
let format: u8 = self.stream.read()?;
let coverage: OTCoverage = self.stream.read()?;
if format != 0 && format != 2 {
return None;
}
let data_len = if self.number_of_tables == 1 {
self.stream.tail()?.len()
} else {
usize::from(table_len).checked_sub(usize::from(HEADER_SIZE))?
};
Some(Subtable {
is_horizontal: coverage.is_horizontal(),
is_variable: false,
has_cross_stream: coverage.has_cross_stream(),
format,
header_size: HEADER_SIZE,
data: self.stream.read_bytes(data_len)?,
})
}
}
}
pub(crate) fn parse(data: &[u8]) -> Option<Subtables> {
let mut s = Stream::new(data);
let version: u16 = s.read()?;
if version == 0 {
let number_of_tables: u16 = s.read()?;
Some(Subtables {
is_aat: false,
table_index: 0,
number_of_tables: u32::from(number_of_tables),
stream: s,
})
} else {
s.skip::<u16>();
let number_of_tables: u32 = s.read()?;
Some(Subtables {
is_aat: true,
table_index: 0,
number_of_tables: u32::from(number_of_tables),
stream: s,
})
}
}
fn parse_format0(data: &[u8], left: GlyphId, right: GlyphId) -> Option<i16> {
let mut s = Stream::new(data);
let number_of_pairs: u16 = s.read()?;
s.advance(6);
let pairs = s.read_array16::<KerningRecord>(number_of_pairs)?;
let needle = u32::from(left.0) << 16 | u32::from(right.0);
pairs.binary_search_by(|v| v.pair.cmp(&needle)).map(|(_, v)| v.value)
}
fn parse_format2(left: GlyphId, right: GlyphId, header_len: u8, data: &[u8]) -> Option<i16> {
let mut s = Stream::new(data);
s.skip::<u16>();
let header_len = usize::from(header_len);
let left_hand_table_offset = s.read::<Offset16>()?.to_usize().checked_sub(header_len)?;
let right_hand_table_offset = s.read::<Offset16>()?.to_usize().checked_sub(header_len)?;
let array_offset = s.read::<Offset16>()?.to_usize().checked_sub(header_len)?;
let left_class = get_format2_class(left.0, left_hand_table_offset, data).unwrap_or(0);
let right_class = get_format2_class(right.0, right_hand_table_offset, data).unwrap_or(0);
if usize::from(left_class) < array_offset {
return None;
}
let index = usize::from(left_class) + usize::from(right_class);
let value_offset = index.checked_sub(header_len)?;
Stream::read_at(data, value_offset)
}
fn get_format2_class(glyph_id: u16, offset: usize, data: &[u8]) -> Option<u16> {
let mut s = Stream::new_at(data, offset)?;
let first_glyph: u16 = s.read()?;
let index = glyph_id.checked_sub(first_glyph)?;
let number_of_classes: u16 = s.read()?;
let classes = s.read_array16::<u16>(number_of_classes)?;
classes.get(index)
}
fn parse_format3(data: &[u8], left: GlyphId, right: GlyphId) -> Option<i16> {
let mut s = Stream::new(data);
let glyph_count: u16 = s.read()?;
let kerning_values_count: u8 = s.read()?;
let left_hand_classes_count: u8 = s.read()?;
let right_hand_classes_count: u8 = s.read()?;
s.skip::<u8>();
let indices_count = u16::from(left_hand_classes_count) * u16::from(right_hand_classes_count);
let kerning_values = s.read_array16::<i16>(u16::from(kerning_values_count))?;
let left_hand_classes = s.read_array16::<u8>(glyph_count)?;
let right_hand_classes = s.read_array16::<u8>(glyph_count)?;
let indices = s.read_array16::<u8>(indices_count)?;
let left_class = left_hand_classes.get(left.0)?;
let right_class = right_hand_classes.get(right.0)?;
if left_class > left_hand_classes_count || right_class > right_hand_classes_count {
return None;
}
let index = u16::from(left_class) * u16::from(right_hand_classes_count) + u16::from(right_class);
let index = indices.get(index)?;
kerning_values.get(u16::from(index))
}
pub mod state_machine {
use super::*;
pub mod class {
#![allow(missing_docs)]
pub const END_OF_TEXT: u8 = 0;
pub const OUT_OF_BOUNDS: u8 = 1;
pub const DELETED_GLYPH: u8 = 2;
pub const END_OF_LINE: u8 = 3;
pub const LETTER: u8 = 4;
pub const SPACE: u8 = 5;
pub const PUNCTUATION: u8 = 6;
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub struct State(u16);
pub mod state {
#![allow(missing_docs)]
use super::State;
pub const START_OF_TEXT: State = State(0);
pub const START_OF_LINE: State = State(1);
pub const IN_WORD: State = State(2);
}
#[derive(Clone, Copy, Debug)]
pub struct ValueOffset(u16);
impl ValueOffset {
#[inline]
pub fn next(self) -> Self {
ValueOffset(self.0.wrapping_add(u16::SIZE as u16))
}
}
#[derive(Clone, Copy, Debug)]
pub struct Entry {
new_state: u16,
flags: u16,
}
impl Entry {
#[inline]
pub fn new_state(&self) -> State {
State(self.new_state)
}
#[inline]
pub fn has_offset(&self) -> bool {
self.flags & 0x3FFF != 0
}
#[inline]
pub fn value_offset(&self) -> ValueOffset {
ValueOffset(self.flags & 0x3FFF)
}
#[inline]
pub fn has_push(&self) -> bool {
self.flags & 0x8000 != 0
}
#[inline]
pub fn has_advance(&self) -> bool {
self.flags & 0x4000 == 0
}
}
impl FromData for Entry {
const SIZE: usize = 4;
#[inline]
fn parse(data: &[u8]) -> Option<Self> {
let mut s = Stream::new(data);
Some(Entry {
new_state: s.read()?,
flags: s.read()?,
})
}
}
pub struct Machine<'a> {
number_of_classes: u16,
first_glyph: GlyphId,
class_table: &'a [u8],
state_array_offset: u16,
state_array: &'a [u8],
entry_table: &'a [u8],
actions: &'a [u8],
}
impl<'a> Machine<'a> {
pub(crate) fn parse(data: &'a [u8]) -> Option<Self> {
let mut s = Stream::new(data);
let number_of_classes: u16 = s.read::<u16>()?;
let class_table_offset = s.read::<Offset16>()?.to_usize();
let state_array_offset = s.read::<Offset16>()?.to_usize();
let entry_table_offset = s.read::<Offset16>()?.to_usize();
let mut s = Stream::new_at(data, class_table_offset)?;
let first_glyph: GlyphId = s.read()?;
let number_of_glyphs: u16 = s.read()?;
let class_table = s.read_bytes(usize::from(number_of_glyphs))?;
Some(Machine {
number_of_classes,
first_glyph,
class_table,
state_array_offset: state_array_offset as u16,
state_array: data.get(state_array_offset..)?,
entry_table: data.get(entry_table_offset..)?,
actions: data,
})
}
#[inline]
pub fn class(&self, glyph_id: GlyphId) -> Option<u8> {
if glyph_id.0 == 0xFFFF {
return Some(class::DELETED_GLYPH);
}
let idx = glyph_id.0.checked_sub(self.first_glyph.0)?;
self.class_table.get(usize::from(idx)).copied()
}
#[inline]
pub fn entry(&self, state: State, mut class: u8) -> Option<Entry> {
if u16::from(class) >= self.number_of_classes {
class = class::OUT_OF_BOUNDS;
}
let entry_idx = self.state_array.get(
usize::from(state.0) * usize::from(self.number_of_classes) + usize::from(class)
)?;
Stream::read_at(self.entry_table, usize::from(*entry_idx) * Entry::SIZE)
}
#[inline]
pub fn kerning(&self, offset: ValueOffset) -> Option<i16> {
Stream::read_at(self.actions, usize::from(offset.0))
}
#[inline]
pub fn new_state(&self, state: State) -> State {
let n = (i32::from(state.0) - i32::from(self.state_array_offset))
/ i32::from(self.number_of_classes);
use core::convert::TryFrom;
State(u16::try_from(n).unwrap_or(0))
}
}
impl core::fmt::Debug for Machine<'_> {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.write_str("Machine(...)")
}
}
}