use crate::blend::Blend;
use crate::encoding;
use pdfrum_common::kurbo::{BezPath, Point};
const MAX_DEPTH: u32 = 30;
const MAX_STACK: usize = 192;
const MAX_SEGMENTS: usize = 65_536;
#[derive(Clone, Copy)]
pub(crate) struct Env<'a> {
pub subrs: &'a [Vec<u8>],
pub charstrings: &'a [Vec<u8>],
pub name_lookup: &'a dyn Fn(&str) -> Option<usize>,
pub weights: &'a [f32],
pub blend: Option<&'a Blend>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Glyph {
pub path: BezPath,
pub advance: f32,
pub left_side_bearing: f32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Abort {
Truncated,
StackUnderflow,
MissingSubr,
TooDeep,
BadBlend,
BadSeac,
TooBig,
}
pub(crate) fn interpret(code: &[u8], env: Env<'_>) -> (Glyph, Option<Abort>) {
let mut m = Machine::new(env);
let abort = m.run(code, 0).err();
m.finish();
(
Glyph {
path: m.path,
advance: m.advance,
left_side_bearing: m.lsb,
},
abort,
)
}
struct Machine<'a> {
env: Env<'a>,
stack: Vec<f64>,
ps_stack: Vec<f64>,
path: BezPath,
open: bool,
current: Point,
start: Point,
advance: f32,
lsb: f32,
segments: usize,
flex: Option<Vec<Point>>,
done: bool,
}
impl<'a> Machine<'a> {
fn new(env: Env<'a>) -> Self {
Self {
env,
stack: Vec::new(),
ps_stack: Vec::new(),
path: BezPath::new(),
open: false,
current: Point::ZERO,
start: Point::ZERO,
advance: 0.0,
lsb: 0.0,
segments: 0,
flex: None,
done: false,
}
}
fn finish(&mut self) {
if self.open {
self.path.close_path();
self.open = false;
}
}
fn push(&mut self, v: f64) -> Result<(), Abort> {
if self.stack.len() >= MAX_STACK {
return Err(Abort::StackUnderflow);
}
self.stack.push(v);
Ok(())
}
fn take(&mut self, n: usize) -> Result<Vec<f64>, Abort> {
if self.stack.len() < n {
self.stack.clear();
return Err(Abort::StackUnderflow);
}
let args = self.stack.get(..n).unwrap_or_default().to_vec();
self.stack.clear();
Ok(args)
}
fn grow(&mut self) -> Result<(), Abort> {
self.segments = self.segments.saturating_add(1);
if self.segments > MAX_SEGMENTS {
return Err(Abort::TooBig);
}
Ok(())
}
fn move_to(&mut self, p: Point) -> Result<(), Abort> {
self.current = p;
if let Some(points) = self.flex.as_mut() {
points.push(p);
return Ok(());
}
self.grow()?;
if self.open {
self.path.close_path();
}
self.path.move_to(p);
self.start = p;
self.open = true;
Ok(())
}
fn line_to(&mut self, p: Point) -> Result<(), Abort> {
self.grow()?;
if !self.open {
self.path.move_to(self.current);
self.start = self.current;
self.open = true;
}
self.current = p;
self.path.line_to(p);
Ok(())
}
fn curve_to(&mut self, c1: Point, c2: Point, p: Point) -> Result<(), Abort> {
self.grow()?;
if !self.open {
self.path.move_to(self.current);
self.start = self.current;
self.open = true;
}
self.current = p;
self.path.curve_to(c1, c2, p);
Ok(())
}
fn close(&mut self) {
if self.open {
self.path.close_path();
self.open = false;
}
}
fn run(&mut self, code: &[u8], depth: u32) -> Result<(), Abort> {
if depth > MAX_DEPTH {
return Err(Abort::TooDeep);
}
let mut i = 0usize;
while let Some(&b) = code.get(i) {
i = i.saturating_add(1);
match b {
32..=246 => self.push(f64::from(i16::from(b) - 139))?,
247..=250 => {
let w = *code.get(i).ok_or(Abort::Truncated)?;
i = i.saturating_add(1);
self.push(f64::from((i16::from(b) - 247) * 256 + i16::from(w) + 108))?;
}
251..=254 => {
let w = *code.get(i).ok_or(Abort::Truncated)?;
i = i.saturating_add(1);
self.push(f64::from(-(i16::from(b) - 251) * 256 - i16::from(w) - 108))?;
}
255 => {
let bytes = code.get(i..i.saturating_add(4)).ok_or(Abort::Truncated)?;
i = i.saturating_add(4);
let mut v: i32 = 0;
for &x in bytes {
v = (v << 8) | i32::from(x);
}
self.push(f64::from(v))?;
}
12 => {
let esc = *code.get(i).ok_or(Abort::Truncated)?;
i = i.saturating_add(1);
if self.escaped(esc, depth)? {
return Ok(());
}
}
_ => {
if self.operator(b, code, &mut i, depth)? {
return Ok(());
}
}
}
if self.done {
return Ok(());
}
}
if depth == 0 {
return Err(Abort::Truncated);
}
Ok(())
}
fn operator(&mut self, op: u8, code: &[u8], i: &mut usize, depth: u32) -> Result<bool, Abort> {
match op {
1 | 3 => {
self.stack.clear();
}
4 => {
let a = self.take(1)?;
let dy = a.first().copied().unwrap_or(0.0);
self.move_to(Point::new(self.current.x, self.current.y + dy))?;
}
5 => {
let a = self.take(2)?;
let (dx, dy) = (arg(&a, 0), arg(&a, 1));
self.line_to(Point::new(self.current.x + dx, self.current.y + dy))?;
}
6 => {
let a = self.take(1)?;
self.line_to(Point::new(self.current.x + arg(&a, 0), self.current.y))?;
}
7 => {
let a = self.take(1)?;
self.line_to(Point::new(self.current.x, self.current.y + arg(&a, 0)))?;
}
8 => {
let a = self.take(6)?;
self.relative_curve(
arg(&a, 0),
arg(&a, 1),
arg(&a, 2),
arg(&a, 3),
arg(&a, 4),
arg(&a, 5),
)?;
}
9 => {
self.stack.clear();
self.close();
}
10 => {
let idx = self.stack.pop().ok_or(Abort::StackUnderflow)?;
let sub = usize::try_from(idx as i64)
.ok()
.and_then(|n| self.env.subrs.get(n))
.ok_or(Abort::MissingSubr)?;
let sub = sub.clone();
self.run(&sub, depth.saturating_add(1))?;
}
11 => return Ok(true), 13 => {
let a = self.take(2)?;
self.lsb = arg(&a, 0) as f32;
self.advance = arg(&a, 1) as f32;
self.current = Point::new(arg(&a, 0), 0.0);
self.start = self.current;
}
14 => {
self.done = true;
return Ok(true); }
21 => {
let a = self.take(2)?;
self.move_to(Point::new(
self.current.x + arg(&a, 0),
self.current.y + arg(&a, 1),
))?;
}
22 => {
let a = self.take(1)?;
self.move_to(Point::new(self.current.x + arg(&a, 0), self.current.y))?;
}
30 => {
let a = self.take(4)?;
self.relative_curve(0.0, arg(&a, 0), arg(&a, 1), arg(&a, 2), arg(&a, 3), 0.0)?;
}
31 => {
let a = self.take(4)?;
self.relative_curve(arg(&a, 0), 0.0, arg(&a, 1), arg(&a, 2), 0.0, arg(&a, 3))?;
}
_ => {
let _ = (code, i);
self.stack.clear();
}
}
Ok(false)
}
fn escaped(&mut self, esc: u8, depth: u32) -> Result<bool, Abort> {
match esc {
6 => {
let a = self.take(5)?;
self.seac(
arg(&a, 0),
arg(&a, 1),
arg(&a, 2),
arg(&a, 3),
arg(&a, 4),
depth,
)?;
self.done = true;
return Ok(true);
}
7 => {
let a = self.take(4)?;
self.lsb = arg(&a, 0) as f32;
self.advance = arg(&a, 2) as f32;
self.current = Point::new(arg(&a, 0), arg(&a, 1));
self.start = self.current;
}
12 => {
let b = self.stack.pop().ok_or(Abort::StackUnderflow)?;
let a = self.stack.pop().ok_or(Abort::StackUnderflow)?;
self.push(if b == 0.0 { 0.0 } else { a / b })?;
}
16 => self.call_other_subr()?,
17 => {
let v = self.ps_stack.pop().unwrap_or(0.0);
self.push(v)?;
}
33 => {
let a = self.take(2)?;
self.current = Point::new(arg(&a, 0), arg(&a, 1));
}
_ => self.stack.clear(),
}
Ok(false)
}
fn call_other_subr(&mut self) -> Result<(), Abort> {
let index = self.stack.pop().ok_or(Abort::StackUnderflow)? as i64;
let count = self.stack.pop().ok_or(Abort::StackUnderflow)?;
let count = usize::try_from(count as i64).unwrap_or(0);
if self.stack.len() < count {
self.stack.clear();
return Err(Abort::StackUnderflow);
}
let base = self.stack.len().saturating_sub(count);
let args: Vec<f64> = self.stack.get(base..).unwrap_or_default().to_vec();
self.stack.truncate(base);
match index {
0 => self.end_flex(&args),
1 => {
self.flex = Some(Vec::new());
Ok(())
}
2 => Ok(()), 3 => {
self.ps_stack.push(3.0);
Ok(())
}
14..=18 => self.blend(index, &args),
_ => {
self.ps_stack.extend(args.iter().rev().copied());
Ok(())
}
}
}
fn end_flex(&mut self, args: &[f64]) -> Result<(), Abort> {
let points = self.flex.take().unwrap_or_default();
let p = |n: usize| points.get(n).copied();
let end = if let (Some(c1), Some(c2), Some(mid), Some(c3), Some(c4), Some(end)) =
(p(1), p(2), p(3), p(4), p(5), p(6))
{
self.curve_to(c1, c2, mid)?;
self.curve_to(c3, c4, end)?;
self.current = end;
end
} else {
if let Some(last) = points.last().copied() {
self.line_to(last)?;
}
self.current
};
self.ps_stack.push(end.y);
self.ps_stack.push(end.x);
let _ = args;
Ok(())
}
fn blend(&mut self, index: i64, args: &[f64]) -> Result<(), Abort> {
let masters = self.env.weights.len();
if masters < 2 || self.env.blend.is_none() {
self.ps_stack.extend(args.iter().rev().copied());
return Ok(());
}
let points = match index {
14 => 1usize,
15 => 2,
16 => 3,
17 => 4,
18 => 6,
_ => return Err(Abort::BadBlend),
};
if args.len() != points.saturating_mul(masters) {
return Err(Abort::BadBlend);
}
let mut blended = Vec::with_capacity(points);
for k in 0..points {
let mut v = args.get(k).copied().unwrap_or(0.0);
for (j, &w) in self.env.weights.iter().enumerate().skip(1) {
let delta = args
.get(
points
.saturating_add(k.saturating_mul(masters.saturating_sub(1)))
.saturating_add(j.saturating_sub(1)),
)
.copied()
.unwrap_or(0.0);
v += delta * f64::from(w);
}
blended.push(v);
}
self.ps_stack.extend(blended.iter().rev().copied());
Ok(())
}
fn seac(
&mut self,
asb: f64,
adx: f64,
ady: f64,
bchar: f64,
achar: f64,
depth: u32,
) -> Result<(), Abort> {
let code = |v: f64| u8::try_from(v as i64).ok();
let lookup = |v: f64| -> Option<Vec<u8>> {
let name = encoding::standard_encoding_name(code(v)?)?;
let gid = (self.env.name_lookup)(name)?;
self.env.charstrings.get(gid).cloned()
};
let (Some(base), Some(accent)) = (lookup(bchar), lookup(achar)) else {
return Err(Abort::BadSeac);
};
let outer_lsb = self.lsb;
self.reset_for_component();
self.run(&base, depth.saturating_add(1))?;
self.finish();
let base_advance = self.advance;
let accent_start = self.path.elements().len();
self.reset_for_component();
self.run(&accent, depth.saturating_add(1))?;
self.finish();
let shift = kurbo_translate(f64::from(outer_lsb) - asb + adx, ady);
translate_from(&mut self.path, accent_start, shift);
self.advance = base_advance;
self.lsb = outer_lsb;
Ok(())
}
fn reset_for_component(&mut self) {
self.stack.clear();
self.ps_stack.clear();
self.flex = None;
self.open = false;
self.current = Point::ZERO;
self.start = Point::ZERO;
self.done = false;
}
fn relative_curve(
&mut self,
dx1: f64,
dy1: f64,
dx2: f64,
dy2: f64,
dx3: f64,
dy3: f64,
) -> Result<(), Abort> {
let c1 = Point::new(self.current.x + dx1, self.current.y + dy1);
let c2 = Point::new(c1.x + dx2, c1.y + dy2);
let end = Point::new(c2.x + dx3, c2.y + dy3);
self.curve_to(c1, c2, end)
}
}
fn arg(args: &[f64], i: usize) -> f64 {
args.get(i).copied().unwrap_or(0.0)
}
fn kurbo_translate(dx: f64, dy: f64) -> (f64, f64) {
(dx, dy)
}
fn translate_from(path: &mut BezPath, from: usize, (dx, dy): (f64, f64)) {
use pdfrum_common::kurbo::PathEl;
let shift = |p: Point| Point::new(p.x + dx, p.y + dy);
let tail: Vec<PathEl> = path
.elements()
.get(from..)
.unwrap_or_default()
.iter()
.map(|el| match *el {
PathEl::MoveTo(p) => PathEl::MoveTo(shift(p)),
PathEl::LineTo(p) => PathEl::LineTo(shift(p)),
PathEl::QuadTo(a, b) => PathEl::QuadTo(shift(a), shift(b)),
PathEl::CurveTo(a, b, c) => PathEl::CurveTo(shift(a), shift(b), shift(c)),
PathEl::ClosePath => PathEl::ClosePath,
})
.collect();
path.truncate(from);
for el in tail {
path.push(el);
}
}
#[cfg(test)]
#[allow(
clippy::indexing_slicing,
clippy::float_cmp,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::similar_names
)]
mod tests {
use super::{Abort, Env, Glyph, interpret};
use crate::eexec;
use pdfrum_common::kurbo::Shape;
fn cs(items: &[Item]) -> Vec<u8> {
let mut out = Vec::new();
for it in items {
match *it {
Item::N(v) => encode_number(&mut out, v),
Item::Op(o) => out.push(o),
Item::Esc(o) => out.extend_from_slice(&[12, o]),
}
}
out
}
#[derive(Clone, Copy)]
enum Item {
N(i32),
Op(u8),
Esc(u8),
}
use Item::{Esc, N, Op};
fn encode_number(out: &mut Vec<u8>, v: i32) {
match v {
-107..=107 => out.push((v + 139) as u8),
108..=1131 => {
let v = v - 108;
out.push(((v >> 8) + 247) as u8);
out.push((v & 0xFF) as u8);
}
-1131..=-108 => {
let v = -v - 108;
out.push(((v >> 8) + 251) as u8);
out.push((v & 0xFF) as u8);
}
_ => {
out.push(255);
out.extend_from_slice(&v.to_be_bytes());
}
}
}
fn no_names(_: &str) -> Option<usize> {
None
}
fn run(code: &[u8]) -> (Glyph, Option<Abort>) {
run_with(code, &[], &[])
}
fn run_with(code: &[u8], subrs: &[Vec<u8>], charstrings: &[Vec<u8>]) -> (Glyph, Option<Abort>) {
interpret(
code,
Env {
subrs,
charstrings,
name_lookup: &no_names,
weights: &[],
blend: None,
},
)
}
#[test]
fn hsbw_sets_the_origin_and_advance() {
let (g, abort) = run(&cs(&[N(50), N(600), Op(13), Op(14)]));
assert_eq!(abort, None);
assert_eq!(g.advance, 600.0);
assert_eq!(g.left_side_bearing, 50.0);
let (g, _) = run(&cs(&[
N(50),
N(600),
Op(13),
N(0),
N(0),
Op(21),
N(10),
Op(6),
Op(9),
Op(14),
]));
assert_eq!(g.path.bounding_box().x0, 50.0);
assert_eq!(g.path.bounding_box().x1, 60.0);
}
#[test]
fn a_square_from_the_line_operators() {
let (g, abort) = run(&cs(&[
N(0),
N(1000),
Op(13),
N(100),
N(100),
Op(21), N(200),
Op(6), N(200),
Op(7), N(-200),
N(0),
Op(5), Op(9), Op(14),
]));
assert_eq!(abort, None);
let bb = g.path.bounding_box();
assert_eq!((bb.x0, bb.y0, bb.x1, bb.y1), (100.0, 100.0, 300.0, 300.0));
assert_eq!(g.path.elements().len(), 5); }
#[test]
fn rrcurveto_vhcurveto_hvcurveto_place_control_points() {
use pdfrum_common::kurbo::{PathEl, Point};
let (g, _) = run(&cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(10),
N(20),
N(30),
N(40),
N(50),
N(60),
Op(8), N(10),
N(20),
N(30),
N(40),
Op(30), N(10),
N(20),
N(30),
N(40),
Op(31), Op(14),
]));
let els = g.path.elements();
assert_eq!(
els.get(1),
Some(&PathEl::CurveTo(
Point::new(10.0, 20.0),
Point::new(40.0, 60.0),
Point::new(90.0, 120.0)
))
);
assert_eq!(
els.get(2),
Some(&PathEl::CurveTo(
Point::new(90.0, 130.0),
Point::new(110.0, 160.0),
Point::new(150.0, 160.0)
))
);
assert_eq!(
els.get(3),
Some(&PathEl::CurveTo(
Point::new(160.0, 160.0),
Point::new(180.0, 190.0),
Point::new(180.0, 230.0)
))
);
}
#[test]
fn callsubr_and_return() {
let subrs = vec![
Vec::new(),
Vec::new(),
Vec::new(),
Vec::new(),
cs(&[N(100), Op(6), Op(11)]), ];
let (g, abort) = run_with(
&cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(4),
Op(10),
Op(14),
]),
&subrs,
&[],
);
assert_eq!(abort, None);
assert_eq!(g.path.bounding_box().x1, 100.0);
}
#[test]
fn a_missing_subr_aborts_but_keeps_the_path() {
let (g, abort) = run(&cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(50),
Op(6),
N(99),
Op(10),
Op(14),
]));
assert_eq!(abort, Some(Abort::MissingSubr));
assert_eq!(g.path.bounding_box().x1, 50.0);
}
#[test]
fn div_leaves_a_quotient_on_the_stack() {
let (g, _) = run(&cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(300),
N(3),
Esc(12),
Op(6),
Op(14),
]));
assert_eq!(g.path.bounding_box().x1, 100.0);
let (g, _) = run(&cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(300),
N(0),
Esc(12),
Op(6),
Op(14),
]));
assert!(g.path.bounding_box().x1.is_finite());
}
#[test]
fn setcurrentpoint_is_absolute() {
let (g, _) = run(&cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(500),
N(500),
Esc(33), N(10),
Op(6),
Op(14),
]));
let bb = g.path.bounding_box();
assert_eq!((bb.x1, bb.y1), (510.0, 500.0));
}
#[test]
fn flex_becomes_two_curves() {
use pdfrum_common::kurbo::PathEl;
let mut items = vec![N(0), N(1000), Op(13), N(0), N(0), Op(21)];
items.extend([N(0), N(1), Esc(16)]); for (dx, dy) in [
(10, 10),
(10, 10),
(10, 10),
(10, 10),
(10, 10),
(10, 10),
(10, 10),
] {
items.extend([N(0), N(2), Esc(16), N(dx), N(dy), Op(21)]);
}
items.extend([N(50), N(70), N(70), N(3), N(0), Esc(16)]);
items.extend([Esc(17), Esc(17), Esc(33)]);
items.push(Op(14));
let (g, abort) = run(&cs(&items));
assert_eq!(abort, None);
let curves = g
.path
.elements()
.iter()
.filter(|e| matches!(e, PathEl::CurveTo(..)))
.count();
assert_eq!(curves, 2);
assert_eq!(
g.path
.elements()
.iter()
.filter(|e| matches!(e, PathEl::MoveTo(_)))
.count(),
1
);
}
#[test]
fn hint_replacement_returns_three_and_calls_the_subr() {
let subrs = vec![Vec::new(), Vec::new(), Vec::new(), cs(&[Op(11)])];
let (g, abort) = run_with(
&cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(3),
N(1),
N(3),
Esc(16), Esc(17),
Op(10), N(70),
Op(6),
Op(14),
]),
&subrs,
&[],
);
assert_eq!(abort, None);
assert_eq!(g.path.bounding_box().x1, 70.0);
}
#[test]
fn seac_composes_two_glyphs_with_the_side_bearing_correction() {
let base = cs(&[
N(0),
N(500),
Op(13),
N(0),
N(0),
Op(21),
N(100),
Op(6),
N(100),
Op(7),
Op(9),
Op(14),
]);
let accent = cs(&[
N(20),
N(300),
Op(13),
N(0),
N(0),
Op(21),
N(50),
Op(6),
N(50),
Op(7),
Op(9),
Op(14),
]);
let charstrings = vec![Vec::new(), base, accent];
let lookup = |n: &str| match n {
"A" => Some(1usize),
"B" => Some(2usize),
_ => None,
};
let (g, abort) = interpret(
&cs(&[
N(0),
N(500),
Op(13),
N(20),
N(300),
N(100),
N(65),
N(66),
Esc(6),
]),
Env {
subrs: &[],
charstrings: &charstrings,
name_lookup: &lookup,
weights: &[],
blend: None,
},
);
assert_eq!(abort, None);
assert_eq!(g.advance, 500.0);
let bb = g.path.bounding_box();
assert_eq!((bb.x0, bb.y0), (0.0, 0.0));
assert_eq!((bb.x1, bb.y1), (350.0, 150.0));
}
#[test]
fn seac_naming_a_missing_component_aborts_cleanly() {
let (_, abort) = run(&cs(&[
N(0),
N(500),
Op(13),
N(0),
N(0),
N(0),
N(65),
N(66),
Esc(6),
]));
assert_eq!(abort, Some(Abort::BadSeac));
}
#[test]
fn multiple_master_blend_interpolates_the_operands() {
use crate::blend::{AxisKind, Blend, DesignMap};
use pdfrum_common::Diagnostics;
let mut d = Diagnostics::default();
let blend = Blend::new(
vec![AxisKind::Weight],
vec![vec![0.0], vec![1.0]],
vec![DesignMap {
knots: vec![(0.0, 0.0), (1.0, 1.0)],
}],
vec![0.5, 0.5],
&mut d,
)
.expect("consistent");
let code = cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(100),
N(200),
N(2),
N(14),
Esc(16), Esc(17), Op(6), Op(14),
]);
let (g, abort) = interpret(
&code,
Env {
subrs: &[],
charstrings: &[],
name_lookup: &no_names,
weights: &[0.25, 0.75],
blend: Some(&blend),
},
);
assert_eq!(abort, None);
assert!((g.path.bounding_box().x1 - 250.0).abs() < 1e-9);
let (g2, _) = interpret(
&code,
Env {
subrs: &[],
charstrings: &[],
name_lookup: &no_names,
weights: &[1.0, 0.0],
blend: Some(&blend),
},
);
assert!((g2.path.bounding_box().x1 - 100.0).abs() < 1e-9);
}
#[test]
fn a_blend_with_the_wrong_arity_aborts() {
use crate::blend::{AxisKind, Blend, DesignMap};
use pdfrum_common::Diagnostics;
let mut d = Diagnostics::default();
let blend = Blend::new(
vec![AxisKind::Weight],
vec![vec![0.0], vec![1.0]],
vec![DesignMap {
knots: vec![(0.0, 0.0), (1.0, 1.0)],
}],
vec![0.5, 0.5],
&mut d,
)
.expect("consistent");
let (_, abort) = interpret(
&cs(&[
N(0),
N(1000),
Op(13),
N(1),
N(2),
N(3),
N(3),
N(14),
Esc(16),
Op(14),
]),
Env {
subrs: &[],
charstrings: &[],
name_lookup: &no_names,
weights: &[0.5, 0.5],
blend: Some(&blend),
},
);
assert_eq!(abort, Some(Abort::BadBlend));
}
#[test]
fn closepath_leaves_the_current_point_alone() {
let (g, _) = run(&cs(&[
N(0),
N(1000),
Op(13),
N(100),
N(0),
Op(21),
N(100),
Op(6), Op(9), N(50),
N(50),
Op(21), N(10),
Op(6),
Op(14),
]));
let bb = g.path.bounding_box();
assert_eq!(bb.x1, 260.0);
}
#[test]
fn recursion_is_capped() {
let subrs = vec![cs(&[N(0), Op(10), Op(11)])];
let (_, abort) = run_with(
&cs(&[N(0), N(1000), Op(13), N(0), Op(10), Op(14)]),
&subrs,
&[],
);
assert_eq!(abort, Some(Abort::TooDeep));
}
#[test]
fn unknown_operators_clear_the_stack_and_carry_on() {
let (g, abort) = run(&cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(1),
N(2),
Op(23),
N(80),
Op(6),
Op(14),
]));
assert_eq!(abort, None);
assert_eq!(g.path.bounding_box().x1, 80.0);
}
#[test]
fn the_four_byte_number_encoding_round_trips() {
let (g, _) = run(&cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(70000),
Op(6),
Op(14),
]));
assert_eq!(g.path.bounding_box().x1, 70000.0);
let (g, _) = run(&cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(-70000),
Op(6),
Op(14),
]));
assert_eq!(g.path.bounding_box().x0, -70000.0);
}
#[test]
fn a_truncated_charstring_reports_it() {
let (_, abort) = run(&[255, 0, 1]);
assert_eq!(abort, Some(Abort::Truncated));
let (g, abort) = run(&[]);
assert_eq!(abort, Some(Abort::Truncated));
assert!(g.path.is_empty());
}
#[test]
fn encrypted_charstrings_decode_through_the_cipher() {
let plain = {
let mut v = vec![0u8; 4];
v.extend(cs(&[
N(0),
N(1000),
Op(13),
N(0),
N(0),
Op(21),
N(42),
Op(6),
Op(14),
]));
v
};
let cipher = eexec::encrypt(&plain, eexec::CHARSTRING_SEED);
let decoded = eexec::decrypt(&cipher, eexec::CHARSTRING_SEED, 4);
let (g, abort) = run(&decoded);
assert_eq!(abort, None);
assert_eq!(g.path.bounding_box().x1, 42.0);
}
}