fontcull_write_fonts/tables/
variations.rs

1//! OpenType variations common table formats
2
3include!("../../generated/generated_variations.rs");
4
5pub use fontcull_read_fonts::tables::variations::{DeltaRunType, TupleIndex, TupleVariationCount};
6
7pub mod ivs_builder;
8
9impl TupleVariationHeader {
10    pub fn new(
11        variation_data_size: u16,
12        shared_tuple_idx: Option<u16>,
13        peak_tuple: Option<Tuple>,
14        intermediate_region: Option<(Tuple, Tuple)>,
15        has_private_points: bool,
16    ) -> Self {
17        assert!(
18            shared_tuple_idx.is_some() != peak_tuple.is_some(),
19            "one and only one of peak_tuple or shared_tuple_idx must be present"
20        );
21        let mut idx = shared_tuple_idx.unwrap_or_default();
22        if peak_tuple.is_some() {
23            idx |= TupleIndex::EMBEDDED_PEAK_TUPLE;
24        }
25        if intermediate_region.is_some() {
26            idx |= TupleIndex::INTERMEDIATE_REGION;
27        }
28        if has_private_points {
29            idx |= TupleIndex::PRIVATE_POINT_NUMBERS;
30        }
31        let (intermediate_start_tuple, intermediate_end_tuple) = intermediate_region
32            .map(|(start, end)| (start.values, end.values))
33            .unwrap_or_default();
34
35        TupleVariationHeader {
36            variation_data_size,
37            tuple_index: TupleIndex::from_bits(idx),
38            peak_tuple: peak_tuple.map(|tup| tup.values).unwrap_or_default(),
39            intermediate_start_tuple,
40            intermediate_end_tuple,
41        }
42    }
43
44    /// Return the number of bytes required to encode this header
45    pub fn compute_size(&self) -> u16 {
46        let len: usize = 2 + 2 // variationDataSize, tupleIndex
47        + self.peak_tuple.len() * F2Dot14::RAW_BYTE_LEN
48        + self.intermediate_start_tuple.len()  * F2Dot14::RAW_BYTE_LEN
49        + self.intermediate_end_tuple.len()  * F2Dot14::RAW_BYTE_LEN;
50        len.try_into().unwrap()
51    }
52}
53
54/// <https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#packed-point-numbers>
55#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57pub enum PackedPointNumbers {
58    /// Contains deltas for all point numbers
59    #[default]
60    All,
61    /// Contains deltas only for these specific point numbers
62    Some(Vec<u16>),
63}
64
65/// <https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#packed-deltas>
66#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
68pub struct PackedDeltas {
69    deltas: Vec<i32>,
70}
71
72impl Validate for PackedDeltas {
73    fn validate_impl(&self, _ctx: &mut ValidationCtx) {}
74}
75
76impl FontWrite for PackedDeltas {
77    fn write_into(&self, writer: &mut TableWriter) {
78        for run in self.iter_runs() {
79            run.write_into(writer)
80        }
81    }
82}
83
84impl PackedDeltas {
85    /// Construct a `PackedDeltas` from a vector of raw delta values.
86    pub fn new(deltas: Vec<i32>) -> Self {
87        Self { deltas }
88    }
89
90    /// Compute the number of bytes required to encode these deltas
91    pub(crate) fn compute_size(&self) -> u16 {
92        self.iter_runs().fold(0u16, |acc, run| {
93            acc.checked_add(run.compute_size()).unwrap()
94        })
95    }
96
97    fn iter_runs(&self) -> impl Iterator<Item = PackedDeltaRun<'_>> {
98        // 6 bits for length per https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#packed-deltas
99        const MAX_POINTS_PER_RUN: usize = 64;
100
101        // preferred run type for this value
102        fn preferred_run_type(v: i32) -> DeltaRunType {
103            match v {
104                0 => DeltaRunType::Zero,
105                _ if v > i16::MAX as i32 || v < i16::MIN as i32 => DeltaRunType::I32,
106                _ if v > i8::MAX as i32 || v < i8::MIN as i32 => DeltaRunType::I16,
107                _ => DeltaRunType::I8,
108            }
109        }
110
111        fn count_leading_zeros(slice: &[i32]) -> u8 {
112            slice
113                .iter()
114                .take(MAX_POINTS_PER_RUN)
115                .take_while(|v| **v == 0)
116                .count() as u8
117        }
118
119        /// compute the number of deltas in the next run, and the value type
120        fn next_run_len(slice: &[i32]) -> (usize, DeltaRunType) {
121            let first = *slice.first().expect("bounds checked before here");
122            debug_assert!(first != 0, "Zeroes are supposed to be handled separately");
123            let run_type = preferred_run_type(first);
124
125            let mut idx = 1;
126            while idx < MAX_POINTS_PER_RUN && idx < slice.len() {
127                let cur = slice[idx];
128                let cur_type = preferred_run_type(cur);
129                let next_type = slice.get(idx + 1).copied().map(preferred_run_type);
130
131                // Any reason to stop?
132                if run_type == DeltaRunType::I8 {
133                    // a single zero is best stored literally inline, but two or more
134                    // should get a new run:
135                    // https://github.com/fonttools/fonttools/blob/eeaa499981c587/Lib/fontTools/ttLib/tables/TupleVariation.py#L423
136                    match cur_type {
137                        DeltaRunType::Zero if next_type == Some(DeltaRunType::Zero) => break,
138                        DeltaRunType::I16 | DeltaRunType::I32 => break,
139                        _ => (),
140                    }
141                } else if run_type == DeltaRunType::I16 {
142                    // with word deltas, a single zero justifies a new run:
143                    //https://github.com/fonttools/fonttools/blob/eeaa499981c587/Lib/fontTools/ttLib/tables/TupleVariation.py#L457
144                    match (cur_type, next_type) {
145                        (DeltaRunType::Zero | DeltaRunType::I32, _) => break,
146                        // and a single byte-size value should be inlined, if it lets
147                        // us combine two adjoining word-size runs:
148                        // https://github.com/fonttools/fonttools/blob/eeaa499981c587/Lib/fontTools/ttLib/tables/TupleVariation.py#L467
149                        (DeltaRunType::I8, Some(DeltaRunType::Zero | DeltaRunType::I8)) => break,
150                        _ => (),
151                    }
152                } else if run_type == DeltaRunType::I32 && cur_type != DeltaRunType::I32 {
153                    break;
154                }
155
156                idx += 1;
157            }
158            (idx, run_type)
159        }
160
161        let mut deltas = self.deltas.as_slice();
162
163        std::iter::from_fn(move || {
164            let run_start = *deltas.first()?;
165            if run_start == 0 {
166                let n_zeros = count_leading_zeros(deltas);
167                deltas = &deltas[n_zeros as usize..];
168                Some(PackedDeltaRun::Zeros(n_zeros))
169            } else {
170                let (len, value_type) = next_run_len(deltas);
171                let (head, tail) = deltas.split_at(len);
172                deltas = tail;
173                Some(match value_type {
174                    DeltaRunType::I32 => PackedDeltaRun::FourBytes(head),
175                    DeltaRunType::I16 => PackedDeltaRun::TwoBytes(head),
176                    DeltaRunType::I8 => PackedDeltaRun::OneByte(head),
177                    _ => {
178                        unreachable!("We should have taken the other branch for first={run_start}")
179                    }
180                })
181            }
182        })
183    }
184}
185
186#[derive(Clone, Debug, PartialEq, Eq)]
187enum PackedDeltaRun<'a> {
188    Zeros(u8),
189    OneByte(&'a [i32]),
190    TwoBytes(&'a [i32]),
191    FourBytes(&'a [i32]),
192}
193
194impl PackedDeltaRun<'_> {
195    fn compute_flag(&self) -> u8 {
196        /// Flag indicating that this run contains no data,
197        /// and that the deltas for this run are all zero.
198        const DELTAS_ARE_ZERO: u8 = 0x80;
199        /// Flag indicating the data type for delta values in the run.
200        const DELTAS_ARE_WORDS: u8 = 0x40;
201
202        match self {
203            PackedDeltaRun::Zeros(count) => (count - 1) | DELTAS_ARE_ZERO,
204            PackedDeltaRun::OneByte(deltas) => deltas.len() as u8 - 1,
205            PackedDeltaRun::TwoBytes(deltas) => (deltas.len() as u8 - 1) | DELTAS_ARE_WORDS,
206            PackedDeltaRun::FourBytes(deltas) => {
207                (deltas.len() as u8 - 1) | DELTAS_ARE_WORDS | DELTAS_ARE_ZERO
208            }
209        }
210    }
211
212    fn compute_size(&self) -> u16 {
213        match self {
214            PackedDeltaRun::Zeros(_) => 1,
215            PackedDeltaRun::OneByte(vals) => vals.len() as u16 + 1,
216            PackedDeltaRun::TwoBytes(vals) => vals.len() as u16 * 2 + 1,
217            PackedDeltaRun::FourBytes(vals) => vals.len() as u16 * 4 + 1,
218        }
219    }
220}
221
222impl FontWrite for PackedDeltaRun<'_> {
223    fn write_into(&self, writer: &mut TableWriter) {
224        self.compute_flag().write_into(writer);
225        match self {
226            PackedDeltaRun::Zeros(_) => (),
227            PackedDeltaRun::OneByte(deltas) => {
228                deltas.iter().for_each(|v| (*v as i8).write_into(writer))
229            }
230            PackedDeltaRun::TwoBytes(deltas) => {
231                deltas.iter().for_each(|v| (*v as i16).write_into(writer))
232            }
233            PackedDeltaRun::FourBytes(deltas) => deltas.iter().for_each(|v| v.write_into(writer)),
234        }
235    }
236}
237
238impl crate::validate::Validate for PackedPointNumbers {
239    fn validate_impl(&self, ctx: &mut ValidationCtx) {
240        if let PackedPointNumbers::Some(pts) = self {
241            if pts.len() > 0x7FFF {
242                ctx.report("length cannot be stored in 15 bites");
243            }
244        }
245    }
246}
247
248impl FontWrite for PackedPointNumbers {
249    fn write_into(&self, writer: &mut TableWriter) {
250        // compute the actual count:
251        match self.as_slice().len() {
252            len @ 0..=127 => (len as u8).write_into(writer),
253            len => (len as u16 | 0x8000u16).write_into(writer),
254        }
255        for run in self.iter_runs() {
256            run.write_into(writer);
257        }
258    }
259}
260
261impl PackedPointNumbers {
262    /// Compute the number of bytes required to encode these points
263    pub(crate) fn compute_size(&self) -> u16 {
264        let mut count = match self {
265            PackedPointNumbers::All => return 1,
266            PackedPointNumbers::Some(pts) if pts.len() < 128 => 1u16,
267            PackedPointNumbers::Some(_) => 2,
268        };
269        for run in self.iter_runs() {
270            count = count.checked_add(run.compute_size()).unwrap();
271        }
272        count
273    }
274
275    fn as_slice(&self) -> &[u16] {
276        match self {
277            PackedPointNumbers::All => &[],
278            PackedPointNumbers::Some(pts) => pts.as_slice(),
279        }
280    }
281
282    fn iter_runs(&self) -> impl Iterator<Item = PackedPointRun<'_>> {
283        const U8_MAX: u16 = u8::MAX as u16;
284        const MAX_POINTS_PER_RUN: usize = 128;
285
286        let mut points = match self {
287            PackedPointNumbers::Some(pts) => pts.as_slice(),
288            PackedPointNumbers::All => &[],
289        };
290
291        let mut prev_point = 0u16;
292
293        // split a run off the front of points:
294        // - if point is more than 255 away from prev, we're using words
295        std::iter::from_fn(move || {
296            let next = points.first()?;
297            let are_words = (next - prev_point) > U8_MAX;
298            let run_len = points
299                .iter()
300                .take(MAX_POINTS_PER_RUN)
301                .scan(prev_point, |prev, point| {
302                    let take_this = if are_words {
303                        (point - *prev) > U8_MAX
304                    } else {
305                        (point - *prev) <= U8_MAX
306                    };
307                    *prev = *point;
308                    take_this.then_some(point)
309                })
310                .count();
311
312            let (head, tail) = points.split_at(run_len);
313            points = tail;
314            let last_point = prev_point;
315            prev_point = head.last().copied().unwrap();
316
317            Some(PackedPointRun {
318                last_point,
319                are_words,
320                points: head,
321            })
322        })
323    }
324}
325
326#[derive(Debug, PartialEq, Eq)]
327struct PackedPointRun<'a> {
328    last_point: u16,
329    are_words: bool,
330    points: &'a [u16],
331}
332
333impl PackedPointRun<'_> {
334    fn compute_size(&self) -> u16 {
335        const LEN_BYTE: u16 = 1;
336        let per_point_len = if self.are_words { 2 } else { 1 };
337        self.points.len() as u16 * per_point_len + LEN_BYTE
338    }
339}
340
341impl FontWrite for PackedPointRun<'_> {
342    fn write_into(&self, writer: &mut TableWriter) {
343        assert!(!self.points.is_empty() && self.points.len() <= 128);
344        let mut len = self.points.len() as u8 - 1;
345        if self.are_words {
346            len |= 0x80;
347        }
348        len.write_into(writer);
349        let mut last_point = self.last_point;
350        for point in self.points {
351            let delta = point - last_point;
352            last_point = *point;
353            if self.are_words {
354                delta.write_into(writer);
355            } else {
356                debug_assert!(delta <= u8::MAX as u16);
357                (delta as u8).write_into(writer);
358            }
359        }
360    }
361}
362
363impl FontWrite for TupleIndex {
364    fn write_into(&self, writer: &mut TableWriter) {
365        self.bits().write_into(writer)
366    }
367}
368
369//hack: unclear if we're even going to do any codegen for writing, but
370//for the time being this lets us compile
371impl<'a> FromObjRef<Option<fontcull_read_fonts::tables::variations::Tuple<'a>>> for Vec<F2Dot14> {
372    fn from_obj_ref(
373        from: &Option<fontcull_read_fonts::tables::variations::Tuple<'a>>,
374        _data: FontData,
375    ) -> Self {
376        from.as_ref()
377            .map(|tup| tup.values.iter().map(BigEndian::get).collect())
378            .unwrap_or_default()
379    }
380}
381
382impl Tuple {
383    pub fn len(&self) -> u16 {
384        self.values.len().try_into().unwrap()
385    }
386
387    pub fn is_empty(&self) -> bool {
388        self.values.is_empty()
389    }
390}
391
392impl DeltaSetIndexMap {
393    /// Return the most compact entry format that can represent this mapping.
394    ///
395    /// EntryFormat is a packed u8 field that describes the compressed representation
396    /// of delta-set indices. For more info, see:
397    /// <https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#associating-target-items-to-variation-data>
398    // This is a direct port from fonttools' DeltaSetMap.getEntryFormat:
399    // https://github.com/fonttools/fonttools/blob/6d531f/Lib/fontTools/ttLib/tables/otTables.py#L644-L666
400    fn get_entry_format(mapping: &[u32]) -> EntryFormat {
401        let ored = mapping.iter().fold(0, |acc, idx| acc | *idx);
402
403        let inner = (ored & 0xFFFF) as u16;
404        let inner_bits = (16 - inner.leading_zeros() as u8).max(1);
405        assert!(inner_bits <= 16);
406
407        let ored = (ored >> (16 - inner_bits)) | (ored & ((1 << inner_bits) - 1));
408        let entry_size = match ored {
409            0..=0xFF => 1,
410            0x100..=0xFFFF => 2,
411            0x10000..=0xFFFFFF => 3,
412            _ => 4,
413        };
414
415        EntryFormat::from_bits(((entry_size - 1) << 4) | (inner_bits - 1)).unwrap()
416    }
417
418    /// Compress u32's into packed data using the most compact entry format.
419    ///
420    /// Returns the computed entry format and the packed data.
421    ///
422    /// For more info, see:
423    /// <https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#associating-target-items-to-variation-data>
424    // Ported from fonttools' VarIdxMapValue.write method:
425    // https://github.com/fonttools/fonttools/blob/6d531fe/Lib/fontTools/ttLib/tables/otConverters.py#L1764-L1781
426    fn pack_map_data(mapping: &[u32]) -> (EntryFormat, Vec<u8>) {
427        let fmt = DeltaSetIndexMap::get_entry_format(mapping);
428        let inner_bits = fmt.bit_count();
429        let inner_mask = (1 << inner_bits as u32) - 1;
430        let outer_shift = 16 - inner_bits;
431        let entry_size = fmt.entry_size();
432        assert!((1..=4).contains(&entry_size));
433
434        // omit trailing entries that are the same as the previous one;
435        // the last entry is assumed when index is >= map_count
436        let mut map_count = mapping.len();
437        while map_count > 1 && mapping[map_count - 1] == mapping[map_count - 2] {
438            map_count -= 1;
439        }
440
441        let mut map_data = Vec::with_capacity(map_count * entry_size as usize);
442        for idx in mapping.iter().take(map_count) {
443            let idx = ((idx & 0xFFFF0000) >> outer_shift) | (idx & inner_mask);
444            // append entry_size bytes to map_data in BigEndian order
445            map_data.extend_from_slice(&idx.to_be_bytes()[4 - entry_size as usize..]);
446        }
447        assert_eq!(map_data.len(), map_count * entry_size as usize);
448        (fmt, map_data)
449    }
450}
451
452impl<I> FromIterator<I> for DeltaSetIndexMap
453where
454    I: Into<u32>,
455{
456    /// Create a DeltaSetIndexMap from an iterator of delta-set indices.
457    fn from_iter<T: IntoIterator<Item = I>>(iter: T) -> Self {
458        let mapping: Vec<u32> = iter.into_iter().map(|v| v.into()).collect();
459        let (fmt, map_data) = DeltaSetIndexMap::pack_map_data(&mapping);
460        let map_count = map_data.len() / fmt.entry_size() as usize;
461        let delta_set_index_map: DeltaSetIndexMap = if map_count <= u16::MAX as usize {
462            DeltaSetIndexMap::format_0(fmt, map_count as u16, map_data)
463        } else {
464            DeltaSetIndexMap::format_1(fmt, map_count as u32, map_data)
465        };
466        delta_set_index_map
467    }
468}
469
470#[cfg(test)]
471mod tests {
472    use super::*;
473    use rstest::rstest;
474
475    #[test]
476    fn point_pack_words() {
477        let thing = PackedPointNumbers::Some(vec![1002, 2002, 8408, 12228]);
478
479        let runs = thing.iter_runs().collect::<Vec<_>>();
480        assert_eq!(runs.len(), 1);
481        assert!(runs[0].are_words);
482        assert_eq!(runs[0].last_point, 0);
483        assert_eq!(runs[0].points, &[1002, 2002, 8408, 12228]);
484    }
485
486    #[test]
487    fn serialize_packed_points() {
488        let thing = PackedPointNumbers::Some(vec![1002, 2002, 8408, 12228]);
489
490        let bytes = crate::dump_table(&thing).unwrap();
491        assert_eq!(thing.compute_size() as usize, bytes.len());
492        let (read, _) =
493            fontcull_read_fonts::tables::variations::PackedPointNumbers::split_off_front(
494                FontData::new(&bytes),
495            );
496        assert_eq!(thing.as_slice(), read.iter().collect::<Vec<_>>());
497    }
498
499    #[test]
500    fn point_pack_runs() {
501        let thing = PackedPointNumbers::Some(vec![5, 25, 225, 1002, 2002, 2008, 2228]);
502
503        let runs = thing.iter_runs().collect::<Vec<_>>();
504        assert!(!runs[0].are_words);
505        assert_eq!(runs[0].last_point, 0);
506        assert_eq!(runs[0].points, &[5, 25, 225]);
507
508        assert!(runs[1].are_words);
509        assert_eq!(runs[1].last_point, 225);
510        assert_eq!(runs[1].points, &[1002, 2002]);
511
512        assert!(!runs[2].are_words);
513        assert_eq!(runs[2].last_point, 2002);
514        assert_eq!(runs[2].points, &[2008, 2228]);
515
516        assert_eq!(runs.len(), 3);
517    }
518
519    #[test]
520    fn point_pack_long_runs() {
521        let mut numbers = vec![0u16; 130];
522        numbers.extend(1u16..=130u16);
523        let thing = PackedPointNumbers::Some(numbers);
524
525        let runs = thing.iter_runs().collect::<Vec<_>>();
526        assert!(!runs[0].are_words);
527        assert_eq!(runs[0].points.len(), 128);
528        assert_eq!(runs[1].last_point, 0);
529        assert_eq!(runs[1].points.len(), 128);
530        assert_eq!(runs[2].last_point, 126);
531        assert_eq!(runs[2].points, &[127, 128, 129, 130]);
532        assert!(runs.get(3).is_none());
533    }
534
535    #[test]
536    fn point_pack_write_one_byte() {
537        let thing = PackedPointNumbers::Some(vec![5, 25, 225, 1002, 2002, 2008, 2228, 10000]);
538
539        let bytes = crate::dump_table(&thing).unwrap();
540        assert_eq!(thing.compute_size() as usize, bytes.len());
541        let (read, _) =
542            fontcull_read_fonts::tables::variations::PackedPointNumbers::split_off_front(
543                FontData::new(&bytes),
544            );
545        assert_eq!(thing.as_slice(), read.iter().collect::<Vec<_>>());
546    }
547
548    #[test]
549    fn point_pack_write_two_byte() {
550        let thing = PackedPointNumbers::Some(vec![0; 200]);
551
552        let bytes = crate::dump_table(&thing).unwrap();
553        assert_eq!(thing.compute_size() as usize, bytes.len());
554        let (read, _) =
555            fontcull_read_fonts::tables::variations::PackedPointNumbers::split_off_front(
556                FontData::new(&bytes),
557            );
558        assert_eq!(thing.as_slice(), read.iter().collect::<Vec<_>>());
559    }
560
561    static PACKED_DELTA_BYTES: &[u8] = &[
562        0x03, 0x0A, 0x97, 0x00, 0xC6, 0x87, 0x41, 0x10, 0x22, 0xFB, 0x34,
563    ];
564
565    // <https://learn.microsoft.com/en-us/typography/opentype/spec/otvarcommonformats#packed-deltas>
566    #[test]
567    fn packed_deltas_spec_runs() {
568        let deltas = PackedDeltas::new(vec![10, -105, 0, -58, 0, 0, 0, 0, 0, 0, 0, 0, 4130, -1228]);
569        let runs = deltas.iter_runs().collect::<Vec<_>>();
570        assert_eq!(
571            runs,
572            vec![
573                PackedDeltaRun::OneByte(&[10, -105, 0, -58]),
574                PackedDeltaRun::Zeros(8),
575                PackedDeltaRun::TwoBytes(&[4130, -1228]),
576            ]
577        );
578    }
579
580    #[test]
581    fn packed_deltas_spec_write() {
582        let deltas = PackedDeltas::new(vec![10, -105, 0, -58, 0, 0, 0, 0, 0, 0, 0, 0, 4130, -1228]);
583        let bytes = crate::dump_table(&deltas).unwrap();
584        assert_eq!(bytes, PACKED_DELTA_BYTES);
585        let read = fontcull_read_fonts::tables::variations::PackedDeltas::consume_all(
586            FontData::new(&bytes),
587        );
588        let decoded = read.iter().collect::<Vec<_>>();
589        assert_eq!(deltas.deltas.len(), decoded.len());
590        assert_eq!(deltas.deltas, decoded);
591        assert_eq!(bytes, PACKED_DELTA_BYTES);
592    }
593
594    #[test]
595    fn empty_deltas() {
596        let deltas = PackedDeltas::new(vec![]);
597        let bytes = crate::dump_table(&deltas).unwrap();
598        assert!(bytes.is_empty());
599    }
600
601    #[test]
602    fn lots_of_zero() {
603        let num_zeroes = 65;
604        let deltas = PackedDeltas::new(vec![0; num_zeroes]);
605        assert_eq!(
606            vec![PackedDeltaRun::Zeros(64), PackedDeltaRun::Zeros(1)],
607            deltas.iter_runs().collect::<Vec<_>>()
608        );
609    }
610
611    #[test]
612    fn respect_my_run_length_authority() {
613        let mut values = (1..196).collect::<Vec<_>>();
614        values.extend([0, 0, 0]);
615        values.push(i16::MAX as i32 + 1);
616        values.push(i16::MIN as i32 - 1);
617        values.push(i16::MAX as i32 * 2);
618        let deltas = PackedDeltas::new(values);
619        assert_eq!(
620            vec![
621                // 64 entries per run please and thank you
622                PackedDeltaRun::OneByte(&(1..65).collect::<Vec<i32>>()),
623                // 63 entries this time because at 128 we switch to 2 bytes
624                PackedDeltaRun::OneByte(&(65..128).collect::<Vec<i32>>()),
625                // 64 per run again
626                PackedDeltaRun::TwoBytes(&(128..192).collect::<Vec<i32>>()),
627                // tail
628                PackedDeltaRun::TwoBytes(&(192..=195).collect::<Vec<i32>>()),
629                PackedDeltaRun::Zeros(3),
630                PackedDeltaRun::FourBytes(&[
631                    i16::MAX as i32 + 1,
632                    i16::MIN as i32 - 1,
633                    i16::MAX as i32 * 2
634                ]),
635            ],
636            deltas.iter_runs().collect::<Vec<_>>()
637        )
638    }
639
640    #[test]
641    fn inline_single_zeros_with_bytes() {
642        let packed = PackedDeltas::new(vec![1, 2, 0, 3]);
643        assert_eq!(packed.iter_runs().count(), 1)
644    }
645
646    #[test]
647    fn split_two_zeros_in_bytes() {
648        let packed = PackedDeltas::new(vec![1, 2, 0, 0, 3]);
649        assert_eq!(packed.iter_runs().count(), 3)
650    }
651
652    #[test]
653    fn split_single_zero_in_words() {
654        let packed = PackedDeltas::new(vec![150, 200, 0, -300]);
655        assert_eq!(packed.iter_runs().count(), 3)
656    }
657
658    #[test]
659    fn inline_single_byte_in_words() {
660        let packed = PackedDeltas::new(vec![150, 200, 1, -300]);
661        assert_eq!(packed.iter_runs().count(), 1)
662    }
663
664    #[test]
665    fn split_double_byte_in_words() {
666        let packed = PackedDeltas::new(vec![150, 200, 1, 3, -300]);
667        assert_eq!(packed.iter_runs().count(), 3)
668    }
669
670    #[test]
671    fn split_byte_then_zero_after_words() {
672        // without split: 10 = 1 + 2 + 2 + 2 + 1 + 2
673        //    with split:  9 = 1 + 2 + 2 + 1 + 3
674        let packed = PackedDeltas::new(vec![150, 200, 1, 0, 1]);
675        assert_eq!(packed.iter_runs().count(), 2);
676        assert_eq!(packed.compute_size(), 9);
677    }
678
679    #[rstest]
680    // Note how the packed data below is b"\x00\x01" and not b"\x00\x01\x01", for the
681    // repeated trailing values can be omitted
682    #[case::one_byte_one_inner_bit(
683        vec![0, 1, 1], 0b00_0000, 1, 1, b"\x00\x01",
684    )]
685    #[case::one_byte_two_inner_bits(
686        vec![0, 1, 2], 0b00_0001, 1, 2, b"\x00\x01\x02",
687    )]
688    #[case::one_byte_three_inner_bits(
689        vec![0, 1, 4], 0b00_0010, 1, 3, b"\x00\x01\x04",
690    )]
691    #[case::one_byte_four_inner_bits(
692        vec![0, 1, 8], 0b00_0011, 1, 4, b"\x00\x01\x08",
693    )]
694    // 256 needs 2 bytes, of which 9 bits for the inner value
695    #[case::two_bytes_nine_inner_bits(
696        vec![0, 1, 256], 0b01_1000, 2, 9, b"\x00\x00\x00\x01\x01\x00",
697    )]
698    #[case::two_bytes_sixteen_inner_bits(
699        vec![0, 1, 0x8000], 0b01_1111, 2, 16, b"\x00\x00\x00\x01\x80\x00",
700    )]
701    // note this gets packed the same as case 'one_byte_two_inner_bits': [0, 1, 2]
702    // above, but it uses only 1 bit for the inner value, while the other bits are
703    // used for the outer value:
704    // 0x0001_0000 => b"\x02" => 0b00000010 => {outer: 1, inner: 0)
705    #[case::one_byte_one_inner_bit_two_vardatas(
706        vec![0, 1, 0x01_0000], 0b00_0000, 1, 1, b"\x00\x01\x02",
707    )]
708    #[case::three_bytes_sixteen_inner_bits(
709        vec![0, 0xFFFF, 0x01_0000],
710        0b10_1111,
711        3,
712        16,
713        b"\x00\x00\x00\x00\xFF\xFF\x01\x00\x00",
714    )]
715    #[case::four_bytes_sixteen_inner_bits(
716        vec![0, 0xFFFF, 0xFFFF_FFFF],
717        0b11_1111,
718        4,
719        16,
720        b"\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF",
721    )]
722    #[test]
723    fn delta_set_index_map_entry_format_and_packed_data(
724        #[case] mapping: Vec<u32>,
725        #[case] expected_format_bits: u8,
726        #[case] expected_entry_size: u8,
727        #[case] expected_inner_bit_count: u8,
728        #[case] expected_map_data: &[u8],
729    ) {
730        let (format, data) = DeltaSetIndexMap::pack_map_data(&mapping);
731        assert_eq!(format.bits(), expected_format_bits);
732        assert_eq!(format.entry_size(), expected_entry_size);
733        assert_eq!(format.bit_count(), expected_inner_bit_count);
734        assert_eq!(data, expected_map_data);
735
736        let dsim: DeltaSetIndexMap = mapping.iter().copied().collect();
737        // all test mappings have fewer than 65536 entries (for practical reasons)
738        // so we should generate a Format0
739        assert!(matches!(dsim, DeltaSetIndexMap::Format0 { .. }));
740
741        // make sure we get the same mapping back after round-tripping to/from bytes
742        let raw_dsim = crate::dump_table(&dsim).unwrap();
743        let dsim2 = fontcull_read_fonts::tables::variations::DeltaSetIndexMap::read(FontData::new(
744            &raw_dsim,
745        ))
746        .unwrap();
747        assert_eq!(
748            (0..mapping.len())
749                .map(|i| {
750                    let index = dsim2.get(i as u32).unwrap();
751                    ((index.outer as u32) << 16) | index.inner as u32
752                })
753                .collect::<Vec<_>>(),
754            mapping
755        );
756    }
757
758    #[test]
759    fn delta_set_index_map_from_variation_index_iterator() {
760        // as returned from VariationStoreBuilder::build() in the VariationIndexRemapping
761        use crate::tables::layout::VariationIndex;
762
763        let mapping = vec![
764            VariationIndex::new(0, 0),
765            VariationIndex::new(0, 1),
766            VariationIndex::new(0, 2),
767            VariationIndex::new(1, 0),
768            VariationIndex::new(1, 1),
769            VariationIndex::new(1, 2),
770        ];
771
772        let dsim: DeltaSetIndexMap = mapping.into_iter().collect();
773        let DeltaSetIndexMap::Format0(dsim) = dsim else {
774            panic!("expected DeltaSetIndexMap::Format0, got {:?}", dsim);
775        };
776        assert_eq!(dsim.map_count, 6);
777        assert_eq!(dsim.entry_format.bits(), 0b000001);
778        assert_eq!(dsim.entry_format.entry_size(), 1); // one byte per entry
779        assert_eq!(dsim.entry_format.bit_count(), 2);
780        // for each entry/byte, the right-most 2 bits are the inner value,
781        // the remaining bits are the outer value
782        assert_eq!(
783            dsim.map_data,
784            vec![
785                0b00_00, // (0, 0)
786                0b00_01, // (0, 1)
787                0b00_10, // (0, 2)
788                0b01_00, // (1, 0)
789                0b01_01, // (1, 1)
790                0b01_10, // (1, 2)
791            ]
792        );
793    }
794
795    #[test]
796    fn huge_mapping_generates_format_1_delta_set_index_map() {
797        // 65536 entries, so we need a Format1 with u32 map_count
798        let mapping = (0..=0xFFFF).collect::<Vec<u32>>();
799        let map_count = mapping.len() as u32;
800        let dsim: DeltaSetIndexMap = mapping.into_iter().collect();
801        let DeltaSetIndexMap::Format1(dsim) = dsim else {
802            panic!("expected DeltaSetIndexMap::Format1, got {:?}", dsim);
803        };
804        assert_eq!(dsim.map_count, map_count);
805    }
806}