include!("../../generated/generated_fvar.rs");
#[path = "./instance_record.rs"]
mod instance_record;
use super::{avar::Avar, variations::DeltaSetIndex};
use alloc::vec::Vec;
pub use instance_record::InstanceRecord;
const MAX_INLINE_AVAR2_AXES: usize = 64;
#[inline]
fn round_f64_to_i32(value: f64) -> i32 {
if value >= 0.0 {
(value + 0.5) as i32
} else {
(value - 0.5) as i32
}
}
#[inline]
fn apply_avar2_delta(coord: Fixed, delta_2dot14: f64) -> Fixed {
Fixed::from_bits(
coord
.to_bits()
.wrapping_add(round_f64_to_i32(delta_2dot14 * 4.0)),
)
}
fn normalize_user_coords<T>(
axes: &[VariationAxisRecord],
user_coords: impl IntoIterator<Item = (Tag, Fixed)>,
coords: &mut [T],
convert: impl Fn(Fixed) -> T,
) {
for user_coord in user_coords {
for (axis, coord) in axes
.iter()
.zip(coords.iter_mut())
.filter(|(axis, _)| axis.axis_tag() == user_coord.0)
{
*coord = convert(axis.normalize(user_coord.1));
}
}
}
fn apply_avar_mappings<T>(
avar: Option<&Avar>,
coords: &mut [T],
to_fixed: impl Fn(&T) -> Fixed,
from_fixed: impl Fn(Fixed) -> T,
) {
let avar_mappings = avar.map(|avar| avar.axis_segment_maps());
for (i, coord) in coords.iter_mut().enumerate() {
if let Some(mapping) = avar_mappings
.as_ref()
.and_then(|mappings| mappings.get(i).transpose().ok())
.flatten()
{
*coord = from_fixed(mapping.apply(to_fixed(coord)));
}
}
}
fn to_normalized_coords(fixed_coords: &[Fixed], normalized_coords: &mut [F2Dot14]) {
for (target_coord, coord) in normalized_coords.iter_mut().zip(fixed_coords.iter()) {
*target_coord = coord.to_f2dot14();
}
}
impl<'a> Fvar<'a> {
pub fn axes(&self) -> Result<&'a [VariationAxisRecord], ReadError> {
Ok(self.axis_instance_arrays()?.axes())
}
pub fn instances(&self) -> Result<ComputedArray<'a, InstanceRecord<'a>>, ReadError> {
Ok(self.axis_instance_arrays()?.instances())
}
pub fn user_to_normalized(
&self,
avar: Option<&Avar>,
user_coords: impl IntoIterator<Item = (Tag, Fixed)>,
normalized_coords: &mut [F2Dot14],
) {
normalized_coords.fill(F2Dot14::ZERO);
let axes = self.axes().unwrap_or_default();
let actual_len = axes.len().min(normalized_coords.len());
let normalized_coords = &mut normalized_coords[..actual_len];
let mut stack_fixed_coords = [Fixed::ZERO; MAX_INLINE_AVAR2_AXES];
let mut heap_fixed_coords = Vec::new();
let fixed_coords = if actual_len > MAX_INLINE_AVAR2_AXES {
heap_fixed_coords.resize(actual_len, Fixed::ZERO);
heap_fixed_coords.as_mut_slice()
} else {
&mut stack_fixed_coords[..actual_len]
};
normalize_user_coords(axes, user_coords, fixed_coords, core::convert::identity);
apply_avar_mappings(avar, fixed_coords, |coord| *coord, core::convert::identity);
let Some(avar) = avar else {
to_normalized_coords(fixed_coords, normalized_coords);
return;
};
if avar.version() == MajorMinor::VERSION_1_0 {
to_normalized_coords(fixed_coords, normalized_coords);
return;
}
let var_store = avar.var_store();
let var_index_map = avar.axis_index_map();
let mut coords_2dot14 = [F2Dot14::ZERO; MAX_INLINE_AVAR2_AXES];
let coords_2dot14 = &mut coords_2dot14[..actual_len];
for (coord_2dot14, coord) in coords_2dot14.iter_mut().zip(fixed_coords.iter()) {
*coord_2dot14 = coord.to_f2dot14();
}
if let Some(Ok(varstore)) = var_store.as_ref() {
for (i, coord) in fixed_coords.iter_mut().enumerate() {
let var_index = if let Some(Ok(ref map)) = var_index_map {
match map.get(i as u32) {
Ok(index) => index,
Err(_) => continue,
}
} else {
DeltaSetIndex {
outer: 0,
inner: i as u16,
}
};
if let Ok(delta) = varstore.compute_float_delta(var_index, coords_2dot14) {
*coord =
apply_avar2_delta(*coord, delta.to_f64()).clamp(Fixed::NEG_ONE, Fixed::ONE);
}
}
}
to_normalized_coords(fixed_coords, normalized_coords);
}
}
impl VariationAxisRecord {
pub fn normalize(&self, mut value: Fixed) -> Fixed {
use core::cmp::Ordering::*;
let min_value = self.min_value();
let default_value = self.default_value();
let max_value = self.max_value().max(min_value);
value = value.clamp(min_value, max_value);
value = match value.cmp(&default_value) {
Less => {
-((default_value.saturating_sub(value)) / (default_value.saturating_sub(min_value)))
}
Greater => {
(value.saturating_sub(default_value)) / (max_value.saturating_sub(default_value))
}
Equal => Fixed::ZERO,
};
value.clamp(Fixed::NEG_ONE, Fixed::ONE)
}
}
#[cfg(test)]
mod tests {
use crate::{FontRef, TableProvider};
use types::{F2Dot14, Fixed, NameId, Tag};
#[test]
fn axes() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let fvar = font.fvar().unwrap();
assert_eq!(fvar.axis_count(), 1);
let wght = &fvar.axes().unwrap().first().unwrap();
assert_eq!(wght.axis_tag(), Tag::new(b"wght"));
assert_eq!(wght.min_value(), Fixed::from_f64(100.0));
assert_eq!(wght.default_value(), Fixed::from_f64(400.0));
assert_eq!(wght.max_value(), Fixed::from_f64(900.0));
assert_eq!(wght.flags(), 0);
assert_eq!(wght.axis_name_id(), NameId::new(257));
}
#[test]
fn instances() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let fvar = font.fvar().unwrap();
assert_eq!(fvar.instance_count(), 9);
let instances = fvar.instances().unwrap();
for i in 0..9 {
let value = 100.0 * (i + 1) as f64;
let name_id = NameId::new(258 + i as u16);
let instance = instances.get(i).unwrap();
assert_eq!(instance.coordinates.len(), 1);
assert_eq!(
instance.coordinates.first().unwrap().get(),
Fixed::from_f64(value)
);
assert_eq!(instance.subfamily_name_id, name_id);
assert_eq!(instance.post_script_name_id, None);
}
}
#[test]
fn normalize() {
let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
let fvar = font.fvar().unwrap();
let axis = fvar.axes().unwrap().first().unwrap();
let values = [100.0, 220.0, 250.0, 400.0, 650.0, 900.0];
let expected = [-1.0, -0.60001, -0.5, 0.0, 0.5, 1.0];
for (value, expected) in values.into_iter().zip(expected) {
assert_eq!(
axis.normalize(Fixed::from_f64(value)),
Fixed::from_f64(expected)
);
}
}
#[test]
fn normalize_overflow() {
let test_case = &[
79, 84, 84, 79, 0, 1, 32, 32, 255, 32, 32, 32, 102, 118, 97, 114, 32, 32, 32, 32, 0, 0,
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,
32, 32, 0, 0, 0, 0, 153, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
];
let font = FontRef::new(test_case).unwrap();
let fvar = font.fvar().unwrap();
let axis = fvar.axes().unwrap()[1];
assert_eq!(
axis.normalize(Fixed::from_f64(0.0)),
Fixed::from_f64(-0.2509765625)
);
}
#[test]
fn user_to_normalized() {
let font = FontRef::from_index(font_test_data::VAZIRMATN_VAR, 0).unwrap();
let fvar = font.fvar().unwrap();
let avar = font.avar().ok();
let wght = Tag::new(b"wght");
let axis = fvar.axes().unwrap()[0];
let mut normalized_coords = [F2Dot14::default(); 1];
let avar_user = axis.default_value().to_f32()
+ (axis.max_value().to_f32() - axis.default_value().to_f32()) * 0.8;
let avar_normalized = 0.83875;
#[rustfmt::skip]
let cases = [
(-1000.0, -1.0f32),
(100.0, -1.0),
(200.0, -0.5),
(400.0, 0.0),
(900.0, 1.0),
(avar_user, avar_normalized),
(1251.5, 1.0),
];
for (user, normalized) in cases {
fvar.user_to_normalized(
avar.as_ref(),
[(wght, Fixed::from_f64(user as f64))],
&mut normalized_coords,
);
assert_eq!(normalized_coords[0], F2Dot14::from_f32(normalized));
}
}
#[test]
fn avar2() {
let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
let avar = font.avar().ok();
let fvar = font.fvar().unwrap();
let avar_axis = Tag::new(b"AVAR");
let avwk_axis = Tag::new(b"AVWK");
let mut normalized_coords = [F2Dot14::default(); 2];
let cases = [
((100.0, 0.0), (1.0, 1.0)),
((50.0, 0.0), (0.5, 0.5)),
((0.0, 50.0), (0.0, 0.5)),
];
for (user, expected) in cases {
fvar.user_to_normalized(
avar.as_ref(),
[
(avar_axis, Fixed::from_f64(user.0)),
(avwk_axis, Fixed::from_f64(user.1)),
],
&mut normalized_coords,
);
assert_eq!(normalized_coords[0], F2Dot14::from_f32(expected.0));
assert_eq!(normalized_coords[1], F2Dot14::from_f32(expected.1));
}
}
#[test]
fn avar2_no_panic_with_wrong_size_coords_array() {
let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
let avar = font.avar().ok();
let fvar = font.fvar().unwrap();
let mut normalized_coords = [F2Dot14::default(); 1];
fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
let mut normalized_coords = [F2Dot14::default(); 4];
fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
}
#[test]
fn avar2_preserves_16_16_precision_until_final_rounding() {
let coord = Fixed::from_bits(3);
assert_eq!(
super::apply_avar2_delta(coord, 0.5).to_f2dot14(),
F2Dot14::from_bits(1)
);
}
#[test]
fn avar2_clamps_hidden_axis_for_amstelvar_repro() {
let font = FontRef::new(font_test_data::AMSTELVAR_AVAR2_A).unwrap();
let avar = font.avar().ok();
let fvar = font.fvar().unwrap();
let mut normalized_coords = [F2Dot14::ZERO; 12];
fvar.user_to_normalized(
avar.as_ref(),
[(Tag::new(b"wght"), Fixed::from_f64(1000.0))],
&mut normalized_coords,
);
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); }
}