mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-11 23:32:35 +00:00
M2: a fax image with more /Rows than its /Height is decoded to its height (hayro-syntax patch 41)
A CCITTFaxDecode image whose /DecodeParms /Rows exceeds the image's /Height (PDFBOX-771: /Rows 1084975104 on 2,592 columns) asked the decoder for /Rows rows; the size check refused that, and every page drew blank. The image is /Height rows (ISO 32000-2 Table 87), so the decoder is asked for those; /Rows still counts for a stream decoded without an image's height. pdfcraft-cos already decodes /Height rows; a test pins it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pq7HyDBXygTf7jcqdsjcfn
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f42fda7342
commit
d4b4218cf1
@@ -274,6 +274,10 @@ fn fax_images_decode_with_their_parameters() {
|
||||
let mut understated = parms.clone();
|
||||
understated.set(b"Rows".to_vec(), Object::Int(1));
|
||||
assert_eq!(image(&doc, Some(Object::Dict(understated.clone())), 10, 3).unwrap(), bits, "/Height rows, whatever /Rows says");
|
||||
// More /Rows than /Height (PDFBOX-771: 1,084,975,104), as the renderer reads it too.
|
||||
let mut overstated = parms.clone();
|
||||
overstated.set(b"Rows".to_vec(), Object::Int(1_000_000_000));
|
||||
assert_eq!(image(&doc, Some(Object::Dict(overstated)), 10, 3).unwrap(), bits, "/Height rows, not /Rows past it");
|
||||
// The parameters as the file gives them, /Rows included, and Table 11's defaults.
|
||||
let mut d = Dict::new();
|
||||
d.set(b"Filter".to_vec(), Object::name("CCITTFaxDecode"));
|
||||
|
||||
@@ -3454,6 +3454,48 @@ trailer << /Root 1 0 R >>
|
||||
}
|
||||
}
|
||||
|
||||
/// PDFBOX-771: a fax image whose `/Rows` says more rows than its `/Height` (1,084,975,104 on a
|
||||
/// 2,592-column scan) is decoded to its height, as the image is (ISO 32000-2 Table 87): the
|
||||
/// decoder was asked for `/Rows` rows, which the size check refused, so every page drew blank.
|
||||
/// Also through an `/Indexed` colour space, the filter's other output path.
|
||||
#[test]
|
||||
fn group4_fax_with_rows_past_its_height_decodes_to_the_image_height() {
|
||||
const ALL_BLACK_8X4: [u8; 6] = [0x26, 0xa2, 0xfe, 0x00, 0x20, 0x02];
|
||||
for space in ["/DeviceGray", "[/Indexed /DeviceRGB 1 <000000FFFFFF>]"] {
|
||||
for rows in ["/Rows 5", "/Rows 1000000000", "/Rows 4294967295"] {
|
||||
let content = "q 20 0 0 20 5 5 cm /Im1 Do Q";
|
||||
let mut pdf = format!(
|
||||
"%PDF-1.7
|
||||
1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj
|
||||
2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj
|
||||
3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 40 40] /Contents 4 0 R /Resources << /XObject << /Im1 5 0 R >> >> >> endobj
|
||||
4 0 obj << /Length {} >> stream
|
||||
{content}
|
||||
endstream endobj
|
||||
5 0 obj << /Type /XObject /Subtype /Image /Width 8 /Height 4 /ColorSpace {space} /BitsPerComponent 1 /Filter /CCITTFaxDecode /DecodeParms << /K -1 /Columns 8 {rows} >> /Length {} >> stream
|
||||
",
|
||||
content.len(),
|
||||
ALL_BLACK_8X4.len()
|
||||
)
|
||||
.into_bytes();
|
||||
pdf.extend_from_slice(&ALL_BLACK_8X4);
|
||||
pdf.extend_from_slice(
|
||||
b"
|
||||
endstream endobj
|
||||
trailer << /Root 1 0 R >>
|
||||
%%EOF",
|
||||
);
|
||||
let mut renderer = PageRenderer::new(Arc::new(pdf), RenderConfig::default());
|
||||
let page = renderer.render(RenderRequest { page: 0, kind: RequestKind::Pixels, tile: None, scale: 1.0, tag: 0, ..Default::default() });
|
||||
assert!(page.error.is_none(), "{space} {rows}: {:?}", page.error);
|
||||
for y in [17, 22, 27, 32] {
|
||||
assert_eq!(&page.rgba[((y * 40 + 15) * 4)..][..4], &[0, 0, 0, 255], "{space} {rows}: image row at y {y} paints black");
|
||||
}
|
||||
assert_eq!(&page.rgba[((38 * 40 + 1) * 4)..][..4], &[255, 255, 255, 255], "{space} {rows}: outside the image stays white");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tiff_predictor_packed_rgb_rows_keep_their_byte_boundaries() {
|
||||
use hayro_syntax::object::{FromBytes, Object};
|
||||
|
||||
@@ -806,8 +806,8 @@ milestone = "M2"
|
||||
status = "partial"
|
||||
commands = []
|
||||
tools = []
|
||||
evidence = []
|
||||
notes = "hayro bootstrap"
|
||||
evidence = ["crates/render/src/raster/tests.rs::group4_fax_with_rows_zero_decodes_to_the_image_height", "crates/render/src/raster/tests.rs::group4_fax_with_rows_below_its_height_decodes_to_the_image_height", "crates/render/src/raster/tests.rs::group4_fax_with_rows_past_its_height_decodes_to_the_image_height"]
|
||||
notes = "hayro bootstrap; a CCITT fax image is decoded to its /Height whatever its /DecodeParms /Rows says (0, fewer or more rows)"
|
||||
|
||||
[[feature]]
|
||||
id = "view.render-annotation-appearances"
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+8
-4
@@ -20,10 +20,14 @@ pub(crate) fn decode(
|
||||
// PdfCraft patch (19): fewer `/Rows` than the image's `/Height` doesn't cut the image short: a
|
||||
// plotter driver wrote `/Rows` equal to `/Columns`, and the rows after it were drawn black
|
||||
// (#3308). The decoder still stops where the data ends.
|
||||
let rows = params
|
||||
.get::<u32>(ROWS)
|
||||
.filter(|&rows| rows != 0)
|
||||
.map_or(image_params.height, |rows| rows.max(image_params.height));
|
||||
// PdfCraft patch (41): more `/Rows` than the image's `/Height` doesn't make the decoder go
|
||||
// past it either: the image is `/Height` rows (ISO 32000-2 Table 87), and a `/Rows` of
|
||||
// 1,084,975,104 on 2,592 columns had the size check refuse every page of a scan (PDFBOX-771).
|
||||
// `/Rows` counts only for a stream decoded without an image's height.
|
||||
let rows = match image_params.height {
|
||||
0 => params.get::<u32>(ROWS).unwrap_or(0),
|
||||
height => height,
|
||||
};
|
||||
let end_of_block = params.get::<bool>(END_OF_BLOCK).unwrap_or(true);
|
||||
|
||||
let settings = DecodeSettings {
|
||||
|
||||
Reference in New Issue
Block a user