1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
9 * @param {ArrayBuffer} arrayBuffer // TODO(JSDOC).
10 * @param {number=} opt_offset // TODO(JSDOC).
11 * @param {number=} opt_length // TODO(JSDOC).
13 function ByteReader(arrayBuffer, opt_offset, opt_length) {
14 opt_offset = opt_offset || 0;
15 opt_length = opt_length || (arrayBuffer.byteLength - opt_offset);
16 this.view_ = new DataView(arrayBuffer, opt_offset, opt_length);
19 this.setByteOrder(ByteReader.BIG_ENDIAN);
22 // Static constants and methods.
25 * Intel, 0x1234 is [0x34, 0x12]
29 ByteReader.LITTLE_ENDIAN = 0;
31 * Motorola, 0x1234 is [0x12, 0x34]
35 ByteReader.BIG_ENDIAN = 1;
38 * Seek relative to the beginning of the buffer.
42 ByteReader.SEEK_BEG = 0;
44 * Seek relative to the current position.
48 ByteReader.SEEK_CUR = 1;
50 * Seek relative to the end of the buffer.
54 ByteReader.SEEK_END = 2;
57 * Throw an error if (0 > pos >= end) or if (pos + size > end).
59 * Static utility function.
61 * @param {number} pos // TODO(JSDOC).
62 * @param {number} size // TODO(JSDOC).
63 * @param {number} end // TODO(JSDOC).
65 ByteReader.validateRead = function(pos, size, end) {
66 if (pos < 0 || pos >= end)
67 throw new Error('Invalid read position');
70 throw new Error('Read past end of buffer');
74 * Read as a sequence of characters, returning them as a single string.
76 * This is a static utility function. There is a member function with the
77 * same name which side-effects the current read position.
79 * @param {DataView} dataView // TODO(JSDOC).
80 * @param {number} pos // TODO(JSDOC).
81 * @param {number} size // TODO(JSDOC).
82 * @param {number=} opt_end // TODO(JSDOC).
83 * @return {string} // TODO(JSDOC).
85 ByteReader.readString = function(dataView, pos, size, opt_end) {
86 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength);
90 for (var i = 0; i < size; ++i)
91 codes.push(dataView.getUint8(pos + i));
93 return String.fromCharCode.apply(null, codes);
97 * Read as a sequence of characters, returning them as a single string.
99 * This is a static utility function. There is a member function with the
100 * same name which side-effects the current read position.
102 * @param {DataView} dataView // TODO(JSDOC).
103 * @param {number} pos // TODO(JSDOC).
104 * @param {number} size // TODO(JSDOC).
105 * @param {number=} opt_end // TODO(JSDOC).
106 * @return {string} // TODO(JSDOC).
108 ByteReader.readNullTerminatedString = function(dataView, pos, size, opt_end) {
109 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength);
113 for (var i = 0; i < size; ++i) {
114 var code = dataView.getUint8(pos + i);
115 if (code == 0) break;
119 return String.fromCharCode.apply(null, codes);
123 * Read as a sequence of UTF16 characters, returning them as a single string.
125 * This is a static utility function. There is a member function with the
126 * same name which side-effects the current read position.
128 * @param {DataView} dataView // TODO(JSDOC).
129 * @param {number} pos // TODO(JSDOC).
130 * @param {boolean} bom // TODO(JSDOC).
131 * @param {number} size // TODO(JSDOC).
132 * @param {number=} opt_end // TODO(JSDOC).
133 * @return {string} // TODO(JSDOC).
135 ByteReader.readNullTerminatedStringUTF16 = function(
136 dataView, pos, bom, size, opt_end) {
137 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength);
139 var littleEndian = false;
143 littleEndian = (dataView.getUint8(pos) == 0xFF);
149 for (var i = start; i < size; i += 2) {
150 var code = dataView.getUint16(pos + i, littleEndian);
151 if (code == 0) break;
155 return String.fromCharCode.apply(null, codes);
160 * @type {Array.<string>}
163 ByteReader.base64Alphabet_ =
164 ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/').
168 * Read as a sequence of bytes, returning them as a single base64 encoded
171 * This is a static utility function. There is a member function with the
172 * same name which side-effects the current read position.
174 * @param {DataView} dataView // TODO(JSDOC).
175 * @param {number} pos // TODO(JSDOC).
176 * @param {number} size // TODO(JSDOC).
177 * @param {number=} opt_end // TODO(JSDOC).
178 * @return {string} // TODO(JSDOC).
180 ByteReader.readBase64 = function(dataView, pos, size, opt_end) {
181 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength);
187 for (var i = 0; i < size; /* incremented inside */) {
188 var bits = dataView.getUint8(pos + (i++)) << 16;
191 bits |= dataView.getUint8(pos + (i++)) << 8;
194 bits |= dataView.getUint8(pos + (i++));
202 chars[3] = ByteReader.base64Alphabet_[bits & 63];
203 chars[2] = ByteReader.base64Alphabet_[(bits >> 6) & 63];
204 chars[1] = ByteReader.base64Alphabet_[(bits >> 12) & 63];
205 chars[0] = ByteReader.base64Alphabet_[(bits >> 18) & 63];
207 rv.push.apply(rv, chars);
211 rv[rv.length - 1] = '=';
213 rv[rv.length - 2] = '=';
219 * Read as an image encoded in a data url.
221 * This is a static utility function. There is a member function with the
222 * same name which side-effects the current read position.
224 * @param {DataView} dataView // TODO(JSDOC).
225 * @param {number} pos // TODO(JSDOC).
226 * @param {number} size // TODO(JSDOC).
227 * @param {number=} opt_end // TODO(JSDOC).
228 * @return {string} // TODO(JSDOC).
230 ByteReader.readImage = function(dataView, pos, size, opt_end) {
231 opt_end = opt_end || dataView.byteLength;
232 ByteReader.validateRead(pos, size, opt_end);
234 // Two bytes is enough to identify the mime type.
242 var prefix = ByteReader.readString(dataView, pos, 2, opt_end);
243 var mime = prefixToMime[prefix] ||
244 dataView.getUint16(pos, false).toString(16); // For debugging.
246 var b64 = ByteReader.readBase64(dataView, pos, size, opt_end);
247 return 'data:image/' + mime + ';base64,' + b64;
253 * Return true if the requested number of bytes can be read from the buffer.
255 * @param {number} size // TODO(JSDOC).
256 * @return {boolean} // TODO(JSDOC).
258 ByteReader.prototype.canRead = function(size) {
259 return this.pos_ + size <= this.view_.byteLength;
263 * Return true if the current position is past the end of the buffer.
264 * @return {boolean} // TODO(JSDOC).
266 ByteReader.prototype.eof = function() {
267 return this.pos_ >= this.view_.byteLength;
271 * Return true if the current position is before the beginning of the buffer.
272 * @return {boolean} // TODO(JSDOC).
274 ByteReader.prototype.bof = function() {
275 return this.pos_ < 0;
279 * Return true if the current position is outside the buffer.
280 * @return {boolean} // TODO(JSDOC).
282 ByteReader.prototype.beof = function() {
283 return this.pos_ >= this.view_.byteLength || this.pos_ < 0;
287 * Set the expected byte ordering for future reads.
288 * @param {number} order // TODO(JSDOC).
290 ByteReader.prototype.setByteOrder = function(order) {
291 this.littleEndian_ = order == ByteReader.LITTLE_ENDIAN;
295 * Throw an error if the reader is at an invalid position, or if a read a read
296 * of |size| would put it in one.
298 * You may optionally pass opt_end to override what is considered to be the
301 * @param {number} size // TODO(JSDOC).
302 * @param {number=} opt_end // TODO(JSDOC).
304 ByteReader.prototype.validateRead = function(size, opt_end) {
305 if (typeof opt_end == 'undefined')
306 opt_end = this.view_.byteLength;
308 ByteReader.validateRead(this.view_, this.pos_, size, opt_end);
312 * @param {number} width // TODO(JSDOC).
313 * @param {boolean=} opt_signed // TODO(JSDOC).
314 * @param {number=} opt_end // TODO(JSDOC).
315 * @return {string} // TODO(JSDOC).
317 ByteReader.prototype.readScalar = function(width, opt_signed, opt_end) {
318 var method = opt_signed ? 'getInt' : 'getUint';
338 throw new Error('Invalid width: ' + width);
342 this.validateRead(width, opt_end);
343 var rv = this.view_[method](this.pos_, this.littleEndian_);
349 * Read as a sequence of characters, returning them as a single string.
351 * Adjusts the current position on success. Throws an exception if the
352 * read would go past the end of the buffer.
354 * @param {number} size // TODO(JSDOC).
355 * @param {number=} opt_end // TODO(JSDOC).
356 * @return {string} // TODO(JSDOC).
358 ByteReader.prototype.readString = function(size, opt_end) {
359 var rv = ByteReader.readString(this.view_, this.pos_, size, opt_end);
366 * Read as a sequence of characters, returning them as a single string.
368 * Adjusts the current position on success. Throws an exception if the
369 * read would go past the end of the buffer.
371 * @param {number} size // TODO(JSDOC).
372 * @param {number=} opt_end // TODO(JSDOC).
373 * @return {string} // TODO(JSDOC).
375 ByteReader.prototype.readNullTerminatedString = function(size, opt_end) {
376 var rv = ByteReader.readNullTerminatedString(this.view_,
380 this.pos_ += rv.length;
382 if (rv.length < size) {
383 // If we've stopped reading because we found '0' but didn't hit size limit
384 // then we should skip additional '0' character
393 * Read as a sequence of UTF16 characters, returning them as a single string.
395 * Adjusts the current position on success. Throws an exception if the
396 * read would go past the end of the buffer.
398 * @param {boolean} bom // TODO(JSDOC).
399 * @param {number} size // TODO(JSDOC).
400 * @param {number=} opt_end // TODO(JSDOC).
401 * @return {string} // TODO(JSDOC).
403 ByteReader.prototype.readNullTerminatedStringUTF16 =
404 function(bom, size, opt_end) {
405 var rv = ByteReader.readNullTerminatedStringUTF16(
406 this.view_, this.pos_, bom, size, opt_end);
409 // If the BOM word was present advance the position.
413 this.pos_ += rv.length;
415 if (rv.length < size) {
416 // If we've stopped reading because we found '0' but didn't hit size limit
417 // then we should skip additional '0' character
426 * Read as an array of numbers.
428 * Adjusts the current position on success. Throws an exception if the
429 * read would go past the end of the buffer.
431 * @param {number} size // TODO(JSDOC).
432 * @param {number=} opt_end // TODO(JSDOC).
433 * @param {function(new:Array.<*>)=} opt_arrayConstructor // TODO(JSDOC).
434 * @return {Array.<*>} // TODO(JSDOC).
436 ByteReader.prototype.readSlice = function(size, opt_end,
437 opt_arrayConstructor) {
438 this.validateRead(size, opt_end);
440 var arrayConstructor = opt_arrayConstructor || Uint8Array;
441 var slice = new arrayConstructor(
442 this.view_.buffer, this.view_.byteOffset + this.pos, size);
449 * Read as a sequence of bytes, returning them as a single base64 encoded
452 * Adjusts the current position on success. Throws an exception if the
453 * read would go past the end of the buffer.
455 * @param {number} size // TODO(JSDOC).
456 * @param {number=} opt_end // TODO(JSDOC).
457 * @return {string} // TODO(JSDOC).
459 ByteReader.prototype.readBase64 = function(size, opt_end) {
460 var rv = ByteReader.readBase64(this.view_, this.pos_, size, opt_end);
466 * Read an image returning it as a data url.
468 * Adjusts the current position on success. Throws an exception if the
469 * read would go past the end of the buffer.
471 * @param {number} size // TODO(JSDOC).
472 * @param {number=} opt_end // TODO(JSDOC).
473 * @return {string} // TODO(JSDOC).
475 ByteReader.prototype.readImage = function(size, opt_end) {
476 var rv = ByteReader.readImage(this.view_, this.pos_, size, opt_end);
482 * Seek to a give position relative to opt_seekStart.
484 * @param {number} pos // TODO(JSDOC).
485 * @param {number=} opt_seekStart // TODO(JSDOC).
486 * @param {number=} opt_end // TODO(JSDOC).
488 ByteReader.prototype.seek = function(pos, opt_seekStart, opt_end) {
489 opt_end = opt_end || this.view_.byteLength;
492 if (opt_seekStart == ByteReader.SEEK_CUR) {
493 newPos = this.pos_ + pos;
494 } else if (opt_seekStart == ByteReader.SEEK_END) {
495 newPos = opt_end + pos;
500 if (newPos < 0 || newPos > this.view_.byteLength)
501 throw new Error('Seek outside of buffer: ' + (newPos - opt_end));
507 * Seek to a given position relative to opt_seekStart, saving the current
510 * Recover the current position with a call to seekPop.
512 * @param {number} pos // TODO(JSDOC).
513 * @param {number=} opt_seekStart // TODO(JSDOC).
515 ByteReader.prototype.pushSeek = function(pos, opt_seekStart) {
516 var oldPos = this.pos_;
517 this.seek(pos, opt_seekStart);
518 // Alter the seekStack_ after the call to seek(), in case it throws.
519 this.seekStack_.push(oldPos);
523 * Undo a previous seekPush.
525 ByteReader.prototype.popSeek = function() {
526 this.seek(this.seekStack_.pop());
530 * Return the current read position.
531 * @return {number} // TODO(JSDOC).
533 ByteReader.prototype.tell = function() {