Skip to main content

stet_core/
file_store.rs

1// stet - A PostScript Interpreter
2// Copyright (c) 2026 Scott Bowman
3// SPDX-License-Identifier: Apache-2.0 OR MIT
4
5//! File handle storage for PostScript file I/O.
6//!
7//! Files are identified by `EntityId` indices. Stdin (0), stdout (1), and
8//! stderr (2) are pre-allocated at well-known positions.
9//!
10//! Phase 5 adds filter support: `FileHandle::Filter` wraps a `FilterState`
11//! that decodes data from an underlying source file, and `FileHandle::StringSource`
12//! provides a read-only byte stream from in-memory data.
13
14use std::collections::HashMap;
15use std::io::{self, BufReader, IsTerminal, Read, Seek, Write};
16
17use crate::object::EntityId;
18
19/// State for a RunLengthDecode filter.
20#[derive(Debug)]
21#[non_exhaustive]
22pub enum RleState {
23    /// Waiting for a length byte.
24    Init,
25    /// Copying `remaining` literal bytes.
26    Literal { remaining: u16 },
27    /// Repeating `byte` for `remaining` times.
28    Repeat { byte: u8, remaining: u16 },
29    /// End of data (saw 128).
30    Eod,
31}
32
33/// Which filter to apply.
34#[non_exhaustive]
35pub enum FilterKind {
36    /// Hex digit pairs → bytes, EOD = `>`.
37    ASCIIHexDecode,
38    /// Base-85 groups → bytes, EOD = `~>`.
39    ASCII85Decode {
40        group: Vec<u8>,
41    },
42    RunLengthDecode {
43        state: RleState,
44    },
45    FlateDecode {
46        decompressor: flate2::Decompress,
47        raw_buf: Vec<u8>,
48        predictor: u8,
49        columns: u32,
50        colors: u32,
51        bpc: u32,
52        prev_row: Vec<u8>,
53    },
54    LZWDecode {
55        decoder: weezl::decode::Decoder,
56        raw_buf: Vec<u8>,
57    },
58    /// JPEG: lazily decoded on first read from source.
59    DCTDecode {
60        decoded: bool,
61        color_transform: Option<bool>,
62    },
63    /// JBIG2 (bi-level raster): buffered, lazily decoded on first read.
64    /// Output is row-packed 1-bit-per-pixel (8 pixels/byte, MSB first), with
65    /// 0=black and 1=white per PDF DeviceGray convention.
66    JBIG2Decode {
67        decoded: bool,
68        /// Optional /JBIG2Globals stream contents (shared globals segment
69        /// referenced by the embedded image data). Decoded once and used
70        /// as the `globals` argument to `hayro_jbig2::decode_embedded`.
71        globals: Option<Vec<u8>>,
72    },
73    /// JPEG 2000 (JP2 / J2K): buffered, lazily decoded on first read.
74    /// Output is interleaved pixel data; the caller declares the colour
75    /// space via `setcolorspace` (the JPX-internal CS is informational).
76    JPXDecode {
77        decoded: bool,
78    },
79    SubFileDecode {
80        eod_string: Vec<u8>,
81        eod_count: i32,
82        bytes_remaining: Option<i64>,
83    },
84    /// CCITT Group 3/4 fax decode (lazily decoded on first read).
85    CCITTFaxDecode {
86        /// Whether decoding has been performed yet.
87        decoded: bool,
88        /// K parameter: <0 = Group 4, =0 = Group 3 1-D, >0 = Group 3 2-D.
89        k: i32,
90        /// Image width in pixels.
91        columns: u32,
92        /// Image height (0 = unknown).
93        rows: u32,
94        /// Whether to expect EOL patterns.
95        end_of_line: bool,
96        /// Whether encoded lines are byte-aligned.
97        encoded_byte_align: bool,
98        /// Whether EOFB/RTC terminates data.
99        end_of_block: bool,
100        /// Pixel polarity: false = 0 is black (PS default).
101        black_is1: bool,
102    },
103    /// eexec decryption filter (Type 1 font encryption).
104    EexecDecode {
105        /// Current cipher state (initial: 55665).
106        r: u16,
107        /// None = not yet detected, Some(true) = hex, Some(false) = binary.
108        is_hex: Option<bool>,
109        /// Number of plaintext bytes produced so far (first 4 are random, skip them).
110        skip_count: u32,
111        /// Leftover hex digit from previous refill (hex mode only).
112        hex_leftover: Option<u8>,
113    },
114    // -- Encode filters (write direction) --
115    /// Bytes → hex digit pairs, EOD = `>`.
116    ASCIIHexEncode,
117    /// Bytes → base-85 groups, EOD = `~>`.
118    ASCII85Encode {
119        /// Accumulates up to 4 bytes before encoding a group.
120        buf: Vec<u8>,
121        /// Column counter for line breaking (~80 chars).
122        col: usize,
123    },
124    /// Bytes → run-length encoded data, EOD = byte 128.
125    /// Encodes incrementally during writes (streaming).
126    RunLengthEncode {
127        /// Pending literal bytes not yet emitted (max 128).
128        pending: Vec<u8>,
129        /// Current repeat byte being tracked.
130        run_byte: Option<u8>,
131        /// Count of current repeat run.
132        run_count: usize,
133    },
134    /// Bytes → zlib-compressed data (with optional predictor pre-processing).
135    FlateEncode {
136        compressor: flate2::Compress,
137        predictor: u8,
138        columns: u32,
139        colors: u32,
140        bpc: u32,
141        /// Row width in bytes (columns * colors * bpc / 8).
142        row_width: usize,
143        /// Bytes per pixel for PNG Sub filter.
144        bpp: usize,
145        /// Buffer for accumulating input until a full row is available.
146        encode_buf: Vec<u8>,
147        /// Previous row for PNG predictor (unused for TIFF predictor 2).
148        prev_row: Vec<u8>,
149    },
150    /// Bytes → LZW-compressed data.
151    LZWEncode {
152        encoder: weezl::encode::Encoder,
153    },
154    /// Identity encode filter (pass-through).
155    NullEncode,
156    /// JPEG encode: buffers all input, encodes on close.
157    DCTEncode {
158        buf: Vec<u8>,
159        columns: u32,
160        rows: u32,
161        colors: u32,
162        quality: u8,
163        color_transform: bool,
164    },
165}
166
167/// Decoded-data buffer state for a filter file.
168pub struct FilterState {
169    pub kind: FilterKind,
170    pub source: EntityId,
171    pub output_buf: Vec<u8>,
172    pub output_pos: usize,
173    pub putback: Vec<u8>,
174    pub eof: bool,
175    /// Total bytes consumed by the caller (for fileposition).
176    pub bytes_read: u64,
177}
178
179/// The underlying handle for a file.
180pub enum FileHandle {
181    /// Real file on disk (buffered for efficient byte-at-a-time reads).
182    Real(BufReader<std::fs::File>),
183    /// Standard input (uses stdin).
184    Stdin,
185    /// Standard output (uses stdout).
186    Stdout,
187    /// Standard error (uses stderr).
188    Stderr,
189    /// File has been closed.
190    Closed,
191    /// Decode/encode filter wrapping another file.
192    Filter(Box<FilterState>),
193    /// In-memory byte source (for string-backed data).
194    StringSource { data: Vec<u8>, pos: usize },
195}
196
197/// Encode a byte slice using PostScript RLE format.
198///
199/// Emit a literal run (prefix = len-1, then the bytes).
200fn rle_emit_literals(pending: &[u8]) -> Vec<u8> {
201    if pending.is_empty() {
202        return Vec::new();
203    }
204    let mut out = Vec::with_capacity(pending.len() + 1);
205    out.push((pending.len() - 1) as u8);
206    out.extend_from_slice(pending);
207    out
208}
209
210/// Emit a repeat run (prefix = 257-count, then the byte).
211fn rle_emit_repeat(byte: u8, count: usize) -> [u8; 2] {
212    [(257 - count) as u8, byte]
213}
214
215/// Metadata and handle for one open file.
216pub struct FileEntry {
217    pub handle: FileHandle,
218    pub name: String,
219    pub mode: String,
220    /// Current line number (1-based), incremented as newlines are consumed.
221    pub line_num: u32,
222    /// Newlines consumed but not yet applied to `line_num`. Flushed at the
223    /// start of the next token read so that `line` reports the line the
224    /// current token is on, not the line after it.
225    pub pending_newlines: u32,
226}
227
228/// Storage for all open PostScript files.
229pub struct FileStore {
230    files: Vec<FileEntry>,
231    /// Virtual filesystem: maps paths to embedded byte data.
232    /// Used in WASM builds to serve resources without real filesystem access.
233    embedded_files: HashMap<String, &'static [u8]>,
234}
235
236/// Well-known file entity IDs.
237pub const FILE_STDIN: EntityId = EntityId(0);
238pub const FILE_STDOUT: EntityId = EntityId(1);
239pub const FILE_STDERR: EntityId = EntityId(2);
240
241impl FileStore {
242    /// Create a new FileStore with stdin/stdout/stderr pre-allocated.
243    pub fn new() -> Self {
244        let mut store = Self {
245            files: Vec::new(),
246            embedded_files: HashMap::new(),
247        };
248        // Pre-allocate standard streams at known positions
249        store.files.push(FileEntry {
250            handle: FileHandle::Stdin,
251            name: "%stdin".to_string(),
252            mode: "r".to_string(),
253            line_num: 1,
254            pending_newlines: 0,
255        });
256        store.files.push(FileEntry {
257            handle: FileHandle::Stdout,
258            name: "%stdout".to_string(),
259            mode: "w".to_string(),
260            line_num: 1,
261            pending_newlines: 0,
262        });
263        store.files.push(FileEntry {
264            handle: FileHandle::Stderr,
265            name: "%stderr".to_string(),
266            mode: "w".to_string(),
267            line_num: 1,
268            pending_newlines: 0,
269        });
270        store
271    }
272
273    /// Register an embedded file mapping (path → static byte data).
274    pub fn add_embedded_file(&mut self, path: &str, data: &'static [u8]) {
275        self.embedded_files.insert(path.to_string(), data);
276    }
277
278    /// Look up an embedded file by path. Returns the data if found.
279    ///
280    /// Normalizes the path by stripping leading `/` and collapsing `//` to `/`
281    /// to handle paths like `/resources/Font//Helvetica.t1` built by PS code.
282    pub fn get_embedded_file(&self, path: &str) -> Option<&'static [u8]> {
283        if let Some(data) = self.embedded_files.get(path) {
284            return Some(*data);
285        }
286        // Normalize: strip leading "/" and collapse "//" → "/"
287        let normalized = path.trim_start_matches('/').replace("//", "/");
288        if normalized != path {
289            return self.embedded_files.get(normalized.as_str()).copied();
290        }
291        None
292    }
293
294    /// Open a file, returning its EntityId.
295    ///
296    /// For read mode, checks the embedded file map first (for WASM builds).
297    pub fn open(&mut self, name: &str, mode: &str) -> io::Result<EntityId> {
298        // Handle special names
299        match name {
300            "%stdin" => {
301                if mode != "r" {
302                    return Err(io::Error::new(
303                        io::ErrorKind::PermissionDenied,
304                        "%stdin is read-only",
305                    ));
306                }
307                return Ok(FILE_STDIN);
308            }
309            "%stdout" => {
310                if mode != "w" {
311                    return Err(io::Error::new(
312                        io::ErrorKind::PermissionDenied,
313                        "%stdout is write-only",
314                    ));
315                }
316                return Ok(FILE_STDOUT);
317            }
318            "%stderr" => {
319                if mode != "w" {
320                    return Err(io::Error::new(
321                        io::ErrorKind::PermissionDenied,
322                        "%stderr is write-only",
323                    ));
324                }
325                return Ok(FILE_STDERR);
326            }
327            "%lineedit" | "%statementedit" => {
328                if mode != "r" {
329                    return Err(io::Error::new(
330                        io::ErrorKind::PermissionDenied,
331                        "read-only special file",
332                    ));
333                }
334                // Read one line from stdin, strip trailing newline
335                let mut line = String::new();
336                let n = io::stdin().read_line(&mut line)?;
337                if n == 0 {
338                    // EOF — signal undefinedfilename (executive catches this and exits)
339                    return Err(io::Error::new(io::ErrorKind::NotFound, "EOF on stdin"));
340                }
341                if line.ends_with('\n') {
342                    line.pop();
343                    if line.ends_with('\r') {
344                        line.pop();
345                    }
346                }
347                return Ok(self.create_string_source(line.into_bytes()));
348            }
349            _ => {}
350        }
351
352        // Check embedded files for read access
353        if mode == "r"
354            && let Some(data) = self.embedded_files.get(name)
355        {
356            let id = EntityId(self.files.len() as u32);
357            self.files.push(FileEntry {
358                handle: FileHandle::StringSource {
359                    data: data.to_vec(),
360                    pos: 0,
361                },
362                name: name.to_string(),
363                mode: mode.to_string(),
364                line_num: 1,
365                pending_newlines: 0,
366            });
367            return Ok(id);
368        }
369
370        let file = match mode {
371            "r" => std::fs::File::open(name)?,
372            "w" => std::fs::File::create(name)?,
373            "a" => std::fs::OpenOptions::new()
374                .append(true)
375                .create(true)
376                .open(name)?,
377            "r+" => std::fs::OpenOptions::new()
378                .read(true)
379                .write(true)
380                .open(name)?,
381            _ => return Err(io::Error::new(io::ErrorKind::InvalidInput, "invalid mode")),
382        };
383
384        let id = EntityId(self.files.len() as u32);
385        self.files.push(FileEntry {
386            handle: FileHandle::Real(BufReader::new(file)),
387            name: name.to_string(),
388            mode: mode.to_string(),
389            line_num: 1,
390            pending_newlines: 0,
391        });
392        Ok(id)
393    }
394
395    /// Create a filter file that reads from `source` through `kind`.
396    pub fn create_filter(&mut self, source: EntityId, kind: FilterKind) -> EntityId {
397        let id = EntityId(self.files.len() as u32);
398        self.files.push(FileEntry {
399            handle: FileHandle::Filter(Box::new(FilterState {
400                kind,
401                source,
402                output_buf: Vec::new(),
403                output_pos: 0,
404                putback: Vec::new(),
405                eof: false,
406                bytes_read: 0,
407            })),
408            name: "%filter".to_string(),
409            mode: "r".to_string(),
410            line_num: 1,
411            pending_newlines: 0,
412        });
413        id
414    }
415
416    /// Create a filter file with pre-filled output buffer (for DCTDecode).
417    pub fn create_filter_with_data(
418        &mut self,
419        source: EntityId,
420        kind: FilterKind,
421        data: Vec<u8>,
422    ) -> EntityId {
423        let id = EntityId(self.files.len() as u32);
424        self.files.push(FileEntry {
425            handle: FileHandle::Filter(Box::new(FilterState {
426                kind,
427                source,
428                output_buf: data,
429                output_pos: 0,
430                putback: Vec::new(),
431                eof: false,
432                bytes_read: 0,
433            })),
434            name: "%filter".to_string(),
435            mode: "r".to_string(),
436            line_num: 1,
437            pending_newlines: 0,
438        });
439        id
440    }
441
442    /// Create an encode filter that writes encoded data to `target`.
443    pub fn create_encode_filter(&mut self, target: EntityId, kind: FilterKind) -> EntityId {
444        let id = EntityId(self.files.len() as u32);
445        self.files.push(FileEntry {
446            handle: FileHandle::Filter(Box::new(FilterState {
447                kind,
448                source: target, // "source" is the write target for encode filters
449                output_buf: Vec::new(),
450                output_pos: 0,
451                putback: Vec::new(),
452                eof: false,
453                bytes_read: 0,
454            })),
455            name: "%filter".to_string(),
456            mode: "w".to_string(),
457            line_num: 1,
458            pending_newlines: 0,
459        });
460        id
461    }
462
463    /// Create a string-backed data source.
464    pub fn create_string_source(&mut self, data: Vec<u8>) -> EntityId {
465        let id = EntityId(self.files.len() as u32);
466        self.files.push(FileEntry {
467            handle: FileHandle::StringSource { data, pos: 0 },
468            name: "%stringsource".to_string(),
469            mode: "r".to_string(),
470            line_num: 1,
471            pending_newlines: 0,
472        });
473        id
474    }
475
476    /// Get remaining unread bytes from a StringSource file as a slice.
477    ///
478    /// Returns a borrowed slice of the remaining bytes (from `pos` to end).
479    /// For non-StringSource files, returns an empty slice.
480    pub fn get_remaining_bytes(&self, entity: EntityId) -> &[u8] {
481        let entry = &self.files[entity.0 as usize];
482        match &entry.handle {
483            FileHandle::StringSource { data, pos } => &data[*pos..],
484            _ => &[],
485        }
486    }
487
488    /// Advance the read position of a StringSource file by `n` bytes.
489    pub fn advance_position(&mut self, entity: EntityId, n: usize) {
490        let entry = &mut self.files[entity.0 as usize];
491        if let FileHandle::StringSource { pos, .. } = &mut entry.handle {
492            *pos += n;
493        }
494    }
495
496    /// Get the current line number (1-based) for a file.
497    pub fn line_num(&self, entity: EntityId) -> u32 {
498        self.files[entity.0 as usize].line_num
499    }
500
501    /// Record newlines as pending. They will be applied to `line_num` at the
502    /// start of the next token read via `flush_pending_newlines`.
503    pub fn add_pending_newlines(&mut self, entity: EntityId, count: u32) {
504        self.files[entity.0 as usize].pending_newlines += count;
505    }
506
507    /// Apply any pending newlines to `line_num`. Call this at the start of
508    /// each token read so the line number reflects the current token's line.
509    pub fn flush_pending_newlines(&mut self, entity: EntityId) {
510        let entry = &mut self.files[entity.0 as usize];
511        entry.line_num += entry.pending_newlines;
512        entry.pending_newlines = 0;
513    }
514
515    /// Close a file.
516    pub fn close(&mut self, entity: EntityId) -> io::Result<()> {
517        // Check if this is an encode filter that needs finalization
518        let is_encode = matches!(
519            &self.files[entity.0 as usize].handle,
520            FileHandle::Filter(state) if state.kind.is_encode()
521        );
522        if is_encode {
523            return self.close_encode_filter(entity);
524        }
525
526        let entry = &mut self.files[entity.0 as usize];
527        match entry.handle {
528            FileHandle::Stdin | FileHandle::Stdout | FileHandle::Stderr => Ok(()),
529            FileHandle::Closed => Ok(()),
530            FileHandle::Filter(_) => {
531                // Close the filter but NOT its underlying source.
532                entry.handle = FileHandle::Closed;
533                Ok(())
534            }
535            _ => {
536                entry.handle = FileHandle::Closed;
537                Ok(())
538            }
539        }
540    }
541
542    /// Read one byte from a file. Returns None on EOF.
543    pub fn read_byte(&mut self, entity: EntityId) -> io::Result<Option<u8>> {
544        let entry = &mut self.files[entity.0 as usize];
545        match &mut entry.handle {
546            FileHandle::Real(f) => {
547                let mut buf = [0u8; 1];
548                let n = f.read(&mut buf)?;
549                if n == 0 { Ok(None) } else { Ok(Some(buf[0])) }
550            }
551            FileHandle::Stdin => {
552                if std::io::stdin().is_terminal() {
553                    Ok(None) // EOF when stdin is a terminal
554                } else {
555                    let mut buf = [0u8; 1];
556                    let n = io::stdin().read(&mut buf)?;
557                    if n == 0 { Ok(None) } else { Ok(Some(buf[0])) }
558                }
559            }
560            FileHandle::StringSource { data, pos } => {
561                if *pos < data.len() {
562                    let b = data[*pos];
563                    *pos += 1;
564                    Ok(Some(b))
565                } else {
566                    Ok(None)
567                }
568            }
569            FileHandle::Filter(_) => {
570                // Take the filter state out to avoid aliasing issues
571                self.read_byte_filter(entity)
572            }
573            FileHandle::Closed => Ok(None), // Closed files return EOF
574            _ => Err(io::Error::other("not readable")),
575        }
576    }
577
578    /// Read a byte from a filter file (handles temporary swap to avoid &mut aliasing).
579    fn read_byte_filter(&mut self, entity: EntityId) -> io::Result<Option<u8>> {
580        let entry = &mut self.files[entity.0 as usize];
581        let mut state = match std::mem::replace(&mut entry.handle, FileHandle::Closed) {
582            FileHandle::Filter(s) => s,
583            other => {
584                entry.handle = other;
585                return Err(io::Error::other("not a filter"));
586            }
587        };
588
589        // 1. Return from putback buffer if non-empty
590        if let Some(b) = state.putback.pop() {
591            state.bytes_read += 1;
592            self.files[entity.0 as usize].handle = FileHandle::Filter(state);
593            return Ok(Some(b));
594        }
595
596        // 2. Return from output_buf if data available
597        if state.output_pos < state.output_buf.len() {
598            let b = state.output_buf[state.output_pos];
599            state.output_pos += 1;
600            state.bytes_read += 1;
601            self.files[entity.0 as usize].handle = FileHandle::Filter(state);
602            return Ok(Some(b));
603        }
604
605        // 3. If already at EOF, done
606        if state.eof {
607            self.files[entity.0 as usize].handle = FileHandle::Filter(state);
608            return Ok(None);
609        }
610
611        // 4. Refill from source
612        self.refill_filter(&mut state)?;
613
614        let result = if state.output_pos < state.output_buf.len() {
615            let b = state.output_buf[state.output_pos];
616            state.output_pos += 1;
617            state.bytes_read += 1;
618            Ok(Some(b))
619        } else {
620            Ok(None)
621        };
622
623        self.files[entity.0 as usize].handle = FileHandle::Filter(state);
624        result
625    }
626
627    /// Read into a buffer. Returns number of bytes actually read.
628    pub fn read_into(&mut self, entity: EntityId, buf: &mut [u8]) -> io::Result<usize> {
629        let entry = &mut self.files[entity.0 as usize];
630        match &mut entry.handle {
631            FileHandle::Real(f) => f.read(buf),
632            FileHandle::Stdin => {
633                // Return EOF immediately when stdin is a terminal (not a pipe)
634                // to avoid blocking the interpreter during file execution.
635                if std::io::stdin().is_terminal() {
636                    Ok(0)
637                } else {
638                    io::stdin().read(buf)
639                }
640            }
641            FileHandle::StringSource { data, pos } => {
642                let remaining = data.len() - *pos;
643                let n = buf.len().min(remaining);
644                buf[..n].copy_from_slice(&data[*pos..*pos + n]);
645                *pos += n;
646                Ok(n)
647            }
648            FileHandle::Filter(_) => {
649                // Read byte-by-byte through the filter
650                let mut count = 0;
651                for slot in buf.iter_mut() {
652                    match self.read_byte(entity)? {
653                        Some(b) => {
654                            *slot = b;
655                            count += 1;
656                        }
657                        None => break,
658                    }
659                }
660                Ok(count)
661            }
662            FileHandle::Closed => Err(io::Error::other("file closed")),
663            _ => Err(io::Error::other("not readable")),
664        }
665    }
666
667    /// Write one byte to a file.
668    pub fn write_byte(&mut self, entity: EntityId, byte: u8) -> io::Result<()> {
669        self.write_from(entity, &[byte])
670    }
671
672    /// Write bytes from a buffer.
673    pub fn write_from(&mut self, entity: EntityId, buf: &[u8]) -> io::Result<()> {
674        // Check if this is an encode filter (needs swap-out pattern)
675        let is_encode = matches!(
676            &self.files[entity.0 as usize].handle,
677            FileHandle::Filter(state) if state.kind.is_encode()
678        );
679        if is_encode {
680            return self.encode_write(entity, buf);
681        }
682        let entry = &mut self.files[entity.0 as usize];
683        match &mut entry.handle {
684            FileHandle::Real(f) => f.get_mut().write_all(buf),
685            FileHandle::Stdout => io::stdout().write_all(buf),
686            FileHandle::Stderr => io::stderr().write_all(buf),
687            FileHandle::Closed => Err(io::Error::other("file closed")),
688            _ => Err(io::Error::other("not writable")),
689        }
690    }
691
692    /// Write data through an encode filter using swap-out pattern.
693    fn encode_write(&mut self, entity: EntityId, data: &[u8]) -> io::Result<()> {
694        // Swap out filter state to avoid borrow conflicts
695        let entry = &mut self.files[entity.0 as usize];
696        let mut state = match std::mem::replace(&mut entry.handle, FileHandle::Closed) {
697            FileHandle::Filter(s) => s,
698            other => {
699                entry.handle = other;
700                return Err(io::Error::other("not an encode filter"));
701            }
702        };
703
704        let target = state.source;
705        let result = match &mut state.kind {
706            FilterKind::ASCIIHexEncode => {
707                // Each byte → 2 uppercase hex chars
708                let mut hex = Vec::with_capacity(data.len() * 2);
709                for &b in data {
710                    hex.push(b"0123456789ABCDEF"[(b >> 4) as usize]);
711                    hex.push(b"0123456789ABCDEF"[(b & 0xF) as usize]);
712                }
713                self.write_from(target, &hex)
714            }
715            FilterKind::ASCII85Encode { buf, col } => {
716                let mut encoded = Vec::new();
717                for &byte in data {
718                    buf.push(byte);
719                    if buf.len() == 4 {
720                        let val = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
721                        if val == 0 {
722                            encoded.push(b'z');
723                            *col += 1;
724                        } else {
725                            let mut chars = [0u8; 5];
726                            let mut v = val;
727                            for c in chars.iter_mut().rev() {
728                                *c = (v % 85) as u8 + b'!';
729                                v /= 85;
730                            }
731                            encoded.extend_from_slice(&chars);
732                            *col += 5;
733                        }
734                        buf.clear();
735                        if *col >= 75 {
736                            encoded.push(b'\n');
737                            *col = 0;
738                        }
739                    }
740                }
741                if encoded.is_empty() {
742                    Ok(())
743                } else {
744                    self.write_from(target, &encoded)
745                }
746            }
747            FilterKind::RunLengthEncode {
748                pending,
749                run_byte,
750                run_count,
751            } => {
752                // Streaming RLE: emit runs incrementally as input arrives
753                for &b in data {
754                    if let Some(rb) = *run_byte {
755                        if b == rb {
756                            *run_count += 1;
757                            if *run_count >= 128 {
758                                // Max repeat run — flush
759                                let lit = rle_emit_literals(pending);
760                                if !lit.is_empty() {
761                                    self.write_from(target, &lit)?;
762                                    pending.clear();
763                                }
764                                let rep = rle_emit_repeat(rb, 128);
765                                self.write_from(target, &rep)?;
766                                *run_byte = None;
767                                *run_count = 0;
768                            }
769                        } else {
770                            // Different byte — decide what to do with accumulated run
771                            if *run_count >= 3 {
772                                // Emit pending literals, then the repeat run
773                                let lit = rle_emit_literals(pending);
774                                if !lit.is_empty() {
775                                    self.write_from(target, &lit)?;
776                                    pending.clear();
777                                }
778                                let rep = rle_emit_repeat(rb, *run_count);
779                                self.write_from(target, &rep)?;
780                            } else {
781                                // Short run (1-2) — absorb into pending literals
782                                for _ in 0..*run_count {
783                                    pending.push(rb);
784                                }
785                                if pending.len() >= 128 {
786                                    let lit = rle_emit_literals(pending);
787                                    self.write_from(target, &lit)?;
788                                    pending.clear();
789                                }
790                            }
791                            *run_byte = Some(b);
792                            *run_count = 1;
793                        }
794                    } else {
795                        // No current run byte — start tracking
796                        *run_byte = Some(b);
797                        *run_count = 1;
798                    }
799                }
800                Ok(())
801            }
802            FilterKind::FlateEncode {
803                compressor,
804                predictor,
805                row_width,
806                bpp,
807                encode_buf,
808                prev_row,
809                ..
810            } => {
811                if *predictor <= 1 {
812                    // No predictor — compress directly
813                    flate_compress_data(compressor, data, |chunk| self.write_from(target, chunk))
814                } else {
815                    // Buffer input, process complete rows with predictor
816                    encode_buf.extend_from_slice(data);
817                    let rw = *row_width;
818                    let pred = *predictor;
819                    let bp = *bpp;
820                    while encode_buf.len() >= rw {
821                        let row: Vec<u8> = encode_buf.drain(..rw).collect();
822                        let encoded = if pred >= 10 {
823                            encode_png_row(&row, bp)
824                        } else {
825                            encode_tiff_row(&row, bp)
826                        };
827                        prev_row.copy_from_slice(&row);
828                        flate_compress_data(compressor, &encoded, |chunk| {
829                            self.write_from(target, chunk)
830                        })?;
831                    }
832                    Ok(())
833                }
834            }
835            FilterKind::LZWEncode { encoder } => {
836                let mut out = vec![0u8; data.len() * 2 + 64];
837                let result = encoder.encode_bytes(data, &mut out);
838                if result.consumed_out > 0 {
839                    self.write_from(target, &out[..result.consumed_out])?;
840                }
841                Ok(())
842            }
843            FilterKind::NullEncode => self.write_from(target, data),
844            FilterKind::DCTEncode { buf, .. } => {
845                // Buffering is inherent to JPEG — DCT transform, Huffman table
846                // optimization, and quantization all require the full image.
847                // Not convertible to streaming.
848                buf.extend_from_slice(data);
849                Ok(())
850            }
851            _ => Err(io::Error::other("not an encode filter")),
852        };
853
854        // Put state back
855        self.files[entity.0 as usize].handle = FileHandle::Filter(state);
856        result
857    }
858
859    /// Finalize and close an encode filter, flushing remaining data and EOD markers.
860    fn close_encode_filter(&mut self, entity: EntityId) -> io::Result<()> {
861        // Swap out filter state
862        let entry = &mut self.files[entity.0 as usize];
863        let mut state = match std::mem::replace(&mut entry.handle, FileHandle::Closed) {
864            FileHandle::Filter(s) => s,
865            other => {
866                entry.handle = other;
867                return Ok(());
868            }
869        };
870
871        let target = state.source;
872        match &mut state.kind {
873            FilterKind::ASCIIHexEncode => {
874                self.write_from(target, b">")?;
875            }
876            FilterKind::ASCII85Encode { buf, .. } => {
877                // Flush remaining 1-3 bytes
878                if !buf.is_empty() {
879                    let n = buf.len();
880                    // Pad to 4 bytes with zeros
881                    while buf.len() < 4 {
882                        buf.push(0);
883                    }
884                    let val = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
885                    let mut chars = [0u8; 5];
886                    let mut v = val;
887                    for c in chars.iter_mut().rev() {
888                        *c = (v % 85) as u8 + b'!';
889                        v /= 85;
890                    }
891                    // Output n+1 chars (for n input bytes)
892                    self.write_from(target, &chars[..n + 1])?;
893                }
894                self.write_from(target, b"~>")?;
895            }
896            FilterKind::RunLengthEncode {
897                pending,
898                run_byte,
899                run_count,
900            } => {
901                // Flush remaining state
902                if let Some(rb) = *run_byte {
903                    if *run_count >= 3 {
904                        let lit = rle_emit_literals(pending);
905                        if !lit.is_empty() {
906                            self.write_from(target, &lit)?;
907                        }
908                        let rep = rle_emit_repeat(rb, *run_count);
909                        self.write_from(target, &rep)?;
910                    } else {
911                        for _ in 0..*run_count {
912                            pending.push(rb);
913                        }
914                        let lit = rle_emit_literals(pending);
915                        if !lit.is_empty() {
916                            self.write_from(target, &lit)?;
917                        }
918                    }
919                } else if !pending.is_empty() {
920                    let lit = rle_emit_literals(pending);
921                    self.write_from(target, &lit)?;
922                }
923                // EOD byte
924                self.write_from(target, &[128])?;
925            }
926            FilterKind::FlateEncode {
927                compressor,
928                predictor,
929                row_width,
930                bpp,
931                encode_buf,
932                ..
933            } => {
934                // Flush any remaining partial row with predictor
935                if *predictor > 1 && !encode_buf.is_empty() {
936                    let rw = *row_width;
937                    let pred = *predictor;
938                    let bp = *bpp;
939                    // Pad partial row to row_width with zeros
940                    encode_buf.resize(rw, 0);
941                    let row: Vec<u8> = std::mem::take(encode_buf);
942                    let encoded = if pred >= 10 {
943                        encode_png_row(&row, bp)
944                    } else {
945                        encode_tiff_row(&row, bp)
946                    };
947                    flate_compress_data(compressor, &encoded, |chunk| {
948                        self.write_from(target, chunk)
949                    })?;
950                }
951                // Flush with Finish
952                let mut out = vec![0u8; 256];
953                loop {
954                    let before_out = compressor.total_out() as usize;
955                    let status = compressor
956                        .compress(&[], &mut out, flate2::FlushCompress::Finish)
957                        .map_err(|e| io::Error::other(e.to_string()))?;
958                    let produced = compressor.total_out() as usize - before_out;
959                    if produced > 0 {
960                        self.write_from(target, &out[..produced])?;
961                    }
962                    if matches!(status, flate2::Status::StreamEnd) {
963                        break;
964                    }
965                }
966            }
967            FilterKind::LZWEncode { encoder } => {
968                // Mark stream as ended, then flush remaining encoded data
969                encoder.finish();
970                let mut out = vec![0u8; 256];
971                loop {
972                    let result = encoder.encode_bytes(&[], &mut out);
973                    if result.consumed_out > 0 {
974                        self.write_from(target, &out[..result.consumed_out])?;
975                    }
976                    if result.consumed_out == 0 {
977                        break;
978                    }
979                }
980            }
981            FilterKind::NullEncode => {}
982            FilterKind::DCTEncode {
983                buf,
984                columns,
985                rows,
986                colors,
987                quality,
988                color_transform,
989            } => {
990                let w = *columns as u16;
991                let h = *rows as u16;
992                let nc = *colors as u8;
993                let q = *quality;
994                let _ct = *color_transform;
995
996                // Determine color type for jpeg-encoder
997                let color_type = match nc {
998                    1 => jpeg_encoder::ColorType::Luma,
999                    3 => jpeg_encoder::ColorType::Rgb,
1000                    4 => jpeg_encoder::ColorType::Cmyk,
1001                    _ => jpeg_encoder::ColorType::Rgb,
1002                };
1003
1004                let expected_len = w as usize * h as usize * nc as usize;
1005                // Pad or truncate to expected size
1006                buf.resize(expected_len, 0);
1007
1008                let mut jpeg_data = Vec::new();
1009                let encoder = jpeg_encoder::Encoder::new(&mut jpeg_data, q);
1010                encoder
1011                    .encode(buf, w, h, color_type)
1012                    .map_err(|e| io::Error::other(format!("JPEG encode error: {e}")))?;
1013                self.write_from(target, &jpeg_data)?;
1014            }
1015            _ => {}
1016        }
1017
1018        // Close the target file (per PLRM, closing a filter closes its target)
1019        self.close(target)?;
1020
1021        // entry.handle is already Closed from swap-out
1022        Ok(())
1023    }
1024
1025    /// Read a line (up to newline or EOF). Returns (bytes_read, hit_newline).
1026    pub fn readline(&mut self, entity: EntityId, buf: &mut [u8]) -> io::Result<(usize, bool)> {
1027        let mut count = 0;
1028        let mut hit_newline = false;
1029        loop {
1030            if count >= buf.len() {
1031                break;
1032            }
1033            match self.read_byte(entity)? {
1034                None => break,
1035                Some(b'\n') => {
1036                    hit_newline = true;
1037                    break;
1038                }
1039                Some(b'\r') => {
1040                    hit_newline = true;
1041                    // Consume optional \n after \r (CR+LF is one line ending)
1042                    match self.read_byte(entity)? {
1043                        Some(b'\n') => {} // \r\n consumed as single line ending
1044                        Some(other) => self.putback_bytes(entity, &[other]),
1045                        None => {}
1046                    }
1047                    break;
1048                }
1049                Some(b) => {
1050                    buf[count] = b;
1051                    count += 1;
1052                }
1053            }
1054        }
1055        Ok((count, hit_newline))
1056    }
1057
1058    /// Get file position.
1059    pub fn position(&mut self, entity: EntityId) -> io::Result<u64> {
1060        let entry = &mut self.files[entity.0 as usize];
1061        match &mut entry.handle {
1062            FileHandle::Real(f) => f.stream_position(),
1063            FileHandle::StringSource { pos, .. } => Ok(*pos as u64),
1064            FileHandle::Filter(state) => Ok(state.bytes_read),
1065            _ => Err(io::Error::other("not seekable")),
1066        }
1067    }
1068
1069    /// Set file position.
1070    pub fn set_position(&mut self, entity: EntityId, pos: u64) -> io::Result<()> {
1071        let entry = &mut self.files[entity.0 as usize];
1072        match &mut entry.handle {
1073            FileHandle::Real(f) => {
1074                f.seek(io::SeekFrom::Start(pos))?;
1075                Ok(())
1076            }
1077            FileHandle::StringSource { data, pos: p } => {
1078                *p = (pos as usize).min(data.len());
1079                Ok(())
1080            }
1081            _ => Err(io::Error::other("not seekable")),
1082        }
1083    }
1084
1085    /// Flush a file.
1086    pub fn flush(&mut self, entity: EntityId) -> io::Result<()> {
1087        let entry = &self.files[entity.0 as usize];
1088        match &entry.handle {
1089            FileHandle::Real(_) => {
1090                let entry = &mut self.files[entity.0 as usize];
1091                if entry.mode.starts_with('r') {
1092                    // Read file: consume remaining data and close (PLRM)
1093                    entry.handle = FileHandle::Closed;
1094                } else if let FileHandle::Real(f) = &mut entry.handle {
1095                    f.get_mut().flush()?;
1096                }
1097            }
1098            FileHandle::Stdout => io::stdout().flush()?,
1099            FileHandle::Stderr => io::stderr().flush()?,
1100            FileHandle::Filter(_) => {
1101                // For read filters, flushfile consumes all remaining data
1102                // until EOF. This is critical for SubFileDecode — it advances
1103                // the underlying source past the filtered content.
1104                let mut buf = [0u8; 4096];
1105                loop {
1106                    match self.read_into(entity, &mut buf) {
1107                        Ok(0) => break,
1108                        Ok(_) => continue,
1109                        Err(_) => break,
1110                    }
1111                }
1112            }
1113            FileHandle::StringSource { .. } => {
1114                // For string sources, advance to EOF
1115                let entry = &mut self.files[entity.0 as usize];
1116                if let FileHandle::StringSource { data, pos } = &mut entry.handle {
1117                    *pos = data.len();
1118                }
1119            }
1120            _ => {}
1121        }
1122        Ok(())
1123    }
1124
1125    /// Check if a file entity is valid (not closed).
1126    pub fn is_open(&self, entity: EntityId) -> bool {
1127        if (entity.0 as usize) >= self.files.len() {
1128            return false;
1129        }
1130        !matches!(self.files[entity.0 as usize].handle, FileHandle::Closed)
1131    }
1132
1133    /// Check if a file entity is readable (open, not stdout/stderr).
1134    pub fn is_readable(&self, entity: EntityId) -> bool {
1135        if (entity.0 as usize) >= self.files.len() {
1136            return false;
1137        }
1138        !matches!(
1139            self.files[entity.0 as usize].handle,
1140            FileHandle::Closed | FileHandle::Stdout | FileHandle::Stderr
1141        )
1142    }
1143
1144    /// Read exactly N bytes from a file. Returns error on premature EOF.
1145    pub fn read_n_bytes(&mut self, entity: EntityId, n: usize) -> io::Result<Vec<u8>> {
1146        let mut result = Vec::with_capacity(n);
1147        for _ in 0..n {
1148            match self.read_byte(entity)? {
1149                Some(b) => result.push(b),
1150                None => return Err(io::Error::other("unexpected EOF")),
1151            }
1152        }
1153        Ok(result)
1154    }
1155
1156    /// Put bytes back into a file so they'll be returned on the next read.
1157    /// For filters, uses the filter's putback buffer. For StringSource,
1158    /// decrements position.
1159    pub fn putback_bytes(&mut self, entity: EntityId, bytes: &[u8]) {
1160        let entry = &mut self.files[entity.0 as usize];
1161        match &mut entry.handle {
1162            FileHandle::Filter(state) => {
1163                // Push in reverse so they come out in the right order (LIFO)
1164                for &b in bytes.iter().rev() {
1165                    state.putback.push(b);
1166                }
1167            }
1168            FileHandle::StringSource { pos, .. } => {
1169                *pos = pos.saturating_sub(bytes.len());
1170            }
1171            FileHandle::Real(f) => {
1172                // Seek backwards to put bytes back
1173                let _ = f.seek(std::io::SeekFrom::Current(-(bytes.len() as i64)));
1174            }
1175            _ => {}
1176        }
1177    }
1178
1179    /// Check if a file entity is seekable (disk file).
1180    pub fn is_seekable(&self, entity: EntityId) -> bool {
1181        matches!(self.files[entity.0 as usize].handle, FileHandle::Real(_))
1182    }
1183
1184    /// Get bytes available for reading. Returns -1 for stdin/filters/unknown,
1185    /// closed files, write-only files, and files at EOF.
1186    /// For disk files opened for reading, returns remaining bytes.
1187    pub fn bytes_available(&mut self, entity: EntityId) -> i32 {
1188        let entry = &mut self.files[entity.0 as usize];
1189        // Write-only files return -1
1190        if entry.mode.starts_with('w') || entry.mode.starts_with('a') {
1191            return -1;
1192        }
1193        match &mut entry.handle {
1194            FileHandle::Real(f) => {
1195                if let Ok(cur) = f.stream_position() {
1196                    if let Ok(end) = f.seek(io::SeekFrom::End(0)) {
1197                        let _ = f.seek(io::SeekFrom::Start(cur));
1198                        let remaining = end.saturating_sub(cur);
1199                        if remaining == 0 {
1200                            -1 // at EOF
1201                        } else {
1202                            remaining.min(i32::MAX as u64) as i32
1203                        }
1204                    } else {
1205                        -1
1206                    }
1207                } else {
1208                    -1
1209                }
1210            }
1211            FileHandle::StringSource { data, pos } => {
1212                let remaining = data.len() - *pos;
1213                if remaining == 0 { -1 } else { remaining as i32 }
1214            }
1215            FileHandle::Closed => -1,
1216            _ => -1,
1217        }
1218    }
1219
1220    /// Get the name of a file.
1221    pub fn name(&self, entity: EntityId) -> &str {
1222        &self.files[entity.0 as usize].name
1223    }
1224
1225    /// Set the name of a file (e.g. to record the resolved path for `run`).
1226    pub fn set_name(&mut self, entity: EntityId, name: String) {
1227        self.files[entity.0 as usize].name = name;
1228    }
1229
1230    /// Get the mode of a file.
1231    pub fn mode(&self, entity: EntityId) -> &str {
1232        &self.files[entity.0 as usize].mode
1233    }
1234
1235    /// Number of files allocated.
1236    pub fn len(&self) -> usize {
1237        self.files.len()
1238    }
1239
1240    /// Whether no files are allocated.
1241    pub fn is_empty(&self) -> bool {
1242        self.files.is_empty()
1243    }
1244
1245    /// Push a byte back onto a filter's putback buffer.
1246    pub fn putback_byte(&mut self, entity: EntityId, byte: u8) {
1247        let entry = &mut self.files[entity.0 as usize];
1248        if let FileHandle::Filter(ref mut state) = entry.handle {
1249            state.putback.push(byte);
1250        }
1251    }
1252
1253    /// Read all remaining bytes from a source (used by DCTDecode at creation).
1254    pub fn read_all(&mut self, entity: EntityId) -> io::Result<Vec<u8>> {
1255        let mut data = Vec::new();
1256        while let Some(b) = self.read_byte(entity)? {
1257            data.push(b);
1258        }
1259        Ok(data)
1260    }
1261
1262    // ---- Filter refill logic ----
1263
1264    /// Refill the filter's output buffer by reading from the source and decoding.
1265    fn refill_filter(&mut self, state: &mut FilterState) -> io::Result<()> {
1266        state.output_buf.clear();
1267        state.output_pos = 0;
1268
1269        match &mut state.kind {
1270            FilterKind::ASCIIHexDecode => {
1271                self.refill_ascii_hex(state.source, &mut state.output_buf, &mut state.eof)?;
1272            }
1273            FilterKind::ASCII85Decode { .. } => {
1274                // Need to pass group around without aliasing
1275                let source = state.source;
1276                self.refill_ascii85(
1277                    source,
1278                    &mut state.kind,
1279                    &mut state.output_buf,
1280                    &mut state.eof,
1281                )?;
1282            }
1283            FilterKind::RunLengthDecode { .. } => {
1284                let source = state.source;
1285                self.refill_rle(
1286                    source,
1287                    &mut state.kind,
1288                    &mut state.output_buf,
1289                    &mut state.eof,
1290                )?;
1291            }
1292            FilterKind::FlateDecode { .. } => {
1293                let source = state.source;
1294                self.refill_flate(
1295                    source,
1296                    &mut state.kind,
1297                    &mut state.output_buf,
1298                    &mut state.eof,
1299                )?;
1300            }
1301            FilterKind::LZWDecode { .. } => {
1302                let source = state.source;
1303                self.refill_lzw(
1304                    source,
1305                    &mut state.kind,
1306                    &mut state.output_buf,
1307                    &mut state.eof,
1308                )?;
1309            }
1310            FilterKind::DCTDecode { decoded, .. } => {
1311                if !*decoded {
1312                    // Lazy decode: read all JPEG data from source, then decode
1313                    let source = state.source;
1314                    let jpeg_data = self.read_all(source)?;
1315                    let mut decoder = jpeg_decoder::Decoder::new(jpeg_data.as_slice());
1316                    let pixels = decoder
1317                        .decode()
1318                        .map_err(|e| io::Error::other(format!("JPEG decode error: {e}")))?;
1319                    state.output_buf = pixels;
1320                    state.output_pos = 0;
1321                    *decoded = true;
1322                } else {
1323                    state.eof = true;
1324                }
1325            }
1326            FilterKind::JBIG2Decode { decoded, .. } => {
1327                if !*decoded {
1328                    let source = state.source;
1329                    let raw = self.read_all(source)?;
1330                    // Snapshot globals before mutating state.kind.
1331                    let globals = match &state.kind {
1332                        FilterKind::JBIG2Decode { globals, .. } => globals.clone(),
1333                        _ => None,
1334                    };
1335                    let image = hayro_jbig2::decode_embedded(&raw, globals.as_deref())
1336                        .map_err(|e| io::Error::other(format!("JBIG2 decode error: {e}")))?;
1337                    // Pack the bool grid into PDF DeviceGray bytes (1 bit per
1338                    // pixel, MSB-first; 0=black, 1=white). Pad each row to a
1339                    // byte boundary so the consumer can treat it as a
1340                    // standard 1-bpc raster.
1341                    let row_bytes = (image.width as usize).div_ceil(8);
1342                    let mut packed = vec![0xFFu8; row_bytes * image.height as usize];
1343                    for y in 0..image.height as usize {
1344                        for x in 0..image.width as usize {
1345                            if image.data[y * image.width as usize + x] {
1346                                packed[y * row_bytes + x / 8] &= !(0x80 >> (x % 8));
1347                            }
1348                        }
1349                    }
1350                    state.output_buf = packed;
1351                    state.output_pos = 0;
1352                    if let FilterKind::JBIG2Decode { decoded, .. } = &mut state.kind {
1353                        *decoded = true;
1354                    }
1355                } else {
1356                    state.eof = true;
1357                }
1358            }
1359            FilterKind::JPXDecode { decoded } => {
1360                if !*decoded {
1361                    let source = state.source;
1362                    let raw = self.read_all(source)?;
1363                    let image = hayro_jpeg2000::Image::new(
1364                        &raw,
1365                        &hayro_jpeg2000::DecodeSettings::default(),
1366                    )
1367                    .map_err(|e| io::Error::other(format!("JPXDecode error: {e}")))?;
1368                    let pixels = image
1369                        .decode()
1370                        .map_err(|e| io::Error::other(format!("JPXDecode error: {e}")))?;
1371                    state.output_buf = pixels;
1372                    state.output_pos = 0;
1373                    *decoded = true;
1374                } else {
1375                    state.eof = true;
1376                }
1377            }
1378            FilterKind::CCITTFaxDecode { decoded, .. } => {
1379                if !*decoded {
1380                    let source = state.source;
1381                    let ccitt_data = self.read_all(source)?;
1382                    let decoded_bytes = Self::decode_ccittfax(&ccitt_data, &mut state.kind)?;
1383                    state.output_buf = decoded_bytes;
1384                    state.output_pos = 0;
1385                    // Mark as decoded (re-borrow after decode_ccittfax)
1386                    if let FilterKind::CCITTFaxDecode { decoded, .. } = &mut state.kind {
1387                        *decoded = true;
1388                    }
1389                } else {
1390                    state.eof = true;
1391                }
1392            }
1393            FilterKind::SubFileDecode { .. } => {
1394                let source = state.source;
1395                self.refill_subfile(
1396                    source,
1397                    &mut state.kind,
1398                    &mut state.output_buf,
1399                    &mut state.eof,
1400                )?;
1401            }
1402            FilterKind::EexecDecode { .. } => {
1403                let source = state.source;
1404                self.refill_eexec(
1405                    source,
1406                    &mut state.kind,
1407                    &mut state.output_buf,
1408                    &mut state.eof,
1409                )?;
1410            }
1411            // Encode filters are write-only, never refilled via read path
1412            _ => {
1413                state.eof = true;
1414            }
1415        }
1416        Ok(())
1417    }
1418
1419    /// Refill ASCIIHexDecode: read hex pairs, skip whitespace, stop at `>`.
1420    fn refill_ascii_hex(
1421        &mut self,
1422        source: EntityId,
1423        out: &mut Vec<u8>,
1424        eof: &mut bool,
1425    ) -> io::Result<()> {
1426        let mut nibble: Option<u8> = None;
1427        let target = 4096;
1428
1429        while out.len() < target {
1430            match self.read_byte(source)? {
1431                None => {
1432                    // Pad odd nibble
1433                    if let Some(high) = nibble.take() {
1434                        out.push(high << 4);
1435                    }
1436                    *eof = true;
1437                    return Ok(());
1438                }
1439                Some(b'>') => {
1440                    // EOD marker
1441                    if let Some(high) = nibble.take() {
1442                        out.push(high << 4);
1443                    }
1444                    *eof = true;
1445                    return Ok(());
1446                }
1447                Some(b) => {
1448                    let nib = match b {
1449                        b'0'..=b'9' => Some(b - b'0'),
1450                        b'a'..=b'f' => Some(b - b'a' + 10),
1451                        b'A'..=b'F' => Some(b - b'A' + 10),
1452                        _ => None, // whitespace skipped
1453                    };
1454                    if let Some(n) = nib {
1455                        match nibble.take() {
1456                            None => nibble = Some(n),
1457                            Some(high) => out.push((high << 4) | n),
1458                        }
1459                    }
1460                }
1461            }
1462        }
1463        Ok(())
1464    }
1465
1466    /// Refill ASCII85Decode: read 5-char groups, handle `z` and partial final group.
1467    fn refill_ascii85(
1468        &mut self,
1469        source: EntityId,
1470        kind: &mut FilterKind,
1471        out: &mut Vec<u8>,
1472        eof: &mut bool,
1473    ) -> io::Result<()> {
1474        let group = match kind {
1475            FilterKind::ASCII85Decode { group } => group,
1476            _ => return Err(io::Error::other("not ASCII85")),
1477        };
1478        let target = 4096;
1479
1480        while out.len() < target {
1481            match self.read_byte(source)? {
1482                None => {
1483                    // Flush partial group at EOF
1484                    if group.len() >= 2 {
1485                        ascii85_decode_partial(group, out);
1486                    }
1487                    group.clear();
1488                    *eof = true;
1489                    return Ok(());
1490                }
1491                Some(b'~') => {
1492                    // Start of EOD `~>` — consume the `>`
1493                    let _ = self.read_byte(source)?; // consume '>'
1494                    if group.len() >= 2 {
1495                        ascii85_decode_partial(group, out);
1496                    }
1497                    group.clear();
1498                    *eof = true;
1499                    return Ok(());
1500                }
1501                Some(b'z') => {
1502                    // Special: four zero bytes
1503                    out.extend_from_slice(&[0, 0, 0, 0]);
1504                }
1505                Some(b) if b.is_ascii_whitespace() => {
1506                    // Skip whitespace
1507                }
1508                Some(b) if (b'!'..=b'u').contains(&b) => {
1509                    group.push(b - b'!');
1510                    if group.len() == 5 {
1511                        // Decode full group
1512                        let val = group[0] as u64 * 85 * 85 * 85 * 85
1513                            + group[1] as u64 * 85 * 85 * 85
1514                            + group[2] as u64 * 85 * 85
1515                            + group[3] as u64 * 85
1516                            + group[4] as u64;
1517                        out.push((val >> 24) as u8);
1518                        out.push((val >> 16) as u8);
1519                        out.push((val >> 8) as u8);
1520                        out.push(val as u8);
1521                        group.clear();
1522                    }
1523                }
1524                Some(_) => {
1525                    // Invalid character — skip
1526                }
1527            }
1528        }
1529        Ok(())
1530    }
1531
1532    /// Refill RunLengthDecode.
1533    fn refill_rle(
1534        &mut self,
1535        source: EntityId,
1536        kind: &mut FilterKind,
1537        out: &mut Vec<u8>,
1538        eof: &mut bool,
1539    ) -> io::Result<()> {
1540        let rle_state = match kind {
1541            FilterKind::RunLengthDecode { state } => state,
1542            _ => return Err(io::Error::other("not RLE")),
1543        };
1544        let target = 4096;
1545
1546        while out.len() < target {
1547            match rle_state {
1548                RleState::Init => {
1549                    match self.read_byte(source)? {
1550                        None | Some(128) => {
1551                            *rle_state = RleState::Eod;
1552                            *eof = true;
1553                            return Ok(());
1554                        }
1555                        Some(b) if b < 128 => {
1556                            *rle_state = RleState::Literal {
1557                                remaining: b as u16 + 1,
1558                            };
1559                        }
1560                        Some(b) => {
1561                            // 129..=255 → repeat next byte (257−b) times
1562                            let count = 257 - b as u16;
1563                            match self.read_byte(source)? {
1564                                None => {
1565                                    *rle_state = RleState::Eod;
1566                                    *eof = true;
1567                                    return Ok(());
1568                                }
1569                                Some(val) => {
1570                                    *rle_state = RleState::Repeat {
1571                                        byte: val,
1572                                        remaining: count,
1573                                    };
1574                                }
1575                            }
1576                        }
1577                    }
1578                }
1579                RleState::Literal { remaining } => {
1580                    if *remaining == 0 {
1581                        *rle_state = RleState::Init;
1582                        continue;
1583                    }
1584                    match self.read_byte(source)? {
1585                        None => {
1586                            *rle_state = RleState::Eod;
1587                            *eof = true;
1588                            return Ok(());
1589                        }
1590                        Some(b) => {
1591                            out.push(b);
1592                            *remaining -= 1;
1593                        }
1594                    }
1595                }
1596                RleState::Repeat { byte, remaining } => {
1597                    if *remaining == 0 {
1598                        *rle_state = RleState::Init;
1599                        continue;
1600                    }
1601                    out.push(*byte);
1602                    *remaining -= 1;
1603                }
1604                RleState::Eod => {
1605                    *eof = true;
1606                    return Ok(());
1607                }
1608            }
1609        }
1610        Ok(())
1611    }
1612
1613    /// Refill FlateDecode: read compressed data from source, decompress.
1614    fn refill_flate(
1615        &mut self,
1616        source: EntityId,
1617        kind: &mut FilterKind,
1618        out: &mut Vec<u8>,
1619        eof: &mut bool,
1620    ) -> io::Result<()> {
1621        let (decompressor, raw_buf, predictor, columns, colors, bpc, prev_row) = match kind {
1622            FilterKind::FlateDecode {
1623                decompressor,
1624                raw_buf,
1625                predictor,
1626                columns,
1627                colors,
1628                bpc,
1629                prev_row,
1630            } => (
1631                decompressor,
1632                raw_buf,
1633                *predictor,
1634                *columns,
1635                *colors,
1636                *bpc,
1637                prev_row,
1638            ),
1639            _ => return Err(io::Error::other("not Flate")),
1640        };
1641
1642        // Read a chunk of compressed data from source
1643        if raw_buf.is_empty() {
1644            let mut chunk = vec![0u8; 8192];
1645            let mut total = 0;
1646            // Try to read a chunk
1647            while let Some(b) = self.read_byte(source)? {
1648                chunk[total] = b;
1649                total += 1;
1650                if total >= chunk.len() {
1651                    break;
1652                }
1653            }
1654            if total == 0 {
1655                *eof = true;
1656                return Ok(());
1657            }
1658            raw_buf.extend_from_slice(&chunk[..total]);
1659        }
1660
1661        // Decompress
1662        let mut decompressed = vec![0u8; 16384];
1663        let before_in = decompressor.total_in();
1664        let before_out = decompressor.total_out();
1665        let status = decompressor
1666            .decompress(raw_buf, &mut decompressed, flate2::FlushDecompress::None)
1667            .map_err(|e| io::Error::other(format!("flate2 decompress error: {}", e)))?;
1668
1669        let consumed = (decompressor.total_in() - before_in) as usize;
1670        let produced = (decompressor.total_out() - before_out) as usize;
1671
1672        // Remove consumed bytes from raw_buf
1673        raw_buf.drain(..consumed);
1674
1675        if status == flate2::Status::StreamEnd {
1676            *eof = true;
1677        }
1678
1679        let decompressed = &decompressed[..produced];
1680
1681        // Apply predictor if needed
1682        if predictor == 1 || predictor == 0 {
1683            // No prediction
1684            out.extend_from_slice(decompressed);
1685        } else if predictor == 2 {
1686            // TIFF predictor 2
1687            apply_tiff_predictor(decompressed, out, columns, colors, bpc);
1688        } else if (10..=15).contains(&predictor) {
1689            // PNG predictors
1690            apply_png_predictor(decompressed, out, columns, colors, bpc, prev_row);
1691        } else {
1692            out.extend_from_slice(decompressed);
1693        }
1694
1695        Ok(())
1696    }
1697
1698    /// Refill LZWDecode: read compressed data from source, decompress incrementally.
1699    fn refill_lzw(
1700        &mut self,
1701        source: EntityId,
1702        kind: &mut FilterKind,
1703        out: &mut Vec<u8>,
1704        eof: &mut bool,
1705    ) -> io::Result<()> {
1706        let (decoder, raw_buf) = match kind {
1707            FilterKind::LZWDecode { decoder, raw_buf } => (decoder, raw_buf),
1708            _ => return Err(io::Error::other("not LZW")),
1709        };
1710
1711        let mut decompressed = vec![0u8; 16384];
1712
1713        loop {
1714            // Read a chunk of compressed data from source if buffer is empty
1715            if raw_buf.is_empty() {
1716                let mut chunk = vec![0u8; 8192];
1717                let mut total = 0;
1718                while let Some(b) = self.read_byte(source)? {
1719                    chunk[total] = b;
1720                    total += 1;
1721                    if total >= chunk.len() {
1722                        break;
1723                    }
1724                }
1725                if total == 0 {
1726                    // Source exhausted — try one more decode to flush decoder internals
1727                    let result = decoder.decode_bytes(&[], &mut decompressed);
1728                    if result.consumed_out > 0 {
1729                        out.extend_from_slice(&decompressed[..result.consumed_out]);
1730                    }
1731                    *eof = true;
1732                    return Ok(());
1733                }
1734                raw_buf.extend_from_slice(&chunk[..total]);
1735            }
1736
1737            // Decompress
1738            let result = decoder.decode_bytes(raw_buf, &mut decompressed);
1739            let consumed_in = result.consumed_in;
1740            let consumed_out = result.consumed_out;
1741
1742            // Remove consumed bytes from raw_buf
1743            raw_buf.drain(..consumed_in);
1744
1745            match result.status {
1746                Ok(weezl::LzwStatus::Done) => {
1747                    out.extend_from_slice(&decompressed[..consumed_out]);
1748                    *eof = true;
1749                    return Ok(());
1750                }
1751                Ok(weezl::LzwStatus::NoProgress) => {
1752                    if consumed_out > 0 {
1753                        out.extend_from_slice(&decompressed[..consumed_out]);
1754                        return Ok(());
1755                    }
1756                    // No progress — need more input data
1757                    if raw_buf.is_empty() {
1758                        continue; // will try to read more from source
1759                    }
1760                    // raw_buf has data but decoder made no progress — done
1761                    *eof = true;
1762                    return Ok(());
1763                }
1764                Ok(weezl::LzwStatus::Ok) => {
1765                    out.extend_from_slice(&decompressed[..consumed_out]);
1766                    if consumed_out > 0 {
1767                        return Ok(());
1768                    }
1769                    // Consumed input but no output yet — keep going
1770                    continue;
1771                }
1772                Err(e) => {
1773                    return Err(io::Error::other(format!("LZW decode error: {}", e)));
1774                }
1775            }
1776        }
1777    }
1778
1779    /// Refill SubFileDecode.
1780    fn refill_subfile(
1781        &mut self,
1782        source: EntityId,
1783        kind: &mut FilterKind,
1784        out: &mut Vec<u8>,
1785        eof: &mut bool,
1786    ) -> io::Result<()> {
1787        let (eod_string, eod_count, bytes_remaining) = match kind {
1788            FilterKind::SubFileDecode {
1789                eod_string,
1790                eod_count,
1791                bytes_remaining,
1792            } => (eod_string, eod_count, bytes_remaining),
1793            _ => return Err(io::Error::other("not SubFile")),
1794        };
1795
1796        if eod_string.is_empty() {
1797            // Byte-count mode
1798            if let Some(remaining) = bytes_remaining {
1799                let target = (*remaining).min(4096) as usize;
1800                for _ in 0..target {
1801                    match self.read_byte(source)? {
1802                        Some(b) => {
1803                            out.push(b);
1804                            *remaining -= 1;
1805                        }
1806                        None => {
1807                            *eof = true;
1808                            return Ok(());
1809                        }
1810                    }
1811                }
1812                if *remaining <= 0 {
1813                    *eof = true;
1814                }
1815            } else {
1816                *eof = true;
1817            }
1818        } else {
1819            // String-search mode: pass data until N occurrences of EOD string found
1820            let eod = eod_string.clone();
1821            let target_count = *eod_count;
1822            let mut match_pos = 0;
1823            let mut found_count = 0;
1824
1825            for _ in 0..4096 {
1826                match self.read_byte(source)? {
1827                    None => {
1828                        *eof = true;
1829                        return Ok(());
1830                    }
1831                    Some(b) => {
1832                        if b == eod[match_pos] {
1833                            match_pos += 1;
1834                            if match_pos == eod.len() {
1835                                found_count += 1;
1836                                // Per PLRM: EOD string is included in output
1837                                out.extend_from_slice(&eod);
1838                                match_pos = 0;
1839                                if found_count >= target_count {
1840                                    *eof = true;
1841                                    return Ok(());
1842                                }
1843                            }
1844                        } else {
1845                            // Output any partially matched bytes
1846                            if match_pos > 0 {
1847                                out.extend_from_slice(&eod[..match_pos]);
1848                                match_pos = 0;
1849                            }
1850                            out.push(b);
1851                        }
1852                    }
1853                }
1854            }
1855        }
1856
1857        Ok(())
1858    }
1859
1860    /// Decode CCITT Group 3 or Group 4 fax data using the `fax` crate.
1861    fn decode_ccittfax(data: &[u8], kind: &mut FilterKind) -> io::Result<Vec<u8>> {
1862        let FilterKind::CCITTFaxDecode {
1863            k,
1864            columns,
1865            rows,
1866            end_of_block,
1867            black_is1,
1868            ..
1869        } = kind
1870        else {
1871            return Err(io::Error::other("not a CCITTFaxDecode filter"));
1872        };
1873        let k = *k;
1874        let width = *columns as u16;
1875        let rows_limit = *rows;
1876        let end_of_block = *end_of_block;
1877        let black_is1 = *black_is1;
1878
1879        // Bytes per scan line (1 bit per pixel, padded to byte boundary)
1880        let row_bytes = (width as usize).div_ceil(8);
1881        let mut output = Vec::new();
1882        let mut line_count: u32 = 0;
1883
1884        // Callback to process each decoded line
1885        let mut process_line = |transitions: &[u16]| {
1886            // Stop after Rows lines if EndOfBlock is false and Rows > 0
1887            if !end_of_block && rows_limit > 0 && line_count >= rows_limit {
1888                return;
1889            }
1890
1891            // Convert transitions to pixels and pack into bytes
1892            let line = fax::decoder::Line { transitions, width };
1893            let mut row = vec![0u8; row_bytes];
1894            for (i, color) in line.pels().enumerate() {
1895                if i >= width as usize {
1896                    break;
1897                }
1898                // fax crate: Color::Black = mark bit, Color::White = no bit
1899                // Pack MSB-first: pixel 0 in bit 7
1900                let is_set = matches!(color, fax::Color::Black);
1901                if is_set {
1902                    row[i / 8] |= 0x80 >> (i % 8);
1903                }
1904            }
1905
1906            // Apply BlackIs1 polarity:
1907            // CCITT convention: 1 = black (mark). fax crate follows this.
1908            // PostScript convention (BlackIs1=false, default): 0 = black, 1 = white
1909            // So when BlackIs1 is false, we invert all bits.
1910            if !black_is1 {
1911                for byte in &mut row {
1912                    *byte = !*byte;
1913                }
1914            }
1915
1916            output.extend_from_slice(&row);
1917            line_count += 1;
1918        };
1919
1920        if k < 0 {
1921            // Group 4
1922            let height = if rows_limit > 0 {
1923                Some(rows_limit as u16)
1924            } else {
1925                None
1926            };
1927            fax::decoder::decode_g4(data.iter().copied(), width, height, |transitions| {
1928                process_line(transitions)
1929            });
1930        } else {
1931            // Group 3 (K=0: 1-D only, K>0: mixed 1-D/2-D)
1932            fax::decoder::decode_g3(data.iter().copied(), |transitions| {
1933                process_line(transitions);
1934            });
1935        }
1936
1937        Ok(output)
1938    }
1939
1940    /// Refill EexecDecode: read from source, decrypt with eexec cipher.
1941    /// Uses a small target (256 bytes) to limit read-ahead, since the
1942    /// encrypted section is followed by a finite cleartext padding area.
1943    fn refill_eexec(
1944        &mut self,
1945        source: EntityId,
1946        kind: &mut FilterKind,
1947        out: &mut Vec<u8>,
1948        eof: &mut bool,
1949    ) -> io::Result<()> {
1950        let FilterKind::EexecDecode {
1951            r,
1952            is_hex,
1953            skip_count,
1954            hex_leftover,
1955        } = kind
1956        else {
1957            return Ok(());
1958        };
1959
1960        const C1: u16 = 52845;
1961        const C2: u16 = 22719;
1962        // Produce exactly 1 output byte per refill (byte-at-a-time). The
1963        // underlying source stream does its own buffering; over-reading
1964        // here would cause the source position to drift past the
1965        // encrypted section into the cleartext padding.
1966        const TARGET: usize = 1;
1967
1968        // Auto-detect format on first call: read 8 bytes and check if hex
1969        if is_hex.is_none() {
1970            let mut probe = Vec::with_capacity(8);
1971            for _ in 0..8 {
1972                match self.read_byte(source)? {
1973                    Some(b) => probe.push(b),
1974                    None => break,
1975                }
1976            }
1977            if probe.is_empty() {
1978                *eof = true;
1979                return Ok(());
1980            }
1981            let hex = probe
1982                .iter()
1983                .all(|&b| b.is_ascii_hexdigit() || is_ps_whitespace(b));
1984            *is_hex = Some(hex);
1985
1986            // Process probe bytes through the cipher
1987            if hex {
1988                let mut hex_digits = Vec::new();
1989                for &b in &probe {
1990                    if b.is_ascii_hexdigit() {
1991                        hex_digits.push(b);
1992                    }
1993                }
1994                let mut i = 0;
1995                while i + 1 < hex_digits.len() {
1996                    let cipher = (hex_val(hex_digits[i]) << 4) | hex_val(hex_digits[i + 1]);
1997                    let plain = cipher ^ (*r >> 8) as u8;
1998                    *r = (cipher as u16)
1999                        .wrapping_add(*r)
2000                        .wrapping_mul(C1)
2001                        .wrapping_add(C2);
2002                    if *skip_count < 4 {
2003                        *skip_count += 1;
2004                    } else {
2005                        out.push(plain);
2006                    }
2007                    i += 2;
2008                }
2009                if i < hex_digits.len() {
2010                    *hex_leftover = Some(hex_digits[i]);
2011                }
2012            } else {
2013                for &cipher in &probe {
2014                    let plain = cipher ^ (*r >> 8) as u8;
2015                    *r = (cipher as u16)
2016                        .wrapping_add(*r)
2017                        .wrapping_mul(C1)
2018                        .wrapping_add(C2);
2019                    if *skip_count < 4 {
2020                        *skip_count += 1;
2021                    } else {
2022                        out.push(plain);
2023                    }
2024                }
2025            }
2026        }
2027
2028        // Continue reading and decrypting until we have TARGET bytes
2029        while out.len() < TARGET {
2030            let cipher_byte = if *is_hex == Some(true) {
2031                // Hex mode: read pairs of hex digits
2032                let hi = if let Some(h) = hex_leftover.take() {
2033                    h
2034                } else {
2035                    match read_next_hex_digit(self, source)? {
2036                        Some(d) => d,
2037                        None => {
2038                            *eof = true;
2039                            return Ok(());
2040                        }
2041                    }
2042                };
2043                // Odd hex digit at end — pad with 0.
2044                let lo = read_next_hex_digit(self, source)?.unwrap_or(b'0');
2045                (hex_val(hi) << 4) | hex_val(lo)
2046            } else {
2047                // Binary mode: read raw bytes
2048                match self.read_byte(source)? {
2049                    Some(b) => b,
2050                    None => {
2051                        *eof = true;
2052                        return Ok(());
2053                    }
2054                }
2055            };
2056
2057            let plain = cipher_byte ^ (*r >> 8) as u8;
2058            *r = (cipher_byte as u16)
2059                .wrapping_add(*r)
2060                .wrapping_mul(C1)
2061                .wrapping_add(C2);
2062            if *skip_count < 4 {
2063                *skip_count += 1;
2064            } else {
2065                out.push(plain);
2066            }
2067        }
2068
2069        Ok(())
2070    }
2071}
2072
2073/// Read the next hex digit from a file, skipping whitespace.
2074fn read_next_hex_digit(store: &mut FileStore, source: EntityId) -> io::Result<Option<u8>> {
2075    loop {
2076        match store.read_byte(source)? {
2077            Some(b) if b.is_ascii_hexdigit() => return Ok(Some(b)),
2078            Some(b) if is_ps_whitespace(b) => continue,
2079            Some(_) | None => return Ok(None),
2080        }
2081    }
2082}
2083
2084/// PostScript whitespace check.
2085fn is_ps_whitespace(b: u8) -> bool {
2086    matches!(b, b'\0' | b'\t' | b'\n' | 0x0C | b'\r' | b' ')
2087}
2088
2089/// Convert a hex digit character to its numeric value.
2090fn hex_val(b: u8) -> u8 {
2091    match b {
2092        b'0'..=b'9' => b - b'0',
2093        b'a'..=b'f' => b - b'a' + 10,
2094        b'A'..=b'F' => b - b'A' + 10,
2095        _ => 0,
2096    }
2097}
2098
2099/// Decode a partial ASCII85 group (2–4 chars) at end of data.
2100fn ascii85_decode_partial(group: &[u8], out: &mut Vec<u8>) {
2101    let n = group.len();
2102    if n < 2 {
2103        return;
2104    }
2105    // Pad with 84 (value of 'u' - '!')
2106    let mut padded = [84u8; 5];
2107    padded[..n].copy_from_slice(group);
2108
2109    let val = padded[0] as u64 * 85 * 85 * 85 * 85
2110        + padded[1] as u64 * 85 * 85 * 85
2111        + padded[2] as u64 * 85 * 85
2112        + padded[3] as u64 * 85
2113        + padded[4] as u64;
2114
2115    let bytes = [
2116        (val >> 24) as u8,
2117        (val >> 16) as u8,
2118        (val >> 8) as u8,
2119        val as u8,
2120    ];
2121    // Output n-1 bytes
2122    out.extend_from_slice(&bytes[..n - 1]);
2123}
2124
2125/// Compress data through a flate compressor, writing output via callback.
2126fn flate_compress_data(
2127    compressor: &mut flate2::Compress,
2128    data: &[u8],
2129    mut write_fn: impl FnMut(&[u8]) -> io::Result<()>,
2130) -> io::Result<()> {
2131    let mut out = vec![0u8; data.len() + 64];
2132    let mut input_pos = 0;
2133    loop {
2134        let before_in = compressor.total_in() as usize;
2135        let before_out = compressor.total_out() as usize;
2136        let status = compressor
2137            .compress(&data[input_pos..], &mut out, flate2::FlushCompress::None)
2138            .map_err(|e| io::Error::other(e.to_string()))?;
2139        let consumed = compressor.total_in() as usize - before_in;
2140        let produced = compressor.total_out() as usize - before_out;
2141        input_pos += consumed;
2142        if produced > 0 {
2143            write_fn(&out[..produced])?;
2144        }
2145        if input_pos >= data.len() || matches!(status, flate2::Status::StreamEnd) {
2146            break;
2147        }
2148    }
2149    Ok(())
2150}
2151
2152/// Encode a row using PNG Sub filter (type 1) for FlateEncode predictor.
2153fn encode_png_row(row: &[u8], bpp: usize) -> Vec<u8> {
2154    let mut encoded = Vec::with_capacity(1 + row.len());
2155    encoded.push(1); // Sub filter type byte
2156    for i in 0..row.len() {
2157        let left = if i >= bpp { row[i - bpp] } else { 0 };
2158        encoded.push(row[i].wrapping_sub(left));
2159    }
2160    encoded
2161}
2162
2163/// Encode a row using TIFF Predictor 2 (horizontal differencing) for FlateEncode.
2164fn encode_tiff_row(row: &[u8], colors: usize) -> Vec<u8> {
2165    let mut encoded = Vec::from(row);
2166    // Work backwards to avoid clobbering values we still need
2167    for i in (colors..encoded.len()).rev() {
2168        encoded[i] = encoded[i].wrapping_sub(encoded[i - colors]);
2169    }
2170    encoded
2171}
2172
2173/// Apply TIFF horizontal differencing predictor.
2174fn apply_tiff_predictor(data: &[u8], out: &mut Vec<u8>, columns: u32, colors: u32, bpc: u32) {
2175    let bytes_per_pixel = (colors * bpc).div_ceil(8);
2176    let row_bytes = (columns * colors * bpc).div_ceil(8);
2177
2178    for row in data.chunks(row_bytes as usize) {
2179        let mut prev = vec![0u8; bytes_per_pixel as usize];
2180        for (i, &b) in row.iter().enumerate() {
2181            let pi = i % bytes_per_pixel as usize;
2182            let val = b.wrapping_add(prev[pi]);
2183            out.push(val);
2184            prev[pi] = val;
2185        }
2186    }
2187}
2188
2189/// Apply PNG row predictor filters.
2190fn apply_png_predictor(
2191    data: &[u8],
2192    out: &mut Vec<u8>,
2193    columns: u32,
2194    colors: u32,
2195    bpc: u32,
2196    prev_row: &mut Vec<u8>,
2197) {
2198    let bytes_per_pixel = (colors * bpc).div_ceil(8) as usize;
2199    let row_bytes = (columns * colors * bpc).div_ceil(8) as usize;
2200    let stride = row_bytes + 1; // +1 for filter type byte
2201
2202    if prev_row.is_empty() {
2203        prev_row.resize(row_bytes, 0);
2204    }
2205
2206    let mut pos = 0;
2207    while pos + stride <= data.len() {
2208        let filter_type = data[pos];
2209        let row_data = &data[pos + 1..pos + stride];
2210        let mut decoded_row = vec![0u8; row_bytes];
2211
2212        for i in 0..row_bytes {
2213            let raw = row_data[i];
2214            let a = if i >= bytes_per_pixel {
2215                decoded_row[i - bytes_per_pixel]
2216            } else {
2217                0
2218            };
2219            let b = prev_row[i];
2220            let c = if i >= bytes_per_pixel {
2221                prev_row[i - bytes_per_pixel]
2222            } else {
2223                0
2224            };
2225
2226            decoded_row[i] = match filter_type {
2227                0 => raw,                                                 // None
2228                1 => raw.wrapping_add(a),                                 // Sub
2229                2 => raw.wrapping_add(b),                                 // Up
2230                3 => raw.wrapping_add(((a as u16 + b as u16) / 2) as u8), // Average
2231                4 => raw.wrapping_add(paeth_predictor(a, b, c)),          // Paeth
2232                _ => raw,
2233            };
2234        }
2235
2236        out.extend_from_slice(&decoded_row);
2237        prev_row.copy_from_slice(&decoded_row);
2238        pos += stride;
2239    }
2240
2241    // Handle any remaining bytes that don't form a complete row
2242    if pos < data.len() {
2243        out.extend_from_slice(&data[pos..]);
2244    }
2245}
2246
2247/// PNG Paeth predictor function.
2248fn paeth_predictor(a: u8, b: u8, c: u8) -> u8 {
2249    let p = a as i32 + b as i32 - c as i32;
2250    let pa = (p - a as i32).abs();
2251    let pb = (p - b as i32).abs();
2252    let pc = (p - c as i32).abs();
2253    if pa <= pb && pa <= pc {
2254        a
2255    } else if pb <= pc {
2256        b
2257    } else {
2258        c
2259    }
2260}
2261
2262impl FilterKind {
2263    /// Create a new ASCIIHexDecode filter.
2264    pub fn ascii_hex_decode() -> Self {
2265        Self::ASCIIHexDecode
2266    }
2267
2268    /// Create a new ASCII85Decode filter.
2269    pub fn ascii85_decode() -> Self {
2270        Self::ASCII85Decode { group: Vec::new() }
2271    }
2272
2273    /// Create a new RunLengthDecode filter.
2274    pub fn run_length_decode() -> Self {
2275        Self::RunLengthDecode {
2276            state: RleState::Init,
2277        }
2278    }
2279
2280    /// Create a new FlateDecode filter with predictor parameters.
2281    pub fn flate_decode(predictor: u8, columns: u32, colors: u32, bpc: u32) -> Self {
2282        Self::FlateDecode {
2283            decompressor: flate2::Decompress::new(true),
2284            raw_buf: Vec::new(),
2285            predictor,
2286            columns,
2287            colors,
2288            bpc,
2289            prev_row: Vec::new(),
2290        }
2291    }
2292
2293    /// Create a new streaming LZWDecode filter.
2294    pub fn lzw_decode(early_change: bool) -> Self {
2295        let decoder = if early_change {
2296            weezl::decode::Decoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8)
2297        } else {
2298            weezl::decode::Decoder::new(weezl::BitOrder::Msb, 8)
2299        };
2300        Self::LZWDecode {
2301            decoder,
2302            raw_buf: Vec::new(),
2303        }
2304    }
2305
2306    /// Create a new DCTDecode filter (lazily decoded on first read).
2307    pub fn dct_decode(color_transform: Option<bool>) -> Self {
2308        Self::DCTDecode {
2309            decoded: false,
2310            color_transform,
2311        }
2312    }
2313
2314    /// Create a new JBIG2Decode filter (lazily decoded on first read).
2315    /// `globals` is the optional /JBIG2Globals stream referenced by the
2316    /// PDF spec; pass `None` for an embedded stream that doesn't share a
2317    /// globals segment.
2318    pub fn jbig2_decode(globals: Option<Vec<u8>>) -> Self {
2319        Self::JBIG2Decode {
2320            decoded: false,
2321            globals,
2322        }
2323    }
2324
2325    /// Create a new JPXDecode filter (lazily decoded on first read).
2326    pub fn jpx_decode() -> Self {
2327        Self::JPXDecode { decoded: false }
2328    }
2329
2330    /// Create a new DCTEncode filter.
2331    pub fn dct_encode(
2332        columns: u32,
2333        rows: u32,
2334        colors: u32,
2335        quality: u8,
2336        color_transform: bool,
2337    ) -> Self {
2338        Self::DCTEncode {
2339            buf: Vec::new(),
2340            columns,
2341            rows,
2342            colors,
2343            quality,
2344            color_transform,
2345        }
2346    }
2347
2348    /// Create a new SubFileDecode filter.
2349    pub fn sub_file_decode(
2350        eod_string: Vec<u8>,
2351        eod_count: i32,
2352        bytes_remaining: Option<i64>,
2353    ) -> Self {
2354        Self::SubFileDecode {
2355            eod_string,
2356            eod_count,
2357            bytes_remaining,
2358        }
2359    }
2360
2361    /// Create a new CCITTFaxDecode filter with PLRM defaults.
2362    pub fn ccittfax_decode(
2363        k: i32,
2364        columns: u32,
2365        rows: u32,
2366        end_of_line: bool,
2367        encoded_byte_align: bool,
2368        end_of_block: bool,
2369        black_is1: bool,
2370    ) -> Self {
2371        Self::CCITTFaxDecode {
2372            decoded: false,
2373            k,
2374            columns,
2375            rows,
2376            end_of_line,
2377            encoded_byte_align,
2378            end_of_block,
2379            black_is1,
2380        }
2381    }
2382
2383    /// Create a new EexecDecode filter.
2384    pub fn eexec_decode() -> Self {
2385        Self::EexecDecode {
2386            r: 55665,
2387            is_hex: None,
2388            skip_count: 0,
2389            hex_leftover: None,
2390        }
2391    }
2392
2393    /// Create a new ASCIIHexEncode filter.
2394    pub fn ascii_hex_encode() -> Self {
2395        Self::ASCIIHexEncode
2396    }
2397
2398    /// Create a new ASCII85Encode filter.
2399    pub fn ascii85_encode() -> Self {
2400        Self::ASCII85Encode {
2401            buf: Vec::with_capacity(4),
2402            col: 0,
2403        }
2404    }
2405
2406    /// Create a new RunLengthEncode filter.
2407    pub fn run_length_encode() -> Self {
2408        Self::RunLengthEncode {
2409            pending: Vec::new(),
2410            run_byte: None,
2411            run_count: 0,
2412        }
2413    }
2414
2415    /// Create a new FlateEncode filter with optional predictor parameters.
2416    pub fn flate_encode(predictor: u8, columns: u32, colors: u32, bpc: u32) -> Self {
2417        let row_width = (columns as usize * colors as usize * bpc as usize).div_ceil(8);
2418        let bpp = (colors as usize * bpc as usize).div_ceil(8);
2419        Self::FlateEncode {
2420            compressor: flate2::Compress::new(flate2::Compression::default(), true),
2421            predictor,
2422            columns,
2423            colors,
2424            bpc,
2425            row_width,
2426            bpp: bpp.max(1),
2427            encode_buf: Vec::new(),
2428            prev_row: vec![0u8; row_width],
2429        }
2430    }
2431
2432    /// Create a new LZWEncode filter (EarlyChange=1 by default).
2433    pub fn lzw_encode(early_change: bool) -> Self {
2434        let encoder = if early_change {
2435            weezl::encode::Encoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8)
2436        } else {
2437            weezl::encode::Encoder::new(weezl::BitOrder::Msb, 8)
2438        };
2439        Self::LZWEncode { encoder }
2440    }
2441
2442    /// Create a new NullEncode filter.
2443    pub fn null_encode() -> Self {
2444        Self::NullEncode
2445    }
2446
2447    /// Returns true if this is an encode (write-direction) filter.
2448    pub fn is_encode(&self) -> bool {
2449        matches!(
2450            self,
2451            Self::ASCIIHexEncode
2452                | Self::ASCII85Encode { .. }
2453                | Self::RunLengthEncode { .. }
2454                | Self::FlateEncode { .. }
2455                | Self::LZWEncode { .. }
2456                | Self::NullEncode
2457                | Self::DCTEncode { .. }
2458        )
2459    }
2460}
2461
2462impl FileStore {
2463    /// Create a lazy DCTDecode filter (decodes on first read).
2464    pub fn create_dct_filter(
2465        &mut self,
2466        source: EntityId,
2467        color_transform: Option<bool>,
2468    ) -> EntityId {
2469        self.create_filter(source, FilterKind::dct_decode(color_transform))
2470    }
2471
2472    /// Create a DCTEncode filter.
2473    pub fn create_dct_encode_filter(
2474        &mut self,
2475        target: EntityId,
2476        columns: u32,
2477        rows: u32,
2478        colors: u32,
2479        quality: u8,
2480        color_transform: bool,
2481    ) -> EntityId {
2482        self.create_encode_filter(
2483            target,
2484            FilterKind::dct_encode(columns, rows, colors, quality, color_transform),
2485        )
2486    }
2487}
2488
2489impl Default for FileStore {
2490    fn default() -> Self {
2491        Self::new()
2492    }
2493}
2494
2495#[cfg(test)]
2496mod tests {
2497    use super::*;
2498    use std::io::Write;
2499
2500    /// Build a path inside the OS temp directory. Portable across Linux
2501    /// (`/tmp`), macOS (`/var/folders/...`), and Windows
2502    /// (`%LOCALAPPDATA%\Temp`).
2503    fn tmp_path(name: &str) -> String {
2504        std::env::temp_dir()
2505            .join(name)
2506            .to_string_lossy()
2507            .into_owned()
2508    }
2509
2510    #[test]
2511    fn test_prealloc_standard_streams() {
2512        let store = FileStore::new();
2513        assert_eq!(store.len(), 3);
2514        assert!(store.is_open(FILE_STDIN));
2515        assert!(store.is_open(FILE_STDOUT));
2516        assert!(store.is_open(FILE_STDERR));
2517        assert_eq!(store.name(FILE_STDIN), "%stdin");
2518    }
2519
2520    #[test]
2521    fn test_open_special_names() {
2522        let mut store = FileStore::new();
2523        assert_eq!(store.open("%stdin", "r").unwrap(), FILE_STDIN);
2524        assert_eq!(store.open("%stdout", "w").unwrap(), FILE_STDOUT);
2525        assert_eq!(store.open("%stderr", "w").unwrap(), FILE_STDERR);
2526    }
2527
2528    #[test]
2529    fn test_file_round_trip() {
2530        let mut store = FileStore::new();
2531        let path = tmp_path("stet_test_file_store.txt");
2532        let path = path.as_str();
2533
2534        // Write
2535        let wid = store.open(path, "w").unwrap();
2536        store.write_from(wid, b"hello\n").unwrap();
2537        store.flush(wid).unwrap();
2538        store.close(wid).unwrap();
2539
2540        // Read
2541        let rid = store.open(path, "r").unwrap();
2542        let mut buf = vec![0u8; 6];
2543        let n = store.read_into(rid, &mut buf).unwrap();
2544        assert_eq!(n, 6);
2545        assert_eq!(&buf, b"hello\n");
2546        store.close(rid).unwrap();
2547
2548        // Cleanup
2549        std::fs::remove_file(path).ok();
2550    }
2551
2552    #[test]
2553    fn test_readline() {
2554        let mut store = FileStore::new();
2555        let path = tmp_path("stet_test_readline.txt");
2556        let path = path.as_str();
2557
2558        // Write test data
2559        {
2560            let mut f = std::fs::File::create(path).unwrap();
2561            f.write_all(b"line1\nline2\n").unwrap();
2562        }
2563
2564        let id = store.open(path, "r").unwrap();
2565        let mut buf = vec![0u8; 20];
2566        let (n, nl) = store.readline(id, &mut buf).unwrap();
2567        assert_eq!(&buf[..n], b"line1");
2568        assert!(nl);
2569
2570        let (n, nl) = store.readline(id, &mut buf).unwrap();
2571        assert_eq!(&buf[..n], b"line2");
2572        assert!(nl);
2573
2574        store.close(id).unwrap();
2575        std::fs::remove_file(path).ok();
2576    }
2577
2578    #[test]
2579    fn test_file_position() {
2580        let mut store = FileStore::new();
2581        let path = tmp_path("stet_test_filepos.txt");
2582        let path = path.as_str();
2583        {
2584            let mut f = std::fs::File::create(path).unwrap();
2585            f.write_all(b"abcdef").unwrap();
2586        }
2587
2588        let id = store.open(path, "r").unwrap();
2589        assert_eq!(store.position(id).unwrap(), 0);
2590        let mut buf = [0u8; 3];
2591        store.read_into(id, &mut buf).unwrap();
2592        assert_eq!(store.position(id).unwrap(), 3);
2593        store.set_position(id, 0).unwrap();
2594        assert_eq!(store.position(id).unwrap(), 0);
2595        store.close(id).unwrap();
2596        std::fs::remove_file(path).ok();
2597    }
2598
2599    #[test]
2600    fn test_close_and_reopen() {
2601        let mut store = FileStore::new();
2602        let path = tmp_path("stet_test_close.txt");
2603        let path = path.as_str();
2604        let id = store.open(path, "w").unwrap();
2605        store.write_from(id, b"test").unwrap();
2606        store.close(id).unwrap();
2607        assert!(!store.is_open(id));
2608        std::fs::remove_file(path).ok();
2609    }
2610
2611    #[test]
2612    fn test_invalid_mode() {
2613        let mut store = FileStore::new();
2614        assert!(
2615            store
2616                .open(&tmp_path("stet_test_invalid_mode"), "z")
2617                .is_err()
2618        );
2619    }
2620
2621    #[test]
2622    fn test_read_byte() {
2623        let mut store = FileStore::new();
2624        let path = tmp_path("stet_test_readbyte.txt");
2625        let path = path.as_str();
2626        {
2627            let mut f = std::fs::File::create(path).unwrap();
2628            f.write_all(b"AB").unwrap();
2629        }
2630        let id = store.open(path, "r").unwrap();
2631        assert_eq!(store.read_byte(id).unwrap(), Some(b'A'));
2632        assert_eq!(store.read_byte(id).unwrap(), Some(b'B'));
2633        assert_eq!(store.read_byte(id).unwrap(), None);
2634        store.close(id).unwrap();
2635        std::fs::remove_file(path).ok();
2636    }
2637
2638    // --- String source tests ---
2639
2640    #[test]
2641    fn test_string_source() {
2642        let mut store = FileStore::new();
2643        let id = store.create_string_source(b"Hello".to_vec());
2644        assert_eq!(store.read_byte(id).unwrap(), Some(b'H'));
2645        assert_eq!(store.read_byte(id).unwrap(), Some(b'e'));
2646        let mut buf = [0u8; 3];
2647        let n = store.read_into(id, &mut buf).unwrap();
2648        assert_eq!(n, 3);
2649        assert_eq!(&buf, b"llo");
2650        assert_eq!(store.read_byte(id).unwrap(), None);
2651    }
2652
2653    #[test]
2654    fn test_string_source_empty() {
2655        let mut store = FileStore::new();
2656        let id = store.create_string_source(Vec::new());
2657        assert_eq!(store.read_byte(id).unwrap(), None);
2658    }
2659
2660    // --- ASCIIHexDecode tests ---
2661
2662    #[test]
2663    fn test_ascii_hex_decode() {
2664        let mut store = FileStore::new();
2665        let src = store.create_string_source(b"48 65 6C 6C 6F>".to_vec());
2666        let filt = store.create_filter(src, FilterKind::ASCIIHexDecode);
2667        let mut result = Vec::new();
2668        loop {
2669            match store.read_byte(filt).unwrap() {
2670                Some(b) => result.push(b),
2671                None => break,
2672            }
2673        }
2674        assert_eq!(&result, b"Hello");
2675    }
2676
2677    #[test]
2678    fn test_ascii_hex_decode_odd_nibble() {
2679        let mut store = FileStore::new();
2680        let src = store.create_string_source(b"4>".to_vec());
2681        let filt = store.create_filter(src, FilterKind::ASCIIHexDecode);
2682        let b = store.read_byte(filt).unwrap().unwrap();
2683        assert_eq!(b, 0x40);
2684        assert_eq!(store.read_byte(filt).unwrap(), None);
2685    }
2686
2687    #[test]
2688    fn test_ascii_hex_decode_whitespace() {
2689        let mut store = FileStore::new();
2690        let src = store.create_string_source(b"4 1\n4 2>".to_vec());
2691        let filt = store.create_filter(src, FilterKind::ASCIIHexDecode);
2692        let mut result = Vec::new();
2693        loop {
2694            match store.read_byte(filt).unwrap() {
2695                Some(b) => result.push(b),
2696                None => break,
2697            }
2698        }
2699        assert_eq!(&result, &[0x41, 0x42]);
2700    }
2701
2702    // --- ASCII85Decode tests ---
2703
2704    #[test]
2705    fn test_ascii85_decode() {
2706        let mut store = FileStore::new();
2707        // "Man " in ASCII85 is "9jqo^"
2708        let src = store.create_string_source(b"9jqo^~>".to_vec());
2709        let filt = store.create_filter(src, FilterKind::ASCII85Decode { group: Vec::new() });
2710        let mut result = Vec::new();
2711        loop {
2712            match store.read_byte(filt).unwrap() {
2713                Some(b) => result.push(b),
2714                None => break,
2715            }
2716        }
2717        assert_eq!(&result, b"Man ");
2718    }
2719
2720    #[test]
2721    fn test_ascii85_decode_z() {
2722        let mut store = FileStore::new();
2723        let src = store.create_string_source(b"z~>".to_vec());
2724        let filt = store.create_filter(src, FilterKind::ASCII85Decode { group: Vec::new() });
2725        let mut result = Vec::new();
2726        loop {
2727            match store.read_byte(filt).unwrap() {
2728                Some(b) => result.push(b),
2729                None => break,
2730            }
2731        }
2732        assert_eq!(&result, &[0, 0, 0, 0]);
2733    }
2734
2735    #[test]
2736    fn test_ascii85_decode_partial() {
2737        let mut store = FileStore::new();
2738        // Partial group: 2 chars "9j" → 1 byte
2739        let src = store.create_string_source(b"9j~>".to_vec());
2740        let filt = store.create_filter(src, FilterKind::ASCII85Decode { group: Vec::new() });
2741        let mut result = Vec::new();
2742        loop {
2743            match store.read_byte(filt).unwrap() {
2744                Some(b) => result.push(b),
2745                None => break,
2746            }
2747        }
2748        assert_eq!(result.len(), 1);
2749    }
2750
2751    // --- RunLengthDecode tests ---
2752
2753    #[test]
2754    fn test_rle_literal() {
2755        let mut store = FileStore::new();
2756        // Length 2 (= 3 literal bytes), then EOD
2757        let src = store.create_string_source(vec![2, b'A', b'B', b'C', 128]);
2758        let filt = store.create_filter(
2759            src,
2760            FilterKind::RunLengthDecode {
2761                state: RleState::Init,
2762            },
2763        );
2764        let mut result = Vec::new();
2765        loop {
2766            match store.read_byte(filt).unwrap() {
2767                Some(b) => result.push(b),
2768                None => break,
2769            }
2770        }
2771        assert_eq!(&result, b"ABC");
2772    }
2773
2774    #[test]
2775    fn test_rle_repeat() {
2776        let mut store = FileStore::new();
2777        // 253 → repeat next byte (257-253)=4 times, then EOD
2778        let src = store.create_string_source(vec![253, b'X', 128]);
2779        let filt = store.create_filter(
2780            src,
2781            FilterKind::RunLengthDecode {
2782                state: RleState::Init,
2783            },
2784        );
2785        let mut result = Vec::new();
2786        loop {
2787            match store.read_byte(filt).unwrap() {
2788                Some(b) => result.push(b),
2789                None => break,
2790            }
2791        }
2792        assert_eq!(&result, b"XXXX");
2793    }
2794
2795    // --- SubFileDecode tests ---
2796
2797    #[test]
2798    fn test_subfile_byte_count() {
2799        let mut store = FileStore::new();
2800        let src = store.create_string_source(b"Hello, World!".to_vec());
2801        let filt = store.create_filter(
2802            src,
2803            FilterKind::SubFileDecode {
2804                eod_string: Vec::new(),
2805                eod_count: 0,
2806                bytes_remaining: Some(5),
2807            },
2808        );
2809        let mut result = Vec::new();
2810        loop {
2811            match store.read_byte(filt).unwrap() {
2812                Some(b) => result.push(b),
2813                None => break,
2814            }
2815        }
2816        assert_eq!(&result, b"Hello");
2817    }
2818
2819    // --- FlateDecode tests ---
2820
2821    #[test]
2822    fn test_flate_decode() {
2823        use flate2::Compression;
2824        use flate2::write::ZlibEncoder;
2825
2826        // Compress some data
2827        let original = b"Hello, stet PostScript interpreter! This is a test of FlateDecode.";
2828        let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
2829        encoder.write_all(original).unwrap();
2830        let compressed = encoder.finish().unwrap();
2831
2832        let mut store = FileStore::new();
2833        let src = store.create_string_source(compressed);
2834        let filt = store.create_filter(
2835            src,
2836            FilterKind::FlateDecode {
2837                decompressor: flate2::Decompress::new(true),
2838                raw_buf: Vec::new(),
2839                predictor: 1,
2840                columns: 0,
2841                colors: 0,
2842                bpc: 8,
2843                prev_row: Vec::new(),
2844            },
2845        );
2846        let mut result = Vec::new();
2847        loop {
2848            match store.read_byte(filt).unwrap() {
2849                Some(b) => result.push(b),
2850                None => break,
2851            }
2852        }
2853        assert_eq!(&result, original);
2854    }
2855
2856    // --- LZWDecode test ---
2857
2858    #[test]
2859    fn test_lzw_decode() {
2860        // LZW-compressed data for "TOBEORNOTTOBEORTOBEORNOT"
2861        // Encode with early-change (tiff_size_switch) to match default PS behavior
2862        let original = b"TOBEORNOTTOBEORTOBEORNOT";
2863        let mut encoder = weezl::encode::Encoder::with_tiff_size_switch(weezl::BitOrder::Msb, 8);
2864        let compressed = encoder.encode(original).unwrap();
2865
2866        let mut store = FileStore::new();
2867        let src = store.create_string_source(compressed);
2868        let filt = store.create_filter(src, FilterKind::lzw_decode(true));
2869        let mut result = Vec::new();
2870        loop {
2871            match store.read_byte(filt).unwrap() {
2872                Some(b) => result.push(b),
2873                None => break,
2874            }
2875        }
2876        assert_eq!(&result, original);
2877    }
2878
2879    #[test]
2880    fn test_lzw_encode_single_byte() {
2881        let mut store = FileStore::new();
2882        let path = tmp_path("stet_test_lzw_single.bin");
2883        let path = path.as_str();
2884
2885        let target = store.open(path, "w").unwrap();
2886        let enc = store.create_encode_filter(target, FilterKind::lzw_encode(true));
2887        store.write_from(enc, b"Q").unwrap();
2888        store.close(enc).unwrap();
2889
2890        let src = store.open(path, "r").unwrap();
2891        let dec = store.create_filter(src, FilterKind::lzw_decode(true));
2892        let mut result = Vec::new();
2893        loop {
2894            match store.read_byte(dec).unwrap() {
2895                Some(b) => result.push(b),
2896                None => break,
2897            }
2898        }
2899        assert_eq!(&result, b"Q");
2900        std::fs::remove_file(path).ok();
2901    }
2902
2903    #[test]
2904    fn test_lzw_encode_roundtrip() {
2905        let mut store = FileStore::new();
2906        let original = b"Hello LZW!";
2907
2908        // Create a string source as the write target (we'll use a temp file)
2909        let path = tmp_path("stet_test_lzw_encode.bin");
2910        let path = path.as_str();
2911        let target = store.open(path, "w").unwrap();
2912        let enc = store.create_encode_filter(target, FilterKind::lzw_encode(true));
2913
2914        // Write data through encoder
2915        store.write_from(enc, original).unwrap();
2916        store.close(enc).unwrap();
2917
2918        // Read back and decode
2919        let src = store.open(path, "r").unwrap();
2920        let dec = store.create_filter(src, FilterKind::lzw_decode(true));
2921        let mut result = Vec::new();
2922        loop {
2923            match store.read_byte(dec).unwrap() {
2924                Some(b) => result.push(b),
2925                None => break,
2926            }
2927        }
2928        assert_eq!(&result, original);
2929        std::fs::remove_file(path).ok();
2930    }
2931
2932    #[test]
2933    fn test_ascii_hex_encode_roundtrip() {
2934        let mut store = FileStore::new();
2935        let original = b"Hello";
2936        let path = tmp_path("stet_test_hex_encode.bin");
2937        let path = path.as_str();
2938
2939        let target = store.open(path, "w").unwrap();
2940        let enc = store.create_encode_filter(target, FilterKind::ascii_hex_encode());
2941        store.write_from(enc, original).unwrap();
2942        store.close(enc).unwrap();
2943
2944        let src = store.open(path, "r").unwrap();
2945        let dec = store.create_filter(src, FilterKind::ascii_hex_decode());
2946        let mut result = Vec::new();
2947        loop {
2948            match store.read_byte(dec).unwrap() {
2949                Some(b) => result.push(b),
2950                None => break,
2951            }
2952        }
2953        assert_eq!(&result, original);
2954        std::fs::remove_file(path).ok();
2955    }
2956
2957    #[test]
2958    fn test_ascii85_encode_roundtrip() {
2959        let mut store = FileStore::new();
2960        let original = b"Man sure.";
2961        let path = tmp_path("stet_test_a85_encode.bin");
2962        let path = path.as_str();
2963
2964        let target = store.open(path, "w").unwrap();
2965        let enc = store.create_encode_filter(target, FilterKind::ascii85_encode());
2966        store.write_from(enc, original).unwrap();
2967        store.close(enc).unwrap();
2968
2969        let src = store.open(path, "r").unwrap();
2970        let dec = store.create_filter(src, FilterKind::ascii85_decode());
2971        let mut result = Vec::new();
2972        loop {
2973            match store.read_byte(dec).unwrap() {
2974                Some(b) => result.push(b),
2975                None => break,
2976            }
2977        }
2978        assert_eq!(&result, original);
2979        std::fs::remove_file(path).ok();
2980    }
2981
2982    #[test]
2983    fn test_rle_encode_roundtrip() {
2984        let mut store = FileStore::new();
2985        let original = b"AAAAAABCBCBCBC";
2986        let path = tmp_path("stet_test_rle_encode.bin");
2987        let path = path.as_str();
2988
2989        let target = store.open(path, "w").unwrap();
2990        let enc = store.create_encode_filter(target, FilterKind::run_length_encode());
2991        store.write_from(enc, original).unwrap();
2992        store.close(enc).unwrap();
2993
2994        let src = store.open(path, "r").unwrap();
2995        let dec = store.create_filter(src, FilterKind::run_length_decode());
2996        let mut result = Vec::new();
2997        loop {
2998            match store.read_byte(dec).unwrap() {
2999                Some(b) => result.push(b),
3000                None => break,
3001            }
3002        }
3003        assert_eq!(&result, original);
3004        std::fs::remove_file(path).ok();
3005    }
3006
3007    #[test]
3008    fn test_flate_encode_roundtrip() {
3009        let mut store = FileStore::new();
3010        let original = b"Hello Flate compression test data!";
3011        let path = tmp_path("stet_test_flate_encode.bin");
3012        let path = path.as_str();
3013
3014        let target = store.open(path, "w").unwrap();
3015        let enc = store.create_encode_filter(target, FilterKind::flate_encode(1, 1, 1, 8));
3016        store.write_from(enc, original).unwrap();
3017        store.close(enc).unwrap();
3018
3019        let src = store.open(path, "r").unwrap();
3020        let dec = store.create_filter(src, FilterKind::flate_decode(1, 1, 1, 8));
3021        let mut result = Vec::new();
3022        loop {
3023            match store.read_byte(dec).unwrap() {
3024                Some(b) => result.push(b),
3025                None => break,
3026            }
3027        }
3028        assert_eq!(&result, original);
3029        std::fs::remove_file(path).ok();
3030    }
3031
3032    // --- JBIG2Decode / JPXDecode tests ---
3033
3034    #[test]
3035    fn test_jbig2_decode_constructor() {
3036        let kind = FilterKind::jbig2_decode(None);
3037        match kind {
3038            FilterKind::JBIG2Decode {
3039                decoded: false,
3040                globals: None,
3041            } => {}
3042            _ => panic!("unexpected variant"),
3043        }
3044        let kind = FilterKind::jbig2_decode(Some(vec![1, 2, 3]));
3045        match kind {
3046            FilterKind::JBIG2Decode {
3047                decoded: false,
3048                globals: Some(ref g),
3049            } if g == &[1, 2, 3] => {}
3050            _ => panic!("globals not stored"),
3051        }
3052    }
3053
3054    #[test]
3055    fn test_jpx_decode_constructor() {
3056        let kind = FilterKind::jpx_decode();
3057        match kind {
3058            FilterKind::JPXDecode { decoded: false } => {}
3059            _ => panic!("unexpected variant"),
3060        }
3061    }
3062
3063    #[test]
3064    fn test_jbig2_decode_malformed_returns_ioerror() {
3065        // Random bytes that don't form a valid JBIG2 stream — the
3066        // decoder must surface the failure as an io::Error rather than
3067        // panicking. We don't assert the exact error string; just that
3068        // reading the filter produces an Err.
3069        let mut store = FileStore::new();
3070        let src = store.create_string_source(b"not a jbig2 stream".to_vec());
3071        let filt = store.create_filter(src, FilterKind::jbig2_decode(None));
3072        assert!(store.read_byte(filt).is_err());
3073    }
3074
3075    #[test]
3076    fn test_jpx_decode_malformed_returns_ioerror() {
3077        let mut store = FileStore::new();
3078        let src = store.create_string_source(b"not a jpeg2000 stream".to_vec());
3079        let filt = store.create_filter(src, FilterKind::jpx_decode());
3080        assert!(store.read_byte(filt).is_err());
3081    }
3082}