Pending filters read the file they were made over

A filter over `currentfile` waits until it is first read, and it took
its data from whichever file was running by then. One made over the
program's own file and first read inside a file run with `cvx exec`
read that file instead, and one made over a file being run, first read
in a file that one runs, read the inner file. The pending stream now
records the file it was made over (a chained filter inherits it), and
`materialize` decodes from that file.
This commit is contained in:
JonRC
2026-11-03 15:57:03 +07:00
committed by bflatastic
parent 6fc82a141f
commit 400a49666a
3 changed files with 53 additions and 24 deletions
+8 -4
View File
@@ -1,15 +1,19 @@
use super::ops::MAX_DATA; use super::ops::MAX_DATA;
use super::{ use super::{
DecodeSettings, Decoder, DecoderContext, DictRef, EncodingMode, ImageBlob, Key, Obj, PsError, Rc, Res, find, hex_decode, jpeg_components, ps, DecodeSettings, Decoder, DecoderContext, DictRef, EncodingMode, ImageBlob, Key, Obj, PsError, Rc, RefCell, Res, find, hex_decode,
ps_err, jpeg_components, ps, ps_err,
}; };
/// A file object: the program itself, or data a filter decoded. /// A file object: the program itself, or data a filter decoded.
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum Stream { pub(crate) enum Stream {
Current, Current,
/// Filters over the program's own data, decoded when first read. /// Filters over the data the program reads, decoded when first read, from the file they were
Pending(Vec<Filter>), /// made over: a file being run (`from`), or else the program's own file.
Pending {
chain: Vec<Filter>,
from: Option<Rc<RefCell<Stream>>>,
},
Buf { Buf {
data: Vec<u8>, data: Vec<u8>,
pos: usize, pos: usize,
+20 -20
View File
@@ -97,7 +97,7 @@ impl Interp<'_> {
let n = match &*f.borrow() { let n = match &*f.borrow() {
Stream::Current => self.lex.rest().len(), Stream::Current => self.lex.rest().len(),
Stream::Buf { data, pos, .. } => data.len().saturating_sub(*pos), Stream::Buf { data, pos, .. } => data.len().saturating_sub(*pos),
Stream::Pending(_) => 0, Stream::Pending { .. } => 0,
}; };
self.push(Obj::Int(n as i64))?; self.push(Obj::Int(n as i64))?;
} }
@@ -189,7 +189,7 @@ impl Interp<'_> {
*pos += rest.len() - lex.data().len(); *pos += rest.len() - lex.data().len();
(o, lex.immediate) (o, lex.immediate)
} }
Stream::Pending(_) => (None, false), Stream::Pending { .. } => (None, false),
}) })
} }
@@ -210,7 +210,7 @@ impl Interp<'_> {
*pos = end; *pos = end;
data.get(start..end).unwrap_or_default().to_vec() data.get(start..end).unwrap_or_default().to_vec()
} }
Stream::Pending(_) => vec![], Stream::Pending { .. } => vec![],
}) })
} }
@@ -229,7 +229,7 @@ impl Interp<'_> {
*pos += 1; *pos += 1;
} }
} }
Stream::Pending(_) => {} Stream::Pending { .. } => {}
} }
} }
@@ -284,28 +284,28 @@ impl Interp<'_> {
let spec = Filter { name, sub, predictor, fax }; let spec = Filter { name, sub, predictor, fax };
let stream = match self.pop()? { let stream = match self.pop()? {
Obj::File { stream: f, .. } => { Obj::File { stream: f, .. } => {
// A filter over the file being run reads it, as one over the program's own file // A filter over the program's own file or the file being run waits until it is
// does, from where the program is when the filter is first read. // 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)); let running = self.running.last().is_some_and(|r| Rc::ptr_eq(r, &f));
match &*f.borrow() { match &*f.borrow() {
Stream::Buf { data, pos, .. } if !running => self.alloc(data.len().saturating_sub(*pos))?, Stream::Buf { data, pos, .. } if !running => self.alloc(data.len().saturating_sub(*pos))?,
_ => {} _ => {}
} }
let chain = match &*f.borrow() { let pending = match &*f.borrow() {
Stream::Current => Some(vec![]), Stream::Current => Some((vec![], None)),
Stream::Buf { .. } if running => Some(vec![]), Stream::Buf { .. } if running => Some((vec![], Some(f.clone()))),
Stream::Pending(c) => Some(c.clone()), Stream::Pending { chain, from } => Some((chain.clone(), from.clone())),
Stream::Buf { .. } => None, Stream::Buf { .. } => None,
}; };
match chain { match pending {
Some(mut c) => { Some((mut chain, from)) => {
// The new filter reads a pending one's data: it has none left of its own // The new filter reads a pending one's data: it has none left of its own
// (`flushfile` on it skips nothing more). // (`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 }; *f.borrow_mut() = Stream::Buf { data: vec![], pos: 0, rgb: None };
} }
c.push(spec); chain.push(spec);
Stream::Pending(c) Stream::Pending { chain, from }
} }
None => { None => {
let mut b = f.borrow_mut(); let mut b = f.borrow_mut();
@@ -348,18 +348,18 @@ impl Interp<'_> {
}) })
} }
/// Decode the filters a file waits to run over the program's data, from where it is now: the /// Decode the filters a file waits to run over the data the program reads, from where it is
/// file being run, or the program's own file. /// 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 { pub(in super::super) fn materialize(&mut self, f: &Rc<RefCell<Stream>>) -> Res {
let chain = match &*f.borrow() { let (chain, from) = match &*f.borrow() {
Stream::Pending(c) => c.clone(), Stream::Pending { chain, from } => (chain.clone(), from.clone()),
_ => return Ok(()), _ => return Ok(()),
}; };
let mut buf: Option<(Vec<u8>, Option<u8>)> = None; let mut buf: Option<(Vec<u8>, Option<u8>)> = None;
for spec in &chain { for spec in &chain {
buf = Some(match buf.take() { buf = Some(match buf.take() {
None => { None => {
let (data, rgb) = match self.running.last() { let (data, rgb) = match &from {
Some(r) => { Some(r) => {
let mut b = r.borrow_mut(); let mut b = r.borrow_mut();
let Stream::Buf { data, pos, .. } = &mut *b else { return io("filter") }; let Stream::Buf { data, pos, .. } = &mut *b else { return io("filter") };
+25
View File
@@ -106,6 +106,31 @@ fn image_data_comes_from_the_file_being_run() {
); );
} }
/// 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 /// 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 /// 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. /// the Decode array. Gray and RGB images stay PNGs.