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