@gmod/cram
Version:
read CRAM files with pure Javascript
118 lines • 4.89 kB
JavaScript
import CramCodec from "./_base.js";
import { getBits } from "./getBits.js";
import { CramMalformedError } from "../../errors.js";
function numberOfSetBits(ii) {
let i = (ii - (ii >> 1)) & 0x55555555;
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (((i + (i >> 4)) & 0x0f0f0f0f) * 0x01010101) >> 24;
}
export default class HuffmanIntCodec extends CramCodec {
constructor(parameters, dataType) {
super(parameters, dataType);
this.codes = {};
this.codeBook = {};
this.sortedCodes = [];
this.sortedValuesByBitCode = [];
this.sortedBitCodes = [];
this.sortedBitLengthsByBitCode = [];
this.bitCodeToValue = [];
if (!['byte', 'int'].includes(this.dataType)) {
throw new TypeError(`${this.dataType} decoding not yet implemented by HUFFMAN_INT codec`);
}
this.buildCodeBook();
this.buildCodes();
this.buildCaches();
// if this is a degenerate zero-length huffman code, special-case the
// decoding
if (this.sortedCodes[0].bitLength === 0) {
this._decode = this._decodeZeroLengthCode;
}
}
buildCodeBook() {
// parse the parameters together into a `codes` data structure
let codes = new Array(this.parameters.numCodes);
for (let i = 0; i < this.parameters.numCodes; i++) {
codes[i] = {
symbol: this.parameters.symbols[i],
bitLength: this.parameters.bitLengths[i],
};
}
// sort the codes by bit length and symbol value
codes = codes.sort((a, b) => a.bitLength - b.bitLength || a.symbol - b.symbol);
this.codeBook = {};
codes.forEach(code => {
if (!this.codeBook[code.bitLength]) {
this.codeBook[code.bitLength] = [];
}
this.codeBook[code.bitLength].push(code.symbol);
});
}
buildCodes() {
this.codes = {}; /* new TreeMap<Integer, HuffmanBitCode>(); */
let codeLength = 0;
let codeValue = -1;
Object.entries(this.codeBook).forEach(([bitLength, symbols]) => {
const bitLengthInt = Number.parseInt(bitLength, 10);
symbols.forEach(symbol => {
const code = {
bitLength: bitLengthInt,
value: symbol,
bitCode: 0,
};
codeValue = codeValue + 1;
const delta = bitLengthInt - codeLength; // new length?
codeValue = codeValue << delta; // pad with 0's
code.bitCode = codeValue; // calculated: huffman code
codeLength = codeLength + delta; // adjust current code length
if (numberOfSetBits(codeValue) > bitLengthInt) {
throw new CramMalformedError('Symbol out of range');
}
this.codes[symbol] = code;
});
});
}
buildCaches() {
this.sortedCodes = Object.values(this.codes).sort((a, b) => a.bitLength - b.bitLength || a.bitCode - b.bitCode);
this.sortedValuesByBitCode = this.sortedCodes.map(c => c.value);
this.sortedBitCodes = this.sortedCodes.map(c => c.bitCode);
this.sortedBitLengthsByBitCode = this.sortedCodes.map(c => c.bitLength);
const maxBitCode = Math.max(...this.sortedBitCodes);
this.bitCodeToValue = new Array(maxBitCode + 1).fill(-1);
for (let i = 0; i < this.sortedBitCodes.length; i += 1) {
this.bitCodeToValue[this.sortedCodes[i].bitCode] = i;
}
}
decode(slice, coreDataBlock, _blocksByContentId, cursors) {
return this._decode(slice, coreDataBlock, cursors.coreBlock);
}
// _decodeNull() {
// return -1
// }
// the special case for zero-length codes
_decodeZeroLengthCode() {
return this.sortedCodes[0].value;
}
_decode(_slice, coreDataBlock, coreCursor) {
const input = coreDataBlock.content;
let prevLen = 0;
let bits = 0;
for (let i = 0; i < this.sortedCodes.length; i += 1) {
const length = this.sortedCodes[i].bitLength;
bits <<= length - prevLen;
bits |= getBits(input, coreCursor, length - prevLen);
prevLen = length;
{
const index = this.bitCodeToValue[bits];
if (index > -1 && this.sortedBitLengthsByBitCode[index] === length) {
return this.sortedValuesByBitCode[index];
}
for (let j = i; this.sortedCodes[j + 1].bitLength === length &&
j < this.sortedCodes.length; j += 1) {
i += 1;
}
}
}
throw new CramMalformedError('Huffman symbol not found.');
}
}
//# sourceMappingURL=huffman.js.map