1use super::Font;
9use crate::{
10 ps::type1::MultipleMaster,
11 tables::fvar::{Fvar, VariationAxisRecord},
12 types::{BigEndian, Fixed, NameId, Tag},
13 FontData, FontRead, TableProvider,
14};
15use alloc::boxed::Box;
16use core::str::FromStr;
17use smallvec::SmallVec;
18
19pub type NormalizedCoord = types::F2Dot14;
22
23#[derive(Copy, Clone, PartialEq, Debug)]
28pub struct Variation {
29 pub tag: Tag,
31 pub value: f32,
33}
34
35impl Variation {
36 pub fn new(tag: Tag, value: f32) -> Self {
38 Self { tag, value }
39 }
40}
41
42impl From<&'_ Variation> for Variation {
48 fn from(value: &'_ Variation) -> Self {
49 *value
50 }
51}
52
53impl From<(Tag, f32)> for Variation {
54 fn from(value: (Tag, f32)) -> Self {
55 Self::new(value.0, value.1)
56 }
57}
58
59impl From<&(Tag, f32)> for Variation {
60 fn from(value: &(Tag, f32)) -> Self {
61 Self::new(value.0, value.1)
62 }
63}
64
65impl From<(&str, f32)> for Variation {
66 fn from(value: (&str, f32)) -> Self {
67 Self::new(Tag::from_str(value.0).unwrap_or_default(), value.1)
68 }
69}
70
71impl From<&(&str, f32)> for Variation {
72 fn from(value: &(&str, f32)) -> Self {
73 Self::new(Tag::from_str(value.0).unwrap_or_default(), value.1)
74 }
75}
76
77#[derive(Copy, Clone, Debug)]
82pub struct Axis {
83 index: u32,
84 record: VariationAxisRecord,
85}
86
87impl Axis {
88 pub fn tag(&self) -> Tag {
90 self.record.axis_tag()
91 }
92
93 pub fn index(&self) -> u32 {
95 self.index
96 }
97
98 pub fn name(&self) -> NameId {
100 self.record.axis_name_id()
101 }
102
103 pub fn is_hidden(&self) -> bool {
105 const HIDDEN_AXIS: u16 = 0x1;
106 self.record.flags() & HIDDEN_AXIS != 0
107 }
108
109 pub fn min_value(&self) -> f32 {
111 self.record.min_value().to_f32()
112 }
113
114 pub fn default_value(&self) -> f32 {
116 self.record.default_value().to_f32()
117 }
118
119 pub fn max_value(&self) -> f32 {
121 self.record.max_value().to_f32()
122 }
123
124 pub(crate) fn clamp(&self, value: f32) -> f32 {
128 let min = self.min_value();
129 let max = self.max_value().max(min);
130 value.max(min).min(max)
131 }
132}
133
134#[derive(Clone, Copy)]
138pub struct Axes<'a> {
139 records: &'a [VariationAxisRecord],
140}
141
142impl<'a> Axes<'a> {
143 pub(crate) fn new(font: &'a Font) -> Self {
144 let records = match font.type1_axes() {
145 Some(records) => records,
146 None => fvar(font)
147 .and_then(|fvar| fvar.axes().ok())
148 .unwrap_or_default(),
149 };
150 Self { records }
151 }
152
153 pub fn len(&self) -> u32 {
155 self.records.len() as u32
156 }
157
158 pub fn is_empty(&self) -> bool {
160 self.records.is_empty()
161 }
162
163 pub fn get(&self, index: u32) -> Option<Axis> {
165 let record = *self.records.get(index as usize)?;
166 Some(Axis { index, record })
167 }
168
169 pub fn get_by_tag(&self, tag: Tag) -> Option<Axis> {
171 self.iter().find(|axis| axis.tag() == tag)
172 }
173
174 pub fn filter<V>(&self, variations: V) -> impl Iterator<Item = Variation> + Clone
197 where
198 V: IntoIterator,
199 V::Item: Into<Variation>,
200 {
201 let mut values: SmallVec<[(Axis, Option<f32>); 8]> =
203 self.iter().map(|axis| (axis, None)).collect();
204 for variation in variations {
205 let variation = variation.into();
206 for (axis, value) in values.iter_mut() {
207 if axis.tag() == variation.tag {
208 *value = Some(axis.clamp(variation.value));
209 }
210 }
211 }
212 values
213 .into_iter()
214 .filter_map(|(axis, value)| Some(Variation::new(axis.tag(), value?)))
215 }
216
217 pub fn iter(&self) -> impl Iterator<Item = Axis> + 'a + Clone {
219 self.records.iter().enumerate().map(|(index, record)| Axis {
220 index: index as u32,
221 record: *record,
222 })
223 }
224}
225
226#[derive(Clone, Copy)]
235pub struct NamedInstance<'a> {
236 index: u32,
237 axes: &'a [VariationAxisRecord],
238 coordinates: &'a [BigEndian<Fixed>],
239 subfamily_name: NameId,
240 postscript_name: Option<NameId>,
241}
242
243impl<'a> NamedInstance<'a> {
244 pub fn index(&self) -> u32 {
247 self.index
248 }
249
250 pub fn subfamily_name(&self) -> NameId {
252 self.subfamily_name
253 }
254
255 pub fn postscript_name(&self) -> Option<NameId> {
258 self.postscript_name
259 }
260
261 pub fn user_coords(&self) -> impl Iterator<Item = f32> + 'a + Clone {
263 self.coordinates.iter().map(|coord| coord.get().to_f32())
264 }
265
266 pub fn variations(&self) -> impl Iterator<Item = Variation> + 'a + Clone {
268 self.axes
269 .iter()
270 .zip(self.user_coords())
271 .map(|(axis, value)| Variation::new(axis.axis_tag(), value))
272 }
273}
274
275#[derive(Clone, Copy)]
279pub struct NamedInstances<'a> {
280 fvar: Option<FontData<'a>>,
282}
283
284impl<'a> NamedInstances<'a> {
285 pub(crate) fn new(font: &'a Font) -> Self {
286 Self {
287 fvar: fvar(font).map(|fvar| fvar.offset_data()),
288 }
289 }
290
291 pub fn len(&self) -> u32 {
293 self.fvar().map_or(0, |fvar| fvar.instance_count() as u32)
294 }
295
296 pub fn is_empty(&self) -> bool {
298 self.len() == 0
299 }
300
301 pub fn get(&self, index: u32) -> Option<NamedInstance<'a>> {
303 let fvar = self.fvar()?;
304 let record = fvar.instances().ok()?.get(index as usize).ok()?;
305 Some(NamedInstance {
306 index,
307 axes: fvar.axes().ok()?,
308 coordinates: record.coordinates,
309 subfamily_name: record.subfamily_name_id,
310 postscript_name: record.post_script_name_id,
311 })
312 }
313
314 fn fvar(&self) -> Option<Fvar<'a>> {
316 Fvar::read(self.fvar?).ok()
317 }
318
319 pub fn iter(&self) -> impl Iterator<Item = NamedInstance<'a>> + 'a + Clone {
321 let instances = *self;
322 (0..self.len()).filter_map(move |index| instances.get(index))
323 }
324}
325
326fn fvar(font: &Font) -> Option<Fvar<'_>> {
330 font.tables().fvar().ok()
331}
332
333pub(crate) fn type1_axis_records(mm: &MultipleMaster) -> Box<[VariationAxisRecord]> {
341 let mut default = [Fixed::ZERO; 4];
342 mm.default_blend(&mut default);
343 mm.axes()
344 .iter()
345 .enumerate()
346 .map(|(index, axis)| {
347 let tag = match axis.name() {
348 Some("Weight") => Tag::new(b"wght"),
349 Some("Width") => Tag::new(b"wdth"),
350 Some("OpticalSize") => Tag::new(b"opsz"),
351 Some("Slant") => Tag::new(b"slnt"),
352 Some("Italic") => Tag::new(b"ital"),
353 _ => Tag::new(&[b'M', b'M', b'A', b'0' + index as u8]),
354 };
355 let design = |value: i32| BigEndian::from(Fixed::from_i32(value.clamp(-32768, 32767)));
357 VariationAxisRecord {
358 axis_tag: BigEndian::from(tag),
359 min_value: design(axis.min_design()),
360 default_value: design(axis.blend_to_design(default[index])),
361 max_value: design(axis.max_design()),
362 flags: BigEndian::from(0),
363 axis_name_id: BigEndian::from(NameId::new(
364 super::name::TYPE1_AXIS_NAMES + index as u16,
365 )),
366 }
367 })
368 .collect()
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use font_test_data::VAZIRMATN_VAR;
375
376 #[test]
377 fn axes() {
378 let font = Font::new(VAZIRMATN_VAR, 0).unwrap();
379 let axes = font.axes();
380 assert_eq!(axes.len(), 1);
381 let axis = axes.get(0).unwrap();
382 assert_eq!(axis.index(), 0);
383 assert_eq!(axis.tag(), Tag::new(b"wght"));
384 assert_eq!(
385 (axis.min_value(), axis.default_value(), axis.max_value()),
386 (100.0, 400.0, 900.0)
387 );
388 assert_eq!(axis.name(), NameId::new(257));
389 assert!(!axis.is_hidden());
390 assert_eq!(
391 axes.get_by_tag(Tag::new(b"wght")).unwrap().index(),
392 axis.index()
393 );
394 assert!(axes.get_by_tag(Tag::new(b"wdth")).is_none());
395 assert!(axes.get(1).is_none());
396 assert_eq!(axes.iter().count(), 1);
397 }
398
399 #[test]
400 fn filter() {
401 let font = Font::new(VAZIRMATN_VAR, 0).unwrap();
402 let axes = font.axes();
404 let filter = |variations: &[(&str, f32)]| axes.filter(variations).collect::<Vec<_>>();
405 assert_eq!(filter(&[("slnt", 25.0), ("wdth", 50.0)]), []);
407 assert_eq!(filter(&[("wght", 50.0)]), [("wght", 100.0).into()]);
409 assert_eq!(
410 filter(&[("slnt", 25.0), ("wght", 1000.0)]),
411 [("wght", 900.0).into()]
412 );
413 assert_eq!(
415 filter(&[("wght", 400.0), ("opsz", 100.0), ("wght", 120.5)]),
416 [("wght", 120.5).into()]
417 );
418 }
419
420 #[test]
423 fn clamp_matches_normalization() {
424 use crate::types::{BigEndian, Fixed};
425 let axis = |min: f32, default: f32, max: f32| Axis {
426 index: 0,
427 record: VariationAxisRecord {
428 axis_tag: BigEndian::from(Tag::new(b"wght")),
429 min_value: BigEndian::from(Fixed::from_f64(min as f64)),
430 default_value: BigEndian::from(Fixed::from_f64(default as f64)),
431 max_value: BigEndian::from(Fixed::from_f64(max as f64)),
432 flags: BigEndian::from(0),
433 axis_name_id: BigEndian::from(NameId::new(256)),
434 },
435 };
436 let inverted = axis(500.0, 400.0, 100.0);
437 assert_eq!(inverted.clamp(300.0), 500.0);
438 assert_eq!(inverted.clamp(700.0), 500.0);
439 for axis in [inverted, axis(100.0, 400.0, 900.0)] {
440 for value in [-1000.0, 0.0, 100.0, 300.0, 400.0, 500.0, 900.0, 2000.0] {
441 let clamped = Fixed::from_f64(axis.clamp(value) as f64);
442 let normalize = |value: Fixed| axis.record.normalize(value);
443 assert_eq!(
444 normalize(clamped),
445 normalize(Fixed::from_f64(value as f64)),
446 "{value}"
447 );
448 }
449 }
450 }
451
452 #[test]
453 fn named_instances() {
454 let font = Font::new(VAZIRMATN_VAR, 0).unwrap();
455 let instances = font.named_instances();
456 assert_eq!(instances.len() as usize, instances.iter().count());
457 let bold = instances.get(6).unwrap();
458 assert_eq!(bold.index(), 6);
459 assert_eq!(bold.subfamily_name(), NameId::new(264));
460 assert_eq!(bold.user_coords().collect::<Vec<_>>(), [700.0]);
461 assert_eq!(
462 bold.variations().collect::<Vec<_>>(),
463 [Variation::new(Tag::new(b"wght"), 700.0)]
464 );
465 assert!(instances.get(instances.len()).is_none());
466 let by_index = font.instance_builder().named_instance(6).build();
468 let by_variations = font
469 .instance_builder()
470 .variations(bold.variations())
471 .build();
472 assert_eq!(
473 by_index.normalized_coords(),
474 by_variations.normalized_coords()
475 );
476 }
477
478 #[test]
479 fn a_static_font_has_no_axes() {
480 let font = Font::new(font_test_data::TINOS_SUBSET, 0).unwrap();
481 assert!(font.axes().is_empty());
482 assert!(font.named_instances().is_empty());
483 assert!(font.named_instances().get(0).is_none());
484 }
485}