1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//! Helpers for production of the
//! [HVAR](https://learn.microsoft.com/en-us/typography/opentype/spec/HVAR),
//! [VVAR](https://learn.microsoft.com/en-us/typography/opentype/spec/VVAR) tables
use std::any::type_name;
use std::collections::{BTreeSet, HashMap, HashSet};
use fontdrasil::{
coords::NormalizedLocation,
types::{Axes, GlyphName},
variations::VariationModel,
};
use fontir::ir::{GlobalMetrics, GlobalMetricsInstance, Glyph, StaticMetadata};
use write_fonts::{
FontWrite, OtRound, dump_table, tables::variations::VariationRegion, validate::Validate,
};
use crate::error::Error;
/// Compute the final size of a table, after it has been serialized to bytes
pub fn table_size<T>(table: &T) -> Result<usize, Error>
where
T: FontWrite + Validate,
{
let data = dump_table(table).map_err(|e| Error::DumpTableError {
e,
context: type_name::<T>().to_string(),
})?;
Ok(data.len())
}
/// Which way a delta goes.
///
/// Impacts how the size of the glyph is accessed
pub(crate) enum DeltaDirection {
Horizontal,
Vertical,
}
/// Helper to collect advance width or height deltas for all glyphs in a font
pub(crate) struct AdvanceDeltas {
/// Variation axes
axes: Axes,
/// Sparse variation models, keyed by the set of locations they define
models: HashMap<BTreeSet<NormalizedLocation>, VariationModel>,
/// Glyph's advance width deltas sorted by glyph order
deltas: Vec<Vec<(VariationRegion, i16)>>,
/// All the glyph locations that are defined in the font
glyph_locations: HashSet<NormalizedLocation>,
/// Cached global metrics at each location (only populated for Vertical direction)
metrics_cache: HashMap<NormalizedLocation, GlobalMetricsInstance>,
direction: DeltaDirection,
}
impl AdvanceDeltas {
pub(crate) fn new<'a>(
static_metadata: &StaticMetadata,
glyph_locations: impl IntoIterator<Item = &'a NormalizedLocation>,
global_metrics: &GlobalMetrics,
direction: DeltaDirection,
) -> Self {
let axes = static_metadata.axes.clone();
let global_locations = static_metadata
.variation_model
.locations()
.cloned()
.collect::<BTreeSet<_>>();
let mut models = HashMap::new();
models.insert(global_locations, static_metadata.variation_model.clone());
// Collect unique glyph locations, pruning axes that are not in the global model
// (e.g. 'point' axes) which might be confused for a distinct sub-model
// https://github.com/googlefonts/fontc/issues/1256
let glyph_locations: HashSet<NormalizedLocation> = glyph_locations
.into_iter()
.map(|loc| loc.subset_axes(&static_metadata.axes))
.collect();
// Pre-compute metrics for all locations if we're computing vertical metrics
// This avoids repeated interpolation when processing each glyph
let metrics_cache = match direction {
DeltaDirection::Vertical => glyph_locations
.iter()
.map(|loc| (loc.clone(), global_metrics.at(loc)))
.collect(),
DeltaDirection::Horizontal => HashMap::new(),
};
AdvanceDeltas {
axes,
models,
deltas: Vec::new(),
glyph_locations,
metrics_cache,
direction,
}
}
pub(crate) fn add(&mut self, glyph: &Glyph) -> Result<(), Error> {
let mut advances: HashMap<_, Vec<f64>> = Default::default();
for (loc, glyph_instance) in glyph.sources().iter() {
let loc = loc.subset_axes(&self.axes);
// Only compute metrics when needed (for vertical direction)
// For horizontal, we just need glyph_instance.width which doesn't require metrics
let advance = match self.direction {
DeltaDirection::Horizontal => glyph_instance.width.ot_round(),
DeltaDirection::Vertical => {
let metrics = self
.metrics_cache
.get(&loc)
.expect("metrics should be pre-computed for all glyph locations");
glyph_instance.height(metrics) as f64
}
};
advances.insert(loc, vec![advance]);
}
let name = glyph.name.clone();
let i = self.deltas.len();
if advances.len() == 1 {
assert!(advances.keys().next().unwrap().is_default());
// this glyph has no variations (it's only defined at the default location),
// therefore the deltas returned from VariationModel will be an empty Vec.
// However, when this is the first .notdef glyph we would like to treat it
// specially in order to match the output of fontTools.varLib.
// In fonttools, all master TTFs have a .notdef glyph as their first glyph; in fontc,
// unless the input source defines a .notdef, only a default instance is generated.
// And that's ok for gvar, however for HVAR the order in which regions and associated
// deltas are added to VariationStoreBuilder, one glyph at a time, can produce
// different orderings of the ItemVariationStore.VariationRegionList (newly seen
// regions get appended, and existing regions reused).
// So, to match the VarRegionList produced by fontTools, we need to make the deltaset
// for the first .notdef glyph similarly "dense", by copying its default instance to
// all other glyph locations...
if i == 0 && name == GlyphName::NOTDEF {
let notdef_dim = advances.values().next().unwrap()[0];
for loc in self.glyph_locations.iter() {
advances
.entry(loc.clone())
.or_insert_with(|| vec![notdef_dim]);
}
} else {
// spare the model the work of computing no-op deltas
self.deltas.push(Vec::new());
return Ok(());
}
}
let locations = advances.keys().cloned().collect::<BTreeSet<_>>();
let model = self.models.entry(locations).or_insert_with(|| {
// this glyph defines its own set of locations, a new sparse model is needed
VariationModel::new(advances.keys().cloned().collect(), self.axes.axis_order())
});
self.deltas.push(
model
.deltas(&advances)
.map_err(|e| Error::GlyphDeltaError(name.clone(), e))?
.into_iter()
.filter_map(|(region, values)| {
if region.is_default() {
return None;
}
// Only 1 value per region for our input
assert!(values.len() == 1, "{} values?!", values.len());
Some((
region.to_write_fonts_variation_region(&self.axes),
values[0].ot_round(),
))
})
.collect(),
);
Ok(())
}
pub(crate) fn is_single_model(&self) -> bool {
self.models.len() == 1
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &Vec<(VariationRegion, i16)>> {
self.deltas.iter()
}
}