Skip to main content

jbig2enc_rust/
jbig2structs.rs

1/// Pruned Rust equivalents of JBIG2 structs and segment headers
2use byteorder::{BigEndian, WriteBytesExt};
3use std::io::{self, Write};
4
5#[cfg(feature = "trace_encoder")]
6use log::debug;
7
8#[cfg(not(feature = "trace_encoder"))]
9use crate::debug;
10
11/// JBIG2 file format magic number
12pub const JB2_MAGIC: &[u8; 10] = b"\x97JBIG2\r\n\x1A\n";
13
14/// JBIG2 file format version
15pub const JB2_VERSION: u8 = 0x02;
16
17/// Top-level configuration for JBIG2 encoding
18#[derive(Debug, Clone)]
19pub struct Jbig2Config {
20    // Generic region settings
21    pub generic: GenericRegionConfig,
22
23    // Symbol dictionary settings
24    pub sd_template: u8,      // Symbol dictionary template (0-3)
25    pub sd_at: [(i8, i8); 4], // Symbol dictionary AT pixels
26
27    // Text region settings
28    pub text_ds_offset: i8,       // SBDSOFFSET (signed 5-bit)
29    pub text_refine: bool,        // SBREFINE
30    pub text_log_strips: u8,      // LOGSBSTRIPS (0-3)
31    pub text_ref_corner: u8, // REFCORNER (0-3): 0=BOTTOMLEFT, 1=TOPLEFT, 2=BOTTOMRIGHT, 3=TOPRIGHT
32    pub text_transposed: bool, // TRANSPOSED
33    pub text_comb_op: u8,    // SBCOMBOP (0-4)
34    pub text_refine_template: u8, // SBRTEMPLATE (0 or 1)
35
36    // Halftone region settings
37    pub halftone: HalftoneConfig,
38
39    // Global settings
40    pub dpi: u32,
41    pub symbol_mode: bool,
42    pub refine: bool,
43    pub refine_template: u8,
44    pub duplicate_line_removal: bool,
45    pub auto_thresh: bool,
46    pub hash: bool,
47    pub want_full_headers: bool,
48    pub is_lossless: bool,
49    pub default_pixel: bool,
50    /// Pixel-error tolerance divisor for symbol matching.
51    /// max_err = (width * height) / match_tolerance
52    /// Lower = more aggressive matching (smaller files, lower quality).
53    /// Typical range: 4 (aggressive) to 10 (conservative). Default: 7.
54    pub match_tolerance: u32,
55    pub lossy_symbol_mode: LossySymbolMode,
56    pub sym_unify_min_class_size: usize,
57    pub sym_unify_max_err: u32,
58    pub sym_unify_max_dx: i32,
59    pub sym_unify_max_dy: i32,
60    pub sym_unify_class_accept_limit: u32,
61    pub sym_unify_core_close_threshold: u32,
62    pub sym_unify_min_core_ratio_permille: u16,
63    pub sym_unify_min_class_usage: usize,
64    pub sym_unify_min_page_span: usize,
65    pub sym_unify_min_estimated_gain: i32,
66    pub sym_unify_context_mode: SymbolContextMode,
67    pub sym_unify_border_score_slack: u32,
68    pub sym_unify_score_rescue_slack: u32,
69    pub sym_unify_max_border_outside_ink: u32,
70    pub sym_unify_refine_min_subcluster_size: usize,
71    pub sym_unify_refine_min_usage: usize,
72    pub sym_unify_refine_min_page_span: usize,
73    pub sym_unify_refine_max_score: u32,
74    pub sym_unify_refine_min_gain: i32,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum LossySymbolMode {
79    Off,
80    SymbolUnify,
81}
82
83/// Configuration for halftone encoding
84#[derive(Debug, Clone, Copy)]
85pub struct HalftoneConfig {
86    /// The grid size (M x M) for decimation and pattern generation.
87    pub grid_size_m: u32,
88    /// The number of quantization levels (N).
89    pub quant_levels_n: u32,
90    /// Sharpening control parameter (L), typically between 0.0 and 2.0.
91    pub sharpening_l: f32,
92    /// The template to use for encoding grayscale bitplanes (usually 0).
93    pub template: u8,
94    /// Whether to use lossless encoding (true) or lossy encoding (false).
95    /// Lossless guarantees bit-perfect reconstruction but produces larger files.
96    pub lossless: bool,
97    /// Diagnostic backend for the gray-value image inside the halftone region.
98    /// When true, use MMR/T.6 coding instead of arithmetic Annex C coding.
99    pub gray_mmr: bool,
100    /// Backend for the pattern dictionary bitmap.
101    /// MMR keeps the tiled halftone cells exact and avoids arithmetic drift in the
102    /// collective pattern bitmap.
103    pub dict_mmr: bool,
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub enum SymbolContextMode {
108    WeightedJaccard,
109    Cosine,
110    Hybrid,
111}
112
113impl Default for HalftoneConfig {
114    fn default() -> Self {
115        Self {
116            grid_size_m: 4,     // 4x4 grid is a common default
117            quant_levels_n: 16, // 16 gray levels
118            sharpening_l: 0.5,  // A moderate amount of sharpening
119            template: 0,
120            lossless: false, // Default to lossy encoding for better compression
121            gray_mmr: false,
122            dict_mmr: true,
123        }
124    }
125}
126
127impl Default for Jbig2Config {
128    fn default() -> Self {
129        Self {
130            generic: GenericRegionConfig::default(),
131            sd_template: 0,
132            sd_at: [(0, 0), (0, 0), (0, 0), (0, 0)],
133            text_ds_offset: 0,
134            text_refine: false,
135            text_log_strips: 0,
136            text_ref_corner: 1,
137            text_transposed: false,
138            text_comb_op: 0,
139            text_refine_template: 0,
140            halftone: HalftoneConfig::default(),
141            dpi: 300,
142            symbol_mode: false,
143            refine: false, // Refinement requires symbol_mode; both disabled until global dictionary encoding is fixed
144            refine_template: 0,
145            duplicate_line_removal: true,
146            auto_thresh: true,
147            hash: true,
148            want_full_headers: true,
149            is_lossless: false,
150            default_pixel: false,
151            match_tolerance: 7,
152            lossy_symbol_mode: LossySymbolMode::Off,
153            sym_unify_min_class_size: 2,
154            sym_unify_max_err: 12,
155            sym_unify_max_dx: 1,
156            sym_unify_max_dy: 1,
157            sym_unify_class_accept_limit: 16,
158            sym_unify_core_close_threshold: 11,
159            sym_unify_min_core_ratio_permille: 350,
160            sym_unify_min_class_usage: 2,
161            sym_unify_min_page_span: 1,
162            sym_unify_min_estimated_gain: 0,
163            sym_unify_context_mode: SymbolContextMode::Hybrid,
164            sym_unify_border_score_slack: 4,
165            sym_unify_score_rescue_slack: 2,
166            sym_unify_max_border_outside_ink: 1,
167            sym_unify_refine_min_subcluster_size: 2,
168            sym_unify_refine_min_usage: 12,
169            sym_unify_refine_min_page_span: 3,
170            sym_unify_refine_max_score: 8,
171            sym_unify_refine_min_gain: 20,
172        }
173    }
174}
175
176impl Jbig2Config {
177    /// Creates a new configuration with default values
178    pub fn new() -> Self {
179        Self::default()
180    }
181
182    /// Creates a configuration optimized for text documents
183    pub fn text() -> Self {
184        let mut cfg = Self::default();
185        cfg.symbol_mode = true;
186        cfg.auto_thresh = true;
187        cfg.duplicate_line_removal = true;
188        // Refinement remains opt-in on the preset because SBREFINE=1 adds
189        // per-instance overhead. Enable it explicitly when clustering or
190        // near-match preservation is expected to pay off.
191        cfg
192    }
193
194    pub fn text_symbol_unify() -> Self {
195        let mut cfg = Self::text();
196        cfg.lossy_symbol_mode = LossySymbolMode::SymbolUnify;
197        cfg.refine = false;
198        cfg.text_refine = false;
199        cfg.sym_unify_min_class_size = 2;
200        cfg.sym_unify_max_err = 12;
201        cfg.sym_unify_max_dx = 1;
202        cfg.sym_unify_max_dy = 1;
203        cfg.sym_unify_class_accept_limit = 16;
204        cfg.sym_unify_core_close_threshold = 11;
205        cfg.sym_unify_min_core_ratio_permille = 350;
206        cfg.sym_unify_min_class_usage = 2;
207        cfg.sym_unify_min_page_span = 1;
208        cfg.sym_unify_min_estimated_gain = 0;
209        cfg.sym_unify_context_mode = SymbolContextMode::Hybrid;
210        cfg.sym_unify_border_score_slack = 4;
211        cfg.sym_unify_score_rescue_slack = 2;
212        cfg.sym_unify_max_border_outside_ink = 1;
213        cfg.sym_unify_refine_min_subcluster_size = 2;
214        cfg.sym_unify_refine_min_usage = 12;
215        cfg.sym_unify_refine_min_page_span = 3;
216        cfg.sym_unify_refine_max_score = 8;
217        cfg.sym_unify_refine_min_gain = 20;
218        cfg
219    }
220
221    #[inline]
222    pub fn uses_symbol_unify(&self) -> bool {
223        self.lossy_symbol_mode == LossySymbolMode::SymbolUnify
224    }
225
226    #[inline]
227    pub fn uses_lossy_symbol_dictionary(&self) -> bool {
228        self.lossy_symbol_mode != LossySymbolMode::Off
229    }
230
231    /// Creates a configuration for lossless image encoding
232    pub fn lossless() -> Self {
233        let mut cfg = Self::default();
234        cfg.symbol_mode = false;
235        cfg.refine = false; // Disable refinement when symbol mode is disabled
236        cfg.is_lossless = true;
237        cfg.duplicate_line_removal = false;
238        cfg
239    }
240}
241
242/// JBIG2 segment types as defined in the specification
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
244pub enum SegmentType {
245    #[default]
246    SymbolDictionary = 0,
247    IntermediateTextRegion = 4,
248    ImmediateTextRegion = 6,
249    ImmediateLosslessTextRegion = 7,
250    PatternDictionary = 16,
251    IntermediateHalftoneRegion = 20,
252    ImmediateHalftoneRegion = 22,
253    ImmediateLosslessHalftoneRegion = 23,
254    IntermediateGenericRegion = 36,
255    ImmediateGenericRegion = 38,
256    ImmediateLosslessGenericRegion = 39,
257    IntermediateGenericRefinementRegion = 40,
258    ImmediateGenericRefinementRegion = 42,
259    ImmediateLosslessGenericRefinementRegion = 43,
260    PageInformation = 48,
261    EndOfPage = 49,
262    EndOfStripe = 50,
263    EndOfFile = 51,
264    Profiles = 52,
265    Tables = 53,
266    ColorPalette = 54,
267    FileHeader = 56,
268    Extension = 62,
269}
270
271impl TryFrom<u8> for SegmentType {
272    type Error = io::Error;
273
274    fn try_from(value: u8) -> Result<Self, Self::Error> {
275        match value {
276            0 => Ok(SegmentType::SymbolDictionary),
277            4 => Ok(SegmentType::IntermediateTextRegion),
278            6 => Ok(SegmentType::ImmediateTextRegion),
279            7 => Ok(SegmentType::ImmediateLosslessTextRegion),
280            16 => Ok(SegmentType::PatternDictionary),
281            20 => Ok(SegmentType::IntermediateHalftoneRegion),
282            22 => Ok(SegmentType::ImmediateHalftoneRegion),
283            23 => Ok(SegmentType::ImmediateLosslessHalftoneRegion),
284            36 => Ok(SegmentType::IntermediateGenericRegion),
285            38 => Ok(SegmentType::ImmediateGenericRegion),
286            39 => Ok(SegmentType::ImmediateLosslessGenericRegion),
287            40 => Ok(SegmentType::IntermediateGenericRefinementRegion),
288            42 => Ok(SegmentType::ImmediateGenericRefinementRegion),
289            43 => Ok(SegmentType::ImmediateLosslessGenericRefinementRegion),
290            48 => Ok(SegmentType::PageInformation),
291            49 => Ok(SegmentType::EndOfPage),
292            50 => Ok(SegmentType::EndOfStripe),
293            51 => Ok(SegmentType::EndOfFile),
294            52 => Ok(SegmentType::Profiles),
295            53 => Ok(SegmentType::Tables),
296            54 => Ok(SegmentType::ColorPalette),
297            56 => Ok(SegmentType::FileHeader),
298            62 => Ok(SegmentType::Extension),
299            _ => Err(io::Error::new(
300                io::ErrorKind::InvalidData,
301                format!("Invalid segment type: {}", value),
302            )),
303        }
304    }
305}
306
307// -----------------------------------------------------------------------------
308// File header (magic + flags + number of pages)
309// -----------------------------------------------------------------------------
310
311/// Represents the JBIG2 file header as per the specification (§D.4.1)
312#[derive(Debug)]
313pub struct FileHeader {
314    pub organisation_type: bool, // 1 bit: 0 = sequential, 1 = random-access
315    pub unknown_n_pages: bool,   // 1 bit: 1 = number of pages unknown
316    pub n_pages: u32,            // Number of pages (big-endian), omitted if unknown_n_pages is true
317}
318
319impl FileHeader {
320    pub fn to_bytes(&self) -> Vec<u8> {
321        const MAGIC: &[u8] = b"\x97JB2\r\n\x1A\n";
322        let mut buf = Vec::with_capacity(8 + 1 + if self.unknown_n_pages { 0 } else { 4 });
323        buf.extend_from_slice(MAGIC);
324
325        let mut flags = 0u8;
326        if self.organisation_type {
327            flags |= 0x01;
328        }
329        if self.unknown_n_pages {
330            flags |= 0x02;
331        }
332        buf.push(flags);
333
334        if !self.unknown_n_pages {
335            buf.write_u32::<BigEndian>(self.n_pages).unwrap();
336        }
337        buf
338    }
339}
340
341// -----------------------------------------------------------------------------
342// Page information segment payload (§7.4.8)
343// -----------------------------------------------------------------------------
344
345/// Represents the page information segment payload
346#[derive(Debug, Default)]
347pub struct PageInfo {
348    pub width: u32,                 // Page width in pixels
349    pub height: u32,                // Page height in pixels
350    pub xres: u32,                  // X resolution in pixels per inch
351    pub yres: u32,                  // Y resolution in pixels per inch
352    pub is_lossless: bool,          // Bit 0: 1 if lossless
353    pub contains_refinements: bool, // Bit 1: 1 if contains refinement regions
354    pub default_pixel: bool,        // Bit 2: Default pixel value (0 = black, 1 = white)
355    pub default_operator: u8,       // Bits 3-4: Default combination operator (0-3)
356    pub aux_buffers: bool,          // Bit 5: 1 if auxiliary buffers are used
357    pub operator_override: bool,    // Bit 6: 1 if combination operator can be overridden
358    pub reserved: bool,             // Bit 7: Must be 0
359    pub segment_flags: u16,         // Additional segment flags
360}
361
362impl PageInfo {
363    pub fn to_bytes(&self) -> Vec<u8> {
364        let mut buf = Vec::with_capacity(4 * 4 + 1 + 2);
365        buf.write_u32::<BigEndian>(self.width).unwrap();
366        buf.write_u32::<BigEndian>(self.height).unwrap();
367        buf.write_u32::<BigEndian>(self.xres).unwrap();
368        buf.write_u32::<BigEndian>(self.yres).unwrap();
369
370        let mut b = 0u8;
371        if self.is_lossless {
372            b |= 0x01;
373        }
374        if self.contains_refinements {
375            b |= 0x02;
376        }
377        if self.default_pixel {
378            b |= 0x04;
379        }
380        b |= (self.default_operator & 0x03) << 3;
381        if self.aux_buffers {
382            b |= 0x20;
383        }
384        if self.operator_override {
385            b |= 0x40;
386        }
387        // Bit 7 (reserved) remains 0
388        buf.push(b);
389        buf.write_u16::<BigEndian>(self.segment_flags).unwrap();
390        buf
391    }
392}
393
394// -----------------------------------------------------------------------------
395// Generic region parameters (§7.4.6)
396// -----------------------------------------------------------------------------
397
398/// Represents the parameters for a generic region segment as per the JBIG2 specification
399#[derive(Debug, Clone)]
400pub struct GenericRegionParams {
401    pub width: u32,               // Region width in pixels
402    pub height: u32,              // Region height in pixels
403    pub x: u32,                   // X-coordinate of the top-left corner
404    pub y: u32,                   // Y-coordinate of the top-left corner
405    pub comb_operator: u8,        // Combination operator (0-4: OR, AND, XOR, XNOR, REPLACE)
406    pub mmr: bool,                // 1 = MMR coding, 0 = arithmetic coding
407    pub template: u8,             // Generic region template (0-3)
408    pub tpgdon: bool,             // Typical prediction generic decoding on/off
409    pub at: [(i8, i8); 4],        // Adaptive template coordinates (a1x, a1y, ..., a4x, a4y)
410    pub at_pixels: Vec<(i8, i8)>, // Adaptive template pixels (for compatibility)
411}
412
413impl GenericRegionParams {
414    pub fn new(width: u32, height: u32, dpi: u32) -> Self {
415        let at_pixels = vec![(3, -1), (-3, -1), (2, -2), (-2, -2)];
416        Self {
417            width,
418            height,
419            x: 0,
420            y: 0,
421            comb_operator: 4, // REPLACE (safer for single image pages)
422            mmr: false,
423            template: 0,
424            tpgdon: true,
425            at: [(3, -1), (-3, -1), (2, -2), (-2, -2)],
426            at_pixels,
427        }
428    }
429
430    /// Validation to ensure compliance with JBIG2 spec
431    pub fn validate(&self) -> Result<(), &'static str> {
432        if self.template > 3 {
433            return Err("Template ID must be 0–3");
434        }
435        if self.at_pixels.len() > 4 {
436            return Err("Maximum 4 AT pixels allowed");
437        }
438        if self.comb_operator > 4 {
439            return Err("Invalid combination operator");
440        }
441        Ok(())
442    }
443    pub fn to_bytes(&self) -> Vec<u8> {
444        use byteorder::{BigEndian, WriteBytesExt};
445        // 18 bytes (width, height, x, y, comb_op, flags) + AT bytes
446        let at_count = match self.template {
447            0 => 4, // Template 0 writes 4 AT pixels to match C behavior
448            1 => 1, // Template 1 uses 1 AT pixel
449            _ => 0, // Templates 2-3 use 0
450        };
451        let mut buf = Vec::with_capacity(18 + at_count * 2);
452
453        buf.write_u32::<BigEndian>(self.width).unwrap();
454        buf.write_u32::<BigEndian>(self.height).unwrap();
455        buf.write_u32::<BigEndian>(self.x).unwrap();
456        buf.write_u32::<BigEndian>(self.y).unwrap();
457        buf.push(self.comb_operator);
458
459        let mut flags = 0u8;
460        if self.mmr {
461            flags |= 0x01; // Bit 0: MMR (only for MMR coding)
462        }
463        flags |= (self.template & 0x03) << 1; // Bits 1-2: GBTEMPLATE
464        if self.tpgdon {
465            flags |= 0x08; // Bit 3: TPGDON
466        }
467        // Bits 4-7 are reserved and set to 0
468        buf.push(flags);
469
470        // Write AT coordinates to match C implementation behavior:
471        // Template 0: 4 AT pixels (despite JBIG2 spec saying 0)
472        // Template 1: 1 AT pixel
473        // Template 2: 0 AT pixels
474        // Template 3: 0 AT pixels
475        let at_count = match self.template {
476            0 => 4, // Template 0 writes 4 AT pixels to match C behavior
477            1 => 1, // Template 1 uses 1 AT pixel
478            _ => 0, // Templates 2-3 use 0
479        };
480        for i in 0..at_count {
481            buf.push(self.at[i].0 as u8);
482            buf.push(self.at[i].1 as u8);
483        }
484        buf
485    }
486}
487
488/// High-level configuration for generic region segments
489#[derive(Clone, Debug)]
490pub struct GenericRegionConfig {
491    // Segment header parameters
492    pub width: u32,
493    pub height: u32,
494    pub x: u32,
495    pub y: u32,
496    pub comb_operator: u8, // Combination operator (0 = OR, 1 = AND, etc.)
497
498    // Arithmetic encoding parameters
499    pub template: u8,             // Template ID (0–3)
500    pub tpgdon: bool,             // Typical prediction generic decoding
501    pub mmr: bool,                // MMR coding (true) or arithmetic (false)
502    pub at_pixels: Vec<(i8, i8)>, // Adaptive template pixels (dx, dy)
503
504    // Metadata (optional, for page info alignment)
505    pub dpi: u32, // Resolution in DPI
506}
507
508impl GenericRegionConfig {
509    /// Creates a new generic region config with defaults
510    pub fn new(width: u32, height: u32, dpi: u32) -> Self {
511        Self {
512            width,
513            height,
514            x: 0,
515            y: 0,
516            comb_operator: 0, // Default to OR
517            template: 0,      // Default to template 0
518            tpgdon: false,    // Disabled — arithmetic coder does not emit TPGD bits yet
519            // Template 0 default AT pixels to match C encoder (even though spec says none needed)
520            at_pixels: vec![(3, -1), (-3, -1), (2, -2), (-2, -2)],
521            mmr: false, // Default to arithmetic coding
522            dpi,
523        }
524    }
525
526    /// Validation to ensure compliance with JBIG2 spec
527    pub fn validate(&self) -> Result<(), &'static str> {
528        if self.template > 3 {
529            return Err("Template ID must be 0–3");
530        }
531        if self.at_pixels.len() > 4 {
532            return Err("Maximum 4 AT pixels allowed");
533        }
534        if self.comb_operator > 4 {
535            return Err("Invalid combination operator");
536        }
537        Ok(())
538    }
539}
540
541impl Default for GenericRegionConfig {
542    fn default() -> Self {
543        Self::new(0, 0, 300)
544    }
545}
546
547impl From<GenericRegionConfig> for GenericRegionParams {
548    fn from(cfg: GenericRegionConfig) -> Self {
549        let mut at = [(0i8, 0i8); 4];
550        for (i, &(dx, dy)) in cfg.at_pixels.iter().enumerate().take(4) {
551            at[i] = (dx, dy);
552        }
553        GenericRegionParams {
554            width: cfg.width,
555            height: cfg.height,
556            x: cfg.x,
557            y: cfg.y,
558            comb_operator: cfg.comb_operator,
559            mmr: cfg.mmr, // MMR coding flag from config
560            template: cfg.template,
561            tpgdon: cfg.tpgdon,
562            at,
563            at_pixels: cfg.at_pixels.clone(),
564        }
565    }
566}
567
568// -----------------------------------------------------------------------------
569// Symbol dictionary parameters (§7.4.2)
570// -----------------------------------------------------------------------------
571
572/// Represents the parameters for a symbol dictionary segment
573#[derive(Debug)]
574pub struct SymbolDictParams {
575    pub sd_template: u8,   // Symbol dictionary template (0-3)
576    pub at: [(i8, i8); 4], // Adaptive template coordinates (a1x, a1y, ..., a4x, a4y)
577    pub refine_aggregate: bool,
578    pub refine_template: u8,
579    pub refine_at: [(i8, i8); 2],
580    pub exsyms: u32,  // Number of exported symbols
581    pub newsyms: u32, // Number of new symbols
582}
583
584impl SymbolDictParams {
585    pub fn to_bytes(&self) -> Vec<u8> {
586        let mut buf = Vec::with_capacity(
587            2 + 8
588                + if self.refine_aggregate && self.refine_template == 0 {
589                    4
590                } else {
591                    0
592                }
593                + 4
594                + 4,
595        );
596
597        let mut flags = 0u16;
598        flags |= ((self.sd_template & 0x03) as u16) << 10;
599        if self.refine_aggregate {
600            flags |= 1 << 1; // SDREFAGG
601        }
602        if self.refine_aggregate && (self.refine_template & 0x01) == 1 {
603            flags |= 1 << 12; // SDRTEMPLATE
604        }
605
606        buf.write_u16::<BigEndian>(flags).unwrap();
607        for &(x, y) in &self.at {
608            buf.push(x as u8);
609            buf.push(y as u8);
610        }
611        if self.refine_aggregate && self.refine_template == 0 {
612            for &(x, y) in &self.refine_at {
613                buf.push(x as u8);
614                buf.push(y as u8);
615            }
616        }
617        buf.write_u32::<BigEndian>(self.exsyms).unwrap();
618        buf.write_u32::<BigEndian>(self.newsyms).unwrap();
619        buf
620    }
621}
622
623// -----------------------------------------------------------------------------
624// Text region parameters (§7.4.3)
625// -----------------------------------------------------------------------------
626
627/// Represents the parameters for a text region segment
628#[derive(Debug)]
629pub struct TextRegionParams {
630    pub width: u32,          // Region width in pixels
631    pub height: u32,         // Region height in pixels
632    pub x: u32,              // X-coordinate of the top-left corner
633    pub y: u32,              // Y-coordinate of the top-left corner
634    pub ds_offset: i8,       // Signed 5-bit offset (SBDSOFFSET)
635    pub refine: bool,        // SBREFINE flag
636    pub log_strips: u8,      // LOGSBSTRIPS (0-3)
637    pub ref_corner: u8,      // REFCORNER (0-3)
638    pub transposed: bool,    // TRANSPOSED flag
639    pub comb_op: u8,         // SBCOMBOP (0-4)
640    pub refine_template: u8, // SBRTEMPLATE (0 or 1)
641}
642
643impl TextRegionParams {
644    pub fn to_bytes(&self) -> Vec<u8> {
645        let mut buf = Vec::with_capacity(16 + 1 + 2 + if self.refine { 4 } else { 0 });
646        buf.write_u32::<BigEndian>(self.width).unwrap();
647        buf.write_u32::<BigEndian>(self.height).unwrap();
648        buf.write_u32::<BigEndian>(self.x).unwrap();
649        buf.write_u32::<BigEndian>(self.y).unwrap();
650        // Region segment combination operator.
651        buf.write_u8(self.comb_op & 0x07).unwrap();
652
653        // Layout mirrors jbig2enc's packed jbig2_text_region bitfields.
654        let mut flags0 = 0u8;
655        flags0 |= ((self.comb_op >> 1) & 0x01) as u8; // SBCOMBOP bit 1 (sbcombop2)
656        flags0 |= ((self.ds_offset as u8) & 0x1F) << 2; // SBDSOFFSET (signed → u8 → 5-bit)
657        if self.refine && self.refine_template == 1 {
658            flags0 |= 1 << 7; // SBRTEMPLATE
659        }
660        buf.write_u8(flags0).unwrap();
661
662        let mut flags1 = 0u8;
663        if self.refine {
664            flags1 |= 1 << 1; // SBREFINE
665        }
666        flags1 |= (self.log_strips & 0x03) << 2; // LOGSBSTRIPS
667        flags1 |= (self.ref_corner & 0x03) << 4; // REFCORNER
668        if self.transposed {
669            flags1 |= 1 << 6; // TRANSPOSED
670        }
671        flags1 |= (self.comb_op & 0x01) << 7; // SBCOMBOP bit 0 (sbcombop1)
672        buf.write_u8(flags1).unwrap();
673
674        // Refinement AT flags: only written for SBRTEMPLATE=0 (which uses 2 AT pixels = 4 bytes).
675        // SBRTEMPLATE=1 has no AT pixels.
676        if self.refine && self.refine_template == 0 {
677            buf.extend_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); // (-1,-1), (-1,-1)
678        }
679        buf
680    }
681}
682
683/// Parameters for a JBIG2 halftone region segment
684#[derive(Debug, Clone, Default)]
685pub struct HalftoneParams {
686    pub width: u32,
687    pub height: u32,
688    pub x: u32,
689    pub y: u32,
690    pub region_comb_operator: u8,
691    pub mmr: bool,
692    pub skip_enabled: bool,
693    pub halftone_comb_operator: u8,
694    pub default_pixel: bool,
695    pub grid_width: u32,    // HGRIDW
696    pub grid_height: u32,   // HGRIDH
697    pub grid_x: i32,        // HGX, fixed-point 24.8
698    pub grid_y: i32,        // HGY, fixed-point 24.8
699    pub grid_vector_x: u16, // HRX, fixed-point 8.8
700    pub grid_vector_y: u16, // HRY, fixed-point 8.8
701    pub pattern_width: u8,  // HPW
702    pub pattern_height: u8, // HPH
703    pub template: u8,       // HTEMPLATE (0-3)
704}
705
706impl HalftoneParams {
707    pub fn to_bytes(&self) -> Vec<u8> {
708        let mut buf = Vec::with_capacity(31);
709        buf.write_u32::<BigEndian>(self.width).unwrap();
710        buf.write_u32::<BigEndian>(self.height).unwrap();
711        buf.write_u32::<BigEndian>(self.x).unwrap();
712        buf.write_u32::<BigEndian>(self.y).unwrap();
713        buf.write_u8(self.region_comb_operator & 0x07).unwrap();
714
715        let mut flags = 0u8;
716        if self.mmr {
717            flags |= 0x01;
718        }
719        flags |= (self.template & 0x03) << 1;
720        if self.skip_enabled {
721            flags |= 0x08;
722        }
723        flags |= (self.halftone_comb_operator & 0x07) << 4;
724        if self.default_pixel {
725            flags |= 0x80;
726        }
727
728        buf.write_u8(flags).unwrap();
729
730        buf.write_u32::<BigEndian>(self.grid_width).unwrap();
731        buf.write_u32::<BigEndian>(self.grid_height).unwrap();
732        buf.write_i32::<BigEndian>(self.grid_x).unwrap();
733        buf.write_i32::<BigEndian>(self.grid_y).unwrap();
734        buf.write_u16::<BigEndian>(self.grid_vector_x).unwrap();
735        buf.write_u16::<BigEndian>(self.grid_vector_y).unwrap();
736
737        buf
738    }
739}
740
741#[derive(Debug, Clone, Default)]
742pub struct PatternDictionaryParams {
743    pub mmr: bool,
744    pub template: u8,
745    pub pattern_width: u8,
746    pub pattern_height: u8,
747    pub gray_max: u32,
748}
749
750impl PatternDictionaryParams {
751    pub fn to_bytes(&self) -> Vec<u8> {
752        let mut buf = Vec::with_capacity(7);
753        let mut flags = 0u8;
754        if self.mmr {
755            flags |= 0x01;
756        }
757        flags |= (self.template & 0x03) << 1;
758        buf.push(flags);
759        buf.push(self.pattern_width);
760        buf.push(self.pattern_height);
761        buf.write_u32::<BigEndian>(self.gray_max).unwrap();
762        buf
763    }
764}
765
766// -----------------------------------------------------------------------------
767// Segment header + payload writer (§7.2)
768// -----------------------------------------------------------------------------
769
770/// Represents a JBIG2 segment, including header and payload
771#[derive(Debug, Clone, Default)]
772pub struct Segment {
773    pub number: u32,               // Segment number
774    pub seg_type: SegmentType,     // Segment type
775    pub deferred_non_retain: bool, // Bit 7 of Flags1: 0 = retain, 1 = non-retain
776    pub retain_flags: u8,          // Up to 5 bits for retention flags
777    pub page_association_type: u8, // Bits 0-1 of Flags2: 0=explicit, 1=deferred, 2=all pages
778    pub referred_to: Vec<u32>,     // List of referred-to segment numbers
779    pub page: Option<u32>,         // Page number if applicable
780    pub payload: Vec<u8>,          // Segment data
781}
782
783fn encode_varint(mut v: u32, buf: &mut Vec<u8>) {
784    while v >= 0x80 {
785        buf.push((v as u8) | 0x80);
786        v >>= 7;
787    }
788    buf.push(v as u8);
789}
790
791impl Segment {
792    pub fn write_into<W: Write>(&self, w: &mut W) -> io::Result<()> {
793        w.write_u32::<BigEndian>(self.number)?;
794
795        // Flags1: segment type, page association size, deferred non-retain.
796        let page_num_val = self.page.unwrap_or(0);
797        let page_size_is_4_bytes = page_num_val > 0xFF;
798        let flags1 = (self.seg_type as u8 & 0x3F)
799            | ((page_size_is_4_bytes as u8) << 6)
800            | ((self.deferred_non_retain as u8) << 7);
801        w.write_u8(flags1)?;
802
803        // Flags2: retain bits + referred-to segment count (compact form).
804        let referred_to_count = self.referred_to.len();
805        if referred_to_count > 7 {
806            return Err(io::Error::new(
807                io::ErrorKind::InvalidInput,
808                "referred_to count > 7 is not supported",
809            ));
810        }
811        let flags2 = (self.retain_flags & 0x1F) | ((referred_to_count as u8) << 5);
812        w.write_u8(flags2)?;
813
814        let ref_size = if self.number <= 256 {
815            1
816        } else if self.number <= 65_536 {
817            2
818        } else {
819            4
820        };
821
822        for &r_num in &self.referred_to {
823            match ref_size {
824                1 => w.write_u8(r_num as u8)?,
825                2 => w.write_u16::<BigEndian>(r_num as u16)?,
826                4 => w.write_u32::<BigEndian>(r_num)?,
827                _ => unreachable!(),
828            }
829        }
830
831        // Page association field is always present (0 means global segment stream).
832        if page_size_is_4_bytes {
833            w.write_u32::<BigEndian>(page_num_val)?;
834        } else {
835            w.write_u8(page_num_val as u8)?;
836        }
837
838        let payload_len = self.payload.len() as u32;
839        debug!("Segment {} payload length: {}", self.number, payload_len);
840        w.write_u32::<BigEndian>(payload_len)?;
841        w.write_all(&self.payload)?;
842
843        debug!(
844            "Segment::write_into: Wrote segment {}: Type={:?}, Page={:?}, PA Type={}, Data Length={}",
845            self.number, self.seg_type, self.page, self.page_association_type, payload_len
846        );
847        Ok(())
848    }
849}