use crate::compressor::core::rfc9841::window::ResolvedWindow;
use crate::compressor::core::shared::constants::WINDOW_GAP;
use crate::compressor::core::shared::distance::DistanceParams;
use crate::compressor::core::shared::format::ContextMode;
use crate::compressor::{BrotliCompressError, CompressMode, CompressParams, QualityLevel};
const MAX_ZOPFLI_LEN_Q10: usize = 150;
const MAX_ZOPFLI_LEN_Q11: usize = 325;
const MAX_INPUT_BLOCK_BITS: usize = 24;
const MIN_INPUT_BLOCK_BITS: usize = 16;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum HqQuality {
Q10,
Q11,
}
impl TryFrom<QualityLevel> for HqQuality {
type Error = BrotliCompressError;
fn try_from(value: QualityLevel) -> Result<Self, Self::Error> {
match value {
QualityLevel::Q10 => Ok(Self::Q10),
QualityLevel::Q11 => Ok(Self::Q11),
other => Err(BrotliCompressError::UnsupportedQuality(usize::from(other))),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) struct HqParams {
#[cfg(feature = "experimental")]
pub(crate) stream_offset: usize,
#[cfg(feature = "experimental")]
pub(crate) dictionary_context_mode: ContextMode,
pub(crate) quality: HqQuality,
pub(crate) window: ResolvedWindow,
pub(crate) lgwin: usize,
pub(crate) lgblock: usize,
pub(crate) disable_literal_context_modeling: bool,
pub(crate) dist: DistanceParams,
}
impl HqParams {
pub(crate) const fn logical_position(&self, position: usize) -> usize {
#[cfg(feature = "experimental")]
{
position + self.stream_offset
}
#[cfg(not(feature = "experimental"))]
{
position
}
}
pub(crate) fn new(params: &CompressParams) -> Result<Self, BrotliCompressError> {
let quality = HqQuality::try_from(params.quality())?;
let window = ResolvedWindow::new(params);
let lgwin = window.encoder_bits();
let lgblock = compute_lgblock(params.lgblock().map(usize::from), lgwin);
Ok(Self {
#[cfg(feature = "experimental")]
stream_offset: params.stream_offset,
#[cfg(feature = "experimental")]
dictionary_context_mode: ContextMode::Utf8,
quality,
window,
lgwin,
lgblock,
disable_literal_context_modeling: !params.literal_context_modeling(),
dist: choose_distance_params(window.is_large(), params.mode(), params.distance_codes()),
})
}
pub(crate) const fn max_zopfli_len(&self) -> usize {
match self.quality {
HqQuality::Q10 => MAX_ZOPFLI_LEN_Q10,
HqQuality::Q11 => MAX_ZOPFLI_LEN_Q11,
}
}
pub(crate) const fn max_zopfli_candidates(&self) -> usize {
match self.quality {
HqQuality::Q10 => 1,
HqQuality::Q11 => 5,
}
}
pub(crate) const fn short_scan(&self) -> usize {
match self.quality {
HqQuality::Q10 => 16,
HqQuality::Q11 => 64,
}
}
pub(crate) const fn split_iterations(&self) -> usize {
match self.quality {
HqQuality::Q10 => 3,
HqQuality::Q11 => 10,
}
}
pub(crate) const fn input_block_size(&self) -> usize {
1usize << self.lgblock
}
pub(crate) const fn rb_bits(&self) -> usize {
1 + if self.lgwin > self.lgblock {
self.lgwin
} else {
self.lgblock
}
}
pub(crate) const fn max_metablock_size(&self) -> usize {
let bits = if self.rb_bits() < MAX_INPUT_BLOCK_BITS {
self.rb_bits()
} else {
MAX_INPUT_BLOCK_BITS
};
1usize << bits
}
pub(crate) const fn max_backward_limit(&self) -> usize {
(1usize << self.lgwin) - WINDOW_GAP
}
pub(crate) fn choose_context_mode(
&self,
data: &[u8],
pos: usize,
mask: usize,
length: usize,
) -> ContextMode {
if super::utf8::is_mostly_utf8(data, pos, mask, length) {
ContextMode::Utf8
} else {
ContextMode::Signed
}
}
}
const fn compute_lgblock(requested: Option<usize>, lgwin: usize) -> usize {
match requested {
None => {
if lgwin > 16 {
if lgwin < 18 { lgwin } else { 18 }
} else {
16
}
}
Some(lgblock) => {
if lgblock < MIN_INPUT_BLOCK_BITS {
MIN_INPUT_BLOCK_BITS
} else if lgblock > MAX_INPUT_BLOCK_BITS {
MAX_INPUT_BLOCK_BITS
} else {
lgblock
}
}
}
}
const fn choose_distance_params(
large_window: bool,
mode: CompressMode,
codes: crate::compressor::DistanceCodes,
) -> DistanceParams {
let mut postfix_bits;
let mut num_direct;
if matches!(mode, CompressMode::Font) {
postfix_bits = 1;
num_direct = 12;
} else {
postfix_bits = codes.postfix_bits();
num_direct = codes.direct_codes();
}
let ndirect_msb = (num_direct >> postfix_bits) & 0x0F;
if postfix_bits > crate::compressor::core::shared::distance::MAX_NPOSTFIX
|| num_direct > crate::compressor::core::shared::distance::MAX_NDIRECT
|| (ndirect_msb << postfix_bits) != num_direct
{
postfix_bits = 0;
num_direct = 0;
}
DistanceParams::for_window(large_window, postfix_bits, num_direct)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compressor::{BlockBits, DistanceCodes, WindowBits};
fn params(quality: QualityLevel, lgwin: u8) -> HqParams {
let lgwin = WindowBits::standard(lgwin).unwrap_or(WindowBits::DEFAULT);
HqParams::new(&CompressParams::new(quality, lgwin)).expect("supported quality")
}
#[test]
fn quality_routing_accepts_only_the_high_quality_range() {
assert_eq!(
HqQuality::try_from(QualityLevel::Q10).ok(),
Some(HqQuality::Q10)
);
assert_eq!(
HqQuality::try_from(QualityLevel::Q11).ok(),
Some(HqQuality::Q11)
);
assert!(matches!(
HqQuality::try_from(QualityLevel::Q9),
Err(BrotliCompressError::UnsupportedQuality(9))
));
assert!(matches!(
HqQuality::try_from(QualityLevel::Q0),
Err(BrotliCompressError::UnsupportedQuality(0))
));
}
#[test]
fn the_search_limits_match_the_reference() {
let q10 = params(QualityLevel::Q10, 22);
assert_eq!(q10.max_zopfli_len(), 150);
assert_eq!(q10.max_zopfli_candidates(), 1);
assert_eq!(q10.short_scan(), 16);
assert_eq!(q10.split_iterations(), 3);
let q11 = params(QualityLevel::Q11, 22);
assert_eq!(q11.max_zopfli_len(), 325);
assert_eq!(q11.max_zopfli_candidates(), 5);
assert_eq!(q11.short_scan(), 64);
assert_eq!(q11.split_iterations(), 10);
}
#[test]
fn the_default_block_grows_with_the_window() {
assert_eq!(compute_lgblock(None, 10), 16);
assert_eq!(compute_lgblock(None, 16), 16);
assert_eq!(compute_lgblock(None, 17), 17);
assert_eq!(compute_lgblock(None, 18), 18);
assert_eq!(compute_lgblock(None, 24), 18);
}
#[test]
fn an_explicit_block_size_is_clamped_into_range() {
assert_eq!(compute_lgblock(Some(10), 22), MIN_INPUT_BLOCK_BITS);
assert_eq!(compute_lgblock(Some(20), 22), 20);
assert_eq!(compute_lgblock(Some(30), 22), MAX_INPUT_BLOCK_BITS);
}
#[test]
fn derived_sizes_follow_the_window_and_block() {
let resolved = params(QualityLevel::Q11, 22);
assert_eq!(resolved.lgblock, 18);
assert_eq!(resolved.input_block_size(), 1 << 18);
assert_eq!(resolved.rb_bits(), 23);
assert_eq!(resolved.max_metablock_size(), 1 << 23);
assert_eq!(resolved.max_backward_limit(), (1 << 22) - 16);
}
#[test]
fn a_huge_ring_buffer_is_capped_at_the_input_block_limit() {
let public = CompressParams::new(QualityLevel::Q11, WindowBits::MAX)
.with_block_bits(Some(BlockBits::MAX));
let resolved = HqParams::new(&public).expect("supported quality");
assert_eq!(resolved.rb_bits(), 25);
assert_eq!(resolved.max_metablock_size(), 1 << MAX_INPUT_BLOCK_BITS);
}
#[test]
fn font_mode_asks_for_the_reference_distance_parameters() {
let font = choose_distance_params(false, CompressMode::Font, DistanceCodes::DEFAULT);
assert_eq!((font.postfix_bits, font.num_direct), (1, 12));
let generic = choose_distance_params(false, CompressMode::Generic, DistanceCodes::DEFAULT);
assert_eq!((generic.postfix_bits, generic.num_direct), (0, 0));
}
#[test]
fn an_unrepresentable_distance_layout_falls_back_to_zero() {
let bad =
choose_distance_params(false, CompressMode::Generic, DistanceCodes::from_raw(2, 6));
assert_eq!((bad.postfix_bits, bad.num_direct), (0, 0));
let good =
choose_distance_params(false, CompressMode::Generic, DistanceCodes::from_raw(2, 8));
assert_eq!((good.postfix_bits, good.num_direct), (2, 8));
}
#[test]
fn the_context_mode_follows_whether_the_block_looks_like_text() {
let resolved = params(QualityLevel::Q11, 22);
let text = b"The quick brown fox jumps over the lazy dog, repeatedly.";
assert_eq!(
resolved.choose_context_mode(text, 0, usize::MAX, text.len()),
ContextMode::Utf8
);
let binary: Vec<u8> = (0..256u32).map(|i| (i * 7 % 256) as u8).collect();
assert_eq!(
resolved.choose_context_mode(&binary, 0, usize::MAX, binary.len()),
ContextMode::Signed
);
}
}