Skip to main content

stet_core/
file_store.rs

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