use pdfboss_core::FastMap;
use crate::truetype::Seg;
const EEXEC_KEY: u16 = 55665;
const CHARSTRING_KEY: u16 = 4330;
const C1: u16 = 52845;
const C2: u16 = 22719;
const EEXEC_SKIP: usize = 4;
fn decrypt(cipher: &[u8], key: u16, skip: usize) -> Option<Vec<u8>> {
if cipher.len() < skip {
return None;
}
let mut r = key;
let mut plain = Vec::with_capacity(cipher.len());
for &c in cipher {
let p = c ^ (r >> 8) as u8;
r = (c as u16).wrapping_add(r).wrapping_mul(C1).wrapping_add(C2);
plain.push(p);
}
Some(plain.split_off(skip))
}
fn is_ps_whitespace(b: u8) -> bool {
matches!(b, 0x00 | b'\t' | b'\n' | 0x0c | b'\r' | b' ')
}
fn looks_like_hex(region: &[u8]) -> bool {
let mut seen = 0usize;
for &b in region {
if is_ps_whitespace(b) {
continue;
}
if !b.is_ascii_hexdigit() {
return false;
}
seen += 1;
if seen == 4 {
return true;
}
}
false
}
fn hex_decode_lenient(region: &[u8]) -> Vec<u8> {
let mut nibbles = Vec::with_capacity(region.len());
for &b in region {
if is_ps_whitespace(b) {
continue;
}
match (b as char).to_digit(16) {
Some(n) => nibbles.push(n as u8),
None => break,
}
}
nibbles
.chunks_exact(2)
.map(|pair| (pair[0] << 4) | pair[1])
.collect()
}
fn segment_raw(program: &[u8]) -> Option<(Vec<u8>, Vec<u8>)> {
const TOKEN: &[u8] = b"eexec";
let token_at = program.windows(TOKEN.len()).position(|w| w == TOKEN)?;
let after_token = token_at + TOKEN.len();
let region_start = if program.get(after_token) == Some(&b'\r')
&& program.get(after_token + 1) == Some(&b'\n')
{
after_token + 2 } else if program
.get(after_token)
.is_some_and(|&b| is_ps_whitespace(b))
{
after_token + 1
} else {
after_token
};
let clear_text = program.get(..region_start)?.to_vec();
let region = program.get(region_start..)?;
let raw = if looks_like_hex(region) {
hex_decode_lenient(region)
} else {
region.to_vec()
};
let priv_dec = decrypt(&raw, EEXEC_KEY, EEXEC_SKIP)?;
Some((clear_text, priv_dec))
}
fn segment_pfb(program: &[u8]) -> Option<(Vec<u8>, Vec<u8>)> {
let mut clear_text = Vec::new();
let mut cipher = Vec::new();
let mut pos = 0usize;
while program.get(pos) == Some(&0x80) {
let seg_type = *program.get(pos.checked_add(1)?)?;
let len_start = pos.checked_add(2)?;
let len_end = len_start.checked_add(4)?;
let len = u32::from_le_bytes(program.get(len_start..len_end)?.try_into().ok()?) as usize;
let data_start = len_end;
let data_end = data_start.checked_add(len)?;
let data = program.get(data_start..data_end)?;
match seg_type {
1 => clear_text.extend_from_slice(data),
2 => cipher.extend_from_slice(data),
_ => break, }
pos = data_end;
}
if cipher.is_empty() {
return None;
}
let priv_dec = decrypt(&cipher, EEXEC_KEY, EEXEC_SKIP)?;
Some((clear_text, priv_dec))
}
fn segment(program: &[u8]) -> Option<(Vec<u8>, Vec<u8>)> {
if program.first() == Some(&0x80) {
segment_pfb(program)
} else {
segment_raw(program)
}
}
const DEFAULT_LEN_IV: usize = 4;
const MAX_GLYPHS: usize = 65_536;
const MAX_SUBR_INDEX: usize = 65_536;
pub(crate) struct Type1Font {
charstrings: Vec<Vec<u8>>,
names: Vec<String>,
name_to_gid: FastMap<String, u16>,
subrs: Vec<Vec<u8>>,
builtin_encoding: Box<[Option<String>; 256]>,
units_per_em: f32,
}
impl Type1Font {
pub(crate) fn parse(program: Vec<u8>) -> Option<Type1Font> {
let (clear, private) = segment(&program)?;
let units_per_em = units_per_em_from_clear(&clear);
let builtin_encoding = parse_encoding(&clear);
let len_iv = parse_len_iv(&private);
let subrs = parse_subrs(&private, len_iv);
let (charstrings, names, name_to_gid) = parse_charstrings(&private, len_iv);
if charstrings.is_empty() {
return None;
}
Some(Type1Font {
charstrings,
names,
name_to_gid,
subrs,
builtin_encoding,
units_per_em,
})
}
pub(crate) fn num_glyphs(&self) -> usize {
self.charstrings.len()
}
pub(crate) fn gid_for_name(&self, name: &str) -> Option<u16> {
self.name_to_gid.get(name).copied()
}
pub(crate) fn name_for_gid(&self, gid: u16) -> Option<&str> {
self.names.get(gid as usize).map(String::as_str)
}
pub(crate) fn builtin_name(&self, code: u8) -> Option<&str> {
self.builtin_encoding[code as usize].as_deref()
}
pub(crate) fn units_per_em(&self) -> f32 {
self.units_per_em
}
pub(crate) fn glyph_path(&self, gid: u16) -> Vec<Seg> {
let Some(code) = self.charstrings.get(gid as usize) else {
return Vec::new();
};
Type1Interpreter::new(self).run(code)
}
}
const MAX_STACK: usize = 48;
const MAX_SUBR_DEPTH: u32 = 10;
const MAX_STEPS: u32 = 50_000;
const T1_HSTEM: u8 = 1;
const T1_VSTEM: u8 = 3;
const T1_VMOVETO: u8 = 4;
const T1_RLINETO: u8 = 5;
const T1_HLINETO: u8 = 6;
const T1_VLINETO: u8 = 7;
const T1_RRCURVETO: u8 = 8;
const T1_CLOSEPATH: u8 = 9;
const T1_CALLSUBR: u8 = 10;
const T1_RETURN: u8 = 11;
const T1_ESCAPE: u8 = 12;
const T1_HSBW: u8 = 13;
const T1_ENDCHAR: u8 = 14;
const T1_RMOVETO: u8 = 21;
const T1_HMOVETO: u8 = 22;
const T1_VHCURVETO: u8 = 30;
const T1_HVCURVETO: u8 = 31;
const T1E_DOTSECTION: u8 = 0;
const T1E_VSTEM3: u8 = 1;
const T1E_HSTEM3: u8 = 2;
const T1E_SEAC: u8 = 6;
const T1E_SBW: u8 = 7;
const T1E_DIV: u8 = 12;
const T1E_CALLOTHERSUBR: u8 = 16;
const T1E_POP: u8 = 17;
const T1E_SETCURRENTPOINT: u8 = 33;
enum OpResult {
Continue,
Return,
Stop,
}
struct Type1Interpreter<'a> {
font: &'a Type1Font,
stack: Vec<f32>,
ps_stack: Vec<f32>,
flex_pts: Vec<(f32, f32)>,
in_flex: bool,
x: f32,
y: f32,
ox: f32,
oy: f32,
open: bool,
depth: u32,
steps: u32,
segs: Vec<Seg>,
}
impl<'a> Type1Interpreter<'a> {
fn new(font: &'a Type1Font) -> Type1Interpreter<'a> {
Type1Interpreter {
font,
stack: Vec::new(),
ps_stack: Vec::new(),
flex_pts: Vec::new(),
in_flex: false,
x: 0.0,
y: 0.0,
ox: 0.0,
oy: 0.0,
open: false,
depth: 0,
steps: 0,
segs: Vec::new(),
}
}
fn run(mut self, code: &[u8]) -> Vec<Seg> {
self.exec(code);
self.close_current();
self.segs
}
fn subr(&self, i: usize) -> Option<&'a [u8]> {
let font: &'a Type1Font = self.font;
font.subrs.get(i).map(Vec::as_slice)
}
fn charstring(&self, gid: u16) -> Option<&'a [u8]> {
let font: &'a Type1Font = self.font;
font.charstrings.get(gid as usize).map(Vec::as_slice)
}
fn push_operand(&mut self, v: f32) {
if self.stack.len() < MAX_STACK {
self.stack.push(v);
}
}
fn arg(&self, i: usize) -> f32 {
self.stack.get(i).copied().unwrap_or(0.0)
}
fn close_current(&mut self) {
if self.open {
self.segs.push(Seg::Close);
self.open = false;
}
}
fn moveto(&mut self, dx: f32, dy: f32) {
self.x += dx;
self.y += dy;
if self.in_flex {
self.flex_pts.push((self.x, self.y));
} else {
self.close_current();
self.segs
.push(Seg::Move(self.x + self.ox, self.y + self.oy));
self.open = true;
}
}
fn lineto(&mut self, dx: f32, dy: f32) {
self.x += dx;
self.y += dy;
self.segs
.push(Seg::Line(self.x + self.ox, self.y + self.oy));
}
fn curveto(&mut self, dx1: f32, dy1: f32, dx2: f32, dy2: f32, dx3: f32, dy3: f32) {
let c1x = self.x + dx1;
let c1y = self.y + dy1;
let c2x = c1x + dx2;
let c2y = c1y + dy2;
self.x = c2x + dx3;
self.y = c2y + dy3;
self.segs.push(Seg::Cubic(
c1x + self.ox,
c1y + self.oy,
c2x + self.ox,
c2y + self.oy,
self.x + self.ox,
self.y + self.oy,
));
}
fn exec(&mut self, code: &[u8]) -> OpResult {
let mut i = 0usize;
while i < code.len() {
self.steps += 1;
if self.steps > MAX_STEPS {
return OpResult::Stop;
}
let b0 = code[i];
match b0 {
32..=246 => {
self.push_operand(b0 as f32 - 139.0);
i += 1;
}
247..=250 => {
let Some(&b1) = code.get(i + 1) else {
return OpResult::Stop;
};
self.push_operand((b0 as f32 - 247.0) * 256.0 + b1 as f32 + 108.0);
i += 2;
}
251..=254 => {
let Some(&b1) = code.get(i + 1) else {
return OpResult::Stop;
};
self.push_operand(-(b0 as f32 - 251.0) * 256.0 - b1 as f32 - 108.0);
i += 2;
}
255 => {
let Some(bytes) = code.get(i + 1..i + 5) else {
return OpResult::Stop;
};
let v = i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
self.push_operand(v as f32);
i += 5;
}
T1_ESCAPE => {
let Some(&b1) = code.get(i + 1) else {
return OpResult::Stop;
};
i += 2;
match self.exec_escape(b1) {
OpResult::Continue => {}
other => return other,
}
}
_ => {
i += 1;
match self.exec_operator(b0) {
OpResult::Continue => {}
other => return other,
}
}
}
}
OpResult::Return
}
fn exec_operator(&mut self, op: u8) -> OpResult {
match op {
T1_HSBW => {
self.x = self.arg(0);
self.y = 0.0;
self.stack.clear();
OpResult::Continue
}
T1_RLINETO => {
let (dx, dy) = (self.arg(0), self.arg(1));
self.stack.clear();
if self.open {
self.lineto(dx, dy);
}
OpResult::Continue
}
T1_HLINETO => {
let dx = self.arg(0);
self.stack.clear();
if self.open {
self.lineto(dx, 0.0);
}
OpResult::Continue
}
T1_VLINETO => {
let dy = self.arg(0);
self.stack.clear();
if self.open {
self.lineto(0.0, dy);
}
OpResult::Continue
}
T1_RRCURVETO => {
let (dx1, dy1, dx2, dy2, dx3, dy3) = (
self.arg(0),
self.arg(1),
self.arg(2),
self.arg(3),
self.arg(4),
self.arg(5),
);
self.stack.clear();
if self.open {
self.curveto(dx1, dy1, dx2, dy2, dx3, dy3);
}
OpResult::Continue
}
T1_VHCURVETO => {
let (dy1, dx2, dy2, dx3) = (self.arg(0), self.arg(1), self.arg(2), self.arg(3));
self.stack.clear();
if self.open {
self.curveto(0.0, dy1, dx2, dy2, dx3, 0.0);
}
OpResult::Continue
}
T1_HVCURVETO => {
let (dx1, dx2, dy2, dy3) = (self.arg(0), self.arg(1), self.arg(2), self.arg(3));
self.stack.clear();
if self.open {
self.curveto(dx1, 0.0, dx2, dy2, 0.0, dy3);
}
OpResult::Continue
}
T1_RMOVETO => {
let (dx, dy) = (self.arg(0), self.arg(1));
self.stack.clear();
self.moveto(dx, dy);
OpResult::Continue
}
T1_HMOVETO => {
let dx = self.arg(0);
self.stack.clear();
self.moveto(dx, 0.0);
OpResult::Continue
}
T1_VMOVETO => {
let dy = self.arg(0);
self.stack.clear();
self.moveto(0.0, dy);
OpResult::Continue
}
T1_CLOSEPATH => {
self.close_current();
self.stack.clear();
OpResult::Continue
}
T1_HSTEM | T1_VSTEM => {
self.stack.clear();
OpResult::Continue
}
T1_CALLSUBR => self.callsubr(),
T1_RETURN => OpResult::Return,
T1_ENDCHAR => {
self.close_current();
self.stack.clear();
OpResult::Stop
}
_ => OpResult::Stop, }
}
fn exec_escape(&mut self, op: u8) -> OpResult {
match op {
T1E_DOTSECTION | T1E_VSTEM3 | T1E_HSTEM3 => {
self.stack.clear();
OpResult::Continue
}
T1E_SBW => {
self.x = self.arg(0);
self.y = self.arg(1);
self.stack.clear();
OpResult::Continue
}
T1E_SEAC => self.seac(),
T1E_DIV => {
let b = self.stack.pop().unwrap_or(1.0);
let a = self.stack.pop().unwrap_or(0.0);
self.push_operand(if b != 0.0 { a / b } else { 0.0 });
OpResult::Continue
}
T1E_CALLOTHERSUBR => {
self.callothersubr();
OpResult::Continue
}
T1E_POP => {
let v = self.ps_stack.pop().unwrap_or(0.0);
self.push_operand(v);
OpResult::Continue
}
T1E_SETCURRENTPOINT => {
self.x = self.arg(0);
self.y = self.arg(1);
self.stack.clear();
OpResult::Continue
}
_ => {
self.stack.clear();
OpResult::Continue
}
}
}
fn callsubr(&mut self) -> OpResult {
let Some(idx) = self.stack.pop() else {
return OpResult::Continue;
};
let Ok(idx) = usize::try_from(idx as i32) else {
return OpResult::Continue;
};
let Some(bytes) = self.subr(idx) else {
return OpResult::Continue;
};
if self.depth >= MAX_SUBR_DEPTH {
return OpResult::Stop;
}
self.depth += 1;
let result = self.exec(bytes);
self.depth -= 1;
match result {
OpResult::Stop => OpResult::Stop,
_ => OpResult::Continue,
}
}
fn callothersubr(&mut self) {
let othersubr = self.stack.pop().unwrap_or(0.0) as i32;
let n = (self.stack.pop().unwrap_or(0.0).max(0.0) as usize).min(self.stack.len());
match othersubr {
1 => {
self.in_flex = true;
self.flex_pts.clear();
}
2 => {
}
0 => {
let end_y = self.stack.pop().unwrap_or(0.0);
let end_x = self.stack.pop().unwrap_or(0.0);
let _flex_depth = self.stack.pop().unwrap_or(0.0);
self.end_flex();
self.push_ps(end_y);
self.push_ps(end_x);
}
_ => {
for _ in 0..n {
let v = self.stack.pop().unwrap_or(0.0);
self.push_ps(v);
}
}
}
}
fn push_ps(&mut self, v: f32) {
if self.ps_stack.len() < MAX_STACK {
self.ps_stack.push(v);
}
}
fn end_flex(&mut self) {
self.in_flex = false;
if self.open && self.flex_pts.len() >= 7 {
let p = self.flex_pts.clone();
self.segs.push(Seg::Cubic(
p[1].0 + self.ox,
p[1].1 + self.oy,
p[2].0 + self.ox,
p[2].1 + self.oy,
p[3].0 + self.ox,
p[3].1 + self.oy,
));
self.segs.push(Seg::Cubic(
p[4].0 + self.ox,
p[4].1 + self.oy,
p[5].0 + self.ox,
p[5].1 + self.oy,
p[6].0 + self.ox,
p[6].1 + self.oy,
));
self.x = p[6].0;
self.y = p[6].1;
}
self.flex_pts.clear();
}
fn seac(&mut self) -> OpResult {
let asb = self.arg(0);
let adx = self.arg(1);
let ady = self.arg(2);
let bchar = self.arg(3);
let achar = self.arg(4);
self.stack.clear();
if self.depth >= MAX_SUBR_DEPTH {
return OpResult::Stop;
}
self.depth += 1;
self.run_component(bchar, 0.0, 0.0);
self.run_component(achar, adx - asb, ady);
self.depth -= 1;
OpResult::Stop
}
fn run_component(&mut self, code: f32, ox: f32, oy: f32) {
let Ok(code) = u8::try_from(code as i32) else {
return;
};
let Some(name) = pdfboss_encoding::standard_encoding_name(code) else {
return;
};
let Some(gid) = self.font.gid_for_name(name) else {
return;
};
let Some(bytes) = self.charstring(gid) else {
return;
};
self.close_current();
self.stack.clear();
self.ps_stack.clear();
self.flex_pts.clear();
self.in_flex = false;
self.x = 0.0;
self.y = 0.0;
self.ox = ox;
self.oy = oy;
self.exec(bytes);
self.close_current();
}
}
fn find_token(haystack: &[u8], needle: &[u8]) -> Option<usize> {
if needle.is_empty() {
return None;
}
haystack.windows(needle.len()).position(|w| w == needle)
}
fn next_token(bytes: &[u8], i: usize) -> Option<(&[u8], usize)> {
let mut p = i;
while bytes.get(p).is_some_and(|&b| is_ps_whitespace(b)) {
p += 1;
}
let start = p;
while bytes.get(p).is_some_and(|&b| !is_ps_whitespace(b)) {
p += 1;
}
if p == start {
return None;
}
bytes.get(start..p).map(|tok| (tok, p))
}
fn parse_uint_token(tok: &[u8]) -> Option<usize> {
std::str::from_utf8(tok).ok()?.parse().ok()
}
fn parse_matrix_number(tok: &[u8]) -> Option<f64> {
let tok = tok.strip_prefix(b"[").unwrap_or(tok);
std::str::from_utf8(tok).ok()?.parse().ok()
}
fn read_rd_blob(bytes: &[u8], i: usize) -> Option<(&[u8], usize)> {
let (len_tok, after_len) = next_token(bytes, i)?;
let len = parse_uint_token(len_tok)?;
let (marker, after_marker) = next_token(bytes, after_len)?;
if marker != b"RD" && marker != b"-|" {
return None;
}
if !bytes
.get(after_marker)
.is_some_and(|&b| is_ps_whitespace(b))
{
return None; }
let blob_start = after_marker.checked_add(1)?;
let blob_end = blob_start.saturating_add(len).min(bytes.len());
let blob = bytes.get(blob_start..blob_end)?;
Some((blob, blob_end))
}
fn parse_len_iv(private: &[u8]) -> usize {
let Some(pos) = find_token(private, b"/lenIV") else {
return DEFAULT_LEN_IV;
};
let after = pos.saturating_add(b"/lenIV".len());
next_token(private, after)
.and_then(|(tok, _)| parse_uint_token(tok))
.unwrap_or(DEFAULT_LEN_IV)
}
fn parse_subrs(private: &[u8], len_iv: usize) -> Vec<Vec<u8>> {
let mut subrs: Vec<Vec<u8>> = Vec::new();
let Some(subrs_pos) = find_token(private, b"/Subrs") else {
return subrs;
};
let tail = private.get(subrs_pos..).unwrap_or(&[]);
let scan_end = find_token(tail, b"/CharStrings")
.map(|off| subrs_pos.saturating_add(off))
.unwrap_or(private.len());
let mut i = subrs_pos;
while i < scan_end {
let Some((tok, after_tok)) = next_token(private, i) else {
break;
};
if tok != b"dup" {
i = after_tok;
continue;
}
let Some((idx_tok, after_idx)) = next_token(private, after_tok) else {
i = after_tok;
continue;
};
let Some(index) = parse_uint_token(idx_tok) else {
i = after_idx;
continue;
};
let Some((blob, end)) = read_rd_blob(private, after_idx) else {
i = after_idx;
continue;
};
if index < MAX_SUBR_INDEX {
if index >= subrs.len() {
subrs.resize(index + 1, Vec::new());
}
if let Some(decoded) = decrypt(blob, CHARSTRING_KEY, len_iv) {
subrs[index] = decoded;
}
}
i = end;
}
subrs
}
fn parse_charstrings(
private: &[u8],
len_iv: usize,
) -> (Vec<Vec<u8>>, Vec<String>, FastMap<String, u16>) {
let mut charstrings: Vec<Vec<u8>> = Vec::new();
let mut names: Vec<String> = Vec::new();
let mut name_to_gid: FastMap<String, u16> = FastMap::default();
let Some(cs_pos) = find_token(private, b"/CharStrings") else {
return (charstrings, names, name_to_gid);
};
let mut i = cs_pos;
while i < private.len() {
if charstrings.len() >= MAX_GLYPHS {
break;
}
let Some((tok, after_tok)) = next_token(private, i) else {
break;
};
let Some(name_bytes) = tok.strip_prefix(b"/") else {
i = after_tok;
continue;
};
let Some((blob, end)) = read_rd_blob(private, after_tok) else {
i = after_tok;
continue;
};
let Ok(name) = std::str::from_utf8(name_bytes) else {
i = end;
continue;
};
let Some(decoded) = decrypt(blob, CHARSTRING_KEY, len_iv) else {
i = end;
continue;
};
let gid = charstrings.len() as u16; charstrings.push(decoded);
names.push(name.to_string());
name_to_gid.insert(name.to_string(), gid);
i = end;
}
(charstrings, names, name_to_gid)
}
fn parse_encoding(clear: &[u8]) -> Box<[Option<String>; 256]> {
let mut table: Box<[Option<String>; 256]> = Box::new(std::array::from_fn(|_| None));
let Some(enc_pos) = find_token(clear, b"/Encoding") else {
return table;
};
let after_enc = enc_pos.saturating_add(b"/Encoding".len());
if let Some((tok, _)) = next_token(clear, after_enc) {
if tok == b"StandardEncoding" {
for (code, slot) in table.iter_mut().enumerate() {
*slot = pdfboss_encoding::standard_encoding_name(code as u8).map(String::from);
}
}
}
let mut i = enc_pos;
while i < clear.len() {
let Some((tok, after_tok)) = next_token(clear, i) else {
break;
};
if tok != b"dup" {
i = after_tok;
continue;
}
let Some((code_tok, after_code)) = next_token(clear, after_tok) else {
i = after_tok;
continue;
};
let Some(code) = parse_uint_token(code_tok) else {
i = after_code;
continue;
};
let Some((name_tok, after_name)) = next_token(clear, after_code) else {
i = after_code;
continue;
};
let Some(name_bytes) = name_tok.strip_prefix(b"/") else {
i = after_code;
continue;
};
let Some((put_tok, after_put)) = next_token(clear, after_name) else {
i = after_name;
continue;
};
if put_tok != b"put" {
i = after_name;
continue;
}
if let Ok(name) = std::str::from_utf8(name_bytes) {
if let Some(slot) = table.get_mut(code) {
*slot = Some(name.to_string());
}
}
i = after_put;
}
table
}
fn units_per_em_from_clear(clear: &[u8]) -> f32 {
let Some(pos) = find_token(clear, b"/FontMatrix") else {
return 1000.0;
};
let after = pos.saturating_add(b"/FontMatrix".len());
let a = next_token(clear, after).and_then(|(tok, _)| parse_matrix_number(tok));
match a {
Some(a) if a != 0.0 => (1.0_f64 / a).abs() as f32,
_ => 1000.0,
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::truetype::Seg;
fn encrypt(plain: &[u8], key: u16, skip: usize) -> Vec<u8> {
encrypt_with_lead(plain, key, &vec![0u8; skip])
}
fn encrypt_with_lead(plain: &[u8], key: u16, lead: &[u8]) -> Vec<u8> {
let mut r = key;
let mut out = Vec::new();
let mut buf = lead.to_vec();
buf.extend_from_slice(plain);
for &p in &buf {
let c = p ^ (r >> 8) as u8;
r = (c as u16).wrapping_add(r).wrapping_mul(C1).wrapping_add(C2);
out.push(c);
}
out
}
#[test]
fn decrypt_round_trips_eexec() {
let plain = b"/Private 10 dict dup begin";
let cipher = encrypt(plain, EEXEC_KEY, EEXEC_SKIP);
assert_eq!(
decrypt(&cipher, EEXEC_KEY, EEXEC_SKIP).as_deref(),
Some(&plain[..])
);
}
#[test]
fn decrypt_drops_skip_bytes_and_rejects_short_input() {
let cipher = encrypt(b"", CHARSTRING_KEY, 4);
assert_eq!(
decrypt(&cipher, CHARSTRING_KEY, 4).as_deref(),
Some(&b""[..])
);
assert_eq!(decrypt(&[1, 2, 3], CHARSTRING_KEY, 4), None);
}
#[test]
fn decrypt_with_wrong_key_differs() {
let plain = b"hello type1";
let cipher = encrypt(plain, EEXEC_KEY, EEXEC_SKIP);
assert_ne!(
decrypt(&cipher, 12345, EEXEC_SKIP).as_deref(),
Some(&plain[..])
);
}
fn raw_program(clear_ascii: &str, eexec_plain: &[u8]) -> Vec<u8> {
let mut p = Vec::new();
p.extend_from_slice(clear_ascii.as_bytes());
p.extend_from_slice(b"eexec\n");
p.extend_from_slice(&encrypt(eexec_plain, EEXEC_KEY, EEXEC_SKIP));
p
}
fn pfb_segment(seg_type: u8, data: &[u8]) -> Vec<u8> {
let mut s = vec![0x80, seg_type];
s.extend_from_slice(&(data.len() as u32).to_le_bytes());
s.extend_from_slice(data);
s
}
#[test]
fn segment_raw_splits_clear_and_decrypts_eexec() {
let prog = raw_program("%!FontType1\n/FontName /X def\n", b"/lenIV 4 def");
let (clear, priv_dec) = segment(&prog).expect("segment");
assert!(clear.starts_with(b"%!FontType1"));
assert_eq!(&priv_dec, b"/lenIV 4 def");
}
#[test]
fn segment_raw_single_separator_does_not_eat_a_whitespace_valued_ciphertext_byte() {
let lead = [211u8, 0, 0, 0];
let plain = b"/lenIV 4 def";
let cipher = encrypt_with_lead(plain, EEXEC_KEY, &lead);
assert_eq!(
cipher[0], 10,
"test setup: first ciphertext byte must itself be a whitespace (newline) value"
);
let mut program = Vec::new();
program.extend_from_slice(b"%!FontType1\neexec\n");
program.extend_from_slice(&cipher);
let (_clear, priv_dec) = segment(&program).expect("segment");
assert_eq!(&priv_dec, plain);
}
#[test]
fn segment_hex_eexec_is_decoded_then_decrypted() {
let bin = encrypt(b"/lenIV 4 def", EEXEC_KEY, EEXEC_SKIP);
let mut hex = String::new();
for b in &bin {
hex.push_str(&format!("{b:02x}"));
}
let mut prog = Vec::new();
prog.extend_from_slice(b"%!\neexec\n");
prog.extend_from_slice(hex.as_bytes());
let (_clear, priv_dec) = segment(&prog).expect("segment");
assert_eq!(&priv_dec, b"/lenIV 4 def");
}
#[test]
fn segment_pfb_concatenates_and_decrypts() {
let clear = b"%!FontType1\n";
let bin = encrypt(b"/lenIV 4 def", EEXEC_KEY, EEXEC_SKIP);
let mut prog = pfb_segment(1, clear);
prog.extend_from_slice(&pfb_segment(2, &bin));
prog.extend_from_slice(&pfb_segment(3, b""));
let (clear_out, priv_dec) = segment(&prog).expect("segment");
assert!(clear_out.starts_with(b"%!FontType1"));
assert_eq!(&priv_dec, b"/lenIV 4 def");
}
#[test]
fn segment_without_eexec_returns_none() {
assert!(segment(b"%!FontType1\nno private here\n").is_none());
}
#[test]
fn segment_pfb_truncated_length_field_returns_none() {
let program = [128u8, 2, 16];
assert!(segment(&program).is_none());
}
#[test]
fn segment_pfb_length_exceeds_available_bytes_returns_none() {
let program = [128u8, 2, 100, 0, 0, 0];
assert!(segment(&program).is_none());
}
#[test]
fn segment_empty_input_returns_none() {
assert!(segment(&[]).is_none());
}
fn cs_num(out: &mut Vec<u8>, v: i32) {
if (-107..=107).contains(&v) {
out.push((v + 139) as u8);
} else if (108..=1131).contains(&v) {
let v = v - 108;
out.push((v / 256 + 247) as u8);
out.push((v % 256) as u8);
} else if (-1131..=-108).contains(&v) {
let v = -v - 108;
out.push((v / 256 + 251) as u8);
out.push((v % 256) as u8);
} else {
out.push(255);
out.extend_from_slice(&v.to_be_bytes());
}
}
fn cs_op(out: &mut Vec<u8>, op: u8) {
out.push(op);
}
fn cs_escape(out: &mut Vec<u8>, op: u8) {
out.push(12);
out.push(op);
}
fn stub_charstring() -> Vec<u8> {
let mut c = Vec::new();
cs_num(&mut c, 0);
cs_num(&mut c, 1000);
cs_op(&mut c, 13); cs_op(&mut c, 14); c
}
fn build_type1_program(
font_matrix: &str,
encoding: &[(u8, &str)],
charstrings: &[(&str, Vec<u8>)],
subrs: &[(u16, Vec<u8>)],
len_iv: usize,
) -> Vec<u8> {
let mut encoding_clause = String::from("/Encoding 256 array\n");
for (code, name) in encoding {
encoding_clause.push_str(&format!("dup {code} /{name} put\n"));
}
build_type1_program_with_encoding_clause(
font_matrix,
&encoding_clause,
charstrings,
subrs,
len_iv,
)
}
fn build_type1_program_with_encoding_clause(
font_matrix: &str,
encoding_clause: &str,
charstrings: &[(&str, Vec<u8>)],
subrs: &[(u16, Vec<u8>)],
len_iv: usize,
) -> Vec<u8> {
let mut clear = String::new();
clear.push_str("%!\n");
clear.push_str(&format!("/FontMatrix {font_matrix} def\n"));
clear.push_str(encoding_clause);
let mut private = Vec::new();
private.extend_from_slice(format!("/lenIV {len_iv} def\n").as_bytes());
private.extend_from_slice(format!("/Subrs {} array\n", subrs.len()).as_bytes());
for (index, plain) in subrs {
let blob = encrypt(plain, CHARSTRING_KEY, len_iv);
private.extend_from_slice(format!("dup {index} {} RD ", blob.len()).as_bytes());
private.extend_from_slice(&blob);
private.extend_from_slice(b" NP\n");
}
private.extend_from_slice(
format!("/CharStrings {} dict dup begin\n", charstrings.len()).as_bytes(),
);
for (name, plain) in charstrings {
let blob = encrypt(plain, CHARSTRING_KEY, len_iv);
private.extend_from_slice(format!("/{name} {} RD ", blob.len()).as_bytes());
private.extend_from_slice(&blob);
private.extend_from_slice(b" ND\n");
}
private.extend_from_slice(b"end");
raw_program(&clear, &private)
}
#[test]
fn parse_reads_charstrings_encoding_and_matrix() {
let prog = build_type1_program(
"[0.001 0 0 0.001 0 0]",
&[(65u8, "A"), (66, "B")],
&[
(".notdef", stub_charstring()),
("A", stub_charstring()),
("B", stub_charstring()),
],
&[],
4,
);
let f = Type1Font::parse(prog).expect("parse");
assert_eq!(f.num_glyphs(), 3);
assert!(f.gid_for_name("A").is_some());
assert!(f.gid_for_name("B").is_some());
assert!(f.gid_for_name("nonesuch").is_none());
assert_eq!(f.builtin_name(65), Some("A"));
assert_eq!(f.builtin_name(66), Some("B"));
assert_eq!(f.units_per_em(), 1000.0);
}
#[test]
fn parse_reads_non_default_font_matrix() {
let prog = build_type1_program(
"[0.0005 0 0 0.0005 0 0]",
&[],
&[(".notdef", stub_charstring())],
&[],
4,
);
let f = Type1Font::parse(prog).expect("parse");
assert_eq!(f.units_per_em(), 2000.0);
}
#[test]
fn parse_rejects_program_without_charstrings() {
let prog = build_type1_program("[0.001 0 0 0.001 0 0]", &[], &[], &[], 4);
assert!(Type1Font::parse(prog).is_none()); }
#[test]
fn parse_tolerates_truncated_charstring_blob() {
let mut prog = build_type1_program(
"[0.001 0 0 0.001 0 0]",
&[],
&[("A", stub_charstring())],
&[],
4,
);
prog.truncate(prog.len() - 3); let _ = Type1Font::parse(prog); }
fn box_charstring() -> Vec<u8> {
let mut c = Vec::new();
cs_num(&mut c, 0);
cs_num(&mut c, 1000);
cs_op(&mut c, 13); cs_num(&mut c, 100);
cs_num(&mut c, 0);
cs_op(&mut c, 21); cs_num(&mut c, 500);
cs_num(&mut c, 0);
cs_op(&mut c, 5); cs_num(&mut c, 0);
cs_num(&mut c, 700);
cs_op(&mut c, 5); cs_num(&mut c, -500);
cs_num(&mut c, 0);
cs_op(&mut c, 5); cs_op(&mut c, 9); cs_op(&mut c, 14); c
}
pub(crate) fn build_type1_box_fixture(glyph_name: &str) -> Vec<u8> {
build_type1_program(
"[0.001 0 0 0.001 0 0]",
&[(128u8, glyph_name)],
&[
(".notdef", stub_charstring()),
(glyph_name, box_charstring()),
],
&[],
4,
)
}
pub(crate) fn build_type1_box_fixture_standard_encoding() -> Vec<u8> {
build_type1_program_with_encoding_clause(
"[0.001 0 0 0.001 0 0]",
"/Encoding StandardEncoding def\n",
&[(".notdef", stub_charstring()), ("A", box_charstring())],
&[],
4,
)
}
#[test]
fn glyph_path_decodes_rectangle() {
let prog = build_type1_program(
"[0.001 0 0 0.001 0 0]",
&[],
&[("box", box_charstring())],
&[],
4,
);
let f = Type1Font::parse(prog).expect("parse");
let gid = f.gid_for_name("box").expect("gid");
assert_eq!(
f.glyph_path(gid),
vec![
Seg::Move(100.0, 0.0),
Seg::Line(600.0, 0.0),
Seg::Line(600.0, 700.0),
Seg::Line(100.0, 700.0),
Seg::Close,
]
);
}
#[test]
fn glyph_path_decodes_rrcurveto_as_cubic() {
let mut cs = Vec::new();
cs_num(&mut cs, 0);
cs_num(&mut cs, 0);
cs_op(&mut cs, 13); cs_num(&mut cs, 0);
cs_num(&mut cs, 0);
cs_op(&mut cs, 21); for d in [100, 0, 100, 100, 0, 100] {
cs_num(&mut cs, d);
}
cs_op(&mut cs, 8);
cs_op(&mut cs, 14);
let prog = build_type1_program("[0.001 0 0 0.001 0 0]", &[], &[("c", cs)], &[], 4);
let f = Type1Font::parse(prog).expect("parse");
let gid = f.gid_for_name("c").unwrap();
assert_eq!(
f.glyph_path(gid),
vec![
Seg::Move(0.0, 0.0),
Seg::Cubic(100.0, 0.0, 200.0, 100.0, 200.0, 200.0),
Seg::Close,
]
);
}
#[test]
fn glyph_path_follows_callsubr() {
let mut subr0 = Vec::new();
cs_num(&mut subr0, 500);
cs_num(&mut subr0, 0);
cs_op(&mut subr0, 5); cs_op(&mut subr0, 11); let mut cs = Vec::new();
cs_num(&mut cs, 0);
cs_num(&mut cs, 0);
cs_op(&mut cs, 13); cs_num(&mut cs, 0);
cs_num(&mut cs, 0);
cs_op(&mut cs, 21); cs_num(&mut cs, 0);
cs_op(&mut cs, 10); cs_op(&mut cs, 14);
let prog = build_type1_program(
"[0.001 0 0 0.001 0 0]",
&[],
&[("s", cs)],
&[(0u16, subr0)],
4,
);
let f = Type1Font::parse(prog).expect("parse");
let gid = f.gid_for_name("s").unwrap();
assert_eq!(
f.glyph_path(gid),
vec![Seg::Move(0.0, 0.0), Seg::Line(500.0, 0.0), Seg::Close]
);
}
#[test]
fn glyph_path_self_recursive_subr_terminates() {
let mut subr0 = Vec::new();
cs_num(&mut subr0, 0);
cs_op(&mut subr0, 10); cs_op(&mut subr0, 11);
let mut cs = Vec::new();
cs_num(&mut cs, 0);
cs_num(&mut cs, 0);
cs_op(&mut cs, 13);
cs_num(&mut cs, 0);
cs_op(&mut cs, 10); cs_op(&mut cs, 14);
let prog = build_type1_program(
"[0.001 0 0 0.001 0 0]",
&[],
&[("g", cs)],
&[(0u16, subr0)],
4,
);
let f = Type1Font::parse(prog).expect("parse");
let gid = f.gid_for_name("g").unwrap();
let _ = f.glyph_path(gid); }
#[test]
fn glyph_path_flex_emits_two_cubics() {
let mut cs = Vec::new();
cs_num(&mut cs, 0);
cs_num(&mut cs, 0);
cs_op(&mut cs, 13); cs_num(&mut cs, 0);
cs_num(&mut cs, 0);
cs_op(&mut cs, 21); cs_num(&mut cs, 0);
cs_num(&mut cs, 1);
cs_escape(&mut cs, 16);
let deltas = [
(0, 0),
(10, 10),
(10, 0),
(10, -10),
(10, -10),
(10, 0),
(10, 10),
];
for (dx, dy) in deltas {
cs_num(&mut cs, dx);
cs_num(&mut cs, dy);
cs_op(&mut cs, 21); cs_num(&mut cs, 0);
cs_num(&mut cs, 2);
cs_escape(&mut cs, 16); }
cs_num(&mut cs, 50);
cs_num(&mut cs, 60);
cs_num(&mut cs, 0);
cs_num(&mut cs, 3);
cs_num(&mut cs, 0);
cs_escape(&mut cs, 16);
cs_escape(&mut cs, 17);
cs_escape(&mut cs, 17);
cs_escape(&mut cs, 33); cs_op(&mut cs, 14); let prog = build_type1_program("[0.001 0 0 0.001 0 0]", &[], &[("f", cs)], &[], 4);
let f = Type1Font::parse(prog).expect("parse");
let gid = f.gid_for_name("f").unwrap();
assert_eq!(
f.glyph_path(gid),
vec![
Seg::Move(0.0, 0.0),
Seg::Cubic(10.0, 10.0, 20.0, 10.0, 30.0, 0.0),
Seg::Cubic(40.0, -10.0, 50.0, -10.0, 60.0, 0.0),
Seg::Close,
]
);
}
#[test]
fn glyph_path_seac_composes_base_and_accent() {
let mut acute = Vec::new();
cs_num(&mut acute, 0);
cs_num(&mut acute, 0);
cs_op(&mut acute, 13); cs_num(&mut acute, 0);
cs_num(&mut acute, 0);
cs_op(&mut acute, 21); cs_num(&mut acute, 50);
cs_num(&mut acute, 0);
cs_op(&mut acute, 5); cs_op(&mut acute, 9);
cs_op(&mut acute, 14); let mut comp = Vec::new();
cs_num(&mut comp, 0);
cs_num(&mut comp, 0);
cs_op(&mut comp, 13); cs_num(&mut comp, 0);
cs_num(&mut comp, 200);
cs_num(&mut comp, 300);
cs_num(&mut comp, 65);
cs_num(&mut comp, 193);
cs_escape(&mut comp, 6); let prog = build_type1_program(
"[0.001 0 0 0.001 0 0]",
&[],
&[("A", box_charstring()), ("grave", acute), ("Agrave", comp)],
&[],
4,
);
let f = Type1Font::parse(prog).expect("parse");
let gid = f.gid_for_name("Agrave").unwrap();
let path = f.glyph_path(gid);
assert!(path.contains(&Seg::Move(100.0, 0.0))); assert!(path.contains(&Seg::Line(250.0, 300.0))); }
#[test]
fn glyph_path_hostile_callothersubr_n_is_bounded() {
let mut cs = Vec::new();
cs_num(&mut cs, 0);
cs_num(&mut cs, 0);
cs_op(&mut cs, 13); cs_num(&mut cs, 0);
cs_num(&mut cs, 0);
cs_op(&mut cs, 21); cs_num(&mut cs, 2_000_000_000); cs_num(&mut cs, 99); cs_escape(&mut cs, 16); cs_op(&mut cs, 14); let prog = build_type1_program("[0.001 0 0 0.001 0 0]", &[], &[("h", cs)], &[], 4);
let f = Type1Font::parse(prog).expect("parse");
let gid = f.gid_for_name("h").unwrap();
let started = std::time::Instant::now();
let _ = f.glyph_path(gid); assert!(
started.elapsed() < std::time::Duration::from_secs(5),
"hostile callothersubr n must be bounded, not loop ~2.1e9 times \
pushing into an unbounded PS stack"
);
}
#[test]
fn glyph_path_large_number_uses_signed_i32_encoding() {
let mut cs = Vec::new();
cs_num(&mut cs, 0);
cs_num(&mut cs, 0);
cs_op(&mut cs, 13); cs_num(&mut cs, 40000);
cs_num(&mut cs, 0);
cs_op(&mut cs, 21); cs_num(&mut cs, 0);
cs_num(&mut cs, 40000);
cs_op(&mut cs, 5); cs_op(&mut cs, 14); let prog = build_type1_program("[0.001 0 0 0.001 0 0]", &[], &[("n", cs)], &[], 4);
let f = Type1Font::parse(prog).expect("parse");
let gid = f.gid_for_name("n").unwrap();
assert_eq!(
f.glyph_path(gid),
vec![
Seg::Move(40000.0, 0.0),
Seg::Line(40000.0, 40000.0),
Seg::Close,
]
);
}
#[test]
fn glyph_path_large_negative_number_uses_signed_i32_encoding() {
let mut cs = Vec::new();
cs_num(&mut cs, 0);
cs_num(&mut cs, 0);
cs_op(&mut cs, 13); cs_num(&mut cs, -40000);
cs_num(&mut cs, 0);
cs_op(&mut cs, 21); cs_op(&mut cs, 14); let prog = build_type1_program("[0.001 0 0 0.001 0 0]", &[], &[("m", cs)], &[], 4);
let f = Type1Font::parse(prog).expect("parse");
let gid = f.gid_for_name("m").unwrap();
assert_eq!(
f.glyph_path(gid),
vec![Seg::Move(-40000.0, 0.0), Seg::Close]
);
}
}