use read_fonts::{
tables::gpos::{PairPos, ValueFormat},
types::GlyphId,
};
#[derive(Default, PartialEq)]
pub(crate) struct PlainKerning {
subtables: Box<[KernSubtable]>,
}
#[derive(Clone, Copy, PartialEq)]
pub(crate) struct KernSubtable {
coverage: u32,
pairs: Pairs,
lookup: u16,
}
#[derive(Clone, Copy, PartialEq)]
enum Pairs {
Glyphs(PairSets),
Classes(ClassMatrix),
}
#[derive(Clone, Copy, PartialEq)]
struct PairSets {
subtable: u32,
offsets: u32,
count: u16,
}
#[derive(Clone, Copy, PartialEq)]
struct ClassMatrix {
first: u32,
second: u32,
counts: [u16; 2],
cells: u32,
}
const PAIR_RECORD: usize = 4;
const RANGE_RECORD: usize = 6;
impl KernSubtable {
pub(crate) fn of(pair: &PairPos<'_>, lookup: u16, font: &[u8]) -> Option<Self> {
let (formats, data, coverage) = match pair {
PairPos::Format1(format) => (
[format.value_format1(), format.value_format2()],
format.offset_data(),
format.coverage_offset(),
),
PairPos::Format2(format) => (
[format.value_format1(), format.value_format2()],
format.offset_data(),
format.coverage_offset(),
),
_ => return None,
};
if formats != [ValueFormat::X_ADVANCE, ValueFormat::empty()] {
return None;
}
let start = data
.as_bytes()
.as_ptr()
.addr()
.checked_sub(font.as_ptr().addr())?;
let at = |offset: usize| u32::try_from(start.checked_add(offset)?).ok();
let pairs = match pair {
PairPos::Format1(format) => Pairs::Glyphs(PairSets {
subtable: at(0)?,
offsets: at(format.pair_set_offsets_byte_range().start)?,
count: format.pair_set_count(),
}),
PairPos::Format2(format) => Pairs::Classes(ClassMatrix {
first: at(index(format.class_def1_offset().to_u32())?)?,
second: at(index(format.class_def2_offset().to_u32())?)?,
counts: [format.class1_count(), format.class2_count()],
cells: at(format.class1_records_byte_range().start)?,
}),
_ => return None,
};
Some(Self {
coverage: at(index(coverage.to_u32())?)?,
pairs,
lookup,
})
}
fn starts(self, font: &[u8], first: u16) -> bool {
index(self.coverage).is_some_and(|at| covered(font, at, first).is_some())
}
fn adjustment(self, font: &[u8], first: u16, second: u16) -> Option<i16> {
let covering = covered(font, index(self.coverage)?, first)?;
let cell = match self.pairs {
Pairs::Glyphs(sets) => sets.cell(font, covering, second)?,
Pairs::Classes(matrix) => matrix.cell(font, first, second)?,
};
Some(word(font, cell)?.cast_signed())
}
}
impl PairSets {
fn cell(self, font: &[u8], covering: u16, second: u16) -> Option<usize> {
if covering >= self.count {
return None;
}
let set = word(font, index(self.offsets)? + 2 * usize::from(covering))?;
let set = index(self.subtable)? + usize::from(set);
let record = last_at_most(font, set + 2, word(font, set)?, PAIR_RECORD, second)?;
(word(font, record)? == second).then_some(record + 2)
}
}
impl ClassMatrix {
fn cell(self, font: &[u8], first: u16, second: u16) -> Option<usize> {
let [count1, count2] = self.counts;
let class1 = class(font, index(self.first)?, first)?;
let class2 = class(font, index(self.second)?, second)?;
if class1 >= count1 || class2 >= count2 {
return None;
}
let cell = usize::from(class1) * usize::from(count2) + usize::from(class2);
index(self.cells)?.checked_add(cell.checked_mul(2)?)
}
}
fn covered(font: &[u8], at: usize, glyph: u16) -> Option<u16> {
let count = word(font, at + 2)?;
match word(font, at)? {
1 => {
let record = last_at_most(font, at + 4, count, 2, glyph)?;
if word(font, record)? != glyph {
return None;
}
u16::try_from((record - at - 4) / 2).ok()
}
2 => {
let record = last_at_most(font, at + 4, count, RANGE_RECORD, glyph)?;
if glyph > word(font, record + 2)? {
return None;
}
word(font, record + 4)?.checked_add(glyph - word(font, record)?)
}
_ => None,
}
}
fn class(font: &[u8], at: usize, glyph: u16) -> Option<u16> {
let ranged = match word(font, at)? {
1 => {
let start = word(font, at + 2)?;
let count = word(font, at + 4)?;
glyph
.checked_sub(start)
.filter(|&index| index < count)
.map(|index| at + 6 + 2 * usize::from(index))
}
2 => last_at_most(font, at + 4, word(font, at + 2)?, RANGE_RECORD, glyph)
.filter(|&record| word(font, record + 2).is_some_and(|last| glyph <= last))
.map(|record| record + 4),
_ => return None,
};
Some(ranged.and_then(|at| word(font, at)).unwrap_or(0))
}
fn last_at_most(font: &[u8], at: usize, count: u16, size: usize, glyph: u16) -> Option<usize> {
let (mut low, mut high) = (0, usize::from(count));
while low < high {
let middle = low + (high - low) / 2;
if word(font, at + middle * size)? <= glyph {
low = middle + 1;
} else {
high = middle;
}
}
Some(at + low.checked_sub(1)? * size)
}
fn word(font: &[u8], at: usize) -> Option<u16> {
let bytes = font.get(at..at.checked_add(2)?)?;
Some(u16::from_be_bytes(bytes.try_into().ok()?))
}
fn index(offset: u32) -> Option<usize> {
usize::try_from(offset).ok()
}
impl PlainKerning {
pub(crate) fn new(subtables: Vec<KernSubtable>) -> Self {
Self {
subtables: subtables.into_boxed_slice(),
}
}
#[inline(never)]
pub(crate) fn starts(&self, font: &[u8], first: GlyphId) -> bool {
u16::try_from(first.to_u32()).is_ok_and(|first| {
self.subtables
.iter()
.any(|subtable| subtable.starts(font, first))
})
}
#[inline(never)]
pub(crate) fn between(&self, font: &[u8], first: GlyphId, second: GlyphId) -> i32 {
let (Ok(first), Ok(second)) = (
u16::try_from(first.to_u32()),
u16::try_from(second.to_u32()),
) else {
return 0;
};
let mut total = 0;
let mut applied = None;
for subtable in &self.subtables {
if applied == Some(subtable.lookup) {
continue;
}
if let Some(advance) = subtable.adjustment(font, first, second) {
total += i32::from(advance);
applied = Some(subtable.lookup);
}
}
total
}
}