// This file is based on the Go implementation found here:
// https://cs.opensource.google/go/go/+/refs/tags/go1.23.3:src/compress/zlib/writer.go
// which has the copyright notice:
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
///|
// A Writer takes data written to it and writes the compressed
// form of that data to an underlying writer (see NewWriter).
struct Writer {
mut w : &@io.Writer
// level : Int // only support BestSpeed
dict : Slice[Byte]
mut compressor : @flate.Writer
digest : &@hash.Hash32
mut err : IOError?
mut scratch : Slice[Byte] // [4]byte
mut wrote_header : Bool
}
///|
// Writer::new creates a new Writer.
// Writes to the returned Writer are compressed and written to w.
//
// It is the caller's responsibility to call Close on the Writer when done.
// Writes may be buffered and not flushed until Close.
pub fn Writer::new(w : &@io.Writer) -> Writer {
Writer::new_dict(w, Slice::new([]))
}
///|
// Writer::new_dict is like Writer::new but specifies a dictionary to compress with.
pub fn Writer::new_dict(w : &@io.Writer, dict : Slice[Byte]) -> Writer {
{
w,
dict,
compressor: @flate.Writer::new_dict(w, dict),
digest: @adler32.new(),
err: None,
scratch: Slice::new([0, 0, 0, 0]),
wrote_header: false,
}
}
///|
// reset clears the state of the Writer z such that it is equivalent to its
// initial state from NewWriterLevel or NewWriterLevelDict, but instead writing
// to w.
pub fn Writer::reset(self : Writer, w : &@io.Writer) -> Unit {
self.w = w
// self.level and self.dict left unchanged.
self.compressor = @flate.Writer::new_dict(w, self.dict)
self.digest.reset()
self.err = None
self.scratch = Slice::new([0, 0, 0, 0])
self.wrote_header = false
}
///|
// write_header writes the ZLIB header.
fn Writer::write_header(self : Writer) -> IOError? {
self.wrote_header = true
// ZLIB has a two-byte header (as documented in RFC 1950).
// The first four bits is the CINFO (compression info), which is 7 for the default deflate window size.
// The next four bits is the CM (compression method), which is 8 for deflate.
self.scratch[0] = 0x78
// The next two bits is the FLEVEL (compression level). The four values are:
// 0=fastest, 1=fast, 2=default, 3=best.
// The next bit, FDICT, is set if a dictionary is given.
// The final five FCHECK bits form a mod-31 checksum.
// This package only supports BestSpeed:
self.scratch[1] = 0
if self.dict.length() > 0 {
self.scratch[1] = self.scratch[1] | (b'\x01' << 5)
}
self.scratch[1] += (31U - be_uint16(self.scratch[:2]) % 31).to_byte()
let (_, err) = self.w.write(self.scratch[0:2])
if err != None {
return err
}
if self.dict.length() > 0 {
// The next four bytes are the Adler-32 checksum of the dictionary.
be_put_uint32(self.scratch, @adler32.checksum(self.dict))
let (_, err) = self.w.write(self.scratch[0:4])
if err != None {
return err
}
}
// if self.compressor == nil {
// // Initialize deflater unless the Writer is being reused
// // after a reset call.
// self.compressor, err = flate.NewWriterDict(self.w, self.level, self.dict)
// if err != nil {
// return err
// }
// self.digest = adler32.New()
// }
None
}
///|
fn be_put_uint32(b : Slice[Byte], value : UInt) -> Unit {
b[3] = (value & 0xff).to_byte()
b[2] = ((value >> 8) & 0xff).to_byte()
b[1] = ((value >> 16) & 0xff).to_byte()
b[0] = ((value >> 24) & 0xff).to_byte()
}
///|
/// write writes a compressed form of p to the underlying io.Writer. The
/// compressed bytes are not necessarily flushed until the Writer is closed or
/// explicitly flushed.
pub impl @io.Writer for Writer with write(self, p) {
if not(self.wrote_header) {
self.err = self.write_header()
}
if self.err != None {
return (0, self.err)
}
if p.length() == 0 {
return (0, None)
}
let (n, err) = self.compressor.write(p)
if err != None {
self.err = err
return (n, self.err)
}
let _ = self.digest.write(p)
return (n, self.err)
}
///|
// flush flushes the Writer to its underlying io.Writer.
pub fn Writer::flush(self : Writer) -> IOError? {
if not(self.wrote_header) {
self.err = self.write_header()
}
// if self.err != None {
// return self.err
// }
// self.err = self.compressor.flush()
return self.err
}
///|
// close closes the Writer, flushing any unwritten data to the underlying
// io.Writer, but does not close the underlying io.Writer.
pub impl @io.Closer for Writer with close(self) {
if not(self.wrote_header) {
self.err = self.write_header()
}
if self.err != None {
return self.err
}
self.err = self.compressor.close()
if self.err != None {
return self.err
}
let checksum = self.digest.sum32()
// ZLIB (RFC 1950) is big-endian, unlike GZIP (RFC 1952).
be_put_uint32(self.scratch, checksum)
let (_, err) = self.w.write(self.scratch)
self.err = err
self.err
}