Skip to main content

write_fonts/tables/gsub/
builders.rs

1//! GSUB lookup builders
2
3use std::{collections::BTreeMap, convert::TryFrom};
4
5use types::{FixedSize, GlyphId16, Offset16};
6
7use crate::{
8    tables::{
9        layout::{builders::Builder, CoverageTable},
10        variations::ivs_builder::VariationStoreBuilder,
11    },
12    FontWrite,
13};
14
15/// Helper type for splitting layout subtables
16// NOTE: currently just used for ligature substitution, but hopefully can be
17// reused for other lookups as needed?
18#[derive(Clone, Debug)]
19struct TableSplitter<T: SplitTable> {
20    finished: Vec<T>,
21    current_coverage: Vec<GlyphId16>,
22    current_items: Vec<T::Component>,
23    current_size: usize,
24}
25
26/// A trait for splitting layout subtables.
27trait SplitTable {
28    /// The component item of this table.
29    type Component;
30
31    /// The (maximum) number of bytes required to store this item.
32    ///
33    /// This should include only the costs of the item, not the cost of adding
34    /// to the coverage table.
35    ///
36    /// 'Maximum' because this does not account for possible size savings from
37    /// deduplication of identical objects, for instance.
38    fn size_for_item(item: &Self::Component) -> usize;
39    /// The starting size of a new table that will contain this item.
40    ///
41    /// This does not include the size of the item itself! The item is provided
42    /// because sometimes the table contains fields derived from its members.
43    fn initial_size_for_item(item: &Self::Component) -> usize;
44    fn instantiate(coverage: CoverageTable, items: Vec<Self::Component>) -> Self;
45}
46
47impl<T: SplitTable + FontWrite> TableSplitter<T> {
48    const MAX_TABLE_SIZE: usize = u16::MAX as usize;
49
50    fn new() -> Self {
51        Self {
52            finished: Vec::new(),
53            current_coverage: Vec::new(),
54            current_items: Vec::new(),
55            current_size: 0,
56        }
57    }
58
59    fn add(&mut self, gid: GlyphId16, item: T::Component) {
60        let item_size = T::size_for_item(&item);
61        if item_size + self.current_size > Self::MAX_TABLE_SIZE {
62            let current_len = self.current_coverage.len();
63            self.finish_current();
64            let type_ = self.finished.last().unwrap().table_type();
65            log::info!("adding split in {type_} at {current_len}");
66        }
67
68        if self.current_size == 0 {
69            self.current_size = T::initial_size_for_item(&item);
70        }
71        self.current_coverage.push(gid);
72        self.current_items.push(item);
73        // item size + a glyph in the coverage table (worst case)
74        self.current_size += item_size + GlyphId16::RAW_BYTE_LEN;
75    }
76
77    fn finish_current(&mut self) {
78        if !self.current_coverage.is_empty() {
79            let coverage = std::mem::take(&mut self.current_coverage).into();
80            self.finished.push(T::instantiate(
81                coverage,
82                std::mem::take(&mut self.current_items),
83            ));
84            self.current_size = 0;
85        }
86    }
87
88    fn finish(mut self) -> Vec<T> {
89        self.finish_current();
90        self.finished
91    }
92}
93/// A builder for [`SingleSubst`](super::SingleSubst) subtables.
94#[derive(Clone, Debug, Default, PartialEq, Eq)]
95pub struct SingleSubBuilder {
96    items: BTreeMap<GlyphId16, GlyphId16>,
97}
98
99impl SingleSubBuilder {
100    /// Add this replacement to the builder.
101    ///
102    /// If there is an existing substitution for the provided target, it will
103    /// be overwritten.
104    pub fn insert(&mut self, target: GlyphId16, replacement: GlyphId16) {
105        self.items.insert(target, replacement);
106    }
107
108    /// Returns `true` if all the pairs of items in the two iterators can be
109    /// added to this lookup.
110    ///
111    /// The iterators are expected to be equal length.
112    pub fn can_add(&self, target: GlyphId16, replacement: GlyphId16) -> bool {
113        // only false if target exists with a different replacement
114        !matches!(self.items.get(&target), Some(x) if *x != replacement)
115    }
116
117    /// Returns the number of rules in the builder.
118    pub fn len(&self) -> usize {
119        self.items.len()
120    }
121
122    /// Returns `true` if there are no rules in this builder.
123    pub fn is_empty(&self) -> bool {
124        self.items.is_empty()
125    }
126
127    /// Iterate all the substitution pairs in this builder.
128    ///
129    /// used when compiling the `aalt` feature.
130    #[deprecated(since = "0.38.2", note = "use ::iter instead")]
131    pub fn iter_pairs(&self) -> impl Iterator<Item = (GlyphId16, GlyphId16)> + '_ {
132        self.iter()
133    }
134
135    /// Iterate all the substitution pairs in this builder.
136    pub fn iter(&self) -> impl Iterator<Item = (GlyphId16, GlyphId16)> + '_ {
137        self.items.iter().map(|(target, alt)| (*target, *alt))
138    }
139
140    /// Convert this `SingleSubBuilder` into a `MultipleSubBuilder`.
141    ///
142    /// This is used by the fea compiler in some cases to reduce the number
143    /// of generated lookups.
144    pub fn promote_to_multi_sub(self) -> MultipleSubBuilder {
145        MultipleSubBuilder {
146            items: self
147                .items
148                .into_iter()
149                .map(|(key, gid)| (key, vec![gid]))
150                .collect(),
151        }
152    }
153
154    /// Convert this `SingleSubBuilder` into a `LigatureSubBuilder`.
155    ///
156    /// This is used by the fea compiler in some cases to reduce the number
157    /// of generated lookups.
158    pub fn promote_to_ligature_sub(self) -> LigatureSubBuilder {
159        let mut items = BTreeMap::new();
160        for (from, to) in self.items.into_iter() {
161            items
162                .entry(from)
163                .or_insert(Vec::new())
164                .push((Vec::new(), to));
165        }
166        LigatureSubBuilder { items }
167    }
168}
169
170impl Builder for SingleSubBuilder {
171    type Output = Vec<super::SingleSubst>;
172
173    fn build(self, _: &mut VariationStoreBuilder) -> Self::Output {
174        if self.items.is_empty() {
175            return Default::default();
176        }
177        // if all pairs are equidistant and within the i16 range, find the
178        // common delta
179        let delta = self
180            .items
181            .iter()
182            .map(|(k, v)| v.to_u16() as i32 - k.to_u16() as i32)
183            .reduce(|acc, val| if acc == val { acc } else { i32::MAX })
184            .and_then(|delta| i16::try_from(delta).ok());
185
186        let coverage = self.items.keys().copied().collect();
187        if let Some(delta) = delta {
188            vec![super::SingleSubst::format_1(coverage, delta)]
189        } else {
190            let replacements = self.items.values().copied().collect();
191            vec![super::SingleSubst::format_2(coverage, replacements)]
192        }
193    }
194}
195
196/// A builder for [`MultipleSubstFormat1`](super::MultipleSubstFormat1) subtables.
197#[derive(Clone, Debug, Default, PartialEq, Eq)]
198pub struct MultipleSubBuilder {
199    items: BTreeMap<GlyphId16, Vec<GlyphId16>>,
200}
201
202impl Builder for MultipleSubBuilder {
203    type Output = Vec<super::MultipleSubstFormat1>;
204
205    fn build(self, _: &mut VariationStoreBuilder) -> Self::Output {
206        if self.items.is_empty() {
207            return Default::default();
208        }
209        let coverage = self.items.keys().copied().collect();
210        let seq_tables = self.items.into_values().map(super::Sequence::new).collect();
211        vec![super::MultipleSubstFormat1::new(coverage, seq_tables)]
212    }
213}
214
215impl MultipleSubBuilder {
216    /// Returns the number of rules in the builder.
217    pub fn len(&self) -> usize {
218        self.items.len()
219    }
220
221    /// Returns `true` if there are no rules in this builder.
222    pub fn is_empty(&self) -> bool {
223        self.items.is_empty()
224    }
225
226    /// Add a new substitution to this builder.
227    ///
228    /// If the target already exists with a different replacement, it will be
229    /// overwritten.
230    pub fn insert(&mut self, target: GlyphId16, replacement: Vec<GlyphId16>) {
231        self.items.insert(target, replacement);
232    }
233
234    /// Returns `true` if no other replacement already exists for this target.
235    pub fn can_add(&self, target: GlyphId16, replacement: &[GlyphId16]) -> bool {
236        match self.items.get(&target) {
237            None => true,
238            Some(thing) => thing == replacement,
239        }
240    }
241
242    /// Iterate over the rules in this builder.
243    pub fn iter(&self) -> impl Iterator<Item = (&GlyphId16, &Vec<GlyphId16>)> {
244        self.items.iter()
245    }
246}
247
248/// A builder for [`AlternateSubstFormat1`](super::AlternateSubstFormat1) subtables
249#[derive(Clone, Debug, Default, PartialEq, Eq)]
250pub struct AlternateSubBuilder {
251    items: BTreeMap<GlyphId16, Vec<GlyphId16>>,
252}
253
254impl AlternateSubBuilder {
255    /// Returns the number of rules in the builder.
256    pub fn len(&self) -> usize {
257        self.items.len()
258    }
259
260    /// Returns `true` if there are no rules in this builder.
261    pub fn is_empty(&self) -> bool {
262        self.items.is_empty()
263    }
264
265    /// Add a new alternate sub rule to this lookup.
266    pub fn insert(&mut self, target: GlyphId16, replacement: Vec<GlyphId16>) {
267        self.items.insert(target, replacement);
268    }
269
270    /// Iterate over the rules in this builder.
271    pub fn iter(&self) -> impl Iterator<Item = (&GlyphId16, &Vec<GlyphId16>)> {
272        self.items.iter()
273    }
274
275    /// Iterate all alternates in this lookup.
276    ///
277    /// used when compiling aalt
278    pub fn iter_pairs(&self) -> impl Iterator<Item = (GlyphId16, GlyphId16)> + '_ {
279        self.items
280            .iter()
281            .flat_map(|(target, alt)| alt.iter().map(|alt| (*target, *alt)))
282    }
283}
284
285impl Builder for AlternateSubBuilder {
286    type Output = Vec<super::AlternateSubstFormat1>;
287
288    fn build(self, _: &mut VariationStoreBuilder) -> Self::Output {
289        if self.items.is_empty() {
290            return Default::default();
291        }
292        let coverage = self.items.keys().copied().collect();
293        let seq_tables = self
294            .items
295            .into_values()
296            .map(super::AlternateSet::new)
297            .collect();
298        vec![super::AlternateSubstFormat1::new(coverage, seq_tables)]
299    }
300}
301
302/// A builder for [`LigatureSubstFormat1`](super::LigatureSubstFormat1) subtables.
303#[derive(Clone, Debug, Default, PartialEq, Eq)]
304pub struct LigatureSubBuilder {
305    items: BTreeMap<GlyphId16, Vec<(Vec<GlyphId16>, GlyphId16)>>,
306}
307
308impl LigatureSubBuilder {
309    /// Returns the number of rules in the builder.
310    pub fn len(&self) -> usize {
311        self.items.len()
312    }
313
314    /// Returns `true` if there are no rules in this builder.
315    pub fn is_empty(&self) -> bool {
316        self.items.is_empty()
317    }
318
319    /// Add a new ligature substitution rule to the builder.
320    pub fn insert(&mut self, target: Vec<GlyphId16>, replacement: GlyphId16) {
321        let (first, rest) = target.split_first().unwrap();
322        let entry = self.items.entry(*first).or_default();
323        // skip duplicates
324        if !entry
325            .iter()
326            .any(|existing| existing.0 == rest && existing.1 == replacement)
327        {
328            entry.push((rest.to_owned(), replacement))
329        }
330    }
331
332    /// Check if this target sequence already has a replacement in this lookup.
333    pub fn can_add(&self, target: &[GlyphId16], replacement: GlyphId16) -> bool {
334        let Some((first, rest)) = target.split_first() else {
335            return false;
336        };
337        match self.items.get(first) {
338            Some(ligs) => !ligs
339                .iter()
340                .any(|(seq, target)| seq == rest && *target != replacement),
341            None => true,
342        }
343    }
344
345    /// Iterate over the current rules in the builder.
346    ///
347    /// The result is a tuple where the first item is the target glyph and the
348    /// second item is a tuple of (components, replacement).
349    pub fn iter(&self) -> impl Iterator<Item = (&GlyphId16, &Vec<(Vec<GlyphId16>, GlyphId16)>)> {
350        self.items.iter()
351    }
352}
353
354impl Builder for LigatureSubBuilder {
355    type Output = Vec<super::LigatureSubstFormat1>;
356
357    fn build(self, _: &mut VariationStoreBuilder) -> Self::Output {
358        let mut splitter = TableSplitter::<super::LigatureSubstFormat1>::new();
359        for (gid, mut ligs) in self.items.into_iter() {
360            // we want to sort longer items first, but otherwise preserve
361            // the order provided by the user.
362            ligs.sort_by_key(|(lig, _)| std::cmp::Reverse(lig.len()));
363            let lig_set = super::LigatureSet::new(
364                ligs.into_iter()
365                    .map(|(components, replacement)| super::Ligature::new(replacement, components))
366                    .collect(),
367            );
368            splitter.add(gid, lig_set);
369        }
370        splitter.finish()
371    }
372}
373
374impl SplitTable for super::LigatureSubstFormat1 {
375    type Component = super::LigatureSet;
376
377    fn size_for_item(item: &Self::Component) -> usize {
378        item.compute_size()
379    }
380
381    fn initial_size_for_item(_item: &Self::Component) -> usize {
382        // format, coverage offset, set count, sets offset
383        u16::RAW_BYTE_LEN * 4
384    }
385
386    fn instantiate(coverage: CoverageTable, items: Vec<Self::Component>) -> Self {
387        Self::new(coverage, items)
388    }
389}
390
391impl super::LigatureSet {
392    fn compute_size(&self) -> usize {
393        // ligatureCount
394        u16::RAW_BYTE_LEN
395            // ligatureOffsets
396            + Offset16::RAW_BYTE_LEN * self.ligatures.len()
397            // size of each referenced ligature table
398            + self
399                .ligatures
400                .iter()
401                .map(|lig| lig.compute_size())
402                .sum::<usize>()
403    }
404}
405
406impl super::Ligature {
407    fn compute_size(&self) -> usize {
408        // ligatureGlyph
409        u16::RAW_BYTE_LEN
410            // componentCount
411            + u16::RAW_BYTE_LEN
412            // componentGlyphIDs
413            + u16::RAW_BYTE_LEN * self.component_glyph_ids.len()
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    use crate::tables::gsub::LigatureSubstFormat1;
420
421    use super::*;
422
423    fn make_lig_table(n_bytes: u16, first: u16) -> super::super::Ligature {
424        assert!(n_bytes >= 6, "minimum table size");
425        assert!(n_bytes % 2 == 0, "can only generate even sizes: {n_bytes}");
426        // 6 bytes per Ligature (header, including offset)
427        let n_glyphs = (n_bytes - 6) / 2;
428        let components = (first..=first + n_glyphs).map(GlyphId16::new).collect();
429        super::super::Ligature::new(GlyphId16::new(first), components)
430    }
431    fn make_2048_bytes_of_ligature() -> super::super::LigatureSet {
432        // 4 bytes for header
433        // 2048 - 4 = 2044
434        // 2044 / 2 = 1022
435        let lig1 = make_lig_table(1022, 1);
436        let lig2 = make_lig_table(1022, 3);
437        super::super::LigatureSet::new(vec![lig1, lig2])
438    }
439
440    #[test]
441    fn who_tests_the_testers1() {
442        for size in [6, 12, 144, 2046, u16::MAX - 1] {
443            let table = make_lig_table(size, 1);
444            let bytes = crate::dump_table(&table).unwrap();
445            assert_eq!(bytes.len(), size as usize);
446        }
447    }
448
449    #[test]
450    fn splitting_ligature_subs() {
451        let mut splitter = TableSplitter::<LigatureSubstFormat1>::new();
452        let ligset = make_2048_bytes_of_ligature();
453        for gid in 0u16..31 {
454            // in real packing these would be deduplicated but we can't now that here
455            splitter.add(GlyphId16::new(gid), ligset.clone());
456        }
457
458        // 31 * 2048 < u16::MAX
459        assert_eq!(splitter.clone().finish().len(), 1);
460        splitter.add(GlyphId16::new(32), ligset);
461        // 32 * 2048 < u16::MAX
462        assert_eq!(splitter.finish().len(), 2)
463    }
464
465    // a `subtable` statement with no rules after it leaves an empty builder
466    #[test]
467    fn empty_builders_build_no_subtables() {
468        let vs = &mut VariationStoreBuilder::new(0);
469        assert!(SingleSubBuilder::default().build(vs).is_empty());
470        assert!(MultipleSubBuilder::default().build(vs).is_empty());
471        assert!(AlternateSubBuilder::default().build(vs).is_empty());
472        assert!(LigatureSubBuilder::default().build(vs).is_empty());
473    }
474}