// Streaming DEFLATE compressor (RFC 1951), the push-based counterpart to
// `Inflater`. Unlike a one-shot `deflate_all`, it emits the stream as a
// sequence of blocks while input arrives, so memory is bounded and output is
// produced incrementally.
//
// Model:
// - Input accumulates in `pending`, whose leading `window_carry` bytes are
// history kept only so cross-block back-references resolve; the rest is
// input not yet emitted.
// - When enough tokenizable input has accrued (a block's worth, with a
// lookahead margin reserved so matches near the boundary aren't truncated),
// one non-final block is emitted via the shared `tokenize` + `emit_block`,
// `pending` is trimmed back to a 32 KB window, and the bytes flow on.
// - `end=true` tokenizes whatever remains into a final block and flushes.
// - A persistent `BitWriter` carries the sub-byte bit remainder across blocks
// (the bit stream is continuous; only the final block pads to a byte). Its
// byte buffer is drained into the caller's output and reclaimed each block,
// suspending with `NeedMoreOutput` whenever that buffer fills.
///|
/// Target tokenizable bytes per block, shared by the streaming `Deflater` and
/// one-shot `deflate_all`. 16 KB = zlib's default token-buffer size (memLevel 8
/// gives `lit_bufsize` = 2^(8+6) = 16384), the field-tested balance: large
/// enough to amortize the dynamic-Huffman table header over many bytes, small
/// enough that one table tracks roughly-stationary statistics and that
/// streaming bounds latency and memory rather than spanning whole megabytes.
let deflate_block_size = 16384
///|
/// Streaming DEFLATE compressor : a push-based, suspendable state
/// machine. It emits the stream block-by-block as input arrives , and chooses fixed- or dynamic-Huffman per block.
pub struct Deflater {
priv mut pending : Array[Byte] // carried window history + un-emitted input
priv mut window_carry : Int // leading history-only bytes in `pending`
priv w : BitWriter // persistent bit sink spanning all blocks
priv mut out_pos : Int // next `w.out` index to hand to the caller
priv mut emitted_final : Bool // the final block has been written into `w`
priv mut done : Bool // the final block has been fully drained
priv mut flush_done : Bool // the current buffered content has been sync-flushed
priv level : Int // compression level 0-9 (0 = stored only)
priv cfg : LevelConfig // match-search tuning for `level`
}
///|
/// Create a fresh compressor. `level` 0-9 (clamped) trades speed for ratio
/// with zlib-equivalent tuning; 0 emits stored blocks only, 6 is the default.
pub fn Deflater::new(level? : Int = 6) -> Deflater {
let lv = if level < 0 { 0 } else if level > 9 { 9 } else { level }
{
pending: [],
window_carry: 0,
w: BitWriter::new(),
out_pos: 0,
emitted_final: false,
done: false,
flush_done: false,
level: lv,
cfg: level_configs[lv],
}
}
///|
/// Preload the match window with a preset dictionary (the last 32 KB of
/// `dict`), so emitted back-references may reach into it (zlib
/// `deflateSetDictionary`). Must be called on a fresh stream — right after
/// `new()` or `reset()`, before any input. The decoder needs the same
/// dictionary.
pub fn Deflater::set_dictionary(self : Deflater, dict : BytesView) -> Unit {
guard self.pending.length() == 0 &&
self.w.out.length() == 0 &&
self.w.bit_count == 0 &&
!self.emitted_final else {
abort("Deflater::set_dictionary: stream already started")
}
let start = if dict.length() > window_size {
dict.length() - window_size
} else {
0
}
for i in start.. Unit {
self.pending.clear()
self.window_carry = 0
self.w.out.clear()
self.w.bitbuf = 0
self.w.bit_count = 0
self.out_pos = 0
self.emitted_final = false
self.done = false
self.flush_done = false
}
///|
/// Whether the final compressed stream has been fully emitted.
pub fn Deflater::is_finished(self : Deflater) -> Bool {
self.done
}
///|
/// Run one compression step. Buffers `input`, emits as many blocks as the
/// accrued input allows, and drains compressed bytes into the mutable view
/// `output`. Returns `(status, consumed, produced)`; `consumed` is always the
/// whole input view. Pass `end=true` (with any remaining input, or none) to
/// finalize, and keep calling with `end=true` while it returns `NeedMoreOutput`
/// to drain the rest. Pass `flush=true` to sync-flush (zlib `Z_SYNC_FLUSH`):
/// everything buffered is compressed and the bit stream is padded to a byte
/// boundary with an empty stored block, so all produced bytes are final and
/// transmittable while the stream continues. An empty `output` only accumulates
/// `input` (no bytes are produced).
pub fn Deflater::step(
self : Deflater,
input : BytesView,
output : MutArrayView[Byte],
end? : Bool = false,
flush? : Bool = false,
) -> (Status, Int, Int) {
for i in 0.. 0 {
self.flush_done = false
}
let consumed = input.length()
let out_end = output.length()
let mut write_pos = 0
for ;; {
// Drain compressed bytes already produced into the caller's buffer.
while self.out_pos < self.w.out.length() && write_pos < out_end {
output.set(write_pos, self.w.out[self.out_pos])
write_pos = write_pos + 1
self.out_pos = self.out_pos + 1
}
if self.out_pos < self.w.out.length() {
return (NeedMoreOutput, consumed, write_pos)
}
// The current block buffer is fully drained; reclaim it.
if self.w.out.length() > 0 {
self.w.out.clear()
self.out_pos = 0
}
if self.emitted_final {
self.done = true
return (Done, consumed, write_pos)
}
// Produce the next block, or wait for more input.
if end {
self.emit_final()
} else if flush && !self.flush_done {
self.emit_flush()
self.flush_done = true
} else if self.pending.length() - self.window_carry >=
deflate_block_size + min_lookahead {
self.emit_one()
} else {
return (NeedMoreInput, consumed, write_pos)
}
}
}