zx0/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#![warn(missing_docs)]

//! A ZX0 compressor implementation for Rust.
//!
//! This crate provides a Rust implementation for Einar Saukas' excellent ZX0 compression
//! algorithm.
//!
//! The algorithm provided in this crate is a more optimized variant of the original C-based
//! implementation, and is therefore about 40% faster compared to the original. Additionally, the
//! Rust implementation also offers thread-safety, meaning that files can now be compressed in
//! parallel. Finally, this implementation is also free of memory leaks.
//!
//! To guarantee correctness the crate offers a sub-crate containing a Rust wrapper of the original
//! C code. This wrapper is used as a reference in the crate's test suite to ensure that its output
//! is 100% equivalent to the original implementation.
//!
//! The compressor can be used in two ways:
//!
//! 1. By instantiating a [`Compressor`] instance, configuring it, and invoking its
//!    [`compress`](Compressor::compress) method.
//!
//! 2. Using the top level [`compress`](compress()) shortcut function to compress with the default settings.
//!
//! Please refer to the documentation for the [`Compressor`] struct for more information on how to
//! use this crate, or inspect the examples that are provided in the crate's source code.
//!
//! Additionally, there is a wealth of information provided in the readme file of Einar Saukas'
//! original implementation.

mod compress;
mod compressor;
mod optimize;

const INITIAL_OFFSET: usize = 1;
const MAX_OFFSET_ZX0: usize = 32640;
const MAX_OFFSET_ZX7: usize = 2176;

pub use compressor::{
    CompressionResult,
    Compressor
};

/// Compress the input slice to an output vector.
///
/// This is a shortcut for:
///
/// ```text
/// Compressor::new().compress(input).output
/// ```
///
/// For a more customized experience please see the [`Compressor`] struct.
pub fn compress(input: &[u8]) -> Vec<u8> {
    Compressor::new().compress(input).output
}

#[cfg(test)]
mod tests {
    use super::Compressor;

    #[test]
    fn defaults() {
        let input = std::fs::read("src/lib.rs").unwrap();

        let reference = reference::Compressor::new().compress(&input);
        let result = Compressor::new().compress(&input);

        assert_eq!(result.output, reference.output);
        assert_eq!(result.delta, reference.delta);
    }

    #[test]
    fn defaults_with_prefix() {
        let input = std::fs::read("src/lib.rs").unwrap();

        // This may take a minute on a debug build
        for skip in (0..input.len()).step_by(512) {
            let reference = reference::Compressor::new().skip(skip).compress(&input);
            unsafe { reference::reset(); }

            let result = Compressor::new().skip(skip).compress(&input);

            assert_eq!(result.output, reference.output);
            assert_eq!(result.delta, reference.delta);
        }
    }

    #[test]
    fn backwards_mode() {
        let input = std::fs::read("src/lib.rs").unwrap();

        let reference = reference::Compressor::new().backwards_mode(true).compress(&input);
        unsafe { reference::reset(); }

        let result = Compressor::new().backwards_mode(true).compress(&input);

        assert_eq!(result.output, reference.output);
        assert_eq!(result.delta, reference.delta);
    }

    #[test]
    fn backwards_mode_with_suffix() {
        let input = std::fs::read("src/lib.rs").unwrap();

        // This may take a minute on a debug build
        for skip in (0..input.len()).step_by(512) {
            let reference = reference::Compressor::new().backwards_mode(true).skip(skip).compress(&input);
            unsafe { reference::reset(); }

            let result = Compressor::new().backwards_mode(true).skip(skip).compress(&input);

            assert_eq!(result.output, reference.output);
            assert_eq!(result.delta, reference.delta);
        }
    }

    #[test]
    fn quick_mode() {
        let input = std::fs::read("src/lib.rs").unwrap();

        let reference = reference::Compressor::new().quick_mode(true).compress(&input);
        unsafe { reference::reset(); }

        let result = Compressor::new().quick_mode(true).compress(&input);

        assert_eq!(result.output, reference.output);
        assert_eq!(result.delta, reference.delta);
    }

    #[test]
    fn classic_mode() {
        let input = std::fs::read("src/lib.rs").unwrap();

        let reference = reference::Compressor::new().classic_mode(true).compress(&input);
        unsafe { reference::reset(); }

        let result = Compressor::new().classic_mode(true).compress(&input);

        assert_eq!(result.output, reference.output);
        assert_eq!(result.delta, reference.delta);
    }

    #[test]
    fn progress_callback() {
        let input = std::fs::read("src/lib.rs").unwrap();

        let called = std::cell::RefCell::new(false);

        Compressor::new().progress_callback(|progress| {
            *called.borrow_mut() = true;
            assert!(progress >= 0.0);
            assert!(progress <= 1.0);
        }).compress(&input);

        assert!(*called.borrow());
    }
}