@vaadin-component-factory/vcf-pdf-viewer 4.1.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,762 +1,762 @@
1
- /* Copyright 2012 Mozilla Foundation
2
- *
3
- * Licensed under the Apache License, Version 2.0 (the "License");
4
- * you may not use this file except in compliance with the License.
5
- * You may obtain a copy of the License at
6
- *
7
- * http://www.apache.org/licenses/LICENSE-2.0
8
- *
9
- * Unless required by applicable law or agreed to in writing, software
10
- * distributed under the License is distributed on an "AS IS" BASIS,
11
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- * See the License for the specific language governing permissions and
13
- * limitations under the License.
14
- */
15
-
16
- const DEFAULT_SCALE_VALUE = "auto";
17
- const DEFAULT_SCALE = 1.0;
18
- const DEFAULT_SCALE_DELTA = 1.1;
19
- const MIN_SCALE = 0.1;
20
- const MAX_SCALE = 10.0;
21
- const UNKNOWN_SCALE = 0;
22
- const MAX_AUTO_SCALE = 1.25;
23
- const SCROLLBAR_PADDING = 40;
24
- const VERTICAL_PADDING = 5;
25
- const RenderingStates = {
26
- INITIAL: 0,
27
- RUNNING: 1,
28
- PAUSED: 2,
29
- FINISHED: 3
30
- };
31
- const PresentationModeState = {
32
- UNKNOWN: 0,
33
- NORMAL: 1,
34
- CHANGING: 2,
35
- FULLSCREEN: 3
36
- };
37
- const SidebarView = {
38
- UNKNOWN: -1,
39
- NONE: 0,
40
- THUMBS: 1,
41
- // Default value.
42
- OUTLINE: 2,
43
- ATTACHMENTS: 3,
44
- LAYERS: 4
45
- };
46
- const TextLayerMode = {
47
- DISABLE: 0,
48
- ENABLE: 1,
49
- ENABLE_PERMISSIONS: 2
50
- };
51
- const ScrollMode = {
52
- UNKNOWN: -1,
53
- VERTICAL: 0,
54
- // Default value.
55
- HORIZONTAL: 1,
56
- WRAPPED: 2,
57
- PAGE: 3
58
- };
59
- const SpreadMode = {
60
- UNKNOWN: -1,
61
- NONE: 0,
62
- // Default value.
63
- ODD: 1,
64
- EVEN: 2
65
- };
66
- const CursorTool = {
67
- SELECT: 0,
68
- // The default value.
69
- HAND: 1,
70
- ZOOM: 2
71
- };
72
-
73
- // Used by `PDFViewerApplication`, and by the API unit-tests.
74
- const AutoPrintRegExp = /\bprint\s*\(/;
75
-
76
- /**
77
- * Scale factors for the canvas, necessary with HiDPI displays.
78
- */
79
- class OutputScale {
80
- constructor() {
81
- const pixelRatio = window.devicePixelRatio || 1;
82
-
83
- /**
84
- * @type {number} Horizontal scale.
85
- */
86
- this.sx = pixelRatio;
87
-
88
- /**
89
- * @type {number} Vertical scale.
90
- */
91
- this.sy = pixelRatio;
92
- }
93
-
94
- /**
95
- * @type {boolean} Returns `true` when scaling is required, `false` otherwise.
96
- */
97
- get scaled() {
98
- return this.sx !== 1 || this.sy !== 1;
99
- }
100
- }
101
-
102
- /**
103
- * Scrolls specified element into view of its parent.
104
- * @param {HTMLElement} element - The element to be visible.
105
- * @param {Object} [spot] - An object with optional top and left properties,
106
- * specifying the offset from the top left edge.
107
- * @param {number} [spot.left]
108
- * @param {number} [spot.top]
109
- * @param {boolean} [scrollMatches] - When scrolling search results into view,
110
- * ignore elements that either: Contains marked content identifiers,
111
- * or have the CSS-rule `overflow: hidden;` set. The default value is `false`.
112
- */
113
- function scrollIntoView(element, spot, scrollMatches = false) {
114
- // Assuming offsetParent is available (it's not available when viewer is in
115
- // hidden iframe or object). We have to scroll: if the offsetParent is not set
116
- // producing the error. See also animationStarted.
117
- let parent = element.offsetParent;
118
- if (!parent) {
119
- console.error("offsetParent is not set -- cannot scroll");
120
- return;
121
- }
122
- let offsetY = element.offsetTop + element.clientTop;
123
- let offsetX = element.offsetLeft + element.clientLeft;
124
- while (parent.clientHeight === parent.scrollHeight && parent.clientWidth === parent.scrollWidth || scrollMatches && (parent.classList.contains("markedContent") || getComputedStyle(parent).overflow === "hidden")) {
125
- offsetY += parent.offsetTop;
126
- offsetX += parent.offsetLeft;
127
- parent = parent.offsetParent;
128
- if (!parent) {
129
- return; // no need to scroll
130
- }
131
- }
132
- if (spot) {
133
- if (spot.top !== undefined) {
134
- offsetY += spot.top;
135
- }
136
- if (spot.left !== undefined) {
137
- offsetX += spot.left;
138
- parent.scrollLeft = offsetX;
139
- }
140
- }
141
- parent.scrollTop = offsetY;
142
- }
143
-
144
- /**
145
- * Helper function to start monitoring the scroll event and converting them into
146
- * PDF.js friendly one: with scroll debounce and scroll direction.
147
- */
148
- function watchScroll(viewAreaElement, callback) {
149
- const debounceScroll = function (evt) {
150
- if (rAF) {
151
- return;
152
- }
153
- // schedule an invocation of scroll for next animation frame.
154
- rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
155
- rAF = null;
156
- const currentX = viewAreaElement.scrollLeft;
157
- const lastX = state.lastX;
158
- if (currentX !== lastX) {
159
- state.right = currentX > lastX;
160
- }
161
- state.lastX = currentX;
162
- const currentY = viewAreaElement.scrollTop;
163
- const lastY = state.lastY;
164
- if (currentY !== lastY) {
165
- state.down = currentY > lastY;
166
- }
167
- state.lastY = currentY;
168
- callback(state);
169
- });
170
- };
171
- const state = {
172
- right: true,
173
- down: true,
174
- lastX: viewAreaElement.scrollLeft,
175
- lastY: viewAreaElement.scrollTop,
176
- _eventHandler: debounceScroll
177
- };
178
- let rAF = null;
179
- viewAreaElement.addEventListener("scroll", debounceScroll, true);
180
- return state;
181
- }
182
-
183
- /**
184
- * Helper function to parse query string (e.g. ?param1=value&param2=...).
185
- * @param {string} query
186
- * @returns {Map}
187
- */
188
- function parseQueryString(query) {
189
- const params = new Map();
190
- for (const [key, value] of new URLSearchParams(query)) {
191
- params.set(key.toLowerCase(), value);
192
- }
193
- return params;
194
- }
195
- const InvisibleCharsRegExp = /[\x00-\x1F]/g;
196
-
197
- /**
198
- * @param {string} str
199
- * @param {boolean} [replaceInvisible]
200
- */
201
- function removeNullCharacters(str, replaceInvisible = false) {
202
- if (!InvisibleCharsRegExp.test(str)) {
203
- return str;
204
- }
205
- if (replaceInvisible) {
206
- return str.replaceAll(InvisibleCharsRegExp, m => m === "\x00" ? "" : " ");
207
- }
208
- return str.replaceAll("\x00", "");
209
- }
210
-
211
- /**
212
- * Use binary search to find the index of the first item in a given array which
213
- * passes a given condition. The items are expected to be sorted in the sense
214
- * that if the condition is true for one item in the array, then it is also true
215
- * for all following items.
216
- *
217
- * @returns {number} Index of the first array element to pass the test,
218
- * or |items.length| if no such element exists.
219
- */
220
- function binarySearchFirstItem(items, condition, start = 0) {
221
- let minIndex = start;
222
- let maxIndex = items.length - 1;
223
- if (maxIndex < 0 || !condition(items[maxIndex])) {
224
- return items.length;
225
- }
226
- if (condition(items[minIndex])) {
227
- return minIndex;
228
- }
229
- while (minIndex < maxIndex) {
230
- const currentIndex = minIndex + maxIndex >> 1;
231
- const currentItem = items[currentIndex];
232
- if (condition(currentItem)) {
233
- maxIndex = currentIndex;
234
- } else {
235
- minIndex = currentIndex + 1;
236
- }
237
- }
238
- return minIndex; /* === maxIndex */
239
- }
240
-
241
- /**
242
- * Approximates float number as a fraction using Farey sequence (max order
243
- * of 8).
244
- * @param {number} x - Positive float number.
245
- * @returns {Array} Estimated fraction: the first array item is a numerator,
246
- * the second one is a denominator.
247
- */
248
- function approximateFraction(x) {
249
- // Fast paths for int numbers or their inversions.
250
- if (Math.floor(x) === x) {
251
- return [x, 1];
252
- }
253
- const xinv = 1 / x;
254
- const limit = 8;
255
- if (xinv > limit) {
256
- return [1, limit];
257
- } else if (Math.floor(xinv) === xinv) {
258
- return [1, xinv];
259
- }
260
- const x_ = x > 1 ? xinv : x;
261
- // a/b and c/d are neighbours in Farey sequence.
262
- let a = 0,
263
- b = 1,
264
- c = 1,
265
- d = 1;
266
- // Limiting search to order 8.
267
- while (true) {
268
- // Generating next term in sequence (order of q).
269
- const p = a + c,
270
- q = b + d;
271
- if (q > limit) {
272
- break;
273
- }
274
- if (x_ <= p / q) {
275
- c = p;
276
- d = q;
277
- } else {
278
- a = p;
279
- b = q;
280
- }
281
- }
282
- let result;
283
- // Select closest of the neighbours to x.
284
- if (x_ - a / b < c / d - x_) {
285
- result = x_ === x ? [a, b] : [b, a];
286
- } else {
287
- result = x_ === x ? [c, d] : [d, c];
288
- }
289
- return result;
290
- }
291
- function roundToDivide(x, div) {
292
- const r = x % div;
293
- return r === 0 ? x : Math.round(x - r + div);
294
- }
295
-
296
- /**
297
- * @typedef {Object} GetPageSizeInchesParameters
298
- * @property {number[]} view
299
- * @property {number} userUnit
300
- * @property {number} rotate
301
- */
302
-
303
- /**
304
- * @typedef {Object} PageSize
305
- * @property {number} width - In inches.
306
- * @property {number} height - In inches.
307
- */
308
-
309
- /**
310
- * Gets the size of the specified page, converted from PDF units to inches.
311
- * @param {GetPageSizeInchesParameters} params
312
- * @returns {PageSize}
313
- */
314
- function getPageSizeInches({
315
- view,
316
- userUnit,
317
- rotate
318
- }) {
319
- const [x1, y1, x2, y2] = view;
320
- // We need to take the page rotation into account as well.
321
- const changeOrientation = rotate % 180 !== 0;
322
- const width = (x2 - x1) / 72 * userUnit;
323
- const height = (y2 - y1) / 72 * userUnit;
324
- return {
325
- width: changeOrientation ? height : width,
326
- height: changeOrientation ? width : height
327
- };
328
- }
329
-
330
- /**
331
- * Helper function for getVisibleElements.
332
- *
333
- * @param {number} index - initial guess at the first visible element
334
- * @param {Array} views - array of pages, into which `index` is an index
335
- * @param {number} top - the top of the scroll pane
336
- * @returns {number} less than or equal to `index` that is definitely at or
337
- * before the first visible element in `views`, but not by too much. (Usually,
338
- * this will be the first element in the first partially visible row in
339
- * `views`, although sometimes it goes back one row further.)
340
- */
341
- function backtrackBeforeAllVisibleElements(index, views, top) {
342
- // binarySearchFirstItem's assumption is that the input is ordered, with only
343
- // one index where the conditions flips from false to true: [false ...,
344
- // true...]. With vertical scrolling and spreads, it is possible to have
345
- // [false ..., true, false, true ...]. With wrapped scrolling we can have a
346
- // similar sequence, with many more mixed true and false in the middle.
347
- //
348
- // So there is no guarantee that the binary search yields the index of the
349
- // first visible element. It could have been any of the other visible elements
350
- // that were preceded by a hidden element.
351
-
352
- // Of course, if either this element or the previous (hidden) element is also
353
- // the first element, there's nothing to worry about.
354
- if (index < 2) {
355
- return index;
356
- }
357
-
358
- // That aside, the possible cases are represented below.
359
- //
360
- // **** = fully hidden
361
- // A*B* = mix of partially visible and/or hidden pages
362
- // CDEF = fully visible
363
- //
364
- // (1) Binary search could have returned A, in which case we can stop.
365
- // (2) Binary search could also have returned B, in which case we need to
366
- // check the whole row.
367
- // (3) Binary search could also have returned C, in which case we need to
368
- // check the whole previous row.
369
- //
370
- // There's one other possibility:
371
- //
372
- // **** = fully hidden
373
- // ABCD = mix of fully and/or partially visible pages
374
- //
375
- // (4) Binary search could only have returned A.
376
-
377
- // Initially assume that we need to find the beginning of the current row
378
- // (case 1, 2, or 4), which means finding a page that is above the current
379
- // page's top. If the found page is partially visible, we're definitely not in
380
- // case 3, and this assumption is correct.
381
- let elt = views[index].div;
382
- let pageTop = elt.offsetTop + elt.clientTop;
383
- if (pageTop >= top) {
384
- // The found page is fully visible, so we're actually either in case 3 or 4,
385
- // and unfortunately we can't tell the difference between them without
386
- // scanning the entire previous row, so we just conservatively assume that
387
- // we do need to backtrack to that row. In both cases, the previous page is
388
- // in the previous row, so use its top instead.
389
- elt = views[index - 1].div;
390
- pageTop = elt.offsetTop + elt.clientTop;
391
- }
392
-
393
- // Now we backtrack to the first page that still has its bottom below
394
- // `pageTop`, which is the top of a page in the first visible row (unless
395
- // we're in case 4, in which case it's the row before that).
396
- // `index` is found by binary search, so the page at `index - 1` is
397
- // invisible and we can start looking for potentially visible pages from
398
- // `index - 2`. (However, if this loop terminates on its first iteration,
399
- // which is the case when pages are stacked vertically, `index` should remain
400
- // unchanged, so we use a distinct loop variable.)
401
- for (let i = index - 2; i >= 0; --i) {
402
- elt = views[i].div;
403
- if (elt.offsetTop + elt.clientTop + elt.clientHeight <= pageTop) {
404
- // We have reached the previous row, so stop now.
405
- // This loop is expected to terminate relatively quickly because the
406
- // number of pages per row is expected to be small.
407
- break;
408
- }
409
- index = i;
410
- }
411
- return index;
412
- }
413
-
414
- /**
415
- * @typedef {Object} GetVisibleElementsParameters
416
- * @property {HTMLElement} scrollEl - A container that can possibly scroll.
417
- * @property {Array} views - Objects with a `div` property that contains an
418
- * HTMLElement, which should all be descendants of `scrollEl` satisfying the
419
- * relevant layout assumptions.
420
- * @property {boolean} sortByVisibility - If `true`, the returned elements are
421
- * sorted in descending order of the percent of their padding box that is
422
- * visible. The default value is `false`.
423
- * @property {boolean} horizontal - If `true`, the elements are assumed to be
424
- * laid out horizontally instead of vertically. The default value is `false`.
425
- * @property {boolean} rtl - If `true`, the `scrollEl` container is assumed to
426
- * be in right-to-left mode. The default value is `false`.
427
- */
428
-
429
- /**
430
- * Generic helper to find out what elements are visible within a scroll pane.
431
- *
432
- * Well, pretty generic. There are some assumptions placed on the elements
433
- * referenced by `views`:
434
- * - If `horizontal`, no left of any earlier element is to the right of the
435
- * left of any later element.
436
- * - Otherwise, `views` can be split into contiguous rows where, within a row,
437
- * no top of any element is below the bottom of any other element, and
438
- * between rows, no bottom of any element in an earlier row is below the
439
- * top of any element in a later row.
440
- *
441
- * (Here, top, left, etc. all refer to the padding edge of the element in
442
- * question. For pages, that ends up being equivalent to the bounding box of the
443
- * rendering canvas. Earlier and later refer to index in `views`, not page
444
- * layout.)
445
- *
446
- * @param {GetVisibleElementsParameters} params
447
- * @returns {Object} `{ first, last, views: [{ id, x, y, view, percent }] }`
448
- */
449
- function getVisibleElements({
450
- scrollEl,
451
- views,
452
- sortByVisibility = false,
453
- horizontal = false,
454
- rtl = false
455
- }) {
456
- const top = scrollEl.scrollTop,
457
- bottom = top + scrollEl.clientHeight;
458
- const left = scrollEl.scrollLeft,
459
- right = left + scrollEl.clientWidth;
460
-
461
- // Throughout this "generic" function, comments will assume we're working with
462
- // PDF document pages, which is the most important and complex case. In this
463
- // case, the visible elements we're actually interested is the page canvas,
464
- // which is contained in a wrapper which adds no padding/border/margin, which
465
- // is itself contained in `view.div` which adds no padding (but does add a
466
- // border). So, as specified in this function's doc comment, this function
467
- // does all of its work on the padding edge of the provided views, starting at
468
- // offsetLeft/Top (which includes margin) and adding clientLeft/Top (which is
469
- // the border). Adding clientWidth/Height gets us the bottom-right corner of
470
- // the padding edge.
471
- function isElementBottomAfterViewTop(view) {
472
- const element = view.div;
473
- const elementBottom = element.offsetTop + element.clientTop + element.clientHeight;
474
- return elementBottom > top;
475
- }
476
- function isElementNextAfterViewHorizontally(view) {
477
- const element = view.div;
478
- const elementLeft = element.offsetLeft + element.clientLeft;
479
- const elementRight = elementLeft + element.clientWidth;
480
- return rtl ? elementLeft < right : elementRight > left;
481
- }
482
- const visible = [],
483
- ids = new Set(),
484
- numViews = views.length;
485
- let firstVisibleElementInd = binarySearchFirstItem(views, horizontal ? isElementNextAfterViewHorizontally : isElementBottomAfterViewTop);
486
-
487
- // Please note the return value of the `binarySearchFirstItem` function when
488
- // no valid element is found (hence the `firstVisibleElementInd` check below).
489
- if (firstVisibleElementInd > 0 && firstVisibleElementInd < numViews && !horizontal) {
490
- // In wrapped scrolling (or vertical scrolling with spreads), with some page
491
- // sizes, isElementBottomAfterViewTop doesn't satisfy the binary search
492
- // condition: there can be pages with bottoms above the view top between
493
- // pages with bottoms below. This function detects and corrects that error;
494
- // see it for more comments.
495
- firstVisibleElementInd = backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top);
496
- }
497
-
498
- // lastEdge acts as a cutoff for us to stop looping, because we know all
499
- // subsequent pages will be hidden.
500
- //
501
- // When using wrapped scrolling or vertical scrolling with spreads, we can't
502
- // simply stop the first time we reach a page below the bottom of the view;
503
- // the tops of subsequent pages on the same row could still be visible. In
504
- // horizontal scrolling, we don't have that issue, so we can stop as soon as
505
- // we pass `right`, without needing the code below that handles the -1 case.
506
- let lastEdge = horizontal ? right : -1;
507
- for (let i = firstVisibleElementInd; i < numViews; i++) {
508
- const view = views[i],
509
- element = view.div;
510
- const currentWidth = element.offsetLeft + element.clientLeft;
511
- const currentHeight = element.offsetTop + element.clientTop;
512
- const viewWidth = element.clientWidth,
513
- viewHeight = element.clientHeight;
514
- const viewRight = currentWidth + viewWidth;
515
- const viewBottom = currentHeight + viewHeight;
516
- if (lastEdge === -1) {
517
- // As commented above, this is only needed in non-horizontal cases.
518
- // Setting lastEdge to the bottom of the first page that is partially
519
- // visible ensures that the next page fully below lastEdge is on the
520
- // next row, which has to be fully hidden along with all subsequent rows.
521
- if (viewBottom >= bottom) {
522
- lastEdge = viewBottom;
523
- }
524
- } else if ((horizontal ? currentWidth : currentHeight) > lastEdge) {
525
- break;
526
- }
527
- if (viewBottom <= top || currentHeight >= bottom || viewRight <= left || currentWidth >= right) {
528
- continue;
529
- }
530
- const hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, viewBottom - bottom);
531
- const hiddenWidth = Math.max(0, left - currentWidth) + Math.max(0, viewRight - right);
532
- const fractionHeight = (viewHeight - hiddenHeight) / viewHeight,
533
- fractionWidth = (viewWidth - hiddenWidth) / viewWidth;
534
- const percent = fractionHeight * fractionWidth * 100 | 0;
535
- visible.push({
536
- id: view.id,
537
- x: currentWidth,
538
- y: currentHeight,
539
- view,
540
- percent,
541
- widthPercent: fractionWidth * 100 | 0
542
- });
543
- ids.add(view.id);
544
- }
545
- const first = visible[0],
546
- last = visible.at(-1);
547
- if (sortByVisibility) {
548
- visible.sort(function (a, b) {
549
- const pc = a.percent - b.percent;
550
- if (Math.abs(pc) > 0.001) {
551
- return -pc;
552
- }
553
- return a.id - b.id; // ensure stability
554
- });
555
- }
556
- return {
557
- first,
558
- last,
559
- views: visible,
560
- ids
561
- };
562
- }
563
- function normalizeWheelEventDirection(evt) {
564
- let delta = Math.hypot(evt.deltaX, evt.deltaY);
565
- const angle = Math.atan2(evt.deltaY, evt.deltaX);
566
- if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
567
- // All that is left-up oriented has to change the sign.
568
- delta = -delta;
569
- }
570
- return delta;
571
- }
572
- function normalizeWheelEventDelta(evt) {
573
- const deltaMode = evt.deltaMode; // Avoid being affected by bug 1392460.
574
- let delta = normalizeWheelEventDirection(evt);
575
- const MOUSE_PIXELS_PER_LINE = 30;
576
- const MOUSE_LINES_PER_PAGE = 30;
577
-
578
- // Converts delta to per-page units
579
- if (deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
580
- delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
581
- } else if (deltaMode === WheelEvent.DOM_DELTA_LINE) {
582
- delta /= MOUSE_LINES_PER_PAGE;
583
- }
584
- return delta;
585
- }
586
- function isValidRotation(angle) {
587
- return Number.isInteger(angle) && angle % 90 === 0;
588
- }
589
- function isValidScrollMode(mode) {
590
- return Number.isInteger(mode) && Object.values(ScrollMode).includes(mode) && mode !== ScrollMode.UNKNOWN;
591
- }
592
- function isValidSpreadMode(mode) {
593
- return Number.isInteger(mode) && Object.values(SpreadMode).includes(mode) && mode !== SpreadMode.UNKNOWN;
594
- }
595
- function isPortraitOrientation(size) {
596
- return size.width <= size.height;
597
- }
598
-
599
- /**
600
- * Promise that is resolved when DOM window becomes visible.
601
- */
602
- const animationStarted = new Promise(function (resolve) {
603
- if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("LIB") && typeof window === "undefined") {
604
- // Prevent "ReferenceError: window is not defined" errors when running the
605
- // unit-tests in Node.js environments.
606
- setTimeout(resolve, 20);
607
- return;
608
- }
609
- window.requestAnimationFrame(resolve);
610
- });
611
- const docStyle = typeof PDFJSDev !== "undefined" && PDFJSDev.test("LIB") && typeof document === "undefined" ? null : document.documentElement.style;
612
- function clamp(v, min, max) {
613
- return Math.min(Math.max(v, min), max);
614
- }
615
- class ProgressBar {
616
- #classList = null;
617
- #disableAutoFetchTimeout = null;
618
- #percent = 0;
619
- #style = null;
620
- #visible = true;
621
- constructor(bar) {
622
- this.#classList = bar.classList;
623
- this.#style = bar.style;
624
- }
625
- get percent() {
626
- return this.#percent;
627
- }
628
- set percent(val) {
629
- this.#percent = clamp(val, 0, 100);
630
- if (isNaN(val)) {
631
- this.#classList.add("indeterminate");
632
- return;
633
- }
634
- this.#classList.remove("indeterminate");
635
- this.#style.setProperty("--progressBar-percent", `${this.#percent}%`);
636
- }
637
- setWidth(viewer) {
638
- if (!viewer) {
639
- return;
640
- }
641
- const container = viewer.parentNode;
642
- const scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
643
- if (scrollbarWidth > 0) {
644
- this.#style.setProperty("--progressBar-end-offset", `${scrollbarWidth}px`);
645
- }
646
- }
647
- setDisableAutoFetch(delay = /* ms = */5000) {
648
- if (isNaN(this.#percent)) {
649
- return;
650
- }
651
- if (this.#disableAutoFetchTimeout) {
652
- clearTimeout(this.#disableAutoFetchTimeout);
653
- }
654
- this.show();
655
- this.#disableAutoFetchTimeout = setTimeout(() => {
656
- this.#disableAutoFetchTimeout = null;
657
- this.hide();
658
- }, delay);
659
- }
660
- hide() {
661
- if (!this.#visible) {
662
- return;
663
- }
664
- this.#visible = false;
665
- this.#classList.add("hidden");
666
- }
667
- show() {
668
- if (this.#visible) {
669
- return;
670
- }
671
- this.#visible = true;
672
- this.#classList.remove("hidden");
673
- }
674
- }
675
-
676
- /**
677
- * Get the active or focused element in current DOM.
678
- *
679
- * Recursively search for the truly active or focused element in case there are
680
- * shadow DOMs.
681
- *
682
- * @returns {Element} the truly active or focused element.
683
- */
684
- function getActiveOrFocusedElement() {
685
- let curRoot = document;
686
- let curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
687
- while ((_curActiveOrFocused = curActiveOrFocused) !== null && _curActiveOrFocused !== void 0 && _curActiveOrFocused.shadowRoot) {
688
- var _curActiveOrFocused;
689
- curRoot = curActiveOrFocused.shadowRoot;
690
- curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
691
- }
692
- return curActiveOrFocused;
693
- }
694
-
695
- /**
696
- * Converts API PageLayout values to the format used by `BaseViewer`.
697
- * @param {string} layout - The API PageLayout value.
698
- * @returns {Object}
699
- */
700
- function apiPageLayoutToViewerModes(layout) {
701
- let scrollMode = ScrollMode.VERTICAL,
702
- spreadMode = SpreadMode.NONE;
703
- switch (layout) {
704
- case "SinglePage":
705
- scrollMode = ScrollMode.PAGE;
706
- break;
707
- case "OneColumn":
708
- break;
709
- case "TwoPageLeft":
710
- scrollMode = ScrollMode.PAGE;
711
- /* falls through */
712
- case "TwoColumnLeft":
713
- spreadMode = SpreadMode.ODD;
714
- break;
715
- case "TwoPageRight":
716
- scrollMode = ScrollMode.PAGE;
717
- /* falls through */
718
- case "TwoColumnRight":
719
- spreadMode = SpreadMode.EVEN;
720
- break;
721
- }
722
- return {
723
- scrollMode,
724
- spreadMode
725
- };
726
- }
727
-
728
- /**
729
- * Converts API PageMode values to the format used by `PDFSidebar`.
730
- * NOTE: There's also a "FullScreen" parameter which is not possible to support,
731
- * since the Fullscreen API used in browsers requires that entering
732
- * fullscreen mode only occurs as a result of a user-initiated event.
733
- * @param {string} mode - The API PageMode value.
734
- * @returns {number} A value from {SidebarView}.
735
- */
736
- function apiPageModeToSidebarView(mode) {
737
- switch (mode) {
738
- case "UseNone":
739
- return SidebarView.NONE;
740
- case "UseThumbs":
741
- return SidebarView.THUMBS;
742
- case "UseOutlines":
743
- return SidebarView.OUTLINE;
744
- case "UseAttachments":
745
- return SidebarView.ATTACHMENTS;
746
- case "UseOC":
747
- return SidebarView.LAYERS;
748
- }
749
- return SidebarView.NONE; // Default value.
750
- }
751
- function toggleCheckedBtn(button, toggle, view = null) {
752
- button.classList.toggle("toggled", toggle);
753
- button.setAttribute("aria-checked", toggle);
754
- view === null || view === void 0 ? void 0 : view.classList.toggle("hidden", !toggle);
755
- }
756
- function toggleExpandedBtn(button, toggle, view = null) {
757
- button.classList.toggle("toggled", toggle);
758
- button.setAttribute("aria-expanded", toggle);
759
- view === null || view === void 0 ? void 0 : view.classList.toggle("hidden", !toggle);
760
- }
761
-
762
- export { AutoPrintRegExp, CursorTool, DEFAULT_SCALE, DEFAULT_SCALE_DELTA, DEFAULT_SCALE_VALUE, MAX_AUTO_SCALE, MAX_SCALE, MIN_SCALE, OutputScale, PresentationModeState, ProgressBar, RenderingStates, SCROLLBAR_PADDING, ScrollMode, SidebarView, SpreadMode, TextLayerMode, UNKNOWN_SCALE, VERTICAL_PADDING, animationStarted, apiPageLayoutToViewerModes, apiPageModeToSidebarView, approximateFraction, backtrackBeforeAllVisibleElements, binarySearchFirstItem, docStyle, getActiveOrFocusedElement, getPageSizeInches, getVisibleElements, isPortraitOrientation, isValidRotation, isValidScrollMode, isValidSpreadMode, normalizeWheelEventDelta, normalizeWheelEventDirection, parseQueryString, removeNullCharacters, roundToDivide, scrollIntoView, toggleCheckedBtn, toggleExpandedBtn, watchScroll };
1
+ /* Copyright 2012 Mozilla Foundation
2
+ *
3
+ * Licensed under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License.
5
+ * You may obtain a copy of the License at
6
+ *
7
+ * http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software
10
+ * distributed under the License is distributed on an "AS IS" BASIS,
11
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ * See the License for the specific language governing permissions and
13
+ * limitations under the License.
14
+ */
15
+
16
+ const DEFAULT_SCALE_VALUE = "auto";
17
+ const DEFAULT_SCALE = 1.0;
18
+ const DEFAULT_SCALE_DELTA = 1.1;
19
+ const MIN_SCALE = 0.1;
20
+ const MAX_SCALE = 10.0;
21
+ const UNKNOWN_SCALE = 0;
22
+ const MAX_AUTO_SCALE = 1.25;
23
+ const SCROLLBAR_PADDING = 40;
24
+ const VERTICAL_PADDING = 5;
25
+ const RenderingStates = {
26
+ INITIAL: 0,
27
+ RUNNING: 1,
28
+ PAUSED: 2,
29
+ FINISHED: 3
30
+ };
31
+ const PresentationModeState = {
32
+ UNKNOWN: 0,
33
+ NORMAL: 1,
34
+ CHANGING: 2,
35
+ FULLSCREEN: 3
36
+ };
37
+ const SidebarView = {
38
+ UNKNOWN: -1,
39
+ NONE: 0,
40
+ THUMBS: 1,
41
+ // Default value.
42
+ OUTLINE: 2,
43
+ ATTACHMENTS: 3,
44
+ LAYERS: 4
45
+ };
46
+ const TextLayerMode = {
47
+ DISABLE: 0,
48
+ ENABLE: 1,
49
+ ENABLE_PERMISSIONS: 2
50
+ };
51
+ const ScrollMode = {
52
+ UNKNOWN: -1,
53
+ VERTICAL: 0,
54
+ // Default value.
55
+ HORIZONTAL: 1,
56
+ WRAPPED: 2,
57
+ PAGE: 3
58
+ };
59
+ const SpreadMode = {
60
+ UNKNOWN: -1,
61
+ NONE: 0,
62
+ // Default value.
63
+ ODD: 1,
64
+ EVEN: 2
65
+ };
66
+ const CursorTool = {
67
+ SELECT: 0,
68
+ // The default value.
69
+ HAND: 1,
70
+ ZOOM: 2
71
+ };
72
+
73
+ // Used by `PDFViewerApplication`, and by the API unit-tests.
74
+ const AutoPrintRegExp = /\bprint\s*\(/;
75
+
76
+ /**
77
+ * Scale factors for the canvas, necessary with HiDPI displays.
78
+ */
79
+ class OutputScale {
80
+ constructor() {
81
+ const pixelRatio = window.devicePixelRatio || 1;
82
+
83
+ /**
84
+ * @type {number} Horizontal scale.
85
+ */
86
+ this.sx = pixelRatio;
87
+
88
+ /**
89
+ * @type {number} Vertical scale.
90
+ */
91
+ this.sy = pixelRatio;
92
+ }
93
+
94
+ /**
95
+ * @type {boolean} Returns `true` when scaling is required, `false` otherwise.
96
+ */
97
+ get scaled() {
98
+ return this.sx !== 1 || this.sy !== 1;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Scrolls specified element into view of its parent.
104
+ * @param {HTMLElement} element - The element to be visible.
105
+ * @param {Object} [spot] - An object with optional top and left properties,
106
+ * specifying the offset from the top left edge.
107
+ * @param {number} [spot.left]
108
+ * @param {number} [spot.top]
109
+ * @param {boolean} [scrollMatches] - When scrolling search results into view,
110
+ * ignore elements that either: Contains marked content identifiers,
111
+ * or have the CSS-rule `overflow: hidden;` set. The default value is `false`.
112
+ */
113
+ function scrollIntoView(element, spot, scrollMatches = false) {
114
+ // Assuming offsetParent is available (it's not available when viewer is in
115
+ // hidden iframe or object). We have to scroll: if the offsetParent is not set
116
+ // producing the error. See also animationStarted.
117
+ let parent = element.offsetParent;
118
+ if (!parent) {
119
+ console.error("offsetParent is not set -- cannot scroll");
120
+ return;
121
+ }
122
+ let offsetY = element.offsetTop + element.clientTop;
123
+ let offsetX = element.offsetLeft + element.clientLeft;
124
+ while (parent.clientHeight === parent.scrollHeight && parent.clientWidth === parent.scrollWidth || scrollMatches && (parent.classList.contains("markedContent") || getComputedStyle(parent).overflow === "hidden")) {
125
+ offsetY += parent.offsetTop;
126
+ offsetX += parent.offsetLeft;
127
+ parent = parent.offsetParent;
128
+ if (!parent) {
129
+ return; // no need to scroll
130
+ }
131
+ }
132
+ if (spot) {
133
+ if (spot.top !== undefined) {
134
+ offsetY += spot.top;
135
+ }
136
+ if (spot.left !== undefined) {
137
+ offsetX += spot.left;
138
+ parent.scrollLeft = offsetX;
139
+ }
140
+ }
141
+ parent.scrollTop = offsetY;
142
+ }
143
+
144
+ /**
145
+ * Helper function to start monitoring the scroll event and converting them into
146
+ * PDF.js friendly one: with scroll debounce and scroll direction.
147
+ */
148
+ function watchScroll(viewAreaElement, callback) {
149
+ const debounceScroll = function (evt) {
150
+ if (rAF) {
151
+ return;
152
+ }
153
+ // schedule an invocation of scroll for next animation frame.
154
+ rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
155
+ rAF = null;
156
+ const currentX = viewAreaElement.scrollLeft;
157
+ const lastX = state.lastX;
158
+ if (currentX !== lastX) {
159
+ state.right = currentX > lastX;
160
+ }
161
+ state.lastX = currentX;
162
+ const currentY = viewAreaElement.scrollTop;
163
+ const lastY = state.lastY;
164
+ if (currentY !== lastY) {
165
+ state.down = currentY > lastY;
166
+ }
167
+ state.lastY = currentY;
168
+ callback(state);
169
+ });
170
+ };
171
+ const state = {
172
+ right: true,
173
+ down: true,
174
+ lastX: viewAreaElement.scrollLeft,
175
+ lastY: viewAreaElement.scrollTop,
176
+ _eventHandler: debounceScroll
177
+ };
178
+ let rAF = null;
179
+ viewAreaElement.addEventListener("scroll", debounceScroll, true);
180
+ return state;
181
+ }
182
+
183
+ /**
184
+ * Helper function to parse query string (e.g. ?param1=value&param2=...).
185
+ * @param {string} query
186
+ * @returns {Map}
187
+ */
188
+ function parseQueryString(query) {
189
+ const params = new Map();
190
+ for (const [key, value] of new URLSearchParams(query)) {
191
+ params.set(key.toLowerCase(), value);
192
+ }
193
+ return params;
194
+ }
195
+ const InvisibleCharsRegExp = /[\x00-\x1F]/g;
196
+
197
+ /**
198
+ * @param {string} str
199
+ * @param {boolean} [replaceInvisible]
200
+ */
201
+ function removeNullCharacters(str, replaceInvisible = false) {
202
+ if (!InvisibleCharsRegExp.test(str)) {
203
+ return str;
204
+ }
205
+ if (replaceInvisible) {
206
+ return str.replaceAll(InvisibleCharsRegExp, m => m === "\x00" ? "" : " ");
207
+ }
208
+ return str.replaceAll("\x00", "");
209
+ }
210
+
211
+ /**
212
+ * Use binary search to find the index of the first item in a given array which
213
+ * passes a given condition. The items are expected to be sorted in the sense
214
+ * that if the condition is true for one item in the array, then it is also true
215
+ * for all following items.
216
+ *
217
+ * @returns {number} Index of the first array element to pass the test,
218
+ * or |items.length| if no such element exists.
219
+ */
220
+ function binarySearchFirstItem(items, condition, start = 0) {
221
+ let minIndex = start;
222
+ let maxIndex = items.length - 1;
223
+ if (maxIndex < 0 || !condition(items[maxIndex])) {
224
+ return items.length;
225
+ }
226
+ if (condition(items[minIndex])) {
227
+ return minIndex;
228
+ }
229
+ while (minIndex < maxIndex) {
230
+ const currentIndex = minIndex + maxIndex >> 1;
231
+ const currentItem = items[currentIndex];
232
+ if (condition(currentItem)) {
233
+ maxIndex = currentIndex;
234
+ } else {
235
+ minIndex = currentIndex + 1;
236
+ }
237
+ }
238
+ return minIndex; /* === maxIndex */
239
+ }
240
+
241
+ /**
242
+ * Approximates float number as a fraction using Farey sequence (max order
243
+ * of 8).
244
+ * @param {number} x - Positive float number.
245
+ * @returns {Array} Estimated fraction: the first array item is a numerator,
246
+ * the second one is a denominator.
247
+ */
248
+ function approximateFraction(x) {
249
+ // Fast paths for int numbers or their inversions.
250
+ if (Math.floor(x) === x) {
251
+ return [x, 1];
252
+ }
253
+ const xinv = 1 / x;
254
+ const limit = 8;
255
+ if (xinv > limit) {
256
+ return [1, limit];
257
+ } else if (Math.floor(xinv) === xinv) {
258
+ return [1, xinv];
259
+ }
260
+ const x_ = x > 1 ? xinv : x;
261
+ // a/b and c/d are neighbours in Farey sequence.
262
+ let a = 0,
263
+ b = 1,
264
+ c = 1,
265
+ d = 1;
266
+ // Limiting search to order 8.
267
+ while (true) {
268
+ // Generating next term in sequence (order of q).
269
+ const p = a + c,
270
+ q = b + d;
271
+ if (q > limit) {
272
+ break;
273
+ }
274
+ if (x_ <= p / q) {
275
+ c = p;
276
+ d = q;
277
+ } else {
278
+ a = p;
279
+ b = q;
280
+ }
281
+ }
282
+ let result;
283
+ // Select closest of the neighbours to x.
284
+ if (x_ - a / b < c / d - x_) {
285
+ result = x_ === x ? [a, b] : [b, a];
286
+ } else {
287
+ result = x_ === x ? [c, d] : [d, c];
288
+ }
289
+ return result;
290
+ }
291
+ function roundToDivide(x, div) {
292
+ const r = x % div;
293
+ return r === 0 ? x : Math.round(x - r + div);
294
+ }
295
+
296
+ /**
297
+ * @typedef {Object} GetPageSizeInchesParameters
298
+ * @property {number[]} view
299
+ * @property {number} userUnit
300
+ * @property {number} rotate
301
+ */
302
+
303
+ /**
304
+ * @typedef {Object} PageSize
305
+ * @property {number} width - In inches.
306
+ * @property {number} height - In inches.
307
+ */
308
+
309
+ /**
310
+ * Gets the size of the specified page, converted from PDF units to inches.
311
+ * @param {GetPageSizeInchesParameters} params
312
+ * @returns {PageSize}
313
+ */
314
+ function getPageSizeInches({
315
+ view,
316
+ userUnit,
317
+ rotate
318
+ }) {
319
+ const [x1, y1, x2, y2] = view;
320
+ // We need to take the page rotation into account as well.
321
+ const changeOrientation = rotate % 180 !== 0;
322
+ const width = (x2 - x1) / 72 * userUnit;
323
+ const height = (y2 - y1) / 72 * userUnit;
324
+ return {
325
+ width: changeOrientation ? height : width,
326
+ height: changeOrientation ? width : height
327
+ };
328
+ }
329
+
330
+ /**
331
+ * Helper function for getVisibleElements.
332
+ *
333
+ * @param {number} index - initial guess at the first visible element
334
+ * @param {Array} views - array of pages, into which `index` is an index
335
+ * @param {number} top - the top of the scroll pane
336
+ * @returns {number} less than or equal to `index` that is definitely at or
337
+ * before the first visible element in `views`, but not by too much. (Usually,
338
+ * this will be the first element in the first partially visible row in
339
+ * `views`, although sometimes it goes back one row further.)
340
+ */
341
+ function backtrackBeforeAllVisibleElements(index, views, top) {
342
+ // binarySearchFirstItem's assumption is that the input is ordered, with only
343
+ // one index where the conditions flips from false to true: [false ...,
344
+ // true...]. With vertical scrolling and spreads, it is possible to have
345
+ // [false ..., true, false, true ...]. With wrapped scrolling we can have a
346
+ // similar sequence, with many more mixed true and false in the middle.
347
+ //
348
+ // So there is no guarantee that the binary search yields the index of the
349
+ // first visible element. It could have been any of the other visible elements
350
+ // that were preceded by a hidden element.
351
+
352
+ // Of course, if either this element or the previous (hidden) element is also
353
+ // the first element, there's nothing to worry about.
354
+ if (index < 2) {
355
+ return index;
356
+ }
357
+
358
+ // That aside, the possible cases are represented below.
359
+ //
360
+ // **** = fully hidden
361
+ // A*B* = mix of partially visible and/or hidden pages
362
+ // CDEF = fully visible
363
+ //
364
+ // (1) Binary search could have returned A, in which case we can stop.
365
+ // (2) Binary search could also have returned B, in which case we need to
366
+ // check the whole row.
367
+ // (3) Binary search could also have returned C, in which case we need to
368
+ // check the whole previous row.
369
+ //
370
+ // There's one other possibility:
371
+ //
372
+ // **** = fully hidden
373
+ // ABCD = mix of fully and/or partially visible pages
374
+ //
375
+ // (4) Binary search could only have returned A.
376
+
377
+ // Initially assume that we need to find the beginning of the current row
378
+ // (case 1, 2, or 4), which means finding a page that is above the current
379
+ // page's top. If the found page is partially visible, we're definitely not in
380
+ // case 3, and this assumption is correct.
381
+ let elt = views[index].div;
382
+ let pageTop = elt.offsetTop + elt.clientTop;
383
+ if (pageTop >= top) {
384
+ // The found page is fully visible, so we're actually either in case 3 or 4,
385
+ // and unfortunately we can't tell the difference between them without
386
+ // scanning the entire previous row, so we just conservatively assume that
387
+ // we do need to backtrack to that row. In both cases, the previous page is
388
+ // in the previous row, so use its top instead.
389
+ elt = views[index - 1].div;
390
+ pageTop = elt.offsetTop + elt.clientTop;
391
+ }
392
+
393
+ // Now we backtrack to the first page that still has its bottom below
394
+ // `pageTop`, which is the top of a page in the first visible row (unless
395
+ // we're in case 4, in which case it's the row before that).
396
+ // `index` is found by binary search, so the page at `index - 1` is
397
+ // invisible and we can start looking for potentially visible pages from
398
+ // `index - 2`. (However, if this loop terminates on its first iteration,
399
+ // which is the case when pages are stacked vertically, `index` should remain
400
+ // unchanged, so we use a distinct loop variable.)
401
+ for (let i = index - 2; i >= 0; --i) {
402
+ elt = views[i].div;
403
+ if (elt.offsetTop + elt.clientTop + elt.clientHeight <= pageTop) {
404
+ // We have reached the previous row, so stop now.
405
+ // This loop is expected to terminate relatively quickly because the
406
+ // number of pages per row is expected to be small.
407
+ break;
408
+ }
409
+ index = i;
410
+ }
411
+ return index;
412
+ }
413
+
414
+ /**
415
+ * @typedef {Object} GetVisibleElementsParameters
416
+ * @property {HTMLElement} scrollEl - A container that can possibly scroll.
417
+ * @property {Array} views - Objects with a `div` property that contains an
418
+ * HTMLElement, which should all be descendants of `scrollEl` satisfying the
419
+ * relevant layout assumptions.
420
+ * @property {boolean} sortByVisibility - If `true`, the returned elements are
421
+ * sorted in descending order of the percent of their padding box that is
422
+ * visible. The default value is `false`.
423
+ * @property {boolean} horizontal - If `true`, the elements are assumed to be
424
+ * laid out horizontally instead of vertically. The default value is `false`.
425
+ * @property {boolean} rtl - If `true`, the `scrollEl` container is assumed to
426
+ * be in right-to-left mode. The default value is `false`.
427
+ */
428
+
429
+ /**
430
+ * Generic helper to find out what elements are visible within a scroll pane.
431
+ *
432
+ * Well, pretty generic. There are some assumptions placed on the elements
433
+ * referenced by `views`:
434
+ * - If `horizontal`, no left of any earlier element is to the right of the
435
+ * left of any later element.
436
+ * - Otherwise, `views` can be split into contiguous rows where, within a row,
437
+ * no top of any element is below the bottom of any other element, and
438
+ * between rows, no bottom of any element in an earlier row is below the
439
+ * top of any element in a later row.
440
+ *
441
+ * (Here, top, left, etc. all refer to the padding edge of the element in
442
+ * question. For pages, that ends up being equivalent to the bounding box of the
443
+ * rendering canvas. Earlier and later refer to index in `views`, not page
444
+ * layout.)
445
+ *
446
+ * @param {GetVisibleElementsParameters} params
447
+ * @returns {Object} `{ first, last, views: [{ id, x, y, view, percent }] }`
448
+ */
449
+ function getVisibleElements({
450
+ scrollEl,
451
+ views,
452
+ sortByVisibility = false,
453
+ horizontal = false,
454
+ rtl = false
455
+ }) {
456
+ const top = scrollEl.scrollTop,
457
+ bottom = top + scrollEl.clientHeight;
458
+ const left = scrollEl.scrollLeft,
459
+ right = left + scrollEl.clientWidth;
460
+
461
+ // Throughout this "generic" function, comments will assume we're working with
462
+ // PDF document pages, which is the most important and complex case. In this
463
+ // case, the visible elements we're actually interested is the page canvas,
464
+ // which is contained in a wrapper which adds no padding/border/margin, which
465
+ // is itself contained in `view.div` which adds no padding (but does add a
466
+ // border). So, as specified in this function's doc comment, this function
467
+ // does all of its work on the padding edge of the provided views, starting at
468
+ // offsetLeft/Top (which includes margin) and adding clientLeft/Top (which is
469
+ // the border). Adding clientWidth/Height gets us the bottom-right corner of
470
+ // the padding edge.
471
+ function isElementBottomAfterViewTop(view) {
472
+ const element = view.div;
473
+ const elementBottom = element.offsetTop + element.clientTop + element.clientHeight;
474
+ return elementBottom > top;
475
+ }
476
+ function isElementNextAfterViewHorizontally(view) {
477
+ const element = view.div;
478
+ const elementLeft = element.offsetLeft + element.clientLeft;
479
+ const elementRight = elementLeft + element.clientWidth;
480
+ return rtl ? elementLeft < right : elementRight > left;
481
+ }
482
+ const visible = [],
483
+ ids = new Set(),
484
+ numViews = views.length;
485
+ let firstVisibleElementInd = binarySearchFirstItem(views, horizontal ? isElementNextAfterViewHorizontally : isElementBottomAfterViewTop);
486
+
487
+ // Please note the return value of the `binarySearchFirstItem` function when
488
+ // no valid element is found (hence the `firstVisibleElementInd` check below).
489
+ if (firstVisibleElementInd > 0 && firstVisibleElementInd < numViews && !horizontal) {
490
+ // In wrapped scrolling (or vertical scrolling with spreads), with some page
491
+ // sizes, isElementBottomAfterViewTop doesn't satisfy the binary search
492
+ // condition: there can be pages with bottoms above the view top between
493
+ // pages with bottoms below. This function detects and corrects that error;
494
+ // see it for more comments.
495
+ firstVisibleElementInd = backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top);
496
+ }
497
+
498
+ // lastEdge acts as a cutoff for us to stop looping, because we know all
499
+ // subsequent pages will be hidden.
500
+ //
501
+ // When using wrapped scrolling or vertical scrolling with spreads, we can't
502
+ // simply stop the first time we reach a page below the bottom of the view;
503
+ // the tops of subsequent pages on the same row could still be visible. In
504
+ // horizontal scrolling, we don't have that issue, so we can stop as soon as
505
+ // we pass `right`, without needing the code below that handles the -1 case.
506
+ let lastEdge = horizontal ? right : -1;
507
+ for (let i = firstVisibleElementInd; i < numViews; i++) {
508
+ const view = views[i],
509
+ element = view.div;
510
+ const currentWidth = element.offsetLeft + element.clientLeft;
511
+ const currentHeight = element.offsetTop + element.clientTop;
512
+ const viewWidth = element.clientWidth,
513
+ viewHeight = element.clientHeight;
514
+ const viewRight = currentWidth + viewWidth;
515
+ const viewBottom = currentHeight + viewHeight;
516
+ if (lastEdge === -1) {
517
+ // As commented above, this is only needed in non-horizontal cases.
518
+ // Setting lastEdge to the bottom of the first page that is partially
519
+ // visible ensures that the next page fully below lastEdge is on the
520
+ // next row, which has to be fully hidden along with all subsequent rows.
521
+ if (viewBottom >= bottom) {
522
+ lastEdge = viewBottom;
523
+ }
524
+ } else if ((horizontal ? currentWidth : currentHeight) > lastEdge) {
525
+ break;
526
+ }
527
+ if (viewBottom <= top || currentHeight >= bottom || viewRight <= left || currentWidth >= right) {
528
+ continue;
529
+ }
530
+ const hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, viewBottom - bottom);
531
+ const hiddenWidth = Math.max(0, left - currentWidth) + Math.max(0, viewRight - right);
532
+ const fractionHeight = (viewHeight - hiddenHeight) / viewHeight,
533
+ fractionWidth = (viewWidth - hiddenWidth) / viewWidth;
534
+ const percent = fractionHeight * fractionWidth * 100 | 0;
535
+ visible.push({
536
+ id: view.id,
537
+ x: currentWidth,
538
+ y: currentHeight,
539
+ view,
540
+ percent,
541
+ widthPercent: fractionWidth * 100 | 0
542
+ });
543
+ ids.add(view.id);
544
+ }
545
+ const first = visible[0],
546
+ last = visible.at(-1);
547
+ if (sortByVisibility) {
548
+ visible.sort(function (a, b) {
549
+ const pc = a.percent - b.percent;
550
+ if (Math.abs(pc) > 0.001) {
551
+ return -pc;
552
+ }
553
+ return a.id - b.id; // ensure stability
554
+ });
555
+ }
556
+ return {
557
+ first,
558
+ last,
559
+ views: visible,
560
+ ids
561
+ };
562
+ }
563
+ function normalizeWheelEventDirection(evt) {
564
+ let delta = Math.hypot(evt.deltaX, evt.deltaY);
565
+ const angle = Math.atan2(evt.deltaY, evt.deltaX);
566
+ if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
567
+ // All that is left-up oriented has to change the sign.
568
+ delta = -delta;
569
+ }
570
+ return delta;
571
+ }
572
+ function normalizeWheelEventDelta(evt) {
573
+ const deltaMode = evt.deltaMode; // Avoid being affected by bug 1392460.
574
+ let delta = normalizeWheelEventDirection(evt);
575
+ const MOUSE_PIXELS_PER_LINE = 30;
576
+ const MOUSE_LINES_PER_PAGE = 30;
577
+
578
+ // Converts delta to per-page units
579
+ if (deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
580
+ delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
581
+ } else if (deltaMode === WheelEvent.DOM_DELTA_LINE) {
582
+ delta /= MOUSE_LINES_PER_PAGE;
583
+ }
584
+ return delta;
585
+ }
586
+ function isValidRotation(angle) {
587
+ return Number.isInteger(angle) && angle % 90 === 0;
588
+ }
589
+ function isValidScrollMode(mode) {
590
+ return Number.isInteger(mode) && Object.values(ScrollMode).includes(mode) && mode !== ScrollMode.UNKNOWN;
591
+ }
592
+ function isValidSpreadMode(mode) {
593
+ return Number.isInteger(mode) && Object.values(SpreadMode).includes(mode) && mode !== SpreadMode.UNKNOWN;
594
+ }
595
+ function isPortraitOrientation(size) {
596
+ return size.width <= size.height;
597
+ }
598
+
599
+ /**
600
+ * Promise that is resolved when DOM window becomes visible.
601
+ */
602
+ const animationStarted = new Promise(function (resolve) {
603
+ if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("LIB") && typeof window === "undefined") {
604
+ // Prevent "ReferenceError: window is not defined" errors when running the
605
+ // unit-tests in Node.js environments.
606
+ setTimeout(resolve, 20);
607
+ return;
608
+ }
609
+ window.requestAnimationFrame(resolve);
610
+ });
611
+ const docStyle = typeof PDFJSDev !== "undefined" && PDFJSDev.test("LIB") && typeof document === "undefined" ? null : document.documentElement.style;
612
+ function clamp(v, min, max) {
613
+ return Math.min(Math.max(v, min), max);
614
+ }
615
+ class ProgressBar {
616
+ #classList = null;
617
+ #disableAutoFetchTimeout = null;
618
+ #percent = 0;
619
+ #style = null;
620
+ #visible = true;
621
+ constructor(bar) {
622
+ this.#classList = bar.classList;
623
+ this.#style = bar.style;
624
+ }
625
+ get percent() {
626
+ return this.#percent;
627
+ }
628
+ set percent(val) {
629
+ this.#percent = clamp(val, 0, 100);
630
+ if (isNaN(val)) {
631
+ this.#classList.add("indeterminate");
632
+ return;
633
+ }
634
+ this.#classList.remove("indeterminate");
635
+ this.#style.setProperty("--progressBar-percent", `${this.#percent}%`);
636
+ }
637
+ setWidth(viewer) {
638
+ if (!viewer) {
639
+ return;
640
+ }
641
+ const container = viewer.parentNode;
642
+ const scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
643
+ if (scrollbarWidth > 0) {
644
+ this.#style.setProperty("--progressBar-end-offset", `${scrollbarWidth}px`);
645
+ }
646
+ }
647
+ setDisableAutoFetch(delay = /* ms = */5000) {
648
+ if (isNaN(this.#percent)) {
649
+ return;
650
+ }
651
+ if (this.#disableAutoFetchTimeout) {
652
+ clearTimeout(this.#disableAutoFetchTimeout);
653
+ }
654
+ this.show();
655
+ this.#disableAutoFetchTimeout = setTimeout(() => {
656
+ this.#disableAutoFetchTimeout = null;
657
+ this.hide();
658
+ }, delay);
659
+ }
660
+ hide() {
661
+ if (!this.#visible) {
662
+ return;
663
+ }
664
+ this.#visible = false;
665
+ this.#classList.add("hidden");
666
+ }
667
+ show() {
668
+ if (this.#visible) {
669
+ return;
670
+ }
671
+ this.#visible = true;
672
+ this.#classList.remove("hidden");
673
+ }
674
+ }
675
+
676
+ /**
677
+ * Get the active or focused element in current DOM.
678
+ *
679
+ * Recursively search for the truly active or focused element in case there are
680
+ * shadow DOMs.
681
+ *
682
+ * @returns {Element} the truly active or focused element.
683
+ */
684
+ function getActiveOrFocusedElement() {
685
+ let curRoot = document;
686
+ let curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
687
+ while ((_curActiveOrFocused = curActiveOrFocused) !== null && _curActiveOrFocused !== void 0 && _curActiveOrFocused.shadowRoot) {
688
+ var _curActiveOrFocused;
689
+ curRoot = curActiveOrFocused.shadowRoot;
690
+ curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
691
+ }
692
+ return curActiveOrFocused;
693
+ }
694
+
695
+ /**
696
+ * Converts API PageLayout values to the format used by `BaseViewer`.
697
+ * @param {string} layout - The API PageLayout value.
698
+ * @returns {Object}
699
+ */
700
+ function apiPageLayoutToViewerModes(layout) {
701
+ let scrollMode = ScrollMode.VERTICAL,
702
+ spreadMode = SpreadMode.NONE;
703
+ switch (layout) {
704
+ case "SinglePage":
705
+ scrollMode = ScrollMode.PAGE;
706
+ break;
707
+ case "OneColumn":
708
+ break;
709
+ case "TwoPageLeft":
710
+ scrollMode = ScrollMode.PAGE;
711
+ /* falls through */
712
+ case "TwoColumnLeft":
713
+ spreadMode = SpreadMode.ODD;
714
+ break;
715
+ case "TwoPageRight":
716
+ scrollMode = ScrollMode.PAGE;
717
+ /* falls through */
718
+ case "TwoColumnRight":
719
+ spreadMode = SpreadMode.EVEN;
720
+ break;
721
+ }
722
+ return {
723
+ scrollMode,
724
+ spreadMode
725
+ };
726
+ }
727
+
728
+ /**
729
+ * Converts API PageMode values to the format used by `PDFSidebar`.
730
+ * NOTE: There's also a "FullScreen" parameter which is not possible to support,
731
+ * since the Fullscreen API used in browsers requires that entering
732
+ * fullscreen mode only occurs as a result of a user-initiated event.
733
+ * @param {string} mode - The API PageMode value.
734
+ * @returns {number} A value from {SidebarView}.
735
+ */
736
+ function apiPageModeToSidebarView(mode) {
737
+ switch (mode) {
738
+ case "UseNone":
739
+ return SidebarView.NONE;
740
+ case "UseThumbs":
741
+ return SidebarView.THUMBS;
742
+ case "UseOutlines":
743
+ return SidebarView.OUTLINE;
744
+ case "UseAttachments":
745
+ return SidebarView.ATTACHMENTS;
746
+ case "UseOC":
747
+ return SidebarView.LAYERS;
748
+ }
749
+ return SidebarView.NONE; // Default value.
750
+ }
751
+ function toggleCheckedBtn(button, toggle, view = null) {
752
+ button.classList.toggle("toggled", toggle);
753
+ button.setAttribute("aria-checked", toggle);
754
+ view === null || view === void 0 ? void 0 : view.classList.toggle("hidden", !toggle);
755
+ }
756
+ function toggleExpandedBtn(button, toggle, view = null) {
757
+ button.classList.toggle("toggled", toggle);
758
+ button.setAttribute("aria-expanded", toggle);
759
+ view === null || view === void 0 ? void 0 : view.classList.toggle("hidden", !toggle);
760
+ }
761
+
762
+ export { AutoPrintRegExp, CursorTool, DEFAULT_SCALE, DEFAULT_SCALE_DELTA, DEFAULT_SCALE_VALUE, MAX_AUTO_SCALE, MAX_SCALE, MIN_SCALE, OutputScale, PresentationModeState, ProgressBar, RenderingStates, SCROLLBAR_PADDING, ScrollMode, SidebarView, SpreadMode, TextLayerMode, UNKNOWN_SCALE, VERTICAL_PADDING, animationStarted, apiPageLayoutToViewerModes, apiPageModeToSidebarView, approximateFraction, backtrackBeforeAllVisibleElements, binarySearchFirstItem, docStyle, getActiveOrFocusedElement, getPageSizeInches, getVisibleElements, isPortraitOrientation, isValidRotation, isValidScrollMode, isValidSpreadMode, normalizeWheelEventDelta, normalizeWheelEventDirection, parseQueryString, removeNullCharacters, roundToDivide, scrollIntoView, toggleCheckedBtn, toggleExpandedBtn, watchScroll };