use crate::parser::read_u8;
use crate::Error;
#[derive(Debug, Clone)]
pub struct Cff2Index<'a> {
pub(crate) bytes: &'a [u8],
pub(crate) count: u32,
#[allow(dead_code)] pub(crate) end: usize,
off_size: u8,
offsets_at: usize,
data_at: usize,
}
impl<'a> Cff2Index<'a> {
pub(crate) fn parse(bytes: &'a [u8], at: usize) -> Result<Self, Error> {
if at > bytes.len() {
return Err(Error::UnexpectedEof);
}
let count = read_u32(bytes, at)?;
if count == 0 {
return Ok(Self {
bytes,
count: 0,
end: at + 4,
off_size: 1,
offsets_at: at + 4,
data_at: at + 4,
});
}
let off_size = read_u8(bytes, at + 4)?;
if !(1..=4).contains(&off_size) {
return Err(Error::Cff("CFF2 INDEX offsetSize out of range"));
}
let offsets_at = at + 5;
let n_offsets = (count as usize)
.checked_add(1)
.ok_or(Error::Cff("CFF2 INDEX count overflow"))?;
let offsets_len = n_offsets
.checked_mul(off_size as usize)
.ok_or(Error::Cff("CFF2 INDEX offsets length overflow"))?;
let data_at = offsets_at
.checked_add(offsets_len)
.ok_or(Error::Cff("CFF2 INDEX data offset overflow"))?;
if data_at > bytes.len() {
return Err(Error::UnexpectedEof);
}
let last = read_offset(bytes, offsets_at, off_size, count as usize)?;
if last < 1 {
return Err(Error::Cff("CFF2 INDEX last offset == 0"));
}
let data_len = (last - 1) as usize;
let end = data_at
.checked_add(data_len)
.ok_or(Error::Cff("CFF2 INDEX end overflow"))?;
if end > bytes.len() {
return Err(Error::UnexpectedEof);
}
Ok(Self {
bytes,
count,
end,
off_size,
offsets_at,
data_at,
})
}
pub(crate) fn entry(&self, i: u32) -> Result<&'a [u8], Error> {
if i >= self.count {
return Err(Error::Cff("CFF2 INDEX entry out of range"));
}
let start = read_offset(self.bytes, self.offsets_at, self.off_size, i as usize)?;
let end_off = read_offset(self.bytes, self.offsets_at, self.off_size, i as usize + 1)?;
if start < 1 || end_off < start {
return Err(Error::Cff("CFF2 INDEX: malformed offsets"));
}
let s = self.data_at + (start - 1) as usize;
let e = self.data_at + (end_off - 1) as usize;
if e > self.bytes.len() {
return Err(Error::UnexpectedEof);
}
Ok(&self.bytes[s..e])
}
}
fn read_u32(bytes: &[u8], off: usize) -> Result<u32, Error> {
let s = bytes.get(off..off + 4).ok_or(Error::UnexpectedEof)?;
Ok(u32::from_be_bytes([s[0], s[1], s[2], s[3]]))
}
fn read_offset(bytes: &[u8], offsets_at: usize, off_size: u8, i: usize) -> Result<u32, Error> {
let off = offsets_at + i * off_size as usize;
let s = bytes
.get(off..off + off_size as usize)
.ok_or(Error::UnexpectedEof)?;
Ok(match off_size {
1 => s[0] as u32,
2 => u16::from_be_bytes([s[0], s[1]]) as u32,
3 => ((s[0] as u32) << 16) | ((s[1] as u32) << 8) | s[2] as u32,
4 => u32::from_be_bytes([s[0], s[1], s[2], s[3]]),
_ => unreachable!("off_size validated to 1..=4 in parse()"),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn build_three_entry_index() -> Vec<u8> {
let mut v = vec![0, 0, 0, 3]; v.push(1); v.extend_from_slice(&[1, 4, 6, 6]); v.extend_from_slice(b"abcde");
v
}
#[test]
fn parses_three_entries() {
let v = build_three_entry_index();
let idx = Cff2Index::parse(&v, 0).expect("parse");
assert_eq!(idx.count, 3);
assert_eq!(idx.entry(0).unwrap(), b"abc");
assert_eq!(idx.entry(1).unwrap(), b"de");
assert_eq!(idx.entry(2).unwrap(), b"");
assert!(idx.entry(3).is_err());
}
#[test]
fn empty_index_is_four_bytes() {
let v = vec![0u8, 0, 0, 0];
let idx = Cff2Index::parse(&v, 0).expect("parse");
assert_eq!(idx.count, 0);
assert_eq!(idx.end, 4);
assert!(idx.entry(0).is_err());
}
#[test]
fn handles_off_size_2() {
let mut v = vec![0, 0, 0, 1];
v.push(2);
v.extend_from_slice(&[0, 1, 0, 5]); v.extend_from_slice(b"WXYZ");
let idx = Cff2Index::parse(&v, 0).expect("parse");
assert_eq!(idx.entry(0).unwrap(), b"WXYZ");
assert_eq!(idx.end, v.len());
}
#[test]
fn handles_off_size_3() {
let mut v = vec![0, 0, 0, 2];
v.push(3);
v.extend_from_slice(&[0, 0, 1, 0, 0, 2, 0, 0, 4]);
v.extend_from_slice(b"xyz");
let idx = Cff2Index::parse(&v, 0).expect("parse");
assert_eq!(idx.entry(0).unwrap(), b"x");
assert_eq!(idx.entry(1).unwrap(), b"yz");
}
#[test]
fn handles_off_size_4() {
let mut v = vec![0, 0, 0, 1];
v.push(4);
v.extend_from_slice(&[0, 0, 0, 1, 0, 0, 0, 2]); v.extend_from_slice(b"Q");
let idx = Cff2Index::parse(&v, 0).expect("parse");
assert_eq!(idx.entry(0).unwrap(), b"Q");
}
#[test]
fn rejects_truncated_data() {
let mut v = build_three_entry_index();
v.pop();
assert!(Cff2Index::parse(&v, 0).is_err());
}
#[test]
fn rejects_off_size_zero() {
let mut v = vec![0, 0, 0, 1];
v.push(0); assert!(matches!(
Cff2Index::parse(&v, 0),
Err(Error::Cff("CFF2 INDEX offsetSize out of range"))
));
}
#[test]
fn rejects_off_size_five() {
let mut v = vec![0, 0, 0, 1];
v.push(5);
assert!(matches!(
Cff2Index::parse(&v, 0),
Err(Error::Cff("CFF2 INDEX offsetSize out of range"))
));
}
#[test]
fn rejects_first_offset_zero() {
let mut v = vec![0, 0, 0, 1, 1, 0, 0]; v.extend_from_slice(b"");
assert!(matches!(
Cff2Index::parse(&v, 0),
Err(Error::Cff("CFF2 INDEX last offset == 0"))
));
}
#[test]
fn end_field_points_past_data() {
let v = build_three_entry_index();
let idx = Cff2Index::parse(&v, 0).expect("parse");
assert_eq!(idx.end, v.len());
}
}