diff --git a/crates/eps/src/import/data/ops.rs b/crates/eps/src/import/data/ops.rs index a39d56081..c5c588557 100644 --- a/crates/eps/src/import/data/ops.rs +++ b/crates/eps/src/import/data/ops.rs @@ -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) } diff --git a/crates/eps/src/import/tests.rs b/crates/eps/src/import/tests.rs index 20593f0e6..52c098bb0 100644 --- a/crates/eps/src/import/tests.rs +++ b/crates/eps/src/import/tests.rs @@ -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