1use font_types::{GlyphId, GlyphId16};
7
8use crate::{
9 collections::IntSet,
10 tables::layout::{ExtensionLookup, Subtables},
11 FontRead, ReadError, Tag,
12};
13
14use super::{
15 AlternateSubstFormat1, ChainedSequenceContext, ClassDef, CoverageTable, Gsub, Ligature,
16 LigatureSet, LigatureSubstFormat1, MultipleSubstFormat1, ReverseChainSingleSubstFormat1,
17 SequenceContext, SingleSubst, SingleSubstFormat1, SingleSubstFormat2, SubstitutionLookup,
18 SubstitutionLookupList, SubstitutionSubtables,
19};
20
21#[cfg(feature = "std")]
22use crate::tables::layout::{
23 ContextFormat1, ContextFormat2, ContextFormat3, LookupClosure, LookupClosureCtx,
24};
25
26mod ctx {
29 use std::collections::HashMap;
30
31 use types::GlyphId16;
32
33 use crate::{collections::IntSet, tables::gsub::SubstitutionLookup};
34
35 use super::GlyphClosure as _;
36
37 pub(super) struct ClosureCtx<'a> {
38 glyphs: &'a mut IntSet<GlyphId16>,
40 cur_glyphs: Option<IntSet<GlyphId16>>,
43 finished_lookups: HashMap<u16, (u64, Option<IntSet<GlyphId16>>)>,
44 contextual_lookup_todos: Vec<super::ContextualLookupRef>,
51 }
52
53 impl<'a> ClosureCtx<'a> {
54 pub(super) fn new(glyphs: &'a mut IntSet<GlyphId16>) -> Self {
55 Self {
56 glyphs,
57 cur_glyphs: Default::default(),
58 contextual_lookup_todos: Default::default(),
59 finished_lookups: Default::default(),
60 }
61 }
62
63 pub(super) fn current_glyphs(&self) -> &IntSet<GlyphId16> {
64 self.cur_glyphs.as_ref().unwrap_or(self.glyphs)
65 }
66
67 pub(super) fn glyphs(&self) -> &IntSet<GlyphId16> {
68 self.glyphs
69 }
70
71 pub(super) fn add_glyph(&mut self, gid: GlyphId16) {
72 self.glyphs.insert(gid);
73 }
74
75 pub(super) fn extend_glyphs(&mut self, iter: impl IntoIterator<Item = GlyphId16>) {
76 self.glyphs.extend(iter)
77 }
78
79 pub(super) fn add_todo(
80 &mut self,
81 lookup_id: u16,
82 active_glyphs: Option<IntSet<GlyphId16>>,
83 ) {
84 self.contextual_lookup_todos
85 .push(super::ContextualLookupRef {
86 lookup_id,
87 active_glyphs,
88 })
89 }
90
91 pub(super) fn pop_a_todo(&mut self) -> Option<super::ContextualLookupRef> {
92 self.contextual_lookup_todos.pop()
93 }
94
95 pub(super) fn closure_glyphs(
96 &mut self,
97 lookup: SubstitutionLookup,
98 lookup_id: u16,
99 current_glyphs: Option<IntSet<GlyphId16>>,
100 ) -> Result<(), crate::ReadError> {
101 if self.needs_to_do_lookup(lookup_id, current_glyphs.as_ref()) {
102 self.cur_glyphs = current_glyphs;
103 lookup.add_reachable_glyphs(self)?;
104 self.cur_glyphs = None;
105 }
106 Ok(())
107 }
108
109 fn needs_to_do_lookup(
112 &mut self,
113 id: u16,
114 current_glyphs: Option<&IntSet<GlyphId16>>,
115 ) -> bool {
116 let (count, covered) = self.finished_lookups.entry(id).or_insert((0, None));
117 if *count != self.glyphs.len() {
118 *count = self.glyphs.len();
119 *covered = Some(IntSet::new());
120 }
121 if current_glyphs.unwrap_or(self.glyphs).iter().all(|gid| {
123 covered
124 .as_ref()
125 .map(|cov| cov.contains(gid))
126 .unwrap_or(false)
128 }) {
129 return false;
130 }
131 covered
132 .get_or_insert_with(Default::default)
133 .extend(current_glyphs.unwrap_or(self.glyphs).iter());
134 true
135 }
136 }
137}
138
139use ctx::ClosureCtx;
140
141#[derive(Debug)]
143struct ContextualLookupRef {
144 lookup_id: u16,
145 active_glyphs: Option<IntSet<GlyphId16>>,
147}
148
149trait GlyphClosure {
151 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError>;
153}
154
155impl Gsub<'_> {
156 pub fn closure_glyphs(
158 &self,
159 mut glyphs: IntSet<GlyphId16>,
160 ) -> Result<IntSet<GlyphId16>, ReadError> {
161 let mut ctx = ClosureCtx::new(&mut glyphs);
165
166 let reachable_lookups = self.find_reachable_lookups()?;
167 let mut prev_lookup_count = 0;
168 let mut prev_glyph_count = 0;
169 let mut new_glyph_count = ctx.glyphs().len();
170 let mut new_lookup_count = reachable_lookups.len();
171
172 while (prev_glyph_count, prev_lookup_count) != (new_glyph_count, new_lookup_count) {
173 (prev_glyph_count, prev_lookup_count) = (new_glyph_count, new_lookup_count);
174
175 self.closure_glyphs_once(&mut ctx, &reachable_lookups)?;
178
179 new_lookup_count = reachable_lookups.len();
180 new_glyph_count = ctx.glyphs().len();
181 }
182
183 Ok(glyphs)
184 }
185
186 fn closure_glyphs_once(
187 &self,
188 ctx: &mut ClosureCtx,
189 lookups_to_use: &IntSet<u16>,
190 ) -> Result<(), ReadError> {
191 let lookup_list = self.lookup_list()?;
192 for idx in lookups_to_use.iter() {
193 let lookup = lookup_list.lookups().get(idx as usize)?;
194 ctx.closure_glyphs(lookup, idx, None)?;
195 }
196 while let Some(todo) = ctx.pop_a_todo() {
198 let lookup = lookup_list.lookups().get(todo.lookup_id as _)?;
199 ctx.closure_glyphs(lookup, todo.lookup_id, todo.active_glyphs)?;
200 }
201 Ok(())
202 }
203
204 fn find_reachable_lookups(&self) -> Result<IntSet<u16>, ReadError> {
205 let feature_list = self.feature_list()?;
206 let mut lookup_ids = IntSet::new();
207
208 let feature_variations = self
209 .feature_variations()
210 .transpose()?
211 .map(|vars| {
212 let data = vars.offset_data();
213 vars.feature_variation_records()
214 .iter()
215 .filter_map(move |rec| {
216 rec.feature_table_substitution(data)
217 .transpose()
218 .ok()
219 .flatten()
220 })
221 .flat_map(|subs| {
222 subs.substitutions()
223 .iter()
224 .map(move |sub| sub.alternate_feature(subs.offset_data()))
225 })
226 })
227 .into_iter()
228 .flatten();
229 for feature in feature_list
230 .feature_records()
231 .iter()
232 .map(|rec| rec.feature(feature_list.offset_data()))
233 .chain(feature_variations)
234 {
235 lookup_ids.extend(feature?.lookup_list_indices().iter().map(|idx| idx.get()));
236 }
237 Ok(lookup_ids)
238 }
239
240 pub fn collect_features(
242 &self,
243 scripts: &IntSet<Tag>,
244 languages: &IntSet<Tag>,
245 features: &IntSet<Tag>,
246 ) -> Result<IntSet<u16>, ReadError> {
247 let feature_list = self.feature_list()?;
248 let script_list = self.script_list()?;
249 let head_ptr = self.offset_data().as_bytes().as_ptr() as usize;
250 script_list.collect_features(head_ptr, &feature_list, scripts, languages, features)
251 }
252}
253
254impl GlyphClosure for SubstitutionLookup<'_> {
255 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError> {
256 self.subtables()?.add_reachable_glyphs(ctx)
257 }
258}
259
260impl GlyphClosure for SubstitutionSubtables<'_> {
261 fn add_reachable_glyphs(&self, glyphs: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
262 match self {
263 SubstitutionSubtables::Single(tables) => tables.add_reachable_glyphs(glyphs),
264 SubstitutionSubtables::Multiple(tables) => tables.add_reachable_glyphs(glyphs),
265 SubstitutionSubtables::Alternate(tables) => tables.add_reachable_glyphs(glyphs),
266 SubstitutionSubtables::Ligature(tables) => tables.add_reachable_glyphs(glyphs),
267 SubstitutionSubtables::Reverse(tables) => tables.add_reachable_glyphs(glyphs),
268 SubstitutionSubtables::Contextual(tables) => tables.add_reachable_glyphs(glyphs),
269 SubstitutionSubtables::ChainContextual(tables) => tables.add_reachable_glyphs(glyphs),
270 }
271 }
272}
273
274impl<'a, T: FontRead<'a> + GlyphClosure + 'a, Ext: ExtensionLookup<'a, T> + 'a> GlyphClosure
275 for Subtables<'a, T, Ext>
276{
277 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
278 self.iter().try_for_each(|t| t?.add_reachable_glyphs(ctx))
279 }
280}
281
282impl GlyphClosure for SingleSubst<'_> {
283 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
284 for (target, replacement) in self.iter_subs()? {
285 if ctx.current_glyphs().contains(target) {
286 ctx.add_glyph(replacement);
287 }
288 }
289 Ok(())
290 }
291}
292
293impl SingleSubst<'_> {
294 fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId16, GlyphId16)> + '_, ReadError> {
295 let (left, right) = match self {
296 SingleSubst::Format1(t) => (Some(t.iter_subs()?), None),
297 SingleSubst::Format2(t) => (None, Some(t.iter_subs()?)),
298 };
299 Ok(left
300 .into_iter()
301 .flatten()
302 .chain(right.into_iter().flatten()))
303 }
304}
305
306impl SingleSubstFormat1<'_> {
307 fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId16, GlyphId16)> + '_, ReadError> {
308 let delta = self.delta_glyph_id();
309 let coverage = self.coverage()?;
310 Ok(coverage.iter().filter_map(move |gid| {
311 let raw = (gid.to_u16() as i32).checked_add(delta as i32);
312 let raw = raw.and_then(|raw| u16::try_from(raw).ok())?;
313 Some((gid, GlyphId16::new(raw)))
314 }))
315 }
316}
317
318impl SingleSubstFormat2<'_> {
319 fn iter_subs(&self) -> Result<impl Iterator<Item = (GlyphId16, GlyphId16)> + '_, ReadError> {
320 let coverage = self.coverage()?;
321 let subs = self.substitute_glyph_ids();
322 Ok(coverage.iter().zip(subs.iter().map(|id| id.get())))
323 }
324}
325
326impl GlyphClosure for MultipleSubstFormat1<'_> {
327 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
328 let coverage = self.coverage()?;
329 let sequences = self.sequences();
330 for (gid, replacements) in coverage.iter().zip(sequences.iter()) {
331 let replacements = replacements?;
332 if ctx.current_glyphs().contains(gid) {
333 ctx.extend_glyphs(
334 replacements
335 .substitute_glyph_ids()
336 .iter()
337 .map(|gid| gid.get()),
338 );
339 }
340 }
341 Ok(())
342 }
343}
344
345impl GlyphClosure for AlternateSubstFormat1<'_> {
346 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
347 let coverage = self.coverage()?;
348 let alts = self.alternate_sets();
349 for (gid, alts) in coverage.iter().zip(alts.iter()) {
350 let alts = alts?;
351 if ctx.current_glyphs().contains(gid) {
352 ctx.extend_glyphs(alts.alternate_glyph_ids().iter().map(|gid| gid.get()));
353 }
354 }
355 Ok(())
356 }
357}
358
359impl GlyphClosure for LigatureSubstFormat1<'_> {
360 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
361 let coverage = self.coverage()?;
362 let ligs = self.ligature_sets();
363 for (gid, lig_set) in coverage.iter().zip(ligs.iter()) {
364 let lig_set = lig_set?;
365 if ctx.current_glyphs().contains(gid) {
366 for lig in lig_set.ligatures().iter() {
367 let lig = lig?;
368 if lig
369 .component_glyph_ids()
370 .iter()
371 .all(|gid| ctx.glyphs().contains(gid.get()))
372 {
373 ctx.add_glyph(lig.ligature_glyph());
374 }
375 }
376 }
377 }
378 Ok(())
379 }
380}
381
382impl GlyphClosure for ReverseChainSingleSubstFormat1<'_> {
383 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
384 for coverage in self
385 .backtrack_coverages()
386 .iter()
387 .chain(self.lookahead_coverages().iter())
388 {
389 if !coverage?.iter().any(|gid| ctx.glyphs().contains(gid)) {
390 return Ok(());
391 }
392 }
393
394 for (gid, sub) in self.coverage()?.iter().zip(self.substitute_glyph_ids()) {
395 if ctx.current_glyphs().contains(gid) {
396 ctx.add_glyph(sub.get());
397 }
398 }
399
400 Ok(())
401 }
402}
403
404impl GlyphClosure for SequenceContext<'_> {
405 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError> {
406 match self {
407 Self::Format1(table) => ContextFormat1::Plain(table.clone()).add_reachable_glyphs(ctx),
408 Self::Format2(table) => ContextFormat2::Plain(table.clone()).add_reachable_glyphs(ctx),
409 Self::Format3(table) => ContextFormat3::Plain(table.clone()).add_reachable_glyphs(ctx),
410 }
411 }
412}
413
414impl GlyphClosure for ChainedSequenceContext<'_> {
415 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError> {
416 match self {
417 Self::Format1(table) => ContextFormat1::Chain(table.clone()).add_reachable_glyphs(ctx),
418 Self::Format2(table) => ContextFormat2::Chain(table.clone()).add_reachable_glyphs(ctx),
419 Self::Format3(table) => ContextFormat3::Chain(table.clone()).add_reachable_glyphs(ctx),
420 }
421 }
422}
423
424impl GlyphClosure for ContextFormat1<'_> {
426 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx<'_>) -> Result<(), ReadError> {
427 let coverage = self.coverage()?;
428 let Some(cur_glyphs) = intersect_coverage(&coverage, ctx.current_glyphs()) else {
429 return Ok(());
430 };
431
432 for (i, seq) in coverage
434 .iter()
435 .zip(self.rule_sets())
436 .enumerate()
437 .filter_map(|(i, (gid, seq))| {
438 seq.filter(|_| cur_glyphs.contains(gid)).map(|seq| (i, seq))
439 })
440 {
441 for rule in seq?.rules() {
442 let rule = rule?;
443 if !rule.matches_glyphs(ctx.glyphs()) {
445 continue;
446 }
447 let mut seen_sequence_indices = IntSet::new();
453 for lookup_record in rule.lookup_records() {
454 let lookup_id = lookup_record.lookup_list_index();
455 let sequence_idx = lookup_record.sequence_index();
456 let active_glyphs = if !seen_sequence_indices.insert(sequence_idx) {
457 None
460 } else if sequence_idx == 0 {
461 Some(IntSet::from([coverage.iter().nth(i).unwrap()]))
462 } else {
463 Some(IntSet::from([rule.input_sequence()
464 [sequence_idx as usize - 1]
465 .get()]))
466 };
467 ctx.add_todo(lookup_id, active_glyphs);
468 }
469 }
470 }
471 Ok(())
472 }
473}
474
475impl GlyphClosure for ContextFormat2<'_> {
477 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError> {
478 let coverage = self.coverage()?;
479 let Some(cur_glyphs) = intersect_coverage(&coverage, ctx.current_glyphs()) else {
480 return Ok(());
481 };
482
483 let classdef = self.input_class_def()?;
484 let our_classes = make_class_set(ctx.glyphs(), &classdef);
485 for (class_i, seq) in self
486 .rule_sets()
487 .enumerate()
488 .filter_map(|(i, seq)| seq.map(|seq| (i as u16, seq)))
489 .filter(|x| our_classes.contains(x.0))
490 {
491 for rule in seq?.rules() {
492 let rule = rule?;
493 if !rule.matches_classes(&our_classes) {
494 continue;
495 }
496
497 let mut seen_sequence_indices = IntSet::new();
498 for lookup_record in rule.lookup_records() {
499 let lookup_id = lookup_record.lookup_list_index();
500 let seq_idx = lookup_record.sequence_index();
501 let active_glyphs = if !seen_sequence_indices.insert(seq_idx) {
502 None
503 } else if seq_idx == 0 {
504 Some(intersect_class(&classdef, &cur_glyphs, class_i))
505 } else {
506 Some(intersect_class(
507 &classdef,
508 ctx.glyphs(),
509 rule.input_sequence()[seq_idx as usize - 1].get(),
510 ))
511 };
512
513 ctx.add_todo(lookup_id, active_glyphs);
514 }
515 }
516 }
517 Ok(())
518 }
519}
520
521impl GlyphClosure for ContextFormat3<'_> {
522 fn add_reachable_glyphs(&self, ctx: &mut ClosureCtx) -> Result<(), ReadError> {
523 let cov0 = self.coverages().get(0)?;
524 let Some(cur_glyphs) = intersect_coverage(&cov0, ctx.current_glyphs()) else {
525 return Ok(());
526 };
527
528 let glyphs = ctx.glyphs().iter().map(GlyphId::from).collect();
529 if !self.matches_glyphs(&glyphs)? {
530 return Ok(());
531 }
532 for record in self.lookup_records() {
533 let mut seen_sequence_indices = IntSet::new();
534 let seq_idx = record.sequence_index();
535 let lookup_id = record.lookup_list_index();
536 let active_glyphs = if !seen_sequence_indices.insert(seq_idx) {
537 None
538 } else if seq_idx == 0 {
539 Some(cur_glyphs.clone())
540 } else {
541 Some(
542 self.coverages()
543 .get(seq_idx as _)?
544 .iter()
545 .filter(|gid| ctx.glyphs().contains(*gid))
546 .collect(),
547 )
548 };
549
550 ctx.add_todo(lookup_id, active_glyphs);
551 }
552 Ok(())
553 }
554}
555
556fn make_class_set(glyphs: &IntSet<GlyphId16>, classdef: &ClassDef) -> IntSet<u16> {
558 glyphs.iter().map(|gid| classdef.get(gid)).collect()
559}
560
561fn intersect_class(
564 classdef: &ClassDef,
565 glyphs: &IntSet<GlyphId16>,
566 class: u16,
567) -> IntSet<GlyphId16> {
568 glyphs
569 .iter()
570 .filter(|gid| classdef.get(*gid) == class)
571 .collect()
572}
573
574fn intersect_coverage(
575 coverage: &CoverageTable,
576 glyphs: &IntSet<GlyphId16>,
577) -> Option<IntSet<GlyphId16>> {
578 let r = coverage
579 .iter()
580 .filter(|gid| glyphs.contains(*gid))
581 .collect::<IntSet<_>>();
582 Some(r).filter(|set| !set.is_empty())
583}
584
585impl SubstitutionLookupList<'_> {
586 pub fn closure_lookups(
587 &self,
588 glyph_set: &IntSet<GlyphId>,
589 lookup_indices: &mut IntSet<u16>,
590 ) -> Result<(), ReadError> {
591 let mut c = LookupClosureCtx::new(glyph_set);
592
593 let lookups = self.lookups();
594 for idx in lookup_indices.iter() {
595 let lookup = lookups.get(idx as usize)?;
596 lookup.closure_lookups(&mut c, idx)?;
597 }
598
599 lookup_indices.union(c.visited_lookups());
600 lookup_indices.subtract(c.inactive_lookups());
601 Ok(())
602 }
603}
604
605impl LookupClosure for SubstitutionLookup<'_> {
606 fn closure_lookups(
607 &self,
608 c: &mut LookupClosureCtx,
609 lookup_index: u16,
610 ) -> Result<(), ReadError> {
611 if !c.should_visit_lookup(lookup_index) {
612 return Ok(());
613 }
614
615 if !self.intersects(c.glyphs())? {
616 c.set_lookup_inactive(lookup_index);
617 return Ok(());
618 }
619
620 let lookup_type = self.lookup_type();
621 self.subtables()?.closure_lookups(c, lookup_type)
622 }
623
624 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
625 self.subtables()?.intersects(glyph_set)
626 }
627}
628
629impl LookupClosure for SubstitutionSubtables<'_> {
630 fn closure_lookups(&self, _c: &mut LookupClosureCtx, _arg: u16) -> Result<(), ReadError> {
631 Ok(())
632 }
633
634 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
635 match self {
636 SubstitutionSubtables::Single(subtables) => subtables.intersects(glyph_set),
637 SubstitutionSubtables::Multiple(subtables) => subtables.intersects(glyph_set),
638 SubstitutionSubtables::Alternate(subtables) => subtables.intersects(glyph_set),
639 SubstitutionSubtables::Ligature(subtables) => subtables.intersects(glyph_set),
640 SubstitutionSubtables::Contextual(subtables) => subtables.intersects(glyph_set),
641 SubstitutionSubtables::ChainContextual(subtables) => subtables.intersects(glyph_set),
642 SubstitutionSubtables::Reverse(subtables) => subtables.intersects(glyph_set),
643 }
644 }
645}
646
647impl LookupClosure for SingleSubst<'_> {
648 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
649 match self {
650 Self::Format1(item) => item.intersects(glyph_set),
651 Self::Format2(item) => item.intersects(glyph_set),
652 }
653 }
654}
655
656impl LookupClosure for SingleSubstFormat1<'_> {
657 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
658 Ok(self.coverage()?.intersects(glyph_set))
659 }
660}
661
662impl LookupClosure for SingleSubstFormat2<'_> {
663 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
664 Ok(self.coverage()?.intersects(glyph_set))
665 }
666}
667
668impl LookupClosure for MultipleSubstFormat1<'_> {
669 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
670 Ok(self.coverage()?.intersects(glyph_set))
671 }
672}
673
674impl LookupClosure for AlternateSubstFormat1<'_> {
675 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
676 Ok(self.coverage()?.intersects(glyph_set))
677 }
678}
679
680impl LookupClosure for LigatureSubstFormat1<'_> {
681 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
682 let coverage = self.coverage()?;
683 let lig_sets = self.ligature_sets();
684 for lig_set in coverage
685 .iter()
686 .zip(lig_sets.iter())
687 .filter_map(|(g, lig_set)| glyph_set.contains(GlyphId::from(g)).then_some(lig_set))
688 {
689 if lig_set?.intersects(glyph_set)? {
690 return Ok(true);
691 }
692 }
693 Ok(false)
694 }
695}
696
697impl LookupClosure for LigatureSet<'_> {
698 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
699 let ligs = self.ligatures();
700 for lig in ligs.iter() {
701 if lig?.intersects(glyph_set)? {
702 return Ok(true);
703 }
704 }
705 Ok(false)
706 }
707}
708
709impl LookupClosure for Ligature<'_> {
710 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
711 let ret = self
712 .component_glyph_ids()
713 .iter()
714 .all(|g| glyph_set.contains(GlyphId::from(g.get())));
715 Ok(ret)
716 }
717}
718
719impl LookupClosure for ReverseChainSingleSubstFormat1<'_> {
720 fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
721 if !self.coverage()?.intersects(glyph_set) {
722 return Ok(false);
723 }
724
725 for coverage in self.backtrack_coverages().iter() {
726 if !coverage?.intersects(glyph_set) {
727 return Ok(false);
728 }
729 }
730
731 for coverage in self.lookahead_coverages().iter() {
732 if !coverage?.intersects(glyph_set) {
733 return Ok(false);
734 }
735 }
736 Ok(true)
737 }
738}
739
740#[cfg(test)]
741mod tests {
742 use std::collections::{HashMap, HashSet};
743
744 use crate::{FontRef, TableProvider};
745
746 use super::*;
747 use font_test_data::closure as test_data;
748
749 struct GlyphMap {
750 to_gid: HashMap<&'static str, GlyphId16>,
751 from_gid: HashMap<GlyphId16, &'static str>,
752 }
753
754 impl GlyphMap {
755 fn new(raw_order: &'static str) -> GlyphMap {
756 let to_gid: HashMap<_, _> = raw_order
757 .split('\n')
758 .map(|line| line.trim())
759 .filter(|line| !(line.starts_with('#') || line.is_empty()))
760 .enumerate()
761 .map(|(gid, name)| (name, GlyphId16::new(gid.try_into().unwrap())))
762 .collect();
763 let from_gid = to_gid.iter().map(|(name, gid)| (*gid, *name)).collect();
764 GlyphMap { from_gid, to_gid }
765 }
766
767 fn get_gid(&self, name: &str) -> Option<GlyphId16> {
768 self.to_gid.get(name).copied()
769 }
770
771 fn get_name(&self, gid: GlyphId16) -> Option<&str> {
772 self.from_gid.get(&gid).copied()
773 }
774 }
775
776 fn get_gsub(test_data: &'static [u8]) -> Gsub<'static> {
777 let font = FontRef::new(test_data).unwrap();
778 font.gsub().unwrap()
779 }
780
781 fn compute_closure(gsub: &Gsub, glyph_map: &GlyphMap, input: &[&str]) -> IntSet<GlyphId16> {
782 let input_glyphs = input
783 .iter()
784 .map(|name| glyph_map.get_gid(name).unwrap())
785 .collect();
786 gsub.closure_glyphs(input_glyphs).unwrap()
787 }
788
789 macro_rules! assert_closure_result {
791 ($glyph_map:expr, $result:expr, $expected:expr) => {
792 let result = $result
793 .iter()
794 .map(|gid| $glyph_map.get_name(gid).unwrap())
795 .collect::<HashSet<_>>();
796 let expected = $expected.iter().copied().collect::<HashSet<_>>();
797 if expected != result {
798 let in_output = result.difference(&expected).collect::<Vec<_>>();
799 let in_expected = expected.difference(&result).collect::<Vec<_>>();
800 let mut msg = format!("Closure output does not match\n");
801 if !in_expected.is_empty() {
802 msg.push_str(format!("missing {in_expected:?}\n").as_str());
803 }
804 if !in_output.is_empty() {
805 msg.push_str(format!("unexpected {in_output:?}").as_str());
806 }
807 panic!("{msg}")
808 }
809 };
810 }
811
812 #[test]
813 fn smoke_test() {
814 let gsub = get_gsub(test_data::SIMPLE);
817 let glyph_map = GlyphMap::new(test_data::SIMPLE_GLYPHS);
818 let result = compute_closure(&gsub, &glyph_map, &["a"]);
819
820 assert_closure_result!(
821 glyph_map,
822 result,
823 &["a", "A", "b", "c", "d", "a_a", "a.1", "a.2", "a.3"]
824 );
825 }
826
827 #[test]
828 fn recursive() {
829 let gsub = get_gsub(test_data::RECURSIVE);
834 let glyph_map = GlyphMap::new(test_data::RECURSIVE_GLYPHS);
835 let result = compute_closure(&gsub, &glyph_map, &["a"]);
836 assert_closure_result!(glyph_map, result, &["a", "b", "c", "d"]);
837 }
838
839 #[test]
840 fn contextual_lookups_nop() {
841 let gsub = get_gsub(test_data::CONTEXTUAL);
842 let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
843
844 let nop = compute_closure(&gsub, &glyph_map, &["three", "four", "e", "f"]);
846 assert_closure_result!(glyph_map, nop, &["three", "four", "e", "f"]);
847 }
848
849 #[test]
850 fn contextual_lookups_chained_f1() {
851 let gsub = get_gsub(test_data::CONTEXTUAL);
852 let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
853 let gsub6f1 = compute_closure(
854 &gsub,
855 &glyph_map,
856 &["one", "two", "three", "four", "five", "six", "seven"],
857 );
858 assert_closure_result!(
859 glyph_map,
860 gsub6f1,
861 &["one", "two", "three", "four", "five", "six", "seven", "X", "Y"]
862 );
863 }
864
865 #[test]
866 fn contextual_lookups_chained_f3() {
867 let gsub = get_gsub(test_data::CONTEXTUAL);
868 let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
869 let gsub6f3 = compute_closure(&gsub, &glyph_map, &["space", "e"]);
870 assert_closure_result!(glyph_map, gsub6f3, &["space", "e", "e.2"]);
871
872 let gsub5f3 = compute_closure(&gsub, &glyph_map, &["f", "g"]);
873 assert_closure_result!(glyph_map, gsub5f3, &["f", "g", "f.2"]);
874 }
875
876 #[test]
877 fn contextual_plain_f1() {
878 let gsub = get_gsub(test_data::CONTEXTUAL);
879 let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
880 let gsub5f1 = compute_closure(&gsub, &glyph_map, &["a", "b"]);
881 assert_closure_result!(glyph_map, gsub5f1, &["a", "b", "a_b"]);
882 }
883
884 #[test]
885 fn contextual_plain_f3() {
886 let gsub = get_gsub(test_data::CONTEXTUAL);
887 let glyph_map = GlyphMap::new(test_data::CONTEXTUAL_GLYPHS);
888 let gsub5f3 = compute_closure(&gsub, &glyph_map, &["f", "g"]);
889 assert_closure_result!(glyph_map, gsub5f3, &["f", "g", "f.2"]);
890 }
891
892 #[test]
893 fn recursive_context() {
894 let gsub = get_gsub(test_data::RECURSIVE_CONTEXTUAL);
895 let glyph_map = GlyphMap::new(test_data::RECURSIVE_CONTEXTUAL_GLYPHS);
896
897 let nop = compute_closure(&gsub, &glyph_map, &["b", "B"]);
898 assert_closure_result!(glyph_map, nop, &["b", "B"]);
899
900 let full = compute_closure(&gsub, &glyph_map, &["a", "b", "c"]);
901 assert_closure_result!(glyph_map, full, &["a", "b", "c", "B", "B.2", "B.3"]);
902
903 let intermediate = compute_closure(&gsub, &glyph_map, &["a", "B.2"]);
904 assert_closure_result!(glyph_map, intermediate, &["a", "B.2", "B.3"]);
905 }
906
907 #[test]
908 fn feature_variations() {
909 let gsub = get_gsub(test_data::VARIATIONS_CLOSURE);
910 let glyph_map = GlyphMap::new(test_data::VARIATIONS_GLYPHS);
911
912 let input = compute_closure(&gsub, &glyph_map, &["a"]);
913 assert_closure_result!(glyph_map, input, &["a", "b", "c"]);
914 }
915
916 #[test]
917 fn context_with_unreachable_rules() {
918 let gsub = get_gsub(test_data::CONTEXT_WITH_UNREACHABLE_BITS);
919 let glyph_map = GlyphMap::new(test_data::CONTEXT_WITH_UNREACHABLE_BITS_GLYPHS);
920
921 let nop = compute_closure(&gsub, &glyph_map, &["c", "z"]);
922 assert_closure_result!(glyph_map, nop, &["c", "z"]);
923
924 let full = compute_closure(&gsub, &glyph_map, &["a", "b", "c", "z"]);
925 assert_closure_result!(glyph_map, full, &["a", "b", "c", "z", "A", "B"]);
926 }
927
928 #[test]
929 fn cyclical_context() {
930 let gsub = get_gsub(test_data::CYCLIC_CONTEXTUAL);
931 let glyph_map = GlyphMap::new(test_data::RECURSIVE_CONTEXTUAL_GLYPHS);
932 let nop = compute_closure(&gsub, &glyph_map, &["a", "b", "c"]);
934 assert_closure_result!(glyph_map, nop, &["a", "b", "c"]);
935 }
936
937 #[test]
938 fn collect_all_features() {
939 let font = FontRef::new(font_test_data::closure::CONTEXTUAL).unwrap();
940 let gsub = font.gsub().unwrap();
941 let ret = gsub
942 .collect_features(&IntSet::all(), &IntSet::all(), &IntSet::all())
943 .unwrap();
944 assert_eq!(ret.len(), 2);
945 assert!(ret.contains(0));
946 assert!(ret.contains(1));
947 }
948
949 #[test]
950 fn collect_all_features_with_feature_filter() {
951 let font = FontRef::new(font_test_data::closure::CONTEXTUAL).unwrap();
952 let gsub = font.gsub().unwrap();
953
954 let mut feature_tags = IntSet::empty();
955 feature_tags.insert(Tag::new(b"SUB5"));
956
957 let ret = gsub
958 .collect_features(&IntSet::all(), &IntSet::all(), &feature_tags)
959 .unwrap();
960 assert_eq!(ret.len(), 1);
961 assert!(ret.contains(0));
962 }
963
964 #[test]
965 fn collect_all_features_with_script_filter() {
966 let font = FontRef::new(font_test_data::closure::CONTEXTUAL).unwrap();
967 let gsub = font.gsub().unwrap();
968
969 let mut script_tags = IntSet::empty();
970 script_tags.insert(Tag::new(b"LATN"));
971
972 let ret = gsub
973 .collect_features(&script_tags, &IntSet::all(), &IntSet::all())
974 .unwrap();
975 assert!(ret.is_empty());
976 }
977}