1#![cfg_attr(not(feature = "std"), no_std)]
31#![deny(unsafe_code)]
32
33#[cfg(not(feature = "std"))]
34extern crate alloc;
35
36#[cfg(not(feature = "std"))]
37use alloc::{vec, vec::Vec};
38#[cfg(feature = "std")]
39use std::{vec, vec::Vec};
40
41use djvu_bitmap::Bitmap;
42use djvu_zp::ZpDecoder;
43
44#[derive(Debug, thiserror::Error, PartialEq, Eq)]
46pub enum Jb2Error {
47 #[error("JB2 stream is truncated")]
49 Truncated,
50
51 #[error("JB2: bad flag bit in header")]
53 BadHeaderFlag,
54
55 #[error("JB2: inherited dict length exceeds shared dict size")]
57 InheritedDictTooLarge,
58
59 #[error("JB2: stream requires shared dict but none provided")]
61 MissingSharedDict,
62
63 #[error("JB2: image dimensions too large")]
65 ImageTooLarge,
66
67 #[error("JB2: dict reference with empty dict")]
69 EmptyDictReference,
70
71 #[error("JB2: decoded symbol index out of dictionary range")]
73 InvalidSymbolIndex,
74
75 #[error("JB2: unknown record type")]
77 UnknownRecordType,
78
79 #[error("JB2: unexpected record type in dict stream")]
81 UnexpectedDictRecordType,
82
83 #[error("JB2: insufficient data to initialize ZP coder")]
85 ZpInitFailed,
86
87 #[error("JB2: record count exceeds safety limit")]
89 TooManyRecords,
90}
91
92struct NumContext {
101 ctx: Vec<u8>,
102 left: Vec<u32>,
103 right: Vec<u32>,
104}
105
106impl NumContext {
107 fn new() -> Self {
108 NumContext {
110 ctx: vec![0, 0],
111 left: vec![0, 0],
112 right: vec![0, 0],
113 }
114 }
115
116 fn root(&self) -> usize {
117 1
118 }
119
120 fn get_left(&mut self, node: usize) -> usize {
121 if self.left[node] == 0 {
122 let idx = self.ctx.len() as u32;
123 self.ctx.push(0);
124 self.left.push(0);
125 self.right.push(0);
126 self.left[node] = idx;
127 }
128 self.left[node] as usize
129 }
130
131 fn get_right(&mut self, node: usize) -> usize {
132 if self.right[node] == 0 {
133 let idx = self.ctx.len() as u32;
134 self.ctx.push(0);
135 self.left.push(0);
136 self.right.push(0);
137 self.right[node] = idx;
138 }
139 self.right[node] as usize
140 }
141}
142
143fn decode_num(zp: &mut ZpDecoder<'_>, ctx: &mut NumContext, low: i32, high: i32) -> i32 {
146 let mut low = low;
147 let mut high = high;
148 let mut negative = false;
149 let mut cutoff: i32 = 0;
150 let mut phase: u32 = 1;
151 let mut range: u32 = 0xffff_ffff;
152 let mut node = ctx.root();
153
154 while range != 1 {
155 let decision = if low >= cutoff {
156 true
157 } else if high >= cutoff {
158 zp.decode_bit(&mut ctx.ctx[node])
159 } else {
160 false
161 };
162
163 node = if decision {
164 ctx.get_right(node)
165 } else {
166 ctx.get_left(node)
167 };
168
169 match phase {
170 1 => {
171 negative = !decision;
172 if negative {
173 let temp = -low - 1;
174 low = -high - 1;
175 high = temp;
176 }
177 phase = 2;
178 cutoff = 1;
179 }
180 2 => {
181 if !decision {
182 phase = 3;
183 range = ((cutoff + 1) / 2) as u32;
184 if range == 1 {
185 cutoff = 0;
187 } else {
188 cutoff -= (range / 2) as i32;
189 }
190 } else {
191 cutoff = cutoff * 2 + 1;
192 }
193 }
194 3 => {
195 range /= 2;
196 if range == 0 {
197 range = 1;
198 }
199 if range != 1 {
200 if !decision {
201 cutoff -= (range / 2) as i32;
202 } else {
203 cutoff += (range / 2) as i32;
204 }
205 } else if !decision {
206 cutoff -= 1;
207 }
208 }
209 _ => {
210 range = range.saturating_sub(1);
213 }
214 }
215 }
216
217 if negative { -cutoff - 1 } else { cutoff }
218}
219
220#[derive(Clone)]
232struct Jbm {
233 width: i32,
234 height: i32,
235 data: Vec<u8>,
236}
237
238impl Jbm {
239 #[inline(always)]
240 fn row_stride_bytes(width: i32) -> usize {
241 (width.max(0) as usize).div_ceil(8)
242 }
243
244 #[inline(always)]
245 fn stride(&self) -> usize {
246 Self::row_stride_bytes(self.width)
247 }
248
249 #[inline(always)]
250 fn storage_bytes(width: i32, height: i32) -> usize {
251 Self::row_stride_bytes(width).saturating_mul(height.max(0) as usize)
252 }
253
254 fn new(width: i32, height: i32) -> Self {
255 let len = Self::storage_bytes(width, height);
256 Jbm {
257 width,
258 height,
259 data: vec![0u8; len],
260 }
261 }
262
263 #[inline(always)]
265 fn get(&self, row: i32, col: i32) -> u8 {
266 if row < 0 || row >= self.height || col < 0 || col >= self.width {
267 return 0;
268 }
269 let stride = self.stride();
270 let byte = self.data[row as usize * stride + (col as usize / 8)];
271 (byte >> (7 - (col as usize & 7))) & 1
272 }
273
274 #[inline(always)]
276 fn set_black(&mut self, row: usize, col: usize) {
277 let stride = self.stride();
278 self.data[row * stride + (col / 8)] |= 0x80u8 >> (col & 7);
279 }
280
281 fn new_from_pool(width: i32, height: i32, pool: &mut Vec<u8>) -> Self {
289 let bytes = Self::storage_bytes(width, height);
290 if pool.len() < bytes {
291 pool.resize(bytes, 0u8);
292 }
293 pool[..bytes].fill(0u8);
295 let mut data = core::mem::take(pool);
296 data.truncate(bytes);
297 Jbm {
298 width,
299 height,
300 data,
301 }
302 }
303
304 fn crop_and_recycle(self, pool: &mut Vec<u8>) -> Jbm {
315 if self.width > 0 && self.height > 0 {
316 let w = self.width as usize;
317 let h = self.height as usize;
318 let stride = self.stride();
319 let last_col = w - 1;
320 let data = &self.data;
321 let top_has = data[..stride].iter().any(|&b| b != 0);
324 let bot_has = data[(h - 1) * stride..h * stride].iter().any(|&b| b != 0);
325 let left_has = (0..h).any(|r| (data[r * stride] & 0x80) != 0);
326 let right_has =
327 (0..h).any(|r| (data[r * stride + last_col / 8] & (0x80u8 >> (last_col & 7))) != 0);
328 if top_has && bot_has && left_has && right_has {
329 *pool = Vec::with_capacity(self.data.len());
333 return self;
334 }
335 }
336 let cropped = self.crop_to_content();
337 *pool = self.data;
339 cropped
340 }
341
342 fn recycle_into(self, pool: &mut Vec<u8>) {
346 *pool = self.data;
347 }
348
349 fn crop_to_content(&self) -> Jbm {
351 if self.width <= 0 || self.height <= 0 {
352 return Jbm::new(0, 0);
353 }
354 let stride = self.stride();
355 let mut min_row = self.height;
356 let mut max_row: i32 = -1;
357 let mut min_col = self.width;
358 let mut max_col: i32 = -1;
359
360 for row in 0..self.height {
361 let row_bytes = &self.data[row as usize * stride..(row as usize + 1) * stride];
362 let mut byte_min: Option<usize> = None;
364 let mut byte_max: Option<usize> = None;
365 for (i, &b) in row_bytes.iter().enumerate() {
366 if b != 0 {
367 if byte_min.is_none() {
368 byte_min = Some(i);
369 }
370 byte_max = Some(i);
371 }
372 }
373 if let (Some(bmin), Some(bmax)) = (byte_min, byte_max) {
374 let col_lo = bmin * 8 + row_bytes[bmin].leading_zeros() as usize;
375 let col_hi = bmax * 8 + (7 - row_bytes[bmax].trailing_zeros() as usize);
378 let col_hi = col_hi.min(self.width as usize - 1) as i32;
379 let col_lo = col_lo as i32;
380 min_row = min_row.min(row);
381 max_row = max_row.max(row);
382 min_col = min_col.min(col_lo);
383 max_col = max_col.max(col_hi);
384 }
385 }
386
387 if max_row < 0 {
388 return Jbm::new(0, 0);
389 }
390
391 let nw = max_col - min_col + 1;
392 let nh = max_row - min_row + 1;
393 let mut out = Jbm::new(nw, nh);
394
395 for row in min_row..=max_row {
396 for col in min_col..=max_col {
397 let src_byte = self.data[row as usize * stride + (col as usize / 8)];
398 if (src_byte >> (7 - (col as usize & 7))) & 1 != 0 {
399 let out_row = (row - min_row) as usize;
400 let out_col = (col - min_col) as usize;
401 out.set_black(out_row, out_col);
402 }
403 }
404 }
405 out
406 }
407}
408
409const MAX_SYMBOL_PIXELS: usize = 16 * 1024 * 1024; const MAX_EXHAUSTED_SYMBOL_PIXELS: usize = 4096; const MAX_EXHAUSTED_TOTAL_SYMBOL_PIXELS: usize = 4 * 1024 * 1024; pub(crate) const MAX_TOTAL_SYMBOL_PIXELS: usize = 256 * 1024 * 1024;
425const MAX_TOTAL_BLIT_PIXELS: usize = 256 * 1024 * 1024; const MAX_RECORDS: usize = 65_536; const MAX_COMMENT_BYTES: usize = 4096; #[inline(always)]
431fn check_pixel_budget(w: i32, h: i32, total: &mut usize) -> Result<(), Jb2Error> {
432 let pixels = (w.max(0) as usize).saturating_mul(h.max(0) as usize);
433 if pixels > MAX_SYMBOL_PIXELS {
434 return Err(Jb2Error::ImageTooLarge);
435 }
436 *total = total.saturating_add(pixels);
437 if *total > MAX_TOTAL_SYMBOL_PIXELS {
438 return Err(Jb2Error::ImageTooLarge);
439 }
440 Ok(())
441}
442
443#[inline(always)]
444fn check_symbol_decode_budget(
445 zp: &ZpDecoder<'_>,
446 w: i32,
447 h: i32,
448 total: &mut usize,
449) -> Result<(), Jb2Error> {
450 let pixels = (w.max(0) as usize).saturating_mul(h.max(0) as usize);
451 check_pixel_budget(w, h, total)?;
452 if zp.is_exhausted()
453 && (pixels > MAX_EXHAUSTED_SYMBOL_PIXELS || *total > MAX_EXHAUSTED_TOTAL_SYMBOL_PIXELS)
454 {
455 return Err(Jb2Error::Truncated);
456 }
457 Ok(())
458}
459
460#[inline(always)]
465fn check_blit_budget(sym: &Jbm, total: &mut usize) -> Result<(), Jb2Error> {
466 let pixels = (sym.width.max(0) as usize).saturating_mul(sym.height.max(0) as usize);
467 *total = total.saturating_add(pixels);
468 if *total > MAX_TOTAL_BLIT_PIXELS {
469 return Err(Jb2Error::ImageTooLarge);
470 }
471 Ok(())
472}
473#[inline(never)]
478fn decode_direct_row(
479 zp: &mut ZpDecoder<'_>,
480 ctx: &mut [u8; 1024],
481 row_slice: &mut [u8],
482 rp1: &[u8],
483 rp2: &[u8],
484) {
485 use djvu_zp::tables::{LPS_NEXT, MPS_NEXT, PROB, THRESHOLD};
486
487 let mut a: u32 = zp.a;
488 let mut c: u32 = zp.c;
489 let mut fence: u32 = zp.fence;
490 let mut bit_buf = zp.bit_buf;
491 let mut bit_count = zp.bit_count;
492 let data = zp.data;
493 let mut pos = zp.pos;
494
495 macro_rules! read_byte {
496 () => {{
497 let b = if pos < data.len() { data[pos] } else { 0xff };
498 pos = pos.wrapping_add(1);
499 b as u32
500 }};
501 }
502 macro_rules! refill {
503 () => {
504 while bit_count <= 24 {
505 bit_buf = (bit_buf << 8) | read_byte!();
506 bit_count += 8;
507 }
508 };
509 }
510 macro_rules! renorm {
511 () => {{
512 let shift = (a as u16).leading_ones();
513 bit_count -= shift as i32;
514 a = (a << shift) & 0xffff;
515 let mask = (1u32 << (shift & 31)).wrapping_sub(1);
516 c = ((c << shift) | (bit_buf >> (bit_count as u32 & 31)) & mask) & 0xffff;
517 if bit_count < 16 {
518 refill!();
519 }
520 fence = c.min(0x7fff);
521 }};
522 }
523
524 let pix = |row: &[u8], col: usize| -> u32 { row.get(col).copied().unwrap_or(0) as u32 };
525 let w = row_slice.len();
526 let mut r2 = pix(rp2, 0) << 1 | pix(rp2, 1);
527 let mut r1 = pix(rp1, 0) << 2 | pix(rp1, 1) << 1 | pix(rp1, 2);
528 let mut r0: u32 = 0;
529
530 let (rp2_off, rp1_off) = if w >= 3 && rp2.len() >= w && rp1.len() >= w {
531 (&rp2[2..w], &rp1[3..w])
532 } else {
533 (&rp2[..0], &rp1[..0])
534 };
535 let mid_end = rp2_off.len().min(rp1_off.len());
536
537 macro_rules! decode_step {
538 ($out:expr, $n2:expr, $n1:expr) => {{
539 let idx = (((r2 << 7) | (r1 << 2) | r0) & 1023) as usize;
540 let state = ctx[idx] as usize;
541 let mps_bit = state & 1;
542 let z = a + PROB[state] as u32;
543 let bit = if z <= fence {
544 a = z;
545 mps_bit != 0
546 } else {
547 let boundary = 0x6000u32 + ((a + z) >> 2);
548 let z_clamped = z.min(boundary);
549 if z_clamped > c {
550 let complement = 0x10000u32 - z_clamped;
551 a = (a + complement) & 0xffff;
552 c = (c + complement) & 0xffff;
553 ctx[idx] = LPS_NEXT[state];
554 renorm!();
555 (1 - mps_bit) != 0
556 } else {
557 if a >= THRESHOLD[state] as u32 {
558 ctx[idx] = MPS_NEXT[state];
559 }
560 bit_count -= 1;
561 a = (z_clamped << 1) & 0xffff;
562 c = ((c << 1) | (bit_buf >> (bit_count as u32 & 31)) & 1) & 0xffff;
563 if bit_count < 16 {
564 refill!();
565 }
566 fence = c.min(0x7fff);
567 mps_bit != 0
568 }
569 };
570 *$out = bit as u8;
571 r2 = ((r2 << 1) & 0b111) | ($n2 as u32);
572 r1 = ((r1 << 1) & 0b11111) | ($n1 as u32);
573 r0 = ((r0 << 1) & 0b11) | bit as u32;
574 }};
575 }
576
577 let (fast_slice, slow_slice) = row_slice.split_at_mut(mid_end);
578 for (out, (n2, n1)) in fast_slice.iter_mut().zip(rp2_off.iter().zip(rp1_off)) {
579 decode_step!(out, *n2, *n1);
580 }
581 for (i, out) in slow_slice.iter_mut().enumerate() {
582 let col = i + mid_end;
583 decode_step!(out, pix(rp2, col + 2), pix(rp1, col + 3));
584 }
585
586 zp.a = a;
587 zp.c = c;
588 zp.fence = fence;
589 zp.bit_buf = bit_buf;
590 zp.bit_count = bit_count;
591 zp.pos = pos;
592}
593
594#[allow(clippy::too_many_arguments)]
601#[inline(never)]
602fn decode_ref_row(
603 zp: &mut ZpDecoder<'_>,
604 ctx: &mut [u8; 2048],
605 ctx_p: &mut [u16; 2048],
606 cbm_row_mut: &mut [u8],
607 cbm_r1: &[u8],
608 mbm_r2: &[u8],
609 mbm_r1: &[u8],
610 mbm_r0: &[u8],
611 col_shift: i32,
612 init_c_r1: u32,
613 init_m_r1: u32,
614 init_m_r0: u32,
615) {
616 use djvu_zp::tables::{LPS_NEXT, MPS_NEXT, PROB, THRESHOLD};
617
618 let mut a: u32 = zp.a;
619 let mut c: u32 = zp.c;
620 let mut fence: u32 = zp.fence;
621 let mut bit_buf = zp.bit_buf;
622 let mut bit_count = zp.bit_count;
623 let data = zp.data;
624 let mut pos = zp.pos;
625
626 macro_rules! read_byte {
627 () => {{
628 let b = if pos < data.len() { data[pos] } else { 0xff };
629 pos = pos.wrapping_add(1);
630 b as u32
631 }};
632 }
633 macro_rules! refill {
634 () => {
635 while bit_count <= 24 {
636 bit_buf = (bit_buf << 8) | read_byte!();
637 bit_count += 8;
638 }
639 };
640 }
641 macro_rules! renorm {
642 () => {{
643 let shift = (a as u16).leading_ones();
644 bit_count -= shift as i32;
645 a = (a << shift) & 0xffff;
646 let mask = (1u32 << (shift & 31)).wrapping_sub(1);
647 c = ((c << shift) | (bit_buf >> (bit_count as u32 & 31)) & mask) & 0xffff;
648 if bit_count < 16 {
649 refill!();
650 }
651 fence = c.min(0x7fff);
652 }};
653 }
654
655 let pix_row = |row_slice: &[u8], col: i32| -> u32 {
656 if col < 0 {
657 return 0;
658 }
659 row_slice.get(col as usize).copied().unwrap_or(0) as u32
660 };
661
662 let mut c_r0: u32 = 0;
664 let mut c_r1 = init_c_r1;
665 let mut m_r1 = init_m_r1;
666 let mut m_r0 = init_m_r0;
667
668 for col in 0..cbm_row_mut.len() as i32 {
669 let m_r2 = pix_row(mbm_r2, col + col_shift);
670 let idx = ((c_r1 << 8) | (c_r0 << 7) | (m_r2 << 6) | (m_r1 << 3) | m_r0) & 2047;
672
673 let state = ctx[idx as usize] as usize;
674 let prob = ctx_p[idx as usize] as u32; let mps_bit = state & 1;
676 let z = a + prob;
677
678 let bit = if z <= fence {
679 a = z;
680 mps_bit != 0
681 } else {
682 let boundary = 0x6000u32 + ((a + z) >> 2);
683 let z_clamped = z.min(boundary);
684 if z_clamped > c {
685 let complement = 0x10000u32 - z_clamped;
686 a = (a + complement) & 0xffff;
687 c = (c + complement) & 0xffff;
688 let next = LPS_NEXT[state];
689 ctx[idx as usize] = next;
690 ctx_p[idx as usize] = PROB[next as usize];
691 renorm!();
692 (1 - mps_bit) != 0
693 } else {
694 if a >= THRESHOLD[state] as u32 {
695 let next = MPS_NEXT[state];
696 ctx[idx as usize] = next;
697 ctx_p[idx as usize] = PROB[next as usize];
698 }
699 bit_count -= 1;
700 a = (z_clamped << 1) & 0xffff;
701 c = ((c << 1) | (bit_buf >> (bit_count as u32 & 31)) & 1) & 0xffff;
702 if bit_count < 16 {
703 refill!();
704 }
705 fence = c.min(0x7fff);
706 mps_bit != 0
707 }
708 };
709
710 if bit {
711 cbm_row_mut[col as usize] = 1;
712 }
713
714 c_r1 = ((c_r1 << 1) & 0b111) | pix_row(cbm_r1, col + 2);
715 c_r0 = bit as u32;
716 m_r1 = ((m_r1 << 1) & 0b111) | pix_row(mbm_r1, col + col_shift + 2);
717 m_r0 = ((m_r0 << 1) & 0b111) | pix_row(mbm_r0, col + col_shift + 2);
718 }
719
720 zp.a = a;
721 zp.c = c;
722 zp.fence = fence;
723 zp.bit_buf = bit_buf;
724 zp.bit_count = bit_count;
725 zp.pos = pos;
726}
727
728#[inline]
731fn pack_row_into(src: &[u8], width: usize, dst: &mut [u8]) {
732 let full_bytes = width / 8;
733 let rem = width % 8;
734 for i in 0..full_bytes {
735 let s: &[u8; 8] = src[i * 8..(i + 1) * 8].try_into().unwrap();
736 dst[i] = pack_byte(s);
737 }
738 if rem > 0 {
739 let base = full_bytes * 8;
740 let mut byte_val = 0u8;
741 for j in 0..rem {
742 if src[base + j] != 0 {
743 byte_val |= 0x80u8 >> j;
744 }
745 }
746 dst[full_bytes] = byte_val;
747 }
748}
749
750#[inline]
753fn unpack_row_into(src: &[u8], width: usize, dst: &mut [u8]) {
754 let full_bytes = width / 8;
755 let rem = width % 8;
756 for i in 0..full_bytes {
757 let b = src[i];
758 let out = &mut dst[i * 8..(i + 1) * 8];
759 out[0] = (b >> 7) & 1;
760 out[1] = (b >> 6) & 1;
761 out[2] = (b >> 5) & 1;
762 out[3] = (b >> 4) & 1;
763 out[4] = (b >> 3) & 1;
764 out[5] = (b >> 2) & 1;
765 out[6] = (b >> 1) & 1;
766 out[7] = b & 1;
767 }
768 if rem > 0 {
769 let b = src[full_bytes];
770 let base = full_bytes * 8;
771 for j in 0..rem {
772 dst[base + j] = (b >> (7 - j)) & 1;
773 }
774 }
775}
776
777fn decode_bitmap_direct(
778 zp: &mut ZpDecoder<'_>,
779 ctx: &mut [u8; 1024],
780 width: i32,
781 height: i32,
782 pool: &mut Vec<u8>,
783) -> Result<Jbm, Jb2Error> {
784 let pixels = (width.max(0) as usize).saturating_mul(height.max(0) as usize);
785 if pixels > MAX_SYMBOL_PIXELS {
786 return Err(Jb2Error::ImageTooLarge);
787 }
788 if width <= 0 || height <= 0 {
789 return Ok(Jbm::new_from_pool(width, height, pool));
790 }
791 let mut bm = Jbm::new_from_pool(width, height, pool);
792 let w = width as usize;
793 let h = height as usize;
794 let stride = bm.stride();
795 debug_assert_eq!(bm.data.len(), stride * h);
796
797 let mut s_curr = vec![0u8; w];
800 let mut s_prev1 = vec![0u8; w];
801 let mut s_prev2 = vec![0u8; w];
802
803 for row in (0..h).rev() {
804 s_curr.iter_mut().for_each(|b| *b = 0);
805 decode_direct_row(zp, ctx, &mut s_curr, &s_prev1, &s_prev2);
806 pack_row_into(&s_curr, w, &mut bm.data[row * stride..(row + 1) * stride]);
807 core::mem::swap(&mut s_prev2, &mut s_prev1);
809 core::mem::swap(&mut s_prev1, &mut s_curr);
810 }
811 Ok(bm)
812}
813
814fn decode_bitmap_ref(
823 zp: &mut ZpDecoder<'_>,
824 ctx: &mut [u8; 2048],
825 ctx_p: &mut [u16; 2048],
826 width: i32,
827 height: i32,
828 mbm: &Jbm,
829 pool: &mut Vec<u8>,
830) -> Result<Jbm, Jb2Error> {
831 let pixels = (width.max(0) as usize).saturating_mul(height.max(0) as usize);
832 if pixels > MAX_SYMBOL_PIXELS {
833 return Err(Jb2Error::ImageTooLarge);
834 }
835 if width <= 0 || height <= 0 {
836 return Ok(Jbm::new_from_pool(width, height, pool));
837 }
838 let mut cbm = Jbm::new_from_pool(width, height, pool);
839
840 let crow = (height - 1) >> 1;
842 let ccol = (width - 1) >> 1;
843 let mrow = (mbm.height - 1) >> 1;
844 let mcol = (mbm.width - 1) >> 1;
845 let row_shift = mrow - crow;
846 let col_shift = mcol - ccol;
847
848 let pix_row = |row_slice: &[u8], col: i32| -> u32 {
850 if col < 0 {
851 return 0;
852 }
853 row_slice.get(col as usize).copied().unwrap_or(0) as u32
854 };
855
856 let cw = width as usize;
857 let cstride = cbm.stride();
858 let mw = mbm.width.max(0) as usize;
859 let mstride = mbm.stride();
860
861 let mut s_mbm_r2 = vec![0u8; mw];
865 let mut s_mbm_r1 = vec![0u8; mw];
866 let mut s_mbm_r0 = vec![0u8; mw];
867 let mut have_r2;
868 let mut have_r1;
869 let mut have_r0;
870
871 let mut s_cbm_curr = vec![0u8; cw];
873 let mut s_cbm_prev1 = vec![0u8; cw];
874
875 let unpack_mbm_row = |r: i32, buf: &mut [u8]| -> bool {
876 if r < 0 || r >= mbm.height || mw == 0 {
877 return false;
878 }
879 let off = r as usize * mstride;
880 unpack_row_into(&mbm.data[off..off + mstride], mw, buf);
881 true
882 };
883
884 let first_mr = (height - 1) + row_shift;
887 have_r2 = unpack_mbm_row(first_mr + 1, &mut s_mbm_r2);
888 have_r1 = unpack_mbm_row(first_mr, &mut s_mbm_r1);
889 have_r0 = unpack_mbm_row(first_mr - 1, &mut s_mbm_r0);
890
891 for row in (0..height).rev() {
892 let mr = row + row_shift;
893
894 let mbm_r2: &[u8] = if have_r2 { &s_mbm_r2 } else { &[] };
896 let mbm_r1: &[u8] = if have_r1 { &s_mbm_r1 } else { &[] };
897 let mbm_r0: &[u8] = if have_r0 { &s_mbm_r0 } else { &[] };
898
899 let cbm_r1: &[u8] = if row + 1 < height { &s_cbm_prev1 } else { &[] };
900 s_cbm_curr.iter_mut().for_each(|b| *b = 0);
901
902 let init_c_r1 = pix_row(cbm_r1, 0) << 1 | pix_row(cbm_r1, 1);
903 let init_m_r1 = pix_row(mbm_r1, col_shift - 1) << 2
904 | pix_row(mbm_r1, col_shift) << 1
905 | pix_row(mbm_r1, col_shift + 1);
906 let init_m_r0 = pix_row(mbm_r0, col_shift - 1) << 2
907 | pix_row(mbm_r0, col_shift) << 1
908 | pix_row(mbm_r0, col_shift + 1);
909
910 decode_ref_row(
911 zp,
912 ctx,
913 ctx_p,
914 &mut s_cbm_curr,
915 cbm_r1,
916 mbm_r2,
917 mbm_r1,
918 mbm_r0,
919 col_shift,
920 init_c_r1,
921 init_m_r1,
922 init_m_r0,
923 );
924
925 pack_row_into(
927 &s_cbm_curr,
928 cw,
929 &mut cbm.data[row as usize * cstride..(row as usize + 1) * cstride],
930 );
931
932 core::mem::swap(&mut s_cbm_prev1, &mut s_cbm_curr);
934
935 core::mem::swap(&mut s_mbm_r2, &mut s_mbm_r1);
941 have_r2 = have_r1;
942 core::mem::swap(&mut s_mbm_r1, &mut s_mbm_r0);
943 have_r1 = have_r0;
944 have_r0 = unpack_mbm_row(mr - 2, &mut s_mbm_r0);
945 }
946 Ok(cbm)
947}
948
949struct Baseline {
954 arr: [i32; 3],
955 index: i32,
956}
957
958impl Baseline {
959 fn new() -> Self {
960 Baseline {
961 arr: [0, 0, 0],
962 index: -1,
963 }
964 }
965
966 fn fill(&mut self, val: i32) {
967 self.arr = [val, val, val];
968 }
969
970 fn add(&mut self, val: i32) {
971 self.index += 1;
972 if self.index == 3 {
973 self.index = 0;
974 }
975 self.arr[self.index as usize] = val;
976 }
977
978 fn get_val(&self) -> i32 {
979 let (a, b, c) = (self.arr[0], self.arr[1], self.arr[2]);
980 if (a >= b && a <= c) || (a <= b && a >= c) {
981 a
982 } else if (b >= a && b <= c) || (b <= a && b >= c) {
983 b
984 } else {
985 c
986 }
987 }
988}
989
990#[allow(clippy::too_many_arguments)]
995fn blit_indexed(
996 page: &mut [u8],
997 blit_map: &mut [i32],
998 page_w: i32,
999 page_h: i32,
1000 symbol: &Jbm,
1001 x: i32,
1002 y: i32,
1003 blit_idx: i32,
1004) {
1005 if symbol.width <= 0 || symbol.height <= 0 {
1008 return;
1009 }
1010 if x >= 0 && y >= 0 && x + symbol.width <= page_w && y + symbol.height <= page_h {
1011 let pw = page_w as usize;
1012 let sw = symbol.width as usize;
1013 let sym_stride = symbol.stride();
1014 let full_bytes = sw / 8;
1015 let rem = sw & 7;
1016 for row in 0..symbol.height as usize {
1017 let src_row_off = row * sym_stride;
1018 let dst_off = (y as usize + row) * pw + x as usize;
1019 for byte_i in 0..full_bytes {
1020 let b = symbol.data[src_row_off + byte_i];
1021 if b == 0 {
1022 continue;
1023 }
1024 let base_col = byte_i * 8;
1025 for j in 0..8 {
1026 if (b >> (7 - j)) & 1 != 0 {
1027 page[dst_off + base_col + j] = 1;
1028 blit_map[dst_off + base_col + j] = blit_idx;
1029 }
1030 }
1031 }
1032 if rem > 0 {
1033 let b = symbol.data[src_row_off + full_bytes];
1034 if b != 0 {
1035 let base_col = full_bytes * 8;
1036 for j in 0..rem {
1037 if (b >> (7 - j)) & 1 != 0 {
1038 page[dst_off + base_col + j] = 1;
1039 blit_map[dst_off + base_col + j] = blit_idx;
1040 }
1041 }
1042 }
1043 }
1044 }
1045 } else {
1046 for row in 0..symbol.height {
1047 let py = y + row;
1048 if py < 0 || py >= page_h {
1049 continue;
1050 }
1051 for col in 0..symbol.width {
1052 if symbol.get(row, col) != 0 {
1053 let px = x + col;
1054 if px >= 0 && px < page_w {
1055 let idx = (py * page_w + px) as usize;
1056 page[idx] = 1;
1057 blit_map[idx] = blit_idx;
1058 }
1059 }
1060 }
1061 }
1062 }
1063}
1064
1065fn blit_to_bitmap(bm: &mut Bitmap, sym: &Jbm, x: i32, y: i32) {
1076 if sym.width <= 0 || sym.height <= 0 {
1077 return;
1078 }
1079 let bw = bm.width as i32;
1080 let bh = bm.height as i32;
1081 let bm_stride = bm.row_stride();
1082 let sw = sym.width;
1083 let sh = sym.height;
1084 let sym_stride = sym.stride();
1085
1086 if x >= 0
1088 && y >= 0
1089 && x.checked_add(sw).is_some_and(|v| v <= bw)
1090 && y.checked_add(sh).is_some_and(|v| v <= bh)
1091 {
1092 let x_off = x as usize;
1093 let byte_off = x_off / 8;
1094 let bit_off = x_off & 7;
1095 let sw_u = sw as usize;
1096 let full = sw_u / 8;
1097 let rem = sw_u & 7;
1098 let bm_y_base = (bm.height as usize) - 1 - y as usize;
1099
1100 if bit_off == 0 {
1101 for sym_row in 0..sh as usize {
1102 let bm_y = bm_y_base - sym_row;
1103 let src = &sym.data[sym_row * sym_stride..sym_row * sym_stride + sym_stride];
1104 let dst = &mut bm.data[bm_y * bm_stride..];
1105 for i in 0..full {
1106 dst[byte_off + i] |= src[i];
1107 }
1108 if rem > 0 {
1109 dst[byte_off + full] |= src[full];
1113 }
1114 }
1115 } else {
1116 let rshift = bit_off as u32;
1117 let lshift = 8 - bit_off as u32;
1118 for sym_row in 0..sh as usize {
1119 let bm_y = bm_y_base - sym_row;
1120 let src = &sym.data[sym_row * sym_stride..sym_row * sym_stride + sym_stride];
1121 let row_off = bm_y * bm_stride;
1122 for (i, &s) in src.iter().enumerate().take(full) {
1123 bm.data[row_off + byte_off + i] |= s >> rshift;
1124 bm.data[row_off + byte_off + i + 1] |= s << lshift;
1125 }
1126 if rem > 0 {
1127 let s = src[full];
1128 bm.data[row_off + byte_off + full] |= s >> rshift;
1129 let overflow = row_off + byte_off + full + 1;
1130 if overflow < bm.data.len() {
1131 bm.data[overflow] |= s << lshift;
1132 }
1133 }
1134 }
1135 }
1136 } else {
1137 for sym_row in 0..sh {
1139 let bm_y = bh - 1 - y - sym_row;
1140 if bm_y < 0 || bm_y >= bh {
1141 continue;
1142 }
1143 let bm_y = bm_y as usize;
1144 let row_off = bm_y * bm_stride;
1145 let src_row_off = sym_row as usize * sym_stride;
1146 for col in 0..sw {
1147 let b = sym.data[src_row_off + (col as usize / 8)];
1148 if (b >> (7 - (col as usize & 7))) & 1 != 0 {
1149 let px = x + col;
1150 if px >= 0 && px < bw {
1151 let px = px as usize;
1152 bm.data[row_off + px / 8] |= 0x80u8 >> (px & 7);
1153 }
1154 }
1155 }
1156 }
1157 }
1158}
1159
1160#[inline(always)]
1167fn pack_byte(s: &[u8; 8]) -> u8 {
1168 ((s[0] != 0) as u8) << 7
1169 | ((s[1] != 0) as u8) << 6
1170 | ((s[2] != 0) as u8) << 5
1171 | ((s[3] != 0) as u8) << 4
1172 | ((s[4] != 0) as u8) << 3
1173 | ((s[5] != 0) as u8) << 2
1174 | ((s[6] != 0) as u8) << 1
1175 | ((s[7] != 0) as u8)
1176}
1177
1178fn page_to_bitmap(page: &[u8], width: i32, height: i32) -> Bitmap {
1179 let w = width as usize;
1180 let h = height as usize;
1181 let mut bm = Bitmap::new(width as u32, height as u32);
1182 let stride = bm.row_stride();
1183 let full_bytes = w / 8;
1184 let remaining = w % 8;
1185
1186 for row in 0..h {
1187 let src_row = &page[row * w..(row + 1) * w];
1188 let dst_y = h - 1 - row; let dst_off = dst_y * stride;
1190
1191 for byte_idx in 0..full_bytes {
1195 let s: &[u8; 8] = src_row[byte_idx * 8..(byte_idx + 1) * 8]
1196 .try_into()
1197 .unwrap();
1198 bm.data[dst_off + byte_idx] = pack_byte(s);
1199 }
1200
1201 if remaining > 0 {
1203 let base = full_bytes * 8;
1204 let mut byte_val = 0u8;
1205 for bit_pos in 0..remaining {
1206 if src_row[base + bit_pos] != 0 {
1207 byte_val |= 0x80u8 >> bit_pos;
1208 }
1209 }
1210 bm.data[dst_off + full_bytes] = byte_val;
1211 }
1212 }
1213 bm
1214}
1215
1216fn flip_blit_map(blit_map: &mut [i32], width: usize, height: usize) {
1218 for row in 0..height / 2 {
1219 let mirror = height - 1 - row;
1220 let a = row * width;
1221 let b = mirror * width;
1222 for col in 0..width {
1223 blit_map.swap(a + col, b + col);
1224 }
1225 }
1226}
1227
1228struct CoordContexts {
1234 offset_type: u8,
1235 hoff: NumContext,
1236 voff: NumContext,
1237 shoff: NumContext,
1238 svoff: NumContext,
1239}
1240
1241impl CoordContexts {
1242 fn new() -> Self {
1243 Self {
1244 offset_type: 0,
1245 hoff: NumContext::new(),
1246 voff: NumContext::new(),
1247 shoff: NumContext::new(),
1248 svoff: NumContext::new(),
1249 }
1250 }
1251}
1252
1253struct LayoutState {
1255 first_left: i32,
1256 first_bottom: i32,
1257 last_right: i32,
1258 baseline: Baseline,
1259}
1260
1261impl LayoutState {
1262 fn new(image_height: i32) -> Self {
1263 Self {
1264 first_left: -1,
1265 first_bottom: image_height - 1,
1266 last_right: 0,
1267 baseline: Baseline::new(),
1268 }
1269 }
1270}
1271
1272fn decode_symbol_coords(
1273 zp: &mut ZpDecoder<'_>,
1274 coord_ctx: &mut CoordContexts,
1275 layout: &mut LayoutState,
1276 sym_width: i32,
1277 sym_height: i32,
1278) -> (i32, i32) {
1279 let new_line = zp.decode_bit(&mut coord_ctx.offset_type);
1280
1281 let (x, y) = if new_line {
1282 let hoff = decode_num(zp, &mut coord_ctx.hoff, -262143, 262142);
1283 let voff = decode_num(zp, &mut coord_ctx.voff, -262143, 262142);
1284 let nx = layout.first_left + hoff;
1285 let ny = layout.first_bottom + voff - sym_height + 1;
1286 layout.first_left = nx;
1287 layout.first_bottom = ny;
1288 layout.baseline.fill(ny);
1289 (nx, ny)
1290 } else {
1291 let hoff = decode_num(zp, &mut coord_ctx.shoff, -262143, 262142);
1292 let voff = decode_num(zp, &mut coord_ctx.svoff, -262143, 262142);
1293 (layout.last_right + hoff, layout.baseline.get_val() + voff)
1294 };
1295
1296 layout.baseline.add(y);
1297 layout.last_right = x + sym_width - 1;
1298 (x, y)
1299}
1300
1301struct JbmDict<'a> {
1312 shared: &'a [Jbm],
1313 local: Vec<Jbm>,
1314}
1315
1316impl<'a> JbmDict<'a> {
1317 fn new(shared: &'a [Jbm]) -> Self {
1318 JbmDict {
1319 shared,
1320 local: Vec::new(),
1321 }
1322 }
1323 fn len(&self) -> usize {
1324 self.shared.len() + self.local.len()
1325 }
1326 fn is_empty(&self) -> bool {
1327 self.shared.is_empty() && self.local.is_empty()
1328 }
1329 fn push(&mut self, sym: Jbm) {
1330 self.local.push(sym);
1331 }
1332 fn into_symbols(self) -> Vec<Jbm> {
1333 let mut out = self.shared.to_vec();
1335 out.extend(self.local);
1336 out
1337 }
1338}
1339
1340impl core::ops::Index<usize> for JbmDict<'_> {
1341 type Output = Jbm;
1342 #[inline(always)]
1343 fn index(&self, index: usize) -> &Jbm {
1344 let n = self.shared.len();
1345 if index < n {
1346 &self.shared[index]
1347 } else {
1348 &self.local[index - n]
1349 }
1350 }
1351}
1352
1353pub struct Jb2Dict {
1362 symbols: Vec<Jbm>,
1363}
1364
1365pub fn decode(data: &[u8], shared_dict: Option<&Jb2Dict>) -> Result<Bitmap, Jb2Error> {
1374 decode_image(data, shared_dict)
1375}
1376
1377pub fn decode_indexed(
1384 data: &[u8],
1385 shared_dict: Option<&Jb2Dict>,
1386) -> Result<(Bitmap, Vec<i32>), Jb2Error> {
1387 decode_image_indexed(data, shared_dict)
1388}
1389
1390pub fn decode_dict(data: &[u8], inherited: Option<&Jb2Dict>) -> Result<Jb2Dict, Jb2Error> {
1399 decode_dictionary(data, inherited)
1400}
1401
1402fn decode_image(data: &[u8], shared_dict: Option<&Jb2Dict>) -> Result<Bitmap, Jb2Error> {
1407 let mut pool = Vec::new();
1408 decode_image_with_pool(data, shared_dict, &mut pool)
1409}
1410
1411fn decode_image_with_pool(
1417 data: &[u8],
1418 shared_dict: Option<&Jb2Dict>,
1419 pool: &mut Vec<u8>,
1420) -> Result<Bitmap, Jb2Error> {
1421 let mut zp = ZpDecoder::new(data).map_err(|_| Jb2Error::ZpInitFailed)?;
1422
1423 let mut record_type_ctx = NumContext::new();
1425 let mut image_size_ctx = NumContext::new();
1426 let mut symbol_width_ctx = NumContext::new();
1427 let mut symbol_height_ctx = NumContext::new();
1428 let mut inherit_dict_size_ctx = NumContext::new();
1429 let mut coord_ctx = CoordContexts::new();
1430 let mut symbol_index_ctx = NumContext::new();
1431 let mut symbol_width_diff_ctx = NumContext::new();
1432 let mut symbol_height_diff_ctx = NumContext::new();
1433 let mut horiz_abs_loc_ctx = NumContext::new();
1434 let mut vert_abs_loc_ctx = NumContext::new();
1435 let mut comment_length_ctx = NumContext::new();
1436 let mut comment_octet_ctx = NumContext::new();
1437
1438 let mut direct_bitmap_ctx = [0u8; 1024];
1439 let mut refinement_bitmap_ctx = [0u8; 2048];
1440 let mut refinement_bitmap_ctx_p = [0x8000u16; 2048];
1441 let mut total_sym_pixels = 0usize;
1442 let mut total_blit_pixels = 0usize;
1443
1444 let mut rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1447 let mut initial_dict_length: usize = 0;
1448 if rtype == 9 {
1449 initial_dict_length = decode_num(&mut zp, &mut inherit_dict_size_ctx, 0, 262142) as usize;
1450 rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1451 }
1452 let _ = rtype;
1454
1455 let image_width = {
1457 let w = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
1458 if w == 0 { 200 } else { w }
1459 };
1460 let image_height = {
1461 let h = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
1462 if h == 0 { 200 } else { h }
1463 };
1464
1465 let mut flag_ctx: u8 = 0;
1467 if zp.decode_bit(&mut flag_ctx) {
1468 return Err(Jb2Error::BadHeaderFlag);
1469 }
1470
1471 let initial_symbols: &[Jbm] = if initial_dict_length > 0 {
1474 match shared_dict {
1475 Some(sd) => {
1476 if initial_dict_length > sd.symbols.len() {
1477 return Err(Jb2Error::InheritedDictTooLarge);
1478 }
1479 &sd.symbols[..initial_dict_length]
1480 }
1481 None => return Err(Jb2Error::MissingSharedDict),
1482 }
1483 } else {
1484 &[]
1485 };
1486 let mut dict = JbmDict::new(initial_symbols);
1487
1488 const MAX_PIXELS: usize = 64 * 1024 * 1024;
1490 let page_size = (image_width as usize).saturating_mul(image_height as usize);
1491 if page_size > MAX_PIXELS {
1492 return Err(Jb2Error::ImageTooLarge);
1493 }
1494 let mut page_bm = Bitmap::new(image_width as u32, image_height as u32);
1498
1499 let mut layout = LayoutState::new(image_height);
1500
1501 let mut record_count = 0usize;
1503 loop {
1504 if record_count >= MAX_RECORDS {
1505 return Err(Jb2Error::TooManyRecords);
1506 }
1507 record_count += 1;
1508 let rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1509
1510 match rtype {
1511 1 => {
1513 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1514 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1515 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels)?;
1516 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1517 let (x, y) =
1518 decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
1519 check_blit_budget(&bm, &mut total_blit_pixels)?;
1520 blit_to_bitmap(&mut page_bm, &bm, x, y);
1521 dict.push(bm.crop_and_recycle(pool));
1522 }
1523
1524 2 => {
1526 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1527 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1528 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels)?;
1529 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1530 dict.push(bm.crop_and_recycle(pool));
1531 }
1532
1533 3 => {
1535 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1536 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1537 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels)?;
1538 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1539 let (x, y) =
1540 decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
1541 check_blit_budget(&bm, &mut total_blit_pixels)?;
1542 blit_to_bitmap(&mut page_bm, &bm, x, y);
1543 bm.recycle_into(pool);
1544 }
1545
1546 4 => {
1548 if dict.is_empty() {
1549 return Err(Jb2Error::EmptyDictReference);
1550 }
1551 let index =
1552 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1553 if index >= dict.len() {
1554 return Err(Jb2Error::InvalidSymbolIndex);
1555 }
1556 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
1557 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
1558 let cbm_w = dict[index].width + wdiff;
1559 let cbm_h = dict[index].height + hdiff;
1560 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels)?;
1561 let cbm = decode_bitmap_ref(
1562 &mut zp,
1563 &mut refinement_bitmap_ctx,
1564 &mut refinement_bitmap_ctx_p,
1565 cbm_w,
1566 cbm_h,
1567 &dict[index],
1568 pool,
1569 )?;
1570 let (x, y) = decode_symbol_coords(
1571 &mut zp,
1572 &mut coord_ctx,
1573 &mut layout,
1574 cbm.width,
1575 cbm.height,
1576 );
1577 check_blit_budget(&cbm, &mut total_blit_pixels)?;
1578 blit_to_bitmap(&mut page_bm, &cbm, x, y);
1579 dict.push(cbm.crop_and_recycle(pool));
1580 }
1581
1582 5 => {
1584 if dict.is_empty() {
1585 return Err(Jb2Error::EmptyDictReference);
1586 }
1587 let index =
1588 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1589 if index >= dict.len() {
1590 return Err(Jb2Error::InvalidSymbolIndex);
1591 }
1592 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
1593 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
1594 let cbm_w = dict[index].width + wdiff;
1595 let cbm_h = dict[index].height + hdiff;
1596 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels)?;
1597 let cbm = decode_bitmap_ref(
1598 &mut zp,
1599 &mut refinement_bitmap_ctx,
1600 &mut refinement_bitmap_ctx_p,
1601 cbm_w,
1602 cbm_h,
1603 &dict[index],
1604 pool,
1605 )?;
1606 dict.push(cbm.crop_and_recycle(pool));
1607 }
1608
1609 6 => {
1611 if dict.is_empty() {
1612 return Err(Jb2Error::EmptyDictReference);
1613 }
1614 let index =
1615 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1616 if index >= dict.len() {
1617 return Err(Jb2Error::InvalidSymbolIndex);
1618 }
1619 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
1620 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
1621 let cbm_w = dict[index].width + wdiff;
1622 let cbm_h = dict[index].height + hdiff;
1623 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels)?;
1624 let cbm = decode_bitmap_ref(
1625 &mut zp,
1626 &mut refinement_bitmap_ctx,
1627 &mut refinement_bitmap_ctx_p,
1628 cbm_w,
1629 cbm_h,
1630 &dict[index],
1631 pool,
1632 )?;
1633 let (x, y) = decode_symbol_coords(
1634 &mut zp,
1635 &mut coord_ctx,
1636 &mut layout,
1637 cbm.width,
1638 cbm.height,
1639 );
1640 check_blit_budget(&cbm, &mut total_blit_pixels)?;
1641 blit_to_bitmap(&mut page_bm, &cbm, x, y);
1642 cbm.recycle_into(pool);
1643 }
1644
1645 7 => {
1647 if dict.is_empty() {
1648 return Err(Jb2Error::EmptyDictReference);
1649 }
1650 let index =
1651 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1652 if index >= dict.len() {
1653 return Err(Jb2Error::InvalidSymbolIndex);
1654 }
1655 let bm_w = dict[index].width;
1656 let bm_h = dict[index].height;
1657 let (x, y) = decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm_w, bm_h);
1658 let sym = &dict[index];
1659 check_blit_budget(sym, &mut total_blit_pixels)?;
1660 blit_to_bitmap(&mut page_bm, sym, x, y);
1661 }
1662
1663 8 => {
1665 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1666 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1667 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels)?;
1668 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1669 let left = decode_num(&mut zp, &mut horiz_abs_loc_ctx, 1, image_width);
1670 let top = decode_num(&mut zp, &mut vert_abs_loc_ctx, 1, image_height);
1671 let x = left - 1;
1672 let y = top - h;
1673 check_blit_budget(&bm, &mut total_blit_pixels)?;
1674 blit_to_bitmap(&mut page_bm, &bm, x, y);
1675 bm.recycle_into(pool);
1676 }
1677
1678 9 => {}
1680
1681 10 => {
1683 let length = decode_num(&mut zp, &mut comment_length_ctx, 0, 262142) as usize;
1684 let length = length.min(MAX_COMMENT_BYTES);
1685 for _ in 0..length {
1686 decode_num(&mut zp, &mut comment_octet_ctx, 0, 255);
1687 }
1688 }
1689
1690 11 => break,
1692
1693 _ => return Err(Jb2Error::UnknownRecordType),
1694 }
1695 }
1696
1697 Ok(page_bm)
1698}
1699
1700fn decode_image_indexed(
1702 data: &[u8],
1703 shared_dict: Option<&Jb2Dict>,
1704) -> Result<(Bitmap, Vec<i32>), Jb2Error> {
1705 let mut pool = Vec::new();
1706 decode_image_indexed_with_pool(data, shared_dict, &mut pool)
1707}
1708
1709fn decode_image_indexed_with_pool(
1710 data: &[u8],
1711 shared_dict: Option<&Jb2Dict>,
1712 pool: &mut Vec<u8>,
1713) -> Result<(Bitmap, Vec<i32>), Jb2Error> {
1714 let mut zp = ZpDecoder::new(data).map_err(|_| Jb2Error::ZpInitFailed)?;
1715
1716 let mut record_type_ctx = NumContext::new();
1717 let mut image_size_ctx = NumContext::new();
1718 let mut symbol_width_ctx = NumContext::new();
1719 let mut symbol_height_ctx = NumContext::new();
1720 let mut inherit_dict_size_ctx = NumContext::new();
1721 let mut coord_ctx = CoordContexts::new();
1722 let mut symbol_index_ctx = NumContext::new();
1723 let mut symbol_width_diff_ctx = NumContext::new();
1724 let mut symbol_height_diff_ctx = NumContext::new();
1725 let mut horiz_abs_loc_ctx = NumContext::new();
1726 let mut vert_abs_loc_ctx = NumContext::new();
1727 let mut comment_length_ctx = NumContext::new();
1728 let mut comment_octet_ctx = NumContext::new();
1729
1730 let mut direct_bitmap_ctx = [0u8; 1024];
1731 let mut refinement_bitmap_ctx = [0u8; 2048];
1732 let mut refinement_bitmap_ctx_p = [0x8000u16; 2048];
1733 let mut total_sym_pixels = 0usize;
1734 let mut total_blit_pixels = 0usize;
1735
1736 let mut rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1737 let mut initial_dict_length: usize = 0;
1738 if rtype == 9 {
1739 initial_dict_length = decode_num(&mut zp, &mut inherit_dict_size_ctx, 0, 262142) as usize;
1740 rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1741 }
1742 let _ = rtype;
1743
1744 let image_width = {
1745 let w = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
1746 if w == 0 { 200 } else { w }
1747 };
1748 let image_height = {
1749 let h = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
1750 if h == 0 { 200 } else { h }
1751 };
1752
1753 let mut flag_ctx: u8 = 0;
1754 if zp.decode_bit(&mut flag_ctx) {
1755 return Err(Jb2Error::BadHeaderFlag);
1756 }
1757
1758 let initial_symbols_idx: &[Jbm] = if initial_dict_length > 0 {
1759 match shared_dict {
1760 Some(sd) => {
1761 if initial_dict_length > sd.symbols.len() {
1762 return Err(Jb2Error::InheritedDictTooLarge);
1763 }
1764 &sd.symbols[..initial_dict_length]
1765 }
1766 None => return Err(Jb2Error::MissingSharedDict),
1767 }
1768 } else {
1769 &[]
1770 };
1771 let mut dict = JbmDict::new(initial_symbols_idx);
1772
1773 const MAX_PIXELS: usize = 64 * 1024 * 1024;
1774 let page_size = (image_width as usize).saturating_mul(image_height as usize);
1775 if page_size > MAX_PIXELS {
1776 return Err(Jb2Error::ImageTooLarge);
1777 }
1778 let mut page = vec![0u8; page_size];
1779 let mut blit_map = vec![-1i32; page_size];
1780
1781 let mut layout = LayoutState::new(image_height);
1782 let mut blit_count: i32 = 0;
1783
1784 let mut record_count = 0usize;
1785 loop {
1786 if record_count >= MAX_RECORDS {
1787 return Err(Jb2Error::TooManyRecords);
1788 }
1789 record_count += 1;
1790 let rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1791
1792 match rtype {
1793 1 => {
1794 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1795 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1796 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels)?;
1797 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1798 let (x, y) =
1799 decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
1800 check_blit_budget(&bm, &mut total_blit_pixels)?;
1801 blit_indexed(
1802 &mut page,
1803 &mut blit_map,
1804 image_width,
1805 image_height,
1806 &bm,
1807 x,
1808 y,
1809 blit_count,
1810 );
1811 blit_count += 1;
1812 dict.push(bm.crop_and_recycle(pool));
1813 }
1814 2 => {
1815 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1816 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1817 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels)?;
1818 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1819 dict.push(bm.crop_and_recycle(pool));
1820 }
1821 3 => {
1822 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1823 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1824 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels)?;
1825 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1826 let (x, y) =
1827 decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
1828 check_blit_budget(&bm, &mut total_blit_pixels)?;
1829 blit_indexed(
1830 &mut page,
1831 &mut blit_map,
1832 image_width,
1833 image_height,
1834 &bm,
1835 x,
1836 y,
1837 blit_count,
1838 );
1839 blit_count += 1;
1840 bm.recycle_into(pool);
1841 }
1842 4 => {
1843 if dict.is_empty() {
1844 return Err(Jb2Error::EmptyDictReference);
1845 }
1846 let index =
1847 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1848 if index >= dict.len() {
1849 return Err(Jb2Error::InvalidSymbolIndex);
1850 }
1851 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
1852 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
1853 let cbm_w = dict[index].width + wdiff;
1854 let cbm_h = dict[index].height + hdiff;
1855 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels)?;
1856 let cbm = decode_bitmap_ref(
1857 &mut zp,
1858 &mut refinement_bitmap_ctx,
1859 &mut refinement_bitmap_ctx_p,
1860 cbm_w,
1861 cbm_h,
1862 &dict[index],
1863 pool,
1864 )?;
1865 let (x, y) = decode_symbol_coords(
1866 &mut zp,
1867 &mut coord_ctx,
1868 &mut layout,
1869 cbm.width,
1870 cbm.height,
1871 );
1872 check_blit_budget(&cbm, &mut total_blit_pixels)?;
1873 blit_indexed(
1874 &mut page,
1875 &mut blit_map,
1876 image_width,
1877 image_height,
1878 &cbm,
1879 x,
1880 y,
1881 blit_count,
1882 );
1883 blit_count += 1;
1884 dict.push(cbm.crop_and_recycle(pool));
1885 }
1886 5 => {
1887 if dict.is_empty() {
1888 return Err(Jb2Error::EmptyDictReference);
1889 }
1890 let index =
1891 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1892 if index >= dict.len() {
1893 return Err(Jb2Error::InvalidSymbolIndex);
1894 }
1895 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
1896 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
1897 let cbm_w = dict[index].width + wdiff;
1898 let cbm_h = dict[index].height + hdiff;
1899 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels)?;
1900 let cbm = decode_bitmap_ref(
1901 &mut zp,
1902 &mut refinement_bitmap_ctx,
1903 &mut refinement_bitmap_ctx_p,
1904 cbm_w,
1905 cbm_h,
1906 &dict[index],
1907 pool,
1908 )?;
1909 dict.push(cbm.crop_and_recycle(pool));
1910 }
1911 6 => {
1912 if dict.is_empty() {
1913 return Err(Jb2Error::EmptyDictReference);
1914 }
1915 let index =
1916 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1917 if index >= dict.len() {
1918 return Err(Jb2Error::InvalidSymbolIndex);
1919 }
1920 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
1921 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
1922 let cbm_w = dict[index].width + wdiff;
1923 let cbm_h = dict[index].height + hdiff;
1924 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels)?;
1925 let cbm = decode_bitmap_ref(
1926 &mut zp,
1927 &mut refinement_bitmap_ctx,
1928 &mut refinement_bitmap_ctx_p,
1929 cbm_w,
1930 cbm_h,
1931 &dict[index],
1932 pool,
1933 )?;
1934 let (x, y) = decode_symbol_coords(
1935 &mut zp,
1936 &mut coord_ctx,
1937 &mut layout,
1938 cbm.width,
1939 cbm.height,
1940 );
1941 check_blit_budget(&cbm, &mut total_blit_pixels)?;
1942 blit_indexed(
1943 &mut page,
1944 &mut blit_map,
1945 image_width,
1946 image_height,
1947 &cbm,
1948 x,
1949 y,
1950 blit_count,
1951 );
1952 blit_count += 1;
1953 cbm.recycle_into(pool);
1954 }
1955 7 => {
1956 if dict.is_empty() {
1957 return Err(Jb2Error::EmptyDictReference);
1958 }
1959 let index =
1960 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1961 if index >= dict.len() {
1962 return Err(Jb2Error::InvalidSymbolIndex);
1963 }
1964 let (x, y) = decode_symbol_coords(
1965 &mut zp,
1966 &mut coord_ctx,
1967 &mut layout,
1968 dict[index].width,
1969 dict[index].height,
1970 );
1971 check_blit_budget(&dict[index], &mut total_blit_pixels)?;
1972 blit_indexed(
1973 &mut page,
1974 &mut blit_map,
1975 image_width,
1976 image_height,
1977 &dict[index],
1978 x,
1979 y,
1980 blit_count,
1981 );
1982 blit_count += 1;
1983 }
1984 8 => {
1985 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1986 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1987 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels)?;
1988 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1989 let left = decode_num(&mut zp, &mut horiz_abs_loc_ctx, 1, image_width);
1990 let top = decode_num(&mut zp, &mut vert_abs_loc_ctx, 1, image_height);
1991 check_blit_budget(&bm, &mut total_blit_pixels)?;
1992 blit_indexed(
1993 &mut page,
1994 &mut blit_map,
1995 image_width,
1996 image_height,
1997 &bm,
1998 left - 1,
1999 top - h,
2000 blit_count,
2001 );
2002 blit_count += 1;
2003 bm.recycle_into(pool);
2004 }
2005 9 => {}
2006 10 => {
2007 let length = decode_num(&mut zp, &mut comment_length_ctx, 0, 262142) as usize;
2008 let length = length.min(MAX_COMMENT_BYTES);
2009 for _ in 0..length {
2010 decode_num(&mut zp, &mut comment_octet_ctx, 0, 255);
2011 }
2012 }
2013 11 => break,
2014 _ => return Err(Jb2Error::UnknownRecordType),
2015 }
2016 }
2017
2018 let bm = page_to_bitmap(&page, image_width, image_height);
2019 flip_blit_map(&mut blit_map, image_width as usize, image_height as usize);
2020 Ok((bm, blit_map))
2021}
2022
2023fn decode_dictionary(data: &[u8], inherited: Option<&Jb2Dict>) -> Result<Jb2Dict, Jb2Error> {
2028 let mut pool: Vec<u8> = Vec::new();
2029 decode_dictionary_with_pool(data, inherited, &mut pool)
2030}
2031
2032fn decode_dictionary_with_pool(
2033 data: &[u8],
2034 inherited: Option<&Jb2Dict>,
2035 pool: &mut Vec<u8>,
2036) -> Result<Jb2Dict, Jb2Error> {
2037 let mut zp = ZpDecoder::new(data).map_err(|_| Jb2Error::ZpInitFailed)?;
2038
2039 let mut record_type_ctx = NumContext::new();
2040 let mut image_size_ctx = NumContext::new();
2041 let mut symbol_width_ctx = NumContext::new();
2042 let mut symbol_height_ctx = NumContext::new();
2043 let mut inherit_dict_size_ctx = NumContext::new();
2044 let mut symbol_index_ctx = NumContext::new();
2045 let mut symbol_width_diff_ctx = NumContext::new();
2046 let mut symbol_height_diff_ctx = NumContext::new();
2047 let mut comment_length_ctx = NumContext::new();
2048 let mut comment_octet_ctx = NumContext::new();
2049
2050 let mut direct_bitmap_ctx = [0u8; 1024];
2051 let mut refinement_bitmap_ctx = [0u8; 2048];
2052 let mut refinement_bitmap_ctx_p = [0x8000u16; 2048];
2053 let mut total_sym_pixels = 0usize;
2054
2055 let mut rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
2057 let mut initial_dict_length: usize = 0;
2058 if rtype == 9 {
2059 initial_dict_length = decode_num(&mut zp, &mut inherit_dict_size_ctx, 0, 262142) as usize;
2060 rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
2061 }
2062 let _ = rtype;
2063
2064 let _dict_width = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
2066 let _dict_height = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
2067
2068 let mut flag_ctx: u8 = 0;
2070 if zp.decode_bit(&mut flag_ctx) {
2071 return Err(Jb2Error::BadHeaderFlag);
2072 }
2073
2074 let initial_inh: &[Jbm] = if initial_dict_length > 0 {
2075 match inherited {
2076 Some(inh) => {
2077 if initial_dict_length > inh.symbols.len() {
2078 return Err(Jb2Error::InheritedDictTooLarge);
2079 }
2080 &inh.symbols[..initial_dict_length]
2081 }
2082 None => return Err(Jb2Error::MissingSharedDict),
2083 }
2084 } else {
2085 &[]
2086 };
2087 let mut dict = JbmDict::new(initial_inh);
2088
2089 let mut record_count = 0usize;
2091 loop {
2092 if record_count >= MAX_RECORDS {
2093 return Err(Jb2Error::TooManyRecords);
2094 }
2095 record_count += 1;
2096 let rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
2097
2098 match rtype {
2099 2 => {
2101 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
2102 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
2103 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels)?;
2104 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
2105 dict.push(bm.crop_and_recycle(pool));
2106 }
2107
2108 5 => {
2110 if dict.is_empty() {
2111 return Err(Jb2Error::EmptyDictReference);
2112 }
2113 let index =
2114 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
2115 if index >= dict.len() {
2116 return Err(Jb2Error::InvalidSymbolIndex);
2117 }
2118 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
2119 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
2120 let cbm_w = dict[index].width + wdiff;
2121 let cbm_h = dict[index].height + hdiff;
2122 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels)?;
2123 let cbm = decode_bitmap_ref(
2124 &mut zp,
2125 &mut refinement_bitmap_ctx,
2126 &mut refinement_bitmap_ctx_p,
2127 cbm_w,
2128 cbm_h,
2129 &dict[index],
2130 pool,
2131 )?;
2132 dict.push(cbm.crop_and_recycle(pool));
2133 }
2134
2135 9 => {}
2137
2138 10 => {
2140 let length = decode_num(&mut zp, &mut comment_length_ctx, 0, 262142) as usize;
2141 let length = length.min(MAX_COMMENT_BYTES);
2142 for _ in 0..length {
2143 decode_num(&mut zp, &mut comment_octet_ctx, 0, 255);
2144 }
2145 }
2146
2147 11 => break,
2149
2150 _ => return Err(Jb2Error::UnexpectedDictRecordType),
2151 }
2152 }
2153
2154 Ok(Jb2Dict {
2155 symbols: dict.into_symbols(),
2156 })
2157}
2158
2159#[cfg(test)]
2164mod tests {
2165 use super::*;
2166
2167 fn assets_path() -> std::path::PathBuf {
2168 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
2169 .join("../../references/djvujs/library/assets")
2170 }
2171
2172 fn golden_path() -> std::path::PathBuf {
2173 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tests/golden/jb2")
2174 }
2175
2176 fn extract_sjbz(djvu_data: &[u8]) -> Vec<u8> {
2179 let file = djvu_iff::parse(djvu_data).unwrap();
2180 let sjbz = file.root.find_first(b"Sjbz").unwrap();
2181 sjbz.data().to_vec()
2182 }
2183
2184 fn extract_first_page_sjbz(djvu_data: &[u8]) -> Vec<u8> {
2185 let file = djvu_iff::parse(djvu_data).unwrap();
2186 let page_form = file
2187 .root
2188 .children()
2189 .iter()
2190 .find(|c| {
2191 matches!(c, djvu_iff::Chunk::Form { secondary_id, .. }
2192 if secondary_id == b"DJVU")
2193 })
2194 .expect("no DJVU form");
2195 page_form.find_first(b"Sjbz").unwrap().data().to_vec()
2196 }
2197
2198 fn find_page_form_data(djvu_data: &[u8], page: usize) -> Vec<u8> {
2199 let file = djvu_iff::parse(djvu_data).unwrap();
2200 let mut idx = 0;
2201 for chunk in file.root.children() {
2202 if matches!(chunk, djvu_iff::Chunk::Form { secondary_id, .. }
2203 if secondary_id == b"DJVU")
2204 {
2205 if idx == page {
2206 return chunk.find_first(b"Sjbz").unwrap().data().to_vec();
2207 }
2208 idx += 1;
2209 }
2210 }
2211 panic!("page {} not found", page);
2212 }
2213
2214 fn find_djvi_djbz_data(djvu_data: &[u8]) -> Vec<u8> {
2215 let file = djvu_iff::parse(djvu_data).unwrap();
2216 for chunk in file.root.children() {
2217 if let djvu_iff::Chunk::Form { secondary_id, .. } = chunk
2218 && secondary_id == b"DJVI"
2219 && let Some(djbz) = chunk.find_first(b"Djbz")
2220 {
2221 return djbz.data().to_vec();
2222 }
2223 }
2224 panic!("DJVI with Djbz not found");
2225 }
2226
2227 #[test]
2232 fn jb2_new_decode_boy_jb2_mask() {
2233 let djvu = std::fs::read(assets_path().join("boy_jb2.djvu")).unwrap();
2234 let sjbz = extract_sjbz(&djvu);
2235 let bitmap = decode(&sjbz, None).unwrap();
2236 let actual_pbm = bitmap.to_pbm();
2237 let expected_pbm = std::fs::read(golden_path().join("boy_jb2_mask.pbm")).unwrap();
2238 assert_eq!(
2239 actual_pbm.len(),
2240 expected_pbm.len(),
2241 "PBM size mismatch: got {} expected {}",
2242 actual_pbm.len(),
2243 expected_pbm.len()
2244 );
2245 assert_eq!(actual_pbm, expected_pbm, "boy_jb2_mask pixel mismatch");
2246 }
2247
2248 #[test]
2249 fn jb2_new_decode_carte_p1_mask() {
2250 let djvu = std::fs::read(assets_path().join("carte.djvu")).unwrap();
2251 let sjbz = extract_first_page_sjbz(&djvu);
2252 let bitmap = decode(&sjbz, None).unwrap();
2253 let actual_pbm = bitmap.to_pbm();
2254 let expected_pbm = std::fs::read(golden_path().join("carte_p1_mask.pbm")).unwrap();
2255 assert_eq!(
2256 actual_pbm.len(),
2257 expected_pbm.len(),
2258 "carte_p1_mask size mismatch"
2259 );
2260 assert_eq!(actual_pbm, expected_pbm, "carte_p1_mask pixel mismatch");
2261 }
2262
2263 #[test]
2264 fn jb2_new_decode_djvu3spec_p1_mask() {
2265 let djvu = std::fs::read(assets_path().join("DjVu3Spec_bundled.djvu")).unwrap();
2266 let file = djvu_iff::parse(&djvu).unwrap();
2267
2268 let mut idx = 0usize;
2270 let mut page_form_opt: Option<&djvu_iff::Chunk> = None;
2271 for chunk in file.root.children() {
2272 if matches!(chunk, djvu_iff::Chunk::Form { secondary_id, .. }
2273 if secondary_id == b"DJVU")
2274 {
2275 if idx == 0 {
2276 page_form_opt = Some(chunk);
2277 break;
2278 }
2279 idx += 1;
2280 }
2281 }
2282 let page_form = page_form_opt.expect("page 0 not found");
2283 let djbz_data = page_form.find_first(b"Djbz").unwrap().data().to_vec();
2284 let sjbz_data = page_form.find_first(b"Sjbz").unwrap().data().to_vec();
2285
2286 let shared_dict = decode_dict(&djbz_data, None).unwrap();
2287 let bitmap = decode(&sjbz_data, Some(&shared_dict)).unwrap();
2288 let actual_pbm = bitmap.to_pbm();
2289 let expected_pbm = std::fs::read(golden_path().join("djvu3spec_p1_mask.pbm")).unwrap();
2290 assert_eq!(
2291 actual_pbm.len(),
2292 expected_pbm.len(),
2293 "djvu3spec_p1_mask size mismatch"
2294 );
2295 assert_eq!(actual_pbm, expected_pbm, "djvu3spec_p1_mask pixel mismatch");
2296 }
2297
2298 #[test]
2299 fn jb2_new_decode_djvu3spec_p2_mask() {
2300 let djvu = std::fs::read(assets_path().join("DjVu3Spec_bundled.djvu")).unwrap();
2301 let djbz_data = find_djvi_djbz_data(&djvu);
2302 let sjbz_data = find_page_form_data(&djvu, 1);
2303
2304 let shared_dict = decode_dict(&djbz_data, None).unwrap();
2305 let bitmap = decode(&sjbz_data, Some(&shared_dict)).unwrap();
2306 let actual_pbm = bitmap.to_pbm();
2307 let expected_pbm = std::fs::read(golden_path().join("djvu3spec_p2_mask.pbm")).unwrap();
2308 assert_eq!(
2309 actual_pbm.len(),
2310 expected_pbm.len(),
2311 "djvu3spec_p2_mask size mismatch"
2312 );
2313 assert_eq!(actual_pbm, expected_pbm, "djvu3spec_p2_mask pixel mismatch");
2314 }
2315
2316 #[test]
2317 fn jb2_new_decode_navm_fgbz_p1_mask() {
2318 let djvu = std::fs::read(assets_path().join("navm_fgbz.djvu")).unwrap();
2319 let djbz_data = find_djvi_djbz_data(&djvu);
2320 let sjbz_data = find_page_form_data(&djvu, 0);
2321
2322 let shared_dict = decode_dict(&djbz_data, None).unwrap();
2323 let bitmap = decode(&sjbz_data, Some(&shared_dict)).unwrap();
2324 let actual_pbm = bitmap.to_pbm();
2325 let expected_pbm = std::fs::read(golden_path().join("navm_fgbz_p1_mask.pbm")).unwrap();
2326 assert_eq!(
2327 actual_pbm.len(),
2328 expected_pbm.len(),
2329 "navm_fgbz_p1_mask size mismatch"
2330 );
2331 assert_eq!(actual_pbm, expected_pbm, "navm_fgbz_p1_mask pixel mismatch");
2332 }
2333
2334 #[test]
2337 fn jb2_new_empty_input_does_not_panic() {
2338 let _ = decode(&[], None);
2339 }
2340
2341 #[test]
2342 fn jb2_new_single_byte_does_not_panic() {
2343 let _ = decode(&[0x00], None);
2344 }
2345
2346 #[test]
2347 fn jb2_new_all_zeros_does_not_panic() {
2348 let _ = decode(&[0u8; 64], None);
2349 }
2350
2351 #[test]
2352 fn jb2_new_dict_empty_input_does_not_panic() {
2353 let _ = decode_dict(&[], None);
2354 }
2355
2356 #[test]
2357 fn jb2_new_dict_truncated_does_not_panic() {
2358 let _ = decode_dict(&[0u8; 8], None);
2359 }
2360
2361 #[test]
2364 fn jb2_error_variants_have_meaningful_messages() {
2365 assert!(Jb2Error::BadHeaderFlag.to_string().contains("flag"));
2366 assert!(Jb2Error::InheritedDictTooLarge.to_string().contains("dict"));
2367 assert!(Jb2Error::MissingSharedDict.to_string().contains("dict"));
2368 assert!(Jb2Error::ImageTooLarge.to_string().contains("large"));
2369 assert!(Jb2Error::EmptyDictReference.to_string().contains("dict"));
2370 assert!(Jb2Error::InvalidSymbolIndex.to_string().contains("symbol"));
2371 assert!(Jb2Error::UnknownRecordType.to_string().contains("record"));
2372 assert!(
2373 Jb2Error::UnexpectedDictRecordType
2374 .to_string()
2375 .contains("record")
2376 );
2377 assert!(Jb2Error::ZpInitFailed.to_string().contains("ZP"));
2378 assert!(Jb2Error::Truncated.to_string().contains("truncated"));
2379 }
2380
2381 #[test]
2383 fn jb2_image_size_overflow_guard() {
2384 let w: usize = 65536;
2385 let h: usize = 65537;
2386 let safe_size = w.saturating_mul(h);
2387 assert!(
2388 safe_size > 64 * 1024 * 1024,
2389 "saturating_mul must exceed MAX_PIXELS"
2390 );
2391 }
2392
2393 #[test]
2396 fn test_decode_empty_data() {
2397 let result = decode(&[], None);
2398 assert!(result.is_err());
2399 }
2400
2401 #[test]
2402 fn test_decode_dict_empty() {
2403 let result = decode_dict(&[], None);
2404 assert!(result.is_err());
2405 }
2406
2407 #[test]
2408 fn test_decode_indexed_empty() {
2409 let result = decode_indexed(&[], None);
2410 assert!(result.is_err());
2411 }
2412
2413 #[test]
2417 fn blit_negative_width_does_not_hang() {
2418 let start = std::time::Instant::now();
2419 let _ = decode(&[0x7e, 0x00, 0x0c], None);
2420 assert!(start.elapsed().as_secs() < 2, "took {:?}", start.elapsed());
2421 }
2422
2423 #[test]
2429 fn jb2_pool_decode_matches_regular_decode_carte() {
2430 let djvu = std::fs::read(assets_path().join("carte.djvu")).unwrap();
2431 let sjbz = extract_first_page_sjbz(&djvu);
2432
2433 let regular = decode(&sjbz, None).expect("regular decode");
2434
2435 let mut pool = Vec::new();
2436 let pooled = decode_image_with_pool(&sjbz, None, &mut pool).expect("pool decode");
2437
2438 assert_eq!(regular.width, pooled.width, "width must match");
2439 assert_eq!(regular.height, pooled.height, "height must match");
2440 assert_eq!(regular.data, pooled.data, "pixel data must be identical");
2441 assert!(
2442 pool.capacity() > 0,
2443 "pool must have been used (capacity > 0 after decode)"
2444 );
2445 }
2446}
2447
2448#[cfg(test)]
2449mod regression_fuzz2 {
2450 use super::*;
2451
2452 #[test]
2456 fn huge_symbol_from_small_input_does_not_hang() {
2457 let data = &[
2458 0x7f, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2459 ];
2460 let start = std::time::Instant::now();
2461 let _ = decode(data, None);
2462 let limit_secs = if cfg!(debug_assertions) { 8 } else { 2 };
2466 assert!(
2467 start.elapsed().as_secs() < limit_secs,
2468 "took {:?}",
2469 start.elapsed()
2470 );
2471 }
2472
2473 #[test]
2476 fn exhausted_refinement_symbol_from_small_input_does_not_hang() {
2477 let data = &[0x2a, 0xce, 0x7d, 0x24, 0x01, 0x00];
2478 let start = std::time::Instant::now();
2479 assert!(matches!(decode(data, None), Err(Jb2Error::Truncated)));
2480 let limit_secs = if cfg!(debug_assertions) { 2 } else { 1 };
2481 assert!(
2482 start.elapsed().as_secs() < limit_secs,
2483 "took {:?}",
2484 start.elapsed()
2485 );
2486 }
2487
2488 #[test]
2491 fn exhausted_repeated_refinement_symbols_do_not_hang() {
2492 let data = &[0x2a, 0xce, 0xf1, 0xce, 0xf1, 0x88, 0x52, 0x82, 0xf7, 0xf7];
2493 let start = std::time::Instant::now();
2494 assert!(matches!(decode(data, None), Err(Jb2Error::Truncated)));
2495 let limit_secs = if cfg!(debug_assertions) { 2 } else { 1 };
2496 assert!(
2497 start.elapsed().as_secs() < limit_secs,
2498 "took {:?}",
2499 start.elapsed()
2500 );
2501 }
2502}