mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-11-07 13:22:42 +00:00
M2: a JBIG2 refinement reads a reference right of it from its first column (#6048)
A refinement whose reference is offset right of it (GRREFERENCEDX above 0, which a refined text instance has from a wider bitmap, ISO/IEC 14492 Table 12, and a refined symbol from an RDX above 0, Table 18) read the reference's first 32 columns as 0. hayro-jbig2 emptied its buffers of the reference left of it and, at its first column, didn't load them again, so the refinement decoded to other pixels than were coded, or failed and lost the page. Vendored patch 15 (13 is #5999, 14 #6016): left of the reference the buffers keep the words of its first column each row starts with. With an offset of 0 or less nothing changes. Claude-Session: https://claude.ai/code/session_01AmsdeUCdTaD72jkDw2pJKY Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
FredApps
parent
c0cd40f2e1
commit
8717a085ff
@@ -320,6 +320,57 @@ mod tests {
|
||||
assert_eq!(u64::MAX - work, 4 * 14);
|
||||
}
|
||||
|
||||
/// A refinement whose reference is offset right of it (GRREFERENCEDX above 0, as a refined text
|
||||
/// instance has from a wider bitmap, ISO/IEC 14492 Table 12, and a refined symbol from an RDX
|
||||
/// above 0, Table 18) reads the reference from its first column. Left of the reference the
|
||||
/// decoder emptied what it had read of it, and at its first column didn't read it again, so its
|
||||
/// first 32 columns read as 0: such a refinement decoded to other pixels than were coded, or
|
||||
/// failed. Each stream is a refinement coded by the standard's procedures on a page of its own
|
||||
/// size, and decodes to its picture, `#` for black. Vendored hayro-jbig2 patch 15.
|
||||
#[test]
|
||||
fn a_refinement_reads_a_reference_right_of_it_from_its_first_column() {
|
||||
let cases: [(&str, &str, &[&str]); 4] = [
|
||||
(
|
||||
"a text instance of an 8 × 6 symbol refined 6 wider: 3 right, template 0",
|
||||
"00000000300001000000130000000e000000060000000000000000420000000000010000010000001d000003fffdff02fefefe00000001000000014fdd3dba48bdddde35ffac00000002062001010000002c0000000e000000060000000000000000040012ffffffff00000001a96b4a6b78b7fc094fadde1b3e881fffac",
|
||||
&[".#.#.#....#...", "##.###....##.#", ".###...###..#.", "#.......#...#.", "#.####.#...#..", "#.##...###.#.."],
|
||||
),
|
||||
(
|
||||
"refined 2 wider and 2 taller, RDX and RDY 1: 2 right, 2 down, template 1",
|
||||
"00000000300001000000130000000a000000080000000000000000420000000000010000010000001d000003fffdff02fefefe00000001000000014fdd9c52d3ea827a43ffac0000000206200101000000280000000a00000008000000000000000004801200000001a96c00cdac6dd5f6f848ce872411cfffac",
|
||||
&["####.##...", "####..####", "####..##..", "...##...#.", "#......#..", "..#.....#.", "##....####", ".#........"],
|
||||
),
|
||||
(
|
||||
"a symbol refined in a symbol dictionary, RDX 5, RDY −1, template 0 with its AT pixels moved",
|
||||
"00000000300001000000130000000c000000060000000000000000420000000000010000010000001e000003fffdff02fefefe00000001000000014fdda13efc4029efc95fffac000000020020010100000027000203fffdff02fefefefeff010100000001000000014fc98c352dd84a906f120ffb49793fffac00000003062002010000001b0000000c00000006000000000000000004001000000001a943ffac",
|
||||
&["#.#.##.#.###", "..#.#.....##", "##..#...#...", "..##....##..", "##.#.##.####", ".#..#####..#"],
|
||||
),
|
||||
(
|
||||
"a text instance of a 40 × 5 symbol refined 2 wider, RDX 1: 2 right, across a word, template 0",
|
||||
"00000000300001000000130000002a0000000500000000000000004200000000000100000100000031000003fffdff02fefefe00000001000000015240f590d953ae3f4069dede672090d4d61b377906d3f41aafa4f77107ffac00000002062001010000003c0000002a000000050000000000000000040012ffffffff00000001a96c0b9386d4b140486bdc3673dbd031490e468c7f7813d19068ab04a79cbfffac",
|
||||
&[
|
||||
"##.#.#..##.##.#####.#....#.#.#..#..######.",
|
||||
".#.#..###....#..##.#..#.####.#..#.###...##",
|
||||
".#.#......#....#####....#...###...#####...",
|
||||
"#...##.##.......#...###.###...##..##.###.#",
|
||||
"..#..###..###..#...#.......##.#........##.",
|
||||
],
|
||||
),
|
||||
];
|
||||
let hex = |text: &str| (0..text.len() / 2).map(|i| u8::from_str_radix(&text[i * 2..i * 2 + 2], 16).unwrap()).collect::<Vec<u8>>();
|
||||
for (form, stream, picture) in cases {
|
||||
let (w, h) = (picture[0].len(), picture.len());
|
||||
// As the filter gives it: black as 0, each row padded to a byte with 0s.
|
||||
let mut rows = vec![0u8; w.div_ceil(8) * h];
|
||||
for (y, row) in picture.iter().enumerate() {
|
||||
for (x, _) in row.bytes().enumerate().filter(|(_, c)| *c == b'.') {
|
||||
rows[y * w.div_ceil(8) + x / 8] |= 0x80 >> (x % 8);
|
||||
}
|
||||
}
|
||||
assert_eq!(decode_jbig2(&hex(stream), None, w as u32, h as u32, usize::MAX).unwrap(), rows, "{form}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The page has to be the image's size, and is bounded before it is decoded, being decoded
|
||||
/// whole: within `max` as rows of one bit, and within [`MAX_JBIG2_PIXELS`]. Globals are given
|
||||
/// to the decoder: ones it skips decode, ones that aren't JBIG2 are an error, as is damaged
|
||||
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+18
-18
@@ -264,25 +264,25 @@ impl<'a> RefinementContextGatherer<'a> {
|
||||
self.reg_cur = ContextGatherer::load_word(region, self.reg_y, new_start);
|
||||
}
|
||||
|
||||
// PdfCraft patch: left of the reference the buffers keep what `start_row` loaded, its words
|
||||
// from column 0. The context takes in the reference a column to the right of the current
|
||||
// one (Figures 12 and 13), so at column −1 it reads column 0, and from 0 on the buffers are
|
||||
// the reference's own. They were zeroed here and, at column 0, not loaded again: with the
|
||||
// reference offset right of the region (GRREFERENCEDX above 0, which a refined text
|
||||
// instance or symbol has from a wider bitmap or an RDX above 0), its first 32 columns read
|
||||
// as 0, and the region decoded to other pixels than were coded, or as garbage that failed.
|
||||
let ref_x_signed = (x as i32).wrapping_sub(self.reference_dx);
|
||||
if ref_x_signed < 0 {
|
||||
self.ref_cur_x = 0;
|
||||
self.ref_m1 = 0;
|
||||
self.ref_cur = 0;
|
||||
self.ref_p1 = 0;
|
||||
} else {
|
||||
let ref_x = ref_x_signed as u32;
|
||||
|
||||
if ref_x + 1 >= self.ref_cur_x + WORD_BITS || ref_x < self.ref_cur_x {
|
||||
let new_start = ref_x.saturating_sub(1);
|
||||
let ref_y = (self.reg_y as i32).wrapping_sub(self.reference_dy);
|
||||
self.ref_cur_x = new_start;
|
||||
self.ref_m1 =
|
||||
ContextGatherer::load_word(reference, ref_y.wrapping_sub(1) as u32, new_start);
|
||||
self.ref_cur = ContextGatherer::load_word(reference, ref_y as u32, new_start);
|
||||
self.ref_p1 =
|
||||
ContextGatherer::load_word(reference, ref_y.wrapping_add(1) as u32, new_start);
|
||||
}
|
||||
if let Ok(ref_x) = u32::try_from(ref_x_signed)
|
||||
&& (ref_x + 1 >= self.ref_cur_x + WORD_BITS || ref_x < self.ref_cur_x)
|
||||
{
|
||||
let new_start = ref_x.saturating_sub(1);
|
||||
let ref_y = (self.reg_y as i32).wrapping_sub(self.reference_dy);
|
||||
self.ref_cur_x = new_start;
|
||||
self.ref_m1 =
|
||||
ContextGatherer::load_word(reference, ref_y.wrapping_sub(1) as u32, new_start);
|
||||
self.ref_cur = ContextGatherer::load_word(reference, ref_y as u32, new_start);
|
||||
self.ref_p1 =
|
||||
ContextGatherer::load_word(reference, ref_y.wrapping_add(1) as u32, new_start);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+43
@@ -1036,6 +1036,49 @@ mod tests {
|
||||
assert!(refused(&more), "one more isn't");
|
||||
}
|
||||
|
||||
/// PdfCraft patch: a refinement whose reference is offset right of it reads the reference from
|
||||
/// its first column, where it read the first 32 as 0. So that a context read wrong changes what
|
||||
/// decodes, the refinement is large, and its picture comes from the hash it was coded from: a
|
||||
/// 104 × 12 text instance refined from a 100 × 12 symbol 2 right of it.
|
||||
#[test]
|
||||
fn a_refinement_reads_a_reference_right_of_it_from_its_first_column() {
|
||||
#[derive(Default)]
|
||||
struct Rows(Vec<Vec<u8>>, Vec<u8>);
|
||||
impl Decoder for Rows {
|
||||
fn push_pixel(&mut self, black: bool) {
|
||||
self.1.push(u8::from(black));
|
||||
}
|
||||
fn push_pixel_chunk(&mut self, black: bool, chunks: u32) {
|
||||
for _ in 0..8 * chunks {
|
||||
self.push_pixel(black);
|
||||
}
|
||||
}
|
||||
fn next_line(&mut self) {
|
||||
self.0.push(core::mem::take(&mut self.1));
|
||||
}
|
||||
}
|
||||
fn bit(seed: u32, x: u32, y: u32) -> u8 {
|
||||
let mut h = x.wrapping_mul(0x9E37_79B1).wrapping_add(y.wrapping_mul(0x85EB_CA77)).wrapping_add(seed.wrapping_mul(0xC2B2_AE3D));
|
||||
h ^= h >> 15;
|
||||
h = h.wrapping_mul(0x2C1B_3C6D);
|
||||
h ^= h >> 12;
|
||||
h = h.wrapping_mul(0x297A_2D39);
|
||||
h ^= h >> 15;
|
||||
((h >> 7) & 1) as u8
|
||||
}
|
||||
// About one pixel in eight: where a refinement differs from its reference.
|
||||
let flipped = |seed: u32, x: u32, y: u32| bit(seed, x, y) & bit(seed + 1, x, y) & bit(seed + 2, x, y);
|
||||
// A symbol of bit(2, ..), and a text instance refined from it, 4 wider with RDX 0.
|
||||
const TEXT: &str = "0000000030000100000013000000680000000c000000000000000042000000000001000001000000b1000003fffdff02fefefe00000001000000013f525ad7118f669a303f1b11efa5210e9765b834b1831c650f3a3d325dbd7317379ea4fb573b09a20c362f065d8f9301e74cbbc50c69910641c0e0eea7c2fd1e831ca1459a943be55ed8e074b426ad7ff1e8d8ff6f1aa44684de5da4b4c3483911604412e00e96a0db6ec7103feb2bc4301ae98d3f9c23078ae72dc46f76e9dfea209d39187f1194f7b9285cd6e7b2dc7abb5def43ff0abe2df554fc55ffac0000000206200101000000af000000680000000c0000000000000000040012ffffffff00000001a96b5febebec5a68693b51f5db25ddf176d4aae89904a71d1690f12888e10b038db723ac59941f76c2844b739ce524d9563f4005e1edbabf2e323e8e9ac1a3b9060587c3010e7d6bcf90c00dbf9000a2f17b1d9b598e01b9158dc16a63f4388a6f647b99dc7118aa09e08d59fba1bc24e3a0f1ebb0cae636b6bedbb65b01945228d3e5d2980a0ad6290c13d2f34f5c861415ffac";
|
||||
let picture: Vec<Vec<u8>> = (0..12)
|
||||
.map(|y| (0..104).map(|x| (if (2..102).contains(&x) { bit(2, x - 2, y) } else { 0 }) ^ flipped(20, x, y)).collect())
|
||||
.collect();
|
||||
let stream: Vec<u8> = (0..TEXT.len() / 2).map(|i| u8::from_str_radix(&TEXT[i * 2..i * 2 + 2], 16).unwrap()).collect();
|
||||
let mut rows = Rows::default();
|
||||
Image::new_embedded(&stream, None).unwrap().decode(&mut rows).unwrap();
|
||||
assert_eq!(rows.0, picture);
|
||||
}
|
||||
|
||||
/// A dictionary being decoded has what the page may still hold, not all of it: refused only
|
||||
/// once it is stored, its copies would already have been made.
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user