use super::variations::{DeltaSetIndex, ItemVariationStore};
use types::F48Dot16;
pub mod tags {
use font_types::Tag;
pub const HASC: Tag = Tag::new(b"hasc");
pub const HDSC: Tag = Tag::new(b"hdsc");
pub const HLGP: Tag = Tag::new(b"hlgp");
pub const HCLA: Tag = Tag::new(b"hcla");
pub const HCLD: Tag = Tag::new(b"hcld");
pub const VASC: Tag = Tag::new(b"vasc");
pub const VDSC: Tag = Tag::new(b"vdsc");
pub const VLGP: Tag = Tag::new(b"vlgp");
pub const HCRS: Tag = Tag::new(b"hcrs");
pub const HCRN: Tag = Tag::new(b"hcrn");
pub const HCOF: Tag = Tag::new(b"hcof");
pub const VCRS: Tag = Tag::new(b"vcrs");
pub const VCRN: Tag = Tag::new(b"vcrn");
pub const VCOF: Tag = Tag::new(b"vcof");
pub const XHGT: Tag = Tag::new(b"xhgt");
pub const CPHT: Tag = Tag::new(b"cpht");
pub const SBXO: Tag = Tag::new(b"sbxo");
pub const SBYO: Tag = Tag::new(b"sbyo");
pub const SBXS: Tag = Tag::new(b"sbxs");
pub const SBYS: Tag = Tag::new(b"sbys");
pub const SPXO: Tag = Tag::new(b"spxo");
pub const SPYO: Tag = Tag::new(b"spyo");
pub const SPXS: Tag = Tag::new(b"spxs");
pub const SPYS: Tag = Tag::new(b"spys");
pub const STRS: Tag = Tag::new(b"strs");
pub const STRO: Tag = Tag::new(b"stro");
pub const UNDS: Tag = Tag::new(b"unds");
pub const UNDO: Tag = Tag::new(b"undo");
pub const GSP0: Tag = Tag::new(b"gsp0");
pub const GSP1: Tag = Tag::new(b"gsp1");
pub const GSP2: Tag = Tag::new(b"gsp2");
pub const GSP3: Tag = Tag::new(b"gsp3");
pub const GSP4: Tag = Tag::new(b"gsp4");
pub const GSP5: Tag = Tag::new(b"gsp5");
pub const GSP6: Tag = Tag::new(b"gsp6");
pub const GSP7: Tag = Tag::new(b"gsp7");
pub const GSP8: Tag = Tag::new(b"gsp8");
pub const GSP9: Tag = Tag::new(b"gsp9");
}
include!("../../generated/generated_mvar.rs");
pub struct MvarInstance<'a> {
records: &'a [ValueRecord],
ivs: ItemVariationStore<'a>,
coords: &'a [F2Dot14],
}
impl<'a> Mvar<'a> {
pub fn at(&self, coords: &'a [F2Dot14]) -> Option<MvarInstance<'a>> {
if coords.is_empty() {
return None;
}
Some(MvarInstance {
records: self.value_records(),
ivs: self.item_variation_store()?.ok()?,
coords,
})
}
}
impl MvarInstance<'_> {
pub fn get(&self, tag: Tag) -> Option<F48Dot16> {
let index = self
.records
.binary_search_by(|record| record.value_tag().cmp(&tag))
.ok()?;
let record = &self.records[index];
self.ivs
.compute_delta(
DeltaSetIndex {
outer: record.delta_set_outer_index(),
inner: record.delta_set_inner_index(),
},
self.coords,
)
.ok()
}
}
impl Mvar<'_> {
pub fn metric_delta(&self, tag: Tag, coords: &[F2Dot14]) -> Result<Fixed, ReadError> {
use std::cmp::Ordering;
let records = self.value_records();
let mut lo = 0;
let mut hi = records.len();
while lo < hi {
let i = (lo + hi) / 2;
let record = &records[i];
match tag.cmp(&record.value_tag()) {
Ordering::Less => {
hi = i;
}
Ordering::Greater => {
lo = i + 1;
}
Ordering::Equal => {
let ivs = self.item_variation_store().ok_or(ReadError::NullOffset)??;
return Ok(Fixed::from_i32(
ivs.compute_delta(
DeltaSetIndex {
outer: record.delta_set_outer_index(),
inner: record.delta_set_inner_index(),
},
coords,
)?
.to_i32(),
));
}
}
}
Err(ReadError::MetricIsMissing(tag))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{FontRef, TableProvider};
use types::F2Dot14;
const VAR: &[u8] = font_test_data::AMSTELVAR_AVAR2_A;
fn far() -> [F2Dot14; 12] {
[F2Dot14::from_f32(1.0); 12]
}
#[test]
fn the_default_location_has_no_instance() {
let font = FontRef::new(VAR).unwrap();
assert!(font.mvar().unwrap().at(&[]).is_none());
}
#[test]
fn an_instance_agrees_with_the_single_metric_accessor() {
let font = FontRef::new(VAR).unwrap();
let mvar = font.mvar().unwrap();
let coords = far();
let instance = mvar.at(&coords).expect("a location away from the default");
let mut varied = 0;
for record in mvar.value_records() {
let tag = record.value_tag();
let exact = instance.get(tag).expect("a tag the table states");
let rounded = mvar.metric_delta(tag, &coords).unwrap();
assert_eq!(exact.to_i32(), rounded.to_i32(), "{tag}");
varied += (exact != F48Dot16::ZERO) as u32;
}
assert!(varied > 0, "no metric moved at the far end of every axis");
}
#[test]
fn a_tag_the_font_does_not_state_is_absent() {
let font = FontRef::new(VAR).unwrap();
let mvar = font.mvar().unwrap();
let coords = far();
let instance = mvar.at(&coords).unwrap();
assert!(instance.get(Tag::new(b"zzzz")).is_none());
}
#[test]
fn an_instance_keeps_a_fraction_the_single_accessor_drops() {
let font = FontRef::new(VAR).unwrap();
let mvar = font.mvar().unwrap();
let coords = [F2Dot14::from_f32(0.37); 12];
let instance = mvar.at(&coords).unwrap();
let fractional = mvar
.value_records()
.iter()
.filter_map(|record| instance.get(record.value_tag()))
.any(|delta| delta.to_bits() & 0xFFFF != 0);
assert!(fractional, "no metric delta carried a fraction");
}
}