use crate::cff::index::Index;
use crate::cff::subrs::bias_for;
use crate::outline::{CubicContour, CubicOutline, CubicSegment, Point};
use crate::Error;
const MAX_CALL_DEPTH: u8 = 16;
const MAX_BYTES_PROCESSED: usize = 1 << 20;
const STACK_CAP: usize = 192;
pub(crate) struct Interpreter<'a> {
stack: Vec<f32>,
out: CubicOutline,
current_contour: CubicContour,
pen: Point,
subpath_start: Point,
contour_has_data: bool,
hint_count: u32,
seen_width_op: bool,
#[allow(dead_code)]
pub(crate) glyph_width: f32,
local_subrs: Option<&'a Index<'a>>,
global_subrs: &'a Index<'a>,
depth: u8,
bytes_processed: usize,
nominal_width_x: f32,
default_width_x: f32,
}
impl<'a> Interpreter<'a> {
pub(crate) fn new(
global_subrs: &'a Index<'a>,
local_subrs: Option<&'a Index<'a>>,
nominal_width_x: f32,
default_width_x: f32,
) -> Self {
Self {
stack: Vec::with_capacity(STACK_CAP),
out: CubicOutline::default(),
current_contour: CubicContour::default(),
pen: Point::new(0.0, 0.0),
subpath_start: Point::new(0.0, 0.0),
contour_has_data: false,
hint_count: 0,
seen_width_op: false,
glyph_width: default_width_x,
local_subrs,
global_subrs,
depth: 0,
bytes_processed: 0,
nominal_width_x,
default_width_x,
}
}
pub(crate) fn run(&mut self, bytes: &[u8]) -> Result<(), Error> {
let result = self.execute(bytes);
match result {
Ok(_) | Err(Error::CharstringEnd) => Ok(()),
Err(e) => Err(e),
}
}
fn execute(&mut self, bytes: &[u8]) -> Result<(), Error> {
let mut i = 0usize;
while i < bytes.len() {
self.bytes_processed += 1;
if self.bytes_processed > MAX_BYTES_PROCESSED {
return Err(Error::CharstringTooLong);
}
let b0 = bytes[i];
if b0 == 255 {
if i + 4 >= bytes.len() {
return Err(Error::UnexpectedEof);
}
let raw =
i32::from_be_bytes([bytes[i + 1], bytes[i + 2], bytes[i + 3], bytes[i + 4]]);
let v = raw as f32 / 65536.0;
self.push(v)?;
i += 5;
continue;
}
if b0 >= 32 {
let (val, n) = parse_int_operand(bytes, i, b0)?;
self.push(val as f32)?;
i += n;
continue;
}
if b0 == 28 {
if i + 2 >= bytes.len() {
return Err(Error::UnexpectedEof);
}
let v = i16::from_be_bytes([bytes[i + 1], bytes[i + 2]]) as i32;
self.push(v as f32)?;
i += 3;
continue;
}
let op: u16 = 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
};
match op {
21 => {
self.handle_initial_width(2);
let n = self.stack.len();
if n < 2 {
return Err(Error::CharstringStackUnderflow);
}
let dx = self.stack[n - 2];
let dy = self.stack[n - 1];
self.move_to(dx, dy);
self.stack.clear();
}
22 => {
self.handle_initial_width(1);
let n = self.stack.len();
if n < 1 {
return Err(Error::CharstringStackUnderflow);
}
let dx = self.stack[n - 1];
self.move_to(dx, 0.0);
self.stack.clear();
}
4 => {
self.handle_initial_width(1);
let n = self.stack.len();
if n < 1 {
return Err(Error::CharstringStackUnderflow);
}
let dy = self.stack[n - 1];
self.move_to(0.0, dy);
self.stack.clear();
}
5 => {
let pairs = self.stack.len() / 2;
for k in 0..pairs {
let dx = self.stack[k * 2];
let dy = self.stack[k * 2 + 1];
self.line_to(dx, dy);
}
self.stack.clear();
}
6 => {
for (k, v) in self.stack.clone().into_iter().enumerate() {
if k % 2 == 0 {
self.line_to(v, 0.0);
} else {
self.line_to(0.0, v);
}
}
self.stack.clear();
}
7 => {
for (k, v) in self.stack.clone().into_iter().enumerate() {
if k % 2 == 0 {
self.line_to(0.0, v);
} else {
self.line_to(v, 0.0);
}
}
self.stack.clear();
}
8 => {
let groups = self.stack.len() / 6;
for k in 0..groups {
let s = &self.stack[k * 6..k * 6 + 6];
self.r_curve_to(s[0], s[1], s[2], s[3], s[4], s[5]);
}
self.stack.clear();
}
27 => {
self.op_hhcurveto()?;
}
31 => {
self.op_hvcurveto()?;
}
26 => {
self.op_vvcurveto()?;
}
30 => {
self.op_vhcurveto()?;
}
24 => {
let n = self.stack.len();
if n < 8 || (n - 2) % 6 != 0 {
return Err(Error::CharstringStackUnderflow);
}
let groups = (n - 2) / 6;
for k in 0..groups {
let s = &self.stack[k * 6..k * 6 + 6];
self.r_curve_to(s[0], s[1], s[2], s[3], s[4], s[5]);
}
let dx = self.stack[n - 2];
let dy = self.stack[n - 1];
self.line_to(dx, dy);
self.stack.clear();
}
25 => {
let n = self.stack.len();
if n < 8 || (n - 6) % 2 != 0 {
return Err(Error::CharstringStackUnderflow);
}
let lines = (n - 6) / 2;
for k in 0..lines {
let dx = self.stack[k * 2];
let dy = self.stack[k * 2 + 1];
self.line_to(dx, dy);
}
let s = &self.stack[lines * 2..lines * 2 + 6];
self.r_curve_to(s[0], s[1], s[2], s[3], s[4], s[5]);
self.stack.clear();
}
10 => {
let n = self.stack.len();
if n == 0 {
return Err(Error::CharstringStackUnderflow);
}
let biased = self.stack[n - 1] as i32;
self.stack.pop();
let subrs = self.local_subrs.ok_or(Error::CharstringNoLocalSubrs)?;
let idx = biased + bias_for(subrs.count);
self.call_subr(subrs, idx)?;
}
29 => {
let n = self.stack.len();
if n == 0 {
return Err(Error::CharstringStackUnderflow);
}
let biased = self.stack[n - 1] as i32;
self.stack.pop();
let subrs = self.global_subrs;
let idx = biased + bias_for(subrs.count);
self.call_subr(subrs, idx)?;
}
11 => {
return Ok(());
}
14 => {
self.handle_initial_width(0);
self.close_subpath_if_open();
return Err(Error::CharstringEnd);
}
1 | 3 | 18 | 23 => {
self.handle_initial_width_for_stem();
self.hint_count += (self.stack.len() / 2) as u32;
self.stack.clear();
}
19 | 20 => {
self.handle_initial_width_for_stem();
self.hint_count += (self.stack.len() / 2) as u32;
self.stack.clear();
let mask_bytes = (self.hint_count as usize).div_ceil(8);
if i + mask_bytes > bytes.len() {
return Err(Error::UnexpectedEof);
}
i += mask_bytes;
}
0x0C22 => self.op_flex()?,
0x0C23 => self.op_flex1()?,
0x0C24 => self.op_hflex()?,
0x0C25 => self.op_hflex1()?,
_ => {
return Err(Error::CharstringUnsupportedOp(op));
}
}
}
Ok(())
}
fn push(&mut self, v: f32) -> Result<(), Error> {
if self.stack.len() >= STACK_CAP {
return Err(Error::CharstringStackOverflow);
}
self.stack.push(v);
Ok(())
}
fn move_to(&mut self, dx: f32, dy: f32) {
self.close_subpath_if_open();
self.pen.x += dx;
self.pen.y += dy;
self.subpath_start = self.pen;
self.current_contour
.segments
.push(CubicSegment::MoveTo(self.pen));
self.contour_has_data = true;
}
fn line_to(&mut self, dx: f32, dy: f32) {
self.pen.x += dx;
self.pen.y += dy;
self.current_contour
.segments
.push(CubicSegment::LineTo(self.pen));
self.contour_has_data = true;
}
fn r_curve_to(&mut self, dxa: f32, dya: f32, dxb: f32, dyb: f32, dxc: f32, dyc: f32) {
let c1 = Point::new(self.pen.x + dxa, self.pen.y + dya);
let c2 = Point::new(c1.x + dxb, c1.y + dyb);
let end = Point::new(c2.x + dxc, c2.y + dyc);
self.pen = end;
self.current_contour
.segments
.push(CubicSegment::CurveTo { c1, c2, end });
self.contour_has_data = true;
}
fn close_subpath_if_open(&mut self) {
if self.contour_has_data {
self.current_contour.segments.push(CubicSegment::ClosePath);
let finished = std::mem::take(&mut self.current_contour);
self.out.contours.push(finished);
self.contour_has_data = false;
}
}
fn handle_initial_width(&mut self, normal_arity: usize) {
if self.seen_width_op {
return;
}
self.seen_width_op = true;
if self.stack.len() == normal_arity + 1 {
let delta = self.stack.remove(0);
self.glyph_width = self.nominal_width_x + delta;
} else {
self.glyph_width = self.default_width_x;
}
}
fn handle_initial_width_for_stem(&mut self) {
if self.seen_width_op {
return;
}
self.seen_width_op = true;
if self.stack.len() % 2 == 1 {
let delta = self.stack.remove(0);
self.glyph_width = self.nominal_width_x + delta;
} else {
self.glyph_width = self.default_width_x;
}
}
fn call_subr(&mut self, subrs: &'a Index<'a>, idx: i32) -> Result<(), Error> {
if idx < 0 || (idx as u32) >= subrs.count {
return Err(Error::CharstringBadSubrIndex(idx));
}
if self.depth >= MAX_CALL_DEPTH {
return Err(Error::CharstringTooDeep);
}
let body = subrs.entry(idx as u32)?;
self.depth += 1;
let r = self.execute(body);
self.depth -= 1;
r
}
pub(crate) fn into_outline(mut self) -> CubicOutline {
self.close_subpath_if_open();
self.out
}
fn op_hhcurveto(&mut self) -> Result<(), Error> {
let mut s = self.stack.clone();
self.stack.clear();
let mut dy1 = 0.0f32;
if s.len() % 4 == 1 {
dy1 = s.remove(0);
}
if s.len() % 4 != 0 {
return Err(Error::CharstringStackUnderflow);
}
let mut first = true;
for chunk in s.chunks_exact(4) {
let dxa = chunk[0];
let (dxb, dyb, dxc) = (chunk[1], chunk[2], chunk[3]);
let dya = if first { dy1 } else { 0.0 };
first = false;
self.r_curve_to(dxa, dya, dxb, dyb, dxc, 0.0);
}
Ok(())
}
fn op_vvcurveto(&mut self) -> Result<(), Error> {
let mut s = self.stack.clone();
self.stack.clear();
let mut dx1 = 0.0f32;
if s.len() % 4 == 1 {
dx1 = s.remove(0);
}
if s.len() % 4 != 0 {
return Err(Error::CharstringStackUnderflow);
}
let mut first = true;
for chunk in s.chunks_exact(4) {
let dya = chunk[0];
let (dxb, dyb, dyc) = (chunk[1], chunk[2], chunk[3]);
let dxa = if first { dx1 } else { 0.0 };
first = false;
self.r_curve_to(dxa, dya, dxb, dyb, 0.0, dyc);
}
Ok(())
}
fn op_hvcurveto(&mut self) -> Result<(), Error> {
let s = self.stack.clone();
self.stack.clear();
self.alt_curveto(s, true)
}
fn op_vhcurveto(&mut self) -> Result<(), Error> {
let s = self.stack.clone();
self.stack.clear();
self.alt_curveto(s, false)
}
fn alt_curveto(&mut self, mut s: Vec<f32>, h_first: bool) -> Result<(), Error> {
let trailing = if s.len() % 8 == 5 || s.len() % 8 == 1 {
Some(s.pop().unwrap())
} else {
None
};
if s.len() % 4 != 0 {
return Err(Error::CharstringStackUnderflow);
}
let mut horiz = h_first;
let mut chunks: Vec<&[f32]> = s.chunks_exact(4).collect();
let last_idx = chunks.len().saturating_sub(1);
for (i, chunk) in chunks.iter_mut().enumerate() {
let (dxa, dya, dxb, dyb, dxc, dyc);
if horiz {
dxa = chunk[0];
dya = 0.0;
dxb = chunk[1];
dyb = chunk[2];
dxc = if i == last_idx {
trailing.unwrap_or(0.0)
} else {
0.0
};
dyc = chunk[3];
} else {
dxa = 0.0;
dya = chunk[0];
dxb = chunk[1];
dyb = chunk[2];
dxc = chunk[3];
dyc = if i == last_idx {
trailing.unwrap_or(0.0)
} else {
0.0
};
}
self.r_curve_to(dxa, dya, dxb, dyb, dxc, dyc);
horiz = !horiz;
}
Ok(())
}
fn op_flex(&mut self) -> Result<(), Error> {
let n = self.stack.len();
if n != 12 && n != 13 {
return Err(Error::CharstringStackUnderflow);
}
let s = self.stack.clone();
self.stack.clear();
self.r_curve_to(s[0], s[1], s[2], s[3], s[4], s[5]);
self.r_curve_to(s[6], s[7], s[8], s[9], s[10], s[11]);
Ok(())
}
fn op_hflex(&mut self) -> Result<(), Error> {
if self.stack.len() != 7 {
return Err(Error::CharstringStackUnderflow);
}
let s = self.stack.clone();
self.stack.clear();
self.r_curve_to(s[0], 0.0, s[1], s[2], s[3], 0.0);
self.r_curve_to(s[4], 0.0, s[5], -s[2], s[6], 0.0);
Ok(())
}
fn op_hflex1(&mut self) -> Result<(), Error> {
if self.stack.len() != 9 {
return Err(Error::CharstringStackUnderflow);
}
let s = self.stack.clone();
self.stack.clear();
self.r_curve_to(s[0], s[1], s[2], s[3], s[4], 0.0);
let dy_total = s[1] + s[3] + 0.0 + s[7];
let dy6 = -dy_total;
self.r_curve_to(s[5], 0.0, s[6], -s[3] - 0.0, s[8], dy6);
Ok(())
}
fn op_flex1(&mut self) -> Result<(), Error> {
if self.stack.len() != 11 {
return Err(Error::CharstringStackUnderflow);
}
let s = self.stack.clone();
self.stack.clear();
let sum_dx = s[0] + s[2] + s[4] + s[6] + s[8];
let sum_dy = s[1] + s[3] + s[5] + s[7] + s[9];
let (dx6, dy6) = if sum_dx.abs() > sum_dy.abs() {
(s[10], -sum_dy)
} else {
(-sum_dx, s[10])
};
self.r_curve_to(s[0], s[1], s[2], s[3], s[4], s[5]);
self.r_curve_to(s[6], s[7], s[8], s[9], dx6, dy6);
Ok(())
}
}
fn parse_int_operand(bytes: &[u8], i: usize, b0: u8) -> Result<(i32, usize), Error> {
match b0 {
32..=246 => Ok((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((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((v, 2))
}
_ => Err(Error::Cff("invalid charstring integer operand byte")),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_index() -> Vec<u8> {
vec![0u8, 0]
}
fn run(cs: &[u8]) -> CubicOutline {
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
interp.run(cs).unwrap();
let mut o = interp.into_outline();
o.recompute_bounds();
o
}
#[test]
fn endchar_only_yields_empty_outline() {
let o = run(&[14]);
assert!(o.is_empty());
}
#[test]
fn single_rmoveto_lineto_box() {
let cs = [
239, 239, 21, 189, 6, 189, 7, 89, 6, 14, ];
let o = run(&cs);
assert_eq!(o.contours.len(), 1);
let segs = &o.contours[0].segments;
assert_eq!(segs.len(), 5);
assert!(matches!(segs[0], CubicSegment::MoveTo(_)));
assert!(matches!(segs[4], CubicSegment::ClosePath));
assert_eq!(o.bounds.x_min, 100.0);
assert_eq!(o.bounds.x_max, 150.0);
assert_eq!(o.bounds.y_min, 100.0);
assert_eq!(o.bounds.y_max, 150.0);
}
#[test]
fn rrcurveto_emits_cubic() {
let cs = [
239, 239, 21, 189, 139, 189, 189, 139, 189, 8, 14,
];
let o = run(&cs);
assert_eq!(o.contours.len(), 1);
let segs = &o.contours[0].segments;
assert_eq!(segs.len(), 3); if let CubicSegment::CurveTo { c1, c2, end } = segs[1] {
assert_eq!(c1, Point::new(150.0, 100.0));
assert_eq!(c2, Point::new(200.0, 150.0));
assert_eq!(end, Point::new(200.0, 200.0));
} else {
panic!("expected CurveTo, got {:?}", segs[1]);
}
}
#[test]
fn callgsubr_jumps_and_returns() {
let subr_body = vec![189, 6, 11];
let mut subr_index_bytes = vec![
0x00, 0x01, 0x01, 0x01, 0x04, ];
subr_index_bytes.extend_from_slice(&subr_body);
let subrs = Index::parse(&subr_index_bytes, 0).unwrap();
assert_eq!(subrs.count, 1);
let cs = [
239, 239, 21, 32, 29, 14, ];
let g = empty_index(); let global = subrs;
let local_index_bytes = empty_index();
let _local = Index::parse(&local_index_bytes, 0).unwrap();
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
interp.run(&cs).unwrap();
let mut o = interp.into_outline();
o.recompute_bounds();
let segs = &o.contours[0].segments;
assert_eq!(segs.len(), 3);
if let CubicSegment::LineTo(p) = segs[1] {
assert_eq!(p, Point::new(150.0, 100.0));
} else {
panic!("expected LineTo");
}
let _ = g; }
#[test]
fn hintmask_skips_correct_bitmask_size() {
let cs = [
139, 189, 1, 139, 167, 3, 19, 0xFF, 239, 239, 21, 14,
];
let o = run(&cs);
assert_eq!(o.contours.len(), 1);
}
#[test]
fn width_decoded_from_first_op_extra_operand() {
let cs = [189, 239, 22, 14]; let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let mut interp = Interpreter::new(&global, None, 100.0, 500.0);
interp.run(&cs).unwrap();
assert_eq!(interp.glyph_width, 150.0);
}
#[test]
fn width_default_when_no_extra_operand() {
let cs = [239, 22, 14];
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let mut interp = Interpreter::new(&global, None, 100.0, 500.0);
interp.run(&cs).unwrap();
assert_eq!(interp.glyph_width, 500.0);
}
}