mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 10:40:45 +00:00
Fix #5321: currentfile in a file being run is that file
A file run with `cvx exec` was read whole and run as a separate program, while `currentfile` stayed the outer EPS. The PLRM has `currentfile` return the topmost file being run, positioned after the last token read. Ghostscript's eps2write runs each page's content and each Type 3 glyph from a SubFileDecode filter, and its `ID` reads the inline image's samples through `currentfile`, so they were read from the outer file and then ran as program. A file being run is now read object by object from its own data, and while it runs it is `currentfile`. A filter made over it waits to read, as one over the program's own file does, so it starts where the program is when the filter is first read.
This commit is contained in:
@@ -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<RefCell<Stream>>) -> Res<(Option<Obj>, 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<RefCell<Stream>>, n: usize) -> Res<Vec<u8>> {
|
||||
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<RefCell<Stream>>) -> Res<Option<Vec<u8>>> {
|
||||
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<RefCell<Stream>>) -> Res<Vec<u8>> {
|
||||
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<RefCell<Stream>>) -> 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<RefCell<Stream>>) -> 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)) => {
|
||||
|
||||
@@ -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<Rc<RefCell<Stream>>>,
|
||||
pub stack: Vec<Obj>,
|
||||
pub dicts: Vec<DictRef>,
|
||||
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`, <https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf>),
|
||||
/// 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<RefCell<Stream>>) -> 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);
|
||||
|
||||
@@ -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`, <https://www.adobe.com/jp/print/postscript/pdfs/PLRM.pdf>): 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.
|
||||
|
||||
Reference in New Issue
Block a user