Skip to main content

write_fonts/tables/
cvar.rs

1//! The [cvar](https://learn.microsoft.com/en-us/typography/opentype/spec/cvar) table.
2//!
3//! This table uses a tuple variation store very similar to per-glyph variation
4//! data in `gvar`, but with one set of scalar deltas (CVT entries) instead of
5//! paired x/y deltas.
6
7use 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
25/// Delta values for one region in design space.
26pub 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    /// Construct a `cvar` table from a set of tuple deltas and expected axis count.
45    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    /// Create a new set of CVT deltas for a tuple variation.
75    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}