1use read_fonts::{
4 tables::avar::Avar,
5 tables::fvar::{self, Fvar},
6 types::{Fixed, Tag},
7 FontRef, TableProvider,
8};
9
10use crate::{
11 collections::SmallVec,
12 instance::{Location, NormalizedCoord},
13 setting::VariationSetting,
14 string::StringId,
15};
16
17#[derive(Clone)]
24pub struct Axis {
25 index: usize,
26 record: fvar::VariationAxisRecord,
27}
28
29impl Axis {
30 pub fn tag(&self) -> Tag {
32 self.record.axis_tag()
33 }
34
35 pub fn index(&self) -> usize {
37 self.index
38 }
39
40 pub fn name_id(&self) -> StringId {
42 self.record.axis_name_id()
43 }
44
45 pub fn is_hidden(&self) -> bool {
47 const AXIS_HIDDEN_FLAG: u16 = 0x1;
48 self.record.flags() & AXIS_HIDDEN_FLAG != 0
49 }
50
51 pub fn min_value(&self) -> f32 {
53 self.record.min_value().to_f64() as _
54 }
55
56 pub fn default_value(&self) -> f32 {
58 self.record.default_value().to_f64() as _
59 }
60
61 pub fn max_value(&self) -> f32 {
63 self.record.max_value().to_f64() as _
64 }
65
66 pub fn normalize(&self, coord: f32) -> NormalizedCoord {
73 self.record
74 .normalize(Fixed::from_f64(coord as _))
75 .to_f2dot14()
76 }
77}
78
79#[derive(Clone)]
86pub struct AxisCollection<'a> {
87 fvar: Option<Fvar<'a>>,
88 avar: Option<Avar<'a>>,
89}
90
91impl<'a> AxisCollection<'a> {
92 pub fn new(font: &FontRef<'a>) -> Self {
94 let fvar = font.fvar().ok();
95 let avar = font.avar().ok();
96 Self { fvar, avar }
97 }
98
99 pub fn len(&self) -> usize {
101 self.fvar
102 .as_ref()
103 .map(|fvar| fvar.axis_count() as usize)
104 .unwrap_or(0)
105 }
106
107 pub fn is_empty(&self) -> bool {
109 self.len() == 0
110 }
111
112 pub fn get(&self, index: usize) -> Option<Axis> {
114 let record = *self.fvar.as_ref()?.axes().ok()?.get(index)?;
115 Some(Axis { index, record })
116 }
117
118 pub fn get_by_tag(&self, tag: Tag) -> Option<Axis> {
130 self.iter().find(|axis| axis.tag() == tag)
131 }
132
133 pub fn location<I>(&self, settings: I) -> Location
153 where
154 I: IntoIterator,
155 I::Item: Into<VariationSetting>,
156 {
157 let mut location = Location::new(self.len());
158 self.location_to_slice(settings, location.coords_mut());
159 location
160 }
161
162 pub fn location_to_slice<I>(&self, settings: I, location: &mut [NormalizedCoord])
186 where
187 I: IntoIterator,
188 I::Item: Into<VariationSetting>,
189 {
190 if let Some(fvar) = self.fvar.as_ref() {
191 fvar.user_to_normalized(
192 self.avar.as_ref(),
193 settings
194 .into_iter()
195 .map(|setting| setting.into())
196 .map(|setting| (setting.selector, Fixed::from_f64(setting.value as f64))),
197 location,
198 );
199 } else {
200 location.fill(NormalizedCoord::default());
201 }
202 }
203
204 pub fn filter<I>(&self, settings: I) -> impl Iterator<Item = VariationSetting> + Clone
230 where
231 I: IntoIterator,
232 I::Item: Into<VariationSetting>,
233 {
234 #[derive(Copy, Clone, Default)]
235 struct Entry {
236 tag: Tag,
237 min: f32,
238 max: f32,
239 value: f32,
240 present: bool,
241 }
242 let mut results = SmallVec::<_, 8>::with_len(self.len(), Entry::default());
243 for (axis, result) in self.iter().zip(results.as_mut_slice()) {
244 result.tag = axis.tag();
245 result.min = axis.min_value();
246 result.max = axis.max_value();
247 result.value = axis.default_value();
248 }
249 for setting in settings {
250 let setting = setting.into();
251 for entry in results.as_mut_slice() {
252 if entry.tag == setting.selector {
253 entry.value = setting.value.max(entry.min).min(entry.max);
254 entry.present = true;
255 }
256 }
257 }
258 results
259 .into_iter()
260 .filter(|entry| entry.present)
261 .map(|entry| VariationSetting::new(entry.tag, entry.value))
262 }
263
264 pub fn iter(&self) -> impl Iterator<Item = Axis> + 'a + Clone {
266 let copy = self.clone();
267 (0..self.len()).filter_map(move |i| copy.get(i))
268 }
269}
270
271#[derive(Clone)]
278pub struct NamedInstance<'a> {
279 axes: AxisCollection<'a>,
280 record: fvar::InstanceRecord<'a>,
281}
282
283impl<'a> NamedInstance<'a> {
284 pub fn subfamily_name_id(&self) -> StringId {
286 self.record.subfamily_name_id
287 }
288
289 pub fn postscript_name_id(&self) -> Option<StringId> {
291 self.record.post_script_name_id
292 }
293
294 pub fn user_coords(&self) -> impl Iterator<Item = f32> + 'a + Clone {
297 self.record
298 .coordinates
299 .iter()
300 .map(|coord| coord.get().to_f64() as _)
301 }
302
303 pub fn location(&self) -> Location {
314 let mut location = Location::new(self.axes.len());
315 self.location_to_slice(location.coords_mut());
316 location
317 }
318
319 pub fn location_to_slice(&self, location: &mut [NormalizedCoord]) {
333 let settings = self
334 .axes
335 .iter()
336 .map(|axis| axis.tag())
337 .zip(self.user_coords());
338 self.axes.location_to_slice(settings, location);
339 }
340}
341
342#[derive(Clone)]
346pub struct NamedInstanceCollection<'a> {
347 axes: AxisCollection<'a>,
348}
349
350impl<'a> NamedInstanceCollection<'a> {
351 pub fn new(font: &FontRef<'a>) -> Self {
353 Self {
354 axes: AxisCollection::new(font),
355 }
356 }
357
358 pub fn len(&self) -> usize {
360 self.axes
361 .fvar
362 .as_ref()
363 .map(|fvar| fvar.instance_count() as usize)
364 .unwrap_or(0)
365 }
366
367 pub fn is_empty(&self) -> bool {
369 self.len() == 0
370 }
371
372 pub fn get(&self, index: usize) -> Option<NamedInstance<'a>> {
374 let record = self.axes.fvar.as_ref()?.instances().ok()?.get(index).ok()?;
375 Some(NamedInstance {
376 axes: self.axes.clone(),
377 record,
378 })
379 }
380
381 pub fn iter(&self) -> impl Iterator<Item = NamedInstance<'a>> + 'a + Clone {
383 let copy = self.clone();
384 (0..self.len()).filter_map(move |i| copy.get(i))
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391 use crate::MetadataProvider as _;
392 use font_test_data::VAZIRMATN_VAR;
393 use read_fonts::FontRef;
394 use std::str::FromStr;
395
396 #[test]
397 fn axis() {
398 let font = FontRef::from_index(VAZIRMATN_VAR, 0).unwrap();
399 let axis = font.axes().get(0).unwrap();
400 assert_eq!(axis.index(), 0);
401 assert_eq!(axis.tag(), Tag::new(b"wght"));
402 assert_eq!(axis.min_value(), 100.0);
403 assert_eq!(axis.default_value(), 400.0);
404 assert_eq!(axis.max_value(), 900.0);
405 assert_eq!(axis.name_id(), StringId::new(257));
406 assert_eq!(
407 font.localized_strings(axis.name_id())
408 .english_or_first()
409 .unwrap()
410 .to_string(),
411 "Weight"
412 );
413 }
414
415 #[test]
416 fn named_instances() {
417 let font = FontRef::from_index(VAZIRMATN_VAR, 0).unwrap();
418 let named_instances = font.named_instances();
419 let thin = named_instances.get(0).unwrap();
420 assert_eq!(thin.subfamily_name_id(), StringId::new(258));
421 assert_eq!(
422 font.localized_strings(thin.subfamily_name_id())
423 .english_or_first()
424 .unwrap()
425 .to_string(),
426 "Thin"
427 );
428 assert_eq!(thin.location().coords(), &[NormalizedCoord::from_f32(-1.0)]);
429 let regular = named_instances.get(3).unwrap();
430 assert_eq!(regular.subfamily_name_id(), StringId::new(261));
431 assert_eq!(
432 font.localized_strings(regular.subfamily_name_id())
433 .english_or_first()
434 .unwrap()
435 .to_string(),
436 "Regular"
437 );
438 assert_eq!(
439 regular.location().coords(),
440 &[NormalizedCoord::from_f32(0.0)]
441 );
442 let bold = named_instances.get(6).unwrap();
443 assert_eq!(bold.subfamily_name_id(), StringId::new(264));
444 assert_eq!(
445 font.localized_strings(bold.subfamily_name_id())
446 .english_or_first()
447 .unwrap()
448 .to_string(),
449 "Bold"
450 );
451 assert_eq!(
452 bold.location().coords(),
453 &[NormalizedCoord::from_f32(0.6776123)]
454 );
455 }
456
457 #[test]
458 fn location() {
459 let font = FontRef::from_index(VAZIRMATN_VAR, 0).unwrap();
460 let axes = font.axes();
461 let axis = axes.get_by_tag(Tag::from_str("wght").unwrap()).unwrap();
462 assert_eq!(
463 axes.location([("wght", -1000.0)]).coords(),
464 &[NormalizedCoord::from_f32(-1.0)]
465 );
466 assert_eq!(
467 axes.location([("wght", 100.0)]).coords(),
468 &[NormalizedCoord::from_f32(-1.0)]
469 );
470 assert_eq!(
471 axes.location([("wght", 200.0)]).coords(),
472 &[NormalizedCoord::from_f32(-0.5)]
473 );
474 assert_eq!(
475 axes.location([("wght", 400.0)]).coords(),
476 &[NormalizedCoord::from_f32(0.0)]
477 );
478 assert_eq!(
480 axes.location(&[(
481 "wght",
482 axis.default_value() + (axis.max_value() - axis.default_value()) * 0.8,
483 )])
484 .coords(),
485 &[NormalizedCoord::from_f32(0.83875)]
486 );
487 assert_eq!(
488 axes.location([("wght", 900.0)]).coords(),
489 &[NormalizedCoord::from_f32(1.0)]
490 );
491 assert_eq!(
492 axes.location([("wght", 1251.5)]).coords(),
493 &[NormalizedCoord::from_f32(1.0)]
494 );
495 }
496
497 #[test]
498 fn filter() {
499 let font = FontRef::from_index(VAZIRMATN_VAR, 0).unwrap();
500 let axes = font.axes();
503 let drop_missing: Vec<_> = axes.filter(&[("slnt", 25.0), ("wdth", 50.0)]).collect();
505 assert_eq!(&drop_missing, &[]);
506 let clamp: Vec<_> = axes.filter(&[("wght", 50.0)]).collect();
508 assert_eq!(&clamp, &[("wght", 100.0).into()]);
509 let drop_missing_and_clamp: Vec<_> =
511 axes.filter(&[("slnt", 25.0), ("wght", 1000.0)]).collect();
512 assert_eq!(&drop_missing_and_clamp, &[("wght", 900.0).into()]);
513 let drop_duplicate_and_missing: Vec<_> = axes
515 .filter(&[("wght", 400.0), ("opsz", 100.0), ("wght", 120.5)])
516 .collect();
517 assert_eq!(&drop_duplicate_and_missing, &[("wght", 120.5).into()]);
518 }
519}