mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-03 06:51:50 -06:00
fix(render): a JBIG2 height class of no symbols decodes no bitmap
Rows are charged by the symbol, so an empty class decoded its collective bitmap for nothing: 65,535 rows a class, a thousand classes a dictionary. 806 KB took 196 s. It steps over the bytes instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b7ec3669cc
commit
e3bc4e855f
@@ -3698,6 +3698,44 @@ fn no_row_jbig2_dictionary(symbols: u32) -> Vec<u8> {
|
||||
dictionary
|
||||
}
|
||||
|
||||
/// A Huffman-coded symbol dictionary that climbs to `height`, stays about there for `classes`
|
||||
/// classes of no symbols, each with a byte of MMR bitmap, and drops to one row for its one symbol,
|
||||
/// not exported.
|
||||
fn empty_class_jbig2_dictionary(classes: u32, height: u32) -> Vec<u8> {
|
||||
let mut bits = Bits::default();
|
||||
bits.put(0b111110, 6); // height class delta, the upper line of table B.5
|
||||
bits.put(height - 76, 32);
|
||||
let mut at = height;
|
||||
for class in 0..classes {
|
||||
// Table B.5 has no delta of 0, so it goes down one and up one.
|
||||
if class % 2 == 1 {
|
||||
bits.put(0b1111110, 7); // height class delta -1 (B.5): -255 and 254
|
||||
bits.put(254, 8);
|
||||
at -= 1;
|
||||
} else if class > 0 {
|
||||
bits.put(0, 1); // height class delta 1 (B.5)
|
||||
at += 1;
|
||||
}
|
||||
bits.put(0b111111, 6); // end of the class (B.2)
|
||||
bits.put(1, 5); // BMSIZE 1 (B.1)
|
||||
bits.align();
|
||||
bits.put(0xFF, 8);
|
||||
}
|
||||
bits.put(0b1111111, 7); // height class delta, the lower line of table B.5
|
||||
bits.put(at - 1 - 256, 32);
|
||||
bits.put(0b10, 2); // width delta 1 (B.2)
|
||||
bits.put(0b111111, 6); // end of the class (B.2)
|
||||
bits.put(0, 5); // BMSIZE 0, uncompressed (B.1)
|
||||
bits.align();
|
||||
bits.put(0, 8); // its row
|
||||
bits.table_b1(1); // the first export run, not exported, is all of them
|
||||
let mut dictionary = vec![0x00, 0x05]; // Huffman, tables B.5, B.2, B.1, B.1
|
||||
dictionary.extend_from_slice(&0u32.to_be_bytes()); // exported
|
||||
dictionary.extend_from_slice(&1u32.to_be_bytes()); // new
|
||||
dictionary.extend_from_slice(&bits.bytes);
|
||||
dictionary
|
||||
}
|
||||
|
||||
/// A Huffman-coded symbol dictionary of `symbols` white symbols of width 1 and `height` rows, none
|
||||
/// exported, for `height` from 76 up.
|
||||
fn narrow_jbig2_dictionary(symbols: u32, height: u32) -> Vec<u8> {
|
||||
@@ -4000,6 +4038,25 @@ fn jbig2_refinement_region() -> Vec<u8> {
|
||||
region
|
||||
}
|
||||
|
||||
/// A class of no symbols has nothing to cut from its bitmap, and rows are charged by the symbol, so
|
||||
/// decoding one would be rows for nothing: a thousand classes of 65,000 rows take a second, from a
|
||||
/// dictionary of 4 KB. It isn't decoded, however many there are, and the symbol after them shows that
|
||||
/// its bytes are stepped over.
|
||||
#[test]
|
||||
fn jbig2_classes_of_no_symbols_decode_no_bitmap() {
|
||||
let mut stream = black_jbig2_page();
|
||||
for number in 0..1_000 {
|
||||
jbig2_segment(&mut stream, number + 1, 0, &[], &empty_class_jbig2_dictionary(1024, 65_000));
|
||||
}
|
||||
let (done, finished) = std::sync::mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
let _ = done.send(jbig2_image_draws(&stream));
|
||||
});
|
||||
// This takes milliseconds now and took a quarter of an hour, so a margin this wide can't fail on
|
||||
// a busy machine.
|
||||
assert_eq!(finished.recv_timeout(std::time::Duration::from_secs(120)), Ok(true));
|
||||
}
|
||||
|
||||
/// Each item a JBIG2 stream decodes costs its work, and the stream may ask for 2^30: four of the
|
||||
/// largest pages, some 6 s to 17 s. Each bitmap is bounded on its own, but nothing bounded how many
|
||||
/// there were, so 1,140 bytes of generic regions took over 40 s, and a 795-byte PDF's one halftone
|
||||
|
||||
+11
@@ -657,6 +657,17 @@ fn decode_height_class_collective_bitmap(ctx: &mut SymbolDecodeContext<'_>) -> R
|
||||
|
||||
ctx.h_ctx.reader.align();
|
||||
|
||||
// PdfCraft patch: a class of no symbols has nothing to cut out, and `bitmap::decode_work` charges
|
||||
// rows by the symbol, so decoding this would be rows for nothing: 65,535 a class, and a dictionary
|
||||
// can hold a thousand such classes. Its bytes still have to be stepped over.
|
||||
if ctx.symbols_decoded_count == ctx.height_class_first_symbol {
|
||||
ctx.h_ctx
|
||||
.reader
|
||||
.read_bytes(bitmap_size as usize)
|
||||
.ok_or(ParseError::UnexpectedEof)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let collective_bitmap = if bitmap_size == 0 {
|
||||
// Bitmap is stored uncompressed, so we basically just copy-paste
|
||||
// each byte. Rows are padded to the byte boundary.
|
||||
|
||||
Reference in New Issue
Block a user