zx0/
lib.rs

1#![warn(missing_docs)]
2
3//! A ZX0 compressor implementation for Rust.
4//!
5//! This crate provides a Rust implementation for Einar Saukas' excellent ZX0 compression
6//! algorithm.
7//!
8//! The algorithm provided in this crate is a more optimized variant of the original C-based
9//! implementation, and is therefore about 40% faster compared to the original. Additionally, the
10//! Rust implementation also offers thread-safety, meaning that files can now be compressed in
11//! parallel. Finally, this implementation is also free of memory leaks.
12//!
13//! To guarantee correctness the crate offers a sub-crate containing a Rust wrapper of the original
14//! C code. This wrapper is used as a reference in the crate's test suite to ensure that its output
15//! is 100% equivalent to the original implementation.
16//!
17//! The compressor can be used in two ways:
18//!
19//! 1. By instantiating a [`Compressor`] instance, configuring it, and invoking its
20//!    [`compress`](Compressor::compress) method.
21//!
22//! 2. Using the top level [`compress`](compress()) shortcut function to compress with the default settings.
23//!
24//! Please refer to the documentation for the [`Compressor`] struct for more information on how to
25//! use this crate, or inspect the examples that are provided in the crate's source code.
26//!
27//! Additionally, there is a wealth of information provided in the readme file of Einar Saukas'
28//! original implementation.
29
30mod compress;
31mod compressor;
32mod optimize;
33
34const INITIAL_OFFSET: usize = 1;
35const MAX_OFFSET_ZX0: usize = 32640;
36const MAX_OFFSET_ZX7: usize = 2176;
37
38pub use compressor::{
39    CompressionResult,
40    Compressor
41};
42
43/// Compress the input slice to an output vector.
44///
45/// This is a shortcut for:
46///
47/// ```text
48/// Compressor::new().compress(input).output
49/// ```
50///
51/// For a more customized experience please see the [`Compressor`] struct.
52pub fn compress(input: &[u8]) -> Vec<u8> {
53    Compressor::new().compress(input).output
54}
55
56#[cfg(test)]
57mod tests {
58    use super::Compressor;
59
60    #[test]
61    fn defaults() {
62        let input = std::fs::read("src/lib.rs").unwrap();
63
64        let reference = reference::Compressor::new().compress(&input);
65        let result = Compressor::new().compress(&input);
66
67        assert_eq!(result.output, reference.output);
68        assert_eq!(result.delta, reference.delta);
69    }
70
71    #[test]
72    fn defaults_with_prefix() {
73        let input = std::fs::read("src/lib.rs").unwrap();
74
75        // This may take a minute on a debug build
76        for skip in (0..input.len()).step_by(512) {
77            let reference = reference::Compressor::new().skip(skip).compress(&input);
78            unsafe { reference::reset(); }
79
80            let result = Compressor::new().skip(skip).compress(&input);
81
82            assert_eq!(result.output, reference.output);
83            assert_eq!(result.delta, reference.delta);
84        }
85    }
86
87    #[test]
88    fn backwards_mode() {
89        let input = std::fs::read("src/lib.rs").unwrap();
90
91        let reference = reference::Compressor::new().backwards_mode(true).compress(&input);
92        unsafe { reference::reset(); }
93
94        let result = Compressor::new().backwards_mode(true).compress(&input);
95
96        assert_eq!(result.output, reference.output);
97        assert_eq!(result.delta, reference.delta);
98    }
99
100    #[test]
101    fn backwards_mode_with_suffix() {
102        let input = std::fs::read("src/lib.rs").unwrap();
103
104        // This may take a minute on a debug build
105        for skip in (0..input.len()).step_by(512) {
106            let reference = reference::Compressor::new().backwards_mode(true).skip(skip).compress(&input);
107            unsafe { reference::reset(); }
108
109            let result = Compressor::new().backwards_mode(true).skip(skip).compress(&input);
110
111            assert_eq!(result.output, reference.output);
112            assert_eq!(result.delta, reference.delta);
113        }
114    }
115
116    #[test]
117    fn quick_mode() {
118        let input = std::fs::read("src/lib.rs").unwrap();
119
120        let reference = reference::Compressor::new().quick_mode(true).compress(&input);
121        unsafe { reference::reset(); }
122
123        let result = Compressor::new().quick_mode(true).compress(&input);
124
125        assert_eq!(result.output, reference.output);
126        assert_eq!(result.delta, reference.delta);
127    }
128
129    #[test]
130    fn classic_mode() {
131        let input = std::fs::read("src/lib.rs").unwrap();
132
133        let reference = reference::Compressor::new().classic_mode(true).compress(&input);
134        unsafe { reference::reset(); }
135
136        let result = Compressor::new().classic_mode(true).compress(&input);
137
138        assert_eq!(result.output, reference.output);
139        assert_eq!(result.delta, reference.delta);
140    }
141
142    #[test]
143    fn progress_callback() {
144        let input = std::fs::read("src/lib.rs").unwrap();
145
146        let called = std::cell::RefCell::new(false);
147
148        Compressor::new().progress_callback(|progress| {
149            *called.borrow_mut() = true;
150            assert!(progress >= 0.0);
151            assert!(progress <= 1.0);
152        }).compress(&input);
153
154        assert!(*called.borrow());
155    }
156}