mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 17:40:14 -06:00
Merge pull request #5323 from JonRC/fix/eps-currentfile-executed-file
Fix #5321: `currentfile` in a file being run is that file
This commit is contained in:
@@ -1,15 +1,19 @@
|
||||
use super::ops::MAX_DATA;
|
||||
use super::{
|
||||
DecodeSettings, Decoder, DecoderContext, DictRef, EncodingMode, ImageBlob, Key, Obj, PsError, Rc, Res, find, hex_decode, jpeg_components, ps,
|
||||
ps_err,
|
||||
DecodeSettings, Decoder, DecoderContext, DictRef, EncodingMode, ImageBlob, Key, Obj, PsError, Rc, RefCell, Res, find, hex_decode,
|
||||
jpeg_components, ps, ps_err,
|
||||
};
|
||||
|
||||
/// A file object: the program itself, or data a filter decoded.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Stream {
|
||||
Current,
|
||||
/// Filters over the program's own data, decoded when first read.
|
||||
Pending(Vec<Filter>),
|
||||
/// Filters over the data the program reads, decoded when first read, from the file they were
|
||||
/// made over: a file being run (`from`), or else the program's own file.
|
||||
Pending {
|
||||
chain: Vec<Filter>,
|
||||
from: Option<Rc<RefCell<Stream>>>,
|
||||
},
|
||||
Buf {
|
||||
data: Vec<u8>,
|
||||
pos: usize,
|
||||
|
||||
@@ -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()?;
|
||||
@@ -94,7 +97,7 @@ impl Interp<'_> {
|
||||
let n = match &*f.borrow() {
|
||||
Stream::Current => self.lex.rest().len(),
|
||||
Stream::Buf { data, pos, .. } => data.len().saturating_sub(*pos),
|
||||
Stream::Pending(_) => 0,
|
||||
Stream::Pending { .. } => 0,
|
||||
};
|
||||
self.push(Obj::Int(n as i64))?;
|
||||
}
|
||||
@@ -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)?;
|
||||
@@ -201,7 +210,7 @@ impl Interp<'_> {
|
||||
*pos = end;
|
||||
data.get(start..end).unwrap_or_default().to_vec()
|
||||
}
|
||||
Stream::Pending(_) => vec![],
|
||||
Stream::Pending { .. } => vec![],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -220,7 +229,7 @@ impl Interp<'_> {
|
||||
*pos += 1;
|
||||
}
|
||||
}
|
||||
Stream::Pending(_) => {}
|
||||
Stream::Pending { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,23 +284,28 @@ 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 program's own file or the file being run waits until it is
|
||||
// first read, and keeps which of them it reads, wherever the program runs by then.
|
||||
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::Pending(c) => Some(c.clone()),
|
||||
let pending = match &*f.borrow() {
|
||||
Stream::Current => Some((vec![], None)),
|
||||
Stream::Buf { .. } if running => Some((vec![], Some(f.clone()))),
|
||||
Stream::Pending { chain, from } => Some((chain.clone(), from.clone())),
|
||||
Stream::Buf { .. } => None,
|
||||
};
|
||||
match chain {
|
||||
Some(mut c) => {
|
||||
match pending {
|
||||
Some((mut chain, from)) => {
|
||||
// The new filter reads a pending one's data: it has none left of its own
|
||||
// (`flushfile` on it skips nothing more).
|
||||
if !c.is_empty() {
|
||||
if !chain.is_empty() {
|
||||
*f.borrow_mut() = Stream::Buf { data: vec![], pos: 0, rgb: None };
|
||||
}
|
||||
c.push(spec);
|
||||
Stream::Pending(c)
|
||||
chain.push(spec);
|
||||
Stream::Pending { chain, from }
|
||||
}
|
||||
None => {
|
||||
let mut b = f.borrow_mut();
|
||||
@@ -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,20 +348,32 @@ 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 {
|
||||
let chain = match &*f.borrow() {
|
||||
Stream::Pending(c) => c.clone(),
|
||||
/// Decode the filters a file waits to run over the data the program reads, from where it is
|
||||
/// now in the file they were made over: a file being run, or the program's own file.
|
||||
pub(in super::super) fn materialize(&mut self, f: &Rc<RefCell<Stream>>) -> Res {
|
||||
let (chain, from) = match &*f.borrow() {
|
||||
Stream::Pending { chain, from } => (chain.clone(), from.clone()),
|
||||
_ => return Ok(()),
|
||||
};
|
||||
let mut buf: Option<(Vec<u8>, Option<u8>)> = None;
|
||||
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 &from {
|
||||
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,60 @@ 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",
|
||||
);
|
||||
}
|
||||
|
||||
/// A filter waits until it is first read, then reads the file it was made over, wherever the
|
||||
/// program runs by then: one over the program's own file, read in a file being run, and one over
|
||||
/// a file being run, read in a file that one runs.
|
||||
#[test]
|
||||
fn filters_read_the_file_they_were_made_over() {
|
||||
super::tests::check(
|
||||
"/f currentfile /ASCIIHexDecode filter def\n\
|
||||
currentfile 0 (endstream) /SubFileDecode filter cvx exec\n\
|
||||
/s f 3 string readstring pop def\n\
|
||||
endstream\n\
|
||||
616263>\n\
|
||||
s (abc) eq",
|
||||
);
|
||||
super::tests::check(
|
||||
"currentfile 0 (endstream) /SubFileDecode filter cvx exec\n\
|
||||
/g currentfile /ASCIIHexDecode filter def\n\
|
||||
currentfile 0 (endinner) /SubFileDecode filter cvx exec\n\
|
||||
/t g 3 string readstring pop def\n\
|
||||
endinner\n\
|
||||
78797A>\n\
|
||||
endstream\n\
|
||||
t (xyz) eq",
|
||||
);
|
||||
}
|
||||
|
||||
/// 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