Merge pull request #3614 from storytold/land/3610

Fix EPS counted metadata reads after CRLF line endings
This commit is contained in:
BFlat
2026-10-22 18:34:10 +07:00
committed by GitHub
3 changed files with 61 additions and 0 deletions
+20
View File
@@ -536,6 +536,7 @@ impl Interp<'_> {
break;
}
Some(b'\r') => {
self.skip_lf(&f);
ended = true;
break;
}
@@ -688,6 +689,25 @@ impl Interp<'_> {
})
}
/// After `readline` consumes CR, consume only an immediately following LF. PostScript
/// treats CR/LF as one line ending (PLRM §3.8); raw reads must keep every other byte.
/// The preceding `read` has already materialized any pending filter.
fn skip_lf(&mut self, f: &Rc<RefCell<Stream>>) {
match &mut *f.borrow_mut() {
Stream::Current => {
if self.lex.rest().first() == Some(&b'\n') {
self.lex.advance(1);
}
}
Stream::Buf { data, pos, .. } => {
if data.get(*pos) == Some(&b'\n') {
*pos += 1;
}
}
Stream::Pending(_) => {}
}
}
/// Up to `n` bytes read as hexadecimal digits from `f` (other characters skipped).
fn read_hex(&mut self, f: &Rc<RefCell<Stream>>, n: usize) -> Res<Vec<u8>> {
let mut out = Vec::with_capacity(n);
+40
View File
@@ -633,6 +633,46 @@ pub(super) fn check(cond: &str) {
blue(&format!("{cond} {{ 0 0 1 setrgbcolor }} {{ 1 0 0 setrgbcolor }} ifelse"));
}
/// #3547: a line before counted metadata ends in CR/LF. Leaving LF unread shifts the
/// metadata by one byte and makes its final `>` look like PostScript instead of data.
#[test]
fn readline_keeps_counted_metadata_aligned_for_every_line_ending() {
let tiff = crate::tiff::encode(&Raster { width: 4, height: 4, rgba: [0, 128, 0, 255].repeat(16) }, false, false).unwrap();
for eol in ["\n", "\r", "\r\n"] {
let ps = eps(&format!(
"/consume {{ currentfile 80 string readline pop pop currentfile 4 string readstring pop pop }} bind def\n\
consume\nheader{eol}<x/>\n0 0 1 setrgbcolor 10 20 30 40 rectfill"
));
for bytes in [ps.clone(), crate::dos_eps(&ps, &tiff).unwrap()] {
let r = import(&bytes).unwrap();
assert!(!r.preview, "valid art must not become the low-resolution TIFF preview");
assert!(r.warnings.is_empty(), "{eol:?}: {:?}", r.warnings);
let art = objects(&r.document);
assert_eq!(art.len(), 1);
assert!(matches!(art[0].kind, NodeKind::Path { .. }));
assert_eq!(fill(&art[0]), Some(&Paint::solid(Color::rgb(0.0, 0.0, 1.0))));
}
}
}
/// CR/LF is one terminator in buffered and decoded streams too; lone CR never consumes
/// the next payload byte, EOF is safe, and ordinary `readstring` preserves binary CR/LF.
#[test]
fn readline_streams_preserve_the_next_payload_byte() {
for eol in ["\n", "\r", "\r\n"] {
let bytes = format!("header{eol}XY");
let hex: String = bytes.bytes().map(|b| format!("{b:02x}")).collect();
for source in [format!("<{hex}> /ReusableStreamDecode filter"), format!("({hex}>) /ASCIIHexDecode filter")] {
check(&format!(
"/f {source} def f 32 string readline exch (header) eq and \
f 2 string readstring exch (XY) eq and and"
));
}
}
check("/f <6865616465720d> /ReusableStreamDecode filter def f 32 string readline exch (header) eq and f read not and");
check("/f <0d0a5859> /ReusableStreamDecode filter def f 4 string readstring exch <0d0a5859> eq and");
}
#[test]
fn the_generic_category_defines_new_categories() {
// #234: the reference app's prolog.
+1
View File
@@ -130,6 +130,7 @@ the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPri
| Date | Change | Summary |
|---|---|---|
| 2026-10-22 | minor | EPS import: a CR LF ending after `readline` counts as one line ending (PostScript Language Reference §3.8), so counted metadata that follows it is no longer shifted by a byte and the editable vectors import instead of the preview; scores and estimates unchanged. |
| 2026-10-21 | minor | PDF export: flattening for PDF/X-1a and PDF/X-3 in a CMYK document draws the areas it rasterizes as ink amounts (a CMYK image), so a CMYK colour under an image with see-through pixels keeps its numbers, K included, instead of going through RGB and back (#2160); own-fixture test; scores and hours unchanged |
| 2026-10-21 | minor | EPS and `.ai` editing data: type with an art style of several fills or strokes, effects on a fill (Offset Path, Transform with copies, Drop Shadow, Gaussian Blur, Bas Relief), pattern and gradient fills, per-fill blend modes and a Warp comes in as type with that live appearance instead of its drawn look (outlines), the style read as a tree of filters; other objects keep the rule for plain styles. Point type whose frame has no matrix (written so by newer files) is read; such stories were left out. Where a styled object's drawn look lay, the page isn't held to look like VectorCraft's drawing of the style. `editingData: "always"` also opens an Illustrator EPS from its editing data. Own-fixture tests; a user's file used locally only; scores and hours unchanged |
| 2026-10-18 | minor | PDF import: invisible text (rendering mode 3, which carries a scanned page's recognized text) opens as type with no fill or stroke instead of being dropped without a word; when it can't be type (text opened as outlines, Type 3 fonts) it is left out with a warning. PDF export writes characters with no fill or stroke as invisible text in rendering mode 3, so a scanned PDF opened and saved keeps its searchable text, in PDF/X-1a and PDF/A too (#2675); own-fixture tests; scores and hours unchanged |