1include!("../../generated/generated_fvar.rs");
4
5#[path = "./instance_record.rs"]
6mod instance_record;
7
8use super::{
9 avar::Avar,
10 variations::{DeltaSetIndex, FloatItemDeltaTarget},
11};
12pub use instance_record::InstanceRecord;
13
14impl<'a> Fvar<'a> {
15 pub fn axes(&self) -> Result<&'a [VariationAxisRecord], ReadError> {
17 Ok(self.axis_instance_arrays()?.axes())
18 }
19
20 pub fn instances(&self) -> Result<ComputedArray<'a, InstanceRecord<'a>>, ReadError> {
22 Ok(self.axis_instance_arrays()?.instances())
23 }
24
25 pub fn user_to_normalized(
46 &self,
47 avar: Option<&Avar>,
48 user_coords: impl IntoIterator<Item = (Tag, Fixed)>,
49 normalized_coords: &mut [F2Dot14],
50 ) {
51 normalized_coords.fill(F2Dot14::default());
52 let axes = self.axes().unwrap_or_default();
53 let avar_mappings = avar.map(|avar| avar.axis_segment_maps());
54 for user_coord in user_coords {
55 for (i, axis) in axes
61 .iter()
62 .enumerate()
63 .filter(|(_, axis)| axis.axis_tag() == user_coord.0)
64 {
65 if let Some(target_coord) = normalized_coords.get_mut(i) {
66 let coord = axis.normalize(user_coord.1);
67 *target_coord = avar_mappings
68 .as_ref()
69 .and_then(|mappings| mappings.get(i).transpose().ok())
70 .flatten()
71 .map(|mapping| mapping.apply(coord))
72 .unwrap_or(coord)
73 .to_f2dot14();
74 }
75 }
76 }
77 let Some(avar) = avar else { return };
78 if avar.version() == MajorMinor::VERSION_1_0 {
79 return;
80 }
81 let var_store = avar.var_store();
82 let var_index_map = avar.axis_index_map();
83
84 let actual_len = axes.len().min(normalized_coords.len());
85 let mut new_coords = [F2Dot14::ZERO; 64];
86 if actual_len > 64 {
87 return;
90 }
91
92 let new_coords = &mut new_coords[..actual_len];
93 let normalized_coords = &mut normalized_coords[..actual_len];
94 new_coords.copy_from_slice(normalized_coords);
95
96 for (i, v) in normalized_coords.iter().enumerate() {
97 let var_index = if let Some(Ok(ref map)) = var_index_map {
98 map.get(i as u32).ok()
99 } else {
100 Some(DeltaSetIndex {
101 outer: 0,
102 inner: i as u16,
103 })
104 };
105 if var_index.is_none() {
106 continue;
107 }
108 if let Some(Ok(varstore)) = var_store.as_ref() {
109 if let Ok(delta) =
110 varstore.compute_float_delta(var_index.unwrap(), normalized_coords)
111 {
112 new_coords[i] = F2Dot14::from_f32((*v).apply_float_delta(delta))
113 .clamp(F2Dot14::MIN, F2Dot14::MAX);
114 }
115 }
116 }
117 normalized_coords.copy_from_slice(new_coords);
118 }
119}
120
121impl VariationAxisRecord {
122 pub fn normalize(&self, mut value: Fixed) -> Fixed {
124 use core::cmp::Ordering::*;
125 let min_value = self.min_value();
126 let default_value = self.default_value();
127 let max_value = self.max_value().max(min_value);
129 value = value.clamp(min_value, max_value);
130 value = match value.cmp(&default_value) {
131 Less => {
132 -((default_value.saturating_sub(value)) / (default_value.saturating_sub(min_value)))
133 }
134 Greater => {
135 (value.saturating_sub(default_value)) / (max_value.saturating_sub(default_value))
136 }
137 Equal => Fixed::ZERO,
138 };
139 value.clamp(-Fixed::ONE, Fixed::ONE)
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use crate::{FontRef, TableProvider};
146 use types::{F2Dot14, Fixed, NameId, Tag};
147
148 #[test]
149 fn axes() {
150 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
151 let fvar = font.fvar().unwrap();
152 assert_eq!(fvar.axis_count(), 1);
153 let wght = &fvar.axes().unwrap().first().unwrap();
154 assert_eq!(wght.axis_tag(), Tag::new(b"wght"));
155 assert_eq!(wght.min_value(), Fixed::from_f64(100.0));
156 assert_eq!(wght.default_value(), Fixed::from_f64(400.0));
157 assert_eq!(wght.max_value(), Fixed::from_f64(900.0));
158 assert_eq!(wght.flags(), 0);
159 assert_eq!(wght.axis_name_id(), NameId::new(257));
160 }
161
162 #[test]
163 fn instances() {
164 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
165 let fvar = font.fvar().unwrap();
166 assert_eq!(fvar.instance_count(), 9);
167 let instances = fvar.instances().unwrap();
170 for i in 0..9 {
171 let value = 100.0 * (i + 1) as f64;
172 let name_id = NameId::new(258 + i as u16);
173 let instance = instances.get(i).unwrap();
174 assert_eq!(instance.coordinates.len(), 1);
175 assert_eq!(
176 instance.coordinates.first().unwrap().get(),
177 Fixed::from_f64(value)
178 );
179 assert_eq!(instance.subfamily_name_id, name_id);
180 assert_eq!(instance.post_script_name_id, None);
181 }
182 }
183
184 #[test]
185 fn normalize() {
186 let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
187 let fvar = font.fvar().unwrap();
188 let axis = fvar.axes().unwrap().first().unwrap();
189 let values = [100.0, 220.0, 250.0, 400.0, 650.0, 900.0];
190 let expected = [-1.0, -0.60001, -0.5, 0.0, 0.5, 1.0];
191 for (value, expected) in values.into_iter().zip(expected) {
192 assert_eq!(
193 axis.normalize(Fixed::from_f64(value)),
194 Fixed::from_f64(expected)
195 );
196 }
197 }
198
199 #[test]
200 fn normalize_overflow() {
201 let test_case = &[
206 79, 84, 84, 79, 0, 1, 32, 32, 255, 32, 32, 32, 102, 118, 97, 114, 32, 32, 32, 32, 0, 0,
207 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,
208 32, 32, 0, 0, 0, 0, 153, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
209 ];
210 let font = FontRef::new(test_case).unwrap();
211 let fvar = font.fvar().unwrap();
212 let axis = fvar.axes().unwrap()[1];
213 assert_eq!(
215 axis.normalize(Fixed::from_f64(0.0)),
216 Fixed::from_f64(-0.2509765625)
217 );
218 }
219
220 #[test]
221 fn user_to_normalized() {
222 let font = FontRef::from_index(font_test_data::VAZIRMATN_VAR, 0).unwrap();
223 let fvar = font.fvar().unwrap();
224 let avar = font.avar().ok();
225 let wght = Tag::new(b"wght");
226 let axis = fvar.axes().unwrap()[0];
227 let mut normalized_coords = [F2Dot14::default(); 1];
228 let avar_user = axis.default_value().to_f32()
230 + (axis.max_value().to_f32() - axis.default_value().to_f32()) * 0.8;
231 let avar_normalized = 0.83875;
232 #[rustfmt::skip]
233 let cases = [
234 (-1000.0, -1.0f32),
236 (100.0, -1.0),
237 (200.0, -0.5),
238 (400.0, 0.0),
239 (900.0, 1.0),
240 (avar_user, avar_normalized),
241 (1251.5, 1.0),
242 ];
243 for (user, normalized) in cases {
244 fvar.user_to_normalized(
245 avar.as_ref(),
246 [(wght, Fixed::from_f64(user as f64))],
247 &mut normalized_coords,
248 );
249 assert_eq!(normalized_coords[0], F2Dot14::from_f32(normalized));
250 }
251 }
252
253 #[test]
254 fn avar2() {
255 let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
256 let avar = font.avar().ok();
257 let fvar = font.fvar().unwrap();
258 let avar_axis = Tag::new(b"AVAR");
259 let avwk_axis = Tag::new(b"AVWK");
260 let mut normalized_coords = [F2Dot14::default(); 2];
261 let cases = [
262 ((100.0, 0.0), (1.0, 1.0)),
263 ((50.0, 0.0), (0.5, 0.5)),
264 ((0.0, 50.0), (0.0, 0.5)),
265 ];
266 for (user, expected) in cases {
267 fvar.user_to_normalized(
268 avar.as_ref(),
269 [
270 (avar_axis, Fixed::from_f64(user.0)),
271 (avwk_axis, Fixed::from_f64(user.1)),
272 ],
273 &mut normalized_coords,
274 );
275 assert_eq!(normalized_coords[0], F2Dot14::from_f32(expected.0));
276 assert_eq!(normalized_coords[1], F2Dot14::from_f32(expected.1));
277 }
278 }
279
280 #[test]
281 fn avar2_no_panic_with_wrong_size_coords_array() {
282 let font = FontRef::new(font_test_data::AVAR2_CHECKER).unwrap();
284 let avar = font.avar().ok();
285 let fvar = font.fvar().unwrap();
286 let mut normalized_coords = [F2Dot14::default(); 1];
288 fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
289 let mut normalized_coords = [F2Dot14::default(); 4];
291 fvar.user_to_normalized(avar.as_ref(), [], &mut normalized_coords);
292 }
293}