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::{
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,
+20 -20
View File
@@ -97,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))?;
}
@@ -189,7 +189,7 @@ impl Interp<'_> {
*pos += rest.len() - lex.data().len();
(o, lex.immediate)
}
Stream::Pending(_) => (None, false),
Stream::Pending { .. } => (None, false),
})
}
@@ -210,7 +210,7 @@ impl Interp<'_> {
*pos = end;
data.get(start..end).unwrap_or_default().to_vec()
}
Stream::Pending(_) => vec![],
Stream::Pending { .. } => vec![],
})
}
@@ -229,7 +229,7 @@ impl Interp<'_> {
*pos += 1;
}
}
Stream::Pending(_) => {}
Stream::Pending { .. } => {}
}
}
@@ -284,28 +284,28 @@ impl Interp<'_> {
let spec = Filter { name, sub, predictor, fax };
let stream = match self.pop()? {
Obj::File { stream: f, .. } => {
// 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.
// 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::Buf { .. } if running => 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();
@@ -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
/// file being run, or the program's own file.
/// 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 = match &*f.borrow() {
Stream::Pending(c) => c.clone(),
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 (data, rgb) = match self.running.last() {
let (data, rgb) = match &from {
Some(r) => {
let mut b = r.borrow_mut();
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
/// 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.