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