LibGfx/JBIG2Writer: Support symbol dicts with more than 32K entries

...as long as fewer than 32K entries are referenced in a single
text region segment.

Having more than 32K entries in a symbol dictionary happens in some
real-world PDFs where the "global" JBIG2 stream that's shared over
all pages has lots of letters. But the text region segment on every
single page just has a couple thousand letters (160 characters per line
at 100 lines and a couple thousand dust speckles is well under 32K
symbol references per page).

I'd like to add a test that's similar to such a file, so we need to
support writing text regions referring to symbol dictionaries with
more than 32K symbols.
This commit is contained in:
Nico Weber committed 2026-09-13 21:12:19 -04:00
1 parent f98de99d00
commit bd7be1ae51
1 file changed
+25 -5
@@ -1987,10 +1987,6 @@ static ErrorOr<void> encode_text_region(JBIG2::TextRegionSegmentData const& text
}
if (uses_huffman_encoding) {
// FIXME: Maybe support this one day; the file format supports 32 bits per symbol.
if (!symbols.is_empty() && symbols.size() - 1 >= (1u << 15))
return Error::from_string_literal("JBIG2Writer: Cannot currently encode more than 32767 symbols with Huffman coding");
// Compute optimal huffman table for symbol IDs.
Vector<u16> histogram;
histogram.resize(symbols.size());
@@ -2000,9 +1996,33 @@ static ErrorOr<void> encode_text_region(JBIG2::TextRegionSegmentData const& text
histogram[instance.symbol_id]++;
}
}
// Compress::generate_huffman_lengths() is u16-based and doesn't support more than 15 bits per symbol.
// Symbol dictionaries might have more than 32k, while a page has just a few thousand symbol instances.
// So collect only the used symbols, compute codes for them, and then expand then out to the full set again
// (unused symbols will get a code length of 0 either way). That way, we support symbol dictionaries with
// many symbols, as long as every text region only uses some of them.
Vector<u32> used_symbol_ids;
Vector<u16> used_symbols_histogram;
for (auto const& [symbol_id, count] : enumerate(histogram)) {
if (count > 0) {
used_symbol_ids.append(symbol_id);
used_symbols_histogram.append(count);
}
}
// FIXME: Maybe support this one day; the file format supports 32 bits per symbol.
if (!used_symbol_ids.is_empty() && used_symbol_ids.size() - 1 >= (1u << 15))
return Error::from_string_literal("JBIG2Writer: Cannot currently encode more than 32767 used symbol instances in a text region with Huffman coding");
Vector<u8> used_code_lengths;
used_code_lengths.resize(used_symbol_ids.size());
Compress::generate_huffman_lengths(used_code_lengths, used_symbols_histogram, 15);
Vector<u8> code_lengths;
code_lengths.resize(symbols.size());
Compress::generate_huffman_lengths(code_lengths, histogram, 15);
for (auto const& [i, symbol_id] : enumerate(used_symbol_ids))
code_lengths[symbol_id] = used_code_lengths[i];
auto codes = TRY(JBIG2::assign_huffman_codes(code_lengths));
for (auto const& [i, length] : enumerate(code_lengths)) {