Skip to main content

zstd_rs/encode/
dictionary.rs

1//! Dictionaries prepared for compression: content, match tables built once for
2//! a compression level ("attached" to every frame, never copied), entropy tables
3//! and repeat offsets.
4use super::entropy::DictCTables;
5use super::matchers::{hash, read64};
6use super::params::{adjust, lookup, CParams, Strategy};
7use crate::dict::Dictionary;
8use crate::error::{Error, Result};
9use alloc::vec;
10use alloc::vec::Vec;
11
12/// Read-only match state of a dictionary. Stored positions are `index + 1` (0 = empty).
13pub struct DictMatchState {
14    pub content: Vec<u8>,
15    pub hash: Vec<u32>,
16    pub hash2: Vec<u32>,
17    pub chain: Vec<u32>,
18    pub params: CParams,
19}
20
21impl DictMatchState {
22    fn build(content: &[u8], p: CParams) -> DictMatchState {
23        let n = content.len();
24        let mls = p.min_match.clamp(4, 7);
25        let mut hash_t = vec![0u32; 1 << p.hash_log];
26        let mut hash2_t = Vec::new();
27        let mut chain_t = Vec::new();
28        let last = n.saturating_sub(8);
29        match p.strategy {
30            Strategy::Fast => {
31                for i in 0..last {
32                    hash_t[hash(read64(content, i), mls, p.hash_log)] = i as u32 + 1;
33                }
34            }
35            Strategy::DFast => {
36                hash2_t = vec![0u32; 1 << p.chain_log];
37                for i in 0..last {
38                    let v = read64(content, i);
39                    hash_t[hash(v, 8, p.hash_log)] = i as u32 + 1;
40                    hash2_t[hash(v, mls, p.chain_log)] = i as u32 + 1;
41                }
42            }
43            _ => {
44                chain_t = vec![0u32; 1 << p.chain_log];
45                let mask = (1usize << p.chain_log) - 1;
46                let hmls = super::matchers::hc::chain_mls(&p);
47                for i in 0..last {
48                    let h = hash(read64(content, i), hmls, p.hash_log);
49                    chain_t[i & mask] = hash_t[h];
50                    hash_t[h] = i as u32 + 1;
51                }
52            }
53        }
54        DictMatchState { content: content.to_vec(), hash: hash_t, hash2: hash2_t, chain: chain_t, params: p }
55    }
56}
57
58/// A dictionary prepared for one compression level. Share it across frames and threads.
59pub struct EncoderDictionary {
60    pub(crate) id: u32,
61    pub(crate) reps: [u32; 3],
62    pub(crate) level: i32,
63    pub(crate) matcher: DictMatchState,
64    pub(crate) tables: Option<DictCTables>,
65}
66
67impl EncoderDictionary {
68    /// Prepares `dict` for compressing at `level` (the level must match the
69    /// compressor's; the reference library binds a CDict to a level the same way).
70    pub fn new(dict: &Dictionary, level: i32) -> Result<EncoderDictionary> {
71        if !(super::params::MIN_LEVEL..=super::params::MAX_LEVEL).contains(&level) || level == 0 {
72            return Err(Error::Parameter("compression level out of range"));
73        }
74        let size = dict.content.len() as u64;
75        let p = adjust(lookup(level, size + 500), 513, size);
76        let tables = match &dict.tables {
77            Some(t) => Some(DictCTables::new(t)?),
78            None => None,
79        };
80        Ok(EncoderDictionary { id: dict.id, reps: dict.reps, level, matcher: DictMatchState::build(&dict.content, p), tables })
81    }
82
83    /// The level this dictionary was prepared for.
84    pub fn level(&self) -> i32 {
85        self.level
86    }
87
88    /// Dictionary ID written to frame headers when enabled.
89    pub fn id(&self) -> u32 {
90        self.id
91    }
92}