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