1use font_types::GlyphId;
6
7use crate::{
8 collections::IntSet,
9 tables::layout::{ExtensionLookup, Subtables},
10 FontRead, ReadError, Tag,
11};
12
13use super::{
14 AlternateSubstFormat1, ChainedSequenceContext, Gsub, Ligature, LigatureSet,
15 LigatureSubstFormat1, MultipleSubstFormat1, ReverseChainSingleSubstFormat1, SequenceContext,
16 SingleSubst, SingleSubstFormat1, SingleSubstFormat2, SubstitutionLookup,
17 SubstitutionLookupList, SubstitutionSubtables,
18};
19
20#[cfg(feature = "std")]
21use crate::tables::layout::{
22 ContextFormat1, ContextFormat2, ContextFormat3, Intersect, LayoutLookupList, LookupClosure,
23 LookupClosureCtx,
24};
25
26mod ctx {
29 use std::collections::HashMap;
30 use types::GlyphId;
31
32 use crate::{
33 collections::IntSet,
34 tables::gsub::{SubstitutionLookup, SubstitutionLookupList},
35 };
36
37 use super::GlyphClosure as _;
38 use super::ReadError;
39
40 #[cfg(feature = "std")]
41 use crate::tables::layout::{MAX_LOOKUP_VISIT_COUNT, MAX_NESTING_LEVEL};
42
43 pub(super) struct ClosureCtx<'a> {
44 glyphs: &'a mut IntSet<GlyphId>,
46 active_glyphs_stack: Vec<IntSet<GlyphId>>,
47 output: IntSet<GlyphId>,
48 lookup_count: u16,
49 nesting_level_left: u8,
50 done_lookups_glyphs: HashMap<u16, (u64, IntSet<GlyphId>)>,
51 }
52
53 impl<'a> ClosureCtx<'a> {
54 pub(super) fn new(glyphs: &'a mut IntSet<GlyphId>) -> Self {
55 Self {
56 glyphs,
57 active_glyphs_stack: Vec::new(),
58 output: IntSet::empty(),
59 lookup_count: 0,
60 nesting_level_left: MAX_NESTING_LEVEL,
61 done_lookups_glyphs: Default::default(),
62 }
63 }
64
65 pub(super) fn lookup_limit_exceed(&self) -> bool {
66 self.lookup_count > MAX_LOOKUP_VISIT_COUNT
67 }
68
69 pub(super) fn parent_active_glyphs(&self) -> &IntSet<GlyphId> {
70 if self.active_glyphs_stack.is_empty() {
71 return &*self.glyphs;
72 }
73
74 self.active_glyphs_stack.last().unwrap()
75 }
76
77 pub(super) fn push_cur_active_glyphs(&mut self, glyphs: IntSet<GlyphId>) {
78 self.active_glyphs_stack.push(glyphs)
79 }
80
81 pub(super) fn pop_cur_done_glyphs(&mut self) {
82 self.active_glyphs_stack.pop();
83 }
84
85 pub(super) fn recurse(
86 &mut self,
87 lookup_list: &SubstitutionLookupList,
88 lookup: &SubstitutionLookup,
89 lookup_index: u16,
90 glyphs: IntSet<GlyphId>,
91 ) -> Result<(), ReadError> {
92 if self.nesting_level_left == 0 {
93 return Ok(());
94 }
95
96 self.nesting_level_left -= 1;
97 self.push_cur_active_glyphs(glyphs);
98
99 lookup.closure_glyphs(self, lookup_list, lookup_index)?;
100 self.nesting_level_left += 1;
101 self.pop_cur_done_glyphs();
102
103 Ok(())
104 }
105
106 pub(super) fn reset_lookup_visit_count(&mut self) {
107 self.lookup_count = 0;
108 }
109
110 pub(super) fn should_visit_lookup(&mut self, lookup_index: u16) -> bool {
111 if self.lookup_limit_exceed() {
112 return false;
113 }
114 self.lookup_count += 1;
115 !self.is_lookup_done(lookup_index)
116 }
117
118 pub(super) fn is_lookup_done(&mut self, lookup_index: u16) -> bool {
120 {
121 let (count, covered) = self
122 .done_lookups_glyphs
123 .entry(lookup_index)
124 .or_insert((0, IntSet::empty()));
125
126 if *count != self.glyphs.len() {
127 *count = self.glyphs.len();
128 covered.clear();
129 }
130 }
131
132 let mut cur_glyphs = IntSet::empty();
133 {
134 let covered = &self.done_lookups_glyphs.get(&lookup_index).unwrap().1;
135 if self
137 .parent_active_glyphs()
138 .iter()
139 .all(|g| covered.contains(g))
140 {
141 return true;
142 }
143 cur_glyphs.extend(self.parent_active_glyphs().iter());
144 }
145
146 let (_, covered) = self.done_lookups_glyphs.get_mut(&lookup_index).unwrap();
147 covered.union(&cur_glyphs);
148 false
149 }
150
151 pub(super) fn glyphs(&self) -> &IntSet<GlyphId> {
152 self.glyphs
153 }
154
155 pub(super) fn add(&mut self, gid: GlyphId) {
156 self.output.insert(gid);
157 }
158
159 pub(super) fn add_glyphs(&mut self, iter: impl IntoIterator<Item = GlyphId>) {
160 self.output.extend(iter)
161 }
162
163 pub(super) fn flush(&mut self) {
164 self.glyphs.union(&self.output);
165 self.output.clear();
166 self.active_glyphs_stack.clear();
167 }
168 }
169}
170
171use ctx::ClosureCtx;
172
173trait GlyphClosure {
175 fn closure_glyphs(
177 &self,
178 ctx: &mut ClosureCtx,
179 lookup_list: &SubstitutionLookupList,
180 lookup_index: u16,
181 ) -> Result<(), ReadError>;
182
183 fn may_have_non_1to1(&self) -> Result<bool, ReadError> {
184 Ok(false)
185 }
186}
187
188const CLOSURE_MAX_STAGES: u8 = 12;
189impl Gsub<'_> {
190 pub fn closure_glyphs(
193 &self,
194 lookups: &IntSet<u16>,
195 glyphs: &mut IntSet<GlyphId>,
196 ) -> Result<(), ReadError> {
197 let lookup_list = self.lookup_list()?;
198 let num_lookups = lookup_list.lookup_count();
199 let lookup_offsets = lookup_list.lookups();
200
201 let mut ctx = ClosureCtx::new(glyphs);
202 let mut iteration_count = 0;
203 let mut glyphs_length;
204 loop {
205 ctx.reset_lookup_visit_count();
206 glyphs_length = ctx.glyphs().len();
207
208 if lookups.is_inverted() {
209 for i in 0..num_lookups {
210 if !lookups.contains(i) {
211 continue;
212 }
213 let lookup = lookup_offsets.get(i as usize)?;
214 lookup.closure_glyphs(&mut ctx, &lookup_list, i)?;
215 ctx.flush();
216 }
217 } else {
218 for i in lookups.iter() {
219 let lookup = lookup_offsets.get(i as usize)?;
220 lookup.closure_glyphs(&mut ctx, &lookup_list, i)?;
221 ctx.flush();
222 }
223 }
224 if iteration_count > CLOSURE_MAX_STAGES || glyphs_length == ctx.glyphs().len() {
225 break;
226 }
227 iteration_count += 1;
228 }
229 Ok(())
230 }
231
232 pub fn collect_lookups(&self, feature_indices: &IntSet<u16>) -> Result<IntSet<u16>, ReadError> {
236 let feature_list = self.feature_list()?;
237 let mut lookup_indices = feature_list.collect_lookups(feature_indices)?;
238
239 if let Some(feature_variations) = self.feature_variations().transpose()? {
240 let subs_lookup_indices = feature_variations.collect_lookups(feature_indices)?;
241 lookup_indices.union(&subs_lookup_indices);
242 }
243 Ok(lookup_indices)
244 }
245
246 pub fn collect_features(
248 &self,
249 scripts: &IntSet<Tag>,
250 languages: &IntSet<Tag>,
251 features: &IntSet<Tag>,
252 ) -> Result<IntSet<u16>, ReadError> {
253 let feature_list = self.feature_list()?;
254 let script_list = self.script_list()?;
255 let head_ptr = self.offset_data().as_bytes().as_ptr() as usize;
256 script_list.collect_features(head_ptr, &feature_list, scripts, languages, features)
257 }
258
259 pub fn closure_lookups(
261 &self,
262 glyphs: &IntSet<GlyphId>,
263 lookup_indices: &mut IntSet<u16>,
264 ) -> Result<(), ReadError> {
265 let lookup_list = self.lookup_list()?;
266 lookup_list.closure_lookups(glyphs, lookup_indices)
267 }
268}
269
270impl GlyphClosure for SubstitutionLookup<'_> {
272 fn closure_glyphs(
273 &self,
274 ctx: &mut ClosureCtx,
275 lookup_list: &SubstitutionLookupList,
276 lookup_index: u16,
277 ) -> Result<(), ReadError> {
278 if !ctx.should_visit_lookup(lookup_index) {
279 return Ok(());
280 }
281 self.subtables()?
282 .closure_glyphs(ctx, lookup_list, lookup_index)
283 }
284
285 fn may_have_non_1to1(&self) -> Result<bool, ReadError> {
286 self.subtables()?.may_have_non_1to1()
287 }
288}
289
290impl GlyphClosure for SubstitutionSubtables<'_> {
291 fn closure_glyphs(
292 &self,
293 ctx: &mut ClosureCtx,
294 lookup_list: &SubstitutionLookupList,
295 lookup_index: u16,
296 ) -> Result<(), ReadError> {
297 match self {
298 SubstitutionSubtables::Single(tables) => {
299 tables.closure_glyphs(ctx, lookup_list, lookup_index)
300 }
301 SubstitutionSubtables::Multiple(tables) => {
302 tables.closure_glyphs(ctx, lookup_list, lookup_index)
303 }
304 SubstitutionSubtables::Alternate(tables) => {
305 tables.closure_glyphs(ctx, lookup_list, lookup_index)
306 }
307 SubstitutionSubtables::Ligature(tables) => {
308 tables.closure_glyphs(ctx, lookup_list, lookup_index)
309 }
310 SubstitutionSubtables::Reverse(tables) => {
311 tables.closure_glyphs(ctx, lookup_list, lookup_index)
312 }
313 SubstitutionSubtables::Contextual(tables) => {
314 tables.closure_glyphs(ctx, lookup_list, lookup_index)
315 }
316 SubstitutionSubtables::ChainContextual(tables) => {
317 tables.closure_glyphs(ctx, lookup_list, lookup_index)
318 }
319 }
320 }
321
322 fn may_have_non_1to1(&self) -> Result<bool, ReadError> {
323 match self {
324 SubstitutionSubtables::Single(_) => Ok(false),
325 SubstitutionSubtables::Multiple(_) => Ok(true),
326 SubstitutionSubtables::Alternate(_) => Ok(false),
327 SubstitutionSubtables::Ligature(_) => Ok(true),
328 SubstitutionSubtables::Reverse(_) => Ok(false),
329 SubstitutionSubtables::Contextual(_) => Ok(true),
330 SubstitutionSubtables::ChainContextual(_) => Ok(true),
331 }
332 }
333}
334
335impl<'a, T: FontRead<'a> + GlyphClosure + 'a, Ext: ExtensionLookup<'a, T> + 'a> GlyphClosure
336 for Subtables<'a, T, Ext>
337{
338 fn closure_glyphs(
339 &self,
340 ctx: &mut ClosureCtx,
341 lookup_list: &SubstitutionLookupList,
342 lookup_index: u16,
343 ) -> Result<(), ReadError> {
344 self.iter()
345 .try_for_each(|t| t?.closure_glyphs(ctx, lookup_list, lookup_index))
346 }
347}
348
349impl GlyphClosure for SingleSubst<'_> {
350 fn closure_glyphs(
351 &self,
352 ctx: &mut ClosureCtx,
353 lookup_list: &SubstitutionLookupList,
354 lookup_index: u16,
355 ) -> Result<(), ReadError> {
356 match self {
357 SingleSubst::Format1(t) => t.closure_glyphs(ctx, lookup_list, lookup_index),
358 SingleSubst::Format2(t) => t.closure_glyphs(ctx, lookup_list, lookup_index),
359 }
360 }
361}
362
363impl GlyphClosure for SingleSubstFormat1<'_> {
365 fn closure_glyphs(
366 &self,
367 ctx: &mut ClosureCtx,
368 _lookup_list: &SubstitutionLookupList,
369 _lookup_index: u16,
370 ) -> Result<(), ReadError> {
371 let coverage = self.coverage()?;
372 let num_glyphs = coverage.population();
373 let mask = u16::MAX;
374 if num_glyphs >= mask as usize {
376 return Ok(());
377 }
378
379 let intersection = coverage.intersect_set(ctx.parent_active_glyphs());
380 if intersection.is_empty() {
381 return Ok(());
382 }
383
384 let d = self.delta_glyph_id() as i32;
387 let mask = mask as i32;
388 let min_before = intersection.first().unwrap().to_u32() as i32;
389 let max_before = intersection.last().unwrap().to_u32() as i32;
390 let min_after = (min_before + d) & mask;
391 let max_after = (max_before + d) & mask;
392
393 if intersection.len() == (max_before - min_before + 1) as u64
394 && ((min_before <= min_after && min_after <= max_before)
395 || (min_before <= max_after && max_after <= max_before))
396 {
397 return Ok(());
398 }
399
400 for g in intersection.iter() {
401 let new_g = (g.to_u32() as i32 + d) & mask;
402 ctx.add(GlyphId::from(new_g as u32));
403 }
404 Ok(())
405 }
406}
407
408impl GlyphClosure for SingleSubstFormat2<'_> {
409 fn closure_glyphs(
410 &self,
411 ctx: &mut ClosureCtx,
412 _lookup_list: &SubstitutionLookupList,
413 _lookup_index: u16,
414 ) -> Result<(), ReadError> {
415 let coverage = self.coverage()?;
416 let glyph_set = ctx.parent_active_glyphs();
417 let subs_glyphs = self.substitute_glyph_ids();
418
419 let new_glyphs: Vec<GlyphId> = if self.glyph_count() as u64 > glyph_set.len() {
420 glyph_set
421 .iter()
422 .filter_map(|g| coverage.get(g))
423 .filter_map(|idx| {
424 subs_glyphs
425 .get(idx as usize)
426 .map(|new_g| GlyphId::from(new_g.get()))
427 })
428 .collect()
429 } else {
430 coverage
431 .iter()
432 .zip(subs_glyphs)
433 .filter(|&(g, _)| glyph_set.contains(GlyphId::from(g)))
434 .map(|(_, &new_g)| GlyphId::from(new_g.get()))
435 .collect()
436 };
437 ctx.add_glyphs(new_glyphs);
438 Ok(())
439 }
440}
441
442impl GlyphClosure for MultipleSubstFormat1<'_> {
443 fn closure_glyphs(
444 &self,
445 ctx: &mut ClosureCtx,
446 _lookup_list: &SubstitutionLookupList,
447 _lookup_index: u16,
448 ) -> Result<(), ReadError> {
449 let coverage = self.coverage()?;
450 let glyph_set = ctx.parent_active_glyphs();
451 let sequences = self.sequences();
452
453 let new_glyphs: Vec<GlyphId> = if self.sequence_count() as u64 > glyph_set.len() {
454 glyph_set
455 .iter()
456 .filter_map(|g| coverage.get(g))
457 .filter_map(|idx| sequences.get(idx as usize).ok())
458 .flat_map(|seq| {
459 seq.substitute_glyph_ids()
460 .iter()
461 .map(|new_g| GlyphId::from(new_g.get()))
462 })
463 .collect()
464 } else {
465 coverage
466 .iter()
467 .zip(sequences.iter())
468 .filter_map(|(g, seq)| {
469 glyph_set
470 .contains(GlyphId::from(g))
471 .then(|| seq.ok())
472 .flatten()
473 })
474 .flat_map(|seq| {
475 seq.substitute_glyph_ids()
476 .iter()
477 .map(|new_g| GlyphId::from(new_g.get()))
478 })
479 .collect()
480 };
481
482 ctx.add_glyphs(new_glyphs);
483 Ok(())
484 }
485}
486
487impl GlyphClosure for AlternateSubstFormat1<'_> {
488 fn closure_glyphs(
489 &self,
490 ctx: &mut ClosureCtx,
491 _lookup_list: &SubstitutionLookupList,
492 _lookup_index: u16,
493 ) -> Result<(), ReadError> {
494 let coverage = self.coverage()?;
495 let glyph_set = ctx.parent_active_glyphs();
496 let alts = self.alternate_sets();
497
498 let new_glyphs: Vec<GlyphId> = if self.alternate_set_count() as u64 > glyph_set.len() {
499 glyph_set
500 .iter()
501 .filter_map(|g| coverage.get(g))
502 .filter_map(|idx| alts.get(idx as usize).ok())
503 .flat_map(|alt_set| {
504 alt_set
505 .alternate_glyph_ids()
506 .iter()
507 .map(|new_g| GlyphId::from(new_g.get()))
508 })
509 .collect()
510 } else {
511 coverage
512 .iter()
513 .zip(alts.iter())
514 .filter_map(|(g, alt_set)| {
515 glyph_set
516 .contains(GlyphId::from(g))
517 .then(|| alt_set.ok())
518 .flatten()
519 })
520 .flat_map(|alt_set| {
521 alt_set
522 .alternate_glyph_ids()
523 .iter()
524 .map(|new_g| GlyphId::from(new_g.get()))
525 })
526 .collect()
527 };
528
529 ctx.add_glyphs(new_glyphs);
530 Ok(())
531 }
532}
533
534impl GlyphClosure for LigatureSubstFormat1<'_> {
535 fn closure_glyphs(
536 &self,
537 ctx: &mut ClosureCtx,
538 _lookup_list: &SubstitutionLookupList,
539 _lookup_index: u16,
540 ) -> Result<(), ReadError> {
541 let coverage = self.coverage()?;
542 let ligs = self.ligature_sets();
543 let lig_set_idxes: Vec<usize> =
544 if self.ligature_set_count() as u64 > ctx.parent_active_glyphs().len() {
545 ctx.parent_active_glyphs()
546 .iter()
547 .filter_map(|g| coverage.get(g))
548 .map(|idx| idx as usize)
549 .collect()
550 } else {
551 coverage
552 .iter()
553 .enumerate()
554 .filter(|&(_idx, g)| ctx.parent_active_glyphs().contains(GlyphId::from(g)))
555 .map(|(idx, _)| idx)
556 .collect()
557 };
558
559 for idx in lig_set_idxes {
560 let lig_set = ligs.get(idx)?;
561 for lig in lig_set.ligatures().iter() {
562 let lig = lig?;
563 if lig.intersects(ctx.glyphs())? {
564 ctx.add(GlyphId::from(lig.ligature_glyph()));
565 }
566 }
567 }
568 Ok(())
569 }
570}
571
572impl GlyphClosure for ReverseChainSingleSubstFormat1<'_> {
573 fn closure_glyphs(
574 &self,
575 ctx: &mut ClosureCtx,
576 _lookup_list: &SubstitutionLookupList,
577 _lookup_index: u16,
578 ) -> Result<(), ReadError> {
579 if !self.intersects(ctx.glyphs())? {
580 return Ok(());
581 }
582
583 let coverage = self.coverage()?;
584 let glyph_set = ctx.parent_active_glyphs();
585 let idxes: Vec<usize> = if self.glyph_count() as u64 > glyph_set.len() {
586 glyph_set
587 .iter()
588 .filter_map(|g| coverage.get(g))
589 .map(|idx| idx as usize)
590 .collect()
591 } else {
592 coverage
593 .iter()
594 .enumerate()
595 .filter(|&(_idx, g)| glyph_set.contains(GlyphId::from(g)))
596 .map(|(idx, _)| idx)
597 .collect()
598 };
599
600 let sub_glyphs = self.substitute_glyph_ids();
601 for i in idxes {
602 let Some(g) = sub_glyphs.get(i) else {
603 continue;
604 };
605 ctx.add(GlyphId::from(g.get()));
606 }
607
608 Ok(())
609 }
610}
611
612impl GlyphClosure for SequenceContext<'_> {
613 fn closure_glyphs(
614 &self,
615 ctx: &mut ClosureCtx,
616 lookup_list: &SubstitutionLookupList,
617 lookup_index: u16,
618 ) -> Result<(), ReadError> {
619 match self {
620 Self::Format1(table) => {
621 ContextFormat1::Plain(table.clone()).closure_glyphs(ctx, lookup_list, lookup_index)
622 }
623 Self::Format2(table) => {
624 ContextFormat2::Plain(table.clone()).closure_glyphs(ctx, lookup_list, lookup_index)
625 }
626 Self::Format3(table) => {
627 ContextFormat3::Plain(table.clone()).closure_glyphs(ctx, lookup_list, lookup_index)
628 }
629 }
630 }
631}
632
633impl GlyphClosure for ChainedSequenceContext<'_> {
634 fn closure_glyphs(
635 &self,
636 ctx: &mut ClosureCtx,
637 lookup_list: &SubstitutionLookupList,
638 lookup_index: u16,
639 ) -> Result<(), ReadError> {
640 match self {
641 Self::Format1(table) => {
642 ContextFormat1::Chain(table.clone()).closure_glyphs(ctx, lookup_list, lookup_index)
643 }
644 Self::Format2(table) => {
645 ContextFormat2::Chain(table.clone()).closure_glyphs(ctx, lookup_list, lookup_index)
646 }
647 Self::Format3(table) => {
648 ContextFormat3::Chain(table.clone()).closure_glyphs(ctx, lookup_list, lookup_index)
649 }
650 }
651 }
652}
653
654impl GlyphClosure for ContextFormat1<'_> {
656 fn closure_glyphs(
657 &self,
658 ctx: &mut ClosureCtx,
659 lookup_list: &SubstitutionLookupList,
660 _lookup_index: u16,
661 ) -> Result<(), ReadError> {
662 let coverage = self.coverage()?;
663 let cov_active_glyphs = coverage.intersect_set(ctx.parent_active_glyphs());
664 if cov_active_glyphs.is_empty() {
665 return Ok(());
666 }
667
668 for (gid, rule_set) in coverage
669 .iter()
670 .zip(self.rule_sets())
671 .filter_map(|(g, rule_set)| {
672 rule_set
673 .filter(|_| cov_active_glyphs.contains(GlyphId::from(g)))
674 .map(|rs| (g, rs))
675 })
676 {
677 if ctx.lookup_limit_exceed() {
678 return Ok(());
679 }
680
681 for rule in rule_set?.rules() {
682 if ctx.lookup_limit_exceed() {
683 return Ok(());
684 }
685 let rule = rule?;
686 if !rule.intersects(ctx.glyphs())? {
687 continue;
688 }
689
690 let input_seq = rule.input_sequence();
691 let input_count = input_seq.len() + 1;
692 let mut seen_sequence_indices = IntSet::new();
698
699 for lookup_record in rule.lookup_records() {
700 let sequence_idx = lookup_record.sequence_index();
701 if sequence_idx as usize >= input_count {
702 continue;
703 }
704
705 let mut active_glyphs = IntSet::empty();
706 if !seen_sequence_indices.insert(sequence_idx) {
707 active_glyphs.extend(ctx.glyphs().iter());
710 } else if sequence_idx == 0 {
711 active_glyphs.insert(GlyphId::from(gid));
712 } else {
713 let g = input_seq[sequence_idx as usize - 1].get();
714 active_glyphs.insert(GlyphId::from(g));
715 };
716
717 let lookup_index = lookup_record.lookup_list_index();
718 let lookup = lookup_list.lookups().get(lookup_index as usize)?;
719 if lookup.may_have_non_1to1()? {
720 seen_sequence_indices.insert_range(sequence_idx..=input_count as u16);
721 }
722 ctx.recurse(lookup_list, &lookup, lookup_index, active_glyphs)?;
723 }
724 }
725 }
726 Ok(())
727 }
728}
729
730impl GlyphClosure for ContextFormat2<'_> {
732 fn closure_glyphs(
733 &self,
734 ctx: &mut ClosureCtx,
735 lookup_list: &SubstitutionLookupList,
736 _lookup_index: u16,
737 ) -> Result<(), ReadError> {
738 let coverage = self.coverage()?;
739 let cov_active_glyphs = coverage.intersect_set(ctx.parent_active_glyphs());
740 if cov_active_glyphs.is_empty() {
741 return Ok(());
742 }
743
744 let input_class_def = self.input_class_def()?;
745 let coverage_glyph_classes = input_class_def.intersect_classes(&cov_active_glyphs);
746
747 let input_glyph_classes = input_class_def.intersect_classes(ctx.glyphs());
748 let backtrack_classes = match self {
749 Self::Plain(_) => IntSet::empty(),
750 Self::Chain(table) => table.backtrack_class_def()?.intersect_classes(ctx.glyphs()),
751 };
752 let lookahead_classes = match self {
753 Self::Plain(_) => IntSet::empty(),
754 Self::Chain(table) => table.lookahead_class_def()?.intersect_classes(ctx.glyphs()),
755 };
756
757 for (i, rule_set) in self
758 .rule_sets()
759 .enumerate()
760 .filter_map(|(class, rs)| rs.map(|rs| (class as u16, rs)))
761 .filter(|&(class, _)| coverage_glyph_classes.contains(class))
762 {
763 if ctx.lookup_limit_exceed() {
764 return Ok(());
765 }
766
767 for rule in rule_set?.rules() {
768 if ctx.lookup_limit_exceed() {
769 return Ok(());
770 }
771 let rule = rule?;
772 if !rule.intersects(&input_glyph_classes, &backtrack_classes, &lookahead_classes) {
773 continue;
774 }
775
776 let input_seq = rule.input_sequence();
777 let input_count = input_seq.len() + 1;
778
779 let mut seen_sequence_indices = IntSet::new();
780
781 for lookup_record in rule.lookup_records() {
782 let sequence_idx = lookup_record.sequence_index();
783 if sequence_idx as usize >= input_count {
784 continue;
785 }
786
787 let active_glyphs = if !seen_sequence_indices.insert(sequence_idx) {
788 ctx.glyphs().clone()
789 } else if sequence_idx == 0 {
790 input_class_def.intersected_class_glyphs(ctx.parent_active_glyphs(), i)
791 } else {
792 let c = input_seq[sequence_idx as usize - 1].get();
793 input_class_def.intersected_class_glyphs(ctx.parent_active_glyphs(), c)
794 };
795
796 let lookup_index = lookup_record.lookup_list_index();
797 let lookup = lookup_list.lookups().get(lookup_index as usize)?;
798 if lookup.may_have_non_1to1()? {
799 seen_sequence_indices.insert_range(sequence_idx..=input_count as u16);
800 }
801 ctx.recurse(lookup_list, &lookup, lookup_index, active_glyphs)?;
802 }
803 }
804 }
805 Ok(())
806 }
807}
808
809impl GlyphClosure for ContextFormat3<'_> {
810 fn closure_glyphs(
811 &self,
812 ctx: &mut ClosureCtx,
813 lookup_list: &SubstitutionLookupList,
814 _lookup_index: u16,
815 ) -> Result<(), ReadError> {
816 if !self.intersects(ctx.glyphs())? {
817 return Ok(());
818 }
819
820 let mut seen_sequence_indices = IntSet::new();
821 let input_coverages = self.coverages();
822 let input_count = input_coverages.len();
823 for record in self.lookup_records() {
824 let seq_idx = record.sequence_index();
825 if seq_idx as usize >= input_count {
826 continue;
827 }
828
829 let active_glyphs = if !seen_sequence_indices.insert(seq_idx) {
830 ctx.glyphs().clone()
831 } else if seq_idx == 0 {
832 let cov = input_coverages.get(0)?;
833 cov.intersect_set(ctx.parent_active_glyphs())
834 } else {
835 let cov = input_coverages.get(seq_idx as usize)?;
836 cov.intersect_set(ctx.glyphs())
837 };
838
839 let lookup_index = record.lookup_list_index();
840 let lookup = lookup_list.lookups().get(lookup_index as usize)?;
841 if lookup.may_have_non_1to1()? {
842 seen_sequence_indices.insert_range(seq_idx..=input_count as u16);
843 }
844 ctx.recurse(lookup_list, &lookup, lookup_index, active_glyphs)?;
845 }
846 Ok(())
847 }
848}
849
850impl SubstitutionLookupList<'_> {
851 pub fn closure_lookups(
852 &self,
853 glyph_set: &IntSet<GlyphId>,
854 lookup_indices: &mut IntSet<u16>,
855 ) -> Result<(), ReadError> {
856 let lookup_list = LayoutLookupList::Gsub(self);
857 let mut c = LookupClosureCtx::new(glyph_set, &lookup_list);
858
859 let lookups = self.lookups();
860 for idx in lookup_indices.iter() {
861 let lookup = lookups.get(idx as usize)?;
862 lookup.closure_lookups(&mut c, idx)?;
863 }
864
865 lookup_indices.union(c.visited_lookups());
866 lookup_indices.subtract(c.inactive_lookups());
867 Ok(())
868 }
869}
870
871impl LookupClosure for SubstitutionLookup<'_> {
872 fn closure_lookups(
873 &self,
874 c: &mut LookupClosureCtx,
875 lookup_index: u16,
876 ) -> Result<(), ReadError> {
877 if !c.should_visit_lookup(lookup_index) {
878 return Ok(());
879 }
880
881 if !self.intersects(c.glyphs())? {
882 c.set_lookup_inactive(lookup_index);
883 return Ok(());
884 }
885
886 self.subtables()?.closure_lookups(c, lookup_index)
887 }
888}
889
890impl Intersect for SubstitutionLookup<'_> {
891 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
892 self.subtables()?.intersects(glyph_set)
893 }
894}
895
896impl LookupClosure for SubstitutionSubtables<'_> {
897 fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
898 match self {
899 SubstitutionSubtables::ChainContextual(subtables) => subtables.closure_lookups(c, arg),
900 SubstitutionSubtables::Contextual(subtables) => subtables.closure_lookups(c, arg),
901 _ => Ok(()),
902 }
903 }
904}
905
906impl Intersect for SubstitutionSubtables<'_> {
907 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
908 match self {
909 SubstitutionSubtables::Single(subtables) => subtables.intersects(glyph_set),
910 SubstitutionSubtables::Multiple(subtables) => subtables.intersects(glyph_set),
911 SubstitutionSubtables::Alternate(subtables) => subtables.intersects(glyph_set),
912 SubstitutionSubtables::Ligature(subtables) => subtables.intersects(glyph_set),
913 SubstitutionSubtables::Contextual(subtables) => subtables.intersects(glyph_set),
914 SubstitutionSubtables::ChainContextual(subtables) => subtables.intersects(glyph_set),
915 SubstitutionSubtables::Reverse(subtables) => subtables.intersects(glyph_set),
916 }
917 }
918}
919
920impl Intersect for SingleSubst<'_> {
921 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
922 match self {
923 Self::Format1(item) => item.intersects(glyph_set),
924 Self::Format2(item) => item.intersects(glyph_set),
925 }
926 }
927}
928
929impl Intersect for SingleSubstFormat1<'_> {
930 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
931 Ok(self.coverage()?.intersects(glyph_set))
932 }
933}
934
935impl Intersect for SingleSubstFormat2<'_> {
936 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
937 Ok(self.coverage()?.intersects(glyph_set))
938 }
939}
940
941impl Intersect for MultipleSubstFormat1<'_> {
942 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
943 Ok(self.coverage()?.intersects(glyph_set))
944 }
945}
946
947impl Intersect for AlternateSubstFormat1<'_> {
948 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
949 Ok(self.coverage()?.intersects(glyph_set))
950 }
951}
952
953impl Intersect for LigatureSubstFormat1<'_> {
954 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
955 let coverage = self.coverage()?;
956 let lig_sets = self.ligature_sets();
957 for lig_set in coverage
958 .iter()
959 .zip(lig_sets.iter())
960 .filter_map(|(g, lig_set)| glyph_set.contains(GlyphId::from(g)).then_some(lig_set))
961 {
962 if lig_set?.intersects(glyph_set)? {
963 return Ok(true);
964 }
965 }
966 Ok(false)
967 }
968}
969
970impl Intersect for LigatureSet<'_> {
971 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
972 let ligs = self.ligatures();
973 for lig in ligs.iter() {
974 if lig?.intersects(glyph_set)? {
975 return Ok(true);
976 }
977 }
978 Ok(false)
979 }
980}
981
982impl Intersect for Ligature<'_> {
983 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
984 let ret = self
985 .component_glyph_ids()
986 .iter()
987 .all(|g| glyph_set.contains(GlyphId::from(g.get())));
988 Ok(ret)
989 }
990}
991
992impl Intersect for ReverseChainSingleSubstFormat1<'_> {
993 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
994 if !self.coverage()?.intersects(glyph_set) {
995 return Ok(false);
996 }
997
998 for coverage in self.backtrack_coverages().iter() {
999 if !coverage?.intersects(glyph_set) {
1000 return Ok(false);
1001 }
1002 }
1003
1004 for coverage in self.lookahead_coverages().iter() {
1005 if !coverage?.intersects(glyph_set) {
1006 return Ok(false);
1007 }
1008 }
1009 Ok(true)
1010 }
1011}
1012
1013#[cfg(test)]
1014mod tests {
1015 use std::collections::{HashMap, HashSet};
1016
1017 use crate::{FontRef, TableProvider};
1018
1019 use super::*;
1020 use font_test_data::closure as test_data;
1021
1022 struct GlyphMap {
1023 to_gid: HashMap<&'static str, GlyphId>,
1024 from_gid: HashMap<GlyphId, &'static str>,
1025 }
1026
1027 impl GlyphMap {
1028 fn new(raw_order: &'static str) -> GlyphMap {
1029 let to_gid: HashMap<_, _> = raw_order
1030 .split('\n')
1031 .map(|line| line.trim())
1032 .filter(|line| !(line.starts_with('#') || line.is_empty()))
1033 .enumerate()
1034 .map(|(gid, name)| (name, GlyphId::new(gid.try_into().unwrap())))
1035 .collect();
1036 let from_gid = to_gid.iter().map(|(name, gid)| (*gid, *name)).collect();
1037 GlyphMap { from_gid, to_gid }
1038 }
1039
1040 fn get_gid(&self, name: &str) -> Option<GlyphId> {
1041 self.to_gid.get(name).copied()
1042 }
1043
1044 fn get_name(&self, gid: GlyphId) -> Option<&str> {
1045 self.from_gid.get(&gid).copied()
1046 }
1047 }
1048
1049 fn get_gsub(test_data: &'static [u8]) -> Gsub<'static> {
1050 let font = FontRef::new(test_data).unwrap();
1051 font.gsub().unwrap()
1052 }
1053
1054 fn compute_closure(gsub: &Gsub, glyph_map: &GlyphMap, input: &[&str]) -> IntSet<GlyphId> {
1055 let lookup_indices = gsub.collect_lookups(&IntSet::all()).unwrap();
1056 let mut input_glyphs = input
1057 .iter()
1058 .map(|name| glyph_map.get_gid(name).unwrap())
1059 .collect();
1060 gsub.closure_glyphs(&lookup_indices, &mut input_glyphs)
1061 .unwrap();
1062 input_glyphs
1063 }
1064
1065 macro_rules! assert_closure_result {
1067 ($glyph_map:expr, $result:expr, $expected:expr) => {
1068 let result = $result
1069 .iter()
1070 .map(|gid| $glyph_map.get_name(gid).unwrap())
1071 .collect::<HashSet<_>>();
1072 let expected = $expected.iter().copied().collect::<HashSet<_>>();
1073 if expected != result {
1074 let in_output = result.difference(&expected).collect::<Vec<_>>();
1075 let in_expected = expected.difference(&result).collect::<Vec<_>>();
1076 let mut msg = format!("Closure output does not match\n");
1077 if !in_expected.is_empty() {
1078 msg.push_str(format!("missing {in_expected:?}\n").as_str());
1079 }
1080 if !in_output.is_empty() {
1081 msg.push_str(format!("unexpected {in_output:?}").as_str());
1082 }
1083 panic!("{msg}")
1084 }
1085 };
1086 }
1087
1088 #[test]
1089 fn smoke_test() {
1090 let gsub = get_gsub(test_data::SIMPLE);
1093 let glyph_map = GlyphMap::new(test_data::SIMPLE_GLYPHS);
1094 let result = compute_closure(&gsub, &glyph_map, &["a"]);
1095
1096 assert_closure_result!(
1097 glyph_map,
1098 result,
1099 &["a", "A", "b", "c", "d", "a_a", "a.1", "a.2", "a.3"]
1100 );
1101 }
1102
1103 #[test]
1104 fn recursive() {
1105 let gsub = get_gsub(test_data::RECURSIVE);
1110 let glyph_map = GlyphMap::new(test_data::RECURSIVE_GLYPHS);
1111 let result = compute_closure(&gsub, &glyph_map, &["a"]);
1112 assert_closure_result!(glyph_map, result, &["a", "b", "c", "d"]);
1113 }
1114
1115 #[test]
1116 fn contextual_lookups_nop() {
1117 let gsub = get_gsub(test_data::CONTEXTUAL);
1118 let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
1119
1120 let nop = compute_closure(&gsub, &glyph_map, &["three", "four", "e", "f"]);
1122 assert_closure_result!(glyph_map, nop, &["three", "four", "e", "f"]);
1123 }
1124
1125 #[test]
1126 fn contextual_lookups_chained_f1() {
1127 let gsub = get_gsub(test_data::CONTEXTUAL);
1128 let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
1129 let gsub6f1 = compute_closure(
1130 &gsub,
1131 &glyph_map,
1132 &["one", "two", "three", "four", "five", "six", "seven"],
1133 );
1134 assert_closure_result!(
1135 glyph_map,
1136 gsub6f1,
1137 &["one", "two", "three", "four", "five", "six", "seven", "X", "Y"]
1138 );
1139 }
1140
1141 #[test]
1142 fn contextual_lookups_chained_f3() {
1143 let gsub = get_gsub(test_data::CONTEXTUAL);
1144 let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
1145 let gsub6f3 = compute_closure(&gsub, &glyph_map, &["space", "e"]);
1146 assert_closure_result!(glyph_map, gsub6f3, &["space", "e", "e.2"]);
1147
1148 let gsub5f3 = compute_closure(&gsub, &glyph_map, &["f", "g"]);
1149 assert_closure_result!(glyph_map, gsub5f3, &["f", "g", "f.2"]);
1150 }
1151
1152 #[test]
1153 fn contextual_plain_f1() {
1154 let gsub = get_gsub(test_data::CONTEXTUAL);
1155 let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
1156 let gsub5f1 = compute_closure(&gsub, &glyph_map, &["a", "b"]);
1157 assert_closure_result!(glyph_map, gsub5f1, &["a", "b", "a_b"]);
1158 }
1159
1160 #[test]
1161 fn contextual_plain_f3() {
1162 let gsub = get_gsub(test_data::CONTEXTUAL);
1163 let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
1164 let gsub5f3 = compute_closure(&gsub, &glyph_map, &["f", "g"]);
1165 assert_closure_result!(glyph_map, gsub5f3, &["f", "g", "f.2"]);
1166 }
1167
1168 #[test]
1169 fn recursive_context() {
1170 let gsub = get_gsub(test_data::RECURSIVE_CONTEXTUAL);
1171 let glyph_map = GlyphMap::new(test_data::RECURSIVE_CONTEXTUAL_GLYPHS);
1172
1173 let nop = compute_closure(&gsub, &glyph_map, &["b", "B"]);
1174 assert_closure_result!(glyph_map, nop, &["b", "B"]);
1175
1176 let full = compute_closure(&gsub, &glyph_map, &["a", "b", "c"]);
1177 assert_closure_result!(glyph_map, full, &["a", "b", "c", "B", "B.2", "B.3"]);
1178
1179 let intermediate = compute_closure(&gsub, &glyph_map, &["a", "B.2"]);
1180 assert_closure_result!(glyph_map, intermediate, &["a", "B.2", "B.3"]);
1181 }
1182
1183 #[test]
1184 fn feature_variations() {
1185 let gsub = get_gsub(test_data::VARIATIONS_CLOSURE);
1186 let glyph_map = GlyphMap::new(test_data::VARIATIONS_GLYPHS);
1187
1188 let input = compute_closure(&gsub, &glyph_map, &["a"]);
1189 assert_closure_result!(glyph_map, input, &["a", "b", "c"]);
1190 }
1191
1192 #[test]
1193 fn chain_context_format3() {
1194 let gsub = get_gsub(test_data::CHAIN_CONTEXT_FORMAT3_BITS);
1195 let glyph_map = GlyphMap::new(test_data::CHAIN_CONTEXT_FORMAT3_BITS_GLYPHS);
1196
1197 let nop = compute_closure(&gsub, &glyph_map, &["c", "z"]);
1198 assert_closure_result!(glyph_map, nop, &["c", "z"]);
1199
1200 let full = compute_closure(&gsub, &glyph_map, &["a", "b", "c", "z"]);
1201 assert_closure_result!(glyph_map, full, &["a", "b", "c", "z", "A", "B"]);
1202 }
1203
1204 #[test]
1205 fn closure_ignore_unreachable_glyphs() {
1206 let font = FontRef::new(font_test_data::closure::CONTEXT_ONLY_REACHABLE).unwrap();
1207 let gsub = font.gsub().unwrap();
1208 let glyph_map = GlyphMap::new(test_data::CONTEXT_ONLY_REACHABLE_GLYPHS);
1209 let result = compute_closure(&gsub, &glyph_map, &["a", "b", "c", "d", "e", "f", "period"]);
1210 assert_closure_result!(
1211 glyph_map,
1212 result,
1213 &["a", "b", "c", "d", "e", "f", "period", "A", "B", "C"]
1214 );
1215 }
1216
1217 #[test]
1218 fn cyclical_context() {
1219 let gsub = get_gsub(test_data::CYCLIC_CONTEXTUAL);
1220 let glyph_map = GlyphMap::new(test_data::RECURSIVE_CONTEXTUAL_GLYPHS);
1221 let nop = compute_closure(&gsub, &glyph_map, &["a", "b", "c"]);
1223 assert_closure_result!(glyph_map, nop, &["a", "b", "c"]);
1224 }
1225
1226 #[test]
1227 fn collect_all_features() {
1228 let font = FontRef::new(font_test_data::closure::CONTEXTUAL).unwrap();
1229 let gsub = font.gsub().unwrap();
1230 let ret = gsub
1231 .collect_features(&IntSet::all(), &IntSet::all(), &IntSet::all())
1232 .unwrap();
1233 assert_eq!(ret.len(), 2);
1234 assert!(ret.contains(0));
1235 assert!(ret.contains(1));
1236 }
1237
1238 #[test]
1239 fn collect_all_features_with_feature_filter() {
1240 let font = FontRef::new(font_test_data::closure::CONTEXTUAL).unwrap();
1241 let gsub = font.gsub().unwrap();
1242
1243 let mut feature_tags = IntSet::empty();
1244 feature_tags.insert(Tag::new(b"SUB5"));
1245
1246 let ret = gsub
1247 .collect_features(&IntSet::all(), &IntSet::all(), &feature_tags)
1248 .unwrap();
1249 assert_eq!(ret.len(), 1);
1250 assert!(ret.contains(0));
1251 }
1252
1253 #[test]
1254 fn collect_all_features_with_script_filter() {
1255 let font = FontRef::new(font_test_data::closure::CONTEXTUAL).unwrap();
1256 let gsub = font.gsub().unwrap();
1257
1258 let mut script_tags = IntSet::empty();
1259 script_tags.insert(Tag::new(b"LATN"));
1260
1261 let ret = gsub
1262 .collect_features(&script_tags, &IntSet::all(), &IntSet::all())
1263 .unwrap();
1264 assert!(ret.is_empty());
1265 }
1266}