2 * @licstart The following is the entire license notice for the
3 * JavaScript code in this page
5 * Copyright 2024 Mozilla Foundation
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
19 * @licend The above is the entire license notice for the
20 * JavaScript code in this page
23 /******/ // The require scope
24 /******/ var __webpack_require__ = {};
26 /************************************************************************/
27 /******/ /* webpack/runtime/define property getters */
29 /******/ // define getter functions for harmony exports
30 /******/ __webpack_require__.d = (exports, definition) => {
31 /******/ for(var key in definition) {
32 /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
33 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
39 /******/ /* webpack/runtime/hasOwnProperty shorthand */
41 /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
44 /************************************************************************/
45 var __webpack_exports__ = {};
48 __webpack_require__.d(__webpack_exports__, {
49 PDFViewerApplication: () => (/* reexport */ PDFViewerApplication),
50 PDFViewerApplicationConstants: () => (/* binding */ AppConstants),
51 PDFViewerApplicationOptions: () => (/* reexport */ AppOptions)
55 const DEFAULT_SCALE_VALUE = "auto";
56 const DEFAULT_SCALE = 1.0;
57 const DEFAULT_SCALE_DELTA = 1.1;
58 const MIN_SCALE = 0.1;
59 const MAX_SCALE = 10.0;
60 const UNKNOWN_SCALE = 0;
61 const MAX_AUTO_SCALE = 1.25;
62 const SCROLLBAR_PADDING = 40;
63 const VERTICAL_PADDING = 5;
64 const RenderingStates = {
70 const PresentationModeState = {
84 const TextLayerMode = {
107 const AutoPrintRegExp = /\bprint\s*\(/;
108 function scrollIntoView(element, spot, scrollMatches = false) {
109 let parent = element.offsetParent;
111 console.error("offsetParent is not set -- cannot scroll");
114 let offsetY = element.offsetTop + element.clientTop;
115 let offsetX = element.offsetLeft + element.clientLeft;
116 while (parent.clientHeight === parent.scrollHeight && parent.clientWidth === parent.scrollWidth || scrollMatches && (parent.classList.contains("markedContent") || getComputedStyle(parent).overflow === "hidden")) {
117 offsetY += parent.offsetTop;
118 offsetX += parent.offsetLeft;
119 parent = parent.offsetParent;
125 if (spot.top !== undefined) {
128 if (spot.left !== undefined) {
129 offsetX += spot.left;
130 parent.scrollLeft = offsetX;
133 parent.scrollTop = offsetY;
135 function watchScroll(viewAreaElement, callback, abortSignal = undefined) {
136 const debounceScroll = function (evt) {
140 rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
142 const currentX = viewAreaElement.scrollLeft;
143 const lastX = state.lastX;
144 if (currentX !== lastX) {
145 state.right = currentX > lastX;
147 state.lastX = currentX;
148 const currentY = viewAreaElement.scrollTop;
149 const lastY = state.lastY;
150 if (currentY !== lastY) {
151 state.down = currentY > lastY;
153 state.lastY = currentY;
160 lastX: viewAreaElement.scrollLeft,
161 lastY: viewAreaElement.scrollTop,
162 _eventHandler: debounceScroll
165 viewAreaElement.addEventListener("scroll", debounceScroll, {
169 abortSignal?.addEventListener("abort", () => window.cancelAnimationFrame(rAF), {
174 function parseQueryString(query) {
175 const params = new Map();
176 for (const [key, value] of new URLSearchParams(query)) {
177 params.set(key.toLowerCase(), value);
181 const InvisibleCharsRegExp = /[\x00-\x1F]/g;
182 function removeNullCharacters(str, replaceInvisible = false) {
183 if (!InvisibleCharsRegExp.test(str)) {
186 if (replaceInvisible) {
187 return str.replaceAll(InvisibleCharsRegExp, m => m === "\x00" ? "" : " ");
189 return str.replaceAll("\x00", "");
191 function binarySearchFirstItem(items, condition, start = 0) {
192 let minIndex = start;
193 let maxIndex = items.length - 1;
194 if (maxIndex < 0 || !condition(items[maxIndex])) {
197 if (condition(items[minIndex])) {
200 while (minIndex < maxIndex) {
201 const currentIndex = minIndex + maxIndex >> 1;
202 const currentItem = items[currentIndex];
203 if (condition(currentItem)) {
204 maxIndex = currentIndex;
206 minIndex = currentIndex + 1;
211 function approximateFraction(x) {
212 if (Math.floor(x) === x) {
219 } else if (Math.floor(xinv) === xinv) {
222 const x_ = x > 1 ? xinv : x;
242 if (x_ - a / b < c / d - x_) {
243 result = x_ === x ? [a, b] : [b, a];
245 result = x_ === x ? [c, d] : [d, c];
249 function floorToDivide(x, div) {
252 function getPageSizeInches({
257 const [x1, y1, x2, y2] = view;
258 const changeOrientation = rotate % 180 !== 0;
259 const width = (x2 - x1) / 72 * userUnit;
260 const height = (y2 - y1) / 72 * userUnit;
262 width: changeOrientation ? height : width,
263 height: changeOrientation ? width : height
266 function backtrackBeforeAllVisibleElements(index, views, top) {
270 let elt = views[index].div;
271 let pageTop = elt.offsetTop + elt.clientTop;
272 if (pageTop >= top) {
273 elt = views[index - 1].div;
274 pageTop = elt.offsetTop + elt.clientTop;
276 for (let i = index - 2; i >= 0; --i) {
278 if (elt.offsetTop + elt.clientTop + elt.clientHeight <= pageTop) {
285 function getVisibleElements({
288 sortByVisibility = false,
292 const top = scrollEl.scrollTop,
293 bottom = top + scrollEl.clientHeight;
294 const left = scrollEl.scrollLeft,
295 right = left + scrollEl.clientWidth;
296 function isElementBottomAfterViewTop(view) {
297 const element = view.div;
298 const elementBottom = element.offsetTop + element.clientTop + element.clientHeight;
299 return elementBottom > top;
301 function isElementNextAfterViewHorizontally(view) {
302 const element = view.div;
303 const elementLeft = element.offsetLeft + element.clientLeft;
304 const elementRight = elementLeft + element.clientWidth;
305 return rtl ? elementLeft < right : elementRight > left;
309 numViews = views.length;
310 let firstVisibleElementInd = binarySearchFirstItem(views, horizontal ? isElementNextAfterViewHorizontally : isElementBottomAfterViewTop);
311 if (firstVisibleElementInd > 0 && firstVisibleElementInd < numViews && !horizontal) {
312 firstVisibleElementInd = backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top);
314 let lastEdge = horizontal ? right : -1;
315 for (let i = firstVisibleElementInd; i < numViews; i++) {
316 const view = views[i],
318 const currentWidth = element.offsetLeft + element.clientLeft;
319 const currentHeight = element.offsetTop + element.clientTop;
320 const viewWidth = element.clientWidth,
321 viewHeight = element.clientHeight;
322 const viewRight = currentWidth + viewWidth;
323 const viewBottom = currentHeight + viewHeight;
324 if (lastEdge === -1) {
325 if (viewBottom >= bottom) {
326 lastEdge = viewBottom;
328 } else if ((horizontal ? currentWidth : currentHeight) > lastEdge) {
331 if (viewBottom <= top || currentHeight >= bottom || viewRight <= left || currentWidth >= right) {
334 const hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, viewBottom - bottom);
335 const hiddenWidth = Math.max(0, left - currentWidth) + Math.max(0, viewRight - right);
336 const fractionHeight = (viewHeight - hiddenHeight) / viewHeight,
337 fractionWidth = (viewWidth - hiddenWidth) / viewWidth;
338 const percent = fractionHeight * fractionWidth * 100 | 0;
345 widthPercent: fractionWidth * 100 | 0
349 const first = visible[0],
350 last = visible.at(-1);
351 if (sortByVisibility) {
352 visible.sort(function (a, b) {
353 const pc = a.percent - b.percent;
354 if (Math.abs(pc) > 0.001) {
367 function normalizeWheelEventDirection(evt) {
368 let delta = Math.hypot(evt.deltaX, evt.deltaY);
369 const angle = Math.atan2(evt.deltaY, evt.deltaX);
370 if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
375 function normalizeWheelEventDelta(evt) {
376 const deltaMode = evt.deltaMode;
377 let delta = normalizeWheelEventDirection(evt);
378 const MOUSE_PIXELS_PER_LINE = 30;
379 const MOUSE_LINES_PER_PAGE = 30;
380 if (deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
381 delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
382 } else if (deltaMode === WheelEvent.DOM_DELTA_LINE) {
383 delta /= MOUSE_LINES_PER_PAGE;
387 function isValidRotation(angle) {
388 return Number.isInteger(angle) && angle % 90 === 0;
390 function isValidScrollMode(mode) {
391 return Number.isInteger(mode) && Object.values(ScrollMode).includes(mode) && mode !== ScrollMode.UNKNOWN;
393 function isValidSpreadMode(mode) {
394 return Number.isInteger(mode) && Object.values(SpreadMode).includes(mode) && mode !== SpreadMode.UNKNOWN;
396 function isPortraitOrientation(size) {
397 return size.width <= size.height;
399 const animationStarted = new Promise(function (resolve) {
400 window.requestAnimationFrame(resolve);
402 const docStyle = document.documentElement.style;
403 function clamp(v, min, max) {
404 return Math.min(Math.max(v, min), max);
408 #disableAutoFetchTimeout = null;
413 this.#classList = bar.classList;
414 this.#style = bar.style;
417 return this.#percent;
420 this.#percent = clamp(val, 0, 100);
422 this.#classList.add("indeterminate");
425 this.#classList.remove("indeterminate");
426 this.#style.setProperty("--progressBar-percent", `${this.#percent}%`);
432 const container = viewer.parentNode;
433 const scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
434 if (scrollbarWidth > 0) {
435 this.#style.setProperty("--progressBar-end-offset", `${scrollbarWidth}px`);
438 setDisableAutoFetch(delay = 5000) {
439 if (this.#percent === 100 || isNaN(this.#percent)) {
442 if (this.#disableAutoFetchTimeout) {
443 clearTimeout(this.#disableAutoFetchTimeout);
446 this.#disableAutoFetchTimeout = setTimeout(() => {
447 this.#disableAutoFetchTimeout = null;
452 if (!this.#visible) {
455 this.#visible = false;
456 this.#classList.add("hidden");
462 this.#visible = true;
463 this.#classList.remove("hidden");
466 function getActiveOrFocusedElement() {
467 let curRoot = document;
468 let curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
469 while (curActiveOrFocused?.shadowRoot) {
470 curRoot = curActiveOrFocused.shadowRoot;
471 curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
473 return curActiveOrFocused;
475 function apiPageLayoutToViewerModes(layout) {
476 let scrollMode = ScrollMode.VERTICAL,
477 spreadMode = SpreadMode.NONE;
480 scrollMode = ScrollMode.PAGE;
485 scrollMode = ScrollMode.PAGE;
486 case "TwoColumnLeft":
487 spreadMode = SpreadMode.ODD;
490 scrollMode = ScrollMode.PAGE;
491 case "TwoColumnRight":
492 spreadMode = SpreadMode.EVEN;
500 function apiPageModeToSidebarView(mode) {
503 return SidebarView.NONE;
505 return SidebarView.THUMBS;
507 return SidebarView.OUTLINE;
508 case "UseAttachments":
509 return SidebarView.ATTACHMENTS;
511 return SidebarView.LAYERS;
513 return SidebarView.NONE;
515 function toggleCheckedBtn(button, toggle, view = null) {
516 button.classList.toggle("toggled", toggle);
517 button.setAttribute("aria-checked", toggle);
518 view?.classList.toggle("hidden", !toggle);
520 function toggleExpandedBtn(button, toggle, view = null) {
521 button.classList.toggle("toggled", toggle);
522 button.setAttribute("aria-expanded", toggle);
523 view?.classList.toggle("hidden", !toggle);
525 const calcRound = Math.fround;
527 ;// ./web/app_options.js
533 EVENT_DISPATCH: 0x10,
543 const defaultOptions = {
544 allowedGlobalEvents: {
546 kind: OptionKind.BROWSER
548 canvasMaxAreaInBytes: {
550 kind: OptionKind.BROWSER + OptionKind.API
554 kind: OptionKind.BROWSER
558 kind: OptionKind.BROWSER
562 kind: OptionKind.BROWSER
564 supportsCaretBrowsingMode: {
566 kind: OptionKind.BROWSER
568 supportsDocumentFonts: {
570 kind: OptionKind.BROWSER
572 supportsIntegratedFind: {
574 kind: OptionKind.BROWSER
576 supportsMouseWheelZoomCtrlKey: {
578 kind: OptionKind.BROWSER
580 supportsMouseWheelZoomMetaKey: {
582 kind: OptionKind.BROWSER
584 supportsPinchToZoom: {
586 kind: OptionKind.BROWSER
590 kind: OptionKind.BROWSER + OptionKind.EVENT_DISPATCH
592 altTextLearnMoreUrl: {
593 value: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pdf-alt-text",
594 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
596 annotationEditorMode: {
598 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
602 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
606 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
609 value: "./debugger.mjs",
610 kind: OptionKind.VIEWER
614 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
618 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
622 kind: OptionKind.VIEWER
626 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
630 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
632 enableAltTextModelDownload: {
634 kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH
636 enableGuessAltText: {
638 kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH
640 enableHighlightFloatingButton: {
642 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
644 enableNewAltTextWhenAddingImage: {
646 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
650 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
652 enablePrintAutoRotate: {
654 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
658 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
660 enableSignatureEditor: {
662 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
664 enableUpdatedAddImage: {
666 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
669 value: "noopener noreferrer nofollow",
670 kind: OptionKind.VIEWER
672 externalLinkTarget: {
674 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
676 highlightEditorColors: {
677 value: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F",
678 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
682 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
684 ignoreDestinationZoom: {
686 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
688 imageResourcesPath: {
689 value: "resource://pdf.js/web/images/",
690 kind: OptionKind.VIEWER
694 kind: OptionKind.VIEWER
698 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
700 pageColorsBackground: {
702 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
704 pageColorsForeground: {
706 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
710 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
714 kind: OptionKind.VIEWER
718 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
722 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
726 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
730 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
734 kind: OptionKind.VIEWER + OptionKind.PREFERENCE
741 value: "resource://pdf.js/web/cmaps/",
746 kind: OptionKind.API + OptionKind.PREFERENCE
750 kind: OptionKind.API + OptionKind.PREFERENCE
754 kind: OptionKind.API + OptionKind.PREFERENCE
758 kind: OptionKind.API + OptionKind.PREFERENCE
766 kind: OptionKind.API + OptionKind.VIEWER + OptionKind.PREFERENCE
770 kind: OptionKind.API + OptionKind.PREFERENCE
772 fontExtraProperties: {
780 isOffscreenCanvasSupported: {
792 standardFontDataUrl: {
793 value: "resource://pdf.js/web/standard_fonts/",
798 kind: OptionKind.API,
799 type: Type.BOOLEAN + Type.UNDEFINED
806 value: "resource://pdf.js/web/wasm/",
811 kind: OptionKind.WORKER
814 value: "resource://pdf.js/build/pdf.worker.mjs",
815 kind: OptionKind.WORKER
820 static #opts = new Map();
822 for (const name in defaultOptions) {
823 this.#opts.set(name, defaultOptions[name].value);
827 return this.#opts.get(name);
829 static getAll(kind = null, defaultOnly = false) {
830 const options = Object.create(null);
831 for (const name in defaultOptions) {
832 const defaultOpt = defaultOptions[name];
833 if (kind && !(kind & defaultOpt.kind)) {
836 options[name] = !defaultOnly ? this.#opts.get(name) : defaultOpt.value;
840 static set(name, value) {
845 static setAll(options, prefs = false) {
847 for (const name in options) {
848 const defaultOpt = defaultOptions[name],
849 userOpt = options[name];
850 if (!defaultOpt || !(typeof userOpt === typeof defaultOpt.value || Type[(typeof userOpt).toUpperCase()] & defaultOpt.type)) {
856 if (prefs && !(kind & OptionKind.BROWSER || kind & OptionKind.PREFERENCE)) {
859 if (this.eventBus && kind & OptionKind.EVENT_DISPATCH) {
860 (events ||= new Map()).set(name, userOpt);
862 this.#opts.set(name, userOpt);
865 for (const [name, value] of events) {
866 this.eventBus.dispatch(name.toLowerCase(), {
875 ;// ./web/pdf_link_service.js
877 const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
885 class PDFLinkService {
886 externalLinkEnabled = true;
889 externalLinkTarget = null,
890 externalLinkRel = null,
891 ignoreDestinationZoom = false
893 this.eventBus = eventBus;
894 this.externalLinkTarget = externalLinkTarget;
895 this.externalLinkRel = externalLinkRel;
896 this._ignoreDestinationZoom = ignoreDestinationZoom;
898 this.pdfDocument = null;
899 this.pdfViewer = null;
900 this.pdfHistory = null;
902 setDocument(pdfDocument, baseUrl = null) {
903 this.baseUrl = baseUrl;
904 this.pdfDocument = pdfDocument;
906 setViewer(pdfViewer) {
907 this.pdfViewer = pdfViewer;
909 setHistory(pdfHistory) {
910 this.pdfHistory = pdfHistory;
913 return this.pdfDocument ? this.pdfDocument.numPages : 0;
916 return this.pdfDocument ? this.pdfViewer.currentPageNumber : 1;
919 if (this.pdfDocument) {
920 this.pdfViewer.currentPageNumber = value;
924 return this.pdfDocument ? this.pdfViewer.pagesRotation : 0;
926 set rotation(value) {
927 if (this.pdfDocument) {
928 this.pdfViewer.pagesRotation = value;
931 get isInPresentationMode() {
932 return this.pdfDocument ? this.pdfViewer.isInPresentationMode : false;
934 async goToDestination(dest) {
935 if (!this.pdfDocument) {
938 let namedDest, explicitDest, pageNumber;
939 if (typeof dest === "string") {
941 explicitDest = await this.pdfDocument.getDestination(dest);
944 explicitDest = await dest;
946 if (!Array.isArray(explicitDest)) {
947 console.error(`goToDestination: "${explicitDest}" is not a valid destination array, for dest="${dest}".`);
950 const [destRef] = explicitDest;
951 if (destRef && typeof destRef === "object") {
952 pageNumber = this.pdfDocument.cachedPageNumber(destRef);
955 pageNumber = (await this.pdfDocument.getPageIndex(destRef)) + 1;
957 console.error(`goToDestination: "${destRef}" is not a valid page reference, for dest="${dest}".`);
961 } else if (Number.isInteger(destRef)) {
962 pageNumber = destRef + 1;
964 if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
965 console.error(`goToDestination: "${pageNumber}" is not a valid page number, for dest="${dest}".`);
968 if (this.pdfHistory) {
969 this.pdfHistory.pushCurrentPosition();
970 this.pdfHistory.push({
976 this.pdfViewer.scrollPageIntoView({
978 destArray: explicitDest,
979 ignoreDestinationZoom: this._ignoreDestinationZoom
983 if (!this.pdfDocument) {
986 const pageNumber = typeof val === "string" && this.pdfViewer.pageLabelToPageNumber(val) || val | 0;
987 if (!(Number.isInteger(pageNumber) && pageNumber > 0 && pageNumber <= this.pagesCount)) {
988 console.error(`PDFLinkService.goToPage: "${val}" is not a valid page.`);
991 if (this.pdfHistory) {
992 this.pdfHistory.pushCurrentPosition();
993 this.pdfHistory.pushPage(pageNumber);
995 this.pdfViewer.scrollPageIntoView({
999 addLinkAttributes(link, url, newWindow = false) {
1000 if (!url || typeof url !== "string") {
1001 throw new Error('A valid "url" parameter must provided.');
1003 const target = newWindow ? LinkTarget.BLANK : this.externalLinkTarget,
1004 rel = this.externalLinkRel;
1005 if (this.externalLinkEnabled) {
1006 link.href = link.title = url;
1009 link.title = `Disabled: ${url}`;
1010 link.onclick = () => false;
1014 case LinkTarget.NONE:
1016 case LinkTarget.SELF:
1017 targetStr = "_self";
1019 case LinkTarget.BLANK:
1020 targetStr = "_blank";
1022 case LinkTarget.PARENT:
1023 targetStr = "_parent";
1025 case LinkTarget.TOP:
1029 link.target = targetStr;
1030 link.rel = typeof rel === "string" ? rel : DEFAULT_LINK_REL;
1032 getDestinationHash(dest) {
1033 if (typeof dest === "string") {
1034 if (dest.length > 0) {
1035 return this.getAnchorUrl("#" + escape(dest));
1037 } else if (Array.isArray(dest)) {
1038 const str = JSON.stringify(dest);
1039 if (str.length > 0) {
1040 return this.getAnchorUrl("#" + escape(str));
1043 return this.getAnchorUrl("");
1045 getAnchorUrl(anchor) {
1046 return this.baseUrl ? this.baseUrl + anchor : anchor;
1049 if (!this.pdfDocument) {
1052 let pageNumber, dest;
1053 if (hash.includes("=")) {
1054 const params = parseQueryString(hash);
1055 if (params.has("search")) {
1056 const query = params.get("search").replaceAll('"', ""),
1057 phrase = params.get("phrase") === "true";
1058 this.eventBus.dispatch("findfromurlhash", {
1060 query: phrase ? query : query.match(/\S+/g)
1063 if (params.has("page")) {
1064 pageNumber = params.get("page") | 0 || 1;
1066 if (params.has("zoom")) {
1067 const zoomArgs = params.get("zoom").split(",");
1068 const zoomArg = zoomArgs[0];
1069 const zoomArgNumber = parseFloat(zoomArg);
1070 if (!zoomArg.includes("Fit")) {
1073 }, zoomArgs.length > 1 ? zoomArgs[1] | 0 : null, zoomArgs.length > 2 ? zoomArgs[2] | 0 : null, zoomArgNumber ? zoomArgNumber / 100 : zoomArg];
1074 } else if (zoomArg === "Fit" || zoomArg === "FitB") {
1078 } else if (zoomArg === "FitH" || zoomArg === "FitBH" || zoomArg === "FitV" || zoomArg === "FitBV") {
1081 }, zoomArgs.length > 1 ? zoomArgs[1] | 0 : null];
1082 } else if (zoomArg === "FitR") {
1083 if (zoomArgs.length !== 5) {
1084 console.error('PDFLinkService.setHash: Not enough parameters for "FitR".');
1088 }, zoomArgs[1] | 0, zoomArgs[2] | 0, zoomArgs[3] | 0, zoomArgs[4] | 0];
1091 console.error(`PDFLinkService.setHash: "${zoomArg}" is not a valid zoom value.`);
1095 this.pdfViewer.scrollPageIntoView({
1096 pageNumber: pageNumber || this.page,
1098 allowNegativeOffset: true
1100 } else if (pageNumber) {
1101 this.page = pageNumber;
1103 if (params.has("pagemode")) {
1104 this.eventBus.dispatch("pagemode", {
1106 mode: params.get("pagemode")
1109 if (params.has("nameddest")) {
1110 this.goToDestination(params.get("nameddest"));
1112 if (!params.has("filename") || !params.has("filedest")) {
1115 hash = params.get("filedest");
1117 dest = unescape(hash);
1119 dest = JSON.parse(dest);
1120 if (!Array.isArray(dest)) {
1121 dest = dest.toString();
1124 if (typeof dest === "string" || PDFLinkService.#isValidExplicitDest(dest)) {
1125 this.goToDestination(dest);
1128 console.error(`PDFLinkService.setHash: "${unescape(hash)}" is not a valid destination.`);
1130 executeNamedAction(action) {
1131 if (!this.pdfDocument) {
1136 this.pdfHistory?.back();
1139 this.pdfHistory?.forward();
1142 this.pdfViewer.nextPage();
1145 this.pdfViewer.previousPage();
1148 this.page = this.pagesCount;
1156 this.eventBus.dispatch("namedaction", {
1161 async executeSetOCGState(action) {
1162 if (!this.pdfDocument) {
1165 const pdfDocument = this.pdfDocument,
1166 optionalContentConfig = await this.pdfViewer.optionalContentConfigPromise;
1167 if (pdfDocument !== this.pdfDocument) {
1170 optionalContentConfig.setOCGState(action);
1171 this.pdfViewer.optionalContentConfigPromise = Promise.resolve(optionalContentConfig);
1173 static #isValidExplicitDest(dest) {
1174 if (!Array.isArray(dest) || dest.length < 2) {
1177 const [page, zoom, ...args] = dest;
1178 if (!(typeof page === "object" && Number.isInteger(page?.num) && Number.isInteger(page?.gen)) && !Number.isInteger(page)) {
1181 if (!(typeof zoom === "object" && typeof zoom?.name === "string")) {
1184 const argsLen = args.length;
1185 let allowNull = true;
1186 switch (zoom.name) {
1188 if (argsLen < 2 || argsLen > 3) {
1194 return argsLen === 0;
1204 if (argsLen !== 4) {
1212 for (const arg of args) {
1213 if (!(typeof arg === "number" || allowNull && arg === null)) {
1220 class SimpleLinkService extends PDFLinkService {
1221 setDocument(pdfDocument, baseUrl = null) {}
1227 AnnotationEditorLayer,
1228 AnnotationEditorParamsType,
1229 AnnotationEditorType,
1230 AnnotationEditorUIManager,
1235 createValidAbsoluteUrl,
1242 getPdfFilenameFromUrl,
1244 GlobalWorkerOptions,
1246 InvalidPDFException,
1254 PDFDataRangeTransport,
1259 RenderingCancelledException,
1264 SupportedImageMimeTypes,
1271 } = globalThis.pdfjsLib;
1273 ;// ./web/event_utils.js
1274 const WaitOnType = {
1278 async function waitOnEventOrTimeout({
1283 if (typeof target !== "object" || !(name && typeof name === "string") || !(Number.isInteger(delay) && delay >= 0)) {
1284 throw new Error("waitOnEventOrTimeout - invalid parameters.");
1289 } = Promise.withResolvers();
1290 const ac = new AbortController();
1291 function handler(type) {
1293 clearTimeout(timeout);
1296 const evtMethod = target instanceof EventBus ? "_on" : "addEventListener";
1297 target[evtMethod](name, handler.bind(null, WaitOnType.EVENT), {
1300 const timeout = setTimeout(handler.bind(null, WaitOnType.TIMEOUT), delay);
1304 #listeners = Object.create(null);
1305 on(eventName, listener, options = null) {
1306 this._on(eventName, listener, {
1308 once: options?.once,
1309 signal: options?.signal
1312 off(eventName, listener, options = null) {
1313 this._off(eventName, listener);
1315 dispatch(eventName, data) {
1316 const eventListeners = this.#listeners[eventName];
1317 if (!eventListeners || eventListeners.length === 0) {
1320 let externalListeners;
1325 } of eventListeners.slice(0)) {
1327 this._off(eventName, listener);
1330 (externalListeners ||= []).push(listener);
1335 if (externalListeners) {
1336 for (const listener of externalListeners) {
1339 externalListeners = null;
1342 _on(eventName, listener, options = null) {
1344 if (options?.signal instanceof AbortSignal) {
1348 if (signal.aborted) {
1349 console.error("Cannot use an `aborted` signal.");
1352 const onAbort = () => this._off(eventName, listener);
1353 rmAbort = () => signal.removeEventListener("abort", onAbort);
1354 signal.addEventListener("abort", onAbort);
1356 const eventListeners = this.#listeners[eventName] ||= [];
1357 eventListeners.push({
1359 external: options?.external === true,
1360 once: options?.once === true,
1364 _off(eventName, listener, options = null) {
1365 const eventListeners = this.#listeners[eventName];
1366 if (!eventListeners) {
1369 for (let i = 0, ii = eventListeners.length; i < ii; i++) {
1370 const evt = eventListeners[i];
1371 if (evt.listener === listener) {
1373 eventListeners.splice(i, 1);
1379 class FirefoxEventBus extends EventBus {
1383 constructor(globalEventNames, externalServices, isInAutomation) {
1385 this.#globalEventNames = globalEventNames;
1386 this.#externalServices = externalServices;
1387 this.#isInAutomation = isInAutomation;
1389 dispatch(eventName, data) {
1390 super.dispatch(eventName, data);
1391 if (this.#isInAutomation) {
1392 const detail = Object.create(null);
1394 for (const key in data) {
1395 const value = data[key];
1396 if (key === "source") {
1397 if (value === window || value === document) {
1402 detail[key] = value;
1405 const event = new CustomEvent(eventName, {
1410 document.dispatchEvent(event);
1412 if (this.#globalEventNames?.has(eventName)) {
1413 this.#externalServices.dispatchGlobalEvent({
1421 ;// ./web/external_services.js
1422 class BaseExternalServices {
1423 updateFindControlState(data) {}
1424 updateFindMatchesCount(data) {}
1425 initPassiveLoading() {}
1426 reportTelemetry(data) {}
1427 async createL10n() {
1428 throw new Error("Not implemented: createL10n");
1431 throw new Error("Not implemented: createScripting");
1433 updateEditorStates(data) {
1434 throw new Error("Not implemented: updateEditorStates");
1436 dispatchGlobalEvent(_event) {}
1439 ;// ./web/preferences.js
1441 class BasePreferences {
1442 #defaults = Object.freeze({
1443 altTextLearnMoreUrl: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pdf-alt-text",
1444 annotationEditorMode: 0,
1446 cursorToolOnLoad: 0,
1447 defaultZoomDelay: 400,
1448 defaultZoomValue: "",
1449 disablePageLabels: false,
1450 enableAltText: false,
1451 enableAltTextModelDownload: true,
1452 enableGuessAltText: true,
1453 enableHighlightFloatingButton: false,
1454 enableNewAltTextWhenAddingImage: true,
1455 enablePermissions: false,
1456 enablePrintAutoRotate: true,
1457 enableScripting: true,
1458 enableSignatureEditor: false,
1459 enableUpdatedAddImage: false,
1460 externalLinkTarget: 0,
1461 highlightEditorColors: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F",
1462 historyUpdateUrl: false,
1463 ignoreDestinationZoom: false,
1464 forcePageColors: false,
1465 pageColorsBackground: "Canvas",
1466 pageColorsForeground: "CanvasText",
1467 pdfBugEnabled: false,
1468 sidebarViewOnLoad: -1,
1469 scrollModeOnLoad: -1,
1470 spreadModeOnLoad: -1,
1473 disableAutoFetch: false,
1474 disableFontFace: false,
1475 disableRange: false,
1476 disableStream: false,
1480 #initializedPromise = null;
1482 this.#initializedPromise = this._readFromStorage(this.#defaults).then(({
1491 window.addEventListener("updatedPreference", async ({
1497 await this.#initializedPromise;
1503 async _writeToStorage(prefObj) {
1504 throw new Error("Not implemented: _writeToStorage");
1506 async _readFromStorage(prefObj) {
1507 throw new Error("Not implemented: _readFromStorage");
1510 throw new Error("Please use `about:config` to change preferences.");
1512 async set(name, value) {
1513 await this.#initializedPromise;
1517 await this._writeToStorage({
1518 [name]: AppOptions.get(name)
1522 throw new Error("Not implemented: get");
1524 get initializedPromise() {
1525 return this.#initializedPromise;
1539 this.#lang = L10n.#fixupLangCode(lang);
1541 this.#dir = isRTL ?? L10n.#isRTL(this.#lang) ? "rtl" : "ltr";
1552 async get(ids, args = null, fallback) {
1553 if (Array.isArray(ids)) {
1554 ids = ids.map(id => ({
1557 const messages = await this.#l10n.formatMessages(ids);
1558 return messages.map(message => message.value);
1560 const messages = await this.#l10n.formatMessages([{
1564 return messages[0]?.value || fallback;
1566 async translate(element) {
1567 (this.#elements ||= new Set()).add(element);
1569 this.#l10n.connectRoot(element);
1570 await this.#l10n.translateRoots();
1573 async translateOnce(element) {
1575 await this.#l10n.translateElements([element]);
1577 console.error("translateOnce:", ex);
1581 if (this.#elements) {
1582 for (const element of this.#elements) {
1583 this.#l10n.disconnectRoot(element);
1585 this.#elements.clear();
1586 this.#elements = null;
1588 this.#l10n.pauseObserving();
1591 this.#l10n.pauseObserving();
1594 this.#l10n.resumeObserving();
1596 static #fixupLangCode(langCode) {
1597 langCode = langCode?.toLowerCase() || "en-us";
1598 const PARTIAL_LANG_CODES = {
1614 return PARTIAL_LANG_CODES[langCode] || langCode;
1616 static #isRTL(lang) {
1617 const shortCode = lang.split("-", 1)[0];
1618 return ["ar", "he", "fa", "ps", "ur"].includes(shortCode);
1621 const GenericL10n = null;
1623 ;// ./web/firefoxcom.js
1633 function initCom(app) {
1637 static requestAsync(action, data) {
1638 return new Promise(resolve => {
1639 this.request(action, data, resolve);
1642 static request(action, data, callback = null) {
1643 const request = document.createTextNode("");
1645 request.addEventListener("pdf.js.response", event => {
1646 const response = event.detail.response;
1647 event.target.remove();
1653 document.documentElement.append(request);
1654 const sender = new CustomEvent("pdf.js.message", {
1660 responseExpected: !!callback
1663 request.dispatchEvent(sender);
1666 class DownloadManager {
1667 #openBlobUrls = new WeakMap();
1668 downloadData(data, filename, contentType) {
1669 const blobUrl = URL.createObjectURL(new Blob([data], {
1672 FirefoxCom.request("download", {
1674 originalUrl: blobUrl,
1679 openOrDownloadData(data, filename, dest = null) {
1680 const isPdfData = isPdfFile(filename);
1681 const contentType = isPdfData ? "application/pdf" : "";
1683 let blobUrl = this.#openBlobUrls.get(data);
1685 blobUrl = URL.createObjectURL(new Blob([data], {
1688 this.#openBlobUrls.set(data, blobUrl);
1690 let viewerUrl = blobUrl + "#filename=" + encodeURIComponent(filename);
1692 viewerUrl += `&filedest=${escape(dest)}`;
1695 window.open(viewerUrl);
1698 console.error("openOrDownloadData:", ex);
1699 URL.revokeObjectURL(blobUrl);
1700 this.#openBlobUrls.delete(data);
1703 this.downloadData(data, filename, contentType);
1706 download(data, url, filename) {
1707 const blobUrl = data ? URL.createObjectURL(new Blob([data], {
1708 type: "application/pdf"
1710 FirefoxCom.request("download", {
1717 class Preferences extends BasePreferences {
1718 async _readFromStorage(prefObj) {
1719 return FirefoxCom.requestAsync("getPreferences", prefObj);
1721 async _writeToStorage(prefObj) {
1722 return FirefoxCom.requestAsync("setPreferences", prefObj);
1725 (function listenFindEvents() {
1726 const events = ["find", "findagain", "findhighlightallchange", "findcasesensitivitychange", "findentirewordchange", "findbarclose", "finddiacriticmatchingchange"];
1727 const findLen = "find".length;
1728 const handleEvent = function ({
1732 if (!viewerApp.initialized) {
1735 if (type === "findbarclose") {
1736 viewerApp.eventBus.dispatch(type, {
1741 viewerApp.eventBus.dispatch("find", {
1743 type: type.substring(findLen),
1744 query: detail.query,
1745 caseSensitive: !!detail.caseSensitive,
1746 entireWord: !!detail.entireWord,
1747 highlightAll: !!detail.highlightAll,
1748 findPrevious: !!detail.findPrevious,
1749 matchDiacritics: !!detail.matchDiacritics
1752 for (const event of events) {
1753 window.addEventListener(event, handleEvent);
1756 (function listenZoomEvents() {
1757 const events = ["zoomin", "zoomout", "zoomreset"];
1758 const handleEvent = function ({
1762 if (!viewerApp.initialized) {
1765 if (type === "zoomreset" && viewerApp.pdfViewer.currentScaleValue === DEFAULT_SCALE_VALUE) {
1768 viewerApp.eventBus.dispatch(type, {
1772 for (const event of events) {
1773 window.addEventListener(event, handleEvent);
1776 (function listenSaveEvent() {
1777 const handleEvent = function ({
1781 if (!viewerApp.initialized) {
1784 viewerApp.eventBus.dispatch("download", {
1788 window.addEventListener("save", handleEvent);
1790 (function listenEditingEvent() {
1791 const handleEvent = function ({
1794 if (!viewerApp.initialized) {
1797 viewerApp.eventBus.dispatch("editingaction", {
1802 window.addEventListener("editingaction", handleEvent);
1804 class FirefoxComDataRangeTransport extends PDFDataRangeTransport {
1805 requestDataRange(begin, end) {
1806 FirefoxCom.request("requestDataRange", {
1812 FirefoxCom.request("abortLoading", null);
1815 class FirefoxScripting {
1816 static async createSandbox(data) {
1817 const success = await FirefoxCom.requestAsync("createSandbox", data);
1819 throw new Error("Cannot create sandbox.");
1822 static async dispatchEventInSandbox(event) {
1823 FirefoxCom.request("dispatchEventInSandbox", event);
1825 static async destroySandbox() {
1826 FirefoxCom.request("destroySandbox", null);
1830 #abortSignal = null;
1834 #requestResolvers = null;
1835 hasProgress = false;
1836 static #AI_ALT_TEXT_MODEL_NAME = "moz-image-to-text";
1838 altTextLearnMoreUrl,
1840 enableAltTextModelDownload
1842 this.altTextLearnMoreUrl = altTextLearnMoreUrl;
1843 this.enableAltTextModelDownload = enableAltTextModelDownload;
1844 this.enableGuessAltText = enableGuessAltText;
1846 setEventBus(eventBus, abortSignal) {
1847 this.#eventBus = eventBus;
1848 this.#abortSignal = abortSignal;
1849 eventBus._on("enablealttextmodeldownload", ({
1852 if (this.enableAltTextModelDownload === value) {
1856 this.downloadModel("altText");
1858 this.deleteModel("altText");
1863 eventBus._on("enableguessalttext", ({
1866 this.toggleService("altText", value);
1871 async isEnabledFor(name) {
1872 return this.enableGuessAltText && !!(await this.#enabled?.get(name));
1875 return this.#ready?.has(name) ?? false;
1877 async deleteModel(name) {
1878 if (name !== "altText" || !this.enableAltTextModelDownload) {
1881 this.enableAltTextModelDownload = false;
1882 this.#ready?.delete(name);
1883 this.#enabled?.delete(name);
1884 await this.toggleService("altText", false);
1885 await FirefoxCom.requestAsync("mlDelete", MLManager.#AI_ALT_TEXT_MODEL_NAME);
1887 async loadModel(name) {
1888 if (name === "altText" && this.enableAltTextModelDownload) {
1889 await this.#loadAltTextEngine(false);
1892 async downloadModel(name) {
1893 if (name !== "altText" || this.enableAltTextModelDownload) {
1896 this.enableAltTextModelDownload = true;
1897 return this.#loadAltTextEngine(true);
1900 if (data?.name !== "altText") {
1903 const resolvers = this.#requestResolvers ||= new Set();
1904 const resolver = Promise.withResolvers();
1905 resolvers.add(resolver);
1906 data.service = MLManager.#AI_ALT_TEXT_MODEL_NAME;
1907 FirefoxCom.requestAsync("mlGuess", data).then(response => {
1908 if (resolvers.has(resolver)) {
1909 resolver.resolve(response);
1910 resolvers.delete(resolver);
1912 }).catch(reason => {
1913 if (resolvers.has(resolver)) {
1914 resolver.reject(reason);
1915 resolvers.delete(resolver);
1918 return resolver.promise;
1920 async #cancelAllRequests() {
1921 if (!this.#requestResolvers) {
1924 for (const resolver of this.#requestResolvers) {
1929 this.#requestResolvers.clear();
1930 this.#requestResolvers = null;
1932 async toggleService(name, enabled) {
1933 if (name !== "altText" || this.enableGuessAltText === enabled) {
1936 this.enableGuessAltText = enabled;
1938 if (this.enableAltTextModelDownload) {
1939 await this.#loadAltTextEngine(false);
1942 this.#cancelAllRequests();
1945 async #loadAltTextEngine(listenToProgress) {
1946 if (this.#enabled?.has("altText")) {
1949 this.#ready ||= new Set();
1950 const promise = FirefoxCom.requestAsync("loadAIEngine", {
1951 service: MLManager.#AI_ALT_TEXT_MODEL_NAME,
1955 this.#ready.add("altText");
1959 (this.#enabled ||= new Map()).set("altText", promise);
1960 if (listenToProgress) {
1961 const ac = new AbortController();
1962 const signal = AbortSignal.any([this.#abortSignal, ac.signal]);
1963 this.hasProgress = true;
1964 window.addEventListener("loadAIEngineProgress", ({
1967 this.#eventBus.dispatch("loadaiengineprogress", {
1971 if (detail.finished) {
1973 this.hasProgress = false;
1978 promise.then(ok => {
1981 this.hasProgress = false;
1988 class ExternalServices extends BaseExternalServices {
1989 updateFindControlState(data) {
1990 FirefoxCom.request("updateFindControlState", data);
1992 updateFindMatchesCount(data) {
1993 FirefoxCom.request("updateFindMatchesCount", data);
1995 initPassiveLoading() {
1996 let pdfDataRangeTransport;
1997 window.addEventListener("message", function windowMessage(e) {
1998 if (e.source !== null) {
1999 console.warn("Rejected untrusted message from " + e.origin);
2002 const args = e.data;
2003 if (typeof args !== "object" || !("pdfjsLoadAction" in args)) {
2006 switch (args.pdfjsLoadAction) {
2007 case "supportsRangedLoading":
2008 if (args.done && !args.data) {
2009 viewerApp._documentError(null);
2012 pdfDataRangeTransport = new FirefoxComDataRangeTransport(args.length, args.data, args.done, args.filename);
2014 range: pdfDataRangeTransport
2018 pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
2020 case "rangeProgress":
2021 pdfDataRangeTransport.onDataProgress(args.loaded);
2023 case "progressiveRead":
2024 pdfDataRangeTransport.onDataProgressiveRead(args.chunk);
2025 pdfDataRangeTransport.onDataProgress(args.loaded, args.total);
2027 case "progressiveDone":
2028 pdfDataRangeTransport?.onDataProgressiveDone();
2031 viewerApp.progress(args.loaded / args.total);
2035 viewerApp._documentError(null, {
2036 message: args.errorCode
2042 filename: args.filename
2047 FirefoxCom.request("initPassiveLoading", null);
2049 reportTelemetry(data) {
2050 FirefoxCom.request("reportTelemetry", data);
2052 updateEditorStates(data) {
2053 FirefoxCom.request("updateEditorStates", data);
2055 async createL10n() {
2056 await document.l10n.ready;
2057 return new L10n(AppOptions.get("localeProperties"), document.l10n);
2060 return FirefoxScripting;
2062 dispatchGlobalEvent(event) {
2063 FirefoxCom.request("dispatchGlobalEvent", event);
2067 ;// ./web/new_alt_text_manager.js
2069 class NewAltTextManager {
2070 #boundCancel = this.#cancel.bind(this);
2071 #createAutomaticallyButton;
2072 #currentEditor = null;
2074 #descriptionContainer;
2078 #downloadModelDescription;
2086 #isAILoading = false;
2087 #wasAILoading = false;
2094 #previousAltText = null;
2096 descriptionContainer,
2106 createAutomaticallyButton,
2108 downloadModelDescription,
2110 }, overlayManager, eventBus) {
2111 this.#cancelButton = cancelButton;
2112 this.#createAutomaticallyButton = createAutomaticallyButton;
2113 this.#descriptionContainer = descriptionContainer;
2114 this.#dialog = dialog;
2115 this.#disclaimer = disclaimer;
2116 this.#notNowButton = notNowButton;
2117 this.#imagePreview = imagePreview;
2118 this.#textarea = textarea;
2119 this.#learnMore = learnMore;
2120 this.#title = title;
2121 this.#downloadModel = downloadModel;
2122 this.#downloadModelDescription = downloadModelDescription;
2123 this.#overlayManager = overlayManager;
2124 this.#eventBus = eventBus;
2125 dialog.addEventListener("close", this.#close.bind(this));
2126 dialog.addEventListener("contextmenu", event => {
2127 if (event.target !== this.#textarea) {
2128 event.preventDefault();
2131 cancelButton.addEventListener("click", this.#boundCancel);
2132 notNowButton.addEventListener("click", this.#boundCancel);
2133 saveButton.addEventListener("click", this.#save.bind(this));
2134 errorCloseButton.addEventListener("click", () => {
2135 this.#toggleError(false);
2137 createAutomaticallyButton.addEventListener("click", async () => {
2138 const checked = createAutomaticallyButton.getAttribute("aria-pressed") !== "true";
2139 this.#currentEditor._reportTelemetry({
2140 action: "pdfjs.image.alt_text.ai_generation_check",
2145 if (this.#uiManager) {
2146 this.#uiManager.setPreference("enableGuessAltText", checked);
2147 await this.#uiManager.mlManager.toggleService("altText", checked);
2149 this.#toggleGuessAltText(checked, false);
2151 textarea.addEventListener("focus", () => {
2152 this.#wasAILoading = this.#isAILoading;
2153 this.#toggleLoading(false);
2154 this.#toggleTitleAndDisclaimer();
2156 textarea.addEventListener("blur", () => {
2157 if (!textarea.value) {
2158 this.#toggleLoading(this.#wasAILoading);
2160 this.#toggleTitleAndDisclaimer();
2162 textarea.addEventListener("input", () => {
2163 this.#toggleTitleAndDisclaimer();
2165 eventBus._on("enableguessalttext", ({
2168 this.#toggleGuessAltText(value, false);
2170 this.#overlayManager.register(dialog);
2171 this.#learnMore.addEventListener("click", () => {
2172 this.#currentEditor._reportTelemetry({
2173 action: "pdfjs.image.alt_text.info",
2180 #toggleLoading(value) {
2181 if (!this.#uiManager || this.#isAILoading === value) {
2184 this.#isAILoading = value;
2185 this.#descriptionContainer.classList.toggle("loading", value);
2187 #toggleError(value) {
2188 if (!this.#uiManager) {
2191 this.#dialog.classList.toggle("error", value);
2193 async #toggleGuessAltText(value, isInitial = false) {
2194 if (!this.#uiManager) {
2197 this.#dialog.classList.toggle("aiDisabled", !value);
2198 this.#createAutomaticallyButton.setAttribute("aria-pressed", value);
2202 } = this.#uiManager.mlManager;
2203 if (altTextLearnMoreUrl) {
2204 this.#learnMore.href = altTextLearnMoreUrl;
2206 this.#mlGuessAltText(isInitial);
2208 this.#toggleLoading(false);
2209 this.#isAILoading = false;
2210 this.#toggleTitleAndDisclaimer();
2214 this.#notNowButton.classList.toggle("hidden", !this.#firstTime);
2215 this.#cancelButton.classList.toggle("hidden", this.#firstTime);
2218 if (!this.#uiManager || this.#hasAI === value) {
2221 this.#hasAI = value;
2222 this.#dialog.classList.toggle("noAi", !value);
2223 this.#toggleTitleAndDisclaimer();
2225 #toggleTitleAndDisclaimer() {
2226 const visible = this.#isAILoading || this.#guessedAltText && this.#guessedAltText === this.#textarea.value;
2227 this.#disclaimer.hidden = !visible;
2228 const isEditing = this.#isAILoading || !!this.#textarea.value;
2229 if (this.#isEditing === isEditing) {
2232 this.#isEditing = isEditing;
2233 this.#title.setAttribute("data-l10n-id", isEditing ? "pdfjs-editor-new-alt-text-dialog-edit-label" : "pdfjs-editor-new-alt-text-dialog-add-label");
2235 async #mlGuessAltText(isInitial) {
2236 if (this.#isAILoading) {
2239 if (this.#textarea.value) {
2242 if (isInitial && this.#previousAltText !== null) {
2245 this.#guessedAltText = this.#currentEditor.guessedAltText;
2246 if (this.#previousAltText === null && this.#guessedAltText) {
2247 this.#addAltText(this.#guessedAltText);
2250 this.#toggleLoading(true);
2251 this.#toggleTitleAndDisclaimer();
2252 let hasError = false;
2254 const altText = await this.#currentEditor.mlGuessAltText(this.#imageData, false);
2256 this.#guessedAltText = altText;
2257 this.#wasAILoading = this.#isAILoading;
2258 if (this.#isAILoading) {
2259 this.#addAltText(altText);
2266 this.#toggleLoading(false);
2267 this.#toggleTitleAndDisclaimer();
2268 if (hasError && this.#uiManager) {
2269 this.#toggleError(true);
2272 #addAltText(altText) {
2273 if (!this.#uiManager || this.#textarea.value) {
2276 this.#textarea.value = altText;
2277 this.#toggleTitleAndDisclaimer();
2280 this.#downloadModel.classList.toggle("hidden", false);
2281 const callback = async ({
2288 const ONE_MEGA_BYTES = 1e6;
2289 totalLoaded = Math.min(0.99 * total, totalLoaded);
2290 const totalSize = this.#downloadModelDescription.ariaValueMax = Math.round(total / ONE_MEGA_BYTES);
2291 const downloadedSize = this.#downloadModelDescription.ariaValueNow = Math.round(totalLoaded / ONE_MEGA_BYTES);
2292 this.#downloadModelDescription.setAttribute("data-l10n-args", JSON.stringify({
2299 this.#eventBus._off("loadaiengineprogress", callback);
2300 this.#downloadModel.classList.toggle("hidden", true);
2301 this.#toggleAI(true);
2302 if (!this.#uiManager) {
2307 } = this.#uiManager;
2308 mlManager.toggleService("altText", true);
2309 this.#toggleGuessAltText(await mlManager.isEnabledFor("altText"), true);
2311 this.#eventBus._on("loadaiengineprogress", callback);
2313 async editAltText(uiManager, editor, firstTime) {
2314 if (this.#currentEditor || !editor) {
2317 if (firstTime && editor.hasAltTextData()) {
2318 editor.altTextFinish();
2321 this.#firstTime = firstTime;
2325 let hasAI = !!mlManager;
2326 this.#toggleTitleAndDisclaimer();
2327 if (mlManager && !mlManager.isReady("altText")) {
2329 if (mlManager.hasProgress) {
2330 this.#setProgress();
2335 this.#downloadModel.classList.toggle("hidden", true);
2337 const isAltTextEnabledPromise = mlManager?.isEnabledFor("altText");
2338 this.#currentEditor = editor;
2339 this.#uiManager = uiManager;
2340 this.#uiManager.removeEditListeners();
2342 altText: this.#previousAltText
2343 } = editor.altTextData);
2344 this.#textarea.value = this.#previousAltText ?? "";
2345 const AI_MAX_IMAGE_DIMENSION = 224;
2346 const MAX_PREVIEW_DIMENSION = 180;
2347 let canvas, width, height;
2353 imageData: this.#imageData
2354 } = editor.copyCanvas(AI_MAX_IMAGE_DIMENSION, MAX_PREVIEW_DIMENSION, true));
2356 this.#toggleGuessAltText(await isAltTextEnabledPromise, true);
2363 } = editor.copyCanvas(AI_MAX_IMAGE_DIMENSION, MAX_PREVIEW_DIMENSION, false));
2365 canvas.setAttribute("role", "presentation");
2369 style.width = `${width}px`;
2370 style.height = `${height}px`;
2371 this.#imagePreview.append(canvas);
2372 this.#toggleNotNow();
2373 this.#toggleAI(hasAI);
2374 this.#toggleError(false);
2376 await this.#overlayManager.open(this.#dialog);
2383 this.#currentEditor.altTextData = {
2386 const altText = this.#textarea.value.trim();
2387 this.#currentEditor._reportTelemetry({
2388 action: "pdfjs.image.alt_text.dismiss",
2390 alt_text_type: altText ? "present" : "empty",
2391 flow: this.#firstTime ? "image_add" : "alt_text_edit"
2394 this.#currentEditor._reportTelemetry({
2395 action: "pdfjs.image.image_added",
2397 alt_text_modal: true,
2398 alt_text_type: "skipped"
2404 if (this.#overlayManager.active === this.#dialog) {
2405 this.#overlayManager.close(this.#dialog);
2409 const canvas = this.#imagePreview.firstChild;
2411 canvas.width = canvas.height = 0;
2412 this.#imageData = null;
2413 this.#toggleLoading(false);
2414 this.#uiManager?.addEditListeners();
2415 this.#currentEditor.altTextFinish();
2416 this.#uiManager?.setSelected(this.#currentEditor);
2417 this.#currentEditor = null;
2418 this.#uiManager = null;
2420 #extractWords(text) {
2421 return new Set(text.toLowerCase().split(/[^\p{L}\p{N}]+/gu).filter(x => !!x));
2424 const altText = this.#textarea.value.trim();
2425 this.#currentEditor.altTextData = {
2429 this.#currentEditor.altTextData.guessedAltText = this.#guessedAltText;
2430 if (this.#guessedAltText && this.#guessedAltText !== altText) {
2431 const guessedWords = this.#extractWords(this.#guessedAltText);
2432 const words = this.#extractWords(altText);
2433 this.#currentEditor._reportTelemetry({
2434 action: "pdfjs.image.alt_text.user_edit",
2436 total_words: guessedWords.size,
2437 words_removed: guessedWords.difference(words).size,
2438 words_added: words.difference(guessedWords).size
2442 this.#currentEditor._reportTelemetry({
2443 action: "pdfjs.image.image_added",
2445 alt_text_modal: true,
2446 alt_text_type: altText ? "present" : "empty"
2449 this.#currentEditor._reportTelemetry({
2450 action: "pdfjs.image.alt_text.save",
2452 alt_text_type: altText ? "present" : "empty",
2453 flow: this.#firstTime ? "image_add" : "alt_text_edit"
2459 this.#uiManager = null;
2463 class ImageAltTextSettings {
2466 #downloadModelButton;
2471 #showAltTextDialogButton;
2479 downloadModelButton,
2480 showAltTextDialogButton
2481 }, overlayManager, eventBus, mlManager) {
2482 this.#dialog = dialog;
2483 this.#aiModelSettings = aiModelSettings;
2484 this.#createModelButton = createModelButton;
2485 this.#downloadModelButton = downloadModelButton;
2486 this.#showAltTextDialogButton = showAltTextDialogButton;
2487 this.#overlayManager = overlayManager;
2488 this.#eventBus = eventBus;
2489 this.#mlManager = mlManager;
2493 if (altTextLearnMoreUrl) {
2494 learnMore.href = altTextLearnMoreUrl;
2496 dialog.addEventListener("contextmenu", noContextMenu);
2497 createModelButton.addEventListener("click", async e => {
2498 const checked = this.#togglePref("enableGuessAltText", e);
2499 await mlManager.toggleService("altText", checked);
2500 this.#reportTelemetry({
2502 action: "pdfjs.image.alt_text.settings_ai_generation_check",
2508 showAltTextDialogButton.addEventListener("click", e => {
2509 const checked = this.#togglePref("enableNewAltTextWhenAddingImage", e);
2510 this.#reportTelemetry({
2512 action: "pdfjs.image.alt_text.settings_edit_alt_text_check",
2518 deleteModelButton.addEventListener("click", this.#delete.bind(this, true));
2519 downloadModelButton.addEventListener("click", this.#download.bind(this, true));
2520 closeButton.addEventListener("click", this.#finish.bind(this));
2521 learnMore.addEventListener("click", () => {
2522 this.#reportTelemetry({
2524 action: "pdfjs.image.alt_text.info",
2526 topic: "ai_generation"
2530 eventBus._on("enablealttextmodeldownload", ({
2534 this.#download(false);
2536 this.#delete(false);
2539 this.#overlayManager.register(dialog);
2541 #reportTelemetry(data) {
2542 this.#eventBus.dispatch("reporttelemetry", {
2550 async #download(isFromUI = false) {
2552 this.#downloadModelButton.disabled = true;
2553 const span = this.#downloadModelButton.firstChild;
2554 span.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-settings-downloading-model-button");
2555 await this.#mlManager.downloadModel("altText");
2556 span.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-settings-download-model-button");
2557 this.#createModelButton.disabled = false;
2558 this.#setPref("enableGuessAltText", true);
2559 this.#mlManager.toggleService("altText", true);
2560 this.#setPref("enableAltTextModelDownload", true);
2561 this.#downloadModelButton.disabled = false;
2563 this.#aiModelSettings.classList.toggle("download", false);
2564 this.#createModelButton.setAttribute("aria-pressed", true);
2566 async #delete(isFromUI = false) {
2568 await this.#mlManager.deleteModel("altText");
2569 this.#setPref("enableGuessAltText", false);
2570 this.#setPref("enableAltTextModelDownload", false);
2572 this.#aiModelSettings.classList.toggle("download", true);
2573 this.#createModelButton.disabled = true;
2574 this.#createModelButton.setAttribute("aria-pressed", false);
2578 enableNewAltTextWhenAddingImage
2581 enableAltTextModelDownload
2582 } = this.#mlManager;
2583 this.#createModelButton.disabled = !enableAltTextModelDownload;
2584 this.#createModelButton.setAttribute("aria-pressed", enableAltTextModelDownload && enableGuessAltText);
2585 this.#showAltTextDialogButton.setAttribute("aria-pressed", enableNewAltTextWhenAddingImage);
2586 this.#aiModelSettings.classList.toggle("download", !enableAltTextModelDownload);
2587 await this.#overlayManager.open(this.#dialog);
2588 this.#reportTelemetry({
2590 action: "pdfjs.image.alt_text.settings_displayed"
2596 const checked = target.getAttribute("aria-pressed") !== "true";
2597 this.#setPref(name, checked);
2598 target.setAttribute("aria-pressed", checked);
2601 #setPref(name, value) {
2602 this.#eventBus.dispatch("setpreference", {
2609 if (this.#overlayManager.active === this.#dialog) {
2610 this.#overlayManager.close(this.#dialog);
2615 ;// ./web/alt_text_manager.js
2617 class AltTextManager {
2619 #currentEditor = null;
2623 #hasUsedPointer = false;
2630 #previousAltText = null;
2633 #rectElement = null;
2635 #telemetryData = null;
2643 }, container, overlayManager, eventBus) {
2644 this.#dialog = dialog;
2645 this.#optionDescription = optionDescription;
2646 this.#optionDecorative = optionDecorative;
2647 this.#textarea = textarea;
2648 this.#cancelButton = cancelButton;
2649 this.#saveButton = saveButton;
2650 this.#overlayManager = overlayManager;
2651 this.#eventBus = eventBus;
2652 this.#container = container;
2653 const onUpdateUIState = this.#updateUIState.bind(this);
2654 dialog.addEventListener("close", this.#close.bind(this));
2655 dialog.addEventListener("contextmenu", event => {
2656 if (event.target !== this.#textarea) {
2657 event.preventDefault();
2660 cancelButton.addEventListener("click", this.#finish.bind(this));
2661 saveButton.addEventListener("click", this.#save.bind(this));
2662 optionDescription.addEventListener("change", onUpdateUIState);
2663 optionDecorative.addEventListener("change", onUpdateUIState);
2664 this.#overlayManager.register(dialog);
2666 #createSVGElement() {
2667 if (this.#svgElement) {
2670 const svgFactory = new DOMSVGFactory();
2671 const svg = this.#svgElement = svgFactory.createElement("svg");
2672 svg.setAttribute("width", "0");
2673 svg.setAttribute("height", "0");
2674 const defs = svgFactory.createElement("defs");
2676 const mask = svgFactory.createElement("mask");
2678 mask.setAttribute("id", "alttext-manager-mask");
2679 mask.setAttribute("maskContentUnits", "objectBoundingBox");
2680 let rect = svgFactory.createElement("rect");
2682 rect.setAttribute("fill", "white");
2683 rect.setAttribute("width", "1");
2684 rect.setAttribute("height", "1");
2685 rect.setAttribute("x", "0");
2686 rect.setAttribute("y", "0");
2687 rect = this.#rectElement = svgFactory.createElement("rect");
2689 rect.setAttribute("fill", "black");
2690 this.#dialog.append(svg);
2692 async editAltText(uiManager, editor) {
2693 if (this.#currentEditor || !editor) {
2696 this.#createSVGElement();
2697 this.#hasUsedPointer = false;
2698 this.#clickAC = new AbortController();
2700 signal: this.#clickAC.signal
2702 onClick = this.#onClick.bind(this);
2703 for (const element of [this.#optionDescription, this.#optionDecorative, this.#textarea, this.#saveButton, this.#cancelButton]) {
2704 element.addEventListener("click", onClick, clickOpts);
2709 } = editor.altTextData;
2710 if (decorative === true) {
2711 this.#optionDecorative.checked = true;
2712 this.#optionDescription.checked = false;
2714 this.#optionDecorative.checked = false;
2715 this.#optionDescription.checked = true;
2717 this.#previousAltText = this.#textarea.value = altText?.trim() || "";
2718 this.#updateUIState();
2719 this.#currentEditor = editor;
2720 this.#uiManager = uiManager;
2721 this.#uiManager.removeEditListeners();
2722 this.#resizeAC = new AbortController();
2723 this.#eventBus._on("resize", this.#setPosition.bind(this), {
2724 signal: this.#resizeAC.signal
2727 await this.#overlayManager.open(this.#dialog);
2728 this.#setPosition();
2735 if (!this.#currentEditor) {
2738 const dialog = this.#dialog;
2747 } = this.#container.getBoundingClientRect();
2749 innerWidth: windowW,
2750 innerHeight: windowH
2755 } = dialog.getBoundingClientRect();
2761 } = this.#currentEditor.getClientDimensions();
2763 const isLTR = this.#uiManager.direction === "ltr";
2764 const xs = Math.max(x, containerX);
2765 const xe = Math.min(x + width, containerX + containerW);
2766 const ys = Math.max(y, containerY);
2767 const ye = Math.min(y + height, containerY + containerH);
2768 this.#rectElement.setAttribute("width", `${(xe - xs) / windowW}`);
2769 this.#rectElement.setAttribute("height", `${(ye - ys) / windowH}`);
2770 this.#rectElement.setAttribute("x", `${xs / windowW}`);
2771 this.#rectElement.setAttribute("y", `${ys / windowH}`);
2773 let top = Math.max(y, 0);
2774 top += Math.min(windowH - (top + dialogH), 0);
2776 if (x + width + MARGIN + dialogW < windowW) {
2777 left = x + width + MARGIN;
2778 } else if (x > dialogW + MARGIN) {
2779 left = x - dialogW - MARGIN;
2781 } else if (x > dialogW + MARGIN) {
2782 left = x - dialogW - MARGIN;
2783 } else if (x + width + MARGIN + dialogW < windowW) {
2784 left = x + width + MARGIN;
2786 if (left === null) {
2788 left = Math.max(x, 0);
2789 left += Math.min(windowW - (left + dialogW), 0);
2790 if (y > dialogH + MARGIN) {
2791 top = y - dialogH - MARGIN;
2792 } else if (y + height + MARGIN + dialogH < windowH) {
2793 top = y + height + MARGIN;
2797 dialog.classList.add("positioned");
2799 style.left = `${left}px`;
2801 style.right = `${windowW - left - dialogW}px`;
2803 style.top = `${top}px`;
2805 dialog.classList.remove("positioned");
2811 if (this.#overlayManager.active === this.#dialog) {
2812 this.#overlayManager.close(this.#dialog);
2816 this.#currentEditor._reportTelemetry(this.#telemetryData || {
2817 action: "alt_text_cancel",
2818 alt_text_keyboard: !this.#hasUsedPointer
2820 this.#telemetryData = null;
2821 this.#removeOnClickListeners();
2822 this.#uiManager?.addEditListeners();
2823 this.#resizeAC?.abort();
2824 this.#resizeAC = null;
2825 this.#currentEditor.altTextFinish();
2826 this.#currentEditor = null;
2827 this.#uiManager = null;
2830 this.#textarea.disabled = this.#optionDecorative.checked;
2833 const altText = this.#textarea.value.trim();
2834 const decorative = this.#optionDecorative.checked;
2835 this.#currentEditor.altTextData = {
2839 this.#telemetryData = {
2840 action: "alt_text_save",
2841 alt_text_description: !!altText,
2842 alt_text_edit: !!this.#previousAltText && this.#previousAltText !== altText,
2843 alt_text_decorative: decorative,
2844 alt_text_keyboard: !this.#hasUsedPointer
2849 if (evt.detail === 0) {
2852 this.#hasUsedPointer = true;
2853 this.#removeOnClickListeners();
2855 #removeOnClickListeners() {
2856 this.#clickAC?.abort();
2857 this.#clickAC = null;
2860 this.#uiManager = null;
2862 this.#svgElement?.remove();
2863 this.#svgElement = this.#rectElement = null;
2867 ;// ./web/annotation_editor_params.js
2869 class AnnotationEditorParams {
2870 constructor(options, eventBus) {
2871 this.eventBus = eventBus;
2872 this.#bindListeners(options);
2875 editorFreeTextFontSize,
2876 editorFreeTextColor,
2880 editorStampAddImage,
2881 editorFreeHighlightThickness,
2882 editorHighlightShowAll,
2883 editorSignatureAddSignature
2888 const dispatchEvent = (typeStr, value) => {
2889 eventBus.dispatch("switchannotationeditorparams", {
2891 type: AnnotationEditorParamsType[typeStr],
2895 editorFreeTextFontSize.addEventListener("input", function () {
2896 dispatchEvent("FREETEXT_SIZE", this.valueAsNumber);
2898 editorFreeTextColor.addEventListener("input", function () {
2899 dispatchEvent("FREETEXT_COLOR", this.value);
2901 editorInkColor.addEventListener("input", function () {
2902 dispatchEvent("INK_COLOR", this.value);
2904 editorInkThickness.addEventListener("input", function () {
2905 dispatchEvent("INK_THICKNESS", this.valueAsNumber);
2907 editorInkOpacity.addEventListener("input", function () {
2908 dispatchEvent("INK_OPACITY", this.valueAsNumber);
2910 editorStampAddImage.addEventListener("click", () => {
2911 eventBus.dispatch("reporttelemetry", {
2916 action: "pdfjs.image.add_image_click"
2920 dispatchEvent("CREATE");
2922 editorFreeHighlightThickness.addEventListener("input", function () {
2923 dispatchEvent("HIGHLIGHT_THICKNESS", this.valueAsNumber);
2925 editorHighlightShowAll.addEventListener("click", function () {
2926 const checked = this.getAttribute("aria-pressed") === "true";
2927 this.setAttribute("aria-pressed", !checked);
2928 dispatchEvent("HIGHLIGHT_SHOW_ALL", !checked);
2930 editorSignatureAddSignature.addEventListener("click", () => {
2931 dispatchEvent("CREATE");
2933 eventBus._on("annotationeditorparamschanged", evt => {
2934 for (const [type, value] of evt.details) {
2936 case AnnotationEditorParamsType.FREETEXT_SIZE:
2937 editorFreeTextFontSize.value = value;
2939 case AnnotationEditorParamsType.FREETEXT_COLOR:
2940 editorFreeTextColor.value = value;
2942 case AnnotationEditorParamsType.INK_COLOR:
2943 editorInkColor.value = value;
2945 case AnnotationEditorParamsType.INK_THICKNESS:
2946 editorInkThickness.value = value;
2948 case AnnotationEditorParamsType.INK_OPACITY:
2949 editorInkOpacity.value = value;
2951 case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR:
2952 eventBus.dispatch("mainhighlightcolorpickerupdatecolor", {
2957 case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS:
2958 editorFreeHighlightThickness.value = value;
2960 case AnnotationEditorParamsType.HIGHLIGHT_FREE:
2961 editorFreeHighlightThickness.disabled = !value;
2963 case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL:
2964 editorHighlightShowAll.setAttribute("aria-pressed", value);
2972 ;// ./web/caret_browsing.js
2973 const PRECISION = 1e-1;
2974 class CaretBrowsingMode {
2978 constructor(abortSignal, mainContainer, viewerContainer, toolbarContainer) {
2979 this.#mainContainer = mainContainer;
2980 this.#viewerContainer = viewerContainer;
2981 if (!toolbarContainer) {
2984 this.#toolBarHeight = toolbarContainer.getBoundingClientRect().height;
2985 const toolbarObserver = new ResizeObserver(entries => {
2986 for (const entry of entries) {
2987 if (entry.target === toolbarContainer) {
2988 this.#toolBarHeight = Math.floor(entry.borderBoxSize[0].blockSize);
2993 toolbarObserver.observe(toolbarContainer);
2994 abortSignal.addEventListener("abort", () => toolbarObserver.disconnect(), {
2998 #isOnSameLine(rect1, rect2) {
2999 const top1 = rect1.y;
3000 const bot1 = rect1.bottom;
3001 const mid1 = rect1.y + rect1.height / 2;
3002 const top2 = rect2.y;
3003 const bot2 = rect2.bottom;
3004 const mid2 = rect2.y + rect2.height / 2;
3005 return top1 <= mid2 && mid2 <= bot1 || top2 <= mid1 && mid1 <= bot2;
3007 #isUnderOver(rect, x, y, isUp) {
3008 const midY = rect.y + rect.height / 2;
3009 return (isUp ? y >= midY : y <= midY) && rect.x - PRECISION <= x && x <= rect.right + PRECISION;
3012 return rect.top >= this.#toolBarHeight && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
3014 #getCaretPosition(selection, isUp) {
3019 const range = document.createRange();
3020 range.setStart(focusNode, focusOffset);
3021 range.setEnd(focusNode, focusOffset);
3022 const rect = range.getBoundingClientRect();
3023 return [rect.x, isUp ? rect.top : rect.bottom];
3025 static #caretPositionFromPoint(x, y) {
3026 return document.caretPositionFromPoint(x, y);
3028 #setCaretPositionHelper(selection, caretX, select, element, rect) {
3029 rect ||= element.getBoundingClientRect();
3030 if (caretX <= rect.x + PRECISION) {
3032 selection.extend(element.firstChild, 0);
3034 selection.setPosition(element.firstChild, 0);
3038 if (rect.right - PRECISION <= caretX) {
3043 selection.extend(lastChild, lastChild.length);
3045 selection.setPosition(lastChild, lastChild.length);
3049 const midY = rect.y + rect.height / 2;
3050 let caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY);
3051 let parentElement = caretPosition.offsetNode?.parentElement;
3052 if (parentElement && parentElement !== element) {
3053 const elementsAtPoint = document.elementsFromPoint(caretX, midY);
3054 const savedVisibilities = [];
3055 for (const el of elementsAtPoint) {
3056 if (el === element) {
3062 savedVisibilities.push([el, style.visibility]);
3063 style.visibility = "hidden";
3065 caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY);
3066 parentElement = caretPosition.offsetNode?.parentElement;
3067 for (const [el, visibility] of savedVisibilities) {
3068 el.style.visibility = visibility;
3071 if (parentElement !== element) {
3073 selection.extend(element.firstChild, 0);
3075 selection.setPosition(element.firstChild, 0);
3080 selection.extend(caretPosition.offsetNode, caretPosition.offset);
3082 selection.setPosition(caretPosition.offsetNode, caretPosition.offset);
3085 #setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX) {
3086 if (this.#isVisible(newLineElementRect)) {
3087 this.#setCaretPositionHelper(selection, caretX, select, newLineElement, newLineElementRect);
3090 this.#mainContainer.addEventListener("scrollend", this.#setCaretPositionHelper.bind(this, selection, caretX, select, newLineElement, null), {
3093 newLineElement.scrollIntoView();
3095 #getNodeOnNextPage(textLayer, isUp) {
3097 const page = textLayer.closest(".page");
3098 const pageNumber = parseInt(page.getAttribute("data-page-number"));
3099 const nextPage = isUp ? pageNumber - 1 : pageNumber + 1;
3100 textLayer = this.#viewerContainer.querySelector(`.page[data-page-number="${nextPage}"] .textLayer`);
3104 const walker = document.createTreeWalker(textLayer, NodeFilter.SHOW_TEXT);
3105 const node = isUp ? walker.lastChild() : walker.firstChild();
3111 moveCaret(isUp, select) {
3112 const selection = document.getSelection();
3113 if (selection.rangeCount === 0) {
3119 const focusElement = focusNode.nodeType !== Node.ELEMENT_NODE ? focusNode.parentElement : focusNode;
3120 const root = focusElement.closest(".textLayer");
3124 const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
3125 walker.currentNode = focusNode;
3126 const focusRect = focusElement.getBoundingClientRect();
3127 let newLineElement = null;
3128 const nodeIterator = (isUp ? walker.previousSibling : walker.nextSibling).bind(walker);
3129 while (nodeIterator()) {
3130 const element = walker.currentNode.parentElement;
3131 if (!this.#isOnSameLine(focusRect, element.getBoundingClientRect())) {
3132 newLineElement = element;
3136 if (!newLineElement) {
3137 const node = this.#getNodeOnNextPage(root, isUp);
3142 const lastNode = (isUp ? walker.firstChild() : walker.lastChild()) || focusNode;
3143 selection.extend(lastNode, isUp ? 0 : lastNode.length);
3144 const range = document.createRange();
3145 range.setStart(node, isUp ? node.length : 0);
3146 range.setEnd(node, isUp ? node.length : 0);
3147 selection.addRange(range);
3150 const [caretX] = this.#getCaretPosition(selection, isUp);
3154 this.#setCaretPosition(select, selection, parentElement, parentElement.getBoundingClientRect(), caretX);
3157 const [caretX, caretY] = this.#getCaretPosition(selection, isUp);
3158 const newLineElementRect = newLineElement.getBoundingClientRect();
3159 if (this.#isUnderOver(newLineElementRect, caretX, caretY, isUp)) {
3160 this.#setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX);
3163 while (nodeIterator()) {
3164 const element = walker.currentNode.parentElement;
3165 const elementRect = element.getBoundingClientRect();
3166 if (!this.#isOnSameLine(newLineElementRect, elementRect)) {
3169 if (this.#isUnderOver(elementRect, caretX, caretY, isUp)) {
3170 this.#setCaretPosition(select, selection, element, elementRect, caretX);
3174 this.#setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX);
3178 ;// ./web/editor_undo_bar.js
3180 class EditorUndoBar {
3181 #closeButton = null;
3184 #focusTimeout = null;
3185 #initController = null;
3188 #showController = null;
3190 static #l10nMessages = Object.freeze({
3191 highlight: "pdfjs-editor-undo-bar-message-highlight",
3192 freetext: "pdfjs-editor-undo-bar-message-freetext",
3193 stamp: "pdfjs-editor-undo-bar-message-stamp",
3194 ink: "pdfjs-editor-undo-bar-message-ink",
3195 signature: "pdfjs-editor-undo-bar-message-signature",
3196 _multiple: "pdfjs-editor-undo-bar-message-multiple"
3204 this.#container = container;
3205 this.#message = message;
3206 this.#undoButton = undoButton;
3207 this.#closeButton = closeButton;
3208 this.#eventBus = eventBus;
3211 this.#initController?.abort();
3212 this.#initController = null;
3215 show(undoAction, messageData) {
3216 if (!this.#initController) {
3217 this.#initController = new AbortController();
3219 signal: this.#initController.signal
3221 const boundHide = this.hide.bind(this);
3222 this.#container.addEventListener("contextmenu", noContextMenu, opts);
3223 this.#closeButton.addEventListener("click", boundHide, opts);
3224 this.#eventBus._on("beforeprint", boundHide, opts);
3225 this.#eventBus._on("download", boundHide, opts);
3228 if (typeof messageData === "string") {
3229 this.#message.setAttribute("data-l10n-id", EditorUndoBar.#l10nMessages[messageData]);
3231 this.#message.setAttribute("data-l10n-id", EditorUndoBar.#l10nMessages._multiple);
3232 this.#message.setAttribute("data-l10n-args", JSON.stringify({
3237 this.#container.hidden = false;
3238 this.#showController = new AbortController();
3239 this.#undoButton.addEventListener("click", () => {
3243 signal: this.#showController.signal
3245 this.#focusTimeout = setTimeout(() => {
3246 this.#container.focus();
3247 this.#focusTimeout = null;
3254 this.isOpen = false;
3255 this.#container.hidden = true;
3256 this.#showController?.abort();
3257 this.#showController = null;
3258 if (this.#focusTimeout) {
3259 clearTimeout(this.#focusTimeout);
3260 this.#focusTimeout = null;
3265 ;// ./web/overlay_manager.js
3266 class OverlayManager {
3267 #overlays = new WeakMap();
3270 return this.#active;
3272 async register(dialog, canForceClose = false) {
3273 if (typeof dialog !== "object") {
3274 throw new Error("Not enough parameters.");
3275 } else if (this.#overlays.has(dialog)) {
3276 throw new Error("The overlay is already registered.");
3278 this.#overlays.set(dialog, {
3281 dialog.addEventListener("cancel", ({
3284 if (this.#active === target) {
3285 this.#active = null;
3289 async open(dialog) {
3290 if (!this.#overlays.has(dialog)) {
3291 throw new Error("The overlay does not exist.");
3292 } else if (this.#active) {
3293 if (this.#active === dialog) {
3294 throw new Error("The overlay is already active.");
3295 } else if (this.#overlays.get(dialog).canForceClose) {
3298 throw new Error("Another overlay is currently active.");
3301 this.#active = dialog;
3304 async close(dialog = this.#active) {
3305 if (!this.#overlays.has(dialog)) {
3306 throw new Error("The overlay does not exist.");
3307 } else if (!this.#active) {
3308 throw new Error("The overlay is currently not active.");
3309 } else if (this.#active !== dialog) {
3310 throw new Error("Another overlay is currently active.");
3313 this.#active = null;
3317 ;// ./web/password_prompt.js
3319 class PasswordPrompt {
3320 #activeCapability = null;
3321 #updateCallback = null;
3323 constructor(options, overlayManager, isViewerEmbedded = false) {
3324 this.dialog = options.dialog;
3325 this.label = options.label;
3326 this.input = options.input;
3327 this.submitButton = options.submitButton;
3328 this.cancelButton = options.cancelButton;
3329 this.overlayManager = overlayManager;
3330 this._isViewerEmbedded = isViewerEmbedded;
3331 this.submitButton.addEventListener("click", this.#verify.bind(this));
3332 this.cancelButton.addEventListener("click", this.close.bind(this));
3333 this.input.addEventListener("keydown", e => {
3334 if (e.keyCode === 13) {
3338 this.overlayManager.register(this.dialog, true);
3339 this.dialog.addEventListener("close", this.#cancel.bind(this));
3342 await this.#activeCapability?.promise;
3343 this.#activeCapability = Promise.withResolvers();
3345 await this.overlayManager.open(this.dialog);
3347 this.#activeCapability.resolve();
3350 const passwordIncorrect = this.#reason === PasswordResponses.INCORRECT_PASSWORD;
3351 if (!this._isViewerEmbedded || passwordIncorrect) {
3354 this.label.setAttribute("data-l10n-id", passwordIncorrect ? "pdfjs-password-invalid" : "pdfjs-password-label");
3357 if (this.overlayManager.active === this.dialog) {
3358 this.overlayManager.close(this.dialog);
3362 const password = this.input.value;
3363 if (password?.length > 0) {
3364 this.#invokeCallback(password);
3368 this.#invokeCallback(new Error("PasswordPrompt cancelled."));
3369 this.#activeCapability.resolve();
3371 #invokeCallback(password) {
3372 if (!this.#updateCallback) {
3376 this.input.value = "";
3377 this.#updateCallback(password);
3378 this.#updateCallback = null;
3380 async setUpdateCallback(updateCallback, reason) {
3381 if (this.#activeCapability) {
3382 await this.#activeCapability.promise;
3384 this.#updateCallback = updateCallback;
3385 this.#reason = reason;
3389 ;// ./web/base_tree_viewer.js
3391 const TREEITEM_OFFSET_TOP = -100;
3392 const TREEITEM_SELECTED_CLASS = "selected";
3393 class BaseTreeViewer {
3394 constructor(options) {
3395 this.container = options.container;
3396 this.eventBus = options.eventBus;
3397 this._l10n = options.l10n;
3401 this._pdfDocument = null;
3402 this._lastToggleIsShow = true;
3403 this._currentTreeItem = null;
3404 this.container.textContent = "";
3405 this.container.classList.remove("treeWithDeepNesting");
3407 _dispatchEvent(count) {
3408 throw new Error("Not implemented: _dispatchEvent");
3410 _bindLink(element, params) {
3411 throw new Error("Not implemented: _bindLink");
3413 _normalizeTextContent(str) {
3414 return removeNullCharacters(str, true) || "\u2013";
3416 _addToggleButton(div, hidden = false) {
3417 const toggler = document.createElement("div");
3418 toggler.className = "treeItemToggler";
3420 toggler.classList.add("treeItemsHidden");
3422 toggler.onclick = evt => {
3423 evt.stopPropagation();
3424 toggler.classList.toggle("treeItemsHidden");
3426 const shouldShowAll = !toggler.classList.contains("treeItemsHidden");
3427 this._toggleTreeItem(div, shouldShowAll);
3430 div.prepend(toggler);
3432 _toggleTreeItem(root, show = false) {
3434 this._lastToggleIsShow = show;
3435 for (const toggler of root.querySelectorAll(".treeItemToggler")) {
3436 toggler.classList.toggle("treeItemsHidden", !show);
3438 this._l10n.resume();
3440 _toggleAllTreeItems() {
3441 this._toggleTreeItem(this.container, !this._lastToggleIsShow);
3443 _finishRendering(fragment, count, hasAnyNesting = false) {
3444 if (hasAnyNesting) {
3445 this.container.classList.add("treeWithDeepNesting");
3446 this._lastToggleIsShow = !fragment.querySelector(".treeItemsHidden");
3449 this.container.append(fragment);
3450 this._l10n.resume();
3451 this._dispatchEvent(count);
3454 throw new Error("Not implemented: render");
3456 _updateCurrentTreeItem(treeItem = null) {
3457 if (this._currentTreeItem) {
3458 this._currentTreeItem.classList.remove(TREEITEM_SELECTED_CLASS);
3459 this._currentTreeItem = null;
3462 treeItem.classList.add(TREEITEM_SELECTED_CLASS);
3463 this._currentTreeItem = treeItem;
3466 _scrollToCurrentTreeItem(treeItem) {
3471 let currentNode = treeItem.parentNode;
3472 while (currentNode && currentNode !== this.container) {
3473 if (currentNode.classList.contains("treeItem")) {
3474 const toggler = currentNode.firstElementChild;
3475 toggler?.classList.remove("treeItemsHidden");
3477 currentNode = currentNode.parentNode;
3479 this._l10n.resume();
3480 this._updateCurrentTreeItem(treeItem);
3481 this.container.scrollTo(treeItem.offsetLeft, treeItem.offsetTop + TREEITEM_OFFSET_TOP);
3485 ;// ./web/pdf_attachment_viewer.js
3488 class PDFAttachmentViewer extends BaseTreeViewer {
3489 constructor(options) {
3491 this.downloadManager = options.downloadManager;
3492 this.eventBus._on("fileattachmentannotation", this.#appendAttachment.bind(this));
3494 reset(keepRenderedCapability = false) {
3496 this._attachments = null;
3497 if (!keepRenderedCapability) {
3498 this._renderedCapability = Promise.withResolvers();
3500 this._pendingDispatchEvent = false;
3502 async _dispatchEvent(attachmentsCount) {
3503 this._renderedCapability.resolve();
3504 if (attachmentsCount === 0 && !this._pendingDispatchEvent) {
3505 this._pendingDispatchEvent = true;
3506 await waitOnEventOrTimeout({
3507 target: this.eventBus,
3508 name: "annotationlayerrendered",
3511 if (!this._pendingDispatchEvent) {
3515 this._pendingDispatchEvent = false;
3516 this.eventBus.dispatch("attachmentsloaded", {
3521 _bindLink(element, {
3527 element.title = description;
3529 element.onclick = () => {
3530 this.downloadManager.openOrDownloadData(content, filename);
3536 keepRenderedCapability = false
3538 if (this._attachments) {
3539 this.reset(keepRenderedCapability);
3541 this._attachments = attachments || null;
3543 this._dispatchEvent(0);
3546 const fragment = document.createDocumentFragment();
3547 let attachmentsCount = 0;
3548 for (const name in attachments) {
3549 const item = attachments[name];
3550 const div = document.createElement("div");
3551 div.className = "treeItem";
3552 const element = document.createElement("a");
3553 this._bindLink(element, item);
3554 element.textContent = this._normalizeTextContent(item.filename);
3555 div.append(element);
3556 fragment.append(div);
3559 this._finishRendering(fragment, attachmentsCount);
3561 #appendAttachment(item) {
3562 const renderedPromise = this._renderedCapability.promise;
3563 renderedPromise.then(() => {
3564 if (renderedPromise !== this._renderedCapability.promise) {
3567 const attachments = this._attachments || Object.create(null);
3568 for (const name in attachments) {
3569 if (item.filename === name) {
3573 attachments[item.filename] = item;
3576 keepRenderedCapability: true
3582 ;// ./web/grab_to_pan.js
3584 const CSS_CLASS_GRAB = "grab-to-pan-grab";
3587 #mouseDownAC = null;
3592 this.element = element;
3593 this.document = element.ownerDocument;
3594 const overlay = this.overlay = document.createElement("div");
3595 overlay.className = "grab-to-pan-grabbing";
3598 if (!this.#activateAC) {
3599 this.#activateAC = new AbortController();
3600 this.element.addEventListener("mousedown", this.#onMouseDown.bind(this), {
3602 signal: this.#activateAC.signal
3604 this.element.classList.add(CSS_CLASS_GRAB);
3608 if (this.#activateAC) {
3609 this.#activateAC.abort();
3610 this.#activateAC = null;
3612 this.element.classList.remove(CSS_CLASS_GRAB);
3616 if (this.#activateAC) {
3622 ignoreTarget(node) {
3623 return node.matches("a[href], a[href] *, input, textarea, button, button *, select, option");
3625 #onMouseDown(event) {
3626 if (event.button !== 0 || this.ignoreTarget(event.target)) {
3629 if (event.originalTarget) {
3631 event.originalTarget.tagName;
3636 this.scrollLeftStart = this.element.scrollLeft;
3637 this.scrollTopStart = this.element.scrollTop;
3638 this.clientXStart = event.clientX;
3639 this.clientYStart = event.clientY;
3640 this.#mouseDownAC = new AbortController();
3641 const boundEndPan = this.#endPan.bind(this),
3644 signal: this.#mouseDownAC.signal
3646 this.document.addEventListener("mousemove", this.#onMouseMove.bind(this), mouseOpts);
3647 this.document.addEventListener("mouseup", boundEndPan, mouseOpts);
3648 this.#scrollAC = new AbortController();
3649 this.element.addEventListener("scroll", boundEndPan, {
3651 signal: this.#scrollAC.signal
3654 const focusedElement = document.activeElement;
3655 if (focusedElement && !focusedElement.contains(event.target)) {
3656 focusedElement.blur();
3659 #onMouseMove(event) {
3660 this.#scrollAC?.abort();
3661 this.#scrollAC = null;
3662 if (!(event.buttons & 1)) {
3666 const xDiff = event.clientX - this.clientXStart;
3667 const yDiff = event.clientY - this.clientYStart;
3668 this.element.scrollTo({
3669 top: this.scrollTopStart - yDiff,
3670 left: this.scrollLeftStart - xDiff,
3673 if (!this.overlay.parentNode) {
3674 document.body.append(this.overlay);
3678 this.#mouseDownAC?.abort();
3679 this.#mouseDownAC = null;
3680 this.#scrollAC?.abort();
3681 this.#scrollAC = null;
3682 this.overlay.remove();
3686 ;// ./web/pdf_cursor_tools.js
3690 class PDFCursorTools {
3691 #active = CursorTool.SELECT;
3696 cursorToolOnLoad = CursorTool.SELECT
3698 this.container = container;
3699 this.eventBus = eventBus;
3700 this.#addEventListeners();
3701 Promise.resolve().then(() => {
3702 this.switchTool(cursorToolOnLoad);
3706 return this.#active;
3709 if (this.#prevActive !== null) {
3712 this.#switchTool(tool);
3714 #switchTool(tool, disabled = false) {
3715 if (tool === this.#active) {
3716 if (this.#prevActive !== null) {
3717 this.eventBus.dispatch("cursortoolchanged", {
3725 const disableActiveTool = () => {
3726 switch (this.#active) {
3727 case CursorTool.SELECT:
3729 case CursorTool.HAND:
3730 this._handTool.deactivate();
3732 case CursorTool.ZOOM:
3736 case CursorTool.SELECT:
3737 disableActiveTool();
3739 case CursorTool.HAND:
3740 disableActiveTool();
3741 this._handTool.activate();
3743 case CursorTool.ZOOM:
3745 console.error(`switchTool: "${tool}" is an unsupported value.`);
3748 this.#active = tool;
3749 this.eventBus.dispatch("cursortoolchanged", {
3755 #addEventListeners() {
3756 this.eventBus._on("switchcursortool", evt => {
3758 this.switchTool(evt.tool);
3759 } else if (this.#prevActive !== null) {
3760 annotationEditorMode = AnnotationEditorType.NONE;
3761 presentationModeState = PresentationModeState.NORMAL;
3765 let annotationEditorMode = AnnotationEditorType.NONE,
3766 presentationModeState = PresentationModeState.NORMAL;
3767 const disableActive = () => {
3768 this.#prevActive ??= this.#active;
3769 this.#switchTool(CursorTool.SELECT, true);
3771 const enableActive = () => {
3772 if (this.#prevActive !== null && annotationEditorMode === AnnotationEditorType.NONE && presentationModeState === PresentationModeState.NORMAL) {
3773 this.#switchTool(this.#prevActive);
3774 this.#prevActive = null;
3777 this.eventBus._on("annotationeditormodechanged", ({
3780 annotationEditorMode = mode;
3781 if (mode === AnnotationEditorType.NONE) {
3787 this.eventBus._on("presentationmodechanged", ({
3790 presentationModeState = state;
3791 if (state === PresentationModeState.NORMAL) {
3793 } else if (state === PresentationModeState.FULLSCREEN) {
3799 return shadow(this, "_handTool", new GrabToPan({
3800 element: this.container
3805 ;// ./web/pdf_document_properties.js
3808 const NON_METRIC_LOCALES = ["en-us", "en-lr", "my"];
3809 const US_PAGE_NAMES = {
3810 "8.5x11": "pdfjs-document-properties-page-size-name-letter",
3811 "8.5x14": "pdfjs-document-properties-page-size-name-legal"
3813 const METRIC_PAGE_NAMES = {
3814 "297x420": "pdfjs-document-properties-page-size-name-a-three",
3815 "210x297": "pdfjs-document-properties-page-size-name-a-four"
3817 function getPageName(size, isPortrait, pageNames) {
3818 const width = isPortrait ? size.width : size.height;
3819 const height = isPortrait ? size.height : size.width;
3820 return pageNames[`${width}x${height}`];
3822 class PDFDocumentProperties {
3828 }, overlayManager, eventBus, l10n, fileNameLookup) {
3829 this.dialog = dialog;
3830 this.fields = fields;
3831 this.overlayManager = overlayManager;
3833 this._fileNameLookup = fileNameLookup;
3835 closeButton.addEventListener("click", this.close.bind(this));
3836 this.overlayManager.register(this.dialog);
3837 eventBus._on("pagechanging", evt => {
3838 this._currentPageNumber = evt.pageNumber;
3840 eventBus._on("rotationchanging", evt => {
3841 this._pagesRotation = evt.pagesRotation;
3845 await Promise.all([this.overlayManager.open(this.dialog), this._dataAvailableCapability.promise]);
3846 const currentPageNumber = this._currentPageNumber;
3847 const pagesRotation = this._pagesRotation;
3848 if (this.#fieldData && currentPageNumber === this.#fieldData._currentPageNumber && pagesRotation === this.#fieldData._pagesRotation) {
3855 }, pdfPage] = await Promise.all([this.pdfDocument.getMetadata(), this.pdfDocument.getPage(currentPageNumber)]);
3856 const [fileName, fileSize, creationDate, modificationDate, pageSize, isLinearized] = await Promise.all([this._fileNameLookup(), this.#parseFileSize(contentLength), this.#parseDate(info.CreationDate), this.#parseDate(info.ModDate), this.#parsePageSize(getPageSizeInches(pdfPage), pagesRotation), this.#parseLinearization(info.IsLinearized)]);
3857 this.#fieldData = Object.freeze({
3861 author: info.Author,
3862 subject: info.Subject,
3863 keywords: info.Keywords,
3866 creator: info.Creator,
3867 producer: info.Producer,
3868 version: info.PDFFormatVersion,
3869 pageCount: this.pdfDocument.numPages,
3871 linearized: isLinearized,
3872 _currentPageNumber: currentPageNumber,
3873 _pagesRotation: pagesRotation
3878 } = await this.pdfDocument.getDownloadInfo();
3879 if (contentLength === length) {
3882 const data = Object.assign(Object.create(null), this.#fieldData);
3883 data.fileSize = await this.#parseFileSize(length);
3884 this.#fieldData = Object.freeze(data);
3888 this.overlayManager.close(this.dialog);
3890 setDocument(pdfDocument) {
3891 if (this.pdfDocument) {
3898 this.pdfDocument = pdfDocument;
3899 this._dataAvailableCapability.resolve();
3902 this.pdfDocument = null;
3903 this.#fieldData = null;
3904 this._dataAvailableCapability = Promise.withResolvers();
3905 this._currentPageNumber = 1;
3906 this._pagesRotation = 0;
3909 if (this.#fieldData && this.overlayManager.active !== this.dialog) {
3912 for (const id in this.fields) {
3913 const content = this.#fieldData?.[id];
3914 this.fields[id].textContent = content || content === 0 ? content : "-";
3917 async #parseFileSize(b = 0) {
3918 const kb = b / 1024,
3920 return kb ? this.l10n.get(mb >= 1 ? "pdfjs-document-properties-size-mb" : "pdfjs-document-properties-size-kb", {
3926 async #parsePageSize(pageSizeInches, pagesRotation) {
3927 if (!pageSizeInches) {
3930 if (pagesRotation % 180 !== 0) {
3932 width: pageSizeInches.height,
3933 height: pageSizeInches.width
3936 const isPortrait = isPortraitOrientation(pageSizeInches),
3937 nonMetric = NON_METRIC_LOCALES.includes(this.l10n.getLanguage());
3939 width: Math.round(pageSizeInches.width * 100) / 100,
3940 height: Math.round(pageSizeInches.height * 100) / 100
3942 let sizeMillimeters = {
3943 width: Math.round(pageSizeInches.width * 25.4 * 10) / 10,
3944 height: Math.round(pageSizeInches.height * 25.4 * 10) / 10
3946 let nameId = getPageName(sizeInches, isPortrait, US_PAGE_NAMES) || getPageName(sizeMillimeters, isPortrait, METRIC_PAGE_NAMES);
3947 if (!nameId && !(Number.isInteger(sizeMillimeters.width) && Number.isInteger(sizeMillimeters.height))) {
3948 const exactMillimeters = {
3949 width: pageSizeInches.width * 25.4,
3950 height: pageSizeInches.height * 25.4
3952 const intMillimeters = {
3953 width: Math.round(sizeMillimeters.width),
3954 height: Math.round(sizeMillimeters.height)
3956 if (Math.abs(exactMillimeters.width - intMillimeters.width) < 0.1 && Math.abs(exactMillimeters.height - intMillimeters.height) < 0.1) {
3957 nameId = getPageName(intMillimeters, isPortrait, METRIC_PAGE_NAMES);
3960 width: Math.round(intMillimeters.width / 25.4 * 100) / 100,
3961 height: Math.round(intMillimeters.height / 25.4 * 100) / 100
3963 sizeMillimeters = intMillimeters;
3970 }, unit, name, orientation] = await Promise.all([nonMetric ? sizeInches : sizeMillimeters, this.l10n.get(nonMetric ? "pdfjs-document-properties-page-size-unit-inches" : "pdfjs-document-properties-page-size-unit-millimeters"), nameId && this.l10n.get(nameId), this.l10n.get(isPortrait ? "pdfjs-document-properties-page-size-orientation-portrait" : "pdfjs-document-properties-page-size-orientation-landscape")]);
3971 return this.l10n.get(name ? "pdfjs-document-properties-page-size-dimension-name-string" : "pdfjs-document-properties-page-size-dimension-string", {
3979 async #parseDate(inputDate) {
3980 const dateObj = PDFDateString.toDateObject(inputDate);
3981 return dateObj ? this.l10n.get("pdfjs-document-properties-date-time-string", {
3982 dateObj: dateObj.valueOf()
3985 #parseLinearization(isLinearized) {
3986 return this.l10n.get(isLinearized ? "pdfjs-document-properties-linearized-yes" : "pdfjs-document-properties-linearized-no");
3990 ;// ./web/pdf_find_utils.js
3991 const CharacterType = {
3998 HALFWIDTH_KATAKANA_LETTER: 6,
4001 function isAlphabeticalScript(charCode) {
4002 return charCode < 0x2e80;
4004 function isAscii(charCode) {
4005 return (charCode & 0xff80) === 0;
4007 function isAsciiAlpha(charCode) {
4008 return charCode >= 0x61 && charCode <= 0x7a || charCode >= 0x41 && charCode <= 0x5a;
4010 function isAsciiDigit(charCode) {
4011 return charCode >= 0x30 && charCode <= 0x39;
4013 function isAsciiSpace(charCode) {
4014 return charCode === 0x20 || charCode === 0x09 || charCode === 0x0d || charCode === 0x0a;
4016 function isHan(charCode) {
4017 return charCode >= 0x3400 && charCode <= 0x9fff || charCode >= 0xf900 && charCode <= 0xfaff;
4019 function isKatakana(charCode) {
4020 return charCode >= 0x30a0 && charCode <= 0x30ff;
4022 function isHiragana(charCode) {
4023 return charCode >= 0x3040 && charCode <= 0x309f;
4025 function isHalfwidthKatakana(charCode) {
4026 return charCode >= 0xff60 && charCode <= 0xff9f;
4028 function isThai(charCode) {
4029 return (charCode & 0xff80) === 0x0e00;
4031 function getCharacterType(charCode) {
4032 if (isAlphabeticalScript(charCode)) {
4033 if (isAscii(charCode)) {
4034 if (isAsciiSpace(charCode)) {
4035 return CharacterType.SPACE;
4036 } else if (isAsciiAlpha(charCode) || isAsciiDigit(charCode) || charCode === 0x5f) {
4037 return CharacterType.ALPHA_LETTER;
4039 return CharacterType.PUNCT;
4040 } else if (isThai(charCode)) {
4041 return CharacterType.THAI_LETTER;
4042 } else if (charCode === 0xa0) {
4043 return CharacterType.SPACE;
4045 return CharacterType.ALPHA_LETTER;
4047 if (isHan(charCode)) {
4048 return CharacterType.HAN_LETTER;
4049 } else if (isKatakana(charCode)) {
4050 return CharacterType.KATAKANA_LETTER;
4051 } else if (isHiragana(charCode)) {
4052 return CharacterType.HIRAGANA_LETTER;
4053 } else if (isHalfwidthKatakana(charCode)) {
4054 return CharacterType.HALFWIDTH_KATAKANA_LETTER;
4056 return CharacterType.ALPHA_LETTER;
4058 let NormalizeWithNFKC;
4059 function getNormalizeWithNFKC() {
4060 NormalizeWithNFKC ||= ` ¨ª¯²-µ¸-º¼-¾IJ-ijĿ-ŀʼnſDŽ-njDZ-dzʰ-ʸ˘-˝ˠ-ˤʹͺ;΄-΅·ϐ-ϖϰ-ϲϴ-ϵϹևٵ-ٸक़-य़ড়-ঢ়য়ਲ਼ਸ਼ਖ਼-ਜ਼ਫ਼ଡ଼-ଢ଼ำຳໜ-ໝ༌གྷཌྷདྷབྷཛྷཀྵჼᴬ-ᴮᴰ-ᴺᴼ-ᵍᵏ-ᵪᵸᶛ-ᶿẚ-ẛάέήίόύώΆ᾽-῁ΈΉ῍-῏ΐΊ῝-῟ΰΎ῭-`ΌΏ´-῾ - ‑‗․-… ″-‴‶-‷‼‾⁇-⁉⁗ ⁰-ⁱ⁴-₎ₐ-ₜ₨℀-℃℅-ℇ℉-ℓℕ-№ℙ-ℝ℠-™ℤΩℨK-ℭℯ-ℱℳ-ℹ℻-⅀ⅅ-ⅉ⅐-ⅿ↉∬-∭∯-∰〈-〉①-⓪⨌⩴-⩶⫝̸ⱼ-ⱽⵯ⺟⻳⼀-⿕ 〶〸-〺゛-゜ゟヿㄱ-ㆎ㆒-㆟㈀-㈞㈠-㉇㉐-㉾㊀-㏿ꚜ-ꚝꝰꟲ-ꟴꟸ-ꟹꭜ-ꭟꭩ豈-嗀塚晴凞-羽蘒諸逸-都飯-舘並-龎ff-stﬓ-ﬗיִײַ-זּטּ-לּמּנּ-סּףּ-פּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-﷼︐-︙︰-﹄﹇-﹒﹔-﹦﹨-﹫ﹰ-ﹲﹴﹶ-ﻼ!-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ¢-₩`;
4061 return NormalizeWithNFKC;
4064 ;// ./web/pdf_find_controller.js
4073 const FIND_TIMEOUT = 250;
4074 const MATCH_SCROLL_OFFSET_TOP = -50;
4075 const MATCH_SCROLL_OFFSET_LEFT = -400;
4076 const CHARACTERS_TO_NORMALIZE = {
4090 const DIACRITICS_EXCEPTION = new Set([0x3099, 0x309a, 0x094d, 0x09cd, 0x0a4d, 0x0acd, 0x0b4d, 0x0bcd, 0x0c4d, 0x0ccd, 0x0d3b, 0x0d3c, 0x0d4d, 0x0dca, 0x0e3a, 0x0eba, 0x0f84, 0x1039, 0x103a, 0x1714, 0x1734, 0x17d2, 0x1a60, 0x1b44, 0x1baa, 0x1bab, 0x1bf2, 0x1bf3, 0x2d7f, 0xa806, 0xa82c, 0xa8c4, 0xa953, 0xa9c0, 0xaaf6, 0xabed, 0x0c56, 0x0f71, 0x0f72, 0x0f7a, 0x0f7b, 0x0f7c, 0x0f7d, 0x0f80, 0x0f74]);
4091 let DIACRITICS_EXCEPTION_STR;
4092 const DIACRITICS_REG_EXP = /\p{M}+/gu;
4093 const SPECIAL_CHARS_REG_EXP = /([.*+?^${}()|[\]\\])|(\p{P})|(\s+)|(\p{M})|(\p{L})/gu;
4094 const NOT_DIACRITIC_FROM_END_REG_EXP = /([^\p{M}])\p{M}*$/u;
4095 const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*([^\p{M}])/u;
4096 const SYLLABLES_REG_EXP = /[\uAC00-\uD7AF\uFA6C\uFACF-\uFAD1\uFAD5-\uFAD7]+/g;
4097 const SYLLABLES_LENGTHS = new Map();
4098 const FIRST_CHAR_SYLLABLES_REG_EXP = "[\\u1100-\\u1112\\ud7a4-\\ud7af\\ud84a\\ud84c\\ud850\\ud854\\ud857\\ud85f]";
4099 const NFKC_CHARS_TO_NORMALIZE = new Map();
4100 let noSyllablesRegExp = null;
4101 let withSyllablesRegExp = null;
4102 function normalize(text) {
4103 const syllablePositions = [];
4105 while ((m = SYLLABLES_REG_EXP.exec(text)) !== null) {
4109 for (const char of m[0]) {
4110 let len = SYLLABLES_LENGTHS.get(char);
4112 len = char.normalize("NFD").length;
4113 SYLLABLES_LENGTHS.set(char, len);
4115 syllablePositions.push([len, index++]);
4118 const hasSyllables = syllablePositions.length > 0;
4119 let normalizationRegex;
4120 if (!hasSyllables && noSyllablesRegExp) {
4121 normalizationRegex = noSyllablesRegExp;
4122 } else if (hasSyllables && withSyllablesRegExp) {
4123 normalizationRegex = withSyllablesRegExp;
4125 const replace = Object.keys(CHARACTERS_TO_NORMALIZE).join("");
4126 const toNormalizeWithNFKC = getNormalizeWithNFKC();
4127 const CJK = "(?:\\p{Ideographic}|[\u3040-\u30FF])";
4128 const HKDiacritics = "(?:\u3099|\u309A)";
4129 const BrokenWord = `\\p{Ll}-\\n(?=\\p{Ll})|\\p{Lu}-\\n(?=\\p{L})`;
4130 const regexps = [`[${replace}]`, `[${toNormalizeWithNFKC}]`, `${HKDiacritics}\\n`, "\\p{M}+(?:-\\n)?", `${BrokenWord}`, "\\S-\\n", `${CJK}\\n`, "\\n", hasSyllables ? FIRST_CHAR_SYLLABLES_REG_EXP : "\\u0000"];
4131 normalizationRegex = new RegExp(regexps.map(r => `(${r})`).join("|"), "gum");
4133 withSyllablesRegExp = normalizationRegex;
4135 noSyllablesRegExp = normalizationRegex;
4138 const rawDiacriticsPositions = [];
4139 while ((m = DIACRITICS_REG_EXP.exec(text)) !== null) {
4140 rawDiacriticsPositions.push([m[0].length, m.index]);
4142 let normalized = text.normalize("NFD");
4143 const positions = [0, 0];
4144 let rawDiacriticsIndex = 0;
4145 let syllableIndex = 0;
4147 let shiftOrigin = 0;
4149 let hasDiacritics = false;
4150 normalized = normalized.replace(normalizationRegex, (match, p1, p2, p3, p4, p5, p6, p7, p8, p9, i) => {
4153 const replacement = CHARACTERS_TO_NORMALIZE[p1];
4154 const jj = replacement.length;
4155 for (let j = 1; j < jj; j++) {
4156 positions.push(i - shift + j, shift - j);
4162 let replacement = NFKC_CHARS_TO_NORMALIZE.get(p2);
4164 replacement = p2.normalize("NFKC");
4165 NFKC_CHARS_TO_NORMALIZE.set(p2, replacement);
4167 const jj = replacement.length;
4168 for (let j = 1; j < jj; j++) {
4169 positions.push(i - shift + j, shift - j);
4175 hasDiacritics = true;
4176 if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) {
4177 ++rawDiacriticsIndex;
4179 positions.push(i - 1 - shift + 1, shift - 1);
4183 positions.push(i - shift + 1, shift);
4186 return p3.charAt(0);
4189 const hasTrailingDashEOL = p4.endsWith("\n");
4190 const len = hasTrailingDashEOL ? p4.length - 2 : p4.length;
4191 hasDiacritics = true;
4193 if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) {
4194 jj -= rawDiacriticsPositions[rawDiacriticsIndex][0];
4195 ++rawDiacriticsIndex;
4197 for (let j = 1; j <= jj; j++) {
4198 positions.push(i - 1 - shift + j, shift - j);
4202 if (hasTrailingDashEOL) {
4204 positions.push(i - shift + 1, 1 + shift);
4208 return p4.slice(0, len);
4213 const len = p5.length - 2;
4214 positions.push(i - shift + len, 1 + shift);
4218 return p5.slice(0, -2);
4223 return p6.slice(0, -1);
4226 const len = p7.length - 1;
4227 positions.push(i - shift + len, shift);
4230 return p7.slice(0, -1);
4233 positions.push(i - shift + 1, shift - 1);
4239 if (i + eol === syllablePositions[syllableIndex]?.[1]) {
4240 const newCharLen = syllablePositions[syllableIndex][0] - 1;
4242 for (let j = 1; j <= newCharLen; j++) {
4243 positions.push(i - (shift - j), shift - j);
4245 shift -= newCharLen;
4246 shiftOrigin += newCharLen;
4250 positions.push(normalized.length, shift);
4251 const starts = new Uint32Array(positions.length >> 1);
4252 const shifts = new Int32Array(positions.length >> 1);
4253 for (let i = 0, ii = positions.length; i < ii; i += 2) {
4254 starts[i >> 1] = positions[i];
4255 shifts[i >> 1] = positions[i + 1];
4257 return [normalized, [starts, shifts], hasDiacritics];
4259 function getOriginalIndex(diffs, pos, len) {
4263 const [starts, shifts] = diffs;
4265 const end = pos + len - 1;
4266 let i = binarySearchFirstItem(starts, x => x >= start);
4267 if (starts[i] > start) {
4270 let j = binarySearchFirstItem(starts, x => x >= end, i);
4271 if (starts[j] > end) {
4274 const oldStart = start + shifts[i];
4275 const oldEnd = end + shifts[j];
4276 const oldLen = oldEnd + 1 - oldStart;
4277 return [oldStart, oldLen];
4279 class PDFFindController {
4281 #updateMatchesCountOnProgress = true;
4282 #visitedPagesCount = 0;
4286 updateMatchesCountOnProgress = true
4288 this._linkService = linkService;
4289 this._eventBus = eventBus;
4290 this.#updateMatchesCountOnProgress = updateMatchesCountOnProgress;
4291 this.onIsPageVisible = null;
4293 eventBus._on("find", this.#onFind.bind(this));
4294 eventBus._on("findbarclose", this.#onFindBarClose.bind(this));
4296 get highlightMatches() {
4297 return this._highlightMatches;
4300 return this._pageMatches;
4302 get pageMatchesLength() {
4303 return this._pageMatchesLength;
4306 return this._selected;
4311 setDocument(pdfDocument) {
4312 if (this._pdfDocument) {
4318 this._pdfDocument = pdfDocument;
4319 this._firstPageCapability.resolve();
4325 const pdfDocument = this._pdfDocument;
4329 if (this.#state === null || this.#shouldDirtyMatch(state)) {
4330 this._dirtyMatch = true;
4332 this.#state = state;
4333 if (type !== "highlightallchange") {
4334 this.#updateUIState(FindState.PENDING);
4336 this._firstPageCapability.promise.then(() => {
4337 if (!this._pdfDocument || pdfDocument && this._pdfDocument !== pdfDocument) {
4340 this.#extractText();
4341 const findbarClosed = !this._highlightMatches;
4342 const pendingTimeout = !!this._findTimeout;
4343 if (this._findTimeout) {
4344 clearTimeout(this._findTimeout);
4345 this._findTimeout = null;
4348 this._findTimeout = setTimeout(() => {
4350 this._findTimeout = null;
4352 } else if (this._dirtyMatch) {
4354 } else if (type === "again") {
4356 if (findbarClosed && this.#state.highlightAll) {
4357 this.#updateAllPages();
4359 } else if (type === "highlightallchange") {
4360 if (pendingTimeout) {
4363 this._highlightMatches = true;
4365 this.#updateAllPages();
4371 scrollMatchIntoView({
4377 if (!this._scrollMatches || !element) {
4379 } else if (matchIndex === -1 || matchIndex !== this._selected.matchIdx) {
4381 } else if (pageIndex === -1 || pageIndex !== this._selected.pageIdx) {
4384 this._scrollMatches = false;
4386 top: MATCH_SCROLL_OFFSET_TOP,
4387 left: selectedLeft + MATCH_SCROLL_OFFSET_LEFT
4389 scrollIntoView(element, spot, true);
4392 this._highlightMatches = false;
4393 this._scrollMatches = false;
4394 this._pdfDocument = null;
4395 this._pageMatches = [];
4396 this._pageMatchesLength = [];
4397 this.#visitedPagesCount = 0;
4408 this._extractTextPromises = [];
4409 this._pageContents = [];
4410 this._pageDiffs = [];
4411 this._hasDiacritics = [];
4412 this._matchesCountTotal = 0;
4413 this._pagesToSearch = null;
4414 this._pendingFindMatches = new Set();
4415 this._resumePageIdx = null;
4416 this._dirtyMatch = false;
4417 clearTimeout(this._findTimeout);
4418 this._findTimeout = null;
4419 this._firstPageCapability = Promise.withResolvers();
4425 if (typeof query === "string") {
4426 if (query !== this._rawQuery) {
4427 this._rawQuery = query;
4428 [this._normalizedQuery] = normalize(query);
4430 return this._normalizedQuery;
4432 return (query || []).filter(q => !!q).map(q => normalize(q)[0]);
4434 #shouldDirtyMatch(state) {
4435 const newQuery = state.query,
4436 prevQuery = this.#state.query;
4437 const newType = typeof newQuery,
4438 prevType = typeof prevQuery;
4439 if (newType !== prevType) {
4442 if (newType === "string") {
4443 if (newQuery !== prevQuery) {
4446 } else if (JSON.stringify(newQuery) !== JSON.stringify(prevQuery)) {
4449 switch (state.type) {
4451 const pageNumber = this._selected.pageIdx + 1;
4452 const linkService = this._linkService;
4453 return pageNumber >= 1 && pageNumber <= linkService.pagesCount && pageNumber !== linkService.page && !(this.onIsPageVisible?.(pageNumber) ?? true);
4454 case "highlightallchange":
4459 #isEntireWord(content, startIdx, length) {
4460 let match = content.slice(0, startIdx).match(NOT_DIACRITIC_FROM_END_REG_EXP);
4462 const first = content.charCodeAt(startIdx);
4463 const limit = match[1].charCodeAt(0);
4464 if (getCharacterType(first) === getCharacterType(limit)) {
4468 match = content.slice(startIdx + length).match(NOT_DIACRITIC_FROM_START_REG_EXP);
4470 const last = content.charCodeAt(startIdx + length - 1);
4471 const limit = match[1].charCodeAt(0);
4472 if (getCharacterType(last) === getCharacterType(limit)) {
4478 #convertToRegExpString(query, hasDiacritics) {
4482 let isUnicode = false;
4483 query = query.replaceAll(SPECIAL_CHARS_REG_EXP, (match, p1, p2, p3, p4, p5) => {
4485 return `[ ]*\\${p1}[ ]*`;
4488 return `[ ]*${p2}[ ]*`;
4493 if (matchDiacritics) {
4497 return DIACRITICS_EXCEPTION.has(p4.charCodeAt(0)) ? p4 : "";
4499 if (hasDiacritics) {
4501 return `${p5}\\p{M}*`;
4505 const trailingSpaces = "[ ]*";
4506 if (query.endsWith(trailingSpaces)) {
4507 query = query.slice(0, query.length - trailingSpaces.length);
4509 if (matchDiacritics) {
4510 if (hasDiacritics) {
4511 DIACRITICS_EXCEPTION_STR ||= String.fromCharCode(...DIACRITICS_EXCEPTION);
4513 query = `${query}(?=[${DIACRITICS_EXCEPTION_STR}]|[^\\p{M}]|$)`;
4516 return [isUnicode, query];
4518 #calculateMatch(pageIndex) {
4519 const query = this.#query;
4520 if (query.length === 0) {
4523 const pageContent = this._pageContents[pageIndex];
4524 const matcherResult = this.match(query, pageContent, pageIndex);
4525 const matches = this._pageMatches[pageIndex] = [];
4526 const matchesLength = this._pageMatchesLength[pageIndex] = [];
4527 const diffs = this._pageDiffs[pageIndex];
4528 matcherResult?.forEach(({
4532 const [matchPos, matchLen] = getOriginalIndex(diffs, index, length);
4534 matches.push(matchPos);
4535 matchesLength.push(matchLen);
4538 if (this.#state.highlightAll) {
4539 this.#updatePage(pageIndex);
4541 if (this._resumePageIdx === pageIndex) {
4542 this._resumePageIdx = null;
4543 this.#nextPageMatch();
4545 const pageMatchesCount = matches.length;
4546 this._matchesCountTotal += pageMatchesCount;
4547 if (this.#updateMatchesCountOnProgress) {
4548 if (pageMatchesCount > 0) {
4549 this.#updateUIResultsCount();
4551 } else if (++this.#visitedPagesCount === this._linkService.pagesCount) {
4552 this.#updateUIResultsCount();
4555 match(query, pageContent, pageIndex) {
4556 const hasDiacritics = this._hasDiacritics[pageIndex];
4557 let isUnicode = false;
4558 if (typeof query === "string") {
4559 [isUnicode, query] = this.#convertToRegExpString(query, hasDiacritics);
4561 query = query.sort().reverse().map(q => {
4562 const [isUnicodePart, queryPart] = this.#convertToRegExpString(q, hasDiacritics);
4563 isUnicode ||= isUnicodePart;
4564 return `(${queryPart})`;
4574 const flags = `g${isUnicode ? "u" : ""}${caseSensitive ? "" : "i"}`;
4575 query = new RegExp(query, flags);
4578 while ((match = query.exec(pageContent)) !== null) {
4579 if (entireWord && !this.#isEntireWord(pageContent, match.index, match[0].length)) {
4584 length: match[0].length
4590 if (this._extractTextPromises.length > 0) {
4593 let deferred = Promise.resolve();
4594 const textOptions = {
4595 disableNormalization: true
4597 for (let i = 0, ii = this._linkService.pagesCount; i < ii; i++) {
4601 } = Promise.withResolvers();
4602 this._extractTextPromises[i] = promise;
4603 deferred = deferred.then(() => {
4604 return this._pdfDocument.getPage(i + 1).then(pdfPage => pdfPage.getTextContent(textOptions)).then(textContent => {
4606 for (const textItem of textContent.items) {
4607 strBuf.push(textItem.str);
4608 if (textItem.hasEOL) {
4612 [this._pageContents[i], this._pageDiffs[i], this._hasDiacritics[i]] = normalize(strBuf.join(""));
4615 console.error(`Unable to get text content for page ${i + 1}`, reason);
4616 this._pageContents[i] = "";
4617 this._pageDiffs[i] = null;
4618 this._hasDiacritics[i] = false;
4624 #updatePage(index) {
4625 if (this._scrollMatches && this._selected.pageIdx === index) {
4626 this._linkService.page = index + 1;
4628 this._eventBus.dispatch("updatetextlayermatches", {
4634 this._eventBus.dispatch("updatetextlayermatches", {
4640 const previous = this.#state.findPrevious;
4641 const currentPageIndex = this._linkService.page - 1;
4642 const numPages = this._linkService.pagesCount;
4643 this._highlightMatches = true;
4644 if (this._dirtyMatch) {
4645 this._dirtyMatch = false;
4646 this._selected.pageIdx = this._selected.matchIdx = -1;
4647 this._offset.pageIdx = currentPageIndex;
4648 this._offset.matchIdx = null;
4649 this._offset.wrapped = false;
4650 this._resumePageIdx = null;
4651 this._pageMatches.length = 0;
4652 this._pageMatchesLength.length = 0;
4653 this.#visitedPagesCount = 0;
4654 this._matchesCountTotal = 0;
4655 this.#updateAllPages();
4656 for (let i = 0; i < numPages; i++) {
4657 if (this._pendingFindMatches.has(i)) {
4660 this._pendingFindMatches.add(i);
4661 this._extractTextPromises[i].then(() => {
4662 this._pendingFindMatches.delete(i);
4663 this.#calculateMatch(i);
4667 const query = this.#query;
4668 if (query.length === 0) {
4669 this.#updateUIState(FindState.FOUND);
4672 if (this._resumePageIdx) {
4675 const offset = this._offset;
4676 this._pagesToSearch = numPages;
4677 if (offset.matchIdx !== null) {
4678 const numPageMatches = this._pageMatches[offset.pageIdx].length;
4679 if (!previous && offset.matchIdx + 1 < numPageMatches || previous && offset.matchIdx > 0) {
4680 offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
4681 this.#updateMatch(true);
4684 this.#advanceOffsetPage(previous);
4686 this.#nextPageMatch();
4688 #matchesReady(matches) {
4689 const offset = this._offset;
4690 const numMatches = matches.length;
4691 const previous = this.#state.findPrevious;
4693 offset.matchIdx = previous ? numMatches - 1 : 0;
4694 this.#updateMatch(true);
4697 this.#advanceOffsetPage(previous);
4698 if (offset.wrapped) {
4699 offset.matchIdx = null;
4700 if (this._pagesToSearch < 0) {
4701 this.#updateMatch(false);
4708 if (this._resumePageIdx !== null) {
4709 console.error("There can only be one pending page.");
4713 const pageIdx = this._offset.pageIdx;
4714 matches = this._pageMatches[pageIdx];
4716 this._resumePageIdx = pageIdx;
4719 } while (!this.#matchesReady(matches));
4721 #advanceOffsetPage(previous) {
4722 const offset = this._offset;
4723 const numPages = this._linkService.pagesCount;
4724 offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
4725 offset.matchIdx = null;
4726 this._pagesToSearch--;
4727 if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
4728 offset.pageIdx = previous ? numPages - 1 : 0;
4729 offset.wrapped = true;
4732 #updateMatch(found = false) {
4733 let state = FindState.NOT_FOUND;
4734 const wrapped = this._offset.wrapped;
4735 this._offset.wrapped = false;
4737 const previousPage = this._selected.pageIdx;
4738 this._selected.pageIdx = this._offset.pageIdx;
4739 this._selected.matchIdx = this._offset.matchIdx;
4740 state = wrapped ? FindState.WRAPPED : FindState.FOUND;
4741 if (previousPage !== -1 && previousPage !== this._selected.pageIdx) {
4742 this.#updatePage(previousPage);
4745 this.#updateUIState(state, this.#state.findPrevious);
4746 if (this._selected.pageIdx !== -1) {
4747 this._scrollMatches = true;
4748 this.#updatePage(this._selected.pageIdx);
4751 #onFindBarClose(evt) {
4752 const pdfDocument = this._pdfDocument;
4753 this._firstPageCapability.promise.then(() => {
4754 if (!this._pdfDocument || pdfDocument && this._pdfDocument !== pdfDocument) {
4757 if (this._findTimeout) {
4758 clearTimeout(this._findTimeout);
4759 this._findTimeout = null;
4761 if (this._resumePageIdx) {
4762 this._resumePageIdx = null;
4763 this._dirtyMatch = true;
4765 this.#updateUIState(FindState.FOUND);
4766 this._highlightMatches = false;
4767 this.#updateAllPages();
4770 #requestMatchesCount() {
4776 total = this._matchesCountTotal;
4777 if (matchIdx !== -1) {
4778 for (let i = 0; i < pageIdx; i++) {
4779 current += this._pageMatches[i]?.length || 0;
4781 current += matchIdx + 1;
4783 if (current < 1 || current > total) {
4784 current = total = 0;
4791 #updateUIResultsCount() {
4792 this._eventBus.dispatch("updatefindmatchescount", {
4794 matchesCount: this.#requestMatchesCount()
4797 #updateUIState(state, previous = false) {
4798 if (!this.#updateMatchesCountOnProgress && (this.#visitedPagesCount !== this._linkService.pagesCount || state === FindState.PENDING)) {
4801 this._eventBus.dispatch("updatefindcontrolstate", {
4805 entireWord: this.#state?.entireWord ?? null,
4806 matchesCount: this.#requestMatchesCount(),
4807 rawQuery: this.#state?.query ?? null
4812 ;// ./web/pdf_find_bar.js
4815 const MATCHES_COUNT_LIMIT = 1000;
4818 #resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this));
4819 constructor(options, mainContainer, eventBus) {
4820 this.opened = false;
4821 this.bar = options.bar;
4822 this.toggleButton = options.toggleButton;
4823 this.findField = options.findField;
4824 this.highlightAll = options.highlightAllCheckbox;
4825 this.caseSensitive = options.caseSensitiveCheckbox;
4826 this.matchDiacritics = options.matchDiacriticsCheckbox;
4827 this.entireWord = options.entireWordCheckbox;
4828 this.findMsg = options.findMsg;
4829 this.findResultsCount = options.findResultsCount;
4830 this.findPreviousButton = options.findPreviousButton;
4831 this.findNextButton = options.findNextButton;
4832 this.eventBus = eventBus;
4833 this.#mainContainer = mainContainer;
4834 const checkedInputs = new Map([[this.highlightAll, "highlightallchange"], [this.caseSensitive, "casesensitivitychange"], [this.entireWord, "entirewordchange"], [this.matchDiacritics, "diacriticmatchingchange"]]);
4835 this.toggleButton.addEventListener("click", () => {
4838 this.findField.addEventListener("input", () => {
4839 this.dispatchEvent("");
4841 this.bar.addEventListener("keydown", ({
4848 if (target === this.findField) {
4849 this.dispatchEvent("again", shiftKey);
4850 } else if (checkedInputs.has(target)) {
4851 target.checked = !target.checked;
4852 this.dispatchEvent(checkedInputs.get(target));
4860 this.findPreviousButton.addEventListener("click", () => {
4861 this.dispatchEvent("again", true);
4863 this.findNextButton.addEventListener("click", () => {
4864 this.dispatchEvent("again", false);
4866 for (const [elem, evtName] of checkedInputs) {
4867 elem.addEventListener("click", () => {
4868 this.dispatchEvent(evtName);
4873 this.updateUIState();
4875 dispatchEvent(type, findPrev = false) {
4876 this.eventBus.dispatch("find", {
4879 query: this.findField.value,
4880 caseSensitive: this.caseSensitive.checked,
4881 entireWord: this.entireWord.checked,
4882 highlightAll: this.highlightAll.checked,
4883 findPrevious: findPrev,
4884 matchDiacritics: this.matchDiacritics.checked
4887 updateUIState(state, previous, matchesCount) {
4895 case FindState.FOUND:
4897 case FindState.PENDING:
4900 case FindState.NOT_FOUND:
4901 findMsgId = "pdfjs-find-not-found";
4902 status = "notFound";
4904 case FindState.WRAPPED:
4905 findMsgId = previous ? "pdfjs-find-reached-top" : "pdfjs-find-reached-bottom";
4908 findField.setAttribute("data-status", status);
4909 findField.setAttribute("aria-invalid", state === FindState.NOT_FOUND);
4910 findMsg.setAttribute("data-status", status);
4912 findMsg.setAttribute("data-l10n-id", findMsgId);
4914 findMsg.removeAttribute("data-l10n-id");
4915 findMsg.textContent = "";
4917 this.updateResultsCount(matchesCount);
4919 updateResultsCount({
4927 const limit = MATCHES_COUNT_LIMIT;
4928 findResultsCount.setAttribute("data-l10n-id", total > limit ? "pdfjs-find-match-count-limit" : "pdfjs-find-match-count");
4929 findResultsCount.setAttribute("data-l10n-args", JSON.stringify({
4935 findResultsCount.removeAttribute("data-l10n-id");
4936 findResultsCount.textContent = "";
4941 this.#resizeObserver.observe(this.#mainContainer);
4942 this.#resizeObserver.observe(this.bar);
4944 toggleExpandedBtn(this.toggleButton, true, this.bar);
4946 this.findField.select();
4947 this.findField.focus();
4953 this.#resizeObserver.disconnect();
4954 this.opened = false;
4955 toggleExpandedBtn(this.toggleButton, false, this.bar);
4956 this.eventBus.dispatch("findbarclose", {
4967 #resizeObserverCallback() {
4971 bar.classList.remove("wrapContainers");
4972 const findbarHeight = bar.clientHeight;
4973 const inputContainerHeight = bar.firstElementChild.clientHeight;
4974 if (findbarHeight > inputContainerHeight) {
4975 bar.classList.add("wrapContainers");
4980 ;// ./web/pdf_history.js
4983 const HASH_CHANGE_TIMEOUT = 1000;
4984 const POSITION_UPDATED_THRESHOLD = 50;
4985 const UPDATE_VIEWAREA_TIMEOUT = 1000;
4986 function getCurrentHash() {
4987 return document.location.hash;
4990 #eventAbortController = null;
4995 this.linkService = linkService;
4996 this.eventBus = eventBus;
4997 this._initialized = false;
4998 this._fingerprint = "";
5000 this.eventBus._on("pagesinit", () => {
5001 this._isPagesLoaded = false;
5002 this.eventBus._on("pagesloaded", evt => {
5003 this._isPagesLoaded = !!evt.pagesCount;
5011 resetHistory = false,
5014 if (!fingerprint || typeof fingerprint !== "string") {
5015 console.error('PDFHistory.initialize: The "fingerprint" must be a non-empty string.');
5018 if (this._initialized) {
5021 const reInitialized = this._fingerprint !== "" && this._fingerprint !== fingerprint;
5022 this._fingerprint = fingerprint;
5023 this._updateUrl = updateUrl === true;
5024 this._initialized = true;
5026 const state = window.history.state;
5027 this._popStateInProgress = false;
5028 this._blockHashChange = 0;
5029 this._currentHash = getCurrentHash();
5030 this._numPositionUpdates = 0;
5031 this._uid = this._maxUid = 0;
5032 this._destination = null;
5033 this._position = null;
5034 if (!this.#isValidState(state, true) || resetHistory) {
5039 } = this.#parseCurrentHash(true);
5040 if (!hash || reInitialized || resetHistory) {
5041 this.#pushOrReplaceState(null, true);
5044 this.#pushOrReplaceState({
5051 const destination = state.destination;
5052 this.#updateInternalState(destination, state.uid, true);
5053 if (destination.rotation !== undefined) {
5054 this._initialRotation = destination.rotation;
5056 if (destination.dest) {
5057 this._initialBookmark = JSON.stringify(destination.dest);
5058 this._destination.page = null;
5059 } else if (destination.hash) {
5060 this._initialBookmark = destination.hash;
5061 } else if (destination.page) {
5062 this._initialBookmark = `page=${destination.page}`;
5066 if (this._initialized) {
5068 this._initialized = false;
5069 this.#unbindEvents();
5071 if (this._updateViewareaTimeout) {
5072 clearTimeout(this._updateViewareaTimeout);
5073 this._updateViewareaTimeout = null;
5075 this._initialBookmark = null;
5076 this._initialRotation = null;
5083 if (!this._initialized) {
5086 if (namedDest && typeof namedDest !== "string") {
5087 console.error("PDFHistory.push: " + `"${namedDest}" is not a valid namedDest parameter.`);
5089 } else if (!Array.isArray(explicitDest)) {
5090 console.error("PDFHistory.push: " + `"${explicitDest}" is not a valid explicitDest parameter.`);
5092 } else if (!this.#isValidPage(pageNumber)) {
5093 if (pageNumber !== null || this._destination) {
5094 console.error("PDFHistory.push: " + `"${pageNumber}" is not a valid pageNumber parameter.`);
5098 const hash = namedDest || JSON.stringify(explicitDest);
5102 let forceReplace = false;
5103 if (this._destination && (isDestHashesEqual(this._destination.hash, hash) || isDestArraysEqual(this._destination.dest, explicitDest))) {
5104 if (this._destination.page) {
5107 forceReplace = true;
5109 if (this._popStateInProgress && !forceReplace) {
5112 this.#pushOrReplaceState({
5116 rotation: this.linkService.rotation
5118 if (!this._popStateInProgress) {
5119 this._popStateInProgress = true;
5120 Promise.resolve().then(() => {
5121 this._popStateInProgress = false;
5125 pushPage(pageNumber) {
5126 if (!this._initialized) {
5129 if (!this.#isValidPage(pageNumber)) {
5130 console.error(`PDFHistory.pushPage: "${pageNumber}" is not a valid page number.`);
5133 if (this._destination?.page === pageNumber) {
5136 if (this._popStateInProgress) {
5139 this.#pushOrReplaceState({
5141 hash: `page=${pageNumber}`,
5143 rotation: this.linkService.rotation
5145 if (!this._popStateInProgress) {
5146 this._popStateInProgress = true;
5147 Promise.resolve().then(() => {
5148 this._popStateInProgress = false;
5152 pushCurrentPosition() {
5153 if (!this._initialized || this._popStateInProgress) {
5156 this.#tryPushCurrentPosition();
5159 if (!this._initialized || this._popStateInProgress) {
5162 const state = window.history.state;
5163 if (this.#isValidState(state) && state.uid > 0) {
5164 window.history.back();
5168 if (!this._initialized || this._popStateInProgress) {
5171 const state = window.history.state;
5172 if (this.#isValidState(state) && state.uid < this._maxUid) {
5173 window.history.forward();
5176 get popStateInProgress() {
5177 return this._initialized && (this._popStateInProgress || this._blockHashChange > 0);
5179 get initialBookmark() {
5180 return this._initialized ? this._initialBookmark : null;
5182 get initialRotation() {
5183 return this._initialized ? this._initialRotation : null;
5185 #pushOrReplaceState(destination, forceReplace = false) {
5186 const shouldReplace = forceReplace || !this._destination;
5188 fingerprint: this._fingerprint,
5189 uid: shouldReplace ? this._uid : this._uid + 1,
5192 this.#updateInternalState(destination, newState.uid);
5194 if (this._updateUrl && destination?.hash) {
5195 const baseUrl = document.location.href.split("#", 1)[0];
5196 if (!baseUrl.startsWith("file://")) {
5197 newUrl = `${baseUrl}#${destination.hash}`;
5200 if (shouldReplace) {
5201 window.history.replaceState(newState, "", newUrl);
5203 window.history.pushState(newState, "", newUrl);
5206 #tryPushCurrentPosition(temporary = false) {
5207 if (!this._position) {
5210 let position = this._position;
5212 position = Object.assign(Object.create(null), this._position);
5213 position.temporary = true;
5215 if (!this._destination) {
5216 this.#pushOrReplaceState(position);
5219 if (this._destination.temporary) {
5220 this.#pushOrReplaceState(position, true);
5223 if (this._destination.hash === position.hash) {
5226 if (!this._destination.page && (POSITION_UPDATED_THRESHOLD <= 0 || this._numPositionUpdates <= POSITION_UPDATED_THRESHOLD)) {
5229 let forceReplace = false;
5230 if (this._destination.page >= position.first && this._destination.page <= position.page) {
5231 if (this._destination.dest !== undefined || !this._destination.first) {
5234 forceReplace = true;
5236 this.#pushOrReplaceState(position, forceReplace);
5239 return Number.isInteger(val) && val > 0 && val <= this.linkService.pagesCount;
5241 #isValidState(state, checkReload = false) {
5245 if (state.fingerprint !== this._fingerprint) {
5247 if (typeof state.fingerprint !== "string" || state.fingerprint.length !== this._fingerprint.length) {
5250 const [perfEntry] = performance.getEntriesByType("navigation");
5251 if (perfEntry?.type !== "reload") {
5258 if (!Number.isInteger(state.uid) || state.uid < 0) {
5261 if (state.destination === null || typeof state.destination !== "object") {
5266 #updateInternalState(destination, uid, removeTemporary = false) {
5267 if (this._updateViewareaTimeout) {
5268 clearTimeout(this._updateViewareaTimeout);
5269 this._updateViewareaTimeout = null;
5271 if (removeTemporary && destination?.temporary) {
5272 delete destination.temporary;
5274 this._destination = destination;
5276 this._maxUid = Math.max(this._maxUid, uid);
5277 this._numPositionUpdates = 0;
5279 #parseCurrentHash(checkNameddest = false) {
5280 const hash = unescape(getCurrentHash()).substring(1);
5281 const params = parseQueryString(hash);
5282 const nameddest = params.get("nameddest") || "";
5283 let page = params.get("page") | 0;
5284 if (!this.#isValidPage(page) || checkNameddest && nameddest.length > 0) {
5290 rotation: this.linkService.rotation
5296 if (this._updateViewareaTimeout) {
5297 clearTimeout(this._updateViewareaTimeout);
5298 this._updateViewareaTimeout = null;
5301 hash: location.pdfOpenParams.substring(1),
5302 page: this.linkService.page,
5303 first: location.pageNumber,
5304 rotation: location.rotation
5306 if (this._popStateInProgress) {
5309 if (POSITION_UPDATED_THRESHOLD > 0 && this._isPagesLoaded && this._destination && !this._destination.page) {
5310 this._numPositionUpdates++;
5312 if (UPDATE_VIEWAREA_TIMEOUT > 0) {
5313 this._updateViewareaTimeout = setTimeout(() => {
5314 if (!this._popStateInProgress) {
5315 this.#tryPushCurrentPosition(true);
5317 this._updateViewareaTimeout = null;
5318 }, UPDATE_VIEWAREA_TIMEOUT);
5324 const newHash = getCurrentHash(),
5325 hashChanged = this._currentHash !== newHash;
5326 this._currentHash = newHash;
5333 } = this.#parseCurrentHash();
5334 this.#pushOrReplaceState({
5341 if (!this.#isValidState(state)) {
5344 this._popStateInProgress = true;
5346 this._blockHashChange++;
5347 waitOnEventOrTimeout({
5350 delay: HASH_CHANGE_TIMEOUT
5352 this._blockHashChange--;
5355 const destination = state.destination;
5356 this.#updateInternalState(destination, state.uid, true);
5357 if (isValidRotation(destination.rotation)) {
5358 this.linkService.rotation = destination.rotation;
5360 if (destination.dest) {
5361 this.linkService.goToDestination(destination.dest);
5362 } else if (destination.hash) {
5363 this.linkService.setHash(destination.hash);
5364 } else if (destination.page) {
5365 this.linkService.page = destination.page;
5367 Promise.resolve().then(() => {
5368 this._popStateInProgress = false;
5372 if (!this._destination || this._destination.temporary) {
5373 this.#tryPushCurrentPosition();
5377 if (this.#eventAbortController) {
5380 this.#eventAbortController = new AbortController();
5383 } = this.#eventAbortController;
5384 this.eventBus._on("updateviewarea", this.#updateViewarea.bind(this), {
5387 window.addEventListener("popstate", this.#popState.bind(this), {
5390 window.addEventListener("pagehide", this.#pageHide.bind(this), {
5395 this.#eventAbortController?.abort();
5396 this.#eventAbortController = null;
5399 function isDestHashesEqual(destHash, pushHash) {
5400 if (typeof destHash !== "string" || typeof pushHash !== "string") {
5403 if (destHash === pushHash) {
5406 const nameddest = parseQueryString(destHash).get("nameddest");
5407 if (nameddest === pushHash) {
5412 function isDestArraysEqual(firstDest, secondDest) {
5413 function isEntryEqual(first, second) {
5414 if (typeof first !== typeof second) {
5417 if (Array.isArray(first) || Array.isArray(second)) {
5420 if (first !== null && typeof first === "object" && second !== null) {
5421 if (Object.keys(first).length !== Object.keys(second).length) {
5424 for (const key in first) {
5425 if (!isEntryEqual(first[key], second[key])) {
5431 return first === second || Number.isNaN(first) && Number.isNaN(second);
5433 if (!(Array.isArray(firstDest) && Array.isArray(secondDest))) {
5436 if (firstDest.length !== secondDest.length) {
5439 for (let i = 0, ii = firstDest.length; i < ii; i++) {
5440 if (!isEntryEqual(firstDest[i], secondDest[i])) {
5447 ;// ./web/pdf_layer_viewer.js
5449 class PDFLayerViewer extends BaseTreeViewer {
5450 constructor(options) {
5452 this.eventBus._on("optionalcontentconfigchanged", evt => {
5453 this.#updateLayers(evt.promise);
5455 this.eventBus._on("resetlayers", () => {
5456 this.#updateLayers();
5458 this.eventBus._on("togglelayerstree", this._toggleAllTreeItems.bind(this));
5462 this._optionalContentConfig = null;
5463 this._optionalContentVisibility?.clear();
5464 this._optionalContentVisibility = null;
5466 _dispatchEvent(layersCount) {
5467 this.eventBus.dispatch("layersloaded", {
5472 _bindLink(element, {
5476 const setVisibility = () => {
5477 const visible = input.checked;
5478 this._optionalContentConfig.setVisibility(groupId, visible);
5479 const cached = this._optionalContentVisibility.get(groupId);
5481 cached.visible = visible;
5483 this.eventBus.dispatch("optionalcontentconfig", {
5485 promise: Promise.resolve(this._optionalContentConfig)
5488 element.onclick = evt => {
5489 if (evt.target === input) {
5492 } else if (evt.target !== element) {
5495 input.checked = !input.checked;
5500 _setNestedName(element, {
5503 if (typeof name === "string") {
5504 element.textContent = this._normalizeTextContent(name);
5507 element.setAttribute("data-l10n-id", "pdfjs-additional-layers");
5508 element.style.fontStyle = "italic";
5509 this._l10n.translateOnce(element);
5511 _addToggleButton(div, {
5514 super._addToggleButton(div, name === null);
5516 _toggleAllTreeItems() {
5517 if (!this._optionalContentConfig) {
5520 super._toggleAllTreeItems();
5523 optionalContentConfig,
5526 if (this._optionalContentConfig) {
5529 this._optionalContentConfig = optionalContentConfig || null;
5530 this._pdfDocument = pdfDocument || null;
5531 const groups = optionalContentConfig?.getOrder();
5533 this._dispatchEvent(0);
5536 this._optionalContentVisibility = new Map();
5537 const fragment = document.createDocumentFragment(),
5542 let layersCount = 0,
5543 hasAnyNesting = false;
5544 while (queue.length > 0) {
5545 const levelData = queue.shift();
5546 for (const groupId of levelData.groups) {
5547 const div = document.createElement("div");
5548 div.className = "treeItem";
5549 const element = document.createElement("a");
5550 div.append(element);
5551 if (typeof groupId === "object") {
5552 hasAnyNesting = true;
5553 this._addToggleButton(div, groupId);
5554 this._setNestedName(element, groupId);
5555 const itemsDiv = document.createElement("div");
5556 itemsDiv.className = "treeItems";
5557 div.append(itemsDiv);
5560 groups: groupId.order
5563 const group = optionalContentConfig.getGroup(groupId);
5564 const input = document.createElement("input");
5565 this._bindLink(element, {
5569 input.type = "checkbox";
5570 input.checked = group.visible;
5571 this._optionalContentVisibility.set(groupId, {
5573 visible: input.checked
5575 const label = document.createElement("label");
5576 label.textContent = this._normalizeTextContent(group.name);
5577 label.append(input);
5578 element.append(label);
5581 levelData.parent.append(div);
5584 this._finishRendering(fragment, layersCount, hasAnyNesting);
5586 async #updateLayers(promise = null) {
5587 if (!this._optionalContentConfig) {
5590 const pdfDocument = this._pdfDocument;
5591 const optionalContentConfig = await (promise || pdfDocument.getOptionalContentConfig({
5594 if (pdfDocument !== this._pdfDocument) {
5598 for (const [groupId, cached] of this._optionalContentVisibility) {
5599 const group = optionalContentConfig.getGroup(groupId);
5600 if (group && cached.visible !== group.visible) {
5601 cached.input.checked = cached.visible = !cached.visible;
5606 this.eventBus.dispatch("optionalcontentconfig", {
5608 promise: Promise.resolve(optionalContentConfig)
5611 optionalContentConfig,
5612 pdfDocument: this._pdfDocument
5617 ;// ./web/pdf_outline_viewer.js
5620 class PDFOutlineViewer extends BaseTreeViewer {
5621 constructor(options) {
5623 this.linkService = options.linkService;
5624 this.downloadManager = options.downloadManager;
5625 this.eventBus._on("toggleoutlinetree", this._toggleAllTreeItems.bind(this));
5626 this.eventBus._on("currentoutlineitem", this._currentOutlineItem.bind(this));
5627 this.eventBus._on("pagechanging", evt => {
5628 this._currentPageNumber = evt.pageNumber;
5630 this.eventBus._on("pagesloaded", evt => {
5631 this._isPagesLoaded = !!evt.pagesCount;
5632 this._currentOutlineItemCapability?.resolve(this._isPagesLoaded);
5634 this.eventBus._on("sidebarviewchanged", evt => {
5635 this._sidebarView = evt.view;
5640 this._outline = null;
5641 this._pageNumberToDestHashCapability = null;
5642 this._currentPageNumber = 1;
5643 this._isPagesLoaded = null;
5644 this._currentOutlineItemCapability?.resolve(false);
5645 this._currentOutlineItemCapability = null;
5647 _dispatchEvent(outlineCount) {
5648 this._currentOutlineItemCapability = Promise.withResolvers();
5649 if (outlineCount === 0 || this._pdfDocument?.loadingParams.disableAutoFetch) {
5650 this._currentOutlineItemCapability.resolve(false);
5651 } else if (this._isPagesLoaded !== null) {
5652 this._currentOutlineItemCapability.resolve(this._isPagesLoaded);
5654 this.eventBus.dispatch("outlineloaded", {
5657 currentOutlineItemPromise: this._currentOutlineItemCapability.promise
5660 _bindLink(element, {
5672 linkService.addLinkAttributes(element, url, newWindow);
5676 element.href = linkService.getAnchorUrl("");
5677 element.onclick = () => {
5678 linkService.executeNamedAction(action);
5684 element.href = linkService.getAnchorUrl("");
5685 element.onclick = () => {
5686 this.downloadManager.openOrDownloadData(attachment.content, attachment.filename);
5692 element.href = linkService.getAnchorUrl("");
5693 element.onclick = () => {
5694 linkService.executeSetOCGState(setOCGState);
5699 element.href = linkService.getDestinationHash(dest);
5700 element.onclick = evt => {
5701 this._updateCurrentTreeItem(evt.target.parentNode);
5703 linkService.goToDestination(dest);
5708 _setStyles(element, {
5713 element.style.fontWeight = "bold";
5716 element.style.fontStyle = "italic";
5719 _addToggleButton(div, {
5725 let totalCount = items.length;
5726 if (totalCount > 0) {
5727 const queue = [...items];
5728 while (queue.length > 0) {
5733 if (nestedCount > 0 && nestedItems.length > 0) {
5734 totalCount += nestedItems.length;
5735 queue.push(...nestedItems);
5739 if (Math.abs(count) === totalCount) {
5743 super._addToggleButton(div, hidden);
5745 _toggleAllTreeItems() {
5746 if (!this._outline) {
5749 super._toggleAllTreeItems();
5755 if (this._outline) {
5758 this._outline = outline || null;
5759 this._pdfDocument = pdfDocument || null;
5761 this._dispatchEvent(0);
5764 const fragment = document.createDocumentFragment();
5769 let outlineCount = 0,
5770 hasAnyNesting = false;
5771 while (queue.length > 0) {
5772 const levelData = queue.shift();
5773 for (const item of levelData.items) {
5774 const div = document.createElement("div");
5775 div.className = "treeItem";
5776 const element = document.createElement("a");
5777 this._bindLink(element, item);
5778 this._setStyles(element, item);
5779 element.textContent = this._normalizeTextContent(item.title);
5780 div.append(element);
5781 if (item.items.length > 0) {
5782 hasAnyNesting = true;
5783 this._addToggleButton(div, item);
5784 const itemsDiv = document.createElement("div");
5785 itemsDiv.className = "treeItems";
5786 div.append(itemsDiv);
5792 levelData.parent.append(div);
5796 this._finishRendering(fragment, outlineCount, hasAnyNesting);
5798 async _currentOutlineItem() {
5799 if (!this._isPagesLoaded) {
5800 throw new Error("_currentOutlineItem: All pages have not been loaded.");
5802 if (!this._outline || !this._pdfDocument) {
5805 const pageNumberToDestHash = await this._getPageNumberToDestHash(this._pdfDocument);
5806 if (!pageNumberToDestHash) {
5809 this._updateCurrentTreeItem(null);
5810 if (this._sidebarView !== SidebarView.OUTLINE) {
5813 for (let i = this._currentPageNumber; i > 0; i--) {
5814 const destHash = pageNumberToDestHash.get(i);
5818 const linkElement = this.container.querySelector(`a[href="${destHash}"]`);
5822 this._scrollToCurrentTreeItem(linkElement.parentNode);
5826 async _getPageNumberToDestHash(pdfDocument) {
5827 if (this._pageNumberToDestHashCapability) {
5828 return this._pageNumberToDestHashCapability.promise;
5830 this._pageNumberToDestHashCapability = Promise.withResolvers();
5831 const pageNumberToDestHash = new Map(),
5832 pageNumberNesting = new Map();
5835 items: this._outline
5837 while (queue.length > 0) {
5838 const levelData = queue.shift(),
5839 currentNesting = levelData.nesting;
5843 } of levelData.items) {
5844 let explicitDest, pageNumber;
5845 if (typeof dest === "string") {
5846 explicitDest = await pdfDocument.getDestination(dest);
5847 if (pdfDocument !== this._pdfDocument) {
5851 explicitDest = dest;
5853 if (Array.isArray(explicitDest)) {
5854 const [destRef] = explicitDest;
5855 if (destRef && typeof destRef === "object") {
5856 pageNumber = pdfDocument.cachedPageNumber(destRef);
5857 } else if (Number.isInteger(destRef)) {
5858 pageNumber = destRef + 1;
5860 if (Number.isInteger(pageNumber) && (!pageNumberToDestHash.has(pageNumber) || currentNesting > pageNumberNesting.get(pageNumber))) {
5861 const destHash = this.linkService.getDestinationHash(dest);
5862 pageNumberToDestHash.set(pageNumber, destHash);
5863 pageNumberNesting.set(pageNumber, currentNesting);
5866 if (items.length > 0) {
5868 nesting: currentNesting + 1,
5874 this._pageNumberToDestHashCapability.resolve(pageNumberToDestHash.size > 0 ? pageNumberToDestHash : null);
5875 return this._pageNumberToDestHashCapability.promise;
5879 ;// ./web/pdf_presentation_mode.js
5882 const DELAY_BEFORE_HIDING_CONTROLS = 3000;
5883 const ACTIVE_SELECTOR = "pdfPresentationMode";
5884 const CONTROLS_SELECTOR = "pdfPresentationModeControls";
5885 const MOUSE_SCROLL_COOLDOWN_TIME = 50;
5886 const PAGE_SWITCH_THRESHOLD = 0.1;
5887 const SWIPE_MIN_DISTANCE_THRESHOLD = 50;
5888 const SWIPE_ANGLE_THRESHOLD = Math.PI / 6;
5889 class PDFPresentationMode {
5890 #state = PresentationModeState.UNKNOWN;
5892 #fullscreenChangeAbortController = null;
5893 #windowAbortController = null;
5899 this.container = container;
5900 this.pdfViewer = pdfViewer;
5901 this.eventBus = eventBus;
5902 this.contextMenuOpen = false;
5903 this.mouseScrollTimeStamp = 0;
5904 this.mouseScrollDelta = 0;
5905 this.touchSwipeState = null;
5912 if (this.active || !pdfViewer.pagesCount || !container.requestFullscreen) {
5915 this.#addFullscreenChangeListeners();
5916 this.#notifyStateChange(PresentationModeState.CHANGING);
5917 const promise = container.requestFullscreen();
5919 pageNumber: pdfViewer.currentPageNumber,
5920 scaleValue: pdfViewer.currentScaleValue,
5921 scrollMode: pdfViewer.scrollMode,
5923 annotationEditorMode: null
5925 if (pdfViewer.spreadMode !== SpreadMode.NONE && !(pdfViewer.pageViewsReady && pdfViewer.hasEqualPageSizes)) {
5926 console.warn("Ignoring Spread modes when entering PresentationMode, " + "since the document may contain varying page sizes.");
5927 this.#args.spreadMode = pdfViewer.spreadMode;
5929 if (pdfViewer.annotationEditorMode !== AnnotationEditorType.DISABLE) {
5930 this.#args.annotationEditorMode = pdfViewer.annotationEditorMode;
5937 this.#removeFullscreenChangeListeners();
5938 this.#notifyStateChange(PresentationModeState.NORMAL);
5943 return this.#state === PresentationModeState.CHANGING || this.#state === PresentationModeState.FULLSCREEN;
5949 evt.preventDefault();
5950 const delta = normalizeWheelEventDelta(evt);
5951 const currentTime = Date.now();
5952 const storedTime = this.mouseScrollTimeStamp;
5953 if (currentTime > storedTime && currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
5956 if (this.mouseScrollDelta > 0 && delta < 0 || this.mouseScrollDelta < 0 && delta > 0) {
5957 this.#resetMouseScrollState();
5959 this.mouseScrollDelta += delta;
5960 if (Math.abs(this.mouseScrollDelta) >= PAGE_SWITCH_THRESHOLD) {
5961 const totalDelta = this.mouseScrollDelta;
5962 this.#resetMouseScrollState();
5963 const success = totalDelta > 0 ? this.pdfViewer.previousPage() : this.pdfViewer.nextPage();
5965 this.mouseScrollTimeStamp = currentTime;
5969 #notifyStateChange(state) {
5970 this.#state = state;
5971 this.eventBus.dispatch("presentationmodechanged", {
5977 this.#notifyStateChange(PresentationModeState.FULLSCREEN);
5978 this.container.classList.add(ACTIVE_SELECTOR);
5980 this.pdfViewer.scrollMode = ScrollMode.PAGE;
5981 if (this.#args.spreadMode !== null) {
5982 this.pdfViewer.spreadMode = SpreadMode.NONE;
5984 this.pdfViewer.currentPageNumber = this.#args.pageNumber;
5985 this.pdfViewer.currentScaleValue = "page-fit";
5986 if (this.#args.annotationEditorMode !== null) {
5987 this.pdfViewer.annotationEditorMode = {
5988 mode: AnnotationEditorType.NONE
5992 this.#addWindowListeners();
5993 this.#showControls();
5994 this.contextMenuOpen = false;
5995 document.getSelection().empty();
5998 const pageNumber = this.pdfViewer.currentPageNumber;
5999 this.container.classList.remove(ACTIVE_SELECTOR);
6001 this.#removeFullscreenChangeListeners();
6002 this.#notifyStateChange(PresentationModeState.NORMAL);
6003 this.pdfViewer.scrollMode = this.#args.scrollMode;
6004 if (this.#args.spreadMode !== null) {
6005 this.pdfViewer.spreadMode = this.#args.spreadMode;
6007 this.pdfViewer.currentScaleValue = this.#args.scaleValue;
6008 this.pdfViewer.currentPageNumber = pageNumber;
6009 if (this.#args.annotationEditorMode !== null) {
6010 this.pdfViewer.annotationEditorMode = {
6011 mode: this.#args.annotationEditorMode
6016 this.#removeWindowListeners();
6017 this.#hideControls();
6018 this.#resetMouseScrollState();
6019 this.contextMenuOpen = false;
6022 if (this.contextMenuOpen) {
6023 this.contextMenuOpen = false;
6024 evt.preventDefault();
6027 if (evt.button !== 0) {
6030 if (evt.target.href && evt.target.parentNode?.hasAttribute("data-internal-link")) {
6033 evt.preventDefault();
6035 this.pdfViewer.previousPage();
6037 this.pdfViewer.nextPage();
6041 this.contextMenuOpen = true;
6044 if (this.controlsTimeout) {
6045 clearTimeout(this.controlsTimeout);
6047 this.container.classList.add(CONTROLS_SELECTOR);
6049 this.controlsTimeout = setTimeout(() => {
6050 this.container.classList.remove(CONTROLS_SELECTOR);
6051 delete this.controlsTimeout;
6052 }, DELAY_BEFORE_HIDING_CONTROLS);
6055 if (!this.controlsTimeout) {
6058 clearTimeout(this.controlsTimeout);
6059 this.container.classList.remove(CONTROLS_SELECTOR);
6060 delete this.controlsTimeout;
6062 #resetMouseScrollState() {
6063 this.mouseScrollTimeStamp = 0;
6064 this.mouseScrollDelta = 0;
6070 if (evt.touches.length > 1) {
6071 this.touchSwipeState = null;
6076 this.touchSwipeState = {
6077 startX: evt.touches[0].pageX,
6078 startY: evt.touches[0].pageY,
6079 endX: evt.touches[0].pageX,
6080 endY: evt.touches[0].pageY
6084 if (this.touchSwipeState === null) {
6087 this.touchSwipeState.endX = evt.touches[0].pageX;
6088 this.touchSwipeState.endY = evt.touches[0].pageY;
6089 evt.preventDefault();
6092 if (this.touchSwipeState === null) {
6096 const dx = this.touchSwipeState.endX - this.touchSwipeState.startX;
6097 const dy = this.touchSwipeState.endY - this.touchSwipeState.startY;
6098 const absAngle = Math.abs(Math.atan2(dy, dx));
6099 if (Math.abs(dx) > SWIPE_MIN_DISTANCE_THRESHOLD && (absAngle <= SWIPE_ANGLE_THRESHOLD || absAngle >= Math.PI - SWIPE_ANGLE_THRESHOLD)) {
6101 } else if (Math.abs(dy) > SWIPE_MIN_DISTANCE_THRESHOLD && Math.abs(absAngle - Math.PI / 2) <= SWIPE_ANGLE_THRESHOLD) {
6105 this.pdfViewer.previousPage();
6106 } else if (delta < 0) {
6107 this.pdfViewer.nextPage();
6112 #addWindowListeners() {
6113 if (this.#windowAbortController) {
6116 this.#windowAbortController = new AbortController();
6119 } = this.#windowAbortController;
6120 const touchSwipeBind = this.#touchSwipe.bind(this);
6121 window.addEventListener("mousemove", this.#showControls.bind(this), {
6124 window.addEventListener("mousedown", this.#mouseDown.bind(this), {
6127 window.addEventListener("wheel", this.#mouseWheel.bind(this), {
6131 window.addEventListener("keydown", this.#resetMouseScrollState.bind(this), {
6134 window.addEventListener("contextmenu", this.#contextMenu.bind(this), {
6137 window.addEventListener("touchstart", touchSwipeBind, {
6140 window.addEventListener("touchmove", touchSwipeBind, {
6143 window.addEventListener("touchend", touchSwipeBind, {
6147 #removeWindowListeners() {
6148 this.#windowAbortController?.abort();
6149 this.#windowAbortController = null;
6151 #addFullscreenChangeListeners() {
6152 if (this.#fullscreenChangeAbortController) {
6155 this.#fullscreenChangeAbortController = new AbortController();
6156 window.addEventListener("fullscreenchange", () => {
6157 if (document.fullscreenElement) {
6163 signal: this.#fullscreenChangeAbortController.signal
6166 #removeFullscreenChangeListeners() {
6167 this.#fullscreenChangeAbortController?.abort();
6168 this.#fullscreenChangeAbortController = null;
6172 ;// ./web/xfa_layer_builder.js
6174 class XfaLayerBuilder {
6177 annotationStorage = null,
6181 this.pdfPage = pdfPage;
6182 this.annotationStorage = annotationStorage;
6183 this.linkService = linkService;
6184 this.xfaHtml = xfaHtml;
6186 this._cancelled = false;
6192 if (intent === "print") {
6193 const parameters = {
6194 viewport: viewport.clone({
6198 xfaHtml: this.xfaHtml,
6199 annotationStorage: this.annotationStorage,
6200 linkService: this.linkService,
6203 this.div = document.createElement("div");
6204 parameters.div = this.div;
6205 return XfaLayer.render(parameters);
6207 const xfaHtml = await this.pdfPage.getXfa();
6208 if (this._cancelled || !xfaHtml) {
6213 const parameters = {
6214 viewport: viewport.clone({
6219 annotationStorage: this.annotationStorage,
6220 linkService: this.linkService,
6224 return XfaLayer.update(parameters);
6226 this.div = document.createElement("div");
6227 parameters.div = this.div;
6228 return XfaLayer.render(parameters);
6231 this._cancelled = true;
6237 this.div.hidden = true;
6241 ;// ./web/print_utils.js
6245 function getXfaHtmlForPrinting(printContainer, pdfDocument) {
6246 const xfaHtml = pdfDocument.allXfaHtml;
6247 const linkService = new SimpleLinkService();
6248 const scale = Math.round(PixelsPerInch.PDF_TO_CSS_UNITS * 100) / 100;
6249 for (const xfaPage of xfaHtml.children) {
6250 const page = document.createElement("div");
6251 page.className = "xfaPrintedPage";
6252 printContainer.append(page);
6253 const builder = new XfaLayerBuilder({
6255 annotationStorage: pdfDocument.annotationStorage,
6259 const viewport = getXfaPageViewport(xfaPage, {
6262 builder.render(viewport, "print");
6263 page.append(builder.div);
6267 ;// ./web/firefox_print_service.js
6270 function composePage(pdfDocument, pageNumber, size, printContainer, printResolution, optionalContentConfigPromise, printAnnotationStoragePromise) {
6271 const canvas = document.createElement("canvas");
6272 const PRINT_UNITS = printResolution / PixelsPerInch.PDF;
6273 canvas.width = Math.floor(size.width * PRINT_UNITS);
6274 canvas.height = Math.floor(size.height * PRINT_UNITS);
6275 const canvasWrapper = document.createElement("div");
6276 canvasWrapper.className = "printedPage";
6277 canvasWrapper.append(canvas);
6278 printContainer.append(canvasWrapper);
6279 let currentRenderTask = null;
6280 canvas.mozPrintCallback = function (obj) {
6281 const ctx = obj.context;
6283 ctx.fillStyle = "rgb(255, 255, 255)";
6284 ctx.fillRect(0, 0, canvas.width, canvas.height);
6286 let thisRenderTask = null;
6287 Promise.all([pdfDocument.getPage(pageNumber), printAnnotationStoragePromise]).then(function ([pdfPage, printAnnotationStorage]) {
6288 if (currentRenderTask) {
6289 currentRenderTask.cancel();
6290 currentRenderTask = null;
6292 const renderContext = {
6294 transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0],
6295 viewport: pdfPage.getViewport({
6297 rotation: size.rotation
6300 annotationMode: AnnotationMode.ENABLE_STORAGE,
6301 optionalContentConfigPromise,
6302 printAnnotationStorage
6304 currentRenderTask = thisRenderTask = pdfPage.render(renderContext);
6305 return thisRenderTask.promise;
6306 }).then(function () {
6307 if (currentRenderTask === thisRenderTask) {
6308 currentRenderTask = null;
6311 }, function (reason) {
6312 if (!(reason instanceof RenderingCancelledException)) {
6313 console.error(reason);
6315 if (currentRenderTask === thisRenderTask) {
6316 currentRenderTask.cancel();
6317 currentRenderTask = null;
6319 if ("abort" in obj) {
6327 class FirefoxPrintService {
6333 printAnnotationStoragePromise = null
6335 this.pdfDocument = pdfDocument;
6336 this.pagesOverview = pagesOverview;
6337 this.printContainer = printContainer;
6338 this._printResolution = printResolution || 150;
6339 this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
6342 this._printAnnotationStoragePromise = printAnnotationStoragePromise || Promise.resolve();
6350 _optionalContentConfigPromise,
6351 _printAnnotationStoragePromise
6353 const body = document.querySelector("body");
6354 body.setAttribute("data-pdfjsprinting", true);
6358 } = this.pagesOverview[0];
6359 const hasEqualPageSizes = this.pagesOverview.every(size => size.width === width && size.height === height);
6360 if (!hasEqualPageSizes) {
6361 console.warn("Not all pages have the same size. The printed result may be incorrect!");
6363 this.pageStyleSheet = document.createElement("style");
6364 this.pageStyleSheet.textContent = `@page { size: ${width}pt ${height}pt;}`;
6365 body.append(this.pageStyleSheet);
6366 if (pdfDocument.isPureXfa) {
6367 getXfaHtmlForPrinting(printContainer, pdfDocument);
6370 for (let i = 0, ii = pagesOverview.length; i < ii; ++i) {
6371 composePage(pdfDocument, i + 1, pagesOverview[i], printContainer, _printResolution, _optionalContentConfigPromise, _printAnnotationStoragePromise);
6375 this.printContainer.textContent = "";
6376 const body = document.querySelector("body");
6377 body.removeAttribute("data-pdfjsprinting");
6378 if (this.pageStyleSheet) {
6379 this.pageStyleSheet.remove();
6380 this.pageStyleSheet = null;
6384 class PDFPrintServiceFactory {
6385 static get supportsPrinting() {
6386 const canvas = document.createElement("canvas");
6387 return shadow(this, "supportsPrinting", "mozPrintCallback" in canvas);
6389 static createPrintService(params) {
6390 return new FirefoxPrintService(params);
6394 ;// ./web/pdf_rendering_queue.js
6397 const CLEANUP_TIMEOUT = 30000;
6398 class PDFRenderingQueue {
6400 this.pdfViewer = null;
6401 this.pdfThumbnailViewer = null;
6403 this.highestPriorityPage = null;
6404 this.idleTimeout = null;
6405 this.printing = false;
6406 this.isThumbnailViewEnabled = false;
6408 setViewer(pdfViewer) {
6409 this.pdfViewer = pdfViewer;
6411 setThumbnailViewer(pdfThumbnailViewer) {
6412 this.pdfThumbnailViewer = pdfThumbnailViewer;
6414 isHighestPriority(view) {
6415 return this.highestPriorityPage === view.renderingId;
6417 renderHighestPriority(currentlyVisiblePages) {
6418 if (this.idleTimeout) {
6419 clearTimeout(this.idleTimeout);
6420 this.idleTimeout = null;
6422 if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
6425 if (this.isThumbnailViewEnabled && this.pdfThumbnailViewer?.forceRendering()) {
6428 if (this.printing) {
6432 this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
6435 getHighestPriority(visible, views, scrolledDown, preRenderExtra = false) {
6436 const visibleViews = visible.views,
6437 numVisible = visibleViews.length;
6438 if (numVisible === 0) {
6441 for (let i = 0; i < numVisible; i++) {
6442 const view = visibleViews[i].view;
6443 if (!this.isViewFinished(view)) {
6447 const firstId = visible.first.id,
6448 lastId = visible.last.id;
6449 if (lastId - firstId + 1 > numVisible) {
6450 const visibleIds = visible.ids;
6451 for (let i = 1, ii = lastId - firstId; i < ii; i++) {
6452 const holeId = scrolledDown ? firstId + i : lastId - i;
6453 if (visibleIds.has(holeId)) {
6456 const holeView = views[holeId - 1];
6457 if (!this.isViewFinished(holeView)) {
6462 let preRenderIndex = scrolledDown ? lastId : firstId - 2;
6463 let preRenderView = views[preRenderIndex];
6464 if (preRenderView && !this.isViewFinished(preRenderView)) {
6465 return preRenderView;
6467 if (preRenderExtra) {
6468 preRenderIndex += scrolledDown ? 1 : -1;
6469 preRenderView = views[preRenderIndex];
6470 if (preRenderView && !this.isViewFinished(preRenderView)) {
6471 return preRenderView;
6476 isViewFinished(view) {
6477 return view.renderingState === RenderingStates.FINISHED;
6480 switch (view.renderingState) {
6481 case RenderingStates.FINISHED:
6483 case RenderingStates.PAUSED:
6484 this.highestPriorityPage = view.renderingId;
6487 case RenderingStates.RUNNING:
6488 this.highestPriorityPage = view.renderingId;
6490 case RenderingStates.INITIAL:
6491 this.highestPriorityPage = view.renderingId;
6492 view.draw().finally(() => {
6493 this.renderHighestPriority();
6494 }).catch(reason => {
6495 if (reason instanceof RenderingCancelledException) {
6498 console.error("renderView:", reason);
6506 ;// ./web/pdf_scripting_manager.js
6509 class PDFScriptingManager {
6510 #closeCapability = null;
6511 #destroyCapability = null;
6512 #docProperties = null;
6513 #eventAbortController = null;
6515 #externalServices = null;
6516 #pdfDocument = null;
6520 #willPrintCapability = null;
6523 externalServices = null,
6524 docProperties = null
6526 this.#eventBus = eventBus;
6527 this.#externalServices = externalServices;
6528 this.#docProperties = docProperties;
6530 setViewer(pdfViewer) {
6531 this.#pdfViewer = pdfViewer;
6533 async setDocument(pdfDocument) {
6534 if (this.#pdfDocument) {
6535 await this.#destroyScripting();
6537 this.#pdfDocument = pdfDocument;
6541 const [objects, calculationOrder, docActions] = await Promise.all([pdfDocument.getFieldObjects(), pdfDocument.getCalculationOrderIds(), pdfDocument.getJSActions()]);
6542 if (!objects && !docActions) {
6543 await this.#destroyScripting();
6546 if (pdfDocument !== this.#pdfDocument) {
6550 this.#scripting = this.#initScripting();
6552 console.error("setDocument:", error);
6553 await this.#destroyScripting();
6556 const eventBus = this.#eventBus;
6557 this.#eventAbortController = new AbortController();
6560 } = this.#eventAbortController;
6561 eventBus._on("updatefromsandbox", event => {
6562 if (event?.source === window) {
6563 this.#updateFromSandbox(event.detail);
6568 eventBus._on("dispatcheventinsandbox", event => {
6569 this.#scripting?.dispatchEventInSandbox(event.detail);
6573 eventBus._on("pagechanging", ({
6577 if (pageNumber === previous) {
6580 this.#dispatchPageClose(previous);
6581 this.#dispatchPageOpen(pageNumber);
6585 eventBus._on("pagerendered", ({
6588 if (!this._pageOpenPending.has(pageNumber)) {
6591 if (pageNumber !== this.#pdfViewer.currentPageNumber) {
6594 this.#dispatchPageOpen(pageNumber);
6598 eventBus._on("pagesdestroy", async () => {
6599 await this.#dispatchPageClose(this.#pdfViewer.currentPageNumber);
6600 await this.#scripting?.dispatchEventInSandbox({
6604 this.#closeCapability?.resolve();
6609 const docProperties = await this.#docProperties(pdfDocument);
6610 if (pdfDocument !== this.#pdfDocument) {
6613 await this.#scripting.createSandbox({
6617 platform: navigator.platform,
6618 language: navigator.language
6625 eventBus.dispatch("sandboxcreated", {
6629 console.error("setDocument:", error);
6630 await this.#destroyScripting();
6633 await this.#scripting?.dispatchEventInSandbox({
6637 await this.#dispatchPageOpen(this.#pdfViewer.currentPageNumber, true);
6638 Promise.resolve().then(() => {
6639 if (pdfDocument === this.#pdfDocument) {
6644 async dispatchWillSave() {
6645 return this.#scripting?.dispatchEventInSandbox({
6650 async dispatchDidSave() {
6651 return this.#scripting?.dispatchEventInSandbox({
6656 async dispatchWillPrint() {
6657 if (!this.#scripting) {
6660 await this.#willPrintCapability?.promise;
6661 this.#willPrintCapability = Promise.withResolvers();
6663 await this.#scripting.dispatchEventInSandbox({
6668 this.#willPrintCapability.resolve();
6669 this.#willPrintCapability = null;
6672 await this.#willPrintCapability.promise;
6674 async dispatchDidPrint() {
6675 return this.#scripting?.dispatchEventInSandbox({
6680 get destroyPromise() {
6681 return this.#destroyCapability?.promise || null;
6686 get _pageOpenPending() {
6687 return shadow(this, "_pageOpenPending", new Set());
6689 get _visitedPages() {
6690 return shadow(this, "_visitedPages", new Map());
6692 async #updateFromSandbox(detail) {
6693 const pdfViewer = this.#pdfViewer;
6694 const isInPresentationMode = pdfViewer.isInPresentationMode || pdfViewer.isChangingPresentationMode;
6707 console.error(value);
6710 if (!isInPresentationMode) {
6711 const modes = apiPageLayoutToViewerModes(value);
6712 pdfViewer.spreadMode = modes.spreadMode;
6716 pdfViewer.currentPageNumber = value + 1;
6719 await pdfViewer.pagesPromise;
6720 this.#eventBus.dispatch("print", {
6728 if (!isInPresentationMode) {
6729 pdfViewer.currentScaleValue = value;
6733 this.#eventBus.dispatch("download", {
6738 pdfViewer.currentPageNumber = 1;
6741 pdfViewer.currentPageNumber = pdfViewer.pagesCount;
6744 pdfViewer.nextPage();
6747 pdfViewer.previousPage();
6750 if (!isInPresentationMode) {
6751 pdfViewer.increaseScale();
6755 if (!isInPresentationMode) {
6756 pdfViewer.decreaseScale();
6759 case "WillPrintFinished":
6760 this.#willPrintCapability?.resolve();
6761 this.#willPrintCapability = null;
6766 if (isInPresentationMode && detail.focus) {
6770 delete detail.siblings;
6771 const ids = siblings ? [id, ...siblings] : [id];
6772 for (const elementId of ids) {
6773 const element = document.querySelector(`[data-element-id="${elementId}"]`);
6775 element.dispatchEvent(new CustomEvent("updatefromsandbox", {
6779 this.#pdfDocument?.annotationStorage.setValue(elementId, detail);
6783 async #dispatchPageOpen(pageNumber, initialize = false) {
6784 const pdfDocument = this.#pdfDocument,
6785 visitedPages = this._visitedPages;
6787 this.#closeCapability = Promise.withResolvers();
6789 if (!this.#closeCapability) {
6792 const pageView = this.#pdfViewer.getPageView(pageNumber - 1);
6793 if (pageView?.renderingState !== RenderingStates.FINISHED) {
6794 this._pageOpenPending.add(pageNumber);
6797 this._pageOpenPending.delete(pageNumber);
6798 const actionsPromise = (async () => {
6799 const actions = await (!visitedPages.has(pageNumber) ? pageView.pdfPage?.getJSActions() : null);
6800 if (pdfDocument !== this.#pdfDocument) {
6803 await this.#scripting?.dispatchEventInSandbox({
6810 visitedPages.set(pageNumber, actionsPromise);
6812 async #dispatchPageClose(pageNumber) {
6813 const pdfDocument = this.#pdfDocument,
6814 visitedPages = this._visitedPages;
6815 if (!this.#closeCapability) {
6818 if (this._pageOpenPending.has(pageNumber)) {
6821 const actionsPromise = visitedPages.get(pageNumber);
6822 if (!actionsPromise) {
6825 visitedPages.set(pageNumber, null);
6826 await actionsPromise;
6827 if (pdfDocument !== this.#pdfDocument) {
6830 await this.#scripting?.dispatchEventInSandbox({
6837 this.#destroyCapability = Promise.withResolvers();
6838 if (this.#scripting) {
6839 throw new Error("#initScripting: Scripting already exists.");
6841 return this.#externalServices.createScripting();
6843 async #destroyScripting() {
6844 if (!this.#scripting) {
6845 this.#pdfDocument = null;
6846 this.#destroyCapability?.resolve();
6849 if (this.#closeCapability) {
6850 await Promise.race([this.#closeCapability.promise, new Promise(resolve => {
6851 setTimeout(resolve, 1000);
6852 })]).catch(() => {});
6853 this.#closeCapability = null;
6855 this.#pdfDocument = null;
6857 await this.#scripting.destroySandbox();
6859 this.#willPrintCapability?.reject(new Error("Scripting destroyed."));
6860 this.#willPrintCapability = null;
6861 this.#eventAbortController?.abort();
6862 this.#eventAbortController = null;
6863 this._pageOpenPending.clear();
6864 this._visitedPages.clear();
6865 this.#scripting = null;
6866 this.#ready = false;
6867 this.#destroyCapability?.resolve();
6871 ;// ./web/pdf_sidebar.js
6873 const SIDEBAR_WIDTH_VAR = "--sidebar-width";
6874 const SIDEBAR_MIN_WIDTH = 200;
6875 const SIDEBAR_RESIZING_CLASS = "sidebarResizing";
6876 const UI_NOTIFICATION_CLASS = "pdfSidebarNotification";
6880 #outerContainerWidth = null;
6887 this.isOpen = false;
6888 this.active = SidebarView.THUMBS;
6889 this.isInitialViewSet = false;
6890 this.isInitialEventDispatched = false;
6891 this.onToggled = null;
6892 this.onUpdateThumbnails = null;
6893 this.outerContainer = elements.outerContainer;
6894 this.sidebarContainer = elements.sidebarContainer;
6895 this.toggleButton = elements.toggleButton;
6896 this.resizer = elements.resizer;
6897 this.thumbnailButton = elements.thumbnailButton;
6898 this.outlineButton = elements.outlineButton;
6899 this.attachmentsButton = elements.attachmentsButton;
6900 this.layersButton = elements.layersButton;
6901 this.thumbnailView = elements.thumbnailView;
6902 this.outlineView = elements.outlineView;
6903 this.attachmentsView = elements.attachmentsView;
6904 this.layersView = elements.layersView;
6905 this._currentOutlineItemButton = elements.currentOutlineItemButton;
6906 this.eventBus = eventBus;
6907 this.#isRTL = l10n.getDirection() === "rtl";
6908 this.#addEventListeners();
6911 this.isInitialViewSet = false;
6912 this.isInitialEventDispatched = false;
6913 this.#hideUINotification(true);
6914 this.switchView(SidebarView.THUMBS);
6915 this.outlineButton.disabled = false;
6916 this.attachmentsButton.disabled = false;
6917 this.layersButton.disabled = false;
6918 this._currentOutlineItemButton.disabled = true;
6921 return this.isOpen ? this.active : SidebarView.NONE;
6923 setInitialView(view = SidebarView.NONE) {
6924 if (this.isInitialViewSet) {
6927 this.isInitialViewSet = true;
6928 if (view === SidebarView.NONE || view === SidebarView.UNKNOWN) {
6929 this.#dispatchEvent();
6932 this.switchView(view, true);
6933 if (!this.isInitialEventDispatched) {
6934 this.#dispatchEvent();
6937 switchView(view, forceOpen = false) {
6938 const isViewChanged = view !== this.active;
6939 let forceRendering = false;
6941 case SidebarView.NONE:
6946 case SidebarView.THUMBS:
6947 if (this.isOpen && isViewChanged) {
6948 forceRendering = true;
6951 case SidebarView.OUTLINE:
6952 if (this.outlineButton.disabled) {
6956 case SidebarView.ATTACHMENTS:
6957 if (this.attachmentsButton.disabled) {
6961 case SidebarView.LAYERS:
6962 if (this.layersButton.disabled) {
6967 console.error(`PDFSidebar.switchView: "${view}" is not a valid view.`);
6971 toggleCheckedBtn(this.thumbnailButton, view === SidebarView.THUMBS, this.thumbnailView);
6972 toggleCheckedBtn(this.outlineButton, view === SidebarView.OUTLINE, this.outlineView);
6973 toggleCheckedBtn(this.attachmentsButton, view === SidebarView.ATTACHMENTS, this.attachmentsView);
6974 toggleCheckedBtn(this.layersButton, view === SidebarView.LAYERS, this.layersView);
6975 if (forceOpen && !this.isOpen) {
6979 if (forceRendering) {
6980 this.onUpdateThumbnails();
6983 if (isViewChanged) {
6984 this.#dispatchEvent();
6992 toggleExpandedBtn(this.toggleButton, true);
6993 this.outerContainer.classList.add("sidebarMoving", "sidebarOpen");
6994 if (this.active === SidebarView.THUMBS) {
6995 this.onUpdateThumbnails();
6998 this.#dispatchEvent();
6999 this.#hideUINotification();
7005 this.isOpen = false;
7006 toggleExpandedBtn(this.toggleButton, false);
7007 this.outerContainer.classList.add("sidebarMoving");
7008 this.outerContainer.classList.remove("sidebarOpen");
7010 this.#dispatchEvent();
7011 if (evt?.detail > 0) {
7012 this.toggleButton.blur();
7015 toggle(evt = null) {
7023 if (this.isInitialViewSet) {
7024 this.isInitialEventDispatched ||= true;
7026 this.eventBus.dispatch("sidebarviewchanged", {
7028 view: this.visibleView
7031 #showUINotification() {
7032 this.toggleButton.setAttribute("data-l10n-id", "pdfjs-toggle-sidebar-notification-button");
7034 this.toggleButton.classList.add(UI_NOTIFICATION_CLASS);
7037 #hideUINotification(reset = false) {
7038 if (this.isOpen || reset) {
7039 this.toggleButton.classList.remove(UI_NOTIFICATION_CLASS);
7042 this.toggleButton.setAttribute("data-l10n-id", "pdfjs-toggle-sidebar-button");
7045 #addEventListeners() {
7050 this.sidebarContainer.addEventListener("transitionend", evt => {
7051 if (evt.target === this.sidebarContainer) {
7052 outerContainer.classList.remove("sidebarMoving");
7053 eventBus.dispatch("resize", {
7058 this.toggleButton.addEventListener("click", evt => {
7061 this.thumbnailButton.addEventListener("click", () => {
7062 this.switchView(SidebarView.THUMBS);
7064 this.outlineButton.addEventListener("click", () => {
7065 this.switchView(SidebarView.OUTLINE);
7067 this.outlineButton.addEventListener("dblclick", () => {
7068 eventBus.dispatch("toggleoutlinetree", {
7072 this.attachmentsButton.addEventListener("click", () => {
7073 this.switchView(SidebarView.ATTACHMENTS);
7075 this.layersButton.addEventListener("click", () => {
7076 this.switchView(SidebarView.LAYERS);
7078 this.layersButton.addEventListener("dblclick", () => {
7079 eventBus.dispatch("resetlayers", {
7083 this._currentOutlineItemButton.addEventListener("click", () => {
7084 eventBus.dispatch("currentoutlineitem", {
7088 const onTreeLoaded = (count, button, view) => {
7089 button.disabled = !count;
7091 this.#showUINotification();
7092 } else if (this.active === view) {
7093 this.switchView(SidebarView.THUMBS);
7096 eventBus._on("outlineloaded", evt => {
7097 onTreeLoaded(evt.outlineCount, this.outlineButton, SidebarView.OUTLINE);
7098 evt.currentOutlineItemPromise.then(enabled => {
7099 if (!this.isInitialViewSet) {
7102 this._currentOutlineItemButton.disabled = !enabled;
7105 eventBus._on("attachmentsloaded", evt => {
7106 onTreeLoaded(evt.attachmentsCount, this.attachmentsButton, SidebarView.ATTACHMENTS);
7108 eventBus._on("layersloaded", evt => {
7109 onTreeLoaded(evt.layersCount, this.layersButton, SidebarView.LAYERS);
7111 eventBus._on("presentationmodechanged", evt => {
7112 if (evt.state === PresentationModeState.NORMAL && this.visibleView === SidebarView.THUMBS) {
7113 this.onUpdateThumbnails();
7116 this.resizer.addEventListener("mousedown", evt => {
7117 if (evt.button !== 0) {
7120 outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
7121 this.#mouseAC = new AbortController();
7123 signal: this.#mouseAC.signal
7125 window.addEventListener("mousemove", this.#mouseMove.bind(this), opts);
7126 window.addEventListener("mouseup", this.#mouseUp.bind(this), opts);
7127 window.addEventListener("blur", this.#mouseUp.bind(this), opts);
7129 eventBus._on("resize", evt => {
7130 if (evt.source !== window) {
7133 this.#outerContainerWidth = null;
7138 this.#updateWidth(this.#width);
7141 outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
7142 const updated = this.#updateWidth(this.#width);
7143 Promise.resolve().then(() => {
7144 outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
7146 eventBus.dispatch("resize", {
7153 get outerContainerWidth() {
7154 return this.#outerContainerWidth ||= this.outerContainer.clientWidth;
7156 #updateWidth(width = 0) {
7157 const maxWidth = Math.floor(this.outerContainerWidth / 2);
7158 if (width > maxWidth) {
7161 if (width < SIDEBAR_MIN_WIDTH) {
7162 width = SIDEBAR_MIN_WIDTH;
7164 if (width === this.#width) {
7167 this.#width = width;
7168 docStyle.setProperty(SIDEBAR_WIDTH_VAR, `${width}px`);
7172 let width = evt.clientX;
7174 width = this.outerContainerWidth - width;
7176 this.#updateWidth(width);
7179 this.outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
7180 this.eventBus.dispatch("resize", {
7183 this.#mouseAC?.abort();
7184 this.#mouseAC = null;
7188 ;// ./web/pdf_thumbnail_view.js
7191 const DRAW_UPSCALE_FACTOR = 2;
7192 const MAX_NUM_SCALING_STEPS = 3;
7193 const THUMBNAIL_WIDTH = 98;
7194 class TempImageFactory {
7195 static #tempCanvas = null;
7196 static getCanvas(width, height) {
7197 const tempCanvas = this.#tempCanvas ||= document.createElement("canvas");
7198 tempCanvas.width = width;
7199 tempCanvas.height = height;
7200 const ctx = tempCanvas.getContext("2d", {
7204 ctx.fillStyle = "rgb(255, 255, 255)";
7205 ctx.fillRect(0, 0, width, height);
7207 return [tempCanvas, tempCanvas.getContext("2d")];
7209 static destroyCanvas() {
7210 const tempCanvas = this.#tempCanvas;
7212 tempCanvas.width = 0;
7213 tempCanvas.height = 0;
7215 this.#tempCanvas = null;
7218 class PDFThumbnailView {
7224 optionalContentConfigPromise,
7231 this.renderingId = "thumbnail" + id;
7232 this.pageLabel = null;
7233 this.pdfPage = null;
7235 this.viewport = defaultViewport;
7236 this.pdfPageRotate = defaultViewport.rotation;
7237 this._optionalContentConfigPromise = optionalContentConfigPromise || null;
7238 this.pageColors = pageColors || null;
7239 this.enableHWA = enableHWA || false;
7240 this.eventBus = eventBus;
7241 this.linkService = linkService;
7242 this.renderingQueue = renderingQueue;
7243 this.renderTask = null;
7244 this.renderingState = RenderingStates.INITIAL;
7246 const anchor = document.createElement("a");
7247 anchor.href = linkService.getAnchorUrl("#page=" + id);
7248 anchor.setAttribute("data-l10n-id", "pdfjs-thumb-page-title");
7249 anchor.setAttribute("data-l10n-args", this.#pageL10nArgs);
7250 anchor.onclick = function () {
7251 linkService.goToPage(id);
7254 this.anchor = anchor;
7255 const div = document.createElement("div");
7256 div.className = "thumbnail";
7257 div.setAttribute("data-page-number", this.id);
7260 const img = document.createElement("div");
7261 img.className = "thumbnailImage";
7262 this._placeholderImg = img;
7265 container.append(anchor);
7272 const ratio = width / height;
7273 this.canvasWidth = THUMBNAIL_WIDTH;
7274 this.canvasHeight = this.canvasWidth / ratio | 0;
7275 this.scale = this.canvasWidth / width;
7279 style.setProperty("--thumbnail-width", `${this.canvasWidth}px`);
7280 style.setProperty("--thumbnail-height", `${this.canvasHeight}px`);
7282 setPdfPage(pdfPage) {
7283 this.pdfPage = pdfPage;
7284 this.pdfPageRotate = pdfPage.rotate;
7285 const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
7286 this.viewport = pdfPage.getViewport({
7288 rotation: totalRotation
7293 this.cancelRendering();
7294 this.renderingState = RenderingStates.INITIAL;
7295 this.div.removeAttribute("data-loaded");
7296 this.image?.replaceWith(this._placeholderImg);
7299 this.image.removeAttribute("src");
7306 if (typeof rotation === "number") {
7307 this.rotation = rotation;
7309 const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
7310 this.viewport = this.viewport.clone({
7312 rotation: totalRotation
7317 if (this.renderTask) {
7318 this.renderTask.cancel();
7319 this.renderTask = null;
7323 #getPageDrawContext(upscaleFactor = 1, enableHWA = this.enableHWA) {
7324 const canvas = document.createElement("canvas");
7325 const ctx = canvas.getContext("2d", {
7327 willReadFrequently: !enableHWA
7329 const outputScale = new OutputScale();
7330 canvas.width = upscaleFactor * this.canvasWidth * outputScale.sx | 0;
7331 canvas.height = upscaleFactor * this.canvasHeight * outputScale.sy | 0;
7332 const transform = outputScale.scaled ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] : null;
7339 #convertCanvasToImage(canvas) {
7340 if (this.renderingState !== RenderingStates.FINISHED) {
7341 throw new Error("#convertCanvasToImage: Rendering has not finished.");
7343 const reducedCanvas = this.#reduceImage(canvas);
7344 const image = document.createElement("img");
7345 image.className = "thumbnailImage";
7346 image.setAttribute("data-l10n-id", "pdfjs-thumb-page-canvas");
7347 image.setAttribute("data-l10n-args", this.#pageL10nArgs);
7348 image.src = reducedCanvas.toDataURL();
7350 this.div.setAttribute("data-loaded", true);
7351 this._placeholderImg.replaceWith(image);
7352 reducedCanvas.width = 0;
7353 reducedCanvas.height = 0;
7355 async #finishRenderTask(renderTask, canvas, error = null) {
7356 if (renderTask === this.renderTask) {
7357 this.renderTask = null;
7359 if (error instanceof RenderingCancelledException) {
7362 this.renderingState = RenderingStates.FINISHED;
7363 this.#convertCanvasToImage(canvas);
7369 if (this.renderingState !== RenderingStates.INITIAL) {
7370 console.error("Must be in new state before drawing");
7377 this.renderingState = RenderingStates.FINISHED;
7378 throw new Error("pdfPage is not loaded");
7380 this.renderingState = RenderingStates.RUNNING;
7385 } = this.#getPageDrawContext(DRAW_UPSCALE_FACTOR);
7386 const drawViewport = this.viewport.clone({
7387 scale: DRAW_UPSCALE_FACTOR * this.scale
7389 const renderContinueCallback = cont => {
7390 if (!this.renderingQueue.isHighestPriority(this)) {
7391 this.renderingState = RenderingStates.PAUSED;
7392 this.resume = () => {
7393 this.renderingState = RenderingStates.RUNNING;
7400 const renderContext = {
7403 viewport: drawViewport,
7404 optionalContentConfigPromise: this._optionalContentConfigPromise,
7405 pageColors: this.pageColors
7407 const renderTask = this.renderTask = pdfPage.render(renderContext);
7408 renderTask.onContinue = renderContinueCallback;
7409 const resultPromise = renderTask.promise.then(() => this.#finishRenderTask(renderTask, canvas), error => this.#finishRenderTask(renderTask, canvas, error));
7410 resultPromise.finally(() => {
7413 this.eventBus.dispatch("thumbnailrendered", {
7415 pageNumber: this.id,
7416 pdfPage: this.pdfPage
7419 return resultPromise;
7421 setImage(pageView) {
7422 if (this.renderingState !== RenderingStates.INITIAL) {
7426 thumbnailCanvas: canvas,
7433 if (!this.pdfPage) {
7434 this.setPdfPage(pdfPage);
7436 if (scale < this.scale) {
7439 this.renderingState = RenderingStates.FINISHED;
7440 this.#convertCanvasToImage(canvas);
7446 } = this.#getPageDrawContext(1, true);
7447 if (img.width <= 2 * canvas.width) {
7448 ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
7451 let reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS;
7452 let reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS;
7453 const [reducedImage, reducedImageCtx] = TempImageFactory.getCanvas(reducedWidth, reducedHeight);
7454 while (reducedWidth > img.width || reducedHeight > img.height) {
7456 reducedHeight >>= 1;
7458 reducedImageCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, reducedWidth, reducedHeight);
7459 while (reducedWidth > 2 * canvas.width) {
7460 reducedImageCtx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, 0, 0, reducedWidth >> 1, reducedHeight >> 1);
7462 reducedHeight >>= 1;
7464 ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, 0, 0, canvas.width, canvas.height);
7467 get #pageL10nArgs() {
7468 return JSON.stringify({
7469 page: this.pageLabel ?? this.id
7472 setPageLabel(label) {
7473 this.pageLabel = typeof label === "string" ? label : null;
7474 this.anchor.setAttribute("data-l10n-args", this.#pageL10nArgs);
7475 if (this.renderingState !== RenderingStates.FINISHED) {
7478 this.image?.setAttribute("data-l10n-args", this.#pageL10nArgs);
7482 ;// ./web/pdf_thumbnail_viewer.js
7485 const THUMBNAIL_SCROLL_MARGIN = -19;
7486 const THUMBNAIL_SELECTED_CLASS = "selected";
7487 class PDFThumbnailViewer {
7497 this.container = container;
7498 this.eventBus = eventBus;
7499 this.linkService = linkService;
7500 this.renderingQueue = renderingQueue;
7501 this.pageColors = pageColors || null;
7502 this.enableHWA = enableHWA || false;
7503 this.scroll = watchScroll(this.container, this.#scrollUpdated.bind(this), abortSignal);
7507 this.renderingQueue.renderHighestPriority();
7509 getThumbnail(index) {
7510 return this._thumbnails[index];
7512 #getVisibleThumbs() {
7513 return getVisibleElements({
7514 scrollEl: this.container,
7515 views: this._thumbnails
7518 scrollThumbnailIntoView(pageNumber) {
7519 if (!this.pdfDocument) {
7522 const thumbnailView = this._thumbnails[pageNumber - 1];
7523 if (!thumbnailView) {
7524 console.error('scrollThumbnailIntoView: Invalid "pageNumber" parameter.');
7527 if (pageNumber !== this._currentPageNumber) {
7528 const prevThumbnailView = this._thumbnails[this._currentPageNumber - 1];
7529 prevThumbnailView.div.classList.remove(THUMBNAIL_SELECTED_CLASS);
7530 thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS);
7536 } = this.#getVisibleThumbs();
7537 if (views.length > 0) {
7538 let shouldScroll = false;
7539 if (pageNumber <= first.id || pageNumber >= last.id) {
7540 shouldScroll = true;
7546 if (id !== pageNumber) {
7549 shouldScroll = percent < 100;
7554 scrollIntoView(thumbnailView.div, {
7555 top: THUMBNAIL_SCROLL_MARGIN
7559 this._currentPageNumber = pageNumber;
7561 get pagesRotation() {
7562 return this._pagesRotation;
7564 set pagesRotation(rotation) {
7565 if (!isValidRotation(rotation)) {
7566 throw new Error("Invalid thumbnails rotation angle.");
7568 if (!this.pdfDocument) {
7571 if (this._pagesRotation === rotation) {
7574 this._pagesRotation = rotation;
7575 const updateArgs = {
7578 for (const thumbnail of this._thumbnails) {
7579 thumbnail.update(updateArgs);
7583 for (const thumbnail of this._thumbnails) {
7584 if (thumbnail.renderingState !== RenderingStates.FINISHED) {
7588 TempImageFactory.destroyCanvas();
7591 this._thumbnails = [];
7592 this._currentPageNumber = 1;
7593 this._pageLabels = null;
7594 this._pagesRotation = 0;
7595 this.container.textContent = "";
7597 setDocument(pdfDocument) {
7598 if (this.pdfDocument) {
7599 this.#cancelRendering();
7602 this.pdfDocument = pdfDocument;
7606 const firstPagePromise = pdfDocument.getPage(1);
7607 const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
7610 firstPagePromise.then(firstPdfPage => {
7611 const pagesCount = pdfDocument.numPages;
7612 const viewport = firstPdfPage.getViewport({
7615 for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
7616 const thumbnail = new PDFThumbnailView({
7617 container: this.container,
7618 eventBus: this.eventBus,
7620 defaultViewport: viewport.clone(),
7621 optionalContentConfigPromise,
7622 linkService: this.linkService,
7623 renderingQueue: this.renderingQueue,
7624 pageColors: this.pageColors,
7625 enableHWA: this.enableHWA
7627 this._thumbnails.push(thumbnail);
7629 this._thumbnails[0]?.setPdfPage(firstPdfPage);
7630 const thumbnailView = this._thumbnails[this._currentPageNumber - 1];
7631 thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS);
7632 }).catch(reason => {
7633 console.error("Unable to initialize thumbnail viewer", reason);
7636 #cancelRendering() {
7637 for (const thumbnail of this._thumbnails) {
7638 thumbnail.cancelRendering();
7641 setPageLabels(labels) {
7642 if (!this.pdfDocument) {
7646 this._pageLabels = null;
7647 } else if (!(Array.isArray(labels) && this.pdfDocument.numPages === labels.length)) {
7648 this._pageLabels = null;
7649 console.error("PDFThumbnailViewer_setPageLabels: Invalid page labels.");
7651 this._pageLabels = labels;
7653 for (let i = 0, ii = this._thumbnails.length; i < ii; i++) {
7654 this._thumbnails[i].setPageLabel(this._pageLabels?.[i] ?? null);
7657 async #ensurePdfPageLoaded(thumbView) {
7658 if (thumbView.pdfPage) {
7659 return thumbView.pdfPage;
7662 const pdfPage = await this.pdfDocument.getPage(thumbView.id);
7663 if (!thumbView.pdfPage) {
7664 thumbView.setPdfPage(pdfPage);
7668 console.error("Unable to get page for thumb view", reason);
7672 #getScrollAhead(visible) {
7673 if (visible.first?.id === 1) {
7675 } else if (visible.last?.id === this._thumbnails.length) {
7678 return this.scroll.down;
7681 const visibleThumbs = this.#getVisibleThumbs();
7682 const scrollAhead = this.#getScrollAhead(visibleThumbs);
7683 const thumbView = this.renderingQueue.getHighestPriority(visibleThumbs, this._thumbnails, scrollAhead);
7685 this.#ensurePdfPageLoaded(thumbView).then(() => {
7686 this.renderingQueue.renderView(thumbView);
7694 ;// ./web/annotation_editor_layer_builder.js
7697 class AnnotationEditorLayerBuilder {
7698 #annotationLayer = null;
7701 #structTreeLayer = null;
7704 constructor(options) {
7705 this.pdfPage = options.pdfPage;
7706 this.accessibilityManager = options.accessibilityManager;
7707 this.l10n = options.l10n;
7708 this.annotationEditorLayer = null;
7710 this._cancelled = false;
7711 this.#uiManager = options.uiManager;
7712 this.#annotationLayer = options.annotationLayer || null;
7713 this.#textLayer = options.textLayer || null;
7714 this.#drawLayer = options.drawLayer || null;
7715 this.#onAppend = options.onAppend || null;
7716 this.#structTreeLayer = options.structTreeLayer || null;
7722 if (intent !== "display") {
7725 if (this._cancelled) {
7728 const clonedViewport = viewport.clone({
7732 this.annotationEditorLayer.update({
7733 viewport: clonedViewport
7738 const div = this.div = document.createElement("div");
7739 div.className = "annotationEditorLayer";
7741 div.dir = this.#uiManager.direction;
7742 this.#onAppend?.(div);
7743 this.annotationEditorLayer = new AnnotationEditorLayer({
7744 uiManager: this.#uiManager,
7746 structTreeLayer: this.#structTreeLayer,
7747 accessibilityManager: this.accessibilityManager,
7748 pageIndex: this.pdfPage.pageNumber - 1,
7750 viewport: clonedViewport,
7751 annotationLayer: this.#annotationLayer,
7752 textLayer: this.#textLayer,
7753 drawLayer: this.#drawLayer
7755 const parameters = {
7756 viewport: clonedViewport,
7761 this.annotationEditorLayer.render(parameters);
7765 this._cancelled = true;
7769 this.annotationEditorLayer.destroy();
7775 this.annotationEditorLayer.pause(true);
7776 this.div.hidden = true;
7779 if (!this.div || this.annotationEditorLayer.isInvisible) {
7782 this.div.hidden = false;
7783 this.annotationEditorLayer.pause(false);
7787 ;// ./web/annotation_layer_builder.js
7790 class AnnotationLayerBuilder {
7792 #eventAbortController = null;
7797 annotationStorage = null,
7798 imageResourcesPath = "",
7800 enableScripting = false,
7801 hasJSActionsPromise = null,
7802 fieldObjectsPromise = null,
7803 annotationCanvasMap = null,
7804 accessibilityManager = null,
7805 annotationEditorUIManager = null,
7808 this.pdfPage = pdfPage;
7809 this.linkService = linkService;
7810 this.downloadManager = downloadManager;
7811 this.imageResourcesPath = imageResourcesPath;
7812 this.renderForms = renderForms;
7813 this.annotationStorage = annotationStorage;
7814 this.enableScripting = enableScripting;
7815 this._hasJSActionsPromise = hasJSActionsPromise || Promise.resolve(false);
7816 this._fieldObjectsPromise = fieldObjectsPromise || Promise.resolve(null);
7817 this._annotationCanvasMap = annotationCanvasMap;
7818 this._accessibilityManager = accessibilityManager;
7819 this._annotationEditorUIManager = annotationEditorUIManager;
7820 this.#onAppend = onAppend;
7821 this.annotationLayer = null;
7823 this._cancelled = false;
7824 this._eventBus = linkService.eventBus;
7829 structTreeLayer = null
7832 if (this._cancelled || !this.annotationLayer) {
7835 this.annotationLayer.update({
7836 viewport: viewport.clone({
7842 const [annotations, hasJSActions, fieldObjects] = await Promise.all([this.pdfPage.getAnnotations({
7844 }), this._hasJSActionsPromise, this._fieldObjectsPromise]);
7845 if (this._cancelled) {
7848 const div = this.div = document.createElement("div");
7849 div.className = "annotationLayer";
7850 this.#onAppend?.(div);
7851 if (annotations.length === 0) {
7855 this.annotationLayer = new AnnotationLayer({
7857 accessibilityManager: this._accessibilityManager,
7858 annotationCanvasMap: this._annotationCanvasMap,
7859 annotationEditorUIManager: this._annotationEditorUIManager,
7861 viewport: viewport.clone({
7866 await this.annotationLayer.render({
7868 imageResourcesPath: this.imageResourcesPath,
7869 renderForms: this.renderForms,
7870 linkService: this.linkService,
7871 downloadManager: this.downloadManager,
7872 annotationStorage: this.annotationStorage,
7873 enableScripting: this.enableScripting,
7877 if (this.linkService.isInPresentationMode) {
7878 this.#updatePresentationModeState(PresentationModeState.FULLSCREEN);
7880 if (!this.#eventAbortController) {
7881 this.#eventAbortController = new AbortController();
7882 this._eventBus?._on("presentationmodechanged", evt => {
7883 this.#updatePresentationModeState(evt.state);
7885 signal: this.#eventAbortController.signal
7890 this._cancelled = true;
7891 this.#eventAbortController?.abort();
7892 this.#eventAbortController = null;
7898 this.div.hidden = true;
7900 hasEditableAnnotations() {
7901 return !!this.annotationLayer?.hasEditableAnnotations();
7903 #updatePresentationModeState(state) {
7907 let disableFormElements = false;
7909 case PresentationModeState.FULLSCREEN:
7910 disableFormElements = true;
7912 case PresentationModeState.NORMAL:
7917 for (const section of this.div.childNodes) {
7918 if (section.hasAttribute("data-internal-link")) {
7921 section.inert = disableFormElements;
7926 ;// ./web/draw_layer_builder.js
7928 class DrawLayerBuilder {
7930 constructor(options) {
7931 this.pageIndex = options.pageIndex;
7936 if (intent !== "display" || this.#drawLayer || this._cancelled) {
7939 this.#drawLayer = new DrawLayer({
7940 pageIndex: this.pageIndex
7944 this._cancelled = true;
7945 if (!this.#drawLayer) {
7948 this.#drawLayer.destroy();
7949 this.#drawLayer = null;
7952 this.#drawLayer?.setParent(parent);
7955 return this.#drawLayer;
7959 ;// ./web/struct_tree_layer_builder.js
7961 const PDF_ROLE_TO_HTML_ROLE = {
7963 DocumentFragment: null,
7995 THead: "columnheader",
8003 const HEADING_PATTERN = /^H(\d+)$/;
8004 class StructTreeLayerBuilder {
8008 #elementAttributes = new Map();
8010 #elementsToAddToTextLayer = null;
8011 constructor(pdfPage, rawDims) {
8012 this.#promise = pdfPage.getStructTree();
8013 this.#rawDims = rawDims;
8016 if (this.#treePromise) {
8017 return this.#treePromise;
8023 } = Promise.withResolvers();
8024 this.#treePromise = promise;
8026 this.#treeDom = this.#walk(await this.#promise);
8030 this.#promise = null;
8031 this.#treeDom?.classList.add("structTree");
8032 resolve(this.#treeDom);
8035 async getAriaAttributes(annotationId) {
8037 await this.render();
8038 return this.#elementAttributes.get(annotationId);
8043 if (this.#treeDom && !this.#treeDom.hidden) {
8044 this.#treeDom.hidden = true;
8048 if (this.#treeDom?.hidden) {
8049 this.#treeDom.hidden = false;
8052 #setAttributes(structElement, htmlElement) {
8058 if (alt !== undefined) {
8060 const label = removeNullCharacters(alt);
8061 for (const child of structElement.children) {
8062 if (child.type === "annotation") {
8063 let attrs = this.#elementAttributes.get(child.id);
8066 this.#elementAttributes.set(child.id, attrs);
8068 attrs.set("aria-label", label);
8073 htmlElement.setAttribute("aria-label", label);
8076 if (id !== undefined) {
8077 htmlElement.setAttribute("aria-owns", id);
8079 if (lang !== undefined) {
8080 htmlElement.setAttribute("lang", removeNullCharacters(lang, true));
8083 #addImageInTextLayer(node, element) {
8089 const child = children?.[0];
8090 if (!this.#rawDims || !alt || !bbox || child?.type !== "content") {
8099 element.setAttribute("aria-owns", id);
8100 const img = document.createElement("span");
8101 (this.#elementsToAddToTextLayer ||= new Map()).set(id, img);
8102 img.setAttribute("role", "img");
8103 img.setAttribute("aria-label", removeNullCharacters(alt));
8109 const calc = "calc(var(--scale-factor)*";
8113 style.width = `${calc}${bbox[2] - bbox[0]}px)`;
8114 style.height = `${calc}${bbox[3] - bbox[1]}px)`;
8115 style.left = `${calc}${bbox[0] - pageX}px)`;
8116 style.top = `${calc}${pageHeight - bbox[3] + pageY}px)`;
8119 addElementsToTextLayer() {
8120 if (!this.#elementsToAddToTextLayer) {
8123 for (const [id, img] of this.#elementsToAddToTextLayer) {
8124 document.getElementById(id)?.append(img);
8126 this.#elementsToAddToTextLayer.clear();
8127 this.#elementsToAddToTextLayer = null;
8133 const element = document.createElement("span");
8134 if ("role" in node) {
8138 const match = role.match(HEADING_PATTERN);
8140 element.setAttribute("role", "heading");
8141 element.setAttribute("aria-level", match[1]);
8142 } else if (PDF_ROLE_TO_HTML_ROLE[role]) {
8143 element.setAttribute("role", PDF_ROLE_TO_HTML_ROLE[role]);
8145 if (role === "Figure" && this.#addImageInTextLayer(node, element)) {
8149 this.#setAttributes(node, element);
8150 if (node.children) {
8151 if (node.children.length === 1 && "id" in node.children[0]) {
8152 this.#setAttributes(node.children[0], element);
8154 for (const kid of node.children) {
8155 element.append(this.#walk(kid));
8163 ;// ./web/text_accessibility.js
8165 class TextAccessibilityManager {
8167 #textChildren = null;
8168 #textNodes = new Map();
8169 #waitingElements = new Map();
8170 setTextMapping(textDivs) {
8171 this.#textChildren = textDivs;
8173 static #compareElementPositions(e1, e2) {
8174 const rect1 = e1.getBoundingClientRect();
8175 const rect2 = e2.getBoundingClientRect();
8176 if (rect1.width === 0 && rect1.height === 0) {
8179 if (rect2.width === 0 && rect2.height === 0) {
8182 const top1 = rect1.y;
8183 const bot1 = rect1.y + rect1.height;
8184 const mid1 = rect1.y + rect1.height / 2;
8185 const top2 = rect2.y;
8186 const bot2 = rect2.y + rect2.height;
8187 const mid2 = rect2.y + rect2.height / 2;
8188 if (mid1 <= top2 && mid2 >= bot1) {
8191 if (mid2 <= top1 && mid1 >= bot2) {
8194 const centerX1 = rect1.x + rect1.width / 2;
8195 const centerX2 = rect2.x + rect2.width / 2;
8196 return centerX1 - centerX2;
8199 if (this.#enabled) {
8200 throw new Error("TextAccessibilityManager is already enabled.");
8202 if (!this.#textChildren) {
8203 throw new Error("Text divs and strings have not been set.");
8205 this.#enabled = true;
8206 this.#textChildren = this.#textChildren.slice();
8207 this.#textChildren.sort(TextAccessibilityManager.#compareElementPositions);
8208 if (this.#textNodes.size > 0) {
8209 const textChildren = this.#textChildren;
8210 for (const [id, nodeIndex] of this.#textNodes) {
8211 const element = document.getElementById(id);
8213 this.#textNodes.delete(id);
8216 this.#addIdToAriaOwns(id, textChildren[nodeIndex]);
8219 for (const [element, isRemovable] of this.#waitingElements) {
8220 this.addPointerInTextLayer(element, isRemovable);
8222 this.#waitingElements.clear();
8225 if (!this.#enabled) {
8228 this.#waitingElements.clear();
8229 this.#textChildren = null;
8230 this.#enabled = false;
8232 removePointerInTextLayer(element) {
8233 if (!this.#enabled) {
8234 this.#waitingElements.delete(element);
8237 const children = this.#textChildren;
8238 if (!children || children.length === 0) {
8244 const nodeIndex = this.#textNodes.get(id);
8245 if (nodeIndex === undefined) {
8248 const node = children[nodeIndex];
8249 this.#textNodes.delete(id);
8250 let owns = node.getAttribute("aria-owns");
8251 if (owns?.includes(id)) {
8252 owns = owns.split(" ").filter(x => x !== id).join(" ");
8254 node.setAttribute("aria-owns", owns);
8256 node.removeAttribute("aria-owns");
8257 node.setAttribute("role", "presentation");
8261 #addIdToAriaOwns(id, node) {
8262 const owns = node.getAttribute("aria-owns");
8263 if (!owns?.includes(id)) {
8264 node.setAttribute("aria-owns", owns ? `${owns} ${id}` : id);
8266 node.removeAttribute("role");
8268 addPointerInTextLayer(element, isRemovable) {
8275 if (!this.#enabled) {
8276 this.#waitingElements.set(element, isRemovable);
8280 this.removePointerInTextLayer(element);
8282 const children = this.#textChildren;
8283 if (!children || children.length === 0) {
8286 const index = binarySearchFirstItem(children, node => TextAccessibilityManager.#compareElementPositions(element, node) < 0);
8287 const nodeIndex = Math.max(0, index - 1);
8288 const child = children[nodeIndex];
8289 this.#addIdToAriaOwns(id, child);
8290 this.#textNodes.set(id, nodeIndex);
8291 const parent = child.parentNode;
8292 return parent?.classList.contains("markedContent") ? parent.id : null;
8294 moveElementInDOM(container, element, contentElement, isRemovable) {
8295 const id = this.addPointerInTextLayer(contentElement, isRemovable);
8296 if (!container.hasChildNodes()) {
8297 container.append(element);
8300 const children = Array.from(container.childNodes).filter(node => node !== element);
8301 if (children.length === 0) {
8304 const elementToCompare = contentElement || element;
8305 const index = binarySearchFirstItem(children, node => TextAccessibilityManager.#compareElementPositions(elementToCompare, node) < 0);
8307 children[0].before(element);
8309 children[index - 1].after(element);
8315 ;// ./web/text_highlighter.js
8316 class TextHighlighter {
8317 #eventAbortController = null;
8323 this.findController = findController;
8325 this.eventBus = eventBus;
8326 this.pageIdx = pageIndex;
8327 this.textDivs = null;
8328 this.textContentItemsStr = null;
8329 this.enabled = false;
8331 setTextMapping(divs, texts) {
8332 this.textDivs = divs;
8333 this.textContentItemsStr = texts;
8336 if (!this.textDivs || !this.textContentItemsStr) {
8337 throw new Error("Text divs and strings have not been set.");
8340 throw new Error("TextHighlighter is already enabled.");
8342 this.enabled = true;
8343 if (!this.#eventAbortController) {
8344 this.#eventAbortController = new AbortController();
8345 this.eventBus._on("updatetextlayermatches", evt => {
8346 if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) {
8347 this._updateMatches();
8350 signal: this.#eventAbortController.signal
8353 this._updateMatches();
8356 if (!this.enabled) {
8359 this.enabled = false;
8360 this.#eventAbortController?.abort();
8361 this.#eventAbortController = null;
8362 this._updateMatches(true);
8364 _convertMatches(matches, matchesLength) {
8373 const end = textContentItemsStr.length - 1;
8375 for (let m = 0, mm = matches.length; m < mm; m++) {
8376 let matchIdx = matches[m];
8377 while (i !== end && matchIdx >= iIndex + textContentItemsStr[i].length) {
8378 iIndex += textContentItemsStr[i].length;
8381 if (i === textContentItemsStr.length) {
8382 console.error("Could not find a matching mapping");
8387 offset: matchIdx - iIndex
8390 matchIdx += matchesLength[m];
8391 while (i !== end && matchIdx > iIndex + textContentItemsStr[i].length) {
8392 iIndex += textContentItemsStr[i].length;
8397 offset: matchIdx - iIndex
8403 _renderMatches(matches) {
8404 if (matches.length === 0) {
8412 textContentItemsStr,
8415 const isSelectedPage = pageIdx === findController.selected.pageIdx;
8416 const selectedMatchIdx = findController.selected.matchIdx;
8417 const highlightAll = findController.state.highlightAll;
8423 function beginText(begin, className) {
8424 const divIdx = begin.divIdx;
8425 textDivs[divIdx].textContent = "";
8426 return appendTextToDiv(divIdx, 0, begin.offset, className);
8428 function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
8429 let div = textDivs[divIdx];
8430 if (div.nodeType === Node.TEXT_NODE) {
8431 const span = document.createElement("span");
8434 textDivs[divIdx] = span;
8437 const content = textContentItemsStr[divIdx].substring(fromOffset, toOffset);
8438 const node = document.createTextNode(content);
8440 const span = document.createElement("span");
8441 span.className = `${className} appended`;
8444 if (className.includes("selected")) {
8447 } = span.getClientRects()[0];
8448 const parentLeft = div.getBoundingClientRect().left;
8449 return left - parentLeft;
8456 let i0 = selectedMatchIdx,
8460 i1 = matches.length;
8461 } else if (!isSelectedPage) {
8464 let lastDivIdx = -1;
8465 let lastOffset = -1;
8466 for (let i = i0; i < i1; i++) {
8467 const match = matches[i];
8468 const begin = match.begin;
8469 if (begin.divIdx === lastDivIdx && begin.offset === lastOffset) {
8472 lastDivIdx = begin.divIdx;
8473 lastOffset = begin.offset;
8474 const end = match.end;
8475 const isSelected = isSelectedPage && i === selectedMatchIdx;
8476 const highlightSuffix = isSelected ? " selected" : "";
8477 let selectedLeft = 0;
8478 if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
8479 if (prevEnd !== null) {
8480 appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
8484 appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
8486 if (begin.divIdx === end.divIdx) {
8487 selectedLeft = appendTextToDiv(begin.divIdx, begin.offset, end.offset, "highlight" + highlightSuffix);
8489 selectedLeft = appendTextToDiv(begin.divIdx, begin.offset, infinity.offset, "highlight begin" + highlightSuffix);
8490 for (let n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
8491 textDivs[n0].className = "highlight middle" + highlightSuffix;
8493 beginText(end, "highlight end" + highlightSuffix);
8497 findController.scrollMatchIntoView({
8498 element: textDivs[begin.divIdx],
8501 matchIndex: selectedMatchIdx
8506 appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
8509 _updateMatches(reset = false) {
8510 if (!this.enabled && !reset) {
8519 textContentItemsStr,
8522 let clearedUntilDivIdx = -1;
8523 for (const match of matches) {
8524 const begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
8525 for (let n = begin, end = match.end.divIdx; n <= end; n++) {
8526 const div = textDivs[n];
8527 div.textContent = textContentItemsStr[n];
8530 clearedUntilDivIdx = match.end.divIdx + 1;
8532 if (!findController?.highlightMatches || reset) {
8535 const pageMatches = findController.pageMatches[pageIdx] || null;
8536 const pageMatchesLength = findController.pageMatchesLength[pageIdx] || null;
8537 this.matches = this._convertMatches(pageMatches, pageMatchesLength);
8538 this._renderMatches(this.matches);
8542 ;// ./web/text_layer_builder.js
8545 class TextLayerBuilder {
8546 #enablePermissions = false;
8548 #renderingDone = false;
8550 static #textLayers = new Map();
8551 static #selectionChangeAbortController = null;
8555 accessibilityManager = null,
8556 enablePermissions = false,
8559 this.pdfPage = pdfPage;
8560 this.highlighter = highlighter;
8561 this.accessibilityManager = accessibilityManager;
8562 this.#enablePermissions = enablePermissions === true;
8563 this.#onAppend = onAppend;
8564 this.div = document.createElement("div");
8565 this.div.tabIndex = 0;
8566 this.div.className = "textLayer";
8570 textContentParams = null
8572 if (this.#renderingDone && this.#textLayer) {
8573 this.#textLayer.update({
8575 onBefore: this.hide.bind(this)
8581 this.#textLayer = new TextLayer({
8582 textContentSource: this.pdfPage.streamTextContent(textContentParams || {
8583 includeMarkedContent: true,
8584 disableNormalization: true
8586 container: this.div,
8592 } = this.#textLayer;
8593 this.highlighter?.setTextMapping(textDivs, textContentItemsStr);
8594 this.accessibilityManager?.setTextMapping(textDivs);
8595 await this.#textLayer.render();
8596 this.#renderingDone = true;
8597 const endOfContent = document.createElement("div");
8598 endOfContent.className = "endOfContent";
8599 this.div.append(endOfContent);
8600 this.#bindMouse(endOfContent);
8601 this.#onAppend?.(this.div);
8602 this.highlighter?.enable();
8603 this.accessibilityManager?.enable();
8606 if (!this.div.hidden && this.#renderingDone) {
8607 this.highlighter?.disable();
8608 this.div.hidden = true;
8612 if (this.div.hidden && this.#renderingDone) {
8613 this.div.hidden = false;
8614 this.highlighter?.enable();
8618 this.#textLayer?.cancel();
8619 this.#textLayer = null;
8620 this.highlighter?.disable();
8621 this.accessibilityManager?.disable();
8622 TextLayerBuilder.#removeGlobalSelectionListener(this.div);
8628 div.addEventListener("mousedown", () => {
8629 div.classList.add("selecting");
8631 div.addEventListener("copy", event => {
8632 if (!this.#enablePermissions) {
8633 const selection = document.getSelection();
8634 event.clipboardData.setData("text/plain", removeNullCharacters(normalizeUnicode(selection.toString())));
8638 TextLayerBuilder.#textLayers.set(div, end);
8639 TextLayerBuilder.#enableGlobalSelectionListener();
8641 static #removeGlobalSelectionListener(textLayerDiv) {
8642 this.#textLayers.delete(textLayerDiv);
8643 if (this.#textLayers.size === 0) {
8644 this.#selectionChangeAbortController?.abort();
8645 this.#selectionChangeAbortController = null;
8648 static #enableGlobalSelectionListener() {
8649 if (this.#selectionChangeAbortController) {
8652 this.#selectionChangeAbortController = new AbortController();
8655 } = this.#selectionChangeAbortController;
8656 const reset = (end, textLayer) => {
8657 textLayer.classList.remove("selecting");
8659 let isPointerDown = false;
8660 document.addEventListener("pointerdown", () => {
8661 isPointerDown = true;
8665 document.addEventListener("pointerup", () => {
8666 isPointerDown = false;
8667 this.#textLayers.forEach(reset);
8671 window.addEventListener("blur", () => {
8672 isPointerDown = false;
8673 this.#textLayers.forEach(reset);
8677 document.addEventListener("keyup", () => {
8678 if (!isPointerDown) {
8679 this.#textLayers.forEach(reset);
8684 document.addEventListener("selectionchange", () => {
8685 const selection = document.getSelection();
8686 if (selection.rangeCount === 0) {
8687 this.#textLayers.forEach(reset);
8690 const activeTextLayers = new Set();
8691 for (let i = 0; i < selection.rangeCount; i++) {
8692 const range = selection.getRangeAt(i);
8693 for (const textLayerDiv of this.#textLayers.keys()) {
8694 if (!activeTextLayers.has(textLayerDiv) && range.intersectsNode(textLayerDiv)) {
8695 activeTextLayers.add(textLayerDiv);
8699 for (const [textLayerDiv, endDiv] of this.#textLayers) {
8700 if (activeTextLayers.has(textLayerDiv)) {
8701 textLayerDiv.classList.add("selecting");
8703 reset(endDiv, textLayerDiv);
8712 ;// ./web/pdf_page_view.js
8726 const DEFAULT_LAYER_PROPERTIES = null;
8727 const LAYERS_ORDER = new Map([["canvasWrapper", 0], ["textLayer", 1], ["annotationLayer", 2], ["annotationEditorLayer", 3], ["xfaLayer", 3]]);
8729 #annotationMode = AnnotationMode.ENABLE_FORMS;
8730 #canvasWrapper = null;
8732 #hasRestrictedScaling = false;
8734 #layerProperties = null;
8736 #originalViewport = null;
8737 #previousRotation = null;
8740 #renderError = null;
8741 #renderingState = RenderingStates.INITIAL;
8742 #textLayerMode = TextLayerMode.ENABLE;
8743 #useThumbnailCanvas = {
8744 directDrawing: true,
8745 initialOptionalContent: true,
8746 regularAnnotations: true
8748 #layers = [null, null, null, null];
8749 constructor(options) {
8750 const container = options.container;
8751 const defaultViewport = options.defaultViewport;
8752 this.id = options.id;
8753 this.renderingId = "page" + this.id;
8754 this.#layerProperties = options.layerProperties || DEFAULT_LAYER_PROPERTIES;
8755 this.pdfPage = null;
8756 this.pageLabel = null;
8758 this.scale = options.scale || DEFAULT_SCALE;
8759 this.viewport = defaultViewport;
8760 this.pdfPageRotate = defaultViewport.rotation;
8761 this._optionalContentConfigPromise = options.optionalContentConfigPromise || null;
8762 this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
8763 this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
8764 this.imageResourcesPath = options.imageResourcesPath || "";
8765 this.maxCanvasPixels = options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels");
8766 this.pageColors = options.pageColors || null;
8767 this.#enableHWA = options.enableHWA || false;
8768 this.eventBus = options.eventBus;
8769 this.renderingQueue = options.renderingQueue;
8770 this.l10n = options.l10n;
8771 this.renderTask = null;
8773 this._annotationCanvasMap = null;
8774 this.annotationLayer = null;
8775 this.annotationEditorLayer = null;
8776 this.textLayer = null;
8777 this.xfaLayer = null;
8778 this.structTreeLayer = null;
8779 this.drawLayer = null;
8780 const div = document.createElement("div");
8781 div.className = "page";
8782 div.setAttribute("data-page-number", this.id);
8783 div.setAttribute("role", "region");
8784 div.setAttribute("data-l10n-id", "pdfjs-page-landmark");
8785 div.setAttribute("data-l10n-args", JSON.stringify({
8789 this.#setDimensions();
8790 container?.append(div);
8792 #addLayer(div, name) {
8793 const pos = LAYERS_ORDER.get(name);
8794 const oldDiv = this.#layers[pos];
8795 this.#layers[pos] = div;
8797 oldDiv.replaceWith(div);
8800 for (let i = pos - 1; i >= 0; i--) {
8801 const layer = this.#layers[i];
8807 this.div.prepend(div);
8809 get renderingState() {
8810 return this.#renderingState;
8812 set renderingState(state) {
8813 if (state === this.#renderingState) {
8816 this.#renderingState = state;
8817 if (this.#loadingId) {
8818 clearTimeout(this.#loadingId);
8819 this.#loadingId = null;
8822 case RenderingStates.PAUSED:
8823 this.div.classList.remove("loading");
8825 case RenderingStates.RUNNING:
8826 this.div.classList.add("loadingIcon");
8827 this.#loadingId = setTimeout(() => {
8828 this.div.classList.add("loading");
8829 this.#loadingId = null;
8832 case RenderingStates.INITIAL:
8833 case RenderingStates.FINISHED:
8834 this.div.classList.remove("loadingIcon", "loading");
8843 if (this.#previousRotation === viewport.rotation) {
8846 this.#previousRotation = viewport.rotation;
8848 setLayerDimensions(this.div, viewport, true, false);
8850 setPdfPage(pdfPage) {
8851 this.pdfPage = pdfPage;
8852 this.pdfPageRotate = pdfPage.rotate;
8853 const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
8854 this.viewport = pdfPage.getViewport({
8855 scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,
8856 rotation: totalRotation
8858 this.#setDimensions();
8863 this.pdfPage?.cleanup();
8865 hasEditableAnnotations() {
8866 return !!this.annotationLayer?.hasEditableAnnotations();
8868 get _textHighlighter() {
8869 return shadow(this, "_textHighlighter", new TextHighlighter({
8870 pageIndex: this.id - 1,
8871 eventBus: this.eventBus,
8872 findController: this.#layerProperties.findController
8875 #dispatchLayerRendered(name, error) {
8876 this.eventBus.dispatch(name, {
8878 pageNumber: this.id,
8882 async #renderAnnotationLayer() {
8885 await this.annotationLayer.render({
8886 viewport: this.viewport,
8888 structTreeLayer: this.structTreeLayer
8891 console.error("#renderAnnotationLayer:", ex);
8894 this.#dispatchLayerRendered("annotationlayerrendered", error);
8897 async #renderAnnotationEditorLayer() {
8900 await this.annotationEditorLayer.render({
8901 viewport: this.viewport,
8905 console.error("#renderAnnotationEditorLayer:", ex);
8908 this.#dispatchLayerRendered("annotationeditorlayerrendered", error);
8911 async #renderDrawLayer() {
8913 await this.drawLayer.render({
8917 console.error("#renderDrawLayer:", ex);
8920 async #renderXfaLayer() {
8923 const result = await this.xfaLayer.render({
8924 viewport: this.viewport,
8927 if (result?.textDivs && this._textHighlighter) {
8928 this.#buildXfaTextContentItems(result.textDivs);
8931 console.error("#renderXfaLayer:", ex);
8934 if (this.xfaLayer?.div) {
8936 this.#addLayer(this.xfaLayer.div, "xfaLayer");
8939 this.#dispatchLayerRendered("xfalayerrendered", error);
8942 async #renderTextLayer() {
8943 if (!this.textLayer) {
8948 await this.textLayer.render({
8949 viewport: this.viewport
8952 if (ex instanceof AbortException) {
8955 console.error("#renderTextLayer:", ex);
8958 this.#dispatchLayerRendered("textlayerrendered", error);
8959 this.#renderStructTreeLayer();
8961 async #renderStructTreeLayer() {
8962 if (!this.textLayer) {
8965 const treeDom = await this.structTreeLayer?.render();
8968 this.structTreeLayer?.addElementsToTextLayer();
8969 if (this.canvas && treeDom.parentNode !== this.canvas) {
8970 this.canvas.append(treeDom);
8974 this.structTreeLayer?.show();
8976 async #buildXfaTextContentItems(textDivs) {
8977 const text = await this.pdfPage.getTextContent();
8979 for (const item of text.items) {
8980 items.push(item.str);
8982 this._textHighlighter.setTextMapping(textDivs, items);
8983 this._textHighlighter.enable();
8993 canvas.width = canvas.height = 0;
8995 this.#originalViewport = null;
8998 keepAnnotationLayer = false,
8999 keepAnnotationEditorLayer = false,
9000 keepXfaLayer = false,
9001 keepTextLayer = false,
9002 keepCanvasWrapper = false
9004 this.cancelRendering({
9005 keepAnnotationLayer,
9006 keepAnnotationEditorLayer,
9010 this.renderingState = RenderingStates.INITIAL;
9011 const div = this.div;
9012 const childNodes = div.childNodes,
9013 annotationLayerNode = keepAnnotationLayer && this.annotationLayer?.div || null,
9014 annotationEditorLayerNode = keepAnnotationEditorLayer && this.annotationEditorLayer?.div || null,
9015 xfaLayerNode = keepXfaLayer && this.xfaLayer?.div || null,
9016 textLayerNode = keepTextLayer && this.textLayer?.div || null,
9017 canvasWrapperNode = keepCanvasWrapper && this.#canvasWrapper || null;
9018 for (let i = childNodes.length - 1; i >= 0; i--) {
9019 const node = childNodes[i];
9021 case annotationLayerNode:
9022 case annotationEditorLayerNode:
9025 case canvasWrapperNode:
9029 const layerIndex = this.#layers.indexOf(node);
9030 if (layerIndex >= 0) {
9031 this.#layers[layerIndex] = null;
9034 div.removeAttribute("data-loaded");
9035 if (annotationLayerNode) {
9036 this.annotationLayer.hide();
9038 if (annotationEditorLayerNode) {
9039 this.annotationEditorLayer.hide();
9042 this.xfaLayer.hide();
9044 if (textLayerNode) {
9045 this.textLayer.hide();
9047 this.structTreeLayer?.hide();
9048 if (!keepCanvasWrapper && this.#canvasWrapper) {
9049 this.#canvasWrapper = null;
9050 this.#resetCanvas();
9053 toggleEditingMode(isEditing) {
9054 this.#isEditing = isEditing;
9055 if (!this.hasEditableAnnotations()) {
9059 keepAnnotationLayer: true,
9060 keepAnnotationEditorLayer: true,
9062 keepTextLayer: true,
9063 keepCanvasWrapper: true
9069 optionalContentConfigPromise = null,
9072 this.scale = scale || this.scale;
9073 if (typeof rotation === "number") {
9074 this.rotation = rotation;
9076 if (optionalContentConfigPromise instanceof Promise) {
9077 this._optionalContentConfigPromise = optionalContentConfigPromise;
9078 optionalContentConfigPromise.then(optionalContentConfig => {
9079 if (optionalContentConfigPromise !== this._optionalContentConfigPromise) {
9082 this.#useThumbnailCanvas.initialOptionalContent = optionalContentConfig.hasInitialVisibility;
9085 this.#useThumbnailCanvas.directDrawing = true;
9086 const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
9087 this.viewport = this.viewport.clone({
9088 scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,
9089 rotation: totalRotation
9091 this.#setDimensions();
9093 let onlyCssZoom = false;
9094 if (this.#hasRestrictedScaling) {
9095 if (this.maxCanvasPixels > 0) {
9103 } = this.outputScale;
9104 onlyCssZoom = (Math.floor(width) * sx | 0) * (Math.floor(height) * sy | 0) > this.maxCanvasPixels;
9107 const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000;
9108 if (postponeDrawing || onlyCssZoom) {
9109 if (postponeDrawing && !onlyCssZoom && this.renderingState !== RenderingStates.FINISHED) {
9110 this.cancelRendering({
9111 keepAnnotationLayer: true,
9112 keepAnnotationEditorLayer: true,
9114 keepTextLayer: true,
9115 cancelExtraDelay: drawingDelay
9117 this.renderingState = RenderingStates.FINISHED;
9118 this.#useThumbnailCanvas.directDrawing = false;
9121 redrawAnnotationLayer: true,
9122 redrawAnnotationEditorLayer: true,
9123 redrawXfaLayer: true,
9124 redrawTextLayer: !postponeDrawing,
9125 hideTextLayer: postponeDrawing
9127 if (postponeDrawing) {
9130 this.eventBus.dispatch("pagerendered", {
9132 pageNumber: this.id,
9134 timestamp: performance.now(),
9135 error: this.#renderError
9140 this.cssTransform({});
9142 keepAnnotationLayer: true,
9143 keepAnnotationEditorLayer: true,
9145 keepTextLayer: true,
9146 keepCanvasWrapper: true
9150 keepAnnotationLayer = false,
9151 keepAnnotationEditorLayer = false,
9152 keepXfaLayer = false,
9153 keepTextLayer = false,
9154 cancelExtraDelay = 0
9156 if (this.renderTask) {
9157 this.renderTask.cancel(cancelExtraDelay);
9158 this.renderTask = null;
9161 if (this.textLayer && (!keepTextLayer || !this.textLayer.div)) {
9162 this.textLayer.cancel();
9163 this.textLayer = null;
9165 if (this.annotationLayer && (!keepAnnotationLayer || !this.annotationLayer.div)) {
9166 this.annotationLayer.cancel();
9167 this.annotationLayer = null;
9168 this._annotationCanvasMap = null;
9170 if (this.structTreeLayer && !this.textLayer) {
9171 this.structTreeLayer = null;
9173 if (this.annotationEditorLayer && (!keepAnnotationEditorLayer || !this.annotationEditorLayer.div)) {
9174 if (this.drawLayer) {
9175 this.drawLayer.cancel();
9176 this.drawLayer = null;
9178 this.annotationEditorLayer.cancel();
9179 this.annotationEditorLayer = null;
9181 if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {
9182 this.xfaLayer.cancel();
9183 this.xfaLayer = null;
9184 this._textHighlighter?.disable();
9188 redrawAnnotationLayer = false,
9189 redrawAnnotationEditorLayer = false,
9190 redrawXfaLayer = false,
9191 redrawTextLayer = false,
9192 hideTextLayer = false
9200 const originalViewport = this.#originalViewport;
9201 if (this.viewport !== originalViewport) {
9202 const relativeRotation = (360 + this.viewport.rotation - originalViewport.rotation) % 360;
9203 if (relativeRotation === 90 || relativeRotation === 270) {
9208 const scaleX = height / width;
9209 const scaleY = width / height;
9210 canvas.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX},${scaleY})`;
9212 canvas.style.transform = relativeRotation === 0 ? "" : `rotate(${relativeRotation}deg)`;
9215 if (redrawAnnotationLayer && this.annotationLayer) {
9216 this.#renderAnnotationLayer();
9218 if (redrawAnnotationEditorLayer && this.annotationEditorLayer) {
9219 if (this.drawLayer) {
9220 this.#renderDrawLayer();
9222 this.#renderAnnotationEditorLayer();
9224 if (redrawXfaLayer && this.xfaLayer) {
9225 this.#renderXfaLayer();
9227 if (this.textLayer) {
9228 if (hideTextLayer) {
9229 this.textLayer.hide();
9230 this.structTreeLayer?.hide();
9231 } else if (redrawTextLayer) {
9232 this.#renderTextLayer();
9237 return this.viewport.width;
9240 return this.viewport.height;
9242 getPagePoint(x, y) {
9243 return this.viewport.convertToPdfPoint(x, y);
9245 async #finishRenderTask(renderTask, error = null) {
9246 if (renderTask === this.renderTask) {
9247 this.renderTask = null;
9249 if (error instanceof RenderingCancelledException) {
9250 this.#renderError = null;
9253 this.#renderError = error;
9254 this.renderingState = RenderingStates.FINISHED;
9255 this.#useThumbnailCanvas.regularAnnotations = !renderTask.separateAnnots;
9256 this.eventBus.dispatch("pagerendered", {
9258 pageNumber: this.id,
9259 cssTransform: false,
9260 timestamp: performance.now(),
9261 error: this.#renderError
9268 if (this.renderingState !== RenderingStates.INITIAL) {
9269 console.error("Must be in new state before drawing");
9280 this.renderingState = RenderingStates.FINISHED;
9281 throw new Error("pdfPage is not loaded");
9283 this.renderingState = RenderingStates.RUNNING;
9284 let canvasWrapper = this.#canvasWrapper;
9285 if (!canvasWrapper) {
9286 canvasWrapper = this.#canvasWrapper = document.createElement("div");
9287 canvasWrapper.classList.add("canvasWrapper");
9288 this.#addLayer(canvasWrapper, "canvasWrapper");
9290 if (!this.textLayer && this.#textLayerMode !== TextLayerMode.DISABLE && !pdfPage.isPureXfa) {
9291 this._accessibilityManager ||= new TextAccessibilityManager();
9292 this.textLayer = new TextLayerBuilder({
9294 highlighter: this._textHighlighter,
9295 accessibilityManager: this._accessibilityManager,
9296 enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS,
9297 onAppend: textLayerDiv => {
9299 this.#addLayer(textLayerDiv, "textLayer");
9304 if (!this.annotationLayer && this.#annotationMode !== AnnotationMode.DISABLE) {
9307 annotationEditorUIManager,
9310 fieldObjectsPromise,
9311 hasJSActionsPromise,
9313 } = this.#layerProperties;
9314 this._annotationCanvasMap ||= new Map();
9315 this.annotationLayer = new AnnotationLayerBuilder({
9318 imageResourcesPath: this.imageResourcesPath,
9319 renderForms: this.#annotationMode === AnnotationMode.ENABLE_FORMS,
9323 hasJSActionsPromise,
9324 fieldObjectsPromise,
9325 annotationCanvasMap: this._annotationCanvasMap,
9326 accessibilityManager: this._accessibilityManager,
9327 annotationEditorUIManager,
9328 onAppend: annotationLayerDiv => {
9329 this.#addLayer(annotationLayerDiv, "annotationLayer");
9333 const renderContinueCallback = cont => {
9334 showCanvas?.(false);
9335 if (this.renderingQueue && !this.renderingQueue.isHighestPriority(this)) {
9336 this.renderingState = RenderingStates.PAUSED;
9337 this.resume = () => {
9338 this.renderingState = RenderingStates.RUNNING;
9349 const canvas = document.createElement("canvas");
9350 canvas.setAttribute("role", "presentation");
9351 const hasHCM = !!(pageColors?.background && pageColors?.foreground);
9352 const prevCanvas = this.canvas;
9353 const updateOnFirstShow = !prevCanvas && !hasHCM;
9354 this.canvas = canvas;
9355 this.#originalViewport = viewport;
9356 let showCanvas = isLastShow => {
9357 if (updateOnFirstShow) {
9358 canvasWrapper.prepend(canvas);
9366 prevCanvas.replaceWith(canvas);
9367 prevCanvas.width = prevCanvas.height = 0;
9369 canvasWrapper.prepend(canvas);
9373 const ctx = canvas.getContext("2d", {
9375 willReadFrequently: !this.#enableHWA
9377 const outputScale = this.outputScale = new OutputScale();
9378 if (this.maxCanvasPixels > 0) {
9379 const pixelsInViewport = width * height;
9380 const maxScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport);
9381 if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
9382 outputScale.sx = maxScale;
9383 outputScale.sy = maxScale;
9384 this.#hasRestrictedScaling = true;
9386 this.#hasRestrictedScaling = false;
9389 const sfx = approximateFraction(outputScale.sx);
9390 const sfy = approximateFraction(outputScale.sy);
9391 const canvasWidth = canvas.width = floorToDivide(calcRound(width * outputScale.sx), sfx[0]);
9392 const canvasHeight = canvas.height = floorToDivide(calcRound(height * outputScale.sy), sfy[0]);
9393 const pageWidth = floorToDivide(calcRound(width), sfx[1]);
9394 const pageHeight = floorToDivide(calcRound(height), sfy[1]);
9395 outputScale.sx = canvasWidth / pageWidth;
9396 outputScale.sy = canvasHeight / pageHeight;
9397 if (this.#scaleRoundX !== sfx[1]) {
9398 div.style.setProperty("--scale-round-x", `${sfx[1]}px`);
9399 this.#scaleRoundX = sfx[1];
9401 if (this.#scaleRoundY !== sfy[1]) {
9402 div.style.setProperty("--scale-round-y", `${sfy[1]}px`);
9403 this.#scaleRoundY = sfy[1];
9405 const transform = outputScale.scaled ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] : null;
9406 const renderContext = {
9410 annotationMode: this.#annotationMode,
9411 optionalContentConfigPromise: this._optionalContentConfigPromise,
9412 annotationCanvasMap: this._annotationCanvasMap,
9414 isEditing: this.#isEditing
9416 const renderTask = this.renderTask = pdfPage.render(renderContext);
9417 renderTask.onContinue = renderContinueCallback;
9418 const resultPromise = renderTask.promise.then(async () => {
9420 await this.#finishRenderTask(renderTask);
9421 this.structTreeLayer ||= new StructTreeLayerBuilder(pdfPage, viewport.rawDims);
9422 this.#renderTextLayer();
9423 if (this.annotationLayer) {
9424 await this.#renderAnnotationLayer();
9427 annotationEditorUIManager
9428 } = this.#layerProperties;
9429 if (!annotationEditorUIManager) {
9432 this.drawLayer ||= new DrawLayerBuilder({
9435 await this.#renderDrawLayer();
9436 this.drawLayer.setParent(canvasWrapper);
9437 this.annotationEditorLayer ||= new AnnotationEditorLayerBuilder({
9438 uiManager: annotationEditorUIManager,
9441 structTreeLayer: this.structTreeLayer,
9442 accessibilityManager: this._accessibilityManager,
9443 annotationLayer: this.annotationLayer?.annotationLayer,
9444 textLayer: this.textLayer,
9445 drawLayer: this.drawLayer.getDrawLayer(),
9446 onAppend: annotationEditorLayerDiv => {
9447 this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer");
9450 this.#renderAnnotationEditorLayer();
9452 if (!(error instanceof RenderingCancelledException)) {
9455 prevCanvas?.remove();
9456 this.#resetCanvas();
9458 return this.#finishRenderTask(renderTask, error);
9460 if (pdfPage.isPureXfa) {
9461 if (!this.xfaLayer) {
9465 } = this.#layerProperties;
9466 this.xfaLayer = new XfaLayerBuilder({
9472 this.#renderXfaLayer();
9474 div.setAttribute("data-loaded", true);
9475 this.eventBus.dispatch("pagerender", {
9479 return resultPromise;
9481 setPageLabel(label) {
9482 this.pageLabel = typeof label === "string" ? label : null;
9483 this.div.setAttribute("data-l10n-args", JSON.stringify({
9484 page: this.pageLabel ?? this.id
9486 if (this.pageLabel !== null) {
9487 this.div.setAttribute("data-page-label", this.pageLabel);
9489 this.div.removeAttribute("data-page-label");
9492 get thumbnailCanvas() {
9495 initialOptionalContent,
9497 } = this.#useThumbnailCanvas;
9498 return directDrawing && initialOptionalContent && regularAnnotations ? this.canvas : null;
9502 ;// ./web/pdf_viewer.js
9509 const DEFAULT_CACHE_SIZE = 10;
9510 const PagesCountLimit = {
9511 FORCE_SCROLL_MODE_PAGE: 10000,
9512 FORCE_LAZY_PAGE_INIT: 5000,
9513 PAUSE_EAGER_PAGE_INIT: 250
9515 function isValidAnnotationEditorMode(mode) {
9516 return Object.values(AnnotationEditorType).includes(mode) && mode !== AnnotationEditorType.DISABLE;
9518 class PDFPageViewBuffer {
9525 const buf = this.#buf;
9526 if (buf.has(view)) {
9530 if (buf.size > this.#size) {
9531 this.#destroyFirstView();
9534 resize(newSize, idsToKeep = null) {
9535 this.#size = newSize;
9536 const buf = this.#buf;
9538 const ii = buf.size;
9540 for (const view of buf) {
9541 if (idsToKeep.has(view.id)) {
9550 while (buf.size > this.#size) {
9551 this.#destroyFirstView();
9555 return this.#buf.has(view);
9557 [Symbol.iterator]() {
9558 return this.#buf.keys();
9560 #destroyFirstView() {
9561 const firstView = this.#buf.keys().next().value;
9562 firstView?.destroy();
9563 this.#buf.delete(firstView);
9568 #altTextManager = null;
9569 #annotationEditorHighlightColors = null;
9570 #annotationEditorMode = AnnotationEditorType.NONE;
9571 #annotationEditorUIManager = null;
9572 #annotationMode = AnnotationMode.ENABLE_FORMS;
9573 #containerTopLeft = null;
9574 #editorUndoBar = null;
9576 #enableHighlightFloatingButton = false;
9577 #enablePermissions = false;
9578 #enableUpdatedAddImage = false;
9579 #enableNewAltTextWhenAddingImage = false;
9580 #eventAbortController = null;
9582 #switchAnnotationEditorModeAC = null;
9583 #switchAnnotationEditorModeTimeoutId = null;
9584 #getAllTextInProgress = false;
9585 #hiddenCopyElement = null;
9586 #interruptCopyCondition = false;
9587 #previousContainerHeight = 0;
9588 #resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this));
9589 #scrollModePageState = null;
9590 #scaleTimeoutId = null;
9591 #supportsPinchToZoom = true;
9592 #textLayerMode = TextLayerMode.ENABLE;
9593 constructor(options) {
9594 const viewerVersion = "5.0.71";
9595 if (version !== viewerVersion) {
9596 throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`);
9598 this.container = options.container;
9599 this.viewer = options.viewer || options.container.firstElementChild;
9600 this.#resizeObserver.observe(this.container);
9601 this.eventBus = options.eventBus;
9602 this.linkService = options.linkService || new SimpleLinkService();
9603 this.downloadManager = options.downloadManager || null;
9604 this.findController = options.findController || null;
9605 this.#altTextManager = options.altTextManager || null;
9606 this.#editorUndoBar = options.editorUndoBar || null;
9607 if (this.findController) {
9608 this.findController.onIsPageVisible = pageNumber => this._getVisiblePages().ids.has(pageNumber);
9610 this._scriptingManager = options.scriptingManager || null;
9611 this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
9612 this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
9613 this.#annotationEditorMode = options.annotationEditorMode ?? AnnotationEditorType.NONE;
9614 this.#annotationEditorHighlightColors = options.annotationEditorHighlightColors || null;
9615 this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true;
9616 this.#enableUpdatedAddImage = options.enableUpdatedAddImage === true;
9617 this.#enableNewAltTextWhenAddingImage = options.enableNewAltTextWhenAddingImage === true;
9618 this.imageResourcesPath = options.imageResourcesPath || "";
9619 this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
9620 this.maxCanvasPixels = options.maxCanvasPixels;
9621 this.l10n = options.l10n;
9622 this.#enablePermissions = options.enablePermissions || false;
9623 this.pageColors = options.pageColors || null;
9624 this.#mlManager = options.mlManager || null;
9625 this.#enableHWA = options.enableHWA || false;
9626 this.#supportsPinchToZoom = options.supportsPinchToZoom !== false;
9627 this.defaultRenderingQueue = !options.renderingQueue;
9628 this.renderingQueue = options.renderingQueue;
9632 abortSignal?.addEventListener("abort", () => {
9633 this.#resizeObserver.disconnect();
9634 this.#resizeObserver = null;
9638 this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this), abortSignal);
9639 this.presentationModeState = PresentationModeState.UNKNOWN;
9641 this.#updateContainerHeightCss();
9642 this.eventBus._on("thumbnailrendered", ({
9646 const pageView = this._pages[pageNumber - 1];
9647 if (!this.#buffer.has(pageView)) {
9653 return this._pages.length;
9655 getPageView(index) {
9656 return this._pages[index];
9658 getCachedPageViews() {
9659 return new Set(this.#buffer);
9661 get pageViewsReady() {
9662 return this._pages.every(pageView => pageView?.pdfPage);
9665 return this.#annotationMode === AnnotationMode.ENABLE_FORMS;
9667 get enableScripting() {
9668 return !!this._scriptingManager;
9670 get currentPageNumber() {
9671 return this._currentPageNumber;
9673 set currentPageNumber(val) {
9674 if (!Number.isInteger(val)) {
9675 throw new Error("Invalid page number.");
9677 if (!this.pdfDocument) {
9680 if (!this._setCurrentPageNumber(val, true)) {
9681 console.error(`currentPageNumber: "${val}" is not a valid page.`);
9684 _setCurrentPageNumber(val, resetCurrentPageView = false) {
9685 if (this._currentPageNumber === val) {
9686 if (resetCurrentPageView) {
9687 this.#resetCurrentPageView();
9691 if (!(0 < val && val <= this.pagesCount)) {
9694 const previous = this._currentPageNumber;
9695 this._currentPageNumber = val;
9696 this.eventBus.dispatch("pagechanging", {
9699 pageLabel: this._pageLabels?.[val - 1] ?? null,
9702 if (resetCurrentPageView) {
9703 this.#resetCurrentPageView();
9707 get currentPageLabel() {
9708 return this._pageLabels?.[this._currentPageNumber - 1] ?? null;
9710 set currentPageLabel(val) {
9711 if (!this.pdfDocument) {
9715 if (this._pageLabels) {
9716 const i = this._pageLabels.indexOf(val);
9721 if (!this._setCurrentPageNumber(page, true)) {
9722 console.error(`currentPageLabel: "${val}" is not a valid page.`);
9725 get currentScale() {
9726 return this._currentScale !== UNKNOWN_SCALE ? this._currentScale : DEFAULT_SCALE;
9728 set currentScale(val) {
9730 throw new Error("Invalid numeric scale.");
9732 if (!this.pdfDocument) {
9735 this.#setScale(val, {
9739 get currentScaleValue() {
9740 return this._currentScaleValue;
9742 set currentScaleValue(val) {
9743 if (!this.pdfDocument) {
9746 this.#setScale(val, {
9750 get pagesRotation() {
9751 return this._pagesRotation;
9753 set pagesRotation(rotation) {
9754 if (!isValidRotation(rotation)) {
9755 throw new Error("Invalid pages rotation angle.");
9757 if (!this.pdfDocument) {
9764 if (this._pagesRotation === rotation) {
9767 this._pagesRotation = rotation;
9768 const pageNumber = this._currentPageNumber;
9769 this.refresh(true, {
9772 if (this._currentScaleValue) {
9773 this.#setScale(this._currentScaleValue, {
9777 this.eventBus.dispatch("rotationchanging", {
9779 pagesRotation: rotation,
9782 if (this.defaultRenderingQueue) {
9786 get firstPagePromise() {
9787 return this.pdfDocument ? this._firstPageCapability.promise : null;
9789 get onePageRendered() {
9790 return this.pdfDocument ? this._onePageRenderedCapability.promise : null;
9792 get pagesPromise() {
9793 return this.pdfDocument ? this._pagesCapability.promise : null;
9795 get _layerProperties() {
9797 return shadow(this, "_layerProperties", {
9798 get annotationEditorUIManager() {
9799 return self.#annotationEditorUIManager;
9801 get annotationStorage() {
9802 return self.pdfDocument?.annotationStorage;
9804 get downloadManager() {
9805 return self.downloadManager;
9807 get enableScripting() {
9808 return !!self._scriptingManager;
9810 get fieldObjectsPromise() {
9811 return self.pdfDocument?.getFieldObjects();
9813 get findController() {
9814 return self.findController;
9816 get hasJSActionsPromise() {
9817 return self.pdfDocument?.hasJSActions();
9820 return self.linkService;
9824 #initializePermissions(permissions) {
9826 annotationEditorMode: this.#annotationEditorMode,
9827 annotationMode: this.#annotationMode,
9828 textLayerMode: this.#textLayerMode
9833 if (!permissions.includes(PermissionFlag.COPY) && this.#textLayerMode === TextLayerMode.ENABLE) {
9834 params.textLayerMode = TextLayerMode.ENABLE_PERMISSIONS;
9836 if (!permissions.includes(PermissionFlag.MODIFY_CONTENTS)) {
9837 params.annotationEditorMode = AnnotationEditorType.DISABLE;
9839 if (!permissions.includes(PermissionFlag.MODIFY_ANNOTATIONS) && !permissions.includes(PermissionFlag.FILL_INTERACTIVE_FORMS) && this.#annotationMode === AnnotationMode.ENABLE_FORMS) {
9840 params.annotationMode = AnnotationMode.ENABLE;
9844 async #onePageRenderedOrForceFetch(signal) {
9845 if (document.visibilityState === "hidden" || !this.container.offsetParent || this._getVisiblePages().views.length === 0) {
9848 const hiddenCapability = Promise.withResolvers(),
9849 ac = new AbortController();
9850 document.addEventListener("visibilitychange", () => {
9851 if (document.visibilityState === "hidden") {
9852 hiddenCapability.resolve();
9855 signal: AbortSignal.any([signal, ac.signal])
9857 await Promise.race([this._onePageRenderedCapability.promise, hiddenCapability.promise]);
9860 async getAllText() {
9863 for (let pageNum = 1, pagesCount = this.pdfDocument.numPages; pageNum <= pagesCount; ++pageNum) {
9864 if (this.#interruptCopyCondition) {
9868 const page = await this.pdfDocument.getPage(pageNum);
9871 } = await page.getTextContent();
9872 for (const item of items) {
9874 buffer.push(item.str);
9880 texts.push(removeNullCharacters(buffer.join("")));
9882 return texts.join("\n");
9884 #copyCallback(textLayerMode, event) {
9885 const selection = document.getSelection();
9890 if (anchorNode && focusNode && selection.containsNode(this.#hiddenCopyElement)) {
9891 if (this.#getAllTextInProgress || textLayerMode === TextLayerMode.ENABLE_PERMISSIONS) {
9895 this.#getAllTextInProgress = true;
9899 classList.add("copyAll");
9900 const ac = new AbortController();
9901 window.addEventListener("keydown", ev => this.#interruptCopyCondition = ev.key === "Escape", {
9904 this.getAllText().then(async text => {
9905 if (text !== null) {
9906 await navigator.clipboard.writeText(text);
9908 }).catch(reason => {
9909 console.warn(`Something goes wrong when extracting the text: ${reason.message}`);
9911 this.#getAllTextInProgress = false;
9912 this.#interruptCopyCondition = false;
9914 classList.remove("copyAll");
9919 setDocument(pdfDocument) {
9920 if (this.pdfDocument) {
9921 this.eventBus.dispatch("pagesdestroy", {
9924 this._cancelRendering();
9926 this.findController?.setDocument(null);
9927 this._scriptingManager?.setDocument(null);
9928 this.#annotationEditorUIManager?.destroy();
9929 this.#annotationEditorUIManager = null;
9931 this.pdfDocument = pdfDocument;
9935 const pagesCount = pdfDocument.numPages;
9936 const firstPagePromise = pdfDocument.getPage(1);
9937 const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
9940 const permissionsPromise = this.#enablePermissions ? pdfDocument.getPermissions() : Promise.resolve();
9946 this.#eventAbortController = new AbortController();
9949 } = this.#eventAbortController;
9950 if (pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) {
9951 console.warn("Forcing PAGE-scrolling for performance reasons, given the length of the document.");
9952 const mode = this._scrollMode = ScrollMode.PAGE;
9953 eventBus.dispatch("scrollmodechanged", {
9958 this._pagesCapability.promise.then(() => {
9959 eventBus.dispatch("pagesloaded", {
9964 const onBeforeDraw = evt => {
9965 const pageView = this._pages[evt.pageNumber - 1];
9969 this.#buffer.push(pageView);
9971 eventBus._on("pagerender", onBeforeDraw, {
9974 const onAfterDraw = evt => {
9975 if (evt.cssTransform) {
9978 this._onePageRenderedCapability.resolve({
9979 timestamp: evt.timestamp
9981 eventBus._off("pagerendered", onAfterDraw);
9983 eventBus._on("pagerendered", onAfterDraw, {
9986 Promise.all([firstPagePromise, permissionsPromise]).then(([firstPdfPage, permissions]) => {
9987 if (pdfDocument !== this.pdfDocument) {
9990 this._firstPageCapability.resolve(firstPdfPage);
9991 this._optionalContentConfigPromise = optionalContentConfigPromise;
9993 annotationEditorMode,
9996 } = this.#initializePermissions(permissions);
9997 if (textLayerMode !== TextLayerMode.DISABLE) {
9998 const element = this.#hiddenCopyElement = document.createElement("div");
9999 element.id = "hiddenCopyElement";
10000 viewer.before(element);
10002 if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
10003 const mode = annotationEditorMode;
10004 if (pdfDocument.isPureXfa) {
10005 console.warn("Warning: XFA-editing is not implemented.");
10006 } else if (isValidAnnotationEditorMode(mode)) {
10007 this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, viewer, this.#altTextManager, eventBus, pdfDocument, pageColors, this.#annotationEditorHighlightColors, this.#enableHighlightFloatingButton, this.#enableUpdatedAddImage, this.#enableNewAltTextWhenAddingImage, this.#mlManager, this.#editorUndoBar, this.#supportsPinchToZoom);
10008 eventBus.dispatch("annotationeditoruimanager", {
10010 uiManager: this.#annotationEditorUIManager
10012 if (mode !== AnnotationEditorType.NONE) {
10013 if (mode === AnnotationEditorType.STAMP) {
10014 this.#mlManager?.loadModel("altText");
10016 this.#annotationEditorUIManager.updateMode(mode);
10019 console.error(`Invalid AnnotationEditor mode: ${mode}`);
10022 const viewerElement = this._scrollMode === ScrollMode.PAGE ? null : viewer;
10023 const scale = this.currentScale;
10024 const viewport = firstPdfPage.getViewport({
10025 scale: scale * PixelsPerInch.PDF_TO_CSS_UNITS
10027 viewer.style.setProperty("--scale-factor", viewport.scale);
10028 if (pageColors?.background) {
10029 viewer.style.setProperty("--page-bg-color", pageColors.background);
10031 if (pageColors?.foreground === "CanvasText" || pageColors?.background === "Canvas") {
10032 viewer.style.setProperty("--hcm-highlight-filter", pdfDocument.filterFactory.addHighlightHCMFilter("highlight", "CanvasText", "Canvas", "HighlightText", "Highlight"));
10033 viewer.style.setProperty("--hcm-highlight-selected-filter", pdfDocument.filterFactory.addHighlightHCMFilter("highlight_selected", "CanvasText", "Canvas", "HighlightText", "ButtonText"));
10035 for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
10036 const pageView = new PDFPageView({
10037 container: viewerElement,
10041 defaultViewport: viewport.clone(),
10042 optionalContentConfigPromise,
10043 renderingQueue: this.renderingQueue,
10046 imageResourcesPath: this.imageResourcesPath,
10047 maxCanvasPixels: this.maxCanvasPixels,
10050 layerProperties: this._layerProperties,
10051 enableHWA: this.#enableHWA
10053 this._pages.push(pageView);
10055 this._pages[0]?.setPdfPage(firstPdfPage);
10056 if (this._scrollMode === ScrollMode.PAGE) {
10057 this.#ensurePageViewVisible();
10058 } else if (this._spreadMode !== SpreadMode.NONE) {
10059 this._updateSpreadMode();
10061 this.#onePageRenderedOrForceFetch(signal).then(async () => {
10062 if (pdfDocument !== this.pdfDocument) {
10065 this.findController?.setDocument(pdfDocument);
10066 this._scriptingManager?.setDocument(pdfDocument);
10067 if (this.#hiddenCopyElement) {
10068 document.addEventListener("copy", this.#copyCallback.bind(this, textLayerMode), {
10072 if (this.#annotationEditorUIManager) {
10073 eventBus.dispatch("annotationeditormodechanged", {
10075 mode: this.#annotationEditorMode
10078 if (pdfDocument.loadingParams.disableAutoFetch || pagesCount > PagesCountLimit.FORCE_LAZY_PAGE_INIT) {
10079 this._pagesCapability.resolve();
10082 let getPagesLeft = pagesCount - 1;
10083 if (getPagesLeft <= 0) {
10084 this._pagesCapability.resolve();
10087 for (let pageNum = 2; pageNum <= pagesCount; ++pageNum) {
10088 const promise = pdfDocument.getPage(pageNum).then(pdfPage => {
10089 const pageView = this._pages[pageNum - 1];
10090 if (!pageView.pdfPage) {
10091 pageView.setPdfPage(pdfPage);
10093 if (--getPagesLeft === 0) {
10094 this._pagesCapability.resolve();
10097 console.error(`Unable to get page ${pageNum} to initialize viewer`, reason);
10098 if (--getPagesLeft === 0) {
10099 this._pagesCapability.resolve();
10102 if (pageNum % PagesCountLimit.PAUSE_EAGER_PAGE_INIT === 0) {
10107 eventBus.dispatch("pagesinit", {
10110 pdfDocument.getMetadata().then(({
10113 if (pdfDocument !== this.pdfDocument) {
10116 if (info.Language) {
10117 viewer.lang = info.Language;
10120 if (this.defaultRenderingQueue) {
10123 }).catch(reason => {
10124 console.error("Unable to initialize viewer", reason);
10125 this._pagesCapability.reject(reason);
10128 setPageLabels(labels) {
10129 if (!this.pdfDocument) {
10133 this._pageLabels = null;
10134 } else if (!(Array.isArray(labels) && this.pdfDocument.numPages === labels.length)) {
10135 this._pageLabels = null;
10136 console.error(`setPageLabels: Invalid page labels.`);
10138 this._pageLabels = labels;
10140 for (let i = 0, ii = this._pages.length; i < ii; i++) {
10141 this._pages[i].setPageLabel(this._pageLabels?.[i] ?? null);
10146 this._currentPageNumber = 1;
10147 this._currentScale = UNKNOWN_SCALE;
10148 this._currentScaleValue = null;
10149 this._pageLabels = null;
10150 this.#buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
10151 this._location = null;
10152 this._pagesRotation = 0;
10153 this._optionalContentConfigPromise = null;
10154 this._firstPageCapability = Promise.withResolvers();
10155 this._onePageRenderedCapability = Promise.withResolvers();
10156 this._pagesCapability = Promise.withResolvers();
10157 this._scrollMode = ScrollMode.VERTICAL;
10158 this._previousScrollMode = ScrollMode.UNKNOWN;
10159 this._spreadMode = SpreadMode.NONE;
10160 this.#scrollModePageState = {
10161 previousPageNumber: 1,
10165 this.#eventAbortController?.abort();
10166 this.#eventAbortController = null;
10167 this.viewer.textContent = "";
10168 this._updateScrollMode();
10169 this.viewer.removeAttribute("lang");
10170 this.#hiddenCopyElement?.remove();
10171 this.#hiddenCopyElement = null;
10172 this.#cleanupSwitchAnnotationEditorMode();
10174 #ensurePageViewVisible() {
10175 if (this._scrollMode !== ScrollMode.PAGE) {
10176 throw new Error("#ensurePageViewVisible: Invalid scrollMode value.");
10178 const pageNumber = this._currentPageNumber,
10179 state = this.#scrollModePageState,
10180 viewer = this.viewer;
10181 viewer.textContent = "";
10182 state.pages.length = 0;
10183 if (this._spreadMode === SpreadMode.NONE && !this.isInPresentationMode) {
10184 const pageView = this._pages[pageNumber - 1];
10185 viewer.append(pageView.div);
10186 state.pages.push(pageView);
10188 const pageIndexSet = new Set(),
10189 parity = this._spreadMode - 1;
10190 if (parity === -1) {
10191 pageIndexSet.add(pageNumber - 1);
10192 } else if (pageNumber % 2 !== parity) {
10193 pageIndexSet.add(pageNumber - 1);
10194 pageIndexSet.add(pageNumber);
10196 pageIndexSet.add(pageNumber - 2);
10197 pageIndexSet.add(pageNumber - 1);
10199 const spread = document.createElement("div");
10200 spread.className = "spread";
10201 if (this.isInPresentationMode) {
10202 const dummyPage = document.createElement("div");
10203 dummyPage.className = "dummyPage";
10204 spread.append(dummyPage);
10206 for (const i of pageIndexSet) {
10207 const pageView = this._pages[i];
10211 spread.append(pageView.div);
10212 state.pages.push(pageView);
10214 viewer.append(spread);
10216 state.scrollDown = pageNumber >= state.previousPageNumber;
10217 state.previousPageNumber = pageNumber;
10220 if (this.pagesCount === 0) {
10225 #scrollIntoView(pageView, pageSpot = null) {
10230 if (this._currentPageNumber !== id) {
10231 this._setCurrentPageNumber(id);
10233 if (this._scrollMode === ScrollMode.PAGE) {
10234 this.#ensurePageViewVisible();
10237 if (!pageSpot && !this.isInPresentationMode) {
10238 const left = div.offsetLeft + div.clientLeft,
10239 right = left + div.clientWidth;
10243 } = this.container;
10244 if (this._scrollMode === ScrollMode.HORIZONTAL || left < scrollLeft || right > scrollLeft + clientWidth) {
10251 scrollIntoView(div, pageSpot);
10252 if (!this._currentScaleValue && this._location) {
10253 this._location = null;
10256 #isSameScale(newScale) {
10257 return newScale === this._currentScale || Math.abs(newScale - this._currentScale) < 1e-15;
10259 #setScaleUpdatePages(newScale, newValue, {
10265 this._currentScaleValue = newValue.toString();
10266 if (this.#isSameScale(newScale)) {
10268 this.eventBus.dispatch("scalechanging", {
10271 presetValue: newValue
10276 this.viewer.style.setProperty("--scale-factor", newScale * PixelsPerInch.PDF_TO_CSS_UNITS);
10277 const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000;
10278 this.refresh(true, {
10280 drawingDelay: postponeDrawing ? drawingDelay : -1
10282 if (postponeDrawing) {
10283 this.#scaleTimeoutId = setTimeout(() => {
10284 this.#scaleTimeoutId = null;
10288 const previousScale = this._currentScale;
10289 this._currentScale = newScale;
10291 let page = this._currentPageNumber,
10293 if (this._location && !(this.isInPresentationMode || this.isChangingPresentationMode)) {
10294 page = this._location.pageNumber;
10297 }, this._location.left, this._location.top, null];
10299 this.scrollPageIntoView({
10302 allowNegativeOffset: true
10304 if (Array.isArray(origin)) {
10305 const scaleDiff = newScale / previousScale - 1;
10306 const [top, left] = this.containerTopLeft;
10307 this.container.scrollLeft += (origin[0] - left) * scaleDiff;
10308 this.container.scrollTop += (origin[1] - top) * scaleDiff;
10311 this.eventBus.dispatch("scalechanging", {
10314 presetValue: preset ? newValue : undefined
10316 if (this.defaultRenderingQueue) {
10320 get #pageWidthScaleFactor() {
10321 if (this._spreadMode !== SpreadMode.NONE && this._scrollMode !== ScrollMode.HORIZONTAL) {
10326 #setScale(value, options) {
10327 let scale = parseFloat(value);
10329 options.preset = false;
10330 this.#setScaleUpdatePages(scale, value, options);
10332 const currentPage = this._pages[this._currentPageNumber - 1];
10333 if (!currentPage) {
10336 let hPadding = SCROLLBAR_PADDING,
10337 vPadding = VERTICAL_PADDING;
10338 if (this.isInPresentationMode) {
10339 hPadding = vPadding = 4;
10340 if (this._spreadMode !== SpreadMode.NONE) {
10343 } else if (this._scrollMode === ScrollMode.HORIZONTAL) {
10344 [hPadding, vPadding] = [vPadding, hPadding];
10346 const pageWidthScale = (this.container.clientWidth - hPadding) / currentPage.width * currentPage.scale / this.#pageWidthScaleFactor;
10347 const pageHeightScale = (this.container.clientHeight - vPadding) / currentPage.height * currentPage.scale;
10349 case "page-actual":
10353 scale = pageWidthScale;
10355 case "page-height":
10356 scale = pageHeightScale;
10359 scale = Math.min(pageWidthScale, pageHeightScale);
10362 const horizontalScale = isPortraitOrientation(currentPage) ? pageWidthScale : Math.min(pageHeightScale, pageWidthScale);
10363 scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
10366 console.error(`#setScale: "${value}" is an unknown zoom value.`);
10369 options.preset = true;
10370 this.#setScaleUpdatePages(scale, value, options);
10373 #resetCurrentPageView() {
10374 const pageView = this._pages[this._currentPageNumber - 1];
10375 if (this.isInPresentationMode) {
10376 this.#setScale(this._currentScaleValue, {
10380 this.#scrollIntoView(pageView);
10382 pageLabelToPageNumber(label) {
10383 if (!this._pageLabels) {
10386 const i = this._pageLabels.indexOf(label);
10392 scrollPageIntoView({
10395 allowNegativeOffset = false,
10396 ignoreDestinationZoom = false
10398 if (!this.pdfDocument) {
10401 const pageView = Number.isInteger(pageNumber) && this._pages[pageNumber - 1];
10403 console.error(`scrollPageIntoView: "${pageNumber}" is not a valid pageNumber parameter.`);
10406 if (this.isInPresentationMode || !destArray) {
10407 this._setCurrentPageNumber(pageNumber, true);
10416 const changeOrientation = pageView.rotation % 180 !== 0;
10417 const pageWidth = (changeOrientation ? pageView.height : pageView.width) / pageView.scale / PixelsPerInch.PDF_TO_CSS_UNITS;
10418 const pageHeight = (changeOrientation ? pageView.width : pageView.height) / pageView.scale / PixelsPerInch.PDF_TO_CSS_UNITS;
10420 switch (destArray[1].name) {
10424 scale = destArray[4];
10425 x = x !== null ? x : 0;
10426 y = y !== null ? y : pageHeight;
10430 scale = "page-fit";
10435 scale = "page-width";
10436 if (y === null && this._location) {
10437 x = this._location.left;
10438 y = this._location.top;
10439 } else if (typeof y !== "number" || y < 0) {
10447 height = pageHeight;
10448 scale = "page-height";
10453 width = destArray[4] - x;
10454 height = destArray[5] - y;
10455 let hPadding = SCROLLBAR_PADDING,
10456 vPadding = VERTICAL_PADDING;
10457 widthScale = (this.container.clientWidth - hPadding) / width / PixelsPerInch.PDF_TO_CSS_UNITS;
10458 heightScale = (this.container.clientHeight - vPadding) / height / PixelsPerInch.PDF_TO_CSS_UNITS;
10459 scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
10462 console.error(`scrollPageIntoView: "${destArray[1].name}" is not a valid destination type.`);
10465 if (!ignoreDestinationZoom) {
10466 if (scale && scale !== this._currentScale) {
10467 this.currentScaleValue = scale;
10468 } else if (this._currentScale === UNKNOWN_SCALE) {
10469 this.currentScaleValue = DEFAULT_SCALE_VALUE;
10472 if (scale === "page-fit" && !destArray[4]) {
10473 this.#scrollIntoView(pageView);
10476 const boundingRect = [pageView.viewport.convertToViewportPoint(x, y), pageView.viewport.convertToViewportPoint(x + width, y + height)];
10477 let left = Math.min(boundingRect[0][0], boundingRect[1][0]);
10478 let top = Math.min(boundingRect[0][1], boundingRect[1][1]);
10479 if (!allowNegativeOffset) {
10480 left = Math.max(left, 0);
10481 top = Math.max(top, 0);
10483 this.#scrollIntoView(pageView, {
10488 _updateLocation(firstPage) {
10489 const currentScale = this._currentScale;
10490 const currentScaleValue = this._currentScaleValue;
10491 const normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ? Math.round(currentScale * 10000) / 100 : currentScaleValue;
10492 const pageNumber = firstPage.id;
10493 const currentPageView = this._pages[pageNumber - 1];
10494 const container = this.container;
10495 const topLeft = currentPageView.getPagePoint(container.scrollLeft - firstPage.x, container.scrollTop - firstPage.y);
10496 const intLeft = Math.round(topLeft[0]);
10497 const intTop = Math.round(topLeft[1]);
10498 let pdfOpenParams = `#page=${pageNumber}`;
10499 if (!this.isInPresentationMode) {
10500 pdfOpenParams += `&zoom=${normalizedScaleValue},${intLeft},${intTop}`;
10504 scale: normalizedScaleValue,
10507 rotation: this._pagesRotation,
10512 const visible = this._getVisiblePages();
10513 const visiblePages = visible.views,
10514 numVisiblePages = visiblePages.length;
10515 if (numVisiblePages === 0) {
10518 const newCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1);
10519 this.#buffer.resize(newCacheSize, visible.ids);
10520 this.renderingQueue.renderHighestPriority(visible);
10521 const isSimpleLayout = this._spreadMode === SpreadMode.NONE && (this._scrollMode === ScrollMode.PAGE || this._scrollMode === ScrollMode.VERTICAL);
10522 const currentId = this._currentPageNumber;
10523 let stillFullyVisible = false;
10524 for (const page of visiblePages) {
10525 if (page.percent < 100) {
10528 if (page.id === currentId && isSimpleLayout) {
10529 stillFullyVisible = true;
10533 this._setCurrentPageNumber(stillFullyVisible ? currentId : visiblePages[0].id);
10534 this._updateLocation(visible.first);
10535 this.eventBus.dispatch("updateviewarea", {
10537 location: this._location
10540 #switchToEditAnnotationMode() {
10541 const visible = this._getVisiblePages();
10542 const pagesToRefresh = [];
10547 for (const page of views) {
10551 if (!view.hasEditableAnnotations()) {
10552 ids.delete(view.id);
10555 pagesToRefresh.push(page);
10557 if (pagesToRefresh.length === 0) {
10560 this.renderingQueue.renderHighestPriority({
10561 first: pagesToRefresh[0],
10562 last: pagesToRefresh.at(-1),
10563 views: pagesToRefresh,
10568 containsElement(element) {
10569 return this.container.contains(element);
10572 this.container.focus();
10574 get _isContainerRtl() {
10575 return getComputedStyle(this.container).direction === "rtl";
10577 get isInPresentationMode() {
10578 return this.presentationModeState === PresentationModeState.FULLSCREEN;
10580 get isChangingPresentationMode() {
10581 return this.presentationModeState === PresentationModeState.CHANGING;
10583 get isHorizontalScrollbarEnabled() {
10584 return this.isInPresentationMode ? false : this.container.scrollWidth > this.container.clientWidth;
10586 get isVerticalScrollbarEnabled() {
10587 return this.isInPresentationMode ? false : this.container.scrollHeight > this.container.clientHeight;
10589 _getVisiblePages() {
10590 const views = this._scrollMode === ScrollMode.PAGE ? this.#scrollModePageState.pages : this._pages,
10591 horizontal = this._scrollMode === ScrollMode.HORIZONTAL,
10592 rtl = horizontal && this._isContainerRtl;
10593 return getVisibleElements({
10594 scrollEl: this.container,
10596 sortByVisibility: true,
10602 for (const pageView of this._pages) {
10603 if (pageView.renderingState !== RenderingStates.FINISHED) {
10608 _cancelRendering() {
10609 for (const pageView of this._pages) {
10610 pageView.cancelRendering();
10613 async #ensurePdfPageLoaded(pageView) {
10614 if (pageView.pdfPage) {
10615 return pageView.pdfPage;
10618 const pdfPage = await this.pdfDocument.getPage(pageView.id);
10619 if (!pageView.pdfPage) {
10620 pageView.setPdfPage(pdfPage);
10624 console.error("Unable to get page for page view", reason);
10628 #getScrollAhead(visible) {
10629 if (visible.first?.id === 1) {
10631 } else if (visible.last?.id === this.pagesCount) {
10634 switch (this._scrollMode) {
10635 case ScrollMode.PAGE:
10636 return this.#scrollModePageState.scrollDown;
10637 case ScrollMode.HORIZONTAL:
10638 return this.scroll.right;
10640 return this.scroll.down;
10642 forceRendering(currentlyVisiblePages) {
10643 const visiblePages = currentlyVisiblePages || this._getVisiblePages();
10644 const scrollAhead = this.#getScrollAhead(visiblePages);
10645 const preRenderExtra = this._spreadMode !== SpreadMode.NONE && this._scrollMode !== ScrollMode.HORIZONTAL;
10646 const pageView = this.renderingQueue.getHighestPriority(visiblePages, this._pages, scrollAhead, preRenderExtra);
10648 this.#ensurePdfPageLoaded(pageView).then(() => {
10649 this.renderingQueue.renderView(pageView);
10655 get hasEqualPageSizes() {
10656 const firstPageView = this._pages[0];
10657 for (let i = 1, ii = this._pages.length; i < ii; ++i) {
10658 const pageView = this._pages[i];
10659 if (pageView.width !== firstPageView.width || pageView.height !== firstPageView.height) {
10665 getPagesOverview() {
10666 let initialOrientation;
10667 return this._pages.map(pageView => {
10668 const viewport = pageView.pdfPage.getViewport({
10671 const orientation = isPortraitOrientation(viewport);
10672 if (initialOrientation === undefined) {
10673 initialOrientation = orientation;
10674 } else if (this.enablePrintAutoRotate && orientation !== initialOrientation) {
10676 width: viewport.height,
10677 height: viewport.width,
10678 rotation: (viewport.rotation - 90) % 360
10682 width: viewport.width,
10683 height: viewport.height,
10684 rotation: viewport.rotation
10688 get optionalContentConfigPromise() {
10689 if (!this.pdfDocument) {
10690 return Promise.resolve(null);
10692 if (!this._optionalContentConfigPromise) {
10693 console.error("optionalContentConfigPromise: Not initialized yet.");
10694 return this.pdfDocument.getOptionalContentConfig({
10698 return this._optionalContentConfigPromise;
10700 set optionalContentConfigPromise(promise) {
10701 if (!(promise instanceof Promise)) {
10702 throw new Error(`Invalid optionalContentConfigPromise: ${promise}`);
10704 if (!this.pdfDocument) {
10707 if (!this._optionalContentConfigPromise) {
10710 this._optionalContentConfigPromise = promise;
10711 this.refresh(false, {
10712 optionalContentConfigPromise: promise
10714 this.eventBus.dispatch("optionalcontentconfigchanged", {
10720 return this._scrollMode;
10722 set scrollMode(mode) {
10723 if (this._scrollMode === mode) {
10726 if (!isValidScrollMode(mode)) {
10727 throw new Error(`Invalid scroll mode: ${mode}`);
10729 if (this.pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) {
10732 this._previousScrollMode = this._scrollMode;
10733 this._scrollMode = mode;
10734 this.eventBus.dispatch("scrollmodechanged", {
10738 this._updateScrollMode(this._currentPageNumber);
10740 _updateScrollMode(pageNumber = null) {
10741 const scrollMode = this._scrollMode,
10742 viewer = this.viewer;
10743 viewer.classList.toggle("scrollHorizontal", scrollMode === ScrollMode.HORIZONTAL);
10744 viewer.classList.toggle("scrollWrapped", scrollMode === ScrollMode.WRAPPED);
10745 if (!this.pdfDocument || !pageNumber) {
10748 if (scrollMode === ScrollMode.PAGE) {
10749 this.#ensurePageViewVisible();
10750 } else if (this._previousScrollMode === ScrollMode.PAGE) {
10751 this._updateSpreadMode();
10753 if (this._currentScaleValue && isNaN(this._currentScaleValue)) {
10754 this.#setScale(this._currentScaleValue, {
10758 this._setCurrentPageNumber(pageNumber, true);
10762 return this._spreadMode;
10764 set spreadMode(mode) {
10765 if (this._spreadMode === mode) {
10768 if (!isValidSpreadMode(mode)) {
10769 throw new Error(`Invalid spread mode: ${mode}`);
10771 this._spreadMode = mode;
10772 this.eventBus.dispatch("spreadmodechanged", {
10776 this._updateSpreadMode(this._currentPageNumber);
10778 _updateSpreadMode(pageNumber = null) {
10779 if (!this.pdfDocument) {
10782 const viewer = this.viewer,
10783 pages = this._pages;
10784 if (this._scrollMode === ScrollMode.PAGE) {
10785 this.#ensurePageViewVisible();
10787 viewer.textContent = "";
10788 if (this._spreadMode === SpreadMode.NONE) {
10789 for (const pageView of this._pages) {
10790 viewer.append(pageView.div);
10793 const parity = this._spreadMode - 1;
10795 for (let i = 0, ii = pages.length; i < ii; ++i) {
10796 if (spread === null) {
10797 spread = document.createElement("div");
10798 spread.className = "spread";
10799 viewer.append(spread);
10800 } else if (i % 2 === parity) {
10801 spread = spread.cloneNode(false);
10802 viewer.append(spread);
10804 spread.append(pages[i].div);
10811 if (this._currentScaleValue && isNaN(this._currentScaleValue)) {
10812 this.#setScale(this._currentScaleValue, {
10816 this._setCurrentPageNumber(pageNumber, true);
10819 _getPageAdvance(currentPageNumber, previous = false) {
10820 switch (this._scrollMode) {
10821 case ScrollMode.WRAPPED:
10825 } = this._getVisiblePages(),
10826 pageLayout = new Map();
10833 if (percent === 0 || widthPercent < 100) {
10836 let yArray = pageLayout.get(y);
10838 pageLayout.set(y, yArray ||= []);
10842 for (const yArray of pageLayout.values()) {
10843 const currentIndex = yArray.indexOf(currentPageNumber);
10844 if (currentIndex === -1) {
10847 const numPages = yArray.length;
10848 if (numPages === 1) {
10852 for (let i = currentIndex - 1, ii = 0; i >= ii; i--) {
10853 const currentId = yArray[i],
10854 expectedId = yArray[i + 1] - 1;
10855 if (currentId < expectedId) {
10856 return currentPageNumber - expectedId;
10860 for (let i = currentIndex + 1, ii = numPages; i < ii; i++) {
10861 const currentId = yArray[i],
10862 expectedId = yArray[i - 1] + 1;
10863 if (currentId > expectedId) {
10864 return expectedId - currentPageNumber;
10869 const firstId = yArray[0];
10870 if (firstId < currentPageNumber) {
10871 return currentPageNumber - firstId + 1;
10874 const lastId = yArray[numPages - 1];
10875 if (lastId > currentPageNumber) {
10876 return lastId - currentPageNumber + 1;
10883 case ScrollMode.HORIZONTAL:
10887 case ScrollMode.PAGE:
10888 case ScrollMode.VERTICAL:
10890 if (this._spreadMode === SpreadMode.NONE) {
10893 const parity = this._spreadMode - 1;
10894 if (previous && currentPageNumber % 2 !== parity) {
10896 } else if (!previous && currentPageNumber % 2 === parity) {
10901 } = this._getVisiblePages(),
10902 expectedId = previous ? currentPageNumber - 1 : currentPageNumber + 1;
10908 if (id !== expectedId) {
10911 if (percent > 0 && widthPercent === 100) {
10922 const currentPageNumber = this._currentPageNumber,
10923 pagesCount = this.pagesCount;
10924 if (currentPageNumber >= pagesCount) {
10927 const advance = this._getPageAdvance(currentPageNumber, false) || 1;
10928 this.currentPageNumber = Math.min(currentPageNumber + advance, pagesCount);
10932 const currentPageNumber = this._currentPageNumber;
10933 if (currentPageNumber <= 1) {
10936 const advance = this._getPageAdvance(currentPageNumber, true) || 1;
10937 this.currentPageNumber = Math.max(currentPageNumber - advance, 1);
10942 scaleFactor = null,
10946 if (steps === null && scaleFactor === null) {
10947 throw new Error("Invalid updateScale options: either `steps` or `scaleFactor` must be provided.");
10949 if (!this.pdfDocument) {
10952 let newScale = this._currentScale;
10953 if (scaleFactor > 0 && scaleFactor !== 1) {
10954 newScale = Math.round(newScale * scaleFactor * 100) / 100;
10955 } else if (steps) {
10956 const delta = steps > 0 ? DEFAULT_SCALE_DELTA : 1 / DEFAULT_SCALE_DELTA;
10957 const round = steps > 0 ? Math.ceil : Math.floor;
10958 steps = Math.abs(steps);
10960 newScale = round((newScale * delta).toFixed(2) * 10) / 10;
10961 } while (--steps > 0);
10963 newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, newScale));
10964 this.#setScale(newScale, {
10970 increaseScale(options = {}) {
10973 steps: options.steps ?? 1
10976 decreaseScale(options = {}) {
10979 steps: -(options.steps ?? 1)
10982 #updateContainerHeightCss(height = this.container.clientHeight) {
10983 if (height !== this.#previousContainerHeight) {
10984 this.#previousContainerHeight = height;
10985 docStyle.setProperty("--viewer-container-height", `${height}px`);
10988 #resizeObserverCallback(entries) {
10989 for (const entry of entries) {
10990 if (entry.target === this.container) {
10991 this.#updateContainerHeightCss(Math.floor(entry.borderBoxSize[0].blockSize));
10992 this.#containerTopLeft = null;
10997 get containerTopLeft() {
10998 return this.#containerTopLeft ||= [this.container.offsetTop, this.container.offsetLeft];
11000 #cleanupSwitchAnnotationEditorMode() {
11001 this.#switchAnnotationEditorModeAC?.abort();
11002 this.#switchAnnotationEditorModeAC = null;
11003 if (this.#switchAnnotationEditorModeTimeoutId !== null) {
11004 clearTimeout(this.#switchAnnotationEditorModeTimeoutId);
11005 this.#switchAnnotationEditorModeTimeoutId = null;
11008 get annotationEditorMode() {
11009 return this.#annotationEditorUIManager ? this.#annotationEditorMode : AnnotationEditorType.DISABLE;
11011 set annotationEditorMode({
11014 isFromKeyboard = false
11016 if (!this.#annotationEditorUIManager) {
11017 throw new Error(`The AnnotationEditor is not enabled.`);
11019 if (this.#annotationEditorMode === mode) {
11022 if (!isValidAnnotationEditorMode(mode)) {
11023 throw new Error(`Invalid AnnotationEditor mode: ${mode}`);
11025 if (!this.pdfDocument) {
11028 if (mode === AnnotationEditorType.STAMP) {
11029 this.#mlManager?.loadModel("altText");
11035 const updater = async () => {
11036 this.#cleanupSwitchAnnotationEditorMode();
11037 this.#annotationEditorMode = mode;
11038 await this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard);
11039 if (mode !== this.#annotationEditorMode || pdfDocument !== this.pdfDocument) {
11042 eventBus.dispatch("annotationeditormodechanged", {
11047 if (mode === AnnotationEditorType.NONE || this.#annotationEditorMode === AnnotationEditorType.NONE) {
11048 const isEditing = mode !== AnnotationEditorType.NONE;
11050 this.pdfDocument.annotationStorage.resetModifiedIds();
11052 for (const pageView of this._pages) {
11053 pageView.toggleEditingMode(isEditing);
11055 const idsToRefresh = this.#switchToEditAnnotationMode();
11056 if (isEditing && idsToRefresh) {
11057 this.#cleanupSwitchAnnotationEditorMode();
11058 this.#switchAnnotationEditorModeAC = new AbortController();
11059 const signal = AbortSignal.any([this.#eventAbortController.signal, this.#switchAnnotationEditorModeAC.signal]);
11060 eventBus._on("pagerendered", ({
11063 idsToRefresh.delete(pageNumber);
11064 if (idsToRefresh.size === 0) {
11065 this.#switchAnnotationEditorModeTimeoutId = setTimeout(updater, 0);
11075 refresh(noUpdate = false, updateArgs = Object.create(null)) {
11076 if (!this.pdfDocument) {
11079 for (const pageView of this._pages) {
11080 pageView.update(updateArgs);
11082 if (this.#scaleTimeoutId !== null) {
11083 clearTimeout(this.#scaleTimeoutId);
11084 this.#scaleTimeoutId = null;
11092 ;// ./web/secondary_toolbar.js
11095 class SecondaryToolbar {
11097 constructor(options, eventBus) {
11098 this.#opts = options;
11100 element: options.presentationModeButton,
11101 eventName: "presentationmode",
11104 element: options.printButton,
11105 eventName: "print",
11108 element: options.downloadButton,
11109 eventName: "download",
11112 element: options.viewBookmarkButton,
11116 element: options.firstPageButton,
11117 eventName: "firstpage",
11120 element: options.lastPageButton,
11121 eventName: "lastpage",
11124 element: options.pageRotateCwButton,
11125 eventName: "rotatecw",
11128 element: options.pageRotateCcwButton,
11129 eventName: "rotateccw",
11132 element: options.cursorSelectToolButton,
11133 eventName: "switchcursortool",
11135 tool: CursorTool.SELECT
11139 element: options.cursorHandToolButton,
11140 eventName: "switchcursortool",
11142 tool: CursorTool.HAND
11146 element: options.scrollPageButton,
11147 eventName: "switchscrollmode",
11149 mode: ScrollMode.PAGE
11153 element: options.scrollVerticalButton,
11154 eventName: "switchscrollmode",
11156 mode: ScrollMode.VERTICAL
11160 element: options.scrollHorizontalButton,
11161 eventName: "switchscrollmode",
11163 mode: ScrollMode.HORIZONTAL
11167 element: options.scrollWrappedButton,
11168 eventName: "switchscrollmode",
11170 mode: ScrollMode.WRAPPED
11174 element: options.spreadNoneButton,
11175 eventName: "switchspreadmode",
11177 mode: SpreadMode.NONE
11181 element: options.spreadOddButton,
11182 eventName: "switchspreadmode",
11184 mode: SpreadMode.ODD
11188 element: options.spreadEvenButton,
11189 eventName: "switchspreadmode",
11191 mode: SpreadMode.EVEN
11195 element: options.imageAltTextSettingsButton,
11196 eventName: "imagealttextsettings",
11199 element: options.documentPropertiesButton,
11200 eventName: "documentproperties",
11203 this.eventBus = eventBus;
11204 this.opened = false;
11205 this.#bindListeners(buttons);
11209 return this.opened;
11211 setPageNumber(pageNumber) {
11212 this.pageNumber = pageNumber;
11213 this.#updateUIState();
11215 setPagesCount(pagesCount) {
11216 this.pagesCount = pagesCount;
11217 this.#updateUIState();
11220 this.pageNumber = 0;
11221 this.pagesCount = 0;
11222 this.#updateUIState();
11223 this.eventBus.dispatch("switchcursortool", {
11227 this.#scrollModeChanged({
11228 mode: ScrollMode.VERTICAL
11230 this.#spreadModeChanged({
11231 mode: SpreadMode.NONE
11238 pageRotateCwButton,
11239 pageRotateCcwButton
11241 firstPageButton.disabled = this.pageNumber <= 1;
11242 lastPageButton.disabled = this.pageNumber >= this.pagesCount;
11243 pageRotateCwButton.disabled = this.pagesCount === 0;
11244 pageRotateCcwButton.disabled = this.pagesCount === 0;
11246 #bindListeners(buttons) {
11253 toggleButton.addEventListener("click", this.toggle.bind(this));
11260 element.addEventListener("click", evt => {
11261 if (eventName !== null) {
11262 eventBus.dispatch(eventName, {
11270 eventBus.dispatch("reporttelemetry", {
11281 eventBus._on("cursortoolchanged", this.#cursorToolChanged.bind(this));
11282 eventBus._on("scrollmodechanged", this.#scrollModeChanged.bind(this));
11283 eventBus._on("spreadmodechanged", this.#spreadModeChanged.bind(this));
11285 #cursorToolChanged({
11290 cursorSelectToolButton,
11291 cursorHandToolButton
11293 toggleCheckedBtn(cursorSelectToolButton, tool === CursorTool.SELECT);
11294 toggleCheckedBtn(cursorHandToolButton, tool === CursorTool.HAND);
11295 cursorSelectToolButton.disabled = disabled;
11296 cursorHandToolButton.disabled = disabled;
11298 #scrollModeChanged({
11303 scrollVerticalButton,
11304 scrollHorizontalButton,
11305 scrollWrappedButton,
11310 toggleCheckedBtn(scrollPageButton, mode === ScrollMode.PAGE);
11311 toggleCheckedBtn(scrollVerticalButton, mode === ScrollMode.VERTICAL);
11312 toggleCheckedBtn(scrollHorizontalButton, mode === ScrollMode.HORIZONTAL);
11313 toggleCheckedBtn(scrollWrappedButton, mode === ScrollMode.WRAPPED);
11314 const forceScrollModePage = this.pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE;
11315 scrollPageButton.disabled = forceScrollModePage;
11316 scrollVerticalButton.disabled = forceScrollModePage;
11317 scrollHorizontalButton.disabled = forceScrollModePage;
11318 scrollWrappedButton.disabled = forceScrollModePage;
11319 const isHorizontal = mode === ScrollMode.HORIZONTAL;
11320 spreadNoneButton.disabled = isHorizontal;
11321 spreadOddButton.disabled = isHorizontal;
11322 spreadEvenButton.disabled = isHorizontal;
11324 #spreadModeChanged({
11332 toggleCheckedBtn(spreadNoneButton, mode === SpreadMode.NONE);
11333 toggleCheckedBtn(spreadOddButton, mode === SpreadMode.ODD);
11334 toggleCheckedBtn(spreadEvenButton, mode === SpreadMode.EVEN);
11340 this.opened = true;
11345 toggleExpandedBtn(toggleButton, true, toolbar);
11348 if (!this.opened) {
11351 this.opened = false;
11356 toggleExpandedBtn(toggleButton, false, toolbar);
11367 ;// ./web/toolbar.js
11371 #colorPicker = null;
11373 constructor(options, eventBus, toolbarDensity = 0) {
11374 this.#opts = options;
11375 this.eventBus = eventBus;
11377 element: options.previous,
11378 eventName: "previouspage"
11380 element: options.next,
11381 eventName: "nextpage"
11383 element: options.zoomIn,
11384 eventName: "zoomin"
11386 element: options.zoomOut,
11387 eventName: "zoomout"
11389 element: options.print,
11392 element: options.download,
11393 eventName: "download"
11395 element: options.editorFreeTextButton,
11396 eventName: "switchannotationeditormode",
11401 } = options.editorFreeTextButton;
11402 return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.FREETEXT;
11406 element: options.editorHighlightButton,
11407 eventName: "switchannotationeditormode",
11412 } = options.editorHighlightButton;
11413 return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.HIGHLIGHT;
11417 element: options.editorInkButton,
11418 eventName: "switchannotationeditormode",
11423 } = options.editorInkButton;
11424 return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.INK;
11428 element: options.editorStampButton,
11429 eventName: "switchannotationeditormode",
11434 } = options.editorStampButton;
11435 return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.STAMP;
11441 action: "pdfjs.image.icon_click"
11445 element: options.editorSignatureButton,
11446 eventName: "switchannotationeditormode",
11451 } = options.editorSignatureButton;
11452 return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.SIGNATURE;
11456 this.#bindListeners(buttons);
11457 this.#updateToolbarDensity({
11458 value: toolbarDensity
11462 #updateToolbarDensity({
11465 let name = "normal";
11474 document.documentElement.setAttribute("data-toolbar-density", name);
11476 setPageNumber(pageNumber, pageLabel) {
11477 this.pageNumber = pageNumber;
11478 this.pageLabel = pageLabel;
11479 this.#updateUIState(false);
11481 setPagesCount(pagesCount, hasPageLabels) {
11482 this.pagesCount = pagesCount;
11483 this.hasPageLabels = hasPageLabels;
11484 this.#updateUIState(true);
11486 setPageScale(pageScaleValue, pageScale) {
11487 this.pageScaleValue = (pageScaleValue || pageScale).toString();
11488 this.pageScale = pageScale;
11489 this.#updateUIState(false);
11492 this.#colorPicker = null;
11493 this.pageNumber = 0;
11494 this.pageLabel = null;
11495 this.hasPageLabels = false;
11496 this.pagesCount = 0;
11497 this.pageScaleValue = DEFAULT_SCALE_VALUE;
11498 this.pageScale = DEFAULT_SCALE;
11499 this.#updateUIState(true);
11500 this.updateLoadingIndicatorState();
11501 this.#editorModeChanged({
11502 mode: AnnotationEditorType.DISABLE
11505 #bindListeners(buttons) {
11510 editorHighlightColorPicker,
11511 editorHighlightButton,
11522 element.addEventListener("click", evt => {
11523 if (eventName !== null) {
11524 eventBus.dispatch(eventName, {
11527 isFromKeyboard: evt.detail === 0
11531 eventBus.dispatch("reporttelemetry", {
11538 pageNumber.addEventListener("click", function () {
11541 pageNumber.addEventListener("change", function () {
11542 eventBus.dispatch("pagenumberchanged", {
11547 scaleSelect.addEventListener("change", function () {
11548 if (this.value === "custom") {
11551 eventBus.dispatch("scalechanged", {
11556 scaleSelect.addEventListener("click", function ({
11559 if (this.value === self.pageScaleValue && target.tagName.toUpperCase() === "OPTION") {
11563 scaleSelect.oncontextmenu = noContextMenu;
11564 eventBus._on("annotationeditormodechanged", this.#editorModeChanged.bind(this));
11565 eventBus._on("showannotationeditorui", ({
11569 case AnnotationEditorType.HIGHLIGHT:
11570 editorHighlightButton.click();
11574 eventBus._on("toolbardensity", this.#updateToolbarDensity.bind(this));
11575 if (editorHighlightColorPicker) {
11576 eventBus._on("annotationeditoruimanager", ({
11579 const cp = this.#colorPicker = new ColorPicker({
11582 uiManager.setMainHighlightColorPicker(cp);
11583 editorHighlightColorPicker.append(cp.renderMainDropdown());
11585 eventBus._on("mainhighlightcolorpickerupdatecolor", ({
11588 this.#colorPicker?.updateColor(value);
11592 #editorModeChanged({
11596 editorFreeTextButton,
11597 editorFreeTextParamsToolbar,
11598 editorHighlightButton,
11599 editorHighlightParamsToolbar,
11601 editorInkParamsToolbar,
11603 editorStampParamsToolbar,
11604 editorSignatureButton,
11605 editorSignatureParamsToolbar
11607 toggleExpandedBtn(editorFreeTextButton, mode === AnnotationEditorType.FREETEXT, editorFreeTextParamsToolbar);
11608 toggleExpandedBtn(editorHighlightButton, mode === AnnotationEditorType.HIGHLIGHT, editorHighlightParamsToolbar);
11609 toggleExpandedBtn(editorInkButton, mode === AnnotationEditorType.INK, editorInkParamsToolbar);
11610 toggleExpandedBtn(editorStampButton, mode === AnnotationEditorType.STAMP, editorStampParamsToolbar);
11611 toggleExpandedBtn(editorSignatureButton, mode === AnnotationEditorType.SIGNATURE, editorSignatureParamsToolbar);
11612 const isDisable = mode === AnnotationEditorType.DISABLE;
11613 editorFreeTextButton.disabled = isDisable;
11614 editorHighlightButton.disabled = isDisable;
11615 editorInkButton.disabled = isDisable;
11616 editorStampButton.disabled = isDisable;
11617 editorSignatureButton.disabled = isDisable;
11619 #updateUIState(resetNumPages = false) {
11626 const opts = this.#opts;
11627 if (resetNumPages) {
11628 if (this.hasPageLabels) {
11629 opts.pageNumber.type = "text";
11630 opts.numPages.setAttribute("data-l10n-id", "pdfjs-page-of-pages");
11632 opts.pageNumber.type = "number";
11633 opts.numPages.setAttribute("data-l10n-id", "pdfjs-of-pages");
11634 opts.numPages.setAttribute("data-l10n-args", JSON.stringify({
11638 opts.pageNumber.max = pagesCount;
11640 if (this.hasPageLabels) {
11641 opts.pageNumber.value = this.pageLabel;
11642 opts.numPages.setAttribute("data-l10n-args", JSON.stringify({
11647 opts.pageNumber.value = pageNumber;
11649 opts.previous.disabled = pageNumber <= 1;
11650 opts.next.disabled = pageNumber >= pagesCount;
11651 opts.zoomOut.disabled = pageScale <= MIN_SCALE;
11652 opts.zoomIn.disabled = pageScale >= MAX_SCALE;
11653 let predefinedValueFound = false;
11654 for (const option of opts.scaleSelect.options) {
11655 if (option.value !== pageScaleValue) {
11656 option.selected = false;
11659 option.selected = true;
11660 predefinedValueFound = true;
11662 if (!predefinedValueFound) {
11663 opts.customScaleOption.selected = true;
11664 opts.customScaleOption.setAttribute("data-l10n-args", JSON.stringify({
11665 scale: Math.round(pageScale * 10000) / 100
11669 updateLoadingIndicatorState(loading = false) {
11673 pageNumber.classList.toggle("loading", loading);
11677 ;// ./web/view_history.js
11678 const DEFAULT_VIEW_HISTORY_CACHE_SIZE = 20;
11679 class ViewHistory {
11680 constructor(fingerprint, cacheSize = DEFAULT_VIEW_HISTORY_CACHE_SIZE) {
11681 this.fingerprint = fingerprint;
11682 this.cacheSize = cacheSize;
11683 this._initializedPromise = this._readFromStorage().then(databaseStr => {
11684 const database = JSON.parse(databaseStr || "{}");
11686 if (!Array.isArray(database.files)) {
11687 database.files = [];
11689 while (database.files.length >= this.cacheSize) {
11690 database.files.shift();
11692 for (let i = 0, ii = database.files.length; i < ii; i++) {
11693 const branch = database.files[i];
11694 if (branch.fingerprint === this.fingerprint) {
11700 if (index === -1) {
11701 index = database.files.push({
11702 fingerprint: this.fingerprint
11705 this.file = database.files[index];
11706 this.database = database;
11709 async _writeToStorage() {
11710 const databaseStr = JSON.stringify(this.database);
11711 sessionStorage.setItem("pdfjs.history", databaseStr);
11713 async _readFromStorage() {
11714 return sessionStorage.getItem("pdfjs.history");
11716 async set(name, val) {
11717 await this._initializedPromise;
11718 this.file[name] = val;
11719 return this._writeToStorage();
11721 async setMultiple(properties) {
11722 await this._initializedPromise;
11723 for (const name in properties) {
11724 this.file[name] = properties[name];
11726 return this._writeToStorage();
11728 async get(name, defaultValue) {
11729 await this._initializedPromise;
11730 const val = this.file[name];
11731 return val !== undefined ? val : defaultValue;
11733 async getMultiple(properties) {
11734 await this._initializedPromise;
11735 const values = Object.create(null);
11736 for (const name in properties) {
11737 const val = this.file[name];
11738 values[name] = val !== undefined ? val : properties[name];
11778 const FORCE_PAGES_LOADED_TIMEOUT = 10000;
11779 const ViewOnLoad = {
11784 const PDFViewerApplication = {
11785 initialBookmark: document.location.hash.substring(1),
11786 _initializedCapability: {
11787 ...Promise.withResolvers(),
11792 pdfLoadingTask: null,
11793 printService: null,
11795 pdfThumbnailViewer: null,
11796 pdfRenderingQueue: null,
11797 pdfPresentationMode: null,
11798 pdfDocumentProperties: null,
11799 pdfLinkService: null,
11802 pdfOutlineViewer: null,
11803 pdfAttachmentViewer: null,
11804 pdfLayerViewer: null,
11805 pdfCursorTools: null,
11806 pdfScriptingManager: null,
11808 downloadManager: null,
11809 overlayManager: null,
11810 preferences: new Preferences(),
11812 secondaryToolbar: null,
11815 annotationEditorParams: null,
11816 imageAltTextSettings: null,
11817 isInitialViewSet: false,
11818 isViewerEmbedded: window.parent !== window,
11823 _eventBusAbortController: null,
11824 _windowAbortController: null,
11825 _globalAbortController: new AbortController(),
11826 documentInfo: null,
11828 _contentDispositionFilename: null,
11829 _contentLength: null,
11830 _saveInProgress: false,
11831 _wheelUnusedTicks: 0,
11832 _wheelUnusedFactor: 1,
11833 _touchManager: null,
11834 _touchUnusedTicks: 0,
11835 _touchUnusedFactor: 1,
11837 _hasAnnotationEditors: false,
11838 _title: document.title,
11839 _printAnnotationStoragePromise: null,
11840 _isCtrlKeyDown: false,
11841 _caretBrowsing: null,
11842 _isScrolling: false,
11843 editorUndoBar: null,
11844 async initialize(appConfig) {
11845 this.appConfig = appConfig;
11847 await this.preferences.initializedPromise;
11849 console.error("initialize:", ex);
11851 if (AppOptions.get("pdfBugEnabled")) {
11852 await this._parseHashParams();
11854 if (AppOptions.get("enableAltText")) {
11855 this.mlManager = new MLManager({
11856 enableGuessAltText: AppOptions.get("enableGuessAltText"),
11857 enableAltTextModelDownload: AppOptions.get("enableAltTextModelDownload"),
11858 altTextLearnMoreUrl: AppOptions.get("altTextLearnMoreUrl")
11861 this.l10n = await this.externalServices.createL10n();
11862 document.getElementsByTagName("html")[0].dir = this.l10n.getDirection();
11863 if (this.isViewerEmbedded && AppOptions.get("externalLinkTarget") === LinkTarget.NONE) {
11864 AppOptions.set("externalLinkTarget", LinkTarget.TOP);
11866 await this._initializeViewerComponents();
11868 this.bindWindowEvents();
11869 this._initializedCapability.settled = true;
11870 this._initializedCapability.resolve();
11872 async _parseHashParams() {
11873 const hash = document.location.hash.substring(1);
11880 } = this.appConfig,
11881 params = parseQueryString(hash);
11882 const loadPDFBug = async () => {
11883 if (this._PDFBug) {
11888 } = await import(/*webpackIgnore: true*/AppOptions.get("debuggerSrc"));
11889 this._PDFBug = PDFBug;
11891 if (params.get("disableworker") === "true") {
11893 GlobalWorkerOptions.workerSrc ||= AppOptions.get("workerSrc");
11894 await import(/*webpackIgnore: true*/PDFWorker.workerSrc);
11896 console.error("_parseHashParams:", ex);
11899 if (params.has("textlayer")) {
11900 switch (params.get("textlayer")) {
11902 AppOptions.set("textLayerMode", TextLayerMode.DISABLE);
11907 viewerContainer.classList.add(`textLayer-${params.get("textlayer")}`);
11909 await loadPDFBug();
11910 this._PDFBug.loadCSS();
11912 console.error("_parseHashParams:", ex);
11917 if (params.has("pdfbug")) {
11918 AppOptions.setAll({
11920 fontExtraProperties: true
11922 const enabled = params.get("pdfbug").split(",");
11924 await loadPDFBug();
11925 this._PDFBug.init(mainContainer, enabled);
11927 console.error("_parseHashParams:", ex);
11931 disableAutoFetch: x => x === "true",
11932 disableFontFace: x => x === "true",
11933 disableHistory: x => x === "true",
11934 disableRange: x => x === "true",
11935 disableStream: x => x === "true",
11936 verbosity: x => x | 0
11938 for (const name in opts) {
11939 const check = opts[name],
11940 key = name.toLowerCase();
11941 if (params.has(key)) {
11942 AppOptions.set(name, check(params.get(key)));
11946 async _initializeViewerComponents() {
11952 const eventBus = new FirefoxEventBus(AppOptions.get("allowedGlobalEvents"), externalServices, AppOptions.get("isInAutomation"));
11953 this.eventBus = AppOptions.eventBus = eventBus;
11954 this.mlManager?.setEventBus(eventBus, this._globalAbortController.signal);
11955 this.overlayManager = new OverlayManager();
11956 const pdfRenderingQueue = new PDFRenderingQueue();
11957 pdfRenderingQueue.onIdle = this._cleanup.bind(this);
11958 this.pdfRenderingQueue = pdfRenderingQueue;
11959 const pdfLinkService = new PDFLinkService({
11961 externalLinkTarget: AppOptions.get("externalLinkTarget"),
11962 externalLinkRel: AppOptions.get("externalLinkRel"),
11963 ignoreDestinationZoom: AppOptions.get("ignoreDestinationZoom")
11965 this.pdfLinkService = pdfLinkService;
11966 const downloadManager = this.downloadManager = new DownloadManager();
11967 const findController = new PDFFindController({
11968 linkService: pdfLinkService,
11970 updateMatchesCountOnProgress: true
11972 this.findController = findController;
11973 const pdfScriptingManager = new PDFScriptingManager({
11976 docProperties: this._scriptingDocProperties.bind(this)
11978 this.pdfScriptingManager = pdfScriptingManager;
11979 const container = appConfig.mainContainer,
11980 viewer = appConfig.viewerContainer;
11981 const annotationEditorMode = AppOptions.get("annotationEditorMode");
11982 const pageColors = AppOptions.get("forcePageColors") || window.matchMedia("(forced-colors: active)").matches ? {
11983 background: AppOptions.get("pageColorsBackground"),
11984 foreground: AppOptions.get("pageColorsForeground")
11986 let altTextManager;
11987 if (AppOptions.get("enableUpdatedAddImage")) {
11988 altTextManager = appConfig.newAltTextDialog ? new NewAltTextManager(appConfig.newAltTextDialog, this.overlayManager, eventBus) : null;
11990 altTextManager = appConfig.altTextDialog ? new AltTextManager(appConfig.altTextDialog, container, this.overlayManager, eventBus) : null;
11992 if (appConfig.editorUndoBar) {
11993 this.editorUndoBar = new EditorUndoBar(appConfig.editorUndoBar, eventBus);
11995 const enableHWA = AppOptions.get("enableHWA");
11996 const pdfViewer = new PDFViewer({
12000 renderingQueue: pdfRenderingQueue,
12001 linkService: pdfLinkService,
12004 editorUndoBar: this.editorUndoBar,
12006 scriptingManager: AppOptions.get("enableScripting") && pdfScriptingManager,
12008 textLayerMode: AppOptions.get("textLayerMode"),
12009 annotationMode: AppOptions.get("annotationMode"),
12010 annotationEditorMode,
12011 annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"),
12012 enableHighlightFloatingButton: AppOptions.get("enableHighlightFloatingButton"),
12013 enableUpdatedAddImage: AppOptions.get("enableUpdatedAddImage"),
12014 enableNewAltTextWhenAddingImage: AppOptions.get("enableNewAltTextWhenAddingImage"),
12015 imageResourcesPath: AppOptions.get("imageResourcesPath"),
12016 enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
12017 maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
12018 enablePermissions: AppOptions.get("enablePermissions"),
12020 mlManager: this.mlManager,
12021 abortSignal: this._globalAbortController.signal,
12023 supportsPinchToZoom: this.supportsPinchToZoom
12025 this.pdfViewer = pdfViewer;
12026 pdfRenderingQueue.setViewer(pdfViewer);
12027 pdfLinkService.setViewer(pdfViewer);
12028 pdfScriptingManager.setViewer(pdfViewer);
12029 if (appConfig.sidebar?.thumbnailView) {
12030 this.pdfThumbnailViewer = new PDFThumbnailViewer({
12031 container: appConfig.sidebar.thumbnailView,
12033 renderingQueue: pdfRenderingQueue,
12034 linkService: pdfLinkService,
12036 abortSignal: this._globalAbortController.signal,
12039 pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
12041 if (!this.isViewerEmbedded && !AppOptions.get("disableHistory")) {
12042 this.pdfHistory = new PDFHistory({
12043 linkService: pdfLinkService,
12046 pdfLinkService.setHistory(this.pdfHistory);
12048 if (!this.supportsIntegratedFind && appConfig.findBar) {
12049 this.findBar = new PDFFindBar(appConfig.findBar, appConfig.principalContainer, eventBus);
12051 if (appConfig.annotationEditorParams) {
12052 if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
12053 const editorSignatureButton = appConfig.toolbar?.editorSignatureButton;
12054 if (editorSignatureButton && AppOptions.get("enableSignatureEditor")) {
12055 editorSignatureButton.parentElement.hidden = false;
12057 this.annotationEditorParams = new AnnotationEditorParams(appConfig.annotationEditorParams, eventBus);
12059 for (const id of ["editorModeButtons", "editorModeSeparator"]) {
12060 document.getElementById(id)?.classList.add("hidden");
12064 if (this.mlManager && appConfig.secondaryToolbar?.imageAltTextSettingsButton) {
12065 this.imageAltTextSettings = new ImageAltTextSettings(appConfig.altTextSettingsDialog, this.overlayManager, eventBus, this.mlManager);
12067 if (appConfig.documentProperties) {
12068 this.pdfDocumentProperties = new PDFDocumentProperties(appConfig.documentProperties, this.overlayManager, eventBus, l10n, () => this._docFilename);
12070 if (appConfig.secondaryToolbar?.cursorHandToolButton) {
12071 this.pdfCursorTools = new PDFCursorTools({
12074 cursorToolOnLoad: AppOptions.get("cursorToolOnLoad")
12077 if (appConfig.toolbar) {
12078 this.toolbar = new Toolbar(appConfig.toolbar, eventBus, AppOptions.get("toolbarDensity"));
12080 if (appConfig.secondaryToolbar) {
12081 if (AppOptions.get("enableAltText")) {
12082 appConfig.secondaryToolbar.imageAltTextSettingsButton?.classList.remove("hidden");
12083 appConfig.secondaryToolbar.imageAltTextSettingsSeparator?.classList.remove("hidden");
12085 this.secondaryToolbar = new SecondaryToolbar(appConfig.secondaryToolbar, eventBus);
12087 if (this.supportsFullscreen && appConfig.secondaryToolbar?.presentationModeButton) {
12088 this.pdfPresentationMode = new PDFPresentationMode({
12094 if (appConfig.passwordOverlay) {
12095 this.passwordPrompt = new PasswordPrompt(appConfig.passwordOverlay, this.overlayManager, this.isViewerEmbedded);
12097 if (appConfig.sidebar?.outlineView) {
12098 this.pdfOutlineViewer = new PDFOutlineViewer({
12099 container: appConfig.sidebar.outlineView,
12102 linkService: pdfLinkService,
12106 if (appConfig.sidebar?.attachmentsView) {
12107 this.pdfAttachmentViewer = new PDFAttachmentViewer({
12108 container: appConfig.sidebar.attachmentsView,
12114 if (appConfig.sidebar?.layersView) {
12115 this.pdfLayerViewer = new PDFLayerViewer({
12116 container: appConfig.sidebar.layersView,
12121 if (appConfig.sidebar) {
12122 this.pdfSidebar = new PDFSidebar({
12123 elements: appConfig.sidebar,
12127 this.pdfSidebar.onToggled = this.forceRendering.bind(this);
12128 this.pdfSidebar.onUpdateThumbnails = () => {
12129 for (const pageView of pdfViewer.getCachedPageViews()) {
12130 if (pageView.renderingState === RenderingStates.FINISHED) {
12131 this.pdfThumbnailViewer.getThumbnail(pageView.id - 1)?.setImage(pageView);
12134 this.pdfThumbnailViewer.scrollThumbnailIntoView(pdfViewer.currentPageNumber);
12138 async run(config) {
12139 await this.initialize(config);
12145 file = window.location.href;
12146 if (!AppOptions.get("supportsDocumentFonts")) {
12147 AppOptions.set("disableFontFace", true);
12148 this.l10n.get("pdfjs-web-fonts-disabled").then(msg => {
12152 if (!this.supportsPrinting) {
12153 appConfig.toolbar?.print?.classList.add("hidden");
12154 appConfig.secondaryToolbar?.printButton.classList.add("hidden");
12156 if (!this.supportsFullscreen) {
12157 appConfig.secondaryToolbar?.presentationModeButton.classList.add("hidden");
12159 if (this.supportsIntegratedFind) {
12160 appConfig.findBar?.toggleButton?.classList.add("hidden");
12162 this.setTitleUsingUrl(file, file);
12163 this.externalServices.initPassiveLoading();
12165 get externalServices() {
12166 return shadow(this, "externalServices", new ExternalServices());
12168 get initialized() {
12169 return this._initializedCapability.settled;
12171 get initializedPromise() {
12172 return this._initializedCapability.promise;
12174 updateZoom(steps, scaleFactor, origin) {
12175 if (this.pdfViewer.isInPresentationMode) {
12178 this.pdfViewer.updateScale({
12179 drawingDelay: AppOptions.get("defaultZoomDelay"),
12186 this.updateZoom(1);
12189 this.updateZoom(-1);
12192 if (this.pdfViewer.isInPresentationMode) {
12195 this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
12197 touchPinchCallback(origin, prevDistance, distance) {
12198 if (this.supportsPinchToZoom) {
12199 const newScaleFactor = this._accumulateFactor(this.pdfViewer.currentScale, distance / prevDistance, "_touchUnusedFactor");
12200 this.updateZoom(null, newScaleFactor, origin);
12202 const PIXELS_PER_LINE_SCALE = 30;
12203 const ticks = this._accumulateTicks((distance - prevDistance) / PIXELS_PER_LINE_SCALE, "_touchUnusedTicks");
12204 this.updateZoom(ticks, null, origin);
12207 touchPinchEndCallback() {
12208 this._touchUnusedTicks = 0;
12209 this._touchUnusedFactor = 1;
12212 return this.pdfDocument ? this.pdfDocument.numPages : 0;
12215 return this.pdfViewer.currentPageNumber;
12218 this.pdfViewer.currentPageNumber = val;
12220 get supportsPrinting() {
12221 return PDFPrintServiceFactory.supportsPrinting;
12223 get supportsFullscreen() {
12224 return shadow(this, "supportsFullscreen", document.fullscreenEnabled);
12226 get supportsPinchToZoom() {
12227 return shadow(this, "supportsPinchToZoom", AppOptions.get("supportsPinchToZoom"));
12229 get supportsIntegratedFind() {
12230 return shadow(this, "supportsIntegratedFind", AppOptions.get("supportsIntegratedFind"));
12233 const barElement = document.getElementById("loadingBar");
12234 const bar = barElement ? new ProgressBar(barElement) : null;
12235 return shadow(this, "loadingBar", bar);
12237 get supportsMouseWheelZoomCtrlKey() {
12238 return shadow(this, "supportsMouseWheelZoomCtrlKey", AppOptions.get("supportsMouseWheelZoomCtrlKey"));
12240 get supportsMouseWheelZoomMetaKey() {
12241 return shadow(this, "supportsMouseWheelZoomMetaKey", AppOptions.get("supportsMouseWheelZoomMetaKey"));
12243 get supportsCaretBrowsingMode() {
12244 return AppOptions.get("supportsCaretBrowsingMode");
12246 moveCaret(isUp, select) {
12247 this._caretBrowsing ||= new CaretBrowsingMode(this._globalAbortController.signal, this.appConfig.mainContainer, this.appConfig.viewerContainer, this.appConfig.toolbar?.container);
12248 this._caretBrowsing.moveCaret(isUp, select);
12250 setTitleUsingUrl(url = "", downloadUrl = null) {
12252 this.baseUrl = url.split("#", 1)[0];
12254 this._downloadUrl = downloadUrl === url ? this.baseUrl : downloadUrl.split("#", 1)[0];
12256 if (isDataScheme(url)) {
12257 this._hideViewBookmark();
12259 AppOptions.set("docBaseUrl", this.baseUrl);
12261 let title = getPdfFilenameFromUrl(url, "");
12264 title = decodeURIComponent(getFilenameFromUrl(url));
12267 this.setTitle(title || url);
12269 setTitle(title = this._title) {
12270 this._title = title;
12271 if (this.isViewerEmbedded) {
12274 const editorIndicator = this._hasAnnotationEditors && !this.pdfRenderingQueue.printing;
12275 document.title = `${editorIndicator ? "* " : ""}${title}`;
12277 get _docFilename() {
12278 return this._contentDispositionFilename || getPdfFilenameFromUrl(this.url);
12280 _hideViewBookmark() {
12283 } = this.appConfig;
12284 secondaryToolbar?.viewBookmarkButton.classList.add("hidden");
12285 if (secondaryToolbar?.presentationModeButton.classList.contains("hidden")) {
12286 document.getElementById("viewBookmarkSeparator")?.classList.add("hidden");
12290 this._unblockDocumentLoadEvent();
12291 this._hideViewBookmark();
12292 if (!this.pdfLoadingTask) {
12295 const promises = [];
12296 promises.push(this.pdfLoadingTask.destroy());
12297 this.pdfLoadingTask = null;
12298 if (this.pdfDocument) {
12299 this.pdfDocument = null;
12300 this.pdfThumbnailViewer?.setDocument(null);
12301 this.pdfViewer.setDocument(null);
12302 this.pdfLinkService.setDocument(null);
12303 this.pdfDocumentProperties?.setDocument(null);
12305 this.pdfLinkService.externalLinkEnabled = true;
12307 this.isInitialViewSet = false;
12310 this._downloadUrl = "";
12311 this.documentInfo = null;
12312 this.metadata = null;
12313 this._contentDispositionFilename = null;
12314 this._contentLength = null;
12315 this._saveInProgress = false;
12316 this._hasAnnotationEditors = false;
12317 promises.push(this.pdfScriptingManager.destroyPromise, this.passwordPrompt.close());
12319 this.pdfSidebar?.reset();
12320 this.pdfOutlineViewer?.reset();
12321 this.pdfAttachmentViewer?.reset();
12322 this.pdfLayerViewer?.reset();
12323 this.pdfHistory?.reset();
12324 this.findBar?.reset();
12325 this.toolbar?.reset();
12326 this.secondaryToolbar?.reset();
12327 this._PDFBug?.cleanup();
12328 await Promise.all(promises);
12331 if (this.pdfLoadingTask) {
12332 await this.close();
12334 const workerParams = AppOptions.getAll(OptionKind.WORKER);
12335 Object.assign(GlobalWorkerOptions, workerParams);
12336 if (args.data && isPdfFile(args.filename)) {
12337 this._contentDispositionFilename = args.filename;
12339 const apiParams = AppOptions.getAll(OptionKind.API);
12340 const loadingTask = getDocument({
12344 this.pdfLoadingTask = loadingTask;
12345 loadingTask.onPassword = (updateCallback, reason) => {
12346 if (this.isViewerEmbedded) {
12347 this._unblockDocumentLoadEvent();
12349 this.pdfLinkService.externalLinkEnabled = false;
12350 this.passwordPrompt.setUpdateCallback(updateCallback, reason);
12351 this.passwordPrompt.open();
12353 loadingTask.onProgress = ({
12357 this.progress(loaded / total);
12359 return loadingTask.promise.then(pdfDocument => {
12360 this.load(pdfDocument);
12362 if (loadingTask !== this.pdfLoadingTask) {
12365 let key = "pdfjs-loading-error";
12366 if (reason instanceof InvalidPDFException) {
12367 key = "pdfjs-invalid-file-error";
12368 } else if (reason instanceof ResponseException) {
12369 key = reason.missing ? "pdfjs-missing-file-error" : "pdfjs-unexpected-response-error";
12371 return this._documentError(key, {
12372 message: reason.message
12381 data = await this.pdfDocument.getData();
12383 this.downloadManager.download(data, this._downloadUrl, this._docFilename);
12386 if (this._saveInProgress) {
12389 this._saveInProgress = true;
12390 await this.pdfScriptingManager.dispatchWillSave();
12392 const data = await this.pdfDocument.saveDocument();
12393 this.downloadManager.download(data, this._downloadUrl, this._docFilename);
12395 console.error(`Error when saving the document:`, reason);
12396 await this.download();
12398 await this.pdfScriptingManager.dispatchDidSave();
12399 this._saveInProgress = false;
12401 if (this._hasAnnotationEditors) {
12402 this.externalServices.reportTelemetry({
12406 stats: this.pdfDocument?.annotationStorage.editorStats
12411 async downloadOrSave() {
12414 } = this.appConfig.appContainer;
12415 classList.add("wait");
12416 await (this.pdfDocument?.annotationStorage.size > 0 ? this.save() : this.download());
12417 classList.remove("wait");
12419 async _documentError(key, moreInfo = null) {
12420 this._unblockDocumentLoadEvent();
12421 const message = await this._otherError(key || "pdfjs-loading-error", moreInfo);
12422 this.eventBus.dispatch("documenterror", {
12425 reason: moreInfo?.message ?? null
12428 async _otherError(key, moreInfo = null) {
12429 const message = await this.l10n.get(key);
12430 const moreInfoText = [`PDF.js v${version || "?"} (build: ${build || "?"})`];
12432 moreInfoText.push(`Message: ${moreInfo.message}`);
12433 if (moreInfo.stack) {
12434 moreInfoText.push(`Stack: ${moreInfo.stack}`);
12436 if (moreInfo.filename) {
12437 moreInfoText.push(`File: ${moreInfo.filename}`);
12439 if (moreInfo.lineNumber) {
12440 moreInfoText.push(`Line: ${moreInfo.lineNumber}`);
12444 console.error(`${message}\n\n${moreInfoText.join("\n")}`);
12448 const percent = Math.round(level * 100);
12449 if (!this.loadingBar || percent <= this.loadingBar.percent) {
12452 this.loadingBar.percent = percent;
12453 if (this.pdfDocument?.loadingParams.disableAutoFetch ?? AppOptions.get("disableAutoFetch")) {
12454 this.loadingBar.setDisableAutoFetch();
12457 load(pdfDocument) {
12458 this.pdfDocument = pdfDocument;
12459 pdfDocument.getDownloadInfo().then(({
12462 this._contentLength = length;
12463 this.loadingBar?.hide();
12464 firstPagePromise.then(() => {
12465 this.eventBus.dispatch("documentloaded", {
12470 const pageLayoutPromise = pdfDocument.getPageLayout().catch(() => {});
12471 const pageModePromise = pdfDocument.getPageMode().catch(() => {});
12472 const openActionPromise = pdfDocument.getOpenAction().catch(() => {});
12473 this.toolbar?.setPagesCount(pdfDocument.numPages, false);
12474 this.secondaryToolbar?.setPagesCount(pdfDocument.numPages);
12475 this.pdfLinkService.setDocument(pdfDocument);
12476 this.pdfDocumentProperties?.setDocument(pdfDocument);
12477 const pdfViewer = this.pdfViewer;
12478 pdfViewer.setDocument(pdfDocument);
12484 this.pdfThumbnailViewer?.setDocument(pdfDocument);
12485 const storedPromise = (this.store = new ViewHistory(pdfDocument.fingerprints[0])).getMultiple({
12487 zoom: DEFAULT_SCALE_VALUE,
12491 sidebarView: SidebarView.UNKNOWN,
12492 scrollMode: ScrollMode.UNKNOWN,
12493 spreadMode: SpreadMode.UNKNOWN
12494 }).catch(() => {});
12495 firstPagePromise.then(pdfPage => {
12496 this.loadingBar?.setWidth(this.appConfig.viewerContainer);
12497 this._initializeAnnotationStorageCallbacks(pdfDocument);
12498 Promise.all([animationStarted, storedPromise, pageLayoutPromise, pageModePromise, openActionPromise]).then(async ([timeStamp, stored, pageLayout, pageMode, openAction]) => {
12499 const viewOnLoad = AppOptions.get("viewOnLoad");
12500 this._initializePdfHistory({
12501 fingerprint: pdfDocument.fingerprints[0],
12503 initialDest: openAction?.dest
12505 const initialBookmark = this.initialBookmark;
12506 const zoom = AppOptions.get("defaultZoomValue");
12507 let hash = zoom ? `zoom=${zoom}` : null;
12508 let rotation = null;
12509 let sidebarView = AppOptions.get("sidebarViewOnLoad");
12510 let scrollMode = AppOptions.get("scrollModeOnLoad");
12511 let spreadMode = AppOptions.get("spreadModeOnLoad");
12512 if (stored?.page && viewOnLoad !== ViewOnLoad.INITIAL) {
12513 hash = `page=${stored.page}&zoom=${zoom || stored.zoom},` + `${stored.scrollLeft},${stored.scrollTop}`;
12514 rotation = parseInt(stored.rotation, 10);
12515 if (sidebarView === SidebarView.UNKNOWN) {
12516 sidebarView = stored.sidebarView | 0;
12518 if (scrollMode === ScrollMode.UNKNOWN) {
12519 scrollMode = stored.scrollMode | 0;
12521 if (spreadMode === SpreadMode.UNKNOWN) {
12522 spreadMode = stored.spreadMode | 0;
12525 if (pageMode && sidebarView === SidebarView.UNKNOWN) {
12526 sidebarView = apiPageModeToSidebarView(pageMode);
12528 if (pageLayout && scrollMode === ScrollMode.UNKNOWN && spreadMode === SpreadMode.UNKNOWN) {
12529 const modes = apiPageLayoutToViewerModes(pageLayout);
12530 spreadMode = modes.spreadMode;
12532 this.setInitialView(hash, {
12538 this.eventBus.dispatch("documentinit", {
12541 if (!this.isViewerEmbedded) {
12544 await Promise.race([pagesPromise, new Promise(resolve => {
12545 setTimeout(resolve, FORCE_PAGES_LOADED_TIMEOUT);
12547 if (!initialBookmark && !hash) {
12550 if (pdfViewer.hasEqualPageSizes) {
12553 this.initialBookmark = initialBookmark;
12554 pdfViewer.currentScaleValue = pdfViewer.currentScaleValue;
12555 this.setInitialView(hash);
12557 this.setInitialView();
12558 }).then(function () {
12559 pdfViewer.update();
12562 pagesPromise.then(() => {
12563 this._unblockDocumentLoadEvent();
12564 this._initializeAutoPrint(pdfDocument, openActionPromise);
12566 this._documentError("pdfjs-loading-error", {
12567 message: reason.message
12570 onePageRendered.then(data => {
12571 this.externalServices.reportTelemetry({
12573 timestamp: data.timestamp
12575 if (this.pdfOutlineViewer) {
12576 pdfDocument.getOutline().then(outline => {
12577 if (pdfDocument !== this.pdfDocument) {
12580 this.pdfOutlineViewer.render({
12586 if (this.pdfAttachmentViewer) {
12587 pdfDocument.getAttachments().then(attachments => {
12588 if (pdfDocument !== this.pdfDocument) {
12591 this.pdfAttachmentViewer.render({
12596 if (this.pdfLayerViewer) {
12597 pdfViewer.optionalContentConfigPromise.then(optionalContentConfig => {
12598 if (pdfDocument !== this.pdfDocument) {
12601 this.pdfLayerViewer.render({
12602 optionalContentConfig,
12608 this._initializePageLabels(pdfDocument);
12609 this._initializeMetadata(pdfDocument);
12611 async _scriptingDocProperties(pdfDocument) {
12612 if (!this.documentInfo) {
12613 await new Promise(resolve => {
12614 this.eventBus._on("metadataloaded", resolve, {
12618 if (pdfDocument !== this.pdfDocument) {
12622 if (!this._contentLength) {
12623 await new Promise(resolve => {
12624 this.eventBus._on("documentloaded", resolve, {
12628 if (pdfDocument !== this.pdfDocument) {
12633 ...this.documentInfo,
12634 baseURL: this.baseUrl,
12635 filesize: this._contentLength,
12636 filename: this._docFilename,
12637 metadata: this.metadata?.getRaw(),
12638 authors: this.metadata?.get("dc:creator"),
12639 numPages: this.pagesCount,
12643 async _initializeAutoPrint(pdfDocument, openActionPromise) {
12644 const [openAction, jsActions] = await Promise.all([openActionPromise, this.pdfViewer.enableScripting ? null : pdfDocument.getJSActions()]);
12645 if (pdfDocument !== this.pdfDocument) {
12648 let triggerAutoPrint = openAction?.action === "Print";
12650 console.warn("Warning: JavaScript support is not enabled");
12651 for (const name in jsActions) {
12652 if (triggerAutoPrint) {
12663 triggerAutoPrint = jsActions[name].some(js => AutoPrintRegExp.test(js));
12666 if (triggerAutoPrint) {
12667 this.triggerPrinting();
12670 async _initializeMetadata(pdfDocument) {
12674 contentDispositionFilename,
12676 } = await pdfDocument.getMetadata();
12677 if (pdfDocument !== this.pdfDocument) {
12680 this.documentInfo = info;
12681 this.metadata = metadata;
12682 this._contentDispositionFilename ??= contentDispositionFilename;
12683 this._contentLength ??= contentLength;
12684 console.log(`PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` + `${(info.Producer || "-").trim()} / ${(info.Creator || "-").trim()}] ` + `(PDF.js: ${version || "?"} [${build || "?"}])`);
12685 let pdfTitle = info.Title;
12686 const metadataTitle = metadata?.get("dc:title");
12687 if (metadataTitle) {
12688 if (metadataTitle !== "Untitled" && !/[\uFFF0-\uFFFF]/g.test(metadataTitle)) {
12689 pdfTitle = metadataTitle;
12693 this.setTitle(`${pdfTitle} - ${this._contentDispositionFilename || this._title}`);
12694 } else if (this._contentDispositionFilename) {
12695 this.setTitle(this._contentDispositionFilename);
12697 if (info.IsXFAPresent && !info.IsAcroFormPresent && !pdfDocument.isPureXfa) {
12698 if (pdfDocument.loadingParams.enableXfa) {
12699 console.warn("Warning: XFA Foreground documents are not supported");
12701 console.warn("Warning: XFA support is not enabled");
12703 } else if ((info.IsAcroFormPresent || info.IsXFAPresent) && !this.pdfViewer.renderForms) {
12704 console.warn("Warning: Interactive form support is not enabled");
12706 if (info.IsSignaturesPresent) {
12707 console.warn("Warning: Digital signatures validation is not supported");
12709 this.eventBus.dispatch("metadataloaded", {
12713 async _initializePageLabels(pdfDocument) {
12714 const labels = await pdfDocument.getPageLabels();
12715 if (pdfDocument !== this.pdfDocument) {
12718 if (!labels || AppOptions.get("disablePageLabels")) {
12721 const numLabels = labels.length;
12722 let standardLabels = 0,
12724 for (let i = 0; i < numLabels; i++) {
12725 const label = labels[i];
12726 if (label === (i + 1).toString()) {
12728 } else if (label === "") {
12734 if (standardLabels >= numLabels || emptyLabels >= numLabels) {
12739 pdfThumbnailViewer,
12742 pdfViewer.setPageLabels(labels);
12743 pdfThumbnailViewer?.setPageLabels(labels);
12744 toolbar?.setPagesCount(numLabels, true);
12745 toolbar?.setPageNumber(pdfViewer.currentPageNumber, pdfViewer.currentPageLabel);
12747 _initializePdfHistory({
12752 if (!this.pdfHistory) {
12755 this.pdfHistory.initialize({
12757 resetHistory: viewOnLoad === ViewOnLoad.INITIAL,
12758 updateUrl: AppOptions.get("historyUpdateUrl")
12760 if (this.pdfHistory.initialBookmark) {
12761 this.initialBookmark = this.pdfHistory.initialBookmark;
12762 this.initialRotation = this.pdfHistory.initialRotation;
12764 if (initialDest && !this.initialBookmark && viewOnLoad === ViewOnLoad.UNKNOWN) {
12765 this.initialBookmark = JSON.stringify(initialDest);
12766 this.pdfHistory.push({
12767 explicitDest: initialDest,
12772 _initializeAnnotationStorageCallbacks(pdfDocument) {
12773 if (pdfDocument !== this.pdfDocument) {
12779 annotationStorage.onSetModified = () => {
12780 window.addEventListener("beforeunload", beforeUnload);
12782 annotationStorage.onResetModified = () => {
12783 window.removeEventListener("beforeunload", beforeUnload);
12785 annotationStorage.onAnnotationEditor = typeStr => {
12786 this._hasAnnotationEditors = !!typeStr;
12790 setInitialView(storedHash, {
12796 const setRotation = angle => {
12797 if (isValidRotation(angle)) {
12798 this.pdfViewer.pagesRotation = angle;
12801 const setViewerModes = (scroll, spread) => {
12802 if (isValidScrollMode(scroll)) {
12803 this.pdfViewer.scrollMode = scroll;
12805 if (isValidSpreadMode(spread)) {
12806 this.pdfViewer.spreadMode = spread;
12809 this.isInitialViewSet = true;
12810 this.pdfSidebar?.setInitialView(sidebarView);
12811 setViewerModes(scrollMode, spreadMode);
12812 if (this.initialBookmark) {
12813 setRotation(this.initialRotation);
12814 delete this.initialRotation;
12815 this.pdfLinkService.setHash(this.initialBookmark);
12816 this.initialBookmark = null;
12817 } else if (storedHash) {
12818 setRotation(rotation);
12819 this.pdfLinkService.setHash(storedHash);
12821 this.toolbar?.setPageNumber(this.pdfViewer.currentPageNumber, this.pdfViewer.currentPageLabel);
12822 this.secondaryToolbar?.setPageNumber(this.pdfViewer.currentPageNumber);
12823 if (!this.pdfViewer.currentScaleValue) {
12824 this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
12828 if (!this.pdfDocument) {
12831 this.pdfViewer.cleanup();
12832 this.pdfThumbnailViewer?.cleanup();
12833 this.pdfDocument.cleanup(AppOptions.get("fontExtraProperties"));
12836 this.pdfRenderingQueue.printing = !!this.printService;
12837 this.pdfRenderingQueue.isThumbnailViewEnabled = this.pdfSidebar?.visibleView === SidebarView.THUMBS;
12838 this.pdfRenderingQueue.renderHighestPriority();
12841 this._printAnnotationStoragePromise = this.pdfScriptingManager.dispatchWillPrint().catch(() => {}).then(() => this.pdfDocument?.annotationStorage.print);
12842 if (this.printService) {
12845 if (!this.supportsPrinting) {
12846 this._otherError("pdfjs-printing-not-supported");
12849 if (!this.pdfViewer.pageViewsReady) {
12850 this.l10n.get("pdfjs-printing-not-ready").then(msg => {
12855 this.printService = PDFPrintServiceFactory.createPrintService({
12856 pdfDocument: this.pdfDocument,
12857 pagesOverview: this.pdfViewer.getPagesOverview(),
12858 printContainer: this.appConfig.printContainer,
12859 printResolution: AppOptions.get("printResolution"),
12860 printAnnotationStoragePromise: this._printAnnotationStoragePromise
12862 this.forceRendering();
12864 this.printService.layout();
12865 if (this._hasAnnotationEditors) {
12866 this.externalServices.reportTelemetry({
12870 stats: this.pdfDocument?.annotationStorage.editorStats
12876 if (this._printAnnotationStoragePromise) {
12877 this._printAnnotationStoragePromise.then(() => {
12878 this.pdfScriptingManager.dispatchDidPrint();
12880 this._printAnnotationStoragePromise = null;
12882 if (this.printService) {
12883 this.printService.destroy();
12884 this.printService = null;
12885 this.pdfDocument?.annotationStorage.resetModified();
12887 this.forceRendering();
12890 rotatePages(delta) {
12891 this.pdfViewer.pagesRotation += delta;
12893 requestPresentationMode() {
12894 this.pdfPresentationMode?.request();
12896 triggerPrinting() {
12897 if (this.supportsPrinting) {
12902 if (this._eventBusAbortController) {
12905 const ac = this._eventBusAbortController = new AbortController();
12912 pdfDocumentProperties,
12916 eventBus._on("resize", onResize.bind(this), opts);
12917 eventBus._on("hashchange", onHashchange.bind(this), opts);
12918 eventBus._on("beforeprint", this.beforePrint.bind(this), opts);
12919 eventBus._on("afterprint", this.afterPrint.bind(this), opts);
12920 eventBus._on("pagerender", onPageRender.bind(this), opts);
12921 eventBus._on("pagerendered", onPageRendered.bind(this), opts);
12922 eventBus._on("updateviewarea", onUpdateViewarea.bind(this), opts);
12923 eventBus._on("pagechanging", onPageChanging.bind(this), opts);
12924 eventBus._on("scalechanging", onScaleChanging.bind(this), opts);
12925 eventBus._on("rotationchanging", onRotationChanging.bind(this), opts);
12926 eventBus._on("sidebarviewchanged", onSidebarViewChanged.bind(this), opts);
12927 eventBus._on("pagemode", onPageMode.bind(this), opts);
12928 eventBus._on("namedaction", onNamedAction.bind(this), opts);
12929 eventBus._on("presentationmodechanged", evt => pdfViewer.presentationModeState = evt.state, opts);
12930 eventBus._on("presentationmode", this.requestPresentationMode.bind(this), opts);
12931 eventBus._on("switchannotationeditormode", evt => pdfViewer.annotationEditorMode = evt, opts);
12932 eventBus._on("print", this.triggerPrinting.bind(this), opts);
12933 eventBus._on("download", this.downloadOrSave.bind(this), opts);
12934 eventBus._on("firstpage", () => this.page = 1, opts);
12935 eventBus._on("lastpage", () => this.page = this.pagesCount, opts);
12936 eventBus._on("nextpage", () => pdfViewer.nextPage(), opts);
12937 eventBus._on("previouspage", () => pdfViewer.previousPage(), opts);
12938 eventBus._on("zoomin", this.zoomIn.bind(this), opts);
12939 eventBus._on("zoomout", this.zoomOut.bind(this), opts);
12940 eventBus._on("zoomreset", this.zoomReset.bind(this), opts);
12941 eventBus._on("pagenumberchanged", onPageNumberChanged.bind(this), opts);
12942 eventBus._on("scalechanged", evt => pdfViewer.currentScaleValue = evt.value, opts);
12943 eventBus._on("rotatecw", this.rotatePages.bind(this, 90), opts);
12944 eventBus._on("rotateccw", this.rotatePages.bind(this, -90), opts);
12945 eventBus._on("optionalcontentconfig", evt => pdfViewer.optionalContentConfigPromise = evt.promise, opts);
12946 eventBus._on("switchscrollmode", evt => pdfViewer.scrollMode = evt.mode, opts);
12947 eventBus._on("scrollmodechanged", onViewerModesChanged.bind(this, "scrollMode"), opts);
12948 eventBus._on("switchspreadmode", evt => pdfViewer.spreadMode = evt.mode, opts);
12949 eventBus._on("spreadmodechanged", onViewerModesChanged.bind(this, "spreadMode"), opts);
12950 eventBus._on("imagealttextsettings", onImageAltTextSettings.bind(this), opts);
12951 eventBus._on("documentproperties", () => pdfDocumentProperties?.open(), opts);
12952 eventBus._on("findfromurlhash", onFindFromUrlHash.bind(this), opts);
12953 eventBus._on("updatefindmatchescount", onUpdateFindMatchesCount.bind(this), opts);
12954 eventBus._on("updatefindcontrolstate", onUpdateFindControlState.bind(this), opts);
12955 eventBus._on("annotationeditorstateschanged", evt => externalServices.updateEditorStates(evt), opts);
12956 eventBus._on("reporttelemetry", evt => externalServices.reportTelemetry(evt.details), opts);
12957 eventBus._on("setpreference", evt => preferences.set(evt.name, evt.value), opts);
12959 bindWindowEvents() {
12960 if (this._windowAbortController) {
12963 this._windowAbortController = new AbortController();
12970 _windowAbortController: {
12974 this._touchManager = new TouchManager({
12976 isPinchingDisabled: () => pdfViewer.isInPresentationMode,
12977 isPinchingStopped: () => this.overlayManager?.active,
12978 onPinching: this.touchPinchCallback.bind(this),
12979 onPinchEnd: this.touchPinchEndCallback.bind(this),
12982 function addWindowResolutionChange(evt = null) {
12984 pdfViewer.refresh();
12986 const mediaQueryList = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`);
12987 mediaQueryList.addEventListener("change", addWindowResolutionChange, {
12992 addWindowResolutionChange();
12993 window.addEventListener("wheel", onWheel.bind(this), {
12997 window.addEventListener("click", onClick.bind(this), {
13000 window.addEventListener("keydown", onKeyDown.bind(this), {
13003 window.addEventListener("keyup", onKeyUp.bind(this), {
13006 window.addEventListener("resize", () => eventBus.dispatch("resize", {
13011 window.addEventListener("hashchange", () => {
13012 eventBus.dispatch("hashchange", {
13014 hash: document.location.hash.substring(1)
13019 window.addEventListener("beforeprint", () => eventBus.dispatch("beforeprint", {
13024 window.addEventListener("afterprint", () => eventBus.dispatch("afterprint", {
13029 window.addEventListener("updatefromsandbox", evt => {
13030 eventBus.dispatch("updatefromsandbox", {
13037 const scrollend = () => {
13038 this._isScrolling = false;
13039 mainContainer.addEventListener("scroll", scroll, {
13043 mainContainer.removeEventListener("scrollend", scrollend);
13044 mainContainer.removeEventListener("blur", scrollend);
13046 const scroll = () => {
13047 if (this._isCtrlKeyDown) {
13050 mainContainer.removeEventListener("scroll", scroll);
13051 this._isScrolling = true;
13052 mainContainer.addEventListener("scrollend", scrollend, {
13055 mainContainer.addEventListener("blur", scrollend, {
13059 mainContainer.addEventListener("scroll", scroll, {
13065 this._eventBusAbortController?.abort();
13066 this._eventBusAbortController = null;
13068 unbindWindowEvents() {
13069 this._windowAbortController?.abort();
13070 this._windowAbortController = null;
13071 this._touchManager = null;
13073 async testingClose() {
13074 this.unbindEvents();
13075 this.unbindWindowEvents();
13076 this._globalAbortController?.abort();
13077 this._globalAbortController = null;
13078 this.findBar?.close();
13079 await Promise.all([this.l10n?.destroy(), this.close()]);
13081 _accumulateTicks(ticks, prop) {
13082 if (this[prop] > 0 && ticks < 0 || this[prop] < 0 && ticks > 0) {
13085 this[prop] += ticks;
13086 const wholeTicks = Math.trunc(this[prop]);
13087 this[prop] -= wholeTicks;
13090 _accumulateFactor(previousScale, factor, prop) {
13091 if (factor === 1) {
13094 if (this[prop] > 1 && factor < 1 || this[prop] < 1 && factor > 1) {
13097 const newFactor = Math.floor(previousScale * factor * this[prop] * 100) / (100 * previousScale);
13098 this[prop] = factor / newFactor;
13101 _unblockDocumentLoadEvent() {
13102 document.blockUnblockOnload?.(false);
13103 this._unblockDocumentLoadEvent = () => {};
13105 get scriptingReady() {
13106 return this.pdfScriptingManager.ready;
13109 initCom(PDFViewerApplication);
13110 function onPageRender({
13113 if (pageNumber === this.page) {
13114 this.toolbar?.updateLoadingIndicatorState(true);
13117 function onPageRendered({
13121 if (pageNumber === this.page) {
13122 this.toolbar?.updateLoadingIndicatorState(false);
13124 if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) {
13125 const pageView = this.pdfViewer.getPageView(pageNumber - 1);
13126 const thumbnailView = this.pdfThumbnailViewer?.getThumbnail(pageNumber - 1);
13128 thumbnailView?.setImage(pageView);
13132 this._otherError("pdfjs-rendering-error", error);
13135 function onPageMode({
13141 view = SidebarView.THUMBS;
13145 view = SidebarView.OUTLINE;
13147 case "attachments":
13148 view = SidebarView.ATTACHMENTS;
13151 view = SidebarView.LAYERS;
13154 view = SidebarView.NONE;
13157 console.error('Invalid "pagemode" hash parameter: ' + mode);
13160 this.pdfSidebar?.switchView(view, true);
13162 function onNamedAction(evt) {
13163 switch (evt.action) {
13165 this.appConfig.toolbar?.pageNumber.select();
13168 if (!this.supportsIntegratedFind) {
13169 this.findBar?.toggle();
13173 this.triggerPrinting();
13176 this.downloadOrSave();
13180 function onSidebarViewChanged({
13183 this.pdfRenderingQueue.isThumbnailViewEnabled = view === SidebarView.THUMBS;
13184 if (this.isInitialViewSet) {
13185 this.store?.set("sidebarView", view).catch(() => {});
13188 function onUpdateViewarea({
13191 if (this.isInitialViewSet) {
13192 this.store?.setMultiple({
13193 page: location.pageNumber,
13194 zoom: location.scale,
13195 scrollLeft: location.left,
13196 scrollTop: location.top,
13197 rotation: location.rotation
13198 }).catch(() => {});
13200 if (this.appConfig.secondaryToolbar) {
13201 this.appConfig.secondaryToolbar.viewBookmarkButton.href = this.pdfLinkService.getAnchorUrl(location.pdfOpenParams);
13204 function onViewerModesChanged(name, evt) {
13205 if (this.isInitialViewSet && !this.pdfViewer.isInPresentationMode) {
13206 this.store?.set(name, evt.mode).catch(() => {});
13209 function onResize() {
13215 if (pdfRenderingQueue.printing && window.matchMedia("print").matches) {
13218 if (!pdfDocument) {
13221 const currentScaleValue = pdfViewer.currentScaleValue;
13222 if (currentScaleValue === "auto" || currentScaleValue === "page-fit" || currentScaleValue === "page-width") {
13223 pdfViewer.currentScaleValue = currentScaleValue;
13225 pdfViewer.update();
13227 function onHashchange(evt) {
13228 const hash = evt.hash;
13232 if (!this.isInitialViewSet) {
13233 this.initialBookmark = hash;
13234 } else if (!this.pdfHistory?.popStateInProgress) {
13235 this.pdfLinkService.setHash(hash);
13238 function onPageNumberChanged(evt) {
13242 if (evt.value !== "") {
13243 this.pdfLinkService.goToPage(evt.value);
13245 if (evt.value !== pdfViewer.currentPageNumber.toString() && evt.value !== pdfViewer.currentPageLabel) {
13246 this.toolbar?.setPageNumber(pdfViewer.currentPageNumber, pdfViewer.currentPageLabel);
13249 function onImageAltTextSettings() {
13250 this.imageAltTextSettings?.open({
13251 enableGuessAltText: AppOptions.get("enableGuessAltText"),
13252 enableNewAltTextWhenAddingImage: AppOptions.get("enableNewAltTextWhenAddingImage")
13255 function onFindFromUrlHash(evt) {
13256 this.eventBus.dispatch("find", {
13257 source: evt.source,
13260 caseSensitive: false,
13262 highlightAll: true,
13263 findPrevious: false,
13264 matchDiacritics: true
13267 function onUpdateFindMatchesCount({
13270 if (this.supportsIntegratedFind) {
13271 this.externalServices.updateFindMatchesCount(matchesCount);
13273 this.findBar?.updateResultsCount(matchesCount);
13276 function onUpdateFindControlState({
13283 if (this.supportsIntegratedFind) {
13284 this.externalServices.updateFindControlState({
13286 findPrevious: previous,
13292 this.findBar?.updateUIState(state, previous, matchesCount);
13295 function onScaleChanging(evt) {
13296 this.toolbar?.setPageScale(evt.presetValue, evt.scale);
13297 this.pdfViewer.update();
13299 function onRotationChanging(evt) {
13300 if (this.pdfThumbnailViewer) {
13301 this.pdfThumbnailViewer.pagesRotation = evt.pagesRotation;
13303 this.forceRendering();
13304 this.pdfViewer.currentPageNumber = evt.pageNumber;
13306 function onPageChanging({
13310 this.toolbar?.setPageNumber(pageNumber, pageLabel);
13311 this.secondaryToolbar?.setPageNumber(pageNumber);
13312 if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) {
13313 this.pdfThumbnailViewer?.scrollThumbnailIntoView(pageNumber);
13315 const currentPage = this.pdfViewer.getPageView(pageNumber - 1);
13316 this.toolbar?.updateLoadingIndicatorState(currentPage?.renderingState === RenderingStates.RUNNING);
13318 function onWheel(evt) {
13321 supportsMouseWheelZoomCtrlKey,
13322 supportsMouseWheelZoomMetaKey,
13323 supportsPinchToZoom
13325 if (pdfViewer.isInPresentationMode) {
13328 const deltaMode = evt.deltaMode;
13329 let scaleFactor = Math.exp(-evt.deltaY / 100);
13330 const isBuiltInMac = FeatureTest.platform.isMac;
13331 const isPinchToZoom = evt.ctrlKey && !this._isCtrlKeyDown && deltaMode === WheelEvent.DOM_DELTA_PIXEL && evt.deltaX === 0 && (Math.abs(scaleFactor - 1) < 0.05 || isBuiltInMac) && evt.deltaZ === 0;
13332 const origin = [evt.clientX, evt.clientY];
13333 if (isPinchToZoom || evt.ctrlKey && supportsMouseWheelZoomCtrlKey || evt.metaKey && supportsMouseWheelZoomMetaKey) {
13334 evt.preventDefault();
13335 if (this._isScrolling || document.visibilityState === "hidden" || this.overlayManager.active) {
13338 if (isPinchToZoom && supportsPinchToZoom) {
13339 scaleFactor = this._accumulateFactor(pdfViewer.currentScale, scaleFactor, "_wheelUnusedFactor");
13340 this.updateZoom(null, scaleFactor, origin);
13342 const delta = normalizeWheelEventDirection(evt);
13344 if (deltaMode === WheelEvent.DOM_DELTA_LINE || deltaMode === WheelEvent.DOM_DELTA_PAGE) {
13345 ticks = Math.abs(delta) >= 1 ? Math.sign(delta) : this._accumulateTicks(delta, "_wheelUnusedTicks");
13347 const PIXELS_PER_LINE_SCALE = 30;
13348 ticks = this._accumulateTicks(delta / PIXELS_PER_LINE_SCALE, "_wheelUnusedTicks");
13350 this.updateZoom(ticks, null, origin);
13354 function closeSecondaryToolbar(evt) {
13355 if (!this.secondaryToolbar?.isOpen) {
13358 const appConfig = this.appConfig;
13359 if (this.pdfViewer.containsElement(evt.target) || appConfig.toolbar?.container.contains(evt.target) && !appConfig.secondaryToolbar?.toggleButton.contains(evt.target)) {
13360 this.secondaryToolbar.close();
13363 function closeEditorUndoBar(evt) {
13364 if (!this.editorUndoBar?.isOpen) {
13367 if (this.appConfig.secondaryToolbar?.toolbar.contains(evt.target)) {
13368 this.editorUndoBar.hide();
13371 function onClick(evt) {
13372 closeSecondaryToolbar.call(this, evt);
13373 closeEditorUndoBar.call(this, evt);
13375 function onKeyUp(evt) {
13376 if (evt.key === "Control") {
13377 this._isCtrlKeyDown = false;
13380 function onKeyDown(evt) {
13381 this._isCtrlKeyDown = evt.key === "Control";
13382 if (this.editorUndoBar?.isOpen && evt.keyCode !== 9 && evt.keyCode !== 16 && !((evt.keyCode === 13 || evt.keyCode === 32) && getActiveOrFocusedElement() === this.appConfig.editorUndoBar.undoButton)) {
13383 this.editorUndoBar.hide();
13385 if (this.overlayManager.active) {
13392 const isViewerInPresentationMode = pdfViewer.isInPresentationMode;
13393 let handled = false,
13394 ensureViewerFocused = false;
13395 const cmd = (evt.ctrlKey ? 1 : 0) | (evt.altKey ? 2 : 0) | (evt.shiftKey ? 4 : 0) | (evt.metaKey ? 8 : 0);
13396 if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
13397 switch (evt.keyCode) {
13399 if (!this.supportsIntegratedFind && !evt.shiftKey) {
13400 this.findBar?.open();
13405 if (!this.supportsIntegratedFind) {
13408 } = this.findController;
13413 findPrevious: cmd === 5 || cmd === 12
13415 eventBus.dispatch("find", {
13438 if (!isViewerInPresentationMode) {
13446 if (isViewerInPresentationMode || this.page > 1) {
13449 ensureViewerFocused = true;
13453 if (isViewerInPresentationMode || this.page < this.pagesCount) {
13454 this.page = this.pagesCount;
13456 ensureViewerFocused = true;
13461 if (cmd === 3 || cmd === 10) {
13462 switch (evt.keyCode) {
13464 this.requestPresentationMode();
13466 this.externalServices.reportTelemetry({
13469 id: "presentationModeKeyboard"
13474 if (this.appConfig.toolbar) {
13475 this.appConfig.toolbar.pageNumber.select();
13482 if (ensureViewerFocused && !isViewerInPresentationMode) {
13485 evt.preventDefault();
13488 const curElement = getActiveOrFocusedElement();
13489 const curElementTagName = curElement?.tagName.toUpperCase();
13490 if (curElementTagName === "INPUT" || curElementTagName === "TEXTAREA" || curElementTagName === "SELECT" || curElementTagName === "BUTTON" && (evt.keyCode === 13 || evt.keyCode === 32) || curElement?.isContentEditable) {
13491 if (evt.keyCode !== 27) {
13497 turnOnlyIfPageFit = false;
13498 switch (evt.keyCode) {
13500 if (this.supportsCaretBrowsingMode) {
13501 this.moveCaret(true, false);
13506 if (pdfViewer.isVerticalScrollbarEnabled) {
13507 turnOnlyIfPageFit = true;
13512 if (!isViewerInPresentationMode) {
13513 turnOnlyIfPageFit = true;
13518 if (this.supportsCaretBrowsingMode) {
13521 if (pdfViewer.isHorizontalScrollbarEnabled) {
13522 turnOnlyIfPageFit = true;
13529 if (this.secondaryToolbar?.isOpen) {
13530 this.secondaryToolbar.close();
13533 if (!this.supportsIntegratedFind && this.findBar?.opened) {
13534 this.findBar.close();
13539 if (this.supportsCaretBrowsingMode) {
13540 this.moveCaret(false, false);
13545 if (pdfViewer.isVerticalScrollbarEnabled) {
13546 turnOnlyIfPageFit = true;
13552 if (!isViewerInPresentationMode) {
13553 turnOnlyIfPageFit = true;
13558 if (this.supportsCaretBrowsingMode) {
13561 if (pdfViewer.isHorizontalScrollbarEnabled) {
13562 turnOnlyIfPageFit = true;
13569 if (isViewerInPresentationMode || this.page > 1) {
13572 ensureViewerFocused = true;
13576 if (isViewerInPresentationMode || this.page < this.pagesCount) {
13577 this.page = this.pagesCount;
13579 ensureViewerFocused = true;
13583 this.pdfCursorTools?.switchTool(CursorTool.SELECT);
13586 this.pdfCursorTools?.switchTool(CursorTool.HAND);
13589 this.rotatePages(90);
13592 this.pdfSidebar?.toggle();
13595 if (turnPage !== 0 && (!turnOnlyIfPageFit || pdfViewer.currentScaleValue === "page-fit")) {
13596 if (turnPage > 0) {
13597 pdfViewer.nextPage();
13599 pdfViewer.previousPage();
13605 switch (evt.keyCode) {
13608 if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== "page-fit") {
13611 pdfViewer.previousPage();
13615 this.moveCaret(true, true);
13619 this.moveCaret(false, true);
13623 this.rotatePages(-90);
13627 if (!handled && !isViewerInPresentationMode) {
13628 if (evt.keyCode >= 33 && evt.keyCode <= 40 || evt.keyCode === 32 && curElementTagName !== "BUTTON") {
13629 ensureViewerFocused = true;
13632 if (ensureViewerFocused && !pdfViewer.containsElement(curElement)) {
13636 evt.preventDefault();
13639 function beforeUnload(evt) {
13640 evt.preventDefault();
13641 evt.returnValue = "";
13645 ;// ./web/viewer.js
13650 const pdfjsVersion = "5.0.71";
13651 const pdfjsBuild = "b48717a99";
13652 const AppConstants = null;
13653 window.PDFViewerApplication = PDFViewerApplication;
13654 window.PDFViewerApplicationConstants = AppConstants;
13655 window.PDFViewerApplicationOptions = AppOptions;
13656 function getViewerConfiguration() {
13658 appContainer: document.body,
13659 principalContainer: document.getElementById("mainContainer"),
13660 mainContainer: document.getElementById("viewerContainer"),
13661 viewerContainer: document.getElementById("viewer"),
13663 container: document.getElementById("toolbarContainer"),
13664 numPages: document.getElementById("numPages"),
13665 pageNumber: document.getElementById("pageNumber"),
13666 scaleSelect: document.getElementById("scaleSelect"),
13667 customScaleOption: document.getElementById("customScaleOption"),
13668 previous: document.getElementById("previous"),
13669 next: document.getElementById("next"),
13670 zoomIn: document.getElementById("zoomInButton"),
13671 zoomOut: document.getElementById("zoomOutButton"),
13672 print: document.getElementById("printButton"),
13673 editorFreeTextButton: document.getElementById("editorFreeTextButton"),
13674 editorFreeTextParamsToolbar: document.getElementById("editorFreeTextParamsToolbar"),
13675 editorHighlightButton: document.getElementById("editorHighlightButton"),
13676 editorHighlightParamsToolbar: document.getElementById("editorHighlightParamsToolbar"),
13677 editorHighlightColorPicker: document.getElementById("editorHighlightColorPicker"),
13678 editorInkButton: document.getElementById("editorInkButton"),
13679 editorInkParamsToolbar: document.getElementById("editorInkParamsToolbar"),
13680 editorStampButton: document.getElementById("editorStampButton"),
13681 editorStampParamsToolbar: document.getElementById("editorStampParamsToolbar"),
13682 editorSignatureButton: document.getElementById("editorSignatureButton"),
13683 editorSignatureParamsToolbar: document.getElementById("editorSignatureParamsToolbar"),
13684 download: document.getElementById("downloadButton")
13686 secondaryToolbar: {
13687 toolbar: document.getElementById("secondaryToolbar"),
13688 toggleButton: document.getElementById("secondaryToolbarToggleButton"),
13689 presentationModeButton: document.getElementById("presentationMode"),
13690 openFileButton: null,
13691 printButton: document.getElementById("secondaryPrint"),
13692 downloadButton: document.getElementById("secondaryDownload"),
13693 viewBookmarkButton: document.getElementById("viewBookmark"),
13694 firstPageButton: document.getElementById("firstPage"),
13695 lastPageButton: document.getElementById("lastPage"),
13696 pageRotateCwButton: document.getElementById("pageRotateCw"),
13697 pageRotateCcwButton: document.getElementById("pageRotateCcw"),
13698 cursorSelectToolButton: document.getElementById("cursorSelectTool"),
13699 cursorHandToolButton: document.getElementById("cursorHandTool"),
13700 scrollPageButton: document.getElementById("scrollPage"),
13701 scrollVerticalButton: document.getElementById("scrollVertical"),
13702 scrollHorizontalButton: document.getElementById("scrollHorizontal"),
13703 scrollWrappedButton: document.getElementById("scrollWrapped"),
13704 spreadNoneButton: document.getElementById("spreadNone"),
13705 spreadOddButton: document.getElementById("spreadOdd"),
13706 spreadEvenButton: document.getElementById("spreadEven"),
13707 imageAltTextSettingsButton: document.getElementById("imageAltTextSettings"),
13708 imageAltTextSettingsSeparator: document.getElementById("imageAltTextSettingsSeparator"),
13709 documentPropertiesButton: document.getElementById("documentProperties")
13712 outerContainer: document.getElementById("outerContainer"),
13713 sidebarContainer: document.getElementById("sidebarContainer"),
13714 toggleButton: document.getElementById("sidebarToggleButton"),
13715 resizer: document.getElementById("sidebarResizer"),
13716 thumbnailButton: document.getElementById("viewThumbnail"),
13717 outlineButton: document.getElementById("viewOutline"),
13718 attachmentsButton: document.getElementById("viewAttachments"),
13719 layersButton: document.getElementById("viewLayers"),
13720 thumbnailView: document.getElementById("thumbnailView"),
13721 outlineView: document.getElementById("outlineView"),
13722 attachmentsView: document.getElementById("attachmentsView"),
13723 layersView: document.getElementById("layersView"),
13724 currentOutlineItemButton: document.getElementById("currentOutlineItem")
13727 bar: document.getElementById("findbar"),
13728 toggleButton: document.getElementById("viewFindButton"),
13729 findField: document.getElementById("findInput"),
13730 highlightAllCheckbox: document.getElementById("findHighlightAll"),
13731 caseSensitiveCheckbox: document.getElementById("findMatchCase"),
13732 matchDiacriticsCheckbox: document.getElementById("findMatchDiacritics"),
13733 entireWordCheckbox: document.getElementById("findEntireWord"),
13734 findMsg: document.getElementById("findMsg"),
13735 findResultsCount: document.getElementById("findResultsCount"),
13736 findPreviousButton: document.getElementById("findPreviousButton"),
13737 findNextButton: document.getElementById("findNextButton")
13740 dialog: document.getElementById("passwordDialog"),
13741 label: document.getElementById("passwordText"),
13742 input: document.getElementById("password"),
13743 submitButton: document.getElementById("passwordSubmit"),
13744 cancelButton: document.getElementById("passwordCancel")
13746 documentProperties: {
13747 dialog: document.getElementById("documentPropertiesDialog"),
13748 closeButton: document.getElementById("documentPropertiesClose"),
13750 fileName: document.getElementById("fileNameField"),
13751 fileSize: document.getElementById("fileSizeField"),
13752 title: document.getElementById("titleField"),
13753 author: document.getElementById("authorField"),
13754 subject: document.getElementById("subjectField"),
13755 keywords: document.getElementById("keywordsField"),
13756 creationDate: document.getElementById("creationDateField"),
13757 modificationDate: document.getElementById("modificationDateField"),
13758 creator: document.getElementById("creatorField"),
13759 producer: document.getElementById("producerField"),
13760 version: document.getElementById("versionField"),
13761 pageCount: document.getElementById("pageCountField"),
13762 pageSize: document.getElementById("pageSizeField"),
13763 linearized: document.getElementById("linearizedField")
13767 dialog: document.getElementById("altTextDialog"),
13768 optionDescription: document.getElementById("descriptionButton"),
13769 optionDecorative: document.getElementById("decorativeButton"),
13770 textarea: document.getElementById("descriptionTextarea"),
13771 cancelButton: document.getElementById("altTextCancel"),
13772 saveButton: document.getElementById("altTextSave")
13774 newAltTextDialog: {
13775 dialog: document.getElementById("newAltTextDialog"),
13776 title: document.getElementById("newAltTextTitle"),
13777 descriptionContainer: document.getElementById("newAltTextDescriptionContainer"),
13778 textarea: document.getElementById("newAltTextDescriptionTextarea"),
13779 disclaimer: document.getElementById("newAltTextDisclaimer"),
13780 learnMore: document.getElementById("newAltTextLearnMore"),
13781 imagePreview: document.getElementById("newAltTextImagePreview"),
13782 createAutomatically: document.getElementById("newAltTextCreateAutomatically"),
13783 createAutomaticallyButton: document.getElementById("newAltTextCreateAutomaticallyButton"),
13784 downloadModel: document.getElementById("newAltTextDownloadModel"),
13785 downloadModelDescription: document.getElementById("newAltTextDownloadModelDescription"),
13786 error: document.getElementById("newAltTextError"),
13787 errorCloseButton: document.getElementById("newAltTextCloseButton"),
13788 cancelButton: document.getElementById("newAltTextCancel"),
13789 notNowButton: document.getElementById("newAltTextNotNow"),
13790 saveButton: document.getElementById("newAltTextSave")
13792 altTextSettingsDialog: {
13793 dialog: document.getElementById("altTextSettingsDialog"),
13794 createModelButton: document.getElementById("createModelButton"),
13795 aiModelSettings: document.getElementById("aiModelSettings"),
13796 learnMore: document.getElementById("altTextSettingsLearnMore"),
13797 deleteModelButton: document.getElementById("deleteModelButton"),
13798 downloadModelButton: document.getElementById("downloadModelButton"),
13799 showAltTextDialogButton: document.getElementById("showAltTextDialogButton"),
13800 altTextSettingsCloseButton: document.getElementById("altTextSettingsCloseButton"),
13801 closeButton: document.getElementById("altTextSettingsCloseButton")
13803 annotationEditorParams: {
13804 editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"),
13805 editorFreeTextColor: document.getElementById("editorFreeTextColor"),
13806 editorInkColor: document.getElementById("editorInkColor"),
13807 editorInkThickness: document.getElementById("editorInkThickness"),
13808 editorInkOpacity: document.getElementById("editorInkOpacity"),
13809 editorStampAddImage: document.getElementById("editorStampAddImage"),
13810 editorSignatureAddSignature: document.getElementById("editorSignatureAddSignature"),
13811 editorFreeHighlightThickness: document.getElementById("editorFreeHighlightThickness"),
13812 editorHighlightShowAll: document.getElementById("editorHighlightShowAll")
13814 printContainer: document.getElementById("printContainer"),
13816 container: document.getElementById("editorUndoBar"),
13817 message: document.getElementById("editorUndoBarMessage"),
13818 undoButton: document.getElementById("editorUndoBarUndoButton"),
13819 closeButton: document.getElementById("editorUndoBarCloseButton")
13823 function webViewerLoad() {
13824 const config = getViewerConfiguration();
13825 PDFViewerApplication.run(config);
13827 document.blockUnblockOnload?.(true);
13828 if (document.readyState === "interactive" || document.readyState === "complete") {
13831 document.addEventListener("DOMContentLoaded", webViewerLoad, true);
13834 var __webpack_exports__PDFViewerApplication = __webpack_exports__.PDFViewerApplication;
13835 var __webpack_exports__PDFViewerApplicationConstants = __webpack_exports__.PDFViewerApplicationConstants;
13836 var __webpack_exports__PDFViewerApplicationOptions = __webpack_exports__.PDFViewerApplicationOptions;
13837 export { __webpack_exports__PDFViewerApplication as PDFViewerApplication, __webpack_exports__PDFViewerApplicationConstants as PDFViewerApplicationConstants, __webpack_exports__PDFViewerApplicationOptions as PDFViewerApplicationOptions };