1use 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 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 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 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 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 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 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
410pub(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
522pub(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 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}