use crate::parser::{read_i32, read_u16, read_u32};
use crate::Error;
const MAX_AXIS_RECORDS: u16 = 1024;
const MAX_AXIS_VALUES: u16 = 16384;
const MAX_FORMAT4_AXES: u16 = 1024;
pub const FLAG_OLDER_SIBLING_FONT_ATTRIBUTE: u16 = 0x0001;
pub const FLAG_ELIDABLE_AXIS_VALUE_NAME: u16 = 0x0002;
pub const RANGE_MIN_NEG_INFINITY: i32 = i32::MIN;
pub const RANGE_MAX_POS_INFINITY: i32 = i32::MAX;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AxisRecord {
pub axis_tag: [u8; 4],
pub axis_name_id: u16,
pub axis_ordering: u16,
}
#[derive(Debug, Clone)]
pub enum AxisValue {
Format1 {
axis_index: u16,
flags: u16,
value_name_id: u16,
value: f32,
},
Format2 {
axis_index: u16,
flags: u16,
value_name_id: u16,
nominal_value: f32,
range_min_value: f32,
range_max_value: f32,
raw_range_min: i32,
raw_range_max: i32,
},
Format3 {
axis_index: u16,
flags: u16,
value_name_id: u16,
value: f32,
linked_value: f32,
},
Format4 {
flags: u16,
value_name_id: u16,
axis_values: Vec<(u16, f32)>,
},
}
impl AxisValue {
pub fn flags(&self) -> u16 {
match self {
Self::Format1 { flags, .. }
| Self::Format2 { flags, .. }
| Self::Format3 { flags, .. }
| Self::Format4 { flags, .. } => *flags,
}
}
pub fn value_name_id(&self) -> u16 {
match self {
Self::Format1 { value_name_id, .. }
| Self::Format2 { value_name_id, .. }
| Self::Format3 { value_name_id, .. }
| Self::Format4 { value_name_id, .. } => *value_name_id,
}
}
pub fn is_older_sibling_font_attribute(&self) -> bool {
self.flags() & FLAG_OLDER_SIBLING_FONT_ATTRIBUTE != 0
}
pub fn is_elidable(&self) -> bool {
self.flags() & FLAG_ELIDABLE_AXIS_VALUE_NAME != 0
}
}
#[derive(Debug, Clone)]
pub struct StatTable {
major_version: u16,
minor_version: u16,
axes: Vec<AxisRecord>,
values: Vec<AxisValue>,
elided_fallback_name_id: u16,
}
impl StatTable {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 18 {
return Err(Error::UnexpectedEof);
}
let major_version = read_u16(bytes, 0)?;
let minor_version = read_u16(bytes, 2)?;
if major_version != 1 {
return Err(Error::BadStructure("STAT majorVersion != 1"));
}
let design_axis_size = read_u16(bytes, 4)?;
let design_axis_count = read_u16(bytes, 6)?;
let offset_to_design_axes = read_u32(bytes, 8)? as usize;
let axis_value_count = read_u16(bytes, 12)?;
let offset_to_axis_value_offsets = read_u32(bytes, 14)? as usize;
let elided_fallback_name_id = if minor_version >= 1 {
if bytes.len() < 20 {
return Err(Error::UnexpectedEof);
}
read_u16(bytes, 18)?
} else {
2
};
if design_axis_count > MAX_AXIS_RECORDS {
return Err(Error::BadStructure("STAT designAxisCount exceeds cap"));
}
if axis_value_count > MAX_AXIS_VALUES {
return Err(Error::BadStructure("STAT axisValueCount exceeds cap"));
}
if design_axis_count > 0 {
if design_axis_size < 8 {
return Err(Error::BadStructure("STAT designAxisSize < 8"));
}
if offset_to_design_axes == 0 {
return Err(Error::BadOffset);
}
}
let mut axes = Vec::with_capacity(design_axis_count as usize);
if design_axis_count > 0 {
let stride = design_axis_size as usize;
let total = (design_axis_count as usize)
.checked_mul(stride)
.and_then(|n| n.checked_add(offset_to_design_axes))
.ok_or(Error::BadOffset)?;
if total > bytes.len() {
return Err(Error::UnexpectedEof);
}
for i in 0..design_axis_count as usize {
let off = offset_to_design_axes + i * stride;
let axis_tag = [bytes[off], bytes[off + 1], bytes[off + 2], bytes[off + 3]];
let axis_name_id = read_u16(bytes, off + 4)?;
let axis_ordering = read_u16(bytes, off + 6)?;
axes.push(AxisRecord {
axis_tag,
axis_name_id,
axis_ordering,
});
}
}
let mut values = Vec::with_capacity(axis_value_count as usize);
if axis_value_count > 0 {
if offset_to_axis_value_offsets == 0 {
return Err(Error::BadOffset);
}
let array_end = (axis_value_count as usize)
.checked_mul(2)
.and_then(|n| n.checked_add(offset_to_axis_value_offsets))
.ok_or(Error::BadOffset)?;
if array_end > bytes.len() {
return Err(Error::UnexpectedEof);
}
for i in 0..axis_value_count as usize {
let off = offset_to_axis_value_offsets + i * 2;
let rel = read_u16(bytes, off)? as usize;
if rel == 0 {
return Err(Error::BadOffset);
}
let table_off = offset_to_axis_value_offsets
.checked_add(rel)
.ok_or(Error::BadOffset)?;
if table_off >= bytes.len() {
return Err(Error::BadOffset);
}
values.push(parse_axis_value(&bytes[table_off..], design_axis_count)?);
}
}
Ok(Self {
major_version,
minor_version,
axes,
values,
elided_fallback_name_id,
})
}
pub fn major_version(&self) -> u16 {
self.major_version
}
pub fn minor_version(&self) -> u16 {
self.minor_version
}
pub fn axes(&self) -> &[AxisRecord] {
&self.axes
}
pub fn axis_values(&self) -> &[AxisValue] {
&self.values
}
pub fn elided_fallback_name_id(&self) -> u16 {
self.elided_fallback_name_id
}
pub fn axis_values_for_tag(&self, axis_tag: [u8; 4]) -> impl Iterator<Item = &AxisValue> + '_ {
let idx = self
.axes
.iter()
.position(|a| a.axis_tag == axis_tag)
.map(|i| i as u16);
self.values.iter().filter(move |v| match (idx, v) {
(Some(want), AxisValue::Format1 { axis_index, .. })
| (Some(want), AxisValue::Format2 { axis_index, .. })
| (Some(want), AxisValue::Format3 { axis_index, .. }) => *axis_index == want,
(Some(want), AxisValue::Format4 { axis_values, .. }) => {
axis_values.iter().any(|(ai, _)| *ai == want)
}
_ => false,
})
}
}
fn parse_axis_value(bytes: &[u8], design_axis_count: u16) -> Result<AxisValue, Error> {
if bytes.len() < 8 {
return Err(Error::UnexpectedEof);
}
let format = read_u16(bytes, 0)?;
match format {
1 => {
if bytes.len() < 12 {
return Err(Error::UnexpectedEof);
}
let axis_index = read_u16(bytes, 2)?;
if axis_index >= design_axis_count {
return Err(Error::BadStructure("STAT format-1 axisIndex out of range"));
}
let flags = read_u16(bytes, 4)?;
let value_name_id = read_u16(bytes, 6)?;
let value = fixed_to_f32(read_i32(bytes, 8)?);
Ok(AxisValue::Format1 {
axis_index,
flags,
value_name_id,
value,
})
}
2 => {
if bytes.len() < 20 {
return Err(Error::UnexpectedEof);
}
let axis_index = read_u16(bytes, 2)?;
if axis_index >= design_axis_count {
return Err(Error::BadStructure("STAT format-2 axisIndex out of range"));
}
let flags = read_u16(bytes, 4)?;
let value_name_id = read_u16(bytes, 6)?;
let raw_nominal = read_i32(bytes, 8)?;
let raw_range_min = read_i32(bytes, 12)?;
let raw_range_max = read_i32(bytes, 16)?;
Ok(AxisValue::Format2 {
axis_index,
flags,
value_name_id,
nominal_value: fixed_to_f32(raw_nominal),
range_min_value: fixed_to_f32(raw_range_min),
range_max_value: fixed_to_f32(raw_range_max),
raw_range_min,
raw_range_max,
})
}
3 => {
if bytes.len() < 16 {
return Err(Error::UnexpectedEof);
}
let axis_index = read_u16(bytes, 2)?;
if axis_index >= design_axis_count {
return Err(Error::BadStructure("STAT format-3 axisIndex out of range"));
}
let flags = read_u16(bytes, 4)?;
let value_name_id = read_u16(bytes, 6)?;
let value = fixed_to_f32(read_i32(bytes, 8)?);
let linked_value = fixed_to_f32(read_i32(bytes, 12)?);
Ok(AxisValue::Format3 {
axis_index,
flags,
value_name_id,
value,
linked_value,
})
}
4 => {
let axis_count = read_u16(bytes, 2)?;
if axis_count == 0 {
return Err(Error::BadStructure("STAT format-4 axisCount == 0"));
}
if axis_count > MAX_FORMAT4_AXES {
return Err(Error::BadStructure("STAT format-4 axisCount exceeds cap"));
}
let flags = read_u16(bytes, 4)?;
let value_name_id = read_u16(bytes, 6)?;
let body_len = (axis_count as usize)
.checked_mul(6)
.and_then(|n| n.checked_add(8))
.ok_or(Error::BadOffset)?;
if body_len > bytes.len() {
return Err(Error::UnexpectedEof);
}
let mut axis_values = Vec::with_capacity(axis_count as usize);
for i in 0..axis_count as usize {
let off = 8 + i * 6;
let axis_index = read_u16(bytes, off)?;
if axis_index >= design_axis_count {
return Err(Error::BadStructure("STAT format-4 axisIndex out of range"));
}
if axis_values
.iter()
.any(|(prev, _): &(u16, f32)| *prev == axis_index)
{
return Err(Error::BadStructure("STAT format-4 duplicate axisIndex"));
}
let value = fixed_to_f32(read_i32(bytes, off + 2)?);
axis_values.push((axis_index, value));
}
Ok(AxisValue::Format4 {
flags,
value_name_id,
axis_values,
})
}
_ => {
Err(Error::BadStructure("STAT unsupported axis value format"))
}
}
}
#[inline]
fn fixed_to_f32(raw: i32) -> f32 {
raw as f32 / 65536.0
}
#[cfg(test)]
mod tests {
use super::*;
fn build_minimal_v11() -> Vec<u8> {
let mut b = vec![0u8; 0];
b.extend_from_slice(&1u16.to_be_bytes()); b.extend_from_slice(&1u16.to_be_bytes()); b.extend_from_slice(&8u16.to_be_bytes()); b.extend_from_slice(&1u16.to_be_bytes()); b.extend_from_slice(&20u32.to_be_bytes()); b.extend_from_slice(&2u16.to_be_bytes()); b.extend_from_slice(&28u32.to_be_bytes()); b.extend_from_slice(&2u16.to_be_bytes());
b.extend_from_slice(b"wght");
b.extend_from_slice(&256u16.to_be_bytes()); b.extend_from_slice(&0u16.to_be_bytes());
let array_base = b.len();
assert_eq!(array_base, 28);
b.extend_from_slice(&0u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
let tbl1_off = b.len();
b.extend_from_slice(&1u16.to_be_bytes()); b.extend_from_slice(&0u16.to_be_bytes()); b.extend_from_slice(&FLAG_ELIDABLE_AXIS_VALUE_NAME.to_be_bytes()); b.extend_from_slice(&257u16.to_be_bytes()); b.extend_from_slice(&((400i32) << 16).to_be_bytes());
let tbl2_off = b.len();
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
b.extend_from_slice(&258u16.to_be_bytes());
b.extend_from_slice(&((700i32) << 16).to_be_bytes());
let off1 = (tbl1_off - array_base) as u16;
let off2 = (tbl2_off - array_base) as u16;
b[array_base..array_base + 2].copy_from_slice(&off1.to_be_bytes());
b[array_base + 2..array_base + 4].copy_from_slice(&off2.to_be_bytes());
b
}
#[test]
fn parses_minimal_v11() {
let bytes = build_minimal_v11();
let t = StatTable::parse(&bytes).expect("parse");
assert_eq!(t.major_version(), 1);
assert_eq!(t.minor_version(), 1);
assert_eq!(t.elided_fallback_name_id(), 2);
assert_eq!(t.axes().len(), 1);
assert_eq!(t.axes()[0].axis_tag, *b"wght");
assert_eq!(t.axes()[0].axis_name_id, 256);
assert_eq!(t.axis_values().len(), 2);
match &t.axis_values()[0] {
AxisValue::Format1 {
axis_index,
value,
value_name_id,
..
} => {
assert_eq!(*axis_index, 0);
assert!((*value - 400.0).abs() < 1e-6);
assert_eq!(*value_name_id, 257);
}
_ => panic!("expected Format1"),
}
assert!(t.axis_values()[0].is_elidable());
assert!(!t.axis_values()[1].is_elidable());
}
#[test]
fn axis_values_for_tag_filter() {
let bytes = build_minimal_v11();
let t = StatTable::parse(&bytes).unwrap();
let n = t.axis_values_for_tag(*b"wght").count();
assert_eq!(n, 2);
let n = t.axis_values_for_tag(*b"opsz").count();
assert_eq!(n, 0);
}
#[test]
fn rejects_truncated_header() {
let bytes = [0u8; 10];
assert!(matches!(
StatTable::parse(&bytes),
Err(Error::UnexpectedEof)
));
}
#[test]
fn rejects_bad_major_version() {
let mut b = vec![0u8; 20];
b[0..2].copy_from_slice(&2u16.to_be_bytes()); assert!(matches!(StatTable::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn parses_format2_range_with_sentinels() {
let mut b = Vec::<u8>::new();
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&8u16.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&20u32.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&28u32.to_be_bytes());
b.extend_from_slice(&2u16.to_be_bytes());
b.extend_from_slice(b"opsz");
b.extend_from_slice(&100u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
let array_base = b.len();
b.extend_from_slice(&0u16.to_be_bytes());
let tbl_off = b.len();
b.extend_from_slice(&2u16.to_be_bytes()); b.extend_from_slice(&0u16.to_be_bytes()); b.extend_from_slice(&0u16.to_be_bytes()); b.extend_from_slice(&200u16.to_be_bytes()); b.extend_from_slice(&((12i32) << 16).to_be_bytes()); b.extend_from_slice(&RANGE_MIN_NEG_INFINITY.to_be_bytes()); b.extend_from_slice(&RANGE_MAX_POS_INFINITY.to_be_bytes()); let off = (tbl_off - array_base) as u16;
b[array_base..array_base + 2].copy_from_slice(&off.to_be_bytes());
let t = StatTable::parse(&b).expect("parse");
match &t.axis_values()[0] {
AxisValue::Format2 {
axis_index,
nominal_value,
raw_range_min,
raw_range_max,
..
} => {
assert_eq!(*axis_index, 0);
assert!((*nominal_value - 12.0).abs() < 1e-6);
assert_eq!(*raw_range_min, RANGE_MIN_NEG_INFINITY);
assert_eq!(*raw_range_max, RANGE_MAX_POS_INFINITY);
}
_ => panic!("expected Format2"),
}
}
#[test]
fn parses_format3_style_linked_pair() {
let mut b = Vec::<u8>::new();
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&8u16.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&20u32.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&28u32.to_be_bytes());
b.extend_from_slice(&2u16.to_be_bytes());
b.extend_from_slice(b"wght");
b.extend_from_slice(&256u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
let array_base = b.len();
b.extend_from_slice(&0u16.to_be_bytes());
let tbl_off = b.len();
b.extend_from_slice(&3u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
b.extend_from_slice(&FLAG_ELIDABLE_AXIS_VALUE_NAME.to_be_bytes());
b.extend_from_slice(&257u16.to_be_bytes());
b.extend_from_slice(&((400i32) << 16).to_be_bytes());
b.extend_from_slice(&((700i32) << 16).to_be_bytes());
let off = (tbl_off - array_base) as u16;
b[array_base..array_base + 2].copy_from_slice(&off.to_be_bytes());
let t = StatTable::parse(&b).unwrap();
match &t.axis_values()[0] {
AxisValue::Format3 {
value,
linked_value,
..
} => {
assert!((*value - 400.0).abs() < 1e-6);
assert!((*linked_value - 700.0).abs() < 1e-6);
}
_ => panic!("expected Format3"),
}
}
#[test]
fn parses_format4_multi_axis_combo() {
let mut b = Vec::<u8>::new();
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&2u16.to_be_bytes()); b.extend_from_slice(&8u16.to_be_bytes());
b.extend_from_slice(&2u16.to_be_bytes()); b.extend_from_slice(&20u32.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&36u32.to_be_bytes()); b.extend_from_slice(&2u16.to_be_bytes());
b.extend_from_slice(b"wght");
b.extend_from_slice(&256u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
b.extend_from_slice(b"wdth");
b.extend_from_slice(&257u16.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
let array_base = b.len();
b.extend_from_slice(&0u16.to_be_bytes());
let tbl_off = b.len();
b.extend_from_slice(&4u16.to_be_bytes()); b.extend_from_slice(&2u16.to_be_bytes()); b.extend_from_slice(&0u16.to_be_bytes()); b.extend_from_slice(&300u16.to_be_bytes()); b.extend_from_slice(&0u16.to_be_bytes());
b.extend_from_slice(&((700i32) << 16).to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&((75i32) << 16).to_be_bytes());
let off = (tbl_off - array_base) as u16;
b[array_base..array_base + 2].copy_from_slice(&off.to_be_bytes());
let t = StatTable::parse(&b).unwrap();
match &t.axis_values()[0] {
AxisValue::Format4 {
axis_values,
value_name_id,
..
} => {
assert_eq!(*value_name_id, 300);
assert_eq!(axis_values.len(), 2);
assert_eq!(axis_values[0].0, 0);
assert!((axis_values[0].1 - 700.0).abs() < 1e-6);
assert_eq!(axis_values[1].0, 1);
assert!((axis_values[1].1 - 75.0).abs() < 1e-6);
}
_ => panic!("expected Format4"),
}
}
#[test]
fn rejects_format4_duplicate_axis_index() {
let mut b = Vec::<u8>::new();
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&2u16.to_be_bytes());
b.extend_from_slice(&8u16.to_be_bytes());
b.extend_from_slice(&2u16.to_be_bytes());
b.extend_from_slice(&20u32.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&36u32.to_be_bytes());
b.extend_from_slice(&2u16.to_be_bytes());
b.extend_from_slice(b"wght");
b.extend_from_slice(&256u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
b.extend_from_slice(b"wdth");
b.extend_from_slice(&257u16.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
let array_base = b.len();
b.extend_from_slice(&0u16.to_be_bytes());
let tbl_off = b.len();
b.extend_from_slice(&4u16.to_be_bytes());
b.extend_from_slice(&2u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
b.extend_from_slice(&300u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
b.extend_from_slice(&((700i32) << 16).to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes()); b.extend_from_slice(&((400i32) << 16).to_be_bytes());
let off = (tbl_off - array_base) as u16;
b[array_base..array_base + 2].copy_from_slice(&off.to_be_bytes());
assert!(matches!(StatTable::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn rejects_unknown_format() {
let mut b = Vec::<u8>::new();
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&8u16.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&20u32.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&28u32.to_be_bytes());
b.extend_from_slice(&2u16.to_be_bytes());
b.extend_from_slice(b"wght");
b.extend_from_slice(&256u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
let array_base = b.len();
b.extend_from_slice(&0u16.to_be_bytes());
let tbl_off = b.len();
b.extend_from_slice(&9u16.to_be_bytes()); b.extend_from_slice(&[0u8; 16]);
let off = (tbl_off - array_base) as u16;
b[array_base..array_base + 2].copy_from_slice(&off.to_be_bytes());
assert!(matches!(StatTable::parse(&b), Err(Error::BadStructure(_))));
}
#[test]
fn rejects_axis_index_out_of_range() {
let mut b = Vec::<u8>::new();
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&8u16.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&20u32.to_be_bytes());
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&28u32.to_be_bytes());
b.extend_from_slice(&2u16.to_be_bytes());
b.extend_from_slice(b"wght");
b.extend_from_slice(&256u16.to_be_bytes());
b.extend_from_slice(&0u16.to_be_bytes());
let array_base = b.len();
b.extend_from_slice(&0u16.to_be_bytes());
let tbl_off = b.len();
b.extend_from_slice(&1u16.to_be_bytes());
b.extend_from_slice(&5u16.to_be_bytes()); b.extend_from_slice(&0u16.to_be_bytes());
b.extend_from_slice(&100u16.to_be_bytes());
b.extend_from_slice(&((400i32) << 16).to_be_bytes());
let off = (tbl_off - array_base) as u16;
b[array_base..array_base + 2].copy_from_slice(&off.to_be_bytes());
assert!(matches!(StatTable::parse(&b), Err(Error::BadStructure(_))));
}
}