zopfli/
lib.rs

1#![deny(trivial_casts, trivial_numeric_casts, missing_docs)]
2
3//! A reimplementation of the [Zopfli](https://github.com/google/zopfli) compression library in Rust.
4//!
5//! Zopfli is a state of the art DEFLATE compressor that heavily prioritizes compression over speed.
6//! It usually compresses much better than other DEFLATE compressors, generating standard DEFLATE
7//! streams that can be decompressed with any DEFLATE decompressor, at the cost of being
8//! significantly slower.
9//!
10//! # Features
11//!
12//! This crate exposes the following features. You can enable or disable them in your `Cargo.toml`
13//! as needed.
14//!
15//! - `gzip` (enabled by default): enables support for compression in the gzip format.
16//! - `zlib` (enabled by default): enables support for compression in the Zlib format.
17//! - `std` (enabled by default): enables linking against the Rust standard library. When not enabled,
18//!   the crate is built with the `#![no_std]` attribute and can be used in any environment where
19//!   [`alloc`](https://doc.rust-lang.org/alloc/) (i.e., a memory allocator) is available. In addition,
20//!   the crate exposes minimalist versions of the `std` I/O traits it needs to function, allowing users
21//!   to implement them.
22//! - `nightly`: enables code constructs and features specific to the nightly Rust toolchain. Currently,
23//!   this feature improves rustdoc generation and enables the namesake feature on `crc32fast`, but this
24//!   may change in the future. This feature also used to enable `simd-adler32`'s namesake feature, but
25//!   it no longer does as the latest `simd-adler32` release does not build with the latest nightlies
26//!   (as of 2024-05-18) when that feature is enabled.
27
28#![cfg_attr(not(feature = "std"), no_std)]
29#![cfg_attr(feature = "nightly", feature(doc_cfg))]
30
31// No-op log implementation for no-std targets
32#[cfg(not(feature = "std"))]
33macro_rules! debug {
34    ( $( $_:expr ),* ) => {};
35}
36#[cfg(not(feature = "std"))]
37macro_rules! trace {
38    ( $( $_:expr ),* ) => {};
39}
40#[cfg(not(feature = "std"))]
41macro_rules! log_enabled {
42    ( $( $_:expr ),* ) => {
43        false
44    };
45}
46
47#[cfg_attr(not(feature = "std"), macro_use)]
48extern crate alloc;
49
50pub use deflate::{BlockType, DeflateEncoder};
51#[cfg(feature = "gzip")]
52pub use gzip::GzipEncoder;
53#[cfg(all(test, feature = "std"))]
54use proptest::prelude::*;
55#[cfg(feature = "zlib")]
56pub use zlib::ZlibEncoder;
57
58mod blocksplitter;
59mod cache;
60mod deflate;
61#[cfg(feature = "gzip")]
62mod gzip;
63mod hash;
64#[cfg(any(doc, not(feature = "std")))]
65mod io;
66mod iter;
67mod katajainen;
68mod lz77;
69#[cfg(not(feature = "std"))]
70mod math;
71mod squeeze;
72mod symbols;
73mod tree;
74mod util;
75#[cfg(feature = "zlib")]
76mod zlib;
77
78use core::num::NonZeroU64;
79#[cfg(all(not(doc), feature = "std"))]
80use std::io::{Error, Write};
81
82#[cfg(any(doc, not(feature = "std")))]
83pub use io::{Error, ErrorKind, Write};
84
85/// Options for the Zopfli compression algorithm.
86#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
87#[cfg_attr(all(test, feature = "std"), derive(proptest_derive::Arbitrary))]
88pub struct Options {
89    /// Maximum amount of times to rerun forward and backward pass to optimize LZ77
90    /// compression cost.
91    /// Good values: 10, 15 for small files, 5 for files over several MB in size or
92    /// it will be too slow.
93    ///
94    /// Default value: 15.
95    #[cfg_attr(
96        all(test, feature = "std"),
97        proptest(
98            strategy = "(1..=10u64).prop_map(|iteration_count| NonZeroU64::new(iteration_count).unwrap())"
99        )
100    )]
101    pub iteration_count: NonZeroU64,
102    /// Stop after rerunning forward and backward pass this many times without finding
103    /// a smaller representation of the block.
104    ///
105    /// Default value: practically infinite (maximum `u64` value)
106    pub iterations_without_improvement: NonZeroU64,
107    /// Maximum amount of blocks to split into (0 for unlimited, but this can give
108    /// extreme results that hurt compression on some files).
109    ///
110    /// Default value: 15.
111    pub maximum_block_splits: u16,
112}
113
114impl Default for Options {
115    fn default() -> Self {
116        Self {
117            iteration_count: NonZeroU64::new(15).unwrap(),
118            iterations_without_improvement: NonZeroU64::new(u64::MAX).unwrap(),
119            maximum_block_splits: 15,
120        }
121    }
122}
123
124/// The output file format to use to store data compressed with Zopfli.
125#[derive(Debug, Copy, Clone)]
126#[cfg(feature = "std")]
127pub enum Format {
128    /// The gzip file format, as defined in
129    /// [RFC 1952](https://datatracker.ietf.org/doc/html/rfc1952).
130    ///
131    /// This file format can be easily decompressed with the gzip
132    /// program.
133    #[cfg(feature = "gzip")]
134    Gzip,
135    /// The zlib file format, as defined in
136    /// [RFC 1950](https://datatracker.ietf.org/doc/html/rfc1950).
137    ///
138    /// The zlib format has less header overhead than gzip, but it
139    /// stores less metadata.
140    #[cfg(feature = "zlib")]
141    Zlib,
142    /// The raw DEFLATE stream format, as defined in
143    /// [RFC 1951](https://datatracker.ietf.org/doc/html/rfc1951).
144    ///
145    /// Raw DEFLATE streams are not meant to be stored as-is because
146    /// they lack error detection and correction metadata. They
147    /// are usually embedded in other file formats, such as gzip
148    /// and zlib.
149    Deflate,
150}
151
152/// Compresses data from a source with the Zopfli algorithm, using the specified
153/// options, and writes the result to a sink in the defined output format.
154#[cfg(feature = "std")]
155pub fn compress<R: std::io::Read, W: Write>(
156    options: Options,
157    output_format: Format,
158    mut in_data: R,
159    out: W,
160) -> Result<(), Error> {
161    match output_format {
162        #[cfg(feature = "gzip")]
163        Format::Gzip => {
164            let mut gzip_encoder = GzipEncoder::new_buffered(options, BlockType::Dynamic, out)?;
165            std::io::copy(&mut in_data, &mut gzip_encoder)?;
166            gzip_encoder.into_inner()?.finish().map(|_| ())
167        }
168        #[cfg(feature = "zlib")]
169        Format::Zlib => {
170            let mut zlib_encoder = ZlibEncoder::new_buffered(options, BlockType::Dynamic, out)?;
171            std::io::copy(&mut in_data, &mut zlib_encoder)?;
172            zlib_encoder.into_inner()?.finish().map(|_| ())
173        }
174        Format::Deflate => {
175            let mut deflate_encoder =
176                DeflateEncoder::new_buffered(options, BlockType::Dynamic, out);
177            std::io::copy(&mut in_data, &mut deflate_encoder)?;
178            deflate_encoder.into_inner()?.finish().map(|_| ())
179        }
180    }
181}
182
183#[doc(hidden)]
184#[deprecated(
185    since = "0.8.2",
186    note = "Object pools are no longer used. This function is now a no-op and will be removed in version 0.9.0."
187)]
188#[cfg(feature = "std")] // TODO remove for 0.9.0
189pub fn prewarm_object_pools() {}
190
191#[cfg(all(test, feature = "std"))]
192mod test {
193    use std::io;
194
195    use miniz_oxide::inflate;
196    use proptest::proptest;
197
198    use super::*;
199
200    proptest! {
201        #[test]
202        fn deflating_is_reversible(
203            options: Options,
204            btype: BlockType,
205            data in prop::collection::vec(any::<u8>(), 0..64 * 1024)
206        ) {
207            let mut compressed_data = Vec::with_capacity(data.len());
208
209            let mut encoder = DeflateEncoder::new(options, btype, &mut compressed_data);
210            io::copy(&mut &*data, &mut encoder).unwrap();
211            encoder.finish().unwrap();
212
213            let decompressed_data = inflate::decompress_to_vec(&compressed_data).expect("Could not inflate compressed stream");
214            prop_assert_eq!(data, decompressed_data, "Decompressed data should match input data");
215        }
216    }
217}