Skip to main content

write_fonts/tables/
varc.rs

1//! The [VARC (Variable Composites/Components)](https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md) table
2
3use std::collections::BTreeMap;
4
5pub use super::layout::{Condition, CoverageTable};
6use crate::ps::cff::v2::Index as Index2;
7use crate::tables::variations::{
8    common_builder::{TemporaryDeltaSetId, NO_VARIATION_INDEX},
9    mivs_builder::{MultiItemVariationStoreBuilder, MultiVariationIndexRemapping},
10    PackedDeltas,
11};
12
13include!("../../generated/generated_varc.rs");
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum VarcVariationIndex {
17    PendingVariationIndex(TemporaryDeltaSetId),
18    VariationIndex(u32),
19}
20
21impl VarcVariationIndex {
22    pub fn to_u32(&self, remappings: &MultiVariationIndexRemapping) -> u32 {
23        match self {
24            VarcVariationIndex::PendingVariationIndex(temp_id) => {
25                remappings.get(*temp_id).unwrap_or(NO_VARIATION_INDEX)
26            }
27            VarcVariationIndex::VariationIndex(idx) => *idx,
28        }
29    }
30}
31
32impl Varc {
33    pub fn new_from_composite_glyphs(
34        coverage: CoverageTable,
35        store_builder: MultiItemVariationStoreBuilder,
36        conditions: Vec<Condition>,
37        composites: Vec<VarCompositeGlyph>,
38    ) -> Self {
39        let axis_indices_list = Varc::create_axis_indices_list(&composites);
40        let axis_indices_list_items = axis_indices_list
41            .iter()
42            .map(|axes_indices| {
43                let mut writer = TableWriter::default();
44                let packed = PackedDeltas::new(axes_indices.iter().map(|v| *v as i32).collect());
45                packed.write_into(&mut writer);
46                writer.into_data().bytes
47            })
48            .collect();
49        let (store, remappings) = store_builder.build();
50
51        let axis_indices_list_raw = Index2::from_items(axis_indices_list_items);
52        let var_composite_glyphs = Index2::from_items(
53            composites
54                .iter()
55                .map(|x| x.to_bytes(&axis_indices_list, &remappings))
56                .collect(),
57        );
58        let condition_list: NullableOffsetMarker<ConditionList, WIDTH_32> =
59            NullableOffsetMarker::new(if conditions.is_empty() {
60                None
61            } else {
62                Some(ConditionList::new(conditions.len() as u32, conditions))
63            });
64        let multi_var_store: NullableOffsetMarker<MultiItemVariationStore, WIDTH_32> =
65            NullableOffsetMarker::new(if store.variation_data_count == 0 {
66                None
67            } else {
68                Some(store)
69            });
70        Self {
71            coverage: coverage.into(),
72            multi_var_store,
73            condition_list,
74            axis_indices_list: axis_indices_list_raw.into(),
75            var_composite_glyphs: var_composite_glyphs.into(),
76        }
77    }
78
79    fn create_axis_indices_list(composites: &Vec<VarCompositeGlyph>) -> Vec<Vec<u16>> {
80        let mut axis_indices_counter: BTreeMap<Vec<u16>, u32> = BTreeMap::new();
81
82        for composite in composites {
83            for component in &composite.0 {
84                if let Some(axis_values) = &component.axis_values {
85                    let axis_indices: Vec<u16> = axis_values.keys().cloned().collect();
86                    *axis_indices_counter.entry(axis_indices).or_insert(0) += 1;
87                }
88            }
89        }
90        // Order by most used to least used
91        let mut axis_indices_list: Vec<(Vec<u16>, u32)> = axis_indices_counter
92            .into_iter()
93            .collect::<Vec<(Vec<u16>, u32)>>();
94        axis_indices_list.sort_by_key(|(_, count)| std::cmp::Reverse(*count));
95
96        axis_indices_list
97            .into_iter()
98            .map(|(indices, _)| indices)
99            .collect()
100    }
101}
102
103pub struct VarCompositeGlyph(pub Vec<VarComponent>);
104impl VarCompositeGlyph {
105    fn to_bytes(
106        &self,
107        axis_indices_list: &[Vec<u16>],
108        remappings: &MultiVariationIndexRemapping,
109    ) -> Vec<u8> {
110        let mut writer = TableWriter::default();
111        for component in &self.0 {
112            let raw_component = RawVarComponent {
113                flags: if component.reset_unspecified_axes {
114                    VarcFlags::RESET_UNSPECIFIED_AXES
115                } else {
116                    VarcFlags::empty()
117                },
118                gid: component.gid,
119                condition_index: component.condition_index.map(|ci| ci.to_u32(remappings)),
120                axis_indices_index: component.axis_values.as_ref().and_then(|axis_values| {
121                    let axis_indices: Vec<u16> = axis_values.keys().cloned().collect();
122                    axis_indices_list
123                        .iter()
124                        .position(|indices| *indices == axis_indices)
125                        .map(|idx| idx as u32)
126                }),
127                axis_values: component
128                    .axis_values
129                    .as_ref()
130                    .map(|axis_values| axis_values.values().cloned().collect::<Vec<f32>>()),
131                axis_values_var_index: component
132                    .axis_values_var_index
133                    .map(|avi| avi.to_u32(remappings)),
134                transform_var_index: component
135                    .transform_var_index
136                    .map(|tvi| tvi.to_u32(remappings)),
137                transform: component.transform.clone(),
138            };
139            raw_component.write_into(&mut writer);
140        }
141        writer.into_data().bytes
142    }
143}
144
145pub struct VarComponent {
146    pub reset_unspecified_axes: bool,
147    pub gid: GlyphId,
148    pub condition_index: Option<VarcVariationIndex>,
149    pub axis_values: Option<BTreeMap<u16, f32>>,
150    pub axis_values_var_index: Option<VarcVariationIndex>,
151    pub transform: DecomposedTransform,
152    pub transform_var_index: Option<VarcVariationIndex>,
153}
154
155struct RawVarComponent {
156    flags: VarcFlags,
157    gid: GlyphId,
158    condition_index: Option<u32>,
159    axis_indices_index: Option<u32>,
160    axis_values: Option<Vec<f32>>,
161    axis_values_var_index: Option<u32>,
162    transform_var_index: Option<u32>,
163    transform: DecomposedTransform,
164}
165
166impl RawVarComponent {
167    fn determine_flags(&self) -> Uint32Var {
168        let mut flags = self.flags;
169
170        if self.gid.to_u32() > 0xFFFF {
171            flags.insert(VarcFlags::GID_IS_24BIT);
172        }
173
174        if self.condition_index.is_some() {
175            flags.insert(VarcFlags::HAVE_CONDITION);
176        }
177
178        if self.axis_indices_index.is_some() {
179            flags.insert(VarcFlags::HAVE_AXES);
180        }
181
182        if self.axis_values_var_index.is_some() {
183            flags.insert(VarcFlags::AXIS_VALUES_HAVE_VARIATION);
184        }
185
186        if self.transform_var_index.is_some() {
187            flags.insert(VarcFlags::TRANSFORM_HAS_VARIATION);
188        }
189        flags |= self.transform.flags();
190
191        // Set the reserved bits to zero
192        flags.remove(VarcFlags::RESERVED_MASK);
193
194        Uint32Var(flags.bits())
195    }
196}
197
198impl FontWrite for RawVarComponent {
199    // Hand-roll this for now
200    fn write_into(&self, writer: &mut TableWriter) {
201        self.determine_flags().write_into(writer);
202        if self.gid.to_u32() > 0xFFFF {
203            Uint24::new(self.gid.to_u32()).write_into(writer);
204        } else {
205            (self.gid.to_u32() as u16).write_into(writer);
206        }
207        if let Some(condition_index) = self.condition_index {
208            Uint32Var(condition_index).write_into(writer);
209        }
210        if let Some(axis_indices_index) = self.axis_indices_index {
211            Uint32Var(axis_indices_index).write_into(writer);
212        }
213        if let Some(axis_values) = &self.axis_values {
214            let converted_axis_values = axis_values
215                .iter()
216                .map(|v| F2Dot14::from_f32(*v).to_bits() as i32)
217                .collect();
218            let packed = PackedDeltas::new(converted_axis_values);
219            packed.write_into(writer);
220        }
221        if let Some(axis_values_var_index) = self.axis_values_var_index {
222            Uint32Var(axis_values_var_index).write_into(writer);
223        }
224
225        if let Some(transform_var_index) = self.transform_var_index {
226            Uint32Var(transform_var_index).write_into(writer);
227        }
228
229        self.transform.write_into(writer);
230
231        // Technically we are supposed to process and discard one uint32var
232        // per each set bit in RESERVED_MASK. But we explicitly set bits in
233        // RESERVED_MASK to zero, as per the spec. So we just do nothing here.
234    }
235}
236
237/// A variable Uint32
238///
239/// See <https://github.com/harfbuzz/boring-expansion-spec/blob/main/VARC.md#uint32var>.
240#[derive(Debug, Clone, Copy, PartialEq, Eq)]
241struct Uint32Var(u32);
242
243impl FontWrite for Uint32Var {
244    fn write_into(&self, writer: &mut TableWriter) {
245        let value = self.0;
246        if value < 0x80 {
247            (value as u8).write_into(writer);
248        } else if value < 0x4000 {
249            let byte1 = ((value >> 8) as u8) | 0x80;
250            let byte2 = (value & 0xFF) as u8;
251            (byte1).write_into(writer);
252            (byte2).write_into(writer);
253        } else if value < 0x200000 {
254            let byte1 = ((value >> 16) as u8) | 0xC0;
255            let byte2 = ((value >> 8) & 0xFF) as u8;
256            let byte3 = (value & 0xFF) as u8;
257            (byte1).write_into(writer);
258            (byte2).write_into(writer);
259            (byte3).write_into(writer);
260        } else if value < 0x10000000 {
261            let byte1 = ((value >> 24) as u8) | 0xE0;
262            let byte2 = ((value >> 16) & 0xFF) as u8;
263            let byte3 = ((value >> 8) & 0xFF) as u8;
264            let byte4 = (value & 0xFF) as u8;
265            (byte1).write_into(writer);
266            (byte2).write_into(writer);
267            (byte3).write_into(writer);
268            (byte4).write_into(writer);
269        } else {
270            (0xF0u8).write_into(writer);
271            (((value >> 24) & 0xFF) as u8).write_into(writer);
272            (((value >> 16) & 0xFF) as u8).write_into(writer);
273            (((value >> 8) & 0xFF) as u8).write_into(writer);
274            ((value & 0xFF) as u8).write_into(writer);
275        }
276    }
277}
278
279/// <https://github.com/fonttools/fonttools/blob/5e6b12d12fa08abafbeb7570f47707fbedf69a45/Lib/fontTools/misc/transform.py#L410>
280#[derive(Debug, Clone, Default, PartialEq)]
281pub struct DecomposedTransform {
282    pub translate_x: Option<f64>,
283    pub translate_y: Option<f64>,
284    pub rotation: Option<f64>, // degrees, counter-clockwise
285    pub scale_x: Option<f64>,
286    pub scale_y: Option<f64>,
287    pub skew_x: Option<f64>,
288    pub skew_y: Option<f64>,
289    pub center_x: Option<f64>,
290    pub center_y: Option<f64>,
291}
292
293impl DecomposedTransform {
294    fn flags(&self) -> VarcFlags {
295        let mut flags = VarcFlags::empty();
296        if self.translate_x.is_some() {
297            flags.insert(VarcFlags::HAVE_TRANSLATE_X);
298        }
299        if self.translate_y.is_some() {
300            flags.insert(VarcFlags::HAVE_TRANSLATE_Y);
301        }
302        if self.rotation.is_some() {
303            flags.insert(VarcFlags::HAVE_ROTATION);
304        }
305        if self.scale_x.is_some() {
306            flags.insert(VarcFlags::HAVE_SCALE_X);
307        }
308        if self.scale_y.is_some() {
309            flags.insert(VarcFlags::HAVE_SCALE_Y);
310        }
311        if self.skew_x.is_some() {
312            flags.insert(VarcFlags::HAVE_SKEW_X);
313        }
314        if self.skew_y.is_some() {
315            flags.insert(VarcFlags::HAVE_SKEW_Y);
316        }
317        if self.center_x.is_some() {
318            flags.insert(VarcFlags::HAVE_TCENTER_X);
319        }
320        if self.center_y.is_some() {
321            flags.insert(VarcFlags::HAVE_TCENTER_Y);
322        }
323        flags
324    }
325}
326
327impl FontWrite for DecomposedTransform {
328    fn write_into(&self, writer: &mut TableWriter) {
329        if let Some(translate_x) = self.translate_x {
330            FWord::from(translate_x as i16).write_into(writer);
331        }
332        if let Some(translate_y) = self.translate_y {
333            FWord::from(translate_y as i16).write_into(writer);
334        }
335        if let Some(rotation) = self.rotation {
336            F4Dot12::from_f32(rotation as f32).write_into(writer);
337        }
338        if let Some(scale_x) = self.scale_x {
339            F6Dot10::from_f32(scale_x as f32).write_into(writer);
340        }
341        if let Some(scale_y) = self.scale_y {
342            F6Dot10::from_f32(scale_y as f32).write_into(writer);
343        }
344        if let Some(skew_x) = self.skew_x {
345            F4Dot12::from_f32(skew_x as f32).write_into(writer);
346        }
347        if let Some(skew_y) = self.skew_y {
348            F4Dot12::from_f32(skew_y as f32).write_into(writer);
349        }
350        if let Some(center_x) = self.center_x {
351            FWord::from(center_x as i16).write_into(writer);
352        }
353        if let Some(center_y) = self.center_y {
354            FWord::from(center_y as i16).write_into(writer);
355        }
356    }
357}
358#[cfg(test)]
359mod tests {
360    use crate::{
361        dump_table,
362        tables::{layout::CoverageFormat1, variations::mivs_builder::SparseRegion},
363        write::TableWriter,
364    };
365
366    use super::*;
367
368    #[test]
369    fn test_write_uint32var() {
370        let mut writer = TableWriter::default();
371        Uint32Var(0x7F).write_into(&mut writer);
372        assert_eq!(writer.into_data().bytes, vec![0x7F]);
373
374        let mut writer = TableWriter::default();
375        Uint32Var(0x3FFF).write_into(&mut writer);
376        assert_eq!(writer.into_data().bytes, vec![0xBF, 0xFF]);
377
378        let mut writer = TableWriter::default();
379        Uint32Var(0x1FFFFF).write_into(&mut writer);
380        assert_eq!(writer.into_data().bytes, vec![0xDF, 0xFF, 0xFF]);
381
382        let mut writer = TableWriter::default();
383        Uint32Var(0x0FFFFFFF).write_into(&mut writer);
384        assert_eq!(writer.into_data().bytes, vec![0xEF, 0xFF, 0xFF, 0xFF]);
385
386        let mut writer = TableWriter::default();
387        Uint32Var(0xB2D05E00).write_into(&mut writer);
388        assert_eq!(writer.into_data().bytes, vec![0xF0, 0xB2, 0xD0, 0x5E, 0x00]);
389    }
390
391    #[test]
392
393    fn test_basic_varc() {
394        let storebuilder = MultiItemVariationStoreBuilder::new();
395        let component = VarComponent {
396            reset_unspecified_axes: false,
397            gid: GlyphId::new(42),
398            condition_index: None,
399            axis_values: None,
400            axis_values_var_index: None,
401            transform: DecomposedTransform {
402                translate_x: None,
403                translate_y: None,
404                ..Default::default()
405            },
406            transform_var_index: None,
407        };
408        let composite = VarCompositeGlyph(vec![component]);
409        let varc = Varc::new_from_composite_glyphs(
410            CoverageTable::Format1(CoverageFormat1::new(vec![1.into()])),
411            storebuilder,
412            vec![],
413            vec![composite],
414        );
415        varc.validate().expect("Varc validation failed");
416        let bytes = dump_table(&varc).expect("Failed to dump varc table");
417
418        let varc_roundtrip = read_fonts::tables::varc::Varc::read(FontData::new(&bytes))
419            .expect("Failed to read varc table");
420        let glyphs: Vec<GlyphId16> = varc_roundtrip.coverage().unwrap().iter().collect();
421        assert_eq!(glyphs, vec![GlyphId16::new(1)]);
422        assert!(varc_roundtrip.multi_var_store().is_none());
423        assert!(varc_roundtrip.condition_list().is_none());
424        let composite = varc_roundtrip.glyph(0).unwrap();
425        assert_eq!(composite.components().count(), 1);
426        let mut components = composite.components();
427        let component = components.next().unwrap().unwrap();
428        assert_eq!(component.gid(), GlyphId16::new(42));
429        assert_eq!(component.condition_index(), None);
430        assert_eq!(component.axis_indices_index(), None);
431        assert!(component.axis_values().is_none());
432        assert_eq!(component.axis_values_var_index(), None);
433        assert_eq!(component.transform_var_index(), None);
434    }
435
436    // Let's do one with some axis values
437    #[test]
438    fn test_varc_with_axis_values_and_transform() {
439        let storebuilder = MultiItemVariationStoreBuilder::new();
440        let component = VarComponent {
441            reset_unspecified_axes: true,
442            gid: GlyphId::new(100),
443            condition_index: None,
444            axis_values: Some(
445                // Magic numbers chosen to be exactly representable in F2Dot14
446                vec![(0u16, 0.2199707f32), (1u16, 0.2999878f32)]
447                    .into_iter()
448                    .collect::<BTreeMap<u16, f32>>(),
449            ),
450            axis_values_var_index: None,
451            transform: DecomposedTransform {
452                translate_x: Some(10.0),
453                translate_y: Some(-10.0),
454                ..Default::default()
455            },
456            transform_var_index: None,
457        };
458        let composite = VarCompositeGlyph(vec![component]);
459        let varc = Varc::new_from_composite_glyphs(
460            CoverageTable::Format1(CoverageFormat1::new(vec![2.into()])),
461            storebuilder,
462            vec![],
463            vec![composite],
464        );
465        varc.validate().expect("Varc validation failed");
466        let bytes = dump_table(&varc).expect("Failed to dump varc table");
467        let varc_roundtrip = read_fonts::tables::varc::Varc::read(FontData::new(&bytes))
468            .expect("Failed to read varc table");
469        let glyphs: Vec<GlyphId16> = varc_roundtrip.coverage().unwrap().iter().collect();
470        assert_eq!(glyphs, vec![GlyphId16::new(2)]);
471        assert!(varc_roundtrip.multi_var_store().is_none());
472        assert!(varc_roundtrip.condition_list().is_none());
473        let axis_indices_list = varc_roundtrip.axis_indices_list().unwrap().unwrap();
474        assert_eq!(axis_indices_list.count(), 1);
475        assert_eq!(
476            varc_roundtrip
477                .axis_indices(0)
478                .unwrap()
479                .iter()
480                .collect::<Vec<_>>(),
481            vec![0, 1],
482        );
483        let composite = varc_roundtrip.glyph(0).unwrap();
484        assert_eq!(composite.components().count(), 1);
485        let mut components = composite.components();
486        let component = components.next().unwrap().unwrap();
487        assert!(component
488            .flags()
489            .contains(VarcFlags::RESET_UNSPECIFIED_AXES));
490        assert!(component.flags().contains(VarcFlags::HAVE_AXES));
491        assert_eq!(component.gid(), GlyphId16::new(100));
492        assert_eq!(component.condition_index(), None);
493        assert_eq!(component.axis_indices_index(), Some(0));
494        let axis_values = component.axis_values().unwrap();
495        let axis_values_vec: Vec<f32> = axis_values
496            .iter()
497            .map(|b| F2Dot14::from_bits(b as i16).to_f32())
498            .collect();
499        assert_eq!(axis_values_vec, vec![0.2199707, 0.2999878]);
500        assert_eq!(component.axis_values_var_index(), None);
501        assert_eq!(component.transform_var_index(), None);
502        let matrix = component.transform().matrix();
503        assert_eq!(matrix.dx, 10.0); // translate x
504        assert_eq!(matrix.dy, -10.0); // translate y
505    }
506
507    // And let's do one with a var store
508    #[test]
509    fn test_varc_with_var_store() {
510        let mut storebuilder = MultiItemVariationStoreBuilder::new();
511        let region1 = SparseRegion::new(vec![(
512            0,
513            F2Dot14::from_f32(0.0),
514            F2Dot14::from_f32(1.0),
515            F2Dot14::from_f32(1.0),
516        )]);
517        let region2 = SparseRegion::new(vec![(
518            1,
519            F2Dot14::from_f32(-1.0),
520            F2Dot14::from_f32(-1.0),
521            F2Dot14::from_f32(0.0),
522        )]);
523
524        let delta_set_id = storebuilder
525            .add_deltas(vec![
526                // weight. Increase translate_x by 500 at region1 peak
527                (region1, vec![500, 0]),
528                // width. Increate translate_y by 500 at region2 peak
529                (region2, vec![0, 500]),
530            ])
531            .unwrap();
532        let component = VarComponent {
533            reset_unspecified_axes: true,
534            gid: GlyphId::new(150),
535            condition_index: None,
536            axis_values: None,
537            axis_values_var_index: None,
538            transform: DecomposedTransform {
539                translate_x: Some(0.0),
540                translate_y: Some(0.0),
541                ..Default::default()
542            },
543            transform_var_index: Some(VarcVariationIndex::PendingVariationIndex(delta_set_id)),
544        };
545        let composite = VarCompositeGlyph(vec![component]);
546        let varc = Varc::new_from_composite_glyphs(
547            CoverageTable::Format1(CoverageFormat1::new(vec![3.into()])),
548            storebuilder,
549            vec![],
550            vec![composite],
551        );
552        varc.validate().expect("Varc validation failed");
553        let bytes = dump_table(&varc).expect("Failed to dump varc table");
554
555        let varc_roundtrip = read_fonts::tables::varc::Varc::read(FontData::new(&bytes))
556            .expect("Failed to read varc table");
557        let glyphs: Vec<GlyphId16> = varc_roundtrip.coverage().unwrap().iter().collect();
558        assert_eq!(glyphs, vec![GlyphId16::new(3)]);
559
560        // Verify the multi var store exists and has expected structure
561        let multi_var_store = varc_roundtrip.multi_var_store().unwrap().unwrap();
562        assert_eq!(multi_var_store.format(), 1);
563        let region_list = multi_var_store.region_list().unwrap();
564        assert_eq!(region_list.region_count(), 2);
565
566        let region_0 = region_list.regions().get(0).unwrap();
567        assert_eq!(region_0.region_axis_count(), 1);
568        let region_1 = region_list.regions().get(1).unwrap();
569        assert_eq!(region_1.region_axis_count(), 1);
570
571        // Verify the component
572        let composite = varc_roundtrip.glyph(0).unwrap();
573        assert_eq!(composite.components().count(), 1);
574        let mut components = composite.components();
575        let component = components.next().unwrap().unwrap();
576        assert!(component
577            .flags()
578            .contains(VarcFlags::RESET_UNSPECIFIED_AXES));
579        assert_eq!(component.gid(), GlyphId16::new(150));
580        assert_eq!(component.condition_index(), None);
581        assert_eq!(component.axis_indices_index(), None);
582        assert!(component.axis_values().is_none());
583        assert_eq!(component.axis_values_var_index(), None);
584        assert_eq!(component.transform_var_index(), Some(0));
585
586        // Verify the transform base values
587        let matrix = component.transform().matrix();
588        assert_eq!(matrix.dx, 0.0); // translate x
589        assert_eq!(matrix.dy, 0.0); // translate y
590    }
591}