Fix #5342: eexec skips in the file it is given

`eexec` dropped its file operand and skipped the encrypted part of a
font program to `cleartomark` in the program's own file. With
`currentfile eexec` inside a file run with `cvx exec`, the program goes
on in that file, so the skip searched the outer file instead and failed
or skipped the wrong text. Ghostscript's eps2write runs each embedded
Type 1 font program that way, so an EPS with one did not open.

`eexec` now skips in its operand, through the same file reading as the
other data operators: the program's own file or the file being run. A
string operand skips in the file being read, as before.
This commit is contained in:
JonRC
2026-11-03 17:19:05 +07:00
committed by bflatastic
parent 60e397c6cb
commit 7db34d9ee0
2 changed files with 31 additions and 9 deletions
+22 -6
View File
@@ -435,10 +435,11 @@ impl Interp<'_> {
Ok(out)
}
/// `eexec`: an encrypted font program follows. It is skipped (type is drawn in the app's
/// fonts, by name): the font dictionary it would complete is defined as it is.
/// `file eexec`: an encrypted font program follows in `file`. It is skipped there, up to the
/// `cleartomark` that ends it (type is drawn in the app's fonts, by name): the font
/// dictionary it would complete is defined as it is.
fn eexec(&mut self) -> Res {
self.pop()?;
let src = self.pop()?;
if let Some(Obj::Dict(d)) = self.stack.last().cloned() {
let name = d.borrow().get(&Key::name("FontName")).cloned();
if let Some(k) = name.and_then(|n| n.key()) {
@@ -446,9 +447,24 @@ impl Interp<'_> {
self.fonts.borrow_mut().insert(k, Obj::Dict(d));
}
}
let rest = self.lex.data();
let at = find(rest, b"cleartomark").ok_or(PsError::Ps("ioerror", "eexec".into()))?;
self.lex.advance(at);
// `currentfile eexec`: the program's own file or the file being run. A string operand
// skips in the file being read.
let f = match src {
Obj::File { stream, .. } => stream,
_ => self.running.last().cloned().unwrap_or_else(|| Rc::new(RefCell::new(Stream::Current))),
};
self.materialize(&f)?;
let missing = || PsError::Ps("ioerror", "eexec".into());
match &mut *f.borrow_mut() {
Stream::Buf { data, pos, .. } => {
let at = find(data.get(*pos..).unwrap_or_default(), b"cleartomark").ok_or_else(missing)?;
*pos += at;
}
_ => {
let at = find(self.lex.data(), b"cleartomark").ok_or_else(missing)?;
self.lex.advance(at);
}
}
self.push(Obj::Mark)
}
+9 -3
View File
@@ -717,14 +717,20 @@ fn type_42_fonts_set_type_at_their_size() {
}
}
/// The encrypted part of a font program is skipped up to its `cleartomark` in the file it is in:
/// the program's own file, or a file being run (`cvx exec`), as Ghostscript's `eps2write` runs
/// each Type 1 font it embeds.
#[test]
fn embedded_font_programs_are_skipped() {
let body = "/MyFont 10 dict begin /FontName /MyFont def /FontMatrix [0.001 0 0 0.001 0 0] def currentdict end currentfile eexec\n\
0123456789abcdef garbage that is encrypted\n0000000000000000000000000000000000000000000000000000000000000000\ncleartomark\n\
/MyFont findfont 10 scalefont setfont 10 10 moveto (x) show";
let r = read(body);
let NodeKind::Text(t) = &objects(&r.document)[0].kind else { panic!("{:?}", r.warnings) };
assert_eq!(t.runs[0].style.font_family, "My Font");
for program in [body.to_string(), format!("currentfile 0 (endstream) /SubFileDecode filter cvx exec\n{body}\nendstream\n")] {
let r = read(&program);
let NodeKind::Text(t) = &objects(&r.document)[0].kind else { panic!("{program}: {:?}", r.warnings) };
assert_eq!(t.runs[0].style.font_family, "My Font");
assert!(r.warnings.is_empty(), "{program}: {:?}", r.warnings);
}
}
/// A document with what the writer writes in its own ways: gradients, type, an image with