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 |