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