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 stack_coords_2dot14 = [F2Dot14::ZERO; MAX_INLINE_AVAR2_AXES];
147 let mut heap_coords_2dot14 = Vec::new();
148 let coords_2dot14 = if actual_len > MAX_INLINE_AVAR2_AXES {
149 heap_coords_2dot14.resize(actual_len, F2Dot14::ZERO);
150 heap_coords_2dot14.as_mut_slice()
151 } else {
152 &mut stack_coords_2dot14[..actual_len]
153 };
154 for (coord_2dot14, coord) in coords_2dot14.iter_mut().zip(fixed_coords.iter()) {
155 *coord_2dot14 = coord.to_f2dot14();
156 }
157
158 if let Some(Ok(varstore)) = var_store.as_ref() {
159 for (i, coord) in fixed_coords.iter_mut().enumerate() {
160 let var_index = if let Some(Ok(ref map)) = var_index_map {
161 match map.get(i as u32) {
162 Ok(index) => index,
163 Err(_) => continue,
164 }
165 } else {
166 DeltaSetIndex {
167 outer: 0,
168 inner: i as u16,
169 }
170 };
171 if let Ok(delta) = varstore.compute_float_delta(var_index, coords_2dot14) {
172 *coord =
173 apply_avar2_delta(*coord, delta.to_f64()).clamp(Fixed::NEG_ONE, Fixed::ONE);
174 }
175 }
176 }
177
178 to_normalized_coords(fixed_coords, normalized_coords);
179 }
180}
181
182impl VariationAxisRecord {
183 pub fn normalize(&self, mut value: Fixed) -> Fixed {
185 use core::cmp::Ordering::*;
186 let min_value = self.min_value();
187 let default_value = self.default_value();
188 let max_value = self.max_value().max(min_value);
190 value = value.clamp(min_value, max_value);
191 value = match value.cmp(&default_value) {
192 Less => {
193 -((default_value.saturating_sub(value)) / (default_value.saturating_sub(min_value)))
194 }
195 Greater => {
196 (value.saturating_sub(default_value)) / (max_value.saturating_sub(default_value))
197 }
198 Equal => Fixed::ZERO,
199 };
200 value.clamp(Fixed::NEG_ONE, Fixed::ONE)
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use crate::{FontRef, TableProvider};
207 use types::{F2Dot14, Fixed, NameId, Tag};
208
209 #[test]
210 fn axes() {
211 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
212 let fvar = font.fvar().unwrap();
213 assert_eq!(fvar.axis_count(), 1);
214 let wght = &fvar.axes().unwrap().first().unwrap();
215 assert_eq!(wght.axis_tag(), Tag::new(b"wght"));
216 assert_eq!(wght.min_value(), Fixed::from_f64(100.0));
217 assert_eq!(wght.default_value(), Fixed::from_f64(400.0));
218 assert_eq!(wght.max_value(), Fixed::from_f64(900.0));
219 assert_eq!(wght.flags(), 0);
220 assert_eq!(wght.axis_name_id(), NameId::new(257));
221 }
222
223 #[test]
224 fn instances() {
225 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
226 let fvar = font.fvar().unwrap();
227 assert_eq!(fvar.instance_count(), 9);
228 let instances = fvar.instances().unwrap();
231 for i in 0..9 {
232 let value = 100.0 * (i + 1) as f64;
233 let name_id = NameId::new(258 + i as u16);
234 let instance = instances.get(i).unwrap();
235 assert_eq!(instance.coordinates.len(), 1);
236 assert_eq!(
237 instance.coordinates.first().unwrap().get(),
238 Fixed::from_f64(value)
239 );
240 assert_eq!(instance.subfamily_name_id, name_id);
241 assert_eq!(instance.post_script_name_id, None);
242 }
243 }
244
245 #[test]
246 fn normalize() {
247 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
248 let fvar = font.fvar().unwrap();
249 let axis = fvar.axes().unwrap().first().unwrap();
250 let values = [100.0, 220.0, 250.0, 400.0, 650.0, 900.0];
251 let expected = [-1.0, -0.60001, -0.5, 0.0, 0.5, 1.0];
252 for (value, expected) in values.into_iter().zip(expected) {
253 assert_eq!(
254 axis.normalize(Fixed::from_f64(value)),
255 Fixed::from_f64(expected)
256 );
257 }
258 }
259
260 #[test]
261 fn normalize_overflow() {
262 let test_case = &[
267 79, 84, 84, 79, 0, 1, 32, 32, 255, 32, 32, 32, 102, 118, 97, 114, 32, 32, 32, 32, 0, 0,
268 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,
269 32, 32, 0, 0, 0, 0, 153, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
270 ];
271 let font = FontRef::new(test_case).unwrap();
272 let fvar = font.fvar().unwrap();
273 let axis = fvar.axes().unwrap()[1];
274 assert_eq!(
276 axis.normalize(Fixed::from_f64(0.0)),
277 Fixed::from_f64(-0.2509765625)
278 );
279 }
280
281 #[test]
282 fn user_to_normalized() {
283 let font = FontRef::from_index(font_test_data::VAZIRMATN_VAR, 0).unwrap();
284 let fvar = font.fvar().unwrap();
285 let avar = font.avar().ok();
286 let wght = Tag::new(b"wght");
287 let axis = fvar.axes().unwrap()[0];
288 let mut normalized_coords = [F2Dot14::default(); 1];
289 let avar_user = axis.default_value().to_f32()
291 + (axis.max_value().to_f32() - axis.default_value().to_f32()) * 0.8;
292 let avar_normalized = 0.83875;
293 #[rustfmt::skip]
294 let cases = [
295 (-1000.0, -1.0f32),
297 (100.0, -1.0),
298 (200.0, -0.5),
299 (400.0, 0.0),
300 (900.0, 1.0),
301 (avar_user, avar_normalized),
302 (1251.5, 1.0),
303 ];
304 for (user, normalized) in cases {
305 fvar.user_to_normalized(
306 avar.as_ref(),
307 [(wght, Fixed::from_f64(user as f64))],
308 &mut normalized_coords,
309 );
310 assert_eq!(normalized_coords[0], F2Dot14::from_f32(normalized));
311 }
312 }
313
314 #[test]
315 fn avar2() {
316 let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
317 let avar = font.avar().ok();
318 let fvar = font.fvar().unwrap();
319 let avar_axis = Tag::new(b"AVAR");
320 let avwk_axis = Tag::new(b"AVWK");
321 let mut normalized_coords = [F2Dot14::default(); 2];
322 let cases = [
323 ((100.0, 0.0), (1.0, 1.0)),
324 ((50.0, 0.0), (0.5, 0.5)),
325 ((0.0, 50.0), (0.0, 0.5)),
326 ];
327 for (user, expected) in cases {
328 fvar.user_to_normalized(
329 avar.as_ref(),
330 [
331 (avar_axis, Fixed::from_f64(user.0)),
332 (avwk_axis, Fixed::from_f64(user.1)),
333 ],
334 &mut normalized_coords,
335 );
336 assert_eq!(normalized_coords[0], F2Dot14::from_f32(expected.0));
337 assert_eq!(normalized_coords[1], F2Dot14::from_f32(expected.1));
338 }
339 }
340
341 #[test]
342 fn avar2_no_panic_with_wrong_size_coords_array() {
343 let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
345 let avar = font.avar().ok();
346 let fvar = font.fvar().unwrap();
347 let mut normalized_coords = [F2Dot14::default(); 1];
349 fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
350 let mut normalized_coords = [F2Dot14::default(); 4];
352 fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
353 }
354
355 #[test]
356 fn avar2_preserves_16_16_precision_until_final_rounding() {
357 let coord = Fixed::from_bits(3);
360 assert_eq!(
361 super::apply_avar2_delta(coord, 0.5).to_f2dot14(),
362 F2Dot14::from_bits(1)
363 );
364 }
365
366 #[test]
367 fn avar2_clamps_hidden_axis_for_amstelvar_repro() {
368 let font = FontRef::new(font_test_data::AMSTELVAR_AVAR2_A).unwrap();
369 let avar = font.avar().ok();
370 let fvar = font.fvar().unwrap();
371 let mut normalized_coords = [F2Dot14::ZERO; 12];
372
373 fvar.user_to_normalized(
374 avar.as_ref(),
375 [(Tag::new(b"wght"), Fixed::from_f64(1000.0))],
376 &mut normalized_coords,
377 );
378
379 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); }
384}