read_fonts/tables/
varc.rs

1//! the [VARC (Variable Composite/Component)](https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md) table
2
3use super::variations::PackedDeltas;
4pub use super::{
5    layout::{Condition, CoverageTable},
6    postscript::Index2,
7};
8
9#[cfg(feature = "libm")]
10#[allow(unused_imports)]
11use core_maths::*;
12
13include!("../../generated/generated_varc.rs");
14
15/// Let's us call self.something().get(i) instead of get(self.something(), i)
16trait Get<'a> {
17    fn get(self, nth: usize) -> Result<&'a [u8], ReadError>;
18}
19
20impl<'a> Get<'a> for Option<Result<Index2<'a>, ReadError>> {
21    fn get(self, nth: usize) -> Result<&'a [u8], ReadError> {
22        self.transpose()?
23            .ok_or(ReadError::NullOffset)
24            .and_then(|index| index.get(nth).map_err(|_| ReadError::OutOfBounds))
25    }
26}
27
28impl Varc<'_> {
29    /// Friendlier accessor than directly using raw data via [Index2]
30    pub fn axis_indices(&self, nth: usize) -> Result<PackedDeltas, ReadError> {
31        let raw = self.axis_indices_list().get(nth)?;
32        Ok(PackedDeltas::consume_all(raw.into()))
33    }
34
35    /// Friendlier accessor than directly using raw data via [Index2]
36    ///
37    /// nth would typically be obtained by looking up a [GlyphId] in [Self::coverage].
38    pub fn glyph(&self, nth: usize) -> Result<VarcGlyph<'_>, ReadError> {
39        let raw = Some(self.var_composite_glyphs()).get(nth)?;
40        Ok(VarcGlyph {
41            table: self,
42            data: raw.into(),
43        })
44    }
45}
46
47/// A VARC glyph doesn't have any root level attributes, it's just a list of components
48///
49/// <https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md#variable-composite-description>
50pub struct VarcGlyph<'a> {
51    table: &'a Varc<'a>,
52    data: FontData<'a>,
53}
54
55impl<'a> VarcGlyph<'a> {
56    /// <https://github.com/fonttools/fonttools/blob/5e6b12d12fa08abafbeb7570f47707fbedf69a45/Lib/fontTools/ttLib/tables/otTables.py#L404-L409>
57    pub fn components(&self) -> impl Iterator<Item = Result<VarcComponent<'a>, ReadError>> {
58        VarcComponentIter {
59            table: self.table,
60            cursor: self.data.cursor(),
61        }
62    }
63}
64
65struct VarcComponentIter<'a> {
66    table: &'a Varc<'a>,
67    cursor: Cursor<'a>,
68}
69
70impl<'a> Iterator for VarcComponentIter<'a> {
71    type Item = Result<VarcComponent<'a>, ReadError>;
72
73    fn next(&mut self) -> Option<Self::Item> {
74        if self.cursor.is_empty() {
75            return None;
76        }
77        Some(VarcComponent::parse(self.table, &mut self.cursor))
78    }
79}
80
81#[allow(dead_code)] // TEMPORARY
82pub struct VarcComponent<'a> {
83    flags: VarcFlags,
84    gid: GlyphId,
85    condition_index: Option<u32>,
86    axis_indices_index: Option<u32>,
87    axis_values: Option<PackedDeltas<'a>>,
88    axis_values_var_index: Option<u32>,
89    transform_var_index: Option<u32>,
90    transform: DecomposedTransform,
91}
92
93impl<'a> VarcComponent<'a> {
94    /// Requires access to VARC fields to fully parse.
95    ///
96    ///  * HarfBuzz [VarComponent::get_path_at](https://github.com/harfbuzz/harfbuzz/blob/0c2f5ecd51d11e32836ee136a1bc765d650a4ec0/src/OT/Var/VARC/VARC.cc#L132)
97    // TODO: do we want to be able to parse into an existing glyph to avoid allocation?
98    fn parse(table: &Varc, cursor: &mut Cursor<'a>) -> Result<Self, ReadError> {
99        let raw_flags = cursor.read_u32_var()?;
100        let flags = VarcFlags::from_bits_truncate(raw_flags);
101        // Ref https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md#variable-component-record
102
103        // This is a GlyphID16 if GID_IS_24BIT bit of flags is clear, else GlyphID24.
104        let gid = if flags.contains(VarcFlags::GID_IS_24BIT) {
105            GlyphId::new(cursor.read::<Uint24>()?.to_u32())
106        } else {
107            GlyphId::from(cursor.read::<u16>()?)
108        };
109
110        let condition_index = if flags.contains(VarcFlags::HAVE_CONDITION) {
111            Some(cursor.read_u32_var()?)
112        } else {
113            None
114        };
115
116        let (axis_indices_index, axis_values) = if flags.contains(VarcFlags::HAVE_AXES) {
117            // <https://github.com/harfbuzz/harfbuzz/blob/0c2f5ecd51d11e32836ee136a1bc765d650a4ec0/src/OT/Var/VARC/VARC.cc#L195-L206>
118            let axis_indices_index = cursor.read_u32_var()?;
119            let num_axis_values = table.axis_indices(axis_indices_index as usize)?.count();
120            // we need to consume num_axis_values entries in packed delta format
121            let deltas = if num_axis_values > 0 {
122                let Some(data) = cursor.remaining() else {
123                    return Err(ReadError::OutOfBounds);
124                };
125                let deltas = PackedDeltas::new(data, num_axis_values);
126                *cursor = deltas.iter().end(); // jump past the packed deltas
127                Some(deltas)
128            } else {
129                None
130            };
131            (Some(axis_indices_index), deltas)
132        } else {
133            (None, None)
134        };
135
136        let axis_values_var_index = flags
137            .contains(VarcFlags::AXIS_VALUES_HAVE_VARIATION)
138            .then(|| cursor.read_u32_var())
139            .transpose()?;
140
141        let transform_var_index = if flags.contains(VarcFlags::TRANSFORM_HAS_VARIATION) {
142            Some(cursor.read_u32_var()?)
143        } else {
144            None
145        };
146
147        let mut transform = DecomposedTransform::default();
148        if flags.contains(VarcFlags::HAVE_TRANSLATE_X) {
149            transform.translate_x = cursor.read::<FWord>()?.to_i16() as f64
150        }
151        if flags.contains(VarcFlags::HAVE_TRANSLATE_Y) {
152            transform.translate_y = cursor.read::<FWord>()?.to_i16() as f64
153        }
154        if flags.contains(VarcFlags::HAVE_ROTATION) {
155            transform.rotation = cursor.read::<F4Dot12>()?.to_f32() as f64
156        }
157        if flags.contains(VarcFlags::HAVE_SCALE_X) {
158            transform.scale_x = cursor.read::<F6Dot10>()?.to_f32() as f64
159        }
160        transform.scale_y = if flags.contains(VarcFlags::HAVE_SCALE_Y) {
161            cursor.read::<F6Dot10>()?.to_f32() as f64
162        } else {
163            transform.scale_x
164        };
165        if flags.contains(VarcFlags::HAVE_SKEW_X) {
166            transform.skew_x = cursor.read::<F4Dot12>()?.to_f32() as f64
167        }
168        if flags.contains(VarcFlags::HAVE_SKEW_Y) {
169            transform.skew_y = cursor.read::<F4Dot12>()?.to_f32() as f64
170        }
171        if flags.contains(VarcFlags::HAVE_TCENTER_X) {
172            transform.center_x = cursor.read::<FWord>()?.to_i16() as f64
173        }
174        if flags.contains(VarcFlags::HAVE_TCENTER_Y) {
175            transform.center_y = cursor.read::<FWord>()?.to_i16() as f64
176        }
177
178        // Optional, process and discard one uint32var per each set bit in RESERVED_MASK.
179        let num_reserved = (raw_flags & VarcFlags::RESERVED_MASK.bits).count_ones();
180        for _ in 0..num_reserved {
181            cursor.read_u32_var()?;
182        }
183        Ok(VarcComponent {
184            flags,
185            gid,
186            condition_index,
187            axis_indices_index,
188            axis_values,
189            axis_values_var_index,
190            transform_var_index,
191            transform,
192        })
193    }
194}
195
196/// <https://github.com/fonttools/fonttools/blob/5e6b12d12fa08abafbeb7570f47707fbedf69a45/Lib/fontTools/misc/transform.py#L410>
197pub struct DecomposedTransform {
198    translate_x: f64,
199    translate_y: f64,
200    rotation: f64, // degrees, counter-clockwise
201    scale_x: f64,
202    scale_y: f64,
203    skew_x: f64,
204    skew_y: f64,
205    center_x: f64,
206    center_y: f64,
207}
208
209impl Default for DecomposedTransform {
210    fn default() -> Self {
211        Self {
212            translate_x: 0.0,
213            translate_y: 0.0,
214            rotation: 0.0,
215            scale_x: 1.0,
216            scale_y: 1.0,
217            skew_x: 0.0,
218            skew_y: 0.0,
219            center_x: 0.0,
220            center_y: 0.0,
221        }
222    }
223}
224
225impl DecomposedTransform {
226    /// Convert decomposed form to 2x3 matrix form.
227    ///
228    /// The first two values are x,y x-basis vector,
229    /// the second 2 values are x,y y-basis vector, and the third 2 are translation.
230    ///
231    /// In augmented matrix
232    /// form, if this method returns `[a, b, c, d, e, f]` that is taken as:
233    ///
234    /// ```text
235    /// | a c e |
236    /// | b d f |
237    /// | 0 0 1 |
238    /// ```
239    ///
240    /// References:
241    /// * FontTools Python implementation <https://github.com/fonttools/fonttools/blob/5e6b12d12fa08abafbeb7570f47707fbedf69a45/Lib/fontTools/misc/transform.py#L484-L500>
242    /// * Wikipedia [affine transformation](https://en.wikipedia.org/wiki/Affine_transformation)
243    pub fn matrix(&self) -> [f64; 6] {
244        // Python: t.translate(self.translateX + self.tCenterX, self.translateY + self.tCenterY)
245        let mut transform = [
246            1.0,
247            0.0,
248            0.0,
249            1.0,
250            self.translate_x + self.center_x,
251            self.translate_y + self.center_y,
252        ];
253
254        // TODO: this produces very small floats for rotations, e.g. 90 degree rotation a basic scale
255        // puts 1.2246467991473532e-16 into [0]. Should we special case? Round?
256
257        // Python: t = t.rotate(math.radians(self.rotation))
258        if self.rotation != 0.0 {
259            let (s, c) = (self.rotation).to_radians().sin_cos();
260            transform = transform.transform([c, s, -s, c, 0.0, 0.0]);
261        }
262
263        // Python: t = t.scale(self.scaleX, self.scaleY)
264        if (self.scale_x, self.scale_y) != (1.0, 1.0) {
265            transform = transform.transform([self.scale_x, 0.0, 0.0, self.scale_y, 0.0, 0.0]);
266        }
267
268        // Python: t = t.skew(math.radians(self.skewX), math.radians(self.skewY))
269        if (self.skew_x, self.skew_y) != (0.0, 0.0) {
270            transform = transform.transform([
271                1.0,
272                self.skew_y.to_radians().tan(),
273                self.skew_x.to_radians().tan(),
274                1.0,
275                0.0,
276                0.0,
277            ])
278        }
279
280        // Python: t = t.translate(-self.tCenterX, -self.tCenterY)
281        if (self.center_x, self.center_y) != (0.0, 0.0) {
282            transform = transform.transform([1.0, 0.0, 0.0, 1.0, -self.center_x, -self.center_y]);
283        }
284
285        transform
286    }
287}
288
289trait Transform {
290    fn transform(self, other: Self) -> Self;
291}
292
293impl Transform for [f64; 6] {
294    fn transform(self, other: Self) -> Self {
295        // Shamelessly copied from kurbo Affine Mul
296        [
297            self[0] * other[0] + self[2] * other[1],
298            self[1] * other[0] + self[3] * other[1],
299            self[0] * other[2] + self[2] * other[3],
300            self[1] * other[2] + self[3] * other[3],
301            self[0] * other[4] + self[2] * other[5] + self[4],
302            self[1] * other[4] + self[3] * other[5] + self[5],
303        ]
304    }
305}
306
307impl<'a> MultiItemVariationData<'a> {
308    /// An [Index2] where each item is a [PackedDeltas]
309    pub fn delta_sets(&self) -> Result<Index2<'a>, ReadError> {
310        Index2::read(self.raw_delta_sets().into())
311    }
312
313    /// Read a specific delta set.
314    ///
315    /// Equivalent to calling [Self::delta_sets], fetching item i, and parsing as [PackedDeltas]
316    pub fn delta_set(&self, i: usize) -> Result<PackedDeltas<'a>, ReadError> {
317        let index = self.delta_sets()?;
318        let raw_deltas = index.get(i).map_err(|_| ReadError::OutOfBounds)?;
319        Ok(PackedDeltas::consume_all(raw_deltas.into()))
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use types::GlyphId16;
326
327    use crate::{FontRef, ReadError, TableProvider};
328
329    use super::{Condition, DecomposedTransform, Varc};
330
331    impl Varc<'_> {
332        fn conditions(&self) -> impl Iterator<Item = Condition<'_>> {
333            self.condition_list()
334                .expect("A condition list is present")
335                .expect("We could read the condition list")
336                .conditions()
337                .iter()
338                .enumerate()
339                .map(|(i, c)| c.unwrap_or_else(|e| panic!("condition {i} {e}")))
340        }
341
342        fn axis_indices_count(&self) -> Result<usize, ReadError> {
343            let Some(axis_indices_list) = self.axis_indices_list() else {
344                return Ok(0);
345            };
346            let axis_indices_list = axis_indices_list?;
347            Ok(axis_indices_list.count() as usize)
348        }
349    }
350
351    fn round6(v: f64) -> f64 {
352        (v * 1_000_000.0).round() / 1_000_000.0
353    }
354
355    trait Round {
356        fn round_for_test(self) -> Self;
357    }
358
359    impl Round for [f64; 6] {
360        fn round_for_test(self) -> Self {
361            [
362                round6(self[0]),
363                round6(self[1]),
364                round6(self[2]),
365                round6(self[3]),
366                round6(self[4]),
367                round6(self[5]),
368            ]
369        }
370    }
371
372    #[test]
373    fn read_cjk_0x6868() {
374        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
375        let table = font.varc().unwrap();
376        table.coverage().unwrap(); // should have coverage
377    }
378
379    #[test]
380    fn identify_all_conditional_types() {
381        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
382        let table = font.varc().unwrap();
383
384        // We should have all 5 condition types in order
385        assert_eq!(
386            (1..=5).collect::<Vec<_>>(),
387            table.conditions().map(|c| c.format()).collect::<Vec<_>>()
388        );
389    }
390
391    #[test]
392    fn read_condition_format1_axis_range() {
393        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
394        let table = font.varc().unwrap();
395        let Some(Condition::Format1AxisRange(condition)) =
396            table.conditions().find(|c| c.format() == 1)
397        else {
398            panic!("No such item");
399        };
400
401        assert_eq!(
402            (0, 0.5, 1.0),
403            (
404                condition.axis_index(),
405                condition.filter_range_min_value().to_f32(),
406                condition.filter_range_max_value().to_f32(),
407            )
408        );
409    }
410
411    #[test]
412    fn read_condition_format2_variable_value() {
413        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
414        let table = font.varc().unwrap();
415        let Some(Condition::Format2VariableValue(condition)) =
416            table.conditions().find(|c| c.format() == 2)
417        else {
418            panic!("No such item");
419        };
420
421        assert_eq!((1, 2), (condition.default_value(), condition.var_index(),));
422    }
423
424    #[test]
425    fn read_condition_format3_and() {
426        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
427        let table = font.varc().unwrap();
428        let Some(Condition::Format3And(condition)) = table.conditions().find(|c| c.format() == 3)
429        else {
430            panic!("No such item");
431        };
432
433        // Should reference a format 1 and a format 2
434        assert_eq!(
435            vec![1, 2],
436            condition
437                .conditions()
438                .iter()
439                .map(|c| c.unwrap().format())
440                .collect::<Vec<_>>()
441        );
442    }
443
444    #[test]
445    fn read_condition_format4_or() {
446        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
447        let table = font.varc().unwrap();
448        let Some(Condition::Format4Or(condition)) = table.conditions().find(|c| c.format() == 4)
449        else {
450            panic!("No such item");
451        };
452
453        // Should reference a format 1 and a format 2
454        assert_eq!(
455            vec![1, 2],
456            condition
457                .conditions()
458                .iter()
459                .map(|c| c.unwrap().format())
460                .collect::<Vec<_>>()
461        );
462    }
463
464    #[test]
465    fn read_condition_format5_negate() {
466        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
467        let table = font.varc().unwrap();
468        let Some(Condition::Format5Negate(condition)) =
469            table.conditions().find(|c| c.format() == 5)
470        else {
471            panic!("No such item");
472        };
473
474        // Should reference a format 1
475        assert_eq!(1, condition.condition().unwrap().format(),);
476    }
477
478    #[test]
479    fn read_axis_indices_list() {
480        let font = FontRef::new(font_test_data::varc::CONDITIONALS).unwrap();
481        let table = font.varc().unwrap();
482        assert_eq!(table.axis_indices_count().unwrap(), 2);
483        assert_eq!(
484            vec![2, 3, 4, 5, 6],
485            table.axis_indices(1).unwrap().iter().collect::<Vec<_>>()
486        );
487    }
488
489    #[test]
490    fn read_glyph_6868() {
491        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
492        let gid = font.cmap().unwrap().map_codepoint(0x6868_u32).unwrap();
493        let table = font.varc().unwrap();
494        let idx = table.coverage().unwrap().get(gid).unwrap();
495
496        let glyph = table.glyph(idx as usize).unwrap();
497        assert_eq!(
498            vec![GlyphId16::new(2), GlyphId16::new(5), GlyphId16::new(7)],
499            glyph
500                .components()
501                .map(|c| c.unwrap().gid)
502                .collect::<Vec<_>>()
503        );
504    }
505
506    // Expected created using the Python DecomposedTransform
507    #[test]
508    fn decomposed_scale_to_matrix() {
509        let scale_x = 2.0;
510        let scale_y = 3.0;
511        assert_eq!(
512            [scale_x, 0.0, 0.0, scale_y, 0.0, 0.0],
513            DecomposedTransform {
514                scale_x,
515                scale_y,
516                ..Default::default()
517            }
518            .matrix()
519            .round_for_test()
520        );
521    }
522
523    // Expected created using the Python DecomposedTransform
524    #[test]
525    fn decomposed_rotate_to_matrix() {
526        assert_eq!(
527            [0.0, 1.0, -1.0, 0.0, 0.0, 0.0],
528            DecomposedTransform {
529                rotation: 90.0,
530                ..Default::default()
531            }
532            .matrix()
533            .round_for_test()
534        );
535    }
536
537    // Expected created using the Python DecomposedTransform
538    #[test]
539    fn decomposed_skew_to_matrix() {
540        let skew_x: f64 = 30.0;
541        let skew_y: f64 = -60.0;
542        assert_eq!(
543            [
544                1.0,
545                round6(skew_y.to_radians().tan()),
546                round6(skew_x.to_radians().tan()),
547                1.0,
548                0.0,
549                0.0
550            ],
551            DecomposedTransform {
552                skew_x,
553                skew_y,
554                ..Default::default()
555            }
556            .matrix()
557            .round_for_test()
558        );
559    }
560
561    // Expected created using the Python DecomposedTransform
562    #[test]
563    fn decomposed_scale_rotate_to_matrix() {
564        let scale_x = 2.0;
565        let scale_y = 3.0;
566        assert_eq!(
567            [0.0, scale_x, -scale_y, 0.0, 0.0, 0.0],
568            DecomposedTransform {
569                scale_x,
570                scale_y,
571                rotation: 90.0,
572                ..Default::default()
573            }
574            .matrix()
575            .round_for_test()
576        );
577    }
578
579    // Expected created using the Python DecomposedTransform
580    #[test]
581    fn decomposed_scale_rotate_translate_to_matrix() {
582        assert_eq!(
583            [0.0, 2.0, -1.0, 0.0, 10.0, 20.0],
584            DecomposedTransform {
585                scale_x: 2.0,
586                rotation: 90.0,
587                translate_x: 10.0,
588                translate_y: 20.0,
589                ..Default::default()
590            }
591            .matrix()
592            .round_for_test()
593        );
594    }
595
596    // Expected created using the Python DecomposedTransform
597    #[test]
598    fn decomposed_scale_skew_translate_to_matrix() {
599        assert_eq!(
600            [-0.866025, 5.5, -0.5, 3.175426, 10.0, 20.0],
601            DecomposedTransform {
602                scale_x: 2.0,
603                scale_y: 3.0,
604                rotation: 30.0,
605                skew_x: 30.0,
606                skew_y: 60.0,
607                translate_x: 10.0,
608                translate_y: 20.0,
609                ..Default::default()
610            }
611            .matrix()
612            .round_for_test()
613        );
614    }
615
616    // Expected created using the Python DecomposedTransform
617    #[test]
618    fn decomposed_rotate_around_to_matrix() {
619        assert_eq!(
620            [1.732051, 1.0, -0.5, 0.866025, 10.267949, 19.267949],
621            DecomposedTransform {
622                scale_x: 2.0,
623                rotation: 30.0,
624                translate_x: 10.0,
625                translate_y: 20.0,
626                center_x: 1.0,
627                center_y: 2.0,
628                ..Default::default()
629            }
630            .matrix()
631            .round_for_test()
632        );
633    }
634
635    #[test]
636    fn read_multivar_store_region_list() {
637        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
638        let table = font.varc().unwrap();
639        let varstore = table.multi_var_store().unwrap().unwrap();
640        let regions = varstore.region_list().unwrap().regions();
641
642        let sparse_regions = regions
643            .iter()
644            .map(|r| {
645                r.unwrap()
646                    .region_axis_offsets()
647                    .iter()
648                    .map(|a| {
649                        (
650                            a.axis_index(),
651                            a.start().to_f32(),
652                            a.peak().to_f32(),
653                            a.end().to_f32(),
654                        )
655                    })
656                    .collect::<Vec<_>>()
657            })
658            .collect::<Vec<_>>();
659
660        // Check a sampling of the regions
661        assert_eq!(
662            vec![
663                vec![(0, 0.0, 1.0, 1.0),],
664                vec![(0, 0.0, 1.0, 1.0), (1, 0.0, 1.0, 1.0),],
665                vec![(6, -1.0, -1.0, 0.0),],
666            ],
667            [0, 2, 38]
668                .into_iter()
669                .map(|i| sparse_regions[i].clone())
670                .collect::<Vec<_>>()
671        );
672    }
673
674    #[test]
675    fn read_multivar_store_delta_sets() {
676        let font = FontRef::new(font_test_data::varc::CJK_6868).unwrap();
677        let table = font.varc().unwrap();
678        let varstore = table.multi_var_store().unwrap().unwrap();
679        assert_eq!(
680            vec![(3, 6), (33, 6), (10, 5), (25, 8),],
681            varstore
682                .variation_data()
683                .iter()
684                .map(|d| d.unwrap())
685                .map(|d| (d.region_index_count(), d.delta_sets().unwrap().count()))
686                .collect::<Vec<_>>()
687        );
688        assert_eq!(
689            vec![-1, 33, 0, 0, 0, 0],
690            varstore
691                .variation_data()
692                .get(0)
693                .unwrap()
694                .delta_set(5)
695                .unwrap()
696                .iter()
697                .collect::<Vec<_>>()
698        )
699    }
700}