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