Source: api.js

  1. /* Copyright 2012 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. /**
  16. * @module pdfjsLib
  17. */
  18. import {
  19. _isValidExplicitDest,
  20. AbortException,
  21. AnnotationMode,
  22. assert,
  23. FeatureTest,
  24. getVerbosityLevel,
  25. info,
  26. isNodeJS,
  27. RenderingIntentFlag,
  28. setVerbosityLevel,
  29. shadow,
  30. stringToBytes,
  31. unreachable,
  32. warn,
  33. } from "../shared/util.js";
  34. import {
  35. AnnotationStorage,
  36. PrintAnnotationStorage,
  37. SerializableEmpty,
  38. } from "./annotation_storage.js";
  39. import { FontFaceObject, FontLoader } from "./font_loader.js";
  40. import {
  41. isDataScheme,
  42. isValidFetchUrl,
  43. PageViewport,
  44. RenderingCancelledException,
  45. StatTimer,
  46. } from "./display_utils.js";
  47. import { MessageHandler, wrapReason } from "../shared/message_handler.js";
  48. import {
  49. NodeCanvasFactory,
  50. NodeCMapReaderFactory,
  51. NodeFilterFactory,
  52. NodeStandardFontDataFactory,
  53. NodeWasmFactory,
  54. } from "display-node_utils";
  55. import { CanvasGraphics } from "./canvas.js";
  56. import { DOMCanvasFactory } from "./canvas_factory.js";
  57. import { DOMCMapReaderFactory } from "display-cmap_reader_factory";
  58. import { DOMFilterFactory } from "./filter_factory.js";
  59. import { DOMStandardFontDataFactory } from "display-standard_fontdata_factory";
  60. import { DOMWasmFactory } from "display-wasm_factory";
  61. import { GlobalWorkerOptions } from "./worker_options.js";
  62. import { Metadata } from "./metadata.js";
  63. import { OptionalContentConfig } from "./optional_content_config.js";
  64. import { PDFDataTransportStream } from "./transport_stream.js";
  65. import { PDFFetchStream } from "display-fetch_stream";
  66. import { PDFNetworkStream } from "display-network";
  67. import { PDFNodeStream } from "display-node_stream";
  68. import { TextLayer } from "./text_layer.js";
  69. import { XfaText } from "./xfa_text.js";
  70. const DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536
  71. const RENDERING_CANCELLED_TIMEOUT = 100; // ms
  72. /**
  73. * @typedef { Int8Array | Uint8Array | Uint8ClampedArray |
  74. * Int16Array | Uint16Array |
  75. * Int32Array | Uint32Array | Float32Array |
  76. * Float64Array
  77. * } TypedArray
  78. */
  79. /**
  80. * @typedef {Object} RefProxy
  81. * @property {number} num
  82. * @property {number} gen
  83. */
  84. /**
  85. * Document initialization / loading parameters object.
  86. *
  87. * @typedef {Object} DocumentInitParameters
  88. * @property {string | URL} [url] - The URL of the PDF.
  89. * @property {TypedArray | ArrayBuffer | Array<number> | string} [data] -
  90. * Binary PDF data.
  91. * Use TypedArrays (Uint8Array) to improve the memory usage. If PDF data is
  92. * BASE64-encoded, use `atob()` to convert it to a binary string first.
  93. *
  94. * NOTE: If TypedArrays are used they will generally be transferred to the
  95. * worker-thread. This will help reduce main-thread memory usage, however
  96. * it will take ownership of the TypedArrays.
  97. * @property {Object} [httpHeaders] - Basic authentication headers.
  98. * @property {boolean} [withCredentials] - Indicates whether or not
  99. * cross-site Access-Control requests should be made using credentials such
  100. * as cookies or authorization headers. The default is `false`.
  101. * @property {string} [password] - For decrypting password-protected PDFs.
  102. * @property {number} [length] - The PDF file length. It's used for progress
  103. * reports and range requests operations.
  104. * @property {PDFDataRangeTransport} [range] - Allows for using a custom range
  105. * transport implementation.
  106. * @property {number} [rangeChunkSize] - Specify maximum number of bytes fetched
  107. * per range request. The default value is {@link DEFAULT_RANGE_CHUNK_SIZE}.
  108. * @property {PDFWorker} [worker] - The worker that will be used for loading and
  109. * parsing the PDF data.
  110. * @property {number} [verbosity] - Controls the logging level; the constants
  111. * from {@link VerbosityLevel} should be used.
  112. * @property {string} [docBaseUrl] - The base URL of the document, used when
  113. * attempting to recover valid absolute URLs for annotations, and outline
  114. * items, that (incorrectly) only specify relative URLs.
  115. * @property {string} [cMapUrl] - The URL where the predefined Adobe CMaps are
  116. * located. Include the trailing slash.
  117. * @property {boolean} [cMapPacked] - Specifies if the Adobe CMaps are binary
  118. * packed or not. The default value is `true`.
  119. * @property {Object} [CMapReaderFactory] - The factory that will be used when
  120. * reading built-in CMap files.
  121. * The default value is {DOMCMapReaderFactory}.
  122. * @property {string} [iccUrl] - The URL where the predefined ICC profiles are
  123. * located. Include the trailing slash.
  124. * @property {boolean} [useSystemFonts] - When `true`, fonts that aren't
  125. * embedded in the PDF document will fallback to a system font.
  126. * The default value is `true` in web environments and `false` in Node.js;
  127. * unless `disableFontFace === true` in which case this defaults to `false`
  128. * regardless of the environment (to prevent completely broken fonts).
  129. * @property {string} [standardFontDataUrl] - The URL where the standard font
  130. * files are located. Include the trailing slash.
  131. * @property {Object} [StandardFontDataFactory] - The factory that will be used
  132. * when reading the standard font files.
  133. * The default value is {DOMStandardFontDataFactory}.
  134. * @property {string} [wasmUrl] - The URL where the wasm files are located.
  135. * Include the trailing slash.
  136. * @property {Object} [WasmFactory] - The factory that will be used
  137. * when reading the wasm files.
  138. * The default value is {DOMWasmFactory}.
  139. * @property {boolean} [useWorkerFetch] - Enable using the Fetch API in the
  140. * worker-thread when reading CMap and standard font files. When `true`,
  141. * the `CMapReaderFactory`, `StandardFontDataFactory`, and `WasmFactory`
  142. * options are ignored.
  143. * The default value is `true` in web environments and `false` in Node.js.
  144. * @property {boolean} [useWasm] - Attempt to use WebAssembly in order to
  145. * improve e.g. image decoding performance.
  146. * The default value is `true`.
  147. * @property {boolean} [stopAtErrors] - Reject certain promises, e.g.
  148. * `getOperatorList`, `getTextContent`, and `RenderTask`, when the associated
  149. * PDF data cannot be successfully parsed, instead of attempting to recover
  150. * whatever possible of the data. The default value is `false`.
  151. * @property {number} [maxImageSize] - The maximum allowed image size in total
  152. * pixels, i.e. width * height. Images above this value will not be rendered.
  153. * Use -1 for no limit, which is also the default value.
  154. * @property {boolean} [isEvalSupported] - Determines if we can evaluate strings
  155. * as JavaScript. Primarily used to improve performance of PDF functions.
  156. * The default value is `true`.
  157. * @property {boolean} [isOffscreenCanvasSupported] - Determines if we can use
  158. * `OffscreenCanvas` in the worker. Primarily used to improve performance of
  159. * image conversion/rendering.
  160. * The default value is `true` in web environments and `false` in Node.js.
  161. * @property {boolean} [isImageDecoderSupported] - Determines if we can use
  162. * `ImageDecoder` in the worker. Primarily used to improve performance of
  163. * image conversion/rendering.
  164. * The default value is `true` in web environments and `false` in Node.js.
  165. *
  166. * NOTE: Also temporarily disabled in Chromium browsers, until we no longer
  167. * support the affected browser versions, because of various bugs:
  168. *
  169. * - Crashes when using the BMP decoder with huge images, e.g. issue6741.pdf;
  170. * see https://issues.chromium.org/issues/374807001
  171. *
  172. * - Broken images when using the JPEG decoder with images that have custom
  173. * colour profiles, e.g. GitHub discussion 19030;
  174. * see https://issues.chromium.org/issues/378869810
  175. *
  176. * @property {number} [canvasMaxAreaInBytes] - The integer value is used to
  177. * know when an image must be resized (uses `OffscreenCanvas` in the worker).
  178. * If it's -1 then a possibly slow algorithm is used to guess the max value.
  179. * @property {boolean} [disableFontFace] - By default fonts are converted to
  180. * OpenType fonts and loaded via the Font Loading API or `@font-face` rules.
  181. * If disabled, fonts will be rendered using a built-in font renderer that
  182. * constructs the glyphs with primitive path commands.
  183. * The default value is `false` in web environments and `true` in Node.js.
  184. * @property {boolean} [fontExtraProperties] - Include additional properties,
  185. * which are unused during rendering of PDF documents, when exporting the
  186. * parsed font data from the worker-thread. This may be useful for debugging
  187. * purposes (and backwards compatibility), but note that it will lead to
  188. * increased memory usage. The default value is `false`.
  189. * @property {boolean} [enableXfa] - Render Xfa forms if any.
  190. * The default value is `false`.
  191. * @property {HTMLDocument} [ownerDocument] - Specify an explicit document
  192. * context to create elements with and to load resources, such as fonts,
  193. * into. Defaults to the current document.
  194. * @property {boolean} [disableRange] - Disable range request loading of PDF
  195. * files. When enabled, and if the server supports partial content requests,
  196. * then the PDF will be fetched in chunks. The default value is `false`.
  197. * @property {boolean} [disableStream] - Disable streaming of PDF file data.
  198. * By default PDF.js attempts to load PDF files in chunks. The default value
  199. * is `false`.
  200. * @property {boolean} [disableAutoFetch] - Disable pre-fetching of PDF file
  201. * data. When range requests are enabled PDF.js will automatically keep
  202. * fetching more data even if it isn't needed to display the current page.
  203. * The default value is `false`.
  204. *
  205. * NOTE: It is also necessary to disable streaming, see above, in order for
  206. * disabling of pre-fetching to work correctly.
  207. * @property {boolean} [pdfBug] - Enables special hooks for debugging PDF.js
  208. * (see `web/debugger.js`). The default value is `false`.
  209. * @property {Object} [CanvasFactory] - The factory that will be used when
  210. * creating canvases. The default value is {DOMCanvasFactory}.
  211. * @property {Object} [FilterFactory] - The factory that will be used to
  212. * create SVG filters when rendering some images on the main canvas.
  213. * The default value is {DOMFilterFactory}.
  214. * @property {boolean} [enableHWA] - Enables hardware acceleration for
  215. * rendering. The default value is `false`.
  216. */
  217. /**
  218. * This is the main entry point for loading a PDF and interacting with it.
  219. *
  220. * NOTE: If a URL is used to fetch the PDF data a standard Fetch API call (or
  221. * XHR as fallback) is used, which means it must follow same origin rules,
  222. * e.g. no cross-domain requests without CORS.
  223. *
  224. * @param {string | URL | TypedArray | ArrayBuffer | DocumentInitParameters}
  225. * src - Can be a URL where a PDF file is located, a typed array (Uint8Array)
  226. * already populated with data, or a parameter object.
  227. * @returns {PDFDocumentLoadingTask}
  228. */
  229. function getDocument(src = {}) {
  230. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  231. if (typeof src === "string" || src instanceof URL) {
  232. src = { url: src };
  233. } else if (src instanceof ArrayBuffer || ArrayBuffer.isView(src)) {
  234. src = { data: src };
  235. }
  236. }
  237. const task = new PDFDocumentLoadingTask();
  238. const { docId } = task;
  239. const url = src.url ? getUrlProp(src.url) : null;
  240. const data = src.data ? getDataProp(src.data) : null;
  241. const httpHeaders = src.httpHeaders || null;
  242. const withCredentials = src.withCredentials === true;
  243. const password = src.password ?? null;
  244. const rangeTransport =
  245. src.range instanceof PDFDataRangeTransport ? src.range : null;
  246. const rangeChunkSize =
  247. Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0
  248. ? src.rangeChunkSize
  249. : DEFAULT_RANGE_CHUNK_SIZE;
  250. let worker = src.worker instanceof PDFWorker ? src.worker : null;
  251. const verbosity = src.verbosity;
  252. // Ignore "data:"-URLs, since they can't be used to recover valid absolute
  253. // URLs anyway. We want to avoid sending them to the worker-thread, since
  254. // they contain the *entire* PDF document and can thus be arbitrarily long.
  255. const docBaseUrl =
  256. typeof src.docBaseUrl === "string" && !isDataScheme(src.docBaseUrl)
  257. ? src.docBaseUrl
  258. : null;
  259. const cMapUrl = getFactoryUrlProp(src.cMapUrl);
  260. const cMapPacked = src.cMapPacked !== false;
  261. const CMapReaderFactory =
  262. src.CMapReaderFactory ||
  263. (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS
  264. ? NodeCMapReaderFactory
  265. : DOMCMapReaderFactory);
  266. const iccUrl = getFactoryUrlProp(src.iccUrl);
  267. const standardFontDataUrl = getFactoryUrlProp(src.standardFontDataUrl);
  268. const StandardFontDataFactory =
  269. src.StandardFontDataFactory ||
  270. (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS
  271. ? NodeStandardFontDataFactory
  272. : DOMStandardFontDataFactory);
  273. const wasmUrl = getFactoryUrlProp(src.wasmUrl);
  274. const WasmFactory =
  275. src.WasmFactory ||
  276. (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS
  277. ? NodeWasmFactory
  278. : DOMWasmFactory);
  279. const ignoreErrors = src.stopAtErrors !== true;
  280. const maxImageSize =
  281. Number.isInteger(src.maxImageSize) && src.maxImageSize > -1
  282. ? src.maxImageSize
  283. : -1;
  284. const isEvalSupported = src.isEvalSupported !== false;
  285. const isOffscreenCanvasSupported =
  286. typeof src.isOffscreenCanvasSupported === "boolean"
  287. ? src.isOffscreenCanvasSupported
  288. : !isNodeJS;
  289. const isImageDecoderSupported =
  290. // eslint-disable-next-line no-nested-ternary
  291. typeof src.isImageDecoderSupported === "boolean"
  292. ? src.isImageDecoderSupported
  293. : // eslint-disable-next-line no-nested-ternary
  294. typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")
  295. ? true
  296. : typeof PDFJSDev !== "undefined" && PDFJSDev.test("CHROME")
  297. ? false
  298. : !isNodeJS && (FeatureTest.platform.isFirefox || !globalThis.chrome);
  299. const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes)
  300. ? src.canvasMaxAreaInBytes
  301. : -1;
  302. const disableFontFace =
  303. typeof src.disableFontFace === "boolean" ? src.disableFontFace : isNodeJS;
  304. const fontExtraProperties = src.fontExtraProperties === true;
  305. const enableXfa = src.enableXfa === true;
  306. const ownerDocument = src.ownerDocument || globalThis.document;
  307. const disableRange = src.disableRange === true;
  308. const disableStream = src.disableStream === true;
  309. const disableAutoFetch = src.disableAutoFetch === true;
  310. const pdfBug = src.pdfBug === true;
  311. const CanvasFactory =
  312. src.CanvasFactory ||
  313. (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS
  314. ? NodeCanvasFactory
  315. : DOMCanvasFactory);
  316. const FilterFactory =
  317. src.FilterFactory ||
  318. (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS
  319. ? NodeFilterFactory
  320. : DOMFilterFactory);
  321. const enableHWA = src.enableHWA === true;
  322. const useWasm = src.useWasm !== false;
  323. // Parameters whose default values depend on other parameters.
  324. const length = rangeTransport ? rangeTransport.length : (src.length ?? NaN);
  325. const useSystemFonts =
  326. typeof src.useSystemFonts === "boolean"
  327. ? src.useSystemFonts
  328. : !isNodeJS && !disableFontFace;
  329. const useWorkerFetch =
  330. typeof src.useWorkerFetch === "boolean"
  331. ? src.useWorkerFetch
  332. : (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
  333. !!(
  334. CMapReaderFactory === DOMCMapReaderFactory &&
  335. StandardFontDataFactory === DOMStandardFontDataFactory &&
  336. WasmFactory === DOMWasmFactory &&
  337. cMapUrl &&
  338. standardFontDataUrl &&
  339. wasmUrl &&
  340. isValidFetchUrl(cMapUrl, document.baseURI) &&
  341. isValidFetchUrl(standardFontDataUrl, document.baseURI) &&
  342. isValidFetchUrl(wasmUrl, document.baseURI)
  343. );
  344. // Parameters only intended for development/testing purposes.
  345. const styleElement =
  346. typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")
  347. ? src.styleElement
  348. : null;
  349. // Set the main-thread verbosity level.
  350. setVerbosityLevel(verbosity);
  351. // Ensure that the various factories can be initialized, when necessary,
  352. // since the user may provide *custom* ones.
  353. const transportFactory = {
  354. canvasFactory: new CanvasFactory({ ownerDocument, enableHWA }),
  355. filterFactory: new FilterFactory({ docId, ownerDocument }),
  356. cMapReaderFactory:
  357. (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
  358. useWorkerFetch
  359. ? null
  360. : new CMapReaderFactory({ baseUrl: cMapUrl, isCompressed: cMapPacked }),
  361. standardFontDataFactory:
  362. (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
  363. useWorkerFetch
  364. ? null
  365. : new StandardFontDataFactory({ baseUrl: standardFontDataUrl }),
  366. wasmFactory:
  367. (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
  368. useWorkerFetch
  369. ? null
  370. : new WasmFactory({ baseUrl: wasmUrl }),
  371. };
  372. if (!worker) {
  373. const workerParams = {
  374. verbosity,
  375. port: GlobalWorkerOptions.workerPort,
  376. };
  377. // Worker was not provided -- creating and owning our own. If message port
  378. // is specified in global worker options, using it.
  379. worker = workerParams.port
  380. ? PDFWorker.fromPort(workerParams)
  381. : new PDFWorker(workerParams);
  382. task._worker = worker;
  383. }
  384. const docParams = {
  385. docId,
  386. apiVersion:
  387. typeof PDFJSDev !== "undefined" && !PDFJSDev.test("TESTING")
  388. ? PDFJSDev.eval("BUNDLE_VERSION")
  389. : null,
  390. data,
  391. password,
  392. disableAutoFetch,
  393. rangeChunkSize,
  394. length,
  395. docBaseUrl,
  396. enableXfa,
  397. evaluatorOptions: {
  398. maxImageSize,
  399. disableFontFace,
  400. ignoreErrors,
  401. isEvalSupported,
  402. isOffscreenCanvasSupported,
  403. isImageDecoderSupported,
  404. canvasMaxAreaInBytes,
  405. fontExtraProperties,
  406. useSystemFonts,
  407. useWasm,
  408. useWorkerFetch,
  409. cMapUrl,
  410. iccUrl,
  411. standardFontDataUrl,
  412. wasmUrl,
  413. },
  414. };
  415. const transportParams = {
  416. ownerDocument,
  417. pdfBug,
  418. styleElement,
  419. loadingParams: {
  420. disableAutoFetch,
  421. enableXfa,
  422. },
  423. };
  424. worker.promise
  425. .then(function () {
  426. if (task.destroyed) {
  427. throw new Error("Loading aborted");
  428. }
  429. if (worker.destroyed) {
  430. throw new Error("Worker was destroyed");
  431. }
  432. const workerIdPromise = worker.messageHandler.sendWithPromise(
  433. "GetDocRequest",
  434. docParams,
  435. data ? [data.buffer] : null
  436. );
  437. let networkStream;
  438. if (rangeTransport) {
  439. networkStream = new PDFDataTransportStream(rangeTransport, {
  440. disableRange,
  441. disableStream,
  442. });
  443. } else if (!data) {
  444. if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
  445. throw new Error("Not implemented: NetworkStream");
  446. }
  447. if (!url) {
  448. throw new Error("getDocument - no `url` parameter provided.");
  449. }
  450. let NetworkStream;
  451. if (
  452. typeof PDFJSDev !== "undefined" &&
  453. PDFJSDev.test("GENERIC") &&
  454. isNodeJS
  455. ) {
  456. if (isValidFetchUrl(url)) {
  457. if (
  458. typeof fetch === "undefined" ||
  459. typeof Response === "undefined" ||
  460. !("body" in Response.prototype)
  461. ) {
  462. throw new Error(
  463. "getDocument - the Fetch API was disabled in Node.js, see `--no-experimental-fetch`."
  464. );
  465. }
  466. NetworkStream = PDFFetchStream;
  467. } else {
  468. NetworkStream = PDFNodeStream;
  469. }
  470. } else {
  471. NetworkStream = isValidFetchUrl(url)
  472. ? PDFFetchStream
  473. : PDFNetworkStream;
  474. }
  475. networkStream = new NetworkStream({
  476. url,
  477. length,
  478. httpHeaders,
  479. withCredentials,
  480. rangeChunkSize,
  481. disableRange,
  482. disableStream,
  483. });
  484. }
  485. return workerIdPromise.then(workerId => {
  486. if (task.destroyed) {
  487. throw new Error("Loading aborted");
  488. }
  489. if (worker.destroyed) {
  490. throw new Error("Worker was destroyed");
  491. }
  492. const messageHandler = new MessageHandler(docId, workerId, worker.port);
  493. const transport = new WorkerTransport(
  494. messageHandler,
  495. task,
  496. networkStream,
  497. transportParams,
  498. transportFactory
  499. );
  500. task._transport = transport;
  501. messageHandler.send("Ready", null);
  502. });
  503. })
  504. .catch(task._capability.reject);
  505. return task;
  506. }
  507. function getUrlProp(val) {
  508. if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
  509. return null; // The 'url' is unused with `PDFDataRangeTransport`.
  510. }
  511. if (val instanceof URL) {
  512. return val.href;
  513. }
  514. if (typeof val === "string") {
  515. if (
  516. typeof PDFJSDev !== "undefined" &&
  517. PDFJSDev.test("GENERIC") &&
  518. isNodeJS
  519. ) {
  520. return val; // Use the url as-is in Node.js environments.
  521. }
  522. // The full path is required in the 'url' field.
  523. const url = URL.parse(val, window.location);
  524. if (url) {
  525. return url.href;
  526. }
  527. }
  528. throw new Error(
  529. "Invalid PDF url data: " +
  530. "either string or URL-object is expected in the url property."
  531. );
  532. }
  533. function getDataProp(val) {
  534. // Converting string or array-like data to Uint8Array.
  535. if (
  536. typeof PDFJSDev !== "undefined" &&
  537. PDFJSDev.test("GENERIC") &&
  538. isNodeJS &&
  539. typeof Buffer !== "undefined" && // eslint-disable-line no-undef
  540. val instanceof Buffer // eslint-disable-line no-undef
  541. ) {
  542. throw new Error(
  543. "Please provide binary data as `Uint8Array`, rather than `Buffer`."
  544. );
  545. }
  546. if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) {
  547. // Use the data as-is when it's already a Uint8Array that completely
  548. // "utilizes" its underlying ArrayBuffer, to prevent any possible
  549. // issues when transferring it to the worker-thread.
  550. return val;
  551. }
  552. if (typeof val === "string") {
  553. return stringToBytes(val);
  554. }
  555. if (
  556. val instanceof ArrayBuffer ||
  557. ArrayBuffer.isView(val) ||
  558. (typeof val === "object" && !isNaN(val?.length))
  559. ) {
  560. return new Uint8Array(val);
  561. }
  562. throw new Error(
  563. "Invalid PDF binary data: either TypedArray, " +
  564. "string, or array-like object is expected in the data property."
  565. );
  566. }
  567. function getFactoryUrlProp(val) {
  568. if (typeof val !== "string") {
  569. return null;
  570. }
  571. if (val.endsWith("/")) {
  572. return val;
  573. }
  574. throw new Error(`Invalid factory url: "${val}" must include trailing slash.`);
  575. }
  576. const isRefProxy = v =>
  577. typeof v === "object" &&
  578. Number.isInteger(v?.num) &&
  579. v.num >= 0 &&
  580. Number.isInteger(v?.gen) &&
  581. v.gen >= 0;
  582. const isNameProxy = v => typeof v === "object" && typeof v?.name === "string";
  583. const isValidExplicitDest = _isValidExplicitDest.bind(
  584. null,
  585. /* validRef = */ isRefProxy,
  586. /* validName = */ isNameProxy
  587. );
  588. /**
  589. * @typedef {Object} OnProgressParameters
  590. * @property {number} loaded - Currently loaded number of bytes.
  591. * @property {number} total - Total number of bytes in the PDF file.
  592. */
  593. /**
  594. * The loading task controls the operations required to load a PDF document
  595. * (such as network requests) and provides a way to listen for completion,
  596. * after which individual pages can be rendered.
  597. */
  598. class PDFDocumentLoadingTask {
  599. static #docId = 0;
  600. /**
  601. * @private
  602. */
  603. _capability = Promise.withResolvers();
  604. /**
  605. * @private
  606. */
  607. _transport = null;
  608. /**
  609. * @private
  610. */
  611. _worker = null;
  612. /**
  613. * Unique identifier for the document loading task.
  614. * @type {string}
  615. */
  616. docId = `d${PDFDocumentLoadingTask.#docId++}`;
  617. /**
  618. * Whether the loading task is destroyed or not.
  619. * @type {boolean}
  620. */
  621. destroyed = false;
  622. /**
  623. * Callback to request a password if a wrong or no password was provided.
  624. * The callback receives two parameters: a function that should be called
  625. * with the new password, and a reason (see {@link PasswordResponses}).
  626. * @type {function}
  627. */
  628. onPassword = null;
  629. /**
  630. * Callback to be able to monitor the loading progress of the PDF file
  631. * (necessary to implement e.g. a loading bar).
  632. * The callback receives an {@link OnProgressParameters} argument.
  633. * @type {function}
  634. */
  635. onProgress = null;
  636. /**
  637. * Promise for document loading task completion.
  638. * @type {Promise<PDFDocumentProxy>}
  639. */
  640. get promise() {
  641. return this._capability.promise;
  642. }
  643. /**
  644. * Abort all network requests and destroy the worker.
  645. * @returns {Promise<void>} A promise that is resolved when destruction is
  646. * completed.
  647. */
  648. async destroy() {
  649. this.destroyed = true;
  650. if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
  651. await this._transport?.destroy();
  652. } else {
  653. try {
  654. if (this._worker?.port) {
  655. this._worker._pendingDestroy = true;
  656. }
  657. await this._transport?.destroy();
  658. } catch (ex) {
  659. if (this._worker?.port) {
  660. delete this._worker._pendingDestroy;
  661. }
  662. throw ex;
  663. }
  664. }
  665. this._transport = null;
  666. this._worker?.destroy();
  667. this._worker = null;
  668. }
  669. /**
  670. * Attempt to fetch the raw data of the PDF document, when e.g.
  671. * - An exception was thrown during document initialization.
  672. * - An `onPassword` callback is delaying initialization.
  673. * @returns {Promise<Uint8Array>}
  674. */
  675. async getData() {
  676. return this._transport.getData();
  677. }
  678. }
  679. /**
  680. * Abstract class to support range requests file loading.
  681. *
  682. * NOTE: The TypedArrays passed to the constructor and relevant methods below
  683. * will generally be transferred to the worker-thread. This will help reduce
  684. * main-thread memory usage, however it will take ownership of the TypedArrays.
  685. */
  686. class PDFDataRangeTransport {
  687. /**
  688. * @param {number} length
  689. * @param {Uint8Array|null} initialData
  690. * @param {boolean} [progressiveDone]
  691. * @param {string} [contentDispositionFilename]
  692. */
  693. constructor(
  694. length,
  695. initialData,
  696. progressiveDone = false,
  697. contentDispositionFilename = null
  698. ) {
  699. this.length = length;
  700. this.initialData = initialData;
  701. this.progressiveDone = progressiveDone;
  702. this.contentDispositionFilename = contentDispositionFilename;
  703. this._rangeListeners = [];
  704. this._progressListeners = [];
  705. this._progressiveReadListeners = [];
  706. this._progressiveDoneListeners = [];
  707. this._readyCapability = Promise.withResolvers();
  708. }
  709. /**
  710. * @param {function} listener
  711. */
  712. addRangeListener(listener) {
  713. this._rangeListeners.push(listener);
  714. }
  715. /**
  716. * @param {function} listener
  717. */
  718. addProgressListener(listener) {
  719. this._progressListeners.push(listener);
  720. }
  721. /**
  722. * @param {function} listener
  723. */
  724. addProgressiveReadListener(listener) {
  725. this._progressiveReadListeners.push(listener);
  726. }
  727. /**
  728. * @param {function} listener
  729. */
  730. addProgressiveDoneListener(listener) {
  731. this._progressiveDoneListeners.push(listener);
  732. }
  733. /**
  734. * @param {number} begin
  735. * @param {Uint8Array|null} chunk
  736. */
  737. onDataRange(begin, chunk) {
  738. for (const listener of this._rangeListeners) {
  739. listener(begin, chunk);
  740. }
  741. }
  742. /**
  743. * @param {number} loaded
  744. * @param {number|undefined} total
  745. */
  746. onDataProgress(loaded, total) {
  747. this._readyCapability.promise.then(() => {
  748. for (const listener of this._progressListeners) {
  749. listener(loaded, total);
  750. }
  751. });
  752. }
  753. /**
  754. * @param {Uint8Array|null} chunk
  755. */
  756. onDataProgressiveRead(chunk) {
  757. this._readyCapability.promise.then(() => {
  758. for (const listener of this._progressiveReadListeners) {
  759. listener(chunk);
  760. }
  761. });
  762. }
  763. onDataProgressiveDone() {
  764. this._readyCapability.promise.then(() => {
  765. for (const listener of this._progressiveDoneListeners) {
  766. listener();
  767. }
  768. });
  769. }
  770. transportReady() {
  771. this._readyCapability.resolve();
  772. }
  773. /**
  774. * @param {number} begin
  775. * @param {number} end
  776. */
  777. requestDataRange(begin, end) {
  778. unreachable("Abstract method PDFDataRangeTransport.requestDataRange");
  779. }
  780. abort() {}
  781. }
  782. /**
  783. * Proxy to a `PDFDocument` in the worker thread.
  784. */
  785. class PDFDocumentProxy {
  786. constructor(pdfInfo, transport) {
  787. this._pdfInfo = pdfInfo;
  788. this._transport = transport;
  789. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
  790. // For testing purposes.
  791. Object.defineProperty(this, "getNetworkStreamName", {
  792. value: () => this._transport.getNetworkStreamName(),
  793. });
  794. Object.defineProperty(this, "getXFADatasets", {
  795. value: () => this._transport.getXFADatasets(),
  796. });
  797. Object.defineProperty(this, "getXRefPrevValue", {
  798. value: () => this._transport.getXRefPrevValue(),
  799. });
  800. Object.defineProperty(this, "getStartXRefPos", {
  801. value: () => this._transport.getStartXRefPos(),
  802. });
  803. Object.defineProperty(this, "getAnnotArray", {
  804. value: pageIndex => this._transport.getAnnotArray(pageIndex),
  805. });
  806. }
  807. }
  808. /**
  809. * @type {AnnotationStorage} Storage for annotation data in forms.
  810. */
  811. get annotationStorage() {
  812. return this._transport.annotationStorage;
  813. }
  814. /**
  815. * @type {Object} The canvas factory instance.
  816. */
  817. get canvasFactory() {
  818. return this._transport.canvasFactory;
  819. }
  820. /**
  821. * @type {Object} The filter factory instance.
  822. */
  823. get filterFactory() {
  824. return this._transport.filterFactory;
  825. }
  826. /**
  827. * @type {number} Total number of pages in the PDF file.
  828. */
  829. get numPages() {
  830. return this._pdfInfo.numPages;
  831. }
  832. /**
  833. * @type {Array<string | null>} A (not guaranteed to be) unique ID to identify
  834. * the PDF document.
  835. * NOTE: The first element will always be defined for all PDF documents,
  836. * whereas the second element is only defined for *modified* PDF documents.
  837. */
  838. get fingerprints() {
  839. return this._pdfInfo.fingerprints;
  840. }
  841. /**
  842. * @type {boolean} True if only XFA form.
  843. */
  844. get isPureXfa() {
  845. return shadow(this, "isPureXfa", !!this._transport._htmlForXfa);
  846. }
  847. /**
  848. * NOTE: This is (mostly) intended to support printing of XFA forms.
  849. *
  850. * @type {Object | null} An object representing a HTML tree structure
  851. * to render the XFA, or `null` when no XFA form exists.
  852. */
  853. get allXfaHtml() {
  854. return this._transport._htmlForXfa;
  855. }
  856. /**
  857. * @param {number} pageNumber - The page number to get. The first page is 1.
  858. * @returns {Promise<PDFPageProxy>} A promise that is resolved with
  859. * a {@link PDFPageProxy} object.
  860. */
  861. getPage(pageNumber) {
  862. return this._transport.getPage(pageNumber);
  863. }
  864. /**
  865. * @param {RefProxy} ref - The page reference.
  866. * @returns {Promise<number>} A promise that is resolved with the page index,
  867. * starting from zero, that is associated with the reference.
  868. */
  869. getPageIndex(ref) {
  870. return this._transport.getPageIndex(ref);
  871. }
  872. /**
  873. * @returns {Promise<Object<string, Array<any>>>} A promise that is resolved
  874. * with a mapping from named destinations to references.
  875. *
  876. * This can be slow for large documents. Use `getDestination` instead.
  877. */
  878. getDestinations() {
  879. return this._transport.getDestinations();
  880. }
  881. /**
  882. * @param {string} id - The named destination to get.
  883. * @returns {Promise<Array<any> | null>} A promise that is resolved with all
  884. * information of the given named destination, or `null` when the named
  885. * destination is not present in the PDF file.
  886. */
  887. getDestination(id) {
  888. return this._transport.getDestination(id);
  889. }
  890. /**
  891. * @returns {Promise<Array<string> | null>} A promise that is resolved with
  892. * an {Array} containing the page labels that correspond to the page
  893. * indexes, or `null` when no page labels are present in the PDF file.
  894. */
  895. getPageLabels() {
  896. return this._transport.getPageLabels();
  897. }
  898. /**
  899. * @returns {Promise<string>} A promise that is resolved with a {string}
  900. * containing the page layout name.
  901. */
  902. getPageLayout() {
  903. return this._transport.getPageLayout();
  904. }
  905. /**
  906. * @returns {Promise<string>} A promise that is resolved with a {string}
  907. * containing the page mode name.
  908. */
  909. getPageMode() {
  910. return this._transport.getPageMode();
  911. }
  912. /**
  913. * @returns {Promise<Object | null>} A promise that is resolved with an
  914. * {Object} containing the viewer preferences, or `null` when no viewer
  915. * preferences are present in the PDF file.
  916. */
  917. getViewerPreferences() {
  918. return this._transport.getViewerPreferences();
  919. }
  920. /**
  921. * @returns {Promise<any | null>} A promise that is resolved with an {Array}
  922. * containing the destination, or `null` when no open action is present
  923. * in the PDF.
  924. */
  925. getOpenAction() {
  926. return this._transport.getOpenAction();
  927. }
  928. /**
  929. * @returns {Promise<any>} A promise that is resolved with a lookup table
  930. * for mapping named attachments to their content.
  931. */
  932. getAttachments() {
  933. return this._transport.getAttachments();
  934. }
  935. /**
  936. * @returns {Promise<Object | null>} A promise that is resolved with
  937. * an {Object} with the JavaScript actions:
  938. * - from the name tree.
  939. * - from A or AA entries in the catalog dictionary.
  940. * , or `null` if no JavaScript exists.
  941. */
  942. getJSActions() {
  943. return this._transport.getDocJSActions();
  944. }
  945. /**
  946. * @typedef {Object} OutlineNode
  947. * @property {string} title
  948. * @property {boolean} bold
  949. * @property {boolean} italic
  950. * @property {Uint8ClampedArray} color - The color in RGB format to use for
  951. * display purposes.
  952. * @property {string | Array<any> | null} dest
  953. * @property {string | null} url
  954. * @property {string | undefined} unsafeUrl
  955. * @property {boolean | undefined} newWindow
  956. * @property {number | undefined} count
  957. * @property {Array<OutlineNode>} items
  958. */
  959. /**
  960. * @returns {Promise<Array<OutlineNode>>} A promise that is resolved with an
  961. * {Array} that is a tree outline (if it has one) of the PDF file.
  962. */
  963. getOutline() {
  964. return this._transport.getOutline();
  965. }
  966. /**
  967. * @typedef {Object} GetOptionalContentConfigParameters
  968. * @property {string} [intent] - Determines the optional content groups that
  969. * are visible by default; valid values are:
  970. * - 'display' (viewable groups).
  971. * - 'print' (printable groups).
  972. * - 'any' (all groups).
  973. * The default value is 'display'.
  974. */
  975. /**
  976. * @param {GetOptionalContentConfigParameters} [params] - Optional content
  977. * config parameters.
  978. * @returns {Promise<OptionalContentConfig>} A promise that is resolved with
  979. * an {@link OptionalContentConfig} that contains all the optional content
  980. * groups (assuming that the document has any).
  981. */
  982. getOptionalContentConfig({ intent = "display" } = {}) {
  983. const { renderingIntent } = this._transport.getRenderingIntent(intent);
  984. return this._transport.getOptionalContentConfig(renderingIntent);
  985. }
  986. /**
  987. * @returns {Promise<Array<number> | null>} A promise that is resolved with
  988. * an {Array} that contains the permission flags for the PDF document, or
  989. * `null` when no permissions are present in the PDF file.
  990. */
  991. getPermissions() {
  992. return this._transport.getPermissions();
  993. }
  994. /**
  995. * @returns {Promise<{ info: Object, metadata: Metadata }>} A promise that is
  996. * resolved with an {Object} that has `info` and `metadata` properties.
  997. * `info` is an {Object} filled with anything available in the information
  998. * dictionary and similarly `metadata` is a {Metadata} object with
  999. * information from the metadata section of the PDF.
  1000. */
  1001. getMetadata() {
  1002. return this._transport.getMetadata();
  1003. }
  1004. /**
  1005. * @typedef {Object} MarkInfo
  1006. * Properties correspond to Table 321 of the PDF 32000-1:2008 spec.
  1007. * @property {boolean} Marked
  1008. * @property {boolean} UserProperties
  1009. * @property {boolean} Suspects
  1010. */
  1011. /**
  1012. * @returns {Promise<MarkInfo | null>} A promise that is resolved with
  1013. * a {MarkInfo} object that contains the MarkInfo flags for the PDF
  1014. * document, or `null` when no MarkInfo values are present in the PDF file.
  1015. */
  1016. getMarkInfo() {
  1017. return this._transport.getMarkInfo();
  1018. }
  1019. /**
  1020. * @returns {Promise<Uint8Array>} A promise that is resolved with a
  1021. * {Uint8Array} containing the raw data of the PDF document.
  1022. */
  1023. getData() {
  1024. return this._transport.getData();
  1025. }
  1026. /**
  1027. * @returns {Promise<Uint8Array>} A promise that is resolved with a
  1028. * {Uint8Array} containing the full data of the saved document.
  1029. */
  1030. saveDocument() {
  1031. return this._transport.saveDocument();
  1032. }
  1033. /**
  1034. * @returns {Promise<{ length: number }>} A promise that is resolved when the
  1035. * document's data is loaded. It is resolved with an {Object} that contains
  1036. * the `length` property that indicates size of the PDF data in bytes.
  1037. */
  1038. getDownloadInfo() {
  1039. return this._transport.downloadInfoCapability.promise;
  1040. }
  1041. /**
  1042. * Cleans up resources allocated by the document on both the main and worker
  1043. * threads.
  1044. *
  1045. * NOTE: Do not, under any circumstances, call this method when rendering is
  1046. * currently ongoing since that may lead to rendering errors.
  1047. *
  1048. * @param {boolean} [keepLoadedFonts] - Let fonts remain attached to the DOM.
  1049. * NOTE: This will increase persistent memory usage, hence don't use this
  1050. * option unless absolutely necessary. The default value is `false`.
  1051. * @returns {Promise} A promise that is resolved when clean-up has finished.
  1052. */
  1053. cleanup(keepLoadedFonts = false) {
  1054. return this._transport.startCleanup(keepLoadedFonts || this.isPureXfa);
  1055. }
  1056. /**
  1057. * Destroys the current document instance and terminates the worker.
  1058. */
  1059. destroy() {
  1060. return this.loadingTask.destroy();
  1061. }
  1062. /**
  1063. * @param {RefProxy} ref - The page reference.
  1064. * @returns {number | null} The page number, if it's cached.
  1065. */
  1066. cachedPageNumber(ref) {
  1067. return this._transport.cachedPageNumber(ref);
  1068. }
  1069. /**
  1070. * @type {DocumentInitParameters} A subset of the current
  1071. * {DocumentInitParameters}, which are needed in the viewer.
  1072. */
  1073. get loadingParams() {
  1074. return this._transport.loadingParams;
  1075. }
  1076. /**
  1077. * @type {PDFDocumentLoadingTask} The loadingTask for the current document.
  1078. */
  1079. get loadingTask() {
  1080. return this._transport.loadingTask;
  1081. }
  1082. /**
  1083. * @returns {Promise<Object<string, Array<Object>> | null>} A promise that is
  1084. * resolved with an {Object} containing /AcroForm field data for the JS
  1085. * sandbox, or `null` when no field data is present in the PDF file.
  1086. */
  1087. getFieldObjects() {
  1088. return this._transport.getFieldObjects();
  1089. }
  1090. /**
  1091. * @returns {Promise<boolean>} A promise that is resolved with `true`
  1092. * if some /AcroForm fields have JavaScript actions.
  1093. */
  1094. hasJSActions() {
  1095. return this._transport.hasJSActions();
  1096. }
  1097. /**
  1098. * @returns {Promise<Array<string> | null>} A promise that is resolved with an
  1099. * {Array<string>} containing IDs of annotations that have a calculation
  1100. * action, or `null` when no such annotations are present in the PDF file.
  1101. */
  1102. getCalculationOrderIds() {
  1103. return this._transport.getCalculationOrderIds();
  1104. }
  1105. }
  1106. /**
  1107. * Page getViewport parameters.
  1108. *
  1109. * @typedef {Object} GetViewportParameters
  1110. * @property {number} scale - The desired scale of the viewport.
  1111. * @property {number} [rotation] - The desired rotation, in degrees, of
  1112. * the viewport. If omitted it defaults to the page rotation.
  1113. * @property {number} [offsetX] - The horizontal, i.e. x-axis, offset.
  1114. * The default value is `0`.
  1115. * @property {number} [offsetY] - The vertical, i.e. y-axis, offset.
  1116. * The default value is `0`.
  1117. * @property {boolean} [dontFlip] - If true, the y-axis will not be
  1118. * flipped. The default value is `false`.
  1119. */
  1120. /**
  1121. * Page getTextContent parameters.
  1122. *
  1123. * @typedef {Object} getTextContentParameters
  1124. * @property {boolean} [includeMarkedContent] - When true include marked
  1125. * content items in the items array of TextContent. The default is `false`.
  1126. * @property {boolean} [disableNormalization] - When true the text is *not*
  1127. * normalized in the worker-thread. The default is `false`.
  1128. */
  1129. /**
  1130. * Page text content.
  1131. *
  1132. * @typedef {Object} TextContent
  1133. * @property {Array<TextItem | TextMarkedContent>} items - Array of
  1134. * {@link TextItem} and {@link TextMarkedContent} objects. TextMarkedContent
  1135. * items are included when includeMarkedContent is true.
  1136. * @property {Object<string, TextStyle>} styles - {@link TextStyle} objects,
  1137. * indexed by font name.
  1138. * @property {string | null} lang - The document /Lang attribute.
  1139. */
  1140. /**
  1141. * Page text content part.
  1142. *
  1143. * @typedef {Object} TextItem
  1144. * @property {string} str - Text content.
  1145. * @property {string} dir - Text direction: 'ttb', 'ltr' or 'rtl'.
  1146. * @property {Array<any>} transform - Transformation matrix.
  1147. * @property {number} width - Width in device space.
  1148. * @property {number} height - Height in device space.
  1149. * @property {string} fontName - Font name used by PDF.js for converted font.
  1150. * @property {boolean} hasEOL - Indicating if the text content is followed by a
  1151. * line-break.
  1152. */
  1153. /**
  1154. * Page text marked content part.
  1155. *
  1156. * @typedef {Object} TextMarkedContent
  1157. * @property {string} type - Either 'beginMarkedContent',
  1158. * 'beginMarkedContentProps', or 'endMarkedContent'.
  1159. * @property {string} id - The marked content identifier. Only used for type
  1160. * 'beginMarkedContentProps'.
  1161. */
  1162. /**
  1163. * Text style.
  1164. *
  1165. * @typedef {Object} TextStyle
  1166. * @property {number} ascent - Font ascent.
  1167. * @property {number} descent - Font descent.
  1168. * @property {boolean} vertical - Whether or not the text is in vertical mode.
  1169. * @property {string} fontFamily - The possible font family.
  1170. */
  1171. /**
  1172. * Page annotation parameters.
  1173. *
  1174. * @typedef {Object} GetAnnotationsParameters
  1175. * @property {string} [intent] - Determines the annotations that are fetched,
  1176. * can be 'display' (viewable annotations), 'print' (printable annotations),
  1177. * or 'any' (all annotations). The default value is 'display'.
  1178. */
  1179. /**
  1180. * Page render parameters.
  1181. *
  1182. * @typedef {Object} RenderParameters
  1183. * @property {CanvasRenderingContext2D} canvasContext - A 2D context of a DOM
  1184. * Canvas object.
  1185. * @property {PageViewport} viewport - Rendering viewport obtained by calling
  1186. * the `PDFPageProxy.getViewport` method.
  1187. * @property {string} [intent] - Rendering intent, can be 'display', 'print',
  1188. * or 'any'. The default value is 'display'.
  1189. * @property {number} [annotationMode] Controls which annotations are rendered
  1190. * onto the canvas, for annotations with appearance-data; the values from
  1191. * {@link AnnotationMode} should be used. The following values are supported:
  1192. * - `AnnotationMode.DISABLE`, which disables all annotations.
  1193. * - `AnnotationMode.ENABLE`, which includes all possible annotations (thus
  1194. * it also depends on the `intent`-option, see above).
  1195. * - `AnnotationMode.ENABLE_FORMS`, which excludes annotations that contain
  1196. * interactive form elements (those will be rendered in the display layer).
  1197. * - `AnnotationMode.ENABLE_STORAGE`, which includes all possible annotations
  1198. * (as above) but where interactive form elements are updated with data
  1199. * from the {@link AnnotationStorage}-instance; useful e.g. for printing.
  1200. * The default value is `AnnotationMode.ENABLE`.
  1201. * @property {Array<any>} [transform] - Additional transform, applied just
  1202. * before viewport transform.
  1203. * @property {CanvasGradient | CanvasPattern | string} [background] - Background
  1204. * to use for the canvas.
  1205. * Any valid `canvas.fillStyle` can be used: a `DOMString` parsed as CSS
  1206. * <color> value, a `CanvasGradient` object (a linear or radial gradient) or
  1207. * a `CanvasPattern` object (a repetitive image). The default value is
  1208. * 'rgb(255,255,255)'.
  1209. *
  1210. * NOTE: This option may be partially, or completely, ignored when the
  1211. * `pageColors`-option is used.
  1212. * @property {Object} [pageColors] - Overwrites background and foreground colors
  1213. * with user defined ones in order to improve readability in high contrast
  1214. * mode.
  1215. * @property {Promise<OptionalContentConfig>} [optionalContentConfigPromise] -
  1216. * A promise that should resolve with an {@link OptionalContentConfig}
  1217. * created from `PDFDocumentProxy.getOptionalContentConfig`. If `null`,
  1218. * the configuration will be fetched automatically with the default visibility
  1219. * states set.
  1220. * @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap] - Map some
  1221. * annotation ids with canvases used to render them.
  1222. * @property {PrintAnnotationStorage} [printAnnotationStorage]
  1223. * @property {boolean} [isEditing] - Render the page in editing mode.
  1224. */
  1225. /**
  1226. * Page getOperatorList parameters.
  1227. *
  1228. * @typedef {Object} GetOperatorListParameters
  1229. * @property {string} [intent] - Rendering intent, can be 'display', 'print',
  1230. * or 'any'. The default value is 'display'.
  1231. * @property {number} [annotationMode] Controls which annotations are included
  1232. * in the operatorList, for annotations with appearance-data; the values from
  1233. * {@link AnnotationMode} should be used. The following values are supported:
  1234. * - `AnnotationMode.DISABLE`, which disables all annotations.
  1235. * - `AnnotationMode.ENABLE`, which includes all possible annotations (thus
  1236. * it also depends on the `intent`-option, see above).
  1237. * - `AnnotationMode.ENABLE_FORMS`, which excludes annotations that contain
  1238. * interactive form elements (those will be rendered in the display layer).
  1239. * - `AnnotationMode.ENABLE_STORAGE`, which includes all possible annotations
  1240. * (as above) but where interactive form elements are updated with data
  1241. * from the {@link AnnotationStorage}-instance; useful e.g. for printing.
  1242. * The default value is `AnnotationMode.ENABLE`.
  1243. * @property {PrintAnnotationStorage} [printAnnotationStorage]
  1244. * @property {boolean} [isEditing] - Render the page in editing mode.
  1245. */
  1246. /**
  1247. * Structure tree node. The root node will have a role "Root".
  1248. *
  1249. * @typedef {Object} StructTreeNode
  1250. * @property {Array<StructTreeNode | StructTreeContent>} children - Array of
  1251. * {@link StructTreeNode} and {@link StructTreeContent} objects.
  1252. * @property {string} role - element's role, already mapped if a role map exists
  1253. * in the PDF.
  1254. */
  1255. /**
  1256. * Structure tree content.
  1257. *
  1258. * @typedef {Object} StructTreeContent
  1259. * @property {string} type - either "content" for page and stream structure
  1260. * elements or "object" for object references.
  1261. * @property {string} id - unique id that will map to the text layer.
  1262. */
  1263. /**
  1264. * PDF page operator list.
  1265. *
  1266. * @typedef {Object} PDFOperatorList
  1267. * @property {Array<number>} fnArray - Array containing the operator functions.
  1268. * @property {Array<any>} argsArray - Array containing the arguments of the
  1269. * functions.
  1270. */
  1271. /**
  1272. * Proxy to a `PDFPage` in the worker thread.
  1273. */
  1274. class PDFPageProxy {
  1275. #pendingCleanup = false;
  1276. constructor(pageIndex, pageInfo, transport, pdfBug = false) {
  1277. this._pageIndex = pageIndex;
  1278. this._pageInfo = pageInfo;
  1279. this._transport = transport;
  1280. this._stats = pdfBug ? new StatTimer() : null;
  1281. this._pdfBug = pdfBug;
  1282. /** @type {PDFObjects} */
  1283. this.commonObjs = transport.commonObjs;
  1284. this.objs = new PDFObjects();
  1285. this._intentStates = new Map();
  1286. this.destroyed = false;
  1287. }
  1288. /**
  1289. * @type {number} Page number of the page. First page is 1.
  1290. */
  1291. get pageNumber() {
  1292. return this._pageIndex + 1;
  1293. }
  1294. /**
  1295. * @type {number} The number of degrees the page is rotated clockwise.
  1296. */
  1297. get rotate() {
  1298. return this._pageInfo.rotate;
  1299. }
  1300. /**
  1301. * @type {RefProxy | null} The reference that points to this page.
  1302. */
  1303. get ref() {
  1304. return this._pageInfo.ref;
  1305. }
  1306. /**
  1307. * @type {number} The default size of units in 1/72nds of an inch.
  1308. */
  1309. get userUnit() {
  1310. return this._pageInfo.userUnit;
  1311. }
  1312. /**
  1313. * @type {Array<number>} An array of the visible portion of the PDF page in
  1314. * user space units [x1, y1, x2, y2].
  1315. */
  1316. get view() {
  1317. return this._pageInfo.view;
  1318. }
  1319. /**
  1320. * @param {GetViewportParameters} params - Viewport parameters.
  1321. * @returns {PageViewport} Contains 'width' and 'height' properties
  1322. * along with transforms required for rendering.
  1323. */
  1324. getViewport({
  1325. scale,
  1326. rotation = this.rotate,
  1327. offsetX = 0,
  1328. offsetY = 0,
  1329. dontFlip = false,
  1330. } = {}) {
  1331. return new PageViewport({
  1332. viewBox: this.view,
  1333. userUnit: this.userUnit,
  1334. scale,
  1335. rotation,
  1336. offsetX,
  1337. offsetY,
  1338. dontFlip,
  1339. });
  1340. }
  1341. /**
  1342. * @param {GetAnnotationsParameters} [params] - Annotation parameters.
  1343. * @returns {Promise<Array<any>>} A promise that is resolved with an
  1344. * {Array} of the annotation objects.
  1345. */
  1346. getAnnotations({ intent = "display" } = {}) {
  1347. const { renderingIntent } = this._transport.getRenderingIntent(intent);
  1348. return this._transport.getAnnotations(this._pageIndex, renderingIntent);
  1349. }
  1350. /**
  1351. * @returns {Promise<Object>} A promise that is resolved with an
  1352. * {Object} with JS actions.
  1353. */
  1354. getJSActions() {
  1355. return this._transport.getPageJSActions(this._pageIndex);
  1356. }
  1357. /**
  1358. * @type {Object} The filter factory instance.
  1359. */
  1360. get filterFactory() {
  1361. return this._transport.filterFactory;
  1362. }
  1363. /**
  1364. * @type {boolean} True if only XFA form.
  1365. */
  1366. get isPureXfa() {
  1367. return shadow(this, "isPureXfa", !!this._transport._htmlForXfa);
  1368. }
  1369. /**
  1370. * @returns {Promise<Object | null>} A promise that is resolved with
  1371. * an {Object} with a fake DOM object (a tree structure where elements
  1372. * are {Object} with a name, attributes (class, style, ...), value and
  1373. * children, very similar to a HTML DOM tree), or `null` if no XFA exists.
  1374. */
  1375. async getXfa() {
  1376. return this._transport._htmlForXfa?.children[this._pageIndex] || null;
  1377. }
  1378. /**
  1379. * Begins the process of rendering a page to the desired context.
  1380. *
  1381. * @param {RenderParameters} params - Page render parameters.
  1382. * @returns {RenderTask} An object that contains a promise that is
  1383. * resolved when the page finishes rendering.
  1384. */
  1385. render({
  1386. canvasContext,
  1387. viewport,
  1388. intent = "display",
  1389. annotationMode = AnnotationMode.ENABLE,
  1390. transform = null,
  1391. background = null,
  1392. optionalContentConfigPromise = null,
  1393. annotationCanvasMap = null,
  1394. pageColors = null,
  1395. printAnnotationStorage = null,
  1396. isEditing = false,
  1397. }) {
  1398. this._stats?.time("Overall");
  1399. const intentArgs = this._transport.getRenderingIntent(
  1400. intent,
  1401. annotationMode,
  1402. printAnnotationStorage,
  1403. isEditing
  1404. );
  1405. const { renderingIntent, cacheKey } = intentArgs;
  1406. // If there was a pending destroy, cancel it so no cleanup happens during
  1407. // this call to render.
  1408. this.#pendingCleanup = false;
  1409. optionalContentConfigPromise ||=
  1410. this._transport.getOptionalContentConfig(renderingIntent);
  1411. let intentState = this._intentStates.get(cacheKey);
  1412. if (!intentState) {
  1413. intentState = Object.create(null);
  1414. this._intentStates.set(cacheKey, intentState);
  1415. }
  1416. // Ensure that a pending `streamReader` cancel timeout is always aborted.
  1417. if (intentState.streamReaderCancelTimeout) {
  1418. clearTimeout(intentState.streamReaderCancelTimeout);
  1419. intentState.streamReaderCancelTimeout = null;
  1420. }
  1421. const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);
  1422. // If there's no displayReadyCapability yet, then the operatorList
  1423. // was never requested before. Make the request and create the promise.
  1424. if (!intentState.displayReadyCapability) {
  1425. intentState.displayReadyCapability = Promise.withResolvers();
  1426. intentState.operatorList = {
  1427. fnArray: [],
  1428. argsArray: [],
  1429. lastChunk: false,
  1430. separateAnnots: null,
  1431. };
  1432. this._stats?.time("Page Request");
  1433. this._pumpOperatorList(intentArgs);
  1434. }
  1435. const complete = error => {
  1436. intentState.renderTasks.delete(internalRenderTask);
  1437. // Attempt to reduce memory usage during *printing*, by always running
  1438. // cleanup immediately once rendering has finished.
  1439. if (intentPrint) {
  1440. this.#pendingCleanup = true;
  1441. }
  1442. this.#tryCleanup();
  1443. if (error) {
  1444. internalRenderTask.capability.reject(error);
  1445. this._abortOperatorList({
  1446. intentState,
  1447. reason: error instanceof Error ? error : new Error(error),
  1448. });
  1449. } else {
  1450. internalRenderTask.capability.resolve();
  1451. }
  1452. if (this._stats) {
  1453. this._stats.timeEnd("Rendering");
  1454. this._stats.timeEnd("Overall");
  1455. if (globalThis.Stats?.enabled) {
  1456. globalThis.Stats.add(this.pageNumber, this._stats);
  1457. }
  1458. }
  1459. };
  1460. const internalRenderTask = new InternalRenderTask({
  1461. callback: complete,
  1462. // Only include the required properties, and *not* the entire object.
  1463. params: {
  1464. canvasContext,
  1465. viewport,
  1466. transform,
  1467. background,
  1468. },
  1469. objs: this.objs,
  1470. commonObjs: this.commonObjs,
  1471. annotationCanvasMap,
  1472. operatorList: intentState.operatorList,
  1473. pageIndex: this._pageIndex,
  1474. canvasFactory: this._transport.canvasFactory,
  1475. filterFactory: this._transport.filterFactory,
  1476. useRequestAnimationFrame: !intentPrint,
  1477. pdfBug: this._pdfBug,
  1478. pageColors,
  1479. });
  1480. (intentState.renderTasks ||= new Set()).add(internalRenderTask);
  1481. const renderTask = internalRenderTask.task;
  1482. Promise.all([
  1483. intentState.displayReadyCapability.promise,
  1484. optionalContentConfigPromise,
  1485. ])
  1486. .then(([transparency, optionalContentConfig]) => {
  1487. if (this.destroyed) {
  1488. complete();
  1489. return;
  1490. }
  1491. this._stats?.time("Rendering");
  1492. if (!(optionalContentConfig.renderingIntent & renderingIntent)) {
  1493. throw new Error(
  1494. "Must use the same `intent`-argument when calling the `PDFPageProxy.render` " +
  1495. "and `PDFDocumentProxy.getOptionalContentConfig` methods."
  1496. );
  1497. }
  1498. internalRenderTask.initializeGraphics({
  1499. transparency,
  1500. optionalContentConfig,
  1501. });
  1502. internalRenderTask.operatorListChanged();
  1503. })
  1504. .catch(complete);
  1505. return renderTask;
  1506. }
  1507. /**
  1508. * @param {GetOperatorListParameters} params - Page getOperatorList
  1509. * parameters.
  1510. * @returns {Promise<PDFOperatorList>} A promise resolved with an
  1511. * {@link PDFOperatorList} object that represents the page's operator list.
  1512. */
  1513. getOperatorList({
  1514. intent = "display",
  1515. annotationMode = AnnotationMode.ENABLE,
  1516. printAnnotationStorage = null,
  1517. isEditing = false,
  1518. } = {}) {
  1519. if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("GENERIC")) {
  1520. throw new Error("Not implemented: getOperatorList");
  1521. }
  1522. function operatorListChanged() {
  1523. if (intentState.operatorList.lastChunk) {
  1524. intentState.opListReadCapability.resolve(intentState.operatorList);
  1525. intentState.renderTasks.delete(opListTask);
  1526. }
  1527. }
  1528. const intentArgs = this._transport.getRenderingIntent(
  1529. intent,
  1530. annotationMode,
  1531. printAnnotationStorage,
  1532. isEditing,
  1533. /* isOpList = */ true
  1534. );
  1535. let intentState = this._intentStates.get(intentArgs.cacheKey);
  1536. if (!intentState) {
  1537. intentState = Object.create(null);
  1538. this._intentStates.set(intentArgs.cacheKey, intentState);
  1539. }
  1540. let opListTask;
  1541. if (!intentState.opListReadCapability) {
  1542. opListTask = Object.create(null);
  1543. opListTask.operatorListChanged = operatorListChanged;
  1544. intentState.opListReadCapability = Promise.withResolvers();
  1545. (intentState.renderTasks ||= new Set()).add(opListTask);
  1546. intentState.operatorList = {
  1547. fnArray: [],
  1548. argsArray: [],
  1549. lastChunk: false,
  1550. separateAnnots: null,
  1551. };
  1552. this._stats?.time("Page Request");
  1553. this._pumpOperatorList(intentArgs);
  1554. }
  1555. return intentState.opListReadCapability.promise;
  1556. }
  1557. /**
  1558. * NOTE: All occurrences of whitespace will be replaced by
  1559. * standard spaces (0x20).
  1560. *
  1561. * @param {getTextContentParameters} params - getTextContent parameters.
  1562. * @returns {ReadableStream} Stream for reading text content chunks.
  1563. */
  1564. streamTextContent({
  1565. includeMarkedContent = false,
  1566. disableNormalization = false,
  1567. } = {}) {
  1568. const TEXT_CONTENT_CHUNK_SIZE = 100;
  1569. return this._transport.messageHandler.sendWithStream(
  1570. "GetTextContent",
  1571. {
  1572. pageIndex: this._pageIndex,
  1573. includeMarkedContent: includeMarkedContent === true,
  1574. disableNormalization: disableNormalization === true,
  1575. },
  1576. {
  1577. highWaterMark: TEXT_CONTENT_CHUNK_SIZE,
  1578. size(textContent) {
  1579. return textContent.items.length;
  1580. },
  1581. }
  1582. );
  1583. }
  1584. /**
  1585. * NOTE: All occurrences of whitespace will be replaced by
  1586. * standard spaces (0x20).
  1587. *
  1588. * @param {getTextContentParameters} params - getTextContent parameters.
  1589. * @returns {Promise<TextContent>} A promise that is resolved with a
  1590. * {@link TextContent} object that represents the page's text content.
  1591. */
  1592. getTextContent(params = {}) {
  1593. if (this._transport._htmlForXfa) {
  1594. // TODO: We need to revisit this once the XFA foreground patch lands and
  1595. // only do this for non-foreground XFA.
  1596. return this.getXfa().then(xfa => XfaText.textContent(xfa));
  1597. }
  1598. const readableStream = this.streamTextContent(params);
  1599. return new Promise(function (resolve, reject) {
  1600. function pump() {
  1601. reader.read().then(function ({ value, done }) {
  1602. if (done) {
  1603. resolve(textContent);
  1604. return;
  1605. }
  1606. textContent.lang ??= value.lang;
  1607. Object.assign(textContent.styles, value.styles);
  1608. textContent.items.push(...value.items);
  1609. pump();
  1610. }, reject);
  1611. }
  1612. const reader = readableStream.getReader();
  1613. const textContent = {
  1614. items: [],
  1615. styles: Object.create(null),
  1616. lang: null,
  1617. };
  1618. pump();
  1619. });
  1620. }
  1621. /**
  1622. * @returns {Promise<StructTreeNode>} A promise that is resolved with a
  1623. * {@link StructTreeNode} object that represents the page's structure tree,
  1624. * or `null` when no structure tree is present for the current page.
  1625. */
  1626. getStructTree() {
  1627. return this._transport.getStructTree(this._pageIndex);
  1628. }
  1629. /**
  1630. * Destroys the page object.
  1631. * @private
  1632. */
  1633. _destroy() {
  1634. this.destroyed = true;
  1635. const waitOn = [];
  1636. for (const intentState of this._intentStates.values()) {
  1637. this._abortOperatorList({
  1638. intentState,
  1639. reason: new Error("Page was destroyed."),
  1640. force: true,
  1641. });
  1642. if (intentState.opListReadCapability) {
  1643. // Avoid errors below, since the renderTasks are just stubs.
  1644. continue;
  1645. }
  1646. for (const internalRenderTask of intentState.renderTasks) {
  1647. waitOn.push(internalRenderTask.completed);
  1648. internalRenderTask.cancel();
  1649. }
  1650. }
  1651. this.objs.clear();
  1652. this.#pendingCleanup = false;
  1653. return Promise.all(waitOn);
  1654. }
  1655. /**
  1656. * Cleans up resources allocated by the page.
  1657. *
  1658. * @param {boolean} [resetStats] - Reset page stats, if enabled.
  1659. * The default value is `false`.
  1660. * @returns {boolean} Indicates if clean-up was successfully run.
  1661. */
  1662. cleanup(resetStats = false) {
  1663. this.#pendingCleanup = true;
  1664. const success = this.#tryCleanup();
  1665. if (resetStats && success) {
  1666. this._stats &&= new StatTimer();
  1667. }
  1668. return success;
  1669. }
  1670. /**
  1671. * Attempts to clean up if rendering is in a state where that's possible.
  1672. * @returns {boolean} Indicates if clean-up was successfully run.
  1673. */
  1674. #tryCleanup() {
  1675. if (!this.#pendingCleanup || this.destroyed) {
  1676. return false;
  1677. }
  1678. for (const { renderTasks, operatorList } of this._intentStates.values()) {
  1679. if (renderTasks.size > 0 || !operatorList.lastChunk) {
  1680. return false;
  1681. }
  1682. }
  1683. this._intentStates.clear();
  1684. this.objs.clear();
  1685. this.#pendingCleanup = false;
  1686. return true;
  1687. }
  1688. /**
  1689. * @private
  1690. */
  1691. _startRenderPage(transparency, cacheKey) {
  1692. const intentState = this._intentStates.get(cacheKey);
  1693. if (!intentState) {
  1694. return; // Rendering was cancelled.
  1695. }
  1696. this._stats?.timeEnd("Page Request");
  1697. // TODO Refactor RenderPageRequest to separate rendering
  1698. // and operator list logic
  1699. intentState.displayReadyCapability?.resolve(transparency);
  1700. }
  1701. /**
  1702. * @private
  1703. */
  1704. _renderPageChunk(operatorListChunk, intentState) {
  1705. // Add the new chunk to the current operator list.
  1706. for (let i = 0, ii = operatorListChunk.length; i < ii; i++) {
  1707. intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
  1708. intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
  1709. }
  1710. intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
  1711. intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots;
  1712. // Notify all the rendering tasks there are more operators to be consumed.
  1713. for (const internalRenderTask of intentState.renderTasks) {
  1714. internalRenderTask.operatorListChanged();
  1715. }
  1716. if (operatorListChunk.lastChunk) {
  1717. this.#tryCleanup();
  1718. }
  1719. }
  1720. /**
  1721. * @private
  1722. */
  1723. _pumpOperatorList({
  1724. renderingIntent,
  1725. cacheKey,
  1726. annotationStorageSerializable,
  1727. modifiedIds,
  1728. }) {
  1729. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
  1730. assert(
  1731. Number.isInteger(renderingIntent) && renderingIntent > 0,
  1732. '_pumpOperatorList: Expected valid "renderingIntent" argument.'
  1733. );
  1734. }
  1735. const { map, transfer } = annotationStorageSerializable;
  1736. const readableStream = this._transport.messageHandler.sendWithStream(
  1737. "GetOperatorList",
  1738. {
  1739. pageIndex: this._pageIndex,
  1740. intent: renderingIntent,
  1741. cacheKey,
  1742. annotationStorage: map,
  1743. modifiedIds,
  1744. },
  1745. transfer
  1746. );
  1747. const reader = readableStream.getReader();
  1748. const intentState = this._intentStates.get(cacheKey);
  1749. intentState.streamReader = reader;
  1750. const pump = () => {
  1751. reader.read().then(
  1752. ({ value, done }) => {
  1753. if (done) {
  1754. intentState.streamReader = null;
  1755. return;
  1756. }
  1757. if (this._transport.destroyed) {
  1758. return; // Ignore any pending requests if the worker was terminated.
  1759. }
  1760. this._renderPageChunk(value, intentState);
  1761. pump();
  1762. },
  1763. reason => {
  1764. intentState.streamReader = null;
  1765. if (this._transport.destroyed) {
  1766. return; // Ignore any pending requests if the worker was terminated.
  1767. }
  1768. if (intentState.operatorList) {
  1769. // Mark operator list as complete.
  1770. intentState.operatorList.lastChunk = true;
  1771. for (const internalRenderTask of intentState.renderTasks) {
  1772. internalRenderTask.operatorListChanged();
  1773. }
  1774. this.#tryCleanup();
  1775. }
  1776. if (intentState.displayReadyCapability) {
  1777. intentState.displayReadyCapability.reject(reason);
  1778. } else if (intentState.opListReadCapability) {
  1779. intentState.opListReadCapability.reject(reason);
  1780. } else {
  1781. throw reason;
  1782. }
  1783. }
  1784. );
  1785. };
  1786. pump();
  1787. }
  1788. /**
  1789. * @private
  1790. */
  1791. _abortOperatorList({ intentState, reason, force = false }) {
  1792. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
  1793. assert(
  1794. reason instanceof Error,
  1795. '_abortOperatorList: Expected valid "reason" argument.'
  1796. );
  1797. }
  1798. if (!intentState.streamReader) {
  1799. return;
  1800. }
  1801. // Ensure that a pending `streamReader` cancel timeout is always aborted.
  1802. if (intentState.streamReaderCancelTimeout) {
  1803. clearTimeout(intentState.streamReaderCancelTimeout);
  1804. intentState.streamReaderCancelTimeout = null;
  1805. }
  1806. if (!force) {
  1807. // Ensure that an Error occurring in *only* one `InternalRenderTask`, e.g.
  1808. // multiple render() calls on the same canvas, won't break all rendering.
  1809. if (intentState.renderTasks.size > 0) {
  1810. return;
  1811. }
  1812. // Don't immediately abort parsing on the worker-thread when rendering is
  1813. // cancelled, since that will unnecessarily delay re-rendering when (for
  1814. // partially parsed pages) e.g. zooming/rotation occurs in the viewer.
  1815. if (reason instanceof RenderingCancelledException) {
  1816. let delay = RENDERING_CANCELLED_TIMEOUT;
  1817. if (reason.extraDelay > 0 && reason.extraDelay < /* ms = */ 1000) {
  1818. // Above, we prevent the total delay from becoming arbitrarily large.
  1819. delay += reason.extraDelay;
  1820. }
  1821. intentState.streamReaderCancelTimeout = setTimeout(() => {
  1822. intentState.streamReaderCancelTimeout = null;
  1823. this._abortOperatorList({ intentState, reason, force: true });
  1824. }, delay);
  1825. return;
  1826. }
  1827. }
  1828. intentState.streamReader
  1829. .cancel(new AbortException(reason.message))
  1830. .catch(() => {
  1831. // Avoid "Uncaught promise" messages in the console.
  1832. });
  1833. intentState.streamReader = null;
  1834. if (this._transport.destroyed) {
  1835. return; // Ignore any pending requests if the worker was terminated.
  1836. }
  1837. // Remove the current `intentState`, since a cancelled `getOperatorList`
  1838. // call on the worker-thread cannot be re-started...
  1839. for (const [curCacheKey, curIntentState] of this._intentStates) {
  1840. if (curIntentState === intentState) {
  1841. this._intentStates.delete(curCacheKey);
  1842. break;
  1843. }
  1844. }
  1845. // ... and force clean-up to ensure that any old state is always removed.
  1846. this.cleanup();
  1847. }
  1848. /**
  1849. * @type {StatTimer | null} Returns page stats, if enabled; returns `null`
  1850. * otherwise.
  1851. */
  1852. get stats() {
  1853. return this._stats;
  1854. }
  1855. }
  1856. class LoopbackPort {
  1857. #listeners = new Map();
  1858. #deferred = Promise.resolve();
  1859. postMessage(obj, transfer) {
  1860. const event = {
  1861. data: structuredClone(obj, transfer ? { transfer } : null),
  1862. };
  1863. this.#deferred.then(() => {
  1864. for (const [listener] of this.#listeners) {
  1865. listener.call(this, event);
  1866. }
  1867. });
  1868. }
  1869. addEventListener(name, listener, options = null) {
  1870. let rmAbort = null;
  1871. if (options?.signal instanceof AbortSignal) {
  1872. const { signal } = options;
  1873. if (signal.aborted) {
  1874. warn("LoopbackPort - cannot use an `aborted` signal.");
  1875. return;
  1876. }
  1877. const onAbort = () => this.removeEventListener(name, listener);
  1878. rmAbort = () => signal.removeEventListener("abort", onAbort);
  1879. signal.addEventListener("abort", onAbort);
  1880. }
  1881. this.#listeners.set(listener, rmAbort);
  1882. }
  1883. removeEventListener(name, listener) {
  1884. const rmAbort = this.#listeners.get(listener);
  1885. rmAbort?.();
  1886. this.#listeners.delete(listener);
  1887. }
  1888. terminate() {
  1889. for (const [, rmAbort] of this.#listeners) {
  1890. rmAbort?.();
  1891. }
  1892. this.#listeners.clear();
  1893. }
  1894. }
  1895. /**
  1896. * @typedef {Object} PDFWorkerParameters
  1897. * @property {string} [name] - The name of the worker.
  1898. * @property {Worker} [port] - The `workerPort` object.
  1899. * @property {number} [verbosity] - Controls the logging level;
  1900. * the constants from {@link VerbosityLevel} should be used.
  1901. */
  1902. /**
  1903. * PDF.js web worker abstraction that controls the instantiation of PDF
  1904. * documents. Message handlers are used to pass information from the main
  1905. * thread to the worker thread and vice versa. If the creation of a web
  1906. * worker is not possible, a "fake" worker will be used instead.
  1907. *
  1908. * @param {PDFWorkerParameters} params - The worker initialization parameters.
  1909. */
  1910. class PDFWorker {
  1911. static #fakeWorkerId = 0;
  1912. static #isWorkerDisabled = false;
  1913. static #workerPorts;
  1914. static {
  1915. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
  1916. if (isNodeJS) {
  1917. // Workers aren't supported in Node.js, force-disabling them there.
  1918. this.#isWorkerDisabled = true;
  1919. GlobalWorkerOptions.workerSrc ||= PDFJSDev.test("LIB")
  1920. ? "../pdf.worker.js"
  1921. : "./pdf.worker.mjs";
  1922. }
  1923. // Check if URLs have the same origin. For non-HTTP based URLs, returns
  1924. // false.
  1925. this._isSameOrigin = (baseUrl, otherUrl) => {
  1926. const base = URL.parse(baseUrl);
  1927. if (!base?.origin || base.origin === "null") {
  1928. return false; // non-HTTP url
  1929. }
  1930. const other = new URL(otherUrl, base);
  1931. return base.origin === other.origin;
  1932. };
  1933. this._createCDNWrapper = url => {
  1934. // We will rely on blob URL's property to specify origin.
  1935. // We want this function to fail in case if createObjectURL or Blob do
  1936. // not exist or fail for some reason -- our Worker creation will fail
  1937. // anyway.
  1938. const wrapper = `await import("${url}");`;
  1939. return URL.createObjectURL(
  1940. new Blob([wrapper], { type: "text/javascript" })
  1941. );
  1942. };
  1943. }
  1944. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
  1945. this._resetGlobalState = () => {
  1946. this.#isWorkerDisabled = false;
  1947. delete globalThis.pdfjsWorker;
  1948. };
  1949. }
  1950. }
  1951. constructor({
  1952. name = null,
  1953. port = null,
  1954. verbosity = getVerbosityLevel(),
  1955. } = {}) {
  1956. this.name = name;
  1957. this.destroyed = false;
  1958. this.verbosity = verbosity;
  1959. this._readyCapability = Promise.withResolvers();
  1960. this._port = null;
  1961. this._webWorker = null;
  1962. this._messageHandler = null;
  1963. if (
  1964. (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) &&
  1965. port
  1966. ) {
  1967. if (PDFWorker.#workerPorts?.has(port)) {
  1968. throw new Error("Cannot use more than one PDFWorker per port.");
  1969. }
  1970. (PDFWorker.#workerPorts ||= new WeakMap()).set(port, this);
  1971. this._initializeFromPort(port);
  1972. return;
  1973. }
  1974. this._initialize();
  1975. }
  1976. /**
  1977. * Promise for worker initialization completion.
  1978. * @type {Promise<void>}
  1979. */
  1980. get promise() {
  1981. return this._readyCapability.promise;
  1982. }
  1983. #resolve() {
  1984. this._readyCapability.resolve();
  1985. // Send global setting, e.g. verbosity level.
  1986. this._messageHandler.send("configure", {
  1987. verbosity: this.verbosity,
  1988. });
  1989. }
  1990. /**
  1991. * The current `workerPort`, when it exists.
  1992. * @type {Worker}
  1993. */
  1994. get port() {
  1995. return this._port;
  1996. }
  1997. /**
  1998. * The current MessageHandler-instance.
  1999. * @type {MessageHandler}
  2000. */
  2001. get messageHandler() {
  2002. return this._messageHandler;
  2003. }
  2004. _initializeFromPort(port) {
  2005. if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
  2006. throw new Error("Not implemented: _initializeFromPort");
  2007. }
  2008. this._port = port;
  2009. this._messageHandler = new MessageHandler("main", "worker", port);
  2010. this._messageHandler.on("ready", function () {
  2011. // Ignoring "ready" event -- MessageHandler should already be initialized
  2012. // and ready to accept messages.
  2013. });
  2014. this.#resolve();
  2015. }
  2016. _initialize() {
  2017. // If worker support isn't disabled explicit and the browser has worker
  2018. // support, create a new web worker and test if it/the browser fulfills
  2019. // all requirements to run parts of pdf.js in a web worker.
  2020. // Right now, the requirement is, that an Uint8Array is still an
  2021. // Uint8Array as it arrives on the worker.
  2022. if (
  2023. PDFWorker.#isWorkerDisabled ||
  2024. PDFWorker.#mainThreadWorkerMessageHandler
  2025. ) {
  2026. this._setupFakeWorker();
  2027. return;
  2028. }
  2029. let { workerSrc } = PDFWorker;
  2030. try {
  2031. // Wraps workerSrc path into blob URL, if the former does not belong
  2032. // to the same origin.
  2033. if (
  2034. typeof PDFJSDev !== "undefined" &&
  2035. PDFJSDev.test("GENERIC") &&
  2036. !PDFWorker._isSameOrigin(window.location, workerSrc)
  2037. ) {
  2038. workerSrc = PDFWorker._createCDNWrapper(
  2039. new URL(workerSrc, window.location).href
  2040. );
  2041. }
  2042. const worker = new Worker(workerSrc, { type: "module" });
  2043. const messageHandler = new MessageHandler("main", "worker", worker);
  2044. const terminateEarly = () => {
  2045. ac.abort();
  2046. messageHandler.destroy();
  2047. worker.terminate();
  2048. if (this.destroyed) {
  2049. this._readyCapability.reject(new Error("Worker was destroyed"));
  2050. } else {
  2051. // Fall back to fake worker if the termination is caused by an
  2052. // error (e.g. NetworkError / SecurityError).
  2053. this._setupFakeWorker();
  2054. }
  2055. };
  2056. const ac = new AbortController();
  2057. worker.addEventListener(
  2058. "error",
  2059. () => {
  2060. if (!this._webWorker) {
  2061. // Worker failed to initialize due to an error. Clean up and fall
  2062. // back to the fake worker.
  2063. terminateEarly();
  2064. }
  2065. },
  2066. { signal: ac.signal }
  2067. );
  2068. messageHandler.on("test", data => {
  2069. ac.abort();
  2070. if (this.destroyed || !data) {
  2071. terminateEarly();
  2072. return;
  2073. }
  2074. this._messageHandler = messageHandler;
  2075. this._port = worker;
  2076. this._webWorker = worker;
  2077. this.#resolve();
  2078. });
  2079. messageHandler.on("ready", data => {
  2080. ac.abort();
  2081. if (this.destroyed) {
  2082. terminateEarly();
  2083. return;
  2084. }
  2085. try {
  2086. sendTest();
  2087. } catch {
  2088. // We need fallback to a faked worker.
  2089. this._setupFakeWorker();
  2090. }
  2091. });
  2092. const sendTest = () => {
  2093. const testObj = new Uint8Array();
  2094. // Ensure that we can use `postMessage` transfers.
  2095. messageHandler.send("test", testObj, [testObj.buffer]);
  2096. };
  2097. // It might take time for the worker to initialize. We will try to send
  2098. // the "test" message immediately, and once the "ready" message arrives.
  2099. // The worker shall process only the first received "test" message.
  2100. sendTest();
  2101. return;
  2102. } catch {
  2103. info("The worker has been disabled.");
  2104. }
  2105. // Either workers are not supported or have thrown an exception.
  2106. // Thus, we fallback to a faked worker.
  2107. this._setupFakeWorker();
  2108. }
  2109. _setupFakeWorker() {
  2110. if (!PDFWorker.#isWorkerDisabled) {
  2111. warn("Setting up fake worker.");
  2112. PDFWorker.#isWorkerDisabled = true;
  2113. }
  2114. PDFWorker._setupFakeWorkerGlobal
  2115. .then(WorkerMessageHandler => {
  2116. if (this.destroyed) {
  2117. this._readyCapability.reject(new Error("Worker was destroyed"));
  2118. return;
  2119. }
  2120. const port = new LoopbackPort();
  2121. this._port = port;
  2122. // All fake workers use the same port, making id unique.
  2123. const id = `fake${PDFWorker.#fakeWorkerId++}`;
  2124. // If the main thread is our worker, setup the handling for the
  2125. // messages -- the main thread sends to it self.
  2126. const workerHandler = new MessageHandler(id + "_worker", id, port);
  2127. WorkerMessageHandler.setup(workerHandler, port);
  2128. this._messageHandler = new MessageHandler(id, id + "_worker", port);
  2129. this.#resolve();
  2130. })
  2131. .catch(reason => {
  2132. this._readyCapability.reject(
  2133. new Error(`Setting up fake worker failed: "${reason.message}".`)
  2134. );
  2135. });
  2136. }
  2137. /**
  2138. * Destroys the worker instance.
  2139. */
  2140. destroy() {
  2141. this.destroyed = true;
  2142. // We need to terminate only web worker created resource.
  2143. this._webWorker?.terminate();
  2144. this._webWorker = null;
  2145. PDFWorker.#workerPorts?.delete(this._port);
  2146. this._port = null;
  2147. this._messageHandler?.destroy();
  2148. this._messageHandler = null;
  2149. }
  2150. /**
  2151. * @param {PDFWorkerParameters} params - The worker initialization parameters.
  2152. * @returns {PDFWorker}
  2153. */
  2154. static fromPort(params) {
  2155. if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
  2156. throw new Error("Not implemented: fromPort");
  2157. }
  2158. if (!params?.port) {
  2159. throw new Error("PDFWorker.fromPort - invalid method signature.");
  2160. }
  2161. const cachedPort = this.#workerPorts?.get(params.port);
  2162. if (cachedPort) {
  2163. if (cachedPort._pendingDestroy) {
  2164. throw new Error(
  2165. "PDFWorker.fromPort - the worker is being destroyed.\n" +
  2166. "Please remember to await `PDFDocumentLoadingTask.destroy()`-calls."
  2167. );
  2168. }
  2169. return cachedPort;
  2170. }
  2171. return new PDFWorker(params);
  2172. }
  2173. /**
  2174. * The current `workerSrc`, when it exists.
  2175. * @type {string}
  2176. */
  2177. static get workerSrc() {
  2178. if (GlobalWorkerOptions.workerSrc) {
  2179. return GlobalWorkerOptions.workerSrc;
  2180. }
  2181. throw new Error('No "GlobalWorkerOptions.workerSrc" specified.');
  2182. }
  2183. static get #mainThreadWorkerMessageHandler() {
  2184. try {
  2185. return globalThis.pdfjsWorker?.WorkerMessageHandler || null;
  2186. } catch {
  2187. return null;
  2188. }
  2189. }
  2190. // Loads worker code into the main-thread.
  2191. static get _setupFakeWorkerGlobal() {
  2192. const loader = async () => {
  2193. if (this.#mainThreadWorkerMessageHandler) {
  2194. // The worker was already loaded using e.g. a `<script>` tag.
  2195. return this.#mainThreadWorkerMessageHandler;
  2196. }
  2197. const worker =
  2198. typeof PDFJSDev === "undefined"
  2199. ? await import("pdfjs/pdf.worker.js")
  2200. : await __raw_import__(this.workerSrc);
  2201. return worker.WorkerMessageHandler;
  2202. };
  2203. return shadow(this, "_setupFakeWorkerGlobal", loader());
  2204. }
  2205. }
  2206. /**
  2207. * For internal use only.
  2208. * @ignore
  2209. */
  2210. class WorkerTransport {
  2211. #methodPromises = new Map();
  2212. #pageCache = new Map();
  2213. #pagePromises = new Map();
  2214. #pageRefCache = new Map();
  2215. #passwordCapability = null;
  2216. constructor(messageHandler, loadingTask, networkStream, params, factory) {
  2217. this.messageHandler = messageHandler;
  2218. this.loadingTask = loadingTask;
  2219. this.commonObjs = new PDFObjects();
  2220. this.fontLoader = new FontLoader({
  2221. ownerDocument: params.ownerDocument,
  2222. styleElement: params.styleElement,
  2223. });
  2224. this.loadingParams = params.loadingParams;
  2225. this._params = params;
  2226. this.canvasFactory = factory.canvasFactory;
  2227. this.filterFactory = factory.filterFactory;
  2228. this.cMapReaderFactory = factory.cMapReaderFactory;
  2229. this.standardFontDataFactory = factory.standardFontDataFactory;
  2230. this.wasmFactory = factory.wasmFactory;
  2231. this.destroyed = false;
  2232. this.destroyCapability = null;
  2233. this._networkStream = networkStream;
  2234. this._fullReader = null;
  2235. this._lastProgress = null;
  2236. this.downloadInfoCapability = Promise.withResolvers();
  2237. this.setupMessageHandler();
  2238. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
  2239. // For testing purposes.
  2240. Object.defineProperty(this, "getNetworkStreamName", {
  2241. value: () => networkStream?.constructor?.name || null,
  2242. });
  2243. Object.defineProperty(this, "getXFADatasets", {
  2244. value: () =>
  2245. this.messageHandler.sendWithPromise("GetXFADatasets", null),
  2246. });
  2247. Object.defineProperty(this, "getXRefPrevValue", {
  2248. value: () =>
  2249. this.messageHandler.sendWithPromise("GetXRefPrevValue", null),
  2250. });
  2251. Object.defineProperty(this, "getStartXRefPos", {
  2252. value: () =>
  2253. this.messageHandler.sendWithPromise("GetStartXRefPos", null),
  2254. });
  2255. Object.defineProperty(this, "getAnnotArray", {
  2256. value: pageIndex =>
  2257. this.messageHandler.sendWithPromise("GetAnnotArray", { pageIndex }),
  2258. });
  2259. }
  2260. }
  2261. #cacheSimpleMethod(name, data = null) {
  2262. const cachedPromise = this.#methodPromises.get(name);
  2263. if (cachedPromise) {
  2264. return cachedPromise;
  2265. }
  2266. const promise = this.messageHandler.sendWithPromise(name, data);
  2267. this.#methodPromises.set(name, promise);
  2268. return promise;
  2269. }
  2270. get annotationStorage() {
  2271. return shadow(this, "annotationStorage", new AnnotationStorage());
  2272. }
  2273. getRenderingIntent(
  2274. intent,
  2275. annotationMode = AnnotationMode.ENABLE,
  2276. printAnnotationStorage = null,
  2277. isEditing = false,
  2278. isOpList = false
  2279. ) {
  2280. let renderingIntent = RenderingIntentFlag.DISPLAY; // Default value.
  2281. let annotationStorageSerializable = SerializableEmpty;
  2282. switch (intent) {
  2283. case "any":
  2284. renderingIntent = RenderingIntentFlag.ANY;
  2285. break;
  2286. case "display":
  2287. break;
  2288. case "print":
  2289. renderingIntent = RenderingIntentFlag.PRINT;
  2290. break;
  2291. default:
  2292. warn(`getRenderingIntent - invalid intent: ${intent}`);
  2293. }
  2294. const annotationStorage =
  2295. renderingIntent & RenderingIntentFlag.PRINT &&
  2296. printAnnotationStorage instanceof PrintAnnotationStorage
  2297. ? printAnnotationStorage
  2298. : this.annotationStorage;
  2299. switch (annotationMode) {
  2300. case AnnotationMode.DISABLE:
  2301. renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISABLE;
  2302. break;
  2303. case AnnotationMode.ENABLE:
  2304. break;
  2305. case AnnotationMode.ENABLE_FORMS:
  2306. renderingIntent += RenderingIntentFlag.ANNOTATIONS_FORMS;
  2307. break;
  2308. case AnnotationMode.ENABLE_STORAGE:
  2309. renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE;
  2310. annotationStorageSerializable = annotationStorage.serializable;
  2311. break;
  2312. default:
  2313. warn(`getRenderingIntent - invalid annotationMode: ${annotationMode}`);
  2314. }
  2315. if (isEditing) {
  2316. renderingIntent += RenderingIntentFlag.IS_EDITING;
  2317. }
  2318. if (isOpList) {
  2319. renderingIntent += RenderingIntentFlag.OPLIST;
  2320. }
  2321. const { ids: modifiedIds, hash: modifiedIdsHash } =
  2322. annotationStorage.modifiedIds;
  2323. const cacheKeyBuf = [
  2324. renderingIntent,
  2325. annotationStorageSerializable.hash,
  2326. modifiedIdsHash,
  2327. ];
  2328. return {
  2329. renderingIntent,
  2330. cacheKey: cacheKeyBuf.join("_"),
  2331. annotationStorageSerializable,
  2332. modifiedIds,
  2333. };
  2334. }
  2335. destroy() {
  2336. if (this.destroyCapability) {
  2337. return this.destroyCapability.promise;
  2338. }
  2339. this.destroyed = true;
  2340. this.destroyCapability = Promise.withResolvers();
  2341. this.#passwordCapability?.reject(
  2342. new Error("Worker was destroyed during onPassword callback")
  2343. );
  2344. const waitOn = [];
  2345. // We need to wait for all renderings to be completed, e.g.
  2346. // timeout/rAF can take a long time.
  2347. for (const page of this.#pageCache.values()) {
  2348. waitOn.push(page._destroy());
  2349. }
  2350. this.#pageCache.clear();
  2351. this.#pagePromises.clear();
  2352. this.#pageRefCache.clear();
  2353. // Allow `AnnotationStorage`-related clean-up when destroying the document.
  2354. if (this.hasOwnProperty("annotationStorage")) {
  2355. this.annotationStorage.resetModified();
  2356. }
  2357. // We also need to wait for the worker to finish its long running tasks.
  2358. const terminated = this.messageHandler.sendWithPromise("Terminate", null);
  2359. waitOn.push(terminated);
  2360. Promise.all(waitOn).then(() => {
  2361. this.commonObjs.clear();
  2362. this.fontLoader.clear();
  2363. this.#methodPromises.clear();
  2364. this.filterFactory.destroy();
  2365. TextLayer.cleanup();
  2366. this._networkStream?.cancelAllRequests(
  2367. new AbortException("Worker was terminated.")
  2368. );
  2369. this.messageHandler?.destroy();
  2370. this.messageHandler = null;
  2371. this.destroyCapability.resolve();
  2372. }, this.destroyCapability.reject);
  2373. return this.destroyCapability.promise;
  2374. }
  2375. setupMessageHandler() {
  2376. const { messageHandler, loadingTask } = this;
  2377. messageHandler.on("GetReader", (data, sink) => {
  2378. assert(
  2379. this._networkStream,
  2380. "GetReader - no `IPDFStream` instance available."
  2381. );
  2382. this._fullReader = this._networkStream.getFullReader();
  2383. this._fullReader.onProgress = evt => {
  2384. this._lastProgress = {
  2385. loaded: evt.loaded,
  2386. total: evt.total,
  2387. };
  2388. };
  2389. sink.onPull = () => {
  2390. this._fullReader
  2391. .read()
  2392. .then(function ({ value, done }) {
  2393. if (done) {
  2394. sink.close();
  2395. return;
  2396. }
  2397. assert(
  2398. value instanceof ArrayBuffer,
  2399. "GetReader - expected an ArrayBuffer."
  2400. );
  2401. // Enqueue data chunk into sink, and transfer it
  2402. // to other side as `Transferable` object.
  2403. sink.enqueue(new Uint8Array(value), 1, [value]);
  2404. })
  2405. .catch(reason => {
  2406. sink.error(reason);
  2407. });
  2408. };
  2409. sink.onCancel = reason => {
  2410. this._fullReader.cancel(reason);
  2411. sink.ready.catch(readyReason => {
  2412. if (this.destroyed) {
  2413. return; // Ignore any pending requests if the worker was terminated.
  2414. }
  2415. throw readyReason;
  2416. });
  2417. };
  2418. });
  2419. messageHandler.on("ReaderHeadersReady", async data => {
  2420. await this._fullReader.headersReady;
  2421. const { isStreamingSupported, isRangeSupported, contentLength } =
  2422. this._fullReader;
  2423. // If stream or range are disabled, it's our only way to report
  2424. // loading progress.
  2425. if (!isStreamingSupported || !isRangeSupported) {
  2426. if (this._lastProgress) {
  2427. loadingTask.onProgress?.(this._lastProgress);
  2428. }
  2429. this._fullReader.onProgress = evt => {
  2430. loadingTask.onProgress?.({
  2431. loaded: evt.loaded,
  2432. total: evt.total,
  2433. });
  2434. };
  2435. }
  2436. return { isStreamingSupported, isRangeSupported, contentLength };
  2437. });
  2438. messageHandler.on("GetRangeReader", (data, sink) => {
  2439. assert(
  2440. this._networkStream,
  2441. "GetRangeReader - no `IPDFStream` instance available."
  2442. );
  2443. const rangeReader = this._networkStream.getRangeReader(
  2444. data.begin,
  2445. data.end
  2446. );
  2447. // When streaming is enabled, it's possible that the data requested here
  2448. // has already been fetched via the `_fullRequestReader` implementation.
  2449. // However, given that the PDF data is loaded asynchronously on the
  2450. // main-thread and then sent via `postMessage` to the worker-thread,
  2451. // it may not have been available during parsing (hence the attempt to
  2452. // use range requests here).
  2453. //
  2454. // To avoid wasting time and resources here, we'll thus *not* dispatch
  2455. // range requests if the data was already loaded but has not been sent to
  2456. // the worker-thread yet (which will happen via the `_fullRequestReader`).
  2457. if (!rangeReader) {
  2458. sink.close();
  2459. return;
  2460. }
  2461. sink.onPull = () => {
  2462. rangeReader
  2463. .read()
  2464. .then(function ({ value, done }) {
  2465. if (done) {
  2466. sink.close();
  2467. return;
  2468. }
  2469. assert(
  2470. value instanceof ArrayBuffer,
  2471. "GetRangeReader - expected an ArrayBuffer."
  2472. );
  2473. sink.enqueue(new Uint8Array(value), 1, [value]);
  2474. })
  2475. .catch(reason => {
  2476. sink.error(reason);
  2477. });
  2478. };
  2479. sink.onCancel = reason => {
  2480. rangeReader.cancel(reason);
  2481. sink.ready.catch(readyReason => {
  2482. if (this.destroyed) {
  2483. return; // Ignore any pending requests if the worker was terminated.
  2484. }
  2485. throw readyReason;
  2486. });
  2487. };
  2488. });
  2489. messageHandler.on("GetDoc", ({ pdfInfo }) => {
  2490. this._numPages = pdfInfo.numPages;
  2491. this._htmlForXfa = pdfInfo.htmlForXfa;
  2492. delete pdfInfo.htmlForXfa;
  2493. loadingTask._capability.resolve(new PDFDocumentProxy(pdfInfo, this));
  2494. });
  2495. messageHandler.on("DocException", ex => {
  2496. loadingTask._capability.reject(wrapReason(ex));
  2497. });
  2498. messageHandler.on("PasswordRequest", ex => {
  2499. this.#passwordCapability = Promise.withResolvers();
  2500. try {
  2501. if (!loadingTask.onPassword) {
  2502. throw wrapReason(ex);
  2503. }
  2504. const updatePassword = password => {
  2505. if (password instanceof Error) {
  2506. this.#passwordCapability.reject(password);
  2507. } else {
  2508. this.#passwordCapability.resolve({ password });
  2509. }
  2510. };
  2511. loadingTask.onPassword(updatePassword, ex.code);
  2512. } catch (err) {
  2513. this.#passwordCapability.reject(err);
  2514. }
  2515. return this.#passwordCapability.promise;
  2516. });
  2517. messageHandler.on("DataLoaded", data => {
  2518. // For consistency: Ensure that progress is always reported when the
  2519. // entire PDF file has been loaded, regardless of how it was fetched.
  2520. loadingTask.onProgress?.({
  2521. loaded: data.length,
  2522. total: data.length,
  2523. });
  2524. this.downloadInfoCapability.resolve(data);
  2525. });
  2526. messageHandler.on("StartRenderPage", data => {
  2527. if (this.destroyed) {
  2528. return; // Ignore any pending requests if the worker was terminated.
  2529. }
  2530. const page = this.#pageCache.get(data.pageIndex);
  2531. page._startRenderPage(data.transparency, data.cacheKey);
  2532. });
  2533. messageHandler.on("commonobj", ([id, type, exportedData]) => {
  2534. if (this.destroyed) {
  2535. return null; // Ignore any pending requests if the worker was terminated.
  2536. }
  2537. if (this.commonObjs.has(id)) {
  2538. return null;
  2539. }
  2540. switch (type) {
  2541. case "Font":
  2542. if ("error" in exportedData) {
  2543. const exportedError = exportedData.error;
  2544. warn(`Error during font loading: ${exportedError}`);
  2545. this.commonObjs.resolve(id, exportedError);
  2546. break;
  2547. }
  2548. const inspectFont =
  2549. this._params.pdfBug && globalThis.FontInspector?.enabled
  2550. ? (font, url) => globalThis.FontInspector.fontAdded(font, url)
  2551. : null;
  2552. const font = new FontFaceObject(exportedData, inspectFont);
  2553. this.fontLoader
  2554. .bind(font)
  2555. .catch(() => messageHandler.sendWithPromise("FontFallback", { id }))
  2556. .finally(() => {
  2557. if (!font.fontExtraProperties && font.data) {
  2558. // Immediately release the `font.data` property once the font
  2559. // has been attached to the DOM, since it's no longer needed,
  2560. // rather than waiting for a `PDFDocumentProxy.cleanup` call.
  2561. // Since `font.data` could be very large, e.g. in some cases
  2562. // multiple megabytes, this will help reduce memory usage.
  2563. font.data = null;
  2564. }
  2565. this.commonObjs.resolve(id, font);
  2566. });
  2567. break;
  2568. case "CopyLocalImage":
  2569. const { imageRef } = exportedData;
  2570. assert(imageRef, "The imageRef must be defined.");
  2571. for (const pageProxy of this.#pageCache.values()) {
  2572. for (const [, data] of pageProxy.objs) {
  2573. if (data?.ref !== imageRef) {
  2574. continue;
  2575. }
  2576. if (!data.dataLen) {
  2577. return null;
  2578. }
  2579. this.commonObjs.resolve(id, structuredClone(data));
  2580. return data.dataLen;
  2581. }
  2582. }
  2583. break;
  2584. case "FontPath":
  2585. case "Image":
  2586. case "Pattern":
  2587. this.commonObjs.resolve(id, exportedData);
  2588. break;
  2589. default:
  2590. throw new Error(`Got unknown common object type ${type}`);
  2591. }
  2592. return null;
  2593. });
  2594. messageHandler.on("obj", ([id, pageIndex, type, imageData]) => {
  2595. if (this.destroyed) {
  2596. // Ignore any pending requests if the worker was terminated.
  2597. return;
  2598. }
  2599. const pageProxy = this.#pageCache.get(pageIndex);
  2600. if (pageProxy.objs.has(id)) {
  2601. return;
  2602. }
  2603. // Don't store data *after* cleanup has successfully run, see bug 1854145.
  2604. if (pageProxy._intentStates.size === 0) {
  2605. imageData?.bitmap?.close(); // Release any `ImageBitmap` data.
  2606. return;
  2607. }
  2608. switch (type) {
  2609. case "Image":
  2610. case "Pattern":
  2611. pageProxy.objs.resolve(id, imageData);
  2612. break;
  2613. default:
  2614. throw new Error(`Got unknown object type ${type}`);
  2615. }
  2616. });
  2617. messageHandler.on("DocProgress", data => {
  2618. if (this.destroyed) {
  2619. return; // Ignore any pending requests if the worker was terminated.
  2620. }
  2621. loadingTask.onProgress?.({
  2622. loaded: data.loaded,
  2623. total: data.total,
  2624. });
  2625. });
  2626. messageHandler.on("FetchBinaryData", async data => {
  2627. if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
  2628. throw new Error("Not implemented: FetchBinaryData");
  2629. }
  2630. if (this.destroyed) {
  2631. throw new Error("Worker was destroyed.");
  2632. }
  2633. const factory = this[data.type];
  2634. if (!factory) {
  2635. throw new Error(
  2636. `${data.type} not initialized, see the \`useWorkerFetch\` parameter.`
  2637. );
  2638. }
  2639. return factory.fetch(data);
  2640. });
  2641. }
  2642. getData() {
  2643. return this.messageHandler.sendWithPromise("GetData", null);
  2644. }
  2645. saveDocument() {
  2646. if (this.annotationStorage.size <= 0) {
  2647. warn(
  2648. "saveDocument called while `annotationStorage` is empty, " +
  2649. "please use the getData-method instead."
  2650. );
  2651. }
  2652. const { map, transfer } = this.annotationStorage.serializable;
  2653. return this.messageHandler
  2654. .sendWithPromise(
  2655. "SaveDocument",
  2656. {
  2657. isPureXfa: !!this._htmlForXfa,
  2658. numPages: this._numPages,
  2659. annotationStorage: map,
  2660. filename: this._fullReader?.filename ?? null,
  2661. },
  2662. transfer
  2663. )
  2664. .finally(() => {
  2665. this.annotationStorage.resetModified();
  2666. });
  2667. }
  2668. getPage(pageNumber) {
  2669. if (
  2670. !Number.isInteger(pageNumber) ||
  2671. pageNumber <= 0 ||
  2672. pageNumber > this._numPages
  2673. ) {
  2674. return Promise.reject(new Error("Invalid page request."));
  2675. }
  2676. const pageIndex = pageNumber - 1,
  2677. cachedPromise = this.#pagePromises.get(pageIndex);
  2678. if (cachedPromise) {
  2679. return cachedPromise;
  2680. }
  2681. const promise = this.messageHandler
  2682. .sendWithPromise("GetPage", {
  2683. pageIndex,
  2684. })
  2685. .then(pageInfo => {
  2686. if (this.destroyed) {
  2687. throw new Error("Transport destroyed");
  2688. }
  2689. if (pageInfo.refStr) {
  2690. this.#pageRefCache.set(pageInfo.refStr, pageNumber);
  2691. }
  2692. const page = new PDFPageProxy(
  2693. pageIndex,
  2694. pageInfo,
  2695. this,
  2696. this._params.pdfBug
  2697. );
  2698. this.#pageCache.set(pageIndex, page);
  2699. return page;
  2700. });
  2701. this.#pagePromises.set(pageIndex, promise);
  2702. return promise;
  2703. }
  2704. getPageIndex(ref) {
  2705. if (!isRefProxy(ref)) {
  2706. return Promise.reject(new Error("Invalid pageIndex request."));
  2707. }
  2708. return this.messageHandler.sendWithPromise("GetPageIndex", {
  2709. num: ref.num,
  2710. gen: ref.gen,
  2711. });
  2712. }
  2713. getAnnotations(pageIndex, intent) {
  2714. return this.messageHandler.sendWithPromise("GetAnnotations", {
  2715. pageIndex,
  2716. intent,
  2717. });
  2718. }
  2719. getFieldObjects() {
  2720. return this.#cacheSimpleMethod("GetFieldObjects");
  2721. }
  2722. hasJSActions() {
  2723. return this.#cacheSimpleMethod("HasJSActions");
  2724. }
  2725. getCalculationOrderIds() {
  2726. return this.messageHandler.sendWithPromise("GetCalculationOrderIds", null);
  2727. }
  2728. getDestinations() {
  2729. return this.messageHandler.sendWithPromise("GetDestinations", null);
  2730. }
  2731. getDestination(id) {
  2732. if (typeof id !== "string") {
  2733. return Promise.reject(new Error("Invalid destination request."));
  2734. }
  2735. return this.messageHandler.sendWithPromise("GetDestination", {
  2736. id,
  2737. });
  2738. }
  2739. getPageLabels() {
  2740. return this.messageHandler.sendWithPromise("GetPageLabels", null);
  2741. }
  2742. getPageLayout() {
  2743. return this.messageHandler.sendWithPromise("GetPageLayout", null);
  2744. }
  2745. getPageMode() {
  2746. return this.messageHandler.sendWithPromise("GetPageMode", null);
  2747. }
  2748. getViewerPreferences() {
  2749. return this.messageHandler.sendWithPromise("GetViewerPreferences", null);
  2750. }
  2751. getOpenAction() {
  2752. return this.messageHandler.sendWithPromise("GetOpenAction", null);
  2753. }
  2754. getAttachments() {
  2755. return this.messageHandler.sendWithPromise("GetAttachments", null);
  2756. }
  2757. getDocJSActions() {
  2758. return this.#cacheSimpleMethod("GetDocJSActions");
  2759. }
  2760. getPageJSActions(pageIndex) {
  2761. return this.messageHandler.sendWithPromise("GetPageJSActions", {
  2762. pageIndex,
  2763. });
  2764. }
  2765. getStructTree(pageIndex) {
  2766. return this.messageHandler.sendWithPromise("GetStructTree", {
  2767. pageIndex,
  2768. });
  2769. }
  2770. getOutline() {
  2771. return this.messageHandler.sendWithPromise("GetOutline", null);
  2772. }
  2773. getOptionalContentConfig(renderingIntent) {
  2774. return this.#cacheSimpleMethod("GetOptionalContentConfig").then(
  2775. data => new OptionalContentConfig(data, renderingIntent)
  2776. );
  2777. }
  2778. getPermissions() {
  2779. return this.messageHandler.sendWithPromise("GetPermissions", null);
  2780. }
  2781. getMetadata() {
  2782. const name = "GetMetadata",
  2783. cachedPromise = this.#methodPromises.get(name);
  2784. if (cachedPromise) {
  2785. return cachedPromise;
  2786. }
  2787. const promise = this.messageHandler
  2788. .sendWithPromise(name, null)
  2789. .then(results => ({
  2790. info: results[0],
  2791. metadata: results[1] ? new Metadata(results[1]) : null,
  2792. contentDispositionFilename: this._fullReader?.filename ?? null,
  2793. contentLength: this._fullReader?.contentLength ?? null,
  2794. }));
  2795. this.#methodPromises.set(name, promise);
  2796. return promise;
  2797. }
  2798. getMarkInfo() {
  2799. return this.messageHandler.sendWithPromise("GetMarkInfo", null);
  2800. }
  2801. async startCleanup(keepLoadedFonts = false) {
  2802. if (this.destroyed) {
  2803. return; // No need to manually clean-up when destruction has started.
  2804. }
  2805. await this.messageHandler.sendWithPromise("Cleanup", null);
  2806. for (const page of this.#pageCache.values()) {
  2807. const cleanupSuccessful = page.cleanup();
  2808. if (!cleanupSuccessful) {
  2809. throw new Error(
  2810. `startCleanup: Page ${page.pageNumber} is currently rendering.`
  2811. );
  2812. }
  2813. }
  2814. this.commonObjs.clear();
  2815. if (!keepLoadedFonts) {
  2816. this.fontLoader.clear();
  2817. }
  2818. this.#methodPromises.clear();
  2819. this.filterFactory.destroy(/* keepHCM = */ true);
  2820. TextLayer.cleanup();
  2821. }
  2822. cachedPageNumber(ref) {
  2823. if (!isRefProxy(ref)) {
  2824. return null;
  2825. }
  2826. const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`;
  2827. return this.#pageRefCache.get(refStr) ?? null;
  2828. }
  2829. }
  2830. const INITIAL_DATA = Symbol("INITIAL_DATA");
  2831. /**
  2832. * A PDF document and page is built of many objects. E.g. there are objects for
  2833. * fonts, images, rendering code, etc. These objects may get processed inside of
  2834. * a worker. This class implements some basic methods to manage these objects.
  2835. */
  2836. class PDFObjects {
  2837. #objs = Object.create(null);
  2838. /**
  2839. * Ensures there is an object defined for `objId`.
  2840. *
  2841. * @param {string} objId
  2842. * @returns {Object}
  2843. */
  2844. #ensureObj(objId) {
  2845. return (this.#objs[objId] ||= {
  2846. ...Promise.withResolvers(),
  2847. data: INITIAL_DATA,
  2848. });
  2849. }
  2850. /**
  2851. * If called *without* callback, this returns the data of `objId` but the
  2852. * object needs to be resolved. If it isn't, this method throws.
  2853. *
  2854. * If called *with* a callback, the callback is called with the data of the
  2855. * object once the object is resolved. That means, if you call this method
  2856. * and the object is already resolved, the callback gets called right away.
  2857. *
  2858. * @param {string} objId
  2859. * @param {function} [callback]
  2860. * @returns {any}
  2861. */
  2862. get(objId, callback = null) {
  2863. // If there is a callback, then the get can be async and the object is
  2864. // not required to be resolved right now.
  2865. if (callback) {
  2866. const obj = this.#ensureObj(objId);
  2867. obj.promise.then(() => callback(obj.data));
  2868. return null;
  2869. }
  2870. // If there isn't a callback, the user expects to get the resolved data
  2871. // directly.
  2872. const obj = this.#objs[objId];
  2873. // If there isn't an object yet or the object isn't resolved, then the
  2874. // data isn't ready yet!
  2875. if (!obj || obj.data === INITIAL_DATA) {
  2876. throw new Error(`Requesting object that isn't resolved yet ${objId}.`);
  2877. }
  2878. return obj.data;
  2879. }
  2880. /**
  2881. * @param {string} objId
  2882. * @returns {boolean}
  2883. */
  2884. has(objId) {
  2885. const obj = this.#objs[objId];
  2886. return !!obj && obj.data !== INITIAL_DATA;
  2887. }
  2888. /**
  2889. * @param {string} objId
  2890. * @returns {boolean}
  2891. */
  2892. delete(objId) {
  2893. const obj = this.#objs[objId];
  2894. if (!obj || obj.data === INITIAL_DATA) {
  2895. // Only allow removing the object *after* it's been resolved.
  2896. return false;
  2897. }
  2898. delete this.#objs[objId];
  2899. return true;
  2900. }
  2901. /**
  2902. * Resolves the object `objId` with optional `data`.
  2903. *
  2904. * @param {string} objId
  2905. * @param {any} [data]
  2906. */
  2907. resolve(objId, data = null) {
  2908. const obj = this.#ensureObj(objId);
  2909. obj.data = data;
  2910. obj.resolve();
  2911. }
  2912. clear() {
  2913. for (const objId in this.#objs) {
  2914. const { data } = this.#objs[objId];
  2915. data?.bitmap?.close(); // Release any `ImageBitmap` data.
  2916. }
  2917. this.#objs = Object.create(null);
  2918. }
  2919. *[Symbol.iterator]() {
  2920. for (const objId in this.#objs) {
  2921. const { data } = this.#objs[objId];
  2922. if (data === INITIAL_DATA) {
  2923. continue;
  2924. }
  2925. yield [objId, data];
  2926. }
  2927. }
  2928. }
  2929. /**
  2930. * Allows controlling of the rendering tasks.
  2931. */
  2932. class RenderTask {
  2933. #internalRenderTask = null;
  2934. /**
  2935. * Callback for incremental rendering -- a function that will be called
  2936. * each time the rendering is paused. To continue rendering call the
  2937. * function that is the first argument to the callback.
  2938. * @type {function}
  2939. */
  2940. onContinue = null;
  2941. /**
  2942. * A function that will be synchronously called when the rendering tasks
  2943. * finishes with an error (either because of an actual error, or because the
  2944. * rendering is cancelled).
  2945. *
  2946. * @type {function}
  2947. * @param {Error} error
  2948. */
  2949. onError = null;
  2950. constructor(internalRenderTask) {
  2951. this.#internalRenderTask = internalRenderTask;
  2952. if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
  2953. // For testing purposes.
  2954. Object.defineProperty(this, "getOperatorList", {
  2955. value: () => this.#internalRenderTask.operatorList,
  2956. });
  2957. }
  2958. }
  2959. /**
  2960. * Promise for rendering task completion.
  2961. * @type {Promise<void>}
  2962. */
  2963. get promise() {
  2964. return this.#internalRenderTask.capability.promise;
  2965. }
  2966. /**
  2967. * Cancels the rendering task. If the task is currently rendering it will
  2968. * not be cancelled until graphics pauses with a timeout. The promise that
  2969. * this object extends will be rejected when cancelled.
  2970. *
  2971. * @param {number} [extraDelay]
  2972. */
  2973. cancel(extraDelay = 0) {
  2974. this.#internalRenderTask.cancel(/* error = */ null, extraDelay);
  2975. }
  2976. /**
  2977. * Whether form fields are rendered separately from the main operatorList.
  2978. * @type {boolean}
  2979. */
  2980. get separateAnnots() {
  2981. const { separateAnnots } = this.#internalRenderTask.operatorList;
  2982. if (!separateAnnots) {
  2983. return false;
  2984. }
  2985. const { annotationCanvasMap } = this.#internalRenderTask;
  2986. return (
  2987. separateAnnots.form ||
  2988. (separateAnnots.canvas && annotationCanvasMap?.size > 0)
  2989. );
  2990. }
  2991. }
  2992. /**
  2993. * For internal use only.
  2994. * @ignore
  2995. */
  2996. class InternalRenderTask {
  2997. #rAF = null;
  2998. static #canvasInUse = new WeakSet();
  2999. constructor({
  3000. callback,
  3001. params,
  3002. objs,
  3003. commonObjs,
  3004. annotationCanvasMap,
  3005. operatorList,
  3006. pageIndex,
  3007. canvasFactory,
  3008. filterFactory,
  3009. useRequestAnimationFrame = false,
  3010. pdfBug = false,
  3011. pageColors = null,
  3012. }) {
  3013. this.callback = callback;
  3014. this.params = params;
  3015. this.objs = objs;
  3016. this.commonObjs = commonObjs;
  3017. this.annotationCanvasMap = annotationCanvasMap;
  3018. this.operatorListIdx = null;
  3019. this.operatorList = operatorList;
  3020. this._pageIndex = pageIndex;
  3021. this.canvasFactory = canvasFactory;
  3022. this.filterFactory = filterFactory;
  3023. this._pdfBug = pdfBug;
  3024. this.pageColors = pageColors;
  3025. this.running = false;
  3026. this.graphicsReadyCallback = null;
  3027. this.graphicsReady = false;
  3028. this._useRequestAnimationFrame =
  3029. useRequestAnimationFrame === true && typeof window !== "undefined";
  3030. this.cancelled = false;
  3031. this.capability = Promise.withResolvers();
  3032. this.task = new RenderTask(this);
  3033. // caching this-bound methods
  3034. this._cancelBound = this.cancel.bind(this);
  3035. this._continueBound = this._continue.bind(this);
  3036. this._scheduleNextBound = this._scheduleNext.bind(this);
  3037. this._nextBound = this._next.bind(this);
  3038. this._canvas = params.canvasContext.canvas;
  3039. }
  3040. get completed() {
  3041. return this.capability.promise.catch(function () {
  3042. // Ignoring errors, since we only want to know when rendering is
  3043. // no longer pending.
  3044. });
  3045. }
  3046. initializeGraphics({ transparency = false, optionalContentConfig }) {
  3047. if (this.cancelled) {
  3048. return;
  3049. }
  3050. if (this._canvas) {
  3051. if (InternalRenderTask.#canvasInUse.has(this._canvas)) {
  3052. throw new Error(
  3053. "Cannot use the same canvas during multiple render() operations. " +
  3054. "Use different canvas or ensure previous operations were " +
  3055. "cancelled or completed."
  3056. );
  3057. }
  3058. InternalRenderTask.#canvasInUse.add(this._canvas);
  3059. }
  3060. if (this._pdfBug && globalThis.StepperManager?.enabled) {
  3061. this.stepper = globalThis.StepperManager.create(this._pageIndex);
  3062. this.stepper.init(this.operatorList);
  3063. this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
  3064. }
  3065. const { canvasContext, viewport, transform, background } = this.params;
  3066. this.gfx = new CanvasGraphics(
  3067. canvasContext,
  3068. this.commonObjs,
  3069. this.objs,
  3070. this.canvasFactory,
  3071. this.filterFactory,
  3072. { optionalContentConfig },
  3073. this.annotationCanvasMap,
  3074. this.pageColors
  3075. );
  3076. this.gfx.beginDrawing({
  3077. transform,
  3078. viewport,
  3079. transparency,
  3080. background,
  3081. });
  3082. this.operatorListIdx = 0;
  3083. this.graphicsReady = true;
  3084. this.graphicsReadyCallback?.();
  3085. }
  3086. cancel(error = null, extraDelay = 0) {
  3087. this.running = false;
  3088. this.cancelled = true;
  3089. this.gfx?.endDrawing();
  3090. if (this.#rAF) {
  3091. window.cancelAnimationFrame(this.#rAF);
  3092. this.#rAF = null;
  3093. }
  3094. InternalRenderTask.#canvasInUse.delete(this._canvas);
  3095. error ||= new RenderingCancelledException(
  3096. `Rendering cancelled, page ${this._pageIndex + 1}`,
  3097. extraDelay
  3098. );
  3099. this.callback(error);
  3100. this.task.onError?.(error);
  3101. }
  3102. operatorListChanged() {
  3103. if (!this.graphicsReady) {
  3104. this.graphicsReadyCallback ||= this._continueBound;
  3105. return;
  3106. }
  3107. this.stepper?.updateOperatorList(this.operatorList);
  3108. if (this.running) {
  3109. return;
  3110. }
  3111. this._continue();
  3112. }
  3113. _continue() {
  3114. this.running = true;
  3115. if (this.cancelled) {
  3116. return;
  3117. }
  3118. if (this.task.onContinue) {
  3119. this.task.onContinue(this._scheduleNextBound);
  3120. } else {
  3121. this._scheduleNext();
  3122. }
  3123. }
  3124. _scheduleNext() {
  3125. if (this._useRequestAnimationFrame) {
  3126. this.#rAF = window.requestAnimationFrame(() => {
  3127. this.#rAF = null;
  3128. this._nextBound().catch(this._cancelBound);
  3129. });
  3130. } else {
  3131. Promise.resolve().then(this._nextBound).catch(this._cancelBound);
  3132. }
  3133. }
  3134. async _next() {
  3135. if (this.cancelled) {
  3136. return;
  3137. }
  3138. this.operatorListIdx = this.gfx.executeOperatorList(
  3139. this.operatorList,
  3140. this.operatorListIdx,
  3141. this._continueBound,
  3142. this.stepper
  3143. );
  3144. if (this.operatorListIdx === this.operatorList.argsArray.length) {
  3145. this.running = false;
  3146. if (this.operatorList.lastChunk) {
  3147. this.gfx.endDrawing();
  3148. InternalRenderTask.#canvasInUse.delete(this._canvas);
  3149. this.callback();
  3150. }
  3151. }
  3152. }
  3153. }
  3154. /** @type {string} */
  3155. const version =
  3156. typeof PDFJSDev !== "undefined" ? PDFJSDev.eval("BUNDLE_VERSION") : null;
  3157. /** @type {string} */
  3158. const build =
  3159. typeof PDFJSDev !== "undefined" ? PDFJSDev.eval("BUNDLE_BUILD") : null;
  3160. export {
  3161. build,
  3162. getDocument,
  3163. isValidExplicitDest,
  3164. LoopbackPort,
  3165. PDFDataRangeTransport,
  3166. PDFDocumentLoadingTask,
  3167. PDFDocumentProxy,
  3168. PDFPageProxy,
  3169. PDFWorker,
  3170. RenderTask,
  3171. version,
  3172. };