use alloc::vec::Vec;
use core::cmp;
use crate::{
cache::Cache,
hash::{Which, ZopfliHash},
symbols::{get_dist_symbol, get_length_symbol},
util::{
ZOPFLI_MAX_CHAIN_HITS, ZOPFLI_MAX_MATCH, ZOPFLI_MIN_MATCH, ZOPFLI_NUM_D, ZOPFLI_NUM_LL,
ZOPFLI_WINDOW_MASK, ZOPFLI_WINDOW_SIZE,
},
};
#[derive(Clone, Copy)]
pub enum LitLen {
Literal(u16),
LengthDist(u16, u16),
}
impl LitLen {
pub const fn size(&self) -> usize {
match *self {
Self::Literal(_) => 1,
Self::LengthDist(len, _) => len as usize,
}
}
}
#[derive(Clone, Default)]
pub struct Lz77Store {
pub litlens: Vec<LitLen>,
pub pos: Vec<u32>,
ll_symbol: Vec<u16>,
d_symbol: Vec<u16>,
ll_counts: Vec<u32>,
d_counts: Vec<u32>,
}
impl Lz77Store {
pub const fn new() -> Self {
Self {
litlens: vec![],
pos: vec![],
ll_symbol: vec![],
d_symbol: vec![],
ll_counts: vec![],
d_counts: vec![],
}
}
pub fn with_capacity(blocksize: usize) -> Self {
let cap = blocksize / 2;
let ll_counts_cap = (cap / ZOPFLI_NUM_LL + 1) * ZOPFLI_NUM_LL;
let d_counts_cap = (cap / ZOPFLI_NUM_D + 1) * ZOPFLI_NUM_D;
Self {
litlens: Vec::with_capacity(cap),
pos: Vec::with_capacity(cap),
ll_symbol: Vec::with_capacity(cap),
d_symbol: Vec::with_capacity(cap),
ll_counts: Vec::with_capacity(ll_counts_cap),
d_counts: Vec::with_capacity(d_counts_cap),
}
}
pub fn reset(&mut self) {
self.litlens.clear();
self.pos.clear();
self.ll_symbol.clear();
self.d_symbol.clear();
self.ll_counts.clear();
self.d_counts.clear();
}
pub fn size(&self) -> usize {
self.litlens.len()
}
pub fn append_store_item(&mut self, litlen: LitLen, pos: usize) {
let origsize = self.litlens.len();
let llstart = ZOPFLI_NUM_LL * (origsize / ZOPFLI_NUM_LL);
let dstart = ZOPFLI_NUM_D * (origsize / ZOPFLI_NUM_D);
if origsize.is_multiple_of(ZOPFLI_NUM_LL) {
if origsize == 0 {
self.ll_counts.resize(origsize + ZOPFLI_NUM_LL, 0);
} else {
self.ll_counts
.extend_from_within((origsize - ZOPFLI_NUM_LL)..origsize);
}
}
if origsize.is_multiple_of(ZOPFLI_NUM_D) {
if origsize == 0 {
self.d_counts.resize(ZOPFLI_NUM_D, 0);
} else {
self.d_counts
.extend_from_within((origsize - ZOPFLI_NUM_D)..origsize);
}
}
self.pos.push(pos as u32);
self.litlens.push(litlen);
match litlen {
LitLen::Literal(length) => {
self.ll_symbol.push(length);
self.d_symbol.push(0);
self.ll_counts[llstart + length as usize] += 1;
}
LitLen::LengthDist(length, dist) => {
let len_sym = get_length_symbol(length as usize);
let dist_sym = get_dist_symbol(dist);
self.ll_symbol.push(len_sym as u16);
self.d_symbol.push(dist_sym);
self.ll_counts[llstart + len_sym] += 1;
self.d_counts[dstart + dist_sym as usize] += 1;
}
}
}
pub fn lit_len_dist(&mut self, length: u16, dist: u16, pos: usize) {
let litlen = if dist == 0 {
LitLen::Literal(length)
} else {
LitLen::LengthDist(length, dist)
};
self.append_store_item(litlen, pos);
}
pub fn greedy<C: Cache>(&mut self, lmc: &mut C, in_data: &[u8], instart: usize, inend: usize) {
if instart == inend {
return;
}
let windowstart = instart.saturating_sub(ZOPFLI_WINDOW_SIZE);
let mut h = ZopfliHash::new();
let arr = &in_data[..inend];
h.warmup(arr, windowstart, inend);
for i in windowstart..instart {
h.update(arr, i);
}
let mut i = instart;
let mut leng;
let mut dist;
let mut lengthscore;
let mut prev_length = 0;
let mut prev_match = 0;
let mut prevlengthscore;
let mut match_available = false;
while i < inend {
h.update(arr, i);
let longest_match =
find_longest_match(lmc, &h, arr, i, inend, instart, ZOPFLI_MAX_MATCH, &mut None);
dist = longest_match.distance;
leng = longest_match.length;
lengthscore = get_length_score(i32::from(leng), i32::from(dist));
prevlengthscore = get_length_score(prev_length as i32, prev_match as i32);
if match_available {
match_available = false;
if lengthscore > prevlengthscore + 1 {
self.lit_len_dist(u16::from(arr[i - 1]), 0, i - 1);
if (lengthscore as usize) >= ZOPFLI_MIN_MATCH
&& (leng as usize) < ZOPFLI_MAX_MATCH
{
match_available = true;
prev_length = u32::from(leng);
prev_match = u32::from(dist);
i += 1;
continue;
}
} else {
leng = prev_length as u16;
dist = prev_match as u16;
verify_len_dist(arr, i - 1, dist, leng);
self.lit_len_dist(leng, dist, i - 1);
for _ in 2..leng {
debug_assert!(i < inend);
i += 1;
h.update(arr, i);
}
i += 1;
continue;
}
} else if (lengthscore as usize) >= ZOPFLI_MIN_MATCH
&& (leng as usize) < ZOPFLI_MAX_MATCH
{
match_available = true;
prev_length = u32::from(leng);
prev_match = u32::from(dist);
i += 1;
continue;
}
if (lengthscore as usize) >= ZOPFLI_MIN_MATCH {
verify_len_dist(arr, i, dist, leng);
self.lit_len_dist(leng, dist, i);
} else {
leng = 1;
self.lit_len_dist(u16::from(arr[i]), 0, i);
}
for _ in 1..leng {
debug_assert!(i < inend);
i += 1;
h.update(arr, i);
}
i += 1;
}
}
pub fn store_from_path(&mut self, in_data: &[u8], instart: usize, path: &[(u16, u16)]) {
let mut pos = instart;
for &(length, dist) in path.iter().rev() {
if length >= ZOPFLI_MIN_MATCH as u16 {
verify_len_dist(in_data, pos, dist, length);
self.lit_len_dist(length, dist, pos);
} else {
self.lit_len_dist(u16::from(in_data[pos]), 0, pos);
}
pos += length as usize;
}
}
fn get_histogram_at(&self, lpos: usize) -> ([usize; ZOPFLI_NUM_LL], [usize; ZOPFLI_NUM_D]) {
let mut ll = [0usize; ZOPFLI_NUM_LL];
let mut d = [0usize; ZOPFLI_NUM_D];
let llpos = ZOPFLI_NUM_LL * (lpos / ZOPFLI_NUM_LL);
let dpos = ZOPFLI_NUM_D * (lpos / ZOPFLI_NUM_D);
let ll_src = &self.ll_counts[llpos..llpos + ZOPFLI_NUM_LL];
for (dst, &src) in ll.iter_mut().zip(ll_src.iter()) {
*dst = src as usize;
}
let end = cmp::min(llpos + ZOPFLI_NUM_LL, self.size());
for i in (lpos + 1)..end {
ll[self.ll_symbol[i] as usize] -= 1;
}
let d_src = &self.d_counts[dpos..dpos + ZOPFLI_NUM_D];
for (dst, &src) in d.iter_mut().zip(d_src.iter()) {
*dst = src as usize;
}
let end = cmp::min(dpos + ZOPFLI_NUM_D, self.size());
for i in (lpos + 1)..end {
if let LitLen::LengthDist(_, _) = self.litlens[i] {
d[self.d_symbol[i] as usize] -= 1;
}
}
(ll, d)
}
pub fn get_histogram(
&self,
lstart: usize,
lend: usize,
) -> ([usize; ZOPFLI_NUM_LL], [usize; ZOPFLI_NUM_D]) {
if lstart + ZOPFLI_NUM_LL * 3 > lend {
let mut ll_counts = [0usize; ZOPFLI_NUM_LL];
let mut d_counts = [0usize; ZOPFLI_NUM_D];
for i in lstart..lend {
ll_counts[self.ll_symbol[i] as usize] += 1;
if let LitLen::LengthDist(_, _) = self.litlens[i] {
d_counts[self.d_symbol[i] as usize] += 1;
}
}
(ll_counts, d_counts)
} else {
let (ll, d) = self.get_histogram_at(lend - 1);
if lstart > 0 {
let (ll2, d2) = self.get_histogram_at(lstart - 1);
let mut result_ll = [0usize; ZOPFLI_NUM_LL];
for i in 0..ZOPFLI_NUM_LL {
result_ll[i] = ll[i] - ll2[i];
}
let mut result_d = [0usize; ZOPFLI_NUM_D];
for i in 0..ZOPFLI_NUM_D {
result_d[i] = d[i] - d2[i];
}
(result_ll, result_d)
} else {
(ll, d)
}
}
}
pub fn get_byte_range(&self, lstart: usize, lend: usize) -> usize {
if lstart == lend {
return 0;
}
let l = lend - 1;
self.pos[l] as usize + self.litlens[l].size() - self.pos[lstart] as usize
}
}
pub struct LongestMatch {
pub distance: u16,
pub length: u16,
pub from_cache: bool,
pub limit: usize,
}
impl LongestMatch {
pub const fn new(limit: usize) -> Self {
Self {
distance: 0,
length: 0,
from_cache: false,
limit,
}
}
}
fn get_match(scan_arr: &[u8], match_arr: &[u8]) -> usize {
let max_prefix_len = cmp::min(scan_arr.len(), match_arr.len()); let mut i = 0;
const CHUNK_SIZE: usize = core::mem::size_of::<u128>();
while i + CHUNK_SIZE < max_prefix_len && i + CHUNK_SIZE <= usize::MAX - CHUNK_SIZE {
let scan_chunk = u128::from_le_bytes(scan_arr[i..i + CHUNK_SIZE].try_into().unwrap());
let match_chunk = u128::from_le_bytes(match_arr[i..i + CHUNK_SIZE].try_into().unwrap());
let bit_diff_mask = scan_chunk ^ match_chunk;
if bit_diff_mask != 0 {
return i + bit_diff_mask.trailing_zeros() as usize / 8;
}
i += CHUNK_SIZE;
}
for j in i..max_prefix_len {
if scan_arr[j] != match_arr[j] {
return j; }
}
max_prefix_len
}
#[allow(clippy::too_many_arguments)]
pub fn find_longest_match<C: Cache>(
lmc: &mut C,
h: &ZopfliHash,
array: &[u8],
pos: usize,
size: usize,
blockstart: usize,
limit: usize,
sublen: &mut Option<&mut [u16]>,
) -> LongestMatch {
let mut longest_match = lmc.try_get(pos, limit, sublen, blockstart);
if longest_match.from_cache {
debug_assert!(pos + (longest_match.length as usize) <= size);
return longest_match;
}
let mut limit = longest_match.limit;
debug_assert!(limit <= ZOPFLI_MAX_MATCH);
debug_assert!(limit >= ZOPFLI_MIN_MATCH);
debug_assert!(pos < size);
if size - pos < ZOPFLI_MIN_MATCH {
longest_match.distance = 0;
longest_match.length = 0;
longest_match.from_cache = false;
longest_match.limit = 0;
return longest_match;
}
if pos + limit > size {
limit = size - pos;
}
let (bestdist, bestlength) = find_longest_match_loop(h, array, pos, size, limit, sublen);
lmc.store(pos, limit, sublen, bestdist, bestlength, blockstart);
debug_assert!(bestlength <= limit as u16);
debug_assert!(pos + bestlength as usize <= size);
longest_match.distance = bestdist;
longest_match.length = bestlength;
longest_match.from_cache = false;
longest_match.limit = limit;
longest_match
}
fn find_longest_match_loop(
h: &ZopfliHash,
array: &[u8],
pos: usize,
size: usize,
limit: usize,
sublen: &mut Option<&mut [u16]>,
) -> (u16, u16) {
let mut which_hash = Which::Hash1;
let hpos = pos & ZOPFLI_WINDOW_MASK;
let mut pp = hpos;
let mut p = h.prev_at(pp, which_hash);
let mut dist = if p < pp {
pp - p
} else {
ZOPFLI_WINDOW_SIZE - p + pp
};
let mut bestlength = 1;
let mut bestdist = 0;
let mut chain_counter = ZOPFLI_MAX_CHAIN_HITS;
let arrayend = pos + limit;
let mut scan_offset;
let mut match_offset;
while dist < ZOPFLI_WINDOW_SIZE && chain_counter > 0 {
let mut currentlength = 0;
debug_assert!(p < ZOPFLI_WINDOW_SIZE);
debug_assert_eq!(p, h.prev_at(pp, which_hash));
debug_assert_eq!(h.hash_val_at(p, which_hash), i32::from(h.val(which_hash)));
if dist > 0 {
debug_assert!(pos < size);
debug_assert!(dist <= pos);
scan_offset = pos;
match_offset = pos - dist;
if pos + bestlength >= size
|| array[scan_offset + bestlength] == array[match_offset + bestlength]
{
let same0 = h.same[pos & ZOPFLI_WINDOW_MASK];
if same0 > 2 && array[scan_offset] == array[match_offset] {
let same1 = h.same[(pos - dist) & ZOPFLI_WINDOW_MASK];
let same = cmp::min(cmp::min(same0, same1), limit as u16) as usize;
scan_offset += same;
match_offset += same;
}
scan_offset = get_match(
&array[scan_offset..arrayend],
&array[match_offset..arrayend],
) + scan_offset;
currentlength = scan_offset - pos;
}
if currentlength > bestlength {
if let Some(ref mut subl) = *sublen {
for sublength in subl.iter_mut().take(currentlength + 1).skip(bestlength + 1) {
*sublength = dist as u16;
}
}
bestdist = dist;
bestlength = currentlength;
if currentlength >= limit {
break;
}
}
}
if which_hash == Which::Hash1
&& bestlength >= h.same[hpos] as usize
&& i32::from(h.val(Which::Hash2)) == h.hash_val_at(p, Which::Hash2)
{
which_hash = Which::Hash2;
}
pp = p;
p = h.prev_at(p, which_hash);
if p == pp {
break;
}
dist += if p < pp {
pp - p
} else {
ZOPFLI_WINDOW_SIZE - p + pp
};
chain_counter -= 1;
}
(bestdist as u16, bestlength as u16)
}
const fn get_length_score(length: i32, distance: i32) -> i32 {
if distance > 1024 { length - 1 } else { length }
}
#[cfg(debug_assertions)]
fn verify_len_dist(data: &[u8], pos: usize, dist: u16, length: u16) {
for i in 0..length {
let d1 = data[pos - (dist as usize) + (i as usize)];
let d2 = data[pos + (i as usize)];
if d1 != d2 {
assert_eq!(d1, d2);
break;
}
}
}
#[cfg(not(debug_assertions))]
fn verify_len_dist(_data: &[u8], _pos: usize, _dist: u16, _length: u16) {}