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