use crate::compress::flate;
use crate::encoding::binary::{self, ByteOrder};
use crate::errors;
use crate::hash::{self, adler32};
use std::io::Write;
pub const NO_COMPRESSION: isize = flate::NO_COMPRESSION;
pub const BEST_SPEED: isize = flate::BEST_SPEED;
pub const BEST_COMPRESSION: isize = flate::BEST_COMPRESSION;
pub const DEFAULT_COMPRESSION: isize = flate::DEFAULT_COMPRESSION;
pub const HUFFMAN_ONLY: isize = flate::HUFFMAN_ONLY;
pub struct Writer<'a, Output: std::io::Write> {
level: isize,
dict: &'a [u8],
compressor: flate::Writer<'a, Output>,
digest: Box<dyn hash::Hash32>,
scratch: [u8; 4],
wrote_header: bool,
}
impl<'a, Output: std::io::Write> Writer<'a, Output> {
pub fn new(w: &'a mut Output) -> Self {
Writer::new_level_dict(w, DEFAULT_COMPRESSION, &[]).unwrap()
}
pub fn new_level(w: &'a mut Output, level: isize) -> Self {
Writer::new_level_dict(w, level, &[]).unwrap()
}
pub fn new_level_dict(
w: &'a mut Output,
level: isize,
dict: &'a [u8],
) -> std::io::Result<Self> {
if !(HUFFMAN_ONLY..=BEST_COMPRESSION).contains(&level) {
return Err(errors::new_stdio_other_error(format!(
"zlib: invalid compression level: {}",
level
)));
}
Ok(Writer {
level,
dict,
compressor: flate::Writer::new_dict(w, level, dict).unwrap(),
digest: Box::new(adler32::new()),
scratch: [0; 4],
wrote_header: false,
})
}
pub fn reset(&mut self, w: &'a mut Output) {
self.compressor.reset(w);
self.digest.reset();
self.scratch = [0; 4];
self.wrote_header = false;
}
fn write_header(&mut self) -> std::io::Result<()> {
if self.wrote_header {
return Ok(());
}
self.wrote_header = true;
self.scratch[0] = 0x78;
self.scratch[1] = match self.level {
-2 | 0 | 1 => 0 << 6,
2..=5 => 1 << 6,
6 | -1 => 2 << 6,
7..=9 => 3 << 6,
_ => {
panic!("unreachable");
}
};
if !self.dict.is_empty() {
self.scratch[1] |= 1 << 5;
}
self.scratch[1] +=
(31 - (((self.scratch[0] as u16) << 8) + (self.scratch[1] as u16)) % 31) as u8;
self.compressor.output().write_all(&self.scratch[0..2])?;
if !self.dict.is_empty() {
binary::BIG_ENDIAN.put_uint32(&mut self.scratch[..], adler32::checksum(self.dict));
self.compressor.output().write_all(&self.scratch[0..4])?;
}
Ok(())
}
pub fn write(&mut self, p: &[u8]) -> std::io::Result<usize> {
self.write_header()?;
if p.is_empty() {
return Ok(0);
}
let written = self.compressor.write(p)?;
if written != p.len() {
panic!(
"not all data were written: written {}, all {}",
written,
p.len()
);
}
let written = self.digest.write(p)?;
if written != p.len() {
panic!(
"not all data were written to digest: written {}, all {}",
written,
p.len()
);
}
Ok(written)
}
pub fn flush(&'a mut self) -> std::io::Result<()> {
self.write_header()?;
self.compressor.flush()?;
Ok(())
}
pub fn close(&mut self) -> std::io::Result<()> {
self.write_header()?;
self.compressor.close()?;
let checksum = self.digest.sum32();
binary::BIG_ENDIAN.put_uint32(&mut self.scratch[..], checksum);
self.compressor.output().write_all(&self.scratch[0..4])?;
Ok(())
}
}