use crate::cff::charset::Charset;
use crate::cff::encoding::STANDARD_ENCODING;
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;
const TRANSIENT_LEN: usize = 32;
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,
charstrings: Option<&'a Index<'a>>,
charset: Option<&'a Charset<'a>>,
is_seac_component: bool,
transient: [f32; TRANSIENT_LEN],
rng_state: u32,
}
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,
charstrings: None,
charset: None,
is_seac_component: false,
transient: [0.0; TRANSIENT_LEN],
rng_state: 0x2545_F491,
}
}
pub(crate) fn with_seac_resolver(
mut self,
charstrings: &'a Index<'a>,
charset: &'a Charset<'a>,
) -> Self {
self.charstrings = Some(charstrings);
self.charset = Some(charset);
self
}
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 => {
let n = self.stack.len();
if n == 4 || n == 5 {
self.op_seac()?;
return Err(Error::CharstringEnd);
}
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_hflex()?,
0x0C23 => self.op_flex()?,
0x0C24 => self.op_hflex1()?,
0x0C25 => self.op_flex1()?,
0x0C09 => { let a = self.arith_pop1()?; self.push(a.abs())?; }
0x0C0A => { let (a, b) = self.arith_pop2()?; self.push(a + b)?; }
0x0C0B => { let (a, b) = self.arith_pop2()?; self.push(a - b)?; }
0x0C0C => {
let (a, b) = self.arith_pop2()?;
self.push(if b == 0.0 { 0.0 } else { a / b })?;
}
0x0C0E => { let a = self.arith_pop1()?; self.push(-a)?; }
0x0C18 => { let (a, b) = self.arith_pop2()?; self.push(a * b)?; }
0x0C1A => {
let a = self.arith_pop1()?;
self.push(if a < 0.0 { 0.0 } else { a.sqrt() })?;
}
0x0C17 => { let r = self.next_random(); self.push(r)?; }
0x0C12 => { self.arith_pop1()?; }
0x0C1B => {
let a = self.arith_pop1()?;
self.push(a)?;
self.push(a)?;
}
0x0C1C => {
let n = self.stack.len();
if n < 2 {
return Err(Error::CharstringStackUnderflow);
}
self.stack.swap(n - 1, n - 2);
}
0x0C1D => self.op_index()?,
0x0C1E => self.op_roll()?,
0x0C14 => {
let (val, idx) = self.arith_pop2()?;
let i = idx as i32;
if i < 0 || i as usize >= TRANSIENT_LEN {
return Err(Error::CharstringTransientIndex(i));
}
self.transient[i as usize] = val;
}
0x0C15 => {
let idx = self.arith_pop1()?;
let i = idx as i32;
if i < 0 || i as usize >= TRANSIENT_LEN {
return Err(Error::CharstringTransientIndex(i));
}
let v = self.transient[i as usize];
self.push(v)?;
}
0x0C03 => {
let (a, b) = self.arith_pop2()?;
self.push(bool01(a != 0.0 && b != 0.0))?;
}
0x0C04 => {
let (a, b) = self.arith_pop2()?;
self.push(bool01(a != 0.0 || b != 0.0))?;
}
0x0C05 => {
let a = self.arith_pop1()?;
self.push(bool01(a == 0.0))?;
}
0x0C0F => {
let (a, b) = self.arith_pop2()?;
self.push(bool01(a == b))?;
}
0x0C16 => {
let n = self.stack.len();
if n < 4 {
return Err(Error::CharstringStackUnderflow);
}
let v2 = self.stack.pop().unwrap();
let v1 = self.stack.pop().unwrap();
let s2 = self.stack.pop().unwrap();
let s1 = self.stack.pop().unwrap();
self.push(if v1 <= v2 { s1 } else { s2 })?;
}
_ => {
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 arith_pop1(&mut self) -> Result<f32, Error> {
self.stack.pop().ok_or(Error::CharstringStackUnderflow)
}
fn arith_pop2(&mut self) -> Result<(f32, f32), Error> {
let b = self.stack.pop().ok_or(Error::CharstringStackUnderflow)?;
let a = self.stack.pop().ok_or(Error::CharstringStackUnderflow)?;
Ok((a, b))
}
fn op_index(&mut self) -> Result<(), Error> {
let i = self.arith_pop1()? as i32;
let n = self.stack.len();
if n == 0 {
return Err(Error::CharstringStackUnderflow);
}
let depth = if i < 0 { 0usize } else { i as usize };
if depth >= n {
return Err(Error::CharstringStackUnderflow);
}
let v = self.stack[n - 1 - depth];
self.push(v)
}
fn op_roll(&mut self) -> Result<(), Error> {
let j = self.arith_pop1()? as i32;
let nf = self.arith_pop1()?;
let n = nf as i32;
if n < 0 {
return Err(Error::CharstringStackUnderflow);
}
let n = n as usize;
if n == 0 {
return Ok(());
}
let len = self.stack.len();
if n > len {
return Err(Error::CharstringStackUnderflow);
}
let base = len - n;
let shift = j.rem_euclid(n as i32) as usize;
if shift != 0 {
self.stack[base..].rotate_right(shift);
}
Ok(())
}
fn next_random(&mut self) -> f32 {
self.rng_state = self
.rng_state
.wrapping_mul(1_664_525)
.wrapping_add(1_013_904_223);
let bits = (self.rng_state >> 8) & 0x00FF_FFFF;
(bits as f32 + 1.0) / 16_777_216.0
}
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();
let (dx1, dx2, dy2, dx3, dx4, dx5, dx6) = (s[0], s[1], s[2], s[3], s[4], s[5], s[6]);
self.r_curve_to(dx1, 0.0, dx2, dy2, dx3, 0.0);
self.r_curve_to(dx4, 0.0, dx5, -dy2, dx6, 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();
let (dx1, dy1, dx2, dy2, dx3) = (s[0], s[1], s[2], s[3], s[4]);
let (dx4, dx5, dy5, dx6) = (s[5], s[6], s[7], s[8]);
let dy6 = -(dy1 + dy2 + dy5);
self.r_curve_to(dx1, dy1, dx2, dy2, dx3, 0.0);
self.r_curve_to(dx4, 0.0, dx5, dy5, dx6, dy6);
Ok(())
}
fn op_seac(&mut self) -> Result<(), Error> {
if self.is_seac_component {
return Err(Error::CharstringSeacNested);
}
self.handle_initial_width(4);
if self.stack.len() != 4 {
return Err(Error::CharstringStackUnderflow);
}
let achar = self.stack[3] as i32;
let bchar = self.stack[2] as i32;
let ady = self.stack[1];
let adx = self.stack[0];
self.stack.clear();
let charstrings = self
.charstrings
.ok_or(Error::CharstringSeacBadComponent(0))?;
let charset = self.charset.ok_or(Error::CharstringSeacBadComponent(0))?;
let b_gid = resolve_seac_component(bchar, charset)?;
let a_gid = resolve_seac_component(achar, charset)?;
self.close_subpath_if_open();
self.render_seac_component(b_gid, 0.0, 0.0, charstrings, charset)?;
self.render_seac_component(a_gid, adx, ady, charstrings, charset)?;
Ok(())
}
fn render_seac_component(
&mut self,
gid: u16,
off_x: f32,
off_y: f32,
charstrings: &'a Index<'a>,
charset: &'a Charset<'a>,
) -> Result<(), Error> {
let body = charstrings
.entry(gid as u32)
.map_err(|_| Error::CharstringSeacBadComponent(0))?;
let mut sub = Interpreter::new(
self.global_subrs,
self.local_subrs,
self.nominal_width_x,
self.default_width_x,
)
.with_seac_resolver(charstrings, charset);
sub.is_seac_component = true;
sub.run(body)?;
let component_outline = sub.into_outline();
for contour in component_outline.contours.into_iter() {
let mut shifted = CubicContour::default();
for seg in contour.segments.into_iter() {
let shifted_seg = match seg {
CubicSegment::MoveTo(p) => {
CubicSegment::MoveTo(Point::new(p.x + off_x, p.y + off_y))
}
CubicSegment::LineTo(p) => {
CubicSegment::LineTo(Point::new(p.x + off_x, p.y + off_y))
}
CubicSegment::CurveTo { c1, c2, end } => CubicSegment::CurveTo {
c1: Point::new(c1.x + off_x, c1.y + off_y),
c2: Point::new(c2.x + off_x, c2.y + off_y),
end: Point::new(end.x + off_x, end.y + off_y),
},
CubicSegment::ClosePath => CubicSegment::ClosePath,
};
shifted.segments.push(shifted_seg);
}
if !shifted.segments.is_empty() {
self.out.contours.push(shifted);
}
}
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 resolve_seac_component(code: i32, charset: &Charset<'_>) -> Result<u16, Error> {
if !(0..=255).contains(&code) {
return Err(Error::CharstringSeacBadComponent(0));
}
let code_u = code as u8;
let sid = STANDARD_ENCODING[code_u as usize];
if sid == 0 {
return Err(Error::CharstringSeacBadComponent(code_u));
}
charset
.gid_of_sid(sid)
.ok_or(Error::CharstringSeacBadComponent(code_u))
}
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")),
}
}
fn bool01(b: bool) -> f32 {
if b {
1.0
} else {
0.0
}
}
#[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);
}
fn cubic_endpoint(seg: &CubicSegment) -> Point {
match *seg {
CubicSegment::MoveTo(p) | CubicSegment::LineTo(p) => p,
CubicSegment::CurveTo { end, .. } => end,
CubicSegment::ClosePath => Point::new(f32::NAN, f32::NAN),
}
}
#[test]
fn flex_op_expands_to_two_cubics() {
let cs = [
239, 239, 21, 149, 144, 149, 149, 149, 144, 149, 134, 149, 129, 149, 134, 189, 12, 35, 14, ];
let o = run(&cs);
assert_eq!(o.contours.len(), 1);
let segs = &o.contours[0].segments;
assert_eq!(segs.len(), 4);
if let CubicSegment::CurveTo { c1, c2, end } = segs[1] {
assert_eq!(c1, Point::new(110.0, 105.0));
assert_eq!(c2, Point::new(120.0, 115.0));
assert_eq!(end, Point::new(130.0, 120.0));
} else {
panic!("expected first CurveTo, got {:?}", segs[1]);
}
if let CubicSegment::CurveTo { c1, c2, end } = segs[2] {
assert_eq!(c1, Point::new(140.0, 115.0));
assert_eq!(c2, Point::new(150.0, 105.0));
assert_eq!(end, Point::new(160.0, 100.0));
} else {
panic!("expected second CurveTo, got {:?}", segs[2]);
}
}
#[test]
fn hflex_op_returns_to_baseline_y() {
let cs = [
239, 239, 21, 149, 149, 159, 149, 149, 149, 149, 12, 34, 14, ];
let o = run(&cs);
assert_eq!(o.contours.len(), 1);
let segs = &o.contours[0].segments;
assert_eq!(segs.len(), 4);
if let CubicSegment::CurveTo { c1, c2, end } = segs[1] {
assert_eq!(c1, Point::new(110.0, 100.0));
assert_eq!(c2, Point::new(120.0, 120.0));
assert_eq!(end, Point::new(130.0, 120.0));
} else {
panic!("expected hflex curve 1, got {:?}", segs[1]);
}
if let CubicSegment::CurveTo { c1, c2, end } = segs[2] {
assert_eq!(c1, Point::new(140.0, 120.0));
assert_eq!(c2, Point::new(150.0, 100.0));
assert_eq!(end, Point::new(160.0, 100.0));
} else {
panic!("expected hflex curve 2, got {:?}", segs[2]);
}
assert_eq!(cubic_endpoint(&segs[2]).y, 100.0);
}
#[test]
fn hflex1_op_closes_to_start_y() {
let cs = [
239, 239, 21, 149, 144, 149, 149, 149, 149, 149, 134, 149, 12, 36, 14, ];
let o = run(&cs);
assert_eq!(o.contours.len(), 1);
let segs = &o.contours[0].segments;
assert_eq!(segs.len(), 4);
if let CubicSegment::CurveTo { c1, c2, end } = segs[1] {
assert_eq!(c1, Point::new(110.0, 105.0));
assert_eq!(c2, Point::new(120.0, 115.0));
assert_eq!(end, Point::new(130.0, 115.0));
} else {
panic!("expected hflex1 curve 1, got {:?}", segs[1]);
}
if let CubicSegment::CurveTo { c1, c2, end } = segs[2] {
assert_eq!(c1, Point::new(140.0, 115.0));
assert_eq!(c2, Point::new(150.0, 110.0));
assert_eq!(end, Point::new(160.0, 100.0));
} else {
panic!("expected hflex1 curve 2, got {:?}", segs[2]);
}
assert_eq!(cubic_endpoint(&segs[2]).y, 100.0);
}
#[test]
fn flex1_op_picks_dominant_x_axis() {
let cs = [
239, 239, 21, 149, 144, 149, 144, 149, 144, 149, 134, 149, 134, 189, 12, 37, 14, ];
let o = run(&cs);
assert_eq!(o.contours.len(), 1);
let segs = &o.contours[0].segments;
assert_eq!(segs.len(), 4);
if let CubicSegment::CurveTo { c1, c2, end } = segs[1] {
assert_eq!(c1, Point::new(110.0, 105.0));
assert_eq!(c2, Point::new(120.0, 110.0));
assert_eq!(end, Point::new(130.0, 115.0));
} else {
panic!("expected flex1 curve 1, got {:?}", segs[1]);
}
if let CubicSegment::CurveTo { c1, c2, end } = segs[2] {
assert_eq!(c1, Point::new(140.0, 110.0));
assert_eq!(c2, Point::new(150.0, 105.0));
assert_eq!(end, Point::new(200.0, 100.0));
} else {
panic!("expected flex1 curve 2, got {:?}", segs[2]);
}
assert_eq!(cubic_endpoint(&segs[2]).y, 100.0);
}
#[test]
fn flex1_op_picks_dominant_y_axis() {
let cs = [
239, 239, 21, 144, 149, 144, 149, 144, 149, 134, 149, 134, 149, 189, 12, 37, 14, ];
let o = run(&cs);
let segs = &o.contours[0].segments;
assert_eq!(segs.len(), 4);
if let CubicSegment::CurveTo { c1, c2, end } = segs[1] {
assert_eq!(c1, Point::new(105.0, 110.0));
assert_eq!(c2, Point::new(110.0, 120.0));
assert_eq!(end, Point::new(115.0, 130.0));
} else {
panic!("expected flex1-y curve 1, got {:?}", segs[1]);
}
if let CubicSegment::CurveTo { c1, c2, end } = segs[2] {
assert_eq!(c1, Point::new(110.0, 140.0));
assert_eq!(c2, Point::new(105.0, 150.0));
assert_eq!(end, Point::new(100.0, 200.0));
} else {
panic!("expected flex1-y curve 2, got {:?}", segs[2]);
}
assert_eq!(cubic_endpoint(&segs[2]).x, 100.0);
}
#[test]
fn flex_op_rejects_wrong_arity() {
let cs = [
239, 239, 21, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 12, 35, 14,
];
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
let r = interp.run(&cs);
assert!(matches!(r, Err(Error::CharstringStackUnderflow)));
}
#[test]
fn hflex_op_rejects_wrong_arity() {
let cs = [
239, 239, 21, 149, 149, 149, 149, 149, 149, 12, 34, 14,
];
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
let r = interp.run(&cs);
assert!(matches!(r, Err(Error::CharstringStackUnderflow)));
}
#[test]
fn hflex1_op_rejects_wrong_arity() {
let cs = [
239, 239, 21, 149, 149, 149, 149, 149, 149, 149, 149, 12, 36, 14,
];
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
let r = interp.run(&cs);
assert!(matches!(r, Err(Error::CharstringStackUnderflow)));
}
#[test]
fn flex1_op_rejects_wrong_arity() {
let cs = [
239, 239, 21, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 12, 37, 14,
];
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
let r = interp.run(&cs);
assert!(matches!(r, Err(Error::CharstringStackUnderflow)));
}
fn build_seac_fixture() -> (Vec<u8>, Vec<u8>) {
let notdef: Vec<u8> = vec![14];
let a: Vec<u8> = vec![
149, 149, 21, 159, 6, 159, 7, 119, 6, 14, ];
let grave: Vec<u8> = vec![
144, 144, 21, 149, 6, 149, 7, 129, 6, 14, ];
let mut data = Vec::new();
data.extend_from_slice(¬def);
data.extend_from_slice(&a);
data.extend_from_slice(&grave);
let off1 = 1u8;
let off2 = (1 + notdef.len()) as u8;
let off3 = (1 + notdef.len() + a.len()) as u8;
let off4 = (1 + notdef.len() + a.len() + grave.len()) as u8;
let mut bytes = vec![
0x00, 0x03, 0x01, off1, off2, off3, off4,
];
bytes.extend_from_slice(&data);
let charset_payload = vec![0x00, 0x22, 0x00, 0x7C];
(bytes, charset_payload)
}
#[test]
fn seac_composes_two_components_with_offset() {
let (cs_bytes, charset_payload) = build_seac_fixture();
let charstrings = Index::parse(&cs_bytes, 0).unwrap();
assert_eq!(charstrings.count, 3);
let charset = Charset::Format0 {
bytes: &charset_payload,
num_glyphs: 3,
};
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let top_cs = [
239, 189, 204, 247, 85, 14, ];
let mut interp =
Interpreter::new(&global, None, 0.0, 500.0).with_seac_resolver(&charstrings, &charset);
interp.run(&top_cs).unwrap();
let mut outline = interp.into_outline();
outline.recompute_bounds();
assert_eq!(outline.contours.len(), 2);
assert_eq!(outline.bounds.x_min, 10.0);
assert_eq!(outline.bounds.y_min, 10.0);
assert_eq!(outline.bounds.x_max, 115.0);
assert_eq!(outline.bounds.y_max, 65.0);
if let CubicSegment::MoveTo(p) = outline.contours[0].segments[0] {
assert_eq!(p, Point::new(10.0, 10.0));
} else {
panic!("bchar should open with MoveTo");
}
if let CubicSegment::MoveTo(p) = outline.contours[1].segments[0] {
assert_eq!(p, Point::new(105.0, 55.0));
} else {
panic!("achar should open with MoveTo");
}
}
#[test]
fn seac_with_width_extra_operand_records_glyph_width() {
let (cs_bytes, charset_payload) = build_seac_fixture();
let charstrings = Index::parse(&cs_bytes, 0).unwrap();
let charset = Charset::Format0 {
bytes: &charset_payload,
num_glyphs: 3,
};
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let top_cs = [
189, 239, 189, 204, 247, 85, 14, ];
let mut interp = Interpreter::new(&global, None, 100.0, 500.0)
.with_seac_resolver(&charstrings, &charset);
interp.run(&top_cs).unwrap();
assert_eq!(interp.glyph_width, 150.0);
}
#[test]
fn seac_bchar_not_in_charset_is_an_error() {
let (cs_bytes, charset_payload) = build_seac_fixture();
let charstrings = Index::parse(&cs_bytes, 0).unwrap();
let charset = Charset::Format0 {
bytes: &charset_payload,
num_glyphs: 3,
};
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let top_cs = [
139, 139, 229, 204, 14, ];
let mut interp =
Interpreter::new(&global, None, 0.0, 500.0).with_seac_resolver(&charstrings, &charset);
let r = interp.run(&top_cs);
assert!(matches!(r, Err(Error::CharstringSeacBadComponent(90))));
}
#[test]
fn seac_without_resolver_errors_out() {
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let top_cs = [139, 139, 204, 247, 85, 14];
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
let r = interp.run(&top_cs);
assert!(matches!(r, Err(Error::CharstringSeacBadComponent(_))));
}
#[test]
fn seac_nested_is_rejected() {
let nested_a: Vec<u8> = vec![139, 139, 204, 204, 14];
let grave: Vec<u8> = vec![144, 144, 21, 149, 6, 149, 7, 129, 6, 14];
let notdef: Vec<u8> = vec![14];
let mut data = Vec::new();
data.extend_from_slice(¬def);
data.extend_from_slice(&nested_a);
data.extend_from_slice(&grave);
let off1 = 1u8;
let off2 = (1 + notdef.len()) as u8;
let off3 = (1 + notdef.len() + nested_a.len()) as u8;
let off4 = (1 + notdef.len() + nested_a.len() + grave.len()) as u8;
let mut bytes = vec![0x00, 0x03, 0x01, off1, off2, off3, off4];
bytes.extend_from_slice(&data);
let charstrings = Index::parse(&bytes, 0).unwrap();
let charset_payload = vec![0x00, 0x22, 0x00, 0x7C];
let charset = Charset::Format0 {
bytes: &charset_payload,
num_glyphs: 3,
};
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let top_cs = [139, 139, 204, 247, 85, 14];
let mut interp =
Interpreter::new(&global, None, 0.0, 500.0).with_seac_resolver(&charstrings, &charset);
let r = interp.run(&top_cs);
assert!(matches!(r, Err(Error::CharstringSeacNested)));
}
#[test]
fn flex_family_opcodes_routed_per_tn5177() {
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let cs = [239, 239, 21, 149, 149, 149, 149, 149, 149, 149, 12, 34, 14];
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
interp
.run(&cs)
.expect("hflex (12 34) should accept 7 operands");
let cs = [
239, 239, 21, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 12, 35,
14,
];
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
interp
.run(&cs)
.expect("flex (12 35) should accept 13 operands");
let cs = [
239, 239, 21, 149, 149, 149, 149, 149, 149, 149, 149, 149, 12, 36, 14,
];
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
interp
.run(&cs)
.expect("hflex1 (12 36) should accept 9 operands");
let cs = [
239, 239, 21, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 149, 12, 37, 14,
];
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
interp
.run(&cs)
.expect("flex1 (12 37) should accept 11 operands");
}
fn first_move(cs: &[u8]) -> Point {
let o = run(cs);
match o.contours[0].segments[0] {
CubicSegment::MoveTo(p) => p,
ref other => panic!("expected MoveTo, got {other:?}"),
}
}
fn run_err(cs: &[u8]) -> Error {
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).expect_err("charstring should have errored")
}
#[test]
fn arith_add_sub() {
let p = first_move(&[
189, 189, 12, 10, 247, 92, 239, 12, 11, 21, 14,
]);
assert_eq!(p, Point::new(100.0, 100.0));
}
#[test]
fn arith_neg_abs() {
let p = first_move(&[
89, 12, 14, 89, 12, 9, 21, 14,
]);
assert_eq!(p, Point::new(50.0, 50.0));
}
#[test]
fn arith_mul_div() {
let p = first_move(&[
149, 149, 12, 24, 239, 141, 12, 12, 21, 14,
]);
assert_eq!(p, Point::new(100.0, 50.0));
}
#[test]
fn arith_div_by_zero_is_zero() {
let p = first_move(&[
239, 139, 12, 12, 189, 21, 14,
]);
assert_eq!(p, Point::new(0.0, 50.0));
assert!(p.x.is_finite());
}
#[test]
fn arith_sqrt() {
let p = first_move(&[
239, 12, 26, 143, 12, 26, 21, 14,
]);
assert_eq!(p, Point::new(10.0, 2.0));
}
#[test]
fn stack_dup_drop_exch() {
let p = first_move(&[
189, 12, 27, 12, 18, 239, 12, 28, 21, 14,
]);
assert_eq!(p, Point::new(100.0, 50.0));
}
#[test]
fn stack_index() {
let p = first_move(&[
149, 189, 140, 12, 29, 12, 18, 21, 14,
]);
assert_eq!(p, Point::new(10.0, 50.0));
}
#[test]
fn stack_index_negative_copies_top() {
let p = first_move(&[
149, 189, 138, 12, 29, 12, 18, 21, 14,
]);
assert_eq!(p, Point::new(10.0, 50.0));
}
#[test]
fn stack_roll_upward() {
let p = first_move(&[
149, 189, 239, 142, 140, 12, 30, 12, 18, 21, 14,
]);
assert_eq!(p, Point::new(100.0, 10.0));
}
#[test]
fn storage_put_get_roundtrip() {
let p = first_move(&[
239, 139, 12, 20, 189, 139, 12, 21, 21, 14,
]);
assert_eq!(p, Point::new(50.0, 100.0));
}
#[test]
fn storage_get_unwritten_is_zero() {
let p = first_move(&[
189, 144, 12, 21, 21, 14,
]);
assert_eq!(p, Point::new(50.0, 0.0));
}
#[test]
fn storage_put_out_of_range_errors() {
let e = run_err(&[239, 189, 12, 20, 14]);
assert!(matches!(e, Error::CharstringTransientIndex(50)));
}
#[test]
fn cond_and_or_not() {
let p = first_move(&[
140, 139, 12, 3, 139, 140, 12, 4, 12, 5, 21, 14,
]);
assert_eq!(p, Point::new(0.0, 0.0));
}
#[test]
fn cond_eq() {
let p = first_move(&[
189, 189, 12, 15, 189, 239, 12, 15, 21, 14,
]);
assert_eq!(p, Point::new(1.0, 0.0));
}
#[test]
fn cond_ifelse_picks_s1_when_v1_le_v2() {
let p = first_move(&[
149, 189, 140, 141, 12, 22, 189, 21, 14,
]);
assert_eq!(p, Point::new(10.0, 50.0));
}
#[test]
fn cond_ifelse_picks_s2_when_v1_gt_v2() {
let p = first_move(&[
149, 189, 141, 140, 12, 22, 149, 21, 14,
]);
assert_eq!(p, Point::new(50.0, 10.0));
}
#[test]
fn random_is_in_unit_interval() {
let g = empty_index();
let global = Index::parse(&g, 0).unwrap();
let mut interp = Interpreter::new(&global, None, 0.0, 500.0);
let cs = [12, 23, 139, 21, 14];
interp.run(&cs).unwrap();
let o = interp.into_outline();
if let CubicSegment::MoveTo(p) = o.contours[0].segments[0] {
assert!(p.x > 0.0 && p.x <= 1.0, "random {} not in (0,1]", p.x);
} else {
panic!("expected MoveTo");
}
}
#[test]
fn arith_underflow_errors() {
let e = run_err(&[189, 12, 10, 14]);
assert!(matches!(e, Error::CharstringStackUnderflow));
}
}