▶preamble[L1-L233]233 lines
▼function getDocument[257 lines]
234function getDocument(src = {}) {
235 const task = new PDFDocumentLoadingTask();
236 const { docId } = task;
237
238 const url = src.url ? getUrlProp(src.url) : null;
239 const data = src.data ? getDataProp(src.data) : null;
240 const httpHeaders = src.httpHeaders || null;
241 const withCredentials = src.withCredentials === true;
242 const password = src.password ?? null;
243 const rangeTransport =
244 src.range instanceof PDFDataRangeTransport ? src.range : null;
245 const rangeChunkSize =
246 Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0
247 ? src.rangeChunkSize
248 : 2 ** 16;
249 let worker = src.worker instanceof PDFWorker ? src.worker : null;
250 const verbosity = src.verbosity;
251 // Ignore "data:"-URLs, since they can't be used to recover valid absolute
252 // URLs anyway. We want to avoid sending them to the worker-thread, since
253 // they contain the *entire* PDF document and can thus be arbitrarily long.
254 const docBaseUrl =
255 typeof src.docBaseUrl === "string" && !isDataScheme(src.docBaseUrl)
256 ? src.docBaseUrl
257 : null;
258 const cMapUrl = getFactoryUrlProp(src.cMapUrl);
259 const cMapPacked = src.cMapPacked !== false;
260 const iccUrl = getFactoryUrlProp(src.iccUrl);
261 const standardFontDataUrl = getFactoryUrlProp(src.standardFontDataUrl);
262 const wasmUrl = getFactoryUrlProp(src.wasmUrl);
263 const ignoreErrors = src.stopAtErrors !== true;
264 const maxImageSize =
265 Number.isInteger(src.maxImageSize) && src.maxImageSize > -1
266 ? src.maxImageSize
267 : -1;
268 const isOffscreenCanvasSupported =
269 typeof src.isOffscreenCanvasSupported === "boolean"
270 ? src.isOffscreenCanvasSupported
271 : !isNodeJS;
272 const isImageDecoderSupported =
273 typeof src.isImageDecoderSupported === "boolean"
274 ? src.isImageDecoderSupported
275 : !isNodeJS;
276 const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes)
277 ? src.canvasMaxAreaInBytes
278 : -1;
279 const disableFontFace =
280 typeof src.disableFontFace === "boolean" ? src.disableFontFace : isNodeJS;
281 const fontExtraProperties = src.fontExtraProperties === true;
282 const enableXfa = src.enableXfa === true;
283 const ownerDocument = src.ownerDocument || globalThis.document;
284 const disableRange = src.disableRange === true;
285 const disableStream = src.disableStream === true;
286 const disableAutoFetch = src.disableAutoFetch === true;
287 const pdfBug = src.pdfBug === true;
288 const CanvasFactory =
289 src.CanvasFactory ||
290 (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS
291 ? NodeCanvasFactory
292 : DOMCanvasFactory);
293 const FilterFactory =
294 src.FilterFactory ||
295 (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS
296 ? NodeFilterFactory
297 : DOMFilterFactory);
298 const BinaryDataFactory =
299 src.BinaryDataFactory ||
300 (typeof PDFJSDev !== "undefined" && PDFJSDev.test("GENERIC") && isNodeJS
301 ? NodeBinaryDataFactory
302 : DOMBinaryDataFactory);
303 const enableHWA = src.enableHWA === true;
304 const enableWebGPU = src.enableWebGPU === true;
305 // Start GPU initialisation immediately so it runs in parallel with the
306 // worker bootstrap; the resolved boolean is forwarded to the worker.
307 const gpuPromise = enableWebGPU ? initGPU() : Promise.resolve(false);
308 const useWasm = src.useWasm !== false;
309 const pagesMapper = src.pagesMapper || new PagesMapper();
310
311 // Parameters whose default values depend on other parameters.
312 const useSystemFonts =
313 typeof src.useSystemFonts === "boolean"
314 ? src.useSystemFonts
315 : !isNodeJS && !disableFontFace;
316 const useWorkerFetch =
317 typeof src.useWorkerFetch === "boolean"
318 ? src.useWorkerFetch
319 : (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
320 !!(
321 BinaryDataFactory === DOMBinaryDataFactory &&
322 cMapUrl &&
323 cMapPacked &&
324 standardFontDataUrl &&
325 wasmUrl &&
326 isValidFetchUrl(cMapUrl, document.baseURI) &&
327 isValidFetchUrl(standardFontDataUrl, document.baseURI) &&
328 isValidFetchUrl(wasmUrl, document.baseURI)
329 );
330
331 // Parameters only intended for development/testing purposes.
332 const styleElement =
333 typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")
334 ? src.styleElement
335 : null;
336
337 // Set the main-thread verbosity level.
338 setVerbosityLevel(verbosity);
339
340 // Ensure that the various factories can be initialized, when necessary,
341 // since the user may provide *custom* ones.
342 const transportFactory = {
343 canvasFactory: new CanvasFactory({ ownerDocument, enableHWA }),
344 filterFactory: new FilterFactory({ docId, ownerDocument }),
345 binaryDataFactory:
346 (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
347 useWorkerFetch
348 ? null
349 : new BinaryDataFactory({ cMapUrl, standardFontDataUrl, wasmUrl }),
350 };
351
352 if (!worker) {
353 // Worker was not provided -- creating and owning our own. If message port
354 // is specified in global worker options, using it.
355 worker = PDFWorker.create({
356 verbosity,
357 port: GlobalWorkerOptions.workerPort,
358 });
359 task._worker = worker;
360 }
361
362 const docParams = {
363 docId,
364 apiVersion:
365 typeof PDFJSDev !== "undefined" && !PDFJSDev.test("TESTING")
366 ? PDFJSDev.eval("BUNDLE_VERSION")
367 : null,
368 data,
369 password,
370 disableAutoFetch,
371 rangeChunkSize,
372 docBaseUrl,
373 enableXfa,
374 evaluatorOptions: {
375 maxImageSize,
376 disableFontFace,
377 ignoreErrors,
378 isOffscreenCanvasSupported,
379 isImageDecoderSupported,
380 canvasMaxAreaInBytes,
381 fontExtraProperties,
382 useSystemFonts,
383 useWasm,
384 useWorkerFetch,
385 cMapUrl,
386 cMapPacked,
387 iccUrl,
388 standardFontDataUrl,
389 wasmUrl,
390 hasGPU: false, // Set below.
391 },
392 };
393 const transportParams = {
394 ownerDocument,
395 pdfBug,
396 styleElement,
397 enableHWA,
398 loadingParams: {
399 disableAutoFetch,
400 enableXfa,
401 },
402 };
403
404 Promise.all([worker.promise, gpuPromise])
405 .then(function ([, hasGPU]) {
406 if (worker.destroyed) {
407 throw new Error("Worker was destroyed");
408 }
409
410 docParams.evaluatorOptions.hasGPU = hasGPU;
411
412 const workerIdPromise = worker.messageHandler.sendWithPromise(
413 "GetDocRequest",
414 docParams,
415 data ? [data.buffer] : null
416 );
417
418 let networkStream;
419 if (data) {
420 // The entire PDF was provided, no `networkStream` necessary.
421 } else if (rangeTransport) {
422 networkStream = new PDFDataTransportStream({
423 pdfDataRangeTransport: rangeTransport,
424 disableRange,
425 disableStream,
426 });
427 } else if (url) {
428 if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
429 throw new Error("Not implemented: NetworkStream");
430 }
431 const NetworkStream = getNetworkStream(url);
432
433 networkStream = new NetworkStream({
434 url,
435 httpHeaders,
436 withCredentials,
437 rangeChunkSize,
438 disableRange,
439 disableStream,
440 });
441 } else {
442 throw new Error(
443 "getDocument - expected either `data`, `range`, or `url` parameter."
444 );
445 }
446
447 return workerIdPromise.then(workerId => {
448 if (worker.destroyed) {
449 throw new Error("Worker was destroyed");
450 }
451
452 const messageHandler = new MessageHandler(docId, workerId, worker.port);
453 const transport = new WorkerTransport(
454 messageHandler,
455 task,
456 networkStream,
457 transportParams,
458 transportFactory,
459 pagesMapper
460 );
461 task._transport = transport;
462
463 if (task.destroyed) {
464 // `destroy()` was called during the worker handshake; the orderly
465 // shutdown (including the "Terminate" message) will be issued
466 // through the transport once destroy resumes.
467 throw new Error("Loading aborted");
468 }
469 messageHandler.send("Ready", null);
470 });
471 })
472 .catch(task._capability.reject)
473 .finally(task._setupCapability.resolve);
474
475 return task;
476}
477
478/**
479 * @typedef {Object} OnProgressParameters
480 * @property {number} loaded - Currently loaded number of bytes.
481 * @property {number} total - Total number of bytes in the PDF file.
482 * @property {number} percent - Currently loaded percentage, as an integer value
483 * in the [0, 100] range. If `total` is undefined, the percentage is `NaN`.
484 */
485
486/**
487 * The loading task controls the operations required to load a PDF document
488 * (such as network requests) and provides a way to listen for completion,
489 * after which individual pages can be rendered.
490 */
▼class PDFDocumentLoadingTask[119 lines]
491class PDFDocumentLoadingTask {
492 static #docId = 0;
493
494 /**
495 * @private
496 */
497 _capability = Promise.withResolvers();
498
499 /**
500 * Resolves once the load-time setup chain has settled, regardless of
501 * outcome; used by `destroy()` to wait until `_transport` is either set
502 * or definitely never going to be.
503 * @private
504 */
505 _setupCapability = Promise.withResolvers();
506
507 /**
508 * @private
509 */
510 _transport = null;
511
512 /**
513 * @private
514 */
515 _worker = null;
516
517 /**
518 * Unique identifier for the document loading task.
519 * @type {string}
520 */
521 docId = `d${PDFDocumentLoadingTask.#docId++}`;
522
523 /**
524 * Whether the loading task is destroyed or not.
525 * @type {boolean}
526 */
527 destroyed = false;
528
529 /**
530 * Callback to request a password if a wrong or no password was provided.
531 * The callback receives two parameters: a function that should be called
532 * with the new password, and a reason (see {@link PasswordResponses}).
533 * @type {function}
534 */
535 onPassword = null;
536
537 /**
538 * Callback to be able to monitor the loading progress of the PDF file
539 * (necessary to implement e.g. a loading bar).
540 * The callback receives an {@link OnProgressParameters} argument.
541 * @type {function}
542 */
543 onProgress = null;
544
545 /**
546 * Promise for document loading task completion.
547 * @type {Promise<PDFDocumentProxy>}
548 */
549 get promise() {
550 return this._capability.promise;
551 }
552
553 /**
554 * Abort all network requests and destroy the worker.
555 * @returns {Promise<void>} A promise that is resolved when destruction is
556 * completed.
557 */
558 async destroy() {
559 this.destroyed = true;
560 // The setup chain rejects `_capability` with "Loading aborted" once the
561 // load-time chain unwinds (see `getDocument`). Claim that rejection
562 // here so it isn't reported as unhandled during the awaits below;
563 // callers awaiting `task.promise` still see it.
564 this._capability.promise.catch(() => {});
565
566 try {
567 // `_pendingDestroy` must be set synchronously, before any `await`,
568 // so subsequent `PDFWorker.create()` calls on the shared `workerPort`
569 // observe it and throw (see issue 16777).
570 if (this._worker?.port) {
571 this._worker._pendingDestroy = true;
572 }
573 // Wait for the load-time setup chain to settle so `_transport` is set
574 // (when applicable) before we tear down. This is what guarantees the
575 // "Terminate" message gets sent through `WorkerTransport.destroy` if
576 // `destroy` races with the initial worker handshake.
577 await this._setupCapability.promise;
578
579 await this._transport?.destroy();
580 } catch (ex) {
581 if (this._worker?.port) {
582 delete this._worker._pendingDestroy;
583 }
584 throw ex;
585 }
586 this._transport = null;
587
588 this._worker?.destroy();
589 this._worker = null;
590 }
591
592 /**
593 * Attempt to fetch the raw data of the PDF document, when e.g.
594 * - An exception was thrown during document initialization.
595 * - An `onPassword` callback is delaying initialization.
596 * @returns {Promise<Uint8Array>}
597 */
598 async getData() {
599 return this._transport.getData();
600 }
601}
602
603/**
604 * Abstract class to support range requests file loading.
605 *
606 * NOTE: The TypedArrays passed to the constructor and relevant methods below
607 * will generally be transferred to the worker-thread. This will help reduce
608 * main-thread memory usage, however it will take ownership of the TypedArrays.
609 */
▼class PDFDataRangeTransport[65 lines]
610class PDFDataRangeTransport {
611 #capability = Promise.withResolvers();
612
613 #listener = null;
614
615 /**
616 * @param {number} length
617 * @param {Uint8Array|null} initialData
618 * @param {boolean} [progressiveDone]
619 * @param {string} [contentDispositionFilename]
620 */
621 constructor(
622 length,
623 initialData,
624 progressiveDone = false,
625 contentDispositionFilename = null
626 ) {
627 this.length = length;
628 this.initialData = initialData;
629 this.progressiveDone = progressiveDone;
630 this.contentDispositionFilename = contentDispositionFilename;
631 }
632
633 /**
634 * @param {number} begin
635 * @param {Uint8Array|null} chunk
636 */
637 onDataRange(begin, chunk) {
638 this.#listener({ type: "range", begin, chunk });
639 }
640
641 /**
642 * @param {Uint8Array|null} chunk
643 */
644 onDataProgressiveRead(chunk) {
645 this.#capability.promise.then(() => {
646 this.#listener({ type: "progressiveRead", chunk });
647 });
648 }
649
650 onDataProgressiveDone() {
651 this.#capability.promise.then(() => {
652 this.#listener({ type: "progressiveDone" });
653 });
654 }
655
656 transportReady(listener) {
657 this.#listener = listener;
658 this.#capability.resolve();
659 }
660
661 /**
662 * @param {number} begin
663 * @param {number} end
664 */
665 requestDataRange(begin, end) {
666 unreachable("Abstract method PDFDataRangeTransport.requestDataRange");
667 }
668
669 abort() {}
670}
671
672/**
673 * Proxy to a `PDFDocument` in the worker thread.
674 */
▼class PDFDocumentProxy[664 lines]
675class PDFDocumentProxy {
676 constructor(pdfInfo, transport) {
677 this._pdfInfo = pdfInfo;
678 this._transport = transport;
679
680 if (
681 typeof PDFJSDev === "undefined" ||
682 PDFJSDev.test("TESTING || INTERNAL_VIEWER")
683 ) {
684 // For the PDF debugger.
685 Object.defineProperty(this, "getRawData", {
686 value: data => this._transport.getRawData(data),
687 });
688 }
689 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
690 // For testing purposes.
691 Object.defineProperty(this, "getNetworkStreamName", {
692 value: () => this._transport.getNetworkStreamName(),
693 });
694 Object.defineProperty(this, "getXFADatasets", {
695 value: () => this._transport.getXFADatasets(),
696 });
697 Object.defineProperty(this, "getStartXRefPos", {
698 value: () => this._transport.getStartXRefPos(),
699 });
700 Object.defineProperty(this, "getAnnotArray", {
701 value: pageIndex => this._transport.getAnnotArray(pageIndex),
702 });
703 }
704 }
705
706 /**
707 * @type {PagesMapper} The pages mapper instance.
708 */
709 get pagesMapper() {
710 return this._transport.pagesMapper;
711 }
712
713 /**
714 * @type {AnnotationStorage} Storage for annotation data in forms.
715 */
716 get annotationStorage() {
717 return this._transport.annotationStorage;
718 }
719
720 /**
721 * @type {Object} The canvas factory instance.
722 */
723 get canvasFactory() {
724 return this._transport.canvasFactory;
725 }
726
727 /**
728 * @type {Object} The filter factory instance.
729 */
730 get filterFactory() {
731 return this._transport.filterFactory;
732 }
733
734 /**
735 * @type {number} Total number of pages in the PDF file.
736 */
737 get numPages() {
738 return this._pdfInfo.numPages;
739 }
740
741 /**
742 * @type {Array<string | null>} A (not guaranteed to be) unique ID to identify
743 * the PDF document.
744 * NOTE: The first element will always be defined for all PDF documents,
745 * whereas the second element is only defined for *modified* PDF documents.
746 */
747 get fingerprints() {
748 return this._pdfInfo.fingerprints;
749 }
750
751 /**
752 * @type {boolean} True if only XFA form.
753 */
754 get isPureXfa() {
755 return shadow(this, "isPureXfa", !!this._transport._htmlForXfa);
756 }
757
758 /**
759 * NOTE: This is (mostly) intended to support printing of XFA forms.
760 *
761 * @type {Object | null} An object representing a HTML tree structure
762 * to render the XFA, or `null` when no XFA form exists.
763 */
764 get allXfaHtml() {
765 return this._transport._htmlForXfa;
766 }
767
768 /**
769 * @param {number} pageNumber - The page number to get. The first page is 1.
770 * @returns {Promise<PDFPageProxy>} A promise that is resolved with
771 * a {@link PDFPageProxy} object.
772 */
773 getPage(pageNumber) {
774 return this._transport.getPage(pageNumber);
775 }
776
777 /**
778 * @param {RefProxy} ref - The page reference.
779 * @returns {Promise<number>} A promise that is resolved with the page index,
780 * starting from zero, that is associated with the reference.
781 */
782 getPageIndex(ref) {
783 return this._transport.getPageIndex(ref);
784 }
785
786 /**
787 * @returns {Promise<Map<string, Array<any>>>} A promise that is resolved
788 * with a mapping from named destinations to references.
789 *
790 * This can be slow for large documents. Use `getDestination` instead.
791 */
792 getDestinations() {
793 return this._transport.getDestinations();
794 }
795
796 /**
797 * @param {string} id - The named destination to get.
798 * @returns {Promise<Array<any> | null>} A promise that is resolved with all
799 * information of the given named destination, or `null` when the named
800 * destination is not present in the PDF file.
801 */
802 getDestination(id) {
803 return this._transport.getDestination(id);
804 }
805
806 /**
807 * @returns {Promise<Array<string> | null>} A promise that is resolved with
808 * an {Array} containing the page labels that correspond to the page
809 * indexes, or `null` when no page labels are present in the PDF file.
810 */
811 getPageLabels() {
812 return this._transport.getPageLabels();
813 }
814
815 /**
816 * @returns {Promise<string>} A promise that is resolved with a {string}
817 * containing the page layout name.
818 */
819 getPageLayout() {
820 return this._transport.getPageLayout();
821 }
822
823 /**
824 * @returns {Promise<string>} A promise that is resolved with a {string}
825 * containing the page mode name.
826 */
827 getPageMode() {
828 return this._transport.getPageMode();
829 }
830
831 /**
832 * @returns {Promise<Map | null>} A promise that is resolved with a {Map}
833 * containing the viewer preferences, or `null` when no viewer preferences
834 * are present in the PDF file.
835 */
836 getViewerPreferences() {
837 return this._transport.getViewerPreferences();
838 }
839
840 /**
841 * @returns {Promise<Map | null>} A promise that is resolved with a {Map}
842 * containing a destination or action, or `null` when no open action is
843 * present in the PDF.
844 */
845 getOpenAction() {
846 return this._transport.getOpenAction();
847 }
848
849 /**
850 * @returns {Promise<Map<string, CatalogAttachment> | null>}
851 * Promise that is resolved with a lookup table for mapping named
852 * attachments to their content.
853 */
854 getAttachments() {
855 return this._transport.getAttachments();
856 }
857
858 /**
859 * @param {string} id
860 * Unique attachment identifier (required).
861 * @returns {Promise<CatalogAttachmentContent>}
862 * Promise that resolves to attachment content.
863 */
864 getAttachmentContent(id) {
865 return this._transport.getAttachmentContent(id);
866 }
867
868 /**
869 * @param {Set<number>} types - The annotation types to retrieve.
870 * @param {Set<number>} pageIndexesToSkip
871 * @returns {Promise<Array<Object>>} A promise that is resolved with a list of
872 * annotations data.
873 */
874 getAnnotationsByType(types, pageIndexesToSkip) {
875 return this._transport.getAnnotationsByType(types, pageIndexesToSkip);
876 }
877
878 /**
879 * @returns {Promise<Map | null>} A promise that is resolved with a {Map} with
880 * the JavaScript actions:
881 * - from the name tree.
882 * - from A or AA entries in the catalog dictionary.
883 * , or `null` if no JavaScript exists.
884 */
885 getJSActions() {
886 return this._transport.getDocJSActions();
887 }
888
889 /**
890 * @typedef {Object} OutlineNode
891 * @property {string} title
892 * @property {boolean} bold
893 * @property {boolean} italic
894 * @property {Uint8ClampedArray} color - The color in RGB format to use for
895 * display purposes.
896 * @property {string | Array<any> | null} dest
897 * @property {string | null} url
898 * @property {string | undefined} unsafeUrl
899 * @property {boolean | undefined} newWindow
900 * @property {number | undefined} count
901 * @property {Array<OutlineNode>} items
902 */
903
904 /**
905 * @returns {Promise<Array<OutlineNode>>} A promise that is resolved with an
906 * {Array} that is a tree outline (if it has one) of the PDF file.
907 */
908 getOutline() {
909 return this._transport.getOutline();
910 }
911
912 /**
913 * @typedef {Object} GetOptionalContentConfigParameters
914 * @property {string} [intent] - Determines the optional content groups that
915 * are visible by default; valid values are:
916 * - 'display' (viewable groups).
917 * - 'print' (printable groups).
918 * - 'any' (all groups).
919 * The default value is 'display'.
920 */
921
922 /**
923 * @param {GetOptionalContentConfigParameters} [params] - Optional content
924 * config parameters.
925 * @returns {Promise<OptionalContentConfig>} A promise that is resolved with
926 * an {@link OptionalContentConfig} that contains all the optional content
927 * groups (assuming that the document has any).
928 */
929 getOptionalContentConfig({ intent = "display" } = {}) {
930 const { renderingIntent } = this._transport.getRenderingIntent(intent);
931
932 return this._transport.getOptionalContentConfig(renderingIntent);
933 }
934
935 /**
936 * @returns {Promise<Set<number> | null>} A promise that is resolved with
937 * a {Set} that contains the permission flags for the PDF document,
938 * or `null` when no permissions are present in the PDF file.
939 */
940 getPermissions() {
941 return this._transport.getPermissions();
942 }
943
944 /**
945 * @returns {Promise<{ info: Object, metadata: Metadata }>} A promise that is
946 * resolved with an {Object} that has `info` and `metadata` properties.
947 * `info` is an {Object} filled with anything available in the information
948 * dictionary and similarly `metadata` is a {Metadata} object with
949 * information from the metadata section of the PDF.
950 */
951 getMetadata() {
952 return this._transport.getMetadata();
953 }
954
955 /**
956 * @typedef {Object} MarkInfo
957 * Properties correspond to Table 321 of the PDF 32000-1:2008 spec.
958 * @property {boolean} Marked
959 * @property {boolean} UserProperties
960 * @property {boolean} Suspects
961 */
962
963 /**
964 * @returns {Promise<MarkInfo | null>} A promise that is resolved with
965 * a {MarkInfo} object that contains the MarkInfo flags for the PDF
966 * document, or `null` when no MarkInfo values are present in the PDF file.
967 */
968 getMarkInfo() {
969 return this._transport.getMarkInfo();
970 }
971
972 /**
973 * @returns {Promise<Uint8Array>} A promise that is resolved with a
974 * {Uint8Array} containing the raw data of the PDF document.
975 */
976 getData() {
977 return this._transport.getData();
978 }
979
980 /**
981 * @returns {Promise<Uint8Array<ArrayBuffer>>} A promise that is
982 * resolved with a {Uint8Array<ArrayBuffer>} containing the
983 * full data of the saved document.
984 */
985 saveDocument() {
986 return this._transport.saveDocument();
987 }
988
989 /**
990 * @typedef {Object} PageInfo
991 * @property {null|Uint8Array} [document]
992 * @property {ImageBitmap} [image] Image to insert as a synthetic page.
993 * @property {Array<Array<number>|number>} [includePages]
994 * included ranges or indices.
995 * @property {Array<Array<number>|number>} [excludePages]
996 * excluded ranges or indices.
997 * @property {Array<number>} [pageIndices] Explicit 0-based positions in the
998 * final document for pages contributed by this entry. If shorter than the
999 * filtered page list, the remaining pages are placed in the first free
1000 * slots at extraction time. Positions must not overlap with those of
1001 * other entries, and the union of all explicit/auto-filled positions
1002 * across the call must form a dense `[0, N)` range (where `N` is the
1003 * total page count of the final document) — sparse layouts leave empty
1004 * slots and are not supported. Cannot be combined with `insertAfter` on
1005 * the same entry, and must fully cover the filtered page list when any
1006 * entry in the same call specifies `insertAfter` (partial arrays are
1007 * rejected in that case).
1008 * @property {number} [insertAfter] 0-based index in the base sequential
1009 * sequence (the concatenation of entries that have neither `pageIndices`
1010 * nor `insertAfter`) after which to insert the pages. When every
1011 * contributing entry carries explicit `pageIndices`, this is interpreted
1012 * against that explicit layout instead, shifting any existing positions
1013 * beyond the insertion point to make room. Use `-1` to insert before
1014 * everything. Values beyond the current layout are clamped so the pages
1015 * are appended at the end. Cannot be combined with `pageIndices` on the
1016 * same entry.
1017 */
1018
1019 /**
1020 * @param {Array<PageInfo>} pageInfos - The pages to extract.
1021 * @param {Int32Array} [copyLevels] - For each viewer page, its rank among the
1022 * extracted pages sharing the same source page, or -1 if it isn't extracted.
1023 * This routes editor annotations when the viewer contains multiple copies
1024 * of a source page.
1025 * @returns {Promise<Uint8Array>} A promise that is resolved with a
1026 * {Uint8Array} containing the full data of the saved document.
1027 */
1028 extractPages(pageInfos, copyLevels = null) {
1029 return this._transport.extractPages(pageInfos, copyLevels);
1030 }
1031
1032 /**
1033 * @returns {Promise<{ length: number }>} A promise that is resolved when the
1034 * document's data is loaded. It is resolved with an {Object} that contains
1035 * the `length` property that indicates size of the PDF data in bytes.
1036 */
1037 getDownloadInfo() {
1038 return this._transport.downloadInfoCapability.promise;
1039 }
1040
1041 /**
1042 * Cleans up resources allocated by the document on both the main and worker
1043 * threads.
1044 *
1045 * NOTE: Do not, under any circumstances, call this method when rendering is
1046 * currently ongoing since that may lead to rendering errors.
1047 *
1048 * @param {boolean} [keepLoadedFonts] - Let fonts remain attached to the DOM.
1049 * NOTE: This will increase persistent memory usage, hence don't use this
1050 * option unless absolutely necessary. The default value is `false`.
1051 * @returns {Promise} A promise that is resolved when clean-up has finished.
1052 */
1053 cleanup(keepLoadedFonts = false) {
1054 return this._transport.startCleanup(keepLoadedFonts || this.isPureXfa);
1055 }
1056
1057 /**
1058 * @param {RefProxy} ref - The page reference.
1059 * @returns {number | null} The page number, if it's cached.
1060 */
1061 cachedPageNumber(ref) {
1062 return this._transport.cachedPageNumber(ref);
1063 }
1064
1065 /**
1066 * @type {DocumentInitParameters} A subset of the current
1067 * {DocumentInitParameters}, which are needed in the viewer.
1068 */
1069 get loadingParams() {
1070 return this._transport.loadingParams;
1071 }
1072
1073 /**
1074 * @type {PDFDocumentLoadingTask} The loadingTask for the current document.
1075 */
1076 get loadingTask() {
1077 return this._transport.loadingTask;
1078 }
1079
1080 /**
1081 * @returns {Promise<Map<string, Array<Object>> | null>} A promise that is
1082 * resolved with a {Map} containing /AcroForm field data for the JS sandbox,
1083 * or `null` when no field data is present in the PDF file.
1084 */
1085 getFieldObjects() {
1086 return this._transport.getFieldObjects();
1087 }
1088
1089 /**
1090 * @returns {Promise<Array<Object> | null>} A promise that is resolved
1091 * with an {Array} of digital signature metadata (signerName, reason,
1092 * signingTime, byteRange, subFilter, …), or `null` when the document
1093 * has no signatures. The PKCS#7 blob and signed-data byte spans
1094 * needed for verification are fetched separately via
1095 * {@link PDFDocumentProxy.getSignatureData} so they don't ride the
1096 * worker boundary unless verification is actually requested.
1097 */
1098 getSignatures() {
1099 return this._transport.getSignatures();
1100 }
1101
1102 /**
1103 * @param {string} id Signature `id` from a {@link getSignatures} entry.
1104 * @returns {Promise<{ data: Uint8Array[], pkcs7: Uint8Array } | null>}
1105 * The byte payload needed to verify the signature, or `null` if the
1106 * id is unknown.
1107 */
1108 getSignatureData(id) {
1109 return this._transport.getSignatureData(id);
1110 }
1111
1112 /**
1113 * @returns {Promise<boolean>} A promise that is resolved with `true`
1114 * if some /AcroForm fields have JavaScript actions.
1115 */
1116 hasJSActions() {
1117 return this._transport.hasJSActions();
1118 }
1119
1120 /**
1121 * @returns {Promise<Array<string> | null>} A promise that is resolved with an
1122 * {Array<string>} containing IDs of annotations that have a calculation
1123 * action, or `null` when no such annotations are present in the PDF file.
1124 */
1125 getCalculationOrderIds() {
1126 return this._transport.getCalculationOrderIds();
1127 }
1128}
1129
1130/**
1131 * Page getViewport parameters.
1132 *
1133 * @typedef {Object} GetViewportParameters
1134 * @property {number} scale - The desired scale of the viewport.
1135 * @property {number} [rotation] - The desired rotation, in degrees, of
1136 * the viewport. If omitted it defaults to the page rotation.
1137 * @property {number} [offsetX] - The horizontal, i.e. x-axis, offset.
1138 * The default value is `0`.
1139 * @property {number} [offsetY] - The vertical, i.e. y-axis, offset.
1140 * The default value is `0`.
1141 * @property {boolean} [dontFlip] - If true, the y-axis will not be
1142 * flipped. The default value is `false`.
1143 */
1144
1145/**
1146 * Page getTextContent parameters.
1147 *
1148 * @typedef {Object} getTextContentParameters
1149 * @property {boolean} [includeMarkedContent] - When true include marked
1150 * content items in the items array of TextContent. The default is `false`.
1151 * @property {boolean} [disableNormalization] - When true the text is *not*
1152 * normalized in the worker-thread. The default is `false`.
1153 */
1154
1155/**
1156 * Page text content.
1157 *
1158 * @typedef {Object} TextContent
1159 * @property {Array<TextItem | TextMarkedContent>} items - Array of
1160 * {@link TextItem} and {@link TextMarkedContent} objects. TextMarkedContent
1161 * items are included when includeMarkedContent is true.
1162 * @property {Object<string, TextStyle>} styles - {@link TextStyle} objects,
1163 * indexed by font name.
1164 * @property {string | null} lang - The document /Lang attribute.
1165 */
1166
1167/**
1168 * Page text content part.
1169 *
1170 * @typedef {Object} TextItem
1171 * @property {string} str - Text content.
1172 * @property {string} dir - Text direction: 'ttb', 'ltr' or 'rtl'.
1173 * @property {Array<any>} transform - Transformation matrix.
1174 * @property {number} width - Width in device space.
1175 * @property {number} height - Height in device space.
1176 * @property {string} fontName - Font name used by PDF.js for converted font.
1177 * @property {boolean} hasEOL - Indicating if the text content is followed by a
1178 * line-break.
1179 */
1180
1181/**
1182 * Page text marked content part.
1183 *
1184 * @typedef {Object} TextMarkedContent
1185 * @property {string} type - Either 'beginMarkedContent',
1186 * 'beginMarkedContentProps', or 'endMarkedContent'.
1187 * @property {string} id - The marked content identifier. Only used for type
1188 * 'beginMarkedContentProps'.
1189 */
1190
1191/**
1192 * Text style.
1193 *
1194 * @typedef {Object} TextStyle
1195 * @property {number} ascent - Font ascent.
1196 * @property {number} descent - Font descent.
1197 * @property {boolean} vertical - Whether or not the text is in vertical mode.
1198 * @property {string} fontFamily - The possible font family.
1199 */
1200
1201/**
1202 * Page annotation parameters.
1203 *
1204 * @typedef {Object} GetAnnotationsParameters
1205 * @property {string} [intent] - Determines the annotations that are fetched,
1206 * can be 'display' (viewable annotations), 'print' (printable annotations),
1207 * or 'any' (all annotations). The default value is 'display'.
1208 */
1209
1210/**
1211 * Page render parameters.
1212 *
1213 * @typedef {Object} RenderParameters
1214 * @property {HTMLCanvasElement|null} canvas - A DOM Canvas object. The default
1215 * value is the canvas associated with the `canvasContext` parameter if no
1216 * value is provided explicitly.
1217 * @property {PageViewport} viewport - Rendering viewport obtained by calling
1218 * the `PDFPageProxy.getViewport` method.
1219 * @property {CanvasRenderingContext2D} [canvasContext] - 2D context of a DOM
1220 * Canvas object for backwards compatibility; it is recommended to use the
1221 * `canvas` parameter instead.
1222 * If the context must absolutely be used to render the page, the canvas must
1223 * be null.
1224 * @property {string} [intent] - Rendering intent, can be 'display', 'print',
1225 * or 'any'. The default value is 'display'.
1226 * @property {number} [annotationMode] Controls which annotations are rendered
1227 * onto the canvas, for annotations with appearance-data; the values from
1228 * {@link AnnotationMode} should be used. The following values are supported:
1229 * - `AnnotationMode.DISABLE`, which disables all annotations.
1230 * - `AnnotationMode.ENABLE`, which includes all possible annotations (thus
1231 * it also depends on the `intent`-option, see above).
1232 * - `AnnotationMode.ENABLE_FORMS`, which excludes annotations that contain
1233 * interactive form elements (those will be rendered in the display layer).
1234 * - `AnnotationMode.ENABLE_STORAGE`, which includes all possible annotations
1235 * (as above) but where interactive form elements are updated with data
1236 * from the {@link AnnotationStorage}-instance; useful e.g. for printing.
1237 * The default value is `AnnotationMode.ENABLE`.
1238 * @property {Array<any>} [transform] - Additional transform, applied just
1239 * before viewport transform.
1240 * @property {CanvasGradient | CanvasPattern | string} [background] - Background
1241 * to use for the canvas.
1242 * Any valid `canvas.fillStyle` can be used: a `DOMString` parsed as CSS
1243 * <color> value, a `CanvasGradient` object (a linear or radial gradient) or
1244 * a `CanvasPattern` object (a repetitive image). The default value is
1245 * 'rgb(255,255,255)'.
1246 *
1247 * NOTE: This option may be partially, or completely, ignored when the
1248 * `pageColors`-option is used.
1249 * @property {Object} [pageColors] - Overwrites background and foreground colors
1250 * with user defined ones in order to improve readability in high contrast
1251 * mode.
1252 * @property {Promise<OptionalContentConfig>} [optionalContentConfigPromise] -
1253 * A promise that should resolve with an {@link OptionalContentConfig}
1254 * created from `PDFDocumentProxy.getOptionalContentConfig`. If `null`,
1255 * the configuration will be fetched automatically with the default visibility
1256 * states set.
1257 * @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap] - Map some
1258 * annotation ids with canvases used to render them.
1259 * @property {PrintAnnotationStorage} [printAnnotationStorage]
1260 * @property {boolean} [isEditing] - Render the page in editing mode.
1261 * @property {boolean} [recordImages] - Record the location of images in the PDF
1262 * @property {boolean} [recordOperations] - Record the dependencies and bounding
1263 * boxes of all PDF operations that render onto the canvas.
1264 * @property {OperationsFilter} [operationsFilter] - If provided, only
1265 * run for which this function returns `true`.
1266 */
1267
1268/**
1269 * @callback OperationsFilter
1270 * @param {number} index - The index of the operation.
1271 * @returns {boolean} If false, the operation is ignored.
1272 */
1273
1274/**
1275 * Page getOperatorList parameters.
1276 *
1277 * @typedef {Object} GetOperatorListParameters
1278 * @property {string} [intent] - Rendering intent, can be 'display', 'print',
1279 * or 'any'. The default value is 'display'.
1280 * @property {number} [annotationMode] Controls which annotations are included
1281 * in the operatorList, for annotations with appearance-data; the values from
1282 * {@link AnnotationMode} should be used. The following values are supported:
1283 * - `AnnotationMode.DISABLE`, which disables all annotations.
1284 * - `AnnotationMode.ENABLE`, which includes all possible annotations (thus
1285 * it also depends on the `intent`-option, see above).
1286 * - `AnnotationMode.ENABLE_FORMS`, which excludes annotations that contain
1287 * interactive form elements (those will be rendered in the display layer).
1288 * - `AnnotationMode.ENABLE_STORAGE`, which includes all possible annotations
1289 * (as above) but where interactive form elements are updated with data
1290 * from the {@link AnnotationStorage}-instance; useful e.g. for printing.
1291 * The default value is `AnnotationMode.ENABLE`.
1292 * @property {PrintAnnotationStorage} [printAnnotationStorage]
1293 * @property {boolean} [isEditing] - Render the page in editing mode.
1294 */
1295
1296/**
1297 * Structure tree node. The root node will have a role "Root".
1298 *
1299 * @typedef {Object} StructTreeNode
1300 * @property {Array<StructTreeNode | StructTreeContent>} children - Array of
1301 * {@link StructTreeNode} and {@link StructTreeContent} objects.
1302 * @property {string} role - element's role, already mapped if a role map exists
1303 * in the PDF.
1304 * @property {string} [structId] - A table header's structure element
1305 * identifier, i.e. its `ID` entry. Note that this is unrelated to the `id`
1306 * property of a {@link StructTreeContent} object.
1307 * @property {number} [rowSpan] - The number of rows spanned by a table cell.
1308 * @property {number} [colSpan] - The number of columns spanned by a table cell.
1309 * @property {Array<string>} [headers] - The `structId` values of the table
1310 * headers associated with a table cell.
1311 * @property {"Row" | "Column" | "Both"} [scope] - The cells to which a table
1312 * header applies.
1313 * @property {string} [short] - An abbreviated version of a table header's
1314 * content.
1315 * @property {string} [summary] - A summary of a table's purpose and structure.
1316 */
1317
1318/**
1319 * Structure tree content.
1320 *
1321 * @typedef {Object} StructTreeContent
1322 * @property {string} type - either "content" for page and stream structure
1323 * elements or "object" for object references.
1324 * @property {string} id - unique id that will map to the text layer.
1325 */
1326
1327/**
1328 * PDF page operator list.
1329 *
1330 * @typedef {Object} PDFOperatorList
1331 * @property {Array<number>} fnArray - Array containing the operator functions.
1332 * @property {Array<any>} argsArray - Array containing the arguments of the
1333 * functions.
1334 */
1335
1336/**
1337 * Proxy to a `PDFPage` in the worker thread.
1338 */
▼class PDFPageProxy[738 lines]
1339class PDFPageProxy {
1340 #pendingCleanup = false;
1341
1342 #pagesMapper = null;
1343
1344 constructor(pageIndex, pageInfo, transport, pagesMapper, pdfBug = false) {
1345 this._pageIndex = pageIndex;
1346 this._pageInfo = pageInfo;
1347 this._transport = transport;
1348 this._stats = pdfBug ? new StatTimer() : null;
1349 this._pdfBug = pdfBug;
1350 /** @type {PDFObjects} */
1351 this.commonObjs = transport.commonObjs;
1352 this.objs = new PDFObjects();
1353
1354 this._intentStates = new Map();
1355 this.destroyed = false;
1356 this.recordedBBoxes = null;
1357 this.#pagesMapper = pagesMapper;
1358 this.imageCoordinates = null;
1359 }
1360
1361 clone(id) {
1362 const clone = new PDFPageProxy(
1363 id,
1364 this._pageInfo,
1365 this._transport,
1366 this.#pagesMapper,
1367 this._pdfBug
1368 );
1369 clone.clonedFromIndex = this.clonedFromIndex ?? this._pageIndex;
1370 this._transport.updatePage(clone);
1371 return clone;
1372 }
1373
1374 /**
1375 * @type {number} Page number of the page. First page is 1.
1376 */
1377 get pageNumber() {
1378 return this._pageIndex + 1;
1379 }
1380
1381 /**
1382 * @param {number} value - The page number to set. First page is 1.
1383 */
1384 set pageNumber(value) {
1385 this._pageIndex = value - 1;
1386 this._transport.updatePage(this);
1387 }
1388
1389 /**
1390 * @type {number} The number of degrees the page is rotated clockwise.
1391 */
1392 get rotate() {
1393 return this._pageInfo.rotate;
1394 }
1395
1396 /**
1397 * @type {RefProxy | null} The reference that points to this page.
1398 */
1399 get ref() {
1400 return this._pageInfo.ref;
1401 }
1402
1403 /**
1404 * @type {number} The default size of units in 1/72nds of an inch.
1405 */
1406 get userUnit() {
1407 return this._pageInfo.userUnit;
1408 }
1409
1410 /**
1411 * @type {Array<number>} An array of the visible portion of the PDF page in
1412 * user space units [x1, y1, x2, y2].
1413 */
1414 get view() {
1415 return this._pageInfo.view;
1416 }
1417
1418 /**
1419 * @param {GetViewportParameters} params - Viewport parameters.
1420 * @returns {PageViewport} Contains 'width' and 'height' properties
1421 * along with transforms required for rendering.
1422 */
1423 getViewport({
1424 scale,
1425 rotation = this.rotate,
1426 offsetX = 0,
1427 offsetY = 0,
1428 dontFlip = false,
1429 } = {}) {
1430 return new PageViewport({
1431 viewBox: this.view,
1432 userUnit: this.userUnit,
1433 scale,
1434 rotation,
1435 offsetX,
1436 offsetY,
1437 dontFlip,
1438 });
1439 }
1440
1441 /**
1442 * @param {GetAnnotationsParameters} [params] - Annotation parameters.
1443 * @returns {Promise<Array<any>>} A promise that is resolved with an
1444 * {Array} of the annotation objects.
1445 */
1446 getAnnotations({ intent = "display" } = {}) {
1447 const { renderingIntent } = this._transport.getRenderingIntent(intent);
1448
1449 return this._transport.getAnnotations(this._pageIndex, renderingIntent);
1450 }
1451
1452 /**
1453 * @returns {Promise<Map | null>} A promise that is resolved with a {Map} with
1454 * the JavaScript actions, or `null` if no JavaScript exists.
1455 */
1456 getJSActions() {
1457 return this._transport.getPageJSActions(this._pageIndex);
1458 }
1459
1460 /**
1461 * @type {Object} The filter factory instance.
1462 */
1463 get filterFactory() {
1464 return this._transport.filterFactory;
1465 }
1466
1467 /**
1468 * @type {boolean} True if only XFA form.
1469 */
1470 get isPureXfa() {
1471 return shadow(this, "isPureXfa", !!this._transport._htmlForXfa);
1472 }
1473
1474 /**
1475 * @returns {Promise<Object | null>} A promise that is resolved with
1476 * an {Object} with a fake DOM object (a tree structure where elements
1477 * are {Object} with a name, attributes (class, style, ...), value and
1478 * children, very similar to a HTML DOM tree), or `null` if no XFA exists.
1479 */
1480 async getXfa() {
1481 return this._transport._htmlForXfa?.children[this._pageIndex] || null;
1482 }
1483
1484 /**
1485 * Begins the process of rendering a page to the desired context.
1486 *
1487 * @param {RenderParameters} params - Page render parameters.
1488 * @returns {RenderTask} An object that contains a promise that is
1489 * resolved when the page finishes rendering.
1490 */
1491 render({
1492 canvasContext,
1493 canvas = canvasContext.canvas,
1494 viewport,
1495 intent = "display",
1496 annotationMode = AnnotationMode.ENABLE,
1497 transform = null,
1498 background = null,
1499 optionalContentConfigPromise = null,
1500 annotationCanvasMap = null,
1501 pageColors = null,
1502 printAnnotationStorage = null,
1503 isEditing = false,
1504 recordImages = false,
1505 recordOperations = false,
1506 operationsFilter = null,
1507 }) {
1508 this._stats?.time("Overall");
1509
1510 const intentArgs = this._transport.getRenderingIntent(
1511 intent,
1512 annotationMode,
1513 printAnnotationStorage,
1514 isEditing
1515 );
1516 const { renderingIntent, cacheKey } = intentArgs;
1517 // If there was a pending destroy, cancel it so no cleanup happens during
1518 // this call to render.
1519 this.#pendingCleanup = false;
1520
1521 optionalContentConfigPromise ||=
1522 this._transport.getOptionalContentConfig(renderingIntent);
1523
1524 const intentState = this._intentStates.getOrInsertComputed(
1525 cacheKey,
1526 makeObj
1527 );
1528 // Ensure that a pending `streamReader` cancel timeout is always aborted.
1529 if (intentState.streamReaderCancelTimeout) {
1530 clearTimeout(intentState.streamReaderCancelTimeout);
1531 intentState.streamReaderCancelTimeout = null;
1532 }
1533
1534 const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);
1535
1536 // If there's no displayReadyCapability yet, then the operatorList
1537 // was never requested before. Make the request and create the promise.
1538 if (!intentState.displayReadyCapability) {
1539 intentState.displayReadyCapability = Promise.withResolvers();
1540 intentState.operatorList = {
1541 fnArray: [],
1542 argsArray: [],
1543 lastChunk: false,
1544 separateAnnots: null,
1545 };
1546
1547 this._stats?.time("Page Request");
1548 this._pumpOperatorList(intentArgs);
1549 }
1550
1551 const recordForDebugger = !!(
1552 this._pdfBug && globalThis.StepperManager?.enabled
1553 );
1554 const shouldRecordOperations =
1555 !!canvas &&
1556 !this.recordedBBoxes &&
1557 (recordOperations || recordForDebugger);
1558 const shouldRecordImages =
1559 !!canvas && !this.imageCoordinates && recordImages;
1560
1561 const complete = error => {
1562 intentState.renderTasks.delete(internalRenderTask);
1563
1564 if (shouldRecordOperations) {
1565 const recordedBBoxes = internalRenderTask.gfx?.dependencyTracker.take();
1566 if (recordedBBoxes) {
1567 internalRenderTask.stepper?.setOperatorBBoxes(
1568 recordedBBoxes,
1569 internalRenderTask.gfx.dependencyTracker.takeDebugMetadata()
1570 );
1571
1572 if (recordOperations) {
1573 this.recordedBBoxes = recordedBBoxes;
1574 }
1575 }
1576 }
1577
1578 if (shouldRecordImages && !error) {
1579 this.imageCoordinates = internalRenderTask.gfx?.imagesTracker.take();
1580 }
1581
1582 // Attempt to reduce memory usage during *printing*, by always running
1583 // cleanup immediately once rendering has finished.
1584 if (intentPrint) {
1585 this.#pendingCleanup = true;
1586 }
1587 this.#tryCleanup();
1588
1589 if (error) {
1590 internalRenderTask.capability.reject(error);
1591
1592 this._abortOperatorList({
1593 intentState,
1594 reason: error instanceof Error ? error : new Error(error),
1595 });
1596 } else {
1597 internalRenderTask.capability.resolve();
1598 }
1599
1600 if (this._stats) {
1601 this._stats.timeEnd("Rendering");
1602 this._stats.timeEnd("Overall");
1603
1604 if (globalThis.Stats?.enabled) {
1605 globalThis.Stats.add(this.pageNumber, this._stats);
1606 }
1607 }
1608 };
1609
1610 let dependencyTracker = null;
1611 let bboxTracker = null;
1612 if (shouldRecordOperations || shouldRecordImages) {
1613 bboxTracker = new CanvasBBoxTracker(
1614 canvas,
1615 intentState.operatorList.length
1616 );
1617 }
1618 if (shouldRecordOperations) {
1619 dependencyTracker = new CanvasDependencyTracker(
1620 bboxTracker,
1621 recordForDebugger
1622 );
1623 }
1624
1625 const internalRenderTask = new InternalRenderTask({
1626 callback: complete,
1627 // Only include the required properties, and *not* the entire object.
1628 params: {
1629 canvas,
1630 canvasContext,
1631 dependencyTracker: dependencyTracker ?? bboxTracker,
1632 imagesTracker: shouldRecordImages
1633 ? new CanvasImagesTracker(canvas)
1634 : null,
1635 viewport,
1636 transform,
1637 background,
1638 },
1639 objs: this.objs,
1640 commonObjs: this.commonObjs,
1641 annotationCanvasMap,
1642 operatorList: intentState.operatorList,
1643 pageIndex: this._pageIndex,
1644 canvasFactory: this._transport.canvasFactory,
1645 filterFactory: this._transport.filterFactory,
1646 useRequestAnimationFrame: !intentPrint,
1647 pdfBug: this._pdfBug,
1648 pageColors,
1649 enableHWA: this._transport.enableHWA,
1650 operationsFilter,
1651 });
1652
1653 (intentState.renderTasks ||= new Set()).add(internalRenderTask);
1654 const renderTask = internalRenderTask.task;
1655
1656 Promise.all([
1657 intentState.displayReadyCapability.promise,
1658 optionalContentConfigPromise,
1659 ])
1660 .then(([transparency, optionalContentConfig]) => {
1661 if (this.destroyed) {
1662 complete();
1663 return;
1664 }
1665 this._stats?.time("Rendering");
1666
1667 if (!(optionalContentConfig.renderingIntent & renderingIntent)) {
1668 throw new Error(
1669 "Must use the same `intent`-argument when calling the `PDFPageProxy.render` " +
1670 "and `PDFDocumentProxy.getOptionalContentConfig` methods."
1671 );
1672 }
1673 internalRenderTask.initializeGraphics({
1674 transparency,
1675 optionalContentConfig,
1676 });
1677 internalRenderTask.operatorListChanged();
1678 })
1679 .catch(complete);
1680
1681 return renderTask;
1682 }
1683
1684 /**
1685 * @param {GetOperatorListParameters} params - Page getOperatorList
1686 * parameters.
1687 * @returns {Promise<PDFOperatorList>} A promise resolved with an
1688 * {@link PDFOperatorList} object that represents the page's operator list.
1689 */
1690 getOperatorList({
1691 intent = "display",
1692 annotationMode = AnnotationMode.ENABLE,
1693 printAnnotationStorage = null,
1694 isEditing = false,
1695 } = {}) {
1696 if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("GENERIC")) {
1697 throw new Error("Not implemented: getOperatorList");
1698 }
1699 function operatorListChanged() {
1700 if (intentState.operatorList.lastChunk) {
1701 intentState.opListReadCapability.resolve(intentState.operatorList);
1702
1703 intentState.renderTasks.delete(opListTask);
1704 }
1705 }
1706
1707 const intentArgs = this._transport.getRenderingIntent(
1708 intent,
1709 annotationMode,
1710 printAnnotationStorage,
1711 isEditing,
1712 /* isOpList = */ true
1713 );
1714 const intentState = this._intentStates.getOrInsertComputed(
1715 intentArgs.cacheKey,
1716 makeObj
1717 );
1718 let opListTask;
1719
1720 if (!intentState.opListReadCapability) {
1721 opListTask = Object.create(null);
1722 opListTask.operatorListChanged = operatorListChanged;
1723 intentState.opListReadCapability = Promise.withResolvers();
1724 (intentState.renderTasks ||= new Set()).add(opListTask);
1725 intentState.operatorList = {
1726 fnArray: [],
1727 argsArray: [],
1728 lastChunk: false,
1729 separateAnnots: null,
1730 };
1731
1732 this._stats?.time("Page Request");
1733 this._pumpOperatorList(intentArgs);
1734 }
1735 return intentState.opListReadCapability.promise;
1736 }
1737
1738 /**
1739 * NOTE: All occurrences of whitespace will be replaced by
1740 * standard spaces (0x20).
1741 *
1742 * @param {getTextContentParameters} params - getTextContent parameters.
1743 * @returns {ReadableStream} Stream for reading text content chunks.
1744 */
1745 streamTextContent({
1746 includeMarkedContent = false,
1747 disableNormalization = false,
1748 } = {}) {
1749 const TEXT_CONTENT_CHUNK_SIZE = 100;
1750
1751 return this._transport.messageHandler.sendWithStream(
1752 "GetTextContent",
1753 {
1754 pageId: this.#pagesMapper.getPageId(this._pageIndex + 1) - 1,
1755 pageIndex: this._pageIndex,
1756 includeMarkedContent: includeMarkedContent === true,
1757 disableNormalization: disableNormalization === true,
1758 },
1759 {
1760 highWaterMark: TEXT_CONTENT_CHUNK_SIZE,
1761 size(textContent) {
1762 return textContent.items.length;
1763 },
1764 }
1765 );
1766 }
1767
1768 /**
1769 * NOTE: All occurrences of whitespace will be replaced by
1770 * standard spaces (0x20).
1771 *
1772 * @param {getTextContentParameters} params - getTextContent parameters.
1773 * @returns {Promise<TextContent>} A promise that is resolved with a
1774 * {@link TextContent} object that represents the page's text content.
1775 */
1776 async getTextContent(params = {}) {
1777 if (this._transport._htmlForXfa) {
1778 // TODO: We need to revisit this once the XFA foreground patch lands and
1779 // only do this for non-foreground XFA.
1780 return this.getXfa().then(xfa => XfaText.textContent(xfa));
1781 }
1782 const readableStream = this.streamTextContent(params);
1783
1784 const textContent = {
1785 items: [],
1786 styles: Object.create(null),
1787 lang: null,
1788 };
1789
1790 for await (const value of readableStream) {
1791 textContent.lang ??= value.lang;
1792 Object.assign(textContent.styles, value.styles);
1793 textContent.items.push(...value.items);
1794 }
1795 return textContent;
1796 }
1797
1798 /**
1799 * @returns {Promise<StructTreeNode>} A promise that is resolved with a
1800 * {@link StructTreeNode} object that represents the page's structure tree,
1801 * or `null` when no structure tree is present for the current page.
1802 */
1803 getStructTree() {
1804 return this._transport.getStructTree(this._pageIndex);
1805 }
1806
1807 /**
1808 * Destroys the page object.
1809 * @private
1810 */
1811 _destroy() {
1812 this.destroyed = true;
1813
1814 const waitOn = [];
1815 for (const intentState of this._intentStates.values()) {
1816 this._abortOperatorList({
1817 intentState,
1818 reason: new Error("Page was destroyed."),
1819 force: true,
1820 });
1821
1822 if (intentState.opListReadCapability) {
1823 // Avoid errors below, since the renderTasks are just stubs.
1824 continue;
1825 }
1826 for (const internalRenderTask of intentState.renderTasks) {
1827 waitOn.push(internalRenderTask.completed);
1828 internalRenderTask.cancel();
1829 }
1830 }
1831 this.objs.clear();
1832 this.#pendingCleanup = false;
1833
1834 return Promise.all(waitOn);
1835 }
1836
1837 /**
1838 * Cleans up resources allocated by the page.
1839 *
1840 * @param {boolean} [resetStats] - Reset page stats, if enabled.
1841 * The default value is `false`.
1842 * @returns {boolean} Indicates if clean-up was successfully run.
1843 */
1844 cleanup(resetStats = false) {
1845 this.#pendingCleanup = true;
1846 const success = this.#tryCleanup();
1847
1848 if (resetStats && success) {
1849 this._stats &&= new StatTimer();
1850 }
1851 return success;
1852 }
1853
1854 /**
1855 * Attempts to clean up if rendering is in a state where that's possible.
1856 * @returns {boolean} Indicates if clean-up was successfully run.
1857 */
1858 #tryCleanup() {
1859 if (!this.#pendingCleanup || this.destroyed) {
1860 return false;
1861 }
1862 for (const { renderTasks, operatorList } of this._intentStates.values()) {
1863 if (renderTasks.size > 0 || !operatorList.lastChunk) {
1864 return false;
1865 }
1866 }
1867 this._intentStates.clear();
1868 this.objs.clear();
1869 this.#pendingCleanup = false;
1870 return true;
1871 }
1872
1873 /**
1874 * @private
1875 */
1876 _startRenderPage(transparency, cacheKey) {
1877 const intentState = this._intentStates.get(cacheKey);
1878 if (!intentState) {
1879 return; // Rendering was cancelled.
1880 }
1881 this._stats?.timeEnd("Page Request");
1882
1883 // TODO Refactor RenderPageRequest to separate rendering
1884 // and operator list logic
1885 intentState.displayReadyCapability?.resolve(transparency);
1886 }
1887
1888 /**
1889 * @private
1890 */
1891 _renderPageChunk(operatorListChunk, intentState) {
1892 // Add the new chunk to the current operator list.
1893 for (let i = 0, ii = operatorListChunk.length; i < ii; i++) {
1894 intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
1895 intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
1896 }
1897 intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
1898 intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots;
1899
1900 // Notify all the rendering tasks there are more operators to be consumed.
1901 for (const internalRenderTask of intentState.renderTasks) {
1902 internalRenderTask.operatorListChanged();
1903 }
1904
1905 if (operatorListChunk.lastChunk) {
1906 this.#tryCleanup();
1907 }
1908 }
1909
1910 /**
1911 * @private
1912 */
1913 _pumpOperatorList({
1914 renderingIntent,
1915 cacheKey,
1916 annotationStorageSerializable,
1917 modifiedIds,
1918 }) {
1919 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
1920 assert(
1921 Number.isInteger(renderingIntent) && renderingIntent > 0,
1922 '_pumpOperatorList: Expected valid "renderingIntent" argument.'
1923 );
1924 }
1925 const { map, transfer } = annotationStorageSerializable;
1926
1927 const readableStream = this._transport.messageHandler.sendWithStream(
1928 "GetOperatorList",
1929 {
1930 pageId: this.#pagesMapper.getPageId(this._pageIndex + 1) - 1,
1931 pageIndex: this._pageIndex,
1932 intent: renderingIntent,
1933 cacheKey,
1934 annotationStorage: map,
1935 modifiedIds,
1936 },
1937 /* queueingStrategy = */ undefined,
1938 transfer
1939 );
1940 const reader = readableStream.getReader();
1941
1942 const intentState = this._intentStates.get(cacheKey);
1943 intentState.streamReader = reader;
1944
1945 const pump = () => {
1946 reader.read().then(
1947 ({ value, done }) => {
1948 if (done) {
1949 intentState.streamReader = null;
1950 return;
1951 }
1952 if (this._transport.destroyed) {
1953 return; // Ignore any pending requests if the worker was terminated.
1954 }
1955 this._renderPageChunk(value, intentState);
1956 pump();
1957 },
1958 reason => {
1959 intentState.streamReader = null;
1960
1961 if (this._transport.destroyed) {
1962 return; // Ignore any pending requests if the worker was terminated.
1963 }
1964 if (intentState.operatorList) {
1965 // Mark operator list as complete.
1966 intentState.operatorList.lastChunk = true;
1967
1968 for (const internalRenderTask of intentState.renderTasks) {
1969 internalRenderTask.operatorListChanged();
1970 }
1971 this.#tryCleanup();
1972 }
1973
1974 if (intentState.displayReadyCapability) {
1975 intentState.displayReadyCapability.reject(reason);
1976 } else if (intentState.opListReadCapability) {
1977 intentState.opListReadCapability.reject(reason);
1978 } else {
1979 throw reason;
1980 }
1981 }
1982 );
1983 };
1984 pump();
1985 }
1986
1987 /**
1988 * @private
1989 */
1990 _abortOperatorList({ intentState, reason, force = false }) {
1991 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
1992 assert(
1993 reason instanceof Error,
1994 '_abortOperatorList: Expected valid "reason" argument.'
1995 );
1996 }
1997
1998 if (!intentState.streamReader) {
1999 return;
2000 }
2001 // Ensure that a pending `streamReader` cancel timeout is always aborted.
2002 if (intentState.streamReaderCancelTimeout) {
2003 clearTimeout(intentState.streamReaderCancelTimeout);
2004 intentState.streamReaderCancelTimeout = null;
2005 }
2006
2007 if (!force) {
2008 // Ensure that an Error occurring in *only* one `InternalRenderTask`, e.g.
2009 // multiple render() calls on the same canvas, won't break all rendering.
2010 if (intentState.renderTasks.size > 0) {
2011 return;
2012 }
2013 // Don't immediately abort parsing on the worker-thread when rendering is
2014 // cancelled, since that will unnecessarily delay re-rendering when (for
2015 // partially parsed pages) e.g. zooming/rotation occurs in the viewer.
2016 if (reason instanceof RenderingCancelledException) {
2017 let delay = RENDERING_CANCELLED_TIMEOUT;
2018 if (reason.extraDelay > 0 && reason.extraDelay < /* ms = */ 1000) {
2019 // Above, we prevent the total delay from becoming arbitrarily large.
2020 delay += reason.extraDelay;
2021 }
2022
2023 intentState.streamReaderCancelTimeout = setTimeout(() => {
2024 intentState.streamReaderCancelTimeout = null;
2025 this._abortOperatorList({ intentState, reason, force: true });
2026 }, delay);
2027 return;
2028 }
2029 }
2030 intentState.streamReader
2031 .cancel(new AbortException(reason.message))
2032 .catch(() => {
2033 // Avoid "Uncaught promise" messages in the console.
2034 });
2035 intentState.streamReader = null;
2036
2037 if (this._transport.destroyed) {
2038 return; // Ignore any pending requests if the worker was terminated.
2039 }
2040 // Remove the current `intentState`, since a cancelled `getOperatorList`
2041 // call on the worker-thread cannot be re-started...
2042 for (const [curCacheKey, curIntentState] of this._intentStates) {
2043 if (curIntentState === intentState) {
2044 this._intentStates.delete(curCacheKey);
2045 break;
2046 }
2047 }
2048 // ... and force clean-up to ensure that any old state is always removed.
2049 this.cleanup();
2050 }
2051
2052 /**
2053 * @type {StatTimer | null} Returns page stats, if enabled; returns `null`
2054 * otherwise.
2055 */
2056 get stats() {
2057 return this._stats;
2058 }
2059}
2060
2061/**
2062 * @typedef {Object} PDFWorkerParameters
2063 * @property {string} [name] - The name of the worker.
2064 * @property {Worker} [port] - The `workerPort` object.
2065 * @property {number} [verbosity] - Controls the logging level;
2066 * the constants from {@link VerbosityLevel} should be used.
2067 */
2068
2069/**
2070 * PDF.js web worker abstraction that controls the instantiation of PDF
2071 * documents. Message handlers are used to pass information from the main
2072 * thread to the worker thread and vice versa. If the creation of a web
2073 * worker is not possible, a "fake" worker will be used instead.
2074 *
2075 * @param {PDFWorkerParameters} params - The worker initialization parameters.
2076 */
▼class PDFWorker[339 lines]
2077class PDFWorker {
2078 #capability = Promise.withResolvers();
2079
2080 #messageHandler = null;
2081
2082 #port = null;
2083
2084 #webWorker = null;
2085
2086 static #fakeWorkerId = 0;
2087
2088 static #isWorkerDisabled = false;
2089
2090 static #workerPorts = new WeakMap();
2091
2092 static {
2093 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
2094 if (isNodeJS) {
2095 // Workers aren't supported in Node.js, force-disabling them there.
2096 this.#isWorkerDisabled = true;
2097
2098 GlobalWorkerOptions.workerSrc ||= PDFJSDev.test("LIB")
2099 ? "../pdf.worker.js"
2100 : "./pdf.worker.mjs";
2101 }
2102
2103 // Check if URLs have the same origin. For non-HTTP based URLs, returns
2104 // false.
2105 this._isSameOrigin = (baseUrl, otherUrl) => {
2106 const base = URL.parse(baseUrl);
2107 if (!base?.origin || base.origin === "null") {
2108 return false; // non-HTTP url
2109 }
2110 const other = new URL(otherUrl, base);
2111 return base.origin === other.origin;
2112 };
2113
2114 this._createCDNWrapper = url => {
2115 // We will rely on blob URL's property to specify origin.
2116 // We want this function to fail in case if createObjectURL or Blob do
2117 // not exist or fail for some reason -- our Worker creation will fail
2118 // anyway.
2119 const wrapper = `await import("${url}");`;
2120 return URL.createObjectURL(
2121 new Blob([wrapper], { type: "text/javascript" })
2122 );
2123 };
2124 }
2125
2126 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
2127 this._resetGlobalState = () => {
2128 this.#isWorkerDisabled = false;
2129 delete globalThis.pdfjsWorker;
2130 };
2131 }
2132 }
2133
2134 constructor({
2135 name = null,
2136 port = null,
2137 verbosity = getVerbosityLevel(),
2138 } = {}) {
2139 this.name = name;
2140 this.destroyed = false;
2141 this.verbosity = verbosity;
2142
2143 if (port) {
2144 if (PDFWorker.#workerPorts.has(port)) {
2145 throw new Error("Cannot use more than one PDFWorker per port.");
2146 }
2147 PDFWorker.#workerPorts.set(port, this);
2148 this.#initializeFromPort(port);
2149 } else {
2150 this.#initialize();
2151 }
2152
2153 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
2154 // For testing purposes.
2155 Object.defineProperty(this, "_webWorker", {
2156 get() {
2157 return this.#webWorker;
2158 },
2159 });
2160 }
2161 }
2162
2163 /**
2164 * Promise for worker initialization completion.
2165 * @type {Promise<void>}
2166 */
2167 get promise() {
2168 return this.#capability.promise;
2169 }
2170
2171 #resolve() {
2172 this.#capability.resolve();
2173 // Send global setting, e.g. verbosity level.
2174 this.#messageHandler.send("configure", {
2175 verbosity: this.verbosity,
2176 });
2177 }
2178
2179 /**
2180 * The current `workerPort`, when it exists.
2181 * @type {Worker}
2182 */
2183 get port() {
2184 return this.#port;
2185 }
2186
2187 /**
2188 * The current MessageHandler-instance.
2189 * @type {MessageHandler}
2190 */
2191 get messageHandler() {
2192 return this.#messageHandler;
2193 }
2194
2195 #initializeFromPort(port) {
2196 this.#port = port;
2197 this.#messageHandler = new MessageHandler("main", "worker", port);
2198 this.#messageHandler.on("ready", () => {
2199 // Ignoring "ready" event -- MessageHandler should already be initialized
2200 // and ready to accept messages.
2201 });
2202 this.#resolve();
2203 }
2204
2205 #initialize() {
2206 // If worker support isn't disabled explicit and the browser has worker
2207 // support, create a new web worker and test if it/the browser fulfills
2208 // all requirements to run parts of pdf.js in a web worker.
2209 // Right now, the requirement is, that an Uint8Array is still an
2210 // Uint8Array as it arrives on the worker.
2211 if (
2212 PDFWorker.#isWorkerDisabled ||
2213 PDFWorker.#mainThreadWorkerMessageHandler
2214 ) {
2215 this.#setupFakeWorker();
2216 return;
2217 }
2218 let { workerSrc } = PDFWorker;
2219
2220 try {
2221 // Wraps workerSrc path into blob URL, if the former does not belong
2222 // to the same origin.
2223 if (
2224 typeof PDFJSDev !== "undefined" &&
2225 PDFJSDev.test("GENERIC") &&
2226 !PDFWorker._isSameOrigin(window.location, workerSrc)
2227 ) {
2228 workerSrc = PDFWorker._createCDNWrapper(
2229 new URL(workerSrc, window.location).href
2230 );
2231 }
2232
2233 const worker = new Worker(workerSrc, { type: "module" });
2234 const messageHandler = new MessageHandler("main", "worker", worker);
2235 const terminateEarly = () => {
2236 ac.abort();
2237 messageHandler.destroy();
2238 worker.terminate();
2239 if (this.destroyed) {
2240 this.#capability.reject(new Error("Worker was destroyed"));
2241 } else {
2242 // Fall back to fake worker if the termination is caused by an
2243 // error (e.g. NetworkError / SecurityError).
2244 this.#setupFakeWorker();
2245 }
2246 };
2247
2248 const ac = new AbortController();
2249 worker.addEventListener(
2250 "error",
2251 () => {
2252 if (!this.#webWorker) {
2253 // Worker failed to initialize due to an error. Clean up and fall
2254 // back to the fake worker.
2255 terminateEarly();
2256 }
2257 },
2258 { signal: ac.signal }
2259 );
2260
2261 messageHandler.on("test", data => {
2262 ac.abort();
2263 if (this.destroyed || !data) {
2264 terminateEarly();
2265 return;
2266 }
2267 this.#messageHandler = messageHandler;
2268 this.#port = worker;
2269 this.#webWorker = worker;
2270
2271 this.#resolve();
2272 });
2273
2274 messageHandler.on("ready", data => {
2275 ac.abort();
2276 if (this.destroyed) {
2277 terminateEarly();
2278 return;
2279 }
2280 try {
2281 sendTest();
2282 } catch {
2283 // We need fallback to a faked worker.
2284 this.#setupFakeWorker();
2285 }
2286 });
2287
2288 const sendTest = () => {
2289 const testObj = new Uint8Array();
2290 // Ensure that we can use `postMessage` transfers.
2291 messageHandler.send("test", testObj, [testObj.buffer]);
2292 };
2293
2294 // It might take time for the worker to initialize. We will try to send
2295 // the "test" message immediately, and once the "ready" message arrives.
2296 // The worker shall process only the first received "test" message.
2297 sendTest();
2298 return;
2299 } catch {
2300 info("The worker has been disabled.");
2301 }
2302 // Either workers are not supported or have thrown an exception.
2303 // Thus, we fallback to a faked worker.
2304 this.#setupFakeWorker();
2305 }
2306
2307 #setupFakeWorker() {
2308 if (!PDFWorker.#isWorkerDisabled) {
2309 warn("Setting up fake worker.");
2310 PDFWorker.#isWorkerDisabled = true;
2311 }
2312
2313 PDFWorker._setupFakeWorkerGlobal
2314 .then(WorkerMessageHandler => {
2315 if (this.destroyed) {
2316 this.#capability.reject(new Error("Worker was destroyed"));
2317 return;
2318 }
2319 const port = new LoopbackPort();
2320 this.#port = port;
2321
2322 // All fake workers use the same port, making id unique.
2323 const id = `fake${PDFWorker.#fakeWorkerId++}`;
2324
2325 // If the main thread is our worker, setup the handling for the
2326 // messages -- the main thread sends to it self.
2327 const workerHandler = new MessageHandler(id + "_worker", id, port);
2328 WorkerMessageHandler.setup(workerHandler, port);
2329
2330 this.#messageHandler = new MessageHandler(id, id + "_worker", port);
2331 this.#resolve();
2332 })
2333 .catch(reason => {
2334 this.#capability.reject(
2335 new Error(`Setting up fake worker failed: "${reason.message}".`)
2336 );
2337 });
2338 }
2339
2340 /**
2341 * Destroys the worker instance.
2342 */
2343 destroy() {
2344 this.destroyed = true;
2345
2346 // We need to terminate only web worker created resource.
2347 this.#webWorker?.terminate();
2348 this.#webWorker = null;
2349
2350 PDFWorker.#workerPorts.delete(this.#port);
2351 this.#port = null;
2352
2353 this.#messageHandler?.destroy();
2354 this.#messageHandler = null;
2355 }
2356
2357 /**
2358 * @param {PDFWorkerParameters} params - The worker initialization parameters.
2359 * @returns {PDFWorker}
2360 */
2361 static create(params) {
2362 const cachedPort = this.#workerPorts.get(params?.port);
2363 if (cachedPort) {
2364 if (cachedPort._pendingDestroy) {
2365 throw new Error(
2366 "PDFWorker.create - the worker is being destroyed.\n" +
2367 "Please remember to await `PDFDocumentLoadingTask.destroy()`-calls."
2368 );
2369 }
2370 return cachedPort;
2371 }
2372 return new PDFWorker(params);
2373 }
2374
2375 /**
2376 * The current `workerSrc`, when it exists.
2377 * @type {string}
2378 */
2379 static get workerSrc() {
2380 if (GlobalWorkerOptions.workerSrc) {
2381 return GlobalWorkerOptions.workerSrc;
2382 }
2383 throw new Error('No "GlobalWorkerOptions.workerSrc" specified.');
2384 }
2385
2386 static get #mainThreadWorkerMessageHandler() {
2387 try {
2388 return globalThis.pdfjsWorker?.WorkerMessageHandler || null;
2389 } catch {
2390 return null;
2391 }
2392 }
2393
2394 // Loads worker code into the main-thread.
2395 static get _setupFakeWorkerGlobal() {
2396 const loader = async () => {
2397 if (this.#mainThreadWorkerMessageHandler) {
2398 // The worker was already loaded using e.g. a `<script>` tag.
2399 return this.#mainThreadWorkerMessageHandler;
2400 }
2401 const worker =
2402 typeof PDFJSDev === "undefined"
2403 ? await import("pdfjs/pdf.worker.js")
2404 : await __raw_import__(this.workerSrc);
2405 return worker.WorkerMessageHandler;
2406 };
2407
2408 return shadow(this, "_setupFakeWorkerGlobal", loader());
2409 }
2410}
2411
2412/**
2413 * For internal use only.
2414 * @ignore
2415 */
▼class WorkerTransport[848 lines]
2416class WorkerTransport {
2417 downloadInfoCapability = Promise.withResolvers();
2418
2419 #fullReader = null;
2420
2421 #methodPromises = new Map();
2422
2423 #networkStream = null;
2424
2425 #pageCache = new Map();
2426
2427 #pagePromises = new Map();
2428
2429 #pageRefCache = new Map();
2430
2431 #passwordCapability = null;
2432
2433 constructor(
2434 messageHandler,
2435 loadingTask,
2436 networkStream,
2437 params,
2438 factory,
2439 pagesMapper
2440 ) {
2441 this.messageHandler = messageHandler;
2442 this.loadingTask = loadingTask;
2443 this.#networkStream = networkStream;
2444
2445 this.commonObjs = new PDFObjects();
2446 this.fontLoader = new FontLoader({
2447 ownerDocument: params.ownerDocument,
2448 styleElement: params.styleElement,
2449 });
2450 this.enableHWA = params.enableHWA;
2451 this.loadingParams = params.loadingParams;
2452 this._params = params;
2453
2454 this.canvasFactory = factory.canvasFactory;
2455 this.filterFactory = factory.filterFactory;
2456 if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
2457 this.binaryDataFactory = factory.binaryDataFactory;
2458 }
2459 this.pagesMapper = pagesMapper;
2460
2461 this.destroyed = false;
2462 this.destroyCapability = null;
2463
2464 this.setupMessageHandler();
2465
2466 if (
2467 typeof PDFJSDev === "undefined" ||
2468 PDFJSDev.test("TESTING || INTERNAL_VIEWER")
2469 ) {
2470 // For the PDF debugger.
2471 Object.defineProperty(this, "getRawData", {
2472 value: data => this.messageHandler.sendWithPromise("GetRawData", data),
2473 });
2474 }
2475 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
2476 // For testing purposes.
2477 Object.defineProperty(this, "getNetworkStreamName", {
2478 value: () => networkStream?.constructor?.name || null,
2479 });
2480 Object.defineProperty(this, "getXFADatasets", {
2481 value: () =>
2482 this.messageHandler.sendWithPromise("GetXFADatasets", null),
2483 });
2484 Object.defineProperty(this, "getXRefPrevValue", {
2485 value: () =>
2486 this.messageHandler.sendWithPromise("GetXRefPrevValue", null),
2487 });
2488 Object.defineProperty(this, "getStartXRefPos", {
2489 value: () =>
2490 this.messageHandler.sendWithPromise("GetStartXRefPos", null),
2491 });
2492 Object.defineProperty(this, "getAnnotArray", {
2493 value: pageIndex =>
2494 this.messageHandler.sendWithPromise("GetAnnotArray", { pageIndex }),
2495 });
2496 }
2497 }
2498
2499 updatePage(page) {
2500 const { _pageIndex } = page;
2501 this.#pageCache.set(_pageIndex, page);
2502 this.#pagePromises.set(_pageIndex, Promise.resolve(page));
2503 }
2504
2505 #cacheSimpleMethod(name, data = null) {
2506 return this.#methodPromises.getOrInsertComputed(name, () =>
2507 this.messageHandler.sendWithPromise(name, data)
2508 );
2509 }
2510
2511 #onProgress({ loaded, total }) {
2512 this.loadingTask.onProgress?.({
2513 loaded,
2514 total,
2515 percent: total
2516 ? MathClamp(Math.round((loaded / total) * 100), 0, 100)
2517 : NaN,
2518 });
2519 }
2520
2521 get annotationStorage() {
2522 return shadow(this, "annotationStorage", new AnnotationStorage());
2523 }
2524
2525 getRenderingIntent(
2526 intent,
2527 annotationMode = AnnotationMode.ENABLE,
2528 printAnnotationStorage = null,
2529 isEditing = false,
2530 isOpList = false
2531 ) {
2532 let renderingIntent = RenderingIntentFlag.DISPLAY; // Default value.
2533 let annotationStorageSerializable = SerializableEmpty;
2534
2535 switch (intent) {
2536 case "any":
2537 renderingIntent = RenderingIntentFlag.ANY;
2538 break;
2539 case "display":
2540 break;
2541 case "print":
2542 renderingIntent = RenderingIntentFlag.PRINT;
2543 break;
2544 default:
2545 warn(`getRenderingIntent - invalid intent: ${intent}`);
2546 }
2547
2548 const annotationStorage =
2549 renderingIntent & RenderingIntentFlag.PRINT &&
2550 printAnnotationStorage instanceof PrintAnnotationStorage
2551 ? printAnnotationStorage
2552 : this.annotationStorage;
2553
2554 switch (annotationMode) {
2555 case AnnotationMode.DISABLE:
2556 renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISABLE;
2557 break;
2558 case AnnotationMode.ENABLE:
2559 break;
2560 case AnnotationMode.ENABLE_FORMS:
2561 renderingIntent += RenderingIntentFlag.ANNOTATIONS_FORMS;
2562 break;
2563 case AnnotationMode.ENABLE_STORAGE:
2564 renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE;
2565
2566 annotationStorageSerializable = annotationStorage.serializable;
2567 break;
2568 default:
2569 warn(`getRenderingIntent - invalid annotationMode: ${annotationMode}`);
2570 }
2571
2572 if (isEditing) {
2573 renderingIntent += RenderingIntentFlag.IS_EDITING;
2574 }
2575 if (isOpList) {
2576 renderingIntent += RenderingIntentFlag.OPLIST;
2577 }
2578
2579 const { ids: modifiedIds, hash: modifiedIdsHash } =
2580 annotationStorage.modifiedIds;
2581
2582 const cacheKeyBuf = [
2583 renderingIntent,
2584 annotationStorageSerializable.hash,
2585 modifiedIdsHash,
2586 ];
2587
2588 return {
2589 renderingIntent,
2590 cacheKey: cacheKeyBuf.join("_"),
2591 annotationStorageSerializable,
2592 modifiedIds,
2593 };
2594 }
2595
2596 destroy() {
2597 if (this.destroyCapability) {
2598 return this.destroyCapability.promise;
2599 }
2600
2601 this.destroyed = true;
2602 this.destroyCapability = Promise.withResolvers();
2603
2604 this.#passwordCapability?.reject(
2605 new Error("Worker was destroyed during onPassword callback")
2606 );
2607
2608 const waitOn = [];
2609 // We need to wait for all renderings to be completed, e.g.
2610 // timeout/rAF can take a long time.
2611 for (const page of this.#pageCache.values()) {
2612 waitOn.push(page._destroy());
2613 }
2614 this.#pageCache.clear();
2615 this.#pagePromises.clear();
2616 this.#pageRefCache.clear();
2617 // Allow `AnnotationStorage`-related clean-up when destroying the document.
2618 if (Object.hasOwn(this, "annotationStorage")) {
2619 this.annotationStorage.resetModified();
2620 }
2621 // We also need to wait for the worker to finish its long running tasks.
2622 const terminated = this.messageHandler.sendWithPromise("Terminate", null);
2623 waitOn.push(terminated);
2624
2625 Promise.all(waitOn).then(() => {
2626 this.commonObjs.clear();
2627 this.fontLoader.clear();
2628 this.#methodPromises.clear();
2629 this.filterFactory.destroy();
2630 TextLayer.cleanup();
2631
2632 this.#networkStream?.cancelAllRequests(
2633 new AbortException("Worker was terminated.")
2634 );
2635
2636 this.messageHandler?.destroy();
2637 this.messageHandler = null;
2638
2639 this.destroyCapability.resolve();
2640 }, this.destroyCapability.reject);
2641 return this.destroyCapability.promise;
2642 }
2643
2644 setupMessageHandler() {
2645 const { messageHandler, loadingTask } = this;
2646
2647 messageHandler.on("GetReader", (data, sink) => {
2648 assert(
2649 this.#networkStream,
2650 "GetReader - no `BasePDFStream` instance available."
2651 );
2652 this.#fullReader = this.#networkStream.getFullReader();
2653 // If stream or range turn out to be disabled, once `headersReady` is
2654 // resolved, this is our only way to report loading progress.
2655 this.#fullReader.onProgress = evt => this.#onProgress(evt);
2656
2657 sink.onPull = () => {
2658 this.#fullReader
2659 .read()
2660 .then(function ({ value, done }) {
2661 if (done) {
2662 sink.close();
2663 return;
2664 }
2665 assert(
2666 value instanceof ArrayBuffer,
2667 "GetReader - expected an ArrayBuffer."
2668 );
2669 // Enqueue data chunk into sink, and transfer it
2670 // to other side as `Transferable` object.
2671 sink.enqueue(new Uint8Array(value), 1, [value]);
2672 })
2673 .catch(reason => {
2674 sink.error(reason);
2675 });
2676 };
2677
2678 sink.onCancel = reason => {
2679 this.#fullReader.cancel(reason);
2680
2681 sink.ready.catch(readyReason => {
2682 if (this.destroyed) {
2683 return; // Ignore any pending requests if the worker was terminated.
2684 }
2685 throw readyReason;
2686 });
2687 };
2688 });
2689
2690 messageHandler.on("ReaderHeadersReady", async data => {
2691 await this.#fullReader.headersReady;
2692
2693 const { isStreamingSupported, isRangeSupported, contentLength } =
2694 this.#fullReader;
2695
2696 if (isStreamingSupported && isRangeSupported) {
2697 this.#fullReader.onProgress = null; // See comment in "GetReader" above.
2698 }
2699 return { isStreamingSupported, isRangeSupported, contentLength };
2700 });
2701
2702 messageHandler.on("GetRangeReader", (data, sink) => {
2703 assert(
2704 this.#networkStream,
2705 "GetRangeReader - no `BasePDFStream` instance available."
2706 );
2707 const rangeReader = this.#networkStream.getRangeReader(
2708 data.begin,
2709 data.end
2710 );
2711
2712 // When streaming is enabled, it's possible that the data requested here
2713 // has already been fetched via the `#fullReader` implementation.
2714 // However, given that the PDF data is loaded asynchronously on the
2715 // main-thread and then sent via `postMessage` to the worker-thread,
2716 // it may not have been available during parsing (hence the attempt to
2717 // use range requests here).
2718 //
2719 // To avoid wasting time and resources here, we'll thus *not* dispatch
2720 // range requests if the data was already loaded but has not been sent to
2721 // the worker-thread yet (which will happen via the `#fullReader`).
2722 if (!rangeReader) {
2723 sink.close();
2724 return;
2725 }
2726
2727 sink.onPull = () => {
2728 rangeReader
2729 .read()
2730 .then(function ({ value, done }) {
2731 if (done) {
2732 sink.close();
2733 return;
2734 }
2735 assert(
2736 value instanceof ArrayBuffer,
2737 "GetRangeReader - expected an ArrayBuffer."
2738 );
2739 sink.enqueue(new Uint8Array(value), 1, [value]);
2740 })
2741 .catch(reason => {
2742 sink.error(reason);
2743 });
2744 };
2745
2746 sink.onCancel = reason => {
2747 rangeReader.cancel(reason);
2748
2749 sink.ready.catch(readyReason => {
2750 if (this.destroyed) {
2751 return; // Ignore any pending requests if the worker was terminated.
2752 }
2753 throw readyReason;
2754 });
2755 };
2756 });
2757
2758 messageHandler.on("GetDoc", ({ pdfInfo }) => {
2759 this.pagesMapper.pagesNumber = pdfInfo.numPages;
2760 this._numPages = pdfInfo.numPages;
2761 this._htmlForXfa = pdfInfo.htmlForXfa;
2762 delete pdfInfo.htmlForXfa;
2763 loadingTask._capability.resolve(new PDFDocumentProxy(pdfInfo, this));
2764 });
2765
2766 messageHandler.on("DocException", ex => {
2767 loadingTask._capability.reject(wrapReason(ex));
2768 });
2769
2770 messageHandler.on("PasswordRequest", ex => {
2771 this.#passwordCapability = Promise.withResolvers();
2772
2773 try {
2774 if (!loadingTask.onPassword) {
2775 throw wrapReason(ex);
2776 }
2777
2778 const updatePassword = password => {
2779 if (password instanceof Error) {
2780 this.#passwordCapability.reject(password);
2781 } else {
2782 this.#passwordCapability.resolve({ password });
2783 }
2784 };
2785 loadingTask.onPassword(updatePassword, ex.code);
2786 } catch (err) {
2787 this.#passwordCapability.reject(err);
2788 }
2789 return this.#passwordCapability.promise;
2790 });
2791
2792 messageHandler.on("DataLoaded", data => {
2793 // For consistency: Ensure that progress is always reported when the
2794 // entire PDF file has been loaded, regardless of how it was fetched.
2795 this.#onProgress({ loaded: data.length, total: data.length });
2796
2797 this.downloadInfoCapability.resolve(data);
2798 });
2799
2800 messageHandler.on("StartRenderPage", data => {
2801 if (this.destroyed) {
2802 return; // Ignore any pending requests if the worker was terminated.
2803 }
2804
2805 const page = this.#pageCache.get(data.pageIndex);
2806 page._startRenderPage(data.transparency, data.cacheKey);
2807 });
2808
2809 messageHandler.on("commonobj", ([id, type, exportedData]) => {
2810 if (this.destroyed) {
2811 return null; // Ignore any pending requests if the worker was terminated.
2812 }
2813
2814 if (this.commonObjs.has(id)) {
2815 return null;
2816 }
2817
2818 switch (type) {
2819 case "Font":
2820 if ("error" in exportedData) {
2821 const exportedError = exportedData.error;
2822 warn(`Error during font loading: ${exportedError}`);
2823 this.commonObjs.resolve(id, exportedError);
2824 break;
2825 }
2826
2827 const fontData = new FontInfo(exportedData);
2828 const inspectFont =
2829 this._params.pdfBug && globalThis.FontInspector?.enabled
2830 ? (font, url) => globalThis.FontInspector.fontAdded(font, url)
2831 : null;
2832 const font = new FontFaceObject(
2833 fontData,
2834 inspectFont,
2835 exportedData.charProcOperatorList,
2836 exportedData.extra
2837 );
2838
2839 this.fontLoader
2840 .bind(font)
2841 .catch(() => messageHandler.sendWithPromise("FontFallback", { id }))
2842 .finally(() => {
2843 if (!font.fontExtraProperties) {
2844 // Immediately release the `font.data` property once the font
2845 // has been attached to the DOM, since it's no longer needed,
2846 // rather than waiting for a `PDFDocumentProxy.cleanup` call.
2847 // Since `font.data` could be very large, e.g. in some cases
2848 // multiple megabytes, this will help reduce memory usage.
2849 font.clearData();
2850 }
2851 this.commonObjs.resolve(id, font);
2852 });
2853 break;
2854 case "CopyLocalImage":
2855 const { imageRef } = exportedData;
2856 assert(imageRef, "The imageRef must be defined.");
2857
2858 for (const pageProxy of this.#pageCache.values()) {
2859 for (const [, data] of pageProxy.objs) {
2860 if (data?.ref !== imageRef) {
2861 continue;
2862 }
2863 if (!data.dataLen) {
2864 return null;
2865 }
2866 const copy = structuredClone(data);
2867 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
2868 copy.CopyLocalImage = true;
2869 }
2870 this.commonObjs.resolve(id, copy);
2871 return data.dataLen;
2872 }
2873 }
2874 break;
2875 case "FontPath":
2876 this.commonObjs.resolve(id, new FontPathInfo(exportedData));
2877 break;
2878 case "Image":
2879 this.commonObjs.resolve(id, exportedData);
2880 break;
2881 case "Pattern":
2882 const pattern = new PatternInfo(exportedData);
2883 this.commonObjs.resolve(id, pattern.getIR());
2884 break;
2885 default:
2886 throw new Error(`Got unknown common object type ${type}`);
2887 }
2888
2889 return null;
2890 });
2891
2892 messageHandler.on("obj", ([id, pageIndex, type, imageData]) => {
2893 if (this.destroyed) {
2894 // Ignore any pending requests if the worker was terminated.
2895 return;
2896 }
2897
2898 const pageProxy = this.#pageCache.get(pageIndex);
2899 if (pageProxy.objs.has(id)) {
2900 return;
2901 }
2902 // Don't store data *after* cleanup has successfully run, see bug 1854145.
2903 if (pageProxy._intentStates.size === 0) {
2904 imageData?.bitmap?.close(); // Release any `ImageBitmap` data.
2905 return;
2906 }
2907
2908 switch (type) {
2909 case "Image":
2910 case "Pattern":
2911 pageProxy.objs.resolve(id, imageData);
2912 break;
2913 default:
2914 throw new Error(`Got unknown object type ${type}`);
2915 }
2916 });
2917
2918 messageHandler.on("DocProgress", data => {
2919 if (this.destroyed) {
2920 return; // Ignore any pending requests if the worker was terminated.
2921 }
2922 this.#onProgress(data);
2923 });
2924
2925 if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
2926 messageHandler.on("FetchBinaryData", async data => {
2927 if (this.destroyed) {
2928 throw new Error("Worker was destroyed.");
2929 }
2930 if (!this.binaryDataFactory) {
2931 throw new Error(
2932 "`BinaryDataFactory` not initialized, see the `useWorkerFetch` parameter."
2933 );
2934 }
2935 return this.binaryDataFactory.fetch(data);
2936 });
2937 }
2938 }
2939
2940 getData() {
2941 return this.messageHandler.sendWithPromise("GetData", null);
2942 }
2943
2944 saveDocument() {
2945 if (this.annotationStorage.size <= 0) {
2946 warn(
2947 "saveDocument called while `annotationStorage` is empty, " +
2948 "please use the getData-method instead."
2949 );
2950 }
2951 const { map, transfer } = this.annotationStorage.serializable;
2952
2953 return this.messageHandler
2954 .sendWithPromise(
2955 "SaveDocument",
2956 {
2957 isPureXfa: !!this._htmlForXfa,
2958 numPages: this._numPages,
2959 annotationStorage: map,
2960 filename: this.#fullReader?.filename ?? null,
2961 },
2962 transfer
2963 )
2964 .finally(() => {
2965 this.annotationStorage.resetModified();
2966 });
2967 }
2968
2969 extractPages(pageInfos, copyLevels = null) {
2970 const params = {
2971 pageInfos,
2972 };
2973 let transfer;
2974 const ImageBitmapCtor = globalThis.ImageBitmap;
2975 if (typeof ImageBitmapCtor === "function") {
2976 const infos = Array.isArray(pageInfos) ? pageInfos : [pageInfos];
2977 for (const pageInfo of infos) {
2978 if (pageInfo?.image instanceof ImageBitmapCtor) {
2979 (transfer ||= []).push(pageInfo.image);
2980 }
2981 }
2982 }
2983 if (this.annotationStorage.size > 0) {
2984 const serialized = this.annotationStorage.serializable;
2985 let { map } = serialized;
2986 if (serialized.transfer?.length) {
2987 if (transfer) {
2988 transfer.push(...serialized.transfer);
2989 } else {
2990 transfer = serialized.transfer;
2991 }
2992 }
2993 // Annotation pageIndex tracks the editor's current viewer position; the
2994 // worker keys lookups by source index. Remap UI -> source via pagesMapper
2995 // so reorganized pages still receive their annotations after extraction.
2996 // Multiple viewer pages can share a source page. The copy level routes
2997 // each editor annotation to the corresponding extracted copy.
2998 const mapping = this.pagesMapper.getMapping();
2999 if (mapping) {
3000 const remapped = new Map();
3001 for (const [k, v] of map) {
3002 if (
3003 v?.pageIndex !== undefined &&
3004 v.pageIndex >= 0 &&
3005 v.pageIndex < mapping.length
3006 ) {
3007 // copyLevels uses -1 for non-extracted pages. Keep their entries
3008 // because an extracted stamp may share their bitmapId; the worker
3009 // uses the negative level to skip the annotation itself.
3010 const copyLevel = copyLevels?.[v.pageIndex] ?? 0;
3011 const sourceIdx = mapping[v.pageIndex] - 1;
3012 if (sourceIdx !== v.pageIndex || copyLevel !== 0) {
3013 remapped.set(k, { ...v, pageIndex: sourceIdx, copyLevel });
3014 continue;
3015 }
3016 }
3017 remapped.set(k, v);
3018 }
3019 map = remapped;
3020 }
3021 params.annotationStorage = map;
3022 }
3023 return this.messageHandler
3024 .sendWithPromise("ExtractPages", params, transfer)
3025 .finally(() => {
3026 this.annotationStorage.resetModified();
3027 });
3028 }
3029
3030 getPage(pageNumber) {
3031 if (
3032 !Number.isInteger(pageNumber) ||
3033 pageNumber <= 0 ||
3034 pageNumber > this.pagesMapper.pagesNumber
3035 ) {
3036 return Promise.reject(new Error("Invalid page request."));
3037 }
3038 const pageIndex = pageNumber - 1;
3039 const newPageIndex = this.pagesMapper.getPageId(pageNumber) - 1;
3040
3041 const cachedPromise = this.#pagePromises.get(pageIndex);
3042 if (cachedPromise) {
3043 return cachedPromise;
3044 }
3045 const promise = this.messageHandler
3046 .sendWithPromise("GetPage", {
3047 pageIndex: newPageIndex,
3048 })
3049 .then(pageInfo => {
3050 if (this.destroyed) {
3051 throw new Error("Transport destroyed");
3052 }
3053 if (pageInfo.refStr) {
3054 this.#pageRefCache.set(pageInfo.refStr, newPageIndex);
3055 }
3056
3057 const page = new PDFPageProxy(
3058 pageIndex,
3059 pageInfo,
3060 this,
3061 this.pagesMapper,
3062 this._params.pdfBug
3063 );
3064 this.#pageCache.set(pageIndex, page);
3065 return page;
3066 });
3067 this.#pagePromises.set(pageIndex, promise);
3068 return promise;
3069 }
3070
3071 async getPageIndex(ref) {
3072 if (!isRefProxy(ref)) {
3073 throw new Error("Invalid pageIndex request.");
3074 }
3075 const index = await this.messageHandler.sendWithPromise("GetPageIndex", {
3076 num: ref.num,
3077 gen: ref.gen,
3078 });
3079 const pageNumber = this.pagesMapper.getPageNumber(index + 1);
3080 if (pageNumber === 0) {
3081 throw new Error("GetPageIndex: page has been removed.");
3082 }
3083 return pageNumber - 1;
3084 }
3085
3086 getAnnotations(pageIndex, intent) {
3087 return this.messageHandler.sendWithPromise("GetAnnotations", {
3088 pageIndex: this.pagesMapper.getPageId(pageIndex + 1) - 1,
3089 intent,
3090 });
3091 }
3092
3093 getFieldObjects() {
3094 return this.#cacheSimpleMethod("GetFieldObjects");
3095 }
3096
3097 getSignatures() {
3098 return this.#cacheSimpleMethod("GetSignatures");
3099 }
3100
3101 getSignatureData(id) {
3102 // Not cached: bytes should be one-shot. Holding them in the
3103 // `#methodPromises` map would keep them alive for the document's
3104 // lifetime, which defeats the metadata/data split.
3105 return this.messageHandler.sendWithPromise("GetSignatureData", id);
3106 }
3107
3108 hasJSActions() {
3109 return this.#cacheSimpleMethod("HasJSActions");
3110 }
3111
3112 getCalculationOrderIds() {
3113 return this.messageHandler.sendWithPromise("GetCalculationOrderIds", null);
3114 }
3115
3116 getDestinations() {
3117 return this.messageHandler.sendWithPromise("GetDestinations", null);
3118 }
3119
3120 getDestination(id) {
3121 if (typeof id !== "string") {
3122 return Promise.reject(new Error("Invalid destination request."));
3123 }
3124 return this.messageHandler.sendWithPromise("GetDestination", { id });
3125 }
3126
3127 getPageLabels() {
3128 return this.messageHandler.sendWithPromise("GetPageLabels", null);
3129 }
3130
3131 getPageLayout() {
3132 return this.messageHandler.sendWithPromise("GetPageLayout", null);
3133 }
3134
3135 getPageMode() {
3136 return this.messageHandler.sendWithPromise("GetPageMode", null);
3137 }
3138
3139 getViewerPreferences() {
3140 return this.messageHandler.sendWithPromise("GetViewerPreferences", null);
3141 }
3142
3143 getOpenAction() {
3144 return this.messageHandler.sendWithPromise("GetOpenAction", null);
3145 }
3146
3147 /**
3148 * @returns {Promise<Map<string, CatalogAttachment> | null>}
3149 * Promise that is resolved with a lookup table for mapping named
3150 * attachments to their content.
3151 */
3152 getAttachments() {
3153 return this.messageHandler.sendWithPromise("GetAttachments", null);
3154 }
3155
3156 /**
3157 * @param {string} id
3158 * Unique attachment identifier (required).
3159 * @returns {Promise<CatalogAttachmentContent>}
3160 * Promise that resolves to attachment content.
3161 */
3162 getAttachmentContent(id) {
3163 return this.messageHandler.sendWithPromise("GetAttachmentContent", id);
3164 }
3165
3166 getAnnotationsByType(types, pageIndexesToSkip) {
3167 return this.messageHandler.sendWithPromise("GetAnnotationsByType", {
3168 types,
3169 pageIndexesToSkip,
3170 });
3171 }
3172
3173 getDocJSActions() {
3174 return this.#cacheSimpleMethod("GetDocJSActions");
3175 }
3176
3177 getPageJSActions(pageIndex) {
3178 return this.messageHandler.sendWithPromise("GetPageJSActions", {
3179 pageIndex: this.pagesMapper.getPageId(pageIndex + 1) - 1,
3180 });
3181 }
3182
3183 getStructTree(pageIndex) {
3184 return this.messageHandler.sendWithPromise("GetStructTree", {
3185 pageIndex: this.pagesMapper.getPageId(pageIndex + 1) - 1,
3186 });
3187 }
3188
3189 getOutline() {
3190 return this.messageHandler.sendWithPromise("GetOutline", null);
3191 }
3192
3193 getOptionalContentConfig(renderingIntent) {
3194 return this.#cacheSimpleMethod("GetOptionalContentConfig").then(
3195 data => new OptionalContentConfig(data, renderingIntent)
3196 );
3197 }
3198
3199 getPermissions() {
3200 return this.messageHandler.sendWithPromise("GetPermissions", null);
3201 }
3202
3203 getMetadata() {
3204 const name = "GetMetadata";
3205
3206 return this.#methodPromises.getOrInsertComputed(name, () =>
3207 this.messageHandler.sendWithPromise(name, null).then(results => ({
3208 info: results[0],
3209 metadata: results[1] ? new Metadata(results[1]) : null,
3210 contentDispositionFilename: this.#fullReader?.filename ?? null,
3211 contentLength: this.#fullReader?.contentLength ?? null,
3212 hasStructTree: results[2],
3213 }))
3214 );
3215 }
3216
3217 getMarkInfo() {
3218 return this.messageHandler.sendWithPromise("GetMarkInfo", null);
3219 }
3220
3221 async startCleanup(keepLoadedFonts = false) {
3222 if (this.destroyed) {
3223 return; // No need to manually clean-up when destruction has started.
3224 }
3225 await this.messageHandler.sendWithPromise("Cleanup", null);
3226
3227 for (const page of this.#pageCache.values()) {
3228 const cleanupSuccessful = page.cleanup();
3229
3230 if (!cleanupSuccessful) {
3231 throw new Error(
3232 `startCleanup: Page ${page.pageNumber} is currently rendering.`
3233 );
3234 }
3235 }
3236 this.commonObjs.clear();
3237 if (!keepLoadedFonts) {
3238 this.fontLoader.clear();
3239 }
3240 this.#methodPromises.clear();
3241 this.filterFactory.destroy(/* keepHCM = */ true);
3242 TextLayer.cleanup();
3243 }
3244
3245 cachedPageNumber(ref) {
3246 if (!isRefProxy(ref)) {
3247 return null;
3248 }
3249 const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`;
3250 const pageIndex = this.#pageRefCache.get(refStr);
3251 if (pageIndex >= 0) {
3252 const pageNumber = this.pagesMapper.getPageNumber(pageIndex + 1);
3253 if (pageNumber !== 0) {
3254 return pageNumber;
3255 }
3256 }
3257 return null;
3258 }
3259}
3260
3261/**
3262 * Allows controlling of the rendering tasks.
3263 */
▼class RenderTask[76 lines]
3264class RenderTask {
3265 _internalRenderTask = null;
3266
3267 /**
3268 * Callback for incremental rendering -- a function that will be called
3269 * each time the rendering is paused. To continue rendering call the
3270 * function that is the first argument to the callback.
3271 * @type {function}
3272 */
3273 onContinue = null;
3274
3275 /**
3276 * A function that will be synchronously called when the rendering tasks
3277 * finishes with an error (either because of an actual error, or because the
3278 * rendering is cancelled).
3279 *
3280 * @type {function}
3281 * @param {Error} error
3282 */
3283 onError = null;
3284
3285 constructor(internalRenderTask) {
3286 this._internalRenderTask = internalRenderTask;
3287
3288 if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
3289 // For testing purposes.
3290 Object.defineProperty(this, "getOperatorList", {
3291 value: () => this._internalRenderTask.operatorList,
3292 });
3293 }
3294 }
3295
3296 /**
3297 * Promise for rendering task completion.
3298 * @type {Promise<void>}
3299 */
3300 get promise() {
3301 return this._internalRenderTask.capability.promise;
3302 }
3303
3304 /**
3305 * Cancels the rendering task. If the task is currently rendering it will
3306 * not be cancelled until graphics pauses with a timeout. The promise that
3307 * this object extends will be rejected when cancelled.
3308 *
3309 * @param {number} [extraDelay]
3310 */
3311 cancel(extraDelay = 0) {
3312 this._internalRenderTask.cancel(/* error = */ null, extraDelay);
3313 }
3314
3315 /**
3316 * Whether form fields are rendered separately from the main operatorList.
3317 * @type {boolean}
3318 */
3319 get separateAnnots() {
3320 const { separateAnnots } = this._internalRenderTask.operatorList;
3321 if (!separateAnnots) {
3322 return false;
3323 }
3324 const { annotationCanvasMap } = this._internalRenderTask;
3325 return (
3326 separateAnnots.form ||
3327 (separateAnnots.canvas && annotationCanvasMap?.size > 0)
3328 );
3329 }
3330
3331 get imageCoordinates() {
3332 return this._internalRenderTask.imageCoordinates || null;
3333 }
3334}
3335
3336/**
3337 * For internal use only.
3338 * @ignore
3339 */
▼class InternalRenderTask[220 lines]
3340class InternalRenderTask {
3341 #rAF = null;
3342
3343 static #canvasInUse = new WeakSet();
3344
3345 constructor({
3346 callback,
3347 params,
3348 objs,
3349 commonObjs,
3350 annotationCanvasMap,
3351 operatorList,
3352 pageIndex,
3353 canvasFactory,
3354 filterFactory,
3355 useRequestAnimationFrame = false,
3356 pdfBug = false,
3357 pageColors = null,
3358 enableHWA = false,
3359 operationsFilter = null,
3360 }) {
3361 this.callback = callback;
3362 this.params = params;
3363 this.objs = objs;
3364 this.commonObjs = commonObjs;
3365 this.annotationCanvasMap = annotationCanvasMap;
3366 this.operatorListIdx = null;
3367 this.operatorList = operatorList;
3368 this._pageIndex = pageIndex;
3369 this.canvasFactory = canvasFactory;
3370 this.filterFactory = filterFactory;
3371 this._pdfBug = pdfBug;
3372 this.pageColors = pageColors;
3373
3374 this.running = false;
3375 this.graphicsReadyCallback = null;
3376 this.graphicsReady = false;
3377 this._useRequestAnimationFrame =
3378 useRequestAnimationFrame === true && typeof window !== "undefined";
3379 this.cancelled = false;
3380 this.capability = Promise.withResolvers();
3381 this.task = new RenderTask(this);
3382 // caching this-bound methods
3383 this._cancelBound = this.cancel.bind(this);
3384 this._continueBound = this._continue.bind(this);
3385 this._scheduleNextBound = this._scheduleNext.bind(this);
3386 this._nextBound = this._next.bind(this);
3387 this._canvas = params.canvas;
3388 this._canvasContext = params.canvas ? null : params.canvasContext;
3389 this._enableHWA = enableHWA;
3390 this._dependencyTracker = params.dependencyTracker;
3391 this._imagesTracker = params.imagesTracker;
3392 this._operationsFilter = operationsFilter;
3393 }
3394
3395 get completed() {
3396 return this.capability.promise.catch(function () {
3397 // Ignoring errors, since we only want to know when rendering is
3398 // no longer pending.
3399 });
3400 }
3401
3402 initializeGraphics({ transparency = false, optionalContentConfig }) {
3403 if (this.cancelled) {
3404 return;
3405 }
3406 if (this._canvas) {
3407 if (InternalRenderTask.#canvasInUse.has(this._canvas)) {
3408 throw new Error(
3409 "Cannot use the same canvas during multiple render() operations. " +
3410 "Use different canvas or ensure previous operations were " +
3411 "cancelled or completed."
3412 );
3413 }
3414 InternalRenderTask.#canvasInUse.add(this._canvas);
3415 }
3416
3417 if (this._pdfBug && globalThis.StepperManager?.enabled) {
3418 this.stepper = globalThis.StepperManager.create(this._pageIndex);
3419 this.stepper.init(this.operatorList);
3420 this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
3421 }
3422 const {
3423 viewport,
3424 transform,
3425 background,
3426 dependencyTracker,
3427 imagesTracker,
3428 } = this.params;
3429
3430 // When printing in Firefox, we get a specific context in mozPrintCallback
3431 // which cannot be created from the canvas itself.
3432 const canvasContext =
3433 this._canvasContext ||
3434 this._canvas.getContext("2d", {
3435 alpha: false,
3436 willReadFrequently: !this._enableHWA,
3437 });
3438
3439 this.gfx = new CanvasGraphics(
3440 canvasContext,
3441 this.commonObjs,
3442 this.objs,
3443 this.canvasFactory,
3444 this.filterFactory,
3445 { optionalContentConfig },
3446 this.annotationCanvasMap,
3447 this.pageColors,
3448 dependencyTracker,
3449 imagesTracker
3450 );
3451 this.gfx.beginDrawing({
3452 transform,
3453 viewport,
3454 transparency,
3455 background,
3456 });
3457 this.operatorListIdx = 0;
3458 this.graphicsReady = true;
3459 this.graphicsReadyCallback?.();
3460 }
3461
3462 cancel(error = null, extraDelay = 0) {
3463 this.running = false;
3464 this.cancelled = true;
3465 this.gfx?.endDrawing();
3466 if (this.#rAF) {
3467 window.cancelAnimationFrame(this.#rAF);
3468 this.#rAF = null;
3469 }
3470 InternalRenderTask.#canvasInUse.delete(this._canvas);
3471
3472 error ||= new RenderingCancelledException(
3473 `Rendering cancelled, page ${this._pageIndex + 1}`,
3474 extraDelay
3475 );
3476 this.callback(error);
3477
3478 this.task.onError?.(error);
3479 }
3480
3481 operatorListChanged() {
3482 if (!this.graphicsReady) {
3483 this.graphicsReadyCallback ||= this._continueBound;
3484 return;
3485 }
3486 this.gfx.dependencyTracker?.growOperationsCount(
3487 this.operatorList.fnArray.length
3488 );
3489 this.stepper?.updateOperatorList(this.operatorList);
3490
3491 if (this.running) {
3492 return;
3493 }
3494 this._continue();
3495 }
3496
3497 _continue() {
3498 this.running = true;
3499 if (this.cancelled) {
3500 return;
3501 }
3502 if (this.task.onContinue) {
3503 this.task.onContinue(this._scheduleNextBound);
3504 } else {
3505 this._scheduleNext();
3506 }
3507 }
3508
3509 _scheduleNext() {
3510 if (this._useRequestAnimationFrame) {
3511 this.#rAF = window.requestAnimationFrame(() => {
3512 this.#rAF = null;
3513 this._nextBound().catch(this._cancelBound);
3514 });
3515 } else {
3516 Promise.resolve().then(this._nextBound).catch(this._cancelBound);
3517 }
3518 }
3519
3520 async _next() {
3521 if (this.cancelled) {
3522 return;
3523 }
3524 this.operatorListIdx = this.gfx.executeOperatorList(
3525 this.operatorList,
3526 this.operatorListIdx,
3527 this._continueBound,
3528 this.stepper,
3529 this._operationsFilter
3530 );
3531 if (this.operatorListIdx === this.operatorList.argsArray.length) {
3532 this.running = false;
3533 if (this.operatorList.lastChunk) {
3534 this.gfx.endDrawing();
3535 InternalRenderTask.#canvasInUse.delete(this._canvas);
3536 this.callback();
3537 }
3538 }
3539 }
3540}
3541
3542/** @type {string} */
3543const version =
3544 typeof PDFJSDev !== "undefined" ? PDFJSDev.eval("BUNDLE_VERSION") : null;
3545/** @type {string} */
3546const build =
3547 typeof PDFJSDev !== "undefined" ? PDFJSDev.eval("BUNDLE_BUILD") : null;
3548
3549export {
3550 build,
3551 getDocument,
3552 PDFDataRangeTransport,
3553 PDFDocumentLoadingTask,
3554 PDFDocumentProxy,
3555 PDFPageProxy,
3556 PDFWorker,
3557 RenderTask,
3558 version,
3559};