Skip to main content

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) || 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, Ext> Intersect for Subtables<'a, T, Ext>
466where
467    T: Intersect + FontRead<'a> + 'a,
468    Ext: ExtensionLookup<'a, T> + 'a,
469{
470    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
471        for t in self.iter().filter_map(|table| match table {
472            Err(ReadError::NullOffset) => None,
473            other => Some(other),
474        }) {
475            if t?.intersects(glyph_set)? {
476                return Ok(true);
477            }
478        }
479        Ok(false)
480    }
481}
482
483// these are basically the same; but we need to jump through some hoops
484// to get the fields to line up
485pub(crate) enum ContextFormat1<'a> {
486    Plain(SequenceContextFormat1<'a>),
487    Chain(ChainedSequenceContextFormat1<'a>),
488}
489
490pub(crate) enum Format1RuleSet<'a> {
491    Plain(SequenceRuleSet<'a>),
492    Chain(ChainedSequenceRuleSet<'a>),
493}
494
495pub(crate) enum Format1Rule<'a> {
496    Plain(SequenceRule<'a>),
497    Chain(ChainedSequenceRule<'a>),
498}
499
500impl ContextFormat1<'_> {
501    pub(crate) fn coverage(&self) -> Option<Result<CoverageTable<'_>, ReadError>> {
502        match self {
503            ContextFormat1::Plain(table) if !table.coverage_offset().is_null() => {
504                Some(table.coverage())
505            }
506            ContextFormat1::Chain(table) if !table.coverage_offset().is_null() => {
507                Some(table.coverage())
508            }
509            _ => None,
510        }
511    }
512
513    pub(crate) fn rule_sets(
514        &self,
515    ) -> impl Iterator<Item = Option<Result<Format1RuleSet<'_>, ReadError>>> {
516        let (left, right) = match self {
517            ContextFormat1::Plain(table) => (
518                Some(
519                    table
520                        .seq_rule_sets()
521                        .iter()
522                        .map(|rs| rs.map(|rs| rs.map(Format1RuleSet::Plain))),
523                ),
524                None,
525            ),
526            ContextFormat1::Chain(table) => (
527                None,
528                Some(
529                    table
530                        .chained_seq_rule_sets()
531                        .iter()
532                        .map(|rs| rs.map(|rs| rs.map(Format1RuleSet::Chain))),
533                ),
534            ),
535        };
536        left.into_iter()
537            .flatten()
538            .chain(right.into_iter().flatten())
539    }
540}
541
542impl Format1RuleSet<'_> {
543    pub(crate) fn rules(&self) -> impl Iterator<Item = Option<Result<Format1Rule<'_>, ReadError>>> {
544        let (left, right) = match self {
545            Self::Plain(table) => (
546                Some(
547                    table
548                        .seq_rules()
549                        .iter_as_nullable()
550                        .map(|rule| rule.map(|r| r.map(Format1Rule::Plain))),
551                ),
552                None,
553            ),
554            Self::Chain(table) => (
555                None,
556                Some(
557                    table
558                        .chained_seq_rules()
559                        .iter_as_nullable()
560                        .map(|rule| rule.map(|r| r.map(Format1Rule::Chain))),
561                ),
562            ),
563        };
564        left.into_iter()
565            .flatten()
566            .chain(right.into_iter().flatten())
567    }
568}
569
570impl Format1Rule<'_> {
571    pub(crate) fn input_sequence(&self) -> &[BigEndian<GlyphId16>] {
572        match self {
573            Self::Plain(table) => table.input_sequence(),
574            Self::Chain(table) => table.input_sequence(),
575        }
576    }
577
578    pub(crate) fn lookup_records(&self) -> &[SequenceLookupRecord] {
579        match self {
580            Self::Plain(table) => table.seq_lookup_records(),
581            Self::Chain(table) => table.seq_lookup_records(),
582        }
583    }
584}
585
586impl Intersect for &[BigEndian<GlyphId16>] {
587    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
588        Ok(self
589            .iter()
590            .all(|g| glyph_set.contains(GlyphId::from(g.get()))))
591    }
592}
593
594impl Intersect for Format1Rule<'_> {
595    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
596        match self {
597            Self::Plain(table) => table.input_sequence().intersects(glyph_set),
598            Self::Chain(table) => Ok(table.backtrack_sequence().intersects(glyph_set)?
599                && table.input_sequence().intersects(glyph_set)?
600                && table.lookahead_sequence().intersects(glyph_set)?),
601        }
602    }
603}
604
605impl LookupClosure for Format1Rule<'_> {
606    fn closure_lookups(&self, c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
607        if c.lookup_limit_exceed() || !self.intersects(c.glyphs())? {
608            return Ok(());
609        }
610
611        for lookup_record in self.lookup_records() {
612            let index = lookup_record.lookup_list_index();
613            c.recurse(index)?;
614        }
615        Ok(())
616    }
617}
618
619pub(crate) enum ContextFormat2<'a> {
620    Plain(SequenceContextFormat2<'a>),
621    Chain(ChainedSequenceContextFormat2<'a>),
622}
623
624pub(crate) enum Format2RuleSet<'a> {
625    Plain(ClassSequenceRuleSet<'a>),
626    Chain(ChainedClassSequenceRuleSet<'a>),
627}
628
629pub(crate) enum Format2Rule<'a> {
630    Plain(ClassSequenceRule<'a>),
631    Chain(ChainedClassSequenceRule<'a>),
632}
633
634impl ContextFormat2<'_> {
635    pub(crate) fn coverage(&self) -> Option<Result<CoverageTable<'_>, ReadError>> {
636        match self {
637            ContextFormat2::Plain(table) if !table.coverage_offset().is_null() => {
638                Some(table.coverage())
639            }
640            ContextFormat2::Chain(table) if !table.coverage_offset().is_null() => {
641                Some(table.coverage())
642            }
643            _ => None,
644        }
645    }
646
647    pub(crate) fn input_class_def(&self) -> Option<Result<ClassDef<'_>, ReadError>> {
648        match self {
649            ContextFormat2::Plain(table_ref) if !table_ref.class_def_offset().is_null() => {
650                Some(table_ref.class_def())
651            }
652            ContextFormat2::Chain(table_ref) if !table_ref.input_class_def_offset().is_null() => {
653                Some(table_ref.input_class_def())
654            }
655            _ => None,
656        }
657    }
658
659    pub(crate) fn rule_sets(
660        &self,
661    ) -> impl Iterator<Item = Option<Result<Format2RuleSet<'_>, ReadError>>> {
662        let (left, right) = match self {
663            ContextFormat2::Plain(table) => (
664                Some(
665                    table
666                        .class_seq_rule_sets()
667                        .iter()
668                        .map(|rs| rs.map(|rs| rs.map(Format2RuleSet::Plain))),
669                ),
670                None,
671            ),
672            ContextFormat2::Chain(table) => (
673                None,
674                Some(
675                    table
676                        .chained_class_seq_rule_sets()
677                        .iter()
678                        .map(|rs| rs.map(|rs| rs.map(Format2RuleSet::Chain))),
679                ),
680            ),
681        };
682        left.into_iter()
683            .flatten()
684            .chain(right.into_iter().flatten())
685    }
686}
687
688impl Format2RuleSet<'_> {
689    pub(crate) fn rules(&self) -> impl Iterator<Item = Option<Result<Format2Rule<'_>, ReadError>>> {
690        let (left, right) = match self {
691            Format2RuleSet::Plain(table) => (
692                Some(
693                    table
694                        .class_seq_rules()
695                        .iter_as_nullable()
696                        .map(|rule| rule.map(|r| r.map(Format2Rule::Plain))),
697                ),
698                None,
699            ),
700            Format2RuleSet::Chain(table) => (
701                None,
702                Some(
703                    table
704                        .chained_class_seq_rules()
705                        .iter_as_nullable()
706                        .map(|rule| rule.map(|r| r.map(Format2Rule::Chain))),
707                ),
708            ),
709        };
710        left.into_iter()
711            .flatten()
712            .chain(right.into_iter().flatten())
713    }
714}
715
716impl Format2Rule<'_> {
717    pub(crate) fn input_sequence(&self) -> &[BigEndian<u16>] {
718        match self {
719            Self::Plain(table) => table.input_sequence(),
720            Self::Chain(table) => table.input_sequence(),
721        }
722    }
723
724    pub(crate) fn lookup_records(&self) -> &[SequenceLookupRecord] {
725        match self {
726            Self::Plain(table) => table.seq_lookup_records(),
727            Self::Chain(table) => table.seq_lookup_records(),
728        }
729    }
730
731    pub(crate) fn intersects(
732        &self,
733        input_classes: &IntSet<u16>,
734        backtrack_classes: &IntSet<u16>,
735        lookahead_classes: &IntSet<u16>,
736    ) -> bool {
737        match self {
738            Self::Plain(table) => table.intersects(input_classes),
739            Self::Chain(table) => {
740                table.intersects(input_classes, backtrack_classes, lookahead_classes)
741            }
742        }
743    }
744}
745
746impl ClassSequenceRule<'_> {
747    fn intersects(&self, input_classes: &IntSet<u16>) -> bool {
748        self.input_sequence()
749            .iter()
750            .all(|c| input_classes.contains(c.get()))
751    }
752}
753
754impl ChainedClassSequenceRule<'_> {
755    fn intersects(
756        &self,
757        input_classes: &IntSet<u16>,
758        backtrack_classes: &IntSet<u16>,
759        lookahead_classes: &IntSet<u16>,
760    ) -> bool {
761        self.input_sequence()
762            .iter()
763            .all(|c| input_classes.contains(c.get()))
764            && self
765                .backtrack_sequence()
766                .iter()
767                .all(|c| backtrack_classes.contains(c.get()))
768            && self
769                .lookahead_sequence()
770                .iter()
771                .all(|c| lookahead_classes.contains(c.get()))
772    }
773}
774
775pub(crate) enum ContextFormat3<'a> {
776    Plain(SequenceContextFormat3<'a>),
777    Chain(ChainedSequenceContextFormat3<'a>),
778}
779
780impl ContextFormat3<'_> {
781    pub(crate) fn coverages(&self) -> ArrayOfOffsets<'_, CoverageTable<'_>> {
782        match self {
783            ContextFormat3::Plain(table) => table.coverages(),
784            ContextFormat3::Chain(table) => table.input_coverages(),
785        }
786    }
787
788    pub(crate) fn lookup_records(&self) -> &[SequenceLookupRecord] {
789        match self {
790            ContextFormat3::Plain(table) => table.seq_lookup_records(),
791            ContextFormat3::Chain(table) => table.seq_lookup_records(),
792        }
793    }
794
795    pub(crate) fn matches_glyphs(&self, glyphs: &IntSet<GlyphId>) -> Result<bool, ReadError> {
796        let (backtrack, lookahead) = match self {
797            Self::Plain(_) => (None, None),
798            Self::Chain(table) => (
799                Some(table.backtrack_coverages()),
800                Some(table.lookahead_coverages()),
801            ),
802        };
803
804        for coverage in self
805            .coverages()
806            .iter_as_nullable()
807            .chain(backtrack.into_iter().flat_map(|x| x.iter_as_nullable()))
808            .chain(lookahead.into_iter().flat_map(|x| x.iter_as_nullable()))
809        {
810            let Some(coverage) = coverage.transpose()? else {
811                return Ok(false);
812            };
813            if !coverage.intersects(glyphs) {
814                return Ok(false);
815            }
816        }
817        Ok(true)
818    }
819}
820
821impl Intersect for ContextFormat1<'_> {
822    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
823        let Some(coverage) = self.coverage().transpose()? else {
824            return Ok(false);
825        };
826        for rule_set in coverage
827            .iter()
828            .zip(self.rule_sets())
829            .filter_map(|(g, rule_set)| rule_set.filter(|_| glyph_set.contains(GlyphId::from(g))))
830        {
831            for rule in rule_set?.rules() {
832                let Some(rule) = rule.transpose()? else {
833                    continue;
834                };
835                if rule.intersects(glyph_set)? {
836                    return Ok(true);
837                }
838            }
839        }
840        Ok(false)
841    }
842}
843
844impl LookupClosure for ContextFormat1<'_> {
845    fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
846        let Some(coverage) = self.coverage().transpose()? else {
847            return Ok(());
848        };
849        let glyph_set = c.glyphs();
850
851        let intersected_idxes: IntSet<u16> = coverage
852            .iter()
853            .enumerate()
854            .filter(|&(_, g)| glyph_set.contains(GlyphId::from(g)))
855            .map(|(idx, _)| idx as u16)
856            .collect();
857
858        for rule_set in self.rule_sets().enumerate().filter_map(|(idx, rule_set)| {
859            rule_set.filter(|_| intersected_idxes.contains(idx as u16))
860        }) {
861            if c.lookup_limit_exceed() {
862                return Ok(());
863            }
864            for rule in rule_set?.rules() {
865                let Some(rule) = rule.transpose()? else {
866                    continue;
867                };
868                rule.closure_lookups(c, arg)?;
869            }
870        }
871
872        Ok(())
873    }
874}
875
876impl Intersect for ContextFormat2<'_> {
877    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
878        let Some(coverage) = self.coverage().transpose()? else {
879            return Ok(false);
880        };
881        let retained_coverage_glyphs = coverage.intersect_set(glyph_set);
882        if retained_coverage_glyphs.is_empty() {
883            return Ok(false);
884        }
885
886        let Some(input_class_def) = self.input_class_def().transpose()? else {
887            return Ok(false);
888        };
889        let coverage_glyph_classes = input_class_def.intersect_classes(&retained_coverage_glyphs);
890        let input_glyph_classes = input_class_def.intersect_classes(glyph_set);
891
892        let backtrack_classes = match self {
893            Self::Plain(_) => IntSet::empty(),
894            Self::Chain(table) => {
895                if table.backtrack_class_def_offset().is_null() {
896                    IntSet::empty()
897                } else {
898                    table.backtrack_class_def()?.intersect_classes(glyph_set)
899                }
900            }
901        };
902        let lookahead_classes = match self {
903            Self::Plain(_) => IntSet::empty(),
904            Self::Chain(table) => {
905                if table.lookahead_class_def_offset().is_null() {
906                    IntSet::empty()
907                } else {
908                    table.lookahead_class_def()?.intersect_classes(glyph_set)
909                }
910            }
911        };
912
913        for rule_set in self.rule_sets().enumerate().filter_map(|(c, rule_set)| {
914            coverage_glyph_classes
915                .contains(c as u16)
916                .then_some(rule_set)
917                .flatten()
918        }) {
919            for rule in rule_set?.rules() {
920                let Some(rule) = rule.transpose()? else {
921                    continue;
922                };
923                if rule.intersects(&input_glyph_classes, &backtrack_classes, &lookahead_classes) {
924                    return Ok(true);
925                }
926            }
927        }
928        Ok(false)
929    }
930}
931
932impl LookupClosure for ContextFormat2<'_> {
933    fn closure_lookups(&self, c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
934        let Some(coverage) = self.coverage().transpose()? else {
935            return Ok(());
936        };
937        let glyph_set = c.glyphs();
938        let retained_coverage_glyphs = coverage.intersect_set(glyph_set);
939        if retained_coverage_glyphs.is_empty() {
940            return Ok(());
941        }
942
943        let Some(input_class_def) = self.input_class_def().transpose()? else {
944            return Ok(());
945        };
946        let coverage_glyph_classes = input_class_def.intersect_classes(&retained_coverage_glyphs);
947        let input_glyph_classes = input_class_def.intersect_classes(glyph_set);
948
949        let backtrack_classes = match self {
950            Self::Plain(_) => IntSet::empty(),
951            Self::Chain(table) => {
952                if table.backtrack_class_def_offset().is_null() {
953                    IntSet::empty()
954                } else {
955                    table.backtrack_class_def()?.intersect_classes(glyph_set)
956                }
957            }
958        };
959        let lookahead_classes = match self {
960            Self::Plain(_) => IntSet::empty(),
961            Self::Chain(table) => {
962                if table.lookahead_class_def_offset().is_null() {
963                    IntSet::empty()
964                } else {
965                    table.lookahead_class_def()?.intersect_classes(glyph_set)
966                }
967            }
968        };
969
970        for rule_set in self.rule_sets().enumerate().filter_map(|(c, rule_set)| {
971            coverage_glyph_classes
972                .contains(c as u16)
973                .then_some(rule_set)
974                .flatten()
975        }) {
976            if c.lookup_limit_exceed() {
977                return Ok(());
978            }
979
980            for rule in rule_set?.rules() {
981                if c.lookup_limit_exceed() {
982                    return Ok(());
983                }
984                let Some(rule) = rule.transpose()? else {
985                    continue;
986                };
987
988                if !rule.intersects(&input_glyph_classes, &backtrack_classes, &lookahead_classes) {
989                    continue;
990                }
991
992                for lookup_record in rule.lookup_records() {
993                    let index = lookup_record.lookup_list_index();
994                    c.recurse(index)?;
995                }
996            }
997        }
998        Ok(())
999    }
1000}
1001
1002impl Intersect for ContextFormat3<'_> {
1003    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
1004        self.matches_glyphs(glyph_set)
1005    }
1006}
1007
1008impl LookupClosure for ContextFormat3<'_> {
1009    fn closure_lookups(&self, c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
1010        if !self.intersects(c.glyphs())? {
1011            return Ok(());
1012        }
1013
1014        for lookup_record in self.lookup_records() {
1015            let index = lookup_record.lookup_list_index();
1016            c.recurse(index)?;
1017        }
1018
1019        Ok(())
1020    }
1021}
1022
1023impl Intersect for SequenceContext<'_> {
1024    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
1025        match self {
1026            Self::Format1(table) => ContextFormat1::Plain(table.clone()).intersects(glyph_set),
1027            Self::Format2(table) => ContextFormat2::Plain(table.clone()).intersects(glyph_set),
1028            Self::Format3(table) => ContextFormat3::Plain(table.clone()).intersects(glyph_set),
1029        }
1030    }
1031}
1032
1033impl LookupClosure for SequenceContext<'_> {
1034    fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
1035        match self {
1036            Self::Format1(table) => ContextFormat1::Plain(table.clone()).closure_lookups(c, arg),
1037            Self::Format2(table) => ContextFormat2::Plain(table.clone()).closure_lookups(c, arg),
1038            Self::Format3(table) => ContextFormat3::Plain(table.clone()).closure_lookups(c, arg),
1039        }
1040    }
1041}
1042
1043impl Intersect for ChainedSequenceContext<'_> {
1044    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
1045        match self {
1046            Self::Format1(table) => ContextFormat1::Chain(table.clone()).intersects(glyph_set),
1047            Self::Format2(table) => ContextFormat2::Chain(table.clone()).intersects(glyph_set),
1048            Self::Format3(table) => ContextFormat3::Chain(table.clone()).intersects(glyph_set),
1049        }
1050    }
1051}
1052
1053impl LookupClosure for ChainedSequenceContext<'_> {
1054    fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
1055        match self {
1056            Self::Format1(table) => ContextFormat1::Chain(table.clone()).closure_lookups(c, arg),
1057            Self::Format2(table) => ContextFormat2::Chain(table.clone()).closure_lookups(c, arg),
1058            Self::Format3(table) => ContextFormat3::Chain(table.clone()).closure_lookups(c, arg),
1059        }
1060    }
1061}