Brotli native

This commit is contained in:
Kenny Daniel 2024-05-19 22:06:38 -07:00
parent e2dae829b2
commit 209ca7d791
No known key found for this signature in database
GPG Key ID: 90AB653A8CAD7E45
13 changed files with 2818 additions and 9 deletions

@ -52,7 +52,7 @@
"no-var": "error",
"object-curly-spacing": ["error", "always"],
"prefer-const": "error",
"prefer-destructuring": ["warn", {"object": true, "array": false}],
"prefer-destructuring": ["warn", { "VariableDeclarator": { "object": true, "array": false }} ],
"prefer-promise-reject-errors": "error",
"quotes": ["error", "single"],
"require-await": "warn",

@ -3,7 +3,7 @@
[![npm](https://img.shields.io/npm/v/hyparquet-compressors)](https://www.npmjs.com/package/hyparquet-compressors)
[![workflow status](https://github.com/hyparam/hyparquet-compressors/actions/workflows/ci.yml/badge.svg)](https://github.com/hyparam/hyparquet-compressors/actions)
[![mit license](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
![coverage](https://img.shields.io/badge/Coverage-93-darkred)
![coverage](https://img.shields.io/badge/Coverage-86-darkred)
This package exports a `compressors` object intended to be passed into [hyparquet](https://github.com/hyparam/hyparquet).
@ -47,7 +47,7 @@ Includes modifications to handle repeated back-to-back gzip streams that sometim
## Brotli
Uses [brotli.js](https://github.com/foliojs/brotli.js) for brotli decompression.
Includes a minimal port of [brotli.js](https://github.com/foliojs/brotli.js) which compresses the brotli dictionary using gzip and base64 to minimize the distribution bundle size.
## LZ4
@ -61,8 +61,8 @@ Uses [fzstd](https://github.com/101arrowz/fzstd) for Zstandard decompression.
| File | Size |
| - | - |
| hyparquet-compressors.min.js | 502.1kb |
| hyparquet-compressors.min.js.gz | 102.2kb |
| hyparquet-compressors.min.js | 116.1kb |
| hyparquet-compressors.min.js.gz | 75.2kb |
# References

@ -32,7 +32,6 @@
"test": "vitest run"
},
"dependencies": {
"brotli": "1.3.3",
"fzstd": "0.1.1",
"hysnappy": "0.3.1"
},
@ -41,7 +40,6 @@
"@rollup/plugin-commonjs": "25.0.8",
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-terser": "0.4.4",
"@types/brotli": "1.3.4",
"@types/node": "20.12.12",
"@vitest/coverage-v8": "1.6.0",
"eslint": "8.57.0",

706
src/brotli.js Normal file

@ -0,0 +1,706 @@
/* Adapted from https://github.com/foliojs/brotli.js
* Copyright 2015 Devon Govett, MIT License
* Copyright 2013 Google Inc, Apache License 2.0
*/
import BrotliBitReader from './brotliBitReader.js'
import { lookup, lookupOffsets } from './brotliContext.js'
import { HuffmanCode, readHuffmanCode, readSymbol } from './brotliHuffman.js'
import { kBlockLengthPrefixCode, kCopyLengthPrefixCode, kCopyRangeLut, kInsertLengthPrefixCode, kInsertRangeLut } from './brotliPrefix.js'
import { BrotliInput, BrotliOutput } from './brotliStreams.js'
import { kNumTransforms, transformDictionaryWord } from './brotliTransform.js'
const kNumLiteralCodes = 256
const kNumInsertAndCopyCodes = 704
const kNumBlockLengthCodes = 26
const kLiteralContextBits = 6
const kDistanceContextBits = 2
/* Maximum possible Huffman table size for an alphabet size of 704, max code
* length 15 and root table bits 8. */
const HUFFMAN_MAX_TABLE_SIZE = 1080
const NUM_DISTANCE_SHORT_CODES = 16
const kDistanceShortCodeIndexOffset = new Uint8Array([
3, 2, 1, 0, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2,
])
const kDistanceShortCodeValueOffset = new Int8Array([
0, 0, 0, 0, -1, 1, -2, 2, -3, 3, -1, 1, -2, 2, -3, 3,
])
const kMaxHuffmanTableSize = new Uint16Array([
256, 402, 436, 468, 500, 534, 566, 598, 630, 662, 694, 726, 758, 790, 822,
854, 886, 920, 952, 984, 1016, 1048, 1080,
])
// Brotli dictionary
const offsetsByLength = new Uint32Array([
0, 0, 0, 0, 0, 4096, 9216, 21504, 35840, 44032,
53248, 63488, 74752, 87040, 93696, 100864, 104704, 106752, 108928, 113536,
115968, 118528, 119872, 121280, 122016,
])
const sizeBitsByLength = new Uint8Array([
0, 0, 0, 0, 10, 10, 11, 11, 10, 10,
10, 10, 10, 9, 9, 8, 7, 7, 8, 7,
7, 6, 6, 5, 5,
])
const minDictionaryWordLength = 4
const maxDictionaryWordLength = 24
/**
* @param {Uint8Array} input
* @param {number} outputLength
* @returns {Uint8Array}
*/
export function BROTLI(input, outputLength) {
const output = new Uint8Array(outputLength)
const brotliInput = new BrotliInput(input)
const brotliOutput = new BrotliOutput(output)
brotli(brotliInput, brotliOutput)
return output
}
/**
* @param {BrotliInput} input
* @param {BrotliOutput} output
*/
function brotli(input, output) {
let pos = 0
let input_end = 0
let window_bits = 0
let max_distance = 0
/* This ring buffer holds a few past copy distances that will be used by */
/* some special distance codes. */
const dist_rb = [ 16, 15, 11, 4 ]
let dist_rb_idx = 0
/* The previous 2 bytes used for context */
let prev_byte1 = 0
let prev_byte2 = 0
const hgroup = [new HuffmanTreeGroup(0, 0), new HuffmanTreeGroup(0, 0), new HuffmanTreeGroup(0, 0)]
/* We need the slack region for the following reasons:
- always doing two 8-byte copies for fast backward copying
- transforms
- flushing the input ringbuffer when decoding uncompressed blocks */
const kRingBufferWriteAheadSlack = 128 + BrotliBitReader.READ_SIZE
const br = new BrotliBitReader(input)
/* Decode window size. */
window_bits = decodeWindowBits(br)
const max_backward_distance = (1 << window_bits) - 16
const ringbuffer_size = 1 << window_bits
const ringbuffer_mask = ringbuffer_size - 1
const ringbuffer = new Uint8Array(ringbuffer_size + kRingBufferWriteAheadSlack + maxDictionaryWordLength)
const ringbuffer_end = ringbuffer_size
const block_type_trees = []
const block_len_trees = []
for (let x = 0; x < 3 * HUFFMAN_MAX_TABLE_SIZE; x++) {
block_type_trees[x] = new HuffmanCode(0, 0)
block_len_trees[x] = new HuffmanCode(0, 0)
}
while (!input_end) {
let meta_block_remaining_len = 0
const block_length = [ 1 << 28, 1 << 28, 1 << 28 ]
const block_type = [ 0 ]
const num_block_types = [ 1, 1, 1 ]
const block_type_rb = [ 0, 1, 0, 1, 0, 1 ]
const block_type_rb_index = [ 0 ]
let context_offset = 0
for (let i = 0; i < 3; i++) {
hgroup[i].codes = []
hgroup[i].htrees = new Uint32Array()
}
br.readMoreInput()
const _out = DecodeMetaBlockLength(br)
meta_block_remaining_len = _out.meta_block_length
if (pos + meta_block_remaining_len > output.buffer.length) {
/* We need to grow the output buffer to fit the additional data. */
const tmp = new Uint8Array( pos + meta_block_remaining_len )
tmp.set( output.buffer )
output.buffer = tmp
}
input_end = _out.input_end
if (_out.is_metadata) {
jumpToByteBoundary(br)
for (; meta_block_remaining_len > 0; --meta_block_remaining_len) {
br.readMoreInput()
/* Read one byte and ignore it. */
br.readBits(8)
}
continue
}
if (meta_block_remaining_len === 0) continue
if (_out.is_uncompressed) {
br.bit_pos_ = br.bit_pos_ + 7 & ~7
copyUncompressedBlockToOutput(output, meta_block_remaining_len, pos, ringbuffer, ringbuffer_mask, br)
pos += meta_block_remaining_len
continue
}
for (let i = 0; i < 3; i++) {
num_block_types[i] = decodeVarLenUint8(br) + 1
if (num_block_types[i] >= 2) {
readHuffmanCode(num_block_types[i] + 2, block_type_trees, i * HUFFMAN_MAX_TABLE_SIZE, br)
readHuffmanCode(kNumBlockLengthCodes, block_len_trees, i * HUFFMAN_MAX_TABLE_SIZE, br)
block_length[i] = readBlockLength(block_len_trees, i * HUFFMAN_MAX_TABLE_SIZE, br)
block_type_rb_index[i] = 1
}
}
br.readMoreInput()
const distance_postfix_bits = br.readBits(2)
const num_direct_distance_codes = NUM_DISTANCE_SHORT_CODES + (br.readBits(4) << distance_postfix_bits)
const distance_postfix_mask = (1 << distance_postfix_bits) - 1
const num_distance_codes = num_direct_distance_codes + (48 << distance_postfix_bits)
const context_modes = new Uint8Array(num_block_types[0])
for (let i = 0; i < num_block_types[0]; i++) {
br.readMoreInput()
context_modes[i] = br.readBits(2) << 1
}
const _o1 = DecodeContextMap(num_block_types[0] << kLiteralContextBits, br)
const num_literal_htrees = _o1.num_htrees
const { context_map } = _o1
const _o2 = DecodeContextMap(num_block_types[2] << kDistanceContextBits, br)
const num_dist_htrees = _o2.num_htrees
const dist_context_map = _o2.context_map
hgroup[0] = new HuffmanTreeGroup(kNumLiteralCodes, num_literal_htrees)
hgroup[1] = new HuffmanTreeGroup(kNumInsertAndCopyCodes, num_block_types[1])
hgroup[2] = new HuffmanTreeGroup(num_distance_codes, num_dist_htrees)
for (let i = 0; i < 3; ++i) {
hgroup[i].decode(br)
}
let context_map_slice = 0
let dist_context_map_slice = 0
let context_mode = context_modes[block_type[0]]
let context_lookup_offset1 = lookupOffsets[context_mode]
let context_lookup_offset2 = lookupOffsets[context_mode + 1]
let htree_command = hgroup[1].htrees[0]
while (meta_block_remaining_len > 0) {
let distance_code
br.readMoreInput()
if (block_length[1] === 0) {
decodeBlockType(num_block_types[1],
block_type_trees, 1, block_type, block_type_rb,
block_type_rb_index, br)
block_length[1] = readBlockLength(block_len_trees, HUFFMAN_MAX_TABLE_SIZE, br)
htree_command = hgroup[1].htrees[block_type[1]]
}
block_length[1]--
const cmd_code = readSymbol(hgroup[1].codes, htree_command, br)
let range_idx = cmd_code >> 6
if (range_idx >= 2) {
range_idx -= 2
distance_code = -1
} else {
distance_code = 0
}
const insert_code = kInsertRangeLut[range_idx] + (cmd_code >> 3 & 7)
const copy_code = kCopyRangeLut[range_idx] + (cmd_code & 7)
const insert_length = kInsertLengthPrefixCode[insert_code].offset +
br.readBits(kInsertLengthPrefixCode[insert_code].nbits)
const copy_length = kCopyLengthPrefixCode[copy_code].offset +
br.readBits(kCopyLengthPrefixCode[copy_code].nbits)
prev_byte1 = ringbuffer[pos - 1 & ringbuffer_mask]
prev_byte2 = ringbuffer[pos - 2 & ringbuffer_mask]
for (let j = 0; j < insert_length; j++) {
br.readMoreInput()
if (block_length[0] === 0) {
decodeBlockType(num_block_types[0],
block_type_trees, 0, block_type, block_type_rb,
block_type_rb_index, br)
block_length[0] = readBlockLength(block_len_trees, 0, br)
context_offset = block_type[0] << kLiteralContextBits
context_map_slice = context_offset
context_mode = context_modes[block_type[0]]
context_lookup_offset1 = lookupOffsets[context_mode]
context_lookup_offset2 = lookupOffsets[context_mode + 1]
}
const context = lookup[context_lookup_offset1 + prev_byte1] |
lookup[context_lookup_offset2 + prev_byte2]
const literal_htree_index = context_map[context_map_slice + context]
block_length[0]--
prev_byte2 = prev_byte1
prev_byte1 = readSymbol(hgroup[0].codes, hgroup[0].htrees[literal_htree_index], br)
ringbuffer[pos & ringbuffer_mask] = prev_byte1
if ((pos & ringbuffer_mask) === ringbuffer_mask) {
output.write(ringbuffer, ringbuffer_size)
}
pos++
}
meta_block_remaining_len -= insert_length
if (meta_block_remaining_len <= 0) break
if (distance_code < 0) {
br.readMoreInput()
if (block_length[2] === 0) {
decodeBlockType(num_block_types[2],
block_type_trees, 2, block_type, block_type_rb,
block_type_rb_index, br)
block_length[2] = readBlockLength(block_len_trees, 2 * HUFFMAN_MAX_TABLE_SIZE, br)
dist_context_map_slice = block_type[2] << kDistanceContextBits
}
block_length[2]--
const context = (copy_length > 4 ? 3 : copy_length - 2) & 0xff
const dist_htree_index = dist_context_map[dist_context_map_slice + context]
distance_code = readSymbol(hgroup[2].codes, hgroup[2].htrees[dist_htree_index], br)
if (distance_code >= num_direct_distance_codes) {
distance_code -= num_direct_distance_codes
const postfix = distance_code & distance_postfix_mask
distance_code >>= distance_postfix_bits
const nbits = (distance_code >> 1) + 1
const offset = (2 + (distance_code & 1) << nbits) - 4
distance_code = num_direct_distance_codes +
(offset + br.readBits(nbits) <<
distance_postfix_bits) + postfix
}
}
/* Convert the distance code to the actual distance by possibly looking */
/* up past distnaces from the ringbuffer. */
const distance = translateShortCodes(distance_code, dist_rb, dist_rb_idx)
if (distance < 0) throw new Error('[BrotliDecompress] invalid distance')
if (pos < max_backward_distance && max_distance !== max_backward_distance) {
max_distance = pos
} else {
max_distance = max_backward_distance
}
let copy_dst = pos & ringbuffer_mask
if (distance > max_distance) {
if (copy_length >= minDictionaryWordLength && copy_length <= maxDictionaryWordLength) {
let offset = offsetsByLength[copy_length]
const word_id = distance - max_distance - 1
const shift = sizeBitsByLength[copy_length]
const mask = (1 << shift) - 1
const word_idx = word_id & mask
const transform_idx = word_id >> shift
offset += word_idx * copy_length
if (transform_idx < kNumTransforms) {
const len = transformDictionaryWord(ringbuffer, copy_dst, offset, copy_length, transform_idx)
copy_dst += len
pos += len
meta_block_remaining_len -= len
if (copy_dst >= ringbuffer_end) {
output.write(ringbuffer, ringbuffer_size)
for (let _x = 0; _x < copy_dst - ringbuffer_end; _x++)
ringbuffer[_x] = ringbuffer[ringbuffer_end + _x]
}
} else {
throw new Error('Invalid backward reference')
}
} else {
throw new Error('Invalid backward reference')
}
} else {
if (distance_code > 0) {
dist_rb[dist_rb_idx & 3] = distance
dist_rb_idx++
}
if (copy_length > meta_block_remaining_len) {
throw new Error('Invalid backward reference')
}
for (let j = 0; j < copy_length; j++) {
ringbuffer[pos & ringbuffer_mask] = ringbuffer[pos - distance & ringbuffer_mask]
if ((pos & ringbuffer_mask) === ringbuffer_mask) {
output.write(ringbuffer, ringbuffer_size)
}
pos++
meta_block_remaining_len--
}
}
/* When we get here, we must have inserted at least one literal and */
/* made a copy of at least length two, therefore accessing the last 2 */
/* bytes is valid. */
prev_byte1 = ringbuffer[pos - 1 & ringbuffer_mask]
prev_byte2 = ringbuffer[pos - 2 & ringbuffer_mask]
}
/* Protect pos from overflow, wrap it around at every GB of input data */
pos &= 0x3fffffff
}
output.write(ringbuffer, pos & ringbuffer_mask)
}
/**
* @param {BrotliBitReader} br
* @returns {number}
*/
function decodeWindowBits(br) {
if (br.readBits(1) === 0) return 16
let n = br.readBits(3)
if (n > 0) return 17 + n
n = br.readBits(3)
if (n > 0) return 8 + n
return 17
}
/**
* @param {number} max_block_type
* @param {HuffmanCode[]} trees
* @param {number} tree_type
* @param {number[]} block_types
* @param {number[]} ringbuffers
* @param {number[]} indexes
* @param {BrotliBitReader} br
*/
function decodeBlockType(max_block_type, trees, tree_type, block_types, ringbuffers, indexes, br) {
const ringbuffer = tree_type * 2
const index = tree_type
const type_code = readSymbol(trees, tree_type * HUFFMAN_MAX_TABLE_SIZE, br)
let block_type
if (type_code === 0) {
block_type = ringbuffers[ringbuffer + (indexes[index] & 1)]
} else if (type_code === 1) {
block_type = ringbuffers[ringbuffer + (indexes[index] - 1 & 1)] + 1
} else {
block_type = type_code - 2
}
if (block_type >= max_block_type) {
block_type -= max_block_type
}
block_types[tree_type] = block_type
ringbuffers[ringbuffer + (indexes[index] & 1)] = block_type
++indexes[index]
}
/**
* Contains a collection of huffman trees with the same alphabet size.
*
* @param {number} alphabet_size
* @param {number} num_htrees
*/
function HuffmanTreeGroup(alphabet_size, num_htrees) {
this.alphabet_size = alphabet_size
this.num_htrees = num_htrees
this.codes = new Array(num_htrees + num_htrees * kMaxHuffmanTableSize[alphabet_size + 31 >>> 5])
this.htrees = new Uint32Array(num_htrees)
}
/**
* @param {BrotliBitReader} br
*/
HuffmanTreeGroup.prototype.decode = function(br) {
let next = 0
for (let i = 0; i < this.num_htrees; i++) {
this.htrees[i] = next
next += readHuffmanCode(this.alphabet_size, this.codes, next, br)
}
}
/**
* @param {HuffmanCode[]} table
* @param {number} index
* @param {BrotliBitReader} br
* @returns {number}
*/
function readBlockLength(table, index, br) {
const code = readSymbol(table, index, br)
const { nbits } = kBlockLengthPrefixCode[code]
return kBlockLengthPrefixCode[code].offset + br.readBits(nbits)
}
/**
* @param {number} code
* @param {number[]} ringbuffer
* @param {number} index
* @returns {number}
*/
function translateShortCodes(code, ringbuffer, index) {
if (code < NUM_DISTANCE_SHORT_CODES) {
index += kDistanceShortCodeIndexOffset[code]
index &= 3
return ringbuffer[index] + kDistanceShortCodeValueOffset[code]
} else {
return code - NUM_DISTANCE_SHORT_CODES + 1
}
}
/**
* @param {*} output
* @param {number} len
* @param {number} pos
* @param {Uint8Array} ringbuffer
* @param {number} ringbuffer_mask
* @param {BrotliBitReader} br
*/
function copyUncompressedBlockToOutput(output, len, pos, ringbuffer, ringbuffer_mask, br) {
const rb_size = ringbuffer_mask + 1
let rb_pos = pos & ringbuffer_mask
let br_pos = br.pos_ & BrotliBitReader.IBUF_MASK
/* For short lengths copy byte-by-byte */
if (len < 8 || br.bit_pos_ + (len << 3) < br.bit_end_pos_) {
while (len-- > 0) {
br.readMoreInput()
ringbuffer[rb_pos++] = br.readBits(8)
if (rb_pos === rb_size) {
output.write(ringbuffer, rb_size)
rb_pos = 0
}
}
return
}
if (br.bit_end_pos_ < 32) {
throw new Error('[CopyUncompressedBlockToOutput] br.bit_end_pos_ < 32')
}
/* Copy remaining 0-4 bytes from br.val_ to ringbuffer. */
while (br.bit_pos_ < 32) {
ringbuffer[rb_pos] = br.val_ >>> br.bit_pos_
br.bit_pos_ += 8
rb_pos++
len--
}
/* Copy remaining bytes from br.buf_ to ringbuffer. */
let nbytes = br.bit_end_pos_ - br.bit_pos_ >> 3
if (br_pos + nbytes > BrotliBitReader.IBUF_MASK) {
const tail = BrotliBitReader.IBUF_MASK + 1 - br_pos
for (let x = 0; x < tail; x++)
ringbuffer[rb_pos + x] = br.buf_[br_pos + x]
nbytes -= tail
rb_pos += tail
len -= tail
br_pos = 0
}
for (let x = 0; x < nbytes; x++)
ringbuffer[rb_pos + x] = br.buf_[br_pos + x]
rb_pos += nbytes
len -= nbytes
/* If we wrote past the logical end of the ringbuffer, copy the tail of the
ringbuffer to its beginning and flush the ringbuffer to the output. */
if (rb_pos >= rb_size) {
output.write(ringbuffer, rb_size)
rb_pos -= rb_size
for (let x = 0; x < rb_pos; x++)
ringbuffer[x] = ringbuffer[rb_size + x]
}
/* If we have more to copy than the remaining size of the ringbuffer, then we
first fill the ringbuffer from the input and then flush the ringbuffer to
the output */
while (rb_pos + len >= rb_size) {
nbytes = rb_size - rb_pos
if (br.input_.read(ringbuffer, rb_pos, nbytes) < nbytes) {
throw new Error('[CopyUncompressedBlockToOutput] not enough bytes')
}
output.write(ringbuffer, rb_size)
len -= nbytes
rb_pos = 0
}
/* Copy straight from the input onto the ringbuffer. The ringbuffer will be
flushed to the output at a later time. */
if (br.input_.read(ringbuffer, rb_pos, len) < len) {
throw new Error('[CopyUncompressedBlockToOutput] not enough bytes')
}
/* Restore the state of the bit reader. */
br.reset()
}
/**
* Decodes a number in the range [0..255], by reading 1 - 11 bits.
* @param {BrotliBitReader} br
* @returns {number}
*/
function decodeVarLenUint8(br) {
if (br.readBits(1)) {
const nbits = br.readBits(3)
if (nbits === 0) {
return 1
} else {
return br.readBits(nbits) + (1 << nbits)
}
}
return 0
}
function MetaBlockLength() {
this.meta_block_length = 0
this.input_end = 0
this.is_uncompressed = 0
this.is_metadata = false
}
/**
* @param {BrotliBitReader} br
* @returns {MetaBlockLength}
*/
function DecodeMetaBlockLength(br) {
const out = new MetaBlockLength
out.input_end = br.readBits(1)
if (out.input_end && br.readBits(1)) {
return out
}
const size_nibbles = br.readBits(2) + 4
if (size_nibbles === 7) {
out.is_metadata = true
if (br.readBits(1) !== 0)
throw new Error('Invalid reserved bit')
const size_bytes = br.readBits(2)
if (size_bytes === 0)
return out
for (let i = 0; i < size_bytes; i++) {
const next_byte = br.readBits(8)
if (i + 1 === size_bytes && size_bytes > 1 && next_byte === 0)
throw new Error('Invalid size byte')
out.meta_block_length |= next_byte << i * 8
}
} else {
for (let i = 0; i < size_nibbles; i++) {
const next_nibble = br.readBits(4)
if (i + 1 === size_nibbles && size_nibbles > 4 && next_nibble === 0)
throw new Error('Invalid size nibble')
out.meta_block_length |= next_nibble << i * 4
}
}
out.meta_block_length++
if (!out.input_end && !out.is_metadata) {
out.is_uncompressed = br.readBits(1)
}
return out
}
/**
* @param {number} context_map_size
* @param {BrotliBitReader} br
* @returns {{ num_htrees: number, context_map: Uint8Array }}
*/
function DecodeContextMap(context_map_size, br) {
let max_run_length_prefix = 0
br.readMoreInput()
const num_htrees = decodeVarLenUint8(br) + 1
const context_map = new Uint8Array(context_map_size)
if (num_htrees <= 1) {
return { num_htrees, context_map }
}
const use_rle_for_zeros = br.readBits(1)
if (use_rle_for_zeros) {
max_run_length_prefix = br.readBits(4) + 1
}
const table = []
for (let i = 0; i < HUFFMAN_MAX_TABLE_SIZE; i++) {
table[i] = new HuffmanCode(0, 0)
}
readHuffmanCode(num_htrees + max_run_length_prefix, table, 0, br)
for (let i = 0; i < context_map_size;) {
br.readMoreInput()
const code = readSymbol(table, 0, br)
if (code === 0) {
context_map[i] = 0
i++
} else if (code <= max_run_length_prefix) {
let reps = 1 + (1 << code) + br.readBits(code)
while (--reps) {
if (i >= context_map_size) {
throw new Error('[DecodeContextMap] i >= context_map_size')
}
context_map[i] = 0
i++
}
} else {
context_map[i] = code - max_run_length_prefix
i++
}
}
if (br.readBits(1)) {
inverseMoveToFrontTransform(context_map, context_map_size)
}
return { num_htrees, context_map }
}
/**
* @param {Uint8Array} v
* @param {number} index
*/
function moveToFront(v, index) {
const value = v[index]
for (let i = index; i; i--) v[i] = v[i - 1]
v[0] = value
}
/**
* @param {Uint8Array} v
* @param {number} v_len
*/
function inverseMoveToFrontTransform(v, v_len) {
const mtf = new Uint8Array(256)
for (let i = 0; i < 256; i++) {
mtf[i] = i
}
for (let i = 0; i < v_len; i++) {
const index = v[i]
v[i] = mtf[index]
if (index) moveToFront(mtf, index)
}
}
/**
* Advances the bit reader position to the next byte boundary and verifies
* that any skipped bits are set to zero.
* @param {BrotliBitReader} br
* @returns {boolean}
*/
function jumpToByteBoundary(br) {
const new_bit_pos = br.bit_pos_ + 7 & ~7
return !br.readBits(new_bit_pos - br.bit_pos_)
}

136
src/brotliBitReader.js Normal file

@ -0,0 +1,136 @@
/* Copyright 2013 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Bit reading helpers
*/
const BROTLI_READ_SIZE = 4096
const BROTLI_IBUF_SIZE = 2 * BROTLI_READ_SIZE + 32
const BROTLI_IBUF_MASK = 2 * BROTLI_READ_SIZE - 1
const kBitMask = new Uint32Array([
0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767,
65535, 131071, 262143, 524287, 1048575, 2097151, 4194303, 8388607, 16777215,
])
/**
* Input byte buffer, consist of a ringbuffer and a "slack" region where
* bytes from the start of the ringbuffer are copied.
*
* @typedef {import('./brotliStreams.js').BrotliInput} BrotliInput
* @param {BrotliInput} input
*/
function BrotliBitReader(input) {
this.buf_ = new Uint8Array(BROTLI_IBUF_SIZE)
this.input_ = input /* input callback */
this.buf_ptr_ = 0 /* next input will write here */
this.val_ = 0 /* pre-fetched bits */
this.pos_ = 0 /* byte position in stream */
this.reset()
}
BrotliBitReader.READ_SIZE = BROTLI_READ_SIZE
BrotliBitReader.IBUF_MASK = BROTLI_IBUF_MASK
BrotliBitReader.prototype.reset = function() {
this.buf_ptr_ = 0 /* next input will write here */
this.val_ = 0 /* pre-fetched bits */
this.pos_ = 0 /* byte position in stream */
this.bit_pos_ = 0 /* current bit-reading position in val_ */
this.bit_end_pos_ = 0 /* bit-reading end position from LSB of val_ */
this.eos_ = 0 /* input stream is finished */
this.readMoreInput()
for (let i = 0; i < 4; i++) {
this.val_ |= this.buf_[this.pos_] << 8 * i
this.pos_++
}
return this.bit_end_pos_ > 0
}
/**
* Fills up the input ringbuffer by calling the input callback.
*
* Does nothing if there are at least 32 bytes present after current position.
*
* Returns 0 if either:
* - the input callback returned an error, or
* - there is no more input and the position is past the end of the stream.
*
* After encountering the end of the input stream, 32 additional zero bytes are
* copied to the ringbuffer, therefore it is safe to call this function after
* every 32 bytes of input is read.
*/
BrotliBitReader.prototype.readMoreInput = function() {
if (this.bit_end_pos_ > 256) {
// return
} else if (this.eos_) {
if (this.bit_pos_ > this.bit_end_pos_)
throw new Error('Unexpected end of input ' + this.bit_pos_ + ' ' + this.bit_end_pos_)
} else {
const dst = this.buf_ptr_
const bytes_read = this.input_.read(this.buf_, dst, BROTLI_READ_SIZE)
if (bytes_read < 0) {
throw new Error('Unexpected end of input')
}
if (bytes_read < BROTLI_READ_SIZE) {
this.eos_ = 1
/* Store 32 bytes of zero after the stream end. */
for (let p = 0; p < 32; p++)
this.buf_[dst + bytes_read + p] = 0
}
if (dst === 0) {
/* Copy the head of the ringbuffer to the slack region. */
for (let p = 0; p < 32; p++)
this.buf_[(BROTLI_READ_SIZE << 1) + p] = this.buf_[p]
this.buf_ptr_ = BROTLI_READ_SIZE
} else {
this.buf_ptr_ = 0
}
this.bit_end_pos_ += bytes_read << 3
}
}
/* Guarantees that there are at least 24 bits in the buffer. */
BrotliBitReader.prototype.fillBitWindow = function() {
while (this.bit_pos_ >= 8) {
this.val_ >>>= 8
this.val_ |= this.buf_[this.pos_ & BROTLI_IBUF_MASK] << 24
this.pos_++
this.bit_pos_ = this.bit_pos_ - 8 >>> 0
this.bit_end_pos_ = this.bit_end_pos_ - 8 >>> 0
}
}
/**
* Reads the specified number of bits from Read Buffer.
*
* @param {number} n_bits
* @returns {number}
*/
BrotliBitReader.prototype.readBits = function(n_bits) {
if (32 - this.bit_pos_ < n_bits) this.fillBitWindow()
const val = this.val_ >>> this.bit_pos_ & kBitMask[n_bits]
this.bit_pos_ += n_bits
return val
}
export default BrotliBitReader

245
src/brotliContext.js Normal file

@ -0,0 +1,245 @@
/* Copyright 2013 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Lookup table to map the previous two bytes to a context id.
There are four different context modeling modes defined here:
CONTEXT_LSB6: context id is the least significant 6 bits of the last byte,
CONTEXT_MSB6: context id is the most significant 6 bits of the last byte,
CONTEXT_UTF8: second-order context model tuned for UTF8-encoded text,
CONTEXT_SIGNED: second-order context model tuned for signed integers.
The context id for the UTF8 context model is calculated as follows. If p1
and p2 are the previous two bytes, we calcualte the context as
context = kContextLookup[p1] | kContextLookup[p2 + 256].
If the previous two bytes are ASCII characters (i.e. < 128), this will be
equivalent to
context = 4 * context1(p1) + context2(p2),
where context1 is based on the previous byte in the following way:
0 : non-ASCII control
1 : \t, \n, \r
2 : space
3 : other punctuation
4 : " '
5 : %
6 : ( < [ {
7 : ) > ] }
8 : , ; :
9 : .
10 : =
11 : number
12 : upper-case vowel
13 : upper-case consonant
14 : lower-case vowel
15 : lower-case consonant
and context2 is based on the second last byte:
0 : control, space
1 : punctuation
2 : upper-case letter, number
3 : lower-case letter
If the last byte is ASCII, and the second last byte is not (in a valid UTF8
stream it will be a continuation byte, value between 128 and 191), the
context is the same as if the second last byte was an ASCII control or space.
If the last byte is a UTF8 lead byte (value >= 192), then the next byte will
be a continuation byte and the context id is 2 or 3 depending on the LSB of
the last byte and to a lesser extent on the second last byte if it is ASCII.
If the last byte is a UTF8 continuation byte, the second last byte can be:
- continuation byte: the next byte is probably ASCII or lead byte (assuming
4-byte UTF8 characters are rare) and the context id is 0 or 1.
- lead byte (192 - 207): next byte is ASCII or lead byte, context is 0 or 1
- lead byte (208 - 255): next byte is continuation byte, context is 2 or 3
The possible value combinations of the previous two bytes, the range of
context ids and the type of the next byte is summarized in the table below:
|--------\-----------------------------------------------------------------|
| \ Last byte |
| Second \---------------------------------------------------------------|
| last byte \ ASCII | cont. byte | lead byte |
| \ (0-127) | (128-191) | (192-) |
|=============|===================|=====================|==================|
| ASCII | next: ASCII/lead | not valid | next: cont. |
| (0-127) | context: 4 - 63 | | context: 2 - 3 |
|-------------|-------------------|---------------------|------------------|
| cont. byte | next: ASCII/lead | next: ASCII/lead | next: cont. |
| (128-191) | context: 4 - 63 | context: 0 - 1 | context: 2 - 3 |
|-------------|-------------------|---------------------|------------------|
| lead byte | not valid | next: ASCII/lead | not valid |
| (192-207) | | context: 0 - 1 | |
|-------------|-------------------|---------------------|------------------|
| lead byte | not valid | next: cont. | not valid |
| (208-) | | context: 2 - 3 | |
|-------------|-------------------|---------------------|------------------|
The context id for the signed context mode is calculated as:
context = (kContextLookup[512 + p1] << 3) | kContextLookup[512 + p2].
For any context modeling modes, the context ids can be calculated by |-ing
together two lookups from one table using context model dependent offsets:
context = kContextLookup[offset1 + p1] | kContextLookup[offset2 + p2].
where offset1 and offset2 are dependent on the context mode.
*/
/* Common context lookup table for all context modes. */
export const lookup = new Uint8Array([
/* CONTEXT_UTF8, last byte. */
/* ASCII range. */
0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 4, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
8, 12, 16, 12, 12, 20, 12, 16, 24, 28, 12, 12, 32, 12, 36, 12,
44, 44, 44, 44, 44, 44, 44, 44, 44, 44, 32, 32, 24, 40, 28, 12,
12, 48, 52, 52, 52, 48, 52, 52, 52, 48, 52, 52, 52, 52, 52, 48,
52, 52, 52, 52, 52, 48, 52, 52, 52, 52, 52, 24, 12, 28, 12, 12,
12, 56, 60, 60, 60, 56, 60, 60, 60, 56, 60, 60, 60, 60, 60, 56,
60, 60, 60, 60, 60, 56, 60, 60, 60, 60, 60, 24, 12, 28, 12, 0,
/* UTF8 continuation byte range. */
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1,
/* UTF8 lead byte range. */
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3, 2, 3,
/* CONTEXT_UTF8 second last byte. */
/* ASCII range. */
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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1,
1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1,
1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 0,
/* UTF8 continuation byte range. */
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, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* UTF8 lead byte range. */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
/* CONTEXT_SIGNED, second last byte. */
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7,
/* CONTEXT_SIGNED, last byte, same as the above values shifted by 3 bits. */
0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40, 40,
48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 48, 56,
/* CONTEXT_LSB6, last byte. */
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63,
/* CONTEXT_MSB6, last byte. */
0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,
4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7,
8, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11,
12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 15,
16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, 19, 19, 19, 19,
20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23,
24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27,
28, 28, 28, 28, 29, 29, 29, 29, 30, 30, 30, 30, 31, 31, 31, 31,
32, 32, 32, 32, 33, 33, 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
36, 36, 36, 36, 37, 37, 37, 37, 38, 38, 38, 38, 39, 39, 39, 39,
40, 40, 40, 40, 41, 41, 41, 41, 42, 42, 42, 42, 43, 43, 43, 43,
44, 44, 44, 44, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47,
48, 48, 48, 48, 49, 49, 49, 49, 50, 50, 50, 50, 51, 51, 51, 51,
52, 52, 52, 52, 53, 53, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55,
56, 56, 56, 56, 57, 57, 57, 57, 58, 58, 58, 58, 59, 59, 59, 59,
60, 60, 60, 60, 61, 61, 61, 61, 62, 62, 62, 62, 63, 63, 63, 63,
/* CONTEXT_{M,L}SB6, second last byte, */
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, 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, 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, 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,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
])
export const lookupOffsets = new Uint16Array([
/* CONTEXT_LSB6 */
1024, 1536,
/* CONTEXT_MSB6 */
1280, 1536,
/* CONTEXT_UTF8 */
0, 256,
/* CONTEXT_SIGNED */
768, 512,
])

1006
src/brotliDictionary.js Normal file

File diff suppressed because it is too large Load Diff

349
src/brotliHuffman.js Normal file

@ -0,0 +1,349 @@
const kDefaultCodeLength = 8
const HUFFMAN_TABLE_BITS = 8
const HUFFMAN_TABLE_MASK = 0xff
const CODE_LENGTH_CODES = 18
const kCodeLengthCodeOrder = new Uint8Array([
1, 2, 3, 4, 0, 5, 17, 6, 16, 7, 8, 9, 10, 11, 12, 13, 14, 15,
])
/**
* @param {number} bits
* @param {number} value
*/
export function HuffmanCode(bits, value) {
this.bits = bits /* number of bits used for this symbol */
this.value = value /* symbol value or table offset */
}
const kCodeLengthRepeatCode = 16
const MAX_LENGTH = 15
/**
* Returns reverse(reverse(key, len) + 1, len), where reverse(key, len) is the
* bit-wise reversal of the len least significant bits of key.
* @param {number} key
* @param {number} len
* @returns {number}
*/
function getNextKey(key, len) {
let step = 1 << len - 1
while (key & step) {
step >>= 1
}
return (key & step - 1) + step
}
/**
* Stores code in table[0], table[step], table[2*step], ..., table[end]
* Assumes that end is an integer multiple of step
* @param {HuffmanCode[]} table
* @param {number} i
* @param {number} step
* @param {number} end
* @param {HuffmanCode} code
*/
function replicateValue(table, i, step, end, code) {
do {
end -= step
table[i + end] = new HuffmanCode(code.bits, code.value)
} while (end > 0)
}
/**
* Returns the table width of the next 2nd level table. count is the histogram
* of bit lengths for the remaining symbols, len is the code length of the next
* processed symbol
* @param {Int32Array} count
* @param {number} len
* @param {number} root_bits
* @returns {number}
*/
function nextTableBitSize(count, len, root_bits) {
let left = 1 << len - root_bits
while (len < MAX_LENGTH) {
left -= count[len]
if (left <= 0) break
++len
left <<= 1
}
return len - root_bits
}
/**
* @param {HuffmanCode[]} root_table
* @param {number} table
* @param {number} root_bits
* @param {Uint8Array} code_lengths
* @param {number} code_lengths_size
* @returns {number}
*/
export function buildHuffmanTable(root_table, table, root_bits, code_lengths, code_lengths_size) {
const start_table = table
const count = new Int32Array(MAX_LENGTH + 1) /* number of codes of each length */
const offset = new Int32Array(MAX_LENGTH + 1) /* offsets in sorted table for each length */
const sorted = new Int32Array(code_lengths_size) /* symbols sorted by code length */
/* build histogram of code lengths */
for (let i = 0; i < code_lengths_size; i++) {
count[code_lengths[i]]++
}
/* generate offsets into sorted symbol table by code length */
offset[1] = 0
for (let i = 1; i < MAX_LENGTH; i++) {
offset[i + 1] = offset[i] + count[i]
}
/* sort symbols by length, by symbol order within each length */
for (let i = 0; i < code_lengths_size; i++) {
if (code_lengths[i] !== 0) {
sorted[offset[code_lengths[i]]++] = i
}
}
let table_bits = root_bits // key length of current table
let table_size = 1 << table_bits
let total_size = table_size // sum of root table size and 2nd level table sizes
/* special case code with only one value */
if (offset[MAX_LENGTH] === 1) {
for (let key = 0; key < total_size; ++key) {
root_table[table + key] = new HuffmanCode(0, sorted[0] & 0xffff)
}
return total_size
}
/* fill in root table */
let key = 0 // reversed prefix code
let symbol = 0 // symbol index in original or sorted table
for (let len = 1, step = 2; len <= root_bits; ++len, step <<= 1) {
for (; count[len] > 0; --count[len]) {
const code = new HuffmanCode(len & 0xff, sorted[symbol++] & 0xffff)
replicateValue(root_table, table + key, step, table_size, code)
key = getNextKey(key, len)
}
}
/* fill in 2nd level tables and add pointers to root table */
const mask = total_size - 1
let low = -1 // low bits for current root entry
for (let len = root_bits + 1, step = 2; len <= MAX_LENGTH; ++len, step <<= 1) {
for (; count[len] > 0; --count[len]) {
if ((key & mask) !== low) {
table += table_size
table_bits = nextTableBitSize(count, len, root_bits)
table_size = 1 << table_bits
total_size += table_size
low = key & mask
root_table[start_table + low] = new HuffmanCode(table_bits + root_bits & 0xff, table - start_table - low & 0xffff)
}
const code = new HuffmanCode(len - root_bits & 0xff, sorted[symbol++] & 0xffff)
replicateValue(root_table, table + (key >> root_bits), step, table_size, code)
key = getNextKey(key, len)
}
}
return total_size
}
/**
* @typedef {import('./brotliBitReader.js').default} BrotliBitReader
* @param {number} alphabet_size
* @param {HuffmanCode[]} tables
* @param {number} table
* @param {BrotliBitReader} br
* @returns {number}
*/
export function readHuffmanCode(alphabet_size, tables, table, br) {
const code_lengths = new Uint8Array(alphabet_size)
br.readMoreInput()
/* simple_code_or_skip is used as follows:
1 for simple code;
0 for no skipping, 2 skips 2 code lengths, 3 skips 3 code lengths */
const simple_code_or_skip = br.readBits(2)
if (simple_code_or_skip === 1) {
/* Read symbols, codes & code lengths directly. */
let max_bits_counter = alphabet_size - 1
let max_bits = 0
const symbols = new Int32Array(4)
const num_symbols = br.readBits(2) + 1
while (max_bits_counter) {
max_bits_counter >>= 1
max_bits++
}
for (let i = 0; i < num_symbols; i++) {
symbols[i] = br.readBits(max_bits) % alphabet_size
code_lengths[symbols[i]] = 2
}
code_lengths[symbols[0]] = 1
switch (num_symbols) {
case 1:
break
case 3:
if (symbols[0] === symbols[1] ||
symbols[0] === symbols[2] ||
symbols[1] === symbols[2]) {
throw new Error('[ReadHuffmanCode] invalid symbols')
}
break
case 2:
if (symbols[0] === symbols[1]) {
throw new Error('[ReadHuffmanCode] invalid symbols')
}
code_lengths[symbols[1]] = 1
break
case 4:
if (symbols[0] === symbols[1] ||
symbols[0] === symbols[2] ||
symbols[0] === symbols[3] ||
symbols[1] === symbols[2] ||
symbols[1] === symbols[3] ||
symbols[2] === symbols[3]) {
throw new Error('[ReadHuffmanCode] invalid symbols')
}
if (br.readBits(1)) {
code_lengths[symbols[2]] = 3
code_lengths[symbols[3]] = 3
} else {
code_lengths[symbols[0]] = 2
}
break
}
} else { /* Decode Huffman-coded code lengths. */
const code_length_code_lengths = new Uint8Array(CODE_LENGTH_CODES)
let space = 32
let num_codes = 0
/* Static Huffman code for the code length code lengths */
const huff = [
new HuffmanCode(2, 0), new HuffmanCode(2, 4), new HuffmanCode(2, 3), new HuffmanCode(3, 2),
new HuffmanCode(2, 0), new HuffmanCode(2, 4), new HuffmanCode(2, 3), new HuffmanCode(4, 1),
new HuffmanCode(2, 0), new HuffmanCode(2, 4), new HuffmanCode(2, 3), new HuffmanCode(3, 2),
new HuffmanCode(2, 0), new HuffmanCode(2, 4), new HuffmanCode(2, 3), new HuffmanCode(4, 5),
]
for (let i = simple_code_or_skip; i < CODE_LENGTH_CODES && space > 0; i++) {
const code_len_idx = kCodeLengthCodeOrder[i]
let p = 0
br.fillBitWindow()
p += br.val_ >>> br.bit_pos_ & 15
br.bit_pos_ += huff[p].bits
const v = huff[p].value
code_length_code_lengths[code_len_idx] = v
if (v !== 0) {
space -= 32 >> v
num_codes++
}
}
if (!(num_codes === 1 || space === 0))
throw new Error('[ReadHuffmanCode] invalid num_codes or space')
readHuffmanCodeLengths(code_length_code_lengths, alphabet_size, code_lengths, br)
}
const table_size = buildHuffmanTable(tables, table, HUFFMAN_TABLE_BITS, code_lengths, alphabet_size)
if (!table_size) throw new Error('brotli BuildHuffmanTable failed')
return table_size
}
/**
* Decodes the next Huffman code from bit-stream.
* @param {HuffmanCode[]} table
* @param {number} index
* @param {BrotliBitReader} br
* @returns {number}
*/
export function readSymbol(table, index, br) {
br.fillBitWindow()
index += br.val_ >>> br.bit_pos_ & HUFFMAN_TABLE_MASK
const nbits = table[index].bits - HUFFMAN_TABLE_BITS
if (nbits > 0) {
br.bit_pos_ += HUFFMAN_TABLE_BITS
index += table[index].value
index += br.val_ >>> br.bit_pos_ & (1 << nbits) - 1
}
br.bit_pos_ += table[index].bits
return table[index].value
}
/**
* @param {Uint8Array} code_length_code_lengths
* @param {number} num_symbols
* @param {Uint8Array} code_lengths
* @param {BrotliBitReader} br
*/
function readHuffmanCodeLengths(code_length_code_lengths, num_symbols, code_lengths, br) {
let symbol = 0
let prev_code_len = kDefaultCodeLength
let repeat = 0
let repeat_code_len = 0
let space = 32768
const table = []
for (let i = 0; i < 32; i++)
table.push(new HuffmanCode(0, 0))
buildHuffmanTable(table, 0, 5, code_length_code_lengths, CODE_LENGTH_CODES)
while (symbol < num_symbols && space > 0) {
let p = 0
br.readMoreInput()
br.fillBitWindow()
p += br.val_ >>> br.bit_pos_ & 31
br.bit_pos_ += table[p].bits
const code_len = table[p].value & 0xff
if (code_len < kCodeLengthRepeatCode) {
repeat = 0
code_lengths[symbol++] = code_len
if (code_len !== 0) {
prev_code_len = code_len
space -= 32768 >> code_len
}
} else {
const extra_bits = code_len - 14
let new_len = 0
if (code_len === kCodeLengthRepeatCode) {
new_len = prev_code_len
}
if (repeat_code_len !== new_len) {
repeat = 0
repeat_code_len = new_len
}
const old_repeat = repeat
if (repeat > 0) {
repeat -= 2
repeat <<= extra_bits
}
repeat += br.readBits(extra_bits) + 3
const repeat_delta = repeat - old_repeat
if (symbol + repeat_delta > num_symbols) {
throw new Error('[ReadHuffmanCodeLengths] symbol + repeat_delta > num_symbols')
}
for (let x = 0; x < repeat_delta; x++)
code_lengths[symbol + x] = repeat_code_len
symbol += repeat_delta
if (repeat_code_len !== 0) {
space -= repeat_delta << 15 - repeat_code_len
}
}
}
if (space !== 0) {
throw new Error('[ReadHuffmanCodeLengths] space = ' + space)
}
for (; symbol < num_symbols; symbol++)
code_lengths[symbol] = 0
}

64
src/brotliPrefix.js Normal file

@ -0,0 +1,64 @@
/* Copyright 2013 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Lookup tables to map prefix codes to value ranges. This is used during
decoding of the block lengths, literal insertion lengths and copy lengths.
*/
/**
* Represents the range of values belonging to a prefix code:
* [offset, offset + 2^nbits)
* @param {number} offset
* @param {number} nbits
*/
function PrefixCodeRange(offset, nbits) {
this.offset = offset
this.nbits = nbits
}
export const kBlockLengthPrefixCode = [
new PrefixCodeRange(1, 2), new PrefixCodeRange(5, 2), new PrefixCodeRange(9, 2), new PrefixCodeRange(13, 2),
new PrefixCodeRange(17, 3), new PrefixCodeRange(25, 3), new PrefixCodeRange(33, 3), new PrefixCodeRange(41, 3),
new PrefixCodeRange(49, 4), new PrefixCodeRange(65, 4), new PrefixCodeRange(81, 4), new PrefixCodeRange(97, 4),
new PrefixCodeRange(113, 5), new PrefixCodeRange(145, 5), new PrefixCodeRange(177, 5), new PrefixCodeRange(209, 5),
new PrefixCodeRange(241, 6), new PrefixCodeRange(305, 6), new PrefixCodeRange(369, 7), new PrefixCodeRange(497, 8),
new PrefixCodeRange(753, 9), new PrefixCodeRange(1265, 10), new PrefixCodeRange(2289, 11), new PrefixCodeRange(4337, 12),
new PrefixCodeRange(8433, 13), new PrefixCodeRange(16625, 24),
]
export const kInsertLengthPrefixCode = [
new PrefixCodeRange(0, 0), new PrefixCodeRange(1, 0), new PrefixCodeRange(2, 0), new PrefixCodeRange(3, 0),
new PrefixCodeRange(4, 0), new PrefixCodeRange(5, 0), new PrefixCodeRange(6, 1), new PrefixCodeRange(8, 1),
new PrefixCodeRange(10, 2), new PrefixCodeRange(14, 2), new PrefixCodeRange(18, 3), new PrefixCodeRange(26, 3),
new PrefixCodeRange(34, 4), new PrefixCodeRange(50, 4), new PrefixCodeRange(66, 5), new PrefixCodeRange(98, 5),
new PrefixCodeRange(130, 6), new PrefixCodeRange(194, 7), new PrefixCodeRange(322, 8), new PrefixCodeRange(578, 9),
new PrefixCodeRange(1090, 10), new PrefixCodeRange(2114, 12), new PrefixCodeRange(6210, 14), new PrefixCodeRange(22594, 24),
]
export const kCopyLengthPrefixCode = [
new PrefixCodeRange(2, 0), new PrefixCodeRange(3, 0), new PrefixCodeRange(4, 0), new PrefixCodeRange(5, 0),
new PrefixCodeRange(6, 0), new PrefixCodeRange(7, 0), new PrefixCodeRange(8, 0), new PrefixCodeRange(9, 0),
new PrefixCodeRange(10, 1), new PrefixCodeRange(12, 1), new PrefixCodeRange(14, 2), new PrefixCodeRange(18, 2),
new PrefixCodeRange(22, 3), new PrefixCodeRange(30, 3), new PrefixCodeRange(38, 4), new PrefixCodeRange(54, 4),
new PrefixCodeRange(70, 5), new PrefixCodeRange(102, 5), new PrefixCodeRange(134, 6), new PrefixCodeRange(198, 7),
new PrefixCodeRange(326, 8), new PrefixCodeRange(582, 9), new PrefixCodeRange(1094, 10), new PrefixCodeRange(2118, 24),
]
export const kInsertRangeLut = [
0, 0, 8, 8, 0, 16, 8, 16, 16,
]
export const kCopyRangeLut = [
0, 8, 0, 8, 16, 0, 16, 8, 16,
]

46
src/brotliStreams.js Normal file

@ -0,0 +1,46 @@
/**
* @param {Uint8Array} buffer
*/
export function BrotliInput(buffer) {
this.buffer = buffer
this.pos = 0
}
/**
* @param {Uint8Array} buf
* @param {number} i
* @param {number} count
* @returns {number}
*/
BrotliInput.prototype.read = function(buf, i, count) {
if (this.pos + count > this.buffer.length) {
count = this.buffer.length - this.pos
}
for (let p = 0; p < count; p++)
buf[i + p] = this.buffer[this.pos + p]
this.pos += count
return count
}
/**
* @param {Uint8Array} buf
*/
export function BrotliOutput(buf) {
this.buffer = buf
this.pos = 0
}
/**
* @param {Uint8Array} buf
* @param {number} count
* @returns {number}
*/
BrotliOutput.prototype.write = function(buf, count) {
if (this.pos + count > this.buffer.length) throw new Error('brotli output buffer is not large enough')
this.buffer.set(buf.subarray(0, count), this.pos)
this.pos += count
return count
}

256
src/brotliTransform.js Normal file

@ -0,0 +1,256 @@
/* Copyright 2013 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Transformations on dictionary words.
*/
import { getDictionary } from './brotliDictionary.js'
const kIdentity = 0
const kOmitLast1 = 1
const kOmitLast2 = 2
const kOmitLast3 = 3
const kOmitLast4 = 4
const kOmitLast5 = 5
const kOmitLast6 = 6
const kOmitLast7 = 7
const kOmitLast8 = 8
const kOmitLast9 = 9
const kUppercaseFirst = 10
const kUppercaseAll = 11
const kOmitFirst1 = 12
const kOmitFirst2 = 13
const kOmitFirst3 = 14
const kOmitFirst4 = 15
const kOmitFirst5 = 16
const kOmitFirst6 = 17
const kOmitFirst7 = 18
// const kOmitFirst8 = 19
const kOmitFirst9 = 20
/**
* @param {string} prefix
* @param {number} transform
* @param {string} suffix
*/
function Transform(prefix, transform, suffix) {
this.prefix = new Uint8Array(prefix.length)
this.transform = transform
this.suffix = new Uint8Array(suffix.length)
for (let i = 0; i < prefix.length; i++) this.prefix[i] = prefix.charCodeAt(i)
for (let i = 0; i < suffix.length; i++) this.suffix[i] = suffix.charCodeAt(i)
}
export const kTransforms = [
new Transform( '', kIdentity, '' ),
new Transform( '', kIdentity, ' ' ),
new Transform( ' ', kIdentity, ' ' ),
new Transform( '', kOmitFirst1, '' ),
new Transform( '', kUppercaseFirst, ' ' ),
new Transform( '', kIdentity, ' the ' ),
new Transform( ' ', kIdentity, '' ),
new Transform( 's ', kIdentity, ' ' ),
new Transform( '', kIdentity, ' of ' ),
new Transform( '', kUppercaseFirst, '' ),
new Transform( '', kIdentity, ' and ' ),
new Transform( '', kOmitFirst2, '' ),
new Transform( '', kOmitLast1, '' ),
new Transform( ', ', kIdentity, ' ' ),
new Transform( '', kIdentity, ', ' ),
new Transform( ' ', kUppercaseFirst, ' ' ),
new Transform( '', kIdentity, ' in ' ),
new Transform( '', kIdentity, ' to ' ),
new Transform( 'e ', kIdentity, ' ' ),
new Transform( '', kIdentity, '"' ),
new Transform( '', kIdentity, '.' ),
new Transform( '', kIdentity, '">' ),
new Transform( '', kIdentity, '\n' ),
new Transform( '', kOmitLast3, '' ),
new Transform( '', kIdentity, ']' ),
new Transform( '', kIdentity, ' for ' ),
new Transform( '', kOmitFirst3, '' ),
new Transform( '', kOmitLast2, '' ),
new Transform( '', kIdentity, ' a ' ),
new Transform( '', kIdentity, ' that ' ),
new Transform( ' ', kUppercaseFirst, '' ),
new Transform( '', kIdentity, '. ' ),
new Transform( '.', kIdentity, '' ),
new Transform( ' ', kIdentity, ', ' ),
new Transform( '', kOmitFirst4, '' ),
new Transform( '', kIdentity, ' with ' ),
new Transform( '', kIdentity, '\'' ),
new Transform( '', kIdentity, ' from ' ),
new Transform( '', kIdentity, ' by ' ),
new Transform( '', kOmitFirst5, '' ),
new Transform( '', kOmitFirst6, '' ),
new Transform( ' the ', kIdentity, '' ),
new Transform( '', kOmitLast4, '' ),
new Transform( '', kIdentity, '. The ' ),
new Transform( '', kUppercaseAll, '' ),
new Transform( '', kIdentity, ' on ' ),
new Transform( '', kIdentity, ' as ' ),
new Transform( '', kIdentity, ' is ' ),
new Transform( '', kOmitLast7, '' ),
new Transform( '', kOmitLast1, 'ing ' ),
new Transform( '', kIdentity, '\n\t' ),
new Transform( '', kIdentity, ':' ),
new Transform( ' ', kIdentity, '. ' ),
new Transform( '', kIdentity, 'ed ' ),
new Transform( '', kOmitFirst9, '' ),
new Transform( '', kOmitFirst7, '' ),
new Transform( '', kOmitLast6, '' ),
new Transform( '', kIdentity, '(' ),
new Transform( '', kUppercaseFirst, ', ' ),
new Transform( '', kOmitLast8, '' ),
new Transform( '', kIdentity, ' at ' ),
new Transform( '', kIdentity, 'ly ' ),
new Transform( ' the ', kIdentity, ' of ' ),
new Transform( '', kOmitLast5, '' ),
new Transform( '', kOmitLast9, '' ),
new Transform( ' ', kUppercaseFirst, ', ' ),
new Transform( '', kUppercaseFirst, '"' ),
new Transform( '.', kIdentity, '(' ),
new Transform( '', kUppercaseAll, ' ' ),
new Transform( '', kUppercaseFirst, '">' ),
new Transform( '', kIdentity, '="' ),
new Transform( ' ', kIdentity, '.' ),
new Transform( '.com/', kIdentity, '' ),
new Transform( ' the ', kIdentity, ' of the ' ),
new Transform( '', kUppercaseFirst, '\'' ),
new Transform( '', kIdentity, '. This ' ),
new Transform( '', kIdentity, ',' ),
new Transform( '.', kIdentity, ' ' ),
new Transform( '', kUppercaseFirst, '(' ),
new Transform( '', kUppercaseFirst, '.' ),
new Transform( '', kIdentity, ' not ' ),
new Transform( ' ', kIdentity, '="' ),
new Transform( '', kIdentity, 'er ' ),
new Transform( ' ', kUppercaseAll, ' ' ),
new Transform( '', kIdentity, 'al ' ),
new Transform( ' ', kUppercaseAll, '' ),
new Transform( '', kIdentity, '=\'' ),
new Transform( '', kUppercaseAll, '"' ),
new Transform( '', kUppercaseFirst, '. ' ),
new Transform( ' ', kIdentity, '(' ),
new Transform( '', kIdentity, 'ful ' ),
new Transform( ' ', kUppercaseFirst, '. ' ),
new Transform( '', kIdentity, 'ive ' ),
new Transform( '', kIdentity, 'less ' ),
new Transform( '', kUppercaseAll, '\'' ),
new Transform( '', kIdentity, 'est ' ),
new Transform( ' ', kUppercaseFirst, '.' ),
new Transform( '', kUppercaseAll, '">' ),
new Transform( ' ', kIdentity, '=\'' ),
new Transform( '', kUppercaseFirst, ',' ),
new Transform( '', kIdentity, 'ize ' ),
new Transform( '', kUppercaseAll, '.' ),
new Transform( '\xc2\xa0', kIdentity, '' ),
new Transform( ' ', kIdentity, ',' ),
new Transform( '', kUppercaseFirst, '="' ),
new Transform( '', kUppercaseAll, '="' ),
new Transform( '', kIdentity, 'ous ' ),
new Transform( '', kUppercaseAll, ', ' ),
new Transform( '', kUppercaseFirst, '=\'' ),
new Transform( ' ', kUppercaseFirst, ',' ),
new Transform( ' ', kUppercaseAll, '="' ),
new Transform( ' ', kUppercaseAll, ', ' ),
new Transform( '', kUppercaseAll, ',' ),
new Transform( '', kUppercaseAll, '(' ),
new Transform( '', kUppercaseAll, '. ' ),
new Transform( ' ', kUppercaseAll, '.' ),
new Transform( '', kUppercaseAll, '=\'' ),
new Transform( ' ', kUppercaseAll, '. ' ),
new Transform( ' ', kUppercaseFirst, '="' ),
new Transform( ' ', kUppercaseAll, '=\'' ),
new Transform( ' ', kUppercaseFirst, '=\'' ),
]
export const kNumTransforms = kTransforms.length
/**
* @param {Uint8Array} p
* @param {number} i
* @returns {number}
*/
function ToUpperCase(p, i) {
if (p[i] < 0xc0) {
if (p[i] >= 97 && p[i] <= 122) {
p[i] ^= 32
}
return 1
}
/* An overly simplified uppercasing model for utf-8. */
if (p[i] < 0xe0) {
p[i + 1] ^= 32
return 2
}
/* An arbitrary transform for three byte characters. */
p[i + 2] ^= 5
return 3
}
/**
* @param {Uint8Array} dst
* @param {number} idx
* @param {number} word
* @param {number} len
* @param {number} transform
* @returns {number}
*/
export function transformDictionaryWord(dst, idx, word, len, transform) {
const dictionary = getDictionary()
const { prefix } = kTransforms[transform]
const { suffix } = kTransforms[transform]
const t = kTransforms[transform].transform
let skip = t < kOmitFirst1 ? 0 : t - (kOmitFirst1 - 1)
const start_idx = idx
if (skip > len) skip = len
let prefix_pos = 0
while (prefix_pos < prefix.length) {
dst[idx++] = prefix[prefix_pos++]
}
word += skip
len -= skip
if (t <= kOmitLast9) len -= t
for (let i = 0; i < len; i++) {
dst[idx++] = dictionary[word + i]
}
let uppercase = idx - len
if (t === kUppercaseFirst) {
ToUpperCase(dst, uppercase)
} else if (t === kUppercaseAll) {
while (len > 0) {
const step = ToUpperCase(dst, uppercase)
uppercase += step
len -= step
}
}
let suffix_pos = 0
while (suffix_pos < suffix.length) {
dst[idx++] = suffix[suffix_pos++]
}
return idx - start_idx
}

4
src/index.d.ts vendored

@ -13,3 +13,7 @@ export type Compressors = {
}
export const compressors: Compressors
declare module 'brotli/decompress' {
export default function(input: Buffer, outputLength: number): Buffer
}

@ -1,6 +1,6 @@
import BROTLI from 'brotli/decompress.js'
import { decompress as ZSTD } from 'fzstd'
import { snappyUncompressor } from 'hysnappy'
import { BROTLI } from './brotli.js'
import { gunzip } from './gzip.js'
import { LZ4, LZ4_RAW } from './lz4.js'
@ -14,7 +14,6 @@ export const compressors = {
gunzip(input, out)
return out
},
// @ts-expect-error brotli expects Buffer but Uint8Array works
BROTLI,
ZSTD: input => ZSTD(input),
LZ4,