1//! Support for the dictionary and charstring blend operator.
23use font_types::{BigEndian, F2Dot14, Fixed};
45use super::Error;
6use crate::tables::variations::{ItemVariationData, ItemVariationStore};
78/// 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.
14const MAX_PRECOMPUTED_SCALARS: usize = 16;
1516/// 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>
28pub 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`
33data: Option<ItemVariationData<'a>>,
34 region_indices: &'a [BigEndian<u16>],
35 scalars: [Fixed; MAX_PRECOMPUTED_SCALARS],
36}
3738impl<'a> BlendState<'a> {
39pub fn new(
40 store: ItemVariationStore<'a>,
41 coords: &'a [F2Dot14],
42 store_index: u16,
43 ) -> Result<Self, Error> {
44let 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()?;
53Ok(state)
54 }
5556/// 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.
60pub fn set_store_index(&mut self, store_index: u16) -> Result<(), Error> {
61if self.store_index != store_index {
62self.store_index = store_index;
63self.update_precomputed_scalars()?;
64 }
65Ok(())
66 }
6768/// Returns the number of variation regions for the currently active
69 /// variation store index.
70pub fn region_count(&self) -> Result<usize, Error> {
71Ok(self.region_indices.len())
72 }
7374/// Returns an iterator yielding scalars for each variation region of
75 /// the currently active variation store index.
76pub fn scalars(&self) -> Result<impl Iterator<Item = Result<Fixed, Error>> + '_, Error> {
77let total_count = self.region_indices.len();
78let cached = &self.scalars[..MAX_PRECOMPUTED_SCALARS.min(total_count)];
79let remaining_regions = if total_count > MAX_PRECOMPUTED_SCALARS {
80&self.region_indices[MAX_PRECOMPUTED_SCALARS..]
81 } else {
82&[]
83 };
84Ok(cached.iter().copied().map(Ok).chain(
85 remaining_regions
86 .iter()
87 .map(|region_ix| self.region_scalar(region_ix.get())),
88 ))
89 }
9091fn update_precomputed_scalars(&mut self) -> Result<(), Error> {
92self.data = None;
93self.region_indices = &[];
94let store = &self.store;
95let variation_data = store.item_variation_data();
96let data = variation_data
97 .get(self.store_index as usize)
98 .ok_or(Error::InvalidVariationStoreIndex(self.store_index))??;
99let region_indices = data.region_indexes();
100let regions = self.store.variation_region_list()?.variation_regions();
101// Precompute scalars for all regions up to MAX_PRECOMPUTED_SCALARS
102for (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
109let region = regions.get(region_ix.get() as usize)?;
110*scalar = region.compute_scalar(self.coords);
111 }
112self.data = Some(data);
113self.region_indices = region_indices;
114Ok(())
115 }
116117fn region_scalar(&self, index: u16) -> Result<Fixed, Error> {
118Ok(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}
127128#[cfg(test)]
129mod test {
130use super::*;
131use crate::{FontData, FontRead};
132133#[test]
134fn example_blends() {
135// args are (coords, expected_scalars)
136example_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 }
144145fn example_test(coords: &[f32], expected: &[f64]) {
146let scalars = example_scalars_for_coords(coords);
147let expected: Vec<_> = expected.iter().copied().map(Fixed::from_f64).collect();
148assert_eq!(scalars, expected);
149 }
150151fn example_scalars_for_coords(coords: &[f32]) -> Vec<Fixed> {
152let ivs = example_ivs();
153let coords: Vec<_> = coords
154 .iter()
155 .map(|coord| F2Dot14::from_f32(*coord))
156 .collect();
157let blender = BlendState::new(ivs, &coords, 0).unwrap();
158 blender.scalars().unwrap().map(|res| res.unwrap()).collect()
159 }
160161fn example_ivs() -> ItemVariationStore<'static> {
162// ItemVariationStore is at offset 18 in the CFF example table
163let ivs_data = &font_test_data::cff2::EXAMPLE[18..];
164 ItemVariationStore::read(FontData::new(ivs_data)).unwrap()
165 }
166}