use super::Font;
use crate::{
ps::type1::MultipleMaster,
tables::fvar::{Fvar, VariationAxisRecord},
types::{BigEndian, Fixed, NameId, Tag},
FontData, FontRead, TableProvider,
};
use alloc::boxed::Box;
use core::str::FromStr;
use smallvec::SmallVec;
pub type NormalizedCoord = types::F2Dot14;
#[derive(Copy, Clone, PartialEq, Debug)]
pub struct Variation {
pub tag: Tag,
pub value: f32,
}
impl Variation {
pub fn new(tag: Tag, value: f32) -> Self {
Self { tag, value }
}
}
impl From<&'_ Variation> for Variation {
fn from(value: &'_ Variation) -> Self {
*value
}
}
impl From<(Tag, f32)> for Variation {
fn from(value: (Tag, f32)) -> Self {
Self::new(value.0, value.1)
}
}
impl From<&(Tag, f32)> for Variation {
fn from(value: &(Tag, f32)) -> Self {
Self::new(value.0, value.1)
}
}
impl From<(&str, f32)> for Variation {
fn from(value: (&str, f32)) -> Self {
Self::new(Tag::from_str(value.0).unwrap_or_default(), value.1)
}
}
impl From<&(&str, f32)> for Variation {
fn from(value: &(&str, f32)) -> Self {
Self::new(Tag::from_str(value.0).unwrap_or_default(), value.1)
}
}
#[derive(Copy, Clone, Debug)]
pub struct Axis {
index: u32,
record: VariationAxisRecord,
}
impl Axis {
pub fn tag(&self) -> Tag {
self.record.axis_tag()
}
pub fn index(&self) -> u32 {
self.index
}
pub fn name(&self) -> NameId {
self.record.axis_name_id()
}
pub fn is_hidden(&self) -> bool {
const HIDDEN_AXIS: u16 = 0x1;
self.record.flags() & HIDDEN_AXIS != 0
}
pub fn min_value(&self) -> f32 {
self.record.min_value().to_f32()
}
pub fn default_value(&self) -> f32 {
self.record.default_value().to_f32()
}
pub fn max_value(&self) -> f32 {
self.record.max_value().to_f32()
}
pub(crate) fn clamp(&self, value: f32) -> f32 {
let min = self.min_value();
let max = self.max_value().max(min);
value.max(min).min(max)
}
}
#[derive(Clone, Copy)]
pub struct Axes<'a> {
records: &'a [VariationAxisRecord],
}
impl<'a> Axes<'a> {
pub(crate) fn new(font: &'a Font) -> Self {
let records = match font.type1_axes() {
Some(records) => records,
None => fvar(font)
.and_then(|fvar| fvar.axes().ok())
.unwrap_or_default(),
};
Self { records }
}
pub fn len(&self) -> u32 {
self.records.len() as u32
}
pub fn is_empty(&self) -> bool {
self.records.is_empty()
}
pub fn get(&self, index: u32) -> Option<Axis> {
let record = *self.records.get(index as usize)?;
Some(Axis { index, record })
}
pub fn get_by_tag(&self, tag: Tag) -> Option<Axis> {
self.iter().find(|axis| axis.tag() == tag)
}
pub fn filter<V>(&self, variations: V) -> impl Iterator<Item = Variation> + Clone
where
V: IntoIterator,
V::Item: Into<Variation>,
{
let mut values: SmallVec<[(Axis, Option<f32>); 8]> =
self.iter().map(|axis| (axis, None)).collect();
for variation in variations {
let variation = variation.into();
for (axis, value) in values.iter_mut() {
if axis.tag() == variation.tag {
*value = Some(axis.clamp(variation.value));
}
}
}
values
.into_iter()
.filter_map(|(axis, value)| Some(Variation::new(axis.tag(), value?)))
}
pub fn iter(&self) -> impl Iterator<Item = Axis> + 'a + Clone {
self.records.iter().enumerate().map(|(index, record)| Axis {
index: index as u32,
record: *record,
})
}
}
#[derive(Clone, Copy)]
pub struct NamedInstance<'a> {
index: u32,
axes: &'a [VariationAxisRecord],
coordinates: &'a [BigEndian<Fixed>],
subfamily_name: NameId,
postscript_name: Option<NameId>,
}
impl<'a> NamedInstance<'a> {
pub fn index(&self) -> u32 {
self.index
}
pub fn subfamily_name(&self) -> NameId {
self.subfamily_name
}
pub fn postscript_name(&self) -> Option<NameId> {
self.postscript_name
}
pub fn user_coords(&self) -> impl Iterator<Item = f32> + 'a + Clone {
self.coordinates.iter().map(|coord| coord.get().to_f32())
}
pub fn variations(&self) -> impl Iterator<Item = Variation> + 'a + Clone {
self.axes
.iter()
.zip(self.user_coords())
.map(|(axis, value)| Variation::new(axis.axis_tag(), value))
}
}
#[derive(Clone, Copy)]
pub struct NamedInstances<'a> {
fvar: Option<FontData<'a>>,
}
impl<'a> NamedInstances<'a> {
pub(crate) fn new(font: &'a Font) -> Self {
Self {
fvar: fvar(font).map(|fvar| fvar.offset_data()),
}
}
pub fn len(&self) -> u32 {
self.fvar().map_or(0, |fvar| fvar.instance_count() as u32)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, index: u32) -> Option<NamedInstance<'a>> {
let fvar = self.fvar()?;
let record = fvar.instances().ok()?.get(index as usize).ok()?;
Some(NamedInstance {
index,
axes: fvar.axes().ok()?,
coordinates: record.coordinates,
subfamily_name: record.subfamily_name_id,
postscript_name: record.post_script_name_id,
})
}
fn fvar(&self) -> Option<Fvar<'a>> {
Fvar::read(self.fvar?).ok()
}
pub fn iter(&self) -> impl Iterator<Item = NamedInstance<'a>> + 'a + Clone {
let instances = *self;
(0..self.len()).filter_map(move |index| instances.get(index))
}
}
fn fvar(font: &Font) -> Option<Fvar<'_>> {
font.tables().fvar().ok()
}
pub(crate) fn type1_axis_records(mm: &MultipleMaster) -> Box<[VariationAxisRecord]> {
let mut default = [Fixed::ZERO; 4];
mm.default_blend(&mut default);
mm.axes()
.iter()
.enumerate()
.map(|(index, axis)| {
let tag = match axis.name() {
Some("Weight") => Tag::new(b"wght"),
Some("Width") => Tag::new(b"wdth"),
Some("OpticalSize") => Tag::new(b"opsz"),
Some("Slant") => Tag::new(b"slnt"),
Some("Italic") => Tag::new(b"ital"),
_ => Tag::new(&[b'M', b'M', b'A', b'0' + index as u8]),
};
let design = |value: i32| BigEndian::from(Fixed::from_i32(value.clamp(-32768, 32767)));
VariationAxisRecord {
axis_tag: BigEndian::from(tag),
min_value: design(axis.min_design()),
default_value: design(axis.blend_to_design(default[index])),
max_value: design(axis.max_design()),
flags: BigEndian::from(0),
axis_name_id: BigEndian::from(NameId::new(
super::name::TYPE1_AXIS_NAMES + index as u16,
)),
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use font_test_data::VAZIRMATN_VAR;
#[test]
fn axes() {
let font = Font::new(VAZIRMATN_VAR, 0).unwrap();
let axes = font.axes();
assert_eq!(axes.len(), 1);
let axis = axes.get(0).unwrap();
assert_eq!(axis.index(), 0);
assert_eq!(axis.tag(), Tag::new(b"wght"));
assert_eq!(
(axis.min_value(), axis.default_value(), axis.max_value()),
(100.0, 400.0, 900.0)
);
assert_eq!(axis.name(), NameId::new(257));
assert!(!axis.is_hidden());
assert_eq!(
axes.get_by_tag(Tag::new(b"wght")).unwrap().index(),
axis.index()
);
assert!(axes.get_by_tag(Tag::new(b"wdth")).is_none());
assert!(axes.get(1).is_none());
assert_eq!(axes.iter().count(), 1);
}
#[test]
fn filter() {
let font = Font::new(VAZIRMATN_VAR, 0).unwrap();
let axes = font.axes();
let filter = |variations: &[(&str, f32)]| axes.filter(variations).collect::<Vec<_>>();
assert_eq!(filter(&[("slnt", 25.0), ("wdth", 50.0)]), []);
assert_eq!(filter(&[("wght", 50.0)]), [("wght", 100.0).into()]);
assert_eq!(
filter(&[("slnt", 25.0), ("wght", 1000.0)]),
[("wght", 900.0).into()]
);
assert_eq!(
filter(&[("wght", 400.0), ("opsz", 100.0), ("wght", 120.5)]),
[("wght", 120.5).into()]
);
}
#[test]
fn clamp_matches_normalization() {
use crate::types::{BigEndian, Fixed};
let axis = |min: f32, default: f32, max: f32| Axis {
index: 0,
record: VariationAxisRecord {
axis_tag: BigEndian::from(Tag::new(b"wght")),
min_value: BigEndian::from(Fixed::from_f64(min as f64)),
default_value: BigEndian::from(Fixed::from_f64(default as f64)),
max_value: BigEndian::from(Fixed::from_f64(max as f64)),
flags: BigEndian::from(0),
axis_name_id: BigEndian::from(NameId::new(256)),
},
};
let inverted = axis(500.0, 400.0, 100.0);
assert_eq!(inverted.clamp(300.0), 500.0);
assert_eq!(inverted.clamp(700.0), 500.0);
for axis in [inverted, axis(100.0, 400.0, 900.0)] {
for value in [-1000.0, 0.0, 100.0, 300.0, 400.0, 500.0, 900.0, 2000.0] {
let clamped = Fixed::from_f64(axis.clamp(value) as f64);
let normalize = |value: Fixed| axis.record.normalize(value);
assert_eq!(
normalize(clamped),
normalize(Fixed::from_f64(value as f64)),
"{value}"
);
}
}
}
#[test]
fn named_instances() {
let font = Font::new(VAZIRMATN_VAR, 0).unwrap();
let instances = font.named_instances();
assert_eq!(instances.len() as usize, instances.iter().count());
let bold = instances.get(6).unwrap();
assert_eq!(bold.index(), 6);
assert_eq!(bold.subfamily_name(), NameId::new(264));
assert_eq!(bold.user_coords().collect::<Vec<_>>(), [700.0]);
assert_eq!(
bold.variations().collect::<Vec<_>>(),
[Variation::new(Tag::new(b"wght"), 700.0)]
);
assert!(instances.get(instances.len()).is_none());
let by_index = font.instance_builder().named_instance(6).build();
let by_variations = font
.instance_builder()
.variations(bold.variations())
.build();
assert_eq!(
by_index.normalized_coords(),
by_variations.normalized_coords()
);
}
#[test]
fn a_static_font_has_no_axes() {
let font = Font::new(font_test_data::TINOS_SUBSET, 0).unwrap();
assert!(font.axes().is_empty());
assert!(font.named_instances().is_empty());
assert!(font.named_instances().get(0).is_none());
}
}