// One-shot DEFLATE compression: the whole input in, a complete raw DEFLATE
// stream out. The counterpart to the streaming `Deflater` (deflate.mbt) for
// when all input is already in hand.
///|
/// A reusable, whole-buffer raw DEFLATE compressor. Unlike the suspendable
/// `Deflater`, it reads the input directly and writes one complete stream into
/// a caller-provided buffer.
pub struct Compressor {
priv level : Int
priv cfg : LevelConfig
priv finder : MatchFinder
priv tokens : Array[Int]
priv ll_freq : FixedArray[Int]
priv d_freq : FixedArray[Int]
}
///|
/// Create a reusable whole-buffer compressor. `level` 0-9 is clamped, with
/// level 0 selecting stored blocks only.
pub fn Compressor::Compressor(level? : Int = 6) -> Compressor {
let level = clamp_level(level)
{
level,
cfg: level_configs[level],
finder: MatchFinder(),
tokens: [],
ll_freq: FixedArray::make(MAX_NUM_LIT, 0),
d_freq: FixedArray::make(MAX_NUM_DIST, 0),
}
}
///|
/// A conservative output capacity for `deflate_into` or
/// `Compressor::compress_into`. The returned value covers the fixed 16 KiB
/// block cadence and final bit padding for a non-negative input length.
pub fn deflate_bound(input_size : Int) -> Int {
let input_size = input_size.max(0)
let block_count = if input_size == 0 {
1
} else {
(input_size - 1) / DEFLATE_BLOCK_SIZE + 1
}
input_size + 5 * block_count + 1
}
///|
/// Compress `input` directly into `output`, returning its byte length. `None`
/// means that `output` was too small; any prefix written before exhaustion is
/// unspecified. Reuse one `Compressor` and one caller-owned output buffer to
/// avoid per-stream workspace and result-buffer allocation.
pub fn Compressor::compress_into(
self : Compressor,
input : Bytes,
output : FixedArray[Byte],
) -> Int? {
let w = BitWriter::into(output)
let n = input.length()
// Level 0 (and the empty input it subsumes): one stored pass, no parsing.
guard self.level != 0 else {
write_stored(w, input, 0, n, is_final=true)
w.flush()
if w.overflowed {
None
} else {
Some(w.direct_pos)
}
}
self.finder.reset()
// Each non-final block covers up to `DEFLATE_BLOCK_SIZE` tokenizable bytes; a
// boundary match may overhang, so `processed` (not `target`) sets the next
// start.
let mut pos = 0
for ;; {
let target = (pos + DEFLATE_BLOCK_SIZE).min(n)
self.tokens.clear()
self.ll_freq.fill(0)
self.d_freq.fill(0)
let processed = tokenize(
input,
self.finder,
pos,
target,
self.cfg,
self.tokens,
self.ll_freq,
self.d_freq,
)
emit_block(
w,
input,
pos,
processed,
self.tokens[:],
self.ll_freq,
self.d_freq,
is_final=processed >= n,
)
pos = processed
guard pos < n else { break }
}
w.flush()
if w.overflowed {
None
} else {
Some(w.direct_pos)
}
}
///|
/// Compress bytes into a complete raw DEFLATE stream held entirely in memory.
/// The whole input is tokenized directly, advancing a single offset and a
/// session-owned fixed matchfinder; unlike `Deflater`, no input staging ring
/// is needed.
pub fn deflate_all(input : Bytes, level? : Int = 6) -> Bytes {
let lv = clamp_level(level)
let w = BitWriter()
let n = input.length()
// Level 0 (and the empty input it subsumes): one stored pass, no parsing.
guard lv != 0 else {
write_stored(w, input, 0, n, is_final=true)
w.flush()
return Bytes::from_array(w.out)
}
let cfg = level_configs[lv]
let finder = MatchFinder()
let mut pos = 0
for ;; {
let target = (pos + DEFLATE_BLOCK_SIZE).min(n)
let tokens : Array[Int] = []
let ll_freq = FixedArray::make(286, 0)
let d_freq = FixedArray::make(30, 0)
let processed = tokenize(
input, finder, pos, target, cfg, tokens, ll_freq, d_freq,
)
emit_block(
w,
input,
pos,
processed,
tokens[:],
ll_freq,
d_freq,
is_final=processed >= n,
)
pos = processed
guard pos < n else { break }
}
w.flush()
Bytes::from_array(w.out)
}
///|
/// One-shot convenience form of `Compressor::compress_into`. It creates a
/// compressor for this call; use `Compressor` when encoding many streams.
pub fn deflate_into(
input : Bytes,
output : FixedArray[Byte],
level? : Int = 6,
) -> Int? {
Compressor(level~).compress_into(input, output)
}