use read_fonts::TopLevelTable;
use types::{MajorMinor, Offset16, Tag};
include!("../../generated/generated_cvar.rs");
use crate::{
table_type::TableType,
tables::variations::{Deltas, TupleVariationStoreInputError},
types::FixedSize,
validate::{Validate, ValidationCtx},
FontWrite, TableWriter,
};
use super::variations::{
compute_shared_points, compute_tuple_variation_count, compute_tuple_variation_data_offset,
PackedDeltas, PackedPointNumbers, Tent, Tuple, TupleVariationCount, TupleVariationHeader,
};
pub type CvtDeltas = Deltas<i32>;
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CvarVariationData {
tuple_variation_headers: Vec<TupleVariationHeader>,
shared_point_numbers: Option<PackedPointNumbers>,
per_tuple_data: Vec<CvarTupleVariationData>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct CvarTupleVariationData {
private_point_numbers: Option<PackedPointNumbers>,
deltas: PackedDeltas,
}
impl Cvar {
pub fn new(
variations: Vec<CvtDeltas>,
axis_count: u16,
) -> Result<Self, TupleVariationStoreInputError<usize>> {
if let Some(first) = variations.first() {
let found_axis_count = first.peak_tuple.len();
first.validate_against(axis_count)?;
let expected_delta_len = first.deltas.len();
for (index, var) in variations[1..].iter().enumerate() {
if var.peak_tuple.len() != found_axis_count {
return Err(TupleVariationStoreInputError::InconsistentAxisCount(index));
}
var.validate_against(axis_count)?;
if var.deltas.len() != expected_delta_len {
return Err(TupleVariationStoreInputError::InconsistentDeltaLength(
index,
));
}
}
}
let tuples = CvarVariationData::from_variations(variations);
Ok(Self {
tuple_variation_headers: tuples,
})
}
}
impl CvtDeltas {
pub fn new(tents: Vec<Tent>, deltas: Vec<i32>) -> Self {
let peak_tuple = Tuple::new(tents.iter().map(Tent::peak).collect());
let intermediate_region = if tents.iter().any(Tent::requires_intermediate) {
Some(tents.iter().map(Tent::bounds).unzip())
} else {
None
};
let best_point_packing = Self::pick_best_point_number_repr(&deltas);
Self {
peak_tuple,
intermediate_region,
deltas,
best_point_packing,
}
}
fn validate_against(
&self,
axis_count: u16,
) -> Result<(), TupleVariationStoreInputError<usize>> {
if self.peak_tuple.len() != axis_count {
return Err(TupleVariationStoreInputError::UnexpectedAxisCount {
expected: axis_count,
actual: self.peak_tuple.len(),
index: 0,
});
}
if let Some((start, end)) = self.intermediate_region.as_ref() {
if start.len() != axis_count || end.len() != axis_count {
return Err(TupleVariationStoreInputError::InconsistentTupleLengths(0));
}
}
Ok(())
}
fn pick_best_point_number_repr(deltas: &[i32]) -> PackedPointNumbers {
if deltas.iter().all(|d| *d != 0) {
return PackedPointNumbers::All;
}
let dense = Self::build_non_sparse_data(deltas);
let sparse = Self::build_sparse_data(deltas);
if sparse.compute_size() < dense.compute_size() {
sparse.private_point_numbers.unwrap()
} else {
PackedPointNumbers::All
}
}
fn build_non_sparse_data(deltas: &[i32]) -> CvarTupleVariationData {
CvarTupleVariationData {
private_point_numbers: Some(PackedPointNumbers::All),
deltas: PackedDeltas::new(deltas.to_vec()),
}
}
fn build_sparse_data(deltas: &[i32]) -> CvarTupleVariationData {
let sparse_deltas = deltas.iter().copied().filter(|delta| *delta != 0).collect();
let point_numbers = deltas
.iter()
.enumerate()
.filter_map(|(i, delta)| (*delta != 0).then_some(i as u16))
.collect();
CvarTupleVariationData {
private_point_numbers: Some(PackedPointNumbers::Some(point_numbers)),
deltas: PackedDeltas::new(sparse_deltas),
}
}
fn build(
self,
shared_points: Option<&PackedPointNumbers>,
) -> (TupleVariationHeader, CvarTupleVariationData) {
let CvtDeltas {
peak_tuple,
intermediate_region,
deltas,
best_point_packing: point_numbers,
} = self;
let has_private_points = Some(&point_numbers) != shared_points;
let packed_deltas = match &point_numbers {
PackedPointNumbers::All => deltas,
PackedPointNumbers::Some(pts) => pts.iter().map(|idx| deltas[*idx as usize]).collect(),
};
let data = CvarTupleVariationData {
private_point_numbers: has_private_points.then_some(point_numbers),
deltas: PackedDeltas::new(packed_deltas),
};
let header = TupleVariationHeader::new(
data.compute_size(),
None,
Some(peak_tuple),
intermediate_region,
has_private_points,
);
(header, data)
}
}
impl CvarVariationData {
fn from_variations(variations: Vec<CvtDeltas>) -> Self {
let shared_points = compute_shared_points(&variations);
let (tuple_variation_headers, per_tuple_data): (Vec<_>, Vec<_>) = variations
.into_iter()
.map(|var| var.build(shared_points.as_ref()))
.unzip();
Self {
tuple_variation_headers,
shared_point_numbers: shared_points,
per_tuple_data,
}
}
fn compute_tuple_variation_count(&self) -> TupleVariationCount {
compute_tuple_variation_count(
self.tuple_variation_headers.len(),
self.shared_point_numbers.is_some(),
)
}
fn compute_data_offset(&self) -> u16 {
compute_tuple_variation_data_offset(
&self.tuple_variation_headers,
MajorMinor::RAW_BYTE_LEN + TupleVariationCount::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN,
)
}
}
impl Validate for CvarVariationData {
fn validate_impl(&self, ctx: &mut ValidationCtx) {
const MAX_TUPLE_VARIATIONS: usize = 4095;
if !(0..=MAX_TUPLE_VARIATIONS).contains(&self.tuple_variation_headers.len()) {
ctx.in_field("tuple_variation_headers", |ctx| {
ctx.report("expected 0-4095 tuple variation tables")
})
}
}
}
impl FontWrite for CvarVariationData {
fn write_into(&self, writer: &mut TableWriter) {
self.compute_tuple_variation_count().write_into(writer);
self.compute_data_offset().write_into(writer);
self.tuple_variation_headers.write_into(writer);
self.shared_point_numbers.write_into(writer);
self.per_tuple_data.write_into(writer);
}
}
impl CvarTupleVariationData {
fn compute_size(&self) -> u16 {
self.private_point_numbers
.as_ref()
.map(PackedPointNumbers::compute_size)
.unwrap_or_default()
.checked_add(self.deltas.compute_size())
.unwrap()
}
}
impl FontWrite for CvarTupleVariationData {
fn write_into(&self, writer: &mut TableWriter) {
self.private_point_numbers.write_into(writer);
self.deltas.write_into(writer);
}
}
#[cfg(test)]
mod tests {
use super::*;
use read_fonts::{FontData, FontRead};
use types::F2Dot14;
fn peaks(peaks: Vec<F2Dot14>) -> Vec<Tent> {
peaks
.into_iter()
.map(|peak| Tent::new(peak, None))
.collect()
}
#[test]
fn cvar_smoke_test() {
let table = Cvar::new(
vec![
CvtDeltas::new(peaks(vec![F2Dot14::from_f32(1.0)]), vec![10, 20, 0, -5, 0]),
CvtDeltas::new(peaks(vec![F2Dot14::from_f32(-1.0)]), vec![0, -6, 8, 0, 0]),
],
1,
)
.unwrap();
let bytes = crate::dump_table(&table).unwrap();
let read = read_fonts::tables::cvar::Cvar::read(FontData::new(&bytes)).unwrap();
assert_eq!(read.version(), MajorMinor::VERSION_1_0);
let var_data = read.variation_data(1).unwrap();
let tuples = var_data.tuples().collect::<Vec<_>>();
assert_eq!(tuples.len(), 2);
let first = tuples[0]
.deltas()
.map(|d| (d.position, d.value))
.filter(|(_, delta)| *delta != 0)
.collect::<Vec<_>>();
let second = tuples[1]
.deltas()
.map(|d| (d.position, d.value))
.filter(|(_, delta)| *delta != 0)
.collect::<Vec<_>>();
assert_eq!(first, vec![(0, 10), (1, 20), (3, -5)]);
assert_eq!(second, vec![(1, -6), (2, 8)]);
}
#[test]
fn shared_points_when_beneficial() {
let variations = vec![
CvtDeltas::new(peaks(vec![F2Dot14::from_f32(1.0)]), vec![0, 3, 0, 4, 0]),
CvtDeltas::new(peaks(vec![F2Dot14::from_f32(0.5)]), vec![0, 7, 0, 2, 0]),
CvtDeltas::new(peaks(vec![F2Dot14::from_f32(-1.0)]), vec![1, 0, 2, 0, 3]),
];
let table = Cvar::new(variations, 1).unwrap();
let bytes = crate::dump_table(&table).unwrap();
let read = read_fonts::tables::cvar::Cvar::read(FontData::new(&bytes)).unwrap();
assert!(read.tuple_variation_count().shared_point_numbers());
}
}