mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-03 15:05:02 -06:00
fix(render): what a JBIG2 item reads is charged, 38 an integer
From a review of the work count. An item with no pixels was charged a step, though reading it can take hundreds of decisions, so a 22,515-byte PDF of refined text instances of no size stayed inside the limit and took 730 s to render. Empty height classes and export runs weren't charged at all, and 19 KB of them took 47 s. Each integer an item reads now costs what one takes at worst, 38, so a decision never costs more than a pixel. A full region of 10,000 instances is 0.14% of the limit, so a real file doesn't notice. 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
72e763ad12
commit
b7ec3669cc
@@ -4108,47 +4108,47 @@ fn jbig2_decoding_work_is_counted_item_by_item() {
|
||||
data: jbig2_pattern_dictionary(32, 2),
|
||||
cost: two_patterns(1, 32),
|
||||
},
|
||||
// Each symbol costs one more than its pixels, so that one of no rows isn't free.
|
||||
Item { what: "symbols of no rows", before: vec![], spent: 0, kind: 0, refs: &[], data: no_row_jbig2_dictionary(5), cost: 5 },
|
||||
// Rows cost in a symbol of no pixels too.
|
||||
Item { what: "symbols without pixels", before: vec![], spent: 0, kind: 0, refs: &[], data: zero_width_jbig2_dictionary(3, false), cost: 15 },
|
||||
Item { what: "a tall symbol of no width", before: vec![], spent: 0, kind: 0, refs: &[], data: zero(1000), cost: 4001 },
|
||||
Item { what: "new symbols", before: vec![], spent: 0, kind: 0, refs: &[], data: square_jbig2_dictionary(80, false), cost: 6721 },
|
||||
Item { what: "referred symbols", before: symbol(), spent: 5, kind: 0, refs: &[1], data: exporting_none, cost: 1 },
|
||||
// What an item reads counts, at 38 an integer: two classes, five widths and an export run.
|
||||
Item { what: "symbols of no rows", before: vec![], spent: 0, kind: 0, refs: &[], data: no_row_jbig2_dictionary(5), cost: 304 },
|
||||
// Rows cost in a symbol of no pixels too, on top of a class, three widths and an export run.
|
||||
Item { what: "symbols without pixels", before: vec![], spent: 0, kind: 0, refs: &[], data: zero_width_jbig2_dictionary(3, false), cost: 202 },
|
||||
Item { what: "a tall symbol of no width", before: vec![], spent: 0, kind: 0, refs: &[], data: zero(1000), cost: 4114 },
|
||||
Item { what: "new symbols", before: vec![], spent: 0, kind: 0, refs: &[], data: square_jbig2_dictionary(80, false), cost: 6834 },
|
||||
Item { what: "referred symbols", before: symbol(), spent: 156, kind: 0, refs: &[1], data: exporting_none, cost: 39 },
|
||||
// Its symbol, and its one row and word; Huffman-coded, a table line for the symbol.
|
||||
Item { what: "immediate text", before: symbol(), spent: 5, kind: 6, refs: &[1], data: jbig2_text_region(1, 1, 0), cost: 13 },
|
||||
Item { what: "intermediate text", before: symbol(), spent: 5, kind: 4, refs: &[1], data: jbig2_text_region(1, 1, 0), cost: 13 },
|
||||
Item { what: "a text region's pixels", before: symbol(), spent: 5, kind: 6, refs: &[1], data: jbig2_text_region(32, 32, 0), cost: 75 },
|
||||
Item { what: "immediate text", before: symbol(), spent: 156, kind: 6, refs: &[1], data: jbig2_text_region(1, 1, 0), cost: 13 },
|
||||
Item { what: "intermediate text", before: symbol(), spent: 156, kind: 4, refs: &[1], data: jbig2_text_region(1, 1, 0), cost: 13 },
|
||||
Item { what: "a text region's pixels", before: symbol(), spent: 156, kind: 6, refs: &[1], data: jbig2_text_region(32, 32, 0), cost: 75 },
|
||||
Item {
|
||||
what: "an intermediate text region's pixels",
|
||||
before: symbol(),
|
||||
spent: 5,
|
||||
spent: 156,
|
||||
kind: 4,
|
||||
refs: &[1],
|
||||
data: jbig2_text_region(32, 32, 0),
|
||||
cost: 75,
|
||||
},
|
||||
// The region's 13, a copy of the symbol's row and the round of the loop.
|
||||
Item { what: "an instance", before: symbol(), spent: 5, kind: 6, refs: &[1], data: jbig2_text_region(1, 1, 1), cost: 15 },
|
||||
// The region's 13, 80 rows of 4 words copied, the round of the loop.
|
||||
// The region's 13, a copy of the symbol's row, and the instance's 4 integers.
|
||||
Item { what: "an instance", before: symbol(), spent: 156, kind: 6, refs: &[1], data: jbig2_text_region(1, 1, 1), cost: 166 },
|
||||
// The region's 13, 80 rows of 4 words copied, and the instance's 4 integers.
|
||||
Item {
|
||||
what: "an instance of a large symbol",
|
||||
before: vec![(0, square_jbig2_dictionary(80, true))],
|
||||
spent: 6721,
|
||||
spent: 6872,
|
||||
kind: 6,
|
||||
refs: &[1],
|
||||
data: jbig2_text_region(1, 1, 1),
|
||||
cost: 334,
|
||||
cost: 485,
|
||||
},
|
||||
// And decoded as well as copied.
|
||||
// And decoded as well as copied, with the 4 integers that gave its size.
|
||||
Item {
|
||||
what: "a refined instance",
|
||||
before: vec![(0, square_jbig2_dictionary(80, true))],
|
||||
spent: 6721,
|
||||
spent: 6872,
|
||||
kind: 6,
|
||||
refs: &[1],
|
||||
data: jbig2_refined_text_region(),
|
||||
cost: 7054,
|
||||
cost: 7357,
|
||||
},
|
||||
// A bit plane, a cell's copy and the region's.
|
||||
Item {
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+6
@@ -31,6 +31,12 @@ pub(crate) const MAX_GRID_CELLS: u64 = 1 << 25;
|
||||
/// halftone 265 s, and a 923-byte PDF's regions would have taken days.
|
||||
pub(crate) const MAX_WORK: u64 = 4 * MAX_PIXELS;
|
||||
|
||||
/// PdfCraft patch: the work of reading one integer of an item, what an arithmetic-coded one takes
|
||||
/// at worst in decisions (see [`MAX_WORK`]), so that a decision costs no more than a pixel. An item
|
||||
/// with no pixels still reads its integers: charged a step each, a 22,515-byte PDF of refined text
|
||||
/// instances of no size stayed inside `MAX_WORK` and took 730 s.
|
||||
pub(crate) const INTEGER_WORK: u64 = 38;
|
||||
|
||||
/// PdfCraft patch: the work of a line of a Huffman table built for symbols, about 50 ns (see
|
||||
/// [`MAX_WORK`]).
|
||||
pub(crate) const HUFFMAN_LINE_WORK: u64 = 10;
|
||||
|
||||
+10
-4
@@ -4,7 +4,7 @@ use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::arithmetic_decoder::{ArithmeticDecoder, ArithmeticDecoderContext};
|
||||
use crate::bitmap::{BITMAP_WORDS, Bitmap, HUFFMAN_LINE_WORK, WORD_BITS, Word, decode_work};
|
||||
use crate::bitmap::{BITMAP_WORDS, Bitmap, HUFFMAN_LINE_WORK, INTEGER_WORK, WORD_BITS, Word, decode_work};
|
||||
use crate::decode::generic::{decode_bitmap_mmr, parse_adaptive_template_pixels};
|
||||
use crate::decode::text::{
|
||||
DecodeContext, ReferenceCorner, TextRegionContexts, TextRegionFlags, TextRegionHeader,
|
||||
@@ -101,6 +101,8 @@ pub(crate) fn decode(
|
||||
bail!(SymbolError::Invalid);
|
||||
}
|
||||
height_classes_left -= 1;
|
||||
// PdfCraft patch: see `bitmap::INTEGER_WORK`. A class of no symbols still reads its delta.
|
||||
*ctx.work_left = ctx.work_left.checked_sub(INTEGER_WORK).ok_or(OverflowError::DecodingWork)?;
|
||||
let height_class_delta =
|
||||
read_height_class_delta(&mut ctx)?.ok_or(SymbolError::UnexpectedOob)?;
|
||||
|
||||
@@ -135,9 +137,11 @@ pub(crate) fn decode(
|
||||
if ctx.pixels > MAX_DICTIONARY_PIXELS {
|
||||
bail!(OverflowError::BitmapDimension);
|
||||
}
|
||||
// PdfCraft patch: see `bitmap::MAX_WORK`. One with no rows costs no pixels, but it is
|
||||
// decoded all the same, and a stream can hold thousands of dictionaries of them.
|
||||
let work = decode_work(symbol_width, ctx.height_class_height).saturating_add(1);
|
||||
// PdfCraft patch: see `bitmap::INTEGER_WORK`. Its width, and a stream can hold thousands
|
||||
// of dictionaries of symbols of no pixels. An aggregated one reads its count, a symbol
|
||||
// and two offsets as well.
|
||||
let integers = if ctx.header.flags.use_refagg { 5 } else { 1 };
|
||||
let work = decode_work(symbol_width, ctx.height_class_height).saturating_add(integers * INTEGER_WORK);
|
||||
*ctx.work_left = ctx.work_left.checked_sub(work).ok_or(OverflowError::DecodingWork)?;
|
||||
// PdfCraft patch: see `room`. Rows take a word at least, so symbols of one column
|
||||
// held 270 MiB within the pixel bound, however few the dictionary exported.
|
||||
@@ -760,6 +764,8 @@ fn export_symbols(ctx: &mut SymbolDecodeContext<'_>) -> Result<Vec<Bitmap>> {
|
||||
bail!(SymbolError::Invalid);
|
||||
}
|
||||
runs_left -= 1;
|
||||
// PdfCraft patch: see `bitmap::INTEGER_WORK`. A run of no symbols still reads its length.
|
||||
*ctx.work_left = ctx.work_left.checked_sub(INTEGER_WORK).ok_or(OverflowError::DecodingWork)?;
|
||||
let run_length = read_run_length()?;
|
||||
|
||||
let end_index = index.checked_add(run_length).ok_or(OverflowError::Index)?;
|
||||
|
||||
+5
-5
@@ -11,7 +11,7 @@ use super::{
|
||||
use super::{RegionBitmap, generic_refinement};
|
||||
use crate::ScratchBuffers;
|
||||
use crate::arithmetic_decoder::{ArithmeticDecoder, ArithmeticDecoderContext};
|
||||
use crate::bitmap::{Bitmap, copy_work, decode_work};
|
||||
use crate::bitmap::{Bitmap, INTEGER_WORK, copy_work, decode_work};
|
||||
use crate::error::{HuffmanError, OverflowError, ParseError, Result, SymbolError, bail};
|
||||
use crate::huffman_table::{HuffmanTable, StandardHuffmanTables, TableLine};
|
||||
use crate::integer_decoder::IntegerDecoder;
|
||||
@@ -161,8 +161,8 @@ pub(crate) fn decode_with(
|
||||
let symbol_height = symbol_bitmap_ref.height as i32;
|
||||
|
||||
// PdfCraft patch: see `bitmap::MAX_WORK`. An instance of a large symbol costs its copy,
|
||||
// and 10,000 of one took 64 s. Even one with no pixels goes round this loop.
|
||||
let work = copy_work(symbol_bitmap_ref.width, symbol_bitmap_ref.height).saturating_add(1);
|
||||
// and 10,000 of one took 64 s. Even one with no pixels reads its four integers.
|
||||
let work = copy_work(symbol_bitmap_ref.width, symbol_bitmap_ref.height).saturating_add(4 * INTEGER_WORK);
|
||||
*work_left = work_left.checked_sub(work).ok_or(OverflowError::DecodingWork)?;
|
||||
|
||||
if !header.flags.transposed
|
||||
@@ -573,8 +573,8 @@ fn decode_symbol_instance_bitmap(
|
||||
.ok_or(OverflowError::ReferenceOffset)?;
|
||||
|
||||
// PdfCraft patch: see `bitmap::MAX_WORK`. Before it is decoded: charged after, one instance
|
||||
// could still be 2^28 pixels past what was left.
|
||||
let work = decode_work(refined_width, refined_height);
|
||||
// could still be 2^28 pixels past what was left. With the four integers that gave its size.
|
||||
let work = decode_work(refined_width, refined_height).saturating_add(4 * INTEGER_WORK);
|
||||
*work_left = work_left.checked_sub(work).ok_or(OverflowError::DecodingWork)?;
|
||||
|
||||
let mut refined_bitmap = Bitmap::new(refined_width, refined_height)?;
|
||||
|
||||
Reference in New Issue
Block a user