use crate::parser::read_u16;
use crate::tables::mvar::ItemVariationStore;
use crate::Error;
pub const DELTA_FORMAT_VARIATION_INDEX: u16 = 0x8000;
pub const DELTA_FORMAT_LOCAL_2_BIT: u16 = 0x0001;
pub const DELTA_FORMAT_LOCAL_4_BIT: u16 = 0x0002;
pub const DELTA_FORMAT_LOCAL_8_BIT: u16 = 0x0003;
const MAX_DEVICE_PPEM_SPAN: usize = 4096;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeviceOrVariationIndex {
Variation {
outer: u16,
inner: u16,
},
Device {
start_size: u16,
end_size: u16,
deltas: Vec<i8>,
},
}
impl DeviceOrVariationIndex {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 6 {
return Err(Error::UnexpectedEof);
}
let field0 = read_u16(bytes, 0)?;
let field1 = read_u16(bytes, 2)?;
let delta_format = read_u16(bytes, 4)?;
match delta_format {
DELTA_FORMAT_VARIATION_INDEX => Ok(Self::Variation {
outer: field0,
inner: field1,
}),
DELTA_FORMAT_LOCAL_2_BIT | DELTA_FORMAT_LOCAL_4_BIT | DELTA_FORMAT_LOCAL_8_BIT => {
let start_size = field0;
let end_size = field1;
if end_size < start_size {
return Err(Error::BadStructure("Device endSize < startSize"));
}
let count = (end_size - start_size) as usize + 1;
if count > MAX_DEVICE_PPEM_SPAN {
return Err(Error::BadStructure("Device ppem span exceeds cap"));
}
let bits_per_value = match delta_format {
DELTA_FORMAT_LOCAL_2_BIT => 2u32,
DELTA_FORMAT_LOCAL_4_BIT => 4,
_ => 8,
};
let values_per_word = 16 / bits_per_value as usize;
let word_count = count.div_ceil(values_per_word);
let need = 6 + word_count * 2;
if need > bytes.len() {
return Err(Error::UnexpectedEof);
}
let deltas = unpack_device_deltas(&bytes[6..], count, bits_per_value);
Ok(Self::Device {
start_size,
end_size,
deltas,
})
}
_ => Err(Error::BadStructure("Device deltaFormat reserved/unknown")),
}
}
pub fn is_variation_index(&self) -> bool {
matches!(self, Self::Variation { .. })
}
pub fn pixel_delta(&self, ppem: u16) -> Option<i8> {
match self {
Self::Device {
start_size,
end_size,
deltas,
} => {
if ppem < *start_size || ppem > *end_size {
return None;
}
deltas.get((ppem - start_size) as usize).copied()
}
Self::Variation { .. } => None,
}
}
pub fn font_unit_delta(
&self,
ivs: Option<&ItemVariationStore>,
normalised_coords: &[f32],
) -> Option<f32> {
match self {
Self::Variation { outer, inner } => ivs?.delta(*outer, *inner, normalised_coords),
Self::Device { .. } => Some(0.0),
}
}
}
pub fn resolve_device_delta(
table_bytes: &[u8],
offset: u16,
ivs: Option<&ItemVariationStore>,
normalised_coords: &[f32],
) -> f32 {
if offset == 0 {
return 0.0;
}
let off = offset as usize;
if off >= table_bytes.len() {
return 0.0;
}
DeviceOrVariationIndex::parse(&table_bytes[off..])
.ok()
.and_then(|d| d.font_unit_delta(ivs, normalised_coords))
.unwrap_or(0.0)
}
fn unpack_device_deltas(bytes: &[u8], count: usize, bits_per_value: u32) -> Vec<i8> {
let mut out = Vec::with_capacity(count);
let mask: u16 = ((1u32 << bits_per_value) - 1) as u16;
let sign_bit: u16 = 1 << (bits_per_value - 1);
let values_per_word = 16 / bits_per_value as usize;
for i in 0..count {
let word_idx = i / values_per_word;
let slot = i % values_per_word;
let word_off = word_idx * 2;
let word = read_u16(bytes, word_off).unwrap_or(0);
let shift = 16 - (slot as u32 + 1) * bits_per_value;
let raw = (word >> shift) & mask;
let val = if raw & sign_bit != 0 {
(raw | !mask) as i16
} else {
raw as i16
};
out.push(val as i8);
}
out
}
pub(crate) fn read_device_offset(bytes: &[u8], off: usize) -> u16 {
read_u16(bytes, off).unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_variation_index() {
let mut b = vec![0u8; 6];
b[0..2].copy_from_slice(&3u16.to_be_bytes());
b[2..4].copy_from_slice(&7u16.to_be_bytes());
b[4..6].copy_from_slice(&0x8000u16.to_be_bytes());
let d = DeviceOrVariationIndex::parse(&b).expect("parse");
assert_eq!(d, DeviceOrVariationIndex::Variation { outer: 3, inner: 7 });
assert!(d.is_variation_index());
assert_eq!(d.pixel_delta(12), None);
}
#[test]
fn parses_device_4bit_packed() {
let mut b = vec![0u8; 8];
b[0..2].copy_from_slice(&12u16.to_be_bytes());
b[2..4].copy_from_slice(&15u16.to_be_bytes());
b[4..6].copy_from_slice(&0x0002u16.to_be_bytes());
b[6..8].copy_from_slice(&0x123Fu16.to_be_bytes());
let d = DeviceOrVariationIndex::parse(&b).expect("parse");
assert_eq!(
d,
DeviceOrVariationIndex::Device {
start_size: 12,
end_size: 15,
deltas: vec![1, 2, 3, -1],
}
);
assert_eq!(d.pixel_delta(12), Some(1));
assert_eq!(d.pixel_delta(14), Some(3));
assert_eq!(d.pixel_delta(15), Some(-1));
assert_eq!(d.pixel_delta(11), None);
assert_eq!(d.pixel_delta(16), None);
}
#[test]
fn parses_device_2bit_packed() {
let bits: u16 = 0x7187;
let mut b = vec![0u8; 8];
b[0..2].copy_from_slice(&20u16.to_be_bytes());
b[2..4].copy_from_slice(&27u16.to_be_bytes());
b[4..6].copy_from_slice(&0x0001u16.to_be_bytes());
b[6..8].copy_from_slice(&bits.to_be_bytes());
let d = DeviceOrVariationIndex::parse(&b).expect("parse");
match d {
DeviceOrVariationIndex::Device { deltas, .. } => {
assert_eq!(deltas, vec![1, -1, 0, 1, -2, 0, 1, -1]);
}
_ => panic!("expected Device"),
}
}
#[test]
fn parses_device_8bit_packed() {
let mut b = vec![0u8; 10];
b[0..2].copy_from_slice(&8u16.to_be_bytes());
b[2..4].copy_from_slice(&10u16.to_be_bytes());
b[4..6].copy_from_slice(&0x0003u16.to_be_bytes());
b[6] = 5u8;
b[7] = (-7i8) as u8;
b[8] = 100u8;
b[9] = 0u8;
let d = DeviceOrVariationIndex::parse(&b).expect("parse");
match d {
DeviceOrVariationIndex::Device { deltas, .. } => {
assert_eq!(deltas, vec![5, -7, 100]);
}
_ => panic!("expected Device"),
}
}
#[test]
fn rejects_unknown_delta_format() {
let mut b = vec![0u8; 6];
b[4..6].copy_from_slice(&0x0004u16.to_be_bytes());
assert!(DeviceOrVariationIndex::parse(&b).is_err());
}
#[test]
fn rejects_end_before_start() {
let mut b = vec![0u8; 6];
b[0..2].copy_from_slice(&15u16.to_be_bytes());
b[2..4].copy_from_slice(&12u16.to_be_bytes());
b[4..6].copy_from_slice(&0x0001u16.to_be_bytes());
assert!(DeviceOrVariationIndex::parse(&b).is_err());
}
#[test]
fn rejects_short_slice() {
assert!(DeviceOrVariationIndex::parse(&[0, 0, 0x80]).is_err());
}
#[test]
fn resolve_null_offset_is_zero() {
assert_eq!(resolve_device_delta(&[0u8; 8], 0, None, &[]), 0.0);
}
#[test]
fn resolve_out_of_range_offset_is_zero() {
assert_eq!(resolve_device_delta(&[0u8; 4], 99, None, &[]), 0.0);
}
#[test]
fn resolve_device_table_contributes_zero_font_units() {
let mut b = vec![0u8; 12];
b[4..6].copy_from_slice(&12u16.to_be_bytes());
b[6..8].copy_from_slice(&15u16.to_be_bytes());
b[8..10].copy_from_slice(&0x0002u16.to_be_bytes());
b[10..12].copy_from_slice(&0x123Fu16.to_be_bytes());
assert_eq!(resolve_device_delta(&b, 4, None, &[]), 0.0);
}
#[test]
fn variation_index_needs_ivs_to_resolve() {
let mut b = vec![0u8; 12];
b[8..10].copy_from_slice(&0x8000u16.to_be_bytes());
assert_eq!(resolve_device_delta(&b, 4, None, &[]), 0.0);
}
#[test]
fn read_device_offset_out_of_range_is_null() {
assert_eq!(read_device_offset(&[0, 5], 0), 5);
assert_eq!(read_device_offset(&[0, 5], 10), 0);
}
}