1include!("../../generated/generated_fvar.rs");
4
5#[path = "./instance_record.rs"]
6mod instance_record;
7
8use super::{avar::Avar, variations::DeltaSetIndex};
9use alloc::vec::Vec;
10
11pub use instance_record::InstanceRecord;
12
13const MAX_INLINE_AVAR2_AXES: usize = 64;
14
15#[inline]
16fn round_f64_to_i32(value: f64) -> i32 {
17 if value >= 0.0 {
18 (value + 0.5) as i32
19 } else {
20 (value - 0.5) as i32
21 }
22}
23
24#[inline]
25fn apply_avar2_delta(coord: Fixed, delta_2dot14: f64) -> Fixed {
26 Fixed::from_bits(
29 coord
30 .to_bits()
31 .wrapping_add(round_f64_to_i32(delta_2dot14 * 4.0)),
32 )
33}
34
35fn normalize_user_coords<T>(
36 axes: &[VariationAxisRecord],
37 user_coords: impl IntoIterator<Item = (Tag, Fixed)>,
38 coords: &mut [T],
39 convert: impl Fn(Fixed) -> T,
40) {
41 for user_coord in user_coords {
42 for (axis, coord) in axes
48 .iter()
49 .zip(coords.iter_mut())
50 .filter(|(axis, _)| axis.axis_tag() == user_coord.0)
51 {
52 *coord = convert(axis.normalize(user_coord.1));
53 }
54 }
55}
56
57fn apply_avar_mappings<T>(
58 avar: Option<&Avar>,
59 coords: &mut [T],
60 to_fixed: impl Fn(&T) -> Fixed,
61 from_fixed: impl Fn(Fixed) -> T,
62) {
63 let avar_mappings = avar.map(|avar| avar.axis_segment_maps());
64 for (i, coord) in coords.iter_mut().enumerate() {
65 if let Some(mapping) = avar_mappings
66 .as_ref()
67 .and_then(|mappings| mappings.get(i).transpose().ok())
68 .flatten()
69 {
70 *coord = from_fixed(mapping.apply(to_fixed(coord)));
71 }
72 }
73}
74
75fn to_normalized_coords(fixed_coords: &[Fixed], normalized_coords: &mut [F2Dot14]) {
76 for (target_coord, coord) in normalized_coords.iter_mut().zip(fixed_coords.iter()) {
77 *target_coord = coord.to_f2dot14();
78 }
79}
80
81impl<'a> Fvar<'a> {
82 pub fn axes(&self) -> Result<&'a [VariationAxisRecord], ReadError> {
84 Ok(self.axis_instance_arrays()?.axes())
85 }
86
87 pub fn instances(&self) -> Result<ComputedArray<'a, InstanceRecord<'a>>, ReadError> {
89 Ok(self.axis_instance_arrays()?.instances())
90 }
91
92 pub fn user_to_normalized(
113 &self,
114 avar: Option<&Avar>,
115 user_coords: impl IntoIterator<Item = (Tag, Fixed)>,
116 normalized_coords: &mut [F2Dot14],
117 ) {
118 normalized_coords.fill(F2Dot14::ZERO);
119 let axes = self.axes().unwrap_or_default();
120 let actual_len = axes.len().min(normalized_coords.len());
121 let normalized_coords = &mut normalized_coords[..actual_len];
122
123 let mut stack_fixed_coords = [Fixed::ZERO; MAX_INLINE_AVAR2_AXES];
124 let mut heap_fixed_coords = Vec::new();
125 let fixed_coords = if actual_len > MAX_INLINE_AVAR2_AXES {
126 heap_fixed_coords.resize(actual_len, Fixed::ZERO);
127 heap_fixed_coords.as_mut_slice()
128 } else {
129 &mut stack_fixed_coords[..actual_len]
130 };
131 normalize_user_coords(axes, user_coords, fixed_coords, core::convert::identity);
132 apply_avar_mappings(avar, fixed_coords, |coord| *coord, core::convert::identity);
133
134 let Some(avar) = avar else {
135 to_normalized_coords(fixed_coords, normalized_coords);
136 return;
137 };
138 if avar.version() == MajorMinor::VERSION_1_0 {
139 to_normalized_coords(fixed_coords, normalized_coords);
140 return;
141 }
142
143 let var_store = avar.var_store();
144 let var_index_map = avar.axis_index_map();
145
146 let mut coords_2dot14 = [F2Dot14::ZERO; MAX_INLINE_AVAR2_AXES];
147 let coords_2dot14 = &mut coords_2dot14[..actual_len];
148 for (coord_2dot14, coord) in coords_2dot14.iter_mut().zip(fixed_coords.iter()) {
149 *coord_2dot14 = coord.to_f2dot14();
150 }
151
152 if let Some(Ok(varstore)) = var_store.as_ref() {
153 for (i, coord) in fixed_coords.iter_mut().enumerate() {
154 let var_index = if let Some(Ok(ref map)) = var_index_map {
155 match map.get(i as u32) {
156 Ok(index) => index,
157 Err(_) => continue,
158 }
159 } else {
160 DeltaSetIndex {
161 outer: 0,
162 inner: i as u16,
163 }
164 };
165 if let Ok(delta) = varstore.compute_float_delta(var_index, coords_2dot14) {
166 *coord =
167 apply_avar2_delta(*coord, delta.to_f64()).clamp(Fixed::NEG_ONE, Fixed::ONE);
168 }
169 }
170 }
171
172 to_normalized_coords(fixed_coords, normalized_coords);
173 }
174}
175
176impl VariationAxisRecord {
177 pub fn normalize(&self, mut value: Fixed) -> Fixed {
179 use core::cmp::Ordering::*;
180 let min_value = self.min_value();
181 let default_value = self.default_value();
182 let max_value = self.max_value().max(min_value);
184 value = value.clamp(min_value, max_value);
185 value = match value.cmp(&default_value) {
186 Less => {
187 -((default_value.saturating_sub(value)) / (default_value.saturating_sub(min_value)))
188 }
189 Greater => {
190 (value.saturating_sub(default_value)) / (max_value.saturating_sub(default_value))
191 }
192 Equal => Fixed::ZERO,
193 };
194 value.clamp(Fixed::NEG_ONE, Fixed::ONE)
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use crate::{FontRef, TableProvider};
201 use types::{F2Dot14, Fixed, NameId, Tag};
202
203 #[test]
204 fn axes() {
205 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
206 let fvar = font.fvar().unwrap();
207 assert_eq!(fvar.axis_count(), 1);
208 let wght = &fvar.axes().unwrap().first().unwrap();
209 assert_eq!(wght.axis_tag(), Tag::new(b"wght"));
210 assert_eq!(wght.min_value(), Fixed::from_f64(100.0));
211 assert_eq!(wght.default_value(), Fixed::from_f64(400.0));
212 assert_eq!(wght.max_value(), Fixed::from_f64(900.0));
213 assert_eq!(wght.flags(), 0);
214 assert_eq!(wght.axis_name_id(), NameId::new(257));
215 }
216
217 #[test]
218 fn instances() {
219 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
220 let fvar = font.fvar().unwrap();
221 assert_eq!(fvar.instance_count(), 9);
222 let instances = fvar.instances().unwrap();
225 for i in 0..9 {
226 let value = 100.0 * (i + 1) as f64;
227 let name_id = NameId::new(258 + i as u16);
228 let instance = instances.get(i).unwrap();
229 assert_eq!(instance.coordinates.len(), 1);
230 assert_eq!(
231 instance.coordinates.first().unwrap().get(),
232 Fixed::from_f64(value)
233 );
234 assert_eq!(instance.subfamily_name_id, name_id);
235 assert_eq!(instance.post_script_name_id, None);
236 }
237 }
238
239 #[test]
240 fn normalize() {
241 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
242 let fvar = font.fvar().unwrap();
243 let axis = fvar.axes().unwrap().first().unwrap();
244 let values = [100.0, 220.0, 250.0, 400.0, 650.0, 900.0];
245 let expected = [-1.0, -0.60001, -0.5, 0.0, 0.5, 1.0];
246 for (value, expected) in values.into_iter().zip(expected) {
247 assert_eq!(
248 axis.normalize(Fixed::from_f64(value)),
249 Fixed::from_f64(expected)
250 );
251 }
252 }
253
254 #[test]
255 fn normalize_overflow() {
256 let test_case = &[
261 79, 84, 84, 79, 0, 1, 32, 32, 255, 32, 32, 32, 102, 118, 97, 114, 32, 32, 32, 32, 0, 0,
262 0, 28, 0, 0, 0, 41, 32, 0, 0, 0, 0, 1, 32, 32, 0, 2, 32, 32, 32, 32, 0, 0, 32, 32, 32,
263 32, 32, 0, 0, 0, 0, 153, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
264 ];
265 let font = FontRef::new(test_case).unwrap();
266 let fvar = font.fvar().unwrap();
267 let axis = fvar.axes().unwrap()[1];
268 assert_eq!(
270 axis.normalize(Fixed::from_f64(0.0)),
271 Fixed::from_f64(-0.2509765625)
272 );
273 }
274
275 #[test]
276 fn user_to_normalized() {
277 let font = FontRef::from_index(font_test_data::VAZIRMATN_VAR, 0).unwrap();
278 let fvar = font.fvar().unwrap();
279 let avar = font.avar().ok();
280 let wght = Tag::new(b"wght");
281 let axis = fvar.axes().unwrap()[0];
282 let mut normalized_coords = [F2Dot14::default(); 1];
283 let avar_user = axis.default_value().to_f32()
285 + (axis.max_value().to_f32() - axis.default_value().to_f32()) * 0.8;
286 let avar_normalized = 0.83875;
287 #[rustfmt::skip]
288 let cases = [
289 (-1000.0, -1.0f32),
291 (100.0, -1.0),
292 (200.0, -0.5),
293 (400.0, 0.0),
294 (900.0, 1.0),
295 (avar_user, avar_normalized),
296 (1251.5, 1.0),
297 ];
298 for (user, normalized) in cases {
299 fvar.user_to_normalized(
300 avar.as_ref(),
301 [(wght, Fixed::from_f64(user as f64))],
302 &mut normalized_coords,
303 );
304 assert_eq!(normalized_coords[0], F2Dot14::from_f32(normalized));
305 }
306 }
307
308 #[test]
309 fn avar2() {
310 let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
311 let avar = font.avar().ok();
312 let fvar = font.fvar().unwrap();
313 let avar_axis = Tag::new(b"AVAR");
314 let avwk_axis = Tag::new(b"AVWK");
315 let mut normalized_coords = [F2Dot14::default(); 2];
316 let cases = [
317 ((100.0, 0.0), (1.0, 1.0)),
318 ((50.0, 0.0), (0.5, 0.5)),
319 ((0.0, 50.0), (0.0, 0.5)),
320 ];
321 for (user, expected) in cases {
322 fvar.user_to_normalized(
323 avar.as_ref(),
324 [
325 (avar_axis, Fixed::from_f64(user.0)),
326 (avwk_axis, Fixed::from_f64(user.1)),
327 ],
328 &mut normalized_coords,
329 );
330 assert_eq!(normalized_coords[0], F2Dot14::from_f32(expected.0));
331 assert_eq!(normalized_coords[1], F2Dot14::from_f32(expected.1));
332 }
333 }
334
335 #[test]
336 fn avar2_no_panic_with_wrong_size_coords_array() {
337 let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
339 let avar = font.avar().ok();
340 let fvar = font.fvar().unwrap();
341 let mut normalized_coords = [F2Dot14::default(); 1];
343 fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
344 let mut normalized_coords = [F2Dot14::default(); 4];
346 fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
347 }
348
349 #[test]
350 fn avar2_preserves_16_16_precision_until_final_rounding() {
351 let coord = Fixed::from_bits(3);
354 assert_eq!(
355 super::apply_avar2_delta(coord, 0.5).to_f2dot14(),
356 F2Dot14::from_bits(1)
357 );
358 }
359
360 #[test]
361 fn avar2_clamps_hidden_axis_for_amstelvar_repro() {
362 let font = FontRef::new(font_test_data::AMSTELVAR_AVAR2_A).unwrap();
363 let avar = font.avar().ok();
364 let fvar = font.fvar().unwrap();
365 let mut normalized_coords = [F2Dot14::ZERO; 12];
366
367 fvar.user_to_normalized(
368 avar.as_ref(),
369 [(Tag::new(b"wght"), Fixed::from_f64(1000.0))],
370 &mut normalized_coords,
371 );
372
373 assert_eq!(normalized_coords[1], F2Dot14::from_bits(-5370)); assert_eq!(normalized_coords[2], F2Dot14::ONE); assert_eq!(normalized_coords[3], F2Dot14::from_bits(2254)); assert_eq!(normalized_coords[11], F2Dot14::ONE); }
378}