lzma-purejs-requirejs
Version:
pure JavaScript LZMA de/compression, for node.js, volo, and the browser.
1,373 lines (1,220 loc) • 46.3 kB
JavaScript
'use strict';
const Base = require('./Base');
const RangeCoder = require('../RangeCoder');
const LZ = require('../LZ');
const freeze = require('../freeze');
const makeBuffer = require('../makeBuffer');
//});var Base,RangeCoder,LZ,makeBuffer;//XXX
// shortcuts
var initBitModels = RangeCoder.Encoder.initBitModels;
// constants
var EMatchFinderTypeBT2 = 0;
var EMatchFinderTypeBT4 = 1;
var kInfinityPrice = 0xFFFFFFF;
var kDefaultDictionaryLogSize = 22;
var kNumFastBytesDefault = 0x20;
var kNumOpts = 1 << 12;
var kPropSize = 5;
var g_FastPos = (function() {
var g_FastPos = makeBuffer(1 << 11);
var kFastSlots = 22;
var c = 2;
var slotFast;
g_FastPos[0] = 0;
g_FastPos[1] = 1;
for (slotFast = 2; slotFast < kFastSlots; slotFast++) {
var j, k = 1 << ((slotFast >> 1) - 1);
for (j = 0; j < k; j++,c++) {
g_FastPos[c] = slotFast;
}
}
return g_FastPos;
})();
var getPosSlot = function(pos) {
if (pos < (1 << 11)) {
return g_FastPos[pos];
}
if (pos < (1 << 21)) {
return g_FastPos[pos >>> 10] + 20;
}
return g_FastPos[pos >>> 20] + 40;
};
var getPosSlot2 = function(pos) {
if (pos < (1 << 17)) {
return g_FastPos[pos >>> 6] + 12;
}
if (pos < (1 << 27)) {
return g_FastPos[pos >>> 16] + 32;
}
return g_FastPos[pos >>> 26] + 52;
};
var Encoder = function() {
var i;
this._state = Base.stateInit();
this._previousByte = 0;
this._repDistances = []; // XXX use Uint32Array?
this._repDistances.length = Base.kNumRepDistances;
// these fields are defined much lower in the original Java source file
this._optimum = [];
this._matchFinder = null;
this._rangeEncoder = new RangeCoder.Encoder();
this._isMatch = initBitModels(null, Base.kNumStates << Base.kNumPosStatesBitsMax);
this._isRep = initBitModels(null, Base.kNumStates);
this._isRepG0 = initBitModels(null, Base.kNumStates);
this._isRepG1 = initBitModels(null, Base.kNumStates);
this._isRepG2 = initBitModels(null, Base.kNumStates);
this._isRep0Long = initBitModels(null, Base.kNumStates << Base.kNumPosStatesBitsMax);
this._posSlotEncoder = [];
this._posEncoders = initBitModels(null, Base.kNumFullDistances-Base.kEndPosModelIndex);
this._posAlignEncoder = new RangeCoder.BitTreeEncoder(Base.kNumAlignBits);
this._lenEncoder = new Encoder.LenPriceTableEncoder();
this._repMatchLenEncoder = new Encoder.LenPriceTableEncoder();
this._literalEncoder = new Encoder.LiteralEncoder();
this._matchDistances = [];
this._matchDistances.length = Base.kMatchMaxLen*2 + 2;
for (i=0; i < this._matchDistances.length; i++) {
this._matchDistances[i] = 0;
}
this._numFastBytes = kNumFastBytesDefault;
this._longestMatchLength = 0;
this._numDistancePairs = 0;
this._additionalOffset = 0;
this._optimumEndIndex = 0;
this._optimumCurrentIndex = 0;
this._longestMatchWasFound = false;
this._posSlotPrices = [];
this._distancesPrices = [];
this._alignPrices = [];
this._alignPriceCount = 0;
this._distTableSize = kDefaultDictionaryLogSize * 2;
this._posStateBits = 2;
this._posStateMask = 4 - 1;
this._numLiteralPosStateBits = 0;
this._numLiteralContextBits = 3;
this._dictionarySize = (1 << kDefaultDictionaryLogSize);
this._dictionarySizePrev = 0xFFFFFFFF;
this._numFastBytesPrev = 0xFFFFFFFF;
// note that this is a 53-bit variable, not 64-bit. This sets the maximum
// encoded file size.
this.nowPos64 = 0;
this._finished = false;
this._inStream = null;
this._matchFinderType = EMatchFinderTypeBT4;
this._writeEndMark = false;
this._needReleaseMFStream = false;
// ...and even further down we find these:
this.reps = [];
this.repLens = [];
this.backRes = 0;
// ...keep going, eventually we find the constructor:
for (i = 0; i < kNumOpts; i++) {
this._optimum[i] = new Encoder.Optimal();
}
for (i = 0; i < Base.kNumLenToPosStates; i++) {
this._posSlotEncoder[i] = new RangeCoder.BitTreeEncoder(Base.kNumPosSlotBits);
}
this._matchPriceCount = 0;
// ...and just above the 'Code' method, we find:
this.processedInSize = [0];
this.processedOutSize = [0];
this.finished = [false];
};
Encoder.prototype.baseInit = function() {
var i;
this._state = Base.stateInit();
this._previousByte = 0;
for (i = 0; i < Base.kNumRepDistances; i++) {
this._repDistances[i] = 0;
}
};
var LiteralEncoder = Encoder.LiteralEncoder = function() {
this._coders = null;
this._numPrevBits = -1;
this._numPosBits = -1;
this._posMask = 0;
};
LiteralEncoder.Encoder2 = function() {
this._encoders = initBitModels(null, 0x300);
};
LiteralEncoder.Encoder2.prototype.init = function() {
initBitModels(this._encoders);
};
LiteralEncoder.Encoder2.prototype.encode = function(rangeEncoder, symbol) {
var context = 1, i;
for (i = 7; i >= 0; i--) {
var bit = (symbol >>> i) & 1;
rangeEncoder.encode(this._encoders, context, bit);
context = (context << 1) | bit;
}
};
LiteralEncoder.Encoder2.prototype.encodeMatched = function(rangeEncoder, matchByte, symbol) {
var context = 1, same = true, i;
for (i = 7; i>= 0; i--) {
var bit = (symbol >> i) & 1;
var state = context;
if (same) {
var matchBit = (matchByte >>> i) & 1;
state += (1 + matchBit) << 8;
same = (matchBit === bit);
}
rangeEncoder.encode(this._encoders, state, bit);
context = (context << 1) | bit;
}
};
LiteralEncoder.Encoder2.prototype.getPrice = function(matchMode, matchByte, symbol) {
var price = 0;
var context = 1;
var i = 7;
var bit, matchBit;
if (matchMode) {
for (; i >= 0; i--) {
matchBit = (matchByte >>> i) & 1;
bit = (symbol >>> i) & 1;
price += RangeCoder.Encoder.getPrice(this._encoders[((1 + matchBit) << 8) + context], bit);
context = (context << 1) | bit;
if (matchBit !== bit) {
i--;
break;
}
}
}
for (; i >= 0; i--) {
bit = (symbol >>> i) & 1;
price += RangeCoder.Encoder.getPrice(this._encoders[context], bit);
context = (context << 1) | bit;
}
return price;
};
LiteralEncoder.prototype.create = function(numPosBits, numPrevBits) {
var i;
if (this._coders &&
this._numPrevBits === numPrevBits &&
this._numPosBits === numPosBits) {
return;
}
this._numPosBits = numPosBits;
this._posMask = (1 << numPosBits) - 1;
this._numPrevBits = numPrevBits;
var numStates = 1 << (this._numPrevBits + this._numPosBits);
this._coders = [];
for (i = 0; i < numStates; i++) {
this._coders[i] = new LiteralEncoder.Encoder2();
}
};
LiteralEncoder.prototype.init = function() {
var numStates = 1 << (this._numPrevBits + this._numPosBits), i;
for (i = 0; i < numStates; i++) {
this._coders[i].init();
}
};
LiteralEncoder.prototype.getSubCoder = function(pos, prevByte) {
return this._coders[((pos & this._posMask) << this._numPrevBits) + (prevByte >> (8 - this._numPrevBits))];
};
var LenEncoder = Encoder.LenEncoder = function() {
var posState;
this._choice = initBitModels(null, 2);
this._lowCoder = [];
this._midCoder = [];
this._highCoder = new RangeCoder.BitTreeEncoder(Base.kNumHighLenBits);
for (posState = 0; posState < Base.kNumPosStatesEncodingMax; posState++) {
this._lowCoder[posState] = new RangeCoder.BitTreeEncoder(Base.kNumLowLenBits);
this._midCoder[posState] = new RangeCoder.BitTreeEncoder(Base.kNumMidLenBits);
}
};
LenEncoder.prototype.init = function(numPosStates) {
var posState;
initBitModels(this._choice);
for (posState = 0; posState < numPosStates; posState++) {
this._lowCoder[posState].init();
this._midCoder[posState].init();
}
this._highCoder.init();
};
LenEncoder.prototype.encode = function(rangeEncoder, symbol, posState) {
if (symbol < Base.kNumLowLenSymbols) {
rangeEncoder.encode(this._choice, 0, 0);
this._lowCoder[posState].encode(rangeEncoder, symbol);
} else {
symbol -= Base.kNumLowLenSymbols;
rangeEncoder.encode(this._choice, 0, 1);
if (symbol < Base.kNumMidLenSymbols) {
rangeEncoder.encode(this._choice, 1, 0);
this._midCoder[posState].encode(rangeEncoder, symbol);
} else {
rangeEncoder.encode(this._choice, 1, 1);
this._highCoder.encode(rangeEncoder, symbol - Base.kNumMidLenSymbols);
}
}
};
LenEncoder.prototype.setPrices = function(posState, numSymbols, prices, st) {
var a0 = RangeCoder.Encoder.getPrice0(this._choice[0]);
var a1 = RangeCoder.Encoder.getPrice1(this._choice[0]);
var b0 = a1 + RangeCoder.Encoder.getPrice0(this._choice[1]);
var b1 = a1 + RangeCoder.Encoder.getPrice1(this._choice[1]);
var i;
for (i = 0; i < Base.kNumLowLenSymbols; i++) {
if (i >= numSymbols) {
return;
}
prices[st + i] = a0 + this._lowCoder[posState].getPrice(i);
}
for (; i < Base.kNumLowLenSymbols + Base.kNumMidLenSymbols; i++) {
if (i >= numSymbols) {
return;
}
prices[st + i] = b0 + this._midCoder[posState].getPrice(i - Base.kNumLowLenSymbols);
}
for (; i < numSymbols; i++) {
prices[st + i] = b1 + this._highCoder.getPrice(i - Base.kNumLowLenSymbols - Base.kNumMidLenSymbols);
}
};
var kNumLenSpecSymbols = Base.kNumLowLenSymbols + Base.kNumMidLenSymbols;
var LenPriceTableEncoder = Encoder.LenPriceTableEncoder = function() {
LenEncoder.call(this); // superclass constructor
this._prices = [];
this._counters = [];
this._tableSize = 0;
};
LenPriceTableEncoder.prototype = Object.create(LenEncoder.prototype);
LenPriceTableEncoder.prototype.setTableSize = function(tableSize) {
this._tableSize = tableSize;
};
LenPriceTableEncoder.prototype.getPrice = function(symbol, posState) {
return this._prices[posState * Base.kNumLenSymbols + symbol];
};
LenPriceTableEncoder.prototype.updateTable = function(posState) {
this.setPrices(posState, this._tableSize, this._prices,
posState * Base.kNumLenSymbols);
this._counters[posState] = this._tableSize;
};
LenPriceTableEncoder.prototype.updateTables = function(numPosStates) {
var posState;
for (posState = 0; posState < numPosStates; posState++) {
this.updateTable(posState);
}
};
LenPriceTableEncoder.prototype.encode = (function(superEncode) {
return function(rangeEncoder, symbol, posState) {
superEncode.call(this, rangeEncoder, symbol, posState);
if (--this._counters[posState] === 0) {
this.updateTable(posState);
}
};
})(LenPriceTableEncoder.prototype.encode);
var Optimal = Encoder.Optimal = function() {
this.state = 0;
this.prev1IsChar = false;
this.prev2 = false;
this.posPrev2 = 0;
this.backPrev2 = 0;
this.price = 0;
this.posPrev = 0;
this.backPrev = 0;
this.backs0 = 0;
this.backs1 = 0;
this.backs2 = 0;
this.backs3 = 0;
};
Optimal.prototype.makeAsChar = function() {
this.backPrev = -1;
this.prev1IsChar = false;
};
Optimal.prototype.makeAsShortRep = function() {
this.backPrev = 0;
this.prev1IsChar= false;
};
Optimal.prototype.isShortRep = function() {
return this.backPrev === 0;
};
// back to the Encoder class!
Encoder.prototype.create = function() {
var numHashBytes;
if (!this._matchFinder) {
var bt = new LZ.BinTree();
numHashBytes = 4;
if (this._matchFinderType === EMatchFinderTypeBT2) {
numHashBytes = 2;
}
bt.setType(numHashBytes);
this._matchFinder = bt;
}
this._literalEncoder.create(this._numLiteralPosStateBits,
this._numLiteralContextBits);
if (this._dictionarySize === this._dictionarySizePrev &&
this._numFastBytesPrev === this._numFastBytes) {
return;
}
this._matchFinder.create(this._dictionarySize, kNumOpts, this._numFastBytes,
Base.kMatchMaxLen + 1);
this._dictionarySizePrev = this._dictionarySize;
this._numFastBytesPrev = this._numFastBytes;
};
Encoder.prototype.setWriteEndMarkerMode = function(writeEndMarker) {
this._writeEndMark = writeEndMarker;
};
Encoder.prototype.init = function() {
var i;
this.baseInit();
this._rangeEncoder.init();
initBitModels(this._isMatch);
initBitModels(this._isRep0Long);
initBitModels(this._isRep);
initBitModels(this._isRepG0);
initBitModels(this._isRepG1);
initBitModels(this._isRepG2);
initBitModels(this._posEncoders);
this._literalEncoder.init();
for (i = 0; i < Base.kNumLenToPosStates; i++) {
this._posSlotEncoder[i].init();
}
this._lenEncoder.init(1 << this._posStateBits);
this._repMatchLenEncoder.init(1 << this._posStateBits);
this._posAlignEncoder.init();
this._longestMatchWasFound = false;
this._optimumEndIndex = 0;
this._optimumCurrentIndex = 0;
this._additionalOffset = 0;
};
Encoder.prototype.readMatchDistances = function() {
var lenRes = 0;
this._numDistancePairs = this._matchFinder.getMatches(this._matchDistances);
if (this._numDistancePairs > 0) {
lenRes = this._matchDistances[this._numDistancePairs - 2];
if (lenRes === this._numFastBytes) {
lenRes += this._matchFinder.getMatchLen(lenRes - 1, this._matchDistances[this._numDistancePairs - 1], Base.kMatchMaxLen - lenRes);
}
}
this._additionalOffset++;
// [csa] Gary Linscott thinks that numDistancePairs should be a retval here.
return lenRes;
};
Encoder.prototype.movePos = function(num) {
if (num > 0) {
this._matchFinder.skip(num);
this._additionalOffset += num;
}
};
Encoder.prototype.getRepLen1Price = function(state, posState) {
return RangeCoder.Encoder.getPrice0(this._isRepG0[state]) +
RangeCoder.Encoder.getPrice0(this._isRep0Long[(state << Base.kNumPosStatesBitsMax) + posState]);
};
Encoder.prototype.getPureRepPrice = function(repIndex, state, posState) {
var price;
if (repIndex === 0) {
price = RangeCoder.Encoder.getPrice0(this._isRepG0[state]);
price += RangeCoder.Encoder.getPrice1(this._isRep0Long[(state << Base.kNumPosStatesBitsMax) + posState]);
} else {
price = RangeCoder.Encoder.getPrice1(this._isRepG0[state]);
if (repIndex === 1) {
price += RangeCoder.Encoder.getPrice0(this._isRepG1[state]);
} else {
price += RangeCoder.Encoder.getPrice1(this._isRepG1[state]);
price += RangeCoder.Encoder.getPrice(this._isRepG2[state], repIndex - 2);
}
}
return price;
};
Encoder.prototype.getRepPrice = function(repIndex, len, state, posState) {
var price = this._repMatchLenEncoder.getPrice(len - Base.kMatchMinLen, posState);
return price + this.getPureRepPrice(repIndex, state, posState);
};
Encoder.prototype.getPosLenPrice = function(pos, len, posState) {
var price;
var lenToPosState = Base.getLenToPosState(len);
if (pos < Base.kNumFullDistances) {
price = this._distancesPrices[(lenToPosState * Base.kNumFullDistances) + pos];
} else {
price = this._posSlotPrices[(lenToPosState << Base.kNumPosSlotBits) + getPosSlot2(pos)] + this._alignPrices[pos & Base.kAlignMask];
}
return price + this._lenEncoder.getPrice(len - Base.kMatchMinLen, posState);
};
Encoder.prototype.backward = function(cur) {
this._optimumEndIndex = cur;
var posMem = this._optimum[cur].posPrev;
var backMem = this._optimum[cur].backPrev;
do {
if (this._optimum[cur].prev1IsChar) {
this._optimum[posMem].makeAsChar();
this._optimum[posMem].posPrev = posMem - 1;
if (this._optimum[cur].prev2) {
this._optimum[posMem - 1].prev1IsChar = false;
this._optimum[posMem - 1].posPrev = this._optimum[cur].posPrev2;
this._optimum[posMem - 1].backPrev = this._optimum[cur].backPrev2;
}
}
var posPrev = posMem;
var backCur = backMem;
backMem = this._optimum[posPrev].backPrev;
posMem = this._optimum[posPrev].posPrev;
this._optimum[posPrev].backPrev = backCur;
this._optimum[posPrev].posPrev = cur;
cur = posPrev;
} while (cur > 0);
this.backRes = this._optimum[0].backPrev;
this._optimumCurrentIndex = this._optimum[0].posPrev;
// [csa] Gary Linscott thinks that backRes should be a retval here.
return this._optimumCurrentIndex;
};
Encoder.prototype.getOptimum = function(position) {
if (this._optimumEndIndex !== this._optimumCurrentIndex) {
var lenRes = this._optimum[this._optimumCurrentIndex].posPrev - this._optimumCurrentIndex;
this.backRes = this._optimum[this._optimumCurrentIndex].backPrev;
this._optimumCurrentIndex = this._optimum[this._optimumCurrentIndex].posPrev;
// [csa] Gary Linscott thinks that backRes should be a retval here.
return lenRes;
}
this._optimumCurrentIndex = this._optimumEndIndex = 0;
var lenMain;
if (!this._longestMatchWasFound) {
lenMain = this.readMatchDistances();
} else {
lenMain = this._longestMatchLength;
this._longestMatchWasFound = false;
}
var numDistancePairs = this._numDistancePairs;
var numAvailableBytes = this._matchFinder.getNumAvailableBytes() + 1;
if (numAvailableBytes < 2) {
this.backRes = -1;
// [csa] Gary Linscott thinks that backRes should be a retval here.
return 1;
}
if (numAvailableBytes > Base.kMatchMaxLen) {
numAvailableBytes = Base.kMatchMaxLen;
}
var repMaxIndex = 0, i;
for (i = 0; i < Base.kNumRepDistances; i++) {
this.reps[i] = this._repDistances[i];
this.repLens[i] = this._matchFinder.getMatchLen(-1, this.reps[i], Base.kMatchMaxLen);
if (this.repLens[i] > this.repLens[repMaxIndex]) {
repMaxIndex = i;
}
}
if (this.repLens[repMaxIndex] >= this._numFastBytes) {
this.backRes = repMaxIndex;
var lenRes2 = this.repLens[repMaxIndex];
this.movePos(lenRes2 - 1);
// [csa] Gary Linscott thinks that backRes should be a retval here.
return lenRes2;
}
if (lenMain >= this._numFastBytes) {
// [csa] Gary Linscott thinks that backRes should be a retval here.
this.backRes = this._matchDistances[numDistancePairs - 1] + Base.kNumRepDistances;
this.movePos(lenMain - 1);
return lenMain;
}
var currentByte = this._matchFinder.getIndexByte(-1);
var matchByte = this._matchFinder.getIndexByte(-this._repDistances[0] - 2);
if (lenMain < 2 && currentByte !== matchByte && this.repLens[repMaxIndex] < 2) {
// [csa] Gary Linscott thinks that backRes should be a retval here.
this.backRes = -1;
return 1;
}
this._optimum[0].state = this._state;
var posState = position & this._posStateMask;
this._optimum[1].price = RangeCoder.Encoder.getPrice0(this._isMatch[(this._state << Base.kNumPosStatesBitsMax) + posState]) +
this._literalEncoder.getSubCoder(position, this._previousByte).getPrice(!Base.stateIsCharState(this._state), matchByte, currentByte);
this._optimum[1].makeAsChar();
var matchPrice = RangeCoder.Encoder.getPrice1(this._isMatch[(this._state << Base.kNumPosStatesBitsMax) + posState]);
var repMatchPrice = matchPrice + RangeCoder.Encoder.getPrice1(this._isRep[this._state]);
if (matchByte === currentByte) {
var shortRepPrice = repMatchPrice + this.getRepLen1Price(this._state, posState);
if (shortRepPrice < this._optimum[1].price) {
this._optimum[1].price = shortRepPrice;
this._optimum[1].makeAsShortRep();
}
}
var lenEnd = (lenMain >= this.repLens[repMaxIndex]) ?
lenMain : this.repLens[repMaxIndex];
if (lenEnd < 2) {
// [csa] Gary Linscott thinks that backRes should be a retval here.
this.backRes = this._optimum[1].backPrev;
return 1;
}
this._optimum[1].posPrev = 0;
this._optimum[0].backs0 = this.reps[0];
this._optimum[0].backs1 = this.reps[1];
this._optimum[0].backs2 = this.reps[2];
this._optimum[0].backs3 = this.reps[3];
var len = lenEnd;
do {
this._optimum[len--].price = kInfinityPrice;
} while (len >= 2);
for (i = 0; i < Base.kNumRepDistances; i++) {
var repLen = this.repLens[i];
if (repLen < 2) {
continue;
}
var price = repMatchPrice + this.getPureRepPrice(i, this._state, posState);
do {
var curAndLenPrice = price +
this._repMatchLenEncoder.getPrice(repLen - 2, posState);
var optimum = this._optimum[repLen];
if (curAndLenPrice < optimum.price) {
optimum.price = curAndLenPrice;
optimum.posPrev = 0;
optimum.backPrev = i;
optimum.prev1IsChar = false;
}
} while (--repLen >= 2);
}
var normalMatchPrice = matchPrice +
RangeCoder.Encoder.getPrice0(this._isRep[this._state]);
len = this.repLens[0] >= 2 ? this.repLens[0] + 1 : 2;
if (len <= lenMain) {
var offs = 0;
while (len > this._matchDistances[offs]) {
offs += 2;
}
for (;; len++) {
var distance = this._matchDistances[offs + 1];
var curAndLenPrice2 = normalMatchPrice +
this.getPosLenPrice(distance, len, posState);
var optimum2 = this._optimum[len];
if (curAndLenPrice2 < optimum2.price) {
optimum2.price = curAndLenPrice2;
optimum2.posPrev = 0;
optimum2.backPrev = distance + Base.kNumRepDistances;
optimum2.prev1IsChar = false;
}
if (len === this._matchDistances[offs]) {
offs += 2;
if (offs === numDistancePairs) {
break;
}
}
}
}
var cur = 0;
while (true) {
cur++;
if (cur === lenEnd) {
return this.backward(cur);
}
var newLen = this.readMatchDistances();
numDistancePairs = this._numDistancePairs;
if (newLen >= this._numFastBytes) {
this._longestMatchLength = newLen;
this._longestMatchWasFound = true;
return this.backward(cur);
}
position++;
var posPrev = this._optimum[cur].posPrev;
var state;
if (this._optimum[cur].prev1IsChar) {
posPrev--;
if (this._optimum[cur].prev2) {
state = this._optimum[this._optimum[cur].posPrev2].state;
if (this._optimum[cur].backPrev2 < Base.kNumRepDistances) {
state = Base.stateUpdateRep(state);
} else {
state = Base.stateUpdateMatch(state);
}
} else {
state = this._optimum[posPrev].state;
}
state = Base.stateUpdateChar(state);
} else {
state = this._optimum[posPrev].state;
}
if (posPrev === cur - 1) {
if (this._optimum[cur].isShortRep()) {
state = Base.stateUpdateShortRep(state);
} else {
state = Base.stateUpdateChar(state);
}
} else {
var pos;
if (this._optimum[cur].prev1IsChar && this._optimum[cur].prev2) {
posPrev = this._optimum[cur].posPrev2;
pos = this._optimum[cur].backPrev2;
state = Base.stateUpdateRep(state);
} else {
pos = this._optimum[cur].backPrev;
if (pos < Base.kNumRepDistances) {
state = Base.stateUpdateRep(state);
} else {
state = Base.stateUpdateMatch(state);
}
}
var opt = this._optimum[posPrev];
if (pos < Base.kNumRepDistances) {
if (pos === 0) {
this.reps[0] = opt.backs0;
this.reps[1] = opt.backs1;
this.reps[2] = opt.backs2;
this.reps[3] = opt.backs3;
} else if (pos === 1) {
this.reps[0] = opt.backs1;
this.reps[1] = opt.backs0;
this.reps[2] = opt.backs2;
this.reps[3] = opt.backs3;
} else if (pos === 2) {
this.reps[0] = opt.backs2;
this.reps[1] = opt.backs0;
this.reps[2] = opt.backs1;
this.reps[3] = opt.backs3;
} else {
this.reps[0] = opt.backs3;
this.reps[1] = opt.backs0;
this.reps[2] = opt.backs1;
this.reps[3] = opt.backs2;
}
} else {
this.reps[0] = pos - Base.kNumRepDistances;
this.reps[1] = opt.backs0;
this.reps[2] = opt.backs1;
this.reps[3] = opt.backs2;
}
}
this._optimum[cur].state = state;
this._optimum[cur].backs0 = this.reps[0];
this._optimum[cur].backs1 = this.reps[1];
this._optimum[cur].backs2 = this.reps[2];
this._optimum[cur].backs3 = this.reps[3];
var curPrice = this._optimum[cur].price;
currentByte = this._matchFinder.getIndexByte(-1);
matchByte = this._matchFinder.getIndexByte(-this.reps[0] - 2);
posState = position & this._posStateMask;
var curAnd1Price = curPrice +
RangeCoder.Encoder.getPrice0(this._isMatch[(state << Base.kNumPosStatesBitsMax) + posState]) +
this._literalEncoder.getSubCoder(position, this._matchFinder.getIndexByte(-2)).
getPrice(!Base.stateIsCharState(state), matchByte, currentByte);
var nextOptimum = this._optimum[cur + 1];
var nextIsChar = false;
if (curAnd1Price < nextOptimum.price) {
nextOptimum.price = curAnd1Price;
nextOptimum.posPrev = cur;
nextOptimum.makeAsChar();
nextIsChar = true;
}
matchPrice = curPrice + RangeCoder.Encoder.getPrice1(this._isMatch[(state << Base.kNumPosStatesBitsMax) + posState]);
repMatchPrice = matchPrice + RangeCoder.Encoder.getPrice1(this._isRep[state]);
if (matchByte === currentByte &&
!(nextOptimum.posPrev < cur && nextOptimum.backPrev === 0)) {
var shortRepPrice2 =
repMatchPrice + this.getRepLen1Price(state, posState);
if (shortRepPrice2 <= nextOptimum.price) {
nextOptimum.price = shortRepPrice2;
nextOptimum.posPrev = cur;
nextOptimum.makeAsShortRep();
nextIsChar = true;
}
}
var numAvailableBytesFull = this._matchFinder.getNumAvailableBytes() + 1;
numAvailableBytesFull = Math.min(kNumOpts - 1 - cur, numAvailableBytesFull);
numAvailableBytes = numAvailableBytesFull;
if (numAvailableBytes < 2) {
continue;
}
if (numAvailableBytes > this._numFastBytes) {
numAvailableBytes = this._numFastBytes;
}
if (!nextIsChar && matchByte !== currentByte) {
// Try Literal + rep0
var t = Math.min(numAvailableBytesFull - 1, this._numFastBytes);
var lenTest2 = this._matchFinder.getMatchLen(0, this.reps[0], t);
if (lenTest2 >= 2) {
var state2 = Base.stateUpdateChar(state);
var posStateNext = (position + 1) & this._posStateMask;
var nextRepMatchPrice = curAnd1Price +
RangeCoder.Encoder.getPrice1(this._isMatch[(state2 << Base.kNumPosStatesBitsMax) + posStateNext]) +
RangeCoder.Encoder.getPrice1(this._isRep[state2]);
var offset = cur + 1 + lenTest2;
while (lenEnd < offset) {
this._optimum[++lenEnd].price = kInfinityPrice;
}
var curAndLenPrice3 = nextRepMatchPrice + this.getRepPrice(0, lenTest2, state2, posStateNext);
var optimum3 = this._optimum[offset];
if (curAndLenPrice3 < optimum3.price) {
optimum3.price = curAndLenPrice3;
optimum3.posPrev = cur + 1;
optimum3.backPrev = 0;
optimum3.prev1IsChar = true;
optimum3.prev2 = false;
}
}
}
var startLen = 2; // Speed optimization
var repIndex;
for (repIndex = 0; repIndex < Base.kNumRepDistances; repIndex++) {
var lenTest = this._matchFinder.getMatchLen(-1, this.reps[repIndex], numAvailableBytes);
if (lenTest < 2) {
continue;
}
var lenTestTemp = lenTest;
do {
while (lenEnd < cur + lenTest) {
this._optimum[++lenEnd].price = kInfinityPrice;
}
var curAndLenPrice4 = repMatchPrice + this.getRepPrice(repIndex, lenTest, state, posState);
var optimum4 = this._optimum[cur + lenTest];
if (curAndLenPrice4 < optimum4.price) {
optimum4.price = curAndLenPrice4;
optimum4.posPrev = cur;
optimum4.backPrev = repIndex;
optimum4.prev1IsChar = false;
}
} while (--lenTest >= 2);
lenTest = lenTestTemp;
if (repIndex === 0) {
startLen = lenTest + 1;
}
// if (_maxMode)
if (lenTest < numAvailableBytesFull) {
var t5 = Math.min(numAvailableBytesFull - 1 - lenTest, this._numFastBytes);
var lenTest25 = this._matchFinder.getMatchLen(lenTest, this.reps[repIndex], t5);
if (lenTest25 >= 2) {
var state25 = Base.stateUpdateRep(state);
var posStateNext5 = (position + lenTest) & this._posStateMask;
var curAndLenCharPrice = repMatchPrice +
this.getRepPrice(repIndex, lenTest, state, posState) +
RangeCoder.Encoder.getPrice0(this._isMatch[(state25 << Base.kNumPosStatesBitsMax) + posStateNext5]) +
this._literalEncoder.getSubCoder(position + lenTest,
this._matchFinder.getIndexByte(lenTest - 2)).
getPrice(true,
this._matchFinder.getIndexByte(lenTest - 1 - (this.reps[repIndex] + 1)),
this._matchFinder.getIndexByte(lenTest - 1));
state25 = Base.stateUpdateChar(state25);
posStateNext5 = (position + lenTest + 1) & this._posStateMask;
var nextMatchPrice5 = curAndLenCharPrice +
RangeCoder.Encoder.getPrice1(this._isMatch[(state25 << Base.kNumPosStatesBitsMax) + posStateNext5]);
var nextRepMatchPrice5 = nextMatchPrice5 +
RangeCoder.Encoder.getPrice1(this._isRep[state25]);
// for(; lenTest2 >= 2; lenTest2--) {
var offset5 = lenTest + 1 + lenTest25;
while (lenEnd < cur + offset5) {
this._optimum[++lenEnd].price = kInfinityPrice;
}
var curAndLenPrice5 = nextRepMatchPrice5 +
this.getRepPrice(0, lenTest25, state25, posStateNext5);
var optimum5 = this._optimum[cur + offset5];
if (curAndLenPrice5 < optimum5.price) {
optimum5.price = curAndLenPrice5;
optimum5.posPrev = cur + lenTest + 1;
optimum5.backPrev = 0;
optimum5.prev1IsChar = true;
optimum5.prev2 = true;
optimum5.posPrev2 = cur;
optimum5.backPrev2 = repIndex;
}
}
}
}
if (newLen > numAvailableBytes) {
newLen = numAvailableBytes;
numDistancePairs = 0;
while(newLen > this._matchDistances[numDistancePairs]) {
numDistancePairs += 2;
}
this._matchDistances[numDistancePairs] = newLen;
numDistancePairs += 2;
}
if (newLen >= startLen) {
normalMatchPrice = matchPrice +
RangeCoder.Encoder.getPrice0(this._isRep[state]);
while (lenEnd < cur + newLen) {
this._optimum[++lenEnd].price = kInfinityPrice;
}
var offs6 = 0;
while (startLen > this._matchDistances[offs6]) {
offs6 += 2;
}
var lenTest6;
for (lenTest6 = startLen; ; lenTest6++) {
var curBack = this._matchDistances[offs6 + 1];
var curAndLenPrice6 = normalMatchPrice +
this.getPosLenPrice(curBack, lenTest6, posState);
var optimum6 = this._optimum[cur + lenTest6];
if (curAndLenPrice6 < optimum6.price) {
optimum6.price = curAndLenPrice6;
optimum6.posPrev = cur;
optimum6.backPrev = curBack + Base.kNumRepDistances;
optimum6.prev1IsChar = false;
}
if (lenTest6 === this._matchDistances[offs6]) {
if (lenTest6 < numAvailableBytesFull) {
var t7 = Math.min(numAvailableBytesFull - 1 - lenTest6,
this._numFastBytes);
var lenTest27 = this._matchFinder.getMatchLen(lenTest6, curBack, t7);
if (lenTest27 >= 2) {
var state27 = Base.stateUpdateMatch(state);
var posStateNext7 = (position + lenTest6) & this._posStateMask;
var curAndLenCharPrice7 = curAndLenPrice6 +
RangeCoder.Encoder.getPrice0(this._isMatch[(state27 << Base.kNumPosStatesBitsMax) + posStateNext7]) +
this._literalEncoder.getSubCoder(position + lenTest6,
this._matchFinder.getIndexByte(lenTest6 - 2)).
getPrice(true,
this._matchFinder.getIndexByte(lenTest6 - (curBack + 1) -1),
this._matchFinder.getIndexByte(lenTest6 - 1));
state27 = Base.stateUpdateChar(state27);
posStateNext7 = (position + lenTest6 + 1) & this._posStateMask;
var nextMatchPrice7 = curAndLenCharPrice7 +
RangeCoder.Encoder.getPrice1(this._isMatch[(state27 << Base.kNumPosStatesBitsMax) + posStateNext7]);
var nextRepMatchPrice7 = nextMatchPrice7 +
RangeCoder.Encoder.getPrice1(this._isRep[state27]);
var offset7 = lenTest6 + 1 + lenTest27;
while (lenEnd < cur + offset7) {
this._optimum[++lenEnd].price = kInfinityPrice;
}
var curAndLenPrice7 = nextRepMatchPrice7 +
this.getRepPrice(0, lenTest27, state27, posStateNext7);
var optimum7 = this._optimum[cur + offset7];
if (curAndLenPrice7 < optimum7.price) {
optimum7.price = curAndLenPrice7;
optimum7.posPrev = cur + lenTest6 + 1;
optimum7.backPrev = 0;
optimum7.prev1IsChar = true;
optimum7.prev2 = true;
optimum7.posPrev2 = cur;
optimum7.backPrev2 = curBack + Base.kNumRepDistances;
}
}
}
offs6 += 2;
if (offs6 === numDistancePairs) {
break;
}
}
}
}
}
};
Encoder.prototype.changePair = function(smallDist, bigDist) {
var kDif = 7;
return (smallDist < (1 << (32 - kDif)) && bigDist >= (smallDist << kDif));
};
Encoder.prototype.writeEndMarker = function(posState) {
if (!this._writeEndMark) {
return;
}
this._rangeEncoder.encode(this._isMatch, (this._state << Base.kNumPosStatesBitsMax) + posState, 1);
this._rangeEncoder.encode(this._isRep, this._state, 0);
this._state = Base.stateUpdateMatch(this._state);
var len = Base.kMatchMinLen;
this._lenEncoder.encode(this._rangeEncoder, len - Base.kMatchMinLen, posState);
var posSlot = (1 << Base.kNumPosSlotBits) - 1;
var lenToPosState = Base.getLenToPosState(len);
this._posSlotEncoder[lenToPosState].encode(this._rangeEncoder, posSlot);
var footerBits = 30;
var posReduced = (1 << footerBits) - 1;
this._rangeEncoder.encodeDirectBits(posReduced >> Base.kNumAlignBits,
footerBits - Base.kNumAlignBits);
this._posAlignEncoder.reverseEncode(this._rangeEncoder,
posReduced & Base.kAlignMask);
};
Encoder.prototype.flush = function(nowPos) {
this.releaseMFStream();
this.writeEndMarker(nowPos & this._posStateMask);
this._rangeEncoder.flushData();
this._rangeEncoder.flushStream();
};
Encoder.prototype.codeOneBlock = function(inSize, outSize, finished) {
inSize[0] = 0;
outSize[0] = 0;
finished[0] = true;
if (this._inStream) {
this._matchFinder.setStream(this._inStream);
this._matchFinder.init();
this._needReleaseMFStream = true;
this._inStream = null;
}
if (this._finished) {
return;
}
this._finished = true;
var progressPosValuePrev = this.nowPos64;
var posState, curByte, i;
if (this.nowPos64 === 0) {
if (this._matchFinder.getNumAvailableBytes() === 0) {
this.flush(this.nowPos64);
return;
}
this.readMatchDistances();
posState = this.nowPos64 & this._posStateMask;
this._rangeEncoder.encode(this._isMatch, (this._state << Base.kNumPosStatesBitsMax) + posState, 0);
this._state = Base.stateUpdateChar(this._state);
curByte = this._matchFinder.getIndexByte(0 - this._additionalOffset);
this._literalEncoder.getSubCoder(this.nowPos64, this._previousByte).
encode(this._rangeEncoder, curByte);
this._previousByte = curByte;
this._additionalOffset--;
this.nowPos64++;
}
if (this._matchFinder.getNumAvailableBytes() === 0) {
this.flush(this.nowPos64);
return;
}
while (true) {
var len = this.getOptimum(this.nowPos64);
var pos = this.backRes;
posState = this.nowPos64 & this._posStateMask;
var complexState = (this._state << Base.kNumPosStatesBitsMax) + posState;
if (len === 1 && pos === -1) {
this._rangeEncoder.encode(this._isMatch, complexState, 0);
curByte = this._matchFinder.getIndexByte(- this._additionalOffset);
var subCoder = this._literalEncoder.getSubCoder(this.nowPos64,
this._previousByte);
if (!Base.stateIsCharState(this._state)) {
var matchByte = this._matchFinder.getIndexByte(- this._repDistances[0] - 1 - this._additionalOffset);
subCoder.encodeMatched(this._rangeEncoder, matchByte, curByte);
} else {
subCoder.encode(this._rangeEncoder, curByte);
}
this._previousByte = curByte;
this._state = Base.stateUpdateChar(this._state);
} else {
this._rangeEncoder.encode(this._isMatch, complexState, 1);
if (pos < Base.kNumRepDistances) {
this._rangeEncoder.encode(this._isRep, this._state, 1);
if (pos === 0) {
this._rangeEncoder.encode(this._isRepG0, this._state, 0);
if (len === 1) {
this._rangeEncoder.encode(this._isRep0Long, complexState, 0);
} else {
this._rangeEncoder.encode(this._isRep0Long, complexState, 1);
}
} else {
this._rangeEncoder.encode(this._isRepG0, this._state, 1);
if (pos === 1) {
this._rangeEncoder.encode(this._isRepG1, this._state, 0);
} else {
this._rangeEncoder.encode(this._isRepG1, this._state, 1);
this._rangeEncoder.encode(this._isRepG2, this._state, pos - 2);
}
}
if (len === 1) {
this._state = Base.stateUpdateShortRep(this._state);
} else {
this._repMatchLenEncoder.encode(this._rangeEncoder,
len - Base.kMatchMinLen, posState);
this._state = Base.stateUpdateRep(this._state);
}
var distance = this._repDistances[pos];
if (pos !== 0) {
for (i = pos; i >= 1; i--) {
this._repDistances[i] = this._repDistances[i - 1];
}
this._repDistances[0] = distance;
}
} else {
this._rangeEncoder.encode(this._isRep, this._state, 0);
this._state = Base.stateUpdateMatch(this._state);
this._lenEncoder.encode(this._rangeEncoder, len - Base.kMatchMinLen,
posState);
pos -= Base.kNumRepDistances;
var posSlot = getPosSlot(pos);
var lenToPosState = Base.getLenToPosState(len);
this._posSlotEncoder[lenToPosState].encode(this._rangeEncoder, posSlot);
if (posSlot >= Base.kStartPosModelIndex) {
var footerBits = ((posSlot >>> 1) - 1);
var baseVal = ((2 | (posSlot & 1)) << footerBits);
var posReduced = pos - baseVal;
if (posSlot < Base.kEndPosModelIndex) {
RangeCoder.BitTreeEncoder.reverseEncode(this._posEncoders,
baseVal - posSlot - 1,
this._rangeEncoder,
footerBits, posReduced);
} else {
this._rangeEncoder.encodeDirectBits(posReduced >> Base.kNumAlignBits, footerBits - Base.kNumAlignBits);
this._posAlignEncoder.reverseEncode(this._rangeEncoder,
posReduced & Base.kAlignMask);
this._alignPriceCount++;
}
}
var distance2 = pos;
for (i = Base.kNumRepDistances - 1; i >= 1; i--) {
this._repDistances[i] = this._repDistances[i - 1];
}
this._repDistances[0] = distance2;
this._matchPriceCount++;
}
this._previousByte =
this._matchFinder.getIndexByte(len - 1 - this._additionalOffset);
}
this._additionalOffset -= len;
this.nowPos64 += len;
if (this._additionalOffset === 0) {
// if (!_fastMode)
if (this._matchPriceCount >= (1 << 7)) {
this.fillDistancesPrices();
}
if (this._alignPriceCount >= Base.kAlignTableSize) {
this.fillAlignPrices();
}
inSize[0] = this.nowPos64;
outSize[0] = this._rangeEncoder.getProcessedSizeAdd();
if (this._matchFinder.getNumAvailableBytes() === 0) {
this.flush(this.nowPos64);
return;
}
if (this.nowPos64 - progressPosValuePrev >= (1 << 12)) {
this._finished = false;
finished[0] = false;
return;
}
}
}
};
Encoder.prototype.releaseMFStream = function() {
if (this._matchFinder && this._needReleaseMFStream) {
this._matchFinder.releaseStream();
this._needReleaseMFStream = false;
}
};
Encoder.prototype.setOutStream = function(outStream) {
this._rangeEncoder.setStream(outStream);
};
Encoder.prototype.releaseOutStream = function() {
this._rangeEncoder.releaseStream();
};
Encoder.prototype.releaseStreams = function() {
this.releaseMFStream();
this.releaseOutStream();
};
Encoder.prototype.setStreams = function(inStream, outStream, inSize, outSize) {
this._inStream = inStream;
this._finished = false;
this.create();
this.setOutStream(outStream);
this.init();
// if (!_fastMode)
if (true) {
this.fillDistancesPrices();
this.fillAlignPrices();
}
this._lenEncoder.setTableSize(this._numFastBytes + 1 - Base.kMatchMinLen);
this._lenEncoder.updateTables(1 << this._posStateBits);
this._repMatchLenEncoder.setTableSize(this._numFastBytes +
1 - Base.kMatchMinLen);
this._repMatchLenEncoder.updateTables(1 << this._posStateBits);
this.nowPos64 = 0;
};
Encoder.prototype.code = function(inStream, outStream, inSize, outSize, progress) {
this._needReleaseMFStream = false;
try {
this.setStreams(inStream, outStream, inSize, outSize);
while (true) {
this.codeOneBlock(this.processedInSize, this.processedOutSize,
this.finished);
if (this.finished[0]) {
return;
}
if (progress) {
progress.setProgress(this.processedInSize[0], this.processedOutSize[0]);
}
}
} finally {
this.releaseStreams();
}
};
Encoder.prototype.writeCoderProperties = function(outStream) {
var properties = makeBuffer(kPropSize), i;
properties[0] = ((this._posStateBits * 5 + this._numLiteralPosStateBits) * 9+
this._numLiteralContextBits);
for (i = 0; i < 4; i++) {
properties[1 + i] = (this._dictionarySize >>> (8 * i));
}
for (i = 0; i< kPropSize; i++) {
outStream.writeByte(properties[i]);
}
};
Encoder.prototype.fillDistancesPrices = function() {
var tempPrices = [];
tempPrices.length = Base.kNumFullDistances;
var i, posSlot;
for (i = Base.kStartPosModelIndex; i < Base.kNumFullDistances; i++) {
posSlot = getPosSlot(i);
var footerBits = ((posSlot >>> 1) - 1);
var baseVal = ((2 | (posSlot & 1)) << footerBits);
tempPrices[i] =
RangeCoder.BitTreeEncoder.reverseGetPrice(this._posEncoders,
baseVal - posSlot - 1,
footerBits, i - baseVal);
}
var lenToPosState = 0;
for ( ; lenToPosState < Base.kNumLenToPosStates; lenToPosState++) {
var encoder = this._posSlotEncoder[lenToPosState];
var st = (lenToPosState << Base.kNumPosSlotBits);
for (posSlot = 0; posSlot < this._distTableSize; posSlot++) {
this._posSlotPrices[st + posSlot] = encoder.getPrice(posSlot);
}
for (posSlot = Base.kEndPosModelIndex; posSlot < this._distTableSize; posSlot++) {
this._posSlotPrices[st + posSlot] += ((((posSlot >>> 1) - 1) - Base.kNumAlignBits) << RangeCoder.Encoder.kNumBitPriceShiftBits);
}
var st2 = lenToPosState * Base.kNumFullDistances;
for (i = 0; i < Base.kStartPosModelIndex; i++) {
this._distancesPrices[st2 + i] = this._posSlotPrices[st + i];
}
for (; i < Base.kNumFullDistances; i++) {
this._distancesPrices[st2 + i] =
this._posSlotPrices[st + getPosSlot(i)] + tempPrices[i];
}
}
this._matchPriceCount = 0;
};
Encoder.prototype.fillAlignPrices = function() {
var i;
for (i = 0; i < Base.kAlignTableSize; i++) {
this._alignPrices[i] = this._posAlignEncoder.reverseGetPrice(i);
}
this._alignPriceCount = 0;
};
Encoder.prototype.setAlgorithm = function(algorithm) {
/*
_fastMode = (algorithm == 0);
_maxMode = (algorithm >= 2);
*/
return true;
};
Encoder.prototype.setDictionarySize = function(dictionarySize) {
var kDicLogSizeMaxCompress = 29;
if (dictionarySize < (1 << Base.kDicLogSizeMin) ||
dictionarySize > (1 << kDicLogSizeMaxCompress)) {
return false;
}
this._dictionarySize = dictionarySize;
var dicLogSize = 0;
while (dictionarySize > (1 << dicLogSize)) {
dicLogSize++;
}
this._distTableSize = dicLogSize * 2;
return true;
};
Encoder.prototype.setNumFastBytes = function(numFastBytes) {
if (numFastBytes < 5 || numFastBytes > Base.kMatchMaxLen) {
return false;
}
this._numFastBytes = numFastBytes;
return true;
};
Encoder.prototype.setMatchFinder = function(matchFinderIndex) {
if (matchFinderIndex < 0 || matchFinderIndex > 2) {
return false;
}
var matchFinderIndexPrev = this._matchFinderType;
this._matchFinderType = matchFinderIndex;
if (this._matchFinder && matchFinderIndexPrev != this._matchFinderType) {
this._dictionarySizePrev = -1;
this._matchFinder = null;
}
return true;
};
Encoder.prototype.setLcLpPb = function(lc, lp, pb) {
if (lp < 0 || lp > Base.kNumLitPosStatesBitsEncodingMax ||
lc < 0 || lc > Base.kNumLitContextBitsMax ||
pb < 0 || pb > Base.kNumPosStatesBitsEncodingMax) {
return false;
}
this._numLiteralPosStateBits = lp;
this._numLiteralContextBits = lc;
this._posStateBits = pb;
this._posStateMask = ((1) << this._posStateBits) - 1;
return true;
};
Encoder.prototype.setEndMarkerMode = function(endMarkerMode) {
this._writeEndMark = endMarkerMode;
};
Encoder.EMatchFinderTypeBT2 = EMatchFinderTypeBT2;
Encoder.EMatchFinderTypeBT4 = EMatchFinderTypeBT4;
freeze(Encoder.prototype);
module.exports = freeze(Encoder);