644 lines
16 KiB
JavaScript
644 lines
16 KiB
JavaScript
/**
|
|
* @licstart The following is the entire license notice for the
|
|
* JavaScript code in this page
|
|
*
|
|
* Copyright 2022 Mozilla Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* @licend The above is the entire license notice for the
|
|
* JavaScript code in this page
|
|
*/
|
|
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.animationStarted = exports.VERTICAL_PADDING = exports.UNKNOWN_SCALE = exports.TextLayerMode = exports.SpreadMode = exports.SidebarView = exports.ScrollMode = exports.SCROLLBAR_PADDING = exports.RenderingStates = exports.RendererType = exports.ProgressBar = exports.PresentationModeState = exports.OutputScale = exports.MIN_SCALE = exports.MAX_SCALE = exports.MAX_AUTO_SCALE = exports.DEFAULT_SCALE_VALUE = exports.DEFAULT_SCALE_DELTA = exports.DEFAULT_SCALE = exports.AutoPrintRegExp = void 0;
|
|
exports.apiPageLayoutToViewerModes = apiPageLayoutToViewerModes;
|
|
exports.apiPageModeToSidebarView = apiPageModeToSidebarView;
|
|
exports.approximateFraction = approximateFraction;
|
|
exports.backtrackBeforeAllVisibleElements = backtrackBeforeAllVisibleElements;
|
|
exports.binarySearchFirstItem = binarySearchFirstItem;
|
|
exports.docStyle = void 0;
|
|
exports.getActiveOrFocusedElement = getActiveOrFocusedElement;
|
|
exports.getPageSizeInches = getPageSizeInches;
|
|
exports.getVisibleElements = getVisibleElements;
|
|
exports.isPortraitOrientation = isPortraitOrientation;
|
|
exports.isValidRotation = isValidRotation;
|
|
exports.isValidScrollMode = isValidScrollMode;
|
|
exports.isValidSpreadMode = isValidSpreadMode;
|
|
exports.noContextMenuHandler = noContextMenuHandler;
|
|
exports.normalizeWheelEventDelta = normalizeWheelEventDelta;
|
|
exports.normalizeWheelEventDirection = normalizeWheelEventDirection;
|
|
exports.parseQueryString = parseQueryString;
|
|
exports.removeNullCharacters = removeNullCharacters;
|
|
exports.roundToDivide = roundToDivide;
|
|
exports.scrollIntoView = scrollIntoView;
|
|
exports.watchScroll = watchScroll;
|
|
const DEFAULT_SCALE_VALUE = "auto";
|
|
exports.DEFAULT_SCALE_VALUE = DEFAULT_SCALE_VALUE;
|
|
const DEFAULT_SCALE = 1.0;
|
|
exports.DEFAULT_SCALE = DEFAULT_SCALE;
|
|
const DEFAULT_SCALE_DELTA = 1.1;
|
|
exports.DEFAULT_SCALE_DELTA = DEFAULT_SCALE_DELTA;
|
|
const MIN_SCALE = 0.1;
|
|
exports.MIN_SCALE = MIN_SCALE;
|
|
const MAX_SCALE = 10.0;
|
|
exports.MAX_SCALE = MAX_SCALE;
|
|
const UNKNOWN_SCALE = 0;
|
|
exports.UNKNOWN_SCALE = UNKNOWN_SCALE;
|
|
const MAX_AUTO_SCALE = 1.25;
|
|
exports.MAX_AUTO_SCALE = MAX_AUTO_SCALE;
|
|
const SCROLLBAR_PADDING = 40;
|
|
exports.SCROLLBAR_PADDING = SCROLLBAR_PADDING;
|
|
const VERTICAL_PADDING = 5;
|
|
exports.VERTICAL_PADDING = VERTICAL_PADDING;
|
|
const RenderingStates = {
|
|
INITIAL: 0,
|
|
RUNNING: 1,
|
|
PAUSED: 2,
|
|
FINISHED: 3
|
|
};
|
|
exports.RenderingStates = RenderingStates;
|
|
const PresentationModeState = {
|
|
UNKNOWN: 0,
|
|
NORMAL: 1,
|
|
CHANGING: 2,
|
|
FULLSCREEN: 3
|
|
};
|
|
exports.PresentationModeState = PresentationModeState;
|
|
const SidebarView = {
|
|
UNKNOWN: -1,
|
|
NONE: 0,
|
|
THUMBS: 1,
|
|
OUTLINE: 2,
|
|
ATTACHMENTS: 3,
|
|
LAYERS: 4
|
|
};
|
|
exports.SidebarView = SidebarView;
|
|
const RendererType = {
|
|
CANVAS: "canvas",
|
|
SVG: "svg"
|
|
};
|
|
exports.RendererType = RendererType;
|
|
const TextLayerMode = {
|
|
DISABLE: 0,
|
|
ENABLE: 1,
|
|
ENABLE_ENHANCE: 2
|
|
};
|
|
exports.TextLayerMode = TextLayerMode;
|
|
const ScrollMode = {
|
|
UNKNOWN: -1,
|
|
VERTICAL: 0,
|
|
HORIZONTAL: 1,
|
|
WRAPPED: 2,
|
|
PAGE: 3
|
|
};
|
|
exports.ScrollMode = ScrollMode;
|
|
const SpreadMode = {
|
|
UNKNOWN: -1,
|
|
NONE: 0,
|
|
ODD: 1,
|
|
EVEN: 2
|
|
};
|
|
exports.SpreadMode = SpreadMode;
|
|
const AutoPrintRegExp = /\bprint\s*\(/;
|
|
exports.AutoPrintRegExp = AutoPrintRegExp;
|
|
|
|
class OutputScale {
|
|
constructor() {
|
|
const pixelRatio = window.devicePixelRatio || 1;
|
|
this.sx = pixelRatio;
|
|
this.sy = pixelRatio;
|
|
}
|
|
|
|
get scaled() {
|
|
return this.sx !== 1 || this.sy !== 1;
|
|
}
|
|
|
|
}
|
|
|
|
exports.OutputScale = OutputScale;
|
|
|
|
function scrollIntoView(element, spot, scrollMatches = false) {
|
|
let parent = element.offsetParent;
|
|
|
|
if (!parent) {
|
|
console.error("offsetParent is not set -- cannot scroll");
|
|
return;
|
|
}
|
|
|
|
let offsetY = element.offsetTop + element.clientTop;
|
|
let offsetX = element.offsetLeft + element.clientLeft;
|
|
|
|
while (parent.clientHeight === parent.scrollHeight && parent.clientWidth === parent.scrollWidth || scrollMatches && (parent.classList.contains("markedContent") || getComputedStyle(parent).overflow === "hidden")) {
|
|
offsetY += parent.offsetTop;
|
|
offsetX += parent.offsetLeft;
|
|
parent = parent.offsetParent;
|
|
|
|
if (!parent) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (spot) {
|
|
if (spot.top !== undefined) {
|
|
offsetY += spot.top;
|
|
}
|
|
|
|
if (spot.left !== undefined) {
|
|
offsetX += spot.left;
|
|
parent.scrollLeft = offsetX;
|
|
}
|
|
}
|
|
|
|
parent.scrollTop = offsetY;
|
|
}
|
|
|
|
function watchScroll(viewAreaElement, callback) {
|
|
const debounceScroll = function (evt) {
|
|
if (rAF) {
|
|
return;
|
|
}
|
|
|
|
rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
|
|
rAF = null;
|
|
const currentX = viewAreaElement.scrollLeft;
|
|
const lastX = state.lastX;
|
|
|
|
if (currentX !== lastX) {
|
|
state.right = currentX > lastX;
|
|
}
|
|
|
|
state.lastX = currentX;
|
|
const currentY = viewAreaElement.scrollTop;
|
|
const lastY = state.lastY;
|
|
|
|
if (currentY !== lastY) {
|
|
state.down = currentY > lastY;
|
|
}
|
|
|
|
state.lastY = currentY;
|
|
callback(state);
|
|
});
|
|
};
|
|
|
|
const state = {
|
|
right: true,
|
|
down: true,
|
|
lastX: viewAreaElement.scrollLeft,
|
|
lastY: viewAreaElement.scrollTop,
|
|
_eventHandler: debounceScroll
|
|
};
|
|
let rAF = null;
|
|
viewAreaElement.addEventListener("scroll", debounceScroll, true);
|
|
return state;
|
|
}
|
|
|
|
function parseQueryString(query) {
|
|
const params = new Map();
|
|
|
|
for (const [key, value] of new URLSearchParams(query)) {
|
|
params.set(key.toLowerCase(), value);
|
|
}
|
|
|
|
return params;
|
|
}
|
|
|
|
const NullCharactersRegExp = /\x00/g;
|
|
const InvisibleCharactersRegExp = /[\x01-\x1F]/g;
|
|
|
|
function removeNullCharacters(str, replaceInvisible = false) {
|
|
if (typeof str !== "string") {
|
|
console.error(`The argument must be a string.`);
|
|
return str;
|
|
}
|
|
|
|
if (replaceInvisible) {
|
|
str = str.replace(InvisibleCharactersRegExp, " ");
|
|
}
|
|
|
|
return str.replace(NullCharactersRegExp, "");
|
|
}
|
|
|
|
function binarySearchFirstItem(items, condition, start = 0) {
|
|
let minIndex = start;
|
|
let maxIndex = items.length - 1;
|
|
|
|
if (maxIndex < 0 || !condition(items[maxIndex])) {
|
|
return items.length;
|
|
}
|
|
|
|
if (condition(items[minIndex])) {
|
|
return minIndex;
|
|
}
|
|
|
|
while (minIndex < maxIndex) {
|
|
const currentIndex = minIndex + maxIndex >> 1;
|
|
const currentItem = items[currentIndex];
|
|
|
|
if (condition(currentItem)) {
|
|
maxIndex = currentIndex;
|
|
} else {
|
|
minIndex = currentIndex + 1;
|
|
}
|
|
}
|
|
|
|
return minIndex;
|
|
}
|
|
|
|
function approximateFraction(x) {
|
|
if (Math.floor(x) === x) {
|
|
return [x, 1];
|
|
}
|
|
|
|
const xinv = 1 / x;
|
|
const limit = 8;
|
|
|
|
if (xinv > limit) {
|
|
return [1, limit];
|
|
} else if (Math.floor(xinv) === xinv) {
|
|
return [1, xinv];
|
|
}
|
|
|
|
const x_ = x > 1 ? xinv : x;
|
|
let a = 0,
|
|
b = 1,
|
|
c = 1,
|
|
d = 1;
|
|
|
|
while (true) {
|
|
const p = a + c,
|
|
q = b + d;
|
|
|
|
if (q > limit) {
|
|
break;
|
|
}
|
|
|
|
if (x_ <= p / q) {
|
|
c = p;
|
|
d = q;
|
|
} else {
|
|
a = p;
|
|
b = q;
|
|
}
|
|
}
|
|
|
|
let result;
|
|
|
|
if (x_ - a / b < c / d - x_) {
|
|
result = x_ === x ? [a, b] : [b, a];
|
|
} else {
|
|
result = x_ === x ? [c, d] : [d, c];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function roundToDivide(x, div) {
|
|
const r = x % div;
|
|
return r === 0 ? x : Math.round(x - r + div);
|
|
}
|
|
|
|
function getPageSizeInches({
|
|
view,
|
|
userUnit,
|
|
rotate
|
|
}) {
|
|
const [x1, y1, x2, y2] = view;
|
|
const changeOrientation = rotate % 180 !== 0;
|
|
const width = (x2 - x1) / 72 * userUnit;
|
|
const height = (y2 - y1) / 72 * userUnit;
|
|
return {
|
|
width: changeOrientation ? height : width,
|
|
height: changeOrientation ? width : height
|
|
};
|
|
}
|
|
|
|
function backtrackBeforeAllVisibleElements(index, views, top) {
|
|
if (index < 2) {
|
|
return index;
|
|
}
|
|
|
|
let elt = views[index].div;
|
|
let pageTop = elt.offsetTop + elt.clientTop;
|
|
|
|
if (pageTop >= top) {
|
|
elt = views[index - 1].div;
|
|
pageTop = elt.offsetTop + elt.clientTop;
|
|
}
|
|
|
|
for (let i = index - 2; i >= 0; --i) {
|
|
elt = views[i].div;
|
|
|
|
if (elt.offsetTop + elt.clientTop + elt.clientHeight <= pageTop) {
|
|
break;
|
|
}
|
|
|
|
index = i;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
function getVisibleElements({
|
|
scrollEl,
|
|
views,
|
|
sortByVisibility = false,
|
|
horizontal = false,
|
|
rtl = false
|
|
}) {
|
|
const top = scrollEl.scrollTop,
|
|
bottom = top + scrollEl.clientHeight;
|
|
const left = scrollEl.scrollLeft,
|
|
right = left + scrollEl.clientWidth;
|
|
|
|
function isElementBottomAfterViewTop(view) {
|
|
const element = view.div;
|
|
const elementBottom = element.offsetTop + element.clientTop + element.clientHeight;
|
|
return elementBottom > top;
|
|
}
|
|
|
|
function isElementNextAfterViewHorizontally(view) {
|
|
const element = view.div;
|
|
const elementLeft = element.offsetLeft + element.clientLeft;
|
|
const elementRight = elementLeft + element.clientWidth;
|
|
return rtl ? elementLeft < right : elementRight > left;
|
|
}
|
|
|
|
const visible = [],
|
|
ids = new Set(),
|
|
numViews = views.length;
|
|
let firstVisibleElementInd = binarySearchFirstItem(views, horizontal ? isElementNextAfterViewHorizontally : isElementBottomAfterViewTop);
|
|
|
|
if (firstVisibleElementInd > 0 && firstVisibleElementInd < numViews && !horizontal) {
|
|
firstVisibleElementInd = backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top);
|
|
}
|
|
|
|
let lastEdge = horizontal ? right : -1;
|
|
|
|
for (let i = firstVisibleElementInd; i < numViews; i++) {
|
|
const view = views[i],
|
|
element = view.div;
|
|
const currentWidth = element.offsetLeft + element.clientLeft;
|
|
const currentHeight = element.offsetTop + element.clientTop;
|
|
const viewWidth = element.clientWidth,
|
|
viewHeight = element.clientHeight;
|
|
const viewRight = currentWidth + viewWidth;
|
|
const viewBottom = currentHeight + viewHeight;
|
|
|
|
if (lastEdge === -1) {
|
|
if (viewBottom >= bottom) {
|
|
lastEdge = viewBottom;
|
|
}
|
|
} else if ((horizontal ? currentWidth : currentHeight) > lastEdge) {
|
|
break;
|
|
}
|
|
|
|
if (viewBottom <= top || currentHeight >= bottom || viewRight <= left || currentWidth >= right) {
|
|
continue;
|
|
}
|
|
|
|
const hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, viewBottom - bottom);
|
|
const hiddenWidth = Math.max(0, left - currentWidth) + Math.max(0, viewRight - right);
|
|
const fractionHeight = (viewHeight - hiddenHeight) / viewHeight,
|
|
fractionWidth = (viewWidth - hiddenWidth) / viewWidth;
|
|
const percent = fractionHeight * fractionWidth * 100 | 0;
|
|
visible.push({
|
|
id: view.id,
|
|
x: currentWidth,
|
|
y: currentHeight,
|
|
view,
|
|
percent,
|
|
widthPercent: fractionWidth * 100 | 0
|
|
});
|
|
ids.add(view.id);
|
|
}
|
|
|
|
const first = visible[0],
|
|
last = visible.at(-1);
|
|
|
|
if (sortByVisibility) {
|
|
visible.sort(function (a, b) {
|
|
const pc = a.percent - b.percent;
|
|
|
|
if (Math.abs(pc) > 0.001) {
|
|
return -pc;
|
|
}
|
|
|
|
return a.id - b.id;
|
|
});
|
|
}
|
|
|
|
return {
|
|
first,
|
|
last,
|
|
views: visible,
|
|
ids
|
|
};
|
|
}
|
|
|
|
function noContextMenuHandler(evt) {
|
|
evt.preventDefault();
|
|
}
|
|
|
|
function normalizeWheelEventDirection(evt) {
|
|
let delta = Math.hypot(evt.deltaX, evt.deltaY);
|
|
const angle = Math.atan2(evt.deltaY, evt.deltaX);
|
|
|
|
if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
|
|
delta = -delta;
|
|
}
|
|
|
|
return delta;
|
|
}
|
|
|
|
function normalizeWheelEventDelta(evt) {
|
|
let delta = normalizeWheelEventDirection(evt);
|
|
const MOUSE_DOM_DELTA_PIXEL_MODE = 0;
|
|
const MOUSE_DOM_DELTA_LINE_MODE = 1;
|
|
const MOUSE_PIXELS_PER_LINE = 30;
|
|
const MOUSE_LINES_PER_PAGE = 30;
|
|
|
|
if (evt.deltaMode === MOUSE_DOM_DELTA_PIXEL_MODE) {
|
|
delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
|
|
} else if (evt.deltaMode === MOUSE_DOM_DELTA_LINE_MODE) {
|
|
delta /= MOUSE_LINES_PER_PAGE;
|
|
}
|
|
|
|
return delta;
|
|
}
|
|
|
|
function isValidRotation(angle) {
|
|
return Number.isInteger(angle) && angle % 90 === 0;
|
|
}
|
|
|
|
function isValidScrollMode(mode) {
|
|
return Number.isInteger(mode) && Object.values(ScrollMode).includes(mode) && mode !== ScrollMode.UNKNOWN;
|
|
}
|
|
|
|
function isValidSpreadMode(mode) {
|
|
return Number.isInteger(mode) && Object.values(SpreadMode).includes(mode) && mode !== SpreadMode.UNKNOWN;
|
|
}
|
|
|
|
function isPortraitOrientation(size) {
|
|
return size.width <= size.height;
|
|
}
|
|
|
|
const animationStarted = new Promise(function (resolve) {
|
|
if (typeof window === "undefined") {
|
|
setTimeout(resolve, 20);
|
|
return;
|
|
}
|
|
|
|
window.requestAnimationFrame(resolve);
|
|
});
|
|
exports.animationStarted = animationStarted;
|
|
const docStyle = typeof document === "undefined" ? null : document.documentElement.style;
|
|
exports.docStyle = docStyle;
|
|
|
|
function clamp(v, min, max) {
|
|
return Math.min(Math.max(v, min), max);
|
|
}
|
|
|
|
class ProgressBar {
|
|
#classList = null;
|
|
#percent = 0;
|
|
#visible = true;
|
|
|
|
constructor(id) {
|
|
if (arguments.length > 1) {
|
|
throw new Error("ProgressBar no longer accepts any additional options, " + "please use CSS rules to modify its appearance instead.");
|
|
}
|
|
|
|
const bar = document.getElementById(id);
|
|
this.#classList = bar.classList;
|
|
}
|
|
|
|
get percent() {
|
|
return this.#percent;
|
|
}
|
|
|
|
set percent(val) {
|
|
this.#percent = clamp(val, 0, 100);
|
|
|
|
if (isNaN(val)) {
|
|
this.#classList.add("indeterminate");
|
|
return;
|
|
}
|
|
|
|
this.#classList.remove("indeterminate");
|
|
docStyle.setProperty("--progressBar-percent", `${this.#percent}%`);
|
|
}
|
|
|
|
setWidth(viewer) {
|
|
if (!viewer) {
|
|
return;
|
|
}
|
|
|
|
const container = viewer.parentNode;
|
|
const scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
|
|
|
|
if (scrollbarWidth > 0) {
|
|
docStyle.setProperty("--progressBar-end-offset", `${scrollbarWidth}px`);
|
|
}
|
|
}
|
|
|
|
hide() {
|
|
if (!this.#visible) {
|
|
return;
|
|
}
|
|
|
|
this.#visible = false;
|
|
this.#classList.add("hidden");
|
|
}
|
|
|
|
show() {
|
|
if (this.#visible) {
|
|
return;
|
|
}
|
|
|
|
this.#visible = true;
|
|
this.#classList.remove("hidden");
|
|
}
|
|
|
|
}
|
|
|
|
exports.ProgressBar = ProgressBar;
|
|
|
|
function getActiveOrFocusedElement() {
|
|
let curRoot = document;
|
|
let curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
|
|
|
|
while (curActiveOrFocused?.shadowRoot) {
|
|
curRoot = curActiveOrFocused.shadowRoot;
|
|
curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
|
|
}
|
|
|
|
return curActiveOrFocused;
|
|
}
|
|
|
|
function apiPageLayoutToViewerModes(layout) {
|
|
let scrollMode = ScrollMode.VERTICAL,
|
|
spreadMode = SpreadMode.NONE;
|
|
|
|
switch (layout) {
|
|
case "SinglePage":
|
|
scrollMode = ScrollMode.PAGE;
|
|
break;
|
|
|
|
case "OneColumn":
|
|
break;
|
|
|
|
case "TwoPageLeft":
|
|
scrollMode = ScrollMode.PAGE;
|
|
|
|
case "TwoColumnLeft":
|
|
spreadMode = SpreadMode.ODD;
|
|
break;
|
|
|
|
case "TwoPageRight":
|
|
scrollMode = ScrollMode.PAGE;
|
|
|
|
case "TwoColumnRight":
|
|
spreadMode = SpreadMode.EVEN;
|
|
break;
|
|
}
|
|
|
|
return {
|
|
scrollMode,
|
|
spreadMode
|
|
};
|
|
}
|
|
|
|
function apiPageModeToSidebarView(mode) {
|
|
switch (mode) {
|
|
case "UseNone":
|
|
return SidebarView.NONE;
|
|
|
|
case "UseThumbs":
|
|
return SidebarView.THUMBS;
|
|
|
|
case "UseOutlines":
|
|
return SidebarView.OUTLINE;
|
|
|
|
case "UseAttachments":
|
|
return SidebarView.ATTACHMENTS;
|
|
|
|
case "UseOC":
|
|
return SidebarView.LAYERS;
|
|
}
|
|
|
|
return SidebarView.NONE;
|
|
} |