Merge pull request #19962 from Snuffleupagus/api_utils
Move a few helper functions/classes out of the `src/display/api.js` file
This commit is contained in:
commit
21ef454faf
@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
_isValidExplicitDest,
|
|
||||||
AbortException,
|
AbortException,
|
||||||
AnnotationMode,
|
AnnotationMode,
|
||||||
assert,
|
assert,
|
||||||
@ -29,7 +28,6 @@ import {
|
|||||||
RenderingIntentFlag,
|
RenderingIntentFlag,
|
||||||
setVerbosityLevel,
|
setVerbosityLevel,
|
||||||
shadow,
|
shadow,
|
||||||
stringToBytes,
|
|
||||||
unreachable,
|
unreachable,
|
||||||
warn,
|
warn,
|
||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
@ -47,6 +45,13 @@ import {
|
|||||||
StatTimer,
|
StatTimer,
|
||||||
} from "./display_utils.js";
|
} from "./display_utils.js";
|
||||||
import { FontFaceObject, FontLoader } from "./font_loader.js";
|
import { FontFaceObject, FontLoader } from "./font_loader.js";
|
||||||
|
import {
|
||||||
|
getDataProp,
|
||||||
|
getFactoryUrlProp,
|
||||||
|
getUrlProp,
|
||||||
|
isRefProxy,
|
||||||
|
LoopbackPort,
|
||||||
|
} from "./api_utils.js";
|
||||||
import { MessageHandler, wrapReason } from "../shared/message_handler.js";
|
import { MessageHandler, wrapReason } from "../shared/message_handler.js";
|
||||||
import {
|
import {
|
||||||
NodeCanvasFactory,
|
NodeCanvasFactory,
|
||||||
@ -68,10 +73,10 @@ import { PDFDataTransportStream } from "./transport_stream.js";
|
|||||||
import { PDFFetchStream } from "display-fetch_stream";
|
import { PDFFetchStream } from "display-fetch_stream";
|
||||||
import { PDFNetworkStream } from "display-network";
|
import { PDFNetworkStream } from "display-network";
|
||||||
import { PDFNodeStream } from "display-node_stream";
|
import { PDFNodeStream } from "display-node_stream";
|
||||||
|
import { PDFObjects } from "./pdf_objects.js";
|
||||||
import { TextLayer } from "./text_layer.js";
|
import { TextLayer } from "./text_layer.js";
|
||||||
import { XfaText } from "./xfa_text.js";
|
import { XfaText } from "./xfa_text.js";
|
||||||
|
|
||||||
const DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536
|
|
||||||
const RENDERING_CANCELLED_TIMEOUT = 100; // ms
|
const RENDERING_CANCELLED_TIMEOUT = 100; // ms
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,7 +116,7 @@ const RENDERING_CANCELLED_TIMEOUT = 100; // ms
|
|||||||
* @property {PDFDataRangeTransport} [range] - Allows for using a custom range
|
* @property {PDFDataRangeTransport} [range] - Allows for using a custom range
|
||||||
* transport implementation.
|
* transport implementation.
|
||||||
* @property {number} [rangeChunkSize] - Specify maximum number of bytes fetched
|
* @property {number} [rangeChunkSize] - Specify maximum number of bytes fetched
|
||||||
* per range request. The default value is {@link DEFAULT_RANGE_CHUNK_SIZE}.
|
* per range request. The default value is 65536 (= 2^16).
|
||||||
* @property {PDFWorker} [worker] - The worker that will be used for loading and
|
* @property {PDFWorker} [worker] - The worker that will be used for loading and
|
||||||
* parsing the PDF data.
|
* parsing the PDF data.
|
||||||
* @property {number} [verbosity] - Controls the logging level; the constants
|
* @property {number} [verbosity] - Controls the logging level; the constants
|
||||||
@ -255,7 +260,7 @@ function getDocument(src = {}) {
|
|||||||
const rangeChunkSize =
|
const rangeChunkSize =
|
||||||
Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0
|
Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0
|
||||||
? src.rangeChunkSize
|
? src.rangeChunkSize
|
||||||
: DEFAULT_RANGE_CHUNK_SIZE;
|
: 2 ** 16;
|
||||||
let worker = src.worker instanceof PDFWorker ? src.worker : null;
|
let worker = src.worker instanceof PDFWorker ? src.worker : null;
|
||||||
const verbosity = src.verbosity;
|
const verbosity = src.verbosity;
|
||||||
// Ignore "data:"-URLs, since they can't be used to recover valid absolute
|
// Ignore "data:"-URLs, since they can't be used to recover valid absolute
|
||||||
@ -507,94 +512,6 @@ function getDocument(src = {}) {
|
|||||||
return task;
|
return task;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUrlProp(val) {
|
|
||||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
|
||||||
return null; // The 'url' is unused with `PDFDataRangeTransport`.
|
|
||||||
}
|
|
||||||
if (val instanceof URL) {
|
|
||||||
return val.href;
|
|
||||||
}
|
|
||||||
if (typeof val === "string") {
|
|
||||||
if (
|
|
||||||
typeof PDFJSDev !== "undefined" &&
|
|
||||||
PDFJSDev.test("GENERIC") &&
|
|
||||||
isNodeJS
|
|
||||||
) {
|
|
||||||
return val; // Use the url as-is in Node.js environments.
|
|
||||||
}
|
|
||||||
|
|
||||||
// The full path is required in the 'url' field.
|
|
||||||
const url = URL.parse(val, window.location);
|
|
||||||
if (url) {
|
|
||||||
return url.href;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(
|
|
||||||
"Invalid PDF url data: " +
|
|
||||||
"either string or URL-object is expected in the url property."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDataProp(val) {
|
|
||||||
// Converting string or array-like data to Uint8Array.
|
|
||||||
if (
|
|
||||||
typeof PDFJSDev !== "undefined" &&
|
|
||||||
PDFJSDev.test("GENERIC") &&
|
|
||||||
isNodeJS &&
|
|
||||||
typeof Buffer !== "undefined" && // eslint-disable-line no-undef
|
|
||||||
val instanceof Buffer // eslint-disable-line no-undef
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
"Please provide binary data as `Uint8Array`, rather than `Buffer`."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) {
|
|
||||||
// Use the data as-is when it's already a Uint8Array that completely
|
|
||||||
// "utilizes" its underlying ArrayBuffer, to prevent any possible
|
|
||||||
// issues when transferring it to the worker-thread.
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
if (typeof val === "string") {
|
|
||||||
return stringToBytes(val);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
val instanceof ArrayBuffer ||
|
|
||||||
ArrayBuffer.isView(val) ||
|
|
||||||
(typeof val === "object" && !isNaN(val?.length))
|
|
||||||
) {
|
|
||||||
return new Uint8Array(val);
|
|
||||||
}
|
|
||||||
throw new Error(
|
|
||||||
"Invalid PDF binary data: either TypedArray, " +
|
|
||||||
"string, or array-like object is expected in the data property."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFactoryUrlProp(val) {
|
|
||||||
if (typeof val !== "string") {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (val.endsWith("/")) {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
throw new Error(`Invalid factory url: "${val}" must include trailing slash.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isRefProxy = v =>
|
|
||||||
typeof v === "object" &&
|
|
||||||
Number.isInteger(v?.num) &&
|
|
||||||
v.num >= 0 &&
|
|
||||||
Number.isInteger(v?.gen) &&
|
|
||||||
v.gen >= 0;
|
|
||||||
|
|
||||||
const isNameProxy = v => typeof v === "object" && typeof v?.name === "string";
|
|
||||||
|
|
||||||
const isValidExplicitDest = _isValidExplicitDest.bind(
|
|
||||||
null,
|
|
||||||
/* validRef = */ isRefProxy,
|
|
||||||
/* validName = */ isNameProxy
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} OnProgressParameters
|
* @typedef {Object} OnProgressParameters
|
||||||
* @property {number} loaded - Currently loaded number of bytes.
|
* @property {number} loaded - Currently loaded number of bytes.
|
||||||
@ -2012,54 +1929,6 @@ class PDFPageProxy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoopbackPort {
|
|
||||||
#listeners = new Map();
|
|
||||||
|
|
||||||
#deferred = Promise.resolve();
|
|
||||||
|
|
||||||
postMessage(obj, transfer) {
|
|
||||||
const event = {
|
|
||||||
data: structuredClone(obj, transfer ? { transfer } : null),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.#deferred.then(() => {
|
|
||||||
for (const [listener] of this.#listeners) {
|
|
||||||
listener.call(this, event);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
addEventListener(name, listener, options = null) {
|
|
||||||
let rmAbort = null;
|
|
||||||
if (options?.signal instanceof AbortSignal) {
|
|
||||||
const { signal } = options;
|
|
||||||
if (signal.aborted) {
|
|
||||||
warn("LoopbackPort - cannot use an `aborted` signal.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const onAbort = () => this.removeEventListener(name, listener);
|
|
||||||
rmAbort = () => signal.removeEventListener("abort", onAbort);
|
|
||||||
|
|
||||||
signal.addEventListener("abort", onAbort);
|
|
||||||
}
|
|
||||||
this.#listeners.set(listener, rmAbort);
|
|
||||||
}
|
|
||||||
|
|
||||||
removeEventListener(name, listener) {
|
|
||||||
const rmAbort = this.#listeners.get(listener);
|
|
||||||
rmAbort?.();
|
|
||||||
|
|
||||||
this.#listeners.delete(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
terminate() {
|
|
||||||
for (const [, rmAbort] of this.#listeners) {
|
|
||||||
rmAbort?.();
|
|
||||||
}
|
|
||||||
this.#listeners.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} PDFWorkerParameters
|
* @typedef {Object} PDFWorkerParameters
|
||||||
* @property {string} [name] - The name of the worker.
|
* @property {string} [name] - The name of the worker.
|
||||||
@ -3142,115 +3011,6 @@ class WorkerTransport {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const INITIAL_DATA = Symbol("INITIAL_DATA");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A PDF document and page is built of many objects. E.g. there are objects for
|
|
||||||
* fonts, images, rendering code, etc. These objects may get processed inside of
|
|
||||||
* a worker. This class implements some basic methods to manage these objects.
|
|
||||||
*/
|
|
||||||
class PDFObjects {
|
|
||||||
#objs = Object.create(null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures there is an object defined for `objId`.
|
|
||||||
*
|
|
||||||
* @param {string} objId
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
#ensureObj(objId) {
|
|
||||||
return (this.#objs[objId] ||= {
|
|
||||||
...Promise.withResolvers(),
|
|
||||||
data: INITIAL_DATA,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If called *without* callback, this returns the data of `objId` but the
|
|
||||||
* object needs to be resolved. If it isn't, this method throws.
|
|
||||||
*
|
|
||||||
* If called *with* a callback, the callback is called with the data of the
|
|
||||||
* object once the object is resolved. That means, if you call this method
|
|
||||||
* and the object is already resolved, the callback gets called right away.
|
|
||||||
*
|
|
||||||
* @param {string} objId
|
|
||||||
* @param {function} [callback]
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
get(objId, callback = null) {
|
|
||||||
// If there is a callback, then the get can be async and the object is
|
|
||||||
// not required to be resolved right now.
|
|
||||||
if (callback) {
|
|
||||||
const obj = this.#ensureObj(objId);
|
|
||||||
obj.promise.then(() => callback(obj.data));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// If there isn't a callback, the user expects to get the resolved data
|
|
||||||
// directly.
|
|
||||||
const obj = this.#objs[objId];
|
|
||||||
// If there isn't an object yet or the object isn't resolved, then the
|
|
||||||
// data isn't ready yet!
|
|
||||||
if (!obj || obj.data === INITIAL_DATA) {
|
|
||||||
throw new Error(`Requesting object that isn't resolved yet ${objId}.`);
|
|
||||||
}
|
|
||||||
return obj.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} objId
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
has(objId) {
|
|
||||||
const obj = this.#objs[objId];
|
|
||||||
return !!obj && obj.data !== INITIAL_DATA;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} objId
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
delete(objId) {
|
|
||||||
const obj = this.#objs[objId];
|
|
||||||
if (!obj || obj.data === INITIAL_DATA) {
|
|
||||||
// Only allow removing the object *after* it's been resolved.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
delete this.#objs[objId];
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the object `objId` with optional `data`.
|
|
||||||
*
|
|
||||||
* @param {string} objId
|
|
||||||
* @param {any} [data]
|
|
||||||
*/
|
|
||||||
resolve(objId, data = null) {
|
|
||||||
const obj = this.#ensureObj(objId);
|
|
||||||
obj.data = data;
|
|
||||||
obj.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
for (const objId in this.#objs) {
|
|
||||||
const { data } = this.#objs[objId];
|
|
||||||
data?.bitmap?.close(); // Release any `ImageBitmap` data.
|
|
||||||
}
|
|
||||||
this.#objs = Object.create(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
*[Symbol.iterator]() {
|
|
||||||
for (const objId in this.#objs) {
|
|
||||||
const { data } = this.#objs[objId];
|
|
||||||
|
|
||||||
if (data === INITIAL_DATA) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
yield [objId, data];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows controlling of the rendering tasks.
|
* Allows controlling of the rendering tasks.
|
||||||
*/
|
*/
|
||||||
@ -3511,8 +3271,6 @@ const build =
|
|||||||
export {
|
export {
|
||||||
build,
|
build,
|
||||||
getDocument,
|
getDocument,
|
||||||
isValidExplicitDest,
|
|
||||||
LoopbackPort,
|
|
||||||
PDFDataRangeTransport,
|
PDFDataRangeTransport,
|
||||||
PDFDocumentLoadingTask,
|
PDFDocumentLoadingTask,
|
||||||
PDFDocumentProxy,
|
PDFDocumentProxy,
|
||||||
|
|||||||
167
src/display/api_utils.js
Normal file
167
src/display/api_utils.js
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
/* Copyright 2012 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 {
|
||||||
|
_isValidExplicitDest,
|
||||||
|
isNodeJS,
|
||||||
|
stringToBytes,
|
||||||
|
warn,
|
||||||
|
} from "../shared/util.js";
|
||||||
|
|
||||||
|
function getUrlProp(val) {
|
||||||
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||||
|
return null; // The 'url' is unused with `PDFDataRangeTransport`.
|
||||||
|
}
|
||||||
|
if (val instanceof URL) {
|
||||||
|
return val.href;
|
||||||
|
}
|
||||||
|
if (typeof val === "string") {
|
||||||
|
if (
|
||||||
|
typeof PDFJSDev !== "undefined" &&
|
||||||
|
PDFJSDev.test("GENERIC") &&
|
||||||
|
isNodeJS
|
||||||
|
) {
|
||||||
|
return val; // Use the url as-is in Node.js environments.
|
||||||
|
}
|
||||||
|
|
||||||
|
// The full path is required in the 'url' field.
|
||||||
|
const url = URL.parse(val, window.location);
|
||||||
|
if (url) {
|
||||||
|
return url.href;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
"Invalid PDF url data: " +
|
||||||
|
"either string or URL-object is expected in the url property."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDataProp(val) {
|
||||||
|
// Converting string or array-like data to Uint8Array.
|
||||||
|
if (
|
||||||
|
typeof PDFJSDev !== "undefined" &&
|
||||||
|
PDFJSDev.test("GENERIC") &&
|
||||||
|
isNodeJS &&
|
||||||
|
typeof Buffer !== "undefined" && // eslint-disable-line no-undef
|
||||||
|
val instanceof Buffer // eslint-disable-line no-undef
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
"Please provide binary data as `Uint8Array`, rather than `Buffer`."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) {
|
||||||
|
// Use the data as-is when it's already a Uint8Array that completely
|
||||||
|
// "utilizes" its underlying ArrayBuffer, to prevent any possible
|
||||||
|
// issues when transferring it to the worker-thread.
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
if (typeof val === "string") {
|
||||||
|
return stringToBytes(val);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
val instanceof ArrayBuffer ||
|
||||||
|
ArrayBuffer.isView(val) ||
|
||||||
|
(typeof val === "object" && !isNaN(val?.length))
|
||||||
|
) {
|
||||||
|
return new Uint8Array(val);
|
||||||
|
}
|
||||||
|
throw new Error(
|
||||||
|
"Invalid PDF binary data: either TypedArray, " +
|
||||||
|
"string, or array-like object is expected in the data property."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFactoryUrlProp(val) {
|
||||||
|
if (typeof val !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (val.endsWith("/")) {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
throw new Error(`Invalid factory url: "${val}" must include trailing slash.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRefProxy = v =>
|
||||||
|
typeof v === "object" &&
|
||||||
|
Number.isInteger(v?.num) &&
|
||||||
|
v.num >= 0 &&
|
||||||
|
Number.isInteger(v?.gen) &&
|
||||||
|
v.gen >= 0;
|
||||||
|
|
||||||
|
const isNameProxy = v => typeof v === "object" && typeof v?.name === "string";
|
||||||
|
|
||||||
|
const isValidExplicitDest = _isValidExplicitDest.bind(
|
||||||
|
null,
|
||||||
|
/* validRef = */ isRefProxy,
|
||||||
|
/* validName = */ isNameProxy
|
||||||
|
);
|
||||||
|
|
||||||
|
class LoopbackPort {
|
||||||
|
#listeners = new Map();
|
||||||
|
|
||||||
|
#deferred = Promise.resolve();
|
||||||
|
|
||||||
|
postMessage(obj, transfer) {
|
||||||
|
const event = {
|
||||||
|
data: structuredClone(obj, transfer ? { transfer } : null),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.#deferred.then(() => {
|
||||||
|
for (const [listener] of this.#listeners) {
|
||||||
|
listener.call(this, event);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener(name, listener, options = null) {
|
||||||
|
let rmAbort = null;
|
||||||
|
if (options?.signal instanceof AbortSignal) {
|
||||||
|
const { signal } = options;
|
||||||
|
if (signal.aborted) {
|
||||||
|
warn("LoopbackPort - cannot use an `aborted` signal.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const onAbort = () => this.removeEventListener(name, listener);
|
||||||
|
rmAbort = () => signal.removeEventListener("abort", onAbort);
|
||||||
|
|
||||||
|
signal.addEventListener("abort", onAbort);
|
||||||
|
}
|
||||||
|
this.#listeners.set(listener, rmAbort);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeEventListener(name, listener) {
|
||||||
|
const rmAbort = this.#listeners.get(listener);
|
||||||
|
rmAbort?.();
|
||||||
|
|
||||||
|
this.#listeners.delete(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
terminate() {
|
||||||
|
for (const [, rmAbort] of this.#listeners) {
|
||||||
|
rmAbort?.();
|
||||||
|
}
|
||||||
|
this.#listeners.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getDataProp,
|
||||||
|
getFactoryUrlProp,
|
||||||
|
getUrlProp,
|
||||||
|
isNameProxy,
|
||||||
|
isRefProxy,
|
||||||
|
isValidExplicitDest,
|
||||||
|
LoopbackPort,
|
||||||
|
};
|
||||||
125
src/display/pdf_objects.js
Normal file
125
src/display/pdf_objects.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/* Copyright 2012 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 INITIAL_DATA = Symbol("INITIAL_DATA");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A PDF document and page is built of many objects. E.g. there are objects for
|
||||||
|
* fonts, images, rendering code, etc. These objects may get processed inside of
|
||||||
|
* a worker. This class implements some basic methods to manage these objects.
|
||||||
|
*/
|
||||||
|
class PDFObjects {
|
||||||
|
#objs = Object.create(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures there is an object defined for `objId`.
|
||||||
|
*
|
||||||
|
* @param {string} objId
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
#ensureObj(objId) {
|
||||||
|
return (this.#objs[objId] ||= {
|
||||||
|
...Promise.withResolvers(),
|
||||||
|
data: INITIAL_DATA,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If called *without* callback, this returns the data of `objId` but the
|
||||||
|
* object needs to be resolved. If it isn't, this method throws.
|
||||||
|
*
|
||||||
|
* If called *with* a callback, the callback is called with the data of the
|
||||||
|
* object once the object is resolved. That means, if you call this method
|
||||||
|
* and the object is already resolved, the callback gets called right away.
|
||||||
|
*
|
||||||
|
* @param {string} objId
|
||||||
|
* @param {function} [callback]
|
||||||
|
* @returns {any}
|
||||||
|
*/
|
||||||
|
get(objId, callback = null) {
|
||||||
|
// If there is a callback, then the get can be async and the object is
|
||||||
|
// not required to be resolved right now.
|
||||||
|
if (callback) {
|
||||||
|
const obj = this.#ensureObj(objId);
|
||||||
|
obj.promise.then(() => callback(obj.data));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// If there isn't a callback, the user expects to get the resolved data
|
||||||
|
// directly.
|
||||||
|
const obj = this.#objs[objId];
|
||||||
|
// If there isn't an object yet or the object isn't resolved, then the
|
||||||
|
// data isn't ready yet!
|
||||||
|
if (!obj || obj.data === INITIAL_DATA) {
|
||||||
|
throw new Error(`Requesting object that isn't resolved yet ${objId}.`);
|
||||||
|
}
|
||||||
|
return obj.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} objId
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
has(objId) {
|
||||||
|
const obj = this.#objs[objId];
|
||||||
|
return !!obj && obj.data !== INITIAL_DATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} objId
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
delete(objId) {
|
||||||
|
const obj = this.#objs[objId];
|
||||||
|
if (!obj || obj.data === INITIAL_DATA) {
|
||||||
|
// Only allow removing the object *after* it's been resolved.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delete this.#objs[objId];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves the object `objId` with optional `data`.
|
||||||
|
*
|
||||||
|
* @param {string} objId
|
||||||
|
* @param {any} [data]
|
||||||
|
*/
|
||||||
|
resolve(objId, data = null) {
|
||||||
|
const obj = this.#ensureObj(objId);
|
||||||
|
obj.data = data;
|
||||||
|
obj.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
for (const objId in this.#objs) {
|
||||||
|
const { data } = this.#objs[objId];
|
||||||
|
data?.bitmap?.close(); // Release any `ImageBitmap` data.
|
||||||
|
}
|
||||||
|
this.#objs = Object.create(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
*[Symbol.iterator]() {
|
||||||
|
for (const objId in this.#objs) {
|
||||||
|
const { data } = this.#objs[objId];
|
||||||
|
|
||||||
|
if (data === INITIAL_DATA) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
yield [objId, data];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { PDFObjects };
|
||||||
@ -47,7 +47,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
build,
|
build,
|
||||||
getDocument,
|
getDocument,
|
||||||
isValidExplicitDest,
|
|
||||||
PDFDataRangeTransport,
|
PDFDataRangeTransport,
|
||||||
PDFWorker,
|
PDFWorker,
|
||||||
version,
|
version,
|
||||||
@ -76,6 +75,7 @@ import { DOMSVGFactory } from "./display/svg_factory.js";
|
|||||||
import { DrawLayer } from "./display/draw_layer.js";
|
import { DrawLayer } from "./display/draw_layer.js";
|
||||||
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
||||||
import { HighlightOutliner } from "./display/editor/drawers/highlight.js";
|
import { HighlightOutliner } from "./display/editor/drawers/highlight.js";
|
||||||
|
import { isValidExplicitDest } from "./display/api_utils.js";
|
||||||
import { SignatureExtractor } from "./display/editor/drawers/signaturedraw.js";
|
import { SignatureExtractor } from "./display/editor/drawers/signaturedraw.js";
|
||||||
import { TextLayer } from "./display/text_layer.js";
|
import { TextLayer } from "./display/text_layer.js";
|
||||||
import { TouchManager } from "./display/touch_manager.js";
|
import { TouchManager } from "./display/touch_manager.js";
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import {
|
|||||||
AbortException,
|
AbortException,
|
||||||
UnknownErrorException,
|
UnknownErrorException,
|
||||||
} from "../../src/shared/util.js";
|
} from "../../src/shared/util.js";
|
||||||
import { LoopbackPort } from "../../src/display/api.js";
|
import { LoopbackPort } from "../../src/display/api_utils.js";
|
||||||
import { MessageHandler } from "../../src/shared/message_handler.js";
|
import { MessageHandler } from "../../src/shared/message_handler.js";
|
||||||
|
|
||||||
describe("message_handler", function () {
|
describe("message_handler", function () {
|
||||||
|
|||||||
@ -38,7 +38,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
build,
|
build,
|
||||||
getDocument,
|
getDocument,
|
||||||
isValidExplicitDest,
|
|
||||||
PDFDataRangeTransport,
|
PDFDataRangeTransport,
|
||||||
PDFWorker,
|
PDFWorker,
|
||||||
version,
|
version,
|
||||||
@ -66,6 +65,7 @@ import { ColorPicker } from "../../src/display/editor/color_picker.js";
|
|||||||
import { DOMSVGFactory } from "../../src/display/svg_factory.js";
|
import { DOMSVGFactory } from "../../src/display/svg_factory.js";
|
||||||
import { DrawLayer } from "../../src/display/draw_layer.js";
|
import { DrawLayer } from "../../src/display/draw_layer.js";
|
||||||
import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
|
import { GlobalWorkerOptions } from "../../src/display/worker_options.js";
|
||||||
|
import { isValidExplicitDest } from "../../src/display/api_utils.js";
|
||||||
import { SignatureExtractor } from "../../src/display/editor/drawers/signaturedraw.js";
|
import { SignatureExtractor } from "../../src/display/editor/drawers/signaturedraw.js";
|
||||||
import { TextLayer } from "../../src/display/text_layer.js";
|
import { TextLayer } from "../../src/display/text_layer.js";
|
||||||
import { TouchManager } from "../../src/display/touch_manager.js";
|
import { TouchManager } from "../../src/display/touch_manager.js";
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user