use std::collections::HashMap;
use leptonica::Pix;
use crate::arith::{ArithEncoder, IntProc};
use crate::error::Jbig2Error;
pub struct SymbolInstance {
pub x: i32,
pub y: i32,
pub class_id: usize,
}
pub struct TextRegionConfig<'a> {
pub symmap: &'a HashMap<usize, usize>,
pub symmap2: Option<&'a HashMap<usize, usize>>,
pub global_sym_count: usize,
pub symbits: u32,
pub strip_width: u32,
pub unborder: bool,
pub border_size: u32,
}
pub struct TextRegionResult {
pub data: Vec<u8>,
}
pub fn encode_text_region(
instances: &[SymbolInstance],
symbols: &[Pix],
cfg: &TextRegionConfig<'_>,
) -> Result<TextRegionResult, Jbig2Error> {
if !matches!(cfg.strip_width, 1 | 2 | 4 | 8) {
return Err(Jbig2Error::InvalidInput(format!(
"invalid strip_width {}: must be 1, 2, 4, or 8",
cfg.strip_width
)));
}
if cfg.symbits > 31 {
return Err(Jbig2Error::InvalidInput(format!(
"symbits {} exceeds maximum of 31",
cfg.symbits
)));
}
if instances.is_empty() {
return Ok(TextRegionResult { data: Vec::new() });
}
let max_symid = 1usize << cfg.symbits;
let symids: Vec<usize> = instances
.iter()
.map(|inst| {
if inst.class_id >= symbols.len() {
return Err(Jbig2Error::InvalidInput(format!(
"class_id {} out of bounds (symbols.len() = {})",
inst.class_id,
symbols.len()
)));
}
let symid = resolve_symid(inst.class_id, cfg)?;
if symid >= max_symid {
return Err(Jbig2Error::InvalidInput(format!(
"resolved symid {symid} does not fit in {} bits",
cfg.symbits
)));
}
Ok(symid)
})
.collect::<Result<_, _>>()?;
let mut sorted: Vec<usize> = (0..instances.len()).collect();
sorted.sort_by_key(|&i| instances[i].y);
let mut encoder = ArithEncoder::new();
encoder.encode_int(IntProc::Dt, 0);
let sw = cfg.strip_width as i32;
let mut stript: i32 = 0;
let mut firsts: i32 = 0;
let mut i = 0;
while i < sorted.len() {
let y0 = instances[sorted[i]].y;
let height = (y0 / sw) * sw;
let j = sorted[i..]
.iter()
.position(|&idx| instances[idx].y >= height + sw)
.map(|p| i + p)
.unwrap_or(sorted.len());
let strip_slice = &sorted[i..j];
let mut strip: Vec<usize> = strip_slice.to_vec();
strip.sort_by_key(|&idx| instances[idx].x);
let delta_t = (height - stript) / sw;
encoder.encode_int(IntProc::Dt, delta_t);
stript = height;
let mut first_in_strip = true;
let mut curs: i32 = 0;
for &idx in &strip {
let inst = &instances[idx];
let symid = symids[idx];
if first_in_strip {
first_in_strip = false;
let delta_fs = inst.x - firsts;
encoder.encode_int(IntProc::Fs, delta_fs);
firsts += delta_fs;
curs = firsts;
} else {
let delta_s = inst.x - curs;
encoder.encode_int(IntProc::Ds, delta_s);
curs += delta_s;
}
if sw > 1 {
let delta_t_in = inst.y - stript;
encoder.encode_int(IntProc::It, delta_t_in);
}
encoder.encode_iaid(cfg.symbits, symid as u32);
let sym_w = symbols[inst.class_id].width() as i32;
let effective_w = if cfg.unborder {
(sym_w - 2 * cfg.border_size as i32).max(1)
} else {
sym_w
};
curs += effective_w - 1;
}
encoder.encode_oob(IntProc::Ds);
i = j;
}
encoder.encode_final();
Ok(TextRegionResult {
data: encoder.to_vec(),
})
}
fn resolve_symid(class_id: usize, cfg: &TextRegionConfig<'_>) -> Result<usize, Jbig2Error> {
if let Some(&id) = cfg.symmap.get(&class_id) {
return Ok(id);
}
if let Some(sm2) = cfg.symmap2
&& let Some(&id) = sm2.get(&class_id)
{
return Ok(id + cfg.global_sym_count);
}
Err(Jbig2Error::InvalidInput(format!(
"class_id {class_id} not found in symmap or symmap2"
)))
}