Skip to main content

read_fonts/tables/gpos/
closure.rs

1//! support closure for GPOS
2
3use super::{
4    CursivePosFormat1, ExtensionPosFormat1, ExtensionSubtable, Gpos, MarkBasePosFormat1,
5    MarkLigPosFormat1, MarkMarkPosFormat1, PairPos, PairPosFormat1, PairPosFormat2, PairSet,
6    PositionLookup, PositionLookupList, PositionSubtables, SinglePos, SinglePosFormat1,
7    SinglePosFormat2,
8};
9use crate::{collections::IntSet, FontRead, GlyphId, ReadError, Tag};
10
11#[cfg(feature = "std")]
12use crate::tables::layout::{Intersect, LayoutLookupList, LookupClosure, LookupClosureCtx};
13
14impl Gpos<'_> {
15    /// Return a set of all feature indices underneath the specified scripts, languages and features
16    pub fn collect_features(
17        &self,
18        scripts: &IntSet<Tag>,
19        languages: &IntSet<Tag>,
20        features: &IntSet<Tag>,
21    ) -> Result<IntSet<u16>, ReadError> {
22        if self.script_list_offset().is_null() || self.feature_list_offset().is_null() {
23            return Ok(IntSet::empty());
24        }
25        let feature_list = self.feature_list()?;
26        let script_list = self.script_list()?;
27        let head_ptr = self.offset_data().as_bytes().as_ptr() as usize;
28        script_list.collect_features(head_ptr, &feature_list, scripts, languages, features)
29    }
30
31    /// Return a set of lookups referenced by the specified features
32    pub fn collect_lookups(&self, feature_indices: &IntSet<u16>) -> Result<IntSet<u16>, ReadError> {
33        if self.feature_list_offset().is_null() {
34            return Ok(IntSet::empty());
35        }
36        let feature_list = self.feature_list()?;
37        let mut lookup_indices = feature_list.collect_lookups(feature_indices)?;
38
39        if let Some(feature_variations) = self.feature_variations().transpose()? {
40            let subs_lookup_indices = feature_variations.collect_lookups(feature_indices)?;
41            lookup_indices.union(&subs_lookup_indices);
42        }
43        Ok(lookup_indices)
44    }
45
46    /// Update the set of lookup indices with all lookups reachable from specified glyph set and lookup_indices.
47    pub fn closure_lookups(
48        &self,
49        glyphs: &IntSet<GlyphId>,
50        lookup_indices: &mut IntSet<u16>,
51    ) -> Result<(), ReadError> {
52        if self.lookup_list_offset().is_null() {
53            return Ok(());
54        }
55        let lookup_list = self.lookup_list()?;
56        lookup_list.closure_lookups(glyphs, lookup_indices)
57    }
58}
59
60impl PositionLookupList<'_> {
61    pub fn closure_lookups(
62        &self,
63        glyph_set: &IntSet<GlyphId>,
64        lookup_indices: &mut IntSet<u16>,
65    ) -> Result<(), ReadError> {
66        lookup_indices.remove_range(self.lookup_count()..=u16::MAX);
67        if lookup_indices.is_empty() {
68            return Ok(());
69        }
70        let lookup_list = LayoutLookupList::Gpos(self);
71        let mut c = LookupClosureCtx::new(glyph_set, &lookup_list);
72
73        let lookups = self.lookups();
74        for idx in lookup_indices.iter() {
75            let lookup = match lookups.get(idx as usize) {
76                Err(ReadError::NullOffset) => {
77                    c.set_lookup_inactive(idx);
78                    continue;
79                }
80                other => other,
81            }?;
82            lookup.closure_lookups(&mut c, idx)?;
83        }
84
85        lookup_indices.union(c.visited_lookups());
86        lookup_indices.subtract(c.inactive_lookups());
87        Ok(())
88    }
89}
90
91impl LookupClosure for PositionLookup<'_> {
92    fn closure_lookups(
93        &self,
94        c: &mut LookupClosureCtx,
95        lookup_index: u16,
96    ) -> Result<(), ReadError> {
97        if !c.should_visit_lookup(lookup_index) {
98            return Ok(());
99        }
100
101        if !self.intersects(c.glyphs())? {
102            c.set_lookup_inactive(lookup_index);
103            return Ok(());
104        }
105        self.subtables()?.closure_lookups(c, lookup_index)
106    }
107}
108
109impl LookupClosure for PositionSubtables<'_> {
110    fn closure_lookups(&self, c: &mut LookupClosureCtx, arg: u16) -> Result<(), ReadError> {
111        match self {
112            PositionSubtables::Contextual(subtables) => subtables.closure_lookups(c, arg),
113            PositionSubtables::ChainContextual(subtables) => subtables.closure_lookups(c, arg),
114            _ => Ok(()),
115        }
116    }
117}
118
119impl Intersect for PositionLookup<'_> {
120    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
121        match self {
122            PositionLookup::Single(inner) => inner.subtables().intersects(glyph_set),
123            PositionLookup::Pair(inner) => inner.subtables().intersects(glyph_set),
124            PositionLookup::Cursive(inner) => inner.subtables().intersects(glyph_set),
125            PositionLookup::MarkToBase(inner) => inner.subtables().intersects(glyph_set),
126            PositionLookup::MarkToLig(inner) => inner.subtables().intersects(glyph_set),
127            PositionLookup::MarkToMark(inner) => inner.subtables().intersects(glyph_set),
128            PositionLookup::Contextual(inner) => inner.subtables().intersects(glyph_set),
129            PositionLookup::ChainContextual(inner) => inner.subtables().intersects(glyph_set),
130            PositionLookup::Extension(inner) => inner.subtables().intersects(glyph_set),
131        }
132    }
133}
134
135impl Intersect for ExtensionSubtable<'_> {
136    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
137        match self {
138            ExtensionSubtable::Single(inner) => inner.intersects(glyph_set),
139            ExtensionSubtable::Pair(inner) => inner.intersects(glyph_set),
140            ExtensionSubtable::Cursive(inner) => inner.intersects(glyph_set),
141            ExtensionSubtable::MarkToBase(inner) => inner.intersects(glyph_set),
142            ExtensionSubtable::MarkToLig(inner) => inner.intersects(glyph_set),
143            ExtensionSubtable::MarkToMark(inner) => inner.intersects(glyph_set),
144            ExtensionSubtable::Contextual(inner) => inner.intersects(glyph_set),
145            ExtensionSubtable::ChainContextual(inner) => inner.intersects(glyph_set),
146        }
147    }
148}
149
150impl<'a, T> Intersect for ExtensionPosFormat1<'a, T>
151where
152    T: Intersect + FontRead<'a>,
153{
154    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
155        if self.extension_offset().is_null() {
156            return Ok(false);
157        }
158        self.extension()?.intersects(glyph_set)
159    }
160}
161
162impl Intersect for SinglePos<'_> {
163    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
164        match self {
165            Self::Format1(item) => item.intersects(glyph_set),
166            Self::Format2(item) => item.intersects(glyph_set),
167        }
168    }
169}
170
171impl Intersect for SinglePosFormat1<'_> {
172    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
173        if self.coverage_offset().is_null() {
174            return Ok(false);
175        }
176        Ok(self.coverage()?.intersects(glyph_set))
177    }
178}
179
180impl Intersect for SinglePosFormat2<'_> {
181    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
182        if self.coverage_offset().is_null() {
183            return Ok(false);
184        }
185        Ok(self.coverage()?.intersects(glyph_set))
186    }
187}
188
189impl Intersect for PairPos<'_> {
190    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
191        match self {
192            Self::Format1(item) => item.intersects(glyph_set),
193            Self::Format2(item) => item.intersects(glyph_set),
194        }
195    }
196}
197
198impl Intersect for PairPosFormat1<'_> {
199    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
200        if self.coverage_offset().is_null() {
201            return Ok(false);
202        }
203        let coverage = self.coverage()?;
204        let pair_sets = self.pair_sets();
205
206        let num_pair_sets = self.pair_set_count();
207        let num_bits = 16 - num_pair_sets.leading_zeros();
208        if num_pair_sets as u64 > glyph_set.len() * num_bits as u64 {
209            for g in glyph_set.iter() {
210                let Some(i) = coverage.get(g) else {
211                    continue;
212                };
213                let pair_set = match pair_sets.get(i as usize) {
214                    Err(ReadError::NullOffset) => continue,
215                    other => other,
216                }?;
217                if pair_set.intersects(glyph_set)? {
218                    return Ok(true);
219                }
220            }
221        } else {
222            for (g, pair_set) in coverage.iter().zip(pair_sets.iter_as_nullable()) {
223                if !glyph_set.contains(GlyphId::from(g)) {
224                    continue;
225                }
226                let Some(pair_set) = pair_set.transpose()? else {
227                    continue;
228                };
229                if pair_set.intersects(glyph_set)? {
230                    return Ok(true);
231                }
232            }
233        }
234        Ok(false)
235    }
236}
237
238impl Intersect for PairSet<'_> {
239    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
240        for record in self.pair_value_records().iter() {
241            let second_glyph = record?.second_glyph();
242            if glyph_set.contains(GlyphId::from(second_glyph)) {
243                return Ok(true);
244            }
245        }
246        Ok(false)
247    }
248}
249
250impl Intersect for PairPosFormat2<'_> {
251    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
252        if self.coverage_offset().is_null()
253            || self.class_def1_offset().is_null()
254            || self.class_def2_offset().is_null()
255        {
256            return Ok(false);
257        }
258        Ok(self.coverage()?.intersects(glyph_set) && self.class_def2()?.intersects(glyph_set)?)
259    }
260}
261
262impl Intersect for CursivePosFormat1<'_> {
263    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
264        if self.coverage_offset().is_null() {
265            return Ok(false);
266        }
267        Ok(self.coverage()?.intersects(glyph_set))
268    }
269}
270
271impl Intersect for MarkBasePosFormat1<'_> {
272    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
273        if self.mark_coverage_offset().is_null() || self.base_coverage_offset().is_null() {
274            return Ok(false);
275        }
276        Ok(self.mark_coverage()?.intersects(glyph_set)
277            && self.base_coverage()?.intersects(glyph_set))
278    }
279}
280
281impl Intersect for MarkLigPosFormat1<'_> {
282    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
283        if self.mark_coverage_offset().is_null() || self.ligature_coverage_offset().is_null() {
284            return Ok(false);
285        }
286        Ok(self.mark_coverage()?.intersects(glyph_set)
287            && self.ligature_coverage()?.intersects(glyph_set))
288    }
289}
290
291impl Intersect for MarkMarkPosFormat1<'_> {
292    fn intersects(&self, glyph_set: &IntSet<GlyphId>) -> Result<bool, ReadError> {
293        if self.mark1_coverage_offset().is_null() || self.mark2_coverage_offset().is_null() {
294            return Ok(false);
295        }
296        Ok(self.mark1_coverage()?.intersects(glyph_set)
297            && self.mark2_coverage()?.intersects(glyph_set))
298    }
299}