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