use crate::outline::TtOutline;
use crate::parser::{read_u16, read_u8};
use crate::Error;
pub mod charstring;
pub mod strings;
#[derive(Debug, Clone)]
pub struct CffTable<'a> {
data: &'a [u8],
char_strings: Index<'a>,
global_subrs: Index<'a>,
local_subrs: Index<'a>,
width: (f32, f32),
charset: Vec<u16>,
cid: Option<CidData<'a>>,
charstring_type: i32,
strings: Index<'a>,
}
#[derive(Debug, Clone)]
struct CidData<'a> {
fd_select: FdSelect,
fd_locals: Vec<Index<'a>>,
fd_widths: Vec<(f32, f32)>,
}
impl<'a> CffTable<'a> {
pub fn parse(data: &'a [u8]) -> Result<Self, Error> {
let hdr_size = read_u8(data, 2)? as usize;
if hdr_size < 4 {
return Err(Error::BadStructure("CFF header size too small"));
}
let mut pos = hdr_size;
let _name = Index::parse(data, &mut pos)?;
let top_dicts = Index::parse(data, &mut pos)?;
let strings = Index::parse(data, &mut pos)?;
let global_subrs = Index::parse(data, &mut pos)?;
let top_dict_bytes = top_dicts
.get(0)
.ok_or(Error::BadStructure("CFF empty Top DICT INDEX"))?;
let top = Dict::parse(top_dict_bytes)?;
let cs_off = top
.first_int(op::CHAR_STRINGS)
.ok_or(Error::BadStructure("CFF Top DICT missing CharStrings"))?
as usize;
let mut cs_pos = cs_off;
let char_strings = Index::parse(data, &mut cs_pos)?;
let charstring_type = top.first_int(op::CHARSTRING_TYPE).unwrap_or(2);
let n_glyphs = char_strings.count();
let charset_off = top.first_int(op::CHARSET).unwrap_or(0);
let charset = parse_charset(data, charset_off, n_glyphs)?;
let (local_subrs, width, cid) = if top.contains(op::ROS) {
let cid = parse_cid(data, &top, n_glyphs)?;
(Index::empty(), (0.0, 0.0), Some(cid))
} else {
let (local_subrs, width) = parse_private(data, &top)?;
(local_subrs, width, None)
};
Ok(Self {
data,
char_strings,
global_subrs,
local_subrs,
width,
charset,
cid,
charstring_type,
strings,
})
}
pub fn string_for_sid(&self, sid: u16) -> Option<&str> {
if sid < strings::N_STD_STRINGS {
strings::STANDARD_STRINGS.get(sid as usize).copied()
} else {
let i = (sid - strings::N_STD_STRINGS) as usize;
std::str::from_utf8(self.strings.get(i)?).ok()
}
}
pub fn glyph_name(&self, gid: u16) -> Option<&str> {
if self.cid.is_some() {
return None;
}
if self.charset.is_empty() {
return if gid == 0 {
strings::STANDARD_STRINGS.first().copied()
} else {
None
};
}
let sid = *self.charset.get(gid as usize)?;
self.string_for_sid(sid)
}
pub fn gid_for_name(&self, name: &str) -> Option<u16> {
if self.cid.is_some() {
return None;
}
if self.charset.is_empty() {
return (name == ".notdef").then_some(0);
}
self.charset
.iter()
.position(|&sid| self.string_for_sid(sid) == Some(name))
.map(|p| p as u16)
}
pub fn iter_glyph_names(&self) -> impl Iterator<Item = (u16, &str)> + '_ {
let active = self.cid.is_none() && !self.charset.is_empty();
self.charset
.iter()
.enumerate()
.filter(move |_| active)
.filter_map(move |(gid, &sid)| self.string_for_sid(sid).map(|n| (gid as u16, n)))
}
pub fn glyph_count(&self) -> u16 {
self.char_strings.count().min(u16::MAX as usize) as u16
}
pub fn is_cid(&self) -> bool {
self.cid.is_some()
}
pub fn sid_for_gid(&self, gid: u16) -> Option<u16> {
if self.charset.is_empty() {
(gid as usize).lt(&self.char_strings.count()).then_some(gid)
} else {
self.charset.get(gid as usize).copied()
}
}
pub fn glyph_outline(&self, gid: u16) -> Option<TtOutline> {
if self.charstring_type != 2 {
return None;
}
let cs = self.char_strings.get(gid as usize)?;
let (locals, nominal) = self.subrs_for_gid(gid);
let mut interp = charstring::Interp::new(self.global_subrs, locals, nominal);
interp.run(cs).ok()?;
Some(interp.into_outline())
}
pub fn glyph_width(&self, gid: u16) -> Option<f32> {
if self.charstring_type != 2 {
return None;
}
let cs = self.char_strings.get(gid as usize)?;
let (locals, nominal) = self.subrs_for_gid(gid);
let default = if let Some(cid) = &self.cid {
let fd = cid.fd_select.fd_for_gid(gid) as usize;
cid.fd_widths.get(fd).map(|w| w.0).unwrap_or(0.0)
} else {
self.width.0
};
let mut interp = charstring::Interp::new(self.global_subrs, locals, nominal);
interp.run(cs).ok()?;
Some(interp.width().unwrap_or(default))
}
fn subrs_for_gid(&self, gid: u16) -> (Index<'a>, f32) {
if let Some(cid) = &self.cid {
let fd = cid.fd_select.fd_for_gid(gid) as usize;
let locals = cid.fd_locals.get(fd).copied().unwrap_or_else(Index::empty);
let nominal = cid.fd_widths.get(fd).map(|w| w.1).unwrap_or(0.0);
(locals, nominal)
} else {
(self.local_subrs, self.width.1)
}
}
pub fn data(&self) -> &'a [u8] {
self.data
}
}
#[derive(Debug, Clone, Copy)]
pub struct Index<'a> {
data: &'a [u8],
count: usize,
off_size: usize,
offsets_at: usize,
data_base: usize,
}
impl<'a> Index<'a> {
fn empty() -> Self {
Self {
data: &[],
count: 0,
off_size: 1,
offsets_at: 0,
data_base: 0,
}
}
pub(crate) fn empty_pub() -> Self {
Self::empty()
}
pub(crate) fn parse(data: &'a [u8], pos: &mut usize) -> Result<Self, Error> {
Self::parse_impl(data, pos, false)
}
pub(crate) fn parse_wide(data: &'a [u8], pos: &mut usize) -> Result<Self, Error> {
Self::parse_impl(data, pos, true)
}
fn parse_impl(data: &'a [u8], pos: &mut usize, wide_count: bool) -> Result<Self, Error> {
let start = *pos;
let (count, after_count) = if wide_count {
(crate::parser::read_u32(data, start)? as usize, start + 4)
} else {
(read_u16(data, start)? as usize, start + 2)
};
if count == 0 {
*pos = after_count;
return Ok(Self {
data,
count: 0,
off_size: 1,
offsets_at: after_count,
data_base: after_count,
});
}
let off_size = read_u8(data, after_count)? as usize;
if !(1..=4).contains(&off_size) {
return Err(Error::BadStructure("CFF INDEX offSize out of range"));
}
let offsets_at = after_count + 1;
let off_array_len = (count + 1) * off_size;
let data_base = offsets_at + off_array_len - 1;
let last_off = read_offset(data, offsets_at + count * off_size, off_size)?;
let end = data_base + last_off;
if end > data.len() {
return Err(Error::UnexpectedEof);
}
let me = Self {
data,
count,
off_size,
offsets_at,
data_base,
};
*pos = end;
Ok(me)
}
pub fn count(&self) -> usize {
self.count
}
pub fn get(&self, i: usize) -> Option<&'a [u8]> {
if i >= self.count {
return None;
}
let off_lo = read_offset_slice(self.data, self.offsets_at, self.off_size, i)?;
let off_hi = read_offset_slice(self.data, self.offsets_at, self.off_size, i + 1)?;
if off_hi < off_lo {
return None;
}
let lo = self.data_base + off_lo;
let hi = self.data_base + off_hi;
self.data.get(lo..hi)
}
}
fn read_offset(data: &[u8], at: usize, off_size: usize) -> Result<usize, Error> {
let s = data.get(at..at + off_size).ok_or(Error::UnexpectedEof)?;
let mut v = 0usize;
for &b in s {
v = (v << 8) | b as usize;
}
Ok(v)
}
fn read_offset_slice(data: &[u8], offsets_at: usize, off_size: usize, idx: usize) -> Option<usize> {
read_offset(data, offsets_at + idx * off_size, off_size).ok()
}
#[derive(Debug, Clone, Default)]
pub struct Dict {
entries: Vec<(u16, Vec<f64>)>,
}
impl Dict {
pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
let mut entries = Vec::new();
let mut operands: Vec<f64> = Vec::new();
let mut i = 0;
while i < bytes.len() {
let b0 = bytes[i];
match b0 {
0..=24 => {
let key = if b0 == 12 {
let b1 = *bytes.get(i + 1).ok_or(Error::UnexpectedEof)?;
i += 2;
1200 + b1 as u16
} else {
i += 1;
b0 as u16
};
entries.push((key, std::mem::take(&mut operands)));
}
28 => {
let hi = *bytes.get(i + 1).ok_or(Error::UnexpectedEof)?;
let lo = *bytes.get(i + 2).ok_or(Error::UnexpectedEof)?;
operands.push((i16::from_be_bytes([hi, lo])) as f64);
i += 3;
}
29 => {
let s = bytes.get(i + 1..i + 5).ok_or(Error::UnexpectedEof)?;
let v = i32::from_be_bytes([s[0], s[1], s[2], s[3]]);
operands.push(v as f64);
i += 5;
}
30 => {
let (val, consumed) = parse_real(&bytes[i + 1..])?;
operands.push(val);
i += 1 + consumed;
}
32..=246 => {
operands.push((b0 as i32 - 139) as f64);
i += 1;
}
247..=250 => {
let w = *bytes.get(i + 1).ok_or(Error::UnexpectedEof)?;
operands.push(((b0 as i32 - 247) * 256 + w as i32 + 108) as f64);
i += 2;
}
251..=254 => {
let w = *bytes.get(i + 1).ok_or(Error::UnexpectedEof)?;
operands.push((-(b0 as i32 - 251) * 256 - w as i32 - 108) as f64);
i += 2;
}
_ => return Err(Error::BadStructure("CFF DICT reserved operand byte")),
}
}
Ok(Self { entries })
}
pub fn operands(&self, key: u16) -> Option<&[f64]> {
self.entries
.iter()
.find(|(k, _)| *k == key)
.map(|(_, v)| v.as_slice())
}
pub fn contains(&self, key: u16) -> bool {
self.entries.iter().any(|(k, _)| *k == key)
}
pub fn first_int(&self, key: u16) -> Option<i32> {
self.operands(key)
.and_then(|v| v.first())
.map(|&f| f as i32)
}
pub fn first_float(&self, key: u16) -> Option<f64> {
self.operands(key).and_then(|v| v.first()).copied()
}
}
fn parse_real(bytes: &[u8]) -> Result<(f64, usize), Error> {
let mut s = String::new();
let mut consumed = 0;
'outer: for &b in bytes {
consumed += 1;
for nib in [b >> 4, b & 0x0f] {
match nib {
0..=9 => s.push((b'0' + nib) as char),
0xa => s.push('.'),
0xb => s.push('E'),
0xc => s.push_str("E-"),
0xd => return Err(Error::BadStructure("CFF real reserved nibble")),
0xe => s.push('-'),
0xf => break 'outer,
_ => unreachable!(),
}
}
}
let val = s.parse::<f64>().unwrap_or(0.0);
Ok((val, consumed))
}
pub mod op {
pub const CHARSET: u16 = 15;
pub const CHAR_STRINGS: u16 = 17;
pub const PRIVATE: u16 = 18;
pub const CHARSTRING_TYPE: u16 = 1206;
pub const ROS: u16 = 1230;
pub const FD_ARRAY: u16 = 1236;
pub const FD_SELECT: u16 = 1237;
pub const SUBRS: u16 = 19;
pub const DEFAULT_WIDTH_X: u16 = 20;
pub const NOMINAL_WIDTH_X: u16 = 21;
}
fn parse_private<'a>(data: &'a [u8], top: &Dict) -> Result<(Index<'a>, (f32, f32)), Error> {
let priv_ops = match top.operands(op::PRIVATE) {
Some(v) if v.len() >= 2 => v,
_ => return Ok((Index::empty(), (0.0, 0.0))),
};
let size = priv_ops[0] as usize;
let off = priv_ops[1] as usize;
if size == 0 {
return Ok((Index::empty(), (0.0, 0.0)));
}
let pd = data.get(off..off + size).ok_or(Error::UnexpectedEof)?;
let priv_dict = Dict::parse(pd)?;
let default_w = priv_dict.first_float(op::DEFAULT_WIDTH_X).unwrap_or(0.0) as f32;
let nominal_w = priv_dict.first_float(op::NOMINAL_WIDTH_X).unwrap_or(0.0) as f32;
let locals = match priv_dict.first_int(op::SUBRS) {
Some(subr_off) => {
let mut p = off + subr_off as usize;
Index::parse(data, &mut p)?
}
None => Index::empty(),
};
Ok((locals, (default_w, nominal_w)))
}
fn parse_cid<'a>(data: &'a [u8], top: &Dict, n_glyphs: usize) -> Result<CidData<'a>, Error> {
let fd_array_off = top
.first_int(op::FD_ARRAY)
.ok_or(Error::BadStructure("CIDFont missing FDArray"))? as usize;
let fd_select_off = top
.first_int(op::FD_SELECT)
.ok_or(Error::BadStructure("CIDFont missing FDSelect"))? as usize;
let mut p = fd_array_off;
let fd_array = Index::parse(data, &mut p)?;
let mut fd_locals = Vec::with_capacity(fd_array.count());
let mut fd_widths = Vec::with_capacity(fd_array.count());
for i in 0..fd_array.count() {
let fd_bytes = fd_array
.get(i)
.ok_or(Error::BadStructure("CIDFont FDArray entry"))?;
let fd_dict = Dict::parse(fd_bytes)?;
let (locals, w) = parse_private(data, &fd_dict)?;
fd_locals.push(locals);
fd_widths.push(w);
}
let fd_select = FdSelect::parse(data, fd_select_off, n_glyphs)?;
Ok(CidData {
fd_select,
fd_locals,
fd_widths,
})
}
#[derive(Debug, Clone)]
enum FdSelect {
Format0(Vec<u8>),
Format3 {
ranges: Vec<(u16, u8)>,
sentinel: u16,
},
}
impl FdSelect {
fn parse(data: &[u8], off: usize, n_glyphs: usize) -> Result<Self, Error> {
let format = read_u8(data, off)?;
match format {
0 => {
let arr = data
.get(off + 1..off + 1 + n_glyphs)
.ok_or(Error::UnexpectedEof)?;
Ok(FdSelect::Format0(arr.to_vec()))
}
3 => {
let n_ranges = read_u16(data, off + 1)? as usize;
let mut ranges = Vec::with_capacity(n_ranges);
let mut p = off + 3;
for _ in 0..n_ranges {
let first = read_u16(data, p)?;
let fd = read_u8(data, p + 2)?;
ranges.push((first, fd));
p += 3;
}
let sentinel = read_u16(data, p)?;
Ok(FdSelect::Format3 { ranges, sentinel })
}
_ => Err(Error::BadStructure("CFF FDSelect format unsupported")),
}
}
fn fd_for_gid(&self, gid: u16) -> u8 {
match self {
FdSelect::Format0(arr) => arr.get(gid as usize).copied().unwrap_or(0),
FdSelect::Format3 { ranges, sentinel } => {
if gid >= *sentinel {
return 0;
}
let mut fd = 0;
for &(first, this_fd) in ranges {
if first <= gid {
fd = this_fd;
} else {
break;
}
}
fd
}
}
}
}
fn parse_charset(data: &[u8], charset_off: i32, n_glyphs: usize) -> Result<Vec<u16>, Error> {
if n_glyphs == 0 {
return Ok(Vec::new());
}
if (0..=2).contains(&charset_off) {
return Ok(Vec::new());
}
let off = charset_off as usize;
let format = read_u8(data, off)?;
let mut out = Vec::with_capacity(n_glyphs);
out.push(0); match format {
0 => {
let mut p = off + 1;
for _ in 1..n_glyphs {
out.push(read_u16(data, p)?);
p += 2;
}
}
1 => {
let mut p = off + 1;
while out.len() < n_glyphs {
let first = read_u16(data, p)?;
let n_left = read_u8(data, p + 2)? as usize;
p += 3;
for k in 0..=n_left {
if out.len() >= n_glyphs {
break;
}
out.push(first.wrapping_add(k as u16));
}
}
}
2 => {
let mut p = off + 1;
while out.len() < n_glyphs {
let first = read_u16(data, p)?;
let n_left = read_u16(data, p + 2)? as usize;
p += 4;
for k in 0..=n_left {
if out.len() >= n_glyphs {
break;
}
out.push(first.wrapping_add(k as u16));
}
}
}
_ => return Err(Error::BadStructure("CFF charset format unsupported")),
}
Ok(out)
}
pub(crate) fn subr_bias(n_subrs: usize) -> i32 {
if n_subrs < 1240 {
107
} else if n_subrs < 33900 {
1131
} else {
32768
}
}
pub const CFF_TABLE_TAG: [u8; 4] = *b"CFF ";
#[cfg(test)]
mod tests {
use super::*;
fn build_index(objs: &[&[u8]]) -> Vec<u8> {
let mut out = Vec::new();
out.extend_from_slice(&(objs.len() as u16).to_be_bytes());
if objs.is_empty() {
return out;
}
out.push(1); let mut off = 1u8;
out.push(off);
for o in objs {
off += o.len() as u8;
out.push(off);
}
for o in objs {
out.extend_from_slice(o);
}
out
}
#[test]
fn index_roundtrip() {
let data = build_index(&[b"hello", b"hi", b"world"]);
let mut pos = 0;
let idx = Index::parse(&data, &mut pos).expect("parse");
assert_eq!(idx.count(), 3);
assert_eq!(idx.get(0), Some(&b"hello"[..]));
assert_eq!(idx.get(1), Some(&b"hi"[..]));
assert_eq!(idx.get(2), Some(&b"world"[..]));
assert_eq!(idx.get(3), None);
assert_eq!(pos, data.len());
}
#[test]
fn empty_index() {
let data = build_index(&[]);
let mut pos = 0;
let idx = Index::parse(&data, &mut pos).expect("parse");
assert_eq!(idx.count(), 0);
assert_eq!(idx.get(0), None);
assert_eq!(pos, 2);
}
#[test]
fn dict_integer_operands() {
let bytes = [239u8, 17];
let d = Dict::parse(&bytes).expect("parse");
assert_eq!(d.first_int(op::CHAR_STRINGS), Some(100));
}
#[test]
fn dict_two_byte_operator() {
let bytes = [141u8, 12, 6];
let d = Dict::parse(&bytes).expect("parse");
assert_eq!(d.first_int(op::CHARSTRING_TYPE), Some(2));
}
#[test]
fn dict_negative_and_large() {
let mut bytes = vec![254u8, 124]; bytes.push(29);
bytes.extend_from_slice(&100_000i32.to_be_bytes());
bytes.push(13); let d = Dict::parse(&bytes).expect("parse");
let ops = d.operands(13).expect("uniqueid");
assert_eq!(ops[0], -1000.0);
assert_eq!(ops[1], 100_000.0);
}
#[test]
fn dict_real_operand() {
let bytes = [30u8, 0x0a, 0x5f, 12, 9]; let d = Dict::parse(&bytes).expect("parse");
assert_eq!(d.first_float(1209), Some(0.5));
}
#[test]
fn bias_values() {
assert_eq!(subr_bias(0), 107);
assert_eq!(subr_bias(1239), 107);
assert_eq!(subr_bias(1240), 1131);
assert_eq!(subr_bias(33899), 1131);
assert_eq!(subr_bias(33900), 32768);
}
fn build_minimal_cff() -> Vec<u8> {
let cs0: Vec<u8> = vec![14];
let i500 = [248u8, 136];
let im500 = [252u8, 136];
let i100 = [239u8];
let i0 = [139u8];
let mut cs1: Vec<u8> = Vec::new();
cs1.extend_from_slice(&i100); cs1.extend_from_slice(&i100); cs1.push(21); cs1.extend_from_slice(&i500);
cs1.extend_from_slice(&i0);
cs1.push(5); cs1.extend_from_slice(&i0);
cs1.extend_from_slice(&i500);
cs1.push(5);
cs1.extend_from_slice(&im500);
cs1.extend_from_slice(&i0);
cs1.push(5);
cs1.push(14);
let charstrings = build_index(&[&cs0, &cs1]);
let private_dict: Vec<u8> = Vec::new();
let name = build_index(&[b"Test"]);
let strings = build_index(&[]);
let gsubrs = build_index(&[]);
let header = vec![1u8, 0, 4, 1];
fn enc5(v: i32) -> Vec<u8> {
let mut b = vec![29u8];
b.extend_from_slice(&v.to_be_bytes());
b
}
let make_top = |cs_off: i32, priv_size: i32, priv_off: i32| -> Vec<u8> {
let mut d = Vec::new();
d.extend_from_slice(&enc5(cs_off));
d.push(17); d.extend_from_slice(&enc5(priv_size));
d.extend_from_slice(&enc5(priv_off));
d.push(18); d
};
let top_dict_placeholder = make_top(0, 0, 0);
let top_index_placeholder = build_index(&[&top_dict_placeholder]);
let prefix_len =
header.len() + name.len() + top_index_placeholder.len() + strings.len() + gsubrs.len();
let cs_off = prefix_len as i32;
let priv_off = (prefix_len + charstrings.len()) as i32;
let priv_size = private_dict.len() as i32;
let top_dict = make_top(cs_off, priv_size, priv_off);
let top_index = build_index(&[&top_dict]);
assert_eq!(top_index.len(), top_index_placeholder.len());
let mut cff = Vec::new();
cff.extend_from_slice(&header);
cff.extend_from_slice(&name);
cff.extend_from_slice(&top_index);
cff.extend_from_slice(&strings);
cff.extend_from_slice(&gsubrs);
cff.extend_from_slice(&charstrings);
cff.extend_from_slice(&private_dict);
cff
}
#[test]
fn minimal_cff_outline() {
let data = build_minimal_cff();
let cff = CffTable::parse(&data).expect("parse cff");
assert_eq!(cff.glyph_count(), 2);
assert!(!cff.is_cid());
let g0 = cff.glyph_outline(0).expect("gid0");
assert!(g0.is_empty());
let g1 = cff.glyph_outline(1).expect("gid1");
assert_eq!(g1.contours.len(), 1);
let pts = &g1.contours[0].points;
assert_eq!(pts.len(), 4);
assert_eq!((pts[0].x, pts[0].y), (100, 100));
assert_eq!((pts[1].x, pts[1].y), (600, 100));
assert_eq!((pts[2].x, pts[2].y), (600, 600));
assert_eq!((pts[3].x, pts[3].y), (100, 600));
let b = g1.bounds.expect("bounds");
assert_eq!((b.x_min, b.y_min, b.x_max, b.y_max), (100, 100, 600, 600));
}
#[test]
fn standard_strings_resolve() {
assert_eq!(strings::STANDARD_STRINGS[0], ".notdef");
assert_eq!(strings::STANDARD_STRINGS[1], "space");
assert_eq!(strings::STANDARD_STRINGS[3], "quotedbl");
assert_eq!(strings::STANDARD_STRINGS[34], "A");
assert_eq!(strings::N_STD_STRINGS, 391);
assert_eq!(strings::STANDARD_STRINGS.len(), 391);
}
#[test]
fn string_for_sid_standard_and_custom() {
let data = build_minimal_cff();
let cff = CffTable::parse(&data).expect("parse cff");
assert_eq!(cff.string_for_sid(1), Some("space"));
assert_eq!(cff.string_for_sid(34), Some("A"));
assert_eq!(cff.string_for_sid(391), None);
assert_eq!(cff.string_for_sid(390), Some("Semibold"));
}
#[test]
fn predefined_charset_name_lookups() {
let data = build_minimal_cff();
let cff = CffTable::parse(&data).expect("parse cff");
assert_eq!(cff.glyph_name(0), Some(".notdef"));
assert_eq!(cff.gid_for_name(".notdef"), Some(0));
assert_eq!(cff.gid_for_name("A"), None);
assert_eq!(cff.iter_glyph_names().count(), 0);
}
}