read_fonts/tables/layout/
closure.rs

1//! Support Layout Closure
2
3use types::{BigEndian, GlyphId16};
4
5use super::{
6    ArrayOfOffsets, ChainedClassSequenceRule, ChainedClassSequenceRuleSet, ChainedSequenceContext,
7    ChainedSequenceContextFormat1, ChainedSequenceContextFormat2, ChainedSequenceContextFormat3,
8    ChainedSequenceRule, ChainedSequenceRuleSet, ClassDef, ClassDefFormat1, ClassDefFormat2,
9    ClassSequenceRule, ClassSequenceRuleSet, CoverageTable, ExtensionLookup, Feature, FeatureList,
10    FeatureVariations, FontRead, GlyphId, LangSys, ReadError, Script, ScriptList, SequenceContext,
11    SequenceContextFormat1, SequenceContextFormat2, SequenceContextFormat3, SequenceLookupRecord,
12    SequenceRule, SequenceRuleSet, Subtables, Tag,
13};
14use crate::{
15    collections::IntSet,
16    tables::{gpos::PositionLookupList, gsub::SubstitutionLookupList},
17};
18
19const MAX_SCRIPTS: u16 = 500;
20const MAX_LANGSYS: u16 = 2000;
21const MAX_FEATURE_INDICES: u16 = 1500;
22pub(crate) const MAX_NESTING_LEVEL: u8 = 64;
23pub(crate) const MAX_LOOKUP_VISIT_COUNT: u16 = 35000;
24
25struct CollectFeaturesContext<'a> {
26    script_count: u16,
27    langsys_count: u16,
28    feature_index_count: u16,
29    visited_script: IntSet<u32>,
30    visited_langsys: IntSet<u32>,
31    feature_indices: &'a mut IntSet<u16>,
32    feature_indices_filter: IntSet<u16>,
33    table_head: usize,
34}
35
36impl<'a> CollectFeaturesContext<'a> {
37    pub(crate) fn new(
38        features: &IntSet<Tag>,
39        table_head: usize,
40        feature_list: &'a FeatureList<'a>,
41        feature_indices: &'a mut IntSet<u16>,
42    ) -> Self {
43        Self {
44            script_count: 0,
45            langsys_count: 0,
46            feature_index_count: 0,
47            visited_script: IntSet::empty(),
48            visited_langsys: IntSet::empty(),
49            feature_indices,
50            feature_indices_filter: feature_list
51                .feature_records()
52                .iter()
53                .enumerate()
54                .filter(|(_i, record)| features.contains(record.feature_tag()))
55                .map(|(idx, _)| idx as u16)
56                .collect(),
57            table_head,
58        }
59    }
60
61    /// Return true if the script limit has been exceeded or the script is visited before
62    pub(crate) fn script_visited(&mut self, s: &Script) -> bool {
63        if self.script_count > MAX_SCRIPTS {
64            return true;
65        }
66
67        self.script_count += 1;
68
69        let delta = (s.offset_data().as_bytes().as_ptr() as usize - self.table_head) as u32;
70        !self.visited_script.insert(delta)
71    }
72
73    /// Return true if the Langsys limit has been exceeded or the Langsys is visited before
74    pub(crate) fn langsys_visited(&mut self, langsys: &LangSys) -> bool {
75        if self.langsys_count > MAX_LANGSYS {
76            return true;
77        }
78
79        self.langsys_count += 1;
80
81        let delta = (langsys.offset_data().as_bytes().as_ptr() as usize - self.table_head) as u32;
82        !self.visited_langsys.insert(delta)
83    }
84
85    /// Returns true if the feature limit has been exceeded
86    pub(crate) fn feature_indices_limit_exceeded(&mut self, count: u16) -> bool {
87        let (new_count, overflow) = self.feature_index_count.overflowing_add(count);
88        if overflow {
89            self.feature_index_count = MAX_FEATURE_INDICES;
90            return true;
91        }
92        self.feature_index_count = new_count;
93        new_count > MAX_FEATURE_INDICES
94    }
95}
96
97impl ScriptList<'_> {
98    /// Return a set of all feature indices underneath the specified scripts, languages and features
99    pub(crate) fn collect_features(
100        &self,
101        layout_table_head: usize,
102        feature_list: &FeatureList,
103        scripts: &IntSet<Tag>,
104        languages: &IntSet<Tag>,
105        features: &IntSet<Tag>,
106    ) -> Result<IntSet<u16>, ReadError> {
107        let mut out = IntSet::empty();
108        let mut c =
109            CollectFeaturesContext::new(features, layout_table_head, feature_list, &mut out);
110        let script_records = self.script_records();
111        let font_data = self.offset_data();
112        if scripts.is_inverted() {
113            for record in script_records {
114                let tag = record.script_tag();
115                if !scripts.contains(tag) {
116                    continue;
117                }
118                let script = record.script(font_data)?;
119                script.collect_features(&mut c, languages)?;
120            }
121        } else {
122            for idx in scripts.iter().filter_map(|tag| self.index_for_tag(tag)) {
123                let script = script_records[idx as usize].script(font_data)?;
124                script.collect_features(&mut c, languages)?;
125            }
126        }
127        Ok(out)
128    }
129}
130
131impl Script<'_> {
132    fn collect_features(
133        &self,
134        c: &mut CollectFeaturesContext,
135        languages: &IntSet<Tag>,
136    ) -> Result<(), ReadError> {
137        if c.script_visited(self) {
138            return Ok(());
139        }
140
141        let lang_sys_records = self.lang_sys_records();
142        let font_data = self.offset_data();
143
144        if let Some(default_lang_sys) = self.default_lang_sys().transpose()? {
145            default_lang_sys.collect_features(c);
146        }
147
148        if languages.is_inverted() {
149            for record in lang_sys_records {
150                let tag = record.lang_sys_tag();
151                if !languages.contains(tag) {
152                    continue;
153                }
154                let lang_sys = record.lang_sys(font_data)?;
155                lang_sys.collect_features(c);
156            }
157        } else {
158            for idx in languages
159                .iter()
160                .filter_map(|tag| self.lang_sys_index_for_tag(tag))
161            {
162                let lang_sys = lang_sys_records[idx as usize].lang_sys(font_data)?;
163                lang_sys.collect_features(c);
164            }
165        }
166        Ok(())
167    }
168}
169
170impl LangSys<'_> {
171    fn collect_features(&self, c: &mut CollectFeaturesContext) {
172        if c.langsys_visited(self) {
173            return;
174        }
175
176        if c.feature_indices_filter.is_empty() {
177            return;
178        }
179
180        let required_feature_idx = self.required_feature_index();
181        if required_feature_idx != 0xFFFF
182            && !c.feature_indices_limit_exceeded(1)
183            && c.feature_indices_filter.contains(required_feature_idx)
184        {
185            c.feature_indices.insert(required_feature_idx);
186        }
187
188        if c.feature_indices_limit_exceeded(self.feature_index_count()) {
189            return;
190        }
191
192        for feature_index in self.feature_indices() {
193            let idx = feature_index.get();
194            if !c.feature_indices_filter.contains(idx) {
195                continue;
196            }
197            c.feature_indices.insert(idx);
198            c.feature_indices_filter.remove(idx);
199        }
200    }
201}
202
203impl Feature<'_> {
204    pub(crate) fn collect_lookups(&self) -> Vec<u16> {
205        self.lookup_list_indices()
206            .iter()
207            .map(|idx| idx.get())
208            .collect()
209    }
210}
211
212impl FeatureList<'_> {
213    pub(crate) fn collect_lookups(
214        &self,
215        feature_indices: &IntSet<u16>,
216    ) -> Result<IntSet<u16>, ReadError> {
217        let features_records = self.feature_records();
218        let num_features = self.feature_count();
219        let font_data = self.offset_data();
220        let mut lookup_idxes = IntSet::empty();
221
222        if feature_indices.is_inverted() {
223            for feature_rec in (0..num_features).filter_map(|i| {
224                feature_indices
225                    .contains(i)
226                    .then(|| features_records.get(i as usize))
227                    .flatten()
228            }) {
229                lookup_idxes.extend_unsorted(feature_rec.feature(font_data)?.collect_lookups());
230            }
231        } else {
232            for feature_rec in feature_indices
233                .iter()
234                .filter_map(|i| features_records.get(i as usize))
235            {
236                lookup_idxes.extend_unsorted(feature_rec.feature(font_data)?.collect_lookups());
237            }
238        }
239        Ok(lookup_idxes)
240    }
241}
242
243impl FeatureVariations<'_> {
244    pub(crate) fn collect_lookups(
245        &self,
246        feature_indices: &IntSet<u16>,
247    ) -> Result<IntSet<u16>, ReadError> {
248        let mut out = IntSet::empty();
249
250        for variation_rec in self.feature_variation_records() {
251            let Some(subs) = variation_rec
252                .feature_table_substitution(self.offset_data())
253                .transpose()?
254            else {
255                continue;
256            };
257
258            for sub_record in subs
259                .substitutions()
260                .iter()
261                .filter(|sub_rec| feature_indices.contains(sub_rec.feature_index()))
262            {
263                let sub_f = sub_record.alternate_feature(subs.offset_data())?;
264                out.extend_unsorted(sub_f.lookup_list_indices().iter().map(|i| i.get()));
265            }
266        }
267        Ok(out)
268    }
269}
270
271pub(crate) enum LayoutLookupList<'a> {
272    Gsub(&'a SubstitutionLookupList<'a>),
273    Gpos(&'a PositionLookupList<'a>),
274}
275
276pub(crate) struct LookupClosureCtx<'a> {
277    visited_lookups: IntSet<u16>,
278    inactive_lookups: IntSet<u16>,
279    glyph_set: &'a IntSet<GlyphId>,
280    lookup_count: u16,
281    nesting_level_left: u8,
282    lookup_list: &'a LayoutLookupList<'a>,
283}
284
285impl<'a> LookupClosureCtx<'a> {
286    pub(crate) fn new(glyph_set: &'a IntSet<GlyphId>, lookup_list: &'a LayoutLookupList) -> Self {
287        Self {
288            visited_lookups: IntSet::empty(),
289            inactive_lookups: IntSet::empty(),
290            glyph_set,
291            lookup_count: 0,
292            nesting_level_left: MAX_NESTING_LEVEL,
293            lookup_list,
294        }
295    }
296
297    pub(crate) fn visited_lookups(&self) -> &IntSet<u16> {
298        &self.visited_lookups
299    }
300
301    pub(crate) fn inactive_lookups(&self) -> &IntSet<u16> {
302        &self.inactive_lookups
303    }
304
305    pub(crate) fn glyphs(&self) -> &IntSet<GlyphId> {
306        self.glyph_set
307    }
308
309    pub(crate) fn set_lookup_inactive(&mut self, lookup_index: u16) {
310        self.inactive_lookups.insert(lookup_index);
311    }
312
313    pub(crate) fn lookup_limit_exceed(&self) -> bool {
314        self.lookup_count > MAX_LOOKUP_VISIT_COUNT
315    }
316
317    // return false if lookup limit exceeded or lookup visited,and visited set is not modified
318    // Otherwise return true and insert lookup index into the visited set
319    pub(crate) fn should_visit_lookup(&mut self, lookup_index: u16) -> bool {
320        if self.lookup_count > MAX_LOOKUP_VISIT_COUNT {
321            return false;
322        }
323        self.lookup_count += 1;
324        self.visited_lookups.insert(lookup_index)
325    }
326
327    pub(crate) fn recurse(&mut self, lookup_index: u16) -> Result<(), ReadError> {
328        if self.nesting_level_left == 0 {
329            return Ok(());
330        }
331
332        if self.lookup_limit_exceed() || self.visited_lookups.contains(lookup_index) {
333            return Ok(());
334        }
335
336        self.nesting_level_left -= 1;
337        match self.lookup_list {
338            LayoutLookupList::Gpos(lookuplist) => {
339                lookuplist
340                    .lookups()
341                    .get(lookup_index as usize)?
342                    .closure_lookups(self, lookup_index)?;
343            }
344            LayoutLookupList::Gsub(lookuplist) => {
345                lookuplist
346                    .lookups()
347                    .get(lookup_index as usize)?
348                    .closure_lookups(self, lookup_index)?;
349            }
350        }
351        self.nesting_level_left += 1;
352        Ok(())
353    }
354}
355
356/// Compute the transitive closure of lookups
357pub(crate) trait LookupClosure {
358    fn closure_lookups(&self, _c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
359        Ok(())
360    }
361}
362
363pub trait Intersect {
364    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError>;
365}
366
367impl Intersect for ClassDef<'_> {
368    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
369        match self {
370            ClassDef::Format1(table) => table.intersects(glyph_set),
371            ClassDef::Format2(table) => table.intersects(glyph_set),
372        }
373    }
374}
375
376impl Intersect for ClassDefFormat1<'_> {
377    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
378        let glyph_count = self.glyph_count();
379        if glyph_count == 0 {
380            return Ok(false);
381        }
382
383        let start = self.start_glyph_id().to_u32();
384        let end = start + glyph_count as u32;
385
386        let mut start_glyph = GlyphId::from(start);
387        let class_values = self.class_value_array();
388        if glyph_set.contains(start_glyph) && class_values[0] != 0 {
389            return Ok(true);
390        }
391
392        while let Some(g) = glyph_set.iter_after(start_glyph).next() {
393            let g = g.to_u32();
394            if g >= end {
395                break;
396            }
397            let Some(class) = class_values.get((g - start) as usize) else {
398                break;
399            };
400            if class.get() != 0 {
401                return Ok(true);
402            }
403            start_glyph = GlyphId::from(g);
404        }
405        Ok(false)
406    }
407}
408
409impl Intersect for ClassDefFormat2<'_> {
410    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
411        let num_ranges = self.class_range_count();
412        let num_bits = 16 - num_ranges.leading_zeros();
413        if num_ranges as u64 > glyph_set.len() * num_bits as u64 {
414            for g in glyph_set.iter().map(|g| GlyphId16::from(g.to_u32() as u16)) {
415                if self.get(g) != 0 {
416                    return Ok(true);
417                }
418            }
419        } else {
420            for record in self.class_range_records() {
421                let first = GlyphId::from(record.start_glyph_id());
422                let last = GlyphId::from(record.end_glyph_id());
423                if glyph_set.intersects_range(first..=last) && record.class() != 0 {
424                    return Ok(true);
425                }
426            }
427        }
428        Ok(false)
429    }
430}
431
432impl<'a, T, Ext> LookupClosure for Subtables<'a, T, Ext>
433where
434    T: LookupClosure + Intersect + FontRead<'a> + 'a,
435    Ext: ExtensionLookup<'a, T> + 'a,
436{
437    fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
438        for sub in self.iter() {
439            sub?.closure_lookups(c, arg)?;
440        }
441        Ok(())
442    }
443}
444
445impl<'a, T, Ext> Intersect for Subtables<'a, T, Ext>
446where
447    T: Intersect + FontRead<'a> + 'a,
448    Ext: ExtensionLookup<'a, T> + 'a,
449{
450    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
451        for sub in self.iter() {
452            if sub?.intersects(glyph_set)? {
453                return Ok(true);
454            }
455        }
456        Ok(false)
457    }
458}
459
460// these are basically the same; but we need to jump through some hoops
461// to get the fields to line up
462pub(crate) enum ContextFormat1<'a> {
463    Plain(SequenceContextFormat1<'a>),
464    Chain(ChainedSequenceContextFormat1<'a>),
465}
466
467pub(crate) enum Format1RuleSet<'a> {
468    Plain(SequenceRuleSet<'a>),
469    Chain(ChainedSequenceRuleSet<'a>),
470}
471
472pub(crate) enum Format1Rule<'a> {
473    Plain(SequenceRule<'a>),
474    Chain(ChainedSequenceRule<'a>),
475}
476
477impl ContextFormat1<'_> {
478    pub(crate) fn coverage(&self) -> Result<CoverageTable<'_>, ReadError> {
479        match self {
480            ContextFormat1::Plain(table) => table.coverage(),
481            ContextFormat1::Chain(table) => table.coverage(),
482        }
483    }
484
485    pub(crate) fn rule_sets(
486        &self,
487    ) -> impl Iterator<Item = Option<Result<Format1RuleSet<'_>, ReadError>>> {
488        let (left, right) = match self {
489            ContextFormat1::Plain(table) => (
490                Some(
491                    table
492                        .seq_rule_sets()
493                        .iter()
494                        .map(|rs| rs.map(|rs| rs.map(Format1RuleSet::Plain))),
495                ),
496                None,
497            ),
498            ContextFormat1::Chain(table) => (
499                None,
500                Some(
501                    table
502                        .chained_seq_rule_sets()
503                        .iter()
504                        .map(|rs| rs.map(|rs| rs.map(Format1RuleSet::Chain))),
505                ),
506            ),
507        };
508        left.into_iter()
509            .flatten()
510            .chain(right.into_iter().flatten())
511    }
512}
513
514impl Format1RuleSet<'_> {
515    pub(crate) fn rules(&self) -> impl Iterator<Item = Result<Format1Rule<'_>, ReadError>> {
516        let (left, right) = match self {
517            Self::Plain(table) => (
518                Some(
519                    table
520                        .seq_rules()
521                        .iter()
522                        .map(|rule| rule.map(Format1Rule::Plain)),
523                ),
524                None,
525            ),
526            Self::Chain(table) => (
527                None,
528                Some(
529                    table
530                        .chained_seq_rules()
531                        .iter()
532                        .map(|rule| rule.map(Format1Rule::Chain)),
533                ),
534            ),
535        };
536        left.into_iter()
537            .flatten()
538            .chain(right.into_iter().flatten())
539    }
540}
541
542impl Format1Rule<'_> {
543    pub(crate) fn input_sequence(&self) -> &[BigEndian<GlyphId16>] {
544        match self {
545            Self::Plain(table) => table.input_sequence(),
546            Self::Chain(table) => table.input_sequence(),
547        }
548    }
549
550    pub(crate) fn lookup_records(&self) -> &[SequenceLookupRecord] {
551        match self {
552            Self::Plain(table) => table.seq_lookup_records(),
553            Self::Chain(table) => table.seq_lookup_records(),
554        }
555    }
556}
557
558impl Intersect for &[BigEndian<GlyphId16>] {
559    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
560        Ok(self
561            .iter()
562            .all(|g| glyph_set.contains(GlyphId::from(g.get()))))
563    }
564}
565
566impl Intersect for Format1Rule<'_> {
567    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
568        match self {
569            Self::Plain(table) => table.input_sequence().intersects(glyph_set),
570            Self::Chain(table) => Ok(table.backtrack_sequence().intersects(glyph_set)?
571                && table.input_sequence().intersects(glyph_set)?
572                && table.lookahead_sequence().intersects(glyph_set)?),
573        }
574    }
575}
576
577impl LookupClosure for Format1Rule<'_> {
578    fn closure_lookups(&self, c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
579        if c.lookup_limit_exceed() || !self.intersects(c.glyphs())? {
580            return Ok(());
581        }
582
583        for lookup_record in self.lookup_records() {
584            let index = lookup_record.lookup_list_index();
585            c.recurse(index)?;
586        }
587        Ok(())
588    }
589}
590
591pub(crate) enum ContextFormat2<'a> {
592    Plain(SequenceContextFormat2<'a>),
593    Chain(ChainedSequenceContextFormat2<'a>),
594}
595
596pub(crate) enum Format2RuleSet<'a> {
597    Plain(ClassSequenceRuleSet<'a>),
598    Chain(ChainedClassSequenceRuleSet<'a>),
599}
600
601pub(crate) enum Format2Rule<'a> {
602    Plain(ClassSequenceRule<'a>),
603    Chain(ChainedClassSequenceRule<'a>),
604}
605
606impl ContextFormat2<'_> {
607    pub(crate) fn coverage(&self) -> Result<CoverageTable<'_>, ReadError> {
608        match self {
609            ContextFormat2::Plain(table) => table.coverage(),
610            ContextFormat2::Chain(table) => table.coverage(),
611        }
612    }
613
614    pub(crate) fn input_class_def(&self) -> Result<ClassDef<'_>, ReadError> {
615        match self {
616            ContextFormat2::Plain(table_ref) => table_ref.class_def(),
617            ContextFormat2::Chain(table_ref) => table_ref.input_class_def(),
618        }
619    }
620
621    pub(crate) fn rule_sets(
622        &self,
623    ) -> impl Iterator<Item = Option<Result<Format2RuleSet<'_>, ReadError>>> {
624        let (left, right) = match self {
625            ContextFormat2::Plain(table) => (
626                Some(
627                    table
628                        .class_seq_rule_sets()
629                        .iter()
630                        .map(|rs| rs.map(|rs| rs.map(Format2RuleSet::Plain))),
631                ),
632                None,
633            ),
634            ContextFormat2::Chain(table) => (
635                None,
636                Some(
637                    table
638                        .chained_class_seq_rule_sets()
639                        .iter()
640                        .map(|rs| rs.map(|rs| rs.map(Format2RuleSet::Chain))),
641                ),
642            ),
643        };
644        left.into_iter()
645            .flatten()
646            .chain(right.into_iter().flatten())
647    }
648}
649
650impl Format2RuleSet<'_> {
651    pub(crate) fn rules(&self) -> impl Iterator<Item = Result<Format2Rule<'_>, ReadError>> {
652        let (left, right) = match self {
653            Format2RuleSet::Plain(table) => (
654                Some(
655                    table
656                        .class_seq_rules()
657                        .iter()
658                        .map(|rule| rule.map(Format2Rule::Plain)),
659                ),
660                None,
661            ),
662            Format2RuleSet::Chain(table) => (
663                None,
664                Some(
665                    table
666                        .chained_class_seq_rules()
667                        .iter()
668                        .map(|rule| rule.map(Format2Rule::Chain)),
669                ),
670            ),
671        };
672        left.into_iter()
673            .flatten()
674            .chain(right.into_iter().flatten())
675    }
676}
677
678impl Format2Rule<'_> {
679    pub(crate) fn input_sequence(&self) -> &[BigEndian<u16>] {
680        match self {
681            Self::Plain(table) => table.input_sequence(),
682            Self::Chain(table) => table.input_sequence(),
683        }
684    }
685
686    pub(crate) fn lookup_records(&self) -> &[SequenceLookupRecord] {
687        match self {
688            Self::Plain(table) => table.seq_lookup_records(),
689            Self::Chain(table) => table.seq_lookup_records(),
690        }
691    }
692
693    pub(crate) fn intersects(
694        &self,
695        input_classes: &IntSet<u16>,
696        backtrack_classes: &IntSet<u16>,
697        lookahead_classes: &IntSet<u16>,
698    ) -> bool {
699        match self {
700            Self::Plain(table) => table.intersects(input_classes),
701            Self::Chain(table) => {
702                table.intersects(input_classes, backtrack_classes, lookahead_classes)
703            }
704        }
705    }
706}
707
708impl ClassSequenceRule<'_> {
709    fn intersects(&self, input_classes: &IntSet<u16>) -> bool {
710        self.input_sequence()
711            .iter()
712            .all(|c| input_classes.contains(c.get()))
713    }
714}
715
716impl ChainedClassSequenceRule<'_> {
717    fn intersects(
718        &self,
719        input_classes: &IntSet<u16>,
720        backtrack_classes: &IntSet<u16>,
721        lookahead_classes: &IntSet<u16>,
722    ) -> bool {
723        self.input_sequence()
724            .iter()
725            .all(|c| input_classes.contains(c.get()))
726            && self
727                .backtrack_sequence()
728                .iter()
729                .all(|c| backtrack_classes.contains(c.get()))
730            && self
731                .lookahead_sequence()
732                .iter()
733                .all(|c| lookahead_classes.contains(c.get()))
734    }
735}
736
737pub(crate) enum ContextFormat3<'a> {
738    Plain(SequenceContextFormat3<'a>),
739    Chain(ChainedSequenceContextFormat3<'a>),
740}
741
742impl ContextFormat3<'_> {
743    pub(crate) fn coverages(&self) -> ArrayOfOffsets<'_, CoverageTable<'_>> {
744        match self {
745            ContextFormat3::Plain(table) => table.coverages(),
746            ContextFormat3::Chain(table) => table.input_coverages(),
747        }
748    }
749
750    pub(crate) fn lookup_records(&self) -> &[SequenceLookupRecord] {
751        match self {
752            ContextFormat3::Plain(table) => table.seq_lookup_records(),
753            ContextFormat3::Chain(table) => table.seq_lookup_records(),
754        }
755    }
756
757    pub(crate) fn matches_glyphs(&self, glyphs: &IntSet<GlyphId>) -> Result<bool, ReadError> {
758        let (backtrack, lookahead) = match self {
759            Self::Plain(_) => (None, None),
760            Self::Chain(table) => (
761                Some(table.backtrack_coverages()),
762                Some(table.lookahead_coverages()),
763            ),
764        };
765
766        for coverage in self
767            .coverages()
768            .iter()
769            .chain(backtrack.into_iter().flat_map(|x| x.iter()))
770            .chain(lookahead.into_iter().flat_map(|x| x.iter()))
771        {
772            if !coverage?.intersects(glyphs) {
773                return Ok(false);
774            }
775        }
776        Ok(true)
777    }
778}
779
780impl Intersect for ContextFormat1<'_> {
781    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
782        let coverage = self.coverage()?;
783        for rule_set in coverage
784            .iter()
785            .zip(self.rule_sets())
786            .filter_map(|(g, rule_set)| rule_set.filter(|_| glyph_set.contains(GlyphId::from(g))))
787        {
788            for rule in rule_set?.rules() {
789                if rule?.intersects(glyph_set)? {
790                    return Ok(true);
791                }
792            }
793        }
794        Ok(false)
795    }
796}
797
798impl LookupClosure for ContextFormat1<'_> {
799    fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
800        let coverage = self.coverage()?;
801        let glyph_set = c.glyphs();
802
803        let intersected_idxes: IntSet<u16> = coverage
804            .iter()
805            .enumerate()
806            .filter(|&(_, g)| glyph_set.contains(GlyphId::from(g)))
807            .map(|(idx, _)| idx as u16)
808            .collect();
809
810        for rule_set in self.rule_sets().enumerate().filter_map(|(idx, rule_set)| {
811            rule_set.filter(|_| intersected_idxes.contains(idx as u16))
812        }) {
813            if c.lookup_limit_exceed() {
814                return Ok(());
815            }
816            for rule in rule_set?.rules() {
817                rule?.closure_lookups(c, arg)?;
818            }
819        }
820
821        Ok(())
822    }
823}
824
825impl Intersect for ContextFormat2<'_> {
826    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
827        let coverage = self.coverage()?;
828        let retained_coverage_glyphs = coverage.intersect_set(glyph_set);
829        if retained_coverage_glyphs.is_empty() {
830            return Ok(false);
831        }
832
833        let input_class_def = self.input_class_def()?;
834        let coverage_glyph_classes = input_class_def.intersect_classes(&retained_coverage_glyphs);
835        let input_glyph_classes = input_class_def.intersect_classes(glyph_set);
836
837        let backtrack_classes = match self {
838            Self::Plain(_) => IntSet::empty(),
839            Self::Chain(table) => table.backtrack_class_def()?.intersect_classes(glyph_set),
840        };
841
842        let lookahead_classes = match self {
843            Self::Plain(_) => IntSet::empty(),
844            Self::Chain(table) => table.lookahead_class_def()?.intersect_classes(glyph_set),
845        };
846
847        for rule_set in self.rule_sets().enumerate().filter_map(|(c, rule_set)| {
848            coverage_glyph_classes
849                .contains(c as u16)
850                .then_some(rule_set)
851                .flatten()
852        }) {
853            for rule in rule_set?.rules() {
854                if rule?.intersects(&input_glyph_classes, &backtrack_classes, &lookahead_classes) {
855                    return Ok(true);
856                }
857            }
858        }
859        Ok(false)
860    }
861}
862
863impl LookupClosure for ContextFormat2<'_> {
864    fn closure_lookups(&self, c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
865        let glyph_set = c.glyphs();
866        let coverage = self.coverage()?;
867        let retained_coverage_glyphs = coverage.intersect_set(glyph_set);
868        if retained_coverage_glyphs.is_empty() {
869            return Ok(());
870        }
871
872        let input_class_def = self.input_class_def()?;
873        let coverage_glyph_classes = input_class_def.intersect_classes(&retained_coverage_glyphs);
874        let input_glyph_classes = input_class_def.intersect_classes(glyph_set);
875
876        let backtrack_classes = match self {
877            Self::Plain(_) => IntSet::empty(),
878            Self::Chain(table) => table.backtrack_class_def()?.intersect_classes(glyph_set),
879        };
880
881        let lookahead_classes = match self {
882            Self::Plain(_) => IntSet::empty(),
883            Self::Chain(table) => table.lookahead_class_def()?.intersect_classes(glyph_set),
884        };
885
886        for rule_set in self.rule_sets().enumerate().filter_map(|(c, rule_set)| {
887            coverage_glyph_classes
888                .contains(c as u16)
889                .then_some(rule_set)
890                .flatten()
891        }) {
892            if c.lookup_limit_exceed() {
893                return Ok(());
894            }
895
896            for rule in rule_set?.rules() {
897                let rule = rule?;
898                if c.lookup_limit_exceed()
899                    || !rule.intersects(
900                        &input_glyph_classes,
901                        &backtrack_classes,
902                        &lookahead_classes,
903                    )
904                {
905                    return Ok(());
906                }
907
908                for lookup_record in rule.lookup_records() {
909                    let index = lookup_record.lookup_list_index();
910                    c.recurse(index)?;
911                }
912            }
913        }
914        Ok(())
915    }
916}
917
918impl Intersect for ContextFormat3<'_> {
919    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
920        self.matches_glyphs(glyph_set)
921    }
922}
923
924impl LookupClosure for ContextFormat3<'_> {
925    fn closure_lookups(&self, c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
926        if !self.intersects(c.glyphs())? {
927            return Ok(());
928        }
929
930        for lookup_record in self.lookup_records() {
931            let index = lookup_record.lookup_list_index();
932            c.recurse(index)?;
933        }
934
935        Ok(())
936    }
937}
938
939impl Intersect for SequenceContext<'_> {
940    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
941        match self {
942            Self::Format1(table) => ContextFormat1::Plain(table.clone()).intersects(glyph_set),
943            Self::Format2(table) => ContextFormat2::Plain(table.clone()).intersects(glyph_set),
944            Self::Format3(table) => ContextFormat3::Plain(table.clone()).intersects(glyph_set),
945        }
946    }
947}
948
949impl LookupClosure for SequenceContext<'_> {
950    fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
951        match self {
952            Self::Format1(table) => ContextFormat1::Plain(table.clone()).closure_lookups(c, arg),
953            Self::Format2(table) => ContextFormat2::Plain(table.clone()).closure_lookups(c, arg),
954            Self::Format3(table) => ContextFormat3::Plain(table.clone()).closure_lookups(c, arg),
955        }
956    }
957}
958
959impl Intersect for ChainedSequenceContext<'_> {
960    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
961        match self {
962            Self::Format1(table) => ContextFormat1::Chain(table.clone()).intersects(glyph_set),
963            Self::Format2(table) => ContextFormat2::Chain(table.clone()).intersects(glyph_set),
964            Self::Format3(table) => ContextFormat3::Chain(table.clone()).intersects(glyph_set),
965        }
966    }
967}
968
969impl LookupClosure for ChainedSequenceContext<'_> {
970    fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
971        match self {
972            Self::Format1(table) => ContextFormat1::Chain(table.clone()).closure_lookups(c, arg),
973            Self::Format2(table) => ContextFormat2::Chain(table.clone()).closure_lookups(c, arg),
974            Self::Format3(table) => ContextFormat3::Chain(table.clone()).closure_lookups(c, arg),
975        }
976    }
977}