From 8e6cfe80349491dad6f728cc6a7062a57a195227 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Mon, 5 Jan 2026 15:30:13 -0500 Subject: [PATCH 1/2] Avoid parsing subtable if coverage check fails This extracts the primary coverage table (along with any suitable caches) into a field on `SubtableInfo` so that we can check coverage before attempting to parse the subtable. The glyph id, coverage index, coverage metadata and external cache are then passed to the subtable apply function in a new `ApplyState` parameter. This avoids some buffer indexing and reparsing the primary coverage table. This attempts to clean up and unify some of the caching code paths along with laying some groundwork for future optimizations (like #260). HR benchmarks seem to show a reasonable gain on non-Latin shaping as a bonus. --- src/hb/ot/contextual.rs | 91 ++++-------------- src/hb/ot/gpos/cursive.rs | 11 +-- src/hb/ot/gpos/mark.rs | 17 ++-- src/hb/ot/gpos/pair.rs | 71 ++------------ src/hb/ot/gpos/single.rs | 11 +-- src/hb/ot/gsub/alternate.rs | 13 ++- src/hb/ot/gsub/ligature.rs | 138 ++++++++++----------------- src/hb/ot/gsub/multiple.rs | 7 +- src/hb/ot/gsub/reverse_chain.rs | 9 +- src/hb/ot/gsub/single.rs | 18 ++-- src/hb/ot/lookup.rs | 164 ++++++++++++++++++++++++++------ src/hb/ot/mod.rs | 50 +--------- src/hb/ot_layout_gsubgpos.rs | 138 ++++++++++++++++----------- 13 files changed, 337 insertions(+), 401 deletions(-) diff --git a/src/hb/ot/contextual.rs b/src/hb/ot/contextual.rs index 8314fbc3..8f2bfbfc 100644 --- a/src/hb/ot/contextual.rs +++ b/src/hb/ot/contextual.rs @@ -1,10 +1,10 @@ -use super::{coverage_binary_cached, coverage_index, covered, glyph_class}; +use super::{coverage_index, covered, glyph_class}; use crate::hb::buffer::GlyphInfo; -use crate::hb::ot::{ClassDefInfo, CoverageInfo}; +use crate::hb::ot::ClassDefInfo; use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; use crate::hb::ot_layout_gsubgpos::{ apply_lookup, match_always, match_backtrack, match_glyph, match_input, match_lookahead, - may_skip_t, skipping_iterator_t, Apply, BinaryCache, ChainContextFormat2Cache, + may_skip_t, skipping_iterator_t, Apply, ApplyState, ChainContextFormat2Cache, ContextFormat2Cache, SubtableExternalCache, SubtableExternalCacheMode, WouldApply, WouldApplyContext, }; @@ -47,9 +47,8 @@ impl WouldApply for SequenceContextFormat1<'_> { } impl Apply for SequenceContextFormat1<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - let index = self.coverage().ok()?.get(glyph)? as usize; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let index = state.first_coverage_index as usize; let set = self.seq_rule_sets().get(index)?.ok()?; apply_context_rules(ctx, &set.seq_rules(), match_glyph) } @@ -85,44 +84,24 @@ impl WouldApply for SequenceContextFormat2<'_> { } impl Apply for SequenceContextFormat2<'_> { - fn apply_with_external_cache( - &self, - ctx: &mut hb_ot_apply_context_t, - external_cache: &SubtableExternalCache, - ) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - let SubtableExternalCache::ContextFormat2Cache(cache) = external_cache else { + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let SubtableExternalCache::ContextFormat2Cache(cache) = state.external_cache else { return None; }; let offset_data = self.offset_data(); - coverage_binary_cached( - |gid| cache.coverage.index(&offset_data, gid), - glyph, - &cache.coverage_cache, - )?; let input_class = |gid| cache.input.class(&offset_data, gid); - let index = input_class(glyph) as usize; + let index = input_class(state.first_glyph) as usize; let set = self.class_seq_rule_sets().get(index)?.ok()?; apply_context_rules(ctx, &set.class_seq_rules(), |info, value| { input_class(info.as_glyph()) == value }) } - fn apply_cached( - &self, - ctx: &mut hb_ot_apply_context_t, - external_cache: &SubtableExternalCache, - ) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - let SubtableExternalCache::ContextFormat2Cache(cache) = external_cache else { + fn apply_cached(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let SubtableExternalCache::ContextFormat2Cache(cache) = state.external_cache else { return None; }; let offset_data = self.offset_data(); - coverage_binary_cached( - |gid| cache.coverage.index(&offset_data, gid), - glyph, - &cache.coverage_cache, - )?; let input_class = |gid| cache.input.class(&offset_data, gid); let index = get_class_cached(&input_class, &mut ctx.buffer.info[ctx.buffer.idx]) as usize; let set = self.class_seq_rule_sets().get(index)?.ok()?; @@ -142,9 +121,6 @@ impl Apply for SequenceContextFormat2<'_> { fn external_cache_create(&self, _mode: SubtableExternalCacheMode) -> SubtableExternalCache { let data = self.offset_data(); SubtableExternalCache::ContextFormat2Cache(ContextFormat2Cache { - coverage_cache: BinaryCache::new(), - coverage: CoverageInfo::new(&data, self.coverage_offset().to_u32() as u16) - .unwrap_or_default(), input: ClassDefInfo::new(&data, self.class_def_offset().to_u32() as u16) .unwrap_or_default(), }) @@ -163,10 +139,8 @@ impl WouldApply for SequenceContextFormat3<'_> { } impl Apply for SequenceContextFormat3<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); + fn apply(&self, ctx: &mut hb_ot_apply_context_t, _state: &ApplyState) -> Option<()> { let input_coverages = self.coverages(); - input_coverages.get(0).ok()?.get(glyph)?; let input = |info: &mut GlyphInfo, index: u16| { input_coverages .get(index as usize + 1) @@ -230,9 +204,8 @@ impl WouldApply for ChainedSequenceContextFormat1<'_> { } impl Apply for ChainedSequenceContextFormat1<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - let index = self.coverage().ok()?.get(glyph)? as usize; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let index = state.first_coverage_index as usize; let set = self.chained_seq_rule_sets().get(index)?.ok()?; apply_chain_context_rules( ctx, @@ -344,22 +317,12 @@ fn match_class_cached2<'a>( } impl Apply for ChainedSequenceContextFormat2<'_> { - fn apply_with_external_cache( - &self, - ctx: &mut hb_ot_apply_context_t, - external_cache: &SubtableExternalCache, - ) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - let SubtableExternalCache::ChainContextFormat2Cache(cache) = external_cache else { + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let SubtableExternalCache::ChainContextFormat2Cache(cache) = state.external_cache else { return None; }; let offset_data = self.offset_data(); - coverage_binary_cached( - |gid| cache.coverage.index(&offset_data, gid), - glyph, - &cache.coverage_cache, - )?; - let index = cache.input.class(&offset_data, glyph) as usize; + let index = cache.input.class(&offset_data, state.first_glyph) as usize; let set = self.chained_class_seq_rule_sets().get(index)?.ok()?; apply_chain_context_rules( ctx, @@ -371,21 +334,11 @@ impl Apply for ChainedSequenceContextFormat2<'_> { ), ) } - fn apply_cached( - &self, - ctx: &mut hb_ot_apply_context_t, - external_cache: &SubtableExternalCache, - ) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - let SubtableExternalCache::ChainContextFormat2Cache(cache) = external_cache else { + fn apply_cached(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let SubtableExternalCache::ChainContextFormat2Cache(cache) = state.external_cache else { return None; }; let offset_data = self.offset_data(); - coverage_binary_cached( - |gid| cache.coverage.index(&offset_data, gid), - glyph, - &cache.coverage_cache, - )?; let input_class = |gid| cache.input.class(&offset_data, gid); let lookahead_class = |gid| cache.lookahead.class(&offset_data, gid); let index = get_class_cached2(&input_class, &mut ctx.buffer.info[ctx.buffer.idx]) as usize; @@ -413,9 +366,6 @@ impl Apply for ChainedSequenceContextFormat2<'_> { fn external_cache_create(&self, _mode: SubtableExternalCacheMode) -> SubtableExternalCache { let data = self.offset_data(); SubtableExternalCache::ChainContextFormat2Cache(ChainContextFormat2Cache { - coverage_cache: BinaryCache::new(), - coverage: CoverageInfo::new(&data, self.coverage_offset().to_u32() as u16) - .unwrap_or_default(), backtrack: ClassDefInfo::new(&data, self.backtrack_class_def_offset().to_u32() as u16) .unwrap_or_default(), input: ClassDefInfo::new(&data, self.input_class_def_offset().to_u32() as u16) @@ -446,11 +396,8 @@ impl WouldApply for ChainedSequenceContextFormat3<'_> { } impl Apply for ChainedSequenceContextFormat3<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - + fn apply(&self, ctx: &mut hb_ot_apply_context_t, _state: &ApplyState) -> Option<()> { let input_coverages = self.input_coverages(); - input_coverages.get(0).ok()?.get(glyph)?; let backtrack_coverages = self.backtrack_coverages(); let lookahead_coverages = self.lookahead_coverages(); diff --git a/src/hb/ot/gpos/cursive.rs b/src/hb/ot/gpos/cursive.rs index e408ce1b..011da4e3 100644 --- a/src/hb/ot/gpos/cursive.rs +++ b/src/hb/ot/gpos/cursive.rs @@ -2,16 +2,13 @@ use crate::hb::buffer::HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT; use crate::hb::ot_layout_common::lookup_flags; use crate::hb::ot_layout_gpos_table::attach_type; use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; -use crate::hb::ot_layout_gsubgpos::{skipping_iterator_t, Apply}; +use crate::hb::ot_layout_gsubgpos::{skipping_iterator_t, Apply, ApplyState}; use crate::{Direction, GlyphPosition}; use read_fonts::tables::gpos::CursivePosFormat1; impl Apply for CursivePosFormat1<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let this = ctx.buffer.cur(0).as_glyph(); - - let coverage = self.coverage().ok()?; - let index_this = coverage.get(this)? as usize; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let index_this = state.first_coverage_index as usize; let records = self.entry_exit_record(); let offset_data = self.offset_data(); let entry_this = records.get(index_this)?.entry_anchor(offset_data)?.ok()?; @@ -28,7 +25,7 @@ impl Apply for CursivePosFormat1<'_> { let i = iter.index(); let prev = iter.buffer.info[i].as_glyph(); - let index_prev = coverage.get(prev)? as usize; + let index_prev = state.coverage.index(&self.offset_data(), prev)? as usize; let Some(exit_prev) = records .get(index_prev) .and_then(|rec| rec.exit_anchor(offset_data).transpose().ok().flatten()) diff --git a/src/hb/ot/gpos/mark.rs b/src/hb/ot/gpos/mark.rs index 7cd531a6..f68563c0 100644 --- a/src/hb/ot/gpos/mark.rs +++ b/src/hb/ot/gpos/mark.rs @@ -2,7 +2,7 @@ use crate::hb::buffer::{hb_buffer_t, HB_BUFFER_SCRATCH_FLAG_HAS_GPOS_ATTACHMENT} use crate::hb::ot_layout_common::lookup_flags; use crate::hb::ot_layout_gpos_table::attach_type; use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; -use crate::hb::ot_layout_gsubgpos::{match_t, skipping_iterator_t, Apply, MatchSource}; +use crate::hb::ot_layout_gsubgpos::{match_t, skipping_iterator_t, Apply, ApplyState, MatchSource}; use read_fonts::tables::gpos::{ AnchorTable, MarkArray, MarkBasePosFormat1, MarkLigPosFormat1, MarkMarkPosFormat1, }; @@ -49,9 +49,8 @@ impl MarkArrayExt for MarkArray<'_> { } impl Apply for MarkBasePosFormat1<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let mark_glyph = ctx.buffer.cur(0).as_glyph(); - let mark_index = self.mark_coverage().ok()?.get(mark_glyph)?; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let mark_index = state.first_coverage_index; let base_coverage = self.base_coverage().ok()?; let last_base_until = ctx.last_base_until; @@ -142,9 +141,8 @@ fn accept(buffer: &hb_buffer_t, idx: usize) -> bool { } impl Apply for MarkMarkPosFormat1<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let mark1_glyph = ctx.buffer.cur(0).as_glyph(); - let mark1_index = self.mark1_coverage().ok()?.get(mark1_glyph)?; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let mark1_index = state.first_coverage_index; let lookup_props = ctx.lookup_props; // Now we search backwards for a suitable mark glyph until a non-mark glyph let mut iter = skipping_iterator_t::new(ctx, false); @@ -205,9 +203,8 @@ impl Apply for MarkMarkPosFormat1<'_> { } impl Apply for MarkLigPosFormat1<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let mark_glyph = ctx.buffer.cur(0).as_glyph(); - let mark_index = self.mark_coverage().ok()?.get(mark_glyph)? as usize; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let mark_index = state.first_coverage_index as usize; // Due to borrowing rules, we have this piece of code before creating the // iterator, unlike in harfbuzz. diff --git a/src/hb/ot/gpos/pair.rs b/src/hb/ot/gpos/pair.rs index 4f5ad18c..b59c27cc 100644 --- a/src/hb/ot/gpos/pair.rs +++ b/src/hb/ot/gpos/pair.rs @@ -1,32 +1,16 @@ -use crate::hb::ot::{coverage_index, coverage_index_cached, ClassDefInfo, CoverageInfo}; +use crate::hb::ot::ClassDefInfo; use crate::hb::ot::{glyph_class, glyph_class_cached}; use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; use crate::hb::ot_layout_gsubgpos::{ - skipping_iterator_t, Apply, PairPosFormat1Cache, PairPosFormat1SmallCache, PairPosFormat2Cache, - PairPosFormat2SmallCache, SubtableExternalCache, SubtableExternalCacheMode, + skipping_iterator_t, Apply, ApplyState, PairPosFormat2Cache, PairPosFormat2SmallCache, + SubtableExternalCache, SubtableExternalCacheMode, }; use alloc::boxed::Box; use read_fonts::tables::gpos::{PairPosFormat1, PairPosFormat2}; impl Apply for PairPosFormat1<'_> { - fn apply_with_external_cache( - &self, - ctx: &mut hb_ot_apply_context_t, - external_cache: &SubtableExternalCache, - ) -> Option<()> { - let first_glyph = ctx.buffer.cur(0).as_glyph(); - - let first_glyph_coverage_index = match external_cache { - SubtableExternalCache::PairPosFormat1Cache(cache) => coverage_index_cached( - |gid| self.coverage().ok()?.get(gid), - first_glyph, - &cache.coverage, - )?, - SubtableExternalCache::PairPosFormat1SmallCache(cache) => { - cache.coverage.index(&self.offset_data(), first_glyph)? - } - _ => coverage_index(self.coverage(), first_glyph)?, - }; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let first_glyph_coverage_index = state.first_coverage_index; let mut iter = skipping_iterator_t::new(ctx, false); iter.reset(iter.buffer.idx); @@ -120,46 +104,11 @@ impl Apply for PairPosFormat1<'_> { } None } - - fn external_cache_create(&self, mode: SubtableExternalCacheMode) -> SubtableExternalCache { - match mode { - SubtableExternalCacheMode::Full => { - SubtableExternalCache::PairPosFormat1Cache(Box::new(PairPosFormat1Cache::new())) - } - SubtableExternalCacheMode::Small => { - if let Some(coverage) = - CoverageInfo::new(&self.offset_data(), self.coverage_offset().to_u32() as u16) - { - SubtableExternalCache::PairPosFormat1SmallCache(PairPosFormat1SmallCache { - coverage, - }) - } else { - SubtableExternalCache::None - } - } - SubtableExternalCacheMode::None => SubtableExternalCache::None, - } - } } impl Apply for PairPosFormat2<'_> { - fn apply_with_external_cache( - &self, - ctx: &mut hb_ot_apply_context_t, - 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)?, - }; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let first_glyph = state.first_glyph; let mut iter = skipping_iterator_t::new(ctx, false); iter.reset(iter.buffer.idx); @@ -204,7 +153,7 @@ impl Apply for PairPosFormat2<'_> { } }; let data = self.offset_data(); - let (class1, class2) = match external_cache { + let (class1, class2) = match state.external_cache { SubtableExternalCache::PairPosFormat2Cache(cache) => ( glyph_class_cached( |gid| glyph_class(self.class_def1(), gid), @@ -256,12 +205,10 @@ impl Apply for PairPosFormat2<'_> { } 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)) { + if let Some((first, second)) = class1.zip(class2) { SubtableExternalCache::PairPosFormat2SmallCache(PairPosFormat2SmallCache { - coverage, first, second, }) diff --git a/src/hb/ot/gpos/single.rs b/src/hb/ot/gpos/single.rs index a5b3ffb3..38daeb62 100644 --- a/src/hb/ot/gpos/single.rs +++ b/src/hb/ot/gpos/single.rs @@ -1,11 +1,9 @@ -use crate::hb::ot_layout_gsubgpos::Apply; use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; +use crate::hb::ot_layout_gsubgpos::{Apply, ApplyState}; use read_fonts::tables::gpos::{SinglePosFormat1, SinglePosFormat2}; impl Apply for SinglePosFormat1<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - self.coverage().ok()?.get(glyph)?; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, _state: &ApplyState) -> Option<()> { let format = self.value_format(); let offset = self.shape().value_record_byte_range().start; super::apply_value(ctx, ctx.buffer.idx, &self.offset_data(), offset, format); @@ -15,9 +13,8 @@ impl Apply for SinglePosFormat1<'_> { } impl Apply for SinglePosFormat2<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - let index = self.coverage().ok()?.get(glyph)? as usize; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let index = state.first_coverage_index as usize; let format = self.value_format(); let offset = self.shape().value_records_byte_range().start + (format.record_byte_len() * index); diff --git a/src/hb/ot/gsub/alternate.rs b/src/hb/ot/gsub/alternate.rs index d093d92a..3827ac3d 100644 --- a/src/hb/ot/gsub/alternate.rs +++ b/src/hb/ot/gsub/alternate.rs @@ -1,9 +1,9 @@ use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; -use crate::hb::ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext}; +use crate::hb::ot_layout_gsubgpos::{Apply, ApplyState, WouldApply, WouldApplyContext}; use read_fonts::tables::gsub::{AlternateSet, AlternateSubstFormat1}; impl Apply for AlternateSet<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { + fn apply(&self, ctx: &mut hb_ot_apply_context_t, _state: &ApplyState) -> Option<()> { let alternates = self.alternate_glyph_ids(); let len = alternates.len() as u16; if len == 0 { @@ -41,10 +41,9 @@ impl WouldApply for AlternateSubstFormat1<'_> { } impl Apply for AlternateSubstFormat1<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - let index = self.coverage().ok()?.get(glyph)?; - let set = self.alternate_sets().get(index as usize).ok()?; - set.apply(ctx) + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let index = state.first_coverage_index as usize; + let set = self.alternate_sets().get(index).ok()?; + set.apply(ctx, state) } } diff --git a/src/hb/ot/gsub/ligature.rs b/src/hb/ot/gsub/ligature.rs index b10c05ce..1236094a 100644 --- a/src/hb/ot/gsub/ligature.rs +++ b/src/hb/ot/gsub/ligature.rs @@ -1,15 +1,13 @@ use crate::hb::buffer::GlyphInfo; -use crate::hb::ot::{coverage_index, coverage_index_cached, CoverageInfo}; use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; use crate::hb::ot_layout_gsubgpos::{ ligate_input, match_always, match_glyph, match_input, may_skip_t, skipping_iterator_t, Apply, - LigatureSubstFormat1Cache, LigatureSubstFormat1SmallCache, SubtableExternalCache, - SubtableExternalCacheMode, WouldApply, WouldApplyContext, + ApplyState, LigatureSubstFormat1Cache, SubtableExternalCache, SubtableExternalCacheMode, + WouldApply, WouldApplyContext, }; use crate::hb::set_digest::hb_set_digest_t; -use alloc::boxed::Box; use read_fonts::tables::gsub::{Ligature, LigatureSet, LigatureSubstFormat1}; -use read_fonts::types::GlyphId; +use read_fonts::types::{BigEndian, GlyphId, GlyphId16}; impl WouldApply for Ligature<'_> { fn would_apply(&self, ctx: &WouldApplyContext) -> bool { @@ -23,44 +21,45 @@ impl WouldApply for Ligature<'_> { } } -impl Apply for Ligature<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - // Special-case to make it in-place and not consider this - // as a "ligated" substitution. - let components = self.component_glyph_ids(); - if components.is_empty() { - ctx.replace_glyph(self.ligature_glyph().into()); - Some(()) - } else { - let f = |info: &mut GlyphInfo, index| { - let value = components.get(index as usize).unwrap().get().to_u16(); - match_glyph(info, value) - }; +fn apply_ligature( + ligature: &Ligature, + ctx: &mut hb_ot_apply_context_t, + components: &[BigEndian], +) -> Option<()> { + // Special-case to make it in-place and not consider this + // as a "ligated" substitution. + if components.is_empty() { + ctx.replace_glyph(ligature.ligature_glyph().into()); + Some(()) + } else { + let f = |info: &mut GlyphInfo, index| { + let value = components.get(index as usize).unwrap().get().to_u16(); + match_glyph(info, value) + }; - let mut match_end = 0; - let mut total_component_count = 0; + let mut match_end = 0; + let mut total_component_count = 0; - if !match_input( - ctx, - components.len() as u16, - f, - &mut match_end, - Some(&mut total_component_count), - ) { - ctx.buffer - .unsafe_to_concat(Some(ctx.buffer.idx), Some(match_end)); - return None; - } - let count = components.len() + 1; - ligate_input( - ctx, - count, - match_end, - total_component_count, - self.ligature_glyph().into(), - ); - Some(()) + if !match_input( + ctx, + components.len() as u16, + f, + &mut match_end, + Some(&mut total_component_count), + ) { + ctx.buffer + .unsafe_to_concat(Some(ctx.buffer.idx), Some(match_end)); + return None; } + let count = components.len() + 1; + ligate_input( + ctx, + count, + match_end, + total_component_count, + ligature.ligature_glyph().into(), + ); + Some(()) } } @@ -91,19 +90,20 @@ impl ApplyLigatureSet for LigatureSet<'_> { if !matched { true } else { - second = iter.buffer.info[iter.index()].glyph_id.into(); + let second_info = &iter.buffer.info[iter.index()]; + second = second_info.glyph_id.into(); unsafe_to = iter.index() + 1; // Can't use the fast path if eg. the next char is a default-ignorable // or other skippable. - iter.may_skip(&iter.buffer.info[iter.index()]) != may_skip_t::SKIP_NO + iter.may_skip(&second_info) != may_skip_t::SKIP_NO } }; if slow_path { // Slow path for lig in ligatures.iter().filter_map(Result::ok) { - if lig.apply(ctx).is_some() { + if apply_ligature(&lig, ctx, lig.component_glyph_ids()).is_some() { return Some(()); } } @@ -116,7 +116,7 @@ impl ApplyLigatureSet for LigatureSet<'_> { for lig in ligatures.iter().filter_map(|lig| lig.ok()) { let components = lig.component_glyph_ids(); if components.is_empty() || components[0].get() == second { - if lig.apply(ctx).is_some() { + if apply_ligature(&lig, ctx, components).is_some() { if unsafe_to_concat { ctx.buffer .unsafe_to_concat(Some(ctx.buffer.idx), Some(unsafe_to)); @@ -147,57 +147,23 @@ impl WouldApply for LigatureSubstFormat1<'_> { } impl Apply for LigatureSubstFormat1<'_> { - fn apply_with_external_cache( - &self, - ctx: &mut hb_ot_apply_context_t, - external_cache: &SubtableExternalCache, - ) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - - let (index, seconds) = match external_cache { - SubtableExternalCache::LigatureSubstFormat1Cache(cache) => ( - coverage_index_cached( - |gid| self.coverage().ok()?.get(gid), - glyph, - &cache.coverage, - )?, - &cache.seconds, - ), - SubtableExternalCache::LigatureSubstFormat1SmallCache(cache) => ( - cache.coverage.index(&self.offset_data(), glyph)?, - &cache.seconds, - ), - _ => ( - coverage_index(self.coverage(), glyph)?, - &hb_set_digest_t::full(), - ), + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let seconds = match state.external_cache { + SubtableExternalCache::LigatureSubstFormat1Cache(cache) => &cache.seconds, + _ => &hb_set_digest_t::full(), }; self.ligature_sets() - .get(index as usize) + .get(state.first_coverage_index as usize) .ok() .and_then(|set| set.apply(ctx, seconds)) } fn external_cache_create(&self, mode: SubtableExternalCacheMode) -> SubtableExternalCache { match mode { - SubtableExternalCacheMode::Full => SubtableExternalCache::LigatureSubstFormat1Cache( - Box::new(LigatureSubstFormat1Cache::new(collect_seconds(self))), - ), - SubtableExternalCacheMode::Small => { - if let Some(coverage) = - CoverageInfo::new(&self.offset_data(), self.coverage_offset().to_u32() as u16) - { - SubtableExternalCache::LigatureSubstFormat1SmallCache( - LigatureSubstFormat1SmallCache { - coverage, - seconds: collect_seconds(self), - }, - ) - } else { - SubtableExternalCache::None - } - } SubtableExternalCacheMode::None => SubtableExternalCache::None, + _ => SubtableExternalCache::LigatureSubstFormat1Cache(LigatureSubstFormat1Cache::new( + collect_seconds(self), + )), } } } diff --git a/src/hb/ot/gsub/multiple.rs b/src/hb/ot/gsub/multiple.rs index 3df37932..4368774e 100644 --- a/src/hb/ot/gsub/multiple.rs +++ b/src/hb/ot/gsub/multiple.rs @@ -1,6 +1,6 @@ use crate::hb::buffer::GlyphPropsFlags; use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; -use crate::hb::ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext}; +use crate::hb::ot_layout_gsubgpos::{Apply, ApplyState, WouldApply, WouldApplyContext}; use read_fonts::tables::gsub::MultipleSubstFormat1; impl WouldApply for MultipleSubstFormat1<'_> { @@ -13,9 +13,8 @@ impl WouldApply for MultipleSubstFormat1<'_> { } impl Apply for MultipleSubstFormat1<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let gid = ctx.buffer.cur(0).as_glyph(); - let index = self.coverage().ok()?.get(gid)? as usize; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let index = state.first_coverage_index as usize; let substs = self.sequences().get(index).ok()?.substitute_glyph_ids(); match substs.len() { // Spec disallows this, but Uniscribe allows it. diff --git a/src/hb/ot/gsub/reverse_chain.rs b/src/hb/ot/gsub/reverse_chain.rs index ed990764..c24bb7f1 100644 --- a/src/hb/ot/gsub/reverse_chain.rs +++ b/src/hb/ot/gsub/reverse_chain.rs @@ -2,7 +2,7 @@ use crate::hb::buffer::GlyphInfo; use crate::hb::ot_layout::MAX_NESTING_LEVEL; use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; use crate::hb::ot_layout_gsubgpos::{ - match_backtrack, match_lookahead, Apply, WouldApply, WouldApplyContext, + match_backtrack, match_lookahead, Apply, ApplyState, WouldApply, WouldApplyContext, }; use read_fonts::tables::gsub::ReverseChainSingleSubstFormat1; @@ -18,15 +18,12 @@ impl WouldApply for ReverseChainSingleSubstFormat1<'_> { } impl Apply for ReverseChainSingleSubstFormat1<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { // No chaining to this type. if ctx.nesting_level_left != MAX_NESTING_LEVEL { return None; } - - let glyph = ctx.buffer.cur(0).as_glyph(); - let coverage = self.coverage().ok()?; - let index = coverage.get(glyph)? as usize; + let index = state.first_coverage_index as usize; let substitutes = self.substitute_glyph_ids(); if index >= substitutes.len() { return None; diff --git a/src/hb/ot/gsub/single.rs b/src/hb/ot/gsub/single.rs index 10b2fa77..e7244866 100644 --- a/src/hb/ot/gsub/single.rs +++ b/src/hb/ot/gsub/single.rs @@ -1,5 +1,5 @@ use crate::hb::ot_layout_gsubgpos::OT::hb_ot_apply_context_t; -use crate::hb::ot_layout_gsubgpos::{Apply, WouldApply, WouldApplyContext}; +use crate::hb::ot_layout_gsubgpos::{Apply, ApplyState, WouldApply, WouldApplyContext}; use read_fonts::tables::gsub::{SingleSubstFormat1, SingleSubstFormat2}; impl WouldApply for SingleSubstFormat1<'_> { @@ -10,10 +10,8 @@ impl WouldApply for SingleSubstFormat1<'_> { } impl Apply for SingleSubstFormat1<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - self.coverage().ok()?.get(glyph)?; - let subst = (glyph.to_u32() as i32 + self.delta_glyph_id() as i32) as u16; + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let subst = (state.first_glyph.to_u32() as i32 + self.delta_glyph_id() as i32) as u16; ctx.replace_glyph(subst.into()); Some(()) } @@ -29,10 +27,12 @@ impl WouldApply for SingleSubstFormat2<'_> { } impl Apply for SingleSubstFormat2<'_> { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - let glyph = ctx.buffer.cur(0).as_glyph(); - let index = self.coverage().ok()?.get(glyph)? as usize; - let subst = self.substitute_glyph_ids().get(index)?.get().to_u16(); + fn apply(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { + let subst = self + .substitute_glyph_ids() + .get(state.first_coverage_index as usize)? + .get() + .to_u16(); ctx.replace_glyph(subst.into()); Some(()) } diff --git a/src/hb/ot/lookup.rs b/src/hb/ot/lookup.rs index 43162b74..a7d7a18d 100644 --- a/src/hb/ot/lookup.rs +++ b/src/hb/ot/lookup.rs @@ -2,8 +2,8 @@ use crate::hb::{ hb_font_t, ot_layout::TableIndex, ot_layout_gsubgpos::{ - Apply, SubtableExternalCache, SubtableExternalCacheMode, WouldApply, WouldApplyContext, - OT::hb_ot_apply_context_t, + Apply, ApplyState, SubtableCoverage, SubtableExternalCache, SubtableExternalCacheMode, + WouldApply, WouldApplyContext, OT::hb_ot_apply_context_t, }, set_digest::hb_set_digest_t, GlyphInfo, @@ -26,6 +26,7 @@ use read_fonts::{ SequenceContextFormat1, SequenceContextFormat2, SequenceContextFormat3, }, }, + types::GlyphId, FontData, FontRead, Offset, ReadError, }; @@ -254,7 +255,10 @@ impl LookupInfo { } let is_cached = use_hot_subtable_cache & (self.subtable_cache_user_idx == Some(subtable_idx)); - if subtable_info.apply(ctx, table_data, is_cached).is_some() { + if subtable_info + .apply(ctx, table_data, is_cached, glyph.into()) + .is_some() + { return Some(()); } } @@ -367,13 +371,13 @@ pub struct SubtableInfo { /// Byte offset to the subtable from the base of the GSUB or GPOS /// table. pub offset: u32, + pub coverage: SubtableCoverage, pub digest: hb_set_digest_t, pub apply_fns: [SubtableApplyFn; 2], pub external_cache: SubtableExternalCache, } -pub type SubtableApplyFn = - fn(&mut hb_ot_apply_context_t, &SubtableExternalCache, &[u8]) -> Option<()>; +pub type SubtableApplyFn = fn(&mut hb_ot_apply_context_t, &ApplyState, &[u8]) -> Option<()>; impl SubtableInfo { #[inline] @@ -382,9 +386,17 @@ impl SubtableInfo { ctx: &mut hb_ot_apply_context_t, table_data: &[u8], is_cached: bool, + gid: GlyphId, ) -> Option<()> { let subtable_data = table_data.get(self.offset as usize..)?; - self.apply_fns[is_cached as usize](ctx, &self.external_cache, subtable_data) + let coverage_index = self.coverage.index(&FontData::new(subtable_data), gid)?; + let state = ApplyState { + first_glyph: gid, + first_coverage_index: coverage_index, + coverage: &self.coverage, + external_cache: &self.external_cache, + }; + self.apply_fns[is_cached as usize](ctx, &state, subtable_data) } } @@ -392,20 +404,20 @@ macro_rules! apply_fns { ($apply:ident, $apply_cached:ident, $ty:ident) => { fn $apply( ctx: &mut hb_ot_apply_context_t, - external_cache: &SubtableExternalCache, + state: &ApplyState, table_data: &[u8], ) -> Option<()> { let t = $ty::read(FontData::new(table_data)).ok()?; - t.apply_with_external_cache(ctx, external_cache) + t.apply(ctx, state) } fn $apply_cached( ctx: &mut hb_ot_apply_context_t, - external_cache: &SubtableExternalCache, + state: &ApplyState, table_data: &[u8], ) -> Option<()> { let t = $ty::read(FontData::new(table_data)).ok()?; - t.apply_cached(ctx, external_cache) + t.apply_cached(ctx, state) } }; } @@ -427,6 +439,7 @@ apply_fns!( ligature_subst1_cached, LigatureSubstFormat1 ); + apply_fns!(single_pos1, single_pos1_cached, SinglePosFormat1); apply_fns!(single_pos2, single_pos2_cached, SinglePosFormat2); apply_fns!(pair_pos1, pair_pos1_cached, PairPosFormat1); @@ -494,72 +507,122 @@ impl SubtableInfo { ) -> Option<(Self, u32)> { let data = table_data.split_off(subtable_offset as usize)?; let maybe_external_cache = |s: &dyn Apply| s.external_cache_create(cache_mode); - let (kind, (external_cache, cache_cost, coverage), apply_fns): ( + let (kind, (external_cache, cache_cost, coverage_offset, coverage), apply_fns): ( SubtableKind, - (SubtableExternalCache, u32, CoverageTable), + (SubtableExternalCache, u32, u32, CoverageTable), [SubtableApplyFn; 2], ) = match (is_subst, lookup_type) { (true, 1) => match SingleSubst::read(data).ok()? { SingleSubst::Format1(s) => ( SubtableKind::SingleSubst1, - (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?), + ( + maybe_external_cache(&s), + s.cache_cost(), + s.coverage_offset().to_u32(), + s.coverage().ok()?, + ), [single_subst1, single_subst1_cached as _], ), SingleSubst::Format2(s) => ( SubtableKind::SingleSubst2, - (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?), + ( + maybe_external_cache(&s), + s.cache_cost(), + s.coverage_offset().to_u32(), + s.coverage().ok()?, + ), [single_subst2, single_subst2_cached as _], ), }, (false, 1) => match SinglePos::read(data).ok()? { SinglePos::Format1(s) => ( SubtableKind::SinglePos1, - (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?), + ( + maybe_external_cache(&s), + s.cache_cost(), + s.coverage_offset().to_u32(), + s.coverage().ok()?, + ), [single_pos1, single_pos1_cached as _], ), SinglePos::Format2(s) => ( SubtableKind::SinglePos2, - (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?), + ( + maybe_external_cache(&s), + s.cache_cost(), + s.coverage_offset().to_u32(), + s.coverage().ok()?, + ), [single_pos2, single_pos2_cached as _], ), }, (true, 2) => ( SubtableKind::MultipleSubst1, MultipleSubstFormat1::read(data).ok().and_then(|t| { - Some((maybe_external_cache(&t), t.cache_cost(), t.coverage().ok()?)) + Some(( + maybe_external_cache(&t), + t.cache_cost(), + t.coverage_offset().to_u32(), + t.coverage().ok()?, + )) })?, [multiple_subst1, multiple_subst1_cached as _], ), (false, 2) => match PairPos::read(data).ok()? { PairPos::Format1(s) => ( SubtableKind::PairPos1, - (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?), + ( + maybe_external_cache(&s), + s.cache_cost(), + s.coverage_offset().to_u32(), + s.coverage().ok()?, + ), [pair_pos1, pair_pos1_cached as _], ), PairPos::Format2(s) => ( SubtableKind::PairPos2, - (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?), + ( + maybe_external_cache(&s), + s.cache_cost(), + s.coverage_offset().to_u32(), + s.coverage().ok()?, + ), [pair_pos2, pair_pos2_cached as _], ), }, (true, 3) => ( SubtableKind::AlternateSubst1, AlternateSubstFormat1::read(data).ok().and_then(|t| { - Some((maybe_external_cache(&t), t.cache_cost(), t.coverage().ok()?)) + Some(( + maybe_external_cache(&t), + t.cache_cost(), + t.coverage_offset().to_u32(), + t.coverage().ok()?, + )) })?, [alternate_subst1, alternate_subst1_cached as _], ), (false, 3) => ( SubtableKind::CursivePos1, CursivePosFormat1::read(data).ok().and_then(|t| { - Some((maybe_external_cache(&t), t.cache_cost(), t.coverage().ok()?)) + Some(( + maybe_external_cache(&t), + t.cache_cost(), + t.coverage_offset().to_u32(), + t.coverage().ok()?, + )) })?, [cursive_pos1, cursive_pos1_cached as _], ), (true, 4) => ( SubtableKind::LigatureSubst1, LigatureSubstFormat1::read(data).ok().and_then(|t| { - Some((maybe_external_cache(&t), t.cache_cost(), t.coverage().ok()?)) + Some(( + maybe_external_cache(&t), + t.cache_cost(), + t.coverage_offset().to_u32(), + t.coverage().ok()?, + )) })?, [ligature_subst1, ligature_subst1_cached as _], ), @@ -569,6 +632,7 @@ impl SubtableInfo { Some(( maybe_external_cache(&t), t.cache_cost(), + t.mark_coverage_offset().to_u32(), t.mark_coverage().ok()?, )) })?, @@ -577,12 +641,22 @@ impl SubtableInfo { (true, 5) | (false, 7) => match SequenceContext::read(data).ok()? { SequenceContext::Format1(s) => ( SubtableKind::ContextFormat1, - (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?), + ( + maybe_external_cache(&s), + s.cache_cost(), + s.coverage_offset().to_u32(), + s.coverage().ok()?, + ), [context1, context1_cached as _], ), SequenceContext::Format2(s) => ( SubtableKind::ContextFormat2, - (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?), + ( + maybe_external_cache(&s), + s.cache_cost(), + s.coverage_offset().to_u32(), + s.coverage().ok()?, + ), [context2, context2_cached as _], ), SequenceContext::Format3(s) => ( @@ -590,6 +664,7 @@ impl SubtableInfo { ( maybe_external_cache(&s), s.cache_cost(), + s.coverage_offsets().first()?.get().to_u32(), s.coverages().get(0).ok()?, ), [context3, context3_cached as _], @@ -601,6 +676,7 @@ impl SubtableInfo { Some(( maybe_external_cache(&t), t.cache_cost(), + t.mark_coverage_offset().to_u32(), t.mark_coverage().ok()?, )) })?, @@ -609,12 +685,22 @@ impl SubtableInfo { (true, 6) | (false, 8) => match ChainedSequenceContext::read(data).ok()? { ChainedSequenceContext::Format1(s) => ( SubtableKind::ChainedContextFormat1, - (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?), + ( + maybe_external_cache(&s), + s.cache_cost(), + s.coverage_offset().to_u32(), + s.coverage().ok()?, + ), [chained_context1, chained_context1_cached as _], ), ChainedSequenceContext::Format2(s) => ( SubtableKind::ChainedContextFormat2, - (maybe_external_cache(&s), s.cache_cost(), s.coverage().ok()?), + ( + maybe_external_cache(&s), + s.cache_cost(), + s.coverage_offset().to_u32(), + s.coverage().ok()?, + ), [chained_context2, chained_context2_cached as _], ), ChainedSequenceContext::Format3(s) => ( @@ -622,6 +708,7 @@ impl SubtableInfo { ( maybe_external_cache(&s), s.cache_cost(), + s.input_coverage_offsets().first()?.get().to_u32(), s.input_coverages().get(0).ok()?, ), [chained_context3, chained_context3_cached as _], @@ -645,6 +732,7 @@ impl SubtableInfo { Some(( maybe_external_cache(&t), t.cache_cost(), + t.mark1_coverage_offset().to_u32(), t.mark1_coverage().ok()?, )) })?, @@ -655,7 +743,12 @@ impl SubtableInfo { ReverseChainSingleSubstFormat1::read(data) .ok() .and_then(|t| { - Some((maybe_external_cache(&t), t.cache_cost(), t.coverage().ok()?)) + Some(( + maybe_external_cache(&t), + t.cache_cost(), + t.coverage_offset().to_u32(), + t.coverage().ok()?, + )) })?, [rev_chain_single_subst1, rev_chain_single_subst1_cached as _], ), @@ -663,11 +756,28 @@ impl SubtableInfo { }; let mut digest = hb_set_digest_t::new(); digest.add_coverage(&coverage); + let coverage = SubtableCoverage::new( + &data, + coverage_offset as u16, + cache_mode, + // The following subtables don't need a coverage index so + // we use a smaller binary cache instead + matches!( + kind, + SubtableKind::SingleSubst1 + | SubtableKind::SinglePos1 + | SubtableKind::PairPos2 + | SubtableKind::ContextFormat2 + | SubtableKind::ChainedContextFormat2 + ), + ) + .unwrap_or_default(); Some(( SubtableInfo { kind, offset: subtable_offset, digest, + coverage, apply_fns, external_cache, }, diff --git a/src/hb/ot/mod.rs b/src/hb/ot/mod.rs index 4f6c2b55..1f06d60c 100644 --- a/src/hb/ot/mod.rs +++ b/src/hb/ot/mod.rs @@ -2,7 +2,7 @@ use super::buffer::GlyphPropsFlags; use super::ot_layout::TableIndex; use super::{common::TagExt, set_digest::hb_set_digest_t}; use crate::hb::hb_tag_t; -use crate::hb::ot_layout_gsubgpos::{BinaryCache, MappingCache}; +use crate::hb::ot_layout_gsubgpos::MappingCache; use crate::hb::tables::TableRanges; use alloc::vec::Vec; use lookup::{LookupCache, LookupInfo}; @@ -508,54 +508,6 @@ fn coverage_index(coverage: Result, gid: GlyphId) -> O coverage.ok().and_then(|coverage| coverage.get(gid)) } -fn coverage_index_cached( - coverage: impl Fn(GlyphId) -> Option, - gid: GlyphId, - cache: &MappingCache, -) -> Option { - if let Some(index) = cache.get(gid.into()) { - if index == MappingCache::MAX_VALUE { - None - } else { - Some(index as u16) - } - } else { - let index = coverage(gid); - if let Some(index) = index { - if (index as u32) < MappingCache::MAX_VALUE { - cache.set(gid.into(), index as u32); - } - Some(index) - } else { - cache.set(gid.into(), MappingCache::MAX_VALUE); - None - } - } -} - -fn coverage_binary_cached( - coverage: impl Fn(GlyphId) -> Option, - gid: GlyphId, - cache: &BinaryCache, -) -> Option { - if let Some(index) = cache.get(gid.into()) { - if index == BinaryCache::MAX_VALUE { - None - } else { - Some(true) - } - } else { - let index = coverage(gid); - if index.is_some() { - cache.set(gid.into(), 0); - Some(true) - } else { - cache.set(gid.into(), BinaryCache::MAX_VALUE); - None - } - } -} - fn covered(coverage: Result, gid: GlyphId) -> bool { coverage_index(coverage, gid).is_some() } diff --git a/src/hb/ot_layout_gsubgpos.rs b/src/hb/ot_layout_gsubgpos.rs index a865ce2d..d7d5eaf0 100644 --- a/src/hb/ot_layout_gsubgpos.rs +++ b/src/hb/ot_layout_gsubgpos.rs @@ -14,6 +14,7 @@ use crate::hb::unicode::GeneralCategory; use alloc::boxed::Box; use read_fonts::tables::layout::SequenceLookupRecord; use read_fonts::types::GlyphId; +use read_fonts::FontData; pub(crate) type MatchPositions = smallvec::SmallVec<[u32; 8]>; @@ -674,43 +675,90 @@ pub(crate) enum SubtableExternalCacheMode { Full, } -pub(crate) struct LigatureSubstFormat1Cache { - pub seconds: hb_set_digest_t, - pub coverage: MappingCache, -} - -impl LigatureSubstFormat1Cache { - pub fn new(seconds: hb_set_digest_t) -> Self { - LigatureSubstFormat1Cache { - coverage: MappingCache::new(), - seconds, - } - } +#[derive(Default)] +pub enum SubtableCoverageCache { + #[default] + None, + Mapping(Box), + Binary(Box), } -pub(crate) struct LigatureSubstFormat1SmallCache { +#[derive(Default)] +pub(crate) struct SubtableCoverage { pub coverage: CoverageInfo, - pub seconds: hb_set_digest_t, + pub cache: SubtableCoverageCache, } -pub(crate) struct PairPosFormat1Cache { - pub coverage: MappingCache, -} +impl SubtableCoverage { + pub fn new( + parent_data: &FontData, + coverage_offset: u16, + mode: SubtableExternalCacheMode, + use_binary: bool, + ) -> Option { + let coverage = CoverageInfo::new(parent_data, coverage_offset)?; + let cache = if mode == SubtableExternalCacheMode::Full { + if use_binary { + SubtableCoverageCache::Binary(Box::new(BinaryCache::new())) + } else { + SubtableCoverageCache::Mapping(Box::new(MappingCache::new())) + } + } else { + SubtableCoverageCache::None + }; + Some(Self { coverage, cache }) + } -impl PairPosFormat1Cache { - pub fn new() -> Self { - PairPosFormat1Cache { - coverage: MappingCache::new(), + pub fn index(&self, parent_data: &FontData, gid: GlyphId) -> Option { + match &self.cache { + SubtableCoverageCache::None => self.coverage.index(parent_data, gid), + SubtableCoverageCache::Binary(cache) => { + if let Some(index) = cache.get(gid.into()) { + if index == BinaryCache::MAX_VALUE { + None + } else { + Some(1) + } + } else if self.coverage.index(parent_data, gid).is_some() { + cache.set(gid.into(), 0); + Some(1) + } else { + cache.set(gid.into(), BinaryCache::MAX_VALUE); + None + } + } + SubtableCoverageCache::Mapping(cache) => { + if let Some(index) = cache.get(gid.into()) { + if index == MappingCache::MAX_VALUE { + None + } else { + Some(index as u16) + } + } else if let Some(index) = self.coverage.index(parent_data, gid) { + if (index as u32) < MappingCache::MAX_VALUE { + cache.set(gid.into(), index as u32); + } + Some(index as u16) + } else { + cache.set(gid.into(), MappingCache::MAX_VALUE); + None + } + } } } } -pub(crate) struct PairPosFormat1SmallCache { - pub coverage: CoverageInfo, +pub(crate) struct LigatureSubstFormat1Cache { + pub seconds: hb_set_digest_t, +} + +impl LigatureSubstFormat1Cache { + pub fn new(seconds: hb_set_digest_t) -> Self { + LigatureSubstFormat1Cache { seconds } + } } pub(crate) struct PairPosFormat2Cache { - pub coverage: MappingCache, pub first: MappingCache, pub second: MappingCache, } @@ -718,7 +766,6 @@ pub(crate) struct PairPosFormat2Cache { impl PairPosFormat2Cache { pub fn new() -> Self { PairPosFormat2Cache { - coverage: MappingCache::new(), first: MappingCache::new(), second: MappingCache::new(), } @@ -726,63 +773,44 @@ impl PairPosFormat2Cache { } pub(crate) struct PairPosFormat2SmallCache { - pub coverage: CoverageInfo, pub first: ClassDefInfo, pub second: ClassDefInfo, } pub(crate) struct ContextFormat2Cache { - pub coverage: CoverageInfo, pub input: ClassDefInfo, - pub coverage_cache: BinaryCache, } pub(crate) struct ChainContextFormat2Cache { - pub coverage: CoverageInfo, pub backtrack: ClassDefInfo, pub input: ClassDefInfo, pub lookahead: ClassDefInfo, - pub coverage_cache: BinaryCache, } pub(crate) enum SubtableExternalCache { None, - LigatureSubstFormat1Cache(Box), - LigatureSubstFormat1SmallCache(LigatureSubstFormat1SmallCache), - PairPosFormat1Cache(Box), - PairPosFormat1SmallCache(PairPosFormat1SmallCache), + LigatureSubstFormat1Cache(LigatureSubstFormat1Cache), PairPosFormat2Cache(Box), PairPosFormat2SmallCache(PairPosFormat2SmallCache), ContextFormat2Cache(ContextFormat2Cache), ChainContextFormat2Cache(ChainContextFormat2Cache), } +pub struct ApplyState<'a> { + pub first_glyph: GlyphId, + pub first_coverage_index: u16, + pub coverage: &'a SubtableCoverage, + pub external_cache: &'a SubtableExternalCache, +} + /// Apply a lookup. pub trait Apply { - fn apply(&self, ctx: &mut hb_ot_apply_context_t) -> Option<()> { - // Default implementation just calls `apply_with_external_cache`. - self.apply_with_external_cache(ctx, &SubtableExternalCache::None) - } - - // The rest are relevant to subtables only - - fn apply_with_external_cache( - &self, - ctx: &mut hb_ot_apply_context_t, - _external_cache: &SubtableExternalCache, - ) -> Option<()> { - // Default implementation just calls `apply`. - self.apply(ctx) - } + fn apply(&self, ctx: &mut hb_ot_apply_context_t, _state: &ApplyState) -> Option<()>; - fn apply_cached( - &self, - ctx: &mut hb_ot_apply_context_t, - external_cache: &SubtableExternalCache, - ) -> Option<()> { + fn apply_cached(&self, ctx: &mut hb_ot_apply_context_t, state: &ApplyState) -> Option<()> { // Default implementation just calls `apply_with_external_cache`. // This is used to apply the lookup with glyph-info caching. - self.apply_with_external_cache(ctx, external_cache) + self.apply(ctx, state) } fn cache_cost(&self) -> u32 { From a1f9afa5d3162e0a68a4c5453088c0f337e0cb52 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Mon, 5 Jan 2026 15:35:17 -0500 Subject: [PATCH 2/2] clippy --- src/hb/ot/gsub/ligature.rs | 2 +- src/hb/ot_layout_gsubgpos.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hb/ot/gsub/ligature.rs b/src/hb/ot/gsub/ligature.rs index 1236094a..ee9611eb 100644 --- a/src/hb/ot/gsub/ligature.rs +++ b/src/hb/ot/gsub/ligature.rs @@ -96,7 +96,7 @@ impl ApplyLigatureSet for LigatureSet<'_> { // Can't use the fast path if eg. the next char is a default-ignorable // or other skippable. - iter.may_skip(&second_info) != may_skip_t::SKIP_NO + iter.may_skip(second_info) != may_skip_t::SKIP_NO } }; diff --git a/src/hb/ot_layout_gsubgpos.rs b/src/hb/ot_layout_gsubgpos.rs index d7d5eaf0..abffbdd6 100644 --- a/src/hb/ot_layout_gsubgpos.rs +++ b/src/hb/ot_layout_gsubgpos.rs @@ -738,7 +738,7 @@ impl SubtableCoverage { if (index as u32) < MappingCache::MAX_VALUE { cache.set(gid.into(), index as u32); } - Some(index as u16) + Some(index) } else { cache.set(gid.into(), MappingCache::MAX_VALUE); None