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