mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Merge pull request #3614 from storytold/land/3610
Fix EPS counted metadata reads after CRLF line endings
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user