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 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)]
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        let coverage = self.items.keys().copied().collect();
207        let seq_tables = self.items.into_values().map(super::Sequence::new).collect();
208        vec![super::MultipleSubstFormat1::new(coverage, seq_tables)]
209    }
210}
211
212impl MultipleSubBuilder {
213    /// Returns the number of rules in the builder.
214    pub fn len(&self) -> usize {
215        self.items.len()
216    }
217
218    /// Returns `true` if there are no rules in this builder.
219    pub fn is_empty(&self) -> bool {
220        self.items.is_empty()
221    }
222
223    /// Add a new substitution to this builder.
224    ///
225    /// If the target already exists with a different replacement, it will be
226    /// overwritten.
227    pub fn insert(&mut self, target: GlyphId16, replacement: Vec<GlyphId16>) {
228        self.items.insert(target, replacement);
229    }
230
231    /// Returns `true` if no other replacement already exists for this target.
232    pub fn can_add(&self, target: GlyphId16, replacement: &[GlyphId16]) -> bool {
233        match self.items.get(&target) {
234            None => true,
235            Some(thing) => thing == replacement,
236        }
237    }
238
239    /// Iterate over the rules in this builder.
240    pub fn iter(&self) -> impl Iterator<Item = (&GlyphId16, &Vec<GlyphId16>)> {
241        self.items.iter()
242    }
243}
244
245/// A builder for [`AlternateSubstFormat1`](super::AlternateSubstFormat1) subtables
246#[derive(Clone, Debug, Default)]
247pub struct AlternateSubBuilder {
248    items: BTreeMap<GlyphId16, Vec<GlyphId16>>,
249}
250
251impl AlternateSubBuilder {
252    /// Returns the number of rules in the builder.
253    pub fn len(&self) -> usize {
254        self.items.len()
255    }
256
257    /// Returns `true` if there are no rules in this builder.
258    pub fn is_empty(&self) -> bool {
259        self.items.is_empty()
260    }
261
262    /// Add a new alternate sub rule to this lookup.
263    pub fn insert(&mut self, target: GlyphId16, replacement: Vec<GlyphId16>) {
264        self.items.insert(target, replacement);
265    }
266
267    /// Iterate over the rules in this builder.
268    pub fn iter(&self) -> impl Iterator<Item = (&GlyphId16, &Vec<GlyphId16>)> {
269        self.items.iter()
270    }
271
272    /// Iterate all alternates in this lookup.
273    ///
274    /// used when compiling aalt
275    pub fn iter_pairs(&self) -> impl Iterator<Item = (GlyphId16, GlyphId16)> + '_ {
276        self.items
277            .iter()
278            .flat_map(|(target, alt)| alt.iter().map(|alt| (*target, *alt)))
279    }
280}
281
282impl Builder for AlternateSubBuilder {
283    type Output = Vec<super::AlternateSubstFormat1>;
284
285    fn build(self, _: &mut VariationStoreBuilder) -> Self::Output {
286        let coverage = self.items.keys().copied().collect();
287        let seq_tables = self
288            .items
289            .into_values()
290            .map(super::AlternateSet::new)
291            .collect();
292        vec![super::AlternateSubstFormat1::new(coverage, seq_tables)]
293    }
294}
295
296/// A builder for [`LigatureSubstFormat1`](super::LigatureSubstFormat1) subtables.
297#[derive(Clone, Debug, Default)]
298pub struct LigatureSubBuilder {
299    items: BTreeMap<GlyphId16, Vec<(Vec<GlyphId16>, GlyphId16)>>,
300}
301
302impl LigatureSubBuilder {
303    /// Returns the number of rules in the builder.
304    pub fn len(&self) -> usize {
305        self.items.len()
306    }
307
308    /// Returns `true` if there are no rules in this builder.
309    pub fn is_empty(&self) -> bool {
310        self.items.is_empty()
311    }
312
313    /// Add a new ligature substitution rule to the builder.
314    pub fn insert(&mut self, target: Vec<GlyphId16>, replacement: GlyphId16) {
315        let (first, rest) = target.split_first().unwrap();
316        let entry = self.items.entry(*first).or_default();
317        // skip duplicates
318        if !entry
319            .iter()
320            .any(|existing| existing.0 == rest && existing.1 == replacement)
321        {
322            entry.push((rest.to_owned(), replacement))
323        }
324    }
325
326    /// Check if this target sequence already has a replacement in this lookup.
327    pub fn can_add(&self, target: &[GlyphId16], replacement: GlyphId16) -> bool {
328        let Some((first, rest)) = target.split_first() else {
329            return false;
330        };
331        match self.items.get(first) {
332            Some(ligs) => !ligs
333                .iter()
334                .any(|(seq, target)| seq == rest && *target != replacement),
335            None => true,
336        }
337    }
338
339    /// Iterate over the current rules in the builder.
340    ///
341    /// The result is a tuple where the first item is the target glyph and the
342    /// second item is a tuple of (components, replacement).
343    pub fn iter(&self) -> impl Iterator<Item = (&GlyphId16, &Vec<(Vec<GlyphId16>, GlyphId16)>)> {
344        self.items.iter()
345    }
346}
347
348impl Builder for LigatureSubBuilder {
349    type Output = Vec<super::LigatureSubstFormat1>;
350
351    fn build(self, _: &mut VariationStoreBuilder) -> Self::Output {
352        let mut splitter = TableSplitter::<super::LigatureSubstFormat1>::new();
353        for (gid, mut ligs) in self.items.into_iter() {
354            // we want to sort longer items first, but otherwise preserve
355            // the order provided by the user.
356            ligs.sort_by_key(|(lig, _)| std::cmp::Reverse(lig.len()));
357            let lig_set = super::LigatureSet::new(
358                ligs.into_iter()
359                    .map(|(components, replacement)| super::Ligature::new(replacement, components))
360                    .collect(),
361            );
362            splitter.add(gid, lig_set);
363        }
364        splitter.finish()
365    }
366}
367
368impl SplitTable for super::LigatureSubstFormat1 {
369    type Component = super::LigatureSet;
370
371    fn size_for_item(item: &Self::Component) -> usize {
372        item.compute_size()
373    }
374
375    fn initial_size_for_item(_item: &Self::Component) -> usize {
376        // format, coverage offset, set count, sets offset
377        u16::RAW_BYTE_LEN * 4
378    }
379
380    fn instantiate(coverage: CoverageTable, items: Vec<Self::Component>) -> Self {
381        Self::new(coverage, items)
382    }
383}
384
385impl super::LigatureSet {
386    fn compute_size(&self) -> usize {
387        // ligatureCount
388        u16::RAW_BYTE_LEN
389            // ligatureOffsets
390            + Offset16::RAW_BYTE_LEN * self.ligatures.len()
391            // size of each referenced ligature table
392            + self
393                .ligatures
394                .iter()
395                .map(|lig| lig.compute_size())
396                .sum::<usize>()
397    }
398}
399
400impl super::Ligature {
401    fn compute_size(&self) -> usize {
402        // ligatureGlyph
403        u16::RAW_BYTE_LEN
404            // componentCount
405            + u16::RAW_BYTE_LEN
406            // componentGlyphIDs
407            + u16::RAW_BYTE_LEN * self.component_glyph_ids.len()
408    }
409}
410
411#[cfg(test)]
412mod tests {
413    use crate::tables::gsub::LigatureSubstFormat1;
414
415    use super::*;
416
417    fn make_lig_table(n_bytes: u16, first: u16) -> super::super::Ligature {
418        assert!(n_bytes >= 6, "minimum table size");
419        assert!(n_bytes % 2 == 0, "can only generate even sizes: {n_bytes}");
420        // 6 bytes per Ligature (header, including offset)
421        let n_glyphs = (n_bytes - 6) / 2;
422        let components = (first..=first + n_glyphs).map(GlyphId16::new).collect();
423        super::super::Ligature::new(GlyphId16::new(first), components)
424    }
425    fn make_2048_bytes_of_ligature() -> super::super::LigatureSet {
426        // 4 bytes for header
427        // 2048 - 4 = 2044
428        // 2044 / 2 = 1022
429        let lig1 = make_lig_table(1022, 1);
430        let lig2 = make_lig_table(1022, 3);
431        super::super::LigatureSet::new(vec![lig1, lig2])
432    }
433
434    #[test]
435    fn who_tests_the_testers1() {
436        for size in [6, 12, 144, 2046, u16::MAX - 1] {
437            let table = make_lig_table(size, 1);
438            let bytes = crate::dump_table(&table).unwrap();
439            assert_eq!(bytes.len(), size as usize);
440        }
441    }
442
443    #[test]
444    fn splitting_ligature_subs() {
445        let mut splitter = TableSplitter::<LigatureSubstFormat1>::new();
446        let ligset = make_2048_bytes_of_ligature();
447        for gid in 0u16..31 {
448            // in real packing these would be deduplicated but we can't now that here
449            splitter.add(GlyphId16::new(gid), ligset.clone());
450        }
451
452        // 31 * 2048 < u16::MAX
453        assert_eq!(splitter.clone().finish().len(), 1);
454        splitter.add(GlyphId16::new(32), ligset);
455        // 32 * 2048 < u16::MAX
456        assert_eq!(splitter.finish().len(), 2)
457    }
458}