Skip to main content

read_fonts/tables/layout/
closure.rs

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