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, 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    FontRead,
18};
19
20const MAX_SCRIPTS: u16 = 500;
21const MAX_LANGSYS: u16 = 2000;
22const MAX_FEATURE_INDICES: u16 = 1500;
23pub(crate) const MAX_NESTING_LEVEL: u8 = 64;
24pub(crate) const MAX_LOOKUP_VISIT_COUNT: u16 = 35000;
25
26struct CollectFeaturesContext<'a> {
27    script_count: u16,
28    langsys_count: u16,
29    feature_index_count: u16,
30    visited_script: IntSet<u32>,
31    visited_langsys: IntSet<u32>,
32    feature_indices: &'a mut IntSet<u16>,
33    feature_indices_filter: IntSet<u16>,
34    table_head: usize,
35}
36
37impl<'a> CollectFeaturesContext<'a> {
38    pub(crate) fn new(
39        features: &IntSet<Tag>,
40        table_head: usize,
41        feature_list: &'a FeatureList<'a>,
42        feature_indices: &'a mut IntSet<u16>,
43    ) -> Self {
44        Self {
45            script_count: 0,
46            langsys_count: 0,
47            feature_index_count: 0,
48            visited_script: IntSet::empty(),
49            visited_langsys: IntSet::empty(),
50            feature_indices,
51            feature_indices_filter: feature_list
52                .feature_records()
53                .iter()
54                .enumerate()
55                .filter(|(_i, record)| features.contains(record.feature_tag()))
56                .map(|(idx, _)| idx as u16)
57                .collect(),
58            table_head,
59        }
60    }
61
62    /// Return true if the script limit has been exceeded or the script is visited before
63    pub(crate) fn script_visited(&mut self, s: &Script) -> bool {
64        if self.script_count > MAX_SCRIPTS {
65            return true;
66        }
67
68        self.script_count += 1;
69
70        let delta = (s.offset_data().as_bytes().as_ptr() as usize - self.table_head) as u32;
71        !self.visited_script.insert(delta)
72    }
73
74    /// Return true if the Langsys limit has been exceeded or the Langsys is visited before
75    pub(crate) fn langsys_visited(&mut self, langsys: &LangSys) -> bool {
76        if self.langsys_count > MAX_LANGSYS {
77            return true;
78        }
79
80        self.langsys_count += 1;
81
82        let delta = (langsys.offset_data().as_bytes().as_ptr() as usize - self.table_head) as u32;
83        !self.visited_langsys.insert(delta)
84    }
85
86    /// Returns true if the feature limit has been exceeded
87    pub(crate) fn feature_indices_limit_exceeded(&mut self, count: u16) -> bool {
88        let (new_count, overflow) = self.feature_index_count.overflowing_add(count);
89        if overflow {
90            self.feature_index_count = MAX_FEATURE_INDICES;
91            return true;
92        }
93        self.feature_index_count = new_count;
94        new_count > MAX_FEATURE_INDICES
95    }
96}
97
98impl ScriptList<'_> {
99    /// Return a set of all feature indices underneath the specified scripts, languages and features
100    pub(crate) fn collect_features(
101        &self,
102        layout_table_head: usize,
103        feature_list: &FeatureList,
104        scripts: &IntSet<Tag>,
105        languages: &IntSet<Tag>,
106        features: &IntSet<Tag>,
107    ) -> Result<IntSet<u16>, ReadError> {
108        let mut out = IntSet::empty();
109        let mut c =
110            CollectFeaturesContext::new(features, layout_table_head, feature_list, &mut out);
111        let script_records = self.script_records();
112        let font_data = self.offset_data();
113        if scripts.is_inverted() {
114            for record in script_records {
115                let tag = record.script_tag();
116                if !scripts.contains(tag) || record.script_offset().is_null() {
117                    continue;
118                }
119                let script = record.script(font_data)?;
120                script.collect_features(&mut c, languages)?;
121            }
122        } else {
123            for idx in scripts.iter().filter_map(|tag| self.index_for_tag(tag)) {
124                let record = script_records[idx as usize];
125                if record.script_offset().is_null() {
126                    continue;
127                }
128                let script = record.script(font_data)?;
129                script.collect_features(&mut c, languages)?;
130            }
131        }
132        Ok(out)
133    }
134}
135
136impl Script<'_> {
137    fn collect_features(
138        &self,
139        c: &mut CollectFeaturesContext,
140        languages: &IntSet<Tag>,
141    ) -> Result<(), ReadError> {
142        if c.script_visited(self) {
143            return Ok(());
144        }
145
146        let lang_sys_records = self.lang_sys_records();
147        let font_data = self.offset_data();
148
149        if let Some(default_lang_sys) = self.default_lang_sys().transpose()? {
150            default_lang_sys.collect_features(c);
151        }
152
153        if languages.is_inverted() {
154            for record in lang_sys_records {
155                let tag = record.lang_sys_tag();
156                if !languages.contains(tag) || record.lang_sys_offset().is_null() {
157                    continue;
158                }
159                let lang_sys = record.lang_sys(font_data)?;
160                lang_sys.collect_features(c);
161            }
162        } else {
163            for idx in languages
164                .iter()
165                .filter_map(|tag| self.lang_sys_index_for_tag(tag))
166            {
167                let record = lang_sys_records[idx as usize];
168                if record.lang_sys_offset().is_null() {
169                    continue;
170                }
171                let lang_sys = record.lang_sys(font_data)?;
172                lang_sys.collect_features(c);
173            }
174        }
175        Ok(())
176    }
177}
178
179impl LangSys<'_> {
180    fn collect_features(&self, c: &mut CollectFeaturesContext) {
181        if c.langsys_visited(self) {
182            return;
183        }
184
185        if c.feature_indices_filter.is_empty() {
186            return;
187        }
188
189        let required_feature_idx = self.required_feature_index();
190        if required_feature_idx != 0xFFFF
191            && !c.feature_indices_limit_exceeded(1)
192            && c.feature_indices_filter.contains(required_feature_idx)
193        {
194            c.feature_indices.insert(required_feature_idx);
195        }
196
197        if c.feature_indices_limit_exceeded(self.feature_index_count()) {
198            return;
199        }
200
201        for feature_index in self.feature_indices() {
202            let idx = feature_index.get();
203            if !c.feature_indices_filter.contains(idx) {
204                continue;
205            }
206            c.feature_indices.insert(idx);
207            c.feature_indices_filter.remove(idx);
208        }
209    }
210}
211
212impl Feature<'_> {
213    pub(crate) fn collect_lookups(&self) -> Vec<u16> {
214        self.lookup_list_indices()
215            .iter()
216            .map(|idx| idx.get())
217            .collect()
218    }
219}
220
221impl FeatureList<'_> {
222    pub(crate) fn collect_lookups(
223        &self,
224        feature_indices: &IntSet<u16>,
225    ) -> Result<IntSet<u16>, ReadError> {
226        let features_records = self.feature_records();
227        let num_features = self.feature_count();
228        let font_data = self.offset_data();
229        let mut lookup_idxes = IntSet::empty();
230
231        if feature_indices.is_inverted() {
232            for feature_rec in (0..num_features).filter_map(|i| {
233                feature_indices
234                    .contains(i)
235                    .then(|| features_records.get(i as usize))
236                    .flatten()
237            }) {
238                if feature_rec.feature_offset().is_null() {
239                    continue;
240                }
241                lookup_idxes.extend_unsorted(feature_rec.feature(font_data)?.collect_lookups());
242            }
243        } else {
244            for feature_rec in feature_indices
245                .iter()
246                .filter_map(|i| features_records.get(i as usize))
247            {
248                if feature_rec.feature_offset().is_null() {
249                    continue;
250                }
251                lookup_idxes.extend_unsorted(feature_rec.feature(font_data)?.collect_lookups());
252            }
253        }
254        Ok(lookup_idxes)
255    }
256}
257
258impl FeatureVariations<'_> {
259    pub(crate) fn collect_lookups(
260        &self,
261        feature_indices: &IntSet<u16>,
262    ) -> Result<IntSet<u16>, ReadError> {
263        let mut out = IntSet::empty();
264
265        for variation_rec in self.feature_variation_records() {
266            let Some(subs) = variation_rec
267                .feature_table_substitution(self.offset_data())
268                .transpose()?
269            else {
270                continue;
271            };
272
273            for sub_record in subs
274                .substitutions()
275                .iter()
276                .filter(|sub_rec| feature_indices.contains(sub_rec.feature_index()))
277            {
278                if sub_record.alternate_feature_offset().is_null() {
279                    continue;
280                }
281                let sub_f = sub_record.alternate_feature(subs.offset_data())?;
282                out.extend_unsorted(sub_f.lookup_list_indices().iter().map(|i| i.get()));
283            }
284        }
285        Ok(out)
286    }
287}
288
289pub(crate) enum LayoutLookupList<'a> {
290    Gsub(&'a SubstitutionLookupList<'a>),
291    Gpos(&'a PositionLookupList<'a>),
292}
293
294pub(crate) struct LookupClosureCtx<'a> {
295    visited_lookups: IntSet<u16>,
296    inactive_lookups: IntSet<u16>,
297    glyph_set: &'a IntSet<GlyphId>,
298    lookup_count: u16,
299    nesting_level_left: u8,
300    lookup_list: &'a LayoutLookupList<'a>,
301}
302
303impl<'a> LookupClosureCtx<'a> {
304    pub(crate) fn new(glyph_set: &'a IntSet<GlyphId>, lookup_list: &'a LayoutLookupList) -> Self {
305        Self {
306            visited_lookups: IntSet::empty(),
307            inactive_lookups: IntSet::empty(),
308            glyph_set,
309            lookup_count: 0,
310            nesting_level_left: MAX_NESTING_LEVEL,
311            lookup_list,
312        }
313    }
314
315    pub(crate) fn visited_lookups(&self) -> &IntSet<u16> {
316        &self.visited_lookups
317    }
318
319    pub(crate) fn inactive_lookups(&self) -> &IntSet<u16> {
320        &self.inactive_lookups
321    }
322
323    pub(crate) fn glyphs(&self) -> &IntSet<GlyphId> {
324        self.glyph_set
325    }
326
327    pub(crate) fn set_lookup_inactive(&mut self, lookup_index: u16) {
328        self.inactive_lookups.insert(lookup_index);
329    }
330
331    pub(crate) fn lookup_limit_exceed(&self) -> bool {
332        self.lookup_count > MAX_LOOKUP_VISIT_COUNT
333    }
334
335    // return false if lookup limit exceeded or lookup visited,and visited set is not modified
336    // Otherwise return true and insert lookup index into the visited set
337    pub(crate) fn should_visit_lookup(&mut self, lookup_index: u16) -> bool {
338        if self.lookup_count > MAX_LOOKUP_VISIT_COUNT {
339            return false;
340        }
341        self.lookup_count += 1;
342        self.visited_lookups.insert(lookup_index)
343    }
344
345    pub(crate) fn recurse(&mut self, lookup_index: u16) -> Result<(), ReadError> {
346        if self.nesting_level_left == 0 {
347            return Ok(());
348        }
349
350        if self.lookup_limit_exceed() || self.visited_lookups.contains(lookup_index) {
351            return Ok(());
352        }
353
354        self.nesting_level_left -= 1;
355        match self.lookup_list {
356            LayoutLookupList::Gpos(lookuplist) => {
357                match lookuplist.lookups().get(lookup_index as usize) {
358                    Err(ReadError::NullOffset) | Err(ReadError::InvalidCollectionIndex(_)) => (),
359                    lookup => lookup?.closure_lookups(self, lookup_index)?,
360                }
361            }
362            LayoutLookupList::Gsub(lookuplist) => {
363                match lookuplist.lookups().get(lookup_index as usize) {
364                    Err(ReadError::NullOffset) | Err(ReadError::InvalidCollectionIndex(_)) => (),
365                    lookup => lookup?.closure_lookups(self, lookup_index)?,
366                }
367            }
368        }
369        self.nesting_level_left += 1;
370        Ok(())
371    }
372}
373
374/// Compute the transitive closure of lookups
375pub(crate) trait LookupClosure {
376    fn closure_lookups(&self, _c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
377        Ok(())
378    }
379}
380
381pub trait Intersect {
382    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError>;
383}
384
385impl Intersect for ClassDef<'_> {
386    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
387        match self {
388            ClassDef::Format1(table) => table.intersects(glyph_set),
389            ClassDef::Format2(table) => table.intersects(glyph_set),
390        }
391    }
392}
393
394impl Intersect for ClassDefFormat1<'_> {
395    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
396        let glyph_count = self.glyph_count();
397        if glyph_count == 0 {
398            return Ok(false);
399        }
400
401        let start = self.start_glyph_id().to_u32();
402        let end = start + glyph_count as u32;
403
404        let mut start_glyph = GlyphId::from(start);
405        let class_values = self.class_value_array();
406        if glyph_set.contains(start_glyph) && class_values[0] != 0 {
407            return Ok(true);
408        }
409
410        while let Some(g) = glyph_set.iter_after(start_glyph).next() {
411            let g = g.to_u32();
412            if g >= end {
413                break;
414            }
415            let Some(class) = class_values.get((g - start) as usize) else {
416                break;
417            };
418            if class.get() != 0 {
419                return Ok(true);
420            }
421            start_glyph = GlyphId::from(g);
422        }
423        Ok(false)
424    }
425}
426
427impl Intersect for ClassDefFormat2<'_> {
428    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
429        let num_ranges = self.class_range_count();
430        let num_bits = 16 - num_ranges.leading_zeros();
431        if num_ranges as u64 > glyph_set.len() * num_bits as u64 {
432            for g in glyph_set.iter().map(|g| GlyphId16::from(g.to_u32() as u16)) {
433                if self.get(g) != 0 {
434                    return Ok(true);
435                }
436            }
437        } else {
438            for record in self.class_range_records() {
439                let first = GlyphId::from(record.start_glyph_id());
440                let last = GlyphId::from(record.end_glyph_id());
441                if glyph_set.intersects_range(first..=last) && record.class() != 0 {
442                    return Ok(true);
443                }
444            }
445        }
446        Ok(false)
447    }
448}
449
450impl<'a, T, Ext> LookupClosure for Subtables<'a, T, Ext>
451where
452    T: LookupClosure + Intersect + FontRead<'a, Args = ()> + 'a,
453    Ext: ExtensionLookup<'a, T> + 'a,
454{
455    fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
456        for t in self.iter().filter_map(|table| match table {
457            Err(ReadError::NullOffset) => None,
458            other => Some(other),
459        }) {
460            t?.closure_lookups(c, arg)?;
461        }
462        Ok(())
463    }
464}
465
466impl<'a, T> Intersect for ArrayOfOffsets<'a, T, Offset16>
467where
468    T: Intersect + FontRead<'a, Args = ()> + '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}