mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-03 12:57:20 +02:00
Compare commits
22 Commits
814df09e21
...
f4326e17c4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4326e17c4 | ||
|
|
3f21efc942 | ||
|
|
8eb9340fa7 | ||
|
|
031f633236 | ||
|
|
aa4d0f7c07 | ||
|
|
023af46186 | ||
|
|
839c257f87 | ||
|
|
ff7f87fc21 | ||
|
|
50a12e3e67 | ||
|
|
b517b5c597 | ||
|
|
384c6208b2 | ||
|
|
e4cd3176ab | ||
|
|
ecb09d62fc | ||
|
|
43273fde27 | ||
|
|
4ca205bac3 | ||
|
|
54d8c5e7b4 | ||
|
|
4a8fb4dde1 | ||
|
|
a80f10ff1a | ||
|
|
05b78ce03c | ||
|
|
987265720e | ||
|
|
62d5408cf0 | ||
|
|
50f2d4db65 |
2
.github/workflows/types_tests.yml
vendored
2
.github/workflows/types_tests.yml
vendored
@ -28,4 +28,4 @@ jobs:
|
|||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Run types tests
|
- name: Run types tests
|
||||||
run: npx gulp typestest
|
run: npx gulp types
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export default [
|
|||||||
"**/docs/",
|
"**/docs/",
|
||||||
"**/node_modules/",
|
"**/node_modules/",
|
||||||
"external/bcmaps/",
|
"external/bcmaps/",
|
||||||
|
"external/brotli/",
|
||||||
"external/builder/fixtures/",
|
"external/builder/fixtures/",
|
||||||
"external/builder/fixtures_babel/",
|
"external/builder/fixtures_babel/",
|
||||||
"external/openjpeg/",
|
"external/openjpeg/",
|
||||||
|
|||||||
@ -46,19 +46,13 @@ const PDFViewerApplication = {
|
|||||||
* @returns {Promise} - Returns the promise, which is resolved when document
|
* @returns {Promise} - Returns the promise, which is resolved when document
|
||||||
* is opened.
|
* is opened.
|
||||||
*/
|
*/
|
||||||
open(params) {
|
async open(params) {
|
||||||
if (this.pdfLoadingTask) {
|
if (this.pdfLoadingTask) {
|
||||||
// We need to destroy already opened document
|
// We need to destroy already opened document.
|
||||||
return this.close().then(
|
await this.close();
|
||||||
function () {
|
|
||||||
// ... and repeat the open() call.
|
|
||||||
return this.open(params);
|
|
||||||
}.bind(this)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = params.url;
|
const { url } = params;
|
||||||
const self = this;
|
|
||||||
this.setTitleUsingUrl(url);
|
this.setTitleUsingUrl(url);
|
||||||
|
|
||||||
// Loading document.
|
// Loading document.
|
||||||
@ -70,24 +64,22 @@ const PDFViewerApplication = {
|
|||||||
});
|
});
|
||||||
this.pdfLoadingTask = loadingTask;
|
this.pdfLoadingTask = loadingTask;
|
||||||
|
|
||||||
loadingTask.onProgress = function (progressData) {
|
loadingTask.onProgress = evt => this.progress(evt.percent);
|
||||||
self.progress(progressData.loaded / progressData.total);
|
|
||||||
};
|
|
||||||
|
|
||||||
return loadingTask.promise.then(
|
return loadingTask.promise.then(
|
||||||
function (pdfDocument) {
|
pdfDocument => {
|
||||||
// Document loaded, specifying document for the viewer.
|
// Document loaded, specifying document for the viewer.
|
||||||
self.pdfDocument = pdfDocument;
|
this.pdfDocument = pdfDocument;
|
||||||
self.pdfViewer.setDocument(pdfDocument);
|
this.pdfViewer.setDocument(pdfDocument);
|
||||||
self.pdfLinkService.setDocument(pdfDocument);
|
this.pdfLinkService.setDocument(pdfDocument);
|
||||||
self.pdfHistory.initialize({
|
this.pdfHistory.initialize({
|
||||||
fingerprint: pdfDocument.fingerprints[0],
|
fingerprint: pdfDocument.fingerprints[0],
|
||||||
});
|
});
|
||||||
|
|
||||||
self.loadingBar.hide();
|
this.loadingBar.hide();
|
||||||
self.setTitleUsingMetadata(pdfDocument);
|
this.setTitleUsingMetadata(pdfDocument);
|
||||||
},
|
},
|
||||||
function (reason) {
|
reason => {
|
||||||
let key = "pdfjs-loading-error";
|
let key = "pdfjs-loading-error";
|
||||||
if (reason instanceof pdfjsLib.InvalidPDFException) {
|
if (reason instanceof pdfjsLib.InvalidPDFException) {
|
||||||
key = "pdfjs-invalid-file-error";
|
key = "pdfjs-invalid-file-error";
|
||||||
@ -96,10 +88,10 @@ const PDFViewerApplication = {
|
|||||||
? "pdfjs-missing-file-error"
|
? "pdfjs-missing-file-error"
|
||||||
: "pdfjs-unexpected-response-error";
|
: "pdfjs-unexpected-response-error";
|
||||||
}
|
}
|
||||||
self.l10n.get(key).then(msg => {
|
this.l10n.get(key).then(msg => {
|
||||||
self.error(msg, { message: reason?.message });
|
this.error(msg, { message: reason.message });
|
||||||
});
|
});
|
||||||
self.loadingBar.hide();
|
this.loadingBar.hide();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -109,9 +101,9 @@ const PDFViewerApplication = {
|
|||||||
* @returns {Promise} - Returns the promise, which is resolved when all
|
* @returns {Promise} - Returns the promise, which is resolved when all
|
||||||
* destruction is completed.
|
* destruction is completed.
|
||||||
*/
|
*/
|
||||||
close() {
|
async close() {
|
||||||
if (!this.pdfLoadingTask) {
|
if (!this.pdfLoadingTask) {
|
||||||
return Promise.resolve();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = this.pdfLoadingTask.destroy();
|
const promise = this.pdfLoadingTask.destroy();
|
||||||
@ -128,7 +120,7 @@ const PDFViewerApplication = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise;
|
await promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
get loadingBar() {
|
get loadingBar() {
|
||||||
@ -152,48 +144,36 @@ const PDFViewerApplication = {
|
|||||||
this.setTitle(title);
|
this.setTitle(title);
|
||||||
},
|
},
|
||||||
|
|
||||||
setTitleUsingMetadata(pdfDocument) {
|
async setTitleUsingMetadata(pdfDocument) {
|
||||||
const self = this;
|
const { info, metadata } = await pdfDocument.getMetadata();
|
||||||
pdfDocument.getMetadata().then(function (data) {
|
this.documentInfo = info;
|
||||||
const info = data.info,
|
this.metadata = metadata;
|
||||||
metadata = data.metadata;
|
|
||||||
self.documentInfo = info;
|
|
||||||
self.metadata = metadata;
|
|
||||||
|
|
||||||
// Provides some basic debug information
|
// Provides some basic debug information
|
||||||
console.log(
|
console.log(
|
||||||
"PDF " +
|
`PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` +
|
||||||
pdfDocument.fingerprints[0] +
|
`${(metadata?.get("pdf:producer") || info.Producer || "-").trim()} / ` +
|
||||||
" [" +
|
`${(metadata?.get("xmp:creatortool") || info.Creator || "-").trim()}` +
|
||||||
info.PDFFormatVersion +
|
`] (PDF.js: ${pdfjsLib.version || "?"} [${pdfjsLib.build || "?"}])`
|
||||||
" " +
|
);
|
||||||
(info.Producer || "-").trim() +
|
|
||||||
" / " +
|
|
||||||
(info.Creator || "-").trim() +
|
|
||||||
"]" +
|
|
||||||
" (PDF.js: " +
|
|
||||||
(pdfjsLib.version || "-") +
|
|
||||||
")"
|
|
||||||
);
|
|
||||||
|
|
||||||
let pdfTitle;
|
let pdfTitle;
|
||||||
if (metadata && metadata.has("dc:title")) {
|
if (metadata && metadata.has("dc:title")) {
|
||||||
const title = metadata.get("dc:title");
|
const title = metadata.get("dc:title");
|
||||||
// Ghostscript sometimes returns 'Untitled', so prevent setting the
|
// Ghostscript sometimes returns 'Untitled', so prevent setting the
|
||||||
// title to 'Untitled.
|
// title to 'Untitled.
|
||||||
if (title !== "Untitled") {
|
if (title !== "Untitled") {
|
||||||
pdfTitle = title;
|
pdfTitle = title;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!pdfTitle && info && info.Title) {
|
if (!pdfTitle && info && info.Title) {
|
||||||
pdfTitle = info.Title;
|
pdfTitle = info.Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pdfTitle) {
|
if (pdfTitle) {
|
||||||
self.setTitle(pdfTitle + " - " + document.title);
|
this.setTitle(pdfTitle + " - " + document.title);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
setTitle: function pdfViewSetTitle(title) {
|
setTitle: function pdfViewSetTitle(title) {
|
||||||
@ -223,8 +203,7 @@ const PDFViewerApplication = {
|
|||||||
console.error(`${message}\n\n${moreInfoText.join("\n")}`);
|
console.error(`${message}\n\n${moreInfoText.join("\n")}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
progress: function pdfViewProgress(level) {
|
progress(percent) {
|
||||||
const percent = Math.round(level * 100);
|
|
||||||
// Updating the bar if value increases.
|
// Updating the bar if value increases.
|
||||||
if (percent > this.loadingBar.percent || isNaN(percent)) {
|
if (percent > this.loadingBar.percent || isNaN(percent)) {
|
||||||
this.loadingBar.percent = percent;
|
this.loadingBar.percent = percent;
|
||||||
|
|||||||
19
external/brotli/LICENSE_BROTLI
vendored
Normal file
19
external/brotli/LICENSE_BROTLI
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
8
external/brotli/README.md
vendored
Normal file
8
external/brotli/README.md
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
## Release
|
||||||
|
|
||||||
|
In order to get the file `decoder.js`:
|
||||||
|
* `gulp release-brotli --hash` followed by the git hash of the revision.
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
[brotli](https://github.com/google/brotli/) is under [MIT License](https://github.com/google/brotli/blob/master/LICENSE)
|
||||||
2466
external/brotli/decode.js
vendored
Normal file
2466
external/brotli/decode.js
vendored
Normal file
File diff suppressed because one or more lines are too long
26
gulpfile.mjs
26
gulpfile.mjs
@ -22,6 +22,7 @@ import autoprefixer from "autoprefixer";
|
|||||||
import babel from "@babel/core";
|
import babel from "@babel/core";
|
||||||
import { buildPrefsSchema } from "./external/chromium/prefs.mjs";
|
import { buildPrefsSchema } from "./external/chromium/prefs.mjs";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
|
import { finished } from "stream/promises";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import gulp from "gulp";
|
import gulp from "gulp";
|
||||||
import hljs from "highlight.js";
|
import hljs from "highlight.js";
|
||||||
@ -231,7 +232,7 @@ function createWebpackAlias(defines) {
|
|||||||
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
|
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
|
||||||
libraryAlias["display-network"] = "src/display/network.js";
|
libraryAlias["display-network"] = "src/display/network.js";
|
||||||
|
|
||||||
viewerAlias["web-download_manager"] = "web/download_manager.js";
|
viewerAlias["web-download_manager"] = "web/chromecom.js";
|
||||||
viewerAlias["web-external_services"] = "web/chromecom.js";
|
viewerAlias["web-external_services"] = "web/chromecom.js";
|
||||||
viewerAlias["web-null_l10n"] = "web/l10n.js";
|
viewerAlias["web-null_l10n"] = "web/l10n.js";
|
||||||
viewerAlias["web-preferences"] = "web/chromecom.js";
|
viewerAlias["web-preferences"] = "web/chromecom.js";
|
||||||
@ -813,6 +814,28 @@ gulp.task("default", function (done) {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task("release-brotli", async function (done) {
|
||||||
|
const hashIndex = process.argv.indexOf("--hash");
|
||||||
|
if (hashIndex === -1 || hashIndex + 1 >= process.argv.length) {
|
||||||
|
throw new Error('Missing "--hash <commit-hash>" argument.');
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
console.log("### Getting Brotli js file for release");
|
||||||
|
|
||||||
|
const OUTPUT_DIR = "./external/brotli/";
|
||||||
|
const hash = process.argv[hashIndex + 1];
|
||||||
|
const url = `https://raw.githubusercontent.com/google/brotli/${hash}/js/decode.js`;
|
||||||
|
const outputPath = OUTPUT_DIR + "decode.js";
|
||||||
|
const res = await fetch(url);
|
||||||
|
const fileStream = fs.createWriteStream(outputPath, { flags: "w" });
|
||||||
|
await finished(stream.Readable.fromWeb(res.body).pipe(fileStream));
|
||||||
|
fileStream.end();
|
||||||
|
|
||||||
|
console.log(`Brotli js file saved to: ${outputPath}`);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
function createBuildNumber(done) {
|
function createBuildNumber(done) {
|
||||||
console.log("\n### Getting extension build number");
|
console.log("\n### Getting extension build number");
|
||||||
|
|
||||||
@ -1582,6 +1605,7 @@ function buildLib(defines, dir) {
|
|||||||
gulp.src("external/openjpeg/*.js", { base: "openjpeg/", encoding: false }),
|
gulp.src("external/openjpeg/*.js", { base: "openjpeg/", encoding: false }),
|
||||||
gulp.src("external/qcms/*.js", { base: "qcms/", encoding: false }),
|
gulp.src("external/qcms/*.js", { base: "qcms/", encoding: false }),
|
||||||
gulp.src("external/jbig2/*.js", { base: "jbig2/", encoding: false }),
|
gulp.src("external/jbig2/*.js", { base: "jbig2/", encoding: false }),
|
||||||
|
gulp.src("external/brotli/*.js", { base: "brotli/", encoding: false }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return buildLibHelper(bundleDefines, inputStream, dir);
|
return buildLibHelper(bundleDefines, inputStream, dir);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"stableVersion": "5.4.530",
|
"stableVersion": "5.4.624",
|
||||||
"baseVersion": "1b427a3af5e0a40c296a3cafb08edbd36d973ff1",
|
"baseVersion": "1b427a3af5e0a40c296a3cafb08edbd36d973ff1",
|
||||||
"versionPrefix": "5.4."
|
"versionPrefix": "5.4."
|
||||||
}
|
}
|
||||||
|
|||||||
86
src/core/brotli_stream.js
Normal file
86
src/core/brotli_stream.js
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/* Copyright 2026 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BrotliDecode } from "../../external/brotli/decode.js";
|
||||||
|
import { DecodeStream } from "./decode_stream.js";
|
||||||
|
import { Stream } from "./stream.js";
|
||||||
|
|
||||||
|
class BrotliStream extends DecodeStream {
|
||||||
|
#isAsync = true;
|
||||||
|
|
||||||
|
constructor(stream, maybeLength) {
|
||||||
|
super(maybeLength);
|
||||||
|
|
||||||
|
this.stream = stream;
|
||||||
|
this.dict = stream.dict;
|
||||||
|
}
|
||||||
|
|
||||||
|
readBlock() {
|
||||||
|
// TODO: add some telemetry to measure how often we fallback here.
|
||||||
|
// Get all bytes from the input stream
|
||||||
|
const bytes = this.stream.getBytes();
|
||||||
|
const decodedData = BrotliDecode(
|
||||||
|
new Int8Array(bytes.buffer, bytes.byteOffset, bytes.length)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.buffer = new Uint8Array(
|
||||||
|
decodedData.buffer,
|
||||||
|
decodedData.byteOffset,
|
||||||
|
decodedData.length
|
||||||
|
);
|
||||||
|
this.bufferLength = this.buffer.length;
|
||||||
|
this.eof = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getImageData(length, _decoderOptions) {
|
||||||
|
const data = await this.asyncGetBytes();
|
||||||
|
if (!data) {
|
||||||
|
return this.getBytes(length);
|
||||||
|
}
|
||||||
|
if (data.length <= length) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
return data.subarray(0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
async asyncGetBytes() {
|
||||||
|
const { decompressed, compressed } =
|
||||||
|
await this.asyncGetBytesFromDecompressionStream("brotli");
|
||||||
|
if (decompressed) {
|
||||||
|
return decompressed;
|
||||||
|
}
|
||||||
|
// DecompressionStream failed (for example because there are some extra
|
||||||
|
// bytes after the end of the compressed data), so we fallback to our
|
||||||
|
// decoder.
|
||||||
|
// We already get the bytes from the underlying stream, so we just reuse
|
||||||
|
// them to avoid get them again.
|
||||||
|
|
||||||
|
this.#isAsync = false;
|
||||||
|
this.stream = new Stream(
|
||||||
|
compressed,
|
||||||
|
0,
|
||||||
|
compressed.length,
|
||||||
|
this.stream.dict
|
||||||
|
);
|
||||||
|
this.reset();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAsync() {
|
||||||
|
return this.#isAsync;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BrotliStream };
|
||||||
@ -18,6 +18,12 @@ import { assert } from "../shared/util.js";
|
|||||||
import { Stream } from "./stream.js";
|
import { Stream } from "./stream.js";
|
||||||
|
|
||||||
class ChunkedStream extends Stream {
|
class ChunkedStream extends Stream {
|
||||||
|
progressiveDataLength = 0;
|
||||||
|
|
||||||
|
_lastSuccessfulEnsureByteChunk = -1; // Single-entry cache
|
||||||
|
|
||||||
|
_loadedChunks = new Set();
|
||||||
|
|
||||||
constructor(length, chunkSize, manager) {
|
constructor(length, chunkSize, manager) {
|
||||||
super(
|
super(
|
||||||
/* arrayBuffer = */ new Uint8Array(length),
|
/* arrayBuffer = */ new Uint8Array(length),
|
||||||
@ -27,11 +33,8 @@ class ChunkedStream extends Stream {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.chunkSize = chunkSize;
|
this.chunkSize = chunkSize;
|
||||||
this._loadedChunks = new Set();
|
|
||||||
this.numChunks = Math.ceil(length / chunkSize);
|
this.numChunks = Math.ceil(length / chunkSize);
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
this.progressiveDataLength = 0;
|
|
||||||
this.lastSuccessfulEnsureByteChunk = -1; // Single-entry cache
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If a particular stream does not implement one or more of these methods,
|
// If a particular stream does not implement one or more of these methods,
|
||||||
@ -106,14 +109,14 @@ class ChunkedStream extends Stream {
|
|||||||
if (chunk > this.numChunks) {
|
if (chunk > this.numChunks) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (chunk === this.lastSuccessfulEnsureByteChunk) {
|
if (chunk === this._lastSuccessfulEnsureByteChunk) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._loadedChunks.has(chunk)) {
|
if (!this._loadedChunks.has(chunk)) {
|
||||||
throw new MissingDataException(pos, pos + 1);
|
throw new MissingDataException(pos, pos + 1);
|
||||||
}
|
}
|
||||||
this.lastSuccessfulEnsureByteChunk = chunk;
|
this._lastSuccessfulEnsureByteChunk = chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureRange(begin, end) {
|
ensureRange(begin, end) {
|
||||||
@ -257,40 +260,37 @@ class ChunkedStream extends Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ChunkedStreamManager {
|
class ChunkedStreamManager {
|
||||||
constructor(pdfNetworkStream, args) {
|
aborted = false;
|
||||||
|
|
||||||
|
currRequestId = 0;
|
||||||
|
|
||||||
|
_chunksNeededByRequest = new Map();
|
||||||
|
|
||||||
|
_loadedStreamCapability = Promise.withResolvers();
|
||||||
|
|
||||||
|
_promisesByRequest = new Map();
|
||||||
|
|
||||||
|
_requestsByChunk = new Map();
|
||||||
|
|
||||||
|
constructor(pdfStream, args) {
|
||||||
this.length = args.length;
|
this.length = args.length;
|
||||||
this.chunkSize = args.rangeChunkSize;
|
this.chunkSize = args.rangeChunkSize;
|
||||||
this.stream = new ChunkedStream(this.length, this.chunkSize, this);
|
this.stream = new ChunkedStream(this.length, this.chunkSize, this);
|
||||||
this.pdfNetworkStream = pdfNetworkStream;
|
this.pdfStream = pdfStream;
|
||||||
this.disableAutoFetch = args.disableAutoFetch;
|
this.disableAutoFetch = args.disableAutoFetch;
|
||||||
this.msgHandler = args.msgHandler;
|
this.msgHandler = args.msgHandler;
|
||||||
|
|
||||||
this.currRequestId = 0;
|
|
||||||
|
|
||||||
this._chunksNeededByRequest = new Map();
|
|
||||||
this._requestsByChunk = new Map();
|
|
||||||
this._promisesByRequest = new Map();
|
|
||||||
this.progressiveDataLength = 0;
|
|
||||||
this.aborted = false;
|
|
||||||
|
|
||||||
this._loadedStreamCapability = Promise.withResolvers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sendRequest(begin, end) {
|
sendRequest(begin, end) {
|
||||||
const rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
|
const rangeReader = this.pdfStream.getRangeReader(begin, end);
|
||||||
if (!rangeReader.isStreamingSupported) {
|
|
||||||
rangeReader.onProgress = this.onProgress.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
let chunks = [],
|
let chunks = [];
|
||||||
loaded = 0;
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const readChunk = ({ value, done }) => {
|
const readChunk = ({ value, done }) => {
|
||||||
try {
|
try {
|
||||||
if (done) {
|
if (done) {
|
||||||
const chunkData = arrayBuffersToBytes(chunks);
|
resolve(arrayBuffersToBytes(chunks));
|
||||||
chunks = null;
|
chunks = null;
|
||||||
resolve(chunkData);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
||||||
@ -299,12 +299,6 @@ class ChunkedStreamManager {
|
|||||||
"readChunk (sendRequest) - expected an ArrayBuffer."
|
"readChunk (sendRequest) - expected an ArrayBuffer."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
loaded += value.byteLength;
|
|
||||||
|
|
||||||
if (rangeReader.isStreamingSupported) {
|
|
||||||
this.onProgress({ loaded });
|
|
||||||
}
|
|
||||||
|
|
||||||
chunks.push(value);
|
chunks.push(value);
|
||||||
rangeReader.read().then(readChunk, reject);
|
rangeReader.read().then(readChunk, reject);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -446,34 +440,26 @@ class ChunkedStreamManager {
|
|||||||
return groupedChunks;
|
return groupedChunks;
|
||||||
}
|
}
|
||||||
|
|
||||||
onProgress(args) {
|
|
||||||
this.msgHandler.send("DocProgress", {
|
|
||||||
loaded: this.stream.numChunksLoaded * this.chunkSize + args.loaded,
|
|
||||||
total: this.length,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onReceiveData(args) {
|
onReceiveData(args) {
|
||||||
|
const { chunkSize, length, stream } = this;
|
||||||
|
|
||||||
const chunk = args.chunk;
|
const chunk = args.chunk;
|
||||||
const isProgressive = args.begin === undefined;
|
const isProgressive = args.begin === undefined;
|
||||||
const begin = isProgressive ? this.progressiveDataLength : args.begin;
|
const begin = isProgressive ? stream.progressiveDataLength : args.begin;
|
||||||
const end = begin + chunk.byteLength;
|
const end = begin + chunk.byteLength;
|
||||||
|
|
||||||
const beginChunk = Math.floor(begin / this.chunkSize);
|
const beginChunk = Math.floor(begin / chunkSize);
|
||||||
const endChunk =
|
const endChunk =
|
||||||
end < this.length
|
end < length ? Math.floor(end / chunkSize) : Math.ceil(end / chunkSize);
|
||||||
? Math.floor(end / this.chunkSize)
|
|
||||||
: Math.ceil(end / this.chunkSize);
|
|
||||||
|
|
||||||
if (isProgressive) {
|
if (isProgressive) {
|
||||||
this.stream.onReceiveProgressiveData(chunk);
|
stream.onReceiveProgressiveData(chunk);
|
||||||
this.progressiveDataLength = end;
|
|
||||||
} else {
|
} else {
|
||||||
this.stream.onReceiveData(begin, chunk);
|
stream.onReceiveData(begin, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.stream.isDataLoaded) {
|
if (stream.isDataLoaded) {
|
||||||
this._loadedStreamCapability.resolve(this.stream);
|
this._loadedStreamCapability.resolve(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadedRequests = [];
|
const loadedRequests = [];
|
||||||
@ -502,16 +488,16 @@ class ChunkedStreamManager {
|
|||||||
// unfetched chunk of the PDF file.
|
// unfetched chunk of the PDF file.
|
||||||
if (!this.disableAutoFetch && this._requestsByChunk.size === 0) {
|
if (!this.disableAutoFetch && this._requestsByChunk.size === 0) {
|
||||||
let nextEmptyChunk;
|
let nextEmptyChunk;
|
||||||
if (this.stream.numChunksLoaded === 1) {
|
if (stream.numChunksLoaded === 1) {
|
||||||
// This is a special optimization so that after fetching the first
|
// This is a special optimization so that after fetching the first
|
||||||
// chunk, rather than fetching the second chunk, we fetch the last
|
// chunk, rather than fetching the second chunk, we fetch the last
|
||||||
// chunk.
|
// chunk.
|
||||||
const lastChunk = this.stream.numChunks - 1;
|
const lastChunk = stream.numChunks - 1;
|
||||||
if (!this.stream.hasChunk(lastChunk)) {
|
if (!stream.hasChunk(lastChunk)) {
|
||||||
nextEmptyChunk = lastChunk;
|
nextEmptyChunk = lastChunk;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
|
nextEmptyChunk = stream.nextEmptyChunk(endChunk);
|
||||||
}
|
}
|
||||||
if (Number.isInteger(nextEmptyChunk)) {
|
if (Number.isInteger(nextEmptyChunk)) {
|
||||||
this._requestChunks([nextEmptyChunk]);
|
this._requestChunks([nextEmptyChunk]);
|
||||||
@ -525,8 +511,8 @@ class ChunkedStreamManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.msgHandler.send("DocProgress", {
|
this.msgHandler.send("DocProgress", {
|
||||||
loaded: this.stream.numChunksLoaded * this.chunkSize,
|
loaded: stream.numChunksLoaded * chunkSize,
|
||||||
total: this.length,
|
total: length,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,7 +530,7 @@ class ChunkedStreamManager {
|
|||||||
|
|
||||||
abort(reason) {
|
abort(reason) {
|
||||||
this.aborted = true;
|
this.aborted = true;
|
||||||
this.pdfNetworkStream?.cancelAllRequests(reason);
|
this.pdfStream?.cancelAllRequests(reason);
|
||||||
|
|
||||||
for (const capability of this._promisesByRequest.values()) {
|
for (const capability of this._promisesByRequest.values()) {
|
||||||
capability.reject(reason);
|
capability.reject(reason);
|
||||||
|
|||||||
@ -110,6 +110,46 @@ class DecodeStream extends BaseStream {
|
|||||||
return this.decodeImage(data, decoderOptions);
|
return this.decodeImage(data, decoderOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async asyncGetBytesFromDecompressionStream(name) {
|
||||||
|
this.stream.reset();
|
||||||
|
const bytes = this.stream.isAsync
|
||||||
|
? await this.stream.asyncGetBytes()
|
||||||
|
: this.stream.getBytes();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { readable, writable } = new DecompressionStream(name);
|
||||||
|
const writer = writable.getWriter();
|
||||||
|
await writer.ready;
|
||||||
|
|
||||||
|
// We can't await writer.write() because it'll block until the reader
|
||||||
|
// starts which happens few lines below.
|
||||||
|
writer
|
||||||
|
.write(bytes)
|
||||||
|
.then(async () => {
|
||||||
|
await writer.ready;
|
||||||
|
await writer.close();
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
const chunks = [];
|
||||||
|
let totalLength = 0;
|
||||||
|
|
||||||
|
for await (const chunk of readable) {
|
||||||
|
chunks.push(chunk);
|
||||||
|
totalLength += chunk.byteLength;
|
||||||
|
}
|
||||||
|
const data = new Uint8Array(totalLength);
|
||||||
|
let offset = 0;
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
data.set(chunk, offset);
|
||||||
|
offset += chunk.byteLength;
|
||||||
|
}
|
||||||
|
return { decompressed: data, compressed: bytes };
|
||||||
|
} catch {
|
||||||
|
return { decompressed: null, compressed: bytes };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.pos = 0;
|
this.pos = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -163,57 +163,26 @@ class FlateStream extends DecodeStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async asyncGetBytes() {
|
async asyncGetBytes() {
|
||||||
this.stream.reset();
|
const { decompressed, compressed } =
|
||||||
const bytes = this.stream.isAsync
|
await this.asyncGetBytesFromDecompressionStream("deflate");
|
||||||
? await this.stream.asyncGetBytes()
|
if (decompressed) {
|
||||||
: this.stream.getBytes();
|
return decompressed;
|
||||||
|
|
||||||
try {
|
|
||||||
const { readable, writable } = new DecompressionStream("deflate");
|
|
||||||
const writer = writable.getWriter();
|
|
||||||
await writer.ready;
|
|
||||||
|
|
||||||
// We can't await writer.write() because it'll block until the reader
|
|
||||||
// starts which happens few lines below.
|
|
||||||
writer
|
|
||||||
.write(bytes)
|
|
||||||
.then(async () => {
|
|
||||||
await writer.ready;
|
|
||||||
await writer.close();
|
|
||||||
})
|
|
||||||
.catch(() => {});
|
|
||||||
|
|
||||||
const chunks = [];
|
|
||||||
let totalLength = 0;
|
|
||||||
|
|
||||||
for await (const chunk of readable) {
|
|
||||||
chunks.push(chunk);
|
|
||||||
totalLength += chunk.byteLength;
|
|
||||||
}
|
|
||||||
const data = new Uint8Array(totalLength);
|
|
||||||
let offset = 0;
|
|
||||||
for (const chunk of chunks) {
|
|
||||||
data.set(chunk, offset);
|
|
||||||
offset += chunk.byteLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
} catch {
|
|
||||||
// DecompressionStream failed (for example because there are some extra
|
|
||||||
// bytes after the end of the compressed data), so we fallback to our
|
|
||||||
// decoder.
|
|
||||||
// We already get the bytes from the underlying stream, so we just reuse
|
|
||||||
// them to avoid get them again.
|
|
||||||
this.#isAsync = false;
|
|
||||||
this.stream = new Stream(
|
|
||||||
bytes,
|
|
||||||
2 /* = header size (see ctor) */,
|
|
||||||
bytes.length,
|
|
||||||
this.stream.dict
|
|
||||||
);
|
|
||||||
this.reset();
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
// DecompressionStream failed (for example because there are some extra
|
||||||
|
// bytes after the end of the compressed data), so we fallback to our
|
||||||
|
// decoder.
|
||||||
|
// We already get the bytes from the underlying stream, so we just reuse
|
||||||
|
// them to avoid get them again.
|
||||||
|
|
||||||
|
this.#isAsync = false;
|
||||||
|
this.stream = new Stream(
|
||||||
|
compressed,
|
||||||
|
2 /* = header size (see ctor) */,
|
||||||
|
compressed.length,
|
||||||
|
this.stream.dict
|
||||||
|
);
|
||||||
|
this.reset();
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAsync() {
|
get isAsync() {
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import {
|
|||||||
import { NullStream, Stream } from "./stream.js";
|
import { NullStream, Stream } from "./stream.js";
|
||||||
import { Ascii85Stream } from "./ascii_85_stream.js";
|
import { Ascii85Stream } from "./ascii_85_stream.js";
|
||||||
import { AsciiHexStream } from "./ascii_hex_stream.js";
|
import { AsciiHexStream } from "./ascii_hex_stream.js";
|
||||||
|
import { BrotliStream } from "./brotli_stream.js";
|
||||||
import { CCITTFaxStream } from "./ccitt_stream.js";
|
import { CCITTFaxStream } from "./ccitt_stream.js";
|
||||||
import { FlateStream } from "./flate_stream.js";
|
import { FlateStream } from "./flate_stream.js";
|
||||||
import { Jbig2Stream } from "./jbig2_stream.js";
|
import { Jbig2Stream } from "./jbig2_stream.js";
|
||||||
@ -822,6 +823,8 @@ class Parser {
|
|||||||
return new RunLengthStream(stream, maybeLength);
|
return new RunLengthStream(stream, maybeLength);
|
||||||
case "JBIG2Decode":
|
case "JBIG2Decode":
|
||||||
return new Jbig2Stream(stream, maybeLength, params);
|
return new Jbig2Stream(stream, maybeLength, params);
|
||||||
|
case "BrotliDecode":
|
||||||
|
return new BrotliStream(stream, maybeLength);
|
||||||
}
|
}
|
||||||
warn(`Filter "${name}" is not supported.`);
|
warn(`Filter "${name}" is not supported.`);
|
||||||
return stream;
|
return stream;
|
||||||
|
|||||||
@ -219,23 +219,23 @@ class WorkerMessageHandler {
|
|||||||
|
|
||||||
return new LocalPdfManager(pdfManagerArgs);
|
return new LocalPdfManager(pdfManagerArgs);
|
||||||
}
|
}
|
||||||
const pdfStream = new PDFWorkerStream(handler),
|
const pdfStream = new PDFWorkerStream({ msgHandler: handler }),
|
||||||
fullRequest = pdfStream.getFullReader();
|
fullReader = pdfStream.getFullReader();
|
||||||
|
|
||||||
const pdfManagerCapability = Promise.withResolvers();
|
const pdfManagerCapability = Promise.withResolvers();
|
||||||
let newPdfManager,
|
let newPdfManager,
|
||||||
cachedChunks = [],
|
cachedChunks = [],
|
||||||
loaded = 0;
|
loaded = 0;
|
||||||
|
|
||||||
fullRequest.headersReady
|
fullReader.headersReady
|
||||||
.then(function () {
|
.then(function () {
|
||||||
if (!fullRequest.isRangeSupported) {
|
if (!fullReader.isRangeSupported) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pdfManagerArgs.source = pdfStream;
|
pdfManagerArgs.source = pdfStream;
|
||||||
pdfManagerArgs.length = fullRequest.contentLength;
|
pdfManagerArgs.length = fullReader.contentLength;
|
||||||
// We don't need auto-fetch when streaming is enabled.
|
// We don't need auto-fetch when streaming is enabled.
|
||||||
pdfManagerArgs.disableAutoFetch ||= fullRequest.isStreamingSupported;
|
pdfManagerArgs.disableAutoFetch ||= fullReader.isStreamingSupported;
|
||||||
|
|
||||||
newPdfManager = new NetworkPdfManager(pdfManagerArgs);
|
newPdfManager = new NetworkPdfManager(pdfManagerArgs);
|
||||||
// There may be a chance that `newPdfManager` is not initialized for
|
// There may be a chance that `newPdfManager` is not initialized for
|
||||||
@ -282,10 +282,10 @@ class WorkerMessageHandler {
|
|||||||
}
|
}
|
||||||
loaded += value.byteLength;
|
loaded += value.byteLength;
|
||||||
|
|
||||||
if (!fullRequest.isStreamingSupported) {
|
if (!fullReader.isStreamingSupported) {
|
||||||
handler.send("DocProgress", {
|
handler.send("DocProgress", {
|
||||||
loaded,
|
loaded,
|
||||||
total: Math.max(loaded, fullRequest.contentLength || 0),
|
total: Math.max(loaded, fullReader.contentLength || 0),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,12 +294,12 @@ class WorkerMessageHandler {
|
|||||||
} else {
|
} else {
|
||||||
cachedChunks.push(value);
|
cachedChunks.push(value);
|
||||||
}
|
}
|
||||||
fullRequest.read().then(readChunk, reject);
|
fullReader.read().then(readChunk, reject);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fullRequest.read().then(readChunk, reject);
|
fullReader.read().then(readChunk, reject);
|
||||||
}).catch(function (e) {
|
}).catch(function (e) {
|
||||||
pdfManagerCapability.reject(e);
|
pdfManagerCapability.reject(e);
|
||||||
cancelXHRs = null;
|
cancelXHRs = null;
|
||||||
|
|||||||
@ -13,77 +13,35 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert } from "../shared/util.js";
|
import {
|
||||||
|
BasePDFStream,
|
||||||
|
BasePDFStreamRangeReader,
|
||||||
|
BasePDFStreamReader,
|
||||||
|
} from "../shared/base_pdf_stream.js";
|
||||||
|
|
||||||
/** @implements {IPDFStream} */
|
class PDFWorkerStream extends BasePDFStream {
|
||||||
class PDFWorkerStream {
|
constructor(source) {
|
||||||
constructor(msgHandler) {
|
super(source, PDFWorkerStreamReader, PDFWorkerStreamRangeReader);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamReader} */
|
class PDFWorkerStreamReader extends BasePDFStreamReader {
|
||||||
class PDFWorkerStreamReader {
|
_reader = null;
|
||||||
constructor(msgHandler) {
|
|
||||||
this._msgHandler = msgHandler;
|
|
||||||
this.onProgress = null;
|
|
||||||
|
|
||||||
this._contentLength = null;
|
constructor(stream) {
|
||||||
this._isRangeSupported = false;
|
super(stream);
|
||||||
this._isStreamingSupported = false;
|
const { msgHandler } = stream._source;
|
||||||
|
|
||||||
const readableStream = this._msgHandler.sendWithStream("GetReader");
|
const readableStream = msgHandler.sendWithStream("GetReader");
|
||||||
this._reader = readableStream.getReader();
|
this._reader = readableStream.getReader();
|
||||||
|
|
||||||
this._headersReady = this._msgHandler
|
msgHandler.sendWithPromise("ReaderHeadersReady").then(data => {
|
||||||
.sendWithPromise("ReaderHeadersReady")
|
this._contentLength = data.contentLength;
|
||||||
.then(data => {
|
this._isStreamingSupported = data.isStreamingSupported;
|
||||||
this._isStreamingSupported = data.isStreamingSupported;
|
this._isRangeSupported = data.isRangeSupported;
|
||||||
this._isRangeSupported = data.isRangeSupported;
|
|
||||||
this._contentLength = data.contentLength;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get headersReady() {
|
this._headersCapability.resolve();
|
||||||
return this._headersReady;
|
}, this._headersCapability.reject);
|
||||||
}
|
|
||||||
|
|
||||||
get contentLength() {
|
|
||||||
return this._contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isStreamingSupported() {
|
|
||||||
return this._isStreamingSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRangeSupported() {
|
|
||||||
return this._isRangeSupported;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async read() {
|
async read() {
|
||||||
@ -101,23 +59,20 @@ class PDFWorkerStreamReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamRangeReader} */
|
class PDFWorkerStreamRangeReader extends BasePDFStreamRangeReader {
|
||||||
class PDFWorkerStreamRangeReader {
|
_reader = null;
|
||||||
constructor(begin, end, msgHandler) {
|
|
||||||
this._msgHandler = msgHandler;
|
|
||||||
this.onProgress = null;
|
|
||||||
|
|
||||||
const readableStream = this._msgHandler.sendWithStream("GetRangeReader", {
|
constructor(stream, begin, end) {
|
||||||
|
super(stream, begin, end);
|
||||||
|
const { msgHandler } = stream._source;
|
||||||
|
|
||||||
|
const readableStream = msgHandler.sendWithStream("GetRangeReader", {
|
||||||
begin,
|
begin,
|
||||||
end,
|
end,
|
||||||
});
|
});
|
||||||
this._reader = readableStream.getReader();
|
this._reader = readableStream.getReader();
|
||||||
}
|
}
|
||||||
|
|
||||||
get isStreamingSupported() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async read() {
|
async read() {
|
||||||
const { value, done } = await this._reader.read();
|
const { value, done } = await this._reader.read();
|
||||||
if (done) {
|
if (done) {
|
||||||
|
|||||||
@ -18,9 +18,6 @@
|
|||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
/** @typedef {import("../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../../web/interfaces").IDownloadManager} IDownloadManager */
|
|
||||||
/** @typedef {import("../../web/interfaces").IPDFLinkService} IPDFLinkService */
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
|
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../../web/struct_tree_layer_builder.js").StructTreeLayerBuilder} StructTreeLayerBuilder */
|
/** @typedef {import("../../web/struct_tree_layer_builder.js").StructTreeLayerBuilder} StructTreeLayerBuilder */
|
||||||
@ -57,8 +54,8 @@ const TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
|
|||||||
* @typedef {Object} AnnotationElementParameters
|
* @typedef {Object} AnnotationElementParameters
|
||||||
* @property {Object} data
|
* @property {Object} data
|
||||||
* @property {HTMLDivElement} layer
|
* @property {HTMLDivElement} layer
|
||||||
* @property {IPDFLinkService} linkService
|
* @property {PDFLinkService} linkService
|
||||||
* @property {IDownloadManager} [downloadManager]
|
* @property {BaseDownloadManager} [downloadManager]
|
||||||
* @property {AnnotationStorage} [annotationStorage]
|
* @property {AnnotationStorage} [annotationStorage]
|
||||||
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
||||||
* for annotation icons. Include trailing slash.
|
* for annotation icons. Include trailing slash.
|
||||||
@ -3736,8 +3733,8 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
|
|||||||
* @property {HTMLDivElement} div
|
* @property {HTMLDivElement} div
|
||||||
* @property {Array} annotations
|
* @property {Array} annotations
|
||||||
* @property {PDFPageProxy} page
|
* @property {PDFPageProxy} page
|
||||||
* @property {IPDFLinkService} linkService
|
* @property {PDFLinkService} linkService
|
||||||
* @property {IDownloadManager} [downloadManager]
|
* @property {BaseDownloadManager} [downloadManager]
|
||||||
* @property {AnnotationStorage} [annotationStorage]
|
* @property {AnnotationStorage} [annotationStorage]
|
||||||
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
||||||
* for annotation icons. Include trailing slash.
|
* for annotation icons. Include trailing slash.
|
||||||
@ -4018,8 +4015,6 @@ class AnnotationLayer {
|
|||||||
* Add link annotations to the annotation layer.
|
* Add link annotations to the annotation layer.
|
||||||
*
|
*
|
||||||
* @param {Array<Object>} annotations
|
* @param {Array<Object>} annotations
|
||||||
* @param {IPDFLinkService} linkService
|
|
||||||
* @memberof AnnotationLayer
|
|
||||||
*/
|
*/
|
||||||
async addLinkAnnotations(annotations) {
|
async addLinkAnnotations(annotations) {
|
||||||
const elementParams = {
|
const elementParams = {
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import {
|
|||||||
getVerbosityLevel,
|
getVerbosityLevel,
|
||||||
info,
|
info,
|
||||||
isNodeJS,
|
isNodeJS,
|
||||||
|
MathClamp,
|
||||||
RenderingIntentFlag,
|
RenderingIntentFlag,
|
||||||
setVerbosityLevel,
|
setVerbosityLevel,
|
||||||
shadow,
|
shadow,
|
||||||
@ -440,6 +441,7 @@ function getDocument(src = {}) {
|
|||||||
ownerDocument,
|
ownerDocument,
|
||||||
pdfBug,
|
pdfBug,
|
||||||
styleElement,
|
styleElement,
|
||||||
|
enableHWA,
|
||||||
loadingParams: {
|
loadingParams: {
|
||||||
disableAutoFetch,
|
disableAutoFetch,
|
||||||
enableXfa,
|
enableXfa,
|
||||||
@ -463,7 +465,8 @@ function getDocument(src = {}) {
|
|||||||
|
|
||||||
let networkStream;
|
let networkStream;
|
||||||
if (rangeTransport) {
|
if (rangeTransport) {
|
||||||
networkStream = new PDFDataTransportStream(rangeTransport, {
|
networkStream = new PDFDataTransportStream({
|
||||||
|
pdfDataRangeTransport: rangeTransport,
|
||||||
disableRange,
|
disableRange,
|
||||||
disableStream,
|
disableStream,
|
||||||
});
|
});
|
||||||
@ -508,8 +511,7 @@ function getDocument(src = {}) {
|
|||||||
task,
|
task,
|
||||||
networkStream,
|
networkStream,
|
||||||
transportParams,
|
transportParams,
|
||||||
transportFactory,
|
transportFactory
|
||||||
enableHWA
|
|
||||||
);
|
);
|
||||||
task._transport = transport;
|
task._transport = transport;
|
||||||
messageHandler.send("Ready", null);
|
messageHandler.send("Ready", null);
|
||||||
@ -524,6 +526,8 @@ function getDocument(src = {}) {
|
|||||||
* @typedef {Object} OnProgressParameters
|
* @typedef {Object} OnProgressParameters
|
||||||
* @property {number} loaded - Currently loaded number of bytes.
|
* @property {number} loaded - Currently loaded number of bytes.
|
||||||
* @property {number} total - Total number of bytes in the PDF file.
|
* @property {number} total - Total number of bytes in the PDF file.
|
||||||
|
* @property {number} percent - Currently loaded percentage, as an integer value
|
||||||
|
* in the [0, 100] range. If `total` is undefined, the percentage is `NaN`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2391,8 +2395,14 @@ class PDFWorker {
|
|||||||
* @ignore
|
* @ignore
|
||||||
*/
|
*/
|
||||||
class WorkerTransport {
|
class WorkerTransport {
|
||||||
|
downloadInfoCapability = Promise.withResolvers();
|
||||||
|
|
||||||
|
#fullReader = null;
|
||||||
|
|
||||||
#methodPromises = new Map();
|
#methodPromises = new Map();
|
||||||
|
|
||||||
|
#networkStream = null;
|
||||||
|
|
||||||
#pageCache = new Map();
|
#pageCache = new Map();
|
||||||
|
|
||||||
#pagePromises = new Map();
|
#pagePromises = new Map();
|
||||||
@ -2403,21 +2413,17 @@ class WorkerTransport {
|
|||||||
|
|
||||||
#pagesMapper = PagesMapper.instance;
|
#pagesMapper = PagesMapper.instance;
|
||||||
|
|
||||||
constructor(
|
constructor(messageHandler, loadingTask, networkStream, params, factory) {
|
||||||
messageHandler,
|
|
||||||
loadingTask,
|
|
||||||
networkStream,
|
|
||||||
params,
|
|
||||||
factory,
|
|
||||||
enableHWA
|
|
||||||
) {
|
|
||||||
this.messageHandler = messageHandler;
|
this.messageHandler = messageHandler;
|
||||||
this.loadingTask = loadingTask;
|
this.loadingTask = loadingTask;
|
||||||
|
this.#networkStream = networkStream;
|
||||||
|
|
||||||
this.commonObjs = new PDFObjects();
|
this.commonObjs = new PDFObjects();
|
||||||
this.fontLoader = new FontLoader({
|
this.fontLoader = new FontLoader({
|
||||||
ownerDocument: params.ownerDocument,
|
ownerDocument: params.ownerDocument,
|
||||||
styleElement: params.styleElement,
|
styleElement: params.styleElement,
|
||||||
});
|
});
|
||||||
|
this.enableHWA = params.enableHWA;
|
||||||
this.loadingParams = params.loadingParams;
|
this.loadingParams = params.loadingParams;
|
||||||
this._params = params;
|
this._params = params;
|
||||||
|
|
||||||
@ -2430,12 +2436,6 @@ class WorkerTransport {
|
|||||||
this.destroyed = false;
|
this.destroyed = false;
|
||||||
this.destroyCapability = null;
|
this.destroyCapability = null;
|
||||||
|
|
||||||
this._networkStream = networkStream;
|
|
||||||
this._fullReader = null;
|
|
||||||
this._lastProgress = null;
|
|
||||||
this.downloadInfoCapability = Promise.withResolvers();
|
|
||||||
this.enableHWA = enableHWA;
|
|
||||||
|
|
||||||
this.setupMessageHandler();
|
this.setupMessageHandler();
|
||||||
|
|
||||||
this.#pagesMapper.addListener(this.#updateCaches.bind(this));
|
this.#pagesMapper.addListener(this.#updateCaches.bind(this));
|
||||||
@ -2493,6 +2493,14 @@ class WorkerTransport {
|
|||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#onProgress({ loaded, total }) {
|
||||||
|
this.loadingTask.onProgress?.({
|
||||||
|
loaded,
|
||||||
|
total,
|
||||||
|
percent: MathClamp(Math.round((loaded / total) * 100), 0, 100),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
get annotationStorage() {
|
get annotationStorage() {
|
||||||
return shadow(this, "annotationStorage", new AnnotationStorage());
|
return shadow(this, "annotationStorage", new AnnotationStorage());
|
||||||
}
|
}
|
||||||
@ -2604,7 +2612,7 @@ class WorkerTransport {
|
|||||||
this.filterFactory.destroy();
|
this.filterFactory.destroy();
|
||||||
TextLayer.cleanup();
|
TextLayer.cleanup();
|
||||||
|
|
||||||
this._networkStream?.cancelAllRequests(
|
this.#networkStream?.cancelAllRequests(
|
||||||
new AbortException("Worker was terminated.")
|
new AbortException("Worker was terminated.")
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2621,18 +2629,16 @@ class WorkerTransport {
|
|||||||
|
|
||||||
messageHandler.on("GetReader", (data, sink) => {
|
messageHandler.on("GetReader", (data, sink) => {
|
||||||
assert(
|
assert(
|
||||||
this._networkStream,
|
this.#networkStream,
|
||||||
"GetReader - no `IPDFStream` instance available."
|
"GetReader - no `BasePDFStream` instance available."
|
||||||
);
|
);
|
||||||
this._fullReader = this._networkStream.getFullReader();
|
this.#fullReader = this.#networkStream.getFullReader();
|
||||||
this._fullReader.onProgress = evt => {
|
// If stream or range turn out to be disabled, once `headersReady` is
|
||||||
this._lastProgress = {
|
// resolved, this is our only way to report loading progress.
|
||||||
loaded: evt.loaded,
|
this.#fullReader.onProgress = evt => this.#onProgress(evt);
|
||||||
total: evt.total,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
sink.onPull = () => {
|
sink.onPull = () => {
|
||||||
this._fullReader
|
this.#fullReader
|
||||||
.read()
|
.read()
|
||||||
.then(function ({ value, done }) {
|
.then(function ({ value, done }) {
|
||||||
if (done) {
|
if (done) {
|
||||||
@ -2653,7 +2659,7 @@ class WorkerTransport {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sink.onCancel = reason => {
|
sink.onCancel = reason => {
|
||||||
this._fullReader.cancel(reason);
|
this.#fullReader.cancel(reason);
|
||||||
|
|
||||||
sink.ready.catch(readyReason => {
|
sink.ready.catch(readyReason => {
|
||||||
if (this.destroyed) {
|
if (this.destroyed) {
|
||||||
@ -2665,40 +2671,29 @@ class WorkerTransport {
|
|||||||
});
|
});
|
||||||
|
|
||||||
messageHandler.on("ReaderHeadersReady", async data => {
|
messageHandler.on("ReaderHeadersReady", async data => {
|
||||||
await this._fullReader.headersReady;
|
await this.#fullReader.headersReady;
|
||||||
|
|
||||||
const { isStreamingSupported, isRangeSupported, contentLength } =
|
const { isStreamingSupported, isRangeSupported, contentLength } =
|
||||||
this._fullReader;
|
this.#fullReader;
|
||||||
|
|
||||||
// If stream or range are disabled, it's our only way to report
|
if (isStreamingSupported && isRangeSupported) {
|
||||||
// loading progress.
|
this.#fullReader.onProgress = null; // See comment in "GetReader" above.
|
||||||
if (!isStreamingSupported || !isRangeSupported) {
|
|
||||||
if (this._lastProgress) {
|
|
||||||
loadingTask.onProgress?.(this._lastProgress);
|
|
||||||
}
|
|
||||||
this._fullReader.onProgress = evt => {
|
|
||||||
loadingTask.onProgress?.({
|
|
||||||
loaded: evt.loaded,
|
|
||||||
total: evt.total,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isStreamingSupported, isRangeSupported, contentLength };
|
return { isStreamingSupported, isRangeSupported, contentLength };
|
||||||
});
|
});
|
||||||
|
|
||||||
messageHandler.on("GetRangeReader", (data, sink) => {
|
messageHandler.on("GetRangeReader", (data, sink) => {
|
||||||
assert(
|
assert(
|
||||||
this._networkStream,
|
this.#networkStream,
|
||||||
"GetRangeReader - no `IPDFStream` instance available."
|
"GetRangeReader - no `BasePDFStream` instance available."
|
||||||
);
|
);
|
||||||
const rangeReader = this._networkStream.getRangeReader(
|
const rangeReader = this.#networkStream.getRangeReader(
|
||||||
data.begin,
|
data.begin,
|
||||||
data.end
|
data.end
|
||||||
);
|
);
|
||||||
|
|
||||||
// When streaming is enabled, it's possible that the data requested here
|
// 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
|
// However, given that the PDF data is loaded asynchronously on the
|
||||||
// main-thread and then sent via `postMessage` to the worker-thread,
|
// main-thread and then sent via `postMessage` to the worker-thread,
|
||||||
// it may not have been available during parsing (hence the attempt to
|
// it may not have been available during parsing (hence the attempt to
|
||||||
@ -2706,7 +2701,7 @@ class WorkerTransport {
|
|||||||
//
|
//
|
||||||
// To avoid wasting time and resources here, we'll thus *not* dispatch
|
// 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
|
// 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) {
|
if (!rangeReader) {
|
||||||
sink.close();
|
sink.close();
|
||||||
return;
|
return;
|
||||||
@ -2780,10 +2775,7 @@ class WorkerTransport {
|
|||||||
messageHandler.on("DataLoaded", data => {
|
messageHandler.on("DataLoaded", data => {
|
||||||
// For consistency: Ensure that progress is always reported when the
|
// For consistency: Ensure that progress is always reported when the
|
||||||
// entire PDF file has been loaded, regardless of how it was fetched.
|
// entire PDF file has been loaded, regardless of how it was fetched.
|
||||||
loadingTask.onProgress?.({
|
this.#onProgress({ loaded: data.length, total: data.length });
|
||||||
loaded: data.length,
|
|
||||||
total: data.length,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.downloadInfoCapability.resolve(data);
|
this.downloadInfoCapability.resolve(data);
|
||||||
});
|
});
|
||||||
@ -2906,10 +2898,7 @@ class WorkerTransport {
|
|||||||
if (this.destroyed) {
|
if (this.destroyed) {
|
||||||
return; // Ignore any pending requests if the worker was terminated.
|
return; // Ignore any pending requests if the worker was terminated.
|
||||||
}
|
}
|
||||||
loadingTask.onProgress?.({
|
this.#onProgress(data);
|
||||||
loaded: data.loaded,
|
|
||||||
total: data.total,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
messageHandler.on("FetchBinaryData", async data => {
|
messageHandler.on("FetchBinaryData", async data => {
|
||||||
@ -2950,7 +2939,7 @@ class WorkerTransport {
|
|||||||
isPureXfa: !!this._htmlForXfa,
|
isPureXfa: !!this._htmlForXfa,
|
||||||
numPages: this._numPages,
|
numPages: this._numPages,
|
||||||
annotationStorage: map,
|
annotationStorage: map,
|
||||||
filename: this._fullReader?.filename ?? null,
|
filename: this.#fullReader?.filename ?? null,
|
||||||
},
|
},
|
||||||
transfer
|
transfer
|
||||||
)
|
)
|
||||||
@ -3118,8 +3107,8 @@ class WorkerTransport {
|
|||||||
.then(results => ({
|
.then(results => ({
|
||||||
info: results[0],
|
info: results[0],
|
||||||
metadata: results[1] ? new Metadata(results[1]) : null,
|
metadata: results[1] ? new Metadata(results[1]) : null,
|
||||||
contentDispositionFilename: this._fullReader?.filename ?? null,
|
contentDispositionFilename: this.#fullReader?.filename ?? null,
|
||||||
contentLength: this._fullReader?.contentLength ?? null,
|
contentLength: this.#fullReader?.contentLength ?? null,
|
||||||
hasStructTree: results[2],
|
hasStructTree: results[2],
|
||||||
}));
|
}));
|
||||||
this.#methodPromises.set(name, promise);
|
this.#methodPromises.set(name, promise);
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
/** @typedef {import("../display_utils.js").PageViewport} PageViewport */
|
/** @typedef {import("../display_utils.js").PageViewport} PageViewport */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
/** @typedef {import("../../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
||||||
/** @typedef {import("../../../web/interfaces").IL10n} IL10n */
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../annotation_layer.js").AnnotationLayer} AnnotationLayer */
|
/** @typedef {import("../annotation_layer.js").AnnotationLayer} AnnotationLayer */
|
||||||
/** @typedef {import("../draw_layer.js").DrawLayer} DrawLayer */
|
/** @typedef {import("../draw_layer.js").DrawLayer} DrawLayer */
|
||||||
@ -47,7 +46,7 @@ import { StampEditor } from "./stamp.js";
|
|||||||
* @property {boolean} enabled
|
* @property {boolean} enabled
|
||||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||||
* @property {number} pageIndex
|
* @property {number} pageIndex
|
||||||
* @property {IL10n} l10n
|
* @property {L10n} l10n
|
||||||
* @property {AnnotationLayer} [annotationLayer]
|
* @property {AnnotationLayer} [annotationLayer]
|
||||||
* @property {HTMLDivElement} [textLayer]
|
* @property {HTMLDivElement} [textLayer]
|
||||||
* @property {DrawLayer} drawLayer
|
* @property {DrawLayer} drawLayer
|
||||||
|
|||||||
@ -13,14 +13,19 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AbortException, assert, warn } from "../shared/util.js";
|
import { AbortException, warn } from "../shared/util.js";
|
||||||
|
import {
|
||||||
|
BasePDFStream,
|
||||||
|
BasePDFStreamRangeReader,
|
||||||
|
BasePDFStreamReader,
|
||||||
|
} from "../shared/base_pdf_stream.js";
|
||||||
import {
|
import {
|
||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseError,
|
createResponseError,
|
||||||
|
ensureResponseOrigin,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
getResponseOrigin,
|
getResponseOrigin,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
validateResponseStatus,
|
|
||||||
} from "./network_utils.js";
|
} from "./network_utils.js";
|
||||||
|
|
||||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||||
@ -29,15 +34,21 @@ if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFetchOptions(headers, withCredentials, abortController) {
|
function fetchUrl(url, headers, withCredentials, abortController) {
|
||||||
return {
|
return fetch(url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers,
|
headers,
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
mode: "cors",
|
mode: "cors",
|
||||||
credentials: withCredentials ? "include" : "same-origin",
|
credentials: withCredentials ? "include" : "same-origin",
|
||||||
redirect: "follow",
|
redirect: "follow",
|
||||||
};
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureResponseStatus(status, url) {
|
||||||
|
if (status !== 200 && status !== 206) {
|
||||||
|
throw createResponseError(status, url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArrayBuffer(val) {
|
function getArrayBuffer(val) {
|
||||||
@ -51,86 +62,44 @@ function getArrayBuffer(val) {
|
|||||||
return new Uint8Array(val).buffer;
|
return new Uint8Array(val).buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStream} */
|
class PDFFetchStream extends BasePDFStream {
|
||||||
class PDFFetchStream {
|
|
||||||
_responseOrigin = null;
|
_responseOrigin = null;
|
||||||
|
|
||||||
constructor(source) {
|
constructor(source) {
|
||||||
this.source = source;
|
super(source, PDFFetchStreamReader, PDFFetchStreamRangeReader);
|
||||||
this.isHttp = /^https?:/i.test(source.url);
|
this.isHttp = /^https?:/i.test(source.url);
|
||||||
this.headers = createHeaders(this.isHttp, source.httpHeaders);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamReader} */
|
class PDFFetchStreamReader extends BasePDFStreamReader {
|
||||||
class PDFFetchStreamReader {
|
_abortController = new AbortController();
|
||||||
constructor(stream) {
|
|
||||||
this._stream = stream;
|
|
||||||
this._reader = null;
|
|
||||||
this._loaded = 0;
|
|
||||||
this._filename = null;
|
|
||||||
const source = stream.source;
|
|
||||||
this._withCredentials = source.withCredentials || false;
|
|
||||||
this._contentLength = source.length;
|
|
||||||
this._headersCapability = Promise.withResolvers();
|
|
||||||
this._disableRange = source.disableRange || false;
|
|
||||||
this._rangeChunkSize = source.rangeChunkSize;
|
|
||||||
if (!this._rangeChunkSize && !this._disableRange) {
|
|
||||||
this._disableRange = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._abortController = new AbortController();
|
_reader = null;
|
||||||
this._isStreamingSupported = !source.disableStream;
|
|
||||||
this._isRangeSupported = !source.disableRange;
|
constructor(stream) {
|
||||||
|
super(stream);
|
||||||
|
const {
|
||||||
|
disableRange,
|
||||||
|
disableStream,
|
||||||
|
length,
|
||||||
|
rangeChunkSize,
|
||||||
|
url,
|
||||||
|
withCredentials,
|
||||||
|
} = stream._source;
|
||||||
|
|
||||||
|
this._contentLength = length;
|
||||||
|
this._isStreamingSupported = !disableStream;
|
||||||
|
this._isRangeSupported = !disableRange;
|
||||||
// Always create a copy of the headers.
|
// Always create a copy of the headers.
|
||||||
const headers = new Headers(stream.headers);
|
const headers = new Headers(stream.headers);
|
||||||
|
|
||||||
const url = source.url;
|
fetchUrl(url, headers, withCredentials, this._abortController)
|
||||||
fetch(
|
|
||||||
url,
|
|
||||||
createFetchOptions(headers, this._withCredentials, this._abortController)
|
|
||||||
)
|
|
||||||
.then(response => {
|
.then(response => {
|
||||||
stream._responseOrigin = getResponseOrigin(response.url);
|
stream._responseOrigin = getResponseOrigin(response.url);
|
||||||
|
|
||||||
if (!validateResponseStatus(response.status)) {
|
ensureResponseStatus(response.status, url);
|
||||||
throw createResponseError(response.status, url);
|
|
||||||
}
|
|
||||||
this._reader = response.body.getReader();
|
this._reader = response.body.getReader();
|
||||||
this._headersCapability.resolve();
|
|
||||||
|
|
||||||
const responseHeaders = response.headers;
|
const responseHeaders = response.headers;
|
||||||
|
|
||||||
@ -138,8 +107,8 @@ class PDFFetchStreamReader {
|
|||||||
validateRangeRequestCapabilities({
|
validateRangeRequestCapabilities({
|
||||||
responseHeaders,
|
responseHeaders,
|
||||||
isHttp: stream.isHttp,
|
isHttp: stream.isHttp,
|
||||||
rangeChunkSize: this._rangeChunkSize,
|
rangeChunkSize,
|
||||||
disableRange: this._disableRange,
|
disableRange,
|
||||||
});
|
});
|
||||||
|
|
||||||
this._isRangeSupported = allowRangeRequests;
|
this._isRangeSupported = allowRangeRequests;
|
||||||
@ -153,30 +122,10 @@ class PDFFetchStreamReader {
|
|||||||
if (!this._isStreamingSupported && this._isRangeSupported) {
|
if (!this._isStreamingSupported && this._isRangeSupported) {
|
||||||
this.cancel(new AbortException("Streaming is disabled."));
|
this.cancel(new AbortException("Streaming is disabled."));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._headersCapability.resolve();
|
||||||
})
|
})
|
||||||
.catch(this._headersCapability.reject);
|
.catch(this._headersCapability.reject);
|
||||||
|
|
||||||
this.onProgress = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get headersReady() {
|
|
||||||
return this._headersCapability.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
get filename() {
|
|
||||||
return this._filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
get contentLength() {
|
|
||||||
return this._contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRangeSupported() {
|
|
||||||
return this._isRangeSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isStreamingSupported() {
|
|
||||||
return this._isStreamingSupported;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async read() {
|
async read() {
|
||||||
@ -200,48 +149,32 @@ class PDFFetchStreamReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamRangeReader} */
|
class PDFFetchStreamRangeReader extends BasePDFStreamRangeReader {
|
||||||
class PDFFetchStreamRangeReader {
|
_abortController = new AbortController();
|
||||||
constructor(stream, begin, end) {
|
|
||||||
this._stream = stream;
|
_readCapability = Promise.withResolvers();
|
||||||
this._reader = null;
|
|
||||||
this._loaded = 0;
|
_reader = null;
|
||||||
const source = stream.source;
|
|
||||||
this._withCredentials = source.withCredentials || false;
|
constructor(stream, begin, end) {
|
||||||
this._readCapability = Promise.withResolvers();
|
super(stream, begin, end);
|
||||||
this._isStreamingSupported = !source.disableStream;
|
const { url, withCredentials } = stream._source;
|
||||||
|
|
||||||
this._abortController = new AbortController();
|
|
||||||
// Always create a copy of the headers.
|
// Always create a copy of the headers.
|
||||||
const headers = new Headers(stream.headers);
|
const headers = new Headers(stream.headers);
|
||||||
headers.append("Range", `bytes=${begin}-${end - 1}`);
|
headers.append("Range", `bytes=${begin}-${end - 1}`);
|
||||||
|
|
||||||
const url = source.url;
|
fetchUrl(url, headers, withCredentials, this._abortController)
|
||||||
fetch(
|
|
||||||
url,
|
|
||||||
createFetchOptions(headers, this._withCredentials, this._abortController)
|
|
||||||
)
|
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const responseOrigin = getResponseOrigin(response.url);
|
const responseOrigin = getResponseOrigin(response.url);
|
||||||
|
|
||||||
if (responseOrigin !== stream._responseOrigin) {
|
ensureResponseOrigin(responseOrigin, stream._responseOrigin);
|
||||||
throw new Error(
|
ensureResponseStatus(response.status, url);
|
||||||
`Expected range response-origin "${responseOrigin}" to match "${stream._responseOrigin}".`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!validateResponseStatus(response.status)) {
|
|
||||||
throw createResponseError(response.status, url);
|
|
||||||
}
|
|
||||||
this._readCapability.resolve();
|
|
||||||
this._reader = response.body.getReader();
|
this._reader = response.body.getReader();
|
||||||
|
|
||||||
|
this._readCapability.resolve();
|
||||||
})
|
})
|
||||||
.catch(this._readCapability.reject);
|
.catch(this._readCapability.reject);
|
||||||
|
|
||||||
this.onProgress = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isStreamingSupported() {
|
|
||||||
return this._isStreamingSupported;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async read() {
|
async read() {
|
||||||
@ -250,9 +183,6 @@ class PDFFetchStreamRangeReader {
|
|||||||
if (done) {
|
if (done) {
|
||||||
return { value, done };
|
return { value, done };
|
||||||
}
|
}
|
||||||
this._loaded += value.byteLength;
|
|
||||||
this.onProgress?.({ loaded: this._loaded });
|
|
||||||
|
|
||||||
return { value: getArrayBuffer(value), done: false };
|
return { value: getArrayBuffer(value), done: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,9 +14,15 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { assert, stringToBytes, warn } from "../shared/util.js";
|
import { assert, stringToBytes, warn } from "../shared/util.js";
|
||||||
|
import {
|
||||||
|
BasePDFStream,
|
||||||
|
BasePDFStreamRangeReader,
|
||||||
|
BasePDFStreamReader,
|
||||||
|
} from "../shared/base_pdf_stream.js";
|
||||||
import {
|
import {
|
||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseError,
|
createResponseError,
|
||||||
|
ensureResponseOrigin,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
getResponseOrigin,
|
getResponseOrigin,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
@ -35,18 +41,13 @@ function getArrayBuffer(val) {
|
|||||||
return typeof val !== "string" ? val : stringToBytes(val).buffer;
|
return typeof val !== "string" ? val : stringToBytes(val).buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStream} */
|
class PDFNetworkStream extends BasePDFStream {
|
||||||
class PDFNetworkStream {
|
|
||||||
#pendingRequests = new WeakMap();
|
#pendingRequests = new WeakMap();
|
||||||
|
|
||||||
_fullRequestReader = null;
|
|
||||||
|
|
||||||
_rangeRequestReaders = [];
|
|
||||||
|
|
||||||
_responseOrigin = null;
|
_responseOrigin = null;
|
||||||
|
|
||||||
constructor(source) {
|
constructor(source) {
|
||||||
this._source = source;
|
super(source, PDFNetworkStreamReader, PDFNetworkStreamRangeReader);
|
||||||
this.url = source.url;
|
this.url = source.url;
|
||||||
this.isHttp = /^https?:/i.test(this.url);
|
this.isHttp = /^https?:/i.test(this.url);
|
||||||
this.headers = createHeaders(this.isHttp, source.httpHeaders);
|
this.headers = createHeaders(this.isHttp, source.httpHeaders);
|
||||||
@ -160,70 +161,44 @@ class PDFNetworkStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFullReader() {
|
|
||||||
assert(
|
|
||||||
!this._fullRequestReader,
|
|
||||||
"PDFNetworkStream.getFullReader can only be called once."
|
|
||||||
);
|
|
||||||
this._fullRequestReader = new PDFNetworkStreamFullRequestReader(this);
|
|
||||||
return this._fullRequestReader;
|
|
||||||
}
|
|
||||||
|
|
||||||
getRangeReader(begin, end) {
|
getRangeReader(begin, end) {
|
||||||
const reader = new PDFNetworkStreamRangeRequestReader(this, begin, end);
|
const reader = super.getRangeReader(begin, end);
|
||||||
reader.onClosed = () => {
|
|
||||||
const i = this._rangeRequestReaders.indexOf(reader);
|
|
||||||
if (i >= 0) {
|
|
||||||
this._rangeRequestReaders.splice(i, 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
this._rangeRequestReaders.push(reader);
|
|
||||||
return reader;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelAllRequests(reason) {
|
if (reader) {
|
||||||
this._fullRequestReader?.cancel(reason);
|
reader.onClosed = () => this._rangeReaders.delete(reader);
|
||||||
|
|
||||||
for (const reader of this._rangeRequestReaders.slice(0)) {
|
|
||||||
reader.cancel(reason);
|
|
||||||
}
|
}
|
||||||
|
return reader;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamReader} */
|
class PDFNetworkStreamReader extends BasePDFStreamReader {
|
||||||
class PDFNetworkStreamFullRequestReader {
|
_cachedChunks = [];
|
||||||
|
|
||||||
|
_done = false;
|
||||||
|
|
||||||
|
_requests = [];
|
||||||
|
|
||||||
|
_storedError = null;
|
||||||
|
|
||||||
constructor(stream) {
|
constructor(stream) {
|
||||||
this._stream = stream;
|
super(stream);
|
||||||
const { disableRange, length, rangeChunkSize } = stream._source;
|
const { length } = stream._source;
|
||||||
|
|
||||||
|
this._contentLength = length;
|
||||||
|
// Note that `XMLHttpRequest` doesn't support streaming, and range requests
|
||||||
|
// will be enabled (if supported) in `this.#onHeadersReceived` below.
|
||||||
|
|
||||||
this._fullRequestXhr = stream._request({
|
this._fullRequestXhr = stream._request({
|
||||||
onHeadersReceived: this._onHeadersReceived.bind(this),
|
onHeadersReceived: this.#onHeadersReceived.bind(this),
|
||||||
onDone: this._onDone.bind(this),
|
onDone: this.#onDone.bind(this),
|
||||||
onError: this._onError.bind(this),
|
onError: this.#onError.bind(this),
|
||||||
onProgress: this._onProgress.bind(this),
|
onProgress: this.#onProgress.bind(this),
|
||||||
});
|
});
|
||||||
this._headersCapability = Promise.withResolvers();
|
|
||||||
this._disableRange = disableRange || false;
|
|
||||||
this._contentLength = length; // Optional
|
|
||||||
this._rangeChunkSize = rangeChunkSize;
|
|
||||||
if (!this._rangeChunkSize && !this._disableRange) {
|
|
||||||
this._disableRange = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._isStreamingSupported = false;
|
|
||||||
this._isRangeSupported = false;
|
|
||||||
|
|
||||||
this._cachedChunks = [];
|
|
||||||
this._requests = [];
|
|
||||||
this._done = false;
|
|
||||||
this._storedError = undefined;
|
|
||||||
this._filename = null;
|
|
||||||
|
|
||||||
this.onProgress = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onHeadersReceived() {
|
#onHeadersReceived() {
|
||||||
const stream = this._stream;
|
const stream = this._stream;
|
||||||
|
const { disableRange, rangeChunkSize } = stream._source;
|
||||||
const fullRequestXhr = this._fullRequestXhr;
|
const fullRequestXhr = this._fullRequestXhr;
|
||||||
|
|
||||||
stream._responseOrigin = getResponseOrigin(fullRequestXhr.responseURL);
|
stream._responseOrigin = getResponseOrigin(fullRequestXhr.responseURL);
|
||||||
@ -246,8 +221,8 @@ class PDFNetworkStreamFullRequestReader {
|
|||||||
validateRangeRequestCapabilities({
|
validateRangeRequestCapabilities({
|
||||||
responseHeaders,
|
responseHeaders,
|
||||||
isHttp: stream.isHttp,
|
isHttp: stream.isHttp,
|
||||||
rangeChunkSize: this._rangeChunkSize,
|
rangeChunkSize,
|
||||||
disableRange: this._disableRange,
|
disableRange,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (allowRangeRequests) {
|
if (allowRangeRequests) {
|
||||||
@ -269,10 +244,10 @@ class PDFNetworkStreamFullRequestReader {
|
|||||||
this._headersCapability.resolve();
|
this._headersCapability.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDone(chunk) {
|
#onDone(chunk) {
|
||||||
if (this._requests.length > 0) {
|
if (this._requests.length > 0) {
|
||||||
const requestCapability = this._requests.shift();
|
const capability = this._requests.shift();
|
||||||
requestCapability.resolve({ value: chunk, done: false });
|
capability.resolve({ value: chunk, done: false });
|
||||||
} else {
|
} else {
|
||||||
this._cachedChunks.push(chunk);
|
this._cachedChunks.push(chunk);
|
||||||
}
|
}
|
||||||
@ -280,49 +255,29 @@ class PDFNetworkStreamFullRequestReader {
|
|||||||
if (this._cachedChunks.length > 0) {
|
if (this._cachedChunks.length > 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.resolve({ value: undefined, done: true });
|
capability.resolve({ value: undefined, done: true });
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onError(status) {
|
#onError(status) {
|
||||||
this._storedError = createResponseError(status, this._stream.url);
|
this._storedError = createResponseError(status, this._stream.url);
|
||||||
this._headersCapability.reject(this._storedError);
|
this._headersCapability.reject(this._storedError);
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.reject(this._storedError);
|
capability.reject(this._storedError);
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
this._cachedChunks.length = 0;
|
this._cachedChunks.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onProgress(evt) {
|
#onProgress(evt) {
|
||||||
this.onProgress?.({
|
this.onProgress?.({
|
||||||
loaded: evt.loaded,
|
loaded: evt.loaded,
|
||||||
total: evt.lengthComputable ? evt.total : this._contentLength,
|
total: evt.lengthComputable ? evt.total : this._contentLength,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get filename() {
|
|
||||||
return this._filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRangeSupported() {
|
|
||||||
return this._isRangeSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isStreamingSupported() {
|
|
||||||
return this._isStreamingSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
get contentLength() {
|
|
||||||
return this._contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
get headersReady() {
|
|
||||||
return this._headersCapability.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
async read() {
|
async read() {
|
||||||
await this._headersCapability.promise;
|
await this._headersCapability.promise;
|
||||||
|
|
||||||
@ -336,16 +291,16 @@ class PDFNetworkStreamFullRequestReader {
|
|||||||
if (this._done) {
|
if (this._done) {
|
||||||
return { value: undefined, done: true };
|
return { value: undefined, done: true };
|
||||||
}
|
}
|
||||||
const requestCapability = Promise.withResolvers();
|
const capability = Promise.withResolvers();
|
||||||
this._requests.push(requestCapability);
|
this._requests.push(capability);
|
||||||
return requestCapability.promise;
|
return capability.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(reason) {
|
cancel(reason) {
|
||||||
this._done = true;
|
this._done = true;
|
||||||
this._headersCapability.reject(reason);
|
this._headersCapability.reject(reason);
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.resolve({ value: undefined, done: true });
|
capability.resolve({ value: undefined, done: true });
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
|
|
||||||
@ -354,74 +309,64 @@ class PDFNetworkStreamFullRequestReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamRangeReader} */
|
class PDFNetworkStreamRangeReader extends BasePDFStreamRangeReader {
|
||||||
class PDFNetworkStreamRangeRequestReader {
|
|
||||||
onClosed = null;
|
onClosed = null;
|
||||||
|
|
||||||
|
_done = false;
|
||||||
|
|
||||||
|
_queuedChunk = null;
|
||||||
|
|
||||||
|
_requests = [];
|
||||||
|
|
||||||
|
_storedError = null;
|
||||||
|
|
||||||
constructor(stream, begin, end) {
|
constructor(stream, begin, end) {
|
||||||
this._stream = stream;
|
super(stream, begin, end);
|
||||||
|
|
||||||
this._requestXhr = stream._request({
|
this._requestXhr = stream._request({
|
||||||
begin,
|
begin,
|
||||||
end,
|
end,
|
||||||
onHeadersReceived: this._onHeadersReceived.bind(this),
|
onHeadersReceived: this.#onHeadersReceived.bind(this),
|
||||||
onDone: this._onDone.bind(this),
|
onDone: this.#onDone.bind(this),
|
||||||
onError: this._onError.bind(this),
|
onError: this.#onError.bind(this),
|
||||||
onProgress: this._onProgress.bind(this),
|
onProgress: null,
|
||||||
});
|
});
|
||||||
this._requests = [];
|
|
||||||
this._queuedChunk = null;
|
|
||||||
this._done = false;
|
|
||||||
this._storedError = undefined;
|
|
||||||
|
|
||||||
this.onProgress = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onHeadersReceived() {
|
#onHeadersReceived() {
|
||||||
const responseOrigin = getResponseOrigin(this._requestXhr?.responseURL);
|
const responseOrigin = getResponseOrigin(this._requestXhr?.responseURL);
|
||||||
|
try {
|
||||||
if (responseOrigin !== this._stream._responseOrigin) {
|
ensureResponseOrigin(responseOrigin, this._stream._responseOrigin);
|
||||||
this._storedError = new Error(
|
} catch (ex) {
|
||||||
`Expected range response-origin "${responseOrigin}" to match "${this._stream._responseOrigin}".`
|
this._storedError = ex;
|
||||||
);
|
this.#onError(0);
|
||||||
this._onError(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDone(chunk) {
|
#onDone(chunk) {
|
||||||
if (this._requests.length > 0) {
|
if (this._requests.length > 0) {
|
||||||
const requestCapability = this._requests.shift();
|
const capability = this._requests.shift();
|
||||||
requestCapability.resolve({ value: chunk, done: false });
|
capability.resolve({ value: chunk, done: false });
|
||||||
} else {
|
} else {
|
||||||
this._queuedChunk = chunk;
|
this._queuedChunk = chunk;
|
||||||
}
|
}
|
||||||
this._done = true;
|
this._done = true;
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.resolve({ value: undefined, done: true });
|
capability.resolve({ value: undefined, done: true });
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
this.onClosed?.(this);
|
this.onClosed?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onError(status) {
|
#onError(status) {
|
||||||
this._storedError ??= createResponseError(status, this._stream.url);
|
this._storedError ??= createResponseError(status, this._stream.url);
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.reject(this._storedError);
|
capability.reject(this._storedError);
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
this._queuedChunk = null;
|
this._queuedChunk = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_onProgress(evt) {
|
|
||||||
if (!this.isStreamingSupported) {
|
|
||||||
this.onProgress?.({ loaded: evt.loaded });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get isStreamingSupported() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async read() {
|
async read() {
|
||||||
if (this._storedError) {
|
if (this._storedError) {
|
||||||
throw this._storedError;
|
throw this._storedError;
|
||||||
@ -434,20 +379,20 @@ class PDFNetworkStreamRangeRequestReader {
|
|||||||
if (this._done) {
|
if (this._done) {
|
||||||
return { value: undefined, done: true };
|
return { value: undefined, done: true };
|
||||||
}
|
}
|
||||||
const requestCapability = Promise.withResolvers();
|
const capability = Promise.withResolvers();
|
||||||
this._requests.push(requestCapability);
|
this._requests.push(capability);
|
||||||
return requestCapability.promise;
|
return capability.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(reason) {
|
cancel(reason) {
|
||||||
this._done = true;
|
this._done = true;
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.resolve({ value: undefined, done: true });
|
capability.resolve({ value: undefined, done: true });
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
|
|
||||||
this._stream._abortRequest(this._requestXhr);
|
this._stream._abortRequest(this._requestXhr);
|
||||||
this.onClosed?.(this);
|
this.onClosed?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -107,15 +107,19 @@ function createResponseError(status, url) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateResponseStatus(status) {
|
function ensureResponseOrigin(rangeOrigin, origin) {
|
||||||
return status === 200 || status === 206;
|
if (rangeOrigin !== origin) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected range response-origin "${rangeOrigin}" to match "${origin}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseError,
|
createResponseError,
|
||||||
|
ensureResponseOrigin,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
getResponseOrigin,
|
getResponseOrigin,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
validateResponseStatus,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,6 +15,11 @@
|
|||||||
/* globals process */
|
/* globals process */
|
||||||
|
|
||||||
import { AbortException, assert, warn } from "../shared/util.js";
|
import { AbortException, assert, warn } from "../shared/util.js";
|
||||||
|
import {
|
||||||
|
BasePDFStream,
|
||||||
|
BasePDFStreamRangeReader,
|
||||||
|
BasePDFStreamReader,
|
||||||
|
} from "../shared/base_pdf_stream.js";
|
||||||
import { createResponseError } from "./network_utils.js";
|
import { createResponseError } from "./network_utils.js";
|
||||||
|
|
||||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||||
@ -60,70 +65,28 @@ function getArrayBuffer(val) {
|
|||||||
return new Uint8Array(val).buffer;
|
return new Uint8Array(val).buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PDFNodeStream {
|
class PDFNodeStream extends BasePDFStream {
|
||||||
constructor(source) {
|
constructor(source) {
|
||||||
this.source = source;
|
super(source, PDFNodeStreamReader, PDFNodeStreamRangeReader);
|
||||||
this.url = parseUrlOrPath(source.url);
|
this.url = parseUrlOrPath(source.url);
|
||||||
assert(
|
assert(
|
||||||
this.url.protocol === "file:",
|
this.url.protocol === "file:",
|
||||||
"PDFNodeStream only supports file:// URLs."
|
"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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PDFNodeStreamFsFullReader {
|
class PDFNodeStreamReader extends BasePDFStreamReader {
|
||||||
_headersCapability = Promise.withResolvers();
|
|
||||||
|
|
||||||
_reader = null;
|
_reader = null;
|
||||||
|
|
||||||
constructor(stream) {
|
constructor(stream) {
|
||||||
this.onProgress = null;
|
super(stream);
|
||||||
const source = stream.source;
|
const { disableRange, disableStream, length, rangeChunkSize } =
|
||||||
this._contentLength = source.length; // optional
|
stream._source;
|
||||||
this._loaded = 0;
|
|
||||||
this._filename = null;
|
|
||||||
|
|
||||||
this._disableRange = source.disableRange || false;
|
this._contentLength = length;
|
||||||
this._rangeChunkSize = source.rangeChunkSize;
|
this._isStreamingSupported = !disableStream;
|
||||||
if (!this._rangeChunkSize && !this._disableRange) {
|
this._isRangeSupported = !disableRange;
|
||||||
this._disableRange = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._isStreamingSupported = !source.disableStream;
|
|
||||||
this._isRangeSupported = !source.disableRange;
|
|
||||||
|
|
||||||
const url = stream.url;
|
const url = stream.url;
|
||||||
const fs = process.getBuiltinModule("fs");
|
const fs = process.getBuiltinModule("fs");
|
||||||
@ -136,7 +99,7 @@ class PDFNodeStreamFsFullReader {
|
|||||||
this._reader = readableStream.getReader();
|
this._reader = readableStream.getReader();
|
||||||
|
|
||||||
const { size } = stat;
|
const { size } = stat;
|
||||||
if (size <= 2 * this._rangeChunkSize) {
|
if (size <= 2 * rangeChunkSize) {
|
||||||
// The file size is smaller than the size of two chunks, so it doesn't
|
// The file size is smaller than the size of two chunks, so it doesn't
|
||||||
// make any sense to abort the request and retry with a range request.
|
// make any sense to abort the request and retry with a range request.
|
||||||
this._isRangeSupported = false;
|
this._isRangeSupported = false;
|
||||||
@ -160,26 +123,6 @@ class PDFNodeStreamFsFullReader {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get headersReady() {
|
|
||||||
return this._headersCapability.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
get filename() {
|
|
||||||
return this._filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
get contentLength() {
|
|
||||||
return this._contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRangeSupported() {
|
|
||||||
return this._isRangeSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isStreamingSupported() {
|
|
||||||
return this._isStreamingSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
async read() {
|
async read() {
|
||||||
await this._headersCapability.promise;
|
await this._headersCapability.promise;
|
||||||
const { value, done } = await this._reader.read();
|
const { value, done } = await this._reader.read();
|
||||||
@ -200,16 +143,13 @@ class PDFNodeStreamFsFullReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PDFNodeStreamFsRangeReader {
|
class PDFNodeStreamRangeReader extends BasePDFStreamRangeReader {
|
||||||
_readCapability = Promise.withResolvers();
|
_readCapability = Promise.withResolvers();
|
||||||
|
|
||||||
_reader = null;
|
_reader = null;
|
||||||
|
|
||||||
constructor(stream, begin, end) {
|
constructor(stream, begin, end) {
|
||||||
this.onProgress = null;
|
super(stream, begin, end);
|
||||||
this._loaded = 0;
|
|
||||||
const source = stream.source;
|
|
||||||
this._isStreamingSupported = !source.disableStream;
|
|
||||||
|
|
||||||
const url = stream.url;
|
const url = stream.url;
|
||||||
const fs = process.getBuiltinModule("fs");
|
const fs = process.getBuiltinModule("fs");
|
||||||
@ -228,19 +168,12 @@ class PDFNodeStreamFsRangeReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isStreamingSupported() {
|
|
||||||
return this._isStreamingSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
async read() {
|
async read() {
|
||||||
await this._readCapability.promise;
|
await this._readCapability.promise;
|
||||||
const { value, done } = await this._reader.read();
|
const { value, done } = await this._reader.read();
|
||||||
if (done) {
|
if (done) {
|
||||||
return { value, done };
|
return { value, done };
|
||||||
}
|
}
|
||||||
this._loaded += value.length;
|
|
||||||
this.onProgress?.({ loaded: this._loaded });
|
|
||||||
|
|
||||||
return { value: getArrayBuffer(value), done: false };
|
return { value: getArrayBuffer(value), done: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,185 +13,137 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("../interfaces").IPDFStream} IPDFStream */
|
import {
|
||||||
/** @typedef {import("../interfaces").IPDFStreamReader} IPDFStreamReader */
|
BasePDFStream,
|
||||||
// eslint-disable-next-line max-len
|
BasePDFStreamRangeReader,
|
||||||
/** @typedef {import("../interfaces").IPDFStreamRangeReader} IPDFStreamRangeReader */
|
BasePDFStreamReader,
|
||||||
|
} from "../shared/base_pdf_stream.js";
|
||||||
import { assert } from "../shared/util.js";
|
import { assert } from "../shared/util.js";
|
||||||
import { isPdfFile } from "./display_utils.js";
|
import { isPdfFile } from "./display_utils.js";
|
||||||
|
|
||||||
/** @implements {IPDFStream} */
|
function getArrayBuffer(val) {
|
||||||
class PDFDataTransportStream {
|
// Prevent any possible issues by only transferring a Uint8Array that
|
||||||
constructor(
|
// completely "utilizes" its underlying ArrayBuffer.
|
||||||
pdfDataRangeTransport,
|
return val instanceof Uint8Array && val.byteLength === val.buffer.byteLength
|
||||||
{ disableRange = false, disableStream = false }
|
? val.buffer
|
||||||
) {
|
: new Uint8Array(val).buffer;
|
||||||
assert(
|
}
|
||||||
pdfDataRangeTransport,
|
|
||||||
'PDFDataTransportStream - missing required "pdfDataRangeTransport" argument.'
|
|
||||||
);
|
|
||||||
const { length, initialData, progressiveDone, contentDispositionFilename } =
|
|
||||||
pdfDataRangeTransport;
|
|
||||||
|
|
||||||
this._queuedChunks = [];
|
class PDFDataTransportStream extends BasePDFStream {
|
||||||
this._progressiveDone = progressiveDone;
|
_progressiveDone = false;
|
||||||
this._contentDispositionFilename = contentDispositionFilename;
|
|
||||||
|
_queuedChunks = [];
|
||||||
|
|
||||||
|
constructor(source) {
|
||||||
|
super(
|
||||||
|
source,
|
||||||
|
PDFDataTransportStreamReader,
|
||||||
|
PDFDataTransportStreamRangeReader
|
||||||
|
);
|
||||||
|
const { pdfDataRangeTransport } = source;
|
||||||
|
const { initialData, progressiveDone } = pdfDataRangeTransport;
|
||||||
|
|
||||||
if (initialData?.length > 0) {
|
if (initialData?.length > 0) {
|
||||||
// Prevent any possible issues by only transferring a Uint8Array that
|
const buffer = getArrayBuffer(initialData);
|
||||||
// completely "utilizes" its underlying ArrayBuffer.
|
|
||||||
const buffer =
|
|
||||||
initialData instanceof Uint8Array &&
|
|
||||||
initialData.byteLength === initialData.buffer.byteLength
|
|
||||||
? initialData.buffer
|
|
||||||
: new Uint8Array(initialData).buffer;
|
|
||||||
this._queuedChunks.push(buffer);
|
this._queuedChunks.push(buffer);
|
||||||
}
|
}
|
||||||
|
this._progressiveDone = progressiveDone;
|
||||||
this._pdfDataRangeTransport = pdfDataRangeTransport;
|
|
||||||
this._isStreamingSupported = !disableStream;
|
|
||||||
this._isRangeSupported = !disableRange;
|
|
||||||
this._contentLength = length;
|
|
||||||
|
|
||||||
this._fullRequestReader = null;
|
|
||||||
this._rangeReaders = [];
|
|
||||||
|
|
||||||
pdfDataRangeTransport.addRangeListener((begin, chunk) => {
|
pdfDataRangeTransport.addRangeListener((begin, chunk) => {
|
||||||
this._onReceiveData({ begin, chunk });
|
this.#onReceiveData(begin, chunk);
|
||||||
});
|
});
|
||||||
|
|
||||||
pdfDataRangeTransport.addProgressListener((loaded, total) => {
|
pdfDataRangeTransport.addProgressListener((loaded, total) => {
|
||||||
this._onProgress({ loaded, total });
|
if (total !== undefined) {
|
||||||
|
this._fullReader?.onProgress?.({ loaded, total });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pdfDataRangeTransport.addProgressiveReadListener(chunk => {
|
pdfDataRangeTransport.addProgressiveReadListener(chunk => {
|
||||||
this._onReceiveData({ chunk });
|
this.#onReceiveData(/* begin = */ undefined, chunk);
|
||||||
});
|
});
|
||||||
|
|
||||||
pdfDataRangeTransport.addProgressiveDoneListener(() => {
|
pdfDataRangeTransport.addProgressiveDoneListener(() => {
|
||||||
this._onProgressiveDone();
|
this._fullReader?.progressiveDone();
|
||||||
|
this._progressiveDone = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
pdfDataRangeTransport.transportReady();
|
pdfDataRangeTransport.transportReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onReceiveData({ begin, chunk }) {
|
#onReceiveData(begin, chunk) {
|
||||||
// Prevent any possible issues by only transferring a Uint8Array that
|
const buffer = getArrayBuffer(chunk);
|
||||||
// completely "utilizes" its underlying ArrayBuffer.
|
|
||||||
const buffer =
|
|
||||||
chunk instanceof Uint8Array &&
|
|
||||||
chunk.byteLength === chunk.buffer.byteLength
|
|
||||||
? chunk.buffer
|
|
||||||
: new Uint8Array(chunk).buffer;
|
|
||||||
|
|
||||||
if (begin === undefined) {
|
if (begin === undefined) {
|
||||||
if (this._fullRequestReader) {
|
if (this._fullReader) {
|
||||||
this._fullRequestReader._enqueue(buffer);
|
this._fullReader._enqueue(buffer);
|
||||||
} else {
|
} else {
|
||||||
this._queuedChunks.push(buffer);
|
this._queuedChunks.push(buffer);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const found = this._rangeReaders.some(function (rangeReader) {
|
const rangeReader = this._rangeReaders
|
||||||
if (rangeReader._begin !== begin) {
|
.keys()
|
||||||
return false;
|
.find(r => r._begin === begin);
|
||||||
}
|
|
||||||
rangeReader._enqueue(buffer);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
assert(
|
assert(
|
||||||
found,
|
rangeReader,
|
||||||
"_onReceiveData - no `PDFDataTransportStreamRangeReader` instance found."
|
"#onReceiveData - no `PDFDataTransportStreamRangeReader` instance found."
|
||||||
);
|
);
|
||||||
}
|
rangeReader._enqueue(buffer);
|
||||||
}
|
|
||||||
|
|
||||||
get _progressiveDataLength() {
|
|
||||||
return this._fullRequestReader?._loaded ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onProgress(evt) {
|
|
||||||
if (evt.total === undefined) {
|
|
||||||
// Reporting to first range reader, if it exists.
|
|
||||||
this._rangeReaders[0]?.onProgress?.({ loaded: evt.loaded });
|
|
||||||
} else {
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFullReader() {
|
getFullReader() {
|
||||||
assert(
|
const reader = super.getFullReader();
|
||||||
!this._fullRequestReader,
|
|
||||||
"PDFDataTransportStream.getFullReader can only be called once."
|
|
||||||
);
|
|
||||||
const queuedChunks = this._queuedChunks;
|
|
||||||
this._queuedChunks = null;
|
this._queuedChunks = null;
|
||||||
return new PDFDataTransportStreamReader(
|
return reader;
|
||||||
this,
|
|
||||||
queuedChunks,
|
|
||||||
this._progressiveDone,
|
|
||||||
this._contentDispositionFilename
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getRangeReader(begin, end) {
|
getRangeReader(begin, end) {
|
||||||
if (end <= this._progressiveDataLength) {
|
const reader = super.getRangeReader(begin, end);
|
||||||
return null;
|
|
||||||
|
if (reader) {
|
||||||
|
reader.onDone = () => this._rangeReaders.delete(reader);
|
||||||
|
|
||||||
|
this._source.pdfDataRangeTransport.requestDataRange(begin, end);
|
||||||
}
|
}
|
||||||
const reader = new PDFDataTransportStreamRangeReader(this, begin, end);
|
|
||||||
this._pdfDataRangeTransport.requestDataRange(begin, end);
|
|
||||||
this._rangeReaders.push(reader);
|
|
||||||
return reader;
|
return reader;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancelAllRequests(reason) {
|
cancelAllRequests(reason) {
|
||||||
this._fullRequestReader?.cancel(reason);
|
super.cancelAllRequests(reason);
|
||||||
|
|
||||||
for (const reader of this._rangeReaders.slice(0)) {
|
this._source.pdfDataRangeTransport.abort();
|
||||||
reader.cancel(reason);
|
|
||||||
}
|
|
||||||
this._pdfDataRangeTransport.abort();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamReader} */
|
class PDFDataTransportStreamReader extends BasePDFStreamReader {
|
||||||
class PDFDataTransportStreamReader {
|
_done = false;
|
||||||
constructor(
|
|
||||||
stream,
|
_queuedChunks = null;
|
||||||
queuedChunks,
|
|
||||||
progressiveDone = false,
|
_requests = [];
|
||||||
contentDispositionFilename = null
|
|
||||||
) {
|
constructor(stream) {
|
||||||
this._stream = stream;
|
super(stream);
|
||||||
this._done = progressiveDone || false;
|
const { pdfDataRangeTransport, disableRange, disableStream } =
|
||||||
this._filename = isPdfFile(contentDispositionFilename)
|
stream._source;
|
||||||
? contentDispositionFilename
|
const { length, contentDispositionFilename } = pdfDataRangeTransport;
|
||||||
: null;
|
|
||||||
this._queuedChunks = queuedChunks || [];
|
this._queuedChunks = stream._queuedChunks || [];
|
||||||
this._loaded = 0;
|
|
||||||
for (const chunk of this._queuedChunks) {
|
for (const chunk of this._queuedChunks) {
|
||||||
this._loaded += chunk.byteLength;
|
this._loaded += chunk.byteLength;
|
||||||
}
|
}
|
||||||
this._requests = [];
|
this._done = stream._progressiveDone;
|
||||||
this._headersReady = Promise.resolve();
|
|
||||||
stream._fullRequestReader = this;
|
|
||||||
|
|
||||||
this.onProgress = null;
|
this._contentLength = length;
|
||||||
|
this._isStreamingSupported = !disableStream;
|
||||||
|
this._isRangeSupported = !disableRange;
|
||||||
|
|
||||||
|
if (isPdfFile(contentDispositionFilename)) {
|
||||||
|
this._filename = contentDispositionFilename;
|
||||||
|
}
|
||||||
|
this._headersCapability.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
_enqueue(chunk) {
|
_enqueue(chunk) {
|
||||||
@ -199,34 +151,14 @@ class PDFDataTransportStreamReader {
|
|||||||
return; // Ignore new data.
|
return; // Ignore new data.
|
||||||
}
|
}
|
||||||
if (this._requests.length > 0) {
|
if (this._requests.length > 0) {
|
||||||
const requestCapability = this._requests.shift();
|
const capability = this._requests.shift();
|
||||||
requestCapability.resolve({ value: chunk, done: false });
|
capability.resolve({ value: chunk, done: false });
|
||||||
} else {
|
} else {
|
||||||
this._queuedChunks.push(chunk);
|
this._queuedChunks.push(chunk);
|
||||||
}
|
}
|
||||||
this._loaded += chunk.byteLength;
|
this._loaded += chunk.byteLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
get headersReady() {
|
|
||||||
return this._headersReady;
|
|
||||||
}
|
|
||||||
|
|
||||||
get filename() {
|
|
||||||
return this._filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isRangeSupported() {
|
|
||||||
return this._stream._isRangeSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isStreamingSupported() {
|
|
||||||
return this._stream._isStreamingSupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
get contentLength() {
|
|
||||||
return this._stream._contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
async read() {
|
async read() {
|
||||||
if (this._queuedChunks.length > 0) {
|
if (this._queuedChunks.length > 0) {
|
||||||
const chunk = this._queuedChunks.shift();
|
const chunk = this._queuedChunks.shift();
|
||||||
@ -235,38 +167,38 @@ class PDFDataTransportStreamReader {
|
|||||||
if (this._done) {
|
if (this._done) {
|
||||||
return { value: undefined, done: true };
|
return { value: undefined, done: true };
|
||||||
}
|
}
|
||||||
const requestCapability = Promise.withResolvers();
|
const capability = Promise.withResolvers();
|
||||||
this._requests.push(requestCapability);
|
this._requests.push(capability);
|
||||||
return requestCapability.promise;
|
return capability.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(reason) {
|
cancel(reason) {
|
||||||
this._done = true;
|
this._done = true;
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.resolve({ value: undefined, done: true });
|
capability.resolve({ value: undefined, done: true });
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
progressiveDone() {
|
progressiveDone() {
|
||||||
if (this._done) {
|
this._done ||= true;
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._done = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamRangeReader} */
|
class PDFDataTransportStreamRangeReader extends BasePDFStreamRangeReader {
|
||||||
class PDFDataTransportStreamRangeReader {
|
onDone = null;
|
||||||
constructor(stream, begin, end) {
|
|
||||||
this._stream = stream;
|
|
||||||
this._begin = begin;
|
|
||||||
this._end = end;
|
|
||||||
this._queuedChunk = null;
|
|
||||||
this._requests = [];
|
|
||||||
this._done = false;
|
|
||||||
|
|
||||||
this.onProgress = null;
|
_begin = -1;
|
||||||
|
|
||||||
|
_done = false;
|
||||||
|
|
||||||
|
_queuedChunk = null;
|
||||||
|
|
||||||
|
_requests = [];
|
||||||
|
|
||||||
|
constructor(stream, begin, end) {
|
||||||
|
super(stream, begin, end);
|
||||||
|
this._begin = begin;
|
||||||
}
|
}
|
||||||
|
|
||||||
_enqueue(chunk) {
|
_enqueue(chunk) {
|
||||||
@ -276,19 +208,16 @@ class PDFDataTransportStreamRangeReader {
|
|||||||
if (this._requests.length === 0) {
|
if (this._requests.length === 0) {
|
||||||
this._queuedChunk = chunk;
|
this._queuedChunk = chunk;
|
||||||
} else {
|
} else {
|
||||||
const requestsCapability = this._requests.shift();
|
const firstCapability = this._requests.shift();
|
||||||
requestsCapability.resolve({ value: chunk, done: false });
|
firstCapability.resolve({ value: chunk, done: false });
|
||||||
for (const requestCapability of this._requests) {
|
|
||||||
requestCapability.resolve({ value: undefined, done: true });
|
for (const capability of this._requests) {
|
||||||
|
capability.resolve({ value: undefined, done: true });
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
}
|
}
|
||||||
this._done = true;
|
this._done = true;
|
||||||
this._stream._removeRangeReader(this);
|
this.onDone?.();
|
||||||
}
|
|
||||||
|
|
||||||
get isStreamingSupported() {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async read() {
|
async read() {
|
||||||
@ -300,18 +229,18 @@ class PDFDataTransportStreamRangeReader {
|
|||||||
if (this._done) {
|
if (this._done) {
|
||||||
return { value: undefined, done: true };
|
return { value: undefined, done: true };
|
||||||
}
|
}
|
||||||
const requestCapability = Promise.withResolvers();
|
const capability = Promise.withResolvers();
|
||||||
this._requests.push(requestCapability);
|
this._requests.push(capability);
|
||||||
return requestCapability.promise;
|
return capability.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(reason) {
|
cancel(reason) {
|
||||||
this._done = true;
|
this._done = true;
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.resolve({ value: undefined, done: true });
|
capability.resolve({ value: undefined, done: true });
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
this._stream._removeRangeReader(this);
|
this.onDone?.();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,6 @@
|
|||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./annotation_storage").AnnotationStorage} AnnotationStorage */
|
/** @typedef {import("./annotation_storage").AnnotationStorage} AnnotationStorage */
|
||||||
/** @typedef {import("./display_utils").PageViewport} PageViewport */
|
/** @typedef {import("./display_utils").PageViewport} PageViewport */
|
||||||
/** @typedef {import("../../web/interfaces").IPDFLinkService} IPDFLinkService */
|
|
||||||
|
|
||||||
import { XfaText } from "./xfa_text.js";
|
import { XfaText } from "./xfa_text.js";
|
||||||
|
|
||||||
@ -26,7 +25,7 @@ import { XfaText } from "./xfa_text.js";
|
|||||||
* @property {HTMLDivElement} div
|
* @property {HTMLDivElement} div
|
||||||
* @property {Object} xfaHtml
|
* @property {Object} xfaHtml
|
||||||
* @property {AnnotationStorage} [annotationStorage]
|
* @property {AnnotationStorage} [annotationStorage]
|
||||||
* @property {IPDFLinkService} linkService
|
* @property {PDFLinkService} linkService
|
||||||
* @property {string} [intent] - (default value is 'display').
|
* @property {string} [intent] - (default value is 'display').
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@ -13,56 +13,119 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { assert, unreachable } from "./util.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface that represents PDF data transport. If possible, it allows
|
* Interface that represents PDF data transport. If possible, it allows
|
||||||
* progressively load entire or fragment of the PDF binary data.
|
* 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.
|
* Gets a reader for the entire PDF data.
|
||||||
* @returns {IPDFStreamReader}
|
* @returns {BasePDFStreamReader}
|
||||||
*/
|
*/
|
||||||
getFullReader() {
|
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.
|
* Gets a reader for the range of the PDF data.
|
||||||
*
|
*
|
||||||
* NOTE: Currently this method is only expected to be invoked *after*
|
* 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} begin - the start offset of the data.
|
||||||
* @param {number} end - the end offset of the data.
|
* @param {number} end - the end offset of the data.
|
||||||
* @returns {IPDFStreamRangeReader}
|
* @returns {BasePDFStreamRangeReader}
|
||||||
*/
|
*/
|
||||||
getRangeReader(begin, end) {
|
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.
|
* Cancels all opened reader and closes all their opened requests.
|
||||||
* @param {Object} reason - the reason for cancelling
|
* @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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for a PDF binary data reader.
|
* Interface for a PDF binary data reader.
|
||||||
*
|
|
||||||
* @interface
|
|
||||||
*/
|
*/
|
||||||
class IPDFStreamReader {
|
class BasePDFStreamReader {
|
||||||
constructor() {
|
/**
|
||||||
/**
|
* Sets or gets the progress callback. The callback can be useful when the
|
||||||
* Sets or gets the progress callback. The callback can be useful when the
|
* isStreamingSupported property of the object is defined as false.
|
||||||
* isStreamingSupported property of the object is defined as false.
|
* The callback is called with one parameter: an object with the loaded and
|
||||||
* The callback is called with one parameter: an object with the loaded and
|
* total properties.
|
||||||
* total properties.
|
*/
|
||||||
*/
|
onProgress = null;
|
||||||
this.onProgress = null;
|
|
||||||
|
_contentLength = 0;
|
||||||
|
|
||||||
|
_filename = null;
|
||||||
|
|
||||||
|
_headersCapability = Promise.withResolvers();
|
||||||
|
|
||||||
|
_isRangeSupported = false;
|
||||||
|
|
||||||
|
_isStreamingSupported = false;
|
||||||
|
|
||||||
|
_loaded = 0;
|
||||||
|
|
||||||
|
_stream = null;
|
||||||
|
|
||||||
|
constructor(stream) {
|
||||||
|
if (
|
||||||
|
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||||
|
this.constructor === BasePDFStreamReader
|
||||||
|
) {
|
||||||
|
unreachable("Cannot initialize BasePDFStreamReader.");
|
||||||
|
}
|
||||||
|
this._stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,7 +134,7 @@ class IPDFStreamReader {
|
|||||||
* @type {Promise}
|
* @type {Promise}
|
||||||
*/
|
*/
|
||||||
get headersReady() {
|
get headersReady() {
|
||||||
return Promise.resolve();
|
return this._headersCapability.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -81,7 +144,7 @@ class IPDFStreamReader {
|
|||||||
* header is missing/invalid.
|
* header is missing/invalid.
|
||||||
*/
|
*/
|
||||||
get filename() {
|
get filename() {
|
||||||
return null;
|
return this._filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -90,7 +153,7 @@ class IPDFStreamReader {
|
|||||||
* @type {number} The data length (or 0 if unknown).
|
* @type {number} The data length (or 0 if unknown).
|
||||||
*/
|
*/
|
||||||
get contentLength() {
|
get contentLength() {
|
||||||
return 0;
|
return this._contentLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,7 +163,7 @@ class IPDFStreamReader {
|
|||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
get isRangeSupported() {
|
get isRangeSupported() {
|
||||||
return false;
|
return this._isRangeSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,7 +172,7 @@ class IPDFStreamReader {
|
|||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
get isStreamingSupported() {
|
get isStreamingSupported() {
|
||||||
return false;
|
return this._isStreamingSupported;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -120,37 +183,33 @@ class IPDFStreamReader {
|
|||||||
* set to true.
|
* set to true.
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async read() {}
|
async read() {
|
||||||
|
unreachable("Abstract method `read` called");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels all pending read requests and closes the stream.
|
* Cancels all pending read requests and closes the stream.
|
||||||
* @param {Object} reason
|
* @param {Object} reason
|
||||||
*/
|
*/
|
||||||
cancel(reason) {}
|
cancel(reason) {
|
||||||
|
unreachable("Abstract method `cancel` called");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for a PDF binary data fragment reader.
|
* Interface for a PDF binary data fragment reader.
|
||||||
*
|
|
||||||
* @interface
|
|
||||||
*/
|
*/
|
||||||
class IPDFStreamRangeReader {
|
class BasePDFStreamRangeReader {
|
||||||
constructor() {
|
_stream = null;
|
||||||
/**
|
|
||||||
* Sets or gets the progress callback. The callback can be useful when the
|
|
||||||
* isStreamingSupported property of the object is defined as false.
|
|
||||||
* The callback is called with one parameter: an object with the loaded
|
|
||||||
* property.
|
|
||||||
*/
|
|
||||||
this.onProgress = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
constructor(stream, begin, end) {
|
||||||
* Gets ability of the stream to progressively load binary data.
|
if (
|
||||||
* @type {boolean}
|
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||||
*/
|
this.constructor === BasePDFStreamRangeReader
|
||||||
get isStreamingSupported() {
|
) {
|
||||||
return false;
|
unreachable("Cannot initialize BasePDFStreamRangeReader.");
|
||||||
|
}
|
||||||
|
this._stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -161,13 +220,17 @@ class IPDFStreamRangeReader {
|
|||||||
* set to true.
|
* set to true.
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async read() {}
|
async read() {
|
||||||
|
unreachable("Abstract method `read` called");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels all pending read requests and closes the stream.
|
* Cancels all pending read requests and closes the stream.
|
||||||
* @param {Object} reason
|
* @param {Object} reason
|
||||||
*/
|
*/
|
||||||
cancel(reason) {}
|
cancel(reason) {
|
||||||
|
unreachable("Abstract method `cancel` called");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { IPDFStream, IPDFStreamRangeReader, IPDFStreamReader };
|
export { BasePDFStream, BasePDFStreamRangeReader, BasePDFStreamReader };
|
||||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -870,3 +870,4 @@
|
|||||||
!bug2009627.pdf
|
!bug2009627.pdf
|
||||||
!page_with_number.pdf
|
!page_with_number.pdf
|
||||||
!page_with_number_and_link.pdf
|
!page_with_number_and_link.pdf
|
||||||
|
!Brotli-Prototype-FileA.pdf
|
||||||
|
|||||||
BIN
test/pdfs/Brotli-Prototype-FileA.pdf
Normal file
BIN
test/pdfs/Brotli-Prototype-FileA.pdf
Normal file
Binary file not shown.
@ -13929,5 +13929,12 @@
|
|||||||
"md5": "e515a9abb11ab74332e57e371bfae61e",
|
"md5": "e515a9abb11ab74332e57e371bfae61e",
|
||||||
"rounds": 1,
|
"rounds": 1,
|
||||||
"type": "eq"
|
"type": "eq"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Brotli-Prototype-FileA",
|
||||||
|
"file": "pdfs/Brotli-Prototype-FileA.pdf",
|
||||||
|
"md5": "9113370932798776ba91c807ce95082e",
|
||||||
|
"rounds": 1,
|
||||||
|
"type": "eq"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -160,14 +160,18 @@ describe("api", function () {
|
|||||||
progressReportedCapability.resolve(progressData);
|
progressReportedCapability.resolve(progressData);
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = await Promise.all([
|
const [pdfDoc, progress] = await Promise.all([
|
||||||
progressReportedCapability.promise,
|
|
||||||
loadingTask.promise,
|
loadingTask.promise,
|
||||||
|
progressReportedCapability.promise,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(data[0].loaded / data[0].total >= 0).toEqual(true);
|
expect(pdfDoc instanceof PDFDocumentProxy).toEqual(true);
|
||||||
expect(data[1] instanceof PDFDocumentProxy).toEqual(true);
|
expect(pdfDoc.loadingTask).toBe(loadingTask);
|
||||||
expect(loadingTask).toEqual(data[1].loadingTask);
|
|
||||||
|
expect(progress.loaded).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(progress.total).toEqual(basicApiFileLength);
|
||||||
|
expect(progress.percent).toBeGreaterThanOrEqual(0);
|
||||||
|
expect(progress.percent).toBeLessThanOrEqual(100);
|
||||||
|
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
@ -218,12 +222,17 @@ describe("api", function () {
|
|||||||
progressReportedCapability.resolve(data);
|
progressReportedCapability.resolve(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = await Promise.all([
|
const [pdfDoc, progress] = await Promise.all([
|
||||||
loadingTask.promise,
|
loadingTask.promise,
|
||||||
progressReportedCapability.promise,
|
progressReportedCapability.promise,
|
||||||
]);
|
]);
|
||||||
expect(data[0] instanceof PDFDocumentProxy).toEqual(true);
|
|
||||||
expect(data[1].loaded / data[1].total).toEqual(1);
|
expect(pdfDoc instanceof PDFDocumentProxy).toEqual(true);
|
||||||
|
expect(pdfDoc.loadingTask).toBe(loadingTask);
|
||||||
|
|
||||||
|
expect(progress.loaded).toEqual(basicApiFileLength);
|
||||||
|
expect(progress.total).toEqual(basicApiFileLength);
|
||||||
|
expect(progress.percent).toEqual(100);
|
||||||
|
|
||||||
// Check that the TypedArray was transferred.
|
// Check that the TypedArray was transferred.
|
||||||
expect(typedArrayPdf.length).toEqual(0);
|
expect(typedArrayPdf.length).toEqual(0);
|
||||||
|
|||||||
@ -209,4 +209,14 @@ describe("autolinker", function () {
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should correctly find emails with hyphens in domain (bug 20557)", function () {
|
||||||
|
testLinks([
|
||||||
|
[
|
||||||
|
"john.doe@faculity.uni-cityname.tld",
|
||||||
|
"mailto:john.doe@faculity.uni-cityname.tld",
|
||||||
|
],
|
||||||
|
["john.doe@uni-cityname.tld", "mailto:john.doe@uni-cityname.tld"],
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -16,8 +16,7 @@
|
|||||||
import { AbortException, isNodeJS } from "../../src/shared/util.js";
|
import { AbortException, isNodeJS } from "../../src/shared/util.js";
|
||||||
import { getCrossOriginHostname, TestPdfsServer } from "./test_utils.js";
|
import { getCrossOriginHostname, TestPdfsServer } from "./test_utils.js";
|
||||||
|
|
||||||
// Common tests to verify behavior across implementations of the IPDFStream
|
// Common tests to verify behavior across `BasePDFStream` implementations:
|
||||||
// interface:
|
|
||||||
// - PDFNetworkStream by network_spec.js
|
// - PDFNetworkStream by network_spec.js
|
||||||
// - PDFFetchStream by fetch_stream_spec.js
|
// - PDFFetchStream by fetch_stream_spec.js
|
||||||
async function testCrossOriginRedirects({
|
async function testCrossOriginRedirects({
|
||||||
|
|||||||
@ -35,6 +35,7 @@ describe("fetch_stream", function () {
|
|||||||
it("read with streaming", async function () {
|
it("read with streaming", async function () {
|
||||||
const stream = new PDFFetchStream({
|
const stream = new PDFFetchStream({
|
||||||
url: getPdfUrl(),
|
url: getPdfUrl(),
|
||||||
|
rangeChunkSize: 32768,
|
||||||
disableStream: false,
|
disableStream: false,
|
||||||
disableRange: true,
|
disableRange: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import {
|
|||||||
createResponseError,
|
createResponseError,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
validateResponseStatus,
|
|
||||||
} from "../../src/display/network_utils.js";
|
} from "../../src/display/network_utils.js";
|
||||||
import { ResponseException } from "../../src/shared/util.js";
|
import { ResponseException } from "../../src/shared/util.js";
|
||||||
|
|
||||||
@ -391,18 +390,4 @@ describe("network_utils", function () {
|
|||||||
testCreateResponseError("https://foo.com/bar.pdf", 0, false);
|
testCreateResponseError("https://foo.com/bar.pdf", 0, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("validateResponseStatus", function () {
|
|
||||||
it("accepts valid response statuses", function () {
|
|
||||||
expect(validateResponseStatus(200)).toEqual(true);
|
|
||||||
expect(validateResponseStatus(206)).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects invalid response statuses", function () {
|
|
||||||
expect(validateResponseStatus(302)).toEqual(false);
|
|
||||||
expect(validateResponseStatus(404)).toEqual(false);
|
|
||||||
expect(validateResponseStatus(null)).toEqual(false);
|
|
||||||
expect(validateResponseStatus(undefined)).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
RenderingStates,
|
|
||||||
ScrollMode,
|
ScrollMode,
|
||||||
SpreadMode,
|
SpreadMode,
|
||||||
} from "../../web/ui_utils.js";
|
} from "../../web/ui_utils.js";
|
||||||
@ -35,6 +34,7 @@ import { PDFPageView } from "../../web/pdf_page_view.js";
|
|||||||
import { PDFScriptingManager } from "../../web/pdf_scripting_manager.component.js";
|
import { PDFScriptingManager } from "../../web/pdf_scripting_manager.component.js";
|
||||||
import { PDFSinglePageViewer } from "../../web/pdf_single_page_viewer.js";
|
import { PDFSinglePageViewer } from "../../web/pdf_single_page_viewer.js";
|
||||||
import { PDFViewer } from "../../web/pdf_viewer.js";
|
import { PDFViewer } from "../../web/pdf_viewer.js";
|
||||||
|
import { RenderingStates } from "../../web/renderable_view.js";
|
||||||
import { StructTreeLayerBuilder } from "../../web/struct_tree_layer_builder.js";
|
import { StructTreeLayerBuilder } from "../../web/struct_tree_layer_builder.js";
|
||||||
import { TextLayerBuilder } from "../../web/text_layer_builder.js";
|
import { TextLayerBuilder } from "../../web/text_layer_builder.js";
|
||||||
import { XfaLayerBuilder } from "../../web/xfa_layer_builder.js";
|
import { XfaLayerBuilder } from "../../web/xfa_layer_builder.js";
|
||||||
|
|||||||
@ -20,7 +20,6 @@
|
|||||||
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
|
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
/** @typedef {import("./text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
||||||
/** @typedef {import("./interfaces").IL10n} IL10n */
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../src/display/annotation_layer.js").AnnotationLayer} AnnotationLayer */
|
/** @typedef {import("../src/display/annotation_layer.js").AnnotationLayer} AnnotationLayer */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
@ -33,7 +32,7 @@ import { GenericL10n } from "web-null_l10n";
|
|||||||
* @typedef {Object} AnnotationEditorLayerBuilderOptions
|
* @typedef {Object} AnnotationEditorLayerBuilderOptions
|
||||||
* @property {AnnotationEditorUIManager} [uiManager]
|
* @property {AnnotationEditorUIManager} [uiManager]
|
||||||
* @property {PDFPageProxy} pdfPage
|
* @property {PDFPageProxy} pdfPage
|
||||||
* @property {IL10n} [l10n]
|
* @property {L10n} [l10n]
|
||||||
* @property {StructTreeLayerBuilder} [structTreeLayer]
|
* @property {StructTreeLayerBuilder} [structTreeLayer]
|
||||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||||
* @property {AnnotationLayer} [annotationLayer]
|
* @property {AnnotationLayer} [annotationLayer]
|
||||||
|
|||||||
@ -18,8 +18,6 @@
|
|||||||
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../src/display/annotation_storage").AnnotationStorage} AnnotationStorage */
|
/** @typedef {import("../src/display/annotation_storage").AnnotationStorage} AnnotationStorage */
|
||||||
/** @typedef {import("./interfaces").IDownloadManager} IDownloadManager */
|
|
||||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./struct_tree_layer_builder.js").StructTreeLayerBuilder} StructTreeLayerBuilder */
|
/** @typedef {import("./struct_tree_layer_builder.js").StructTreeLayerBuilder} StructTreeLayerBuilder */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
@ -43,8 +41,8 @@ import { PresentationModeState } from "./ui_utils.js";
|
|||||||
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
||||||
* for annotation icons. Include trailing slash.
|
* for annotation icons. Include trailing slash.
|
||||||
* @property {boolean} renderForms
|
* @property {boolean} renderForms
|
||||||
* @property {IPDFLinkService} linkService
|
* @property {PDFLinkService} linkService
|
||||||
* @property {IDownloadManager} [downloadManager]
|
* @property {BaseDownloadManager} [downloadManager]
|
||||||
* @property {boolean} [enableComment]
|
* @property {boolean} [enableComment]
|
||||||
* @property {boolean} [enableScripting]
|
* @property {boolean} [enableScripting]
|
||||||
* @property {Promise<boolean>} [hasJSActionsPromise]
|
* @property {Promise<boolean>} [hasJSActionsPromise]
|
||||||
|
|||||||
17
web/app.js
17
web/app.js
@ -13,7 +13,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("./interfaces.js").IL10n} IL10n */
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../src/display/api.js").PDFDocumentProxy} PDFDocumentProxy */
|
/** @typedef {import("../src/display/api.js").PDFDocumentProxy} PDFDocumentProxy */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
@ -34,7 +33,6 @@ import {
|
|||||||
normalizeWheelEventDirection,
|
normalizeWheelEventDirection,
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
RenderingStates,
|
|
||||||
ScrollMode,
|
ScrollMode,
|
||||||
SidebarView,
|
SidebarView,
|
||||||
SpreadMode,
|
SpreadMode,
|
||||||
@ -92,6 +90,7 @@ import { PdfTextExtractor } from "./pdf_text_extractor.js";
|
|||||||
import { PDFThumbnailViewer } from "web-pdf_thumbnail_viewer";
|
import { PDFThumbnailViewer } from "web-pdf_thumbnail_viewer";
|
||||||
import { PDFViewer } from "./pdf_viewer.js";
|
import { PDFViewer } from "./pdf_viewer.js";
|
||||||
import { Preferences } from "web-preferences";
|
import { Preferences } from "web-preferences";
|
||||||
|
import { RenderingStates } from "./renderable_view.js";
|
||||||
import { SecondaryToolbar } from "web-secondary_toolbar";
|
import { SecondaryToolbar } from "web-secondary_toolbar";
|
||||||
import { SignatureManager } from "web-signature_manager";
|
import { SignatureManager } from "web-signature_manager";
|
||||||
import { Toolbar } from "web-toolbar";
|
import { Toolbar } from "web-toolbar";
|
||||||
@ -160,7 +159,7 @@ const PDFViewerApplication = {
|
|||||||
secondaryToolbar: null,
|
secondaryToolbar: null,
|
||||||
/** @type {EventBus} */
|
/** @type {EventBus} */
|
||||||
eventBus: null,
|
eventBus: null,
|
||||||
/** @type {IL10n} */
|
/** @type {L10n} */
|
||||||
l10n: null,
|
l10n: null,
|
||||||
/** @type {AnnotationEditorParams} */
|
/** @type {AnnotationEditorParams} */
|
||||||
annotationEditorParams: null,
|
annotationEditorParams: null,
|
||||||
@ -1236,9 +1235,7 @@ const PDFViewerApplication = {
|
|||||||
this.passwordPrompt.open();
|
this.passwordPrompt.open();
|
||||||
};
|
};
|
||||||
|
|
||||||
loadingTask.onProgress = ({ loaded, total }) => {
|
loadingTask.onProgress = evt => this.progress(evt.percent);
|
||||||
this.progress(loaded / total);
|
|
||||||
};
|
|
||||||
|
|
||||||
return loadingTask.promise.then(
|
return loadingTask.promise.then(
|
||||||
pdfDocument => {
|
pdfDocument => {
|
||||||
@ -1374,8 +1371,7 @@ const PDFViewerApplication = {
|
|||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
|
|
||||||
progress(level) {
|
progress(percent) {
|
||||||
const percent = Math.round(level * 100);
|
|
||||||
// When we transition from full request to range requests, it's possible
|
// When we transition from full request to range requests, it's possible
|
||||||
// that we discard some of the loaded data. This can cause the loading
|
// that we discard some of the loaded data. This can cause the loading
|
||||||
// bar to move backwards. So prevent this by only updating the bar if it
|
// bar to move backwards. So prevent this by only updating the bar if it
|
||||||
@ -2460,10 +2456,7 @@ const PDFViewerApplication = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
initCom(PDFViewerApplication);
|
initCom(PDFViewerApplication);
|
||||||
|
PDFPrintServiceFactory.initGlobals(PDFViewerApplication);
|
||||||
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
|
|
||||||
PDFPrintServiceFactory.initGlobals(PDFViewerApplication);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||||
const HOSTED_VIEWER_ORIGINS = new Set([
|
const HOSTED_VIEWER_ORIGINS = new Set([
|
||||||
|
|||||||
@ -138,7 +138,7 @@ class Autolinker {
|
|||||||
static findLinks(text) {
|
static findLinks(text) {
|
||||||
// Regex can be tested and verified at https://regex101.com/r/rXoLiT/2.
|
// Regex can be tested and verified at https://regex101.com/r/rXoLiT/2.
|
||||||
this.#regex ??=
|
this.#regex ??=
|
||||||
/\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|(?=\p{L})[\S--[@\p{Ps}\p{Pe}<>]]+@([\S--[\p{P}<>]]+(?:\.[\S--[\p{P}<>]]+)+)/gmv;
|
/\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|(?=\p{L})[\S--[@\p{Ps}\p{Pe}<>]]+@([\S--[[\p{P}--\-]<>]]+(?:\.[\S--[[\p{P}--\-]<>]]+)+)/gmv;
|
||||||
|
|
||||||
const [normalizedText, diffs] = normalize(text, { ignoreDashEOL: true });
|
const [normalizedText, diffs] = normalize(text, { ignoreDashEOL: true });
|
||||||
const matches = normalizedText.matchAll(this.#regex);
|
const matches = normalizedText.matchAll(this.#regex);
|
||||||
|
|||||||
103
web/base_download_manager.js
Normal file
103
web/base_download_manager.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/* Copyright 2013 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { isPdfFile } from "pdfjs-lib";
|
||||||
|
|
||||||
|
class BaseDownloadManager {
|
||||||
|
#openBlobUrls = new WeakMap();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (
|
||||||
|
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||||
|
this.constructor === BaseDownloadManager
|
||||||
|
) {
|
||||||
|
throw new Error("Cannot initialize BaseDownloadManager.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_triggerDownload(blobUrl, originalUrl, filename, isAttachment = false) {
|
||||||
|
throw new Error("Not implemented: _triggerDownload");
|
||||||
|
}
|
||||||
|
|
||||||
|
_getOpenDataUrl(blobUrl, filename, dest = null) {
|
||||||
|
throw new Error("Not implemented: _getOpenDataUrl");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} data
|
||||||
|
* @param {string} filename
|
||||||
|
* @param {string} [contentType]
|
||||||
|
*/
|
||||||
|
downloadData(data, filename, contentType) {
|
||||||
|
const blobUrl = URL.createObjectURL(
|
||||||
|
new Blob([data], { type: contentType })
|
||||||
|
);
|
||||||
|
|
||||||
|
this._triggerDownload(
|
||||||
|
blobUrl,
|
||||||
|
/* originalUrl = */ blobUrl,
|
||||||
|
filename,
|
||||||
|
/* isAttachment = */ true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} data
|
||||||
|
* @param {string} filename
|
||||||
|
* @param {string | null} [dest]
|
||||||
|
* @returns {boolean} Indicating if the data was opened.
|
||||||
|
*/
|
||||||
|
openOrDownloadData(data, filename, dest = null) {
|
||||||
|
const isPdfData = isPdfFile(filename);
|
||||||
|
const contentType = isPdfData ? "application/pdf" : "";
|
||||||
|
|
||||||
|
if (isPdfData) {
|
||||||
|
let blobUrl;
|
||||||
|
try {
|
||||||
|
blobUrl = this.#openBlobUrls.getOrInsertComputed(data, () =>
|
||||||
|
URL.createObjectURL(new Blob([data], { type: contentType }))
|
||||||
|
);
|
||||||
|
const viewerUrl = this._getOpenDataUrl(blobUrl, filename, dest);
|
||||||
|
|
||||||
|
window.open(viewerUrl);
|
||||||
|
return true;
|
||||||
|
} catch (ex) {
|
||||||
|
console.error("openOrDownloadData:", ex);
|
||||||
|
// Release the `blobUrl`, since opening it failed, and fallback to
|
||||||
|
// downloading the PDF file.
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
this.#openBlobUrls.delete(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.downloadData(data, filename, contentType);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} data
|
||||||
|
* @param {string} url
|
||||||
|
* @param {string} filename
|
||||||
|
*/
|
||||||
|
download(data, url, filename) {
|
||||||
|
const blobUrl = data
|
||||||
|
? URL.createObjectURL(new Blob([data], { type: "application/pdf" }))
|
||||||
|
: null;
|
||||||
|
|
||||||
|
this._triggerDownload(blobUrl, /* originalUrl = */ url, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { BaseDownloadManager };
|
||||||
@ -13,10 +13,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { RenderableView, RenderingStates } from "./renderable_view.js";
|
||||||
import { RenderingCancelledException } from "pdfjs-lib";
|
import { RenderingCancelledException } from "pdfjs-lib";
|
||||||
import { RenderingStates } from "./ui_utils.js";
|
|
||||||
|
|
||||||
class BasePDFPageView {
|
class BasePDFPageView extends RenderableView {
|
||||||
#loadingId = null;
|
#loadingId = null;
|
||||||
|
|
||||||
#minDurationToUpdateCanvas = 0;
|
#minDurationToUpdateCanvas = 0;
|
||||||
@ -48,11 +48,8 @@ class BasePDFPageView {
|
|||||||
|
|
||||||
renderingQueue = null;
|
renderingQueue = null;
|
||||||
|
|
||||||
renderTask = null;
|
|
||||||
|
|
||||||
resume = null;
|
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
super();
|
||||||
this.eventBus = options.eventBus;
|
this.eventBus = options.eventBus;
|
||||||
this.id = options.id;
|
this.id = options.id;
|
||||||
this.pageColors = options.pageColors || null;
|
this.pageColors = options.pageColors || null;
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
import { AppOptions } from "./app_options.js";
|
import { AppOptions } from "./app_options.js";
|
||||||
import { BaseExternalServices } from "./external_services.js";
|
import { BaseExternalServices } from "./external_services.js";
|
||||||
import { BasePreferences } from "./preferences.js";
|
import { BasePreferences } from "./preferences.js";
|
||||||
|
import { DownloadManager as GenericDownloadManager } from "./download_manager.js";
|
||||||
import { GenericL10n } from "./genericl10n.js";
|
import { GenericL10n } from "./genericl10n.js";
|
||||||
import { GenericScripting } from "./generic_scripting.js";
|
import { GenericScripting } from "./generic_scripting.js";
|
||||||
import { SignatureStorage } from "./generic_signature_storage.js";
|
import { SignatureStorage } from "./generic_signature_storage.js";
|
||||||
@ -310,6 +311,25 @@ function setReferer(url, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This "should" really extend the `BaseDownloadManager` class,
|
||||||
|
* however doing it this way instead reduces code duplication.
|
||||||
|
*/
|
||||||
|
class DownloadManager extends GenericDownloadManager {
|
||||||
|
_getOpenDataUrl(blobUrl, filename, dest = null) {
|
||||||
|
// In the Chrome extension, the URL is rewritten using the history API
|
||||||
|
// in viewer.js, so an absolute URL must be generated.
|
||||||
|
let url =
|
||||||
|
chrome.runtime.getURL("/content/web/viewer.html") +
|
||||||
|
"?file=" +
|
||||||
|
encodeURIComponent(blobUrl + "#" + filename);
|
||||||
|
if (dest) {
|
||||||
|
url += `#${escape(dest)}`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// chrome.storage.sync is not supported in every Chromium-derivate.
|
// chrome.storage.sync is not supported in every Chromium-derivate.
|
||||||
// Note: The background page takes care of migrating values from
|
// Note: The background page takes care of migrating values from
|
||||||
// chrome.storage.local to chrome.storage.sync when needed.
|
// chrome.storage.local to chrome.storage.sync when needed.
|
||||||
@ -437,4 +457,4 @@ class MLManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ExternalServices, initCom, MLManager, Preferences };
|
export { DownloadManager, ExternalServices, initCom, MLManager, Preferences };
|
||||||
|
|||||||
@ -13,9 +13,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("./interfaces").IDownloadManager} IDownloadManager */
|
import { BaseDownloadManager } from "./base_download_manager.js";
|
||||||
|
import { createValidAbsoluteUrl } from "pdfjs-lib";
|
||||||
import { createValidAbsoluteUrl, isPdfFile } from "pdfjs-lib";
|
|
||||||
|
|
||||||
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("CHROME || GENERIC")) {
|
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("CHROME || GENERIC")) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -24,101 +23,41 @@ if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("CHROME || GENERIC")) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function download(blobUrl, filename) {
|
class DownloadManager extends BaseDownloadManager {
|
||||||
const a = document.createElement("a");
|
_triggerDownload(blobUrl, originalUrl, filename, isAttachment = false) {
|
||||||
if (!a.click) {
|
if (!blobUrl && !isAttachment) {
|
||||||
throw new Error('DownloadManager: "a.click()" is not supported.');
|
// Fallback to downloading non-attachments by their URL.
|
||||||
}
|
if (!createValidAbsoluteUrl(originalUrl, "http://example.com")) {
|
||||||
a.href = blobUrl;
|
throw new Error(`_triggerDownload - not a valid URL: ${originalUrl}`);
|
||||||
a.target = "_parent";
|
|
||||||
// Use a.download if available. This increases the likelihood that
|
|
||||||
// the file is downloaded instead of opened by another PDF plugin.
|
|
||||||
if ("download" in a) {
|
|
||||||
a.download = filename;
|
|
||||||
}
|
|
||||||
// <a> must be in the document for recent Firefox versions,
|
|
||||||
// otherwise .click() is ignored.
|
|
||||||
(document.body || document.documentElement).append(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @implements {IDownloadManager}
|
|
||||||
*/
|
|
||||||
class DownloadManager {
|
|
||||||
#openBlobUrls = new WeakMap();
|
|
||||||
|
|
||||||
downloadData(data, filename, contentType) {
|
|
||||||
const blobUrl = URL.createObjectURL(
|
|
||||||
new Blob([data], { type: contentType })
|
|
||||||
);
|
|
||||||
download(blobUrl, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {boolean} Indicating if the data was opened.
|
|
||||||
*/
|
|
||||||
openOrDownloadData(data, filename, dest = null) {
|
|
||||||
const isPdfData = isPdfFile(filename);
|
|
||||||
const contentType = isPdfData ? "application/pdf" : "";
|
|
||||||
|
|
||||||
if (
|
|
||||||
(typeof PDFJSDev === "undefined" || !PDFJSDev.test("COMPONENTS")) &&
|
|
||||||
isPdfData
|
|
||||||
) {
|
|
||||||
let blobUrl = this.#openBlobUrls.get(data);
|
|
||||||
if (!blobUrl) {
|
|
||||||
blobUrl = URL.createObjectURL(new Blob([data], { type: contentType }));
|
|
||||||
this.#openBlobUrls.set(data, blobUrl);
|
|
||||||
}
|
|
||||||
let viewerUrl;
|
|
||||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
|
||||||
// The current URL is the viewer, let's use it and append the file.
|
|
||||||
viewerUrl = "?file=" + encodeURIComponent(blobUrl + "#" + filename);
|
|
||||||
} else if (PDFJSDev.test("CHROME")) {
|
|
||||||
// In the Chrome extension, the URL is rewritten using the history API
|
|
||||||
// in viewer.js, so an absolute URL must be generated.
|
|
||||||
viewerUrl =
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
chrome.runtime.getURL("/content/web/viewer.html") +
|
|
||||||
"?file=" +
|
|
||||||
encodeURIComponent(blobUrl + "#" + filename);
|
|
||||||
}
|
|
||||||
if (dest) {
|
|
||||||
viewerUrl += `#${escape(dest)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
window.open(viewerUrl);
|
|
||||||
return true;
|
|
||||||
} catch (ex) {
|
|
||||||
console.error("openOrDownloadData:", ex);
|
|
||||||
// Release the `blobUrl`, since opening it failed, and fallback to
|
|
||||||
// downloading the PDF file.
|
|
||||||
URL.revokeObjectURL(blobUrl);
|
|
||||||
this.#openBlobUrls.delete(data);
|
|
||||||
}
|
}
|
||||||
|
blobUrl = originalUrl + "#pdfjs.action=download";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.downloadData(data, filename, contentType);
|
const a = document.createElement("a");
|
||||||
return false;
|
a.href = blobUrl;
|
||||||
|
a.target = "_parent";
|
||||||
|
// Use a.download if available. This increases the likelihood that
|
||||||
|
// the file is downloaded instead of opened by another PDF plugin.
|
||||||
|
if ("download" in a) {
|
||||||
|
a.download = filename;
|
||||||
|
}
|
||||||
|
// <a> must be in the document for recent Firefox versions,
|
||||||
|
// otherwise .click() is ignored.
|
||||||
|
(document.body || document.documentElement).append(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
download(data, url, filename) {
|
_getOpenDataUrl(blobUrl, filename, dest = null) {
|
||||||
let blobUrl;
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("COMPONENTS")) {
|
||||||
if (data) {
|
throw new Error("Opening data is not supported in `COMPONENTS` builds.");
|
||||||
blobUrl = URL.createObjectURL(
|
|
||||||
new Blob([data], { type: "application/pdf" })
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (!createValidAbsoluteUrl(url, "http://example.com")) {
|
|
||||||
console.error(`download - not a valid URL: ${url}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
blobUrl = url + "#pdfjs.action=download";
|
|
||||||
}
|
}
|
||||||
download(blobUrl, filename);
|
// The current URL is the viewer, let's use it and append the file.
|
||||||
|
let url = "?file=" + encodeURIComponent(blobUrl + "#" + filename);
|
||||||
|
if (dest) {
|
||||||
|
url += `#${escape(dest)}`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,8 +13,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("./interfaces.js").IL10n} IL10n */
|
|
||||||
|
|
||||||
class BaseExternalServices {
|
class BaseExternalServices {
|
||||||
constructor() {
|
constructor() {
|
||||||
if (
|
if (
|
||||||
@ -36,7 +34,7 @@ class BaseExternalServices {
|
|||||||
reportText(data) {}
|
reportText(data) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<IL10n>}
|
* @returns {Promise<L10n>}
|
||||||
*/
|
*/
|
||||||
async createL10n() {
|
async createL10n() {
|
||||||
throw new Error("Not implemented: createL10n");
|
throw new Error("Not implemented: createL10n");
|
||||||
|
|||||||
@ -19,7 +19,10 @@ import {
|
|||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
shadow,
|
shadow,
|
||||||
} from "pdfjs-lib";
|
} from "pdfjs-lib";
|
||||||
import { getXfaHtmlForPrinting } from "./print_utils.js";
|
import {
|
||||||
|
BasePrintServiceFactory,
|
||||||
|
getXfaHtmlForPrinting,
|
||||||
|
} from "./print_utils.js";
|
||||||
|
|
||||||
// Creates a placeholder with div and canvas with right size for the page.
|
// Creates a placeholder with div and canvas with right size for the page.
|
||||||
function composePage(
|
function composePage(
|
||||||
@ -194,10 +197,7 @@ class FirefoxPrintService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
class PDFPrintServiceFactory extends BasePrintServiceFactory {
|
||||||
* @implements {IPDFPrintServiceFactory}
|
|
||||||
*/
|
|
||||||
class PDFPrintServiceFactory {
|
|
||||||
static get supportsPrinting() {
|
static get supportsPrinting() {
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
return shadow(this, "supportsPrinting", "mozPrintCallback" in canvas);
|
return shadow(this, "supportsPrinting", "mozPrintCallback" in canvas);
|
||||||
|
|||||||
@ -13,8 +13,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isPdfFile, PDFDataRangeTransport } from "pdfjs-lib";
|
import { MathClamp, PDFDataRangeTransport } from "pdfjs-lib";
|
||||||
import { AppOptions } from "./app_options.js";
|
import { AppOptions } from "./app_options.js";
|
||||||
|
import { BaseDownloadManager } from "./base_download_manager.js";
|
||||||
import { BaseExternalServices } from "./external_services.js";
|
import { BaseExternalServices } from "./external_services.js";
|
||||||
import { BasePreferences } from "./preferences.js";
|
import { BasePreferences } from "./preferences.js";
|
||||||
import { DEFAULT_SCALE_VALUE } from "./ui_utils.js";
|
import { DEFAULT_SCALE_VALUE } from "./ui_utils.js";
|
||||||
@ -80,69 +81,25 @@ class FirefoxCom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DownloadManager {
|
class DownloadManager extends BaseDownloadManager {
|
||||||
#openBlobUrls = new WeakMap();
|
_triggerDownload(blobUrl, originalUrl, filename, isAttachment = false) {
|
||||||
|
|
||||||
downloadData(data, filename, contentType) {
|
|
||||||
const blobUrl = URL.createObjectURL(
|
|
||||||
new Blob([data], { type: contentType })
|
|
||||||
);
|
|
||||||
|
|
||||||
FirefoxCom.request("download", {
|
FirefoxCom.request("download", {
|
||||||
blobUrl,
|
blobUrl,
|
||||||
originalUrl: blobUrl,
|
originalUrl,
|
||||||
filename,
|
filename,
|
||||||
isAttachment: true,
|
isAttachment,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_getOpenDataUrl(blobUrl, filename, dest = null) {
|
||||||
* @returns {boolean} Indicating if the data was opened.
|
// Let Firefox's content handler catch the URL and display the PDF.
|
||||||
*/
|
// NOTE: This cannot use a query string for the filename, see
|
||||||
openOrDownloadData(data, filename, dest = null) {
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1632644#c5
|
||||||
const isPdfData = isPdfFile(filename);
|
let url = blobUrl + "#filename=" + encodeURIComponent(filename);
|
||||||
const contentType = isPdfData ? "application/pdf" : "";
|
if (dest) {
|
||||||
|
url += `&filedest=${escape(dest)}`;
|
||||||
if (isPdfData) {
|
|
||||||
let blobUrl = this.#openBlobUrls.get(data);
|
|
||||||
if (!blobUrl) {
|
|
||||||
blobUrl = URL.createObjectURL(new Blob([data], { type: contentType }));
|
|
||||||
this.#openBlobUrls.set(data, blobUrl);
|
|
||||||
}
|
|
||||||
// Let Firefox's content handler catch the URL and display the PDF.
|
|
||||||
// NOTE: This cannot use a query string for the filename, see
|
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1632644#c5
|
|
||||||
let viewerUrl = blobUrl + "#filename=" + encodeURIComponent(filename);
|
|
||||||
if (dest) {
|
|
||||||
viewerUrl += `&filedest=${escape(dest)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
window.open(viewerUrl);
|
|
||||||
return true;
|
|
||||||
} catch (ex) {
|
|
||||||
console.error("openOrDownloadData:", ex);
|
|
||||||
// Release the `blobUrl`, since opening it failed, and fallback to
|
|
||||||
// downloading the PDF file.
|
|
||||||
URL.revokeObjectURL(blobUrl);
|
|
||||||
this.#openBlobUrls.delete(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return url;
|
||||||
this.downloadData(data, filename, contentType);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
download(data, url, filename) {
|
|
||||||
const blobUrl = data
|
|
||||||
? URL.createObjectURL(new Blob([data], { type: "application/pdf" }))
|
|
||||||
: null;
|
|
||||||
|
|
||||||
FirefoxCom.request("download", {
|
|
||||||
blobUrl,
|
|
||||||
originalUrl: url,
|
|
||||||
filename,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -627,7 +584,13 @@ class ExternalServices extends BaseExternalServices {
|
|||||||
pdfDataRangeTransport?.onDataProgressiveDone();
|
pdfDataRangeTransport?.onDataProgressiveDone();
|
||||||
break;
|
break;
|
||||||
case "progress":
|
case "progress":
|
||||||
viewerApp.progress(args.loaded / args.total);
|
const percent = MathClamp(
|
||||||
|
Math.round((args.loaded / args.total) * 100),
|
||||||
|
0,
|
||||||
|
100
|
||||||
|
);
|
||||||
|
|
||||||
|
viewerApp.progress(percent);
|
||||||
break;
|
break;
|
||||||
case "complete":
|
case "complete":
|
||||||
if (!args.data) {
|
if (!args.data) {
|
||||||
|
|||||||
@ -13,8 +13,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("./interfaces").IL10n} IL10n */
|
|
||||||
|
|
||||||
import { FeatureTest, fetchData } from "pdfjs-lib";
|
import { FeatureTest, fetchData } from "pdfjs-lib";
|
||||||
import { FluentBundle, FluentResource } from "fluent-bundle";
|
import { FluentBundle, FluentResource } from "fluent-bundle";
|
||||||
import { DOMLocalization } from "fluent-dom";
|
import { DOMLocalization } from "fluent-dom";
|
||||||
@ -49,9 +47,6 @@ function createBundle(lang, text) {
|
|||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @implements {IL10n}
|
|
||||||
*/
|
|
||||||
class GenericL10n extends L10n {
|
class GenericL10n extends L10n {
|
||||||
constructor(lang) {
|
constructor(lang) {
|
||||||
super({ lang });
|
super({ lang });
|
||||||
|
|||||||
@ -1,235 +0,0 @@
|
|||||||
/* Copyright 2018 Mozilla Foundation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
/* eslint-disable getter-return */
|
|
||||||
|
|
||||||
/** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
|
||||||
/** @typedef {import("./ui_utils").RenderingStates} RenderingStates */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @interface
|
|
||||||
*/
|
|
||||||
class IPDFLinkService {
|
|
||||||
/**
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get pagesCount() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get page() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} value
|
|
||||||
*/
|
|
||||||
set page(value) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {number}
|
|
||||||
*/
|
|
||||||
get rotation() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} value
|
|
||||||
*/
|
|
||||||
set rotation(value) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
get isInPresentationMode() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
get externalLinkEnabled() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {boolean} value
|
|
||||||
*/
|
|
||||||
set externalLinkEnabled(value) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string|Array} dest - The named, or explicit, PDF destination.
|
|
||||||
*/
|
|
||||||
async goToDestination(dest) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number|string} val - The page number, or page label.
|
|
||||||
*/
|
|
||||||
goToPage(val) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scrolls to a specific location in the PDF document.
|
|
||||||
* @param {number} pageNumber - The page number to scroll to.
|
|
||||||
* @param {number} x - The x-coordinate to scroll to in page coordinates.
|
|
||||||
* @param {number} y - The y-coordinate to scroll to in page coordinates.
|
|
||||||
*/
|
|
||||||
goToXY(pageNumber, x, y) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {HTMLAnchorElement} link
|
|
||||||
* @param {string} url
|
|
||||||
* @param {boolean} [newWindow]
|
|
||||||
*/
|
|
||||||
addLinkAttributes(link, url, newWindow = false) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param dest - The PDF destination object.
|
|
||||||
* @returns {string} The hyperlink to the PDF object.
|
|
||||||
*/
|
|
||||||
getDestinationHash(dest) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param hash - The PDF parameters/hash.
|
|
||||||
* @returns {string} The hyperlink to the PDF object.
|
|
||||||
*/
|
|
||||||
getAnchorUrl(hash) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} hash
|
|
||||||
*/
|
|
||||||
setHash(hash) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} action
|
|
||||||
*/
|
|
||||||
executeNamedAction(action) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} action
|
|
||||||
*/
|
|
||||||
executeSetOCGState(action) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @interface
|
|
||||||
*/
|
|
||||||
class IRenderableView {
|
|
||||||
constructor() {
|
|
||||||
/** @type {function | null} */
|
|
||||||
this.resume = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {string} - Unique ID for rendering queue.
|
|
||||||
*/
|
|
||||||
get renderingId() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {RenderingStates}
|
|
||||||
*/
|
|
||||||
get renderingState() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Promise} Resolved on draw completion.
|
|
||||||
*/
|
|
||||||
async draw() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @interface
|
|
||||||
*/
|
|
||||||
class IDownloadManager {
|
|
||||||
/**
|
|
||||||
* @param {Uint8Array} data
|
|
||||||
* @param {string} filename
|
|
||||||
* @param {string} [contentType]
|
|
||||||
*/
|
|
||||||
downloadData(data, filename, contentType) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Uint8Array} data
|
|
||||||
* @param {string} filename
|
|
||||||
* @param {string | null} [dest]
|
|
||||||
* @returns {boolean} Indicating if the data was opened.
|
|
||||||
*/
|
|
||||||
openOrDownloadData(data, filename, dest = null) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Uint8Array} data
|
|
||||||
* @param {string} url
|
|
||||||
* @param {string} filename
|
|
||||||
*/
|
|
||||||
download(data, url, filename) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @interface
|
|
||||||
*/
|
|
||||||
class IL10n {
|
|
||||||
/**
|
|
||||||
* @returns {string} - The current locale.
|
|
||||||
*/
|
|
||||||
getLanguage() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {string} - 'rtl' or 'ltr'.
|
|
||||||
*/
|
|
||||||
getDirection() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translates text identified by the key and adds/formats data using the args
|
|
||||||
* property bag. If the key was not found, translation falls back to the
|
|
||||||
* fallback text.
|
|
||||||
* @param {Array | string} ids
|
|
||||||
* @param {Object | null} [args]
|
|
||||||
* @param {string} [fallback]
|
|
||||||
* @returns {Promise<string>}
|
|
||||||
*/
|
|
||||||
async get(ids, args = null, fallback) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Translates HTML element.
|
|
||||||
* @param {HTMLElement} element
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async translate(element) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pause the localization.
|
|
||||||
*/
|
|
||||||
pause() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resume the localization.
|
|
||||||
*/
|
|
||||||
resume() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @interface
|
|
||||||
*/
|
|
||||||
class IPDFPrintServiceFactory {
|
|
||||||
static initGlobals() {}
|
|
||||||
|
|
||||||
static get supportsPrinting() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static createPrintService() {
|
|
||||||
throw new Error("Not implemented: createPrintService");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
IDownloadManager,
|
|
||||||
IL10n,
|
|
||||||
IPDFLinkService,
|
|
||||||
IPDFPrintServiceFactory,
|
|
||||||
IRenderableView,
|
|
||||||
};
|
|
||||||
@ -13,12 +13,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("./interfaces").IL10n} IL10n */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NOTE: The L10n-implementations should use lowercase language-codes
|
* NOTE: The L10n-implementations should use lowercase language-codes
|
||||||
* internally.
|
* internally.
|
||||||
* @implements {IL10n}
|
|
||||||
*/
|
*/
|
||||||
class L10n {
|
class L10n {
|
||||||
#dir;
|
#dir;
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("./event_utils.js").EventBus} EventBus */
|
/** @typedef {import("./event_utils.js").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces.js").IL10n} IL10n */
|
|
||||||
/** @typedef {import("./overlay_manager.js").OverlayManager} OverlayManager */
|
/** @typedef {import("./overlay_manager.js").OverlayManager} OverlayManager */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../src/display/api.js").PDFDocumentProxy} PDFDocumentProxy */
|
/** @typedef {import("../src/display/api.js").PDFDocumentProxy} PDFDocumentProxy */
|
||||||
@ -58,7 +57,7 @@ class PDFDocumentProperties {
|
|||||||
* @param {PDFDocumentPropertiesOptions} options
|
* @param {PDFDocumentPropertiesOptions} options
|
||||||
* @param {OverlayManager} overlayManager - Manager for the viewer overlays.
|
* @param {OverlayManager} overlayManager - Manager for the viewer overlays.
|
||||||
* @param {EventBus} eventBus - The application event bus.
|
* @param {EventBus} eventBus - The application event bus.
|
||||||
* @param {IL10n} l10n - Localization service.
|
* @param {L10n} l10n - Localization service.
|
||||||
* @param {function} fileNameLookup - The function that is used to lookup
|
* @param {function} fileNameLookup - The function that is used to lookup
|
||||||
* the document fileName.
|
* the document fileName.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
/** @typedef {import("../src/display/api").PDFDocumentProxy} PDFDocumentProxy */
|
/** @typedef {import("../src/display/api").PDFDocumentProxy} PDFDocumentProxy */
|
||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
|
||||||
|
|
||||||
import { binarySearchFirstItem, scrollIntoView } from "./ui_utils.js";
|
import { binarySearchFirstItem, scrollIntoView } from "./ui_utils.js";
|
||||||
import { getCharacterType, getNormalizeWithNFKC } from "./pdf_find_utils.js";
|
import { getCharacterType, getNormalizeWithNFKC } from "./pdf_find_utils.js";
|
||||||
@ -405,7 +404,7 @@ function getOriginalIndex(diffs, pos, len) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} PDFFindControllerOptions
|
* @typedef {Object} PDFFindControllerOptions
|
||||||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
* @property {PDFLinkService} linkService - The navigation/linking service.
|
||||||
* @property {EventBus} eventBus - The application event bus.
|
* @property {EventBus} eventBus - The application event bus.
|
||||||
* @property {boolean} [updateMatchesCountOnProgress] - True if the matches
|
* @property {boolean} [updateMatchesCountOnProgress] - True if the matches
|
||||||
* count must be updated on progress or only when the last page is reached.
|
* count must be updated on progress or only when the last page is reached.
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
|
||||||
|
|
||||||
import { isValidRotation, parseQueryString } from "./ui_utils.js";
|
import { isValidRotation, parseQueryString } from "./ui_utils.js";
|
||||||
import { updateUrlHash } from "pdfjs-lib";
|
import { updateUrlHash } from "pdfjs-lib";
|
||||||
@ -29,7 +28,7 @@ const UPDATE_VIEWAREA_TIMEOUT = 1000; // milliseconds
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} PDFHistoryOptions
|
* @typedef {Object} PDFHistoryOptions
|
||||||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
* @property {PDFLinkService} linkService - The navigation/linking service.
|
||||||
* @property {EventBus} eventBus - The application event bus.
|
* @property {EventBus} eventBus - The application event bus.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
|
||||||
|
|
||||||
import { isValidExplicitDest } from "pdfjs-lib";
|
import { isValidExplicitDest } from "pdfjs-lib";
|
||||||
import { parseQueryString } from "./ui_utils.js";
|
import { parseQueryString } from "./ui_utils.js";
|
||||||
@ -45,7 +44,6 @@ const LinkTarget = {
|
|||||||
/**
|
/**
|
||||||
* Performs navigation functions inside PDF, such as opening specified page,
|
* Performs navigation functions inside PDF, such as opening specified page,
|
||||||
* or destination.
|
* or destination.
|
||||||
* @implements {IPDFLinkService}
|
|
||||||
*/
|
*/
|
||||||
class PDFLinkService {
|
class PDFLinkService {
|
||||||
externalLinkEnabled = true;
|
externalLinkEnabled = true;
|
||||||
@ -425,7 +423,7 @@ class PDFLinkService {
|
|||||||
}
|
}
|
||||||
// Support opening of PDF attachments in the Firefox PDF Viewer,
|
// Support opening of PDF attachments in the Firefox PDF Viewer,
|
||||||
// which uses a couple of non-standard hash parameters; refer to
|
// which uses a couple of non-standard hash parameters; refer to
|
||||||
// `DownloadManager.openOrDownloadData` in the firefoxcom.js file.
|
// `DownloadManager._getOpenDataUrl` in the firefoxcom.js file.
|
||||||
if (!params.has("filename") || !params.has("filedest")) {
|
if (!params.has("filename") || !params.has("filedest")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -517,9 +515,6 @@ class PDFLinkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @implements {IPDFLinkService}
|
|
||||||
*/
|
|
||||||
class SimpleLinkService extends PDFLinkService {
|
class SimpleLinkService extends PDFLinkService {
|
||||||
setDocument(pdfDocument, baseUrl = null) {}
|
setDocument(pdfDocument, baseUrl = null) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,6 @@
|
|||||||
/** @typedef {import("./event_utils.js").EventBus} EventBus */
|
/** @typedef {import("./event_utils.js").EventBus} EventBus */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./download_manager.js").DownloadManager} DownloadManager */
|
/** @typedef {import("./download_manager.js").DownloadManager} DownloadManager */
|
||||||
/** @typedef {import("./interfaces.js").IPDFLinkService} IPDFLinkService */
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../src/display/api.js").PDFDocumentProxy} PDFDocumentProxy */
|
/** @typedef {import("../src/display/api.js").PDFDocumentProxy} PDFDocumentProxy */
|
||||||
|
|
||||||
@ -27,7 +26,7 @@ import { SidebarView } from "./ui_utils.js";
|
|||||||
* @typedef {Object} PDFOutlineViewerOptions
|
* @typedef {Object} PDFOutlineViewerOptions
|
||||||
* @property {HTMLDivElement} container - The viewer element.
|
* @property {HTMLDivElement} container - The viewer element.
|
||||||
* @property {EventBus} eventBus - The application event bus.
|
* @property {EventBus} eventBus - The application event bus.
|
||||||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
* @property {PDFLinkService} linkService - The navigation/linking service.
|
||||||
* @property {DownloadManager} downloadManager - The download manager.
|
* @property {DownloadManager} downloadManager - The download manager.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@ -15,13 +15,8 @@
|
|||||||
|
|
||||||
import { BasePDFPageView } from "./base_pdf_page_view.js";
|
import { BasePDFPageView } from "./base_pdf_page_view.js";
|
||||||
import { OutputScale } from "pdfjs-lib";
|
import { OutputScale } from "pdfjs-lib";
|
||||||
import { RenderingStates } from "./ui_utils.js";
|
import { RenderingStates } from "./renderable_view.js";
|
||||||
|
|
||||||
/** @typedef {import("./interfaces").IRenderableView} IRenderableView */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @implements {IRenderableView}
|
|
||||||
*/
|
|
||||||
class PDFPageDetailView extends BasePDFPageView {
|
class PDFPageDetailView extends BasePDFPageView {
|
||||||
#detailArea = null;
|
#detailArea = null;
|
||||||
|
|
||||||
@ -54,9 +49,9 @@ class PDFPageDetailView extends BasePDFPageView {
|
|||||||
return super.renderingState;
|
return super.renderingState;
|
||||||
}
|
}
|
||||||
|
|
||||||
set renderingState(value) {
|
set renderingState(state) {
|
||||||
this.renderingCancelled = false;
|
this.renderingCancelled = false;
|
||||||
super.renderingState = value;
|
super.renderingState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
reset({ keepCanvas = false } = {}) {
|
reset({ keepCanvas = false } = {}) {
|
||||||
|
|||||||
@ -18,8 +18,6 @@
|
|||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig */
|
/** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig */
|
||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces").IL10n} IL10n */
|
|
||||||
/** @typedef {import("./interfaces").IRenderableView} IRenderableView */
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
||||||
/** @typedef {import("./comment_manager.js").CommentManager} CommentManager */
|
/** @typedef {import("./comment_manager.js").CommentManager} CommentManager */
|
||||||
@ -37,7 +35,6 @@ import {
|
|||||||
calcRound,
|
calcRound,
|
||||||
DEFAULT_SCALE,
|
DEFAULT_SCALE,
|
||||||
floorToDivide,
|
floorToDivide,
|
||||||
RenderingStates,
|
|
||||||
TextLayerMode,
|
TextLayerMode,
|
||||||
} from "./ui_utils.js";
|
} from "./ui_utils.js";
|
||||||
import { AnnotationEditorLayerBuilder } from "./annotation_editor_layer_builder.js";
|
import { AnnotationEditorLayerBuilder } from "./annotation_editor_layer_builder.js";
|
||||||
@ -48,6 +45,7 @@ import { BasePDFPageView } from "./base_pdf_page_view.js";
|
|||||||
import { DrawLayerBuilder } from "./draw_layer_builder.js";
|
import { DrawLayerBuilder } from "./draw_layer_builder.js";
|
||||||
import { GenericL10n } from "web-null_l10n";
|
import { GenericL10n } from "web-null_l10n";
|
||||||
import { PDFPageDetailView } from "./pdf_page_detail_view.js";
|
import { PDFPageDetailView } from "./pdf_page_detail_view.js";
|
||||||
|
import { RenderingStates } from "./renderable_view.js";
|
||||||
import { SimpleLinkService } from "./pdf_link_service.js";
|
import { SimpleLinkService } from "./pdf_link_service.js";
|
||||||
import { StructTreeLayerBuilder } from "./struct_tree_layer_builder.js";
|
import { StructTreeLayerBuilder } from "./struct_tree_layer_builder.js";
|
||||||
import { TextAccessibilityManager } from "./text_accessibility.js";
|
import { TextAccessibilityManager } from "./text_accessibility.js";
|
||||||
@ -98,7 +96,7 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js";
|
|||||||
* @property {Object} [pageColors] - Overwrites background and foreground colors
|
* @property {Object} [pageColors] - Overwrites background and foreground colors
|
||||||
* with user defined ones in order to improve readability in high contrast
|
* with user defined ones in order to improve readability in high contrast
|
||||||
* mode.
|
* mode.
|
||||||
* @property {IL10n} [l10n] - Localization service.
|
* @property {L10n} [l10n] - Localization service.
|
||||||
* @property {Object} [layerProperties] - The object that is used to lookup
|
* @property {Object} [layerProperties] - The object that is used to lookup
|
||||||
* the necessary layer-properties.
|
* the necessary layer-properties.
|
||||||
* @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from
|
* @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from
|
||||||
@ -130,9 +128,6 @@ const LAYERS_ORDER = new Map([
|
|||||||
["xfaLayer", 3],
|
["xfaLayer", 3],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
|
||||||
* @implements {IRenderableView}
|
|
||||||
*/
|
|
||||||
class PDFPageView extends BasePDFPageView {
|
class PDFPageView extends BasePDFPageView {
|
||||||
#annotationMode = AnnotationMode.ENABLE_FORMS;
|
#annotationMode = AnnotationMode.ENABLE_FORMS;
|
||||||
|
|
||||||
|
|||||||
@ -13,16 +13,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
/** @typedef {import("./interfaces.js").IPDFPrintServiceFactory} IPDFPrintServiceFactory */
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnnotationMode,
|
AnnotationMode,
|
||||||
PixelsPerInch,
|
PixelsPerInch,
|
||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
shadow,
|
shadow,
|
||||||
} from "pdfjs-lib";
|
} from "pdfjs-lib";
|
||||||
import { getXfaHtmlForPrinting } from "./print_utils.js";
|
import {
|
||||||
|
BasePrintServiceFactory,
|
||||||
|
getXfaHtmlForPrinting,
|
||||||
|
} from "./print_utils.js";
|
||||||
|
|
||||||
let activeService = null;
|
let activeService = null;
|
||||||
let dialog = null;
|
let dialog = null;
|
||||||
@ -371,10 +371,7 @@ function ensureOverlay() {
|
|||||||
return overlayPromise;
|
return overlayPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
class PDFPrintServiceFactory extends BasePrintServiceFactory {
|
||||||
* @implements {IPDFPrintServiceFactory}
|
|
||||||
*/
|
|
||||||
class PDFPrintServiceFactory {
|
|
||||||
static initGlobals(app) {
|
static initGlobals(app) {
|
||||||
viewerApp = app;
|
viewerApp = app;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,13 +13,12 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("./interfaces").IRenderableView} IRenderableView */
|
|
||||||
/** @typedef {import("./pdf_viewer").PDFViewer} PDFViewer */
|
/** @typedef {import("./pdf_viewer").PDFViewer} PDFViewer */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./pdf_thumbnail_viewer").PDFThumbnailViewer} PDFThumbnailViewer */
|
/** @typedef {import("./pdf_thumbnail_viewer").PDFThumbnailViewer} PDFThumbnailViewer */
|
||||||
|
|
||||||
import { RenderingCancelledException } from "pdfjs-lib";
|
import { RenderingCancelledException } from "pdfjs-lib";
|
||||||
import { RenderingStates } from "./ui_utils.js";
|
import { RenderingStates } from "./renderable_view.js";
|
||||||
|
|
||||||
const CLEANUP_TIMEOUT = 30000;
|
const CLEANUP_TIMEOUT = 30000;
|
||||||
|
|
||||||
@ -59,7 +58,7 @@ class PDFRenderingQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {IRenderableView} view
|
* @param {RenderableView} view
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isHighestPriority(view) {
|
isHighestPriority(view) {
|
||||||
@ -183,7 +182,7 @@ class PDFRenderingQueue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {IRenderableView} view
|
* @param {RenderableView} view
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isViewFinished(view) {
|
isViewFinished(view) {
|
||||||
@ -195,7 +194,7 @@ class PDFRenderingQueue {
|
|||||||
* based on the views state. If the view is already rendered it will return
|
* based on the views state. If the view is already rendered it will return
|
||||||
* `false`.
|
* `false`.
|
||||||
*
|
*
|
||||||
* @param {IRenderableView} view
|
* @param {RenderableView} view
|
||||||
*/
|
*/
|
||||||
renderView(view) {
|
renderView(view) {
|
||||||
switch (view.renderingState) {
|
switch (view.renderingState) {
|
||||||
|
|||||||
@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
|
|
||||||
import { apiPageLayoutToViewerModes, RenderingStates } from "./ui_utils.js";
|
import { apiPageLayoutToViewerModes } from "./ui_utils.js";
|
||||||
|
import { RenderingStates } from "./renderable_view.js";
|
||||||
import { shadow } from "pdfjs-lib";
|
import { shadow } from "pdfjs-lib";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -18,8 +18,6 @@
|
|||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
|
||||||
/** @typedef {import("./interfaces").IRenderableView} IRenderableView */
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
||||||
|
|
||||||
@ -28,8 +26,8 @@ import {
|
|||||||
OutputScale,
|
OutputScale,
|
||||||
RenderingCancelledException,
|
RenderingCancelledException,
|
||||||
} from "pdfjs-lib";
|
} from "pdfjs-lib";
|
||||||
|
import { RenderableView, RenderingStates } from "./renderable_view.js";
|
||||||
import { AppOptions } from "./app_options.js";
|
import { AppOptions } from "./app_options.js";
|
||||||
import { RenderingStates } from "./ui_utils.js";
|
|
||||||
|
|
||||||
const DRAW_UPSCALE_FACTOR = 2; // See comment in `PDFThumbnailView.draw` below.
|
const DRAW_UPSCALE_FACTOR = 2; // See comment in `PDFThumbnailView.draw` below.
|
||||||
const MAX_NUM_SCALING_STEPS = 3;
|
const MAX_NUM_SCALING_STEPS = 3;
|
||||||
@ -44,7 +42,7 @@ const THUMBNAIL_WIDTH = 126; // px
|
|||||||
* @property {Promise<OptionalContentConfig>} [optionalContentConfigPromise] -
|
* @property {Promise<OptionalContentConfig>} [optionalContentConfigPromise] -
|
||||||
* A promise that is resolved with an {@link OptionalContentConfig} instance.
|
* A promise that is resolved with an {@link OptionalContentConfig} instance.
|
||||||
* The default value is `null`.
|
* The default value is `null`.
|
||||||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
* @property {PDFLinkService} linkService - The navigation/linking service.
|
||||||
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
|
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
|
||||||
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
||||||
* total pixels, i.e. width * height. Use `-1` for no limit, or `0` for
|
* total pixels, i.e. width * height. Use `-1` for no limit, or `0` for
|
||||||
@ -79,10 +77,9 @@ class TempImageFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
class PDFThumbnailView extends RenderableView {
|
||||||
* @implements {IRenderableView}
|
#renderingState = RenderingStates.INITIAL;
|
||||||
*/
|
|
||||||
class PDFThumbnailView {
|
|
||||||
/**
|
/**
|
||||||
* @param {PDFThumbnailViewOptions} options
|
* @param {PDFThumbnailViewOptions} options
|
||||||
*/
|
*/
|
||||||
@ -99,6 +96,7 @@ class PDFThumbnailView {
|
|||||||
pageColors,
|
pageColors,
|
||||||
enableSplitMerge = false,
|
enableSplitMerge = false,
|
||||||
}) {
|
}) {
|
||||||
|
super();
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.renderingId = `thumbnail${id}`;
|
this.renderingId = `thumbnail${id}`;
|
||||||
this.pageLabel = null;
|
this.pageLabel = null;
|
||||||
@ -116,9 +114,6 @@ class PDFThumbnailView {
|
|||||||
this.linkService = linkService;
|
this.linkService = linkService;
|
||||||
this.renderingQueue = renderingQueue;
|
this.renderingQueue = renderingQueue;
|
||||||
|
|
||||||
this.renderTask = null;
|
|
||||||
this.renderingState = RenderingStates.INITIAL;
|
|
||||||
this.resume = null;
|
|
||||||
this.placeholder = null;
|
this.placeholder = null;
|
||||||
|
|
||||||
const imageContainer = (this.div = document.createElement("div"));
|
const imageContainer = (this.div = document.createElement("div"));
|
||||||
@ -163,6 +158,14 @@ class PDFThumbnailView {
|
|||||||
this.image.style.height = `${canvasHeight}px`;
|
this.image.style.height = `${canvasHeight}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get renderingState() {
|
||||||
|
return this.#renderingState;
|
||||||
|
}
|
||||||
|
|
||||||
|
set renderingState(state) {
|
||||||
|
this.#renderingState = state;
|
||||||
|
}
|
||||||
|
|
||||||
setPdfPage(pdfPage) {
|
setPdfPage(pdfPage) {
|
||||||
this.pdfPage = pdfPage;
|
this.pdfPage = pdfPage;
|
||||||
this.pdfPageRotate = pdfPage.rotate;
|
this.pdfPageRotate = pdfPage.rotate;
|
||||||
|
|||||||
@ -16,7 +16,6 @@
|
|||||||
/** @typedef {import("../src/display/api").PDFDocumentProxy} PDFDocumentProxy */
|
/** @typedef {import("../src/display/api").PDFDocumentProxy} PDFDocumentProxy */
|
||||||
/** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */
|
/** @typedef {import("../src/display/api").PDFPageProxy} PDFPageProxy */
|
||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
/** @typedef {import("./pdf_rendering_queue").PDFRenderingQueue} PDFRenderingQueue */
|
||||||
|
|
||||||
@ -24,12 +23,12 @@ import {
|
|||||||
binarySearchFirstItem,
|
binarySearchFirstItem,
|
||||||
getVisibleElements,
|
getVisibleElements,
|
||||||
isValidRotation,
|
isValidRotation,
|
||||||
RenderingStates,
|
|
||||||
watchScroll,
|
watchScroll,
|
||||||
} from "./ui_utils.js";
|
} from "./ui_utils.js";
|
||||||
import { MathClamp, noContextMenu, PagesMapper, stopEvent } from "pdfjs-lib";
|
import { MathClamp, noContextMenu, PagesMapper, stopEvent } from "pdfjs-lib";
|
||||||
import { Menu } from "./menu.js";
|
import { Menu } from "./menu.js";
|
||||||
import { PDFThumbnailView } from "./pdf_thumbnail_view.js";
|
import { PDFThumbnailView } from "./pdf_thumbnail_view.js";
|
||||||
|
import { RenderingStates } from "./renderable_view.js";
|
||||||
|
|
||||||
const SCROLL_OPTIONS = {
|
const SCROLL_OPTIONS = {
|
||||||
behavior: "instant",
|
behavior: "instant",
|
||||||
@ -51,7 +50,7 @@ const SPACE_FOR_DRAG_MARKER_WHEN_NO_NEXT_ELEMENT = 15;
|
|||||||
* @property {HTMLDivElement} container - The container for the thumbnail
|
* @property {HTMLDivElement} container - The container for the thumbnail
|
||||||
* elements.
|
* elements.
|
||||||
* @property {EventBus} eventBus - The application event bus.
|
* @property {EventBus} eventBus - The application event bus.
|
||||||
* @property {IPDFLinkService} linkService - The navigation/linking service.
|
* @property {PDFLinkService} linkService - The navigation/linking service.
|
||||||
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
|
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
|
||||||
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
* @property {number} [maxCanvasPixels] - The maximum supported canvas size in
|
||||||
* total pixels, i.e. width * height. Use `-1` for no limit, or `0` for
|
* total pixels, i.e. width * height. Use `-1` for no limit, or `0` for
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
RenderingStates,
|
|
||||||
ScrollMode,
|
ScrollMode,
|
||||||
SpreadMode,
|
SpreadMode,
|
||||||
} from "./ui_utils.js";
|
} from "./ui_utils.js";
|
||||||
@ -35,6 +34,7 @@ import { PDFPageView } from "./pdf_page_view.js";
|
|||||||
import { PDFScriptingManager } from "./pdf_scripting_manager.component.js";
|
import { PDFScriptingManager } from "./pdf_scripting_manager.component.js";
|
||||||
import { PDFSinglePageViewer } from "./pdf_single_page_viewer.js";
|
import { PDFSinglePageViewer } from "./pdf_single_page_viewer.js";
|
||||||
import { PDFViewer } from "./pdf_viewer.js";
|
import { PDFViewer } from "./pdf_viewer.js";
|
||||||
|
import { RenderingStates } from "./renderable_view.js";
|
||||||
import { StructTreeLayerBuilder } from "./struct_tree_layer_builder.js";
|
import { StructTreeLayerBuilder } from "./struct_tree_layer_builder.js";
|
||||||
import { TextLayerBuilder } from "./text_layer_builder.js";
|
import { TextLayerBuilder } from "./text_layer_builder.js";
|
||||||
import { XfaLayerBuilder } from "./xfa_layer_builder.js";
|
import { XfaLayerBuilder } from "./xfa_layer_builder.js";
|
||||||
|
|||||||
@ -20,9 +20,6 @@
|
|||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig */
|
/** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig */
|
||||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces").IDownloadManager} IDownloadManager */
|
|
||||||
/** @typedef {import("./interfaces").IL10n} IL10n */
|
|
||||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("./pdf_find_controller").PDFFindController} PDFFindController */
|
/** @typedef {import("./pdf_find_controller").PDFFindController} PDFFindController */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
@ -55,7 +52,6 @@ import {
|
|||||||
MIN_SCALE,
|
MIN_SCALE,
|
||||||
PresentationModeState,
|
PresentationModeState,
|
||||||
removeNullCharacters,
|
removeNullCharacters,
|
||||||
RenderingStates,
|
|
||||||
SCROLLBAR_PADDING,
|
SCROLLBAR_PADDING,
|
||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
ScrollMode,
|
ScrollMode,
|
||||||
@ -68,6 +64,7 @@ import {
|
|||||||
import { GenericL10n } from "web-null_l10n";
|
import { GenericL10n } from "web-null_l10n";
|
||||||
import { PDFPageView } from "./pdf_page_view.js";
|
import { PDFPageView } from "./pdf_page_view.js";
|
||||||
import { PDFRenderingQueue } from "./pdf_rendering_queue.js";
|
import { PDFRenderingQueue } from "./pdf_rendering_queue.js";
|
||||||
|
import { RenderingStates } from "./renderable_view.js";
|
||||||
import { SimpleLinkService } from "./pdf_link_service.js";
|
import { SimpleLinkService } from "./pdf_link_service.js";
|
||||||
|
|
||||||
const DEFAULT_CACHE_SIZE = 10;
|
const DEFAULT_CACHE_SIZE = 10;
|
||||||
@ -90,8 +87,8 @@ function isValidAnnotationEditorMode(mode) {
|
|||||||
* @property {HTMLDivElement} container - The container for the viewer element.
|
* @property {HTMLDivElement} container - The container for the viewer element.
|
||||||
* @property {HTMLDivElement} [viewer] - The viewer element.
|
* @property {HTMLDivElement} [viewer] - The viewer element.
|
||||||
* @property {EventBus} eventBus - The application event bus.
|
* @property {EventBus} eventBus - The application event bus.
|
||||||
* @property {IPDFLinkService} [linkService] - The navigation/linking service.
|
* @property {PDFLinkService} [linkService] - The navigation/linking service.
|
||||||
* @property {IDownloadManager} [downloadManager] - The download manager
|
* @property {BaseDownloadManager} [downloadManager] - The download manager
|
||||||
* component.
|
* component.
|
||||||
* @property {PDFFindController} [findController] - The find controller
|
* @property {PDFFindController} [findController] - The find controller
|
||||||
* component.
|
* component.
|
||||||
@ -135,7 +132,7 @@ function isValidAnnotationEditorMode(mode) {
|
|||||||
* rendering will keep track of which areas of the page each PDF operation
|
* rendering will keep track of which areas of the page each PDF operation
|
||||||
* affects. Then, when rendering a partial page (if `enableDetailCanvas` is
|
* affects. Then, when rendering a partial page (if `enableDetailCanvas` is
|
||||||
* enabled), it will only run through the operations that affect that portion.
|
* enabled), it will only run through the operations that affect that portion.
|
||||||
* @property {IL10n} [l10n] - Localization service.
|
* @property {L10n} [l10n] - Localization service.
|
||||||
* @property {boolean} [enablePermissions] - Enables PDF document permissions,
|
* @property {boolean} [enablePermissions] - Enables PDF document permissions,
|
||||||
* when they exist. The default value is `false`.
|
* when they exist. The default value is `false`.
|
||||||
* @property {Object} [pageColors] - Overwrites background and foreground colors
|
* @property {Object} [pageColors] - Overwrites background and foreground colors
|
||||||
|
|||||||
@ -17,6 +17,24 @@ import { getXfaPageViewport, PixelsPerInch } from "pdfjs-lib";
|
|||||||
import { SimpleLinkService } from "./pdf_link_service.js";
|
import { SimpleLinkService } from "./pdf_link_service.js";
|
||||||
import { XfaLayerBuilder } from "./xfa_layer_builder.js";
|
import { XfaLayerBuilder } from "./xfa_layer_builder.js";
|
||||||
|
|
||||||
|
class BasePrintServiceFactory {
|
||||||
|
constructor() {
|
||||||
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
||||||
|
throw new Error("Cannot initialize BasePrintServiceFactory.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static initGlobals(app) {}
|
||||||
|
|
||||||
|
static get supportsPrinting() {
|
||||||
|
throw new Error("Not implemented: supportsPrinting");
|
||||||
|
}
|
||||||
|
|
||||||
|
static createPrintService(params) {
|
||||||
|
throw new Error("Not implemented: createPrintService");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getXfaHtmlForPrinting(printContainer, pdfDocument) {
|
function getXfaHtmlForPrinting(printContainer, pdfDocument) {
|
||||||
const xfaHtml = pdfDocument.allXfaHtml;
|
const xfaHtml = pdfDocument.allXfaHtml;
|
||||||
const linkService = new SimpleLinkService();
|
const linkService = new SimpleLinkService();
|
||||||
@ -40,4 +58,4 @@ function getXfaHtmlForPrinting(printContainer, pdfDocument) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getXfaHtmlForPrinting };
|
export { BasePrintServiceFactory, getXfaHtmlForPrinting };
|
||||||
|
|||||||
71
web/renderable_view.js
Normal file
71
web/renderable_view.js
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/* Copyright 2018 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const RenderingStates = {
|
||||||
|
INITIAL: 0,
|
||||||
|
RUNNING: 1,
|
||||||
|
PAUSED: 2,
|
||||||
|
FINISHED: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
class RenderableView {
|
||||||
|
/**
|
||||||
|
* Unique ID for rendering queue.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
renderingId = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {RenderTask | null}
|
||||||
|
*/
|
||||||
|
renderTask = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {function | null}
|
||||||
|
*/
|
||||||
|
resume = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (
|
||||||
|
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||||
|
this.constructor === RenderableView
|
||||||
|
) {
|
||||||
|
throw new Error("Cannot initialize RenderableView.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {RenderingStates}
|
||||||
|
*/
|
||||||
|
get renderingState() {
|
||||||
|
throw new Error("Abstract getter `renderingState` accessed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {RenderingStates}
|
||||||
|
*/
|
||||||
|
set renderingState(state) {
|
||||||
|
throw new Error("Abstract setter `renderingState` accessed");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise} Resolved on draw completion.
|
||||||
|
*/
|
||||||
|
async draw() {
|
||||||
|
throw new Error("Not implemented: draw");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { RenderableView, RenderingStates };
|
||||||
@ -25,13 +25,6 @@ const MAX_AUTO_SCALE = 1.25;
|
|||||||
const SCROLLBAR_PADDING = 40;
|
const SCROLLBAR_PADDING = 40;
|
||||||
const VERTICAL_PADDING = 5;
|
const VERTICAL_PADDING = 5;
|
||||||
|
|
||||||
const RenderingStates = {
|
|
||||||
INITIAL: 0,
|
|
||||||
RUNNING: 1,
|
|
||||||
PAUSED: 2,
|
|
||||||
FINISHED: 3,
|
|
||||||
};
|
|
||||||
|
|
||||||
const PresentationModeState = {
|
const PresentationModeState = {
|
||||||
UNKNOWN: 0,
|
UNKNOWN: 0,
|
||||||
NORMAL: 1,
|
NORMAL: 1,
|
||||||
@ -709,7 +702,7 @@ class ProgressBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set percent(val) {
|
set percent(val) {
|
||||||
this.#percent = MathClamp(val, 0, 100);
|
this.#percent = val;
|
||||||
|
|
||||||
if (isNaN(val)) {
|
if (isNaN(val)) {
|
||||||
this.#classList.add("indeterminate");
|
this.#classList.add("indeterminate");
|
||||||
@ -914,7 +907,6 @@ export {
|
|||||||
PresentationModeState,
|
PresentationModeState,
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
removeNullCharacters,
|
removeNullCharacters,
|
||||||
RenderingStates,
|
|
||||||
SCROLLBAR_PADDING,
|
SCROLLBAR_PADDING,
|
||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
ScrollMode,
|
ScrollMode,
|
||||||
|
|||||||
@ -13,10 +13,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RenderingStates, ScrollMode, SpreadMode } from "./ui_utils.js";
|
import { ScrollMode, SpreadMode } from "./ui_utils.js";
|
||||||
import { AppOptions } from "./app_options.js";
|
import { AppOptions } from "./app_options.js";
|
||||||
import { LinkTarget } from "./pdf_link_service.js";
|
import { LinkTarget } from "./pdf_link_service.js";
|
||||||
import { PDFViewerApplication } from "./app.js";
|
import { PDFViewerApplication } from "./app.js";
|
||||||
|
import { RenderingStates } from "./renderable_view.js";
|
||||||
|
|
||||||
const AppConstants =
|
const AppConstants =
|
||||||
typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")
|
typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")
|
||||||
|
|||||||
@ -13,10 +13,11 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { RenderingStates, ScrollMode, SpreadMode } from "./ui_utils.js";
|
import { ScrollMode, SpreadMode } from "./ui_utils.js";
|
||||||
import { AppOptions } from "./app_options.js";
|
import { AppOptions } from "./app_options.js";
|
||||||
import { LinkTarget } from "./pdf_link_service.js";
|
import { LinkTarget } from "./pdf_link_service.js";
|
||||||
import { PDFViewerApplication } from "./app.js";
|
import { PDFViewerApplication } from "./app.js";
|
||||||
|
import { RenderingStates } from "./renderable_view.js";
|
||||||
|
|
||||||
const AppConstants =
|
const AppConstants =
|
||||||
typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")
|
typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/** @typedef {import("./event_utils.js").EventBus} EventBus */
|
/** @typedef {import("./event_utils.js").EventBus} EventBus */
|
||||||
/** @typedef {import("./interfaces.js").IL10n} IL10n */
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
docStyle,
|
docStyle,
|
||||||
@ -34,7 +33,7 @@ const UI_NOTIFICATION_CLASS = "pdfSidebarNotification";
|
|||||||
* @typedef {Object} PDFSidebarOptions
|
* @typedef {Object} PDFSidebarOptions
|
||||||
* @property {PDFSidebarElements} elements - The DOM elements.
|
* @property {PDFSidebarElements} elements - The DOM elements.
|
||||||
* @property {EventBus} eventBus - The application event bus.
|
* @property {EventBus} eventBus - The application event bus.
|
||||||
* @property {IL10n} l10n - The localization service.
|
* @property {L10n} l10n - The localization service.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
/** @typedef {import("../src/display/annotation_storage").AnnotationStorage} AnnotationStorage */
|
/** @typedef {import("../src/display/annotation_storage").AnnotationStorage} AnnotationStorage */
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
||||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
|
||||||
|
|
||||||
import { XfaLayer } from "pdfjs-lib";
|
import { XfaLayer } from "pdfjs-lib";
|
||||||
|
|
||||||
@ -26,7 +25,7 @@ import { XfaLayer } from "pdfjs-lib";
|
|||||||
* @typedef {Object} XfaLayerBuilderOptions
|
* @typedef {Object} XfaLayerBuilderOptions
|
||||||
* @property {PDFPageProxy} pdfPage
|
* @property {PDFPageProxy} pdfPage
|
||||||
* @property {AnnotationStorage} [annotationStorage]
|
* @property {AnnotationStorage} [annotationStorage]
|
||||||
* @property {IPDFLinkService} linkService
|
* @property {PDFLinkService} linkService
|
||||||
* @property {Object} [xfaHtml]
|
* @property {Object} [xfaHtml]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user