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#[cfg(feature = "std")]
48pub mod encode;
49
50#[derive(Debug, thiserror::Error, PartialEq, Eq)]
52#[non_exhaustive]
53pub enum Jb2Error {
54 #[error("JB2 stream is truncated")]
56 Truncated,
57
58 #[error("JB2: bad flag bit in header")]
60 BadHeaderFlag,
61
62 #[error("JB2: inherited dict length exceeds shared dict size")]
64 InheritedDictTooLarge,
65
66 #[error("JB2: stream requires shared dict but none provided")]
68 MissingSharedDict,
69
70 #[error("JB2: image dimensions too large")]
72 ImageTooLarge,
73
74 #[error("JB2: dict reference with empty dict")]
76 EmptyDictReference,
77
78 #[error("JB2: decoded symbol index out of dictionary range")]
80 InvalidSymbolIndex,
81
82 #[error("JB2: unknown record type")]
84 UnknownRecordType,
85
86 #[error("JB2: unexpected record type in dict stream")]
88 UnexpectedDictRecordType,
89
90 #[error("JB2: insufficient data to initialize ZP coder")]
92 ZpInitFailed,
93
94 #[error("JB2: record count exceeds safety limit")]
96 TooManyRecords,
97}
98
99pub(crate) struct NumContext {
110 pub(crate) ctx: Vec<u8>,
111 left: Vec<u32>,
112 right: Vec<u32>,
113}
114
115impl NumContext {
116 pub(crate) fn new() -> Self {
117 NumContext {
119 ctx: vec![0, 0],
120 left: vec![0, 0],
121 right: vec![0, 0],
122 }
123 }
124
125 pub(crate) fn root(&self) -> usize {
126 1
127 }
128
129 pub(crate) fn get_left(&mut self, node: usize) -> usize {
130 if self.left[node] == 0 {
131 let idx = self.ctx.len() as u32;
132 self.ctx.push(0);
133 self.left.push(0);
134 self.right.push(0);
135 self.left[node] = idx;
136 }
137 self.left[node] as usize
138 }
139
140 pub(crate) fn get_right(&mut self, node: usize) -> usize {
141 if self.right[node] == 0 {
142 let idx = self.ctx.len() as u32;
143 self.ctx.push(0);
144 self.left.push(0);
145 self.right.push(0);
146 self.right[node] = idx;
147 }
148 self.right[node] as usize
149 }
150}
151
152fn decode_num(zp: &mut ZpDecoder<'_>, ctx: &mut NumContext, low: i32, high: i32) -> i32 {
155 let mut low = low;
156 let mut high = high;
157 let mut negative = false;
158 let mut cutoff: i32 = 0;
159 let mut phase: u32 = 1;
160 let mut range: u32 = 0xffff_ffff;
161 let mut node = ctx.root();
162
163 while range != 1 {
164 let decision = if low >= cutoff {
165 true
166 } else if high >= cutoff {
167 zp.decode_bit(&mut ctx.ctx[node])
168 } else {
169 false
170 };
171
172 node = if decision {
173 ctx.get_right(node)
174 } else {
175 ctx.get_left(node)
176 };
177
178 match phase {
179 1 => {
180 negative = !decision;
181 if negative {
182 let temp = -low - 1;
183 low = -high - 1;
184 high = temp;
185 }
186 phase = 2;
187 cutoff = 1;
188 }
189 2 => {
190 if !decision {
191 phase = 3;
192 range = ((cutoff + 1) / 2) as u32;
193 if range == 1 {
194 cutoff = 0;
196 } else {
197 cutoff -= (range / 2) as i32;
198 }
199 } else {
200 cutoff = cutoff * 2 + 1;
201 }
202 }
203 3 => {
204 range /= 2;
205 if range == 0 {
206 range = 1;
207 }
208 if range != 1 {
209 if !decision {
210 cutoff -= (range / 2) as i32;
211 } else {
212 cutoff += (range / 2) as i32;
213 }
214 } else if !decision {
215 cutoff -= 1;
216 }
217 }
218 _ => {
219 range = range.saturating_sub(1);
222 }
223 }
224 }
225
226 if negative { -cutoff - 1 } else { cutoff }
227}
228
229#[derive(Clone)]
241struct Jbm {
242 width: i32,
243 height: i32,
244 data: Vec<u8>,
245}
246
247impl Jbm {
248 #[inline(always)]
249 fn row_stride_bytes(width: i32) -> usize {
250 (width.max(0) as usize).div_ceil(8)
251 }
252
253 #[inline(always)]
254 fn stride(&self) -> usize {
255 Self::row_stride_bytes(self.width)
256 }
257
258 #[inline(always)]
259 fn storage_bytes(width: i32, height: i32) -> usize {
260 Self::row_stride_bytes(width).saturating_mul(height.max(0) as usize)
261 }
262
263 fn new(width: i32, height: i32) -> Self {
264 let len = Self::storage_bytes(width, height);
265 Jbm {
266 width,
267 height,
268 data: vec![0u8; len],
269 }
270 }
271
272 #[inline(always)]
274 fn get(&self, row: i32, col: i32) -> u8 {
275 if row < 0 || row >= self.height || col < 0 || col >= self.width {
276 return 0;
277 }
278 let stride = self.stride();
279 let byte = self.data[row as usize * stride + (col as usize / 8)];
280 (byte >> (7 - (col as usize & 7))) & 1
281 }
282
283 #[inline(always)]
285 fn set_black(&mut self, row: usize, col: usize) {
286 let stride = self.stride();
287 self.data[row * stride + (col / 8)] |= 0x80u8 >> (col & 7);
288 }
289
290 fn new_from_pool(width: i32, height: i32, pool: &mut Vec<u8>) -> Self {
298 let bytes = Self::storage_bytes(width, height);
299 if pool.len() < bytes {
300 pool.resize(bytes, 0u8);
301 }
302 pool[..bytes].fill(0u8);
304 let mut data = core::mem::take(pool);
305 data.truncate(bytes);
306 Jbm {
307 width,
308 height,
309 data,
310 }
311 }
312
313 fn crop_and_recycle(self, pool: &mut Vec<u8>) -> Jbm {
324 if self.width > 0 && self.height > 0 {
325 let w = self.width as usize;
326 let h = self.height as usize;
327 let stride = self.stride();
328 let last_col = w - 1;
329 let data = &self.data;
330 let top_has = data[..stride].iter().any(|&b| b != 0);
333 let bot_has = data[(h - 1) * stride..h * stride].iter().any(|&b| b != 0);
334 let left_has = (0..h).any(|r| (data[r * stride] & 0x80) != 0);
335 let right_has =
336 (0..h).any(|r| (data[r * stride + last_col / 8] & (0x80u8 >> (last_col & 7))) != 0);
337 if top_has && bot_has && left_has && right_has {
338 *pool = Vec::with_capacity(self.data.len());
342 return self;
343 }
344 }
345 let cropped = self.crop_to_content();
346 *pool = self.data;
348 cropped
349 }
350
351 fn recycle_into(self, pool: &mut Vec<u8>) {
355 *pool = self.data;
356 }
357
358 fn crop_to_content(&self) -> Jbm {
360 if self.width <= 0 || self.height <= 0 {
361 return Jbm::new(0, 0);
362 }
363 let stride = self.stride();
364 let mut min_row = self.height;
365 let mut max_row: i32 = -1;
366 let mut min_col = self.width;
367 let mut max_col: i32 = -1;
368
369 for row in 0..self.height {
370 let row_bytes = &self.data[row as usize * stride..(row as usize + 1) * stride];
371 let mut byte_min: Option<usize> = None;
373 let mut byte_max: Option<usize> = None;
374 for (i, &b) in row_bytes.iter().enumerate() {
375 if b != 0 {
376 if byte_min.is_none() {
377 byte_min = Some(i);
378 }
379 byte_max = Some(i);
380 }
381 }
382 if let (Some(bmin), Some(bmax)) = (byte_min, byte_max) {
383 let col_lo = bmin * 8 + row_bytes[bmin].leading_zeros() as usize;
384 let col_hi = bmax * 8 + (7 - row_bytes[bmax].trailing_zeros() as usize);
387 let col_hi = col_hi.min(self.width as usize - 1) as i32;
388 let col_lo = col_lo as i32;
389 min_row = min_row.min(row);
390 max_row = max_row.max(row);
391 min_col = min_col.min(col_lo);
392 max_col = max_col.max(col_hi);
393 }
394 }
395
396 if max_row < 0 {
397 return Jbm::new(0, 0);
398 }
399
400 let nw = max_col - min_col + 1;
401 let nh = max_row - min_row + 1;
402 let mut out = Jbm::new(nw, nh);
403
404 for row in min_row..=max_row {
405 for col in min_col..=max_col {
406 let src_byte = self.data[row as usize * stride + (col as usize / 8)];
407 if (src_byte >> (7 - (col as usize & 7))) & 1 != 0 {
408 let out_row = (row - min_row) as usize;
409 let out_col = (col - min_col) as usize;
410 out.set_black(out_row, out_col);
411 }
412 }
413 }
414 out
415 }
416}
417
418const MAX_SYMBOL_PIXELS: usize = 16 * 1024 * 1024; const ZP_EOF_SLACK_BYTES: usize = 16;
441pub(crate) const MAX_TOTAL_SYMBOL_PIXELS: usize = 256 * 1024 * 1024;
446const MAX_PAGE_SYMBOL_PIXELS: usize = 16 * 1024 * 1024;
460const MAX_TOTAL_BLIT_PIXELS: usize = 256 * 1024 * 1024; const MAX_RECORDS: usize = 65_536; #[inline(always)]
465fn check_pixel_budget(w: i32, h: i32, total: &mut usize, max_total: usize) -> Result<(), Jb2Error> {
466 let pixels = (w.max(0) as usize).saturating_mul(h.max(0) as usize);
467 if pixels > MAX_SYMBOL_PIXELS {
468 return Err(Jb2Error::ImageTooLarge);
469 }
470 *total = total.saturating_add(pixels);
471 if *total > max_total {
475 return Err(Jb2Error::ImageTooLarge);
476 }
477 Ok(())
478}
479
480#[inline(always)]
481fn check_symbol_decode_budget(
482 zp: &ZpDecoder<'_>,
483 w: i32,
484 h: i32,
485 total: &mut usize,
486 max_total: usize,
487) -> Result<(), Jb2Error> {
488 check_pixel_budget(w, h, total, max_total)?;
489 if zp.synthetic_bytes() > ZP_EOF_SLACK_BYTES {
494 return Err(Jb2Error::Truncated);
495 }
496 Ok(())
497}
498
499#[inline(always)]
504fn check_blit_budget(sym: &Jbm, total: &mut usize) -> Result<(), Jb2Error> {
505 let pixels = (sym.width.max(0) as usize).saturating_mul(sym.height.max(0) as usize);
506 *total = total.saturating_add(pixels);
507 if *total > MAX_TOTAL_BLIT_PIXELS {
508 return Err(Jb2Error::ImageTooLarge);
509 }
510 Ok(())
511}
512#[inline(never)]
517fn decode_direct_row(
518 zp: &mut ZpDecoder<'_>,
519 ctx: &mut [u8; 1024],
520 row_slice: &mut [u8],
521 rp1: &[u8],
522 rp2: &[u8],
523) {
524 use djvu_zp::tables::{LPS_NEXT, MPS_NEXT, PROB, THRESHOLD};
525
526 let mut a: u32 = zp.a;
527 let mut c: u32 = zp.c;
528 let mut fence: u32 = zp.fence;
529 let mut bit_buf = zp.bit_buf;
530 let mut bit_count = zp.bit_count;
531 let data = zp.data;
532 let mut pos = zp.pos;
533
534 macro_rules! read_byte {
535 () => {{
536 let b = if pos < data.len() { data[pos] } else { 0xff };
537 pos = pos.wrapping_add(1);
538 b as u32
539 }};
540 }
541 macro_rules! refill {
542 () => {
543 while bit_count <= 24 {
544 bit_buf = (bit_buf << 8) | read_byte!();
545 bit_count += 8;
546 }
547 };
548 }
549 macro_rules! renorm {
550 () => {{
551 let shift = (a as u16).leading_ones();
552 bit_count -= shift as i32;
553 a = (a << shift) & 0xffff;
554 let mask = (1u32 << (shift & 31)).wrapping_sub(1);
555 c = ((c << shift) | (bit_buf >> (bit_count as u32 & 31)) & mask) & 0xffff;
556 if bit_count < 16 {
557 refill!();
558 }
559 fence = c.min(0x7fff);
560 }};
561 }
562
563 let pix = |row: &[u8], col: usize| -> u32 { row.get(col).copied().unwrap_or(0) as u32 };
564 let w = row_slice.len();
565 let mut r2 = pix(rp2, 0) << 1 | pix(rp2, 1);
566 let mut r1 = pix(rp1, 0) << 2 | pix(rp1, 1) << 1 | pix(rp1, 2);
567 let mut r0: u32 = 0;
568
569 let (rp2_off, rp1_off) = if w >= 3 && rp2.len() >= w && rp1.len() >= w {
570 (&rp2[2..w], &rp1[3..w])
571 } else {
572 (&rp2[..0], &rp1[..0])
573 };
574 let mid_end = rp2_off.len().min(rp1_off.len());
575
576 macro_rules! decode_step {
577 ($out:expr, $n2:expr, $n1:expr) => {{
578 let idx = (((r2 << 7) | (r1 << 2) | r0) & 1023) as usize;
579 let state = ctx[idx] as usize;
580 let mps_bit = state & 1;
581 let z = a + PROB[state] as u32;
582 let bit = if z <= fence {
583 a = z;
584 mps_bit != 0
585 } else {
586 let boundary = 0x6000u32 + ((a + z) >> 2);
587 let z_clamped = z.min(boundary);
588 if z_clamped > c {
589 let complement = 0x10000u32 - z_clamped;
590 a = (a + complement) & 0xffff;
591 c = (c + complement) & 0xffff;
592 ctx[idx] = LPS_NEXT[state];
593 renorm!();
594 (1 - mps_bit) != 0
595 } else {
596 if a >= THRESHOLD[state] as u32 {
597 ctx[idx] = MPS_NEXT[state];
598 }
599 bit_count -= 1;
600 a = (z_clamped << 1) & 0xffff;
601 c = ((c << 1) | (bit_buf >> (bit_count as u32 & 31)) & 1) & 0xffff;
602 if bit_count < 16 {
603 refill!();
604 }
605 fence = c.min(0x7fff);
606 mps_bit != 0
607 }
608 };
609 *$out = bit as u8;
610 r2 = ((r2 << 1) & 0b111) | ($n2 as u32);
611 r1 = ((r1 << 1) & 0b11111) | ($n1 as u32);
612 r0 = ((r0 << 1) & 0b11) | bit as u32;
613 }};
614 }
615
616 let (fast_slice, slow_slice) = row_slice.split_at_mut(mid_end);
617 for (out, (n2, n1)) in fast_slice.iter_mut().zip(rp2_off.iter().zip(rp1_off)) {
618 decode_step!(out, *n2, *n1);
619 }
620 for (i, out) in slow_slice.iter_mut().enumerate() {
621 let col = i + mid_end;
622 decode_step!(out, pix(rp2, col + 2), pix(rp1, col + 3));
623 }
624
625 zp.a = a;
626 zp.c = c;
627 zp.fence = fence;
628 zp.bit_buf = bit_buf;
629 zp.bit_count = bit_count;
630 zp.pos = pos;
631}
632
633#[allow(clippy::too_many_arguments)]
640#[inline(never)]
641fn decode_ref_row(
642 zp: &mut ZpDecoder<'_>,
643 ctx: &mut [u8; 2048],
644 ctx_p: &mut [u16; 2048],
645 cbm_row_mut: &mut [u8],
646 cbm_r1: &[u8],
647 mbm_r2: &[u8],
648 mbm_r1: &[u8],
649 mbm_r0: &[u8],
650 col_shift: i32,
651 init_c_r1: u32,
652 init_m_r1: u32,
653 init_m_r0: u32,
654) {
655 use djvu_zp::tables::{LPS_NEXT, MPS_NEXT, PROB, THRESHOLD};
656
657 let mut a: u32 = zp.a;
658 let mut c: u32 = zp.c;
659 let mut fence: u32 = zp.fence;
660 let mut bit_buf = zp.bit_buf;
661 let mut bit_count = zp.bit_count;
662 let data = zp.data;
663 let mut pos = zp.pos;
664
665 macro_rules! read_byte {
666 () => {{
667 let b = if pos < data.len() { data[pos] } else { 0xff };
668 pos = pos.wrapping_add(1);
669 b as u32
670 }};
671 }
672 macro_rules! refill {
673 () => {
674 while bit_count <= 24 {
675 bit_buf = (bit_buf << 8) | read_byte!();
676 bit_count += 8;
677 }
678 };
679 }
680 macro_rules! renorm {
681 () => {{
682 let shift = (a as u16).leading_ones();
683 bit_count -= shift as i32;
684 a = (a << shift) & 0xffff;
685 let mask = (1u32 << (shift & 31)).wrapping_sub(1);
686 c = ((c << shift) | (bit_buf >> (bit_count as u32 & 31)) & mask) & 0xffff;
687 if bit_count < 16 {
688 refill!();
689 }
690 fence = c.min(0x7fff);
691 }};
692 }
693
694 let pix_row = |row_slice: &[u8], col: i32| -> u32 {
695 if col < 0 {
696 return 0;
697 }
698 row_slice.get(col as usize).copied().unwrap_or(0) as u32
699 };
700
701 let mut c_r0: u32 = 0;
703 let mut c_r1 = init_c_r1;
704 let mut m_r1 = init_m_r1;
705 let mut m_r0 = init_m_r0;
706
707 for col in 0..cbm_row_mut.len() as i32 {
708 let m_r2 = pix_row(mbm_r2, col + col_shift);
709 let idx = ((c_r1 << 8) | (c_r0 << 7) | (m_r2 << 6) | (m_r1 << 3) | m_r0) & 2047;
711
712 let state = ctx[idx as usize] as usize;
713 let prob = ctx_p[idx as usize] as u32; let mps_bit = state & 1;
715 let z = a + prob;
716
717 let bit = if z <= fence {
718 a = z;
719 mps_bit != 0
720 } else {
721 let boundary = 0x6000u32 + ((a + z) >> 2);
722 let z_clamped = z.min(boundary);
723 if z_clamped > c {
724 let complement = 0x10000u32 - z_clamped;
725 a = (a + complement) & 0xffff;
726 c = (c + complement) & 0xffff;
727 let next = LPS_NEXT[state];
728 ctx[idx as usize] = next;
729 ctx_p[idx as usize] = PROB[next as usize];
730 renorm!();
731 (1 - mps_bit) != 0
732 } else {
733 if a >= THRESHOLD[state] as u32 {
734 let next = MPS_NEXT[state];
735 ctx[idx as usize] = next;
736 ctx_p[idx as usize] = PROB[next as usize];
737 }
738 bit_count -= 1;
739 a = (z_clamped << 1) & 0xffff;
740 c = ((c << 1) | (bit_buf >> (bit_count as u32 & 31)) & 1) & 0xffff;
741 if bit_count < 16 {
742 refill!();
743 }
744 fence = c.min(0x7fff);
745 mps_bit != 0
746 }
747 };
748
749 if bit {
750 cbm_row_mut[col as usize] = 1;
751 }
752
753 c_r1 = ((c_r1 << 1) & 0b111) | pix_row(cbm_r1, col + 2);
754 c_r0 = bit as u32;
755 m_r1 = ((m_r1 << 1) & 0b111) | pix_row(mbm_r1, col + col_shift + 2);
756 m_r0 = ((m_r0 << 1) & 0b111) | pix_row(mbm_r0, col + col_shift + 2);
757 }
758
759 zp.a = a;
760 zp.c = c;
761 zp.fence = fence;
762 zp.bit_buf = bit_buf;
763 zp.bit_count = bit_count;
764 zp.pos = pos;
765}
766
767#[inline]
770fn pack_row_into(src: &[u8], width: usize, dst: &mut [u8]) {
771 let full_bytes = width / 8;
772 let rem = width % 8;
773 for i in 0..full_bytes {
774 let s: &[u8; 8] = src[i * 8..(i + 1) * 8].try_into().unwrap();
775 dst[i] = pack_byte(s);
776 }
777 if rem > 0 {
778 let base = full_bytes * 8;
779 let mut byte_val = 0u8;
780 for j in 0..rem {
781 if src[base + j] != 0 {
782 byte_val |= 0x80u8 >> j;
783 }
784 }
785 dst[full_bytes] = byte_val;
786 }
787}
788
789#[inline]
792fn unpack_row_into(src: &[u8], width: usize, dst: &mut [u8]) {
793 let full_bytes = width / 8;
794 let rem = width % 8;
795 for i in 0..full_bytes {
796 let b = src[i];
797 let out = &mut dst[i * 8..(i + 1) * 8];
798 out[0] = (b >> 7) & 1;
799 out[1] = (b >> 6) & 1;
800 out[2] = (b >> 5) & 1;
801 out[3] = (b >> 4) & 1;
802 out[4] = (b >> 3) & 1;
803 out[5] = (b >> 2) & 1;
804 out[6] = (b >> 1) & 1;
805 out[7] = b & 1;
806 }
807 if rem > 0 {
808 let b = src[full_bytes];
809 let base = full_bytes * 8;
810 for j in 0..rem {
811 dst[base + j] = (b >> (7 - j)) & 1;
812 }
813 }
814}
815
816fn decode_bitmap_direct(
817 zp: &mut ZpDecoder<'_>,
818 ctx: &mut [u8; 1024],
819 width: i32,
820 height: i32,
821 pool: &mut Vec<u8>,
822) -> Result<Jbm, Jb2Error> {
823 let pixels = (width.max(0) as usize).saturating_mul(height.max(0) as usize);
824 if pixels > MAX_SYMBOL_PIXELS {
825 return Err(Jb2Error::ImageTooLarge);
826 }
827 if width <= 0 || height <= 0 {
828 return Ok(Jbm::new_from_pool(width, height, pool));
829 }
830 let mut bm = Jbm::new_from_pool(width, height, pool);
831 let w = width as usize;
832 let h = height as usize;
833 let stride = bm.stride();
834 debug_assert_eq!(bm.data.len(), stride * h);
835
836 let mut s_curr = vec![0u8; w];
839 let mut s_prev1 = vec![0u8; w];
840 let mut s_prev2 = vec![0u8; w];
841
842 for row in (0..h).rev() {
843 s_curr.iter_mut().for_each(|b| *b = 0);
844 decode_direct_row(zp, ctx, &mut s_curr, &s_prev1, &s_prev2);
845 pack_row_into(&s_curr, w, &mut bm.data[row * stride..(row + 1) * stride]);
846 core::mem::swap(&mut s_prev2, &mut s_prev1);
848 core::mem::swap(&mut s_prev1, &mut s_curr);
849 }
850 Ok(bm)
851}
852
853fn decode_bitmap_ref(
862 zp: &mut ZpDecoder<'_>,
863 ctx: &mut [u8; 2048],
864 ctx_p: &mut [u16; 2048],
865 width: i32,
866 height: i32,
867 mbm: &Jbm,
868 pool: &mut Vec<u8>,
869) -> Result<Jbm, Jb2Error> {
870 let pixels = (width.max(0) as usize).saturating_mul(height.max(0) as usize);
871 if pixels > MAX_SYMBOL_PIXELS {
872 return Err(Jb2Error::ImageTooLarge);
873 }
874 if width <= 0 || height <= 0 {
875 return Ok(Jbm::new_from_pool(width, height, pool));
876 }
877 let mut cbm = Jbm::new_from_pool(width, height, pool);
878
879 let crow = (height - 1) >> 1;
881 let ccol = (width - 1) >> 1;
882 let mrow = (mbm.height - 1) >> 1;
883 let mcol = (mbm.width - 1) >> 1;
884 let row_shift = mrow - crow;
885 let col_shift = mcol - ccol;
886
887 let pix_row = |row_slice: &[u8], col: i32| -> u32 {
889 if col < 0 {
890 return 0;
891 }
892 row_slice.get(col as usize).copied().unwrap_or(0) as u32
893 };
894
895 let cw = width as usize;
896 let cstride = cbm.stride();
897 let mw = mbm.width.max(0) as usize;
898 let mstride = mbm.stride();
899
900 let mut s_mbm_r2 = vec![0u8; mw];
904 let mut s_mbm_r1 = vec![0u8; mw];
905 let mut s_mbm_r0 = vec![0u8; mw];
906 let mut have_r2;
907 let mut have_r1;
908 let mut have_r0;
909
910 let mut s_cbm_curr = vec![0u8; cw];
912 let mut s_cbm_prev1 = vec![0u8; cw];
913
914 let unpack_mbm_row = |r: i32, buf: &mut [u8]| -> bool {
915 if r < 0 || r >= mbm.height || mw == 0 {
916 return false;
917 }
918 let off = r as usize * mstride;
919 unpack_row_into(&mbm.data[off..off + mstride], mw, buf);
920 true
921 };
922
923 let first_mr = (height - 1) + row_shift;
926 have_r2 = unpack_mbm_row(first_mr + 1, &mut s_mbm_r2);
927 have_r1 = unpack_mbm_row(first_mr, &mut s_mbm_r1);
928 have_r0 = unpack_mbm_row(first_mr - 1, &mut s_mbm_r0);
929
930 for row in (0..height).rev() {
931 let mr = row + row_shift;
932
933 let mbm_r2: &[u8] = if have_r2 { &s_mbm_r2 } else { &[] };
935 let mbm_r1: &[u8] = if have_r1 { &s_mbm_r1 } else { &[] };
936 let mbm_r0: &[u8] = if have_r0 { &s_mbm_r0 } else { &[] };
937
938 let cbm_r1: &[u8] = if row + 1 < height { &s_cbm_prev1 } else { &[] };
939 s_cbm_curr.iter_mut().for_each(|b| *b = 0);
940
941 let init_c_r1 = pix_row(cbm_r1, 0) << 1 | pix_row(cbm_r1, 1);
942 let init_m_r1 = pix_row(mbm_r1, col_shift - 1) << 2
943 | pix_row(mbm_r1, col_shift) << 1
944 | pix_row(mbm_r1, col_shift + 1);
945 let init_m_r0 = pix_row(mbm_r0, col_shift - 1) << 2
946 | pix_row(mbm_r0, col_shift) << 1
947 | pix_row(mbm_r0, col_shift + 1);
948
949 decode_ref_row(
950 zp,
951 ctx,
952 ctx_p,
953 &mut s_cbm_curr,
954 cbm_r1,
955 mbm_r2,
956 mbm_r1,
957 mbm_r0,
958 col_shift,
959 init_c_r1,
960 init_m_r1,
961 init_m_r0,
962 );
963
964 pack_row_into(
966 &s_cbm_curr,
967 cw,
968 &mut cbm.data[row as usize * cstride..(row as usize + 1) * cstride],
969 );
970
971 core::mem::swap(&mut s_cbm_prev1, &mut s_cbm_curr);
973
974 core::mem::swap(&mut s_mbm_r2, &mut s_mbm_r1);
980 have_r2 = have_r1;
981 core::mem::swap(&mut s_mbm_r1, &mut s_mbm_r0);
982 have_r1 = have_r0;
983 have_r0 = unpack_mbm_row(mr - 2, &mut s_mbm_r0);
984 }
985 Ok(cbm)
986}
987
988struct Baseline {
993 arr: [i32; 3],
994 index: i32,
995}
996
997impl Baseline {
998 fn new() -> Self {
999 Baseline {
1000 arr: [0, 0, 0],
1001 index: -1,
1002 }
1003 }
1004
1005 fn fill(&mut self, val: i32) {
1006 self.arr = [val, val, val];
1007 }
1008
1009 fn add(&mut self, val: i32) {
1010 self.index += 1;
1011 if self.index == 3 {
1012 self.index = 0;
1013 }
1014 self.arr[self.index as usize] = val;
1015 }
1016
1017 fn get_val(&self) -> i32 {
1018 let (a, b, c) = (self.arr[0], self.arr[1], self.arr[2]);
1019 if (a >= b && a <= c) || (a <= b && a >= c) {
1020 a
1021 } else if (b >= a && b <= c) || (b <= a && b >= c) {
1022 b
1023 } else {
1024 c
1025 }
1026 }
1027}
1028
1029#[allow(clippy::too_many_arguments)]
1034fn blit_indexed(
1035 page: &mut [u8],
1036 blit_map: &mut [i32],
1037 page_w: i32,
1038 page_h: i32,
1039 symbol: &Jbm,
1040 x: i32,
1041 y: i32,
1042 blit_idx: i32,
1043) {
1044 if symbol.width <= 0 || symbol.height <= 0 {
1047 return;
1048 }
1049 if x >= 0 && y >= 0 && x + symbol.width <= page_w && y + symbol.height <= page_h {
1050 let pw = page_w as usize;
1051 let sw = symbol.width as usize;
1052 let sym_stride = symbol.stride();
1053 let full_bytes = sw / 8;
1054 let rem = sw & 7;
1055 for row in 0..symbol.height as usize {
1056 let src_row_off = row * sym_stride;
1057 let dst_off = (y as usize + row) * pw + x as usize;
1058 for byte_i in 0..full_bytes {
1059 let b = symbol.data[src_row_off + byte_i];
1060 if b == 0 {
1061 continue;
1062 }
1063 let base_col = byte_i * 8;
1064 for j in 0..8 {
1065 if (b >> (7 - j)) & 1 != 0 {
1066 page[dst_off + base_col + j] = 1;
1067 blit_map[dst_off + base_col + j] = blit_idx;
1068 }
1069 }
1070 }
1071 if rem > 0 {
1072 let b = symbol.data[src_row_off + full_bytes];
1073 if b != 0 {
1074 let base_col = full_bytes * 8;
1075 for j in 0..rem {
1076 if (b >> (7 - j)) & 1 != 0 {
1077 page[dst_off + base_col + j] = 1;
1078 blit_map[dst_off + base_col + j] = blit_idx;
1079 }
1080 }
1081 }
1082 }
1083 }
1084 } else {
1085 for row in 0..symbol.height {
1086 let py = y + row;
1087 if py < 0 || py >= page_h {
1088 continue;
1089 }
1090 for col in 0..symbol.width {
1091 if symbol.get(row, col) != 0 {
1092 let px = x + col;
1093 if px >= 0 && px < page_w {
1094 let idx = (py * page_w + px) as usize;
1095 page[idx] = 1;
1096 blit_map[idx] = blit_idx;
1097 }
1098 }
1099 }
1100 }
1101 }
1102}
1103
1104fn blit_to_bitmap(bm: &mut Bitmap, sym: &Jbm, x: i32, y: i32) {
1115 if sym.width <= 0 || sym.height <= 0 {
1116 return;
1117 }
1118 let bw = bm.width as i32;
1119 let bh = bm.height as i32;
1120 let bm_stride = bm.row_stride();
1121 let sw = sym.width;
1122 let sh = sym.height;
1123 let sym_stride = sym.stride();
1124
1125 if x >= 0
1127 && y >= 0
1128 && x.checked_add(sw).is_some_and(|v| v <= bw)
1129 && y.checked_add(sh).is_some_and(|v| v <= bh)
1130 {
1131 let x_off = x as usize;
1132 let byte_off = x_off / 8;
1133 let bit_off = x_off & 7;
1134 let sw_u = sw as usize;
1135 let full = sw_u / 8;
1136 let rem = sw_u & 7;
1137 let bm_y_base = (bm.height as usize) - 1 - y as usize;
1138
1139 if bit_off == 0 {
1140 for sym_row in 0..sh as usize {
1141 let bm_y = bm_y_base - sym_row;
1142 let src = &sym.data[sym_row * sym_stride..sym_row * sym_stride + sym_stride];
1143 let dst = &mut bm.data[bm_y * bm_stride..];
1144 for i in 0..full {
1145 dst[byte_off + i] |= src[i];
1146 }
1147 if rem > 0 {
1148 dst[byte_off + full] |= src[full];
1152 }
1153 }
1154 } else {
1155 let rshift = bit_off as u32;
1156 let lshift = 8 - bit_off as u32;
1157 for sym_row in 0..sh as usize {
1158 let bm_y = bm_y_base - sym_row;
1159 let src = &sym.data[sym_row * sym_stride..sym_row * sym_stride + sym_stride];
1160 let row_off = bm_y * bm_stride;
1161 for (i, &s) in src.iter().enumerate().take(full) {
1162 bm.data[row_off + byte_off + i] |= s >> rshift;
1163 bm.data[row_off + byte_off + i + 1] |= s << lshift;
1164 }
1165 if rem > 0 {
1166 let s = src[full];
1167 bm.data[row_off + byte_off + full] |= s >> rshift;
1168 let overflow = row_off + byte_off + full + 1;
1169 if overflow < bm.data.len() {
1170 bm.data[overflow] |= s << lshift;
1171 }
1172 }
1173 }
1174 }
1175 } else {
1176 for sym_row in 0..sh {
1178 let bm_y = bh - 1 - y - sym_row;
1179 if bm_y < 0 || bm_y >= bh {
1180 continue;
1181 }
1182 let bm_y = bm_y as usize;
1183 let row_off = bm_y * bm_stride;
1184 let src_row_off = sym_row as usize * sym_stride;
1185 for col in 0..sw {
1186 let b = sym.data[src_row_off + (col as usize / 8)];
1187 if (b >> (7 - (col as usize & 7))) & 1 != 0 {
1188 let px = x + col;
1189 if px >= 0 && px < bw {
1190 let px = px as usize;
1191 bm.data[row_off + px / 8] |= 0x80u8 >> (px & 7);
1192 }
1193 }
1194 }
1195 }
1196 }
1197}
1198
1199fn blit_to_bitmap_downsampled(
1213 bm: &mut Bitmap,
1214 sym: &Jbm,
1215 x: i32,
1216 y: i32,
1217 full_w: i32,
1218 full_h: i32,
1219 shift: u32,
1220) {
1221 if sym.width <= 0 || sym.height <= 0 {
1222 return;
1223 }
1224 let dst_w = bm.width as i32;
1225 let dst_h = bm.height as i32;
1226 let bm_stride = bm.row_stride();
1227 let sw = sym.width;
1228 let sh = sym.height;
1229 let sym_stride = sym.stride();
1230
1231 for sym_row in 0..sh {
1232 let full_y_top = full_h - 1 - (y + sym_row);
1235 if full_y_top < 0 || full_y_top >= full_h {
1236 continue;
1237 }
1238 let dy = full_y_top >> shift;
1239 if dy >= dst_h {
1240 continue;
1241 }
1242 let row_off = dy as usize * bm_stride;
1243 let src_row_off = sym_row as usize * sym_stride;
1244 for col in 0..sw {
1245 let b = sym.data[src_row_off + (col as usize / 8)];
1246 if (b >> (7 - (col as usize & 7))) & 1 == 0 {
1247 continue;
1248 }
1249 let full_x = x + col;
1250 if full_x < 0 || full_x >= full_w {
1251 continue;
1252 }
1253 let dx = full_x >> shift;
1254 if dx >= dst_w {
1255 continue;
1256 }
1257 bm.data[row_off + dx as usize / 8] |= 0x80u8 >> (dx as usize & 7);
1258 }
1259 }
1260}
1261
1262#[inline(always)]
1269fn pack_byte(s: &[u8; 8]) -> u8 {
1270 ((s[0] != 0) as u8) << 7
1271 | ((s[1] != 0) as u8) << 6
1272 | ((s[2] != 0) as u8) << 5
1273 | ((s[3] != 0) as u8) << 4
1274 | ((s[4] != 0) as u8) << 3
1275 | ((s[5] != 0) as u8) << 2
1276 | ((s[6] != 0) as u8) << 1
1277 | ((s[7] != 0) as u8)
1278}
1279
1280fn page_to_bitmap(page: &[u8], width: i32, height: i32) -> Bitmap {
1281 let w = width as usize;
1282 let h = height as usize;
1283 let mut bm = Bitmap::new(width as u32, height as u32);
1284 let stride = bm.row_stride();
1285 let full_bytes = w / 8;
1286 let remaining = w % 8;
1287
1288 for row in 0..h {
1289 let src_row = &page[row * w..(row + 1) * w];
1290 let dst_y = h - 1 - row; let dst_off = dst_y * stride;
1292
1293 for byte_idx in 0..full_bytes {
1297 let s: &[u8; 8] = src_row[byte_idx * 8..(byte_idx + 1) * 8]
1298 .try_into()
1299 .unwrap();
1300 bm.data[dst_off + byte_idx] = pack_byte(s);
1301 }
1302
1303 if remaining > 0 {
1305 let base = full_bytes * 8;
1306 let mut byte_val = 0u8;
1307 for bit_pos in 0..remaining {
1308 if src_row[base + bit_pos] != 0 {
1309 byte_val |= 0x80u8 >> bit_pos;
1310 }
1311 }
1312 bm.data[dst_off + full_bytes] = byte_val;
1313 }
1314 }
1315 bm
1316}
1317
1318fn flip_blit_map(blit_map: &mut [i32], width: usize, height: usize) {
1320 for row in 0..height / 2 {
1321 let mirror = height - 1 - row;
1322 let a = row * width;
1323 let b = mirror * width;
1324 for col in 0..width {
1325 blit_map.swap(a + col, b + col);
1326 }
1327 }
1328}
1329
1330struct CoordContexts {
1336 offset_type: u8,
1337 hoff: NumContext,
1338 voff: NumContext,
1339 shoff: NumContext,
1340 svoff: NumContext,
1341}
1342
1343impl CoordContexts {
1344 fn new() -> Self {
1345 Self {
1346 offset_type: 0,
1347 hoff: NumContext::new(),
1348 voff: NumContext::new(),
1349 shoff: NumContext::new(),
1350 svoff: NumContext::new(),
1351 }
1352 }
1353}
1354
1355struct LayoutState {
1357 first_left: i32,
1358 first_bottom: i32,
1359 last_right: i32,
1360 baseline: Baseline,
1361}
1362
1363impl LayoutState {
1364 fn new(image_height: i32) -> Self {
1365 Self {
1366 first_left: -1,
1367 first_bottom: image_height - 1,
1368 last_right: 0,
1369 baseline: Baseline::new(),
1370 }
1371 }
1372}
1373
1374fn decode_symbol_coords(
1375 zp: &mut ZpDecoder<'_>,
1376 coord_ctx: &mut CoordContexts,
1377 layout: &mut LayoutState,
1378 sym_width: i32,
1379 sym_height: i32,
1380) -> (i32, i32) {
1381 let new_line = zp.decode_bit(&mut coord_ctx.offset_type);
1382
1383 let (x, y) = if new_line {
1384 let hoff = decode_num(zp, &mut coord_ctx.hoff, -262143, 262142);
1385 let voff = decode_num(zp, &mut coord_ctx.voff, -262143, 262142);
1386 let nx = layout.first_left + hoff;
1387 let ny = layout.first_bottom + voff - sym_height + 1;
1388 layout.first_left = nx;
1389 layout.first_bottom = ny;
1390 layout.baseline.fill(ny);
1391 (nx, ny)
1392 } else {
1393 let hoff = decode_num(zp, &mut coord_ctx.shoff, -262143, 262142);
1394 let voff = decode_num(zp, &mut coord_ctx.svoff, -262143, 262142);
1395 (layout.last_right + hoff, layout.baseline.get_val() + voff)
1396 };
1397
1398 layout.baseline.add(y);
1399 layout.last_right = x + sym_width - 1;
1400 (x, y)
1401}
1402
1403struct JbmDict<'a> {
1414 shared: &'a [Jbm],
1415 local: Vec<Jbm>,
1416}
1417
1418impl<'a> JbmDict<'a> {
1419 fn new(shared: &'a [Jbm]) -> Self {
1420 JbmDict {
1421 shared,
1422 local: Vec::new(),
1423 }
1424 }
1425 fn len(&self) -> usize {
1426 self.shared.len() + self.local.len()
1427 }
1428 fn is_empty(&self) -> bool {
1429 self.shared.is_empty() && self.local.is_empty()
1430 }
1431 fn push(&mut self, sym: Jbm) {
1432 self.local.push(sym);
1433 }
1434 fn into_symbols(self) -> Vec<Jbm> {
1435 let mut out = self.shared.to_vec();
1437 out.extend(self.local);
1438 out
1439 }
1440}
1441
1442impl core::ops::Index<usize> for JbmDict<'_> {
1443 type Output = Jbm;
1444 #[inline(always)]
1445 fn index(&self, index: usize) -> &Jbm {
1446 let n = self.shared.len();
1447 if index < n {
1448 &self.shared[index]
1449 } else {
1450 &self.local[index - n]
1451 }
1452 }
1453}
1454
1455pub struct Jb2Dict {
1464 symbols: Vec<Jbm>,
1465}
1466
1467pub fn decode(data: &[u8], shared_dict: Option<&Jb2Dict>) -> Result<Bitmap, Jb2Error> {
1476 decode_image(data, shared_dict)
1477}
1478
1479pub fn decode_downsampled(
1498 data: &[u8],
1499 shared_dict: Option<&Jb2Dict>,
1500 shift: u32,
1501) -> Result<Bitmap, Jb2Error> {
1502 let mut pool = Vec::new();
1503 decode_image_with_pool(data, shared_dict, &mut pool, shift)
1504}
1505
1506pub fn decode_indexed(
1513 data: &[u8],
1514 shared_dict: Option<&Jb2Dict>,
1515) -> Result<(Bitmap, Vec<i32>), Jb2Error> {
1516 decode_image_indexed(data, shared_dict)
1517}
1518
1519pub fn decode_dict(data: &[u8], inherited: Option<&Jb2Dict>) -> Result<Jb2Dict, Jb2Error> {
1528 decode_dictionary(data, inherited)
1529}
1530
1531fn decode_image(data: &[u8], shared_dict: Option<&Jb2Dict>) -> Result<Bitmap, Jb2Error> {
1536 let mut pool = Vec::new();
1537 decode_image_with_pool(data, shared_dict, &mut pool, 0)
1538}
1539
1540fn decode_image_with_pool(
1551 data: &[u8],
1552 shared_dict: Option<&Jb2Dict>,
1553 pool: &mut Vec<u8>,
1554 shift: u32,
1555) -> Result<Bitmap, Jb2Error> {
1556 let mut zp = ZpDecoder::new(data).map_err(|_| Jb2Error::ZpInitFailed)?;
1557
1558 let mut record_type_ctx = NumContext::new();
1560 let mut image_size_ctx = NumContext::new();
1561 let mut symbol_width_ctx = NumContext::new();
1562 let mut symbol_height_ctx = NumContext::new();
1563 let mut inherit_dict_size_ctx = NumContext::new();
1564 let mut coord_ctx = CoordContexts::new();
1565 let mut symbol_index_ctx = NumContext::new();
1566 let mut symbol_width_diff_ctx = NumContext::new();
1567 let mut symbol_height_diff_ctx = NumContext::new();
1568 let mut horiz_abs_loc_ctx = NumContext::new();
1569 let mut vert_abs_loc_ctx = NumContext::new();
1570 let mut comment_length_ctx = NumContext::new();
1571 let mut comment_octet_ctx = NumContext::new();
1572
1573 let mut direct_bitmap_ctx = [0u8; 1024];
1574 let mut refinement_bitmap_ctx = [0u8; 2048];
1575 let mut refinement_bitmap_ctx_p = [0x8000u16; 2048];
1576 let mut total_sym_pixels = 0usize;
1577 let mut total_blit_pixels = 0usize;
1578
1579 let mut rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1582 let mut initial_dict_length: usize = 0;
1583 if rtype == 9 {
1584 initial_dict_length = decode_num(&mut zp, &mut inherit_dict_size_ctx, 0, 262142) as usize;
1585 rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1586 }
1587 let _ = rtype;
1589
1590 let image_width = {
1592 let w = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
1593 if w == 0 { 200 } else { w }
1594 };
1595 let image_height = {
1596 let h = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
1597 if h == 0 { 200 } else { h }
1598 };
1599
1600 let mut flag_ctx: u8 = 0;
1602 if zp.decode_bit(&mut flag_ctx) {
1603 return Err(Jb2Error::BadHeaderFlag);
1604 }
1605
1606 let initial_symbols: &[Jbm] = if initial_dict_length > 0 {
1609 match shared_dict {
1610 Some(sd) => {
1611 if initial_dict_length > sd.symbols.len() {
1612 return Err(Jb2Error::InheritedDictTooLarge);
1613 }
1614 &sd.symbols[..initial_dict_length]
1615 }
1616 None => return Err(Jb2Error::MissingSharedDict),
1617 }
1618 } else {
1619 &[]
1620 };
1621 let mut dict = JbmDict::new(initial_symbols);
1622
1623 const MAX_PIXELS: usize = 64 * 1024 * 1024;
1625 let page_size = (image_width as usize).saturating_mul(image_height as usize);
1626 if page_size > MAX_PIXELS {
1627 return Err(Jb2Error::ImageTooLarge);
1628 }
1629 let (page_w, page_h) = if shift == 0 {
1638 (image_width as u32, image_height as u32)
1639 } else {
1640 (
1641 (image_width as u32).div_ceil(1u32 << shift),
1642 (image_height as u32).div_ceil(1u32 << shift),
1643 )
1644 };
1645 let mut page_bm = Bitmap::new(page_w, page_h);
1646 let blit = |page_bm: &mut Bitmap, sym: &Jbm, x: i32, y: i32| {
1650 if shift == 0 {
1651 blit_to_bitmap(page_bm, sym, x, y);
1652 } else {
1653 blit_to_bitmap_downsampled(page_bm, sym, x, y, image_width, image_height, shift);
1654 }
1655 };
1656
1657 let mut layout = LayoutState::new(image_height);
1658
1659 let max_sym_px = MAX_PAGE_SYMBOL_PIXELS;
1661 let mut record_count = 0usize;
1662 loop {
1663 if zp.synthetic_bytes() > ZP_EOF_SLACK_BYTES {
1664 return Err(Jb2Error::Truncated);
1672 }
1673 if record_count >= MAX_RECORDS {
1674 return Err(Jb2Error::TooManyRecords);
1675 }
1676 record_count += 1;
1677 let rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1678
1679 match rtype {
1680 1 => {
1682 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1683 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1684 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels, max_sym_px)?;
1685 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1686 let (x, y) =
1687 decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
1688 check_blit_budget(&bm, &mut total_blit_pixels)?;
1689 blit(&mut page_bm, &bm, x, y);
1690 dict.push(bm.crop_and_recycle(pool));
1691 }
1692
1693 2 => {
1695 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1696 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1697 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels, max_sym_px)?;
1698 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1699 dict.push(bm.crop_and_recycle(pool));
1700 }
1701
1702 3 => {
1704 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1705 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1706 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels, max_sym_px)?;
1707 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1708 let (x, y) =
1709 decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
1710 check_blit_budget(&bm, &mut total_blit_pixels)?;
1711 blit(&mut page_bm, &bm, x, y);
1712 bm.recycle_into(pool);
1713 }
1714
1715 4 => {
1717 if dict.is_empty() {
1718 return Err(Jb2Error::EmptyDictReference);
1719 }
1720 let index =
1721 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1722 if index >= dict.len() {
1723 return Err(Jb2Error::InvalidSymbolIndex);
1724 }
1725 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
1726 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
1727 let cbm_w = dict[index].width + wdiff;
1728 let cbm_h = dict[index].height + hdiff;
1729 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels, max_sym_px)?;
1730 let cbm = decode_bitmap_ref(
1731 &mut zp,
1732 &mut refinement_bitmap_ctx,
1733 &mut refinement_bitmap_ctx_p,
1734 cbm_w,
1735 cbm_h,
1736 &dict[index],
1737 pool,
1738 )?;
1739 let (x, y) = decode_symbol_coords(
1740 &mut zp,
1741 &mut coord_ctx,
1742 &mut layout,
1743 cbm.width,
1744 cbm.height,
1745 );
1746 check_blit_budget(&cbm, &mut total_blit_pixels)?;
1747 blit(&mut page_bm, &cbm, x, y);
1748 dict.push(cbm.crop_and_recycle(pool));
1749 }
1750
1751 5 => {
1753 if dict.is_empty() {
1754 return Err(Jb2Error::EmptyDictReference);
1755 }
1756 let index =
1757 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1758 if index >= dict.len() {
1759 return Err(Jb2Error::InvalidSymbolIndex);
1760 }
1761 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
1762 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
1763 let cbm_w = dict[index].width + wdiff;
1764 let cbm_h = dict[index].height + hdiff;
1765 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels, max_sym_px)?;
1766 let cbm = decode_bitmap_ref(
1767 &mut zp,
1768 &mut refinement_bitmap_ctx,
1769 &mut refinement_bitmap_ctx_p,
1770 cbm_w,
1771 cbm_h,
1772 &dict[index],
1773 pool,
1774 )?;
1775 dict.push(cbm.crop_and_recycle(pool));
1776 }
1777
1778 6 => {
1780 if dict.is_empty() {
1781 return Err(Jb2Error::EmptyDictReference);
1782 }
1783 let index =
1784 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1785 if index >= dict.len() {
1786 return Err(Jb2Error::InvalidSymbolIndex);
1787 }
1788 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
1789 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
1790 let cbm_w = dict[index].width + wdiff;
1791 let cbm_h = dict[index].height + hdiff;
1792 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels, max_sym_px)?;
1793 let cbm = decode_bitmap_ref(
1794 &mut zp,
1795 &mut refinement_bitmap_ctx,
1796 &mut refinement_bitmap_ctx_p,
1797 cbm_w,
1798 cbm_h,
1799 &dict[index],
1800 pool,
1801 )?;
1802 let (x, y) = decode_symbol_coords(
1803 &mut zp,
1804 &mut coord_ctx,
1805 &mut layout,
1806 cbm.width,
1807 cbm.height,
1808 );
1809 check_blit_budget(&cbm, &mut total_blit_pixels)?;
1810 blit(&mut page_bm, &cbm, x, y);
1811 cbm.recycle_into(pool);
1812 }
1813
1814 7 => {
1816 if dict.is_empty() {
1817 return Err(Jb2Error::EmptyDictReference);
1818 }
1819 let index =
1820 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
1821 if index >= dict.len() {
1822 return Err(Jb2Error::InvalidSymbolIndex);
1823 }
1824 let bm_w = dict[index].width;
1825 let bm_h = dict[index].height;
1826 let (x, y) = decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm_w, bm_h);
1827 let sym = &dict[index];
1828 check_blit_budget(sym, &mut total_blit_pixels)?;
1829 blit(&mut page_bm, sym, x, y);
1830 }
1831
1832 8 => {
1834 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1835 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1836 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels, max_sym_px)?;
1837 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1838 let left = decode_num(&mut zp, &mut horiz_abs_loc_ctx, 1, image_width);
1839 let top = decode_num(&mut zp, &mut vert_abs_loc_ctx, 1, image_height);
1840 let x = left - 1;
1841 let y = top - h;
1842 check_blit_budget(&bm, &mut total_blit_pixels)?;
1843 blit(&mut page_bm, &bm, x, y);
1844 bm.recycle_into(pool);
1845 }
1846
1847 9 => {}
1849
1850 10 => {
1852 let length = decode_num(&mut zp, &mut comment_length_ctx, 0, 262142) as usize;
1853 for _ in 0..length {
1858 decode_num(&mut zp, &mut comment_octet_ctx, 0, 255);
1859 }
1860 }
1861
1862 11 => break,
1864
1865 _ => return Err(Jb2Error::UnknownRecordType),
1866 }
1867 }
1868
1869 Ok(page_bm)
1870}
1871
1872fn decode_image_indexed(
1874 data: &[u8],
1875 shared_dict: Option<&Jb2Dict>,
1876) -> Result<(Bitmap, Vec<i32>), Jb2Error> {
1877 let mut pool = Vec::new();
1878 decode_image_indexed_with_pool(data, shared_dict, &mut pool)
1879}
1880
1881fn decode_image_indexed_with_pool(
1882 data: &[u8],
1883 shared_dict: Option<&Jb2Dict>,
1884 pool: &mut Vec<u8>,
1885) -> Result<(Bitmap, Vec<i32>), Jb2Error> {
1886 let mut zp = ZpDecoder::new(data).map_err(|_| Jb2Error::ZpInitFailed)?;
1887
1888 let mut record_type_ctx = NumContext::new();
1889 let mut image_size_ctx = NumContext::new();
1890 let mut symbol_width_ctx = NumContext::new();
1891 let mut symbol_height_ctx = NumContext::new();
1892 let mut inherit_dict_size_ctx = NumContext::new();
1893 let mut coord_ctx = CoordContexts::new();
1894 let mut symbol_index_ctx = NumContext::new();
1895 let mut symbol_width_diff_ctx = NumContext::new();
1896 let mut symbol_height_diff_ctx = NumContext::new();
1897 let mut horiz_abs_loc_ctx = NumContext::new();
1898 let mut vert_abs_loc_ctx = NumContext::new();
1899 let mut comment_length_ctx = NumContext::new();
1900 let mut comment_octet_ctx = NumContext::new();
1901
1902 let mut direct_bitmap_ctx = [0u8; 1024];
1903 let mut refinement_bitmap_ctx = [0u8; 2048];
1904 let mut refinement_bitmap_ctx_p = [0x8000u16; 2048];
1905 let mut total_sym_pixels = 0usize;
1906 let mut total_blit_pixels = 0usize;
1907
1908 let mut rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1909 let mut initial_dict_length: usize = 0;
1910 if rtype == 9 {
1911 initial_dict_length = decode_num(&mut zp, &mut inherit_dict_size_ctx, 0, 262142) as usize;
1912 rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1913 }
1914 let _ = rtype;
1915
1916 let image_width = {
1917 let w = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
1918 if w == 0 { 200 } else { w }
1919 };
1920 let image_height = {
1921 let h = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
1922 if h == 0 { 200 } else { h }
1923 };
1924
1925 let mut flag_ctx: u8 = 0;
1926 if zp.decode_bit(&mut flag_ctx) {
1927 return Err(Jb2Error::BadHeaderFlag);
1928 }
1929
1930 let initial_symbols_idx: &[Jbm] = if initial_dict_length > 0 {
1931 match shared_dict {
1932 Some(sd) => {
1933 if initial_dict_length > sd.symbols.len() {
1934 return Err(Jb2Error::InheritedDictTooLarge);
1935 }
1936 &sd.symbols[..initial_dict_length]
1937 }
1938 None => return Err(Jb2Error::MissingSharedDict),
1939 }
1940 } else {
1941 &[]
1942 };
1943 let mut dict = JbmDict::new(initial_symbols_idx);
1944
1945 const MAX_PIXELS: usize = 64 * 1024 * 1024;
1946 let page_size = (image_width as usize).saturating_mul(image_height as usize);
1947 if page_size > MAX_PIXELS {
1948 return Err(Jb2Error::ImageTooLarge);
1949 }
1950 let mut page = vec![0u8; page_size];
1951 let mut blit_map = vec![-1i32; page_size];
1952
1953 let mut layout = LayoutState::new(image_height);
1954 let mut blit_count: i32 = 0;
1955
1956 let max_sym_px = MAX_PAGE_SYMBOL_PIXELS;
1957 let mut record_count = 0usize;
1958 loop {
1959 if zp.synthetic_bytes() > ZP_EOF_SLACK_BYTES {
1960 return Err(Jb2Error::Truncated);
1968 }
1969 if record_count >= MAX_RECORDS {
1970 return Err(Jb2Error::TooManyRecords);
1971 }
1972 record_count += 1;
1973 let rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
1974
1975 match rtype {
1976 1 => {
1977 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1978 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
1979 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels, max_sym_px)?;
1980 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
1981 let (x, y) =
1982 decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
1983 check_blit_budget(&bm, &mut total_blit_pixels)?;
1984 blit_indexed(
1985 &mut page,
1986 &mut blit_map,
1987 image_width,
1988 image_height,
1989 &bm,
1990 x,
1991 y,
1992 blit_count,
1993 );
1994 blit_count += 1;
1995 dict.push(bm.crop_and_recycle(pool));
1996 }
1997 2 => {
1998 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
1999 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
2000 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels, max_sym_px)?;
2001 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
2002 dict.push(bm.crop_and_recycle(pool));
2003 }
2004 3 => {
2005 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
2006 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
2007 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels, max_sym_px)?;
2008 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
2009 let (x, y) =
2010 decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
2011 check_blit_budget(&bm, &mut total_blit_pixels)?;
2012 blit_indexed(
2013 &mut page,
2014 &mut blit_map,
2015 image_width,
2016 image_height,
2017 &bm,
2018 x,
2019 y,
2020 blit_count,
2021 );
2022 blit_count += 1;
2023 bm.recycle_into(pool);
2024 }
2025 4 => {
2026 if dict.is_empty() {
2027 return Err(Jb2Error::EmptyDictReference);
2028 }
2029 let index =
2030 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
2031 if index >= dict.len() {
2032 return Err(Jb2Error::InvalidSymbolIndex);
2033 }
2034 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
2035 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
2036 let cbm_w = dict[index].width + wdiff;
2037 let cbm_h = dict[index].height + hdiff;
2038 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels, max_sym_px)?;
2039 let cbm = decode_bitmap_ref(
2040 &mut zp,
2041 &mut refinement_bitmap_ctx,
2042 &mut refinement_bitmap_ctx_p,
2043 cbm_w,
2044 cbm_h,
2045 &dict[index],
2046 pool,
2047 )?;
2048 let (x, y) = decode_symbol_coords(
2049 &mut zp,
2050 &mut coord_ctx,
2051 &mut layout,
2052 cbm.width,
2053 cbm.height,
2054 );
2055 check_blit_budget(&cbm, &mut total_blit_pixels)?;
2056 blit_indexed(
2057 &mut page,
2058 &mut blit_map,
2059 image_width,
2060 image_height,
2061 &cbm,
2062 x,
2063 y,
2064 blit_count,
2065 );
2066 blit_count += 1;
2067 dict.push(cbm.crop_and_recycle(pool));
2068 }
2069 5 => {
2070 if dict.is_empty() {
2071 return Err(Jb2Error::EmptyDictReference);
2072 }
2073 let index =
2074 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
2075 if index >= dict.len() {
2076 return Err(Jb2Error::InvalidSymbolIndex);
2077 }
2078 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
2079 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
2080 let cbm_w = dict[index].width + wdiff;
2081 let cbm_h = dict[index].height + hdiff;
2082 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels, max_sym_px)?;
2083 let cbm = decode_bitmap_ref(
2084 &mut zp,
2085 &mut refinement_bitmap_ctx,
2086 &mut refinement_bitmap_ctx_p,
2087 cbm_w,
2088 cbm_h,
2089 &dict[index],
2090 pool,
2091 )?;
2092 dict.push(cbm.crop_and_recycle(pool));
2093 }
2094 6 => {
2095 if dict.is_empty() {
2096 return Err(Jb2Error::EmptyDictReference);
2097 }
2098 let index =
2099 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
2100 if index >= dict.len() {
2101 return Err(Jb2Error::InvalidSymbolIndex);
2102 }
2103 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
2104 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
2105 let cbm_w = dict[index].width + wdiff;
2106 let cbm_h = dict[index].height + hdiff;
2107 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels, max_sym_px)?;
2108 let cbm = decode_bitmap_ref(
2109 &mut zp,
2110 &mut refinement_bitmap_ctx,
2111 &mut refinement_bitmap_ctx_p,
2112 cbm_w,
2113 cbm_h,
2114 &dict[index],
2115 pool,
2116 )?;
2117 let (x, y) = decode_symbol_coords(
2118 &mut zp,
2119 &mut coord_ctx,
2120 &mut layout,
2121 cbm.width,
2122 cbm.height,
2123 );
2124 check_blit_budget(&cbm, &mut total_blit_pixels)?;
2125 blit_indexed(
2126 &mut page,
2127 &mut blit_map,
2128 image_width,
2129 image_height,
2130 &cbm,
2131 x,
2132 y,
2133 blit_count,
2134 );
2135 blit_count += 1;
2136 cbm.recycle_into(pool);
2137 }
2138 7 => {
2139 if dict.is_empty() {
2140 return Err(Jb2Error::EmptyDictReference);
2141 }
2142 let index =
2143 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
2144 if index >= dict.len() {
2145 return Err(Jb2Error::InvalidSymbolIndex);
2146 }
2147 let (x, y) = decode_symbol_coords(
2148 &mut zp,
2149 &mut coord_ctx,
2150 &mut layout,
2151 dict[index].width,
2152 dict[index].height,
2153 );
2154 check_blit_budget(&dict[index], &mut total_blit_pixels)?;
2155 blit_indexed(
2156 &mut page,
2157 &mut blit_map,
2158 image_width,
2159 image_height,
2160 &dict[index],
2161 x,
2162 y,
2163 blit_count,
2164 );
2165 blit_count += 1;
2166 }
2167 8 => {
2168 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
2169 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
2170 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels, max_sym_px)?;
2171 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
2172 let left = decode_num(&mut zp, &mut horiz_abs_loc_ctx, 1, image_width);
2173 let top = decode_num(&mut zp, &mut vert_abs_loc_ctx, 1, image_height);
2174 check_blit_budget(&bm, &mut total_blit_pixels)?;
2175 blit_indexed(
2176 &mut page,
2177 &mut blit_map,
2178 image_width,
2179 image_height,
2180 &bm,
2181 left - 1,
2182 top - h,
2183 blit_count,
2184 );
2185 blit_count += 1;
2186 bm.recycle_into(pool);
2187 }
2188 9 => {}
2189 10 => {
2190 let length = decode_num(&mut zp, &mut comment_length_ctx, 0, 262142) as usize;
2191 for _ in 0..length {
2196 decode_num(&mut zp, &mut comment_octet_ctx, 0, 255);
2197 }
2198 }
2199 11 => break,
2200 _ => return Err(Jb2Error::UnknownRecordType),
2201 }
2202 }
2203
2204 let bm = page_to_bitmap(&page, image_width, image_height);
2205 flip_blit_map(&mut blit_map, image_width as usize, image_height as usize);
2206 Ok((bm, blit_map))
2207}
2208
2209fn decode_dictionary(data: &[u8], inherited: Option<&Jb2Dict>) -> Result<Jb2Dict, Jb2Error> {
2214 let mut pool: Vec<u8> = Vec::new();
2215 decode_dictionary_with_pool(data, inherited, &mut pool)
2216}
2217
2218fn decode_dictionary_with_pool(
2219 data: &[u8],
2220 inherited: Option<&Jb2Dict>,
2221 pool: &mut Vec<u8>,
2222) -> Result<Jb2Dict, Jb2Error> {
2223 let mut zp = ZpDecoder::new(data).map_err(|_| Jb2Error::ZpInitFailed)?;
2224
2225 let mut record_type_ctx = NumContext::new();
2226 let mut image_size_ctx = NumContext::new();
2227 let mut symbol_width_ctx = NumContext::new();
2228 let mut symbol_height_ctx = NumContext::new();
2229 let mut inherit_dict_size_ctx = NumContext::new();
2230 let mut symbol_index_ctx = NumContext::new();
2231 let mut symbol_width_diff_ctx = NumContext::new();
2232 let mut symbol_height_diff_ctx = NumContext::new();
2233 let mut comment_length_ctx = NumContext::new();
2234 let mut comment_octet_ctx = NumContext::new();
2235
2236 let mut direct_bitmap_ctx = [0u8; 1024];
2237 let mut refinement_bitmap_ctx = [0u8; 2048];
2238 let mut refinement_bitmap_ctx_p = [0x8000u16; 2048];
2239 let mut total_sym_pixels = 0usize;
2240
2241 let mut rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
2243 let mut initial_dict_length: usize = 0;
2244 if rtype == 9 {
2245 initial_dict_length = decode_num(&mut zp, &mut inherit_dict_size_ctx, 0, 262142) as usize;
2246 rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
2247 }
2248 let _ = rtype;
2249
2250 let _dict_width = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
2252 let _dict_height = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
2253
2254 let mut flag_ctx: u8 = 0;
2256 if zp.decode_bit(&mut flag_ctx) {
2257 return Err(Jb2Error::BadHeaderFlag);
2258 }
2259
2260 let initial_inh: &[Jbm] = if initial_dict_length > 0 {
2261 match inherited {
2262 Some(inh) => {
2263 if initial_dict_length > inh.symbols.len() {
2264 return Err(Jb2Error::InheritedDictTooLarge);
2265 }
2266 &inh.symbols[..initial_dict_length]
2267 }
2268 None => return Err(Jb2Error::MissingSharedDict),
2269 }
2270 } else {
2271 &[]
2272 };
2273 let mut dict = JbmDict::new(initial_inh);
2274
2275 let max_sym_px = MAX_TOTAL_SYMBOL_PIXELS;
2277 let mut record_count = 0usize;
2278 loop {
2279 if zp.synthetic_bytes() > ZP_EOF_SLACK_BYTES {
2280 return Err(Jb2Error::Truncated);
2288 }
2289 if record_count >= MAX_RECORDS {
2290 return Err(Jb2Error::TooManyRecords);
2291 }
2292 record_count += 1;
2293 let rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
2294
2295 match rtype {
2296 2 => {
2298 let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
2299 let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
2300 check_symbol_decode_budget(&zp, w, h, &mut total_sym_pixels, max_sym_px)?;
2301 let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
2302 dict.push(bm.crop_and_recycle(pool));
2303 }
2304
2305 5 => {
2307 if dict.is_empty() {
2308 return Err(Jb2Error::EmptyDictReference);
2309 }
2310 let index =
2311 decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
2312 if index >= dict.len() {
2313 return Err(Jb2Error::InvalidSymbolIndex);
2314 }
2315 let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
2316 let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
2317 let cbm_w = dict[index].width + wdiff;
2318 let cbm_h = dict[index].height + hdiff;
2319 check_symbol_decode_budget(&zp, cbm_w, cbm_h, &mut total_sym_pixels, max_sym_px)?;
2320 let cbm = decode_bitmap_ref(
2321 &mut zp,
2322 &mut refinement_bitmap_ctx,
2323 &mut refinement_bitmap_ctx_p,
2324 cbm_w,
2325 cbm_h,
2326 &dict[index],
2327 pool,
2328 )?;
2329 dict.push(cbm.crop_and_recycle(pool));
2330 }
2331
2332 9 => {}
2334
2335 10 => {
2337 let length = decode_num(&mut zp, &mut comment_length_ctx, 0, 262142) as usize;
2338 for _ in 0..length {
2343 decode_num(&mut zp, &mut comment_octet_ctx, 0, 255);
2344 }
2345 }
2346
2347 11 => break,
2349
2350 _ => return Err(Jb2Error::UnexpectedDictRecordType),
2351 }
2352 }
2353
2354 Ok(Jb2Dict {
2355 symbols: dict.into_symbols(),
2356 })
2357}
2358
2359#[cfg(test)]
2364mod tests {
2365 use super::*;
2366
2367 fn assets_path() -> std::path::PathBuf {
2368 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
2369 .join("../../references/djvujs/library/assets")
2370 }
2371
2372 fn golden_path() -> std::path::PathBuf {
2373 std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tests/golden/jb2")
2374 }
2375
2376 fn extract_sjbz(djvu_data: &[u8]) -> Vec<u8> {
2379 let file = djvu_iff::parse(djvu_data).unwrap();
2380 let sjbz = file.root.find_first(b"Sjbz").unwrap();
2381 sjbz.data().to_vec()
2382 }
2383
2384 fn extract_first_page_sjbz(djvu_data: &[u8]) -> Vec<u8> {
2385 let file = djvu_iff::parse(djvu_data).unwrap();
2386 let page_form = file
2387 .root
2388 .children()
2389 .iter()
2390 .find(|c| {
2391 matches!(c, djvu_iff::Chunk::Form { secondary_id, .. }
2392 if secondary_id == b"DJVU")
2393 })
2394 .expect("no DJVU form");
2395 page_form.find_first(b"Sjbz").unwrap().data().to_vec()
2396 }
2397
2398 fn find_page_form_data(djvu_data: &[u8], page: usize) -> Vec<u8> {
2399 let file = djvu_iff::parse(djvu_data).unwrap();
2400 let mut idx = 0;
2401 for chunk in file.root.children() {
2402 if matches!(chunk, djvu_iff::Chunk::Form { secondary_id, .. }
2403 if secondary_id == b"DJVU")
2404 {
2405 if idx == page {
2406 return chunk.find_first(b"Sjbz").unwrap().data().to_vec();
2407 }
2408 idx += 1;
2409 }
2410 }
2411 panic!("page {} not found", page);
2412 }
2413
2414 fn find_djvi_djbz_data(djvu_data: &[u8]) -> Vec<u8> {
2415 let file = djvu_iff::parse(djvu_data).unwrap();
2416 for chunk in file.root.children() {
2417 if let djvu_iff::Chunk::Form { secondary_id, .. } = chunk
2418 && secondary_id == b"DJVI"
2419 && let Some(djbz) = chunk.find_first(b"Djbz")
2420 {
2421 return djbz.data().to_vec();
2422 }
2423 }
2424 panic!("DJVI with Djbz not found");
2425 }
2426
2427 #[test]
2432 fn jb2_new_decode_boy_jb2_mask() {
2433 let djvu = std::fs::read(assets_path().join("boy_jb2.djvu")).unwrap();
2434 let sjbz = extract_sjbz(&djvu);
2435 let bitmap = decode(&sjbz, None).unwrap();
2436 let actual_pbm = bitmap.to_pbm();
2437 let expected_pbm = std::fs::read(golden_path().join("boy_jb2_mask.pbm")).unwrap();
2438 assert_eq!(
2439 actual_pbm.len(),
2440 expected_pbm.len(),
2441 "PBM size mismatch: got {} expected {}",
2442 actual_pbm.len(),
2443 expected_pbm.len()
2444 );
2445 assert_eq!(actual_pbm, expected_pbm, "boy_jb2_mask pixel mismatch");
2446 }
2447
2448 #[test]
2449 fn jb2_new_decode_carte_p1_mask() {
2450 let djvu = std::fs::read(assets_path().join("carte.djvu")).unwrap();
2451 let sjbz = extract_first_page_sjbz(&djvu);
2452 let bitmap = decode(&sjbz, None).unwrap();
2453 let actual_pbm = bitmap.to_pbm();
2454 let expected_pbm = std::fs::read(golden_path().join("carte_p1_mask.pbm")).unwrap();
2455 assert_eq!(
2456 actual_pbm.len(),
2457 expected_pbm.len(),
2458 "carte_p1_mask size mismatch"
2459 );
2460 assert_eq!(actual_pbm, expected_pbm, "carte_p1_mask pixel mismatch");
2461 }
2462
2463 #[test]
2464 fn jb2_new_decode_djvu3spec_p1_mask() {
2465 let djvu = std::fs::read(assets_path().join("DjVu3Spec_bundled.djvu")).unwrap();
2466 let file = djvu_iff::parse(&djvu).unwrap();
2467
2468 let mut idx = 0usize;
2470 let mut page_form_opt: Option<&djvu_iff::Chunk> = None;
2471 for chunk in file.root.children() {
2472 if matches!(chunk, djvu_iff::Chunk::Form { secondary_id, .. }
2473 if secondary_id == b"DJVU")
2474 {
2475 if idx == 0 {
2476 page_form_opt = Some(chunk);
2477 break;
2478 }
2479 idx += 1;
2480 }
2481 }
2482 let page_form = page_form_opt.expect("page 0 not found");
2483 let djbz_data = page_form.find_first(b"Djbz").unwrap().data().to_vec();
2484 let sjbz_data = page_form.find_first(b"Sjbz").unwrap().data().to_vec();
2485
2486 let shared_dict = decode_dict(&djbz_data, None).unwrap();
2487 let bitmap = decode(&sjbz_data, Some(&shared_dict)).unwrap();
2488 let actual_pbm = bitmap.to_pbm();
2489 let expected_pbm = std::fs::read(golden_path().join("djvu3spec_p1_mask.pbm")).unwrap();
2490 assert_eq!(
2491 actual_pbm.len(),
2492 expected_pbm.len(),
2493 "djvu3spec_p1_mask size mismatch"
2494 );
2495 assert_eq!(actual_pbm, expected_pbm, "djvu3spec_p1_mask pixel mismatch");
2496 }
2497
2498 #[test]
2499 fn jb2_new_decode_djvu3spec_p2_mask() {
2500 let djvu = std::fs::read(assets_path().join("DjVu3Spec_bundled.djvu")).unwrap();
2501 let djbz_data = find_djvi_djbz_data(&djvu);
2502 let sjbz_data = find_page_form_data(&djvu, 1);
2503
2504 let shared_dict = decode_dict(&djbz_data, None).unwrap();
2505 let bitmap = decode(&sjbz_data, Some(&shared_dict)).unwrap();
2506 let actual_pbm = bitmap.to_pbm();
2507 let expected_pbm = std::fs::read(golden_path().join("djvu3spec_p2_mask.pbm")).unwrap();
2508 assert_eq!(
2509 actual_pbm.len(),
2510 expected_pbm.len(),
2511 "djvu3spec_p2_mask size mismatch"
2512 );
2513 assert_eq!(actual_pbm, expected_pbm, "djvu3spec_p2_mask pixel mismatch");
2514 }
2515
2516 #[test]
2517 fn jb2_new_decode_navm_fgbz_p1_mask() {
2518 let djvu = std::fs::read(assets_path().join("navm_fgbz.djvu")).unwrap();
2519 let djbz_data = find_djvi_djbz_data(&djvu);
2520 let sjbz_data = find_page_form_data(&djvu, 0);
2521
2522 let shared_dict = decode_dict(&djbz_data, None).unwrap();
2523 let bitmap = decode(&sjbz_data, Some(&shared_dict)).unwrap();
2524 let actual_pbm = bitmap.to_pbm();
2525 let expected_pbm = std::fs::read(golden_path().join("navm_fgbz_p1_mask.pbm")).unwrap();
2526 assert_eq!(
2527 actual_pbm.len(),
2528 expected_pbm.len(),
2529 "navm_fgbz_p1_mask size mismatch"
2530 );
2531 assert_eq!(actual_pbm, expected_pbm, "navm_fgbz_p1_mask pixel mismatch");
2532 }
2533
2534 #[test]
2537 fn jb2_new_empty_input_does_not_panic() {
2538 let _ = decode(&[], None);
2539 }
2540
2541 #[test]
2542 fn jb2_new_single_byte_does_not_panic() {
2543 let _ = decode(&[0x00], None);
2544 }
2545
2546 #[test]
2547 fn jb2_new_all_zeros_does_not_panic() {
2548 let _ = decode(&[0u8; 64], None);
2549 }
2550
2551 #[test]
2552 fn jb2_new_dict_empty_input_does_not_panic() {
2553 let _ = decode_dict(&[], None);
2554 }
2555
2556 #[test]
2557 fn jb2_new_dict_truncated_does_not_panic() {
2558 let _ = decode_dict(&[0u8; 8], None);
2559 }
2560
2561 #[test]
2564 fn jb2_error_variants_have_meaningful_messages() {
2565 assert!(Jb2Error::BadHeaderFlag.to_string().contains("flag"));
2566 assert!(Jb2Error::InheritedDictTooLarge.to_string().contains("dict"));
2567 assert!(Jb2Error::MissingSharedDict.to_string().contains("dict"));
2568 assert!(Jb2Error::ImageTooLarge.to_string().contains("large"));
2569 assert!(Jb2Error::EmptyDictReference.to_string().contains("dict"));
2570 assert!(Jb2Error::InvalidSymbolIndex.to_string().contains("symbol"));
2571 assert!(Jb2Error::UnknownRecordType.to_string().contains("record"));
2572 assert!(
2573 Jb2Error::UnexpectedDictRecordType
2574 .to_string()
2575 .contains("record")
2576 );
2577 assert!(Jb2Error::ZpInitFailed.to_string().contains("ZP"));
2578 assert!(Jb2Error::Truncated.to_string().contains("truncated"));
2579 }
2580
2581 #[test]
2583 fn jb2_image_size_overflow_guard() {
2584 let w: usize = 65536;
2585 let h: usize = 65537;
2586 let safe_size = w.saturating_mul(h);
2587 assert!(
2588 safe_size > 64 * 1024 * 1024,
2589 "saturating_mul must exceed MAX_PIXELS"
2590 );
2591 }
2592
2593 #[test]
2596 fn test_decode_empty_data() {
2597 let result = decode(&[], None);
2598 assert!(result.is_err());
2599 }
2600
2601 #[test]
2602 fn test_decode_dict_empty() {
2603 let result = decode_dict(&[], None);
2604 assert!(result.is_err());
2605 }
2606
2607 #[test]
2608 fn test_decode_indexed_empty() {
2609 let result = decode_indexed(&[], None);
2610 assert!(result.is_err());
2611 }
2612
2613 #[test]
2617 fn blit_negative_width_does_not_hang() {
2618 let start = std::time::Instant::now();
2619 let _ = decode(&[0x7e, 0x00, 0x0c], None);
2620 assert!(start.elapsed().as_secs() < 2, "took {:?}", start.elapsed());
2621 }
2622
2623 #[test]
2629 fn jb2_pool_decode_matches_regular_decode_carte() {
2630 let djvu = std::fs::read(assets_path().join("carte.djvu")).unwrap();
2631 let sjbz = extract_first_page_sjbz(&djvu);
2632
2633 let regular = decode(&sjbz, None).expect("regular decode");
2634
2635 let mut pool = Vec::new();
2636 let pooled = decode_image_with_pool(&sjbz, None, &mut pool, 0).expect("pool decode");
2637
2638 assert_eq!(regular.width, pooled.width, "width must match");
2639 assert_eq!(regular.height, pooled.height, "height must match");
2640 assert_eq!(regular.data, pooled.data, "pixel data must be identical");
2641 assert!(
2642 pool.capacity() > 0,
2643 "pool must have been used (capacity > 0 after decode)"
2644 );
2645 }
2646
2647 #[test]
2653 fn decode_missing_shared_dict_reports_typed_error() {
2654 let djvu = std::fs::read(assets_path().join("DjVu3Spec_bundled.djvu")).unwrap();
2655 let sjbz_data = find_page_form_data(&djvu, 1);
2656 assert!(matches!(
2657 decode(&sjbz_data, None),
2658 Err(Jb2Error::MissingSharedDict)
2659 ));
2660 }
2661
2662 #[test]
2664 fn decode_indexed_missing_shared_dict_reports_typed_error() {
2665 let djvu = std::fs::read(assets_path().join("DjVu3Spec_bundled.djvu")).unwrap();
2666 let sjbz_data = find_page_form_data(&djvu, 1);
2667 assert!(matches!(
2668 decode_indexed(&sjbz_data, None),
2669 Err(Jb2Error::MissingSharedDict)
2670 ));
2671 }
2672
2673 #[test]
2676 fn decode_rejects_shared_dict_shorter_than_declared() {
2677 let djvu = std::fs::read(assets_path().join("DjVu3Spec_bundled.djvu")).unwrap();
2678 let djbz_data = find_djvi_djbz_data(&djvu);
2679 let sjbz_data = find_page_form_data(&djvu, 1);
2680 let full_dict = decode_dict(&djbz_data, None).unwrap();
2681 assert!(
2682 !full_dict.symbols.is_empty(),
2683 "fixture must declare a non-empty shared dict"
2684 );
2685 let short_dict = Jb2Dict {
2686 symbols: full_dict.symbols[..full_dict.symbols.len() - 1].to_vec(),
2687 };
2688 assert!(matches!(
2689 decode(&sjbz_data, Some(&short_dict)),
2690 Err(Jb2Error::InheritedDictTooLarge)
2691 ));
2692 }
2693
2694 #[test]
2699 fn decode_indexed_matches_decode_for_dict_free_fixture() {
2700 let djvu = std::fs::read(assets_path().join("carte.djvu")).unwrap();
2701 let sjbz = extract_first_page_sjbz(&djvu);
2702 let plain = decode(&sjbz, None).unwrap();
2703 let (indexed_bitmap, blit_map) = decode_indexed(&sjbz, None).unwrap();
2704 assert_eq!(plain.width, indexed_bitmap.width);
2705 assert_eq!(plain.height, indexed_bitmap.height);
2706 assert_eq!(plain.data, indexed_bitmap.data);
2707 assert_eq!(blit_map.len(), (plain.width * plain.height) as usize);
2708 for y in 0..plain.height {
2709 for x in 0..plain.width {
2710 let idx = (y * plain.width + x) as usize;
2711 let fg = plain.get(x, y);
2712 assert_eq!(blit_map[idx] >= 0, fg, "pixel ({x},{y}) fg/blit mismatch");
2713 }
2714 }
2715 }
2716
2717 #[test]
2721 fn decode_indexed_matches_decode_for_shared_dict_fixture() {
2722 let djvu = std::fs::read(assets_path().join("DjVu3Spec_bundled.djvu")).unwrap();
2723 let djbz_data = find_djvi_djbz_data(&djvu);
2724 let sjbz_data = find_page_form_data(&djvu, 1);
2725 let shared_dict = decode_dict(&djbz_data, None).unwrap();
2726 let plain = decode(&sjbz_data, Some(&shared_dict)).unwrap();
2727 let (indexed_bitmap, blit_map) = decode_indexed(&sjbz_data, Some(&shared_dict)).unwrap();
2728 assert_eq!(plain.width, indexed_bitmap.width);
2729 assert_eq!(plain.height, indexed_bitmap.height);
2730 assert_eq!(plain.data, indexed_bitmap.data);
2731 assert_eq!(blit_map.len(), (plain.width * plain.height) as usize);
2732 assert!(
2733 blit_map.iter().any(|&b| b >= 0),
2734 "expected at least one foreground blit"
2735 );
2736 }
2737
2738 fn downsample_reference(src: &Bitmap, shift: u32) -> Bitmap {
2744 let block = 1u32 << shift;
2745 let out_w = src.width.div_ceil(block);
2746 let out_h = src.height.div_ceil(block);
2747 let mut out = Bitmap::new(out_w, out_h);
2748 for oy in 0..out_h {
2749 for ox in 0..out_w {
2750 'outer: for dy in 0..block {
2751 for dx in 0..block {
2752 let sx = ox * block + dx;
2753 let sy = oy * block + dy;
2754 if sx < src.width && sy < src.height && src.get(sx, sy) {
2755 out.set(ox, oy, true);
2756 break 'outer;
2757 }
2758 }
2759 }
2760 }
2761 }
2762 out
2763 }
2764
2765 #[test]
2767 fn decode_downsampled_shift0_matches_decode() {
2768 let djvu = std::fs::read(assets_path().join("boy_jb2.djvu")).unwrap();
2769 let sjbz = extract_sjbz(&djvu);
2770 let full = decode(&sjbz, None).unwrap();
2771 let ds0 = decode_downsampled(&sjbz, None, 0).unwrap();
2772 assert_eq!(full.width, ds0.width);
2773 assert_eq!(full.height, ds0.height);
2774 assert_eq!(full.data, ds0.data);
2775 }
2776
2777 #[test]
2781 fn decode_downsampled_matches_full_then_downsample_boy_jb2() {
2782 let djvu = std::fs::read(assets_path().join("boy_jb2.djvu")).unwrap();
2783 let sjbz = extract_sjbz(&djvu);
2784 let full = decode(&sjbz, None).unwrap();
2785 let expected = downsample_reference(&full, 2);
2786 let actual = decode_downsampled(&sjbz, None, 2).unwrap();
2787 assert_eq!(expected.width, actual.width);
2788 assert_eq!(expected.height, actual.height);
2789 assert_eq!(expected.data, actual.data, "downsampled mask mismatch");
2790 }
2791
2792 #[test]
2795 fn decode_downsampled_matches_full_then_downsample_shared_dict() {
2796 let djvu = std::fs::read(assets_path().join("DjVu3Spec_bundled.djvu")).unwrap();
2797 let djbz_data = find_djvi_djbz_data(&djvu);
2798 let sjbz_data = find_page_form_data(&djvu, 1);
2799 let shared_dict = decode_dict(&djbz_data, None).unwrap();
2800 let full = decode(&sjbz_data, Some(&shared_dict)).unwrap();
2801 let expected = downsample_reference(&full, 2);
2802 let actual = decode_downsampled(&sjbz_data, Some(&shared_dict), 2).unwrap();
2803 assert_eq!(expected.width, actual.width);
2804 assert_eq!(expected.height, actual.height);
2805 assert_eq!(expected.data, actual.data, "downsampled mask mismatch");
2806 }
2807
2808 #[test]
2812 fn decode_downsampled_matches_full_then_downsample_shift3() {
2813 let djvu = std::fs::read(assets_path().join("carte.djvu")).unwrap();
2814 let sjbz = extract_first_page_sjbz(&djvu);
2815 let full = decode(&sjbz, None).unwrap();
2816 let expected = downsample_reference(&full, 3);
2817 let actual = decode_downsampled(&sjbz, None, 3).unwrap();
2818 assert_eq!(expected.width, actual.width);
2819 assert_eq!(expected.height, actual.height);
2820 assert_eq!(expected.data, actual.data, "downsampled mask mismatch");
2821 }
2822}
2823
2824#[cfg(test)]
2825mod regression_fuzz2 {
2826 use super::*;
2827
2828 #[test]
2832 fn huge_symbol_from_small_input_does_not_hang() {
2833 let data = &[
2834 0x7f, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2835 ];
2836 let start = std::time::Instant::now();
2837 let _ = decode(data, None);
2838 let limit_secs = if cfg!(debug_assertions) { 8 } else { 2 };
2842 assert!(
2843 start.elapsed().as_secs() < limit_secs,
2844 "took {:?}",
2845 start.elapsed()
2846 );
2847 }
2848
2849 #[test]
2852 fn exhausted_refinement_symbol_from_small_input_does_not_hang() {
2853 let data = &[0x2a, 0xce, 0x7d, 0x24, 0x01, 0x00];
2854 let start = std::time::Instant::now();
2855 assert!(matches!(decode(data, None), Err(Jb2Error::Truncated)));
2856 let limit_secs = if cfg!(debug_assertions) { 2 } else { 1 };
2857 assert!(
2858 start.elapsed().as_secs() < limit_secs,
2859 "took {:?}",
2860 start.elapsed()
2861 );
2862 }
2863
2864 #[test]
2867 fn exhausted_repeated_refinement_symbols_do_not_hang() {
2868 let data = &[0x2a, 0xce, 0xf1, 0xce, 0xf1, 0x88, 0x52, 0x82, 0xf7, 0xf7];
2869 let start = std::time::Instant::now();
2870 assert!(matches!(decode(data, None), Err(Jb2Error::Truncated)));
2871 let limit_secs = if cfg!(debug_assertions) { 2 } else { 1 };
2872 assert!(
2873 start.elapsed().as_secs() < limit_secs,
2874 "took {:?}",
2875 start.elapsed()
2876 );
2877 }
2878}