@awayjs/graphics
Version:
AwayJS graphics classes
804 lines (707 loc) • 20 kB
text/typescript
/*
* Copyright 2015 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// The code derived from:
/* LzmaSpec.c -- LZMA Reference Decoder
2013-07-28 : Igor Pavlov : Public domain */
import { IDataDecoder } from './utilities';
class InputStream {
available: number;
pos: number;
buffer: Uint8Array;
constructor() {
this.available = 0;
this.pos = 0;
this.buffer = new Uint8Array(2000);
}
append(data: Uint8Array) {
const length = this.pos + this.available;
const needLength = length + data.length;
if (needLength > this.buffer.length) {
let newLength = this.buffer.length * 2;
while (newLength < needLength) {
newLength *= 2;
}
const newBuffer = new Uint8Array(newLength);
newBuffer.set(this.buffer);
this.buffer = newBuffer;
}
this.buffer.set(data, length);
this.available += data.length;
}
compact(): void {
if (this.available === 0) {
return;
}
this.buffer.set(this.buffer.subarray(this.pos, this.pos + this.available), 0);
this.pos = 0;
}
readByte(): number {
if (this.available <= 0) {
throw new Error('Unexpected end of file');
}
this.available--;
return this.buffer[this.pos++];
}
}
class OutputStream {
onData: (data: Uint8Array) => void;
processed: number;
constructor (onData: (data: Uint8Array) => void) {
this.onData = onData;
this.processed = 0;
}
writeBytes(data: Uint8Array) {
this.onData.call(null, data);
this.processed += data.length;
}
}
class OutWindow {
outStream: OutputStream;
buf: Uint8Array;
pos: number;
size: number;
isFull: boolean;
writePos: number;
totalPos: number;
constructor(outStream: OutputStream) {
this.outStream = outStream;
this.buf = null;
this.pos = 0;
this.size = 0;
this.isFull = false;
this.writePos = 0;
this.totalPos = 0;
}
create(dictSize: number): void {
this.buf = new Uint8Array(dictSize);
this.pos = 0;
this.size = dictSize;
this.isFull = false;
this.writePos = 0;
this.totalPos = 0;
}
putByte(b: number): void {
this.totalPos++;
this.buf[this.pos++] = b;
if (this.pos === this.size) {
this.flush();
this.pos = 0;
this.isFull = true;
}
}
getByte(dist: number): number {
return this.buf[dist <= this.pos ? this.pos - dist : this.size - dist + this.pos];
}
flush(): void {
if (this.writePos < this.pos) {
this.outStream.writeBytes(this.buf.subarray(this.writePos, this.pos));
this.writePos = this.pos === this.size ? 0 : this.pos;
}
}
copyMatch(dist: number, len: number): void {
let pos = this.pos;
const size = this.size;
const buffer = this.buf;
let getPos = dist <= pos ? pos - dist : size - dist + pos;
let left = len;
while (left > 0) {
const chunk = Math.min(Math.min(left, size - pos), size - getPos);
for (let i = 0; i < chunk; i++) {
const b = buffer[getPos++];
buffer[pos++] = b;
}
if (pos === size) {
this.pos = pos;
this.flush();
pos = 0;
this.isFull = true;
}
if (getPos === size) {
getPos = 0;
}
left -= chunk;
}
this.pos = pos;
this.totalPos += len;
}
checkDistance(dist: number): boolean {
return dist <= this.pos || this.isFull;
}
isEmpty(): boolean {
return this.pos === 0 && !this.isFull;
}
}
const kNumBitModelTotalBits = 11;
const kNumMoveBits = 5;
const PROB_INIT_VAL = ((1 << kNumBitModelTotalBits) >> 1);
function createProbsArray(length: number): Uint16Array {
const p = new Uint16Array(length);
for (let i = 0; i < length; i++) {
p[i] = PROB_INIT_VAL;
}
return p;
}
const kTopValue = 1 << 24;
class RangeDecoder {
inStream: InputStream;
range: number;
code: number;
corrupted: boolean;
constructor(inStream: InputStream) {
this.inStream = inStream;
this.range = 0;
this.code = 0;
this.corrupted = false;
}
init(): void {
if (this.inStream.readByte() !== 0) {
this.corrupted = true;
}
this.range = 0xFFFFFFFF | 0;
let code = 0;
for (let i = 0; i < 4; i++) {
code = (code << 8) | this.inStream.readByte();
}
if (code === this.range) {
this.corrupted = true;
}
this.code = code;
}
isFinishedOK(): boolean {
return this.code === 0;
}
decodeDirectBits(numBits: number): number {
let res = 0;
let range = this.range;
let code = this.code;
do {
range = (range >>> 1) | 0;
code = (code - range) | 0;
const t = code >> 31; // if high bit set -1, otherwise 0
code = (code + (range & t)) | 0;
if (code === range) {
this.corrupted = true;
}
if (range >= 0 && range < kTopValue) {
range = range << 8;
code = (code << 8) | this.inStream.readByte();
}
res = ((res << 1) + t + 1) | 0;
} while (--numBits);
this.range = range;
this.code = code;
return res;
}
decodeBit(prob: Uint16Array, index: number): number {
let range = this.range;
let code = this.code;
let v = prob[index];
const bound = (range >>> kNumBitModelTotalBits) * v; // keep unsigned
let symbol;
if ((code >>> 0) < bound) {
v = (v + (((1 << kNumBitModelTotalBits) - v) >> kNumMoveBits)) | 0;
range = bound | 0;
symbol = 0;
} else {
v = (v - (v >> kNumMoveBits)) | 0;
code = (code - bound) | 0;
range = (range - bound) | 0;
symbol = 1;
}
prob[index] = v & 0xFFFF;
if (range >= 0 && range < kTopValue) {
range = range << 8;
code = (code << 8) | this.inStream.readByte();
}
this.range = range;
this.code = code;
return symbol;
}
}
function bitTreeReverseDecode(probs: Uint16Array, offset: number,
numBits: number, rc: RangeDecoder): number {
let m = 1;
let symbol = 0;
for (let i = 0; i < numBits; i++) {
const bit = rc.decodeBit(probs, m + offset);
m = (m << 1) + bit;
symbol |= bit << i;
}
return symbol;
}
class BitTreeDecoder {
numBits: number;
probs: Uint16Array;
constructor(numBits: number) {
this.numBits = numBits;
this.probs = createProbsArray(1 << numBits);
}
decode(rc: RangeDecoder) {
let m = 1;
for (let i = 0; i < this.numBits; i++) {
m = (m << 1) + rc.decodeBit(this.probs, m);
}
return m - (1 << this.numBits);
}
reverseDecode(rc: RangeDecoder) {
return bitTreeReverseDecode(this.probs, 0, this.numBits, rc);
}
}
function createBitTreeDecoderArray(numBits: number, length: number): BitTreeDecoder[] {
const p: BitTreeDecoder[] = [];
p.length = length;
for (let i = 0; i < length; i++) {
p[i] = new BitTreeDecoder(numBits);
}
return p;
}
const kNumPosBitsMax = 4;
const kNumStates = 12;
const kNumLenToPosStates = 4;
const kNumAlignBits = 4;
//const kStartPosModelIndex = 4;
const kEndPosModelIndex = 14;
const kNumFullDistances = 1 << (kEndPosModelIndex >> 1);
const kMatchMinLen = 2;
class LenDecoder {
choice: Uint16Array;
lowCoder: BitTreeDecoder[];
midCoder: BitTreeDecoder[];
highCoder: BitTreeDecoder;
constructor() {
this.choice = createProbsArray(2);
this.lowCoder = createBitTreeDecoderArray(3, 1 << kNumPosBitsMax);
this.midCoder = createBitTreeDecoderArray(3, 1 << kNumPosBitsMax);
this.highCoder = new BitTreeDecoder(8);
}
decode(rc: RangeDecoder, posState: number): number {
if (rc.decodeBit(this.choice, 0) === 0) {
return this.lowCoder[posState].decode(rc);
}
if (rc.decodeBit(this.choice, 1) === 0) {
return 8 + this.midCoder[posState].decode(rc);
}
return 16 + this.highCoder.decode(rc);
}
}
function updateState_Literal(state: number): number {
if (state < 4) {
return 0;
} else if (state < 10) {
return state - 3;
} else {
return state - 6;
}
}
function updateState_Match(state: number): number {
return state < 7 ? 7 : 10;
}
function updateState_Rep(state: number): number {
return state < 7 ? 8 : 11;
}
function updateState_ShortRep(state: number): number {
return state < 7 ? 9 : 11;
}
const LZMA_DIC_MIN = 1 << 12;
const MAX_DECODE_BITS_CALLS = 48;
class LzmaDecoderInternal {
rangeDec: RangeDecoder;
outWindow: OutWindow;
markerIsMandatory: boolean;
lc: number;
pb: number;
lp: number;
dictSize: number;
dictSizeInProperties: number;
unpackSize: number;
leftToUnpack: number;
reps: Int32Array;
state: number;
constructor(inStream: InputStream, outStream: OutputStream) {
this.rangeDec = new RangeDecoder(inStream);
this.outWindow = new OutWindow(outStream);
this.markerIsMandatory = false;
this.lc = 0;
this.pb = 0;
this.lp = 0;
this.dictSize = 0;
this.dictSizeInProperties = 0;
this.unpackSize = undefined;
this.leftToUnpack = undefined;
this.reps = new Int32Array(4);
this.state = 0;
}
decodeProperties(properties: Uint8Array) {
let d = properties[0];
if (d >= (9 * 5 * 5)) {
throw new Error('Incorrect LZMA properties');
}
this.lc = d % 9;
d = (d / 9) | 0;
this.pb = (d / 5) | 0;
this.lp = d % 5;
this.dictSizeInProperties = 0;
for (let i = 0; i < 4; i++) {
this.dictSizeInProperties |= properties[i + 1] << (8 * i);
}
this.dictSize = this.dictSizeInProperties;
if (this.dictSize < LZMA_DIC_MIN) {
this.dictSize = LZMA_DIC_MIN;
}
}
create(): void {
this.outWindow.create(this.dictSize);
this.init();
this.rangeDec.init();
this.reps[0] = 0;
this.reps[1] = 0;
this.reps[2] = 0;
this.reps[3] = 0;
this.state = 0;
this.leftToUnpack = this.unpackSize;
}
decodeLiteral(state: number, rep0: number): number {
const outWindow = this.outWindow;
const rangeDec = this.rangeDec;
let prevByte = 0;
if (!outWindow.isEmpty()) {
prevByte = outWindow.getByte(1);
}
let symbol = 1;
const litState = ((outWindow.totalPos & ((1 << this.lp) - 1)) << this.lc) + (prevByte >> (8 - this.lc));
const probsIndex = 0x300 * litState;
if (state >= 7) {
let matchByte = outWindow.getByte(rep0 + 1);
do {
const matchBit = (matchByte >> 7) & 1;
matchByte <<= 1;
const bit = rangeDec.decodeBit(this.litProbs, probsIndex + (((1 + matchBit) << 8) + symbol));
symbol = (symbol << 1) | bit;
if (matchBit !== bit) {
break;
}
} while (symbol < 0x100);
}
while (symbol < 0x100) {
symbol =
(symbol << 1) | rangeDec.decodeBit(this.litProbs, probsIndex + symbol);
}
return (symbol - 0x100) & 0xFF;
}
decodeDistance(len: number) {
let lenState = len;
if (lenState > kNumLenToPosStates - 1) {
lenState = kNumLenToPosStates - 1;
}
const rangeDec = this.rangeDec;
const posSlot = this.posSlotDecoder[lenState].decode(rangeDec);
if (posSlot < 4) {
return posSlot;
}
const numDirectBits = (posSlot >> 1) - 1;
let dist = (2 | (posSlot & 1)) << numDirectBits;
if (posSlot < kEndPosModelIndex) {
dist =
(dist + bitTreeReverseDecode(this.posDecoders, dist - posSlot, numDirectBits, rangeDec)) | 0;
} else {
dist =
(dist + (rangeDec.decodeDirectBits(numDirectBits - kNumAlignBits) << kNumAlignBits)) | 0;
dist = (dist + this.alignDecoder.reverseDecode(rangeDec)) | 0;
}
return dist;
}
litProbs: Uint16Array;
posSlotDecoder: BitTreeDecoder[];
alignDecoder: BitTreeDecoder;
posDecoders: Uint16Array;
isMatch: Uint16Array;
isRep: Uint16Array;
isRepG0: Uint16Array;
isRepG1: Uint16Array;
isRepG2: Uint16Array;
isRep0Long: Uint16Array;
lenDecoder: LenDecoder;
repLenDecoder: LenDecoder;
init() {
this.litProbs = createProbsArray(0x300 << (this.lc + this.lp));
this.posSlotDecoder = createBitTreeDecoderArray(6, kNumLenToPosStates);
this.alignDecoder = new BitTreeDecoder(kNumAlignBits);
this.posDecoders =
createProbsArray(1 + kNumFullDistances - kEndPosModelIndex);
this.isMatch = createProbsArray(kNumStates << kNumPosBitsMax);
this.isRep = createProbsArray(kNumStates);
this.isRepG0 = createProbsArray(kNumStates);
this.isRepG1 = createProbsArray(kNumStates);
this.isRepG2 = createProbsArray(kNumStates);
this.isRep0Long = createProbsArray(kNumStates << kNumPosBitsMax);
this.lenDecoder = new LenDecoder();
this.repLenDecoder = new LenDecoder();
}
decode(notFinal: boolean): number {
const rangeDec = this.rangeDec;
const outWindow = this.outWindow;
const pb = this.pb;
const dictSize = this.dictSize;
const markerIsMandatory = this.markerIsMandatory;
let leftToUnpack = this.leftToUnpack;
const isMatch = this.isMatch;
const isRep = this.isRep;
const isRepG0 = this.isRepG0;
const isRepG1 = this.isRepG1;
const isRepG2 = this.isRepG2;
const isRep0Long = this.isRep0Long;
const lenDecoder = this.lenDecoder;
const repLenDecoder = this.repLenDecoder;
let rep0 = this.reps[0];
let rep1 = this.reps[1];
let rep2 = this.reps[2];
let rep3 = this.reps[3];
let state = this.state;
while (true) {
// Based on worse case scenario one byte consumed per decodeBit calls,
// reserving keeping some amount of bytes in the input stream for
// non-final data blocks.
if (notFinal && rangeDec.inStream.available < MAX_DECODE_BITS_CALLS) {
this.outWindow.flush();
break;
}
if (leftToUnpack === 0 && !markerIsMandatory) {
this.outWindow.flush();
if (rangeDec.isFinishedOK()) {
return LZMA_RES_FINISHED_WITHOUT_MARKER;
}
}
const posState = outWindow.totalPos & ((1 << pb) - 1);
if (rangeDec.decodeBit(isMatch, (state << kNumPosBitsMax) + posState) === 0) {
if (leftToUnpack === 0) {
return LZMA_RES_ERROR;
}
outWindow.putByte(this.decodeLiteral(state, rep0));
state = updateState_Literal(state);
leftToUnpack--;
continue;
}
var len;
if (rangeDec.decodeBit(isRep, state) !== 0) {
if (leftToUnpack === 0) {
return LZMA_RES_ERROR;
}
if (outWindow.isEmpty()) {
return LZMA_RES_ERROR;
}
if (rangeDec.decodeBit(isRepG0, state) === 0) {
if (rangeDec.decodeBit(isRep0Long, (state << kNumPosBitsMax) + posState) === 0) {
state = updateState_ShortRep(state);
outWindow.putByte(outWindow.getByte(rep0 + 1));
leftToUnpack--;
continue;
}
} else {
var dist;
if (rangeDec.decodeBit(isRepG1, state) === 0) {
dist = rep1;
} else {
if (rangeDec.decodeBit(isRepG2, state) === 0) {
dist = rep2;
} else {
dist = rep3;
rep3 = rep2;
}
rep2 = rep1;
}
rep1 = rep0;
rep0 = dist;
}
len = repLenDecoder.decode(rangeDec, posState);
state = updateState_Rep(state);
} else {
rep3 = rep2;
rep2 = rep1;
rep1 = rep0;
len = lenDecoder.decode(rangeDec, posState);
state = updateState_Match(state);
rep0 = this.decodeDistance(len);
if (rep0 === -1) {
this.outWindow.flush();
return rangeDec.isFinishedOK() ?
LZMA_RES_FINISHED_WITH_MARKER :
LZMA_RES_ERROR;
}
if (leftToUnpack === 0) {
return LZMA_RES_ERROR;
}
if (rep0 >= dictSize || !outWindow.checkDistance(rep0)) {
return LZMA_RES_ERROR;
}
}
len += kMatchMinLen;
let isError = false;
if (leftToUnpack !== undefined && leftToUnpack < len) {
len = leftToUnpack;
isError = true;
}
outWindow.copyMatch(rep0 + 1, len);
leftToUnpack -= len;
if (isError) {
return LZMA_RES_ERROR;
}
}
this.state = state;
this.reps[0] = rep0;
this.reps[1] = rep1;
this.reps[2] = rep2;
this.reps[3] = rep3;
this.leftToUnpack = leftToUnpack;
return LZMA_RES_NOT_COMPLETE;
}
flushOutput(): void {
this.outWindow.flush();
}
}
var LZMA_RES_ERROR = 0;
var LZMA_RES_FINISHED_WITH_MARKER = 1;
var LZMA_RES_FINISHED_WITHOUT_MARKER = 2;
var LZMA_RES_NOT_COMPLETE = 3;
const SWF_LZMA_HEADER_LENGTH = 17;
const STANDARD_LZMA_HEADER_LENGTH = 13;
const EXTRA_LZMA_BYTES_NEEDED = 5;
enum LzmaDecoderState {
WAIT_FOR_LZMA_HEADER = 0,
WAIT_FOR_SWF_HEADER = 1,
PROCESS_DATA = 2,
CLOSED = 3,
ERROR = 4
}
export class LzmaDecoder implements IDataDecoder {
public onData: (data: Uint8Array) => void;
public onError: (e) => void;
private _state: LzmaDecoderState;
buffer: Uint8Array;
private _inStream: InputStream;
private _outStream: OutputStream;
private _decoder: LzmaDecoderInternal;
constructor(swfHeader: boolean = false) {
this._state = swfHeader ? LzmaDecoderState.WAIT_FOR_SWF_HEADER :
LzmaDecoderState.WAIT_FOR_LZMA_HEADER;
this.buffer = null;
}
public push(data: Uint8Array) {
if (this._state < LzmaDecoderState.PROCESS_DATA) {
const buffered = this.buffer ? this.buffer.length : 0;
const headerBytesExpected =
(this._state === LzmaDecoderState.WAIT_FOR_SWF_HEADER
? SWF_LZMA_HEADER_LENGTH
: STANDARD_LZMA_HEADER_LENGTH)
+ EXTRA_LZMA_BYTES_NEEDED;
if (buffered + data.length < headerBytesExpected) {
const newBuffer = new Uint8Array(buffered + data.length);
if (buffered > 0) {
newBuffer.set(this.buffer);
}
newBuffer.set(data, buffered);
this.buffer = newBuffer;
return;
}
const header = new Uint8Array(headerBytesExpected);
if (buffered > 0) {
header.set(this.buffer);
}
header.set(data.subarray(0, headerBytesExpected - buffered), buffered);
this._inStream = new InputStream();
this._inStream.append(header.subarray(headerBytesExpected - EXTRA_LZMA_BYTES_NEEDED));
this._outStream = new OutputStream(function (data) {
this.onData.call(null, data);
}.bind(this));
this._decoder = new LzmaDecoderInternal(this._inStream, this._outStream);
// See http://helpx.adobe.com/flash-player/kb/exception-thrown-you-decompress-lzma-compressed.html
if (this._state === LzmaDecoderState.WAIT_FOR_SWF_HEADER) {
this._decoder.decodeProperties(header.subarray(12, 17));
this._decoder.markerIsMandatory = false;
this._decoder.unpackSize = ((header[4] | (header[5] << 8) |
(header[6] << 16) | (header[7] << 24)) >>> 0) - 8;
} else {
this._decoder.decodeProperties(header.subarray(0, 5));
let unpackSize = 0;
let unpackSizeDefined = false;
for (let i = 0; i < 8; i++) {
const b = header[5 + i];
if (b !== 0xFF) {
unpackSizeDefined = true;
}
unpackSize |= b << (8 * i);
}
this._decoder.markerIsMandatory = !unpackSizeDefined;
this._decoder.unpackSize = unpackSizeDefined ? unpackSize : undefined;
}
this._decoder.create();
data = data.subarray(headerBytesExpected);
this._state = LzmaDecoderState.PROCESS_DATA;
} else if (this._state !== LzmaDecoderState.PROCESS_DATA) {
return;
}
try {
this._inStream.append(data);
const res = this._decoder.decode(true);
this._inStream.compact();
if (res !== LZMA_RES_NOT_COMPLETE) {
this._checkError(res);
}
} catch (e) {
this._decoder.flushOutput();
this._decoder = null;
this._error(e);
}
}
public close() {
if (this._state !== LzmaDecoderState.PROCESS_DATA) {
return;
}
this._state = LzmaDecoderState.CLOSED;
try {
const res = this._decoder.decode(false);
this._checkError(res);
} catch (e) {
this._decoder.flushOutput();
this._error(e);
}
this._decoder = null;
}
private _error(error) {
// Stopping processing any data if an error occurs.
this._state = LzmaDecoderState.ERROR;
if (this.onError) {
this.onError(error);
}
}
private _checkError(res) {
let error;
if (res === LZMA_RES_ERROR) {
error = 'LZMA decoding error';
} else if (res === LZMA_RES_NOT_COMPLETE) {
error = 'Decoding is not complete';
} else if (res === LZMA_RES_FINISHED_WITH_MARKER) {
if (this._decoder.unpackSize !== undefined &&
this._decoder.unpackSize !== this._outStream.processed) {
error = 'Finished with end marker before than specified size';
}
} else {
error = 'Internal LZMA Error';
}
if (error) {
this._error(error);
}
}
}