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, FontRead, GlyphId, LangSys, ReadError, Script, ScriptList, SequenceContext,
11 SequenceContextFormat1, SequenceContextFormat2, SequenceContextFormat3, SequenceLookupRecord,
12 SequenceRule, SequenceRuleSet, Subtables, Tag,
13};
14use crate::{
15 collections::IntSet,
16 tables::{gpos::PositionLookupList, gsub::SubstitutionLookupList},
17};
18
19const MAX_SCRIPTS: u16 = 500;
20const MAX_LANGSYS: u16 = 2000;
21const MAX_FEATURE_INDICES: u16 = 1500;
22pub(crate) const MAX_NESTING_LEVEL: u8 = 64;
23pub(crate) const MAX_LOOKUP_VISIT_COUNT: u16 = 35000;
24
25struct CollectFeaturesContext<'a> {
26 script_count: u16,
27 langsys_count: u16,
28 feature_index_count: u16,
29 visited_script: IntSet<u32>,
30 visited_langsys: IntSet<u32>,
31 feature_indices: &'a mut IntSet<u16>,
32 feature_indices_filter: IntSet<u16>,
33 table_head: usize,
34}
35
36impl<'a> CollectFeaturesContext<'a> {
37 pub(crate) fn new(
38 features: &IntSet<Tag>,
39 table_head: usize,
40 feature_list: &'a FeatureList<'a>,
41 feature_indices: &'a mut IntSet<u16>,
42 ) -> Self {
43 Self {
44 script_count: 0,
45 langsys_count: 0,
46 feature_index_count: 0,
47 visited_script: IntSet::empty(),
48 visited_langsys: IntSet::empty(),
49 feature_indices,
50 feature_indices_filter: feature_list
51 .feature_records()
52 .iter()
53 .enumerate()
54 .filter(|(_i, record)| features.contains(record.feature_tag()))
55 .map(|(idx, _)| idx as u16)
56 .collect(),
57 table_head,
58 }
59 }
60
61 pub(crate) fn script_visited(&mut self, s: &Script) -> bool {
63 if self.script_count > MAX_SCRIPTS {
64 return true;
65 }
66
67 self.script_count += 1;
68
69 let delta = (s.offset_data().as_bytes().as_ptr() as usize - self.table_head) as u32;
70 !self.visited_script.insert(delta)
71 }
72
73 pub(crate) fn langsys_visited(&mut self, langsys: &LangSys) -> bool {
75 if self.langsys_count > MAX_LANGSYS {
76 return true;
77 }
78
79 self.langsys_count += 1;
80
81 let delta = (langsys.offset_data().as_bytes().as_ptr() as usize - self.table_head) as u32;
82 !self.visited_langsys.insert(delta)
83 }
84
85 pub(crate) fn feature_indices_limit_exceeded(&mut self, count: u16) -> bool {
87 let (new_count, overflow) = self.feature_index_count.overflowing_add(count);
88 if overflow {
89 self.feature_index_count = MAX_FEATURE_INDICES;
90 return true;
91 }
92 self.feature_index_count = new_count;
93 new_count > MAX_FEATURE_INDICES
94 }
95}
96
97impl ScriptList<'_> {
98 pub(crate) fn collect_features(
100 &self,
101 layout_table_head: usize,
102 feature_list: &FeatureList,
103 scripts: &IntSet<Tag>,
104 languages: &IntSet<Tag>,
105 features: &IntSet<Tag>,
106 ) -> Result<IntSet<u16>, ReadError> {
107 let mut out = IntSet::empty();
108 let mut c =
109 CollectFeaturesContext::new(features, layout_table_head, feature_list, &mut out);
110 let script_records = self.script_records();
111 let font_data = self.offset_data();
112 if scripts.is_inverted() {
113 for record in script_records {
114 let tag = record.script_tag();
115 if !scripts.contains(tag) || record.script_offset().is_null() {
116 continue;
117 }
118 let script = record.script(font_data)?;
119 script.collect_features(&mut c, languages)?;
120 }
121 } else {
122 for idx in scripts.iter().filter_map(|tag| self.index_for_tag(tag)) {
123 let record = script_records[idx as usize];
124 if record.script_offset().is_null() {
125 continue;
126 }
127 let script = record.script(font_data)?;
128 script.collect_features(&mut c, languages)?;
129 }
130 }
131 Ok(out)
132 }
133}
134
135impl Script<'_> {
136 fn collect_features(
137 &self,
138 c: &mut CollectFeaturesContext,
139 languages: &IntSet<Tag>,
140 ) -> Result<(), ReadError> {
141 if c.script_visited(self) {
142 return Ok(());
143 }
144
145 let lang_sys_records = self.lang_sys_records();
146 let font_data = self.offset_data();
147
148 if let Some(default_lang_sys) = self.default_lang_sys().transpose()? {
149 default_lang_sys.collect_features(c);
150 }
151
152 if languages.is_inverted() {
153 for record in lang_sys_records {
154 let tag = record.lang_sys_tag();
155 if !languages.contains(tag) || record.lang_sys_offset().is_null() {
156 continue;
157 }
158 let lang_sys = record.lang_sys(font_data)?;
159 lang_sys.collect_features(c);
160 }
161 } else {
162 for idx in languages
163 .iter()
164 .filter_map(|tag| self.lang_sys_index_for_tag(tag))
165 {
166 let record = lang_sys_records[idx as usize];
167 if record.lang_sys_offset().is_null() {
168 continue;
169 }
170 let lang_sys = record.lang_sys(font_data)?;
171 lang_sys.collect_features(c);
172 }
173 }
174 Ok(())
175 }
176}
177
178impl LangSys<'_> {
179 fn collect_features(&self, c: &mut CollectFeaturesContext) {
180 if c.langsys_visited(self) {
181 return;
182 }
183
184 if c.feature_indices_filter.is_empty() {
185 return;
186 }
187
188 let required_feature_idx = self.required_feature_index();
189 if required_feature_idx != 0xFFFF
190 && !c.feature_indices_limit_exceeded(1)
191 && c.feature_indices_filter.contains(required_feature_idx)
192 {
193 c.feature_indices.insert(required_feature_idx);
194 }
195
196 if c.feature_indices_limit_exceeded(self.feature_index_count()) {
197 return;
198 }
199
200 for feature_index in self.feature_indices() {
201 let idx = feature_index.get();
202 if !c.feature_indices_filter.contains(idx) {
203 continue;
204 }
205 c.feature_indices.insert(idx);
206 c.feature_indices_filter.remove(idx);
207 }
208 }
209}
210
211impl Feature<'_> {
212 pub(crate) fn collect_lookups(&self) -> Vec<u16> {
213 self.lookup_list_indices()
214 .iter()
215 .map(|idx| idx.get())
216 .collect()
217 }
218}
219
220impl FeatureList<'_> {
221 pub(crate) fn collect_lookups(
222 &self,
223 feature_indices: &IntSet<u16>,
224 ) -> Result<IntSet<u16>, ReadError> {
225 let features_records = self.feature_records();
226 let num_features = self.feature_count();
227 let font_data = self.offset_data();
228 let mut lookup_idxes = IntSet::empty();
229
230 if feature_indices.is_inverted() {
231 for feature_rec in (0..num_features).filter_map(|i| {
232 feature_indices
233 .contains(i)
234 .then(|| features_records.get(i as usize))
235 .flatten()
236 }) {
237 if feature_rec.feature_offset().is_null() {
238 continue;
239 }
240 lookup_idxes.extend_unsorted(feature_rec.feature(font_data)?.collect_lookups());
241 }
242 } else {
243 for feature_rec in feature_indices
244 .iter()
245 .filter_map(|i| features_records.get(i as usize))
246 {
247 if feature_rec.feature_offset().is_null() {
248 continue;
249 }
250 lookup_idxes.extend_unsorted(feature_rec.feature(font_data)?.collect_lookups());
251 }
252 }
253 Ok(lookup_idxes)
254 }
255}
256
257impl FeatureVariations<'_> {
258 pub(crate) fn collect_lookups(
259 &self,
260 feature_indices: &IntSet<u16>,
261 ) -> Result<IntSet<u16>, ReadError> {
262 let mut out = IntSet::empty();
263
264 for variation_rec in self.feature_variation_records() {
265 let Some(subs) = variation_rec
266 .feature_table_substitution(self.offset_data())
267 .transpose()?
268 else {
269 continue;
270 };
271
272 for sub_record in subs
273 .substitutions()
274 .iter()
275 .filter(|sub_rec| feature_indices.contains(sub_rec.feature_index()))
276 {
277 if sub_record.alternate_feature_offset().is_null() {
278 continue;
279 }
280 let sub_f = sub_record.alternate_feature(subs.offset_data())?;
281 out.extend_unsorted(sub_f.lookup_list_indices().iter().map(|i| i.get()));
282 }
283 }
284 Ok(out)
285 }
286}
287
288pub(crate) enum LayoutLookupList<'a> {
289 Gsub(&'a SubstitutionLookupList<'a>),
290 Gpos(&'a PositionLookupList<'a>),
291}
292
293pub(crate) struct LookupClosureCtx<'a> {
294 visited_lookups: IntSet<u16>,
295 inactive_lookups: IntSet<u16>,
296 glyph_set: &'a IntSet<GlyphId>,
297 lookup_count: u16,
298 nesting_level_left: u8,
299 lookup_list: &'a LayoutLookupList<'a>,
300}
301
302impl<'a> LookupClosureCtx<'a> {
303 pub(crate) fn new(glyph_set: &'a IntSet<GlyphId>, lookup_list: &'a LayoutLookupList) -> Self {
304 Self {
305 visited_lookups: IntSet::empty(),
306 inactive_lookups: IntSet::empty(),
307 glyph_set,
308 lookup_count: 0,
309 nesting_level_left: MAX_NESTING_LEVEL,
310 lookup_list,
311 }
312 }
313
314 pub(crate) fn visited_lookups(&self) -> &IntSet<u16> {
315 &self.visited_lookups
316 }
317
318 pub(crate) fn inactive_lookups(&self) -> &IntSet<u16> {
319 &self.inactive_lookups
320 }
321
322 pub(crate) fn glyphs(&self) -> &IntSet<GlyphId> {
323 self.glyph_set
324 }
325
326 pub(crate) fn set_lookup_inactive(&mut self, lookup_index: u16) {
327 self.inactive_lookups.insert(lookup_index);
328 }
329
330 pub(crate) fn lookup_limit_exceed(&self) -> bool {
331 self.lookup_count > MAX_LOOKUP_VISIT_COUNT
332 }
333
334 pub(crate) fn should_visit_lookup(&mut self, lookup_index: u16) -> bool {
337 if self.lookup_count > MAX_LOOKUP_VISIT_COUNT {
338 return false;
339 }
340 self.lookup_count += 1;
341 self.visited_lookups.insert(lookup_index)
342 }
343
344 pub(crate) fn recurse(&mut self, lookup_index: u16) -> Result<(), ReadError> {
345 if self.nesting_level_left == 0 {
346 return Ok(());
347 }
348
349 if self.lookup_limit_exceed() || self.visited_lookups.contains(lookup_index) {
350 return Ok(());
351 }
352
353 self.nesting_level_left -= 1;
354 match self.lookup_list {
355 LayoutLookupList::Gpos(lookuplist) => {
356 match lookuplist.lookups().get(lookup_index as usize) {
357 Err(ReadError::NullOffset) | Err(ReadError::InvalidCollectionIndex(_)) => (),
358 lookup => lookup?.closure_lookups(self, lookup_index)?,
359 }
360 }
361 LayoutLookupList::Gsub(lookuplist) => {
362 match lookuplist.lookups().get(lookup_index as usize) {
363 Err(ReadError::NullOffset) | Err(ReadError::InvalidCollectionIndex(_)) => (),
364 lookup => lookup?.closure_lookups(self, lookup_index)?,
365 }
366 }
367 }
368 self.nesting_level_left += 1;
369 Ok(())
370 }
371}
372
373pub(crate) trait LookupClosure {
375 fn closure_lookups(&self, _c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
376 Ok(())
377 }
378}
379
380pub trait Intersect {
381 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError>;
382}
383
384impl Intersect for ClassDef<'_> {
385 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
386 match self {
387 ClassDef::Format1(table) => table.intersects(glyph_set),
388 ClassDef::Format2(table) => table.intersects(glyph_set),
389 }
390 }
391}
392
393impl Intersect for ClassDefFormat1<'_> {
394 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
395 let glyph_count = self.glyph_count();
396 if glyph_count == 0 {
397 return Ok(false);
398 }
399
400 let start = self.start_glyph_id().to_u32();
401 let end = start + glyph_count as u32;
402
403 let mut start_glyph = GlyphId::from(start);
404 let class_values = self.class_value_array();
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> + '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>,
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
633impl ContextFormat2<'_> {
634 pub(crate) fn coverage(&self) -> Option<Result<CoverageTable<'_>, ReadError>> {
635 match self {
636 ContextFormat2::Plain(table) if !table.coverage_offset().is_null() => {
637 Some(table.coverage())
638 }
639 ContextFormat2::Chain(table) if !table.coverage_offset().is_null() => {
640 Some(table.coverage())
641 }
642 _ => None,
643 }
644 }
645
646 pub(crate) fn input_class_def(&self) -> Option<Result<ClassDef<'_>, ReadError>> {
647 match self {
648 ContextFormat2::Plain(table_ref) if !table_ref.class_def_offset().is_null() => {
649 Some(table_ref.class_def())
650 }
651 ContextFormat2::Chain(table_ref) if !table_ref.input_class_def_offset().is_null() => {
652 Some(table_ref.input_class_def())
653 }
654 _ => None,
655 }
656 }
657
658 pub(crate) fn rule_sets(
659 &self,
660 ) -> impl Iterator<Item = Option<Result<Format2RuleSet<'_>, ReadError>>> {
661 let (left, right) = match self {
662 ContextFormat2::Plain(table) => (
663 Some(
664 table
665 .class_seq_rule_sets()
666 .iter()
667 .map(|rs| rs.map(|rs| rs.map(Format2RuleSet::Plain))),
668 ),
669 None,
670 ),
671 ContextFormat2::Chain(table) => (
672 None,
673 Some(
674 table
675 .chained_class_seq_rule_sets()
676 .iter()
677 .map(|rs| rs.map(|rs| rs.map(Format2RuleSet::Chain))),
678 ),
679 ),
680 };
681 left.into_iter()
682 .flatten()
683 .chain(right.into_iter().flatten())
684 }
685}
686
687impl Format2RuleSet<'_> {
688 pub(crate) fn rules(&self) -> impl Iterator<Item = Option<Result<Format2Rule<'_>, ReadError>>> {
689 let (left, right) = match self {
690 Format2RuleSet::Plain(table) => (
691 Some(
692 table
693 .class_seq_rules()
694 .iter_as_nullable()
695 .map(|rule| rule.map(|r| r.map(Format2Rule::Plain))),
696 ),
697 None,
698 ),
699 Format2RuleSet::Chain(table) => (
700 None,
701 Some(
702 table
703 .chained_class_seq_rules()
704 .iter_as_nullable()
705 .map(|rule| rule.map(|r| r.map(Format2Rule::Chain))),
706 ),
707 ),
708 };
709 left.into_iter()
710 .flatten()
711 .chain(right.into_iter().flatten())
712 }
713}
714
715impl Format2Rule<'_> {
716 pub(crate) fn input_sequence(&self) -> &[BigEndian<u16>] {
717 match self {
718 Self::Plain(table) => table.input_sequence(),
719 Self::Chain(table) => table.input_sequence(),
720 }
721 }
722
723 pub(crate) fn lookup_records(&self) -> &[SequenceLookupRecord] {
724 match self {
725 Self::Plain(table) => table.seq_lookup_records(),
726 Self::Chain(table) => table.seq_lookup_records(),
727 }
728 }
729
730 pub(crate) fn intersects(
731 &self,
732 input_classes: &IntSet<u16>,
733 backtrack_classes: &IntSet<u16>,
734 lookahead_classes: &IntSet<u16>,
735 ) -> bool {
736 match self {
737 Self::Plain(table) => table.intersects(input_classes),
738 Self::Chain(table) => {
739 table.intersects(input_classes, backtrack_classes, lookahead_classes)
740 }
741 }
742 }
743}
744
745impl ClassSequenceRule<'_> {
746 fn intersects(&self, input_classes: &IntSet<u16>) -> bool {
747 self.input_sequence()
748 .iter()
749 .all(|c| input_classes.contains(c.get()))
750 }
751}
752
753impl ChainedClassSequenceRule<'_> {
754 fn intersects(
755 &self,
756 input_classes: &IntSet<u16>,
757 backtrack_classes: &IntSet<u16>,
758 lookahead_classes: &IntSet<u16>,
759 ) -> bool {
760 self.input_sequence()
761 .iter()
762 .all(|c| input_classes.contains(c.get()))
763 && self
764 .backtrack_sequence()
765 .iter()
766 .all(|c| backtrack_classes.contains(c.get()))
767 && self
768 .lookahead_sequence()
769 .iter()
770 .all(|c| lookahead_classes.contains(c.get()))
771 }
772}
773
774pub(crate) enum ContextFormat3<'a> {
775 Plain(SequenceContextFormat3<'a>),
776 Chain(ChainedSequenceContextFormat3<'a>),
777}
778
779impl ContextFormat3<'_> {
780 pub(crate) fn coverages(&self) -> ArrayOfOffsets<'_, CoverageTable<'_>> {
781 match self {
782 ContextFormat3::Plain(table) => table.coverages(),
783 ContextFormat3::Chain(table) => table.input_coverages(),
784 }
785 }
786
787 pub(crate) fn lookup_records(&self) -> &[SequenceLookupRecord] {
788 match self {
789 ContextFormat3::Plain(table) => table.seq_lookup_records(),
790 ContextFormat3::Chain(table) => table.seq_lookup_records(),
791 }
792 }
793
794 pub(crate) fn matches_glyphs(&self, glyphs: &IntSet<GlyphId>) -> Result<bool, ReadError> {
795 let (backtrack, lookahead) = match self {
796 Self::Plain(_) => (None, None),
797 Self::Chain(table) => (
798 Some(table.backtrack_coverages()),
799 Some(table.lookahead_coverages()),
800 ),
801 };
802
803 for coverage in self
804 .coverages()
805 .iter_as_nullable()
806 .chain(backtrack.into_iter().flat_map(|x| x.iter_as_nullable()))
807 .chain(lookahead.into_iter().flat_map(|x| x.iter_as_nullable()))
808 {
809 let Some(coverage) = coverage.transpose()? else {
810 return Ok(false);
811 };
812 if !coverage.intersects(glyphs) {
813 return Ok(false);
814 }
815 }
816 Ok(true)
817 }
818}
819
820impl Intersect for ContextFormat1<'_> {
821 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
822 let Some(coverage) = self.coverage().transpose()? else {
823 return Ok(false);
824 };
825 for rule_set in coverage
826 .iter()
827 .zip(self.rule_sets())
828 .filter_map(|(g, rule_set)| rule_set.filter(|_| glyph_set.contains(GlyphId::from(g))))
829 {
830 for rule in rule_set?.rules() {
831 let Some(rule) = rule.transpose()? else {
832 continue;
833 };
834 if rule.intersects(glyph_set)? {
835 return Ok(true);
836 }
837 }
838 }
839 Ok(false)
840 }
841}
842
843impl LookupClosure for ContextFormat1<'_> {
844 fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
845 let Some(coverage) = self.coverage().transpose()? else {
846 return Ok(());
847 };
848 let glyph_set = c.glyphs();
849
850 let intersected_idxes: IntSet<u16> = coverage
851 .iter()
852 .enumerate()
853 .filter(|&(_, g)| glyph_set.contains(GlyphId::from(g)))
854 .map(|(idx, _)| idx as u16)
855 .collect();
856
857 for rule_set in self.rule_sets().enumerate().filter_map(|(idx, rule_set)| {
858 rule_set.filter(|_| intersected_idxes.contains(idx as u16))
859 }) {
860 if c.lookup_limit_exceed() {
861 return Ok(());
862 }
863 for rule in rule_set?.rules() {
864 let Some(rule) = rule.transpose()? else {
865 continue;
866 };
867 rule.closure_lookups(c, arg)?;
868 }
869 }
870
871 Ok(())
872 }
873}
874
875impl Intersect for ContextFormat2<'_> {
876 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
877 let Some(coverage) = self.coverage().transpose()? else {
878 return Ok(false);
879 };
880 let retained_coverage_glyphs = coverage.intersect_set(glyph_set);
881 if retained_coverage_glyphs.is_empty() {
882 return Ok(false);
883 }
884
885 let Some(input_class_def) = self.input_class_def().transpose()? else {
886 return Ok(false);
887 };
888 let coverage_glyph_classes = input_class_def.intersect_classes(&retained_coverage_glyphs);
889 let input_glyph_classes = input_class_def.intersect_classes(glyph_set);
890
891 let backtrack_classes = match self {
892 Self::Plain(_) => IntSet::empty(),
893 Self::Chain(table) => {
894 if table.backtrack_class_def_offset().is_null() {
895 IntSet::empty()
896 } else {
897 table.backtrack_class_def()?.intersect_classes(glyph_set)
898 }
899 }
900 };
901 let lookahead_classes = match self {
902 Self::Plain(_) => IntSet::empty(),
903 Self::Chain(table) => {
904 if table.lookahead_class_def_offset().is_null() {
905 IntSet::empty()
906 } else {
907 table.lookahead_class_def()?.intersect_classes(glyph_set)
908 }
909 }
910 };
911
912 for rule_set in self.rule_sets().enumerate().filter_map(|(c, rule_set)| {
913 coverage_glyph_classes
914 .contains(c as u16)
915 .then_some(rule_set)
916 .flatten()
917 }) {
918 for rule in rule_set?.rules() {
919 let Some(rule) = rule.transpose()? else {
920 continue;
921 };
922 if rule.intersects(&input_glyph_classes, &backtrack_classes, &lookahead_classes) {
923 return Ok(true);
924 }
925 }
926 }
927 Ok(false)
928 }
929}
930
931impl LookupClosure for ContextFormat2<'_> {
932 fn closure_lookups(&self, c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
933 let Some(coverage) = self.coverage().transpose()? else {
934 return Ok(());
935 };
936 let glyph_set = c.glyphs();
937 let retained_coverage_glyphs = coverage.intersect_set(glyph_set);
938 if retained_coverage_glyphs.is_empty() {
939 return Ok(());
940 }
941
942 let Some(input_class_def) = self.input_class_def().transpose()? else {
943 return Ok(());
944 };
945 let coverage_glyph_classes = input_class_def.intersect_classes(&retained_coverage_glyphs);
946 let input_glyph_classes = input_class_def.intersect_classes(glyph_set);
947
948 let backtrack_classes = match self {
949 Self::Plain(_) => IntSet::empty(),
950 Self::Chain(table) => {
951 if table.backtrack_class_def_offset().is_null() {
952 IntSet::empty()
953 } else {
954 table.backtrack_class_def()?.intersect_classes(glyph_set)
955 }
956 }
957 };
958 let lookahead_classes = match self {
959 Self::Plain(_) => IntSet::empty(),
960 Self::Chain(table) => {
961 if table.lookahead_class_def_offset().is_null() {
962 IntSet::empty()
963 } else {
964 table.lookahead_class_def()?.intersect_classes(glyph_set)
965 }
966 }
967 };
968
969 for rule_set in self.rule_sets().enumerate().filter_map(|(c, rule_set)| {
970 coverage_glyph_classes
971 .contains(c as u16)
972 .then_some(rule_set)
973 .flatten()
974 }) {
975 if c.lookup_limit_exceed() {
976 return Ok(());
977 }
978
979 for rule in rule_set?.rules() {
980 if c.lookup_limit_exceed() {
981 return Ok(());
982 }
983 let Some(rule) = rule.transpose()? else {
984 continue;
985 };
986
987 if !rule.intersects(&input_glyph_classes, &backtrack_classes, &lookahead_classes) {
988 continue;
989 }
990
991 for lookup_record in rule.lookup_records() {
992 let index = lookup_record.lookup_list_index();
993 c.recurse(index)?;
994 }
995 }
996 }
997 Ok(())
998 }
999}
1000
1001impl Intersect for ContextFormat3<'_> {
1002 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
1003 self.matches_glyphs(glyph_set)
1004 }
1005}
1006
1007impl LookupClosure for ContextFormat3<'_> {
1008 fn closure_lookups(&self, c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
1009 if !self.intersects(c.glyphs())? {
1010 return Ok(());
1011 }
1012
1013 for lookup_record in self.lookup_records() {
1014 let index = lookup_record.lookup_list_index();
1015 c.recurse(index)?;
1016 }
1017
1018 Ok(())
1019 }
1020}
1021
1022impl Intersect for SequenceContext<'_> {
1023 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
1024 match self {
1025 Self::Format1(table) => ContextFormat1::Plain(table.clone()).intersects(glyph_set),
1026 Self::Format2(table) => ContextFormat2::Plain(table.clone()).intersects(glyph_set),
1027 Self::Format3(table) => ContextFormat3::Plain(table.clone()).intersects(glyph_set),
1028 }
1029 }
1030}
1031
1032impl LookupClosure for SequenceContext<'_> {
1033 fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
1034 match self {
1035 Self::Format1(table) => ContextFormat1::Plain(table.clone()).closure_lookups(c, arg),
1036 Self::Format2(table) => ContextFormat2::Plain(table.clone()).closure_lookups(c, arg),
1037 Self::Format3(table) => ContextFormat3::Plain(table.clone()).closure_lookups(c, arg),
1038 }
1039 }
1040}
1041
1042impl Intersect for ChainedSequenceContext<'_> {
1043 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
1044 match self {
1045 Self::Format1(table) => ContextFormat1::Chain(table.clone()).intersects(glyph_set),
1046 Self::Format2(table) => ContextFormat2::Chain(table.clone()).intersects(glyph_set),
1047 Self::Format3(table) => ContextFormat3::Chain(table.clone()).intersects(glyph_set),
1048 }
1049 }
1050}
1051
1052impl LookupClosure for ChainedSequenceContext<'_> {
1053 fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
1054 match self {
1055 Self::Format1(table) => ContextFormat1::Chain(table.clone()).closure_lookups(c, arg),
1056 Self::Format2(table) => ContextFormat2::Chain(table.clone()).closure_lookups(c, arg),
1057 Self::Format3(table) => ContextFormat3::Chain(table.clone()).closure_lookups(c, arg),
1058 }
1059 }
1060}