#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
use crate::bitmap::Bitmap;
use crate::error::Jb2Error;
use crate::zp_impl::ZpDecoder;
struct NumContext {
ctx: Vec<u8>,
left: Vec<u32>,
right: Vec<u32>,
}
impl NumContext {
fn new() -> Self {
NumContext {
ctx: vec![0, 0],
left: vec![0, 0],
right: vec![0, 0],
}
}
fn root(&self) -> usize {
1
}
fn get_left(&mut self, node: usize) -> usize {
if self.left[node] == 0 {
let idx = self.ctx.len() as u32;
self.ctx.push(0);
self.left.push(0);
self.right.push(0);
self.left[node] = idx;
}
self.left[node] as usize
}
fn get_right(&mut self, node: usize) -> usize {
if self.right[node] == 0 {
let idx = self.ctx.len() as u32;
self.ctx.push(0);
self.left.push(0);
self.right.push(0);
self.right[node] = idx;
}
self.right[node] as usize
}
}
fn decode_num(zp: &mut ZpDecoder<'_>, ctx: &mut NumContext, low: i32, high: i32) -> i32 {
let mut low = low;
let mut high = high;
let mut negative = false;
let mut cutoff: i32 = 0;
let mut phase: u32 = 1;
let mut range: u32 = 0xffff_ffff;
let mut node = ctx.root();
while range != 1 {
let decision = if low >= cutoff {
true
} else if high >= cutoff {
zp.decode_bit(&mut ctx.ctx[node])
} else {
false
};
node = if decision {
ctx.get_right(node)
} else {
ctx.get_left(node)
};
match phase {
1 => {
negative = !decision;
if negative {
let temp = -low - 1;
low = -high - 1;
high = temp;
}
phase = 2;
cutoff = 1;
}
2 => {
if !decision {
phase = 3;
range = ((cutoff + 1) / 2) as u32;
if range == 1 {
cutoff = 0;
} else {
cutoff -= (range / 2) as i32;
}
} else {
cutoff = cutoff * 2 + 1;
}
}
3 => {
range /= 2;
if range == 0 {
range = 1;
}
if range != 1 {
if !decision {
cutoff -= (range / 2) as i32;
} else {
cutoff += (range / 2) as i32;
}
} else if !decision {
cutoff -= 1;
}
}
_ => {
range = range.saturating_sub(1);
}
}
}
if negative { -cutoff - 1 } else { cutoff }
}
#[derive(Clone)]
struct Jbm {
width: i32,
height: i32,
data: Vec<u8>,
}
impl Jbm {
#[inline(always)]
fn row_stride_bytes(width: i32) -> usize {
(width.max(0) as usize).div_ceil(8)
}
#[inline(always)]
fn stride(&self) -> usize {
Self::row_stride_bytes(self.width)
}
#[inline(always)]
fn storage_bytes(width: i32, height: i32) -> usize {
Self::row_stride_bytes(width).saturating_mul(height.max(0) as usize)
}
fn new(width: i32, height: i32) -> Self {
let len = Self::storage_bytes(width, height);
Jbm {
width,
height,
data: vec![0u8; len],
}
}
#[inline(always)]
fn get(&self, row: i32, col: i32) -> u8 {
if row < 0 || row >= self.height || col < 0 || col >= self.width {
return 0;
}
let stride = self.stride();
let byte = self.data[row as usize * stride + (col as usize / 8)];
(byte >> (7 - (col as usize & 7))) & 1
}
#[inline(always)]
fn set_black(&mut self, row: usize, col: usize) {
let stride = self.stride();
self.data[row * stride + (col / 8)] |= 0x80u8 >> (col & 7);
}
fn new_from_pool(width: i32, height: i32, pool: &mut Vec<u8>) -> Self {
let bytes = Self::storage_bytes(width, height);
if pool.len() < bytes {
pool.resize(bytes, 0u8);
}
pool[..bytes].fill(0u8);
let mut data = core::mem::take(pool);
data.truncate(bytes);
Jbm {
width,
height,
data,
}
}
fn crop_and_recycle(self, pool: &mut Vec<u8>) -> Jbm {
if self.width > 0 && self.height > 0 {
let w = self.width as usize;
let h = self.height as usize;
let stride = self.stride();
let last_col = w - 1;
let data = &self.data;
let top_has = data[..stride].iter().any(|&b| b != 0);
let bot_has = data[(h - 1) * stride..h * stride].iter().any(|&b| b != 0);
let left_has = (0..h).any(|r| (data[r * stride] & 0x80) != 0);
let right_has =
(0..h).any(|r| (data[r * stride + last_col / 8] & (0x80u8 >> (last_col & 7))) != 0);
if top_has && bot_has && left_has && right_has {
*pool = Vec::with_capacity(self.data.len());
return self;
}
}
let cropped = self.crop_to_content();
*pool = self.data;
cropped
}
fn recycle_into(self, pool: &mut Vec<u8>) {
*pool = self.data;
}
fn crop_to_content(&self) -> Jbm {
if self.width <= 0 || self.height <= 0 {
return Jbm::new(0, 0);
}
let stride = self.stride();
let mut min_row = self.height;
let mut max_row: i32 = -1;
let mut min_col = self.width;
let mut max_col: i32 = -1;
for row in 0..self.height {
let row_bytes = &self.data[row as usize * stride..(row as usize + 1) * stride];
let mut byte_min: Option<usize> = None;
let mut byte_max: Option<usize> = None;
for (i, &b) in row_bytes.iter().enumerate() {
if b != 0 {
if byte_min.is_none() {
byte_min = Some(i);
}
byte_max = Some(i);
}
}
if let (Some(bmin), Some(bmax)) = (byte_min, byte_max) {
let col_lo = bmin * 8 + row_bytes[bmin].leading_zeros() as usize;
let col_hi = bmax * 8 + (7 - row_bytes[bmax].trailing_zeros() as usize);
let col_hi = col_hi.min(self.width as usize - 1) as i32;
let col_lo = col_lo as i32;
min_row = min_row.min(row);
max_row = max_row.max(row);
min_col = min_col.min(col_lo);
max_col = max_col.max(col_hi);
}
}
if max_row < 0 {
return Jbm::new(0, 0);
}
let nw = max_col - min_col + 1;
let nh = max_row - min_row + 1;
let mut out = Jbm::new(nw, nh);
for row in min_row..=max_row {
for col in min_col..=max_col {
let src_byte = self.data[row as usize * stride + (col as usize / 8)];
if (src_byte >> (7 - (col as usize & 7))) & 1 != 0 {
let out_row = (row - min_row) as usize;
let out_col = (col - min_col) as usize;
out.set_black(out_row, out_col);
}
}
}
out
}
}
const MAX_SYMBOL_PIXELS: usize = 1024 * 1024; pub(crate) const MAX_TOTAL_SYMBOL_PIXELS: usize = 64 * 1024 * 1024;
const MAX_TOTAL_BLIT_PIXELS: usize = 256 * 1024 * 1024; const MAX_RECORDS: usize = 65_536; const MAX_COMMENT_BYTES: usize = 4096;
#[inline(always)]
fn check_pixel_budget(w: i32, h: i32, total: &mut usize) -> Result<(), Jb2Error> {
let pixels = (w.max(0) as usize).saturating_mul(h.max(0) as usize);
if pixels > MAX_SYMBOL_PIXELS {
return Err(Jb2Error::ImageTooLarge);
}
*total = total.saturating_add(pixels);
if *total > MAX_TOTAL_SYMBOL_PIXELS {
return Err(Jb2Error::ImageTooLarge);
}
Ok(())
}
#[inline(always)]
fn check_blit_budget(sym: &Jbm, total: &mut usize) -> Result<(), Jb2Error> {
let pixels = (sym.width.max(0) as usize).saturating_mul(sym.height.max(0) as usize);
*total = total.saturating_add(pixels);
if *total > MAX_TOTAL_BLIT_PIXELS {
return Err(Jb2Error::ImageTooLarge);
}
Ok(())
}
#[inline(never)]
fn decode_direct_row(
zp: &mut ZpDecoder<'_>,
ctx: &mut [u8; 1024],
row_slice: &mut [u8],
rp1: &[u8],
rp2: &[u8],
) {
use crate::zp_impl::tables::{LPS_NEXT, MPS_NEXT, PROB, THRESHOLD};
let mut a: u32 = zp.a;
let mut c: u32 = zp.c;
let mut fence: u32 = zp.fence;
let mut bit_buf = zp.bit_buf;
let mut bit_count = zp.bit_count;
let data = zp.data;
let mut pos = zp.pos;
macro_rules! read_byte {
() => {{
let b = if pos < data.len() { data[pos] } else { 0xff };
pos = pos.wrapping_add(1);
b as u32
}};
}
macro_rules! refill {
() => {
while bit_count <= 24 {
bit_buf = (bit_buf << 8) | read_byte!();
bit_count += 8;
}
};
}
macro_rules! renorm {
() => {{
let shift = (a as u16).leading_ones();
bit_count -= shift as i32;
a = (a << shift) & 0xffff;
let mask = (1u32 << (shift & 31)).wrapping_sub(1);
c = ((c << shift) | (bit_buf >> (bit_count as u32 & 31)) & mask) & 0xffff;
if bit_count < 16 {
refill!();
}
fence = c.min(0x7fff);
}};
}
let pix = |row: &[u8], col: usize| -> u32 { row.get(col).copied().unwrap_or(0) as u32 };
let w = row_slice.len();
let mut r2 = pix(rp2, 0) << 1 | pix(rp2, 1);
let mut r1 = pix(rp1, 0) << 2 | pix(rp1, 1) << 1 | pix(rp1, 2);
let mut r0: u32 = 0;
let (rp2_off, rp1_off) = if w >= 3 && rp2.len() >= w && rp1.len() >= w {
(&rp2[2..w], &rp1[3..w])
} else {
(&rp2[..0], &rp1[..0])
};
let mid_end = rp2_off.len().min(rp1_off.len());
macro_rules! decode_step {
($out:expr, $n2:expr, $n1:expr) => {{
let idx = (((r2 << 7) | (r1 << 2) | r0) & 1023) as usize;
let state = ctx[idx] as usize;
let mps_bit = state & 1;
let z = a + PROB[state] as u32;
let bit = if z <= fence {
a = z;
mps_bit != 0
} else {
let boundary = 0x6000u32 + ((a + z) >> 2);
let z_clamped = z.min(boundary);
if z_clamped > c {
let complement = 0x10000u32 - z_clamped;
a = (a + complement) & 0xffff;
c = (c + complement) & 0xffff;
ctx[idx] = LPS_NEXT[state];
renorm!();
(1 - mps_bit) != 0
} else {
if a >= THRESHOLD[state] as u32 {
ctx[idx] = MPS_NEXT[state];
}
bit_count -= 1;
a = (z_clamped << 1) & 0xffff;
c = ((c << 1) | (bit_buf >> (bit_count as u32 & 31)) & 1) & 0xffff;
if bit_count < 16 {
refill!();
}
fence = c.min(0x7fff);
mps_bit != 0
}
};
*$out = bit as u8;
r2 = ((r2 << 1) & 0b111) | ($n2 as u32);
r1 = ((r1 << 1) & 0b11111) | ($n1 as u32);
r0 = ((r0 << 1) & 0b11) | bit as u32;
}};
}
let (fast_slice, slow_slice) = row_slice.split_at_mut(mid_end);
for (out, (n2, n1)) in fast_slice.iter_mut().zip(rp2_off.iter().zip(rp1_off)) {
decode_step!(out, *n2, *n1);
}
for (i, out) in slow_slice.iter_mut().enumerate() {
let col = i + mid_end;
decode_step!(out, pix(rp2, col + 2), pix(rp1, col + 3));
}
zp.a = a;
zp.c = c;
zp.fence = fence;
zp.bit_buf = bit_buf;
zp.bit_count = bit_count;
zp.pos = pos;
}
#[allow(clippy::too_many_arguments)]
#[inline(never)]
fn decode_ref_row(
zp: &mut ZpDecoder<'_>,
ctx: &mut [u8; 2048],
ctx_p: &mut [u16; 2048],
cbm_row_mut: &mut [u8],
cbm_r1: &[u8],
mbm_r2: &[u8],
mbm_r1: &[u8],
mbm_r0: &[u8],
col_shift: i32,
init_c_r1: u32,
init_m_r1: u32,
init_m_r0: u32,
) {
use crate::zp_impl::tables::{LPS_NEXT, MPS_NEXT, PROB, THRESHOLD};
let mut a: u32 = zp.a;
let mut c: u32 = zp.c;
let mut fence: u32 = zp.fence;
let mut bit_buf = zp.bit_buf;
let mut bit_count = zp.bit_count;
let data = zp.data;
let mut pos = zp.pos;
macro_rules! read_byte {
() => {{
let b = if pos < data.len() { data[pos] } else { 0xff };
pos = pos.wrapping_add(1);
b as u32
}};
}
macro_rules! refill {
() => {
while bit_count <= 24 {
bit_buf = (bit_buf << 8) | read_byte!();
bit_count += 8;
}
};
}
macro_rules! renorm {
() => {{
let shift = (a as u16).leading_ones();
bit_count -= shift as i32;
a = (a << shift) & 0xffff;
let mask = (1u32 << (shift & 31)).wrapping_sub(1);
c = ((c << shift) | (bit_buf >> (bit_count as u32 & 31)) & mask) & 0xffff;
if bit_count < 16 {
refill!();
}
fence = c.min(0x7fff);
}};
}
let pix_row = |row_slice: &[u8], col: i32| -> u32 {
if col < 0 {
return 0;
}
row_slice.get(col as usize).copied().unwrap_or(0) as u32
};
let mut c_r0: u32 = 0;
let mut c_r1 = init_c_r1;
let mut m_r1 = init_m_r1;
let mut m_r0 = init_m_r0;
for col in 0..cbm_row_mut.len() as i32 {
let m_r2 = pix_row(mbm_r2, col + col_shift);
let idx = ((c_r1 << 8) | (c_r0 << 7) | (m_r2 << 6) | (m_r1 << 3) | m_r0) & 2047;
let state = ctx[idx as usize] as usize;
let prob = ctx_p[idx as usize] as u32; let mps_bit = state & 1;
let z = a + prob;
let bit = if z <= fence {
a = z;
mps_bit != 0
} else {
let boundary = 0x6000u32 + ((a + z) >> 2);
let z_clamped = z.min(boundary);
if z_clamped > c {
let complement = 0x10000u32 - z_clamped;
a = (a + complement) & 0xffff;
c = (c + complement) & 0xffff;
let next = LPS_NEXT[state];
ctx[idx as usize] = next;
ctx_p[idx as usize] = PROB[next as usize];
renorm!();
(1 - mps_bit) != 0
} else {
if a >= THRESHOLD[state] as u32 {
let next = MPS_NEXT[state];
ctx[idx as usize] = next;
ctx_p[idx as usize] = PROB[next as usize];
}
bit_count -= 1;
a = (z_clamped << 1) & 0xffff;
c = ((c << 1) | (bit_buf >> (bit_count as u32 & 31)) & 1) & 0xffff;
if bit_count < 16 {
refill!();
}
fence = c.min(0x7fff);
mps_bit != 0
}
};
if bit {
cbm_row_mut[col as usize] = 1;
}
c_r1 = ((c_r1 << 1) & 0b111) | pix_row(cbm_r1, col + 2);
c_r0 = bit as u32;
m_r1 = ((m_r1 << 1) & 0b111) | pix_row(mbm_r1, col + col_shift + 2);
m_r0 = ((m_r0 << 1) & 0b111) | pix_row(mbm_r0, col + col_shift + 2);
}
zp.a = a;
zp.c = c;
zp.fence = fence;
zp.bit_buf = bit_buf;
zp.bit_count = bit_count;
zp.pos = pos;
}
#[inline]
fn pack_row_into(src: &[u8], width: usize, dst: &mut [u8]) {
let full_bytes = width / 8;
let rem = width % 8;
for i in 0..full_bytes {
let s: &[u8; 8] = src[i * 8..(i + 1) * 8].try_into().unwrap();
dst[i] = pack_byte(s);
}
if rem > 0 {
let base = full_bytes * 8;
let mut byte_val = 0u8;
for j in 0..rem {
if src[base + j] != 0 {
byte_val |= 0x80u8 >> j;
}
}
dst[full_bytes] = byte_val;
}
}
#[inline]
fn unpack_row_into(src: &[u8], width: usize, dst: &mut [u8]) {
let full_bytes = width / 8;
let rem = width % 8;
for i in 0..full_bytes {
let b = src[i];
let out = &mut dst[i * 8..(i + 1) * 8];
out[0] = (b >> 7) & 1;
out[1] = (b >> 6) & 1;
out[2] = (b >> 5) & 1;
out[3] = (b >> 4) & 1;
out[4] = (b >> 3) & 1;
out[5] = (b >> 2) & 1;
out[6] = (b >> 1) & 1;
out[7] = b & 1;
}
if rem > 0 {
let b = src[full_bytes];
let base = full_bytes * 8;
for j in 0..rem {
dst[base + j] = (b >> (7 - j)) & 1;
}
}
}
fn decode_bitmap_direct(
zp: &mut ZpDecoder<'_>,
ctx: &mut [u8; 1024],
width: i32,
height: i32,
pool: &mut Vec<u8>,
) -> Result<Jbm, Jb2Error> {
let pixels = (width.max(0) as usize).saturating_mul(height.max(0) as usize);
if pixels > MAX_SYMBOL_PIXELS {
return Err(Jb2Error::ImageTooLarge);
}
if width <= 0 || height <= 0 {
return Ok(Jbm::new_from_pool(width, height, pool));
}
let mut bm = Jbm::new_from_pool(width, height, pool);
let w = width as usize;
let h = height as usize;
let stride = bm.stride();
debug_assert_eq!(bm.data.len(), stride * h);
let mut s_curr = vec![0u8; w];
let mut s_prev1 = vec![0u8; w];
let mut s_prev2 = vec![0u8; w];
for row in (0..h).rev() {
s_curr.iter_mut().for_each(|b| *b = 0);
decode_direct_row(zp, ctx, &mut s_curr, &s_prev1, &s_prev2);
pack_row_into(&s_curr, w, &mut bm.data[row * stride..(row + 1) * stride]);
core::mem::swap(&mut s_prev2, &mut s_prev1);
core::mem::swap(&mut s_prev1, &mut s_curr);
}
Ok(bm)
}
fn decode_bitmap_ref(
zp: &mut ZpDecoder<'_>,
ctx: &mut [u8; 2048],
ctx_p: &mut [u16; 2048],
width: i32,
height: i32,
mbm: &Jbm,
pool: &mut Vec<u8>,
) -> Result<Jbm, Jb2Error> {
let pixels = (width.max(0) as usize).saturating_mul(height.max(0) as usize);
if pixels > MAX_SYMBOL_PIXELS {
return Err(Jb2Error::ImageTooLarge);
}
if width <= 0 || height <= 0 {
return Ok(Jbm::new_from_pool(width, height, pool));
}
let mut cbm = Jbm::new_from_pool(width, height, pool);
let crow = (height - 1) >> 1;
let ccol = (width - 1) >> 1;
let mrow = (mbm.height - 1) >> 1;
let mcol = (mbm.width - 1) >> 1;
let row_shift = mrow - crow;
let col_shift = mcol - ccol;
let pix_row = |row_slice: &[u8], col: i32| -> u32 {
if col < 0 {
return 0;
}
row_slice.get(col as usize).copied().unwrap_or(0) as u32
};
let cw = width as usize;
let cstride = cbm.stride();
let mw = mbm.width.max(0) as usize;
let mstride = mbm.stride();
let mut s_mbm_r2 = vec![0u8; mw];
let mut s_mbm_r1 = vec![0u8; mw];
let mut s_mbm_r0 = vec![0u8; mw];
let mut have_r2;
let mut have_r1;
let mut have_r0;
let mut s_cbm_curr = vec![0u8; cw];
let mut s_cbm_prev1 = vec![0u8; cw];
let unpack_mbm_row = |r: i32, buf: &mut [u8]| -> bool {
if r < 0 || r >= mbm.height || mw == 0 {
return false;
}
let off = r as usize * mstride;
unpack_row_into(&mbm.data[off..off + mstride], mw, buf);
true
};
let first_mr = (height - 1) + row_shift;
have_r2 = unpack_mbm_row(first_mr + 1, &mut s_mbm_r2);
have_r1 = unpack_mbm_row(first_mr, &mut s_mbm_r1);
have_r0 = unpack_mbm_row(first_mr - 1, &mut s_mbm_r0);
for row in (0..height).rev() {
let mr = row + row_shift;
let mbm_r2: &[u8] = if have_r2 { &s_mbm_r2 } else { &[] };
let mbm_r1: &[u8] = if have_r1 { &s_mbm_r1 } else { &[] };
let mbm_r0: &[u8] = if have_r0 { &s_mbm_r0 } else { &[] };
let cbm_r1: &[u8] = if row + 1 < height { &s_cbm_prev1 } else { &[] };
s_cbm_curr.iter_mut().for_each(|b| *b = 0);
let init_c_r1 = pix_row(cbm_r1, 0) << 1 | pix_row(cbm_r1, 1);
let init_m_r1 = pix_row(mbm_r1, col_shift - 1) << 2
| pix_row(mbm_r1, col_shift) << 1
| pix_row(mbm_r1, col_shift + 1);
let init_m_r0 = pix_row(mbm_r0, col_shift - 1) << 2
| pix_row(mbm_r0, col_shift) << 1
| pix_row(mbm_r0, col_shift + 1);
decode_ref_row(
zp,
ctx,
ctx_p,
&mut s_cbm_curr,
cbm_r1,
mbm_r2,
mbm_r1,
mbm_r0,
col_shift,
init_c_r1,
init_m_r1,
init_m_r0,
);
pack_row_into(
&s_cbm_curr,
cw,
&mut cbm.data[row as usize * cstride..(row as usize + 1) * cstride],
);
core::mem::swap(&mut s_cbm_prev1, &mut s_cbm_curr);
core::mem::swap(&mut s_mbm_r2, &mut s_mbm_r1);
have_r2 = have_r1;
core::mem::swap(&mut s_mbm_r1, &mut s_mbm_r0);
have_r1 = have_r0;
have_r0 = unpack_mbm_row(mr - 2, &mut s_mbm_r0);
}
Ok(cbm)
}
struct Baseline {
arr: [i32; 3],
index: i32,
}
impl Baseline {
fn new() -> Self {
Baseline {
arr: [0, 0, 0],
index: -1,
}
}
fn fill(&mut self, val: i32) {
self.arr = [val, val, val];
}
fn add(&mut self, val: i32) {
self.index += 1;
if self.index == 3 {
self.index = 0;
}
self.arr[self.index as usize] = val;
}
fn get_val(&self) -> i32 {
let (a, b, c) = (self.arr[0], self.arr[1], self.arr[2]);
if (a >= b && a <= c) || (a <= b && a >= c) {
a
} else if (b >= a && b <= c) || (b <= a && b >= c) {
b
} else {
c
}
}
}
#[allow(clippy::too_many_arguments)]
fn blit_indexed(
page: &mut [u8],
blit_map: &mut [i32],
page_w: i32,
page_h: i32,
symbol: &Jbm,
x: i32,
y: i32,
blit_idx: i32,
) {
if symbol.width <= 0 || symbol.height <= 0 {
return;
}
if x >= 0 && y >= 0 && x + symbol.width <= page_w && y + symbol.height <= page_h {
let pw = page_w as usize;
let sw = symbol.width as usize;
let sym_stride = symbol.stride();
let full_bytes = sw / 8;
let rem = sw & 7;
for row in 0..symbol.height as usize {
let src_row_off = row * sym_stride;
let dst_off = (y as usize + row) * pw + x as usize;
for byte_i in 0..full_bytes {
let b = symbol.data[src_row_off + byte_i];
if b == 0 {
continue;
}
let base_col = byte_i * 8;
for j in 0..8 {
if (b >> (7 - j)) & 1 != 0 {
page[dst_off + base_col + j] = 1;
blit_map[dst_off + base_col + j] = blit_idx;
}
}
}
if rem > 0 {
let b = symbol.data[src_row_off + full_bytes];
if b != 0 {
let base_col = full_bytes * 8;
for j in 0..rem {
if (b >> (7 - j)) & 1 != 0 {
page[dst_off + base_col + j] = 1;
blit_map[dst_off + base_col + j] = blit_idx;
}
}
}
}
}
} else {
for row in 0..symbol.height {
let py = y + row;
if py < 0 || py >= page_h {
continue;
}
for col in 0..symbol.width {
if symbol.get(row, col) != 0 {
let px = x + col;
if px >= 0 && px < page_w {
let idx = (py * page_w + px) as usize;
page[idx] = 1;
blit_map[idx] = blit_idx;
}
}
}
}
}
}
fn blit_to_bitmap(bm: &mut Bitmap, sym: &Jbm, x: i32, y: i32) {
if sym.width <= 0 || sym.height <= 0 {
return;
}
let bw = bm.width as i32;
let bh = bm.height as i32;
let bm_stride = bm.row_stride();
let sw = sym.width;
let sh = sym.height;
let sym_stride = sym.stride();
if x >= 0
&& y >= 0
&& x.checked_add(sw).is_some_and(|v| v <= bw)
&& y.checked_add(sh).is_some_and(|v| v <= bh)
{
let x_off = x as usize;
let byte_off = x_off / 8;
let bit_off = x_off & 7;
let sw_u = sw as usize;
let full = sw_u / 8;
let rem = sw_u & 7;
let bm_y_base = (bm.height as usize) - 1 - y as usize;
if bit_off == 0 {
for sym_row in 0..sh as usize {
let bm_y = bm_y_base - sym_row;
let src = &sym.data[sym_row * sym_stride..sym_row * sym_stride + sym_stride];
let dst = &mut bm.data[bm_y * bm_stride..];
for i in 0..full {
dst[byte_off + i] |= src[i];
}
if rem > 0 {
dst[byte_off + full] |= src[full];
}
}
} else {
let rshift = bit_off as u32;
let lshift = 8 - bit_off as u32;
for sym_row in 0..sh as usize {
let bm_y = bm_y_base - sym_row;
let src = &sym.data[sym_row * sym_stride..sym_row * sym_stride + sym_stride];
let row_off = bm_y * bm_stride;
for (i, &s) in src.iter().enumerate().take(full) {
bm.data[row_off + byte_off + i] |= s >> rshift;
bm.data[row_off + byte_off + i + 1] |= s << lshift;
}
if rem > 0 {
let s = src[full];
bm.data[row_off + byte_off + full] |= s >> rshift;
let overflow = row_off + byte_off + full + 1;
if overflow < bm.data.len() {
bm.data[overflow] |= s << lshift;
}
}
}
}
} else {
for sym_row in 0..sh {
let bm_y = bh - 1 - y - sym_row;
if bm_y < 0 || bm_y >= bh {
continue;
}
let bm_y = bm_y as usize;
let row_off = bm_y * bm_stride;
let src_row_off = sym_row as usize * sym_stride;
for col in 0..sw {
let b = sym.data[src_row_off + (col as usize / 8)];
if (b >> (7 - (col as usize & 7))) & 1 != 0 {
let px = x + col;
if px >= 0 && px < bw {
let px = px as usize;
bm.data[row_off + px / 8] |= 0x80u8 >> (px & 7);
}
}
}
}
}
}
#[inline(always)]
fn pack_byte(s: &[u8; 8]) -> u8 {
((s[0] != 0) as u8) << 7
| ((s[1] != 0) as u8) << 6
| ((s[2] != 0) as u8) << 5
| ((s[3] != 0) as u8) << 4
| ((s[4] != 0) as u8) << 3
| ((s[5] != 0) as u8) << 2
| ((s[6] != 0) as u8) << 1
| ((s[7] != 0) as u8)
}
fn page_to_bitmap(page: &[u8], width: i32, height: i32) -> Bitmap {
let w = width as usize;
let h = height as usize;
let mut bm = Bitmap::new(width as u32, height as u32);
let stride = bm.row_stride();
let full_bytes = w / 8;
let remaining = w % 8;
for row in 0..h {
let src_row = &page[row * w..(row + 1) * w];
let dst_y = h - 1 - row; let dst_off = dst_y * stride;
for byte_idx in 0..full_bytes {
let s: &[u8; 8] = src_row[byte_idx * 8..(byte_idx + 1) * 8]
.try_into()
.unwrap();
bm.data[dst_off + byte_idx] = pack_byte(s);
}
if remaining > 0 {
let base = full_bytes * 8;
let mut byte_val = 0u8;
for bit_pos in 0..remaining {
if src_row[base + bit_pos] != 0 {
byte_val |= 0x80u8 >> bit_pos;
}
}
bm.data[dst_off + full_bytes] = byte_val;
}
}
bm
}
fn flip_blit_map(blit_map: &mut [i32], width: usize, height: usize) {
for row in 0..height / 2 {
let mirror = height - 1 - row;
let a = row * width;
let b = mirror * width;
for col in 0..width {
blit_map.swap(a + col, b + col);
}
}
}
struct CoordContexts {
offset_type: u8,
hoff: NumContext,
voff: NumContext,
shoff: NumContext,
svoff: NumContext,
}
impl CoordContexts {
fn new() -> Self {
Self {
offset_type: 0,
hoff: NumContext::new(),
voff: NumContext::new(),
shoff: NumContext::new(),
svoff: NumContext::new(),
}
}
}
struct LayoutState {
first_left: i32,
first_bottom: i32,
last_right: i32,
baseline: Baseline,
}
impl LayoutState {
fn new(image_height: i32) -> Self {
Self {
first_left: -1,
first_bottom: image_height - 1,
last_right: 0,
baseline: Baseline::new(),
}
}
}
fn decode_symbol_coords(
zp: &mut ZpDecoder<'_>,
coord_ctx: &mut CoordContexts,
layout: &mut LayoutState,
sym_width: i32,
sym_height: i32,
) -> (i32, i32) {
let new_line = zp.decode_bit(&mut coord_ctx.offset_type);
let (x, y) = if new_line {
let hoff = decode_num(zp, &mut coord_ctx.hoff, -262143, 262142);
let voff = decode_num(zp, &mut coord_ctx.voff, -262143, 262142);
let nx = layout.first_left + hoff;
let ny = layout.first_bottom + voff - sym_height + 1;
layout.first_left = nx;
layout.first_bottom = ny;
layout.baseline.fill(ny);
(nx, ny)
} else {
let hoff = decode_num(zp, &mut coord_ctx.shoff, -262143, 262142);
let voff = decode_num(zp, &mut coord_ctx.svoff, -262143, 262142);
(layout.last_right + hoff, layout.baseline.get_val() + voff)
};
layout.baseline.add(y);
layout.last_right = x + sym_width - 1;
(x, y)
}
struct JbmDict<'a> {
shared: &'a [Jbm],
local: Vec<Jbm>,
}
impl<'a> JbmDict<'a> {
fn new(shared: &'a [Jbm]) -> Self {
JbmDict {
shared,
local: Vec::new(),
}
}
fn len(&self) -> usize {
self.shared.len() + self.local.len()
}
fn is_empty(&self) -> bool {
self.shared.is_empty() && self.local.is_empty()
}
fn push(&mut self, sym: Jbm) {
self.local.push(sym);
}
fn into_symbols(self) -> Vec<Jbm> {
let mut out = self.shared.to_vec();
out.extend(self.local);
out
}
}
impl core::ops::Index<usize> for JbmDict<'_> {
type Output = Jbm;
#[inline(always)]
fn index(&self, index: usize) -> &Jbm {
let n = self.shared.len();
if index < n {
&self.shared[index]
} else {
&self.local[index - n]
}
}
}
pub struct Jb2Dict {
symbols: Vec<Jbm>,
}
pub fn decode(data: &[u8], shared_dict: Option<&Jb2Dict>) -> Result<Bitmap, Jb2Error> {
decode_image(data, shared_dict)
}
pub fn decode_indexed(
data: &[u8],
shared_dict: Option<&Jb2Dict>,
) -> Result<(Bitmap, Vec<i32>), Jb2Error> {
decode_image_indexed(data, shared_dict)
}
pub fn decode_dict(data: &[u8], inherited: Option<&Jb2Dict>) -> Result<Jb2Dict, Jb2Error> {
decode_dictionary(data, inherited)
}
fn decode_image(data: &[u8], shared_dict: Option<&Jb2Dict>) -> Result<Bitmap, Jb2Error> {
let mut pool = Vec::new();
decode_image_with_pool(data, shared_dict, &mut pool)
}
fn decode_image_with_pool(
data: &[u8],
shared_dict: Option<&Jb2Dict>,
pool: &mut Vec<u8>,
) -> Result<Bitmap, Jb2Error> {
let mut zp = ZpDecoder::new(data).map_err(|_| Jb2Error::ZpInitFailed)?;
let mut record_type_ctx = NumContext::new();
let mut image_size_ctx = NumContext::new();
let mut symbol_width_ctx = NumContext::new();
let mut symbol_height_ctx = NumContext::new();
let mut inherit_dict_size_ctx = NumContext::new();
let mut coord_ctx = CoordContexts::new();
let mut symbol_index_ctx = NumContext::new();
let mut symbol_width_diff_ctx = NumContext::new();
let mut symbol_height_diff_ctx = NumContext::new();
let mut horiz_abs_loc_ctx = NumContext::new();
let mut vert_abs_loc_ctx = NumContext::new();
let mut comment_length_ctx = NumContext::new();
let mut comment_octet_ctx = NumContext::new();
let mut direct_bitmap_ctx = [0u8; 1024];
let mut refinement_bitmap_ctx = [0u8; 2048];
let mut refinement_bitmap_ctx_p = [0x8000u16; 2048];
let mut total_sym_pixels = 0usize;
let mut total_blit_pixels = 0usize;
let mut rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
let mut initial_dict_length: usize = 0;
if rtype == 9 {
initial_dict_length = decode_num(&mut zp, &mut inherit_dict_size_ctx, 0, 262142) as usize;
rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
}
let _ = rtype;
let image_width = {
let w = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
if w == 0 { 200 } else { w }
};
let image_height = {
let h = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
if h == 0 { 200 } else { h }
};
let mut flag_ctx: u8 = 0;
if zp.decode_bit(&mut flag_ctx) {
return Err(Jb2Error::BadHeaderFlag);
}
let initial_symbols: &[Jbm] = if initial_dict_length > 0 {
match shared_dict {
Some(sd) => {
if initial_dict_length > sd.symbols.len() {
return Err(Jb2Error::InheritedDictTooLarge);
}
&sd.symbols[..initial_dict_length]
}
None => return Err(Jb2Error::MissingSharedDict),
}
} else {
&[]
};
let mut dict = JbmDict::new(initial_symbols);
const MAX_PIXELS: usize = 64 * 1024 * 1024;
let page_size = (image_width as usize).saturating_mul(image_height as usize);
if page_size > MAX_PIXELS {
return Err(Jb2Error::ImageTooLarge);
}
let mut page_bm = Bitmap::new(image_width as u32, image_height as u32);
let mut layout = LayoutState::new(image_height);
let mut record_count = 0usize;
loop {
if record_count >= MAX_RECORDS {
return Err(Jb2Error::TooManyRecords);
}
record_count += 1;
let rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
match rtype {
1 => {
let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
check_pixel_budget(w, h, &mut total_sym_pixels)?;
let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
let (x, y) =
decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
check_blit_budget(&bm, &mut total_blit_pixels)?;
blit_to_bitmap(&mut page_bm, &bm, x, y);
dict.push(bm.crop_and_recycle(pool));
}
2 => {
let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
check_pixel_budget(w, h, &mut total_sym_pixels)?;
let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
dict.push(bm.crop_and_recycle(pool));
}
3 => {
let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
check_pixel_budget(w, h, &mut total_sym_pixels)?;
let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
let (x, y) =
decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
check_blit_budget(&bm, &mut total_blit_pixels)?;
blit_to_bitmap(&mut page_bm, &bm, x, y);
bm.recycle_into(pool);
}
4 => {
if dict.is_empty() {
return Err(Jb2Error::EmptyDictReference);
}
let index =
decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
if index >= dict.len() {
return Err(Jb2Error::InvalidSymbolIndex);
}
let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
let cbm_w = dict[index].width + wdiff;
let cbm_h = dict[index].height + hdiff;
check_pixel_budget(cbm_w, cbm_h, &mut total_sym_pixels)?;
let cbm = decode_bitmap_ref(
&mut zp,
&mut refinement_bitmap_ctx,
&mut refinement_bitmap_ctx_p,
cbm_w,
cbm_h,
&dict[index],
pool,
)?;
let (x, y) = decode_symbol_coords(
&mut zp,
&mut coord_ctx,
&mut layout,
cbm.width,
cbm.height,
);
check_blit_budget(&cbm, &mut total_blit_pixels)?;
blit_to_bitmap(&mut page_bm, &cbm, x, y);
dict.push(cbm.crop_and_recycle(pool));
}
5 => {
if dict.is_empty() {
return Err(Jb2Error::EmptyDictReference);
}
let index =
decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
if index >= dict.len() {
return Err(Jb2Error::InvalidSymbolIndex);
}
let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
let cbm_w = dict[index].width + wdiff;
let cbm_h = dict[index].height + hdiff;
check_pixel_budget(cbm_w, cbm_h, &mut total_sym_pixels)?;
let cbm = decode_bitmap_ref(
&mut zp,
&mut refinement_bitmap_ctx,
&mut refinement_bitmap_ctx_p,
cbm_w,
cbm_h,
&dict[index],
pool,
)?;
dict.push(cbm.crop_and_recycle(pool));
}
6 => {
if dict.is_empty() {
return Err(Jb2Error::EmptyDictReference);
}
let index =
decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
if index >= dict.len() {
return Err(Jb2Error::InvalidSymbolIndex);
}
let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
let cbm_w = dict[index].width + wdiff;
let cbm_h = dict[index].height + hdiff;
check_pixel_budget(cbm_w, cbm_h, &mut total_sym_pixels)?;
let cbm = decode_bitmap_ref(
&mut zp,
&mut refinement_bitmap_ctx,
&mut refinement_bitmap_ctx_p,
cbm_w,
cbm_h,
&dict[index],
pool,
)?;
let (x, y) = decode_symbol_coords(
&mut zp,
&mut coord_ctx,
&mut layout,
cbm.width,
cbm.height,
);
check_blit_budget(&cbm, &mut total_blit_pixels)?;
blit_to_bitmap(&mut page_bm, &cbm, x, y);
cbm.recycle_into(pool);
}
7 => {
if dict.is_empty() {
return Err(Jb2Error::EmptyDictReference);
}
let index =
decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
if index >= dict.len() {
return Err(Jb2Error::InvalidSymbolIndex);
}
let bm_w = dict[index].width;
let bm_h = dict[index].height;
let (x, y) = decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm_w, bm_h);
let sym = &dict[index];
check_blit_budget(sym, &mut total_blit_pixels)?;
blit_to_bitmap(&mut page_bm, sym, x, y);
}
8 => {
let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
check_pixel_budget(w, h, &mut total_sym_pixels)?;
let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
let left = decode_num(&mut zp, &mut horiz_abs_loc_ctx, 1, image_width);
let top = decode_num(&mut zp, &mut vert_abs_loc_ctx, 1, image_height);
let x = left - 1;
let y = top - h;
check_blit_budget(&bm, &mut total_blit_pixels)?;
blit_to_bitmap(&mut page_bm, &bm, x, y);
bm.recycle_into(pool);
}
9 => {}
10 => {
let length = decode_num(&mut zp, &mut comment_length_ctx, 0, 262142) as usize;
let length = length.min(MAX_COMMENT_BYTES);
for _ in 0..length {
decode_num(&mut zp, &mut comment_octet_ctx, 0, 255);
}
}
11 => break,
_ => return Err(Jb2Error::UnknownRecordType),
}
}
Ok(page_bm)
}
fn decode_image_indexed(
data: &[u8],
shared_dict: Option<&Jb2Dict>,
) -> Result<(Bitmap, Vec<i32>), Jb2Error> {
let mut pool = Vec::new();
decode_image_indexed_with_pool(data, shared_dict, &mut pool)
}
fn decode_image_indexed_with_pool(
data: &[u8],
shared_dict: Option<&Jb2Dict>,
pool: &mut Vec<u8>,
) -> Result<(Bitmap, Vec<i32>), Jb2Error> {
let mut zp = ZpDecoder::new(data).map_err(|_| Jb2Error::ZpInitFailed)?;
let mut record_type_ctx = NumContext::new();
let mut image_size_ctx = NumContext::new();
let mut symbol_width_ctx = NumContext::new();
let mut symbol_height_ctx = NumContext::new();
let mut inherit_dict_size_ctx = NumContext::new();
let mut coord_ctx = CoordContexts::new();
let mut symbol_index_ctx = NumContext::new();
let mut symbol_width_diff_ctx = NumContext::new();
let mut symbol_height_diff_ctx = NumContext::new();
let mut horiz_abs_loc_ctx = NumContext::new();
let mut vert_abs_loc_ctx = NumContext::new();
let mut comment_length_ctx = NumContext::new();
let mut comment_octet_ctx = NumContext::new();
let mut direct_bitmap_ctx = [0u8; 1024];
let mut refinement_bitmap_ctx = [0u8; 2048];
let mut refinement_bitmap_ctx_p = [0x8000u16; 2048];
let mut total_sym_pixels = 0usize;
let mut total_blit_pixels = 0usize;
let mut rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
let mut initial_dict_length: usize = 0;
if rtype == 9 {
initial_dict_length = decode_num(&mut zp, &mut inherit_dict_size_ctx, 0, 262142) as usize;
rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
}
let _ = rtype;
let image_width = {
let w = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
if w == 0 { 200 } else { w }
};
let image_height = {
let h = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
if h == 0 { 200 } else { h }
};
let mut flag_ctx: u8 = 0;
if zp.decode_bit(&mut flag_ctx) {
return Err(Jb2Error::BadHeaderFlag);
}
let initial_symbols_idx: &[Jbm] = if initial_dict_length > 0 {
match shared_dict {
Some(sd) => {
if initial_dict_length > sd.symbols.len() {
return Err(Jb2Error::InheritedDictTooLarge);
}
&sd.symbols[..initial_dict_length]
}
None => return Err(Jb2Error::MissingSharedDict),
}
} else {
&[]
};
let mut dict = JbmDict::new(initial_symbols_idx);
const MAX_PIXELS: usize = 64 * 1024 * 1024;
let page_size = (image_width as usize).saturating_mul(image_height as usize);
if page_size > MAX_PIXELS {
return Err(Jb2Error::ImageTooLarge);
}
let mut page = vec![0u8; page_size];
let mut blit_map = vec![-1i32; page_size];
let mut layout = LayoutState::new(image_height);
let mut blit_count: i32 = 0;
let mut record_count = 0usize;
loop {
if record_count >= MAX_RECORDS {
return Err(Jb2Error::TooManyRecords);
}
record_count += 1;
let rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
match rtype {
1 => {
let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
check_pixel_budget(w, h, &mut total_sym_pixels)?;
let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
let (x, y) =
decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
check_blit_budget(&bm, &mut total_blit_pixels)?;
blit_indexed(
&mut page,
&mut blit_map,
image_width,
image_height,
&bm,
x,
y,
blit_count,
);
blit_count += 1;
dict.push(bm.crop_and_recycle(pool));
}
2 => {
let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
check_pixel_budget(w, h, &mut total_sym_pixels)?;
let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
dict.push(bm.crop_and_recycle(pool));
}
3 => {
let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
check_pixel_budget(w, h, &mut total_sym_pixels)?;
let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
let (x, y) =
decode_symbol_coords(&mut zp, &mut coord_ctx, &mut layout, bm.width, bm.height);
check_blit_budget(&bm, &mut total_blit_pixels)?;
blit_indexed(
&mut page,
&mut blit_map,
image_width,
image_height,
&bm,
x,
y,
blit_count,
);
blit_count += 1;
bm.recycle_into(pool);
}
4 => {
if dict.is_empty() {
return Err(Jb2Error::EmptyDictReference);
}
let index =
decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
if index >= dict.len() {
return Err(Jb2Error::InvalidSymbolIndex);
}
let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
let cbm_w = dict[index].width + wdiff;
let cbm_h = dict[index].height + hdiff;
check_pixel_budget(cbm_w, cbm_h, &mut total_sym_pixels)?;
let cbm = decode_bitmap_ref(
&mut zp,
&mut refinement_bitmap_ctx,
&mut refinement_bitmap_ctx_p,
cbm_w,
cbm_h,
&dict[index],
pool,
)?;
let (x, y) = decode_symbol_coords(
&mut zp,
&mut coord_ctx,
&mut layout,
cbm.width,
cbm.height,
);
check_blit_budget(&cbm, &mut total_blit_pixels)?;
blit_indexed(
&mut page,
&mut blit_map,
image_width,
image_height,
&cbm,
x,
y,
blit_count,
);
blit_count += 1;
dict.push(cbm.crop_and_recycle(pool));
}
5 => {
if dict.is_empty() {
return Err(Jb2Error::EmptyDictReference);
}
let index =
decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
if index >= dict.len() {
return Err(Jb2Error::InvalidSymbolIndex);
}
let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
let cbm_w = dict[index].width + wdiff;
let cbm_h = dict[index].height + hdiff;
check_pixel_budget(cbm_w, cbm_h, &mut total_sym_pixels)?;
let cbm = decode_bitmap_ref(
&mut zp,
&mut refinement_bitmap_ctx,
&mut refinement_bitmap_ctx_p,
cbm_w,
cbm_h,
&dict[index],
pool,
)?;
dict.push(cbm.crop_and_recycle(pool));
}
6 => {
if dict.is_empty() {
return Err(Jb2Error::EmptyDictReference);
}
let index =
decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
if index >= dict.len() {
return Err(Jb2Error::InvalidSymbolIndex);
}
let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
let cbm_w = dict[index].width + wdiff;
let cbm_h = dict[index].height + hdiff;
check_pixel_budget(cbm_w, cbm_h, &mut total_sym_pixels)?;
let cbm = decode_bitmap_ref(
&mut zp,
&mut refinement_bitmap_ctx,
&mut refinement_bitmap_ctx_p,
cbm_w,
cbm_h,
&dict[index],
pool,
)?;
let (x, y) = decode_symbol_coords(
&mut zp,
&mut coord_ctx,
&mut layout,
cbm.width,
cbm.height,
);
check_blit_budget(&cbm, &mut total_blit_pixels)?;
blit_indexed(
&mut page,
&mut blit_map,
image_width,
image_height,
&cbm,
x,
y,
blit_count,
);
blit_count += 1;
cbm.recycle_into(pool);
}
7 => {
if dict.is_empty() {
return Err(Jb2Error::EmptyDictReference);
}
let index =
decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
if index >= dict.len() {
return Err(Jb2Error::InvalidSymbolIndex);
}
let (x, y) = decode_symbol_coords(
&mut zp,
&mut coord_ctx,
&mut layout,
dict[index].width,
dict[index].height,
);
check_blit_budget(&dict[index], &mut total_blit_pixels)?;
blit_indexed(
&mut page,
&mut blit_map,
image_width,
image_height,
&dict[index],
x,
y,
blit_count,
);
blit_count += 1;
}
8 => {
let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
check_pixel_budget(w, h, &mut total_sym_pixels)?;
let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
let left = decode_num(&mut zp, &mut horiz_abs_loc_ctx, 1, image_width);
let top = decode_num(&mut zp, &mut vert_abs_loc_ctx, 1, image_height);
check_blit_budget(&bm, &mut total_blit_pixels)?;
blit_indexed(
&mut page,
&mut blit_map,
image_width,
image_height,
&bm,
left - 1,
top - h,
blit_count,
);
blit_count += 1;
bm.recycle_into(pool);
}
9 => {}
10 => {
let length = decode_num(&mut zp, &mut comment_length_ctx, 0, 262142) as usize;
let length = length.min(MAX_COMMENT_BYTES);
for _ in 0..length {
decode_num(&mut zp, &mut comment_octet_ctx, 0, 255);
}
}
11 => break,
_ => return Err(Jb2Error::UnknownRecordType),
}
}
let bm = page_to_bitmap(&page, image_width, image_height);
flip_blit_map(&mut blit_map, image_width as usize, image_height as usize);
Ok((bm, blit_map))
}
fn decode_dictionary(data: &[u8], inherited: Option<&Jb2Dict>) -> Result<Jb2Dict, Jb2Error> {
let mut pool: Vec<u8> = Vec::new();
decode_dictionary_with_pool(data, inherited, &mut pool)
}
fn decode_dictionary_with_pool(
data: &[u8],
inherited: Option<&Jb2Dict>,
pool: &mut Vec<u8>,
) -> Result<Jb2Dict, Jb2Error> {
let mut zp = ZpDecoder::new(data).map_err(|_| Jb2Error::ZpInitFailed)?;
let mut record_type_ctx = NumContext::new();
let mut image_size_ctx = NumContext::new();
let mut symbol_width_ctx = NumContext::new();
let mut symbol_height_ctx = NumContext::new();
let mut inherit_dict_size_ctx = NumContext::new();
let mut symbol_index_ctx = NumContext::new();
let mut symbol_width_diff_ctx = NumContext::new();
let mut symbol_height_diff_ctx = NumContext::new();
let mut comment_length_ctx = NumContext::new();
let mut comment_octet_ctx = NumContext::new();
let mut direct_bitmap_ctx = [0u8; 1024];
let mut refinement_bitmap_ctx = [0u8; 2048];
let mut refinement_bitmap_ctx_p = [0x8000u16; 2048];
let mut total_sym_pixels = 0usize;
let mut rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
let mut initial_dict_length: usize = 0;
if rtype == 9 {
initial_dict_length = decode_num(&mut zp, &mut inherit_dict_size_ctx, 0, 262142) as usize;
rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
}
let _ = rtype;
let _dict_width = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
let _dict_height = decode_num(&mut zp, &mut image_size_ctx, 0, 262142);
let mut flag_ctx: u8 = 0;
if zp.decode_bit(&mut flag_ctx) {
return Err(Jb2Error::BadHeaderFlag);
}
let initial_inh: &[Jbm] = if initial_dict_length > 0 {
match inherited {
Some(inh) => {
if initial_dict_length > inh.symbols.len() {
return Err(Jb2Error::InheritedDictTooLarge);
}
&inh.symbols[..initial_dict_length]
}
None => return Err(Jb2Error::MissingSharedDict),
}
} else {
&[]
};
let mut dict = JbmDict::new(initial_inh);
let mut record_count = 0usize;
loop {
if record_count >= MAX_RECORDS {
return Err(Jb2Error::TooManyRecords);
}
record_count += 1;
let rtype = decode_num(&mut zp, &mut record_type_ctx, 0, 11);
match rtype {
2 => {
let w = decode_num(&mut zp, &mut symbol_width_ctx, 0, 262142);
let h = decode_num(&mut zp, &mut symbol_height_ctx, 0, 262142);
check_pixel_budget(w, h, &mut total_sym_pixels)?;
let bm = decode_bitmap_direct(&mut zp, &mut direct_bitmap_ctx, w, h, pool)?;
dict.push(bm.crop_and_recycle(pool));
}
5 => {
if dict.is_empty() {
return Err(Jb2Error::EmptyDictReference);
}
let index =
decode_num(&mut zp, &mut symbol_index_ctx, 0, dict.len() as i32 - 1) as usize;
if index >= dict.len() {
return Err(Jb2Error::InvalidSymbolIndex);
}
let wdiff = decode_num(&mut zp, &mut symbol_width_diff_ctx, -262143, 262142);
let hdiff = decode_num(&mut zp, &mut symbol_height_diff_ctx, -262143, 262142);
let cbm_w = dict[index].width + wdiff;
let cbm_h = dict[index].height + hdiff;
check_pixel_budget(cbm_w, cbm_h, &mut total_sym_pixels)?;
let cbm = decode_bitmap_ref(
&mut zp,
&mut refinement_bitmap_ctx,
&mut refinement_bitmap_ctx_p,
cbm_w,
cbm_h,
&dict[index],
pool,
)?;
dict.push(cbm.crop_and_recycle(pool));
}
9 => {}
10 => {
let length = decode_num(&mut zp, &mut comment_length_ctx, 0, 262142) as usize;
let length = length.min(MAX_COMMENT_BYTES);
for _ in 0..length {
decode_num(&mut zp, &mut comment_octet_ctx, 0, 255);
}
}
11 => break,
_ => return Err(Jb2Error::UnexpectedDictRecordType),
}
}
Ok(Jb2Dict {
symbols: dict.into_symbols(),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn assets_path() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("references/djvujs/library/assets")
}
fn golden_path() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/golden/jb2")
}
fn extract_sjbz(djvu_data: &[u8]) -> Vec<u8> {
let file = crate::iff::parse(djvu_data).unwrap();
let sjbz = file.root.find_first(b"Sjbz").unwrap();
sjbz.data().to_vec()
}
fn extract_first_page_sjbz(djvu_data: &[u8]) -> Vec<u8> {
let file = crate::iff::parse(djvu_data).unwrap();
let page_form = file
.root
.children()
.iter()
.find(|c| {
matches!(c, crate::iff::Chunk::Form { secondary_id, .. }
if secondary_id == b"DJVU")
})
.expect("no DJVU form");
page_form.find_first(b"Sjbz").unwrap().data().to_vec()
}
fn find_page_form_data(djvu_data: &[u8], page: usize) -> Vec<u8> {
let file = crate::iff::parse(djvu_data).unwrap();
let mut idx = 0;
for chunk in file.root.children() {
if matches!(chunk, crate::iff::Chunk::Form { secondary_id, .. }
if secondary_id == b"DJVU")
{
if idx == page {
return chunk.find_first(b"Sjbz").unwrap().data().to_vec();
}
idx += 1;
}
}
panic!("page {} not found", page);
}
fn find_djvi_djbz_data(djvu_data: &[u8]) -> Vec<u8> {
let file = crate::iff::parse(djvu_data).unwrap();
for chunk in file.root.children() {
if let crate::iff::Chunk::Form { secondary_id, .. } = chunk
&& secondary_id == b"DJVI"
&& let Some(djbz) = chunk.find_first(b"Djbz")
{
return djbz.data().to_vec();
}
}
panic!("DJVI with Djbz not found");
}
#[test]
fn jb2_new_decode_boy_jb2_mask() {
let djvu = std::fs::read(assets_path().join("boy_jb2.djvu")).unwrap();
let sjbz = extract_sjbz(&djvu);
let bitmap = decode(&sjbz, None).unwrap();
let actual_pbm = bitmap.to_pbm();
let expected_pbm = std::fs::read(golden_path().join("boy_jb2_mask.pbm")).unwrap();
assert_eq!(
actual_pbm.len(),
expected_pbm.len(),
"PBM size mismatch: got {} expected {}",
actual_pbm.len(),
expected_pbm.len()
);
assert_eq!(actual_pbm, expected_pbm, "boy_jb2_mask pixel mismatch");
}
#[test]
fn jb2_new_decode_carte_p1_mask() {
let djvu = std::fs::read(assets_path().join("carte.djvu")).unwrap();
let sjbz = extract_first_page_sjbz(&djvu);
let bitmap = decode(&sjbz, None).unwrap();
let actual_pbm = bitmap.to_pbm();
let expected_pbm = std::fs::read(golden_path().join("carte_p1_mask.pbm")).unwrap();
assert_eq!(
actual_pbm.len(),
expected_pbm.len(),
"carte_p1_mask size mismatch"
);
assert_eq!(actual_pbm, expected_pbm, "carte_p1_mask pixel mismatch");
}
#[test]
fn jb2_new_decode_djvu3spec_p1_mask() {
let djvu = std::fs::read(assets_path().join("DjVu3Spec_bundled.djvu")).unwrap();
let file = crate::iff::parse(&djvu).unwrap();
let mut idx = 0usize;
let mut page_form_opt: Option<&crate::iff::Chunk> = None;
for chunk in file.root.children() {
if matches!(chunk, crate::iff::Chunk::Form { secondary_id, .. }
if secondary_id == b"DJVU")
{
if idx == 0 {
page_form_opt = Some(chunk);
break;
}
idx += 1;
}
}
let page_form = page_form_opt.expect("page 0 not found");
let djbz_data = page_form.find_first(b"Djbz").unwrap().data().to_vec();
let sjbz_data = page_form.find_first(b"Sjbz").unwrap().data().to_vec();
let shared_dict = decode_dict(&djbz_data, None).unwrap();
let bitmap = decode(&sjbz_data, Some(&shared_dict)).unwrap();
let actual_pbm = bitmap.to_pbm();
let expected_pbm = std::fs::read(golden_path().join("djvu3spec_p1_mask.pbm")).unwrap();
assert_eq!(
actual_pbm.len(),
expected_pbm.len(),
"djvu3spec_p1_mask size mismatch"
);
assert_eq!(actual_pbm, expected_pbm, "djvu3spec_p1_mask pixel mismatch");
}
#[test]
fn jb2_new_decode_djvu3spec_p2_mask() {
let djvu = std::fs::read(assets_path().join("DjVu3Spec_bundled.djvu")).unwrap();
let djbz_data = find_djvi_djbz_data(&djvu);
let sjbz_data = find_page_form_data(&djvu, 1);
let shared_dict = decode_dict(&djbz_data, None).unwrap();
let bitmap = decode(&sjbz_data, Some(&shared_dict)).unwrap();
let actual_pbm = bitmap.to_pbm();
let expected_pbm = std::fs::read(golden_path().join("djvu3spec_p2_mask.pbm")).unwrap();
assert_eq!(
actual_pbm.len(),
expected_pbm.len(),
"djvu3spec_p2_mask size mismatch"
);
assert_eq!(actual_pbm, expected_pbm, "djvu3spec_p2_mask pixel mismatch");
}
#[test]
fn jb2_new_decode_navm_fgbz_p1_mask() {
let djvu = std::fs::read(assets_path().join("navm_fgbz.djvu")).unwrap();
let djbz_data = find_djvi_djbz_data(&djvu);
let sjbz_data = find_page_form_data(&djvu, 0);
let shared_dict = decode_dict(&djbz_data, None).unwrap();
let bitmap = decode(&sjbz_data, Some(&shared_dict)).unwrap();
let actual_pbm = bitmap.to_pbm();
let expected_pbm = std::fs::read(golden_path().join("navm_fgbz_p1_mask.pbm")).unwrap();
assert_eq!(
actual_pbm.len(),
expected_pbm.len(),
"navm_fgbz_p1_mask size mismatch"
);
assert_eq!(actual_pbm, expected_pbm, "navm_fgbz_p1_mask pixel mismatch");
}
#[test]
fn jb2_new_empty_input_does_not_panic() {
let _ = decode(&[], None);
}
#[test]
fn jb2_new_single_byte_does_not_panic() {
let _ = decode(&[0x00], None);
}
#[test]
fn jb2_new_all_zeros_does_not_panic() {
let _ = decode(&[0u8; 64], None);
}
#[test]
fn jb2_new_dict_empty_input_does_not_panic() {
let _ = decode_dict(&[], None);
}
#[test]
fn jb2_new_dict_truncated_does_not_panic() {
let _ = decode_dict(&[0u8; 8], None);
}
#[test]
fn jb2_error_variants_have_meaningful_messages() {
assert!(Jb2Error::BadHeaderFlag.to_string().contains("flag"));
assert!(Jb2Error::InheritedDictTooLarge.to_string().contains("dict"));
assert!(Jb2Error::MissingSharedDict.to_string().contains("dict"));
assert!(Jb2Error::ImageTooLarge.to_string().contains("large"));
assert!(Jb2Error::EmptyDictReference.to_string().contains("dict"));
assert!(Jb2Error::InvalidSymbolIndex.to_string().contains("symbol"));
assert!(Jb2Error::UnknownRecordType.to_string().contains("record"));
assert!(
Jb2Error::UnexpectedDictRecordType
.to_string()
.contains("record")
);
assert!(Jb2Error::ZpInitFailed.to_string().contains("ZP"));
assert!(Jb2Error::Truncated.to_string().contains("truncated"));
}
#[test]
fn jb2_image_size_overflow_guard() {
let w: usize = 65536;
let h: usize = 65537;
let safe_size = w.saturating_mul(h);
assert!(
safe_size > 64 * 1024 * 1024,
"saturating_mul must exceed MAX_PIXELS"
);
}
#[test]
fn test_decode_empty_data() {
let result = decode(&[], None);
assert!(result.is_err());
}
#[test]
fn test_decode_dict_empty() {
let result = decode_dict(&[], None);
assert!(result.is_err());
}
#[test]
fn test_decode_indexed_empty() {
let result = decode_indexed(&[], None);
assert!(result.is_err());
}
#[test]
fn blit_negative_width_does_not_hang() {
let start = std::time::Instant::now();
let _ = decode(&[0x7e, 0x00, 0x0c], None);
assert!(start.elapsed().as_secs() < 2, "took {:?}", start.elapsed());
}
#[test]
fn jb2_pool_decode_matches_regular_decode_carte() {
let djvu = std::fs::read(assets_path().join("carte.djvu")).unwrap();
let sjbz = extract_first_page_sjbz(&djvu);
let regular = decode(&sjbz, None).expect("regular decode");
let mut pool = Vec::new();
let pooled = decode_image_with_pool(&sjbz, None, &mut pool).expect("pool decode");
assert_eq!(regular.width, pooled.width, "width must match");
assert_eq!(regular.height, pooled.height, "height must match");
assert_eq!(regular.data, pooled.data, "pixel data must be identical");
assert!(
pool.capacity() > 0,
"pool must have been used (capacity > 0 after decode)"
);
}
}
#[cfg(test)]
mod regression_fuzz2 {
use super::*;
#[test]
fn huge_symbol_from_small_input_does_not_hang() {
let data = &[
0x7f, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
let start = std::time::Instant::now();
let _ = decode(data, None);
let limit_secs = if cfg!(debug_assertions) { 8 } else { 2 };
assert!(
start.elapsed().as_secs() < limit_secs,
"took {:?}",
start.elapsed()
);
}
}