1#![allow(missing_docs)]
8#![allow(dead_code)]
9#![allow(unused_variables)]
10#![allow(unused_mut)]
11
12pub use ndarray::Array2;
14
15#[derive(Debug)]
17pub enum Jbig2Error {
18 BufferSizeMismatch {
20 expected: usize,
21 actual: usize,
22 width: u32,
23 height: u32,
24 ratio: f64,
25 },
26
27 BufferTooSmall { expected: usize, actual: usize },
29
30 ArrayShapeError { source: ndarray::ShapeError },
32
33 EncodingFailed { message: String },
35
36 DictionaryFailed { message: String },
38
39 PackedDataDetected,
41
42 StreamCountMismatch { expected: usize, actual: usize },
44
45 SegmentWriteFailed {
47 segment_type: String,
48 message: String,
49 },
50}
51
52impl std::fmt::Display for Jbig2Error {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 Jbig2Error::BufferSizeMismatch {
56 expected,
57 actual,
58 width,
59 height,
60 ratio,
61 } => {
62 write!(
63 f,
64 "Input buffer size mismatch: expected {}, got {} for {}x{} image (ratio: {:.3})",
65 expected, actual, width, height, ratio
66 )
67 }
68 Jbig2Error::BufferTooSmall { expected, actual } => {
69 write!(f, "Buffer too small: expected {}, got {}", expected, actual)
70 }
71 Jbig2Error::ArrayShapeError { source } => {
72 write!(f, "Array shape error: {}", source)
73 }
74 Jbig2Error::EncodingFailed { message } => {
75 write!(f, "Encoding failed: {}", message)
76 }
77 Jbig2Error::DictionaryFailed { message } => {
78 write!(f, "Dictionary creation failed: {}", message)
79 }
80 Jbig2Error::PackedDataDetected => {
81 write!(
82 f,
83 "Input appears to be packed binary data (1 bit per pixel), but JBIG2 encoder expects unpacked data (1 byte per pixel)"
84 )
85 }
86 Jbig2Error::StreamCountMismatch { expected, actual } => {
87 write!(
88 f,
89 "Expected {} stream(s) for single image encoding, got {}",
90 expected, actual
91 )
92 }
93 Jbig2Error::SegmentWriteFailed {
94 segment_type,
95 message,
96 } => {
97 write!(f, "Failed to write {} segment: {}", segment_type, message)
98 }
99 }
100 }
101}
102
103impl std::error::Error for Jbig2Error {
104 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
105 match self {
106 Jbig2Error::ArrayShapeError { source } => Some(source),
107 _ => None,
108 }
109 }
110}
111
112impl From<ndarray::ShapeError> for Jbig2Error {
113 fn from(source: ndarray::ShapeError) -> Self {
114 Jbig2Error::ArrayShapeError { source }
115 }
116}
117
118pub mod jbig2;
120pub mod jbig2arith;
121#[cfg(feature = "symboldict")]
122pub mod jbig2cc;
123pub mod jbig2classify;
124pub mod jbig2comparator;
125pub mod jbig2context;
126pub mod jbig2cost;
127pub mod jbig2enc;
128pub mod jbig2halftone;
129pub mod jbig2shared;
130pub(crate) mod jbig2simd;
131pub mod jbig2structs;
132pub mod jbig2sym;
133pub mod jbig2unify;
134
135pub use crate::jbig2arith::Jbig2ArithCoder;
137#[cfg(feature = "symboldict")]
138pub use jbig2cc::{BBox, CC, CCImage, Run, analyze_page, extract_symbols_for_jbig2};
139pub use jbig2enc::{PdfSplitOutput, encode_document};
140pub use jbig2structs::Jbig2Config;
141
142use jbig2enc::Jbig2Encoder;
143use jbig2sym::binary_pixels_to_bitimage;
144use std::env;
145
146#[derive(Debug, Clone)]
148pub struct Jbig2EncodeResult {
149 pub global_data: Option<Vec<u8>>,
151 pub page_data: Vec<u8>,
153}
154
155#[derive(Debug, Clone)]
157pub struct Jbig2Context {
158 config: Jbig2Config,
160 pdf_mode: bool,
161}
162
163impl Default for Jbig2Context {
164 fn default() -> Self {
165 Self {
166 config: Jbig2Config::default(),
167 pdf_mode: false,
168 }
169 }
170}
171
172impl Jbig2Context {
173 pub fn new() -> Self {
175 Self::default()
176 }
177
178 pub fn with_pdf_mode(pdf_mode: bool) -> Self {
180 Self {
181 config: Jbig2Config::default(),
182 pdf_mode,
183 }
184 }
185
186 pub fn with_config(config: Jbig2Config, pdf_mode: bool) -> Self {
188 Self { config, pdf_mode }
189 }
190
191 pub fn with_lossless_config(pdf_mode: bool) -> Self {
194 Self {
195 config: Jbig2Config::lossless(),
196 pdf_mode,
197 }
198 }
199
200 pub fn get_pdf_mode(&self) -> bool {
202 self.pdf_mode
203 }
204
205 pub fn get_symbol_mode(&self) -> bool {
207 self.config.symbol_mode
208 }
209
210 pub fn get_dpi(&self) -> u32 {
212 if self.config.generic.dpi == 0 {
213 300
214 } else {
215 self.config.generic.dpi
216 }
217 }
218}
219
220pub fn encode_single_image(
235 input: &[u8],
236 width: u32,
237 height: u32,
238 pdf_mode: bool,
239) -> Result<Jbig2EncodeResult, Jbig2Error> {
240 let bitimage = validate_and_build_bitimage(input, width, height)?;
241 encode_single_bitimage(bitimage, Jbig2Context::with_pdf_mode(pdf_mode))
242}
243
244pub fn encode_single_image_with_config(
258 input: &[u8],
259 width: u32,
260 height: u32,
261 context: Jbig2Context,
262) -> Result<Jbig2EncodeResult, Jbig2Error> {
263 let bitimage = validate_and_build_bitimage(input, width, height)?;
264 encode_single_bitimage(bitimage, context)
265}
266
267pub fn encode_single_image_lossless(
281 input: &[u8],
282 width: u32,
283 height: u32,
284 pdf_mode: bool,
285) -> Result<Jbig2EncodeResult, Jbig2Error> {
286 let bitimage = validate_and_build_bitimage(input, width, height)?;
287 encode_single_bitimage(bitimage, Jbig2Context::with_lossless_config(pdf_mode))
288}
289
290pub fn encode_document_pdf_split(
313 images: &[Array2<u8>],
314 config: &Jbig2Config,
315) -> Result<PdfSplitOutput, Jbig2Error> {
316 let mut enc_config = config.clone();
317 enc_config.want_full_headers = false;
318 if !enc_config.symbol_mode {
319 enc_config.refine = false;
320 enc_config.text_refine = false;
321 }
322
323 let mut encoder = Jbig2Encoder::new(&enc_config);
324 for image in images {
325 encoder
326 .add_page(image)
327 .map_err(|e| Jbig2Error::EncodingFailed {
328 message: e.to_string(),
329 })?;
330 }
331
332 encoder
333 .flush_pdf_split()
334 .map_err(|e| Jbig2Error::EncodingFailed {
335 message: e.to_string(),
336 })
337}
338
339fn validate_and_build_bitimage(
340 input: &[u8],
341 width: u32,
342 height: u32,
343) -> Result<jbig2sym::BitImage, Jbig2Error> {
344 let expected_len = width as usize * height as usize;
345 if input.len() < expected_len {
346 let packed_size = (width as usize * height as usize).div_ceil(8);
347 if input.len() == packed_size {
348 return Err(Jbig2Error::PackedDataDetected);
349 }
350
351 return Err(Jbig2Error::BufferSizeMismatch {
352 expected: expected_len,
353 actual: input.len(),
354 width,
355 height,
356 ratio: input.len() as f64 / expected_len as f64,
357 });
358 }
359
360 binary_pixels_to_bitimage(&input[..expected_len], width as usize, height as usize)
361 .map_err(|message| Jbig2Error::EncodingFailed { message })
362}
363
364fn encode_single_bitimage(
365 bitimage: jbig2sym::BitImage,
366 ctx: Jbig2Context,
367) -> Result<Jbig2EncodeResult, Jbig2Error> {
368 let mut enc_config = ctx.config.clone();
369 enc_config.want_full_headers = !ctx.get_pdf_mode();
370 if !enc_config.symbol_mode {
371 enc_config.refine = false;
372 enc_config.text_refine = false;
373 }
374
375 let mut encoder = Jbig2Encoder::new(&enc_config);
376 encoder
377 .add_page_bitimage(bitimage)
378 .map_err(|e| Jbig2Error::EncodingFailed {
379 message: e.to_string(),
380 })?;
381
382 if ctx.get_pdf_mode() {
383 let split = encoder
389 .flush_pdf_split()
390 .map_err(|e| Jbig2Error::EncodingFailed {
391 message: e.to_string(),
392 })?;
393 let n = split.page_streams.len();
394 if n != 1 {
395 return Err(Jbig2Error::StreamCountMismatch {
396 expected: 1,
397 actual: n,
398 });
399 }
400 let page_data = split.page_streams.into_iter().next().unwrap();
401 Ok(Jbig2EncodeResult {
402 global_data: split.global_segments,
403 page_data,
404 })
405 } else {
406 let page_data = encoder.flush().map_err(|e| Jbig2Error::EncodingFailed {
407 message: e.to_string(),
408 })?;
409 Ok(Jbig2EncodeResult {
410 global_data: None,
411 page_data,
412 })
413 }
414}
415
416pub fn get_version() -> String {
418 let enc_version = option_env!("JBIG2ENC_VERSION").unwrap_or("unknown");
419 format!(
420 "jbig2-rs {}, jbig2enc {}",
421 env!("CARGO_PKG_VERSION"),
422 enc_version
423 )
424}
425
426pub fn get_build_info() -> String {
428 let build_ts = option_env!("VERGEN_BUILD_TIMESTAMP").unwrap_or("unknown");
429 let build_type = if cfg!(debug_assertions) {
430 "debug"
431 } else {
432 "release"
433 };
434 format!("{} (built with {})", build_ts, build_type)
435}