use font_types::{BigEndian, F2Dot14, Fixed};
use super::Error;
use crate::tables::variations::{ItemVariationData, ItemVariationStore};
const MAX_PRECOMPUTED_SCALARS: usize = 16;
pub struct BlendState<'a> {
store: ItemVariationStore<'a>,
coords: &'a [F2Dot14],
store_index: u16,
data: Option<ItemVariationData<'a>>,
region_indices: &'a [BigEndian<u16>],
scalars: [Fixed; MAX_PRECOMPUTED_SCALARS],
}
impl<'a> BlendState<'a> {
pub fn new(
store: ItemVariationStore<'a>,
coords: &'a [F2Dot14],
store_index: u16,
) -> Result<Self, Error> {
let mut state = Self {
store,
coords,
store_index,
data: None,
region_indices: &[],
scalars: Default::default(),
};
state.update_precomputed_scalars()?;
Ok(state)
}
pub fn set_store_index(&mut self, store_index: u16) -> Result<(), Error> {
if self.store_index != store_index {
self.store_index = store_index;
self.update_precomputed_scalars()?;
}
Ok(())
}
pub fn region_count(&self) -> Result<usize, Error> {
Ok(self.region_indices.len())
}
pub fn scalars(&self) -> Result<impl Iterator<Item = Result<Fixed, Error>> + '_, Error> {
let total_count = self.region_indices.len();
let cached = &self.scalars[..MAX_PRECOMPUTED_SCALARS.min(total_count)];
let remaining_regions = if total_count > MAX_PRECOMPUTED_SCALARS {
&self.region_indices[MAX_PRECOMPUTED_SCALARS..]
} else {
&[]
};
Ok(cached.iter().copied().map(Ok).chain(
remaining_regions
.iter()
.map(|region_ix| self.region_scalar(region_ix.get())),
))
}
fn update_precomputed_scalars(&mut self) -> Result<(), Error> {
self.data = None;
self.region_indices = &[];
let store = &self.store;
let variation_data = store.item_variation_data();
let data = variation_data
.get(self.store_index as usize)
.ok_or(Error::InvalidVariationStoreIndex(self.store_index))??;
let region_indices = data.region_indexes();
let regions = self.store.variation_region_list()?.variation_regions();
for (region_ix, scalar) in region_indices
.iter()
.take(MAX_PRECOMPUTED_SCALARS)
.zip(&mut self.scalars)
{
let region = regions.get(region_ix.get() as usize)?;
*scalar = region.compute_scalar(self.coords);
}
self.data = Some(data);
self.region_indices = region_indices;
Ok(())
}
fn region_scalar(&self, index: u16) -> Result<Fixed, Error> {
Ok(self
.store
.variation_region_list()?
.variation_regions()
.get(index as usize)
.map_err(Error::Read)?
.compute_scalar(self.coords))
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{FontData, FontRead};
#[test]
fn example_blends() {
example_test(&[-1.0], &[0.0, 1.0]);
example_test(&[-0.25], &[0.5, 0.0]);
example_test(&[-0.5], &[1.0, 0.0]);
example_test(&[-0.75], &[0.5, 0.5]);
example_test(&[0.0], &[0.0, 0.0]);
example_test(&[0.5], &[0.0, 0.0]);
example_test(&[1.0], &[0.0, 0.0]);
}
fn example_test(coords: &[f32], expected: &[f64]) {
let scalars = example_scalars_for_coords(coords);
let expected: Vec<_> = expected.iter().copied().map(Fixed::from_f64).collect();
assert_eq!(scalars, expected);
}
fn example_scalars_for_coords(coords: &[f32]) -> Vec<Fixed> {
let ivs = example_ivs();
let coords: Vec<_> = coords
.iter()
.map(|coord| F2Dot14::from_f32(*coord))
.collect();
let blender = BlendState::new(ivs, &coords, 0).unwrap();
blender.scalars().unwrap().map(|res| res.unwrap()).collect()
}
fn example_ivs() -> ItemVariationStore<'static> {
let ivs_data = &font_test_data::cff2::EXAMPLE[18..];
ItemVariationStore::read(FontData::new(ivs_data)).unwrap()
}
}