mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-02-08 00:21:11 +01:00
Add an abstract BasePDFStream class, that all the old IPDFStream implementations inherit from
Given that there's no less than *five* different, but very similar, implementations this helps reduce code duplication and simplifies maintenance. Also, spotted during rebasing, pass the `enableHWA` option "correctly" (i.e. as part of the existing `transportParams`) to the `WorkerTransport`-class to keep the constructor simpler.
This commit is contained in:
parent
a80f10ff1a
commit
4a8fb4dde1
@ -272,17 +272,17 @@ class ChunkedStreamManager {
|
||||
|
||||
_requestsByChunk = new Map();
|
||||
|
||||
constructor(pdfNetworkStream, args) {
|
||||
constructor(pdfStream, args) {
|
||||
this.length = args.length;
|
||||
this.chunkSize = args.rangeChunkSize;
|
||||
this.stream = new ChunkedStream(this.length, this.chunkSize, this);
|
||||
this.pdfNetworkStream = pdfNetworkStream;
|
||||
this.pdfStream = pdfStream;
|
||||
this.disableAutoFetch = args.disableAutoFetch;
|
||||
this.msgHandler = args.msgHandler;
|
||||
}
|
||||
|
||||
sendRequest(begin, end) {
|
||||
const rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
|
||||
const rangeReader = this.pdfStream.getRangeReader(begin, end);
|
||||
|
||||
let chunks = [];
|
||||
return new Promise((resolve, reject) => {
|
||||
@ -530,7 +530,7 @@ class ChunkedStreamManager {
|
||||
|
||||
abort(reason) {
|
||||
this.aborted = true;
|
||||
this.pdfNetworkStream?.cancelAllRequests(reason);
|
||||
this.pdfStream?.cancelAllRequests(reason);
|
||||
|
||||
for (const capability of this._promisesByRequest.values()) {
|
||||
capability.reject(reason);
|
||||
|
||||
@ -219,23 +219,23 @@ class WorkerMessageHandler {
|
||||
|
||||
return new LocalPdfManager(pdfManagerArgs);
|
||||
}
|
||||
const pdfStream = new PDFWorkerStream(handler),
|
||||
fullRequest = pdfStream.getFullReader();
|
||||
const pdfStream = new PDFWorkerStream({ msgHandler: handler }),
|
||||
fullReader = pdfStream.getFullReader();
|
||||
|
||||
const pdfManagerCapability = Promise.withResolvers();
|
||||
let newPdfManager,
|
||||
cachedChunks = [],
|
||||
loaded = 0;
|
||||
|
||||
fullRequest.headersReady
|
||||
fullReader.headersReady
|
||||
.then(function () {
|
||||
if (!fullRequest.isRangeSupported) {
|
||||
if (!fullReader.isRangeSupported) {
|
||||
return;
|
||||
}
|
||||
pdfManagerArgs.source = pdfStream;
|
||||
pdfManagerArgs.length = fullRequest.contentLength;
|
||||
pdfManagerArgs.length = fullReader.contentLength;
|
||||
// We don't need auto-fetch when streaming is enabled.
|
||||
pdfManagerArgs.disableAutoFetch ||= fullRequest.isStreamingSupported;
|
||||
pdfManagerArgs.disableAutoFetch ||= fullReader.isStreamingSupported;
|
||||
|
||||
newPdfManager = new NetworkPdfManager(pdfManagerArgs);
|
||||
// There may be a chance that `newPdfManager` is not initialized for
|
||||
@ -282,10 +282,10 @@ class WorkerMessageHandler {
|
||||
}
|
||||
loaded += value.byteLength;
|
||||
|
||||
if (!fullRequest.isStreamingSupported) {
|
||||
if (!fullReader.isStreamingSupported) {
|
||||
handler.send("DocProgress", {
|
||||
loaded,
|
||||
total: Math.max(loaded, fullRequest.contentLength || 0),
|
||||
total: Math.max(loaded, fullReader.contentLength || 0),
|
||||
});
|
||||
}
|
||||
|
||||
@ -294,12 +294,12 @@ class WorkerMessageHandler {
|
||||
} else {
|
||||
cachedChunks.push(value);
|
||||
}
|
||||
fullRequest.read().then(readChunk, reject);
|
||||
fullReader.read().then(readChunk, reject);
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
fullRequest.read().then(readChunk, reject);
|
||||
fullReader.read().then(readChunk, reject);
|
||||
}).catch(function (e) {
|
||||
pdfManagerCapability.reject(e);
|
||||
cancelXHRs = null;
|
||||
|
||||
@ -13,55 +13,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert } from "../shared/util.js";
|
||||
import { BasePDFStream } from "../shared/base_pdf_stream.js";
|
||||
|
||||
/** @implements {IPDFStream} */
|
||||
class PDFWorkerStream {
|
||||
constructor(msgHandler) {
|
||||
this._msgHandler = msgHandler;
|
||||
this._contentLength = null;
|
||||
this._fullRequestReader = null;
|
||||
this._rangeRequestReaders = [];
|
||||
}
|
||||
|
||||
getFullReader() {
|
||||
assert(
|
||||
!this._fullRequestReader,
|
||||
"PDFWorkerStream.getFullReader can only be called once."
|
||||
);
|
||||
this._fullRequestReader = new PDFWorkerStreamReader(this._msgHandler);
|
||||
return this._fullRequestReader;
|
||||
}
|
||||
|
||||
getRangeReader(begin, end) {
|
||||
const reader = new PDFWorkerStreamRangeReader(begin, end, this._msgHandler);
|
||||
this._rangeRequestReaders.push(reader);
|
||||
return reader;
|
||||
}
|
||||
|
||||
cancelAllRequests(reason) {
|
||||
this._fullRequestReader?.cancel(reason);
|
||||
|
||||
for (const reader of this._rangeRequestReaders.slice(0)) {
|
||||
reader.cancel(reason);
|
||||
}
|
||||
class PDFWorkerStream extends BasePDFStream {
|
||||
constructor(source) {
|
||||
super(source, PDFWorkerStreamReader, PDFWorkerStreamRangeReader);
|
||||
}
|
||||
}
|
||||
|
||||
/** @implements {IPDFStreamReader} */
|
||||
class PDFWorkerStreamReader {
|
||||
constructor(msgHandler) {
|
||||
this._msgHandler = msgHandler;
|
||||
constructor(stream) {
|
||||
const { msgHandler } = stream._source;
|
||||
this.onProgress = null;
|
||||
|
||||
this._contentLength = null;
|
||||
this._isRangeSupported = false;
|
||||
this._isStreamingSupported = false;
|
||||
|
||||
const readableStream = this._msgHandler.sendWithStream("GetReader");
|
||||
const readableStream = msgHandler.sendWithStream("GetReader");
|
||||
this._reader = readableStream.getReader();
|
||||
|
||||
this._headersReady = this._msgHandler
|
||||
this._headersReady = msgHandler
|
||||
.sendWithPromise("ReaderHeadersReady")
|
||||
.then(data => {
|
||||
this._isStreamingSupported = data.isStreamingSupported;
|
||||
@ -103,10 +76,10 @@ class PDFWorkerStreamReader {
|
||||
|
||||
/** @implements {IPDFStreamRangeReader} */
|
||||
class PDFWorkerStreamRangeReader {
|
||||
constructor(begin, end, msgHandler) {
|
||||
this._msgHandler = msgHandler;
|
||||
constructor(stream, begin, end) {
|
||||
const { msgHandler } = stream._source;
|
||||
|
||||
const readableStream = this._msgHandler.sendWithStream("GetRangeReader", {
|
||||
const readableStream = msgHandler.sendWithStream("GetRangeReader", {
|
||||
begin,
|
||||
end,
|
||||
});
|
||||
|
||||
@ -440,6 +440,7 @@ function getDocument(src = {}) {
|
||||
ownerDocument,
|
||||
pdfBug,
|
||||
styleElement,
|
||||
enableHWA,
|
||||
loadingParams: {
|
||||
disableAutoFetch,
|
||||
enableXfa,
|
||||
@ -463,7 +464,8 @@ function getDocument(src = {}) {
|
||||
|
||||
let networkStream;
|
||||
if (rangeTransport) {
|
||||
networkStream = new PDFDataTransportStream(rangeTransport, {
|
||||
networkStream = new PDFDataTransportStream({
|
||||
pdfDataRangeTransport: rangeTransport,
|
||||
disableRange,
|
||||
disableStream,
|
||||
});
|
||||
@ -508,8 +510,7 @@ function getDocument(src = {}) {
|
||||
task,
|
||||
networkStream,
|
||||
transportParams,
|
||||
transportFactory,
|
||||
enableHWA
|
||||
transportFactory
|
||||
);
|
||||
task._transport = transport;
|
||||
messageHandler.send("Ready", null);
|
||||
@ -2391,8 +2392,16 @@ class PDFWorker {
|
||||
* @ignore
|
||||
*/
|
||||
class WorkerTransport {
|
||||
downloadInfoCapability = Promise.withResolvers();
|
||||
|
||||
#fullReader = null;
|
||||
|
||||
#lastProgress = null;
|
||||
|
||||
#methodPromises = new Map();
|
||||
|
||||
#networkStream = null;
|
||||
|
||||
#pageCache = new Map();
|
||||
|
||||
#pagePromises = new Map();
|
||||
@ -2403,21 +2412,17 @@ class WorkerTransport {
|
||||
|
||||
#pagesMapper = PagesMapper.instance;
|
||||
|
||||
constructor(
|
||||
messageHandler,
|
||||
loadingTask,
|
||||
networkStream,
|
||||
params,
|
||||
factory,
|
||||
enableHWA
|
||||
) {
|
||||
constructor(messageHandler, loadingTask, networkStream, params, factory) {
|
||||
this.messageHandler = messageHandler;
|
||||
this.loadingTask = loadingTask;
|
||||
this.#networkStream = networkStream;
|
||||
|
||||
this.commonObjs = new PDFObjects();
|
||||
this.fontLoader = new FontLoader({
|
||||
ownerDocument: params.ownerDocument,
|
||||
styleElement: params.styleElement,
|
||||
});
|
||||
this.enableHWA = params.enableHWA;
|
||||
this.loadingParams = params.loadingParams;
|
||||
this._params = params;
|
||||
|
||||
@ -2430,12 +2435,6 @@ class WorkerTransport {
|
||||
this.destroyed = false;
|
||||
this.destroyCapability = null;
|
||||
|
||||
this._networkStream = networkStream;
|
||||
this._fullReader = null;
|
||||
this._lastProgress = null;
|
||||
this.downloadInfoCapability = Promise.withResolvers();
|
||||
this.enableHWA = enableHWA;
|
||||
|
||||
this.setupMessageHandler();
|
||||
|
||||
this.#pagesMapper.addListener(this.#updateCaches.bind(this));
|
||||
@ -2604,7 +2603,7 @@ class WorkerTransport {
|
||||
this.filterFactory.destroy();
|
||||
TextLayer.cleanup();
|
||||
|
||||
this._networkStream?.cancelAllRequests(
|
||||
this.#networkStream?.cancelAllRequests(
|
||||
new AbortException("Worker was terminated.")
|
||||
);
|
||||
|
||||
@ -2621,18 +2620,18 @@ class WorkerTransport {
|
||||
|
||||
messageHandler.on("GetReader", (data, sink) => {
|
||||
assert(
|
||||
this._networkStream,
|
||||
"GetReader - no `IPDFStream` instance available."
|
||||
this.#networkStream,
|
||||
"GetReader - no `BasePDFStream` instance available."
|
||||
);
|
||||
this._fullReader = this._networkStream.getFullReader();
|
||||
this._fullReader.onProgress = evt => {
|
||||
this._lastProgress = {
|
||||
this.#fullReader = this.#networkStream.getFullReader();
|
||||
this.#fullReader.onProgress = evt => {
|
||||
this.#lastProgress = {
|
||||
loaded: evt.loaded,
|
||||
total: evt.total,
|
||||
};
|
||||
};
|
||||
sink.onPull = () => {
|
||||
this._fullReader
|
||||
this.#fullReader
|
||||
.read()
|
||||
.then(function ({ value, done }) {
|
||||
if (done) {
|
||||
@ -2653,7 +2652,7 @@ class WorkerTransport {
|
||||
};
|
||||
|
||||
sink.onCancel = reason => {
|
||||
this._fullReader.cancel(reason);
|
||||
this.#fullReader.cancel(reason);
|
||||
|
||||
sink.ready.catch(readyReason => {
|
||||
if (this.destroyed) {
|
||||
@ -2665,18 +2664,18 @@ class WorkerTransport {
|
||||
});
|
||||
|
||||
messageHandler.on("ReaderHeadersReady", async data => {
|
||||
await this._fullReader.headersReady;
|
||||
await this.#fullReader.headersReady;
|
||||
|
||||
const { isStreamingSupported, isRangeSupported, contentLength } =
|
||||
this._fullReader;
|
||||
this.#fullReader;
|
||||
|
||||
// If stream or range are disabled, it's our only way to report
|
||||
// loading progress.
|
||||
if (!isStreamingSupported || !isRangeSupported) {
|
||||
if (this._lastProgress) {
|
||||
loadingTask.onProgress?.(this._lastProgress);
|
||||
if (this.#lastProgress) {
|
||||
loadingTask.onProgress?.(this.#lastProgress);
|
||||
}
|
||||
this._fullReader.onProgress = evt => {
|
||||
this.#fullReader.onProgress = evt => {
|
||||
loadingTask.onProgress?.({
|
||||
loaded: evt.loaded,
|
||||
total: evt.total,
|
||||
@ -2689,16 +2688,16 @@ class WorkerTransport {
|
||||
|
||||
messageHandler.on("GetRangeReader", (data, sink) => {
|
||||
assert(
|
||||
this._networkStream,
|
||||
"GetRangeReader - no `IPDFStream` instance available."
|
||||
this.#networkStream,
|
||||
"GetRangeReader - no `BasePDFStream` instance available."
|
||||
);
|
||||
const rangeReader = this._networkStream.getRangeReader(
|
||||
const rangeReader = this.#networkStream.getRangeReader(
|
||||
data.begin,
|
||||
data.end
|
||||
);
|
||||
|
||||
// When streaming is enabled, it's possible that the data requested here
|
||||
// has already been fetched via the `_fullRequestReader` implementation.
|
||||
// has already been fetched via the `#fullReader` implementation.
|
||||
// However, given that the PDF data is loaded asynchronously on the
|
||||
// main-thread and then sent via `postMessage` to the worker-thread,
|
||||
// it may not have been available during parsing (hence the attempt to
|
||||
@ -2706,7 +2705,7 @@ class WorkerTransport {
|
||||
//
|
||||
// To avoid wasting time and resources here, we'll thus *not* dispatch
|
||||
// range requests if the data was already loaded but has not been sent to
|
||||
// the worker-thread yet (which will happen via the `_fullRequestReader`).
|
||||
// the worker-thread yet (which will happen via the `#fullReader`).
|
||||
if (!rangeReader) {
|
||||
sink.close();
|
||||
return;
|
||||
@ -2950,7 +2949,7 @@ class WorkerTransport {
|
||||
isPureXfa: !!this._htmlForXfa,
|
||||
numPages: this._numPages,
|
||||
annotationStorage: map,
|
||||
filename: this._fullReader?.filename ?? null,
|
||||
filename: this.#fullReader?.filename ?? null,
|
||||
},
|
||||
transfer
|
||||
)
|
||||
@ -3118,8 +3117,8 @@ class WorkerTransport {
|
||||
.then(results => ({
|
||||
info: results[0],
|
||||
metadata: results[1] ? new Metadata(results[1]) : null,
|
||||
contentDispositionFilename: this._fullReader?.filename ?? null,
|
||||
contentLength: this._fullReader?.contentLength ?? null,
|
||||
contentDispositionFilename: this.#fullReader?.filename ?? null,
|
||||
contentLength: this.#fullReader?.contentLength ?? null,
|
||||
hasStructTree: results[2],
|
||||
}));
|
||||
this.#methodPromises.set(name, promise);
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AbortException, assert, warn } from "../shared/util.js";
|
||||
import { AbortException, warn } from "../shared/util.js";
|
||||
import {
|
||||
createHeaders,
|
||||
createResponseError,
|
||||
@ -22,6 +22,7 @@ import {
|
||||
validateRangeRequestCapabilities,
|
||||
validateResponseStatus,
|
||||
} from "./network_utils.js";
|
||||
import { BasePDFStream } from "../shared/base_pdf_stream.js";
|
||||
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||
throw new Error(
|
||||
@ -51,47 +52,13 @@ function getArrayBuffer(val) {
|
||||
return new Uint8Array(val).buffer;
|
||||
}
|
||||
|
||||
/** @implements {IPDFStream} */
|
||||
class PDFFetchStream {
|
||||
class PDFFetchStream extends BasePDFStream {
|
||||
_responseOrigin = null;
|
||||
|
||||
constructor(source) {
|
||||
this.source = source;
|
||||
super(source, PDFFetchStreamReader, PDFFetchStreamRangeReader);
|
||||
this.isHttp = /^https?:/i.test(source.url);
|
||||
this.headers = createHeaders(this.isHttp, source.httpHeaders);
|
||||
|
||||
this._fullRequestReader = null;
|
||||
this._rangeRequestReaders = [];
|
||||
}
|
||||
|
||||
get _progressiveDataLength() {
|
||||
return this._fullRequestReader?._loaded ?? 0;
|
||||
}
|
||||
|
||||
getFullReader() {
|
||||
assert(
|
||||
!this._fullRequestReader,
|
||||
"PDFFetchStream.getFullReader can only be called once."
|
||||
);
|
||||
this._fullRequestReader = new PDFFetchStreamReader(this);
|
||||
return this._fullRequestReader;
|
||||
}
|
||||
|
||||
getRangeReader(begin, end) {
|
||||
if (end <= this._progressiveDataLength) {
|
||||
return null;
|
||||
}
|
||||
const reader = new PDFFetchStreamRangeReader(this, begin, end);
|
||||
this._rangeRequestReaders.push(reader);
|
||||
return reader;
|
||||
}
|
||||
|
||||
cancelAllRequests(reason) {
|
||||
this._fullRequestReader?.cancel(reason);
|
||||
|
||||
for (const reader of this._rangeRequestReaders.slice(0)) {
|
||||
reader.cancel(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +69,7 @@ class PDFFetchStreamReader {
|
||||
this._reader = null;
|
||||
this._loaded = 0;
|
||||
this._filename = null;
|
||||
const source = stream.source;
|
||||
const source = stream._source;
|
||||
this._withCredentials = source.withCredentials || false;
|
||||
this._contentLength = source.length;
|
||||
this._headersCapability = Promise.withResolvers();
|
||||
@ -205,7 +172,7 @@ class PDFFetchStreamRangeReader {
|
||||
constructor(stream, begin, end) {
|
||||
this._stream = stream;
|
||||
this._reader = null;
|
||||
const source = stream.source;
|
||||
const source = stream._source;
|
||||
this._withCredentials = source.withCredentials || false;
|
||||
this._readCapability = Promise.withResolvers();
|
||||
|
||||
|
||||
@ -21,6 +21,7 @@ import {
|
||||
getResponseOrigin,
|
||||
validateRangeRequestCapabilities,
|
||||
} from "./network_utils.js";
|
||||
import { BasePDFStream } from "../shared/base_pdf_stream.js";
|
||||
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||
throw new Error(
|
||||
@ -35,18 +36,13 @@ function getArrayBuffer(val) {
|
||||
return typeof val !== "string" ? val : stringToBytes(val).buffer;
|
||||
}
|
||||
|
||||
/** @implements {IPDFStream} */
|
||||
class PDFNetworkStream {
|
||||
class PDFNetworkStream extends BasePDFStream {
|
||||
#pendingRequests = new WeakMap();
|
||||
|
||||
_fullRequestReader = null;
|
||||
|
||||
_rangeRequestReaders = [];
|
||||
|
||||
_responseOrigin = null;
|
||||
|
||||
constructor(source) {
|
||||
this._source = source;
|
||||
super(source, PDFNetworkStreamFullReader, PDFNetworkStreamRangeReader);
|
||||
this.url = source.url;
|
||||
this.isHttp = /^https?:/i.test(this.url);
|
||||
this.headers = createHeaders(this.isHttp, source.httpHeaders);
|
||||
@ -160,38 +156,18 @@ class PDFNetworkStream {
|
||||
}
|
||||
}
|
||||
|
||||
getFullReader() {
|
||||
assert(
|
||||
!this._fullRequestReader,
|
||||
"PDFNetworkStream.getFullReader can only be called once."
|
||||
);
|
||||
this._fullRequestReader = new PDFNetworkStreamFullRequestReader(this);
|
||||
return this._fullRequestReader;
|
||||
}
|
||||
|
||||
getRangeReader(begin, end) {
|
||||
const reader = new PDFNetworkStreamRangeRequestReader(this, begin, end);
|
||||
reader.onClosed = () => {
|
||||
const i = this._rangeRequestReaders.indexOf(reader);
|
||||
if (i >= 0) {
|
||||
this._rangeRequestReaders.splice(i, 1);
|
||||
}
|
||||
};
|
||||
this._rangeRequestReaders.push(reader);
|
||||
return reader;
|
||||
}
|
||||
const reader = super.getRangeReader(begin, end);
|
||||
|
||||
cancelAllRequests(reason) {
|
||||
this._fullRequestReader?.cancel(reason);
|
||||
|
||||
for (const reader of this._rangeRequestReaders.slice(0)) {
|
||||
reader.cancel(reason);
|
||||
if (reader) {
|
||||
reader.onClosed = () => this._rangeReaders.delete(reader);
|
||||
}
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
|
||||
/** @implements {IPDFStreamReader} */
|
||||
class PDFNetworkStreamFullRequestReader {
|
||||
class PDFNetworkStreamFullReader {
|
||||
constructor(stream) {
|
||||
this._stream = stream;
|
||||
const { disableRange, length, rangeChunkSize } = stream._source;
|
||||
@ -355,7 +331,7 @@ class PDFNetworkStreamFullRequestReader {
|
||||
}
|
||||
|
||||
/** @implements {IPDFStreamRangeReader} */
|
||||
class PDFNetworkStreamRangeRequestReader {
|
||||
class PDFNetworkStreamRangeReader {
|
||||
onClosed = null;
|
||||
|
||||
constructor(stream, begin, end) {
|
||||
@ -398,7 +374,7 @@ class PDFNetworkStreamRangeRequestReader {
|
||||
requestCapability.resolve({ value: undefined, done: true });
|
||||
}
|
||||
this._requests.length = 0;
|
||||
this.onClosed?.(this);
|
||||
this.onClosed?.();
|
||||
}
|
||||
|
||||
_onError(status) {
|
||||
@ -435,7 +411,7 @@ class PDFNetworkStreamRangeRequestReader {
|
||||
this._requests.length = 0;
|
||||
|
||||
this._stream._abortRequest(this._requestXhr);
|
||||
this.onClosed?.(this);
|
||||
this.onClosed?.();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
/* globals process */
|
||||
|
||||
import { AbortException, assert, warn } from "../shared/util.js";
|
||||
import { BasePDFStream } from "../shared/base_pdf_stream.js";
|
||||
import { createResponseError } from "./network_utils.js";
|
||||
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||
@ -60,47 +61,14 @@ function getArrayBuffer(val) {
|
||||
return new Uint8Array(val).buffer;
|
||||
}
|
||||
|
||||
class PDFNodeStream {
|
||||
class PDFNodeStream extends BasePDFStream {
|
||||
constructor(source) {
|
||||
this.source = source;
|
||||
super(source, PDFNodeStreamFsFullReader, PDFNodeStreamFsRangeReader);
|
||||
this.url = parseUrlOrPath(source.url);
|
||||
assert(
|
||||
this.url.protocol === "file:",
|
||||
"PDFNodeStream only supports file:// URLs."
|
||||
);
|
||||
|
||||
this._fullRequestReader = null;
|
||||
this._rangeRequestReaders = [];
|
||||
}
|
||||
|
||||
get _progressiveDataLength() {
|
||||
return this._fullRequestReader?._loaded ?? 0;
|
||||
}
|
||||
|
||||
getFullReader() {
|
||||
assert(
|
||||
!this._fullRequestReader,
|
||||
"PDFNodeStream.getFullReader can only be called once."
|
||||
);
|
||||
this._fullRequestReader = new PDFNodeStreamFsFullReader(this);
|
||||
return this._fullRequestReader;
|
||||
}
|
||||
|
||||
getRangeReader(begin, end) {
|
||||
if (end <= this._progressiveDataLength) {
|
||||
return null;
|
||||
}
|
||||
const rangeReader = new PDFNodeStreamFsRangeReader(this, begin, end);
|
||||
this._rangeRequestReaders.push(rangeReader);
|
||||
return rangeReader;
|
||||
}
|
||||
|
||||
cancelAllRequests(reason) {
|
||||
this._fullRequestReader?.cancel(reason);
|
||||
|
||||
for (const reader of this._rangeRequestReaders.slice(0)) {
|
||||
reader.cancel(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,7 +79,7 @@ class PDFNodeStreamFsFullReader {
|
||||
|
||||
constructor(stream) {
|
||||
this.onProgress = null;
|
||||
const source = stream.source;
|
||||
const source = stream._source;
|
||||
this._contentLength = source.length; // optional
|
||||
this._loaded = 0;
|
||||
this._filename = null;
|
||||
|
||||
@ -13,39 +13,42 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("../interfaces").IPDFStream} IPDFStream */
|
||||
/** @typedef {import("../interfaces").IPDFStreamReader} IPDFStreamReader */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../interfaces").IPDFStreamRangeReader} IPDFStreamRangeReader */
|
||||
|
||||
import { assert } from "../shared/util.js";
|
||||
import { BasePDFStream } from "../shared/base_pdf_stream.js";
|
||||
import { isPdfFile } from "./display_utils.js";
|
||||
|
||||
/** @implements {IPDFStream} */
|
||||
class PDFDataTransportStream {
|
||||
constructor(
|
||||
pdfDataRangeTransport,
|
||||
{ disableRange = false, disableStream = false }
|
||||
) {
|
||||
assert(
|
||||
pdfDataRangeTransport,
|
||||
'PDFDataTransportStream - missing required "pdfDataRangeTransport" argument.'
|
||||
function getArrayBuffer(val) {
|
||||
// Prevent any possible issues by only transferring a Uint8Array that
|
||||
// completely "utilizes" its underlying ArrayBuffer.
|
||||
return val instanceof Uint8Array && val.byteLength === val.buffer.byteLength
|
||||
? val.buffer
|
||||
: new Uint8Array(val).buffer;
|
||||
}
|
||||
|
||||
class PDFDataTransportStream extends BasePDFStream {
|
||||
_pdfDataRangeTransport = null;
|
||||
|
||||
_queuedChunks = [];
|
||||
|
||||
constructor(source) {
|
||||
super(
|
||||
source,
|
||||
PDFDataTransportStreamReader,
|
||||
PDFDataTransportStreamRangeReader
|
||||
);
|
||||
const { pdfDataRangeTransport, disableRange, disableStream } = source;
|
||||
const { length, initialData, progressiveDone, contentDispositionFilename } =
|
||||
pdfDataRangeTransport;
|
||||
|
||||
this._queuedChunks = [];
|
||||
this._progressiveDone = progressiveDone;
|
||||
this._contentDispositionFilename = contentDispositionFilename;
|
||||
|
||||
if (initialData?.length > 0) {
|
||||
// Prevent any possible issues by only transferring a Uint8Array that
|
||||
// completely "utilizes" its underlying ArrayBuffer.
|
||||
const buffer =
|
||||
initialData instanceof Uint8Array &&
|
||||
initialData.byteLength === initialData.buffer.byteLength
|
||||
? initialData.buffer
|
||||
: new Uint8Array(initialData).buffer;
|
||||
const buffer = getArrayBuffer(initialData);
|
||||
this._queuedChunks.push(buffer);
|
||||
}
|
||||
|
||||
@ -54,139 +57,89 @@ class PDFDataTransportStream {
|
||||
this._isRangeSupported = !disableRange;
|
||||
this._contentLength = length;
|
||||
|
||||
this._fullRequestReader = null;
|
||||
this._rangeReaders = [];
|
||||
|
||||
pdfDataRangeTransport.addRangeListener((begin, chunk) => {
|
||||
this._onReceiveData({ begin, chunk });
|
||||
this.#onReceiveData(begin, chunk);
|
||||
});
|
||||
|
||||
pdfDataRangeTransport.addProgressListener((loaded, total) => {
|
||||
this._onProgress({ loaded, total });
|
||||
if (total !== undefined) {
|
||||
this._fullReader?.onProgress?.({ loaded, total });
|
||||
}
|
||||
});
|
||||
|
||||
pdfDataRangeTransport.addProgressiveReadListener(chunk => {
|
||||
this._onReceiveData({ chunk });
|
||||
this.#onReceiveData(/* begin = */ undefined, chunk);
|
||||
});
|
||||
|
||||
pdfDataRangeTransport.addProgressiveDoneListener(() => {
|
||||
this._onProgressiveDone();
|
||||
this._fullReader?.progressiveDone();
|
||||
this._progressiveDone = true;
|
||||
});
|
||||
|
||||
pdfDataRangeTransport.transportReady();
|
||||
}
|
||||
|
||||
_onReceiveData({ begin, chunk }) {
|
||||
// Prevent any possible issues by only transferring a Uint8Array that
|
||||
// completely "utilizes" its underlying ArrayBuffer.
|
||||
const buffer =
|
||||
chunk instanceof Uint8Array &&
|
||||
chunk.byteLength === chunk.buffer.byteLength
|
||||
? chunk.buffer
|
||||
: new Uint8Array(chunk).buffer;
|
||||
#onReceiveData(begin, chunk) {
|
||||
const buffer = getArrayBuffer(chunk);
|
||||
|
||||
if (begin === undefined) {
|
||||
if (this._fullRequestReader) {
|
||||
this._fullRequestReader._enqueue(buffer);
|
||||
if (this._fullReader) {
|
||||
this._fullReader._enqueue(buffer);
|
||||
} else {
|
||||
this._queuedChunks.push(buffer);
|
||||
}
|
||||
} else {
|
||||
const found = this._rangeReaders.some(function (rangeReader) {
|
||||
if (rangeReader._begin !== begin) {
|
||||
return false;
|
||||
}
|
||||
rangeReader._enqueue(buffer);
|
||||
return true;
|
||||
});
|
||||
const rangeReader = this._rangeReaders
|
||||
.keys()
|
||||
.find(r => r._begin === begin);
|
||||
|
||||
assert(
|
||||
found,
|
||||
"_onReceiveData - no `PDFDataTransportStreamRangeReader` instance found."
|
||||
rangeReader,
|
||||
"#onReceiveData - no `PDFDataTransportStreamRangeReader` instance found."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get _progressiveDataLength() {
|
||||
return this._fullRequestReader?._loaded ?? 0;
|
||||
}
|
||||
|
||||
_onProgress(evt) {
|
||||
if (evt.total !== undefined) {
|
||||
this._fullRequestReader?.onProgress?.({
|
||||
loaded: evt.loaded,
|
||||
total: evt.total,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_onProgressiveDone() {
|
||||
this._fullRequestReader?.progressiveDone();
|
||||
this._progressiveDone = true;
|
||||
}
|
||||
|
||||
_removeRangeReader(reader) {
|
||||
const i = this._rangeReaders.indexOf(reader);
|
||||
if (i >= 0) {
|
||||
this._rangeReaders.splice(i, 1);
|
||||
rangeReader._enqueue(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
getFullReader() {
|
||||
assert(
|
||||
!this._fullRequestReader,
|
||||
"PDFDataTransportStream.getFullReader can only be called once."
|
||||
);
|
||||
const queuedChunks = this._queuedChunks;
|
||||
const reader = super.getFullReader();
|
||||
this._queuedChunks = null;
|
||||
return new PDFDataTransportStreamReader(
|
||||
this,
|
||||
queuedChunks,
|
||||
this._progressiveDone,
|
||||
this._contentDispositionFilename
|
||||
);
|
||||
return reader;
|
||||
}
|
||||
|
||||
getRangeReader(begin, end) {
|
||||
if (end <= this._progressiveDataLength) {
|
||||
return null;
|
||||
const reader = super.getRangeReader(begin, end);
|
||||
|
||||
if (reader) {
|
||||
reader.onDone = () => this._rangeReaders.delete(reader);
|
||||
|
||||
this._pdfDataRangeTransport.requestDataRange(begin, end);
|
||||
}
|
||||
const reader = new PDFDataTransportStreamRangeReader(this, begin, end);
|
||||
this._pdfDataRangeTransport.requestDataRange(begin, end);
|
||||
this._rangeReaders.push(reader);
|
||||
return reader;
|
||||
}
|
||||
|
||||
cancelAllRequests(reason) {
|
||||
this._fullRequestReader?.cancel(reason);
|
||||
super.cancelAllRequests(reason);
|
||||
|
||||
for (const reader of this._rangeReaders.slice(0)) {
|
||||
reader.cancel(reason);
|
||||
}
|
||||
this._pdfDataRangeTransport.abort();
|
||||
}
|
||||
}
|
||||
|
||||
/** @implements {IPDFStreamReader} */
|
||||
class PDFDataTransportStreamReader {
|
||||
constructor(
|
||||
stream,
|
||||
queuedChunks,
|
||||
progressiveDone = false,
|
||||
contentDispositionFilename = null
|
||||
) {
|
||||
constructor(stream) {
|
||||
this._stream = stream;
|
||||
this._done = progressiveDone || false;
|
||||
this._filename = isPdfFile(contentDispositionFilename)
|
||||
? contentDispositionFilename
|
||||
this._done = stream._progressiveDone || false;
|
||||
this._filename = isPdfFile(stream._contentDispositionFilename)
|
||||
? stream._contentDispositionFilename
|
||||
: null;
|
||||
this._queuedChunks = queuedChunks || [];
|
||||
this._queuedChunks = stream._queuedChunks || [];
|
||||
this._loaded = 0;
|
||||
for (const chunk of this._queuedChunks) {
|
||||
this._loaded += chunk.byteLength;
|
||||
}
|
||||
this._requests = [];
|
||||
this._headersReady = Promise.resolve();
|
||||
stream._fullRequestReader = this;
|
||||
|
||||
this.onProgress = null;
|
||||
}
|
||||
@ -255,6 +208,8 @@ class PDFDataTransportStreamReader {
|
||||
|
||||
/** @implements {IPDFStreamRangeReader} */
|
||||
class PDFDataTransportStreamRangeReader {
|
||||
onDone = null;
|
||||
|
||||
constructor(stream, begin, end) {
|
||||
this._stream = stream;
|
||||
this._begin = begin;
|
||||
@ -279,7 +234,7 @@ class PDFDataTransportStreamRangeReader {
|
||||
this._requests.length = 0;
|
||||
}
|
||||
this._done = true;
|
||||
this._stream._removeRangeReader(this);
|
||||
this.onDone?.();
|
||||
}
|
||||
|
||||
async read() {
|
||||
@ -302,7 +257,7 @@ class PDFDataTransportStreamRangeReader {
|
||||
requestCapability.resolve({ value: undefined, done: true });
|
||||
}
|
||||
this._requests.length = 0;
|
||||
this._stream._removeRangeReader(this);
|
||||
this.onDone?.();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,40 +13,83 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { assert, unreachable } from "./util.js";
|
||||
|
||||
/**
|
||||
* Interface that represents PDF data transport. If possible, it allows
|
||||
* progressively load entire or fragment of the PDF binary data.
|
||||
*
|
||||
* @interface
|
||||
*/
|
||||
class IPDFStream {
|
||||
class BasePDFStream {
|
||||
#PDFStreamReader = null;
|
||||
|
||||
#PDFStreamRangeReader = null;
|
||||
|
||||
_fullReader = null;
|
||||
|
||||
_rangeReaders = new Set();
|
||||
|
||||
_source = null;
|
||||
|
||||
constructor(source, PDFStreamReader, PDFStreamRangeReader) {
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||
this.constructor === BasePDFStream
|
||||
) {
|
||||
unreachable("Cannot initialize BasePDFStream.");
|
||||
}
|
||||
this._source = source;
|
||||
|
||||
this.#PDFStreamReader = PDFStreamReader;
|
||||
this.#PDFStreamRangeReader = PDFStreamRangeReader;
|
||||
}
|
||||
|
||||
get _progressiveDataLength() {
|
||||
return this._fullReader?._loaded ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reader for the entire PDF data.
|
||||
* @returns {IPDFStreamReader}
|
||||
* @returns {BasePDFStreamReader}
|
||||
*/
|
||||
getFullReader() {
|
||||
return null;
|
||||
assert(
|
||||
!this._fullReader,
|
||||
"BasePDFStream.getFullReader can only be called once."
|
||||
);
|
||||
return (this._fullReader = new this.#PDFStreamReader(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a reader for the range of the PDF data.
|
||||
*
|
||||
* NOTE: Currently this method is only expected to be invoked *after*
|
||||
* the `IPDFStreamReader.prototype.headersReady` promise has resolved.
|
||||
* the `BasePDFStreamReader.prototype.headersReady` promise has resolved.
|
||||
*
|
||||
* @param {number} begin - the start offset of the data.
|
||||
* @param {number} end - the end offset of the data.
|
||||
* @returns {IPDFStreamRangeReader}
|
||||
* @returns {BasePDFStreamRangeReader}
|
||||
*/
|
||||
getRangeReader(begin, end) {
|
||||
return null;
|
||||
if (end <= this._progressiveDataLength) {
|
||||
return null;
|
||||
}
|
||||
const reader = new this.#PDFStreamRangeReader(this, begin, end);
|
||||
this._rangeReaders.add(reader);
|
||||
return reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels all opened reader and closes all their opened requests.
|
||||
* @param {Object} reason - the reason for cancelling
|
||||
*/
|
||||
cancelAllRequests(reason) {}
|
||||
cancelAllRequests(reason) {
|
||||
this._fullReader?.cancel(reason);
|
||||
|
||||
// Always create a copy of the rangeReaders.
|
||||
for (const reader of new Set(this._rangeReaders)) {
|
||||
reader.cancel(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -152,4 +195,4 @@ class IPDFStreamRangeReader {
|
||||
cancel(reason) {}
|
||||
}
|
||||
|
||||
export { IPDFStream, IPDFStreamRangeReader, IPDFStreamReader };
|
||||
export { BasePDFStream, IPDFStreamRangeReader, IPDFStreamReader };
|
||||
@ -16,8 +16,7 @@
|
||||
import { AbortException, isNodeJS } from "../../src/shared/util.js";
|
||||
import { getCrossOriginHostname, TestPdfsServer } from "./test_utils.js";
|
||||
|
||||
// Common tests to verify behavior across implementations of the IPDFStream
|
||||
// interface:
|
||||
// Common tests to verify behavior across `BasePDFStream` implementations:
|
||||
// - PDFNetworkStream by network_spec.js
|
||||
// - PDFFetchStream by fetch_stream_spec.js
|
||||
async function testCrossOriginRedirects({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user