Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/shared/js/blobview.js
diff options
context:
space:
mode:
Diffstat (limited to 'shared/js/blobview.js')
-rw-r--r--shared/js/blobview.js412
1 files changed, 412 insertions, 0 deletions
diff --git a/shared/js/blobview.js b/shared/js/blobview.js
new file mode 100644
index 0000000..eda519a
--- /dev/null
+++ b/shared/js/blobview.js
@@ -0,0 +1,412 @@
+'use strict';
+
+var BlobView = (function() {
+ function fail(msg) {
+ throw Error(msg);
+ }
+
+ // This constructor is for internal use only.
+ // Use the BlobView.get() factory function or the getMore instance method
+ // to obtain a BlobView object.
+ function BlobView(blob, sliceOffset, sliceLength, slice,
+ viewOffset, viewLength, littleEndian)
+ {
+ this.blob = blob; // The parent blob that the data is from
+ this.sliceOffset = sliceOffset; // The start address within the blob
+ this.sliceLength = sliceLength; // How long the slice is
+ this.slice = slice; // The ArrayBuffer of slice data
+ this.viewOffset = viewOffset; // The start of the view within the slice
+ this.viewLength = viewLength; // The length of the view
+ this.littleEndian = littleEndian; // Read little endian by default?
+
+ // DataView wrapper around the ArrayBuffer
+ this.view = new DataView(slice, viewOffset, viewLength);
+
+ // These fields mirror those of DataView
+ this.buffer = slice;
+ this.byteLength = viewLength;
+ this.byteOffset = viewOffset;
+
+ this.index = 0; // The read methods keep track of the read position
+ }
+
+ // Async factory function
+ BlobView.get = function(blob, offset, length, callback, littleEndian) {
+ if (offset < 0)
+ fail('negative offset');
+ if (length < 0)
+ fail('negative length');
+ if (offset > blob.size)
+ fail('offset larger than blob size');
+
+ // Don't fail if the length is too big; just reduce the length
+ if (offset + length > blob.size)
+ length = blob.size - offset;
+
+ var slice = blob.slice(offset, offset + length);
+ var reader = new FileReader();
+ reader.readAsArrayBuffer(slice);
+ reader.onloadend = function() {
+ var result = null;
+ if (reader.result) {
+ result = new BlobView(blob, offset, length, reader.result,
+ 0, length, littleEndian || false);
+ }
+ callback(result, reader.error);
+ }
+ };
+
+ BlobView.prototype = {
+ constructor: BlobView,
+
+ // This instance method is like the BlobView.get() factory method,
+ // but it is here because if the current buffer includes the requested
+ // range of bytes, they can be passed directly to the callback without
+ // going back to the blob to read them
+ getMore: function(offset, length, callback) {
+ if (offset >= this.sliceOffset &&
+ offset + length <= this.sliceOffset + this.sliceLength) {
+ // The quick case: we already have that region of the blob
+ callback(new BlobView(this.blob,
+ this.sliceOffset, this.sliceLength, this.slice,
+ offset - this.sliceOffset, length,
+ this.littleEndian));
+ }
+ else {
+ // Otherwise, we have to do an async read to get more bytes
+ BlobView.get(this.blob, offset, length, callback, this.littleEndian);
+ }
+ },
+
+ // Set the default endianness for the other methods
+ littleEndian: function() {
+ this.littleEndian = true;
+ },
+ bigEndian: function() {
+ this.littleEndian = false;
+ },
+
+ // These "get" methods are just copies of the DataView methods, except
+ // that they honor the default endianness
+ getUint8: function(offset) {
+ return this.view.getUint8(offset);
+ },
+ getInt8: function(offset) {
+ return this.view.getInt8(offset);
+ },
+ getUint16: function(offset, le) {
+ return this.view.getUint16(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+ getInt16: function(offset, le) {
+ return this.view.getInt16(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+ getUint32: function(offset, le) {
+ return this.view.getUint32(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+ getInt32: function(offset, le) {
+ return this.view.getInt32(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+ getFloat32: function(offset, le) {
+ return this.view.getFloat32(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+ getFloat64: function(offset, le) {
+ return this.view.getFloat64(offset,
+ le !== undefined ? le : this.littleEndian);
+ },
+
+ // These "read" methods read from the current position in the view and
+ // update that position accordingly
+ readByte: function() {
+ return this.view.getInt8(this.index++);
+ },
+ readUnsignedByte: function() {
+ return this.view.getUint8(this.index++);
+ },
+ readShort: function(le) {
+ var val = this.view.getInt16(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 2;
+ return val;
+ },
+ readUnsignedShort: function(le) {
+ var val = this.view.getUint16(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 2;
+ return val;
+ },
+ readInt: function(le) {
+ var val = this.view.getInt32(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 4;
+ return val;
+ },
+ readUnsignedInt: function(le) {
+ var val = this.view.getUint32(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 4;
+ return val;
+ },
+ readFloat: function(le) {
+ var val = this.view.getFloat32(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 4;
+ return val;
+ },
+ readDouble: function(le) {
+ var val = this.view.getFloat64(this.index,
+ le !== undefined ? le : this.littleEndian);
+ this.index += 8;
+ return val;
+ },
+
+ // Methods to get and set the current position
+ tell: function() {
+ return this.index;
+ },
+ seek: function(index) {
+ if (index < 0)
+ fail('negative index');
+ if (index >= this.byteLength)
+ fail('index greater than buffer size');
+ this.index = index;
+ },
+ advance: function(n) {
+ var index = this.index + n;
+ if (index < 0)
+ fail('advance past beginning of buffer');
+ if (index >= this.byteLength)
+ fail('advance past end of buffer');
+ this.index = index;
+ },
+
+ // Additional methods to read other useful things
+ getUnsignedByteArray: function(offset, n) {
+ return new Uint8Array(this.buffer, offset + this.viewOffset, n);
+ },
+
+ // Additional methods to read other useful things
+ readUnsignedByteArray: function(n) {
+ var val = new Uint8Array(this.buffer, this.index + this.viewOffset, n);
+ this.index += n;
+ return val;
+ },
+
+ getBit: function(offset, bit) {
+ var byte = this.view.getUint8(offset);
+ return (byte & (1 << bit)) !== 0;
+ },
+
+ getUint24: function(offset, le) {
+ var b1, b2, b3;
+ if (le !== undefined ? le : this.littleEndian) {
+ b1 = this.view.getUint8(offset);
+ b2 = this.view.getUint8(offset + 1);
+ b3 = this.view.getUint8(offset + 2);
+ }
+ else { // big end first
+ b3 = this.view.getUint8(offset);
+ b2 = this.view.getUint8(offset + 1);
+ b1 = this.view.getUint8(offset + 2);
+ }
+
+ return (b3 << 16) + (b2 << 8) + b1;
+ },
+
+ readUint24: function(le) {
+ var value = this.getUint24(this.index, le);
+ this.index += 3;
+ return value;
+ },
+
+ // There are lots of ways to read strings.
+ // ASCII, UTF-8, UTF-16.
+ // null-terminated, character length, byte length
+ // I'll implement string reading methods as needed
+
+ getASCIIText: function(offset, len) {
+ var bytes = new Uint8Array(this.buffer, offset + this.viewOffset, len);
+ return String.fromCharCode.apply(String, bytes);
+ },
+
+ readASCIIText: function(len) {
+ var bytes = new Uint8Array(this.buffer,
+ this.index + this.viewOffset,
+ len);
+ this.index += len;
+ return String.fromCharCode.apply(String, bytes);
+ },
+
+ // Replace this with the StringEncoding API when we've got it.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=764234
+ getUTF8Text: function(offset, len) {
+ function fail() { throw new Error('Illegal UTF-8'); }
+
+ var pos = offset; // Current position in this.view
+ var end = offset + len; // Last position
+ var charcode; // Current charcode
+ var s = ''; // Accumulate the string
+ var b1, b2, b3, b4; // Up to 4 bytes per charcode
+
+ // See http://en.wikipedia.org/wiki/UTF-8
+ while (pos < end) {
+ var b1 = this.view.getUint8(pos);
+ if (b1 < 128) {
+ s += String.fromCharCode(b1);
+ pos += 1;
+ }
+ else if (b1 < 194) {
+ // unexpected continuation character...
+ fail();
+ }
+ else if (b1 < 224) {
+ // 2-byte sequence
+ if (pos + 1 >= end)
+ fail();
+ b2 = this.view.getUint8(pos + 1);
+ if (b2 < 128 || b2 > 191)
+ fail();
+ charcode = ((b1 & 0x1f) << 6) + (b2 & 0x3f);
+ s += String.fromCharCode(charcode);
+ pos += 2;
+ }
+ else if (b1 < 240) {
+ // 3-byte sequence
+ if (pos + 3 >= end)
+ fail();
+ b2 = this.view.getUint8(pos + 1);
+ if (b2 < 128 || b2 > 191)
+ fail();
+ b3 = this.view.getUint8(pos + 2);
+ if (b3 < 128 || b3 > 191)
+ fail();
+ charcode = ((b1 & 0x0f) << 12) + ((b2 & 0x3f) << 6) + (b3 & 0x3f);
+ s += String.fromCharCode(charcode);
+ pos += 3;
+ }
+ else if (b1 < 245) {
+ // 4-byte sequence
+ if (pos + 3 >= end)
+ fail();
+ b2 = this.view.getUint8(pos + 1);
+ if (b2 < 128 || b2 > 191)
+ fail();
+ b3 = this.view.getUint8(pos + 2);
+ if (b3 < 128 || b3 > 191)
+ fail();
+ b4 = this.view.getUint8(pos + 3);
+ if (b4 < 128 || b4 > 191)
+ fail();
+ charcode = ((b1 & 0x07) << 18) +
+ ((b2 & 0x3f) << 12) +
+ ((b3 & 0x3f) << 6) +
+ (b4 & 0x3f);
+
+ // Now turn this code point into two surrogate pairs
+ charcode -= 0x10000;
+ s += String.fromCharCode(0xd800 + ((charcode & 0x0FFC00) >>> 10));
+ s += String.fromCharCode(0xdc00 + (charcode & 0x0003FF));
+
+ pos += 4;
+ }
+ else {
+ // Illegal byte
+ fail();
+ }
+ }
+
+ return s;
+ },
+
+ readUTF8Text: function(len) {
+ try {
+ return this.getUTF8Text(this.index, len);
+ }
+ finally {
+ this.index += len;
+ }
+ },
+
+ // Read 4 bytes, ignore the high bit and combine them into a 28-bit
+ // big-endian unsigned integer.
+ // This format is used by the ID3v2 metadata.
+ getID3Uint28BE: function(offset) {
+ var b1 = this.view.getUint8(offset) & 0x7f;
+ var b2 = this.view.getUint8(offset + 1) & 0x7f;
+ var b3 = this.view.getUint8(offset + 2) & 0x7f;
+ var b4 = this.view.getUint8(offset + 3) & 0x7f;
+ return (b1 << 21) | (b2 << 14) | (b3 << 7) | b4;
+ },
+
+ readID3Uint28BE: function() {
+ var value = this.getID3Uint28BE(this.index);
+ this.index += 4;
+ return value;
+ },
+
+ // Read bytes up to and including a null terminator, but never
+ // more than size bytes. And return as a Latin1 string
+ readNullTerminatedLatin1Text: function(size) {
+ var s = '';
+ for (var i = 0; i < size; i++) {
+ var charcode = this.view.getUint8(this.index + i);
+ if (charcode === 0) {
+ i++;
+ break;
+ }
+ s += String.fromCharCode(charcode);
+ }
+ this.index += i;
+ return s;
+ },
+
+ // Read bytes up to and including a null terminator, but never
+ // more than size bytes. And return as a UTF8 string
+ readNullTerminatedUTF8Text: function(size) {
+ for (var len = 0; len < size; len++) {
+ if (this.view.getUint8(this.index + len) === 0)
+ break;
+ }
+ var s = this.readUTF8Text(len);
+ if (len < size) // skip the null terminator if we found one
+ this.advance(1);
+ return s;
+ },
+
+ // Read UTF16 text. If le is not specified, expect a BOM to define
+ // endianness. If le is true, read UTF16LE, if false, UTF16BE
+ // Read until we find a null-terminator, but never more than size bytes
+ readNullTerminatedUTF16Text: function(size, le) {
+ if (le == null) {
+ var BOM = this.readUnsignedShort();
+ size -= 2;
+ if (BOM === 0xFEFF)
+ le = false;
+ else
+ le = true;
+ }
+
+ var s = '';
+ for (var i = 0; i < size; i += 2) {
+ var charcode = this.getUint16(this.index + i, le);
+ if (charcode === 0) {
+ i += 2;
+ break;
+ }
+ s += String.fromCharCode(charcode);
+ }
+ this.index += i;
+ return s;
+ }
+ };
+
+ // We don't want users of this library to accidentally call the constructor
+ // instead of using the factory function, so we return a dummy object
+ // instead of the real constructor. If someone really needs to get at the
+ // real constructor, the contructor property of the prototype refers to it.
+ return { get: BlobView.get };
+}());