use crate::Error;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum Operand {
Int(i32),
Real(f64),
}
impl Operand {
pub(crate) fn as_int(self) -> Option<i32> {
match self {
Self::Int(n) => Some(n),
Self::Real(_) => None,
}
}
#[allow(dead_code)]
pub(crate) fn as_f64(self) -> f64 {
match self {
Self::Int(n) => n as f64,
Self::Real(r) => r,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u16)]
#[allow(clippy::upper_case_acronyms)] pub(crate) enum Operator {
Version = 0,
Notice = 1,
FullName = 2,
FamilyName = 3,
Weight = 4,
FontBBox = 5,
UniqueID = 13,
XUID = 14,
Charset = 15,
Encoding = 16,
CharStrings = 17,
Private = 18,
BlueValues = 6,
OtherBlues = 7,
FamilyBlues = 8,
FamilyOtherBlues = 9,
StdHW = 10,
StdVW = 11,
Copyright = 0x0C00,
IsFixedPitch = 0x0C01,
ItalicAngle = 0x0C02,
UnderlinePosition = 0x0C03,
UnderlineThickness = 0x0C04,
PaintType = 0x0C05,
CharstringType = 0x0C06,
FontMatrix = 0x0C07,
StrokeWidth = 0x0C08,
SyntheticBase = 0x0C14,
PostScript = 0x0C15,
BaseFontName = 0x0C16,
BaseFontBlend = 0x0C17,
BlueScale = 0x0C09,
BlueShift = 0x0C0A,
BlueFuzz = 0x0C0B,
StemSnapH = 0x0C0C,
StemSnapV = 0x0C0D,
ForceBold = 0x0C0E,
LanguageGroup = 0x0C11,
ExpansionFactor = 0x0C12,
InitialRandomSeed = 0x0C13,
DefaultWidthX = 20,
NominalWidthX = 21,
Subrs = 19,
Ros = 0x0C1E,
CidFontVersion = 0x0C1F,
CidFontRevision = 0x0C20,
CidFontType = 0x0C21,
CidCount = 0x0C22,
UidBase = 0x0C23,
FdArray = 0x0C24,
FdSelect = 0x0C25,
FontName = 0x0C26,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct Dict {
entries: Vec<(u16, Vec<Operand>)>,
}
impl Dict {
pub(crate) fn parse(bytes: &[u8]) -> Result<Self, Error> {
let mut entries: Vec<(u16, Vec<Operand>)> = Vec::new();
let mut operands: Vec<Operand> = Vec::new();
let mut i = 0usize;
while i < bytes.len() {
let b0 = bytes[i];
if b0 <= 21 || b0 == 24 {
let op = if b0 == 12 {
if i + 1 >= bytes.len() {
return Err(Error::UnexpectedEof);
}
let sub = bytes[i + 1];
i += 2;
0x0C00u16 | sub as u16
} else {
i += 1;
b0 as u16
};
let mut consumed = Vec::new();
std::mem::swap(&mut consumed, &mut operands);
entries.push((op, consumed));
} else {
let (operand, consumed) = parse_operand(bytes, i)?;
operands.push(operand);
i += consumed;
}
}
Ok(Self { entries })
}
pub(crate) fn get_int(&self, op: Operator) -> Option<i32> {
let want = op as u16;
for (k, v) in &self.entries {
if *k == want {
return v.last().and_then(|o| o.as_int());
}
}
None
}
pub(crate) fn get_array(&self, op: Operator) -> Option<&[Operand]> {
let want = op as u16;
for (k, v) in &self.entries {
if *k == want {
return Some(v);
}
}
None
}
pub(crate) fn get_number(&self, op: Operator) -> Option<f64> {
let want = op as u16;
for (k, v) in &self.entries {
if *k == want {
return v.last().map(|o| o.as_f64());
}
}
None
}
#[allow(dead_code)]
pub(crate) fn iter(&self) -> impl Iterator<Item = &(u16, Vec<Operand>)> {
self.entries.iter()
}
}
fn parse_operand(bytes: &[u8], i: usize) -> Result<(Operand, usize), Error> {
let b0 = bytes[i];
match b0 {
32..=246 => Ok((Operand::Int(b0 as i32 - 139), 1)),
247..=250 => {
if i + 1 >= bytes.len() {
return Err(Error::UnexpectedEof);
}
let v = (b0 as i32 - 247) * 256 + bytes[i + 1] as i32 + 108;
Ok((Operand::Int(v), 2))
}
251..=254 => {
if i + 1 >= bytes.len() {
return Err(Error::UnexpectedEof);
}
let v = -((b0 as i32 - 251) * 256) - bytes[i + 1] as i32 - 108;
Ok((Operand::Int(v), 2))
}
28 => {
if i + 2 >= bytes.len() {
return Err(Error::UnexpectedEof);
}
let v = i16::from_be_bytes([bytes[i + 1], bytes[i + 2]]) as i32;
Ok((Operand::Int(v), 3))
}
29 => {
if i + 4 >= bytes.len() {
return Err(Error::UnexpectedEof);
}
let v = i32::from_be_bytes([bytes[i + 1], bytes[i + 2], bytes[i + 3], bytes[i + 4]]);
Ok((Operand::Int(v), 5))
}
30 => parse_bcd(bytes, i),
_ => Err(Error::Cff("invalid DICT operand byte")),
}
}
fn parse_bcd(bytes: &[u8], i: usize) -> Result<(Operand, usize), Error> {
let mut s = String::with_capacity(16);
let mut j = i + 1;
loop {
if j >= bytes.len() {
return Err(Error::UnexpectedEof);
}
let b = bytes[j];
let nibbles = [(b >> 4) & 0xf, b & 0xf];
let mut done = false;
for &n in &nibbles {
match n {
0..=9 => s.push((b'0' + n) as char),
0xa => s.push('.'),
0xb => s.push('E'),
0xc => s.push_str("E-"),
0xe => s.push('-'),
0xf => {
done = true;
break;
}
0xd => return Err(Error::Cff("reserved BCD nibble (d)")),
_ => unreachable!("masked to 4 bits"),
}
}
j += 1;
if done {
break;
}
if j - i > 32 {
return Err(Error::Cff("BCD real too long"));
}
}
let v: f64 = s.parse().map_err(|_| Error::Cff("malformed BCD real"))?;
Ok((Operand::Real(v), j - i))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn integer_ranges() {
let d = Dict::parse(&[139, 0]).unwrap();
assert_eq!(d.get_int(Operator::Version), Some(0));
let d = Dict::parse(&[239, 0]).unwrap();
assert_eq!(d.get_int(Operator::Version), Some(100));
let d = Dict::parse(&[39, 0]).unwrap();
assert_eq!(d.get_int(Operator::Version), Some(-100));
let d = Dict::parse(&[250, 124, 0]).unwrap();
assert_eq!(d.get_int(Operator::Version), Some(1000));
let d = Dict::parse(&[254, 124, 0]).unwrap();
assert_eq!(d.get_int(Operator::Version), Some(-1000));
let d = Dict::parse(&[28, 0x75, 0x30, 0]).unwrap();
assert_eq!(d.get_int(Operator::Version), Some(0x7530));
let d = Dict::parse(&[29, 0, 3, 0x0d, 0x40, 0]).unwrap();
assert_eq!(d.get_int(Operator::Version), Some(200000));
}
#[test]
fn private_array_pair() {
let d = Dict::parse(&[181, 239, 18]).unwrap();
let arr = d.get_array(Operator::Private).expect("Private");
assert_eq!(arr.len(), 2);
assert_eq!(arr[0].as_int(), Some(42));
assert_eq!(arr[1].as_int(), Some(100));
}
#[test]
fn two_byte_operator() {
let d = Dict::parse(&[129, 12, 2]).unwrap();
assert_eq!(d.get_int(Operator::ItalicAngle), Some(-10));
}
#[test]
fn bcd_real_one_point_five() {
let d = Dict::parse(&[30, 0x1a, 0x5f, 12, 2]).unwrap();
let arr = d.get_array(Operator::ItalicAngle).unwrap();
assert_eq!(arr.len(), 1);
assert!(matches!(arr[0], Operand::Real(_)));
let v = arr[0].as_f64();
assert!((v - 1.5).abs() < 1e-9);
}
#[test]
fn bcd_real_negative_exponent() {
let d = Dict::parse(&[30, 0xe2, 0xa2, 0x5c, 0x3f, 12, 2]).unwrap();
let arr = d.get_array(Operator::ItalicAngle).unwrap();
let v = arr[0].as_f64();
assert!((v - -0.00225).abs() < 1e-12, "got {v}");
}
}