1use read_fonts::TopLevelTable;
8use types::{MajorMinor, Offset16, Tag};
9
10include!("../../generated/generated_cvar.rs");
11
12use crate::{
13 table_type::TableType,
14 tables::variations::{Deltas, TupleVariationStoreInputError},
15 types::FixedSize,
16 validate::{Validate, ValidationCtx},
17 FontWrite, TableWriter,
18};
19
20use super::variations::{
21 compute_shared_points, compute_tuple_variation_count, compute_tuple_variation_data_offset,
22 PackedDeltas, PackedPointNumbers, Tent, Tuple, TupleVariationCount, TupleVariationHeader,
23};
24
25pub type CvtDeltas = Deltas<i32>;
27
28#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct CvarVariationData {
31 tuple_variation_headers: Vec<TupleVariationHeader>,
32 shared_point_numbers: Option<PackedPointNumbers>,
33 per_tuple_data: Vec<CvarTupleVariationData>,
34}
35
36#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38struct CvarTupleVariationData {
39 private_point_numbers: Option<PackedPointNumbers>,
40 deltas: PackedDeltas,
41}
42
43impl Cvar {
44 pub fn new(
46 variations: Vec<CvtDeltas>,
47 axis_count: u16,
48 ) -> Result<Self, TupleVariationStoreInputError<usize>> {
49 if let Some(first) = variations.first() {
50 let found_axis_count = first.peak_tuple.len();
51 first.validate_against(axis_count)?;
52 let expected_delta_len = first.deltas.len();
53 for (index, var) in variations[1..].iter().enumerate() {
54 if var.peak_tuple.len() != found_axis_count {
55 return Err(TupleVariationStoreInputError::InconsistentAxisCount(index));
56 }
57 var.validate_against(axis_count)?;
58 if var.deltas.len() != expected_delta_len {
59 return Err(TupleVariationStoreInputError::InconsistentDeltaLength(
60 index,
61 ));
62 }
63 }
64 }
65
66 let tuples = CvarVariationData::from_variations(variations);
67 Ok(Self {
68 tuple_variation_headers: tuples,
69 })
70 }
71}
72
73impl CvtDeltas {
74 pub fn new(tents: Vec<Tent>, deltas: Vec<i32>) -> Self {
76 let peak_tuple = Tuple::new(tents.iter().map(Tent::peak).collect());
77 let intermediate_region = if tents.iter().any(Tent::requires_intermediate) {
78 Some(tents.iter().map(Tent::bounds).unzip())
79 } else {
80 None
81 };
82
83 let best_point_packing = Self::pick_best_point_number_repr(&deltas);
84 Self {
85 peak_tuple,
86 intermediate_region,
87 deltas,
88 best_point_packing,
89 }
90 }
91
92 fn validate_against(
93 &self,
94 axis_count: u16,
95 ) -> Result<(), TupleVariationStoreInputError<usize>> {
96 if self.peak_tuple.len() != axis_count {
97 return Err(TupleVariationStoreInputError::UnexpectedAxisCount {
98 expected: axis_count,
99 actual: self.peak_tuple.len(),
100 index: 0,
101 });
102 }
103
104 if let Some((start, end)) = self.intermediate_region.as_ref() {
105 if start.len() != axis_count || end.len() != axis_count {
106 return Err(TupleVariationStoreInputError::InconsistentTupleLengths(0));
107 }
108 }
109 Ok(())
110 }
111
112 fn pick_best_point_number_repr(deltas: &[i32]) -> PackedPointNumbers {
113 if deltas.iter().all(|d| *d != 0) {
114 return PackedPointNumbers::All;
115 }
116
117 let dense = Self::build_non_sparse_data(deltas);
118 let sparse = Self::build_sparse_data(deltas);
119
120 if sparse.compute_size() < dense.compute_size() {
121 sparse.private_point_numbers.unwrap()
122 } else {
123 PackedPointNumbers::All
124 }
125 }
126
127 fn build_non_sparse_data(deltas: &[i32]) -> CvarTupleVariationData {
128 CvarTupleVariationData {
129 private_point_numbers: Some(PackedPointNumbers::All),
130 deltas: PackedDeltas::new(deltas.to_vec()),
131 }
132 }
133
134 fn build_sparse_data(deltas: &[i32]) -> CvarTupleVariationData {
135 let sparse_deltas = deltas.iter().copied().filter(|delta| *delta != 0).collect();
136 let point_numbers = deltas
137 .iter()
138 .enumerate()
139 .filter_map(|(i, delta)| (*delta != 0).then_some(i as u16))
140 .collect();
141
142 CvarTupleVariationData {
143 private_point_numbers: Some(PackedPointNumbers::Some(point_numbers)),
144 deltas: PackedDeltas::new(sparse_deltas),
145 }
146 }
147
148 fn build(
149 self,
150 shared_points: Option<&PackedPointNumbers>,
151 ) -> (TupleVariationHeader, CvarTupleVariationData) {
152 let CvtDeltas {
153 peak_tuple,
154 intermediate_region,
155 deltas,
156 best_point_packing: point_numbers,
157 } = self;
158
159 let has_private_points = Some(&point_numbers) != shared_points;
160 let packed_deltas = match &point_numbers {
161 PackedPointNumbers::All => deltas,
162 PackedPointNumbers::Some(pts) => pts.iter().map(|idx| deltas[*idx as usize]).collect(),
163 };
164
165 let data = CvarTupleVariationData {
166 private_point_numbers: has_private_points.then_some(point_numbers),
167 deltas: PackedDeltas::new(packed_deltas),
168 };
169
170 let header = TupleVariationHeader::new(
171 data.compute_size(),
172 None,
173 Some(peak_tuple),
174 intermediate_region,
175 has_private_points,
176 );
177
178 (header, data)
179 }
180}
181
182impl CvarVariationData {
183 fn from_variations(variations: Vec<CvtDeltas>) -> Self {
184 let shared_points = compute_shared_points(&variations);
185 let (tuple_variation_headers, per_tuple_data): (Vec<_>, Vec<_>) = variations
186 .into_iter()
187 .map(|var| var.build(shared_points.as_ref()))
188 .unzip();
189
190 Self {
191 tuple_variation_headers,
192 shared_point_numbers: shared_points,
193 per_tuple_data,
194 }
195 }
196
197 fn compute_tuple_variation_count(&self) -> TupleVariationCount {
198 compute_tuple_variation_count(
199 self.tuple_variation_headers.len(),
200 self.shared_point_numbers.is_some(),
201 )
202 }
203
204 fn compute_data_offset(&self) -> u16 {
205 compute_tuple_variation_data_offset(
206 &self.tuple_variation_headers,
207 MajorMinor::RAW_BYTE_LEN + TupleVariationCount::RAW_BYTE_LEN + Offset16::RAW_BYTE_LEN,
208 )
209 }
210}
211
212impl Validate for CvarVariationData {
213 fn validate_impl(&self, ctx: &mut ValidationCtx) {
214 const MAX_TUPLE_VARIATIONS: usize = 4095;
215 if !(0..=MAX_TUPLE_VARIATIONS).contains(&self.tuple_variation_headers.len()) {
216 ctx.in_field("tuple_variation_headers", |ctx| {
217 ctx.report("expected 0-4095 tuple variation tables")
218 })
219 }
220 }
221}
222
223impl FontWrite for CvarVariationData {
224 fn write_into(&self, writer: &mut TableWriter) {
225 self.compute_tuple_variation_count().write_into(writer);
226 self.compute_data_offset().write_into(writer);
227 self.tuple_variation_headers.write_into(writer);
228 self.shared_point_numbers.write_into(writer);
229 self.per_tuple_data.write_into(writer);
230 }
231}
232
233impl CvarTupleVariationData {
234 fn compute_size(&self) -> u16 {
235 self.private_point_numbers
236 .as_ref()
237 .map(PackedPointNumbers::compute_size)
238 .unwrap_or_default()
239 .checked_add(self.deltas.compute_size())
240 .unwrap()
241 }
242}
243
244impl FontWrite for CvarTupleVariationData {
245 fn write_into(&self, writer: &mut TableWriter) {
246 self.private_point_numbers.write_into(writer);
247 self.deltas.write_into(writer);
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use read_fonts::{FontData, FontRead};
255 use types::F2Dot14;
256
257 fn peaks(peaks: Vec<F2Dot14>) -> Vec<Tent> {
258 peaks
259 .into_iter()
260 .map(|peak| Tent::new(peak, None))
261 .collect()
262 }
263
264 #[test]
265 fn cvar_smoke_test() {
266 let table = Cvar::new(
267 vec![
268 CvtDeltas::new(peaks(vec![F2Dot14::from_f32(1.0)]), vec![10, 20, 0, -5, 0]),
269 CvtDeltas::new(peaks(vec![F2Dot14::from_f32(-1.0)]), vec![0, -6, 8, 0, 0]),
270 ],
271 1,
272 )
273 .unwrap();
274
275 let bytes = crate::dump_table(&table).unwrap();
276 let read = read_fonts::tables::cvar::Cvar::read(FontData::new(&bytes)).unwrap();
277 assert_eq!(read.version(), MajorMinor::VERSION_1_0);
278
279 let var_data = read.variation_data(1).unwrap();
280 let tuples = var_data.tuples().collect::<Vec<_>>();
281 assert_eq!(tuples.len(), 2);
282
283 let first = tuples[0]
284 .deltas()
285 .map(|d| (d.position, d.value))
286 .filter(|(_, delta)| *delta != 0)
287 .collect::<Vec<_>>();
288 let second = tuples[1]
289 .deltas()
290 .map(|d| (d.position, d.value))
291 .filter(|(_, delta)| *delta != 0)
292 .collect::<Vec<_>>();
293
294 assert_eq!(first, vec![(0, 10), (1, 20), (3, -5)]);
295 assert_eq!(second, vec![(1, -6), (2, 8)]);
296 }
297
298 #[test]
299 fn shared_points_when_beneficial() {
300 let variations = vec![
301 CvtDeltas::new(peaks(vec![F2Dot14::from_f32(1.0)]), vec![0, 3, 0, 4, 0]),
302 CvtDeltas::new(peaks(vec![F2Dot14::from_f32(0.5)]), vec![0, 7, 0, 2, 0]),
303 CvtDeltas::new(peaks(vec![F2Dot14::from_f32(-1.0)]), vec![1, 0, 2, 0, 3]),
304 ];
305
306 let table = Cvar::new(variations, 1).unwrap();
307 let bytes = crate::dump_table(&table).unwrap();
308 let read = read_fonts::tables::cvar::Cvar::read(FontData::new(&bytes)).unwrap();
309 assert!(read.tuple_variation_count().shared_point_numbers());
310 }
311}