Files
dss/static/js/lib/jdataview.js
2014-01-08 22:32:26 +00:00

763 lines
21 KiB
JavaScript

//
// jDataView by Vjeux <vjeuxx@gmail.com> - Jan 2010
// Continued by RReverser <me@rreverser.com> - Feb 2013
//
// A unique way to work with a binary file in the browser
// http://github.com/jDataView/jDataView
// http://jDataView.github.io/
(function (global) {
'use strict';
var compatibility = {
// NodeJS Buffer in v0.5.5 and newer
NodeBuffer: 'Buffer' in global && 'readInt16LE' in Buffer.prototype,
DataView: 'DataView' in global && (
'getFloat64' in DataView.prototype || // Chrome
'getFloat64' in new DataView(new ArrayBuffer(1)) // Node
),
ArrayBuffer: 'ArrayBuffer' in global,
PixelData: 'CanvasPixelArray' in global && 'ImageData' in global && 'document' in global
};
// we don't want to bother with old Buffer implementation
if (compatibility.NodeBuffer) {
(function (buffer) {
try {
buffer.writeFloatLE(Infinity, 0);
} catch (e) {
compatibility.NodeBuffer = false;
}
})(new Buffer(4));
}
if (compatibility.PixelData) {
var createPixelData = function (byteLength, buffer) {
var data = createPixelData.context2d.createImageData((byteLength + 3) / 4, 1).data;
data.byteLength = byteLength;
if (buffer !== undefined) {
for (var i = 0; i < byteLength; i++) {
data[i] = buffer[i];
}
}
return data;
};
createPixelData.context2d = document.createElement('canvas').getContext('2d');
}
var dataTypes = {
'Int8': 1,
'Int16': 2,
'Int32': 4,
'Uint8': 1,
'Uint16': 2,
'Uint32': 4,
'Float32': 4,
'Float64': 8
};
var nodeNaming = {
'Int8': 'Int8',
'Int16': 'Int16',
'Int32': 'Int32',
'Uint8': 'UInt8',
'Uint16': 'UInt16',
'Uint32': 'UInt32',
'Float32': 'Float',
'Float64': 'Double'
};
function arrayFrom(arrayLike, forceCopy) {
return (!forceCopy && (arrayLike instanceof Array)) ? arrayLike : Array.prototype.slice.call(arrayLike);
}
function defined(value, defaultValue) {
return value !== undefined ? value : defaultValue;
}
function jDataView(buffer, byteOffset, byteLength, littleEndian) {
/* jshint validthis:true */
if (buffer instanceof jDataView) {
var result = buffer.slice(byteOffset, byteOffset + byteLength);
result._littleEndian = defined(littleEndian, result._littleEndian);
return result;
}
if (!(this instanceof jDataView)) {
return new jDataView(buffer, byteOffset, byteLength, littleEndian);
}
this.buffer = buffer = jDataView.wrapBuffer(buffer);
// Check parameters and existing functionnalities
this._isArrayBuffer = compatibility.ArrayBuffer && buffer instanceof ArrayBuffer;
this._isPixelData = compatibility.PixelData && buffer instanceof CanvasPixelArray;
this._isDataView = compatibility.DataView && this._isArrayBuffer;
this._isNodeBuffer = compatibility.NodeBuffer && buffer instanceof Buffer;
// Handle Type Errors
if (!this._isNodeBuffer && !this._isArrayBuffer && !this._isPixelData && !(buffer instanceof Array)) {
throw new TypeError('jDataView buffer has an incompatible type');
}
// Default Values
this._littleEndian = !!littleEndian;
var bufferLength = 'byteLength' in buffer ? buffer.byteLength : buffer.length;
this.byteOffset = byteOffset = defined(byteOffset, 0);
this.byteLength = byteLength = defined(byteLength, bufferLength - byteOffset);
if (!this._isDataView) {
this._checkBounds(byteOffset, byteLength, bufferLength);
} else {
this._view = new DataView(buffer, byteOffset, byteLength);
}
// Create uniform methods (action wrappers) for the following data types
this._engineAction =
this._isDataView
? this._dataViewAction
: this._isNodeBuffer
? this._nodeBufferAction
: this._isArrayBuffer
? this._arrayBufferAction
: this._arrayAction;
}
function getCharCodes(string) {
if (compatibility.NodeBuffer) {
return new Buffer(string, 'binary');
}
var Type = compatibility.ArrayBuffer ? Uint8Array : Array,
codes = new Type(string.length);
for (var i = 0, length = string.length; i < length; i++) {
codes[i] = string.charCodeAt(i) & 0xff;
}
return codes;
}
// mostly internal function for wrapping any supported input (String or Array-like) to best suitable buffer format
jDataView.wrapBuffer = function (buffer) {
switch (typeof buffer) {
case 'number':
if (compatibility.NodeBuffer) {
buffer = new Buffer(buffer);
buffer.fill(0);
} else
if (compatibility.ArrayBuffer) {
buffer = new Uint8Array(buffer).buffer;
} else
if (compatibility.PixelData) {
buffer = createPixelData(buffer);
} else {
buffer = new Array(buffer);
for (var i = 0; i < buffer.length; i++) {
buffer[i] = 0;
}
}
return buffer;
case 'string':
buffer = getCharCodes(buffer);
/* falls through */
default:
if ('length' in buffer && !((compatibility.NodeBuffer && buffer instanceof Buffer) || (compatibility.ArrayBuffer && buffer instanceof ArrayBuffer) || (compatibility.PixelData && buffer instanceof CanvasPixelArray))) {
if (compatibility.NodeBuffer) {
buffer = new Buffer(buffer);
} else
if (compatibility.ArrayBuffer) {
if (!(buffer instanceof ArrayBuffer)) {
buffer = new Uint8Array(buffer).buffer;
// bug in Node.js <= 0.8:
if (!(buffer instanceof ArrayBuffer)) {
buffer = new Uint8Array(arrayFrom(buffer, true)).buffer;
}
}
} else
if (compatibility.PixelData) {
buffer = createPixelData(buffer.length, buffer);
} else {
buffer = arrayFrom(buffer);
}
}
return buffer;
}
};
function pow2(n) {
return (n >= 0 && n < 31) ? (1 << n) : (pow2[n] || (pow2[n] = Math.pow(2, n)));
}
// left for backward compatibility
jDataView.createBuffer = function () {
return jDataView.wrapBuffer(arguments);
};
function Uint64(lo, hi) {
this.lo = lo;
this.hi = hi;
}
jDataView.Uint64 = Uint64;
Uint64.prototype = {
valueOf: function () {
return this.lo + pow2(32) * this.hi;
},
toString: function () {
return Number.prototype.toString.apply(this.valueOf(), arguments);
}
};
Uint64.fromNumber = function (number) {
var hi = Math.floor(number / pow2(32)),
lo = number - hi * pow2(32);
return new Uint64(lo, hi);
};
function Int64(lo, hi) {
Uint64.apply(this, arguments);
}
jDataView.Int64 = Int64;
Int64.prototype = 'create' in Object ? Object.create(Uint64.prototype) : new Uint64();
Int64.prototype.valueOf = function () {
if (this.hi < pow2(31)) {
return Uint64.prototype.valueOf.apply(this, arguments);
}
return -((pow2(32) - this.lo) + pow2(32) * (pow2(32) - 1 - this.hi));
};
Int64.fromNumber = function (number) {
var lo, hi;
if (number >= 0) {
var unsigned = Uint64.fromNumber(number);
lo = unsigned.lo;
hi = unsigned.hi;
} else {
hi = Math.floor(number / pow2(32));
lo = number - hi * pow2(32);
hi += pow2(32);
}
return new Int64(lo, hi);
};
jDataView.prototype = {
_offset: 0,
_bitOffset: 0,
compatibility: compatibility,
_checkBounds: function (byteOffset, byteLength, maxLength) {
// Do additional checks to simulate DataView
if (typeof byteOffset !== 'number') {
throw new TypeError('Offset is not a number.');
}
if (typeof byteLength !== 'number') {
throw new TypeError('Size is not a number.');
}
if (byteLength < 0) {
throw new RangeError('Length is negative.');
}
if (byteOffset < 0 || byteOffset + byteLength > defined(maxLength, this.byteLength)) {
throw new RangeError('Offsets are out of bounds.');
}
},
_action: function (type, isReadAction, byteOffset, littleEndian, value) {
return this._engineAction(
type,
isReadAction,
defined(byteOffset, this._offset),
defined(littleEndian, this._littleEndian),
value
);
},
_dataViewAction: function (type, isReadAction, byteOffset, littleEndian, value) {
// Move the internal offset forward
this._offset = byteOffset + dataTypes[type];
return isReadAction ? this._view['get' + type](byteOffset, littleEndian) : this._view['set' + type](byteOffset, value, littleEndian);
},
_nodeBufferAction: function (type, isReadAction, byteOffset, littleEndian, value) {
// Move the internal offset forward
this._offset = byteOffset + dataTypes[type];
var nodeName = nodeNaming[type] + ((type === 'Int8' || type === 'Uint8') ? '' : littleEndian ? 'LE' : 'BE');
byteOffset += this.byteOffset;
return isReadAction ? this.buffer['read' + nodeName](byteOffset) : this.buffer['write' + nodeName](value, byteOffset);
},
_arrayBufferAction: function (type, isReadAction, byteOffset, littleEndian, value) {
var size = dataTypes[type], TypedArray = global[type + 'Array'], typedArray;
littleEndian = defined(littleEndian, this._littleEndian);
// ArrayBuffer: we use a typed array of size 1 from original buffer if alignment is good and from slice when it's not
if (size === 1 || ((this.byteOffset + byteOffset) % size === 0 && littleEndian)) {
typedArray = new TypedArray(this.buffer, this.byteOffset + byteOffset, 1);
this._offset = byteOffset + size;
return isReadAction ? typedArray[0] : (typedArray[0] = value);
} else {
var bytes = new Uint8Array(isReadAction ? this.getBytes(size, byteOffset, littleEndian, true) : size);
typedArray = new TypedArray(bytes.buffer, 0, 1);
if (isReadAction) {
return typedArray[0];
} else {
typedArray[0] = value;
this._setBytes(byteOffset, bytes, littleEndian);
}
}
},
_arrayAction: function (type, isReadAction, byteOffset, littleEndian, value) {
return isReadAction ? this['_get' + type](byteOffset, littleEndian) : this['_set' + type](byteOffset, value, littleEndian);
},
// Helpers
_getBytes: function (length, byteOffset, littleEndian) {
littleEndian = defined(littleEndian, this._littleEndian);
byteOffset = defined(byteOffset, this._offset);
length = defined(length, this.byteLength - byteOffset);
this._checkBounds(byteOffset, length);
byteOffset += this.byteOffset;
this._offset = byteOffset - this.byteOffset + length;
var result = this._isArrayBuffer
? new Uint8Array(this.buffer, byteOffset, length)
: (this.buffer.slice || Array.prototype.slice).call(this.buffer, byteOffset, byteOffset + length);
return littleEndian || length <= 1 ? result : arrayFrom(result).reverse();
},
// wrapper for external calls (do not return inner buffer directly to prevent it's modifying)
getBytes: function (length, byteOffset, littleEndian, toArray) {
var result = this._getBytes(length, byteOffset, defined(littleEndian, true));
return toArray ? arrayFrom(result) : result;
},
_setBytes: function (byteOffset, bytes, littleEndian) {
var length = bytes.length;
// needed for Opera
if (length === 0) {
return;
}
littleEndian = defined(littleEndian, this._littleEndian);
byteOffset = defined(byteOffset, this._offset);
this._checkBounds(byteOffset, length);
if (!littleEndian && length > 1) {
bytes = arrayFrom(bytes, true).reverse();
}
byteOffset += this.byteOffset;
if (this._isArrayBuffer) {
new Uint8Array(this.buffer, byteOffset, length).set(bytes);
}
else {
if (this._isNodeBuffer) {
new Buffer(bytes).copy(this.buffer, byteOffset);
} else {
for (var i = 0; i < length; i++) {
this.buffer[byteOffset + i] = bytes[i];
}
}
}
this._offset = byteOffset - this.byteOffset + length;
},
setBytes: function (byteOffset, bytes, littleEndian) {
this._setBytes(byteOffset, bytes, defined(littleEndian, true));
},
getString: function (byteLength, byteOffset, encoding) {
if (this._isNodeBuffer) {
byteOffset = defined(byteOffset, this._offset);
byteLength = defined(byteLength, this.byteLength - byteOffset);
this._checkBounds(byteOffset, byteLength);
this._offset = byteOffset + byteLength;
return this.buffer.toString(encoding || 'binary', this.byteOffset + byteOffset, this.byteOffset + this._offset);
}
var bytes = this._getBytes(byteLength, byteOffset, true), string = '';
byteLength = bytes.length;
for (var i = 0; i < byteLength; i++) {
string += String.fromCharCode(bytes[i]);
}
if (encoding === 'utf8') {
string = decodeURIComponent(escape(string));
}
return string;
},
setString: function (byteOffset, subString, encoding) {
if (this._isNodeBuffer) {
byteOffset = defined(byteOffset, this._offset);
this._checkBounds(byteOffset, subString.length);
this._offset = byteOffset + this.buffer.write(subString, this.byteOffset + byteOffset, encoding || 'binary');
return;
}
if (encoding === 'utf8') {
subString = unescape(encodeURIComponent(subString));
}
this._setBytes(byteOffset, getCharCodes(subString), true);
},
getChar: function (byteOffset) {
return this.getString(1, byteOffset);
},
setChar: function (byteOffset, character) {
this.setString(byteOffset, character);
},
tell: function () {
return this._offset;
},
seek: function (byteOffset) {
this._checkBounds(byteOffset, 0);
/* jshint boss: true */
return this._offset = byteOffset;
},
skip: function (byteLength) {
return this.seek(this._offset + byteLength);
},
slice: function (start, end, forceCopy) {
function normalizeOffset(offset, byteLength) {
return offset < 0 ? offset + byteLength : offset;
}
start = normalizeOffset(start, this.byteLength);
end = normalizeOffset(defined(end, this.byteLength), this.byteLength);
return forceCopy
? new jDataView(this.getBytes(end - start, start, true, true), undefined, undefined, this._littleEndian)
: new jDataView(this.buffer, this.byteOffset + start, end - start, this._littleEndian);
},
alignBy: function (byteCount) {
this._bitOffset = 0;
if (defined(byteCount, 1) !== 1) {
return this.skip(byteCount - (this._offset % byteCount || byteCount));
} else {
return this._offset;
}
},
// Compatibility functions
_getFloat64: function (byteOffset, littleEndian) {
var b = this._getBytes(8, byteOffset, littleEndian),
sign = 1 - (2 * (b[7] >> 7)),
exponent = ((((b[7] << 1) & 0xff) << 3) | (b[6] >> 4)) - ((1 << 10) - 1),
// Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead
mantissa = ((b[6] & 0x0f) * pow2(48)) + (b[5] * pow2(40)) + (b[4] * pow2(32)) +
(b[3] * pow2(24)) + (b[2] * pow2(16)) + (b[1] * pow2(8)) + b[0];
if (exponent === 1024) {
if (mantissa !== 0) {
return NaN;
} else {
return sign * Infinity;
}
}
if (exponent === -1023) { // Denormalized
return sign * mantissa * pow2(-1022 - 52);
}
return sign * (1 + mantissa * pow2(-52)) * pow2(exponent);
},
_getFloat32: function (byteOffset, littleEndian) {
var b = this._getBytes(4, byteOffset, littleEndian),
sign = 1 - (2 * (b[3] >> 7)),
exponent = (((b[3] << 1) & 0xff) | (b[2] >> 7)) - 127,
mantissa = ((b[2] & 0x7f) << 16) | (b[1] << 8) | b[0];
if (exponent === 128) {
if (mantissa !== 0) {
return NaN;
} else {
return sign * Infinity;
}
}
if (exponent === -127) { // Denormalized
return sign * mantissa * pow2(-126 - 23);
}
return sign * (1 + mantissa * pow2(-23)) * pow2(exponent);
},
_get64: function (Type, byteOffset, littleEndian) {
littleEndian = defined(littleEndian, this._littleEndian);
byteOffset = defined(byteOffset, this._offset);
var parts = littleEndian ? [0, 4] : [4, 0];
for (var i = 0; i < 2; i++) {
parts[i] = this.getUint32(byteOffset + parts[i], littleEndian);
}
this._offset = byteOffset + 8;
return new Type(parts[0], parts[1]);
},
getInt64: function (byteOffset, littleEndian) {
return this._get64(Int64, byteOffset, littleEndian);
},
getUint64: function (byteOffset, littleEndian) {
return this._get64(Uint64, byteOffset, littleEndian);
},
_getInt32: function (byteOffset, littleEndian) {
var b = this._getBytes(4, byteOffset, littleEndian);
return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0];
},
_getUint32: function (byteOffset, littleEndian) {
return this._getInt32(byteOffset, littleEndian) >>> 0;
},
_getInt16: function (byteOffset, littleEndian) {
return (this._getUint16(byteOffset, littleEndian) << 16) >> 16;
},
_getUint16: function (byteOffset, littleEndian) {
var b = this._getBytes(2, byteOffset, littleEndian);
return (b[1] << 8) | b[0];
},
_getInt8: function (byteOffset) {
return (this._getUint8(byteOffset) << 24) >> 24;
},
_getUint8: function (byteOffset) {
return this._getBytes(1, byteOffset)[0];
},
_getBitRangeData: function (bitLength, byteOffset) {
var startBit = (defined(byteOffset, this._offset) << 3) + this._bitOffset,
endBit = startBit + bitLength,
start = startBit >>> 3,
end = (endBit + 7) >>> 3,
b = this._getBytes(end - start, start, true),
wideValue = 0;
/* jshint boss: true */
if (this._bitOffset = endBit & 7) {
this._bitOffset -= 8;
}
for (var i = 0, length = b.length; i < length; i++) {
wideValue = (wideValue << 8) | b[i];
}
return {
start: start,
bytes: b,
wideValue: wideValue
};
},
getSigned: function (bitLength, byteOffset) {
var shift = 32 - bitLength;
return (this.getUnsigned(bitLength, byteOffset) << shift) >> shift;
},
getUnsigned: function (bitLength, byteOffset) {
var value = this._getBitRangeData(bitLength, byteOffset).wideValue >>> -this._bitOffset;
return bitLength < 32 ? (value & ~(-1 << bitLength)) : value;
},
_setBinaryFloat: function (byteOffset, value, mantSize, expSize, littleEndian) {
var signBit = value < 0 ? 1 : 0,
exponent,
mantissa,
eMax = ~(-1 << (expSize - 1)),
eMin = 1 - eMax;
if (value < 0) {
value = -value;
}
if (value === 0) {
exponent = 0;
mantissa = 0;
} else if (isNaN(value)) {
exponent = 2 * eMax + 1;
mantissa = 1;
} else if (value === Infinity) {
exponent = 2 * eMax + 1;
mantissa = 0;
} else {
exponent = Math.floor(Math.log(value) / Math.LN2);
if (exponent >= eMin && exponent <= eMax) {
mantissa = Math.floor((value * pow2(-exponent) - 1) * pow2(mantSize));
exponent += eMax;
} else {
mantissa = Math.floor(value / pow2(eMin - mantSize));
exponent = 0;
}
}
var b = [];
while (mantSize >= 8) {
b.push(mantissa % 256);
mantissa = Math.floor(mantissa / 256);
mantSize -= 8;
}
exponent = (exponent << mantSize) | mantissa;
expSize += mantSize;
while (expSize >= 8) {
b.push(exponent & 0xff);
exponent >>>= 8;
expSize -= 8;
}
b.push((signBit << expSize) | exponent);
this._setBytes(byteOffset, b, littleEndian);
},
_setFloat32: function (byteOffset, value, littleEndian) {
this._setBinaryFloat(byteOffset, value, 23, 8, littleEndian);
},
_setFloat64: function (byteOffset, value, littleEndian) {
this._setBinaryFloat(byteOffset, value, 52, 11, littleEndian);
},
_set64: function (Type, byteOffset, value, littleEndian) {
if (!(value instanceof Type)) {
value = Type.fromNumber(value);
}
littleEndian = defined(littleEndian, this._littleEndian);
byteOffset = defined(byteOffset, this._offset);
var parts = littleEndian ? {lo: 0, hi: 4} : {lo: 4, hi: 0};
for (var partName in parts) {
this.setUint32(byteOffset + parts[partName], value[partName], littleEndian);
}
this._offset = byteOffset + 8;
},
setInt64: function (byteOffset, value, littleEndian) {
this._set64(Int64, byteOffset, value, littleEndian);
},
setUint64: function (byteOffset, value, littleEndian) {
this._set64(Uint64, byteOffset, value, littleEndian);
},
_setUint32: function (byteOffset, value, littleEndian) {
this._setBytes(byteOffset, [
value & 0xff,
(value >>> 8) & 0xff,
(value >>> 16) & 0xff,
value >>> 24
], littleEndian);
},
_setUint16: function (byteOffset, value, littleEndian) {
this._setBytes(byteOffset, [
value & 0xff,
(value >>> 8) & 0xff
], littleEndian);
},
_setUint8: function (byteOffset, value) {
this._setBytes(byteOffset, [value & 0xff]);
},
setUnsigned: function (byteOffset, value, bitLength) {
var data = this._getBitRangeData(bitLength, byteOffset),
wideValue = data.wideValue,
b = data.bytes;
wideValue &= ~(~(-1 << bitLength) << -this._bitOffset); // clearing bit range before binary "or"
wideValue |= (bitLength < 32 ? (value & ~(-1 << bitLength)) : value) << -this._bitOffset; // setting bits
for (var i = b.length - 1; i >= 0; i--) {
b[i] = wideValue & 0xff;
wideValue >>>= 8;
}
this._setBytes(data.start, b, true);
}
};
var proto = jDataView.prototype;
for (var type in dataTypes) {
(function (type) {
proto['get' + type] = function (byteOffset, littleEndian) {
return this._action(type, true, byteOffset, littleEndian);
};
proto['set' + type] = function (byteOffset, value, littleEndian) {
this._action(type, false, byteOffset, littleEndian, value);
};
})(type);
}
proto._setInt32 = proto._setUint32;
proto._setInt16 = proto._setUint16;
proto._setInt8 = proto._setUint8;
proto.setSigned = proto.setUnsigned;
for (var method in proto) {
if (method.slice(0, 3) === 'set') {
(function (type) {
proto['write' + type] = function () {
Array.prototype.unshift.call(arguments, undefined);
this['set' + type].apply(this, arguments);
};
})(method.slice(3));
}
}
if (typeof module !== 'undefined' && typeof module.exports === 'object') {
module.exports = jDataView;
} else
if (typeof define === 'function' && define.amd) {
define([], function () { return jDataView });
} else {
var oldGlobal = global.jDataView;
(global.jDataView = jDataView).noConflict = function () {
global.jDataView = oldGlobal;
return this;
};
}
})((function () { /* jshint strict: false */ return this })());