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;
14const MAX_NORMALIZE_QUADRATIC_AXES: usize = 64;
16const MAX_INLINE_NORMALIZE_AXES: usize = 128;
17
18#[inline]
19fn round_f64_to_i32(value: f64) -> i32 {
20 if value >= 0.0 {
21 (value + 0.5) as i32
22 } else {
23 (value - 0.5) as i32
24 }
25}
26
27#[inline]
28fn apply_avar2_delta(coord: Fixed, delta_2dot14: f64) -> Fixed {
29 Fixed::from_bits(
32 coord
33 .to_bits()
34 .wrapping_add(round_f64_to_i32(delta_2dot14 * 4.0)),
35 )
36}
37
38fn normalize_user_coords<T>(
39 axes: &[VariationAxisRecord],
40 user_coords: impl IntoIterator<Item = (Tag, Fixed)>,
41 coords: &mut [T],
42 convert: impl Fn(Fixed) -> T,
43) {
44 let axis_count = axes.len().min(coords.len());
45 let axes = &axes[..axis_count];
46 let coords = &mut coords[..axis_count];
47 if axis_count <= MAX_NORMALIZE_QUADRATIC_AXES {
48 for (tag, user_coord) in user_coords {
49 for (axis, coord) in axes
53 .iter()
54 .zip(coords.iter_mut())
55 .filter(|(axis, _)| axis.axis_tag() == tag)
56 {
57 *coord = convert(axis.normalize(user_coord));
58 }
59 }
60 return;
61 }
62 let mut stack_axis_order = [0u16; MAX_INLINE_NORMALIZE_AXES];
64 let mut heap_axis_order = Vec::new();
65 let axis_order = if axis_count > MAX_INLINE_NORMALIZE_AXES {
66 heap_axis_order.resize(axis_count, 0);
67 heap_axis_order.as_mut_slice()
68 } else {
69 &mut stack_axis_order[..axis_count]
70 };
71 for (i, axis_index) in axis_order.iter_mut().enumerate() {
73 *axis_index = i as u16;
74 }
75 axis_order.sort_unstable_by_key(|&i| axes[i as usize].axis_tag());
78 for (tag, user_coord) in user_coords {
79 let start = axis_order.partition_point(|&i| axes[i as usize].axis_tag() < tag);
80 let end = axis_order.partition_point(|&i| axes[i as usize].axis_tag() <= tag);
81 for &i in &axis_order[start..end] {
82 coords[i as usize] = convert(axes[i as usize].normalize(user_coord));
83 }
84 }
85}
86
87fn apply_avar_mappings<T>(
88 avar: Option<&Avar>,
89 coords: &mut [T],
90 to_fixed: impl Fn(&T) -> Fixed,
91 from_fixed: impl Fn(Fixed) -> T,
92) {
93 if let Some(maps) = avar.map(|avar| avar.axis_segment_maps()) {
94 for (coord, map) in coords.iter_mut().zip(maps.iter()) {
95 if let Ok(map) = map {
96 *coord = from_fixed(map.apply(to_fixed(coord)));
97 }
98 }
99 }
100}
101
102fn to_normalized_coords(fixed_coords: &[Fixed], normalized_coords: &mut [F2Dot14]) {
103 for (target_coord, coord) in normalized_coords.iter_mut().zip(fixed_coords.iter()) {
104 *target_coord = coord.to_f2dot14();
105 }
106}
107
108impl<'a> Fvar<'a> {
109 pub fn axes(&self) -> Result<&'a [VariationAxisRecord], ReadError> {
111 Ok(self.axis_instance_arrays()?.axes())
112 }
113
114 pub fn instances(&self) -> Result<ComputedArray<'a, InstanceRecord<'a>>, ReadError> {
116 Ok(self.axis_instance_arrays()?.instances())
117 }
118
119 pub fn user_to_normalized(
140 &self,
141 avar: Option<&Avar>,
142 user_coords: impl IntoIterator<Item = (Tag, Fixed)>,
143 normalized_coords: &mut [F2Dot14],
144 ) {
145 normalized_coords.fill(F2Dot14::ZERO);
146 let axes = self.axes().unwrap_or_default();
147 let actual_len = axes.len().min(normalized_coords.len());
148 let normalized_coords = &mut normalized_coords[..actual_len];
149
150 let mut stack_fixed_coords = [Fixed::ZERO; MAX_INLINE_AVAR2_AXES];
151 let mut heap_fixed_coords = Vec::new();
152 let fixed_coords = if actual_len > MAX_INLINE_AVAR2_AXES {
153 heap_fixed_coords.resize(actual_len, Fixed::ZERO);
154 heap_fixed_coords.as_mut_slice()
155 } else {
156 &mut stack_fixed_coords[..actual_len]
157 };
158 normalize_user_coords(axes, user_coords, fixed_coords, core::convert::identity);
159 apply_avar_mappings(avar, fixed_coords, |coord| *coord, core::convert::identity);
160
161 let Some(avar) = avar else {
162 to_normalized_coords(fixed_coords, normalized_coords);
163 return;
164 };
165 if avar.version() == MajorMinor::VERSION_1_0 {
166 to_normalized_coords(fixed_coords, normalized_coords);
167 return;
168 }
169
170 let var_store = avar.var_store();
171 let var_index_map = avar.axis_index_map();
172
173 let mut stack_coords_2dot14 = [F2Dot14::ZERO; MAX_INLINE_AVAR2_AXES];
174 let mut heap_coords_2dot14 = Vec::new();
175 let coords_2dot14 = if actual_len > MAX_INLINE_AVAR2_AXES {
176 heap_coords_2dot14.resize(actual_len, F2Dot14::ZERO);
177 heap_coords_2dot14.as_mut_slice()
178 } else {
179 &mut stack_coords_2dot14[..actual_len]
180 };
181 for (coord_2dot14, coord) in coords_2dot14.iter_mut().zip(fixed_coords.iter()) {
182 *coord_2dot14 = coord.to_f2dot14();
183 }
184
185 if let Some(Ok(varstore)) = var_store.as_ref() {
186 for (i, coord) in fixed_coords.iter_mut().enumerate() {
187 let var_index = if let Some(Ok(ref map)) = var_index_map {
188 match map.get(i as u32) {
189 Ok(index) => index,
190 Err(_) => continue,
191 }
192 } else {
193 DeltaSetIndex {
194 outer: 0,
195 inner: i as u16,
196 }
197 };
198 if let Ok(delta) = varstore.compute_float_delta(var_index, coords_2dot14) {
199 *coord =
200 apply_avar2_delta(*coord, delta.to_f64()).clamp(Fixed::NEG_ONE, Fixed::ONE);
201 }
202 }
203 }
204
205 to_normalized_coords(fixed_coords, normalized_coords);
206 }
207}
208
209impl VariationAxisRecord {
210 pub fn normalize(&self, mut value: Fixed) -> Fixed {
212 use core::cmp::Ordering::*;
213 let min_value = self.min_value();
214 let default_value = self.default_value();
215 let max_value = self.max_value().max(min_value);
217 value = value.clamp(min_value, max_value);
218 value = match value.cmp(&default_value) {
219 Less => {
220 -((default_value.saturating_sub(value)) / (default_value.saturating_sub(min_value)))
221 }
222 Greater => {
223 (value.saturating_sub(default_value)) / (max_value.saturating_sub(default_value))
224 }
225 Equal => Fixed::ZERO,
226 };
227 value.clamp(Fixed::NEG_ONE, Fixed::ONE)
228 }
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234 use crate::{FontRef, TableProvider};
235 use types::{BigEndian, F2Dot14, Fixed, NameId, Tag};
236
237 #[test]
238 fn axes() {
239 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
240 let fvar = font.fvar().unwrap();
241 assert_eq!(fvar.axis_count(), 1);
242 let wght = &fvar.axes().unwrap().first().unwrap();
243 assert_eq!(wght.axis_tag(), Tag::new(b"wght"));
244 assert_eq!(wght.min_value(), Fixed::from_f64(100.0));
245 assert_eq!(wght.default_value(), Fixed::from_f64(400.0));
246 assert_eq!(wght.max_value(), Fixed::from_f64(900.0));
247 assert_eq!(wght.flags(), 0);
248 assert_eq!(wght.axis_name_id(), NameId::new(257));
249 }
250
251 #[test]
252 fn instances() {
253 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
254 let fvar = font.fvar().unwrap();
255 assert_eq!(fvar.instance_count(), 9);
256 let instances = fvar.instances().unwrap();
259 for i in 0..9 {
260 let value = 100.0 * (i + 1) as f64;
261 let name_id = NameId::new(258 + i as u16);
262 let instance = instances.get(i).unwrap();
263 assert_eq!(instance.coordinates.len(), 1);
264 assert_eq!(
265 instance.coordinates.first().unwrap().get(),
266 Fixed::from_f64(value)
267 );
268 assert_eq!(instance.subfamily_name_id, name_id);
269 assert_eq!(instance.post_script_name_id, None);
270 }
271 }
272
273 #[test]
274 fn normalize() {
275 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
276 let fvar = font.fvar().unwrap();
277 let axis = fvar.axes().unwrap().first().unwrap();
278 let values = [100.0, 220.0, 250.0, 400.0, 650.0, 900.0];
279 let expected = [-1.0, -0.60001, -0.5, 0.0, 0.5, 1.0];
280 for (value, expected) in values.into_iter().zip(expected) {
281 assert_eq!(
282 axis.normalize(Fixed::from_f64(value)),
283 Fixed::from_f64(expected)
284 );
285 }
286 }
287
288 #[test]
289 fn normalize_overflow() {
290 let test_case = &[
295 79, 84, 84, 79, 0, 1, 32, 32, 255, 32, 32, 32, 102, 118, 97, 114, 32, 32, 32, 32, 0, 0,
296 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,
297 32, 32, 0, 0, 0, 0, 153, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
298 ];
299 let font = FontRef::new(test_case).unwrap();
300 let fvar = font.fvar().unwrap();
301 let axis = fvar.axes().unwrap()[1];
302 assert_eq!(
304 axis.normalize(Fixed::from_f64(0.0)),
305 Fixed::from_f64(-0.2509765625)
306 );
307 }
308
309 #[test]
310 fn normalize_user_coords_uses_indexed_path_for_large_axis_counts() {
311 let axis = VariationAxisRecord {
312 axis_tag: BigEndian::from(Tag::new(b"wght")),
313 min_value: BigEndian::from(Fixed::from_f64(-1.0)),
314 default_value: BigEndian::from(Fixed::ZERO),
315 max_value: BigEndian::from(Fixed::ONE),
316 flags: BigEndian::from(0),
317 axis_name_id: BigEndian::from(NameId::new(1)),
318 };
319 let axes = vec![axis; MAX_NORMALIZE_QUADRATIC_AXES + 1];
320 let mut coords = vec![Fixed::ZERO; axes.len()];
321 normalize_user_coords(
322 &axes,
323 [(Tag::new(b"wght"), Fixed::from_f64(0.5))],
324 &mut coords,
325 core::convert::identity,
326 );
327 assert!(coords.iter().all(|coord| *coord == Fixed::from_f64(0.5)));
328 }
329
330 #[test]
331 fn normalize_user_coords_handles_duplicate_tags_in_indexed_path() {
332 let wght_axis = VariationAxisRecord {
333 axis_tag: BigEndian::from(Tag::new(b"wght")),
334 min_value: BigEndian::from(Fixed::from_f64(-1.0)),
335 default_value: BigEndian::from(Fixed::ZERO),
336 max_value: BigEndian::from(Fixed::ONE),
337 flags: BigEndian::from(0),
338 axis_name_id: BigEndian::from(NameId::new(1)),
339 };
340 let wdth_axis = VariationAxisRecord {
341 axis_tag: BigEndian::from(Tag::new(b"wdth")),
342 min_value: BigEndian::from(Fixed::from_f64(-1.0)),
343 default_value: BigEndian::from(Fixed::ZERO),
344 max_value: BigEndian::from(Fixed::ONE),
345 flags: BigEndian::from(0),
346 axis_name_id: BigEndian::from(NameId::new(2)),
347 };
348 let axes = (0..(MAX_NORMALIZE_QUADRATIC_AXES + 2))
349 .map(|i| if i % 2 == 0 { wght_axis } else { wdth_axis })
350 .collect::<Vec<_>>();
351 let mut coords = vec![Fixed::ZERO; axes.len()];
352 normalize_user_coords(
353 &axes,
354 [(Tag::new(b"wght"), Fixed::from_f64(0.25))],
355 &mut coords,
356 core::convert::identity,
357 );
358 for (i, coord) in coords.iter().enumerate() {
359 if i % 2 == 0 {
360 assert_eq!(*coord, Fixed::from_f64(0.25));
361 } else {
362 assert_eq!(*coord, Fixed::ZERO);
363 }
364 }
365 }
366
367 #[test]
368 fn user_to_normalized() {
369 let font = FontRef::from_index(font_test_data::VAZIRMATN_VAR, 0).unwrap();
370 let fvar = font.fvar().unwrap();
371 let avar = font.avar().ok();
372 let wght = Tag::new(b"wght");
373 let axis = fvar.axes().unwrap()[0];
374 let mut normalized_coords = [F2Dot14::default(); 1];
375 let avar_user = axis.default_value().to_f32()
377 + (axis.max_value().to_f32() - axis.default_value().to_f32()) * 0.8;
378 let avar_normalized = 0.83875;
379 #[rustfmt::skip]
380 let cases = [
381 (-1000.0, -1.0f32),
383 (100.0, -1.0),
384 (200.0, -0.5),
385 (400.0, 0.0),
386 (900.0, 1.0),
387 (avar_user, avar_normalized),
388 (1251.5, 1.0),
389 ];
390 for (user, normalized) in cases {
391 fvar.user_to_normalized(
392 avar.as_ref(),
393 [(wght, Fixed::from_f64(user as f64))],
394 &mut normalized_coords,
395 );
396 assert_eq!(normalized_coords[0], F2Dot14::from_f32(normalized));
397 }
398 }
399
400 #[test]
401 fn avar2() {
402 let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
403 let avar = font.avar().ok();
404 let fvar = font.fvar().unwrap();
405 let avar_axis = Tag::new(b"AVAR");
406 let avwk_axis = Tag::new(b"AVWK");
407 let mut normalized_coords = [F2Dot14::default(); 2];
408 let cases = [
409 ((100.0, 0.0), (1.0, 1.0)),
410 ((50.0, 0.0), (0.5, 0.5)),
411 ((0.0, 50.0), (0.0, 0.5)),
412 ];
413 for (user, expected) in cases {
414 fvar.user_to_normalized(
415 avar.as_ref(),
416 [
417 (avar_axis, Fixed::from_f64(user.0)),
418 (avwk_axis, Fixed::from_f64(user.1)),
419 ],
420 &mut normalized_coords,
421 );
422 assert_eq!(normalized_coords[0], F2Dot14::from_f32(expected.0));
423 assert_eq!(normalized_coords[1], F2Dot14::from_f32(expected.1));
424 }
425 }
426
427 #[test]
428 fn avar2_no_panic_with_wrong_size_coords_array() {
429 let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
431 let avar = font.avar().ok();
432 let fvar = font.fvar().unwrap();
433 let mut normalized_coords = [F2Dot14::default(); 1];
435 fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
436 let mut normalized_coords = [F2Dot14::default(); 4];
438 fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
439 }
440
441 #[test]
442 fn avar2_preserves_16_16_precision_until_final_rounding() {
443 let coord = Fixed::from_bits(3);
446 assert_eq!(
447 super::apply_avar2_delta(coord, 0.5).to_f2dot14(),
448 F2Dot14::from_bits(1)
449 );
450 }
451
452 #[test]
453 fn avar2_clamps_hidden_axis_for_amstelvar_repro() {
454 let font = FontRef::new(font_test_data::AMSTELVAR_AVAR2_A).unwrap();
455 let avar = font.avar().ok();
456 let fvar = font.fvar().unwrap();
457 let mut normalized_coords = [F2Dot14::ZERO; 12];
458
459 fvar.user_to_normalized(
460 avar.as_ref(),
461 [(Tag::new(b"wght"), Fixed::from_f64(1000.0))],
462 &mut normalized_coords,
463 );
464
465 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); }
470}