use crate::parser::{read_u16, read_u32};
use crate::Error;
const ITEM_VARIATION_STORE_FORMAT: u16 = 1;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RegionAxisCoordinates {
pub start: f32,
pub peak: f32,
pub end: f32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct VariationRegion {
pub region_axes: Vec<RegionAxisCoordinates>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ItemVariationData {
pub item_count: u16,
pub short_delta_count: u16,
pub region_indexes: Vec<u16>,
}
impl ItemVariationData {
pub fn region_count(&self) -> usize {
self.region_indexes.len()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ItemVariationStore {
pub axis_count: u16,
pub regions: Vec<VariationRegion>,
pub item_variation_data: Vec<ItemVariationData>,
}
impl ItemVariationStore {
pub fn parse_variation_store(bytes: &[u8], offset: usize) -> Result<Self, Error> {
let length = read_u16(bytes, offset)? as usize;
let ivs_start = offset
.checked_add(2)
.ok_or(Error::Cff("CFF2 VariationStore offset overflow"))?;
let ivs_end = ivs_start
.checked_add(length)
.ok_or(Error::Cff("CFF2 VariationStore length overflow"))?;
if ivs_end > bytes.len() {
return Err(Error::UnexpectedEof);
}
Self::parse(&bytes[ivs_start..ivs_end])
}
pub fn parse(ivs: &[u8]) -> Result<Self, Error> {
let format = read_u16(ivs, 0)?;
if format != ITEM_VARIATION_STORE_FORMAT {
return Err(Error::Cff("CFF2 ItemVariationStore format must be 1"));
}
let region_list_offset = read_u32(ivs, 2)? as usize;
let ivd_count = read_u16(ivs, 6)? as usize;
let mut ivd_offsets = Vec::with_capacity(ivd_count);
for i in 0..ivd_count {
let off = 8usize
.checked_add(i * 4)
.ok_or(Error::Cff("CFF2 IVS subtable offset table overflow"))?;
ivd_offsets.push(read_u32(ivs, off)? as usize);
}
let (axis_count, regions) = Self::parse_region_list(ivs, region_list_offset)?;
let mut item_variation_data = Vec::with_capacity(ivd_count);
for &ivd_off in &ivd_offsets {
item_variation_data.push(Self::parse_item_variation_data(
ivs,
ivd_off,
regions.len(),
)?);
}
Ok(Self {
axis_count,
regions,
item_variation_data,
})
}
fn parse_region_list(ivs: &[u8], offset: usize) -> Result<(u16, Vec<VariationRegion>), Error> {
let axis_count = read_u16(ivs, offset)?;
let region_count = read_u16(ivs, offset + 2)? as usize;
let axis_count_usize = axis_count as usize;
let mut regions = Vec::with_capacity(region_count);
let mut cursor = offset
.checked_add(4)
.ok_or(Error::Cff("CFF2 VariationRegionList offset overflow"))?;
for _ in 0..region_count {
let mut region_axes = Vec::with_capacity(axis_count_usize);
for _ in 0..axis_count_usize {
let start = read_f2dot14(ivs, cursor)?;
let peak = read_f2dot14(ivs, cursor + 2)?;
let end = read_f2dot14(ivs, cursor + 4)?;
region_axes.push(RegionAxisCoordinates { start, peak, end });
cursor = cursor
.checked_add(6)
.ok_or(Error::Cff("CFF2 VariationRegion extent overflow"))?;
}
regions.push(VariationRegion { region_axes });
}
Ok((axis_count, regions))
}
fn parse_item_variation_data(
ivs: &[u8],
offset: usize,
region_total: usize,
) -> Result<ItemVariationData, Error> {
let item_count = read_u16(ivs, offset)?;
let short_delta_count = read_u16(ivs, offset + 2)?;
let region_index_count = read_u16(ivs, offset + 4)? as usize;
let mut region_indexes = Vec::with_capacity(region_index_count);
for i in 0..region_index_count {
let off = offset
.checked_add(6 + i * 2)
.ok_or(Error::Cff("CFF2 regionIndexes overflow"))?;
let ri = read_u16(ivs, off)?;
if (ri as usize) >= region_total {
return Err(Error::Cff(
"CFF2 regionIndex out of range of VariationRegionList",
));
}
region_indexes.push(ri);
}
Ok(ItemVariationData {
item_count,
short_delta_count,
region_indexes,
})
}
pub fn item_variation_data_count(&self) -> usize {
self.item_variation_data.len()
}
pub fn item_variation_data_at(&self, ivd: usize) -> Option<&ItemVariationData> {
self.item_variation_data.get(ivd)
}
}
#[inline]
fn read_f2dot14(bytes: &[u8], off: usize) -> Result<f32, Error> {
let raw = read_u16(bytes, off)? as i16;
Ok(raw as f32 / 16384.0)
}
#[cfg(test)]
mod tests {
use super::*;
fn spec_example_variation_store() -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&[0x00, 0x26]);
v.extend_from_slice(&[0x00, 0x01]); v.extend_from_slice(&[0x00, 0x00, 0x00, 0x0C]); v.extend_from_slice(&[0x00, 0x01]); v.extend_from_slice(&[0x00, 0x00, 0x00, 0x1C]); v.extend_from_slice(&[0x00, 0x01]); v.extend_from_slice(&[0x00, 0x02]); v.extend_from_slice(&[0xC0, 0x00]); v.extend_from_slice(&[0xE0, 0x00]); v.extend_from_slice(&[0x00, 0x00]); v.extend_from_slice(&[0xC0, 0x00]); v.extend_from_slice(&[0xC0, 0x00]); v.extend_from_slice(&[0xE0, 0x00]); v.extend_from_slice(&[0x00, 0x00]); v.extend_from_slice(&[0x00, 0x00]); v.extend_from_slice(&[0x00, 0x02]); v.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]); v
}
#[test]
fn parses_spec_example_via_wrapper() {
let v = spec_example_variation_store();
let ivs = ItemVariationStore::parse_variation_store(&v, 0).expect("parse");
assert_eq!(ivs.axis_count, 1);
assert_eq!(ivs.regions.len(), 2);
assert_eq!(ivs.item_variation_data_count(), 1);
let r0 = &ivs.regions[0].region_axes[0];
assert_eq!(
*r0,
RegionAxisCoordinates {
start: -1.0,
peak: -0.5,
end: 0.0
}
);
let r1 = &ivs.regions[1].region_axes[0];
assert_eq!(
*r1,
RegionAxisCoordinates {
start: -1.0,
peak: -1.0,
end: -0.5
}
);
let ivd = ivs.item_variation_data_at(0).unwrap();
assert_eq!(ivd.item_count, 0);
assert_eq!(ivd.short_delta_count, 0);
assert_eq!(ivd.region_indexes, vec![0, 1]);
assert_eq!(ivd.region_count(), 2); }
#[test]
fn parses_bare_ivs() {
let v = spec_example_variation_store();
let ivs = ItemVariationStore::parse(&v[2..]).expect("parse");
assert_eq!(ivs.axis_count, 1);
assert_eq!(ivs.regions.len(), 2);
assert_eq!(ivs.item_variation_data[0].region_indexes, vec![0, 1]);
}
#[test]
fn f2dot14_decode() {
assert_eq!(read_f2dot14(&[0xC0, 0x00], 0).unwrap(), -1.0);
assert_eq!(read_f2dot14(&[0xE0, 0x00], 0).unwrap(), -0.5);
assert_eq!(read_f2dot14(&[0x00, 0x00], 0).unwrap(), 0.0);
assert_eq!(read_f2dot14(&[0x40, 0x00], 0).unwrap(), 1.0);
assert_eq!(read_f2dot14(&[0x20, 0x00], 0).unwrap(), 0.5);
let v = read_f2dot14(&[0x7F, 0xFF], 0).unwrap();
assert!((v - 1.999_939).abs() < 1e-6);
}
#[test]
fn rejects_bad_format() {
let mut v = spec_example_variation_store();
v[2] = 0x00;
v[3] = 0x02;
let err = ItemVariationStore::parse_variation_store(&v, 0).unwrap_err();
match err {
Error::Cff(s) => assert!(s.contains("format must be 1")),
_ => panic!("unexpected: {err:?}"),
}
}
#[test]
fn rejects_region_index_out_of_range() {
let mut v = spec_example_variation_store();
let n = v.len();
v[n - 1] = 0x05;
let err = ItemVariationStore::parse_variation_store(&v, 0).unwrap_err();
match err {
Error::Cff(s) => assert!(s.contains("out of range")),
_ => panic!("unexpected: {err:?}"),
}
}
#[test]
fn rejects_length_past_eof() {
let mut v = spec_example_variation_store();
v[0] = 0xFF;
v[1] = 0xFF;
let err = ItemVariationStore::parse_variation_store(&v, 0).unwrap_err();
assert!(matches!(err, Error::UnexpectedEof));
}
#[test]
fn multiple_item_variation_data_subtables() {
let mut v = Vec::new();
v.extend_from_slice(&[0x00, 0x01]); v.extend_from_slice(&[0x00, 0x00, 0x00, 0x10]); v.extend_from_slice(&[0x00, 0x02]); v.extend_from_slice(&[0x00, 0x00, 0x00, 0x26]); v.extend_from_slice(&[0x00, 0x00, 0x00, 0x2E]); assert_eq!(v.len(), 16);
v.extend_from_slice(&[0x00, 0x01]); v.extend_from_slice(&[0x00, 0x03]); for _ in 0..3 {
v.extend_from_slice(&[0x40, 0x00, 0x40, 0x00, 0x40, 0x00]); }
assert_eq!(v.len(), 38);
v.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00]);
assert_eq!(v.len(), 46);
v.extend_from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x02]);
let ivs = ItemVariationStore::parse(&v).expect("parse");
assert_eq!(ivs.regions.len(), 3);
assert_eq!(ivs.item_variation_data_count(), 2);
assert_eq!(
ivs.item_variation_data_at(0).unwrap().region_indexes,
vec![0]
);
assert_eq!(
ivs.item_variation_data_at(1).unwrap().region_indexes,
vec![1, 2]
);
assert!(ivs.item_variation_data_at(2).is_none());
}
}