pub mod header;
pub mod index;
pub mod top_dict;
pub mod varstore;
pub use self::header::Cff2Header;
pub use self::index::Cff2Index;
pub use self::top_dict::{Cff2Op, Cff2TopDict, DEFAULT_FONT_MATRIX};
pub use self::varstore::{
ItemVariationData, ItemVariationStore, RegionAxisCoordinates, VariationRegion,
};
use crate::Error;
#[derive(Debug, Clone)]
pub struct Cff2<'a> {
bytes: &'a [u8],
header: Cff2Header,
top: Cff2TopDict,
global_subrs: Cff2Index<'a>,
charstrings: Cff2Index<'a>,
font_dicts: Cff2Index<'a>,
variation_store: Option<ItemVariationStore>,
}
impl<'a> Cff2<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self, Error> {
let header = Cff2Header::parse(bytes)?;
let td_start = header.top_dict_offset();
let td_end = td_start
.checked_add(header.top_dict_size as usize)
.ok_or(Error::Cff("CFF2 Top DICT extent overflow"))?;
if td_end > bytes.len() {
return Err(Error::UnexpectedEof);
}
let top = Cff2TopDict::parse(&bytes[td_start..td_end])?;
let global_subrs = Cff2Index::parse(bytes, header.global_subr_index_offset())?;
let cs_off = top.charstring_index_offset as usize;
let charstrings = Cff2Index::parse(bytes, cs_off)?;
let fd_off = top.font_dict_index_offset as usize;
let font_dicts = Cff2Index::parse(bytes, fd_off)?;
if font_dicts.count == 0 {
return Err(Error::Cff(
"CFF2 FontDICTINDEX must contain at least one FontDICT",
));
}
let variation_store = match top.variation_store_offset {
Some(off) => Some(ItemVariationStore::parse_variation_store(
bytes,
off as usize,
)?),
None => None,
};
Ok(Self {
bytes,
header,
top,
global_subrs,
charstrings,
font_dicts,
variation_store,
})
}
pub fn header(&self) -> &Cff2Header {
&self.header
}
pub fn top_dict(&self) -> &Cff2TopDict {
&self.top
}
pub fn glyph_count(&self) -> u32 {
self.charstrings.count
}
pub fn font_dict_count(&self) -> u32 {
self.font_dicts.count
}
pub fn global_subr_count(&self) -> u32 {
self.global_subrs.count
}
pub fn is_variable(&self) -> bool {
self.top.is_variable()
}
pub fn variation_store(&self) -> Option<&ItemVariationStore> {
self.variation_store.as_ref()
}
pub fn charstring(&self, gid: u32) -> Result<&'a [u8], Error> {
self.charstrings.entry(gid)
}
pub fn font_dict(&self, fd_index: u32) -> Result<&'a [u8], Error> {
self.font_dicts.entry(fd_index)
}
pub fn global_subr(&self, i: u32) -> Result<&'a [u8], Error> {
self.global_subrs.entry(i)
}
pub fn bytes(&self) -> &'a [u8] {
self.bytes
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(clippy::vec_init_then_push)]
fn build_minimal_cff2() -> Vec<u8> {
let cs_off = 14u32;
let fd_off = 22u32;
let mut v = Vec::new();
v.push(2); v.push(0); v.push(5); v.push(0); v.push(5);
v.push((cs_off + 139) as u8); v.push(17); v.push((fd_off + 139) as u8); v.extend_from_slice(&[12, 36]);
v.extend_from_slice(&[0, 0, 0, 0]);
assert_eq!(v.len(), cs_off as usize);
v.extend_from_slice(&[0, 0, 0, 1]); v.push(1); v.extend_from_slice(&[1, 2]); v.push(b'C');
assert_eq!(v.len(), fd_off as usize);
v.extend_from_slice(&[0, 0, 0, 1]);
v.push(1);
v.extend_from_slice(&[1, 2]);
v.push(b'F');
v
}
#[test]
fn parses_minimal_cff2() {
let v = build_minimal_cff2();
let c = Cff2::parse(&v).expect("parse");
assert_eq!(c.header.major, 2);
assert_eq!(c.header.header_size, 5);
assert_eq!(c.header.top_dict_size, 5);
assert_eq!(c.top.charstring_index_offset, 14);
assert_eq!(c.top.font_dict_index_offset, 22);
assert_eq!(c.glyph_count(), 1);
assert_eq!(c.font_dict_count(), 1);
assert_eq!(c.global_subr_count(), 0);
assert!(!c.is_variable());
assert_eq!(c.charstring(0).unwrap(), b"C");
assert_eq!(c.font_dict(0).unwrap(), b"F");
}
#[test]
#[allow(clippy::vec_init_then_push)]
fn rejects_empty_font_dict_index() {
let cs_off = 14u32;
let fd_off = 22u32;
let mut v = Vec::new();
v.push(2);
v.push(0);
v.push(5);
v.push(0);
v.push(5);
v.push((cs_off + 139) as u8);
v.push(17);
v.push((fd_off + 139) as u8);
v.extend_from_slice(&[12, 36]);
v.extend_from_slice(&[0, 0, 0, 0]);
v.extend_from_slice(&[0, 0, 0, 1, 1, 1, 2, b'C']);
v.extend_from_slice(&[0, 0, 0, 0]); let err = Cff2::parse(&v).unwrap_err();
match err {
Error::Cff(s) => assert!(s.contains("FontDICTINDEX must contain at least one")),
_ => panic!("unexpected: {err:?}"),
}
}
#[test]
#[allow(clippy::vec_init_then_push)]
fn detects_variable_font() {
let cs_off = 16u32;
let fd_off = 24u32;
let mut v = Vec::new();
v.push(2);
v.push(0);
v.push(5);
v.push(0);
v.push(7); v.push((cs_off + 139) as u8); v.push(17); v.push((fd_off + 139) as u8); v.extend_from_slice(&[12, 36]); v.push(50 + 139); v.push(24); v.extend_from_slice(&[0, 0, 0, 0]);
assert_eq!(v.len(), cs_off as usize);
v.extend_from_slice(&[0, 0, 0, 1, 1, 1, 2, b'C']);
assert_eq!(v.len(), fd_off as usize);
v.extend_from_slice(&[0, 0, 0, 1, 1, 1, 2, b'F']);
v.resize(50, 0);
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, 0xE0, 0x00, 0x00, 0x00]); v.extend_from_slice(&[0xC0, 0x00, 0xC0, 0x00, 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]);
let c = Cff2::parse(&v).expect("parse");
assert!(c.is_variable());
assert_eq!(c.top.variation_store_offset, Some(50));
let ivs = c.variation_store().expect("variation store parsed");
assert_eq!(ivs.axis_count, 1);
assert_eq!(ivs.regions.len(), 2);
assert_eq!(ivs.item_variation_data_count(), 1);
assert_eq!(
ivs.item_variation_data_at(0).unwrap().region_indexes,
vec![0, 1]
);
}
#[test]
fn non_variable_font_has_no_variation_store() {
let v = build_minimal_cff2();
let c = Cff2::parse(&v).expect("parse");
assert!(!c.is_variable());
assert!(c.variation_store().is_none());
}
#[test]
fn rejects_top_dict_extent_past_eof() {
let v = vec![2, 0, 5, 0, 100, 0, 0, 0, 0, 0];
let err = Cff2::parse(&v).unwrap_err();
assert!(matches!(err, Error::UnexpectedEof));
}
#[test]
fn rejects_charstring_offset_past_eof() {
let cs_off = 200u32;
let fd_off = 50u32;
let mut v = vec![2, 0, 5, 0, 8];
v.push(29); v.extend_from_slice(&cs_off.to_be_bytes()); v.push(17);
v.push((fd_off + 139) as u8);
v.extend_from_slice(&[12, 36]);
v.resize(100, 0);
let err = Cff2::parse(&v).unwrap_err();
assert!(matches!(err, Error::UnexpectedEof));
}
}