use crate::ot::apply::ApplyContext;
use crate::ot::apply::{
Apply, PairPosFormat1Cache, PairPosFormat1SmallCache, PairPosFormat2Cache,
PairPosFormat2SmallCache, SkippingIterator, SubtableExternalCache, SubtableExternalCacheMode,
};
use crate::ot::{coverage_index, coverage_index_cached, ClassDefInfo, CoverageInfo};
use crate::ot::{glyph_class, glyph_class_cached};
use crate::set_digest::SetDigest;
use alloc::boxed::Box;
use alloc::vec::Vec;
use read_fonts::tables::gpos::{PairPosFormat1, PairPosFormat2};
fn collect_pair_set_digests(table: &PairPosFormat1) -> Box<[SetDigest]> {
table
.pair_sets()
.iter()
.map(|pair_set| {
let Ok(pair_set) = pair_set else {
return SetDigest::full();
};
let mut digest = SetDigest::new();
for pair_value in pair_set.pair_value_records().iter() {
let Ok(pair_value) = pair_value else {
return SetDigest::full();
};
digest.add(pair_value.second_glyph().to_u32());
}
digest
})
.collect::<Vec<_>>()
.into_boxed_slice()
}
impl Apply for PairPosFormat1<'_> {
fn apply_with_external_cache(
&self,
ctx: &mut ApplyContext,
external_cache: &SubtableExternalCache,
) -> Option<()> {
let first_glyph = ctx.buffer.cur(0).as_glyph();
let (first_glyph_coverage_index, pair_set_digests) = match external_cache {
SubtableExternalCache::PairPosFormat1Cache(cache) => (
coverage_index_cached(
|gid| self.coverage().ok()?.get(gid),
first_glyph,
&cache.coverage,
)?,
Some(&cache.pair_sets),
),
SubtableExternalCache::PairPosFormat1SmallCache(cache) => (
cache.coverage.index(&self.offset_data(), first_glyph)?,
Some(&cache.pair_sets),
),
_ => (coverage_index(self.coverage(), first_glyph)?, None),
};
let mut iter = SkippingIterator::new(ctx, false);
iter.reset(iter.buffer.idx);
let mut unsafe_to = 0;
if !iter.next(Some(&mut unsafe_to)) {
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
return None;
}
let second_glyph_index = iter.index();
let second_glyph = iter.buffer.info[second_glyph_index].as_glyph();
if pair_set_digests
.and_then(|digests| digests.get(first_glyph_coverage_index as usize))
.is_some_and(|digest| !digest.may_have(second_glyph.to_u32()))
{
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
return None;
}
let finish = |ctx: &mut ApplyContext, iter_index: &mut usize, has_record2| {
if has_record2 {
*iter_index += 1;
ctx.buffer
.unsafe_to_break(Some(ctx.buffer.idx), Some(*iter_index + 1));
}
ctx.buffer.idx = *iter_index;
Some(())
};
let success =
|ctx: &mut ApplyContext, iter_index: &mut usize, flag1, flag2, has_record2| {
if flag1 || flag2 {
ctx.buffer
.unsafe_to_break(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
} else {
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
}
finish(ctx, iter_index, has_record2)
};
let mut buf_idx = iter.buf_idx;
let set_offset = self
.pair_set_offsets()
.get(first_glyph_coverage_index as usize)?
.get()
.to_u32() as usize;
let format1 = self.value_format1();
let format1_len = format1.record_byte_len();
let format2 = self.value_format2();
let record_size = format1_len + format2.record_byte_len() + 2;
let data = self.offset_data();
let set_data = data.split_off(set_offset)?;
let pair_count = set_data.read_at::<u16>(0).ok()? as usize;
let mut hi = pair_count;
let mut lo = 0;
while lo < hi {
#[allow(clippy::manual_midpoint)]
let mid = (lo + hi) / 2;
let record_offset = 2 + mid * record_size;
let glyph_id = set_data
.read_at::<read_fonts::types::GlyphId16>(record_offset)
.ok()?;
if glyph_id < second_glyph {
lo = mid + 1;
} else if glyph_id > second_glyph {
hi = mid;
} else {
let has_record2 = !format2.is_empty();
let worked1 = !format1.is_empty()
&& super::value::apply(
ctx,
ctx.buffer.idx,
&set_data,
record_offset + 2,
format1,
) == Some(true);
let worked2 = has_record2
&& super::value::apply(
ctx,
second_glyph_index,
&set_data,
record_offset + format1_len + 2,
format2,
) == Some(true);
return success(ctx, &mut buf_idx, worked1, worked2, has_record2);
}
}
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
None
}
fn external_cache_create(&self, mode: SubtableExternalCacheMode) -> SubtableExternalCache {
match mode {
SubtableExternalCacheMode::Full => SubtableExternalCache::PairPosFormat1Cache(
Box::new(PairPosFormat1Cache::new(collect_pair_set_digests(self))),
),
SubtableExternalCacheMode::Small => {
if let Some(coverage) =
CoverageInfo::new(&self.offset_data(), self.coverage_offset().to_u32() as u16)
{
SubtableExternalCache::PairPosFormat1SmallCache(PairPosFormat1SmallCache {
coverage,
pair_sets: collect_pair_set_digests(self),
})
} else {
SubtableExternalCache::None
}
}
SubtableExternalCacheMode::None => SubtableExternalCache::None,
}
}
}
impl Apply for PairPosFormat2<'_> {
fn apply_with_external_cache(
&self,
ctx: &mut ApplyContext,
external_cache: &SubtableExternalCache,
) -> Option<()> {
let first_glyph = ctx.buffer.cur(0).as_glyph();
match external_cache {
SubtableExternalCache::PairPosFormat2Cache(cache) => coverage_index_cached(
|gid| self.coverage().ok()?.get(gid),
first_glyph,
&cache.coverage,
)?,
SubtableExternalCache::PairPosFormat2SmallCache(cache) => {
cache.coverage.index(&self.offset_data(), first_glyph)?
}
_ => coverage_index(self.coverage(), first_glyph)?,
};
let mut iter = SkippingIterator::new(ctx, false);
iter.reset(iter.buffer.idx);
let mut unsafe_to = 0;
if !iter.next(Some(&mut unsafe_to)) {
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to));
return None;
}
let second_glyph_index = iter.index();
let second_glyph = iter.buffer.info[second_glyph_index].as_glyph();
let finish = |ctx: &mut ApplyContext, iter_index: &mut usize, has_record2| {
if has_record2 {
*iter_index += 1;
ctx.buffer
.unsafe_to_break(Some(ctx.buffer.idx), Some(*iter_index + 1));
}
ctx.buffer.idx = *iter_index;
Some(())
};
let boring = |ctx: &mut ApplyContext, iter_index: &mut usize, has_record2| {
ctx.buffer
.unsafe_to_concat(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
finish(ctx, iter_index, has_record2)
};
let success =
|ctx: &mut ApplyContext, iter_index: &mut usize, flag1, flag2, has_record2| {
if flag1 || flag2 {
ctx.buffer
.unsafe_to_break(Some(ctx.buffer.idx), Some(second_glyph_index + 1));
finish(ctx, iter_index, has_record2)
} else {
boring(ctx, iter_index, has_record2)
}
};
let data = self.offset_data();
let (class1, class2) = match external_cache {
SubtableExternalCache::PairPosFormat2Cache(cache) => (
glyph_class_cached(
|gid| glyph_class(self.class_def1(), gid),
first_glyph,
&cache.first,
),
glyph_class_cached(
|gid| glyph_class(self.class_def2(), gid),
second_glyph,
&cache.second,
),
),
SubtableExternalCache::PairPosFormat2SmallCache(cache) => (
cache.first.class(&data, first_glyph),
cache.second.class(&data, second_glyph),
),
_ => (
glyph_class(self.class_def1(), first_glyph),
glyph_class(self.class_def2(), second_glyph),
),
};
let mut buf_idx = iter.buf_idx;
let format1 = self.value_format1();
let format1_len = format1.record_byte_len();
let format2 = self.value_format2();
let record_size = format1_len + format2.record_byte_len();
let record_offset = (class1 as usize * record_size * self.class2_count() as usize)
+ (class2 as usize * record_size)
+ self.class1_records_byte_range().start;
let has_record2 = !format2.is_empty();
let worked1 = !format1.is_empty()
&& super::value::apply(ctx, ctx.buffer.idx, &data, record_offset, format1)
== Some(true);
let worked2 = has_record2
&& super::value::apply(
ctx,
second_glyph_index,
&data,
record_offset + format1_len,
format2,
) == Some(true);
success(ctx, &mut buf_idx, worked1, worked2, has_record2)
}
fn external_cache_create(&self, mode: SubtableExternalCacheMode) -> SubtableExternalCache {
match mode {
SubtableExternalCacheMode::Full => {
SubtableExternalCache::PairPosFormat2Cache(Box::new(PairPosFormat2Cache::new()))
}
SubtableExternalCacheMode::Small => {
let data = self.offset_data();
let coverage = CoverageInfo::new(&data, self.coverage_offset().to_u32() as u16);
let class1 = ClassDefInfo::new(&data, self.class_def1_offset().to_u32() as u16);
let class2 = ClassDefInfo::new(&data, self.class_def2_offset().to_u32() as u16);
if let Some((coverage, (first, second))) = coverage.zip(class1.zip(class2)) {
SubtableExternalCache::PairPosFormat2SmallCache(PairPosFormat2SmallCache {
coverage,
first,
second,
})
} else {
SubtableExternalCache::None
}
}
SubtableExternalCacheMode::None => SubtableExternalCache::None,
}
}
}