-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
GzipEncoder
and ZlibEncoder
, make compress
use encoders
This provides a much more flexible and powerful API even in no-std environments, stepping up the gzip and Zlib formats as we previously did with raw DEFLATE via `DeflateEncoder`. The new encoder writers are just wrappers around `DeflateEncoder` that write header and trailer data before and after the DEFLATE stream, respectively. While at it, let's remove the `deflate` function in favor of composite factory methods that return a `DeflateEncoder` wrapped in a `BufWriter`.
- Loading branch information
1 parent
d5fcfcc
commit 90cfdd4
Showing
7 changed files
with
246 additions
and
169 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,103 @@ | ||
use std::io::Read; | ||
|
||
use crate::{ | ||
deflate::{deflate, BlockType}, | ||
util::HashingAndCountingRead, | ||
Error, Options, Write, | ||
}; | ||
|
||
static HEADER: &[u8] = &[ | ||
31, // ID1 | ||
139, // ID2 | ||
8, // CM | ||
0, // FLG | ||
0, // MTIME | ||
0, 0, 0, 2, // XFL, 2 indicates best compression. | ||
3, // OS follows Unix conventions. | ||
]; | ||
|
||
/// Compresses the data according to the gzip specification, RFC 1952. | ||
pub fn gzip_compress<R: Read, W: Write>( | ||
options: Options, | ||
in_data: R, | ||
mut out: W, | ||
) -> Result<(), Error> { | ||
let mut crc_hasher = crc32fast::Hasher::new(); | ||
let mut insize = 0; | ||
|
||
let in_data = HashingAndCountingRead::new(in_data, &mut crc_hasher, Some(&mut insize)); | ||
|
||
out.write_all(HEADER)?; | ||
|
||
deflate(options, BlockType::Dynamic, in_data, &mut out)?; | ||
|
||
out.write_all(&crc_hasher.finalize().to_le_bytes())?; | ||
|
||
out.write_all(&insize.to_le_bytes()) | ||
use crate::{BlockType, DeflateEncoder, Error, Options, Write}; | ||
|
||
/// A Gzip encoder powered by the Zopfli algorithm, that compresses data using | ||
/// a [`DeflateEncoder`]. Most users will find using [`compress`](crate::compress) | ||
/// easier and more performant. | ||
/// | ||
/// The caveats about short writes in [`DeflateEncoder`]s carry over to `GzipEncoder`s: | ||
/// for best performance and compression, it is best to avoid them. One way to ensure | ||
/// this is to use the [`new_buffered`](GzipEncoder::new_buffered) method. | ||
pub struct GzipEncoder<W: Write> { | ||
deflate_encoder: Option<DeflateEncoder<W>>, | ||
crc32_hasher: crc32fast::Hasher, | ||
input_size: u32, | ||
} | ||
|
||
impl<W: Write> GzipEncoder<W> { | ||
/// Creates a new Gzip encoder that will operate according to the | ||
/// specified options. | ||
pub fn new(options: Options, btype: BlockType, mut sink: W) -> Result<Self, Error> { | ||
static HEADER: &[u8] = &[ | ||
31, // ID1 | ||
139, // ID2 | ||
8, // CM | ||
0, // FLG | ||
0, // MTIME | ||
0, 0, 0, 2, // XFL, 2 indicates best compression. | ||
3, // OS follows Unix conventions. | ||
]; | ||
|
||
sink.write_all(HEADER)?; | ||
|
||
Ok(Self { | ||
deflate_encoder: Some(DeflateEncoder::new(options, btype, sink)), | ||
crc32_hasher: crc32fast::Hasher::new(), | ||
input_size: 0, | ||
}) | ||
} | ||
|
||
/// Creates a new Gzip encoder that operates according to the specified | ||
/// options and is wrapped with a buffer to guarantee that data is | ||
/// compressed in large chunks, which is necessary for decent performance | ||
/// and good compression ratio. | ||
#[cfg(feature = "std")] | ||
pub fn new_buffered( | ||
options: Options, | ||
btype: BlockType, | ||
sink: W, | ||
) -> Result<std::io::BufWriter<Self>, Error> { | ||
Ok(std::io::BufWriter::with_capacity( | ||
crate::util::ZOPFLI_MASTER_BLOCK_SIZE, | ||
Self::new(options, btype, sink)?, | ||
)) | ||
} | ||
|
||
/// Encodes any pending chunks of data and writes them to the sink, | ||
/// consuming the encoder and returning the wrapped sink. The sink | ||
/// will have received a complete Gzip stream when this method | ||
/// returns. | ||
/// | ||
/// The encoder is automatically [`finish`](Self::finish)ed when | ||
/// dropped, but explicitly finishing it with this method allows | ||
/// handling I/O errors. | ||
pub fn finish(mut self) -> Result<W, Error> { | ||
self._finish().map(|sink| sink.unwrap()) | ||
} | ||
|
||
fn _finish(&mut self) -> Result<Option<W>, Error> { | ||
if self.deflate_encoder.is_none() { | ||
return Ok(None); | ||
} | ||
|
||
let mut sink = self.deflate_encoder.take().unwrap().finish()?; | ||
|
||
sink.write_all(&self.crc32_hasher.clone().finalize().to_le_bytes())?; | ||
sink.write_all(&self.input_size.to_le_bytes())?; | ||
|
||
Ok(Some(sink)) | ||
} | ||
} | ||
|
||
impl<W: Write> Write for GzipEncoder<W> { | ||
fn write(&mut self, buf: &[u8]) -> Result<usize, Error> { | ||
self.deflate_encoder | ||
.as_mut() | ||
.unwrap() | ||
.write(buf) | ||
.map(|bytes_written| { | ||
self.crc32_hasher.update(&buf[..bytes_written]); | ||
self.input_size = self.input_size.wrapping_add(bytes_written as u32); | ||
bytes_written | ||
}) | ||
} | ||
|
||
fn flush(&mut self) -> Result<(), Error> { | ||
self.deflate_encoder.as_mut().unwrap().flush() | ||
} | ||
} | ||
|
||
impl<W: Write> Drop for GzipEncoder<W> { | ||
fn drop(&mut self) { | ||
self._finish().ok(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.