UNPKG

14.3 kBJavaScriptView Raw
1/**
2 * @licstart The following is the entire license notice for the
3 * JavaScript code in this page
4 *
5 * Copyright 2022 Mozilla Foundation
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 * @licend The above is the entire license notice for the
20 * JavaScript code in this page
21 */
22"use strict";
23
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.ChunkedStreamManager = exports.ChunkedStream = void 0;
28
29var _util = require("../shared/util.js");
30
31var _core_utils = require("./core_utils.js");
32
33var _stream = require("./stream.js");
34
35class ChunkedStream extends _stream.Stream {
36 constructor(length, chunkSize, manager) {
37 super(new Uint8Array(length), 0, length, null);
38 this.chunkSize = chunkSize;
39 this._loadedChunks = new Set();
40 this.numChunks = Math.ceil(length / chunkSize);
41 this.manager = manager;
42 this.progressiveDataLength = 0;
43 this.lastSuccessfulEnsureByteChunk = -1;
44 }
45
46 getMissingChunks() {
47 const chunks = [];
48
49 for (let chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
50 if (!this._loadedChunks.has(chunk)) {
51 chunks.push(chunk);
52 }
53 }
54
55 return chunks;
56 }
57
58 get numChunksLoaded() {
59 return this._loadedChunks.size;
60 }
61
62 get isDataLoaded() {
63 return this.numChunksLoaded === this.numChunks;
64 }
65
66 onReceiveData(begin, chunk) {
67 const chunkSize = this.chunkSize;
68
69 if (begin % chunkSize !== 0) {
70 throw new Error(`Bad begin offset: ${begin}`);
71 }
72
73 const end = begin + chunk.byteLength;
74
75 if (end % chunkSize !== 0 && end !== this.bytes.length) {
76 throw new Error(`Bad end offset: ${end}`);
77 }
78
79 this.bytes.set(new Uint8Array(chunk), begin);
80 const beginChunk = Math.floor(begin / chunkSize);
81 const endChunk = Math.floor((end - 1) / chunkSize) + 1;
82
83 for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
84 this._loadedChunks.add(curChunk);
85 }
86 }
87
88 onReceiveProgressiveData(data) {
89 let position = this.progressiveDataLength;
90 const beginChunk = Math.floor(position / this.chunkSize);
91 this.bytes.set(new Uint8Array(data), position);
92 position += data.byteLength;
93 this.progressiveDataLength = position;
94 const endChunk = position >= this.end ? this.numChunks : Math.floor(position / this.chunkSize);
95
96 for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
97 this._loadedChunks.add(curChunk);
98 }
99 }
100
101 ensureByte(pos) {
102 if (pos < this.progressiveDataLength) {
103 return;
104 }
105
106 const chunk = Math.floor(pos / this.chunkSize);
107
108 if (chunk > this.numChunks) {
109 return;
110 }
111
112 if (chunk === this.lastSuccessfulEnsureByteChunk) {
113 return;
114 }
115
116 if (!this._loadedChunks.has(chunk)) {
117 throw new _core_utils.MissingDataException(pos, pos + 1);
118 }
119
120 this.lastSuccessfulEnsureByteChunk = chunk;
121 }
122
123 ensureRange(begin, end) {
124 if (begin >= end) {
125 return;
126 }
127
128 if (end <= this.progressiveDataLength) {
129 return;
130 }
131
132 const beginChunk = Math.floor(begin / this.chunkSize);
133
134 if (beginChunk > this.numChunks) {
135 return;
136 }
137
138 const endChunk = Math.min(Math.floor((end - 1) / this.chunkSize) + 1, this.numChunks);
139
140 for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
141 if (!this._loadedChunks.has(chunk)) {
142 throw new _core_utils.MissingDataException(begin, end);
143 }
144 }
145 }
146
147 nextEmptyChunk(beginChunk) {
148 const numChunks = this.numChunks;
149
150 for (let i = 0; i < numChunks; ++i) {
151 const chunk = (beginChunk + i) % numChunks;
152
153 if (!this._loadedChunks.has(chunk)) {
154 return chunk;
155 }
156 }
157
158 return null;
159 }
160
161 hasChunk(chunk) {
162 return this._loadedChunks.has(chunk);
163 }
164
165 getByte() {
166 const pos = this.pos;
167
168 if (pos >= this.end) {
169 return -1;
170 }
171
172 if (pos >= this.progressiveDataLength) {
173 this.ensureByte(pos);
174 }
175
176 return this.bytes[this.pos++];
177 }
178
179 getBytes(length) {
180 const bytes = this.bytes;
181 const pos = this.pos;
182 const strEnd = this.end;
183
184 if (!length) {
185 if (strEnd > this.progressiveDataLength) {
186 this.ensureRange(pos, strEnd);
187 }
188
189 return bytes.subarray(pos, strEnd);
190 }
191
192 let end = pos + length;
193
194 if (end > strEnd) {
195 end = strEnd;
196 }
197
198 if (end > this.progressiveDataLength) {
199 this.ensureRange(pos, end);
200 }
201
202 this.pos = end;
203 return bytes.subarray(pos, end);
204 }
205
206 getByteRange(begin, end) {
207 if (begin < 0) {
208 begin = 0;
209 }
210
211 if (end > this.end) {
212 end = this.end;
213 }
214
215 if (end > this.progressiveDataLength) {
216 this.ensureRange(begin, end);
217 }
218
219 return this.bytes.subarray(begin, end);
220 }
221
222 makeSubStream(start, length, dict = null) {
223 if (length) {
224 if (start + length > this.progressiveDataLength) {
225 this.ensureRange(start, start + length);
226 }
227 } else {
228 if (start >= this.progressiveDataLength) {
229 this.ensureByte(start);
230 }
231 }
232
233 function ChunkedStreamSubstream() {}
234
235 ChunkedStreamSubstream.prototype = Object.create(this);
236
237 ChunkedStreamSubstream.prototype.getMissingChunks = function () {
238 const chunkSize = this.chunkSize;
239 const beginChunk = Math.floor(this.start / chunkSize);
240 const endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
241 const missingChunks = [];
242
243 for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
244 if (!this._loadedChunks.has(chunk)) {
245 missingChunks.push(chunk);
246 }
247 }
248
249 return missingChunks;
250 };
251
252 Object.defineProperty(ChunkedStreamSubstream.prototype, "isDataLoaded", {
253 get() {
254 if (this.numChunksLoaded === this.numChunks) {
255 return true;
256 }
257
258 return this.getMissingChunks().length === 0;
259 },
260
261 configurable: true
262 });
263 const subStream = new ChunkedStreamSubstream();
264 subStream.pos = subStream.start = start;
265 subStream.end = start + length || this.end;
266 subStream.dict = dict;
267 return subStream;
268 }
269
270 getBaseStreams() {
271 return [this];
272 }
273
274}
275
276exports.ChunkedStream = ChunkedStream;
277
278class ChunkedStreamManager {
279 constructor(pdfNetworkStream, args) {
280 this.length = args.length;
281 this.chunkSize = args.rangeChunkSize;
282 this.stream = new ChunkedStream(this.length, this.chunkSize, this);
283 this.pdfNetworkStream = pdfNetworkStream;
284 this.disableAutoFetch = args.disableAutoFetch;
285 this.msgHandler = args.msgHandler;
286 this.currRequestId = 0;
287 this._chunksNeededByRequest = new Map();
288 this._requestsByChunk = new Map();
289 this._promisesByRequest = new Map();
290 this.progressiveDataLength = 0;
291 this.aborted = false;
292 this._loadedStreamCapability = (0, _util.createPromiseCapability)();
293 }
294
295 onLoadedStream() {
296 return this._loadedStreamCapability.promise;
297 }
298
299 sendRequest(begin, end) {
300 const rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
301
302 if (!rangeReader.isStreamingSupported) {
303 rangeReader.onProgress = this.onProgress.bind(this);
304 }
305
306 let chunks = [],
307 loaded = 0;
308 return new Promise((resolve, reject) => {
309 const readChunk = chunk => {
310 try {
311 if (!chunk.done) {
312 const data = chunk.value;
313 chunks.push(data);
314 loaded += (0, _util.arrayByteLength)(data);
315
316 if (rangeReader.isStreamingSupported) {
317 this.onProgress({
318 loaded
319 });
320 }
321
322 rangeReader.read().then(readChunk, reject);
323 return;
324 }
325
326 const chunkData = (0, _util.arraysToBytes)(chunks);
327 chunks = null;
328 resolve(chunkData);
329 } catch (e) {
330 reject(e);
331 }
332 };
333
334 rangeReader.read().then(readChunk, reject);
335 }).then(data => {
336 if (this.aborted) {
337 return;
338 }
339
340 this.onReceiveData({
341 chunk: data,
342 begin
343 });
344 });
345 }
346
347 requestAllChunks() {
348 const missingChunks = this.stream.getMissingChunks();
349
350 this._requestChunks(missingChunks);
351
352 return this._loadedStreamCapability.promise;
353 }
354
355 _requestChunks(chunks) {
356 const requestId = this.currRequestId++;
357 const chunksNeeded = new Set();
358
359 this._chunksNeededByRequest.set(requestId, chunksNeeded);
360
361 for (const chunk of chunks) {
362 if (!this.stream.hasChunk(chunk)) {
363 chunksNeeded.add(chunk);
364 }
365 }
366
367 if (chunksNeeded.size === 0) {
368 return Promise.resolve();
369 }
370
371 const capability = (0, _util.createPromiseCapability)();
372
373 this._promisesByRequest.set(requestId, capability);
374
375 const chunksToRequest = [];
376
377 for (const chunk of chunksNeeded) {
378 let requestIds = this._requestsByChunk.get(chunk);
379
380 if (!requestIds) {
381 requestIds = [];
382
383 this._requestsByChunk.set(chunk, requestIds);
384
385 chunksToRequest.push(chunk);
386 }
387
388 requestIds.push(requestId);
389 }
390
391 if (chunksToRequest.length > 0) {
392 const groupedChunksToRequest = this.groupChunks(chunksToRequest);
393
394 for (const groupedChunk of groupedChunksToRequest) {
395 const begin = groupedChunk.beginChunk * this.chunkSize;
396 const end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
397 this.sendRequest(begin, end).catch(capability.reject);
398 }
399 }
400
401 return capability.promise.catch(reason => {
402 if (this.aborted) {
403 return;
404 }
405
406 throw reason;
407 });
408 }
409
410 getStream() {
411 return this.stream;
412 }
413
414 requestRange(begin, end) {
415 end = Math.min(end, this.length);
416 const beginChunk = this.getBeginChunk(begin);
417 const endChunk = this.getEndChunk(end);
418 const chunks = [];
419
420 for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
421 chunks.push(chunk);
422 }
423
424 return this._requestChunks(chunks);
425 }
426
427 requestRanges(ranges = []) {
428 const chunksToRequest = [];
429
430 for (const range of ranges) {
431 const beginChunk = this.getBeginChunk(range.begin);
432 const endChunk = this.getEndChunk(range.end);
433
434 for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
435 if (!chunksToRequest.includes(chunk)) {
436 chunksToRequest.push(chunk);
437 }
438 }
439 }
440
441 chunksToRequest.sort(function (a, b) {
442 return a - b;
443 });
444 return this._requestChunks(chunksToRequest);
445 }
446
447 groupChunks(chunks) {
448 const groupedChunks = [];
449 let beginChunk = -1;
450 let prevChunk = -1;
451
452 for (let i = 0, ii = chunks.length; i < ii; ++i) {
453 const chunk = chunks[i];
454
455 if (beginChunk < 0) {
456 beginChunk = chunk;
457 }
458
459 if (prevChunk >= 0 && prevChunk + 1 !== chunk) {
460 groupedChunks.push({
461 beginChunk,
462 endChunk: prevChunk + 1
463 });
464 beginChunk = chunk;
465 }
466
467 if (i + 1 === chunks.length) {
468 groupedChunks.push({
469 beginChunk,
470 endChunk: chunk + 1
471 });
472 }
473
474 prevChunk = chunk;
475 }
476
477 return groupedChunks;
478 }
479
480 onProgress(args) {
481 this.msgHandler.send("DocProgress", {
482 loaded: this.stream.numChunksLoaded * this.chunkSize + args.loaded,
483 total: this.length
484 });
485 }
486
487 onReceiveData(args) {
488 const chunk = args.chunk;
489 const isProgressive = args.begin === undefined;
490 const begin = isProgressive ? this.progressiveDataLength : args.begin;
491 const end = begin + chunk.byteLength;
492 const beginChunk = Math.floor(begin / this.chunkSize);
493 const endChunk = end < this.length ? Math.floor(end / this.chunkSize) : Math.ceil(end / this.chunkSize);
494
495 if (isProgressive) {
496 this.stream.onReceiveProgressiveData(chunk);
497 this.progressiveDataLength = end;
498 } else {
499 this.stream.onReceiveData(begin, chunk);
500 }
501
502 if (this.stream.isDataLoaded) {
503 this._loadedStreamCapability.resolve(this.stream);
504 }
505
506 const loadedRequests = [];
507
508 for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
509 const requestIds = this._requestsByChunk.get(curChunk);
510
511 if (!requestIds) {
512 continue;
513 }
514
515 this._requestsByChunk.delete(curChunk);
516
517 for (const requestId of requestIds) {
518 const chunksNeeded = this._chunksNeededByRequest.get(requestId);
519
520 if (chunksNeeded.has(curChunk)) {
521 chunksNeeded.delete(curChunk);
522 }
523
524 if (chunksNeeded.size > 0) {
525 continue;
526 }
527
528 loadedRequests.push(requestId);
529 }
530 }
531
532 if (!this.disableAutoFetch && this._requestsByChunk.size === 0) {
533 let nextEmptyChunk;
534
535 if (this.stream.numChunksLoaded === 1) {
536 const lastChunk = this.stream.numChunks - 1;
537
538 if (!this.stream.hasChunk(lastChunk)) {
539 nextEmptyChunk = lastChunk;
540 }
541 } else {
542 nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
543 }
544
545 if (Number.isInteger(nextEmptyChunk)) {
546 this._requestChunks([nextEmptyChunk]);
547 }
548 }
549
550 for (const requestId of loadedRequests) {
551 const capability = this._promisesByRequest.get(requestId);
552
553 this._promisesByRequest.delete(requestId);
554
555 capability.resolve();
556 }
557
558 this.msgHandler.send("DocProgress", {
559 loaded: this.stream.numChunksLoaded * this.chunkSize,
560 total: this.length
561 });
562 }
563
564 onError(err) {
565 this._loadedStreamCapability.reject(err);
566 }
567
568 getBeginChunk(begin) {
569 return Math.floor(begin / this.chunkSize);
570 }
571
572 getEndChunk(end) {
573 return Math.floor((end - 1) / this.chunkSize) + 1;
574 }
575
576 abort(reason) {
577 this.aborted = true;
578
579 if (this.pdfNetworkStream) {
580 this.pdfNetworkStream.cancelAllRequests(reason);
581 }
582
583 for (const capability of this._promisesByRequest.values()) {
584 capability.reject(reason);
585 }
586 }
587
588}
589
590exports.ChunkedStreamManager = ChunkedStreamManager;
\No newline at end of file