diff --git a/crates/eps/src/import/data/ops.rs b/crates/eps/src/import/data/ops.rs index 3b4046de9..fbac566d0 100644 --- a/crates/eps/src/import/data/ops.rs +++ b/crates/eps/src/import/data/ops.rs @@ -29,7 +29,10 @@ impl Interp<'_> { pub fn data_op(&mut self, op: Op) -> Res { use Op::*; match op { - CurrentFile => self.push(Obj::File { stream: Rc::new(RefCell::new(Stream::Current)), exec: false })?, + CurrentFile => { + let stream = self.running.last().cloned().unwrap_or_else(|| Rc::new(RefCell::new(Stream::Current))); + self.push(Obj::File { stream, exec: false })?; + } Filter => self.filter()?, ReadHexString | ReadString => { let s = self.pop_str()?; @@ -163,18 +166,7 @@ impl Interp<'_> { self.push(o)?; } Obj::File { stream: f, .. } => { - self.materialize(&f)?; - let (o, immediate) = match &mut *f.borrow_mut() { - Stream::Current => (self.lex.next()?, self.lex.immediate), - Stream::Buf { data, pos, .. } => { - let rest = data.get(*pos..).unwrap_or_default(); - let mut lex = Lexer::new(rest); - let o = lex.next()?; - *pos += rest.len() - lex.data().len(); - (o, lex.immediate) - } - Stream::Pending(_) => (None, false), - }; + let (o, immediate) = self.next_in(&f)?; let Some(o) = o else { return self.push(Obj::Bool(false)) }; let o = self.scanned(o, immediate)?; self.push(o)?; @@ -184,6 +176,23 @@ impl Interp<'_> { self.push(Obj::Bool(true)) } + /// The next object in file `f` (`token`, and a file being run), read with the whitespace + /// after it, and whether it has `//name`s in it. + pub(in super::super) fn next_in(&mut self, f: &Rc>) -> Res<(Option, bool)> { + self.materialize(f)?; + Ok(match &mut *f.borrow_mut() { + Stream::Current => (self.lex.next()?, self.lex.immediate), + Stream::Buf { data, pos, .. } => { + let rest = data.get(*pos..).unwrap_or_default(); + let mut lex = Lexer::new(rest); + let o = lex.next()?; + *pos += rest.len() - lex.data().len(); + (o, lex.immediate) + } + Stream::Pending(_) => (None, false), + }) + } + /// Up to `n` bytes of file `f`. fn read(&mut self, f: &Rc>, n: usize) -> Res> { self.materialize(f)?; @@ -275,11 +284,16 @@ impl Interp<'_> { let spec = Filter { name, sub, predictor, fax }; let stream = match self.pop()? { Obj::File { stream: f, .. } => { - if let Stream::Buf { data, pos, .. } = &*f.borrow() { - self.alloc(data.len().saturating_sub(*pos))?; + // A filter over the file being run reads it, as one over the program's own file + // does, from where the program is when the filter is first read. + let running = self.running.last().is_some_and(|r| Rc::ptr_eq(r, &f)); + match &*f.borrow() { + Stream::Buf { data, pos, .. } if !running => self.alloc(data.len().saturating_sub(*pos))?, + _ => {} } let chain = match &*f.borrow() { Stream::Current => Some(vec![]), + Stream::Buf { .. } if running => Some(vec![]), Stream::Pending(c) => Some(c.clone()), Stream::Buf { .. } => None, }; @@ -325,15 +339,6 @@ impl Interp<'_> { self.push(Obj::File { stream, exec: false }) } - /// The data an executable file runs as a program: what is left of it. `None` for the - /// program's own file, which is being run already. - pub(in super::super) fn program_of(&mut self, f: &Rc>) -> Res>> { - if matches!(&*f.borrow(), Stream::Current) { - return Ok(None); - } - self.read(f, MAX_DATA).map(Some) - } - /// The decoded data left in file `f`, without reading it (a shading's or function's data). pub(in super::super) fn peek_all(&mut self, f: &Rc>) -> Res> { self.materialize(f)?; @@ -343,8 +348,9 @@ impl Interp<'_> { }) } - /// Decode the filters a file waits to run over the program's data, from where it is now. - fn materialize(&mut self, f: &Rc>) -> Res { + /// Decode the filters a file waits to run over the program's data, from where it is now: the + /// file being run, or the program's own file. + pub(in super::super) fn materialize(&mut self, f: &Rc>) -> Res { let chain = match &*f.borrow() { Stream::Pending(c) => c.clone(), _ => return Ok(()), @@ -353,10 +359,21 @@ impl Interp<'_> { for spec in &chain { buf = Some(match buf.take() { None => { - let raw = self.lex.data(); - let (data, used, rgb) = decode(spec, raw)?; + let (data, rgb) = match self.running.last() { + Some(r) => { + let mut b = r.borrow_mut(); + let Stream::Buf { data, pos, .. } = &mut *b else { return io("filter") }; + let (out, used, rgb) = decode(spec, data.get(*pos..).unwrap_or_default())?; + *pos = pos.saturating_add(used); + (out, rgb) + } + None => { + let (out, used, rgb) = decode(spec, self.lex.data())?; + self.lex.advance(used); + (out, rgb) + } + }; self.charge_decoded(data.len())?; - self.lex.advance(used); (data, rgb) } Some((prev, was)) => { diff --git a/crates/eps/src/import/interp.rs b/crates/eps/src/import/interp.rs index f44bf7132..f4c210b9c 100644 --- a/crates/eps/src/import/interp.rs +++ b/crates/eps/src/import/interp.rs @@ -4,10 +4,12 @@ //! Every operation counts against a budget and procedures nest only so deep, so a looping or //! recursing program ends with [`PsError::Limit`] instead of hanging. +use std::cell::RefCell; use std::rc::Rc; use vectorcraft_geom::Affine; +use super::data::Stream; use super::graphics::{GState, Out}; use super::lex::{IMMEDIATE, Lexer}; use super::obj::{Dict, DictRef, Key, Obj, Op, PsError, Res, Shared, ps_err}; @@ -32,6 +34,9 @@ pub(crate) struct Interp<'a> { /// The list of the arrays and dictionaries made since this started ([`super::obj::track`]). made: u64, pub lex: Lexer<'a>, + /// The files being run (`cvx exec`), innermost last: the program reads from the last one, and + /// it is `currentfile` while it runs. + pub running: Vec>>, pub stack: Vec, pub dicts: Vec, system: DictRef, @@ -151,6 +156,7 @@ impl<'a> Interp<'a> { Self { made, lex: Lexer::new(src), + running: vec![], stack: vec![], dicts: vec![system.clone(), global.clone(), user.clone()], system, @@ -388,10 +394,7 @@ impl<'a> Interp<'a> { pub fn call(&mut self, v: Obj) -> Res { match v { Obj::Array { items, exec: true } => self.run_proc(&items), - Obj::File { stream, exec: true } => match self.program_of(&stream)? { - Some(program) => self.run_program(&program), - None => Ok(()), - }, + Obj::File { stream, exec: true } => self.run_file(&stream), Obj::ExecStr(s) => self.run_program(&s.to_vec()), // Through the depth guard: `stopped` and the like call back into here. Obj::Op(op) => { @@ -428,7 +431,35 @@ impl<'a> Interp<'a> { r } - /// Run `program` (what is left of an executable file, an executable string), token by token. + /// Run file `f` (`cvx exec`), reading its objects from it as it goes. While it runs it is + /// `currentfile`, the topmost file being run (PLRM 3rd ed., `currentfile`, ), + /// so data the program reads from there (an inline image's samples) comes from `f`, after the + /// object that reads it. The program's own file is being run already. + fn run_file(&mut self, f: &Rc>) -> Res { + if matches!(&*f.borrow(), Stream::Current) { + return Ok(()); + } + // Its data, decoded from the program it was made over, before it becomes the program. + self.materialize(f)?; + self.enter()?; + self.running.push(f.clone()); + let r = loop { + match self.next_in(f) { + Ok((Some(o), immediate)) => { + if let Err(e) = self.scanned(o, immediate).and_then(|o| self.exec_token(o)) { + break Err(e); + } + } + Ok((None, _)) => break Ok(()), + Err(e) => break Err(e), + } + }; + self.running.pop(); + self.depth -= 1; + r + } + + /// Run `program` (an executable string), token by token. fn run_program(&mut self, program: &[u8]) -> Res { self.enter()?; let mut lex = Lexer::new(program); diff --git a/crates/eps/src/import/tests_images.rs b/crates/eps/src/import/tests_images.rs index 199a6ed7a..76dc84670 100644 --- a/crates/eps/src/import/tests_images.rs +++ b/crates/eps/src/import/tests_images.rs @@ -77,6 +77,35 @@ fn separate_sources_keep_their_planes() { assert_eq!(pixels(&r, 3, 2), [[255, 0, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255], [16, 64, 112, 255], [32, 80, 128, 255], [48, 96, 144, 255]]); } +/// A program run from a file (`filter cvx exec`, as Ghostscript's `eps2write` runs each page's +/// content) reads data through `currentfile` from that file, right after the operator that reads +/// it (PLRM 3rd ed., `currentfile`, ): an image's +/// samples, kept after it as an inline image keeps them, plain or up to `~>` through +/// `SubFileDecode` and `ASCII85Decode`. +#[test] +fn image_data_comes_from_the_file_being_run() { + let r = read( + "currentfile 0 (endstream) /SubFileDecode filter cvx exec\n\ + 30 20 scale 3 2 8 [3 0 0 -2 0 2] currentfile /ASCIIHexDecode filter false 3 colorimage\n\ + ff0000 00ff00 0000ff 102030 405060 708090>\n\ + endstream\n", + ); + assert_eq!(pixels(&r, 3, 2), [[255, 0, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255], [16, 32, 48, 255], [64, 80, 96, 255], [112, 128, 144, 255]]); + let r = read( + "/ID { 3 1 8 [3 0 0 -1 0 1] currentfile 0 (~>) /SubFileDecode filter /ASCII85Decode filter false 3 colorimage } def\n\ + currentfile 0 (endstream) /SubFileDecode filter cvx exec\n\ + 30 20 scale ID\n\ + rr<$!rr<$!rr~>\n\ + endstream\n", + ); + assert_eq!(pixels(&r, 3, 1), [[255, 0, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255]]); + super::tests::check( + "/ok false def currentfile 0 (endstream) /SubFileDecode filter cvx exec\n\ + currentfile 4 string readstring WXYZ pop (WXYZ) eq /ok exch def\n\ + endstream\nok", + ); +} + /// CMYK samples keep their ink amounts in a CMYK TIFF: in the operand and dictionary forms, from /// hex and binary data, from one source or one a component, at 4, 8 and 16 bits, read through /// the Decode array. Gray and RGB images stay PNGs.