#![forbid(unsafe_code)] use crate::{ deflate::{ fill_window, flush_pending, BlockState, BlockType, DeflateStream, State, StaticTreeDesc, MIN_LOOKAHEAD, STD_MAX_MATCH, STD_MIN_MATCH, WANT_MIN_MATCH, }, DeflateFlush, }; pub fn deflate_quick(stream: &mut DeflateStream, flush: DeflateFlush) -> BlockState { let mut state = &mut stream.state; macro_rules! quick_end_block { ($last:expr) => { if state.block_open > 0 { state .bit_writer .emit_end_block_and_align(&StaticTreeDesc::L.static_tree, $last); state.block_open = 0; state.block_start = state.strstart as isize; flush_pending(stream); #[allow(unused_assignments)] { state = &mut stream.state; } if stream.avail_out == 0 { return match $last { true => BlockState::FinishStarted, false => BlockState::NeedMore, }; } } }; } macro_rules! quick_start_block { ($last:expr) => { state.bit_writer.emit_tree(BlockType::StaticTrees, $last); state.block_open = 1 + $last as u8; state.block_start = state.strstart as isize; }; } let last = matches!(flush, DeflateFlush::Finish); if last && state.block_open != 2 { /* Emit end of previous block */ quick_end_block!(false); /* Emit start of last block */ quick_start_block!(last); } else if state.block_open == 0 && state.lookahead > 0 { /* Start new block only when we have lookahead data, so that if no input data is given an empty block will not be written */ quick_start_block!(last); } loop { if state.bit_writer.pending.pending + State::BIT_BUF_SIZE.div_ceil(8) as usize >= state.pending_buf_size() { flush_pending(stream); state = &mut stream.state; if stream.avail_out == 0 { return if last && stream.avail_in == 0 && state.bit_writer.bits_used == 0 && state.block_open == 0 { BlockState::FinishStarted } else { BlockState::NeedMore }; } } if state.lookahead < MIN_LOOKAHEAD { fill_window(stream); state = &mut stream.state; if state.lookahead < MIN_LOOKAHEAD && matches!(flush, DeflateFlush::NoFlush) { return BlockState::NeedMore; } if state.lookahead == 0 { break; } if state.block_open == 0 { // Start new block when we have lookahead data, // so that if no input data is given an empty block will not be written quick_start_block!(last); } } if state.lookahead >= WANT_MIN_MATCH { let hash_head = state.quick_insert_string(state.strstart); let dist = state.strstart as isize - hash_head as isize; if dist <= state.max_dist() as isize && dist > 0 { let str_start = &state.window.filled()[state.strstart..]; let match_start = &state.window.filled()[hash_head as usize..]; macro_rules! first_two_bytes { ($slice:expr, $offset:expr) => { u16::from_le_bytes($slice[$offset..$offset + 2].try_into().unwrap()) }; } if first_two_bytes!(str_start, 0) == first_two_bytes!(match_start, 0) { let mut match_len = crate::deflate::compare256::compare256_slice( &str_start[2..], &match_start[2..], ) + 2; if match_len >= WANT_MIN_MATCH { match_len = Ord::min(match_len, state.lookahead); match_len = Ord::min(match_len, STD_MAX_MATCH); // TODO do this with a debug_assert? // check_match(s, state.strstart, hash_head, match_len); // The `dist` value is a distance within the window, // and MAX_WBITS == 15 (32k), hence a u16 can always represent this value. let dist = u16::try_from(dist).unwrap(); state .bit_writer .emit_dist_static((match_len - STD_MIN_MATCH) as u8, dist); state.lookahead -= match_len; state.strstart += match_len; continue; } } } } let lc = state.window.filled()[state.strstart]; state.bit_writer.emit_lit(StaticTreeDesc::L.static_tree, lc); state.strstart += 1; state.lookahead -= 1; } state.insert = Ord::min(state.strstart, STD_MIN_MATCH - 1); quick_end_block!(last); if last { BlockState::FinishDone } else { BlockState::BlockDone } }