| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 'use strict'; | |
| 6 | |
| 7 /** | |
| 8 * @constructor | |
| 9 * @param {ArrayBuffer} arrayBuffer // TODO(JSDOC). | |
| 10 * @param {number=} opt_offset // TODO(JSDOC). | |
| 11 * @param {number=} opt_length // TODO(JSDOC). | |
| 12 */ | |
| 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); | |
| 17 this.pos_ = 0; | |
| 18 this.seekStack_ = []; | |
| 19 this.setByteOrder(ByteReader.BIG_ENDIAN); | |
| 20 } | |
| 21 | |
| 22 // Static constants and methods. | |
| 23 | |
| 24 /** | |
| 25 * Intel, 0x1234 is [0x34, 0x12] | |
| 26 * @const | |
| 27 * @type {number} | |
| 28 */ | |
| 29 ByteReader.LITTLE_ENDIAN = 0; | |
| 30 /** | |
| 31 * Motorola, 0x1234 is [0x12, 0x34] | |
| 32 * @const | |
| 33 * @type {number} | |
| 34 */ | |
| 35 ByteReader.BIG_ENDIAN = 1; | |
| 36 | |
| 37 /** | |
| 38 * Seek relative to the beginning of the buffer. | |
| 39 * @const | |
| 40 * @type {number} | |
| 41 */ | |
| 42 ByteReader.SEEK_BEG = 0; | |
| 43 /** | |
| 44 * Seek relative to the current position. | |
| 45 * @const | |
| 46 * @type {number} | |
| 47 */ | |
| 48 ByteReader.SEEK_CUR = 1; | |
| 49 /** | |
| 50 * Seek relative to the end of the buffer. | |
| 51 * @const | |
| 52 * @type {number} | |
| 53 */ | |
| 54 ByteReader.SEEK_END = 2; | |
| 55 | |
| 56 /** | |
| 57 * Throw an error if (0 > pos >= end) or if (pos + size > end). | |
| 58 * | |
| 59 * Static utility function. | |
| 60 * | |
| 61 * @param {number} pos // TODO(JSDOC). | |
| 62 * @param {number} size // TODO(JSDOC). | |
| 63 * @param {number} end // TODO(JSDOC). | |
| 64 */ | |
| 65 ByteReader.validateRead = function(pos, size, end) { | |
| 66 if (pos < 0 || pos >= end) | |
| 67 throw new Error('Invalid read position'); | |
| 68 | |
| 69 if (pos + size > end) | |
| 70 throw new Error('Read past end of buffer'); | |
| 71 }; | |
| 72 | |
| 73 /** | |
| 74 * Read as a sequence of characters, returning them as a single string. | |
| 75 * | |
| 76 * This is a static utility function. There is a member function with the | |
| 77 * same name which side-effects the current read position. | |
| 78 * | |
| 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). | |
| 84 */ | |
| 85 ByteReader.readString = function(dataView, pos, size, opt_end) { | |
| 86 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength); | |
| 87 | |
| 88 var codes = []; | |
| 89 | |
| 90 for (var i = 0; i < size; ++i) | |
| 91 codes.push(dataView.getUint8(pos + i)); | |
| 92 | |
| 93 return String.fromCharCode.apply(null, codes); | |
| 94 }; | |
| 95 | |
| 96 /** | |
| 97 * Read as a sequence of characters, returning them as a single string. | |
| 98 * | |
| 99 * This is a static utility function. There is a member function with the | |
| 100 * same name which side-effects the current read position. | |
| 101 * | |
| 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). | |
| 107 */ | |
| 108 ByteReader.readNullTerminatedString = function(dataView, pos, size, opt_end) { | |
| 109 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength); | |
| 110 | |
| 111 var codes = []; | |
| 112 | |
| 113 for (var i = 0; i < size; ++i) { | |
| 114 var code = dataView.getUint8(pos + i); | |
| 115 if (code == 0) break; | |
| 116 codes.push(code); | |
| 117 } | |
| 118 | |
| 119 return String.fromCharCode.apply(null, codes); | |
| 120 }; | |
| 121 | |
| 122 /** | |
| 123 * Read as a sequence of UTF16 characters, returning them as a single string. | |
| 124 * | |
| 125 * This is a static utility function. There is a member function with the | |
| 126 * same name which side-effects the current read position. | |
| 127 * | |
| 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). | |
| 134 */ | |
| 135 ByteReader.readNullTerminatedStringUTF16 = function( | |
| 136 dataView, pos, bom, size, opt_end) { | |
| 137 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength); | |
| 138 | |
| 139 var littleEndian = false; | |
| 140 var start = 0; | |
| 141 | |
| 142 if (bom) { | |
| 143 littleEndian = (dataView.getUint8(pos) == 0xFF); | |
| 144 start = 2; | |
| 145 } | |
| 146 | |
| 147 var codes = []; | |
| 148 | |
| 149 for (var i = start; i < size; i += 2) { | |
| 150 var code = dataView.getUint16(pos + i, littleEndian); | |
| 151 if (code == 0) break; | |
| 152 codes.push(code); | |
| 153 } | |
| 154 | |
| 155 return String.fromCharCode.apply(null, codes); | |
| 156 }; | |
| 157 | |
| 158 /** | |
| 159 * @const | |
| 160 * @type {Array.<string>} | |
| 161 * @private | |
| 162 */ | |
| 163 ByteReader.base64Alphabet_ = | |
| 164 ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'). | |
| 165 split(''); | |
| 166 | |
| 167 /** | |
| 168 * Read as a sequence of bytes, returning them as a single base64 encoded | |
| 169 * string. | |
| 170 * | |
| 171 * This is a static utility function. There is a member function with the | |
| 172 * same name which side-effects the current read position. | |
| 173 * | |
| 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). | |
| 179 */ | |
| 180 ByteReader.readBase64 = function(dataView, pos, size, opt_end) { | |
| 181 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength); | |
| 182 | |
| 183 var rv = []; | |
| 184 var chars = []; | |
| 185 var padding = 0; | |
| 186 | |
| 187 for (var i = 0; i < size; /* incremented inside */) { | |
| 188 var bits = dataView.getUint8(pos + (i++)) << 16; | |
| 189 | |
| 190 if (i < size) { | |
| 191 bits |= dataView.getUint8(pos + (i++)) << 8; | |
| 192 | |
| 193 if (i < size) { | |
| 194 bits |= dataView.getUint8(pos + (i++)); | |
| 195 } else { | |
| 196 padding = 1; | |
| 197 } | |
| 198 } else { | |
| 199 padding = 2; | |
| 200 } | |
| 201 | |
| 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]; | |
| 206 | |
| 207 rv.push.apply(rv, chars); | |
| 208 } | |
| 209 | |
| 210 if (padding > 0) | |
| 211 rv[rv.length - 1] = '='; | |
| 212 if (padding > 1) | |
| 213 rv[rv.length - 2] = '='; | |
| 214 | |
| 215 return rv.join(''); | |
| 216 }; | |
| 217 | |
| 218 /** | |
| 219 * Read as an image encoded in a data url. | |
| 220 * | |
| 221 * This is a static utility function. There is a member function with the | |
| 222 * same name which side-effects the current read position. | |
| 223 * | |
| 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). | |
| 229 */ | |
| 230 ByteReader.readImage = function(dataView, pos, size, opt_end) { | |
| 231 opt_end = opt_end || dataView.byteLength; | |
| 232 ByteReader.validateRead(pos, size, opt_end); | |
| 233 | |
| 234 // Two bytes is enough to identify the mime type. | |
| 235 var prefixToMime = { | |
| 236 '\x89P' : 'png', | |
| 237 '\xFF\xD8' : 'jpeg', | |
| 238 'BM' : 'bmp', | |
| 239 'GI' : 'gif' | |
| 240 }; | |
| 241 | |
| 242 var prefix = ByteReader.readString(dataView, pos, 2, opt_end); | |
| 243 var mime = prefixToMime[prefix] || | |
| 244 dataView.getUint16(pos, false).toString(16); // For debugging. | |
| 245 | |
| 246 var b64 = ByteReader.readBase64(dataView, pos, size, opt_end); | |
| 247 return 'data:image/' + mime + ';base64,' + b64; | |
| 248 }; | |
| 249 | |
| 250 // Instance methods. | |
| 251 | |
| 252 /** | |
| 253 * Return true if the requested number of bytes can be read from the buffer. | |
| 254 * | |
| 255 * @param {number} size // TODO(JSDOC). | |
| 256 * @return {boolean} // TODO(JSDOC). | |
| 257 */ | |
| 258 ByteReader.prototype.canRead = function(size) { | |
| 259 return this.pos_ + size <= this.view_.byteLength; | |
| 260 }; | |
| 261 | |
| 262 /** | |
| 263 * Return true if the current position is past the end of the buffer. | |
| 264 * @return {boolean} // TODO(JSDOC). | |
| 265 */ | |
| 266 ByteReader.prototype.eof = function() { | |
| 267 return this.pos_ >= this.view_.byteLength; | |
| 268 }; | |
| 269 | |
| 270 /** | |
| 271 * Return true if the current position is before the beginning of the buffer. | |
| 272 * @return {boolean} // TODO(JSDOC). | |
| 273 */ | |
| 274 ByteReader.prototype.bof = function() { | |
| 275 return this.pos_ < 0; | |
| 276 }; | |
| 277 | |
| 278 /** | |
| 279 * Return true if the current position is outside the buffer. | |
| 280 * @return {boolean} // TODO(JSDOC). | |
| 281 */ | |
| 282 ByteReader.prototype.beof = function() { | |
| 283 return this.pos_ >= this.view_.byteLength || this.pos_ < 0; | |
| 284 }; | |
| 285 | |
| 286 /** | |
| 287 * Set the expected byte ordering for future reads. | |
| 288 * @param {number} order // TODO(JSDOC). | |
| 289 */ | |
| 290 ByteReader.prototype.setByteOrder = function(order) { | |
| 291 this.littleEndian_ = order == ByteReader.LITTLE_ENDIAN; | |
| 292 }; | |
| 293 | |
| 294 /** | |
| 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. | |
| 297 * | |
| 298 * You may optionally pass opt_end to override what is considered to be the | |
| 299 * end of the buffer. | |
| 300 * | |
| 301 * @param {number} size // TODO(JSDOC). | |
| 302 * @param {number=} opt_end // TODO(JSDOC). | |
| 303 */ | |
| 304 ByteReader.prototype.validateRead = function(size, opt_end) { | |
| 305 if (typeof opt_end == 'undefined') | |
| 306 opt_end = this.view_.byteLength; | |
| 307 | |
| 308 ByteReader.validateRead(this.view_, this.pos_, size, opt_end); | |
| 309 }; | |
| 310 | |
| 311 /** | |
| 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). | |
| 316 */ | |
| 317 ByteReader.prototype.readScalar = function(width, opt_signed, opt_end) { | |
| 318 var method = opt_signed ? 'getInt' : 'getUint'; | |
| 319 | |
| 320 switch (width) { | |
| 321 case 1: | |
| 322 method += '8'; | |
| 323 break; | |
| 324 | |
| 325 case 2: | |
| 326 method += '16'; | |
| 327 break; | |
| 328 | |
| 329 case 4: | |
| 330 method += '32'; | |
| 331 break; | |
| 332 | |
| 333 case 8: | |
| 334 method += '64'; | |
| 335 break; | |
| 336 | |
| 337 default: | |
| 338 throw new Error('Invalid width: ' + width); | |
| 339 break; | |
| 340 } | |
| 341 | |
| 342 this.validateRead(width, opt_end); | |
| 343 var rv = this.view_[method](this.pos_, this.littleEndian_); | |
| 344 this.pos_ += width; | |
| 345 return rv; | |
| 346 }; | |
| 347 | |
| 348 /** | |
| 349 * Read as a sequence of characters, returning them as a single string. | |
| 350 * | |
| 351 * Adjusts the current position on success. Throws an exception if the | |
| 352 * read would go past the end of the buffer. | |
| 353 * | |
| 354 * @param {number} size // TODO(JSDOC). | |
| 355 * @param {number=} opt_end // TODO(JSDOC). | |
| 356 * @return {string} // TODO(JSDOC). | |
| 357 */ | |
| 358 ByteReader.prototype.readString = function(size, opt_end) { | |
| 359 var rv = ByteReader.readString(this.view_, this.pos_, size, opt_end); | |
| 360 this.pos_ += size; | |
| 361 return rv; | |
| 362 }; | |
| 363 | |
| 364 | |
| 365 /** | |
| 366 * Read as a sequence of characters, returning them as a single string. | |
| 367 * | |
| 368 * Adjusts the current position on success. Throws an exception if the | |
| 369 * read would go past the end of the buffer. | |
| 370 * | |
| 371 * @param {number} size // TODO(JSDOC). | |
| 372 * @param {number=} opt_end // TODO(JSDOC). | |
| 373 * @return {string} // TODO(JSDOC). | |
| 374 */ | |
| 375 ByteReader.prototype.readNullTerminatedString = function(size, opt_end) { | |
| 376 var rv = ByteReader.readNullTerminatedString(this.view_, | |
| 377 this.pos_, | |
| 378 size, | |
| 379 opt_end); | |
| 380 this.pos_ += rv.length; | |
| 381 | |
| 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 | |
| 385 this.pos_++; | |
| 386 } | |
| 387 | |
| 388 return rv; | |
| 389 }; | |
| 390 | |
| 391 | |
| 392 /** | |
| 393 * Read as a sequence of UTF16 characters, returning them as a single string. | |
| 394 * | |
| 395 * Adjusts the current position on success. Throws an exception if the | |
| 396 * read would go past the end of the buffer. | |
| 397 * | |
| 398 * @param {boolean} bom // TODO(JSDOC). | |
| 399 * @param {number} size // TODO(JSDOC). | |
| 400 * @param {number=} opt_end // TODO(JSDOC). | |
| 401 * @return {string} // TODO(JSDOC). | |
| 402 */ | |
| 403 ByteReader.prototype.readNullTerminatedStringUTF16 = | |
| 404 function(bom, size, opt_end) { | |
| 405 var rv = ByteReader.readNullTerminatedStringUTF16( | |
| 406 this.view_, this.pos_, bom, size, opt_end); | |
| 407 | |
| 408 if (bom) { | |
| 409 // If the BOM word was present advance the position. | |
| 410 this.pos_ += 2; | |
| 411 } | |
| 412 | |
| 413 this.pos_ += rv.length; | |
| 414 | |
| 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 | |
| 418 this.pos_ += 2; | |
| 419 } | |
| 420 | |
| 421 return rv; | |
| 422 }; | |
| 423 | |
| 424 | |
| 425 /** | |
| 426 * Read as an array of numbers. | |
| 427 * | |
| 428 * Adjusts the current position on success. Throws an exception if the | |
| 429 * read would go past the end of the buffer. | |
| 430 * | |
| 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). | |
| 435 */ | |
| 436 ByteReader.prototype.readSlice = function(size, opt_end, | |
| 437 opt_arrayConstructor) { | |
| 438 this.validateRead(size, opt_end); | |
| 439 | |
| 440 var arrayConstructor = opt_arrayConstructor || Uint8Array; | |
| 441 var slice = new arrayConstructor( | |
| 442 this.view_.buffer, this.view_.byteOffset + this.pos, size); | |
| 443 this.pos_ += size; | |
| 444 | |
| 445 return slice; | |
| 446 }; | |
| 447 | |
| 448 /** | |
| 449 * Read as a sequence of bytes, returning them as a single base64 encoded | |
| 450 * string. | |
| 451 * | |
| 452 * Adjusts the current position on success. Throws an exception if the | |
| 453 * read would go past the end of the buffer. | |
| 454 * | |
| 455 * @param {number} size // TODO(JSDOC). | |
| 456 * @param {number=} opt_end // TODO(JSDOC). | |
| 457 * @return {string} // TODO(JSDOC). | |
| 458 */ | |
| 459 ByteReader.prototype.readBase64 = function(size, opt_end) { | |
| 460 var rv = ByteReader.readBase64(this.view_, this.pos_, size, opt_end); | |
| 461 this.pos_ += size; | |
| 462 return rv; | |
| 463 }; | |
| 464 | |
| 465 /** | |
| 466 * Read an image returning it as a data url. | |
| 467 * | |
| 468 * Adjusts the current position on success. Throws an exception if the | |
| 469 * read would go past the end of the buffer. | |
| 470 * | |
| 471 * @param {number} size // TODO(JSDOC). | |
| 472 * @param {number=} opt_end // TODO(JSDOC). | |
| 473 * @return {string} // TODO(JSDOC). | |
| 474 */ | |
| 475 ByteReader.prototype.readImage = function(size, opt_end) { | |
| 476 var rv = ByteReader.readImage(this.view_, this.pos_, size, opt_end); | |
| 477 this.pos_ += size; | |
| 478 return rv; | |
| 479 }; | |
| 480 | |
| 481 /** | |
| 482 * Seek to a give position relative to opt_seekStart. | |
| 483 * | |
| 484 * @param {number} pos // TODO(JSDOC). | |
| 485 * @param {number=} opt_seekStart // TODO(JSDOC). | |
| 486 * @param {number=} opt_end // TODO(JSDOC). | |
| 487 */ | |
| 488 ByteReader.prototype.seek = function(pos, opt_seekStart, opt_end) { | |
| 489 opt_end = opt_end || this.view_.byteLength; | |
| 490 | |
| 491 var newPos; | |
| 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; | |
| 496 } else { | |
| 497 newPos = pos; | |
| 498 } | |
| 499 | |
| 500 if (newPos < 0 || newPos > this.view_.byteLength) | |
| 501 throw new Error('Seek outside of buffer: ' + (newPos - opt_end)); | |
| 502 | |
| 503 this.pos_ = newPos; | |
| 504 }; | |
| 505 | |
| 506 /** | |
| 507 * Seek to a given position relative to opt_seekStart, saving the current | |
| 508 * position. | |
| 509 * | |
| 510 * Recover the current position with a call to seekPop. | |
| 511 * | |
| 512 * @param {number} pos // TODO(JSDOC). | |
| 513 * @param {number=} opt_seekStart // TODO(JSDOC). | |
| 514 */ | |
| 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); | |
| 520 }; | |
| 521 | |
| 522 /** | |
| 523 * Undo a previous seekPush. | |
| 524 */ | |
| 525 ByteReader.prototype.popSeek = function() { | |
| 526 this.seek(this.seekStack_.pop()); | |
| 527 }; | |
| 528 | |
| 529 /** | |
| 530 * Return the current read position. | |
| 531 * @return {number} // TODO(JSDOC). | |
| 532 */ | |
| 533 ByteReader.prototype.tell = function() { | |
| 534 return this.pos_; | |
| 535 }; | |
| OLD | NEW |