Skip to main content

read_fonts/tables/
varc.rs

1//! the [VARC (Variable Composite/Component)](https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md) table
2
3pub use super::layout::{Condition, CoverageTable};
4use super::variations::PackedDeltas;
5pub use crate::ps::cff::index::Index2;
6use crate::types::{F2Dot14, Matrix};
7
8#[cfg(feature = "libm")]
9#[allow(unused_imports)]
10use core_maths::*;
11
12include!("../../generated/generated_varc.rs");
13
14/// Let's us call self.something().get(i) instead of get(self.something(), i)
15trait Get<'a> {
16    fn get(self, nth: usize) -> Result<&'a [u8], ReadError>;
17}
18
19impl<'a> Get<'a> for Option<Result<Index2<'a>, ReadError>> {
20    fn get(self, nth: usize) -> Result<&'a [u8], ReadError> {
21        self.transpose()?
22            .ok_or(ReadError::NullOffset)
23            .and_then(|index| index.get(nth).map_err(|_| ReadError::OutOfBounds))
24    }
25}
26
27impl Varc<'_> {
28    /// Friendlier accessor than directly using raw data via [Index2]
29    pub fn axis_indices(&self, nth: usize) -> Result<PackedDeltas<'_>, ReadError> {
30        let raw = self.axis_indices_list().get(nth)?;
31        Ok(PackedDeltas::consume_all(raw.into()))
32    }
33
34    /// Friendlier accessor than directly using raw data via [Index2]
35    ///
36    /// nth would typically be obtained by looking up a [GlyphId] in [Self::coverage].
37    pub fn glyph(&self, nth: usize) -> Result<VarcGlyph<'_>, ReadError> {
38        let raw = Some(self.var_composite_glyphs()).get(nth)?;
39        Ok(VarcGlyph {
40            table: self,
41            data: raw.into(),
42        })
43    }
44}
45
46impl SparseVariationRegion<'_> {
47    /// Computes a floating point scalar value for this sparse region and the
48    /// specified normalized variation coordinates.
49    pub fn compute_scalar_f32(&self, coords: &[F2Dot14]) -> f32 {
50        let mut scalar = 1.0f32;
51        for axis in self.region_axes() {
52            let peak = axis.peak();
53            if peak == F2Dot14::ZERO {
54                continue;
55            }
56            let axis_index = axis.axis_index() as usize;
57            let coord = coords.get(axis_index).copied().unwrap_or(F2Dot14::ZERO);
58            if coord == peak {
59                continue;
60            }
61            if coord == F2Dot14::ZERO {
62                return 0.0;
63            }
64            let start = axis.start();
65            let end = axis.end();
66            if start > peak || peak > end || (start < F2Dot14::ZERO && end > F2Dot14::ZERO) {
67                continue;
68            }
69            if coord < start || coord > end {
70                return 0.0;
71            } else if coord < peak {
72                // Use raw bits - scale factors cancel in the ratio
73                let numerat = coord.to_bits() - start.to_bits();
74                if numerat == 0 {
75                    return 0.0;
76                }
77                let denom = peak.to_bits() - start.to_bits();
78                scalar *= numerat as f32 / denom as f32;
79            } else {
80                // Use raw bits - scale factors cancel in the ratio
81                let numerat = end.to_bits() - coord.to_bits();
82                if numerat == 0 {
83                    return 0.0;
84                }
85                let denom = end.to_bits() - peak.to_bits();
86                scalar *= numerat as f32 / denom as f32;
87            }
88        }
89        scalar
90    }
91}
92
93/// A VARC glyph doesn't have any root level attributes, it's just a list of components
94///
95/// <https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md#variable-composite-description>
96pub struct VarcGlyph<'a> {
97    table: &'a Varc<'a>,
98    data: FontData<'a>,
99}
100
101impl<'a> VarcGlyph<'a> {
102    /// <https://github.com/fonttools/fonttools/blob/5e6b12d12fa08abafbeb7570f47707fbedf69a45/Lib/fontTools/ttLib/tables/otTables.py#L404-L409>
103    pub fn components(&self) -> impl Iterator<Item = Result<VarcComponent<'a>, ReadError>> {
104        VarcComponentIter {
105            table: self.table,
106            cursor: self.data.cursor(),
107        }
108    }
109}
110
111struct VarcComponentIter<'a> {
112    table: &'a Varc<'a>,
113    cursor: Cursor<'a>,
114}
115
116impl<'a> Iterator for VarcComponentIter<'a> {
117    type Item = Result<VarcComponent<'a>, ReadError>;
118
119    fn next(&mut self) -> Option<Self::Item> {
120        if self.cursor.is_empty() {
121            return None;
122        }
123        Some(VarcComponent::parse(self.table, &mut self.cursor))
124    }
125}
126
127pub struct VarcComponent<'a> {
128    flags: VarcFlags,
129    gid: GlyphId,
130    condition_index: Option<u32>,
131    axis_indices_index: Option<u32>,
132    axis_values: Option<PackedDeltas<'a>>,
133    axis_values_var_index: Option<u32>,
134    transform_var_index: Option<u32>,
135    transform: DecomposedTransform,
136}
137
138impl<'a> VarcComponent<'a> {
139    /// Requires access to VARC fields to fully parse.
140    ///
141    ///  * HarfBuzz [VarComponent::get_path_at](https://github.com/harfbuzz/harfbuzz/blob/0c2f5ecd51d11e32836ee136a1bc765d650a4ec0/src/OT/Var/VARC/VARC.cc#L132)
142    fn parse(table: &Varc, cursor: &mut Cursor<'a>) -> Result<Self, ReadError> {
143        let raw_flags = cursor.read_u32_var()?;
144        let flags = VarcFlags::from_bits_truncate(raw_flags);
145        // Ref https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md#variable-component-record
146
147        // This is a GlyphID16 if GID_IS_24BIT bit of flags is clear, else GlyphID24.
148        let gid = if flags.contains(VarcFlags::GID_IS_24BIT) {
149            GlyphId::new(cursor.read::<Uint24>()?.to_u32())
150        } else {
151            GlyphId::from(cursor.read::<u16>()?)
152        };
153
154        let condition_index = if flags.contains(VarcFlags::HAVE_CONDITION) {
155            Some(cursor.read_u32_var()?)
156        } else {
157            None
158        };
159
160        let (axis_indices_index, axis_values) = if flags.contains(VarcFlags::HAVE_AXES) {
161            // <https://github.com/harfbuzz/harfbuzz/blob/0c2f5ecd51d11e32836ee136a1bc765d650a4ec0/src/OT/Var/VARC/VARC.cc#L195-L206>
162            let axis_indices_index = cursor.read_u32_var()?;
163            let num_axis_values = table
164                .axis_indices(axis_indices_index as usize)?
165                .count_or_compute();
166            // we need to consume num_axis_values entries in packed delta format
167            let deltas = if num_axis_values > 0 {
168                let Some(data) = cursor.remaining() else {
169                    return Err(ReadError::OutOfBounds);
170                };
171                let deltas = PackedDeltas::new(data, num_axis_values);
172                *cursor = deltas.iter().end(); // jump past the packed deltas
173                Some(deltas)
174            } else {
175                None
176            };
177            (Some(axis_indices_index), deltas)
178        } else {
179            (None, None)
180        };
181
182        let axis_values_var_index = if flags.contains(VarcFlags::AXIS_VALUES_HAVE_VARIATION) {
183            Some(cursor.read_u32_var()?)
184        } else {
185            None
186        };
187
188        let transform_var_index = if flags.contains(VarcFlags::TRANSFORM_HAS_VARIATION) {
189            Some(cursor.read_u32_var()?)
190        } else {
191            None
192        };
193
194        let mut transform = DecomposedTransform::default();
195        if flags.intersects(VarcFlags::HAVE_TRANSLATE_X | VarcFlags::HAVE_TRANSLATE_Y) {
196            if flags.contains(VarcFlags::HAVE_TRANSLATE_X) {
197                transform.translate_x = cursor.read::<FWord>()?.to_i16() as f32
198            }
199            if flags.contains(VarcFlags::HAVE_TRANSLATE_Y) {
200                transform.translate_y = cursor.read::<FWord>()?.to_i16() as f32
201            }
202        }
203        if flags.contains(VarcFlags::HAVE_ROTATION) {
204            transform.rotation = cursor.read::<F4Dot12>()?.to_f32()
205        }
206        if flags.intersects(VarcFlags::HAVE_SCALE_X | VarcFlags::HAVE_SCALE_Y) {
207            if flags.contains(VarcFlags::HAVE_SCALE_X) {
208                transform.scale_x = cursor.read::<F6Dot10>()?.to_f32()
209            }
210            transform.scale_y = if flags.contains(VarcFlags::HAVE_SCALE_Y) {
211                cursor.read::<F6Dot10>()?.to_f32()
212            } else {
213                transform.scale_x
214            };
215        }
216        if flags.intersects(VarcFlags::HAVE_TCENTER_X | VarcFlags::HAVE_TCENTER_Y) {
217            if flags.contains(VarcFlags::HAVE_TCENTER_X) {
218                transform.center_x = cursor.read::<FWord>()?.to_i16() as f32
219            }
220            if flags.contains(VarcFlags::HAVE_TCENTER_Y) {
221                transform.center_y = cursor.read::<FWord>()?.to_i16() as f32
222            }
223        }
224        if flags.intersects(VarcFlags::HAVE_SKEW_X | VarcFlags::HAVE_SKEW_Y) {
225            if flags.contains(VarcFlags::HAVE_SKEW_X) {
226                transform.skew_x = cursor.read::<F4Dot12>()?.to_f32()
227            }
228            if flags.contains(VarcFlags::HAVE_SKEW_Y) {
229                transform.skew_y = cursor.read::<F4Dot12>()?.to_f32()
230            }
231        }
232
233        // Optional, process and discard one uint32var per each set bit in RESERVED_MASK.
234        let reserved = raw_flags & VarcFlags::RESERVED_MASK.bits;
235        if reserved != 0 {
236            let num_reserved = reserved.count_ones();
237            for _ in 0..num_reserved {
238                cursor.read_u32_var()?;
239            }
240        }
241        Ok(VarcComponent {
242            flags,
243            gid,
244            condition_index,
245            axis_indices_index,
246            axis_values,
247            axis_values_var_index,
248            transform_var_index,
249            transform,
250        })
251    }
252
253    pub fn flags(&self) -> VarcFlags {
254        self.flags
255    }
256    pub fn gid(&self) -> GlyphId {
257        self.gid
258    }
259    pub fn condition_index(&self) -> Option<u32> {
260        self.condition_index
261    }
262    pub fn transform(&self) -> &DecomposedTransform {
263        &self.transform
264    }
265    pub fn axis_indices_index(&self) -> Option<u32> {
266        self.axis_indices_index
267    }
268    pub fn axis_values(&self) -> Option<&PackedDeltas<'a>> {
269        self.axis_values.as_ref()
270    }
271    pub fn axis_values_var_index(&self) -> Option<u32> {
272        self.axis_values_var_index
273    }
274    pub fn transform_var_index(&self) -> Option<u32> {
275        self.transform_var_index
276    }
277}
278
279/// <https://github.com/fonttools/fonttools/blob/5e6b12d12fa08abafbeb7570f47707fbedf69a45/Lib/fontTools/misc/transform.py#L410>
280#[derive(Clone, Copy)]
281pub struct DecomposedTransform {
282    translate_x: f32,
283    translate_y: f32,
284    rotation: f32, // multiples of Pi, counter-clockwise
285    scale_x: f32,
286    scale_y: f32,
287    skew_x: f32, // multiples of Pi, clockwise
288    skew_y: f32, // multiples of Pi, counter-clockwise
289    center_x: f32,
290    center_y: f32,
291}
292
293impl Default for DecomposedTransform {
294    fn default() -> Self {
295        Self {
296            translate_x: 0.0,
297            translate_y: 0.0,
298            rotation: 0.0,
299            scale_x: 1.0,
300            scale_y: 1.0,
301            skew_x: 0.0,
302            skew_y: 0.0,
303            center_x: 0.0,
304            center_y: 0.0,
305        }
306    }
307}
308
309impl DecomposedTransform {
310    pub fn translate_x(&self) -> f32 {
311        self.translate_x
312    }
313
314    pub fn translate_y(&self) -> f32 {
315        self.translate_y
316    }
317
318    pub fn rotation(&self) -> f32 {
319        self.rotation
320    }
321
322    pub fn scale_x(&self) -> f32 {
323        self.scale_x
324    }
325
326    pub fn scale_y(&self) -> f32 {
327        self.scale_y
328    }
329
330    pub fn skew_x(&self) -> f32 {
331        self.skew_x
332    }
333
334    pub fn skew_y(&self) -> f32 {
335        self.skew_y
336    }
337
338    pub fn center_x(&self) -> f32 {
339        self.center_x
340    }
341
342    pub fn center_y(&self) -> f32 {
343        self.center_y
344    }
345
346    pub fn set_translate_x(&mut self, value: f32) {
347        self.translate_x = value;
348    }
349
350    pub fn set_translate_y(&mut self, value: f32) {
351        self.translate_y = value;
352    }
353
354    pub fn set_rotation(&mut self, value: f32) {
355        self.rotation = value;
356    }
357
358    pub fn set_scale_x(&mut self, value: f32) {
359        self.scale_x = value;
360    }
361
362    pub fn set_scale_y(&mut self, value: f32) {
363        self.scale_y = value;
364    }
365
366    pub fn set_skew_x(&mut self, value: f32) {
367        self.skew_x = value;
368    }
369
370    pub fn set_skew_y(&mut self, value: f32) {
371        self.skew_y = value;
372    }
373
374    pub fn set_center_x(&mut self, value: f32) {
375        self.center_x = value;
376    }
377
378    pub fn set_center_y(&mut self, value: f32) {
379        self.center_y = value;
380    }
381
382    /// Convert decomposed form to 2x3 matrix form.
383    ///
384    /// The first two values are x,y x-basis vector,
385    /// the second 2 values are x,y y-basis vector, and the third 2 are translation.
386    ///
387    /// In augmented matrix
388    /// form, if this method returns `[a, b, c, d, e, f]` that is taken as:
389    ///
390    /// ```text
391    /// | a c e |
392    /// | b d f |
393    /// | 0 0 1 |
394    /// ```
395    ///
396    /// References:
397    ///   FontTools Python implementation <https://github.com/fonttools/fonttools/blob/5e6b12d12fa08abafbeb7570f47707fbedf69a45/Lib/fontTools/misc/transform.py#L484-L500>
398    /// * Wikipedia [affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)
399    pub fn matrix(&self) -> Matrix<f32> {
400        // Python: t.translate(self.translateX + self.tCenterX, self.translateY + self.tCenterY)
401        let mut transform = Matrix::IDENTITY;
402        transform.dx = self.translate_x + self.center_x;
403        transform.dy = self.translate_y + self.center_y;
404
405        // TODO: this produces very small floats for rotations, e.g. 90 degree rotation a basic scale
406        // puts 1.2246467991473532e-16 into [0]. Should we special case? Round?
407
408        // Python: t = t.rotate(self.rotation * math.pi)
409        if self.rotation != 0.0 {
410            let (s, c) = (self.rotation * core::f32::consts::PI).sin_cos();
411            transform *= Matrix::from_elements([c, s, -s, c, 0.0, 0.0]);
412        }
413
414        // Python: t = t.scale(self.scaleX, self.scaleY)
415        if (self.scale_x, self.scale_y) != (1.0, 1.0) {
416            transform *= Matrix::from_elements([self.scale_x, 0.0, 0.0, self.scale_y, 0.0, 0.0]);
417        }
418
419        // Python: t = t.skew(-self.skewX * math.pi, self.skewY * math.pi)
420        if (self.skew_x, self.skew_y) != (0.0, 0.0) {
421            transform *= Matrix::from_elements([
422                1.0,
423                (self.skew_y * core::f32::consts::PI).tan(),
424                (-self.skew_x * core::f32::consts::PI).tan(),
425                1.0,
426                0.0,
427                0.0,
428            ])
429        }
430
431        // Python: t = t.translate(-self.tCenterX, -self.tCenterY)
432        if (self.center_x, self.center_y) != (0.0, 0.0) {
433            transform *=
434                Matrix::from_elements([1.0, 0.0, 0.0, 1.0, -self.center_x, -self.center_y]);
435        }
436
437        transform
438    }
439}
440
441impl<'a> MultiItemVariationData<'a> {
442    /// An [Index2] where each item is a [PackedDeltas]
443    pub fn delta_sets(&self) -> Result<Index2<'a>, ReadError> {
444        Index2::read(self.raw_delta_sets().into())
445    }
446
447    /// Read a specific delta set.
448    ///
449    /// Equivalent to calling [Self::delta_sets], fetching item i, and parsing as [PackedDeltas]
450    pub fn delta_set(&self, i: usize) -> Result<PackedDeltas<'a>, ReadError> {
451        let index = self.delta_sets()?;
452        let raw_deltas = index.get(i).map_err(|_| ReadError::OutOfBounds)?;
453        Ok(PackedDeltas::consume_all(raw_deltas.into()))
454    }
455}
456
457#[cfg(test)]
458mod tests {
459    use types::GlyphId16;
460
461    use crate::types::F2Dot14;
462    use crate::FontData;
463    use crate::{FontRef, ReadError, TableProvider};
464
465    use super::{Condition, DecomposedTransform, Varc};
466
467    impl Varc<'_> {
468        fn conditions(&self) -> impl Iterator<Item = Condition<'_>> {
469            self.condition_list()
470                .expect("A condition list is present")
471                .expect("We could read the condition list")
472                .conditions()
473                .iter()
474                .enumerate()
475                .map(|(i, c)| c.unwrap_or_else(|e| panic!("condition {i} {e}")))
476        }
477
478        fn axis_indices_count(&self) -> Result<usize, ReadError> {
479            let Some(axis_indices_list) = self.axis_indices_list() else {
480                return Ok(0);
481            };
482            let axis_indices_list = axis_indices_list?;
483            Ok(axis_indices_list.count() as usize)
484        }
485    }
486
487    fn round6(v: f32) -> f32 {
488        (v * 1_000_000.0).round() / 1_000_000.0
489    }
490
491    fn coord(value: f32) -> F2Dot14 {
492        F2Dot14::from_f32(value)
493    }
494
495    fn assert_close(actual: f32, expected: f32) {
496        let diff = (actual - expected).abs();
497        assert!(
498            diff <= 1e-6,
499            "expected {expected}, got {actual}, diff {diff}"
500        );
501    }
502
503    fn sfnt_table_range(data: &[u8], tag: [u8; 4]) -> (usize, usize) {
504        let font = FontRef::new(data).unwrap();
505        if let Some(rec) = font
506            .table_directory()
507            .table_records()
508            .iter()
509            .find(|rec| rec.tag() == tag)
510        {
511            return (rec.offset() as usize, rec.length() as usize);
512        }
513        panic!(
514            "missing table {:?}",
515            core::str::from_utf8(&tag).unwrap_or("????")
516        );
517    }
518
519    fn write_be_u32(dst: &mut [u8], value: u32) {
520        dst.copy_from_slice(&value.to_be_bytes());
521    }
522
523    fn read_be_u32(src: &[u8]) -> u32 {
524        u32::from_be_bytes([src[0], src[1], src[2], src[3]])
525    }
526
527    fn encode_u32_var(value: u32) -> Vec<u8> {
528        if value < 0x80 {
529            vec![value as u8]
530        } else if value < 0x4000 {
531            vec![0x80 | ((value >> 8) as u8), value as u8]
532        } else if value < 0x20_0000 {
533            vec![
534                0xC0 | ((value >> 16) as u8),
535                (value >> 8) as u8,
536                value as u8,
537            ]
538        } else if value < 0x1000_0000 {
539            vec![
540                0xE0 | ((value >> 24) as u8),
541                (value >> 16) as u8,
542                (value >> 8) as u8,
543                value as u8,
544            ]
545        } else {
546            vec![
547                0xF0,
548                (value >> 24) as u8,
549                (value >> 16) as u8,
550                (value >> 8) as u8,
551                value as u8,
552            ]
553        }
554    }
555
556    #[test]
557    fn read_cjk_0x6868() {
558        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
559        let table = font.varc().unwrap();
560        table.coverage().unwrap(); // should have coverage
561    }
562
563    #[test]
564    fn identify_all_conditional_types() {
565        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
566        let table = font.varc().unwrap();
567
568        // We should have all 5 condition types in order
569        assert_eq!(
570            (1..=5).collect::<Vec<_>>(),
571            table.conditions().map(|c| c.format()).collect::<Vec<_>>()
572        );
573    }
574
575    #[test]
576    fn read_condition_format1_axis_range() {
577        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
578        let table = font.varc().unwrap();
579        let Some(Condition::Format1AxisRange(condition)) =
580            table.conditions().find(|c| c.format() == 1)
581        else {
582            panic!("No such item");
583        };
584
585        assert_eq!(
586            (0, 0.5, 1.0),
587            (
588                condition.axis_index(),
589                condition.filter_range_min_value().to_f32(),
590                condition.filter_range_max_value().to_f32(),
591            )
592        );
593    }
594
595    #[test]
596    fn read_condition_format2_variable_value() {
597        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
598        let table = font.varc().unwrap();
599        let Some(Condition::Format2VariableValue(condition)) =
600            table.conditions().find(|c| c.format() == 2)
601        else {
602            panic!("No such item");
603        };
604
605        assert_eq!((1, 2), (condition.default_value(), condition.var_index(),));
606    }
607
608    #[test]
609    fn read_condition_format3_and() {
610        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
611        let table = font.varc().unwrap();
612        let Some(Condition::Format3And(condition)) = table.conditions().find(|c| c.format() == 3)
613        else {
614            panic!("No such item");
615        };
616
617        // Should reference a format 1 and a format 2
618        assert_eq!(
619            vec![1, 2],
620            condition
621                .conditions()
622                .iter()
623                .map(|c| c.unwrap().format())
624                .collect::<Vec<_>>()
625        );
626    }
627
628    #[test]
629    fn read_condition_format4_or() {
630        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
631        let table = font.varc().unwrap();
632        let Some(Condition::Format4Or(condition)) = table.conditions().find(|c| c.format() == 4)
633        else {
634            panic!("No such item");
635        };
636
637        // Should reference a format 1 and a format 2
638        assert_eq!(
639            vec![1, 2],
640            condition
641                .conditions()
642                .iter()
643                .map(|c| c.unwrap().format())
644                .collect::<Vec<_>>()
645        );
646    }
647
648    #[test]
649    fn read_condition_format5_negate() {
650        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
651        let table = font.varc().unwrap();
652        let Some(Condition::Format5Negate(condition)) =
653            table.conditions().find(|c| c.format() == 5)
654        else {
655            panic!("No such item");
656        };
657
658        // Should reference a format 1
659        assert_eq!(1, condition.condition().unwrap().format(),);
660    }
661
662    #[test]
663    fn read_axis_indices_list() {
664        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
665        let table = font.varc().unwrap();
666        assert_eq!(table.axis_indices_count().unwrap(), 2);
667        assert_eq!(
668            vec![2, 3, 4, 5, 6],
669            table.axis_indices(1).unwrap().iter().collect::<Vec<_>>()
670        );
671    }
672
673    #[test]
674    fn compute_sparse_region_scalar_handles_boundaries_and_products() {
675        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
676        let varc = font.varc().unwrap();
677        let store = varc.multi_var_store().unwrap().unwrap();
678        let regions = store.region_list().unwrap();
679        let region_list = regions.regions();
680
681        let axis0_region = region_list.get(0).unwrap();
682        assert_close(axis0_region.compute_scalar_f32(&[coord(1.0)]), 1.0);
683        assert_close(axis0_region.compute_scalar_f32(&[coord(0.5)]), 0.5);
684        assert_close(axis0_region.compute_scalar_f32(&[F2Dot14::ZERO]), 0.0);
685        assert_close(axis0_region.compute_scalar_f32(&[]), 0.0);
686        assert_close(axis0_region.compute_scalar_f32(&[coord(-0.25)]), 0.0);
687
688        let axis0_axis1_region = region_list.get(2).unwrap();
689        assert_close(
690            axis0_axis1_region.compute_scalar_f32(&[coord(0.5), coord(0.25)]),
691            0.125,
692        );
693    }
694
695    #[test]
696    fn axis_indices_offset_out_of_bounds_errors() {
697        let mut bytes = font_test_data::varc::CJK_6868.to_vec();
698        let (varc_offset, varc_len) = sfnt_table_range(&bytes, *b"VARC");
699        // axis_indices_list_offset is the 5th u32 in the VARC header.
700        let axis_indices_offset = varc_offset + 16;
701        write_be_u32(
702            &mut bytes[axis_indices_offset..axis_indices_offset + 4],
703            (varc_len as u32).saturating_add(8),
704        );
705
706        let font = FontRef::new(&bytes).unwrap();
707        let table = font.varc().unwrap();
708        assert!(matches!(table.axis_indices(0), Err(ReadError::OutOfBounds)));
709    }
710
711    #[test]
712    fn var_composite_glyphs_offset_out_of_bounds_errors() {
713        let mut bytes = font_test_data::varc::CJK_6868.to_vec();
714        let (varc_offset, varc_len) = sfnt_table_range(&bytes, *b"VARC");
715        // var_composite_glyphs_offset is the 6th u32 in the VARC header.
716        let glyphs_offset = varc_offset + 20;
717        write_be_u32(
718            &mut bytes[glyphs_offset..glyphs_offset + 4],
719            (varc_len as u32).saturating_add(8),
720        );
721
722        let font = FontRef::new(&bytes).unwrap();
723        let table = font.varc().unwrap();
724        assert!(matches!(table.glyph(0), Err(ReadError::OutOfBounds)));
725    }
726
727    #[test]
728    fn parse_component_with_missing_translate_data_errors() {
729        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
730        let table = font.varc().unwrap();
731        // flags=HAVE_TRANSLATE_X, gid=1, missing FWORD payload.
732        let data = FontData::new(&[0x10, 0x00, 0x01]);
733        let mut cursor = data.cursor();
734        assert!(matches!(
735            super::VarcComponent::parse(&table, &mut cursor),
736            Err(ReadError::OutOfBounds)
737        ));
738    }
739
740    #[test]
741    fn parse_component_with_invalid_axis_indices_index_errors() {
742        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
743        let table = font.varc().unwrap();
744        // flags=HAVE_AXES, gid=1, axis_indices_index=127 (out of range).
745        let data = FontData::new(&[0x02, 0x00, 0x01, 0x7F]);
746        let mut cursor = data.cursor();
747        assert!(matches!(
748            super::VarcComponent::parse(&table, &mut cursor),
749            Err(ReadError::OutOfBounds)
750        ));
751    }
752
753    #[test]
754    fn parse_component_reserved_fields_are_consumed() {
755        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
756        let table = font.varc().unwrap();
757        // flags = HAVE_TRANSLATE_X plus two reserved bits.
758        let flags = 0x0001_8010_u32;
759        let mut bytes = Vec::new();
760        bytes.extend_from_slice(&encode_u32_var(flags));
761        bytes.extend_from_slice(&[0x00, 0x01]); // gid
762        bytes.extend_from_slice(&[0x00, 0x07]); // translate_x = 7
763        bytes.extend_from_slice(&encode_u32_var(1)); // reserved payload 1
764        bytes.extend_from_slice(&encode_u32_var(2)); // reserved payload 2
765        bytes.push(0xAA); // sentinel
766        let data = FontData::new(&bytes);
767        let mut cursor = data.cursor();
768
769        let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
770        assert_eq!(component.transform().translate_x(), 7.0);
771        assert_eq!(cursor.read::<u8>().unwrap(), 0xAA);
772    }
773
774    #[test]
775    fn parse_component_reserved_fields_truncation_errors() {
776        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
777        let table = font.varc().unwrap();
778        // flags = HAVE_TRANSLATE_X plus two reserved bits, but only one reserved payload follows.
779        let flags = 0x0001_8010_u32;
780        let mut bytes = Vec::new();
781        bytes.extend_from_slice(&encode_u32_var(flags));
782        bytes.extend_from_slice(&[0x00, 0x01]); // gid
783        bytes.extend_from_slice(&[0x00, 0x07]); // translate_x
784        bytes.extend_from_slice(&encode_u32_var(1)); // only one reserved payload
785        let data = FontData::new(&bytes);
786        let mut cursor = data.cursor();
787
788        assert!(matches!(
789            super::VarcComponent::parse(&table, &mut cursor),
790            Err(ReadError::OutOfBounds)
791        ));
792    }
793
794    #[test]
795    fn parse_component_gid_is_24bit_path() {
796        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
797        let table = font.varc().unwrap();
798        let mut bytes = Vec::new();
799        bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::GID_IS_24BIT.bits()));
800        bytes.extend_from_slice(&[0x12, 0x34, 0x56]);
801        bytes.push(0xAA); // sentinel
802        let data = FontData::new(&bytes);
803        let mut cursor = data.cursor();
804
805        let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
806        assert_eq!(component.gid().to_u32(), 0x12_34_56);
807        assert_eq!(cursor.read::<u8>().unwrap(), 0xAA);
808    }
809
810    #[test]
811    fn parse_component_gid_is_24bit_truncation_errors() {
812        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
813        let table = font.varc().unwrap();
814        let mut bytes = Vec::new();
815        bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::GID_IS_24BIT.bits()));
816        bytes.extend_from_slice(&[0x12, 0x34]); // truncated uint24
817        let data = FontData::new(&bytes);
818        let mut cursor = data.cursor();
819
820        assert!(matches!(
821            super::VarcComponent::parse(&table, &mut cursor),
822            Err(ReadError::OutOfBounds)
823        ));
824    }
825
826    #[test]
827    fn parse_component_with_axes_zero_count_does_not_consume_axis_values() {
828        let mut bytes = font_test_data::varc::CJK_6868.to_vec();
829        let (varc_offset, _) = sfnt_table_range(&bytes, *b"VARC");
830        let axis_indices_rel = read_be_u32(&bytes[varc_offset + 16..varc_offset + 20]) as usize;
831        let axis_indices_abs = varc_offset + axis_indices_rel;
832        // Overwrite axis_indices_list with Index2 { count = 1, off_size = 1, offsets = [1, 1] }.
833        // That single item is empty, so count_or_compute() must be zero.
834        bytes[axis_indices_abs..axis_indices_abs + 7]
835            .copy_from_slice(&[0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01]);
836        let font = FontRef::new(&bytes).unwrap();
837        let table = font.varc().unwrap();
838        assert_eq!(table.axis_indices(0).unwrap().count_or_compute(), 0);
839
840        // flags = HAVE_AXES | HAVE_TRANSLATE_X
841        let mut bytes = Vec::new();
842        bytes.extend_from_slice(&encode_u32_var(0x12));
843        bytes.extend_from_slice(&[0x00, 0x01]); // gid
844        bytes.extend_from_slice(&encode_u32_var(0)); // axis_indices_index, count is zero
845        bytes.extend_from_slice(&[0x00, 0x05]); // translate_x
846        bytes.push(0xAA); // sentinel
847        let data = FontData::new(&bytes);
848        let mut cursor = data.cursor();
849
850        let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
851        assert_eq!(component.transform().translate_x(), 5.0);
852        assert_eq!(cursor.read::<u8>().unwrap(), 0xAA);
853    }
854
855    #[test]
856    fn parse_component_with_axes_nonzero_count_consumes_axis_values() {
857        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
858        let table = font.varc().unwrap();
859        assert_eq!(table.axis_indices(1).unwrap().count_or_compute(), 5);
860
861        // flags = HAVE_AXES | HAVE_TRANSLATE_X
862        let mut bytes = Vec::new();
863        bytes.extend_from_slice(&encode_u32_var(0x12));
864        bytes.extend_from_slice(&[0x00, 0x01]); // gid
865        bytes.extend_from_slice(&encode_u32_var(1)); // axis_indices_index, count is five
866        bytes.push(0x04); // one I8 run, count = 5
867        bytes.extend_from_slice(&[1, 2, 3, 4, 5]); // packed axis values
868        bytes.extend_from_slice(&[0x00, 0x09]); // translate_x
869        bytes.push(0xAA); // sentinel
870        let data = FontData::new(&bytes);
871        let mut cursor = data.cursor();
872
873        let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
874        assert_eq!(component.transform().translate_x(), 9.0);
875        assert_eq!(cursor.read::<u8>().unwrap(), 0xAA);
876    }
877
878    #[test]
879    fn parse_component_scale_x_only_applies_to_scale_y() {
880        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
881        let table = font.varc().unwrap();
882        let mut bytes = Vec::new();
883        bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_SCALE_X.bits()));
884        bytes.extend_from_slice(&[0x00, 0x01]); // gid
885        bytes.extend_from_slice(&[0x08, 0x00]); // scale_x = 2.0 in F6Dot10
886        bytes.push(0xAA); // sentinel
887        let data = FontData::new(&bytes);
888        let mut cursor = data.cursor();
889
890        let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
891        assert_eq!(component.transform().scale_x(), 2.0);
892        assert_eq!(component.transform().scale_y(), 2.0);
893        assert_eq!(cursor.read::<u8>().unwrap(), 0xAA);
894    }
895
896    #[test]
897    fn parse_component_scale_x_and_scale_y_are_independent() {
898        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
899        let table = font.varc().unwrap();
900        let flags = super::VarcFlags::HAVE_SCALE_X.bits() | super::VarcFlags::HAVE_SCALE_Y.bits();
901        let mut bytes = Vec::new();
902        bytes.extend_from_slice(&encode_u32_var(flags));
903        bytes.extend_from_slice(&[0x00, 0x01]); // gid
904        bytes.extend_from_slice(&[0x08, 0x00]); // scale_x = 2.0
905        bytes.extend_from_slice(&[0x0C, 0x00]); // scale_y = 3.0
906        bytes.push(0xAA); // sentinel
907        let data = FontData::new(&bytes);
908        let mut cursor = data.cursor();
909
910        let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
911        assert_eq!(component.transform().scale_x(), 2.0);
912        assert_eq!(component.transform().scale_y(), 3.0);
913        assert_eq!(cursor.read::<u8>().unwrap(), 0xAA);
914    }
915
916    #[test]
917    fn parse_component_multibyte_condition_and_var_indices() {
918        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
919        let table = font.varc().unwrap();
920        let flags = super::VarcFlags::HAVE_CONDITION.bits()
921            | super::VarcFlags::AXIS_VALUES_HAVE_VARIATION.bits()
922            | super::VarcFlags::TRANSFORM_HAS_VARIATION.bits()
923            | super::VarcFlags::HAVE_TRANSLATE_X.bits();
924        let mut bytes = Vec::new();
925        bytes.extend_from_slice(&encode_u32_var(flags));
926        bytes.extend_from_slice(&[0x00, 0x01]); // gid
927        bytes.extend_from_slice(&encode_u32_var(0x2345)); // condition_index
928        bytes.extend_from_slice(&encode_u32_var(0x0123)); // axis_values_var_index
929        bytes.extend_from_slice(&encode_u32_var(0x0222)); // transform_var_index
930        bytes.extend_from_slice(&[0x00, 0x07]); // translate_x = 7
931        bytes.push(0xAA); // sentinel
932        let data = FontData::new(&bytes);
933        let mut cursor = data.cursor();
934
935        let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
936        assert_eq!(component.condition_index(), Some(0x2345));
937        assert_eq!(component.axis_values_var_index(), Some(0x0123));
938        assert_eq!(component.transform_var_index(), Some(0x0222));
939        assert_eq!(component.transform().translate_x(), 7.0);
940        assert_eq!(cursor.read::<u8>().unwrap(), 0xAA);
941    }
942
943    #[test]
944    fn parse_component_truncated_multibyte_condition_index_errors() {
945        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
946        let table = font.varc().unwrap();
947        let mut bytes = Vec::new();
948        bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_CONDITION.bits()));
949        bytes.extend_from_slice(&[0x00, 0x01]); // gid
950        bytes.push(0x81); // truncated uint32var (needs one more byte)
951        let data = FontData::new(&bytes);
952        let mut cursor = data.cursor();
953
954        assert!(matches!(
955            super::VarcComponent::parse(&table, &mut cursor),
956            Err(ReadError::OutOfBounds)
957        ));
958    }
959
960    #[test]
961    fn parse_component_axes_with_var_index_and_zero_axis_count() {
962        let mut bytes = font_test_data::varc::CJK_6868.to_vec();
963        let (varc_offset, _) = sfnt_table_range(&bytes, *b"VARC");
964        let axis_indices_rel = read_be_u32(&bytes[varc_offset + 16..varc_offset + 20]) as usize;
965        let axis_indices_abs = varc_offset + axis_indices_rel;
966        // One empty axis-indices entry at index 0.
967        bytes[axis_indices_abs..axis_indices_abs + 7]
968            .copy_from_slice(&[0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01]);
969        let font = FontRef::new(&bytes).unwrap();
970        let table = font.varc().unwrap();
971
972        let flags = super::VarcFlags::HAVE_AXES.bits()
973            | super::VarcFlags::AXIS_VALUES_HAVE_VARIATION.bits()
974            | super::VarcFlags::HAVE_TRANSLATE_X.bits();
975        let mut bytes = Vec::new();
976        bytes.extend_from_slice(&encode_u32_var(flags));
977        bytes.extend_from_slice(&[0x00, 0x01]); // gid
978        bytes.extend_from_slice(&encode_u32_var(0)); // axis_indices_index => zero-length axis list
979        bytes.extend_from_slice(&encode_u32_var(0x0123)); // axis_values_var_index
980        bytes.extend_from_slice(&[0x00, 0x06]); // translate_x = 6
981        bytes.push(0xAA); // sentinel
982        let data = FontData::new(&bytes);
983        let mut cursor = data.cursor();
984
985        let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
986        assert_eq!(component.axis_indices_index(), Some(0));
987        assert!(component.axis_values().is_none());
988        assert_eq!(component.axis_values_var_index(), Some(0x0123));
989        assert_eq!(component.transform().translate_x(), 6.0);
990        assert_eq!(cursor.read::<u8>().unwrap(), 0xAA);
991    }
992
993    #[test]
994    fn parse_component_axes_truncated_i16_payload_detected_on_fetch() {
995        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
996        let table = font.varc().unwrap();
997        assert_eq!(table.axis_indices(1).unwrap().count_or_compute(), 5);
998
999        // flags = HAVE_AXES.
1000        let mut bytes = Vec::new();
1001        bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_AXES.bits()));
1002        bytes.extend_from_slice(&[0x00, 0x01]); // gid
1003        bytes.extend_from_slice(&encode_u32_var(1)); // axis_indices_index => expects 5 values
1004        bytes.push(0x44); // I16 run, count = 5
1005        bytes.extend_from_slice(&[0x00, 0x01, 0x00, 0x02, 0x00, 0x03]); // only 3 i16 values
1006        let data = FontData::new(&bytes);
1007        let mut cursor = data.cursor();
1008
1009        let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
1010        let mut out = [0.0; 5];
1011        assert!(matches!(
1012            component
1013                .axis_values()
1014                .unwrap()
1015                .fetcher()
1016                .add_to_f32_scaled(&mut out, 1.0),
1017            Err(ReadError::OutOfBounds)
1018        ));
1019    }
1020
1021    #[test]
1022    fn parse_component_axes_truncated_i32_payload_detected_on_fetch() {
1023        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
1024        let table = font.varc().unwrap();
1025        assert_eq!(table.axis_indices(1).unwrap().count_or_compute(), 5);
1026
1027        // flags = HAVE_AXES.
1028        let mut bytes = Vec::new();
1029        bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_AXES.bits()));
1030        bytes.extend_from_slice(&[0x00, 0x01]); // gid
1031        bytes.extend_from_slice(&encode_u32_var(1)); // axis_indices_index => expects 5 values
1032        bytes.push(0xC4); // I32 run, count = 5
1033        bytes.extend_from_slice(&[
1034            0x00, 0x00, 0x00, 0x01, // only 1 i32 value
1035            0x00, 0x00, 0x00, 0x02,
1036        ]);
1037        let data = FontData::new(&bytes);
1038        let mut cursor = data.cursor();
1039
1040        let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
1041        let mut out = [0.0; 5];
1042        assert!(matches!(
1043            component
1044                .axis_values()
1045                .unwrap()
1046                .fetcher()
1047                .add_to_f32_scaled(&mut out, 1.0),
1048            Err(ReadError::OutOfBounds)
1049        ));
1050    }
1051
1052    #[test]
1053    fn parse_component_axes_payload_overshoot_causes_following_field_error() {
1054        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
1055        let table = font.varc().unwrap();
1056        assert_eq!(table.axis_indices(1).unwrap().count_or_compute(), 5);
1057
1058        // flags = HAVE_AXES | HAVE_TRANSLATE_X.
1059        let mut bytes = Vec::new();
1060        bytes.extend_from_slice(&encode_u32_var(
1061            super::VarcFlags::HAVE_AXES.bits() | super::VarcFlags::HAVE_TRANSLATE_X.bits(),
1062        ));
1063        bytes.extend_from_slice(&[0x00, 0x01]); // gid
1064        bytes.extend_from_slice(&encode_u32_var(1)); // axis_indices_index => expects 5 values
1065        bytes.push(0x44); // I16 run, count = 5 (needs 10 bytes)
1066        bytes.extend_from_slice(&[0x00, 0x01, 0x00, 0x02]); // too short
1067        bytes.extend_from_slice(&[0x00, 0x09]); // intended translate_x (should be unreachable)
1068        let data = FontData::new(&bytes);
1069        let mut cursor = data.cursor();
1070
1071        assert!(matches!(
1072            super::VarcComponent::parse(&table, &mut cursor),
1073            Err(ReadError::OutOfBounds)
1074        ));
1075    }
1076
1077    #[test]
1078    fn generated_condition_varints_roundtrip() {
1079        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
1080        let table = font.varc().unwrap();
1081        let mut state = 0x1234_5678_u32;
1082        for _ in 0..256 {
1083            // xorshift32
1084            state ^= state << 13;
1085            state ^= state >> 17;
1086            state ^= state << 5;
1087            let value = state;
1088
1089            let mut bytes = Vec::new();
1090            bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_CONDITION.bits()));
1091            bytes.extend_from_slice(&[0x00, 0x01]); // gid
1092            bytes.extend_from_slice(&encode_u32_var(value));
1093            bytes.push(0xAA); // sentinel
1094            let data = FontData::new(&bytes);
1095            let mut cursor = data.cursor();
1096
1097            let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
1098            assert_eq!(component.condition_index(), Some(value));
1099            assert_eq!(cursor.read::<u8>().unwrap(), 0xAA);
1100        }
1101    }
1102
1103    #[test]
1104    fn generated_axis_values_single_run_roundtrip() {
1105        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
1106        let table = font.varc().unwrap();
1107        assert_eq!(table.axis_indices(1).unwrap().count_or_compute(), 5);
1108
1109        let mut state = 0xA5A5_5A5A_u32;
1110        for _ in 0..128 {
1111            // xorshift32
1112            state ^= state << 13;
1113            state ^= state >> 17;
1114            state ^= state << 5;
1115            let run_kind = state & 3;
1116
1117            let mut payload = Vec::new();
1118            let mut expected = [0f32; 5];
1119            match run_kind {
1120                0 => {
1121                    // I8, count = 5
1122                    payload.push(0x04);
1123                    for out in &mut expected {
1124                        state = state.wrapping_mul(1664525).wrapping_add(1013904223);
1125                        let v = ((state >> 24) as i8) % 64;
1126                        payload.push(v as u8);
1127                        *out = v as f32;
1128                    }
1129                }
1130                1 => {
1131                    // I16, count = 5
1132                    payload.push(0x44);
1133                    for out in &mut expected {
1134                        state = state.wrapping_mul(1664525).wrapping_add(1013904223);
1135                        let v = ((state >> 16) as i16) % 2048;
1136                        payload.extend_from_slice(&v.to_be_bytes());
1137                        *out = v as f32;
1138                    }
1139                }
1140                2 => {
1141                    // I32, count = 5
1142                    payload.push(0xC4);
1143                    for out in &mut expected {
1144                        state = state.wrapping_mul(1664525).wrapping_add(1013904223);
1145                        let v = (state as i32) % 1_000_000;
1146                        payload.extend_from_slice(&v.to_be_bytes());
1147                        *out = v as f32;
1148                    }
1149                }
1150                _ => {
1151                    // Zero, count = 5
1152                    payload.push(0x84);
1153                }
1154            }
1155
1156            let mut bytes = Vec::new();
1157            bytes.extend_from_slice(&encode_u32_var(
1158                super::VarcFlags::HAVE_AXES.bits() | super::VarcFlags::HAVE_TRANSLATE_X.bits(),
1159            ));
1160            bytes.extend_from_slice(&[0x00, 0x01]); // gid
1161            bytes.extend_from_slice(&encode_u32_var(1)); // axis_indices_index => 5 values
1162            bytes.extend_from_slice(&payload);
1163            bytes.extend_from_slice(&[0x00, 0x07]); // translate_x
1164            bytes.push(0xAA); // sentinel
1165
1166            let data = FontData::new(&bytes);
1167            let mut cursor = data.cursor();
1168            let component = super::VarcComponent::parse(&table, &mut cursor).unwrap();
1169            let mut out = [0.0f32; 5];
1170            component
1171                .axis_values()
1172                .unwrap()
1173                .fetcher()
1174                .add_to_f32_scaled(&mut out, 1.0)
1175                .unwrap();
1176            assert_eq!(out, expected);
1177            assert_eq!(component.transform().translate_x(), 7.0);
1178            assert_eq!(cursor.read::<u8>().unwrap(), 0xAA);
1179        }
1180    }
1181
1182    #[test]
1183    fn parse_component_truncated_condition_varint_boundaries_error() {
1184        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
1185        let table = font.varc().unwrap();
1186        let values = [0x80_u32, 0x4000_u32, 0x20_0000_u32, 0x1000_0000_u32];
1187
1188        for value in values {
1189            let encoded = encode_u32_var(value);
1190            assert!(encoded.len() > 1);
1191            let truncated = &encoded[..encoded.len() - 1];
1192
1193            let mut bytes = Vec::new();
1194            bytes.extend_from_slice(&encode_u32_var(super::VarcFlags::HAVE_CONDITION.bits()));
1195            bytes.extend_from_slice(&[0x00, 0x01]); // gid
1196            bytes.extend_from_slice(truncated);
1197            let data = FontData::new(&bytes);
1198            let mut cursor = data.cursor();
1199
1200            assert!(matches!(
1201                super::VarcComponent::parse(&table, &mut cursor),
1202                Err(ReadError::OutOfBounds)
1203            ));
1204        }
1205    }
1206
1207    #[test]
1208    fn read_glyph_6868() {
1209        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
1210        let gid = font.cmap().unwrap().map_codepoint(0x6868_u32).unwrap();
1211        let table = font.varc().unwrap();
1212        let idx = table.coverage().unwrap().get(gid).unwrap();
1213
1214        let glyph = table.glyph(idx as usize).unwrap();
1215        assert_eq!(
1216            vec![GlyphId16::new(2), GlyphId16::new(5), GlyphId16::new(7)],
1217            glyph
1218                .components()
1219                .map(|c| c.unwrap().gid)
1220                .collect::<Vec<_>>()
1221        );
1222    }
1223
1224    // Expected created using the Python DecomposedTransform
1225    #[test]
1226    fn decomposed_scale_to_matrix() {
1227        let scale_x = 2.0;
1228        let scale_y = 3.0;
1229        assert_eq!(
1230            [scale_x, 0.0, 0.0, scale_y, 0.0, 0.0],
1231            DecomposedTransform {
1232                scale_x,
1233                scale_y,
1234                ..Default::default()
1235            }
1236            .matrix()
1237            .map(round6)
1238            .elements()
1239        );
1240    }
1241
1242    // Expected created using the Python DecomposedTransform
1243    #[test]
1244    fn decomposed_rotate_to_matrix() {
1245        assert_eq!(
1246            [0.0, 1.0, -1.0, 0.0, 0.0, 0.0],
1247            DecomposedTransform {
1248                // Rotation is in multiples of Pi (90 degrees = 0.5 * Pi).
1249                rotation: 0.5,
1250                ..Default::default()
1251            }
1252            .matrix()
1253            .map(round6)
1254            .elements()
1255        );
1256    }
1257
1258    // Expected created using the Python DecomposedTransform
1259    #[test]
1260    fn decomposed_skew_to_matrix() {
1261        // Skew is in multiples of Pi.
1262        let skew_x: f32 = 1.0 / 6.0; // 30 degrees
1263        let skew_y: f32 = -1.0 / 3.0; // -60 degrees
1264        assert_eq!(
1265            [
1266                1.0,
1267                round6((skew_y * core::f32::consts::PI).tan()),
1268                round6((-skew_x * core::f32::consts::PI).tan()),
1269                1.0,
1270                0.0,
1271                0.0
1272            ],
1273            DecomposedTransform {
1274                skew_x,
1275                skew_y,
1276                ..Default::default()
1277            }
1278            .matrix()
1279            .map(round6)
1280            .elements()
1281        );
1282    }
1283
1284    // Expected created using the Python DecomposedTransform
1285    #[test]
1286    fn decomposed_scale_rotate_to_matrix() {
1287        let scale_x = 2.0;
1288        let scale_y = 3.0;
1289        assert_eq!(
1290            [0.0, scale_x, -scale_y, 0.0, 0.0, 0.0],
1291            DecomposedTransform {
1292                scale_x,
1293                scale_y,
1294                // 90 degrees = 0.5 * Pi.
1295                rotation: 0.5,
1296                ..Default::default()
1297            }
1298            .matrix()
1299            .map(round6)
1300            .elements()
1301        );
1302    }
1303
1304    // Expected created using the Python DecomposedTransform
1305    #[test]
1306    fn decomposed_scale_rotate_translate_to_matrix() {
1307        assert_eq!(
1308            [0.0, 2.0, -1.0, 0.0, 10.0, 20.0],
1309            DecomposedTransform {
1310                scale_x: 2.0,
1311                // 90 degrees = 0.5 * Pi.
1312                rotation: 0.5,
1313                translate_x: 10.0,
1314                translate_y: 20.0,
1315                ..Default::default()
1316            }
1317            .matrix()
1318            .map(round6)
1319            .elements()
1320        );
1321    }
1322
1323    // Expected created using the Python DecomposedTransform
1324    #[test]
1325    fn decomposed_scale_skew_translate_to_matrix() {
1326        assert_eq!(
1327            [-0.866026, 5.5, -2.5, 2.020726, 10.0, 20.0],
1328            DecomposedTransform {
1329                scale_x: 2.0,
1330                scale_y: 3.0,
1331                // Angles are in multiples of Pi.
1332                rotation: 1.0 / 6.0, // 30 degrees
1333                skew_x: 1.0 / 6.0,   // 30 degrees
1334                skew_y: 1.0 / 3.0,   // 60 degrees
1335                translate_x: 10.0,
1336                translate_y: 20.0,
1337                ..Default::default()
1338            }
1339            .matrix()
1340            .map(round6)
1341            .elements()
1342        );
1343    }
1344
1345    // Expected created using the Python DecomposedTransform
1346    #[test]
1347    fn decomposed_rotate_around_to_matrix() {
1348        assert_eq!(
1349            [1.732051, 1.0, -0.5, 0.866025, 10.267949, 19.267949],
1350            DecomposedTransform {
1351                scale_x: 2.0,
1352                // 30 degrees = 1/6 * Pi.
1353                rotation: 1.0 / 6.0,
1354                translate_x: 10.0,
1355                translate_y: 20.0,
1356                center_x: 1.0,
1357                center_y: 2.0,
1358                ..Default::default()
1359            }
1360            .matrix()
1361            .map(round6)
1362            .elements()
1363        );
1364    }
1365
1366    #[test]
1367    fn read_multivar_store_region_list() {
1368        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
1369        let table = font.varc().unwrap();
1370        let varstore = table.multi_var_store().unwrap().unwrap();
1371        let regions = varstore.region_list().unwrap().regions();
1372
1373        let sparse_regions = regions
1374            .iter()
1375            .map(|r| {
1376                r.unwrap()
1377                    .region_axes()
1378                    .iter()
1379                    .map(|a| {
1380                        (
1381                            a.axis_index(),
1382                            a.start().to_f32(),
1383                            a.peak().to_f32(),
1384                            a.end().to_f32(),
1385                        )
1386                    })
1387                    .collect::<Vec<_>>()
1388            })
1389            .collect::<Vec<_>>();
1390
1391        // Check a sampling of the regions
1392        assert_eq!(
1393            vec![
1394                vec![(0, 0.0, 1.0, 1.0),],
1395                vec![(0, 0.0, 1.0, 1.0), (1, 0.0, 1.0, 1.0),],
1396                vec![(6, -1.0, -1.0, 0.0),],
1397            ],
1398            [0, 2, 38]
1399                .into_iter()
1400                .map(|i| sparse_regions[i].clone())
1401                .collect::<Vec<_>>()
1402        );
1403    }
1404
1405    #[test]
1406    fn read_multivar_store_delta_sets() {
1407        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
1408        let table = font.varc().unwrap();
1409        let varstore = table.multi_var_store().unwrap().unwrap();
1410        assert_eq!(
1411            vec![(3, 6), (33, 6), (10, 5), (25, 8),],
1412            varstore
1413                .variation_data()
1414                .iter()
1415                .map(|d| d.unwrap())
1416                .map(|d| (d.region_index_count(), d.delta_sets().unwrap().count()))
1417                .collect::<Vec<_>>()
1418        );
1419        assert_eq!(
1420            vec![-1, 33, 0, 0, 0, 0],
1421            varstore
1422                .variation_data()
1423                .get(0)
1424                .unwrap()
1425                .delta_set(5)
1426                .unwrap()
1427                .iter()
1428                .collect::<Vec<_>>()
1429        )
1430    }
1431}