1 //! Support for the dictionary and charstring blend operator.
2 
3 use font_types::{BigEndian, F2Dot14, Fixed};
4 
5 use super::Error;
6 use crate::tables::variations::{ItemVariationData, ItemVariationStore};
7 
8 /// The maximum number of region scalars that we precompute.
9 ///
10 /// Completely made up number chosen to balance size with trying to capture as
11 /// many precomputed regions as possible.
12 ///
13 /// TODO: measure with a larger set of CFF2 fonts and adjust accordingly.
14 const MAX_PRECOMPUTED_SCALARS: usize = 16;
15 
16 /// State for processing the blend operator for DICTs and charstrings.
17 ///
18 /// To avoid allocation, scalars are computed on demand but this can be
19 /// prohibitively expensive in charstrings where blends are applied to
20 /// large numbers of elements. To amortize the cost, a fixed number of
21 /// precomputed scalars are stored internally and the overflow is computed
22 /// as needed.
23 ///
24 /// The `MAX_PRECOMPUTED_SCALARS` constant determines the size of the
25 /// internal buffer (currently 16).
26 ///
27 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#45-variation-data-operators>
28 pub struct BlendState<'a> {
29     store: ItemVariationStore<'a>,
30     coords: &'a [F2Dot14],
31     store_index: u16,
32     // The following are dependent on the current `store_index`
33     data: Option<ItemVariationData<'a>>,
34     region_indices: &'a [BigEndian<u16>],
35     scalars: [Fixed; MAX_PRECOMPUTED_SCALARS],
36 }
37 
38 impl<'a> BlendState<'a> {
new( store: ItemVariationStore<'a>, coords: &'a [F2Dot14], store_index: u16, ) -> Result<Self, Error>39     pub fn new(
40         store: ItemVariationStore<'a>,
41         coords: &'a [F2Dot14],
42         store_index: u16,
43     ) -> Result<Self, Error> {
44         let mut state = Self {
45             store,
46             coords,
47             store_index,
48             data: None,
49             region_indices: &[],
50             scalars: Default::default(),
51         };
52         state.update_precomputed_scalars()?;
53         Ok(state)
54     }
55 
56     /// Sets the active variation store index.
57     ///
58     /// This should be called with the operand of the `vsindex` operator
59     /// for both DICTs and charstrings.
set_store_index(&mut self, store_index: u16) -> Result<(), Error>60     pub fn set_store_index(&mut self, store_index: u16) -> Result<(), Error> {
61         if self.store_index != store_index {
62             self.store_index = store_index;
63             self.update_precomputed_scalars()?;
64         }
65         Ok(())
66     }
67 
68     /// Returns the number of variation regions for the currently active
69     /// variation store index.
region_count(&self) -> Result<usize, Error>70     pub fn region_count(&self) -> Result<usize, Error> {
71         Ok(self.region_indices.len())
72     }
73 
74     /// Returns an iterator yielding scalars for each variation region of
75     /// the currently active variation store index.
scalars(&self) -> Result<impl Iterator<Item = Result<Fixed, Error>> + '_, Error>76     pub fn scalars(&self) -> Result<impl Iterator<Item = Result<Fixed, Error>> + '_, Error> {
77         let total_count = self.region_indices.len();
78         let cached = &self.scalars[..MAX_PRECOMPUTED_SCALARS.min(total_count)];
79         let remaining_regions = if total_count > MAX_PRECOMPUTED_SCALARS {
80             &self.region_indices[MAX_PRECOMPUTED_SCALARS..]
81         } else {
82             &[]
83         };
84         Ok(cached.iter().copied().map(Ok).chain(
85             remaining_regions
86                 .iter()
87                 .map(|region_ix| self.region_scalar(region_ix.get())),
88         ))
89     }
90 
update_precomputed_scalars(&mut self) -> Result<(), Error>91     fn update_precomputed_scalars(&mut self) -> Result<(), Error> {
92         self.data = None;
93         self.region_indices = &[];
94         let store = &self.store;
95         let varation_data = store.item_variation_data();
96         let data = varation_data
97             .get(self.store_index as usize)
98             .ok_or(Error::InvalidVariationStoreIndex(self.store_index))??;
99         let region_indices = data.region_indexes();
100         let regions = self.store.variation_region_list()?.variation_regions();
101         // Precompute scalars for all regions up to MAX_PRECOMPUTED_SCALARS
102         for (region_ix, scalar) in region_indices
103             .iter()
104             .take(MAX_PRECOMPUTED_SCALARS)
105             .zip(&mut self.scalars)
106         {
107             // We can't use region_scalar here because self is already borrowed
108             // as mutable above
109             let region = regions.get(region_ix.get() as usize)?;
110             *scalar = region.compute_scalar(self.coords);
111         }
112         self.data = Some(data);
113         self.region_indices = region_indices;
114         Ok(())
115     }
116 
region_scalar(&self, index: u16) -> Result<Fixed, Error>117     fn region_scalar(&self, index: u16) -> Result<Fixed, Error> {
118         Ok(self
119             .store
120             .variation_region_list()?
121             .variation_regions()
122             .get(index as usize)
123             .map_err(Error::Read)?
124             .compute_scalar(self.coords))
125     }
126 }
127 
128 #[cfg(test)]
129 mod test {
130     use super::*;
131     use crate::{FontData, FontRead};
132 
133     #[test]
example_blends()134     fn example_blends() {
135         // args are (coords, expected_scalars)
136         example_test(&[-1.0], &[0.0, 1.0]);
137         example_test(&[-0.25], &[0.5, 0.0]);
138         example_test(&[-0.5], &[1.0, 0.0]);
139         example_test(&[-0.75], &[0.5, 0.5]);
140         example_test(&[0.0], &[0.0, 0.0]);
141         example_test(&[0.5], &[0.0, 0.0]);
142         example_test(&[1.0], &[0.0, 0.0]);
143     }
144 
example_test(coords: &[f32], expected: &[f64])145     fn example_test(coords: &[f32], expected: &[f64]) {
146         let scalars = example_scalars_for_coords(coords);
147         let expected: Vec<_> = expected.iter().copied().map(Fixed::from_f64).collect();
148         assert_eq!(scalars, expected);
149     }
150 
example_scalars_for_coords(coords: &[f32]) -> Vec<Fixed>151     fn example_scalars_for_coords(coords: &[f32]) -> Vec<Fixed> {
152         let ivs = example_ivs();
153         let coords: Vec<_> = coords
154             .iter()
155             .map(|coord| F2Dot14::from_f32(*coord))
156             .collect();
157         let blender = BlendState::new(ivs, &coords, 0).unwrap();
158         blender.scalars().unwrap().map(|res| res.unwrap()).collect()
159     }
160 
example_ivs() -> ItemVariationStore<'static>161     fn example_ivs() -> ItemVariationStore<'static> {
162         // ItemVariationStore is at offset 18 in the CFF example table
163         let ivs_data = &font_test_data::cff2::EXAMPLE[18..];
164         ItemVariationStore::read(FontData::new(ivs_data)).unwrap()
165     }
166 }
167