From a457564ff01359a7f89eb2b132613e7db8d281eb Mon Sep 17 00:00:00 2001 From: codedread Date: Wed, 1 Apr 2020 23:08:09 -0700 Subject: [PATCH] Start on Issue #16. Make BitStream a module, but provide a Web Worker version of BitStream via a build step --- archive/unrar.js | 225 ++++++++-------- archive/unzip.js | 134 +++++----- build/Makefile | 13 + build/io/Makefile | 25 ++ build/io/bitstream-def.js | 287 ++++++++++++++++++++ io/bitstream-worker.js | 291 +++++++++++++++++++++ io/bitstream.js | 493 ++++++++++++++++++----------------- tests/io-bitstream-test.html | 2 +- 8 files changed, 1043 insertions(+), 427 deletions(-) create mode 100644 build/Makefile create mode 100644 build/io/Makefile create mode 100644 build/io/bitstream-def.js create mode 100644 io/bitstream-worker.js diff --git a/archive/unrar.js b/archive/unrar.js index b0c9788..24552bc 100644 --- a/archive/unrar.js +++ b/archive/unrar.js @@ -12,7 +12,7 @@ // present. // This file expects to be invoked as a Worker (see onmessage below). -importScripts('../io/bitstream.js'); +importScripts('../io/bitstream-worker.js'); importScripts('../io/bytestream.js'); importScripts('../io/bytebuffer.js'); importScripts('archive.js'); @@ -40,45 +40,45 @@ let totalUncompressedBytesInArchive = 0; let totalFilesInArchive = 0; // Helper functions. -const info = function(str) { +const info = function (str) { postMessage(new bitjs.archive.UnarchiveInfoEvent(str)); }; -const err = function(str) { +const err = function (str) { postMessage(new bitjs.archive.UnarchiveErrorEvent(str)); }; -const postProgress = function() { +const postProgress = function () { postMessage(new bitjs.archive.UnarchiveProgressEvent( - currentFilename, - currentFileNumber, - currentBytesUnarchivedInFile, - currentBytesUnarchived, - totalUncompressedBytesInArchive, - totalFilesInArchive, - parseInt(bytestream.getNumBytesRead(), 10), + currentFilename, + currentFileNumber, + currentBytesUnarchivedInFile, + currentBytesUnarchived, + totalUncompressedBytesInArchive, + totalFilesInArchive, + parseInt(bytestream.getNumBytesRead(), 10), )); }; // shows a byte value as its hex representation const nibble = '0123456789ABCDEF'; -const byteValueToHexString = function(num) { - return nibble[num>>4] + nibble[num&0xF]; +const byteValueToHexString = function (num) { + return nibble[num >> 4] + nibble[num & 0xF]; }; -const twoByteValueToHexString = function(num) { - return nibble[(num>>12)&0xF] + nibble[(num>>8)&0xF] + nibble[(num>>4)&0xF] + nibble[num&0xF]; +const twoByteValueToHexString = function (num) { + return nibble[(num >> 12) & 0xF] + nibble[(num >> 8) & 0xF] + nibble[(num >> 4) & 0xF] + nibble[num & 0xF]; }; // Volume Types -const MARK_HEAD = 0x72; -const MAIN_HEAD = 0x73; -const FILE_HEAD = 0x74; -const COMM_HEAD = 0x75; -const AV_HEAD = 0x76; -const SUB_HEAD = 0x77; -const PROTECT_HEAD = 0x78; -const SIGN_HEAD = 0x79; -const NEWSUB_HEAD = 0x7a; -const ENDARC_HEAD = 0x7b; +const MARK_HEAD = 0x72; +const MAIN_HEAD = 0x73; +const FILE_HEAD = 0x74; +const COMM_HEAD = 0x75; +const AV_HEAD = 0x76; +const SUB_HEAD = 0x77; +const PROTECT_HEAD = 0x78; +const SIGN_HEAD = 0x79; +const NEWSUB_HEAD = 0x7a; +const ENDARC_HEAD = 0x7b; // ============================================================================================== // @@ -102,7 +102,7 @@ class RarVolumeHeader { this.flags = {}; this.flags.value = bstream.readNumber(2); const flagsValue = this.flags.value; - + switch (this.headType) { case MAIN_HEAD: this.flags.MHD_VOLUME = !!(flagsValue & 0x01); @@ -164,7 +164,7 @@ class RarVolumeHeader { this.nameSize = bstream.readNumber(2); this.fileAttr = bstream.readNumber(4); headBytesRead += 25; - + if (this.flags.LHD_LARGE) { //info('Warning: Reading in LHD_LARGE 64-bit size values'); this.HighPackSize = bstream.readNumber(4); @@ -253,8 +253,8 @@ class RarVolumeHeader { info(' headSize=' + this.headSize); if (this.headType == FILE_HEAD) { info('Found FILE_HEAD with packSize=' + this.packSize + ', unpackedSize= ' + - this.unpackedSize + ', hostOS=' + this.hostOS + ', unpVer=' + this.unpVer + ', method=' + - this.method + ', filename=' + this.filename); + this.unpackedSize + ', hostOS=' + this.hostOS + ', unpVer=' + this.unpVer + ', method=' + + this.method + ', filename=' + this.filename); } } } @@ -262,21 +262,21 @@ class RarVolumeHeader { const BLOCK_LZ = 0; const BLOCK_PPM = 1; -const rLDecode = [0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224]; -const rLBits = [0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5]; -const rDBitLengthCounts = [4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,14,0,12]; -const rSDDecode = [0,4,8,16,32,64,128,192]; -const rSDBits = [2,2,3, 4, 5, 6, 6, 6]; - +const rLDecode = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16, 20, 24, 28, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224]; +const rLBits = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5]; +const rDBitLengthCounts = [4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 14, 0, 12]; +const rSDDecode = [0, 4, 8, 16, 32, 64, 128, 192]; +const rSDBits = [2, 2, 3, 4, 5, 6, 6, 6]; + const rDDecode = [0, 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, - 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, - 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304, - 131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824, - 655360, 720896, 786432, 851968, 917504, 983040]; + 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024, 1536, 2048, 3072, + 4096, 6144, 8192, 12288, 16384, 24576, 32768, 49152, 65536, 98304, + 131072, 196608, 262144, 327680, 393216, 458752, 524288, 589824, + 655360, 720896, 786432, 851968, 917504, 983040]; const rDBits = [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, - 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, - 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]; + 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, + 15, 15, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16]; const rLOW_DIST_REP_COUNT = 16; @@ -285,7 +285,7 @@ const rDC = 60; const rLDC = 17; const rRC = 28; const rBC = 20; -const rHUFF_TABLE_SIZE = (rNC+rDC+rRC+rLDC); +const rHUFF_TABLE_SIZE = (rNC + rDC + rRC + rLDC); const UnpOldTable = new Array(rHUFF_TABLE_SIZE); @@ -353,13 +353,13 @@ function RarReadTables(bstream) { const Table = new Array(rHUFF_TABLE_SIZE); // before we start anything we need to get byte-aligned - bstream.readBits( (8 - bstream.bitPtr) & 0x7 ); - + bstream.readBits((8 - bstream.bitPtr) & 0x7); + if (bstream.readBits(1)) { info('Error! PPM not implemented yet'); return; } - + if (!bstream.readBits(1)) { //discard old table for (let i = UnpOldTable.length; i--;) { UnpOldTable[i] = 0; @@ -384,11 +384,11 @@ function RarReadTables(bstream) { BitLength[I] = Length; } } - + // now all 20 bit lengths are obtained, we construct the Huffman Table: RarMakeDecodeTables(BitLength, 0, BD, rBC); - + const TableSize = rHUFF_TABLE_SIZE; for (let i = 0; i < TableSize;) { const num = RarDecodeNumber(bstream, BD); @@ -410,12 +410,12 @@ function RarReadTables(bstream) { } } } - + RarMakeDecodeTables(Table, 0, LD, rNC); RarMakeDecodeTables(Table, rNC, DD, rDC); RarMakeDecodeTables(Table, rNC + rDC, LDD, rLDC); - RarMakeDecodeTables(Table, rNC + rDC + rLDC, RD, rRC); - + RarMakeDecodeTables(Table, rNC + rDC + rLDC, RD, rRC); + for (let i = UnpOldTable.length; i--;) { UnpOldTable[i] = Table[i]; } @@ -429,24 +429,24 @@ function RarDecodeNumber(bstream, dec) { const DecodeNum = dec.DecodeNum; const bitField = bstream.getBits() & 0xfffe; //some sort of rolled out binary search - const bits = ((bitField < DecodeLen[8])? - ((bitField < DecodeLen[4])? - ((bitField < DecodeLen[2])? - ((bitField < DecodeLen[1])?1:2) - :((bitField < DecodeLen[3])?3:4)) - :(bitField < DecodeLen[6])? - ((bitField < DecodeLen[5])?5:6) - :((bitField < DecodeLen[7])?7:8)) - :((bitField < DecodeLen[12])? - ((bitField < DecodeLen[10])? - ((bitField < DecodeLen[9])?9:10) - :((bitField < DecodeLen[11])?11:12)) - :(bitField < DecodeLen[14])? - ((bitField < DecodeLen[13])?13:14) - :15)); + const bits = ((bitField < DecodeLen[8]) ? + ((bitField < DecodeLen[4]) ? + ((bitField < DecodeLen[2]) ? + ((bitField < DecodeLen[1]) ? 1 : 2) + : ((bitField < DecodeLen[3]) ? 3 : 4)) + : (bitField < DecodeLen[6]) ? + ((bitField < DecodeLen[5]) ? 5 : 6) + : ((bitField < DecodeLen[7]) ? 7 : 8)) + : ((bitField < DecodeLen[12]) ? + ((bitField < DecodeLen[10]) ? + ((bitField < DecodeLen[9]) ? 9 : 10) + : ((bitField < DecodeLen[11]) ? 11 : 12)) + : (bitField < DecodeLen[14]) ? + ((bitField < DecodeLen[13]) ? 13 : 14) + : 15)); bstream.readBits(bits); - const N = DecodePos[bits] + ((bitField - DecodeLen[bits -1]) >>> (16 - bits)); - + const N = DecodePos[bits] + ((bitField - DecodeLen[bits - 1]) >>> (16 - bits)); + return DecodeNum[N]; } @@ -455,8 +455,8 @@ function RarMakeDecodeTables(BitLength, offset, dec, size) { const DecodeLen = dec.DecodeLen; const DecodePos = dec.DecodePos; const DecodeNum = dec.DecodeNum; - const LenCount = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; - const TmpPos = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + const LenCount = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + const TmpPos = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; let N = 0; let M = 0; @@ -470,20 +470,20 @@ function RarMakeDecodeTables(BitLength, offset, dec, size) { TmpPos[0] = 0; DecodePos[0] = 0; DecodeLen[0] = 0; - + for (let I = 1; I < 16; ++I) { - N = 2 * (N+LenCount[I]); - M = (N << (15-I)); + N = 2 * (N + LenCount[I]); + M = (N << (15 - I)); if (M > 0xFFFF) { M = 0xFFFF; } DecodeLen[I] = M; - DecodePos[I] = DecodePos[I-1] + LenCount[I-1]; + DecodePos[I] = DecodePos[I - 1] + LenCount[I - 1]; TmpPos[I] = DecodePos[I]; } for (let I = 0; I < size; ++I) { if (BitLength[I + offset] != 0) { - DecodeNum[ TmpPos[ BitLength[offset + I] & 0xF ]++] = I; + DecodeNum[TmpPos[BitLength[offset + I] & 0xF]++] = I; } } @@ -550,7 +550,7 @@ function Unpack20(bstream, Solid) { if (num < 261) { const Distance = rOldDist[(oldDistPtr - (num - 256)) & 3]; const LengthNumber = RarDecodeNumber(bstream, RD); - let Length = rLDecode[LengthNumber] +2; + let Length = rLDecode[LengthNumber] + 2; if ((Bits = rLBits[LengthNumber]) > 0) { Length += bstream.readBits(Bits); } @@ -578,7 +578,7 @@ function Unpack20(bstream, Solid) { RarCopyString(2, Distance); continue; } - + } RarUpdateProgress(); } @@ -650,7 +650,7 @@ function RarReadTables20(bstream) { let lowDistRepCount = 0; let prevLowDist = 0; -let rOldDist = [0,0,0,0]; +let rOldDist = [0, 0, 0, 0]; let lastDist; let lastLength; @@ -767,11 +767,11 @@ function RarAddVMCode(firstByte, vmCode) { stackFilter.BlockLength = RarVM.readData(bstream); } else { stackFilter.BlockLength = filtPos < OldFilterLengths.length - ? OldFilterLengths[filtPos] - : 0; + ? OldFilterLengths[filtPos] + : 0; } stackFilter.NextWindow = (wBuffer.ptr != rBuffer.ptr) && - (((wBuffer.ptr - rBuffer.ptr) & MAXWINMASK) <= blockStart); + (((wBuffer.ptr - rBuffer.ptr) & MAXWINMASK) <= blockStart); OldFilterLengths[filtPos] = stackFilter.BlockLength; @@ -894,36 +894,36 @@ function Unpack29(bstream, Solid) { const DDecode = new Array(rDC); const DBits = new Array(rDC); - + let Dist = 0; let BitLength = 0; let Slot = 0; - - for (let I = 0; I < rDBitLengthCounts.length; I++,BitLength++) { - for (let J = 0; J < rDBitLengthCounts[I]; J++,Slot++,Dist+=(1< 0; I--) { - rOldDist[I] = rOldDist[I-1]; + rOldDist[I] = rOldDist[I - 1]; } rOldDist[0] = Distance; @@ -1076,8 +1076,8 @@ function RarWriteBuf() { parentPrg.GlobalData = new Uint8Array(globalDataLen); } parentPrg.GlobalData.set( - this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen), - VM_FIXEDGLOBALSIZE); + this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen), + VM_FIXEDGLOBALSIZE); } else { parentPrg.GlobalData = new Uint8Array(0); } @@ -1088,7 +1088,7 @@ function RarWriteBuf() { while (i + 1 < PrgStack.length) { const nextFilter = PrgStack[i + 1]; if (nextFilter == null || nextFilter.BlockStart != blockStart || - nextFilter.BlockLength != filteredData.length || nextFilter.NextWindow) { + nextFilter.BlockLength != filteredData.length || nextFilter.NextWindow) { break; } @@ -1115,8 +1115,8 @@ function RarWriteBuf() { innerParentPrg.GlobalData = new Uint8Array(globalDataLen); } innerParentPrg.GlobalData.set( - this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen), - VM_FIXEDGLOBALSIZE); + this.mem_.subarray(VM_FIXEDGLOBALSIZE, VM_FIXEDGLOBALSIZE + globalDataLen), + VM_FIXEDGLOBALSIZE); } else { innerParentPrg.GlobalData = new Uint8Array(0); } @@ -1158,8 +1158,8 @@ function RarWriteBuf() { function RarWriteArea(startPtr, endPtr) { if (endPtr < startPtr) { console.error('endPtr < startPtr, endPtr=' + endPtr + ', startPtr=' + startPtr); -// RarWriteData(startPtr, -(int)StartPtr & MAXWINMASK); -// RarWriteData(0, endPtr); + // RarWriteData(startPtr, -(int)StartPtr & MAXWINMASK); + // RarWriteData(0, endPtr); return; } else if (startPtr < endPtr) { RarWriteData(startPtr, endPtr - startPtr); @@ -1187,8 +1187,7 @@ function RarWriteData(offset, numBytes) { /** * @param {VM_PreparedProgram} prg */ -function RarExecuteCode(prg) -{ +function RarExecuteCode(prg) { if (prg.GlobalData.length > 0) { const writtenFileSize = wBuffer.ptr; prg.InitR[6] = writtenFileSize; @@ -1219,8 +1218,8 @@ function RarInsertLastMatch(length, distance) { } function RarInsertOldDist(distance) { - rOldDist.splice(3,1); - rOldDist.splice(0,0,distance); + rOldDist.splice(3, 1); + rOldDist.splice(0, 0, distance); } /** @@ -1259,7 +1258,7 @@ function unpack(v) { // TODO: implement what happens when unpVer is < 15 const Ver = v.header.unpVer <= 15 ? 15 : v.header.unpVer; const Solid = v.header.flags.LHD_SOLID; - const bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */, v.fileData.byteOffset, v.fileData.byteLength ); + const bstream = new bitjs.io.BitStream(v.fileData.buffer, true /* rtl */, v.fileData.byteOffset, v.fileData.byteLength); rBuffer = new bitjs.io.ByteBuffer(v.header.unpackedSize); @@ -1296,7 +1295,7 @@ class RarLocalFile { constructor(bstream) { this.header = new RarVolumeHeader(bstream); this.filename = this.header.filename; - + if (this.header.headType != FILE_HEAD && this.header.headType != ENDARC_HEAD) { this.isValid = false; info('Error! RAR Volume did not include a FILE_HEAD header '); @@ -1340,10 +1339,10 @@ class RarLocalFile { function unrar_start() { let bstream = bytestream.tee(); const header = new RarVolumeHeader(bstream); - if (header.crc == 0x6152 && - header.headType == 0x72 && - header.flags.value == 0x1A21 && - header.headSize == 7) { + if (header.crc == 0x6152 && + header.headType == 0x72 && + header.flags.value == 0x1A21 && + header.headSize == 7) { if (logToConsole) { info('Found RAR signature'); } @@ -1388,7 +1387,7 @@ function unrar() { } while (localFile.isValid && bstream.getNumBytesLeft() > 0); totalFilesInArchive = allLocalFiles.length; - + postProgress(); bytestream = bstream.tee(); @@ -1396,7 +1395,7 @@ function unrar() { // event.data.file has the first ArrayBuffer. // event.data.bytes has all subsequent ArrayBuffers. -onmessage = function(event) { +onmessage = function (event) { const bytes = event.data.file || event.data.bytes; logToConsole = !!event.data.logToConsole; @@ -1437,7 +1436,7 @@ onmessage = function(event) { } if (unarchiveState === UnarchiveState.UNARCHIVING || - unarchiveState === UnarchiveState.WAITING) { + unarchiveState === UnarchiveState.WAITING) { try { unrar(); unarchiveState = UnarchiveState.FINISHED; diff --git a/archive/unzip.js b/archive/unzip.js index 8f8fc09..cbe06e1 100644 --- a/archive/unzip.js +++ b/archive/unzip.js @@ -13,7 +13,7 @@ */ // This file expects to be invoked as a Worker (see onmessage below). -importScripts('../io/bitstream.js'); +importScripts('../io/bitstream-worker.js'); importScripts('../io/bytebuffer.js'); importScripts('../io/bytestream.js'); importScripts('archive.js'); @@ -40,21 +40,21 @@ let totalUncompressedBytesInArchive = 0; let totalFilesInArchive = 0; // Helper functions. -const info = function(str) { +const info = function (str) { postMessage(new bitjs.archive.UnarchiveInfoEvent(str)); }; -const err = function(str) { +const err = function (str) { postMessage(new bitjs.archive.UnarchiveErrorEvent(str)); }; -const postProgress = function() { +const postProgress = function () { postMessage(new bitjs.archive.UnarchiveProgressEvent( - currentFilename, - currentFileNumber, - currentBytesUnarchivedInFile, - currentBytesUnarchived, - totalUncompressedBytesInArchive, - totalFilesInArchive, - bytestream.getNumBytesRead(), + currentFilename, + currentFileNumber, + currentBytesUnarchivedInFile, + currentBytesUnarchived, + totalUncompressedBytesInArchive, + totalFilesInArchive, + bytestream.getNumBytesRead(), )); }; @@ -66,16 +66,16 @@ const zEndOfCentralDirSignature = 0x06054b50; const zEndOfCentralDirLocatorSignature = 0x07064b50; // mask for getting the Nth bit (zero-based) -const BIT = [ 0x01, 0x02, 0x04, 0x08, - 0x10, 0x20, 0x40, 0x80, - 0x100, 0x200, 0x400, 0x800, - 0x1000, 0x2000, 0x4000, 0x8000]; +const BIT = [0x01, 0x02, 0x04, 0x08, + 0x10, 0x20, 0x40, 0x80, + 0x100, 0x200, 0x400, 0x800, + 0x1000, 0x2000, 0x4000, 0x8000]; class ZipLocalFile { // takes a ByteStream and parses out the local file information constructor(bstream) { - if (typeof bstream != typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function(){}) { + if (typeof bstream != typeof {} || !bstream.readNumber || typeof bstream.readNumber != typeof function () { }) { return null; } @@ -138,9 +138,9 @@ class ZipLocalFile { // determine what kind of compressed data we have and decompress unzip() { // Zip Version 1.0, no compression (store only) - if (this.compressionMethod == 0 ) { + if (this.compressionMethod == 0) { if (logToConsole) { - info("ZIP v"+this.version+", store only: " + this.filename + " (" + this.compressedSize + " bytes)"); + info("ZIP v" + this.version + ", store only: " + this.filename + " (" + this.compressedSize + " bytes)"); } currentBytesUnarchivedInFile = this.compressedSize; currentBytesUnarchived += this.compressedSize; @@ -193,10 +193,10 @@ function getHuffmanCodes(bitLengths) { const next_code = []; let code = 0; for (let bits = 1; bits <= MAX_BITS; ++bits) { - const length = bits-1; + const length = bits - 1; // ensure undefined lengths are zero if (bl_count[length] == undefined) bl_count[length] = 0; - code = (code + bl_count[bits-1]) << 1; + code = (code + bl_count[bits - 1]) << 1; next_code[bits] = code; } @@ -236,18 +236,18 @@ function getHuffmanCodes(bitLengths) { let fixedHCtoLiteral = null; let fixedHCtoDistance = null; function getFixedLiteralTable() { - // create once - if (!fixedHCtoLiteral) { - const bitlengths = new Array(288); - for (let i = 0; i <= 143; ++i) bitlengths[i] = 8; - for (let i = 144; i <= 255; ++i) bitlengths[i] = 9; - for (let i = 256; i <= 279; ++i) bitlengths[i] = 7; - for (let i = 280; i <= 287; ++i) bitlengths[i] = 8; + // create once + if (!fixedHCtoLiteral) { + const bitlengths = new Array(288); + for (let i = 0; i <= 143; ++i) bitlengths[i] = 8; + for (let i = 144; i <= 255; ++i) bitlengths[i] = 9; + for (let i = 256; i <= 279; ++i) bitlengths[i] = 7; + for (let i = 280; i <= 287; ++i) bitlengths[i] = 8; - // get huffman code table - fixedHCtoLiteral = getHuffmanCodes(bitlengths); - } - return fixedHCtoLiteral; + // get huffman code table + fixedHCtoLiteral = getHuffmanCodes(bitlengths); + } + return fixedHCtoLiteral; } function getFixedDistanceTable() { @@ -270,10 +270,10 @@ function decodeSymbol(bstream, hcTable) { let match = false; // loop until we match - for (;;) { + for (; ;) { // read in next bit const bit = bstream.readBits(1); - code = (code<<1) | bit; + code = (code << 1) | bit; ++len; // check against Huffman Code table and break if found @@ -282,7 +282,7 @@ function decodeSymbol(bstream, hcTable) { } if (len > hcTable.maxLength) { err("Bit stream out of sync, didn't find a Huffman Code, length was " + len + - " and table only max code length of " + hcTable.maxLength); + " and table only max code length of " + hcTable.maxLength); break; } } @@ -308,14 +308,14 @@ Code Bits Length(s) Code Bits Lengths Code Bits Length(s) 266 1 13,14 276 3 59-66 */ const LengthLookupTable = [ - [0,3], [0,4], [0,5], [0,6], - [0,7], [0,8], [0,9], [0,10], - [1,11], [1,13], [1,15], [1,17], - [2,19], [2,23], [2,27], [2,31], - [3,35], [3,43], [3,51], [3,59], - [4,67], [4,83], [4,99], [4,115], - [5,131], [5,163], [5,195], [5,227], - [0,258] + [0, 3], [0, 4], [0, 5], [0, 6], + [0, 7], [0, 8], [0, 9], [0, 10], + [1, 11], [1, 13], [1, 15], [1, 17], + [2, 19], [2, 23], [2, 27], [2, 31], + [3, 35], [3, 43], [3, 51], [3, 59], + [4, 67], [4, 83], [4, 99], [4, 115], + [5, 131], [5, 163], [5, 195], [5, 227], + [0, 258] ]; /* @@ -334,20 +334,20 @@ const LengthLookupTable = [ 9 3 25-32 19 8 769-1024 29 13 24577-32768 */ const DistLookupTable = [ - [0,1], [0,2], [0,3], [0,4], - [1,5], [1,7], - [2,9], [2,13], - [3,17], [3,25], - [4,33], [4,49], - [5,65], [5,97], - [6,129], [6,193], - [7,257], [7,385], - [8,513], [8,769], - [9,1025], [9,1537], - [10,2049], [10,3073], - [11,4097], [11,6145], - [12,8193], [12,12289], - [13,16385], [13,24577] + [0, 1], [0, 2], [0, 3], [0, 4], + [1, 5], [1, 7], + [2, 9], [2, 13], + [3, 17], [3, 25], + [4, 33], [4, 49], + [5, 65], [5, 97], + [6, 129], [6, 193], + [7, 257], [7, 385], + [8, 513], [8, 769], + [9, 1025], [9, 1537], + [10, 2049], [10, 3073], + [11, 4097], [11, 6145], + [12, 8193], [12, 12289], + [13, 16385], [13, 24577] ]; function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) { @@ -368,7 +368,7 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) { */ let numSymbols = 0; let blockSize = 0; - for (;;) { + for (; ;) { const symbol = decodeSymbol(bstream, hcLiteralTable); ++numSymbols; if (symbol < 256) { @@ -398,7 +398,7 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) { // loop for each character let ch = buffer.ptr - distance; blockSize += length; - if(length > distance) { + if (length > distance) { const data = buffer.data; while (length--) { buffer.insertByte(data[ch++]); @@ -418,9 +418,9 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) { function inflate(compressedData, numDecompressedBytes) { // Bit stream representing the compressed data. const bstream = new bitjs.io.BitStream(compressedData.buffer, - false /* rtl */, - compressedData.byteOffset, - compressedData.byteLength); + false /* rtl */, + compressedData.byteOffset, + compressedData.byteLength); const buffer = new bitjs.io.ByteBuffer(numDecompressedBytes); let blockSize = 0; @@ -451,9 +451,9 @@ function inflate(compressedData, numDecompressedBytes) { const numCodeLengthCodes = bstream.readBits(4) + 4; // populate the array of code length codes (first de-compaction) - const codeLengthsCodeLengths = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; + const codeLengthsCodeLengths = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; for (let i = 0; i < numCodeLengthCodes; ++i) { - codeLengthsCodeLengths[ CodeLengthCodeOrder[i] ] = bstream.readBits(3); + codeLengthsCodeLengths[CodeLengthCodeOrder[i]] = bstream.readBits(3); } // get the Huffman Codes for the code lengths @@ -599,7 +599,7 @@ function unzip() { cdfh.fileComment = bstream.readString(cdfh.fileCommentLength); if (logToConsole) { console.log('Central Directory File Header:'); - for (const field in cdfh) { + for (const field in cdfh) { console.log(` ${field} = ${cdfh[field]}`); } } @@ -632,7 +632,7 @@ function unzip() { eocds.comment = bstream.readString(eocds.commentLength); if (logToConsole) { console.log('End of Central Dir Signature:'); - for (const field in eocds) { + for (const field in eocds) { console.log(` ${field} = ${eocds[field]}`); } } @@ -649,7 +649,7 @@ function unzip() { // event.data.file has the first ArrayBuffer. // event.data.bytes has all subsequent ArrayBuffers. -onmessage = function(event) { +onmessage = function (event) { const bytes = event.data.file || event.data.bytes; logToConsole = !!event.data.logToConsole; @@ -678,7 +678,7 @@ onmessage = function(event) { } if (unarchiveState === UnarchiveState.UNARCHIVING || - unarchiveState === UnarchiveState.WAITING) { + unarchiveState === UnarchiveState.WAITING) { try { unzip(); } catch (e) { diff --git a/build/Makefile b/build/Makefile new file mode 100644 index 0000000..5555daa --- /dev/null +++ b/build/Makefile @@ -0,0 +1,13 @@ + +all: bitjs-io bitjs-image-webp-shim + +clean: + $(MAKE) -C io clean + $(MAKE) -C image/webp-shim clean + +bitjs-io: + $(MAKE) -C io + +# Make webp-shim/ +bitjs-image-webp-shim: + $(MAKE) -C image/webp-shim diff --git a/build/io/Makefile b/build/io/Makefile new file mode 100644 index 0000000..9e0581b --- /dev/null +++ b/build/io/Makefile @@ -0,0 +1,25 @@ +OUT_PATH=/out/io +BITSTREAM_MODULE=${OUT_PATH}/bitstream.js +BITSTREAM_WORKER=${OUT_PATH}/bitstream-worker.js + +BITSTREAM_DEF=bitstream-def.js + +DISCLAIMER="// THIS IS A GENERATED FILE! DO NOT EDIT, INSTEAD EDIT THE FILE IN bitjs/build/io." + +all: ${BITSTREAM_MODULE} ${BITSTREAM_WORKER} + +${BITSTREAM_MODULE}: Makefile ${BITSTREAM_DEF} + echo ${DISCLAIMER} > ${BITSTREAM_MODULE} + echo "export const BitStream =" >> ${BITSTREAM_MODULE} + cat ${BITSTREAM_DEF} >> ${BITSTREAM_MODULE} + +${BITSTREAM_WORKER}: Makefile ${BITSTREAM_DEF} + echo ${DISCLAIMER} > ${BITSTREAM_WORKER} + echo "var bitjs = bitjs || {};" >> ${BITSTREAM_WORKER} + echo "bitjs.io = bitjs.io || {};" >> ${BITSTREAM_WORKER} + echo "bitjs.io.BitStream =" >> ${BITSTREAM_WORKER} + cat ${BITSTREAM_DEF} >> ${BITSTREAM_WORKER} + +clean: + rm -rf ${BITSTREAM_MODULE} + rm -rf ${BITSTREAM_WORKER} diff --git a/build/io/bitstream-def.js b/build/io/bitstream-def.js new file mode 100644 index 0000000..5f99455 --- /dev/null +++ b/build/io/bitstream-def.js @@ -0,0 +1,287 @@ +/* + * bitstream-def.js + * + * Provides readers for bitstreams. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +(function () { + // mask for getting N number of bits (0-8) + const BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF]; + + /** + * This object allows you to peek and consume bits and bytes out of a stream. + * Note that this stream is optimized, and thus, will *NOT* throw an error if + * the end of the stream is reached. Only use this in scenarios where you + * already have all the bits you need. + */ + class BitStream { + /** + * @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array. + * @param {boolean} rtl Whether the stream reads bits from the byte starting + * from bit 7 to 0 (true) or bit 0 to 7 (false). + * @param {Number} opt_offset The offset into the ArrayBuffer + * @param {Number} opt_length The length of this BitStream + */ + constructor(ab, rtl, opt_offset, opt_length) { + if (!(ab instanceof ArrayBuffer)) { + throw 'Error! BitArray constructed with an invalid ArrayBuffer object'; + } + + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + + /** + * The bytes in the stream. + * @type {Uint8Array} + * @private + */ + this.bytes = new Uint8Array(ab, offset, length); + + /** + * The byte in the stream that we are currently on. + * @type {Number} + * @private + */ + this.bytePtr = 0; + + /** + * The bit in the current byte that we will read next (can have values 0 through 7). + * @type {Number} + * @private + */ + this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7) + + /** + * An ever-increasing number. + * @type {Number} + * @private + */ + this.bitsRead_ = 0; + + this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr; + } + + /** + * Returns how many bites have been read in the stream since the beginning of time. + */ + getNumBitsRead() { + return this.bitsRead_; + } + + /** + * Returns how many bits are currently in the stream left to be read. + */ + getNumBitsLeft() { + const bitsLeftInByte = 8 - this.bitPtr; + return (this.bytes.byteLength - this.bytePtr - 1) * 8 + bitsLeftInByte; + } + + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit0 of byte0 and moves left until it reaches + * bit7 of byte0, then jumps to bit0 of byte1, etc. + * @param {number} n The number of bits to peek, must be a positive integer. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_ltr(n, opt_movePointers) { + const NUM = parseInt(n, 10); + let num = NUM; + if (n !== num || num <= 0) { + return 0; + } + + const movePointers = opt_movePointers || false; + let bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + let bitsIn = 0; + + // keep going until we have no more bits left to peek at + while (num > 0) { + // We overflowed the stream, so just return what we got. + if (bytePtr >= bytes.length) { + break; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (num >= numBitsLeftInThisByte) { + const mask = (BITMASK[numBitsLeftInThisByte] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bytePtr++; + bitPtr = 0; + bitsIn += numBitsLeftInThisByte; + num -= numBitsLeftInThisByte; + } else { + const mask = (BITMASK[num] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bitPtr += num; + break; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + this.bitsRead_ += NUM; + } + + return result; + } + + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit7 of byte0 and moves right until it reaches + * bit0 of byte0, then goes to bit7 of byte1, etc. + * @param {number} n The number of bits to peek. Must be a positive integer. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_rtl(n, opt_movePointers) { + const NUM = parseInt(n, 10); + let num = NUM; + if (n !== num || num <= 0) { + return 0; + } + + const movePointers = opt_movePointers || false; + let bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + + // keep going until we have no more bits left to peek at + while (num > 0) { + // We overflowed the stream, so just return the bits we got. + if (bytePtr >= bytes.length) { + break; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (num >= numBitsLeftInThisByte) { + result <<= numBitsLeftInThisByte; + result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]); + bytePtr++; + bitPtr = 0; + num -= numBitsLeftInThisByte; + } else { + result <<= num; + const numBits = 8 - num - bitPtr; + result |= ((bytes[bytePtr] & (BITMASK[num] << numBits)) >> numBits); + + bitPtr += num; + break; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + this.bitsRead_ += NUM; + } + + return result; + } + + /** + * Peek at 16 bits from current position in the buffer. + * Bit at (bytePtr,bitPtr) has the highest position in returning data. + * Taken from getbits.hpp in unrar. + * TODO: Move this out of BitStream and into unrar. + */ + getBits() { + return (((((this.bytes[this.bytePtr] & 0xff) << 16) + + ((this.bytes[this.bytePtr + 1] & 0xff) << 8) + + ((this.bytes[this.bytePtr + 2] & 0xff))) >>> (8 - this.bitPtr)) & 0xffff); + } + + /** + * Reads n bits out of the stream, consuming them (moving the bit pointer). + * @param {number} n The number of bits to read. Must be a positive integer. + * @return {number} The read bits, as an unsigned number. + */ + readBits(n) { + return this.peekBits(n, true); + } + + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. Only use this for uncompressed blocks as this throws away remaining + * bits in the current byte. + * @param {number} n The number of bytes to peek. Must be a positive integer. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, opt_movePointers) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekBytes() with a non-positive integer: ' + n; + } else if (num === 0) { + return new Uint8Array(); + } + + // Flush bits until we are byte-aligned. + // from http://tools.ietf.org/html/rfc1951#page-11 + // "Any bits of input up to the next byte boundary are ignored." + while (this.bitPtr != 0) { + this.readBits(1); + } + + const numBytesLeft = this.getNumBitsLeft() / 8; + if (num > numBytesLeft) { + throw 'Error! Overflowed the bit stream! n=' + num + ', bytePtr=' + this.bytePtr + + ', bytes.length=' + this.bytes.length + ', bitPtr=' + this.bitPtr; + } + + const movePointers = opt_movePointers || false; + const result = new Uint8Array(num); + let bytes = this.bytes; + let ptr = this.bytePtr; + let bytesLeftToCopy = num; + while (bytesLeftToCopy > 0) { + const bytesLeftInStream = bytes.length - ptr; + const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInStream); + + result.set(bytes.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy); + + ptr += sourceLength; + // Overflowed the stream, just return what we got. + if (ptr >= bytes.length) { + break; + } + + bytesLeftToCopy -= sourceLength; + } + + if (movePointers) { + this.bytePtr += num; + this.bitsRead_ += (num * 8); + } + + return result; + } + + /** + * @param {number} n The number of bytes to read. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } + } + + return BitStream; + +})(); diff --git a/io/bitstream-worker.js b/io/bitstream-worker.js new file mode 100644 index 0000000..5b31930 --- /dev/null +++ b/io/bitstream-worker.js @@ -0,0 +1,291 @@ +// THIS IS A GENERATED FILE! DO NOT EDIT, INSTEAD EDIT THE FILE IN bitjs/build/io. +var bitjs = bitjs || {}; +bitjs.io = bitjs.io || {}; +bitjs.io.BitStream = +/* + * bitstream-def.js + * + * Provides readers for bitstreams. + * + * Licensed under the MIT License + * + * Copyright(c) 2011 Google Inc. + * Copyright(c) 2011 antimatter15 + */ + +(function () { + // mask for getting N number of bits (0-8) + const BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF]; + + /** + * This object allows you to peek and consume bits and bytes out of a stream. + * Note that this stream is optimized, and thus, will *NOT* throw an error if + * the end of the stream is reached. Only use this in scenarios where you + * already have all the bits you need. + */ + class BitStream { + /** + * @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array. + * @param {boolean} rtl Whether the stream reads bits from the byte starting + * from bit 7 to 0 (true) or bit 0 to 7 (false). + * @param {Number} opt_offset The offset into the ArrayBuffer + * @param {Number} opt_length The length of this BitStream + */ + constructor(ab, rtl, opt_offset, opt_length) { + if (!(ab instanceof ArrayBuffer)) { + throw 'Error! BitArray constructed with an invalid ArrayBuffer object'; + } + + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + + /** + * The bytes in the stream. + * @type {Uint8Array} + * @private + */ + this.bytes = new Uint8Array(ab, offset, length); + + /** + * The byte in the stream that we are currently on. + * @type {Number} + * @private + */ + this.bytePtr = 0; + + /** + * The bit in the current byte that we will read next (can have values 0 through 7). + * @type {Number} + * @private + */ + this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7) + + /** + * An ever-increasing number. + * @type {Number} + * @private + */ + this.bitsRead_ = 0; + + this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr; + } + + /** + * Returns how many bites have been read in the stream since the beginning of time. + */ + getNumBitsRead() { + return this.bitsRead_; + } + + /** + * Returns how many bits are currently in the stream left to be read. + */ + getNumBitsLeft() { + const bitsLeftInByte = 8 - this.bitPtr; + return (this.bytes.byteLength - this.bytePtr - 1) * 8 + bitsLeftInByte; + } + + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit0 of byte0 and moves left until it reaches + * bit7 of byte0, then jumps to bit0 of byte1, etc. + * @param {number} n The number of bits to peek, must be a positive integer. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_ltr(n, opt_movePointers) { + const NUM = parseInt(n, 10); + let num = NUM; + if (n !== num || num <= 0) { + return 0; + } + + const movePointers = opt_movePointers || false; + let bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + let bitsIn = 0; + + // keep going until we have no more bits left to peek at + while (num > 0) { + // We overflowed the stream, so just return what we got. + if (bytePtr >= bytes.length) { + break; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (num >= numBitsLeftInThisByte) { + const mask = (BITMASK[numBitsLeftInThisByte] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bytePtr++; + bitPtr = 0; + bitsIn += numBitsLeftInThisByte; + num -= numBitsLeftInThisByte; + } else { + const mask = (BITMASK[num] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bitPtr += num; + break; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + this.bitsRead_ += NUM; + } + + return result; + } + + /** + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit7 of byte0 and moves right until it reaches + * bit0 of byte0, then goes to bit7 of byte1, etc. + * @param {number} n The number of bits to peek. Must be a positive integer. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. + */ + peekBits_rtl(n, opt_movePointers) { + const NUM = parseInt(n, 10); + let num = NUM; + if (n !== num || num <= 0) { + return 0; + } + + const movePointers = opt_movePointers || false; + let bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + + // keep going until we have no more bits left to peek at + while (num > 0) { + // We overflowed the stream, so just return the bits we got. + if (bytePtr >= bytes.length) { + break; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (num >= numBitsLeftInThisByte) { + result <<= numBitsLeftInThisByte; + result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]); + bytePtr++; + bitPtr = 0; + num -= numBitsLeftInThisByte; + } else { + result <<= num; + const numBits = 8 - num - bitPtr; + result |= ((bytes[bytePtr] & (BITMASK[num] << numBits)) >> numBits); + + bitPtr += num; + break; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + this.bitsRead_ += NUM; + } + + return result; + } + + /** + * Peek at 16 bits from current position in the buffer. + * Bit at (bytePtr,bitPtr) has the highest position in returning data. + * Taken from getbits.hpp in unrar. + * TODO: Move this out of BitStream and into unrar. + */ + getBits() { + return (((((this.bytes[this.bytePtr] & 0xff) << 16) + + ((this.bytes[this.bytePtr + 1] & 0xff) << 8) + + ((this.bytes[this.bytePtr + 2] & 0xff))) >>> (8 - this.bitPtr)) & 0xffff); + } + + /** + * Reads n bits out of the stream, consuming them (moving the bit pointer). + * @param {number} n The number of bits to read. Must be a positive integer. + * @return {number} The read bits, as an unsigned number. + */ + readBits(n) { + return this.peekBits(n, true); + } + + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. Only use this for uncompressed blocks as this throws away remaining + * bits in the current byte. + * @param {number} n The number of bytes to peek. Must be a positive integer. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, opt_movePointers) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekBytes() with a non-positive integer: ' + n; + } else if (num === 0) { + return new Uint8Array(); + } + + // Flush bits until we are byte-aligned. + // from http://tools.ietf.org/html/rfc1951#page-11 + // "Any bits of input up to the next byte boundary are ignored." + while (this.bitPtr != 0) { + this.readBits(1); + } + + const numBytesLeft = this.getNumBitsLeft() / 8; + if (num > numBytesLeft) { + throw 'Error! Overflowed the bit stream! n=' + num + ', bytePtr=' + this.bytePtr + + ', bytes.length=' + this.bytes.length + ', bitPtr=' + this.bitPtr; + } + + const movePointers = opt_movePointers || false; + const result = new Uint8Array(num); + let bytes = this.bytes; + let ptr = this.bytePtr; + let bytesLeftToCopy = num; + while (bytesLeftToCopy > 0) { + const bytesLeftInStream = bytes.length - ptr; + const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInStream); + + result.set(bytes.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy); + + ptr += sourceLength; + // Overflowed the stream, just return what we got. + if (ptr >= bytes.length) { + break; + } + + bytesLeftToCopy -= sourceLength; + } + + if (movePointers) { + this.bytePtr += num; + this.bitsRead_ += (num * 8); + } + + return result; + } + + /** + * @param {number} n The number of bytes to read. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } + } + + return BitStream; + +})(); diff --git a/io/bitstream.js b/io/bitstream.js index 3ae1c3e..0ac4606 100644 --- a/io/bitstream.js +++ b/io/bitstream.js @@ -1,5 +1,7 @@ +// THIS IS A GENERATED FILE! DO NOT EDIT, INSTEAD EDIT THE FILE IN bitjs/build/io. +export const BitStream = /* - * bitstream.js + * bitstream-def.js * * Provides readers for bitstreams. * @@ -9,280 +11,279 @@ * Copyright(c) 2011 antimatter15 */ -var bitjs = bitjs || {}; -bitjs.io = bitjs.io || {}; +(function () { + // mask for getting N number of bits (0-8) + const BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF]; - -/** - * This object allows you to peek and consume bits and bytes out of a stream. - * Note that this stream is optimized, and thus, will *NOT* throw an error if - * the end of the stream is reached. Only use this in scenarios where you - * already have all the bits you need. - */ -bitjs.io.BitStream = class { /** - * @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array. - * @param {boolean} rtl Whether the stream reads bits from the byte starting - * from bit 7 to 0 (true) or bit 0 to 7 (false). - * @param {Number} opt_offset The offset into the ArrayBuffer - * @param {Number} opt_length The length of this BitStream + * This object allows you to peek and consume bits and bytes out of a stream. + * Note that this stream is optimized, and thus, will *NOT* throw an error if + * the end of the stream is reached. Only use this in scenarios where you + * already have all the bits you need. */ - constructor(ab, rtl, opt_offset, opt_length) { - if (!(ab instanceof ArrayBuffer)) { - throw 'Error! BitArray constructed with an invalid ArrayBuffer object'; - } + class BitStream { + /** + * @param {ArrayBuffer} ab An ArrayBuffer object or a Uint8Array. + * @param {boolean} rtl Whether the stream reads bits from the byte starting + * from bit 7 to 0 (true) or bit 0 to 7 (false). + * @param {Number} opt_offset The offset into the ArrayBuffer + * @param {Number} opt_length The length of this BitStream + */ + constructor(ab, rtl, opt_offset, opt_length) { + if (!(ab instanceof ArrayBuffer)) { + throw 'Error! BitArray constructed with an invalid ArrayBuffer object'; + } - const offset = opt_offset || 0; - const length = opt_length || ab.byteLength; + const offset = opt_offset || 0; + const length = opt_length || ab.byteLength; + + /** + * The bytes in the stream. + * @type {Uint8Array} + * @private + */ + this.bytes = new Uint8Array(ab, offset, length); + + /** + * The byte in the stream that we are currently on. + * @type {Number} + * @private + */ + this.bytePtr = 0; + + /** + * The bit in the current byte that we will read next (can have values 0 through 7). + * @type {Number} + * @private + */ + this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7) + + /** + * An ever-increasing number. + * @type {Number} + * @private + */ + this.bitsRead_ = 0; + + this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr; + } /** - * The bytes in the stream. - * @type {Uint8Array} - * @private + * Returns how many bites have been read in the stream since the beginning of time. */ - this.bytes = new Uint8Array(ab, offset, length); + getNumBitsRead() { + return this.bitsRead_; + } /** - * The byte in the stream that we are currently on. - * @type {Number} - * @private + * Returns how many bits are currently in the stream left to be read. */ - this.bytePtr = 0; + getNumBitsLeft() { + const bitsLeftInByte = 8 - this.bitPtr; + return (this.bytes.byteLength - this.bytePtr - 1) * 8 + bitsLeftInByte; + } /** - * The bit in the current byte that we will read next (can have values 0 through 7). - * @type {Number} - * @private + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit0 of byte0 and moves left until it reaches + * bit7 of byte0, then jumps to bit0 of byte1, etc. + * @param {number} n The number of bits to peek, must be a positive integer. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. */ - this.bitPtr = 0; // tracks which bit we are on (can have values 0 through 7) + peekBits_ltr(n, opt_movePointers) { + const NUM = parseInt(n, 10); + let num = NUM; + if (n !== num || num <= 0) { + return 0; + } + + const movePointers = opt_movePointers || false; + let bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; + let bitsIn = 0; + + // keep going until we have no more bits left to peek at + while (num > 0) { + // We overflowed the stream, so just return what we got. + if (bytePtr >= bytes.length) { + break; + } + + const numBitsLeftInThisByte = (8 - bitPtr); + if (num >= numBitsLeftInThisByte) { + const mask = (BITMASK[numBitsLeftInThisByte] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bytePtr++; + bitPtr = 0; + bitsIn += numBitsLeftInThisByte; + num -= numBitsLeftInThisByte; + } else { + const mask = (BITMASK[num] << bitPtr); + result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + + bitPtr += num; + break; + } + } + + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + this.bitsRead_ += NUM; + } + + return result; + } /** - * An ever-increasing number. - * @type {Number} - * @private + * byte0 byte1 byte2 byte3 + * 7......0 | 7......0 | 7......0 | 7......0 + * + * The bit pointer starts at bit7 of byte0 and moves right until it reaches + * bit0 of byte0, then goes to bit7 of byte1, etc. + * @param {number} n The number of bits to peek. Must be a positive integer. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {number} The peeked bits, as an unsigned number. */ - this.bitsRead_ = 0; - - this.peekBits = rtl ? this.peekBits_rtl : this.peekBits_ltr; - } - - /** - * Returns how many bites have been read in the stream since the beginning of time. - */ - getNumBitsRead() { - return this.bitsRead_; - } - - /** - * Returns how many bits are currently in the stream left to be read. - */ - getNumBitsLeft() { - const bitsLeftInByte = 8 - this.bitPtr; - return (this.bytes.byteLength - this.bytePtr - 1) * 8 + bitsLeftInByte; - } - - /** - * byte0 byte1 byte2 byte3 - * 7......0 | 7......0 | 7......0 | 7......0 - * - * The bit pointer starts at bit0 of byte0 and moves left until it reaches - * bit7 of byte0, then jumps to bit0 of byte1, etc. - * @param {number} n The number of bits to peek, must be a positive integer. - * @param {boolean=} movePointers Whether to move the pointer, defaults false. - * @return {number} The peeked bits, as an unsigned number. - */ - peekBits_ltr(n, opt_movePointers) { - const NUM = parseInt(n, 10); - let num = NUM; - if (n !== num || num <= 0) { - return 0; - } - - const BITMASK = bitjs.io.BitStream.BITMASK; - const movePointers = opt_movePointers || false; - let bytes = this.bytes; - let bytePtr = this.bytePtr; - let bitPtr = this.bitPtr; - let result = 0; - let bitsIn = 0; - - // keep going until we have no more bits left to peek at - while (num > 0) { - // We overflowed the stream, so just return what we got. - if (bytePtr >= bytes.length) { - break; + peekBits_rtl(n, opt_movePointers) { + const NUM = parseInt(n, 10); + let num = NUM; + if (n !== num || num <= 0) { + return 0; } - const numBitsLeftInThisByte = (8 - bitPtr); - if (num >= numBitsLeftInThisByte) { - const mask = (BITMASK[numBitsLeftInThisByte] << bitPtr); - result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + const movePointers = opt_movePointers || false; + let bytes = this.bytes; + let bytePtr = this.bytePtr; + let bitPtr = this.bitPtr; + let result = 0; - bytePtr++; - bitPtr = 0; - bitsIn += numBitsLeftInThisByte; - num -= numBitsLeftInThisByte; - } else { - const mask = (BITMASK[num] << bitPtr); - result |= (((bytes[bytePtr] & mask) >> bitPtr) << bitsIn); + // keep going until we have no more bits left to peek at + while (num > 0) { + // We overflowed the stream, so just return the bits we got. + if (bytePtr >= bytes.length) { + break; + } - bitPtr += num; - break; - } - } + const numBitsLeftInThisByte = (8 - bitPtr); + if (num >= numBitsLeftInThisByte) { + result <<= numBitsLeftInThisByte; + result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]); + bytePtr++; + bitPtr = 0; + num -= numBitsLeftInThisByte; + } else { + result <<= num; + const numBits = 8 - num - bitPtr; + result |= ((bytes[bytePtr] & (BITMASK[num] << numBits)) >> numBits); - if (movePointers) { - this.bitPtr = bitPtr; - this.bytePtr = bytePtr; - this.bitsRead_ += NUM; - } - - return result; - } - - /** - * byte0 byte1 byte2 byte3 - * 7......0 | 7......0 | 7......0 | 7......0 - * - * The bit pointer starts at bit7 of byte0 and moves right until it reaches - * bit0 of byte0, then goes to bit7 of byte1, etc. - * @param {number} n The number of bits to peek. Must be a positive integer. - * @param {boolean=} movePointers Whether to move the pointer, defaults false. - * @return {number} The peeked bits, as an unsigned number. - */ - peekBits_rtl(n, opt_movePointers) { - const NUM = parseInt(n, 10); - let num = NUM; - if (n !== num || num <= 0) { - return 0; - } - - const BITMASK = bitjs.io.BitStream.BITMASK; - const movePointers = opt_movePointers || false; - let bytes = this.bytes; - let bytePtr = this.bytePtr; - let bitPtr = this.bitPtr; - let result = 0; - - // keep going until we have no more bits left to peek at - while (num > 0) { - // We overflowed the stream, so just return the bits we got. - if (bytePtr >= bytes.length) { - break; + bitPtr += num; + break; + } } - const numBitsLeftInThisByte = (8 - bitPtr); - if (num >= numBitsLeftInThisByte) { - result <<= numBitsLeftInThisByte; - result |= (BITMASK[numBitsLeftInThisByte] & bytes[bytePtr]); - bytePtr++; - bitPtr = 0; - num -= numBitsLeftInThisByte; - } else { - result <<= num; - const numBits = 8 - num - bitPtr; - result |= ((bytes[bytePtr] & (BITMASK[num] << numBits)) >> numBits); - - bitPtr += num; - break; - } - } - - if (movePointers) { - this.bitPtr = bitPtr; - this.bytePtr = bytePtr; - this.bitsRead_ += NUM; - } - - return result; - } - - /** - * Peek at 16 bits from current position in the buffer. - * Bit at (bytePtr,bitPtr) has the highest position in returning data. - * Taken from getbits.hpp in unrar. - * TODO: Move this out of BitStream and into unrar. - */ - getBits() { - return (((((this.bytes[this.bytePtr] & 0xff) << 16) + - ((this.bytes[this.bytePtr+1] & 0xff) << 8) + - ((this.bytes[this.bytePtr+2] & 0xff))) >>> (8-this.bitPtr)) & 0xffff); - } - - /** - * Reads n bits out of the stream, consuming them (moving the bit pointer). - * @param {number} n The number of bits to read. Must be a positive integer. - * @return {number} The read bits, as an unsigned number. - */ - readBits(n) { - return this.peekBits(n, true); - } - - /** - * This returns n bytes as a sub-array, advancing the pointer if movePointers - * is true. Only use this for uncompressed blocks as this throws away remaining - * bits in the current byte. - * @param {number} n The number of bytes to peek. Must be a positive integer. - * @param {boolean=} movePointers Whether to move the pointer, defaults false. - * @return {Uint8Array} The subarray. - */ - peekBytes(n, opt_movePointers) { - const num = parseInt(n, 10); - if (n !== num || num < 0) { - throw 'Error! Called peekBytes() with a non-positive integer: ' + n; - } else if (num === 0) { - return new Uint8Array(); - } - - // Flush bits until we are byte-aligned. - // from http://tools.ietf.org/html/rfc1951#page-11 - // "Any bits of input up to the next byte boundary are ignored." - while (this.bitPtr != 0) { - this.readBits(1); - } - - const numBytesLeft = this.getNumBitsLeft() / 8; - if (num > numBytesLeft) { - throw 'Error! Overflowed the bit stream! n=' + num + ', bytePtr=' + this.bytePtr + - ', bytes.length=' + this.bytes.length + ', bitPtr=' + this.bitPtr; - } - - const movePointers = opt_movePointers || false; - const result = new Uint8Array(num); - let bytes = this.bytes; - let ptr = this.bytePtr; - let bytesLeftToCopy = num; - while (bytesLeftToCopy > 0) { - const bytesLeftInStream = bytes.length - ptr; - const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInStream); - - result.set(bytes.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy); - - ptr += sourceLength; - // Overflowed the stream, just return what we got. - if (ptr >= bytes.length) { - break; + if (movePointers) { + this.bitPtr = bitPtr; + this.bytePtr = bytePtr; + this.bitsRead_ += NUM; } - bytesLeftToCopy -= sourceLength; + return result; } - if (movePointers) { - this.bytePtr += num; - this.bitsRead_ += (num * 8); + /** + * Peek at 16 bits from current position in the buffer. + * Bit at (bytePtr,bitPtr) has the highest position in returning data. + * Taken from getbits.hpp in unrar. + * TODO: Move this out of BitStream and into unrar. + */ + getBits() { + return (((((this.bytes[this.bytePtr] & 0xff) << 16) + + ((this.bytes[this.bytePtr + 1] & 0xff) << 8) + + ((this.bytes[this.bytePtr + 2] & 0xff))) >>> (8 - this.bitPtr)) & 0xffff); } - return result; + /** + * Reads n bits out of the stream, consuming them (moving the bit pointer). + * @param {number} n The number of bits to read. Must be a positive integer. + * @return {number} The read bits, as an unsigned number. + */ + readBits(n) { + return this.peekBits(n, true); + } + + /** + * This returns n bytes as a sub-array, advancing the pointer if movePointers + * is true. Only use this for uncompressed blocks as this throws away remaining + * bits in the current byte. + * @param {number} n The number of bytes to peek. Must be a positive integer. + * @param {boolean=} movePointers Whether to move the pointer, defaults false. + * @return {Uint8Array} The subarray. + */ + peekBytes(n, opt_movePointers) { + const num = parseInt(n, 10); + if (n !== num || num < 0) { + throw 'Error! Called peekBytes() with a non-positive integer: ' + n; + } else if (num === 0) { + return new Uint8Array(); + } + + // Flush bits until we are byte-aligned. + // from http://tools.ietf.org/html/rfc1951#page-11 + // "Any bits of input up to the next byte boundary are ignored." + while (this.bitPtr != 0) { + this.readBits(1); + } + + const numBytesLeft = this.getNumBitsLeft() / 8; + if (num > numBytesLeft) { + throw 'Error! Overflowed the bit stream! n=' + num + ', bytePtr=' + this.bytePtr + + ', bytes.length=' + this.bytes.length + ', bitPtr=' + this.bitPtr; + } + + const movePointers = opt_movePointers || false; + const result = new Uint8Array(num); + let bytes = this.bytes; + let ptr = this.bytePtr; + let bytesLeftToCopy = num; + while (bytesLeftToCopy > 0) { + const bytesLeftInStream = bytes.length - ptr; + const sourceLength = Math.min(bytesLeftToCopy, bytesLeftInStream); + + result.set(bytes.subarray(ptr, ptr + sourceLength), num - bytesLeftToCopy); + + ptr += sourceLength; + // Overflowed the stream, just return what we got. + if (ptr >= bytes.length) { + break; + } + + bytesLeftToCopy -= sourceLength; + } + + if (movePointers) { + this.bytePtr += num; + this.bitsRead_ += (num * 8); + } + + return result; + } + + /** + * @param {number} n The number of bytes to read. + * @return {Uint8Array} The subarray. + */ + readBytes(n) { + return this.peekBytes(n, true); + } } - /** - * @param {number} n The number of bytes to read. - * @return {Uint8Array} The subarray. - */ - readBytes(n) { - return this.peekBytes(n, true); - } -} + return BitStream; -// mask for getting N number of bits (0-8) -bitjs.io.BitStream.BITMASK = [0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF ]; +})(); diff --git a/tests/io-bitstream-test.html b/tests/io-bitstream-test.html index 909f11f..1656652 100644 --- a/tests/io-bitstream-test.html +++ b/tests/io-bitstream-test.html @@ -3,7 +3,7 @@ Unit tests for bitjs.io.BitStream - +