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 ByteReader = function(arrayBuffer, opt_offset, opt_length) { | |
6 opt_offset = opt_offset || 0; | |
7 opt_length = opt_length || (arrayBuffer.byteLength - opt_offset); | |
8 this.view_ = new DataView(arrayBuffer, opt_offset, opt_length); | |
9 this.pos_ = 0; | |
10 this.seekStack_ = []; | |
11 this.setByteOrder(ByteReader.BIG_ENDIAN); | |
12 }; | |
13 | |
14 // Static const and methods. | |
15 | |
16 ByteReader.LITTLE_ENDIAN = 0; // Intel, 0x1234 is [0x34, 0x12] | |
17 ByteReader.BIG_ENDIAN = 1; // Motorola, 0x1234 is [0x12, 0x34] | |
18 | |
19 ByteReader.SEEK_BEG = 0; // Seek relative to the beginning of the buffer. | |
20 ByteReader.SEEK_CUR = 1; // Seek relative to the current position. | |
21 ByteReader.SEEK_END = 2; // Seek relative to the end of the buffer. | |
22 | |
23 /** | |
24 * Throw an error if (0 > pos >= end) or if (pos + size > end). | |
25 * | |
26 * Static utility function. | |
27 */ | |
28 ByteReader.validateRead = function(pos, size, end) { | |
29 if (pos < 0 || pos >= end) | |
30 throw new Error('Invalid read position'); | |
31 | |
32 if (pos + size > end) | |
33 throw new Error('Read past end of buffer'); | |
34 }; | |
35 | |
36 /** | |
37 * Read as a sequence of characters, returning them as a single string. | |
38 * | |
39 * This is a static utility function. There is a member function with the | |
40 * same name which side-effects the current read position. | |
41 */ | |
42 ByteReader.readString = function(dataView, pos, size, opt_end) { | |
43 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength); | |
44 | |
45 var codes = []; | |
46 | |
47 for (var i = 0; i < size; ++i) | |
48 codes.push(dataView.getUint8(pos + i)); | |
49 | |
50 return String.fromCharCode.apply(null, codes); | |
51 }; | |
52 | |
53 /** | |
54 * Read as a sequence of characters, returning them as a single string. | |
55 * | |
56 * This is a static utility function. There is a member function with the | |
57 * same name which side-effects the current read position. | |
58 */ | |
59 ByteReader.readNullTerminatedString = function(dataView, pos, size, opt_end) { | |
60 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength); | |
61 | |
62 var codes = []; | |
63 | |
64 for (var i = 0; i < size; ++i) { | |
65 var code = dataView.getUint8(pos + i); | |
66 if (code == 0) break; | |
67 codes.push(code); | |
68 } | |
69 | |
70 return String.fromCharCode.apply(null, codes); | |
71 }; | |
72 | |
73 /** | |
74 * Read as a sequence of UTF16 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 ByteReader.readNullTerminatedStringUTF16 = function( | |
80 dataView, pos, bom, size, opt_end) { | |
81 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength); | |
82 | |
83 var littleEndian = false; | |
84 var start = 0; | |
85 | |
86 if (bom) { | |
87 littleEndian = (dataView.getUint8(pos) == 0xFF); | |
88 start = 2; | |
89 } | |
90 | |
91 var codes = []; | |
92 | |
93 for (var i = start; i < size; i += 2) { | |
94 var code = dataView.getUint16(pos + i, littleEndian); | |
95 if (code == 0) break; | |
96 codes.push(code); | |
97 } | |
98 | |
99 return String.fromCharCode.apply(null, codes); | |
100 }; | |
101 | |
102 ByteReader.base64Alphabet_ = | |
103 ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'). | |
104 split(''); | |
105 | |
106 /** | |
107 * Read as a sequence of bytes, returning them as a single base64 encoded | |
108 * string. | |
109 * | |
110 * This is a static utility function. There is a member function with the | |
111 * same name which side-effects the current read position. | |
112 */ | |
113 ByteReader.readBase64 = function(dataView, pos, size, opt_end) { | |
114 ByteReader.validateRead(pos, size, opt_end || dataView.byteLength); | |
115 | |
116 var rv = []; | |
117 var chars = []; | |
118 var padding = 0; | |
119 | |
120 for (var i = 0; i < size; /* incremented inside */) { | |
121 var bits = dataView.getUint8(pos + (i++)) << 16; | |
122 | |
123 if (i < size) { | |
124 bits |= dataView.getUint8(pos + (i++)) << 8; | |
125 | |
126 if (i < size) { | |
127 bits |= dataView.getUint8(pos + (i++)); | |
128 } else { | |
129 padding = 1; | |
130 } | |
131 } else { | |
132 padding = 2; | |
133 } | |
134 | |
135 chars[3] = ByteReader.base64Alphabet_[bits & 63]; | |
136 chars[2] = ByteReader.base64Alphabet_[(bits >> 6) & 63]; | |
137 chars[1] = ByteReader.base64Alphabet_[(bits >> 12) & 63]; | |
138 chars[0] = ByteReader.base64Alphabet_[(bits >> 18) & 63]; | |
139 | |
140 rv.push.apply(rv, chars); | |
141 } | |
142 | |
143 if (padding > 0) | |
144 rv[rv.length - 1] = '='; | |
145 if (padding > 1) | |
146 rv[rv.length - 2] = '='; | |
147 | |
148 return rv.join(''); | |
149 }; | |
150 | |
151 /** | |
152 * Read as an image encoded in a data url. | |
153 * | |
154 * This is a static utility function. There is a member function with the | |
155 * same name which side-effects the current read position. | |
156 */ | |
157 ByteReader.readImage = function(dataView, pos, size, opt_end) { | |
158 opt_end = opt_end || dataView.byteLength; | |
159 ByteReader.validateRead(pos, size, opt_end); | |
160 | |
161 // Two bytes is enough to identify the mime type. | |
162 var prefixToMime = { | |
163 '\x89P' : 'png', | |
164 '\xFF\xD8' : 'jpeg', | |
165 'BM' : 'bmp', | |
166 'GI' : 'gif' | |
167 }; | |
168 | |
169 var prefix = ByteReader.readString(dataView, pos, 2, opt_end); | |
170 var mime = prefixToMime[prefix] || | |
171 dataView.getUint16(pos, false).toString(16); // For debugging. | |
172 | |
173 var b64 = ByteReader.readBase64(dataView, pos, size, opt_end); | |
174 return 'data:image/' + mime + ';base64,' + b64; | |
175 }; | |
176 | |
177 // Instance methods. | |
178 | |
179 /** | |
180 * Return true if the requested number of bytes can be read from the buffer. | |
181 */ | |
182 ByteReader.prototype.canRead = function(size) { | |
183 return this.pos_ + size <= this.view_.byteLength; | |
184 }, | |
185 | |
186 /** | |
187 * Return true if the current position is past the end of the buffer. | |
188 */ | |
189 ByteReader.prototype.eof = function() { | |
190 return this.pos_ >= this.view_.byteLength; | |
191 }; | |
192 | |
193 /** | |
194 * Return true if the current position is before the beginning of the buffer. | |
195 */ | |
196 ByteReader.prototype.bof = function() { | |
197 return this.pos_ < 0; | |
198 }; | |
199 | |
200 /** | |
201 * Return true if the current position is outside the buffer. | |
202 */ | |
203 ByteReader.prototype.beof = function() { | |
204 return this.pos_ >= this.view_.byteLength || this.pos_ < 0; | |
205 }; | |
206 | |
207 /** | |
208 * Set the expected byte ordering for future reads. | |
209 */ | |
210 ByteReader.prototype.setByteOrder = function(order) { | |
211 this.littleEndian_ = order == ByteReader.LITTLE_ENDIAN; | |
212 }; | |
213 | |
214 /** | |
215 * Throw an error if the reader is at an invalid position, or if a read a read | |
216 * of |size| would put it in one. | |
217 * | |
218 * You may optionally pass opt_end to override what is considered to be the | |
219 * end of the buffer. | |
220 */ | |
221 ByteReader.prototype.validateRead = function(size, opt_end) { | |
222 if (typeof opt_end == 'undefined') | |
223 opt_end = this.view_.byteLength; | |
224 | |
225 ByteReader.validateRead(this.view_, this.pos_, size, opt_end); | |
226 }; | |
227 | |
228 ByteReader.prototype.readScalar = function(width, opt_signed, opt_end) { | |
229 var method = opt_signed ? 'getInt' : 'getUint'; | |
230 | |
231 switch (width) { | |
232 case 1: | |
233 method += '8'; | |
234 break; | |
235 | |
236 case 2: | |
237 method += '16'; | |
238 break; | |
239 | |
240 case 4: | |
241 method += '32'; | |
242 break; | |
243 | |
244 case 8: | |
245 method += '64'; | |
246 break; | |
247 | |
248 default: | |
249 throw new Error('Invalid width: ' + width); | |
250 break; | |
251 } | |
252 | |
253 this.validateRead(width, opt_end); | |
254 var rv = this.view_[method](this.pos_, this.littleEndian_); | |
255 this.pos_ += width; | |
256 return rv; | |
257 } | |
258 | |
259 /** | |
260 * Read as a sequence of characters, returning them as a single string. | |
261 * | |
262 * Adjusts the current position on success. Throws an exception if the | |
263 * read would go past the end of the buffer. | |
264 */ | |
265 ByteReader.prototype.readString = function(size, opt_end) { | |
266 var rv = ByteReader.readString(this.view_, this.pos_, size, opt_end); | |
267 this.pos_ += size; | |
268 return rv; | |
269 }; | |
270 | |
271 | |
272 /** | |
273 * Read as a sequence of characters, returning them as a single string. | |
274 * | |
275 * Adjusts the current position on success. Throws an exception if the | |
276 * read would go past the end of the buffer. | |
277 */ | |
278 ByteReader.prototype.readNullTerminatedString = function(size, opt_end) { | |
279 var rv = ByteReader.readNullTerminatedString(this.view_, | |
280 this.pos_, | |
281 size, | |
282 opt_end); | |
283 this.pos_ += rv.length; | |
284 | |
285 if (rv.length < size) { | |
286 // If we've stopped reading because we found '0' but didn't hit size limit | |
287 // then we should skip additional '0' character | |
288 this.pos_++; | |
289 } | |
290 | |
291 return rv; | |
292 }; | |
293 | |
294 | |
295 /** | |
296 * Read as a sequence of UTF16 characters, returning them as a single string. | |
297 * | |
298 * Adjusts the current position on success. Throws an exception if the | |
299 * read would go past the end of the buffer. | |
300 */ | |
301 ByteReader.prototype.readNullTerminatedStringUTF16 = | |
302 function(bom, size, opt_end) { | |
303 var rv = ByteReader.readNullTerminatedStringUTF16( | |
304 this.view_, this.pos_, bom, size, opt_end); | |
305 | |
306 if (bom) { | |
307 // If the BOM word was present advance the position. | |
308 this.pos_ += 2; | |
309 } | |
310 | |
311 this.pos_ += rv.length; | |
312 | |
313 if (rv.length < size) { | |
314 // If we've stopped reading because we found '0' but didn't hit size limit | |
315 // then we should skip additional '0' character | |
316 this.pos_ += 2; | |
317 } | |
318 | |
319 return rv; | |
320 }; | |
321 | |
322 | |
323 /** | |
324 * Read as an array of numbers. | |
325 * | |
326 * Adjusts the current position on success. Throws an exception if the | |
327 * read would go past the end of the buffer. | |
328 */ | |
329 ByteReader.prototype.readSlice = function(size, opt_end, | |
330 opt_arrayConstructor) { | |
331 this.validateRead(width, opt_end); | |
332 | |
333 var arrayConstructor = opt_arrayConstructor || Uint8Array; | |
334 var slice = new arrayConstructor( | |
335 this.view_.buffer, this.view_.byteOffset + this.pos, size); | |
336 this.pos_ += size; | |
337 | |
338 return slice; | |
339 }; | |
340 | |
341 /** | |
342 * Read as a sequence of bytes, returning them as a single base64 encoded | |
343 * string. | |
344 * | |
345 * Adjusts the current position on success. Throws an exception if the | |
346 * read would go past the end of the buffer. | |
347 */ | |
348 ByteReader.prototype.readBase64 = function(size, opt_end) { | |
349 var rv = ByteReader.readBase64(this.view_, this.pos_, size, opt_end); | |
350 this.pos_ += size; | |
351 return rv; | |
352 }; | |
353 | |
354 /** | |
355 * Read an image returning it as a data url. | |
356 * | |
357 * Adjusts the current position on success. Throws an exception if the | |
358 * read would go past the end of the buffer. | |
359 */ | |
360 ByteReader.prototype.readImage = function(size, opt_end) { | |
361 var rv = ByteReader.readImage(this.view_, this.pos_, size, opt_end); | |
362 this.pos_ += size; | |
363 return rv; | |
364 }; | |
365 | |
366 /** | |
367 * Seek to a give position relative to opt_seekStart. | |
368 */ | |
369 ByteReader.prototype.seek = function(pos, opt_seekStart, opt_end) { | |
370 opt_end = opt_end || this.view_.byteLength; | |
371 | |
372 var newPos; | |
373 if (opt_seekStart == ByteReader.SEEK_CUR) { | |
374 newPos = this.pos_ + pos; | |
375 } else if (opt_seekStart == ByteReader.SEEK_END) { | |
376 newPos = opt_end + pos; | |
377 } else { | |
378 newPos = pos; | |
379 } | |
380 | |
381 if (newPos < 0 || newPos > this.view_.byteLength) | |
382 throw new Error('Seek outside of buffer: ' + (newPos - opt_end)); | |
383 | |
384 this.pos_ = newPos; | |
385 }; | |
386 | |
387 /** | |
388 * Seek to a given position relative to opt_seekStart, saving the current | |
389 * position. | |
390 * | |
391 * Recover the current position with a call to seekPop. | |
392 */ | |
393 ByteReader.prototype.pushSeek = function(pos, opt_seekStart) { | |
394 var oldPos = this.pos_; | |
395 this.seek(pos, opt_seekStart); | |
396 // Alter the seekStack_ after the call to seek(), in case it throws. | |
397 this.seekStack_.push(oldPos); | |
398 }; | |
399 | |
400 /** | |
401 * Undo a previous seekPush. | |
402 */ | |
403 ByteReader.prototype.popSeek = function() { | |
404 this.seek(this.seekStack_.pop()); | |
405 }; | |
406 | |
407 /** | |
408 * Return the current read position. | |
409 */ | |
410 ByteReader.prototype.tell = function() { | |
411 return this.pos_; | |
412 }; | |
OLD | NEW |