mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 13:08:07 -06:00
173 lines
5.6 KiB
Rust
173 lines
5.6 KiB
Rust
use super::eps::embedded_fonts;
|
|
use super::scanner::Src;
|
|
use super::value::{Flow, GState, V, new_dict};
|
|
use super::{Affine, BezPath, Builder, Cap, HashMap, Interp, Join};
|
|
|
|
/// The deepest procedure nesting run (deeper recursion stops the program).
|
|
const MAX_CALL_DEPTH: usize = 256;
|
|
|
|
impl Interp {
|
|
pub fn new(page: [f64; 4]) -> Interp {
|
|
Interp {
|
|
b: Builder::new(),
|
|
st: vec![],
|
|
dicts: vec![new_dict()],
|
|
gs: GState {
|
|
ctm: Affine::IDENTITY,
|
|
color: [0.0; 3],
|
|
lw: 1.0,
|
|
cap: Cap::Butt,
|
|
join: Join::Miter,
|
|
miter: 10.0,
|
|
dash: None,
|
|
path: BezPath::new(),
|
|
cur: None,
|
|
start: None,
|
|
depth: 1,
|
|
font: None,
|
|
},
|
|
saved: vec![],
|
|
budget: 8_000_000,
|
|
calls: 0,
|
|
font_dir: HashMap::new(),
|
|
embedded: HashMap::new(),
|
|
font_cache: HashMap::new(),
|
|
page,
|
|
unknown: vec![],
|
|
}
|
|
}
|
|
|
|
pub fn run(&mut self, data: &[u8]) {
|
|
self.embedded = embedded_fonts(data);
|
|
let mut src = Src { d: data, pos: 0 };
|
|
while let Some(t) = src.token(0) {
|
|
if let Err(Flow::Quit) = self.exec_token(t, &mut src) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn pop(&mut self) -> V {
|
|
self.st.pop().unwrap_or(V::Null)
|
|
}
|
|
|
|
pub(super) fn popn(&mut self) -> f64 {
|
|
self.pop().num().unwrap_or(0.0)
|
|
}
|
|
|
|
pub(super) fn push(&mut self, v: V) {
|
|
if self.st.len() < 100_000 {
|
|
self.st.push(v);
|
|
}
|
|
}
|
|
|
|
pub(super) fn lookup(&self, k: &str) -> Option<V> {
|
|
self.dicts.iter().rev().find_map(|d| d.borrow().get(k).cloned())
|
|
}
|
|
|
|
/// A token read from the file: procedures are pushed, names executed.
|
|
fn exec_token(&mut self, t: V, src: &mut Src) -> Result<(), Flow> {
|
|
match t {
|
|
V::Name(n) => self.exec_name(&n, src),
|
|
other => {
|
|
self.push(other);
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Execute a value as an object (procedures run).
|
|
pub(super) fn exec_obj(&mut self, v: V, src: &mut Src) -> Result<(), Flow> {
|
|
match v {
|
|
V::Proc(p) => {
|
|
if self.calls >= MAX_CALL_DEPTH {
|
|
self.b.skip("PostScript (recursion limit)");
|
|
return Err(Flow::Quit);
|
|
}
|
|
self.calls += 1;
|
|
let mut r = Ok(());
|
|
for x in p.iter() {
|
|
match x {
|
|
V::Name(n) => {
|
|
r = self.exec_name(n, src);
|
|
if r.is_err() {
|
|
break;
|
|
}
|
|
}
|
|
V::Op(op) => {
|
|
r = self.spend(1).and_then(|()| self.builtin(op, src));
|
|
if r.is_err() {
|
|
break;
|
|
}
|
|
}
|
|
// Other items count against the budget as names do.
|
|
other => {
|
|
r = self.spend(1);
|
|
if r.is_err() {
|
|
break;
|
|
}
|
|
self.push(other.clone());
|
|
}
|
|
}
|
|
}
|
|
self.calls -= 1;
|
|
r
|
|
}
|
|
// An executable name can run another (`exec` of `/exec cvx`), so names count too.
|
|
V::Name(n) => {
|
|
if self.calls >= MAX_CALL_DEPTH {
|
|
self.b.skip("PostScript (recursion limit)");
|
|
return Err(Flow::Quit);
|
|
}
|
|
self.calls += 1;
|
|
let r = self.exec_name(&n, src);
|
|
self.calls -= 1;
|
|
r
|
|
}
|
|
V::Op(op) => {
|
|
self.spend(1)?;
|
|
self.builtin(&op, src)
|
|
}
|
|
other => {
|
|
self.push(other);
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
fn exec_name(&mut self, n: &str, src: &mut Src) -> Result<(), Flow> {
|
|
self.spend(1)?;
|
|
if let Some(v) = self.lookup(n) {
|
|
return match v {
|
|
V::Proc(_) => self.exec_obj(v, src),
|
|
// An operator stored under another name (`/mo /moveto load def`, as Illustrator's
|
|
// EPS prolog does for every path operator) runs that operator. It pushed the
|
|
// operator's name instead, so such files drew nothing (#955).
|
|
V::Op(op) => self.builtin(&op, src),
|
|
// An executable name (`/y /x cvx def`) runs what that name is bound to.
|
|
V::Name(_) => self.exec_obj(v, src),
|
|
other => {
|
|
self.push(other);
|
|
Ok(())
|
|
}
|
|
};
|
|
}
|
|
self.builtin(n, src)
|
|
}
|
|
|
|
/// Count `n` operations against the budget, and stop the program when fewer are left. An
|
|
/// operator that makes, reads or copies path elements counts each one as an operation.
|
|
pub(super) fn spend(&mut self, n: usize) -> Result<(), Flow> {
|
|
match self.budget.checked_sub(n) {
|
|
Some(left) => {
|
|
self.budget = left;
|
|
Ok(())
|
|
}
|
|
None => {
|
|
self.b.skip("PostScript (operation limit)");
|
|
Err(Flow::Quit)
|
|
}
|
|
}
|
|
}
|
|
}
|