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)]
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 `true` if there are no substitutions in this builder.
118    pub fn is_empty(&self) -> bool {
119        self.items.is_empty()
120    }
121
122    /// Iterate all the substitution pairs in this builder.
123    ///
124    /// used when compiling the `aalt` feature.
125    pub fn iter_pairs(&self) -> impl Iterator<Item = (GlyphId16, GlyphId16)> + '_ {
126        self.items.iter().map(|(target, alt)| (*target, *alt))
127    }
128
129    /// Convert this `SingleSubBuilder` into a `MultipleSubBuilder`.
130    ///
131    /// This is used by the fea compiler in some cases to reduce the number
132    /// of generated lookups.
133    pub fn promote_to_multi_sub(self) -> MultipleSubBuilder {
134        MultipleSubBuilder {
135            items: self
136                .items
137                .into_iter()
138                .map(|(key, gid)| (key, vec![gid]))
139                .collect(),
140        }
141    }
142}
143
144impl Builder for SingleSubBuilder {
145    type Output = Vec<super::SingleSubst>;
146
147    fn build(self, _: &mut VariationStoreBuilder) -> Self::Output {
148        if self.items.is_empty() {
149            return Default::default();
150        }
151        // if all pairs are equidistant and within the i16 range, find the
152        // common delta
153        let delta = self
154            .items
155            .iter()
156            .map(|(k, v)| v.to_u16() as i32 - k.to_u16() as i32)
157            .reduce(|acc, val| if acc == val { acc } else { i32::MAX })
158            .and_then(|delta| i16::try_from(delta).ok());
159
160        let coverage = self.items.keys().copied().collect();
161        if let Some(delta) = delta {
162            vec![super::SingleSubst::format_1(coverage, delta)]
163        } else {
164            let replacements = self.items.values().copied().collect();
165            vec![super::SingleSubst::format_2(coverage, replacements)]
166        }
167    }
168}
169
170/// A builder for [`MultipleSubstFormat1`](super::MultipleSubstFormat1) subtables.
171#[derive(Clone, Debug, Default)]
172pub struct MultipleSubBuilder {
173    items: BTreeMap<GlyphId16, Vec<GlyphId16>>,
174}
175
176impl Builder for MultipleSubBuilder {
177    type Output = Vec<super::MultipleSubstFormat1>;
178
179    fn build(self, _: &mut VariationStoreBuilder) -> Self::Output {
180        let coverage = self.items.keys().copied().collect();
181        let seq_tables = self.items.into_values().map(super::Sequence::new).collect();
182        vec![super::MultipleSubstFormat1::new(coverage, seq_tables)]
183    }
184}
185
186impl MultipleSubBuilder {
187    /// Add a new substitution to this builder.
188    ///
189    /// If the target already exists with a different replacement, it will be
190    /// overwritten.
191    pub fn insert(&mut self, target: GlyphId16, replacement: Vec<GlyphId16>) {
192        self.items.insert(target, replacement);
193    }
194
195    /// Returns `true` if no other replacement already exists for this target.
196    pub fn can_add(&self, target: GlyphId16, replacement: &[GlyphId16]) -> bool {
197        match self.items.get(&target) {
198            None => true,
199            Some(thing) => thing == replacement,
200        }
201    }
202}
203
204/// A builder for [`AlternateSubstFormat1`](super::AlternateSubstFormat1) subtables
205#[derive(Clone, Debug, Default)]
206pub struct AlternateSubBuilder {
207    items: BTreeMap<GlyphId16, Vec<GlyphId16>>,
208}
209
210impl AlternateSubBuilder {
211    /// Add a new alternate sub rule to this lookup.
212    pub fn insert(&mut self, target: GlyphId16, replacement: Vec<GlyphId16>) {
213        self.items.insert(target, replacement);
214    }
215
216    /// Returns `true` if this builder contains no rules.
217    pub fn is_empty(&self) -> bool {
218        self.items.is_empty()
219    }
220
221    /// Iterate all alternates in this lookup.
222    ///
223    /// used when compiling aalt
224    pub fn iter_pairs(&self) -> impl Iterator<Item = (GlyphId16, GlyphId16)> + '_ {
225        self.items
226            .iter()
227            .flat_map(|(target, alt)| alt.iter().map(|alt| (*target, *alt)))
228    }
229}
230
231impl Builder for AlternateSubBuilder {
232    type Output = Vec<super::AlternateSubstFormat1>;
233
234    fn build(self, _: &mut VariationStoreBuilder) -> Self::Output {
235        let coverage = self.items.keys().copied().collect();
236        let seq_tables = self
237            .items
238            .into_values()
239            .map(super::AlternateSet::new)
240            .collect();
241        vec![super::AlternateSubstFormat1::new(coverage, seq_tables)]
242    }
243}
244
245/// A builder for [`LigatureSubstFormat1`](super::LigatureSubstFormat1) subtables.
246#[derive(Clone, Debug, Default)]
247pub struct LigatureSubBuilder {
248    items: BTreeMap<GlyphId16, Vec<(Vec<GlyphId16>, GlyphId16)>>,
249}
250
251impl LigatureSubBuilder {
252    /// Add a new ligature substitution rule to the builder.
253    pub fn insert(&mut self, target: Vec<GlyphId16>, replacement: GlyphId16) {
254        let (first, rest) = target.split_first().unwrap();
255        let entry = self.items.entry(*first).or_default();
256        // skip duplicates
257        if !entry
258            .iter()
259            .any(|existing| (existing.0 == rest && existing.1 == replacement))
260        {
261            entry.push((rest.to_owned(), replacement))
262        }
263    }
264
265    /// Check if this target sequence already has a replacement in this lookup.
266    pub fn can_add(&self, target: &[GlyphId16], replacement: GlyphId16) -> bool {
267        let Some((first, rest)) = target.split_first() else {
268            return false;
269        };
270        match self.items.get(first) {
271            Some(ligs) => !ligs
272                .iter()
273                .any(|(seq, target)| seq == rest && *target != replacement),
274            None => true,
275        }
276    }
277}
278
279impl Builder for LigatureSubBuilder {
280    type Output = Vec<super::LigatureSubstFormat1>;
281
282    fn build(self, _: &mut VariationStoreBuilder) -> Self::Output {
283        let mut splitter = TableSplitter::<super::LigatureSubstFormat1>::new();
284        for (gid, mut ligs) in self.items.into_iter() {
285            // we want to sort longer items first, but otherwise preserve
286            // the order provided by the user.
287            ligs.sort_by_key(|(lig, _)| std::cmp::Reverse(lig.len()));
288            let lig_set = super::LigatureSet::new(
289                ligs.into_iter()
290                    .map(|(components, replacement)| super::Ligature::new(replacement, components))
291                    .collect(),
292            );
293            splitter.add(gid, lig_set);
294        }
295        splitter.finish()
296    }
297}
298
299impl SplitTable for super::LigatureSubstFormat1 {
300    type Component = super::LigatureSet;
301
302    fn size_for_item(item: &Self::Component) -> usize {
303        item.compute_size()
304    }
305
306    fn initial_size_for_item(_item: &Self::Component) -> usize {
307        // format, coverage offset, set count, sets offset
308        u16::RAW_BYTE_LEN * 4
309    }
310
311    fn instantiate(coverage: CoverageTable, items: Vec<Self::Component>) -> Self {
312        Self::new(coverage, items)
313    }
314}
315
316impl super::LigatureSet {
317    fn compute_size(&self) -> usize {
318        // ligatureCount
319        u16::RAW_BYTE_LEN
320            // ligatureOffsets
321            + Offset16::RAW_BYTE_LEN * self.ligatures.len()
322            // size of each referenced ligature table
323            + self
324                .ligatures
325                .iter()
326                .map(|lig| lig.compute_size())
327                .sum::<usize>()
328    }
329}
330
331impl super::Ligature {
332    fn compute_size(&self) -> usize {
333        // ligatureGlyph
334        u16::RAW_BYTE_LEN
335            // componentCount
336            + u16::RAW_BYTE_LEN
337            // componentGlyphIDs
338            + u16::RAW_BYTE_LEN * self.component_glyph_ids.len()
339    }
340}
341
342#[cfg(test)]
343mod tests {
344    use crate::tables::gsub::LigatureSubstFormat1;
345
346    use super::*;
347
348    fn make_lig_table(n_bytes: u16, first: u16) -> super::super::Ligature {
349        assert!(n_bytes >= 6, "minimum table size");
350        assert!(n_bytes % 2 == 0, "can only generate even sizes: {n_bytes}");
351        // 6 bytes per Ligature (header, including offset)
352        let n_glyphs = (n_bytes - 6) / 2;
353        let components = (first..=first + n_glyphs).map(GlyphId16::new).collect();
354        super::super::Ligature::new(GlyphId16::new(first), components)
355    }
356    fn make_2048_bytes_of_ligature() -> super::super::LigatureSet {
357        // 4 bytes for header
358        // 2048 - 4 = 2044
359        // 2044 / 2 = 1022
360        let lig1 = make_lig_table(1022, 1);
361        let lig2 = make_lig_table(1022, 3);
362        super::super::LigatureSet::new(vec![lig1, lig2])
363    }
364
365    #[test]
366    fn who_tests_the_testers1() {
367        for size in [6, 12, 144, 2046, u16::MAX - 1] {
368            let table = make_lig_table(size, 1);
369            let bytes = crate::dump_table(&table).unwrap();
370            assert_eq!(bytes.len(), size as usize);
371        }
372    }
373
374    #[test]
375    fn splitting_ligature_subs() {
376        let mut splitter = TableSplitter::<LigatureSubstFormat1>::new();
377        let ligset = make_2048_bytes_of_ligature();
378        for gid in 0u16..31 {
379            // in real packing these would be deduplicated but we can't now that here
380            splitter.add(GlyphId16::new(gid), ligset.clone());
381        }
382
383        // 31 * 2048 < u16::MAX
384        assert_eq!(splitter.clone().finish().len(), 1);
385        splitter.add(GlyphId16::new(32), ligset);
386        // 32 * 2048 < u16::MAX
387        assert_eq!(splitter.finish().len(), 2)
388    }
389}