1use 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 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 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 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 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 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
374pub(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
482pub(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 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}