Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1158 lines
38 KiB
1158 lines
38 KiB
package brotli |
|
|
|
import ( |
|
"io" |
|
"math" |
|
) |
|
|
|
/* Copyright 2016 Google Inc. All Rights Reserved. |
|
|
|
Distributed under MIT license. |
|
See file LICENSE for detail or copy at https://opensource.org/licenses/MIT |
|
*/ |
|
|
|
/** Minimal value for ::BROTLI_PARAM_LGWIN parameter. */ |
|
const minWindowBits = 10 |
|
|
|
/** |
|
* Maximal value for ::BROTLI_PARAM_LGWIN parameter. |
|
* |
|
* @note equal to @c BROTLI_MAX_DISTANCE_BITS constant. |
|
*/ |
|
const maxWindowBits = 24 |
|
|
|
/** |
|
* Maximal value for ::BROTLI_PARAM_LGWIN parameter |
|
* in "Large Window Brotli" (32-bit). |
|
*/ |
|
const largeMaxWindowBits = 30 |
|
|
|
/** Minimal value for ::BROTLI_PARAM_LGBLOCK parameter. */ |
|
const minInputBlockBits = 16 |
|
|
|
/** Maximal value for ::BROTLI_PARAM_LGBLOCK parameter. */ |
|
const maxInputBlockBits = 24 |
|
|
|
/** Minimal value for ::BROTLI_PARAM_QUALITY parameter. */ |
|
const minQuality = 0 |
|
|
|
/** Maximal value for ::BROTLI_PARAM_QUALITY parameter. */ |
|
const maxQuality = 11 |
|
|
|
/** Options for ::BROTLI_PARAM_MODE parameter. */ |
|
const ( |
|
modeGeneric = 0 |
|
modeText = 1 |
|
modeFont = 2 |
|
) |
|
|
|
/** Default value for ::BROTLI_PARAM_QUALITY parameter. */ |
|
const defaultQuality = 11 |
|
|
|
/** Default value for ::BROTLI_PARAM_LGWIN parameter. */ |
|
const defaultWindow = 22 |
|
|
|
/** Default value for ::BROTLI_PARAM_MODE parameter. */ |
|
const defaultMode = modeGeneric |
|
|
|
/** Operations that can be performed by streaming encoder. */ |
|
const ( |
|
operationProcess = 0 |
|
operationFlush = 1 |
|
operationFinish = 2 |
|
operationEmitMetadata = 3 |
|
) |
|
|
|
const ( |
|
streamProcessing = 0 |
|
streamFlushRequested = 1 |
|
streamFinished = 2 |
|
streamMetadataHead = 3 |
|
streamMetadataBody = 4 |
|
) |
|
|
|
type Writer struct { |
|
dst io.Writer |
|
options WriterOptions |
|
err error |
|
|
|
params encoderParams |
|
hasher_ hasherHandle |
|
input_pos_ uint64 |
|
ringbuffer_ ringBuffer |
|
commands []command |
|
num_literals_ uint |
|
last_insert_len_ uint |
|
last_flush_pos_ uint64 |
|
last_processed_pos_ uint64 |
|
dist_cache_ [numDistanceShortCodes]int |
|
saved_dist_cache_ [4]int |
|
prev_byte_ byte |
|
prev_byte2_ byte |
|
bw bitWriter |
|
small_table_ [1 << 10]int |
|
large_table_ []int |
|
large_table_size_ uint |
|
cmd_depths_ [128]byte |
|
cmd_bits_ [128]uint16 |
|
cmd_code_ [512]byte |
|
cmd_code_numbits_ uint |
|
command_buf_ []uint32 |
|
literal_buf_ []byte |
|
tiny_buf_ struct { |
|
u64 [2]uint64 |
|
u8 [16]byte |
|
} |
|
remaining_metadata_bytes_ uint32 |
|
stream_state_ int |
|
is_last_block_emitted_ bool |
|
is_initialized_ bool |
|
} |
|
|
|
func inputBlockSize(s *Writer) uint { |
|
return uint(1) << uint(s.params.lgblock) |
|
} |
|
|
|
func unprocessedInputSize(s *Writer) uint64 { |
|
return s.input_pos_ - s.last_processed_pos_ |
|
} |
|
|
|
func remainingInputBlockSize(s *Writer) uint { |
|
var delta uint64 = unprocessedInputSize(s) |
|
var block_size uint = inputBlockSize(s) |
|
if delta >= uint64(block_size) { |
|
return 0 |
|
} |
|
return block_size - uint(delta) |
|
} |
|
|
|
/* Wraps 64-bit input position to 32-bit ring-buffer position preserving |
|
"not-a-first-lap" feature. */ |
|
func wrapPosition(position uint64) uint32 { |
|
var result uint32 = uint32(position) |
|
var gb uint64 = position >> 30 |
|
if gb > 2 { |
|
/* Wrap every 2GiB; The first 3GB are continuous. */ |
|
result = result&((1<<30)-1) | (uint32((gb-1)&1)+1)<<30 |
|
} |
|
|
|
return result |
|
} |
|
|
|
func hashTableSize(max_table_size uint, input_size uint) uint { |
|
var htsize uint = 256 |
|
for htsize < max_table_size && htsize < input_size { |
|
htsize <<= 1 |
|
} |
|
|
|
return htsize |
|
} |
|
|
|
func getHashTable(s *Writer, quality int, input_size uint, table_size *uint) []int { |
|
var max_table_size uint = maxHashTableSize(quality) |
|
var htsize uint = hashTableSize(max_table_size, input_size) |
|
/* Use smaller hash table when input.size() is smaller, since we |
|
fill the table, incurring O(hash table size) overhead for |
|
compression, and if the input is short, we won't need that |
|
many hash table entries anyway. */ |
|
|
|
var table []int |
|
assert(max_table_size >= 256) |
|
if quality == fastOnePassCompressionQuality { |
|
/* Only odd shifts are supported by fast-one-pass. */ |
|
if htsize&0xAAAAA == 0 { |
|
htsize <<= 1 |
|
} |
|
} |
|
|
|
if htsize <= uint(len(s.small_table_)) { |
|
table = s.small_table_[:] |
|
} else { |
|
if htsize > s.large_table_size_ { |
|
s.large_table_size_ = htsize |
|
s.large_table_ = nil |
|
s.large_table_ = make([]int, htsize) |
|
} |
|
|
|
table = s.large_table_ |
|
} |
|
|
|
*table_size = htsize |
|
for i := 0; i < int(htsize); i++ { |
|
table[i] = 0 |
|
} |
|
return table |
|
} |
|
|
|
func encodeWindowBits(lgwin int, large_window bool, bw *bitWriter) { |
|
if large_window { |
|
bw.writeBits(14, uint64((lgwin&0x3F)<<8|0x11)) |
|
} else { |
|
if lgwin == 16 { |
|
bw.writeBits(1, 0) |
|
} else if lgwin == 17 { |
|
bw.writeBits(7, 1) |
|
} else if lgwin > 17 { |
|
bw.writeBits(4, uint64((lgwin-17)<<1|0x01)) |
|
} else { |
|
bw.writeBits(7, uint64((lgwin-8)<<4|0x01)) |
|
} |
|
} |
|
} |
|
|
|
/* Decide about the context map based on the ability of the prediction |
|
ability of the previous byte UTF8-prefix on the next byte. The |
|
prediction ability is calculated as Shannon entropy. Here we need |
|
Shannon entropy instead of 'BitsEntropy' since the prefix will be |
|
encoded with the remaining 6 bits of the following byte, and |
|
BitsEntropy will assume that symbol to be stored alone using Huffman |
|
coding. */ |
|
|
|
var kStaticContextMapContinuation = [64]uint32{ |
|
1, 1, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
} |
|
var kStaticContextMapSimpleUTF8 = [64]uint32{ |
|
0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
} |
|
|
|
func chooseContextMap(quality int, bigram_histo []uint32, num_literal_contexts *uint, literal_context_map *[]uint32) { |
|
var monogram_histo = [3]uint32{0} |
|
var two_prefix_histo = [6]uint32{0} |
|
var total uint |
|
var i uint |
|
var dummy uint |
|
var entropy [4]float64 |
|
for i = 0; i < 9; i++ { |
|
monogram_histo[i%3] += bigram_histo[i] |
|
two_prefix_histo[i%6] += bigram_histo[i] |
|
} |
|
|
|
entropy[1] = shannonEntropy(monogram_histo[:], 3, &dummy) |
|
entropy[2] = (shannonEntropy(two_prefix_histo[:], 3, &dummy) + shannonEntropy(two_prefix_histo[3:], 3, &dummy)) |
|
entropy[3] = 0 |
|
for i = 0; i < 3; i++ { |
|
entropy[3] += shannonEntropy(bigram_histo[3*i:], 3, &dummy) |
|
} |
|
|
|
total = uint(monogram_histo[0] + monogram_histo[1] + monogram_histo[2]) |
|
assert(total != 0) |
|
entropy[0] = 1.0 / float64(total) |
|
entropy[1] *= entropy[0] |
|
entropy[2] *= entropy[0] |
|
entropy[3] *= entropy[0] |
|
|
|
if quality < minQualityForHqContextModeling { |
|
/* 3 context models is a bit slower, don't use it at lower qualities. */ |
|
entropy[3] = entropy[1] * 10 |
|
} |
|
|
|
/* If expected savings by symbol are less than 0.2 bits, skip the |
|
context modeling -- in exchange for faster decoding speed. */ |
|
if entropy[1]-entropy[2] < 0.2 && entropy[1]-entropy[3] < 0.2 { |
|
*num_literal_contexts = 1 |
|
} else if entropy[2]-entropy[3] < 0.02 { |
|
*num_literal_contexts = 2 |
|
*literal_context_map = kStaticContextMapSimpleUTF8[:] |
|
} else { |
|
*num_literal_contexts = 3 |
|
*literal_context_map = kStaticContextMapContinuation[:] |
|
} |
|
} |
|
|
|
/* Decide if we want to use a more complex static context map containing 13 |
|
context values, based on the entropy reduction of histograms over the |
|
first 5 bits of literals. */ |
|
|
|
var kStaticContextMapComplexUTF8 = [64]uint32{ |
|
11, 11, 12, 12, /* 0 special */ |
|
0, 0, 0, 0, /* 4 lf */ |
|
1, 1, 9, 9, /* 8 space */ |
|
2, 2, 2, 2, /* !, first after space/lf and after something else. */ |
|
1, 1, 1, 1, /* " */ |
|
8, 3, 3, 3, /* % */ |
|
1, 1, 1, 1, /* ({[ */ |
|
2, 2, 2, 2, /* }]) */ |
|
8, 4, 4, 4, /* :; */ |
|
8, 7, 4, 4, /* . */ |
|
8, 0, 0, 0, /* > */ |
|
3, 3, 3, 3, /* [0..9] */ |
|
5, 5, 10, 5, /* [A-Z] */ |
|
5, 5, 10, 5, |
|
6, 6, 6, 6, /* [a-z] */ |
|
6, 6, 6, 6, |
|
} |
|
|
|
func shouldUseComplexStaticContextMap(input []byte, start_pos uint, length uint, mask uint, quality int, size_hint uint, num_literal_contexts *uint, literal_context_map *[]uint32) bool { |
|
/* Try the more complex static context map only for long data. */ |
|
if size_hint < 1<<20 { |
|
return false |
|
} else { |
|
var end_pos uint = start_pos + length |
|
var combined_histo = [32]uint32{0} |
|
var context_histo = [13][32]uint32{[32]uint32{0}} |
|
var total uint32 = 0 |
|
var entropy [3]float64 |
|
var dummy uint |
|
var i uint |
|
var utf8_lut contextLUT = getContextLUT(contextUTF8) |
|
/* To make entropy calculations faster and to fit on the stack, we collect |
|
histograms over the 5 most significant bits of literals. One histogram |
|
without context and 13 additional histograms for each context value. */ |
|
for ; start_pos+64 <= end_pos; start_pos += 4096 { |
|
var stride_end_pos uint = start_pos + 64 |
|
var prev2 byte = input[start_pos&mask] |
|
var prev1 byte = input[(start_pos+1)&mask] |
|
var pos uint |
|
|
|
/* To make the analysis of the data faster we only examine 64 byte long |
|
strides at every 4kB intervals. */ |
|
for pos = start_pos + 2; pos < stride_end_pos; pos++ { |
|
var literal byte = input[pos&mask] |
|
var context byte = byte(kStaticContextMapComplexUTF8[getContext(prev1, prev2, utf8_lut)]) |
|
total++ |
|
combined_histo[literal>>3]++ |
|
context_histo[context][literal>>3]++ |
|
prev2 = prev1 |
|
prev1 = literal |
|
} |
|
} |
|
|
|
entropy[1] = shannonEntropy(combined_histo[:], 32, &dummy) |
|
entropy[2] = 0 |
|
for i = 0; i < 13; i++ { |
|
entropy[2] += shannonEntropy(context_histo[i][0:], 32, &dummy) |
|
} |
|
|
|
entropy[0] = 1.0 / float64(total) |
|
entropy[1] *= entropy[0] |
|
entropy[2] *= entropy[0] |
|
|
|
/* The triggering heuristics below were tuned by compressing the individual |
|
files of the silesia corpus. If we skip this kind of context modeling |
|
for not very well compressible input (i.e. entropy using context modeling |
|
is 60% of maximal entropy) or if expected savings by symbol are less |
|
than 0.2 bits, then in every case when it triggers, the final compression |
|
ratio is improved. Note however that this heuristics might be too strict |
|
for some cases and could be tuned further. */ |
|
if entropy[2] > 3.0 || entropy[1]-entropy[2] < 0.2 { |
|
return false |
|
} else { |
|
*num_literal_contexts = 13 |
|
*literal_context_map = kStaticContextMapComplexUTF8[:] |
|
return true |
|
} |
|
} |
|
} |
|
|
|
func decideOverLiteralContextModeling(input []byte, start_pos uint, length uint, mask uint, quality int, size_hint uint, num_literal_contexts *uint, literal_context_map *[]uint32) { |
|
if quality < minQualityForContextModeling || length < 64 { |
|
return |
|
} else if shouldUseComplexStaticContextMap(input, start_pos, length, mask, quality, size_hint, num_literal_contexts, literal_context_map) { |
|
} else /* Context map was already set, nothing else to do. */ |
|
{ |
|
var end_pos uint = start_pos + length |
|
/* Gather bi-gram data of the UTF8 byte prefixes. To make the analysis of |
|
UTF8 data faster we only examine 64 byte long strides at every 4kB |
|
intervals. */ |
|
|
|
var bigram_prefix_histo = [9]uint32{0} |
|
for ; start_pos+64 <= end_pos; start_pos += 4096 { |
|
var lut = [4]int{0, 0, 1, 2} |
|
var stride_end_pos uint = start_pos + 64 |
|
var prev int = lut[input[start_pos&mask]>>6] * 3 |
|
var pos uint |
|
for pos = start_pos + 1; pos < stride_end_pos; pos++ { |
|
var literal byte = input[pos&mask] |
|
bigram_prefix_histo[prev+lut[literal>>6]]++ |
|
prev = lut[literal>>6] * 3 |
|
} |
|
} |
|
|
|
chooseContextMap(quality, bigram_prefix_histo[0:], num_literal_contexts, literal_context_map) |
|
} |
|
} |
|
|
|
func shouldCompress_encode(data []byte, mask uint, last_flush_pos uint64, bytes uint, num_literals uint, num_commands uint) bool { |
|
/* TODO: find more precise minimal block overhead. */ |
|
if bytes <= 2 { |
|
return false |
|
} |
|
if num_commands < (bytes>>8)+2 { |
|
if float64(num_literals) > 0.99*float64(bytes) { |
|
var literal_histo = [256]uint32{0} |
|
const kSampleRate uint32 = 13 |
|
const kMinEntropy float64 = 7.92 |
|
var bit_cost_threshold float64 = float64(bytes) * kMinEntropy / float64(kSampleRate) |
|
var t uint = uint((uint32(bytes) + kSampleRate - 1) / kSampleRate) |
|
var pos uint32 = uint32(last_flush_pos) |
|
var i uint |
|
for i = 0; i < t; i++ { |
|
literal_histo[data[pos&uint32(mask)]]++ |
|
pos += kSampleRate |
|
} |
|
|
|
if bitsEntropy(literal_histo[:], 256) > bit_cost_threshold { |
|
return false |
|
} |
|
} |
|
} |
|
|
|
return true |
|
} |
|
|
|
/* Chooses the literal context mode for a metablock */ |
|
func chooseContextMode(params *encoderParams, data []byte, pos uint, mask uint, length uint) int { |
|
/* We only do the computation for the option of something else than |
|
CONTEXT_UTF8 for the highest qualities */ |
|
if params.quality >= minQualityForHqBlockSplitting && !isMostlyUTF8(data, pos, mask, length, kMinUTF8Ratio) { |
|
return contextSigned |
|
} |
|
|
|
return contextUTF8 |
|
} |
|
|
|
func writeMetaBlockInternal(data []byte, mask uint, last_flush_pos uint64, bytes uint, is_last bool, literal_context_mode int, params *encoderParams, prev_byte byte, prev_byte2 byte, num_literals uint, commands []command, saved_dist_cache []int, dist_cache []int, bw *bitWriter) { |
|
var wrapped_last_flush_pos uint32 = wrapPosition(last_flush_pos) |
|
var literal_context_lut contextLUT = getContextLUT(literal_context_mode) |
|
var block_params encoderParams = *params |
|
|
|
if bytes == 0 { |
|
/* Write the ISLAST and ISEMPTY bits. */ |
|
bw.writeBits(2, 3) |
|
bw.jumpToByteBoundary() |
|
return |
|
} |
|
|
|
if !shouldCompress_encode(data, mask, last_flush_pos, bytes, num_literals, uint(len(commands))) { |
|
/* Restore the distance cache, as its last update by |
|
CreateBackwardReferences is now unused. */ |
|
copy(dist_cache, saved_dist_cache[:4]) |
|
|
|
storeUncompressedMetaBlock(is_last, data, uint(wrapped_last_flush_pos), mask, bytes, bw) |
|
return |
|
} |
|
|
|
savedPos := bw.getPos() |
|
if params.quality <= maxQualityForStaticEntropyCodes { |
|
storeMetaBlockFast(data, uint(wrapped_last_flush_pos), bytes, mask, is_last, params, commands, bw) |
|
} else if params.quality < minQualityForBlockSplit { |
|
storeMetaBlockTrivial(data, uint(wrapped_last_flush_pos), bytes, mask, is_last, params, commands, bw) |
|
} else { |
|
mb := getMetaBlockSplit() |
|
if params.quality < minQualityForHqBlockSplitting { |
|
var num_literal_contexts uint = 1 |
|
var literal_context_map []uint32 = nil |
|
if !params.disable_literal_context_modeling { |
|
decideOverLiteralContextModeling(data, uint(wrapped_last_flush_pos), bytes, mask, params.quality, params.size_hint, &num_literal_contexts, &literal_context_map) |
|
} |
|
|
|
buildMetaBlockGreedy(data, uint(wrapped_last_flush_pos), mask, prev_byte, prev_byte2, literal_context_lut, num_literal_contexts, literal_context_map, commands, mb) |
|
} else { |
|
buildMetaBlock(data, uint(wrapped_last_flush_pos), mask, &block_params, prev_byte, prev_byte2, commands, literal_context_mode, mb) |
|
} |
|
|
|
if params.quality >= minQualityForOptimizeHistograms { |
|
/* The number of distance symbols effectively used for distance |
|
histograms. It might be less than distance alphabet size |
|
for "Large Window Brotli" (32-bit). */ |
|
var num_effective_dist_codes uint32 = block_params.dist.alphabet_size |
|
if num_effective_dist_codes > numHistogramDistanceSymbols { |
|
num_effective_dist_codes = numHistogramDistanceSymbols |
|
} |
|
|
|
optimizeHistograms(num_effective_dist_codes, mb) |
|
} |
|
|
|
storeMetaBlock(data, uint(wrapped_last_flush_pos), bytes, mask, prev_byte, prev_byte2, is_last, &block_params, literal_context_mode, commands, mb, bw) |
|
freeMetaBlockSplit(mb) |
|
} |
|
|
|
if bytes+4 < bw.getPos()>>3 { |
|
/* Restore the distance cache and last byte. */ |
|
copy(dist_cache, saved_dist_cache[:4]) |
|
bw.rewind(savedPos) |
|
storeUncompressedMetaBlock(is_last, data, uint(wrapped_last_flush_pos), mask, bytes, bw) |
|
} |
|
} |
|
|
|
func chooseDistanceParams(params *encoderParams) { |
|
var distance_postfix_bits uint32 = 0 |
|
var num_direct_distance_codes uint32 = 0 |
|
|
|
if params.quality >= minQualityForNonzeroDistanceParams { |
|
var ndirect_msb uint32 |
|
if params.mode == modeFont { |
|
distance_postfix_bits = 1 |
|
num_direct_distance_codes = 12 |
|
} else { |
|
distance_postfix_bits = params.dist.distance_postfix_bits |
|
num_direct_distance_codes = params.dist.num_direct_distance_codes |
|
} |
|
|
|
ndirect_msb = (num_direct_distance_codes >> distance_postfix_bits) & 0x0F |
|
if distance_postfix_bits > maxNpostfix || num_direct_distance_codes > maxNdirect || ndirect_msb<<distance_postfix_bits != num_direct_distance_codes { |
|
distance_postfix_bits = 0 |
|
num_direct_distance_codes = 0 |
|
} |
|
} |
|
|
|
initDistanceParams(params, distance_postfix_bits, num_direct_distance_codes) |
|
} |
|
|
|
func ensureInitialized(s *Writer) bool { |
|
if s.is_initialized_ { |
|
return true |
|
} |
|
|
|
s.bw.bits = 0 |
|
s.bw.nbits = 0 |
|
s.bw.dst = s.bw.dst[:0] |
|
|
|
s.remaining_metadata_bytes_ = math.MaxUint32 |
|
|
|
sanitizeParams(&s.params) |
|
s.params.lgblock = computeLgBlock(&s.params) |
|
chooseDistanceParams(&s.params) |
|
|
|
ringBufferSetup(&s.params, &s.ringbuffer_) |
|
|
|
/* Initialize last byte with stream header. */ |
|
{ |
|
var lgwin int = int(s.params.lgwin) |
|
if s.params.quality == fastOnePassCompressionQuality || s.params.quality == fastTwoPassCompressionQuality { |
|
lgwin = brotli_max_int(lgwin, 18) |
|
} |
|
|
|
encodeWindowBits(lgwin, s.params.large_window, &s.bw) |
|
} |
|
|
|
if s.params.quality == fastOnePassCompressionQuality { |
|
s.cmd_depths_ = [128]byte{ |
|
0, 4, 4, 5, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, |
|
0, 0, 0, 4, 4, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, |
|
7, 7, 10, 10, 10, 10, 10, 10, 0, 4, 4, 5, 5, 5, 6, 6, |
|
7, 8, 8, 9, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, |
|
5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
6, 6, 6, 6, 6, 6, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, |
|
4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 7, 7, 7, 8, 10, |
|
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, |
|
} |
|
s.cmd_bits_ = [128]uint16{ |
|
0, 0, 8, 9, 3, 35, 7, 71, |
|
39, 103, 23, 47, 175, 111, 239, 31, |
|
0, 0, 0, 4, 12, 2, 10, 6, |
|
13, 29, 11, 43, 27, 59, 87, 55, |
|
15, 79, 319, 831, 191, 703, 447, 959, |
|
0, 14, 1, 25, 5, 21, 19, 51, |
|
119, 159, 95, 223, 479, 991, 63, 575, |
|
127, 639, 383, 895, 255, 767, 511, 1023, |
|
14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|
27, 59, 7, 39, 23, 55, 30, 1, 17, 9, 25, 5, 0, 8, 4, 12, |
|
2, 10, 6, 21, 13, 29, 3, 19, 11, 15, 47, 31, 95, 63, 127, 255, |
|
767, 2815, 1791, 3839, 511, 2559, 1535, 3583, 1023, 3071, 2047, 4095, |
|
} |
|
s.cmd_code_ = [512]byte{ |
|
0xff, 0x77, 0xd5, 0xbf, 0xe7, 0xde, 0xea, 0x9e, 0x51, 0x5d, 0xde, 0xc6, |
|
0x70, 0x57, 0xbc, 0x58, 0x58, 0x58, 0xd8, 0xd8, 0x58, 0xd5, 0xcb, 0x8c, |
|
0xea, 0xe0, 0xc3, 0x87, 0x1f, 0x83, 0xc1, 0x60, 0x1c, 0x67, 0xb2, 0xaa, |
|
0x06, 0x83, 0xc1, 0x60, 0x30, 0x18, 0xcc, 0xa1, 0xce, 0x88, 0x54, 0x94, |
|
0x46, 0xe1, 0xb0, 0xd0, 0x4e, 0xb2, 0xf7, 0x04, 0x00, |
|
} |
|
s.cmd_code_numbits_ = 448 |
|
} |
|
|
|
s.is_initialized_ = true |
|
return true |
|
} |
|
|
|
func encoderInitParams(params *encoderParams) { |
|
params.mode = defaultMode |
|
params.large_window = false |
|
params.quality = defaultQuality |
|
params.lgwin = defaultWindow |
|
params.lgblock = 0 |
|
params.size_hint = 0 |
|
params.disable_literal_context_modeling = false |
|
initEncoderDictionary(¶ms.dictionary) |
|
params.dist.distance_postfix_bits = 0 |
|
params.dist.num_direct_distance_codes = 0 |
|
params.dist.alphabet_size = uint32(distanceAlphabetSize(0, 0, maxDistanceBits)) |
|
params.dist.max_distance = maxDistance |
|
} |
|
|
|
func encoderInitState(s *Writer) { |
|
encoderInitParams(&s.params) |
|
s.input_pos_ = 0 |
|
s.commands = s.commands[:0] |
|
s.num_literals_ = 0 |
|
s.last_insert_len_ = 0 |
|
s.last_flush_pos_ = 0 |
|
s.last_processed_pos_ = 0 |
|
s.prev_byte_ = 0 |
|
s.prev_byte2_ = 0 |
|
if s.hasher_ != nil { |
|
s.hasher_.Common().is_prepared_ = false |
|
} |
|
s.cmd_code_numbits_ = 0 |
|
s.stream_state_ = streamProcessing |
|
s.is_last_block_emitted_ = false |
|
s.is_initialized_ = false |
|
|
|
ringBufferInit(&s.ringbuffer_) |
|
|
|
/* Initialize distance cache. */ |
|
s.dist_cache_[0] = 4 |
|
|
|
s.dist_cache_[1] = 11 |
|
s.dist_cache_[2] = 15 |
|
s.dist_cache_[3] = 16 |
|
|
|
/* Save the state of the distance cache in case we need to restore it for |
|
emitting an uncompressed block. */ |
|
copy(s.saved_dist_cache_[:], s.dist_cache_[:]) |
|
} |
|
|
|
/* |
|
Copies the given input data to the internal ring buffer of the compressor. |
|
No processing of the data occurs at this time and this function can be |
|
called multiple times before calling WriteBrotliData() to process the |
|
accumulated input. At most input_block_size() bytes of input data can be |
|
copied to the ring buffer, otherwise the next WriteBrotliData() will fail. |
|
*/ |
|
func copyInputToRingBuffer(s *Writer, input_size uint, input_buffer []byte) { |
|
var ringbuffer_ *ringBuffer = &s.ringbuffer_ |
|
ringBufferWrite(input_buffer, input_size, ringbuffer_) |
|
s.input_pos_ += uint64(input_size) |
|
|
|
/* TL;DR: If needed, initialize 7 more bytes in the ring buffer to make the |
|
hashing not depend on uninitialized data. This makes compression |
|
deterministic and it prevents uninitialized memory warnings in Valgrind. |
|
Even without erasing, the output would be valid (but nondeterministic). |
|
|
|
Background information: The compressor stores short (at most 8 bytes) |
|
substrings of the input already read in a hash table, and detects |
|
repetitions by looking up such substrings in the hash table. If it |
|
can find a substring, it checks whether the substring is really there |
|
in the ring buffer (or it's just a hash collision). Should the hash |
|
table become corrupt, this check makes sure that the output is |
|
still valid, albeit the compression ratio would be bad. |
|
|
|
The compressor populates the hash table from the ring buffer as it's |
|
reading new bytes from the input. However, at the last few indexes of |
|
the ring buffer, there are not enough bytes to build full-length |
|
substrings from. Since the hash table always contains full-length |
|
substrings, we erase with dummy zeros here to make sure that those |
|
substrings will contain zeros at the end instead of uninitialized |
|
data. |
|
|
|
Please note that erasing is not necessary (because the |
|
memory region is already initialized since he ring buffer |
|
has a `tail' that holds a copy of the beginning,) so we |
|
skip erasing if we have already gone around at least once in |
|
the ring buffer. |
|
|
|
Only clear during the first round of ring-buffer writes. On |
|
subsequent rounds data in the ring-buffer would be affected. */ |
|
if ringbuffer_.pos_ <= ringbuffer_.mask_ { |
|
/* This is the first time when the ring buffer is being written. |
|
We clear 7 bytes just after the bytes that have been copied from |
|
the input buffer. |
|
|
|
The ring-buffer has a "tail" that holds a copy of the beginning, |
|
but only once the ring buffer has been fully written once, i.e., |
|
pos <= mask. For the first time, we need to write values |
|
in this tail (where index may be larger than mask), so that |
|
we have exactly defined behavior and don't read uninitialized |
|
memory. Due to performance reasons, hashing reads data using a |
|
LOAD64, which can go 7 bytes beyond the bytes written in the |
|
ring-buffer. */ |
|
for i := 0; i < int(7); i++ { |
|
ringbuffer_.buffer_[ringbuffer_.pos_:][i] = 0 |
|
} |
|
} |
|
} |
|
|
|
/* Marks all input as processed. |
|
Returns true if position wrapping occurs. */ |
|
func updateLastProcessedPos(s *Writer) bool { |
|
var wrapped_last_processed_pos uint32 = wrapPosition(s.last_processed_pos_) |
|
var wrapped_input_pos uint32 = wrapPosition(s.input_pos_) |
|
s.last_processed_pos_ = s.input_pos_ |
|
return wrapped_input_pos < wrapped_last_processed_pos |
|
} |
|
|
|
func extendLastCommand(s *Writer, bytes *uint32, wrapped_last_processed_pos *uint32) { |
|
var last_command *command = &s.commands[len(s.commands)-1] |
|
var data []byte = s.ringbuffer_.buffer_ |
|
var mask uint32 = s.ringbuffer_.mask_ |
|
var max_backward_distance uint64 = ((uint64(1)) << s.params.lgwin) - windowGap |
|
var last_copy_len uint64 = uint64(last_command.copy_len_) & 0x1FFFFFF |
|
var last_processed_pos uint64 = s.last_processed_pos_ - last_copy_len |
|
var max_distance uint64 |
|
if last_processed_pos < max_backward_distance { |
|
max_distance = last_processed_pos |
|
} else { |
|
max_distance = max_backward_distance |
|
} |
|
var cmd_dist uint64 = uint64(s.dist_cache_[0]) |
|
var distance_code uint32 = commandRestoreDistanceCode(last_command, &s.params.dist) |
|
if distance_code < numDistanceShortCodes || uint64(distance_code-(numDistanceShortCodes-1)) == cmd_dist { |
|
if cmd_dist <= max_distance { |
|
for *bytes != 0 && data[*wrapped_last_processed_pos&mask] == data[(uint64(*wrapped_last_processed_pos)-cmd_dist)&uint64(mask)] { |
|
last_command.copy_len_++ |
|
(*bytes)-- |
|
(*wrapped_last_processed_pos)++ |
|
} |
|
} |
|
|
|
/* The copy length is at most the metablock size, and thus expressible. */ |
|
getLengthCode(uint(last_command.insert_len_), uint(int(last_command.copy_len_&0x1FFFFFF)+int(last_command.copy_len_>>25)), (last_command.dist_prefix_&0x3FF == 0), &last_command.cmd_prefix_) |
|
} |
|
} |
|
|
|
/* |
|
Processes the accumulated input data and writes |
|
the new output meta-block to s.dest, if one has been |
|
created (otherwise the processed input data is buffered internally). |
|
If |is_last| or |force_flush| is true, an output meta-block is |
|
always created. However, until |is_last| is true encoder may retain up |
|
to 7 bits of the last byte of output. To force encoder to dump the remaining |
|
bits use WriteMetadata() to append an empty meta-data block. |
|
Returns false if the size of the input data is larger than |
|
input_block_size(). |
|
*/ |
|
func encodeData(s *Writer, is_last bool, force_flush bool) bool { |
|
var delta uint64 = unprocessedInputSize(s) |
|
var bytes uint32 = uint32(delta) |
|
var wrapped_last_processed_pos uint32 = wrapPosition(s.last_processed_pos_) |
|
var data []byte |
|
var mask uint32 |
|
var literal_context_mode int |
|
|
|
data = s.ringbuffer_.buffer_ |
|
mask = s.ringbuffer_.mask_ |
|
|
|
/* Adding more blocks after "last" block is forbidden. */ |
|
if s.is_last_block_emitted_ { |
|
return false |
|
} |
|
if is_last { |
|
s.is_last_block_emitted_ = true |
|
} |
|
|
|
if delta > uint64(inputBlockSize(s)) { |
|
return false |
|
} |
|
|
|
if s.params.quality == fastTwoPassCompressionQuality { |
|
if s.command_buf_ == nil || cap(s.command_buf_) < int(kCompressFragmentTwoPassBlockSize) { |
|
s.command_buf_ = make([]uint32, kCompressFragmentTwoPassBlockSize) |
|
s.literal_buf_ = make([]byte, kCompressFragmentTwoPassBlockSize) |
|
} else { |
|
s.command_buf_ = s.command_buf_[:kCompressFragmentTwoPassBlockSize] |
|
s.literal_buf_ = s.literal_buf_[:kCompressFragmentTwoPassBlockSize] |
|
} |
|
} |
|
|
|
if s.params.quality == fastOnePassCompressionQuality || s.params.quality == fastTwoPassCompressionQuality { |
|
var table_size uint |
|
var table []int |
|
|
|
if delta == 0 && !is_last { |
|
/* We have no new input data and we don't have to finish the stream, so |
|
nothing to do. */ |
|
return true |
|
} |
|
|
|
table = getHashTable(s, s.params.quality, uint(bytes), &table_size) |
|
if s.params.quality == fastOnePassCompressionQuality { |
|
compressFragmentFast(data[wrapped_last_processed_pos&mask:], uint(bytes), is_last, table, table_size, s.cmd_depths_[:], s.cmd_bits_[:], &s.cmd_code_numbits_, s.cmd_code_[:], &s.bw) |
|
} else { |
|
compressFragmentTwoPass(data[wrapped_last_processed_pos&mask:], uint(bytes), is_last, s.command_buf_, s.literal_buf_, table, table_size, &s.bw) |
|
} |
|
|
|
updateLastProcessedPos(s) |
|
s.writeOutput(s.bw.dst) |
|
s.bw.dst = s.bw.dst[:0] |
|
return true |
|
} |
|
{ |
|
/* Theoretical max number of commands is 1 per 2 bytes. */ |
|
newsize := len(s.commands) + int(bytes)/2 + 1 |
|
if newsize > cap(s.commands) { |
|
/* Reserve a bit more memory to allow merging with a next block |
|
without reallocation: that would impact speed. */ |
|
newsize += int(bytes/4) + 16 |
|
|
|
new_commands := make([]command, len(s.commands), newsize) |
|
if s.commands != nil { |
|
copy(new_commands, s.commands) |
|
} |
|
|
|
s.commands = new_commands |
|
} |
|
} |
|
|
|
initOrStitchToPreviousBlock(&s.hasher_, data, uint(mask), &s.params, uint(wrapped_last_processed_pos), uint(bytes), is_last) |
|
|
|
literal_context_mode = chooseContextMode(&s.params, data, uint(wrapPosition(s.last_flush_pos_)), uint(mask), uint(s.input_pos_-s.last_flush_pos_)) |
|
|
|
if len(s.commands) != 0 && s.last_insert_len_ == 0 { |
|
extendLastCommand(s, &bytes, &wrapped_last_processed_pos) |
|
} |
|
|
|
if s.params.quality == zopflificationQuality { |
|
assert(s.params.hasher.type_ == 10) |
|
createZopfliBackwardReferences(uint(bytes), uint(wrapped_last_processed_pos), data, uint(mask), &s.params, s.hasher_.(*h10), s.dist_cache_[:], &s.last_insert_len_, &s.commands, &s.num_literals_) |
|
} else if s.params.quality == hqZopflificationQuality { |
|
assert(s.params.hasher.type_ == 10) |
|
createHqZopfliBackwardReferences(uint(bytes), uint(wrapped_last_processed_pos), data, uint(mask), &s.params, s.hasher_, s.dist_cache_[:], &s.last_insert_len_, &s.commands, &s.num_literals_) |
|
} else { |
|
createBackwardReferences(uint(bytes), uint(wrapped_last_processed_pos), data, uint(mask), &s.params, s.hasher_, s.dist_cache_[:], &s.last_insert_len_, &s.commands, &s.num_literals_) |
|
} |
|
{ |
|
var max_length uint = maxMetablockSize(&s.params) |
|
var max_literals uint = max_length / 8 |
|
max_commands := int(max_length / 8) |
|
var processed_bytes uint = uint(s.input_pos_ - s.last_flush_pos_) |
|
var next_input_fits_metablock bool = (processed_bytes+inputBlockSize(s) <= max_length) |
|
var should_flush bool = (s.params.quality < minQualityForBlockSplit && s.num_literals_+uint(len(s.commands)) >= maxNumDelayedSymbols) |
|
/* If maximal possible additional block doesn't fit metablock, flush now. */ |
|
/* TODO: Postpone decision until next block arrives? */ |
|
|
|
/* If block splitting is not used, then flush as soon as there is some |
|
amount of commands / literals produced. */ |
|
if !is_last && !force_flush && !should_flush && next_input_fits_metablock && s.num_literals_ < max_literals && len(s.commands) < max_commands { |
|
/* Merge with next input block. Everything will happen later. */ |
|
if updateLastProcessedPos(s) { |
|
hasherReset(s.hasher_) |
|
} |
|
|
|
return true |
|
} |
|
} |
|
|
|
/* Create the last insert-only command. */ |
|
if s.last_insert_len_ > 0 { |
|
s.commands = append(s.commands, makeInsertCommand(s.last_insert_len_)) |
|
s.num_literals_ += s.last_insert_len_ |
|
s.last_insert_len_ = 0 |
|
} |
|
|
|
if !is_last && s.input_pos_ == s.last_flush_pos_ { |
|
/* We have no new input data and we don't have to finish the stream, so |
|
nothing to do. */ |
|
return true |
|
} |
|
|
|
assert(s.input_pos_ >= s.last_flush_pos_) |
|
assert(s.input_pos_ > s.last_flush_pos_ || is_last) |
|
assert(s.input_pos_-s.last_flush_pos_ <= 1<<24) |
|
{ |
|
var metablock_size uint32 = uint32(s.input_pos_ - s.last_flush_pos_) |
|
writeMetaBlockInternal(data, uint(mask), s.last_flush_pos_, uint(metablock_size), is_last, literal_context_mode, &s.params, s.prev_byte_, s.prev_byte2_, s.num_literals_, s.commands, s.saved_dist_cache_[:], s.dist_cache_[:], &s.bw) |
|
s.last_flush_pos_ = s.input_pos_ |
|
if updateLastProcessedPos(s) { |
|
hasherReset(s.hasher_) |
|
} |
|
|
|
if s.last_flush_pos_ > 0 { |
|
s.prev_byte_ = data[(uint32(s.last_flush_pos_)-1)&mask] |
|
} |
|
|
|
if s.last_flush_pos_ > 1 { |
|
s.prev_byte2_ = data[uint32(s.last_flush_pos_-2)&mask] |
|
} |
|
|
|
s.commands = s.commands[:0] |
|
s.num_literals_ = 0 |
|
|
|
/* Save the state of the distance cache in case we need to restore it for |
|
emitting an uncompressed block. */ |
|
copy(s.saved_dist_cache_[:], s.dist_cache_[:]) |
|
|
|
s.writeOutput(s.bw.dst) |
|
s.bw.dst = s.bw.dst[:0] |
|
return true |
|
} |
|
} |
|
|
|
/* Dumps remaining output bits and metadata header to s.bw. |
|
REQUIRED: |block_size| <= (1 << 24). */ |
|
func writeMetadataHeader(s *Writer, block_size uint) { |
|
bw := &s.bw |
|
|
|
bw.writeBits(1, 0) |
|
bw.writeBits(2, 3) |
|
bw.writeBits(1, 0) |
|
if block_size == 0 { |
|
bw.writeBits(2, 0) |
|
} else { |
|
var nbits uint32 |
|
if block_size == 1 { |
|
nbits = 0 |
|
} else { |
|
nbits = log2FloorNonZero(uint(uint32(block_size)-1)) + 1 |
|
} |
|
var nbytes uint32 = (nbits + 7) / 8 |
|
bw.writeBits(2, uint64(nbytes)) |
|
bw.writeBits(uint(8*nbytes), uint64(block_size)-1) |
|
} |
|
|
|
bw.jumpToByteBoundary() |
|
} |
|
|
|
func injectBytePaddingBlock(s *Writer) { |
|
/* is_last = 0, data_nibbles = 11, reserved = 0, meta_nibbles = 00 */ |
|
s.bw.writeBits(6, 0x6) |
|
s.bw.jumpToByteBoundary() |
|
s.writeOutput(s.bw.dst) |
|
s.bw.dst = s.bw.dst[:0] |
|
} |
|
|
|
func checkFlushComplete(s *Writer) { |
|
if s.stream_state_ == streamFlushRequested && s.err == nil { |
|
s.stream_state_ = streamProcessing |
|
} |
|
} |
|
|
|
func encoderCompressStreamFast(s *Writer, op int, available_in *uint, next_in *[]byte) bool { |
|
var block_size_limit uint = uint(1) << s.params.lgwin |
|
var buf_size uint = brotli_min_size_t(kCompressFragmentTwoPassBlockSize, brotli_min_size_t(*available_in, block_size_limit)) |
|
var command_buf []uint32 = nil |
|
var literal_buf []byte = nil |
|
if s.params.quality != fastOnePassCompressionQuality && s.params.quality != fastTwoPassCompressionQuality { |
|
return false |
|
} |
|
|
|
if s.params.quality == fastTwoPassCompressionQuality { |
|
if s.command_buf_ == nil || cap(s.command_buf_) < int(buf_size) { |
|
s.command_buf_ = make([]uint32, buf_size) |
|
s.literal_buf_ = make([]byte, buf_size) |
|
} else { |
|
s.command_buf_ = s.command_buf_[:buf_size] |
|
s.literal_buf_ = s.literal_buf_[:buf_size] |
|
} |
|
|
|
command_buf = s.command_buf_ |
|
literal_buf = s.literal_buf_ |
|
} |
|
|
|
for { |
|
if s.stream_state_ == streamFlushRequested && s.bw.nbits&7 != 0 { |
|
injectBytePaddingBlock(s) |
|
continue |
|
} |
|
|
|
/* Compress block only when stream is not |
|
finished, there is no pending flush request, and there is either |
|
additional input or pending operation. */ |
|
if s.stream_state_ == streamProcessing && (*available_in != 0 || op != int(operationProcess)) { |
|
var block_size uint = brotli_min_size_t(block_size_limit, *available_in) |
|
var is_last bool = (*available_in == block_size) && (op == int(operationFinish)) |
|
var force_flush bool = (*available_in == block_size) && (op == int(operationFlush)) |
|
var table_size uint |
|
var table []int |
|
|
|
if force_flush && block_size == 0 { |
|
s.stream_state_ = streamFlushRequested |
|
continue |
|
} |
|
|
|
table = getHashTable(s, s.params.quality, block_size, &table_size) |
|
|
|
if s.params.quality == fastOnePassCompressionQuality { |
|
compressFragmentFast(*next_in, block_size, is_last, table, table_size, s.cmd_depths_[:], s.cmd_bits_[:], &s.cmd_code_numbits_, s.cmd_code_[:], &s.bw) |
|
} else { |
|
compressFragmentTwoPass(*next_in, block_size, is_last, command_buf, literal_buf, table, table_size, &s.bw) |
|
} |
|
|
|
*next_in = (*next_in)[block_size:] |
|
*available_in -= block_size |
|
s.writeOutput(s.bw.dst) |
|
s.bw.dst = s.bw.dst[:0] |
|
|
|
if force_flush { |
|
s.stream_state_ = streamFlushRequested |
|
} |
|
if is_last { |
|
s.stream_state_ = streamFinished |
|
} |
|
continue |
|
} |
|
|
|
break |
|
} |
|
|
|
checkFlushComplete(s) |
|
return true |
|
} |
|
|
|
func processMetadata(s *Writer, available_in *uint, next_in *[]byte) bool { |
|
if *available_in > 1<<24 { |
|
return false |
|
} |
|
|
|
/* Switch to metadata block workflow, if required. */ |
|
if s.stream_state_ == streamProcessing { |
|
s.remaining_metadata_bytes_ = uint32(*available_in) |
|
s.stream_state_ = streamMetadataHead |
|
} |
|
|
|
if s.stream_state_ != streamMetadataHead && s.stream_state_ != streamMetadataBody { |
|
return false |
|
} |
|
|
|
for { |
|
if s.stream_state_ == streamFlushRequested && s.bw.nbits&7 != 0 { |
|
injectBytePaddingBlock(s) |
|
continue |
|
} |
|
|
|
if s.input_pos_ != s.last_flush_pos_ { |
|
var result bool = encodeData(s, false, true) |
|
if !result { |
|
return false |
|
} |
|
continue |
|
} |
|
|
|
if s.stream_state_ == streamMetadataHead { |
|
writeMetadataHeader(s, uint(s.remaining_metadata_bytes_)) |
|
s.writeOutput(s.bw.dst) |
|
s.bw.dst = s.bw.dst[:0] |
|
s.stream_state_ = streamMetadataBody |
|
continue |
|
} else { |
|
/* Exit workflow only when there is no more input and no more output. |
|
Otherwise client may continue producing empty metadata blocks. */ |
|
if s.remaining_metadata_bytes_ == 0 { |
|
s.remaining_metadata_bytes_ = math.MaxUint32 |
|
s.stream_state_ = streamProcessing |
|
break |
|
} |
|
|
|
/* This guarantees progress in "TakeOutput" workflow. */ |
|
var c uint32 = brotli_min_uint32_t(s.remaining_metadata_bytes_, 16) |
|
copy(s.tiny_buf_.u8[:], (*next_in)[:c]) |
|
*next_in = (*next_in)[c:] |
|
*available_in -= uint(c) |
|
s.remaining_metadata_bytes_ -= c |
|
s.writeOutput(s.tiny_buf_.u8[:c]) |
|
|
|
continue |
|
} |
|
} |
|
|
|
return true |
|
} |
|
|
|
func updateSizeHint(s *Writer, available_in uint) { |
|
if s.params.size_hint == 0 { |
|
var delta uint64 = unprocessedInputSize(s) |
|
var tail uint64 = uint64(available_in) |
|
var limit uint32 = 1 << 30 |
|
var total uint32 |
|
if (delta >= uint64(limit)) || (tail >= uint64(limit)) || ((delta + tail) >= uint64(limit)) { |
|
total = limit |
|
} else { |
|
total = uint32(delta + tail) |
|
} |
|
|
|
s.params.size_hint = uint(total) |
|
} |
|
} |
|
|
|
func encoderCompressStream(s *Writer, op int, available_in *uint, next_in *[]byte) bool { |
|
if !ensureInitialized(s) { |
|
return false |
|
} |
|
|
|
/* Unfinished metadata block; check requirements. */ |
|
if s.remaining_metadata_bytes_ != math.MaxUint32 { |
|
if uint32(*available_in) != s.remaining_metadata_bytes_ { |
|
return false |
|
} |
|
if op != int(operationEmitMetadata) { |
|
return false |
|
} |
|
} |
|
|
|
if op == int(operationEmitMetadata) { |
|
updateSizeHint(s, 0) /* First data metablock might be emitted here. */ |
|
return processMetadata(s, available_in, next_in) |
|
} |
|
|
|
if s.stream_state_ == streamMetadataHead || s.stream_state_ == streamMetadataBody { |
|
return false |
|
} |
|
|
|
if s.stream_state_ != streamProcessing && *available_in != 0 { |
|
return false |
|
} |
|
|
|
if s.params.quality == fastOnePassCompressionQuality || s.params.quality == fastTwoPassCompressionQuality { |
|
return encoderCompressStreamFast(s, op, available_in, next_in) |
|
} |
|
|
|
for { |
|
var remaining_block_size uint = remainingInputBlockSize(s) |
|
|
|
if remaining_block_size != 0 && *available_in != 0 { |
|
var copy_input_size uint = brotli_min_size_t(remaining_block_size, *available_in) |
|
copyInputToRingBuffer(s, copy_input_size, *next_in) |
|
*next_in = (*next_in)[copy_input_size:] |
|
*available_in -= copy_input_size |
|
continue |
|
} |
|
|
|
if s.stream_state_ == streamFlushRequested && s.bw.nbits&7 != 0 { |
|
injectBytePaddingBlock(s) |
|
continue |
|
} |
|
|
|
/* Compress data only when stream is not |
|
finished and there is no pending flush request. */ |
|
if s.stream_state_ == streamProcessing { |
|
if remaining_block_size == 0 || op != int(operationProcess) { |
|
var is_last bool = ((*available_in == 0) && op == int(operationFinish)) |
|
var force_flush bool = ((*available_in == 0) && op == int(operationFlush)) |
|
var result bool |
|
updateSizeHint(s, *available_in) |
|
result = encodeData(s, is_last, force_flush) |
|
if !result { |
|
return false |
|
} |
|
if force_flush { |
|
s.stream_state_ = streamFlushRequested |
|
} |
|
if is_last { |
|
s.stream_state_ = streamFinished |
|
} |
|
continue |
|
} |
|
} |
|
|
|
break |
|
} |
|
|
|
checkFlushComplete(s) |
|
return true |
|
} |
|
|
|
func (w *Writer) writeOutput(data []byte) { |
|
if w.err != nil { |
|
return |
|
} |
|
|
|
_, w.err = w.dst.Write(data) |
|
if w.err == nil { |
|
checkFlushComplete(w) |
|
} |
|
}
|
|
|