xref: /aosp_15_r20/external/cronet/third_party/rust/chromium_crates_io/vendor/skrifa-0.15.5/src/variation.rs (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 //! Axes of variation in a variable font.
2 
3 use read_fonts::{
4     tables::avar::Avar,
5     tables::fvar::{self, Fvar},
6     types::{Fixed, Tag},
7     TableProvider,
8 };
9 
10 use crate::{
11     instance::{Location, NormalizedCoord},
12     setting::VariationSetting,
13     small_array::SmallArray,
14     string::StringId,
15 };
16 
17 /// Axis of variation in a variable font.
18 ///
19 /// In variable fonts, an axis usually refers to a single aspect of a
20 /// typeface's design that can be altered by the user.
21 ///
22 /// See <https://fonts.google.com/knowledge/glossary/axis_in_variable_fonts>
23 #[derive(Clone)]
24 pub struct Axis {
25     index: usize,
26     record: fvar::VariationAxisRecord,
27 }
28 
29 impl Axis {
30     /// Returns the tag that identifies the axis.
tag(&self) -> Tag31     pub fn tag(&self) -> Tag {
32         self.record.axis_tag()
33     }
34 
35     /// Returns the index of the axis in its owning collection.
index(&self) -> usize36     pub fn index(&self) -> usize {
37         self.index
38     }
39 
40     /// Returns the localized string identifier for the name of the axis.
name_id(&self) -> StringId41     pub fn name_id(&self) -> StringId {
42         self.record.axis_name_id()
43     }
44 
45     /// Returns true if the axis should be hidden in user interfaces.
is_hidden(&self) -> bool46     pub fn is_hidden(&self) -> bool {
47         const AXIS_HIDDEN_FLAG: u16 = 0x1;
48         self.record.flags() & AXIS_HIDDEN_FLAG != 0
49     }
50 
51     /// Returns the minimum value of the axis.
min_value(&self) -> f3252     pub fn min_value(&self) -> f32 {
53         self.record.min_value().to_f64() as _
54     }
55 
56     /// Returns the default value of the axis.
default_value(&self) -> f3257     pub fn default_value(&self) -> f32 {
58         self.record.default_value().to_f64() as _
59     }
60 
61     /// Returns the maximum value of the axis.
max_value(&self) -> f3262     pub fn max_value(&self) -> f32 {
63         self.record.max_value().to_f64() as _
64     }
65 
66     /// Returns a normalized coordinate for the given user coordinate.
67     ///
68     /// The value will be clamped to the range specified by the minimum
69     /// and maximum values.
70     ///
71     /// This does not apply any axis variation remapping.
normalize(&self, coord: f32) -> NormalizedCoord72     pub fn normalize(&self, coord: f32) -> NormalizedCoord {
73         self.record
74             .normalize(Fixed::from_f64(coord as _))
75             .to_f2dot14()
76     }
77 }
78 
79 /// Collection of axes in a variable font.
80 ///
81 /// See the [`Axis`] type for more detail.
82 #[derive(Clone)]
83 pub struct AxisCollection<'a> {
84     fvar: Option<Fvar<'a>>,
85     avar: Option<Avar<'a>>,
86 }
87 
88 impl<'a> AxisCollection<'a> {
89     /// Creates a new axis collection from the given font.
new(font: &impl TableProvider<'a>) -> Self90     pub fn new(font: &impl TableProvider<'a>) -> Self {
91         let fvar = font.fvar().ok();
92         let avar = font.avar().ok();
93         Self { fvar, avar }
94     }
95 
96     /// Returns the number of variation axes in the font.
len(&self) -> usize97     pub fn len(&self) -> usize {
98         self.fvar
99             .as_ref()
100             .map(|fvar| fvar.axis_count() as usize)
101             .unwrap_or(0)
102     }
103 
104     /// Returns true if the collection is empty.
is_empty(&self) -> bool105     pub fn is_empty(&self) -> bool {
106         self.len() == 0
107     }
108 
109     /// Returns the axis at the given index.
get(&self, index: usize) -> Option<Axis>110     pub fn get(&self, index: usize) -> Option<Axis> {
111         let raw = self.fvar.as_ref()?.axes().ok()?.get(index)?.clone();
112         Some(Axis { index, record: raw })
113     }
114 
115     /// Returns the axis with the given tag.
116     ///
117     /// # Examples
118     ///
119     /// ```rust
120     /// # use skrifa::prelude::*;
121     /// # fn wrapper(font: &FontRef) {
122     /// let opsz = Tag::new(b"opsz");
123     /// assert_eq!(font.axes().get_by_tag(opsz).unwrap().tag(), opsz);
124     /// # }
125     /// ```
get_by_tag(&self, tag: Tag) -> Option<Axis>126     pub fn get_by_tag(&self, tag: Tag) -> Option<Axis> {
127         self.iter().find(|axis| axis.tag() == tag)
128     }
129 
130     /// Given an iterator of variation settings in user space, computes an
131     /// ordered sequence of normalized coordinates.
132     ///
133     /// * Setting selectors that don't match an axis are ignored.
134     /// * Setting values are clamped to the range of their associated axis
135     ///     before normalization.
136     /// * If more than one setting for an axis is provided, the last one is
137     ///     used.
138     /// * Omitted settings are set to 0.0, representing the default position
139     ///     in variation space.
140     ///
141     /// # Examples
142     ///
143     /// ```rust
144     /// # use skrifa::prelude::*;
145     /// # fn wrapper(font: &FontRef) {
146     /// let location = font.axes().location([("wght", 250.0), ("wdth", 75.0)]);
147     /// # }
148     /// ```
location<I>(&self, settings: I) -> Location where I: IntoIterator, I::Item: Into<VariationSetting>,149     pub fn location<I>(&self, settings: I) -> Location
150     where
151         I: IntoIterator,
152         I::Item: Into<VariationSetting>,
153     {
154         let mut location = Location::new(self.len());
155         self.location_to_slice(settings, location.coords_mut());
156         location
157     }
158 
159     /// Given an iterator of variation settings in user space, computes an
160     /// ordered sequence of normalized coordinates and stores them in the
161     /// target slice.
162     ///
163     /// * Setting selectors that don't match an axis are ignored.
164     /// * Setting values are clamped to the range of their associated axis
165     ///     before normalization.
166     /// * If more than one setting for an axis is provided, the last one is
167     ///     used.
168     /// * If no setting for an axis is provided, the associated coordinate is
169     ///     set to the normalized value 0.0, representing the default position
170     ///     in variation space.
171     ///
172     /// # Examples
173     ///
174     /// ```rust
175     /// # use skrifa::prelude::*;
176     /// # fn wrapper(font: &FontRef) {
177     /// let axes = font.axes();
178     /// let mut location = vec![NormalizedCoord::default(); axes.len()];
179     /// axes.location_to_slice([("wght", 250.0), ("wdth", 75.0)], &mut location);
180     /// # }
181     /// ```
location_to_slice<I>(&self, settings: I, location: &mut [NormalizedCoord]) where I: IntoIterator, I::Item: Into<VariationSetting>,182     pub fn location_to_slice<I>(&self, settings: I, location: &mut [NormalizedCoord])
183     where
184         I: IntoIterator,
185         I::Item: Into<VariationSetting>,
186     {
187         for coord in location.iter_mut() {
188             *coord = NormalizedCoord::default();
189         }
190         let avar_mappings = self.avar.as_ref().map(|avar| avar.axis_segment_maps());
191         for setting in settings.into_iter() {
192             let setting = setting.into();
193             // To permit non-linear interpolation, iterate over all axes to ensure we match
194             // multiple axes with the same tag:
195             // https://github.com/PeterConstable/OT_Drafts/blob/master/NLI/UnderstandingNLI.md
196             // We accept quadratic behavior here to avoid dynamic allocation and with the assumption
197             // that fonts contain a relatively small number of axes.
198             for (i, axis) in self
199                 .iter()
200                 .enumerate()
201                 .filter(|v| v.1.tag() == setting.selector)
202             {
203                 if let Some(target_coord) = location.get_mut(i) {
204                     let coord = axis.record.normalize(Fixed::from_f64(setting.value as f64));
205                     *target_coord = avar_mappings
206                         .as_ref()
207                         .and_then(|mappings| mappings.get(i).transpose().ok())
208                         .flatten()
209                         .map(|mapping| mapping.apply(coord))
210                         .unwrap_or(coord)
211                         .to_f2dot14();
212                 }
213             }
214         }
215     }
216 
217     /// Given an iterator of variation settings in user space, returns a
218     /// new iterator yielding those settings that are valid for this axis
219     /// collection.
220     ///
221     /// * Setting selectors that don't match an axis are dropped.
222     /// * If more than one setting for an axis is provided, the last one is
223     ///     retained.
224     /// * Setting values are clamped to the range of their associated axis.
225     ///
226     /// # Examples
227     ///
228     /// ```rust
229     /// # use skrifa::prelude::*;
230     /// # fn wrapper(font: &FontRef) {
231     /// // Assuming a font contains a single "wght" (weight) axis with range
232     /// // 100-900:
233     /// let axes = font.axes();
234     /// let filtered: Vec<_> = axes
235     ///     .filter([("wght", 400.0), ("opsz", 100.0), ("wght", 1200.0)])
236     ///     .collect();
237     /// // The first "wght" and "opsz" settings are dropped and the final
238     /// // "wght" axis is clamped to the maximum value of 900.
239     /// assert_eq!(&filtered, &[("wght", 900.0).into()]);
240     /// # }
241     /// ```
filter<I>(&self, settings: I) -> impl Iterator<Item = VariationSetting> + Clone where I: IntoIterator, I::Item: Into<VariationSetting>,242     pub fn filter<I>(&self, settings: I) -> impl Iterator<Item = VariationSetting> + Clone
243     where
244         I: IntoIterator,
245         I::Item: Into<VariationSetting>,
246     {
247         #[derive(Copy, Clone, Default)]
248         struct Entry {
249             tag: Tag,
250             min: f32,
251             max: f32,
252             value: f32,
253             present: bool,
254         }
255         let mut results = SmallArray::<_, 8>::new(Entry::default(), self.len());
256         for (axis, result) in self.iter().zip(results.as_mut_slice()) {
257             result.tag = axis.tag();
258             result.min = axis.min_value();
259             result.max = axis.max_value();
260             result.value = axis.default_value();
261         }
262         for setting in settings {
263             let setting = setting.into();
264             for entry in results.as_mut_slice() {
265                 if entry.tag == setting.selector {
266                     entry.value = setting.value.max(entry.min).min(entry.max);
267                     entry.present = true;
268                 }
269             }
270         }
271         results
272             .into_iter()
273             .filter(|entry| entry.present)
274             .map(|entry| VariationSetting::new(entry.tag, entry.value))
275     }
276 
277     /// Returns an iterator over the axes in the collection.
iter(&self) -> impl Iterator<Item = Axis> + 'a + Clone278     pub fn iter(&self) -> impl Iterator<Item = Axis> + 'a + Clone {
279         let copy = self.clone();
280         (0..self.len()).filter_map(move |i| copy.get(i))
281     }
282 }
283 
284 /// Named instance of a variation.
285 ///
286 /// A set of fixed axis positions selected by the type designer and assigned a
287 /// name.
288 ///
289 /// See <https://fonts.google.com/knowledge/glossary/instance>
290 #[derive(Clone)]
291 pub struct NamedInstance<'a> {
292     axes: AxisCollection<'a>,
293     record: fvar::InstanceRecord<'a>,
294 }
295 
296 impl<'a> NamedInstance<'a> {
297     /// Returns the string identifier for the subfamily name of the instance.
subfamily_name_id(&self) -> StringId298     pub fn subfamily_name_id(&self) -> StringId {
299         self.record.subfamily_name_id
300     }
301 
302     /// Returns the string identifier for the PostScript name of the instance.
postscript_name_id(&self) -> Option<StringId>303     pub fn postscript_name_id(&self) -> Option<StringId> {
304         self.record.post_script_name_id
305     }
306 
307     /// Returns an iterator over the ordered sequence of user space coordinates
308     /// that define the instance, one coordinate per axis.
user_coords(&self) -> impl Iterator<Item = f32> + 'a + Clone309     pub fn user_coords(&self) -> impl Iterator<Item = f32> + 'a + Clone {
310         self.record
311             .coordinates
312             .iter()
313             .map(|coord| coord.get().to_f64() as _)
314     }
315 
316     /// Computes a location in normalized variation space for this instance.
317     ///
318     /// # Examples
319     ///
320     /// ```rust
321     /// # use skrifa::prelude::*;
322     /// # fn wrapper(font: &FontRef) {
323     /// let location = font.named_instances().get(0).unwrap().location();
324     /// # }
325     /// ```
location(&self) -> Location326     pub fn location(&self) -> Location {
327         let mut location = Location::new(self.axes.len());
328         self.location_to_slice(location.coords_mut());
329         location
330     }
331 
332     /// Computes a location in normalized variation space for this instance and
333     /// stores the result in the given slice.
334     ///
335     /// # Examples
336     ///
337     /// ```rust
338     /// # use skrifa::prelude::*;
339     /// # fn wrapper(font: &FontRef) {
340     /// let instance = font.named_instances().get(0).unwrap();
341     /// let mut location = vec![NormalizedCoord::default(); instance.user_coords().count()];
342     /// instance.location_to_slice(&mut location);
343     /// # }
344     /// ```
location_to_slice(&self, location: &mut [NormalizedCoord])345     pub fn location_to_slice(&self, location: &mut [NormalizedCoord]) {
346         let settings = self
347             .axes
348             .iter()
349             .map(|axis| axis.tag())
350             .zip(self.user_coords());
351         self.axes.location_to_slice(settings, location);
352     }
353 }
354 
355 /// Collection of named instances in a variable font.
356 ///
357 /// See the [`NamedInstance`] type for more detail.
358 #[derive(Clone)]
359 pub struct NamedInstanceCollection<'a> {
360     axes: AxisCollection<'a>,
361 }
362 
363 impl<'a> NamedInstanceCollection<'a> {
364     /// Creates a new instance collection from the given font.
new(font: &impl TableProvider<'a>) -> Self365     pub fn new(font: &impl TableProvider<'a>) -> Self {
366         Self {
367             axes: AxisCollection::new(font),
368         }
369     }
370 
371     /// Returns the number of instances in the collection.
len(&self) -> usize372     pub fn len(&self) -> usize {
373         self.axes
374             .fvar
375             .as_ref()
376             .map(|fvar| fvar.instance_count() as usize)
377             .unwrap_or(0)
378     }
379 
380     /// Returns true if the collection is empty.
is_empty(&self) -> bool381     pub fn is_empty(&self) -> bool {
382         self.len() == 0
383     }
384 
385     /// Returns the instance at the given index.
get(&self, index: usize) -> Option<NamedInstance<'a>>386     pub fn get(&self, index: usize) -> Option<NamedInstance<'a>> {
387         let record = self.axes.fvar.as_ref()?.instances().ok()?.get(index).ok()?;
388         Some(NamedInstance {
389             axes: self.axes.clone(),
390             record,
391         })
392     }
393 
394     /// Returns an iterator over the instances in the colletion.
iter(&self) -> impl Iterator<Item = NamedInstance<'a>> + 'a + Clone395     pub fn iter(&self) -> impl Iterator<Item = NamedInstance<'a>> + 'a + Clone {
396         let copy = self.clone();
397         (0..self.len()).filter_map(move |i| copy.get(i))
398     }
399 }
400 
401 #[cfg(test)]
402 mod tests {
403     use super::*;
404     use crate::MetadataProvider as _;
405     use font_test_data::VAZIRMATN_VAR;
406     use read_fonts::FontRef;
407     use std::str::FromStr;
408 
409     #[test]
axis()410     fn axis() {
411         let font = FontRef::from_index(VAZIRMATN_VAR, 0).unwrap();
412         let axis = font.axes().get(0).unwrap();
413         assert_eq!(axis.index(), 0);
414         assert_eq!(axis.tag(), Tag::new(b"wght"));
415         assert_eq!(axis.min_value(), 100.0);
416         assert_eq!(axis.default_value(), 400.0);
417         assert_eq!(axis.max_value(), 900.0);
418         assert_eq!(axis.name_id(), StringId::new(257));
419         assert_eq!(
420             font.localized_strings(axis.name_id())
421                 .english_or_first()
422                 .unwrap()
423                 .to_string(),
424             "Weight"
425         );
426     }
427 
428     #[test]
named_instances()429     fn named_instances() {
430         let font = FontRef::from_index(VAZIRMATN_VAR, 0).unwrap();
431         let named_instances = font.named_instances();
432         let thin = named_instances.get(0).unwrap();
433         assert_eq!(thin.subfamily_name_id(), StringId::new(258));
434         assert_eq!(
435             font.localized_strings(thin.subfamily_name_id())
436                 .english_or_first()
437                 .unwrap()
438                 .to_string(),
439             "Thin"
440         );
441         assert_eq!(thin.location().coords(), &[NormalizedCoord::from_f32(-1.0)]);
442         let regular = named_instances.get(3).unwrap();
443         assert_eq!(regular.subfamily_name_id(), StringId::new(261));
444         assert_eq!(
445             font.localized_strings(regular.subfamily_name_id())
446                 .english_or_first()
447                 .unwrap()
448                 .to_string(),
449             "Regular"
450         );
451         assert_eq!(
452             regular.location().coords(),
453             &[NormalizedCoord::from_f32(0.0)]
454         );
455         let bold = named_instances.get(6).unwrap();
456         assert_eq!(bold.subfamily_name_id(), StringId::new(264));
457         assert_eq!(
458             font.localized_strings(bold.subfamily_name_id())
459                 .english_or_first()
460                 .unwrap()
461                 .to_string(),
462             "Bold"
463         );
464         assert_eq!(
465             bold.location().coords(),
466             &[NormalizedCoord::from_f32(0.6776123)]
467         );
468     }
469 
470     #[test]
location()471     fn location() {
472         let font = FontRef::from_index(VAZIRMATN_VAR, 0).unwrap();
473         let axes = font.axes();
474         let axis = axes.get_by_tag(Tag::from_str("wght").unwrap()).unwrap();
475         assert_eq!(
476             axes.location([("wght", -1000.0)]).coords(),
477             &[NormalizedCoord::from_f32(-1.0)]
478         );
479         assert_eq!(
480             axes.location([("wght", 100.0)]).coords(),
481             &[NormalizedCoord::from_f32(-1.0)]
482         );
483         assert_eq!(
484             axes.location([("wght", 200.0)]).coords(),
485             &[NormalizedCoord::from_f32(-0.5)]
486         );
487         assert_eq!(
488             axes.location([("wght", 400.0)]).coords(),
489             &[NormalizedCoord::from_f32(0.0)]
490         );
491         // avar table maps 0.8 to 0.83875
492         assert_eq!(
493             axes.location(&[(
494                 "wght",
495                 axis.default_value() + (axis.max_value() - axis.default_value()) * 0.8,
496             )])
497             .coords(),
498             &[NormalizedCoord::from_f32(0.83875)]
499         );
500         assert_eq!(
501             axes.location([("wght", 900.0)]).coords(),
502             &[NormalizedCoord::from_f32(1.0)]
503         );
504         assert_eq!(
505             axes.location([("wght", 1251.5)]).coords(),
506             &[NormalizedCoord::from_f32(1.0)]
507         );
508     }
509 
510     #[test]
filter()511     fn filter() {
512         let font = FontRef::from_index(VAZIRMATN_VAR, 0).unwrap();
513         // This font contains one wght axis with the range 100-900 and default
514         // value of 400.
515         let axes = font.axes();
516         // Drop axes that are not present in the font
517         let drop_missing: Vec<_> = axes.filter(&[("slnt", 25.0), ("wdth", 50.0)]).collect();
518         assert_eq!(&drop_missing, &[]);
519         // Clamp an out of range value
520         let clamp: Vec<_> = axes.filter(&[("wght", 50.0)]).collect();
521         assert_eq!(&clamp, &[("wght", 100.0).into()]);
522         // Combination of the above two: drop the missing axis and clamp out of range value
523         let drop_missing_and_clamp: Vec<_> =
524             axes.filter(&[("slnt", 25.0), ("wght", 1000.0)]).collect();
525         assert_eq!(&drop_missing_and_clamp, &[("wght", 900.0).into()]);
526         // Ensure we take the later value in the case of duplicates
527         let drop_duplicate_and_missing: Vec<_> = axes
528             .filter(&[("wght", 400.0), ("opsz", 100.0), ("wght", 120.5)])
529             .collect();
530         assert_eq!(&drop_duplicate_and_missing, &[("wght", 120.5).into()]);
531     }
532 }
533