Skip to main content

read_fonts/tables/layout/
closure.rs

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