Write on the margins of the internet. Powered by the AT Protocol.
margin.at
extension
web
atproto
comments
1/**
2 * @licstart The following is the entire license notice for the
3 * JavaScript code in this page
4 *
5 * Copyright 2024 Mozilla Foundation
6 *
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
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
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.
18 *
19 * @licend The above is the entire license notice for the
20 * JavaScript code in this page
21 */
22
23/**
24 * pdfjsVersion = 5.4.624
25 * pdfjsBuild = 384c6208b
26 */
27/******/ // The require scope
28/******/ var __webpack_require__ = {};
29/******/
30/************************************************************************/
31/******/ /* webpack/runtime/define property getters */
32/******/ (() => {
33 /******/ // define getter functions for harmony exports
34 /******/ __webpack_require__.d = (exports, definition) => {
35 /******/ for (var key in definition) {
36 /******/ if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
37 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
38 /******/
39 }
40 /******/
41 }
42 /******/
43 };
44 /******/
45})();
46/******/
47/******/ /* webpack/runtime/hasOwnProperty shorthand */
48/******/ (() => {
49 /******/ __webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
50 /******/
51})();
52/******/
53/************************************************************************/
54var __webpack_exports__ = {}; // ./web/pdfjs.js
55
56const {
57 AbortException,
58 AnnotationEditorLayer,
59 AnnotationEditorParamsType,
60 AnnotationEditorType,
61 AnnotationEditorUIManager,
62 AnnotationLayer,
63 AnnotationMode,
64 AnnotationType,
65 applyOpacity,
66 build,
67 ColorPicker,
68 createValidAbsoluteUrl,
69 CSSConstants,
70 DOMSVGFactory,
71 DrawLayer,
72 FeatureTest,
73 fetchData,
74 findContrastColor,
75 getDocument,
76 getFilenameFromUrl,
77 getPdfFilenameFromUrl: pdfjs_getPdfFilenameFromUrl,
78 getRGB,
79 getUuid,
80 getXfaPageViewport,
81 GlobalWorkerOptions,
82 ImageKind,
83 InvalidPDFException,
84 isDataScheme,
85 isPdfFile,
86 isValidExplicitDest,
87 MathClamp,
88 noContextMenu,
89 normalizeUnicode,
90 OPS,
91 OutputScale,
92 PagesMapper,
93 PasswordResponses,
94 PDFDataRangeTransport,
95 PDFDateString,
96 PDFWorker,
97 PermissionFlag,
98 PixelsPerInch,
99 RenderingCancelledException,
100 renderRichText,
101 ResponseException,
102 setLayerDimensions,
103 shadow,
104 SignatureExtractor,
105 stopEvent,
106 SupportedImageMimeTypes,
107 TextLayer,
108 TouchManager,
109 updateUrlHash,
110 Util,
111 VerbosityLevel,
112 version,
113 XfaLayer,
114} = globalThis.pdfjsLib; // ./web/ui_utils.js
115
116const DEFAULT_SCALE_VALUE = 'auto';
117const DEFAULT_SCALE = 1.0;
118const DEFAULT_SCALE_DELTA = 1.1;
119const MIN_SCALE = 0.1;
120const MAX_SCALE = 10.0;
121const UNKNOWN_SCALE = 0;
122const MAX_AUTO_SCALE = 1.25;
123const SCROLLBAR_PADDING = 40;
124const VERTICAL_PADDING = 5;
125const RenderingStates = {
126 INITIAL: 0,
127 RUNNING: 1,
128 PAUSED: 2,
129 FINISHED: 3,
130};
131const PresentationModeState = {
132 UNKNOWN: 0,
133 NORMAL: 1,
134 CHANGING: 2,
135 FULLSCREEN: 3,
136};
137const SidebarView = {
138 UNKNOWN: -1,
139 NONE: 0,
140 THUMBS: 1,
141 OUTLINE: 2,
142 ATTACHMENTS: 3,
143 LAYERS: 4,
144};
145const TextLayerMode = {
146 DISABLE: 0,
147 ENABLE: 1,
148 ENABLE_PERMISSIONS: 2,
149};
150const ScrollMode = {
151 UNKNOWN: -1,
152 VERTICAL: 0,
153 HORIZONTAL: 1,
154 WRAPPED: 2,
155 PAGE: 3,
156};
157const SpreadMode = {
158 UNKNOWN: -1,
159 NONE: 0,
160 ODD: 1,
161 EVEN: 2,
162};
163const CursorTool = {
164 SELECT: 0,
165 HAND: 1,
166 ZOOM: 2,
167};
168const AutoPrintRegExp = /\bprint\s*\(/;
169function scrollIntoView(element, spot, scrollMatches = false) {
170 let parent = element.offsetParent;
171 if (!parent) {
172 console.error('offsetParent is not set -- cannot scroll');
173 return;
174 }
175 let offsetY = element.offsetTop + element.clientTop;
176 let offsetX = element.offsetLeft + element.clientLeft;
177 while (
178 (parent.clientHeight === parent.scrollHeight && parent.clientWidth === parent.scrollWidth) ||
179 (scrollMatches &&
180 (parent.classList.contains('markedContent') ||
181 getComputedStyle(parent).overflow === 'hidden'))
182 ) {
183 offsetY += parent.offsetTop;
184 offsetX += parent.offsetLeft;
185 parent = parent.offsetParent;
186 if (!parent) {
187 return;
188 }
189 }
190 if (spot) {
191 if (spot.top !== undefined) {
192 offsetY += spot.top;
193 }
194 if (spot.left !== undefined) {
195 if (scrollMatches) {
196 const elementWidth = element.getBoundingClientRect().width;
197 const padding = MathClamp((parent.clientWidth - elementWidth) / 2, 20, 400);
198 offsetX += spot.left - padding;
199 } else {
200 offsetX += spot.left;
201 }
202 parent.scrollLeft = offsetX;
203 }
204 }
205 parent.scrollTop = offsetY;
206}
207function watchScroll(viewAreaElement, callback, abortSignal = undefined) {
208 const debounceScroll = function (evt) {
209 if (rAF) {
210 return;
211 }
212 rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
213 rAF = null;
214 const currentX = viewAreaElement.scrollLeft;
215 const lastX = state.lastX;
216 if (currentX !== lastX) {
217 state.right = currentX > lastX;
218 }
219 state.lastX = currentX;
220 const currentY = viewAreaElement.scrollTop;
221 const lastY = state.lastY;
222 if (currentY !== lastY) {
223 state.down = currentY > lastY;
224 }
225 state.lastY = currentY;
226 callback(state);
227 });
228 };
229 const state = {
230 right: true,
231 down: true,
232 lastX: viewAreaElement.scrollLeft,
233 lastY: viewAreaElement.scrollTop,
234 _eventHandler: debounceScroll,
235 };
236 let rAF = null;
237 viewAreaElement.addEventListener('scroll', debounceScroll, {
238 useCapture: true,
239 signal: abortSignal,
240 });
241 abortSignal?.addEventListener('abort', () => window.cancelAnimationFrame(rAF), {
242 once: true,
243 });
244 return state;
245}
246function parseQueryString(query) {
247 const params = new Map();
248 for (const [key, value] of new URLSearchParams(query)) {
249 params.set(key.toLowerCase(), value);
250 }
251 return params;
252}
253const InvisibleCharsRegExp = /[\x00-\x1F]/g;
254function removeNullCharacters(str, replaceInvisible = false) {
255 if (!InvisibleCharsRegExp.test(str)) {
256 return str;
257 }
258 if (replaceInvisible) {
259 return str.replaceAll(InvisibleCharsRegExp, (m) => (m === '\x00' ? '' : ' '));
260 }
261 return str.replaceAll('\x00', '');
262}
263function binarySearchFirstItem(items, condition, start = 0) {
264 let minIndex = start;
265 let maxIndex = items.length - 1;
266 if (maxIndex < 0 || !condition(items[maxIndex])) {
267 return items.length;
268 }
269 if (condition(items[minIndex])) {
270 return minIndex;
271 }
272 while (minIndex < maxIndex) {
273 const currentIndex = (minIndex + maxIndex) >> 1;
274 const currentItem = items[currentIndex];
275 if (condition(currentItem)) {
276 maxIndex = currentIndex;
277 } else {
278 minIndex = currentIndex + 1;
279 }
280 }
281 return minIndex;
282}
283function approximateFraction(x) {
284 if (Math.floor(x) === x) {
285 return [x, 1];
286 }
287 const xinv = 1 / x;
288 const limit = 8;
289 if (xinv > limit) {
290 return [1, limit];
291 } else if (Math.floor(xinv) === xinv) {
292 return [1, xinv];
293 }
294 const x_ = x > 1 ? xinv : x;
295 let a = 0,
296 b = 1,
297 c = 1,
298 d = 1;
299 while (true) {
300 const p = a + c,
301 q = b + d;
302 if (q > limit) {
303 break;
304 }
305 if (x_ <= p / q) {
306 c = p;
307 d = q;
308 } else {
309 a = p;
310 b = q;
311 }
312 }
313 let result;
314 if (x_ - a / b < c / d - x_) {
315 result = x_ === x ? [a, b] : [b, a];
316 } else {
317 result = x_ === x ? [c, d] : [d, c];
318 }
319 return result;
320}
321function floorToDivide(x, div) {
322 return x - (x % div);
323}
324function getPageSizeInches({ view, userUnit, rotate }) {
325 const [x1, y1, x2, y2] = view;
326 const changeOrientation = rotate % 180 !== 0;
327 const width = ((x2 - x1) / 72) * userUnit;
328 const height = ((y2 - y1) / 72) * userUnit;
329 return {
330 width: changeOrientation ? height : width,
331 height: changeOrientation ? width : height,
332 };
333}
334function backtrackBeforeAllVisibleElements(index, views, top) {
335 if (index < 2) {
336 return index;
337 }
338 let elt = views[index].div;
339 let pageTop = elt.offsetTop + elt.clientTop;
340 if (pageTop >= top) {
341 elt = views[index - 1].div;
342 pageTop = elt.offsetTop + elt.clientTop;
343 }
344 for (let i = index - 2; i >= 0; --i) {
345 elt = views[i].div;
346 if (elt.offsetTop + elt.clientTop + elt.clientHeight <= pageTop) {
347 break;
348 }
349 index = i;
350 }
351 return index;
352}
353function getVisibleElements({
354 scrollEl,
355 views,
356 sortByVisibility = false,
357 horizontal = false,
358 rtl = false,
359}) {
360 const top = scrollEl.scrollTop,
361 bottom = top + scrollEl.clientHeight;
362 const left = scrollEl.scrollLeft,
363 right = left + scrollEl.clientWidth;
364 function isElementBottomAfterViewTop(view) {
365 const element = view.div;
366 const elementBottom = element.offsetTop + element.clientTop + element.clientHeight;
367 return elementBottom > top;
368 }
369 function isElementNextAfterViewHorizontally(view) {
370 const element = view.div;
371 const elementLeft = element.offsetLeft + element.clientLeft;
372 const elementRight = elementLeft + element.clientWidth;
373 return rtl ? elementLeft < right : elementRight > left;
374 }
375 const visible = [],
376 ids = new Set(),
377 numViews = views.length;
378 let firstVisibleElementInd = binarySearchFirstItem(
379 views,
380 horizontal ? isElementNextAfterViewHorizontally : isElementBottomAfterViewTop
381 );
382 if (firstVisibleElementInd > 0 && firstVisibleElementInd < numViews && !horizontal) {
383 firstVisibleElementInd = backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top);
384 }
385 let lastEdge = horizontal ? right : -1;
386 for (let i = firstVisibleElementInd; i < numViews; i++) {
387 const view = views[i],
388 element = view.div;
389 const currentWidth = element.offsetLeft + element.clientLeft;
390 const currentHeight = element.offsetTop + element.clientTop;
391 const viewWidth = element.clientWidth,
392 viewHeight = element.clientHeight;
393 const viewRight = currentWidth + viewWidth;
394 const viewBottom = currentHeight + viewHeight;
395 if (lastEdge === -1) {
396 if (viewBottom >= bottom) {
397 lastEdge = viewBottom;
398 }
399 } else if ((horizontal ? currentWidth : currentHeight) > lastEdge) {
400 break;
401 }
402 if (
403 viewBottom <= top ||
404 currentHeight >= bottom ||
405 viewRight <= left ||
406 currentWidth >= right
407 ) {
408 continue;
409 }
410 const minY = Math.max(0, top - currentHeight);
411 const minX = Math.max(0, left - currentWidth);
412 const hiddenHeight = minY + Math.max(0, viewBottom - bottom);
413 const hiddenWidth = minX + Math.max(0, viewRight - right);
414 const fractionHeight = (viewHeight - hiddenHeight) / viewHeight,
415 fractionWidth = (viewWidth - hiddenWidth) / viewWidth;
416 const percent = (fractionHeight * fractionWidth * 100) | 0;
417 visible.push({
418 id: view.id,
419 x: currentWidth,
420 y: currentHeight,
421 visibleArea:
422 percent === 100
423 ? null
424 : {
425 minX,
426 minY,
427 maxX: Math.min(viewRight, right) - currentWidth,
428 maxY: Math.min(viewBottom, bottom) - currentHeight,
429 },
430 view,
431 percent,
432 widthPercent: (fractionWidth * 100) | 0,
433 });
434 ids.add(view.id);
435 }
436 const first = visible[0],
437 last = visible.at(-1);
438 if (sortByVisibility) {
439 visible.sort(function (a, b) {
440 const pc = a.percent - b.percent;
441 if (Math.abs(pc) > 0.001) {
442 return -pc;
443 }
444 return a.id - b.id;
445 });
446 }
447 return {
448 first,
449 last,
450 views: visible,
451 ids,
452 };
453}
454function normalizeWheelEventDirection(evt) {
455 let delta = Math.hypot(evt.deltaX, evt.deltaY);
456 const angle = Math.atan2(evt.deltaY, evt.deltaX);
457 if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
458 delta = -delta;
459 }
460 return delta;
461}
462function normalizeWheelEventDelta(evt) {
463 const deltaMode = evt.deltaMode;
464 let delta = normalizeWheelEventDirection(evt);
465 const MOUSE_PIXELS_PER_LINE = 30;
466 const MOUSE_LINES_PER_PAGE = 30;
467 if (deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
468 delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
469 } else if (deltaMode === WheelEvent.DOM_DELTA_LINE) {
470 delta /= MOUSE_LINES_PER_PAGE;
471 }
472 return delta;
473}
474function isValidRotation(angle) {
475 return Number.isInteger(angle) && angle % 90 === 0;
476}
477function isValidScrollMode(mode) {
478 return (
479 Number.isInteger(mode) &&
480 Object.values(ScrollMode).includes(mode) &&
481 mode !== ScrollMode.UNKNOWN
482 );
483}
484function isValidSpreadMode(mode) {
485 return (
486 Number.isInteger(mode) &&
487 Object.values(SpreadMode).includes(mode) &&
488 mode !== SpreadMode.UNKNOWN
489 );
490}
491function isPortraitOrientation(size) {
492 return size.width <= size.height;
493}
494const animationStarted = new Promise(function (resolve) {
495 window.requestAnimationFrame(resolve);
496});
497const docStyle = document.documentElement.style;
498class ProgressBar {
499 #classList = null;
500 #disableAutoFetchTimeout = null;
501 #percent = 0;
502 #style = null;
503 #visible = true;
504 constructor(bar) {
505 this.#classList = bar.classList;
506 this.#style = bar.style;
507 }
508 get percent() {
509 return this.#percent;
510 }
511 set percent(val) {
512 this.#percent = val;
513 if (isNaN(val)) {
514 this.#classList.add('indeterminate');
515 return;
516 }
517 this.#classList.remove('indeterminate');
518 this.#style.setProperty('--progressBar-percent', `${this.#percent}%`);
519 }
520 setWidth(viewer) {
521 if (!viewer) {
522 return;
523 }
524 const container = viewer.parentNode;
525 const scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
526 if (scrollbarWidth > 0) {
527 this.#style.setProperty('--progressBar-end-offset', `${scrollbarWidth}px`);
528 }
529 }
530 setDisableAutoFetch(delay = 5000) {
531 if (this.#percent === 100 || isNaN(this.#percent)) {
532 return;
533 }
534 if (this.#disableAutoFetchTimeout) {
535 clearTimeout(this.#disableAutoFetchTimeout);
536 }
537 this.show();
538 this.#disableAutoFetchTimeout = setTimeout(() => {
539 this.#disableAutoFetchTimeout = null;
540 this.hide();
541 }, delay);
542 }
543 hide() {
544 if (!this.#visible) {
545 return;
546 }
547 this.#visible = false;
548 this.#classList.add('hidden');
549 }
550 show() {
551 if (this.#visible) {
552 return;
553 }
554 this.#visible = true;
555 this.#classList.remove('hidden');
556 }
557}
558function getActiveOrFocusedElement() {
559 let curRoot = document;
560 let curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(':focus');
561 while (curActiveOrFocused?.shadowRoot) {
562 curRoot = curActiveOrFocused.shadowRoot;
563 curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(':focus');
564 }
565 return curActiveOrFocused;
566}
567function apiPageLayoutToViewerModes(layout) {
568 let scrollMode = ScrollMode.VERTICAL,
569 spreadMode = SpreadMode.NONE;
570 switch (layout) {
571 case 'SinglePage':
572 scrollMode = ScrollMode.PAGE;
573 break;
574 case 'OneColumn':
575 break;
576 case 'TwoPageLeft':
577 scrollMode = ScrollMode.PAGE;
578 case 'TwoColumnLeft':
579 spreadMode = SpreadMode.ODD;
580 break;
581 case 'TwoPageRight':
582 scrollMode = ScrollMode.PAGE;
583 case 'TwoColumnRight':
584 spreadMode = SpreadMode.EVEN;
585 break;
586 }
587 return {
588 scrollMode,
589 spreadMode,
590 };
591}
592function apiPageModeToSidebarView(mode) {
593 switch (mode) {
594 case 'UseNone':
595 return SidebarView.NONE;
596 case 'UseThumbs':
597 return SidebarView.THUMBS;
598 case 'UseOutlines':
599 return SidebarView.OUTLINE;
600 case 'UseAttachments':
601 return SidebarView.ATTACHMENTS;
602 case 'UseOC':
603 return SidebarView.LAYERS;
604 }
605 return SidebarView.NONE;
606}
607function toggleCheckedBtn(button, toggle, view = null) {
608 button.classList.toggle('toggled', toggle);
609 button.setAttribute('aria-checked', toggle);
610 view?.classList.toggle('hidden', !toggle);
611}
612function toggleSelectedBtn(button, toggle, view = null) {
613 button.classList.toggle('selected', toggle);
614 button.setAttribute('aria-selected', toggle);
615 view?.classList.toggle('hidden', !toggle);
616}
617function toggleExpandedBtn(button, toggle, view = null) {
618 button.classList.toggle('toggled', toggle);
619 button.setAttribute('aria-expanded', toggle);
620 view?.classList.toggle('hidden', !toggle);
621}
622const calcRound = (function () {
623 const e = document.createElement('div');
624 e.style.width = 'round(down, calc(1.6666666666666665 * 792px), 1px)';
625 return e.style.width === 'calc(1320px)' ? Math.fround : (x) => x;
626})(); // ./web/app_options.js
627
628{
629 var compatParams = new Map();
630 const { maxTouchPoints, platform, userAgent } = navigator;
631 const isAndroid = /Android/.test(userAgent);
632 const isIOS =
633 /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) || (platform === 'MacIntel' && maxTouchPoints > 1);
634 if (isIOS || isAndroid) {
635 compatParams.set('maxCanvasPixels', 5242880);
636 }
637 if (isAndroid) {
638 compatParams.set('useSystemFonts', false);
639 }
640}
641const OptionKind = {
642 BROWSER: 0x01,
643 VIEWER: 0x02,
644 API: 0x04,
645 WORKER: 0x08,
646 EVENT_DISPATCH: 0x10,
647 PREFERENCE: 0x80,
648};
649const Type = {
650 BOOLEAN: 0x01,
651 NUMBER: 0x02,
652 OBJECT: 0x04,
653 STRING: 0x08,
654 UNDEFINED: 0x10,
655};
656const defaultOptions = {
657 allowedGlobalEvents: {
658 value: null,
659 kind: OptionKind.BROWSER,
660 },
661 canvasMaxAreaInBytes: {
662 value: -1,
663 kind: OptionKind.BROWSER + OptionKind.API,
664 },
665 isInAutomation: {
666 value: false,
667 kind: OptionKind.BROWSER,
668 },
669 localeProperties: {
670 value: {
671 lang: navigator.language || 'en-US',
672 },
673 kind: OptionKind.BROWSER,
674 },
675 maxCanvasDim: {
676 value: 32767,
677 kind: OptionKind.BROWSER + OptionKind.VIEWER,
678 },
679 nimbusDataStr: {
680 value: '',
681 kind: OptionKind.BROWSER,
682 },
683 supportsCaretBrowsingMode: {
684 value: false,
685 kind: OptionKind.BROWSER,
686 },
687 supportsDocumentFonts: {
688 value: true,
689 kind: OptionKind.BROWSER,
690 },
691 supportsIntegratedFind: {
692 value: false,
693 kind: OptionKind.BROWSER,
694 },
695 supportsMouseWheelZoomCtrlKey: {
696 value: true,
697 kind: OptionKind.BROWSER,
698 },
699 supportsMouseWheelZoomMetaKey: {
700 value: true,
701 kind: OptionKind.BROWSER,
702 },
703 supportsPinchToZoom: {
704 value: true,
705 kind: OptionKind.BROWSER,
706 },
707 supportsPrinting: {
708 value: true,
709 kind: OptionKind.BROWSER,
710 },
711 toolbarDensity: {
712 value: 0,
713 kind: OptionKind.BROWSER + OptionKind.EVENT_DISPATCH,
714 },
715 altTextLearnMoreUrl: {
716 value: '',
717 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
718 },
719 annotationEditorMode: {
720 value: 0,
721 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
722 },
723 annotationMode: {
724 value: 2,
725 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
726 },
727 capCanvasAreaFactor: {
728 value: 200,
729 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
730 },
731 commentLearnMoreUrl: {
732 value: '',
733 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
734 },
735 cursorToolOnLoad: {
736 value: 0,
737 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
738 },
739 debuggerSrc: {
740 value: './debugger.mjs',
741 kind: OptionKind.VIEWER,
742 },
743 defaultZoomDelay: {
744 value: 400,
745 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
746 },
747 defaultZoomValue: {
748 value: '',
749 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
750 },
751 disableHistory: {
752 value: false,
753 kind: OptionKind.VIEWER,
754 },
755 disablePageLabels: {
756 value: false,
757 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
758 },
759 enableAltText: {
760 value: false,
761 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
762 },
763 enableAltTextModelDownload: {
764 value: true,
765 kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH,
766 },
767 enableAutoLinking: {
768 value: true,
769 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
770 },
771 enableComment: {
772 value: false,
773 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
774 },
775 enableDetailCanvas: {
776 value: true,
777 kind: OptionKind.VIEWER,
778 },
779 enableGuessAltText: {
780 value: true,
781 kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH,
782 },
783 enableHighlightFloatingButton: {
784 value: false,
785 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
786 },
787 enableNewAltTextWhenAddingImage: {
788 value: true,
789 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
790 },
791 enableOptimizedPartialRendering: {
792 value: false,
793 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
794 },
795 enablePermissions: {
796 value: false,
797 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
798 },
799 enablePrintAutoRotate: {
800 value: true,
801 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
802 },
803 enableScripting: {
804 value: true,
805 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
806 },
807 enableSignatureEditor: {
808 value: false,
809 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
810 },
811 enableSplitMerge: {
812 value: false,
813 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
814 },
815 enableUpdatedAddImage: {
816 value: false,
817 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
818 },
819 externalLinkRel: {
820 value: 'noopener noreferrer nofollow',
821 kind: OptionKind.VIEWER,
822 },
823 externalLinkTarget: {
824 value: 0,
825 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
826 },
827 highlightEditorColors: {
828 value:
829 'yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F,' +
830 'yellow_HCM=#FFFFCC,green_HCM=#53FFBC,blue_HCM=#80EBFF,pink_HCM=#F6B8FF,red_HCM=#C50043',
831 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
832 },
833 historyUpdateUrl: {
834 value: false,
835 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
836 },
837 ignoreDestinationZoom: {
838 value: false,
839 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
840 },
841 imageResourcesPath: {
842 value: './images/',
843 kind: OptionKind.VIEWER,
844 },
845 maxCanvasPixels: {
846 value: 2 ** 25,
847 kind: OptionKind.VIEWER,
848 },
849 minDurationToUpdateCanvas: {
850 value: 500,
851 kind: OptionKind.VIEWER,
852 },
853 forcePageColors: {
854 value: false,
855 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
856 },
857 pageColorsBackground: {
858 value: 'Canvas',
859 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
860 },
861 pageColorsForeground: {
862 value: 'CanvasText',
863 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
864 },
865 pdfBugEnabled: {
866 value: false,
867 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
868 },
869 printResolution: {
870 value: 150,
871 kind: OptionKind.VIEWER,
872 },
873 sidebarViewOnLoad: {
874 value: -1,
875 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
876 },
877 scrollModeOnLoad: {
878 value: -1,
879 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
880 },
881 spreadModeOnLoad: {
882 value: -1,
883 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
884 },
885 textLayerMode: {
886 value: 1,
887 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
888 },
889 viewerCssTheme: {
890 value: 0,
891 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
892 },
893 viewOnLoad: {
894 value: 0,
895 kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
896 },
897 cMapPacked: {
898 value: true,
899 kind: OptionKind.API,
900 },
901 cMapUrl: {
902 value: '../web/cmaps/',
903 kind: OptionKind.API,
904 },
905 disableAutoFetch: {
906 value: false,
907 kind: OptionKind.API + OptionKind.PREFERENCE,
908 },
909 disableFontFace: {
910 value: false,
911 kind: OptionKind.API + OptionKind.PREFERENCE,
912 },
913 disableRange: {
914 value: false,
915 kind: OptionKind.API + OptionKind.PREFERENCE,
916 },
917 disableStream: {
918 value: false,
919 kind: OptionKind.API + OptionKind.PREFERENCE,
920 },
921 docBaseUrl: {
922 value: '',
923 kind: OptionKind.API,
924 },
925 enableHWA: {
926 value: true,
927 kind: OptionKind.API + OptionKind.VIEWER + OptionKind.PREFERENCE,
928 },
929 enableXfa: {
930 value: true,
931 kind: OptionKind.API + OptionKind.PREFERENCE,
932 },
933 fontExtraProperties: {
934 value: false,
935 kind: OptionKind.API,
936 },
937 iccUrl: {
938 value: '../web/iccs/',
939 kind: OptionKind.API,
940 },
941 isEvalSupported: {
942 value: true,
943 kind: OptionKind.API,
944 },
945 isOffscreenCanvasSupported: {
946 value: true,
947 kind: OptionKind.API,
948 },
949 maxImageSize: {
950 value: -1,
951 kind: OptionKind.API,
952 },
953 pdfBug: {
954 value: false,
955 kind: OptionKind.API,
956 },
957 standardFontDataUrl: {
958 value: '../web/standard_fonts/',
959 kind: OptionKind.API,
960 },
961 useSystemFonts: {
962 value: undefined,
963 kind: OptionKind.API,
964 type: Type.BOOLEAN + Type.UNDEFINED,
965 },
966 verbosity: {
967 value: 1,
968 kind: OptionKind.API,
969 },
970 wasmUrl: {
971 value: '../web/wasm/',
972 kind: OptionKind.API,
973 },
974 workerPort: {
975 value: null,
976 kind: OptionKind.WORKER,
977 },
978 workerSrc: {
979 value: '../build/pdf.worker.mjs',
980 kind: OptionKind.WORKER,
981 },
982};
983{
984 defaultOptions.defaultUrl = {
985 value: 'compressed.tracemonkey-pldi-09.pdf',
986 kind: OptionKind.VIEWER,
987 };
988 defaultOptions.sandboxBundleSrc = {
989 value: '../build/pdf.sandbox.mjs',
990 kind: OptionKind.VIEWER,
991 };
992 defaultOptions.enableFakeMLManager = {
993 value: true,
994 kind: OptionKind.VIEWER,
995 };
996}
997{
998 defaultOptions.disablePreferences = {
999 value: false,
1000 kind: OptionKind.VIEWER,
1001 };
1002}
1003class AppOptions {
1004 static eventBus;
1005 static #opts = new Map();
1006 static {
1007 for (const name in defaultOptions) {
1008 this.#opts.set(name, defaultOptions[name].value);
1009 }
1010 for (const [name, value] of compatParams) {
1011 this.#opts.set(name, value);
1012 }
1013 this._hasInvokedSet = false;
1014 this._checkDisablePreferences = () => {
1015 if (this.get('disablePreferences')) {
1016 return true;
1017 }
1018 if (this._hasInvokedSet) {
1019 console.warn(
1020 'The Preferences may override manually set AppOptions; ' +
1021 'please use the "disablePreferences"-option to prevent that.'
1022 );
1023 }
1024 return false;
1025 };
1026 }
1027 static get(name) {
1028 return this.#opts.get(name);
1029 }
1030 static getAll(kind = null, defaultOnly = false) {
1031 const options = Object.create(null);
1032 for (const name in defaultOptions) {
1033 const defaultOpt = defaultOptions[name];
1034 if (kind && !(kind & defaultOpt.kind)) {
1035 continue;
1036 }
1037 options[name] = !defaultOnly ? this.#opts.get(name) : defaultOpt.value;
1038 }
1039 return options;
1040 }
1041 static set(name, value) {
1042 this.setAll({
1043 [name]: value,
1044 });
1045 }
1046 static setAll(options, prefs = false) {
1047 this._hasInvokedSet ||= true;
1048 let events;
1049 for (const name in options) {
1050 const defaultOpt = defaultOptions[name],
1051 userOpt = options[name];
1052 if (
1053 !defaultOpt ||
1054 !(
1055 typeof userOpt === typeof defaultOpt.value ||
1056 Type[(typeof userOpt).toUpperCase()] & defaultOpt.type
1057 )
1058 ) {
1059 continue;
1060 }
1061 const { kind } = defaultOpt;
1062 if (prefs && !(kind & OptionKind.BROWSER || kind & OptionKind.PREFERENCE)) {
1063 continue;
1064 }
1065 if (this.eventBus && kind & OptionKind.EVENT_DISPATCH) {
1066 (events ||= new Map()).set(name, userOpt);
1067 }
1068 this.#opts.set(name, userOpt);
1069 }
1070 if (events) {
1071 for (const [name, value] of events) {
1072 this.eventBus.dispatch(name.toLowerCase(), {
1073 source: this,
1074 value,
1075 });
1076 }
1077 }
1078 }
1079} // ./web/pdf_link_service.js
1080
1081const DEFAULT_LINK_REL = 'noopener noreferrer nofollow';
1082const LinkTarget = {
1083 NONE: 0,
1084 SELF: 1,
1085 BLANK: 2,
1086 PARENT: 3,
1087 TOP: 4,
1088};
1089class PDFLinkService {
1090 externalLinkEnabled = true;
1091 constructor({
1092 eventBus,
1093 externalLinkTarget = null,
1094 externalLinkRel = null,
1095 ignoreDestinationZoom = false,
1096 } = {}) {
1097 this.eventBus = eventBus;
1098 this.externalLinkTarget = externalLinkTarget;
1099 this.externalLinkRel = externalLinkRel;
1100 this._ignoreDestinationZoom = ignoreDestinationZoom;
1101 this.baseUrl = null;
1102 this.pdfDocument = null;
1103 this.pdfViewer = null;
1104 this.pdfHistory = null;
1105 }
1106 setDocument(pdfDocument, baseUrl = null) {
1107 this.baseUrl = baseUrl;
1108 this.pdfDocument = pdfDocument;
1109 }
1110 setViewer(pdfViewer) {
1111 this.pdfViewer = pdfViewer;
1112 }
1113 setHistory(pdfHistory) {
1114 this.pdfHistory = pdfHistory;
1115 }
1116 get pagesCount() {
1117 return this.pdfDocument ? this.pdfDocument.numPages : 0;
1118 }
1119 get page() {
1120 return this.pdfDocument ? this.pdfViewer.currentPageNumber : 1;
1121 }
1122 set page(value) {
1123 if (this.pdfDocument) {
1124 this.pdfViewer.currentPageNumber = value;
1125 }
1126 }
1127 get rotation() {
1128 return this.pdfDocument ? this.pdfViewer.pagesRotation : 0;
1129 }
1130 set rotation(value) {
1131 if (this.pdfDocument) {
1132 this.pdfViewer.pagesRotation = value;
1133 }
1134 }
1135 get isInPresentationMode() {
1136 return this.pdfDocument ? this.pdfViewer.isInPresentationMode : false;
1137 }
1138 async goToDestination(dest) {
1139 if (!this.pdfDocument) {
1140 return;
1141 }
1142 let namedDest, explicitDest, pageNumber;
1143 if (typeof dest === 'string') {
1144 namedDest = dest;
1145 explicitDest = await this.pdfDocument.getDestination(dest);
1146 } else {
1147 namedDest = null;
1148 explicitDest = await dest;
1149 }
1150 if (!Array.isArray(explicitDest)) {
1151 console.error(
1152 `goToDestination: "${explicitDest}" is not a valid destination array, for dest="${dest}".`
1153 );
1154 return;
1155 }
1156 const [destRef] = explicitDest;
1157 if (destRef && typeof destRef === 'object') {
1158 pageNumber = this.pdfDocument.cachedPageNumber(destRef);
1159 if (!pageNumber) {
1160 try {
1161 pageNumber = (await this.pdfDocument.getPageIndex(destRef)) + 1;
1162 } catch {
1163 console.error(
1164 `goToDestination: "${destRef}" is not a valid page reference, for dest="${dest}".`
1165 );
1166 return;
1167 }
1168 }
1169 } else if (Number.isInteger(destRef)) {
1170 pageNumber = destRef + 1;
1171 }
1172 if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
1173 console.error(
1174 `goToDestination: "${pageNumber}" is not a valid page number, for dest="${dest}".`
1175 );
1176 return;
1177 }
1178 if (this.pdfHistory) {
1179 this.pdfHistory.pushCurrentPosition();
1180 this.pdfHistory.push({
1181 namedDest,
1182 explicitDest,
1183 pageNumber,
1184 });
1185 }
1186 this.pdfViewer.scrollPageIntoView({
1187 pageNumber,
1188 destArray: explicitDest,
1189 ignoreDestinationZoom: this._ignoreDestinationZoom,
1190 });
1191 const ac = new AbortController();
1192 this.eventBus._on(
1193 'textlayerrendered',
1194 (evt) => {
1195 if (evt.pageNumber === pageNumber) {
1196 evt.source.textLayer.div.focus();
1197 ac.abort();
1198 }
1199 },
1200 {
1201 signal: ac.signal,
1202 }
1203 );
1204 }
1205 goToPage(val) {
1206 if (!this.pdfDocument) {
1207 return;
1208 }
1209 const pageNumber =
1210 (typeof val === 'string' && this.pdfViewer.pageLabelToPageNumber(val)) || val | 0;
1211 if (!(Number.isInteger(pageNumber) && pageNumber > 0 && pageNumber <= this.pagesCount)) {
1212 console.error(`PDFLinkService.goToPage: "${val}" is not a valid page.`);
1213 return;
1214 }
1215 if (this.pdfHistory) {
1216 this.pdfHistory.pushCurrentPosition();
1217 this.pdfHistory.pushPage(pageNumber);
1218 }
1219 this.pdfViewer.scrollPageIntoView({
1220 pageNumber,
1221 });
1222 }
1223 goToXY(pageNumber, x, y, options = {}) {
1224 this.pdfViewer.scrollPageIntoView({
1225 pageNumber,
1226 destArray: [
1227 null,
1228 {
1229 name: 'XYZ',
1230 },
1231 x,
1232 y,
1233 ],
1234 ignoreDestinationZoom: true,
1235 ...options,
1236 });
1237 }
1238 addLinkAttributes(link, url, newWindow = false) {
1239 if (!url || typeof url !== 'string') {
1240 throw new Error('A valid "url" parameter must provided.');
1241 }
1242 const target = newWindow ? LinkTarget.BLANK : this.externalLinkTarget,
1243 rel = this.externalLinkRel;
1244 if (this.externalLinkEnabled) {
1245 link.href = link.title = url;
1246 } else {
1247 link.href = '';
1248 link.title = `Disabled: ${url}`;
1249 link.onclick = () => false;
1250 }
1251 let targetStr = '';
1252 switch (target) {
1253 case LinkTarget.NONE:
1254 break;
1255 case LinkTarget.SELF:
1256 targetStr = '_self';
1257 break;
1258 case LinkTarget.BLANK:
1259 targetStr = '_blank';
1260 break;
1261 case LinkTarget.PARENT:
1262 targetStr = '_parent';
1263 break;
1264 case LinkTarget.TOP:
1265 targetStr = '_top';
1266 break;
1267 }
1268 link.target = targetStr;
1269 link.rel = typeof rel === 'string' ? rel : DEFAULT_LINK_REL;
1270 }
1271 getDestinationHash(dest) {
1272 if (typeof dest === 'string') {
1273 if (dest.length > 0) {
1274 return this.getAnchorUrl('#' + escape(dest));
1275 }
1276 } else if (Array.isArray(dest)) {
1277 const str = JSON.stringify(dest);
1278 if (str.length > 0) {
1279 return this.getAnchorUrl('#' + escape(str));
1280 }
1281 }
1282 return this.getAnchorUrl('');
1283 }
1284 getAnchorUrl(anchor) {
1285 return this.baseUrl ? this.baseUrl + anchor : anchor;
1286 }
1287 setHash(hash) {
1288 if (!this.pdfDocument) {
1289 return;
1290 }
1291 let pageNumber, dest;
1292 if (hash.includes('=')) {
1293 const params = parseQueryString(hash);
1294 if (params.has('search')) {
1295 const query = params.get('search').replaceAll('"', ''),
1296 phrase = params.get('phrase') === 'true';
1297 this.eventBus.dispatch('findfromurlhash', {
1298 source: this,
1299 query: phrase ? query : query.match(/\S+/g),
1300 });
1301 }
1302 if (params.has('page')) {
1303 pageNumber = params.get('page') | 0 || 1;
1304 }
1305 if (params.has('zoom')) {
1306 const zoomArgs = params.get('zoom').split(',');
1307 const zoomArg = zoomArgs[0];
1308 const zoomArgNumber = parseFloat(zoomArg);
1309 if (!zoomArg.includes('Fit')) {
1310 dest = [
1311 null,
1312 {
1313 name: 'XYZ',
1314 },
1315 zoomArgs.length > 1 ? zoomArgs[1] | 0 : null,
1316 zoomArgs.length > 2 ? zoomArgs[2] | 0 : null,
1317 zoomArgNumber ? zoomArgNumber / 100 : zoomArg,
1318 ];
1319 } else if (zoomArg === 'Fit' || zoomArg === 'FitB') {
1320 dest = [
1321 null,
1322 {
1323 name: zoomArg,
1324 },
1325 ];
1326 } else if (
1327 zoomArg === 'FitH' ||
1328 zoomArg === 'FitBH' ||
1329 zoomArg === 'FitV' ||
1330 zoomArg === 'FitBV'
1331 ) {
1332 dest = [
1333 null,
1334 {
1335 name: zoomArg,
1336 },
1337 zoomArgs.length > 1 ? zoomArgs[1] | 0 : null,
1338 ];
1339 } else if (zoomArg === 'FitR') {
1340 if (zoomArgs.length !== 5) {
1341 console.error('PDFLinkService.setHash: Not enough parameters for "FitR".');
1342 } else {
1343 dest = [
1344 null,
1345 {
1346 name: zoomArg,
1347 },
1348 zoomArgs[1] | 0,
1349 zoomArgs[2] | 0,
1350 zoomArgs[3] | 0,
1351 zoomArgs[4] | 0,
1352 ];
1353 }
1354 } else {
1355 console.error(`PDFLinkService.setHash: "${zoomArg}" is not a valid zoom value.`);
1356 }
1357 }
1358 if (dest) {
1359 this.pdfViewer.scrollPageIntoView({
1360 pageNumber: pageNumber || this.page,
1361 destArray: dest,
1362 allowNegativeOffset: true,
1363 });
1364 } else if (pageNumber) {
1365 this.page = pageNumber;
1366 }
1367 if (params.has('pagemode')) {
1368 this.eventBus.dispatch('pagemode', {
1369 source: this,
1370 mode: params.get('pagemode'),
1371 });
1372 }
1373 if (params.has('nameddest')) {
1374 this.goToDestination(params.get('nameddest'));
1375 }
1376 return;
1377 }
1378 dest = unescape(hash);
1379 try {
1380 dest = JSON.parse(dest);
1381 if (!Array.isArray(dest)) {
1382 dest = dest.toString();
1383 }
1384 } catch {}
1385 if (typeof dest === 'string' || isValidExplicitDest(dest)) {
1386 this.goToDestination(dest);
1387 return;
1388 }
1389 console.error(`PDFLinkService.setHash: "${unescape(hash)}" is not a valid destination.`);
1390 }
1391 executeNamedAction(action) {
1392 if (!this.pdfDocument) {
1393 return;
1394 }
1395 switch (action) {
1396 case 'GoBack':
1397 this.pdfHistory?.back();
1398 break;
1399 case 'GoForward':
1400 this.pdfHistory?.forward();
1401 break;
1402 case 'NextPage':
1403 this.pdfViewer.nextPage();
1404 break;
1405 case 'PrevPage':
1406 this.pdfViewer.previousPage();
1407 break;
1408 case 'LastPage':
1409 this.page = this.pagesCount;
1410 break;
1411 case 'FirstPage':
1412 this.page = 1;
1413 break;
1414 default:
1415 break;
1416 }
1417 this.eventBus.dispatch('namedaction', {
1418 source: this,
1419 action,
1420 });
1421 }
1422 async executeSetOCGState(action) {
1423 if (!this.pdfDocument) {
1424 return;
1425 }
1426 const pdfDocument = this.pdfDocument,
1427 optionalContentConfig = await this.pdfViewer.optionalContentConfigPromise;
1428 if (pdfDocument !== this.pdfDocument) {
1429 return;
1430 }
1431 optionalContentConfig.setOCGState(action);
1432 this.pdfViewer.optionalContentConfigPromise = Promise.resolve(optionalContentConfig);
1433 }
1434}
1435class SimpleLinkService extends PDFLinkService {
1436 setDocument(pdfDocument, baseUrl = null) {}
1437} // ./web/event_utils.js
1438
1439const WaitOnType = {
1440 EVENT: 'event',
1441 TIMEOUT: 'timeout',
1442};
1443async function waitOnEventOrTimeout({ target, name, delay = 0 }) {
1444 if (
1445 typeof target !== 'object' ||
1446 !(name && typeof name === 'string') ||
1447 !(Number.isInteger(delay) && delay >= 0)
1448 ) {
1449 throw new Error('waitOnEventOrTimeout - invalid parameters.');
1450 }
1451 const { promise, resolve } = Promise.withResolvers();
1452 const ac = new AbortController();
1453 function handler(type) {
1454 ac.abort();
1455 clearTimeout(timeout);
1456 resolve(type);
1457 }
1458 const evtMethod = target instanceof EventBus ? '_on' : 'addEventListener';
1459 target[evtMethod](name, handler.bind(null, WaitOnType.EVENT), {
1460 signal: ac.signal,
1461 });
1462 const timeout = setTimeout(handler.bind(null, WaitOnType.TIMEOUT), delay);
1463 return promise;
1464}
1465class EventBus {
1466 #listeners = Object.create(null);
1467 on(eventName, listener, options = null) {
1468 this._on(eventName, listener, {
1469 external: true,
1470 once: options?.once,
1471 signal: options?.signal,
1472 });
1473 }
1474 off(eventName, listener, options = null) {
1475 this._off(eventName, listener);
1476 }
1477 dispatch(eventName, data) {
1478 const eventListeners = this.#listeners[eventName];
1479 if (!eventListeners || eventListeners.length === 0) {
1480 return;
1481 }
1482 let externalListeners;
1483 for (const { listener, external, once } of eventListeners.slice(0)) {
1484 if (once) {
1485 this._off(eventName, listener);
1486 }
1487 if (external) {
1488 (externalListeners ||= []).push(listener);
1489 continue;
1490 }
1491 listener(data);
1492 }
1493 if (externalListeners) {
1494 for (const listener of externalListeners) {
1495 listener(data);
1496 }
1497 externalListeners = null;
1498 }
1499 }
1500 _on(eventName, listener, options = null) {
1501 let rmAbort = null;
1502 if (options?.signal instanceof AbortSignal) {
1503 const { signal } = options;
1504 if (signal.aborted) {
1505 console.error('Cannot use an `aborted` signal.');
1506 return;
1507 }
1508 const onAbort = () => this._off(eventName, listener);
1509 rmAbort = () => signal.removeEventListener('abort', onAbort);
1510 signal.addEventListener('abort', onAbort);
1511 }
1512 const eventListeners = (this.#listeners[eventName] ||= []);
1513 eventListeners.push({
1514 listener,
1515 external: options?.external === true,
1516 once: options?.once === true,
1517 rmAbort,
1518 });
1519 }
1520 _off(eventName, listener, options = null) {
1521 const eventListeners = this.#listeners[eventName];
1522 if (!eventListeners) {
1523 return;
1524 }
1525 for (let i = 0, ii = eventListeners.length; i < ii; i++) {
1526 const evt = eventListeners[i];
1527 if (evt.listener === listener) {
1528 evt.rmAbort?.();
1529 eventListeners.splice(i, 1);
1530 return;
1531 }
1532 }
1533 }
1534}
1535class FirefoxEventBus extends EventBus {
1536 #externalServices;
1537 #globalEventNames;
1538 #isInAutomation;
1539 constructor(globalEventNames, externalServices, isInAutomation) {
1540 super();
1541 this.#globalEventNames = globalEventNames;
1542 this.#externalServices = externalServices;
1543 this.#isInAutomation = isInAutomation;
1544 }
1545 dispatch(eventName, data) {
1546 throw new Error('Not implemented: FirefoxEventBus.dispatch');
1547 }
1548} // ./web/external_services.js
1549
1550class BaseExternalServices {
1551 updateFindControlState(data) {}
1552 updateFindMatchesCount(data) {}
1553 initPassiveLoading() {}
1554 reportTelemetry(data) {}
1555 reportText(data) {}
1556 async createL10n() {
1557 throw new Error('Not implemented: createL10n');
1558 }
1559 createScripting() {
1560 throw new Error('Not implemented: createScripting');
1561 }
1562 createSignatureStorage() {
1563 throw new Error('Not implemented: createSignatureStorage');
1564 }
1565 updateEditorStates(data) {
1566 throw new Error('Not implemented: updateEditorStates');
1567 }
1568 dispatchGlobalEvent(_event) {}
1569} // ./web/preferences.js
1570
1571class BasePreferences {
1572 #defaults = Object.freeze(AppOptions.getAll(OptionKind.PREFERENCE, true));
1573 #initializedPromise = null;
1574 constructor() {
1575 this.#initializedPromise = this._readFromStorage(this.#defaults).then(
1576 ({ browserPrefs, prefs }) => {
1577 if (AppOptions._checkDisablePreferences()) {
1578 return;
1579 }
1580 AppOptions.setAll(
1581 {
1582 ...browserPrefs,
1583 ...prefs,
1584 },
1585 true
1586 );
1587 }
1588 );
1589 }
1590 async _writeToStorage(prefObj) {
1591 throw new Error('Not implemented: _writeToStorage');
1592 }
1593 async _readFromStorage(prefObj) {
1594 throw new Error('Not implemented: _readFromStorage');
1595 }
1596 async reset() {
1597 await this.#initializedPromise;
1598 AppOptions.setAll(this.#defaults, true);
1599 await this._writeToStorage(this.#defaults);
1600 }
1601 async set(name, value) {
1602 await this.#initializedPromise;
1603 AppOptions.setAll(
1604 {
1605 [name]: value,
1606 },
1607 true
1608 );
1609 await this._writeToStorage(AppOptions.getAll(OptionKind.PREFERENCE));
1610 }
1611 async get(name) {
1612 await this.#initializedPromise;
1613 return AppOptions.get(name);
1614 }
1615 get defaults() {
1616 return this.#defaults;
1617 }
1618 get initializedPromise() {
1619 return this.#initializedPromise;
1620 }
1621} // ./node_modules/@fluent/bundle/esm/types.js
1622
1623class FluentType {
1624 constructor(value) {
1625 this.value = value;
1626 }
1627 valueOf() {
1628 return this.value;
1629 }
1630}
1631class FluentNone extends FluentType {
1632 constructor(value = '???') {
1633 super(value);
1634 }
1635 toString(scope) {
1636 return `{${this.value}}`;
1637 }
1638}
1639class FluentNumber extends FluentType {
1640 constructor(value, opts = {}) {
1641 super(value);
1642 this.opts = opts;
1643 }
1644 toString(scope) {
1645 if (scope) {
1646 try {
1647 const nf = scope.memoizeIntlObject(Intl.NumberFormat, this.opts);
1648 return nf.format(this.value);
1649 } catch (err) {
1650 scope.reportError(err);
1651 }
1652 }
1653 return this.value.toString(10);
1654 }
1655}
1656class FluentDateTime extends FluentType {
1657 static supportsValue(value) {
1658 if (typeof value === 'number') return true;
1659 if (value instanceof Date) return true;
1660 if (value instanceof FluentType) return FluentDateTime.supportsValue(value.valueOf());
1661 if ('Temporal' in globalThis) {
1662 const _Temporal = globalThis.Temporal;
1663 if (
1664 value instanceof _Temporal.Instant ||
1665 value instanceof _Temporal.PlainDateTime ||
1666 value instanceof _Temporal.PlainDate ||
1667 value instanceof _Temporal.PlainMonthDay ||
1668 value instanceof _Temporal.PlainTime ||
1669 value instanceof _Temporal.PlainYearMonth
1670 ) {
1671 return true;
1672 }
1673 }
1674 return false;
1675 }
1676 constructor(value, opts = {}) {
1677 if (value instanceof FluentDateTime) {
1678 opts = {
1679 ...value.opts,
1680 ...opts,
1681 };
1682 value = value.value;
1683 } else if (value instanceof FluentType) {
1684 value = value.valueOf();
1685 }
1686 if (typeof value === 'object' && 'calendarId' in value && opts.calendar === undefined) {
1687 opts = {
1688 ...opts,
1689 calendar: value.calendarId,
1690 };
1691 }
1692 super(value);
1693 this.opts = opts;
1694 }
1695 [Symbol.toPrimitive](hint) {
1696 return hint === 'string' ? this.toString() : this.toNumber();
1697 }
1698 toNumber() {
1699 const value = this.value;
1700 if (typeof value === 'number') return value;
1701 if (value instanceof Date) return value.getTime();
1702 if ('epochMilliseconds' in value) {
1703 return value.epochMilliseconds;
1704 }
1705 if ('toZonedDateTime' in value) {
1706 return value.toZonedDateTime('UTC').epochMilliseconds;
1707 }
1708 throw new TypeError('Unwrapping a non-number value as a number');
1709 }
1710 toString(scope) {
1711 if (scope) {
1712 try {
1713 const dtf = scope.memoizeIntlObject(Intl.DateTimeFormat, this.opts);
1714 return dtf.format(this.value);
1715 } catch (err) {
1716 scope.reportError(err);
1717 }
1718 }
1719 if (typeof this.value === 'number' || this.value instanceof Date) {
1720 return new Date(this.value).toISOString();
1721 }
1722 return this.value.toString();
1723 }
1724} // ./node_modules/@fluent/bundle/esm/resolver.js
1725const MAX_PLACEABLES = 100;
1726const FSI = '\u2068';
1727const PDI = '\u2069';
1728function match(scope, selector, key) {
1729 if (key === selector) {
1730 return true;
1731 }
1732 if (
1733 key instanceof FluentNumber &&
1734 selector instanceof FluentNumber &&
1735 key.value === selector.value
1736 ) {
1737 return true;
1738 }
1739 if (selector instanceof FluentNumber && typeof key === 'string') {
1740 let category = scope.memoizeIntlObject(Intl.PluralRules, selector.opts).select(selector.value);
1741 if (key === category) {
1742 return true;
1743 }
1744 }
1745 return false;
1746}
1747function getDefault(scope, variants, star) {
1748 if (variants[star]) {
1749 return resolvePattern(scope, variants[star].value);
1750 }
1751 scope.reportError(new RangeError('No default'));
1752 return new FluentNone();
1753}
1754function getArguments(scope, args) {
1755 const positional = [];
1756 const named = Object.create(null);
1757 for (const arg of args) {
1758 if (arg.type === 'narg') {
1759 named[arg.name] = resolveExpression(scope, arg.value);
1760 } else {
1761 positional.push(resolveExpression(scope, arg));
1762 }
1763 }
1764 return {
1765 positional,
1766 named,
1767 };
1768}
1769function resolveExpression(scope, expr) {
1770 switch (expr.type) {
1771 case 'str':
1772 return expr.value;
1773 case 'num':
1774 return new FluentNumber(expr.value, {
1775 minimumFractionDigits: expr.precision,
1776 });
1777 case 'var':
1778 return resolveVariableReference(scope, expr);
1779 case 'mesg':
1780 return resolveMessageReference(scope, expr);
1781 case 'term':
1782 return resolveTermReference(scope, expr);
1783 case 'func':
1784 return resolveFunctionReference(scope, expr);
1785 case 'select':
1786 return resolveSelectExpression(scope, expr);
1787 default:
1788 return new FluentNone();
1789 }
1790}
1791function resolveVariableReference(scope, { name }) {
1792 let arg;
1793 if (scope.params) {
1794 if (Object.prototype.hasOwnProperty.call(scope.params, name)) {
1795 arg = scope.params[name];
1796 } else {
1797 return new FluentNone(`$${name}`);
1798 }
1799 } else if (scope.args && Object.prototype.hasOwnProperty.call(scope.args, name)) {
1800 arg = scope.args[name];
1801 } else {
1802 scope.reportError(new ReferenceError(`Unknown variable: $${name}`));
1803 return new FluentNone(`$${name}`);
1804 }
1805 if (arg instanceof FluentType) {
1806 return arg;
1807 }
1808 switch (typeof arg) {
1809 case 'string':
1810 return arg;
1811 case 'number':
1812 return new FluentNumber(arg);
1813 case 'object':
1814 if (FluentDateTime.supportsValue(arg)) {
1815 return new FluentDateTime(arg);
1816 }
1817 default:
1818 scope.reportError(new TypeError(`Variable type not supported: $${name}, ${typeof arg}`));
1819 return new FluentNone(`$${name}`);
1820 }
1821}
1822function resolveMessageReference(scope, { name, attr }) {
1823 const message = scope.bundle._messages.get(name);
1824 if (!message) {
1825 scope.reportError(new ReferenceError(`Unknown message: ${name}`));
1826 return new FluentNone(name);
1827 }
1828 if (attr) {
1829 const attribute = message.attributes[attr];
1830 if (attribute) {
1831 return resolvePattern(scope, attribute);
1832 }
1833 scope.reportError(new ReferenceError(`Unknown attribute: ${attr}`));
1834 return new FluentNone(`${name}.${attr}`);
1835 }
1836 if (message.value) {
1837 return resolvePattern(scope, message.value);
1838 }
1839 scope.reportError(new ReferenceError(`No value: ${name}`));
1840 return new FluentNone(name);
1841}
1842function resolveTermReference(scope, { name, attr, args }) {
1843 const id = `-${name}`;
1844 const term = scope.bundle._terms.get(id);
1845 if (!term) {
1846 scope.reportError(new ReferenceError(`Unknown term: ${id}`));
1847 return new FluentNone(id);
1848 }
1849 if (attr) {
1850 const attribute = term.attributes[attr];
1851 if (attribute) {
1852 scope.params = getArguments(scope, args).named;
1853 const resolved = resolvePattern(scope, attribute);
1854 scope.params = null;
1855 return resolved;
1856 }
1857 scope.reportError(new ReferenceError(`Unknown attribute: ${attr}`));
1858 return new FluentNone(`${id}.${attr}`);
1859 }
1860 scope.params = getArguments(scope, args).named;
1861 const resolved = resolvePattern(scope, term.value);
1862 scope.params = null;
1863 return resolved;
1864}
1865function resolveFunctionReference(scope, { name, args }) {
1866 let func = scope.bundle._functions[name];
1867 if (!func) {
1868 scope.reportError(new ReferenceError(`Unknown function: ${name}()`));
1869 return new FluentNone(`${name}()`);
1870 }
1871 if (typeof func !== 'function') {
1872 scope.reportError(new TypeError(`Function ${name}() is not callable`));
1873 return new FluentNone(`${name}()`);
1874 }
1875 try {
1876 let resolved = getArguments(scope, args);
1877 return func(resolved.positional, resolved.named);
1878 } catch (err) {
1879 scope.reportError(err);
1880 return new FluentNone(`${name}()`);
1881 }
1882}
1883function resolveSelectExpression(scope, { selector, variants, star }) {
1884 let sel = resolveExpression(scope, selector);
1885 if (sel instanceof FluentNone) {
1886 return getDefault(scope, variants, star);
1887 }
1888 for (const variant of variants) {
1889 const key = resolveExpression(scope, variant.key);
1890 if (match(scope, sel, key)) {
1891 return resolvePattern(scope, variant.value);
1892 }
1893 }
1894 return getDefault(scope, variants, star);
1895}
1896function resolveComplexPattern(scope, ptn) {
1897 if (scope.dirty.has(ptn)) {
1898 scope.reportError(new RangeError('Cyclic reference'));
1899 return new FluentNone();
1900 }
1901 scope.dirty.add(ptn);
1902 const result = [];
1903 const useIsolating = scope.bundle._useIsolating && ptn.length > 1;
1904 for (const elem of ptn) {
1905 if (typeof elem === 'string') {
1906 result.push(scope.bundle._transform(elem));
1907 continue;
1908 }
1909 scope.placeables++;
1910 if (scope.placeables > MAX_PLACEABLES) {
1911 scope.dirty.delete(ptn);
1912 throw new RangeError(
1913 `Too many placeables expanded: ${scope.placeables}, ` + `max allowed is ${MAX_PLACEABLES}`
1914 );
1915 }
1916 if (useIsolating) {
1917 result.push(FSI);
1918 }
1919 result.push(resolveExpression(scope, elem).toString(scope));
1920 if (useIsolating) {
1921 result.push(PDI);
1922 }
1923 }
1924 scope.dirty.delete(ptn);
1925 return result.join('');
1926}
1927function resolvePattern(scope, value) {
1928 if (typeof value === 'string') {
1929 return scope.bundle._transform(value);
1930 }
1931 return resolveComplexPattern(scope, value);
1932} // ./node_modules/@fluent/bundle/esm/scope.js
1933class Scope {
1934 constructor(bundle, errors, args) {
1935 this.dirty = new WeakSet();
1936 this.params = null;
1937 this.placeables = 0;
1938 this.bundle = bundle;
1939 this.errors = errors;
1940 this.args = args;
1941 }
1942 reportError(error) {
1943 if (!this.errors || !(error instanceof Error)) {
1944 throw error;
1945 }
1946 this.errors.push(error);
1947 }
1948 memoizeIntlObject(ctor, opts) {
1949 let cache = this.bundle._intls.get(ctor);
1950 if (!cache) {
1951 cache = {};
1952 this.bundle._intls.set(ctor, cache);
1953 }
1954 let id = JSON.stringify(opts);
1955 if (!cache[id]) {
1956 cache[id] = new ctor(this.bundle.locales, opts);
1957 }
1958 return cache[id];
1959 }
1960} // ./node_modules/@fluent/bundle/esm/builtins.js
1961function values(opts, allowed) {
1962 const unwrapped = Object.create(null);
1963 for (const [name, opt] of Object.entries(opts)) {
1964 if (allowed.includes(name)) {
1965 unwrapped[name] = opt.valueOf();
1966 }
1967 }
1968 return unwrapped;
1969}
1970const NUMBER_ALLOWED = [
1971 'unitDisplay',
1972 'currencyDisplay',
1973 'useGrouping',
1974 'minimumIntegerDigits',
1975 'minimumFractionDigits',
1976 'maximumFractionDigits',
1977 'minimumSignificantDigits',
1978 'maximumSignificantDigits',
1979];
1980function NUMBER(args, opts) {
1981 let arg = args[0];
1982 if (arg instanceof FluentNone) {
1983 return new FluentNone(`NUMBER(${arg.valueOf()})`);
1984 }
1985 if (arg instanceof FluentNumber) {
1986 return new FluentNumber(arg.valueOf(), {
1987 ...arg.opts,
1988 ...values(opts, NUMBER_ALLOWED),
1989 });
1990 }
1991 if (arg instanceof FluentDateTime) {
1992 return new FluentNumber(arg.toNumber(), {
1993 ...values(opts, NUMBER_ALLOWED),
1994 });
1995 }
1996 throw new TypeError('Invalid argument to NUMBER');
1997}
1998const DATETIME_ALLOWED = [
1999 'dateStyle',
2000 'timeStyle',
2001 'fractionalSecondDigits',
2002 'dayPeriod',
2003 'hour12',
2004 'weekday',
2005 'era',
2006 'year',
2007 'month',
2008 'day',
2009 'hour',
2010 'minute',
2011 'second',
2012 'timeZoneName',
2013];
2014function DATETIME(args, opts) {
2015 let arg = args[0];
2016 if (arg instanceof FluentNone) {
2017 return new FluentNone(`DATETIME(${arg.valueOf()})`);
2018 }
2019 if (arg instanceof FluentDateTime || arg instanceof FluentNumber) {
2020 return new FluentDateTime(arg, values(opts, DATETIME_ALLOWED));
2021 }
2022 throw new TypeError('Invalid argument to DATETIME');
2023} // ./node_modules/@fluent/bundle/esm/memoizer.js
2024const cache = new Map();
2025function getMemoizerForLocale(locales) {
2026 const stringLocale = Array.isArray(locales) ? locales.join(' ') : locales;
2027 let memoizer = cache.get(stringLocale);
2028 if (memoizer === undefined) {
2029 memoizer = new Map();
2030 cache.set(stringLocale, memoizer);
2031 }
2032 return memoizer;
2033} // ./node_modules/@fluent/bundle/esm/bundle.js
2034class FluentBundle {
2035 constructor(locales, { functions, useIsolating = true, transform = (v) => v } = {}) {
2036 this._terms = new Map();
2037 this._messages = new Map();
2038 this.locales = Array.isArray(locales) ? locales : [locales];
2039 this._functions = {
2040 NUMBER: NUMBER,
2041 DATETIME: DATETIME,
2042 ...functions,
2043 };
2044 this._useIsolating = useIsolating;
2045 this._transform = transform;
2046 this._intls = getMemoizerForLocale(locales);
2047 }
2048 hasMessage(id) {
2049 return this._messages.has(id);
2050 }
2051 getMessage(id) {
2052 return this._messages.get(id);
2053 }
2054 addResource(res, { allowOverrides = false } = {}) {
2055 const errors = [];
2056 for (let i = 0; i < res.body.length; i++) {
2057 let entry = res.body[i];
2058 if (entry.id.startsWith('-')) {
2059 if (allowOverrides === false && this._terms.has(entry.id)) {
2060 errors.push(new Error(`Attempt to override an existing term: "${entry.id}"`));
2061 continue;
2062 }
2063 this._terms.set(entry.id, entry);
2064 } else {
2065 if (allowOverrides === false && this._messages.has(entry.id)) {
2066 errors.push(new Error(`Attempt to override an existing message: "${entry.id}"`));
2067 continue;
2068 }
2069 this._messages.set(entry.id, entry);
2070 }
2071 }
2072 return errors;
2073 }
2074 formatPattern(pattern, args = null, errors = null) {
2075 if (typeof pattern === 'string') {
2076 return this._transform(pattern);
2077 }
2078 let scope = new Scope(this, errors, args);
2079 try {
2080 let value = resolveComplexPattern(scope, pattern);
2081 return value.toString(scope);
2082 } catch (err) {
2083 if (scope.errors && err instanceof Error) {
2084 scope.errors.push(err);
2085 return new FluentNone().toString(scope);
2086 }
2087 throw err;
2088 }
2089 }
2090} // ./node_modules/@fluent/bundle/esm/resource.js
2091const RE_MESSAGE_START = /^(-?[a-zA-Z][\w-]*) *= */gm;
2092const RE_ATTRIBUTE_START = /\.([a-zA-Z][\w-]*) *= */y;
2093const RE_VARIANT_START = /\*?\[/y;
2094const RE_NUMBER_LITERAL = /(-?[0-9]+(?:\.([0-9]+))?)/y;
2095const RE_IDENTIFIER = /([a-zA-Z][\w-]*)/y;
2096const RE_REFERENCE = /([$-])?([a-zA-Z][\w-]*)(?:\.([a-zA-Z][\w-]*))?/y;
2097const RE_FUNCTION_NAME = /^[A-Z][A-Z0-9_-]*$/;
2098const RE_TEXT_RUN = /([^{}\n\r]+)/y;
2099const RE_STRING_RUN = /([^\\"\n\r]*)/y;
2100const RE_STRING_ESCAPE = /\\([\\"])/y;
2101const RE_UNICODE_ESCAPE = /\\u([a-fA-F0-9]{4})|\\U([a-fA-F0-9]{6})/y;
2102const RE_LEADING_NEWLINES = /^\n+/;
2103const RE_TRAILING_SPACES = / +$/;
2104const RE_BLANK_LINES = / *\r?\n/g;
2105const RE_INDENT = /( *)$/;
2106const TOKEN_BRACE_OPEN = /{\s*/y;
2107const TOKEN_BRACE_CLOSE = /\s*}/y;
2108const TOKEN_BRACKET_OPEN = /\[\s*/y;
2109const TOKEN_BRACKET_CLOSE = /\s*] */y;
2110const TOKEN_PAREN_OPEN = /\s*\(\s*/y;
2111const TOKEN_ARROW = /\s*->\s*/y;
2112const TOKEN_COLON = /\s*:\s*/y;
2113const TOKEN_COMMA = /\s*,?\s*/y;
2114const TOKEN_BLANK = /\s+/y;
2115class FluentResource {
2116 constructor(source) {
2117 this.body = [];
2118 RE_MESSAGE_START.lastIndex = 0;
2119 let cursor = 0;
2120 while (true) {
2121 let next = RE_MESSAGE_START.exec(source);
2122 if (next === null) {
2123 break;
2124 }
2125 cursor = RE_MESSAGE_START.lastIndex;
2126 try {
2127 this.body.push(parseMessage(next[1]));
2128 } catch (err) {
2129 if (err instanceof SyntaxError) {
2130 continue;
2131 }
2132 throw err;
2133 }
2134 }
2135 function test(re) {
2136 re.lastIndex = cursor;
2137 return re.test(source);
2138 }
2139 function consumeChar(char, errorClass) {
2140 if (source[cursor] === char) {
2141 cursor++;
2142 return true;
2143 }
2144 if (errorClass) {
2145 throw new errorClass(`Expected ${char}`);
2146 }
2147 return false;
2148 }
2149 function consumeToken(re, errorClass) {
2150 if (test(re)) {
2151 cursor = re.lastIndex;
2152 return true;
2153 }
2154 if (errorClass) {
2155 throw new errorClass(`Expected ${re.toString()}`);
2156 }
2157 return false;
2158 }
2159 function match(re) {
2160 re.lastIndex = cursor;
2161 let result = re.exec(source);
2162 if (result === null) {
2163 throw new SyntaxError(`Expected ${re.toString()}`);
2164 }
2165 cursor = re.lastIndex;
2166 return result;
2167 }
2168 function match1(re) {
2169 return match(re)[1];
2170 }
2171 function parseMessage(id) {
2172 let value = parsePattern();
2173 let attributes = parseAttributes();
2174 if (value === null && Object.keys(attributes).length === 0) {
2175 throw new SyntaxError('Expected message value or attributes');
2176 }
2177 return {
2178 id,
2179 value,
2180 attributes,
2181 };
2182 }
2183 function parseAttributes() {
2184 let attrs = Object.create(null);
2185 while (test(RE_ATTRIBUTE_START)) {
2186 let name = match1(RE_ATTRIBUTE_START);
2187 let value = parsePattern();
2188 if (value === null) {
2189 throw new SyntaxError('Expected attribute value');
2190 }
2191 attrs[name] = value;
2192 }
2193 return attrs;
2194 }
2195 function parsePattern() {
2196 let first;
2197 if (test(RE_TEXT_RUN)) {
2198 first = match1(RE_TEXT_RUN);
2199 }
2200 if (source[cursor] === '{' || source[cursor] === '}') {
2201 return parsePatternElements(first ? [first] : [], Infinity);
2202 }
2203 let indent = parseIndent();
2204 if (indent) {
2205 if (first) {
2206 return parsePatternElements([first, indent], indent.length);
2207 }
2208 indent.value = trim(indent.value, RE_LEADING_NEWLINES);
2209 return parsePatternElements([indent], indent.length);
2210 }
2211 if (first) {
2212 return trim(first, RE_TRAILING_SPACES);
2213 }
2214 return null;
2215 }
2216 function parsePatternElements(elements = [], commonIndent) {
2217 while (true) {
2218 if (test(RE_TEXT_RUN)) {
2219 elements.push(match1(RE_TEXT_RUN));
2220 continue;
2221 }
2222 if (source[cursor] === '{') {
2223 elements.push(parsePlaceable());
2224 continue;
2225 }
2226 if (source[cursor] === '}') {
2227 throw new SyntaxError('Unbalanced closing brace');
2228 }
2229 let indent = parseIndent();
2230 if (indent) {
2231 elements.push(indent);
2232 commonIndent = Math.min(commonIndent, indent.length);
2233 continue;
2234 }
2235 break;
2236 }
2237 let lastIndex = elements.length - 1;
2238 let lastElement = elements[lastIndex];
2239 if (typeof lastElement === 'string') {
2240 elements[lastIndex] = trim(lastElement, RE_TRAILING_SPACES);
2241 }
2242 let baked = [];
2243 for (let element of elements) {
2244 if (element instanceof Indent) {
2245 element = element.value.slice(0, element.value.length - commonIndent);
2246 }
2247 if (element) {
2248 baked.push(element);
2249 }
2250 }
2251 return baked;
2252 }
2253 function parsePlaceable() {
2254 consumeToken(TOKEN_BRACE_OPEN, SyntaxError);
2255 let selector = parseInlineExpression();
2256 if (consumeToken(TOKEN_BRACE_CLOSE)) {
2257 return selector;
2258 }
2259 if (consumeToken(TOKEN_ARROW)) {
2260 let variants = parseVariants();
2261 consumeToken(TOKEN_BRACE_CLOSE, SyntaxError);
2262 return {
2263 type: 'select',
2264 selector,
2265 ...variants,
2266 };
2267 }
2268 throw new SyntaxError('Unclosed placeable');
2269 }
2270 function parseInlineExpression() {
2271 if (source[cursor] === '{') {
2272 return parsePlaceable();
2273 }
2274 if (test(RE_REFERENCE)) {
2275 let [, sigil, name, attr = null] = match(RE_REFERENCE);
2276 if (sigil === '$') {
2277 return {
2278 type: 'var',
2279 name,
2280 };
2281 }
2282 if (consumeToken(TOKEN_PAREN_OPEN)) {
2283 let args = parseArguments();
2284 if (sigil === '-') {
2285 return {
2286 type: 'term',
2287 name,
2288 attr,
2289 args,
2290 };
2291 }
2292 if (RE_FUNCTION_NAME.test(name)) {
2293 return {
2294 type: 'func',
2295 name,
2296 args,
2297 };
2298 }
2299 throw new SyntaxError('Function names must be all upper-case');
2300 }
2301 if (sigil === '-') {
2302 return {
2303 type: 'term',
2304 name,
2305 attr,
2306 args: [],
2307 };
2308 }
2309 return {
2310 type: 'mesg',
2311 name,
2312 attr,
2313 };
2314 }
2315 return parseLiteral();
2316 }
2317 function parseArguments() {
2318 let args = [];
2319 while (true) {
2320 switch (source[cursor]) {
2321 case ')':
2322 cursor++;
2323 return args;
2324 case undefined:
2325 throw new SyntaxError('Unclosed argument list');
2326 }
2327 args.push(parseArgument());
2328 consumeToken(TOKEN_COMMA);
2329 }
2330 }
2331 function parseArgument() {
2332 let expr = parseInlineExpression();
2333 if (expr.type !== 'mesg') {
2334 return expr;
2335 }
2336 if (consumeToken(TOKEN_COLON)) {
2337 return {
2338 type: 'narg',
2339 name: expr.name,
2340 value: parseLiteral(),
2341 };
2342 }
2343 return expr;
2344 }
2345 function parseVariants() {
2346 let variants = [];
2347 let count = 0;
2348 let star;
2349 while (test(RE_VARIANT_START)) {
2350 if (consumeChar('*')) {
2351 star = count;
2352 }
2353 let key = parseVariantKey();
2354 let value = parsePattern();
2355 if (value === null) {
2356 throw new SyntaxError('Expected variant value');
2357 }
2358 variants[count++] = {
2359 key,
2360 value,
2361 };
2362 }
2363 if (count === 0) {
2364 return null;
2365 }
2366 if (star === undefined) {
2367 throw new SyntaxError('Expected default variant');
2368 }
2369 return {
2370 variants,
2371 star,
2372 };
2373 }
2374 function parseVariantKey() {
2375 consumeToken(TOKEN_BRACKET_OPEN, SyntaxError);
2376 let key;
2377 if (test(RE_NUMBER_LITERAL)) {
2378 key = parseNumberLiteral();
2379 } else {
2380 key = {
2381 type: 'str',
2382 value: match1(RE_IDENTIFIER),
2383 };
2384 }
2385 consumeToken(TOKEN_BRACKET_CLOSE, SyntaxError);
2386 return key;
2387 }
2388 function parseLiteral() {
2389 if (test(RE_NUMBER_LITERAL)) {
2390 return parseNumberLiteral();
2391 }
2392 if (source[cursor] === '"') {
2393 return parseStringLiteral();
2394 }
2395 throw new SyntaxError('Invalid expression');
2396 }
2397 function parseNumberLiteral() {
2398 let [, value, fraction = ''] = match(RE_NUMBER_LITERAL);
2399 let precision = fraction.length;
2400 return {
2401 type: 'num',
2402 value: parseFloat(value),
2403 precision,
2404 };
2405 }
2406 function parseStringLiteral() {
2407 consumeChar('"', SyntaxError);
2408 let value = '';
2409 while (true) {
2410 value += match1(RE_STRING_RUN);
2411 if (source[cursor] === '\\') {
2412 value += parseEscapeSequence();
2413 continue;
2414 }
2415 if (consumeChar('"')) {
2416 return {
2417 type: 'str',
2418 value,
2419 };
2420 }
2421 throw new SyntaxError('Unclosed string literal');
2422 }
2423 }
2424 function parseEscapeSequence() {
2425 if (test(RE_STRING_ESCAPE)) {
2426 return match1(RE_STRING_ESCAPE);
2427 }
2428 if (test(RE_UNICODE_ESCAPE)) {
2429 let [, codepoint4, codepoint6] = match(RE_UNICODE_ESCAPE);
2430 let codepoint = parseInt(codepoint4 || codepoint6, 16);
2431 return codepoint <= 0xd7ff || 0xe000 <= codepoint ? String.fromCodePoint(codepoint) : '�';
2432 }
2433 throw new SyntaxError('Unknown escape sequence');
2434 }
2435 function parseIndent() {
2436 let start = cursor;
2437 consumeToken(TOKEN_BLANK);
2438 switch (source[cursor]) {
2439 case '.':
2440 case '[':
2441 case '*':
2442 case '}':
2443 case undefined:
2444 return false;
2445 case '{':
2446 return makeIndent(source.slice(start, cursor));
2447 }
2448 if (source[cursor - 1] === ' ') {
2449 return makeIndent(source.slice(start, cursor));
2450 }
2451 return false;
2452 }
2453 function trim(text, re) {
2454 return text.replace(re, '');
2455 }
2456 function makeIndent(blank) {
2457 let value = blank.replace(RE_BLANK_LINES, '\n');
2458 let length = RE_INDENT.exec(blank)[1].length;
2459 return new Indent(value, length);
2460 }
2461 }
2462}
2463class Indent {
2464 constructor(value, length) {
2465 this.value = value;
2466 this.length = length;
2467 }
2468} // ./node_modules/@fluent/bundle/esm/index.js
2469// ./node_modules/@fluent/dom/esm/overlay.js
2470const reOverlay = /<|&#?\w+;/;
2471const TEXT_LEVEL_ELEMENTS = {
2472 'http://www.w3.org/1999/xhtml': [
2473 'em',
2474 'strong',
2475 'small',
2476 's',
2477 'cite',
2478 'q',
2479 'dfn',
2480 'abbr',
2481 'data',
2482 'time',
2483 'code',
2484 'var',
2485 'samp',
2486 'kbd',
2487 'sub',
2488 'sup',
2489 'i',
2490 'b',
2491 'u',
2492 'mark',
2493 'bdi',
2494 'bdo',
2495 'span',
2496 'br',
2497 'wbr',
2498 ],
2499};
2500const LOCALIZABLE_ATTRIBUTES = {
2501 'http://www.w3.org/1999/xhtml': {
2502 global: ['title', 'aria-description', 'aria-label', 'aria-valuetext'],
2503 a: ['download'],
2504 area: ['download', 'alt'],
2505 input: ['alt', 'placeholder'],
2506 menuitem: ['label'],
2507 menu: ['label'],
2508 optgroup: ['label'],
2509 option: ['label'],
2510 track: ['label'],
2511 img: ['alt'],
2512 textarea: ['placeholder'],
2513 th: ['abbr'],
2514 },
2515 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul': {
2516 global: ['accesskey', 'aria-label', 'aria-valuetext', 'label', 'title', 'tooltiptext'],
2517 description: ['value'],
2518 key: ['key', 'keycode'],
2519 label: ['value'],
2520 textbox: ['placeholder', 'value'],
2521 },
2522};
2523function translateElement(element, translation) {
2524 const { value } = translation;
2525 if (typeof value === 'string') {
2526 if (element.localName === 'title' && element.namespaceURI === 'http://www.w3.org/1999/xhtml') {
2527 element.textContent = value;
2528 } else if (!reOverlay.test(value)) {
2529 element.textContent = value;
2530 } else {
2531 const templateElement = element.ownerDocument.createElementNS(
2532 'http://www.w3.org/1999/xhtml',
2533 'template'
2534 );
2535 templateElement.innerHTML = value;
2536 overlayChildNodes(templateElement.content, element);
2537 }
2538 }
2539 overlayAttributes(translation, element);
2540}
2541function overlayChildNodes(fromFragment, toElement) {
2542 for (const childNode of fromFragment.childNodes) {
2543 if (childNode.nodeType === childNode.TEXT_NODE) {
2544 continue;
2545 }
2546 if (childNode.hasAttribute('data-l10n-name')) {
2547 const sanitized = getNodeForNamedElement(toElement, childNode);
2548 fromFragment.replaceChild(sanitized, childNode);
2549 continue;
2550 }
2551 if (isElementAllowed(childNode)) {
2552 const sanitized = createSanitizedElement(childNode);
2553 fromFragment.replaceChild(sanitized, childNode);
2554 continue;
2555 }
2556 console.warn(
2557 `An element of forbidden type "${childNode.localName}" was found in ` +
2558 'the translation. Only safe text-level elements and elements with ' +
2559 'data-l10n-name are allowed.'
2560 );
2561 fromFragment.replaceChild(createTextNodeFromTextContent(childNode), childNode);
2562 }
2563 toElement.textContent = '';
2564 toElement.appendChild(fromFragment);
2565}
2566function hasAttribute(attributes, name) {
2567 if (!attributes) {
2568 return false;
2569 }
2570 for (let attr of attributes) {
2571 if (attr.name === name) {
2572 return true;
2573 }
2574 }
2575 return false;
2576}
2577function overlayAttributes(fromElement, toElement) {
2578 const explicitlyAllowed = toElement.hasAttribute('data-l10n-attrs')
2579 ? toElement
2580 .getAttribute('data-l10n-attrs')
2581 .split(',')
2582 .map((i) => i.trim())
2583 : null;
2584 for (const attr of Array.from(toElement.attributes)) {
2585 if (
2586 isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed) &&
2587 !hasAttribute(fromElement.attributes, attr.name)
2588 ) {
2589 toElement.removeAttribute(attr.name);
2590 }
2591 }
2592 if (!fromElement.attributes) {
2593 return;
2594 }
2595 for (const attr of Array.from(fromElement.attributes)) {
2596 if (
2597 isAttrNameLocalizable(attr.name, toElement, explicitlyAllowed) &&
2598 toElement.getAttribute(attr.name) !== attr.value
2599 ) {
2600 toElement.setAttribute(attr.name, attr.value);
2601 }
2602 }
2603}
2604function getNodeForNamedElement(sourceElement, translatedChild) {
2605 const childName = translatedChild.getAttribute('data-l10n-name');
2606 const sourceChild = sourceElement.querySelector(`[data-l10n-name="${childName}"]`);
2607 if (!sourceChild) {
2608 console.warn(`An element named "${childName}" wasn't found in the source.`);
2609 return createTextNodeFromTextContent(translatedChild);
2610 }
2611 if (sourceChild.localName !== translatedChild.localName) {
2612 console.warn(
2613 `An element named "${childName}" was found in the translation ` +
2614 `but its type ${translatedChild.localName} didn't match the ` +
2615 `element found in the source (${sourceChild.localName}).`
2616 );
2617 return createTextNodeFromTextContent(translatedChild);
2618 }
2619 sourceElement.removeChild(sourceChild);
2620 const clone = sourceChild.cloneNode(false);
2621 return shallowPopulateUsing(translatedChild, clone);
2622}
2623function createSanitizedElement(element) {
2624 const clone = element.ownerDocument.createElement(element.localName);
2625 return shallowPopulateUsing(element, clone);
2626}
2627function createTextNodeFromTextContent(element) {
2628 return element.ownerDocument.createTextNode(element.textContent);
2629}
2630function isElementAllowed(element) {
2631 const allowed = TEXT_LEVEL_ELEMENTS[element.namespaceURI];
2632 return allowed && allowed.includes(element.localName);
2633}
2634function isAttrNameLocalizable(name, element, explicitlyAllowed = null) {
2635 if (explicitlyAllowed && explicitlyAllowed.includes(name)) {
2636 return true;
2637 }
2638 const allowed = LOCALIZABLE_ATTRIBUTES[element.namespaceURI];
2639 if (!allowed) {
2640 return false;
2641 }
2642 const attrName = name.toLowerCase();
2643 const elemName = element.localName;
2644 if (allowed.global.includes(attrName)) {
2645 return true;
2646 }
2647 if (!allowed[elemName]) {
2648 return false;
2649 }
2650 if (allowed[elemName].includes(attrName)) {
2651 return true;
2652 }
2653 if (
2654 element.namespaceURI === 'http://www.w3.org/1999/xhtml' &&
2655 elemName === 'input' &&
2656 attrName === 'value'
2657 ) {
2658 const type = element.type.toLowerCase();
2659 if (type === 'submit' || type === 'button' || type === 'reset') {
2660 return true;
2661 }
2662 }
2663 return false;
2664}
2665function shallowPopulateUsing(fromElement, toElement) {
2666 toElement.textContent = fromElement.textContent;
2667 overlayAttributes(fromElement, toElement);
2668 return toElement;
2669} // ./node_modules/cached-iterable/src/cached_iterable.mjs
2670class CachedIterable extends Array {
2671 static from(iterable) {
2672 if (iterable instanceof this) {
2673 return iterable;
2674 }
2675 return new this(iterable);
2676 }
2677} // ./node_modules/cached-iterable/src/cached_sync_iterable.mjs
2678class CachedSyncIterable extends CachedIterable {
2679 constructor(iterable) {
2680 super();
2681 if (Symbol.iterator in Object(iterable)) {
2682 this.iterator = iterable[Symbol.iterator]();
2683 } else {
2684 throw new TypeError('Argument must implement the iteration protocol.');
2685 }
2686 }
2687 [Symbol.iterator]() {
2688 const cached = this;
2689 let cur = 0;
2690 return {
2691 next() {
2692 if (cached.length <= cur) {
2693 cached.push(cached.iterator.next());
2694 }
2695 return cached[cur++];
2696 },
2697 };
2698 }
2699 touchNext(count = 1) {
2700 let idx = 0;
2701 while (idx++ < count) {
2702 const last = this[this.length - 1];
2703 if (last && last.done) {
2704 break;
2705 }
2706 this.push(this.iterator.next());
2707 }
2708 return this[this.length - 1];
2709 }
2710} // ./node_modules/cached-iterable/src/cached_async_iterable.mjs
2711class CachedAsyncIterable extends CachedIterable {
2712 constructor(iterable) {
2713 super();
2714 if (Symbol.asyncIterator in Object(iterable)) {
2715 this.iterator = iterable[Symbol.asyncIterator]();
2716 } else if (Symbol.iterator in Object(iterable)) {
2717 this.iterator = iterable[Symbol.iterator]();
2718 } else {
2719 throw new TypeError('Argument must implement the iteration protocol.');
2720 }
2721 }
2722 [Symbol.asyncIterator]() {
2723 const cached = this;
2724 let cur = 0;
2725 return {
2726 async next() {
2727 if (cached.length <= cur) {
2728 cached.push(cached.iterator.next());
2729 }
2730 return cached[cur++];
2731 },
2732 };
2733 }
2734 async touchNext(count = 1) {
2735 let idx = 0;
2736 while (idx++ < count) {
2737 const last = this[this.length - 1];
2738 if (last && (await last).done) {
2739 break;
2740 }
2741 this.push(this.iterator.next());
2742 }
2743 return this[this.length - 1];
2744 }
2745} // ./node_modules/cached-iterable/src/index.mjs
2746// ./node_modules/@fluent/dom/esm/localization.js
2747class Localization {
2748 constructor(resourceIds = [], generateBundles) {
2749 this.resourceIds = resourceIds;
2750 this.generateBundles = generateBundles;
2751 this.onChange(true);
2752 }
2753 addResourceIds(resourceIds, eager = false) {
2754 this.resourceIds.push(...resourceIds);
2755 this.onChange(eager);
2756 return this.resourceIds.length;
2757 }
2758 removeResourceIds(resourceIds) {
2759 this.resourceIds = this.resourceIds.filter((r) => !resourceIds.includes(r));
2760 this.onChange();
2761 return this.resourceIds.length;
2762 }
2763 async formatWithFallback(keys, method) {
2764 const translations = [];
2765 let hasAtLeastOneBundle = false;
2766 for await (const bundle of this.bundles) {
2767 hasAtLeastOneBundle = true;
2768 const missingIds = keysFromBundle(method, bundle, keys, translations);
2769 if (missingIds.size === 0) {
2770 break;
2771 }
2772 if (typeof console !== 'undefined') {
2773 const locale = bundle.locales[0];
2774 const ids = Array.from(missingIds).join(', ');
2775 console.warn(`[fluent] Missing translations in ${locale}: ${ids}`);
2776 }
2777 }
2778 if (!hasAtLeastOneBundle && typeof console !== 'undefined') {
2779 console.warn(`[fluent] Request for keys failed because no resource bundles got generated.
2780 keys: ${JSON.stringify(keys)}.
2781 resourceIds: ${JSON.stringify(this.resourceIds)}.`);
2782 }
2783 return translations;
2784 }
2785 formatMessages(keys) {
2786 return this.formatWithFallback(keys, messageFromBundle);
2787 }
2788 formatValues(keys) {
2789 return this.formatWithFallback(keys, valueFromBundle);
2790 }
2791 async formatValue(id, args) {
2792 const [val] = await this.formatValues([
2793 {
2794 id,
2795 args,
2796 },
2797 ]);
2798 return val;
2799 }
2800 handleEvent() {
2801 this.onChange();
2802 }
2803 onChange(eager = false) {
2804 this.bundles = CachedAsyncIterable.from(this.generateBundles(this.resourceIds));
2805 if (eager) {
2806 this.bundles.touchNext(2);
2807 }
2808 }
2809}
2810function valueFromBundle(bundle, errors, message, args) {
2811 if (message.value) {
2812 return bundle.formatPattern(message.value, args, errors);
2813 }
2814 return null;
2815}
2816function messageFromBundle(bundle, errors, message, args) {
2817 const formatted = {
2818 value: null,
2819 attributes: null,
2820 };
2821 if (message.value) {
2822 formatted.value = bundle.formatPattern(message.value, args, errors);
2823 }
2824 let attrNames = Object.keys(message.attributes);
2825 if (attrNames.length > 0) {
2826 formatted.attributes = new Array(attrNames.length);
2827 for (let [i, name] of attrNames.entries()) {
2828 let value = bundle.formatPattern(message.attributes[name], args, errors);
2829 formatted.attributes[i] = {
2830 name,
2831 value,
2832 };
2833 }
2834 }
2835 return formatted;
2836}
2837function keysFromBundle(method, bundle, keys, translations) {
2838 const messageErrors = [];
2839 const missingIds = new Set();
2840 keys.forEach(({ id, args }, i) => {
2841 if (translations[i] !== undefined) {
2842 return;
2843 }
2844 let message = bundle.getMessage(id);
2845 if (message) {
2846 messageErrors.length = 0;
2847 translations[i] = method(bundle, messageErrors, message, args);
2848 if (messageErrors.length > 0 && typeof console !== 'undefined') {
2849 const locale = bundle.locales[0];
2850 const errors = messageErrors.join(', ');
2851 console.warn(`[fluent][resolver] errors in ${locale}/${id}: ${errors}.`);
2852 }
2853 } else {
2854 missingIds.add(id);
2855 }
2856 });
2857 return missingIds;
2858} // ./node_modules/@fluent/dom/esm/dom_localization.js
2859const L10NID_ATTR_NAME = 'data-l10n-id';
2860const L10NARGS_ATTR_NAME = 'data-l10n-args';
2861const L10N_ELEMENT_QUERY = `[${L10NID_ATTR_NAME}]`;
2862class DOMLocalization extends Localization {
2863 constructor(resourceIds, generateBundles) {
2864 super(resourceIds, generateBundles);
2865 this.roots = new Set();
2866 this.pendingrAF = null;
2867 this.pendingElements = new Set();
2868 this.windowElement = null;
2869 this.mutationObserver = null;
2870 this.observerConfig = {
2871 attributes: true,
2872 characterData: false,
2873 childList: true,
2874 subtree: true,
2875 attributeFilter: [L10NID_ATTR_NAME, L10NARGS_ATTR_NAME],
2876 };
2877 }
2878 onChange(eager = false) {
2879 super.onChange(eager);
2880 if (this.roots) {
2881 this.translateRoots();
2882 }
2883 }
2884 setAttributes(element, id, args) {
2885 element.setAttribute(L10NID_ATTR_NAME, id);
2886 if (args) {
2887 element.setAttribute(L10NARGS_ATTR_NAME, JSON.stringify(args));
2888 } else {
2889 element.removeAttribute(L10NARGS_ATTR_NAME);
2890 }
2891 return element;
2892 }
2893 getAttributes(element) {
2894 return {
2895 id: element.getAttribute(L10NID_ATTR_NAME),
2896 args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null),
2897 };
2898 }
2899 connectRoot(newRoot) {
2900 for (const root of this.roots) {
2901 if (root === newRoot || root.contains(newRoot) || newRoot.contains(root)) {
2902 throw new Error('Cannot add a root that overlaps with existing root.');
2903 }
2904 }
2905 if (this.windowElement) {
2906 if (this.windowElement !== newRoot.ownerDocument.defaultView) {
2907 throw new Error(`Cannot connect a root:
2908 DOMLocalization already has a root from a different window.`);
2909 }
2910 } else {
2911 this.windowElement = newRoot.ownerDocument.defaultView;
2912 this.mutationObserver = new this.windowElement.MutationObserver((mutations) =>
2913 this.translateMutations(mutations)
2914 );
2915 }
2916 this.roots.add(newRoot);
2917 this.mutationObserver.observe(newRoot, this.observerConfig);
2918 }
2919 disconnectRoot(root) {
2920 this.roots.delete(root);
2921 this.pauseObserving();
2922 if (this.roots.size === 0) {
2923 this.mutationObserver = null;
2924 if (this.windowElement && this.pendingrAF) {
2925 this.windowElement.cancelAnimationFrame(this.pendingrAF);
2926 }
2927 this.windowElement = null;
2928 this.pendingrAF = null;
2929 this.pendingElements.clear();
2930 return true;
2931 }
2932 this.resumeObserving();
2933 return false;
2934 }
2935 translateRoots() {
2936 const roots = Array.from(this.roots);
2937 return Promise.all(roots.map((root) => this.translateFragment(root)));
2938 }
2939 pauseObserving() {
2940 if (!this.mutationObserver) {
2941 return;
2942 }
2943 this.translateMutations(this.mutationObserver.takeRecords());
2944 this.mutationObserver.disconnect();
2945 }
2946 resumeObserving() {
2947 if (!this.mutationObserver) {
2948 return;
2949 }
2950 for (const root of this.roots) {
2951 this.mutationObserver.observe(root, this.observerConfig);
2952 }
2953 }
2954 translateMutations(mutations) {
2955 for (const mutation of mutations) {
2956 switch (mutation.type) {
2957 case 'attributes':
2958 if (mutation.target.hasAttribute('data-l10n-id')) {
2959 this.pendingElements.add(mutation.target);
2960 }
2961 break;
2962 case 'childList':
2963 for (const addedNode of mutation.addedNodes) {
2964 if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
2965 if (addedNode.childElementCount) {
2966 for (const element of this.getTranslatables(addedNode)) {
2967 this.pendingElements.add(element);
2968 }
2969 } else if (addedNode.hasAttribute(L10NID_ATTR_NAME)) {
2970 this.pendingElements.add(addedNode);
2971 }
2972 }
2973 }
2974 break;
2975 }
2976 }
2977 if (this.pendingElements.size > 0) {
2978 if (this.pendingrAF === null) {
2979 this.pendingrAF = this.windowElement.requestAnimationFrame(() => {
2980 this.translateElements(Array.from(this.pendingElements));
2981 this.pendingElements.clear();
2982 this.pendingrAF = null;
2983 });
2984 }
2985 }
2986 }
2987 translateFragment(frag) {
2988 return this.translateElements(this.getTranslatables(frag));
2989 }
2990 async translateElements(elements) {
2991 if (!elements.length) {
2992 return undefined;
2993 }
2994 const keys = elements.map(this.getKeysForElement);
2995 const translations = await this.formatMessages(keys);
2996 return this.applyTranslations(elements, translations);
2997 }
2998 applyTranslations(elements, translations) {
2999 this.pauseObserving();
3000 for (let i = 0; i < elements.length; i++) {
3001 if (translations[i] !== undefined) {
3002 translateElement(elements[i], translations[i]);
3003 }
3004 }
3005 this.resumeObserving();
3006 }
3007 getTranslatables(element) {
3008 const nodes = Array.from(element.querySelectorAll(L10N_ELEMENT_QUERY));
3009 if (typeof element.hasAttribute === 'function' && element.hasAttribute(L10NID_ATTR_NAME)) {
3010 nodes.push(element);
3011 }
3012 return nodes;
3013 }
3014 getKeysForElement(element) {
3015 return {
3016 id: element.getAttribute(L10NID_ATTR_NAME),
3017 args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null),
3018 };
3019 }
3020} // ./node_modules/@fluent/dom/esm/index.js
3021// ./web/l10n.js
3022class L10n {
3023 #dir;
3024 #elements;
3025 #lang;
3026 #l10n;
3027 constructor({ lang, isRTL }, l10n = null) {
3028 this.#lang = L10n.#fixupLangCode(lang);
3029 this.#l10n = l10n;
3030 this.#dir = (isRTL ?? L10n.#isRTL(this.#lang)) ? 'rtl' : 'ltr';
3031 }
3032 _setL10n(l10n) {
3033 this.#l10n = l10n;
3034 }
3035 getLanguage() {
3036 return this.#lang;
3037 }
3038 getDirection() {
3039 return this.#dir;
3040 }
3041 async get(ids, args = null, fallback) {
3042 if (Array.isArray(ids)) {
3043 ids = ids.map((id) => ({
3044 id,
3045 }));
3046 const messages = await this.#l10n.formatMessages(ids);
3047 return messages.map((message) => message.value);
3048 }
3049 const messages = await this.#l10n.formatMessages([
3050 {
3051 id: ids,
3052 args,
3053 },
3054 ]);
3055 return messages[0]?.value || fallback;
3056 }
3057 async translate(element) {
3058 (this.#elements ||= new Set()).add(element);
3059 try {
3060 this.#l10n.connectRoot(element);
3061 await this.#l10n.translateRoots();
3062 } catch {}
3063 }
3064 async translateOnce(element) {
3065 try {
3066 await this.#l10n.translateElements([element]);
3067 } catch (ex) {
3068 console.error('translateOnce:', ex);
3069 }
3070 }
3071 async destroy() {
3072 if (this.#elements) {
3073 for (const element of this.#elements) {
3074 this.#l10n.disconnectRoot(element);
3075 }
3076 this.#elements.clear();
3077 this.#elements = null;
3078 }
3079 this.#l10n.pauseObserving();
3080 }
3081 pause() {
3082 this.#l10n.pauseObserving();
3083 }
3084 resume() {
3085 this.#l10n.resumeObserving();
3086 }
3087 static #fixupLangCode(langCode) {
3088 langCode = langCode?.toLowerCase() || 'en-us';
3089 const PARTIAL_LANG_CODES = {
3090 en: 'en-us',
3091 es: 'es-es',
3092 fy: 'fy-nl',
3093 ga: 'ga-ie',
3094 gu: 'gu-in',
3095 hi: 'hi-in',
3096 hy: 'hy-am',
3097 nb: 'nb-no',
3098 ne: 'ne-np',
3099 nn: 'nn-no',
3100 pa: 'pa-in',
3101 pt: 'pt-pt',
3102 sv: 'sv-se',
3103 zh: 'zh-cn',
3104 };
3105 return PARTIAL_LANG_CODES[langCode] || langCode;
3106 }
3107 static #isRTL(lang) {
3108 const shortCode = lang.split('-', 1)[0];
3109 return ['ar', 'he', 'fa', 'ps', 'ur'].includes(shortCode);
3110 }
3111}
3112const GenericL10n = null; // ./web/genericl10n.js
3113
3114function PLATFORM() {
3115 const { isAndroid, isLinux, isMac, isWindows } = FeatureTest.platform;
3116 if (isLinux) {
3117 return 'linux';
3118 }
3119 if (isWindows) {
3120 return 'windows';
3121 }
3122 if (isMac) {
3123 return 'macos';
3124 }
3125 if (isAndroid) {
3126 return 'android';
3127 }
3128 return 'other';
3129}
3130function createBundle(lang, text) {
3131 const resource = new FluentResource(text);
3132 const bundle = new FluentBundle(lang, {
3133 functions: {
3134 PLATFORM,
3135 },
3136 });
3137 const errors = bundle.addResource(resource);
3138 if (errors.length) {
3139 console.error('L10n errors', errors);
3140 }
3141 return bundle;
3142}
3143class genericl10n_GenericL10n extends L10n {
3144 constructor(lang) {
3145 super({
3146 lang,
3147 });
3148 const generateBundles = !lang
3149 ? genericl10n_GenericL10n.#generateBundlesFallback.bind(
3150 genericl10n_GenericL10n,
3151 this.getLanguage()
3152 )
3153 : genericl10n_GenericL10n.#generateBundles.bind(
3154 genericl10n_GenericL10n,
3155 'en-us',
3156 this.getLanguage()
3157 );
3158 this._setL10n(new DOMLocalization([], generateBundles));
3159 }
3160 static async *#generateBundles(defaultLang, baseLang) {
3161 const { baseURL, paths } = await this.#getPaths();
3162 const langs = [baseLang];
3163 if (defaultLang !== baseLang) {
3164 const shortLang = baseLang.split('-', 1)[0];
3165 if (shortLang !== baseLang) {
3166 langs.push(shortLang);
3167 }
3168 langs.push(defaultLang);
3169 }
3170 const bundles = langs.map((lang) => [lang, this.#createBundle(lang, baseURL, paths)]);
3171 for (const [lang, bundlePromise] of bundles) {
3172 const bundle = await bundlePromise;
3173 if (bundle) {
3174 yield bundle;
3175 } else if (lang === 'en-us') {
3176 yield this.#createBundleFallback(lang);
3177 }
3178 }
3179 }
3180 static async #createBundle(lang, baseURL, paths) {
3181 const path = paths[lang];
3182 if (!path) {
3183 return null;
3184 }
3185 const url = new URL(path, baseURL);
3186 const text = await fetchData(url, 'text');
3187 return createBundle(lang, text);
3188 }
3189 static async #getPaths() {
3190 try {
3191 const { href } = document.querySelector(`link[type="application/l10n"]`);
3192 const paths = await fetchData(href, 'json');
3193 return {
3194 baseURL: href.substring(0, href.lastIndexOf('/') + 1) || './',
3195 paths,
3196 };
3197 } catch {}
3198 return {
3199 baseURL: './',
3200 paths: Object.create(null),
3201 };
3202 }
3203 static async *#generateBundlesFallback(lang) {
3204 yield this.#createBundleFallback(lang);
3205 }
3206 static async #createBundleFallback(lang) {
3207 const text =
3208 'pdfjs-previous-button =\n .title = Previous Page\npdfjs-previous-button-label = Previous\npdfjs-next-button =\n .title = Next Page\npdfjs-next-button-label = Next\npdfjs-page-input =\n .title = Page\npdfjs-of-pages = of { $pagesCount }\npdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })\npdfjs-zoom-out-button =\n .title = Zoom Out\npdfjs-zoom-out-button-label = Zoom Out\npdfjs-zoom-in-button =\n .title = Zoom In\npdfjs-zoom-in-button-label = Zoom In\npdfjs-zoom-select =\n .title = Zoom\npdfjs-presentation-mode-button =\n .title = Switch to Presentation Mode\npdfjs-presentation-mode-button-label = Presentation Mode\npdfjs-open-file-button =\n .title = Open File\npdfjs-open-file-button-label = Open\npdfjs-print-button =\n .title = Print\npdfjs-print-button-label = Print\npdfjs-save-button =\n .title = Save\npdfjs-save-button-label = Save\npdfjs-download-button =\n .title = Download\npdfjs-download-button-label = Download\npdfjs-bookmark-button =\n .title = Current Page (View URL from Current Page)\npdfjs-bookmark-button-label = Current Page\npdfjs-tools-button =\n .title = Tools\npdfjs-tools-button-label = Tools\npdfjs-first-page-button =\n .title = Go to First Page\npdfjs-first-page-button-label = Go to First Page\npdfjs-last-page-button =\n .title = Go to Last Page\npdfjs-last-page-button-label = Go to Last Page\npdfjs-page-rotate-cw-button =\n .title = Rotate Clockwise\npdfjs-page-rotate-cw-button-label = Rotate Clockwise\npdfjs-page-rotate-ccw-button =\n .title = Rotate Counterclockwise\npdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise\npdfjs-cursor-text-select-tool-button =\n .title = Enable Text Selection Tool\npdfjs-cursor-text-select-tool-button-label = Text Selection Tool\npdfjs-cursor-hand-tool-button =\n .title = Enable Hand Tool\npdfjs-cursor-hand-tool-button-label = Hand Tool\npdfjs-scroll-page-button =\n .title = Use Page Scrolling\npdfjs-scroll-page-button-label = Page Scrolling\npdfjs-scroll-vertical-button =\n .title = Use Vertical Scrolling\npdfjs-scroll-vertical-button-label = Vertical Scrolling\npdfjs-scroll-horizontal-button =\n .title = Use Horizontal Scrolling\npdfjs-scroll-horizontal-button-label = Horizontal Scrolling\npdfjs-scroll-wrapped-button =\n .title = Use Wrapped Scrolling\npdfjs-scroll-wrapped-button-label = Wrapped Scrolling\npdfjs-spread-none-button =\n .title = Do not join page spreads\npdfjs-spread-none-button-label = No Spreads\npdfjs-spread-odd-button =\n .title = Join page spreads starting with odd-numbered pages\npdfjs-spread-odd-button-label = Odd Spreads\npdfjs-spread-even-button =\n .title = Join page spreads starting with even-numbered pages\npdfjs-spread-even-button-label = Even Spreads\npdfjs-document-properties-button =\n .title = Document Properties\u2026\npdfjs-document-properties-button-label = Document Properties\u2026\npdfjs-document-properties-file-name = File name:\npdfjs-document-properties-file-size = File size:\npdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)\npdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)\npdfjs-document-properties-title = Title:\npdfjs-document-properties-author = Author:\npdfjs-document-properties-subject = Subject:\npdfjs-document-properties-keywords = Keywords:\npdfjs-document-properties-creation-date = Creation Date:\npdfjs-document-properties-modification-date = Modification Date:\npdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }\npdfjs-document-properties-creator = Creator:\npdfjs-document-properties-producer = PDF Producer:\npdfjs-document-properties-version = PDF Version:\npdfjs-document-properties-page-count = Page Count:\npdfjs-document-properties-page-size = Page Size:\npdfjs-document-properties-page-size-unit-inches = in\npdfjs-document-properties-page-size-unit-millimeters = mm\npdfjs-document-properties-page-size-orientation-portrait = portrait\npdfjs-document-properties-page-size-orientation-landscape = landscape\npdfjs-document-properties-page-size-name-a-three = A3\npdfjs-document-properties-page-size-name-a-four = A4\npdfjs-document-properties-page-size-name-letter = Letter\npdfjs-document-properties-page-size-name-legal = Legal\npdfjs-document-properties-page-size-dimension-string = { $width } \xD7 { $height } { $unit } ({ $orientation })\npdfjs-document-properties-page-size-dimension-name-string = { $width } \xD7 { $height } { $unit } ({ $name }, { $orientation })\npdfjs-document-properties-linearized = Fast Web View:\npdfjs-document-properties-linearized-yes = Yes\npdfjs-document-properties-linearized-no = No\npdfjs-document-properties-close-button = Close\npdfjs-print-progress-message = Preparing document for printing\u2026\npdfjs-print-progress-percent = { $progress }%\npdfjs-print-progress-close-button = Cancel\npdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser.\npdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing.\npdfjs-current-outline-item-button =\n .title = Find Current Outline Item\npdfjs-current-outline-item-button-label = Current Outline Item\npdfjs-findbar-button =\n .title = Find in Document\npdfjs-findbar-button-label = Find\npdfjs-additional-layers = Additional Layers\npdfjs-thumb-page-title =\n .title = Page { $page }\npdfjs-thumb-page-canvas =\n .aria-label = Thumbnail of Page { $page }\npdfjs-find-input =\n .title = Find\n .placeholder = Find in document\u2026\npdfjs-find-previous-button =\n .title = Find the previous occurrence of the phrase\npdfjs-find-previous-button-label = Previous\npdfjs-find-next-button =\n .title = Find the next occurrence of the phrase\npdfjs-find-next-button-label = Next\npdfjs-find-highlight-checkbox = Highlight All\npdfjs-find-match-case-checkbox-label = Match Case\npdfjs-find-match-diacritics-checkbox-label = Match Diacritics\npdfjs-find-entire-word-checkbox-label = Whole Words\npdfjs-find-reached-top = Reached top of document, continued from bottom\npdfjs-find-reached-bottom = Reached end of document, continued from top\npdfjs-find-match-count =\n { $total ->\n [one] { $current } of { $total } match\n *[other] { $current } of { $total } matches\n }\npdfjs-find-match-count-limit =\n { $limit ->\n [one] More than { $limit } match\n *[other] More than { $limit } matches\n }\npdfjs-find-not-found = Phrase not found\npdfjs-page-scale-width = Page Width\npdfjs-page-scale-fit = Page Fit\npdfjs-page-scale-auto = Automatic Zoom\npdfjs-page-scale-actual = Actual Size\npdfjs-page-scale-percent = { $scale }%\npdfjs-page-landmark =\n .aria-label = Page { $page }\npdfjs-loading-error = An error occurred while loading the PDF.\npdfjs-invalid-file-error = Invalid or corrupted PDF file.\npdfjs-missing-file-error = Missing PDF file.\npdfjs-unexpected-response-error = Unexpected server response.\npdfjs-rendering-error = An error occurred while rendering the page.\npdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }\npdfjs-text-annotation-type =\n .alt = [{ $type } Annotation]\npdfjs-password-label = Enter the password to open this PDF file.\npdfjs-password-invalid = Invalid password. Please try again.\npdfjs-password-ok-button = OK\npdfjs-password-cancel-button = Cancel\npdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts.\npdfjs-editor-free-text-button =\n .title = Text\npdfjs-editor-color-picker-free-text-input =\n .title = Change text color\npdfjs-editor-free-text-button-label = Text\npdfjs-editor-ink-button =\n .title = Draw\npdfjs-editor-color-picker-ink-input =\n .title = Change drawing color\npdfjs-editor-ink-button-label = Draw\npdfjs-editor-stamp-button =\n .title = Add or edit images\npdfjs-editor-stamp-button-label = Add or edit images\npdfjs-editor-highlight-button =\n .title = Highlight\npdfjs-editor-highlight-button-label = Highlight\npdfjs-highlight-floating-button1 =\n .title = Highlight\n .aria-label = Highlight\npdfjs-highlight-floating-button-label = Highlight\npdfjs-comment-floating-button =\n .title = Comment\n .aria-label = Comment\npdfjs-comment-floating-button-label = Comment\npdfjs-editor-comment-button =\n .title = Comment\n .aria-label = Comment\npdfjs-editor-comment-button-label = Comment\npdfjs-editor-signature-button =\n .title = Add signature\npdfjs-editor-signature-button-label = Add signature\npdfjs-editor-highlight-editor =\n .aria-label = Highlight editor\npdfjs-editor-ink-editor =\n .aria-label = Drawing editor\npdfjs-editor-signature-editor1 =\n .aria-description = Signature editor: { $description }\npdfjs-editor-stamp-editor =\n .aria-label = Image editor\npdfjs-editor-remove-ink-button =\n .title = Remove drawing\npdfjs-editor-remove-freetext-button =\n .title = Remove text\npdfjs-editor-remove-stamp-button =\n .title = Remove image\npdfjs-editor-remove-highlight-button =\n .title = Remove highlight\npdfjs-editor-remove-signature-button =\n .title = Remove signature\npdfjs-editor-free-text-color-input = Color\npdfjs-editor-free-text-size-input = Size\npdfjs-editor-ink-color-input = Color\npdfjs-editor-ink-thickness-input = Thickness\npdfjs-editor-ink-opacity-input = Opacity\npdfjs-editor-stamp-add-image-button =\n .title = Add image\npdfjs-editor-stamp-add-image-button-label = Add image\npdfjs-editor-free-highlight-thickness-input = Thickness\npdfjs-editor-free-highlight-thickness-title =\n .title = Change thickness when highlighting items other than text\npdfjs-editor-add-signature-container =\n .aria-label = Signature controls and saved signatures\npdfjs-editor-signature-add-signature-button =\n .title = Add new signature\npdfjs-editor-signature-add-signature-button-label = Add new signature\npdfjs-editor-add-saved-signature-button =\n .title = Saved signature: { $description }\npdfjs-free-text2 =\n .aria-label = Text Editor\n .default-content = Start typing\u2026\npdfjs-editor-comments-sidebar-title =\n { $count ->\n [one] Comment\n *[other] Comments\n }\npdfjs-editor-comments-sidebar-close-button =\n .title = Close the sidebar\n .aria-label = Close the sidebar\npdfjs-editor-comments-sidebar-close-button-label = Close the sidebar\npdfjs-editor-comments-sidebar-no-comments1 = See something noteworthy? Highlight it and leave a comment.\npdfjs-editor-comments-sidebar-no-comments-link = Learn more\npdfjs-editor-alt-text-button =\n .aria-label = Alt text\npdfjs-editor-alt-text-button-label = Alt text\npdfjs-editor-alt-text-edit-button =\n .aria-label = Edit alt text\npdfjs-editor-alt-text-dialog-label = Choose an option\npdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can\u2019t see the image or when it doesn\u2019t load.\npdfjs-editor-alt-text-add-description-label = Add a description\npdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions.\npdfjs-editor-alt-text-mark-decorative-label = Mark as decorative\npdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks.\npdfjs-editor-alt-text-cancel-button = Cancel\npdfjs-editor-alt-text-save-button = Save\npdfjs-editor-alt-text-decorative-tooltip = Marked as decorative\npdfjs-editor-alt-text-textarea =\n .placeholder = For example, \u201CA young man sits down at a table to eat a meal\u201D\npdfjs-editor-resizer-top-left =\n .aria-label = Top left corner \u2014 resize\npdfjs-editor-resizer-top-middle =\n .aria-label = Top middle \u2014 resize\npdfjs-editor-resizer-top-right =\n .aria-label = Top right corner \u2014 resize\npdfjs-editor-resizer-middle-right =\n .aria-label = Middle right \u2014 resize\npdfjs-editor-resizer-bottom-right =\n .aria-label = Bottom right corner \u2014 resize\npdfjs-editor-resizer-bottom-middle =\n .aria-label = Bottom middle \u2014 resize\npdfjs-editor-resizer-bottom-left =\n .aria-label = Bottom left corner \u2014 resize\npdfjs-editor-resizer-middle-left =\n .aria-label = Middle left \u2014 resize\npdfjs-editor-highlight-colorpicker-label = Highlight color\npdfjs-editor-colorpicker-button =\n .title = Change color\npdfjs-editor-colorpicker-dropdown =\n .aria-label = Color choices\npdfjs-editor-colorpicker-yellow =\n .title = Yellow\npdfjs-editor-colorpicker-green =\n .title = Green\npdfjs-editor-colorpicker-blue =\n .title = Blue\npdfjs-editor-colorpicker-pink =\n .title = Pink\npdfjs-editor-colorpicker-red =\n .title = Red\npdfjs-editor-highlight-show-all-button-label = Show all\npdfjs-editor-highlight-show-all-button =\n .title = Show all\npdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description)\npdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description)\npdfjs-editor-new-alt-text-textarea =\n .placeholder = Write your description here\u2026\npdfjs-editor-new-alt-text-description = Short description for people who can\u2019t see the image or when the image doesn\u2019t load.\npdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate.\npdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more\npdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically\npdfjs-editor-new-alt-text-not-now-button = Not now\npdfjs-editor-new-alt-text-error-title = Couldn\u2019t create alt text automatically\npdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later.\npdfjs-editor-new-alt-text-error-close-button = Close\npdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)\n .aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)\npdfjs-editor-new-alt-text-added-button =\n .aria-label = Alt text added\npdfjs-editor-new-alt-text-added-button-label = Alt text added\npdfjs-editor-new-alt-text-missing-button =\n .aria-label = Missing alt text\npdfjs-editor-new-alt-text-missing-button-label = Missing alt text\npdfjs-editor-new-alt-text-to-review-button =\n .aria-label = Review alt text\npdfjs-editor-new-alt-text-to-review-button-label = Review alt text\npdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText }\npdfjs-image-alt-text-settings-button =\n .title = Image alt text settings\npdfjs-image-alt-text-settings-button-label = Image alt text settings\npdfjs-editor-alt-text-settings-dialog-label = Image alt text settings\npdfjs-editor-alt-text-settings-automatic-title = Automatic alt text\npdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically\npdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can\u2019t see the image or when the image doesn\u2019t load.\npdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB)\npdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text.\npdfjs-editor-alt-text-settings-delete-model-button = Delete\npdfjs-editor-alt-text-settings-download-model-button = Download\npdfjs-editor-alt-text-settings-downloading-model-button = Downloading\u2026\npdfjs-editor-alt-text-settings-editor-title = Alt text editor\npdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image\npdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text.\npdfjs-editor-alt-text-settings-close-button = Close\npdfjs-editor-highlight-added-alert = Highlight added\npdfjs-editor-freetext-added-alert = Text added\npdfjs-editor-ink-added-alert = Drawing added\npdfjs-editor-stamp-added-alert = Image added\npdfjs-editor-signature-added-alert = Signature added\npdfjs-editor-undo-bar-message-highlight = Highlight removed\npdfjs-editor-undo-bar-message-freetext = Text removed\npdfjs-editor-undo-bar-message-ink = Drawing removed\npdfjs-editor-undo-bar-message-stamp = Image removed\npdfjs-editor-undo-bar-message-signature = Signature removed\npdfjs-editor-undo-bar-message-comment = Comment removed\npdfjs-editor-undo-bar-message-multiple =\n { $count ->\n [one] { $count } annotation removed\n *[other] { $count } annotations removed\n }\npdfjs-editor-undo-bar-undo-button =\n .title = Undo\npdfjs-editor-undo-bar-undo-button-label = Undo\npdfjs-editor-undo-bar-close-button =\n .title = Close\npdfjs-editor-undo-bar-close-button-label = Close\npdfjs-editor-add-signature-dialog-label = This modal allows the user to create a signature to add to a PDF document. The user can edit the name (which also serves as the alt text), and optionally save the signature for repeated use.\npdfjs-editor-add-signature-dialog-title = Add a signature\npdfjs-editor-add-signature-type-button = Type\n .title = Type\npdfjs-editor-add-signature-draw-button = Draw\n .title = Draw\npdfjs-editor-add-signature-image-button = Image\n .title = Image\npdfjs-editor-add-signature-type-input =\n .aria-label = Type your signature\n .placeholder = Type your signature\npdfjs-editor-add-signature-draw-placeholder = Draw your signature\npdfjs-editor-add-signature-draw-thickness-range-label = Thickness\npdfjs-editor-add-signature-draw-thickness-range =\n .title = Drawing thickness: { $thickness }\npdfjs-editor-add-signature-image-placeholder = Drag a file here to upload\npdfjs-editor-add-signature-image-browse-link =\n { PLATFORM() ->\n [macos] Or choose image files\n *[other] Or browse image files\n }\npdfjs-editor-add-signature-description-label = Description (alt text)\npdfjs-editor-add-signature-description-input =\n .title = Description (alt text)\npdfjs-editor-add-signature-description-default-when-drawing = Signature\npdfjs-editor-add-signature-clear-button-label = Clear signature\npdfjs-editor-add-signature-clear-button =\n .title = Clear signature\npdfjs-editor-add-signature-save-checkbox = Save signature\npdfjs-editor-add-signature-save-warning-message = You\u2019ve reached the limit of 5 saved signatures. Remove one to save more.\npdfjs-editor-add-signature-image-upload-error-title = Couldn\u2019t upload image\npdfjs-editor-add-signature-image-upload-error-description = Check your network connection or try another image.\npdfjs-editor-add-signature-image-no-data-error-title = Can\u2019t convert this image into a signature\npdfjs-editor-add-signature-image-no-data-error-description = Please try uploading a different image.\npdfjs-editor-add-signature-error-close-button = Close\npdfjs-editor-add-signature-cancel-button = Cancel\npdfjs-editor-add-signature-add-button = Add\npdfjs-editor-delete-signature-button1 =\n .title = Remove saved signature\npdfjs-editor-delete-signature-button-label1 = Remove saved signature\npdfjs-editor-add-signature-edit-button-label = Edit description\npdfjs-editor-edit-signature-dialog-title = Edit description\npdfjs-editor-edit-signature-update-button = Update\npdfjs-show-comment-button =\n .title = Show comment\npdfjs-editor-edit-comment-popup-button-label = Edit comment\npdfjs-editor-edit-comment-popup-button =\n .title = Edit comment\npdfjs-editor-delete-comment-popup-button-label = Remove comment\npdfjs-editor-delete-comment-popup-button =\n .title = Remove comment\npdfjs-editor-edit-comment-dialog-title-when-editing = Edit comment\npdfjs-editor-edit-comment-dialog-save-button-when-editing = Update\npdfjs-editor-edit-comment-dialog-title-when-adding = Add comment\npdfjs-editor-edit-comment-dialog-save-button-when-adding = Add\npdfjs-editor-edit-comment-dialog-text-input =\n .placeholder = Start typing\u2026\npdfjs-editor-edit-comment-dialog-cancel-button = Cancel\npdfjs-editor-add-comment-button =\n .title = Add comment\npdfjs-toggle-views-manager-button =\n .title = Toggle Sidebar\npdfjs-toggle-views-manager-notification-button =\n .title = Toggle Sidebar (document contains thumbnails/outline/attachments/layers)\npdfjs-toggle-views-manager-button-label = Toggle Sidebar\npdfjs-views-manager-sidebar =\n .aria-label = Sidebar\npdfjs-views-manager-view-selector-button =\n .title = Views\npdfjs-views-manager-view-selector-button-label = Views\npdfjs-views-manager-pages-title = Pages\npdfjs-views-manager-outlines-title = Document outline\npdfjs-views-manager-attachments-title = Attachments\npdfjs-views-manager-layers-title = Layers\npdfjs-views-manager-pages-option-label = Pages\npdfjs-views-manager-outlines-option-label = Document outline\npdfjs-views-manager-attachments-option-label = Attachments\npdfjs-views-manager-layers-option-label = Layers\npdfjs-views-manager-add-file-button =\n .title = Add file\npdfjs-views-manager-add-file-button-label = Add file\npdfjs-views-manager-pages-status-action-label =\n { $count ->\n [one] { $count } selected\n *[other] { $count } selected\n }\npdfjs-views-manager-pages-status-none-action-label = Select pages\npdfjs-views-manager-pages-status-action-button-label = Manage\npdfjs-views-manager-pages-status-copy-button-label = Copy\npdfjs-views-manager-pages-status-cut-button-label = Cut\npdfjs-views-manager-pages-status-delete-button-label = Delete\npdfjs-views-manager-pages-status-save-as-button-label = Save as\u2026\npdfjs-views-manager-status-undo-cut-label =\n { $count ->\n [one] 1 page cut\n *[other] { $count } pages cut\n }\npdfjs-views-manager-pages-status-undo-copy-label =\n { $count ->\n [one] 1 page copied\n *[other] { $count } pages copied\n }\npdfjs-views-manager-pages-status-undo-delete-label =\n { $count ->\n [one] 1 page deleted\n *[other] { $count } pages deleted\n }\npdfjs-views-manager-pages-status-waiting-ready-label = Getting your file ready\u2026\npdfjs-views-manager-pages-status-waiting-uploading-label = Uploading file\u2026\npdfjs-views-manager-status-warning-cut-label = Couldn\u2019t cut. Refresh page and try again.\npdfjs-views-manager-status-warning-copy-label = Couldn\u2019t copy. Refresh page and try again.\npdfjs-views-manager-status-warning-delete-label = Couldn\u2019t delete. Refresh page and try again.\npdfjs-views-manager-status-warning-save-label = Couldn\u2019t save. Refresh page and try again.\npdfjs-views-manager-status-undo-button-label = Undo\npdfjs-views-manager-status-close-button =\n .title = Close\npdfjs-views-manager-status-close-button-label = Close';
3209 return createBundle(lang, text);
3210 }
3211} // ./web/generic_scripting.js
3212
3213async function docProperties(pdfDocument) {
3214 const url = '',
3215 baseUrl = '';
3216 const { info, metadata, contentDispositionFilename, contentLength } =
3217 await pdfDocument.getMetadata();
3218 return {
3219 ...info,
3220 baseURL: baseUrl,
3221 filesize: contentLength || (await pdfDocument.getDownloadInfo()).length,
3222 filename: contentDispositionFilename || getPdfFilenameFromUrl(url),
3223 metadata: metadata?.getRaw(),
3224 authors: metadata?.get('dc:creator'),
3225 numPages: pdfDocument.numPages,
3226 URL: url,
3227 };
3228}
3229class GenericScripting {
3230 constructor(sandboxBundleSrc) {
3231 this._ready = new Promise((resolve, reject) => {
3232 const sandbox = import(
3233 /*webpackIgnore: true*/
3234 /*@vite-ignore*/
3235 sandboxBundleSrc
3236 );
3237 sandbox
3238 .then((pdfjsSandbox) => {
3239 resolve(pdfjsSandbox.QuickJSSandbox());
3240 })
3241 .catch(reject);
3242 });
3243 }
3244 async createSandbox(data) {
3245 const sandbox = await this._ready;
3246 sandbox.create(data);
3247 }
3248 async dispatchEventInSandbox(event) {
3249 const sandbox = await this._ready;
3250 setTimeout(() => sandbox.dispatchEvent(event), 0);
3251 }
3252 async destroySandbox() {
3253 const sandbox = await this._ready;
3254 sandbox.nukeSandbox();
3255 }
3256} // ./web/generic_signature_storage.js
3257
3258const KEY_STORAGE = 'pdfjs.signature';
3259class SignatureStorage {
3260 #eventBus;
3261 #signatures = null;
3262 #signal = null;
3263 constructor(eventBus, signal) {
3264 this.#eventBus = eventBus;
3265 this.#signal = signal;
3266 }
3267 #save() {
3268 localStorage.setItem(KEY_STORAGE, JSON.stringify(Object.fromEntries(this.#signatures)));
3269 }
3270 async getAll() {
3271 if (this.#signal) {
3272 window.addEventListener(
3273 'storage',
3274 ({ key }) => {
3275 if (key === KEY_STORAGE) {
3276 this.#signatures = null;
3277 this.#eventBus?.dispatch('storedsignatureschanged', {
3278 source: this,
3279 });
3280 }
3281 },
3282 {
3283 signal: this.#signal,
3284 }
3285 );
3286 this.#signal = null;
3287 }
3288 if (!this.#signatures) {
3289 this.#signatures = new Map();
3290 const data = localStorage.getItem(KEY_STORAGE);
3291 if (data) {
3292 for (const [key, value] of Object.entries(JSON.parse(data))) {
3293 this.#signatures.set(key, value);
3294 }
3295 }
3296 }
3297 return this.#signatures;
3298 }
3299 async isFull() {
3300 return (await this.size()) === 5;
3301 }
3302 async size() {
3303 return (await this.getAll()).size;
3304 }
3305 async create(data) {
3306 if (await this.isFull()) {
3307 return null;
3308 }
3309 const uuid = getUuid();
3310 this.#signatures.set(uuid, data);
3311 this.#save();
3312 return uuid;
3313 }
3314 async delete(uuid) {
3315 const signatures = await this.getAll();
3316 if (!signatures.has(uuid)) {
3317 return false;
3318 }
3319 signatures.delete(uuid);
3320 this.#save();
3321 return true;
3322 }
3323} // ./web/genericcom.js
3324
3325function initCom(app) {}
3326class Preferences extends BasePreferences {
3327 async _writeToStorage(prefObj) {
3328 localStorage.setItem('pdfjs.preferences', JSON.stringify(prefObj));
3329 }
3330 async _readFromStorage(prefObj) {
3331 return {
3332 prefs: JSON.parse(localStorage.getItem('pdfjs.preferences')),
3333 };
3334 }
3335}
3336class ExternalServices extends BaseExternalServices {
3337 async createL10n() {
3338 return new genericl10n_GenericL10n(AppOptions.get('localeProperties')?.lang);
3339 }
3340 createScripting() {
3341 return new GenericScripting(AppOptions.get('sandboxBundleSrc'));
3342 }
3343 createSignatureStorage(eventBus, signal) {
3344 return new SignatureStorage(eventBus, signal);
3345 }
3346}
3347class MLManager {
3348 async isEnabledFor(_name) {
3349 return false;
3350 }
3351 async deleteModel(_service) {
3352 return null;
3353 }
3354 isReady(_name) {
3355 return false;
3356 }
3357 guess(_data) {}
3358 toggleService(_name, _enabled) {}
3359} // ./web/new_alt_text_manager.js
3360
3361class NewAltTextManager {
3362 #boundCancel = this.#cancel.bind(this);
3363 #createAutomaticallyButton;
3364 #currentEditor = null;
3365 #cancelButton;
3366 #descriptionContainer;
3367 #dialog;
3368 #disclaimer;
3369 #downloadModel;
3370 #downloadModelDescription;
3371 #eventBus;
3372 #firstTime = false;
3373 #guessedAltText;
3374 #hasAI = null;
3375 #isEditing = null;
3376 #imagePreview;
3377 #imageData;
3378 #isAILoading = false;
3379 #wasAILoading = false;
3380 #learnMore;
3381 #notNowButton;
3382 #overlayManager;
3383 #textarea;
3384 #title;
3385 #uiManager;
3386 #previousAltText = null;
3387 constructor(
3388 {
3389 descriptionContainer,
3390 dialog,
3391 imagePreview,
3392 cancelButton,
3393 disclaimer,
3394 notNowButton,
3395 saveButton,
3396 textarea,
3397 learnMore,
3398 errorCloseButton,
3399 createAutomaticallyButton,
3400 downloadModel,
3401 downloadModelDescription,
3402 title,
3403 },
3404 overlayManager,
3405 eventBus
3406 ) {
3407 this.#cancelButton = cancelButton;
3408 this.#createAutomaticallyButton = createAutomaticallyButton;
3409 this.#descriptionContainer = descriptionContainer;
3410 this.#dialog = dialog;
3411 this.#disclaimer = disclaimer;
3412 this.#notNowButton = notNowButton;
3413 this.#imagePreview = imagePreview;
3414 this.#textarea = textarea;
3415 this.#learnMore = learnMore;
3416 this.#title = title;
3417 this.#downloadModel = downloadModel;
3418 this.#downloadModelDescription = downloadModelDescription;
3419 this.#overlayManager = overlayManager;
3420 this.#eventBus = eventBus;
3421 dialog.addEventListener('close', this.#close.bind(this));
3422 dialog.addEventListener('contextmenu', (event) => {
3423 if (event.target !== this.#textarea) {
3424 event.preventDefault();
3425 }
3426 });
3427 cancelButton.addEventListener('click', this.#boundCancel);
3428 notNowButton.addEventListener('click', this.#boundCancel);
3429 saveButton.addEventListener('click', this.#save.bind(this));
3430 errorCloseButton.addEventListener('click', () => {
3431 this.#toggleError(false);
3432 });
3433 createAutomaticallyButton.addEventListener('click', async () => {
3434 const checked = createAutomaticallyButton.getAttribute('aria-pressed') !== 'true';
3435 this.#currentEditor._reportTelemetry({
3436 action: 'pdfjs.image.alt_text.ai_generation_check',
3437 data: {
3438 status: checked,
3439 },
3440 });
3441 if (this.#uiManager) {
3442 this.#uiManager.setPreference('enableGuessAltText', checked);
3443 await this.#uiManager.mlManager.toggleService('altText', checked);
3444 }
3445 this.#toggleGuessAltText(checked, false);
3446 });
3447 textarea.addEventListener('focus', () => {
3448 this.#wasAILoading = this.#isAILoading;
3449 this.#toggleLoading(false);
3450 this.#toggleTitleAndDisclaimer();
3451 });
3452 textarea.addEventListener('blur', () => {
3453 if (!textarea.value) {
3454 this.#toggleLoading(this.#wasAILoading);
3455 }
3456 this.#toggleTitleAndDisclaimer();
3457 });
3458 textarea.addEventListener('input', () => {
3459 this.#toggleTitleAndDisclaimer();
3460 });
3461 textarea.addEventListener('keydown', (e) => {
3462 if ((e.ctrlKey || e.metaKey) && e.key === 'Enter' && !saveButton.disabled) {
3463 this.#save();
3464 }
3465 });
3466 eventBus._on('enableguessalttext', ({ value }) => {
3467 this.#toggleGuessAltText(value, false);
3468 });
3469 this.#overlayManager.register(dialog);
3470 this.#learnMore.addEventListener('click', () => {
3471 this.#currentEditor._reportTelemetry({
3472 action: 'pdfjs.image.alt_text.info',
3473 data: {
3474 topic: 'alt_text',
3475 },
3476 });
3477 });
3478 }
3479 #toggleLoading(value) {
3480 if (!this.#uiManager || this.#isAILoading === value) {
3481 return;
3482 }
3483 this.#isAILoading = value;
3484 this.#descriptionContainer.classList.toggle('loading', value);
3485 }
3486 #toggleError(value) {
3487 if (!this.#uiManager) {
3488 return;
3489 }
3490 this.#dialog.classList.toggle('error', value);
3491 }
3492 async #toggleGuessAltText(value, isInitial = false) {
3493 if (!this.#uiManager) {
3494 return;
3495 }
3496 this.#dialog.classList.toggle('aiDisabled', !value);
3497 this.#createAutomaticallyButton.setAttribute('aria-pressed', value);
3498 if (value) {
3499 const { altTextLearnMoreUrl } = this.#uiManager.mlManager;
3500 if (altTextLearnMoreUrl) {
3501 this.#learnMore.href = altTextLearnMoreUrl;
3502 }
3503 this.#mlGuessAltText(isInitial);
3504 } else {
3505 this.#toggleLoading(false);
3506 this.#isAILoading = false;
3507 this.#toggleTitleAndDisclaimer();
3508 }
3509 }
3510 #toggleNotNow() {
3511 this.#notNowButton.classList.toggle('hidden', !this.#firstTime);
3512 this.#cancelButton.classList.toggle('hidden', this.#firstTime);
3513 }
3514 #toggleAI(value) {
3515 if (!this.#uiManager || this.#hasAI === value) {
3516 return;
3517 }
3518 this.#hasAI = value;
3519 this.#dialog.classList.toggle('noAi', !value);
3520 this.#toggleTitleAndDisclaimer();
3521 }
3522 #toggleTitleAndDisclaimer() {
3523 const visible =
3524 this.#isAILoading || (this.#guessedAltText && this.#guessedAltText === this.#textarea.value);
3525 this.#disclaimer.hidden = !visible;
3526 const isEditing = this.#isAILoading || !!this.#textarea.value;
3527 if (this.#isEditing === isEditing) {
3528 return;
3529 }
3530 this.#isEditing = isEditing;
3531 this.#title.setAttribute(
3532 'data-l10n-id',
3533 isEditing
3534 ? 'pdfjs-editor-new-alt-text-dialog-edit-label'
3535 : 'pdfjs-editor-new-alt-text-dialog-add-label'
3536 );
3537 }
3538 async #mlGuessAltText(isInitial) {
3539 if (this.#isAILoading) {
3540 return;
3541 }
3542 if (this.#textarea.value) {
3543 return;
3544 }
3545 if (isInitial && this.#previousAltText !== null) {
3546 return;
3547 }
3548 this.#guessedAltText = this.#currentEditor.guessedAltText;
3549 if (this.#previousAltText === null && this.#guessedAltText) {
3550 this.#addAltText(this.#guessedAltText);
3551 return;
3552 }
3553 this.#toggleLoading(true);
3554 this.#toggleTitleAndDisclaimer();
3555 let hasError = false;
3556 try {
3557 const altText = await this.#currentEditor.mlGuessAltText(this.#imageData, false);
3558 if (altText) {
3559 this.#guessedAltText = altText;
3560 this.#wasAILoading = this.#isAILoading;
3561 if (this.#isAILoading) {
3562 this.#addAltText(altText);
3563 }
3564 }
3565 } catch (e) {
3566 console.error(e);
3567 hasError = true;
3568 }
3569 this.#toggleLoading(false);
3570 this.#toggleTitleAndDisclaimer();
3571 if (hasError && this.#uiManager) {
3572 this.#toggleError(true);
3573 }
3574 }
3575 #addAltText(altText) {
3576 if (!this.#uiManager || this.#textarea.value) {
3577 return;
3578 }
3579 this.#textarea.value = altText;
3580 this.#toggleTitleAndDisclaimer();
3581 }
3582 #setProgress() {
3583 this.#downloadModel.classList.toggle('hidden', false);
3584 const callback = async ({ detail: { finished, total, totalLoaded } }) => {
3585 const ONE_MEGA_BYTES = 1e6;
3586 totalLoaded = Math.min(0.99 * total, totalLoaded);
3587 const totalSize = (this.#downloadModelDescription.ariaValueMax = Math.round(
3588 total / ONE_MEGA_BYTES
3589 ));
3590 const downloadedSize = (this.#downloadModelDescription.ariaValueNow = Math.round(
3591 totalLoaded / ONE_MEGA_BYTES
3592 ));
3593 this.#downloadModelDescription.setAttribute(
3594 'data-l10n-args',
3595 JSON.stringify({
3596 totalSize,
3597 downloadedSize,
3598 })
3599 );
3600 if (!finished) {
3601 return;
3602 }
3603 this.#eventBus._off('loadaiengineprogress', callback);
3604 this.#downloadModel.classList.toggle('hidden', true);
3605 this.#toggleAI(true);
3606 if (!this.#uiManager) {
3607 return;
3608 }
3609 const { mlManager } = this.#uiManager;
3610 mlManager.toggleService('altText', true);
3611 this.#toggleGuessAltText(await mlManager.isEnabledFor('altText'), true);
3612 };
3613 this.#eventBus._on('loadaiengineprogress', callback);
3614 }
3615 async editAltText(uiManager, editor, firstTime) {
3616 if (this.#currentEditor || !editor) {
3617 return;
3618 }
3619 if (firstTime && editor.hasAltTextData()) {
3620 editor.altTextFinish();
3621 return;
3622 }
3623 this.#firstTime = firstTime;
3624 let { mlManager } = uiManager;
3625 let hasAI = !!mlManager;
3626 this.#toggleTitleAndDisclaimer();
3627 if (mlManager && !mlManager.isReady('altText')) {
3628 hasAI = false;
3629 if (mlManager.hasProgress) {
3630 this.#setProgress();
3631 } else {
3632 mlManager = null;
3633 }
3634 } else {
3635 this.#downloadModel.classList.toggle('hidden', true);
3636 }
3637 const isAltTextEnabledPromise = mlManager?.isEnabledFor('altText');
3638 this.#currentEditor = editor;
3639 this.#uiManager = uiManager;
3640 this.#uiManager.removeEditListeners();
3641 ({ altText: this.#previousAltText } = editor.altTextData);
3642 this.#textarea.value = this.#previousAltText ?? '';
3643 const AI_MAX_IMAGE_DIMENSION = 224;
3644 const MAX_PREVIEW_DIMENSION = 180;
3645 let canvas, width, height;
3646 if (mlManager) {
3647 ({
3648 canvas,
3649 width,
3650 height,
3651 imageData: this.#imageData,
3652 } = editor.copyCanvas(AI_MAX_IMAGE_DIMENSION, MAX_PREVIEW_DIMENSION, true));
3653 if (hasAI) {
3654 this.#toggleGuessAltText(await isAltTextEnabledPromise, true);
3655 }
3656 } else {
3657 ({ canvas, width, height } = editor.copyCanvas(
3658 AI_MAX_IMAGE_DIMENSION,
3659 MAX_PREVIEW_DIMENSION,
3660 false
3661 ));
3662 }
3663 canvas.setAttribute('role', 'presentation');
3664 const { style } = canvas;
3665 style.width = `${width}px`;
3666 style.height = `${height}px`;
3667 this.#imagePreview.append(canvas);
3668 this.#toggleNotNow();
3669 this.#toggleAI(hasAI);
3670 this.#toggleError(false);
3671 try {
3672 await this.#overlayManager.open(this.#dialog);
3673 } catch (ex) {
3674 this.#close();
3675 throw ex;
3676 }
3677 }
3678 #cancel() {
3679 this.#currentEditor.altTextData = {
3680 cancel: true,
3681 };
3682 const altText = this.#textarea.value.trim();
3683 this.#currentEditor._reportTelemetry({
3684 action: 'pdfjs.image.alt_text.dismiss',
3685 data: {
3686 alt_text_type: altText ? 'present' : 'empty',
3687 flow: this.#firstTime ? 'image_add' : 'alt_text_edit',
3688 },
3689 });
3690 this.#currentEditor._reportTelemetry({
3691 action: 'pdfjs.image.image_added',
3692 data: {
3693 alt_text_modal: true,
3694 alt_text_type: 'skipped',
3695 },
3696 });
3697 this.#finish();
3698 }
3699 #finish() {
3700 this.#overlayManager.closeIfActive(this.#dialog);
3701 }
3702 #close() {
3703 const canvas = this.#imagePreview.firstElementChild;
3704 canvas.remove();
3705 canvas.width = canvas.height = 0;
3706 this.#imageData = null;
3707 this.#toggleLoading(false);
3708 this.#uiManager?.addEditListeners();
3709 this.#currentEditor.altTextFinish();
3710 this.#uiManager?.setSelected(this.#currentEditor);
3711 this.#currentEditor = null;
3712 this.#uiManager = null;
3713 }
3714 #extractWords(text) {
3715 return new Set(
3716 text
3717 .toLowerCase()
3718 .split(/[^\p{L}\p{N}]+/gu)
3719 .filter((x) => !!x)
3720 );
3721 }
3722 #save() {
3723 const altText = this.#textarea.value.trim();
3724 this.#currentEditor.altTextData = {
3725 altText,
3726 decorative: false,
3727 };
3728 this.#currentEditor.altTextData.guessedAltText = this.#guessedAltText;
3729 if (this.#guessedAltText && this.#guessedAltText !== altText) {
3730 const guessedWords = this.#extractWords(this.#guessedAltText);
3731 const words = this.#extractWords(altText);
3732 this.#currentEditor._reportTelemetry({
3733 action: 'pdfjs.image.alt_text.user_edit',
3734 data: {
3735 total_words: guessedWords.size,
3736 words_removed: guessedWords.difference(words).size,
3737 words_added: words.difference(guessedWords).size,
3738 },
3739 });
3740 }
3741 this.#currentEditor._reportTelemetry({
3742 action: 'pdfjs.image.image_added',
3743 data: {
3744 alt_text_modal: true,
3745 alt_text_type: altText ? 'present' : 'empty',
3746 },
3747 });
3748 this.#currentEditor._reportTelemetry({
3749 action: 'pdfjs.image.alt_text.save',
3750 data: {
3751 alt_text_type: altText ? 'present' : 'empty',
3752 flow: this.#firstTime ? 'image_add' : 'alt_text_edit',
3753 },
3754 });
3755 this.#finish();
3756 }
3757 destroy() {
3758 this.#uiManager = null;
3759 this.#finish();
3760 }
3761}
3762class ImageAltTextSettings {
3763 #aiModelSettings;
3764 #createModelButton;
3765 #downloadModelButton;
3766 #dialog;
3767 #eventBus;
3768 #mlManager;
3769 #overlayManager;
3770 #showAltTextDialogButton;
3771 constructor(
3772 {
3773 dialog,
3774 createModelButton,
3775 aiModelSettings,
3776 learnMore,
3777 closeButton,
3778 deleteModelButton,
3779 downloadModelButton,
3780 showAltTextDialogButton,
3781 },
3782 overlayManager,
3783 eventBus,
3784 mlManager
3785 ) {
3786 this.#dialog = dialog;
3787 this.#aiModelSettings = aiModelSettings;
3788 this.#createModelButton = createModelButton;
3789 this.#downloadModelButton = downloadModelButton;
3790 this.#showAltTextDialogButton = showAltTextDialogButton;
3791 this.#overlayManager = overlayManager;
3792 this.#eventBus = eventBus;
3793 this.#mlManager = mlManager;
3794 const { altTextLearnMoreUrl } = mlManager;
3795 if (altTextLearnMoreUrl) {
3796 learnMore.href = altTextLearnMoreUrl;
3797 }
3798 dialog.addEventListener('contextmenu', noContextMenu);
3799 createModelButton.addEventListener('click', async (e) => {
3800 const checked = this.#togglePref('enableGuessAltText', e);
3801 await mlManager.toggleService('altText', checked);
3802 this.#reportTelemetry({
3803 type: 'stamp',
3804 action: 'pdfjs.image.alt_text.settings_ai_generation_check',
3805 data: {
3806 status: checked,
3807 },
3808 });
3809 });
3810 showAltTextDialogButton.addEventListener('click', (e) => {
3811 const checked = this.#togglePref('enableNewAltTextWhenAddingImage', e);
3812 this.#reportTelemetry({
3813 type: 'stamp',
3814 action: 'pdfjs.image.alt_text.settings_edit_alt_text_check',
3815 data: {
3816 status: checked,
3817 },
3818 });
3819 });
3820 deleteModelButton.addEventListener('click', this.#delete.bind(this, true));
3821 downloadModelButton.addEventListener('click', this.#download.bind(this, true));
3822 closeButton.addEventListener('click', this.#finish.bind(this));
3823 learnMore.addEventListener('click', () => {
3824 this.#reportTelemetry({
3825 type: 'stamp',
3826 action: 'pdfjs.image.alt_text.info',
3827 data: {
3828 topic: 'ai_generation',
3829 },
3830 });
3831 });
3832 eventBus._on('enablealttextmodeldownload', ({ value }) => {
3833 if (value) {
3834 this.#download(false);
3835 } else {
3836 this.#delete(false);
3837 }
3838 });
3839 this.#overlayManager.register(dialog);
3840 }
3841 #reportTelemetry(data) {
3842 this.#eventBus.dispatch('reporttelemetry', {
3843 source: this,
3844 details: {
3845 type: 'editing',
3846 data,
3847 },
3848 });
3849 }
3850 async #download(isFromUI = false) {
3851 if (isFromUI) {
3852 this.#downloadModelButton.disabled = true;
3853 const span = this.#downloadModelButton.firstElementChild;
3854 span.setAttribute('data-l10n-id', 'pdfjs-editor-alt-text-settings-downloading-model-button');
3855 await this.#mlManager.downloadModel('altText');
3856 span.setAttribute('data-l10n-id', 'pdfjs-editor-alt-text-settings-download-model-button');
3857 this.#createModelButton.disabled = false;
3858 this.#setPref('enableGuessAltText', true);
3859 this.#mlManager.toggleService('altText', true);
3860 this.#setPref('enableAltTextModelDownload', true);
3861 this.#downloadModelButton.disabled = false;
3862 }
3863 this.#aiModelSettings.classList.toggle('download', false);
3864 this.#createModelButton.setAttribute('aria-pressed', true);
3865 }
3866 async #delete(isFromUI = false) {
3867 if (isFromUI) {
3868 await this.#mlManager.deleteModel('altText');
3869 this.#setPref('enableGuessAltText', false);
3870 this.#setPref('enableAltTextModelDownload', false);
3871 }
3872 this.#aiModelSettings.classList.toggle('download', true);
3873 this.#createModelButton.disabled = true;
3874 this.#createModelButton.setAttribute('aria-pressed', false);
3875 }
3876 async open({ enableGuessAltText, enableNewAltTextWhenAddingImage }) {
3877 const { enableAltTextModelDownload } = this.#mlManager;
3878 this.#createModelButton.disabled = !enableAltTextModelDownload;
3879 this.#createModelButton.setAttribute(
3880 'aria-pressed',
3881 enableAltTextModelDownload && enableGuessAltText
3882 );
3883 this.#showAltTextDialogButton.setAttribute('aria-pressed', enableNewAltTextWhenAddingImage);
3884 this.#aiModelSettings.classList.toggle('download', !enableAltTextModelDownload);
3885 await this.#overlayManager.open(this.#dialog);
3886 this.#reportTelemetry({
3887 type: 'stamp',
3888 action: 'pdfjs.image.alt_text.settings_displayed',
3889 });
3890 }
3891 #togglePref(name, { target }) {
3892 const checked = target.getAttribute('aria-pressed') !== 'true';
3893 this.#setPref(name, checked);
3894 target.setAttribute('aria-pressed', checked);
3895 return checked;
3896 }
3897 #setPref(name, value) {
3898 this.#eventBus.dispatch('setpreference', {
3899 source: this,
3900 name,
3901 value,
3902 });
3903 }
3904 #finish() {
3905 this.#overlayManager.closeIfActive(this.#dialog);
3906 }
3907} // ./web/alt_text_manager.js
3908
3909class AltTextManager {
3910 #clickAC = null;
3911 #currentEditor = null;
3912 #cancelButton;
3913 #dialog;
3914 #eventBus;
3915 #hasUsedPointer = false;
3916 #optionDescription;
3917 #optionDecorative;
3918 #overlayManager;
3919 #saveButton;
3920 #textarea;
3921 #uiManager;
3922 #previousAltText = null;
3923 #resizeAC = null;
3924 #svgElement = null;
3925 #rectElement = null;
3926 #container;
3927 #telemetryData = null;
3928 constructor(
3929 { dialog, optionDescription, optionDecorative, textarea, cancelButton, saveButton },
3930 container,
3931 overlayManager,
3932 eventBus
3933 ) {
3934 this.#dialog = dialog;
3935 this.#optionDescription = optionDescription;
3936 this.#optionDecorative = optionDecorative;
3937 this.#textarea = textarea;
3938 this.#cancelButton = cancelButton;
3939 this.#saveButton = saveButton;
3940 this.#overlayManager = overlayManager;
3941 this.#eventBus = eventBus;
3942 this.#container = container;
3943 const onUpdateUIState = this.#updateUIState.bind(this);
3944 dialog.addEventListener('close', this.#close.bind(this));
3945 dialog.addEventListener('contextmenu', (event) => {
3946 if (event.target !== this.#textarea) {
3947 event.preventDefault();
3948 }
3949 });
3950 cancelButton.addEventListener('click', this.#finish.bind(this));
3951 saveButton.addEventListener('click', this.#save.bind(this));
3952 optionDescription.addEventListener('change', onUpdateUIState);
3953 optionDecorative.addEventListener('change', onUpdateUIState);
3954 textarea.addEventListener('keydown', (e) => {
3955 if ((e.ctrlKey || e.metaKey) && e.key === 'Enter' && !saveButton.disabled) {
3956 this.#save();
3957 }
3958 });
3959 this.#overlayManager.register(dialog);
3960 }
3961 #createSVGElement() {
3962 if (this.#svgElement) {
3963 return;
3964 }
3965 const svgFactory = new DOMSVGFactory();
3966 const svg = (this.#svgElement = svgFactory.createElement('svg'));
3967 svg.setAttribute('width', '0');
3968 svg.setAttribute('height', '0');
3969 const defs = svgFactory.createElement('defs');
3970 svg.append(defs);
3971 const mask = svgFactory.createElement('mask');
3972 defs.append(mask);
3973 mask.setAttribute('id', 'alttext-manager-mask');
3974 mask.setAttribute('maskContentUnits', 'objectBoundingBox');
3975 let rect = svgFactory.createElement('rect');
3976 mask.append(rect);
3977 rect.setAttribute('fill', 'white');
3978 rect.setAttribute('width', '1');
3979 rect.setAttribute('height', '1');
3980 rect.setAttribute('x', '0');
3981 rect.setAttribute('y', '0');
3982 rect = this.#rectElement = svgFactory.createElement('rect');
3983 mask.append(rect);
3984 rect.setAttribute('fill', 'black');
3985 this.#dialog.append(svg);
3986 }
3987 async editAltText(uiManager, editor) {
3988 if (this.#currentEditor || !editor) {
3989 return;
3990 }
3991 this.#createSVGElement();
3992 this.#hasUsedPointer = false;
3993 this.#clickAC = new AbortController();
3994 const clickOpts = {
3995 signal: this.#clickAC.signal,
3996 },
3997 onClick = this.#onClick.bind(this);
3998 for (const element of [
3999 this.#optionDescription,
4000 this.#optionDecorative,
4001 this.#textarea,
4002 this.#saveButton,
4003 this.#cancelButton,
4004 ]) {
4005 element.addEventListener('click', onClick, clickOpts);
4006 }
4007 const { altText, decorative } = editor.altTextData;
4008 if (decorative === true) {
4009 this.#optionDecorative.checked = true;
4010 this.#optionDescription.checked = false;
4011 } else {
4012 this.#optionDecorative.checked = false;
4013 this.#optionDescription.checked = true;
4014 }
4015 this.#previousAltText = this.#textarea.value = altText?.trim() || '';
4016 this.#updateUIState();
4017 this.#currentEditor = editor;
4018 this.#uiManager = uiManager;
4019 this.#uiManager.removeEditListeners();
4020 this.#resizeAC = new AbortController();
4021 this.#eventBus._on('resize', this.#setPosition.bind(this), {
4022 signal: this.#resizeAC.signal,
4023 });
4024 try {
4025 await this.#overlayManager.open(this.#dialog);
4026 this.#setPosition();
4027 } catch (ex) {
4028 this.#close();
4029 throw ex;
4030 }
4031 }
4032 #setPosition() {
4033 if (!this.#currentEditor) {
4034 return;
4035 }
4036 const dialog = this.#dialog;
4037 const { style } = dialog;
4038 const {
4039 x: containerX,
4040 y: containerY,
4041 width: containerW,
4042 height: containerH,
4043 } = this.#container.getBoundingClientRect();
4044 const { innerWidth: windowW, innerHeight: windowH } = window;
4045 const { width: dialogW, height: dialogH } = dialog.getBoundingClientRect();
4046 const { x, y, width, height } = this.#currentEditor.getClientDimensions();
4047 const MARGIN = 10;
4048 const isLTR = this.#uiManager.direction === 'ltr';
4049 const xs = Math.max(x, containerX);
4050 const xe = Math.min(x + width, containerX + containerW);
4051 const ys = Math.max(y, containerY);
4052 const ye = Math.min(y + height, containerY + containerH);
4053 this.#rectElement.setAttribute('width', `${(xe - xs) / windowW}`);
4054 this.#rectElement.setAttribute('height', `${(ye - ys) / windowH}`);
4055 this.#rectElement.setAttribute('x', `${xs / windowW}`);
4056 this.#rectElement.setAttribute('y', `${ys / windowH}`);
4057 let left = null;
4058 let top = Math.max(y, 0);
4059 top += Math.min(windowH - (top + dialogH), 0);
4060 if (isLTR) {
4061 if (x + width + MARGIN + dialogW < windowW) {
4062 left = x + width + MARGIN;
4063 } else if (x > dialogW + MARGIN) {
4064 left = x - dialogW - MARGIN;
4065 }
4066 } else if (x > dialogW + MARGIN) {
4067 left = x - dialogW - MARGIN;
4068 } else if (x + width + MARGIN + dialogW < windowW) {
4069 left = x + width + MARGIN;
4070 }
4071 if (left === null) {
4072 top = null;
4073 left = Math.max(x, 0);
4074 left += Math.min(windowW - (left + dialogW), 0);
4075 if (y > dialogH + MARGIN) {
4076 top = y - dialogH - MARGIN;
4077 } else if (y + height + MARGIN + dialogH < windowH) {
4078 top = y + height + MARGIN;
4079 }
4080 }
4081 if (top !== null) {
4082 dialog.classList.add('positioned');
4083 if (isLTR) {
4084 style.left = `${left}px`;
4085 } else {
4086 style.right = `${windowW - left - dialogW}px`;
4087 }
4088 style.top = `${top}px`;
4089 } else {
4090 dialog.classList.remove('positioned');
4091 style.left = '';
4092 style.top = '';
4093 }
4094 }
4095 #finish() {
4096 this.#overlayManager.closeIfActive(this.#dialog);
4097 }
4098 #close() {
4099 this.#currentEditor._reportTelemetry(
4100 this.#telemetryData || {
4101 action: 'alt_text_cancel',
4102 alt_text_keyboard: !this.#hasUsedPointer,
4103 }
4104 );
4105 this.#telemetryData = null;
4106 this.#removeOnClickListeners();
4107 this.#uiManager?.addEditListeners();
4108 this.#resizeAC?.abort();
4109 this.#resizeAC = null;
4110 this.#currentEditor.altTextFinish();
4111 this.#currentEditor = null;
4112 this.#uiManager = null;
4113 }
4114 #updateUIState() {
4115 this.#textarea.disabled = this.#optionDecorative.checked;
4116 }
4117 #save() {
4118 const altText = this.#textarea.value.trim();
4119 const decorative = this.#optionDecorative.checked;
4120 this.#currentEditor.altTextData = {
4121 altText,
4122 decorative,
4123 };
4124 this.#telemetryData = {
4125 action: 'alt_text_save',
4126 alt_text_description: !!altText,
4127 alt_text_edit: !!this.#previousAltText && this.#previousAltText !== altText,
4128 alt_text_decorative: decorative,
4129 alt_text_keyboard: !this.#hasUsedPointer,
4130 };
4131 this.#finish();
4132 }
4133 #onClick(evt) {
4134 if (evt.detail === 0) {
4135 return;
4136 }
4137 this.#hasUsedPointer = true;
4138 this.#removeOnClickListeners();
4139 }
4140 #removeOnClickListeners() {
4141 this.#clickAC?.abort();
4142 this.#clickAC = null;
4143 }
4144 destroy() {
4145 this.#uiManager = null;
4146 this.#finish();
4147 this.#svgElement?.remove();
4148 this.#svgElement = this.#rectElement = null;
4149 }
4150} // ./web/annotation_editor_params.js
4151
4152class AnnotationEditorParams {
4153 constructor(options, eventBus) {
4154 this.eventBus = eventBus;
4155 this.#bindListeners(options);
4156 }
4157 #bindListeners({
4158 editorFreeTextFontSize,
4159 editorFreeTextColor,
4160 editorInkColor,
4161 editorInkThickness,
4162 editorInkOpacity,
4163 editorStampAddImage,
4164 editorFreeHighlightThickness,
4165 editorHighlightShowAll,
4166 editorSignatureAddSignature,
4167 }) {
4168 const { eventBus } = this;
4169 const dispatchEvent = (typeStr, value) => {
4170 eventBus.dispatch('switchannotationeditorparams', {
4171 source: this,
4172 type: AnnotationEditorParamsType[typeStr],
4173 value,
4174 });
4175 };
4176 editorFreeTextFontSize.addEventListener('input', function () {
4177 dispatchEvent('FREETEXT_SIZE', this.valueAsNumber);
4178 });
4179 editorFreeTextColor.addEventListener('input', function () {
4180 dispatchEvent('FREETEXT_COLOR', this.value);
4181 });
4182 editorInkColor.addEventListener('input', function () {
4183 dispatchEvent('INK_COLOR', this.value);
4184 });
4185 editorInkThickness.addEventListener('input', function () {
4186 dispatchEvent('INK_THICKNESS', this.valueAsNumber);
4187 });
4188 editorInkOpacity.addEventListener('input', function () {
4189 dispatchEvent('INK_OPACITY', this.valueAsNumber);
4190 });
4191 editorStampAddImage.addEventListener('click', () => {
4192 eventBus.dispatch('reporttelemetry', {
4193 source: this,
4194 details: {
4195 type: 'editing',
4196 data: {
4197 action: 'pdfjs.image.add_image_click',
4198 },
4199 },
4200 });
4201 dispatchEvent('CREATE');
4202 });
4203 editorFreeHighlightThickness.addEventListener('input', function () {
4204 dispatchEvent('HIGHLIGHT_THICKNESS', this.valueAsNumber);
4205 });
4206 editorHighlightShowAll.addEventListener('click', function () {
4207 const checked = this.getAttribute('aria-pressed') === 'true';
4208 this.setAttribute('aria-pressed', !checked);
4209 dispatchEvent('HIGHLIGHT_SHOW_ALL', !checked);
4210 });
4211 editorSignatureAddSignature.addEventListener('click', () => {
4212 dispatchEvent('CREATE');
4213 });
4214 eventBus._on('annotationeditorparamschanged', (evt) => {
4215 for (const [type, value] of evt.details) {
4216 switch (type) {
4217 case AnnotationEditorParamsType.FREETEXT_SIZE:
4218 editorFreeTextFontSize.value = value;
4219 break;
4220 case AnnotationEditorParamsType.FREETEXT_COLOR:
4221 editorFreeTextColor.value = value;
4222 break;
4223 case AnnotationEditorParamsType.INK_COLOR:
4224 editorInkColor.value = value;
4225 break;
4226 case AnnotationEditorParamsType.INK_THICKNESS:
4227 editorInkThickness.value = value;
4228 break;
4229 case AnnotationEditorParamsType.INK_OPACITY:
4230 editorInkOpacity.value = value;
4231 break;
4232 case AnnotationEditorParamsType.HIGHLIGHT_COLOR:
4233 eventBus.dispatch('mainhighlightcolorpickerupdatecolor', {
4234 source: this,
4235 value,
4236 });
4237 break;
4238 case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS:
4239 editorFreeHighlightThickness.value = value;
4240 break;
4241 case AnnotationEditorParamsType.HIGHLIGHT_FREE:
4242 editorFreeHighlightThickness.disabled = !value;
4243 break;
4244 case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL:
4245 editorHighlightShowAll.setAttribute('aria-pressed', value);
4246 break;
4247 }
4248 }
4249 });
4250 }
4251} // ./web/caret_browsing.js
4252
4253const PRECISION = 1e-1;
4254class CaretBrowsingMode {
4255 #mainContainer;
4256 #toolBarHeight = 0;
4257 #viewerContainer;
4258 constructor(abortSignal, mainContainer, viewerContainer, toolbarContainer) {
4259 this.#mainContainer = mainContainer;
4260 this.#viewerContainer = viewerContainer;
4261 if (!toolbarContainer) {
4262 return;
4263 }
4264 this.#toolBarHeight = toolbarContainer.getBoundingClientRect().height;
4265 const toolbarObserver = new ResizeObserver((entries) => {
4266 for (const entry of entries) {
4267 if (entry.target === toolbarContainer) {
4268 this.#toolBarHeight = Math.floor(entry.borderBoxSize[0].blockSize);
4269 break;
4270 }
4271 }
4272 });
4273 toolbarObserver.observe(toolbarContainer);
4274 abortSignal.addEventListener('abort', () => toolbarObserver.disconnect(), {
4275 once: true,
4276 });
4277 }
4278 #isOnSameLine(rect1, rect2) {
4279 const top1 = rect1.y;
4280 const bot1 = rect1.bottom;
4281 const mid1 = rect1.y + rect1.height / 2;
4282 const top2 = rect2.y;
4283 const bot2 = rect2.bottom;
4284 const mid2 = rect2.y + rect2.height / 2;
4285 return (top1 <= mid2 && mid2 <= bot1) || (top2 <= mid1 && mid1 <= bot2);
4286 }
4287 #isUnderOver(rect, x, y, isUp) {
4288 const midY = rect.y + rect.height / 2;
4289 return (isUp ? y >= midY : y <= midY) && rect.x - PRECISION <= x && x <= rect.right + PRECISION;
4290 }
4291 #isVisible(rect) {
4292 return (
4293 rect.top >= this.#toolBarHeight &&
4294 rect.left >= 0 &&
4295 rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
4296 rect.right <= (window.innerWidth || document.documentElement.clientWidth)
4297 );
4298 }
4299 #getCaretPosition(selection, isUp) {
4300 const { focusNode, focusOffset } = selection;
4301 const range = document.createRange();
4302 range.setStart(focusNode, focusOffset);
4303 range.setEnd(focusNode, focusOffset);
4304 const rect = range.getBoundingClientRect();
4305 return [rect.x, isUp ? rect.top : rect.bottom];
4306 }
4307 static #caretPositionFromPoint(x, y) {
4308 if (!document.caretPositionFromPoint) {
4309 const { startContainer: offsetNode, startOffset: offset } = document.caretRangeFromPoint(
4310 x,
4311 y
4312 );
4313 return {
4314 offsetNode,
4315 offset,
4316 };
4317 }
4318 return document.caretPositionFromPoint(x, y);
4319 }
4320 #setCaretPositionHelper(selection, caretX, select, element, rect) {
4321 rect ||= element.getBoundingClientRect();
4322 if (caretX <= rect.x + PRECISION) {
4323 if (select) {
4324 selection.extend(element.firstChild, 0);
4325 } else {
4326 selection.setPosition(element.firstChild, 0);
4327 }
4328 return;
4329 }
4330 if (rect.right - PRECISION <= caretX) {
4331 const { lastChild } = element;
4332 if (select) {
4333 selection.extend(lastChild, lastChild.length);
4334 } else {
4335 selection.setPosition(lastChild, lastChild.length);
4336 }
4337 return;
4338 }
4339 const midY = rect.y + rect.height / 2;
4340 let caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY);
4341 let parentElement = caretPosition.offsetNode?.parentElement;
4342 if (parentElement && parentElement !== element) {
4343 const elementsAtPoint = document.elementsFromPoint(caretX, midY);
4344 const savedVisibilities = [];
4345 for (const el of elementsAtPoint) {
4346 if (el === element) {
4347 break;
4348 }
4349 const { style } = el;
4350 savedVisibilities.push([el, style.visibility]);
4351 style.visibility = 'hidden';
4352 }
4353 caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY);
4354 parentElement = caretPosition.offsetNode?.parentElement;
4355 for (const [el, visibility] of savedVisibilities) {
4356 el.style.visibility = visibility;
4357 }
4358 }
4359 if (parentElement !== element) {
4360 if (select) {
4361 selection.extend(element.firstChild, 0);
4362 } else {
4363 selection.setPosition(element.firstChild, 0);
4364 }
4365 return;
4366 }
4367 if (select) {
4368 selection.extend(caretPosition.offsetNode, caretPosition.offset);
4369 } else {
4370 selection.setPosition(caretPosition.offsetNode, caretPosition.offset);
4371 }
4372 }
4373 #setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX) {
4374 if (this.#isVisible(newLineElementRect)) {
4375 this.#setCaretPositionHelper(selection, caretX, select, newLineElement, newLineElementRect);
4376 return;
4377 }
4378 this.#mainContainer.addEventListener(
4379 'scrollend',
4380 this.#setCaretPositionHelper.bind(this, selection, caretX, select, newLineElement, null),
4381 {
4382 once: true,
4383 }
4384 );
4385 newLineElement.scrollIntoView();
4386 }
4387 #getNodeOnNextPage(textLayer, isUp) {
4388 while (true) {
4389 const page = textLayer.closest('.page');
4390 const pageNumber = parseInt(page.getAttribute('data-page-number'));
4391 const nextPage = isUp ? pageNumber - 1 : pageNumber + 1;
4392 textLayer = this.#viewerContainer.querySelector(
4393 `.page[data-page-number="${nextPage}"] .textLayer`
4394 );
4395 if (!textLayer) {
4396 return null;
4397 }
4398 const walker = document.createTreeWalker(textLayer, NodeFilter.SHOW_TEXT);
4399 const node = isUp ? walker.lastChild() : walker.firstChild();
4400 if (node) {
4401 return node;
4402 }
4403 }
4404 }
4405 moveCaret(isUp, select) {
4406 const selection = document.getSelection();
4407 if (selection.rangeCount === 0) {
4408 return;
4409 }
4410 const { focusNode } = selection;
4411 const focusElement =
4412 focusNode.nodeType !== Node.ELEMENT_NODE ? focusNode.parentElement : focusNode;
4413 const root = focusElement.closest('.textLayer');
4414 if (!root) {
4415 return;
4416 }
4417 const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
4418 walker.currentNode = focusNode;
4419 const focusRect = focusElement.getBoundingClientRect();
4420 let newLineElement = null;
4421 const nodeIterator = (isUp ? walker.previousSibling : walker.nextSibling).bind(walker);
4422 while (nodeIterator()) {
4423 const element = walker.currentNode.parentElement;
4424 if (!this.#isOnSameLine(focusRect, element.getBoundingClientRect())) {
4425 newLineElement = element;
4426 break;
4427 }
4428 }
4429 if (!newLineElement) {
4430 const node = this.#getNodeOnNextPage(root, isUp);
4431 if (!node) {
4432 return;
4433 }
4434 if (select) {
4435 const lastNode = (isUp ? walker.firstChild() : walker.lastChild()) || focusNode;
4436 selection.extend(lastNode, isUp ? 0 : lastNode.length);
4437 const range = document.createRange();
4438 range.setStart(node, isUp ? node.length : 0);
4439 range.setEnd(node, isUp ? node.length : 0);
4440 selection.addRange(range);
4441 return;
4442 }
4443 const [caretX] = this.#getCaretPosition(selection, isUp);
4444 const { parentElement } = node;
4445 this.#setCaretPosition(
4446 select,
4447 selection,
4448 parentElement,
4449 parentElement.getBoundingClientRect(),
4450 caretX
4451 );
4452 return;
4453 }
4454 const [caretX, caretY] = this.#getCaretPosition(selection, isUp);
4455 const newLineElementRect = newLineElement.getBoundingClientRect();
4456 if (this.#isUnderOver(newLineElementRect, caretX, caretY, isUp)) {
4457 this.#setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX);
4458 return;
4459 }
4460 while (nodeIterator()) {
4461 const element = walker.currentNode.parentElement;
4462 const elementRect = element.getBoundingClientRect();
4463 if (!this.#isOnSameLine(newLineElementRect, elementRect)) {
4464 break;
4465 }
4466 if (this.#isUnderOver(elementRect, caretX, caretY, isUp)) {
4467 this.#setCaretPosition(select, selection, element, elementRect, caretX);
4468 return;
4469 }
4470 }
4471 this.#setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX);
4472 }
4473} // ./web/sidebar.js
4474
4475const RESIZE_TIMEOUT = 400;
4476class Sidebar {
4477 #initialWidth = 0;
4478 #width = 0;
4479 #coefficient;
4480 #resizeTimeout = null;
4481 #resizer;
4482 #isResizerOnTheLeft;
4483 #isKeyboardResizing = false;
4484 #resizeObserver;
4485 #prevX = 0;
4486 constructor({ sidebar, resizer, toggleButton }, ltr, isResizerOnTheLeft) {
4487 this._sidebar = sidebar;
4488 this.#coefficient = ltr === isResizerOnTheLeft ? -1 : 1;
4489 this.#resizer = resizer;
4490 this.#isResizerOnTheLeft = isResizerOnTheLeft;
4491 const style = window.getComputedStyle(sidebar);
4492 this.#initialWidth = this.#width = parseFloat(style.getPropertyValue('--sidebar-width'));
4493 resizer.ariaValueMin = parseFloat(style.getPropertyValue('--sidebar-min-width')) || 0;
4494 resizer.ariaValueMax = parseFloat(style.getPropertyValue('--sidebar-max-width')) || Infinity;
4495 resizer.ariaValueNow = this.#width;
4496 this.#makeSidebarResizable();
4497 toggleButton.addEventListener('click', this.toggle.bind(this));
4498 this._isOpen = false;
4499 sidebar.hidden = true;
4500 this.#resizeObserver = new ResizeObserver(
4501 ([
4502 {
4503 borderBoxSize: [{ inlineSize }],
4504 },
4505 ]) => {
4506 if (!isNaN(this.#prevX)) {
4507 this.#prevX += this.#coefficient * (inlineSize - this.#width);
4508 }
4509 this.#setWidth(inlineSize);
4510 }
4511 );
4512 this.#resizeObserver.observe(sidebar);
4513 }
4514 #makeSidebarResizable() {
4515 const sidebarStyle = this._sidebar.style;
4516 let pointerMoveAC;
4517 const cancelResize = () => {
4518 this.#resizeTimeout = null;
4519 this._sidebar.classList.remove('resizing');
4520 pointerMoveAC?.abort();
4521 pointerMoveAC = null;
4522 this.#isKeyboardResizing = false;
4523 this.onStopResizing();
4524 this.#prevX = NaN;
4525 };
4526 this.#resizer.addEventListener('pointerdown', (e) => {
4527 if (pointerMoveAC) {
4528 cancelResize();
4529 return;
4530 }
4531 this.onStartResizing();
4532 const { clientX } = e;
4533 stopEvent(e);
4534 this.#prevX = clientX;
4535 pointerMoveAC = new AbortController();
4536 const { signal } = pointerMoveAC;
4537 const sidebar = this._sidebar;
4538 sidebar.classList.add('resizing');
4539 const parentStyle = sidebar.parentElement.style;
4540 parentStyle.minWidth = 0;
4541 window.addEventListener('contextmenu', noContextMenu, {
4542 signal,
4543 });
4544 window.addEventListener(
4545 'pointermove',
4546 (ev) => {
4547 if (!pointerMoveAC || Math.abs(ev.clientX - this.#prevX) < 1) {
4548 return;
4549 }
4550 stopEvent(ev);
4551 sidebarStyle.width = `${Math.round(this.#width + this.#coefficient * (ev.clientX - this.#prevX))}px`;
4552 },
4553 {
4554 signal,
4555 capture: true,
4556 }
4557 );
4558 window.addEventListener('blur', cancelResize, {
4559 signal,
4560 });
4561 window.addEventListener(
4562 'pointerup',
4563 (ev) => {
4564 if (pointerMoveAC) {
4565 cancelResize();
4566 stopEvent(ev);
4567 }
4568 },
4569 {
4570 signal,
4571 }
4572 );
4573 });
4574 this.#resizer.addEventListener('keydown', (e) => {
4575 const { key } = e;
4576 const isArrowLeft = key === 'ArrowLeft';
4577 if (isArrowLeft || key === 'ArrowRight') {
4578 if (!this.#isKeyboardResizing) {
4579 this._sidebar.classList.add('resizing');
4580 this.#isKeyboardResizing = true;
4581 this.onStartResizing();
4582 }
4583 const base = e.ctrlKey || e.metaKey ? 10 : 1;
4584 const dx = base * (isArrowLeft ? -1 : 1);
4585 clearTimeout(this.#resizeTimeout);
4586 this.#resizeTimeout = setTimeout(cancelResize, RESIZE_TIMEOUT);
4587 sidebarStyle.width = `${Math.round(this.#width + this.#coefficient * dx)}px`;
4588 stopEvent(e);
4589 }
4590 });
4591 }
4592 #setWidth(newWidth) {
4593 this.#width = newWidth;
4594 this.#resizer.ariaValueNow = Math.round(newWidth);
4595 if (this.#isResizerOnTheLeft) {
4596 this._sidebar.parentElement.style.insetInlineStart = `${(this.#initialWidth - newWidth).toFixed(3)}px`;
4597 }
4598 this.onResizing(newWidth);
4599 }
4600 get width() {
4601 return this.#width;
4602 }
4603 set width(newWidth) {
4604 this._sidebar.style.width = `${newWidth}px`;
4605 }
4606 onStartResizing() {}
4607 onStopResizing() {}
4608 onResizing(_newWidth) {}
4609 toggle(visibility = !this._isOpen) {
4610 this._sidebar.hidden = !(this._isOpen = visibility);
4611 }
4612 destroy() {
4613 this.#resizeObserver?.disconnect();
4614 this.#resizeObserver = null;
4615 }
4616} // ./web/comment_manager.js
4617
4618class CommentManager {
4619 #dialog;
4620 #popup;
4621 #sidebar;
4622 static #hasForcedColors = null;
4623 constructor(commentDialog, sidebar, eventBus, linkService, overlayManager, ltr, hasForcedColors) {
4624 const dateFormat = new Intl.DateTimeFormat(undefined, {
4625 dateStyle: 'long',
4626 });
4627 this.dialogElement = commentDialog.dialog;
4628 this.#dialog = new CommentDialog(commentDialog, overlayManager, eventBus, ltr);
4629 this.#popup = new CommentPopup(eventBus, dateFormat, ltr, this.dialogElement);
4630 this.#sidebar = new CommentSidebar(
4631 sidebar,
4632 eventBus,
4633 linkService,
4634 this.#popup,
4635 dateFormat,
4636 ltr
4637 );
4638 this.#popup.sidebar = this.#sidebar;
4639 CommentManager.#hasForcedColors = hasForcedColors;
4640 }
4641 setSidebarUiManager(uiManager) {
4642 this.#sidebar.setUIManager(uiManager);
4643 }
4644 showSidebar(annotations) {
4645 this.#sidebar.show(annotations);
4646 }
4647 hideSidebar() {
4648 this.#sidebar.hide();
4649 }
4650 removeComments(ids) {
4651 this.#sidebar.removeComments(ids);
4652 }
4653 selectComment(id) {
4654 this.#sidebar.selectComment(null, id);
4655 }
4656 addComment(annotation) {
4657 this.#sidebar.addComment(annotation);
4658 }
4659 updateComment(annotation) {
4660 this.#sidebar.updateComment(annotation);
4661 }
4662 toggleCommentPopup(editor, isSelected, visibility, isEditable) {
4663 if (isSelected) {
4664 this.selectComment(editor.uid);
4665 }
4666 this.#popup.toggle(editor, isSelected, visibility, isEditable);
4667 }
4668 destroyPopup() {
4669 this.#popup.destroy();
4670 }
4671 updatePopupColor(editor) {
4672 this.#popup.updateColor(editor);
4673 }
4674 showDialog(uiManager, editor, posX, posY, options) {
4675 return this.#dialog.open(uiManager, editor, posX, posY, options);
4676 }
4677 makeCommentColor(color, opacity) {
4678 return CommentManager._makeCommentColor(color, opacity);
4679 }
4680 static _makeCommentColor(color, opacity) {
4681 return this.#hasForcedColors
4682 ? null
4683 : findContrastColor(
4684 applyOpacity(...color, opacity ?? 1),
4685 CSSConstants.commentForegroundColor
4686 );
4687 }
4688 destroy() {
4689 this.#dialog.destroy();
4690 this.#sidebar.hide();
4691 this.#popup.destroy();
4692 }
4693}
4694class CommentSidebar extends Sidebar {
4695 #annotations = null;
4696 #eventBus;
4697 #boundCommentClick = this.#commentClick.bind(this);
4698 #boundCommentKeydown = this.#commentKeydown.bind(this);
4699 #closeButton;
4700 #commentsList;
4701 #commentCount;
4702 #dateFormat;
4703 #sidebarTitle;
4704 #learnMoreUrl;
4705 #linkService;
4706 #popup;
4707 #elementsToAnnotations = null;
4708 #idsToElements = null;
4709 #uiManager = null;
4710 constructor(
4711 {
4712 learnMoreUrl,
4713 sidebar,
4714 sidebarResizer,
4715 commentsList,
4716 commentCount,
4717 sidebarTitle,
4718 closeButton,
4719 commentToolbarButton,
4720 },
4721 eventBus,
4722 linkService,
4723 popup,
4724 dateFormat,
4725 ltr
4726 ) {
4727 super(
4728 {
4729 sidebar,
4730 resizer: sidebarResizer,
4731 toggleButton: commentToolbarButton,
4732 },
4733 ltr,
4734 true
4735 );
4736 this.#sidebarTitle = sidebarTitle;
4737 this.#commentsList = commentsList;
4738 this.#commentCount = commentCount;
4739 this.#learnMoreUrl = learnMoreUrl;
4740 this.#linkService = linkService;
4741 this.#closeButton = closeButton;
4742 this.#popup = popup;
4743 this.#dateFormat = dateFormat;
4744 this.#eventBus = eventBus;
4745 closeButton.addEventListener('click', () => {
4746 eventBus.dispatch('switchannotationeditormode', {
4747 source: this,
4748 mode: AnnotationEditorType.NONE,
4749 });
4750 });
4751 const keyDownCallback = (e) => {
4752 if (e.key === 'ArrowDown' || e.key === 'Home' || e.key === 'F6') {
4753 this.#commentsList.firstElementChild.focus();
4754 stopEvent(e);
4755 } else if (e.key === 'ArrowUp' || e.key === 'End') {
4756 this.#commentsList.lastElementChild.focus();
4757 stopEvent(e);
4758 }
4759 };
4760 commentToolbarButton.addEventListener('keydown', keyDownCallback);
4761 sidebar.addEventListener('keydown', keyDownCallback);
4762 }
4763 setUIManager(uiManager) {
4764 this.#uiManager = uiManager;
4765 }
4766 show(annotations) {
4767 this.#elementsToAnnotations = new WeakMap();
4768 this.#idsToElements = new Map();
4769 this.#annotations = annotations;
4770 annotations.sort(this.#sortComments.bind(this));
4771 if (annotations.length !== 0) {
4772 const fragment = document.createDocumentFragment();
4773 for (const annotation of annotations) {
4774 fragment.append(this.#createCommentElement(annotation));
4775 }
4776 this.#setCommentsCount(fragment);
4777 this.#commentsList.append(fragment);
4778 } else {
4779 this.#setCommentsCount();
4780 }
4781 this._sidebar.hidden = false;
4782 this.#eventBus.dispatch('reporttelemetry', {
4783 source: this,
4784 details: {
4785 type: 'commentSidebar',
4786 data: {
4787 numberOfAnnotations: annotations.length,
4788 },
4789 },
4790 });
4791 }
4792 hide() {
4793 this._sidebar.hidden = true;
4794 this.#commentsList.replaceChildren();
4795 this.#elementsToAnnotations = null;
4796 this.#idsToElements = null;
4797 this.#annotations = null;
4798 }
4799 removeComments(ids) {
4800 if (ids.length === 0 || !this.#idsToElements) {
4801 return;
4802 }
4803 if (new Set(this.#idsToElements.keys()).difference(new Set(ids)).size === 0) {
4804 this.#removeAll();
4805 return;
4806 }
4807 for (const id of ids) {
4808 this.#removeComment(id);
4809 }
4810 }
4811 focusComment(id) {
4812 const element = this.#idsToElements.get(id);
4813 if (!element) {
4814 return;
4815 }
4816 this._sidebar.scrollTop = element.offsetTop - this._sidebar.offsetTop;
4817 for (const el of this.#commentsList.children) {
4818 el.classList.toggle('selected', el === element);
4819 }
4820 }
4821 updateComment(annotation) {
4822 if (!this.#idsToElements) {
4823 return;
4824 }
4825 const { id, creationDate, modificationDate, richText, contentsObj, popupRef } = annotation;
4826 if (!popupRef || (!richText && !contentsObj?.str)) {
4827 this.#removeComment(id);
4828 }
4829 const element = this.#idsToElements.get(id);
4830 if (!element) {
4831 return;
4832 }
4833 const prevAnnotation = this.#elementsToAnnotations.get(element);
4834 let index = binarySearchFirstItem(
4835 this.#annotations,
4836 (a) => this.#sortComments(a, prevAnnotation) >= 0
4837 );
4838 if (index >= this.#annotations.length) {
4839 return;
4840 }
4841 this.#setDate(element.firstElementChild, modificationDate || creationDate);
4842 this.#setText(element.lastElementChild, richText, contentsObj);
4843 this.#annotations.splice(index, 1);
4844 index = binarySearchFirstItem(this.#annotations, (a) => this.#sortComments(a, annotation) >= 0);
4845 this.#annotations.splice(index, 0, annotation);
4846 if (index >= this.#commentsList.children.length) {
4847 this.#commentsList.append(element);
4848 } else {
4849 this.#commentsList.insertBefore(element, this.#commentsList.children[index]);
4850 }
4851 }
4852 #removeComment(id) {
4853 const element = this.#idsToElements?.get(id);
4854 if (!element) {
4855 return;
4856 }
4857 const annotation = this.#elementsToAnnotations.get(element);
4858 const index = binarySearchFirstItem(
4859 this.#annotations,
4860 (a) => this.#sortComments(a, annotation) >= 0
4861 );
4862 if (index >= this.#annotations.length) {
4863 return;
4864 }
4865 this.#annotations.splice(index, 1);
4866 element.remove();
4867 this.#idsToElements.delete(id);
4868 this.#setCommentsCount();
4869 }
4870 #removeAll() {
4871 this.#commentsList.replaceChildren();
4872 this.#elementsToAnnotations = new WeakMap();
4873 this.#idsToElements.clear();
4874 this.#annotations.length = 0;
4875 this.#setCommentsCount();
4876 }
4877 selectComment(element, id = null) {
4878 if (!this.#idsToElements) {
4879 return;
4880 }
4881 const hasNoElement = !element;
4882 element ||= this.#idsToElements.get(id);
4883 for (const el of this.#commentsList.children) {
4884 el.classList.toggle('selected', el === element);
4885 }
4886 if (hasNoElement) {
4887 element?.scrollIntoView({
4888 behavior: 'instant',
4889 block: 'center',
4890 });
4891 }
4892 }
4893 addComment(annotation) {
4894 if (this.#idsToElements?.has(annotation.id)) {
4895 return;
4896 }
4897 const { popupRef, contentsObj } = annotation;
4898 if (!popupRef || !contentsObj?.str) {
4899 return;
4900 }
4901 const commentItem = this.#createCommentElement(annotation);
4902 if (this.#annotations.length === 0) {
4903 this.#commentsList.replaceChildren(commentItem);
4904 this.#annotations.push(annotation);
4905 this.#setCommentsCount();
4906 return;
4907 }
4908 const index = binarySearchFirstItem(
4909 this.#annotations,
4910 (a) => this.#sortComments(a, annotation) >= 0
4911 );
4912 this.#annotations.splice(index, 0, annotation);
4913 if (index >= this.#commentsList.children.length) {
4914 this.#commentsList.append(commentItem);
4915 } else {
4916 this.#commentsList.insertBefore(commentItem, this.#commentsList.children[index]);
4917 }
4918 this.#setCommentsCount();
4919 }
4920 #setCommentsCount(container = this.#commentsList) {
4921 const count = this.#idsToElements.size;
4922 this.#sidebarTitle.setAttribute(
4923 'data-l10n-args',
4924 JSON.stringify({
4925 count,
4926 })
4927 );
4928 this.#commentCount.textContent = count;
4929 if (count === 0) {
4930 container.append(this.#createZeroCommentElement());
4931 }
4932 }
4933 #createZeroCommentElement() {
4934 const commentItem = document.createElement('li');
4935 commentItem.classList.add('sidebarComment', 'noComments');
4936 const textDiv = document.createElement('div');
4937 textDiv.className = 'sidebarCommentText';
4938 textDiv.setAttribute('data-l10n-id', 'pdfjs-editor-comments-sidebar-no-comments1');
4939 commentItem.append(textDiv);
4940 if (this.#learnMoreUrl) {
4941 const a = document.createElement('a');
4942 a.setAttribute('data-l10n-id', 'pdfjs-editor-comments-sidebar-no-comments-link');
4943 a.href = this.#learnMoreUrl;
4944 a.target = '_blank';
4945 a.rel = 'noopener noreferrer';
4946 commentItem.append(a);
4947 }
4948 return commentItem;
4949 }
4950 #setDate(element, date) {
4951 date = PDFDateString.toDateObject(date);
4952 element.dateTime = date.toISOString();
4953 element.textContent = this.#dateFormat.format(date);
4954 }
4955 #setText(element, richText, contentsObj) {
4956 element.replaceChildren();
4957 const html =
4958 richText?.str && (!contentsObj?.str || richText.str === contentsObj.str)
4959 ? richText.html
4960 : contentsObj?.str;
4961 renderRichText(
4962 {
4963 html,
4964 dir: contentsObj?.dir || 'auto',
4965 className: 'richText',
4966 },
4967 element
4968 );
4969 }
4970 #createCommentElement(annotation) {
4971 const { id, creationDate, modificationDate, richText, contentsObj, color, opacity } =
4972 annotation;
4973 const commentItem = document.createElement('li');
4974 commentItem.role = 'button';
4975 commentItem.className = 'sidebarComment';
4976 commentItem.tabIndex = -1;
4977 commentItem.style.backgroundColor =
4978 (color && CommentManager._makeCommentColor(color, opacity)) || '';
4979 const dateDiv = document.createElement('time');
4980 this.#setDate(dateDiv, modificationDate || creationDate);
4981 const textDiv = document.createElement('div');
4982 textDiv.className = 'sidebarCommentText';
4983 this.#setText(textDiv, richText, contentsObj);
4984 commentItem.append(dateDiv, textDiv);
4985 commentItem.addEventListener('click', this.#boundCommentClick);
4986 commentItem.addEventListener('keydown', this.#boundCommentKeydown);
4987 this.#elementsToAnnotations.set(commentItem, annotation);
4988 this.#idsToElements.set(id, commentItem);
4989 return commentItem;
4990 }
4991 async #commentClick({ currentTarget }) {
4992 if (currentTarget.classList.contains('selected')) {
4993 currentTarget.classList.remove('selected');
4994 this.#popup._hide();
4995 return;
4996 }
4997 const annotation = this.#elementsToAnnotations.get(currentTarget);
4998 if (!annotation) {
4999 return;
5000 }
5001 this.#popup._hide();
5002 const { id, pageIndex, rect } = annotation;
5003 const pageNumber = pageIndex + 1;
5004 const pageVisiblePromise = this.#uiManager?.waitForEditorsRendered(pageNumber);
5005 this.#linkService?.goToXY(pageNumber, rect[0], rect[3], {
5006 center: 'both',
5007 });
5008 this.selectComment(currentTarget);
5009 await pageVisiblePromise;
5010 this.#uiManager?.selectComment(pageIndex, id);
5011 }
5012 #commentKeydown(e) {
5013 const { key, currentTarget } = e;
5014 switch (key) {
5015 case 'ArrowDown':
5016 (currentTarget.nextElementSibling || this.#commentsList.firstElementChild).focus();
5017 stopEvent(e);
5018 break;
5019 case 'ArrowUp':
5020 (currentTarget.previousElementSibling || this.#commentsList.lastElementChild).focus();
5021 stopEvent(e);
5022 break;
5023 case 'Home':
5024 this.#commentsList.firstElementChild.focus();
5025 stopEvent(e);
5026 break;
5027 case 'End':
5028 this.#commentsList.lastElementChild.focus();
5029 stopEvent(e);
5030 break;
5031 case 'Enter':
5032 case ' ':
5033 this.#commentClick(e);
5034 stopEvent(e);
5035 break;
5036 case 'ShiftTab':
5037 this.#closeButton.focus();
5038 stopEvent(e);
5039 break;
5040 }
5041 }
5042 #sortComments(a, b) {
5043 const dateA = PDFDateString.toDateObject(a.modificationDate || a.creationDate);
5044 const dateB = PDFDateString.toDateObject(b.modificationDate || b.creationDate);
5045 if (dateA !== dateB) {
5046 if (dateA !== null && dateB !== null) {
5047 return dateB - dateA;
5048 }
5049 return dateA !== null ? -1 : 1;
5050 }
5051 if (a.pageIndex !== b.pageIndex) {
5052 return a.pageIndex - b.pageIndex;
5053 }
5054 if (a.rect[3] !== b.rect[3]) {
5055 return b.rect[3] - a.rect[3];
5056 }
5057 if (a.rect[0] !== b.rect[0]) {
5058 return a.rect[0] - b.rect[0];
5059 }
5060 if (a.rect[1] !== b.rect[1]) {
5061 return b.rect[1] - a.rect[1];
5062 }
5063 if (a.rect[2] !== b.rect[2]) {
5064 return a.rect[2] - b.rect[2];
5065 }
5066 return a.id.localeCompare(b.id);
5067 }
5068}
5069class CommentDialog {
5070 #dialog;
5071 #editor;
5072 #overlayManager;
5073 #previousText = '';
5074 #commentText = '';
5075 #textInput;
5076 #title;
5077 #saveButton;
5078 #uiManager;
5079 #prevDragX = 0;
5080 #prevDragY = 0;
5081 #dialogX = 0;
5082 #dialogY = 0;
5083 #isLTR;
5084 #eventBus;
5085 constructor(
5086 { dialog, toolbar, title, textInput, cancelButton, saveButton },
5087 overlayManager,
5088 eventBus,
5089 ltr
5090 ) {
5091 this.#dialog = dialog;
5092 this.#textInput = textInput;
5093 this.#overlayManager = overlayManager;
5094 this.#eventBus = eventBus;
5095 this.#saveButton = saveButton;
5096 this.#title = title;
5097 this.#isLTR = ltr;
5098 const finishBound = this.#finish.bind(this);
5099 dialog.addEventListener('close', finishBound);
5100 dialog.addEventListener('contextmenu', (e) => {
5101 if (e.target !== this.#textInput) {
5102 e.preventDefault();
5103 }
5104 });
5105 cancelButton.addEventListener('click', finishBound);
5106 saveButton.addEventListener('click', this.#save.bind(this));
5107 textInput.addEventListener('input', () => {
5108 saveButton.disabled = textInput.value === this.#previousText;
5109 });
5110 textInput.addEventListener('keydown', (e) => {
5111 if ((e.ctrlKey || e.metaKey) && e.key === 'Enter' && !saveButton.disabled) {
5112 this.#save();
5113 }
5114 });
5115 let pointerMoveAC;
5116 const cancelDrag = () => {
5117 dialog.classList.remove('dragging');
5118 pointerMoveAC?.abort();
5119 pointerMoveAC = null;
5120 };
5121 toolbar.addEventListener('pointerdown', (e) => {
5122 if (pointerMoveAC) {
5123 cancelDrag();
5124 return;
5125 }
5126 const { clientX, clientY } = e;
5127 stopEvent(e);
5128 this.#prevDragX = clientX;
5129 this.#prevDragY = clientY;
5130 pointerMoveAC = new AbortController();
5131 const { signal } = pointerMoveAC;
5132 const { innerHeight, innerWidth } = window;
5133 dialog.classList.add('dragging');
5134 window.addEventListener(
5135 'pointermove',
5136 (ev) => {
5137 if (!pointerMoveAC) {
5138 return;
5139 }
5140 const { clientX: x, clientY: y } = ev;
5141 this.#setPosition(
5142 this.#dialogX + (x - this.#prevDragX) / innerWidth,
5143 this.#dialogY + (y - this.#prevDragY) / innerHeight
5144 );
5145 this.#prevDragX = x;
5146 this.#prevDragY = y;
5147 stopEvent(ev);
5148 },
5149 {
5150 signal,
5151 }
5152 );
5153 window.addEventListener('blur', cancelDrag, {
5154 signal,
5155 });
5156 window.addEventListener(
5157 'pointerup',
5158 (ev) => {
5159 if (pointerMoveAC) {
5160 cancelDrag();
5161 stopEvent(ev);
5162 }
5163 },
5164 {
5165 signal,
5166 }
5167 );
5168 });
5169 overlayManager.register(dialog);
5170 }
5171 async open(uiManager, editor, posX, posY, options) {
5172 if (editor) {
5173 this.#uiManager = uiManager;
5174 this.#editor = editor;
5175 }
5176 const {
5177 contentsObj: { str },
5178 color,
5179 opacity,
5180 } = editor.getData();
5181 const { style: dialogStyle } = this.#dialog;
5182 if (color) {
5183 dialogStyle.backgroundColor = CommentManager._makeCommentColor(color, opacity);
5184 dialogStyle.borderColor = Util.makeHexColor(...color);
5185 } else {
5186 dialogStyle.backgroundColor = dialogStyle.borderColor = '';
5187 }
5188 this.#commentText = str || '';
5189 const textInput = this.#textInput;
5190 textInput.value = this.#previousText = this.#commentText;
5191 if (str) {
5192 this.#title.setAttribute(
5193 'data-l10n-id',
5194 'pdfjs-editor-edit-comment-dialog-title-when-editing'
5195 );
5196 this.#saveButton.setAttribute(
5197 'data-l10n-id',
5198 'pdfjs-editor-edit-comment-dialog-save-button-when-editing'
5199 );
5200 } else {
5201 this.#title.setAttribute(
5202 'data-l10n-id',
5203 'pdfjs-editor-edit-comment-dialog-title-when-adding'
5204 );
5205 this.#saveButton.setAttribute(
5206 'data-l10n-id',
5207 'pdfjs-editor-edit-comment-dialog-save-button-when-adding'
5208 );
5209 }
5210 if (options?.height) {
5211 textInput.style.height = `${options.height}px`;
5212 }
5213 this.#uiManager?.removeEditListeners();
5214 this.#saveButton.disabled = true;
5215 const parentDimensions = options?.parentDimensions;
5216 const { innerHeight, innerWidth } = window;
5217 if (editor.hasDefaultPopupPosition()) {
5218 const { dialogWidth, dialogHeight } = this._dialogDimensions;
5219 if (parentDimensions) {
5220 if (
5221 this.#isLTR &&
5222 posX + dialogWidth > Math.min(parentDimensions.x + parentDimensions.width, innerWidth)
5223 ) {
5224 const buttonWidth = this.#editor.commentButtonWidth;
5225 posX -= dialogWidth - buttonWidth * parentDimensions.width;
5226 } else if (!this.#isLTR) {
5227 const buttonWidth = this.#editor.commentButtonWidth * parentDimensions.width;
5228 if (posX - dialogWidth < Math.max(0, parentDimensions.x)) {
5229 posX = Math.max(0, posX);
5230 } else {
5231 posX -= dialogWidth - buttonWidth;
5232 }
5233 }
5234 }
5235 const height = Math.max(dialogHeight, options?.height || 0);
5236 if (posY + height > innerHeight) {
5237 posY = innerHeight - height;
5238 }
5239 if (posY < 0) {
5240 posY = 0;
5241 }
5242 }
5243 posX = MathClamp(posX / innerWidth, 0, 1);
5244 posY = MathClamp(posY / innerHeight, 0, 1);
5245 this.#setPosition(posX, posY);
5246 await this.#overlayManager.open(this.#dialog);
5247 textInput.focus();
5248 }
5249 async #save() {
5250 this.#editor.comment = this.#textInput.value;
5251 this.#finish();
5252 }
5253 get _dialogDimensions() {
5254 const dialog = this.#dialog;
5255 const { style } = dialog;
5256 style.opacity = '0';
5257 style.display = 'block';
5258 const { width, height } = dialog.getBoundingClientRect();
5259 style.opacity = style.display = '';
5260 return shadow(this, '_dialogDimensions', {
5261 dialogWidth: width,
5262 dialogHeight: height,
5263 });
5264 }
5265 #setPosition(x, y) {
5266 this.#dialogX = x;
5267 this.#dialogY = y;
5268 const { style } = this.#dialog;
5269 style.left = `${100 * x}%`;
5270 style.top = `${100 * y}%`;
5271 }
5272 #finish() {
5273 if (!this.#editor) {
5274 return;
5275 }
5276 const edited = this.#textInput.value !== this.#commentText;
5277 this.#eventBus.dispatch('reporttelemetry', {
5278 source: this,
5279 details: {
5280 type: 'comment',
5281 data: {
5282 edited,
5283 },
5284 },
5285 });
5286 this.#editor?.focusCommentButton();
5287 this.#editor = null;
5288 this.#textInput.value = this.#previousText = this.#commentText = '';
5289 this.#overlayManager.closeIfActive(this.#dialog);
5290 this.#textInput.style.height = '';
5291 this.#uiManager?.addEditListeners();
5292 this.#uiManager = null;
5293 }
5294 destroy() {
5295 this.#uiManager = null;
5296 this.#editor = null;
5297 this.#finish();
5298 }
5299}
5300class CommentPopup {
5301 #buttonsContainer = null;
5302 #eventBus;
5303 #commentDialog;
5304 #dateFormat;
5305 #editor = null;
5306 #isLTR;
5307 #container = null;
5308 #text = null;
5309 #time = null;
5310 #prevDragX = 0;
5311 #prevDragY = 0;
5312 #posX = 0;
5313 #posY = 0;
5314 #previousFocusedElement = null;
5315 #selected = false;
5316 #visible = false;
5317 constructor(eventBus, dateFormat, ltr, commentDialog) {
5318 this.#eventBus = eventBus;
5319 this.#dateFormat = dateFormat;
5320 this.#isLTR = ltr;
5321 this.#commentDialog = commentDialog;
5322 this.sidebar = null;
5323 }
5324 get _popupWidth() {
5325 const container = this.#createPopup();
5326 const { style } = container;
5327 style.opacity = '0';
5328 style.display = 'block';
5329 document.body.append(container);
5330 const width = container.getBoundingClientRect().width;
5331 container.remove();
5332 style.opacity = style.display = '';
5333 return shadow(this, '_popupWidth', width);
5334 }
5335 #createPopup() {
5336 if (this.#container) {
5337 return this.#container;
5338 }
5339 const container = (this.#container = document.createElement('div'));
5340 container.className = 'commentPopup';
5341 container.id = 'commentPopup';
5342 container.tabIndex = -1;
5343 container.role = 'dialog';
5344 container.ariaModal = 'false';
5345 container.addEventListener('contextmenu', noContextMenu);
5346 container.addEventListener('keydown', (e) => {
5347 if (e.key === 'Escape') {
5348 this.toggle(this.#editor, true, false);
5349 this.#previousFocusedElement?.focus();
5350 stopEvent(e);
5351 }
5352 });
5353 container.addEventListener('click', () => {
5354 container.focus();
5355 });
5356 const top = document.createElement('div');
5357 top.className = 'commentPopupTop';
5358 const time = (this.#time = document.createElement('time'));
5359 time.className = 'commentPopupTime';
5360 const buttons = (this.#buttonsContainer = document.createElement('div'));
5361 buttons.className = 'commentPopupButtons';
5362 const edit = document.createElement('button');
5363 edit.classList.add('commentPopupEdit', 'toolbarButton');
5364 edit.tabIndex = 0;
5365 edit.setAttribute('data-l10n-id', 'pdfjs-editor-edit-comment-popup-button');
5366 edit.ariaHasPopup = 'dialog';
5367 edit.ariaControlsElements = [this.#commentDialog];
5368 const editLabel = document.createElement('span');
5369 editLabel.setAttribute('data-l10n-id', 'pdfjs-editor-edit-comment-popup-button-label');
5370 edit.append(editLabel);
5371 edit.addEventListener('click', () => {
5372 const editor = this.#editor;
5373 const height = parseFloat(getComputedStyle(this.#text).height);
5374 this.toggle(editor, true, false);
5375 editor.editComment({
5376 height,
5377 });
5378 });
5379 edit.addEventListener('contextmenu', noContextMenu);
5380 const del = document.createElement('button');
5381 del.classList.add('commentPopupDelete', 'toolbarButton');
5382 del.tabIndex = 0;
5383 del.setAttribute('data-l10n-id', 'pdfjs-editor-delete-comment-popup-button');
5384 const delLabel = document.createElement('span');
5385 delLabel.setAttribute('data-l10n-id', 'pdfjs-editor-delete-comment-popup-button-label');
5386 del.append(delLabel);
5387 del.addEventListener('click', () => {
5388 this.#eventBus.dispatch('reporttelemetry', {
5389 source: this,
5390 details: {
5391 type: 'comment',
5392 data: {
5393 deleted: true,
5394 },
5395 },
5396 });
5397 const editor = this.#editor;
5398 const savedData = editor.comment;
5399 this.destroy();
5400 if (savedData?.text) {
5401 editor._uiManager.deleteComment(editor, savedData);
5402 } else {
5403 editor.comment = null;
5404 }
5405 editor.focus();
5406 });
5407 del.addEventListener('contextmenu', noContextMenu);
5408 buttons.append(edit, del);
5409 top.append(time, buttons);
5410 const separator = document.createElement('hr');
5411 const text = (this.#text = document.createElement('div'));
5412 text.className = 'commentPopupText';
5413 container.append(top, separator, text);
5414 let pointerMoveAC;
5415 const cancelDrag = () => {
5416 container.classList.remove('dragging');
5417 pointerMoveAC?.abort();
5418 pointerMoveAC = null;
5419 };
5420 top.addEventListener('pointerdown', (e) => {
5421 if (pointerMoveAC) {
5422 cancelDrag();
5423 return;
5424 }
5425 const { target, clientX, clientY } = e;
5426 if (buttons.contains(target)) {
5427 return;
5428 }
5429 stopEvent(e);
5430 const { width: parentWidth, height: parentHeight } = this.#editor.parentBoundingClientRect;
5431 this.#prevDragX = clientX;
5432 this.#prevDragY = clientY;
5433 pointerMoveAC = new AbortController();
5434 const { signal } = pointerMoveAC;
5435 container.classList.add('dragging');
5436 window.addEventListener(
5437 'pointermove',
5438 (ev) => {
5439 if (!pointerMoveAC) {
5440 return;
5441 }
5442 const { clientX: x, clientY: y } = ev;
5443 this.#setPosition(
5444 this.#posX + (x - this.#prevDragX) / parentWidth,
5445 this.#posY + (y - this.#prevDragY) / parentHeight,
5446 false
5447 );
5448 this.#prevDragX = x;
5449 this.#prevDragY = y;
5450 stopEvent(ev);
5451 },
5452 {
5453 signal,
5454 }
5455 );
5456 window.addEventListener('blur', cancelDrag, {
5457 signal,
5458 });
5459 window.addEventListener(
5460 'pointerup',
5461 (ev) => {
5462 if (pointerMoveAC) {
5463 cancelDrag();
5464 stopEvent(ev);
5465 }
5466 },
5467 {
5468 signal,
5469 }
5470 );
5471 });
5472 return container;
5473 }
5474 updateColor(editor) {
5475 if (this.#editor !== editor || !this.#visible) {
5476 return;
5477 }
5478 const { color, opacity } = editor.getData();
5479 this.#container.style.backgroundColor =
5480 (color && CommentManager._makeCommentColor(color, opacity)) || '';
5481 }
5482 _hide(editor) {
5483 const container = this.#createPopup();
5484 container.classList.toggle('hidden', true);
5485 container.classList.toggle('selected', false);
5486 (editor || this.#editor)?.setCommentButtonStates({
5487 selected: false,
5488 hasPopup: false,
5489 });
5490 this.#editor = null;
5491 this.#selected = false;
5492 this.#visible = false;
5493 this.#text.replaceChildren();
5494 this.sidebar.selectComment(null);
5495 }
5496 toggle(editor, isSelected, visibility = undefined, isEditable = true) {
5497 if (!editor) {
5498 this.destroy();
5499 return;
5500 }
5501 if (isSelected) {
5502 visibility ??= this.#editor === editor ? !this.#selected || !this.#visible : true;
5503 } else {
5504 if (this.#selected) {
5505 return;
5506 }
5507 visibility ??= !this.#visible;
5508 }
5509 if (!visibility) {
5510 this._hide(editor);
5511 return;
5512 }
5513 this.#visible = true;
5514 if (this.#editor !== editor) {
5515 this.#editor?.setCommentButtonStates({
5516 selected: false,
5517 hasPopup: false,
5518 });
5519 }
5520 const container = this.#createPopup();
5521 this.#buttonsContainer.classList.toggle('hidden', !isEditable);
5522 container.classList.toggle('hidden', false);
5523 container.classList.toggle('selected', isSelected);
5524 this.#selected = isSelected;
5525 this.#editor = editor;
5526 editor.setCommentButtonStates({
5527 selected: isSelected,
5528 hasPopup: true,
5529 });
5530 const { contentsObj, richText, creationDate, modificationDate, color, opacity } =
5531 editor.getData();
5532 container.style.backgroundColor =
5533 (color && CommentManager._makeCommentColor(color, opacity)) || '';
5534 this.#text.replaceChildren();
5535 const html =
5536 richText?.str && (!contentsObj?.str || richText.str === contentsObj.str)
5537 ? richText.html
5538 : contentsObj?.str;
5539 if (html) {
5540 renderRichText(
5541 {
5542 html,
5543 dir: contentsObj?.dir || 'auto',
5544 className: 'richText',
5545 },
5546 this.#text
5547 );
5548 }
5549 this.#time.textContent = this.#dateFormat.format(
5550 PDFDateString.toDateObject(modificationDate || creationDate)
5551 );
5552 this.#setPosition(...editor.commentPopupPosition, editor.hasDefaultPopupPosition());
5553 editor.elementBeforePopup.after(container);
5554 container.addEventListener(
5555 'focus',
5556 ({ relatedTarget }) => {
5557 this.#previousFocusedElement = relatedTarget;
5558 },
5559 {
5560 once: true,
5561 }
5562 );
5563 if (isSelected) {
5564 setTimeout(() => container.focus(), 0);
5565 }
5566 }
5567 #setPosition(x, y, correctPosition) {
5568 if (!correctPosition) {
5569 this.#editor.commentPopupPosition = [x, y];
5570 } else {
5571 const parentRect = this.#editor.parentBoundingClientRect;
5572 const widthRatio = this._popupWidth / parentRect.width;
5573 if ((this.#isLTR && x + widthRatio > 1) || (!this.#isLTR && x - widthRatio >= 0)) {
5574 const buttonWidth = this.#editor.commentButtonWidth;
5575 x -= widthRatio - buttonWidth;
5576 }
5577 const margin = 0.01;
5578 if (this.#isLTR) {
5579 x = Math.max(x, -parentRect.x / parentRect.width + margin);
5580 } else {
5581 x = Math.min(
5582 x,
5583 (window.innerWidth - parentRect.x) / parentRect.width - widthRatio - margin
5584 );
5585 }
5586 }
5587 this.#posX = x;
5588 this.#posY = y;
5589 const { style } = this.#container;
5590 style.left = `${100 * x}%`;
5591 style.top = `${100 * y}%`;
5592 }
5593 destroy() {
5594 this._hide();
5595 this.#container?.remove();
5596 this.#container = this.#text = this.#time = null;
5597 this.#prevDragX = this.#prevDragY = Infinity;
5598 this.#posX = this.#posY = 0;
5599 this.#previousFocusedElement = null;
5600 }
5601} // ./web/download_manager.js
5602
5603function download(blobUrl, filename) {
5604 const a = document.createElement('a');
5605 if (!a.click) {
5606 throw new Error('DownloadManager: "a.click()" is not supported.');
5607 }
5608 a.href = blobUrl;
5609 a.target = '_parent';
5610 if ('download' in a) {
5611 a.download = filename;
5612 }
5613 (document.body || document.documentElement).append(a);
5614 a.click();
5615 a.remove();
5616}
5617class DownloadManager {
5618 #openBlobUrls = new WeakMap();
5619 downloadData(data, filename, contentType) {
5620 const blobUrl = URL.createObjectURL(
5621 new Blob([data], {
5622 type: contentType,
5623 })
5624 );
5625 download(blobUrl, filename);
5626 }
5627 openOrDownloadData(data, filename, dest = null) {
5628 const isPdfData = isPdfFile(filename);
5629 const contentType = isPdfData ? 'application/pdf' : '';
5630 if (isPdfData) {
5631 let blobUrl = this.#openBlobUrls.get(data);
5632 if (!blobUrl) {
5633 blobUrl = URL.createObjectURL(
5634 new Blob([data], {
5635 type: contentType,
5636 })
5637 );
5638 this.#openBlobUrls.set(data, blobUrl);
5639 }
5640 let viewerUrl;
5641 viewerUrl = '?file=' + encodeURIComponent(blobUrl + '#' + filename);
5642 if (dest) {
5643 viewerUrl += `#${escape(dest)}`;
5644 }
5645 try {
5646 window.open(viewerUrl);
5647 return true;
5648 } catch (ex) {
5649 console.error('openOrDownloadData:', ex);
5650 URL.revokeObjectURL(blobUrl);
5651 this.#openBlobUrls.delete(data);
5652 }
5653 }
5654 this.downloadData(data, filename, contentType);
5655 return false;
5656 }
5657 download(data, url, filename) {
5658 let blobUrl;
5659 if (data) {
5660 blobUrl = URL.createObjectURL(
5661 new Blob([data], {
5662 type: 'application/pdf',
5663 })
5664 );
5665 } else {
5666 if (!createValidAbsoluteUrl(url, 'http://example.com')) {
5667 console.error(`download - not a valid URL: ${url}`);
5668 return;
5669 }
5670 blobUrl = url + '#pdfjs.action=download';
5671 }
5672 download(blobUrl, filename);
5673 }
5674} // ./web/editor_undo_bar.js
5675
5676class EditorUndoBar {
5677 #closeButton = null;
5678 #container;
5679 #eventBus = null;
5680 #focusTimeout = null;
5681 #initController = null;
5682 isOpen = false;
5683 #message;
5684 #showController = null;
5685 #undoButton;
5686 static #l10nMessages = Object.freeze({
5687 highlight: 'pdfjs-editor-undo-bar-message-highlight',
5688 freetext: 'pdfjs-editor-undo-bar-message-freetext',
5689 stamp: 'pdfjs-editor-undo-bar-message-stamp',
5690 ink: 'pdfjs-editor-undo-bar-message-ink',
5691 signature: 'pdfjs-editor-undo-bar-message-signature',
5692 comment: 'pdfjs-editor-undo-bar-message-comment',
5693 _multiple: 'pdfjs-editor-undo-bar-message-multiple',
5694 });
5695 constructor({ container, message, undoButton, closeButton }, eventBus) {
5696 this.#container = container;
5697 this.#message = message;
5698 this.#undoButton = undoButton;
5699 this.#closeButton = closeButton;
5700 this.#eventBus = eventBus;
5701 }
5702 destroy() {
5703 this.#initController?.abort();
5704 this.#initController = null;
5705 this.hide();
5706 }
5707 show(undoAction, messageData) {
5708 if (!this.#initController) {
5709 this.#initController = new AbortController();
5710 const opts = {
5711 signal: this.#initController.signal,
5712 };
5713 const boundHide = this.hide.bind(this);
5714 this.#container.addEventListener('contextmenu', noContextMenu, opts);
5715 this.#closeButton.addEventListener('click', boundHide, opts);
5716 this.#eventBus._on('beforeprint', boundHide, opts);
5717 this.#eventBus._on('download', boundHide, opts);
5718 }
5719 this.hide();
5720 if (typeof messageData === 'string') {
5721 this.#message.setAttribute('data-l10n-id', EditorUndoBar.#l10nMessages[messageData]);
5722 } else {
5723 this.#message.setAttribute('data-l10n-id', EditorUndoBar.#l10nMessages._multiple);
5724 this.#message.setAttribute(
5725 'data-l10n-args',
5726 JSON.stringify({
5727 count: messageData,
5728 })
5729 );
5730 }
5731 this.isOpen = true;
5732 this.#container.hidden = false;
5733 this.#showController = new AbortController();
5734 this.#undoButton.addEventListener(
5735 'click',
5736 () => {
5737 undoAction();
5738 this.hide();
5739 },
5740 {
5741 signal: this.#showController.signal,
5742 }
5743 );
5744 this.#focusTimeout = setTimeout(() => {
5745 this.#container.focus();
5746 this.#focusTimeout = null;
5747 }, 100);
5748 }
5749 hide() {
5750 if (!this.isOpen) {
5751 return;
5752 }
5753 this.isOpen = false;
5754 this.#container.hidden = true;
5755 this.#showController?.abort();
5756 this.#showController = null;
5757 if (this.#focusTimeout) {
5758 clearTimeout(this.#focusTimeout);
5759 this.#focusTimeout = null;
5760 }
5761 }
5762} // ./web/overlay_manager.js
5763
5764class OverlayManager {
5765 #overlays = new WeakMap();
5766 #active = null;
5767 get active() {
5768 return this.#active;
5769 }
5770 async register(dialog, canForceClose = false) {
5771 if (typeof dialog !== 'object') {
5772 throw new Error('Not enough parameters.');
5773 } else if (this.#overlays.has(dialog)) {
5774 throw new Error('The overlay is already registered.');
5775 }
5776 this.#overlays.set(dialog, {
5777 canForceClose,
5778 });
5779 dialog.addEventListener('cancel', ({ target }) => {
5780 if (this.#active === target) {
5781 this.#active = null;
5782 }
5783 });
5784 }
5785 async open(dialog) {
5786 if (!this.#overlays.has(dialog)) {
5787 throw new Error('The overlay does not exist.');
5788 } else if (this.#active) {
5789 if (this.#active === dialog) {
5790 throw new Error('The overlay is already active.');
5791 } else if (this.#overlays.get(dialog).canForceClose) {
5792 await this.close();
5793 } else {
5794 throw new Error('Another overlay is currently active.');
5795 }
5796 }
5797 this.#active = dialog;
5798 dialog.showModal();
5799 }
5800 async close(dialog = this.#active) {
5801 if (!this.#overlays.has(dialog)) {
5802 throw new Error('The overlay does not exist.');
5803 } else if (!this.#active) {
5804 throw new Error('The overlay is currently not active.');
5805 } else if (this.#active !== dialog) {
5806 throw new Error('Another overlay is currently active.');
5807 }
5808 dialog.close();
5809 this.#active = null;
5810 }
5811 async closeIfActive(dialog) {
5812 if (this.#active === dialog) {
5813 await this.close(dialog);
5814 }
5815 }
5816} // ./web/password_prompt.js
5817
5818class PasswordPrompt {
5819 #activeCapability = null;
5820 #updateCallback = null;
5821 #reason = null;
5822 constructor(options, overlayManager, isViewerEmbedded = false) {
5823 this.dialog = options.dialog;
5824 this.label = options.label;
5825 this.input = options.input;
5826 this.submitButton = options.submitButton;
5827 this.cancelButton = options.cancelButton;
5828 this.overlayManager = overlayManager;
5829 this._isViewerEmbedded = isViewerEmbedded;
5830 this.submitButton.addEventListener('click', this.#verify.bind(this));
5831 this.cancelButton.addEventListener('click', this.close.bind(this));
5832 this.input.addEventListener('keydown', (e) => {
5833 if (e.keyCode === 13) {
5834 this.#verify();
5835 }
5836 });
5837 this.overlayManager.register(this.dialog, true);
5838 this.dialog.addEventListener('close', this.#cancel.bind(this));
5839 }
5840 async open() {
5841 await this.#activeCapability?.promise;
5842 this.#activeCapability = Promise.withResolvers();
5843 try {
5844 await this.overlayManager.open(this.dialog);
5845 } catch (ex) {
5846 this.#activeCapability.resolve();
5847 throw ex;
5848 }
5849 const passwordIncorrect = this.#reason === PasswordResponses.INCORRECT_PASSWORD;
5850 if (!this._isViewerEmbedded || passwordIncorrect) {
5851 this.input.focus();
5852 }
5853 this.label.setAttribute(
5854 'data-l10n-id',
5855 passwordIncorrect ? 'pdfjs-password-invalid' : 'pdfjs-password-label'
5856 );
5857 }
5858 async close() {
5859 this.overlayManager.closeIfActive(this.dialog);
5860 }
5861 #verify() {
5862 const password = this.input.value;
5863 if (password?.length > 0) {
5864 this.#invokeCallback(password);
5865 }
5866 }
5867 #cancel() {
5868 this.#invokeCallback(new Error('PasswordPrompt cancelled.'));
5869 this.#activeCapability.resolve();
5870 }
5871 #invokeCallback(password) {
5872 if (!this.#updateCallback) {
5873 return;
5874 }
5875 this.close();
5876 this.input.value = '';
5877 this.#updateCallback(password);
5878 this.#updateCallback = null;
5879 }
5880 async setUpdateCallback(updateCallback, reason) {
5881 if (this.#activeCapability) {
5882 await this.#activeCapability.promise;
5883 }
5884 this.#updateCallback = updateCallback;
5885 this.#reason = reason;
5886 }
5887} // ./web/base_tree_viewer.js
5888
5889const TREEITEM_OFFSET_TOP = -100;
5890const TREEITEM_SELECTED_CLASS = 'selected';
5891class BaseTreeViewer {
5892 constructor(options) {
5893 this.container = options.container;
5894 this.eventBus = options.eventBus;
5895 this._l10n = options.l10n;
5896 this.reset();
5897 }
5898 reset() {
5899 this._pdfDocument = null;
5900 this._lastToggleIsShow = true;
5901 this._currentTreeItem = null;
5902 this.container.replaceChildren();
5903 this.container.classList.remove('withNesting');
5904 }
5905 _dispatchEvent(count) {
5906 throw new Error('Not implemented: _dispatchEvent');
5907 }
5908 _bindLink(element, params) {
5909 throw new Error('Not implemented: _bindLink');
5910 }
5911 _normalizeTextContent(str) {
5912 return removeNullCharacters(str, true) || '\u2013';
5913 }
5914 _addToggleButton(div, hidden = false) {
5915 const toggler = document.createElement('div');
5916 toggler.className = 'treeItemToggler';
5917 if (hidden) {
5918 toggler.classList.add('treeItemsHidden');
5919 }
5920 div.prepend(toggler);
5921 }
5922 _toggleTreeItem(root, show = false) {
5923 this._l10n.pause();
5924 this._lastToggleIsShow = show;
5925 for (const toggler of root.querySelectorAll('.treeItemToggler')) {
5926 toggler.classList.toggle('treeItemsHidden', !show);
5927 }
5928 this._l10n.resume();
5929 }
5930 _toggleAllTreeItems() {
5931 this._toggleTreeItem(this.container, !this._lastToggleIsShow);
5932 }
5933 _finishRendering(fragment, count, hasAnyNesting = false) {
5934 if (hasAnyNesting) {
5935 this.container.classList.add('withNesting');
5936 this._lastToggleIsShow = !fragment.querySelector('.treeItemsHidden');
5937 this.container.addEventListener('click', (e) => {
5938 const { target } = e;
5939 if (!target.classList.contains('treeItemToggler')) {
5940 return;
5941 }
5942 stopEvent(e);
5943 target.classList.toggle('treeItemsHidden');
5944 if (e.shiftKey) {
5945 const shouldShowAll = !target.classList.contains('treeItemsHidden');
5946 this._toggleTreeItem(this.container, shouldShowAll);
5947 }
5948 });
5949 }
5950 this._l10n.pause();
5951 this.container.append(fragment);
5952 this._l10n.resume();
5953 this._dispatchEvent(count);
5954 }
5955 render(params) {
5956 throw new Error('Not implemented: render');
5957 }
5958 _updateCurrentTreeItem(treeItem = null) {
5959 if (this._currentTreeItem) {
5960 this._currentTreeItem.classList.remove(TREEITEM_SELECTED_CLASS);
5961 this._currentTreeItem = null;
5962 }
5963 if (treeItem) {
5964 treeItem.classList.add(TREEITEM_SELECTED_CLASS);
5965 this._currentTreeItem = treeItem;
5966 }
5967 }
5968 _scrollToCurrentTreeItem(treeItem) {
5969 if (!treeItem) {
5970 return;
5971 }
5972 this._l10n.pause();
5973 let currentNode = treeItem.parentNode;
5974 while (currentNode && currentNode !== this.container) {
5975 if (currentNode.classList.contains('treeItem')) {
5976 const toggler = currentNode.firstElementChild;
5977 toggler?.classList.remove('treeItemsHidden');
5978 }
5979 currentNode = currentNode.parentNode;
5980 }
5981 this._l10n.resume();
5982 this._updateCurrentTreeItem(treeItem);
5983 this.container.scrollTo(treeItem.offsetLeft, treeItem.offsetTop + TREEITEM_OFFSET_TOP);
5984 }
5985} // ./web/pdf_attachment_viewer.js
5986
5987class PDFAttachmentViewer extends BaseTreeViewer {
5988 constructor(options) {
5989 super(options);
5990 this.downloadManager = options.downloadManager;
5991 this.eventBus._on('fileattachmentannotation', this.#appendAttachment.bind(this));
5992 }
5993 reset(keepRenderedCapability = false) {
5994 super.reset();
5995 this._attachments = null;
5996 if (!keepRenderedCapability) {
5997 this._renderedCapability = Promise.withResolvers();
5998 }
5999 this._pendingDispatchEvent = false;
6000 }
6001 async _dispatchEvent(attachmentsCount) {
6002 this._renderedCapability.resolve();
6003 if (attachmentsCount === 0 && !this._pendingDispatchEvent) {
6004 this._pendingDispatchEvent = true;
6005 await waitOnEventOrTimeout({
6006 target: this.eventBus,
6007 name: 'annotationlayerrendered',
6008 delay: 1000,
6009 });
6010 if (!this._pendingDispatchEvent) {
6011 return;
6012 }
6013 }
6014 this._pendingDispatchEvent = false;
6015 this.eventBus.dispatch('attachmentsloaded', {
6016 source: this,
6017 attachmentsCount,
6018 });
6019 }
6020 _bindLink(element, { content, description, filename }) {
6021 if (description) {
6022 element.title = description;
6023 }
6024 element.onclick = () => {
6025 this.downloadManager.openOrDownloadData(content, filename);
6026 return false;
6027 };
6028 }
6029 render({ attachments, keepRenderedCapability = false }) {
6030 if (this._attachments) {
6031 this.reset(keepRenderedCapability);
6032 }
6033 this._attachments = attachments || null;
6034 if (!attachments) {
6035 this._dispatchEvent(0);
6036 return;
6037 }
6038 const fragment = document.createDocumentFragment();
6039 const ul = document.createElement('ul');
6040 fragment.append(ul);
6041 let attachmentsCount = 0;
6042 for (const name in attachments) {
6043 const item = attachments[name];
6044 const li = document.createElement('li');
6045 ul.append(li);
6046 const element = document.createElement('a');
6047 li.append(element);
6048 this._bindLink(element, item);
6049 element.textContent = this._normalizeTextContent(item.filename);
6050 attachmentsCount++;
6051 }
6052 this._finishRendering(fragment, attachmentsCount);
6053 }
6054 #appendAttachment(item) {
6055 const renderedPromise = this._renderedCapability.promise;
6056 renderedPromise.then(() => {
6057 if (renderedPromise !== this._renderedCapability.promise) {
6058 return;
6059 }
6060 const attachments = this._attachments || Object.create(null);
6061 for (const name in attachments) {
6062 if (item.filename === name) {
6063 return;
6064 }
6065 }
6066 attachments[item.filename] = item;
6067 this.render({
6068 attachments,
6069 keepRenderedCapability: true,
6070 });
6071 });
6072 }
6073} // ./web/grab_to_pan.js
6074
6075const CSS_CLASS_GRAB = 'grab-to-pan-grab';
6076class GrabToPan {
6077 #activateAC = null;
6078 #mouseDownAC = null;
6079 #scrollAC = null;
6080 constructor({ element }) {
6081 this.element = element;
6082 this.document = element.ownerDocument;
6083 const overlay = (this.overlay = document.createElement('div'));
6084 overlay.className = 'grab-to-pan-grabbing';
6085 }
6086 activate() {
6087 if (!this.#activateAC) {
6088 this.#activateAC = new AbortController();
6089 this.element.addEventListener('mousedown', this.#onMouseDown.bind(this), {
6090 capture: true,
6091 signal: this.#activateAC.signal,
6092 });
6093 this.element.classList.add(CSS_CLASS_GRAB);
6094 }
6095 }
6096 deactivate() {
6097 if (this.#activateAC) {
6098 this.#activateAC.abort();
6099 this.#activateAC = null;
6100 this.#endPan();
6101 this.element.classList.remove(CSS_CLASS_GRAB);
6102 }
6103 }
6104 toggle() {
6105 if (this.#activateAC) {
6106 this.deactivate();
6107 } else {
6108 this.activate();
6109 }
6110 }
6111 ignoreTarget(node) {
6112 return node.matches('a[href], a[href] *, input, textarea, button, button *, select, option');
6113 }
6114 #onMouseDown(event) {
6115 if (event.button !== 0 || this.ignoreTarget(event.target)) {
6116 return;
6117 }
6118 if (event.originalTarget) {
6119 try {
6120 event.originalTarget.tagName;
6121 } catch {
6122 return;
6123 }
6124 }
6125 this.scrollLeftStart = this.element.scrollLeft;
6126 this.scrollTopStart = this.element.scrollTop;
6127 this.clientXStart = event.clientX;
6128 this.clientYStart = event.clientY;
6129 this.#mouseDownAC = new AbortController();
6130 const boundEndPan = this.#endPan.bind(this),
6131 mouseOpts = {
6132 capture: true,
6133 signal: this.#mouseDownAC.signal,
6134 };
6135 this.document.addEventListener('mousemove', this.#onMouseMove.bind(this), mouseOpts);
6136 this.document.addEventListener('mouseup', boundEndPan, mouseOpts);
6137 this.#scrollAC = new AbortController();
6138 this.element.addEventListener('scroll', boundEndPan, {
6139 capture: true,
6140 signal: this.#scrollAC.signal,
6141 });
6142 stopEvent(event);
6143 const focusedElement = document.activeElement;
6144 if (focusedElement && !focusedElement.contains(event.target)) {
6145 focusedElement.blur();
6146 }
6147 }
6148 #onMouseMove(event) {
6149 this.#scrollAC?.abort();
6150 this.#scrollAC = null;
6151 if (!(event.buttons & 1)) {
6152 this.#endPan();
6153 return;
6154 }
6155 const xDiff = event.clientX - this.clientXStart;
6156 const yDiff = event.clientY - this.clientYStart;
6157 this.element.scrollTo({
6158 top: this.scrollTopStart - yDiff,
6159 left: this.scrollLeftStart - xDiff,
6160 behavior: 'instant',
6161 });
6162 if (!this.overlay.parentNode) {
6163 document.body.append(this.overlay);
6164 }
6165 }
6166 #endPan() {
6167 this.#mouseDownAC?.abort();
6168 this.#mouseDownAC = null;
6169 this.#scrollAC?.abort();
6170 this.#scrollAC = null;
6171 this.overlay.remove();
6172 }
6173} // ./web/pdf_cursor_tools.js
6174
6175class PDFCursorTools {
6176 #active = CursorTool.SELECT;
6177 #prevActive = null;
6178 constructor({ container, eventBus, cursorToolOnLoad = CursorTool.SELECT }) {
6179 this.container = container;
6180 this.eventBus = eventBus;
6181 this.#addEventListeners();
6182 Promise.resolve().then(() => {
6183 this.switchTool(cursorToolOnLoad);
6184 });
6185 }
6186 get activeTool() {
6187 return this.#active;
6188 }
6189 switchTool(tool) {
6190 if (this.#prevActive !== null) {
6191 return;
6192 }
6193 this.#switchTool(tool);
6194 }
6195 #switchTool(tool, disabled = false) {
6196 if (tool === this.#active) {
6197 if (this.#prevActive !== null) {
6198 this.eventBus.dispatch('cursortoolchanged', {
6199 source: this,
6200 tool,
6201 disabled,
6202 });
6203 }
6204 return;
6205 }
6206 const disableActiveTool = () => {
6207 switch (this.#active) {
6208 case CursorTool.SELECT:
6209 break;
6210 case CursorTool.HAND:
6211 this._handTool.deactivate();
6212 break;
6213 case CursorTool.ZOOM:
6214 }
6215 };
6216 switch (tool) {
6217 case CursorTool.SELECT:
6218 disableActiveTool();
6219 break;
6220 case CursorTool.HAND:
6221 disableActiveTool();
6222 this._handTool.activate();
6223 break;
6224 case CursorTool.ZOOM:
6225 default:
6226 console.error(`switchTool: "${tool}" is an unsupported value.`);
6227 return;
6228 }
6229 this.#active = tool;
6230 this.eventBus.dispatch('cursortoolchanged', {
6231 source: this,
6232 tool,
6233 disabled,
6234 });
6235 }
6236 #addEventListeners() {
6237 this.eventBus._on('switchcursortool', (evt) => {
6238 if (!evt.reset) {
6239 this.switchTool(evt.tool);
6240 } else if (this.#prevActive !== null) {
6241 annotationEditorMode = AnnotationEditorType.NONE;
6242 presentationModeState = PresentationModeState.NORMAL;
6243 enableActive();
6244 }
6245 });
6246 let annotationEditorMode = AnnotationEditorType.NONE,
6247 presentationModeState = PresentationModeState.NORMAL;
6248 const disableActive = () => {
6249 this.#prevActive ??= this.#active;
6250 this.#switchTool(CursorTool.SELECT, true);
6251 };
6252 const enableActive = () => {
6253 if (
6254 this.#prevActive !== null &&
6255 annotationEditorMode === AnnotationEditorType.NONE &&
6256 presentationModeState === PresentationModeState.NORMAL
6257 ) {
6258 this.#switchTool(this.#prevActive);
6259 this.#prevActive = null;
6260 }
6261 };
6262 this.eventBus._on('annotationeditormodechanged', ({ mode }) => {
6263 annotationEditorMode = mode;
6264 if (mode === AnnotationEditorType.NONE) {
6265 enableActive();
6266 } else {
6267 disableActive();
6268 }
6269 });
6270 this.eventBus._on('presentationmodechanged', ({ state }) => {
6271 presentationModeState = state;
6272 if (state === PresentationModeState.NORMAL) {
6273 enableActive();
6274 } else if (state === PresentationModeState.FULLSCREEN) {
6275 disableActive();
6276 }
6277 });
6278 }
6279 get _handTool() {
6280 return shadow(
6281 this,
6282 '_handTool',
6283 new GrabToPan({
6284 element: this.container,
6285 })
6286 );
6287 }
6288} // ./web/pdf_document_properties.js
6289
6290const NON_METRIC_LOCALES = ['en-us', 'en-lr', 'my'];
6291const US_PAGE_NAMES = {
6292 '8.5x11': 'pdfjs-document-properties-page-size-name-letter',
6293 '8.5x14': 'pdfjs-document-properties-page-size-name-legal',
6294};
6295const METRIC_PAGE_NAMES = {
6296 '297x420': 'pdfjs-document-properties-page-size-name-a-three',
6297 '210x297': 'pdfjs-document-properties-page-size-name-a-four',
6298};
6299function getPageName(size, isPortrait, pageNames) {
6300 const width = isPortrait ? size.width : size.height;
6301 const height = isPortrait ? size.height : size.width;
6302 return pageNames[`${width}x${height}`];
6303}
6304class PDFDocumentProperties {
6305 #fieldData = null;
6306 constructor(
6307 { dialog, fields, closeButton },
6308 overlayManager,
6309 eventBus,
6310 l10n,
6311 fileNameLookup,
6312 titleLookup
6313 ) {
6314 this.dialog = dialog;
6315 this.fields = fields;
6316 this.overlayManager = overlayManager;
6317 this.l10n = l10n;
6318 this._fileNameLookup = fileNameLookup;
6319 this._titleLookup = titleLookup;
6320 this.#reset();
6321 closeButton.addEventListener('click', this.close.bind(this));
6322 this.overlayManager.register(this.dialog);
6323 eventBus._on('pagechanging', (evt) => {
6324 this._currentPageNumber = evt.pageNumber;
6325 });
6326 eventBus._on('rotationchanging', (evt) => {
6327 this._pagesRotation = evt.pagesRotation;
6328 });
6329 }
6330 async open() {
6331 await Promise.all([
6332 this.overlayManager.open(this.dialog),
6333 this._dataAvailableCapability.promise,
6334 ]);
6335 const currentPageNumber = this._currentPageNumber;
6336 const pagesRotation = this._pagesRotation;
6337 if (
6338 this.#fieldData &&
6339 currentPageNumber === this.#fieldData._currentPageNumber &&
6340 pagesRotation === this.#fieldData._pagesRotation
6341 ) {
6342 this.#updateUI();
6343 return;
6344 }
6345 const [{ info, metadata, contentLength }, pdfPage] = await Promise.all([
6346 this.pdfDocument.getMetadata(),
6347 this.pdfDocument.getPage(currentPageNumber),
6348 ]);
6349 const [fileName, fileSize, title, creationDate, modificationDate, pageSize, isLinearized] =
6350 await Promise.all([
6351 this._fileNameLookup(),
6352 this.#parseFileSize(contentLength),
6353 this._titleLookup(),
6354 this.#parseDate(metadata?.get('xmp:createdate'), info.CreationDate),
6355 this.#parseDate(metadata?.get('xmp:modifydate'), info.ModDate),
6356 this.#parsePageSize(getPageSizeInches(pdfPage), pagesRotation),
6357 this.#parseLinearization(info.IsLinearized),
6358 ]);
6359 this.#fieldData = Object.freeze({
6360 fileName,
6361 fileSize,
6362 title,
6363 author: metadata?.get('dc:creator')?.join('\n') || info.Author,
6364 subject: metadata?.get('dc:subject')?.join('\n') || info.Subject,
6365 keywords: metadata?.get('pdf:keywords') || info.Keywords,
6366 creationDate,
6367 modificationDate,
6368 creator: metadata?.get('xmp:creatortool') || info.Creator,
6369 producer: metadata?.get('pdf:producer') || info.Producer,
6370 version: info.PDFFormatVersion,
6371 pageCount: this.pdfDocument.numPages,
6372 pageSize,
6373 linearized: isLinearized,
6374 _currentPageNumber: currentPageNumber,
6375 _pagesRotation: pagesRotation,
6376 });
6377 this.#updateUI();
6378 const { length } = await this.pdfDocument.getDownloadInfo();
6379 if (contentLength === length) {
6380 return;
6381 }
6382 const data = Object.assign(Object.create(null), this.#fieldData);
6383 data.fileSize = await this.#parseFileSize(length);
6384 this.#fieldData = Object.freeze(data);
6385 this.#updateUI();
6386 }
6387 async close() {
6388 this.overlayManager.close(this.dialog);
6389 }
6390 setDocument(pdfDocument) {
6391 if (this.pdfDocument) {
6392 this.#reset();
6393 this.#updateUI();
6394 }
6395 if (!pdfDocument) {
6396 return;
6397 }
6398 this.pdfDocument = pdfDocument;
6399 this._dataAvailableCapability.resolve();
6400 }
6401 #reset() {
6402 this.pdfDocument = null;
6403 this.#fieldData = null;
6404 this._dataAvailableCapability = Promise.withResolvers();
6405 this._currentPageNumber = 1;
6406 this._pagesRotation = 0;
6407 }
6408 #updateUI() {
6409 if (this.#fieldData && this.overlayManager.active !== this.dialog) {
6410 return;
6411 }
6412 for (const id in this.fields) {
6413 const content = this.#fieldData?.[id];
6414 this.fields[id].textContent = content || content === 0 ? content : '-';
6415 }
6416 }
6417 async #parseFileSize(b = 0) {
6418 const kb = b / 1024,
6419 mb = kb / 1024;
6420 return kb
6421 ? this.l10n.get(
6422 mb >= 1 ? 'pdfjs-document-properties-size-mb' : 'pdfjs-document-properties-size-kb',
6423 {
6424 mb,
6425 kb,
6426 b,
6427 }
6428 )
6429 : undefined;
6430 }
6431 async #parsePageSize(pageSizeInches, pagesRotation) {
6432 if (!pageSizeInches) {
6433 return undefined;
6434 }
6435 if (pagesRotation % 180 !== 0) {
6436 pageSizeInches = {
6437 width: pageSizeInches.height,
6438 height: pageSizeInches.width,
6439 };
6440 }
6441 const isPortrait = isPortraitOrientation(pageSizeInches),
6442 nonMetric = NON_METRIC_LOCALES.includes(this.l10n.getLanguage());
6443 let sizeInches = {
6444 width: Math.round(pageSizeInches.width * 100) / 100,
6445 height: Math.round(pageSizeInches.height * 100) / 100,
6446 };
6447 let sizeMillimeters = {
6448 width: Math.round(pageSizeInches.width * 25.4 * 10) / 10,
6449 height: Math.round(pageSizeInches.height * 25.4 * 10) / 10,
6450 };
6451 let nameId =
6452 getPageName(sizeInches, isPortrait, US_PAGE_NAMES) ||
6453 getPageName(sizeMillimeters, isPortrait, METRIC_PAGE_NAMES);
6454 if (
6455 !nameId &&
6456 !(Number.isInteger(sizeMillimeters.width) && Number.isInteger(sizeMillimeters.height))
6457 ) {
6458 const exactMillimeters = {
6459 width: pageSizeInches.width * 25.4,
6460 height: pageSizeInches.height * 25.4,
6461 };
6462 const intMillimeters = {
6463 width: Math.round(sizeMillimeters.width),
6464 height: Math.round(sizeMillimeters.height),
6465 };
6466 if (
6467 Math.abs(exactMillimeters.width - intMillimeters.width) < 0.1 &&
6468 Math.abs(exactMillimeters.height - intMillimeters.height) < 0.1
6469 ) {
6470 nameId = getPageName(intMillimeters, isPortrait, METRIC_PAGE_NAMES);
6471 if (nameId) {
6472 sizeInches = {
6473 width: Math.round((intMillimeters.width / 25.4) * 100) / 100,
6474 height: Math.round((intMillimeters.height / 25.4) * 100) / 100,
6475 };
6476 sizeMillimeters = intMillimeters;
6477 }
6478 }
6479 }
6480 const [{ width, height }, unit, name, orientation] = await Promise.all([
6481 nonMetric ? sizeInches : sizeMillimeters,
6482 this.l10n.get(
6483 nonMetric
6484 ? 'pdfjs-document-properties-page-size-unit-inches'
6485 : 'pdfjs-document-properties-page-size-unit-millimeters'
6486 ),
6487 nameId && this.l10n.get(nameId),
6488 this.l10n.get(
6489 isPortrait
6490 ? 'pdfjs-document-properties-page-size-orientation-portrait'
6491 : 'pdfjs-document-properties-page-size-orientation-landscape'
6492 ),
6493 ]);
6494 return this.l10n.get(
6495 name
6496 ? 'pdfjs-document-properties-page-size-dimension-name-string'
6497 : 'pdfjs-document-properties-page-size-dimension-string',
6498 {
6499 width,
6500 height,
6501 unit,
6502 name,
6503 orientation,
6504 }
6505 );
6506 }
6507 async #parseDate(metadataDate, infoDate) {
6508 const dateObj = Date.parse(metadataDate) || PDFDateString.toDateObject(infoDate);
6509 return dateObj
6510 ? this.l10n.get('pdfjs-document-properties-date-time-string', {
6511 dateObj: dateObj.valueOf(),
6512 })
6513 : undefined;
6514 }
6515 #parseLinearization(isLinearized) {
6516 return this.l10n.get(
6517 isLinearized
6518 ? 'pdfjs-document-properties-linearized-yes'
6519 : 'pdfjs-document-properties-linearized-no'
6520 );
6521 }
6522} // ./web/pdf_find_utils.js
6523
6524const CharacterType = {
6525 SPACE: 0,
6526 ALPHA_LETTER: 1,
6527 PUNCT: 2,
6528 HAN_LETTER: 3,
6529 KATAKANA_LETTER: 4,
6530 HIRAGANA_LETTER: 5,
6531 HALFWIDTH_KATAKANA_LETTER: 6,
6532 THAI_LETTER: 7,
6533};
6534function isAlphabeticalScript(charCode) {
6535 return charCode < 0x2e80;
6536}
6537function isAscii(charCode) {
6538 return (charCode & 0xff80) === 0;
6539}
6540function isAsciiAlpha(charCode) {
6541 return (charCode >= 0x61 && charCode <= 0x7a) || (charCode >= 0x41 && charCode <= 0x5a);
6542}
6543function isAsciiDigit(charCode) {
6544 return charCode >= 0x30 && charCode <= 0x39;
6545}
6546function isAsciiSpace(charCode) {
6547 return charCode === 0x20 || charCode === 0x09 || charCode === 0x0d || charCode === 0x0a;
6548}
6549function isHan(charCode) {
6550 return (charCode >= 0x3400 && charCode <= 0x9fff) || (charCode >= 0xf900 && charCode <= 0xfaff);
6551}
6552function isKatakana(charCode) {
6553 return charCode >= 0x30a0 && charCode <= 0x30ff;
6554}
6555function isHiragana(charCode) {
6556 return charCode >= 0x3040 && charCode <= 0x309f;
6557}
6558function isHalfwidthKatakana(charCode) {
6559 return charCode >= 0xff60 && charCode <= 0xff9f;
6560}
6561function isThai(charCode) {
6562 return (charCode & 0xff80) === 0x0e00;
6563}
6564function getCharacterType(charCode) {
6565 if (isAlphabeticalScript(charCode)) {
6566 if (isAscii(charCode)) {
6567 if (isAsciiSpace(charCode)) {
6568 return CharacterType.SPACE;
6569 } else if (isAsciiAlpha(charCode) || isAsciiDigit(charCode) || charCode === 0x5f) {
6570 return CharacterType.ALPHA_LETTER;
6571 }
6572 return CharacterType.PUNCT;
6573 } else if (isThai(charCode)) {
6574 return CharacterType.THAI_LETTER;
6575 } else if (charCode === 0xa0) {
6576 return CharacterType.SPACE;
6577 }
6578 return CharacterType.ALPHA_LETTER;
6579 }
6580 if (isHan(charCode)) {
6581 return CharacterType.HAN_LETTER;
6582 } else if (isKatakana(charCode)) {
6583 return CharacterType.KATAKANA_LETTER;
6584 } else if (isHiragana(charCode)) {
6585 return CharacterType.HIRAGANA_LETTER;
6586 } else if (isHalfwidthKatakana(charCode)) {
6587 return CharacterType.HALFWIDTH_KATAKANA_LETTER;
6588 }
6589 return CharacterType.ALPHA_LETTER;
6590}
6591let NormalizeWithNFKC;
6592function getNormalizeWithNFKC() {
6593 return NormalizeWithNFKC;
6594} // ./web/pdf_find_controller.js
6595
6596const FindState = {
6597 FOUND: 0,
6598 NOT_FOUND: 1,
6599 WRAPPED: 2,
6600 PENDING: 3,
6601};
6602const FIND_TIMEOUT = 250;
6603const MATCH_SCROLL_OFFSET_TOP = -50;
6604const CHARACTERS_TO_NORMALIZE = {
6605 '\u2010': '-',
6606 '\u2018': "'",
6607 '\u2019': "'",
6608 '\u201A': "'",
6609 '\u201B': "'",
6610 '\u201C': '"',
6611 '\u201D': '"',
6612 '\u201E': '"',
6613 '\u201F': '"',
6614 '\u00BC': '1/4',
6615 '\u00BD': '1/2',
6616 '\u00BE': '3/4',
6617};
6618const DIACRITICS_EXCEPTION = new Set([
6619 0x3099, 0x309a, 0x094d, 0x09cd, 0x0a4d, 0x0acd, 0x0b4d, 0x0bcd, 0x0c4d, 0x0ccd, 0x0d3b, 0x0d3c,
6620 0x0d4d, 0x0dca, 0x0e3a, 0x0eba, 0x0f84, 0x1039, 0x103a, 0x1714, 0x1734, 0x17d2, 0x1a60, 0x1b44,
6621 0x1baa, 0x1bab, 0x1bf2, 0x1bf3, 0x2d7f, 0xa806, 0xa82c, 0xa8c4, 0xa953, 0xa9c0, 0xaaf6, 0xabed,
6622 0x0c56, 0x0f71, 0x0f72, 0x0f7a, 0x0f7b, 0x0f7c, 0x0f7d, 0x0f80, 0x0f74,
6623]);
6624let DIACRITICS_EXCEPTION_STR;
6625const DIACRITICS_REG_EXP = /\p{M}+/gu;
6626const SPECIAL_CHARS_REG_EXP = /([+^$|])|(\p{P}+)|(\s+)|(\p{M})|(\p{L})/gu;
6627const NOT_DIACRITIC_FROM_END_REG_EXP = /([^\p{M}])\p{M}*$/u;
6628const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*([^\p{M}])/u;
6629const SYLLABLES_REG_EXP = /[\uAC00-\uD7AF\uFA6C\uFACF-\uFAD1\uFAD5-\uFAD7]+/g;
6630const SYLLABLES_LENGTHS = new Map();
6631const FIRST_CHAR_SYLLABLES_REG_EXP =
6632 '[\\u1100-\\u1112\\ud7a4-\\ud7af\\ud84a\\ud84c\\ud850\\ud854\\ud857\\ud85f]';
6633const NFKC_CHARS_TO_NORMALIZE = new Map();
6634let noSyllablesRegExp = null;
6635let withSyllablesRegExp = null;
6636function normalize(text, options = {}) {
6637 const syllablePositions = [];
6638 let m;
6639 while ((m = SYLLABLES_REG_EXP.exec(text)) !== null) {
6640 let { index } = m;
6641 for (const char of m[0]) {
6642 let len = SYLLABLES_LENGTHS.get(char);
6643 if (!len) {
6644 len = char.normalize('NFD').length;
6645 SYLLABLES_LENGTHS.set(char, len);
6646 }
6647 syllablePositions.push([len, index++]);
6648 }
6649 }
6650 const hasSyllables = syllablePositions.length > 0;
6651 const ignoreDashEOL = options.ignoreDashEOL ?? false;
6652 let normalizationRegex;
6653 if (!hasSyllables && noSyllablesRegExp) {
6654 normalizationRegex = noSyllablesRegExp;
6655 } else if (hasSyllables && withSyllablesRegExp) {
6656 normalizationRegex = withSyllablesRegExp;
6657 } else {
6658 const replace = Object.keys(CHARACTERS_TO_NORMALIZE).join('');
6659 const toNormalizeWithNFKC = getNormalizeWithNFKC();
6660 const CJK = '(?:\\p{Ideographic}|[\u3040-\u30FF])';
6661 const HKDiacritics = '(?:\u3099|\u309A)';
6662 const BrokenWord = `\\p{Ll}-\\n(?=\\p{Ll})|\\p{Lu}-\\n(?=\\p{L})`;
6663 const regexps = [
6664 `[${replace}]`,
6665 `[${toNormalizeWithNFKC}]`,
6666 `${HKDiacritics}\\n`,
6667 '\\p{M}+(?:-\\n)?',
6668 `${BrokenWord}`,
6669 '\\S-\\n',
6670 `${CJK}\\n`,
6671 '\\n',
6672 hasSyllables ? FIRST_CHAR_SYLLABLES_REG_EXP : '\\u0000',
6673 ];
6674 normalizationRegex = new RegExp(regexps.map((r) => `(${r})`).join('|'), 'gum');
6675 if (hasSyllables) {
6676 withSyllablesRegExp = normalizationRegex;
6677 } else {
6678 noSyllablesRegExp = normalizationRegex;
6679 }
6680 }
6681 const rawDiacriticsPositions = [];
6682 while ((m = DIACRITICS_REG_EXP.exec(text)) !== null) {
6683 rawDiacriticsPositions.push([m[0].length, m.index]);
6684 }
6685 let normalized = text.normalize('NFD');
6686 const positions = [0, 0];
6687 let rawDiacriticsIndex = 0;
6688 let syllableIndex = 0;
6689 let shift = 0;
6690 let shiftOrigin = 0;
6691 let eol = 0;
6692 let hasDiacritics = false;
6693 normalized = normalized.replace(
6694 normalizationRegex,
6695 (match, p1, p2, p3, p4, p5, p6, p7, p8, p9, i) => {
6696 i -= shiftOrigin;
6697 if (p1) {
6698 const replacement = CHARACTERS_TO_NORMALIZE[p1];
6699 const jj = replacement.length;
6700 for (let j = 1; j < jj; j++) {
6701 positions.push(i - shift + j, shift - j);
6702 }
6703 shift -= jj - 1;
6704 return replacement;
6705 }
6706 if (p2) {
6707 let replacement = NFKC_CHARS_TO_NORMALIZE.get(p2);
6708 if (!replacement) {
6709 replacement = p2.normalize('NFKC');
6710 NFKC_CHARS_TO_NORMALIZE.set(p2, replacement);
6711 }
6712 const jj = replacement.length;
6713 for (let j = 1; j < jj; j++) {
6714 positions.push(i - shift + j, shift - j);
6715 }
6716 shift -= jj - 1;
6717 return replacement;
6718 }
6719 if (p3) {
6720 hasDiacritics = true;
6721 if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) {
6722 ++rawDiacriticsIndex;
6723 } else {
6724 positions.push(i - 1 - shift + 1, shift - 1);
6725 shift -= 1;
6726 shiftOrigin += 1;
6727 }
6728 positions.push(i - shift + 1, shift);
6729 shiftOrigin += 1;
6730 eol += 1;
6731 return p3.charAt(0);
6732 }
6733 if (p4) {
6734 const hasTrailingDashEOL = p4.endsWith('\n');
6735 const len = hasTrailingDashEOL ? p4.length - 2 : p4.length;
6736 hasDiacritics = true;
6737 let jj = len;
6738 if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) {
6739 jj -= rawDiacriticsPositions[rawDiacriticsIndex][0];
6740 ++rawDiacriticsIndex;
6741 }
6742 for (let j = 1; j <= jj; j++) {
6743 positions.push(i - 1 - shift + j, shift - j);
6744 }
6745 shift -= jj;
6746 shiftOrigin += jj;
6747 if (hasTrailingDashEOL) {
6748 i += len - 1;
6749 positions.push(i - shift + 1, 1 + shift);
6750 shift += 1;
6751 shiftOrigin += 1;
6752 eol += 1;
6753 return p4.slice(0, len);
6754 }
6755 return p4;
6756 }
6757 if (p5) {
6758 if (ignoreDashEOL) {
6759 shiftOrigin += 1;
6760 eol += 1;
6761 return p5.slice(0, -1);
6762 }
6763 const len = p5.length - 2;
6764 positions.push(i - shift + len, 1 + shift);
6765 shift += 1;
6766 shiftOrigin += 1;
6767 eol += 1;
6768 return p5.slice(0, -2);
6769 }
6770 if (p6) {
6771 shiftOrigin += 1;
6772 eol += 1;
6773 return p6.slice(0, -1);
6774 }
6775 if (p7) {
6776 const len = p7.length - 1;
6777 positions.push(i - shift + len, shift);
6778 shiftOrigin += 1;
6779 eol += 1;
6780 return p7.slice(0, -1);
6781 }
6782 if (p8) {
6783 positions.push(i - shift + 1, shift - 1);
6784 shift -= 1;
6785 shiftOrigin += 1;
6786 eol += 1;
6787 return ' ';
6788 }
6789 if (i + eol === syllablePositions[syllableIndex]?.[1]) {
6790 const newCharLen = syllablePositions[syllableIndex][0] - 1;
6791 ++syllableIndex;
6792 for (let j = 1; j <= newCharLen; j++) {
6793 positions.push(i - (shift - j), shift - j);
6794 }
6795 shift -= newCharLen;
6796 shiftOrigin += newCharLen;
6797 }
6798 return p9;
6799 }
6800 );
6801 positions.push(normalized.length, shift);
6802 const starts = new Uint32Array(positions.length >> 1);
6803 const shifts = new Int32Array(positions.length >> 1);
6804 for (let i = 0, ii = positions.length; i < ii; i += 2) {
6805 starts[i >> 1] = positions[i];
6806 shifts[i >> 1] = positions[i + 1];
6807 }
6808 return [normalized, [starts, shifts], hasDiacritics];
6809}
6810function getOriginalIndex(diffs, pos, len) {
6811 if (!diffs) {
6812 return [pos, len];
6813 }
6814 const [starts, shifts] = diffs;
6815 const start = pos;
6816 const end = pos + len - 1;
6817 let i = binarySearchFirstItem(starts, (x) => x >= start);
6818 if (starts[i] > start) {
6819 --i;
6820 }
6821 let j = binarySearchFirstItem(starts, (x) => x >= end, i);
6822 if (starts[j] > end) {
6823 --j;
6824 }
6825 const oldStart = start + shifts[i];
6826 const oldEnd = end + shifts[j];
6827 const oldLen = oldEnd + 1 - oldStart;
6828 return [oldStart, oldLen];
6829}
6830class PDFFindController {
6831 #state = null;
6832 #updateMatchesCountOnProgress = true;
6833 #visitedPagesCount = 0;
6834 constructor({ linkService, eventBus, updateMatchesCountOnProgress = true }) {
6835 this._linkService = linkService;
6836 this._eventBus = eventBus;
6837 this.#updateMatchesCountOnProgress = updateMatchesCountOnProgress;
6838 this.onIsPageVisible = null;
6839 this.#reset();
6840 eventBus._on('find', this.#onFind.bind(this));
6841 eventBus._on('findbarclose', this.#onFindBarClose.bind(this));
6842 eventBus._on('pagesedited', this.#onPagesEdited.bind(this));
6843 }
6844 get highlightMatches() {
6845 return this._highlightMatches;
6846 }
6847 get pageMatches() {
6848 return this._pageMatches;
6849 }
6850 get pageMatchesLength() {
6851 return this._pageMatchesLength;
6852 }
6853 get selected() {
6854 return this._selected;
6855 }
6856 get state() {
6857 return this.#state;
6858 }
6859 setDocument(pdfDocument) {
6860 if (this._pdfDocument) {
6861 this.#reset();
6862 }
6863 if (!pdfDocument) {
6864 return;
6865 }
6866 this._pdfDocument = pdfDocument;
6867 this._firstPageCapability.resolve();
6868 }
6869 #onFind(state) {
6870 if (!state) {
6871 return;
6872 }
6873 const pdfDocument = this._pdfDocument;
6874 const { type } = state;
6875 if (this.#state === null || this.#shouldDirtyMatch(state)) {
6876 this._dirtyMatch = true;
6877 }
6878 this.#state = state;
6879 if (type !== 'highlightallchange') {
6880 this.#updateUIState(FindState.PENDING);
6881 }
6882 this._firstPageCapability.promise.then(() => {
6883 if (!this._pdfDocument || (pdfDocument && this._pdfDocument !== pdfDocument)) {
6884 return;
6885 }
6886 this.#extractText();
6887 const findbarClosed = !this._highlightMatches;
6888 const pendingTimeout = !!this._findTimeout;
6889 if (this._findTimeout) {
6890 clearTimeout(this._findTimeout);
6891 this._findTimeout = null;
6892 }
6893 if (!type) {
6894 this._findTimeout = setTimeout(() => {
6895 this.#nextMatch();
6896 this._findTimeout = null;
6897 }, FIND_TIMEOUT);
6898 } else if (this._dirtyMatch) {
6899 this.#nextMatch();
6900 } else if (type === 'again') {
6901 this.#nextMatch();
6902 if (findbarClosed && this.#state.highlightAll) {
6903 this.#updateAllPages();
6904 }
6905 } else if (type === 'highlightallchange') {
6906 if (pendingTimeout) {
6907 this.#nextMatch();
6908 } else {
6909 this._highlightMatches = true;
6910 }
6911 this.#updateAllPages();
6912 } else {
6913 this.#nextMatch();
6914 }
6915 });
6916 }
6917 scrollMatchIntoView({ element = null, selectedLeft = 0, pageIndex = -1, matchIndex = -1 }) {
6918 if (!this._scrollMatches || !element) {
6919 return;
6920 } else if (matchIndex === -1 || matchIndex !== this._selected.matchIdx) {
6921 return;
6922 } else if (pageIndex === -1 || pageIndex !== this._selected.pageIdx) {
6923 return;
6924 }
6925 this._scrollMatches = false;
6926 const spot = {
6927 top: MATCH_SCROLL_OFFSET_TOP,
6928 left: selectedLeft,
6929 };
6930 scrollIntoView(element, spot, true);
6931 }
6932 #reset() {
6933 this._highlightMatches = false;
6934 this._scrollMatches = false;
6935 this._pdfDocument = null;
6936 this._pageMatches = [];
6937 this._pageMatchesLength = [];
6938 this.#visitedPagesCount = 0;
6939 this.#state = null;
6940 this._selected = {
6941 pageIdx: -1,
6942 matchIdx: -1,
6943 };
6944 this._offset = {
6945 pageIdx: null,
6946 matchIdx: null,
6947 wrapped: false,
6948 };
6949 this._extractTextPromises = [];
6950 this._pageContents = [];
6951 this._pageDiffs = [];
6952 this._hasDiacritics = [];
6953 this._matchesCountTotal = 0;
6954 this._pagesToSearch = null;
6955 this._pendingFindMatches = new Set();
6956 this._resumePageIdx = null;
6957 this._dirtyMatch = false;
6958 clearTimeout(this._findTimeout);
6959 this._findTimeout = null;
6960 this._firstPageCapability = Promise.withResolvers();
6961 }
6962 get #query() {
6963 const { query } = this.#state;
6964 if (typeof query === 'string') {
6965 if (query !== this._rawQuery) {
6966 this._rawQuery = query;
6967 [this._normalizedQuery] = normalize(query);
6968 }
6969 return this._normalizedQuery;
6970 }
6971 return (query || []).filter((q) => !!q).map((q) => normalize(q)[0]);
6972 }
6973 #shouldDirtyMatch(state) {
6974 const newQuery = state.query,
6975 prevQuery = this.#state.query;
6976 const newType = typeof newQuery,
6977 prevType = typeof prevQuery;
6978 if (newType !== prevType) {
6979 return true;
6980 }
6981 if (newType === 'string') {
6982 if (newQuery !== prevQuery) {
6983 return true;
6984 }
6985 } else if (JSON.stringify(newQuery) !== JSON.stringify(prevQuery)) {
6986 return true;
6987 }
6988 switch (state.type) {
6989 case 'again':
6990 const pageNumber = this._selected.pageIdx + 1;
6991 const linkService = this._linkService;
6992 return (
6993 pageNumber >= 1 &&
6994 pageNumber <= linkService.pagesCount &&
6995 pageNumber !== linkService.page &&
6996 !(this.onIsPageVisible?.(pageNumber) ?? true)
6997 );
6998 case 'highlightallchange':
6999 return false;
7000 }
7001 return true;
7002 }
7003 #isEntireWord(content, startIdx, length) {
7004 let match = content.slice(0, startIdx).match(NOT_DIACRITIC_FROM_END_REG_EXP);
7005 if (match) {
7006 const first = content.charCodeAt(startIdx);
7007 const limit = match[1].charCodeAt(0);
7008 if (getCharacterType(first) === getCharacterType(limit)) {
7009 return false;
7010 }
7011 }
7012 match = content.slice(startIdx + length).match(NOT_DIACRITIC_FROM_START_REG_EXP);
7013 if (match) {
7014 const last = content.charCodeAt(startIdx + length - 1);
7015 const limit = match[1].charCodeAt(0);
7016 if (getCharacterType(last) === getCharacterType(limit)) {
7017 return false;
7018 }
7019 }
7020 return true;
7021 }
7022 #convertToRegExpString(query, hasDiacritics) {
7023 const { matchDiacritics } = this.#state;
7024 let isUnicode = false;
7025 const addExtraWhitespaces = (original, fixed) => {
7026 if (original === query) {
7027 return fixed;
7028 }
7029 if (query.startsWith(original)) {
7030 return `${fixed}[ ]*`;
7031 }
7032 if (query.endsWith(original)) {
7033 return `[ ]*${fixed}`;
7034 }
7035 return `[ ]*${fixed}[ ]*`;
7036 };
7037 query = query.replaceAll(SPECIAL_CHARS_REG_EXP, (match, p1, p2, p3, p4, p5) => {
7038 if (p1) {
7039 return addExtraWhitespaces(p1, RegExp.escape(p1));
7040 }
7041 if (p2) {
7042 return addExtraWhitespaces(p2, RegExp.escape(p2));
7043 }
7044 if (p3) {
7045 return '[ ]+';
7046 }
7047 if (matchDiacritics) {
7048 return p4 || p5;
7049 }
7050 if (p4) {
7051 return DIACRITICS_EXCEPTION.has(p4.charCodeAt(0)) ? p4 : '';
7052 }
7053 if (hasDiacritics) {
7054 isUnicode = true;
7055 return `${p5}\\p{M}*`;
7056 }
7057 return p5;
7058 });
7059 const trailingSpaces = '[ ]*';
7060 if (query.endsWith(trailingSpaces)) {
7061 query = query.slice(0, query.length - trailingSpaces.length);
7062 }
7063 if (matchDiacritics) {
7064 if (hasDiacritics) {
7065 DIACRITICS_EXCEPTION_STR ||= String.fromCharCode(...DIACRITICS_EXCEPTION);
7066 isUnicode = true;
7067 query = `${query}(?=[${DIACRITICS_EXCEPTION_STR}]|[^\\p{M}]|$)`;
7068 }
7069 }
7070 return [isUnicode, query];
7071 }
7072 #calculateMatch(pageIndex) {
7073 if (!this.#state) {
7074 return;
7075 }
7076 const query = this.#query;
7077 if (query.length === 0) {
7078 return;
7079 }
7080 const pageContent = this._pageContents[pageIndex];
7081 const matcherResult = this.match(query, pageContent, pageIndex);
7082 const matches = (this._pageMatches[pageIndex] = []);
7083 const matchesLength = (this._pageMatchesLength[pageIndex] = []);
7084 const diffs = this._pageDiffs[pageIndex];
7085 matcherResult?.forEach(({ index, length }) => {
7086 const [matchPos, matchLen] = getOriginalIndex(diffs, index, length);
7087 if (matchLen) {
7088 matches.push(matchPos);
7089 matchesLength.push(matchLen);
7090 }
7091 });
7092 if (this.#state.highlightAll) {
7093 this.#updatePage(pageIndex);
7094 }
7095 if (this._resumePageIdx === pageIndex) {
7096 this._resumePageIdx = null;
7097 this.#nextPageMatch();
7098 }
7099 const pageMatchesCount = matches.length;
7100 this._matchesCountTotal += pageMatchesCount;
7101 if (this.#updateMatchesCountOnProgress) {
7102 if (pageMatchesCount > 0) {
7103 this.#updateUIResultsCount();
7104 }
7105 } else if (++this.#visitedPagesCount === this._linkService.pagesCount) {
7106 this.#updateUIResultsCount();
7107 }
7108 }
7109 match(query, pageContent, pageIndex) {
7110 const hasDiacritics = this._hasDiacritics[pageIndex];
7111 let isUnicode = false;
7112 if (typeof query === 'string') {
7113 [isUnicode, query] = this.#convertToRegExpString(query, hasDiacritics);
7114 } else {
7115 query = query
7116 .sort()
7117 .reverse()
7118 .map((q) => {
7119 const [isUnicodePart, queryPart] = this.#convertToRegExpString(q, hasDiacritics);
7120 isUnicode ||= isUnicodePart;
7121 return `(${queryPart})`;
7122 })
7123 .join('|');
7124 }
7125 if (!query) {
7126 return undefined;
7127 }
7128 const { caseSensitive, entireWord } = this.#state;
7129 const flags = `g${isUnicode ? 'u' : ''}${caseSensitive ? '' : 'i'}`;
7130 query = new RegExp(query, flags);
7131 const matches = [];
7132 let match;
7133 while ((match = query.exec(pageContent)) !== null) {
7134 if (entireWord && !this.#isEntireWord(pageContent, match.index, match[0].length)) {
7135 continue;
7136 }
7137 matches.push({
7138 index: match.index,
7139 length: match[0].length,
7140 });
7141 }
7142 return matches;
7143 }
7144 #extractText() {
7145 if (this._extractTextPromises.length > 0) {
7146 return;
7147 }
7148 let deferred = Promise.resolve();
7149 const textOptions = {
7150 disableNormalization: true,
7151 };
7152 const pdfDoc = this._pdfDocument;
7153 for (let i = 0, ii = this._linkService.pagesCount; i < ii; i++) {
7154 const { promise, resolve } = Promise.withResolvers();
7155 this._extractTextPromises[i] = promise;
7156 deferred = deferred.then(async () => {
7157 if (pdfDoc !== this._pdfDocument) {
7158 resolve();
7159 return;
7160 }
7161 await pdfDoc
7162 .getPage(i + 1)
7163 .then((pdfPage) => pdfPage.getTextContent(textOptions))
7164 .then(
7165 (textContent) => {
7166 const strBuf = [];
7167 for (const textItem of textContent.items) {
7168 strBuf.push(textItem.str);
7169 if (textItem.hasEOL) {
7170 strBuf.push('\n');
7171 }
7172 }
7173 [this._pageContents[i], this._pageDiffs[i], this._hasDiacritics[i]] = normalize(
7174 strBuf.join('')
7175 );
7176 resolve();
7177 },
7178 (reason) => {
7179 console.error(`Unable to get text content for page ${i + 1}`, reason);
7180 this._pageContents[i] = '';
7181 this._pageDiffs[i] = null;
7182 this._hasDiacritics[i] = false;
7183 resolve();
7184 }
7185 );
7186 });
7187 }
7188 }
7189 #updatePage(index) {
7190 if (this._scrollMatches && this._selected.pageIdx === index) {
7191 this._linkService.page = index + 1;
7192 }
7193 this._eventBus.dispatch('updatetextlayermatches', {
7194 source: this,
7195 pageIndex: index,
7196 });
7197 }
7198 #updateAllPages() {
7199 this._eventBus.dispatch('updatetextlayermatches', {
7200 source: this,
7201 pageIndex: -1,
7202 });
7203 }
7204 #nextMatch() {
7205 const previous = this.#state.findPrevious;
7206 const currentPageIndex = this._linkService.page - 1;
7207 const numPages = this._linkService.pagesCount;
7208 this._highlightMatches = true;
7209 if (this._dirtyMatch) {
7210 this._dirtyMatch = false;
7211 this._selected.pageIdx = this._selected.matchIdx = -1;
7212 this._offset.pageIdx = currentPageIndex;
7213 this._offset.matchIdx = null;
7214 this._offset.wrapped = false;
7215 this._resumePageIdx = null;
7216 this._pageMatches.length = 0;
7217 this._pageMatchesLength.length = 0;
7218 this.#visitedPagesCount = 0;
7219 this._matchesCountTotal = 0;
7220 this.#updateAllPages();
7221 for (let i = 0; i < numPages; i++) {
7222 if (this._pendingFindMatches.has(i)) {
7223 continue;
7224 }
7225 this._pendingFindMatches.add(i);
7226 this._extractTextPromises[i].then(() => {
7227 this._pendingFindMatches.delete(i);
7228 this.#calculateMatch(i);
7229 });
7230 }
7231 }
7232 const query = this.#query;
7233 if (query.length === 0) {
7234 this.#updateUIState(FindState.FOUND);
7235 return;
7236 }
7237 if (this._resumePageIdx) {
7238 return;
7239 }
7240 const offset = this._offset;
7241 this._pagesToSearch = numPages;
7242 if (offset.matchIdx !== null) {
7243 const numPageMatches = this._pageMatches[offset.pageIdx].length;
7244 if (
7245 (!previous && offset.matchIdx + 1 < numPageMatches) ||
7246 (previous && offset.matchIdx > 0)
7247 ) {
7248 offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
7249 this.#updateMatch(true);
7250 return;
7251 }
7252 this.#advanceOffsetPage(previous);
7253 }
7254 this.#nextPageMatch();
7255 }
7256 #matchesReady(matches) {
7257 const offset = this._offset;
7258 const numMatches = matches.length;
7259 const previous = this.#state.findPrevious;
7260 if (numMatches) {
7261 offset.matchIdx = previous ? numMatches - 1 : 0;
7262 this.#updateMatch(true);
7263 return true;
7264 }
7265 this.#advanceOffsetPage(previous);
7266 if (offset.wrapped) {
7267 offset.matchIdx = null;
7268 if (this._pagesToSearch < 0) {
7269 this.#updateMatch(false);
7270 return true;
7271 }
7272 }
7273 return false;
7274 }
7275 #nextPageMatch() {
7276 if (this._resumePageIdx !== null) {
7277 console.error('There can only be one pending page.');
7278 }
7279 let matches = null;
7280 do {
7281 const pageIdx = this._offset.pageIdx;
7282 matches = this._pageMatches[pageIdx];
7283 if (!matches) {
7284 this._resumePageIdx = pageIdx;
7285 break;
7286 }
7287 } while (!this.#matchesReady(matches));
7288 }
7289 #advanceOffsetPage(previous) {
7290 const offset = this._offset;
7291 const numPages = this._linkService.pagesCount;
7292 offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
7293 offset.matchIdx = null;
7294 this._pagesToSearch--;
7295 if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
7296 offset.pageIdx = previous ? numPages - 1 : 0;
7297 offset.wrapped = true;
7298 }
7299 }
7300 #updateMatch(found = false) {
7301 let state = FindState.NOT_FOUND;
7302 const wrapped = this._offset.wrapped;
7303 this._offset.wrapped = false;
7304 if (found) {
7305 const previousPage = this._selected.pageIdx;
7306 this._selected.pageIdx = this._offset.pageIdx;
7307 this._selected.matchIdx = this._offset.matchIdx;
7308 state = wrapped ? FindState.WRAPPED : FindState.FOUND;
7309 if (previousPage !== -1 && previousPage !== this._selected.pageIdx) {
7310 this.#updatePage(previousPage);
7311 }
7312 }
7313 this.#updateUIState(state, this.#state.findPrevious);
7314 if (this._selected.pageIdx !== -1) {
7315 this._scrollMatches = true;
7316 this.#updatePage(this._selected.pageIdx);
7317 }
7318 }
7319 #onPagesEdited({ pagesMapper }) {
7320 if (this._extractTextPromises.length === 0) {
7321 return;
7322 }
7323 this.#onFindBarClose();
7324 this._dirtyMatch = true;
7325 const prevTextPromises = this._extractTextPromises;
7326 const extractTextPromises = (this._extractTextPromises.length = []);
7327 for (let i = 0, ii = pagesMapper.length; i < ii; i++) {
7328 const prevPageIndex = pagesMapper.getPrevPageNumber(i + 1) - 1;
7329 if (prevPageIndex === -1) {
7330 continue;
7331 }
7332 extractTextPromises.push(prevTextPromises[prevPageIndex] || Promise.resolve());
7333 }
7334 }
7335 #onFindBarClose(evt) {
7336 const pdfDocument = this._pdfDocument;
7337 this._firstPageCapability.promise.then(() => {
7338 if (!this._pdfDocument || (pdfDocument && this._pdfDocument !== pdfDocument)) {
7339 return;
7340 }
7341 if (this._findTimeout) {
7342 clearTimeout(this._findTimeout);
7343 this._findTimeout = null;
7344 }
7345 if (this._resumePageIdx) {
7346 this._resumePageIdx = null;
7347 this._dirtyMatch = true;
7348 }
7349 this.#updateUIState(FindState.FOUND);
7350 this._highlightMatches = false;
7351 this.#updateAllPages();
7352 });
7353 }
7354 #requestMatchesCount() {
7355 const { pageIdx, matchIdx } = this._selected;
7356 let current = 0,
7357 total = this._matchesCountTotal;
7358 if (matchIdx !== -1) {
7359 for (let i = 0; i < pageIdx; i++) {
7360 current += this._pageMatches[i]?.length || 0;
7361 }
7362 current += matchIdx + 1;
7363 }
7364 if (current < 1 || current > total) {
7365 current = total = 0;
7366 }
7367 return {
7368 current,
7369 total,
7370 };
7371 }
7372 #updateUIResultsCount() {
7373 this._eventBus.dispatch('updatefindmatchescount', {
7374 source: this,
7375 matchesCount: this.#requestMatchesCount(),
7376 });
7377 }
7378 #updateUIState(state, previous = false) {
7379 if (
7380 !this.#updateMatchesCountOnProgress &&
7381 (this.#visitedPagesCount !== this._linkService.pagesCount || state === FindState.PENDING)
7382 ) {
7383 return;
7384 }
7385 this._eventBus.dispatch('updatefindcontrolstate', {
7386 source: this,
7387 state,
7388 previous,
7389 entireWord: this.#state?.entireWord ?? null,
7390 matchesCount: this.#requestMatchesCount(),
7391 rawQuery: this.#state?.query ?? null,
7392 });
7393 }
7394} // ./web/pdf_find_bar.js
7395
7396const MATCHES_COUNT_LIMIT = 1000;
7397class PDFFindBar {
7398 #mainContainer;
7399 #resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this));
7400 constructor(options, mainContainer, eventBus) {
7401 this.opened = false;
7402 this.bar = options.bar;
7403 this.toggleButton = options.toggleButton;
7404 this.findField = options.findField;
7405 this.highlightAll = options.highlightAllCheckbox;
7406 this.caseSensitive = options.caseSensitiveCheckbox;
7407 this.matchDiacritics = options.matchDiacriticsCheckbox;
7408 this.entireWord = options.entireWordCheckbox;
7409 this.findMsg = options.findMsg;
7410 this.findResultsCount = options.findResultsCount;
7411 this.findPreviousButton = options.findPreviousButton;
7412 this.findNextButton = options.findNextButton;
7413 this.eventBus = eventBus;
7414 this.#mainContainer = mainContainer;
7415 const checkedInputs = new Map([
7416 [this.highlightAll, 'highlightallchange'],
7417 [this.caseSensitive, 'casesensitivitychange'],
7418 [this.entireWord, 'entirewordchange'],
7419 [this.matchDiacritics, 'diacriticmatchingchange'],
7420 ]);
7421 this.toggleButton.addEventListener('click', () => {
7422 this.toggle();
7423 });
7424 this.findField.addEventListener('input', () => {
7425 this.dispatchEvent('');
7426 });
7427 this.bar.addEventListener('keydown', ({ keyCode, shiftKey, target }) => {
7428 switch (keyCode) {
7429 case 13:
7430 if (target === this.findField) {
7431 this.dispatchEvent('again', shiftKey);
7432 } else if (checkedInputs.has(target)) {
7433 target.checked = !target.checked;
7434 this.dispatchEvent(checkedInputs.get(target));
7435 }
7436 break;
7437 case 27:
7438 this.close();
7439 break;
7440 }
7441 });
7442 this.findPreviousButton.addEventListener('click', () => {
7443 this.dispatchEvent('again', true);
7444 });
7445 this.findNextButton.addEventListener('click', () => {
7446 this.dispatchEvent('again', false);
7447 });
7448 for (const [elem, evtName] of checkedInputs) {
7449 elem.addEventListener('click', () => {
7450 this.dispatchEvent(evtName);
7451 });
7452 }
7453 }
7454 reset() {
7455 this.updateUIState();
7456 }
7457 dispatchEvent(type, findPrev = false) {
7458 this.eventBus.dispatch('find', {
7459 source: this,
7460 type,
7461 query: this.findField.value,
7462 caseSensitive: this.caseSensitive.checked,
7463 entireWord: this.entireWord.checked,
7464 highlightAll: this.highlightAll.checked,
7465 findPrevious: findPrev,
7466 matchDiacritics: this.matchDiacritics.checked,
7467 });
7468 }
7469 updateUIState(state, previous, matchesCount) {
7470 const { findField, findMsg } = this;
7471 let findMsgId = '',
7472 status = '';
7473 switch (state) {
7474 case FindState.FOUND:
7475 break;
7476 case FindState.PENDING:
7477 status = 'pending';
7478 break;
7479 case FindState.NOT_FOUND:
7480 findMsgId = 'pdfjs-find-not-found';
7481 status = 'notFound';
7482 break;
7483 case FindState.WRAPPED:
7484 findMsgId = previous ? 'pdfjs-find-reached-top' : 'pdfjs-find-reached-bottom';
7485 break;
7486 }
7487 findField.setAttribute('data-status', status);
7488 findField.setAttribute('aria-invalid', state === FindState.NOT_FOUND);
7489 findMsg.setAttribute('data-status', status);
7490 if (findMsgId) {
7491 findMsg.setAttribute('data-l10n-id', findMsgId);
7492 } else {
7493 findMsg.removeAttribute('data-l10n-id');
7494 findMsg.textContent = '';
7495 }
7496 this.updateResultsCount(matchesCount);
7497 }
7498 updateResultsCount({ current = 0, total = 0 } = {}) {
7499 const { findResultsCount } = this;
7500 if (total > 0) {
7501 const limit = MATCHES_COUNT_LIMIT;
7502 findResultsCount.setAttribute(
7503 'data-l10n-id',
7504 total > limit ? 'pdfjs-find-match-count-limit' : 'pdfjs-find-match-count'
7505 );
7506 findResultsCount.setAttribute(
7507 'data-l10n-args',
7508 JSON.stringify({
7509 limit,
7510 current,
7511 total,
7512 })
7513 );
7514 } else {
7515 findResultsCount.removeAttribute('data-l10n-id');
7516 findResultsCount.textContent = '';
7517 }
7518 }
7519 open() {
7520 if (!this.opened) {
7521 this.#resizeObserver.observe(this.#mainContainer);
7522 this.#resizeObserver.observe(this.bar);
7523 this.opened = true;
7524 toggleExpandedBtn(this.toggleButton, true, this.bar);
7525 }
7526 this.findField.select();
7527 this.findField.focus();
7528 }
7529 close() {
7530 if (!this.opened) {
7531 return;
7532 }
7533 this.#resizeObserver.disconnect();
7534 this.opened = false;
7535 toggleExpandedBtn(this.toggleButton, false, this.bar);
7536 this.eventBus.dispatch('findbarclose', {
7537 source: this,
7538 });
7539 }
7540 toggle() {
7541 if (this.opened) {
7542 this.close();
7543 } else {
7544 this.open();
7545 }
7546 }
7547 #resizeObserverCallback() {
7548 const { bar } = this;
7549 bar.classList.remove('wrapContainers');
7550 const findbarHeight = bar.clientHeight;
7551 const inputContainerHeight = bar.firstElementChild.clientHeight;
7552 if (findbarHeight > inputContainerHeight) {
7553 bar.classList.add('wrapContainers');
7554 }
7555 }
7556} // ./web/pdf_history.js
7557
7558const HASH_CHANGE_TIMEOUT = 1000;
7559const POSITION_UPDATED_THRESHOLD = 50;
7560const UPDATE_VIEWAREA_TIMEOUT = 1000;
7561function getCurrentHash() {
7562 return document.location.hash;
7563}
7564class PDFHistory {
7565 #eventAbortController = null;
7566 constructor({ linkService, eventBus }) {
7567 this.linkService = linkService;
7568 this.eventBus = eventBus;
7569 this._initialized = false;
7570 this._fingerprint = '';
7571 this.reset();
7572 this.eventBus._on('pagesinit', () => {
7573 this._isPagesLoaded = false;
7574 this.eventBus._on(
7575 'pagesloaded',
7576 (evt) => {
7577 this._isPagesLoaded = !!evt.pagesCount;
7578 },
7579 {
7580 once: true,
7581 }
7582 );
7583 });
7584 }
7585 initialize({ fingerprint, resetHistory = false, updateUrl = false }) {
7586 if (!fingerprint || typeof fingerprint !== 'string') {
7587 console.error('PDFHistory.initialize: The "fingerprint" must be a non-empty string.');
7588 return;
7589 }
7590 if (this._initialized) {
7591 this.reset();
7592 }
7593 const reInitialized = this._fingerprint !== '' && this._fingerprint !== fingerprint;
7594 this._fingerprint = fingerprint;
7595 this._updateUrl = updateUrl === true;
7596 this._initialized = true;
7597 this.#bindEvents();
7598 const state = window.history.state;
7599 this._popStateInProgress = false;
7600 this._blockHashChange = 0;
7601 this._currentHash = getCurrentHash();
7602 this._numPositionUpdates = 0;
7603 this._uid = this._maxUid = 0;
7604 this._destination = null;
7605 this._position = null;
7606 if (!this.#isValidState(state, true) || resetHistory) {
7607 const { hash, page, rotation } = this.#parseCurrentHash(true);
7608 if (!hash || reInitialized || resetHistory) {
7609 this.#pushOrReplaceState(null, true);
7610 return;
7611 }
7612 this.#pushOrReplaceState(
7613 {
7614 hash,
7615 page,
7616 rotation,
7617 },
7618 true
7619 );
7620 return;
7621 }
7622 const destination = state.destination;
7623 this.#updateInternalState(destination, state.uid, true);
7624 if (destination.rotation !== undefined) {
7625 this._initialRotation = destination.rotation;
7626 }
7627 if (destination.dest) {
7628 this._initialBookmark = JSON.stringify(destination.dest);
7629 this._destination.page = null;
7630 } else if (destination.hash) {
7631 this._initialBookmark = destination.hash;
7632 } else if (destination.page) {
7633 this._initialBookmark = `page=${destination.page}`;
7634 }
7635 }
7636 reset() {
7637 if (this._initialized) {
7638 this.#pageHide();
7639 this._initialized = false;
7640 this.#unbindEvents();
7641 }
7642 if (this._updateViewareaTimeout) {
7643 clearTimeout(this._updateViewareaTimeout);
7644 this._updateViewareaTimeout = null;
7645 }
7646 this._initialBookmark = null;
7647 this._initialRotation = null;
7648 }
7649 push({ namedDest = null, explicitDest, pageNumber }) {
7650 if (!this._initialized) {
7651 return;
7652 }
7653 if (namedDest && typeof namedDest !== 'string') {
7654 console.error('PDFHistory.push: ' + `"${namedDest}" is not a valid namedDest parameter.`);
7655 return;
7656 } else if (!Array.isArray(explicitDest)) {
7657 console.error(
7658 'PDFHistory.push: ' + `"${explicitDest}" is not a valid explicitDest parameter.`
7659 );
7660 return;
7661 } else if (!this.#isValidPage(pageNumber)) {
7662 if (pageNumber !== null || this._destination) {
7663 console.error('PDFHistory.push: ' + `"${pageNumber}" is not a valid pageNumber parameter.`);
7664 return;
7665 }
7666 }
7667 const hash = namedDest || JSON.stringify(explicitDest);
7668 if (!hash) {
7669 return;
7670 }
7671 let forceReplace = false;
7672 if (
7673 this._destination &&
7674 (isDestHashesEqual(this._destination.hash, hash) ||
7675 isDestArraysEqual(this._destination.dest, explicitDest))
7676 ) {
7677 if (this._destination.page) {
7678 return;
7679 }
7680 forceReplace = true;
7681 }
7682 if (this._popStateInProgress && !forceReplace) {
7683 return;
7684 }
7685 this.#pushOrReplaceState(
7686 {
7687 dest: explicitDest,
7688 hash,
7689 page: pageNumber,
7690 rotation: this.linkService.rotation,
7691 },
7692 forceReplace
7693 );
7694 if (!this._popStateInProgress) {
7695 this._popStateInProgress = true;
7696 Promise.resolve().then(() => {
7697 this._popStateInProgress = false;
7698 });
7699 }
7700 }
7701 pushPage(pageNumber) {
7702 if (!this._initialized) {
7703 return;
7704 }
7705 if (!this.#isValidPage(pageNumber)) {
7706 console.error(`PDFHistory.pushPage: "${pageNumber}" is not a valid page number.`);
7707 return;
7708 }
7709 if (this._destination?.page === pageNumber) {
7710 return;
7711 }
7712 if (this._popStateInProgress) {
7713 return;
7714 }
7715 this.#pushOrReplaceState({
7716 dest: null,
7717 hash: `page=${pageNumber}`,
7718 page: pageNumber,
7719 rotation: this.linkService.rotation,
7720 });
7721 if (!this._popStateInProgress) {
7722 this._popStateInProgress = true;
7723 Promise.resolve().then(() => {
7724 this._popStateInProgress = false;
7725 });
7726 }
7727 }
7728 pushCurrentPosition() {
7729 if (!this._initialized || this._popStateInProgress) {
7730 return;
7731 }
7732 this.#tryPushCurrentPosition();
7733 }
7734 back() {
7735 if (!this._initialized || this._popStateInProgress) {
7736 return;
7737 }
7738 const state = window.history.state;
7739 if (this.#isValidState(state) && state.uid > 0) {
7740 window.history.back();
7741 }
7742 }
7743 forward() {
7744 if (!this._initialized || this._popStateInProgress) {
7745 return;
7746 }
7747 const state = window.history.state;
7748 if (this.#isValidState(state) && state.uid < this._maxUid) {
7749 window.history.forward();
7750 }
7751 }
7752 get popStateInProgress() {
7753 return this._initialized && (this._popStateInProgress || this._blockHashChange > 0);
7754 }
7755 get initialBookmark() {
7756 return this._initialized ? this._initialBookmark : null;
7757 }
7758 get initialRotation() {
7759 return this._initialized ? this._initialRotation : null;
7760 }
7761 #pushOrReplaceState(destination, forceReplace = false) {
7762 const shouldReplace = forceReplace || !this._destination;
7763 const newState = {
7764 fingerprint: this._fingerprint,
7765 uid: shouldReplace ? this._uid : this._uid + 1,
7766 destination,
7767 };
7768 this.#updateInternalState(destination, newState.uid);
7769 let newUrl;
7770 if (this._updateUrl && destination?.hash) {
7771 const { href, protocol } = document.location;
7772 if (protocol !== 'file:') {
7773 newUrl = updateUrlHash(href, destination.hash);
7774 }
7775 }
7776 if (shouldReplace) {
7777 window.history.replaceState(newState, '', newUrl);
7778 } else {
7779 window.history.pushState(newState, '', newUrl);
7780 }
7781 }
7782 #tryPushCurrentPosition(temporary = false) {
7783 if (!this._position) {
7784 return;
7785 }
7786 let position = this._position;
7787 if (temporary) {
7788 position = Object.assign(Object.create(null), this._position);
7789 position.temporary = true;
7790 }
7791 if (!this._destination) {
7792 this.#pushOrReplaceState(position);
7793 return;
7794 }
7795 if (this._destination.temporary) {
7796 this.#pushOrReplaceState(position, true);
7797 return;
7798 }
7799 if (this._destination.hash === position.hash) {
7800 return;
7801 }
7802 if (
7803 !this._destination.page &&
7804 (POSITION_UPDATED_THRESHOLD <= 0 || this._numPositionUpdates <= POSITION_UPDATED_THRESHOLD)
7805 ) {
7806 return;
7807 }
7808 let forceReplace = false;
7809 if (this._destination.page >= position.first && this._destination.page <= position.page) {
7810 if (this._destination.dest !== undefined || !this._destination.first) {
7811 return;
7812 }
7813 forceReplace = true;
7814 }
7815 this.#pushOrReplaceState(position, forceReplace);
7816 }
7817 #isValidPage(val) {
7818 return Number.isInteger(val) && val > 0 && val <= this.linkService.pagesCount;
7819 }
7820 #isValidState(state, checkReload = false) {
7821 if (!state) {
7822 return false;
7823 }
7824 if (state.fingerprint !== this._fingerprint) {
7825 if (checkReload) {
7826 if (
7827 typeof state.fingerprint !== 'string' ||
7828 state.fingerprint.length !== this._fingerprint.length
7829 ) {
7830 return false;
7831 }
7832 const [perfEntry] = performance.getEntriesByType('navigation');
7833 if (perfEntry?.type !== 'reload') {
7834 return false;
7835 }
7836 } else {
7837 return false;
7838 }
7839 }
7840 if (!Number.isInteger(state.uid) || state.uid < 0) {
7841 return false;
7842 }
7843 if (state.destination === null || typeof state.destination !== 'object') {
7844 return false;
7845 }
7846 return true;
7847 }
7848 #updateInternalState(destination, uid, removeTemporary = false) {
7849 if (this._updateViewareaTimeout) {
7850 clearTimeout(this._updateViewareaTimeout);
7851 this._updateViewareaTimeout = null;
7852 }
7853 if (removeTemporary && destination?.temporary) {
7854 delete destination.temporary;
7855 }
7856 this._destination = destination;
7857 this._uid = uid;
7858 this._maxUid = Math.max(this._maxUid, uid);
7859 this._numPositionUpdates = 0;
7860 }
7861 #parseCurrentHash(checkNameddest = false) {
7862 const hash = unescape(getCurrentHash()).substring(1);
7863 const params = parseQueryString(hash);
7864 const nameddest = params.get('nameddest') || '';
7865 let page = params.get('page') | 0;
7866 if (!this.#isValidPage(page) || (checkNameddest && nameddest.length > 0)) {
7867 page = null;
7868 }
7869 return {
7870 hash,
7871 page,
7872 rotation: this.linkService.rotation,
7873 };
7874 }
7875 #updateViewarea({ location }) {
7876 if (this._updateViewareaTimeout) {
7877 clearTimeout(this._updateViewareaTimeout);
7878 this._updateViewareaTimeout = null;
7879 }
7880 this._position = {
7881 hash: location.pdfOpenParams.substring(1),
7882 page: this.linkService.page,
7883 first: location.pageNumber,
7884 rotation: location.rotation,
7885 };
7886 if (this._popStateInProgress) {
7887 return;
7888 }
7889 if (
7890 POSITION_UPDATED_THRESHOLD > 0 &&
7891 this._isPagesLoaded &&
7892 this._destination &&
7893 !this._destination.page
7894 ) {
7895 this._numPositionUpdates++;
7896 }
7897 if (UPDATE_VIEWAREA_TIMEOUT > 0) {
7898 this._updateViewareaTimeout = setTimeout(() => {
7899 if (!this._popStateInProgress) {
7900 this.#tryPushCurrentPosition(true);
7901 }
7902 this._updateViewareaTimeout = null;
7903 }, UPDATE_VIEWAREA_TIMEOUT);
7904 }
7905 }
7906 #popState({ state }) {
7907 const newHash = getCurrentHash(),
7908 hashChanged = this._currentHash !== newHash;
7909 this._currentHash = newHash;
7910 if (!state) {
7911 this._uid++;
7912 const { hash, page, rotation } = this.#parseCurrentHash();
7913 this.#pushOrReplaceState(
7914 {
7915 hash,
7916 page,
7917 rotation,
7918 },
7919 true
7920 );
7921 return;
7922 }
7923 if (!this.#isValidState(state)) {
7924 return;
7925 }
7926 this._popStateInProgress = true;
7927 if (hashChanged) {
7928 this._blockHashChange++;
7929 waitOnEventOrTimeout({
7930 target: window,
7931 name: 'hashchange',
7932 delay: HASH_CHANGE_TIMEOUT,
7933 }).then(() => {
7934 this._blockHashChange--;
7935 });
7936 }
7937 const destination = state.destination;
7938 this.#updateInternalState(destination, state.uid, true);
7939 if (isValidRotation(destination.rotation)) {
7940 this.linkService.rotation = destination.rotation;
7941 }
7942 if (destination.dest) {
7943 this.linkService.goToDestination(destination.dest);
7944 } else if (destination.hash) {
7945 this.linkService.setHash(destination.hash);
7946 } else if (destination.page) {
7947 this.linkService.page = destination.page;
7948 }
7949 Promise.resolve().then(() => {
7950 this._popStateInProgress = false;
7951 });
7952 }
7953 #pageHide() {
7954 if (!this._destination || this._destination.temporary) {
7955 this.#tryPushCurrentPosition();
7956 }
7957 }
7958 #bindEvents() {
7959 if (this.#eventAbortController) {
7960 return;
7961 }
7962 this.#eventAbortController = new AbortController();
7963 const { signal } = this.#eventAbortController;
7964 this.eventBus._on('updateviewarea', this.#updateViewarea.bind(this), {
7965 signal,
7966 });
7967 window.addEventListener('popstate', this.#popState.bind(this), {
7968 signal,
7969 });
7970 window.addEventListener('pagehide', this.#pageHide.bind(this), {
7971 signal,
7972 });
7973 }
7974 #unbindEvents() {
7975 this.#eventAbortController?.abort();
7976 this.#eventAbortController = null;
7977 }
7978}
7979function isDestHashesEqual(destHash, pushHash) {
7980 if (typeof destHash !== 'string' || typeof pushHash !== 'string') {
7981 return false;
7982 }
7983 if (destHash === pushHash) {
7984 return true;
7985 }
7986 const nameddest = parseQueryString(destHash).get('nameddest');
7987 if (nameddest === pushHash) {
7988 return true;
7989 }
7990 return false;
7991}
7992function isDestArraysEqual(firstDest, secondDest) {
7993 function isEntryEqual(first, second) {
7994 if (typeof first !== typeof second) {
7995 return false;
7996 }
7997 if (Array.isArray(first) || Array.isArray(second)) {
7998 return false;
7999 }
8000 if (first !== null && typeof first === 'object' && second !== null) {
8001 if (Object.keys(first).length !== Object.keys(second).length) {
8002 return false;
8003 }
8004 for (const key in first) {
8005 if (!isEntryEqual(first[key], second[key])) {
8006 return false;
8007 }
8008 }
8009 return true;
8010 }
8011 return first === second || (Number.isNaN(first) && Number.isNaN(second));
8012 }
8013 if (!(Array.isArray(firstDest) && Array.isArray(secondDest))) {
8014 return false;
8015 }
8016 if (firstDest.length !== secondDest.length) {
8017 return false;
8018 }
8019 for (let i = 0, ii = firstDest.length; i < ii; i++) {
8020 if (!isEntryEqual(firstDest[i], secondDest[i])) {
8021 return false;
8022 }
8023 }
8024 return true;
8025} // ./web/pdf_layer_viewer.js
8026
8027class PDFLayerViewer extends BaseTreeViewer {
8028 constructor(options) {
8029 super(options);
8030 this.eventBus._on('optionalcontentconfigchanged', (evt) => {
8031 this.#updateLayers(evt.promise);
8032 });
8033 this.eventBus._on('resetlayers', () => {
8034 this.#updateLayers();
8035 });
8036 this.eventBus._on('togglelayerstree', this._toggleAllTreeItems.bind(this));
8037 }
8038 reset() {
8039 super.reset();
8040 this._optionalContentConfig = null;
8041 this._optionalContentVisibility?.clear();
8042 this._optionalContentVisibility = null;
8043 }
8044 _dispatchEvent(layersCount) {
8045 this.eventBus.dispatch('layersloaded', {
8046 source: this,
8047 layersCount,
8048 });
8049 }
8050 _bindLink(element, { groupId, input }) {
8051 const setVisibility = () => {
8052 const visible = input.checked;
8053 this._optionalContentConfig.setVisibility(groupId, visible);
8054 const cached = this._optionalContentVisibility.get(groupId);
8055 if (cached) {
8056 cached.visible = visible;
8057 }
8058 this.eventBus.dispatch('optionalcontentconfig', {
8059 source: this,
8060 promise: Promise.resolve(this._optionalContentConfig),
8061 });
8062 };
8063 element.onclick = (evt) => {
8064 if (evt.target === input) {
8065 setVisibility();
8066 return true;
8067 } else if (evt.target !== element) {
8068 return true;
8069 }
8070 input.checked = !input.checked;
8071 setVisibility();
8072 return false;
8073 };
8074 }
8075 _setNestedName(element, { name = null }) {
8076 if (typeof name === 'string') {
8077 element.textContent = this._normalizeTextContent(name);
8078 return;
8079 }
8080 element.setAttribute('data-l10n-id', 'pdfjs-additional-layers');
8081 element.style.fontStyle = 'italic';
8082 this._l10n.translateOnce(element);
8083 }
8084 _addToggleButton(div, { name = null }) {
8085 super._addToggleButton(div, name === null);
8086 }
8087 _toggleAllTreeItems() {
8088 if (!this._optionalContentConfig) {
8089 return;
8090 }
8091 super._toggleAllTreeItems();
8092 }
8093 render({ optionalContentConfig, pdfDocument }) {
8094 if (this._optionalContentConfig) {
8095 this.reset();
8096 }
8097 this._optionalContentConfig = optionalContentConfig || null;
8098 this._pdfDocument = pdfDocument || null;
8099 const groups = optionalContentConfig?.getOrder();
8100 if (!groups) {
8101 this._dispatchEvent(0);
8102 return;
8103 }
8104 this._optionalContentVisibility = new Map();
8105 const fragment = document.createDocumentFragment(),
8106 queue = [
8107 {
8108 parent: fragment,
8109 groups,
8110 },
8111 ];
8112 let layersCount = 0,
8113 hasAnyNesting = false;
8114 while (queue.length > 0) {
8115 const levelData = queue.shift();
8116 for (const groupId of levelData.groups) {
8117 const div = document.createElement('div');
8118 div.className = 'treeItem';
8119 const element = document.createElement('a');
8120 div.append(element);
8121 if (typeof groupId === 'object') {
8122 hasAnyNesting = true;
8123 this._addToggleButton(div, groupId);
8124 this._setNestedName(element, groupId);
8125 const itemsDiv = document.createElement('div');
8126 itemsDiv.className = 'treeItems';
8127 div.append(itemsDiv);
8128 queue.push({
8129 parent: itemsDiv,
8130 groups: groupId.order,
8131 });
8132 } else {
8133 const group = optionalContentConfig.getGroup(groupId);
8134 const label = document.createElement('label');
8135 const input = document.createElement('input');
8136 label.append(input, document.createTextNode(this._normalizeTextContent(group.name)));
8137 this._bindLink(element, {
8138 groupId,
8139 input,
8140 });
8141 input.type = 'checkbox';
8142 input.checked = group.visible;
8143 this._optionalContentVisibility.set(groupId, {
8144 input,
8145 visible: input.checked,
8146 });
8147 element.append(label);
8148 layersCount++;
8149 }
8150 levelData.parent.append(div);
8151 }
8152 }
8153 this._finishRendering(fragment, layersCount, hasAnyNesting);
8154 }
8155 async #updateLayers(promise = null) {
8156 if (!this._optionalContentConfig) {
8157 return;
8158 }
8159 const pdfDocument = this._pdfDocument;
8160 const optionalContentConfig = await (promise ||
8161 pdfDocument.getOptionalContentConfig({
8162 intent: 'display',
8163 }));
8164 if (pdfDocument !== this._pdfDocument) {
8165 return;
8166 }
8167 if (promise) {
8168 for (const [groupId, cached] of this._optionalContentVisibility) {
8169 const group = optionalContentConfig.getGroup(groupId);
8170 if (group && cached.visible !== group.visible) {
8171 cached.input.checked = cached.visible = !cached.visible;
8172 }
8173 }
8174 return;
8175 }
8176 this.eventBus.dispatch('optionalcontentconfig', {
8177 source: this,
8178 promise: Promise.resolve(optionalContentConfig),
8179 });
8180 this.render({
8181 optionalContentConfig,
8182 pdfDocument: this._pdfDocument,
8183 });
8184 }
8185} // ./web/pdf_outline_viewer.js
8186
8187class PDFOutlineViewer extends BaseTreeViewer {
8188 constructor(options) {
8189 super(options);
8190 this.linkService = options.linkService;
8191 this.downloadManager = options.downloadManager;
8192 this.eventBus._on('toggleoutlinetree', this._toggleAllTreeItems.bind(this));
8193 this.eventBus._on('currentoutlineitem', this._currentOutlineItem.bind(this));
8194 this.eventBus._on('pagechanging', (evt) => {
8195 this._currentPageNumber = evt.pageNumber;
8196 });
8197 this.eventBus._on('pagesloaded', (evt) => {
8198 this._isPagesLoaded = !!evt.pagesCount;
8199 this._currentOutlineItemCapability?.resolve(this._isPagesLoaded);
8200 });
8201 this.eventBus._on('sidebarviewchanged', (evt) => {
8202 this._sidebarView = evt.view;
8203 });
8204 }
8205 reset() {
8206 super.reset();
8207 this._outline = null;
8208 this._pageNumberToDestHashCapability = null;
8209 this._currentPageNumber = 1;
8210 this._isPagesLoaded = null;
8211 this._currentOutlineItemCapability?.resolve(false);
8212 this._currentOutlineItemCapability = null;
8213 }
8214 _dispatchEvent(outlineCount) {
8215 this._currentOutlineItemCapability = Promise.withResolvers();
8216 if (outlineCount === 0 || this._pdfDocument?.loadingParams.disableAutoFetch) {
8217 this._currentOutlineItemCapability.resolve(false);
8218 } else if (this._isPagesLoaded !== null) {
8219 this._currentOutlineItemCapability.resolve(this._isPagesLoaded);
8220 }
8221 this.eventBus.dispatch('outlineloaded', {
8222 source: this,
8223 outlineCount,
8224 currentOutlineItemPromise: this._currentOutlineItemCapability.promise,
8225 });
8226 }
8227 _bindLink(element, { url, newWindow, action, attachment, dest, setOCGState }) {
8228 const { linkService } = this;
8229 if (url) {
8230 linkService.addLinkAttributes(element, url, newWindow);
8231 return;
8232 }
8233 if (action) {
8234 element.href = linkService.getAnchorUrl('');
8235 element.onclick = () => {
8236 linkService.executeNamedAction(action);
8237 return false;
8238 };
8239 return;
8240 }
8241 if (attachment) {
8242 element.href = linkService.getAnchorUrl('');
8243 element.onclick = () => {
8244 this.downloadManager.openOrDownloadData(attachment.content, attachment.filename);
8245 return false;
8246 };
8247 return;
8248 }
8249 if (setOCGState) {
8250 element.href = linkService.getAnchorUrl('');
8251 element.onclick = () => {
8252 linkService.executeSetOCGState(setOCGState);
8253 return false;
8254 };
8255 return;
8256 }
8257 element.href = linkService.getDestinationHash(dest);
8258 element.onclick = (evt) => {
8259 this._updateCurrentTreeItem(evt.target.parentNode);
8260 if (dest) {
8261 linkService.goToDestination(dest);
8262 }
8263 return false;
8264 };
8265 }
8266 _setStyles(element, { bold, italic }) {
8267 if (bold) {
8268 element.style.fontWeight = 'bold';
8269 }
8270 if (italic) {
8271 element.style.fontStyle = 'italic';
8272 }
8273 }
8274 _addToggleButton(div, { count, items }) {
8275 let hidden = false;
8276 if (count < 0) {
8277 let totalCount = items.length;
8278 if (totalCount > 0) {
8279 const queue = [...items];
8280 while (queue.length > 0) {
8281 const { count: nestedCount, items: nestedItems } = queue.shift();
8282 if (nestedCount > 0 && nestedItems.length > 0) {
8283 totalCount += nestedItems.length;
8284 queue.push(...nestedItems);
8285 }
8286 }
8287 }
8288 if (Math.abs(count) === totalCount) {
8289 hidden = true;
8290 }
8291 }
8292 super._addToggleButton(div, hidden);
8293 }
8294 _toggleAllTreeItems() {
8295 if (!this._outline) {
8296 return;
8297 }
8298 super._toggleAllTreeItems();
8299 }
8300 render({ outline, pdfDocument }) {
8301 if (this._outline) {
8302 this.reset();
8303 }
8304 this._outline = outline || null;
8305 this._pdfDocument = pdfDocument || null;
8306 if (!outline) {
8307 this._dispatchEvent(0);
8308 return;
8309 }
8310 const fragment = document.createDocumentFragment();
8311 const queue = [
8312 {
8313 parent: fragment,
8314 items: outline,
8315 },
8316 ];
8317 let outlineCount = 0,
8318 hasAnyNesting = false;
8319 while (queue.length > 0) {
8320 const levelData = queue.shift();
8321 for (const item of levelData.items) {
8322 const div = document.createElement('div');
8323 div.className = 'treeItem';
8324 const element = document.createElement('a');
8325 this._bindLink(element, item);
8326 this._setStyles(element, item);
8327 element.textContent = this._normalizeTextContent(item.title);
8328 div.append(element);
8329 if (item.items.length > 0) {
8330 hasAnyNesting = true;
8331 this._addToggleButton(div, item);
8332 const itemsDiv = document.createElement('div');
8333 itemsDiv.className = 'treeItems';
8334 div.append(itemsDiv);
8335 queue.push({
8336 parent: itemsDiv,
8337 items: item.items,
8338 });
8339 }
8340 levelData.parent.append(div);
8341 outlineCount++;
8342 }
8343 }
8344 this._finishRendering(fragment, outlineCount, hasAnyNesting);
8345 }
8346 async _currentOutlineItem() {
8347 if (!this._isPagesLoaded) {
8348 throw new Error('_currentOutlineItem: All pages have not been loaded.');
8349 }
8350 if (!this._outline || !this._pdfDocument) {
8351 return;
8352 }
8353 const pageNumberToDestHash = await this._getPageNumberToDestHash(this._pdfDocument);
8354 if (!pageNumberToDestHash) {
8355 return;
8356 }
8357 this._updateCurrentTreeItem(null);
8358 if (this._sidebarView !== SidebarView.OUTLINE) {
8359 return;
8360 }
8361 for (let i = this._currentPageNumber; i > 0; i--) {
8362 const destHash = pageNumberToDestHash.get(i);
8363 if (!destHash) {
8364 continue;
8365 }
8366 const linkElement = this.container.querySelector(`a[href="${destHash}"]`);
8367 if (!linkElement) {
8368 continue;
8369 }
8370 this._scrollToCurrentTreeItem(linkElement.parentNode);
8371 break;
8372 }
8373 }
8374 async _getPageNumberToDestHash(pdfDocument) {
8375 if (this._pageNumberToDestHashCapability) {
8376 return this._pageNumberToDestHashCapability.promise;
8377 }
8378 this._pageNumberToDestHashCapability = Promise.withResolvers();
8379 const pageNumberToDestHash = new Map(),
8380 pageNumberNesting = new Map();
8381 const queue = [
8382 {
8383 nesting: 0,
8384 items: this._outline,
8385 },
8386 ];
8387 while (queue.length > 0) {
8388 const levelData = queue.shift(),
8389 currentNesting = levelData.nesting;
8390 for (const { dest, items } of levelData.items) {
8391 let explicitDest, pageNumber;
8392 if (typeof dest === 'string') {
8393 explicitDest = await pdfDocument.getDestination(dest);
8394 if (pdfDocument !== this._pdfDocument) {
8395 return null;
8396 }
8397 } else {
8398 explicitDest = dest;
8399 }
8400 if (Array.isArray(explicitDest)) {
8401 const [destRef] = explicitDest;
8402 if (destRef && typeof destRef === 'object') {
8403 pageNumber = pdfDocument.cachedPageNumber(destRef);
8404 } else if (Number.isInteger(destRef)) {
8405 pageNumber = destRef + 1;
8406 }
8407 if (
8408 Number.isInteger(pageNumber) &&
8409 (!pageNumberToDestHash.has(pageNumber) ||
8410 currentNesting > pageNumberNesting.get(pageNumber))
8411 ) {
8412 const destHash = this.linkService.getDestinationHash(dest);
8413 pageNumberToDestHash.set(pageNumber, destHash);
8414 pageNumberNesting.set(pageNumber, currentNesting);
8415 }
8416 }
8417 if (items.length > 0) {
8418 queue.push({
8419 nesting: currentNesting + 1,
8420 items,
8421 });
8422 }
8423 }
8424 }
8425 this._pageNumberToDestHashCapability.resolve(
8426 pageNumberToDestHash.size > 0 ? pageNumberToDestHash : null
8427 );
8428 return this._pageNumberToDestHashCapability.promise;
8429 }
8430} // ./web/pdf_presentation_mode.js
8431
8432const DELAY_BEFORE_HIDING_CONTROLS = 3000;
8433const ACTIVE_SELECTOR = 'pdfPresentationMode';
8434const CONTROLS_SELECTOR = 'pdfPresentationModeControls';
8435const MOUSE_SCROLL_COOLDOWN_TIME = 50;
8436const PAGE_SWITCH_THRESHOLD = 0.1;
8437const SWIPE_MIN_DISTANCE_THRESHOLD = 50;
8438const SWIPE_ANGLE_THRESHOLD = Math.PI / 6;
8439class PDFPresentationMode {
8440 #state = PresentationModeState.UNKNOWN;
8441 #args = null;
8442 #fullscreenChangeAbortController = null;
8443 #windowAbortController = null;
8444 constructor({ container, pdfViewer, eventBus }) {
8445 this.container = container;
8446 this.pdfViewer = pdfViewer;
8447 this.eventBus = eventBus;
8448 this.contextMenuOpen = false;
8449 this.mouseScrollTimeStamp = 0;
8450 this.mouseScrollDelta = 0;
8451 this.touchSwipeState = null;
8452 }
8453 async request() {
8454 const { container, pdfViewer } = this;
8455 if (this.active || !pdfViewer.pagesCount || !container.requestFullscreen) {
8456 return false;
8457 }
8458 this.#addFullscreenChangeListeners();
8459 this.#notifyStateChange(PresentationModeState.CHANGING);
8460 const promise = container.requestFullscreen();
8461 this.#args = {
8462 pageNumber: pdfViewer.currentPageNumber,
8463 scaleValue: pdfViewer.currentScaleValue,
8464 scrollMode: pdfViewer.scrollMode,
8465 spreadMode: null,
8466 annotationEditorMode: null,
8467 };
8468 if (
8469 pdfViewer.spreadMode !== SpreadMode.NONE &&
8470 !(pdfViewer.pageViewsReady && pdfViewer.hasEqualPageSizes)
8471 ) {
8472 console.warn(
8473 'Ignoring Spread modes when entering PresentationMode, ' +
8474 'since the document may contain varying page sizes.'
8475 );
8476 this.#args.spreadMode = pdfViewer.spreadMode;
8477 }
8478 if (pdfViewer.annotationEditorMode !== AnnotationEditorType.DISABLE) {
8479 this.#args.annotationEditorMode = pdfViewer.annotationEditorMode;
8480 }
8481 try {
8482 await promise;
8483 pdfViewer.focus();
8484 return true;
8485 } catch {
8486 this.#removeFullscreenChangeListeners();
8487 this.#notifyStateChange(PresentationModeState.NORMAL);
8488 }
8489 return false;
8490 }
8491 get active() {
8492 return (
8493 this.#state === PresentationModeState.CHANGING ||
8494 this.#state === PresentationModeState.FULLSCREEN
8495 );
8496 }
8497 #mouseWheel(evt) {
8498 if (!this.active) {
8499 return;
8500 }
8501 evt.preventDefault();
8502 const delta = normalizeWheelEventDelta(evt);
8503 const currentTime = Date.now();
8504 const storedTime = this.mouseScrollTimeStamp;
8505 if (currentTime > storedTime && currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
8506 return;
8507 }
8508 if ((this.mouseScrollDelta > 0 && delta < 0) || (this.mouseScrollDelta < 0 && delta > 0)) {
8509 this.#resetMouseScrollState();
8510 }
8511 this.mouseScrollDelta += delta;
8512 if (Math.abs(this.mouseScrollDelta) >= PAGE_SWITCH_THRESHOLD) {
8513 const totalDelta = this.mouseScrollDelta;
8514 this.#resetMouseScrollState();
8515 const success = totalDelta > 0 ? this.pdfViewer.previousPage() : this.pdfViewer.nextPage();
8516 if (success) {
8517 this.mouseScrollTimeStamp = currentTime;
8518 }
8519 }
8520 }
8521 #notifyStateChange(state) {
8522 this.#state = state;
8523 this.eventBus.dispatch('presentationmodechanged', {
8524 source: this,
8525 state,
8526 });
8527 }
8528 #enter() {
8529 this.#notifyStateChange(PresentationModeState.FULLSCREEN);
8530 this.container.classList.add(ACTIVE_SELECTOR);
8531 setTimeout(() => {
8532 this.pdfViewer.scrollMode = ScrollMode.PAGE;
8533 if (this.#args.spreadMode !== null) {
8534 this.pdfViewer.spreadMode = SpreadMode.NONE;
8535 }
8536 this.pdfViewer.currentPageNumber = this.#args.pageNumber;
8537 this.pdfViewer.currentScaleValue = 'page-fit';
8538 if (this.#args.annotationEditorMode !== null) {
8539 this.pdfViewer.annotationEditorMode = {
8540 mode: AnnotationEditorType.NONE,
8541 };
8542 }
8543 }, 0);
8544 this.#addWindowListeners();
8545 this.#showControls();
8546 this.contextMenuOpen = false;
8547 document.getSelection().empty();
8548 }
8549 #exit() {
8550 const pageNumber = this.pdfViewer.currentPageNumber;
8551 this.container.classList.remove(ACTIVE_SELECTOR);
8552 setTimeout(() => {
8553 this.#removeFullscreenChangeListeners();
8554 this.#notifyStateChange(PresentationModeState.NORMAL);
8555 this.pdfViewer.scrollMode = this.#args.scrollMode;
8556 if (this.#args.spreadMode !== null) {
8557 this.pdfViewer.spreadMode = this.#args.spreadMode;
8558 }
8559 this.pdfViewer.currentScaleValue = this.#args.scaleValue;
8560 this.pdfViewer.currentPageNumber = pageNumber;
8561 if (this.#args.annotationEditorMode !== null) {
8562 this.pdfViewer.annotationEditorMode = {
8563 mode: this.#args.annotationEditorMode,
8564 };
8565 }
8566 this.#args = null;
8567 }, 0);
8568 this.#removeWindowListeners();
8569 this.#hideControls();
8570 this.#resetMouseScrollState();
8571 this.contextMenuOpen = false;
8572 }
8573 #mouseDown(evt) {
8574 if (this.contextMenuOpen) {
8575 this.contextMenuOpen = false;
8576 evt.preventDefault();
8577 return;
8578 }
8579 if (evt.button !== 0) {
8580 return;
8581 }
8582 if (evt.target.href && evt.target.parentNode?.hasAttribute('data-internal-link')) {
8583 return;
8584 }
8585 evt.preventDefault();
8586 if (evt.shiftKey) {
8587 this.pdfViewer.previousPage();
8588 } else {
8589 this.pdfViewer.nextPage();
8590 }
8591 }
8592 #contextMenu() {
8593 this.contextMenuOpen = true;
8594 }
8595 #showControls() {
8596 if (this.controlsTimeout) {
8597 clearTimeout(this.controlsTimeout);
8598 } else {
8599 this.container.classList.add(CONTROLS_SELECTOR);
8600 }
8601 this.controlsTimeout = setTimeout(() => {
8602 this.container.classList.remove(CONTROLS_SELECTOR);
8603 delete this.controlsTimeout;
8604 }, DELAY_BEFORE_HIDING_CONTROLS);
8605 }
8606 #hideControls() {
8607 if (!this.controlsTimeout) {
8608 return;
8609 }
8610 clearTimeout(this.controlsTimeout);
8611 this.container.classList.remove(CONTROLS_SELECTOR);
8612 delete this.controlsTimeout;
8613 }
8614 #resetMouseScrollState() {
8615 this.mouseScrollTimeStamp = 0;
8616 this.mouseScrollDelta = 0;
8617 }
8618 #touchSwipe(evt) {
8619 if (!this.active) {
8620 return;
8621 }
8622 if (evt.touches.length > 1) {
8623 this.touchSwipeState = null;
8624 return;
8625 }
8626 switch (evt.type) {
8627 case 'touchstart':
8628 this.touchSwipeState = {
8629 startX: evt.touches[0].pageX,
8630 startY: evt.touches[0].pageY,
8631 endX: evt.touches[0].pageX,
8632 endY: evt.touches[0].pageY,
8633 };
8634 break;
8635 case 'touchmove':
8636 if (this.touchSwipeState === null) {
8637 return;
8638 }
8639 this.touchSwipeState.endX = evt.touches[0].pageX;
8640 this.touchSwipeState.endY = evt.touches[0].pageY;
8641 evt.preventDefault();
8642 break;
8643 case 'touchend':
8644 if (this.touchSwipeState === null) {
8645 return;
8646 }
8647 let delta = 0;
8648 const dx = this.touchSwipeState.endX - this.touchSwipeState.startX;
8649 const dy = this.touchSwipeState.endY - this.touchSwipeState.startY;
8650 const absAngle = Math.abs(Math.atan2(dy, dx));
8651 if (
8652 Math.abs(dx) > SWIPE_MIN_DISTANCE_THRESHOLD &&
8653 (absAngle <= SWIPE_ANGLE_THRESHOLD || absAngle >= Math.PI - SWIPE_ANGLE_THRESHOLD)
8654 ) {
8655 delta = dx;
8656 } else if (
8657 Math.abs(dy) > SWIPE_MIN_DISTANCE_THRESHOLD &&
8658 Math.abs(absAngle - Math.PI / 2) <= SWIPE_ANGLE_THRESHOLD
8659 ) {
8660 delta = dy;
8661 }
8662 if (delta > 0) {
8663 this.pdfViewer.previousPage();
8664 } else if (delta < 0) {
8665 this.pdfViewer.nextPage();
8666 }
8667 break;
8668 }
8669 }
8670 #addWindowListeners() {
8671 if (this.#windowAbortController) {
8672 return;
8673 }
8674 this.#windowAbortController = new AbortController();
8675 const { signal } = this.#windowAbortController;
8676 const touchSwipeBind = this.#touchSwipe.bind(this);
8677 window.addEventListener('mousemove', this.#showControls.bind(this), {
8678 signal,
8679 });
8680 window.addEventListener('mousedown', this.#mouseDown.bind(this), {
8681 signal,
8682 });
8683 window.addEventListener('wheel', this.#mouseWheel.bind(this), {
8684 passive: false,
8685 signal,
8686 });
8687 window.addEventListener('keydown', this.#resetMouseScrollState.bind(this), {
8688 signal,
8689 });
8690 window.addEventListener('contextmenu', this.#contextMenu.bind(this), {
8691 signal,
8692 });
8693 window.addEventListener('touchstart', touchSwipeBind, {
8694 signal,
8695 });
8696 window.addEventListener('touchmove', touchSwipeBind, {
8697 signal,
8698 });
8699 window.addEventListener('touchend', touchSwipeBind, {
8700 signal,
8701 });
8702 }
8703 #removeWindowListeners() {
8704 this.#windowAbortController?.abort();
8705 this.#windowAbortController = null;
8706 }
8707 #addFullscreenChangeListeners() {
8708 if (this.#fullscreenChangeAbortController) {
8709 return;
8710 }
8711 this.#fullscreenChangeAbortController = new AbortController();
8712 window.addEventListener(
8713 'fullscreenchange',
8714 () => {
8715 if (document.fullscreenElement) {
8716 this.#enter();
8717 } else {
8718 this.#exit();
8719 }
8720 },
8721 {
8722 signal: this.#fullscreenChangeAbortController.signal,
8723 }
8724 );
8725 }
8726 #removeFullscreenChangeListeners() {
8727 this.#fullscreenChangeAbortController?.abort();
8728 this.#fullscreenChangeAbortController = null;
8729 }
8730} // ./web/xfa_layer_builder.js
8731
8732class XfaLayerBuilder {
8733 constructor({ pdfPage, annotationStorage = null, linkService, xfaHtml = null }) {
8734 this.pdfPage = pdfPage;
8735 this.annotationStorage = annotationStorage;
8736 this.linkService = linkService;
8737 this.xfaHtml = xfaHtml;
8738 this.div = null;
8739 this._cancelled = false;
8740 }
8741 async render({ viewport, intent = 'display' }) {
8742 if (intent === 'print') {
8743 const parameters = {
8744 viewport: viewport.clone({
8745 dontFlip: true,
8746 }),
8747 div: this.div,
8748 xfaHtml: this.xfaHtml,
8749 annotationStorage: this.annotationStorage,
8750 linkService: this.linkService,
8751 intent,
8752 };
8753 this.div = document.createElement('div');
8754 parameters.div = this.div;
8755 return XfaLayer.render(parameters);
8756 }
8757 const xfaHtml = await this.pdfPage.getXfa();
8758 if (this._cancelled || !xfaHtml) {
8759 return {
8760 textDivs: [],
8761 };
8762 }
8763 const parameters = {
8764 viewport: viewport.clone({
8765 dontFlip: true,
8766 }),
8767 div: this.div,
8768 xfaHtml,
8769 annotationStorage: this.annotationStorage,
8770 linkService: this.linkService,
8771 intent,
8772 };
8773 if (this.div) {
8774 return XfaLayer.update(parameters);
8775 }
8776 this.div = document.createElement('div');
8777 parameters.div = this.div;
8778 return XfaLayer.render(parameters);
8779 }
8780 cancel() {
8781 this._cancelled = true;
8782 }
8783 hide() {
8784 if (!this.div) {
8785 return;
8786 }
8787 this.div.hidden = true;
8788 }
8789} // ./web/print_utils.js
8790
8791function getXfaHtmlForPrinting(printContainer, pdfDocument) {
8792 const xfaHtml = pdfDocument.allXfaHtml;
8793 const linkService = new SimpleLinkService();
8794 const scale = Math.round(PixelsPerInch.PDF_TO_CSS_UNITS * 100) / 100;
8795 for (const xfaPage of xfaHtml.children) {
8796 const page = document.createElement('div');
8797 page.className = 'xfaPrintedPage';
8798 printContainer.append(page);
8799 const builder = new XfaLayerBuilder({
8800 pdfPage: null,
8801 annotationStorage: pdfDocument.annotationStorage,
8802 linkService,
8803 xfaHtml: xfaPage,
8804 });
8805 const viewport = getXfaPageViewport(xfaPage, {
8806 scale,
8807 });
8808 builder.render({
8809 viewport,
8810 intent: 'print',
8811 });
8812 page.append(builder.div);
8813 }
8814} // ./web/pdf_print_service.js
8815
8816let activeService = null;
8817let dialog = null;
8818let overlayManager = null;
8819let viewerApp = {
8820 initialized: false,
8821};
8822function renderPage(
8823 activeServiceOnEntry,
8824 pdfDocument,
8825 pageNumber,
8826 size,
8827 printResolution,
8828 optionalContentConfigPromise,
8829 printAnnotationStoragePromise
8830) {
8831 const scratchCanvas = activeService.scratchCanvas;
8832 const PRINT_UNITS = printResolution / PixelsPerInch.PDF;
8833 scratchCanvas.width = Math.floor(size.width * PRINT_UNITS);
8834 scratchCanvas.height = Math.floor(size.height * PRINT_UNITS);
8835 const ctx = scratchCanvas.getContext('2d');
8836 ctx.save();
8837 ctx.fillStyle = 'rgb(255, 255, 255)';
8838 ctx.fillRect(0, 0, scratchCanvas.width, scratchCanvas.height);
8839 ctx.restore();
8840 return Promise.all([pdfDocument.getPage(pageNumber), printAnnotationStoragePromise]).then(
8841 function ([pdfPage, printAnnotationStorage]) {
8842 const renderContext = {
8843 canvas: scratchCanvas,
8844 transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0],
8845 viewport: pdfPage.getViewport({
8846 scale: 1,
8847 rotation: size.rotation,
8848 }),
8849 intent: 'print',
8850 annotationMode: AnnotationMode.ENABLE_STORAGE,
8851 optionalContentConfigPromise,
8852 printAnnotationStorage,
8853 };
8854 const renderTask = pdfPage.render(renderContext);
8855 return renderTask.promise.catch((reason) => {
8856 if (!(reason instanceof RenderingCancelledException)) {
8857 console.error(reason);
8858 }
8859 throw reason;
8860 });
8861 }
8862 );
8863}
8864class PDFPrintService {
8865 constructor({
8866 pdfDocument,
8867 pagesOverview,
8868 printContainer,
8869 printResolution,
8870 printAnnotationStoragePromise = null,
8871 }) {
8872 this.pdfDocument = pdfDocument;
8873 this.pagesOverview = pagesOverview;
8874 this.printContainer = printContainer;
8875 this._printResolution = printResolution || 150;
8876 this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
8877 intent: 'print',
8878 });
8879 this._printAnnotationStoragePromise = printAnnotationStoragePromise || Promise.resolve();
8880 this.currentPage = -1;
8881 this.scratchCanvas = document.createElement('canvas');
8882 }
8883 layout() {
8884 this.throwIfInactive();
8885 const body = document.querySelector('body');
8886 body.setAttribute('data-pdfjsprinting', true);
8887 const { width, height } = this.pagesOverview[0];
8888 const hasEqualPageSizes = this.pagesOverview.every(
8889 (size) => size.width === width && size.height === height
8890 );
8891 if (!hasEqualPageSizes) {
8892 console.warn('Not all pages have the same size. The printed result may be incorrect!');
8893 }
8894 this.pageStyleSheet = document.createElement('style');
8895 this.pageStyleSheet.textContent = `@page { size: ${width}pt ${height}pt;}`;
8896 body.append(this.pageStyleSheet);
8897 }
8898 destroy() {
8899 if (activeService !== this) {
8900 return;
8901 }
8902 this.printContainer.textContent = '';
8903 const body = document.querySelector('body');
8904 body.removeAttribute('data-pdfjsprinting');
8905 if (this.pageStyleSheet) {
8906 this.pageStyleSheet.remove();
8907 this.pageStyleSheet = null;
8908 }
8909 this.scratchCanvas.width = this.scratchCanvas.height = 0;
8910 this.scratchCanvas = null;
8911 activeService = null;
8912 ensureOverlay().then(function () {
8913 overlayManager.closeIfActive(dialog);
8914 });
8915 }
8916 renderPages() {
8917 if (this.pdfDocument.isPureXfa) {
8918 getXfaHtmlForPrinting(this.printContainer, this.pdfDocument);
8919 return Promise.resolve();
8920 }
8921 const pageCount = this.pagesOverview.length;
8922 const renderNextPage = (resolve, reject) => {
8923 this.throwIfInactive();
8924 if (++this.currentPage >= pageCount) {
8925 renderProgress(pageCount, pageCount);
8926 resolve();
8927 return;
8928 }
8929 const index = this.currentPage;
8930 renderProgress(index, pageCount);
8931 renderPage(
8932 this,
8933 this.pdfDocument,
8934 index + 1,
8935 this.pagesOverview[index],
8936 this._printResolution,
8937 this._optionalContentConfigPromise,
8938 this._printAnnotationStoragePromise
8939 )
8940 .then(this.useRenderedPage.bind(this))
8941 .then(function () {
8942 renderNextPage(resolve, reject);
8943 }, reject);
8944 };
8945 return new Promise(renderNextPage);
8946 }
8947 useRenderedPage() {
8948 this.throwIfInactive();
8949 const img = document.createElement('img');
8950 this.scratchCanvas.toBlob((blob) => {
8951 img.src = URL.createObjectURL(blob);
8952 });
8953 const wrapper = document.createElement('div');
8954 wrapper.className = 'printedPage';
8955 wrapper.append(img);
8956 this.printContainer.append(wrapper);
8957 const { promise, resolve, reject } = Promise.withResolvers();
8958 img.onload = resolve;
8959 img.onerror = reject;
8960 promise
8961 .catch(() => {})
8962 .then(() => {
8963 URL.revokeObjectURL(img.src);
8964 });
8965 return promise;
8966 }
8967 performPrint() {
8968 this.throwIfInactive();
8969 return new Promise((resolve) => {
8970 setTimeout(() => {
8971 if (!this.active) {
8972 resolve();
8973 return;
8974 }
8975 print.call(window);
8976 setTimeout(resolve, 20);
8977 }, 0);
8978 });
8979 }
8980 get active() {
8981 return this === activeService;
8982 }
8983 throwIfInactive() {
8984 if (!this.active) {
8985 throw new Error('This print request was cancelled or completed.');
8986 }
8987 }
8988}
8989const print = window.print;
8990window.print = function () {
8991 if (activeService) {
8992 console.warn('Ignored window.print() because of a pending print job.');
8993 return;
8994 }
8995 ensureOverlay().then(function () {
8996 if (activeService) {
8997 overlayManager.open(dialog);
8998 }
8999 });
9000 try {
9001 dispatchEvent('beforeprint');
9002 } finally {
9003 if (!activeService) {
9004 console.error('Expected print service to be initialized.');
9005 ensureOverlay().then(function () {
9006 overlayManager.closeIfActive(dialog);
9007 });
9008 } else {
9009 const activeServiceOnEntry = activeService;
9010 activeService
9011 .renderPages()
9012 .then(() => activeServiceOnEntry.performPrint())
9013 .catch(() => {})
9014 .then(() => {
9015 if (activeServiceOnEntry.active) {
9016 abort();
9017 }
9018 });
9019 }
9020 }
9021};
9022function dispatchEvent(eventType) {
9023 const event = new CustomEvent(eventType, {
9024 bubbles: false,
9025 cancelable: false,
9026 detail: 'custom',
9027 });
9028 window.dispatchEvent(event);
9029}
9030function abort() {
9031 if (activeService) {
9032 activeService.destroy();
9033 dispatchEvent('afterprint');
9034 }
9035}
9036function renderProgress(index, total) {
9037 dialog ||= document.getElementById('printServiceDialog');
9038 const progress = Math.round((100 * index) / total);
9039 const progressBar = dialog.querySelector('progress');
9040 const progressPerc = dialog.querySelector('.relative-progress');
9041 progressBar.value = progress;
9042 progressPerc.setAttribute(
9043 'data-l10n-args',
9044 JSON.stringify({
9045 progress,
9046 })
9047 );
9048}
9049window.addEventListener(
9050 'keydown',
9051 function (event) {
9052 if (
9053 event.keyCode === 80 &&
9054 (event.ctrlKey || event.metaKey) &&
9055 !event.altKey &&
9056 (!event.shiftKey || window.chrome || window.opera)
9057 ) {
9058 window.print();
9059 event.preventDefault();
9060 event.stopImmediatePropagation();
9061 }
9062 },
9063 true
9064);
9065if ('onbeforeprint' in window) {
9066 const stopPropagationIfNeeded = function (event) {
9067 if (event.detail !== 'custom') {
9068 event.stopImmediatePropagation();
9069 }
9070 };
9071 window.addEventListener('beforeprint', stopPropagationIfNeeded);
9072 window.addEventListener('afterprint', stopPropagationIfNeeded);
9073}
9074let overlayPromise;
9075function ensureOverlay() {
9076 if (!overlayPromise) {
9077 overlayManager = viewerApp.overlayManager;
9078 if (!overlayManager) {
9079 throw new Error('The overlay manager has not yet been initialized.');
9080 }
9081 dialog ||= document.getElementById('printServiceDialog');
9082 overlayPromise = overlayManager.register(dialog, true);
9083 document.getElementById('printCancel').onclick = abort;
9084 dialog.addEventListener('close', abort);
9085 }
9086 return overlayPromise;
9087}
9088class PDFPrintServiceFactory {
9089 static initGlobals(app) {
9090 viewerApp = app;
9091 }
9092 static get supportsPrinting() {
9093 return shadow(this, 'supportsPrinting', true);
9094 }
9095 static createPrintService(params) {
9096 if (activeService) {
9097 throw new Error('The print service is created and active.');
9098 }
9099 return (activeService = new PDFPrintService(params));
9100 }
9101} // ./web/pdf_rendering_queue.js
9102
9103const CLEANUP_TIMEOUT = 30000;
9104class PDFRenderingQueue {
9105 constructor() {
9106 this.pdfViewer = null;
9107 this.pdfThumbnailViewer = null;
9108 this.onIdle = null;
9109 this.highestPriorityPage = null;
9110 this.idleTimeout = null;
9111 this.printing = false;
9112 this.isThumbnailViewEnabled = false;
9113 Object.defineProperty(this, 'hasViewer', {
9114 value: () => !!this.pdfViewer,
9115 });
9116 }
9117 setViewer(pdfViewer) {
9118 this.pdfViewer = pdfViewer;
9119 }
9120 setThumbnailViewer(pdfThumbnailViewer) {
9121 this.pdfThumbnailViewer = pdfThumbnailViewer;
9122 }
9123 isHighestPriority(view) {
9124 return this.highestPriorityPage === view.renderingId;
9125 }
9126 renderHighestPriority(currentlyVisiblePages) {
9127 if (this.idleTimeout) {
9128 clearTimeout(this.idleTimeout);
9129 this.idleTimeout = null;
9130 }
9131 if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
9132 return;
9133 }
9134 if (this.isThumbnailViewEnabled && this.pdfThumbnailViewer?.forceRendering()) {
9135 return;
9136 }
9137 if (this.printing) {
9138 return;
9139 }
9140 if (this.onIdle) {
9141 this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
9142 }
9143 }
9144 getHighestPriority(
9145 visible,
9146 views,
9147 scrolledDown,
9148 preRenderExtra = false,
9149 ignoreDetailViews = false
9150 ) {
9151 const visibleViews = visible.views,
9152 numVisible = visibleViews.length;
9153 if (numVisible === 0) {
9154 return null;
9155 }
9156 for (let i = 0; i < numVisible; i++) {
9157 const view = visibleViews[i].view;
9158 if (!this.isViewFinished(view)) {
9159 return view;
9160 }
9161 }
9162 if (!ignoreDetailViews) {
9163 for (let i = 0; i < numVisible; i++) {
9164 const { detailView } = visibleViews[i].view;
9165 if (detailView && !this.isViewFinished(detailView)) {
9166 return detailView;
9167 }
9168 }
9169 }
9170 const firstId = visible.first.id,
9171 lastId = visible.last.id;
9172 if (lastId - firstId + 1 > numVisible) {
9173 const visibleIds = visible.ids;
9174 for (let i = 1, ii = lastId - firstId; i < ii; i++) {
9175 const holeId = scrolledDown ? firstId + i : lastId - i;
9176 if (visibleIds.has(holeId)) {
9177 continue;
9178 }
9179 const holeView = views[holeId - 1];
9180 if (!this.isViewFinished(holeView)) {
9181 return holeView;
9182 }
9183 }
9184 }
9185 let preRenderIndex = scrolledDown ? lastId : firstId - 2;
9186 let preRenderView = views[preRenderIndex];
9187 if (preRenderView && !this.isViewFinished(preRenderView)) {
9188 return preRenderView;
9189 }
9190 if (preRenderExtra) {
9191 preRenderIndex += scrolledDown ? 1 : -1;
9192 preRenderView = views[preRenderIndex];
9193 if (preRenderView && !this.isViewFinished(preRenderView)) {
9194 return preRenderView;
9195 }
9196 }
9197 return null;
9198 }
9199 isViewFinished(view) {
9200 return view.renderingState === RenderingStates.FINISHED;
9201 }
9202 renderView(view) {
9203 switch (view.renderingState) {
9204 case RenderingStates.FINISHED:
9205 return false;
9206 case RenderingStates.PAUSED:
9207 this.highestPriorityPage = view.renderingId;
9208 view.resume();
9209 break;
9210 case RenderingStates.RUNNING:
9211 this.highestPriorityPage = view.renderingId;
9212 break;
9213 case RenderingStates.INITIAL:
9214 this.highestPriorityPage = view.renderingId;
9215 view
9216 .draw()
9217 .finally(() => {
9218 this.renderHighestPriority();
9219 })
9220 .catch((reason) => {
9221 if (reason instanceof RenderingCancelledException) {
9222 return;
9223 }
9224 console.error('renderView:', reason);
9225 });
9226 break;
9227 }
9228 return true;
9229 }
9230} // ./web/pdf_scripting_manager.js
9231
9232class PDFScriptingManager {
9233 #closeCapability = null;
9234 #destroyCapability = null;
9235 #docProperties = null;
9236 #eventAbortController = null;
9237 #eventBus = null;
9238 #externalServices = null;
9239 #pdfDocument = null;
9240 #pdfViewer = null;
9241 #ready = false;
9242 #scripting = null;
9243 #willPrintCapability = null;
9244 constructor({ eventBus, externalServices = null, docProperties = null }) {
9245 this.#eventBus = eventBus;
9246 this.#externalServices = externalServices;
9247 this.#docProperties = docProperties;
9248 }
9249 setViewer(pdfViewer) {
9250 this.#pdfViewer = pdfViewer;
9251 }
9252 async setDocument(pdfDocument) {
9253 if (this.#pdfDocument) {
9254 await this.#destroyScripting();
9255 }
9256 this.#pdfDocument = pdfDocument;
9257 if (!pdfDocument) {
9258 return;
9259 }
9260 const [objects, calculationOrder, docActions] = await Promise.all([
9261 pdfDocument.getFieldObjects(),
9262 pdfDocument.getCalculationOrderIds(),
9263 pdfDocument.getJSActions(),
9264 ]);
9265 if (!objects && !docActions) {
9266 await this.#destroyScripting();
9267 return;
9268 }
9269 if (pdfDocument !== this.#pdfDocument) {
9270 return;
9271 }
9272 try {
9273 this.#scripting = this.#initScripting();
9274 } catch (error) {
9275 console.error('setDocument:', error);
9276 await this.#destroyScripting();
9277 return;
9278 }
9279 const eventBus = this.#eventBus;
9280 this.#eventAbortController = new AbortController();
9281 const { signal } = this.#eventAbortController;
9282 eventBus._on(
9283 'updatefromsandbox',
9284 (event) => {
9285 if (event?.source === window) {
9286 this.#updateFromSandbox(event.detail);
9287 }
9288 },
9289 {
9290 signal,
9291 }
9292 );
9293 eventBus._on(
9294 'dispatcheventinsandbox',
9295 (event) => {
9296 this.#scripting?.dispatchEventInSandbox(event.detail);
9297 },
9298 {
9299 signal,
9300 }
9301 );
9302 eventBus._on(
9303 'pagechanging',
9304 ({ pageNumber, previous }) => {
9305 if (pageNumber === previous) {
9306 return;
9307 }
9308 this.#dispatchPageClose(previous);
9309 this.#dispatchPageOpen(pageNumber);
9310 },
9311 {
9312 signal,
9313 }
9314 );
9315 eventBus._on(
9316 'pagerendered',
9317 ({ pageNumber }) => {
9318 if (!this._pageOpenPending.has(pageNumber)) {
9319 return;
9320 }
9321 if (pageNumber !== this.#pdfViewer.currentPageNumber) {
9322 return;
9323 }
9324 this.#dispatchPageOpen(pageNumber);
9325 },
9326 {
9327 signal,
9328 }
9329 );
9330 eventBus._on(
9331 'pagesdestroy',
9332 async () => {
9333 await this.#dispatchPageClose(this.#pdfViewer.currentPageNumber);
9334 await this.#scripting?.dispatchEventInSandbox({
9335 id: 'doc',
9336 name: 'WillClose',
9337 });
9338 this.#closeCapability?.resolve();
9339 },
9340 {
9341 signal,
9342 }
9343 );
9344 try {
9345 const docProperties = await this.#docProperties(pdfDocument);
9346 if (pdfDocument !== this.#pdfDocument) {
9347 return;
9348 }
9349 await this.#scripting.createSandbox({
9350 objects,
9351 calculationOrder,
9352 appInfo: {
9353 platform: navigator.platform,
9354 language: navigator.language,
9355 },
9356 docInfo: {
9357 ...docProperties,
9358 actions: docActions,
9359 },
9360 });
9361 eventBus.dispatch('sandboxcreated', {
9362 source: this,
9363 });
9364 } catch (error) {
9365 console.error('setDocument:', error);
9366 await this.#destroyScripting();
9367 return;
9368 }
9369 await this.#scripting?.dispatchEventInSandbox({
9370 id: 'doc',
9371 name: 'Open',
9372 });
9373 await this.#dispatchPageOpen(this.#pdfViewer.currentPageNumber, true);
9374 Promise.resolve().then(() => {
9375 if (pdfDocument === this.#pdfDocument) {
9376 this.#ready = true;
9377 }
9378 });
9379 }
9380 async dispatchWillSave() {
9381 return this.#scripting?.dispatchEventInSandbox({
9382 id: 'doc',
9383 name: 'WillSave',
9384 });
9385 }
9386 async dispatchDidSave() {
9387 return this.#scripting?.dispatchEventInSandbox({
9388 id: 'doc',
9389 name: 'DidSave',
9390 });
9391 }
9392 async dispatchWillPrint() {
9393 if (!this.#scripting) {
9394 return;
9395 }
9396 await this.#willPrintCapability?.promise;
9397 this.#willPrintCapability = Promise.withResolvers();
9398 try {
9399 await this.#scripting.dispatchEventInSandbox({
9400 id: 'doc',
9401 name: 'WillPrint',
9402 });
9403 } catch (ex) {
9404 this.#willPrintCapability.resolve();
9405 this.#willPrintCapability = null;
9406 throw ex;
9407 }
9408 await this.#willPrintCapability.promise;
9409 }
9410 async dispatchDidPrint() {
9411 return this.#scripting?.dispatchEventInSandbox({
9412 id: 'doc',
9413 name: 'DidPrint',
9414 });
9415 }
9416 get destroyPromise() {
9417 return this.#destroyCapability?.promise || null;
9418 }
9419 get ready() {
9420 return this.#ready;
9421 }
9422 get _pageOpenPending() {
9423 return shadow(this, '_pageOpenPending', new Set());
9424 }
9425 get _visitedPages() {
9426 return shadow(this, '_visitedPages', new Map());
9427 }
9428 async #updateFromSandbox(detail) {
9429 const pdfViewer = this.#pdfViewer;
9430 const isInPresentationMode =
9431 pdfViewer.isInPresentationMode || pdfViewer.isChangingPresentationMode;
9432 const { id, siblings, command, value } = detail;
9433 if (!id) {
9434 switch (command) {
9435 case 'clear':
9436 console.clear();
9437 break;
9438 case 'error':
9439 console.error(value);
9440 break;
9441 case 'layout':
9442 if (!isInPresentationMode) {
9443 const modes = apiPageLayoutToViewerModes(value);
9444 pdfViewer.spreadMode = modes.spreadMode;
9445 }
9446 break;
9447 case 'page-num':
9448 pdfViewer.currentPageNumber = value + 1;
9449 break;
9450 case 'print':
9451 await pdfViewer.pagesPromise;
9452 this.#eventBus.dispatch('print', {
9453 source: this,
9454 });
9455 break;
9456 case 'println':
9457 console.log(value);
9458 break;
9459 case 'zoom':
9460 if (!isInPresentationMode) {
9461 pdfViewer.currentScaleValue = value;
9462 }
9463 break;
9464 case 'SaveAs':
9465 this.#eventBus.dispatch('download', {
9466 source: this,
9467 });
9468 break;
9469 case 'FirstPage':
9470 pdfViewer.currentPageNumber = 1;
9471 break;
9472 case 'LastPage':
9473 pdfViewer.currentPageNumber = pdfViewer.pagesCount;
9474 break;
9475 case 'NextPage':
9476 pdfViewer.nextPage();
9477 break;
9478 case 'PrevPage':
9479 pdfViewer.previousPage();
9480 break;
9481 case 'ZoomViewIn':
9482 if (!isInPresentationMode) {
9483 pdfViewer.increaseScale();
9484 }
9485 break;
9486 case 'ZoomViewOut':
9487 if (!isInPresentationMode) {
9488 pdfViewer.decreaseScale();
9489 }
9490 break;
9491 case 'WillPrintFinished':
9492 this.#willPrintCapability?.resolve();
9493 this.#willPrintCapability = null;
9494 break;
9495 }
9496 return;
9497 }
9498 if (isInPresentationMode && detail.focus) {
9499 return;
9500 }
9501 delete detail.id;
9502 delete detail.siblings;
9503 const ids = siblings ? [id, ...siblings] : [id];
9504 for (const elementId of ids) {
9505 const element = document.querySelector(`[data-element-id="${elementId}"]`);
9506 if (element) {
9507 element.dispatchEvent(
9508 new CustomEvent('updatefromsandbox', {
9509 detail,
9510 })
9511 );
9512 } else {
9513 this.#pdfDocument?.annotationStorage.setValue(elementId, detail);
9514 }
9515 }
9516 }
9517 async #dispatchPageOpen(pageNumber, initialize = false) {
9518 const pdfDocument = this.#pdfDocument,
9519 visitedPages = this._visitedPages;
9520 if (initialize) {
9521 this.#closeCapability = Promise.withResolvers();
9522 }
9523 if (!this.#closeCapability) {
9524 return;
9525 }
9526 const pageView = this.#pdfViewer.getPageView(pageNumber - 1);
9527 if (pageView?.renderingState !== RenderingStates.FINISHED) {
9528 this._pageOpenPending.add(pageNumber);
9529 return;
9530 }
9531 this._pageOpenPending.delete(pageNumber);
9532 const actionsPromise = (async () => {
9533 const actions = await (!visitedPages.has(pageNumber)
9534 ? pageView.pdfPage?.getJSActions()
9535 : null);
9536 if (pdfDocument !== this.#pdfDocument) {
9537 return;
9538 }
9539 await this.#scripting?.dispatchEventInSandbox({
9540 id: 'page',
9541 name: 'PageOpen',
9542 pageNumber,
9543 actions,
9544 });
9545 })();
9546 visitedPages.set(pageNumber, actionsPromise);
9547 }
9548 async #dispatchPageClose(pageNumber) {
9549 const pdfDocument = this.#pdfDocument,
9550 visitedPages = this._visitedPages;
9551 if (!this.#closeCapability) {
9552 return;
9553 }
9554 if (this._pageOpenPending.has(pageNumber)) {
9555 return;
9556 }
9557 const actionsPromise = visitedPages.get(pageNumber);
9558 if (!actionsPromise) {
9559 return;
9560 }
9561 visitedPages.set(pageNumber, null);
9562 await actionsPromise;
9563 if (pdfDocument !== this.#pdfDocument) {
9564 return;
9565 }
9566 await this.#scripting?.dispatchEventInSandbox({
9567 id: 'page',
9568 name: 'PageClose',
9569 pageNumber,
9570 });
9571 }
9572 #initScripting() {
9573 this.#destroyCapability = Promise.withResolvers();
9574 if (this.#scripting) {
9575 throw new Error('#initScripting: Scripting already exists.');
9576 }
9577 return this.#externalServices.createScripting();
9578 }
9579 async #destroyScripting() {
9580 if (!this.#scripting) {
9581 this.#pdfDocument = null;
9582 this.#destroyCapability?.resolve();
9583 return;
9584 }
9585 if (this.#closeCapability) {
9586 await Promise.race([
9587 this.#closeCapability.promise,
9588 new Promise((resolve) => {
9589 setTimeout(resolve, 1000);
9590 }),
9591 ]).catch(() => {});
9592 this.#closeCapability = null;
9593 }
9594 this.#pdfDocument = null;
9595 try {
9596 await this.#scripting.destroySandbox();
9597 } catch {}
9598 this.#willPrintCapability?.reject(new Error('Scripting destroyed.'));
9599 this.#willPrintCapability = null;
9600 this.#eventAbortController?.abort();
9601 this.#eventAbortController = null;
9602 this._pageOpenPending.clear();
9603 this._visitedPages.clear();
9604 this.#scripting = null;
9605 this.#ready = false;
9606 this.#destroyCapability?.resolve();
9607 }
9608} // ./web/pdf_text_extractor.js
9609
9610class PdfTextExtractor {
9611 #pdfViewer;
9612 #externalServices;
9613 #textPromise;
9614 #pendingRequests = new Set();
9615 constructor(externalServices) {
9616 this.#externalServices = externalServices;
9617 window.addEventListener('requestTextContent', ({ detail }) => {
9618 this.extractTextContent(detail.requestId);
9619 });
9620 }
9621 setViewer(pdfViewer) {
9622 this.#pdfViewer = pdfViewer;
9623 if (this.#pdfViewer && this.#pendingRequests.size) {
9624 for (const pendingRequest of this.#pendingRequests) {
9625 this.extractTextContent(pendingRequest);
9626 }
9627 this.#pendingRequests.clear();
9628 }
9629 }
9630 async extractTextContent(requestId) {
9631 if (!this.#pdfViewer) {
9632 this.#pendingRequests.add(requestId);
9633 return;
9634 }
9635 if (!this.#textPromise) {
9636 const textPromise = (this.#textPromise = this.#pdfViewer.getAllText());
9637 textPromise.then(() => {
9638 setTimeout(() => {
9639 if (this.#textPromise === textPromise) {
9640 this.#textPromise = null;
9641 }
9642 }, 5000);
9643 });
9644 }
9645 this.#externalServices.reportText({
9646 text: await this.#textPromise,
9647 requestId,
9648 });
9649 }
9650} // ./web/menu.js
9651
9652class Menu {
9653 #triggeringButton;
9654 #menu;
9655 #menuItems;
9656 #openMenuAC = null;
9657 #menuAC = new AbortController();
9658 #lastIndex = -1;
9659 constructor(menuContainer, triggeringButton, menuItems) {
9660 this.#menu = menuContainer;
9661 this.#triggeringButton = triggeringButton;
9662 if (Array.isArray(menuItems)) {
9663 this.#menuItems = menuItems;
9664 } else {
9665 this.#menuItems = [];
9666 for (const button of this.#menu.querySelectorAll('button')) {
9667 this.#menuItems.push(button);
9668 }
9669 }
9670 this.#setUpMenu();
9671 }
9672 #closeMenu() {
9673 if (!this.#openMenuAC) {
9674 return;
9675 }
9676 const menu = this.#menu;
9677 this.#triggeringButton.ariaExpanded = 'false';
9678 this.#openMenuAC.abort();
9679 this.#openMenuAC = null;
9680 if (menu.contains(document.activeElement)) {
9681 setTimeout(() => {
9682 if (!menu.contains(document.activeElement)) {
9683 this.#triggeringButton.focus();
9684 }
9685 }, 0);
9686 }
9687 this.#lastIndex = -1;
9688 }
9689 #setUpMenu() {
9690 this.#triggeringButton.addEventListener('click', (e) => {
9691 if (this.#openMenuAC) {
9692 this.#closeMenu();
9693 return;
9694 }
9695 const menu = this.#menu;
9696 this.#triggeringButton.ariaExpanded = 'true';
9697 this.#openMenuAC = new AbortController();
9698 const signal = AbortSignal.any([this.#menuAC.signal, this.#openMenuAC.signal]);
9699 window.addEventListener(
9700 'pointerdown',
9701 ({ target }) => {
9702 if (target !== this.#triggeringButton && !menu.contains(target)) {
9703 this.#closeMenu();
9704 }
9705 },
9706 {
9707 signal,
9708 }
9709 );
9710 window.addEventListener('blur', this.#closeMenu.bind(this), {
9711 signal,
9712 });
9713 });
9714 const { signal } = this.#menuAC;
9715 this.#menu.addEventListener(
9716 'keydown',
9717 (e) => {
9718 switch (e.key) {
9719 case 'Escape':
9720 this.#closeMenu();
9721 stopEvent(e);
9722 break;
9723 case 'ArrowDown':
9724 case 'Tab':
9725 this.#goToNextItem(e.target, true);
9726 stopEvent(e);
9727 break;
9728 case 'ArrowUp':
9729 case 'ShiftTab':
9730 this.#goToNextItem(e.target, false);
9731 stopEvent(e);
9732 break;
9733 case 'Home':
9734 this.#menuItems
9735 .find((item) => !item.disabled && !item.classList.contains('hidden'))
9736 .focus();
9737 stopEvent(e);
9738 break;
9739 case 'End':
9740 this.#menuItems
9741 .findLast((item) => !item.disabled && !item.classList.contains('hidden'))
9742 .focus();
9743 stopEvent(e);
9744 break;
9745 default:
9746 const char = e.key.toLocaleLowerCase();
9747 this.#goToNextItem(e.target, true, (item) =>
9748 item.textContent.trim().toLowerCase().startsWith(char)
9749 );
9750 stopEvent(e);
9751 break;
9752 }
9753 },
9754 {
9755 signal,
9756 capture: true,
9757 }
9758 );
9759 this.#menu.addEventListener('contextmenu', noContextMenu, {
9760 signal,
9761 });
9762 this.#menu.addEventListener('click', this.#closeMenu.bind(this), {
9763 signal,
9764 capture: true,
9765 });
9766 this.#triggeringButton.addEventListener(
9767 'keydown',
9768 (e) => {
9769 switch (e.key) {
9770 case ' ':
9771 case 'Enter':
9772 case 'ArrowDown':
9773 case 'Home':
9774 if (!this.#openMenuAC) {
9775 this.#triggeringButton.click();
9776 }
9777 this.#menuItems
9778 .find((item) => !item.disabled && !item.classList.contains('hidden'))
9779 .focus();
9780 stopEvent(e);
9781 break;
9782 case 'ArrowUp':
9783 case 'End':
9784 if (!this.#openMenuAC) {
9785 this.#triggeringButton.click();
9786 }
9787 this.#menuItems
9788 .findLast((item) => !item.disabled && !item.classList.contains('hidden'))
9789 .focus();
9790 stopEvent(e);
9791 break;
9792 case 'Escape':
9793 this.#closeMenu();
9794 stopEvent(e);
9795 break;
9796 }
9797 },
9798 {
9799 signal,
9800 }
9801 );
9802 }
9803 #goToNextItem(element, forward, check = () => true) {
9804 const index = this.#lastIndex === -1 ? this.#menuItems.indexOf(element) : this.#lastIndex;
9805 const len = this.#menuItems.length;
9806 const increment = forward ? 1 : len - 1;
9807 for (let i = (index + increment) % len; i !== index; i = (i + increment) % len) {
9808 const menuItem = this.#menuItems[i];
9809 if (!menuItem.disabled && !menuItem.classList.contains('hidden') && check(menuItem)) {
9810 menuItem.focus();
9811 this.#lastIndex = i;
9812 break;
9813 }
9814 }
9815 }
9816 destroy() {
9817 this.#closeMenu();
9818 this.#menuAC?.abort();
9819 this.#menuAC = null;
9820 }
9821} // ./web/pdf_thumbnail_view.js
9822
9823const DRAW_UPSCALE_FACTOR = 2;
9824const MAX_NUM_SCALING_STEPS = 3;
9825const THUMBNAIL_WIDTH = 126;
9826class TempImageFactory {
9827 static getCanvas(width, height) {
9828 let tempCanvas;
9829 if (FeatureTest.isOffscreenCanvasSupported) {
9830 tempCanvas = new OffscreenCanvas(width, height);
9831 } else {
9832 tempCanvas = document.createElement('canvas');
9833 tempCanvas.width = width;
9834 tempCanvas.height = height;
9835 }
9836 const ctx = tempCanvas.getContext('2d', {
9837 alpha: false,
9838 });
9839 ctx.save();
9840 ctx.fillStyle = 'rgb(255, 255, 255)';
9841 ctx.fillRect(0, 0, width, height);
9842 ctx.restore();
9843 return [tempCanvas, ctx];
9844 }
9845}
9846class PDFThumbnailView {
9847 constructor({
9848 container,
9849 eventBus,
9850 id,
9851 defaultViewport,
9852 optionalContentConfigPromise,
9853 linkService,
9854 renderingQueue,
9855 maxCanvasPixels,
9856 maxCanvasDim,
9857 pageColors,
9858 enableSplitMerge = false,
9859 }) {
9860 this.id = id;
9861 this.renderingId = `thumbnail${id}`;
9862 this.pageLabel = null;
9863 this.pdfPage = null;
9864 this.rotation = 0;
9865 this.viewport = defaultViewport;
9866 this.pdfPageRotate = defaultViewport.rotation;
9867 this._optionalContentConfigPromise = optionalContentConfigPromise || null;
9868 this.maxCanvasPixels = maxCanvasPixels ?? AppOptions.get('maxCanvasPixels');
9869 this.maxCanvasDim = maxCanvasDim || AppOptions.get('maxCanvasDim');
9870 this.pageColors = pageColors || null;
9871 this.eventBus = eventBus;
9872 this.linkService = linkService;
9873 this.renderingQueue = renderingQueue;
9874 this.renderTask = null;
9875 this.renderingState = RenderingStates.INITIAL;
9876 this.resume = null;
9877 this.placeholder = null;
9878 const imageContainer = (this.div = document.createElement('div'));
9879 imageContainer.className = 'thumbnail';
9880 imageContainer.setAttribute('page-number', id);
9881 imageContainer.setAttribute('page-id', id);
9882 if (enableSplitMerge) {
9883 const checkbox = (this.checkbox = document.createElement('input'));
9884 checkbox.type = 'checkbox';
9885 checkbox.tabIndex = -1;
9886 imageContainer.append(checkbox);
9887 }
9888 const image = (this.image = document.createElement('img'));
9889 image.classList.add('thumbnailImage', 'missingThumbnailImage');
9890 image.role = 'button';
9891 image.tabIndex = -1;
9892 image.draggable = false;
9893 this.#updateDims();
9894 imageContainer.append(image);
9895 container.append(imageContainer);
9896 }
9897 updateId(newId) {
9898 this.id = newId;
9899 this.renderingId = `thumbnail${newId}`;
9900 this.div.setAttribute('page-number', newId);
9901 this.setPageLabel(this.pageLabel);
9902 }
9903 #updateDims() {
9904 const { width, height } = this.viewport;
9905 const ratio = width / height;
9906 const canvasWidth = (this.canvasWidth = THUMBNAIL_WIDTH);
9907 const canvasHeight = (this.canvasHeight = (canvasWidth / ratio) | 0);
9908 this.scale = canvasWidth / width;
9909 this.image.style.height = `${canvasHeight}px`;
9910 }
9911 setPdfPage(pdfPage) {
9912 this.pdfPage = pdfPage;
9913 this.pdfPageRotate = pdfPage.rotate;
9914 const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
9915 this.viewport = pdfPage.getViewport({
9916 scale: 1,
9917 rotation: totalRotation,
9918 });
9919 this.reset();
9920 }
9921 reset() {
9922 this.cancelRendering();
9923 this.renderingState = RenderingStates.INITIAL;
9924 this.#updateDims();
9925 const { image } = this;
9926 const url = image.src;
9927 if (url) {
9928 URL.revokeObjectURL(url);
9929 image.removeAttribute('data-l10n-id');
9930 image.removeAttribute('data-l10n-args');
9931 image.src = '';
9932 this.image.classList.add('missingThumbnailImage');
9933 }
9934 }
9935 update({ rotation = null }) {
9936 if (typeof rotation === 'number') {
9937 this.rotation = rotation;
9938 }
9939 const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
9940 this.viewport = this.viewport.clone({
9941 scale: 1,
9942 rotation: totalRotation,
9943 });
9944 this.reset();
9945 }
9946 toggleCurrent(isCurrent) {
9947 if (isCurrent) {
9948 this.image.ariaCurrent = 'page';
9949 this.image.tabIndex = 0;
9950 } else {
9951 this.image.ariaCurrent = false;
9952 this.image.tabIndex = -1;
9953 }
9954 }
9955 cancelRendering() {
9956 if (this.renderTask) {
9957 this.renderTask.cancel();
9958 this.renderTask = null;
9959 }
9960 this.resume = null;
9961 }
9962 #getPageDrawContext(upscaleFactor = 1) {
9963 const outputScale = new OutputScale();
9964 const width = upscaleFactor * this.canvasWidth,
9965 height = upscaleFactor * this.canvasHeight;
9966 outputScale.limitCanvas(width, height, this.maxCanvasPixels, this.maxCanvasDim);
9967 const canvas = document.createElement('canvas');
9968 canvas.width = (width * outputScale.sx) | 0;
9969 canvas.height = (height * outputScale.sy) | 0;
9970 const transform = outputScale.scaled ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] : null;
9971 return {
9972 canvas,
9973 transform,
9974 };
9975 }
9976 async #convertCanvasToImage(canvas) {
9977 if (this.renderingState !== RenderingStates.FINISHED) {
9978 throw new Error('#convertCanvasToImage: Rendering has not finished.');
9979 }
9980 const reducedCanvas = this.#reduceImage(canvas);
9981 const { image } = this;
9982 const { promise, resolve } = Promise.withResolvers();
9983 reducedCanvas.toBlob(resolve);
9984 const blob = await promise;
9985 image.src = URL.createObjectURL(blob);
9986 image.setAttribute('data-l10n-id', 'pdfjs-thumb-page-canvas');
9987 image.setAttribute('data-l10n-args', this.#pageL10nArgs);
9988 image.classList.remove('missingThumbnailImage');
9989 if (!FeatureTest.isOffscreenCanvasSupported) {
9990 reducedCanvas.width = reducedCanvas.height = 0;
9991 }
9992 }
9993 async draw() {
9994 if (this.renderingState !== RenderingStates.INITIAL) {
9995 console.error('Must be in new state before drawing');
9996 return;
9997 }
9998 const { pageColors, pdfPage } = this;
9999 if (!pdfPage) {
10000 this.renderingState = RenderingStates.FINISHED;
10001 throw new Error('pdfPage is not loaded');
10002 }
10003 this.renderingState = RenderingStates.RUNNING;
10004 const { canvas, transform } = this.#getPageDrawContext(DRAW_UPSCALE_FACTOR);
10005 const drawViewport = this.viewport.clone({
10006 scale: DRAW_UPSCALE_FACTOR * this.scale,
10007 });
10008 const renderContinueCallback = (cont) => {
10009 if (!this.renderingQueue.isHighestPriority(this)) {
10010 this.renderingState = RenderingStates.PAUSED;
10011 this.resume = () => {
10012 this.renderingState = RenderingStates.RUNNING;
10013 cont();
10014 };
10015 return;
10016 }
10017 cont();
10018 };
10019 const renderContext = {
10020 canvas,
10021 transform,
10022 viewport: drawViewport,
10023 optionalContentConfigPromise: this._optionalContentConfigPromise,
10024 pageColors,
10025 };
10026 const renderTask = (this.renderTask = pdfPage.render(renderContext));
10027 renderTask.onContinue = renderContinueCallback;
10028 let error = null;
10029 try {
10030 await renderTask.promise;
10031 } catch (e) {
10032 if (e instanceof RenderingCancelledException) {
10033 return;
10034 }
10035 error = e;
10036 } finally {
10037 if (renderTask === this.renderTask) {
10038 this.renderTask = null;
10039 }
10040 }
10041 this.renderingState = RenderingStates.FINISHED;
10042 await this.#convertCanvasToImage(canvas);
10043 this.eventBus.dispatch('thumbnailrendered', {
10044 source: this,
10045 pageNumber: this.id,
10046 pdfPage,
10047 });
10048 if (error) {
10049 throw error;
10050 }
10051 }
10052 setImage(pageView) {
10053 if (this.renderingState !== RenderingStates.INITIAL) {
10054 return;
10055 }
10056 const { thumbnailCanvas: canvas, pdfPage, scale } = pageView;
10057 if (!canvas) {
10058 return;
10059 }
10060 if (!this.pdfPage) {
10061 this.setPdfPage(pdfPage);
10062 }
10063 if (scale < this.scale) {
10064 return;
10065 }
10066 this.renderingState = RenderingStates.FINISHED;
10067 this.#convertCanvasToImage(canvas);
10068 }
10069 #getReducedImageDims(canvas) {
10070 const width = canvas.width << MAX_NUM_SCALING_STEPS,
10071 height = canvas.height << MAX_NUM_SCALING_STEPS;
10072 const outputScale = new OutputScale();
10073 outputScale.sx = outputScale.sy = 1;
10074 outputScale.limitCanvas(width, height, this.maxCanvasPixels, this.maxCanvasDim);
10075 return [(width * outputScale.sx) | 0, (height * outputScale.sy) | 0];
10076 }
10077 #reduceImage(img) {
10078 const { canvas } = this.#getPageDrawContext(1);
10079 const ctx = canvas.getContext('2d', {
10080 alpha: false,
10081 willReadFrequently: false,
10082 });
10083 if (img.width <= 2 * canvas.width) {
10084 ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
10085 return canvas;
10086 }
10087 let [reducedWidth, reducedHeight] = this.#getReducedImageDims(canvas);
10088 const [reducedImage, reducedImageCtx] = TempImageFactory.getCanvas(reducedWidth, reducedHeight);
10089 while (reducedWidth > img.width || reducedHeight > img.height) {
10090 reducedWidth >>= 1;
10091 reducedHeight >>= 1;
10092 }
10093 reducedImageCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, reducedWidth, reducedHeight);
10094 while (reducedWidth > 2 * canvas.width) {
10095 reducedImageCtx.drawImage(
10096 reducedImage,
10097 0,
10098 0,
10099 reducedWidth,
10100 reducedHeight,
10101 0,
10102 0,
10103 reducedWidth >> 1,
10104 reducedHeight >> 1
10105 );
10106 reducedWidth >>= 1;
10107 reducedHeight >>= 1;
10108 }
10109 ctx.drawImage(
10110 reducedImage,
10111 0,
10112 0,
10113 reducedWidth,
10114 reducedHeight,
10115 0,
10116 0,
10117 canvas.width,
10118 canvas.height
10119 );
10120 return canvas;
10121 }
10122 get #pageL10nArgs() {
10123 return JSON.stringify({
10124 page: this.pageLabel ?? this.id,
10125 });
10126 }
10127 setPageLabel(label) {
10128 this.pageLabel = typeof label === 'string' ? label : null;
10129 this.image.setAttribute('data-l10n-args', this.#pageL10nArgs);
10130 }
10131} // ./web/pdf_thumbnail_viewer.js
10132
10133const SCROLL_OPTIONS = {
10134 behavior: 'instant',
10135 block: 'nearest',
10136 inline: 'nearest',
10137 container: 'nearest',
10138};
10139const DRAG_THRESHOLD_IN_PIXELS = 5;
10140const PIXELS_TO_SCROLL_WHEN_DRAGGING = 20;
10141const SPACE_FOR_DRAG_MARKER_WHEN_NO_NEXT_ELEMENT = 15;
10142class PDFThumbnailViewer {
10143 static #draggingScaleFactor = 0;
10144 #enableSplitMerge = false;
10145 #dragAC = null;
10146 #draggedContainer = null;
10147 #thumbnailsPositions = null;
10148 #lastDraggedOverIndex = NaN;
10149 #selectedPages = null;
10150 #draggedImageX = 0;
10151 #draggedImageY = 0;
10152 #draggedImageWidth = 0;
10153 #draggedImageHeight = 0;
10154 #draggedImageOffsetX = 0;
10155 #draggedImageOffsetY = 0;
10156 #dragMarker = null;
10157 #pageNumberToRemove = NaN;
10158 #currentScrollBottom = 0;
10159 #currentScrollTop = 0;
10160 #pagesMapper = PagesMapper.instance;
10161 #manageSaveAsButton = null;
10162 constructor({
10163 container,
10164 eventBus,
10165 linkService,
10166 renderingQueue,
10167 maxCanvasPixels,
10168 maxCanvasDim,
10169 pageColors,
10170 abortSignal,
10171 enableHWA,
10172 enableSplitMerge,
10173 manageMenu,
10174 }) {
10175 this.scrollableContainer = container.parentElement;
10176 this.container = container;
10177 this.eventBus = eventBus;
10178 this.linkService = linkService;
10179 this.renderingQueue = renderingQueue;
10180 this.maxCanvasPixels = maxCanvasPixels;
10181 this.maxCanvasDim = maxCanvasDim;
10182 this.pageColors = pageColors || null;
10183 this.enableHWA = enableHWA || false;
10184 this.#enableSplitMerge = enableSplitMerge || false;
10185 if (this.#enableSplitMerge && manageMenu) {
10186 const { button, menu, copy, cut, delete: del, saveAs } = manageMenu;
10187 this._manageMenu = new Menu(menu, button, [copy, cut, del, saveAs]);
10188 this.#manageSaveAsButton = saveAs;
10189 saveAs.addEventListener('click', () => {
10190 this.eventBus.dispatch('savepageseditedpdf', {
10191 source: this,
10192 data: this.#pagesMapper.getPageMappingForSaving(),
10193 });
10194 });
10195 } else {
10196 manageMenu.button.hidden = true;
10197 }
10198 this.scroll = watchScroll(
10199 this.scrollableContainer,
10200 this.#scrollUpdated.bind(this),
10201 abortSignal
10202 );
10203 this.#resetView();
10204 this.#addEventListeners();
10205 }
10206 #scrollUpdated() {
10207 this.renderingQueue.renderHighestPriority();
10208 }
10209 getThumbnail(index) {
10210 return this._thumbnails[index];
10211 }
10212 #getVisibleThumbs() {
10213 return getVisibleElements({
10214 scrollEl: this.scrollableContainer,
10215 views: this._thumbnails,
10216 });
10217 }
10218 scrollThumbnailIntoView(pageNumber) {
10219 if (!this.pdfDocument) {
10220 return;
10221 }
10222 const thumbnailView = this._thumbnails[pageNumber - 1];
10223 if (!thumbnailView) {
10224 console.error('scrollThumbnailIntoView: Invalid "pageNumber" parameter.');
10225 return;
10226 }
10227 if (pageNumber !== this._currentPageNumber) {
10228 const prevThumbnailView = this._thumbnails[this._currentPageNumber - 1];
10229 prevThumbnailView.toggleCurrent(false);
10230 thumbnailView.toggleCurrent(true);
10231 this._currentPageNumber = pageNumber;
10232 }
10233 const { first, last, views } = this.#getVisibleThumbs();
10234 if (views.length > 0) {
10235 let shouldScroll = false;
10236 if (
10237 pageNumber <= this.#pagesMapper.getPageNumber(first.id) ||
10238 pageNumber >= this.#pagesMapper.getPageNumber(last.id)
10239 ) {
10240 shouldScroll = true;
10241 } else {
10242 for (const { id, percent } of views) {
10243 const mappedPageNumber = this.#pagesMapper.getPageNumber(id);
10244 if (mappedPageNumber !== pageNumber) {
10245 continue;
10246 }
10247 shouldScroll = percent < 100;
10248 break;
10249 }
10250 }
10251 if (shouldScroll) {
10252 thumbnailView.div.scrollIntoView(SCROLL_OPTIONS);
10253 }
10254 }
10255 this._currentPageNumber = pageNumber;
10256 }
10257 get pagesRotation() {
10258 return this._pagesRotation;
10259 }
10260 set pagesRotation(rotation) {
10261 if (!isValidRotation(rotation)) {
10262 throw new Error('Invalid thumbnails rotation angle.');
10263 }
10264 if (!this.pdfDocument) {
10265 return;
10266 }
10267 if (this._pagesRotation === rotation) {
10268 return;
10269 }
10270 this._pagesRotation = rotation;
10271 const updateArgs = {
10272 rotation,
10273 };
10274 for (const thumbnail of this._thumbnails) {
10275 thumbnail.update(updateArgs);
10276 }
10277 }
10278 cleanup() {
10279 for (const thumbnail of this._thumbnails) {
10280 if (thumbnail.renderingState !== RenderingStates.FINISHED) {
10281 thumbnail.reset();
10282 }
10283 }
10284 }
10285 #resetView() {
10286 this._thumbnails = [];
10287 this._currentPageNumber = 1;
10288 this._pageLabels = null;
10289 this._pagesRotation = 0;
10290 this.container.textContent = '';
10291 }
10292 setDocument(pdfDocument) {
10293 if (this.pdfDocument) {
10294 this.#cancelRendering();
10295 this.#resetView();
10296 }
10297 this.pdfDocument = pdfDocument;
10298 if (!pdfDocument) {
10299 return;
10300 }
10301 const firstPagePromise = pdfDocument.getPage(1);
10302 const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
10303 intent: 'display',
10304 });
10305 firstPagePromise
10306 .then((firstPdfPage) => {
10307 const pagesCount = pdfDocument.numPages;
10308 const viewport = firstPdfPage.getViewport({
10309 scale: 1,
10310 });
10311 const fragment = document.createDocumentFragment();
10312 for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
10313 const thumbnail = new PDFThumbnailView({
10314 container: fragment,
10315 eventBus: this.eventBus,
10316 id: pageNum,
10317 defaultViewport: viewport.clone(),
10318 optionalContentConfigPromise,
10319 linkService: this.linkService,
10320 renderingQueue: this.renderingQueue,
10321 maxCanvasPixels: this.maxCanvasPixels,
10322 maxCanvasDim: this.maxCanvasDim,
10323 pageColors: this.pageColors,
10324 enableHWA: this.enableHWA,
10325 enableSplitMerge: this.#enableSplitMerge,
10326 });
10327 this._thumbnails.push(thumbnail);
10328 }
10329 this._thumbnails[0]?.setPdfPage(firstPdfPage);
10330 const thumbnailView = this._thumbnails[this._currentPageNumber - 1];
10331 thumbnailView.toggleCurrent(true);
10332 this.container.append(fragment);
10333 })
10334 .catch((reason) => {
10335 console.error('Unable to initialize thumbnail viewer', reason);
10336 });
10337 }
10338 #cancelRendering() {
10339 for (const thumbnail of this._thumbnails) {
10340 thumbnail.cancelRendering();
10341 }
10342 }
10343 setPageLabels(labels) {
10344 if (!this.pdfDocument) {
10345 return;
10346 }
10347 if (!labels) {
10348 this._pageLabels = null;
10349 } else if (!(Array.isArray(labels) && this.pdfDocument.numPages === labels.length)) {
10350 this._pageLabels = null;
10351 console.error('PDFThumbnailViewer_setPageLabels: Invalid page labels.');
10352 } else {
10353 this._pageLabels = labels;
10354 }
10355 for (let i = 0, ii = this._thumbnails.length; i < ii; i++) {
10356 this._thumbnails[i].setPageLabel(this._pageLabels?.[i] ?? null);
10357 }
10358 }
10359 async #ensurePdfPageLoaded(thumbView) {
10360 if (thumbView.pdfPage) {
10361 return thumbView.pdfPage;
10362 }
10363 try {
10364 const pdfPage = await this.pdfDocument.getPage(thumbView.id);
10365 if (!thumbView.pdfPage) {
10366 thumbView.setPdfPage(pdfPage);
10367 }
10368 return pdfPage;
10369 } catch (reason) {
10370 console.error('Unable to get page for thumb view', reason);
10371 return null;
10372 }
10373 }
10374 #getScrollAhead(visible) {
10375 if (visible.first?.id === 1) {
10376 return true;
10377 } else if (visible.last?.id === this._thumbnails.length) {
10378 return false;
10379 }
10380 return this.scroll.down;
10381 }
10382 forceRendering() {
10383 const visibleThumbs = this.#getVisibleThumbs();
10384 const scrollAhead = this.#getScrollAhead(visibleThumbs);
10385 const thumbView = this.renderingQueue.getHighestPriority(
10386 visibleThumbs,
10387 this._thumbnails,
10388 scrollAhead,
10389 false,
10390 true
10391 );
10392 if (thumbView) {
10393 this.#ensurePdfPageLoaded(thumbView).then(() => {
10394 this.renderingQueue.renderView(thumbView);
10395 });
10396 return true;
10397 }
10398 return false;
10399 }
10400 static #getScaleFactor(image) {
10401 return (PDFThumbnailViewer.#draggingScaleFactor ||= parseFloat(
10402 getComputedStyle(image).getPropertyValue('--thumbnail-dragging-scale')
10403 ));
10404 }
10405 #updateThumbnails() {
10406 const pagesMapper = this.#pagesMapper;
10407 this.container.replaceChildren();
10408 const prevThumbnails = this._thumbnails;
10409 const newThumbnails = (this._thumbnails = []);
10410 const fragment = document.createDocumentFragment();
10411 for (let i = 0, ii = pagesMapper.pagesNumber; i < ii; i++) {
10412 const prevPageIndex = pagesMapper.getPrevPageNumber(i + 1) - 1;
10413 if (prevPageIndex === -1) {
10414 continue;
10415 }
10416 const newThumbnail = prevThumbnails[prevPageIndex];
10417 newThumbnails.push(newThumbnail);
10418 newThumbnail.updateId(i + 1);
10419 newThumbnail.checkbox.checked = false;
10420 fragment.append(newThumbnail.div);
10421 }
10422 this.container.append(fragment);
10423 }
10424 #onStartDragging(draggedThumbnail) {
10425 this.#currentScrollTop = this.scrollableContainer.scrollTop;
10426 this.#currentScrollBottom = this.#currentScrollTop + this.scrollableContainer.clientHeight;
10427 this.#dragAC = new AbortController();
10428 this.container.classList.add('isDragging');
10429 const startPageNumber = parseInt(draggedThumbnail.getAttribute('page-number'), 10);
10430 this.#lastDraggedOverIndex = startPageNumber - 1;
10431 if (!this.#selectedPages?.has(startPageNumber)) {
10432 this.#pageNumberToRemove = startPageNumber;
10433 this.#selectPage(startPageNumber, true);
10434 }
10435 for (const selected of this.#selectedPages) {
10436 const thumbnail = this._thumbnails[selected - 1];
10437 const placeholder = (thumbnail.placeholder = document.createElement('div'));
10438 placeholder.classList.add('thumbnailImage', 'placeholder');
10439 const { div, image } = thumbnail;
10440 div.classList.add('isDragging');
10441 placeholder.style.height = getComputedStyle(image).height;
10442 image.after(placeholder);
10443 if (selected !== startPageNumber) {
10444 image.classList.add('hidden');
10445 continue;
10446 }
10447 if (this.#selectedPages.size === 1) {
10448 image.classList.add('draggingThumbnail');
10449 this.#draggedContainer = image;
10450 continue;
10451 }
10452 const draggedContainer = (this.#draggedContainer = document.createElement('div'));
10453 draggedContainer.classList.add('draggingThumbnail', 'thumbnailImage', 'multiple');
10454 draggedContainer.style.height = getComputedStyle(image).height;
10455 image.replaceWith(draggedContainer);
10456 image.classList.remove('thumbnailImage');
10457 draggedContainer.append(image);
10458 draggedContainer.setAttribute('data-multiple-count', this.#selectedPages.size);
10459 }
10460 }
10461 #onStopDragging(isDropping = false) {
10462 const draggedContainer = this.#draggedContainer;
10463 this.#draggedContainer = null;
10464 const lastDraggedOverIndex = this.#lastDraggedOverIndex;
10465 this.#lastDraggedOverIndex = NaN;
10466 this.#dragMarker?.remove();
10467 this.#dragMarker = null;
10468 this.#dragAC.abort();
10469 this.#dragAC = null;
10470 this.container.classList.remove('isDragging');
10471 for (const selected of this.#selectedPages) {
10472 const thumbnail = this._thumbnails[selected - 1];
10473 const { div, placeholder, image } = thumbnail;
10474 placeholder.remove();
10475 image.classList.remove('draggingThumbnail', 'hidden');
10476 div.classList.remove('isDragging');
10477 }
10478 if (draggedContainer.classList.contains('multiple')) {
10479 const originalImage = draggedContainer.firstElementChild;
10480 draggedContainer.replaceWith(originalImage);
10481 originalImage.classList.add('thumbnailImage');
10482 } else {
10483 draggedContainer.style.translate = '';
10484 }
10485 const selectedPages = this.#selectedPages;
10486 if (
10487 !isNaN(lastDraggedOverIndex) &&
10488 isDropping &&
10489 !(
10490 selectedPages.size === 1 &&
10491 (selectedPages.has(lastDraggedOverIndex + 1) || selectedPages.has(lastDraggedOverIndex + 2))
10492 )
10493 ) {
10494 const newIndex = lastDraggedOverIndex + 1;
10495 const pagesToMove = Array.from(selectedPages).sort((a, b) => a - b);
10496 const pagesMapper = this.#pagesMapper;
10497 const currentPageId = pagesMapper.getPageId(this._currentPageNumber);
10498 const newCurrentPageId = pagesMapper.getPageId(
10499 isNaN(this.#pageNumberToRemove) ? pagesToMove[0] : this.#pageNumberToRemove
10500 );
10501 this.eventBus.dispatch('beforepagesedited', {
10502 source: this,
10503 pagesMapper,
10504 });
10505 pagesMapper.movePages(selectedPages, pagesToMove, newIndex);
10506 this.#updateThumbnails();
10507 this._currentPageNumber = pagesMapper.getPageNumber(currentPageId);
10508 this.#computeThumbnailsPosition();
10509 selectedPages.clear();
10510 this.#pageNumberToRemove = NaN;
10511 const isIdentity = (this.#manageSaveAsButton.disabled = !this.#pagesMapper.hasBeenAltered());
10512 if (!isIdentity) {
10513 this.eventBus.dispatch('pagesedited', {
10514 source: this,
10515 pagesMapper,
10516 index: newIndex,
10517 pagesToMove,
10518 });
10519 }
10520 const newCurrentPageNumber = pagesMapper.getPageNumber(newCurrentPageId);
10521 setTimeout(() => {
10522 this.linkService.goToPage(newCurrentPageNumber);
10523 }, 0);
10524 }
10525 if (!isNaN(this.#pageNumberToRemove)) {
10526 this.#selectPage(this.#pageNumberToRemove, false);
10527 this.#pageNumberToRemove = NaN;
10528 }
10529 }
10530 #moveDraggedContainer(dx, dy) {
10531 this.#draggedImageOffsetX += dx;
10532 this.#draggedImageOffsetY += dy;
10533 this.#draggedImageX += dx;
10534 this.#draggedImageY += dy;
10535 this.#draggedContainer.style.translate = `${this.#draggedImageOffsetX}px ${this.#draggedImageOffsetY}px`;
10536 if (this.#draggedImageY + this.#draggedImageHeight > this.#currentScrollBottom) {
10537 this.scrollableContainer.scrollTop = Math.min(
10538 this.scrollableContainer.scrollTop + PIXELS_TO_SCROLL_WHEN_DRAGGING,
10539 this.scrollableContainer.scrollHeight
10540 );
10541 } else if (this.#draggedImageY < this.#currentScrollTop) {
10542 this.scrollableContainer.scrollTop = Math.max(
10543 this.scrollableContainer.scrollTop - PIXELS_TO_SCROLL_WHEN_DRAGGING,
10544 0
10545 );
10546 }
10547 const positionData = this.#findClosestThumbnail(
10548 this.#draggedImageX + this.#draggedImageWidth / 2,
10549 this.#draggedImageY + this.#draggedImageHeight / 2
10550 );
10551 if (!positionData) {
10552 return;
10553 }
10554 let dragMarker = this.#dragMarker;
10555 if (!dragMarker) {
10556 dragMarker = this.#dragMarker = document.createElement('div');
10557 dragMarker.className = 'dragMarker';
10558 this.container.firstChild.before(dragMarker);
10559 }
10560 const [index, space] = positionData;
10561 const dragMarkerStyle = dragMarker.style;
10562 const { bbox, x: xPos } = this.#thumbnailsPositions;
10563 let x, y, width, height;
10564 if (index < 0) {
10565 if (xPos.length === 1) {
10566 y = bbox[1] - SPACE_FOR_DRAG_MARKER_WHEN_NO_NEXT_ELEMENT;
10567 x = bbox[4];
10568 width = bbox[2];
10569 } else {
10570 y = bbox[1];
10571 x = bbox[0] - SPACE_FOR_DRAG_MARKER_WHEN_NO_NEXT_ELEMENT;
10572 height = bbox[3];
10573 }
10574 } else if (xPos.length === 1) {
10575 y = bbox[index * 4 + 1] + bbox[index * 4 + 3] + space;
10576 x = bbox[index * 4];
10577 width = bbox[index * 4 + 2];
10578 } else {
10579 y = bbox[index * 4 + 1];
10580 x = bbox[index * 4] + bbox[index * 4 + 2] + space;
10581 height = bbox[index * 4 + 3];
10582 }
10583 dragMarkerStyle.translate = `${x}px ${y}px`;
10584 dragMarkerStyle.width = width ? `${width}px` : '';
10585 dragMarkerStyle.height = height ? `${height}px` : '';
10586 }
10587 #computeThumbnailsPosition() {
10588 const positionsX = [];
10589 const positionsY = [];
10590 const positionsLastX = [];
10591 const bbox = new Float32Array(this._thumbnails.length * 4);
10592 let prevX = -Infinity;
10593 let prevY = -Infinity;
10594 let reminder = -1;
10595 let firstRightX;
10596 let lastRightX;
10597 let firstBottomY;
10598 for (let i = 0, ii = this._thumbnails.length; i < ii; i++) {
10599 const { div } = this._thumbnails[i];
10600 const { offsetTop: y, offsetLeft: x, offsetWidth: w, offsetHeight: h } = div;
10601 if (w === 0) {
10602 return;
10603 }
10604 bbox[i * 4] = x;
10605 bbox[i * 4 + 1] = y;
10606 bbox[i * 4 + 2] = w;
10607 bbox[i * 4 + 3] = h;
10608 if (x > prevX) {
10609 prevX = x + w / 2;
10610 firstRightX ??= prevX + w;
10611 positionsX.push(prevX);
10612 }
10613 if (reminder > 0 && i >= ii - reminder) {
10614 const cx = x + w / 2;
10615 positionsLastX.push(cx);
10616 lastRightX ??= cx + w;
10617 }
10618 if (y > prevY) {
10619 if (reminder === -1 && positionsX.length > 1) {
10620 reminder = ii % positionsX.length;
10621 }
10622 prevY = y + h / 2;
10623 firstBottomY ??= prevY + h;
10624 positionsY.push(prevY);
10625 }
10626 }
10627 const space =
10628 positionsX.length > 1
10629 ? (positionsX[1] - firstRightX) / 2
10630 : (positionsY[1] - firstBottomY) / 2;
10631 this.#thumbnailsPositions = {
10632 x: positionsX,
10633 y: positionsY,
10634 lastX: positionsLastX,
10635 space,
10636 lastSpace: (positionsLastX.at(-1) - lastRightX) / 2,
10637 bbox,
10638 };
10639 }
10640 #addEventListeners() {
10641 this.eventBus.on('resize', ({ source }) => {
10642 if (source.thumbnailsView === this.container) {
10643 this.#computeThumbnailsPosition();
10644 }
10645 });
10646 this.container.addEventListener('keydown', (e) => {
10647 switch (e.key) {
10648 case 'ArrowLeft':
10649 this.#goToNextItem(e.target, false, true);
10650 stopEvent(e);
10651 break;
10652 case 'ArrowRight':
10653 this.#goToNextItem(e.target, true, true);
10654 stopEvent(e);
10655 break;
10656 case 'ArrowDown':
10657 this.#goToNextItem(e.target, true, false);
10658 stopEvent(e);
10659 break;
10660 case 'ArrowUp':
10661 this.#goToNextItem(e.target, false, false);
10662 stopEvent(e);
10663 break;
10664 case 'Home':
10665 this._thumbnails[0].image.focus();
10666 stopEvent(e);
10667 break;
10668 case 'End':
10669 this._thumbnails.at(-1).image.focus();
10670 stopEvent(e);
10671 break;
10672 case 'Enter':
10673 case ' ':
10674 this.#goToPage(e);
10675 break;
10676 }
10677 });
10678 this.container.addEventListener('click', (e) => {
10679 const { target } = e;
10680 if (target instanceof HTMLInputElement) {
10681 const pageNumber = parseInt(target.parentElement.getAttribute('page-number'), 10);
10682 this.#selectPage(pageNumber, target.checked);
10683 return;
10684 }
10685 this.#goToPage(e);
10686 });
10687 this.#addDragListeners();
10688 }
10689 #selectPage(pageNumber, checked) {
10690 const set = (this.#selectedPages ??= new Set());
10691 if (checked) {
10692 set.add(pageNumber);
10693 } else {
10694 set.delete(pageNumber);
10695 }
10696 }
10697 #addDragListeners() {
10698 if (!this.#enableSplitMerge) {
10699 return;
10700 }
10701 this.container.addEventListener('pointerdown', (e) => {
10702 const {
10703 target: draggedImage,
10704 clientX: clickX,
10705 clientY: clickY,
10706 pointerId: dragPointerId,
10707 } = e;
10708 if (
10709 !isNaN(this.#lastDraggedOverIndex) ||
10710 !draggedImage.classList.contains('thumbnailImage')
10711 ) {
10712 return;
10713 }
10714 const thumbnail = draggedImage.parentElement;
10715 const pointerDownAC = new AbortController();
10716 const { signal: pointerDownSignal } = pointerDownAC;
10717 let prevDragX = clickX;
10718 let prevDragY = clickY;
10719 let prevScrollTop = this.scrollableContainer.scrollTop;
10720 const scaleFactor = PDFThumbnailViewer.#getScaleFactor(draggedImage);
10721 this.#draggedImageOffsetX =
10722 ((scaleFactor - 1) * e.layerX + draggedImage.offsetLeft) / scaleFactor;
10723 this.#draggedImageOffsetY =
10724 ((scaleFactor - 1) * e.layerY + draggedImage.offsetTop) / scaleFactor;
10725 this.#draggedImageX = thumbnail.offsetLeft + this.#draggedImageOffsetX;
10726 this.#draggedImageY = thumbnail.offsetTop + this.#draggedImageOffsetY;
10727 this.#draggedImageWidth = draggedImage.offsetWidth / scaleFactor;
10728 this.#draggedImageHeight = draggedImage.offsetHeight / scaleFactor;
10729 this.container.addEventListener(
10730 'pointermove',
10731 (ev) => {
10732 const { clientX: x, clientY: y, pointerId } = ev;
10733 if (
10734 pointerId !== dragPointerId ||
10735 (Math.abs(x - clickX) <= DRAG_THRESHOLD_IN_PIXELS &&
10736 Math.abs(y - clickY) <= DRAG_THRESHOLD_IN_PIXELS)
10737 ) {
10738 return;
10739 }
10740 if (isNaN(this.#lastDraggedOverIndex)) {
10741 this.#onStartDragging(thumbnail);
10742 const stopDragging = (_e, isDropping = false) => {
10743 this.#onStopDragging(isDropping);
10744 pointerDownAC.abort();
10745 };
10746 const { signal } = this.#dragAC;
10747 window.addEventListener('touchmove', stopEvent, {
10748 passive: false,
10749 signal,
10750 });
10751 window.addEventListener('contextmenu', noContextMenu, {
10752 signal,
10753 });
10754 this.scrollableContainer.addEventListener(
10755 'scrollend',
10756 () => {
10757 const {
10758 scrollableContainer: { clientHeight, scrollTop },
10759 } = this;
10760 this.#currentScrollTop = scrollTop;
10761 this.#currentScrollBottom = scrollTop + clientHeight;
10762 const dy = scrollTop - prevScrollTop;
10763 prevScrollTop = scrollTop;
10764 this.#moveDraggedContainer(0, dy);
10765 },
10766 {
10767 passive: true,
10768 signal,
10769 }
10770 );
10771 window.addEventListener(
10772 'pointerup',
10773 (upEv) => {
10774 if (upEv.pointerId !== dragPointerId) {
10775 return;
10776 }
10777 window.addEventListener('click', stopEvent, {
10778 capture: true,
10779 once: true,
10780 signal,
10781 });
10782 stopEvent(upEv);
10783 stopDragging(upEv, true);
10784 },
10785 {
10786 signal,
10787 }
10788 );
10789 window.addEventListener('blur', stopDragging, {
10790 signal,
10791 });
10792 window.addEventListener('pointercancel', stopDragging, {
10793 signal,
10794 });
10795 window.addEventListener('wheel', stopEvent, {
10796 passive: false,
10797 signal,
10798 });
10799 }
10800 const dx = x - prevDragX;
10801 const dy = y - prevDragY;
10802 prevDragX = x;
10803 prevDragY = y;
10804 this.#moveDraggedContainer(dx, dy);
10805 },
10806 {
10807 passive: true,
10808 signal: pointerDownSignal,
10809 }
10810 );
10811 window.addEventListener(
10812 'pointerup',
10813 ({ pointerId }) => {
10814 if (pointerId !== dragPointerId) {
10815 return;
10816 }
10817 pointerDownAC.abort();
10818 },
10819 {
10820 signal: pointerDownSignal,
10821 }
10822 );
10823 window.addEventListener('dragstart', stopEvent, {
10824 capture: true,
10825 signal: pointerDownSignal,
10826 });
10827 });
10828 }
10829 #goToPage(e) {
10830 const { target } = e;
10831 if (target.classList.contains('thumbnailImage')) {
10832 const pageNumber = parseInt(target.parentElement.getAttribute('page-number'), 10);
10833 this.linkService.goToPage(pageNumber);
10834 stopEvent(e);
10835 }
10836 }
10837 #goToNextItem(element, forward, horizontal) {
10838 let currentPageNumber = parseInt(element.parentElement.getAttribute('page-number'), 10);
10839 if (isNaN(currentPageNumber)) {
10840 currentPageNumber = this._currentPageNumber;
10841 }
10842 const increment = forward ? 1 : -1;
10843 let nextThumbnail;
10844 if (horizontal) {
10845 const nextPageNumber = MathClamp(
10846 currentPageNumber + increment,
10847 1,
10848 this._thumbnails.length + 1
10849 );
10850 nextThumbnail = this._thumbnails[nextPageNumber - 1];
10851 } else {
10852 const currentThumbnail = this._thumbnails[currentPageNumber - 1];
10853 const { x: currentX, y: currentY } = currentThumbnail.div.getBoundingClientRect();
10854 let firstWithDifferentY;
10855 for (
10856 let i = currentPageNumber - 1 + increment;
10857 i >= 0 && i < this._thumbnails.length;
10858 i += increment
10859 ) {
10860 const thumbnail = this._thumbnails[i];
10861 const { x, y } = thumbnail.div.getBoundingClientRect();
10862 if (!firstWithDifferentY && y !== currentY) {
10863 firstWithDifferentY = thumbnail;
10864 }
10865 if (x === currentX) {
10866 nextThumbnail = thumbnail;
10867 break;
10868 }
10869 }
10870 if (!nextThumbnail) {
10871 nextThumbnail = firstWithDifferentY;
10872 }
10873 }
10874 if (nextThumbnail) {
10875 nextThumbnail.image.focus();
10876 }
10877 }
10878 #findClosestThumbnail(x, y) {
10879 if (!this.#thumbnailsPositions) {
10880 this.#computeThumbnailsPosition();
10881 }
10882 const {
10883 x: positionsX,
10884 y: positionsY,
10885 lastX: positionsLastX,
10886 space: spaceBetweenThumbnails,
10887 lastSpace: lastSpaceBetweenThumbnails,
10888 } = this.#thumbnailsPositions;
10889 const lastDraggedOverIndex = this.#lastDraggedOverIndex;
10890 let xPos = lastDraggedOverIndex % positionsX.length;
10891 let yPos = Math.floor(lastDraggedOverIndex / positionsX.length);
10892 let xArray = yPos === positionsY.length - 1 ? positionsLastX : positionsX;
10893 if (
10894 positionsY[yPos] <= y &&
10895 y < (positionsY[yPos + 1] ?? Infinity) &&
10896 xArray[xPos] <= x &&
10897 x < (xArray[xPos + 1] ?? Infinity)
10898 ) {
10899 return null;
10900 }
10901 yPos = binarySearchFirstItem(positionsY, (cy) => y < cy) - 1;
10902 xArray =
10903 yPos === positionsY.length - 1 && positionsLastX.length > 0 ? positionsLastX : positionsX;
10904 xPos = Math.max(0, binarySearchFirstItem(xArray, (cx) => x < cx) - 1);
10905 if (yPos < 0) {
10906 if (xPos <= 0) {
10907 xPos = -1;
10908 }
10909 yPos = 0;
10910 }
10911 const index = MathClamp(yPos * positionsX.length + xPos, -1, this._thumbnails.length - 1);
10912 if (index === lastDraggedOverIndex) {
10913 return null;
10914 }
10915 this.#lastDraggedOverIndex = index;
10916 const space =
10917 yPos === positionsY.length - 1 && positionsLastX.length > 0 && xPos >= 0
10918 ? lastSpaceBetweenThumbnails
10919 : spaceBetweenThumbnails;
10920 return [index, space];
10921 }
10922} // ./web/annotation_editor_layer_builder.js
10923
10924class AnnotationEditorLayerBuilder {
10925 #annotationLayer = null;
10926 #drawLayer = null;
10927 #onAppend = null;
10928 #structTreeLayer = null;
10929 #textLayer = null;
10930 #uiManager;
10931 constructor(options) {
10932 this.pdfPage = options.pdfPage;
10933 this.accessibilityManager = options.accessibilityManager;
10934 this.l10n = options.l10n;
10935 this.l10n ||= new genericl10n_GenericL10n();
10936 this.annotationEditorLayer = null;
10937 this.div = null;
10938 this._cancelled = false;
10939 this.#uiManager = options.uiManager;
10940 this.#annotationLayer = options.annotationLayer || null;
10941 this.#textLayer = options.textLayer || null;
10942 this.#drawLayer = options.drawLayer || null;
10943 this.#onAppend = options.onAppend || null;
10944 this.#structTreeLayer = options.structTreeLayer || null;
10945 }
10946 async render({ viewport, intent = 'display' }) {
10947 if (intent !== 'display') {
10948 return;
10949 }
10950 if (this._cancelled) {
10951 return;
10952 }
10953 const clonedViewport = viewport.clone({
10954 dontFlip: true,
10955 });
10956 if (this.div) {
10957 this.annotationEditorLayer.update({
10958 viewport: clonedViewport,
10959 });
10960 this.show();
10961 return;
10962 }
10963 const div = (this.div = document.createElement('div'));
10964 div.className = 'annotationEditorLayer';
10965 div.hidden = true;
10966 div.dir = this.#uiManager.direction;
10967 this.#onAppend?.(div);
10968 this.annotationEditorLayer = new AnnotationEditorLayer({
10969 uiManager: this.#uiManager,
10970 div,
10971 structTreeLayer: this.#structTreeLayer,
10972 accessibilityManager: this.accessibilityManager,
10973 pageIndex: this.pdfPage.pageNumber - 1,
10974 l10n: this.l10n,
10975 viewport: clonedViewport,
10976 annotationLayer: this.#annotationLayer,
10977 textLayer: this.#textLayer,
10978 drawLayer: this.#drawLayer,
10979 });
10980 const parameters = {
10981 viewport: clonedViewport,
10982 div,
10983 annotations: null,
10984 intent,
10985 };
10986 this.annotationEditorLayer.render(parameters);
10987 this.show();
10988 }
10989 cancel() {
10990 this._cancelled = true;
10991 if (!this.div) {
10992 return;
10993 }
10994 this.annotationEditorLayer.destroy();
10995 }
10996 hide() {
10997 if (!this.div) {
10998 return;
10999 }
11000 this.annotationEditorLayer.pause(true);
11001 this.div.hidden = true;
11002 }
11003 show() {
11004 if (!this.div || this.annotationEditorLayer.isInvisible) {
11005 return;
11006 }
11007 this.div.hidden = false;
11008 this.annotationEditorLayer.pause(false);
11009 }
11010} // ./web/annotation_layer_builder.js
11011
11012class AnnotationLayerBuilder {
11013 #annotations = null;
11014 #commentManager = null;
11015 #externalHide = false;
11016 #onAppend = null;
11017 #eventAbortController = null;
11018 #linksInjected = false;
11019 constructor({
11020 pdfPage,
11021 linkService,
11022 downloadManager,
11023 annotationStorage = null,
11024 imageResourcesPath = '',
11025 renderForms = true,
11026 enableComment = false,
11027 commentManager = null,
11028 enableScripting = false,
11029 hasJSActionsPromise = null,
11030 fieldObjectsPromise = null,
11031 annotationCanvasMap = null,
11032 accessibilityManager = null,
11033 annotationEditorUIManager = null,
11034 onAppend = null,
11035 }) {
11036 this.pdfPage = pdfPage;
11037 this.linkService = linkService;
11038 this.downloadManager = downloadManager;
11039 this.imageResourcesPath = imageResourcesPath;
11040 this.renderForms = renderForms;
11041 this.annotationStorage = annotationStorage;
11042 this.enableComment = enableComment;
11043 this.#commentManager = commentManager;
11044 this.enableScripting = enableScripting;
11045 this._hasJSActionsPromise = hasJSActionsPromise || Promise.resolve(false);
11046 this._fieldObjectsPromise = fieldObjectsPromise || Promise.resolve(null);
11047 this._annotationCanvasMap = annotationCanvasMap;
11048 this._accessibilityManager = accessibilityManager;
11049 this._annotationEditorUIManager = annotationEditorUIManager;
11050 this.#onAppend = onAppend;
11051 this.annotationLayer = null;
11052 this.div = null;
11053 this._cancelled = false;
11054 this._eventBus = linkService.eventBus;
11055 }
11056 async render({ viewport, intent = 'display', structTreeLayer = null }) {
11057 if (this.div) {
11058 if (this._cancelled || !this.annotationLayer) {
11059 return;
11060 }
11061 this.annotationLayer.update({
11062 viewport: viewport.clone({
11063 dontFlip: true,
11064 }),
11065 });
11066 return;
11067 }
11068 const [annotations, hasJSActions, fieldObjects] = await Promise.all([
11069 this.pdfPage.getAnnotations({
11070 intent,
11071 }),
11072 this._hasJSActionsPromise,
11073 this._fieldObjectsPromise,
11074 ]);
11075 if (this._cancelled) {
11076 return;
11077 }
11078 const div = (this.div = document.createElement('div'));
11079 div.className = 'annotationLayer';
11080 this.#onAppend?.(div);
11081 this.#initAnnotationLayer(viewport, structTreeLayer);
11082 if (annotations.length === 0) {
11083 this.#annotations = annotations;
11084 setLayerDimensions(this.div, viewport);
11085 return;
11086 }
11087 await this.annotationLayer.render({
11088 annotations,
11089 imageResourcesPath: this.imageResourcesPath,
11090 renderForms: this.renderForms,
11091 downloadManager: this.downloadManager,
11092 enableComment: this.enableComment,
11093 enableScripting: this.enableScripting,
11094 hasJSActions,
11095 fieldObjects,
11096 });
11097 this.#annotations = annotations;
11098 if (this.linkService.isInPresentationMode) {
11099 this.#updatePresentationModeState(PresentationModeState.FULLSCREEN);
11100 }
11101 if (!this.#eventAbortController) {
11102 this.#eventAbortController = new AbortController();
11103 this._eventBus?._on(
11104 'presentationmodechanged',
11105 (evt) => {
11106 this.#updatePresentationModeState(evt.state);
11107 },
11108 {
11109 signal: this.#eventAbortController.signal,
11110 }
11111 );
11112 }
11113 }
11114 #initAnnotationLayer(viewport, structTreeLayer) {
11115 this.annotationLayer = new AnnotationLayer({
11116 div: this.div,
11117 accessibilityManager: this._accessibilityManager,
11118 annotationCanvasMap: this._annotationCanvasMap,
11119 annotationEditorUIManager: this._annotationEditorUIManager,
11120 annotationStorage: this.annotationStorage,
11121 page: this.pdfPage,
11122 viewport: viewport.clone({
11123 dontFlip: true,
11124 }),
11125 structTreeLayer,
11126 commentManager: this.#commentManager,
11127 linkService: this.linkService,
11128 });
11129 }
11130 cancel() {
11131 this._cancelled = true;
11132 this.#eventAbortController?.abort();
11133 this.#eventAbortController = null;
11134 }
11135 hide(internal = false) {
11136 this.#externalHide = !internal;
11137 if (!this.div) {
11138 return;
11139 }
11140 this.div.hidden = true;
11141 }
11142 hasEditableAnnotations() {
11143 return !!this.annotationLayer?.hasEditableAnnotations();
11144 }
11145 async injectLinkAnnotations(inferredLinks) {
11146 if (this.#annotations === null) {
11147 throw new Error('`render` method must be called before `injectLinkAnnotations`.');
11148 }
11149 if (this._cancelled || this.#linksInjected) {
11150 return;
11151 }
11152 this.#linksInjected = true;
11153 const newLinks = this.#annotations.length
11154 ? this.#checkInferredLinks(inferredLinks)
11155 : inferredLinks;
11156 if (!newLinks.length) {
11157 return;
11158 }
11159 await this.annotationLayer.addLinkAnnotations(newLinks);
11160 if (!this.#externalHide) {
11161 this.div.hidden = false;
11162 }
11163 }
11164 #updatePresentationModeState(state) {
11165 if (!this.div) {
11166 return;
11167 }
11168 let disableFormElements = false;
11169 switch (state) {
11170 case PresentationModeState.FULLSCREEN:
11171 disableFormElements = true;
11172 break;
11173 case PresentationModeState.NORMAL:
11174 break;
11175 default:
11176 return;
11177 }
11178 for (const section of this.div.childNodes) {
11179 if (section.hasAttribute('data-internal-link')) {
11180 continue;
11181 }
11182 section.inert = disableFormElements;
11183 }
11184 }
11185 #checkInferredLinks(inferredLinks) {
11186 function annotationRects(annot) {
11187 if (!annot.quadPoints) {
11188 return [annot.rect];
11189 }
11190 const rects = [];
11191 for (let i = 2, ii = annot.quadPoints.length; i < ii; i += 8) {
11192 const trX = annot.quadPoints[i];
11193 const trY = annot.quadPoints[i + 1];
11194 const blX = annot.quadPoints[i + 2];
11195 const blY = annot.quadPoints[i + 3];
11196 rects.push([blX, blY, trX, trY]);
11197 }
11198 return rects;
11199 }
11200 function intersectAnnotations(annot1, annot2) {
11201 const intersections = [];
11202 const annot1Rects = annotationRects(annot1);
11203 const annot2Rects = annotationRects(annot2);
11204 for (const rect1 of annot1Rects) {
11205 for (const rect2 of annot2Rects) {
11206 const intersection = Util.intersect(rect1, rect2);
11207 if (intersection) {
11208 intersections.push(intersection);
11209 }
11210 }
11211 }
11212 return intersections;
11213 }
11214 function areaRects(rects) {
11215 let totalArea = 0;
11216 for (const rect of rects) {
11217 totalArea += Math.abs((rect[2] - rect[0]) * (rect[3] - rect[1]));
11218 }
11219 return totalArea;
11220 }
11221 return inferredLinks.filter((link) => {
11222 let linkAreaRects;
11223 for (const annotation of this.#annotations) {
11224 if (annotation.annotationType !== AnnotationType.LINK || !annotation.url) {
11225 continue;
11226 }
11227 const intersections = intersectAnnotations(annotation, link);
11228 if (intersections.length === 0) {
11229 continue;
11230 }
11231 linkAreaRects ??= areaRects(annotationRects(link));
11232 if (areaRects(intersections) / linkAreaRects > 0.5) {
11233 return false;
11234 }
11235 }
11236 return true;
11237 });
11238 }
11239} // ./web/autolinker.js
11240
11241function DOMRectToPDF({ width, height, left, top }, pdfPageView) {
11242 if (width === 0 || height === 0) {
11243 return null;
11244 }
11245 const pageBox = pdfPageView.textLayer.div.getBoundingClientRect();
11246 const bottomLeft = pdfPageView.getPagePoint(left - pageBox.left, top - pageBox.top);
11247 const topRight = pdfPageView.getPagePoint(
11248 left - pageBox.left + width,
11249 top - pageBox.top + height
11250 );
11251 return Util.normalizeRect([bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]]);
11252}
11253function calculateLinkPosition(range, pdfPageView) {
11254 const rangeRects = range.getClientRects();
11255 if (rangeRects.length === 1) {
11256 return {
11257 rect: DOMRectToPDF(rangeRects[0], pdfPageView),
11258 };
11259 }
11260 const rect = [Infinity, Infinity, -Infinity, -Infinity];
11261 const quadPoints = [];
11262 let i = 0;
11263 for (const domRect of rangeRects) {
11264 const normalized = DOMRectToPDF(domRect, pdfPageView);
11265 if (normalized === null) {
11266 continue;
11267 }
11268 quadPoints[i] = quadPoints[i + 4] = normalized[0];
11269 quadPoints[i + 1] = quadPoints[i + 3] = normalized[3];
11270 quadPoints[i + 2] = quadPoints[i + 6] = normalized[2];
11271 quadPoints[i + 5] = quadPoints[i + 7] = normalized[1];
11272 Util.rectBoundingBox(...normalized, rect);
11273 i += 8;
11274 }
11275 return {
11276 quadPoints,
11277 rect,
11278 };
11279}
11280function textPosition(container, offset) {
11281 let currentContainer = container;
11282 do {
11283 if (currentContainer.nodeType === Node.TEXT_NODE) {
11284 const currentLength = currentContainer.textContent.length;
11285 if (offset <= currentLength) {
11286 return [currentContainer, offset];
11287 }
11288 offset -= currentLength;
11289 } else if (currentContainer.firstChild) {
11290 currentContainer = currentContainer.firstChild;
11291 continue;
11292 }
11293 while (!currentContainer.nextSibling && currentContainer !== container) {
11294 currentContainer = currentContainer.parentNode;
11295 }
11296 if (currentContainer !== container) {
11297 currentContainer = currentContainer.nextSibling;
11298 }
11299 } while (currentContainer !== container);
11300 throw new Error("Offset is bigger than container's contents length.");
11301}
11302function createLinkAnnotation({ url, index, length }, pdfPageView, id) {
11303 const highlighter = pdfPageView._textHighlighter;
11304 const [{ begin, end }] = highlighter._convertMatches([index], [length]);
11305 const range = new Range();
11306 range.setStart(...textPosition(highlighter.textDivs[begin.divIdx], begin.offset));
11307 range.setEnd(...textPosition(highlighter.textDivs[end.divIdx], end.offset));
11308 return {
11309 id: `inferred_link_${id}`,
11310 unsafeUrl: url,
11311 url,
11312 annotationType: AnnotationType.LINK,
11313 rotation: 0,
11314 ...calculateLinkPosition(range, pdfPageView),
11315 borderStyle: null,
11316 };
11317}
11318class Autolinker {
11319 static #index = 0;
11320 static #regex;
11321 static #numericTLDRegex;
11322 static findLinks(text) {
11323 this.#regex ??=
11324 /\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|(?=\p{L})[\S--[@\p{Ps}\p{Pe}<>]]+@([\S--[[\p{P}--\-]<>]]+(?:\.[\S--[[\p{P}--\-]<>]]+)+)/gmv;
11325 const [normalizedText, diffs] = normalize(text, {
11326 ignoreDashEOL: true,
11327 });
11328 const matches = normalizedText.matchAll(this.#regex);
11329 const links = [];
11330 for (const match of matches) {
11331 const [url, emailDomain] = match;
11332 let raw;
11333 if (url.startsWith('www.') || url.startsWith('http://') || url.startsWith('https://')) {
11334 raw = url;
11335 } else if (emailDomain) {
11336 const hostname = URL.parse(`http://${emailDomain}`)?.hostname;
11337 if (!hostname) {
11338 continue;
11339 }
11340 this.#numericTLDRegex ??= /\.\d+$/;
11341 if (this.#numericTLDRegex.test(hostname)) {
11342 continue;
11343 }
11344 }
11345 raw ??= url.startsWith('mailto:') ? url : `mailto:${url}`;
11346 const absoluteURL = createValidAbsoluteUrl(raw, null, {
11347 addDefaultProtocol: true,
11348 });
11349 if (absoluteURL) {
11350 const [index, length] = getOriginalIndex(diffs, match.index, url.length);
11351 links.push({
11352 url: absoluteURL.href,
11353 index,
11354 length,
11355 });
11356 }
11357 }
11358 return links;
11359 }
11360 static processLinks(pdfPageView) {
11361 return this.findLinks(pdfPageView._textHighlighter.textContentItemsStr.join('\n')).map((link) =>
11362 createLinkAnnotation(link, pdfPageView, this.#index++)
11363 );
11364 }
11365} // ./web/base_pdf_page_view.js
11366
11367class BasePDFPageView {
11368 #loadingId = null;
11369 #minDurationToUpdateCanvas = 0;
11370 #renderError = null;
11371 #renderingState = RenderingStates.INITIAL;
11372 #showCanvas = null;
11373 #startTime = 0;
11374 #tempCanvas = null;
11375 canvas = null;
11376 div = null;
11377 enableOptimizedPartialRendering = false;
11378 eventBus = null;
11379 id = null;
11380 pageColors = null;
11381 recordedBBoxes = null;
11382 renderingQueue = null;
11383 renderTask = null;
11384 resume = null;
11385 constructor(options) {
11386 this.eventBus = options.eventBus;
11387 this.id = options.id;
11388 this.pageColors = options.pageColors || null;
11389 this.renderingQueue = options.renderingQueue;
11390 this.enableOptimizedPartialRendering = options.enableOptimizedPartialRendering ?? false;
11391 this.#minDurationToUpdateCanvas = options.minDurationToUpdateCanvas ?? 500;
11392 }
11393 get renderingState() {
11394 return this.#renderingState;
11395 }
11396 set renderingState(state) {
11397 if (state === this.#renderingState) {
11398 return;
11399 }
11400 this.#renderingState = state;
11401 if (this.#loadingId) {
11402 clearTimeout(this.#loadingId);
11403 this.#loadingId = null;
11404 }
11405 switch (state) {
11406 case RenderingStates.PAUSED:
11407 this.div.classList.remove('loading');
11408 this.#startTime = 0;
11409 this.#showCanvas?.(false);
11410 break;
11411 case RenderingStates.RUNNING:
11412 this.div.classList.add('loadingIcon');
11413 this.#loadingId = setTimeout(() => {
11414 this.div.classList.add('loading');
11415 this.#loadingId = null;
11416 }, 0);
11417 this.#startTime = Date.now();
11418 break;
11419 case RenderingStates.INITIAL:
11420 case RenderingStates.FINISHED:
11421 this.div.classList.remove('loadingIcon', 'loading');
11422 this.#startTime = 0;
11423 break;
11424 }
11425 }
11426 _createCanvas(onShow, hideUntilComplete = false) {
11427 const { pageColors } = this;
11428 const hasHCM = !!(pageColors?.background && pageColors?.foreground);
11429 const prevCanvas = this.canvas;
11430 const updateOnFirstShow = !prevCanvas && !hasHCM && !hideUntilComplete;
11431 let canvas = (this.canvas = document.createElement('canvas'));
11432 this.#showCanvas = (isLastShow) => {
11433 if (updateOnFirstShow) {
11434 let tempCanvas = this.#tempCanvas;
11435 if (!isLastShow && this.#minDurationToUpdateCanvas > 0) {
11436 if (Date.now() - this.#startTime < this.#minDurationToUpdateCanvas) {
11437 return;
11438 }
11439 if (!tempCanvas) {
11440 tempCanvas = this.#tempCanvas = canvas;
11441 canvas = this.canvas = canvas.cloneNode(false);
11442 onShow(canvas);
11443 }
11444 }
11445 if (tempCanvas) {
11446 const ctx = canvas.getContext('2d', {
11447 alpha: false,
11448 });
11449 ctx.drawImage(tempCanvas, 0, 0);
11450 if (isLastShow) {
11451 this.#resetTempCanvas();
11452 } else {
11453 this.#startTime = Date.now();
11454 }
11455 return;
11456 }
11457 onShow(canvas);
11458 this.#showCanvas = null;
11459 return;
11460 }
11461 if (!isLastShow) {
11462 return;
11463 }
11464 if (prevCanvas) {
11465 prevCanvas.replaceWith(canvas);
11466 prevCanvas.width = prevCanvas.height = 0;
11467 } else {
11468 onShow(canvas);
11469 }
11470 };
11471 return {
11472 canvas,
11473 prevCanvas,
11474 };
11475 }
11476 #renderContinueCallback = (cont) => {
11477 this.#showCanvas?.(false);
11478 if (this.renderingQueue && !this.renderingQueue.isHighestPriority(this)) {
11479 this.renderingState = RenderingStates.PAUSED;
11480 this.resume = () => {
11481 this.renderingState = RenderingStates.RUNNING;
11482 cont();
11483 };
11484 return;
11485 }
11486 cont();
11487 };
11488 _resetCanvas() {
11489 const { canvas } = this;
11490 if (!canvas) {
11491 return;
11492 }
11493 canvas.remove();
11494 canvas.width = canvas.height = 0;
11495 this.canvas = null;
11496 this.#resetTempCanvas();
11497 }
11498 #resetTempCanvas() {
11499 if (this.#tempCanvas) {
11500 this.#tempCanvas.width = this.#tempCanvas.height = 0;
11501 this.#tempCanvas = null;
11502 }
11503 }
11504 async _drawCanvas(options, onCancel, onFinish) {
11505 const renderTask = (this.renderTask = this.pdfPage.render(options));
11506 renderTask.onContinue = this.#renderContinueCallback;
11507 renderTask.onError = (error) => {
11508 if (error instanceof RenderingCancelledException) {
11509 onCancel();
11510 this.#renderError = null;
11511 }
11512 };
11513 let error = null;
11514 try {
11515 await renderTask.promise;
11516 this.#showCanvas?.(true);
11517 } catch (e) {
11518 if (e instanceof RenderingCancelledException) {
11519 return;
11520 }
11521 error = e;
11522 this.#showCanvas?.(true);
11523 } finally {
11524 this.#renderError = error;
11525 if (renderTask === this.renderTask) {
11526 this.renderTask = null;
11527 if (this.enableOptimizedPartialRendering) {
11528 this.recordedBBoxes ??= renderTask.recordedBBoxes;
11529 }
11530 }
11531 }
11532 this.renderingState = RenderingStates.FINISHED;
11533 onFinish(renderTask);
11534 if (error) {
11535 throw error;
11536 }
11537 }
11538 cancelRendering({ cancelExtraDelay = 0 } = {}) {
11539 if (this.renderTask) {
11540 this.renderTask.cancel(cancelExtraDelay);
11541 this.renderTask = null;
11542 }
11543 this.resume = null;
11544 }
11545 dispatchPageRender() {
11546 this.eventBus.dispatch('pagerender', {
11547 source: this,
11548 pageNumber: this.id,
11549 });
11550 }
11551 dispatchPageRendered(cssTransform, isDetailView) {
11552 this.eventBus.dispatch('pagerendered', {
11553 source: this,
11554 pageNumber: this.id,
11555 cssTransform,
11556 isDetailView,
11557 timestamp: performance.now(),
11558 error: this.#renderError,
11559 });
11560 }
11561} // ./web/draw_layer_builder.js
11562
11563class DrawLayerBuilder {
11564 #drawLayer = null;
11565 async render({ intent = 'display' }) {
11566 if (intent !== 'display' || this.#drawLayer || this._cancelled) {
11567 return;
11568 }
11569 this.#drawLayer = new DrawLayer();
11570 }
11571 cancel() {
11572 this._cancelled = true;
11573 if (!this.#drawLayer) {
11574 return;
11575 }
11576 this.#drawLayer.destroy();
11577 this.#drawLayer = null;
11578 }
11579 setParent(parent) {
11580 this.#drawLayer?.setParent(parent);
11581 }
11582 getDrawLayer() {
11583 return this.#drawLayer;
11584 }
11585} // ./web/pdf_page_detail_view.js
11586
11587class PDFPageDetailView extends BasePDFPageView {
11588 #detailArea = null;
11589 renderingCancelled = false;
11590 constructor({ pageView }) {
11591 super(pageView);
11592 this.pageView = pageView;
11593 this.renderingId = 'detail' + this.id;
11594 this.div = pageView.div;
11595 }
11596 setPdfPage(pdfPage) {
11597 this.pageView.setPdfPage(pdfPage);
11598 }
11599 get pdfPage() {
11600 return this.pageView.pdfPage;
11601 }
11602 get renderingState() {
11603 return super.renderingState;
11604 }
11605 set renderingState(value) {
11606 this.renderingCancelled = false;
11607 super.renderingState = value;
11608 }
11609 reset({ keepCanvas = false } = {}) {
11610 const renderingCancelled =
11611 this.renderingCancelled ||
11612 this.renderingState === RenderingStates.RUNNING ||
11613 this.renderingState === RenderingStates.PAUSED;
11614 this.cancelRendering();
11615 this.renderingState = RenderingStates.INITIAL;
11616 this.renderingCancelled = renderingCancelled;
11617 if (!keepCanvas) {
11618 this._resetCanvas();
11619 }
11620 }
11621 #shouldRenderDifferentArea(visibleArea) {
11622 if (!this.#detailArea) {
11623 return true;
11624 }
11625 const minDetailX = this.#detailArea.minX;
11626 const minDetailY = this.#detailArea.minY;
11627 const maxDetailX = this.#detailArea.width + minDetailX;
11628 const maxDetailY = this.#detailArea.height + minDetailY;
11629 if (
11630 visibleArea.minX < minDetailX ||
11631 visibleArea.minY < minDetailY ||
11632 visibleArea.maxX > maxDetailX ||
11633 visibleArea.maxY > maxDetailY
11634 ) {
11635 return true;
11636 }
11637 const { width: maxWidth, height: maxHeight, scale } = this.pageView.viewport;
11638 if (this.#detailArea.scale !== scale) {
11639 return true;
11640 }
11641 const paddingLeftSize = visibleArea.minX - minDetailX;
11642 const paddingRightSize = maxDetailX - visibleArea.maxX;
11643 const paddingTopSize = visibleArea.minY - minDetailY;
11644 const paddingBottomSize = maxDetailY - visibleArea.maxY;
11645 const MOVEMENT_THRESHOLD = 0.5;
11646 const ratio = (1 + MOVEMENT_THRESHOLD) / MOVEMENT_THRESHOLD;
11647 if (
11648 (minDetailX > 0 && paddingRightSize / paddingLeftSize > ratio) ||
11649 (maxDetailX < maxWidth && paddingLeftSize / paddingRightSize > ratio) ||
11650 (minDetailY > 0 && paddingBottomSize / paddingTopSize > ratio) ||
11651 (maxDetailY < maxHeight && paddingTopSize / paddingBottomSize > ratio)
11652 ) {
11653 return true;
11654 }
11655 return false;
11656 }
11657 update({ visibleArea = null, underlyingViewUpdated = false } = {}) {
11658 if (underlyingViewUpdated) {
11659 this.cancelRendering();
11660 this.renderingState = RenderingStates.INITIAL;
11661 return;
11662 }
11663 if (!this.#shouldRenderDifferentArea(visibleArea)) {
11664 return;
11665 }
11666 const { viewport, maxCanvasPixels, capCanvasAreaFactor } = this.pageView;
11667 const visibleWidth = visibleArea.maxX - visibleArea.minX;
11668 const visibleHeight = visibleArea.maxY - visibleArea.minY;
11669 const visiblePixels = visibleWidth * visibleHeight * OutputScale.pixelRatio ** 2;
11670 const maxDetailToVisibleLinearRatio = Math.sqrt(
11671 OutputScale.capPixels(maxCanvasPixels, capCanvasAreaFactor) / visiblePixels
11672 );
11673 const maxOverflowScale = (maxDetailToVisibleLinearRatio - 1) / 2;
11674 let overflowScale = Math.min(1, maxOverflowScale);
11675 if (overflowScale < 0) {
11676 overflowScale = 0;
11677 }
11678 const overflowWidth = visibleWidth * overflowScale;
11679 const overflowHeight = visibleHeight * overflowScale;
11680 const minX = Math.max(0, visibleArea.minX - overflowWidth);
11681 const maxX = Math.min(viewport.width, visibleArea.maxX + overflowWidth);
11682 const minY = Math.max(0, visibleArea.minY - overflowHeight);
11683 const maxY = Math.min(viewport.height, visibleArea.maxY + overflowHeight);
11684 const width = maxX - minX;
11685 const height = maxY - minY;
11686 this.#detailArea = {
11687 minX,
11688 minY,
11689 width,
11690 height,
11691 scale: viewport.scale,
11692 };
11693 this.reset({
11694 keepCanvas: true,
11695 });
11696 }
11697 _getRenderingContext(canvas, transform) {
11698 const baseContext = this.pageView._getRenderingContext(canvas, transform, false);
11699 const recordedBBoxes = this.pdfPage.recordedBBoxes;
11700 if (!recordedBBoxes || !this.enableOptimizedPartialRendering) {
11701 return baseContext;
11702 }
11703 const {
11704 viewport: { width: vWidth, height: vHeight },
11705 } = this.pageView;
11706 const { width: aWidth, height: aHeight, minX: aMinX, minY: aMinY } = this.#detailArea;
11707 const detailMinX = aMinX / vWidth;
11708 const detailMinY = aMinY / vHeight;
11709 const detailMaxX = (aMinX + aWidth) / vWidth;
11710 const detailMaxY = (aMinY + aHeight) / vHeight;
11711 return {
11712 ...baseContext,
11713 operationsFilter(index) {
11714 if (recordedBBoxes.isEmpty(index)) {
11715 return false;
11716 }
11717 return (
11718 recordedBBoxes.minX(index) <= detailMaxX &&
11719 recordedBBoxes.maxX(index) >= detailMinX &&
11720 recordedBBoxes.minY(index) <= detailMaxY &&
11721 recordedBBoxes.maxY(index) >= detailMinY
11722 );
11723 },
11724 };
11725 }
11726 async draw() {
11727 if (this.pageView.detailView !== this) {
11728 return undefined;
11729 }
11730 const hideUntilComplete =
11731 this.pageView.renderingState === RenderingStates.FINISHED ||
11732 this.renderingState === RenderingStates.FINISHED;
11733 if (this.renderingState !== RenderingStates.INITIAL) {
11734 console.error('Must be in new state before drawing');
11735 this.reset();
11736 }
11737 const { div, pdfPage, viewport } = this.pageView;
11738 if (!pdfPage) {
11739 this.renderingState = RenderingStates.FINISHED;
11740 throw new Error('pdfPage is not loaded');
11741 }
11742 this.renderingState = RenderingStates.RUNNING;
11743 const canvasWrapper = this.pageView._ensureCanvasWrapper();
11744 const { canvas, prevCanvas } = this._createCanvas((newCanvas) => {
11745 if (canvasWrapper.firstElementChild?.tagName === 'CANVAS') {
11746 canvasWrapper.firstElementChild.after(newCanvas);
11747 } else {
11748 canvasWrapper.prepend(newCanvas);
11749 }
11750 }, hideUntilComplete);
11751 canvas.ariaHidden = true;
11752 if (this.enableOptimizedPartialRendering) {
11753 canvas.className = 'detailView';
11754 }
11755 const { width, height } = viewport;
11756 const area = this.#detailArea;
11757 const { pixelRatio } = OutputScale;
11758 const transform = [
11759 pixelRatio,
11760 0,
11761 0,
11762 pixelRatio,
11763 -area.minX * pixelRatio,
11764 -area.minY * pixelRatio,
11765 ];
11766 canvas.width = area.width * pixelRatio;
11767 canvas.height = area.height * pixelRatio;
11768 const { style } = canvas;
11769 style.width = `${(area.width * 100) / width}%`;
11770 style.height = `${(area.height * 100) / height}%`;
11771 style.top = `${(area.minY * 100) / height}%`;
11772 style.left = `${(area.minX * 100) / width}%`;
11773 const renderingPromise = this._drawCanvas(
11774 this._getRenderingContext(canvas, transform),
11775 () => {
11776 this.canvas?.remove();
11777 this.canvas = prevCanvas;
11778 },
11779 () => {
11780 this.dispatchPageRendered(false, true);
11781 }
11782 );
11783 div.setAttribute('data-loaded', true);
11784 this.dispatchPageRender();
11785 return renderingPromise;
11786 }
11787} // ./web/struct_tree_layer_builder.js
11788
11789const PDF_ROLE_TO_HTML_ROLE = {
11790 Document: null,
11791 DocumentFragment: null,
11792 Part: 'group',
11793 Sect: 'group',
11794 Div: 'group',
11795 Aside: 'note',
11796 NonStruct: 'none',
11797 P: null,
11798 H: 'heading',
11799 Title: null,
11800 FENote: 'note',
11801 Sub: 'group',
11802 Lbl: null,
11803 Span: null,
11804 Em: null,
11805 Strong: null,
11806 Link: 'link',
11807 Annot: 'note',
11808 Form: 'form',
11809 Ruby: null,
11810 RB: null,
11811 RT: null,
11812 RP: null,
11813 Warichu: null,
11814 WT: null,
11815 WP: null,
11816 L: 'list',
11817 LI: 'listitem',
11818 LBody: null,
11819 Table: 'table',
11820 TR: 'row',
11821 TH: 'columnheader',
11822 TD: 'cell',
11823 THead: 'columnheader',
11824 TBody: null,
11825 TFoot: null,
11826 Caption: null,
11827 Figure: 'figure',
11828 Formula: null,
11829 Artifact: null,
11830};
11831const MathMLElements = new Set([
11832 'math',
11833 'merror',
11834 'mfrac',
11835 'mi',
11836 'mmultiscripts',
11837 'mn',
11838 'mo',
11839 'mover',
11840 'mpadded',
11841 'mprescripts',
11842 'mroot',
11843 'mrow',
11844 'ms',
11845 'mspace',
11846 'msqrt',
11847 'mstyle',
11848 'msub',
11849 'msubsup',
11850 'msup',
11851 'mtable',
11852 'mtd',
11853 'mtext',
11854 'mtr',
11855 'munder',
11856 'munderover',
11857 'semantics',
11858]);
11859const MathMLNamespace = 'http://www.w3.org/1998/Math/MathML';
11860class MathMLSanitizer {
11861 static get sanitizer() {
11862 return shadow(
11863 this,
11864 'sanitizer',
11865 FeatureTest.isSanitizerSupported
11866 ? new Sanitizer({
11867 elements: [...MathMLElements].map((name) => ({
11868 name,
11869 namespace: MathMLNamespace,
11870 })),
11871 replaceWithChildrenElements: [
11872 {
11873 name: 'maction',
11874 namespace: MathMLNamespace,
11875 },
11876 ],
11877 attributes: [
11878 'dir',
11879 'displaystyle',
11880 'mathbackground',
11881 'mathcolor',
11882 'mathsize',
11883 'scriptlevel',
11884 'encoding',
11885 'display',
11886 'linethickness',
11887 'intent',
11888 'arg',
11889 'form',
11890 'fence',
11891 'separator',
11892 'lspace',
11893 'rspace',
11894 'stretchy',
11895 'symmetric',
11896 'maxsize',
11897 'minsize',
11898 'largeop',
11899 'movablelimits',
11900 'width',
11901 'height',
11902 'depth',
11903 'voffset',
11904 'accent',
11905 'accentunder',
11906 'columnspan',
11907 'rowspan',
11908 ],
11909 comments: false,
11910 })
11911 : null
11912 );
11913 }
11914}
11915const HEADING_PATTERN = /^H(\d+)$/;
11916class StructTreeLayerBuilder {
11917 #promise;
11918 #treeDom = null;
11919 #treePromise;
11920 #elementAttributes = new Map();
11921 #rawDims;
11922 #elementsToAddToTextLayer = null;
11923 #elementsToHideInTextLayer = null;
11924 #elementsToStealFromTextLayer = null;
11925 constructor(pdfPage, rawDims) {
11926 this.#promise = pdfPage.getStructTree();
11927 this.#rawDims = rawDims;
11928 }
11929 async render() {
11930 if (this.#treePromise) {
11931 return this.#treePromise;
11932 }
11933 const { promise, resolve, reject } = Promise.withResolvers();
11934 this.#treePromise = promise;
11935 try {
11936 this.#treeDom = this.#walk(await this.#promise);
11937 } catch (ex) {
11938 reject(ex);
11939 }
11940 this.#promise = null;
11941 this.#treeDom?.classList.add('structTree');
11942 resolve(this.#treeDom);
11943 return promise;
11944 }
11945 async getAriaAttributes(annotationId) {
11946 try {
11947 await this.render();
11948 return this.#elementAttributes.get(annotationId);
11949 } catch {}
11950 return null;
11951 }
11952 hide() {
11953 if (this.#treeDom && !this.#treeDom.hidden) {
11954 this.#treeDom.hidden = true;
11955 }
11956 }
11957 show() {
11958 if (this.#treeDom?.hidden) {
11959 this.#treeDom.hidden = false;
11960 }
11961 }
11962 #setAttributes(structElement, htmlElement) {
11963 const { alt, id, lang } = structElement;
11964 if (alt !== undefined) {
11965 let added = false;
11966 const label = removeNullCharacters(alt);
11967 for (const child of structElement.children) {
11968 if (child.type === 'annotation') {
11969 let attrs = this.#elementAttributes.get(child.id);
11970 if (!attrs) {
11971 attrs = new Map();
11972 this.#elementAttributes.set(child.id, attrs);
11973 }
11974 attrs.set('aria-label', label);
11975 added = true;
11976 }
11977 }
11978 if (!added) {
11979 htmlElement.setAttribute('aria-label', label);
11980 }
11981 }
11982 if (id !== undefined) {
11983 htmlElement.setAttribute('aria-owns', id);
11984 }
11985 if (lang !== undefined) {
11986 htmlElement.setAttribute('lang', removeNullCharacters(lang, true));
11987 }
11988 }
11989 #addImageInTextLayer(node, element) {
11990 const { alt, bbox, children } = node;
11991 const child = children?.[0];
11992 if (!this.#rawDims || !alt || !bbox || child?.type !== 'content') {
11993 return false;
11994 }
11995 const { id } = child;
11996 if (!id) {
11997 return false;
11998 }
11999 element.setAttribute('aria-owns', id);
12000 const img = document.createElement('span');
12001 (this.#elementsToAddToTextLayer ||= new Map()).set(id, img);
12002 img.setAttribute('role', 'img');
12003 img.setAttribute('aria-label', removeNullCharacters(alt));
12004 const { pageHeight, pageX, pageY } = this.#rawDims;
12005 const calc = 'calc(var(--total-scale-factor) *';
12006 const { style } = img;
12007 style.width = `${calc}${bbox[2] - bbox[0]}px)`;
12008 style.height = `${calc}${bbox[3] - bbox[1]}px)`;
12009 style.left = `${calc}${bbox[0] - pageX}px)`;
12010 style.top = `${calc}${pageHeight - bbox[3] + pageY}px)`;
12011 return true;
12012 }
12013 updateTextLayer() {
12014 if (this.#elementsToAddToTextLayer) {
12015 for (const [id, img] of this.#elementsToAddToTextLayer) {
12016 document.getElementById(id)?.append(img);
12017 }
12018 this.#elementsToAddToTextLayer.clear();
12019 this.#elementsToAddToTextLayer = null;
12020 }
12021 if (this.#elementsToHideInTextLayer) {
12022 for (const id of this.#elementsToHideInTextLayer) {
12023 const elem = document.getElementById(id);
12024 if (elem) {
12025 elem.ariaHidden = true;
12026 }
12027 }
12028 this.#elementsToHideInTextLayer.length = 0;
12029 this.#elementsToHideInTextLayer = null;
12030 }
12031 if (this.#elementsToStealFromTextLayer) {
12032 for (let i = 0, ii = this.#elementsToStealFromTextLayer.length; i < ii; i += 2) {
12033 const element = this.#elementsToStealFromTextLayer[i];
12034 const ids = this.#elementsToStealFromTextLayer[i + 1];
12035 let textContent = '';
12036 for (const id of ids) {
12037 const elem = document.getElementById(id);
12038 if (elem) {
12039 textContent += elem.textContent.trim() || '';
12040 elem.ariaHidden = 'true';
12041 }
12042 }
12043 if (textContent) {
12044 element.textContent = textContent;
12045 }
12046 }
12047 this.#elementsToStealFromTextLayer.length = 0;
12048 this.#elementsToStealFromTextLayer = null;
12049 }
12050 }
12051 #walk(node) {
12052 if (!node) {
12053 return null;
12054 }
12055 let element;
12056 if ('role' in node) {
12057 const { role } = node;
12058 if (MathMLElements.has(role)) {
12059 element = document.createElementNS(MathMLNamespace, role);
12060 const ids = [];
12061 (this.#elementsToStealFromTextLayer ||= []).push(element, ids);
12062 for (const { type, id } of node.children || []) {
12063 if (type === 'content' && id) {
12064 ids.push(id);
12065 }
12066 }
12067 } else {
12068 element = document.createElement('span');
12069 }
12070 const match = role.match(HEADING_PATTERN);
12071 if (match) {
12072 element.setAttribute('role', 'heading');
12073 element.setAttribute('aria-level', match[1]);
12074 } else if (PDF_ROLE_TO_HTML_ROLE[role]) {
12075 element.setAttribute('role', PDF_ROLE_TO_HTML_ROLE[role]);
12076 }
12077 if (role === 'Figure' && this.#addImageInTextLayer(node, element)) {
12078 return element;
12079 }
12080 if (role === 'Formula') {
12081 if (node.mathML && MathMLSanitizer.sanitizer) {
12082 element.setHTML(node.mathML, {
12083 sanitizer: MathMLSanitizer.sanitizer,
12084 });
12085 for (const { id } of node.children || []) {
12086 if (!id) {
12087 continue;
12088 }
12089 (this.#elementsToHideInTextLayer ||= []).push(id);
12090 }
12091 delete node.alt;
12092 }
12093 if (!node.mathML && node.children.length === 1 && node.children[0].role !== 'math') {
12094 element = document.createElementNS(MathMLNamespace, 'math');
12095 delete node.alt;
12096 }
12097 }
12098 }
12099 element ||= document.createElement('span');
12100 this.#setAttributes(node, element);
12101 if (node.children) {
12102 if (node.children.length === 1 && 'id' in node.children[0]) {
12103 this.#setAttributes(node.children[0], element);
12104 } else {
12105 for (const kid of node.children) {
12106 element.append(this.#walk(kid));
12107 }
12108 }
12109 }
12110 return element;
12111 }
12112} // ./web/text_accessibility.js
12113
12114class TextAccessibilityManager {
12115 #enabled = false;
12116 #textChildren = null;
12117 #textNodes = new Map();
12118 #waitingElements = new Map();
12119 setTextMapping(textDivs) {
12120 this.#textChildren = textDivs;
12121 }
12122 static #compareElementPositions(e1, e2) {
12123 const rect1 = e1.getBoundingClientRect();
12124 const rect2 = e2.getBoundingClientRect();
12125 if (rect1.width === 0 && rect1.height === 0) {
12126 return +1;
12127 }
12128 if (rect2.width === 0 && rect2.height === 0) {
12129 return -1;
12130 }
12131 const top1 = rect1.y;
12132 const bot1 = rect1.y + rect1.height;
12133 const mid1 = rect1.y + rect1.height / 2;
12134 const top2 = rect2.y;
12135 const bot2 = rect2.y + rect2.height;
12136 const mid2 = rect2.y + rect2.height / 2;
12137 if (mid1 <= top2 && mid2 >= bot1) {
12138 return -1;
12139 }
12140 if (mid2 <= top1 && mid1 >= bot2) {
12141 return +1;
12142 }
12143 const centerX1 = rect1.x + rect1.width / 2;
12144 const centerX2 = rect2.x + rect2.width / 2;
12145 return centerX1 - centerX2;
12146 }
12147 enable() {
12148 if (this.#enabled) {
12149 throw new Error('TextAccessibilityManager is already enabled.');
12150 }
12151 if (!this.#textChildren) {
12152 throw new Error('Text divs and strings have not been set.');
12153 }
12154 this.#enabled = true;
12155 this.#textChildren = this.#textChildren.slice();
12156 this.#textChildren.sort(TextAccessibilityManager.#compareElementPositions);
12157 if (this.#textNodes.size > 0) {
12158 const textChildren = this.#textChildren;
12159 for (const [id, nodeIndex] of this.#textNodes) {
12160 const element = document.getElementById(id);
12161 if (!element) {
12162 this.#textNodes.delete(id);
12163 continue;
12164 }
12165 this.#addIdToAriaOwns(id, textChildren[nodeIndex]);
12166 }
12167 }
12168 for (const [element, isRemovable] of this.#waitingElements) {
12169 this.addPointerInTextLayer(element, isRemovable);
12170 }
12171 this.#waitingElements.clear();
12172 }
12173 disable() {
12174 if (!this.#enabled) {
12175 return;
12176 }
12177 this.#waitingElements.clear();
12178 this.#textChildren = null;
12179 this.#enabled = false;
12180 }
12181 removePointerInTextLayer(element) {
12182 if (!this.#enabled) {
12183 this.#waitingElements.delete(element);
12184 return;
12185 }
12186 const children = this.#textChildren;
12187 if (!children || children.length === 0) {
12188 return;
12189 }
12190 const { id } = element;
12191 const nodeIndex = this.#textNodes.get(id);
12192 if (nodeIndex === undefined) {
12193 return;
12194 }
12195 const node = children[nodeIndex];
12196 this.#textNodes.delete(id);
12197 let owns = node.getAttribute('aria-owns');
12198 if (owns?.includes(id)) {
12199 owns = owns
12200 .split(' ')
12201 .filter((x) => x !== id)
12202 .join(' ');
12203 if (owns) {
12204 node.setAttribute('aria-owns', owns);
12205 } else {
12206 node.removeAttribute('aria-owns');
12207 node.setAttribute('role', 'presentation');
12208 }
12209 }
12210 }
12211 #addIdToAriaOwns(id, node) {
12212 const owns = node.getAttribute('aria-owns');
12213 if (!owns?.includes(id)) {
12214 node.setAttribute('aria-owns', owns ? `${owns} ${id}` : id);
12215 }
12216 node.removeAttribute('role');
12217 }
12218 addPointerInTextLayer(element, isRemovable) {
12219 const { id } = element;
12220 if (!id) {
12221 return null;
12222 }
12223 if (!this.#enabled) {
12224 this.#waitingElements.set(element, isRemovable);
12225 return null;
12226 }
12227 if (isRemovable) {
12228 this.removePointerInTextLayer(element);
12229 }
12230 const children = this.#textChildren;
12231 if (!children || children.length === 0) {
12232 return null;
12233 }
12234 const index = binarySearchFirstItem(
12235 children,
12236 (node) => TextAccessibilityManager.#compareElementPositions(element, node) < 0
12237 );
12238 const nodeIndex = Math.max(0, index - 1);
12239 const child = children[nodeIndex];
12240 this.#addIdToAriaOwns(id, child);
12241 this.#textNodes.set(id, nodeIndex);
12242 const parent = child.parentNode;
12243 return parent?.classList.contains('markedContent') ? parent.id : null;
12244 }
12245 moveElementInDOM(container, element, contentElement, isRemovable) {
12246 const id = this.addPointerInTextLayer(contentElement, isRemovable);
12247 if (!container.hasChildNodes()) {
12248 container.append(element);
12249 return id;
12250 }
12251 const children = Array.from(container.childNodes).filter((node) => node !== element);
12252 if (children.length === 0) {
12253 return id;
12254 }
12255 const index = binarySearchFirstItem(
12256 children,
12257 (node) => TextAccessibilityManager.#compareElementPositions(element, node) < 0
12258 );
12259 if (index === 0) {
12260 children[0].before(element);
12261 } else {
12262 children[index - 1].after(element);
12263 }
12264 return id;
12265 }
12266} // ./web/text_highlighter.js
12267
12268class TextHighlighter {
12269 #eventAbortController = null;
12270 constructor({ findController, eventBus, pageIndex }) {
12271 this.findController = findController;
12272 this.matches = [];
12273 this.eventBus = eventBus;
12274 this.pageIdx = pageIndex;
12275 this.textDivs = null;
12276 this.textContentItemsStr = null;
12277 this.enabled = false;
12278 }
12279 setTextMapping(divs, texts) {
12280 this.textDivs = divs;
12281 this.textContentItemsStr = texts;
12282 }
12283 enable() {
12284 if (!this.textDivs || !this.textContentItemsStr) {
12285 throw new Error('Text divs and strings have not been set.');
12286 }
12287 if (this.enabled) {
12288 throw new Error('TextHighlighter is already enabled.');
12289 }
12290 this.enabled = true;
12291 if (!this.#eventAbortController) {
12292 this.#eventAbortController = new AbortController();
12293 this.eventBus._on(
12294 'updatetextlayermatches',
12295 (evt) => {
12296 if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) {
12297 this._updateMatches();
12298 }
12299 },
12300 {
12301 signal: this.#eventAbortController.signal,
12302 }
12303 );
12304 }
12305 this._updateMatches();
12306 }
12307 disable() {
12308 if (!this.enabled) {
12309 return;
12310 }
12311 this.enabled = false;
12312 this.#eventAbortController?.abort();
12313 this.#eventAbortController = null;
12314 this._updateMatches(true);
12315 }
12316 _convertMatches(matches, matchesLength) {
12317 if (!matches) {
12318 return [];
12319 }
12320 const { textContentItemsStr } = this;
12321 let i = 0,
12322 iIndex = 0;
12323 const end = textContentItemsStr.length - 1;
12324 const result = [];
12325 for (let m = 0, mm = matches.length; m < mm; m++) {
12326 let matchIdx = matches[m];
12327 while (i !== end && matchIdx >= iIndex + textContentItemsStr[i].length) {
12328 iIndex += textContentItemsStr[i].length;
12329 i++;
12330 }
12331 if (i === textContentItemsStr.length) {
12332 console.error('Could not find a matching mapping');
12333 }
12334 const match = {
12335 begin: {
12336 divIdx: i,
12337 offset: matchIdx - iIndex,
12338 },
12339 };
12340 matchIdx += matchesLength[m];
12341 while (i !== end && matchIdx > iIndex + textContentItemsStr[i].length) {
12342 iIndex += textContentItemsStr[i].length;
12343 i++;
12344 }
12345 match.end = {
12346 divIdx: i,
12347 offset: matchIdx - iIndex,
12348 };
12349 result.push(match);
12350 }
12351 return result;
12352 }
12353 _renderMatches(matches) {
12354 if (matches.length === 0) {
12355 return;
12356 }
12357 const { findController, pageIdx } = this;
12358 const { textContentItemsStr, textDivs } = this;
12359 const isSelectedPage = pageIdx === findController.selected.pageIdx;
12360 const selectedMatchIdx = findController.selected.matchIdx;
12361 const highlightAll = findController.state.highlightAll;
12362 let prevEnd = null;
12363 const infinity = {
12364 divIdx: -1,
12365 offset: undefined,
12366 };
12367 function beginText(begin, className) {
12368 const divIdx = begin.divIdx;
12369 textDivs[divIdx].textContent = '';
12370 return appendTextToDiv(divIdx, 0, begin.offset, className);
12371 }
12372 function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
12373 let div = textDivs[divIdx];
12374 if (div.nodeType === Node.TEXT_NODE) {
12375 const span = document.createElement('span');
12376 div.before(span);
12377 span.append(div);
12378 textDivs[divIdx] = span;
12379 div = span;
12380 }
12381 const content = textContentItemsStr[divIdx].substring(fromOffset, toOffset);
12382 const node = document.createTextNode(content);
12383 if (className) {
12384 const span = document.createElement('span');
12385 span.className = `${className} appended`;
12386 span.append(node);
12387 div.append(span);
12388 if (className.includes('selected')) {
12389 const { left } = span.getClientRects()[0];
12390 const parentLeft = div.getBoundingClientRect().left;
12391 return left - parentLeft;
12392 }
12393 return 0;
12394 }
12395 div.append(node);
12396 return 0;
12397 }
12398 let i0 = selectedMatchIdx,
12399 i1 = i0 + 1;
12400 if (highlightAll) {
12401 i0 = 0;
12402 i1 = matches.length;
12403 } else if (!isSelectedPage) {
12404 return;
12405 }
12406 let lastDivIdx = -1;
12407 let lastOffset = -1;
12408 for (let i = i0; i < i1; i++) {
12409 const match = matches[i];
12410 const begin = match.begin;
12411 if (begin.divIdx === lastDivIdx && begin.offset === lastOffset) {
12412 continue;
12413 }
12414 lastDivIdx = begin.divIdx;
12415 lastOffset = begin.offset;
12416 const end = match.end;
12417 const isSelected = isSelectedPage && i === selectedMatchIdx;
12418 const highlightSuffix = isSelected ? ' selected' : '';
12419 let selectedLeft = 0;
12420 if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
12421 if (prevEnd !== null) {
12422 appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
12423 }
12424 beginText(begin);
12425 } else {
12426 appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
12427 }
12428 if (begin.divIdx === end.divIdx) {
12429 selectedLeft = appendTextToDiv(
12430 begin.divIdx,
12431 begin.offset,
12432 end.offset,
12433 'highlight' + highlightSuffix
12434 );
12435 } else {
12436 selectedLeft = appendTextToDiv(
12437 begin.divIdx,
12438 begin.offset,
12439 infinity.offset,
12440 'highlight begin' + highlightSuffix
12441 );
12442 for (let n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
12443 textDivs[n0].className = 'highlight middle' + highlightSuffix;
12444 }
12445 beginText(end, 'highlight end' + highlightSuffix);
12446 }
12447 prevEnd = end;
12448 if (isSelected) {
12449 findController.scrollMatchIntoView({
12450 element: textDivs[begin.divIdx],
12451 selectedLeft,
12452 pageIndex: pageIdx,
12453 matchIndex: selectedMatchIdx,
12454 });
12455 }
12456 }
12457 if (prevEnd) {
12458 appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
12459 }
12460 }
12461 _updateMatches(reset = false) {
12462 if (!this.enabled && !reset) {
12463 return;
12464 }
12465 const { findController, matches, pageIdx } = this;
12466 const { textContentItemsStr, textDivs } = this;
12467 let clearedUntilDivIdx = -1;
12468 for (const match of matches) {
12469 const begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
12470 for (let n = begin, end = match.end.divIdx; n <= end; n++) {
12471 const div = textDivs[n];
12472 div.textContent = textContentItemsStr[n];
12473 div.className = '';
12474 }
12475 clearedUntilDivIdx = match.end.divIdx + 1;
12476 }
12477 if (!findController?.highlightMatches || reset) {
12478 return;
12479 }
12480 const pageMatches = findController.pageMatches[pageIdx] || null;
12481 const pageMatchesLength = findController.pageMatchesLength[pageIdx] || null;
12482 this.matches = this._convertMatches(pageMatches, pageMatchesLength);
12483 this._renderMatches(this.matches);
12484 }
12485} // ./web/text_layer_builder.js
12486
12487class TextLayerBuilder {
12488 #enablePermissions = false;
12489 #onAppend = null;
12490 #renderingDone = false;
12491 #textLayer = null;
12492 static #textLayers = new Map();
12493 static #selectionChangeAbortController = null;
12494 constructor({
12495 pdfPage,
12496 highlighter = null,
12497 accessibilityManager = null,
12498 enablePermissions = false,
12499 onAppend = null,
12500 }) {
12501 this.pdfPage = pdfPage;
12502 this.highlighter = highlighter;
12503 this.accessibilityManager = accessibilityManager;
12504 this.#enablePermissions = enablePermissions === true;
12505 this.#onAppend = onAppend;
12506 this.div = document.createElement('div');
12507 this.div.tabIndex = 0;
12508 this.div.className = 'textLayer';
12509 }
12510 async render({ viewport, textContentParams = null }) {
12511 if (this.#renderingDone && this.#textLayer) {
12512 this.#textLayer.update({
12513 viewport,
12514 onBefore: this.hide.bind(this),
12515 });
12516 this.show();
12517 return;
12518 }
12519 this.cancel();
12520 this.#textLayer = new TextLayer({
12521 textContentSource: this.pdfPage.streamTextContent(
12522 textContentParams || {
12523 includeMarkedContent: true,
12524 disableNormalization: true,
12525 }
12526 ),
12527 container: this.div,
12528 viewport,
12529 });
12530 const { textDivs, textContentItemsStr } = this.#textLayer;
12531 this.highlighter?.setTextMapping(textDivs, textContentItemsStr);
12532 this.accessibilityManager?.setTextMapping(textDivs);
12533 await this.#textLayer.render();
12534 this.#renderingDone = true;
12535 const endOfContent = document.createElement('div');
12536 endOfContent.className = 'endOfContent';
12537 this.div.append(endOfContent);
12538 this.#bindMouse(endOfContent);
12539 this.#onAppend?.(this.div);
12540 this.highlighter?.enable();
12541 this.accessibilityManager?.enable();
12542 }
12543 hide() {
12544 if (!this.div.hidden && this.#renderingDone) {
12545 this.highlighter?.disable();
12546 this.div.hidden = true;
12547 }
12548 }
12549 show() {
12550 if (this.div.hidden && this.#renderingDone) {
12551 this.div.hidden = false;
12552 this.highlighter?.enable();
12553 }
12554 }
12555 cancel() {
12556 this.#textLayer?.cancel();
12557 this.#textLayer = null;
12558 this.highlighter?.disable();
12559 this.accessibilityManager?.disable();
12560 TextLayerBuilder.#removeGlobalSelectionListener(this.div);
12561 }
12562 #bindMouse(end) {
12563 const { div } = this;
12564 div.addEventListener('mousedown', () => {
12565 div.classList.add('selecting');
12566 });
12567 div.addEventListener('copy', (event) => {
12568 if (!this.#enablePermissions) {
12569 const selection = document.getSelection();
12570 event.clipboardData.setData(
12571 'text/plain',
12572 removeNullCharacters(normalizeUnicode(selection.toString()))
12573 );
12574 }
12575 stopEvent(event);
12576 });
12577 TextLayerBuilder.#textLayers.set(div, end);
12578 TextLayerBuilder.#enableGlobalSelectionListener();
12579 }
12580 static #removeGlobalSelectionListener(textLayerDiv) {
12581 this.#textLayers.delete(textLayerDiv);
12582 if (this.#textLayers.size === 0) {
12583 this.#selectionChangeAbortController?.abort();
12584 this.#selectionChangeAbortController = null;
12585 }
12586 }
12587 static #enableGlobalSelectionListener() {
12588 if (this.#selectionChangeAbortController) {
12589 return;
12590 }
12591 this.#selectionChangeAbortController = new AbortController();
12592 const { signal } = this.#selectionChangeAbortController;
12593 const reset = (end, textLayer) => {
12594 textLayer.append(end);
12595 end.style.width = '';
12596 end.style.height = '';
12597 textLayer.classList.remove('selecting');
12598 };
12599 let isPointerDown = false;
12600 document.addEventListener(
12601 'pointerdown',
12602 () => {
12603 isPointerDown = true;
12604 },
12605 {
12606 signal,
12607 }
12608 );
12609 document.addEventListener(
12610 'pointerup',
12611 () => {
12612 isPointerDown = false;
12613 this.#textLayers.forEach(reset);
12614 },
12615 {
12616 signal,
12617 }
12618 );
12619 window.addEventListener(
12620 'blur',
12621 () => {
12622 isPointerDown = false;
12623 this.#textLayers.forEach(reset);
12624 },
12625 {
12626 signal,
12627 }
12628 );
12629 document.addEventListener(
12630 'keyup',
12631 () => {
12632 if (!isPointerDown) {
12633 this.#textLayers.forEach(reset);
12634 }
12635 },
12636 {
12637 signal,
12638 }
12639 );
12640 var isFirefox, prevRange;
12641 document.addEventListener(
12642 'selectionchange',
12643 () => {
12644 const selection = document.getSelection();
12645 if (selection.rangeCount === 0) {
12646 this.#textLayers.forEach(reset);
12647 return;
12648 }
12649 const activeTextLayers = new Set();
12650 for (let i = 0; i < selection.rangeCount; i++) {
12651 const range = selection.getRangeAt(i);
12652 for (const textLayerDiv of this.#textLayers.keys()) {
12653 if (!activeTextLayers.has(textLayerDiv) && range.intersectsNode(textLayerDiv)) {
12654 activeTextLayers.add(textLayerDiv);
12655 }
12656 }
12657 }
12658 for (const [textLayerDiv, endDiv] of this.#textLayers) {
12659 if (activeTextLayers.has(textLayerDiv)) {
12660 textLayerDiv.classList.add('selecting');
12661 } else {
12662 reset(endDiv, textLayerDiv);
12663 }
12664 }
12665 isFirefox ??=
12666 getComputedStyle(this.#textLayers.values().next().value).getPropertyValue(
12667 '-moz-user-select'
12668 ) === 'none';
12669 if (isFirefox) {
12670 return;
12671 }
12672 const range = selection.getRangeAt(0);
12673 const modifyStart =
12674 prevRange &&
12675 (range.compareBoundaryPoints(Range.END_TO_END, prevRange) === 0 ||
12676 range.compareBoundaryPoints(Range.START_TO_END, prevRange) === 0);
12677 let anchor = modifyStart ? range.startContainer : range.endContainer;
12678 if (anchor.nodeType === Node.TEXT_NODE) {
12679 anchor = anchor.parentNode;
12680 }
12681 if (!modifyStart && range.endOffset === 0) {
12682 do {
12683 while (!anchor.previousSibling) {
12684 anchor = anchor.parentNode;
12685 }
12686 anchor = anchor.previousSibling;
12687 } while (!anchor.childNodes.length);
12688 }
12689 const parentTextLayer = anchor.parentElement?.closest('.textLayer');
12690 const endDiv = this.#textLayers.get(parentTextLayer);
12691 if (endDiv) {
12692 endDiv.style.width = parentTextLayer.style.width;
12693 endDiv.style.height = parentTextLayer.style.height;
12694 endDiv.style.userSelect = 'text';
12695 anchor.parentElement.insertBefore(endDiv, modifyStart ? anchor : anchor.nextSibling);
12696 }
12697 prevRange = range.cloneRange();
12698 },
12699 {
12700 signal,
12701 }
12702 );
12703 }
12704} // ./web/pdf_page_view.js
12705
12706const DEFAULT_LAYER_PROPERTIES = null;
12707const LAYERS_ORDER = new Map([
12708 ['canvasWrapper', 0],
12709 ['textLayer', 1],
12710 ['annotationLayer', 2],
12711 ['annotationEditorLayer', 3],
12712 ['xfaLayer', 3],
12713]);
12714class PDFPageView extends BasePDFPageView {
12715 #annotationMode = AnnotationMode.ENABLE_FORMS;
12716 #canvasWrapper = null;
12717 #commentManager = null;
12718 #enableAutoLinking = true;
12719 #hasRestrictedScaling = false;
12720 #isEditing = false;
12721 #layerProperties = null;
12722 #needsRestrictedScaling = false;
12723 #originalViewport = null;
12724 #previousRotation = null;
12725 #scaleRoundX = 1;
12726 #scaleRoundY = 1;
12727 #textLayerMode = TextLayerMode.ENABLE;
12728 #userUnit = 1;
12729 #useThumbnailCanvas = {
12730 directDrawing: true,
12731 initialOptionalContent: true,
12732 regularAnnotations: true,
12733 };
12734 #layers = [null, null, null, null];
12735 constructor(options) {
12736 super(options);
12737 const container = options.container;
12738 const defaultViewport = options.defaultViewport;
12739 this.renderingId = 'page' + this.id;
12740 this.#layerProperties = options.layerProperties || DEFAULT_LAYER_PROPERTIES;
12741 this.pdfPage = null;
12742 this.pageLabel = null;
12743 this.rotation = 0;
12744 this.scale = options.scale || DEFAULT_SCALE;
12745 this.viewport = defaultViewport;
12746 this.pdfPageRotate = defaultViewport.rotation;
12747 this._optionalContentConfigPromise = options.optionalContentConfigPromise || null;
12748 this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
12749 this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
12750 this.imageResourcesPath = options.imageResourcesPath || '';
12751 this.enableDetailCanvas = options.enableDetailCanvas ?? true;
12752 this.maxCanvasPixels = options.maxCanvasPixels ?? AppOptions.get('maxCanvasPixels');
12753 this.maxCanvasDim = options.maxCanvasDim || AppOptions.get('maxCanvasDim');
12754 this.capCanvasAreaFactor = options.capCanvasAreaFactor ?? AppOptions.get('capCanvasAreaFactor');
12755 this.#enableAutoLinking = options.enableAutoLinking !== false;
12756 this.#commentManager = options.commentManager || null;
12757 this.l10n = options.l10n;
12758 this.l10n ||= new genericl10n_GenericL10n();
12759 this._isStandalone = !this.renderingQueue?.hasViewer();
12760 this._container = container;
12761 this._annotationCanvasMap = null;
12762 this.annotationLayer = null;
12763 this.annotationEditorLayer = null;
12764 this.textLayer = null;
12765 this.xfaLayer = null;
12766 this.structTreeLayer = null;
12767 this.drawLayer = null;
12768 this.detailView = null;
12769 const div = document.createElement('div');
12770 div.className = 'page';
12771 div.setAttribute('data-page-number', this.id);
12772 div.setAttribute('role', 'region');
12773 div.setAttribute('data-l10n-id', 'pdfjs-page-landmark');
12774 div.setAttribute(
12775 'data-l10n-args',
12776 JSON.stringify({
12777 page: this.id,
12778 })
12779 );
12780 this.div = div;
12781 this.#setDimensions();
12782 container?.append(div);
12783 if (this._isStandalone) {
12784 container?.style.setProperty('--scale-factor', this.scale * PixelsPerInch.PDF_TO_CSS_UNITS);
12785 if (this.pageColors?.background) {
12786 container?.style.setProperty('--page-bg-color', this.pageColors.background);
12787 }
12788 const { optionalContentConfigPromise } = options;
12789 if (optionalContentConfigPromise) {
12790 optionalContentConfigPromise.then((optionalContentConfig) => {
12791 if (optionalContentConfigPromise !== this._optionalContentConfigPromise) {
12792 return;
12793 }
12794 this.#useThumbnailCanvas.initialOptionalContent =
12795 optionalContentConfig.hasInitialVisibility;
12796 });
12797 }
12798 if (!options.l10n) {
12799 this.l10n.translate(this.div);
12800 }
12801 }
12802 }
12803 #addLayer(div, name) {
12804 const pos = LAYERS_ORDER.get(name);
12805 const oldDiv = this.#layers[pos];
12806 this.#layers[pos] = div;
12807 if (oldDiv) {
12808 oldDiv.replaceWith(div);
12809 return;
12810 }
12811 for (let i = pos - 1; i >= 0; i--) {
12812 const layer = this.#layers[i];
12813 if (layer) {
12814 layer.after(div);
12815 return;
12816 }
12817 }
12818 this.div.prepend(div);
12819 }
12820 #setDimensions() {
12821 const { div, viewport } = this;
12822 if (viewport.userUnit !== this.#userUnit) {
12823 if (viewport.userUnit !== 1) {
12824 div.style.setProperty('--user-unit', viewport.userUnit);
12825 } else {
12826 div.style.removeProperty('--user-unit');
12827 }
12828 this.#userUnit = viewport.userUnit;
12829 }
12830 if (this.pdfPage) {
12831 if (this.#previousRotation === viewport.rotation) {
12832 return;
12833 }
12834 this.#previousRotation = viewport.rotation;
12835 }
12836 setLayerDimensions(div, viewport, true, false);
12837 }
12838 updatePageNumber(newPageNumber) {
12839 if (this.id === newPageNumber) {
12840 return;
12841 }
12842 this.id = newPageNumber;
12843 this.renderingId = `page${newPageNumber}`;
12844 if (this.pdfPage) {
12845 this.pdfPage.pageNumber = newPageNumber;
12846 }
12847 this.setPageLabel(this.pageLabel);
12848 const { div } = this;
12849 div.setAttribute('data-page-number', newPageNumber);
12850 div.setAttribute(
12851 'data-l10n-args',
12852 JSON.stringify({
12853 page: newPageNumber,
12854 })
12855 );
12856 this._textHighlighter.pageIdx = newPageNumber - 1;
12857 }
12858 setPdfPage(pdfPage) {
12859 if (
12860 this._isStandalone &&
12861 (this.pageColors?.foreground === 'CanvasText' || this.pageColors?.background === 'Canvas')
12862 ) {
12863 this._container?.style.setProperty(
12864 '--hcm-highlight-filter',
12865 pdfPage.filterFactory.addHighlightHCMFilter(
12866 'highlight',
12867 'CanvasText',
12868 'Canvas',
12869 'HighlightText',
12870 'Highlight'
12871 )
12872 );
12873 this._container?.style.setProperty(
12874 '--hcm-highlight-selected-filter',
12875 pdfPage.filterFactory.addHighlightHCMFilter(
12876 'highlight_selected',
12877 'CanvasText',
12878 'Canvas',
12879 'HighlightText',
12880 'Highlight'
12881 )
12882 );
12883 }
12884 this.pdfPage = pdfPage;
12885 this.pdfPageRotate = pdfPage.rotate;
12886 const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
12887 this.viewport = pdfPage.getViewport({
12888 scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,
12889 rotation: totalRotation,
12890 });
12891 this.#setDimensions();
12892 this.reset();
12893 }
12894 destroy() {
12895 this.reset();
12896 this.pdfPage?.cleanup();
12897 }
12898 hasEditableAnnotations() {
12899 return !!this.annotationLayer?.hasEditableAnnotations();
12900 }
12901 get _textHighlighter() {
12902 return shadow(
12903 this,
12904 '_textHighlighter',
12905 new TextHighlighter({
12906 pageIndex: this.id - 1,
12907 eventBus: this.eventBus,
12908 findController: this.#layerProperties.findController,
12909 })
12910 );
12911 }
12912 #dispatchLayerRendered(name, error) {
12913 this.eventBus.dispatch(name, {
12914 source: this,
12915 pageNumber: this.id,
12916 error,
12917 });
12918 }
12919 async #renderAnnotationLayer() {
12920 let error = null;
12921 try {
12922 await this.annotationLayer.render({
12923 viewport: this.viewport,
12924 intent: 'display',
12925 structTreeLayer: this.structTreeLayer,
12926 });
12927 } catch (ex) {
12928 console.error('#renderAnnotationLayer:', ex);
12929 error = ex;
12930 } finally {
12931 this.#dispatchLayerRendered('annotationlayerrendered', error);
12932 }
12933 }
12934 async #renderAnnotationEditorLayer() {
12935 let error = null;
12936 try {
12937 await this.annotationEditorLayer.render({
12938 viewport: this.viewport,
12939 intent: 'display',
12940 });
12941 } catch (ex) {
12942 console.error('#renderAnnotationEditorLayer:', ex);
12943 error = ex;
12944 } finally {
12945 this.#dispatchLayerRendered('annotationeditorlayerrendered', error);
12946 }
12947 }
12948 async #renderDrawLayer() {
12949 try {
12950 await this.drawLayer.render({
12951 intent: 'display',
12952 });
12953 } catch (ex) {
12954 console.error('#renderDrawLayer:', ex);
12955 }
12956 }
12957 async #renderXfaLayer() {
12958 let error = null;
12959 try {
12960 const result = await this.xfaLayer.render({
12961 viewport: this.viewport,
12962 intent: 'display',
12963 });
12964 if (result?.textDivs && this._textHighlighter) {
12965 this.#buildXfaTextContentItems(result.textDivs);
12966 }
12967 } catch (ex) {
12968 console.error('#renderXfaLayer:', ex);
12969 error = ex;
12970 } finally {
12971 if (this.xfaLayer?.div) {
12972 this.l10n.pause();
12973 this.#addLayer(this.xfaLayer.div, 'xfaLayer');
12974 this.l10n.resume();
12975 }
12976 this.#dispatchLayerRendered('xfalayerrendered', error);
12977 }
12978 }
12979 async #renderTextLayer() {
12980 if (!this.textLayer) {
12981 return;
12982 }
12983 let error = null;
12984 try {
12985 await this.textLayer.render({
12986 viewport: this.viewport,
12987 });
12988 } catch (ex) {
12989 if (ex instanceof AbortException) {
12990 return;
12991 }
12992 console.error('#renderTextLayer:', ex);
12993 error = ex;
12994 }
12995 this.#dispatchLayerRendered('textlayerrendered', error);
12996 this.#renderStructTreeLayer();
12997 }
12998 async #renderStructTreeLayer() {
12999 if (!this.textLayer) {
13000 return;
13001 }
13002 const treeDom = await this.structTreeLayer?.render();
13003 if (treeDom) {
13004 this.l10n.pause();
13005 this.structTreeLayer?.updateTextLayer();
13006 if (this.canvas && treeDom.parentNode !== this.canvas) {
13007 this.canvas.append(treeDom);
13008 }
13009 this.l10n.resume();
13010 }
13011 this.structTreeLayer?.show();
13012 }
13013 async #buildXfaTextContentItems(textDivs) {
13014 const text = await this.pdfPage.getTextContent();
13015 const items = [];
13016 for (const item of text.items) {
13017 items.push(item.str);
13018 }
13019 this._textHighlighter.setTextMapping(textDivs, items);
13020 this._textHighlighter.enable();
13021 }
13022 async #injectLinkAnnotations(textLayerPromise) {
13023 let error = null;
13024 try {
13025 await textLayerPromise;
13026 if (!this.annotationLayer) {
13027 return;
13028 }
13029 await this.annotationLayer.injectLinkAnnotations(Autolinker.processLinks(this));
13030 } catch (ex) {
13031 console.error('#injectLinkAnnotations:', ex);
13032 error = ex;
13033 }
13034 }
13035 _resetCanvas() {
13036 super._resetCanvas();
13037 this.#originalViewport = null;
13038 }
13039 reset({
13040 keepAnnotationLayer = false,
13041 keepAnnotationEditorLayer = false,
13042 keepXfaLayer = false,
13043 keepTextLayer = false,
13044 keepCanvasWrapper = false,
13045 preserveDetailViewState = false,
13046 } = {}) {
13047 const keepPdfBugGroups = this.pdfPage?._pdfBug ?? false;
13048 this.cancelRendering({
13049 keepAnnotationLayer,
13050 keepAnnotationEditorLayer,
13051 keepXfaLayer,
13052 keepTextLayer,
13053 });
13054 this.renderingState = RenderingStates.INITIAL;
13055 const div = this.div;
13056 const childNodes = div.childNodes,
13057 annotationLayerNode = (keepAnnotationLayer && this.annotationLayer?.div) || null,
13058 annotationEditorLayerNode =
13059 (keepAnnotationEditorLayer && this.annotationEditorLayer?.div) || null,
13060 xfaLayerNode = (keepXfaLayer && this.xfaLayer?.div) || null,
13061 textLayerNode = (keepTextLayer && this.textLayer?.div) || null,
13062 canvasWrapperNode = (keepCanvasWrapper && this.#canvasWrapper) || null;
13063 for (let i = childNodes.length - 1; i >= 0; i--) {
13064 const node = childNodes[i];
13065 switch (node) {
13066 case annotationLayerNode:
13067 case annotationEditorLayerNode:
13068 case xfaLayerNode:
13069 case textLayerNode:
13070 case canvasWrapperNode:
13071 continue;
13072 }
13073 if (keepPdfBugGroups && node.classList.contains('pdfBugGroupsLayer')) {
13074 continue;
13075 }
13076 node.remove();
13077 const layerIndex = this.#layers.indexOf(node);
13078 if (layerIndex >= 0) {
13079 this.#layers[layerIndex] = null;
13080 }
13081 }
13082 div.removeAttribute('data-loaded');
13083 if (annotationLayerNode) {
13084 this.annotationLayer.hide();
13085 }
13086 if (annotationEditorLayerNode) {
13087 this.annotationEditorLayer.hide();
13088 }
13089 if (xfaLayerNode) {
13090 this.xfaLayer.hide();
13091 }
13092 if (textLayerNode) {
13093 this.textLayer.hide();
13094 }
13095 this.structTreeLayer?.hide();
13096 if (!keepCanvasWrapper && this.#canvasWrapper) {
13097 this.#canvasWrapper = null;
13098 this._resetCanvas();
13099 }
13100 if (!preserveDetailViewState) {
13101 this.detailView?.reset({
13102 keepCanvas: keepCanvasWrapper,
13103 });
13104 if (!keepCanvasWrapper) {
13105 this.detailView = null;
13106 }
13107 }
13108 }
13109 toggleEditingMode(isEditing) {
13110 this.#isEditing = isEditing;
13111 if (!this.hasEditableAnnotations()) {
13112 return;
13113 }
13114 this.reset({
13115 keepAnnotationLayer: true,
13116 keepAnnotationEditorLayer: true,
13117 keepXfaLayer: true,
13118 keepTextLayer: true,
13119 keepCanvasWrapper: true,
13120 });
13121 }
13122 updateVisibleArea(visibleArea) {
13123 if (this.enableDetailCanvas) {
13124 if (this.#needsRestrictedScaling && this.maxCanvasPixels > 0 && visibleArea) {
13125 this.detailView ??= new PDFPageDetailView({
13126 pageView: this,
13127 enableOptimizedPartialRendering: this.enableOptimizedPartialRendering,
13128 });
13129 this.detailView.update({
13130 visibleArea,
13131 });
13132 } else if (this.detailView) {
13133 this.detailView.reset();
13134 this.detailView = null;
13135 }
13136 }
13137 }
13138 update({ scale = 0, rotation = null, optionalContentConfigPromise = null, drawingDelay = -1 }) {
13139 this.scale = scale || this.scale;
13140 if (typeof rotation === 'number') {
13141 this.rotation = rotation;
13142 }
13143 if (optionalContentConfigPromise instanceof Promise) {
13144 this._optionalContentConfigPromise = optionalContentConfigPromise;
13145 optionalContentConfigPromise.then((optionalContentConfig) => {
13146 if (optionalContentConfigPromise !== this._optionalContentConfigPromise) {
13147 return;
13148 }
13149 this.#useThumbnailCanvas.initialOptionalContent =
13150 optionalContentConfig.hasInitialVisibility;
13151 });
13152 }
13153 this.#useThumbnailCanvas.directDrawing = true;
13154 const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
13155 this.viewport = this.viewport.clone({
13156 scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,
13157 rotation: totalRotation,
13158 });
13159 this.#setDimensions();
13160 if (this._isStandalone) {
13161 this._container?.style.setProperty('--scale-factor', this.viewport.scale);
13162 }
13163 this.#computeScale();
13164 if (this.canvas) {
13165 const onlyCssZoom = this.#hasRestrictedScaling && this.#needsRestrictedScaling;
13166 const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000;
13167 if (postponeDrawing || onlyCssZoom) {
13168 if (postponeDrawing && !onlyCssZoom && this.renderingState !== RenderingStates.FINISHED) {
13169 this.cancelRendering({
13170 keepAnnotationLayer: true,
13171 keepAnnotationEditorLayer: true,
13172 keepXfaLayer: true,
13173 keepTextLayer: true,
13174 cancelExtraDelay: drawingDelay,
13175 });
13176 this.renderingState = RenderingStates.FINISHED;
13177 this.#useThumbnailCanvas.directDrawing = false;
13178 }
13179 this.cssTransform({
13180 redrawAnnotationLayer: true,
13181 redrawAnnotationEditorLayer: true,
13182 redrawXfaLayer: true,
13183 redrawTextLayer: !postponeDrawing,
13184 hideTextLayer: postponeDrawing,
13185 });
13186 if (!postponeDrawing) {
13187 this.detailView?.update({
13188 underlyingViewUpdated: true,
13189 });
13190 this.dispatchPageRendered(true, false);
13191 }
13192 return;
13193 }
13194 }
13195 this.cssTransform({});
13196 this.reset({
13197 keepAnnotationLayer: true,
13198 keepAnnotationEditorLayer: true,
13199 keepXfaLayer: true,
13200 keepTextLayer: true,
13201 keepCanvasWrapper: true,
13202 preserveDetailViewState: true,
13203 });
13204 this.detailView?.update({
13205 underlyingViewUpdated: true,
13206 });
13207 }
13208 #computeScale() {
13209 const { width, height } = this.viewport;
13210 const outputScale = (this.outputScale = new OutputScale());
13211 if (this.maxCanvasPixels === 0) {
13212 const invScale = 1 / this.scale;
13213 outputScale.sx *= invScale;
13214 outputScale.sy *= invScale;
13215 this.#needsRestrictedScaling = true;
13216 } else {
13217 this.#needsRestrictedScaling = outputScale.limitCanvas(
13218 width,
13219 height,
13220 this.maxCanvasPixels,
13221 this.maxCanvasDim,
13222 this.capCanvasAreaFactor
13223 );
13224 if (this.#needsRestrictedScaling && this.enableDetailCanvas) {
13225 const factor = this.enableOptimizedPartialRendering ? 4 : 2;
13226 outputScale.sx /= factor;
13227 outputScale.sy /= factor;
13228 }
13229 }
13230 }
13231 cancelRendering({
13232 keepAnnotationLayer = false,
13233 keepAnnotationEditorLayer = false,
13234 keepXfaLayer = false,
13235 keepTextLayer = false,
13236 cancelExtraDelay = 0,
13237 } = {}) {
13238 super.cancelRendering({
13239 cancelExtraDelay,
13240 });
13241 if (this.textLayer && (!keepTextLayer || !this.textLayer.div)) {
13242 this.textLayer.cancel();
13243 this.textLayer = null;
13244 }
13245 if (this.annotationLayer && (!keepAnnotationLayer || !this.annotationLayer.div)) {
13246 this.annotationLayer.cancel();
13247 this.annotationLayer = null;
13248 this._annotationCanvasMap = null;
13249 }
13250 if (this.structTreeLayer && !this.textLayer) {
13251 this.structTreeLayer = null;
13252 }
13253 if (
13254 this.annotationEditorLayer &&
13255 (!keepAnnotationEditorLayer || !this.annotationEditorLayer.div)
13256 ) {
13257 if (this.drawLayer) {
13258 this.drawLayer.cancel();
13259 this.drawLayer = null;
13260 }
13261 this.annotationEditorLayer.cancel();
13262 this.annotationEditorLayer = null;
13263 }
13264 if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {
13265 this.xfaLayer.cancel();
13266 this.xfaLayer = null;
13267 this._textHighlighter?.disable();
13268 }
13269 }
13270 cssTransform({
13271 redrawAnnotationLayer = false,
13272 redrawAnnotationEditorLayer = false,
13273 redrawXfaLayer = false,
13274 redrawTextLayer = false,
13275 hideTextLayer = false,
13276 }) {
13277 const { canvas } = this;
13278 if (!canvas) {
13279 return;
13280 }
13281 const originalViewport = this.#originalViewport;
13282 if (this.viewport !== originalViewport) {
13283 const relativeRotation = (360 + this.viewport.rotation - originalViewport.rotation) % 360;
13284 if (relativeRotation === 90 || relativeRotation === 270) {
13285 const { width, height } = this.viewport;
13286 const scaleX = height / width;
13287 const scaleY = width / height;
13288 canvas.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX},${scaleY})`;
13289 } else {
13290 canvas.style.transform = relativeRotation === 0 ? '' : `rotate(${relativeRotation}deg)`;
13291 }
13292 }
13293 if (redrawAnnotationLayer && this.annotationLayer) {
13294 this.#renderAnnotationLayer();
13295 }
13296 if (redrawAnnotationEditorLayer && this.annotationEditorLayer) {
13297 if (this.drawLayer) {
13298 this.#renderDrawLayer();
13299 }
13300 this.#renderAnnotationEditorLayer();
13301 }
13302 if (redrawXfaLayer && this.xfaLayer) {
13303 this.#renderXfaLayer();
13304 }
13305 if (this.textLayer) {
13306 if (hideTextLayer) {
13307 this.textLayer.hide();
13308 this.structTreeLayer?.hide();
13309 } else if (redrawTextLayer) {
13310 this.#renderTextLayer();
13311 }
13312 }
13313 }
13314 get width() {
13315 return this.viewport.width;
13316 }
13317 get height() {
13318 return this.viewport.height;
13319 }
13320 getPagePoint(x, y) {
13321 return this.viewport.convertToPdfPoint(x, y);
13322 }
13323 _ensureCanvasWrapper() {
13324 let canvasWrapper = this.#canvasWrapper;
13325 if (!canvasWrapper) {
13326 canvasWrapper = this.#canvasWrapper = document.createElement('div');
13327 canvasWrapper.classList.add('canvasWrapper');
13328 this.#addLayer(canvasWrapper, 'canvasWrapper');
13329 }
13330 return canvasWrapper;
13331 }
13332 _getRenderingContext(canvas, transform, recordOperations) {
13333 return {
13334 canvas,
13335 transform,
13336 viewport: this.viewport,
13337 annotationMode: this.#annotationMode,
13338 optionalContentConfigPromise: this._optionalContentConfigPromise,
13339 annotationCanvasMap: this._annotationCanvasMap,
13340 pageColors: this.pageColors,
13341 isEditing: this.#isEditing,
13342 recordOperations,
13343 };
13344 }
13345 async draw() {
13346 if (this.renderingState !== RenderingStates.INITIAL) {
13347 console.error('Must be in new state before drawing');
13348 this.reset();
13349 }
13350 const { div, l10n, pdfPage, viewport } = this;
13351 if (!pdfPage) {
13352 this.renderingState = RenderingStates.FINISHED;
13353 throw new Error('pdfPage is not loaded');
13354 }
13355 this.renderingState = RenderingStates.RUNNING;
13356 const canvasWrapper = this._ensureCanvasWrapper();
13357 if (!this.textLayer && this.#textLayerMode !== TextLayerMode.DISABLE && !pdfPage.isPureXfa) {
13358 this._accessibilityManager ||= new TextAccessibilityManager();
13359 this.textLayer = new TextLayerBuilder({
13360 pdfPage,
13361 highlighter: this._textHighlighter,
13362 accessibilityManager: this._accessibilityManager,
13363 enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS,
13364 onAppend: (textLayerDiv) => {
13365 this.l10n.pause();
13366 this.#addLayer(textLayerDiv, 'textLayer');
13367 this.l10n.resume();
13368 },
13369 });
13370 }
13371 if (!this.annotationLayer && this.#annotationMode !== AnnotationMode.DISABLE) {
13372 const {
13373 annotationStorage,
13374 annotationEditorUIManager,
13375 downloadManager,
13376 enableComment,
13377 enableScripting,
13378 fieldObjectsPromise,
13379 hasJSActionsPromise,
13380 linkService,
13381 } = this.#layerProperties;
13382 this._annotationCanvasMap ||= new Map();
13383 this.annotationLayer = new AnnotationLayerBuilder({
13384 pdfPage,
13385 annotationStorage,
13386 imageResourcesPath: this.imageResourcesPath,
13387 renderForms: this.#annotationMode === AnnotationMode.ENABLE_FORMS,
13388 linkService,
13389 downloadManager,
13390 enableComment,
13391 enableScripting,
13392 hasJSActionsPromise,
13393 fieldObjectsPromise,
13394 annotationCanvasMap: this._annotationCanvasMap,
13395 accessibilityManager: this._accessibilityManager,
13396 annotationEditorUIManager,
13397 commentManager: this.#commentManager,
13398 onAppend: (annotationLayerDiv) => {
13399 this.#addLayer(annotationLayerDiv, 'annotationLayer');
13400 },
13401 });
13402 }
13403 const { width, height } = viewport;
13404 this.#originalViewport = viewport;
13405 const { canvas, prevCanvas } = this._createCanvas((newCanvas) => {
13406 canvasWrapper.prepend(newCanvas);
13407 });
13408 canvas.setAttribute('role', 'presentation');
13409 if (!this.outputScale) {
13410 this.#computeScale();
13411 }
13412 const { outputScale } = this;
13413 this.#hasRestrictedScaling = this.#needsRestrictedScaling;
13414 const sfx = approximateFraction(outputScale.sx);
13415 const sfy = approximateFraction(outputScale.sy);
13416 const canvasWidth = (canvas.width = floorToDivide(calcRound(width * outputScale.sx), sfx[0]));
13417 const canvasHeight = (canvas.height = floorToDivide(
13418 calcRound(height * outputScale.sy),
13419 sfy[0]
13420 ));
13421 const pageWidth = floorToDivide(calcRound(width), sfx[1]);
13422 const pageHeight = floorToDivide(calcRound(height), sfy[1]);
13423 outputScale.sx = canvasWidth / pageWidth;
13424 outputScale.sy = canvasHeight / pageHeight;
13425 if (this.#scaleRoundX !== sfx[1]) {
13426 div.style.setProperty('--scale-round-x', `${sfx[1]}px`);
13427 this.#scaleRoundX = sfx[1];
13428 }
13429 if (this.#scaleRoundY !== sfy[1]) {
13430 div.style.setProperty('--scale-round-y', `${sfy[1]}px`);
13431 this.#scaleRoundY = sfy[1];
13432 }
13433 const recordBBoxes =
13434 this.enableOptimizedPartialRendering && this.#hasRestrictedScaling && !this.recordedBBoxes;
13435 const transform = outputScale.scaled ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] : null;
13436 const resultPromise = this._drawCanvas(
13437 this._getRenderingContext(canvas, transform, recordBBoxes),
13438 () => {
13439 prevCanvas?.remove();
13440 this._resetCanvas();
13441 },
13442 (renderTask) => {
13443 this.#useThumbnailCanvas.regularAnnotations = !renderTask.separateAnnots;
13444 this.dispatchPageRendered(false, false);
13445 }
13446 ).then(async () => {
13447 if (this.renderingState !== RenderingStates.FINISHED) {
13448 return;
13449 }
13450 this.structTreeLayer ||= new StructTreeLayerBuilder(pdfPage, viewport.rawDims);
13451 const textLayerPromise = this.#renderTextLayer();
13452 if (this.annotationLayer) {
13453 await this.#renderAnnotationLayer();
13454 if (this.#enableAutoLinking && this.annotationLayer && this.textLayer) {
13455 await this.#injectLinkAnnotations(textLayerPromise);
13456 }
13457 }
13458 const { annotationEditorUIManager } = this.#layerProperties;
13459 if (!annotationEditorUIManager) {
13460 return;
13461 }
13462 this.drawLayer ||= new DrawLayerBuilder();
13463 await this.#renderDrawLayer();
13464 this.drawLayer.setParent(canvasWrapper);
13465 if (this.annotationLayer || this.#annotationMode === AnnotationMode.DISABLE) {
13466 this.annotationEditorLayer ||= new AnnotationEditorLayerBuilder({
13467 uiManager: annotationEditorUIManager,
13468 pdfPage,
13469 l10n,
13470 structTreeLayer: this.structTreeLayer,
13471 accessibilityManager: this._accessibilityManager,
13472 annotationLayer: this.annotationLayer?.annotationLayer,
13473 textLayer: this.textLayer,
13474 drawLayer: this.drawLayer.getDrawLayer(),
13475 onAppend: (annotationEditorLayerDiv) => {
13476 this.#addLayer(annotationEditorLayerDiv, 'annotationEditorLayer');
13477 },
13478 });
13479 this.#renderAnnotationEditorLayer();
13480 }
13481 });
13482 if (pdfPage.isPureXfa) {
13483 if (!this.xfaLayer) {
13484 const { annotationStorage, linkService } = this.#layerProperties;
13485 this.xfaLayer = new XfaLayerBuilder({
13486 pdfPage,
13487 annotationStorage,
13488 linkService,
13489 });
13490 }
13491 this.#renderXfaLayer();
13492 }
13493 div.setAttribute('data-loaded', true);
13494 this.dispatchPageRender();
13495 return resultPromise;
13496 }
13497 setPageLabel(label) {
13498 this.pageLabel = typeof label === 'string' ? label : null;
13499 this.div.setAttribute(
13500 'data-l10n-args',
13501 JSON.stringify({
13502 page: this.pageLabel ?? this.id,
13503 })
13504 );
13505 if (this.pageLabel !== null) {
13506 this.div.setAttribute('data-page-label', this.pageLabel);
13507 } else {
13508 this.div.removeAttribute('data-page-label');
13509 }
13510 }
13511 get thumbnailCanvas() {
13512 const { directDrawing, initialOptionalContent, regularAnnotations } = this.#useThumbnailCanvas;
13513 return directDrawing && initialOptionalContent && regularAnnotations ? this.canvas : null;
13514 }
13515} // ./web/pdf_viewer.js
13516
13517const DEFAULT_CACHE_SIZE = 10;
13518const PagesCountLimit = {
13519 FORCE_SCROLL_MODE_PAGE: 10000,
13520 FORCE_LAZY_PAGE_INIT: 5000,
13521 PAUSE_EAGER_PAGE_INIT: 250,
13522};
13523function isValidAnnotationEditorMode(mode) {
13524 return (
13525 Object.values(AnnotationEditorType).includes(mode) && mode !== AnnotationEditorType.DISABLE
13526 );
13527}
13528class PDFPageViewBuffer {
13529 #buf = new Set();
13530 #size = 0;
13531 constructor(size) {
13532 this.#size = size;
13533 }
13534 push(view) {
13535 const buf = this.#buf;
13536 if (buf.has(view)) {
13537 buf.delete(view);
13538 }
13539 buf.add(view);
13540 if (buf.size > this.#size) {
13541 this.#destroyFirstView();
13542 }
13543 }
13544 resize(newSize, idsToKeep = null) {
13545 this.#size = newSize;
13546 const buf = this.#buf;
13547 if (idsToKeep) {
13548 const ii = buf.size;
13549 let i = 1;
13550 for (const view of buf) {
13551 if (idsToKeep.has(view.id)) {
13552 buf.delete(view);
13553 buf.add(view);
13554 }
13555 if (++i > ii) {
13556 break;
13557 }
13558 }
13559 }
13560 while (buf.size > this.#size) {
13561 this.#destroyFirstView();
13562 }
13563 }
13564 has(view) {
13565 return this.#buf.has(view);
13566 }
13567 [Symbol.iterator]() {
13568 return this.#buf.keys();
13569 }
13570 #destroyFirstView() {
13571 const firstView = this.#buf.keys().next().value;
13572 firstView?.destroy();
13573 this.#buf.delete(firstView);
13574 }
13575}
13576class PDFViewer {
13577 #buffer = null;
13578 #altTextManager = null;
13579 #annotationEditorHighlightColors = null;
13580 #annotationEditorMode = AnnotationEditorType.NONE;
13581 #annotationEditorUIManager = null;
13582 #annotationMode = AnnotationMode.ENABLE_FORMS;
13583 #commentManager = null;
13584 #containerTopLeft = null;
13585 #editorUndoBar = null;
13586 #enableHWA = false;
13587 #enableHighlightFloatingButton = false;
13588 #enablePermissions = false;
13589 #enableUpdatedAddImage = false;
13590 #enableNewAltTextWhenAddingImage = false;
13591 #enableAutoLinking = true;
13592 #eventAbortController = null;
13593 #minDurationToUpdateCanvas = 0;
13594 #mlManager = null;
13595 #printingAllowed = true;
13596 #scrollTimeoutId = null;
13597 #switchAnnotationEditorModeAC = null;
13598 #switchAnnotationEditorModeTimeoutId = null;
13599 #getAllTextInProgress = false;
13600 #hiddenCopyElement = null;
13601 #interruptCopyCondition = false;
13602 #previousContainerHeight = 0;
13603 #resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this));
13604 #scrollModePageState = null;
13605 #scaleTimeoutId = null;
13606 #signatureManager = null;
13607 #supportsPinchToZoom = true;
13608 #textLayerMode = TextLayerMode.ENABLE;
13609 #viewerAlert = null;
13610 #pagesMapper = PagesMapper.instance;
13611 constructor(options) {
13612 const viewerVersion = '5.4.624';
13613 if (version !== viewerVersion) {
13614 throw new Error(
13615 `The API version "${version}" does not match the Viewer version "${viewerVersion}".`
13616 );
13617 }
13618 this.container = options.container;
13619 this.viewer = options.viewer || options.container.firstElementChild;
13620 this.#viewerAlert = options.viewerAlert || null;
13621 if (this.container?.tagName !== 'DIV' || this.viewer?.tagName !== 'DIV') {
13622 throw new Error('Invalid `container` and/or `viewer` option.');
13623 }
13624 if (this.container.offsetParent && getComputedStyle(this.container).position !== 'absolute') {
13625 throw new Error('The `container` must be absolutely positioned.');
13626 }
13627 this.#resizeObserver.observe(this.container);
13628 this.eventBus = options.eventBus;
13629 this.linkService = options.linkService || new SimpleLinkService();
13630 this.downloadManager = options.downloadManager || null;
13631 this.findController = options.findController || null;
13632 this.#altTextManager = options.altTextManager || null;
13633 this.#commentManager = options.commentManager || null;
13634 this.#signatureManager = options.signatureManager || null;
13635 this.#editorUndoBar = options.editorUndoBar || null;
13636 if (this.findController) {
13637 this.findController.onIsPageVisible = (pageNumber) =>
13638 this._getVisiblePages().ids.has(pageNumber);
13639 }
13640 this._scriptingManager = options.scriptingManager || null;
13641 this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
13642 this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
13643 this.#annotationEditorMode = options.annotationEditorMode ?? AnnotationEditorType.NONE;
13644 this.#annotationEditorHighlightColors = options.annotationEditorHighlightColors || null;
13645 this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true;
13646 this.#enableUpdatedAddImage = options.enableUpdatedAddImage === true;
13647 this.#enableNewAltTextWhenAddingImage = options.enableNewAltTextWhenAddingImage === true;
13648 this.imageResourcesPath = options.imageResourcesPath || '';
13649 this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
13650 this.removePageBorders = options.removePageBorders || false;
13651 this.maxCanvasPixels = options.maxCanvasPixels;
13652 this.maxCanvasDim = options.maxCanvasDim;
13653 this.capCanvasAreaFactor = options.capCanvasAreaFactor;
13654 this.enableDetailCanvas = options.enableDetailCanvas ?? true;
13655 this.enableOptimizedPartialRendering = options.enableOptimizedPartialRendering ?? false;
13656 this.l10n = options.l10n;
13657 this.l10n ||= new genericl10n_GenericL10n();
13658 this.#enablePermissions = options.enablePermissions || false;
13659 this.pageColors = options.pageColors || null;
13660 this.#mlManager = options.mlManager || null;
13661 this.#enableHWA = options.enableHWA || false;
13662 this.#supportsPinchToZoom = options.supportsPinchToZoom !== false;
13663 this.#enableAutoLinking = options.enableAutoLinking !== false;
13664 this.#minDurationToUpdateCanvas = options.minDurationToUpdateCanvas ?? 500;
13665 this.defaultRenderingQueue = !options.renderingQueue;
13666 if (this.defaultRenderingQueue) {
13667 this.renderingQueue = new PDFRenderingQueue();
13668 this.renderingQueue.setViewer(this);
13669 } else {
13670 this.renderingQueue = options.renderingQueue;
13671 }
13672 const { abortSignal } = options;
13673 abortSignal?.addEventListener(
13674 'abort',
13675 () => {
13676 this.#resizeObserver.disconnect();
13677 this.#resizeObserver = null;
13678 },
13679 {
13680 once: true,
13681 }
13682 );
13683 this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this), abortSignal);
13684 this.presentationModeState = PresentationModeState.UNKNOWN;
13685 this._resetView();
13686 if (this.removePageBorders) {
13687 this.viewer.classList.add('removePageBorders');
13688 }
13689 this.#updateContainerHeightCss();
13690 this.eventBus._on('thumbnailrendered', ({ pageNumber, pdfPage }) => {
13691 const pageView = this._pages[pageNumber - 1];
13692 if (!this.#buffer.has(pageView)) {
13693 pdfPage?.cleanup();
13694 }
13695 });
13696 if (!options.l10n) {
13697 this.l10n.translate(this.container);
13698 }
13699 }
13700 get printingAllowed() {
13701 return this.#printingAllowed;
13702 }
13703 get pagesCount() {
13704 return this._pages.length;
13705 }
13706 getPageView(index) {
13707 return this._pages[index];
13708 }
13709 getCachedPageViews() {
13710 return new Set(this.#buffer);
13711 }
13712 get pageViewsReady() {
13713 return this._pages.every((pageView) => pageView?.pdfPage);
13714 }
13715 get renderForms() {
13716 return this.#annotationMode === AnnotationMode.ENABLE_FORMS;
13717 }
13718 get enableScripting() {
13719 return !!this._scriptingManager;
13720 }
13721 get currentPageNumber() {
13722 return this._currentPageNumber;
13723 }
13724 set currentPageNumber(val) {
13725 if (!Number.isInteger(val)) {
13726 throw new Error('Invalid page number.');
13727 }
13728 if (!this.pdfDocument) {
13729 return;
13730 }
13731 if (!this._setCurrentPageNumber(val, true)) {
13732 console.error(`currentPageNumber: "${val}" is not a valid page.`);
13733 }
13734 }
13735 _setCurrentPageNumber(val, resetCurrentPageView = false) {
13736 if (this._currentPageNumber === val) {
13737 if (resetCurrentPageView) {
13738 this.#resetCurrentPageView();
13739 }
13740 return true;
13741 }
13742 if (!(0 < val && val <= this.pagesCount)) {
13743 return false;
13744 }
13745 const previous = this._currentPageNumber;
13746 this._currentPageNumber = val;
13747 this.eventBus.dispatch('pagechanging', {
13748 source: this,
13749 pageNumber: val,
13750 pageLabel: this._pageLabels?.[val - 1] ?? null,
13751 previous,
13752 });
13753 if (resetCurrentPageView) {
13754 this.#resetCurrentPageView();
13755 }
13756 return true;
13757 }
13758 get currentPageLabel() {
13759 return this._pageLabels?.[this._currentPageNumber - 1] ?? null;
13760 }
13761 set currentPageLabel(val) {
13762 if (!this.pdfDocument) {
13763 return;
13764 }
13765 let page = val | 0;
13766 if (this._pageLabels) {
13767 const i = this._pageLabels.indexOf(val);
13768 if (i >= 0) {
13769 page = i + 1;
13770 }
13771 }
13772 if (!this._setCurrentPageNumber(page, true)) {
13773 console.error(`currentPageLabel: "${val}" is not a valid page.`);
13774 }
13775 }
13776 get currentScale() {
13777 return this._currentScale !== UNKNOWN_SCALE ? this._currentScale : DEFAULT_SCALE;
13778 }
13779 set currentScale(val) {
13780 if (isNaN(val)) {
13781 throw new Error('Invalid numeric scale.');
13782 }
13783 if (!this.pdfDocument) {
13784 return;
13785 }
13786 this.#setScale(val, {
13787 noScroll: false,
13788 });
13789 }
13790 get currentScaleValue() {
13791 return this._currentScaleValue;
13792 }
13793 set currentScaleValue(val) {
13794 if (!this.pdfDocument) {
13795 return;
13796 }
13797 this.#setScale(val, {
13798 noScroll: false,
13799 });
13800 }
13801 get pagesRotation() {
13802 return this._pagesRotation;
13803 }
13804 set pagesRotation(rotation) {
13805 if (!isValidRotation(rotation)) {
13806 throw new Error('Invalid pages rotation angle.');
13807 }
13808 if (!this.pdfDocument) {
13809 return;
13810 }
13811 rotation %= 360;
13812 if (rotation < 0) {
13813 rotation += 360;
13814 }
13815 if (this._pagesRotation === rotation) {
13816 return;
13817 }
13818 this._pagesRotation = rotation;
13819 const pageNumber = this._currentPageNumber;
13820 this.refresh(true, {
13821 rotation,
13822 });
13823 if (this._currentScaleValue) {
13824 this.#setScale(this._currentScaleValue, {
13825 noScroll: true,
13826 });
13827 }
13828 this.eventBus.dispatch('rotationchanging', {
13829 source: this,
13830 pagesRotation: rotation,
13831 pageNumber,
13832 });
13833 if (this.defaultRenderingQueue) {
13834 this.update();
13835 }
13836 }
13837 get firstPagePromise() {
13838 return this.pdfDocument ? this._firstPageCapability.promise : null;
13839 }
13840 get onePageRendered() {
13841 return this.pdfDocument ? this._onePageRenderedCapability.promise : null;
13842 }
13843 get pagesPromise() {
13844 return this.pdfDocument ? this._pagesCapability.promise : null;
13845 }
13846 get _layerProperties() {
13847 const self = this;
13848 return shadow(this, '_layerProperties', {
13849 get annotationEditorUIManager() {
13850 return self.#annotationEditorUIManager;
13851 },
13852 get annotationStorage() {
13853 return self.pdfDocument?.annotationStorage;
13854 },
13855 get downloadManager() {
13856 return self.downloadManager;
13857 },
13858 get enableComment() {
13859 return !!self.#commentManager;
13860 },
13861 get enableScripting() {
13862 return !!self._scriptingManager;
13863 },
13864 get fieldObjectsPromise() {
13865 return self.pdfDocument?.getFieldObjects();
13866 },
13867 get findController() {
13868 return self.findController;
13869 },
13870 get hasJSActionsPromise() {
13871 return self.pdfDocument?.hasJSActions();
13872 },
13873 get linkService() {
13874 return self.linkService;
13875 },
13876 });
13877 }
13878 #initializePermissions(permissions) {
13879 const params = {
13880 annotationEditorMode: this.#annotationEditorMode,
13881 annotationMode: this.#annotationMode,
13882 textLayerMode: this.#textLayerMode,
13883 };
13884 if (!permissions) {
13885 this.#printingAllowed = true;
13886 this.eventBus.dispatch('printingallowed', {
13887 source: this,
13888 isAllowed: this.#printingAllowed,
13889 });
13890 return params;
13891 }
13892 this.#printingAllowed =
13893 permissions.includes(PermissionFlag.PRINT_HIGH_QUALITY) ||
13894 permissions.includes(PermissionFlag.PRINT);
13895 this.eventBus.dispatch('printingallowed', {
13896 source: this,
13897 isAllowed: this.#printingAllowed,
13898 });
13899 if (
13900 !permissions.includes(PermissionFlag.COPY) &&
13901 this.#textLayerMode === TextLayerMode.ENABLE
13902 ) {
13903 params.textLayerMode = TextLayerMode.ENABLE_PERMISSIONS;
13904 }
13905 if (!permissions.includes(PermissionFlag.MODIFY_CONTENTS)) {
13906 params.annotationEditorMode = AnnotationEditorType.DISABLE;
13907 }
13908 if (
13909 !permissions.includes(PermissionFlag.MODIFY_ANNOTATIONS) &&
13910 !permissions.includes(PermissionFlag.FILL_INTERACTIVE_FORMS) &&
13911 this.#annotationMode === AnnotationMode.ENABLE_FORMS
13912 ) {
13913 params.annotationMode = AnnotationMode.ENABLE;
13914 }
13915 return params;
13916 }
13917 async #onePageRenderedOrForceFetch(signal) {
13918 if (
13919 document.visibilityState === 'hidden' ||
13920 !this.container.offsetParent ||
13921 this._getVisiblePages().views.length === 0
13922 ) {
13923 return;
13924 }
13925 const hiddenCapability = Promise.withResolvers(),
13926 ac = new AbortController();
13927 document.addEventListener(
13928 'visibilitychange',
13929 () => {
13930 if (document.visibilityState === 'hidden') {
13931 hiddenCapability.resolve();
13932 }
13933 },
13934 {
13935 signal: AbortSignal.any([signal, ac.signal]),
13936 }
13937 );
13938 await Promise.race([this._onePageRenderedCapability.promise, hiddenCapability.promise]);
13939 ac.abort();
13940 }
13941 async getAllText() {
13942 const texts = [];
13943 const buffer = [];
13944 for (
13945 let pageNum = 1, pagesCount = this.pdfDocument.numPages;
13946 pageNum <= pagesCount;
13947 ++pageNum
13948 ) {
13949 if (this.#interruptCopyCondition) {
13950 return null;
13951 }
13952 buffer.length = 0;
13953 const page = await this.pdfDocument.getPage(pageNum);
13954 const { items } = await page.getTextContent();
13955 for (const item of items) {
13956 if (item.str) {
13957 buffer.push(item.str);
13958 }
13959 if (item.hasEOL) {
13960 buffer.push('\n');
13961 }
13962 }
13963 texts.push(removeNullCharacters(buffer.join('')));
13964 }
13965 return texts.join('\n');
13966 }
13967 #copyCallback(textLayerMode, event) {
13968 const selection = document.getSelection();
13969 const { focusNode, anchorNode } = selection;
13970 if (anchorNode && focusNode && selection.containsNode(this.#hiddenCopyElement)) {
13971 if (this.#getAllTextInProgress || textLayerMode === TextLayerMode.ENABLE_PERMISSIONS) {
13972 stopEvent(event);
13973 return;
13974 }
13975 this.#getAllTextInProgress = true;
13976 const { classList } = this.viewer;
13977 classList.add('copyAll');
13978 const ac = new AbortController();
13979 window.addEventListener(
13980 'keydown',
13981 (ev) => (this.#interruptCopyCondition = ev.key === 'Escape'),
13982 {
13983 signal: ac.signal,
13984 }
13985 );
13986 this.getAllText()
13987 .then(async (text) => {
13988 if (text !== null) {
13989 await navigator.clipboard.writeText(text);
13990 }
13991 })
13992 .catch((reason) => {
13993 console.warn(`Something goes wrong when extracting the text: ${reason.message}`);
13994 })
13995 .finally(() => {
13996 this.#getAllTextInProgress = false;
13997 this.#interruptCopyCondition = false;
13998 ac.abort();
13999 classList.remove('copyAll');
14000 });
14001 stopEvent(event);
14002 }
14003 }
14004 setDocument(pdfDocument) {
14005 if (this.pdfDocument) {
14006 this.eventBus.dispatch('pagesdestroy', {
14007 source: this,
14008 });
14009 this._cancelRendering();
14010 this._resetView();
14011 this.findController?.setDocument(null);
14012 this._scriptingManager?.setDocument(null);
14013 this.#annotationEditorUIManager?.destroy();
14014 this.#annotationEditorUIManager = null;
14015 this.#annotationEditorMode = AnnotationEditorType.NONE;
14016 this.#printingAllowed = true;
14017 this.#pagesMapper.pagesNumber = 0;
14018 }
14019 this.pdfDocument = pdfDocument;
14020 if (!pdfDocument) {
14021 return;
14022 }
14023 const pagesCount = pdfDocument.numPages;
14024 const firstPagePromise = pdfDocument.getPage(1);
14025 const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
14026 intent: 'display',
14027 });
14028 const permissionsPromise = this.#enablePermissions
14029 ? pdfDocument.getPermissions()
14030 : Promise.resolve();
14031 const { eventBus, pageColors, viewer } = this;
14032 this.#eventAbortController = new AbortController();
14033 const { signal } = this.#eventAbortController;
14034 if (pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) {
14035 console.warn(
14036 'Forcing PAGE-scrolling for performance reasons, given the length of the document.'
14037 );
14038 const mode = (this._scrollMode = ScrollMode.PAGE);
14039 eventBus.dispatch('scrollmodechanged', {
14040 source: this,
14041 mode,
14042 });
14043 }
14044 this._pagesCapability.promise.then(
14045 () => {
14046 eventBus.dispatch('pagesloaded', {
14047 source: this,
14048 pagesCount,
14049 });
14050 },
14051 () => {}
14052 );
14053 const onBeforeDraw = (evt) => {
14054 const pageView = this._pages[evt.pageNumber - 1];
14055 if (!pageView) {
14056 return;
14057 }
14058 this.#buffer.push(pageView);
14059 };
14060 eventBus._on('pagerender', onBeforeDraw, {
14061 signal,
14062 });
14063 const onAfterDraw = (evt) => {
14064 if (evt.cssTransform || evt.isDetailView) {
14065 return;
14066 }
14067 this._onePageRenderedCapability.resolve({
14068 timestamp: evt.timestamp,
14069 });
14070 eventBus._off('pagerendered', onAfterDraw);
14071 };
14072 eventBus._on('pagerendered', onAfterDraw, {
14073 signal,
14074 });
14075 Promise.all([firstPagePromise, permissionsPromise])
14076 .then(([firstPdfPage, permissions]) => {
14077 if (pdfDocument !== this.pdfDocument) {
14078 return;
14079 }
14080 this._firstPageCapability.resolve(firstPdfPage);
14081 this._optionalContentConfigPromise = optionalContentConfigPromise;
14082 const { annotationEditorMode, annotationMode, textLayerMode } =
14083 this.#initializePermissions(permissions);
14084 if (textLayerMode !== TextLayerMode.DISABLE) {
14085 const element = (this.#hiddenCopyElement = document.createElement('div'));
14086 element.id = 'hiddenCopyElement';
14087 viewer.before(element);
14088 }
14089 if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
14090 const mode = annotationEditorMode;
14091 if (pdfDocument.isPureXfa) {
14092 console.warn('Warning: XFA-editing is not implemented.');
14093 } else if (isValidAnnotationEditorMode(mode)) {
14094 this.#annotationEditorUIManager = new AnnotationEditorUIManager(
14095 this.container,
14096 viewer,
14097 this.#viewerAlert,
14098 this.#altTextManager,
14099 this.#commentManager,
14100 this.#signatureManager,
14101 eventBus,
14102 pdfDocument,
14103 pageColors,
14104 this.#annotationEditorHighlightColors,
14105 this.#enableHighlightFloatingButton,
14106 this.#enableUpdatedAddImage,
14107 this.#enableNewAltTextWhenAddingImage,
14108 this.#mlManager,
14109 this.#editorUndoBar,
14110 this.#supportsPinchToZoom
14111 );
14112 eventBus.dispatch('annotationeditoruimanager', {
14113 source: this,
14114 uiManager: this.#annotationEditorUIManager,
14115 });
14116 if (mode !== AnnotationEditorType.NONE) {
14117 this.#preloadEditingData(mode);
14118 this.#annotationEditorUIManager.updateMode(mode);
14119 }
14120 } else {
14121 console.error(`Invalid AnnotationEditor mode: ${mode}`);
14122 }
14123 }
14124 const viewerElement = this._scrollMode === ScrollMode.PAGE ? null : viewer;
14125 const scale = this.currentScale;
14126 const viewport = firstPdfPage.getViewport({
14127 scale: scale * PixelsPerInch.PDF_TO_CSS_UNITS,
14128 });
14129 viewer.style.setProperty('--scale-factor', viewport.scale);
14130 if (pageColors?.background) {
14131 viewer.style.setProperty('--page-bg-color', pageColors.background);
14132 }
14133 if (pageColors?.foreground === 'CanvasText' || pageColors?.background === 'Canvas') {
14134 viewer.style.setProperty(
14135 '--hcm-highlight-filter',
14136 pdfDocument.filterFactory.addHighlightHCMFilter(
14137 'highlight',
14138 'CanvasText',
14139 'Canvas',
14140 'HighlightText',
14141 'Highlight'
14142 )
14143 );
14144 viewer.style.setProperty(
14145 '--hcm-highlight-selected-filter',
14146 pdfDocument.filterFactory.addHighlightHCMFilter(
14147 'highlight_selected',
14148 'CanvasText',
14149 'Canvas',
14150 'HighlightText',
14151 'ButtonText'
14152 )
14153 );
14154 }
14155 for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
14156 const pageView = new PDFPageView({
14157 container: viewerElement,
14158 eventBus,
14159 id: pageNum,
14160 scale,
14161 defaultViewport: viewport.clone(),
14162 optionalContentConfigPromise,
14163 renderingQueue: this.renderingQueue,
14164 textLayerMode,
14165 annotationMode,
14166 imageResourcesPath: this.imageResourcesPath,
14167 maxCanvasPixels: this.maxCanvasPixels,
14168 maxCanvasDim: this.maxCanvasDim,
14169 capCanvasAreaFactor: this.capCanvasAreaFactor,
14170 enableDetailCanvas: this.enableDetailCanvas,
14171 enableOptimizedPartialRendering: this.enableOptimizedPartialRendering,
14172 pageColors,
14173 l10n: this.l10n,
14174 layerProperties: this._layerProperties,
14175 enableHWA: this.#enableHWA,
14176 enableAutoLinking: this.#enableAutoLinking,
14177 minDurationToUpdateCanvas: this.#minDurationToUpdateCanvas,
14178 commentManager: this.#commentManager,
14179 });
14180 this._pages.push(pageView);
14181 }
14182 this._pages[0]?.setPdfPage(firstPdfPage);
14183 if (this._scrollMode === ScrollMode.PAGE) {
14184 this.#ensurePageViewVisible();
14185 } else if (this._spreadMode !== SpreadMode.NONE) {
14186 this._updateSpreadMode();
14187 }
14188 eventBus._on(
14189 'annotationeditorlayerrendered',
14190 (evt) => {
14191 if (this.#annotationEditorUIManager) {
14192 eventBus.dispatch('annotationeditormodechanged', {
14193 source: this,
14194 mode: this.#annotationEditorMode,
14195 });
14196 }
14197 },
14198 {
14199 once: true,
14200 signal,
14201 }
14202 );
14203 this.#onePageRenderedOrForceFetch(signal).then(async () => {
14204 if (pdfDocument !== this.pdfDocument) {
14205 return;
14206 }
14207 this.findController?.setDocument(pdfDocument);
14208 this._scriptingManager?.setDocument(pdfDocument);
14209 if (this.#hiddenCopyElement) {
14210 document.addEventListener('copy', this.#copyCallback.bind(this, textLayerMode), {
14211 signal,
14212 });
14213 }
14214 if (
14215 pdfDocument.loadingParams.disableAutoFetch ||
14216 pagesCount > PagesCountLimit.FORCE_LAZY_PAGE_INIT
14217 ) {
14218 this._pagesCapability.resolve();
14219 return;
14220 }
14221 let getPagesLeft = pagesCount - 1;
14222 if (getPagesLeft <= 0) {
14223 this._pagesCapability.resolve();
14224 return;
14225 }
14226 for (let pageNum = 2; pageNum <= pagesCount; ++pageNum) {
14227 const promise = pdfDocument.getPage(pageNum).then(
14228 (pdfPage) => {
14229 const pageView = this._pages[pageNum - 1];
14230 if (!pageView.pdfPage) {
14231 pageView.setPdfPage(pdfPage);
14232 }
14233 if (--getPagesLeft === 0) {
14234 this._pagesCapability.resolve();
14235 }
14236 },
14237 (reason) => {
14238 console.error(`Unable to get page ${pageNum} to initialize viewer`, reason);
14239 if (--getPagesLeft === 0) {
14240 this._pagesCapability.resolve();
14241 }
14242 }
14243 );
14244 if (pageNum % PagesCountLimit.PAUSE_EAGER_PAGE_INIT === 0) {
14245 await promise;
14246 }
14247 }
14248 });
14249 eventBus.dispatch('pagesinit', {
14250 source: this,
14251 });
14252 pdfDocument.getMetadata().then(({ info }) => {
14253 if (pdfDocument !== this.pdfDocument) {
14254 return;
14255 }
14256 if (info.Language) {
14257 viewer.lang = info.Language;
14258 }
14259 });
14260 if (this.defaultRenderingQueue) {
14261 this.update();
14262 }
14263 })
14264 .catch((reason) => {
14265 console.error('Unable to initialize viewer', reason);
14266 this._pagesCapability.reject(reason);
14267 });
14268 }
14269 async onBeforePagesEdited({ pagesMapper }) {
14270 await this._pagesCapability.promise;
14271 this._currentPageId = pagesMapper.getPageId(this._currentPageNumber);
14272 }
14273 onPagesEdited({ pagesMapper }) {
14274 this._currentPageNumber = pagesMapper.getPageNumber(this._currentPageId);
14275 const prevPages = this._pages;
14276 const newPages = (this._pages = []);
14277 for (let i = 0, ii = pagesMapper.pagesNumber; i < ii; i++) {
14278 const prevPageNumber = pagesMapper.getPrevPageNumber(i + 1) - 1;
14279 if (prevPageNumber === -1) {
14280 continue;
14281 }
14282 const page = prevPages[prevPageNumber];
14283 newPages[i] = page;
14284 page.updatePageNumber(i + 1);
14285 }
14286 const viewerElement = this._scrollMode === ScrollMode.PAGE ? null : this.viewer;
14287 if (viewerElement) {
14288 viewerElement.replaceChildren();
14289 const fragment = document.createDocumentFragment();
14290 for (let i = 0, ii = pagesMapper.pagesNumber; i < ii; i++) {
14291 const { div } = newPages[i];
14292 div.setAttribute('data-page-number', i + 1);
14293 fragment.append(div);
14294 }
14295 viewerElement.append(fragment);
14296 }
14297 setTimeout(() => {
14298 this.forceRendering();
14299 });
14300 }
14301 setPageLabels(labels) {
14302 if (!this.pdfDocument) {
14303 return;
14304 }
14305 if (!labels) {
14306 this._pageLabels = null;
14307 } else if (!(Array.isArray(labels) && this.pdfDocument.numPages === labels.length)) {
14308 this._pageLabels = null;
14309 console.error(`setPageLabels: Invalid page labels.`);
14310 } else {
14311 this._pageLabels = labels;
14312 }
14313 for (let i = 0, ii = this._pages.length; i < ii; i++) {
14314 this._pages[i].setPageLabel(this._pageLabels?.[i] ?? null);
14315 }
14316 }
14317 _resetView() {
14318 this._pages = [];
14319 this._currentPageNumber = 1;
14320 this._currentScale = UNKNOWN_SCALE;
14321 this._currentScaleValue = null;
14322 this._pageLabels = null;
14323 this.#buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
14324 this._location = null;
14325 this._pagesRotation = 0;
14326 this._optionalContentConfigPromise = null;
14327 this._firstPageCapability = Promise.withResolvers();
14328 this._onePageRenderedCapability = Promise.withResolvers();
14329 this._pagesCapability = Promise.withResolvers();
14330 this._scrollMode = ScrollMode.VERTICAL;
14331 this._previousScrollMode = ScrollMode.UNKNOWN;
14332 this._spreadMode = SpreadMode.NONE;
14333 this.#scrollModePageState = {
14334 previousPageNumber: 1,
14335 scrollDown: true,
14336 pages: [],
14337 };
14338 this.#eventAbortController?.abort();
14339 this.#eventAbortController = null;
14340 this.viewer.textContent = '';
14341 this._updateScrollMode();
14342 this.viewer.removeAttribute('lang');
14343 this.#hiddenCopyElement?.remove();
14344 this.#hiddenCopyElement = null;
14345 this.#cleanupTimeouts();
14346 this.#cleanupSwitchAnnotationEditorMode();
14347 }
14348 #ensurePageViewVisible() {
14349 if (this._scrollMode !== ScrollMode.PAGE) {
14350 throw new Error('#ensurePageViewVisible: Invalid scrollMode value.');
14351 }
14352 const pageNumber = this._currentPageNumber,
14353 state = this.#scrollModePageState,
14354 viewer = this.viewer;
14355 viewer.textContent = '';
14356 state.pages.length = 0;
14357 if (this._spreadMode === SpreadMode.NONE && !this.isInPresentationMode) {
14358 const pageView = this._pages[pageNumber - 1];
14359 viewer.append(pageView.div);
14360 state.pages.push(pageView);
14361 } else {
14362 const pageIndexSet = new Set(),
14363 parity = this._spreadMode - 1;
14364 if (parity === -1) {
14365 pageIndexSet.add(pageNumber - 1);
14366 } else if (pageNumber % 2 !== parity) {
14367 pageIndexSet.add(pageNumber - 1);
14368 pageIndexSet.add(pageNumber);
14369 } else {
14370 pageIndexSet.add(pageNumber - 2);
14371 pageIndexSet.add(pageNumber - 1);
14372 }
14373 const spread = document.createElement('div');
14374 spread.className = 'spread';
14375 if (this.isInPresentationMode) {
14376 const dummyPage = document.createElement('div');
14377 dummyPage.className = 'dummyPage';
14378 spread.append(dummyPage);
14379 }
14380 for (const i of pageIndexSet) {
14381 const pageView = this._pages[i];
14382 if (!pageView) {
14383 continue;
14384 }
14385 spread.append(pageView.div);
14386 state.pages.push(pageView);
14387 }
14388 viewer.append(spread);
14389 }
14390 state.scrollDown = pageNumber >= state.previousPageNumber;
14391 state.previousPageNumber = pageNumber;
14392 }
14393 _scrollUpdate() {
14394 if (this.pagesCount === 0) {
14395 return;
14396 }
14397 if (this.#scrollTimeoutId) {
14398 clearTimeout(this.#scrollTimeoutId);
14399 }
14400 this.#scrollTimeoutId = setTimeout(() => {
14401 this.#scrollTimeoutId = null;
14402 this.update();
14403 }, 100);
14404 this.update();
14405 }
14406 #scrollIntoView(pageView, pageSpot = null) {
14407 const { div, id } = pageView;
14408 if (this._currentPageNumber !== id) {
14409 this._setCurrentPageNumber(id);
14410 }
14411 if (this._scrollMode === ScrollMode.PAGE) {
14412 this.#ensurePageViewVisible();
14413 this.update();
14414 }
14415 if (!pageSpot && !this.isInPresentationMode) {
14416 const left = div.offsetLeft + div.clientLeft,
14417 right = left + div.clientWidth;
14418 const { scrollLeft, clientWidth } = this.container;
14419 if (
14420 this._scrollMode === ScrollMode.HORIZONTAL ||
14421 left < scrollLeft ||
14422 right > scrollLeft + clientWidth
14423 ) {
14424 pageSpot = {
14425 left: 0,
14426 top: 0,
14427 };
14428 }
14429 }
14430 scrollIntoView(div, pageSpot);
14431 if (!this._currentScaleValue && this._location) {
14432 this._location = null;
14433 }
14434 }
14435 #isSameScale(newScale) {
14436 return newScale === this._currentScale || Math.abs(newScale - this._currentScale) < 1e-15;
14437 }
14438 #setScaleUpdatePages(
14439 newScale,
14440 newValue,
14441 { noScroll = false, preset = false, drawingDelay = -1, origin = null }
14442 ) {
14443 this._currentScaleValue = newValue.toString();
14444 if (this.#isSameScale(newScale)) {
14445 if (preset) {
14446 this.eventBus.dispatch('scalechanging', {
14447 source: this,
14448 scale: newScale,
14449 presetValue: newValue,
14450 });
14451 }
14452 return;
14453 }
14454 this.viewer.style.setProperty('--scale-factor', newScale * PixelsPerInch.PDF_TO_CSS_UNITS);
14455 const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000;
14456 this.refresh(true, {
14457 scale: newScale,
14458 drawingDelay: postponeDrawing ? drawingDelay : -1,
14459 });
14460 if (postponeDrawing) {
14461 this.#scaleTimeoutId = setTimeout(() => {
14462 this.#scaleTimeoutId = null;
14463 this.refresh();
14464 }, drawingDelay);
14465 }
14466 const previousScale = this._currentScale;
14467 this._currentScale = newScale;
14468 if (!noScroll) {
14469 let page = this._currentPageNumber,
14470 dest;
14471 if (this._location && !(this.isInPresentationMode || this.isChangingPresentationMode)) {
14472 page = this._location.pageNumber;
14473 dest = [
14474 null,
14475 {
14476 name: 'XYZ',
14477 },
14478 this._location.left,
14479 this._location.top,
14480 null,
14481 ];
14482 }
14483 this.scrollPageIntoView({
14484 pageNumber: page,
14485 destArray: dest,
14486 allowNegativeOffset: true,
14487 });
14488 if (Array.isArray(origin)) {
14489 const scaleDiff = newScale / previousScale - 1;
14490 const [top, left] = this.containerTopLeft;
14491 this.container.scrollLeft += (origin[0] - left) * scaleDiff;
14492 this.container.scrollTop += (origin[1] - top) * scaleDiff;
14493 }
14494 }
14495 this.eventBus.dispatch('scalechanging', {
14496 source: this,
14497 scale: newScale,
14498 presetValue: preset ? newValue : undefined,
14499 });
14500 if (this.defaultRenderingQueue) {
14501 this.update();
14502 }
14503 }
14504 get #pageWidthScaleFactor() {
14505 if (this._spreadMode !== SpreadMode.NONE && this._scrollMode !== ScrollMode.HORIZONTAL) {
14506 return 2;
14507 }
14508 return 1;
14509 }
14510 #setScale(value, options) {
14511 let scale = parseFloat(value);
14512 if (scale > 0) {
14513 options.preset = false;
14514 this.#setScaleUpdatePages(scale, value, options);
14515 } else {
14516 const currentPage = this._pages[this._currentPageNumber - 1];
14517 if (!currentPage) {
14518 return;
14519 }
14520 let hPadding = SCROLLBAR_PADDING,
14521 vPadding = VERTICAL_PADDING;
14522 if (this.isInPresentationMode) {
14523 hPadding = vPadding = 4;
14524 if (this._spreadMode !== SpreadMode.NONE) {
14525 hPadding *= 2;
14526 }
14527 } else if (this.removePageBorders) {
14528 hPadding = vPadding = 0;
14529 } else if (this._scrollMode === ScrollMode.HORIZONTAL) {
14530 [hPadding, vPadding] = [vPadding, hPadding];
14531 }
14532 const pageWidthScale =
14533 (((this.container.clientWidth - hPadding) / currentPage.width) * currentPage.scale) /
14534 this.#pageWidthScaleFactor;
14535 const pageHeightScale =
14536 ((this.container.clientHeight - vPadding) / currentPage.height) * currentPage.scale;
14537 switch (value) {
14538 case 'page-actual':
14539 scale = 1;
14540 break;
14541 case 'page-width':
14542 scale = pageWidthScale;
14543 break;
14544 case 'page-height':
14545 scale = pageHeightScale;
14546 break;
14547 case 'page-fit':
14548 scale = Math.min(pageWidthScale, pageHeightScale);
14549 break;
14550 case 'auto':
14551 const horizontalScale = isPortraitOrientation(currentPage)
14552 ? pageWidthScale
14553 : Math.min(pageHeightScale, pageWidthScale);
14554 scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
14555 break;
14556 default:
14557 console.error(`#setScale: "${value}" is an unknown zoom value.`);
14558 return;
14559 }
14560 options.preset = true;
14561 this.#setScaleUpdatePages(scale, value, options);
14562 }
14563 }
14564 #resetCurrentPageView() {
14565 const pageView = this._pages[this._currentPageNumber - 1];
14566 if (this.isInPresentationMode) {
14567 this.#setScale(this._currentScaleValue, {
14568 noScroll: true,
14569 });
14570 }
14571 this.#scrollIntoView(pageView);
14572 }
14573 pageLabelToPageNumber(label) {
14574 if (!this._pageLabels) {
14575 return null;
14576 }
14577 const i = this._pageLabels.indexOf(label);
14578 if (i < 0) {
14579 return null;
14580 }
14581 return i + 1;
14582 }
14583 scrollPageIntoView({
14584 pageNumber,
14585 destArray = null,
14586 allowNegativeOffset = false,
14587 ignoreDestinationZoom = false,
14588 center = null,
14589 }) {
14590 if (!this.pdfDocument) {
14591 return;
14592 }
14593 const pageView = Number.isInteger(pageNumber) && this._pages[pageNumber - 1];
14594 if (!pageView) {
14595 console.error(`scrollPageIntoView: "${pageNumber}" is not a valid pageNumber parameter.`);
14596 return;
14597 }
14598 if (this.isInPresentationMode || !destArray) {
14599 this._setCurrentPageNumber(pageNumber, true);
14600 return;
14601 }
14602 let x = 0,
14603 y = 0;
14604 let width = 0,
14605 height = 0,
14606 widthScale,
14607 heightScale;
14608 const changeOrientation = pageView.rotation % 180 !== 0;
14609 const pageWidth =
14610 (changeOrientation ? pageView.height : pageView.width) /
14611 pageView.scale /
14612 PixelsPerInch.PDF_TO_CSS_UNITS;
14613 const pageHeight =
14614 (changeOrientation ? pageView.width : pageView.height) /
14615 pageView.scale /
14616 PixelsPerInch.PDF_TO_CSS_UNITS;
14617 let scale = 0;
14618 switch (destArray[1].name) {
14619 case 'XYZ':
14620 x = destArray[2];
14621 y = destArray[3];
14622 scale = destArray[4];
14623 x = x !== null ? x : 0;
14624 y = y !== null ? y : pageHeight;
14625 break;
14626 case 'Fit':
14627 case 'FitB':
14628 scale = 'page-fit';
14629 break;
14630 case 'FitH':
14631 case 'FitBH':
14632 y = destArray[2];
14633 scale = 'page-width';
14634 if (y === null && this._location) {
14635 x = this._location.left;
14636 y = this._location.top;
14637 } else if (typeof y !== 'number' || y < 0) {
14638 y = pageHeight;
14639 }
14640 break;
14641 case 'FitV':
14642 case 'FitBV':
14643 x = destArray[2];
14644 width = pageWidth;
14645 height = pageHeight;
14646 scale = 'page-height';
14647 break;
14648 case 'FitR':
14649 x = destArray[2];
14650 y = destArray[3];
14651 width = destArray[4] - x;
14652 height = destArray[5] - y;
14653 let hPadding = SCROLLBAR_PADDING,
14654 vPadding = VERTICAL_PADDING;
14655 if (this.removePageBorders) {
14656 hPadding = vPadding = 0;
14657 }
14658 widthScale =
14659 (this.container.clientWidth - hPadding) / width / PixelsPerInch.PDF_TO_CSS_UNITS;
14660 heightScale =
14661 (this.container.clientHeight - vPadding) / height / PixelsPerInch.PDF_TO_CSS_UNITS;
14662 scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
14663 break;
14664 default:
14665 console.error(
14666 `scrollPageIntoView: "${destArray[1].name}" is not a valid destination type.`
14667 );
14668 return;
14669 }
14670 if (!ignoreDestinationZoom) {
14671 if (scale && scale !== this._currentScale) {
14672 this.currentScaleValue = scale;
14673 } else if (this._currentScale === UNKNOWN_SCALE) {
14674 this.currentScaleValue = DEFAULT_SCALE_VALUE;
14675 }
14676 }
14677 if (scale === 'page-fit' && !destArray[4]) {
14678 this.#scrollIntoView(pageView);
14679 return;
14680 }
14681 const boundingRect = [
14682 pageView.viewport.convertToViewportPoint(x, y),
14683 pageView.viewport.convertToViewportPoint(x + width, y + height),
14684 ];
14685 let left = Math.min(boundingRect[0][0], boundingRect[1][0]);
14686 let top = Math.min(boundingRect[0][1], boundingRect[1][1]);
14687 if (center) {
14688 if (center === 'both' || center === 'vertical') {
14689 top -=
14690 (this.container.clientHeight - Math.abs(boundingRect[1][1] - boundingRect[0][1])) / 2;
14691 }
14692 if (center === 'both' || center === 'horizontal') {
14693 left -=
14694 (this.container.clientWidth - Math.abs(boundingRect[1][0] - boundingRect[0][0])) / 2;
14695 }
14696 } else if (!allowNegativeOffset) {
14697 left = Math.max(left, 0);
14698 top = Math.max(top, 0);
14699 }
14700 this.#scrollIntoView(pageView, {
14701 left,
14702 top,
14703 });
14704 }
14705 _updateLocation(firstPage) {
14706 const currentScale = this._currentScale;
14707 const currentScaleValue = this._currentScaleValue;
14708 const normalizedScaleValue =
14709 parseFloat(currentScaleValue) === currentScale
14710 ? Math.round(currentScale * 10000) / 100
14711 : currentScaleValue;
14712 const pageNumber = firstPage.id;
14713 const currentPageView = this._pages[pageNumber - 1];
14714 const container = this.container;
14715 const topLeft = currentPageView.getPagePoint(
14716 container.scrollLeft - firstPage.x,
14717 container.scrollTop - firstPage.y
14718 );
14719 const intLeft = Math.round(topLeft[0]);
14720 const intTop = Math.round(topLeft[1]);
14721 let pdfOpenParams = `#page=${pageNumber}`;
14722 if (!this.isInPresentationMode) {
14723 pdfOpenParams += `&zoom=${normalizedScaleValue},${intLeft},${intTop}`;
14724 }
14725 this._location = {
14726 pageNumber,
14727 scale: normalizedScaleValue,
14728 top: intTop,
14729 left: intLeft,
14730 rotation: this._pagesRotation,
14731 pdfOpenParams,
14732 };
14733 }
14734 update() {
14735 const visible = this._getVisiblePages();
14736 const visiblePages = visible.views,
14737 numVisiblePages = visiblePages.length;
14738 if (numVisiblePages === 0) {
14739 return;
14740 }
14741 const newCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1);
14742 this.#buffer.resize(newCacheSize, visible.ids);
14743 for (const { view, visibleArea } of visiblePages) {
14744 view.updateVisibleArea(visibleArea);
14745 }
14746 for (const view of this.#buffer) {
14747 if (!visible.ids.has(view.id)) {
14748 view.updateVisibleArea(null);
14749 }
14750 }
14751 this.renderingQueue.renderHighestPriority(visible);
14752 const isSimpleLayout =
14753 this._spreadMode === SpreadMode.NONE &&
14754 (this._scrollMode === ScrollMode.PAGE || this._scrollMode === ScrollMode.VERTICAL);
14755 const currentPageNumber = this._currentPageNumber;
14756 let stillFullyVisible = false;
14757 for (const page of visiblePages) {
14758 if (page.percent < 100) {
14759 break;
14760 }
14761 if (page.id === currentPageNumber && isSimpleLayout) {
14762 stillFullyVisible = true;
14763 break;
14764 }
14765 }
14766 this._setCurrentPageNumber(stillFullyVisible ? this._currentPageNumber : visiblePages[0].id);
14767 this._updateLocation(visible.first);
14768 this.eventBus.dispatch('updateviewarea', {
14769 source: this,
14770 location: this._location,
14771 });
14772 }
14773 #switchToEditAnnotationMode() {
14774 const visible = this._getVisiblePages();
14775 const pagesToRefresh = [];
14776 const { ids, views } = visible;
14777 for (const page of views) {
14778 const { view } = page;
14779 if (!view.hasEditableAnnotations()) {
14780 ids.delete(view.id);
14781 continue;
14782 }
14783 pagesToRefresh.push(page);
14784 }
14785 if (pagesToRefresh.length === 0) {
14786 return null;
14787 }
14788 this.renderingQueue.renderHighestPriority({
14789 first: pagesToRefresh[0],
14790 last: pagesToRefresh.at(-1),
14791 views: pagesToRefresh,
14792 ids,
14793 });
14794 return ids;
14795 }
14796 containsElement(element) {
14797 return this.container.contains(element);
14798 }
14799 focus() {
14800 this.container.focus();
14801 }
14802 get _isContainerRtl() {
14803 return getComputedStyle(this.container).direction === 'rtl';
14804 }
14805 get isInPresentationMode() {
14806 return this.presentationModeState === PresentationModeState.FULLSCREEN;
14807 }
14808 get isChangingPresentationMode() {
14809 return this.presentationModeState === PresentationModeState.CHANGING;
14810 }
14811 get isHorizontalScrollbarEnabled() {
14812 return this.isInPresentationMode
14813 ? false
14814 : this.container.scrollWidth > this.container.clientWidth;
14815 }
14816 get isVerticalScrollbarEnabled() {
14817 return this.isInPresentationMode
14818 ? false
14819 : this.container.scrollHeight > this.container.clientHeight;
14820 }
14821 _getVisiblePages() {
14822 const views =
14823 this._scrollMode === ScrollMode.PAGE ? this.#scrollModePageState.pages : this._pages,
14824 horizontal = this._scrollMode === ScrollMode.HORIZONTAL,
14825 rtl = horizontal && this._isContainerRtl;
14826 return getVisibleElements({
14827 scrollEl: this.container,
14828 views,
14829 sortByVisibility: true,
14830 horizontal,
14831 rtl,
14832 });
14833 }
14834 cleanup() {
14835 for (const pageView of this._pages) {
14836 if (pageView.renderingState !== RenderingStates.FINISHED) {
14837 pageView.reset();
14838 }
14839 }
14840 }
14841 _cancelRendering() {
14842 for (const pageView of this._pages) {
14843 pageView.cancelRendering();
14844 }
14845 }
14846 async #ensurePdfPageLoaded(pageView) {
14847 if (pageView.pdfPage) {
14848 return pageView.pdfPage;
14849 }
14850 try {
14851 const pdfPage = await this.pdfDocument.getPage(pageView.id);
14852 if (!pageView.pdfPage) {
14853 pageView.setPdfPage(pdfPage);
14854 }
14855 return pdfPage;
14856 } catch (reason) {
14857 console.error('Unable to get page for page view', reason);
14858 return null;
14859 }
14860 }
14861 #getScrollAhead(visible) {
14862 if (visible.first?.id === 1) {
14863 return true;
14864 } else if (visible.last?.id === this.pagesCount) {
14865 return false;
14866 }
14867 switch (this._scrollMode) {
14868 case ScrollMode.PAGE:
14869 return this.#scrollModePageState.scrollDown;
14870 case ScrollMode.HORIZONTAL:
14871 return this.scroll.right;
14872 }
14873 return this.scroll.down;
14874 }
14875 forceRendering(currentlyVisiblePages) {
14876 const visiblePages = currentlyVisiblePages || this._getVisiblePages();
14877 const scrollAhead = this.#getScrollAhead(visiblePages);
14878 const preRenderExtra =
14879 this._spreadMode !== SpreadMode.NONE && this._scrollMode !== ScrollMode.HORIZONTAL;
14880 const ignoreDetailViews =
14881 this.#scaleTimeoutId !== null ||
14882 (this.#scrollTimeoutId !== null &&
14883 visiblePages.views.some((page) => page.detailView?.renderingCancelled));
14884 const pageView = this.renderingQueue.getHighestPriority(
14885 visiblePages,
14886 this._pages,
14887 scrollAhead,
14888 preRenderExtra,
14889 ignoreDetailViews
14890 );
14891 if (pageView) {
14892 this.#ensurePdfPageLoaded(pageView).then(() => {
14893 this.renderingQueue.renderView(pageView);
14894 });
14895 return true;
14896 }
14897 return false;
14898 }
14899 get hasEqualPageSizes() {
14900 const firstPageView = this._pages[0];
14901 for (let i = 1, ii = this._pages.length; i < ii; ++i) {
14902 const pageView = this._pages[i];
14903 if (pageView.width !== firstPageView.width || pageView.height !== firstPageView.height) {
14904 return false;
14905 }
14906 }
14907 return true;
14908 }
14909 getPagesOverview() {
14910 let initialOrientation;
14911 return this._pages.map((pageView) => {
14912 const viewport = pageView.pdfPage.getViewport({
14913 scale: 1,
14914 });
14915 const orientation = isPortraitOrientation(viewport);
14916 if (initialOrientation === undefined) {
14917 initialOrientation = orientation;
14918 } else if (this.enablePrintAutoRotate && orientation !== initialOrientation) {
14919 return {
14920 width: viewport.height,
14921 height: viewport.width,
14922 rotation: (viewport.rotation - 90) % 360,
14923 };
14924 }
14925 return {
14926 width: viewport.width,
14927 height: viewport.height,
14928 rotation: viewport.rotation,
14929 };
14930 });
14931 }
14932 get optionalContentConfigPromise() {
14933 if (!this.pdfDocument) {
14934 return Promise.resolve(null);
14935 }
14936 if (!this._optionalContentConfigPromise) {
14937 console.error('optionalContentConfigPromise: Not initialized yet.');
14938 return this.pdfDocument.getOptionalContentConfig({
14939 intent: 'display',
14940 });
14941 }
14942 return this._optionalContentConfigPromise;
14943 }
14944 set optionalContentConfigPromise(promise) {
14945 if (!(promise instanceof Promise)) {
14946 throw new Error(`Invalid optionalContentConfigPromise: ${promise}`);
14947 }
14948 if (!this.pdfDocument) {
14949 return;
14950 }
14951 if (!this._optionalContentConfigPromise) {
14952 return;
14953 }
14954 this._optionalContentConfigPromise = promise;
14955 this.refresh(false, {
14956 optionalContentConfigPromise: promise,
14957 });
14958 this.eventBus.dispatch('optionalcontentconfigchanged', {
14959 source: this,
14960 promise,
14961 });
14962 }
14963 get scrollMode() {
14964 return this._scrollMode;
14965 }
14966 set scrollMode(mode) {
14967 if (this._scrollMode === mode) {
14968 return;
14969 }
14970 if (!isValidScrollMode(mode)) {
14971 throw new Error(`Invalid scroll mode: ${mode}`);
14972 }
14973 if (this.pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) {
14974 return;
14975 }
14976 this._previousScrollMode = this._scrollMode;
14977 this._scrollMode = mode;
14978 this.eventBus.dispatch('scrollmodechanged', {
14979 source: this,
14980 mode,
14981 });
14982 this._updateScrollMode(this._currentPageNumber);
14983 }
14984 _updateScrollMode(pageNumber = null) {
14985 const scrollMode = this._scrollMode,
14986 viewer = this.viewer;
14987 viewer.classList.toggle('scrollHorizontal', scrollMode === ScrollMode.HORIZONTAL);
14988 viewer.classList.toggle('scrollWrapped', scrollMode === ScrollMode.WRAPPED);
14989 if (!this.pdfDocument || !pageNumber) {
14990 return;
14991 }
14992 if (scrollMode === ScrollMode.PAGE) {
14993 this.#ensurePageViewVisible();
14994 } else if (this._previousScrollMode === ScrollMode.PAGE) {
14995 this._updateSpreadMode();
14996 }
14997 if (this._currentScaleValue && isNaN(this._currentScaleValue)) {
14998 this.#setScale(this._currentScaleValue, {
14999 noScroll: true,
15000 });
15001 }
15002 this._setCurrentPageNumber(pageNumber, true);
15003 this.update();
15004 }
15005 get spreadMode() {
15006 return this._spreadMode;
15007 }
15008 set spreadMode(mode) {
15009 if (this._spreadMode === mode) {
15010 return;
15011 }
15012 if (!isValidSpreadMode(mode)) {
15013 throw new Error(`Invalid spread mode: ${mode}`);
15014 }
15015 this._spreadMode = mode;
15016 this.eventBus.dispatch('spreadmodechanged', {
15017 source: this,
15018 mode,
15019 });
15020 this._updateSpreadMode(this._currentPageNumber);
15021 }
15022 _updateSpreadMode(pageNumber = null) {
15023 if (!this.pdfDocument) {
15024 return;
15025 }
15026 const viewer = this.viewer,
15027 pages = this._pages;
15028 if (this._scrollMode === ScrollMode.PAGE) {
15029 this.#ensurePageViewVisible();
15030 } else {
15031 viewer.textContent = '';
15032 if (this._spreadMode === SpreadMode.NONE) {
15033 for (const pageView of this._pages) {
15034 viewer.append(pageView.div);
15035 }
15036 } else {
15037 const parity = this._spreadMode - 1;
15038 let spread = null;
15039 for (let i = 0, ii = pages.length; i < ii; ++i) {
15040 if (spread === null) {
15041 spread = document.createElement('div');
15042 spread.className = 'spread';
15043 viewer.append(spread);
15044 } else if (i % 2 === parity) {
15045 spread = spread.cloneNode(false);
15046 viewer.append(spread);
15047 }
15048 spread.append(pages[i].div);
15049 }
15050 }
15051 }
15052 if (!pageNumber) {
15053 return;
15054 }
15055 if (this._currentScaleValue && isNaN(this._currentScaleValue)) {
15056 this.#setScale(this._currentScaleValue, {
15057 noScroll: true,
15058 });
15059 }
15060 this._setCurrentPageNumber(pageNumber, true);
15061 this.update();
15062 }
15063 _getPageAdvance(currentPageNumber, previous = false) {
15064 switch (this._scrollMode) {
15065 case ScrollMode.WRAPPED: {
15066 const { views } = this._getVisiblePages(),
15067 pageLayout = new Map();
15068 for (const { id, y, percent, widthPercent } of views) {
15069 if (percent === 0 || widthPercent < 100) {
15070 continue;
15071 }
15072 let yArray = pageLayout.get(y);
15073 if (!yArray) {
15074 pageLayout.set(y, (yArray ||= []));
15075 }
15076 yArray.push(id);
15077 }
15078 for (const yArray of pageLayout.values()) {
15079 const currentIndex = yArray.indexOf(currentPageNumber);
15080 if (currentIndex === -1) {
15081 continue;
15082 }
15083 const numPages = yArray.length;
15084 if (numPages === 1) {
15085 break;
15086 }
15087 if (previous) {
15088 for (let i = currentIndex - 1, ii = 0; i >= ii; i--) {
15089 const currentId = yArray[i],
15090 expectedId = yArray[i + 1] - 1;
15091 if (currentId < expectedId) {
15092 return currentPageNumber - expectedId;
15093 }
15094 }
15095 } else {
15096 for (let i = currentIndex + 1, ii = numPages; i < ii; i++) {
15097 const currentId = yArray[i],
15098 expectedId = yArray[i - 1] + 1;
15099 if (currentId > expectedId) {
15100 return expectedId - currentPageNumber;
15101 }
15102 }
15103 }
15104 if (previous) {
15105 const firstId = yArray[0];
15106 if (firstId < currentPageNumber) {
15107 return currentPageNumber - firstId + 1;
15108 }
15109 } else {
15110 const lastId = yArray[numPages - 1];
15111 if (lastId > currentPageNumber) {
15112 return lastId - currentPageNumber + 1;
15113 }
15114 }
15115 break;
15116 }
15117 break;
15118 }
15119 case ScrollMode.HORIZONTAL: {
15120 break;
15121 }
15122 case ScrollMode.PAGE:
15123 case ScrollMode.VERTICAL: {
15124 if (this._spreadMode === SpreadMode.NONE) {
15125 break;
15126 }
15127 const parity = this._spreadMode - 1;
15128 if (previous && currentPageNumber % 2 !== parity) {
15129 break;
15130 } else if (!previous && currentPageNumber % 2 === parity) {
15131 break;
15132 }
15133 const { views } = this._getVisiblePages(),
15134 expectedId = previous ? currentPageNumber - 1 : currentPageNumber + 1;
15135 for (const { id, percent, widthPercent } of views) {
15136 if (id !== expectedId) {
15137 continue;
15138 }
15139 if (percent > 0 && widthPercent === 100) {
15140 return 2;
15141 }
15142 break;
15143 }
15144 break;
15145 }
15146 }
15147 return 1;
15148 }
15149 nextPage() {
15150 const currentPageNumber = this._currentPageNumber,
15151 pagesCount = this.pagesCount;
15152 if (currentPageNumber >= pagesCount) {
15153 return false;
15154 }
15155 const advance = this._getPageAdvance(currentPageNumber, false) || 1;
15156 this.currentPageNumber = Math.min(currentPageNumber + advance, pagesCount);
15157 return true;
15158 }
15159 previousPage() {
15160 const currentPageNumber = this._currentPageNumber;
15161 if (currentPageNumber <= 1) {
15162 return false;
15163 }
15164 const advance = this._getPageAdvance(currentPageNumber, true) || 1;
15165 this.currentPageNumber = Math.max(currentPageNumber - advance, 1);
15166 return true;
15167 }
15168 updateScale({ drawingDelay, scaleFactor = null, steps = null, origin }) {
15169 if (steps === null && scaleFactor === null) {
15170 throw new Error(
15171 'Invalid updateScale options: either `steps` or `scaleFactor` must be provided.'
15172 );
15173 }
15174 if (!this.pdfDocument) {
15175 return;
15176 }
15177 let newScale = this._currentScale;
15178 if (scaleFactor > 0 && scaleFactor !== 1) {
15179 newScale = Math.round(newScale * scaleFactor * 100) / 100;
15180 } else if (steps) {
15181 const delta = steps > 0 ? DEFAULT_SCALE_DELTA : 1 / DEFAULT_SCALE_DELTA;
15182 const round = steps > 0 ? Math.ceil : Math.floor;
15183 steps = Math.abs(steps);
15184 do {
15185 newScale = round((newScale * delta).toFixed(2) * 10) / 10;
15186 } while (--steps > 0);
15187 }
15188 newScale = MathClamp(newScale, MIN_SCALE, MAX_SCALE);
15189 this.#setScale(newScale, {
15190 noScroll: false,
15191 drawingDelay,
15192 origin,
15193 });
15194 }
15195 increaseScale(options = {}) {
15196 this.updateScale({
15197 ...options,
15198 steps: options.steps ?? 1,
15199 });
15200 }
15201 decreaseScale(options = {}) {
15202 this.updateScale({
15203 ...options,
15204 steps: -(options.steps ?? 1),
15205 });
15206 }
15207 #updateContainerHeightCss(height = this.container.clientHeight) {
15208 if (height !== this.#previousContainerHeight) {
15209 this.#previousContainerHeight = height;
15210 docStyle.setProperty('--viewer-container-height', `${height}px`);
15211 }
15212 }
15213 #resizeObserverCallback(entries) {
15214 for (const entry of entries) {
15215 if (entry.target === this.container) {
15216 this.#updateContainerHeightCss(Math.floor(entry.borderBoxSize[0].blockSize));
15217 this.#containerTopLeft = null;
15218 break;
15219 }
15220 }
15221 }
15222 get containerTopLeft() {
15223 return (this.#containerTopLeft ||= [this.container.offsetTop, this.container.offsetLeft]);
15224 }
15225 #cleanupTimeouts() {
15226 if (this.#scaleTimeoutId !== null) {
15227 clearTimeout(this.#scaleTimeoutId);
15228 this.#scaleTimeoutId = null;
15229 }
15230 if (this.#scrollTimeoutId !== null) {
15231 clearTimeout(this.#scrollTimeoutId);
15232 this.#scrollTimeoutId = null;
15233 }
15234 }
15235 #cleanupSwitchAnnotationEditorMode() {
15236 this.#switchAnnotationEditorModeAC?.abort();
15237 this.#switchAnnotationEditorModeAC = null;
15238 if (this.#switchAnnotationEditorModeTimeoutId !== null) {
15239 clearTimeout(this.#switchAnnotationEditorModeTimeoutId);
15240 this.#switchAnnotationEditorModeTimeoutId = null;
15241 }
15242 }
15243 #preloadEditingData(mode) {
15244 switch (mode) {
15245 case AnnotationEditorType.STAMP:
15246 this.#mlManager?.loadModel('altText');
15247 break;
15248 case AnnotationEditorType.SIGNATURE:
15249 this.#signatureManager?.loadSignatures();
15250 break;
15251 }
15252 }
15253 get annotationEditorMode() {
15254 return this.#annotationEditorUIManager
15255 ? this.#annotationEditorMode
15256 : AnnotationEditorType.DISABLE;
15257 }
15258 set annotationEditorMode({
15259 mode,
15260 editId = null,
15261 isFromKeyboard = false,
15262 mustEnterInEditMode = false,
15263 editComment = false,
15264 }) {
15265 if (!this.#annotationEditorUIManager) {
15266 throw new Error(`The AnnotationEditor is not enabled.`);
15267 }
15268 if (this.#annotationEditorMode === mode) {
15269 return;
15270 }
15271 if (!isValidAnnotationEditorMode(mode)) {
15272 throw new Error(`Invalid AnnotationEditor mode: ${mode}`);
15273 }
15274 if (!this.pdfDocument) {
15275 return;
15276 }
15277 this.#preloadEditingData(mode);
15278 const { eventBus, pdfDocument } = this;
15279 const updater = async () => {
15280 this.#cleanupSwitchAnnotationEditorMode();
15281 this.#annotationEditorMode = mode;
15282 await this.#annotationEditorUIManager.updateMode(
15283 mode,
15284 editId,
15285 true,
15286 isFromKeyboard,
15287 mustEnterInEditMode,
15288 editComment
15289 );
15290 if (mode !== this.#annotationEditorMode || pdfDocument !== this.pdfDocument) {
15291 return;
15292 }
15293 eventBus.dispatch('annotationeditormodechanged', {
15294 source: this,
15295 mode,
15296 });
15297 };
15298 if (
15299 mode === AnnotationEditorType.NONE ||
15300 this.#annotationEditorMode === AnnotationEditorType.NONE
15301 ) {
15302 const isEditing = mode !== AnnotationEditorType.NONE;
15303 if (!isEditing) {
15304 this.pdfDocument.annotationStorage.resetModifiedIds();
15305 }
15306 this.cleanup();
15307 for (const pageView of this._pages) {
15308 pageView.toggleEditingMode(isEditing);
15309 }
15310 const idsToRefresh = this.#switchToEditAnnotationMode();
15311 if (isEditing && idsToRefresh) {
15312 this.#cleanupSwitchAnnotationEditorMode();
15313 this.#switchAnnotationEditorModeAC = new AbortController();
15314 const signal = AbortSignal.any([
15315 this.#eventAbortController.signal,
15316 this.#switchAnnotationEditorModeAC.signal,
15317 ]);
15318 eventBus._on(
15319 'pagerendered',
15320 ({ pageNumber }) => {
15321 idsToRefresh.delete(pageNumber);
15322 if (idsToRefresh.size === 0) {
15323 this.#switchAnnotationEditorModeTimeoutId = setTimeout(updater, 0);
15324 }
15325 },
15326 {
15327 signal,
15328 }
15329 );
15330 return;
15331 }
15332 }
15333 updater();
15334 }
15335 refresh(noUpdate = false, updateArgs = Object.create(null)) {
15336 if (!this.pdfDocument) {
15337 return;
15338 }
15339 for (const pageView of this._pages) {
15340 pageView.update(updateArgs);
15341 }
15342 this.#cleanupTimeouts();
15343 if (!noUpdate) {
15344 this.update();
15345 }
15346 }
15347} // ./web/secondary_toolbar.js
15348
15349class SecondaryToolbar {
15350 #opts;
15351 constructor(options, eventBus) {
15352 this.#opts = options;
15353 const buttons = [
15354 {
15355 element: options.presentationModeButton,
15356 eventName: 'presentationmode',
15357 close: true,
15358 },
15359 {
15360 element: options.printButton,
15361 eventName: 'print',
15362 close: true,
15363 },
15364 {
15365 element: options.downloadButton,
15366 eventName: 'download',
15367 close: true,
15368 },
15369 {
15370 element: options.viewBookmarkButton,
15371 eventName: null,
15372 close: true,
15373 },
15374 {
15375 element: options.firstPageButton,
15376 eventName: 'firstpage',
15377 close: true,
15378 },
15379 {
15380 element: options.lastPageButton,
15381 eventName: 'lastpage',
15382 close: true,
15383 },
15384 {
15385 element: options.pageRotateCwButton,
15386 eventName: 'rotatecw',
15387 close: false,
15388 },
15389 {
15390 element: options.pageRotateCcwButton,
15391 eventName: 'rotateccw',
15392 close: false,
15393 },
15394 {
15395 element: options.cursorSelectToolButton,
15396 eventName: 'switchcursortool',
15397 eventDetails: {
15398 tool: CursorTool.SELECT,
15399 },
15400 close: true,
15401 },
15402 {
15403 element: options.cursorHandToolButton,
15404 eventName: 'switchcursortool',
15405 eventDetails: {
15406 tool: CursorTool.HAND,
15407 },
15408 close: true,
15409 },
15410 {
15411 element: options.scrollPageButton,
15412 eventName: 'switchscrollmode',
15413 eventDetails: {
15414 mode: ScrollMode.PAGE,
15415 },
15416 close: true,
15417 },
15418 {
15419 element: options.scrollVerticalButton,
15420 eventName: 'switchscrollmode',
15421 eventDetails: {
15422 mode: ScrollMode.VERTICAL,
15423 },
15424 close: true,
15425 },
15426 {
15427 element: options.scrollHorizontalButton,
15428 eventName: 'switchscrollmode',
15429 eventDetails: {
15430 mode: ScrollMode.HORIZONTAL,
15431 },
15432 close: true,
15433 },
15434 {
15435 element: options.scrollWrappedButton,
15436 eventName: 'switchscrollmode',
15437 eventDetails: {
15438 mode: ScrollMode.WRAPPED,
15439 },
15440 close: true,
15441 },
15442 {
15443 element: options.spreadNoneButton,
15444 eventName: 'switchspreadmode',
15445 eventDetails: {
15446 mode: SpreadMode.NONE,
15447 },
15448 close: true,
15449 },
15450 {
15451 element: options.spreadOddButton,
15452 eventName: 'switchspreadmode',
15453 eventDetails: {
15454 mode: SpreadMode.ODD,
15455 },
15456 close: true,
15457 },
15458 {
15459 element: options.spreadEvenButton,
15460 eventName: 'switchspreadmode',
15461 eventDetails: {
15462 mode: SpreadMode.EVEN,
15463 },
15464 close: true,
15465 },
15466 {
15467 element: options.imageAltTextSettingsButton,
15468 eventName: 'imagealttextsettings',
15469 close: true,
15470 },
15471 {
15472 element: options.documentPropertiesButton,
15473 eventName: 'documentproperties',
15474 close: true,
15475 },
15476 ];
15477 buttons.push({
15478 element: options.openFileButton,
15479 eventName: 'openfile',
15480 close: true,
15481 });
15482 this.eventBus = eventBus;
15483 this.opened = false;
15484 this.#bindListeners(buttons);
15485 this.reset();
15486 }
15487 get isOpen() {
15488 return this.opened;
15489 }
15490 setPageNumber(pageNumber) {
15491 this.pageNumber = pageNumber;
15492 this.#updateUIState();
15493 }
15494 setPagesCount(pagesCount) {
15495 this.pagesCount = pagesCount;
15496 this.#updateUIState();
15497 }
15498 reset() {
15499 this.pageNumber = 0;
15500 this.pagesCount = 0;
15501 this.#updateUIState();
15502 this.eventBus.dispatch('switchcursortool', {
15503 source: this,
15504 reset: true,
15505 });
15506 this.#scrollModeChanged({
15507 mode: ScrollMode.VERTICAL,
15508 });
15509 this.#spreadModeChanged({
15510 mode: SpreadMode.NONE,
15511 });
15512 }
15513 #updateUIState() {
15514 const { firstPageButton, lastPageButton, pageRotateCwButton, pageRotateCcwButton } = this.#opts;
15515 firstPageButton.disabled = this.pageNumber <= 1;
15516 lastPageButton.disabled = this.pageNumber >= this.pagesCount;
15517 pageRotateCwButton.disabled = this.pagesCount === 0;
15518 pageRotateCcwButton.disabled = this.pagesCount === 0;
15519 }
15520 #bindListeners(buttons) {
15521 const { eventBus } = this;
15522 const { toggleButton } = this.#opts;
15523 toggleButton.addEventListener('click', this.toggle.bind(this));
15524 for (const { element, eventName, close, eventDetails } of buttons) {
15525 element.addEventListener('click', (evt) => {
15526 if (eventName !== null) {
15527 eventBus.dispatch(eventName, {
15528 source: this,
15529 ...eventDetails,
15530 });
15531 }
15532 if (close) {
15533 this.close();
15534 }
15535 eventBus.dispatch('reporttelemetry', {
15536 source: this,
15537 details: {
15538 type: 'buttons',
15539 data: {
15540 id: element.id,
15541 },
15542 },
15543 });
15544 });
15545 }
15546 eventBus._on('cursortoolchanged', this.#cursorToolChanged.bind(this));
15547 eventBus._on('scrollmodechanged', this.#scrollModeChanged.bind(this));
15548 eventBus._on('spreadmodechanged', this.#spreadModeChanged.bind(this));
15549 }
15550 #cursorToolChanged({ tool, disabled }) {
15551 const { cursorSelectToolButton, cursorHandToolButton } = this.#opts;
15552 toggleCheckedBtn(cursorSelectToolButton, tool === CursorTool.SELECT);
15553 toggleCheckedBtn(cursorHandToolButton, tool === CursorTool.HAND);
15554 cursorSelectToolButton.disabled = disabled;
15555 cursorHandToolButton.disabled = disabled;
15556 }
15557 #scrollModeChanged({ mode }) {
15558 const {
15559 scrollPageButton,
15560 scrollVerticalButton,
15561 scrollHorizontalButton,
15562 scrollWrappedButton,
15563 spreadNoneButton,
15564 spreadOddButton,
15565 spreadEvenButton,
15566 } = this.#opts;
15567 toggleCheckedBtn(scrollPageButton, mode === ScrollMode.PAGE);
15568 toggleCheckedBtn(scrollVerticalButton, mode === ScrollMode.VERTICAL);
15569 toggleCheckedBtn(scrollHorizontalButton, mode === ScrollMode.HORIZONTAL);
15570 toggleCheckedBtn(scrollWrappedButton, mode === ScrollMode.WRAPPED);
15571 const forceScrollModePage = this.pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE;
15572 scrollPageButton.disabled = forceScrollModePage;
15573 scrollVerticalButton.disabled = forceScrollModePage;
15574 scrollHorizontalButton.disabled = forceScrollModePage;
15575 scrollWrappedButton.disabled = forceScrollModePage;
15576 const isHorizontal = mode === ScrollMode.HORIZONTAL;
15577 spreadNoneButton.disabled = isHorizontal;
15578 spreadOddButton.disabled = isHorizontal;
15579 spreadEvenButton.disabled = isHorizontal;
15580 }
15581 #spreadModeChanged({ mode }) {
15582 const { spreadNoneButton, spreadOddButton, spreadEvenButton } = this.#opts;
15583 toggleCheckedBtn(spreadNoneButton, mode === SpreadMode.NONE);
15584 toggleCheckedBtn(spreadOddButton, mode === SpreadMode.ODD);
15585 toggleCheckedBtn(spreadEvenButton, mode === SpreadMode.EVEN);
15586 }
15587 open() {
15588 if (this.opened) {
15589 return;
15590 }
15591 this.opened = true;
15592 const { toggleButton, toolbar } = this.#opts;
15593 toggleExpandedBtn(toggleButton, true, toolbar);
15594 }
15595 close() {
15596 if (!this.opened) {
15597 return;
15598 }
15599 this.opened = false;
15600 const { toggleButton, toolbar } = this.#opts;
15601 toggleExpandedBtn(toggleButton, false, toolbar);
15602 }
15603 toggle() {
15604 if (this.opened) {
15605 this.close();
15606 } else {
15607 this.open();
15608 }
15609 }
15610} // ./web/signature_manager.js
15611
15612const DEFAULT_HEIGHT_IN_PAGE = 40;
15613class SignatureManager {
15614 #addButton;
15615 #tabsToAltText = null;
15616 #clearButton;
15617 #clearDescription;
15618 #currentEditor;
15619 #description;
15620 #dialog;
15621 #drawCurves = null;
15622 #drawPlaceholder;
15623 #drawPath = null;
15624 #drawPathString = '';
15625 #drawPoints = null;
15626 #drawSVG;
15627 #drawThickness;
15628 #errorBar;
15629 #errorDescription;
15630 #errorTitle;
15631 #extractedSignatureData = null;
15632 #imagePath = null;
15633 #imagePicker;
15634 #imagePickerLink;
15635 #imagePlaceholder;
15636 #imageSVG;
15637 #saveCheckbox;
15638 #saveContainer;
15639 #tabButtons;
15640 #addSignatureToolbarButton;
15641 #loadSignaturesPromise = null;
15642 #typeInput;
15643 #currentTab = null;
15644 #currentTabAC = null;
15645 #hasDescriptionChanged = false;
15646 #eventBus;
15647 #isStorageFull = false;
15648 #l10n;
15649 #overlayManager;
15650 #editDescriptionDialog;
15651 #signatureStorage;
15652 #uiManager = null;
15653 static #l10nDescription = null;
15654 constructor(
15655 {
15656 dialog,
15657 panels,
15658 typeButton,
15659 typeInput,
15660 drawButton,
15661 drawPlaceholder,
15662 drawSVG,
15663 drawThickness,
15664 imageButton,
15665 imageSVG,
15666 imagePlaceholder,
15667 imagePicker,
15668 imagePickerLink,
15669 description,
15670 clearButton,
15671 cancelButton,
15672 addButton,
15673 errorCloseButton,
15674 errorBar,
15675 errorTitle,
15676 errorDescription,
15677 saveCheckbox,
15678 saveContainer,
15679 },
15680 editSignatureElements,
15681 addSignatureToolbarButton,
15682 overlayManager,
15683 l10n,
15684 signatureStorage,
15685 eventBus
15686 ) {
15687 this.#addButton = addButton;
15688 this.#clearButton = clearButton;
15689 this.#clearDescription = description.lastElementChild;
15690 this.#description = description.firstElementChild;
15691 this.#dialog = dialog;
15692 this.#drawSVG = drawSVG;
15693 this.#drawPlaceholder = drawPlaceholder;
15694 this.#drawThickness = drawThickness;
15695 this.#errorBar = errorBar;
15696 this.#errorTitle = errorTitle;
15697 this.#errorDescription = errorDescription;
15698 this.#imageSVG = imageSVG;
15699 this.#imagePlaceholder = imagePlaceholder;
15700 this.#imagePicker = imagePicker;
15701 this.#imagePickerLink = imagePickerLink;
15702 this.#overlayManager = overlayManager;
15703 this.#saveCheckbox = saveCheckbox;
15704 this.#saveContainer = saveContainer;
15705 this.#addSignatureToolbarButton = addSignatureToolbarButton;
15706 this.#typeInput = typeInput;
15707 this.#l10n = l10n;
15708 this.#signatureStorage = signatureStorage;
15709 this.#eventBus = eventBus;
15710 this.#editDescriptionDialog = new EditDescriptionDialog(editSignatureElements, overlayManager);
15711 SignatureManager.#l10nDescription ||= Object.freeze({
15712 signature: 'pdfjs-editor-add-signature-description-default-when-drawing',
15713 errorUploadTitle: 'pdfjs-editor-add-signature-image-upload-error-title',
15714 errorUploadDescription: 'pdfjs-editor-add-signature-image-upload-error-description',
15715 errorNoDataTitle: 'pdfjs-editor-add-signature-image-no-data-error-title',
15716 errorNoDataDescription: 'pdfjs-editor-add-signature-image-no-data-error-description',
15717 });
15718 dialog.addEventListener('close', this.#close.bind(this));
15719 dialog.addEventListener('contextmenu', (e) => {
15720 const { target } = e;
15721 if (target !== this.#typeInput && target !== this.#description) {
15722 e.preventDefault();
15723 }
15724 });
15725 dialog.addEventListener('drop', (e) => {
15726 stopEvent(e);
15727 });
15728 cancelButton.addEventListener('click', this.#cancel.bind(this));
15729 addButton.addEventListener('click', this.#add.bind(this));
15730 clearButton.addEventListener(
15731 'click',
15732 () => {
15733 this.#reportTelemetry({
15734 type: 'signature',
15735 action: 'pdfjs.signature.clear',
15736 data: {
15737 type: this.#currentTab,
15738 },
15739 });
15740 this.#initTab(null);
15741 },
15742 {
15743 passive: true,
15744 }
15745 );
15746 this.#description.addEventListener(
15747 'input',
15748 () => {
15749 this.#clearDescription.disabled = this.#description.value === '';
15750 },
15751 {
15752 passive: true,
15753 }
15754 );
15755 this.#clearDescription.addEventListener(
15756 'click',
15757 () => {
15758 this.#description.value = '';
15759 this.#clearDescription.disabled = true;
15760 },
15761 {
15762 passive: true,
15763 }
15764 );
15765 errorCloseButton.addEventListener(
15766 'click',
15767 () => {
15768 errorBar.hidden = true;
15769 },
15770 {
15771 passive: true,
15772 }
15773 );
15774 this.#initTabButtons(typeButton, drawButton, imageButton, panels);
15775 imagePicker.accept = SupportedImageMimeTypes.join(',');
15776 eventBus._on('storedsignatureschanged', this.#signaturesChanged.bind(this));
15777 overlayManager.register(dialog);
15778 }
15779 #initTabButtons(typeButton, drawButton, imageButton, panels) {
15780 const buttons = (this.#tabButtons = new Map([
15781 ['type', typeButton],
15782 ['draw', drawButton],
15783 ['image', imageButton],
15784 ]));
15785 const tabCallback = (e) => {
15786 for (const [name, button] of buttons) {
15787 if (button === e.target) {
15788 button.setAttribute('aria-selected', true);
15789 button.setAttribute('tabindex', 0);
15790 panels.setAttribute('data-selected', name);
15791 this.#initTab(name);
15792 } else {
15793 button.setAttribute('aria-selected', false);
15794 button.setAttribute('tabindex', -1);
15795 }
15796 }
15797 };
15798 const buttonsArray = Array.from(buttons.values());
15799 for (let i = 0, ii = buttonsArray.length; i < ii; i++) {
15800 const button = buttonsArray[i];
15801 button.addEventListener('click', tabCallback, {
15802 passive: true,
15803 });
15804 button.addEventListener(
15805 'keydown',
15806 ({ key }) => {
15807 if (key !== 'ArrowLeft' && key !== 'ArrowRight') {
15808 return;
15809 }
15810 buttonsArray[i + (key === 'ArrowLeft' ? -1 : 1)]?.focus();
15811 },
15812 {
15813 passive: true,
15814 }
15815 );
15816 }
15817 }
15818 #resetCommon() {
15819 this.#hasDescriptionChanged = false;
15820 this.#description.value = '';
15821 if (this.#currentTab) {
15822 this.#tabsToAltText.get(this.#currentTab).value = '';
15823 }
15824 }
15825 #resetTab(name) {
15826 switch (name) {
15827 case 'type':
15828 this.#typeInput.value = '';
15829 break;
15830 case 'draw':
15831 this.#drawCurves = null;
15832 this.#drawPoints = null;
15833 this.#drawPathString = '';
15834 this.#drawPath?.remove();
15835 this.#drawPath = null;
15836 this.#drawPlaceholder.hidden = false;
15837 this.#drawThickness.value = 1;
15838 break;
15839 case 'image':
15840 this.#imagePlaceholder.hidden = false;
15841 this.#imagePath?.remove();
15842 this.#imagePath = null;
15843 break;
15844 }
15845 }
15846 #initTab(name) {
15847 if (name && this.#currentTab === name) {
15848 return;
15849 }
15850 if (this.#currentTab) {
15851 this.#tabsToAltText.get(this.#currentTab).value = this.#description.value;
15852 }
15853 if (name) {
15854 this.#currentTab = name;
15855 }
15856 this.#errorBar.hidden = true;
15857 const reset = !name;
15858 if (reset) {
15859 this.#resetCommon();
15860 } else {
15861 this.#description.value = this.#tabsToAltText.get(this.#currentTab).value;
15862 }
15863 this.#clearDescription.disabled = this.#description.value === '';
15864 this.#currentTabAC?.abort();
15865 this.#currentTabAC = new AbortController();
15866 switch (this.#currentTab) {
15867 case 'type':
15868 this.#initTypeTab(reset);
15869 break;
15870 case 'draw':
15871 this.#initDrawTab(reset);
15872 break;
15873 case 'image':
15874 this.#initImageTab(reset);
15875 break;
15876 }
15877 }
15878 #disableButtons(value) {
15879 if (!value || !this.#isStorageFull) {
15880 this.#saveCheckbox.disabled = !value;
15881 }
15882 this.#clearButton.disabled = this.#addButton.disabled = this.#description.disabled = !value;
15883 }
15884 #initTypeTab(reset) {
15885 if (reset) {
15886 this.#resetTab('type');
15887 }
15888 this.#disableButtons(this.#typeInput.value);
15889 const { signal } = this.#currentTabAC;
15890 const options = {
15891 passive: true,
15892 signal,
15893 };
15894 this.#typeInput.addEventListener(
15895 'input',
15896 () => {
15897 const { value } = this.#typeInput;
15898 if (!this.#hasDescriptionChanged) {
15899 this.#tabsToAltText.get('type').default = this.#description.value = value;
15900 this.#clearDescription.disabled = value === '';
15901 }
15902 this.#disableButtons(value);
15903 },
15904 options
15905 );
15906 this.#description.addEventListener(
15907 'input',
15908 () => {
15909 this.#hasDescriptionChanged = this.#typeInput.value !== this.#description.value;
15910 },
15911 options
15912 );
15913 }
15914 #initDrawTab(reset) {
15915 if (reset) {
15916 this.#resetTab('draw');
15917 }
15918 this.#disableButtons(this.#drawPath);
15919 const { signal } = this.#currentTabAC;
15920 const options = {
15921 signal,
15922 };
15923 let currentPointerId = NaN;
15924 const drawCallback = (e) => {
15925 const { pointerId } = e;
15926 if (!isNaN(currentPointerId) && currentPointerId !== pointerId) {
15927 return;
15928 }
15929 currentPointerId = pointerId;
15930 e.preventDefault();
15931 this.#drawSVG.setPointerCapture(pointerId);
15932 const { width: drawWidth, height: drawHeight } = this.#drawSVG.getBoundingClientRect();
15933 let { offsetX, offsetY } = e;
15934 offsetX = Math.round(offsetX);
15935 offsetY = Math.round(offsetY);
15936 if (e.target === this.#drawPlaceholder) {
15937 this.#drawPlaceholder.hidden = true;
15938 }
15939 if (!this.#drawCurves) {
15940 this.#drawCurves = {
15941 width: drawWidth,
15942 height: drawHeight,
15943 thickness: parseInt(this.#drawThickness.value),
15944 curves: [],
15945 };
15946 this.#disableButtons(true);
15947 const svgFactory = new DOMSVGFactory();
15948 const path = (this.#drawPath = svgFactory.createElement('path'));
15949 path.setAttribute('stroke-width', this.#drawThickness.value);
15950 this.#drawSVG.append(path);
15951 this.#drawSVG.addEventListener('pointerdown', drawCallback, options);
15952 this.#drawPlaceholder.removeEventListener('pointerdown', drawCallback);
15953 if (this.#description.value === '') {
15954 this.#l10n.get(SignatureManager.#l10nDescription.signature).then((description) => {
15955 this.#tabsToAltText.get('draw').default = description;
15956 this.#description.value ||= description;
15957 this.#clearDescription.disabled = this.#description.value === '';
15958 });
15959 }
15960 }
15961 this.#drawPoints = [offsetX, offsetY];
15962 this.#drawCurves.curves.push({
15963 points: this.#drawPoints,
15964 });
15965 this.#drawPathString += `M ${offsetX} ${offsetY}`;
15966 this.#drawPath.setAttribute('d', this.#drawPathString);
15967 const finishDrawAC = new AbortController();
15968 const listenerDrawOptions = {
15969 signal: AbortSignal.any([signal, finishDrawAC.signal]),
15970 };
15971 this.#drawSVG.addEventListener('contextmenu', noContextMenu, listenerDrawOptions);
15972 this.#drawSVG.addEventListener(
15973 'pointermove',
15974 (evt) => {
15975 evt.preventDefault();
15976 let { offsetX: x, offsetY: y } = evt;
15977 x = Math.round(x);
15978 y = Math.round(y);
15979 const drawPoints = this.#drawPoints;
15980 if (
15981 x < 0 ||
15982 y < 0 ||
15983 x > drawWidth ||
15984 y > drawHeight ||
15985 (x === drawPoints.at(-2) && y === drawPoints.at(-1))
15986 ) {
15987 return;
15988 }
15989 if (drawPoints.length >= 4) {
15990 const [x1, y1, x2, y2] = drawPoints.slice(-4);
15991 this.#drawPathString += `C${(x1 + 5 * x2) / 6} ${(y1 + 5 * y2) / 6} ${(5 * x2 + x) / 6} ${(5 * y2 + y) / 6} ${(x2 + x) / 2} ${(y2 + y) / 2}`;
15992 } else {
15993 this.#drawPathString += `L${x} ${y}`;
15994 }
15995 drawPoints.push(x, y);
15996 this.#drawPath.setAttribute('d', this.#drawPathString);
15997 },
15998 listenerDrawOptions
15999 );
16000 this.#drawSVG.addEventListener(
16001 'pointerup',
16002 (evt) => {
16003 const { pointerId: pId } = evt;
16004 if (!isNaN(currentPointerId) && currentPointerId !== pId) {
16005 return;
16006 }
16007 currentPointerId = NaN;
16008 evt.preventDefault();
16009 this.#drawSVG.releasePointerCapture(pId);
16010 finishDrawAC.abort();
16011 if (this.#drawPoints.length === 2) {
16012 this.#drawPathString += `L${this.#drawPoints[0]} ${this.#drawPoints[1]}`;
16013 this.#drawPath.setAttribute('d', this.#drawPathString);
16014 }
16015 },
16016 listenerDrawOptions
16017 );
16018 };
16019 if (this.#drawCurves) {
16020 this.#drawSVG.addEventListener('pointerdown', drawCallback, options);
16021 } else {
16022 this.#drawPlaceholder.addEventListener('pointerdown', drawCallback, options);
16023 }
16024 this.#drawThickness.addEventListener(
16025 'input',
16026 () => {
16027 const { value: thickness } = this.#drawThickness;
16028 this.#drawThickness.setAttribute(
16029 'data-l10n-args',
16030 JSON.stringify({
16031 thickness,
16032 })
16033 );
16034 if (!this.#drawCurves) {
16035 return;
16036 }
16037 this.#drawPath.setAttribute('stroke-width', thickness);
16038 this.#drawCurves.thickness = thickness;
16039 },
16040 options
16041 );
16042 }
16043 #showError(type) {
16044 this.#errorTitle.setAttribute(
16045 'data-l10n-id',
16046 SignatureManager.#l10nDescription[`error${type}Title`]
16047 );
16048 this.#errorDescription.setAttribute(
16049 'data-l10n-id',
16050 SignatureManager.#l10nDescription[`error${type}Description`]
16051 );
16052 this.#errorBar.hidden = false;
16053 }
16054 #initImageTab(reset) {
16055 if (reset) {
16056 this.#resetTab('image');
16057 }
16058 this.#disableButtons(this.#imagePath);
16059 const { signal } = this.#currentTabAC;
16060 const options = {
16061 signal,
16062 };
16063 const passiveOptions = {
16064 passive: true,
16065 signal,
16066 };
16067 this.#imagePickerLink.addEventListener(
16068 'keydown',
16069 (e) => {
16070 const { key } = e;
16071 if (key === 'Enter' || key === ' ') {
16072 stopEvent(e);
16073 this.#imagePicker.click();
16074 }
16075 },
16076 options
16077 );
16078 this.#imagePicker.addEventListener(
16079 'click',
16080 () => {
16081 this.#dialog.classList.toggle('waiting', true);
16082 },
16083 passiveOptions
16084 );
16085 this.#imagePicker.addEventListener(
16086 'change',
16087 async () => {
16088 const file = this.#imagePicker.files?.[0];
16089 if (!file || !SupportedImageMimeTypes.includes(file.type)) {
16090 this.#showError('Upload');
16091 this.#dialog.classList.toggle('waiting', false);
16092 return;
16093 }
16094 await this.#extractSignature(file);
16095 },
16096 passiveOptions
16097 );
16098 this.#imagePicker.addEventListener(
16099 'cancel',
16100 () => {
16101 this.#dialog.classList.toggle('waiting', false);
16102 },
16103 passiveOptions
16104 );
16105 this.#imagePlaceholder.addEventListener(
16106 'dragover',
16107 (e) => {
16108 const { dataTransfer } = e;
16109 for (const { type } of dataTransfer.items) {
16110 if (!SupportedImageMimeTypes.includes(type)) {
16111 continue;
16112 }
16113 dataTransfer.dropEffect = dataTransfer.effectAllowed === 'copy' ? 'copy' : 'move';
16114 stopEvent(e);
16115 return;
16116 }
16117 dataTransfer.dropEffect = 'none';
16118 },
16119 options
16120 );
16121 this.#imagePlaceholder.addEventListener(
16122 'drop',
16123 (e) => {
16124 const {
16125 dataTransfer: { files },
16126 } = e;
16127 if (!files?.length) {
16128 return;
16129 }
16130 for (const file of files) {
16131 if (SupportedImageMimeTypes.includes(file.type)) {
16132 this.#extractSignature(file);
16133 break;
16134 }
16135 }
16136 stopEvent(e);
16137 this.#dialog.classList.toggle('waiting', true);
16138 },
16139 options
16140 );
16141 }
16142 async #extractSignature(file) {
16143 let data;
16144 try {
16145 data = await this.#uiManager.imageManager.getFromFile(file);
16146 } catch (e) {
16147 console.error('SignatureManager.#extractSignature.', e);
16148 }
16149 if (!data) {
16150 this.#showError('Upload');
16151 this.#dialog.classList.toggle('waiting', false);
16152 return;
16153 }
16154 const lineData = (this.#extractedSignatureData = this.#currentEditor.getFromImage(data.bitmap));
16155 if (!lineData) {
16156 this.#showError('NoData');
16157 this.#dialog.classList.toggle('waiting', false);
16158 return;
16159 }
16160 const { outline } = lineData;
16161 this.#imagePlaceholder.hidden = true;
16162 this.#disableButtons(true);
16163 const svgFactory = new DOMSVGFactory();
16164 const path = (this.#imagePath = svgFactory.createElement('path'));
16165 this.#imageSVG.setAttribute('viewBox', outline.viewBox);
16166 this.#imageSVG.setAttribute('preserveAspectRatio', 'xMidYMid meet');
16167 this.#imageSVG.append(path);
16168 path.setAttribute('d', outline.toSVGPath());
16169 this.#tabsToAltText.get('image').default = file.name;
16170 if (this.#description.value === '') {
16171 this.#description.value = file.name || '';
16172 this.#clearDescription.disabled = this.#description.value === '';
16173 }
16174 this.#dialog.classList.toggle('waiting', false);
16175 }
16176 #getOutlineForType() {
16177 return this.#currentEditor.getFromText(
16178 this.#typeInput.value,
16179 window.getComputedStyle(this.#typeInput)
16180 );
16181 }
16182 #getOutlineForDraw() {
16183 const { width, height } = this.#drawSVG.getBoundingClientRect();
16184 return this.#currentEditor.getDrawnSignature(this.#drawCurves, width, height);
16185 }
16186 #reportTelemetry(data) {
16187 this.#eventBus.dispatch('reporttelemetry', {
16188 source: this,
16189 details: {
16190 type: 'editing',
16191 data,
16192 },
16193 });
16194 }
16195 #addToolbarButton(signatureData, uuid, description) {
16196 const { curves, areContours, thickness, width, height } = signatureData;
16197 const maxDim = Math.max(width, height);
16198 const outlineData = SignatureExtractor.processDrawnLines({
16199 lines: {
16200 curves,
16201 thickness,
16202 width,
16203 height,
16204 },
16205 pageWidth: maxDim,
16206 pageHeight: maxDim,
16207 rotation: 0,
16208 innerMargin: 0,
16209 mustSmooth: false,
16210 areContours,
16211 });
16212 if (!outlineData) {
16213 return;
16214 }
16215 const { outline } = outlineData;
16216 const svgFactory = new DOMSVGFactory();
16217 const div = document.createElement('div');
16218 const button = document.createElement('button');
16219 button.addEventListener('click', () => {
16220 this.#eventBus.dispatch('switchannotationeditorparams', {
16221 source: this,
16222 type: AnnotationEditorParamsType.CREATE,
16223 value: {
16224 signatureData: {
16225 lines: {
16226 curves,
16227 thickness,
16228 width,
16229 height,
16230 },
16231 mustSmooth: false,
16232 areContours,
16233 description,
16234 uuid,
16235 heightInPage: DEFAULT_HEIGHT_IN_PAGE,
16236 },
16237 },
16238 });
16239 });
16240 div.append(button);
16241 div.classList.add('toolbarAddSignatureButtonContainer');
16242 const svg = svgFactory.create(1, 1, true);
16243 button.append(svg);
16244 const span = document.createElement('span');
16245 span.ariaHidden = true;
16246 button.append(span);
16247 button.classList.add('toolbarAddSignatureButton');
16248 button.type = 'button';
16249 span.textContent = description;
16250 button.setAttribute('data-l10n-id', 'pdfjs-editor-add-saved-signature-button');
16251 button.setAttribute(
16252 'data-l10n-args',
16253 JSON.stringify({
16254 description,
16255 })
16256 );
16257 button.tabIndex = 0;
16258 const path = svgFactory.createElement('path');
16259 svg.append(path);
16260 svg.setAttribute('viewBox', outline.viewBox);
16261 svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
16262 if (areContours) {
16263 path.classList.add('contours');
16264 }
16265 path.setAttribute('d', outline.toSVGPath());
16266 const deleteButton = document.createElement('button');
16267 div.append(deleteButton);
16268 deleteButton.classList.add('toolbarButton', 'deleteButton');
16269 deleteButton.setAttribute('data-l10n-id', 'pdfjs-editor-delete-signature-button1');
16270 deleteButton.type = 'button';
16271 deleteButton.tabIndex = 0;
16272 deleteButton.addEventListener('click', async () => {
16273 if (await this.#signatureStorage.delete(uuid)) {
16274 div.remove();
16275 this.#reportTelemetry({
16276 type: 'signature',
16277 action: 'pdfjs.signature.delete_saved',
16278 data: {
16279 savedCount: await this.#signatureStorage.size(),
16280 },
16281 });
16282 }
16283 });
16284 const deleteSpan = document.createElement('span');
16285 deleteButton.append(deleteSpan);
16286 deleteSpan.setAttribute('data-l10n-id', 'pdfjs-editor-delete-signature-button-label1');
16287 this.#addSignatureToolbarButton.before(div);
16288 }
16289 async #signaturesChanged() {
16290 const parent = this.#addSignatureToolbarButton.parentElement;
16291 while (parent.firstElementChild !== this.#addSignatureToolbarButton) {
16292 parent.firstElementChild.remove();
16293 }
16294 this.#loadSignaturesPromise = null;
16295 await this.loadSignatures(true);
16296 }
16297 getSignature(params) {
16298 return this.open(params);
16299 }
16300 async loadSignatures(reload = false) {
16301 if (
16302 !this.#addSignatureToolbarButton ||
16303 (!reload && this.#addSignatureToolbarButton.previousElementSibling) ||
16304 !this.#signatureStorage
16305 ) {
16306 return;
16307 }
16308 if (!this.#loadSignaturesPromise) {
16309 this.#loadSignaturesPromise = this.#signatureStorage
16310 .getAll()
16311 .then(async (signatures) => [
16312 signatures,
16313 await Promise.all(
16314 Array.from(signatures.values(), ({ signatureData }) =>
16315 SignatureExtractor.decompressSignature(signatureData)
16316 )
16317 ),
16318 ]);
16319 if (!reload) {
16320 return;
16321 }
16322 }
16323 const [signatures, signaturesData] = await this.#loadSignaturesPromise;
16324 this.#loadSignaturesPromise = null;
16325 let i = 0;
16326 for (const [uuid, { description }] of signatures) {
16327 const data = signaturesData[i++];
16328 if (!data) {
16329 continue;
16330 }
16331 data.curves = data.outlines.map((points) => ({
16332 points,
16333 }));
16334 delete data.outlines;
16335 this.#addToolbarButton(data, uuid, description);
16336 }
16337 }
16338 async renderEditButton(editor) {
16339 const button = document.createElement('button');
16340 button.classList.add('altText', 'editDescription');
16341 button.tabIndex = 0;
16342 if (editor.description) {
16343 button.title = editor.description;
16344 }
16345 const span = document.createElement('span');
16346 button.append(span);
16347 span.setAttribute('data-l10n-id', 'pdfjs-editor-add-signature-edit-button-label');
16348 button.addEventListener(
16349 'click',
16350 () => {
16351 this.#editDescriptionDialog.open(editor);
16352 },
16353 {
16354 passive: true,
16355 }
16356 );
16357 return button;
16358 }
16359 async open({ uiManager, editor }) {
16360 this.#tabsToAltText ||= new Map(
16361 this.#tabButtons.keys().map((name) => [
16362 name,
16363 {
16364 value: '',
16365 default: '',
16366 },
16367 ])
16368 );
16369 this.#uiManager = uiManager;
16370 this.#currentEditor = editor;
16371 this.#uiManager.removeEditListeners();
16372 const isStorageFull = (this.#isStorageFull = await this.#signatureStorage.isFull());
16373 this.#saveContainer.classList.toggle('fullStorage', isStorageFull);
16374 this.#saveCheckbox.checked = !isStorageFull;
16375 await this.#overlayManager.open(this.#dialog);
16376 const tabType = this.#tabButtons.get('type');
16377 tabType.focus();
16378 tabType.click();
16379 }
16380 #cancel() {
16381 this.#finish();
16382 }
16383 #finish() {
16384 this.#overlayManager.closeIfActive(this.#dialog);
16385 }
16386 #close() {
16387 if (this.#currentEditor._drawId === null) {
16388 this.#currentEditor.remove();
16389 }
16390 this.#uiManager?.addEditListeners();
16391 this.#currentTabAC?.abort();
16392 this.#currentTabAC = null;
16393 this.#uiManager = null;
16394 this.#currentEditor = null;
16395 this.#resetCommon();
16396 for (const [name] of this.#tabButtons) {
16397 this.#resetTab(name);
16398 }
16399 this.#disableButtons(false);
16400 this.#currentTab = null;
16401 this.#tabsToAltText = null;
16402 }
16403 async #add() {
16404 let data;
16405 const type = this.#currentTab;
16406 switch (type) {
16407 case 'type':
16408 data = this.#getOutlineForType();
16409 break;
16410 case 'draw':
16411 data = this.#getOutlineForDraw();
16412 break;
16413 case 'image':
16414 data = this.#extractedSignatureData;
16415 break;
16416 }
16417 let uuid = null;
16418 const description = this.#description.value;
16419 if (this.#saveCheckbox.checked) {
16420 const { newCurves, areContours, thickness, width, height } = data;
16421 const signatureData = await SignatureExtractor.compressSignature({
16422 outlines: newCurves,
16423 areContours,
16424 thickness,
16425 width,
16426 height,
16427 });
16428 uuid = await this.#signatureStorage.create({
16429 description,
16430 signatureData,
16431 });
16432 if (uuid) {
16433 this.#addToolbarButton(
16434 {
16435 curves: newCurves.map((points) => ({
16436 points,
16437 })),
16438 areContours,
16439 thickness,
16440 width,
16441 height,
16442 },
16443 uuid,
16444 description
16445 );
16446 } else {
16447 console.warn('SignatureManager.add: cannot save the signature.');
16448 }
16449 }
16450 const altText = this.#tabsToAltText.get(type);
16451 this.#reportTelemetry({
16452 type: 'signature',
16453 action: 'pdfjs.signature.created',
16454 data: {
16455 type,
16456 saved: !!uuid,
16457 savedCount: await this.#signatureStorage.size(),
16458 descriptionChanged: description !== altText.default,
16459 },
16460 });
16461 this.#currentEditor.addSignature(data, DEFAULT_HEIGHT_IN_PAGE, this.#description.value, uuid);
16462 this.#finish();
16463 }
16464 destroy() {
16465 this.#uiManager = null;
16466 this.#finish();
16467 }
16468}
16469class EditDescriptionDialog {
16470 #currentEditor;
16471 #previousDescription;
16472 #description;
16473 #dialog;
16474 #overlayManager;
16475 #signatureSVG;
16476 #uiManager;
16477 constructor(
16478 { dialog, description, cancelButton, updateButton, editSignatureView },
16479 overlayManager
16480 ) {
16481 const descriptionInput = (this.#description = description.firstElementChild);
16482 this.#signatureSVG = editSignatureView;
16483 this.#dialog = dialog;
16484 this.#overlayManager = overlayManager;
16485 dialog.addEventListener('close', this.#close.bind(this));
16486 dialog.addEventListener('contextmenu', (e) => {
16487 if (e.target !== this.#description) {
16488 e.preventDefault();
16489 }
16490 });
16491 cancelButton.addEventListener('click', this.#cancel.bind(this));
16492 updateButton.addEventListener('click', this.#update.bind(this));
16493 const clearDescription = description.lastElementChild;
16494 clearDescription.addEventListener('click', () => {
16495 descriptionInput.value = '';
16496 clearDescription.disabled = true;
16497 updateButton.disabled = this.#previousDescription === '';
16498 });
16499 descriptionInput.addEventListener(
16500 'input',
16501 () => {
16502 const { value } = descriptionInput;
16503 clearDescription.disabled = value === '';
16504 updateButton.disabled = value === this.#previousDescription;
16505 editSignatureView.setAttribute('aria-label', value);
16506 },
16507 {
16508 passive: true,
16509 }
16510 );
16511 overlayManager.register(dialog);
16512 }
16513 async open(editor) {
16514 this.#uiManager = editor._uiManager;
16515 this.#currentEditor = editor;
16516 this.#previousDescription = this.#description.value = editor.description;
16517 this.#description.dispatchEvent(new Event('input'));
16518 this.#uiManager.removeEditListeners();
16519 const { areContours, outline } = editor.getSignaturePreview();
16520 const svgFactory = new DOMSVGFactory();
16521 const path = svgFactory.createElement('path');
16522 this.#signatureSVG.append(path);
16523 this.#signatureSVG.setAttribute('viewBox', outline.viewBox);
16524 path.setAttribute('d', outline.toSVGPath());
16525 if (areContours) {
16526 path.classList.add('contours');
16527 }
16528 await this.#overlayManager.open(this.#dialog);
16529 }
16530 async #update() {
16531 this.#currentEditor._reportTelemetry({
16532 action: 'pdfjs.signature.edit_description',
16533 data: {
16534 hasBeenChanged: true,
16535 },
16536 });
16537 this.#currentEditor.description = this.#description.value;
16538 this.#finish();
16539 }
16540 #cancel() {
16541 this.#currentEditor._reportTelemetry({
16542 action: 'pdfjs.signature.edit_description',
16543 data: {
16544 hasBeenChanged: false,
16545 },
16546 });
16547 this.#finish();
16548 }
16549 #finish() {
16550 this.#overlayManager.closeIfActive(this.#dialog);
16551 }
16552 #close() {
16553 this.#uiManager?.addEditListeners();
16554 this.#uiManager = null;
16555 this.#currentEditor = null;
16556 this.#signatureSVG.firstElementChild.remove();
16557 }
16558} // ./web/toolbar.js
16559
16560class Toolbar {
16561 #colorPicker = null;
16562 #opts;
16563 constructor(options, eventBus, toolbarDensity = 0) {
16564 this.#opts = options;
16565 this.eventBus = eventBus;
16566 const buttons = [
16567 {
16568 element: options.previous,
16569 eventName: 'previouspage',
16570 },
16571 {
16572 element: options.next,
16573 eventName: 'nextpage',
16574 },
16575 {
16576 element: options.zoomIn,
16577 eventName: 'zoomin',
16578 },
16579 {
16580 element: options.zoomOut,
16581 eventName: 'zoomout',
16582 },
16583 {
16584 element: options.print,
16585 eventName: 'print',
16586 },
16587 {
16588 element: options.download,
16589 eventName: 'download',
16590 },
16591 {
16592 element: options.editorCommentButton,
16593 eventName: 'switchannotationeditormode',
16594 eventDetails: {
16595 get mode() {
16596 const { classList } = options.editorCommentButton;
16597 return classList.contains('toggled')
16598 ? AnnotationEditorType.NONE
16599 : AnnotationEditorType.POPUP;
16600 },
16601 },
16602 },
16603 {
16604 element: options.editorFreeTextButton,
16605 eventName: 'switchannotationeditormode',
16606 eventDetails: {
16607 get mode() {
16608 const { classList } = options.editorFreeTextButton;
16609 return classList.contains('toggled')
16610 ? AnnotationEditorType.NONE
16611 : AnnotationEditorType.FREETEXT;
16612 },
16613 },
16614 },
16615 {
16616 element: options.editorHighlightButton,
16617 eventName: 'switchannotationeditormode',
16618 eventDetails: {
16619 get mode() {
16620 const { classList } = options.editorHighlightButton;
16621 return classList.contains('toggled')
16622 ? AnnotationEditorType.NONE
16623 : AnnotationEditorType.HIGHLIGHT;
16624 },
16625 },
16626 },
16627 {
16628 element: options.editorInkButton,
16629 eventName: 'switchannotationeditormode',
16630 eventDetails: {
16631 get mode() {
16632 const { classList } = options.editorInkButton;
16633 return classList.contains('toggled')
16634 ? AnnotationEditorType.NONE
16635 : AnnotationEditorType.INK;
16636 },
16637 },
16638 },
16639 {
16640 element: options.editorStampButton,
16641 eventName: 'switchannotationeditormode',
16642 eventDetails: {
16643 get mode() {
16644 const { classList } = options.editorStampButton;
16645 return classList.contains('toggled')
16646 ? AnnotationEditorType.NONE
16647 : AnnotationEditorType.STAMP;
16648 },
16649 },
16650 telemetry: {
16651 type: 'editing',
16652 data: {
16653 action: 'pdfjs.image.icon_click',
16654 },
16655 },
16656 },
16657 {
16658 element: options.editorSignatureButton,
16659 eventName: 'switchannotationeditormode',
16660 eventDetails: {
16661 get mode() {
16662 const { classList } = options.editorSignatureButton;
16663 return classList.contains('toggled')
16664 ? AnnotationEditorType.NONE
16665 : AnnotationEditorType.SIGNATURE;
16666 },
16667 },
16668 },
16669 ];
16670 this.#bindListeners(buttons);
16671 this.#updateToolbarDensity({
16672 value: toolbarDensity,
16673 });
16674 this.reset();
16675 }
16676 #updateToolbarDensity({ value }) {
16677 let name = 'normal';
16678 switch (value) {
16679 case 1:
16680 name = 'compact';
16681 break;
16682 case 2:
16683 name = 'touch';
16684 break;
16685 }
16686 document.documentElement.setAttribute('data-toolbar-density', name);
16687 }
16688 setPageNumber(pageNumber, pageLabel) {
16689 this.pageNumber = pageNumber;
16690 this.pageLabel = pageLabel;
16691 this.#updateUIState(false);
16692 }
16693 setPagesCount(pagesCount, hasPageLabels) {
16694 this.pagesCount = pagesCount;
16695 this.hasPageLabels = hasPageLabels;
16696 this.#updateUIState(true);
16697 }
16698 setPageScale(pageScaleValue, pageScale) {
16699 this.pageScaleValue = (pageScaleValue || pageScale).toString();
16700 this.pageScale = pageScale;
16701 this.#updateUIState(false);
16702 }
16703 reset() {
16704 this.#colorPicker = null;
16705 this.pageNumber = 0;
16706 this.pageLabel = null;
16707 this.hasPageLabels = false;
16708 this.pagesCount = 0;
16709 this.pageScaleValue = DEFAULT_SCALE_VALUE;
16710 this.pageScale = DEFAULT_SCALE;
16711 this.#updateUIState(true);
16712 this.updateLoadingIndicatorState();
16713 this.#editorModeChanged({
16714 mode: AnnotationEditorType.DISABLE,
16715 });
16716 }
16717 #bindListeners(buttons) {
16718 const { eventBus } = this;
16719 const { editorHighlightColorPicker, editorHighlightButton, pageNumber, scaleSelect } =
16720 this.#opts;
16721 const self = this;
16722 for (const { element, eventName, eventDetails, telemetry } of buttons) {
16723 element.addEventListener('click', (evt) => {
16724 if (eventName !== null) {
16725 eventBus.dispatch(eventName, {
16726 source: this,
16727 ...eventDetails,
16728 isFromKeyboard: evt.detail === 0,
16729 });
16730 }
16731 if (telemetry) {
16732 eventBus.dispatch('reporttelemetry', {
16733 source: this,
16734 details: telemetry,
16735 });
16736 }
16737 });
16738 }
16739 pageNumber.addEventListener('click', function () {
16740 this.select();
16741 });
16742 pageNumber.addEventListener('change', function () {
16743 eventBus.dispatch('pagenumberchanged', {
16744 source: self,
16745 value: this.value,
16746 });
16747 });
16748 scaleSelect.addEventListener('change', function () {
16749 if (this.value === 'custom') {
16750 return;
16751 }
16752 eventBus.dispatch('scalechanged', {
16753 source: self,
16754 value: this.value,
16755 });
16756 });
16757 scaleSelect.addEventListener('click', function ({ target }) {
16758 if (this.value === self.pageScaleValue && target.tagName.toUpperCase() === 'OPTION') {
16759 this.blur();
16760 }
16761 });
16762 scaleSelect.oncontextmenu = noContextMenu;
16763 eventBus._on('annotationeditormodechanged', this.#editorModeChanged.bind(this));
16764 eventBus._on('showannotationeditorui', ({ mode }) => {
16765 switch (mode) {
16766 case AnnotationEditorType.HIGHLIGHT:
16767 editorHighlightButton.click();
16768 break;
16769 }
16770 });
16771 eventBus._on('toolbardensity', this.#updateToolbarDensity.bind(this));
16772 if (editorHighlightColorPicker) {
16773 eventBus._on('annotationeditoruimanager', ({ uiManager }) => {
16774 const cp = (this.#colorPicker = new ColorPicker({
16775 uiManager,
16776 }));
16777 uiManager.setMainHighlightColorPicker(cp);
16778 editorHighlightColorPicker.append(cp.renderMainDropdown());
16779 });
16780 eventBus._on('mainhighlightcolorpickerupdatecolor', ({ value }) => {
16781 this.#colorPicker?.updateColor(value);
16782 });
16783 }
16784 }
16785 #editorModeChanged({ mode }) {
16786 const {
16787 editorCommentButton,
16788 editorCommentParamsToolbar,
16789 editorFreeTextButton,
16790 editorFreeTextParamsToolbar,
16791 editorHighlightButton,
16792 editorHighlightParamsToolbar,
16793 editorInkButton,
16794 editorInkParamsToolbar,
16795 editorStampButton,
16796 editorStampParamsToolbar,
16797 editorSignatureButton,
16798 editorSignatureParamsToolbar,
16799 } = this.#opts;
16800 toggleExpandedBtn(
16801 editorCommentButton,
16802 mode === AnnotationEditorType.POPUP,
16803 editorCommentParamsToolbar
16804 );
16805 toggleExpandedBtn(
16806 editorFreeTextButton,
16807 mode === AnnotationEditorType.FREETEXT,
16808 editorFreeTextParamsToolbar
16809 );
16810 toggleExpandedBtn(
16811 editorHighlightButton,
16812 mode === AnnotationEditorType.HIGHLIGHT,
16813 editorHighlightParamsToolbar
16814 );
16815 toggleExpandedBtn(editorInkButton, mode === AnnotationEditorType.INK, editorInkParamsToolbar);
16816 toggleExpandedBtn(
16817 editorStampButton,
16818 mode === AnnotationEditorType.STAMP,
16819 editorStampParamsToolbar
16820 );
16821 toggleExpandedBtn(
16822 editorSignatureButton,
16823 mode === AnnotationEditorType.SIGNATURE,
16824 editorSignatureParamsToolbar
16825 );
16826 editorCommentButton.disabled =
16827 editorFreeTextButton.disabled =
16828 editorHighlightButton.disabled =
16829 editorInkButton.disabled =
16830 editorStampButton.disabled =
16831 editorSignatureButton.disabled =
16832 mode === AnnotationEditorType.DISABLE;
16833 }
16834 #updateUIState(resetNumPages = false) {
16835 const { pageNumber, pagesCount, pageScaleValue, pageScale } = this;
16836 const opts = this.#opts;
16837 if (resetNumPages) {
16838 if (this.hasPageLabels) {
16839 opts.pageNumber.type = 'text';
16840 opts.numPages.setAttribute('data-l10n-id', 'pdfjs-page-of-pages');
16841 } else {
16842 opts.pageNumber.type = 'number';
16843 opts.numPages.setAttribute('data-l10n-id', 'pdfjs-of-pages');
16844 opts.numPages.setAttribute(
16845 'data-l10n-args',
16846 JSON.stringify({
16847 pagesCount,
16848 })
16849 );
16850 }
16851 opts.pageNumber.max = pagesCount;
16852 }
16853 if (this.hasPageLabels) {
16854 opts.pageNumber.value = this.pageLabel;
16855 opts.numPages.setAttribute(
16856 'data-l10n-args',
16857 JSON.stringify({
16858 pageNumber,
16859 pagesCount,
16860 })
16861 );
16862 } else {
16863 opts.pageNumber.value = pageNumber;
16864 }
16865 opts.previous.disabled = pageNumber <= 1;
16866 opts.next.disabled = pageNumber >= pagesCount;
16867 opts.zoomOut.disabled = pageScale <= MIN_SCALE;
16868 opts.zoomIn.disabled = pageScale >= MAX_SCALE;
16869 let predefinedValueFound = false;
16870 for (const option of opts.scaleSelect.options) {
16871 if (option.value !== pageScaleValue) {
16872 option.selected = false;
16873 continue;
16874 }
16875 option.selected = true;
16876 predefinedValueFound = true;
16877 }
16878 if (!predefinedValueFound) {
16879 opts.customScaleOption.selected = true;
16880 opts.customScaleOption.setAttribute(
16881 'data-l10n-args',
16882 JSON.stringify({
16883 scale: Math.round(pageScale * 10000) / 100,
16884 })
16885 );
16886 }
16887 }
16888 updateLoadingIndicatorState(loading = false) {
16889 const { pageNumber } = this.#opts;
16890 pageNumber.classList.toggle('loading', loading);
16891 }
16892} // ./web/view_history.js
16893
16894const DEFAULT_VIEW_HISTORY_CACHE_SIZE = 20;
16895class ViewHistory {
16896 constructor(fingerprint, cacheSize = DEFAULT_VIEW_HISTORY_CACHE_SIZE) {
16897 this.fingerprint = fingerprint;
16898 this.cacheSize = cacheSize;
16899 this._initializedPromise = this._readFromStorage().then((databaseStr) => {
16900 const database = JSON.parse(databaseStr || '{}');
16901 let index = -1;
16902 if (!Array.isArray(database.files)) {
16903 database.files = [];
16904 } else {
16905 while (database.files.length >= this.cacheSize) {
16906 database.files.shift();
16907 }
16908 for (let i = 0, ii = database.files.length; i < ii; i++) {
16909 const branch = database.files[i];
16910 if (branch.fingerprint === this.fingerprint) {
16911 index = i;
16912 break;
16913 }
16914 }
16915 }
16916 if (index === -1) {
16917 index =
16918 database.files.push({
16919 fingerprint: this.fingerprint,
16920 }) - 1;
16921 }
16922 this.file = database.files[index];
16923 this.database = database;
16924 });
16925 }
16926 async _writeToStorage() {
16927 const databaseStr = JSON.stringify(this.database);
16928 localStorage.setItem('pdfjs.history', databaseStr);
16929 }
16930 async _readFromStorage() {
16931 return localStorage.getItem('pdfjs.history');
16932 }
16933 async set(name, val) {
16934 await this._initializedPromise;
16935 this.file[name] = val;
16936 return this._writeToStorage();
16937 }
16938 async setMultiple(properties) {
16939 await this._initializedPromise;
16940 for (const name in properties) {
16941 this.file[name] = properties[name];
16942 }
16943 return this._writeToStorage();
16944 }
16945 async get(name, defaultValue) {
16946 await this._initializedPromise;
16947 const val = this.file[name];
16948 return val !== undefined ? val : defaultValue;
16949 }
16950 async getMultiple(properties) {
16951 await this._initializedPromise;
16952 const values = Object.create(null);
16953 for (const name in properties) {
16954 const val = this.file[name];
16955 values[name] = val !== undefined ? val : properties[name];
16956 }
16957 return values;
16958 }
16959} // ./web/views_manager.js
16960
16961const SIDEBAR_WIDTH_VAR = '--viewsManager-width';
16962const SIDEBAR_RESIZING_CLASS = 'viewsManagerResizing';
16963const UI_NOTIFICATION_CLASS = 'pdfSidebarNotification';
16964class ViewsManager extends Sidebar {
16965 static #l10nDescription = null;
16966 constructor({
16967 elements: {
16968 outerContainer,
16969 sidebarContainer,
16970 toggleButton,
16971 resizer,
16972 thumbnailButton,
16973 outlineButton,
16974 attachmentsButton,
16975 layersButton,
16976 thumbnailsView,
16977 outlinesView,
16978 attachmentsView,
16979 layersView,
16980 viewsManagerCurrentOutlineButton,
16981 viewsManagerSelectorButton,
16982 viewsManagerSelectorOptions,
16983 viewsManagerHeaderLabel,
16984 },
16985 eventBus,
16986 l10n,
16987 }) {
16988 super(
16989 {
16990 sidebar: sidebarContainer,
16991 resizer,
16992 toggleButton,
16993 },
16994 l10n.getDirection() === 'ltr',
16995 false
16996 );
16997 this.isOpen = false;
16998 this.active = SidebarView.THUMBS;
16999 this.isInitialViewSet = false;
17000 this.isInitialEventDispatched = false;
17001 this.onToggled = null;
17002 this.onUpdateThumbnails = null;
17003 this.outerContainer = outerContainer;
17004 this.sidebarContainer = sidebarContainer;
17005 this.toggleButton = toggleButton;
17006 this.resizer = resizer;
17007 this.thumbnailButton = thumbnailButton;
17008 this.outlineButton = outlineButton;
17009 this.attachmentsButton = attachmentsButton;
17010 this.layersButton = layersButton;
17011 this.thumbnailsView = thumbnailsView;
17012 this.outlinesView = outlinesView;
17013 this.attachmentsView = attachmentsView;
17014 this.layersView = layersView;
17015 this.viewsManagerCurrentOutlineButton = viewsManagerCurrentOutlineButton;
17016 this.viewsManagerHeaderLabel = viewsManagerHeaderLabel;
17017 this.eventBus = eventBus;
17018 this.menu = new Menu(viewsManagerSelectorOptions, viewsManagerSelectorButton, [
17019 thumbnailButton,
17020 outlineButton,
17021 attachmentsButton,
17022 layersButton,
17023 ]);
17024 ViewsManager.#l10nDescription ||= Object.freeze({
17025 pagesTitle: 'pdfjs-views-manager-pages-title',
17026 outlinesTitle: 'pdfjs-views-manager-outlines-title',
17027 attachmentsTitle: 'pdfjs-views-manager-attachments-title',
17028 layersTitle: 'pdfjs-views-manager-layers-title',
17029 notificationButton: 'pdfjs-toggle-views-manager-notification-button',
17030 toggleButton: 'pdfjs-toggle-views-manager-button',
17031 });
17032 this.#addEventListeners();
17033 }
17034 reset() {
17035 this.isInitialViewSet = false;
17036 this.isInitialEventDispatched = false;
17037 this.#hideUINotification(true);
17038 this.switchView(SidebarView.THUMBS);
17039 this.outlineButton.disabled =
17040 this.attachmentsButton.disabled =
17041 this.layersButton.disabled =
17042 false;
17043 this.viewsManagerCurrentOutlineButton.disabled = true;
17044 }
17045 get visibleView() {
17046 return this.isOpen ? this.active : SidebarView.NONE;
17047 }
17048 setInitialView(view = SidebarView.NONE) {
17049 if (this.isInitialViewSet) {
17050 return;
17051 }
17052 this.isInitialViewSet = true;
17053 if (view === SidebarView.NONE || view === SidebarView.UNKNOWN) {
17054 this.#dispatchEvent();
17055 return;
17056 }
17057 this.switchView(view, true);
17058 if (!this.isInitialEventDispatched) {
17059 this.#dispatchEvent();
17060 }
17061 }
17062 switchView(view, forceOpen = false) {
17063 const isViewChanged = view !== this.active;
17064 let forceRendering = false;
17065 let titleL10nId = null;
17066 switch (view) {
17067 case SidebarView.NONE:
17068 if (this.isOpen) {
17069 this.close();
17070 }
17071 return;
17072 case SidebarView.THUMBS:
17073 titleL10nId = 'pagesTitle';
17074 if (this.isOpen && isViewChanged) {
17075 forceRendering = true;
17076 }
17077 break;
17078 case SidebarView.OUTLINE:
17079 titleL10nId = 'outlinesTitle';
17080 if (this.outlineButton.disabled) {
17081 return;
17082 }
17083 break;
17084 case SidebarView.ATTACHMENTS:
17085 titleL10nId = 'attachmentsTitle';
17086 if (this.attachmentsButton.disabled) {
17087 return;
17088 }
17089 break;
17090 case SidebarView.LAYERS:
17091 titleL10nId = 'layersTitle';
17092 if (this.layersButton.disabled) {
17093 return;
17094 }
17095 break;
17096 default:
17097 console.error(`PDFSidebar.switchView: "${view}" is not a valid view.`);
17098 return;
17099 }
17100 this.viewsManagerCurrentOutlineButton.hidden = view !== SidebarView.OUTLINE;
17101 this.viewsManagerHeaderLabel.setAttribute(
17102 'data-l10n-id',
17103 ViewsManager.#l10nDescription[titleL10nId] || ''
17104 );
17105 this.active = view;
17106 toggleSelectedBtn(this.thumbnailButton, view === SidebarView.THUMBS, this.thumbnailsView);
17107 toggleSelectedBtn(this.outlineButton, view === SidebarView.OUTLINE, this.outlinesView);
17108 toggleSelectedBtn(
17109 this.attachmentsButton,
17110 view === SidebarView.ATTACHMENTS,
17111 this.attachmentsView
17112 );
17113 toggleSelectedBtn(this.layersButton, view === SidebarView.LAYERS, this.layersView);
17114 if (forceOpen && !this.isOpen) {
17115 this.open();
17116 return;
17117 }
17118 if (forceRendering) {
17119 this.onUpdateThumbnails();
17120 this.onToggled();
17121 }
17122 if (isViewChanged) {
17123 this.#dispatchEvent();
17124 }
17125 }
17126 open() {
17127 if (this.isOpen) {
17128 return;
17129 }
17130 this.isOpen = true;
17131 this.onResizing(this.width);
17132 this._sidebar.hidden = false;
17133 toggleExpandedBtn(this.toggleButton, true);
17134 this.switchView(this.active);
17135 queueMicrotask(() => {
17136 this.outerContainer.classList.add('viewsManagerMoving', 'viewsManagerOpen');
17137 });
17138 if (this.active === SidebarView.THUMBS) {
17139 this.onUpdateThumbnails();
17140 }
17141 this.onToggled();
17142 this.#dispatchEvent();
17143 this.#hideUINotification();
17144 }
17145 close(evt = null) {
17146 if (!this.isOpen) {
17147 return;
17148 }
17149 this.isOpen = false;
17150 this._sidebar.hidden = true;
17151 toggleExpandedBtn(this.toggleButton, false);
17152 this.outerContainer.classList.add('viewsManagerMoving');
17153 this.outerContainer.classList.remove('viewsManagerOpen');
17154 this.onToggled();
17155 this.#dispatchEvent();
17156 if (evt?.detail > 0) {
17157 this.toggleButton.blur();
17158 }
17159 }
17160 toggle(evt = null) {
17161 super.toggle();
17162 if (this.isOpen) {
17163 this.close(evt);
17164 } else {
17165 this.open();
17166 }
17167 }
17168 #dispatchEvent() {
17169 if (this.isInitialViewSet) {
17170 this.isInitialEventDispatched ||= true;
17171 }
17172 this.eventBus.dispatch('sidebarviewchanged', {
17173 source: this,
17174 view: this.visibleView,
17175 });
17176 }
17177 #showUINotification() {
17178 this.toggleButton.setAttribute(
17179 'data-l10n-id',
17180 ViewsManager.#l10nDescription.notificationButton
17181 );
17182 if (!this.isOpen) {
17183 this.toggleButton.classList.add(UI_NOTIFICATION_CLASS);
17184 }
17185 }
17186 #hideUINotification(reset = false) {
17187 if (this.isOpen || reset) {
17188 this.toggleButton.classList.remove(UI_NOTIFICATION_CLASS);
17189 }
17190 if (reset) {
17191 this.toggleButton.setAttribute('data-l10n-id', ViewsManager.#l10nDescription.toggleButton);
17192 }
17193 }
17194 #addEventListeners() {
17195 const { eventBus, outerContainer } = this;
17196 this.sidebarContainer.addEventListener('transitionend', (evt) => {
17197 if (evt.target === this.sidebarContainer) {
17198 outerContainer.classList.remove('viewsManagerMoving');
17199 eventBus.dispatch('resize', {
17200 source: this,
17201 });
17202 }
17203 });
17204 this.thumbnailButton.addEventListener('click', () => {
17205 this.switchView(SidebarView.THUMBS);
17206 });
17207 this.outlineButton.addEventListener('click', () => {
17208 this.switchView(SidebarView.OUTLINE);
17209 });
17210 this.outlineButton.addEventListener('dblclick', () => {
17211 eventBus.dispatch('toggleoutlinetree', {
17212 source: this,
17213 });
17214 });
17215 this.attachmentsButton.addEventListener('click', () => {
17216 this.switchView(SidebarView.ATTACHMENTS);
17217 });
17218 this.layersButton.addEventListener('click', () => {
17219 this.switchView(SidebarView.LAYERS);
17220 });
17221 this.layersButton.addEventListener('dblclick', () => {
17222 eventBus.dispatch('resetlayers', {
17223 source: this,
17224 });
17225 });
17226 this.viewsManagerCurrentOutlineButton.addEventListener('click', () => {
17227 eventBus.dispatch('currentoutlineitem', {
17228 source: this,
17229 });
17230 });
17231 const onTreeLoaded = (count, button, view) => {
17232 button.disabled = !count;
17233 if (count) {
17234 this.#showUINotification();
17235 } else if (this.active === view) {
17236 this.switchView(SidebarView.THUMBS);
17237 }
17238 };
17239 eventBus._on('outlineloaded', (evt) => {
17240 onTreeLoaded(evt.outlineCount, this.outlineButton, SidebarView.OUTLINE);
17241 evt.currentOutlineItemPromise.then((enabled) => {
17242 if (!this.isInitialViewSet) {
17243 return;
17244 }
17245 this.viewsManagerCurrentOutlineButton.disabled = !enabled;
17246 });
17247 });
17248 eventBus._on('attachmentsloaded', (evt) => {
17249 onTreeLoaded(evt.attachmentsCount, this.attachmentsButton, SidebarView.ATTACHMENTS);
17250 });
17251 eventBus._on('layersloaded', (evt) => {
17252 onTreeLoaded(evt.layersCount, this.layersButton, SidebarView.LAYERS);
17253 });
17254 eventBus._on('presentationmodechanged', (evt) => {
17255 if (evt.state === PresentationModeState.NORMAL && this.visibleView === SidebarView.THUMBS) {
17256 this.onUpdateThumbnails();
17257 }
17258 });
17259 }
17260 onStartResizing() {
17261 this.outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
17262 }
17263 onStopResizing() {
17264 this.eventBus.dispatch('resize', {
17265 source: this,
17266 });
17267 this.outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
17268 }
17269 onResizing(newWidth) {
17270 docStyle.setProperty(SIDEBAR_WIDTH_VAR, `${newWidth}px`);
17271 }
17272} // ./web/app.js
17273
17274const FORCE_PAGES_LOADED_TIMEOUT = 10000;
17275const ViewOnLoad = {
17276 UNKNOWN: -1,
17277 PREVIOUS: 0,
17278 INITIAL: 1,
17279};
17280const PDFViewerApplication = {
17281 initialBookmark: document.location.hash.substring(1),
17282 _initializedCapability: {
17283 ...Promise.withResolvers(),
17284 settled: false,
17285 },
17286 appConfig: null,
17287 pdfDocument: null,
17288 pdfLoadingTask: null,
17289 printService: null,
17290 pdfViewer: null,
17291 pdfThumbnailViewer: null,
17292 pdfRenderingQueue: null,
17293 pdfPresentationMode: null,
17294 pdfDocumentProperties: null,
17295 pdfLinkService: null,
17296 pdfTextExtractor: null,
17297 pdfHistory: null,
17298 viewsManager: null,
17299 pdfOutlineViewer: null,
17300 pdfAttachmentViewer: null,
17301 pdfLayerViewer: null,
17302 pdfCursorTools: null,
17303 pdfScriptingManager: null,
17304 store: null,
17305 downloadManager: null,
17306 overlayManager: null,
17307 preferences: new Preferences(),
17308 toolbar: null,
17309 secondaryToolbar: null,
17310 eventBus: null,
17311 l10n: null,
17312 annotationEditorParams: null,
17313 imageAltTextSettings: null,
17314 isInitialViewSet: false,
17315 isViewerEmbedded: window.parent !== window,
17316 url: '',
17317 baseUrl: '',
17318 mlManager: null,
17319 _downloadUrl: '',
17320 _eventBusAbortController: null,
17321 _windowAbortController: null,
17322 _globalAbortController: new AbortController(),
17323 documentInfo: null,
17324 metadata: null,
17325 _contentDispositionFilename: null,
17326 _contentLength: null,
17327 _saveInProgress: false,
17328 _wheelUnusedTicks: 0,
17329 _wheelUnusedFactor: 1,
17330 _touchManager: null,
17331 _touchUnusedTicks: 0,
17332 _touchUnusedFactor: 1,
17333 _PDFBug: null,
17334 _hasAnnotationEditors: false,
17335 _title: document.title,
17336 _printAnnotationStoragePromise: null,
17337 _isCtrlKeyDown: false,
17338 _caretBrowsing: null,
17339 _isScrolling: false,
17340 editorUndoBar: null,
17341 _printPermissionPromise: null,
17342 async initialize(appConfig) {
17343 this.appConfig = appConfig;
17344 try {
17345 await this.preferences.initializedPromise;
17346 } catch (ex) {
17347 console.error('initialize:', ex);
17348 }
17349 if (AppOptions.get('pdfBugEnabled')) {
17350 await this._parseHashParams();
17351 }
17352 let mode;
17353 switch (AppOptions.get('viewerCssTheme')) {
17354 case 1:
17355 mode = 'light';
17356 break;
17357 case 2:
17358 mode = 'dark';
17359 break;
17360 }
17361 if (mode) {
17362 docStyle.setProperty('color-scheme', mode);
17363 }
17364 this.l10n = await this.externalServices.createL10n();
17365 document.getElementsByTagName('html')[0].dir = this.l10n.getDirection();
17366 this.l10n.translate(appConfig.appContainer || document.documentElement);
17367 if (this.isViewerEmbedded && AppOptions.get('externalLinkTarget') === LinkTarget.NONE) {
17368 AppOptions.set('externalLinkTarget', LinkTarget.TOP);
17369 }
17370 await this._initializeViewerComponents();
17371 this.pdfTextExtractor = new PdfTextExtractor(this.externalServices);
17372 this.bindEvents();
17373 this.bindWindowEvents();
17374 this._initializedCapability.settled = true;
17375 this._initializedCapability.resolve();
17376 },
17377 async _parseHashParams() {
17378 const hash = document.location.hash.substring(1);
17379 if (!hash) {
17380 return;
17381 }
17382 const { mainContainer, viewerContainer } = this.appConfig,
17383 params = parseQueryString(hash);
17384 const loadPDFBug = async () => {
17385 if (this._PDFBug) {
17386 return;
17387 }
17388 const { PDFBug } = await import(
17389 /*webpackIgnore: true*/
17390 /*@vite-ignore*/
17391 AppOptions.get('debuggerSrc')
17392 );
17393 this._PDFBug = PDFBug;
17394 };
17395 if (params.get('disableworker') === 'true') {
17396 try {
17397 GlobalWorkerOptions.workerSrc ||= AppOptions.get('workerSrc');
17398 await import(
17399 /*webpackIgnore: true*/
17400 /*@vite-ignore*/
17401 PDFWorker.workerSrc
17402 );
17403 AppOptions.set('workerPort', null);
17404 } catch (ex) {
17405 console.error('_parseHashParams:', ex);
17406 }
17407 }
17408 if (params.has('textlayer')) {
17409 switch (params.get('textlayer')) {
17410 case 'off':
17411 AppOptions.set('textLayerMode', TextLayerMode.DISABLE);
17412 break;
17413 case 'visible':
17414 case 'shadow':
17415 case 'hover':
17416 viewerContainer.classList.add(`textLayer-${params.get('textlayer')}`);
17417 try {
17418 await loadPDFBug();
17419 this._PDFBug.loadCSS();
17420 } catch (ex) {
17421 console.error('_parseHashParams:', ex);
17422 }
17423 break;
17424 }
17425 }
17426 if (params.has('pdfbug')) {
17427 const enabled = params.get('pdfbug').split(',');
17428 try {
17429 await loadPDFBug();
17430 this._PDFBug.init(mainContainer, enabled);
17431 } catch (ex) {
17432 console.error('_parseHashParams:', ex);
17433 }
17434 const debugOpts = {
17435 pdfBug: true,
17436 fontExtraProperties: true,
17437 };
17438 if (globalThis.StepperManager?.enabled) {
17439 debugOpts.minDurationToUpdateCanvas = 0;
17440 }
17441 AppOptions.setAll(debugOpts);
17442 }
17443 if (params.has('locale')) {
17444 AppOptions.set('localeProperties', {
17445 lang: params.get('locale'),
17446 });
17447 }
17448 const opts = {
17449 disableAutoFetch: (x) => x === 'true',
17450 disableFontFace: (x) => x === 'true',
17451 disableHistory: (x) => x === 'true',
17452 disableRange: (x) => x === 'true',
17453 disableStream: (x) => x === 'true',
17454 verbosity: (x) => x | 0,
17455 };
17456 for (const name in opts) {
17457 const check = opts[name],
17458 key = name.toLowerCase();
17459 if (params.has(key)) {
17460 AppOptions.set(name, check(params.get(key)));
17461 }
17462 }
17463 },
17464 async _initializeViewerComponents() {
17465 const { appConfig, externalServices, l10n, mlManager } = this;
17466 const abortSignal = this._globalAbortController.signal;
17467 const eventBus = new EventBus();
17468 this.eventBus = AppOptions.eventBus = eventBus;
17469 mlManager?.setEventBus(eventBus, abortSignal);
17470 const overlayManager = (this.overlayManager = new OverlayManager());
17471 const renderingQueue = (this.pdfRenderingQueue = new PDFRenderingQueue());
17472 renderingQueue.onIdle = this._cleanup.bind(this);
17473 const linkService = (this.pdfLinkService = new PDFLinkService({
17474 eventBus,
17475 externalLinkTarget: AppOptions.get('externalLinkTarget'),
17476 externalLinkRel: AppOptions.get('externalLinkRel'),
17477 ignoreDestinationZoom: AppOptions.get('ignoreDestinationZoom'),
17478 }));
17479 const downloadManager = (this.downloadManager = new DownloadManager());
17480 const findController = (this.findController = new PDFFindController({
17481 linkService,
17482 eventBus,
17483 updateMatchesCountOnProgress: true,
17484 }));
17485 const pdfScriptingManager = (this.pdfScriptingManager = new PDFScriptingManager({
17486 eventBus,
17487 externalServices,
17488 docProperties: this._scriptingDocProperties.bind(this),
17489 }));
17490 const container = appConfig.mainContainer,
17491 viewer = appConfig.viewerContainer;
17492 const annotationEditorMode = AppOptions.get('annotationEditorMode');
17493 const hasForcedColors =
17494 AppOptions.get('forcePageColors') || window.matchMedia('(forced-colors: active)').matches;
17495 const pageColors = hasForcedColors
17496 ? {
17497 background: AppOptions.get('pageColorsBackground'),
17498 foreground: AppOptions.get('pageColorsForeground'),
17499 }
17500 : null;
17501 let altTextManager;
17502 if (AppOptions.get('enableUpdatedAddImage')) {
17503 altTextManager = appConfig.newAltTextDialog
17504 ? new NewAltTextManager(appConfig.newAltTextDialog, overlayManager, eventBus)
17505 : null;
17506 } else {
17507 altTextManager = appConfig.altTextDialog
17508 ? new AltTextManager(appConfig.altTextDialog, container, overlayManager, eventBus)
17509 : null;
17510 }
17511 if (appConfig.editorUndoBar) {
17512 this.editorUndoBar = new EditorUndoBar(appConfig.editorUndoBar, eventBus);
17513 }
17514 const signatureManager =
17515 AppOptions.get('enableSignatureEditor') && appConfig.addSignatureDialog
17516 ? new SignatureManager(
17517 appConfig.addSignatureDialog,
17518 appConfig.editSignatureDialog,
17519 appConfig.annotationEditorParams?.editorSignatureAddSignature || null,
17520 overlayManager,
17521 l10n,
17522 externalServices.createSignatureStorage(eventBus, abortSignal),
17523 eventBus
17524 )
17525 : null;
17526 const ltr = appConfig.viewerContainer
17527 ? getComputedStyle(appConfig.viewerContainer).direction === 'ltr'
17528 : true;
17529 const commentManager =
17530 AppOptions.get('enableComment') && appConfig.editCommentDialog
17531 ? new CommentManager(
17532 appConfig.editCommentDialog,
17533 {
17534 learnMoreUrl: AppOptions.get('commentLearnMoreUrl'),
17535 sidebar: appConfig.annotationEditorParams?.editorCommentsSidebar || null,
17536 sidebarResizer:
17537 appConfig.annotationEditorParams?.editorCommentsSidebarResizer || null,
17538 commentsList: appConfig.annotationEditorParams?.editorCommentsSidebarList || null,
17539 commentCount: appConfig.annotationEditorParams?.editorCommentsSidebarCount || null,
17540 sidebarTitle: appConfig.annotationEditorParams?.editorCommentsSidebarTitle || null,
17541 closeButton:
17542 appConfig.annotationEditorParams?.editorCommentsSidebarCloseButton || null,
17543 commentToolbarButton: appConfig.toolbar?.editorCommentButton || null,
17544 },
17545 eventBus,
17546 linkService,
17547 overlayManager,
17548 ltr,
17549 hasForcedColors
17550 )
17551 : null;
17552 const enableHWA = AppOptions.get('enableHWA'),
17553 maxCanvasPixels = AppOptions.get('maxCanvasPixels'),
17554 maxCanvasDim = AppOptions.get('maxCanvasDim'),
17555 capCanvasAreaFactor = AppOptions.get('capCanvasAreaFactor');
17556 const pdfViewer = (this.pdfViewer = new PDFViewer({
17557 container,
17558 viewer,
17559 viewerAlert: appConfig.viewerAlert,
17560 eventBus,
17561 renderingQueue,
17562 linkService,
17563 downloadManager,
17564 altTextManager,
17565 commentManager,
17566 signatureManager,
17567 editorUndoBar: this.editorUndoBar,
17568 findController,
17569 scriptingManager: AppOptions.get('enableScripting') && pdfScriptingManager,
17570 l10n,
17571 textLayerMode: AppOptions.get('textLayerMode'),
17572 annotationMode: AppOptions.get('annotationMode'),
17573 annotationEditorMode,
17574 annotationEditorHighlightColors: AppOptions.get('highlightEditorColors'),
17575 enableHighlightFloatingButton: AppOptions.get('enableHighlightFloatingButton'),
17576 enableUpdatedAddImage: AppOptions.get('enableUpdatedAddImage'),
17577 enableNewAltTextWhenAddingImage: AppOptions.get('enableNewAltTextWhenAddingImage'),
17578 imageResourcesPath: AppOptions.get('imageResourcesPath'),
17579 enablePrintAutoRotate: AppOptions.get('enablePrintAutoRotate'),
17580 maxCanvasPixels,
17581 maxCanvasDim,
17582 capCanvasAreaFactor,
17583 enableDetailCanvas: AppOptions.get('enableDetailCanvas'),
17584 enablePermissions: AppOptions.get('enablePermissions'),
17585 enableOptimizedPartialRendering: AppOptions.get('enableOptimizedPartialRendering'),
17586 pageColors,
17587 mlManager,
17588 abortSignal,
17589 enableHWA,
17590 supportsPinchToZoom: this.supportsPinchToZoom,
17591 enableAutoLinking: AppOptions.get('enableAutoLinking'),
17592 minDurationToUpdateCanvas: AppOptions.get('minDurationToUpdateCanvas'),
17593 }));
17594 renderingQueue.setViewer(pdfViewer);
17595 linkService.setViewer(pdfViewer);
17596 pdfScriptingManager.setViewer(pdfViewer);
17597 if (appConfig.viewsManager?.thumbnailsView) {
17598 this.pdfThumbnailViewer = new PDFThumbnailViewer({
17599 container: appConfig.viewsManager.thumbnailsView,
17600 eventBus,
17601 renderingQueue,
17602 linkService,
17603 maxCanvasPixels,
17604 maxCanvasDim,
17605 pageColors,
17606 abortSignal,
17607 enableHWA,
17608 enableSplitMerge: AppOptions.get('enableSplitMerge'),
17609 manageMenu: appConfig.viewsManager.manageMenu,
17610 });
17611 renderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
17612 }
17613 if (!this.isViewerEmbedded && !AppOptions.get('disableHistory')) {
17614 this.pdfHistory = new PDFHistory({
17615 linkService,
17616 eventBus,
17617 });
17618 linkService.setHistory(this.pdfHistory);
17619 }
17620 if (!this.supportsIntegratedFind && appConfig.findBar) {
17621 this.findBar = new PDFFindBar(appConfig.findBar, appConfig.principalContainer, eventBus);
17622 }
17623 if (appConfig.annotationEditorParams) {
17624 if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
17625 const editorSignatureButton = appConfig.toolbar?.editorSignatureButton;
17626 if (editorSignatureButton && AppOptions.get('enableSignatureEditor')) {
17627 editorSignatureButton.parentElement.hidden = false;
17628 }
17629 const editorCommentButton = appConfig.toolbar?.editorCommentButton;
17630 if (editorCommentButton && AppOptions.get('enableComment')) {
17631 editorCommentButton.parentElement.hidden = false;
17632 }
17633 this.annotationEditorParams = new AnnotationEditorParams(
17634 appConfig.annotationEditorParams,
17635 eventBus
17636 );
17637 } else {
17638 for (const id of ['editorModeButtons', 'editorModeSeparator']) {
17639 document.getElementById(id)?.classList.add('hidden');
17640 }
17641 }
17642 }
17643 if (mlManager && appConfig.secondaryToolbar?.imageAltTextSettingsButton) {
17644 this.imageAltTextSettings = new ImageAltTextSettings(
17645 appConfig.altTextSettingsDialog,
17646 overlayManager,
17647 eventBus,
17648 mlManager
17649 );
17650 }
17651 if (appConfig.documentProperties) {
17652 this.pdfDocumentProperties = new PDFDocumentProperties(
17653 appConfig.documentProperties,
17654 overlayManager,
17655 eventBus,
17656 l10n,
17657 () => this._docFilename,
17658 () => this._docTitle
17659 );
17660 }
17661 if (appConfig.secondaryToolbar?.cursorHandToolButton) {
17662 this.pdfCursorTools = new PDFCursorTools({
17663 container,
17664 eventBus,
17665 cursorToolOnLoad: AppOptions.get('cursorToolOnLoad'),
17666 });
17667 }
17668 if (appConfig.toolbar) {
17669 this.toolbar = new Toolbar(appConfig.toolbar, eventBus, AppOptions.get('toolbarDensity'));
17670 }
17671 if (appConfig.secondaryToolbar) {
17672 if (AppOptions.get('enableAltText')) {
17673 appConfig.secondaryToolbar.imageAltTextSettingsButton?.classList.remove('hidden');
17674 appConfig.secondaryToolbar.imageAltTextSettingsSeparator?.classList.remove('hidden');
17675 }
17676 this.secondaryToolbar = new SecondaryToolbar(appConfig.secondaryToolbar, eventBus);
17677 }
17678 if (this.supportsFullscreen && appConfig.secondaryToolbar?.presentationModeButton) {
17679 this.pdfPresentationMode = new PDFPresentationMode({
17680 container,
17681 pdfViewer,
17682 eventBus,
17683 });
17684 }
17685 if (appConfig.passwordOverlay) {
17686 this.passwordPrompt = new PasswordPrompt(
17687 appConfig.passwordOverlay,
17688 overlayManager,
17689 this.isViewerEmbedded
17690 );
17691 }
17692 if (appConfig.viewsManager?.outlinesView) {
17693 this.pdfOutlineViewer = new PDFOutlineViewer({
17694 container: appConfig.viewsManager.outlinesView,
17695 eventBus,
17696 l10n,
17697 linkService,
17698 downloadManager,
17699 });
17700 }
17701 if (appConfig.viewsManager?.attachmentsView) {
17702 this.pdfAttachmentViewer = new PDFAttachmentViewer({
17703 container: appConfig.viewsManager.attachmentsView,
17704 eventBus,
17705 l10n,
17706 downloadManager,
17707 });
17708 }
17709 if (appConfig.viewsManager?.layersView) {
17710 this.pdfLayerViewer = new PDFLayerViewer({
17711 container: appConfig.viewsManager.layersView,
17712 eventBus,
17713 l10n,
17714 });
17715 }
17716 if (appConfig.viewsManager) {
17717 this.viewsManager = new ViewsManager({
17718 elements: appConfig.viewsManager,
17719 eventBus,
17720 l10n,
17721 });
17722 this.viewsManager.onToggled = this.forceRendering.bind(this);
17723 this.viewsManager.onUpdateThumbnails = () => {
17724 for (const pageView of pdfViewer.getCachedPageViews()) {
17725 if (pageView.renderingState === RenderingStates.FINISHED) {
17726 this.pdfThumbnailViewer.getThumbnail(pageView.id - 1)?.setImage(pageView);
17727 }
17728 }
17729 this.pdfThumbnailViewer.scrollThumbnailIntoView(pdfViewer.currentPageNumber);
17730 };
17731 }
17732 },
17733 async run(config) {
17734 await this.initialize(config);
17735 const { appConfig, eventBus } = this;
17736 let file;
17737 const queryString = document.location.search.substring(1);
17738 const params = parseQueryString(queryString);
17739 file = params.get('file') ?? AppOptions.get('defaultUrl');
17740 try {
17741 file = new URL(file).href;
17742 } catch {
17743 file = encodeURIComponent(file).replaceAll('%2F', '/');
17744 }
17745 validateFileURL(file);
17746 const fileInput = (this._openFileInput = document.createElement('input'));
17747 fileInput.id = 'fileInput';
17748 fileInput.hidden = true;
17749 fileInput.type = 'file';
17750 fileInput.value = null;
17751 document.body.append(fileInput);
17752 fileInput.addEventListener('change', function (evt) {
17753 const { files } = evt.target;
17754 if (!files || files.length === 0) {
17755 return;
17756 }
17757 eventBus.dispatch('fileinputchange', {
17758 source: this,
17759 fileInput: evt.target,
17760 });
17761 });
17762 appConfig.mainContainer.addEventListener('dragover', function (evt) {
17763 for (const item of evt.dataTransfer.items) {
17764 if (item.type === 'application/pdf') {
17765 evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === 'copy' ? 'copy' : 'move';
17766 stopEvent(evt);
17767 return;
17768 }
17769 }
17770 });
17771 appConfig.mainContainer.addEventListener('drop', function (evt) {
17772 if (evt.dataTransfer.files?.[0].type !== 'application/pdf') {
17773 return;
17774 }
17775 stopEvent(evt);
17776 eventBus.dispatch('fileinputchange', {
17777 source: this,
17778 fileInput: evt.dataTransfer,
17779 });
17780 });
17781 if (!AppOptions.get('supportsDocumentFonts')) {
17782 AppOptions.set('disableFontFace', true);
17783 this.l10n.get('pdfjs-web-fonts-disabled').then((msg) => {
17784 console.warn(msg);
17785 });
17786 }
17787 const togglePrintingButtons = (visible) => {
17788 appConfig.toolbar?.print?.classList.toggle('hidden', !visible);
17789 appConfig.secondaryToolbar?.printButton.classList.toggle('hidden', !visible);
17790 };
17791 if (!this.supportsPrinting) {
17792 togglePrintingButtons(false);
17793 } else {
17794 eventBus.on('printingallowed', ({ isAllowed }) => togglePrintingButtons(isAllowed));
17795 }
17796 if (!this.supportsFullscreen) {
17797 appConfig.secondaryToolbar?.presentationModeButton.classList.add('hidden');
17798 }
17799 if (this.supportsIntegratedFind) {
17800 appConfig.findBar?.toggleButton?.classList.add('hidden');
17801 }
17802 if (file) {
17803 this.open({
17804 url: file,
17805 });
17806 } else {
17807 this._hideViewBookmark();
17808 }
17809 },
17810 get externalServices() {
17811 return shadow(this, 'externalServices', new ExternalServices());
17812 },
17813 get initialized() {
17814 return this._initializedCapability.settled;
17815 },
17816 get initializedPromise() {
17817 return this._initializedCapability.promise;
17818 },
17819 updateZoom(steps, scaleFactor, origin) {
17820 if (this.pdfViewer.isInPresentationMode) {
17821 return;
17822 }
17823 this.pdfViewer.updateScale({
17824 drawingDelay: AppOptions.get('defaultZoomDelay'),
17825 steps,
17826 scaleFactor,
17827 origin,
17828 });
17829 },
17830 zoomIn() {
17831 this.updateZoom(1);
17832 },
17833 zoomOut() {
17834 this.updateZoom(-1);
17835 },
17836 zoomReset() {
17837 if (this.pdfViewer.isInPresentationMode) {
17838 return;
17839 }
17840 this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
17841 },
17842 touchPinchCallback(origin, prevDistance, distance) {
17843 if (this.supportsPinchToZoom) {
17844 const newScaleFactor = this._accumulateFactor(
17845 this.pdfViewer.currentScale,
17846 distance / prevDistance,
17847 '_touchUnusedFactor'
17848 );
17849 this.updateZoom(null, newScaleFactor, origin);
17850 } else {
17851 const PIXELS_PER_LINE_SCALE = 30;
17852 const ticks = this._accumulateTicks(
17853 (distance - prevDistance) / PIXELS_PER_LINE_SCALE,
17854 '_touchUnusedTicks'
17855 );
17856 this.updateZoom(ticks, null, origin);
17857 }
17858 },
17859 touchPinchEndCallback() {
17860 this._touchUnusedTicks = 0;
17861 this._touchUnusedFactor = 1;
17862 },
17863 get pagesCount() {
17864 return this.pdfDocument ? this.pdfDocument.numPages : 0;
17865 },
17866 get page() {
17867 return this.pdfViewer.currentPageNumber;
17868 },
17869 set page(val) {
17870 this.pdfViewer.currentPageNumber = val;
17871 },
17872 get supportsPrinting() {
17873 return shadow(
17874 this,
17875 'supportsPrinting',
17876 AppOptions.get('supportsPrinting') && PDFPrintServiceFactory.supportsPrinting
17877 );
17878 },
17879 get supportsFullscreen() {
17880 return shadow(this, 'supportsFullscreen', document.fullscreenEnabled);
17881 },
17882 get supportsPinchToZoom() {
17883 return shadow(this, 'supportsPinchToZoom', AppOptions.get('supportsPinchToZoom'));
17884 },
17885 get supportsIntegratedFind() {
17886 return shadow(this, 'supportsIntegratedFind', AppOptions.get('supportsIntegratedFind'));
17887 },
17888 get loadingBar() {
17889 const barElement = document.getElementById('loadingBar');
17890 const bar = barElement ? new ProgressBar(barElement) : null;
17891 return shadow(this, 'loadingBar', bar);
17892 },
17893 get supportsMouseWheelZoomCtrlKey() {
17894 return shadow(
17895 this,
17896 'supportsMouseWheelZoomCtrlKey',
17897 AppOptions.get('supportsMouseWheelZoomCtrlKey')
17898 );
17899 },
17900 get supportsMouseWheelZoomMetaKey() {
17901 return shadow(
17902 this,
17903 'supportsMouseWheelZoomMetaKey',
17904 AppOptions.get('supportsMouseWheelZoomMetaKey')
17905 );
17906 },
17907 get supportsCaretBrowsingMode() {
17908 return AppOptions.get('supportsCaretBrowsingMode');
17909 },
17910 moveCaret(isUp, select) {
17911 this._caretBrowsing ||= new CaretBrowsingMode(
17912 this._globalAbortController.signal,
17913 this.appConfig.mainContainer,
17914 this.appConfig.viewerContainer,
17915 this.appConfig.toolbar?.container
17916 );
17917 this._caretBrowsing.moveCaret(isUp, select);
17918 },
17919 setTitleUsingUrl(url = '', downloadUrl = null) {
17920 this.url = url;
17921 this.baseUrl = updateUrlHash(url, '', true);
17922 if (downloadUrl) {
17923 this._downloadUrl = downloadUrl === url ? this.baseUrl : updateUrlHash(downloadUrl, '', true);
17924 }
17925 if (isDataScheme(url)) {
17926 this._hideViewBookmark();
17927 }
17928 let title = pdfjs_getPdfFilenameFromUrl(url, '');
17929 if (!title) {
17930 try {
17931 title = decodeURIComponent(getFilenameFromUrl(url));
17932 } catch {}
17933 }
17934 this.setTitle(title || url);
17935 },
17936 setTitle(title = this._title) {
17937 this._title = title;
17938 if (this.isViewerEmbedded) {
17939 return;
17940 }
17941 const editorIndicator = this._hasAnnotationEditors && !this.pdfRenderingQueue.printing;
17942 document.title = `${editorIndicator ? '* ' : ''}${title}`;
17943 },
17944 get _docFilename() {
17945 return this._contentDispositionFilename || pdfjs_getPdfFilenameFromUrl(this.url);
17946 },
17947 get _docTitle() {
17948 const { documentInfo, metadata } = this;
17949 const title = metadata?.get('dc:title');
17950 if (title) {
17951 if (title !== 'Untitled' && !/[\uFFF0-\uFFFF]/g.test(title)) {
17952 return title;
17953 }
17954 }
17955 return documentInfo.Title;
17956 },
17957 _hideViewBookmark() {
17958 const { secondaryToolbar } = this.appConfig;
17959 secondaryToolbar?.viewBookmarkButton.classList.add('hidden');
17960 if (secondaryToolbar?.presentationModeButton.classList.contains('hidden')) {
17961 document.getElementById('viewBookmarkSeparator')?.classList.add('hidden');
17962 }
17963 },
17964 async close() {
17965 this._unblockDocumentLoadEvent();
17966 this._hideViewBookmark();
17967 if (!this.pdfLoadingTask) {
17968 return;
17969 }
17970 if (this.pdfDocument?.annotationStorage.size > 0 && this._annotationStorageModified) {
17971 try {
17972 await this.save();
17973 } catch {}
17974 }
17975 const promises = [];
17976 promises.push(this.pdfLoadingTask.destroy());
17977 this.pdfLoadingTask = null;
17978 if (this.pdfDocument) {
17979 this.pdfDocument = null;
17980 this.pdfThumbnailViewer?.setDocument(null);
17981 this.pdfViewer.setDocument(null);
17982 this.pdfLinkService.setDocument(null);
17983 this.pdfDocumentProperties?.setDocument(null);
17984 this.pdfTextExtractor?.setViewer(null);
17985 }
17986 this.pdfLinkService.externalLinkEnabled = true;
17987 this.store = null;
17988 this.isInitialViewSet = false;
17989 this.url = '';
17990 this.baseUrl = '';
17991 this._downloadUrl = '';
17992 this.documentInfo = null;
17993 this.metadata = null;
17994 this._contentDispositionFilename = null;
17995 this._contentLength = null;
17996 this._saveInProgress = false;
17997 this._hasAnnotationEditors = false;
17998 promises.push(this.pdfScriptingManager.destroyPromise, this.passwordPrompt.close());
17999 this.setTitle();
18000 this.viewsManager?.reset();
18001 this.pdfOutlineViewer?.reset();
18002 this.pdfAttachmentViewer?.reset();
18003 this.pdfLayerViewer?.reset();
18004 this.pdfHistory?.reset();
18005 this.findBar?.reset();
18006 this.toolbar?.reset();
18007 this.secondaryToolbar?.reset();
18008 this._PDFBug?.cleanup();
18009 await Promise.all(promises);
18010 },
18011 async open(args) {
18012 if (this.pdfLoadingTask) {
18013 await this.close();
18014 }
18015 const workerParams = AppOptions.getAll(OptionKind.WORKER);
18016 Object.assign(GlobalWorkerOptions, workerParams);
18017 if (args.url) {
18018 this.setTitleUsingUrl(args.originalUrl || args.url, args.url);
18019 }
18020 const apiParams = AppOptions.getAll(OptionKind.API);
18021 const loadingTask = getDocument({
18022 ...apiParams,
18023 ...args,
18024 });
18025 this.pdfLoadingTask = loadingTask;
18026 loadingTask.onPassword = (updateCallback, reason) => {
18027 if (this.isViewerEmbedded) {
18028 this._unblockDocumentLoadEvent();
18029 }
18030 this.pdfLinkService.externalLinkEnabled = false;
18031 this.passwordPrompt.setUpdateCallback(updateCallback, reason);
18032 this.passwordPrompt.open();
18033 };
18034 loadingTask.onProgress = (evt) => this.progress(evt.percent);
18035 return loadingTask.promise.then(
18036 (pdfDocument) => {
18037 this.load(pdfDocument);
18038 },
18039 (reason) => {
18040 if (loadingTask !== this.pdfLoadingTask) {
18041 return undefined;
18042 }
18043 let key = 'pdfjs-loading-error';
18044 if (reason instanceof InvalidPDFException) {
18045 key = 'pdfjs-invalid-file-error';
18046 } else if (reason instanceof ResponseException) {
18047 key = reason.missing ? 'pdfjs-missing-file-error' : 'pdfjs-unexpected-response-error';
18048 }
18049 return this._documentError(key, {
18050 message: reason.message,
18051 }).then(() => {
18052 throw reason;
18053 });
18054 }
18055 );
18056 },
18057 async download() {
18058 let data;
18059 try {
18060 data = await (this.pdfDocument ? this.pdfDocument.getData() : this.pdfLoadingTask.getData());
18061 } catch {}
18062 this.downloadManager.download(data, this._downloadUrl, this._docFilename);
18063 },
18064 async save() {
18065 if (this._saveInProgress) {
18066 return;
18067 }
18068 this._saveInProgress = true;
18069 await this.pdfScriptingManager.dispatchWillSave();
18070 try {
18071 const data = await this.pdfDocument.saveDocument();
18072 this.downloadManager.download(data, this._downloadUrl, this._docFilename);
18073 } catch (reason) {
18074 console.error(`Error when saving the document:`, reason);
18075 await this.download();
18076 } finally {
18077 await this.pdfScriptingManager.dispatchDidSave();
18078 this._saveInProgress = false;
18079 }
18080 const editorStats = this.pdfDocument?.annotationStorage.editorStats;
18081 if (editorStats) {
18082 this.externalServices.reportTelemetry({
18083 type: 'editing',
18084 data: {
18085 type: 'save',
18086 stats: editorStats,
18087 },
18088 });
18089 }
18090 },
18091 async downloadOrSave() {
18092 const { classList } = this.appConfig.appContainer;
18093 classList.add('wait');
18094 await (this.pdfDocument?.annotationStorage.size > 0 ? this.save() : this.download());
18095 classList.remove('wait');
18096 },
18097 async _documentError(key, moreInfo = null) {
18098 this._unblockDocumentLoadEvent();
18099 const message = await this._otherError(key || 'pdfjs-loading-error', moreInfo);
18100 this.eventBus.dispatch('documenterror', {
18101 source: this,
18102 message,
18103 reason: moreInfo?.message ?? null,
18104 });
18105 },
18106 async _otherError(key, moreInfo = null) {
18107 const message = await this.l10n.get(key);
18108 const moreInfoText = [`PDF.js v${version || '?'} (build: ${build || '?'})`];
18109 if (moreInfo) {
18110 moreInfoText.push(`Message: ${moreInfo.message}`);
18111 if (moreInfo.stack) {
18112 moreInfoText.push(`Stack: ${moreInfo.stack}`);
18113 } else {
18114 if (moreInfo.filename) {
18115 moreInfoText.push(`File: ${moreInfo.filename}`);
18116 }
18117 if (moreInfo.lineNumber) {
18118 moreInfoText.push(`Line: ${moreInfo.lineNumber}`);
18119 }
18120 }
18121 }
18122 console.error(`${message}\n\n${moreInfoText.join('\n')}`);
18123 return message;
18124 },
18125 progress(percent) {
18126 if (!this.loadingBar || percent <= this.loadingBar.percent) {
18127 return;
18128 }
18129 this.loadingBar.percent = percent;
18130 if (this.pdfDocument?.loadingParams.disableAutoFetch ?? AppOptions.get('disableAutoFetch')) {
18131 this.loadingBar.setDisableAutoFetch();
18132 }
18133 },
18134 load(pdfDocument) {
18135 this.pdfDocument = pdfDocument;
18136 this._printPermissionPromise = new Promise((resolve) => {
18137 this.eventBus.on(
18138 'printingallowed',
18139 ({ isAllowed }) => {
18140 resolve(isAllowed);
18141 },
18142 {
18143 once: true,
18144 }
18145 );
18146 });
18147 pdfDocument.getDownloadInfo().then(({ length }) => {
18148 this._contentLength = length;
18149 this.loadingBar?.hide();
18150 firstPagePromise.then(() => {
18151 this.eventBus.dispatch('documentloaded', {
18152 source: this,
18153 });
18154 });
18155 });
18156 const pageLayoutPromise = pdfDocument.getPageLayout().catch(() => {});
18157 const pageModePromise = pdfDocument.getPageMode().catch(() => {});
18158 const openActionPromise = pdfDocument.getOpenAction().catch(() => {});
18159 this.toolbar?.setPagesCount(pdfDocument.numPages, false);
18160 this.secondaryToolbar?.setPagesCount(pdfDocument.numPages);
18161 this.pdfLinkService.setDocument(pdfDocument);
18162 this.pdfDocumentProperties?.setDocument(pdfDocument);
18163 const pdfViewer = this.pdfViewer;
18164 pdfViewer.setDocument(pdfDocument);
18165 this.pdfTextExtractor.setViewer(pdfViewer);
18166 const { firstPagePromise, onePageRendered, pagesPromise } = pdfViewer;
18167 this.pdfThumbnailViewer?.setDocument(pdfDocument);
18168 const storedPromise = (this.store = new ViewHistory(pdfDocument.fingerprints[0]))
18169 .getMultiple({
18170 page: null,
18171 zoom: DEFAULT_SCALE_VALUE,
18172 scrollLeft: '0',
18173 scrollTop: '0',
18174 rotation: null,
18175 sidebarView: SidebarView.UNKNOWN,
18176 scrollMode: ScrollMode.UNKNOWN,
18177 spreadMode: SpreadMode.UNKNOWN,
18178 })
18179 .catch(() => {});
18180 firstPagePromise.then((pdfPage) => {
18181 this.loadingBar?.setWidth(this.appConfig.viewerContainer);
18182 this._initializeAnnotationStorageCallbacks(pdfDocument);
18183 Promise.all([
18184 animationStarted,
18185 storedPromise,
18186 pageLayoutPromise,
18187 pageModePromise,
18188 openActionPromise,
18189 ])
18190 .then(async ([timeStamp, stored, pageLayout, pageMode, openAction]) => {
18191 const viewOnLoad = AppOptions.get('viewOnLoad');
18192 this._initializePdfHistory({
18193 fingerprint: pdfDocument.fingerprints[0],
18194 viewOnLoad,
18195 initialDest: openAction?.dest,
18196 });
18197 const initialBookmark = this.initialBookmark;
18198 const zoom = AppOptions.get('defaultZoomValue');
18199 let hash = zoom ? `zoom=${zoom}` : null;
18200 let rotation = null;
18201 let sidebarView = AppOptions.get('sidebarViewOnLoad');
18202 let scrollMode = AppOptions.get('scrollModeOnLoad');
18203 let spreadMode = AppOptions.get('spreadModeOnLoad');
18204 if (stored?.page && viewOnLoad !== ViewOnLoad.INITIAL) {
18205 hash =
18206 `page=${stored.page}&zoom=${zoom || stored.zoom},` +
18207 `${stored.scrollLeft},${stored.scrollTop}`;
18208 rotation = parseInt(stored.rotation, 10);
18209 if (sidebarView === SidebarView.UNKNOWN) {
18210 sidebarView = stored.sidebarView | 0;
18211 }
18212 if (scrollMode === ScrollMode.UNKNOWN) {
18213 scrollMode = stored.scrollMode | 0;
18214 }
18215 if (spreadMode === SpreadMode.UNKNOWN) {
18216 spreadMode = stored.spreadMode | 0;
18217 }
18218 }
18219 if (pageMode && sidebarView === SidebarView.UNKNOWN) {
18220 sidebarView = apiPageModeToSidebarView(pageMode);
18221 }
18222 if (
18223 pageLayout &&
18224 scrollMode === ScrollMode.UNKNOWN &&
18225 spreadMode === SpreadMode.UNKNOWN
18226 ) {
18227 const modes = apiPageLayoutToViewerModes(pageLayout);
18228 spreadMode = modes.spreadMode;
18229 }
18230 this.setInitialView(hash, {
18231 rotation,
18232 sidebarView,
18233 scrollMode,
18234 spreadMode,
18235 });
18236 this.eventBus.dispatch('documentinit', {
18237 source: this,
18238 });
18239 await Promise.race([
18240 pagesPromise,
18241 new Promise((resolve) => {
18242 setTimeout(resolve, FORCE_PAGES_LOADED_TIMEOUT);
18243 }),
18244 ]);
18245 if (!initialBookmark && !hash) {
18246 return;
18247 }
18248 if (pdfViewer.hasEqualPageSizes) {
18249 return;
18250 }
18251 this.initialBookmark = initialBookmark;
18252 pdfViewer.currentScaleValue = pdfViewer.currentScaleValue;
18253 this.setInitialView(hash);
18254 })
18255 .catch(() => {
18256 this.setInitialView();
18257 })
18258 .then(function () {
18259 pdfViewer.update();
18260 });
18261 });
18262 pagesPromise.then(
18263 () => {
18264 this._unblockDocumentLoadEvent();
18265 this._initializeAutoPrint(pdfDocument, openActionPromise);
18266 },
18267 (reason) => {
18268 this._documentError('pdfjs-loading-error', {
18269 message: reason.message,
18270 });
18271 }
18272 );
18273 onePageRendered.then((data) => {
18274 this.externalServices.reportTelemetry({
18275 type: 'pageInfo',
18276 timestamp: data.timestamp,
18277 });
18278 if (this.pdfOutlineViewer) {
18279 pdfDocument.getOutline().then((outline) => {
18280 if (pdfDocument !== this.pdfDocument) {
18281 return;
18282 }
18283 this.pdfOutlineViewer.render({
18284 outline,
18285 pdfDocument,
18286 });
18287 });
18288 }
18289 if (this.pdfAttachmentViewer) {
18290 pdfDocument.getAttachments().then((attachments) => {
18291 if (pdfDocument !== this.pdfDocument) {
18292 return;
18293 }
18294 this.pdfAttachmentViewer.render({
18295 attachments,
18296 });
18297 });
18298 }
18299 if (this.pdfLayerViewer) {
18300 pdfViewer.optionalContentConfigPromise.then((optionalContentConfig) => {
18301 if (pdfDocument !== this.pdfDocument) {
18302 return;
18303 }
18304 this.pdfLayerViewer.render({
18305 optionalContentConfig,
18306 pdfDocument,
18307 });
18308 });
18309 }
18310 });
18311 this._initializePageLabels(pdfDocument);
18312 this._initializeMetadata(pdfDocument);
18313 },
18314 async _scriptingDocProperties(pdfDocument) {
18315 if (!this.documentInfo) {
18316 await new Promise((resolve) => {
18317 this.eventBus._on('metadataloaded', resolve, {
18318 once: true,
18319 });
18320 });
18321 if (pdfDocument !== this.pdfDocument) {
18322 return null;
18323 }
18324 }
18325 if (!this._contentLength) {
18326 await new Promise((resolve) => {
18327 this.eventBus._on('documentloaded', resolve, {
18328 once: true,
18329 });
18330 });
18331 if (pdfDocument !== this.pdfDocument) {
18332 return null;
18333 }
18334 }
18335 return {
18336 ...this.documentInfo,
18337 baseURL: this.baseUrl,
18338 filesize: this._contentLength,
18339 filename: this._docFilename,
18340 metadata: this.metadata?.getRaw(),
18341 authors: this.metadata?.get('dc:creator'),
18342 numPages: this.pagesCount,
18343 URL: this.url,
18344 };
18345 },
18346 async _initializeAutoPrint(pdfDocument, openActionPromise) {
18347 const [openAction, jsActions] = await Promise.all([
18348 openActionPromise,
18349 this.pdfViewer.enableScripting ? null : pdfDocument.getJSActions(),
18350 ]);
18351 if (pdfDocument !== this.pdfDocument) {
18352 return;
18353 }
18354 let triggerAutoPrint = openAction?.action === 'Print';
18355 if (jsActions) {
18356 console.warn('Warning: JavaScript support is not enabled');
18357 for (const name in jsActions) {
18358 if (triggerAutoPrint) {
18359 break;
18360 }
18361 switch (name) {
18362 case 'WillClose':
18363 case 'WillSave':
18364 case 'DidSave':
18365 case 'WillPrint':
18366 case 'DidPrint':
18367 continue;
18368 }
18369 triggerAutoPrint = jsActions[name].some((js) => AutoPrintRegExp.test(js));
18370 }
18371 }
18372 if (triggerAutoPrint) {
18373 this.triggerPrinting();
18374 }
18375 },
18376 async _initializeMetadata(pdfDocument) {
18377 const { info, metadata, contentDispositionFilename, contentLength, hasStructTree } =
18378 await pdfDocument.getMetadata();
18379 if (pdfDocument !== this.pdfDocument) {
18380 return;
18381 }
18382 this.externalServices.reportTelemetry({
18383 type: 'taggedPDF',
18384 data: hasStructTree,
18385 });
18386 this.documentInfo = info;
18387 this.metadata = metadata;
18388 this._contentDispositionFilename ??= contentDispositionFilename;
18389 this._contentLength ??= contentLength;
18390 console.log(
18391 `PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` +
18392 `${(metadata?.get('pdf:producer') || info.Producer || '-').trim()} / ` +
18393 `${(metadata?.get('xmp:creatortool') || info.Creator || '-').trim()}` +
18394 `] (PDF.js: ${version || '?'} [${build || '?'}])`
18395 );
18396 const pdfTitle = this._docTitle;
18397 if (pdfTitle) {
18398 this.setTitle(`${pdfTitle} - ${this._contentDispositionFilename || this._title}`);
18399 } else if (this._contentDispositionFilename) {
18400 this.setTitle(this._contentDispositionFilename);
18401 }
18402 if (info.IsXFAPresent && !info.IsAcroFormPresent && !pdfDocument.isPureXfa) {
18403 if (pdfDocument.loadingParams.enableXfa) {
18404 console.warn('Warning: XFA Foreground documents are not supported');
18405 } else {
18406 console.warn('Warning: XFA support is not enabled');
18407 }
18408 } else if ((info.IsAcroFormPresent || info.IsXFAPresent) && !this.pdfViewer.renderForms) {
18409 console.warn('Warning: Interactive form support is not enabled');
18410 }
18411 if (info.IsSignaturesPresent) {
18412 console.warn('Warning: Digital signatures validation is not supported');
18413 }
18414 this.eventBus.dispatch('metadataloaded', {
18415 source: this,
18416 });
18417 },
18418 async _initializePageLabels(pdfDocument) {
18419 const labels = await pdfDocument.getPageLabels();
18420 if (pdfDocument !== this.pdfDocument) {
18421 return;
18422 }
18423 if (!labels || AppOptions.get('disablePageLabels')) {
18424 return;
18425 }
18426 const numLabels = labels.length;
18427 let standardLabels = 0,
18428 emptyLabels = 0;
18429 for (let i = 0; i < numLabels; i++) {
18430 const label = labels[i];
18431 if (label === (i + 1).toString()) {
18432 standardLabels++;
18433 } else if (label === '') {
18434 emptyLabels++;
18435 } else {
18436 break;
18437 }
18438 }
18439 if (standardLabels >= numLabels || emptyLabels >= numLabels) {
18440 return;
18441 }
18442 const { pdfViewer, pdfThumbnailViewer, toolbar } = this;
18443 pdfViewer.setPageLabels(labels);
18444 pdfThumbnailViewer?.setPageLabels(labels);
18445 toolbar?.setPagesCount(numLabels, true);
18446 toolbar?.setPageNumber(pdfViewer.currentPageNumber, pdfViewer.currentPageLabel);
18447 },
18448 _initializePdfHistory({ fingerprint, viewOnLoad, initialDest = null }) {
18449 if (!this.pdfHistory) {
18450 return;
18451 }
18452 this.pdfHistory.initialize({
18453 fingerprint,
18454 resetHistory: viewOnLoad === ViewOnLoad.INITIAL,
18455 updateUrl: AppOptions.get('historyUpdateUrl'),
18456 });
18457 if (this.pdfHistory.initialBookmark) {
18458 this.initialBookmark = this.pdfHistory.initialBookmark;
18459 this.initialRotation = this.pdfHistory.initialRotation;
18460 }
18461 if (initialDest && !this.initialBookmark && viewOnLoad === ViewOnLoad.UNKNOWN) {
18462 this.initialBookmark = JSON.stringify(initialDest);
18463 this.pdfHistory.push({
18464 explicitDest: initialDest,
18465 pageNumber: null,
18466 });
18467 }
18468 },
18469 _initializeAnnotationStorageCallbacks(pdfDocument) {
18470 if (pdfDocument !== this.pdfDocument) {
18471 return;
18472 }
18473 const { annotationStorage } = pdfDocument;
18474 annotationStorage.onSetModified = () => {
18475 window.addEventListener('beforeunload', beforeUnload);
18476 this._annotationStorageModified = true;
18477 };
18478 annotationStorage.onResetModified = () => {
18479 window.removeEventListener('beforeunload', beforeUnload);
18480 delete this._annotationStorageModified;
18481 };
18482 annotationStorage.onAnnotationEditor = (typeStr) => {
18483 this._hasAnnotationEditors = !!typeStr;
18484 this.setTitle();
18485 };
18486 },
18487 setInitialView(storedHash, { rotation, sidebarView, scrollMode, spreadMode } = {}) {
18488 const setRotation = (angle) => {
18489 if (isValidRotation(angle)) {
18490 this.pdfViewer.pagesRotation = angle;
18491 }
18492 };
18493 const setViewerModes = (scroll, spread) => {
18494 if (isValidScrollMode(scroll)) {
18495 this.pdfViewer.scrollMode = scroll;
18496 }
18497 if (isValidSpreadMode(spread)) {
18498 this.pdfViewer.spreadMode = spread;
18499 }
18500 };
18501 this.isInitialViewSet = true;
18502 this.viewsManager?.setInitialView(sidebarView);
18503 setViewerModes(scrollMode, spreadMode);
18504 if (this.initialBookmark) {
18505 setRotation(this.initialRotation);
18506 delete this.initialRotation;
18507 this.pdfLinkService.setHash(this.initialBookmark);
18508 this.initialBookmark = null;
18509 } else if (storedHash) {
18510 setRotation(rotation);
18511 this.pdfLinkService.setHash(storedHash);
18512 }
18513 this.toolbar?.setPageNumber(this.pdfViewer.currentPageNumber, this.pdfViewer.currentPageLabel);
18514 this.secondaryToolbar?.setPageNumber(this.pdfViewer.currentPageNumber);
18515 if (!this.pdfViewer.currentScaleValue) {
18516 this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
18517 }
18518 },
18519 _cleanup() {
18520 if (!this.pdfDocument) {
18521 return;
18522 }
18523 this.pdfViewer.cleanup();
18524 this.pdfThumbnailViewer?.cleanup();
18525 this.pdfDocument.cleanup(AppOptions.get('fontExtraProperties'));
18526 },
18527 forceRendering() {
18528 this.pdfRenderingQueue.printing = !!this.printService;
18529 this.pdfRenderingQueue.isThumbnailViewEnabled =
18530 this.viewsManager?.visibleView === SidebarView.THUMBS;
18531 this.pdfRenderingQueue.renderHighestPriority();
18532 },
18533 beforePrint() {
18534 this._printAnnotationStoragePromise = this.pdfScriptingManager
18535 .dispatchWillPrint()
18536 .catch(() => {})
18537 .then(() => this.pdfDocument?.annotationStorage.print);
18538 if (this.printService) {
18539 return;
18540 }
18541 if (!this.supportsPrinting || !this.pdfViewer.printingAllowed) {
18542 this._otherError('pdfjs-printing-not-supported');
18543 return;
18544 }
18545 if (!this.pdfViewer.pageViewsReady) {
18546 this.l10n.get('pdfjs-printing-not-ready').then((msg) => {
18547 window.alert(msg);
18548 });
18549 return;
18550 }
18551 this.printService = PDFPrintServiceFactory.createPrintService({
18552 pdfDocument: this.pdfDocument,
18553 pagesOverview: this.pdfViewer.getPagesOverview(),
18554 printContainer: this.appConfig.printContainer,
18555 printResolution: AppOptions.get('printResolution'),
18556 printAnnotationStoragePromise: this._printAnnotationStoragePromise,
18557 });
18558 this.forceRendering();
18559 this.setTitle();
18560 this.printService.layout();
18561 if (this._hasAnnotationEditors) {
18562 this.externalServices.reportTelemetry({
18563 type: 'editing',
18564 data: {
18565 type: 'print',
18566 stats: this.pdfDocument?.annotationStorage.editorStats,
18567 },
18568 });
18569 }
18570 },
18571 afterPrint() {
18572 if (this._printAnnotationStoragePromise) {
18573 this._printAnnotationStoragePromise.then(() => {
18574 this.pdfScriptingManager.dispatchDidPrint();
18575 });
18576 this._printAnnotationStoragePromise = null;
18577 }
18578 if (this.printService) {
18579 this.printService.destroy();
18580 this.printService = null;
18581 this.pdfDocument?.annotationStorage.resetModified();
18582 }
18583 this.forceRendering();
18584 this.setTitle();
18585 },
18586 rotatePages(delta) {
18587 this.pdfViewer.pagesRotation += delta;
18588 },
18589 requestPresentationMode() {
18590 this.pdfPresentationMode?.request();
18591 },
18592 async triggerPrinting() {
18593 if (this.supportsPrinting && (await this._printPermissionPromise)) {
18594 window.print();
18595 }
18596 },
18597 bindEvents() {
18598 if (this._eventBusAbortController) {
18599 return;
18600 }
18601 const ac = (this._eventBusAbortController = new AbortController());
18602 const opts = {
18603 signal: ac.signal,
18604 };
18605 const { eventBus, externalServices, pdfDocumentProperties, pdfViewer, preferences } = this;
18606 eventBus._on('resize', onResize.bind(this), opts);
18607 eventBus._on('hashchange', onHashchange.bind(this), opts);
18608 eventBus._on('beforeprint', this.beforePrint.bind(this), opts);
18609 eventBus._on('afterprint', this.afterPrint.bind(this), opts);
18610 eventBus._on('pagerender', onPageRender.bind(this), opts);
18611 eventBus._on('pagerendered', onPageRendered.bind(this), opts);
18612 eventBus._on('updateviewarea', onUpdateViewarea.bind(this), opts);
18613 eventBus._on('pagechanging', onPageChanging.bind(this), opts);
18614 eventBus._on('scalechanging', onScaleChanging.bind(this), opts);
18615 eventBus._on('rotationchanging', onRotationChanging.bind(this), opts);
18616 eventBus._on('sidebarviewchanged', onSidebarViewChanged.bind(this), opts);
18617 eventBus._on('pagemode', onPageMode.bind(this), opts);
18618 eventBus._on('namedaction', onNamedAction.bind(this), opts);
18619 eventBus._on(
18620 'presentationmodechanged',
18621 (evt) => (pdfViewer.presentationModeState = evt.state),
18622 opts
18623 );
18624 eventBus._on('presentationmode', this.requestPresentationMode.bind(this), opts);
18625 eventBus._on(
18626 'switchannotationeditormode',
18627 (evt) => (pdfViewer.annotationEditorMode = evt),
18628 opts
18629 );
18630 eventBus._on('print', this.triggerPrinting.bind(this), opts);
18631 eventBus._on('download', this.downloadOrSave.bind(this), opts);
18632 eventBus._on('firstpage', () => (this.page = 1), opts);
18633 eventBus._on('lastpage', () => (this.page = this.pagesCount), opts);
18634 eventBus._on('nextpage', () => pdfViewer.nextPage(), opts);
18635 eventBus._on('previouspage', () => pdfViewer.previousPage(), opts);
18636 eventBus._on('zoomin', this.zoomIn.bind(this), opts);
18637 eventBus._on('zoomout', this.zoomOut.bind(this), opts);
18638 eventBus._on('zoomreset', this.zoomReset.bind(this), opts);
18639 eventBus._on('pagenumberchanged', onPageNumberChanged.bind(this), opts);
18640 eventBus._on('scalechanged', (evt) => (pdfViewer.currentScaleValue = evt.value), opts);
18641 eventBus._on('rotatecw', this.rotatePages.bind(this, 90), opts);
18642 eventBus._on('rotateccw', this.rotatePages.bind(this, -90), opts);
18643 eventBus._on(
18644 'optionalcontentconfig',
18645 (evt) => (pdfViewer.optionalContentConfigPromise = evt.promise),
18646 opts
18647 );
18648 eventBus._on('switchscrollmode', (evt) => (pdfViewer.scrollMode = evt.mode), opts);
18649 eventBus._on('scrollmodechanged', onViewerModesChanged.bind(this, 'scrollMode'), opts);
18650 eventBus._on('switchspreadmode', (evt) => (pdfViewer.spreadMode = evt.mode), opts);
18651 eventBus._on('spreadmodechanged', onViewerModesChanged.bind(this, 'spreadMode'), opts);
18652 eventBus._on('imagealttextsettings', onImageAltTextSettings.bind(this), opts);
18653 eventBus._on('documentproperties', () => pdfDocumentProperties?.open(), opts);
18654 eventBus._on('findfromurlhash', onFindFromUrlHash.bind(this), opts);
18655 eventBus._on('updatefindmatchescount', onUpdateFindMatchesCount.bind(this), opts);
18656 eventBus._on('updatefindcontrolstate', onUpdateFindControlState.bind(this), opts);
18657 eventBus._on('fileinputchange', onFileInputChange.bind(this), opts);
18658 eventBus._on('openfile', onOpenFile.bind(this), opts);
18659 eventBus._on('pagesedited', this.onPagesEdited.bind(this), opts);
18660 eventBus._on('beforepagesedited', this.onBeforePagesEdited.bind(this), opts);
18661 eventBus._on('savepageseditedpdf', this.onSavePagesEditedPDF.bind(this), opts);
18662 },
18663 bindWindowEvents() {
18664 if (this._windowAbortController) {
18665 return;
18666 }
18667 this._windowAbortController = new AbortController();
18668 const {
18669 eventBus,
18670 appConfig: { mainContainer },
18671 pdfViewer,
18672 _windowAbortController: { signal },
18673 } = this;
18674 this._touchManager = new TouchManager({
18675 container: window,
18676 isPinchingDisabled: () => pdfViewer.isInPresentationMode,
18677 isPinchingStopped: () => this.overlayManager?.active,
18678 onPinching: this.touchPinchCallback.bind(this),
18679 onPinchEnd: this.touchPinchEndCallback.bind(this),
18680 signal,
18681 });
18682 function addWindowResolutionChange(evt = null) {
18683 if (evt) {
18684 pdfViewer.refresh();
18685 }
18686 const mediaQueryList = window.matchMedia(`(resolution: ${OutputScale.pixelRatio}dppx)`);
18687 mediaQueryList.addEventListener('change', addWindowResolutionChange, {
18688 once: true,
18689 signal,
18690 });
18691 }
18692 addWindowResolutionChange();
18693 window.addEventListener('wheel', onWheel.bind(this), {
18694 passive: false,
18695 signal,
18696 });
18697 window.addEventListener('click', onClick.bind(this), {
18698 signal,
18699 });
18700 window.addEventListener('keydown', onKeyDown.bind(this), {
18701 signal,
18702 });
18703 window.addEventListener('keyup', onKeyUp.bind(this), {
18704 signal,
18705 });
18706 window.addEventListener(
18707 'resize',
18708 () =>
18709 eventBus.dispatch('resize', {
18710 source: window,
18711 }),
18712 {
18713 signal,
18714 }
18715 );
18716 window.addEventListener(
18717 'hashchange',
18718 () => {
18719 eventBus.dispatch('hashchange', {
18720 source: window,
18721 hash: document.location.hash.substring(1),
18722 });
18723 },
18724 {
18725 signal,
18726 }
18727 );
18728 window.addEventListener(
18729 'beforeprint',
18730 () =>
18731 eventBus.dispatch('beforeprint', {
18732 source: window,
18733 }),
18734 {
18735 signal,
18736 }
18737 );
18738 window.addEventListener(
18739 'afterprint',
18740 () =>
18741 eventBus.dispatch('afterprint', {
18742 source: window,
18743 }),
18744 {
18745 signal,
18746 }
18747 );
18748 window.addEventListener(
18749 'updatefromsandbox',
18750 (evt) => {
18751 eventBus.dispatch('updatefromsandbox', {
18752 source: window,
18753 detail: evt.detail,
18754 });
18755 },
18756 {
18757 signal,
18758 }
18759 );
18760 if (!('onscrollend' in document.documentElement)) {
18761 return;
18762 }
18763 ({ scrollTop: this._lastScrollTop, scrollLeft: this._lastScrollLeft } = mainContainer);
18764 let scrollendTimeoutID, scrollAbortController;
18765 const scrollend = () => {
18766 ({ scrollTop: this._lastScrollTop, scrollLeft: this._lastScrollLeft } = mainContainer);
18767 clearTimeout(scrollendTimeoutID);
18768 if (this._isScrolling) {
18769 scrollAbortController.abort();
18770 scrollAbortController = null;
18771 this._isScrolling = false;
18772 }
18773 };
18774 const scroll = () => {
18775 if (this._isCtrlKeyDown) {
18776 return;
18777 }
18778 if (
18779 this._lastScrollTop === mainContainer.scrollTop &&
18780 this._lastScrollLeft === mainContainer.scrollLeft
18781 ) {
18782 return;
18783 }
18784 if (!this._isScrolling) {
18785 scrollAbortController = new AbortController();
18786 const abortSignal = AbortSignal.any([scrollAbortController.signal, signal]);
18787 mainContainer.addEventListener('scrollend', scrollend, {
18788 signal: abortSignal,
18789 });
18790 mainContainer.addEventListener('blur', scrollend, {
18791 signal: abortSignal,
18792 });
18793 this._isScrolling = true;
18794 }
18795 clearTimeout(scrollendTimeoutID);
18796 scrollendTimeoutID = setTimeout(scrollend, 100);
18797 };
18798 mainContainer.addEventListener('scroll', scroll, {
18799 passive: true,
18800 signal,
18801 });
18802 },
18803 unbindEvents() {
18804 this._eventBusAbortController?.abort();
18805 this._eventBusAbortController = null;
18806 },
18807 unbindWindowEvents() {
18808 this._windowAbortController?.abort();
18809 this._windowAbortController = null;
18810 this._touchManager = null;
18811 },
18812 async testingClose() {
18813 this.unbindEvents();
18814 this.unbindWindowEvents();
18815 this._globalAbortController?.abort();
18816 this._globalAbortController = null;
18817 this.findBar?.close();
18818 await Promise.all([this.l10n?.destroy(), this.close()]);
18819 },
18820 onBeforePagesEdited(data) {
18821 this.pdfViewer.onBeforePagesEdited(data);
18822 },
18823 onPagesEdited(data) {
18824 this.pdfViewer.onPagesEdited(data);
18825 },
18826 async onSavePagesEditedPDF({ data: { includePages, excludePages, pageIndices } }) {
18827 if (!this.pdfDocument) {
18828 return;
18829 }
18830 const pageInfo = {
18831 document: null,
18832 includePages,
18833 excludePages,
18834 pageIndices,
18835 };
18836 const modifiedPdfBytes = await this.pdfDocument.extractPages([pageInfo]);
18837 if (!modifiedPdfBytes) {
18838 console.error('Something wrong happened when saving the edited PDF.\nPlease file a bug.');
18839 return;
18840 }
18841 this.downloadManager.download(modifiedPdfBytes, this._downloadUrl, this._docFilename);
18842 },
18843 _accumulateTicks(ticks, prop) {
18844 if ((this[prop] > 0 && ticks < 0) || (this[prop] < 0 && ticks > 0)) {
18845 this[prop] = 0;
18846 }
18847 this[prop] += ticks;
18848 const wholeTicks = Math.trunc(this[prop]);
18849 this[prop] -= wholeTicks;
18850 return wholeTicks;
18851 },
18852 _accumulateFactor(previousScale, factor, prop) {
18853 if (factor === 1) {
18854 return 1;
18855 }
18856 if ((this[prop] > 1 && factor < 1) || (this[prop] < 1 && factor > 1)) {
18857 this[prop] = 1;
18858 }
18859 const newFactor = Math.floor(previousScale * factor * this[prop] * 100) / (100 * previousScale);
18860 this[prop] = factor / newFactor;
18861 return newFactor;
18862 },
18863 _unblockDocumentLoadEvent() {
18864 document.blockUnblockOnload?.(false);
18865 this._unblockDocumentLoadEvent = () => {};
18866 },
18867 get scriptingReady() {
18868 return this.pdfScriptingManager.ready;
18869 },
18870};
18871initCom(PDFViewerApplication);
18872{
18873 PDFPrintServiceFactory.initGlobals(PDFViewerApplication);
18874}
18875{
18876 const HOSTED_VIEWER_ORIGINS = new Set([
18877 'null',
18878 'http://mozilla.github.io',
18879 'https://mozilla.github.io',
18880 ]);
18881 var validateFileURL = function (file) {
18882 if (!file) {
18883 return;
18884 }
18885 const viewerOrigin = URL.parse(window.location)?.origin || 'null';
18886 if (true) {
18887 return;
18888 }
18889 const fileOrigin = URL.parse(file, window.location)?.origin;
18890 if (fileOrigin === viewerOrigin) {
18891 return;
18892 }
18893 const ex = new Error("file origin does not match viewer's");
18894 PDFViewerApplication._documentError('pdfjs-loading-error', {
18895 message: ex.message,
18896 });
18897 throw ex;
18898 };
18899 var onFileInputChange = function (evt) {
18900 if (this.pdfViewer?.isInPresentationMode) {
18901 return;
18902 }
18903 const file = evt.fileInput.files[0];
18904 this.open({
18905 url: URL.createObjectURL(file),
18906 originalUrl: encodeURIComponent(file.name),
18907 });
18908 };
18909 var onOpenFile = function (evt) {
18910 this._openFileInput?.click();
18911 };
18912}
18913function onPageRender({ pageNumber }) {
18914 if (pageNumber === this.page) {
18915 this.toolbar?.updateLoadingIndicatorState(true);
18916 }
18917}
18918function onPageRendered({ pageNumber, isDetailView, error }) {
18919 if (pageNumber === this.page) {
18920 this.toolbar?.updateLoadingIndicatorState(false);
18921 }
18922 if (!isDetailView && this.viewsManager?.visibleView === SidebarView.THUMBS) {
18923 const pageView = this.pdfViewer.getPageView(pageNumber - 1);
18924 const thumbnailView = this.pdfThumbnailViewer?.getThumbnail(pageNumber - 1);
18925 if (pageView) {
18926 thumbnailView?.setImage(pageView);
18927 }
18928 }
18929 if (error) {
18930 this._otherError('pdfjs-rendering-error', error);
18931 }
18932}
18933function onPageMode({ mode }) {
18934 let view;
18935 switch (mode) {
18936 case 'thumbs':
18937 view = SidebarView.THUMBS;
18938 break;
18939 case 'bookmarks':
18940 case 'outline':
18941 view = SidebarView.OUTLINE;
18942 break;
18943 case 'attachments':
18944 view = SidebarView.ATTACHMENTS;
18945 break;
18946 case 'layers':
18947 view = SidebarView.LAYERS;
18948 break;
18949 case 'none':
18950 view = SidebarView.NONE;
18951 break;
18952 default:
18953 console.error('Invalid "pagemode" hash parameter: ' + mode);
18954 return;
18955 }
18956 this.viewsManager?.switchView(view, true);
18957}
18958function onNamedAction(evt) {
18959 switch (evt.action) {
18960 case 'GoToPage':
18961 this.appConfig.toolbar?.pageNumber.select();
18962 break;
18963 case 'Find':
18964 if (!this.supportsIntegratedFind) {
18965 this.findBar?.toggle();
18966 }
18967 break;
18968 case 'Print':
18969 this.triggerPrinting();
18970 break;
18971 case 'SaveAs':
18972 this.downloadOrSave();
18973 break;
18974 }
18975}
18976function onSidebarViewChanged({ view }) {
18977 this.pdfRenderingQueue.isThumbnailViewEnabled = view === SidebarView.THUMBS;
18978 if (this.isInitialViewSet) {
18979 this.store?.set('sidebarView', view).catch(() => {});
18980 }
18981}
18982function onUpdateViewarea({ location }) {
18983 if (this.isInitialViewSet) {
18984 this.store
18985 ?.setMultiple({
18986 page: location.pageNumber,
18987 zoom: location.scale,
18988 scrollLeft: location.left,
18989 scrollTop: location.top,
18990 rotation: location.rotation,
18991 })
18992 .catch(() => {});
18993 }
18994 if (this.appConfig.secondaryToolbar) {
18995 this.appConfig.secondaryToolbar.viewBookmarkButton.href = this.pdfLinkService.getAnchorUrl(
18996 location.pdfOpenParams
18997 );
18998 }
18999}
19000function onViewerModesChanged(name, evt) {
19001 if (this.isInitialViewSet && !this.pdfViewer.isInPresentationMode) {
19002 this.store?.set(name, evt.mode).catch(() => {});
19003 }
19004}
19005function onResize() {
19006 const { pdfDocument, pdfViewer, pdfRenderingQueue } = this;
19007 if (pdfRenderingQueue.printing && window.matchMedia('print').matches) {
19008 return;
19009 }
19010 if (!pdfDocument) {
19011 return;
19012 }
19013 const currentScaleValue = pdfViewer.currentScaleValue;
19014 if (
19015 currentScaleValue === 'auto' ||
19016 currentScaleValue === 'page-fit' ||
19017 currentScaleValue === 'page-width'
19018 ) {
19019 pdfViewer.currentScaleValue = currentScaleValue;
19020 }
19021 pdfViewer.update();
19022}
19023function onHashchange(evt) {
19024 const hash = evt.hash;
19025 if (!hash) {
19026 return;
19027 }
19028 if (!this.isInitialViewSet) {
19029 this.initialBookmark = hash;
19030 } else if (!this.pdfHistory?.popStateInProgress) {
19031 this.pdfLinkService.setHash(hash);
19032 }
19033}
19034function onPageNumberChanged(evt) {
19035 const { pdfViewer } = this;
19036 if (evt.value !== '') {
19037 this.pdfLinkService.goToPage(evt.value);
19038 }
19039 if (
19040 evt.value !== pdfViewer.currentPageNumber.toString() &&
19041 evt.value !== pdfViewer.currentPageLabel
19042 ) {
19043 this.toolbar?.setPageNumber(pdfViewer.currentPageNumber, pdfViewer.currentPageLabel);
19044 }
19045}
19046function onImageAltTextSettings() {
19047 this.imageAltTextSettings?.open({
19048 enableGuessAltText: AppOptions.get('enableGuessAltText'),
19049 enableNewAltTextWhenAddingImage: AppOptions.get('enableNewAltTextWhenAddingImage'),
19050 });
19051}
19052function onFindFromUrlHash(evt) {
19053 this.eventBus.dispatch('find', {
19054 source: evt.source,
19055 type: '',
19056 query: evt.query,
19057 caseSensitive: false,
19058 entireWord: false,
19059 highlightAll: true,
19060 findPrevious: false,
19061 matchDiacritics: true,
19062 });
19063}
19064function onUpdateFindMatchesCount({ matchesCount }) {
19065 if (this.supportsIntegratedFind) {
19066 this.externalServices.updateFindMatchesCount(matchesCount);
19067 } else {
19068 this.findBar?.updateResultsCount(matchesCount);
19069 }
19070}
19071function onUpdateFindControlState({ state, previous, entireWord, matchesCount, rawQuery }) {
19072 if (this.supportsIntegratedFind) {
19073 this.externalServices.updateFindControlState({
19074 result: state,
19075 findPrevious: previous,
19076 entireWord,
19077 matchesCount,
19078 rawQuery,
19079 });
19080 } else {
19081 this.findBar?.updateUIState(state, previous, matchesCount);
19082 }
19083}
19084function onScaleChanging(evt) {
19085 this.toolbar?.setPageScale(evt.presetValue, evt.scale);
19086 this.pdfViewer.update();
19087}
19088function onRotationChanging(evt) {
19089 if (this.pdfThumbnailViewer) {
19090 this.pdfThumbnailViewer.pagesRotation = evt.pagesRotation;
19091 }
19092 this.forceRendering();
19093 this.pdfViewer.currentPageNumber = evt.pageNumber;
19094}
19095function onPageChanging({ pageNumber, pageLabel }) {
19096 this.toolbar?.setPageNumber(pageNumber, pageLabel);
19097 this.secondaryToolbar?.setPageNumber(pageNumber);
19098 if (this.viewsManager?.visibleView === SidebarView.THUMBS) {
19099 this.pdfThumbnailViewer?.scrollThumbnailIntoView(pageNumber);
19100 }
19101 const currentPage = this.pdfViewer.getPageView(pageNumber - 1);
19102 this.toolbar?.updateLoadingIndicatorState(
19103 currentPage?.renderingState === RenderingStates.RUNNING
19104 );
19105}
19106function onWheel(evt) {
19107 const {
19108 pdfViewer,
19109 supportsMouseWheelZoomCtrlKey,
19110 supportsMouseWheelZoomMetaKey,
19111 supportsPinchToZoom,
19112 } = this;
19113 if (pdfViewer.isInPresentationMode) {
19114 return;
19115 }
19116 const deltaMode = evt.deltaMode;
19117 let scaleFactor = Math.exp(-evt.deltaY / 100);
19118 const isBuiltInMac = false;
19119 const isPinchToZoom =
19120 evt.ctrlKey &&
19121 !this._isCtrlKeyDown &&
19122 deltaMode === WheelEvent.DOM_DELTA_PIXEL &&
19123 evt.deltaX === 0 &&
19124 (Math.abs(scaleFactor - 1) < 0.05 || isBuiltInMac) &&
19125 evt.deltaZ === 0;
19126 const origin = [evt.clientX, evt.clientY];
19127 if (
19128 isPinchToZoom ||
19129 (evt.ctrlKey && supportsMouseWheelZoomCtrlKey) ||
19130 (evt.metaKey && supportsMouseWheelZoomMetaKey)
19131 ) {
19132 evt.preventDefault();
19133 if (this._isScrolling || document.visibilityState === 'hidden' || this.overlayManager.active) {
19134 return;
19135 }
19136 if (isPinchToZoom && supportsPinchToZoom) {
19137 scaleFactor = this._accumulateFactor(
19138 pdfViewer.currentScale,
19139 scaleFactor,
19140 '_wheelUnusedFactor'
19141 );
19142 this.updateZoom(null, scaleFactor, origin);
19143 } else {
19144 const delta = normalizeWheelEventDirection(evt);
19145 let ticks = 0;
19146 if (deltaMode === WheelEvent.DOM_DELTA_LINE || deltaMode === WheelEvent.DOM_DELTA_PAGE) {
19147 ticks =
19148 Math.abs(delta) >= 1
19149 ? Math.sign(delta)
19150 : this._accumulateTicks(delta, '_wheelUnusedTicks');
19151 } else {
19152 const PIXELS_PER_LINE_SCALE = 30;
19153 ticks = this._accumulateTicks(delta / PIXELS_PER_LINE_SCALE, '_wheelUnusedTicks');
19154 }
19155 this.updateZoom(ticks, null, origin);
19156 }
19157 }
19158}
19159function closeSecondaryToolbar({ target }) {
19160 if (!this.secondaryToolbar?.isOpen) {
19161 return;
19162 }
19163 const { toolbar, secondaryToolbar } = this.appConfig;
19164 if (
19165 this.pdfViewer.containsElement(target) ||
19166 (toolbar?.container.contains(target) &&
19167 !secondaryToolbar?.toolbar.contains(target) &&
19168 !secondaryToolbar?.toggleButton.contains(target))
19169 ) {
19170 this.secondaryToolbar.close();
19171 }
19172}
19173function closeEditorUndoBar(evt) {
19174 if (!this.editorUndoBar?.isOpen) {
19175 return;
19176 }
19177 if (this.appConfig.secondaryToolbar?.toolbar.contains(evt.target)) {
19178 this.editorUndoBar.hide();
19179 }
19180}
19181function onClick(evt) {
19182 closeSecondaryToolbar.call(this, evt);
19183 closeEditorUndoBar.call(this, evt);
19184}
19185function onKeyUp(evt) {
19186 if (evt.key === 'Control') {
19187 this._isCtrlKeyDown = false;
19188 }
19189}
19190function onKeyDown(evt) {
19191 this._isCtrlKeyDown = evt.key === 'Control';
19192 if (
19193 this.editorUndoBar?.isOpen &&
19194 evt.keyCode !== 9 &&
19195 evt.keyCode !== 16 &&
19196 !(
19197 (evt.keyCode === 13 || evt.keyCode === 32) &&
19198 getActiveOrFocusedElement() === this.appConfig.editorUndoBar.undoButton
19199 )
19200 ) {
19201 this.editorUndoBar.hide();
19202 }
19203 if (this.overlayManager.active) {
19204 return;
19205 }
19206 const { eventBus, pdfViewer } = this;
19207 const isViewerInPresentationMode = pdfViewer.isInPresentationMode;
19208 let handled = false,
19209 ensureViewerFocused = false;
19210 const cmd =
19211 (evt.ctrlKey ? 1 : 0) | (evt.altKey ? 2 : 0) | (evt.shiftKey ? 4 : 0) | (evt.metaKey ? 8 : 0);
19212 if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
19213 switch (evt.keyCode) {
19214 case 70:
19215 if (!this.supportsIntegratedFind && !evt.shiftKey) {
19216 this.findBar?.open();
19217 handled = true;
19218 }
19219 break;
19220 case 71:
19221 if (!this.supportsIntegratedFind) {
19222 const { state } = this.findController;
19223 if (state) {
19224 const newState = {
19225 source: window,
19226 type: 'again',
19227 findPrevious: cmd === 5 || cmd === 12,
19228 };
19229 eventBus.dispatch('find', {
19230 ...state,
19231 ...newState,
19232 });
19233 }
19234 handled = true;
19235 }
19236 break;
19237 case 61:
19238 case 107:
19239 case 187:
19240 case 171:
19241 this.zoomIn();
19242 handled = true;
19243 break;
19244 case 173:
19245 case 109:
19246 case 189:
19247 this.zoomOut();
19248 handled = true;
19249 break;
19250 case 48:
19251 case 96:
19252 if (!isViewerInPresentationMode) {
19253 setTimeout(() => {
19254 this.zoomReset();
19255 });
19256 handled = false;
19257 }
19258 break;
19259 case 38:
19260 if (isViewerInPresentationMode || this.page > 1) {
19261 this.page = 1;
19262 handled = true;
19263 ensureViewerFocused = true;
19264 }
19265 break;
19266 case 40:
19267 if (isViewerInPresentationMode || this.page < this.pagesCount) {
19268 this.page = this.pagesCount;
19269 handled = true;
19270 ensureViewerFocused = true;
19271 }
19272 break;
19273 }
19274 }
19275 if (cmd === 1 || cmd === 8) {
19276 switch (evt.keyCode) {
19277 case 83:
19278 eventBus.dispatch('download', {
19279 source: window,
19280 });
19281 handled = true;
19282 break;
19283 case 79:
19284 {
19285 eventBus.dispatch('openfile', {
19286 source: window,
19287 });
19288 handled = true;
19289 }
19290 break;
19291 }
19292 }
19293 if (cmd === 3 || cmd === 10) {
19294 switch (evt.keyCode) {
19295 case 80:
19296 this.requestPresentationMode();
19297 handled = true;
19298 this.externalServices.reportTelemetry({
19299 type: 'buttons',
19300 data: {
19301 id: 'presentationModeKeyboard',
19302 },
19303 });
19304 break;
19305 case 71:
19306 if (this.appConfig.toolbar) {
19307 this.appConfig.toolbar.pageNumber.select();
19308 handled = true;
19309 }
19310 break;
19311 }
19312 }
19313 if (handled) {
19314 if (ensureViewerFocused && !isViewerInPresentationMode) {
19315 pdfViewer.focus();
19316 }
19317 evt.preventDefault();
19318 return;
19319 }
19320 const curElement = getActiveOrFocusedElement();
19321 const curElementTagName = curElement?.tagName.toUpperCase();
19322 if (
19323 curElementTagName === 'INPUT' ||
19324 curElementTagName === 'TEXTAREA' ||
19325 curElementTagName === 'SELECT' ||
19326 (curElementTagName === 'BUTTON' && evt.keyCode === 32) ||
19327 curElement?.isContentEditable
19328 ) {
19329 if (evt.keyCode !== 27) {
19330 return;
19331 }
19332 }
19333 if (cmd === 0) {
19334 let turnPage = 0,
19335 turnOnlyIfPageFit = false;
19336 switch (evt.keyCode) {
19337 case 38:
19338 if (this.supportsCaretBrowsingMode) {
19339 this.moveCaret(true, false);
19340 handled = true;
19341 break;
19342 }
19343 case 33:
19344 if (pdfViewer.isVerticalScrollbarEnabled) {
19345 turnOnlyIfPageFit = true;
19346 }
19347 turnPage = -1;
19348 break;
19349 case 8:
19350 if (!isViewerInPresentationMode) {
19351 turnOnlyIfPageFit = true;
19352 }
19353 turnPage = -1;
19354 break;
19355 case 37:
19356 if (this.supportsCaretBrowsingMode) {
19357 return;
19358 }
19359 if (pdfViewer.isHorizontalScrollbarEnabled) {
19360 turnOnlyIfPageFit = true;
19361 }
19362 case 75:
19363 case 80:
19364 turnPage = -1;
19365 break;
19366 case 27:
19367 if (this.secondaryToolbar?.isOpen) {
19368 this.secondaryToolbar.close();
19369 handled = true;
19370 }
19371 if (!this.supportsIntegratedFind && this.findBar?.opened) {
19372 this.findBar.close();
19373 handled = true;
19374 }
19375 break;
19376 case 40:
19377 if (this.supportsCaretBrowsingMode) {
19378 this.moveCaret(false, false);
19379 handled = true;
19380 break;
19381 }
19382 case 34:
19383 if (pdfViewer.isVerticalScrollbarEnabled) {
19384 turnOnlyIfPageFit = true;
19385 }
19386 turnPage = 1;
19387 break;
19388 case 32:
19389 if (!isViewerInPresentationMode) {
19390 turnOnlyIfPageFit = true;
19391 }
19392 turnPage = 1;
19393 break;
19394 case 39:
19395 if (this.supportsCaretBrowsingMode) {
19396 return;
19397 }
19398 if (pdfViewer.isHorizontalScrollbarEnabled) {
19399 turnOnlyIfPageFit = true;
19400 }
19401 case 74:
19402 case 78:
19403 turnPage = 1;
19404 break;
19405 case 36:
19406 if (isViewerInPresentationMode || this.page > 1) {
19407 this.page = 1;
19408 handled = true;
19409 ensureViewerFocused = true;
19410 }
19411 break;
19412 case 35:
19413 if (isViewerInPresentationMode || this.page < this.pagesCount) {
19414 this.page = this.pagesCount;
19415 handled = true;
19416 ensureViewerFocused = true;
19417 }
19418 break;
19419 case 83:
19420 this.pdfCursorTools?.switchTool(CursorTool.SELECT);
19421 break;
19422 case 72:
19423 this.pdfCursorTools?.switchTool(CursorTool.HAND);
19424 break;
19425 case 82:
19426 this.rotatePages(90);
19427 break;
19428 case 115:
19429 this.viewsManager?.toggle();
19430 break;
19431 }
19432 if (turnPage !== 0 && (!turnOnlyIfPageFit || pdfViewer.currentScaleValue === 'page-fit')) {
19433 if (turnPage > 0) {
19434 pdfViewer.nextPage();
19435 } else {
19436 pdfViewer.previousPage();
19437 }
19438 handled = true;
19439 }
19440 }
19441 if (cmd === 4) {
19442 switch (evt.keyCode) {
19443 case 32:
19444 if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== 'page-fit') {
19445 break;
19446 }
19447 pdfViewer.previousPage();
19448 handled = true;
19449 break;
19450 case 38:
19451 this.moveCaret(true, true);
19452 handled = true;
19453 break;
19454 case 40:
19455 this.moveCaret(false, true);
19456 handled = true;
19457 break;
19458 case 82:
19459 this.rotatePages(-90);
19460 break;
19461 }
19462 }
19463 if (!handled && !isViewerInPresentationMode) {
19464 if (
19465 (evt.keyCode >= 33 && evt.keyCode <= 40) ||
19466 (evt.keyCode === 32 && curElementTagName !== 'BUTTON')
19467 ) {
19468 ensureViewerFocused = true;
19469 }
19470 }
19471 if (ensureViewerFocused && !pdfViewer.containsElement(curElement)) {
19472 pdfViewer.focus();
19473 }
19474 if (handled) {
19475 evt.preventDefault();
19476 }
19477}
19478function beforeUnload(evt) {
19479 evt.preventDefault();
19480 evt.returnValue = '';
19481 return false;
19482} // ./web/viewer.js
19483
19484const AppConstants = {
19485 LinkTarget: LinkTarget,
19486 RenderingStates: RenderingStates,
19487 ScrollMode: ScrollMode,
19488 SpreadMode: SpreadMode,
19489};
19490window.PDFViewerApplication = PDFViewerApplication;
19491window.PDFViewerApplicationConstants = AppConstants;
19492window.PDFViewerApplicationOptions = AppOptions;
19493function getViewerConfiguration() {
19494 return {
19495 appContainer: document.body,
19496 principalContainer: document.getElementById('mainContainer'),
19497 mainContainer: document.getElementById('viewerContainer'),
19498 viewerContainer: document.getElementById('viewer'),
19499 viewerAlert: document.getElementById('viewer-alert'),
19500 toolbar: {
19501 container: document.getElementById('toolbarContainer'),
19502 numPages: document.getElementById('numPages'),
19503 pageNumber: document.getElementById('pageNumber'),
19504 scaleSelect: document.getElementById('scaleSelect'),
19505 customScaleOption: document.getElementById('customScaleOption'),
19506 previous: document.getElementById('previous'),
19507 next: document.getElementById('next'),
19508 zoomIn: document.getElementById('zoomInButton'),
19509 zoomOut: document.getElementById('zoomOutButton'),
19510 print: document.getElementById('printButton'),
19511 editorCommentButton: document.getElementById('editorCommentButton'),
19512 editorCommentParamsToolbar: document.getElementById('editorCommentParamsToolbar'),
19513 editorFreeTextButton: document.getElementById('editorFreeTextButton'),
19514 editorFreeTextParamsToolbar: document.getElementById('editorFreeTextParamsToolbar'),
19515 editorHighlightButton: document.getElementById('editorHighlightButton'),
19516 editorHighlightParamsToolbar: document.getElementById('editorHighlightParamsToolbar'),
19517 editorHighlightColorPicker: document.getElementById('editorHighlightColorPicker'),
19518 editorInkButton: document.getElementById('editorInkButton'),
19519 editorInkParamsToolbar: document.getElementById('editorInkParamsToolbar'),
19520 editorStampButton: document.getElementById('editorStampButton'),
19521 editorStampParamsToolbar: document.getElementById('editorStampParamsToolbar'),
19522 editorSignatureButton: document.getElementById('editorSignatureButton'),
19523 editorSignatureParamsToolbar: document.getElementById('editorSignatureParamsToolbar'),
19524 download: document.getElementById('downloadButton'),
19525 },
19526 secondaryToolbar: {
19527 toolbar: document.getElementById('secondaryToolbar'),
19528 toggleButton: document.getElementById('secondaryToolbarToggleButton'),
19529 presentationModeButton: document.getElementById('presentationMode'),
19530 openFileButton: document.getElementById('secondaryOpenFile'),
19531 printButton: document.getElementById('secondaryPrint'),
19532 downloadButton: document.getElementById('secondaryDownload'),
19533 viewBookmarkButton: document.getElementById('viewBookmark'),
19534 firstPageButton: document.getElementById('firstPage'),
19535 lastPageButton: document.getElementById('lastPage'),
19536 pageRotateCwButton: document.getElementById('pageRotateCw'),
19537 pageRotateCcwButton: document.getElementById('pageRotateCcw'),
19538 cursorSelectToolButton: document.getElementById('cursorSelectTool'),
19539 cursorHandToolButton: document.getElementById('cursorHandTool'),
19540 scrollPageButton: document.getElementById('scrollPage'),
19541 scrollVerticalButton: document.getElementById('scrollVertical'),
19542 scrollHorizontalButton: document.getElementById('scrollHorizontal'),
19543 scrollWrappedButton: document.getElementById('scrollWrapped'),
19544 spreadNoneButton: document.getElementById('spreadNone'),
19545 spreadOddButton: document.getElementById('spreadOdd'),
19546 spreadEvenButton: document.getElementById('spreadEven'),
19547 imageAltTextSettingsButton: document.getElementById('imageAltTextSettings'),
19548 imageAltTextSettingsSeparator: document.getElementById('imageAltTextSettingsSeparator'),
19549 documentPropertiesButton: document.getElementById('documentProperties'),
19550 },
19551 viewsManager: {
19552 outerContainer: document.getElementById('outerContainer'),
19553 toggleButton: document.getElementById('viewsManagerToggleButton'),
19554 sidebarContainer: document.getElementById('viewsManager'),
19555 resizer: document.getElementById('viewsManagerResizer'),
19556 thumbnailButton: document.getElementById('thumbnailsViewMenu'),
19557 outlineButton: document.getElementById('outlinesViewMenu'),
19558 attachmentsButton: document.getElementById('attachmentsViewMenu'),
19559 layersButton: document.getElementById('layersViewMenu'),
19560 viewsManagerSelectorButton: document.getElementById('viewsManagerSelectorButton'),
19561 viewsManagerSelectorOptions: document.getElementById('viewsManagerSelectorOptions'),
19562 thumbnailsView: document.getElementById('thumbnailsView'),
19563 outlinesView: document.getElementById('outlinesView'),
19564 attachmentsView: document.getElementById('attachmentsView'),
19565 layersView: document.getElementById('layersView'),
19566 viewsManagerAddFileButton: document.getElementById('viewsManagerAddFileButton'),
19567 viewsManagerCurrentOutlineButton: document.getElementById('viewsManagerCurrentOutlineButton'),
19568 viewsManagerHeaderLabel: document.getElementById('viewsManagerHeaderLabel'),
19569 manageMenu: {
19570 button: document.getElementById('viewsManagerStatusActionButton'),
19571 menu: document.getElementById('viewsManagerStatusActionOptions'),
19572 copy: document.getElementById('viewsManagerStatusActionCopy'),
19573 cut: document.getElementById('viewsManagerStatusActionCut'),
19574 delete: document.getElementById('viewsManagerStatusActionDelete'),
19575 saveAs: document.getElementById('viewsManagerStatusActionSaveAs'),
19576 },
19577 },
19578 findBar: {
19579 bar: document.getElementById('findbar'),
19580 toggleButton: document.getElementById('viewFindButton'),
19581 findField: document.getElementById('findInput'),
19582 highlightAllCheckbox: document.getElementById('findHighlightAll'),
19583 caseSensitiveCheckbox: document.getElementById('findMatchCase'),
19584 matchDiacriticsCheckbox: document.getElementById('findMatchDiacritics'),
19585 entireWordCheckbox: document.getElementById('findEntireWord'),
19586 findMsg: document.getElementById('findMsg'),
19587 findResultsCount: document.getElementById('findResultsCount'),
19588 findPreviousButton: document.getElementById('findPreviousButton'),
19589 findNextButton: document.getElementById('findNextButton'),
19590 },
19591 passwordOverlay: {
19592 dialog: document.getElementById('passwordDialog'),
19593 label: document.getElementById('passwordText'),
19594 input: document.getElementById('password'),
19595 submitButton: document.getElementById('passwordSubmit'),
19596 cancelButton: document.getElementById('passwordCancel'),
19597 },
19598 documentProperties: {
19599 dialog: document.getElementById('documentPropertiesDialog'),
19600 closeButton: document.getElementById('documentPropertiesClose'),
19601 fields: {
19602 fileName: document.getElementById('fileNameField'),
19603 fileSize: document.getElementById('fileSizeField'),
19604 title: document.getElementById('titleField'),
19605 author: document.getElementById('authorField'),
19606 subject: document.getElementById('subjectField'),
19607 keywords: document.getElementById('keywordsField'),
19608 creationDate: document.getElementById('creationDateField'),
19609 modificationDate: document.getElementById('modificationDateField'),
19610 creator: document.getElementById('creatorField'),
19611 producer: document.getElementById('producerField'),
19612 version: document.getElementById('versionField'),
19613 pageCount: document.getElementById('pageCountField'),
19614 pageSize: document.getElementById('pageSizeField'),
19615 linearized: document.getElementById('linearizedField'),
19616 },
19617 },
19618 altTextDialog: {
19619 dialog: document.getElementById('altTextDialog'),
19620 optionDescription: document.getElementById('descriptionButton'),
19621 optionDecorative: document.getElementById('decorativeButton'),
19622 textarea: document.getElementById('descriptionTextarea'),
19623 cancelButton: document.getElementById('altTextCancel'),
19624 saveButton: document.getElementById('altTextSave'),
19625 },
19626 newAltTextDialog: {
19627 dialog: document.getElementById('newAltTextDialog'),
19628 title: document.getElementById('newAltTextTitle'),
19629 descriptionContainer: document.getElementById('newAltTextDescriptionContainer'),
19630 textarea: document.getElementById('newAltTextDescriptionTextarea'),
19631 disclaimer: document.getElementById('newAltTextDisclaimer'),
19632 learnMore: document.getElementById('newAltTextLearnMore'),
19633 imagePreview: document.getElementById('newAltTextImagePreview'),
19634 createAutomatically: document.getElementById('newAltTextCreateAutomatically'),
19635 createAutomaticallyButton: document.getElementById('newAltTextCreateAutomaticallyButton'),
19636 downloadModel: document.getElementById('newAltTextDownloadModel'),
19637 downloadModelDescription: document.getElementById('newAltTextDownloadModelDescription'),
19638 error: document.getElementById('newAltTextError'),
19639 errorCloseButton: document.getElementById('newAltTextCloseButton'),
19640 cancelButton: document.getElementById('newAltTextCancel'),
19641 notNowButton: document.getElementById('newAltTextNotNow'),
19642 saveButton: document.getElementById('newAltTextSave'),
19643 },
19644 altTextSettingsDialog: {
19645 dialog: document.getElementById('altTextSettingsDialog'),
19646 createModelButton: document.getElementById('createModelButton'),
19647 aiModelSettings: document.getElementById('aiModelSettings'),
19648 learnMore: document.getElementById('altTextSettingsLearnMore'),
19649 deleteModelButton: document.getElementById('deleteModelButton'),
19650 downloadModelButton: document.getElementById('downloadModelButton'),
19651 showAltTextDialogButton: document.getElementById('showAltTextDialogButton'),
19652 altTextSettingsCloseButton: document.getElementById('altTextSettingsCloseButton'),
19653 closeButton: document.getElementById('altTextSettingsCloseButton'),
19654 },
19655 addSignatureDialog: {
19656 dialog: document.getElementById('addSignatureDialog'),
19657 panels: document.getElementById('addSignatureActionContainer'),
19658 typeButton: document.getElementById('addSignatureTypeButton'),
19659 typeInput: document.getElementById('addSignatureTypeInput'),
19660 drawButton: document.getElementById('addSignatureDrawButton'),
19661 drawSVG: document.getElementById('addSignatureDraw'),
19662 drawPlaceholder: document.getElementById('addSignatureDrawPlaceholder'),
19663 drawThickness: document.getElementById('addSignatureDrawThickness'),
19664 imageButton: document.getElementById('addSignatureImageButton'),
19665 imageSVG: document.getElementById('addSignatureImage'),
19666 imagePlaceholder: document.getElementById('addSignatureImagePlaceholder'),
19667 imagePicker: document.getElementById('addSignatureFilePicker'),
19668 imagePickerLink: document.getElementById('addSignatureImageBrowse'),
19669 description: document.getElementById('addSignatureDescription'),
19670 clearButton: document.getElementById('clearSignatureButton'),
19671 saveContainer: document.getElementById('addSignatureSaveContainer'),
19672 saveCheckbox: document.getElementById('addSignatureSaveCheckbox'),
19673 errorBar: document.getElementById('addSignatureError'),
19674 errorTitle: document.getElementById('addSignatureErrorTitle'),
19675 errorDescription: document.getElementById('addSignatureErrorDescription'),
19676 errorCloseButton: document.getElementById('addSignatureErrorCloseButton'),
19677 cancelButton: document.getElementById('addSignatureCancelButton'),
19678 addButton: document.getElementById('addSignatureAddButton'),
19679 },
19680 editSignatureDialog: {
19681 dialog: document.getElementById('editSignatureDescriptionDialog'),
19682 description: document.getElementById('editSignatureDescription'),
19683 editSignatureView: document.getElementById('editSignatureView'),
19684 cancelButton: document.getElementById('editSignatureCancelButton'),
19685 updateButton: document.getElementById('editSignatureUpdateButton'),
19686 },
19687 annotationEditorParams: {
19688 editorCommentsSidebar: document.getElementById('editorCommentsSidebar'),
19689 editorCommentsSidebarCount: document.getElementById('editorCommentsSidebarCount'),
19690 editorCommentsSidebarTitle: document.getElementById('editorCommentsSidebarTitle'),
19691 editorCommentsSidebarCloseButton: document.getElementById('editorCommentsSidebarCloseButton'),
19692 editorCommentsSidebarList: document.getElementById('editorCommentsSidebarList'),
19693 editorCommentsSidebarResizer: document.getElementById('editorCommentsSidebarResizer'),
19694 editorFreeTextFontSize: document.getElementById('editorFreeTextFontSize'),
19695 editorFreeTextColor: document.getElementById('editorFreeTextColor'),
19696 editorInkColor: document.getElementById('editorInkColor'),
19697 editorInkThickness: document.getElementById('editorInkThickness'),
19698 editorInkOpacity: document.getElementById('editorInkOpacity'),
19699 editorStampAddImage: document.getElementById('editorStampAddImage'),
19700 editorSignatureAddSignature: document.getElementById('editorSignatureAddSignature'),
19701 editorFreeHighlightThickness: document.getElementById('editorFreeHighlightThickness'),
19702 editorHighlightShowAll: document.getElementById('editorHighlightShowAll'),
19703 },
19704 printContainer: document.getElementById('printContainer'),
19705 editorUndoBar: {
19706 container: document.getElementById('editorUndoBar'),
19707 message: document.getElementById('editorUndoBarMessage'),
19708 undoButton: document.getElementById('editorUndoBarUndoButton'),
19709 closeButton: document.getElementById('editorUndoBarCloseButton'),
19710 },
19711 editCommentDialog: {
19712 dialog: document.getElementById('commentManagerDialog'),
19713 toolbar: document.getElementById('commentManagerToolbar'),
19714 title: document.getElementById('commentManagerTitle'),
19715 textInput: document.getElementById('commentManagerTextInput'),
19716 cancelButton: document.getElementById('commentManagerCancelButton'),
19717 saveButton: document.getElementById('commentManagerSaveButton'),
19718 },
19719 };
19720}
19721function webViewerLoad() {
19722 const config = getViewerConfiguration();
19723 const event = new CustomEvent('webviewerloaded', {
19724 bubbles: true,
19725 cancelable: true,
19726 detail: {
19727 source: window,
19728 },
19729 });
19730 try {
19731 parent.document.dispatchEvent(event);
19732 } catch (ex) {
19733 console.error('webviewerloaded:', ex);
19734 document.dispatchEvent(event);
19735 }
19736 PDFViewerApplication.run(config);
19737}
19738document.blockUnblockOnload?.(true);
19739if (document.readyState === 'interactive' || document.readyState === 'complete') {
19740 webViewerLoad();
19741} else {
19742 document.addEventListener('DOMContentLoaded', webViewerLoad, true);
19743}
19744
19745export {
19746 PDFViewerApplication,
19747 AppConstants as PDFViewerApplicationConstants,
19748 AppOptions as PDFViewerApplicationOptions,
19749};
19750
19751//# sourceMappingURL=viewer.mjs.map