M2: a reference in /Decode, /W or a palette that leads back to itself ends

Colour spaces already had tests of this through their bases. `/Decode`, in the renderer and in image
export, `/W`, `/W2` and an Indexed space's table hadn't: each read now has such a reference in its
test, and ends.

These hold that the read ends, not what value it comes to. A reference to a reference in a number
array reads as the first number of that reference in the renderer (`/Decode [11 0 R 0]`, with 11
naming 6 and 6 holding 1, reads as 6). That is older than this branch, in `IndirectObject::read` for
an object stored on its own and `ObjectStream::get` for one in an object stream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
This commit is contained in:
Grzegorz Martens
2026-11-01 09:31:58 +01:00
co-authored by Claude Opus 5.5
parent 4c815786c6
commit 33d7a51301
3 changed files with 30 additions and 1 deletions
+8 -1
View File
@@ -2221,7 +2221,8 @@ fn a_stencil_masks_decode_array_can_hold_references() {
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n\
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] /Resources << /XObject << /Im 4 0 R >> >> >> endobj\n\
4 0 obj << /Subtype /Image /ImageMask true /Width 8 /Height 1 /BitsPerComponent 1 /Decode {decode} /Filter /ASCIIHexDecode /Length 3 >> stream\nF0>\nendstream endobj\n\
5 0 obj 1 endobj\n6 0 obj 0 endobj\ntrailer << /Root 1 0 R >>\n%%EOF"
5 0 obj 1 endobj\n6 0 obj 0 endobj\n7 0 obj 7 0 R endobj\n8 0 obj 9 0 R endobj\n9 0 obj 8 0 R endobj\n\
trailer << /Root 1 0 R >>\n%%EOF"
);
let doc = Document::open(std::sync::Arc::new(pdf.into_bytes())).unwrap();
let mut export = crate::extract::extract_images(&doc, &[0], 1);
@@ -2236,4 +2237,10 @@ fn a_stencil_masks_decode_array_can_hold_references() {
for decode in ["[0 5 0 R]", "[6 0 R 1]", "[6 0 R 5 0 R]"] {
assert_eq!(export(decode), identity, "{decode}");
}
// A reference that only leads back to itself holds no number. The read ends, and the file comes
// out one way round or the other.
for decode in ["[7 0 R 0]", "[1 7 0 R]", "[8 0 R 0]", "[1 8 0 R]", "[7 0 R 8 0 R]"] {
let file = export(decode);
assert!(file == inverted || file == identity, "{decode}");
}
}
+13
View File
@@ -1062,6 +1062,9 @@ fn a_decode_array_with_references_in_it_is_read() {
5 0 obj << /Type /XObject /Subtype /Image /Width {width_of} /Height 1 /BitsPerComponent 8 /ColorSpace {space} /Decode {decode} /Filter /ASCIIHexDecode /Length {} >> stream\n{data}\nendstream endobj\n\
6 0 obj 1 endobj\n\
7 0 obj 0 endobj\n\
8 0 obj 8 0 R endobj\n\
9 0 obj 10 0 R endobj\n\
10 0 obj 9 0 R endobj\n\
trailer << /Root 1 0 R >>\n%%EOF",
content.len(),
data.len()
@@ -1090,6 +1093,11 @@ fn a_decode_array_with_references_in_it_is_read() {
for decode in ["[6 0 R 0 1 0 1 0]", "[1 0 6 0 R 0 1 0]", "[1 0 1 0 6 0 R 0]", "[6 0 R 7 0 R 6 0 R 7 0 R 6 0 R 7 0 R]"] {
assert_eq!(render("1", decode, "/DeviceRGB", "204060>"), rgb, "{decode}");
}
// A reference that only leads back to itself holds no number. The read ends, and the image is
// drawn over the red under it.
for decode in ["[8 0 R 0]", "[1 8 0 R]", "[9 0 R 0]", "[1 9 0 R]", "[8 0 R 9 0 R]"] {
assert_ne!(render("1", decode, "/DeviceGray", "40>"), [255, 0, 0, 255], "{decode}");
}
}
/// A function's `/Encode`, `/Decode`, `/Domain` and `/Range` are read a pair at a time too, so they
@@ -1193,6 +1201,11 @@ fn an_indexed_lookup_table_is_read_in_every_legal_form() {
for space in ["[/Indexed /DeviceRGB 1 8 0 R]", "[/Indexed /DeviceRGB 1 99 0 R]", "[/Indexed /DeviceRGB 1]"] {
let _ = render(space, paint, "");
}
// So does a table that only leads back to itself, and the read of it ends.
let cycle = "11 0 obj 11 0 R endobj 12 0 obj 13 0 R endobj 13 0 obj 12 0 R endobj";
for space in ["[/Indexed /DeviceRGB 1 11 0 R]", "[/Indexed /DeviceRGB 1 12 0 R]"] {
let _ = render(space, paint, cycle);
}
}
#[test]
fn watchdog_skips_a_stuck_page_and_keeps_rendering() {
+9
View File
@@ -712,6 +712,7 @@ mod width_tests {
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj\n\
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 10 10] >> endobj\n\
4 0 obj 20 endobj\n5 0 obj {array} endobj\n6 0 obj 22 endobj\n\
7 0 obj 7 0 R endobj\n8 0 obj 9 0 R endobj\n9 0 obj 8 0 R endobj\n\
trailer << /Root 1 0 R >>\n%%EOF\n"
);
Pdf::new(pdf.into_bytes()).unwrap()
@@ -750,5 +751,13 @@ mod width_tests {
let by_range = vec![(20, [-900.0, 400.0, 800.0]), (21, [-900.0, 400.0, 800.0]), (22, [-900.0, 400.0, 800.0])];
assert_eq!(vertical("[20 22 -900 400 800]"), by_range);
assert_eq!(vertical("[4 0 R 22 -900 400 800]"), by_range);
// A reference that only leads back to itself holds no number. The reads end.
for array in ["[7 0 R [500 600]]", "[8 0 R [500 600]]", "[20 7 0 R 700]", "[7 0 R 8 0 R 700]", "[20 [7 0 R 600]]"] {
widths(array);
}
for array in ["[7 0 R [-1000 500 880]]", "[8 0 R 22 -900 400 800]", "[20 [7 0 R 500 880]]"] {
vertical(array);
}
}
}