Skip to main content

lz4rip_encode/
compressor.rs

1//! Owning block compressors (alloc).
2#![forbid(unsafe_code)]
3
4use core::fmt;
5
6use alloc::vec::Vec;
7
8use crate::compress::{
9    CompressorRefN, DEFAULT_DICT_ENTRIES, DEFAULT_NODICT_ENTRIES, HashTableU32U16,
10    compress_dict_tables, get_maximum_output_size, init_dict,
11};
12use lz4rip_core::CompressError;
13use lz4rip_core::{MINMATCH, WINDOW_SIZE};
14
15/// A reusable no-dict block compressor (owning) with `N` hash-table entries.
16///
17/// [`Compressor`] is the standard-sized alias (8 KB table). Use this generic
18/// form to pick a smaller table, e.g. `CompressorN::<512>::new()` for a 2 KB
19/// table. `N` must be a power of two (checked at compile time).
20///
21/// This is the ergonomic owning API for use with `alloc`. For a no-alloc
22/// variant, see [`CompressorRef`](crate::CompressorRef). For a dictionary-seeded
23/// compressor, see [`DictCompressor`].
24///
25/// For one-shot compression, use [`compress`](crate::compress) or
26/// [`compress_into`](crate::compress_into) instead.
27///
28/// # Example
29/// ```
30/// use lz4rip_encode::{Compressor, get_maximum_output_size};
31///
32/// let mut comp = Compressor::new();
33/// let input = b"hello world, hello world, hello!";
34/// let mut output = vec![0u8; get_maximum_output_size(input.len())];
35/// let compressed_len = comp.compress_into(input, &mut output).unwrap();
36/// ```
37#[derive(Debug, Default)]
38pub struct CompressorN<const N: usize = DEFAULT_NODICT_ENTRIES> {
39    inner: CompressorRefN<N>,
40}
41
42/// A reusable no-dict block compressor (owning) with the standard 8 KB table.
43pub type Compressor = CompressorN<DEFAULT_NODICT_ENTRIES>;
44
45impl<const N: usize> CompressorN<N> {
46    /// Create a new compressor without a dictionary.
47    #[must_use]
48    pub fn new() -> Self {
49        CompressorN {
50            inner: CompressorRefN::<N>::new(),
51        }
52    }
53
54    /// Compress `input` into `output`, returning the number of compressed bytes.
55    ///
56    /// `output` must be at least [`get_maximum_output_size`](crate::get_maximum_output_size)`(input.len())` bytes.
57    pub fn compress_into(
58        &mut self,
59        input: &[u8],
60        output: &mut [u8],
61    ) -> Result<usize, CompressError> {
62        self.inner.compress_into(input, output)
63    }
64
65    /// Compress `input` into a new `Vec<u8>`.
66    pub fn compress(&mut self, input: &[u8]) -> Vec<u8> {
67        self.inner.compress(input)
68    }
69}
70
71/// A reusable dict block compressor (owning) with `N` entries per table.
72///
73/// [`DictCompressor`] is the standard-sized alias (two 8 KB tables). Use this
74/// generic form to pick smaller tables, e.g. `DictCompressorN::<1024>::new(dict)`
75/// for two 2 KB tables. `N` must be a power of two (checked at compile time).
76///
77/// This is the ergonomic owning dict API for use with `alloc`. For a no-alloc
78/// variant that borrows the dictionary, see
79/// [`DictCompressorRef`](crate::DictCompressorRef).
80///
81/// Unlike the previous self-referential design, this owns its hash tables and
82/// dictionary as sibling fields and needs no `unsafe`.
83///
84/// # Example
85/// ```
86/// use lz4rip_encode::{DictCompressor, get_maximum_output_size};
87///
88/// let dict = b"the quick brown fox";
89/// let mut comp = DictCompressor::new(dict);
90/// let input = b"the quick brown fox jumps";
91/// let mut output = vec![0u8; get_maximum_output_size(input.len())];
92/// let compressed_len = comp.compress_into(input, &mut output).unwrap();
93/// ```
94pub struct DictCompressorN<const N: usize = DEFAULT_DICT_ENTRIES> {
95    /// Trimmed dictionary bytes (empty when the dictionary was shorter than
96    /// [`MINMATCH`]).
97    dict: Vec<u8>,
98    table: HashTableU32U16<N>,
99    pristine: HashTableU32U16<N>,
100}
101
102/// A reusable dict block compressor (owning) with the standard 8 KB tables.
103pub type DictCompressor = DictCompressorN<DEFAULT_DICT_ENTRIES>;
104
105impl<const N: usize> fmt::Debug for DictCompressorN<N> {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        f.debug_struct("DictCompressor")
108            .field("dict_len", &self.dict.len())
109            .finish()
110    }
111}
112
113impl<const N: usize> DictCompressorN<N> {
114    /// Create a new compressor seeded with an external dictionary.
115    ///
116    /// The dictionary is cloned into owned storage. If `dict` is longer than the
117    /// LZ4 window it is trimmed to the last [`WINDOW_SIZE`] bytes. A dictionary
118    /// shorter than 4 bytes is ignored (no dict matches); use [`Compressor`] for
119    /// that case.
120    #[must_use]
121    pub fn new(dict: &[u8]) -> Self {
122        let trimmed = if dict.len() < MINMATCH {
123            b"".as_slice()
124        } else if dict.len() > WINDOW_SIZE {
125            &dict[dict.len() - WINDOW_SIZE..]
126        } else {
127            dict
128        };
129        let mut pristine = HashTableU32U16::<N>::new();
130        let mut dict_ref = trimmed;
131        init_dict(&mut pristine, &mut dict_ref);
132        DictCompressorN {
133            dict: trimmed.to_vec(),
134            table: HashTableU32U16::<N>::new(),
135            pristine,
136        }
137    }
138
139    /// Compress `input` into `output`, returning the number of compressed bytes.
140    ///
141    /// `output` must be at least [`get_maximum_output_size`](crate::get_maximum_output_size)`(input.len())` bytes.
142    pub fn compress_into(
143        &mut self,
144        input: &[u8],
145        output: &mut [u8],
146    ) -> Result<usize, CompressError> {
147        // Split the borrow so the dict and tables can be passed separately.
148        let DictCompressorN {
149            dict,
150            table,
151            pristine,
152        } = self;
153        compress_dict_tables(table, pristine, dict, input, output)
154    }
155
156    /// Compress `input` into a new `Vec<u8>`.
157    pub fn compress(&mut self, input: &[u8]) -> Vec<u8> {
158        let max = get_maximum_output_size(input.len());
159        let mut out = alloc::vec![0u8; max];
160        let n = self.compress_into(input, &mut out).unwrap();
161        out.truncate(n);
162        out
163    }
164}