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}