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